@brainervirus/workit-claude-code 8.1.0 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/workit.js CHANGED
@@ -65,7 +65,7 @@ var package_default;
65
65
  var init_package = __esm(() => {
66
66
  package_default = {
67
67
  name: "@brainervirus/workit-cli",
68
- version: "8.1.0",
68
+ version: "8.2.0",
69
69
  private: false,
70
70
  description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
71
71
  keywords: [
@@ -103,9 +103,9 @@ var init_package = __esm(() => {
103
103
  },
104
104
  dependencies: {
105
105
  "@babel/parser": "7.29.9",
106
- "@brainervirus/workit-core": "^8.1.0",
107
- "@brainervirus/workit-cursor": "^8.1.0",
108
- "@brainervirus/workit-opencode": "^8.1.0",
106
+ "@brainervirus/workit-core": "^8.2.0",
107
+ "@brainervirus/workit-cursor": "^8.2.0",
108
+ "@brainervirus/workit-opencode": "^8.2.0",
109
109
  "@inkjs/ui": "2.0.0",
110
110
  ink: "7.1.1",
111
111
  react: "19.2.8"
@@ -12831,12 +12831,13 @@ function pushForge(cwd, options = {}) {
12831
12831
  };
12832
12832
  const forge = deriveForge(url, options);
12833
12833
  if (!forge) {
12834
- const host = parseRemoteUrl(url)?.host || null;
12834
+ const parsed = parseRemoteUrl(url);
12835
+ const host = parsed?.host || null;
12835
12836
  return {
12836
12837
  ok: false,
12837
12838
  code: "unavailable",
12838
- 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`,
12839
- 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>`
12839
+ error: host ? parsed?.protocol === "ssh" && !host.includes(".") ? `unsupported_forge: could not resolve ssh alias "${host}" to GitHub or GitLab; PR and CI verbs need one (workit git push still pushes with git)` : parsed?.protocol === "ssh" ? `unsupported_forge: ssh host "${host}" is not a GitHub/GitLab host or an alias workit can resolve; PR and CI verbs need one (workit git push still pushes with git)` : `unsupported_forge: cannot tell whether push host "${host}" is GitHub or GitLab; PR and CI verbs need one (workit git push still pushes with git)` : `push URL ${redactRemote(url)} of remote "${remote}" is not a GitHub/GitLab URL`,
12840
+ unblock: host ? `if ${host} is an ssh alias of GitHub/GitLab, add "Host ${host}" with "HostName github.com" (or gitlab.com) to ~/.ssh/config; if it is a self-hosted GitHub/GitLab, set github.host or gitlab.host in ~/.config/workit/vcs.json; other forges (Bitbucket, Gitea…) have no PR/CI verbs` : `git remote set-url --push ${remote} <ssh-or-https-url>`
12840
12841
  };
12841
12842
  }
12842
12843
  return { ok: true, remote, url: redactRemote(url), forge };
@@ -12847,8 +12848,8 @@ function forgeConflict(derived, configuredProvider) {
12847
12848
  return null;
12848
12849
  return {
12849
12850
  code: "blocked",
12850
- error: `forge_mismatch: push remote ${derived.host} is ${derived.kind} but the workspace vcs.provider is ${configured}`,
12851
- unblock: `set vcs.provider to "${derived.kind}" for this repo in ~/.config/workit/workspaces.json (workit-github-override), or push to a ${configured} remote`
12851
+ error: `forge_mismatch: push remote ${derived.host} is ${derived.kind} but the workspace vcs.provider is ${configured}, so its account and grants do not apply; PR and CI verbs stop here (workit git push still pushes with git)`,
12852
+ unblock: `add a workspace whose glob matches only this repo, with "vcs": {"provider": "${derived.kind}"} (and its account), to ~/.config/workit/workspaces.json; the most specific glob wins`
12852
12853
  };
12853
12854
  }
12854
12855
  var MAX_BUFFER, GIT_TIMEOUTS, git2 = (cwd, args, options = {}) => {
@@ -15235,6 +15236,7 @@ var init_commit_flavors = __esm(() => {
15235
15236
  var init_git = () => {};
15236
15237
 
15237
15238
  // packages/workit-core/src/core/workspaces.ts
15239
+ import { spawnSync as spawnSync8 } from "node:child_process";
15238
15240
  import { createHash as createHash8 } from "node:crypto";
15239
15241
  import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
15240
15242
  import path14 from "node:path";
@@ -15362,6 +15364,46 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
15362
15364
  };
15363
15365
  }
15364
15366
  return { ok: true };
15367
+ }, WorkspaceConfigError, mainCheckouts, mainCheckoutOf = (cwd) => {
15368
+ const cached = mainCheckouts.get(cwd);
15369
+ if (cached !== undefined)
15370
+ return cached;
15371
+ const env = { ...process.env };
15372
+ for (const name of ["GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"])
15373
+ delete env[name];
15374
+ const git = (args) => {
15375
+ const run = spawnSync8("git", args, {
15376
+ cwd,
15377
+ env,
15378
+ encoding: "utf8",
15379
+ timeout: 5000,
15380
+ windowsHide: true
15381
+ });
15382
+ return run.status === 0 ? run.stdout : null;
15383
+ };
15384
+ let main = null;
15385
+ const dirs = git(["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]);
15386
+ const [gitDir, common] = (dirs ?? "").trim().split(/\r?\n/u);
15387
+ if (gitDir && common && path14.resolve(gitDir) !== path14.resolve(common)) {
15388
+ const listed = /^worktree (.+)$/mu.exec(git(["worktree", "list", "--porcelain"]) ?? "")?.[1];
15389
+ main = listed?.trim() || (path14.basename(common) === ".git" ? path14.dirname(common) : common);
15390
+ }
15391
+ mainCheckouts.set(cwd, main);
15392
+ return main;
15393
+ }, matchingEntries = (cwd, entries) => {
15394
+ const match = (where) => {
15395
+ const targets = [where, realpathOf(where)].map((value) => value.replaceAll("\\", "/"));
15396
+ return entries.filter((entry) => {
15397
+ const glob = entry.glob.replaceAll("\\", "/");
15398
+ const canonical = canonicalGlob(glob);
15399
+ return targets.some((target) => matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target));
15400
+ });
15401
+ };
15402
+ const direct = match(cwd);
15403
+ if (direct.length)
15404
+ return direct;
15405
+ const main = mainCheckoutOf(cwd);
15406
+ return main ? match(main) : direct;
15365
15407
  }, realpathOf = (p) => {
15366
15408
  const real = realpathSync3.native ?? realpathSync3;
15367
15409
  try {
@@ -15383,41 +15425,27 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
15383
15425
  if (named.length === 1)
15384
15426
  return named[0];
15385
15427
  if (named.length > 1)
15386
- throw new Error(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
15428
+ throw new WorkspaceConfigError(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
15387
15429
  if (matches.length > 0) {
15388
- throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`);
15430
+ throw new WorkspaceConfigError(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`);
15389
15431
  }
15390
- throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
15432
+ throw new WorkspaceConfigError(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
15391
15433
  }
15392
15434
  if (matches.length < 2)
15393
15435
  return matches[0];
15394
15436
  const highestSpecificity = Math.max(...matches.map((entry) => globSpecificity(entry.glob)));
15395
15437
  const mostSpecific = matches.filter((entry) => globSpecificity(entry.glob) === highestSpecificity);
15396
15438
  if (mostSpecific.length > 1) {
15397
- throw new Error(`ambiguous workspace for ${cwd}; choose one of: ${mostSpecific.map((entry) => entry.name).join(", ")}`);
15439
+ throw new WorkspaceConfigError(`ambiguous workspace for ${cwd}; choose one of: ${mostSpecific.map((entry) => entry.name).join(", ")}`);
15398
15440
  }
15399
15441
  return mostSpecific[0];
15400
15442
  }, resolveWorkspaceFromEntries = (cwd, entries, workspaceName) => {
15401
- const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
15402
- const matches = [];
15403
- for (const ws of entries) {
15404
- const glob = ws.glob.replaceAll("\\", "/");
15405
- const canonical = canonicalGlob(glob);
15406
- let matched = false;
15407
- for (const target of targets) {
15408
- if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
15409
- matched = true;
15410
- break;
15411
- }
15412
- }
15413
- if (matched)
15414
- matches.push(ws);
15415
- }
15443
+ const matches = matchingEntries(cwd, entries);
15416
15444
  return selectWorkspaceMatch(matches, cwd, workspaceName) ?? null;
15417
15445
  }, resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
15418
15446
  const result = readWorkspacesResult(dir);
15419
15447
  if (result.status === "malformed" || result.status === "invalid")
15420
- throw new Error(result.error);
15448
+ throw new WorkspaceConfigError(result.error);
15421
15449
  return resolveWorkspaceFromEntries(cwd, result.entries, workspaceName);
15422
15450
  }, runtimeWorkspacePolicySchema = (kind) => {
15423
15451
  const key = kind === "branch" ? "branchPolicy" : kind === "commit" ? "commitPolicy" : "vcs";
@@ -15443,31 +15471,26 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
15443
15471
  } catch (error) {
15444
15472
  if (error.code === "ENOENT")
15445
15473
  return null;
15446
- throw new Error(`${file} could not be read: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
15474
+ throw new WorkspaceConfigError(`${file} could not be read: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
15447
15475
  }
15448
15476
  let parsed;
15449
15477
  try {
15450
15478
  parsed = JSON.parse(raw);
15451
15479
  } catch {
15452
- throw new Error(`${file} is not valid JSON`);
15480
+ throw new WorkspaceConfigError(`${file} is not valid JSON`);
15453
15481
  }
15454
15482
  const validated = runtimeWorkspaceIndexSchema.safeParse(parsed);
15455
15483
  if (!validated.success) {
15456
15484
  const detail = validated.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}`).join("; ");
15457
- throw new Error(`${file} has invalid workspace matching data: ${detail}`);
15485
+ throw new WorkspaceConfigError(`${file} has invalid workspace matching data: ${detail}`);
15458
15486
  }
15459
15487
  const entries = validated.data.workspaces;
15460
15488
  for (const entry of entries ?? []) {
15461
15489
  const glob = validateWorkspaceGlob(entry.glob);
15462
15490
  if (!glob.ok)
15463
- throw new Error(`${file} workspace ${entry.name}: ${glob.error}`);
15491
+ throw new WorkspaceConfigError(`${file} workspace ${entry.name}: ${glob.error}`);
15464
15492
  }
15465
- const targets = [cwd, realpathOf(cwd)].map((value) => value.replaceAll("\\", "/"));
15466
- const matches = (entries ?? []).filter((entry) => {
15467
- const glob = entry.glob.replaceAll("\\", "/");
15468
- const canonical = canonicalGlob(glob);
15469
- return targets.some((target) => matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target));
15470
- });
15493
+ const matches = matchingEntries(cwd, entries ?? []);
15471
15494
  const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
15472
15495
  const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
15473
15496
  return selected ? { file, selected } : null;
@@ -15482,7 +15505,7 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
15482
15505
  const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
15483
15506
  if (!policy.success) {
15484
15507
  const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
15485
- throw new Error(`${file} has invalid ${kind} policy configuration: ${detail}`);
15508
+ throw new WorkspaceConfigError(`${file} has invalid ${kind} policy configuration: ${detail}`);
15486
15509
  }
15487
15510
  return policy.data;
15488
15511
  }, resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
@@ -15492,10 +15515,10 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
15492
15515
  const key = kind === "branch" ? "branchPolicy" : "commitPolicy";
15493
15516
  const profileName = requestedProfile?.trim() || candidate.defaultProfile;
15494
15517
  if (candidate.defaultProfile && !Object.hasOwn(candidate.profiles ?? {}, candidate.defaultProfile))
15495
- throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(candidate.defaultProfile)}`);
15518
+ throw new WorkspaceConfigError(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(candidate.defaultProfile)}`);
15496
15519
  const profile = profileName ? candidate.profiles?.[profileName] : undefined;
15497
15520
  if (profileName && !profile)
15498
- throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(profileName)}`);
15521
+ throw new WorkspaceConfigError(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(profileName)}`);
15499
15522
  const profilePolicy = profile?.[key];
15500
15523
  const workspacePolicy = candidate[key];
15501
15524
  return {
@@ -15666,6 +15689,10 @@ var init_workspaces = __esm(() => {
15666
15689
  });
15667
15690
  UNSUPPORTED_GLOB = /[?[\]{}]/;
15668
15691
  UNSUPPORTED_EXTGLOB = /[@+*]\(|!/;
15692
+ WorkspaceConfigError = class WorkspaceConfigError extends Error {
15693
+ name = "WorkspaceConfigError";
15694
+ };
15695
+ mainCheckouts = new Map;
15669
15696
  runtimeWorkspaceIndexSchema = object({
15670
15697
  workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
15671
15698
  }).passthrough();
@@ -15673,7 +15700,7 @@ var init_workspaces = __esm(() => {
15673
15700
 
15674
15701
  // packages/workit-core/src/core/release-tracks.ts
15675
15702
  import { createHash as createHash9 } from "node:crypto";
15676
- import { spawnSync as spawnSync8 } from "node:child_process";
15703
+ import { spawnSync as spawnSync9 } from "node:child_process";
15677
15704
  import { readFileSync as readFileSync5 } from "node:fs";
15678
15705
  function readReleaseTracks(raw) {
15679
15706
  const out = { tracks: [], issues: [], error: null, protectedBranches: [] };
@@ -16075,7 +16102,7 @@ var ReleaseTrackError, KNOWN_FIELDS, BRANCH_FIELDS, PLACEHOLDER, HAS_PLACEHOLDER
16075
16102
  const remote = remotes.find((name) => bare.startsWith(`${name}/`));
16076
16103
  return remote ? bare.slice(remote.length + 1) : bare;
16077
16104
  }, MAX_RECORDED_HOPS = 10, git4 = (cwd, args) => {
16078
- const run = spawnSync8("git", args, {
16105
+ const run = spawnSync9("git", args, {
16079
16106
  cwd,
16080
16107
  encoding: "utf8",
16081
16108
  timeout: 20000,
@@ -16136,7 +16163,7 @@ var init_release_tracks = __esm(() => {
16136
16163
  // packages/workit-core/src/core/vcs-config.ts
16137
16164
  import fs12 from "node:fs";
16138
16165
  import path15 from "node:path";
16139
- import { spawnSync as spawnSync9 } from "node:child_process";
16166
+ import { spawnSync as spawnSync10 } from "node:child_process";
16140
16167
  function vcsConfig(mode, cwd, options = {}) {
16141
16168
  const ws = resolveRuntimeWorkspaceVcs(vcsCwd(cwd));
16142
16169
  const wsVcs = ws?.vcs ?? {};
@@ -16219,7 +16246,7 @@ function vcsConfig(mode, cwd, options = {}) {
16219
16246
  return out;
16220
16247
  }
16221
16248
  var workspacesPath = () => path15.join(configDir(), "workspaces.json"), vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd(), remoteProvider = (cwd) => {
16222
- const r = spawnSync9("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
16249
+ const r = spawnSync10("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
16223
16250
  if (r.status !== 0)
16224
16251
  return null;
16225
16252
  const url = (r.stdout ?? "").trim();
@@ -18535,7 +18562,7 @@ var init_pr_body = __esm(() => {
18535
18562
 
18536
18563
  // packages/workit-core/src/core/pr-create.ts
18537
18564
  import path19 from "node:path";
18538
- import { spawnSync as spawnSync10 } from "node:child_process";
18565
+ import { spawnSync as spawnSync11 } from "node:child_process";
18539
18566
  import { pathToFileURL } from "node:url";
18540
18567
  var safePushUrl = (remote) => {
18541
18568
  const raw = remote.trim();
@@ -18563,7 +18590,7 @@ var safePushUrl = (remote) => {
18563
18590
  const target = safePushUrl(remote);
18564
18591
  if (!target)
18565
18592
  return false;
18566
- const result = spawnSync10("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
18593
+ const result = spawnSync11("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
18567
18594
  if (result.status !== 0 && result.status !== 1)
18568
18595
  return false;
18569
18596
  const prefixes = (result.stdout ?? "").split(/\r?\n/u).map((line) => line.trim().slice(line.trim().indexOf(" ") + 1)).filter(Boolean);
@@ -62851,7 +62878,7 @@ var init_support_matrix = __esm(() => {
62851
62878
  });
62852
62879
 
62853
62880
  // packages/workit-cli/src/admin/host-install.ts
62854
- import { spawnSync as spawnSync11 } from "node:child_process";
62881
+ import { spawnSync as spawnSync12 } from "node:child_process";
62855
62882
  import {
62856
62883
  accessSync,
62857
62884
  constants as constants2,
@@ -63122,7 +63149,7 @@ function runHostCommand(step, context = {}) {
63122
63149
  stderr: `Cannot safely execute batch shim ${step.command}; resolve its Node.js CLI entrypoint and retry`
63123
63150
  };
63124
63151
  }
63125
- const result = spawnSync11(step.command, step.args, {
63152
+ const result = spawnSync12(step.command, step.args, {
63126
63153
  cwd: step.cwd,
63127
63154
  env: (() => {
63128
63155
  const home = context.home ?? context.env?.HOME ?? os6.homedir();
@@ -63327,7 +63354,7 @@ var init_skill_manifests = __esm(() => {
63327
63354
  });
63328
63355
 
63329
63356
  // packages/workit-cli/src/admin/doctor.ts
63330
- import { spawnSync as spawnSync12 } from "node:child_process";
63357
+ import { spawnSync as spawnSync13 } from "node:child_process";
63331
63358
  import {
63332
63359
  existsSync as existsSync13,
63333
63360
  lstatSync as lstatSync4,
@@ -63393,9 +63420,8 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63393
63420
  return [
63394
63421
  ...new Set(entries.filter((entry) => typeof entry === "string" && isWorkitPlugin(entry)))
63395
63422
  ];
63396
- }, commandOnPath = (name, env) => {
63423
+ }, commandOnPath = (name, env) => findOnPath(process.platform === "win32" ? [name, `${name}.exe`] : [name], env) !== null, pathextNames = (name, env) => process.platform === "win32" ? (env.PATHEXT ?? process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean).map((ext) => `${name}${ext.toLowerCase()}`) : [name], findOnPath = (names, env) => {
63397
63424
  const dirs = (env.PATH ?? process.env.PATH ?? "").split(path29.delimiter);
63398
- const names = process.platform === "win32" ? [name, `${name}.exe`] : [name];
63399
63425
  for (const dir of dirs) {
63400
63426
  if (!dir)
63401
63427
  continue;
@@ -63405,13 +63431,13 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63405
63431
  const st = statSync4(candidate);
63406
63432
  if (process.platform !== "win32" && (st.mode & 73) === 0)
63407
63433
  continue;
63408
- return true;
63434
+ return candidate;
63409
63435
  } catch {}
63410
63436
  }
63411
63437
  }
63412
- return false;
63438
+ return null;
63413
63439
  }, versionOf = (bin, env, timeout) => {
63414
- const r = spawnSync12(bin, ["--version"], { encoding: "utf8", env, timeout });
63440
+ const r = spawnSync13(bin, ["--version"], { encoding: "utf8", env, timeout });
63415
63441
  if (r.error)
63416
63442
  return null;
63417
63443
  return (r.stdout ?? "").trim();
@@ -63660,7 +63686,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63660
63686
  `)) {
63661
63687
  return false;
63662
63688
  }
63663
- return spawnSync12(runtime, ["--check", entry], { encoding: "utf8", env }).status === 0;
63689
+ return spawnSync13(runtime, ["--check", entry], { encoding: "utf8", env }).status === 0;
63664
63690
  } catch {
63665
63691
  return false;
63666
63692
  }
@@ -63874,7 +63900,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
63874
63900
  if (!cmd)
63875
63901
  return null;
63876
63902
  try {
63877
- const r = spawnSync12(cmd, ["view", pkgName, "version"], {
63903
+ const r = spawnSync13(cmd, ["view", pkgName, "version"], {
63878
63904
  encoding: "utf8",
63879
63905
  timeout: 20000,
63880
63906
  env: res.env
@@ -64313,7 +64339,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64313
64339
  }, defaultIdentityProbes = (env) => ({
64314
64340
  origin: (cwd) => {
64315
64341
  try {
64316
- const out = spawnSync12("git", ["remote", "get-url", "origin"], {
64342
+ const out = spawnSync13("git", ["remote", "get-url", "origin"], {
64317
64343
  cwd,
64318
64344
  encoding: "utf8",
64319
64345
  env
@@ -64325,7 +64351,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64325
64351
  },
64326
64352
  ghLogin: (e) => {
64327
64353
  try {
64328
- const out = spawnSync12("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", env: e });
64354
+ const out = spawnSync13("gh", ["api", "user", "--jq", ".login"], { encoding: "utf8", env: e });
64329
64355
  const login = (out.stdout ?? "").trim();
64330
64356
  return out.status === 0 && login ? login : null;
64331
64357
  } catch {
@@ -64334,7 +64360,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64334
64360
  },
64335
64361
  glabLogin: (e) => {
64336
64362
  try {
64337
- const out = spawnSync12("glab", ["api", "user", "--jq", ".username"], {
64363
+ const out = spawnSync13("glab", ["api", "user", "--jq", ".username"], {
64338
64364
  encoding: "utf8",
64339
64365
  env: e
64340
64366
  });
@@ -64346,7 +64372,7 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64346
64372
  },
64347
64373
  sshLogin: (host) => {
64348
64374
  try {
64349
- const out = spawnSync12("ssh", ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", "-T", `git@${host}`], {
64375
+ const out = spawnSync13("ssh", ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", "-T", `git@${host}`], {
64350
64376
  encoding: "utf8",
64351
64377
  env
64352
64378
  });
@@ -64503,6 +64529,67 @@ var TOKEN_PLACEHOLDER2 = "YOUR_TOKEN_HERE", findDevFromCwd = (cwd) => {
64503
64529
  fix: repair,
64504
64530
  registryProbed
64505
64531
  };
64532
+ }, CLI_PACKAGE = "@brainervirus/workit-cli", SEMVER, WORKIT_PROBE_TIMEOUT_MS = 1e4, workitVersions, workitVersionAt = (bin, env) => {
64533
+ let key;
64534
+ try {
64535
+ const st = statSync4(bin);
64536
+ key = `${bin}:${st.ino}:${st.size}:${st.mtimeMs}`;
64537
+ } catch {
64538
+ return { version: null, timedOut: false };
64539
+ }
64540
+ const cached = workitVersions.get(key);
64541
+ if (cached)
64542
+ return cached;
64543
+ const viaShell = process.platform === "win32" && /\.(?:cmd|bat)$/i.test(bin);
64544
+ const r = viaShell ? spawnSync13(`"${bin}" --version`, {
64545
+ encoding: "utf8",
64546
+ env,
64547
+ timeout: WORKIT_PROBE_TIMEOUT_MS,
64548
+ shell: true
64549
+ }) : spawnSync13(bin, ["--version"], { encoding: "utf8", env, timeout: WORKIT_PROBE_TIMEOUT_MS });
64550
+ const timedOut = r.error !== undefined && "code" in r.error && r.error.code === "ETIMEDOUT";
64551
+ const version = r.status === 0 ? (r.stdout ?? "").match(SEMVER)?.[0] ?? null : null;
64552
+ const probe = { version: timedOut ? null : version, timedOut };
64553
+ workitVersions.set(key, probe);
64554
+ return probe;
64555
+ }, checkWorkitOnPath = (res) => {
64556
+ const plugins = [
64557
+ ...claudeWorkitInstalls(res.home, res.env, res.cwd).map((i) => ({
64558
+ label: `Claude Code plugin ${i.id}`,
64559
+ version: i.version
64560
+ })),
64561
+ { label: "Cursor plugin", version: installedPluginVersion(res) },
64562
+ { label: "OpenCode plugin", version: installedOpenCodeCacheVersion(res) }
64563
+ ].filter((p) => p.version !== null);
64564
+ const newest = plugins.reduce((best, p) => best && semverAtLeast(best.version, p.version) ? best : p, null);
64565
+ const target = newest?.version ?? "latest";
64566
+ const fix = `npm i -g ${CLI_PACKAGE}@${target} (or run it without a global install: npx -y ${CLI_PACKAGE}@${target} <command>)`;
64567
+ const bin = findOnPath(pathextNames("workit", res.env), res.env);
64568
+ if (!bin) {
64569
+ return { id: "workit_on_path", status: "warn", detail: "no workit on PATH", fix };
64570
+ }
64571
+ const { version, timedOut } = workitVersionAt(bin, res.env);
64572
+ if (!version) {
64573
+ return {
64574
+ id: "workit_on_path",
64575
+ status: "warn",
64576
+ detail: timedOut ? `${bin} does not run (\`workit --version\` timed out after ${WORKIT_PROBE_TIMEOUT_MS / 1000}s)` : `${bin} does not run (\`workit --version\` failed)`,
64577
+ fix
64578
+ };
64579
+ }
64580
+ if (newest && versionBehind(version, newest.version)) {
64581
+ return {
64582
+ id: "workit_on_path",
64583
+ status: "warn",
64584
+ detail: `workit ${version} at ${bin} is older than the ${newest.label} ${newest.version}`,
64585
+ fix
64586
+ };
64587
+ }
64588
+ return {
64589
+ id: "workit_on_path",
64590
+ status: "pass",
64591
+ detail: `workit ${version} at ${bin}${newest ? ` (newest host plugin ${newest.version})` : ""}`
64592
+ };
64506
64593
  }, RUN_CHECKS, INSTALLER_REQUIRED, runDoctor = (options = {}) => {
64507
64594
  const res = resolve(options);
64508
64595
  const raw = RUN_CHECKS.map((fn) => fn(res));
@@ -64550,12 +64637,15 @@ var init_doctor = __esm(() => {
64550
64637
  init_skill_manifests();
64551
64638
  OPENCODE_V1_FIX = `OpenCode < ${SUPPORT_MATRIX.opencode.minimum} cannot load Workit 3 (V2 plugin API only): upgrade OpenCode to ${SUPPORT_MATRIX.opencode.minimum}+, or stay on Workit 2.x by pinning "@brainervirus/workit-opencode@2" in the opencode.json "plugin" array`;
64552
64639
  canonicalCursorHook = cursorHooksEntry("").command;
64640
+ SEMVER = /\d+\.\d+\.\d+/;
64641
+ workitVersions = new Map;
64553
64642
  RUN_CHECKS = [
64554
64643
  checkRuntime,
64555
64644
  checkVersions,
64556
64645
  checkCodexPin,
64557
64646
  checkOpencodeVersion,
64558
64647
  checkClaudePlugin,
64648
+ checkWorkitOnPath,
64559
64649
  checkAssets,
64560
64650
  checkLauncher,
64561
64651
  checkUtility,
@@ -69504,7 +69594,7 @@ var init_init = __esm(() => {
69504
69594
  });
69505
69595
 
69506
69596
  // packages/workit-cli/src/upgrade.ts
69507
- import { spawnSync as spawnSync13 } from "node:child_process";
69597
+ import { spawnSync as spawnSync14 } from "node:child_process";
69508
69598
  import {
69509
69599
  existsSync as existsSync17,
69510
69600
  mkdtempSync as mkdtempSync3,
@@ -69860,7 +69950,7 @@ async function runLaunchCommand(argv, deps = {}) {
69860
69950
  }
69861
69951
  if (deps.run)
69862
69952
  return deps.run(host, hostArgs).status ?? 1;
69863
- return spawnSync13(launch.command, launch.args, {
69953
+ return spawnSync14(launch.command, launch.args, {
69864
69954
  stdio: "inherit",
69865
69955
  env
69866
69956
  }).status ?? 1;
@@ -70042,7 +70132,7 @@ var init_launch = __esm(() => {
70042
70132
  });
70043
70133
 
70044
70134
  // packages/workit-core/src/knowledge.ts
70045
- import { spawnSync as spawnSync14 } from "node:child_process";
70135
+ import { spawnSync as spawnSync15 } from "node:child_process";
70046
70136
  import fs17 from "node:fs";
70047
70137
  import path36 from "node:path";
70048
70138
  function lintKnowledge(root) {
@@ -70163,7 +70253,7 @@ var AGENTS_BYTE_BUDGET, STEERING_FILES, NEED_BASED_FILES, STANDARDS_FILE = "CODI
70163
70253
  return "unreadable";
70164
70254
  }
70165
70255
  }, trackedTopLevel = (root) => {
70166
- const run = spawnSync14("git", ["ls-files", "-z", "--cached"], {
70256
+ const run = spawnSync15("git", ["ls-files", "-z", "--cached"], {
70167
70257
  cwd: root,
70168
70258
  encoding: "utf8",
70169
70259
  maxBuffer: 256 * 1024 * 1024,
@@ -70542,7 +70632,7 @@ var init_redact = __esm(() => {
70542
70632
  });
70543
70633
 
70544
70634
  // packages/workit-core/src/checks.ts
70545
- import { spawn, spawnSync as spawnSync15 } from "node:child_process";
70635
+ import { spawn, spawnSync as spawnSync16 } from "node:child_process";
70546
70636
  import { createHash as createHash14 } from "node:crypto";
70547
70637
  import fs18 from "node:fs";
70548
70638
  import os13 from "node:os";
@@ -70721,7 +70811,7 @@ function runCheckCommand(argv, options) {
70721
70811
  if (!child?.pid)
70722
70812
  return;
70723
70813
  if (windows)
70724
- spawnSync15("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
70814
+ spawnSync16("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
70725
70815
  stdio: "ignore",
70726
70816
  windowsHide: true
70727
70817
  });
@@ -71443,7 +71533,7 @@ var init_check = __esm(() => {
71443
71533
  var success2 = (data) => ({ ok: true, data }), failure3 = (code, error, unblock) => ({ ok: false, code, error, ...unblock ? { unblock } : {} });
71444
71534
 
71445
71535
  // packages/workit-core/src/git/ops.ts
71446
- import { spawnSync as spawnSync16 } from "node:child_process";
71536
+ import { spawnSync as spawnSync17 } from "node:child_process";
71447
71537
  import { existsSync as existsSync18, readFileSync as readFileSync18 } from "node:fs";
71448
71538
  import path39 from "node:path";
71449
71539
  function operationInProgress(cwd) {
@@ -71931,7 +72021,8 @@ ${gitOutput(run)}`);
71931
72021
  url: plan.url,
71932
72022
  previous: before.sha,
71933
72023
  pushed,
71934
- forced
72024
+ forced,
72025
+ ...input.identity ? { identity: input.identity } : {}
71935
72026
  });
71936
72027
  return success2({
71937
72028
  remote: plan.remote,
@@ -71975,7 +72066,7 @@ ${run.stdout}`;
71975
72066
  } : { ok: false, lease: false, error: gitError(run) };
71976
72067
  }
71977
72068
  var COMMIT_TIMEOUT_MS, PUSH_TIMEOUT_MS = 120000, git5 = (cwd, args, options = {}) => {
71978
- const result = spawnSync16("git", args, {
72069
+ const result = spawnSync17("git", args, {
71979
72070
  cwd,
71980
72071
  encoding: "utf8",
71981
72072
  env: { ...options.env ?? process.env, GIT_TERMINAL_PROMPT: "0" },
@@ -72355,16 +72446,16 @@ function waitVerdict(doc, options) {
72355
72446
  const checks = doc.checks.state;
72356
72447
  if (checks === "failing")
72357
72448
  return { state: "failed", reason: "checks_failing" };
72358
- if (checks === "pending")
72359
- return { state: "waiting", reason: "checks_pending" };
72360
- if (checks === "passing")
72361
- return { state: "ready", reason: "checks_passing" };
72362
- if (doc.conflicts)
72449
+ if (doc.conflicts && checks !== "passing")
72363
72450
  return {
72364
72451
  state: "blocked",
72365
72452
  reason: "conflicts",
72366
72453
  unblock: `rebase onto ${doc.base} and push; CI does not run on a conflicting PR`
72367
72454
  };
72455
+ if (checks === "pending")
72456
+ return { state: "waiting", reason: "checks_pending" };
72457
+ if (checks === "passing")
72458
+ return { state: "ready", reason: "checks_passing" };
72368
72459
  const ciRequired = doc.checks.missingRequired === null || doc.mergeState === "ci_must_pass" || doc.mergeState === "ci_still_running";
72369
72460
  if (ciRequired)
72370
72461
  return { state: "waiting", reason: "no_checks_yet" };
@@ -72472,7 +72563,7 @@ var init_report = __esm(() => {
72472
72563
  });
72473
72564
 
72474
72565
  // packages/workit-core/src/forge/pr-ops.ts
72475
- import { spawnSync as spawnSync17 } from "node:child_process";
72566
+ import { spawnSync as spawnSync18 } from "node:child_process";
72476
72567
  function fillFromCommits(cwd, resolved, base) {
72477
72568
  const tracking = `refs/remotes/${resolved.baseRemote}/${base}`;
72478
72569
  if (!base.startsWith("-"))
@@ -72480,7 +72571,7 @@ function fillFromCommits(cwd, resolved, base) {
72480
72571
  const from = resolveRef(cwd, tracking) ?? resolveRef(cwd, `refs/heads/${base}`);
72481
72572
  if (!from)
72482
72573
  return failure3("not_found", `base ${base} is not available locally`, `git fetch ${resolved.baseRemote} ${base}`);
72483
- const log = spawnSync17("git", ["log", "--reverse", "--no-merges", "--format=%s%x1f%b%x1e", `${from}..HEAD`], {
72574
+ const log = spawnSync18("git", ["log", "--reverse", "--no-merges", "--format=%s%x1f%b%x1e", `${from}..HEAD`], {
72484
72575
  cwd,
72485
72576
  encoding: "utf8",
72486
72577
  timeout: GIT_TIMEOUTS.local,
@@ -72785,7 +72876,7 @@ var init_pr_ops = __esm(() => {
72785
72876
  });
72786
72877
 
72787
72878
  // packages/workit-core/src/forge/exec.ts
72788
- import { spawnSync as spawnSync18 } from "node:child_process";
72879
+ import { spawnSync as spawnSync19 } from "node:child_process";
72789
72880
  import fs20 from "node:fs";
72790
72881
  import path41 from "node:path";
72791
72882
  function findExecutable(bin, env) {
@@ -72820,7 +72911,7 @@ function systemRunner(kind, apiHost, env = process.env) {
72820
72911
  if (!exe)
72821
72912
  return { status: null, stdout: "", stderr: "", timedOut: false, missing: true };
72822
72913
  const token = options.token ? kind === "github" ? { GH_TOKEN: options.token, GH_ENTERPRISE_TOKEN: options.token } : { GITLAB_TOKEN: options.token } : {};
72823
- const result = spawnSync18(exe, [...args], {
72914
+ const result = spawnSync19(exe, [...args], {
72824
72915
  encoding: "utf8",
72825
72916
  env: { ...childEnv, ...token },
72826
72917
  maxBuffer: MAX_BUFFER2,
@@ -72839,17 +72930,45 @@ function systemRunner(kind, apiHost, env = process.env) {
72839
72930
  };
72840
72931
  };
72841
72932
  }
72933
+ function forgeReason(run) {
72934
+ const parts = [];
72935
+ try {
72936
+ const body = JSON.parse(run.stdout);
72937
+ const add = (value) => {
72938
+ if (typeof value === "string" && value.trim())
72939
+ parts.push(value.trim());
72940
+ else if (Array.isArray(value))
72941
+ value.forEach(add);
72942
+ else if (value && typeof value === "object") {
72943
+ const record = value;
72944
+ if (typeof record.message === "string")
72945
+ add(record.message);
72946
+ else if (!("code" in record))
72947
+ Object.values(record).forEach(add);
72948
+ }
72949
+ };
72950
+ add(body.message);
72951
+ add(body.error);
72952
+ add(body.errors);
72953
+ } catch {}
72954
+ const stderr = (run.stderr.split(/\r?\n/u).find((value) => value.trim()) ?? "").trim();
72955
+ const unique = [...new Set(parts)];
72956
+ const text = unique.length ? `${unique.join("; ")}${stderr ? ` (${stderr.replace(/^gh: |^glab: /u, "")})` : ""}` : stderr || (run.stdout.split(/\r?\n/u).find((value) => value.trim()) ?? "");
72957
+ return redactText(text).slice(0, 300);
72958
+ }
72842
72959
  function cliFailure(bin, apiHost, run, what, timeoutMs) {
72843
72960
  if (run.missing)
72844
72961
  return failure3("unavailable", `${bin} is not installed`, installHint(bin));
72845
72962
  if (run.timedOut)
72846
- return failure3("unavailable", `${bin} api ${what} timed out after ${timeoutMs} ms`);
72963
+ return failure3("unavailable", run.timeoutMs === 0 ? `${bin} api ${what} not attempted: the command's time budget ran out` : `${bin} api ${what} timed out after ${run.timeoutMs ?? timeoutMs} ms`, `check the network to ${apiHost}, then retry`);
72847
72964
  const stderr = redactText(run.stderr).trim();
72848
72965
  if (/auth login|not logged in|no token|authentication required|bad credentials|\b401\b|unauthorized/iu.test(stderr))
72849
72966
  return failure3("unavailable", `${bin} is not authenticated for ${apiHost}`, loginHint(bin, apiHost));
72850
72967
  if (/\b404\b|not found/iu.test(stderr))
72851
72968
  return failure3("not_found", `${what} was not found`);
72852
72969
  const first = stderr.split(/\r?\n/u).find((value) => value.trim()) ?? `exit ${run.status}`;
72970
+ if (isNetworkError(stderr))
72971
+ return failure3("unavailable", `${bin} api ${what} failed (network): ${first.slice(0, 200)}`, `check the network to ${apiHost}, then retry`);
72853
72972
  return failure3("failed", `${bin} api ${what} failed: ${first.slice(0, 200)}`);
72854
72973
  }
72855
72974
  function apiJson(runner, bin, apiHost, args, what, timeoutMs = FORGE_TIMEOUTS.api, options = {}) {
@@ -72880,12 +72999,15 @@ function apiWrite(runner, bin, apiHost, args, what, timeoutMs = FORGE_TIMEOUTS.a
72880
72999
  if (run.status !== 0) {
72881
73000
  const text = redactText(`${run.stderr}
72882
73001
  ${run.stdout}`).trim();
72883
- const reason = (text.split(/\r?\n/u).find((value) => value.trim()) ?? "").slice(0, 200);
73002
+ const reason = forgeReason(run);
72884
73003
  if (!run.missing && !run.timedOut && /\b409\b|head branch was modified|sha does not match|sha.*mismatch/iu.test(text))
72885
73004
  return failure3("blocked", `head_moved: ${what} refused because the head changed (${reason})`, "workit pr status # re-check the new head, then verify it again");
72886
73005
  if (!run.missing && !run.timedOut && /\b(405|406|422)\b/u.test(text))
72887
73006
  return failure3("blocked", `${what} refused by the forge: ${reason}`);
72888
- return cliFailure(bin, apiHost, run, what, timeoutMs);
73007
+ const failed = cliFailure(bin, apiHost, run, what, timeoutMs);
73008
+ if (!failed.ok && failed.code === "unavailable" && !run.missing && !/ is not authenticated for /u.test(failed.error))
73009
+ return failure3("unavailable", failed.error, "workit pr status # the write may have gone through; check it first, then retry");
73010
+ return failed;
72889
73011
  }
72890
73012
  if (!run.stdout.trim())
72891
73013
  return { ok: true, data: {} };
@@ -72895,11 +73017,12 @@ ${run.stdout}`).trim();
72895
73017
  return failure3("failed", `${bin} api ${what} returned invalid JSON`);
72896
73018
  }
72897
73019
  }
72898
- 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}`;
73020
+ 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}`, NETWORK, isNetworkError = (text) => NETWORK.test(text);
72899
73021
  var init_exec = __esm(() => {
72900
73022
  init_redact();
72901
73023
  FORGE_TIMEOUTS = { api: 20000, log: 30000 };
72902
73024
  MAX_BUFFER2 = 64 * 1024 * 1024;
73025
+ NETWORK = /error connecting|could not resolve|no such host|connection (refused|reset|timed out)|network is unreachable|i\/o timeout|tls handshake|unexpected eof|\bHTTP 5\d\d\b|bad gateway|service unavailable|gateway time-?out/iu;
72903
73026
  });
72904
73027
 
72905
73028
  // packages/workit-core/src/forge/github.ts
@@ -73491,6 +73614,82 @@ var init_gitlab = __esm(() => {
73491
73614
 
73492
73615
  // packages/workit-core/src/forge/resolve.ts
73493
73616
  import fs21 from "node:fs";
73617
+ function memo4(scope, key, compute, now = Date.now) {
73618
+ let entries = memoScopes.get(scope.owner);
73619
+ if (!entries)
73620
+ memoScopes.set(scope.owner, entries = new Map);
73621
+ const full = `${scope.key} ${key}`;
73622
+ const hit = entries.get(full);
73623
+ if (hit && now() - hit.at < MEMO_TTL_MS)
73624
+ return hit.value;
73625
+ const { keep, value } = compute();
73626
+ if (keep)
73627
+ entries.set(full, { at: now(), value });
73628
+ return value;
73629
+ }
73630
+ function clampedRunner(base, limits, now, token) {
73631
+ return (bin, args, callOptions) => {
73632
+ let timeoutMs = callOptions.timeoutMs;
73633
+ if (limits.deadline !== null) {
73634
+ const remaining = limits.deadline - now();
73635
+ if (remaining <= 0)
73636
+ return {
73637
+ status: null,
73638
+ stdout: "",
73639
+ stderr: "",
73640
+ timedOut: true,
73641
+ missing: false,
73642
+ timeoutMs: 0
73643
+ };
73644
+ timeoutMs = Math.min(timeoutMs, remaining);
73645
+ }
73646
+ const run = base(bin, args, { ...callOptions, timeoutMs, ...token ? { token } : {} });
73647
+ return run.timedOut && run.timeoutMs === undefined ? { ...run, timeoutMs } : run;
73648
+ };
73649
+ }
73650
+ function resolveCredential(workspace, derived, run, scope) {
73651
+ const { kind, apiHost } = derived;
73652
+ const expectedAccount = workspace?.vcs?.account ?? null;
73653
+ const tokenFile = workspace?.vcs?.tokenFile;
73654
+ if (typeof tokenFile === "string" && tokenFile.trim()) {
73655
+ const workspaceHost = workspace?.vcs?.host;
73656
+ const explicit = typeof workspaceHost === "string" && workspaceHost.trim().toLowerCase().replace(/^https?:\/\//u, "").replace(/\/.*$/u, "") === apiHost;
73657
+ if (derived.via === "host_name" && !explicit)
73658
+ 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`);
73659
+ let token = "";
73660
+ try {
73661
+ token = fs21.readFileSync(tokenFile.trim(), "utf8").trim();
73662
+ } catch {
73663
+ return failure3("blocked", `workspace ${workspace?.name ?? "?"} vcs.tokenFile cannot be read`, "fix vcs.tokenFile in ~/.config/workit/workspaces.json");
73664
+ }
73665
+ if (!token)
73666
+ return failure3("blocked", `workspace ${workspace?.name ?? "?"} vcs.tokenFile is empty`, "fix vcs.tokenFile in ~/.config/workit/workspaces.json");
73667
+ return success2({ token, credential: "workspace_token_file" });
73668
+ }
73669
+ if (!expectedAccount || kind !== "github")
73670
+ return success2({ token: undefined, credential: "cli_login" });
73671
+ return memo4(scope, `token ${apiHost} ${expectedAccount}`, () => {
73672
+ const issued = run("gh", ["auth", "token", "--hostname", apiHost, "--user", expectedAccount], {
73673
+ timeoutMs: FORGE_TIMEOUTS.api
73674
+ });
73675
+ const value = authTokenResult(issued, apiHost, expectedAccount, workspace?.name ?? "?");
73676
+ return { keep: value.ok, value };
73677
+ });
73678
+ }
73679
+ function authTokenResult(issued, apiHost, account, workspace) {
73680
+ if (issued.missing)
73681
+ return failure3("unavailable", "gh is not installed", "install the GitHub CLI (https://cli.github.com) and run: gh auth login");
73682
+ if (issued.timedOut)
73683
+ return failure3("unavailable", `gh auth token ${issued.timeoutMs === 0 ? "not attempted: the time budget ran out" : `timed out after ${issued.timeoutMs ?? FORGE_TIMEOUTS.api} ms`} (looking up the ${account} login on ${apiHost})`, "gh auth status # check gh and the network, then retry");
73684
+ const token = issued.status === 0 ? issued.stdout.trim() : "";
73685
+ if (token)
73686
+ return success2({ token, credential: "gh_account_token" });
73687
+ const stderr = redactText(issued.stderr).trim();
73688
+ const first = stderr.split(/\r?\n/u).find((line) => line.trim()) ?? "";
73689
+ if (issued.status !== 0 && first && !/no oauth token|not logged in|no account/iu.test(stderr))
73690
+ return failure3("unavailable", `gh auth token failed${isNetworkError(stderr) ? " (network)" : ""}: ${first.slice(0, 200)}`, "gh auth status # check gh and the network, then retry");
73691
+ return failure3("blocked", `identity_unavailable: gh has no login for ${account} on ${apiHost} (workspace ${workspace})`, `gh auth login --hostname ${apiHost} # sign in as ${account}; workit passes that account's token per call and never switches the active account`);
73692
+ }
73494
73693
  function resolveForge(cwd, options = {}) {
73495
73694
  let workspace;
73496
73695
  try {
@@ -73508,48 +73707,16 @@ function resolveForge(cwd, options = {}) {
73508
73707
  const derived = pushed.forge;
73509
73708
  const { kind, apiHost } = derived;
73510
73709
  const base = options.runner ?? systemRunner(kind, apiHost, options.env ?? process.env);
73710
+ const scope = memoScope(options.runner, options.env ?? process.env);
73511
73711
  const now = options.now ?? Date.now;
73512
73712
  const expectedAccount = workspace?.vcs?.account ?? null;
73513
- let token;
73514
- let credential = "cli_login";
73515
- const tokenFile = workspace?.vcs?.tokenFile;
73516
- if (typeof tokenFile === "string" && tokenFile.trim()) {
73517
- const workspaceHost = workspace?.vcs?.host;
73518
- const explicit = typeof workspaceHost === "string" && workspaceHost.trim().toLowerCase().replace(/^https?:\/\//u, "").replace(/\/.*$/u, "") === apiHost;
73519
- if (derived.via === "host_name" && !explicit)
73520
- 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`);
73521
- try {
73522
- token = fs21.readFileSync(tokenFile.trim(), "utf8").trim();
73523
- } catch {
73524
- return failure3("blocked", `workspace ${workspace?.name ?? "?"} vcs.tokenFile cannot be read`, "fix vcs.tokenFile in ~/.config/workit/workspaces.json");
73525
- }
73526
- if (!token)
73527
- return failure3("blocked", `workspace ${workspace?.name ?? "?"} vcs.tokenFile is empty`, "fix vcs.tokenFile in ~/.config/workit/workspaces.json");
73528
- credential = "workspace_token_file";
73529
- } else if (expectedAccount && kind === "github") {
73530
- const issued = base("gh", ["auth", "token", "--hostname", apiHost, "--user", expectedAccount], {
73531
- timeoutMs: FORGE_TIMEOUTS.api
73532
- });
73533
- if (issued.missing)
73534
- return failure3("unavailable", "gh is not installed", "install the GitHub CLI (https://cli.github.com) and run: gh auth login");
73535
- token = issued.status === 0 ? issued.stdout.trim() : "";
73536
- if (!token)
73537
- 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`);
73538
- credential = "gh_account_token";
73539
- }
73540
- const limits = { deadline: null };
73541
- const runner = (bin, args, callOptions) => {
73542
- let timeoutMs = callOptions.timeoutMs;
73543
- if (limits.deadline !== null) {
73544
- const remaining = limits.deadline - now();
73545
- if (remaining <= 0)
73546
- return { status: null, stdout: "", stderr: "", timedOut: true, missing: false };
73547
- timeoutMs = Math.min(timeoutMs, remaining);
73548
- }
73549
- return base(bin, args, { ...callOptions, timeoutMs, ...token ? { token } : {} });
73550
- };
73713
+ const limits = { deadline: options.deadline ?? null };
73714
+ const credential = resolveCredential(workspace, derived, clampedRunner(base, limits, now), scope);
73715
+ if (!credential.ok)
73716
+ return credential;
73717
+ const runner = clampedRunner(base, limits, now, credential.data.token);
73551
73718
  const headRepo = derived.repo;
73552
- const probe = createForge(derived, headRepo, runner);
73719
+ const probe = memoizedIdentity(createForge(derived, headRepo, runner), scope, credential.data);
73553
73720
  const info = probe.repoInfo(headRepo);
73554
73721
  if (!info.ok && info.code !== "not_found")
73555
73722
  return info;
@@ -73564,13 +73731,13 @@ function resolveForge(cwd, options = {}) {
73564
73731
  const baseRepo = upstream && upstream !== headRepo ? upstream : info.ok && info.data.parent ? info.data.parent : headRepo;
73565
73732
  const baseRemote = baseRepo === headRepo ? pushed.remote : remotes.find((name) => repoOfRemote(name) === baseRepo) ?? pushed.remote;
73566
73733
  return success2({
73567
- forge: baseRepo === headRepo ? probe : createForge(derived, baseRepo, runner),
73734
+ forge: baseRepo === headRepo ? probe : memoizedIdentity(createForge(derived, baseRepo, runner), scope, credential.data),
73568
73735
  remote: pushed.remote,
73569
73736
  url: pushed.url,
73570
73737
  derived,
73571
73738
  workspace: workspace?.name ?? null,
73572
73739
  expectedAccount,
73573
- credential,
73740
+ credential: credential.data.credential,
73574
73741
  headRepo,
73575
73742
  headProjectId,
73576
73743
  fork: baseRepo !== headRepo,
@@ -73583,14 +73750,74 @@ function checkIdentity(resolved) {
73583
73750
  const identity = forge.identity(expectedAccount);
73584
73751
  if (!identity.ok)
73585
73752
  return identity;
73586
- if (identity.data.matches === false) {
73587
- const bin = forge.kind === "github" ? "gh" : "glab";
73588
- const where = `workspace ${resolved.workspace ?? "?"}`;
73589
- 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}`);
73590
- }
73753
+ if (identity.data.matches === false)
73754
+ return mismatch(resolved, identity.data.login ?? "?", resolved.workspace);
73591
73755
  return identity;
73592
73756
  }
73593
- var configuredHosts = () => {
73757
+ function checkPushIdentity(cwd, options = {}) {
73758
+ let workspace;
73759
+ try {
73760
+ workspace = resolveRuntimeWorkspaceVcs(cwd);
73761
+ } catch (error) {
73762
+ return failure3("blocked", error instanceof Error ? error.message : String(error), "fix ~/.config/workit/workspaces.json (workit init → Workspaces)");
73763
+ }
73764
+ const account = workspace?.vcs?.account ?? null;
73765
+ const skipped = (forge, reason, unblock) => success2({
73766
+ status: "skipped",
73767
+ forge,
73768
+ account,
73769
+ login: null,
73770
+ reason,
73771
+ ...unblock ? { unblock } : {}
73772
+ });
73773
+ const pushed = pushForge(cwd, {
73774
+ branch: options.branch,
73775
+ hosts: configuredHosts(),
73776
+ sshConfig: options.sshConfig
73777
+ });
73778
+ if (!pushed.ok) {
73779
+ const host = /^unsupported_forge: .*?"([^"]+)"/u.exec(pushed.error)?.[1];
73780
+ const what = /could not resolve ssh alias/u.test(pushed.error) ? `could not resolve ssh alias ${host} to GitHub or GitLab` : pushed.error.startsWith("unsupported_forge: ssh host") ? `ssh host ${host} is not a GitHub/GitLab host or an alias workit can resolve` : `${host} is not a GitHub or GitLab host workit knows`;
73781
+ return skipped(null, host ? `unsupported_forge: ${what}; account checks skipped, pushed with git only (no PR or CI verbs)` : pushed.error, pushed.unblock);
73782
+ }
73783
+ const derived = pushed.forge;
73784
+ const conflict = forgeConflict(derived, workspace?.vcs?.provider);
73785
+ if (conflict)
73786
+ return skipped(derived.kind, conflict.error, conflict.unblock);
73787
+ if (!account)
73788
+ return success2({ status: "no_account", forge: derived.kind, account, login: null });
73789
+ const base = options.runner ?? systemRunner(derived.kind, derived.apiHost, options.env ?? process.env);
73790
+ const scope = memoScope(options.runner, options.env ?? process.env);
73791
+ const now = options.now ?? Date.now;
73792
+ const limits = { deadline: now() + (options.budgetMs ?? PUSH_IDENTITY_BUDGET_MS) };
73793
+ const credential = resolveCredential(workspace, derived, clampedRunner(base, limits, now), scope);
73794
+ if (!credential.ok)
73795
+ return unreachable(credential) ? skipped(derived.kind, credential.error, credential.unblock) : credential;
73796
+ const forge = memoizedIdentity(createForge(derived, derived.repo, clampedRunner(base, limits, now, credential.data.token)), scope, credential.data);
73797
+ const identity = forge.identity(account);
73798
+ if (!identity.ok)
73799
+ return unreachable(identity) ? skipped(derived.kind, identity.error, identity.unblock) : identity;
73800
+ if (identity.data.matches === false)
73801
+ return mismatch({ forge, credential: credential.data.credential, expectedAccount: account }, identity.data.login ?? "?", workspace?.name ?? null);
73802
+ if (identity.data.matches === null)
73803
+ return skipped(derived.kind, identity.data.note ?? "the credential cannot read /user");
73804
+ return success2({ status: "verified", forge: derived.kind, account, login: identity.data.login });
73805
+ }
73806
+ var MEMO_TTL_MS, SYSTEM_SCOPE, memoScopes, memoScope = (runner, env) => ({
73807
+ owner: runner ?? SYSTEM_SCOPE,
73808
+ key: [env.GH_CONFIG_DIR, env.GLAB_CONFIG_DIR, env.XDG_CONFIG_HOME, env.HOME].map((value) => value ?? "").join("|")
73809
+ }), memoizedIdentity = (forge, scope, credential) => {
73810
+ if (credential.credential === "cli_login")
73811
+ return forge;
73812
+ const identity = (expected) => forge.identity(expected);
73813
+ return {
73814
+ ...forge,
73815
+ identity: (expected) => memo4(scope, `identity ${forge.kind} ${forge.apiHost} ${credential.credential} ${credential.token ?? ""} ${expected ?? ""}`, () => {
73816
+ const value = identity(expected);
73817
+ return { keep: value.ok, value };
73818
+ })
73819
+ };
73820
+ }, configuredHosts = () => {
73594
73821
  const vcs = readVcsConfig();
73595
73822
  if (vcs.status !== "valid")
73596
73823
  return {};
@@ -73599,7 +73826,13 @@ var configuredHosts = () => {
73599
73826
  return typeof value === "string" && value.trim() ? [value.trim()] : [];
73600
73827
  };
73601
73828
  return { github: host("github"), gitlab: host("gitlab") };
73602
- }, createForge = (derived, repo, runner) => derived.kind === "github" ? createGitHubForge({ apiHost: derived.apiHost, repo, runner }) : createGitLabForge({ apiHost: derived.apiHost, repo, runner });
73829
+ }, createForge = (derived, repo, runner) => derived.kind === "github" ? createGitHubForge({ apiHost: derived.apiHost, repo, runner }) : createGitLabForge({ apiHost: derived.apiHost, repo, runner }), mismatch = (resolved, login, workspace) => {
73830
+ const { forge, expectedAccount } = resolved;
73831
+ const bin = forge.kind === "github" ? "gh" : "glab";
73832
+ const where = `workspace ${workspace ?? "?"}`;
73833
+ const fileFix = `put a token for ${expectedAccount} in the ${where} vcs.tokenFile`;
73834
+ return failure3("blocked", `identity_mismatch: the ${resolved.credential === "workspace_token_file" ? "vcs.tokenFile token" : `${bin} login`} is ${login} but ${where} expects ${expectedAccount}`, resolved.credential === "workspace_token_file" ? fileFix : forge.kind === "github" ? `gh auth login --hostname ${forge.apiHost} # sign in as ${expectedAccount}` : `${fileFix} (glab keeps one login per host, so workit never switches it)`);
73835
+ }, unreachable = (result) => result.code === "unavailable" && !/ is not authenticated for /u.test(result.error), PUSH_IDENTITY_BUDGET_MS = 5000;
73603
73836
  var init_resolve = __esm(() => {
73604
73837
  init_vcs_config();
73605
73838
  init_workspaces();
@@ -73607,6 +73840,10 @@ var init_resolve = __esm(() => {
73607
73840
  init_exec();
73608
73841
  init_github();
73609
73842
  init_gitlab();
73843
+ init_redact();
73844
+ MEMO_TTL_MS = 5 * 60000;
73845
+ SYSTEM_SCOPE = {};
73846
+ memoScopes = new WeakMap;
73610
73847
  });
73611
73848
 
73612
73849
  // packages/workit-core/src/duration.ts
@@ -73699,16 +73936,19 @@ function releaseTrunk(io, track, branch) {
73699
73936
  tracked: Boolean(resolved.releaseTrack)
73700
73937
  };
73701
73938
  }
73702
- function connect(io, branch) {
73939
+ function connect(io, branch, options = {}) {
73940
+ const verbDeadline = options.deadline ?? null;
73703
73941
  const resolved = resolveForge(io.cwd, {
73704
73942
  branch,
73705
73943
  env: io.env,
73706
73944
  runner: forgeDeps.runner,
73707
- now: forgeDeps.now
73945
+ now: forgeDeps.now,
73946
+ deadline: verbDeadline ?? forgeDeps.now() + CONNECT_BUDGET_MS
73708
73947
  });
73709
73948
  if (!resolved.ok)
73710
73949
  return resolved;
73711
73950
  const identity = checkIdentity(resolved.data);
73951
+ resolved.data.limits.deadline = verbDeadline;
73712
73952
  if (!identity.ok)
73713
73953
  return identity;
73714
73954
  return {
@@ -73763,7 +74003,12 @@ function renderStatus(doc) {
73763
74003
  lines.push(`next: ${doc.next}`);
73764
74004
  return lines;
73765
74005
  }
73766
- var forgeDeps, MAX_REASON = 500, 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) : "?";
74006
+ var forgeDeps, MAX_REASON = 500, 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 })), CONNECT_BUDGET_MS = 15000, pushIdentity = (io, branch) => checkPushIdentity(io.cwd, {
74007
+ branch,
74008
+ env: io.env,
74009
+ runner: forgeDeps.runner,
74010
+ now: forgeDeps.now
74011
+ }), short = (sha) => sha ? sha.slice(0, 7) : "?";
73767
74012
  var init_forge_common = __esm(() => {
73768
74013
  init_resolve();
73769
74014
  init_vcs_config();
@@ -74013,16 +74258,17 @@ async function wait(argv, io) {
74013
74258
  const head = flags.values.head ?? null;
74014
74259
  if (head !== null && !/^[0-9a-f]{7,64}$/u.test(head))
74015
74260
  return usage3(io, "--head must be a commit sha (7-64 hex chars)", WAIT_USAGE);
74016
- const connected = connect(io, flags.values.branch);
74261
+ const started = forgeDeps.now();
74262
+ const deadline = started + timeoutMs;
74263
+ const connected = connect(io, flags.values.branch, {
74264
+ deadline: started + Math.max(timeoutMs, ONE_POLL_MS)
74265
+ });
74017
74266
  if (!connected.ok)
74018
74267
  return forgeFail(io, connected);
74019
74268
  const resolved = connected.data;
74020
74269
  const number = selectPr(io.cwd, resolved, { pr, branch: flags.values.branch ?? null });
74021
74270
  if (!number.ok)
74022
74271
  return forgeFail(io, number);
74023
- const started = forgeDeps.now();
74024
- const deadline = started + timeoutMs;
74025
- resolved.limits.deadline = started + Math.max(timeoutMs, ONE_POLL_MS);
74026
74272
  let polls = 0;
74027
74273
  let errors = 0;
74028
74274
  let status = null;
@@ -74359,12 +74605,11 @@ async function push(argv, io) {
74359
74605
  const plan = pushPreflight(io.cwd);
74360
74606
  if (!plan.ok)
74361
74607
  return forgeFail(io, plan);
74362
- if (!plan.data.local) {
74363
- const connected = connect(io, plan.data.branch);
74364
- if (!connected.ok)
74365
- return forgeFail(io, connected);
74366
- }
74367
- const grant = requireGrant(io.cwd, "push", { forge: !plan.data.local });
74608
+ const identity = plan.data.local ? null : pushIdentity(io, plan.data.branch);
74609
+ if (identity && !identity.ok)
74610
+ return forgeFail(io, identity);
74611
+ const checked = identity?.data ?? null;
74612
+ const grant = requireGrant(io.cwd, "push", { forge: checked?.forge != null });
74368
74613
  if (!grant.allowed)
74369
74614
  return emit(io, fail("blocked", grant.error, {
74370
74615
  unblock: grant.unblock,
@@ -74375,14 +74620,22 @@ async function push(argv, io) {
74375
74620
  overwriteUnintegrated: flags.booleans.has("overwrite-unintegrated"),
74376
74621
  expect,
74377
74622
  setUpstream: flags.booleans.has("set-upstream"),
74378
- actor: actorFromEnv(io.env)
74623
+ actor: actorFromEnv(io.env),
74624
+ ...checked ? { identity: { ...checked } } : {}
74379
74625
  });
74380
74626
  if (!result.ok)
74381
74627
  return forgeFail(io, result, {
74382
74628
  branch: plan.data.branch,
74383
74629
  sha: plan.data.sha
74384
74630
  });
74385
- return emit(io, ok(result.data), (data) => [
74631
+ const warnings = checked?.status === "skipped" ? [
74632
+ `identity check skipped: ${checked.reason}${checked.unblock ? ` (to restore it: ${checked.unblock})` : ""}`
74633
+ ] : [];
74634
+ for (const warning of warnings)
74635
+ io.stderr(`warning: ${warning}
74636
+ `);
74637
+ const envelope = ok({ ...result.data, identity: checked });
74638
+ return emit(io, warnings.length ? { ...envelope, warnings } : envelope, (data) => [
74386
74639
  data.pushed ? `pushed ${data.branch} ${short2(data.previous)} -> ${short2(data.sha)} to ${data.remote}${data.forced ? " (force-with-lease)" : ""}; remote tip verified` : `${data.remote}/${data.branch} is already at ${short2(data.sha)}; remote tip verified`,
74387
74640
  ...data.upstream ? [`upstream: ${data.remote}/${data.branch}`] : []
74388
74641
  ]);
@@ -74415,7 +74668,7 @@ var init_git2 = __esm(() => {
74415
74668
  });
74416
74669
 
74417
74670
  // packages/workit-core/src/forge/verify.ts
74418
- import { spawnSync as spawnSync19 } from "node:child_process";
74671
+ import { spawnSync as spawnSync20 } from "node:child_process";
74419
74672
  function finish(cwd, report, actor) {
74420
74673
  const delivered = report.observations.length > 0 && report.observations.every((entry) => entry.ok);
74421
74674
  const out = { ...report, delivered };
@@ -74546,7 +74799,7 @@ function parsePackageSpec(spec) {
74546
74799
  const version = at > 0 ? value.slice(at + 1) : null;
74547
74800
  if (!name || name.length > 214 || !NPM_NAME.test(name))
74548
74801
  return null;
74549
- if (version !== null && !SEMVER.test(version))
74802
+ if (version !== null && !SEMVER2.test(version))
74550
74803
  return null;
74551
74804
  return { name, version };
74552
74805
  }
@@ -74581,7 +74834,7 @@ function verifyReleased(cwd, input, npm = systemNpm) {
74581
74834
  const version = spec.version ?? (input.tag ? input.tag.replace(/^v(?=\d)/u, "") : null);
74582
74835
  if (!version)
74583
74836
  return failure3("invalid_input", "--package needs a version (<name>@<version>) or --tag");
74584
- if (!SEMVER.test(version))
74837
+ if (!SEMVER2.test(version))
74585
74838
  return failure3("invalid_input", `${version} (from --tag) is not a semver version; pass <name>@<version>`);
74586
74839
  const viewed = npm(["view", `${spec.name}@${version}`, "version", "gitHead", "--json"]);
74587
74840
  if (viewed.status !== 0 && !/E404|404 Not Found|is not in this registry|No match found/iu.test(viewed.stderr))
@@ -74615,7 +74868,7 @@ function verifyReleased(cwd, input, npm = systemNpm) {
74615
74868
  observations
74616
74869
  }, input.actor));
74617
74870
  }
74618
- var NPM_NAME, SEMVER, SAFE_ARG, systemNpm = (args) => {
74871
+ var NPM_NAME, SEMVER2, SAFE_ARG, systemNpm = (args) => {
74619
74872
  if (!args.every((arg) => SAFE_ARG.test(arg)))
74620
74873
  return { status: 2, stdout: "", stderr: "refused: unsafe npm argument" };
74621
74874
  const options = {
@@ -74628,7 +74881,7 @@ var NPM_NAME, SEMVER, SAFE_ARG, systemNpm = (args) => {
74628
74881
  };
74629
74882
  const found = process.platform === "win32" ? findExecutable("npm.cmd", process.env) : null;
74630
74883
  const npmCmd = found && !/["%!^]/u.test(found) ? `"${found}"` : "npm.cmd";
74631
- const result = process.platform === "win32" ? spawnSync19(`${npmCmd} ${args.join(" ")}`, { ...options, shell: true }) : spawnSync19("npm", [...args], options);
74884
+ const result = process.platform === "win32" ? spawnSync20(`${npmCmd} ${args.join(" ")}`, { ...options, shell: true }) : spawnSync20("npm", [...args], options);
74632
74885
  return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
74633
74886
  }, short3 = (sha) => sha ? sha.slice(0, 12) : "(absent)", npmView = (stdout) => {
74634
74887
  try {
@@ -74646,7 +74899,7 @@ var init_verify = __esm(() => {
74646
74899
  init_report();
74647
74900
  init_redact();
74648
74901
  NPM_NAME = /^(?:@[A-Za-z0-9~][A-Za-z0-9-._~]*\/)?[A-Za-z0-9~][A-Za-z0-9-._~]*$/u;
74649
- SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u;
74902
+ SEMVER2 = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u;
74650
74903
  SAFE_ARG = /^[0-9A-Za-z@/._~+-]+$/u;
74651
74904
  });
74652
74905
 
@@ -74735,7 +74988,7 @@ var init_verify_delivery = __esm(() => {
74735
74988
  });
74736
74989
 
74737
74990
  // packages/workit-core/src/stack.ts
74738
- import { spawnSync as spawnSync20 } from "node:child_process";
74991
+ import { spawnSync as spawnSync21 } from "node:child_process";
74739
74992
  import { createHash as createHash15, randomBytes as randomBytes3 } from "node:crypto";
74740
74993
  import fs22 from "node:fs";
74741
74994
  import path44 from "node:path";
@@ -75248,7 +75501,7 @@ function commitKeys(cwd, range) {
75248
75501
  const patches = new Map;
75249
75502
  const diff = gitRun3(cwd, ["log", "--no-merges", "-p", "--format=commit %H", range]);
75250
75503
  if (diff.ok && diff.stdout.trim()) {
75251
- const ids = spawnSync20("git", ["patch-id", "--stable"], {
75504
+ const ids = spawnSync21("git", ["patch-id", "--stable"], {
75252
75505
  cwd,
75253
75506
  input: diff.stdout,
75254
75507
  encoding: "utf8",
@@ -75872,7 +76125,7 @@ var STACK_VERSION = 1, stackFail = (code, error, unblock, data) => ({
75872
76125
  ...unblock ? { unblock } : {},
75873
76126
  ...data ? { data } : {}
75874
76127
  }), fromForge = (result) => stackFail(result.code, result.error, result.unblock), stackDeps, gitRun3 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
75875
- const run = spawnSync20("git", args, {
76128
+ const run = spawnSync21("git", args, {
75876
76129
  cwd,
75877
76130
  encoding: "utf8",
75878
76131
  timeout: timeoutMs,
@@ -76167,7 +76420,7 @@ var init_stack2 = __esm(() => {
76167
76420
  });
76168
76421
 
76169
76422
  // packages/workit-core/src/fanout.ts
76170
- import { spawnSync as spawnSync21 } from "node:child_process";
76423
+ import { spawnSync as spawnSync22 } from "node:child_process";
76171
76424
  import { createHash as createHash16 } from "node:crypto";
76172
76425
  import fs23 from "node:fs";
76173
76426
  import path45 from "node:path";
@@ -76940,7 +77193,7 @@ var FANOUT_VERSION = 1, TIERS, FAN_IN_MODES, fanoutFail = (code, error, unblock,
76940
77193
  text: edges.map((edge) => `add "${edge.dependsOn}" to "dependsOn" of slice ${edge.slice}`).join("; ")
76941
77194
  };
76942
77195
  }, gitRun4 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
76943
- const run = spawnSync21("git", args, {
77196
+ const run = spawnSync22("git", args, {
76944
77197
  cwd,
76945
77198
  encoding: "utf8",
76946
77199
  timeout: timeoutMs,
@@ -77460,7 +77713,7 @@ var init_fanout_brief = __esm(() => {
77460
77713
  });
77461
77714
 
77462
77715
  // packages/workit-core/src/fanout-landed.ts
77463
- import { spawnSync as spawnSync22 } from "node:child_process";
77716
+ import { spawnSync as spawnSync23 } from "node:child_process";
77464
77717
  function forgeBreaker(resolved) {
77465
77718
  if (!resolved)
77466
77719
  return { forge: null, down: () => null };
@@ -77551,7 +77804,7 @@ function trunkPatchIds(ctx, fork) {
77551
77804
  "--"
77552
77805
  ]);
77553
77806
  if (log.ok && log.stdout.trim()) {
77554
- const run = spawnSync22("git", ["patch-id", "--stable"], {
77807
+ const run = spawnSync23("git", ["patch-id", "--stable"], {
77555
77808
  cwd: ctx.cwd,
77556
77809
  input: log.stdout,
77557
77810
  encoding: "utf8",
@@ -94494,11 +94747,11 @@ var init_rules = __esm(() => {
94494
94747
  });
94495
94748
 
94496
94749
  // packages/workit-cli/src/test-audit/audit.ts
94497
- import { spawnSync as spawnSync23 } from "node:child_process";
94750
+ import { spawnSync as spawnSync24 } from "node:child_process";
94498
94751
  import { existsSync as existsSync19, readdirSync as readdirSync8, readFileSync as readFileSync21, statSync as statSync7 } from "node:fs";
94499
94752
  import path49 from "node:path";
94500
94753
  function git6(cwd, args) {
94501
- const run = spawnSync23("git", ["-c", "core.quotepath=off", ...args], {
94754
+ const run = spawnSync24("git", ["-c", "core.quotepath=off", ...args], {
94502
94755
  cwd,
94503
94756
  encoding: "utf8",
94504
94757
  maxBuffer: 64 * 1024 * 1024
@@ -96391,9 +96644,21 @@ async function main(argv, overrides = {}, options = {}) {
96391
96644
  globalThis[Symbol.for("workit.migrationReporter")] = (report) => io.stderr(`workit: migrated ${report.tasks} task${report.tasks === 1 ? "" : "s"} from ${report.from} to ${report.to} (backup: ${report.backup})
96392
96645
  `);
96393
96646
  const verb = await entry.load();
96647
+ const runVerb = (target) => guarded(target, () => verb.run(ended, target));
96394
96648
  if (!io.json)
96395
- return verb.run(ended, io);
96396
- return runJsonPure(io, command, (pure) => verb.run(ended, pure));
96649
+ return runVerb(io);
96650
+ return runJsonPure(io, command, runVerb);
96651
+ }
96652
+ async function guarded(io, run) {
96653
+ try {
96654
+ return await run();
96655
+ } catch (error) {
96656
+ if (!(error instanceof Error) || error.name !== "WorkspaceConfigError")
96657
+ throw error;
96658
+ return emit(io, fail("blocked", error.message, {
96659
+ unblock: "fix ~/.config/workit/workspaces.json (workit init → Workspaces)"
96660
+ }));
96661
+ }
96397
96662
  }
96398
96663
  var CODE_FOR_EXIT = {
96399
96664
  1: "failed",