@parabolic-technologies/banzai 2.6.4 → 2.6.5

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/banzai.mjs CHANGED
@@ -5719,6 +5719,14 @@ var CLAUDE_CATALOG = {
5719
5719
  ["claude-3-sonnet-20240229", retired(SONNET, "2025-07-21")],
5720
5720
  ["claude-3-5-haiku-20241022", retired(HAIKU, "2026-02-19")],
5721
5721
  ["claude-3-haiku-20240307", retired(HAIKU, "2026-04-20")]
5722
+ ]),
5723
+ // From Claude Code's model configuration documentation. An older client
5724
+ // refuses these models itself, before any request; tier aliases need no
5725
+ // entry because they resolve to a model the installed client serves.
5726
+ cliMinimums: /* @__PURE__ */ new Map([
5727
+ [OPUS, "2.1.280"],
5728
+ [SONNET, "2.1.284"],
5729
+ ["claude-fable-5-1", "2.1.257"]
5722
5730
  ])
5723
5731
  };
5724
5732
  var CODEX_SOL = "gpt-6.1-sol";
@@ -5737,6 +5745,14 @@ var CODEX_CATALOG = {
5737
5745
  ["gpt-5.4", retired(CODEX_SOL, "2026-08-31")],
5738
5746
  ["gpt-5.4-mini", retired("gpt-6-luna", "2026-08-31")],
5739
5747
  ["gpt-5", superseded(CODEX_SOL)]
5748
+ ]),
5749
+ // The first stable Codex CLI release whose bundled model catalog carries the
5750
+ // model, from the Codex CLI release notes.
5751
+ cliMinimums: /* @__PURE__ */ new Map([
5752
+ [CODEX_SOL, "0.159.1"],
5753
+ ["gpt-6-sol", "0.156.1"],
5754
+ ["gpt-6-luna", "0.156.1"],
5755
+ ["gpt-6-astra", "0.153.1"]
5740
5756
  ])
5741
5757
  };
5742
5758
  var FULL_EFFORTS = [
@@ -12500,6 +12516,14 @@ var CLAUDE_CATALOG2 = {
12500
12516
  ["claude-3-sonnet-20240229", retired2(SONNET2, "2025-07-21")],
12501
12517
  ["claude-3-5-haiku-20241022", retired2(HAIKU2, "2026-02-19")],
12502
12518
  ["claude-3-haiku-20240307", retired2(HAIKU2, "2026-04-20")]
12519
+ ]),
12520
+ // From Claude Code's model configuration documentation. An older client
12521
+ // refuses these models itself, before any request; tier aliases need no
12522
+ // entry because they resolve to a model the installed client serves.
12523
+ cliMinimums: /* @__PURE__ */ new Map([
12524
+ [OPUS2, "2.1.280"],
12525
+ [SONNET2, "2.1.284"],
12526
+ ["claude-fable-5-1", "2.1.257"]
12503
12527
  ])
12504
12528
  };
12505
12529
  var CODEX_SOL2 = "gpt-6.1-sol";
@@ -12518,6 +12542,14 @@ var CODEX_CATALOG2 = {
12518
12542
  ["gpt-5.4", retired2(CODEX_SOL2, "2026-08-31")],
12519
12543
  ["gpt-5.4-mini", retired2("gpt-6-luna", "2026-08-31")],
12520
12544
  ["gpt-5", superseded2(CODEX_SOL2)]
12545
+ ]),
12546
+ // The first stable Codex CLI release whose bundled model catalog carries the
12547
+ // model, from the Codex CLI release notes.
12548
+ cliMinimums: /* @__PURE__ */ new Map([
12549
+ [CODEX_SOL2, "0.159.1"],
12550
+ ["gpt-6-sol", "0.156.1"],
12551
+ ["gpt-6-luna", "0.156.1"],
12552
+ ["gpt-6-astra", "0.153.1"]
12521
12553
  ])
12522
12554
  };
12523
12555
  var CATALOGS = /* @__PURE__ */ new Map([
@@ -12661,6 +12693,9 @@ function selectableModel(id3) {
12661
12693
  function isCatalogSupportedModel(vendor, id3) {
12662
12694
  return CATALOGS.get(vendor)?.supported.has(id3) ?? false;
12663
12695
  }
12696
+ function modelCliMinimum(vendor, model) {
12697
+ return CATALOGS.get(vendor)?.cliMinimums.get(model) ?? null;
12698
+ }
12664
12699
  var FALLBACK_VENDOR = {
12665
12700
  planner: "codex",
12666
12701
  implementer: "codex",
@@ -24360,7 +24395,7 @@ import process7 from "node:process";
24360
24395
  // package.json
24361
24396
  var package_default = {
24362
24397
  name: "@parabolic-technologies/banzai",
24363
- version: "2.6.4",
24398
+ version: "2.6.5",
24364
24399
  private: false,
24365
24400
  description: "Spec-driven AI development CLI for Codex and Claude Code, with human approvals and independent review for complex changes.",
24366
24401
  keywords: [
@@ -27028,9 +27063,9 @@ async function requireCodexStateWriteAccess(sourceEnvironment) {
27028
27063
  );
27029
27064
  }
27030
27065
  }
27031
- async function requireVersion(runner, settings, minimum, sourceEnvironment = process.env) {
27066
+ async function requireVersion(runner, vendor, target, sourceEnvironment = process.env) {
27032
27067
  const result = await runner({
27033
- command: settings.executable,
27068
+ command: target.executable,
27034
27069
  args: ["--version"],
27035
27070
  cwd: process.cwd(),
27036
27071
  env: subscriptionOnlyEnvironment(sourceEnvironment),
@@ -27039,17 +27074,25 @@ async function requireVersion(runner, settings, minimum, sourceEnvironment = pro
27039
27074
  if (result.exitCode !== 0) {
27040
27075
  throw new BanzaiError(
27041
27076
  "CLI_UNAVAILABLE",
27042
- `${settings.executable} --version failed: ${result.stderr.trim()}`
27077
+ `${target.executable} --version failed: ${result.stderr.trim()}`
27043
27078
  );
27044
27079
  }
27045
27080
  const actual = parseVersion(
27046
27081
  result.stdout || result.stderr,
27047
- settings.executable
27082
+ target.executable
27048
27083
  );
27084
+ const minimum = vendor === "claude" ? MINIMUM_CLAUDE_VERSION : MINIMUM_CODEX_VERSION;
27049
27085
  if (!versionAtLeast(actual, minimum)) {
27050
27086
  throw new BanzaiError(
27051
27087
  "CLI_TOO_OLD",
27052
- `${settings.executable} ${actual} is older than required ${minimum}.`
27088
+ `${target.executable} ${actual} is older than required ${minimum}.`
27089
+ );
27090
+ }
27091
+ const modelMinimum = target.model === null ? null : modelCliMinimum(vendor, target.model);
27092
+ if (modelMinimum !== null && !versionAtLeast(actual, modelMinimum)) {
27093
+ throw new BanzaiError(
27094
+ "CLI_TOO_OLD",
27095
+ `${target.executable} ${actual} cannot run ${target.model}, which requires ${modelMinimum} or newer. Update ${target.executable}, or choose another model with \`banzai model\`.`
27053
27096
  );
27054
27097
  }
27055
27098
  }
@@ -27527,16 +27570,11 @@ function createVendorAdapters(runner = runProcess) {
27527
27570
  runner
27528
27571
  });
27529
27572
  },
27530
- async preflightCodex(settings, sourceEnvironment = process.env) {
27573
+ async preflightCodex(target, sourceEnvironment = process.env) {
27531
27574
  await requireCodexStateWriteAccess(sourceEnvironment);
27532
- await requireVersion(
27533
- runner,
27534
- settings,
27535
- MINIMUM_CODEX_VERSION,
27536
- sourceEnvironment
27537
- );
27575
+ await requireVersion(runner, "codex", target, sourceEnvironment);
27538
27576
  const result = await runner({
27539
- command: settings.executable,
27577
+ command: target.executable,
27540
27578
  args: ["login", "status"],
27541
27579
  cwd: process.cwd(),
27542
27580
  env: subscriptionOnlyEnvironment(sourceEnvironment),
@@ -27549,15 +27587,10 @@ function createVendorAdapters(runner = runProcess) {
27549
27587
  );
27550
27588
  }
27551
27589
  },
27552
- async preflightClaude(settings, sourceEnvironment = process.env) {
27553
- await requireVersion(
27554
- runner,
27555
- settings,
27556
- MINIMUM_CLAUDE_VERSION,
27557
- sourceEnvironment
27558
- );
27590
+ async preflightClaude(target, sourceEnvironment = process.env) {
27591
+ await requireVersion(runner, "claude", target, sourceEnvironment);
27559
27592
  const result = await runner({
27560
- command: settings.executable,
27593
+ command: target.executable,
27561
27594
  args: ["auth", "status", "--json"],
27562
27595
  cwd: process.cwd(),
27563
27596
  env: subscriptionOnlyEnvironment(sourceEnvironment),
@@ -27889,6 +27922,270 @@ function createVendorAdapters(runner = runProcess) {
27889
27922
  };
27890
27923
  }
27891
27924
 
27925
+ // src/claude-permissions.ts
27926
+ import { readFile as readFile7 } from "node:fs/promises";
27927
+ import { homedir as homedir3 } from "node:os";
27928
+ import path19 from "node:path";
27929
+ var ALL_SOURCES = [
27930
+ "user",
27931
+ "project",
27932
+ "local"
27933
+ ];
27934
+ var MANAGED_SETTINGS_FILES = {
27935
+ darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
27936
+ linux: "/etc/claude-code/managed-settings.json"
27937
+ };
27938
+ var EMPTY_RULES = { allow: [], deny: [], ask: [] };
27939
+ function isRecord3(value) {
27940
+ return typeof value === "object" && value !== null && !Array.isArray(value);
27941
+ }
27942
+ function stringArray2(value) {
27943
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
27944
+ }
27945
+ function ruleTool(rule) {
27946
+ const open14 = rule.indexOf("(");
27947
+ return (open14 === -1 ? rule : rule.slice(0, open14)).trim();
27948
+ }
27949
+ function namesTool(rule, tool) {
27950
+ return ruleTool(rule) === tool;
27951
+ }
27952
+ function isUnscoped(rule) {
27953
+ return !rule.includes("(");
27954
+ }
27955
+ function homeDirectory(env) {
27956
+ const configured = env.HOME;
27957
+ return configured === void 0 || configured.length === 0 ? homedir3() : configured;
27958
+ }
27959
+ function configDirectory(env) {
27960
+ const configured = env.CLAUDE_CONFIG_DIR;
27961
+ return configured === void 0 || configured.length === 0 ? null : configured;
27962
+ }
27963
+ function trustFile(env) {
27964
+ const configured = configDirectory(env);
27965
+ return configured === null ? path19.join(homeDirectory(env), ".claude.json") : path19.join(configured, ".claude.json");
27966
+ }
27967
+ function settingsFile(source, repoRoot, env) {
27968
+ if (source === "user") {
27969
+ return path19.join(
27970
+ configDirectory(env) ?? path19.join(homeDirectory(env), ".claude"),
27971
+ "settings.json"
27972
+ );
27973
+ }
27974
+ return path19.join(
27975
+ repoRoot,
27976
+ ".claude",
27977
+ source === "project" ? "settings.json" : "settings.local.json"
27978
+ );
27979
+ }
27980
+ async function readRules(filePath) {
27981
+ let raw;
27982
+ try {
27983
+ raw = await readFile7(filePath, "utf8");
27984
+ } catch {
27985
+ return { rules: EMPTY_RULES, intact: true };
27986
+ }
27987
+ try {
27988
+ const document = JSON.parse(raw);
27989
+ const permissions = isRecord3(document) ? document.permissions : void 0;
27990
+ if (!isRecord3(permissions)) {
27991
+ return { rules: EMPTY_RULES, intact: true };
27992
+ }
27993
+ return {
27994
+ rules: {
27995
+ allow: stringArray2(permissions.allow),
27996
+ deny: stringArray2(permissions.deny),
27997
+ ask: stringArray2(permissions.ask)
27998
+ },
27999
+ intact: true
28000
+ };
28001
+ } catch {
28002
+ return { rules: EMPTY_RULES, intact: false };
28003
+ }
28004
+ }
28005
+ async function readManagedRules(override) {
28006
+ const file = override === void 0 ? MANAGED_SETTINGS_FILES[process.platform] : override;
28007
+ return file === void 0 || file === null ? { rules: EMPTY_RULES, intact: true } : await readRules(file);
28008
+ }
28009
+ async function inspectTrust(repoRoot, env) {
28010
+ let raw;
28011
+ try {
28012
+ raw = await readFile7(trustFile(env), "utf8");
28013
+ } catch {
28014
+ return "unknown";
28015
+ }
28016
+ try {
28017
+ const document = JSON.parse(raw);
28018
+ const projects = isRecord3(document) ? document.projects : void 0;
28019
+ if (!isRecord3(projects)) {
28020
+ return "untrusted";
28021
+ }
28022
+ const entry = projects[repoRoot];
28023
+ return isRecord3(entry) && entry.hasTrustDialogAccepted === true ? "trusted" : "untrusted";
28024
+ } catch {
28025
+ return "unknown";
28026
+ }
28027
+ }
28028
+ function resolveGrant(tool, refusing, granting) {
28029
+ for (const set of refusing) {
28030
+ const refused = [...set.deny, ...set.ask];
28031
+ if (refused.some((rule) => namesTool(rule, tool) && isUnscoped(rule))) {
28032
+ return "denied";
28033
+ }
28034
+ }
28035
+ let scoped = false;
28036
+ for (const set of granting) {
28037
+ for (const rule of set.allow) {
28038
+ if (!namesTool(rule, tool)) {
28039
+ continue;
28040
+ }
28041
+ if (isUnscoped(rule)) {
28042
+ return "granted";
28043
+ }
28044
+ scoped = true;
28045
+ }
28046
+ }
28047
+ return scoped ? "scoped" : "missing";
28048
+ }
28049
+ function parseSources(settingSources) {
28050
+ if (settingSources === null) {
28051
+ return [...ALL_SOURCES];
28052
+ }
28053
+ const named = new Set(
28054
+ settingSources.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0)
28055
+ );
28056
+ return ALL_SOURCES.filter((source) => named.has(source));
28057
+ }
28058
+ function claudePermissionDemand(config) {
28059
+ const claudeRoles = new Set(
28060
+ MODEL_ROLES.filter(
28061
+ (role) => (vendorFor(config.models[role].executable) ?? FALLBACK_VENDOR[role]) === "claude"
28062
+ )
28063
+ );
28064
+ const builds = claudeRoles.has("planner") || claudeRoles.has("implementer");
28065
+ return { fileWrites: builds, shellCommands: builds };
28066
+ }
28067
+ function claudePermissionDemanded(demand) {
28068
+ return demand.fileWrites || demand.shellCommands;
28069
+ }
28070
+ async function inspectConfiguredClaudePermissions(repoRoot, config, dependencies) {
28071
+ const demand = claudePermissionDemand(config);
28072
+ if (!claudePermissionDemanded(demand)) {
28073
+ return { state: "not_demanded" };
28074
+ }
28075
+ let settingSources;
28076
+ try {
28077
+ settingSources = (await dependencies.resolveScope(config.claudeEnvironment)).scope.settingSources;
28078
+ } catch {
28079
+ return { state: "unresolved" };
28080
+ }
28081
+ const audit = await (dependencies.inspect ?? inspectClaudePermissions)({
28082
+ repoRoot,
28083
+ demand,
28084
+ settingSources
28085
+ });
28086
+ return { state: "audited", audit };
28087
+ }
28088
+ async function inspectClaudePermissions(input) {
28089
+ const env = input.env ?? process.env;
28090
+ const repoRoot = path19.resolve(input.repoRoot);
28091
+ const demand = input.demand;
28092
+ const sources = parseSources(input.settingSources);
28093
+ const [managed, trust, loaded, unloadedLocal] = await Promise.all([
28094
+ readManagedRules(input.managedSettingsFile),
28095
+ inspectTrust(repoRoot, env),
28096
+ Promise.all(
28097
+ sources.map(async (source) => ({
28098
+ source,
28099
+ ...await readRules(settingsFile(source, repoRoot, env))
28100
+ }))
28101
+ ),
28102
+ sources.includes("local") ? Promise.resolve(null) : readRules(settingsFile("local", repoRoot, env))
28103
+ ]);
28104
+ const honored = loaded.filter(
28105
+ (entry) => entry.source === "user" || trust !== "untrusted"
28106
+ );
28107
+ const granting = [managed.rules, ...honored.map((entry) => entry.rules)];
28108
+ const refusing = [managed.rules, ...loaded.map((entry) => entry.rules)];
28109
+ const audited = [
28110
+ ...demand.fileWrites ? ["Write", "Edit"] : [],
28111
+ ...demand.shellCommands ? ["Bash"] : []
28112
+ ];
28113
+ const tools = audited.map((tool) => ({
28114
+ tool,
28115
+ grant: resolveGrant(tool, refusing, granting)
28116
+ }));
28117
+ const grantOf = (tool) => tools.find((entry) => entry.tool === tool)?.grant ?? null;
28118
+ const repositoryRulesIgnored = trust === "untrusted" && loaded.some(
28119
+ (entry) => entry.source !== "user" && entry.rules.allow.length > 0
28120
+ );
28121
+ const blocking = [];
28122
+ const advisories = [];
28123
+ const refused = tools.filter((entry) => entry.tool !== "Bash" && entry.grant === "denied").map((entry) => entry.tool);
28124
+ if (refused.length > 0) {
28125
+ blocking.push(
28126
+ `A deny or ask rule refuses ${refused.join(" and ")}. No hook decision overrides it and \`--permission-mode dontAsk\` cannot escalate it to a human, so Claude planning and implementation writes are denied.`
28127
+ );
28128
+ }
28129
+ const bash = grantOf("Bash");
28130
+ if (bash === "missing" || bash === "denied") {
28131
+ advisories.push(
28132
+ "No loaded permission rule grants Bash, so every command a phase runs is denied."
28133
+ );
28134
+ } else if (bash === "scoped") {
28135
+ advisories.push(
28136
+ "Bash is granted only for the commands its scoped rules name; anything else a phase runs is denied."
28137
+ );
28138
+ }
28139
+ if (repositoryRulesIgnored) {
28140
+ advisories.push(
28141
+ "Claude Code has not been trusted in this checkout, so it ignores the repository's own allow rules."
28142
+ );
28143
+ }
28144
+ if (bash !== null && bash !== "granted" && unloadedLocal !== null && unloadedLocal.rules.allow.length > 0) {
28145
+ advisories.push(
28146
+ "Local settings hold allow rules the scoped invocation does not load; repository grants belong in the tracked project settings."
28147
+ );
28148
+ }
28149
+ if (trust === "unknown") {
28150
+ advisories.push(
28151
+ "Claude Code's workspace trust record could not be read, so repository rules were treated as honored."
28152
+ );
28153
+ }
28154
+ if (!managed.intact || loaded.some((entry) => !entry.intact)) {
28155
+ advisories.push(
28156
+ "A settings file could not be parsed, so its permission rules were treated as absent."
28157
+ );
28158
+ }
28159
+ return {
28160
+ demand,
28161
+ sources,
28162
+ trust,
28163
+ tools,
28164
+ repositoryRulesIgnored,
28165
+ blocking,
28166
+ advisories
28167
+ };
28168
+ }
28169
+ function claudePermissionRemediation(audit) {
28170
+ const steps = [];
28171
+ const refused = audit.tools.filter(
28172
+ (entry) => entry.tool !== "Bash" && entry.grant === "denied"
28173
+ );
28174
+ if (refused.length > 0) {
28175
+ steps.push(
28176
+ `remove the deny or ask rules naming ${refused.map((entry) => `"${entry.tool}"`).join(
28177
+ " and "
28178
+ )} from the settings Claude loads; a rule in administrator policy settings needs your administrator`
28179
+ );
28180
+ }
28181
+ if (audit.repositoryRulesIgnored) {
28182
+ steps.push(
28183
+ "run `claude` once in this checkout and accept the trust prompt so its repository allow rules apply"
28184
+ );
28185
+ }
28186
+ return steps.join("; ");
28187
+ }
28188
+
27892
28189
  // src/agent-control.ts
27893
28190
  import path35 from "node:path";
27894
28191
 
@@ -27962,13 +28259,13 @@ import path34 from "node:path";
27962
28259
  import process4 from "node:process";
27963
28260
 
27964
28261
  // src/review-snapshot.ts
27965
- import { lstat as lstat13, readFile as readFile8 } from "node:fs/promises";
27966
- import path20 from "node:path";
28262
+ import { lstat as lstat13, readFile as readFile9 } from "node:fs/promises";
28263
+ import path21 from "node:path";
27967
28264
 
27968
28265
  // src/git.ts
27969
28266
  import { createHash as createHash12 } from "node:crypto";
27970
- import { lstat as lstat12, readFile as readFile7 } from "node:fs/promises";
27971
- import path19 from "node:path";
28267
+ import { lstat as lstat12, readFile as readFile8 } from "node:fs/promises";
28268
+ import path20 from "node:path";
27972
28269
  var GIT_TIMEOUT_MS = 2 * 60 * 1e3;
27973
28270
  async function runGit(repoRoot, args) {
27974
28271
  return await runProcess({
@@ -28040,15 +28337,15 @@ async function currentBranch(repoRoot) {
28040
28337
  return branch.length === 0 || branch === "HEAD" ? null : branch;
28041
28338
  }
28042
28339
  function resolveRepositoryPath(repoRoot, relativePath3) {
28043
- if (relativePath3.length === 0 || path19.isAbsolute(relativePath3)) {
28340
+ if (relativePath3.length === 0 || path20.isAbsolute(relativePath3)) {
28044
28341
  throw new BanzaiError(
28045
28342
  "INVALID_ARTIFACT_PATH",
28046
28343
  `Artifact path must be repository-relative: ${relativePath3}`
28047
28344
  );
28048
28345
  }
28049
- const normalizedRoot = path19.resolve(repoRoot);
28050
- const candidate = path19.resolve(normalizedRoot, relativePath3);
28051
- if (!candidate.startsWith(`${normalizedRoot}${path19.sep}`)) {
28346
+ const normalizedRoot = path20.resolve(repoRoot);
28347
+ const candidate = path20.resolve(normalizedRoot, relativePath3);
28348
+ if (!candidate.startsWith(`${normalizedRoot}${path20.sep}`)) {
28052
28349
  throw new BanzaiError(
28053
28350
  "INVALID_ARTIFACT_PATH",
28054
28351
  `Artifact path escapes the repository: ${relativePath3}`
@@ -28065,7 +28362,7 @@ async function hashFile(repoRoot, relativePath3) {
28065
28362
  `Required artifact does not exist as a file: ${relativePath3}`
28066
28363
  );
28067
28364
  }
28068
- const contents = await readFile7(absolutePath);
28365
+ const contents = await readFile8(absolutePath);
28069
28366
  if (metadata.isSymbolicLink()) {
28070
28367
  return createHash12("sha256").update("symlink").update("\0").update(contents).digest("hex");
28071
28368
  }
@@ -28244,10 +28541,10 @@ function sealReviewSnapshot3(value) {
28244
28541
  }
28245
28542
  async function assertReviewPathOwned(repoRoot, relativePath3, allowMissing = false) {
28246
28543
  reviewScopePath4(relativePath3);
28247
- let current = path20.resolve(repoRoot);
28544
+ let current = path21.resolve(repoRoot);
28248
28545
  const segments = relativePath3.split("/");
28249
28546
  for (const [index, part] of segments.entries()) {
28250
- current = path20.join(current, part);
28547
+ current = path21.join(current, part);
28251
28548
  let stat6;
28252
28549
  try {
28253
28550
  stat6 = await lstat13(current);
@@ -28266,7 +28563,7 @@ async function reviewApprovedArtifacts(repoRoot, paths) {
28266
28563
  await assertReviewPathOwned(repoRoot, relative4);
28267
28564
  return {
28268
28565
  path: relative4,
28269
- sha256: hashArtifactBytes(await readFile8(path20.join(repoRoot, relative4)))
28566
+ sha256: hashArtifactBytes(await readFile9(path21.join(repoRoot, relative4)))
28270
28567
  };
28271
28568
  })
28272
28569
  );
@@ -28276,7 +28573,7 @@ async function reviewApprovedArtifacts(repoRoot, paths) {
28276
28573
  import { createHash as createHash13 } from "node:crypto";
28277
28574
  import { constants as constants10 } from "node:fs";
28278
28575
  import { lstat as lstat14, open as open10, realpath as realpath8 } from "node:fs/promises";
28279
- import path21 from "node:path";
28576
+ import path23 from "node:path";
28280
28577
 
28281
28578
  // src/phase-resources.ts
28282
28579
  var PHASE_RESOURCE_DEFINITIONS = {
@@ -28523,7 +28820,7 @@ async function installedDigest(directory4, id3) {
28523
28820
  const root = await realpath8(directory4);
28524
28821
  let target = root;
28525
28822
  for (const part of id3.split("/")) {
28526
- target = path21.join(target, part);
28823
+ target = path23.join(target, part);
28527
28824
  const entry = await lstat14(target);
28528
28825
  if (entry.isSymbolicLink()) throw new Error("Unsafe instruction resource");
28529
28826
  }
@@ -29935,8 +30232,8 @@ async function digestRejectionReason(context, state, sourceAttemptId, error) {
29935
30232
 
29936
30233
  // src/workflow/completion/inputs.ts
29937
30234
  import { createHash as createHash15 } from "node:crypto";
29938
- import { lstat as lstat15, readFile as readFile9, realpath as realpath9 } from "node:fs/promises";
29939
- import path23 from "node:path";
30235
+ import { lstat as lstat15, readFile as readFile10, realpath as realpath9 } from "node:fs/promises";
30236
+ import path24 from "node:path";
29940
30237
  async function observeCompletionInputs(context, state, artifacts) {
29941
30238
  const implementation = await observeReport(
29942
30239
  context,
@@ -29975,8 +30272,8 @@ async function observeReport(context, relativePath3) {
29975
30272
  return { observation: { path: null, state: "absent" }, bytes: null };
29976
30273
  let absolute;
29977
30274
  try {
29978
- absolute = path23.resolve(context.repoRoot, relativePath3);
29979
- if (relativePath3.length === 0 || relativePath3.includes("\\") || path23.posix.isAbsolute(relativePath3) || path23.posix.normalize(relativePath3) !== relativePath3 || relativePath3.startsWith("../") || !absolute.startsWith(`${path23.resolve(context.repoRoot)}${path23.sep}`))
30275
+ absolute = path24.resolve(context.repoRoot, relativePath3);
30276
+ if (relativePath3.length === 0 || relativePath3.includes("\\") || path24.posix.isAbsolute(relativePath3) || path24.posix.normalize(relativePath3) !== relativePath3 || relativePath3.startsWith("../") || !absolute.startsWith(`${path24.resolve(context.repoRoot)}${path24.sep}`))
29980
30277
  return {
29981
30278
  observation: { path: relativePath3, state: "unsafe" },
29982
30279
  bytes: null
@@ -29988,7 +30285,7 @@ async function observeReport(context, relativePath3) {
29988
30285
  bytes: null
29989
30286
  };
29990
30287
  const root = await realpath9(context.repoRoot);
29991
- if (!(await realpath9(absolute)).startsWith(`${root}${path23.sep}`))
30288
+ if (!(await realpath9(absolute)).startsWith(`${root}${path24.sep}`))
29992
30289
  return {
29993
30290
  observation: { path: relativePath3, state: "unsafe" },
29994
30291
  bytes: null
@@ -30003,7 +30300,7 @@ async function observeReport(context, relativePath3) {
30003
30300
  };
30004
30301
  }
30005
30302
  try {
30006
- const bytes = await readFile9(absolute);
30303
+ const bytes = await readFile10(absolute);
30007
30304
  return {
30008
30305
  observation: {
30009
30306
  path: relativePath3,
@@ -30025,13 +30322,13 @@ function sameObservedInputs(a, b) {
30025
30322
  }
30026
30323
 
30027
30324
  // src/workflow/completion/plan-tasks.ts
30028
- import { readFile as readFile11 } from "node:fs/promises";
30029
- import path25 from "node:path";
30325
+ import { readFile as readFile12 } from "node:fs/promises";
30326
+ import path26 from "node:path";
30030
30327
 
30031
30328
  // src/plan-task-completion.ts
30032
30329
  import { createHash as createHash16 } from "node:crypto";
30033
- import { lstat as lstat16, readFile as readFile10, realpath as realpath10 } from "node:fs/promises";
30034
- import path24 from "node:path";
30330
+ import { lstat as lstat16, readFile as readFile11, realpath as realpath10 } from "node:fs/promises";
30331
+ import path25 from "node:path";
30035
30332
  var PROJECTION_FORMAT = "banzai-plan-task-completion/v1";
30036
30333
  var SECTION_HEADING = "## Plan Task Completion";
30037
30334
  var TASK_HEADING = /^- \[ \] \*\*(T[0-9]{3}) - (.+)\*\*\s*$/u;
@@ -30132,10 +30429,10 @@ function validatePlanRunnerControlTasks(markdown, repoRoot, allowedArtifactPaths
30132
30429
  const section = markdown.split(/^## Implementation Tasks\s*$/mu)[1]?.split(/^## /mu)[0] ?? "";
30133
30430
  const allowed = new Set(
30134
30431
  allowedArtifactPaths.map(
30135
- (value) => path24.posix.normalize(value.replaceAll("\\", "/"))
30432
+ (value) => path25.posix.normalize(value.replaceAll("\\", "/"))
30136
30433
  )
30137
30434
  );
30138
- const root = path24.posix.normalize(repoRoot.replaceAll("\\", "/")).replace(/\/$/u, "");
30435
+ const root = path25.posix.normalize(repoRoot.replaceAll("\\", "/")).replace(/\/$/u, "");
30139
30436
  let previousTaskId = "";
30140
30437
  let previousPaths = [];
30141
30438
  for (const { taskId, text: text4, access: access7 } of planTaskStatements(section)) {
@@ -30146,8 +30443,8 @@ function validatePlanRunnerControlTasks(markdown, repoRoot, allowedArtifactPaths
30146
30443
  const token = raw.replaceAll("\\", "/").replace(/[.!?]+$/u, "").replace(/:\d+(?::\d+)?$/u, "");
30147
30444
  if (!token.includes("/") && !/^\.[\w-]+$/u.test(token) && !/\.[a-z0-9]+$/iu.test(token))
30148
30445
  return raw;
30149
- const normalized = path24.posix.normalize(
30150
- path24.posix.isAbsolute(token) || /^[a-z]:\//iu.test(token) ? token : `${root}/${token}`
30446
+ const normalized = path25.posix.normalize(
30447
+ path25.posix.isAbsolute(token) || /^[a-z]:\//iu.test(token) ? token : `${root}/${token}`
30151
30448
  );
30152
30449
  const relative4 = normalized.toLowerCase().startsWith(`${root.toLowerCase()}/`) ? normalized.slice(root.length + 1) : normalized;
30153
30450
  paths.push(relative4);
@@ -30391,7 +30688,7 @@ function parsePlanTaskCompletionProjection(markdown) {
30391
30688
  };
30392
30689
  }
30393
30690
  function normalizedRepositoryPath(relativePath3, requiredReports = /* @__PURE__ */ new Set()) {
30394
- if (relativePath3.length === 0 || relativePath3.includes("\\") || /[\u0000-\u001f\u007f-\u009f]/u.test(relativePath3) || relativePath3.length > 500 || path24.posix.isAbsolute(relativePath3) || path24.posix.normalize(relativePath3) !== relativePath3 || relativePath3 === ".." || relativePath3.startsWith("../")) {
30691
+ if (relativePath3.length === 0 || relativePath3.includes("\\") || /[\u0000-\u001f\u007f-\u009f]/u.test(relativePath3) || relativePath3.length > 500 || path25.posix.isAbsolute(relativePath3) || path25.posix.normalize(relativePath3) !== relativePath3 || relativePath3 === ".." || relativePath3.startsWith("../")) {
30395
30692
  return completionFailure(
30396
30693
  "invalid_evidence_path",
30397
30694
  "Completion evidence path must be normalized and repository-relative."
@@ -30418,7 +30715,7 @@ async function validateEvidenceFile(repoRoot, relativePath3, allowedPaths, appro
30418
30715
  );
30419
30716
  }
30420
30717
  const repositoryRoot = await realpath10(repoRoot);
30421
- const absolutePath = path24.resolve(repositoryRoot, normalized);
30718
+ const absolutePath = path25.resolve(repositoryRoot, normalized);
30422
30719
  let resolvedPath;
30423
30720
  let metadata;
30424
30721
  try {
@@ -30431,7 +30728,7 @@ async function validateEvidenceFile(repoRoot, relativePath3, allowedPaths, appro
30431
30728
  taskId
30432
30729
  );
30433
30730
  }
30434
- if (!metadata.isFile() || metadata.isSymbolicLink() || !resolvedPath.startsWith(`${repositoryRoot}${path24.sep}`)) {
30731
+ if (!metadata.isFile() || metadata.isSymbolicLink() || !resolvedPath.startsWith(`${repositoryRoot}${path25.sep}`)) {
30435
30732
  return completionFailure(
30436
30733
  "invalid_evidence_path",
30437
30734
  `${taskId} evidence must be a regular file inside the repository.`,
@@ -30533,7 +30830,7 @@ async function readRequiredReport(repoRoot, relativePath3, violation, requiredRe
30533
30830
  const normalized = normalizedRepositoryPath(relativePath3, requiredReports);
30534
30831
  if (relativePath3.startsWith(".banzai/"))
30535
30832
  await safePath3(repoRoot, relativePath3);
30536
- const absolute = path24.resolve(repoRoot, normalized);
30833
+ const absolute = path25.resolve(repoRoot, normalized);
30537
30834
  let bytes;
30538
30835
  try {
30539
30836
  const metadata = await lstat16(absolute);
@@ -30542,10 +30839,10 @@ async function readRequiredReport(repoRoot, relativePath3, violation, requiredRe
30542
30839
  }
30543
30840
  const root = await realpath10(repoRoot);
30544
30841
  const resolved = await realpath10(absolute);
30545
- if (!resolved.startsWith(`${root}${path24.sep}`)) {
30842
+ if (!resolved.startsWith(`${root}${path25.sep}`)) {
30546
30843
  throw new Error("outside repository");
30547
30844
  }
30548
- bytes = captured ?? await readFile10(absolute);
30845
+ bytes = captured ?? await readFile11(absolute);
30549
30846
  } catch {
30550
30847
  return completionFailure(
30551
30848
  violation,
@@ -30588,8 +30885,8 @@ async function validatePlanTaskCompletion(input) {
30588
30885
  "Approved artifact bundle digest or plan membership is invalid."
30589
30886
  );
30590
30887
  }
30591
- const plan = await readFile10(
30592
- path24.resolve(input.repoRoot, input.planPath),
30888
+ const plan = await readFile11(
30889
+ path25.resolve(input.repoRoot, input.planPath),
30593
30890
  "utf8"
30594
30891
  ).catch(
30595
30892
  () => completionFailure(
@@ -31024,8 +31321,8 @@ async function validateCompletionClaims(input) {
31024
31321
  "Approved artifact bundle digest or plan membership is invalid."
31025
31322
  );
31026
31323
  }
31027
- const plan = await readFile10(
31028
- path24.resolve(input.repoRoot, input.planPath),
31324
+ const plan = await readFile11(
31325
+ path25.resolve(input.repoRoot, input.planPath),
31029
31326
  "utf8"
31030
31327
  ).catch(
31031
31328
  () => completionFailure(
@@ -31176,8 +31473,8 @@ function completionPlanPath(state) {
31176
31473
  return planPath;
31177
31474
  }
31178
31475
  async function approvedTaskIds(context, state) {
31179
- const plan = await readFile11(
31180
- path25.join(context.repoRoot, completionPlanPath(state)),
31476
+ const plan = await readFile12(
31477
+ path26.join(context.repoRoot, completionPlanPath(state)),
31181
31478
  "utf8"
31182
31479
  );
31183
31480
  validateRunnerControlTasks(context, state, plan);
@@ -31213,8 +31510,8 @@ async function validateImplementationCompletion(context, state, attempt, result)
31213
31510
  async function validatePlanningResult(context, state, attempt, result) {
31214
31511
  if (result.gate !== "plan_approval" || result.artifacts.plan === null) return;
31215
31512
  try {
31216
- const markdown = await readFile11(
31217
- path25.join(context.repoRoot, result.artifacts.plan),
31513
+ const markdown = await readFile12(
31514
+ path26.join(context.repoRoot, result.artifacts.plan),
31218
31515
  "utf8"
31219
31516
  );
31220
31517
  parseApprovedPlanTaskIds(markdown);
@@ -31287,8 +31584,8 @@ function completionInvalidCheckpoint(context, sourceAttemptId, error, sourceAtte
31287
31584
  }
31288
31585
 
31289
31586
  // src/workflow/verification/required-checks.ts
31290
- import { readFile as readFile12 } from "node:fs/promises";
31291
- import path26 from "node:path";
31587
+ import { readFile as readFile13 } from "node:fs/promises";
31588
+ import path27 from "node:path";
31292
31589
  async function requiredCheckIds(context, state, config) {
31293
31590
  const ids = new Set(
31294
31591
  (taskVerificationPolicy(config)?.required ?? []).map((check2) => check2.id)
@@ -31296,8 +31593,8 @@ async function requiredCheckIds(context, state, config) {
31296
31593
  if ((state.workflowSelection?.workflow ?? "strict") !== "strict" || state.planApproval === null)
31297
31594
  return ids;
31298
31595
  for (const check2 of parseTaskVerificationObligations(
31299
- await readFile12(
31300
- path26.join(context.repoRoot, completionPlanPath(state)),
31596
+ await readFile13(
31597
+ path27.join(context.repoRoot, completionPlanPath(state)),
31301
31598
  "utf8"
31302
31599
  )
31303
31600
  ).checks)
@@ -31324,8 +31621,8 @@ import { createHash as createHash18 } from "node:crypto";
31324
31621
 
31325
31622
  // src/review-artifact.ts
31326
31623
  import { createHash as createHash17, randomUUID as randomUUID10 } from "node:crypto";
31327
- import { link as link3, mkdir as mkdir8, open as open11, readFile as readFile13, rename as rename7, unlink as unlink7 } from "node:fs/promises";
31328
- import path27 from "node:path";
31624
+ import { link as link3, mkdir as mkdir8, open as open11, readFile as readFile14, rename as rename7, unlink as unlink7 } from "node:fs/promises";
31625
+ import path28 from "node:path";
31329
31626
  var REQUIRED_SECTIONS = [
31330
31627
  "## Review Scope",
31331
31628
  "## Summary",
@@ -31348,7 +31645,7 @@ var ReviewArtifactValidationError = class extends BanzaiError {
31348
31645
  function reviewArtifactPath(slug, iteration, bound) {
31349
31646
  if (bound !== void 0)
31350
31647
  return `${bound.binding.artifacts.reviewDirectory}/review-${iteration}.md`;
31351
- return path27.posix.join(
31648
+ return path28.posix.join(
31352
31649
  "documentation",
31353
31650
  "features",
31354
31651
  slug,
@@ -31614,9 +31911,9 @@ function approvedArtifactScope(contents, diagnostics) {
31614
31911
  return paths;
31615
31912
  }
31616
31913
  function repositoryPath(repoRoot, relativePath3) {
31617
- const repositoryRoot = path27.resolve(repoRoot);
31618
- const absolutePath = path27.resolve(repositoryRoot, relativePath3);
31619
- if (!absolutePath.startsWith(`${repositoryRoot}${path27.sep}`)) {
31914
+ const repositoryRoot = path28.resolve(repoRoot);
31915
+ const absolutePath = path28.resolve(repositoryRoot, relativePath3);
31916
+ if (!absolutePath.startsWith(`${repositoryRoot}${path28.sep}`)) {
31620
31917
  throw new BanzaiError(
31621
31918
  "INVALID_REVIEW_ARTIFACT_PATH",
31622
31919
  "Review artifact path escapes the repository."
@@ -31650,7 +31947,7 @@ async function validateReviewArtifactSource(repoRoot, result, context, sourcePat
31650
31947
  "Review candidate path must differ from the canonical artifact path."
31651
31948
  );
31652
31949
  }
31653
- const contents = await readFile13(absoluteCandidatePath, "utf8").catch(() => {
31950
+ const contents = await readFile14(absoluteCandidatePath, "utf8").catch(() => {
31654
31951
  throw new BanzaiError(
31655
31952
  "REVIEW_ARTIFACT_MISSING",
31656
31953
  `Reviewer did not write candidate ${sourcePath3}.`
@@ -31879,7 +32176,7 @@ async function publishReviewArtifact(repoRoot, context, candidate) {
31879
32176
  const absolutePath = repositoryPath(repoRoot, context.path);
31880
32177
  if (context.snapshot !== void 0) {
31881
32178
  await assertReviewPathOwned(repoRoot, context.path, true);
31882
- const previous = await readFile13(absolutePath, "utf8").catch(
32179
+ const previous = await readFile14(absolutePath, "utf8").catch(
31883
32180
  (error) => {
31884
32181
  if (error.code === "ENOENT") return null;
31885
32182
  throw error;
@@ -31901,11 +32198,11 @@ async function publishReviewArtifact(repoRoot, context, candidate) {
31901
32198
  if (privateWork)
31902
32199
  await verifyIgnores4(repoRoot, binding, [
31903
32200
  context.path,
31904
- path27.relative(repoRoot, temporaryPath)
32201
+ path28.relative(repoRoot, temporaryPath)
31905
32202
  ]);
31906
32203
  };
31907
32204
  await verifyDestinations();
31908
- await mkdir8(path27.dirname(absolutePath), {
32205
+ await mkdir8(path28.dirname(absolutePath), {
31909
32206
  recursive: true,
31910
32207
  mode: privateWork ? 448 : 493
31911
32208
  });
@@ -32025,7 +32322,7 @@ function artifactWritePaths(state, phase) {
32025
32322
  }
32026
32323
 
32027
32324
  // src/workflow/attempts/lifecycle.ts
32028
- import path28 from "node:path";
32325
+ import path29 from "node:path";
32029
32326
  function newAttempt(context, state, kind, mutable) {
32030
32327
  return {
32031
32328
  id: `${kind}-${state.reviewIteration + 1}-${context.ids().slice(0, 8)}`,
@@ -32359,7 +32656,7 @@ async function persistAdapterResult(context, state, record7, adapterResult) {
32359
32656
  await context.store.writeRunAttempt(state.runId, record7);
32360
32657
  }
32361
32658
  async function writeAttemptOutput(context, state, record7, stdout, stderr) {
32362
- const stdoutPath = path28.relative(
32659
+ const stdoutPath = path29.relative(
32363
32660
  context.store.runDirectory(state.runId),
32364
32661
  await context.store.writeText(
32365
32662
  state.runId,
@@ -32367,7 +32664,7 @@ async function writeAttemptOutput(context, state, record7, stdout, stderr) {
32367
32664
  redactSensitiveText(stdout)
32368
32665
  )
32369
32666
  );
32370
- const stderrPath = path28.relative(
32667
+ const stderrPath = path29.relative(
32371
32668
  context.store.runDirectory(state.runId),
32372
32669
  await context.store.writeText(
32373
32670
  state.runId,
@@ -32419,7 +32716,7 @@ function legacyPhaseResult(result) {
32419
32716
  }
32420
32717
 
32421
32718
  // src/workflow/checkpoints/terminal-checkpoints.ts
32422
- import { lstat as lstat17, readFile as readFile14 } from "node:fs/promises";
32719
+ import { lstat as lstat17, readFile as readFile15 } from "node:fs/promises";
32423
32720
  function terminalCheckpointWriter(context, state, attempt, settings, policy, completion = false) {
32424
32721
  return async (terminal) => {
32425
32722
  const current = await context.store.readState(state.runId);
@@ -32762,7 +33059,7 @@ async function establishResultSourceIdentities(context, state, attempt, result,
32762
33059
  const file = await taskVerificationPath(context.repoRoot, relativePath3);
32763
33060
  const size = (await lstat17(file)).size;
32764
33061
  return size > TASK_VERIFICATION_SOURCE_MAX_BYTES ? { observed: null, size, limit: TASK_VERIFICATION_SOURCE_MAX_BYTES } : {
32765
- observed: evidenceDigest(await readFile14(file)),
33062
+ observed: evidenceDigest(await readFile15(file)),
32766
33063
  size,
32767
33064
  limit: null
32768
33065
  };
@@ -33144,7 +33441,7 @@ async function completionRepairDiagnostic(context, state, sourceAttemptId, sourc
33144
33441
  // src/workflow/completion/publication.ts
33145
33442
  import { createHash as createHash20 } from "node:crypto";
33146
33443
  import { lstat as lstat18, rename as rename8, unlink as unlink8, writeFile as writeFile3 } from "node:fs/promises";
33147
- import path29 from "node:path";
33444
+ import path30 from "node:path";
33148
33445
  async function storeCursor(context, state, cursor) {
33149
33446
  state.completionPublication = cursor;
33150
33447
  try {
@@ -33555,7 +33852,7 @@ async function publishCapturedCompletion(context, state, record7, envelope, capt
33555
33852
  const current = await context.store.readState(state.runId);
33556
33853
  if (current.status === "cancelled")
33557
33854
  return { kind: "stopped", state: current };
33558
- const destination = path29.join(context.repoRoot, candidate.destination);
33855
+ const destination = path30.join(context.repoRoot, candidate.destination);
33559
33856
  if (!published) {
33560
33857
  if (candidate.destination.startsWith(".banzai/"))
33561
33858
  await safePath3(context.repoRoot, candidate.destination);
@@ -33964,8 +34261,8 @@ async function blockForCompletionFailure(context, state, sourceAttemptId, error)
33964
34261
  }
33965
34262
 
33966
34263
  // src/workflow/verification/evidence.ts
33967
- import { readFile as readFile15 } from "node:fs/promises";
33968
- import path30 from "node:path";
34264
+ import { readFile as readFile16 } from "node:fs/promises";
34265
+ import path31 from "node:path";
33969
34266
  function verificationStorage(context, state) {
33970
34267
  if (!usesVerificationPolicy(state) || !state.artifactBinding)
33971
34268
  throw new BanzaiError(
@@ -34011,8 +34308,8 @@ async function prepareVerificationDecision(context, state, config, round, record
34011
34308
  const storage2 = verificationStorage(context, state);
34012
34309
  const configuration = parseVerificationConfiguration(config.verification);
34013
34310
  const strict = (state.workflowSelection?.workflow ?? "strict") === "strict";
34014
- const plan = strict ? await readFile15(
34015
- path30.join(context.repoRoot, completionPlanPath(state)),
34311
+ const plan = strict ? await readFile16(
34312
+ path31.join(context.repoRoot, completionPlanPath(state)),
34016
34313
  "utf8"
34017
34314
  ) : null;
34018
34315
  const current = await captureVerification(context, state, config);
@@ -34231,261 +34528,6 @@ var InvocationScopeCache = class {
34231
34528
  }
34232
34529
  };
34233
34530
 
34234
- // src/claude-permissions.ts
34235
- import { readFile as readFile16 } from "node:fs/promises";
34236
- import { homedir as homedir3 } from "node:os";
34237
- import path31 from "node:path";
34238
- var ALL_SOURCES = [
34239
- "user",
34240
- "project",
34241
- "local"
34242
- ];
34243
- var MANAGED_SETTINGS_FILES = {
34244
- darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
34245
- linux: "/etc/claude-code/managed-settings.json"
34246
- };
34247
- var EMPTY_RULES = { allow: [], deny: [], ask: [] };
34248
- function isRecord3(value) {
34249
- return typeof value === "object" && value !== null && !Array.isArray(value);
34250
- }
34251
- function stringArray2(value) {
34252
- return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
34253
- }
34254
- function ruleTool(rule) {
34255
- const open14 = rule.indexOf("(");
34256
- return (open14 === -1 ? rule : rule.slice(0, open14)).trim();
34257
- }
34258
- function namesTool(rule, tool) {
34259
- return ruleTool(rule) === tool;
34260
- }
34261
- function isUnscoped(rule) {
34262
- return !rule.includes("(");
34263
- }
34264
- function homeDirectory(env) {
34265
- const configured = env.HOME;
34266
- return configured === void 0 || configured.length === 0 ? homedir3() : configured;
34267
- }
34268
- function configDirectory(env) {
34269
- const configured = env.CLAUDE_CONFIG_DIR;
34270
- return configured === void 0 || configured.length === 0 ? null : configured;
34271
- }
34272
- function trustFile(env) {
34273
- const configured = configDirectory(env);
34274
- return configured === null ? path31.join(homeDirectory(env), ".claude.json") : path31.join(configured, ".claude.json");
34275
- }
34276
- function settingsFile(source, repoRoot, env) {
34277
- if (source === "user") {
34278
- return path31.join(
34279
- configDirectory(env) ?? path31.join(homeDirectory(env), ".claude"),
34280
- "settings.json"
34281
- );
34282
- }
34283
- return path31.join(
34284
- repoRoot,
34285
- ".claude",
34286
- source === "project" ? "settings.json" : "settings.local.json"
34287
- );
34288
- }
34289
- async function readRules(filePath) {
34290
- let raw;
34291
- try {
34292
- raw = await readFile16(filePath, "utf8");
34293
- } catch {
34294
- return { rules: EMPTY_RULES, intact: true };
34295
- }
34296
- try {
34297
- const document = JSON.parse(raw);
34298
- const permissions = isRecord3(document) ? document.permissions : void 0;
34299
- if (!isRecord3(permissions)) {
34300
- return { rules: EMPTY_RULES, intact: true };
34301
- }
34302
- return {
34303
- rules: {
34304
- allow: stringArray2(permissions.allow),
34305
- deny: stringArray2(permissions.deny),
34306
- ask: stringArray2(permissions.ask)
34307
- },
34308
- intact: true
34309
- };
34310
- } catch {
34311
- return { rules: EMPTY_RULES, intact: false };
34312
- }
34313
- }
34314
- async function readManagedRules(override) {
34315
- const file = override === void 0 ? MANAGED_SETTINGS_FILES[process.platform] : override;
34316
- return file === void 0 || file === null ? { rules: EMPTY_RULES, intact: true } : await readRules(file);
34317
- }
34318
- async function inspectTrust(repoRoot, env) {
34319
- let raw;
34320
- try {
34321
- raw = await readFile16(trustFile(env), "utf8");
34322
- } catch {
34323
- return "unknown";
34324
- }
34325
- try {
34326
- const document = JSON.parse(raw);
34327
- const projects = isRecord3(document) ? document.projects : void 0;
34328
- if (!isRecord3(projects)) {
34329
- return "untrusted";
34330
- }
34331
- const entry = projects[repoRoot];
34332
- return isRecord3(entry) && entry.hasTrustDialogAccepted === true ? "trusted" : "untrusted";
34333
- } catch {
34334
- return "unknown";
34335
- }
34336
- }
34337
- function resolveGrant(tool, sets) {
34338
- for (const set of sets) {
34339
- const refused = [...set.deny, ...set.ask];
34340
- if (refused.some((rule) => namesTool(rule, tool) && isUnscoped(rule))) {
34341
- return "denied";
34342
- }
34343
- }
34344
- let scoped = false;
34345
- for (const set of sets) {
34346
- for (const rule of set.allow) {
34347
- if (!namesTool(rule, tool)) {
34348
- continue;
34349
- }
34350
- if (isUnscoped(rule)) {
34351
- return "granted";
34352
- }
34353
- scoped = true;
34354
- }
34355
- }
34356
- return scoped ? "scoped" : "missing";
34357
- }
34358
- function parseSources(settingSources) {
34359
- if (settingSources === null) {
34360
- return [...ALL_SOURCES];
34361
- }
34362
- const named = new Set(
34363
- settingSources.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0)
34364
- );
34365
- return ALL_SOURCES.filter((source) => named.has(source));
34366
- }
34367
- function claudePermissionDemand(config) {
34368
- const claudeRoles = new Set(
34369
- MODEL_ROLES.filter(
34370
- (role) => (vendorFor(config.models[role].executable) ?? FALLBACK_VENDOR[role]) === "claude"
34371
- )
34372
- );
34373
- return {
34374
- planningWrites: claudeRoles.has("planner"),
34375
- shellCommands: claudeRoles.has("planner") || claudeRoles.has("implementer")
34376
- };
34377
- }
34378
- function claudePermissionDemanded(demand) {
34379
- return demand.planningWrites || demand.shellCommands;
34380
- }
34381
- async function inspectClaudePermissions(input) {
34382
- const env = input.env ?? process.env;
34383
- const repoRoot = path31.resolve(input.repoRoot);
34384
- const demand = input.demand;
34385
- const sources = parseSources(input.settingSources);
34386
- const [managed, trust, loaded, unloadedLocal] = await Promise.all([
34387
- readManagedRules(input.managedSettingsFile),
34388
- inspectTrust(repoRoot, env),
34389
- Promise.all(
34390
- sources.map(async (source) => ({
34391
- source,
34392
- ...await readRules(settingsFile(source, repoRoot, env))
34393
- }))
34394
- ),
34395
- sources.includes("local") ? Promise.resolve(null) : readRules(settingsFile("local", repoRoot, env))
34396
- ]);
34397
- const honored = loaded.filter(
34398
- (entry) => entry.source === "user" || trust !== "untrusted"
34399
- );
34400
- const sets = [managed.rules, ...honored.map((entry) => entry.rules)];
34401
- const audited = [
34402
- ...demand.planningWrites ? ["Write", "Edit"] : [],
34403
- ...demand.shellCommands ? ["Bash"] : []
34404
- ];
34405
- const tools = audited.map((tool) => ({
34406
- tool,
34407
- grant: resolveGrant(tool, sets)
34408
- }));
34409
- const grantOf = (tool) => tools.find((entry) => entry.tool === tool)?.grant ?? null;
34410
- const blocking = [];
34411
- const advisories = [];
34412
- const write = grantOf("Write");
34413
- if (write === "denied") {
34414
- blocking.push(
34415
- "A deny or ask rule refuses Write, and `--permission-mode dontAsk` cannot escalate either one to a human."
34416
- );
34417
- }
34418
- if (write === "missing") {
34419
- blocking.push(
34420
- "No loaded permission rule grants Write, so a Claude planning phase is denied the description, plan, and decision artifacts it exists to produce."
34421
- );
34422
- }
34423
- if ((write === "missing" || write === "denied") && trust === "untrusted" && sources.some((source) => source !== "user")) {
34424
- blocking.push(
34425
- "Claude Code has not been trusted in this checkout, so it ignores the repository's own permission rules."
34426
- );
34427
- }
34428
- if ((write === "missing" || write === "denied") && unloadedLocal !== null && unloadedLocal.rules.allow.length > 0) {
34429
- advisories.push(
34430
- "Local settings hold allow rules the scoped invocation does not load; repository grants belong in the tracked project settings."
34431
- );
34432
- }
34433
- if (write === "scoped") {
34434
- advisories.push(
34435
- "Every rule granting Write is path-scoped, so a planning artifact outside those paths is still denied."
34436
- );
34437
- }
34438
- const edit = grantOf("Edit");
34439
- if (edit === "missing" || edit === "denied") {
34440
- advisories.push(
34441
- "No loaded permission rule grants Edit, so a planning phase can create artifacts but not revise them."
34442
- );
34443
- }
34444
- const bash = grantOf("Bash");
34445
- if (bash === "missing" || bash === "denied") {
34446
- advisories.push(
34447
- "No loaded permission rule grants Bash, so every command a phase runs is denied."
34448
- );
34449
- } else if (bash === "scoped") {
34450
- advisories.push(
34451
- "Bash is granted only for the commands its scoped rules name; anything else a phase runs is denied."
34452
- );
34453
- }
34454
- if (trust === "unknown") {
34455
- advisories.push(
34456
- "Claude Code's workspace trust record could not be read, so repository rules were treated as honored."
34457
- );
34458
- }
34459
- if (!managed.intact || loaded.some((entry) => !entry.intact)) {
34460
- advisories.push(
34461
- "A settings file could not be parsed, so its permission rules were treated as absent."
34462
- );
34463
- }
34464
- return { demand, sources, trust, tools, blocking, advisories };
34465
- }
34466
- function claudePermissionRemediation(audit) {
34467
- const steps = [];
34468
- if (audit.trust === "untrusted" && audit.sources.includes("project")) {
34469
- steps.push(
34470
- "run `claude` once in this checkout and accept the trust prompt"
34471
- );
34472
- }
34473
- const unresolved = audit.tools.filter(
34474
- (entry) => entry.tool !== "Bash" && (entry.grant === "missing" || entry.grant === "denied")
34475
- );
34476
- if (unresolved.length > 0) {
34477
- steps.push(
34478
- `add ${unresolved.map((entry) => `"${entry.tool}"`).join(" and ")} to \`permissions.allow\` in \`.claude/settings.json\``
34479
- );
34480
- if (!audit.sources.includes("local")) {
34481
- steps.push(
34482
- "keep the grants there rather than in local settings, the one repository source a scoped invocation never loads"
34483
- );
34484
- }
34485
- }
34486
- return steps.join("; ");
34487
- }
34488
-
34489
34531
  // src/context-accounting.ts
34490
34532
  var PHASES = [
34491
34533
  "selection",
@@ -46368,8 +46410,15 @@ async function claudePermissionsCheck(repoRoot, config, dependencies) {
46368
46410
  "Claude permission inspection requires a valid configuration."
46369
46411
  );
46370
46412
  }
46371
- const demand = claudePermissionDemand(config);
46372
- if (!claudePermissionDemanded(demand)) {
46413
+ const permissions = await inspectConfiguredClaudePermissions(
46414
+ repoRoot,
46415
+ config,
46416
+ {
46417
+ resolveScope: async (settings) => await (dependencies.resolveClaudeInvocationScope ?? resolveClaudeInvocationScope)(settings),
46418
+ inspect: async (input) => await (dependencies.inspectClaudePermissions ?? inspectClaudePermissions)(input)
46419
+ }
46420
+ );
46421
+ if (permissions.state === "not_demanded") {
46373
46422
  return check(
46374
46423
  "claude.permissions",
46375
46424
  "config",
@@ -46377,11 +46426,7 @@ async function claudePermissionsCheck(repoRoot, config, dependencies) {
46377
46426
  "No configured role runs a Claude phase that depends on repository permission rules."
46378
46427
  );
46379
46428
  }
46380
- let settingSources;
46381
- try {
46382
- const resolved = await (dependencies.resolveClaudeInvocationScope ?? resolveClaudeInvocationScope)(config.claudeEnvironment);
46383
- settingSources = resolved.scope.settingSources;
46384
- } catch {
46429
+ if (permissions.state === "unresolved") {
46385
46430
  return check(
46386
46431
  "claude.permissions",
46387
46432
  "config",
@@ -46389,13 +46434,21 @@ async function claudePermissionsCheck(repoRoot, config, dependencies) {
46389
46434
  "Claude permission inspection requires a verifiable invocation environment."
46390
46435
  );
46391
46436
  }
46392
- const audit = await (dependencies.inspectClaudePermissions ?? inspectClaudePermissions)({ repoRoot, demand, settingSources });
46437
+ const audit = permissions.audit;
46438
+ const remediation = claudePermissionRemediation(audit);
46439
+ const fixes = remediation.length === 0 ? [] : [
46440
+ {
46441
+ action: `${remediation.charAt(0).toUpperCase()}${remediation.slice(1)}.`,
46442
+ documentation: "documentation/orchestrator.md#claude-tool-permissions"
46443
+ }
46444
+ ];
46393
46445
  if (audit.blocking.length > 0) {
46394
46446
  return check(
46395
46447
  "claude.permissions",
46396
46448
  "config",
46397
46449
  "fail",
46398
- audit.blocking.join(" ")
46450
+ audit.blocking.join(" "),
46451
+ fixes
46399
46452
  );
46400
46453
  }
46401
46454
  if (audit.advisories.length > 0) {
@@ -46403,7 +46456,8 @@ async function claudePermissionsCheck(repoRoot, config, dependencies) {
46403
46456
  "claude.permissions",
46404
46457
  "config",
46405
46458
  "warning",
46406
- audit.advisories.join(" ")
46459
+ audit.advisories.join(" "),
46460
+ fixes
46407
46461
  );
46408
46462
  }
46409
46463
  return check(
@@ -46810,7 +46864,7 @@ async function inspectDoctor(repoRoot, dependencies = DEFAULT_DEPENDENCIES) {
46810
46864
  "authentication.models",
46811
46865
  "executables",
46812
46866
  "warning",
46813
- "Model authentication readiness was not checked. Doctor is offline and executable availability is not authentication proof; startup preflight remains authoritative. Run Banzai from a regular terminal, restore vendor login, or use the vendor-supported setup-token flow when unattended authentication is required."
46867
+ "Model authentication readiness and vendor CLI versions were not checked. Doctor is offline and executable availability is not authentication proof; startup preflight remains authoritative. Run Banzai from a regular terminal, restore vendor login, or use the vendor-supported setup-token flow when unattended authentication is required."
46814
46868
  ),
46815
46869
  await activeRunCheck(repoRoot),
46816
46870
  await runnerLockCheck(repoRoot, dependencies)
@@ -51746,9 +51800,9 @@ async function prepareSetupLaunch(client, repoRoot, config, sourceEnvironment =
51746
51800
  );
51747
51801
  const env = setupEnvironment(sourceEnvironment);
51748
51802
  const adapters = dependencies.adapters ?? createVendorAdapters(runner);
51749
- if (client.vendor === "codex")
51750
- await adapters.preflightCodex(fresh.settings, env);
51751
- else await adapters.preflightClaude(fresh.settings, env);
51803
+ const target = { executable: fresh.settings.executable, model: null };
51804
+ if (client.vendor === "codex") await adapters.preflightCodex(target, env);
51805
+ else await adapters.preflightClaude(target, env);
51752
51806
  const mcp = await resolveSerenaWhitelist({
51753
51807
  repoRoot,
51754
51808
  codexExecutable: client.vendor === "codex" ? fresh.settings.executable : null,
@@ -52043,6 +52097,20 @@ function authenticationGateway(adapters) {
52043
52097
  }
52044
52098
  };
52045
52099
  }
52100
+ async function reportClaudePermissionGaps(repoRoot, config) {
52101
+ const permissions = await inspectConfiguredClaudePermissions(
52102
+ repoRoot,
52103
+ config,
52104
+ { resolveScope: resolveClaudeInvocationScope }
52105
+ );
52106
+ if (permissions.state !== "audited" || permissions.audit.blocking.length === 0) {
52107
+ return;
52108
+ }
52109
+ console.log(`Claude permissions: ${permissions.audit.blocking.join(" ")}`);
52110
+ console.log(
52111
+ `Fix it before the first run: ${claudePermissionRemediation(permissions.audit)}.`
52112
+ );
52113
+ }
52046
52114
  function modelConfigurationGateway(repoRoot) {
52047
52115
  return {
52048
52116
  async load() {
@@ -52610,6 +52678,7 @@ async function main() {
52610
52678
  console.log(
52611
52679
  "Verification follows each task; no profile setup is required."
52612
52680
  );
52681
+ await reportClaudePermissionGaps(repoRoot, config);
52613
52682
  console.log(
52614
52683
  "Run banzai doctor, then review and commit your files before starting a task."
52615
52684
  );
@@ -52645,11 +52714,14 @@ async function main() {
52645
52714
  "TTY_REQUIRED",
52646
52715
  `banzai setup requires terminal input and output. Open your client in this project and explicitly invoke banzai:project-manifest, or rerun banzai setup in a terminal. ${SETUP_PLUGIN_PREREQUISITE}`
52647
52716
  );
52717
+ const config = await loadConfig(repoRoot);
52648
52718
  process7.exitCode = await runProjectSetup(
52649
52719
  repoRoot,
52650
- await loadConfig(repoRoot),
52720
+ config,
52651
52721
  new InquirerInteractiveIO()
52652
52722
  );
52723
+ if (process7.exitCode === 0)
52724
+ await reportClaudePermissionGaps(repoRoot, config);
52653
52725
  return;
52654
52726
  }
52655
52727
  if (mode2 === "interactive") {