@brainervirus/workit-cli 2.2.2 → 2.4.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.
Files changed (2) hide show
  1. package/dist/index.js +3075 -27
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -73016,15 +73016,1851 @@ var init_check = __esm(() => {
73016
73016
  run12 = notImplemented("check", "S9b");
73017
73017
  });
73018
73018
 
73019
+ // packages/workit-core/src/git/rev.ts
73020
+ import { spawnSync as spawnSync13 } from "node:child_process";
73021
+ import { createHash as createHash12 } from "node:crypto";
73022
+ import fs15 from "node:fs";
73023
+ import os12 from "node:os";
73024
+ import path34 from "node:path";
73025
+ function networkGitInvocation(cwd, args, timeoutMs) {
73026
+ const seconds = Math.max(1, Math.ceil(timeoutMs / 1000));
73027
+ const env = {
73028
+ ...process.env,
73029
+ GIT_TERMINAL_PROMPT: "0",
73030
+ GIT_ASKPASS: "",
73031
+ SSH_ASKPASS: "",
73032
+ SSH_ASKPASS_REQUIRE: "never",
73033
+ GCM_INTERACTIVE: "never"
73034
+ };
73035
+ const base = process.env.GIT_SSH_COMMAND || line(git(cwd, ["config", "--get", "core.sshCommand"])) || (process.env.GIT_SSH ? null : "ssh");
73036
+ if (base)
73037
+ env.GIT_SSH_COMMAND = `${base} -o BatchMode=yes -o ConnectTimeout=${Math.min(10, seconds)}`;
73038
+ return {
73039
+ args: ["-c", "http.lowSpeedLimit=1", "-c", `http.lowSpeedTime=${seconds}`, ...args],
73040
+ env
73041
+ };
73042
+ }
73043
+ function redactRemote(raw) {
73044
+ const value = raw.trim();
73045
+ if (!value)
73046
+ return UNPARSEABLE;
73047
+ if (/^[A-Za-z][A-Za-z0-9+.-]*::/u.test(value))
73048
+ return UNPARSEABLE;
73049
+ if (/^[A-Za-z]:[\\/]/u.test(value) || value.startsWith("/") || value.startsWith("."))
73050
+ return value;
73051
+ if (!/[:@]/u.test(value))
73052
+ return value;
73053
+ if (!value.includes("://")) {
73054
+ if (/^[^/@]*:[^/]*@/u.test(value))
73055
+ return UNPARSEABLE;
73056
+ const scp = /^(?:([^@\s/:]+)@)?([^:/\s]+):([^?#]+)/u.exec(value);
73057
+ return scp ? `${scp[1] ? `${scp[1]}@` : ""}${scp[2]}:${scp[3]}` : UNPARSEABLE;
73058
+ }
73059
+ try {
73060
+ const url = new URL(value);
73061
+ const ssh = /^(?:ssh|git\+ssh|ssh\+git):$/u.test(url.protocol);
73062
+ url.password = "";
73063
+ if (!ssh)
73064
+ url.username = "";
73065
+ url.search = "";
73066
+ url.hash = "";
73067
+ return url.toString();
73068
+ } catch {
73069
+ return UNPARSEABLE;
73070
+ }
73071
+ }
73072
+ function headSha(cwd) {
73073
+ return line(git(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
73074
+ }
73075
+ function worktreeTree(cwd, options = {}) {
73076
+ const top = line(git(cwd, ["rev-parse", "--show-toplevel"]));
73077
+ const indexPath = line(git(cwd, ["rev-parse", "--path-format=absolute", "--git-path", "index"]));
73078
+ if (!top || !indexPath)
73079
+ return null;
73080
+ const cap = options.maxUntrackedBytes ?? MAX_UNTRACKED_BYTES;
73081
+ const timeoutMs = options.timeoutMs ?? GIT_TIMEOUTS.worktree;
73082
+ const scratch = fs15.mkdtempSync(path34.join(os12.tmpdir(), "workit-tree-"));
73083
+ try {
73084
+ const tempIndex = path34.join(scratch, "index");
73085
+ if (fs15.existsSync(indexPath))
73086
+ seedIndex(indexPath, tempIndex);
73087
+ const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
73088
+ const untracked = git(top, ["ls-files", "--others", "--exclude-standard", "-z"], {
73089
+ env,
73090
+ timeoutMs
73091
+ });
73092
+ if (!untracked.ok)
73093
+ return null;
73094
+ const skipped = [];
73095
+ for (const file of untracked.stdout.split("\x00").filter(Boolean)) {
73096
+ try {
73097
+ const stat = fs15.statSync(path34.join(top, file));
73098
+ if (stat.isFile() && stat.size > cap)
73099
+ skipped.push({ path: file, size: stat.size, mtimeMs: Math.trunc(stat.mtimeMs) });
73100
+ } catch {}
73101
+ }
73102
+ skipped.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
73103
+ const added = skipped.length ? git(top, ["add", "-A", "--pathspec-from-file=-", "--pathspec-file-nul"], {
73104
+ env,
73105
+ timeoutMs,
73106
+ input: [".", ...skipped.map((file) => `:(exclude,literal)${file.path}`)].join("\x00")
73107
+ }) : git(top, ["add", "-A"], { env, timeoutMs });
73108
+ if (!added.ok)
73109
+ return null;
73110
+ const tree = line(git(top, ["write-tree"], { env, timeoutMs }));
73111
+ if (!tree)
73112
+ return null;
73113
+ const base = line(git(top, ["rev-parse", "--verify", "-q", "HEAD^{tree}"])) ?? emptyTree(top);
73114
+ const key = skipped.length ? `sha256:${createHash12("sha256").update(JSON.stringify({ tree, skipped })).digest("hex")}` : tree;
73115
+ return { tree, key, dirty: tree !== base || skipped.length > 0, skipped };
73116
+ } finally {
73117
+ fs15.rmSync(scratch, { recursive: true, force: true });
73118
+ }
73119
+ }
73120
+ function patchId(cwd, base, head) {
73121
+ if (!safeArg(base) || !safeArg(head))
73122
+ return null;
73123
+ const diff = git(cwd, [
73124
+ "diff",
73125
+ "-U3",
73126
+ "--diff-algorithm=myers",
73127
+ "--indent-heuristic",
73128
+ "--no-color",
73129
+ "--no-ext-diff",
73130
+ "--no-textconv",
73131
+ "--no-renames",
73132
+ "--full-index",
73133
+ "--src-prefix=a/",
73134
+ "--dst-prefix=b/",
73135
+ `${base}...${head}`,
73136
+ "--"
73137
+ ]);
73138
+ if (!diff.ok || !diff.stdout.trim())
73139
+ return null;
73140
+ const id = git(cwd, ["patch-id", "--stable"], { input: diff.stdout });
73141
+ return line(id)?.split(/\s+/u)[0] ?? null;
73142
+ }
73143
+ function fetchRefs(cwd, remote, refspecs, options = {}) {
73144
+ if (!safeArg(remote) || refspecs.length === 0 || !refspecs.every((spec) => safeArg(spec.replace(/^\+/u, ""))))
73145
+ return { ok: false, code: "invalid_input", error: "remote and refspecs must not start with -" };
73146
+ const timeoutMs = options.timeoutMs ?? GIT_TIMEOUTS.network;
73147
+ const fetched = gitNetwork(cwd, [
73148
+ "fetch",
73149
+ "--no-tags",
73150
+ "--no-recurse-submodules",
73151
+ "--no-write-fetch-head",
73152
+ "--quiet",
73153
+ remote,
73154
+ ...refspecs
73155
+ ], timeoutMs);
73156
+ if (fetched.ok)
73157
+ return { ok: true };
73158
+ if (/\.lock'?:? File exists|unable to lock|another git process/iu.test(fetched.stderr))
73159
+ return {
73160
+ ok: false,
73161
+ code: "busy",
73162
+ error: `git fetch ${redactRemote(remote)}: the repository is locked by another git process`
73163
+ };
73164
+ return {
73165
+ ok: false,
73166
+ code: "unavailable",
73167
+ error: fetched.timedOut ? `git fetch ${redactRemote(remote)} timed out after ${timeoutMs} ms` : `git fetch ${redactRemote(remote)} failed (unreachable, not authorized, or ref missing)`
73168
+ };
73169
+ }
73170
+ function remoteNames(cwd) {
73171
+ return (git(cwd, ["remote"]).stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
73172
+ }
73173
+ function gitCommonDir(cwd) {
73174
+ return line(git(cwd, ["rev-parse", "--path-format=absolute", "--git-common-dir"]));
73175
+ }
73176
+ function hasCommit(cwd, rev) {
73177
+ if (!safeArg(rev))
73178
+ return false;
73179
+ return git(cwd, ["cat-file", "-e", `${rev}^{commit}`]).ok;
73180
+ }
73181
+ function resolveRef(cwd, ref) {
73182
+ if (!safeArg(ref))
73183
+ return null;
73184
+ return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
73185
+ }
73186
+ function aheadBehind(cwd, base, head) {
73187
+ if (!safeArg(base) || !safeArg(head))
73188
+ return null;
73189
+ const counted = line(git(cwd, ["rev-list", "--left-right", "--count", `${base}...${head}`]));
73190
+ const match = counted ? /^(\d+)\s+(\d+)$/u.exec(counted) : null;
73191
+ if (!match)
73192
+ return null;
73193
+ const upToDate = git(cwd, ["merge-base", "--is-ancestor", base, head]).ok;
73194
+ return { behind: Number(match[1]), ahead: Number(match[2]), upToDate };
73195
+ }
73196
+ function currentBranch2(cwd) {
73197
+ return line(git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
73198
+ }
73199
+ function pushRemoteName(cwd, branch) {
73200
+ const name = branch ?? currentBranch2(cwd);
73201
+ const remotes = remoteNames(cwd);
73202
+ const candidates = [
73203
+ name ? config2(cwd, `branch.${name}.pushRemote`) : null,
73204
+ config2(cwd, "remote.pushDefault"),
73205
+ name ? config2(cwd, `branch.${name}.remote`) : null
73206
+ ];
73207
+ for (const candidate of candidates)
73208
+ if (candidate && candidate !== "." && remotes.includes(candidate))
73209
+ return candidate;
73210
+ if (remotes.includes("origin"))
73211
+ return "origin";
73212
+ return remotes.length === 1 ? remotes[0] : null;
73213
+ }
73214
+ function pushUrl(cwd, remote) {
73215
+ if (!safeArg(remote))
73216
+ return null;
73217
+ const urls = git(cwd, ["remote", "get-url", "--push", "--all", remote]).stdout.split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
73218
+ return urls.length === 1 ? urls[0] : null;
73219
+ }
73220
+ function parseRemoteUrl(raw) {
73221
+ const value = raw.trim();
73222
+ if (!value || /^[A-Za-z][A-Za-z0-9+.-]*::/u.test(value))
73223
+ return null;
73224
+ if (/^[A-Za-z]:[\\/]/u.test(value) || value.startsWith("/") || value.startsWith(".")) {
73225
+ return { protocol: "file", user: null, host: "", port: null, path: value };
73226
+ }
73227
+ if (!value.includes("://")) {
73228
+ if (/^[^/@]*:[^/]*@/u.test(value))
73229
+ return null;
73230
+ const scp = /^(?:([^@\s/:]+)@)?([^:/\s]+):(.+)$/u.exec(value);
73231
+ if (!scp)
73232
+ return null;
73233
+ return {
73234
+ protocol: "ssh",
73235
+ user: scp[1] ?? null,
73236
+ host: scp[2],
73237
+ port: null,
73238
+ path: cleanPath(scp[3])
73239
+ };
73240
+ }
73241
+ let url;
73242
+ try {
73243
+ url = new URL(value);
73244
+ } catch {
73245
+ return null;
73246
+ }
73247
+ const scheme = url.protocol.replace(/:$/u, "");
73248
+ const protocol = scheme === "ssh" || scheme === "git+ssh" || scheme === "ssh+git" ? "ssh" : scheme === "https" || scheme === "http" || scheme === "git" || scheme === "file" ? scheme : null;
73249
+ if (!protocol)
73250
+ return null;
73251
+ return {
73252
+ protocol,
73253
+ user: protocol === "ssh" && url.username ? decodeURIComponent(url.username) : null,
73254
+ host: url.hostname.replace(/\.$/u, ""),
73255
+ port: url.port || null,
73256
+ path: cleanPath(decodeURIComponent(url.pathname))
73257
+ };
73258
+ }
73259
+ function readSshConfig(home = os12.homedir()) {
73260
+ try {
73261
+ return { text: fs15.readFileSync(path34.join(home, ".ssh", "config"), "utf8"), home };
73262
+ } catch {
73263
+ return { text: "", home };
73264
+ }
73265
+ }
73266
+ function resolveSshHost(alias, sshConfig) {
73267
+ const source = typeof sshConfig === "string" ? { text: sshConfig, home: os12.homedir() } : sshConfig;
73268
+ let hostname = null;
73269
+ let port = null;
73270
+ const evaluate = (text, initiallyActive, neverMatch, depth) => {
73271
+ let active = initiallyActive;
73272
+ for (const { key, args } of directives(text)) {
73273
+ if (key === "host") {
73274
+ active = !neverMatch && hostMatches(args, alias);
73275
+ continue;
73276
+ }
73277
+ if (key === "match") {
73278
+ active = false;
73279
+ continue;
73280
+ }
73281
+ if (key === "include") {
73282
+ if (depth >= 16)
73283
+ continue;
73284
+ for (const spec of args)
73285
+ for (const file of includeFiles(spec, source.home)) {
73286
+ let included;
73287
+ try {
73288
+ included = fs15.readFileSync(file, "utf8");
73289
+ } catch {
73290
+ continue;
73291
+ }
73292
+ evaluate(included, active, neverMatch || !active, depth + 1);
73293
+ }
73294
+ continue;
73295
+ }
73296
+ if (!active || args.length === 0)
73297
+ continue;
73298
+ if (key === "hostname" && hostname === null)
73299
+ hostname = args[0].replace(/%(%|h)/gu, (_, token) => token === "h" ? alias : "%");
73300
+ if (key === "port" && port === null)
73301
+ port = args[0];
73302
+ }
73303
+ };
73304
+ evaluate(source.text, true, false, 0);
73305
+ return { hostname: (hostname ?? alias).replace(/\.$/u, ""), port };
73306
+ }
73307
+ function deriveForge(url, options = {}) {
73308
+ const parsed = parseRemoteUrl(url);
73309
+ if (!parsed || parsed.protocol === "file" || !parsed.host)
73310
+ return null;
73311
+ const segments = parsed.path.split("/").filter(Boolean);
73312
+ if (segments.length < 2)
73313
+ return null;
73314
+ const hostname = (parsed.protocol === "ssh" ? resolveSshHost(parsed.host, options.sshConfig ?? readSshConfig()).hostname : parsed.host).toLowerCase();
73315
+ const withPort = parsed.protocol !== "ssh" && parsed.port ? `${hostname}:${parsed.port}` : hostname;
73316
+ const base = { host: parsed.host, hostname, repo: segments.join("/") };
73317
+ const known = KNOWN_HOSTS[hostname];
73318
+ if (known) {
73319
+ if (known.kind === "github" && segments.length !== 2)
73320
+ return null;
73321
+ return { ...base, kind: known.kind, apiHost: known.apiHost, via: "known_host" };
73322
+ }
73323
+ for (const kind of ["github", "gitlab"]) {
73324
+ const hosts = new Set((options.hosts?.[kind] ?? []).map(normalizeHost));
73325
+ if (hosts.has(withPort) || hosts.has(hostname)) {
73326
+ const apiHost = hosts.has(withPort) ? withPort : hostname;
73327
+ return { ...base, kind, apiHost, via: "configured_host" };
73328
+ }
73329
+ }
73330
+ const label = hostname.split(".")[0];
73331
+ if ((label === "github" || label === "gitlab") && hostname.includes("."))
73332
+ return { ...base, kind: label, apiHost: withPort, via: "host_name" };
73333
+ return null;
73334
+ }
73335
+ function pushForge(cwd, options = {}) {
73336
+ const remote = pushRemoteName(cwd, options.branch);
73337
+ if (!remote)
73338
+ return {
73339
+ ok: false,
73340
+ code: "not_found",
73341
+ error: "no push remote is configured for this branch",
73342
+ unblock: "git remote add origin <url>"
73343
+ };
73344
+ const url = pushUrl(cwd, remote);
73345
+ if (!url)
73346
+ return {
73347
+ ok: false,
73348
+ code: "unavailable",
73349
+ error: `remote "${remote}" has no single push URL`,
73350
+ unblock: `git remote get-url --push --all ${remote} # keep exactly one push URL`
73351
+ };
73352
+ const forge = deriveForge(url, options);
73353
+ if (!forge) {
73354
+ const host = parseRemoteUrl(url)?.host || null;
73355
+ return {
73356
+ ok: false,
73357
+ code: "unavailable",
73358
+ error: host ? `cannot tell whether push host "${host}" is GitHub or GitLab` : `push URL ${redactRemote(url)} of remote "${remote}" is not a GitHub/GitLab URL`,
73359
+ unblock: host ? `map the alias in ~/.ssh/config (Host ${host} / HostName github.com|gitlab.com) or set github.host / gitlab.host in ~/.config/workit/vcs.json` : `git remote set-url --push ${remote} <ssh-or-https-url>`
73360
+ };
73361
+ }
73362
+ return { ok: true, remote, url: redactRemote(url), forge };
73363
+ }
73364
+ function forgeConflict(derived, configuredProvider) {
73365
+ const configured = configuredProvider?.trim().toLowerCase();
73366
+ if (!configured || configured === derived.kind)
73367
+ return null;
73368
+ return {
73369
+ code: "blocked",
73370
+ error: `forge_mismatch: push remote ${derived.host} is ${derived.kind} but the workspace vcs.provider is ${configured}`,
73371
+ unblock: `set vcs.provider to "${derived.kind}" for this repo in ~/.config/workit/workspaces.json (workit-github-override), or push to a ${configured} remote`
73372
+ };
73373
+ }
73374
+ var MAX_BUFFER, GIT_TIMEOUTS, git = (cwd, args, options = {}) => {
73375
+ const result = spawnSync13("git", args, {
73376
+ cwd,
73377
+ encoding: "utf8",
73378
+ env: options.env ?? process.env,
73379
+ input: options.input,
73380
+ maxBuffer: MAX_BUFFER,
73381
+ timeout: options.timeoutMs ?? GIT_TIMEOUTS.local,
73382
+ killSignal: "SIGKILL",
73383
+ windowsHide: true
73384
+ });
73385
+ const error = result.error;
73386
+ return {
73387
+ ok: result.status === 0,
73388
+ stdout: result.stdout ?? "",
73389
+ stderr: result.stderr ?? "",
73390
+ timedOut: error?.code === "ETIMEDOUT"
73391
+ };
73392
+ }, line = (run) => {
73393
+ if (!run.ok)
73394
+ return null;
73395
+ const value = run.stdout.trim();
73396
+ return value ? value : null;
73397
+ }, safeArg = (value) => value.length > 0 && !value.startsWith("-"), WATCHDOG = `
73398
+ const cp = require("node:child_process");
73399
+ const { ms, args } = JSON.parse(process.env.WORKIT_GIT_WATCHDOG);
73400
+ delete process.env.WORKIT_GIT_WATCHDOG;
73401
+ const win = process.platform === "win32";
73402
+ const child = cp.spawn("git", args, { stdio: "inherit", detached: !win, windowsHide: true });
73403
+ const killTree = () => {
73404
+ if (!child.pid) return;
73405
+ if (win) cp.spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
73406
+ else try { process.kill(-child.pid, "SIGKILL"); } catch {}
73407
+ };
73408
+ let timedOut = false;
73409
+ const timer = setTimeout(() => { timedOut = true; killTree(); }, ms);
73410
+ child.on("error", () => { clearTimeout(timer); process.exit(127); });
73411
+ child.on("exit", (code) => {
73412
+ clearTimeout(timer);
73413
+ if (!win) killTree();
73414
+ process.exit(timedOut ? 124 : code ?? 1);
73415
+ });
73416
+ `, WATCHDOG_TIMEOUT_EXIT = 124, gitNetwork = (cwd, args, timeoutMs) => {
73417
+ const invocation = networkGitInvocation(cwd, args, timeoutMs);
73418
+ const result = spawnSync13(process.execPath, ["-e", WATCHDOG], {
73419
+ cwd,
73420
+ encoding: "utf8",
73421
+ env: {
73422
+ ...invocation.env,
73423
+ WORKIT_GIT_WATCHDOG: JSON.stringify({ ms: timeoutMs, args: invocation.args })
73424
+ },
73425
+ maxBuffer: MAX_BUFFER,
73426
+ timeout: timeoutMs + 1e4,
73427
+ killSignal: "SIGKILL",
73428
+ windowsHide: true
73429
+ });
73430
+ const error = result.error;
73431
+ return {
73432
+ ok: result.status === 0,
73433
+ stdout: result.stdout ?? "",
73434
+ stderr: result.stderr ?? "",
73435
+ timedOut: result.status === WATCHDOG_TIMEOUT_EXIT || error?.code === "ETIMEDOUT"
73436
+ };
73437
+ }, UNPARSEABLE = "<unparseable remote>", emptyTree = (cwd) => line(git(cwd, ["mktree"], { input: "" })), MAX_UNTRACKED_BYTES, seedIndex = (from, to) => {
73438
+ fs15.copyFileSync(from, to);
73439
+ const { atimeNs, mtimeNs } = fs15.statSync(from, { bigint: true });
73440
+ const seconds = (ns) => (Number(ns / 1000n) - 1) / 1e6;
73441
+ fs15.utimesSync(to, seconds(atimeNs), seconds(mtimeNs));
73442
+ }, config2 = (cwd, key) => line(git(cwd, ["config", "--get", key])), cleanPath = (value) => value.replace(/^\/+/u, "").replace(/\/+$/u, "").replace(/\.git$/u, ""), globToRegExp2 = (pattern) => new RegExp(`^${pattern.split("").map((char) => char === "*" ? ".*" : char === "?" ? "." : char.replace(/[.+^${}()|[\]\\]/gu, "\\$&")).join("")}$`, "iu"), hostMatches = (patterns, host) => {
73443
+ let matched = false;
73444
+ for (const pattern of patterns) {
73445
+ const negated = pattern.startsWith("!");
73446
+ if (globToRegExp2(negated ? pattern.slice(1) : pattern).test(host)) {
73447
+ if (negated)
73448
+ return false;
73449
+ matched = true;
73450
+ }
73451
+ }
73452
+ return matched;
73453
+ }, tokens = (rest) => [...rest.matchAll(/"([^"]*)"|(\S+)/gu)].map((match) => match[1] ?? match[2]), directives = (text) => text.split(/\r?\n/u).flatMap((raw) => {
73454
+ const trimmed = raw.trim();
73455
+ if (!trimmed || trimmed.startsWith("#"))
73456
+ return [];
73457
+ const match = /^(\S+?)(?:\s*=\s*|\s+)(.*)$/u.exec(trimmed);
73458
+ if (!match)
73459
+ return [];
73460
+ return [{ key: match[1].toLowerCase(), args: tokens(match[2]) }];
73461
+ }), includeFiles = (spec, home) => {
73462
+ const expanded = spec.startsWith("~") ? path34.join(home, spec.slice(1)) : spec;
73463
+ const absolute = path34.isAbsolute(expanded) ? expanded : path34.join(home, ".ssh", expanded);
73464
+ const dir = path34.dirname(absolute);
73465
+ const base = path34.basename(absolute);
73466
+ if (!/[*?]/u.test(base))
73467
+ return [absolute];
73468
+ try {
73469
+ const pattern = globToRegExp2(base);
73470
+ return fs15.readdirSync(dir).filter((name) => pattern.test(name)).toSorted().map((name) => path34.join(dir, name));
73471
+ } catch {
73472
+ return [];
73473
+ }
73474
+ }, KNOWN_HOSTS, normalizeHost = (value) => value.trim().toLowerCase().replace(/^https?:\/\//u, "").replace(/\/.*$/u, "").replace(/\.$/u, "");
73475
+ var init_rev = __esm(() => {
73476
+ MAX_BUFFER = 256 * 1024 * 1024;
73477
+ GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
73478
+ MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
73479
+ KNOWN_HOSTS = {
73480
+ "github.com": { kind: "github", apiHost: "github.com" },
73481
+ "ssh.github.com": { kind: "github", apiHost: "github.com" },
73482
+ "gitlab.com": { kind: "gitlab", apiHost: "gitlab.com" },
73483
+ "altssh.gitlab.com": { kind: "gitlab", apiHost: "gitlab.com" }
73484
+ };
73485
+ });
73486
+
73487
+ // packages/workit-core/src/forge/reruns.ts
73488
+ import fs16 from "node:fs";
73489
+ import path35 from "node:path";
73490
+ function rerunLogPath(cwd) {
73491
+ const common = gitCommonDir(cwd);
73492
+ return common ? path35.join(common, "workit", "ci-reruns.jsonl") : null;
73493
+ }
73494
+ function rerunCounts(cwd, key) {
73495
+ const counts = new Map;
73496
+ const file = rerunLogPath(cwd);
73497
+ if (!file)
73498
+ return counts;
73499
+ let text;
73500
+ try {
73501
+ text = fs16.readFileSync(file, "utf8");
73502
+ } catch {
73503
+ return counts;
73504
+ }
73505
+ for (const raw of text.split(`
73506
+ `)) {
73507
+ if (!raw.trim())
73508
+ continue;
73509
+ let row;
73510
+ try {
73511
+ row = JSON.parse(raw);
73512
+ } catch {
73513
+ continue;
73514
+ }
73515
+ if (isRow(row) && row.repo === key.repo && row.pr === key.pr && row.head === key.head)
73516
+ counts.set(row.check, (counts.get(row.check) ?? 0) + 1);
73517
+ }
73518
+ return counts;
73519
+ }
73520
+ function recordReruns(cwd, rows) {
73521
+ if (rows.length === 0)
73522
+ return true;
73523
+ const file = rerunLogPath(cwd);
73524
+ if (!file)
73525
+ return false;
73526
+ try {
73527
+ fs16.mkdirSync(path35.dirname(file), { recursive: true });
73528
+ fs16.appendFileSync(file, rows.map((row) => `${JSON.stringify(row)}
73529
+ `).join(""));
73530
+ return true;
73531
+ } catch {
73532
+ return false;
73533
+ }
73534
+ }
73535
+ function withRerunLock(cwd, fn) {
73536
+ const file = rerunLogPath(cwd);
73537
+ if (!file)
73538
+ return fn();
73539
+ const lock = `${file.slice(0, -".jsonl".length)}.lock`;
73540
+ fs16.mkdirSync(path35.dirname(lock), { recursive: true });
73541
+ const until = Date.now() + LOCK_WAIT_MS;
73542
+ for (;; ) {
73543
+ try {
73544
+ fs16.closeSync(fs16.openSync(lock, "wx"));
73545
+ break;
73546
+ } catch (error) {
73547
+ if (error.code !== "EEXIST")
73548
+ throw error;
73549
+ try {
73550
+ if (Date.now() - fs16.statSync(lock).mtimeMs > LOCK_STALE_MS) {
73551
+ fs16.rmSync(lock, { force: true });
73552
+ continue;
73553
+ }
73554
+ } catch {
73555
+ continue;
73556
+ }
73557
+ if (Date.now() >= until)
73558
+ return null;
73559
+ pause(50);
73560
+ }
73561
+ }
73562
+ try {
73563
+ return fn();
73564
+ } finally {
73565
+ fs16.rmSync(lock, { force: true });
73566
+ }
73567
+ }
73568
+ var isRow = (value) => {
73569
+ const row = value;
73570
+ return !!row && row.type === "ci.rerun" && typeof row.pr === "number" && typeof row.head === "string" && typeof row.check === "string" && typeof row.repo === "string";
73571
+ }, LOCK_WAIT_MS = 2000, LOCK_STALE_MS = 60000, pause = (ms) => {
73572
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
73573
+ };
73574
+ var init_reruns = __esm(() => {
73575
+ init_rev();
73576
+ });
73577
+
73578
+ // packages/workit-core/src/forge/types.ts
73579
+ var success2 = (data) => ({ ok: true, data }), failure3 = (code, error, unblock) => ({ ok: false, code, error, ...unblock ? { unblock } : {} });
73580
+
73581
+ // packages/workit-core/src/forge/report.ts
73582
+ function gatingChecks(status) {
73583
+ const required = status.checks.filter((check) => check.required === true);
73584
+ const protectionNamesChecks = (status.requiredContexts?.length ?? 0) > 0;
73585
+ return required.length > 0 || protectionNamesChecks ? { checks: required, protected: true } : { checks: status.checks, protected: false };
73586
+ }
73587
+ function missingRequired(status) {
73588
+ if (status.requiredContexts === null)
73589
+ return null;
73590
+ const seen = new Set(status.checks.map((check) => check.context));
73591
+ return status.requiredContexts.filter((context) => !seen.has(context));
73592
+ }
73593
+ function checksState(checks, missing = []) {
73594
+ if (checks.some((check) => check.state === "failing"))
73595
+ return "failing";
73596
+ if (checks.some((check) => check.state === "pending") || (missing?.length ?? 0) > 0)
73597
+ return "pending";
73598
+ return checks.length > 0 ? "passing" : "none";
73599
+ }
73600
+ function blockersOf(input) {
73601
+ if (input.state === "merged")
73602
+ return [{ next: "MERGED", reason: "merged" }];
73603
+ if (input.state === "closed")
73604
+ return [{ next: "CLOSED", reason: "closed" }];
73605
+ const out = [];
73606
+ const ms = input.mergeState ?? "";
73607
+ if (input.conflicts)
73608
+ out.push({ next: "RESOLVE_CONFLICTS", reason: "conflicts" });
73609
+ if (input.rebaseRequired)
73610
+ out.push({ next: "REBASE", reason: "behind_base_required" });
73611
+ if (input.threads > 0 || ms === "discussions_not_resolved")
73612
+ out.push({ next: "RESOLVE_THREADS", reason: "unresolved_threads" });
73613
+ if (input.checks === "failing")
73614
+ out.push({
73615
+ next: "FIX_CI",
73616
+ reason: input.checksRequired ? "required_checks_failing" : "checks_failing"
73617
+ });
73618
+ else if (input.checks === "pending" || ms === "ci_still_running" || ms === "ci_must_pass" && input.checks !== "passing")
73619
+ out.push({ next: "WAITING_CI", reason: "checks_pending" });
73620
+ if (input.reviewDecision === "changes_requested" || ms === "requested_changes")
73621
+ out.push({ next: "ADDRESS_REVIEW", reason: "changes_requested" });
73622
+ else if (input.reviewDecision === "review_required" || ms === "not_approved")
73623
+ out.push({ next: "REVIEW", reason: "review_required" });
73624
+ if (input.draft || ms === "draft" || ms === "draft_status")
73625
+ out.push({ next: "MARK_READY", reason: "draft" });
73626
+ if (input.inMergeQueue)
73627
+ out.push({ next: "IN_MERGE_QUEUE", reason: "in_merge_queue" });
73628
+ if (out.length === 0) {
73629
+ const allowed = input.forge === "github" ? GITHUB_MERGEABLE.has(ms) : GITLAB_MERGEABLE.has(ms);
73630
+ if (input.mergeable === "unknown")
73631
+ out.push({ next: "NOT_MERGEABLE", reason: "mergeability_unknown" });
73632
+ else if (!allowed)
73633
+ out.push({ next: "NOT_MERGEABLE", reason: `merge_state_${ms || "unknown"}` });
73634
+ }
73635
+ return out;
73636
+ }
73637
+ function computeBehindBase(cwd, remotes, baseSha, headSha_, options = {}) {
73638
+ const empty = { behind: null, ahead: null, upToDate: null };
73639
+ if (!baseSha)
73640
+ return success2({ ...empty, baseSha: null, error: "the forge reported no base tip" });
73641
+ for (const [sha, remote] of [
73642
+ [baseSha, remotes.base],
73643
+ [headSha_, remotes.head]
73644
+ ]) {
73645
+ if (hasCommit(cwd, sha))
73646
+ continue;
73647
+ const fetched = fetchRefs(cwd, remote, [sha], options);
73648
+ if (!fetched.ok && remote !== remotes.base)
73649
+ fetchRefs(cwd, remotes.base, [sha], options);
73650
+ if (!hasCommit(cwd, sha))
73651
+ return success2({
73652
+ ...empty,
73653
+ baseSha,
73654
+ error: fetched.ok ? `${sha.slice(0, 12)} is not available locally` : fetched.error
73655
+ });
73656
+ }
73657
+ const counts = aheadBehind(cwd, baseSha, headSha_);
73658
+ return success2(counts ? { ...counts, baseSha } : { ...empty, baseSha, error: "ahead/behind did not resolve" });
73659
+ }
73660
+ function selectPr(cwd, resolved, options) {
73661
+ if (options.pr)
73662
+ return success2(options.pr);
73663
+ const branch = options.branch ?? currentBranch2(cwd);
73664
+ if (!branch)
73665
+ return failure3("invalid_input", "HEAD is detached; pass --pr <n> or --branch <name>");
73666
+ const localSha = resolveRef(cwd, `refs/heads/${branch}`);
73667
+ const found = resolved.forge.findPr(branch, {
73668
+ owner: resolved.headRepo.split("/")[0] ?? null,
73669
+ projectId: resolved.headProjectId,
73670
+ sha: localSha
73671
+ });
73672
+ if (!found.ok)
73673
+ return found;
73674
+ if (!found.data) {
73675
+ const noun = resolved.forge.kind === "github" ? "pull request" : "merge request";
73676
+ return failure3("not_found", `no ${noun} for branch ${branch} from ${resolved.headRepo} in ${resolved.forge.repo}`, "push the branch and open one (workit pr create, S11), or pass --pr <n>");
73677
+ }
73678
+ return success2(found.data.number);
73679
+ }
73680
+ function buildStatusDoc(cwd, resolved, status, options) {
73681
+ const { forge } = resolved;
73682
+ const reruns = rerunCounts(cwd, { repo: forge.repo, pr: status.number, head: status.head.sha });
73683
+ const gating = gatingChecks(status);
73684
+ const missing = missingRequired(status);
73685
+ const failing = status.checks.filter((check) => check.state === "failing");
73686
+ const failingDocs = failing.map((check, index) => {
73687
+ const doc = {
73688
+ name: check.name,
73689
+ url: check.url,
73690
+ runId: check.runId,
73691
+ jobId: check.jobId,
73692
+ conclusion: check.conclusion,
73693
+ required: check.required,
73694
+ logTail: [],
73695
+ rerunsOnHead: reruns.get(check.name) ?? 0
73696
+ };
73697
+ if (check.jobId !== null && options.logLines > 0 && index < MAX_LOG_TAILS) {
73698
+ const tail = forge.jobLogTail({ jobId: check.jobId, scope: check.scope }, options.logLines);
73699
+ if (tail.ok)
73700
+ doc.logTail = tail.data;
73701
+ else
73702
+ doc.logError = tail.error;
73703
+ }
73704
+ return doc;
73705
+ });
73706
+ const local = currentBranch2(cwd) === status.head.branch ? headSha(cwd) : resolveRef(cwd, `refs/heads/${status.head.branch}`);
73707
+ let behindBase = null;
73708
+ if (options.behind && status.state === "open") {
73709
+ const counted = computeBehindBase(cwd, { base: resolved.baseRemote, head: resolved.remote }, status.baseSha, status.head.sha, options.behindTimeoutMs === undefined ? {} : { timeoutMs: options.behindTimeoutMs });
73710
+ if (!counted.ok)
73711
+ return counted;
73712
+ behindBase = counted.data;
73713
+ }
73714
+ const state = checksState(gating.checks, missing);
73715
+ const blockers = blockersOf({
73716
+ forge: forge.kind,
73717
+ state: status.state,
73718
+ draft: status.draft,
73719
+ conflicts: status.conflicts,
73720
+ rebaseRequired: status.rebaseRequired,
73721
+ mergeable: status.mergeable,
73722
+ mergeState: status.mergeState,
73723
+ inMergeQueue: status.inMergeQueue,
73724
+ reviewDecision: status.reviewDecision,
73725
+ threads: status.threads.length,
73726
+ checks: state,
73727
+ checksRequired: gating.protected
73728
+ });
73729
+ return success2({
73730
+ forge: forge.kind,
73731
+ repo: forge.repo,
73732
+ headRepo: resolved.fork ? resolved.headRepo : null,
73733
+ number: status.number,
73734
+ url: status.url,
73735
+ state: status.state,
73736
+ draft: status.draft,
73737
+ base: status.base,
73738
+ head: {
73739
+ branch: status.head.branch,
73740
+ sha: status.head.sha,
73741
+ localSha: local,
73742
+ pushed: local === null ? null : local === status.head.sha
73743
+ },
73744
+ mergeable: status.mergeable,
73745
+ mergeState: status.mergeState,
73746
+ conflicts: status.conflicts,
73747
+ rebaseRequired: status.rebaseRequired,
73748
+ inMergeQueue: status.inMergeQueue,
73749
+ behindBase,
73750
+ checks: {
73751
+ state,
73752
+ failing: failingDocs,
73753
+ pending: status.checks.filter((check) => check.state === "pending").map((check) => check.name),
73754
+ passing: status.checks.filter((check) => check.state === "passing").length,
73755
+ missingRequired: missing
73756
+ },
73757
+ reviews: { decision: status.reviewDecision, unresolvedThreads: status.threads },
73758
+ blockers: status.state === "open" ? blockers.map((blocker) => blocker.reason) : [],
73759
+ identity: options.identity ?? null,
73760
+ truncated: status.truncated,
73761
+ next: blockers[0]?.next ?? "READY"
73762
+ });
73763
+ }
73764
+ function prStatusReport(cwd, resolved, options = {}) {
73765
+ const number = selectPr(cwd, resolved, options);
73766
+ if (!number.ok)
73767
+ return number;
73768
+ const status = resolved.forge.prStatus(number.data);
73769
+ if (!status.ok)
73770
+ return status;
73771
+ const doc = buildStatusDoc(cwd, resolved, status.data, {
73772
+ logLines: options.logLines ?? 60,
73773
+ behind: options.behind ?? true,
73774
+ identity: options.identity
73775
+ });
73776
+ if (!doc.ok)
73777
+ return doc;
73778
+ return success2({ doc: doc.data, raw: status.data });
73779
+ }
73780
+ function waitVerdict(doc, options) {
73781
+ if (doc.state !== "open")
73782
+ return {
73783
+ state: "blocked",
73784
+ reason: `pr_${doc.state}`,
73785
+ unblock: `the ${doc.forge === "github" ? "PR" : "MR"} is ${doc.state}; nothing to wait for`
73786
+ };
73787
+ if (options.head && !doc.head.sha.startsWith(options.head))
73788
+ return { state: "waiting", reason: "head_mismatch" };
73789
+ const checks = doc.checks.state;
73790
+ if (checks === "failing")
73791
+ return { state: "failed", reason: "checks_failing" };
73792
+ if (checks === "pending")
73793
+ return { state: "waiting", reason: "checks_pending" };
73794
+ if (checks === "passing")
73795
+ return { state: "ready", reason: "checks_passing" };
73796
+ if (doc.conflicts)
73797
+ return {
73798
+ state: "blocked",
73799
+ reason: "conflicts",
73800
+ unblock: `rebase onto ${doc.base} and push; CI does not run on a conflicting PR`
73801
+ };
73802
+ const ciRequired = doc.checks.missingRequired === null || doc.mergeState === "ci_must_pass" || doc.mergeState === "ci_still_running";
73803
+ if (ciRequired)
73804
+ return { state: "waiting", reason: "no_checks_yet" };
73805
+ if (options.elapsedMs < (options.noChecksGraceMs ?? NO_CHECKS_GRACE_MS))
73806
+ return { state: "waiting", reason: "no_checks_yet" };
73807
+ return { state: "ready", reason: "no_checks" };
73808
+ }
73809
+ function pollDelay(intervalMs, poll) {
73810
+ const cap = Math.max(intervalMs, 120000);
73811
+ return Math.min(cap, Math.round(intervalMs * 1.5 ** poll));
73812
+ }
73813
+ function planRerun(status, selection) {
73814
+ let checks;
73815
+ if (selection.names.length > 0) {
73816
+ const unknown = selection.names.filter((name) => !status.checks.some((check) => check.name === name));
73817
+ if (unknown.length)
73818
+ return failure3("invalid_input", `unknown check ${unknown.map((name) => JSON.stringify(name)).join(", ")}; known: ${status.checks.map((check) => check.name).join(", ") || "none"}`);
73819
+ checks = status.checks.filter((check) => selection.names.includes(check.name));
73820
+ const running = checks.filter((check) => check.state === "pending");
73821
+ if (running.length)
73822
+ return failure3("busy", `still running: ${running.map((check) => check.name).join(", ")}`, "workit ci wait");
73823
+ } else {
73824
+ checks = status.checks.filter((check) => check.state === "failing");
73825
+ }
73826
+ if (checks.length === 0)
73827
+ return failure3("not_found", `no failing checks on head ${status.head.sha.slice(0, 12)}`);
73828
+ const rerunnable = checks.filter((check) => check.jobId !== null && check.runId !== null);
73829
+ const skipped = checks.filter((check) => !rerunnable.includes(check)).map((check) => check.name);
73830
+ const targets = [];
73831
+ if (selection.names.length === 0) {
73832
+ const busy = status.checks.filter((check) => check.state === "pending" && check.runId !== null && rerunnable.some((failing) => failing.runId === check.runId));
73833
+ if (busy.length)
73834
+ return failure3("busy", `the failed jobs' run is still in progress (${busy.map((check) => check.name).join(", ")})`, "workit ci wait, then rerun");
73835
+ const seen = new Set;
73836
+ for (const check of rerunnable)
73837
+ if (!seen.has(check.runId)) {
73838
+ seen.add(check.runId);
73839
+ targets.push({ kind: "failed_in_run", runId: check.runId, scope: check.scope });
73840
+ }
73841
+ } else {
73842
+ for (const check of rerunnable)
73843
+ targets.push({ kind: "job", jobId: check.jobId, scope: check.scope });
73844
+ }
73845
+ if (targets.length === 0)
73846
+ return failure3("unavailable", `${skipped.join(", ")} cannot be rerun from here (not a CI job); rerun it on the forge`);
73847
+ return success2({ checks: rerunnable, targets, skipped });
73848
+ }
73849
+ function executeRerun(cwd, resolved, status, options) {
73850
+ if (status.state !== "open")
73851
+ return failure3("blocked", `${status.state} PR: CI cannot be rerun`, "nothing to do");
73852
+ const plan = planRerun(status, options);
73853
+ if (!plan.ok)
73854
+ return plan;
73855
+ const locked = withRerunLock(cwd, () => rerunLocked(cwd, resolved, status, plan.data, options));
73856
+ return locked ?? failure3("busy", "another ci rerun is in progress for this repository", "retry in a moment");
73857
+ }
73858
+ function rerunLocked(cwd, resolved, status, plan, options) {
73859
+ const { forge } = resolved;
73860
+ const counts = rerunCounts(cwd, { repo: forge.repo, pr: status.number, head: status.head.sha });
73861
+ const repeated = plan.checks.filter((check) => (counts.get(check.name) ?? 0) > 0);
73862
+ if (repeated.length && !options.force)
73863
+ return failure3("blocked", `rerun_limit: ${repeated.map((check) => check.name).join(", ")} already rerun once on head ${status.head.sha.slice(0, 12)}`, "treat it as a real failure and fix it; if it is a confirmed flake, rerun with --force");
73864
+ const accepted = [];
73865
+ for (const target of plan.targets) {
73866
+ const done = forge.rerun(target);
73867
+ if (!done.ok) {
73868
+ recordAccepted(cwd, resolved, status, accepted, options);
73869
+ return failure3(done.code, accepted.length ? `${done.error} (already rerun: ${accepted.map((check) => check.name).join(", ")})` : done.error, done.unblock);
73870
+ }
73871
+ accepted.push(...plan.checks.filter((check) => target.kind === "failed_in_run" ? check.runId === target.runId : check.jobId === target.jobId));
73872
+ }
73873
+ const recorded = recordAccepted(cwd, resolved, status, accepted, options);
73874
+ return success2({
73875
+ pr: status.number,
73876
+ head: status.head.sha,
73877
+ reason: options.reason,
73878
+ forced: options.force,
73879
+ rerun: accepted.map((check) => ({ name: check.name, runId: check.runId, jobId: check.jobId })),
73880
+ skipped: plan.skipped,
73881
+ recorded
73882
+ });
73883
+ }
73884
+ function recordAccepted(cwd, resolved, status, accepted, options) {
73885
+ const at = new Date().toISOString();
73886
+ const rows = accepted.map((check) => ({
73887
+ v: 1,
73888
+ at,
73889
+ type: "ci.rerun",
73890
+ forge: resolved.forge.kind,
73891
+ repo: resolved.forge.repo,
73892
+ pr: status.number,
73893
+ head: status.head.sha,
73894
+ check: check.name,
73895
+ reason: options.reason,
73896
+ forced: options.force
73897
+ }));
73898
+ return recordReruns(cwd, rows);
73899
+ }
73900
+ var MAX_LOG_TAILS = 5, GITLAB_MERGEABLE, GITHUB_MERGEABLE, NO_CHECKS_GRACE_MS = 90000;
73901
+ var init_report = __esm(() => {
73902
+ init_rev();
73903
+ init_reruns();
73904
+ GITLAB_MERGEABLE = new Set(["mergeable"]);
73905
+ GITHUB_MERGEABLE = new Set(["clean", "unstable", "has_hooks"]);
73906
+ });
73907
+
73908
+ // packages/workit-core/src/forge/redact.ts
73909
+ function redactText(value) {
73910
+ let out = stripAnsi2(value).replace(PRIVATE_KEY, "[REDACTED PRIVATE KEY]");
73911
+ out = out.replace(WEBHOOK, `$1/${R}`);
73912
+ out = out.replace(URL_USERINFO, `$1${R}@`);
73913
+ for (const pattern of TOKEN_PATTERNS)
73914
+ out = out.replace(pattern, R);
73915
+ out = out.replace(CURL_USER, `$1$2${R}$2`);
73916
+ out = out.replace(BEARER, `$1${R}`);
73917
+ out = out.replace(QUOTED_ASSIGNMENT, (match, lead, quote, secret) => secret.startsWith(R) ? match : `${lead}${quote}${R}${quote}`);
73918
+ out = out.replace(NETRC, `$1${R}`);
73919
+ for (const pattern of FLAG_SECRETS)
73920
+ out = out.replace(pattern, `$1${R}`);
73921
+ out = out.replace(QUERY_SECRET, `$1${R}`);
73922
+ out = out.replace(ASSIGNMENT, (match, lead, scheme, secret) => secret.startsWith(R) ? match : `${lead}${scheme ?? ""}${R}`);
73923
+ return out.split(`
73924
+ `).map((line) => line.replace(BASE64, (blob) => /\bbase64\b/iu.test(line) || decodesToSecret(blob) ? R : blob)).join(`
73925
+ `);
73926
+ }
73927
+ function logTail(text, lines) {
73928
+ if (lines <= 0)
73929
+ return [];
73930
+ const cleaned = text.replace(GITLAB_SECTION, "").split(/\r?\n/u).map((raw) => stripAnsi2(raw).split("\r").findLast((part) => part.trim()) ?? "").map((raw) => raw.replace(LINE_NOISE, ""));
73931
+ const all = redactText(cleaned.join(`
73932
+ `)).split(`
73933
+ `);
73934
+ let end = all.length;
73935
+ for (let index = all.length - 1;index >= 0; index -= 1)
73936
+ if (FAILURE_END.some((pattern) => pattern.test(all[index]))) {
73937
+ end = index + 1;
73938
+ break;
73939
+ }
73940
+ const kept = all.slice(0, end);
73941
+ while (kept.length > 0 && !kept[kept.length - 1].trim())
73942
+ kept.pop();
73943
+ const cap = (value) => value.length > MAX_LINE ? `${value.slice(0, MAX_LINE)}…` : value;
73944
+ const tailStart = Math.max(0, kept.length - lines);
73945
+ const find = (pattern) => kept.findIndex((value) => pattern.test(value) && !NOT_FIRST_ERROR.test(value));
73946
+ const strong = find(STRONG_ERROR);
73947
+ const first = strong >= 0 ? strong : find(WEAK_ERROR);
73948
+ if (first < 0 || first >= tailStart || lines < 8)
73949
+ return kept.slice(tailStart).map(cap);
73950
+ const headCount = Math.ceil((lines - 1) * (2 / 3));
73951
+ const from = Math.max(0, first - LEAD);
73952
+ const head = kept.slice(from, from + headCount);
73953
+ const tailCount = lines - 1 - head.length;
73954
+ const tailFrom = Math.max(from + head.length, kept.length - tailCount);
73955
+ const tail = kept.slice(tailFrom);
73956
+ return [...head, ...tailFrom > from + head.length ? ["…"] : [], ...tail].map(cap);
73957
+ }
73958
+ function shortBody(value, max = 300) {
73959
+ const flat = redactText(value).replace(/\s+/gu, " ").trim();
73960
+ return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
73961
+ }
73962
+ var R = "[REDACTED]", TOKEN_PATTERNS, WEBHOOK, URL_USERINFO, CURL_USER, KEY, ASSIGNMENT, BEARER, QUOTED_ASSIGNMENT, NETRC, FLAG_SECRETS, QUERY_SECRET, PRIVATE_KEY, BASE64, ANSI, stripAnsi2 = (value) => value.replace(ANSI, ""), decodesToSecret = (blob) => {
73963
+ if (/^[0-9a-f]+$/iu.test(blob))
73964
+ return false;
73965
+ let decoded;
73966
+ try {
73967
+ decoded = Buffer.from(blob, "base64").toString("utf8");
73968
+ } catch {
73969
+ return false;
73970
+ }
73971
+ if (!/^[\x20-\x7e\s]+$/u.test(decoded))
73972
+ return false;
73973
+ return TOKEN_PATTERNS.some((pattern) => new RegExp(pattern.source, "u").test(decoded)) || /^[^\s:]{1,64}:[^\s]{6,}$/u.test(decoded.trim());
73974
+ }, LINE_NOISE, GITLAB_SECTION, FAILURE_END, MAX_LINE = 400, STRONG_ERROR, WEAK_ERROR, NOT_FIRST_ERROR, LEAD = 3;
73975
+ var init_redact = __esm(() => {
73976
+ TOKEN_PATTERNS = [
73977
+ /\b(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})/gu,
73978
+ /\bgl[a-z]{1,8}-[A-Za-z0-9_-]{16,}/gu,
73979
+ /\bnpm_[A-Za-z0-9]{30,}/gu,
73980
+ /\bxox[abposr]-[A-Za-z0-9-]{10,}/gu,
73981
+ /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/gu,
73982
+ /\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{20,}/gu,
73983
+ /\b[sr]k_(?:live|test)_[A-Za-z0-9]{16,}/gu,
73984
+ /\bAIza[0-9A-Za-z_-]{35}/gu,
73985
+ /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/gu
73986
+ ];
73987
+ WEBHOOK = /\b(https?:\/\/(?:hooks\.slack\.com\/(?:services|workflows|triggers)|discord(?:app)?\.com\/api\/webhooks))\/[^\s"'<>]+/giu;
73988
+ URL_USERINFO = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/giu;
73989
+ CURL_USER = /(\s(?:-u|--user)(?:\s+|=))(["']?)[^\s"':]+:[^\s"']+\2/gu;
73990
+ 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_.-]*`;
73991
+ ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*["']?)((?:bearer|basic|token|digest)\s+)?([^\s"',;&]+)`, "giu");
73992
+ BEARER = /\b(bearer\s+)[A-Za-z0-9._~+/-]{12,}=*/giu;
73993
+ QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
73994
+ NETRC = /\b((?:machine|login)\s+\S+\s+(?:login\s+\S+\s+)?password\s+)\S+/giu;
73995
+ FLAG_SECRETS = [
73996
+ /\b(sshpass\s+-p\s*)\S+/gu,
73997
+ /\b(mysql(?:dump|admin|import|show)?\b[^\n]*?\s-p)(?!\s|$)\S+/gu
73998
+ ];
73999
+ QUERY_SECRET = /([?&;](?:sig|signature|token|access_token|api_key|apikey|key|password|secret|client_secret|X-Amz-Signature|X-Goog-Signature)=)[^&\s#"']+/giu;
74000
+ PRIVATE_KEY = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----|$)/gu;
74001
+ BASE64 = /[A-Za-z0-9+/]{32,}={0,2}/gu;
74002
+ ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
74003
+ LINE_NOISE = /^(?:\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z\s?)?(?:\d{2}[A-Z]\s)?/u;
74004
+ GITLAB_SECTION = /section_(?:start|end):\d+:[A-Za-z0-9_.-]+(?:\[[^\]]*\])?\r?/gu;
74005
+ FAILURE_END = [/##\[error\]Process completed with exit code/u, /^ERROR: Job failed\b/u];
74006
+ STRONG_ERROR = /^\s*(?:\(fail\)|FAIL\b|✗|✕|×|error(?:\[[A-Z0-9]+\])?:|[A-Za-z]*Error\b:|AssertionError|Traceback|panic:|Exception\b)/u;
74007
+ WEAK_ERROR = /\berror\b[:[]|\bERROR\b|\bFAIL(?:ED)?\b|\bfailed\b|\bassert/u;
74008
+ NOT_FIRST_ERROR = /^\s*(?:\(pass\)|\(skip\)|\(todo\)|✓|✔|ok\b|PASS\b)|^\s*\d+ fail\b|##\[error\]Process completed|^\s*0 (?:errors?|fail)/u;
74009
+ });
74010
+
74011
+ // packages/workit-core/src/forge/exec.ts
74012
+ import { spawnSync as spawnSync14 } from "node:child_process";
74013
+ import fs17 from "node:fs";
74014
+ import path36 from "node:path";
74015
+ function findExecutable(bin, env) {
74016
+ const dirs = (env.PATH ?? env.Path ?? "").split(path36.delimiter).filter(Boolean);
74017
+ const exts = process.platform === "win32" ? [".exe"] : [""];
74018
+ for (const dir of dirs)
74019
+ for (const ext of exts) {
74020
+ const candidate = path36.join(dir, `${bin}${ext}`);
74021
+ try {
74022
+ const stat = fs17.statSync(candidate);
74023
+ if (stat.isFile())
74024
+ return candidate;
74025
+ } catch {}
74026
+ }
74027
+ return null;
74028
+ }
74029
+ function systemRunner(kind, apiHost, env = process.env) {
74030
+ const childEnv = {
74031
+ ...env,
74032
+ ...kind === "github" ? { GH_HOST: apiHost } : { GITLAB_HOST: apiHost },
74033
+ GH_PROMPT_DISABLED: "1",
74034
+ GH_NO_UPDATE_NOTIFIER: "1",
74035
+ GLAB_CHECK_UPDATE: "false",
74036
+ NO_PROMPT: "1",
74037
+ NO_COLOR: "1",
74038
+ GH_PAGER: "",
74039
+ GLAB_PAGER: "",
74040
+ PAGER: ""
74041
+ };
74042
+ return (bin, args, options) => {
74043
+ const exe = findExecutable(bin, env);
74044
+ if (!exe)
74045
+ return { status: null, stdout: "", stderr: "", timedOut: false, missing: true };
74046
+ const token = options.token ? kind === "github" ? { GH_TOKEN: options.token, GH_ENTERPRISE_TOKEN: options.token } : { GITLAB_TOKEN: options.token } : {};
74047
+ const result = spawnSync14(exe, [...args], {
74048
+ encoding: "utf8",
74049
+ env: { ...childEnv, ...token },
74050
+ maxBuffer: MAX_BUFFER2,
74051
+ timeout: options.timeoutMs,
74052
+ killSignal: "SIGKILL",
74053
+ windowsHide: true,
74054
+ stdio: ["ignore", "pipe", "pipe"]
74055
+ });
74056
+ const error = result.error;
74057
+ return {
74058
+ status: result.status,
74059
+ stdout: result.stdout ?? "",
74060
+ stderr: result.stderr ?? "",
74061
+ timedOut: error?.code === "ETIMEDOUT",
74062
+ missing: error?.code === "ENOENT"
74063
+ };
74064
+ };
74065
+ }
74066
+ function cliFailure(bin, apiHost, run, what, timeoutMs) {
74067
+ if (run.missing)
74068
+ return failure3("unavailable", `${bin} is not installed`, installHint(bin));
74069
+ if (run.timedOut)
74070
+ return failure3("unavailable", `${bin} api ${what} timed out after ${timeoutMs} ms`);
74071
+ const stderr = redactText(run.stderr).trim();
74072
+ if (/auth login|not logged in|no token|authentication required|bad credentials|\b401\b|unauthorized/iu.test(stderr))
74073
+ return failure3("unavailable", `${bin} is not authenticated for ${apiHost}`, loginHint(bin, apiHost));
74074
+ if (/\b404\b|not found/iu.test(stderr))
74075
+ return failure3("not_found", `${what} was not found`);
74076
+ const first = stderr.split(/\r?\n/u).find((value) => value.trim()) ?? `exit ${run.status}`;
74077
+ return failure3("failed", `${bin} api ${what} failed: ${first.slice(0, 200)}`);
74078
+ }
74079
+ function apiJson(runner, bin, apiHost, args, what, timeoutMs = FORGE_TIMEOUTS.api, options = {}) {
74080
+ const run = runner(bin, args, { timeoutMs });
74081
+ if (run.status !== 0) {
74082
+ if (options.graphql && !run.missing && !run.timedOut)
74083
+ try {
74084
+ const body = JSON.parse(run.stdout);
74085
+ if (Array.isArray(body.errors))
74086
+ return { ok: true, data: body };
74087
+ } catch {}
74088
+ return cliFailure(bin, apiHost, run, what, timeoutMs);
74089
+ }
74090
+ try {
74091
+ return { ok: true, data: JSON.parse(run.stdout) };
74092
+ } catch {
74093
+ return failure3("failed", `${bin} api ${what} returned invalid JSON`);
74094
+ }
74095
+ }
74096
+ function apiText(runner, bin, apiHost, args, what, timeoutMs = FORGE_TIMEOUTS.log) {
74097
+ const run = runner(bin, args, { timeoutMs });
74098
+ if (run.status !== 0)
74099
+ return cliFailure(bin, apiHost, run, what, timeoutMs);
74100
+ return { ok: true, data: run.stdout };
74101
+ }
74102
+ var FORGE_TIMEOUTS, MAX_BUFFER2, installHint = (bin) => bin === "gh" ? "install the GitHub CLI (https://cli.github.com) and run: gh auth login" : "install glab (https://gitlab.com/gitlab-org/cli) and run: glab auth login", loginHint = (bin, apiHost) => `${bin} auth login --hostname ${apiHost}`;
74103
+ var init_exec = __esm(() => {
74104
+ init_redact();
74105
+ FORGE_TIMEOUTS = { api: 20000, log: 30000 };
74106
+ MAX_BUFFER2 = 64 * 1024 * 1024;
74107
+ });
74108
+
74109
+ // packages/workit-core/src/forge/github.ts
74110
+ function checkFromContext(node) {
74111
+ const required = node.isRequired ?? null;
74112
+ if (node.__typename === "StatusContext") {
74113
+ const state = node.state.toUpperCase();
74114
+ return {
74115
+ name: node.context,
74116
+ context: node.context,
74117
+ state: state === "SUCCESS" ? "passing" : state === "FAILURE" || state === "ERROR" ? "failing" : "pending",
74118
+ conclusion: state.toLowerCase(),
74119
+ url: safeUrl(node.targetUrl),
74120
+ runId: null,
74121
+ jobId: null,
74122
+ scope: null,
74123
+ required
74124
+ };
74125
+ }
74126
+ if (node.conclusion === "STALE")
74127
+ return null;
74128
+ const run = node.checkSuite?.workflowRun ?? null;
74129
+ const workflow = run?.workflow?.name;
74130
+ const completed = node.status === "COMPLETED";
74131
+ const conclusion = node.conclusion?.toUpperCase() ?? null;
74132
+ return {
74133
+ name: workflow ? `${workflow} / ${node.name}` : node.name,
74134
+ context: node.name,
74135
+ state: !completed ? "pending" : conclusion && FAILING.has(conclusion) ? "failing" : conclusion === "SKIPPED" || conclusion === "NEUTRAL" ? "skipped" : "passing",
74136
+ conclusion: completed ? conclusion?.toLowerCase() ?? null : node.status.toLowerCase(),
74137
+ url: safeUrl(node.detailsUrl),
74138
+ runId: run?.databaseId ?? null,
74139
+ jobId: run ? node.databaseId ?? null : null,
74140
+ scope: null,
74141
+ required
74142
+ };
74143
+ }
74144
+ function latestPerName(checks) {
74145
+ const byName = new Map;
74146
+ for (const check of checks) {
74147
+ const seen = byName.get(check.name);
74148
+ if (!seen || (check.jobId ?? 0) >= (seen.jobId ?? 0))
74149
+ byName.set(check.name, check);
74150
+ }
74151
+ return [...byName.values()];
74152
+ }
74153
+ function threadFrom(node) {
74154
+ const first = node.comments.nodes[0];
74155
+ return {
74156
+ id: node.id,
74157
+ path: node.path,
74158
+ line: node.line,
74159
+ author: first?.author?.login ?? null,
74160
+ isBot: first?.author?.__typename === "Bot",
74161
+ outdated: node.isOutdated,
74162
+ url: safeUrl(first?.url ?? null),
74163
+ body: shortBody(first?.body ?? "")
74164
+ };
74165
+ }
74166
+ function createGitHubForge(options) {
74167
+ const { apiHost, repo, runner } = options;
74168
+ const maxPages = options.maxPages ?? GITHUB_MAX_PAGES;
74169
+ const [owner, name] = repo.split("/");
74170
+ const rest = (endpoint, what) => apiJson(runner, "gh", apiHost, ["api", endpoint], what);
74171
+ const graphql = (query, variables, what) => {
74172
+ const args = ["api", "graphql", "-f", `query=${query}`];
74173
+ for (const [key, value] of Object.entries(variables)) {
74174
+ if (value === null)
74175
+ continue;
74176
+ args.push(typeof value === "number" ? "-F" : "-f", `${key}=${value}`);
74177
+ }
74178
+ const response = apiJson(runner, "gh", apiHost, args, what, FORGE_TIMEOUTS.api, { graphql: true });
74179
+ if (!response.ok)
74180
+ return response;
74181
+ if (response.data.errors?.length) {
74182
+ const message = redactText(response.data.errors[0]?.message ?? "GraphQL error").slice(0, 300);
74183
+ return /could not resolve to a (pullrequest|repository)/iu.test(message) ? failure3("not_found", `${what}: ${message}`) : failure3("failed", `${what}: ${message}`);
74184
+ }
74185
+ if (!response.data.data)
74186
+ return failure3("failed", `${what}: empty GraphQL response`);
74187
+ return success2(response.data.data);
74188
+ };
74189
+ const fetchPr = (number, cursors) => {
74190
+ const data = graphql(PR_STATUS_QUERY, { owner, name, number, threads: cursors.threads, contexts: cursors.contexts }, `pull request #${number}`);
74191
+ if (!data.ok)
74192
+ return data;
74193
+ const pr = data.data.repository?.pullRequest;
74194
+ return pr ? success2(pr) : failure3("not_found", `pull request #${number} was not found`);
74195
+ };
74196
+ const requiredContexts = (base) => {
74197
+ const branch = rest(`repos/${repo}/branches/${encodeRef(base)}`, `branch ${base}`);
74198
+ const rules = rest(`repos/${repo}/rules/branches/${encodeRef(base)}`, `rules for ${base}`);
74199
+ if (!branch.ok || !rules.ok || !Array.isArray(rules.data))
74200
+ return null;
74201
+ const contexts = new Set;
74202
+ const classic = branch.data.protection?.required_status_checks;
74203
+ for (const context of classic?.contexts ?? [])
74204
+ contexts.add(context);
74205
+ for (const check of classic?.checks ?? [])
74206
+ if (check.context)
74207
+ contexts.add(check.context);
74208
+ for (const rule of rules.data)
74209
+ if (rule.type === "required_status_checks") {
74210
+ const listed = rule.parameters?.required_status_checks;
74211
+ if (Array.isArray(listed)) {
74212
+ for (const item of listed)
74213
+ if (typeof item.context === "string")
74214
+ contexts.add(item.context);
74215
+ }
74216
+ }
74217
+ return [...contexts];
74218
+ };
74219
+ return {
74220
+ kind: "github",
74221
+ apiHost,
74222
+ repo,
74223
+ identity(expected) {
74224
+ const run = runner("gh", ["api", "user"], { timeoutMs: FORGE_TIMEOUTS.api });
74225
+ if (run.status !== 0) {
74226
+ if (/HTTP 403|resource not accessible by integration/iu.test(run.stderr))
74227
+ return success2({
74228
+ login: null,
74229
+ expected,
74230
+ matches: null,
74231
+ note: "the credential cannot read /user (app or Actions token); identity not checked"
74232
+ });
74233
+ return cliFailure("gh", apiHost, run, "user", FORGE_TIMEOUTS.api);
74234
+ }
74235
+ let login = "";
74236
+ try {
74237
+ const user = JSON.parse(run.stdout);
74238
+ login = typeof user.login === "string" ? user.login : "";
74239
+ } catch {}
74240
+ if (!login)
74241
+ return failure3("failed", "gh api user returned no login");
74242
+ return success2({
74243
+ login,
74244
+ expected,
74245
+ matches: expected ? login.toLowerCase() === expected.toLowerCase() : null
74246
+ });
74247
+ },
74248
+ repoInfo(target) {
74249
+ const info = rest(`repos/${target}`, `repository ${target}`);
74250
+ if (!info.ok)
74251
+ return info;
74252
+ return success2({
74253
+ id: typeof info.data.id === "number" ? info.data.id : null,
74254
+ parent: info.data.fork && info.data.parent?.full_name ? info.data.parent.full_name : null
74255
+ });
74256
+ },
74257
+ findPr(head, match) {
74258
+ const data = graphql(FIND_PR_QUERY, { owner, name, head }, `pull requests for ${head}`);
74259
+ if (!data.ok)
74260
+ return data;
74261
+ const nodes = data.data.repository?.pullRequests.nodes ?? [];
74262
+ const ours = nodes.filter((node) => node.headRefName === head && (match.owner !== null && node.headRepositoryOwner?.login.toLowerCase() === match.owner.toLowerCase() || match.sha !== null && node.headRefOid === match.sha));
74263
+ const pick = ours.find((node) => node.state === "OPEN") ?? ours[0];
74264
+ if (!pick)
74265
+ return success2(null);
74266
+ const ref = {
74267
+ number: pick.number,
74268
+ url: pick.url,
74269
+ state: prState(pick.state),
74270
+ headBranch: pick.headRefName,
74271
+ headSha: pick.headRefOid
74272
+ };
74273
+ return success2(ref);
74274
+ },
74275
+ prStatus(number) {
74276
+ const first = fetchPr(number, { threads: null, contexts: null });
74277
+ if (!first.ok)
74278
+ return first;
74279
+ const pr = first.data;
74280
+ const threads = [...pr.reviewThreads.nodes];
74281
+ const rollup = pr.commits.nodes[0]?.commit.statusCheckRollup ?? null;
74282
+ const contexts = [...rollup?.contexts.nodes ?? []];
74283
+ let threadPage = pr.reviewThreads.pageInfo;
74284
+ let contextPage = rollup?.contexts.pageInfo ?? { hasNextPage: false, endCursor: null };
74285
+ let pages = 1;
74286
+ while ((threadPage.hasNextPage || contextPage.hasNextPage) && pages < maxPages) {
74287
+ pages += 1;
74288
+ const next = fetchPr(number, {
74289
+ threads: threadPage.hasNextPage ? threadPage.endCursor : null,
74290
+ contexts: contextPage.hasNextPage ? contextPage.endCursor : null
74291
+ });
74292
+ if (!next.ok)
74293
+ return next;
74294
+ if (next.data.headRefOid !== pr.headRefOid)
74295
+ return failure3("failed", `pull request #${number} head moved while paging; retry`);
74296
+ if (threadPage.hasNextPage) {
74297
+ threads.push(...next.data.reviewThreads.nodes);
74298
+ threadPage = next.data.reviewThreads.pageInfo;
74299
+ }
74300
+ const nextRollup = next.data.commits.nodes[0]?.commit.statusCheckRollup;
74301
+ if (contextPage.hasNextPage && nextRollup) {
74302
+ contexts.push(...nextRollup.contexts.nodes);
74303
+ contextPage = nextRollup.contexts.pageInfo;
74304
+ }
74305
+ }
74306
+ const checks = latestPerName(contexts.map(checkFromContext).filter((check) => check !== null));
74307
+ const open = pr.state === "OPEN";
74308
+ const status = {
74309
+ number: pr.number,
74310
+ url: pr.url,
74311
+ state: prState(pr.state),
74312
+ draft: pr.isDraft,
74313
+ base: pr.baseRefName,
74314
+ baseSha: pr.baseRef?.target?.oid ?? pr.baseRefOid,
74315
+ head: { branch: pr.headRefName, sha: pr.headRefOid },
74316
+ mergeable: pr.mergeable === "MERGEABLE" ? "yes" : pr.mergeable === "CONFLICTING" ? "no" : "unknown",
74317
+ conflicts: pr.mergeable === "CONFLICTING" || pr.mergeStateStatus === "DIRTY",
74318
+ rebaseRequired: pr.mergeStateStatus === "BEHIND",
74319
+ reviewDecision: pr.reviewDecision?.toLowerCase() ?? null,
74320
+ mergeState: pr.mergeStateStatus?.toLowerCase() ?? null,
74321
+ inMergeQueue: pr.isInMergeQueue === true,
74322
+ checks,
74323
+ requiredContexts: open ? requiredContexts(pr.baseRefName) : null,
74324
+ threads: threads.filter((node) => !node.isResolved).map(threadFrom),
74325
+ truncated: threadPage.hasNextPage || contextPage.hasNextPage
74326
+ };
74327
+ return success2(status);
74328
+ },
74329
+ jobLogTail(job, lines) {
74330
+ const text = apiText(runner, "gh", apiHost, ["api", `repos/${repo}/actions/jobs/${job.jobId}/logs`], `job ${job.jobId} log`, FORGE_TIMEOUTS.log);
74331
+ return text.ok ? success2(logTail(text.data, lines)) : text;
74332
+ },
74333
+ rerun(target) {
74334
+ const endpoint = target.kind === "failed_in_run" ? `repos/${repo}/actions/runs/${target.runId}/rerun-failed-jobs` : `repos/${repo}/actions/jobs/${target.jobId}/rerun`;
74335
+ const what = target.kind === "failed_in_run" ? `rerun of run ${target.runId}` : `rerun of job ${target.jobId}`;
74336
+ const run = apiText(runner, "gh", apiHost, ["api", "-X", "POST", endpoint], what, FORGE_TIMEOUTS.api);
74337
+ return run.ok ? success2(undefined) : run;
74338
+ }
74339
+ };
74340
+ }
74341
+ var GITHUB_MAX_PAGES = 10, PR_FIELDS = `number url state isDraft isInMergeQueue mergeable mergeStateStatus baseRefName baseRefOid baseRef { target { oid } } headRefName headRefOid reviewDecision`, THREADS = `reviewThreads(first: 100, after: $threads) { pageInfo { hasNextPage endCursor } nodes { id isResolved isOutdated path line comments(first: 1) { nodes { author { login __typename } body url } } } }`, CONTEXTS = `contexts(first: 100, after: $contexts) { pageInfo { hasNextPage endCursor } nodes { __typename ... on CheckRun { name status conclusion detailsUrl databaseId isRequired(pullRequestNumber: $number) checkSuite { workflowRun { databaseId workflow { name } } } } ... on StatusContext { context state targetUrl isRequired(pullRequestNumber: $number) } } }`, PR_STATUS_QUERY, FIND_PR_QUERY = `query($owner: String!, $name: String!, $head: String!) { repository(owner: $owner, name: $name) { pullRequests(headRefName: $head, first: 20, orderBy: { field: CREATED_AT, direction: DESC }) { nodes { number url state headRefName headRefOid headRepositoryOwner { login } } } } }`, prState = (value) => value === "MERGED" ? "merged" : value === "CLOSED" ? "closed" : "open", FAILING, safeUrl = (value) => value ? redactText(value) : null, encodeRef = (ref) => ref.split("/").map(encodeURIComponent).join("/");
74342
+ var init_github = __esm(() => {
74343
+ init_exec();
74344
+ init_redact();
74345
+ PR_STATUS_QUERY = `query($owner: String!, $name: String!, $number: Int!, $threads: String, $contexts: String) { repository(owner: $owner, name: $name) { pullRequest(number: $number) { ${PR_FIELDS} ${THREADS} commits(last: 1) { nodes { commit { oid statusCheckRollup { state ${CONTEXTS} } } } } } } }`;
74346
+ FAILING = new Set([
74347
+ "FAILURE",
74348
+ "TIMED_OUT",
74349
+ "CANCELLED",
74350
+ "ACTION_REQUIRED",
74351
+ "STARTUP_FAILURE"
74352
+ ]);
74353
+ });
74354
+
74355
+ // packages/workit-core/src/forge/gitlab.ts
74356
+ function checkFromBridge(bridge, pipelineId, scope) {
74357
+ const status = bridge.downstream_pipeline?.status ?? bridge.status;
74358
+ const allow = bridge.allow_failure === true;
74359
+ const failed = status === "failed" || status === "canceled";
74360
+ return {
74361
+ name: `${bridge.stage} / ${bridge.name}`,
74362
+ context: bridge.name,
74363
+ state: stateOf(status, allow),
74364
+ conclusion: failed && allow ? "allowed_failure" : `downstream_${status}`,
74365
+ url: safeUrl2(bridge.downstream_pipeline?.web_url ?? bridge.web_url ?? null),
74366
+ runId: pipelineId,
74367
+ jobId: null,
74368
+ scope,
74369
+ required: !allow
74370
+ };
74371
+ }
74372
+ function checkFromJob(job, pipelineId, scope) {
74373
+ const failed = job.status === "failed" || job.status === "canceled";
74374
+ return {
74375
+ name: `${job.stage} / ${job.name}`,
74376
+ context: job.name,
74377
+ state: stateOf(job.status, job.allow_failure === true),
74378
+ conclusion: failed && job.allow_failure ? "allowed_failure" : job.status,
74379
+ url: safeUrl2(job.web_url ?? null),
74380
+ runId: pipelineId,
74381
+ jobId: job.id,
74382
+ scope,
74383
+ required: job.allow_failure !== true
74384
+ };
74385
+ }
74386
+ function createGitLabForge(options) {
74387
+ const { apiHost, repo, runner } = options;
74388
+ const maxPages = options.maxPages ?? GITLAB_MAX_PAGES;
74389
+ const project = `projects/${encodeURIComponent(repo)}`;
74390
+ const api = (endpoint) => ["api", endpoint];
74391
+ const pages = (endpoint, what) => {
74392
+ const items = [];
74393
+ const sep = endpoint.includes("?") ? "&" : "?";
74394
+ for (let page = 1;page <= maxPages; page += 1) {
74395
+ const chunk = apiJson(runner, "glab", apiHost, api(`${endpoint}${sep}per_page=${PER_PAGE}&page=${page}`), what);
74396
+ if (!chunk.ok)
74397
+ return chunk;
74398
+ if (!Array.isArray(chunk.data))
74399
+ return failure3("failed", `${what}: expected a list`);
74400
+ items.push(...chunk.data);
74401
+ if (chunk.data.length < PER_PAGE)
74402
+ return success2({ items, truncated: false });
74403
+ }
74404
+ return success2({ items, truncated: true });
74405
+ };
74406
+ const mergeCommitTests = (scope, sha, head) => {
74407
+ const commit = apiJson(runner, "glab", apiHost, api(`projects/${scope}/repository/commits/${sha}`), `commit ${sha.slice(0, 12)}`);
74408
+ return commit.ok && Array.isArray(commit.data.parent_ids) && commit.data.parent_ids.includes(head);
74409
+ };
74410
+ const baseTip = (branch) => {
74411
+ const tip = apiJson(runner, "glab", apiHost, api(`${project}/repository/branches/${encodeURIComponent(branch)}`), `branch ${branch}`);
74412
+ return tip.ok && typeof tip.data.commit?.id === "string" ? tip.data.commit.id : null;
74413
+ };
74414
+ return {
74415
+ kind: "gitlab",
74416
+ apiHost,
74417
+ repo,
74418
+ identity(expected) {
74419
+ const user = apiJson(runner, "glab", apiHost, api("user"), "user");
74420
+ if (!user.ok && user.code === "failed" && /\b403\b|forbidden/iu.test(user.error))
74421
+ return success2({
74422
+ login: null,
74423
+ expected,
74424
+ matches: null,
74425
+ note: "the credential cannot read /user (job or project token); identity not checked"
74426
+ });
74427
+ if (!user.ok)
74428
+ return user;
74429
+ const login = typeof user.data.username === "string" ? user.data.username : "";
74430
+ if (!login)
74431
+ return failure3("failed", "glab api user returned no username");
74432
+ return success2({
74433
+ login,
74434
+ expected,
74435
+ matches: expected ? login.toLowerCase() === expected.toLowerCase() : null
74436
+ });
74437
+ },
74438
+ repoInfo(target) {
74439
+ const info = apiJson(runner, "glab", apiHost, api(`projects/${encodeURIComponent(target)}`), `project ${target}`);
74440
+ if (!info.ok)
74441
+ return info;
74442
+ return success2({
74443
+ id: typeof info.data.id === "number" ? info.data.id : null,
74444
+ parent: info.data.forked_from_project?.path_with_namespace ?? null
74445
+ });
74446
+ },
74447
+ findPr(head, match) {
74448
+ const list = apiJson(runner, "glab", apiHost, api(`${project}/merge_requests?source_branch=${encodeURIComponent(head)}&order_by=created_at&sort=desc&per_page=20`), `merge requests for ${head}`);
74449
+ if (!list.ok)
74450
+ return list;
74451
+ if (!Array.isArray(list.data))
74452
+ return failure3("failed", "merge request list is not a list");
74453
+ const matches = list.data.filter((mr) => mr.source_branch === head && (match.projectId !== null && mr.source_project_id === match.projectId || match.sha !== null && mr.sha === match.sha));
74454
+ const pick = matches.find((mr) => mr.state === "opened") ?? matches[0];
74455
+ return success2(pick ? {
74456
+ number: pick.iid,
74457
+ url: pick.web_url,
74458
+ state: mrState(pick.state),
74459
+ headBranch: pick.source_branch,
74460
+ headSha: pick.sha ?? null
74461
+ } : null);
74462
+ },
74463
+ prStatus(iid) {
74464
+ const mr = apiJson(runner, "glab", apiHost, api(`${project}/merge_requests/${iid}`), `merge request !${iid}`);
74465
+ if (!mr.ok)
74466
+ return mr;
74467
+ const value = mr.data;
74468
+ let truncated = false;
74469
+ let checks = [];
74470
+ const pipeline = value.head_pipeline ?? null;
74471
+ const open = value.state === "opened";
74472
+ const current = pipeline !== null && (pipeline.sha === value.sha || pipeline.source_sha === value.sha || !pipeline.source_sha && new RegExp(`^refs/merge-requests/${value.iid}/(?:merge|train)$`, "u").test(pipeline.ref ?? "") && mergeCommitTests(pipeline.project_id, pipeline.sha, value.sha));
74473
+ if (pipeline && current) {
74474
+ const scope = String(pipeline.project_id);
74475
+ const jobs = pages(`projects/${scope}/pipelines/${pipeline.id}/jobs`, `pipeline ${pipeline.id} jobs`);
74476
+ if (!jobs.ok)
74477
+ return jobs;
74478
+ const bridges = pages(`projects/${scope}/pipelines/${pipeline.id}/bridges`, `pipeline ${pipeline.id} bridges`);
74479
+ if (!bridges.ok)
74480
+ return bridges;
74481
+ truncated ||= jobs.data.truncated || bridges.data.truncated;
74482
+ checks = [
74483
+ ...jobs.data.items.map((job) => checkFromJob(job, pipeline.id, scope)),
74484
+ ...bridges.data.items.map((bridge) => checkFromBridge(bridge, pipeline.id, scope))
74485
+ ];
74486
+ const pipelineState = stateOf(pipeline.status, false);
74487
+ if (pipelineState === "failing" && !checks.some((check) => check.state === "failing"))
74488
+ checks.push(pipelineCheck("failing", pipeline.status, pipeline));
74489
+ if (pipelineState === "pending" && !checks.some((check) => check.state === "pending"))
74490
+ checks.push(pipelineCheck("pending", pipeline.status, pipeline));
74491
+ } else if (open && pipeline) {
74492
+ checks = [pipelineCheck("pending", "awaiting_pipeline_for_head", null)];
74493
+ }
74494
+ const discussions = pages(`${project}/merge_requests/${iid}/discussions`, `merge request !${iid} discussions`);
74495
+ if (!discussions.ok)
74496
+ return discussions;
74497
+ truncated ||= discussions.data.truncated;
74498
+ const threads = discussions.data.items.filter((discussion) => discussion.notes.some((note) => note.resolvable === true && note.resolved !== true)).map((discussion) => {
74499
+ const note = discussion.notes[0];
74500
+ return {
74501
+ id: discussion.id,
74502
+ path: note.position?.new_path ?? note.position?.old_path ?? null,
74503
+ line: note.position?.new_line ?? null,
74504
+ author: note.author?.username ?? null,
74505
+ isBot: isBot(note.author),
74506
+ outdated: false,
74507
+ url: safeUrl2(`${value.web_url}#note_${note.id}`),
74508
+ body: shortBody(note.body ?? "")
74509
+ };
74510
+ });
74511
+ const detailed = value.detailed_merge_status ?? "unchecked";
74512
+ const conflicts = value.has_conflicts === true || detailed === "conflict";
74513
+ const status = {
74514
+ number: value.iid,
74515
+ url: value.web_url,
74516
+ state: mrState(value.state),
74517
+ draft: value.draft ?? value.work_in_progress ?? false,
74518
+ base: value.target_branch,
74519
+ baseSha: open ? baseTip(value.target_branch) : null,
74520
+ head: { branch: value.source_branch, sha: value.sha },
74521
+ mergeable: conflicts ? "no" : UNSETTLED.has(detailed) ? "unknown" : "yes",
74522
+ conflicts,
74523
+ rebaseRequired: detailed === "need_rebase",
74524
+ reviewDecision: detailed === "not_approved" ? "review_required" : detailed === "requested_changes" ? "changes_requested" : null,
74525
+ mergeState: detailed,
74526
+ inMergeQueue: false,
74527
+ checks,
74528
+ requiredContexts: [],
74529
+ threads,
74530
+ truncated
74531
+ };
74532
+ return success2(status);
74533
+ },
74534
+ jobLogTail(job, lines) {
74535
+ const scope = job.scope ?? encodeURIComponent(repo);
74536
+ const text = apiText(runner, "glab", apiHost, ["api", `projects/${scope}/jobs/${job.jobId}/trace`], `job ${job.jobId} trace`, FORGE_TIMEOUTS.log);
74537
+ return text.ok ? success2(logTail(text.data, lines)) : text;
74538
+ },
74539
+ rerun(target) {
74540
+ const scope = target.scope ?? encodeURIComponent(repo);
74541
+ const endpoint = target.kind === "failed_in_run" ? `projects/${scope}/pipelines/${target.runId}/retry` : `projects/${scope}/jobs/${target.jobId}/retry`;
74542
+ const what = target.kind === "failed_in_run" ? `retry of pipeline ${target.runId}` : `retry of job ${target.jobId}`;
74543
+ const run = apiText(runner, "glab", apiHost, ["api", "-X", "POST", endpoint], what, FORGE_TIMEOUTS.api);
74544
+ return run.ok ? success2(undefined) : run;
74545
+ }
74546
+ };
74547
+ }
74548
+ var GITLAB_MAX_PAGES = 10, PER_PAGE = 100, mrState = (value) => value === "merged" ? "merged" : value === "opened" ? "open" : "closed", PENDING, stateOf = (status, allowFailure) => {
74549
+ const failed = status === "failed" || status === "canceled";
74550
+ return PENDING.has(status) ? "pending" : failed ? allowFailure ? "passing" : "failing" : status === "success" ? "passing" : "skipped";
74551
+ }, pipelineCheck = (state, conclusion, pipeline) => ({
74552
+ name: "pipeline",
74553
+ context: "pipeline",
74554
+ state,
74555
+ conclusion,
74556
+ url: safeUrl2(pipeline?.web_url ?? null),
74557
+ runId: pipeline?.id ?? null,
74558
+ jobId: null,
74559
+ scope: pipeline ? String(pipeline.project_id) : null,
74560
+ required: true
74561
+ }), safeUrl2 = (value) => value ? redactText(value) : null, isBot = (author) => author?.bot === true || /(?:^project_\d+_bot|bot$|\[bot\]$)/iu.test(author?.username ?? ""), UNSETTLED;
74562
+ var init_gitlab = __esm(() => {
74563
+ init_exec();
74564
+ init_redact();
74565
+ PENDING = new Set([
74566
+ "created",
74567
+ "pending",
74568
+ "running",
74569
+ "waiting_for_resource",
74570
+ "waiting_for_callback",
74571
+ "preparing",
74572
+ "scheduled"
74573
+ ]);
74574
+ UNSETTLED = new Set(["checking", "unchecked", "preparing", "approvals_syncing"]);
74575
+ });
74576
+
74577
+ // packages/workit-core/src/forge/resolve.ts
74578
+ import fs18 from "node:fs";
74579
+ function resolveForge(cwd, options = {}) {
74580
+ let workspace;
74581
+ try {
74582
+ workspace = resolveRuntimeWorkspaceVcs(cwd);
74583
+ } catch (error) {
74584
+ return failure3("blocked", error instanceof Error ? error.message : String(error), "fix ~/.config/workit/workspaces.json (workit init → Workspaces)");
74585
+ }
74586
+ const hosts = configuredHosts();
74587
+ const pushed = pushForge(cwd, { branch: options.branch, hosts, sshConfig: options.sshConfig });
74588
+ if (!pushed.ok)
74589
+ return failure3(pushed.code, pushed.error, pushed.unblock);
74590
+ const conflict = forgeConflict(pushed.forge, workspace?.vcs?.provider);
74591
+ if (conflict)
74592
+ return failure3("blocked", conflict.error, conflict.unblock);
74593
+ const derived = pushed.forge;
74594
+ const { kind, apiHost } = derived;
74595
+ const base = options.runner ?? systemRunner(kind, apiHost, options.env ?? process.env);
74596
+ const now = options.now ?? Date.now;
74597
+ const expectedAccount = workspace?.vcs?.account ?? null;
74598
+ let token;
74599
+ let credential = "cli_login";
74600
+ const tokenFile = workspace?.vcs?.tokenFile;
74601
+ if (typeof tokenFile === "string" && tokenFile.trim()) {
74602
+ const workspaceHost = workspace?.vcs?.host;
74603
+ const explicit = typeof workspaceHost === "string" && workspaceHost.trim().toLowerCase().replace(/^https?:\/\//u, "").replace(/\/.*$/u, "") === apiHost;
74604
+ if (derived.via === "host_name" && !explicit)
74605
+ return failure3("blocked", `token_host_unverified: push host ${derived.host} only looks like ${kind}; workspace ${workspace?.name ?? "?"} vcs.tokenFile is not sent to it`, `if ${apiHost} is your ${kind} server, set ${kind}.host in ~/.config/workit/vcs.json (or vcs.host for the workspace); otherwise fix the push remote`);
74606
+ try {
74607
+ token = fs18.readFileSync(tokenFile.trim(), "utf8").trim();
74608
+ } catch {
74609
+ return failure3("blocked", `workspace ${workspace?.name ?? "?"} vcs.tokenFile cannot be read`, "fix vcs.tokenFile in ~/.config/workit/workspaces.json");
74610
+ }
74611
+ if (!token)
74612
+ return failure3("blocked", `workspace ${workspace?.name ?? "?"} vcs.tokenFile is empty`, "fix vcs.tokenFile in ~/.config/workit/workspaces.json");
74613
+ credential = "workspace_token_file";
74614
+ } else if (expectedAccount && kind === "github") {
74615
+ const issued = base("gh", ["auth", "token", "--hostname", apiHost, "--user", expectedAccount], {
74616
+ timeoutMs: FORGE_TIMEOUTS.api
74617
+ });
74618
+ if (issued.missing)
74619
+ return failure3("unavailable", "gh is not installed", "install the GitHub CLI (https://cli.github.com) and run: gh auth login");
74620
+ token = issued.status === 0 ? issued.stdout.trim() : "";
74621
+ if (!token)
74622
+ return failure3("blocked", `identity_unavailable: gh has no login for ${expectedAccount} on ${apiHost} (workspace ${workspace?.name ?? "?"})`, `gh auth login --hostname ${apiHost} # sign in as ${expectedAccount}; workit passes that account's token per call and never switches the active account`);
74623
+ credential = "gh_account_token";
74624
+ }
74625
+ const limits = { deadline: null };
74626
+ const runner = (bin, args, callOptions) => {
74627
+ let timeoutMs = callOptions.timeoutMs;
74628
+ if (limits.deadline !== null) {
74629
+ const remaining = limits.deadline - now();
74630
+ if (remaining <= 0)
74631
+ return { status: null, stdout: "", stderr: "", timedOut: true, missing: false };
74632
+ timeoutMs = Math.min(timeoutMs, remaining);
74633
+ }
74634
+ return base(bin, args, { ...callOptions, timeoutMs, ...token ? { token } : {} });
74635
+ };
74636
+ const headRepo = derived.repo;
74637
+ const probe = createForge(derived, headRepo, runner);
74638
+ const info = probe.repoInfo(headRepo);
74639
+ if (!info.ok && info.code !== "not_found")
74640
+ return info;
74641
+ const headProjectId = info.ok ? info.data.id : null;
74642
+ const remotes = remoteNames(cwd);
74643
+ const repoOfRemote = (name) => {
74644
+ const url = pushUrl(cwd, name);
74645
+ const other = url ? deriveForge(url, { hosts, sshConfig: options.sshConfig }) : null;
74646
+ return other && other.kind === kind && other.apiHost === apiHost ? other.repo : null;
74647
+ };
74648
+ const upstream = pushed.remote !== "upstream" && remotes.includes("upstream") ? repoOfRemote("upstream") : null;
74649
+ const baseRepo = upstream && upstream !== headRepo ? upstream : info.ok && info.data.parent ? info.data.parent : headRepo;
74650
+ const baseRemote = baseRepo === headRepo ? pushed.remote : remotes.find((name) => repoOfRemote(name) === baseRepo) ?? pushed.remote;
74651
+ return success2({
74652
+ forge: baseRepo === headRepo ? probe : createForge(derived, baseRepo, runner),
74653
+ remote: pushed.remote,
74654
+ url: pushed.url,
74655
+ derived,
74656
+ workspace: workspace?.name ?? null,
74657
+ expectedAccount,
74658
+ credential,
74659
+ headRepo,
74660
+ headProjectId,
74661
+ fork: baseRepo !== headRepo,
74662
+ baseRemote,
74663
+ limits
74664
+ });
74665
+ }
74666
+ function checkIdentity(resolved) {
74667
+ const { forge, expectedAccount } = resolved;
74668
+ const identity = forge.identity(expectedAccount);
74669
+ if (!identity.ok)
74670
+ return identity;
74671
+ if (identity.data.matches === false) {
74672
+ const bin = forge.kind === "github" ? "gh" : "glab";
74673
+ const where = `workspace ${resolved.workspace ?? "?"}`;
74674
+ return failure3("blocked", `identity_mismatch: the ${resolved.credential === "workspace_token_file" ? "vcs.tokenFile token" : `${bin} login`} is ${identity.data.login} but ${where} expects ${expectedAccount}`, resolved.credential === "workspace_token_file" ? `put a token for ${expectedAccount} in the ${where} vcs.tokenFile` : `${bin} auth login --hostname ${forge.apiHost} # sign in as ${expectedAccount}`);
74675
+ }
74676
+ return identity;
74677
+ }
74678
+ var configuredHosts = () => {
74679
+ const vcs = readVcsConfig();
74680
+ if (vcs.status !== "valid")
74681
+ return {};
74682
+ const host = (kind) => {
74683
+ const value = vcs.config[kind]?.host;
74684
+ return typeof value === "string" && value.trim() ? [value.trim()] : [];
74685
+ };
74686
+ return { github: host("github"), gitlab: host("gitlab") };
74687
+ }, createForge = (derived, repo, runner) => derived.kind === "github" ? createGitHubForge({ apiHost: derived.apiHost, repo, runner }) : createGitLabForge({ apiHost: derived.apiHost, repo, runner });
74688
+ var init_resolve = __esm(() => {
74689
+ init_vcs_config();
74690
+ init_workspaces();
74691
+ init_rev();
74692
+ init_exec();
74693
+ init_github();
74694
+ init_gitlab();
74695
+ });
74696
+
74697
+ // packages/workit-cli/src/verbs/forge-common.ts
74698
+ function parseFlags(argv, spec) {
74699
+ const parsed = { values: {}, booleans: new Set, lists: {}, positionals: [] };
74700
+ for (let index = 0;index < argv.length; index += 1) {
74701
+ const arg = argv[index];
74702
+ if (arg === "--json")
74703
+ continue;
74704
+ if (!arg.startsWith("--")) {
74705
+ parsed.positionals.push(arg);
74706
+ continue;
74707
+ }
74708
+ const eq = arg.indexOf("=");
74709
+ const name = arg.slice(2, eq < 0 ? undefined : eq);
74710
+ const kind = spec[name];
74711
+ if (!kind)
74712
+ return `unknown option --${name}`;
74713
+ if (kind === "boolean") {
74714
+ if (eq >= 0)
74715
+ return `--${name} takes no value`;
74716
+ parsed.booleans.add(name);
74717
+ continue;
74718
+ }
74719
+ const value = eq >= 0 ? arg.slice(eq + 1) : argv[++index];
74720
+ if (value === undefined || value === "" || eq < 0 && value.startsWith("--"))
74721
+ return `--${name} requires a value`;
74722
+ if (kind === "list")
74723
+ (parsed.lists[name] ??= []).push(value);
74724
+ else
74725
+ parsed.values[name] = value;
74726
+ }
74727
+ return parsed;
74728
+ }
74729
+ function positiveInt(value, flag) {
74730
+ if (value === undefined)
74731
+ return null;
74732
+ return /^[1-9]\d{0,8}$/u.test(value) ? Number(value) : `${flag} must be a positive integer`;
74733
+ }
74734
+ function parseDuration2(value) {
74735
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m|h)?$/u.exec(value.trim());
74736
+ if (!match)
74737
+ return null;
74738
+ const unit = match[2] ?? "s";
74739
+ const factor = unit === "ms" ? 1 : unit === "s" ? 1000 : unit === "m" ? 60000 : 3600000;
74740
+ return Math.round(Number(match[1]) * factor);
74741
+ }
74742
+ function connect(io, branch) {
74743
+ const resolved = resolveForge(io.cwd, {
74744
+ branch,
74745
+ env: io.env,
74746
+ runner: forgeDeps.runner,
74747
+ now: forgeDeps.now
74748
+ });
74749
+ if (!resolved.ok)
74750
+ return resolved;
74751
+ const identity = checkIdentity(resolved.data);
74752
+ if (!identity.ok)
74753
+ return identity;
74754
+ return {
74755
+ ok: true,
74756
+ data: {
74757
+ ...resolved.data,
74758
+ identity: {
74759
+ login: identity.data.login,
74760
+ credential: resolved.data.credential,
74761
+ ...identity.data.note ? { note: identity.data.note } : {}
74762
+ }
74763
+ }
74764
+ };
74765
+ }
74766
+ function renderStatus(doc) {
74767
+ const noun = doc.forge === "github" ? `PR #${doc.number}` : `MR !${doc.number}`;
74768
+ const lines = [
74769
+ `${noun} ${doc.state}${doc.draft ? " (draft)" : ""}: ${doc.headRepo ? `${doc.headRepo}:` : ""}${doc.head.branch} -> ${doc.base} ${doc.url}`
74770
+ ];
74771
+ const local = doc.head.localSha === null ? "not checked out" : doc.head.pushed ? "local matches" : `local ${short(doc.head.localSha)} differs`;
74772
+ lines.push(`head ${short(doc.head.sha)} (${local})`);
74773
+ const behind = doc.behindBase;
74774
+ const behindText = !behind ? "" : behind.behind === null ? ` · behind ${doc.base}: unknown (${behind.error ?? "unavailable"})` : ` · behind ${doc.base}: ${behind.behind}, ahead ${behind.ahead}`;
74775
+ lines.push(`mergeable ${doc.mergeable} · conflicts ${doc.conflicts ? "yes" : "no"}${doc.rebaseRequired ? " · rebase required" : ""}${behindText}`);
74776
+ const checks = doc.checks;
74777
+ lines.push(`checks ${checks.state}: ${checks.failing.length} failing, ${checks.pending.length} pending, ${checks.passing} passing`);
74778
+ for (const check of checks.failing) {
74779
+ lines.push(` x ${check.name} (${check.conclusion ?? "failed"}${check.required === false ? ", optional" : ""})${check.rerunsOnHead ? ` [rerun ${check.rerunsOnHead}x on this head]` : ""}${check.url ? ` ${check.url}` : ""}`);
74780
+ for (const value of check.logTail)
74781
+ lines.push(` ${value}`);
74782
+ if (check.logError)
74783
+ lines.push(` (log unavailable: ${check.logError})`);
74784
+ }
74785
+ if (checks.pending.length)
74786
+ lines.push(` pending: ${checks.pending.join(", ")}`);
74787
+ if (checks.missingRequired === null)
74788
+ lines.push(" required checks: unknown (branch protection not readable)");
74789
+ else if (checks.missingRequired.length)
74790
+ lines.push(` required, not reported yet: ${checks.missingRequired.join(", ")}`);
74791
+ const threads = doc.reviews.unresolvedThreads;
74792
+ lines.push(`reviews ${doc.reviews.decision ?? "none"} · ${threads.length} unresolved thread${threads.length === 1 ? "" : "s"}`);
74793
+ for (const thread of threads)
74794
+ lines.push(` - ${thread.path ? `${thread.path}${thread.line ? `:${thread.line}` : ""} ` : ""}@${thread.author ?? "?"}${thread.isBot ? " (bot)" : ""}: ${thread.body}`);
74795
+ if (doc.truncated)
74796
+ lines.push("(lists truncated at the page cap)");
74797
+ if (doc.blockers.length)
74798
+ lines.push(`blockers: ${doc.blockers.join(", ")}`);
74799
+ if (doc.identity?.note)
74800
+ lines.push(`identity: ${doc.identity.note}`);
74801
+ lines.push(`next: ${doc.next}`);
74802
+ return lines;
74803
+ }
74804
+ var forgeDeps, usage3 = (io, message, line) => emit(io, fail("invalid_input", message, { unblock: line })), forgeFail = (io, result, data) => emit(io, fail(result.code, result.error, { data, unblock: result.unblock })), short = (sha) => sha ? sha.slice(0, 7) : "?";
74805
+ var init_forge_common = __esm(() => {
74806
+ init_resolve();
74807
+ init_output();
74808
+ forgeDeps = {
74809
+ runner: undefined,
74810
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
74811
+ now: () => Date.now()
74812
+ };
74813
+ });
74814
+
73019
74815
  // packages/workit-cli/src/verbs/pr.ts
73020
74816
  var exports_pr = {};
73021
74817
  __export(exports_pr, {
73022
74818
  run: () => run13
73023
74819
  });
73024
- var run13;
74820
+ async function status(argv, io) {
74821
+ const flags = parseFlags(argv, { pr: "value", branch: "value", "log-lines": "value" });
74822
+ if (typeof flags === "string")
74823
+ return usage3(io, flags, STATUS_USAGE);
74824
+ if (flags.positionals.length)
74825
+ return usage3(io, `unexpected argument ${flags.positionals[0]}`, STATUS_USAGE);
74826
+ const pr = positiveInt(flags.values.pr, "--pr");
74827
+ if (typeof pr === "string")
74828
+ return usage3(io, pr, STATUS_USAGE);
74829
+ if (pr && flags.values.branch)
74830
+ return usage3(io, "pass --pr or --branch, not both", STATUS_USAGE);
74831
+ const logLines = flags.values["log-lines"] === "0" ? 0 : positiveInt(flags.values["log-lines"], "--log-lines");
74832
+ if (typeof logLines === "string")
74833
+ return usage3(io, logLines, STATUS_USAGE);
74834
+ if (logLines !== null && logLines > 500)
74835
+ return usage3(io, "--log-lines is capped at 500", STATUS_USAGE);
74836
+ const connected = connect(io, flags.values.branch);
74837
+ if (!connected.ok)
74838
+ return forgeFail(io, connected);
74839
+ const report = prStatusReport(io.cwd, connected.data, {
74840
+ pr,
74841
+ branch: flags.values.branch ?? null,
74842
+ logLines: logLines ?? 60,
74843
+ identity: connected.data.identity
74844
+ });
74845
+ if (!report.ok)
74846
+ return forgeFail(io, report);
74847
+ return emit(io, ok(report.data.doc), renderStatus);
74848
+ }
74849
+ async function run13(argv, io) {
74850
+ const [sub, ...rest] = argv;
74851
+ if (sub === "status")
74852
+ return status(rest, io);
74853
+ if (sub === "create" || sub === "merge")
74854
+ return later(argv, io);
74855
+ return usage3(io, sub && !sub.startsWith("-") ? `unknown pr subcommand "${sub}"` : "missing pr subcommand", STATUS_USAGE);
74856
+ }
74857
+ var STATUS_USAGE = "workit pr status [--pr <n> | --branch <b>] [--log-lines 60] [--json]", later;
73025
74858
  var init_pr = __esm(() => {
74859
+ init_report();
74860
+ init_output();
74861
+ init_forge_common();
73026
74862
  init_stub();
73027
- run13 = notImplemented("pr", "S10/S11");
74863
+ later = notImplemented("pr", "S11");
73028
74864
  });
73029
74865
 
73030
74866
  // packages/workit-cli/src/verbs/ci.ts
@@ -73032,10 +74868,181 @@ var exports_ci = {};
73032
74868
  __export(exports_ci, {
73033
74869
  run: () => run14
73034
74870
  });
73035
- var run14;
74871
+ async function wait(argv, io) {
74872
+ const flags = parseFlags(argv, {
74873
+ pr: "value",
74874
+ branch: "value",
74875
+ head: "value",
74876
+ timeout: "value",
74877
+ interval: "value"
74878
+ });
74879
+ if (typeof flags === "string")
74880
+ return usage3(io, flags, WAIT_USAGE);
74881
+ if (flags.positionals.length)
74882
+ return usage3(io, `unexpected argument ${flags.positionals[0]}`, WAIT_USAGE);
74883
+ const pr = positiveInt(flags.values.pr, "--pr");
74884
+ if (typeof pr === "string")
74885
+ return usage3(io, pr, WAIT_USAGE);
74886
+ const timeoutMs = parseDuration2(flags.values.timeout ?? "20m");
74887
+ const intervalMs = parseDuration2(flags.values.interval ?? "30s");
74888
+ if (timeoutMs === null || timeoutMs > 6 * 3600000)
74889
+ return usage3(io, "--timeout must be a duration up to 6h (20m, 90s)", WAIT_USAGE);
74890
+ if (intervalMs === null || intervalMs < 1000 || intervalMs > 600000)
74891
+ return usage3(io, "--interval must be between 1s and 10m", WAIT_USAGE);
74892
+ if (pr && flags.values.branch)
74893
+ return usage3(io, "pass --pr or --branch, not both", WAIT_USAGE);
74894
+ const head = flags.values.head ?? null;
74895
+ if (head !== null && !/^[0-9a-f]{7,64}$/u.test(head))
74896
+ return usage3(io, "--head must be a commit sha (7-64 hex chars)", WAIT_USAGE);
74897
+ const connected = connect(io, flags.values.branch);
74898
+ if (!connected.ok)
74899
+ return forgeFail(io, connected);
74900
+ const resolved = connected.data;
74901
+ const number = selectPr(io.cwd, resolved, { pr, branch: flags.values.branch ?? null });
74902
+ if (!number.ok)
74903
+ return forgeFail(io, number);
74904
+ const started = forgeDeps.now();
74905
+ const deadline = started + timeoutMs;
74906
+ resolved.limits.deadline = started + Math.max(timeoutMs, ONE_POLL_MS);
74907
+ let polls = 0;
74908
+ let errors = 0;
74909
+ let status = null;
74910
+ let verdict = { state: "waiting", reason: "not_polled" };
74911
+ for (;; ) {
74912
+ const read = resolved.forge.prStatus(number.data);
74913
+ polls += 1;
74914
+ const elapsedMs = forgeDeps.now() - started;
74915
+ if (read.ok) {
74916
+ errors = 0;
74917
+ status = read.data;
74918
+ const doc = buildStatusDoc(io.cwd, resolved, status, { logLines: 0, behind: false });
74919
+ if (!doc.ok)
74920
+ return forgeFail(io, doc);
74921
+ verdict = waitVerdict(doc.data, { head, elapsedMs });
74922
+ if (verdict.state !== "waiting")
74923
+ break;
74924
+ } else {
74925
+ errors += 1;
74926
+ if (read.code !== "unavailable" && read.code !== "failed")
74927
+ return forgeFail(io, read);
74928
+ if (errors >= MAX_POLL_ERRORS)
74929
+ return forgeFail(io, read, { polls });
74930
+ }
74931
+ const remaining = deadline - forgeDeps.now();
74932
+ if (remaining <= 0)
74933
+ break;
74934
+ await forgeDeps.sleep(Math.min(pollDelay(intervalMs, polls - 1), remaining));
74935
+ }
74936
+ const elapsedMs = forgeDeps.now() - started;
74937
+ if (!status)
74938
+ return emit(io, fail("unavailable", "no successful poll before the timeout", { data: { polls } }));
74939
+ const left = deadline - forgeDeps.now();
74940
+ const built = buildStatusDoc(io.cwd, resolved, status, {
74941
+ logLines: verdict.state === "failed" && left > 0 ? 60 : 0,
74942
+ behind: left >= 1000,
74943
+ behindTimeoutMs: Math.max(1000, Math.min(15000, left)),
74944
+ identity: resolved.identity
74945
+ });
74946
+ if (!built.ok)
74947
+ return forgeFail(io, built);
74948
+ const doc = built.data;
74949
+ const data = {
74950
+ state: verdict.state,
74951
+ reason: verdict.reason,
74952
+ polls,
74953
+ elapsedMs,
74954
+ timeoutMs,
74955
+ status: doc
74956
+ };
74957
+ if (!io.json && verdict.state !== "ready")
74958
+ for (const value of renderStatus(doc))
74959
+ io.stdout(`${value}
74960
+ `);
74961
+ if (verdict.state === "ready")
74962
+ return emit(io, ok(data), () => [
74963
+ `CI ready (${verdict.reason}) after ${polls} poll${polls === 1 ? "" : "s"}, ${seconds(elapsedMs)}`,
74964
+ ...renderStatus(doc)
74965
+ ]);
74966
+ if (verdict.state === "failed")
74967
+ return emit(io, fail("failed", `CI failed: ${doc.checks.failing.map((check) => check.name).join(", ")}`, {
74968
+ data,
74969
+ unblock: "fix the failure, or if it is a flake: workit ci rerun --failed --reason flake"
74970
+ }));
74971
+ if (verdict.state === "blocked")
74972
+ return emit(io, fail("blocked", `CI wait blocked: ${verdict.reason}`, { data, unblock: verdict.unblock }));
74973
+ return emit(io, fail("pending", `CI still ${verdict.reason.replaceAll("_", " ")} after ${seconds(elapsedMs)}`, {
74974
+ data,
74975
+ unblock: `workit ci wait --pr ${doc.number}${head ? ` --head ${head}` : ""}`
74976
+ }));
74977
+ }
74978
+ async function rerun(argv, io) {
74979
+ const flags = parseFlags(argv, {
74980
+ pr: "value",
74981
+ branch: "value",
74982
+ check: "list",
74983
+ failed: "boolean",
74984
+ reason: "value",
74985
+ force: "boolean"
74986
+ });
74987
+ if (typeof flags === "string")
74988
+ return usage3(io, flags, RERUN_USAGE);
74989
+ if (flags.positionals.length)
74990
+ return usage3(io, `unexpected argument ${flags.positionals[0]}`, RERUN_USAGE);
74991
+ const pr = positiveInt(flags.values.pr, "--pr");
74992
+ if (typeof pr === "string")
74993
+ return usage3(io, pr, RERUN_USAGE);
74994
+ const names = flags.lists.check ?? [];
74995
+ if (pr && flags.values.branch)
74996
+ return usage3(io, "pass --pr or --branch, not both", RERUN_USAGE);
74997
+ if (names.length && flags.booleans.has("failed"))
74998
+ return usage3(io, "pass --check or --failed, not both", RERUN_USAGE);
74999
+ const reason = flags.values.reason;
75000
+ if (reason !== "flake" && reason !== "infra")
75001
+ return usage3(io, "--reason flake|infra is required: rerun only what you judged a flake or an infrastructure failure", RERUN_USAGE);
75002
+ const connected = connect(io, flags.values.branch);
75003
+ if (!connected.ok)
75004
+ return forgeFail(io, connected);
75005
+ const resolved = connected.data;
75006
+ if (resolved.expectedAccount && resolved.identity.login === null)
75007
+ return emit(io, fail("blocked", `identity_unverified: the credential cannot prove it is ${resolved.expectedAccount} (${resolved.identity.note ?? "no /user access"})`, {
75008
+ unblock: `use a credential for ${resolved.expectedAccount} that can read /user (vcs.tokenFile or ${resolved.forge.kind === "github" ? "gh" : "glab"} auth login --hostname ${resolved.forge.apiHost})`
75009
+ }));
75010
+ const number = selectPr(io.cwd, resolved, { pr, branch: flags.values.branch ?? null });
75011
+ if (!number.ok)
75012
+ return forgeFail(io, number);
75013
+ const status = resolved.forge.prStatus(number.data);
75014
+ if (!status.ok)
75015
+ return forgeFail(io, status);
75016
+ const done = executeRerun(io.cwd, resolved, status.data, {
75017
+ failed: names.length === 0,
75018
+ names,
75019
+ reason,
75020
+ force: flags.booleans.has("force")
75021
+ });
75022
+ if (!done.ok)
75023
+ return forgeFail(io, done);
75024
+ const outcome = done.data;
75025
+ return emit(io, ok(outcome), () => [
75026
+ `rerun requested on #${outcome.pr} head ${outcome.head.slice(0, 7)} (${outcome.reason}${outcome.forced ? ", forced" : ""}): ${outcome.rerun.map((check) => check.name).join(", ")}`,
75027
+ ...outcome.skipped.length ? [`not rerunnable here: ${outcome.skipped.join(", ")}`] : [],
75028
+ ...outcome.recorded ? [] : ["warning: the rerun could not be recorded; the once-per-head guard will not see it"],
75029
+ "next: workit ci wait"
75030
+ ]);
75031
+ }
75032
+ async function run14(argv, io) {
75033
+ const [sub, ...rest] = argv;
75034
+ if (sub === "wait")
75035
+ return wait(rest, io);
75036
+ if (sub === "rerun")
75037
+ return rerun(rest, io);
75038
+ return usage3(io, sub && !sub.startsWith("-") ? `unknown ci subcommand "${sub}"` : "missing ci subcommand", `${WAIT_USAGE}
75039
+ ${RERUN_USAGE}`);
75040
+ }
75041
+ var WAIT_USAGE = "workit ci wait [--pr <n> | --branch <b>] [--head <sha>] [--timeout 20m] [--interval 30s] [--json]", RERUN_USAGE = "workit ci rerun [--pr <n> | --branch <b>] [--check <name>… | --failed] --reason flake|infra [--force] [--json]", ONE_POLL_MS = 20000, MAX_POLL_ERRORS = 3, seconds = (ms) => `${Math.round(ms / 1000)}s`;
73036
75042
  var init_ci = __esm(() => {
73037
- init_stub();
73038
- run14 = notImplemented("ci", "S10");
75043
+ init_report();
75044
+ init_output();
75045
+ init_forge_common();
73039
75046
  });
73040
75047
 
73041
75048
  // packages/workit-cli/src/verbs/git.ts
@@ -73071,15 +75078,991 @@ var init_stack = __esm(() => {
73071
75078
  run17 = notImplemented("stack", "S12");
73072
75079
  });
73073
75080
 
75081
+ // packages/workit-core/src/ledger.ts
75082
+ import { spawnSync as spawnSync15 } from "node:child_process";
75083
+ import { createHash as createHash13, randomBytes as randomBytes2 } from "node:crypto";
75084
+ import fs19 from "node:fs";
75085
+ import path37 from "node:path";
75086
+ function resolveCommit(cwd, ref) {
75087
+ if (!safeRef(ref))
75088
+ return null;
75089
+ return git2(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
75090
+ }
75091
+ function branchHead(cwd, branch) {
75092
+ if (!safeRef(branch))
75093
+ return null;
75094
+ return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
75095
+ }
75096
+ function defaultBase2(cwd) {
75097
+ const originHead = git2(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
75098
+ if (originHead && resolveCommit(cwd, originHead))
75099
+ return originHead;
75100
+ for (const candidate of ["origin/main", "origin/master", "main", "master"])
75101
+ if (resolveCommit(cwd, candidate))
75102
+ return candidate;
75103
+ return null;
75104
+ }
75105
+ function diffHash(cwd, base, head) {
75106
+ if (!safeRef(base) || !safeRef(head))
75107
+ return null;
75108
+ const diff = gitRun(cwd, [
75109
+ "diff",
75110
+ "-U3",
75111
+ "--diff-algorithm=myers",
75112
+ "--indent-heuristic",
75113
+ "--no-color",
75114
+ "--no-ext-diff",
75115
+ "--no-textconv",
75116
+ "--no-renames",
75117
+ "--full-index",
75118
+ "--binary",
75119
+ "--src-prefix=a/",
75120
+ "--dst-prefix=b/",
75121
+ `${base}...${head}`,
75122
+ "--"
75123
+ ], GIT_TIMEOUTS.worktree);
75124
+ if (!diff.ok || !diff.stdout)
75125
+ return null;
75126
+ const hash = createHash13("sha256");
75127
+ for (const line of diff.stdout.split(`
75128
+ `)) {
75129
+ if (line.startsWith("index "))
75130
+ continue;
75131
+ hash.update(line.startsWith("@@ ") ? "@@" : line);
75132
+ hash.update(`
75133
+ `);
75134
+ }
75135
+ return `sha256:${hash.digest("hex")}`;
75136
+ }
75137
+ function storeRoot(cwd) {
75138
+ const run = gitRun(cwd, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
75139
+ const common = run.stdout.trim();
75140
+ if (run.ok && common)
75141
+ return {
75142
+ ok: true,
75143
+ value: { root: path37.join(path37.resolve(cwd, common), "workit"), shared: true }
75144
+ };
75145
+ if (!run.ok && NOT_A_REPO.test(run.stderr))
75146
+ return { ok: true, value: { root: path37.join(path37.resolve(cwd), ".workit"), shared: false } };
75147
+ return err("unavailable", `cannot locate the workit store: git rev-parse failed (${run.stderr.trim().split(`
75148
+ `)[0] || "no output"})`, "fix the repository (git status should work here) or run from a non-git directory");
75149
+ }
75150
+ function ledgerPath(cwd) {
75151
+ const root = storeRoot(cwd);
75152
+ return root.ok ? { ok: true, value: path37.join(root.value.root, "ledger", "ledger.jsonl") } : root;
75153
+ }
75154
+ function actorFromEnv(env) {
75155
+ const value = (key) => {
75156
+ const raw = env[key]?.trim();
75157
+ return raw ? raw : null;
75158
+ };
75159
+ return {
75160
+ host: value("WORKIT_HOST") ?? "cli",
75161
+ session: value("WORKIT_SESSION_ID"),
75162
+ agentId: value("WORKIT_AGENT_ID")
75163
+ };
75164
+ }
75165
+ function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS2) {
75166
+ const lock = `${file}.lock`;
75167
+ const deadline = Date.now() + waitMs;
75168
+ let token = null;
75169
+ for (;; ) {
75170
+ token = ledgerLock.acquire(lock);
75171
+ if (token)
75172
+ break;
75173
+ const observed = lockToken(lock);
75174
+ const age = lockAgeMs(lock);
75175
+ if (age !== null && age > LOCK_STALE_MS2 && ledgerLock.reap(lock, observed))
75176
+ continue;
75177
+ if (Date.now() >= deadline)
75178
+ return null;
75179
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
75180
+ }
75181
+ try {
75182
+ return fn();
75183
+ } finally {
75184
+ ledgerLock.release(lock, token);
75185
+ }
75186
+ }
75187
+ function appendLine(file, line, options) {
75188
+ const write = () => {
75189
+ const fd = fs19.openSync(file, "a+");
75190
+ try {
75191
+ const size = fs19.fstatSync(fd).size;
75192
+ let fence = "";
75193
+ if (size > 0) {
75194
+ const last = Buffer.alloc(1);
75195
+ fs19.readSync(fd, last, 0, 1, size - 1);
75196
+ if (last[0] !== 10)
75197
+ fence = `
75198
+ `;
75199
+ }
75200
+ const bytes = Buffer.from(fence + line);
75201
+ const written = fs19.writeSync(fd, bytes);
75202
+ if (written !== bytes.length)
75203
+ return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
75204
+ return { ok: true, value: undefined };
75205
+ } finally {
75206
+ fs19.closeSync(fd);
75207
+ }
75208
+ };
75209
+ try {
75210
+ fs19.mkdirSync(path37.dirname(file), { recursive: true });
75211
+ if (!options.forceLock && !onNetworkFs(path37.dirname(file)))
75212
+ return write();
75213
+ const locked = withLedgerLock(file, write);
75214
+ return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
75215
+ } catch (error) {
75216
+ return err("unavailable", `cannot append to ${file}: ${error.message}`);
75217
+ }
75218
+ }
75219
+ function appendRaw(cwd, row, options) {
75220
+ const { full, line } = serialize(row, options.now ?? new Date);
75221
+ const bytes = Buffer.byteLength(line);
75222
+ if (bytes > MAX_LINE_BYTES)
75223
+ 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>`);
75224
+ const file = ledgerPath(cwd);
75225
+ if (!file.ok)
75226
+ return file;
75227
+ const written = appendLine(file.value, line, options);
75228
+ return written.ok ? { ok: true, value: full } : written;
75229
+ }
75230
+ function appendRow(cwd, row, options = {}) {
75231
+ if (row.observer === "workit_cli")
75232
+ return err("invalid_input", 'observer "workit_cli" is reserved for appendObserved');
75233
+ return appendRaw(cwd, row, options);
75234
+ }
75235
+ function supersedeAllowed(by, target) {
75236
+ if (by.type !== target.type)
75237
+ return `a ${by.type} row cannot supersede a ${target.type} row`;
75238
+ if (!by.actor.session || by.actor.session !== target.actor.session)
75239
+ return "only the session that wrote a row can supersede it";
75240
+ if (by.type === "verdict" && by.self === true && target.self !== true)
75241
+ return "a self verdict cannot replace an independent verdict";
75242
+ return null;
75243
+ }
75244
+ function readLedger(cwd) {
75245
+ const file = ledgerPath(cwd);
75246
+ if (!file.ok)
75247
+ return file;
75248
+ let text = "";
75249
+ try {
75250
+ text = fs19.readFileSync(file.value, "utf8");
75251
+ } catch (error) {
75252
+ if (error.code === "ENOENT")
75253
+ return { ok: true, value: { path: file.value, rows: [], skipped: 0 } };
75254
+ return err("unavailable", `cannot read ${file.value}: ${error.message}`);
75255
+ }
75256
+ const rows = [];
75257
+ let skipped = 0;
75258
+ for (const raw of text.split(`
75259
+ `)) {
75260
+ const trimmed = raw.trim();
75261
+ if (!trimmed)
75262
+ continue;
75263
+ let parsed;
75264
+ try {
75265
+ parsed = JSON.parse(trimmed);
75266
+ } catch {
75267
+ skipped += 1;
75268
+ continue;
75269
+ }
75270
+ if (!isRecord5(parsed) || !str(parsed.id) || !str(parsed.type) || !str(parsed.at)) {
75271
+ skipped += 1;
75272
+ continue;
75273
+ }
75274
+ const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
75275
+ rows.push({
75276
+ ...parsed,
75277
+ v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
75278
+ id: parsed.id,
75279
+ at: parsed.at,
75280
+ type: parsed.type,
75281
+ actor: normalizeActor(parsed.actor),
75282
+ branch: str(parsed.branch),
75283
+ head: str(parsed.head),
75284
+ base: str(parsed.base),
75285
+ baseSha: str(parsed.baseSha),
75286
+ patchId: str(parsed.patchId),
75287
+ diffHash: str(parsed.diffHash),
75288
+ tree: str(parsed.tree),
75289
+ dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
75290
+ ...pr === undefined ? {} : { pr },
75291
+ ...str(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
75292
+ seq: rows.length + 1,
75293
+ superseded: false,
75294
+ supersedeIgnored: false
75295
+ });
75296
+ }
75297
+ const byId = new Map;
75298
+ for (const row of rows) {
75299
+ const target = row.supersedes ? byId.get(row.supersedes) : undefined;
75300
+ if (target && supersedeAllowed(row, target) === null)
75301
+ target.superseded = true;
75302
+ else if (row.supersedes)
75303
+ row.supersedeIgnored = true;
75304
+ if (!byId.has(row.id))
75305
+ byId.set(row.id, row);
75306
+ }
75307
+ return { ok: true, value: { path: file.value, rows, skipped } };
75308
+ }
75309
+ function filterRows(rows, filter) {
75310
+ const matched = rows.filter((row) => (filter.branch === undefined || row.branch === filter.branch) && (filter.pr === undefined || row.pr === filter.pr) && (filter.type === undefined || row.type === filter.type));
75311
+ return filter.last === undefined ? matched : matched.slice(Math.max(0, matched.length - filter.last));
75312
+ }
75313
+ function branchForPr(cwd, rows, pr) {
75314
+ for (let index = rows.length - 1;index >= 0; index -= 1) {
75315
+ const row = rows[index];
75316
+ if (row.pr === pr && row.branch && PR_ROW_TYPES.has(row.type) && row.observer === "workit_cli")
75317
+ return { ok: true, branch: row.branch, source: "pr_row" };
75318
+ }
75319
+ const tips = git2(cwd, [
75320
+ "for-each-ref",
75321
+ "--format=%(objectname)",
75322
+ `refs/pull/${pr}/head`,
75323
+ `refs/merge-requests/${pr}/head`,
75324
+ `refs/remotes/*/pull/${pr}/head`,
75325
+ `refs/remotes/*/merge-requests/${pr}/head`
75326
+ ]);
75327
+ const shas = [...new Set((tips ?? "").split(`
75328
+ `).filter(Boolean))];
75329
+ if (shas.length === 0)
75330
+ return { ok: false, reason: "unknown", candidates: [] };
75331
+ if (shas.length > 1)
75332
+ return { ok: false, reason: "ambiguous", candidates: shas };
75333
+ const branches = (git2(cwd, ["for-each-ref", "--format=%(refname:short)", "--points-at", shas[0], "refs/heads"]) ?? "").split(`
75334
+ `).filter(Boolean);
75335
+ if (branches.length === 1)
75336
+ return { ok: true, branch: branches[0], source: "forge_ref" };
75337
+ return branches.length === 0 ? { ok: false, reason: "unknown", candidates: [] } : { ok: false, reason: "ambiguous", candidates: branches };
75338
+ }
75339
+ function codeKey(cwd, options = {}) {
75340
+ const checkedOut = currentBranch2(cwd);
75341
+ const branch = options.branch ?? checkedOut;
75342
+ const head = branch ? branchHead(cwd, branch) : headSha(cwd);
75343
+ const base = options.base ?? defaultBase2(cwd);
75344
+ const baseSha = base ? resolveCommit(cwd, base) : null;
75345
+ const onTree = branch !== null && branch === checkedOut;
75346
+ const tree = onTree ? worktreeTree(cwd) : null;
75347
+ return {
75348
+ branch,
75349
+ head,
75350
+ base,
75351
+ baseSha,
75352
+ patchId: base && head ? patchId(cwd, base, head) : null,
75353
+ diffHash: base && head ? diffHash(cwd, base, head) : null,
75354
+ tree: tree?.key ?? null,
75355
+ dirty: tree ? tree.dirty : null
75356
+ };
75357
+ }
75358
+ function verdictBasis(verdict, current) {
75359
+ if (verdict.head && verdict.head === current.head)
75360
+ return "fresh";
75361
+ if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
75362
+ return "carried";
75363
+ return "stale";
75364
+ }
75365
+ function trailerSessions(cwd, base, head) {
75366
+ if (!base || !head || !safeRef(base) || !safeRef(head))
75367
+ return [];
75368
+ const out = git2(cwd, [
75369
+ "log",
75370
+ "--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
75371
+ `${base}..${head}`
75372
+ ]);
75373
+ return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
75374
+ }
75375
+ function authorSessions(cwd, rows, branch, range = { base: null, head: null }) {
75376
+ const sessions = new Set;
75377
+ for (const row of rows) {
75378
+ if (row.branch !== branch || row.type !== "commit.recorded" && row.type !== "task.opened")
75379
+ continue;
75380
+ const session = str(row.session) ?? row.actor.session;
75381
+ if (session)
75382
+ sessions.add(session);
75383
+ }
75384
+ for (const session of trailerSessions(cwd, range.base, range.head))
75385
+ sessions.add(session);
75386
+ return sessions;
75387
+ }
75388
+ function effectiveOf(rows, authors) {
75389
+ let effective = null;
75390
+ for (const row of rows) {
75391
+ if (!effective) {
75392
+ effective = row;
75393
+ continue;
75394
+ }
75395
+ const rowIndependent = independenceReasons(row, authors).length === 0;
75396
+ const effIndependent = independenceReasons(effective, authors).length === 0;
75397
+ if (effIndependent && !rowIndependent)
75398
+ continue;
75399
+ if (effIndependent && FAILING2.has(String(effective.result)) && row.actor.session === effective.actor.session)
75400
+ continue;
75401
+ effective = row;
75402
+ }
75403
+ return effective;
75404
+ }
75405
+ function checkVerdicts(cwd, branch, rows) {
75406
+ const head = branchHead(cwd, branch);
75407
+ const fallbackBase = defaultBase2(cwd);
75408
+ const keyCache = new Map;
75409
+ const keyFor = (base) => {
75410
+ if (!base || !head)
75411
+ return { patchId: null, diffHash: null };
75412
+ let key = keyCache.get(base);
75413
+ if (!key) {
75414
+ key = { patchId: patchId(cwd, base, head), diffHash: diffHash(cwd, base, head) };
75415
+ keyCache.set(base, key);
75416
+ }
75417
+ return key;
75418
+ };
75419
+ const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head });
75420
+ const byKind = new Map;
75421
+ for (const row of rows)
75422
+ if (row.branch === branch && isLiveVerdict(row)) {
75423
+ const kind = typeof row.kind === "string" ? row.kind : "review";
75424
+ byKind.set(kind, [...byKind.get(kind) ?? [], row]);
75425
+ }
75426
+ const verdicts = [];
75427
+ for (const [kind, list] of byKind) {
75428
+ const row = effectiveOf(list, authors);
75429
+ if (!row)
75430
+ continue;
75431
+ const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
75432
+ const current = basis !== "stale";
75433
+ const independence = independenceReasons(row, authors);
75434
+ const reasons = [
75435
+ ...current ? [] : ["stale"],
75436
+ ...PASSING.has(String(row.result)) ? [] : ["not_passing"],
75437
+ ...independence
75438
+ ];
75439
+ verdicts.push({
75440
+ kind,
75441
+ basis,
75442
+ current,
75443
+ independent: independence.length === 0,
75444
+ accepted: reasons.length === 0,
75445
+ reasons,
75446
+ verdict: row
75447
+ });
75448
+ }
75449
+ verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
75450
+ const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
75451
+ const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING2.has(String(entry.verdict.result)));
75452
+ const newestAccepted = verdicts.findLast((entry) => entry.accepted) ?? null;
75453
+ const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
75454
+ accepted: false,
75455
+ verdict: failing?.verdict ?? null,
75456
+ reasons: failing ? ["failing_verdict"] : newestCurrent?.reasons ?? ["no_verdict"]
75457
+ };
75458
+ return {
75459
+ branch,
75460
+ head,
75461
+ base: newestCurrent?.verdict.base ?? fallbackBase,
75462
+ patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
75463
+ current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
75464
+ accepted,
75465
+ verdicts,
75466
+ authors: [...authors].toSorted()
75467
+ };
75468
+ }
75469
+ function checkSupersede(context, type, self2) {
75470
+ if (!context.supersedes)
75471
+ return { ok: true, value: undefined };
75472
+ const ledger = readLedger(context.cwd);
75473
+ if (!ledger.ok)
75474
+ return ledger;
75475
+ const target = ledger.value.rows.find((row) => row.id === context.supersedes);
75476
+ if (!target)
75477
+ return err("not_found", `no ledger row ${context.supersedes} to supersede`);
75478
+ const problem = supersedeAllowed({ type, actor: context.actor, self: self2 }, target);
75479
+ return problem ? err("invalid_input", `--supersedes: ${problem}`) : { ok: true, value: undefined };
75480
+ }
75481
+ function recordDecision(context, input) {
75482
+ const what = required2(input.what, "<what>");
75483
+ if (!what.ok)
75484
+ return what;
75485
+ const why = required2(input.why, "--why");
75486
+ if (!why.ok)
75487
+ return why;
75488
+ const link = checkSupersede(context, "decision");
75489
+ if (!link.ok)
75490
+ return link;
75491
+ return appendRow(context.cwd, {
75492
+ ...common2(context, noteKey(context)),
75493
+ type: "decision",
75494
+ what: what.value,
75495
+ why: why.value,
75496
+ refs: input.refs ?? []
75497
+ }, { now: context.now });
75498
+ }
75499
+ function recordRuling(context, input) {
75500
+ const what = required2(input.what, "<what>");
75501
+ if (!what.ok)
75502
+ return what;
75503
+ const why = required2(input.why, "--why");
75504
+ if (!why.ok)
75505
+ return why;
75506
+ const cost = required2(input.costIfWrong, "--cost-if-wrong");
75507
+ if (!cost.ok)
75508
+ return cost;
75509
+ const link = checkSupersede(context, "ruling");
75510
+ if (!link.ok)
75511
+ return link;
75512
+ return appendRow(context.cwd, {
75513
+ ...common2(context, noteKey(context)),
75514
+ type: "ruling",
75515
+ what: what.value,
75516
+ why: why.value,
75517
+ costIfWrong: cost.value,
75518
+ refs: input.refs ?? []
75519
+ }, { now: context.now });
75520
+ }
75521
+ function recordVerdict(context, input) {
75522
+ if (!VERDICT_RESULTS.includes(input.result))
75523
+ return err("invalid_input", `unknown verdict "${input.result}"; expected ${VERDICT_RESULTS.join("|")}`);
75524
+ const kind = input.kind ?? "review";
75525
+ if (!VERDICT_KINDS.includes(kind))
75526
+ return err("invalid_input", `--kind must be ${VERDICT_KINDS.join("|")}`);
75527
+ const surface = input.surface ?? null;
75528
+ if (surface !== null && !VERDICT_SURFACES.includes(surface))
75529
+ return err("invalid_input", `--surface must be ${VERDICT_SURFACES.join("|")}`);
75530
+ const how = required2(input.how, "--how");
75531
+ if (!how.ok)
75532
+ return how;
75533
+ const branch = context.branch ?? currentBranch2(context.cwd);
75534
+ if (!branch)
75535
+ return err("invalid_input", "HEAD is detached; name the branch the verdict is for", "pass --branch <branch>");
75536
+ const ledger = readLedger(context.cwd);
75537
+ if (!ledger.ok)
75538
+ return ledger;
75539
+ const key = codeKey(context.cwd, { branch, base: context.base });
75540
+ if (!key.head)
75541
+ return err("not_found", `branch "${branch}" has no commit to judge`);
75542
+ if (key.dirty === true)
75543
+ return err("blocked", `dirty_worktree: ${branch} has uncommitted changes, so its head is not what was judged`, "commit or stash the changes, re-check, then record the verdict");
75544
+ const session = context.actor.session;
75545
+ const selfReason = input.self === true ? "flag" : session ? null : "no_session";
75546
+ const self2 = selfReason !== null;
75547
+ if (!self2 && session && authorSessions(context.cwd, ledger.value.rows, branch, { base: key.base, head: key.head }).has(session))
75548
+ return err("blocked", `author_verdict: session ${session} authored ${branch}; a verdict must come from a different session`, 'have a non-author session record the verdict, or pass --self (a self verdict is never accepted by merge:"verified")');
75549
+ const link = checkSupersede(context, "verdict", self2);
75550
+ if (!link.ok)
75551
+ return link;
75552
+ return appendRow(context.cwd, {
75553
+ ...common2(context, key),
75554
+ type: "verdict",
75555
+ kind,
75556
+ result: input.result,
75557
+ how: how.value,
75558
+ surface,
75559
+ observer: "agent_asserted",
75560
+ self: self2,
75561
+ selfReason,
75562
+ evidenceRefs: input.evidenceRefs ?? []
75563
+ }, { now: context.now });
75564
+ }
75565
+ function recordHandoff(context, input) {
75566
+ return appendRow(context.cwd, { ...common2(context, noteKey(context)), type: "handoff", note: input.note, next: input.next }, { now: context.now });
75567
+ }
75568
+ function rowLabels(row) {
75569
+ const labels = [];
75570
+ if (row.superseded)
75571
+ labels.push("superseded");
75572
+ if (row.supersedeIgnored || OBSERVED_ONLY.has(row.type) && row.observer !== "workit_cli")
75573
+ labels.push("ignored");
75574
+ if (row.type === "verdict" && row.self === true)
75575
+ labels.push("self");
75576
+ return labels;
75577
+ }
75578
+ function summarizeRow(row) {
75579
+ const text = (key) => str(row[key]) ?? "";
75580
+ let summary;
75581
+ switch (row.type) {
75582
+ case "decision":
75583
+ summary = `${text("what")} (why: ${text("why")})`;
75584
+ break;
75585
+ case "ruling":
75586
+ summary = `${text("what")} (why: ${text("why")}; cost if wrong: ${text("costIfWrong")})`;
75587
+ break;
75588
+ case "verdict":
75589
+ summary = `${text("result")} [${text("kind") || "review"}] at ${(row.head ?? "?").slice(0, 12)}${row.self === true ? ` (self${text("selfReason") === "no_session" ? ": no session" : ""})` : ""}: ${text("how")}`;
75590
+ break;
75591
+ case "handoff":
75592
+ summary = `next: ${text("next")}${text("note") ? ` (note: ${text("note")})` : ""}`;
75593
+ break;
75594
+ default:
75595
+ summary = text("what") || text("name") || row.type;
75596
+ }
75597
+ return {
75598
+ seq: row.seq,
75599
+ id: row.id,
75600
+ at: row.at,
75601
+ type: row.type,
75602
+ branch: row.branch,
75603
+ summary,
75604
+ labels: rowLabels(row)
75605
+ };
75606
+ }
75607
+ function stackPosition(root, branch) {
75608
+ const dir = path37.join(root, "stacks");
75609
+ let names = [];
75610
+ try {
75611
+ names = fs19.readdirSync(dir).filter((name) => name.endsWith(".json"));
75612
+ } catch {
75613
+ return null;
75614
+ }
75615
+ for (const name of names.toSorted()) {
75616
+ let parsed;
75617
+ try {
75618
+ parsed = JSON.parse(fs19.readFileSync(path37.join(dir, name), "utf8"));
75619
+ } catch {
75620
+ continue;
75621
+ }
75622
+ if (!isRecord5(parsed) || !Array.isArray(parsed.branches))
75623
+ continue;
75624
+ const branches = parsed.branches.filter(isRecord5);
75625
+ const index = branches.findIndex((entry) => entry.branch === branch);
75626
+ if (index < 0)
75627
+ continue;
75628
+ const entry = branches[index];
75629
+ return {
75630
+ name: str(parsed.name) ?? name.replace(/\.json$/u, ""),
75631
+ index: index + 1,
75632
+ size: branches.length,
75633
+ parent: str(entry.parent),
75634
+ pr: typeof entry.pr === "number" ? entry.pr : null
75635
+ };
75636
+ }
75637
+ return null;
75638
+ }
75639
+ function buildHandoff(cwd, options = {}) {
75640
+ const store = storeRoot(cwd);
75641
+ if (!store.ok)
75642
+ return store;
75643
+ const read = readLedger(cwd);
75644
+ if (!read.ok)
75645
+ return read;
75646
+ const ledger = read.value;
75647
+ const branch = currentBranch2(cwd);
75648
+ const head = headSha(cwd);
75649
+ const base = defaultBase2(cwd);
75650
+ let ahead = null;
75651
+ let behind = null;
75652
+ if (base && head) {
75653
+ const counts = git2(cwd, ["rev-list", "--left-right", "--count", `${base}...HEAD`]);
75654
+ const [left, right] = (counts ?? "").split(/\s+/u).map(Number);
75655
+ if (Number.isFinite(left) && Number.isFinite(right)) {
75656
+ behind = left;
75657
+ ahead = right;
75658
+ }
75659
+ }
75660
+ const mine = branch ? filterRows(ledger.rows, { branch }) : [];
75661
+ const stack = branch ? stackPosition(store.value.root, branch) : null;
75662
+ const pr = stack?.pr ?? mine.findLast((row) => row.pr !== undefined && PR_ROW_TYPES.has(row.type) && row.observer === "workit_cli")?.pr ?? null;
75663
+ const check = branch ? checkVerdicts(cwd, branch, ledger.rows) : null;
75664
+ const checkRows = mine.filter((row) => row.type === "check" && row.observer === "workit_cli" && str(row.name));
75665
+ const latestChecks = new Map;
75666
+ for (const row of checkRows)
75667
+ latestChecks.set(str(row.name), row);
75668
+ const tree = latestChecks.size ? worktreeTree(cwd)?.key ?? null : null;
75669
+ const checks = [...latestChecks.entries()].map(([name, row]) => ({
75670
+ name,
75671
+ result: str(row.result),
75672
+ fresh: tree !== null && row.tree === tree,
75673
+ at: row.at
75674
+ }));
75675
+ const lastHandoffRow = mine.findLast((row) => row.type === "handoff");
75676
+ const partial = {
75677
+ store: { root: store.value.root, shared: store.value.shared, ledger: ledger.path },
75678
+ branch,
75679
+ head,
75680
+ subject: head ? git2(cwd, ["log", "-1", "--format=%s", head]) : null,
75681
+ base,
75682
+ ahead,
75683
+ behind,
75684
+ dirty: dirtyState(cwd),
75685
+ pr,
75686
+ stack,
75687
+ verdict: {
75688
+ current: check?.current.basis ?? "none",
75689
+ accepted: check?.accepted.accepted ?? false,
75690
+ reasons: check?.accepted.reasons ?? ["no_verdict"],
75691
+ verdicts: (check?.verdicts ?? []).map(({ verdict: row, ...rest }) => ({
75692
+ ...rest,
75693
+ summary: summarizeRow(row).summary
75694
+ }))
75695
+ },
75696
+ checks,
75697
+ rulings: mine.filter((row) => row.type === "ruling" && !row.superseded).slice(-5).map(summarizeRow),
75698
+ recent: ledger.rows.slice(-(options.last ?? 10)).map(summarizeRow),
75699
+ lastHandoff: lastHandoffRow ? {
75700
+ at: lastHandoffRow.at,
75701
+ note: str(lastHandoffRow.note),
75702
+ next: str(lastHandoffRow.next) ?? ""
75703
+ } : null,
75704
+ note: options.note?.trim() || null,
75705
+ skippedRows: ledger.skipped
75706
+ };
75707
+ const nextCommand = nextCommandFor(partial);
75708
+ return {
75709
+ ok: true,
75710
+ value: { ...partial, nextCommand, next: options.next?.trim() || nextCommand }
75711
+ };
75712
+ }
75713
+ var LEDGER_VERSION = 1, MAX_LINE_BYTES = 4096, VERDICT_RESULTS, PASSING, FAILING2, VERDICT_KINDS, VERDICT_SURFACES, PR_ROW_TYPES, err = (code, error, unblock) => ({
75714
+ ok: false,
75715
+ code,
75716
+ error,
75717
+ ...unblock ? { unblock } : {}
75718
+ }), gitRun = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
75719
+ const run = spawnSync15("git", args, {
75720
+ cwd,
75721
+ encoding: "utf8",
75722
+ timeout: timeoutMs,
75723
+ killSignal: "SIGKILL",
75724
+ windowsHide: true,
75725
+ maxBuffer: 256 * 1024 * 1024,
75726
+ env: {
75727
+ ...process.env,
75728
+ GIT_TERMINAL_PROMPT: "0",
75729
+ GIT_OPTIONAL_LOCKS: "0",
75730
+ LC_ALL: "C",
75731
+ LANGUAGE: "C"
75732
+ }
75733
+ });
75734
+ return {
75735
+ ok: run.status === 0,
75736
+ stdout: run.stdout ?? "",
75737
+ stderr: run.error ? String(run.error.message) : run.stderr ?? ""
75738
+ };
75739
+ }, git2 = (cwd, args) => {
75740
+ const run = gitRun(cwd, args);
75741
+ const value = run.ok ? run.stdout.trim() : "";
75742
+ return value ? value : null;
75743
+ }, safeRef = (value) => value.length > 0 && !value.startsWith("-"), NOT_A_REPO, newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`, NETWORK_FS_TYPES, isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0), onNetworkFs = (dir) => {
75744
+ if (process.platform !== "linux")
75745
+ return false;
75746
+ try {
75747
+ return isNetworkFsType(fs19.statfsSync(dir).type);
75748
+ } catch {
75749
+ return false;
75750
+ }
75751
+ }, LOCK_WAIT_MS2 = 5000, LOCK_STALE_MS2 = 30000, LOCK_OWNER = "owner", lockToken = (lock) => {
75752
+ try {
75753
+ return fs19.readFileSync(path37.join(lock, LOCK_OWNER), "utf8");
75754
+ } catch {
75755
+ return null;
75756
+ }
75757
+ }, lockAgeMs = (lock) => {
75758
+ for (const target of [path37.join(lock, LOCK_OWNER), lock]) {
75759
+ try {
75760
+ return Date.now() - fs19.statSync(target).mtimeMs;
75761
+ } catch {}
75762
+ }
75763
+ return null;
75764
+ }, ledgerLock, serialize = (row, now) => {
75765
+ const full = { v: LEDGER_VERSION, id: newId2(), at: now.toISOString(), ...row };
75766
+ return { full, line: `${JSON.stringify(full)}
75767
+ ` };
75768
+ }, isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), str = (value) => typeof value === "string" ? value : null, normalizeActor = (value) => {
75769
+ const actor = isRecord5(value) ? value : {};
75770
+ return {
75771
+ host: str(actor.host) ?? "unknown",
75772
+ session: str(actor.session),
75773
+ agentId: str(actor.agentId)
75774
+ };
75775
+ }, isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.superseded, independenceReasons = (row, authors) => {
75776
+ const reasons = [];
75777
+ if (!row.actor.session)
75778
+ reasons.push("no_session");
75779
+ else if (authors.has(row.actor.session))
75780
+ reasons.push("author_session");
75781
+ if (row.self === true && !reasons.includes("no_session"))
75782
+ reasons.push("self");
75783
+ return reasons;
75784
+ }, common2 = (context, key) => ({
75785
+ ...key,
75786
+ actor: context.actor,
75787
+ ...context.pr === undefined ? {} : { pr: context.pr },
75788
+ ...context.supersedes ? { supersedes: context.supersedes } : {}
75789
+ }), noteKey = (context) => {
75790
+ const branch = context.branch ?? currentBranch2(context.cwd);
75791
+ return {
75792
+ branch,
75793
+ head: branch ? branchHead(context.cwd, branch) : headSha(context.cwd),
75794
+ base: null,
75795
+ baseSha: null,
75796
+ patchId: null,
75797
+ diffHash: null,
75798
+ tree: null,
75799
+ dirty: null
75800
+ };
75801
+ }, required2 = (value, flag) => {
75802
+ const text = value?.trim();
75803
+ return text ? { ok: true, value: text } : err("invalid_input", `${flag} is required`);
75804
+ }, OBSERVED_ONLY, MAX_DIRTY_PATHS = 10, dirtyState = (cwd) => {
75805
+ const run = gitRun(cwd, ["status", "--porcelain=v1", "-z", "--untracked-files=normal"], GIT_TIMEOUTS.worktree);
75806
+ if (!run.ok)
75807
+ return { dirty: false, files: 0, paths: [] };
75808
+ const paths = [];
75809
+ const entries = run.stdout.split("\x00");
75810
+ for (let index = 0;index < entries.length; index += 1) {
75811
+ const entry = entries[index];
75812
+ if (entry.length < 4)
75813
+ continue;
75814
+ paths.push(entry.slice(3));
75815
+ if (entry[0] === "R" || entry[0] === "C")
75816
+ index += 1;
75817
+ }
75818
+ return { dirty: paths.length > 0, files: paths.length, paths: paths.slice(0, MAX_DIRTY_PATHS) };
75819
+ }, nextCommandFor = (brief) => {
75820
+ const branchFlag = brief.branch ? ` --branch ${brief.branch}` : "";
75821
+ const verify = `workit ledger verdict verified${branchFlag} --how "<method and evidence>" (from a session that did not author the branch, with WORKIT_SESSION_ID set)`;
75822
+ if (!brief.head)
75823
+ return "make the first commit on this branch, then: workit handoff";
75824
+ if (brief.dirty.dirty)
75825
+ return `commit or stash the ${brief.dirty.files} changed file(s), then: workit handoff`;
75826
+ const stale = brief.checks.find((check) => !check.fresh);
75827
+ if (stale)
75828
+ return `workit check ${stale.name}`;
75829
+ const { verdict } = brief;
75830
+ if (verdict.reasons.includes("failing_verdict"))
75831
+ return `fix what the failing verdict found, then get a new independent verdict: ${verify}`;
75832
+ if (!verdict.accepted)
75833
+ return verify;
75834
+ return brief.pr === null ? "workit pr create" : `workit pr status --pr ${brief.pr}`;
75835
+ };
75836
+ var init_ledger = __esm(() => {
75837
+ init_rev();
75838
+ VERDICT_RESULTS = [
75839
+ "verified",
75840
+ "tests-verified",
75841
+ "type-check-only",
75842
+ "blocked",
75843
+ "failed"
75844
+ ];
75845
+ PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
75846
+ FAILING2 = new Set(["failed", "blocked"]);
75847
+ VERDICT_KINDS = ["unit", "live", "perf", "review"];
75848
+ VERDICT_SURFACES = ["ui", "cli", "api"];
75849
+ PR_ROW_TYPES = new Set(["pr.created", "pr.status"]);
75850
+ NOT_A_REPO = /not a git repository \(or any (of the parent directories|parent up to)/u;
75851
+ NETWORK_FS_TYPES = new Set([
75852
+ 26985,
75853
+ 20859,
75854
+ 4266872130,
75855
+ 4283649346,
75856
+ 1702057286,
75857
+ 1397113167,
75858
+ 12805120,
75859
+ 16914839,
75860
+ 1196443219,
75861
+ 198183888
75862
+ ]);
75863
+ ledgerLock = {
75864
+ acquire(lock) {
75865
+ try {
75866
+ fs19.mkdirSync(lock);
75867
+ } catch (error) {
75868
+ if (error.code === "EEXIST")
75869
+ return null;
75870
+ throw error;
75871
+ }
75872
+ const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
75873
+ fs19.writeFileSync(path37.join(lock, LOCK_OWNER), token);
75874
+ return token;
75875
+ },
75876
+ release(lock, token) {
75877
+ if (lockToken(lock) !== token)
75878
+ return false;
75879
+ const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
75880
+ try {
75881
+ fs19.renameSync(lock, tomb);
75882
+ } catch {
75883
+ return false;
75884
+ }
75885
+ if (lockToken(tomb) !== token) {
75886
+ try {
75887
+ fs19.renameSync(tomb, lock);
75888
+ } catch {}
75889
+ return false;
75890
+ }
75891
+ fs19.rmSync(tomb, { recursive: true, force: true });
75892
+ return true;
75893
+ },
75894
+ token: lockToken,
75895
+ reap(lock, observed) {
75896
+ const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
75897
+ try {
75898
+ fs19.renameSync(lock, tomb);
75899
+ } catch {
75900
+ return false;
75901
+ }
75902
+ if (lockToken(tomb) === observed) {
75903
+ fs19.rmSync(tomb, { recursive: true, force: true });
75904
+ return true;
75905
+ }
75906
+ try {
75907
+ fs19.renameSync(tomb, lock);
75908
+ } catch {
75909
+ fs19.rmSync(tomb, { recursive: true, force: true });
75910
+ }
75911
+ return false;
75912
+ }
75913
+ };
75914
+ OBSERVED_ONLY = new Set(["check", ...PR_ROW_TYPES]);
75915
+ });
75916
+
73074
75917
  // packages/workit-cli/src/verbs/ledger.ts
73075
75918
  var exports_ledger = {};
73076
75919
  __export(exports_ledger, {
73077
75920
  run: () => run18
73078
75921
  });
73079
- var run18;
73080
- var init_ledger = __esm(() => {
73081
- init_stub();
73082
- run18 = notImplemented("ledger", "S13");
75922
+ import { parseArgs } from "node:util";
75923
+ function targetBranch(io, rows, branch, pr) {
75924
+ if (pr !== undefined) {
75925
+ const resolved = branchForPr(io.cwd, rows, pr);
75926
+ if (!resolved.ok)
75927
+ return {
75928
+ ok: false,
75929
+ code: "invalid_input",
75930
+ error: resolved.reason === "ambiguous" ? `cannot resolve PR ${pr} to a branch: ambiguous (${resolved.candidates.join(", ")})` : `cannot resolve PR ${pr} to a branch: no workit pr row records it and no fetched forge ref (refs/pull/${pr}/head) points at a local branch tip`,
75931
+ unblock: "pass --branch <branch> instead, or fetch the PR ref"
75932
+ };
75933
+ if (branch !== undefined && branch !== resolved.branch)
75934
+ return {
75935
+ ok: false,
75936
+ code: "invalid_input",
75937
+ error: `PR ${pr} is branch ${resolved.branch}, not ${branch}`
75938
+ };
75939
+ return { ok: true, value: resolved.branch };
75940
+ }
75941
+ return { ok: true, value: branch ?? null };
75942
+ }
75943
+ async function run18(argv, io) {
75944
+ let parsed;
75945
+ try {
75946
+ parsed = parseArgs({ args: argv, options: OPTIONS, allowPositionals: true, strict: true });
75947
+ } catch (error) {
75948
+ return usage4(io, error.message);
75949
+ }
75950
+ const { values } = parsed;
75951
+ let positionals = parsed.positionals;
75952
+ if (positionals[0] === "add") {
75953
+ positionals = positionals.slice(1);
75954
+ if (!["decision", "ruling", "verdict"].includes(positionals[0] ?? ""))
75955
+ return usage4(io, "ledger add takes decision|ruling|verdict");
75956
+ }
75957
+ const [sub, ...rest] = positionals;
75958
+ const pr = positiveInt2(values.pr, "--pr");
75959
+ if (pr instanceof Error)
75960
+ return usage4(io, pr.message);
75961
+ const last = positiveInt2(values.last, "--last");
75962
+ if (last instanceof Error)
75963
+ return usage4(io, last.message);
75964
+ const text = rest.join(" ");
75965
+ if (sub === undefined)
75966
+ return usage4(io, "missing subcommand");
75967
+ if (!["decision", "ruling", "verdict", "list", "show", "check"].includes(sub))
75968
+ return usage4(io, `unknown ledger subcommand "${sub}"`);
75969
+ const ledger = readLedger(io.cwd);
75970
+ if (!ledger.ok)
75971
+ return failed(io, ledger);
75972
+ const { rows } = ledger.value;
75973
+ if (sub === "list" || sub === "show") {
75974
+ if (rest.length)
75975
+ return usage4(io, `unexpected argument: ${rest[0]}`);
75976
+ const listed = filterRows(rows, {
75977
+ branch: values.branch,
75978
+ pr,
75979
+ type: values.type,
75980
+ last: last ?? 50
75981
+ });
75982
+ return emit(io, ok({ path: ledger.value.path, skipped: ledger.value.skipped, rows: listed }), (data) => data.rows.length ? data.rows.map((row) => {
75983
+ const line = summarizeRow(row);
75984
+ return `#${line.seq} ${line.at} ${line.type}${line.branch ? ` [${line.branch}]` : ""}${line.labels.length ? ` (${line.labels.join(", ")})` : ""} ${line.summary}`;
75985
+ }) : "ledger is empty");
75986
+ }
75987
+ const isRead = sub === "check" || sub === "verdict" && values.how === undefined && !VERDICT_RESULTS.includes(rest[0] ?? "");
75988
+ if (isRead) {
75989
+ if (rest.length > (sub === "verdict" ? 1 : 0))
75990
+ return usage4(io, `unexpected argument: ${rest.at(-1)}`);
75991
+ const target = targetBranch(io, rows, rest[0] ?? values.branch, pr);
75992
+ if (!target.ok)
75993
+ return failed(io, target);
75994
+ const branch = target.value ?? currentBranch2(io.cwd);
75995
+ if (!branch)
75996
+ return emit(io, fail("invalid_input", "HEAD is detached; name the branch", {
75997
+ unblock: "pass --branch <branch>"
75998
+ }));
75999
+ return emit(io, ok(checkVerdicts(io.cwd, branch, rows)), verdictLines);
76000
+ }
76001
+ const target = targetBranch(io, rows, values.branch, pr);
76002
+ if (!target.ok)
76003
+ return failed(io, target);
76004
+ const context = {
76005
+ cwd: io.cwd,
76006
+ actor: actorFromEnv(io.env),
76007
+ branch: target.value,
76008
+ base: values.base ?? null,
76009
+ ...pr === undefined ? {} : { pr },
76010
+ ...values.supersedes ? { supersedes: values.supersedes } : {}
76011
+ };
76012
+ if (sub === "decision")
76013
+ return fromResult(io, recordDecision(context, { what: text, why: values.why, refs: values.ref }), (row) => `recorded decision ${row.id}`);
76014
+ if (sub === "ruling")
76015
+ return fromResult(io, recordRuling(context, {
76016
+ what: text,
76017
+ why: values.why,
76018
+ costIfWrong: values["cost-if-wrong"],
76019
+ refs: values.ref
76020
+ }), (row) => `recorded ruling ${row.id}`);
76021
+ if (rest.length !== 1)
76022
+ return usage4(io, `ledger verdict takes one result: ${VERDICT_RESULTS.join("|")}`);
76023
+ return fromResult(io, recordVerdict(context, {
76024
+ result: rest[0],
76025
+ kind: values.kind,
76026
+ how: values.how,
76027
+ surface: values.surface ?? null,
76028
+ self: values.self === true,
76029
+ evidenceRefs: values.evidence
76030
+ }), (row) => `recorded verdict ${row.id}: ${row.result} [${row.kind}] for ${row.branch} @ ${(row.head ?? "").slice(0, 12)}${row.self ? ` (self${row.selfReason === "no_session" ? ": WORKIT_SESSION_ID unset" : ""}; never accepted)` : ""}`);
76031
+ }
76032
+ var USAGE = "workit ledger decision|ruling|verdict|list|check ... (workit help ledger for the grammar)", OPTIONS, positiveInt2 = (value, flag) => {
76033
+ if (value === undefined)
76034
+ return;
76035
+ const parsed = Number(value);
76036
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : new Error(`${flag} must be a positive integer`);
76037
+ }, failed = (io, result) => emit(io, fail(result.code, result.error, result.unblock ? { unblock: result.unblock } : {})), fromResult = (io, result, human) => result.ok ? emit(io, ok(result.value), human) : failed(io, result), usage4 = (io, error) => emit(io, fail("invalid_input", error, { unblock: USAGE })), verdictLines = (check) => {
76038
+ const lines = [
76039
+ `${check.branch} @ ${(check.head ?? "no commit").slice(0, 12)}: current: ${check.current.basis}${typeof check.current.verdict?.result === "string" ? ` (${check.current.verdict.result})` : ""}; ${check.accepted.accepted ? "accepted" : `not accepted (${check.accepted.reasons.join(", ")})`}`
76040
+ ];
76041
+ for (const entry of check.verdicts)
76042
+ lines.push(` ${entry.kind}: ${entry.basis}${entry.accepted ? ", accepted" : ` (${entry.reasons.join(", ")})`} ${summarizeRow(entry.verdict).summary}`);
76043
+ return lines;
76044
+ };
76045
+ var init_ledger2 = __esm(() => {
76046
+ init_ledger();
76047
+ init_rev();
76048
+ init_output();
76049
+ OPTIONS = {
76050
+ why: { type: "string" },
76051
+ "cost-if-wrong": { type: "string" },
76052
+ ref: { type: "string", multiple: true },
76053
+ how: { type: "string" },
76054
+ kind: { type: "string" },
76055
+ surface: { type: "string" },
76056
+ evidence: { type: "string", multiple: true },
76057
+ self: { type: "boolean" },
76058
+ branch: { type: "string" },
76059
+ base: { type: "string" },
76060
+ pr: { type: "string" },
76061
+ type: { type: "string" },
76062
+ last: { type: "string" },
76063
+ supersedes: { type: "string" },
76064
+ json: { type: "boolean" }
76065
+ };
73083
76066
  });
73084
76067
 
73085
76068
  // packages/workit-cli/src/verbs/handoff.ts
@@ -73087,24 +76070,92 @@ var exports_handoff = {};
73087
76070
  __export(exports_handoff, {
73088
76071
  run: () => run19
73089
76072
  });
76073
+ import { parseArgs as parseArgs2 } from "node:util";
76074
+ function human(brief) {
76075
+ const lines = [
76076
+ `branch: ${brief.branch ?? "(detached)"} @ ${short2(brief.head)}${brief.subject ? ` ${brief.subject}` : ""}`
76077
+ ];
76078
+ if (brief.base)
76079
+ lines.push(`base: ${brief.base}${brief.ahead === null ? "" : ` (ahead ${brief.ahead}, behind ${brief.behind})`}`);
76080
+ lines.push(brief.dirty.dirty ? `dirty: ${brief.dirty.files} file(s): ${brief.dirty.paths.join(", ")}${brief.dirty.files > brief.dirty.paths.length ? ", …" : ""}` : "dirty: clean");
76081
+ if (brief.stack)
76082
+ lines.push(`stack: ${brief.stack.name} ${brief.stack.index}/${brief.stack.size}${brief.stack.parent ? ` on ${brief.stack.parent}` : ""}`);
76083
+ if (brief.pr !== null)
76084
+ lines.push(`pr: #${brief.pr}`);
76085
+ lines.push(`verdict: current ${brief.verdict.current}; ${brief.verdict.accepted ? "accepted" : `not accepted (${brief.verdict.reasons.join(", ")})`}`, ...brief.verdict.verdicts.map((entry) => ` ${entry.kind}: ${entry.basis}${entry.accepted ? ", accepted" : ` (${entry.reasons.join(", ")})`} ${entry.summary}`));
76086
+ if (brief.checks.length)
76087
+ lines.push("checks:", ...brief.checks.map((check) => ` ${check.name}: ${check.result ?? "?"} (${check.fresh ? "fresh" : "stale"})`));
76088
+ if (brief.rulings.length)
76089
+ lines.push("rulings:", ...brief.rulings.map((row) => ` #${row.seq} ${row.summary}`));
76090
+ if (brief.lastHandoff)
76091
+ lines.push(`last handoff (${brief.lastHandoff.at}): ${brief.lastHandoff.next}${brief.lastHandoff.note ? ` (note: ${brief.lastHandoff.note})` : ""}`);
76092
+ if (brief.recent.length)
76093
+ lines.push("recent ledger:", ...brief.recent.map((row) => ` #${row.seq} ${row.type}${row.branch ? ` [${row.branch}]` : ""}${row.labels.length ? ` (${row.labels.join(", ")})` : ""} ${row.summary}`));
76094
+ if (brief.note)
76095
+ lines.push(`note: ${brief.note}`);
76096
+ lines.push(`next: ${brief.next}`);
76097
+ if (brief.next !== brief.nextCommand)
76098
+ lines.push(`next command: ${brief.nextCommand}`);
76099
+ if (brief.recorded)
76100
+ lines.push(`recorded handoff ${brief.recorded}`);
76101
+ return lines;
76102
+ }
76103
+ async function resumeBrief(argv, io) {
76104
+ let values;
76105
+ try {
76106
+ ({ values } = parseArgs2({
76107
+ args: argv,
76108
+ options: {
76109
+ note: { type: "string" },
76110
+ next: { type: "string" },
76111
+ record: { type: "boolean" },
76112
+ last: { type: "string" },
76113
+ json: { type: "boolean" }
76114
+ },
76115
+ allowPositionals: false,
76116
+ strict: true
76117
+ }));
76118
+ } catch (error) {
76119
+ return emit(io, fail("invalid_input", error.message, { unblock: USAGE2 }));
76120
+ }
76121
+ const last = values.last === undefined ? 10 : Number(values.last);
76122
+ if (!Number.isInteger(last) || last < 1)
76123
+ return emit(io, fail("invalid_input", "--last must be a positive integer", { unblock: USAGE2 }));
76124
+ const built = buildHandoff(io.cwd, {
76125
+ last,
76126
+ note: values.note ?? null,
76127
+ next: values.next ?? null
76128
+ });
76129
+ if (!built.ok)
76130
+ return emit(io, fail(built.code, built.error, built.unblock ? { unblock: built.unblock } : {}));
76131
+ const data = built.value;
76132
+ let recorded = null;
76133
+ if (values.record) {
76134
+ const row = recordHandoff({ cwd: io.cwd, actor: actorFromEnv(io.env), branch: data.branch }, { note: data.note, next: data.next });
76135
+ if (!row.ok)
76136
+ return emit(io, fail(row.code, row.error, { data: { ...data, recorded } }));
76137
+ recorded = row.value.id;
76138
+ }
76139
+ return emit(io, ok({ ...data, recorded }), human);
76140
+ }
73090
76141
  async function run19(argv, io) {
73091
76142
  if (!argv.includes("--task"))
73092
- return brief(argv, io);
76143
+ return resumeBrief(argv, io);
73093
76144
  await Promise.resolve().then(() => init_task());
73094
76145
  return runTaskCommand(["handoff", ...argv]);
73095
76146
  }
73096
- var brief;
76147
+ var USAGE2 = "workit handoff [--note <text>] [--next <text>] [--record] [--last <n>] [--json]", short2 = (sha) => sha ? sha.slice(0, 12) : "no commit";
73097
76148
  var init_handoff = __esm(() => {
73098
- init_stub();
73099
- brief = notImplemented("handoff", "S13");
76149
+ init_ledger();
76150
+ init_output();
73100
76151
  });
73101
76152
 
73102
76153
  // packages/workit-cli/src/main.ts
73103
- import path34 from "node:path";
76154
+ import path38 from "node:path";
73104
76155
  // packages/workit-cli/package.json
73105
76156
  var package_default = {
73106
76157
  name: "@brainervirus/workit-cli",
73107
- version: "2.2.1",
76158
+ version: "2.3.0",
73108
76159
  private: false,
73109
76160
  description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
73110
76161
  keywords: [
@@ -73243,17 +76294,15 @@ var VERBS = [
73243
76294
  {
73244
76295
  name: "pr",
73245
76296
  group: "delivery",
73246
- usage: "workit pr status|create|merge [--pr <n>] [--json]",
73247
- summary: "Read PR/MR state (checks, threads, behind-base, next) or create/merge it",
73248
- planned: "S10/S11",
76297
+ usage: "workit pr status [--pr <n> | --branch <b>] [--log-lines 60] [--json]",
76298
+ summary: "Read PR/MR state: checks with failing log tails, unresolved threads, behind-base, next action",
73249
76299
  load: () => Promise.resolve().then(() => (init_pr(), exports_pr))
73250
76300
  },
73251
76301
  {
73252
76302
  name: "ci",
73253
76303
  group: "delivery",
73254
- usage: "workit ci wait|rerun [--pr <n>] [--json]",
73255
- summary: "Wait for CI on the current head or rerun failed jobs once",
73256
- planned: "S10",
76304
+ usage: "workit ci wait [--pr <n>] [--head <sha>] [--timeout 20m] | ci rerun [--failed | --check <name>…] --reason flake|infra [--force]",
76305
+ summary: "Wait for CI on the PR head (exit 0/1/3/4), or rerun failed jobs once per head",
73257
76306
  load: () => Promise.resolve().then(() => (init_ci(), exports_ci))
73258
76307
  },
73259
76308
  {
@@ -73284,15 +76333,14 @@ var VERBS = [
73284
76333
  name: "ledger",
73285
76334
  group: "delivery",
73286
76335
  usage: "workit ledger decision|ruling|verdict|list|check ...",
73287
- summary: "Record and query decisions, rulings and verdicts",
73288
- planned: "S13",
73289
- load: () => Promise.resolve().then(() => (init_ledger(), exports_ledger))
76336
+ summary: "Record and query decisions, rulings and SHA-keyed verdicts (patch-id carry-over across rebases)",
76337
+ load: () => Promise.resolve().then(() => (init_ledger2(), exports_ledger))
73290
76338
  },
73291
76339
  {
73292
76340
  name: "handoff",
73293
76341
  group: "delivery",
73294
- usage: "workit handoff --task <id>",
73295
- summary: "Export task state and compact destination context",
76342
+ usage: "workit handoff [--note <t>] [--next <t>] [--record] | workit handoff --task <id>",
76343
+ summary: "Print a resume brief (branch, HEAD, dirty state, verdict, rulings, next command); --task exports v1 task state",
73296
76344
  load: () => Promise.resolve().then(() => (init_handoff(), exports_handoff))
73297
76345
  }
73298
76346
  ];
@@ -73378,7 +76426,7 @@ var defaultIo = (json) => ({
73378
76426
  });
73379
76427
  async function main(argv, overrides = {}, options = {}) {
73380
76428
  const parsed = parseGlobals(argv);
73381
- const cwd = path34.resolve(overrides.cwd ?? process.cwd(), parsed.cwd ?? ".");
76429
+ const cwd = path38.resolve(overrides.cwd ?? process.cwd(), parsed.cwd ?? ".");
73382
76430
  const io = { ...defaultIo(parsed.json), ...overrides, json: parsed.json, cwd };
73383
76431
  if (parsed.error)
73384
76432
  return emit(io, fail("invalid_input", parsed.error));