@brainervirus/workit-pi 8.24.0 → 8.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/workit.js CHANGED
@@ -8965,6 +8965,26 @@ function resolveRef(cwd, ref) {
8965
8965
  return null;
8966
8966
  return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
8967
8967
  }
8968
+ function originHeadRef(cwd) {
8969
+ return line(git(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]));
8970
+ }
8971
+ function ownChangeDrift(cwd, range) {
8972
+ const before = diffRaw(cwd, range.fromFork, range.from);
8973
+ const after = diffRaw(cwd, range.toFork, range.to);
8974
+ if (!before || !after)
8975
+ return null;
8976
+ const judged = new Map(before.map((entry) => [entry.path, entry]));
8977
+ const current = new Map(after.map((entry) => [entry.path, entry]));
8978
+ const same = (a, b) => a !== undefined && b !== undefined && a.oldMode === b.oldMode && a.oldBlob === b.oldBlob && a.newMode === b.newMode && a.newBlob === b.newBlob;
8979
+ return [...new Set([...judged.keys(), ...current.keys()])].filter((file) => !same(judged.get(file), current.get(file))).toSorted();
8980
+ }
8981
+ function sameOwnChange(cwd, range) {
8982
+ const fromFork = mergeBase(cwd, range.fromBase, range.from);
8983
+ const toFork = mergeBase(cwd, range.toBase, range.to);
8984
+ if (!fromFork || !toFork)
8985
+ return false;
8986
+ return ownChangeDrift(cwd, { from: range.from, to: range.to, fromFork, toFork })?.length === 0;
8987
+ }
8968
8988
  var LANE_DIFF = [
8969
8989
  "-c",
8970
8990
  "core.quotePath=false",
@@ -12860,9 +12880,9 @@ var workerBlocksTransition = (transition, state) => transition === "pause" ? fal
12860
12880
 
12861
12881
  // packages/workit-core/src/core/task-evaluation.ts
12862
12882
  import { createHash as createHash12 } from "node:crypto";
12863
- import * as fs15 from "node:fs";
12864
- import { spawnSync as spawnSync11 } from "node:child_process";
12865
- import path19 from "node:path";
12883
+ import * as fs14 from "node:fs";
12884
+ import { spawnSync as spawnSync10 } from "node:child_process";
12885
+ import path18 from "node:path";
12866
12886
 
12867
12887
  // packages/workit-core/src/check-config.ts
12868
12888
  import { spawnSync as spawnSync8 } from "node:child_process";
@@ -13093,125 +13113,20 @@ function checkCommand(args, version, onPath = workitAvailable()) {
13093
13113
 
13094
13114
  // packages/workit-core/src/core/policy/derive.ts
13095
13115
  import { existsSync as existsSync5 } from "node:fs";
13096
- import path18 from "node:path";
13116
+ import path17 from "node:path";
13097
13117
 
13098
13118
  // packages/workit-core/src/ledger.ts
13099
13119
  import { spawnSync as spawnSync9 } from "node:child_process";
13100
13120
  import { createHash as createHash11, randomBytes as randomBytes3 } from "node:crypto";
13101
13121
  import fs13 from "node:fs";
13102
- import path15 from "node:path";
13122
+ import path16 from "node:path";
13103
13123
 
13104
- // packages/workit-core/src/hooks/standing-signal.ts
13105
- import { createHash as createHash10 } from "node:crypto";
13106
- import fs12 from "node:fs";
13124
+ // packages/workit-core/src/core/policy/lane.ts
13125
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
13107
13126
  import path14 from "node:path";
13108
- var gitStoreDir = (cwd) => {
13109
- try {
13110
- for (let dir = path14.resolve(cwd);; dir = path14.dirname(dir)) {
13111
- const dotGit = path14.join(dir, ".git");
13112
- if (fs12.existsSync(dotGit)) {
13113
- let gitDir = dotGit;
13114
- if (fs12.statSync(dotGit).isFile()) {
13115
- const target = /^gitdir:\s*(.+)$/mu.exec(fs12.readFileSync(dotGit, "utf8"))?.[1]?.trim();
13116
- if (!target)
13117
- return null;
13118
- gitDir = path14.resolve(dir, target);
13119
- const common = path14.join(gitDir, "commondir");
13120
- if (fs12.existsSync(common))
13121
- gitDir = path14.resolve(gitDir, fs12.readFileSync(common, "utf8").trim());
13122
- }
13123
- return path14.join(gitDir, "workit");
13124
- }
13125
- if (path14.dirname(dir) === dir)
13126
- return null;
13127
- }
13128
- } catch {
13129
- return null;
13130
- }
13131
- };
13132
- var standingRevFile = (store) => path14.join(store, "standing", "rev");
13133
- var standingSeenFile = (store, session, agentId) => path14.join(store, "standing", "seen", `${createHash10("sha256").update(`${session}\x00${agentId ?? ""}`).digest("hex").slice(0, 32)}.json`);
13134
- var readStandingRev = (store) => {
13135
- try {
13136
- return fs12.readFileSync(standingRevFile(store), "utf8").trim() || null;
13137
- } catch {
13138
- return null;
13139
- }
13140
- };
13141
- var readStandingSeen = (file) => {
13142
- try {
13143
- const value = JSON.parse(fs12.readFileSync(file, "utf8"));
13144
- return {
13145
- rev: typeof value.rev === "string" ? value.rev : null,
13146
- ids: Array.isArray(value.ids) ? value.ids.filter((id) => typeof id === "string") : []
13147
- };
13148
- } catch {
13149
- return { rev: null, ids: [] };
13150
- }
13151
- };
13152
- var writeStandingSeen = (file, seen) => {
13153
- try {
13154
- fs12.mkdirSync(path14.dirname(file), { recursive: true });
13155
- fs12.writeFileSync(file, JSON.stringify(seen));
13156
- } catch {}
13157
- };
13158
13127
 
13159
- // packages/workit-core/src/ledger.ts
13160
- var LEDGER_VERSION = 1;
13161
- var MAX_LINE_BYTES = 4096;
13162
- var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
13163
- var FAILING = new Set(["failed", "blocked"]);
13164
- var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
13165
- var PR_ROW_TYPES = new Set([
13166
- "pr.created",
13167
- "pr.status",
13168
- "pr.merged",
13169
- "pr.linked"
13170
- ]);
13171
- var err = (code, error, unblock) => ({
13172
- ok: false,
13173
- code,
13174
- error,
13175
- ...unblock ? { unblock } : {}
13176
- });
13177
- var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
13178
- const run = spawnSync9("git", args, {
13179
- cwd,
13180
- encoding: "utf8",
13181
- timeout: timeoutMs,
13182
- killSignal: "SIGKILL",
13183
- windowsHide: true,
13184
- maxBuffer: 256 * 1024 * 1024,
13185
- env: {
13186
- ...process.env,
13187
- GIT_TERMINAL_PROMPT: "0",
13188
- GIT_OPTIONAL_LOCKS: "0",
13189
- LC_ALL: "C",
13190
- LANGUAGE: "C"
13191
- }
13192
- });
13193
- return {
13194
- ok: run.status === 0,
13195
- stdout: run.stdout ?? "",
13196
- stderr: run.error ? String(run.error.message) : run.stderr ?? ""
13197
- };
13198
- };
13199
- var git4 = (cwd, args) => {
13200
- const run = gitRun2(cwd, args);
13201
- const value = run.ok ? run.stdout.trim() : "";
13202
- return value ? value : null;
13203
- };
13128
+ // packages/workit-core/src/git/default-base.ts
13204
13129
  var safeRef = (value) => value.length > 0 && !value.startsWith("-");
13205
- function resolveCommit(cwd, ref) {
13206
- if (!safeRef(ref))
13207
- return null;
13208
- return git4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
13209
- }
13210
- function branchHead(cwd, branch) {
13211
- if (!safeRef(branch))
13212
- return null;
13213
- return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
13214
- }
13215
13130
  var trackTrunk = (cwd) => {
13216
13131
  try {
13217
13132
  const resolved = vcsConfig("resolve", cwd);
@@ -13227,11 +13142,11 @@ function defaultBase(cwd) {
13227
13142
  const trunk = trackTrunk(cwd);
13228
13143
  if (trunk) {
13229
13144
  for (const candidate of [`origin/${trunk}`, trunk])
13230
- if (resolveCommit(cwd, candidate))
13145
+ if (resolveRef(cwd, candidate))
13231
13146
  return candidate;
13232
13147
  }
13233
- const originHead = git4(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
13234
- if (originHead && resolveCommit(cwd, originHead))
13148
+ const originHead = originHeadRef(cwd);
13149
+ if (originHead && resolveRef(cwd, originHead))
13235
13150
  return originHead;
13236
13151
  for (const candidate of [
13237
13152
  "origin/main",
@@ -13241,1514 +13156,1691 @@ function defaultBase(cwd) {
13241
13156
  "origin/develop",
13242
13157
  "develop"
13243
13158
  ])
13244
- if (resolveCommit(cwd, candidate))
13159
+ if (resolveRef(cwd, candidate))
13245
13160
  return candidate;
13246
13161
  return null;
13247
13162
  }
13248
- function diffHash(cwd, base, head) {
13249
- if (!safeRef(base) || !safeRef(head))
13163
+
13164
+ // packages/workit-core/src/core/policy/attributes.ts
13165
+ function patternRegex(dir, pattern) {
13166
+ if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
13250
13167
  return null;
13251
- const diff = gitRun2(cwd, [
13252
- "diff",
13253
- "-U3",
13254
- "--diff-algorithm=myers",
13255
- "--indent-heuristic",
13256
- "--no-color",
13257
- "--no-ext-diff",
13258
- "--no-textconv",
13259
- "--no-renames",
13260
- "--full-index",
13261
- "--binary",
13262
- "--src-prefix=a/",
13263
- "--dst-prefix=b/",
13264
- `${base}...${head}`,
13265
- "--"
13266
- ], GIT_TIMEOUTS.worktree);
13267
- if (!diff.ok || !diff.stdout)
13168
+ const anchored = pattern.includes("/");
13169
+ const body = pattern.replace(/^\//u, "");
13170
+ let source = "";
13171
+ for (let index = 0;index < body.length; index += 1) {
13172
+ const char = body[index];
13173
+ if (char === "*" && body[index + 1] === "*") {
13174
+ const slash = body[index + 2] === "/";
13175
+ source += slash ? "(?:.*/)?" : ".*";
13176
+ index += slash ? 2 : 1;
13177
+ } else if (char === "*")
13178
+ source += "[^/]*";
13179
+ else if (char === "?")
13180
+ source += "[^/]";
13181
+ else if (char === "[") {
13182
+ const end = body.indexOf("]", index + 2);
13183
+ if (end < 0)
13184
+ return null;
13185
+ const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
13186
+ source += `[${set}]`;
13187
+ index = end;
13188
+ } else if (char === "\\" && index + 1 < body.length) {
13189
+ source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
13190
+ index += 1;
13191
+ } else
13192
+ source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
13193
+ }
13194
+ const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
13195
+ try {
13196
+ return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
13197
+ } catch {
13268
13198
  return null;
13269
- const hash = createHash11("sha256");
13270
- for (const line of diff.stdout.split(`
13199
+ }
13200
+ }
13201
+ function rulesOf(dir, text) {
13202
+ const out = [];
13203
+ for (const raw of text.split(`
13271
13204
  `)) {
13272
- if (line.startsWith("index "))
13205
+ const line = raw.trim();
13206
+ if (!line || line.startsWith("#") || line.startsWith("[attr]"))
13273
13207
  continue;
13274
- hash.update(line.startsWith("@@ ") ? "@@" : line);
13275
- hash.update(`
13276
- `);
13208
+ const [pattern, ...attributes] = line.split(/\s+/u);
13209
+ let generated = null;
13210
+ for (const attribute of attributes) {
13211
+ if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
13212
+ generated = true;
13213
+ else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
13214
+ generated = false;
13215
+ }
13216
+ if (generated === null)
13217
+ continue;
13218
+ const regex = patternRegex(dir, pattern);
13219
+ if (regex)
13220
+ out.push({ regex, generated });
13277
13221
  }
13278
- return `sha256:${hash.digest("hex")}`;
13279
- }
13280
- function storeRoot(cwd) {
13281
- const location = resolveStore(path15.resolve(cwd));
13282
- if (location instanceof Error)
13283
- 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");
13284
- return { ok: true, value: { root: location.dir, shared: location.shared } };
13222
+ return out;
13285
13223
  }
13286
- function ledgerPath(cwd) {
13287
- const root = storeRoot(cwd);
13288
- return root.ok ? { ok: true, value: path15.join(root.value.root, "ledger", "ledger.jsonl") } : root;
13224
+ function generatedByAttributes(files, paths) {
13225
+ const rules = [...files].map(([file, text]) => {
13226
+ const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
13227
+ return {
13228
+ depth: dir ? dir.split("/").length : 0,
13229
+ rules: rulesOf(dir, text)
13230
+ };
13231
+ }).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
13232
+ const out = new Set;
13233
+ for (const file of paths) {
13234
+ const last = rules.findLast((rule) => rule.regex.test(file));
13235
+ if (last?.generated)
13236
+ out.add(file);
13237
+ }
13238
+ return out;
13289
13239
  }
13290
- var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
13291
- var NETWORK_FS_TYPES = new Set([
13292
- 26985,
13293
- 20859,
13294
- 4266872130,
13295
- 4283649346,
13296
- 1702057286,
13297
- 1397113167,
13298
- 12805120,
13299
- 16914839,
13300
- 1196443219,
13301
- 198183888
13240
+
13241
+ // packages/workit-core/src/core/policy/substance.ts
13242
+ var SLASH_BLOCK = [["/*", "*/"]];
13243
+ var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
13244
+ var SYNTAXES = Object.fromEntries([
13245
+ ...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
13246
+ ...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
13247
+ ext,
13248
+ { ...C_LIKE, triple: true }
13249
+ ]),
13250
+ ["go", { ...C_LIKE, rawBacktick: true }],
13251
+ ...["css", "scss", "less"].map((ext) => [
13252
+ ext,
13253
+ { line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
13254
+ ]),
13255
+ ...["py", "pyi"].map((ext) => [
13256
+ ext,
13257
+ { line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
13258
+ ]),
13259
+ ["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
13260
+ ...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
13261
+ ext,
13262
+ { line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
13263
+ ])
13302
13264
  ]);
13303
- var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
13304
- var onNetworkFs = (dir) => {
13305
- if (process.platform !== "linux")
13306
- return false;
13307
- try {
13308
- return isNetworkFsType(fs13.statfsSync(dir).type);
13309
- } catch {
13310
- return false;
13311
- }
13312
- };
13313
- var LOCK_WAIT_MS = 5000;
13314
- var LOCK_STALE_MS = 30000;
13315
- var LOCK_OWNER = "owner";
13316
- var lockToken = (lock) => {
13317
- try {
13318
- return fs13.readFileSync(path15.join(lock, LOCK_OWNER), "utf8");
13319
- } catch {
13320
- return null;
13321
- }
13265
+ 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;
13266
+ var extensionOf = (file) => {
13267
+ const name = file.slice(file.lastIndexOf("/") + 1);
13268
+ const dot = name.lastIndexOf(".");
13269
+ return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
13322
13270
  };
13323
- var lockAgeMs = (lock) => {
13324
- for (const target of [path15.join(lock, LOCK_OWNER), lock]) {
13325
- try {
13326
- return Date.now() - fs13.statSync(target).mtimeMs;
13327
- } catch {}
13271
+ var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
13272
+ var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
13273
+ var SPACE = /[ \t\r\f\v]/u;
13274
+ var WORD = /[\w$]/u;
13275
+ var NO_SPACE_AFTER = "([{,;";
13276
+ var NO_SPACE_BEFORE = ")]},;";
13277
+ var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
13278
+ var REGEX_KEYWORDS = new Set([
13279
+ "return",
13280
+ "typeof",
13281
+ "instanceof",
13282
+ "case",
13283
+ "do",
13284
+ "else",
13285
+ "in",
13286
+ "of",
13287
+ "new",
13288
+ "delete",
13289
+ "void",
13290
+ "throw",
13291
+ "yield",
13292
+ "await"
13293
+ ]);
13294
+ function substanceOf(file, text) {
13295
+ const syntax = syntaxOf(file);
13296
+ if (!syntax)
13297
+ return plainSubstance(text);
13298
+ const markup = syntax.block.some(([open]) => open === "<!--");
13299
+ if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
13300
+ return plainSubstance(text);
13301
+ return lex(syntax, text);
13302
+ }
13303
+ function plainSubstance(text) {
13304
+ return text.split(`
13305
+ `).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
13306
+ `);
13307
+ }
13308
+ function lex(syntax, text) {
13309
+ const collapse = !syntax.indented;
13310
+ let out = "";
13311
+ let lineStart = 0;
13312
+ let kept = 0;
13313
+ let pendingSpace = false;
13314
+ const frames = [{ kind: "code", braces: 0 }];
13315
+ let index = 0;
13316
+ if (text.startsWith("#!")) {
13317
+ const end = text.indexOf(`
13318
+ `);
13319
+ out = end < 0 ? text : text.slice(0, end);
13320
+ kept = out.length;
13321
+ index = end < 0 ? text.length : end;
13328
13322
  }
13329
- return null;
13330
- };
13331
- var ledgerLock = {
13332
- acquire(lock) {
13333
- try {
13334
- fs13.mkdirSync(lock);
13335
- } catch (error) {
13336
- if (error.code === "EEXIST")
13337
- return null;
13338
- throw error;
13339
- }
13340
- const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
13341
- fs13.writeFileSync(path15.join(lock, LOCK_OWNER), token);
13342
- return token;
13343
- },
13344
- release(lock, token) {
13345
- if (lockToken(lock) !== token)
13346
- return false;
13347
- const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
13348
- try {
13349
- fs13.renameSync(lock, tomb);
13350
- } catch {
13351
- return false;
13323
+ const emitCode = (chunk) => {
13324
+ if (collapse && pendingSpace && out.length > lineStart) {
13325
+ const before = out[out.length - 1];
13326
+ if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
13327
+ out += " ";
13352
13328
  }
13353
- if (lockToken(tomb) !== token) {
13354
- try {
13355
- fs13.renameSync(tomb, lock);
13356
- } catch {}
13357
- return false;
13329
+ pendingSpace = false;
13330
+ out += chunk;
13331
+ };
13332
+ const emitVerbatim = (chunk) => {
13333
+ emitCode(chunk);
13334
+ kept = out.length;
13335
+ };
13336
+ const newline = () => {
13337
+ if (!collapse) {
13338
+ let end = out.length;
13339
+ while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
13340
+ end -= 1;
13341
+ out = out.slice(0, end);
13358
13342
  }
13359
- fs13.rmSync(tomb, { recursive: true, force: true });
13360
- return true;
13361
- },
13362
- token: lockToken,
13363
- reap(lock, observed) {
13364
- const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
13365
- try {
13366
- fs13.renameSync(lock, tomb);
13367
- } catch {
13368
- return false;
13343
+ pendingSpace = false;
13344
+ if (out.length > lineStart) {
13345
+ out += `
13346
+ `;
13347
+ lineStart = out.length;
13369
13348
  }
13370
- if (lockToken(tomb) === observed) {
13371
- fs13.rmSync(tomb, { recursive: true, force: true });
13349
+ };
13350
+ const regexAllowed = () => {
13351
+ let end = out.length;
13352
+ while (end > 0 && /\s/u.test(out[end - 1]))
13353
+ end -= 1;
13354
+ if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
13372
13355
  return true;
13373
- }
13374
- try {
13375
- fs13.renameSync(tomb, lock);
13376
- } catch {
13377
- fs13.rmSync(tomb, { recursive: true, force: true });
13356
+ let start = end;
13357
+ while (start > 0 && WORD.test(out[start - 1]))
13358
+ start -= 1;
13359
+ return REGEX_KEYWORDS.has(out.slice(start, end));
13360
+ };
13361
+ const scanString = (quote, escapes, multiline) => {
13362
+ const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
13363
+ const fstring = syntax.fstring === true && /f/iu.test(prefix);
13364
+ let end = index + quote.length;
13365
+ while (end < text.length) {
13366
+ const char = text[end];
13367
+ if (escapes && char === "\\") {
13368
+ end += 2;
13369
+ continue;
13370
+ }
13371
+ if (text.startsWith(quote, end)) {
13372
+ if (fstring && unbalanced(text.slice(index + quote.length, end)))
13373
+ return false;
13374
+ emitVerbatim(text.slice(index, end + quote.length));
13375
+ index = end + quote.length;
13376
+ return true;
13377
+ }
13378
+ if (char === `
13379
+ ` && !multiline)
13380
+ return false;
13381
+ end += 1;
13378
13382
  }
13379
13383
  return false;
13380
- }
13381
- };
13382
- function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
13383
- const lock = `${file}.lock`;
13384
- const deadline = Date.now() + waitMs;
13385
- let token = null;
13386
- for (;; ) {
13387
- token = ledgerLock.acquire(lock);
13388
- if (token)
13389
- break;
13390
- const observed = lockToken(lock);
13391
- const age = lockAgeMs(lock);
13392
- if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
13384
+ };
13385
+ while (index < text.length) {
13386
+ const frame = frames[frames.length - 1];
13387
+ const char = text[index];
13388
+ if (frame.kind === "template") {
13389
+ if (char === "\\") {
13390
+ emitVerbatim(text.slice(index, index + 2));
13391
+ index += 2;
13392
+ } else if (char === "`") {
13393
+ emitVerbatim(char);
13394
+ frames.pop();
13395
+ index += 1;
13396
+ } else if (text.startsWith("${", index)) {
13397
+ emitVerbatim("${");
13398
+ frames.push({ kind: "code", braces: 0 });
13399
+ index += 2;
13400
+ } else {
13401
+ out += char;
13402
+ kept = out.length;
13403
+ index += 1;
13404
+ }
13393
13405
  continue;
13394
- if (Date.now() >= deadline)
13395
- return null;
13396
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
13397
- }
13398
- try {
13399
- return fn();
13400
- } finally {
13401
- ledgerLock.release(lock, token);
13402
- }
13403
- }
13404
- var sha2562 = (text) => createHash11("sha256").update(text).digest("hex");
13405
- var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
13406
- ${body}`);
13407
- var TAIL_BYTES = 64 * 1024;
13408
- var lastLine = (fd, size) => {
13409
- for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
13410
- const buffer = Buffer.alloc(span);
13411
- fs13.readSync(fd, buffer, 0, span, size - span);
13412
- const end = buffer.lastIndexOf(10);
13413
- if (end < 0) {
13414
- if (span === size)
13415
- return null;
13406
+ }
13407
+ if (char === `
13408
+ `) {
13409
+ newline();
13410
+ index += 1;
13416
13411
  continue;
13417
13412
  }
13418
- let stop = end;
13419
- let text = "";
13420
- while (stop >= 0) {
13421
- const start = buffer.lastIndexOf(10, stop - 1);
13422
- text = buffer.toString("utf8", start + 1, stop).trim();
13423
- if (text) {
13424
- if (start >= 0 || span === size)
13425
- return text;
13426
- break;
13427
- }
13428
- if (start < 0) {
13429
- if (span === size)
13430
- return null;
13431
- break;
13432
- }
13433
- stop = start;
13413
+ if (SPACE.test(char)) {
13414
+ if (collapse)
13415
+ pendingSpace = true;
13416
+ else
13417
+ out += char;
13418
+ index += 1;
13419
+ continue;
13434
13420
  }
13435
- if (span === size)
13436
- return text || null;
13437
- }
13438
- return null;
13439
- };
13440
- var tighten = (fd) => {
13441
- try {
13442
- if ((fs13.fstatSync(fd).mode & 63) !== 0)
13443
- fs13.fchmodSync(fd, 384);
13444
- } catch {}
13445
- };
13446
- function appendLine(file, build, options) {
13447
- const write = () => {
13448
- const fd = fs13.openSync(file, "a+", 384);
13449
- try {
13450
- if (process.platform !== "win32")
13451
- tighten(fd);
13452
- const size = fs13.fstatSync(fd).size;
13453
- let fence = "";
13454
- if (size > 0) {
13455
- const last = Buffer.alloc(1);
13456
- fs13.readSync(fd, last, 0, 1, size - 1);
13457
- if (last[0] !== 10)
13458
- fence = `
13459
- `;
13421
+ const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
13422
+ if (lineOpener) {
13423
+ const end = text.indexOf(`
13424
+ `, index);
13425
+ const comment = text.slice(index, end < 0 ? text.length : end);
13426
+ if (DIRECTIVE.test(comment.slice(lineOpener.length)))
13427
+ emitVerbatim(comment);
13428
+ else
13429
+ pendingSpace = true;
13430
+ index = end < 0 ? text.length : end;
13431
+ continue;
13432
+ }
13433
+ const block = syntax.block.find(([open]) => text.startsWith(open, index));
13434
+ if (block) {
13435
+ const end = text.indexOf(block[1], index + block[0].length);
13436
+ if (end < 0)
13437
+ return null;
13438
+ const comment = text.slice(index, end + block[1].length);
13439
+ if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
13440
+ emitVerbatim(comment);
13441
+ else
13442
+ pendingSpace = true;
13443
+ if (comment.includes(`
13444
+ `))
13445
+ newline();
13446
+ index = end + block[1].length;
13447
+ continue;
13448
+ }
13449
+ if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
13450
+ if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
13451
+ return null;
13452
+ continue;
13453
+ }
13454
+ if (syntax.quotes.includes(char)) {
13455
+ if (!scanString(char, true, Boolean(syntax.multiline)))
13456
+ return null;
13457
+ continue;
13458
+ }
13459
+ if (syntax.literal?.includes(char)) {
13460
+ if (!scanString(char, false, false))
13461
+ return null;
13462
+ continue;
13463
+ }
13464
+ if (syntax.rawBacktick && char === "`") {
13465
+ if (!scanString("`", false, true))
13466
+ return null;
13467
+ continue;
13468
+ }
13469
+ if (syntax.js && char === "`") {
13470
+ emitVerbatim(char);
13471
+ frames.push({ kind: "template" });
13472
+ index += 1;
13473
+ continue;
13474
+ }
13475
+ if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
13476
+ return null;
13477
+ if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
13478
+ const lineEnd = text.indexOf(`
13479
+ `, index);
13480
+ if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
13481
+ return null;
13482
+ }
13483
+ if (syntax.js && char === "/" && regexAllowed()) {
13484
+ let end = index + 1;
13485
+ let inClass = false;
13486
+ while (end < text.length && (inClass || text[end] !== "/")) {
13487
+ if (text[end] === `
13488
+ `)
13489
+ return null;
13490
+ if (text[end] === "\\")
13491
+ end += 1;
13492
+ else if (text[end] === "[")
13493
+ inClass = true;
13494
+ else if (text[end] === "]")
13495
+ inClass = false;
13496
+ end += 1;
13460
13497
  }
13461
- const line = build(lastLine(fd, size));
13462
- if (!line.ok)
13463
- return line;
13464
- const bytes = Buffer.from(fence + line.value);
13465
- const written = fs13.writeSync(fd, bytes);
13466
- if (written !== bytes.length)
13467
- return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
13468
- return { ok: true, value: undefined };
13469
- } finally {
13470
- fs13.closeSync(fd);
13498
+ if (end >= text.length)
13499
+ return null;
13500
+ emitVerbatim(text.slice(index, end + 1));
13501
+ index = end + 1;
13502
+ continue;
13471
13503
  }
13472
- };
13473
- try {
13474
- fs13.mkdirSync(path15.dirname(file), { recursive: true });
13475
- if (!options.forceLock && !onNetworkFs(path15.dirname(file)))
13476
- return write();
13477
- const locked = withLedgerLock(file, write);
13478
- return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
13479
- } catch (error) {
13480
- return err("unavailable", `cannot append to ${file}: ${error.message}`);
13504
+ if (frames.length > 1 && frame.kind === "code") {
13505
+ if (char === "{")
13506
+ frame.braces += 1;
13507
+ else if (char === "}") {
13508
+ if (frame.braces === 0) {
13509
+ emitVerbatim(char);
13510
+ frames.pop();
13511
+ index += 1;
13512
+ continue;
13513
+ }
13514
+ frame.braces -= 1;
13515
+ }
13516
+ }
13517
+ emitCode(char);
13518
+ index += 1;
13481
13519
  }
13520
+ if (frames.length !== 1)
13521
+ return null;
13522
+ newline();
13523
+ return out;
13482
13524
  }
13483
- var sealed = (row, id, at, previous) => {
13484
- const { prevHash: _prev, rowHash: _row, ...fields } = row;
13485
- const prevHash = previous === null ? null : sha2562(previous);
13486
- const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
13487
- const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
13488
- return { full, line: `${JSON.stringify(full)}
13489
- ` };
13490
- };
13491
- function appendRaw(cwd, row, options) {
13492
- const file = ledgerPath(cwd);
13493
- if (!file.ok)
13494
- return file;
13495
- const id = newId2();
13496
- const at = (options.now ?? new Date).toISOString();
13497
- let full = null;
13498
- const written = appendLine(file.value, (previous) => {
13499
- const next = sealed(row, id, at, previous);
13500
- const bytes = Buffer.byteLength(next.line);
13501
- if (bytes > MAX_LINE_BYTES)
13502
- return err("invalid_input", `ledger row is ${bytes} bytes; the limit is ${MAX_LINE_BYTES}. Shorten the text and pass details by --ref <path|url>`);
13503
- full = next.full;
13504
- return { ok: true, value: next.line };
13505
- }, options);
13506
- return written.ok ? { ok: true, value: full } : written;
13525
+ function unbalanced(body) {
13526
+ let depth = 0;
13527
+ for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
13528
+ if (part === "{")
13529
+ depth += 1;
13530
+ else if (part === "}")
13531
+ depth = Math.max(0, depth - 1);
13532
+ }
13533
+ return depth > 0;
13507
13534
  }
13508
- function observedRowBytes(row) {
13509
- const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
13510
- return Buffer.byteLength(sample.line);
13535
+ function hasOpaqueLineBreak(text) {
13536
+ return /\r(?!\n)|[\u2028\u2029]/u.test(text);
13511
13537
  }
13512
- function appendObserved(cwd, row, options = {}) {
13513
- return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
13538
+ var decoder = new TextDecoder("utf-8", { fatal: true });
13539
+ function textOf(bytes) {
13540
+ if (bytes.includes(0))
13541
+ return null;
13542
+ try {
13543
+ return decoder.decode(bytes);
13544
+ } catch {
13545
+ return null;
13546
+ }
13514
13547
  }
13515
- function appendHookObserved(cwd, row) {
13516
- return appendRaw(cwd, { ...row, observer: "host_hook" }, {});
13548
+ function cosmeticChange(file, before, after) {
13549
+ const left = textOf(before);
13550
+ const right = textOf(after);
13551
+ if (left === null || right === null)
13552
+ return false;
13553
+ if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
13554
+ return false;
13555
+ const a = substanceOf(file, left);
13556
+ return a !== null && a === substanceOf(file, right);
13517
13557
  }
13518
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
13519
- var str2 = (value) => typeof value === "string" ? value : null;
13520
- var normalizeActor = (value) => {
13521
- const actor = isRecord3(value) ? value : {};
13522
- return {
13523
- host: str2(actor.host) ?? "unknown",
13524
- session: str2(actor.session),
13525
- agentId: str2(actor.agentId)
13526
- };
13558
+
13559
+ // packages/workit-core/src/core/policy/lane.ts
13560
+ var LANE_THRESHOLDS = {
13561
+ selfMaxLines: 100,
13562
+ selfMaxFiles: 5,
13563
+ fullMinLines: 500
13527
13564
  };
13528
- function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
13529
- if (by.type !== target.type)
13530
- return `a ${by.type} row cannot supersede a ${target.type} row`;
13531
- if (!by.actor.session || by.actor.session !== target.actor.session)
13532
- return "only the session that wrote a row can supersede it";
13533
- if (!onBranch(target))
13534
- return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
13535
- if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
13536
- return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
13537
- if (by.type === "verdict" && by.self === true && target.self !== true)
13538
- return "a self verdict cannot replace an independent verdict";
13539
- return null;
13540
- }
13541
- function readLedger(cwd, options = {}) {
13542
- const file = ledgerPath(cwd);
13543
- if (!file.ok)
13544
- return file;
13545
- let text = "";
13546
- let stat = null;
13547
- try {
13548
- stat = fs13.statSync(file.value);
13549
- text = fs13.readFileSync(file.value, "utf8");
13550
- } catch (error) {
13551
- if (error.code === "ENOENT")
13552
- return {
13553
- ok: true,
13554
- value: {
13555
- path: file.value,
13556
- rows: [],
13557
- skipped: 0,
13558
- integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
13559
- }
13560
- };
13561
- return err("unavailable", `cannot read ${file.value}: ${error.message}`);
13562
- }
13563
- const rows = [];
13564
- let skipped = 0;
13565
- const integrity = { chained: 0, legacy: 0, unverified: [] };
13566
- const earlier = new Set([null]);
13567
- let chainStarted = false;
13568
- const ids = new Set;
13569
- for (const raw of text.split(`
13570
- `)) {
13571
- const trimmed = raw.trim();
13572
- if (!trimmed)
13573
- continue;
13574
- const linked = (prevHash) => earlier.has(prevHash);
13575
- if (options.integrity)
13576
- earlier.add(sha2562(trimmed));
13577
- let parsed;
13578
- try {
13579
- parsed = JSON.parse(trimmed);
13580
- } catch {
13581
- skipped += 1;
13582
- continue;
13583
- }
13584
- if (!isRecord3(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
13585
- skipped += 1;
13586
- continue;
13587
- }
13588
- let rowIntegrity = options.integrity ? "verified" : undefined;
13589
- let reason = null;
13590
- if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
13591
- chainStarted = true;
13592
- const { rowHash, ...body } = parsed;
13593
- const prevHash = str2(parsed.prevHash);
13594
- if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
13595
- reason = "hash_mismatch";
13596
- else if (!linked(prevHash))
13597
- reason = "broken_link";
13598
- } else if (chainStarted)
13599
- reason = "unsigned";
13600
- else
13601
- rowIntegrity = "legacy";
13602
- if (options.integrity && !reason && ids.has(parsed.id))
13603
- reason = "duplicate_id";
13604
- if (options.integrity)
13605
- ids.add(parsed.id);
13606
- if (reason)
13607
- rowIntegrity = "unverified";
13608
- const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
13609
- const row = {
13610
- ...parsed,
13611
- v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
13612
- id: parsed.id,
13613
- at: parsed.at,
13614
- type: parsed.type,
13615
- actor: normalizeActor(parsed.actor),
13616
- branch: str2(parsed.branch),
13617
- head: str2(parsed.head),
13618
- base: str2(parsed.base),
13619
- baseSha: str2(parsed.baseSha),
13620
- patchId: str2(parsed.patchId),
13621
- diffHash: str2(parsed.diffHash),
13622
- tree: str2(parsed.tree),
13623
- dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
13624
- ...pr === undefined ? {} : { pr },
13625
- ...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
13626
- seq: rows.length + 1,
13627
- superseded: false,
13628
- supersedeIgnored: false,
13629
- ...rowIntegrity ? { integrity: rowIntegrity } : {},
13630
- ...reason ? { integrityReason: reason } : {}
13631
- };
13632
- rows.push(row);
13633
- if (!options.integrity)
13634
- continue;
13635
- if (rowIntegrity === "verified")
13636
- integrity.chained += 1;
13637
- else if (rowIntegrity === "legacy")
13638
- integrity.legacy += 1;
13639
- else if (reason)
13640
- integrity.unverified.push({
13641
- seq: row.seq,
13642
- id: row.id,
13643
- type: row.type,
13644
- branch: row.branch,
13645
- reason
13646
- });
13647
- }
13648
- const scopes = new Map;
13649
- const scopeOf = (branch) => {
13650
- let scope = scopes.get(branch);
13651
- if (!scope)
13652
- scopes.set(branch, scope = branchScope(cwd, branch));
13653
- return scope;
13654
- };
13655
- const byId = new Map;
13656
- for (const row of rows) {
13657
- const target = row.supersedes ? byId.get(row.supersedes) : undefined;
13658
- if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
13659
- target.superseded = true;
13660
- else if (row.supersedes)
13661
- row.supersedeIgnored = true;
13662
- if (!byId.has(row.id))
13663
- byId.set(row.id, row);
13664
- }
13665
- if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
13666
- tamperCache.set(file.value, {
13667
- size: stat.size,
13668
- stamp: fileStamp(stat),
13669
- breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
13670
- });
13671
- return {
13672
- ok: true,
13673
- value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
13674
- };
13675
- }
13676
- var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
13677
- var tamperCache = new Map;
13678
- var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
13679
- function ledgerTampering(cwd) {
13680
- const file = ledgerPath(cwd);
13681
- if (!file.ok)
13682
- return [];
13683
- try {
13684
- const stat = fs13.statSync(file.value);
13685
- const cached = tamperCache.get(file.value);
13686
- if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
13687
- return cached.breaks;
13688
- } catch {
13689
- return [];
13690
- }
13691
- const read = readLedger(cwd, { integrity: true });
13692
- return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
13693
- }
13694
- function tamperedUnblock(cwd) {
13695
- const file = ledgerPath(cwd);
13696
- const where = file.ok ? file.value : "the ledger file";
13697
- return `workit ledger verify-integrity # lists the edited or missing rows; if the edit is understood, move ${where} aside (mv ${where} ${where}.tampered-<date>) and record the verdicts again`;
13698
- }
13699
- var tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`;
13700
- function verdictBasis(verdict, current) {
13701
- if (verdict.head && verdict.head === current.head)
13702
- return "fresh";
13703
- if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
13704
- return "carried";
13705
- return "stale";
13706
- }
13707
- var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
13708
- function trailerSessions(cwd, base, head) {
13709
- if (!base || !head || !safeRef(base) || !safeRef(head))
13710
- return [];
13711
- const out = git4(cwd, [
13712
- "log",
13713
- "--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
13714
- `${base}..${head}`
13715
- ]);
13716
- return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
13717
- }
13718
- var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
13719
- function branchScope(cwd, branch) {
13720
- const renamedAt = new Map;
13721
- let since = Number.NEGATIVE_INFINITY;
13722
- if (safeRef(branch)) {
13723
- const log = git4(cwd, [
13724
- "log",
13725
- "-g",
13726
- "--date=unix",
13727
- "--format=%gd%x09%gs",
13728
- `refs/heads/${branch}`,
13729
- "--"
13730
- ]);
13731
- for (const line of (log ?? "").split(`
13732
- `)) {
13733
- const [selector = "", subject = ""] = line.split("\t");
13734
- const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
13735
- if (!Number.isFinite(seconds))
13736
- continue;
13737
- since = seconds * 1000;
13738
- const match = RENAMED.exec(subject);
13739
- if (!match || match[1] === branch)
13740
- continue;
13741
- const until = (seconds + 1) * 1000;
13742
- renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
13743
- }
13744
- }
13745
- return (row) => {
13746
- if (row.branch === branch)
13747
- return true;
13748
- const until = row.branch === null ? undefined : renamedAt.get(row.branch);
13749
- if (until === undefined)
13750
- return false;
13751
- const at = Date.parse(row.at);
13752
- return at >= since && at <= until;
13753
- };
13754
- }
13755
- var renameAwareBranch = (scopeOf, branch) => {
13756
- return (target) => {
13757
- const own = typeof branch === "string" ? branch : null;
13758
- const theirs = typeof target.branch === "string" ? target.branch : null;
13759
- if (own === theirs)
13760
- return true;
13761
- if (own === null || theirs === null || typeof target.at !== "string")
13762
- return false;
13763
- return scopeOf(own)({ branch: theirs, at: target.at });
13764
- };
13765
- };
13766
- function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
13767
- const sessions = new Set;
13768
- for (const row of rows) {
13769
- if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
13770
- continue;
13771
- const session = str2(row.session) ?? row.actor.session;
13772
- if (session)
13773
- sessions.add(session);
13774
- }
13775
- for (const session of trailerSessions(cwd, range.base, range.head))
13776
- sessions.add(session);
13777
- return sessions;
13778
- }
13779
- var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
13780
- var authoredBy = (authors, session) => {
13781
- if (authors.has(session))
13782
- return true;
13783
- for (const author of authors)
13784
- if (derivesFrom(author, session) || derivesFrom(session, author))
13785
- return true;
13786
- return false;
13787
- };
13788
- var TRUST_REASONS = new Set([
13789
- "unsigned",
13790
- "hash_mismatch",
13791
- "broken_link",
13792
- "duplicate_id",
13793
- "unissued_session",
13794
- "unbound_session"
13565
+ var RISK_TOKENS = new Set([
13566
+ "auth",
13567
+ "authn",
13568
+ "authz",
13569
+ "oauth",
13570
+ "security",
13571
+ "permission",
13572
+ "permissions",
13573
+ "secret",
13574
+ "secrets",
13575
+ "crypto",
13576
+ "gate",
13577
+ "gates",
13578
+ "policy",
13579
+ "policies",
13580
+ "hook",
13581
+ "hooks",
13582
+ "husky",
13583
+ "lefthook",
13584
+ "release",
13585
+ "releases",
13586
+ "migration",
13587
+ "migrations",
13588
+ "migrate",
13589
+ "workflows"
13795
13590
  ]);
13796
- var SHELL_VOUCHED_HOSTS = new Set([
13797
- "cli",
13798
- "opencode",
13799
- "pi",
13800
- "codex_cli",
13801
- "codex_desktop"
13591
+ var RISK_FILES = new Set([
13592
+ ".gitlab-ci.yml",
13593
+ "Jenkinsfile",
13594
+ "azure-pipelines.yml",
13595
+ ".pre-commit-config.yaml",
13596
+ ".workit/risk-paths"
13802
13597
  ]);
13803
- function sessionTrust(cwd, row) {
13804
- const session = row.actor.session;
13805
- if (!session || row.self === true)
13806
- return null;
13807
- const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
13808
- const host = row.actor.host;
13809
- const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
13810
- if (!issuedOnly)
13811
- return null;
13812
- const binding = issuedBinding(cwd, session);
13813
- if (binding === null)
13814
- return "unissued_session";
13815
- return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
13816
- }
13817
- function trustReasons(cwd, row) {
13818
- if (row.integrity === "unverified")
13819
- return [row.integrityReason ?? "unsigned"];
13820
- if (row.integrity !== "verified")
13821
- return ["unsigned"];
13822
- const session = sessionTrust(cwd, row);
13823
- return session ? [session] : [];
13824
- }
13825
- function withIntegrity(cwd, rows) {
13826
- if (rows.every((row) => row.integrity !== undefined))
13827
- return rows;
13828
- const read = readLedger(cwd, { integrity: true });
13829
- const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
13830
- return rows.map((row) => {
13831
- if (row.integrity !== undefined)
13832
- return row;
13833
- const fresh = bySeq.get(row.seq);
13834
- return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
13835
- });
13836
- }
13837
- var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
13838
- var independenceReasons = (row, authors) => {
13839
- const reasons = [];
13840
- if (!row.actor.session)
13841
- reasons.push("no_session");
13842
- else if (authoredBy(authors, row.actor.session))
13843
- reasons.push("author_session");
13844
- if (row.self === true && !reasons.includes("no_session"))
13845
- reasons.push("self");
13846
- return reasons;
13847
- };
13848
- function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
13849
- let effective = null;
13850
- for (const row of rows) {
13851
- if (!effective) {
13852
- effective = row;
13853
- continue;
13854
- }
13855
- if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
13856
- continue;
13857
- const rowIndependent = reasonsOf(row).length === 0;
13858
- const effIndependent = reasonsOf(effective).length === 0;
13859
- if (effIndependent && !rowIndependent && isCurrent(effective))
13860
- continue;
13861
- if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
13862
- continue;
13863
- effective = row;
13864
- }
13865
- return effective;
13866
- }
13867
- function restackCarries(rows, branch, from, to) {
13868
- if (!from || !to)
13869
- return false;
13870
- const edges = new Map;
13871
- for (const row of rows)
13872
- if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
13873
- edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
13874
- const seen = new Set([from]);
13875
- const queue = [from];
13876
- while (queue.length) {
13877
- const next = queue.shift();
13878
- if (next === to)
13879
- return true;
13880
- for (const target of edges.get(next) ?? [])
13881
- if (!seen.has(target)) {
13882
- seen.add(target);
13883
- queue.push(target);
13884
- }
13885
- }
13886
- return false;
13887
- }
13888
- function checkVerdicts(cwd, branch, ledgerRows) {
13889
- const rows = withIntegrity(cwd, ledgerRows);
13890
- const head = branchHead(cwd, branch);
13891
- const fallbackBase = defaultBase(cwd);
13892
- const scope = branchScope(cwd, branch);
13893
- const keyCache = new Map;
13894
- const keysAt = (base, at) => {
13895
- if (!base || !at)
13896
- return { patchId: null, diffHash: null };
13897
- const cacheKey = `${base}\x00${at}`;
13898
- let key = keyCache.get(cacheKey);
13899
- if (!key) {
13900
- key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
13901
- keyCache.set(cacheKey, key);
13902
- }
13903
- return key;
13904
- };
13905
- const keyFor = (base) => keysAt(base, head);
13906
- const basisOf = (row) => {
13907
- const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
13908
- return basis === "stale" && restackCarries(rows, branch, row.head, head) ? "carried" : basis;
13909
- };
13910
- const sameCode = (failed, row) => {
13911
- if (failed.head && failed.head === row.head)
13912
- return true;
13913
- const base = failed.base ?? fallbackBase;
13914
- const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
13915
- const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
13916
- return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
13917
- };
13918
- const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
13919
- const failureAuthorCache = new Map;
13920
- const failureAuthors = (row) => {
13921
- let set = failureAuthorCache.get(row.seq);
13922
- if (!set) {
13923
- set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
13924
- failureAuthorCache.set(row.seq, set);
13925
- }
13926
- return set;
13927
- };
13928
- const reasonCache = new Map;
13929
- const reasonsOf = (row) => {
13930
- let reasons = reasonCache.get(row);
13931
- if (!reasons) {
13932
- const failing = FAILING.has(String(row.result));
13933
- reasons = [
13934
- ...trustReasons(cwd, row),
13935
- ...independenceReasons(row, failing ? failureAuthors(row) : authors)
13936
- ];
13937
- reasonCache.set(row, reasons);
13938
- }
13939
- return reasons;
13940
- };
13941
- const retired = new Set;
13942
- for (const row of rows)
13943
- if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
13944
- retired.add(row.supersedes);
13945
- const byKind = new Map;
13946
- for (const row of rows)
13947
- if (isLiveVerdict(row, retired) && scope(row)) {
13948
- const kind = typeof row.kind === "string" ? row.kind : "review";
13949
- byKind.set(kind, [...byKind.get(kind) ?? [], row]);
13950
- }
13951
- const verdicts = [];
13952
- const warnings = [];
13953
- for (const [kind, list] of byKind) {
13954
- const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
13955
- if (!row)
13956
- continue;
13957
- const basis = basisOf(row);
13958
- const current = basis !== "stale";
13959
- const independence = reasonsOf(row);
13960
- const reasons = [
13961
- ...current ? [] : ["stale"],
13962
- ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
13963
- ...independence
13964
- ];
13965
- verdicts.push({
13966
- kind,
13967
- basis,
13968
- current,
13969
- independent: independence.length === 0,
13970
- accepted: reasons.length === 0,
13971
- reasons,
13972
- verdict: row
13973
- });
13974
- if (reasons.length === 0) {
13975
- for (const later of list)
13976
- if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
13977
- warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
13598
+ var RISK_NAMES = new Set([".gitattributes"]);
13599
+ var RISK_PATHS_FILE = ".workit/risk-paths";
13600
+ var LOCKFILES = new Set([
13601
+ "bun.lock",
13602
+ "bun.lockb",
13603
+ "package-lock.json",
13604
+ "npm-shrinkwrap.json",
13605
+ "yarn.lock",
13606
+ "pnpm-lock.yaml",
13607
+ "Cargo.lock",
13608
+ "go.sum",
13609
+ "poetry.lock",
13610
+ "uv.lock",
13611
+ "Pipfile.lock",
13612
+ "Gemfile.lock",
13613
+ "composer.lock"
13614
+ ]);
13615
+ var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc)$/iu;
13616
+ var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
13617
+ var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
13618
+ var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__)\/|\.(?:test|spec)\.[^/]+$|_test\.[^/]+$/iu;
13619
+ var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
13620
+ var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
13621
+ function kindOf(file) {
13622
+ if (file.binary)
13623
+ return "behavior";
13624
+ if (file.generated)
13625
+ return "generated";
13626
+ if (LOCKFILES.has(basename(file.path)))
13627
+ return "lockfile";
13628
+ if (isDoc(file.path))
13629
+ return "doc";
13630
+ if (file.cosmetic)
13631
+ return "cosmetic";
13632
+ return TEST_PATH.test(file.path) ? "test" : "behavior";
13633
+ }
13634
+ var globCache = new Map;
13635
+ function globMatch(pattern, file) {
13636
+ let regex = globCache.get(pattern);
13637
+ if (!regex) {
13638
+ let source = "";
13639
+ for (let index = 0;index < pattern.length; index += 1) {
13640
+ const char = pattern[index];
13641
+ if (char === "*" && pattern[index + 1] === "*") {
13642
+ source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
13643
+ index += pattern[index + 2] === "/" ? 2 : 1;
13644
+ } else if (char === "*")
13645
+ source += "[^/]*";
13646
+ else if (char === "?")
13647
+ source += "[^/]";
13648
+ else
13649
+ source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
13978
13650
  }
13651
+ regex = new RegExp(`^${source}$`, "u");
13652
+ globCache.set(pattern, regex);
13979
13653
  }
13980
- for (const row of rows)
13981
- if (row.integrity === "unverified" && scope(row))
13982
- warnings.push(`ledger row ${row.id} (${row.type}) on ${row.branch ?? branch} is unverified: the workit CLI did not write it or it changed afterwards (workit ledger verify-integrity)`);
13983
- const tampered = ledgerTampering(cwd);
13984
- if (tampered.length) {
13985
- warnings.push(tamperedWarning(cwd, tampered));
13986
- for (const entry of verdicts) {
13987
- entry.accepted = false;
13988
- entry.reasons = [...entry.reasons, "ledger_tampered"];
13989
- }
13654
+ return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
13655
+ }
13656
+ function isRiskPath(file, extra = []) {
13657
+ if (extra.some((pattern) => globMatch(pattern, file)))
13658
+ return true;
13659
+ if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
13660
+ return true;
13661
+ if (TEST_PATH.test(file) || isDoc(file))
13662
+ return false;
13663
+ 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)));
13664
+ }
13665
+ var parseRiskPaths = (text) => text.split(`
13666
+ `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
13667
+ function computeLane(input) {
13668
+ const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
13669
+ const risky = new Set(riskFiles);
13670
+ const kinds = input.files.map((file) => ({
13671
+ file,
13672
+ kind: risky.has(file.path) ? "behavior" : kindOf(file)
13673
+ }));
13674
+ const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
13675
+ const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
13676
+ const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
13677
+ const result = (lane, reasons) => ({
13678
+ lane,
13679
+ reasons,
13680
+ lines,
13681
+ files: behavior.length,
13682
+ riskFiles
13683
+ });
13684
+ const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
13685
+ if (input.riskTier === "high")
13686
+ return result("full", ["the task judgment records high risk"]);
13687
+ if (riskFiles.length)
13688
+ return result("full", [
13689
+ `touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
13690
+ ]);
13691
+ if (!behavior.length)
13692
+ return result("none", [
13693
+ input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
13694
+ ]);
13695
+ if (lines >= fullMinLines)
13696
+ return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
13697
+ if (lines > selfMaxLines)
13698
+ return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
13699
+ if (behavior.length > selfMaxFiles)
13700
+ return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
13701
+ if (opaque.length)
13702
+ return result("focused", [
13703
+ `a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
13704
+ ]);
13705
+ return result("self", [
13706
+ `small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
13707
+ ]);
13708
+ }
13709
+ function untrackedStat(cwd, file) {
13710
+ try {
13711
+ const bytes = readFileSync4(path14.join(cwd, file));
13712
+ if (bytes.includes(0))
13713
+ return { path: file, added: 0, deleted: 0, binary: true };
13714
+ const text = bytes.toString("utf8");
13715
+ if (hasOpaqueLineBreak(text))
13716
+ return { path: file, added: 0, deleted: 0, binary: true };
13717
+ const lines = text.split(`
13718
+ `).length - (text.endsWith(`
13719
+ `) ? 1 : 0);
13720
+ return { path: file, added: lines, deleted: 0 };
13721
+ } catch {
13722
+ return { path: file, added: 0, deleted: 0, binary: true };
13990
13723
  }
13991
- verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
13992
- const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
13993
- const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
13994
- const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
13995
- const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
13996
- accepted: false,
13997
- verdict: failing?.verdict ?? null,
13998
- reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
13999
- };
14000
- 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))));
14001
- const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
14002
- return {
14003
- branch,
14004
- head,
14005
- review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
14006
- selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
14007
- base: newestCurrent?.verdict.base ?? fallbackBase,
14008
- patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
14009
- current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
14010
- accepted,
14011
- verdicts,
14012
- authors: [...authors].toSorted(),
14013
- warnings
14014
- };
14015
13724
  }
14016
- var fromLead = (row, leads) => leads === null || row.actor.session !== null && leads.includes(row.actor.session);
14017
- function activeStanding(rows, fanout, leads = null) {
14018
- const active = [];
14019
- for (const row of rows) {
14020
- if (row.fanout !== fanout || !fromLead(row, leads))
14021
- continue;
14022
- if (row.type === "standing" && typeof row.what === "string" && row.what.trim()) {
14023
- if (!row.superseded)
14024
- active.push(row);
14025
- } else if (row.type === "standing.cleared") {
14026
- const target = str2(row.target);
14027
- if (target === null)
14028
- active.length = 0;
14029
- else {
14030
- const index = active.findIndex((order) => order.id === target);
14031
- if (index >= 0)
14032
- active.splice(index, 1);
13725
+ var REGULAR = new Set(["100644", "100755"]);
13726
+ var ABSENT = /^0+$/u;
13727
+ function cosmeticFiles(cwd, fork, head) {
13728
+ const raw = diffRaw(cwd, fork, head);
13729
+ if (!raw)
13730
+ return null;
13731
+ const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
13732
+ const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
13733
+ if (!blobs)
13734
+ return null;
13735
+ const out = new Set;
13736
+ const opaque = new Set;
13737
+ for (const entry of candidates) {
13738
+ const before = blobs.get(entry.oldBlob);
13739
+ let after = blobs.get(entry.newBlob);
13740
+ if (!after && head === null && ABSENT.test(entry.newBlob))
13741
+ try {
13742
+ after = readFileSync4(path14.join(cwd, entry.path));
13743
+ } catch {
13744
+ after = undefined;
14033
13745
  }
14034
- }
13746
+ if (before && after && cosmeticChange(entry.path, before, after))
13747
+ out.add(entry.path);
13748
+ else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
13749
+ opaque.add(entry.path);
14035
13750
  }
14036
- return active;
13751
+ return { cosmetic: out, opaque };
14037
13752
  }
14038
- var OBSERVED_ONLY = new Set([
14039
- "check",
14040
- "merge.unverified",
14041
- "policy.judged",
14042
- ...PR_ROW_TYPES
14043
- ]);
14044
-
14045
- // packages/workit-core/src/core/policy/lane.ts
14046
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
14047
- import path17 from "node:path";
14048
-
14049
- // packages/workit-core/src/core/pr-create.ts
14050
- import fs14 from "node:fs";
14051
- import path16 from "node:path";
14052
- import { spawnSync as spawnSync10 } from "node:child_process";
14053
- // packages/workit-core/src/forge/pr-body.ts
14054
- function parseGhIssue(value) {
14055
- const m = /issues\/(\d+)/.exec(value);
14056
- return m ? m[1] : String(value).trim().replace(/^#/, "");
13753
+ function generatedAt(cwd, fork, paths) {
13754
+ const files = treeFilesNamed(cwd, fork, ".gitattributes");
13755
+ const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
13756
+ if (!files || !blobs)
13757
+ return new Set;
13758
+ const texts = new Map;
13759
+ for (const [file, id] of files) {
13760
+ const bytes = blobs.get(id);
13761
+ if (bytes)
13762
+ texts.set(file, bytes.toString("utf8"));
13763
+ }
13764
+ return generatedByAttributes(texts, paths);
14057
13765
  }
14058
-
14059
- // packages/workit-core/src/core/pr-create.ts
14060
- function whichOnPath(tool) {
14061
- const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
14062
- for (const dir of (process.env.PATH ?? "").split(path16.delimiter)) {
14063
- if (!dir)
14064
- continue;
14065
- for (const name of names) {
14066
- const candidate = path16.join(dir, name);
14067
- try {
14068
- fs14.accessSync(candidate, fs14.constants.X_OK);
14069
- return candidate;
14070
- } catch {}
14071
- }
13766
+ var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
13767
+ function laneFiles(cwd, fork, head, attributesAt = fork) {
13768
+ const stats = diffNumstat(cwd, fork, head);
13769
+ const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
13770
+ if (!stats || !cosmetic)
13771
+ return null;
13772
+ const files = [
13773
+ ...stats.map((entry) => ({
13774
+ ...entry,
13775
+ ...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
13776
+ cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
13777
+ })),
13778
+ ...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
13779
+ ];
13780
+ const generated = generatedAt(cwd, attributesAt, files.map((entry) => entry.path));
13781
+ return files.map((entry) => ({
13782
+ ...entry,
13783
+ generated: generated.has(entry.path)
13784
+ }));
13785
+ }
13786
+ function driftFiles(cwd, range) {
13787
+ if (range.fromFork === range.toFork)
13788
+ return laneFiles(cwd, range.from, range.to, range.toFork);
13789
+ const drifted = ownChangeDrift(cwd, range);
13790
+ const stats = drifted ? diffNumstat(cwd, range.from, range.to) : null;
13791
+ if (!drifted || !stats)
13792
+ return null;
13793
+ const byPath = new Map(stats.map((entry) => [entry.path, entry]));
13794
+ const generated = generatedAt(cwd, range.toFork, drifted);
13795
+ return drifted.map((file) => ({
13796
+ path: file,
13797
+ added: byPath.get(file)?.added ?? 0,
13798
+ deleted: byPath.get(file)?.deleted ?? 0,
13799
+ ...byPath.get(file)?.binary ? { binary: true } : {},
13800
+ cosmetic: false,
13801
+ generated: generated.has(file)
13802
+ }));
13803
+ }
13804
+ function branchLane(cwd, options) {
13805
+ const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
13806
+ const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
13807
+ const head = options.workingTree ? null : options.head;
13808
+ const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
13809
+ const files = fork ? laneFiles(cwd, fork, head) : null;
13810
+ const riskTier = options.judgment?.riskTier ?? null;
13811
+ if (!fork || !files) {
13812
+ const high = riskTier === "high";
13813
+ return {
13814
+ lane: high ? "full" : "focused",
13815
+ reasons: [
13816
+ `the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
13817
+ ...high ? ["the task judgment records high risk"] : []
13818
+ ],
13819
+ lines: 0,
13820
+ files: 0,
13821
+ riskFiles: [],
13822
+ base,
13823
+ head: options.head
13824
+ };
14072
13825
  }
14073
- return null;
13826
+ const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
13827
+ return {
13828
+ ...computeLane({
13829
+ files,
13830
+ riskTier,
13831
+ riskPaths: [...repoRisk, ...options.riskPaths ?? []]
13832
+ }),
13833
+ base,
13834
+ head: options.head
13835
+ };
14074
13836
  }
14075
- var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
14076
- var pushRemote = (cwd) => {
14077
- const result = spawnSync10("git", ["remote", "get-url", "--push", "--all", "origin"], {
14078
- cwd,
14079
- encoding: "utf8"
14080
- });
14081
- if (result.status !== 0)
14082
- return null;
14083
- const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
14084
- return urls.length === 1 ? urls[0] : null;
14085
- };
14086
-
14087
- // packages/workit-core/src/forge/redact.ts
14088
- var KEY = String.raw`[A-Za-z0-9_.-]*?(?:token|secret|password|passwd|pwd|api[_-]?key|access[_-]?key|account[_-]?key|private[_-]?key|client[_-]?secret|credentials?|authorization|cookie|signature)[A-Za-z0-9_.-]*`;
14089
- var ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*["']?)((?:bearer|basic|token|digest)\s+)?([^\s"',;&]+)`, "giu");
14090
- var QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
14091
- var ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
13837
+ var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
14092
13838
 
14093
- // packages/workit-core/src/git/ops.ts
14094
- var COMMIT_TIMEOUT_MS = 10 * 60000;
13839
+ // packages/workit-core/src/core/policy/carry.ts
13840
+ var TEST_ONLY_KINDS = new Set(["doc", "cosmetic", "generated", "test"]);
13841
+ function verdictDrift(cwd, input) {
13842
+ const since = input.from;
13843
+ const stale = (reason, files = null, focused = false) => ({
13844
+ carries: false,
13845
+ since,
13846
+ files,
13847
+ focused,
13848
+ reason
13849
+ });
13850
+ const fromFork = mergeBase(cwd, input.fromBase, input.from);
13851
+ const toFork = mergeBase(cwd, input.toBase, input.to);
13852
+ const files = fromFork && toFork ? driftFiles(cwd, { from: input.from, to: input.to, fromFork, toFork }) : null;
13853
+ if (!fromFork || !toFork || !files)
13854
+ return stale(`the change since ${since.slice(0, 12)} cannot be read`);
13855
+ const riskPaths = [fromFork, toFork].flatMap((fork) => parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? ""));
13856
+ const lane = computeLane({ files, riskPaths });
13857
+ const paths = files.map((file) => file.path);
13858
+ if (lane.lane === "none")
13859
+ return { carries: true, rule: "lane-none", since, files: paths };
13860
+ if (input.kind === "unit" && lane.lane === "self" && files.every((file) => TEST_ONLY_KINDS.has(kindOf(file))))
13861
+ return { carries: true, rule: "tests-only", since, files: paths };
13862
+ return stale(lane.reasons[0] ?? `lane ${lane.lane}`, paths, lane.lane === "self");
13863
+ }
14095
13864
 
14096
- // packages/workit-core/src/core/policy/attributes.ts
14097
- function patternRegex(dir, pattern) {
14098
- if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
14099
- return null;
14100
- const anchored = pattern.includes("/");
14101
- const body = pattern.replace(/^\//u, "");
14102
- let source = "";
14103
- for (let index = 0;index < body.length; index += 1) {
14104
- const char = body[index];
14105
- if (char === "*" && body[index + 1] === "*") {
14106
- const slash = body[index + 2] === "/";
14107
- source += slash ? "(?:.*/)?" : ".*";
14108
- index += slash ? 2 : 1;
14109
- } else if (char === "*")
14110
- source += "[^/]*";
14111
- else if (char === "?")
14112
- source += "[^/]";
14113
- else if (char === "[") {
14114
- const end = body.indexOf("]", index + 2);
14115
- if (end < 0)
13865
+ // packages/workit-core/src/hooks/standing-signal.ts
13866
+ import { createHash as createHash10 } from "node:crypto";
13867
+ import fs12 from "node:fs";
13868
+ import path15 from "node:path";
13869
+ var gitStoreDir = (cwd) => {
13870
+ try {
13871
+ for (let dir = path15.resolve(cwd);; dir = path15.dirname(dir)) {
13872
+ const dotGit = path15.join(dir, ".git");
13873
+ if (fs12.existsSync(dotGit)) {
13874
+ let gitDir = dotGit;
13875
+ if (fs12.statSync(dotGit).isFile()) {
13876
+ const target = /^gitdir:\s*(.+)$/mu.exec(fs12.readFileSync(dotGit, "utf8"))?.[1]?.trim();
13877
+ if (!target)
13878
+ return null;
13879
+ gitDir = path15.resolve(dir, target);
13880
+ const common = path15.join(gitDir, "commondir");
13881
+ if (fs12.existsSync(common))
13882
+ gitDir = path15.resolve(gitDir, fs12.readFileSync(common, "utf8").trim());
13883
+ }
13884
+ return path15.join(gitDir, "workit");
13885
+ }
13886
+ if (path15.dirname(dir) === dir)
14116
13887
  return null;
14117
- const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
14118
- source += `[${set}]`;
14119
- index = end;
14120
- } else if (char === "\\" && index + 1 < body.length) {
14121
- source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
14122
- index += 1;
14123
- } else
14124
- source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
13888
+ }
13889
+ } catch {
13890
+ return null;
14125
13891
  }
14126
- const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
13892
+ };
13893
+ var standingRevFile = (store) => path15.join(store, "standing", "rev");
13894
+ var standingSeenFile = (store, session, agentId) => path15.join(store, "standing", "seen", `${createHash10("sha256").update(`${session}\x00${agentId ?? ""}`).digest("hex").slice(0, 32)}.json`);
13895
+ var readStandingRev = (store) => {
14127
13896
  try {
14128
- return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
13897
+ return fs12.readFileSync(standingRevFile(store), "utf8").trim() || null;
14129
13898
  } catch {
14130
13899
  return null;
14131
13900
  }
14132
- }
14133
- function rulesOf(dir, text) {
14134
- const out = [];
14135
- for (const raw of text.split(`
14136
- `)) {
14137
- const line = raw.trim();
14138
- if (!line || line.startsWith("#") || line.startsWith("[attr]"))
14139
- continue;
14140
- const [pattern, ...attributes] = line.split(/\s+/u);
14141
- let generated = null;
14142
- for (const attribute of attributes) {
14143
- if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
14144
- generated = true;
14145
- else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
14146
- generated = false;
14147
- }
14148
- if (generated === null)
14149
- continue;
14150
- const regex = patternRegex(dir, pattern);
14151
- if (regex)
14152
- out.push({ regex, generated });
14153
- }
14154
- return out;
14155
- }
14156
- function generatedByAttributes(files, paths) {
14157
- const rules = [...files].map(([file, text]) => {
14158
- const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
13901
+ };
13902
+ var readStandingSeen = (file) => {
13903
+ try {
13904
+ const value = JSON.parse(fs12.readFileSync(file, "utf8"));
14159
13905
  return {
14160
- depth: dir ? dir.split("/").length : 0,
14161
- rules: rulesOf(dir, text)
13906
+ rev: typeof value.rev === "string" ? value.rev : null,
13907
+ ids: Array.isArray(value.ids) ? value.ids.filter((id) => typeof id === "string") : []
14162
13908
  };
14163
- }).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
14164
- const out = new Set;
14165
- for (const file of paths) {
14166
- const last = rules.findLast((rule) => rule.regex.test(file));
14167
- if (last?.generated)
14168
- out.add(file);
13909
+ } catch {
13910
+ return { rev: null, ids: [] };
14169
13911
  }
14170
- return out;
14171
- }
13912
+ };
13913
+ var writeStandingSeen = (file, seen) => {
13914
+ try {
13915
+ fs12.mkdirSync(path15.dirname(file), { recursive: true });
13916
+ fs12.writeFileSync(file, JSON.stringify(seen));
13917
+ } catch {}
13918
+ };
14172
13919
 
14173
- // packages/workit-core/src/core/policy/substance.ts
14174
- var SLASH_BLOCK = [["/*", "*/"]];
14175
- var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
14176
- var SYNTAXES = Object.fromEntries([
14177
- ...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
14178
- ...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
14179
- ext,
14180
- { ...C_LIKE, triple: true }
14181
- ]),
14182
- ["go", { ...C_LIKE, rawBacktick: true }],
14183
- ...["css", "scss", "less"].map((ext) => [
14184
- ext,
14185
- { line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
14186
- ]),
14187
- ...["py", "pyi"].map((ext) => [
14188
- ext,
14189
- { line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
14190
- ]),
14191
- ["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
14192
- ...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
14193
- ext,
14194
- { line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
14195
- ])
13920
+ // packages/workit-core/src/ledger.ts
13921
+ var LEDGER_VERSION = 1;
13922
+ var MAX_LINE_BYTES = 4096;
13923
+ var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
13924
+ var FAILING = new Set(["failed", "blocked"]);
13925
+ var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
13926
+ var PR_ROW_TYPES = new Set([
13927
+ "pr.created",
13928
+ "pr.status",
13929
+ "pr.merged",
13930
+ "pr.linked"
14196
13931
  ]);
14197
- 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;
14198
- var extensionOf = (file) => {
14199
- const name = file.slice(file.lastIndexOf("/") + 1);
14200
- const dot = name.lastIndexOf(".");
14201
- return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
13932
+ var err = (code, error, unblock) => ({
13933
+ ok: false,
13934
+ code,
13935
+ error,
13936
+ ...unblock ? { unblock } : {}
13937
+ });
13938
+ var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
13939
+ const run = spawnSync9("git", args, {
13940
+ cwd,
13941
+ encoding: "utf8",
13942
+ timeout: timeoutMs,
13943
+ killSignal: "SIGKILL",
13944
+ windowsHide: true,
13945
+ maxBuffer: 256 * 1024 * 1024,
13946
+ env: {
13947
+ ...process.env,
13948
+ GIT_TERMINAL_PROMPT: "0",
13949
+ GIT_OPTIONAL_LOCKS: "0",
13950
+ LC_ALL: "C",
13951
+ LANGUAGE: "C"
13952
+ }
13953
+ });
13954
+ return {
13955
+ ok: run.status === 0,
13956
+ stdout: run.stdout ?? "",
13957
+ stderr: run.error ? String(run.error.message) : run.stderr ?? ""
13958
+ };
14202
13959
  };
14203
- var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
14204
- var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
14205
- var SPACE = /[ \t\r\f\v]/u;
14206
- var WORD = /[\w$]/u;
14207
- var NO_SPACE_AFTER = "([{,;";
14208
- var NO_SPACE_BEFORE = ")]},;";
14209
- var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
14210
- var REGEX_KEYWORDS = new Set([
14211
- "return",
14212
- "typeof",
14213
- "instanceof",
14214
- "case",
14215
- "do",
14216
- "else",
14217
- "in",
14218
- "of",
14219
- "new",
14220
- "delete",
14221
- "void",
14222
- "throw",
14223
- "yield",
14224
- "await"
14225
- ]);
14226
- function substanceOf(file, text) {
14227
- const syntax = syntaxOf(file);
14228
- if (!syntax)
14229
- return plainSubstance(text);
14230
- const markup = syntax.block.some(([open]) => open === "<!--");
14231
- if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
14232
- return plainSubstance(text);
14233
- return lex(syntax, text);
13960
+ var git4 = (cwd, args) => {
13961
+ const run = gitRun2(cwd, args);
13962
+ const value = run.ok ? run.stdout.trim() : "";
13963
+ return value ? value : null;
13964
+ };
13965
+ var safeRef2 = (value) => value.length > 0 && !value.startsWith("-");
13966
+ function resolveCommit(cwd, ref) {
13967
+ if (!safeRef2(ref))
13968
+ return null;
13969
+ return git4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
14234
13970
  }
14235
- function plainSubstance(text) {
14236
- return text.split(`
14237
- `).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
13971
+ function branchHead(cwd, branch) {
13972
+ if (!safeRef2(branch))
13973
+ return null;
13974
+ return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
13975
+ }
13976
+ function diffHash(cwd, base, head) {
13977
+ if (!safeRef2(base) || !safeRef2(head))
13978
+ return null;
13979
+ const diff = gitRun2(cwd, [
13980
+ "diff",
13981
+ "-U3",
13982
+ "--diff-algorithm=myers",
13983
+ "--indent-heuristic",
13984
+ "--no-color",
13985
+ "--no-ext-diff",
13986
+ "--no-textconv",
13987
+ "--no-renames",
13988
+ "--full-index",
13989
+ "--binary",
13990
+ "--src-prefix=a/",
13991
+ "--dst-prefix=b/",
13992
+ `${base}...${head}`,
13993
+ "--"
13994
+ ], GIT_TIMEOUTS.worktree);
13995
+ if (!diff.ok || !diff.stdout)
13996
+ return null;
13997
+ const hash = createHash11("sha256");
13998
+ for (const line of diff.stdout.split(`
13999
+ `)) {
14000
+ if (line.startsWith("index "))
14001
+ continue;
14002
+ hash.update(line.startsWith("@@ ") ? "@@" : line);
14003
+ hash.update(`
14238
14004
  `);
14005
+ }
14006
+ return `sha256:${hash.digest("hex")}`;
14239
14007
  }
14240
- function lex(syntax, text) {
14241
- const collapse = !syntax.indented;
14242
- let out = "";
14243
- let lineStart = 0;
14244
- let kept = 0;
14245
- let pendingSpace = false;
14246
- const frames = [{ kind: "code", braces: 0 }];
14247
- let index = 0;
14248
- if (text.startsWith("#!")) {
14249
- const end = text.indexOf(`
14250
- `);
14251
- out = end < 0 ? text : text.slice(0, end);
14252
- kept = out.length;
14253
- index = end < 0 ? text.length : end;
14008
+ function storeRoot(cwd) {
14009
+ const location = resolveStore(path16.resolve(cwd));
14010
+ if (location instanceof Error)
14011
+ 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");
14012
+ return { ok: true, value: { root: location.dir, shared: location.shared } };
14013
+ }
14014
+ function ledgerPath(cwd) {
14015
+ const root = storeRoot(cwd);
14016
+ return root.ok ? { ok: true, value: path16.join(root.value.root, "ledger", "ledger.jsonl") } : root;
14017
+ }
14018
+ var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
14019
+ var NETWORK_FS_TYPES = new Set([
14020
+ 26985,
14021
+ 20859,
14022
+ 4266872130,
14023
+ 4283649346,
14024
+ 1702057286,
14025
+ 1397113167,
14026
+ 12805120,
14027
+ 16914839,
14028
+ 1196443219,
14029
+ 198183888
14030
+ ]);
14031
+ var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
14032
+ var onNetworkFs = (dir) => {
14033
+ if (process.platform !== "linux")
14034
+ return false;
14035
+ try {
14036
+ return isNetworkFsType(fs13.statfsSync(dir).type);
14037
+ } catch {
14038
+ return false;
14254
14039
  }
14255
- const emitCode = (chunk) => {
14256
- if (collapse && pendingSpace && out.length > lineStart) {
14257
- const before = out[out.length - 1];
14258
- if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
14259
- out += " ";
14260
- }
14261
- pendingSpace = false;
14262
- out += chunk;
14263
- };
14264
- const emitVerbatim = (chunk) => {
14265
- emitCode(chunk);
14266
- kept = out.length;
14267
- };
14268
- const newline = () => {
14269
- if (!collapse) {
14270
- let end = out.length;
14271
- while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
14272
- end -= 1;
14273
- out = out.slice(0, end);
14274
- }
14275
- pendingSpace = false;
14276
- if (out.length > lineStart) {
14277
- out += `
14278
- `;
14279
- lineStart = out.length;
14040
+ };
14041
+ var LOCK_WAIT_MS = 5000;
14042
+ var LOCK_STALE_MS = 30000;
14043
+ var LOCK_OWNER = "owner";
14044
+ var lockToken = (lock) => {
14045
+ try {
14046
+ return fs13.readFileSync(path16.join(lock, LOCK_OWNER), "utf8");
14047
+ } catch {
14048
+ return null;
14049
+ }
14050
+ };
14051
+ var lockAgeMs = (lock) => {
14052
+ for (const target of [path16.join(lock, LOCK_OWNER), lock]) {
14053
+ try {
14054
+ return Date.now() - fs13.statSync(target).mtimeMs;
14055
+ } catch {}
14056
+ }
14057
+ return null;
14058
+ };
14059
+ var ledgerLock = {
14060
+ acquire(lock) {
14061
+ try {
14062
+ fs13.mkdirSync(lock);
14063
+ } catch (error) {
14064
+ if (error.code === "EEXIST")
14065
+ return null;
14066
+ throw error;
14280
14067
  }
14281
- };
14282
- const regexAllowed = () => {
14283
- let end = out.length;
14284
- while (end > 0 && /\s/u.test(out[end - 1]))
14285
- end -= 1;
14286
- if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
14287
- return true;
14288
- let start = end;
14289
- while (start > 0 && WORD.test(out[start - 1]))
14290
- start -= 1;
14291
- return REGEX_KEYWORDS.has(out.slice(start, end));
14292
- };
14293
- const scanString = (quote, escapes, multiline) => {
14294
- const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
14295
- const fstring = syntax.fstring === true && /f/iu.test(prefix);
14296
- let end = index + quote.length;
14297
- while (end < text.length) {
14298
- const char = text[end];
14299
- if (escapes && char === "\\") {
14300
- end += 2;
14301
- continue;
14302
- }
14303
- if (text.startsWith(quote, end)) {
14304
- if (fstring && unbalanced(text.slice(index + quote.length, end)))
14305
- return false;
14306
- emitVerbatim(text.slice(index, end + quote.length));
14307
- index = end + quote.length;
14308
- return true;
14309
- }
14310
- if (char === `
14311
- ` && !multiline)
14312
- return false;
14313
- end += 1;
14068
+ const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
14069
+ fs13.writeFileSync(path16.join(lock, LOCK_OWNER), token);
14070
+ return token;
14071
+ },
14072
+ release(lock, token) {
14073
+ if (lockToken(lock) !== token)
14074
+ return false;
14075
+ const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
14076
+ try {
14077
+ fs13.renameSync(lock, tomb);
14078
+ } catch {
14079
+ return false;
14314
14080
  }
14315
- return false;
14316
- };
14317
- while (index < text.length) {
14318
- const frame = frames[frames.length - 1];
14319
- const char = text[index];
14320
- if (frame.kind === "template") {
14321
- if (char === "\\") {
14322
- emitVerbatim(text.slice(index, index + 2));
14323
- index += 2;
14324
- } else if (char === "`") {
14325
- emitVerbatim(char);
14326
- frames.pop();
14327
- index += 1;
14328
- } else if (text.startsWith("${", index)) {
14329
- emitVerbatim("${");
14330
- frames.push({ kind: "code", braces: 0 });
14331
- index += 2;
14332
- } else {
14333
- out += char;
14334
- kept = out.length;
14335
- index += 1;
14336
- }
14337
- continue;
14081
+ if (lockToken(tomb) !== token) {
14082
+ try {
14083
+ fs13.renameSync(tomb, lock);
14084
+ } catch {}
14085
+ return false;
14338
14086
  }
14339
- if (char === `
14340
- `) {
14341
- newline();
14342
- index += 1;
14343
- continue;
14087
+ fs13.rmSync(tomb, { recursive: true, force: true });
14088
+ return true;
14089
+ },
14090
+ token: lockToken,
14091
+ reap(lock, observed) {
14092
+ const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
14093
+ try {
14094
+ fs13.renameSync(lock, tomb);
14095
+ } catch {
14096
+ return false;
14344
14097
  }
14345
- if (SPACE.test(char)) {
14346
- if (collapse)
14347
- pendingSpace = true;
14348
- else
14349
- out += char;
14350
- index += 1;
14351
- continue;
14098
+ if (lockToken(tomb) === observed) {
14099
+ fs13.rmSync(tomb, { recursive: true, force: true });
14100
+ return true;
14352
14101
  }
14353
- const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
14354
- if (lineOpener) {
14355
- const end = text.indexOf(`
14356
- `, index);
14357
- const comment = text.slice(index, end < 0 ? text.length : end);
14358
- if (DIRECTIVE.test(comment.slice(lineOpener.length)))
14359
- emitVerbatim(comment);
14360
- else
14361
- pendingSpace = true;
14362
- index = end < 0 ? text.length : end;
14363
- continue;
14102
+ try {
14103
+ fs13.renameSync(tomb, lock);
14104
+ } catch {
14105
+ fs13.rmSync(tomb, { recursive: true, force: true });
14364
14106
  }
14365
- const block = syntax.block.find(([open]) => text.startsWith(open, index));
14366
- if (block) {
14367
- const end = text.indexOf(block[1], index + block[0].length);
14368
- if (end < 0)
14369
- return null;
14370
- const comment = text.slice(index, end + block[1].length);
14371
- if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
14372
- emitVerbatim(comment);
14373
- else
14374
- pendingSpace = true;
14375
- if (comment.includes(`
14376
- `))
14377
- newline();
14378
- index = end + block[1].length;
14107
+ return false;
14108
+ }
14109
+ };
14110
+ function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
14111
+ const lock = `${file}.lock`;
14112
+ const deadline = Date.now() + waitMs;
14113
+ let token = null;
14114
+ for (;; ) {
14115
+ token = ledgerLock.acquire(lock);
14116
+ if (token)
14117
+ break;
14118
+ const observed = lockToken(lock);
14119
+ const age = lockAgeMs(lock);
14120
+ if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
14379
14121
  continue;
14380
- }
14381
- if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
14382
- if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
14122
+ if (Date.now() >= deadline)
14123
+ return null;
14124
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
14125
+ }
14126
+ try {
14127
+ return fn();
14128
+ } finally {
14129
+ ledgerLock.release(lock, token);
14130
+ }
14131
+ }
14132
+ var sha2562 = (text) => createHash11("sha256").update(text).digest("hex");
14133
+ var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
14134
+ ${body}`);
14135
+ var TAIL_BYTES = 64 * 1024;
14136
+ var lastLine = (fd, size) => {
14137
+ for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
14138
+ const buffer = Buffer.alloc(span);
14139
+ fs13.readSync(fd, buffer, 0, span, size - span);
14140
+ const end = buffer.lastIndexOf(10);
14141
+ if (end < 0) {
14142
+ if (span === size)
14383
14143
  return null;
14384
14144
  continue;
14385
14145
  }
14386
- if (syntax.quotes.includes(char)) {
14387
- if (!scanString(char, true, Boolean(syntax.multiline)))
14388
- return null;
14389
- continue;
14146
+ let stop = end;
14147
+ let text = "";
14148
+ while (stop >= 0) {
14149
+ const start = buffer.lastIndexOf(10, stop - 1);
14150
+ text = buffer.toString("utf8", start + 1, stop).trim();
14151
+ if (text) {
14152
+ if (start >= 0 || span === size)
14153
+ return text;
14154
+ break;
14155
+ }
14156
+ if (start < 0) {
14157
+ if (span === size)
14158
+ return null;
14159
+ break;
14160
+ }
14161
+ stop = start;
14390
14162
  }
14391
- if (syntax.literal?.includes(char)) {
14392
- if (!scanString(char, false, false))
14393
- return null;
14394
- continue;
14163
+ if (span === size)
14164
+ return text || null;
14165
+ }
14166
+ return null;
14167
+ };
14168
+ var tighten = (fd) => {
14169
+ try {
14170
+ if ((fs13.fstatSync(fd).mode & 63) !== 0)
14171
+ fs13.fchmodSync(fd, 384);
14172
+ } catch {}
14173
+ };
14174
+ function appendLine(file, build, options) {
14175
+ const write = () => {
14176
+ const fd = fs13.openSync(file, "a+", 384);
14177
+ try {
14178
+ if (process.platform !== "win32")
14179
+ tighten(fd);
14180
+ const size = fs13.fstatSync(fd).size;
14181
+ let fence = "";
14182
+ if (size > 0) {
14183
+ const last = Buffer.alloc(1);
14184
+ fs13.readSync(fd, last, 0, 1, size - 1);
14185
+ if (last[0] !== 10)
14186
+ fence = `
14187
+ `;
14188
+ }
14189
+ const line = build(lastLine(fd, size));
14190
+ if (!line.ok)
14191
+ return line;
14192
+ const bytes = Buffer.from(fence + line.value);
14193
+ const written = fs13.writeSync(fd, bytes);
14194
+ if (written !== bytes.length)
14195
+ return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
14196
+ return { ok: true, value: undefined };
14197
+ } finally {
14198
+ fs13.closeSync(fd);
14395
14199
  }
14396
- if (syntax.rawBacktick && char === "`") {
14397
- if (!scanString("`", false, true))
14398
- return null;
14200
+ };
14201
+ try {
14202
+ fs13.mkdirSync(path16.dirname(file), { recursive: true });
14203
+ if (!options.forceLock && !onNetworkFs(path16.dirname(file)))
14204
+ return write();
14205
+ const locked = withLedgerLock(file, write);
14206
+ return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
14207
+ } catch (error) {
14208
+ return err("unavailable", `cannot append to ${file}: ${error.message}`);
14209
+ }
14210
+ }
14211
+ var sealed = (row, id, at, previous) => {
14212
+ const { prevHash: _prev, rowHash: _row, ...fields } = row;
14213
+ const prevHash = previous === null ? null : sha2562(previous);
14214
+ const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
14215
+ const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
14216
+ return { full, line: `${JSON.stringify(full)}
14217
+ ` };
14218
+ };
14219
+ function appendRaw(cwd, row, options) {
14220
+ const file = ledgerPath(cwd);
14221
+ if (!file.ok)
14222
+ return file;
14223
+ const id = newId2();
14224
+ const at = (options.now ?? new Date).toISOString();
14225
+ let full = null;
14226
+ const written = appendLine(file.value, (previous) => {
14227
+ const next = sealed(row, id, at, previous);
14228
+ const bytes = Buffer.byteLength(next.line);
14229
+ if (bytes > MAX_LINE_BYTES)
14230
+ return err("invalid_input", `ledger row is ${bytes} bytes; the limit is ${MAX_LINE_BYTES}. Shorten the text and pass details by --ref <path|url>`);
14231
+ full = next.full;
14232
+ return { ok: true, value: next.line };
14233
+ }, options);
14234
+ return written.ok ? { ok: true, value: full } : written;
14235
+ }
14236
+ function observedRowBytes(row) {
14237
+ const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
14238
+ return Buffer.byteLength(sample.line);
14239
+ }
14240
+ function appendObserved(cwd, row, options = {}) {
14241
+ return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
14242
+ }
14243
+ function appendHookObserved(cwd, row) {
14244
+ return appendRaw(cwd, { ...row, observer: "host_hook" }, {});
14245
+ }
14246
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
14247
+ var str2 = (value) => typeof value === "string" ? value : null;
14248
+ var normalizeActor = (value) => {
14249
+ const actor = isRecord3(value) ? value : {};
14250
+ return {
14251
+ host: str2(actor.host) ?? "unknown",
14252
+ session: str2(actor.session),
14253
+ agentId: str2(actor.agentId)
14254
+ };
14255
+ };
14256
+ function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
14257
+ if (by.type !== target.type)
14258
+ return `a ${by.type} row cannot supersede a ${target.type} row`;
14259
+ if (!by.actor.session || by.actor.session !== target.actor.session)
14260
+ return "only the session that wrote a row can supersede it";
14261
+ if (!onBranch(target))
14262
+ return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
14263
+ if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
14264
+ return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
14265
+ if (by.type === "verdict" && by.self === true && target.self !== true)
14266
+ return "a self verdict cannot replace an independent verdict";
14267
+ return null;
14268
+ }
14269
+ function readLedger(cwd, options = {}) {
14270
+ const file = ledgerPath(cwd);
14271
+ if (!file.ok)
14272
+ return file;
14273
+ let text = "";
14274
+ let stat = null;
14275
+ try {
14276
+ stat = fs13.statSync(file.value);
14277
+ text = fs13.readFileSync(file.value, "utf8");
14278
+ } catch (error) {
14279
+ if (error.code === "ENOENT")
14280
+ return {
14281
+ ok: true,
14282
+ value: {
14283
+ path: file.value,
14284
+ rows: [],
14285
+ skipped: 0,
14286
+ integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
14287
+ }
14288
+ };
14289
+ return err("unavailable", `cannot read ${file.value}: ${error.message}`);
14290
+ }
14291
+ const rows = [];
14292
+ let skipped = 0;
14293
+ const integrity = { chained: 0, legacy: 0, unverified: [] };
14294
+ const earlier = new Set([null]);
14295
+ let chainStarted = false;
14296
+ const ids = new Set;
14297
+ for (const raw of text.split(`
14298
+ `)) {
14299
+ const trimmed = raw.trim();
14300
+ if (!trimmed)
14399
14301
  continue;
14400
- }
14401
- if (syntax.js && char === "`") {
14402
- emitVerbatim(char);
14403
- frames.push({ kind: "template" });
14404
- index += 1;
14302
+ const linked = (prevHash) => earlier.has(prevHash);
14303
+ if (options.integrity)
14304
+ earlier.add(sha2562(trimmed));
14305
+ let parsed;
14306
+ try {
14307
+ parsed = JSON.parse(trimmed);
14308
+ } catch {
14309
+ skipped += 1;
14405
14310
  continue;
14406
14311
  }
14407
- if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
14408
- return null;
14409
- if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
14410
- const lineEnd = text.indexOf(`
14411
- `, index);
14412
- if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
14413
- return null;
14312
+ if (!isRecord3(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
14313
+ skipped += 1;
14314
+ continue;
14414
14315
  }
14415
- if (syntax.js && char === "/" && regexAllowed()) {
14416
- let end = index + 1;
14417
- let inClass = false;
14418
- while (end < text.length && (inClass || text[end] !== "/")) {
14419
- if (text[end] === `
14420
- `)
14421
- return null;
14422
- if (text[end] === "\\")
14423
- end += 1;
14424
- else if (text[end] === "[")
14425
- inClass = true;
14426
- else if (text[end] === "]")
14427
- inClass = false;
14428
- end += 1;
14429
- }
14430
- if (end >= text.length)
14431
- return null;
14432
- emitVerbatim(text.slice(index, end + 1));
14433
- index = end + 1;
14316
+ let rowIntegrity = options.integrity ? "verified" : undefined;
14317
+ let reason = null;
14318
+ if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
14319
+ chainStarted = true;
14320
+ const { rowHash, ...body } = parsed;
14321
+ const prevHash = str2(parsed.prevHash);
14322
+ if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
14323
+ reason = "hash_mismatch";
14324
+ else if (!linked(prevHash))
14325
+ reason = "broken_link";
14326
+ } else if (chainStarted)
14327
+ reason = "unsigned";
14328
+ else
14329
+ rowIntegrity = "legacy";
14330
+ if (options.integrity && !reason && ids.has(parsed.id))
14331
+ reason = "duplicate_id";
14332
+ if (options.integrity)
14333
+ ids.add(parsed.id);
14334
+ if (reason)
14335
+ rowIntegrity = "unverified";
14336
+ const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
14337
+ const row = {
14338
+ ...parsed,
14339
+ v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
14340
+ id: parsed.id,
14341
+ at: parsed.at,
14342
+ type: parsed.type,
14343
+ actor: normalizeActor(parsed.actor),
14344
+ branch: str2(parsed.branch),
14345
+ head: str2(parsed.head),
14346
+ base: str2(parsed.base),
14347
+ baseSha: str2(parsed.baseSha),
14348
+ patchId: str2(parsed.patchId),
14349
+ diffHash: str2(parsed.diffHash),
14350
+ tree: str2(parsed.tree),
14351
+ dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
14352
+ ...pr === undefined ? {} : { pr },
14353
+ ...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
14354
+ seq: rows.length + 1,
14355
+ superseded: false,
14356
+ supersedeIgnored: false,
14357
+ ...rowIntegrity ? { integrity: rowIntegrity } : {},
14358
+ ...reason ? { integrityReason: reason } : {}
14359
+ };
14360
+ rows.push(row);
14361
+ if (!options.integrity)
14434
14362
  continue;
14435
- }
14436
- if (frames.length > 1 && frame.kind === "code") {
14437
- if (char === "{")
14438
- frame.braces += 1;
14439
- else if (char === "}") {
14440
- if (frame.braces === 0) {
14441
- emitVerbatim(char);
14442
- frames.pop();
14443
- index += 1;
14444
- continue;
14445
- }
14446
- frame.braces -= 1;
14447
- }
14448
- }
14449
- emitCode(char);
14450
- index += 1;
14363
+ if (rowIntegrity === "verified")
14364
+ integrity.chained += 1;
14365
+ else if (rowIntegrity === "legacy")
14366
+ integrity.legacy += 1;
14367
+ else if (reason)
14368
+ integrity.unverified.push({
14369
+ seq: row.seq,
14370
+ id: row.id,
14371
+ type: row.type,
14372
+ branch: row.branch,
14373
+ reason
14374
+ });
14451
14375
  }
14452
- if (frames.length !== 1)
14453
- return null;
14454
- newline();
14455
- return out;
14456
- }
14457
- function unbalanced(body) {
14458
- let depth = 0;
14459
- for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
14460
- if (part === "{")
14461
- depth += 1;
14462
- else if (part === "}")
14463
- depth = Math.max(0, depth - 1);
14376
+ const scopes = new Map;
14377
+ const scopeOf = (branch) => {
14378
+ let scope = scopes.get(branch);
14379
+ if (!scope)
14380
+ scopes.set(branch, scope = branchScope(cwd, branch));
14381
+ return scope;
14382
+ };
14383
+ const byId = new Map;
14384
+ for (const row of rows) {
14385
+ const target = row.supersedes ? byId.get(row.supersedes) : undefined;
14386
+ if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
14387
+ target.superseded = true;
14388
+ else if (row.supersedes)
14389
+ row.supersedeIgnored = true;
14390
+ if (!byId.has(row.id))
14391
+ byId.set(row.id, row);
14464
14392
  }
14465
- return depth > 0;
14466
- }
14467
- function hasOpaqueLineBreak(text) {
14468
- return /\r(?!\n)|[\u2028\u2029]/u.test(text);
14393
+ if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
14394
+ tamperCache.set(file.value, {
14395
+ size: stat.size,
14396
+ stamp: fileStamp(stat),
14397
+ breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
14398
+ });
14399
+ return {
14400
+ ok: true,
14401
+ value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
14402
+ };
14469
14403
  }
14470
- var decoder = new TextDecoder("utf-8", { fatal: true });
14471
- function textOf(bytes) {
14472
- if (bytes.includes(0))
14473
- return null;
14404
+ var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
14405
+ var tamperCache = new Map;
14406
+ var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
14407
+ function ledgerTampering(cwd) {
14408
+ const file = ledgerPath(cwd);
14409
+ if (!file.ok)
14410
+ return [];
14474
14411
  try {
14475
- return decoder.decode(bytes);
14412
+ const stat = fs13.statSync(file.value);
14413
+ const cached = tamperCache.get(file.value);
14414
+ if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
14415
+ return cached.breaks;
14476
14416
  } catch {
14477
- return null;
14417
+ return [];
14478
14418
  }
14419
+ const read = readLedger(cwd, { integrity: true });
14420
+ return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
14479
14421
  }
14480
- function cosmeticChange(file, before, after) {
14481
- const left = textOf(before);
14482
- const right = textOf(after);
14483
- if (left === null || right === null)
14484
- return false;
14485
- if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
14486
- return false;
14487
- const a = substanceOf(file, left);
14488
- return a !== null && a === substanceOf(file, right);
14422
+ function tamperedUnblock(cwd) {
14423
+ const file = ledgerPath(cwd);
14424
+ const where = file.ok ? file.value : "the ledger file";
14425
+ return `workit ledger verify-integrity # lists the edited or missing rows; if the edit is understood, move ${where} aside (mv ${where} ${where}.tampered-<date>) and record the verdicts again`;
14489
14426
  }
14490
-
14491
- // packages/workit-core/src/core/policy/lane.ts
14492
- var LANE_THRESHOLDS = {
14493
- selfMaxLines: 100,
14494
- selfMaxFiles: 5,
14495
- fullMinLines: 500
14496
- };
14497
- var RISK_TOKENS = new Set([
14498
- "auth",
14499
- "authn",
14500
- "authz",
14501
- "oauth",
14502
- "security",
14503
- "permission",
14504
- "permissions",
14505
- "secret",
14506
- "secrets",
14507
- "crypto",
14508
- "gate",
14509
- "gates",
14510
- "policy",
14511
- "policies",
14512
- "hook",
14513
- "hooks",
14514
- "husky",
14515
- "lefthook",
14516
- "release",
14517
- "releases",
14518
- "migration",
14519
- "migrations",
14520
- "migrate",
14521
- "workflows"
14522
- ]);
14523
- var RISK_FILES = new Set([
14524
- ".gitlab-ci.yml",
14525
- "Jenkinsfile",
14526
- "azure-pipelines.yml",
14527
- ".pre-commit-config.yaml",
14528
- ".workit/risk-paths"
14529
- ]);
14530
- var RISK_NAMES = new Set([".gitattributes"]);
14531
- var RISK_PATHS_FILE = ".workit/risk-paths";
14532
- var LOCKFILES = new Set([
14533
- "bun.lock",
14534
- "bun.lockb",
14535
- "package-lock.json",
14536
- "npm-shrinkwrap.json",
14537
- "yarn.lock",
14538
- "pnpm-lock.yaml",
14539
- "Cargo.lock",
14540
- "go.sum",
14541
- "poetry.lock",
14542
- "uv.lock",
14543
- "Pipfile.lock",
14544
- "Gemfile.lock",
14545
- "composer.lock"
14546
- ]);
14547
- var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc|txt)$/iu;
14548
- var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
14549
- var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
14550
- var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__|spec|specs|fixtures)\/|\.(?:test|spec)\.[^/]+$/iu;
14551
- var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
14552
- var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
14553
- function kindOf(file) {
14554
- if (file.binary)
14555
- return "behavior";
14556
- if (file.generated)
14557
- return "generated";
14558
- if (LOCKFILES.has(basename(file.path)))
14559
- return "lockfile";
14560
- if (isDoc(file.path))
14561
- return "doc";
14562
- if (file.cosmetic)
14563
- return "cosmetic";
14564
- return TEST_PATH.test(file.path) ? "test" : "behavior";
14427
+ var tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`;
14428
+ function verdictBasis(verdict, current) {
14429
+ if (verdict.head && verdict.head === current.head)
14430
+ return "fresh";
14431
+ if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
14432
+ return "carried";
14433
+ return "stale";
14565
14434
  }
14566
- var globCache = new Map;
14567
- function globMatch(pattern, file) {
14568
- let regex = globCache.get(pattern);
14569
- if (!regex) {
14570
- let source = "";
14571
- for (let index = 0;index < pattern.length; index += 1) {
14572
- const char = pattern[index];
14573
- if (char === "*" && pattern[index + 1] === "*") {
14574
- source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
14575
- index += pattern[index + 2] === "/" ? 2 : 1;
14576
- } else if (char === "*")
14577
- source += "[^/]*";
14578
- else if (char === "?")
14579
- source += "[^/]";
14580
- else
14581
- source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
14435
+ var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
14436
+ function trailerSessions(cwd, base, head) {
14437
+ if (!base || !head || !safeRef2(base) || !safeRef2(head))
14438
+ return [];
14439
+ const out = git4(cwd, [
14440
+ "log",
14441
+ "--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
14442
+ `${base}..${head}`
14443
+ ]);
14444
+ return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
14445
+ }
14446
+ var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
14447
+ function branchScope(cwd, branch) {
14448
+ const renamedAt = new Map;
14449
+ let since = Number.NEGATIVE_INFINITY;
14450
+ if (safeRef2(branch)) {
14451
+ const log = git4(cwd, [
14452
+ "log",
14453
+ "-g",
14454
+ "--date=unix",
14455
+ "--format=%gd%x09%gs",
14456
+ `refs/heads/${branch}`,
14457
+ "--"
14458
+ ]);
14459
+ for (const line of (log ?? "").split(`
14460
+ `)) {
14461
+ const [selector = "", subject = ""] = line.split("\t");
14462
+ const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
14463
+ if (!Number.isFinite(seconds))
14464
+ continue;
14465
+ since = seconds * 1000;
14466
+ const match = RENAMED.exec(subject);
14467
+ if (!match || match[1] === branch)
14468
+ continue;
14469
+ const until = (seconds + 1) * 1000;
14470
+ renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
14582
14471
  }
14583
- regex = new RegExp(`^${source}$`, "u");
14584
- globCache.set(pattern, regex);
14585
14472
  }
14586
- return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
14473
+ return (row) => {
14474
+ if (row.branch === branch)
14475
+ return true;
14476
+ const until = row.branch === null ? undefined : renamedAt.get(row.branch);
14477
+ if (until === undefined)
14478
+ return false;
14479
+ const at = Date.parse(row.at);
14480
+ return at >= since && at <= until;
14481
+ };
14482
+ }
14483
+ var renameAwareBranch = (scopeOf, branch) => {
14484
+ return (target) => {
14485
+ const own = typeof branch === "string" ? branch : null;
14486
+ const theirs = typeof target.branch === "string" ? target.branch : null;
14487
+ if (own === theirs)
14488
+ return true;
14489
+ if (own === null || theirs === null || typeof target.at !== "string")
14490
+ return false;
14491
+ return scopeOf(own)({ branch: theirs, at: target.at });
14492
+ };
14493
+ };
14494
+ function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
14495
+ const sessions = new Set;
14496
+ for (const row of rows) {
14497
+ if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
14498
+ continue;
14499
+ const session = str2(row.session) ?? row.actor.session;
14500
+ if (session)
14501
+ sessions.add(session);
14502
+ }
14503
+ for (const session of trailerSessions(cwd, range.base, range.head))
14504
+ sessions.add(session);
14505
+ return sessions;
14587
14506
  }
14588
- function isRiskPath(file, extra = []) {
14589
- if (extra.some((pattern) => globMatch(pattern, file)))
14590
- return true;
14591
- if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
14507
+ var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
14508
+ var authoredBy = (authors, session) => {
14509
+ if (authors.has(session))
14592
14510
  return true;
14593
- if (TEST_PATH.test(file) || isDoc(file))
14594
- return false;
14595
- 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)));
14511
+ for (const author of authors)
14512
+ if (derivesFrom(author, session) || derivesFrom(session, author))
14513
+ return true;
14514
+ return false;
14515
+ };
14516
+ var TRUST_REASONS = new Set([
14517
+ "unsigned",
14518
+ "hash_mismatch",
14519
+ "broken_link",
14520
+ "duplicate_id",
14521
+ "unissued_session",
14522
+ "unbound_session"
14523
+ ]);
14524
+ var SHELL_VOUCHED_HOSTS = new Set([
14525
+ "cli",
14526
+ "opencode",
14527
+ "pi",
14528
+ "codex_cli",
14529
+ "codex_desktop"
14530
+ ]);
14531
+ function sessionTrust(cwd, row) {
14532
+ const session = row.actor.session;
14533
+ if (!session || row.self === true)
14534
+ return null;
14535
+ const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
14536
+ const host = row.actor.host;
14537
+ const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
14538
+ if (!issuedOnly)
14539
+ return null;
14540
+ const binding = issuedBinding(cwd, session);
14541
+ if (binding === null)
14542
+ return "unissued_session";
14543
+ return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
14596
14544
  }
14597
- var parseRiskPaths = (text) => text.split(`
14598
- `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
14599
- function computeLane(input) {
14600
- const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
14601
- const risky = new Set(riskFiles);
14602
- const kinds = input.files.map((file) => ({
14603
- file,
14604
- kind: risky.has(file.path) ? "behavior" : kindOf(file)
14605
- }));
14606
- const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
14607
- const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
14608
- const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
14609
- const result = (lane, reasons) => ({
14610
- lane,
14611
- reasons,
14612
- lines,
14613
- files: behavior.length,
14614
- riskFiles
14545
+ function trustReasons(cwd, row) {
14546
+ if (row.integrity === "unverified")
14547
+ return [row.integrityReason ?? "unsigned"];
14548
+ if (row.integrity !== "verified")
14549
+ return ["unsigned"];
14550
+ const session = sessionTrust(cwd, row);
14551
+ return session ? [session] : [];
14552
+ }
14553
+ function withIntegrity(cwd, rows) {
14554
+ if (rows.every((row) => row.integrity !== undefined))
14555
+ return rows;
14556
+ const read = readLedger(cwd, { integrity: true });
14557
+ const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
14558
+ return rows.map((row) => {
14559
+ if (row.integrity !== undefined)
14560
+ return row;
14561
+ const fresh = bySeq.get(row.seq);
14562
+ return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
14615
14563
  });
14616
- const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
14617
- if (input.riskTier === "high")
14618
- return result("full", ["the task judgment records high risk"]);
14619
- if (riskFiles.length)
14620
- return result("full", [
14621
- `touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
14622
- ]);
14623
- if (!behavior.length)
14624
- return result("none", [
14625
- input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
14626
- ]);
14627
- if (lines >= fullMinLines)
14628
- return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
14629
- if (lines > selfMaxLines)
14630
- return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
14631
- if (behavior.length > selfMaxFiles)
14632
- return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
14633
- if (opaque.length)
14634
- return result("focused", [
14635
- `a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
14636
- ]);
14637
- return result("self", [
14638
- `small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
14639
- ]);
14640
14564
  }
14641
- function untrackedStat(cwd, file) {
14642
- try {
14643
- const bytes = readFileSync4(path17.join(cwd, file));
14644
- if (bytes.includes(0))
14645
- return { path: file, added: 0, deleted: 0, binary: true };
14646
- const text = bytes.toString("utf8");
14647
- if (hasOpaqueLineBreak(text))
14648
- return { path: file, added: 0, deleted: 0, binary: true };
14649
- const lines = text.split(`
14650
- `).length - (text.endsWith(`
14651
- `) ? 1 : 0);
14652
- return { path: file, added: lines, deleted: 0 };
14653
- } catch {
14654
- return { path: file, added: 0, deleted: 0, binary: true };
14565
+ var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
14566
+ var independenceReasons = (row, authors) => {
14567
+ const reasons = [];
14568
+ if (!row.actor.session)
14569
+ reasons.push("no_session");
14570
+ else if (authoredBy(authors, row.actor.session))
14571
+ reasons.push("author_session");
14572
+ if (row.self === true && !reasons.includes("no_session"))
14573
+ reasons.push("self");
14574
+ return reasons;
14575
+ };
14576
+ function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
14577
+ let effective = null;
14578
+ for (const row of rows) {
14579
+ if (!effective) {
14580
+ effective = row;
14581
+ continue;
14582
+ }
14583
+ if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
14584
+ continue;
14585
+ const rowIndependent = reasonsOf(row).length === 0;
14586
+ const effIndependent = reasonsOf(effective).length === 0;
14587
+ if (effIndependent && !rowIndependent && isCurrent(effective))
14588
+ continue;
14589
+ if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
14590
+ continue;
14591
+ effective = row;
14655
14592
  }
14593
+ return effective;
14656
14594
  }
14657
- var REGULAR = new Set(["100644", "100755"]);
14658
- var ABSENT = /^0+$/u;
14659
- function cosmeticFiles(cwd, fork, head) {
14660
- const raw = diffRaw(cwd, fork, head);
14661
- if (!raw)
14662
- return null;
14663
- const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
14664
- const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
14665
- if (!blobs)
14666
- return null;
14667
- const out = new Set;
14668
- const opaque = new Set;
14669
- for (const entry of candidates) {
14670
- const before = blobs.get(entry.oldBlob);
14671
- let after = blobs.get(entry.newBlob);
14672
- if (!after && head === null && ABSENT.test(entry.newBlob))
14673
- try {
14674
- after = readFileSync4(path17.join(cwd, entry.path));
14675
- } catch {
14676
- after = undefined;
14595
+ var restackExact = new WeakMap;
14596
+ function restackCarries(exactIn, rows, branch, from, to) {
14597
+ if (!from || !to)
14598
+ return false;
14599
+ const exact = (row, fromHead, head) => {
14600
+ if (exactIn === null)
14601
+ return true;
14602
+ const cwd = exactIn;
14603
+ let known = restackExact.get(row);
14604
+ if (known === undefined) {
14605
+ known = typeof row.fromBase === "string" && typeof row.toBase === "string" && sameOwnChange(cwd, {
14606
+ from: fromHead,
14607
+ to: head,
14608
+ fromBase: row.fromBase,
14609
+ toBase: row.toBase
14610
+ });
14611
+ restackExact.set(row, known);
14612
+ }
14613
+ return known;
14614
+ };
14615
+ const edges = new Map;
14616
+ for (const row of rows)
14617
+ 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))
14618
+ edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
14619
+ const seen = new Set([from]);
14620
+ const queue = [from];
14621
+ while (queue.length) {
14622
+ const next = queue.shift();
14623
+ if (next === to)
14624
+ return true;
14625
+ for (const target of edges.get(next) ?? [])
14626
+ if (!seen.has(target)) {
14627
+ seen.add(target);
14628
+ queue.push(target);
14677
14629
  }
14678
- if (before && after && cosmeticChange(entry.path, before, after))
14679
- out.add(entry.path);
14680
- else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
14681
- opaque.add(entry.path);
14682
14630
  }
14683
- return { cosmetic: out, opaque };
14631
+ return false;
14684
14632
  }
14685
- function generatedAt(cwd, fork, paths) {
14686
- const files = treeFilesNamed(cwd, fork, ".gitattributes");
14687
- const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
14688
- if (!files || !blobs)
14689
- return new Set;
14690
- const texts = new Map;
14691
- for (const [file, id] of files) {
14692
- const bytes = blobs.get(id);
14693
- if (bytes)
14694
- texts.set(file, bytes.toString("utf8"));
14633
+ function checkVerdicts(cwd, branch, ledgerRows) {
14634
+ const rows = withIntegrity(cwd, ledgerRows);
14635
+ const head = branchHead(cwd, branch);
14636
+ const fallbackBase = defaultBase(cwd);
14637
+ const scope = branchScope(cwd, branch);
14638
+ const keyCache = new Map;
14639
+ const keysAt = (base, at) => {
14640
+ if (!base || !at)
14641
+ return { patchId: null, diffHash: null };
14642
+ const cacheKey = `${base}\x00${at}`;
14643
+ let key = keyCache.get(cacheKey);
14644
+ if (!key) {
14645
+ key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
14646
+ keyCache.set(cacheKey, key);
14647
+ }
14648
+ return key;
14649
+ };
14650
+ const keyFor = (base) => keysAt(base, head);
14651
+ const driftCache = new Map;
14652
+ const driftOf = (row) => {
14653
+ if (driftCache.has(row))
14654
+ return driftCache.get(row) ?? null;
14655
+ let drift = null;
14656
+ const toBase = row.base ?? fallbackBase;
14657
+ const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
14658
+ const fromBase = row.baseSha ?? toBaseSha;
14659
+ if (row.head && head && row.head !== head && toBaseSha && fromBase && !untrusted(reasonsOf(row)))
14660
+ drift = verdictDrift(cwd, {
14661
+ from: row.head,
14662
+ to: head,
14663
+ fromBase,
14664
+ toBase: toBaseSha,
14665
+ kind: typeof row.kind === "string" ? row.kind : "review"
14666
+ });
14667
+ driftCache.set(row, drift);
14668
+ return drift;
14669
+ };
14670
+ const exactCache = new Map;
14671
+ const exactCarry = (row) => {
14672
+ if (FAILING.has(String(row.result)))
14673
+ return true;
14674
+ let exact = exactCache.get(row);
14675
+ if (exact === undefined) {
14676
+ const toBase = row.base ?? fallbackBase;
14677
+ const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
14678
+ const fromBase = row.baseSha ?? toBaseSha;
14679
+ exact = Boolean(row.head && head && toBaseSha && fromBase && sameOwnChange(cwd, { from: row.head, to: head, fromBase, toBase: toBaseSha }));
14680
+ exactCache.set(row, exact);
14681
+ }
14682
+ return exact;
14683
+ };
14684
+ const keyBasis = (row) => {
14685
+ const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
14686
+ return basis === "carried" && !exactCarry(row) ? "stale" : basis;
14687
+ };
14688
+ const restacked = (row) => restackCarries(FAILING.has(String(row.result)) ? null : cwd, rows, branch, row.head, head);
14689
+ const carryOf = (row) => {
14690
+ if (keyBasis(row) !== "stale")
14691
+ return null;
14692
+ if (restacked(row))
14693
+ return null;
14694
+ const drift = driftOf(row);
14695
+ return drift?.carries ? { rule: drift.rule, since: drift.since, files: drift.files } : null;
14696
+ };
14697
+ const basisOf = (row) => {
14698
+ const basis = keyBasis(row);
14699
+ if (basis !== "stale")
14700
+ return basis;
14701
+ return restacked(row) || carryOf(row) ? "carried" : basis;
14702
+ };
14703
+ const sameCode = (failed, row) => {
14704
+ if (failed.head && failed.head === row.head)
14705
+ return true;
14706
+ const base = failed.base ?? fallbackBase;
14707
+ const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
14708
+ const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
14709
+ return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(null, rows, branch, failed.head, row.head);
14710
+ };
14711
+ const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
14712
+ const failureAuthorCache = new Map;
14713
+ const failureAuthors = (row) => {
14714
+ let set = failureAuthorCache.get(row.seq);
14715
+ if (!set) {
14716
+ set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
14717
+ failureAuthorCache.set(row.seq, set);
14718
+ }
14719
+ return set;
14720
+ };
14721
+ const reasonCache = new Map;
14722
+ const reasonsOf = (row) => {
14723
+ let reasons = reasonCache.get(row);
14724
+ if (!reasons) {
14725
+ const failing = FAILING.has(String(row.result));
14726
+ reasons = [
14727
+ ...trustReasons(cwd, row),
14728
+ ...independenceReasons(row, failing ? failureAuthors(row) : authors)
14729
+ ];
14730
+ reasonCache.set(row, reasons);
14731
+ }
14732
+ return reasons;
14733
+ };
14734
+ const retired = new Set;
14735
+ for (const row of rows)
14736
+ if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
14737
+ retired.add(row.supersedes);
14738
+ const byKind = new Map;
14739
+ for (const row of rows)
14740
+ if (isLiveVerdict(row, retired) && scope(row)) {
14741
+ const kind = typeof row.kind === "string" ? row.kind : "review";
14742
+ byKind.set(kind, [...byKind.get(kind) ?? [], row]);
14743
+ }
14744
+ const verdicts = [];
14745
+ const warnings = [];
14746
+ for (const [kind, list] of byKind) {
14747
+ const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
14748
+ if (!row)
14749
+ continue;
14750
+ const basis = basisOf(row);
14751
+ const current = basis !== "stale";
14752
+ const independence = reasonsOf(row);
14753
+ const reasons = [
14754
+ ...current ? [] : ["stale"],
14755
+ ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
14756
+ ...independence
14757
+ ];
14758
+ verdicts.push({
14759
+ kind,
14760
+ basis,
14761
+ current,
14762
+ independent: independence.length === 0,
14763
+ accepted: reasons.length === 0,
14764
+ reasons,
14765
+ carry: basis === "carried" ? carryOf(row) : null,
14766
+ verdict: row
14767
+ });
14768
+ if (reasons.length === 0) {
14769
+ for (const later of list)
14770
+ if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
14771
+ warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
14772
+ }
14695
14773
  }
14696
- return generatedByAttributes(texts, paths);
14697
- }
14698
- var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
14699
- function laneFiles(cwd, fork, head) {
14700
- const stats = diffNumstat(cwd, fork, head);
14701
- const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
14702
- if (!stats || !cosmetic)
14703
- return null;
14704
- const files = [
14705
- ...stats.map((entry) => ({
14706
- ...entry,
14707
- ...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
14708
- cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
14709
- })),
14710
- ...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
14711
- ];
14712
- const generated = generatedAt(cwd, fork, files.map((entry) => entry.path));
14713
- return files.map((entry) => ({
14714
- ...entry,
14715
- generated: generated.has(entry.path)
14716
- }));
14717
- }
14718
- function branchLane(cwd, options) {
14719
- const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
14720
- const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
14721
- const head = options.workingTree ? null : options.head;
14722
- const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
14723
- const files = fork ? laneFiles(cwd, fork, head) : null;
14724
- const riskTier = options.judgment?.riskTier ?? null;
14725
- if (!fork || !files) {
14726
- const high = riskTier === "high";
14727
- return {
14728
- lane: high ? "full" : "focused",
14729
- reasons: [
14730
- `the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
14731
- ...high ? ["the task judgment records high risk"] : []
14732
- ],
14733
- lines: 0,
14734
- files: 0,
14735
- riskFiles: [],
14736
- base,
14737
- head: options.head
14738
- };
14774
+ for (const row of rows)
14775
+ if (row.integrity === "unverified" && scope(row))
14776
+ warnings.push(`ledger row ${row.id} (${row.type}) on ${row.branch ?? branch} is unverified: the workit CLI did not write it or it changed afterwards (workit ledger verify-integrity)`);
14777
+ const tampered = ledgerTampering(cwd);
14778
+ if (tampered.length) {
14779
+ warnings.push(tamperedWarning(cwd, tampered));
14780
+ for (const entry of verdicts) {
14781
+ entry.accepted = false;
14782
+ entry.reasons = [...entry.reasons, "ledger_tampered"];
14783
+ }
14739
14784
  }
14740
- const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
14785
+ verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
14786
+ const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
14787
+ const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
14788
+ const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
14789
+ const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
14790
+ accepted: false,
14791
+ verdict: failing?.verdict ?? null,
14792
+ reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
14793
+ };
14794
+ 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))));
14795
+ const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
14796
+ const carryEntry = verdicts.find((entry) => entry.verdict === accepted.verdict && entry.accepted) ?? newestCurrent;
14797
+ const lastPass = accepted.accepted || failing || tampered.length ? undefined : verdicts.findLast((entry) => !entry.current && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
14798
+ const drift = lastPass ? driftOf(lastPass.verdict) : null;
14799
+ const reverify = drift && !drift.carries && drift.focused && drift.files ? { scope: "focused", since: drift.since, files: drift.files } : null;
14741
14800
  return {
14742
- ...computeLane({
14743
- files,
14744
- riskTier,
14745
- riskPaths: [...repoRisk, ...options.riskPaths ?? []]
14746
- }),
14747
- base,
14748
- head: options.head
14801
+ branch,
14802
+ head,
14803
+ review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
14804
+ selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
14805
+ base: newestCurrent?.verdict.base ?? fallbackBase,
14806
+ patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
14807
+ current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
14808
+ carry: carryEntry?.carry ?? null,
14809
+ reverify,
14810
+ accepted,
14811
+ verdicts,
14812
+ authors: [...authors].toSorted(),
14813
+ warnings
14749
14814
  };
14750
14815
  }
14751
- var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
14816
+ var fromLead = (row, leads) => leads === null || row.actor.session !== null && leads.includes(row.actor.session);
14817
+ function activeStanding(rows, fanout, leads = null) {
14818
+ const active = [];
14819
+ for (const row of rows) {
14820
+ if (row.fanout !== fanout || !fromLead(row, leads))
14821
+ continue;
14822
+ if (row.type === "standing" && typeof row.what === "string" && row.what.trim()) {
14823
+ if (!row.superseded)
14824
+ active.push(row);
14825
+ } else if (row.type === "standing.cleared") {
14826
+ const target = str2(row.target);
14827
+ if (target === null)
14828
+ active.length = 0;
14829
+ else {
14830
+ const index = active.findIndex((order) => order.id === target);
14831
+ if (index >= 0)
14832
+ active.splice(index, 1);
14833
+ }
14834
+ }
14835
+ }
14836
+ return active;
14837
+ }
14838
+ var OBSERVED_ONLY = new Set([
14839
+ "check",
14840
+ "merge.unverified",
14841
+ "policy.judged",
14842
+ ...PR_ROW_TYPES
14843
+ ]);
14752
14844
 
14753
14845
  // packages/workit-core/src/core/policy/derive.ts
14754
14846
  var RULES = {
@@ -14903,7 +14995,7 @@ var productDecision = (task, ledger) => {
14903
14995
  };
14904
14996
  };
14905
14997
  var planRecorded = (task, root) => {
14906
- const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(path18.resolve(root, ref.path)));
14998
+ const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(path17.resolve(root, ref.path)));
14907
14999
  if (cited)
14908
15000
  return {
14909
15001
  met: true,
@@ -14981,14 +15073,14 @@ function writeBlockers(task, root) {
14981
15073
 
14982
15074
  // packages/workit-core/src/core/task-evaluation.ts
14983
15075
  var digestBytes = (value) => createHash12("sha256").update(value).digest("hex");
14984
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path19.sep}`);
14985
- var relative = (root, candidate) => path19.relative(root, candidate).split(path19.sep).join("/") || ".";
15076
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path18.sep}`);
15077
+ var relative = (root, candidate) => path18.relative(root, candidate).split(path18.sep).join("/") || ".";
14986
15078
  var canonicalPath = (value) => {
14987
15079
  if (typeof value !== "string" || !value)
14988
15080
  return null;
14989
15081
  if (value.includes("\\") || value.split("/").includes(".."))
14990
15082
  return null;
14991
- const normalized = path19.posix.normalize(value);
15083
+ const normalized = path18.posix.normalize(value);
14992
15084
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
14993
15085
  return null;
14994
15086
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -15024,7 +15116,7 @@ var compareCodeUnits2 = (left, right) => {
15024
15116
  return left.length - right.length;
15025
15117
  };
15026
15118
  var gitPaths = (root) => {
15027
- const result = spawnSync11("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
15119
+ const result = spawnSync10("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
15028
15120
  cwd: root,
15029
15121
  encoding: "buffer"
15030
15122
  });
@@ -15036,21 +15128,21 @@ var gitPaths = (root) => {
15036
15128
  git: !/not a git repository/i.test(stderr)
15037
15129
  };
15038
15130
  }
15039
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path19.sep).join("/")) : [];
15040
- const stagedDeleted = spawnSync11("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
15131
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path18.sep).join("/")) : [];
15132
+ const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
15041
15133
  if (stagedDeleted.status !== 0) {
15042
15134
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
15043
15135
  if (!/not a git repository/i.test(stderr))
15044
15136
  return { paths, uncertain: true, git: true };
15045
15137
  } else if (stagedDeleted.stdout) {
15046
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path19.sep).join("/")));
15138
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path18.sep).join("/")));
15047
15139
  }
15048
15140
  return { paths: [...new Set(paths)], uncertain: false, git: true };
15049
15141
  };
15050
15142
  var walk = (root, directory, output) => {
15051
15143
  let names;
15052
15144
  try {
15053
- names = fs15.readdirSync(directory);
15145
+ names = fs14.readdirSync(directory);
15054
15146
  } catch {
15055
15147
  return true;
15056
15148
  }
@@ -15058,10 +15150,10 @@ var walk = (root, directory, output) => {
15058
15150
  for (const name of names) {
15059
15151
  if (name === ".git" || name === ".workit")
15060
15152
  continue;
15061
- const target = path19.join(directory, name);
15153
+ const target = path18.join(directory, name);
15062
15154
  let stat;
15063
15155
  try {
15064
- stat = fs15.lstatSync(target);
15156
+ stat = fs14.lstatSync(target);
15065
15157
  } catch {
15066
15158
  uncertain = true;
15067
15159
  continue;
@@ -15076,14 +15168,14 @@ var walk = (root, directory, output) => {
15076
15168
  var scopeRoots = (root, scope) => {
15077
15169
  const roots = [];
15078
15170
  for (const item of scope.paths.length ? scope.paths : ["."]) {
15079
- const target = path19.resolve(root, item);
15171
+ const target = path18.resolve(root, item);
15080
15172
  if (!inside(root, target))
15081
15173
  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` }] });
15082
15174
  let ancestor = target;
15083
- while (!fs15.existsSync(ancestor) && ancestor !== root)
15084
- ancestor = path19.dirname(ancestor);
15175
+ while (!fs14.existsSync(ancestor) && ancestor !== root)
15176
+ ancestor = path18.dirname(ancestor);
15085
15177
  try {
15086
- if (!inside(root, fs15.realpathSync(ancestor)))
15178
+ if (!inside(root, fs14.realpathSync(ancestor)))
15087
15179
  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` }] });
15088
15180
  } catch {
15089
15181
  return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
@@ -15095,7 +15187,7 @@ var scopeRoots = (root, scope) => {
15095
15187
  function captureCandidate(root, scope, environment = []) {
15096
15188
  let checkout;
15097
15189
  try {
15098
- checkout = fs15.realpathSync(root);
15190
+ checkout = fs14.realpathSync(root);
15099
15191
  } catch {
15100
15192
  return failure2("invalid_input", "candidate root does not exist");
15101
15193
  }
@@ -15113,11 +15205,11 @@ function captureCandidate(root, scope, environment = []) {
15113
15205
  if (item === ".git" || item === ".workit")
15114
15206
  continue;
15115
15207
  names.add(item);
15116
- if (!fs15.existsSync(target))
15208
+ if (!fs14.existsSync(target))
15117
15209
  continue;
15118
15210
  let stat;
15119
15211
  try {
15120
- stat = fs15.lstatSync(target);
15212
+ stat = fs14.lstatSync(target);
15121
15213
  } catch {
15122
15214
  uncertain = true;
15123
15215
  continue;
@@ -15133,17 +15225,17 @@ function captureCandidate(root, scope, environment = []) {
15133
15225
  for (const item of [...names].toSorted()) {
15134
15226
  if (!scopeMatches(item, normalizedScope))
15135
15227
  continue;
15136
- const target = path19.join(checkout, item);
15228
+ const target = path18.join(checkout, item);
15137
15229
  try {
15138
- const stat = fs15.lstatSync(target);
15230
+ const stat = fs14.lstatSync(target);
15139
15231
  if (stat.isSymbolicLink()) {
15140
- const link = fs15.readlinkSync(target);
15232
+ const link = fs14.readlinkSync(target);
15141
15233
  files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
15142
15234
  } else if (stat.isFile()) {
15143
15235
  files.push({
15144
15236
  path: item,
15145
15237
  kind: "file",
15146
- digest: digestBytes(fs15.readFileSync(target)),
15238
+ digest: digestBytes(fs14.readFileSync(target)),
15147
15239
  executable: (stat.mode & 73) !== 0
15148
15240
  });
15149
15241
  } else if (!stat.isDirectory()) {
@@ -15168,7 +15260,7 @@ function captureCandidate(root, scope, environment = []) {
15168
15260
  const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
15169
15261
  if (values.some(({ value }) => value === null))
15170
15262
  completeness = "uncertain";
15171
- const headResult = spawnSync11("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
15263
+ const headResult = spawnSync10("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
15172
15264
  if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
15173
15265
  completeness = "uncertain";
15174
15266
  const head = headResult.status === 0 ? headResult.stdout.trim() : null;
@@ -15308,7 +15400,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
15308
15400
  var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
15309
15401
  let root;
15310
15402
  try {
15311
- root = fs15.realpathSync(checkoutRoot);
15403
+ root = fs14.realpathSync(checkoutRoot);
15312
15404
  } catch {
15313
15405
  return failure2("permission_denied", "checkout root is unavailable");
15314
15406
  }
@@ -15317,23 +15409,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
15317
15409
  continue;
15318
15410
  if (!reference.digest)
15319
15411
  return failure2("invalid_input", "document references require a byte digest");
15320
- const target = path19.resolve(root, reference.path);
15321
- if (target !== root && !target.startsWith(`${root}${path19.sep}`))
15412
+ const target = path18.resolve(root, reference.path);
15413
+ if (target !== root && !target.startsWith(`${root}${path18.sep}`))
15322
15414
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
15323
15415
  fields: [
15324
15416
  { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
15325
15417
  ]
15326
15418
  });
15327
15419
  try {
15328
- const below = path19.relative(root, target);
15420
+ const below = path18.relative(root, target);
15329
15421
  let current = root;
15330
- for (const segment of below.split(path19.sep)) {
15422
+ for (const segment of below.split(path18.sep)) {
15331
15423
  if (!segment)
15332
15424
  continue;
15333
- current = path19.join(current, segment);
15334
- const stat = fs15.lstatSync(current);
15335
- const real = fs15.realpathSync(current);
15336
- if (real !== root && !real.startsWith(`${root}${path19.sep}`))
15425
+ current = path18.join(current, segment);
15426
+ const stat = fs14.lstatSync(current);
15427
+ const real = fs14.realpathSync(current);
15428
+ if (real !== root && !real.startsWith(`${root}${path18.sep}`))
15337
15429
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
15338
15430
  fields: [
15339
15431
  {
@@ -15349,7 +15441,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
15349
15441
  if (current !== target && !stat.isDirectory())
15350
15442
  return failure2("invalid_input", "document reference ancestor is not a directory");
15351
15443
  }
15352
- if (digestBytes(fs15.readFileSync(target)) !== reference.digest)
15444
+ if (digestBytes(fs14.readFileSync(target)) !== reference.digest)
15353
15445
  return failure2("permission_denied", "decision document bytes have changed");
15354
15446
  } catch {
15355
15447
  return failure2("invalid_input", "decision document is unavailable");
@@ -15780,7 +15872,7 @@ Autonomy contract:
15780
15872
  CLI first: if a step has one right answer, use the \`workit\` verb instead of
15781
15873
  hand-running git, gh or glab (\`workit git|pr|ci|check|help\`;
15782
15874
  else \`npx -y @brainervirus/workit-cli\`).
15783
- Other checkout: \`workit --cwd <dir> <verb>\` (Codex: \`<verb> --cwd <dir>\`); one workit
15875
+ Other checkout: \`workit <verb> --cwd <dir> …\` (verb first: host rules match it); one workit
15784
15876
  verb per shell call, never \`cd <dir> && workit\`. zsh-safe: no \`echo =====\`
15785
15877
  (\`printf '%s\\n' ---\`), quote globs and URLs, no variable named \`status\` or \`path\`.
15786
15878
  Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
@@ -15792,7 +15884,7 @@ authorizes an effect. Judge work once with
15792
15884
  \`workit policy assess --judge risk= behavior= product-choice= plan=\`.
15793
15885
  Across repositories, bind items to checkout, branch and endpoint, resolve
15794
15886
  competing targets before a mutation, and reconcile an uncertain
15795
- external effect before a retry.
15887
+ effect before a retry.
15796
15888
 
15797
15889
  Mid-task: never silently drop or resume an objective (workit-continue).
15798
15890
 
@@ -15934,21 +16026,21 @@ var exportDigest = (bundle) => sha256({
15934
16026
  task: bundle.task
15935
16027
  });
15936
16028
  // packages/workit-core/src/core/route-intent.ts
15937
- import { spawnSync as spawnSync13 } from "node:child_process";
16029
+ import { spawnSync as spawnSync12 } from "node:child_process";
15938
16030
  import { existsSync as existsSync7 } from "node:fs";
15939
- import path22 from "node:path";
16031
+ import path21 from "node:path";
15940
16032
 
15941
16033
  // packages/workit-core/src/hooks/raw-git.ts
15942
- import { spawnSync as spawnSync12 } from "node:child_process";
16034
+ import { spawnSync as spawnSync11 } from "node:child_process";
15943
16035
  import { createHash as createHash14 } from "node:crypto";
15944
- import fs17 from "node:fs";
16036
+ import fs16 from "node:fs";
15945
16037
  import os4 from "node:os";
15946
- import path21 from "node:path";
16038
+ import path20 from "node:path";
15947
16039
 
15948
16040
  // packages/workit-core/src/hooks/session-marker.ts
15949
16041
  import { createHash as createHash13 } from "node:crypto";
15950
- import fs16 from "node:fs";
15951
- import path20 from "node:path";
16042
+ import fs15 from "node:fs";
16043
+ import path19 from "node:path";
15952
16044
  var sessionMarkerFile = (input, session, store) => {
15953
16045
  let dir = store;
15954
16046
  if (dir === undefined) {
@@ -15958,13 +16050,13 @@ var sessionMarkerFile = (input, session, store) => {
15958
16050
  dir = location.dir;
15959
16051
  }
15960
16052
  const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
15961
- return path20.join(dir, "hooks", `skills-${name}.json`);
16053
+ return path19.join(dir, "hooks", `skills-${name}.json`);
15962
16054
  };
15963
16055
  var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
15964
16056
  var EMPTY = { nudged: [], loaded: [], raw: [], stops: [], judges: [] };
15965
16057
  var readRaw = (file) => {
15966
16058
  try {
15967
- return fs16.readFileSync(file, "utf8");
16059
+ return fs15.readFileSync(file, "utf8");
15968
16060
  } catch {
15969
16061
  return "";
15970
16062
  }
@@ -15985,13 +16077,13 @@ var parseMarker = (text) => {
15985
16077
  };
15986
16078
  var readSessionMarker = (file) => parseMarker(readRaw(file));
15987
16079
  var writeAtomic = (file, marker) => {
15988
- fs16.mkdirSync(path20.dirname(file), { recursive: true });
16080
+ fs15.mkdirSync(path19.dirname(file), { recursive: true });
15989
16081
  const temp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
15990
16082
  try {
15991
- fs16.writeFileSync(temp, JSON.stringify(marker));
15992
- fs16.renameSync(temp, file);
16083
+ fs15.writeFileSync(temp, JSON.stringify(marker));
16084
+ fs15.renameSync(temp, file);
15993
16085
  } catch (error) {
15994
- fs16.rmSync(temp, { force: true });
16086
+ fs15.rmSync(temp, { force: true });
15995
16087
  throw error;
15996
16088
  }
15997
16089
  };
@@ -16272,6 +16364,18 @@ var substitutions = (body) => {
16272
16364
  };
16273
16365
  var PLAIN_POSIX = /[^\s'"\\`$(){},<>|;&#]+/uy;
16274
16366
  var PLAIN_POWERSHELL = /[^\s'"`(){}<>|;&#]+/uy;
16367
+ var expandsInQuotes = (body, dialect) => {
16368
+ const escape = dialect === "posix" ? "\\" : "`";
16369
+ for (let index = 0;index < body.length; index++) {
16370
+ const char = body[index];
16371
+ if (char === escape)
16372
+ index++;
16373
+ else if (char === "$" || dialect === "posix" && char === "`")
16374
+ return true;
16375
+ }
16376
+ return false;
16377
+ };
16378
+ var EXPANDING_RUN = /[*?[]/u;
16275
16379
  var arithmeticEnd = (command, start) => {
16276
16380
  const end = substitutionEnd(command, start);
16277
16381
  return command[end - 1] === ")" && end > start + 2 ? end : 0;
@@ -16290,6 +16394,7 @@ var tokenize = (command, dialect) => {
16290
16394
  let current = { words: [], redirects: [] };
16291
16395
  let word = "";
16292
16396
  let inWord = false;
16397
+ let expanding = false;
16293
16398
  let pending = null;
16294
16399
  const heredocs = [];
16295
16400
  const escape = posix ? "\\" : "`";
@@ -16304,11 +16409,16 @@ var tokenize = (command, dialect) => {
16304
16409
  heredocs.push({ delimiter: text.replace(/^-/, ""), owner: current });
16305
16410
  else if (pending === "herestring")
16306
16411
  (current.stdin ??= []).push(text);
16307
- else if (pending !== "input")
16412
+ else if (pending !== "input") {
16413
+ const at = current.words.length;
16308
16414
  current.words.push(...MARKERS.test(word) ? expandBraces(word) : [word]);
16415
+ if (expanding || !posix && word.startsWith("@"))
16416
+ (current.expands ??= []).push(...current.words.slice(at).map((_, i) => at + i));
16417
+ }
16309
16418
  pending = null;
16310
16419
  word = "";
16311
16420
  inWord = false;
16421
+ expanding = false;
16312
16422
  };
16313
16423
  const flushSegment = () => {
16314
16424
  flushWord();
@@ -16323,6 +16433,7 @@ var tokenize = (command, dialect) => {
16323
16433
  };
16324
16434
  const substitution = (inner, text) => {
16325
16435
  (current.subs ??= []).push(inner);
16436
+ expanding = true;
16326
16437
  word += text;
16327
16438
  inWord = true;
16328
16439
  };
@@ -16333,6 +16444,8 @@ var tokenize = (command, dialect) => {
16333
16444
  if (run) {
16334
16445
  word += run[0];
16335
16446
  inWord = true;
16447
+ if (EXPANDING_RUN.test(run[0]))
16448
+ expanding = true;
16336
16449
  index += run[0].length - 1;
16337
16450
  continue;
16338
16451
  }
@@ -16361,6 +16474,8 @@ var tokenize = (command, dialect) => {
16361
16474
  const body = command.slice(index + 1, end < 0 ? undefined : end);
16362
16475
  if (posix && char === '"' && /\$\(|`/.test(body))
16363
16476
  (current.subs ??= []).push(...substitutions(body));
16477
+ if (char === '"' && expandsInQuotes(body, dialect))
16478
+ expanding = true;
16364
16479
  word += unquote(body, char, dialect);
16365
16480
  inWord = true;
16366
16481
  index = end < 0 ? command.length : end;
@@ -16406,6 +16521,8 @@ var tokenize = (command, dialect) => {
16406
16521
  if (char === "(" && inWord && word.endsWith("$")) {
16407
16522
  word = word.slice(0, -1);
16408
16523
  inWord = word !== "";
16524
+ if (!inWord)
16525
+ expanding = false;
16409
16526
  }
16410
16527
  group(char === "(" ? "open" : "close");
16411
16528
  } else if (!posix && (char === "{" || char === "}")) {
@@ -16459,6 +16576,8 @@ var tokenize = (command, dialect) => {
16459
16576
  else if (posix && char === "}" && word.includes(OPEN))
16460
16577
  word += CLOSE;
16461
16578
  else {
16579
+ if (char === "$")
16580
+ expanding = true;
16462
16581
  word += char;
16463
16582
  inWord = true;
16464
16583
  }
@@ -16513,17 +16632,17 @@ var GIT_VALUE_OPTIONS = new Set([
16513
16632
  ]);
16514
16633
  var GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
16515
16634
  var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
16516
- var ASSIGNMENT2 = /^([A-Za-z_]\w*)=([\s\S]*)$/;
16635
+ var ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
16517
16636
  var MAX_DEPTH = 4;
16518
16637
  var MAX_EXPANSIONS = 16;
16519
- var isAbsolute = (value) => path21.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
16638
+ var isAbsolute = (value) => path20.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
16520
16639
  var HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
16521
16640
  var joinDir = (base, raw) => {
16522
16641
  const next = raw.replace(HERE, ".");
16523
16642
  if (base === undefined || /[$`*?]/.test(next) || next === "-")
16524
16643
  return;
16525
- const expanded = next === "~" || next.startsWith("~/") ? path21.join(os4.homedir(), next.slice(1)) : next;
16526
- return base === null || isAbsolute(expanded) ? expanded : path21.join(base, expanded);
16644
+ const expanded = next === "~" || next.startsWith("~/") ? path20.join(os4.homedir(), next.slice(1)) : next;
16645
+ return base === null || isAbsolute(expanded) ? expanded : path20.join(base, expanded);
16527
16646
  };
16528
16647
  var rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner);
16529
16648
  var cdTarget = (words) => {
@@ -16540,7 +16659,7 @@ var stripPrefixes = (input) => {
16540
16659
  const first = words[0];
16541
16660
  if (first === undefined)
16542
16661
  return { words, env };
16543
- const assignment = ASSIGNMENT2.exec(first);
16662
+ const assignment = ASSIGNMENT.exec(first);
16544
16663
  if (assignment) {
16545
16664
  if (GIT_CONFIG_ENV.test(assignment[1]))
16546
16665
  env[assignment[1]] = assignment[2];
@@ -16599,10 +16718,10 @@ var assignments = (words, vars) => {
16599
16718
  while (rest[0]?.startsWith("-"))
16600
16719
  rest = rest.slice(1);
16601
16720
  }
16602
- if (rest.length === 0 || !rest.every((word) => ASSIGNMENT2.test(word)))
16721
+ if (rest.length === 0 || !rest.every((word) => ASSIGNMENT.test(word)))
16603
16722
  return null;
16604
16723
  return rest.map((word) => {
16605
- const [, name, value] = ASSIGNMENT2.exec(word);
16724
+ const [, name, value] = ASSIGNMENT.exec(word);
16606
16725
  return [name, expandWord(value, vars)];
16607
16726
  });
16608
16727
  };
@@ -16790,7 +16909,7 @@ var splitString = (line) => {
16790
16909
  return line.slice(index).join(" ").slice(15);
16791
16910
  if (/^-S./.test(word))
16792
16911
  return [word.slice(2), ...line.slice(index + 1)].join(" ");
16793
- if (!word.startsWith("-") && !ASSIGNMENT2.test(word))
16912
+ if (!word.startsWith("-") && !ASSIGNMENT.test(word))
16794
16913
  return null;
16795
16914
  }
16796
16915
  return null;
@@ -16903,8 +17022,17 @@ function walk2(command, dialect, depth, seen) {
16903
17022
  visit(next, undefined, hops + 1);
16904
17023
  };
16905
17024
  const wrapped = seen ? null : workitCheckTail(words);
16906
- if (wrapped)
16907
- return "script" in wrapped ? nested(wrapped.script, "posix") : runs([wrapped.argv]);
17025
+ if (wrapped) {
17026
+ const outer = dir;
17027
+ if (wrapped.cwd !== null)
17028
+ dir = joinDir(dir, wrapped.cwd);
17029
+ if ("script" in wrapped)
17030
+ nested(wrapped.script, "posix");
17031
+ else
17032
+ runs([wrapped.argv]);
17033
+ dir = outer;
17034
+ return;
17035
+ }
16908
17036
  if (CD.has(name)) {
16909
17037
  const target = cdTarget(words);
16910
17038
  dir = target === null ? os4.homedir() : joinDir(dir, target);
@@ -17005,14 +17133,14 @@ function workitCheckTail(words) {
17005
17133
  return null;
17006
17134
  } else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
17007
17135
  return null;
17008
- const args = words.slice(at + 1);
17136
+ const { rest: args, cwd } = skipWorkitGlobals(words.slice(at + 1));
17009
17137
  if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
17010
17138
  return null;
17011
17139
  const split = args.indexOf("--");
17012
17140
  const tail = split < 0 ? [] : args.slice(split + 1);
17013
17141
  if (!tail.length)
17014
17142
  return null;
17015
- return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
17143
+ return args.slice(0, split).includes("--shell") ? { script: tail.join(" "), cwd } : { argv: tail, cwd };
17016
17144
  }
17017
17145
  function simpleCommand(command, dialect = "posix", depth = 0) {
17018
17146
  let segments;
@@ -17073,7 +17201,21 @@ function skipWorkitGlobals(args) {
17073
17201
  } else if (args[at] !== "--json")
17074
17202
  break;
17075
17203
  }
17076
- return { rest: args.slice(at), cwd };
17204
+ const rest = [];
17205
+ for (;at < args.length; at++) {
17206
+ const arg = args[at];
17207
+ if (arg === "--") {
17208
+ rest.push(...args.slice(at));
17209
+ break;
17210
+ }
17211
+ if (arg.startsWith("--cwd="))
17212
+ cwd = arg.slice("--cwd=".length);
17213
+ else if (arg === "--cwd" && args[at + 1] !== undefined)
17214
+ cwd = args[++at];
17215
+ else
17216
+ rest.push(arg);
17217
+ }
17218
+ return { rest, cwd };
17077
17219
  }
17078
17220
  var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
17079
17221
  var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
@@ -17353,7 +17495,7 @@ var repoAt = (dir) => {
17353
17495
  return { location, dir, branch: resolveTaskKey(dir, location).branch };
17354
17496
  };
17355
17497
  var isWorkitWorkspace = (repo) => {
17356
- if (fs17.existsSync(repo.location.dir))
17498
+ if (fs16.existsSync(repo.location.dir))
17357
17499
  return true;
17358
17500
  try {
17359
17501
  return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
@@ -17374,17 +17516,17 @@ var headSha2 = (repo) => {
17374
17516
  if (!git || git.reftable)
17375
17517
  return null;
17376
17518
  try {
17377
- const head = fs17.readFileSync(path21.join(git.gitDir, "HEAD"), "utf8").trim();
17519
+ const head = fs16.readFileSync(path20.join(git.gitDir, "HEAD"), "utf8").trim();
17378
17520
  if (/^[0-9a-f]{40,64}$/.test(head))
17379
17521
  return head;
17380
17522
  const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
17381
17523
  if (!ref)
17382
17524
  return null;
17383
- const common = path21.dirname(repo.location.dir);
17525
+ const common = path20.dirname(repo.location.dir);
17384
17526
  try {
17385
- return fs17.readFileSync(path21.join(common, ref), "utf8").trim() || null;
17527
+ return fs16.readFileSync(path20.join(common, ref), "utf8").trim() || null;
17386
17528
  } catch {}
17387
- const packed = fs17.readFileSync(path21.join(common, "packed-refs"), "utf8");
17529
+ const packed = fs16.readFileSync(path20.join(common, "packed-refs"), "utf8");
17388
17530
  return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
17389
17531
  } catch {
17390
17532
  return null;
@@ -17396,7 +17538,7 @@ var gitConfig = (dir, invocation, cache) => {
17396
17538
  if (cached)
17397
17539
  return cached;
17398
17540
  const config = new Map;
17399
- const listed = spawnSync12("git", [...invocation.config ?? [], "config", "-z", "--list"], {
17541
+ const listed = spawnSync11("git", [...invocation.config ?? [], "config", "-z", "--list"], {
17400
17542
  cwd: dir,
17401
17543
  encoding: "utf8",
17402
17544
  timeout: 3000,
@@ -17472,8 +17614,8 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
17472
17614
  const sub = invocation.args[0];
17473
17615
  if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
17474
17616
  return [invocation];
17475
- const dir = path21.resolve(root, invocation.dir ?? ".");
17476
- if (!fs17.existsSync(dir))
17617
+ const dir = path20.resolve(root, invocation.dir ?? ".");
17618
+ if (!fs16.existsSync(dir))
17477
17619
  return [invocation];
17478
17620
  const config = gitConfig(dir, invocation, cache);
17479
17621
  const alias = configValue(config, `alias.${sub.toLowerCase()}`);
@@ -17495,11 +17637,11 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
17495
17637
  var normalizedUrl = (url, dir) => {
17496
17638
  const trimmed = url.trim();
17497
17639
  if (/^file:\/\//i.test(trimmed))
17498
- return path21.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
17640
+ return path20.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
17499
17641
  const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
17500
17642
  const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
17501
17643
  if (!scheme && !scp)
17502
- return path21.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
17644
+ return path20.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
17503
17645
  const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
17504
17646
  return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
17505
17647
  };
@@ -17519,7 +17661,7 @@ var rewrittenUrl = (url, config) => {
17519
17661
  }
17520
17662
  return url;
17521
17663
  };
17522
- var pushUrl2 = (config, remote, dir) => {
17664
+ var pushUrl = (config, remote, dir) => {
17523
17665
  const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
17524
17666
  return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
17525
17667
  };
@@ -17547,7 +17689,7 @@ var toPushRemote = (repo, remote, config) => {
17547
17689
  const pushRemote = pushRemoteOf(config, repo.branch);
17548
17690
  if (pushRemote === null || pushRemote === remote)
17549
17691
  return true;
17550
- return pushUrl2(config, remote, repo.dir) === pushUrl2(config, pushRemote, repo.dir);
17692
+ return pushUrl(config, remote, repo.dir) === pushUrl(config, pushRemote, repo.dir);
17551
17693
  };
17552
17694
  var quote = (text) => `\`${text}\``;
17553
17695
  var refusal = (action, branch, dir) => {
@@ -17560,7 +17702,7 @@ var refusal = (action, branch, dir) => {
17560
17702
  };
17561
17703
  var refFacts = (repo, remote) => {
17562
17704
  const facts = { heads: new Set, tags: new Set, remoteHeads: new Set };
17563
- const listed = spawnSync12("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags", `refs/remotes/${remote}`], { cwd: repo.dir, encoding: "utf8", timeout: 5000 });
17705
+ const listed = spawnSync11("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags", `refs/remotes/${remote}`], { cwd: repo.dir, encoding: "utf8", timeout: 5000 });
17564
17706
  const remotePrefix = `refs/remotes/${remote}/`;
17565
17707
  for (const ref of (listed.stdout ?? "").split(`
17566
17708
  `)) {
@@ -17577,7 +17719,7 @@ var globBranches = (repo, glob) => {
17577
17719
  const source = glob.source.startsWith("refs/") ? glob.source : `refs/heads/${glob.source}`;
17578
17720
  const [sourcePrefix, sourceSuffix = ""] = source.split("*");
17579
17721
  const [destinationPrefix, destinationSuffix = ""] = glob.destination.split("*");
17580
- const listed = spawnSync12("git", [
17722
+ const listed = spawnSync11("git", [
17581
17723
  "for-each-ref",
17582
17724
  "--format=%(refname)",
17583
17725
  sourcePrefix.slice(0, sourcePrefix.lastIndexOf("/") + 1)
@@ -17654,7 +17796,7 @@ var tagRefusal = (action, facts, dir) => {
17654
17796
  return `workit: raw ${quote(`git push${action.allTags ? " --tags" : ""}`)} of ${tags.length ? `tag ${tags.join(", ")}` : "tags"} bypasses the release grant. Push a release tag with ${quote(`workit git push --release ${named}`)} (${shape} tags whose commit the production branch already has; a tag never moves), under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}).`;
17655
17797
  };
17656
17798
  var remoteHeads = (repo, remote) => {
17657
- const listed = spawnSync12("git", ["ls-remote", "--heads", remote], {
17799
+ const listed = spawnSync11("git", ["ls-remote", "--heads", remote], {
17658
17800
  cwd: repo.dir,
17659
17801
  encoding: "utf8",
17660
17802
  timeout: 3000,
@@ -17723,7 +17865,7 @@ var actionsIn = (cwd, command, dialect, cache = new Map) => {
17723
17865
  const out = [];
17724
17866
  const repos = new Map;
17725
17867
  const repoFor = (relative) => {
17726
- const dir = path21.resolve(cwd, relative ?? ".");
17868
+ const dir = path20.resolve(cwd, relative ?? ".");
17727
17869
  if (!repos.has(dir)) {
17728
17870
  const repo = repoAt(dir);
17729
17871
  repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
@@ -17809,7 +17951,7 @@ var analysisDenial = (error) => ({
17809
17951
  });
17810
17952
  var NEXT_COMMAND = "next";
17811
17953
  var MARKER_TTL_SECONDS = 3600;
17812
- var markerFile = (repo, session, key) => path21.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
17954
+ var markerFile = (repo, session, key) => path20.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
17813
17955
  var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
17814
17956
  function noteRawCommit(input, command, key) {
17815
17957
  try {
@@ -17820,14 +17962,14 @@ function noteRawCommit(input, command, key) {
17820
17962
  if (action.kind !== "commit")
17821
17963
  continue;
17822
17964
  const file = markerFile(repo, input.session.id, key);
17823
- fs17.mkdirSync(path21.dirname(file), { recursive: true });
17824
- fs17.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
17965
+ fs16.mkdirSync(path20.dirname(file), { recursive: true });
17966
+ fs16.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
17825
17967
  `);
17826
17968
  }
17827
17969
  } catch {}
17828
17970
  }
17829
17971
  var headCommit = (dir) => {
17830
- const run = spawnSync12("git", [
17972
+ const run = spawnSync11("git", [
17831
17973
  "log",
17832
17974
  "-1",
17833
17975
  "--format=%H%x1f%ct%x1f%s%x1f%(trailers:key=Workit-Session,valueonly,separator=%x2c)"
@@ -17844,11 +17986,11 @@ var headCommit = (dir) => {
17844
17986
  var settle2 = (input, repo, file) => {
17845
17987
  let marker;
17846
17988
  try {
17847
- marker = JSON.parse(fs17.readFileSync(file, "utf8"));
17989
+ marker = JSON.parse(fs16.readFileSync(file, "utf8"));
17848
17990
  } catch {
17849
17991
  return;
17850
17992
  }
17851
- fs17.rmSync(file, { force: true });
17993
+ fs16.rmSync(file, { force: true });
17852
17994
  const session = input.session.id;
17853
17995
  const now = Date.now() / 1000;
17854
17996
  if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
@@ -17897,7 +18039,7 @@ function settlePendingCommit(input) {
17897
18039
  if (!repo)
17898
18040
  return;
17899
18041
  const file = markerFile(repo, input.session.id, NEXT_COMMAND);
17900
- if (fs17.existsSync(file))
18042
+ if (fs16.existsSync(file))
17901
18043
  settle2(input, repo, file);
17902
18044
  } catch {}
17903
18045
  }
@@ -18041,7 +18183,7 @@ var targetsOf = (invocations) => invocations.flatMap((invocation) => invocation.
18041
18183
  branch,
18042
18184
  dir: invocation.dir
18043
18185
  })) : []);
18044
- var localBranchExists = (dir, branch) => spawnSync13("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
18186
+ var localBranchExists = (dir, branch) => spawnSync12("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
18045
18187
  cwd: dir,
18046
18188
  stdio: "ignore",
18047
18189
  timeout: 5000
@@ -18051,7 +18193,7 @@ var judgedDir = (root, dir) => {
18051
18193
  return null;
18052
18194
  if (dir === null)
18053
18195
  return root;
18054
- const resolved = path22.resolve(root, dir);
18196
+ const resolved = path21.resolve(root, dir);
18055
18197
  return existsSync7(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
18056
18198
  };
18057
18199
  var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
@@ -18287,7 +18429,7 @@ var judgeInputSummary = (raw) => {
18287
18429
 
18288
18430
  // packages/workit-core/src/autonomy.ts
18289
18431
  import os5 from "node:os";
18290
- import path23 from "node:path";
18432
+ import path22 from "node:path";
18291
18433
  var accountHome = () => {
18292
18434
  try {
18293
18435
  return os5.userInfo().homedir;
@@ -18296,10 +18438,10 @@ var accountHome = () => {
18296
18438
  }
18297
18439
  };
18298
18440
  var grantsHome = { resolve: accountHome };
18299
- var grantsDir = () => path23.join(grantsHome.resolve(), ".config", "workit");
18441
+ var grantsDir = () => path22.join(grantsHome.resolve(), ".config", "workit");
18300
18442
  var grantsOverride = () => {
18301
- const configured = path23.resolve(resolveConfigDir());
18302
- return configured === path23.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 ${path23.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
18443
+ const configured = path22.resolve(resolveConfigDir());
18444
+ return configured === path22.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 ${path22.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
18303
18445
  };
18304
18446
  var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
18305
18447
  var DEFAULT_GRANTS = Object.freeze({
@@ -20108,9 +20250,9 @@ class WorkitCore {
20108
20250
  }
20109
20251
  }
20110
20252
  // packages/workit-core/src/core/repo-context.ts
20111
- import { spawnSync as spawnSync14 } from "node:child_process";
20253
+ import { spawnSync as spawnSync13 } from "node:child_process";
20112
20254
  import { existsSync as existsSync8, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
20113
- import path24 from "node:path";
20255
+ import path23 from "node:path";
20114
20256
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
20115
20257
  try {
20116
20258
  gitRevisionParts(value);
@@ -20137,7 +20279,7 @@ var invalidRange = (cwd) => ({
20137
20279
  cwd
20138
20280
  });
20139
20281
  var runGit = (cwd, args) => {
20140
- const result = spawnSync14("git", args, { cwd, encoding: "utf8" });
20282
+ const result = spawnSync13("git", args, { cwd, encoding: "utf8" });
20141
20283
  return {
20142
20284
  stdout: (result.stdout ?? "").trimEnd(),
20143
20285
  stderr: (result.stderr ?? "").trimEnd(),
@@ -20244,7 +20386,7 @@ var findPrTemplate = (cwd) => {
20244
20386
  ]) {
20245
20387
  const parts = rel.split("/");
20246
20388
  const base = parts[parts.length - 1];
20247
- const dir = parts.length > 1 ? path24.join(cwd, ...parts.slice(0, -1)) : cwd;
20389
+ const dir = parts.length > 1 ? path23.join(cwd, ...parts.slice(0, -1)) : cwd;
20248
20390
  const actual = probeCaseInsensitive(dir, base);
20249
20391
  if (actual)
20250
20392
  return [...parts.slice(0, -1), actual].join("/");
@@ -20275,7 +20417,7 @@ var diffStatForRange = (cwd, range) => {
20275
20417
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
20276
20418
  };
20277
20419
  var packageScripts = (cwd, keys) => {
20278
- const pkgPath = path24.join(cwd, "package.json");
20420
+ const pkgPath = path23.join(cwd, "package.json");
20279
20421
  if (!existsSync8(pkgPath))
20280
20422
  return [];
20281
20423
  try {
@@ -20301,9 +20443,9 @@ var documentationFiles = (cwd) => {
20301
20443
  if (entry.isDirectory()) {
20302
20444
  if (excluded.has(entry.name))
20303
20445
  continue;
20304
- walk(path24.join(dir, entry.name), depth + 1);
20446
+ walk(path23.join(dir, entry.name), depth + 1);
20305
20447
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
20306
- matches.push("./" + path24.relative(cwd, path24.join(dir, entry.name)).replaceAll("\\", "/"));
20448
+ matches.push("./" + path23.relative(cwd, path23.join(dir, entry.name)).replaceAll("\\", "/"));
20307
20449
  }
20308
20450
  }
20309
20451
  };
@@ -20319,7 +20461,7 @@ var readTrimmed2 = (file, maxLines) => {
20319
20461
  `);
20320
20462
  };
20321
20463
  function prReadyContext(root, range) {
20322
- const cwd = path24.resolve(repoRoot(root));
20464
+ const cwd = path23.resolve(repoRoot(root));
20323
20465
  if (range !== undefined && !isResolvableContextRange(cwd, range))
20324
20466
  return invalidRange(cwd);
20325
20467
  let stdout = "";
@@ -20377,7 +20519,7 @@ ${status.stderr}` : "");
20377
20519
  stdout += `template_path: ${template}
20378
20520
 
20379
20521
  `;
20380
- stdout += readTrimmed2(path24.join(cwd, template), 220);
20522
+ stdout += readTrimmed2(path23.join(cwd, template), 220);
20381
20523
  } else {
20382
20524
  stdout += `template_path: none
20383
20525
 
@@ -20387,7 +20529,7 @@ ${status.stderr}` : "");
20387
20529
  stdout += `
20388
20530
  `;
20389
20531
  stdout += printSection("Recent Validation Signals");
20390
- if (existsSync8(path24.join(cwd, "package.json"))) {
20532
+ if (existsSync8(path23.join(cwd, "package.json"))) {
20391
20533
  stdout += `package.json detected. Common scripts:
20392
20534
  `;
20393
20535
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -20396,7 +20538,7 @@ ${status.stderr}` : "");
20396
20538
  `) + `
20397
20539
  `;
20398
20540
  }
20399
- if (existsSync8(path24.join(cwd, "Cargo.toml")) || existsSync8(path24.join(cwd, "src-tauri/Cargo.toml"))) {
20541
+ if (existsSync8(path23.join(cwd, "Cargo.toml")) || existsSync8(path23.join(cwd, "src-tauri/Cargo.toml"))) {
20400
20542
  stdout += `Rust project detected.
20401
20543
  `;
20402
20544
  }
@@ -20429,7 +20571,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
20429
20571
  - Apply with the host-native editor, preserving the repository changelog conventions.
20430
20572
  - If Unreleased already has duplicate category headings, normalize_only first.`;
20431
20573
  function changelogContext(root, range) {
20432
- const cwd = path24.resolve(repoRoot(root));
20574
+ const cwd = path23.resolve(repoRoot(root));
20433
20575
  if (range !== undefined && !isResolvableContextRange(cwd, range))
20434
20576
  return invalidRange(cwd);
20435
20577
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -20447,7 +20589,7 @@ function changelogContext(root, range) {
20447
20589
  stdout += CHANGELOG_RULES + `
20448
20590
  `;
20449
20591
  stdout += printSection("Existing CHANGELOG.md");
20450
- const changelogPath = path24.join(cwd, "CHANGELOG.md");
20592
+ const changelogPath = path23.join(cwd, "CHANGELOG.md");
20451
20593
  if (existsSync8(changelogPath)) {
20452
20594
  stdout += readTrimmed2(changelogPath, 260) + `
20453
20595
  `;
@@ -20467,7 +20609,7 @@ function changelogContext(root, range) {
20467
20609
  return { stdout, stderr: "", exitCode: 0, cwd };
20468
20610
  }
20469
20611
  function docsRefreshContext(root, range) {
20470
- const cwd = path24.resolve(repoRoot(root));
20612
+ const cwd = path23.resolve(repoRoot(root));
20471
20613
  if (range !== undefined && !isResolvableContextRange(cwd, range))
20472
20614
  return invalidRange(cwd);
20473
20615
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -20489,7 +20631,7 @@ function docsRefreshContext(root, range) {
20489
20631
  `) + `
20490
20632
  `;
20491
20633
  stdout += printSection("README Preview");
20492
- const readme = path24.join(cwd, "README.md");
20634
+ const readme = path23.join(cwd, "README.md");
20493
20635
  if (existsSync8(readme)) {
20494
20636
  stdout += readTrimmed2(readme, 220) + `
20495
20637
  `;
@@ -20498,7 +20640,7 @@ function docsRefreshContext(root, range) {
20498
20640
  `;
20499
20641
  }
20500
20642
  stdout += printSection("Package Scripts");
20501
- const pkgPath = path24.join(cwd, "package.json");
20643
+ const pkgPath = path23.join(cwd, "package.json");
20502
20644
  if (existsSync8(pkgPath)) {
20503
20645
  try {
20504
20646
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
@@ -20512,7 +20654,7 @@ function docsRefreshContext(root, range) {
20512
20654
  return { stdout, stderr: "", exitCode: 0, cwd };
20513
20655
  }
20514
20656
  function releaseNotesContext(root, rangeOrTag) {
20515
- const cwd = path24.resolve(repoRoot(root));
20657
+ const cwd = path23.resolve(repoRoot(root));
20516
20658
  if (!rangeOrTag) {
20517
20659
  return { stdout: "", stderr: `ERROR: release tag or range required
20518
20660
  `, exitCode: 1, cwd };
@@ -20550,7 +20692,7 @@ function releaseNotesContext(root, rangeOrTag) {
20550
20692
  `;
20551
20693
  stdout += printSection("Existing Release Files");
20552
20694
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
20553
- if (existsSync8(path24.join(cwd, rel)))
20695
+ if (existsSync8(path23.join(cwd, rel)))
20554
20696
  stdout += rel + `
20555
20697
  `;
20556
20698
  }
@@ -20579,6 +20721,46 @@ function releaseNotesContext(root, rangeOrTag) {
20579
20721
 
20580
20722
  // packages/workit-core/src/core/tracker-issues.ts
20581
20723
  import { spawnSync as spawnSync15 } from "node:child_process";
20724
+
20725
+ // packages/workit-core/src/core/pr-create.ts
20726
+ import fs17 from "node:fs";
20727
+ import path24 from "node:path";
20728
+ import { spawnSync as spawnSync14 } from "node:child_process";
20729
+ // packages/workit-core/src/forge/pr-body.ts
20730
+ function parseGhIssue(value) {
20731
+ const m = /issues\/(\d+)/.exec(value);
20732
+ return m ? m[1] : String(value).trim().replace(/^#/, "");
20733
+ }
20734
+
20735
+ // packages/workit-core/src/core/pr-create.ts
20736
+ function whichOnPath(tool) {
20737
+ const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
20738
+ for (const dir of (process.env.PATH ?? "").split(path24.delimiter)) {
20739
+ if (!dir)
20740
+ continue;
20741
+ for (const name of names) {
20742
+ const candidate = path24.join(dir, name);
20743
+ try {
20744
+ fs17.accessSync(candidate, fs17.constants.X_OK);
20745
+ return candidate;
20746
+ } catch {}
20747
+ }
20748
+ }
20749
+ return null;
20750
+ }
20751
+ var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
20752
+ var pushRemote = (cwd) => {
20753
+ const result = spawnSync14("git", ["remote", "get-url", "--push", "--all", "origin"], {
20754
+ cwd,
20755
+ encoding: "utf8"
20756
+ });
20757
+ if (result.status !== 0)
20758
+ return null;
20759
+ const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
20760
+ return urls.length === 1 ? urls[0] : null;
20761
+ };
20762
+
20763
+ // packages/workit-core/src/core/tracker-issues.ts
20582
20764
  var repoPathFromRemote = (remote) => {
20583
20765
  let rest = (remote || "").trim().replace(/\/+$/, "");
20584
20766
  if (!rest)
@@ -21404,6 +21586,53 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
21404
21586
  }
21405
21587
  return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
21406
21588
  };
21589
+ // packages/workit-core/src/hooks/delivery-verbs.ts
21590
+ var WORKIT_ALLOWED_VERBS = [
21591
+ "git",
21592
+ "pr",
21593
+ "ci",
21594
+ "stack",
21595
+ "release",
21596
+ "task",
21597
+ "ledger",
21598
+ "evidence",
21599
+ "finding",
21600
+ "decision",
21601
+ "worker",
21602
+ "verify-delivery",
21603
+ "policy",
21604
+ "doctor",
21605
+ "help"
21606
+ ];
21607
+ var WORKIT_BYPASS_FLAGS = ["--unverified", "--overwrite-unintegrated"];
21608
+ var ALLOWED = new Set(WORKIT_ALLOWED_VERBS);
21609
+ var isBypass = (word) => WORKIT_BYPASS_FLAGS.some((flag) => word === flag || word.startsWith(`${flag}=`));
21610
+ var isWorkitWord = (word) => /^(?:.*[\\/])?workit(?:-cli)?$/u.test(word);
21611
+ var bypassReason = (flag) => `Workit: \`${flag}\` overrides a Workit gate, so the user decides this call (no allow rule covers it).`;
21612
+ var EXPANSION_REASON = "Workit: an expansion before `--` could become a bypass flag, so the user decides this call (no allow rule covers it).";
21613
+ var workitBypassFlag = (command, dialect = "posix") => {
21614
+ for (const segment of segmentsOf(command, dialect)) {
21615
+ if (segment.unanalyzable) {
21616
+ const text = command.replaceAll(/['"\\`]/gu, "");
21617
+ if (!/\bworkit\b/u.test(text))
21618
+ return null;
21619
+ const found = WORKIT_BYPASS_FLAGS.find((flag) => text.includes(flag));
21620
+ return found ? bypassReason(found) : null;
21621
+ }
21622
+ const start = segment.words.findIndex(isWorkitWord);
21623
+ if (start < 0)
21624
+ continue;
21625
+ const words = segment.words.slice(start + 1);
21626
+ const end = words.indexOf("--");
21627
+ const flag = (end < 0 ? words : words.slice(0, end)).find(isBypass);
21628
+ if (flag)
21629
+ return bypassReason(flag.split("=")[0] ?? flag);
21630
+ const limit = start + 1 + (end < 0 ? words.length : end);
21631
+ if (segment.expands?.some((index) => index > start && index < limit))
21632
+ return EXPANSION_REASON;
21633
+ }
21634
+ return null;
21635
+ };
21407
21636
  // packages/workit-core/src/core/heavy-rules.ts
21408
21637
  import fs20 from "node:fs";
21409
21638
  import os6 from "node:os";
@@ -22788,7 +23017,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
22788
23017
  }
22789
23018
  const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
22790
23019
  if (inner !== null && dir !== undefined)
22791
- targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
23020
+ targets.push(...segmentTargets(workitRunDir(dir, words), inner, "posix", depth + 1, strict));
22792
23021
  if (strict && dir !== undefined) {
22793
23022
  const runIn = dir;
22794
23023
  for (const sub of depth < 4 ? segment.subs ?? [] : [])
@@ -22828,6 +23057,14 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
22828
23057
  }
22829
23058
  return targets;
22830
23059
  };
23060
+ var workitRunDir = (dir, words) => {
23061
+ for (const invocation of workitInvocations(words.map(quoteWord2).join(" "), "posix")) {
23062
+ const { at, args } = workitCwd(dir, invocation);
23063
+ if (args[0] === "check" || args[0] === "lock" && args[1] === "run")
23064
+ return at;
23065
+ }
23066
+ return dir;
23067
+ };
22831
23068
  var workitCwd = (dir, args) => {
22832
23069
  const { rest, cwd } = skipWorkitGlobals(args);
22833
23070
  return { at: cwd === null ? dir : path32.resolve(dir, homeExpanded(cwd)), args: rest };
@@ -23575,8 +23812,13 @@ var standingDelta = (input) => {
23575
23812
  };
23576
23813
  var sinceStart = new Map;
23577
23814
  // packages/workit-core/src/hooks/cwd-normalize.ts
23578
- var COMPOUND = /^\s*cd\s+([A-Za-z0-9_./@%+:,=-]+|'[^'$`\\\n]*'|"[^"$`\\!\n]*")\s*&&\s*(workit(?:\s[\s\S]*)?)$/u;
23815
+ var DIR = String.raw`[A-Za-z0-9_./@%+:,=-]+|'[^'$${"`"}\\\n]*'|"[^"$${"`"}\\!\n]*"`;
23816
+ var COMPOUND = new RegExp(String.raw`^\s*cd\s+(${DIR})\s*&&\s*(workit(?:\s[\s\S]*)?)$`, "u");
23817
+ var GLOBALS_FIRST = new RegExp(String.raw`^(\s*workit)((?:\s+(?:--json|--cwd(?:=|\s+)(?:${DIR})))+)\s+([a-z][a-z-]*)(?=\s|$)`, "u");
23818
+ var VERB_FIRST = /^((?:--json\s+)*)([a-z][a-z-]*)(?=\s|$)/u;
23819
+ var VERBS = new Set(WORKIT_ALLOWED_VERBS);
23579
23820
  var FILTERS = new Set(["tail", "head", "grep"]);
23821
+ var clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
23580
23822
  var stdinOnly = (words) => {
23581
23823
  const [name, ...args] = words;
23582
23824
  const operands = args.filter((w) => !w.startsWith("-"));
@@ -23598,14 +23840,28 @@ var cwdRewrite = (command) => {
23598
23840
  return null;
23599
23841
  if (head.words.some((w) => w === "--cwd" || w.startsWith("--cwd=")))
23600
23842
  return null;
23601
- const clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
23602
23843
  if (!segments.every(clean))
23603
23844
  return null;
23604
23845
  if (!segments.slice(0, -1).every((s) => s.pipe))
23605
23846
  return null;
23606
23847
  if (!filters.every((s) => FILTERS.has(s.words[0] ?? "") && stdinOnly(s.words)))
23607
23848
  return null;
23608
- return `workit --cwd ${dir} ${rest.slice("workit".length).trimStart()}`.trimEnd();
23849
+ const args = rest.slice("workit".length).trimStart();
23850
+ const verb = VERB_FIRST.exec(args);
23851
+ if (!verb?.[2] || !VERBS.has(verb[2]))
23852
+ return `workit --cwd ${dir} ${args}`.trimEnd();
23853
+ const json = verb[1] ? " --json" : "";
23854
+ return `workit ${verb[2]} --cwd ${dir}${json}${args.slice(verb[0].length)}`.trimEnd();
23855
+ };
23856
+ var verbFirst = (command) => {
23857
+ const match = GLOBALS_FIRST.exec(command);
23858
+ const [whole, lead, globals, verb] = match ?? [];
23859
+ if (!whole || !verb || !VERBS.has(verb) || !globals?.includes("--cwd"))
23860
+ return null;
23861
+ const segments = segmentsOf(command);
23862
+ if (segments.length !== 1 || !segments.every(clean))
23863
+ return null;
23864
+ return `${lead} ${verb}${globals}${command.slice(whole.length)}`;
23609
23865
  };
23610
23866
 
23611
23867
  // packages/workit-core/src/hooks/worktree-confine.ts
@@ -23721,7 +23977,7 @@ var hostIsolated = (cwd, agentId) => {
23721
23977
  };
23722
23978
  var shown = (target) => path34.basename(target) === ".workit-live-target" ? path34.dirname(target) : target;
23723
23979
  var unboundDeny = (input, target) => {
23724
- 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> git commit -m … -- <paths>`, `workit --cwd <worktree> check <name>`). If your brief names no worktree, stop and report that to the lead.";
23980
+ 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.";
23725
23981
  return {
23726
23982
  kind: "deny",
23727
23983
  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}`,
@@ -23729,7 +23985,7 @@ var unboundDeny = (input, target) => {
23729
23985
  };
23730
23986
  };
23731
23987
  var outsideDeny = (input, worktree, target) => {
23732
- const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit --cwd ${worktree} git commit -m … -- <paths>\`, \`workit --cwd ${worktree} check <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.`;
23988
+ 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.`;
23733
23989
  return {
23734
23990
  kind: "deny",
23735
23991
  reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
@@ -23738,7 +23994,7 @@ var outsideDeny = (input, worktree, target) => {
23738
23994
  };
23739
23995
  var unknownDirDeny = (input, worktree) => {
23740
23996
  const wt = worktree ?? "<worktree>";
23741
- const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
23997
+ const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit <verb> --cwd ${wt}\`.`;
23742
23998
  return {
23743
23999
  kind: "deny",
23744
24000
  reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
@@ -23896,7 +24152,7 @@ var subagentStartText = (input, descriptor, event) => {
23896
24152
  return `${isolatedGit(input.cwd)}
23897
24153
  ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
23898
24154
  if (role === "implementer")
23899
- 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> git commit -m "<message>" -- <paths>\`, \`workit --cwd <worktree> check <name>\`. The worktree is on the brief's branch once the lead made it; otherwise switch with \`workit --cwd <worktree> git branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
24155
+ 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.`;
23900
24156
  const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
23901
24157
  if (role !== "judge" || !event.agentId)
23902
24158
  return readOnly;
@@ -23913,7 +24169,7 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
23913
24169
  var PRE_TOOL = new Set(["shell.pre", "write.pre", "tool.pre"]);
23914
24170
  function handleHook(input, deps) {
23915
24171
  const decision = decide(input, deps);
23916
- if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny")
24172
+ if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny" || decision.kind === "ask")
23917
24173
  return decision;
23918
24174
  const delta = standingDelta(input);
23919
24175
  if (!delta)
@@ -23980,9 +24236,12 @@ function decide(input, deps) {
23980
24236
  if (plan.kind === "rewrite" && !rewrites && canDeny)
23981
24237
  return heavyDenial(event.command, plan);
23982
24238
  const lease = plan.kind === "rewrite" && rewrites ? plan : null;
23983
- const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) : null;
24239
+ const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) ?? verbFirst(event.command) : null;
23984
24240
  const aside = plan.kind === "passthrough" ? plan.note : null;
23985
24241
  noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
24242
+ const bypass = !lease && input.host === "claude_code" ? workitBypassFlag(event.command, event.dialect) : null;
24243
+ if (bypass)
24244
+ return { kind: "ask", reason: bypass };
23986
24245
  const rewrittenTo = lease?.command ?? cwdCommand;
23987
24246
  const rewritten = (text) => rewrittenTo ? {
23988
24247
  kind: "rewrite",