@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/README.md +2 -1
- package/dist/banzai.mjs +431 -359
- package/dist/banzai.mjs.map +4 -4
- package/package.json +1 -1
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.
|
|
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,
|
|
27066
|
+
async function requireVersion(runner, vendor, target, sourceEnvironment = process.env) {
|
|
27032
27067
|
const result = await runner({
|
|
27033
|
-
command:
|
|
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
|
-
`${
|
|
27077
|
+
`${target.executable} --version failed: ${result.stderr.trim()}`
|
|
27043
27078
|
);
|
|
27044
27079
|
}
|
|
27045
27080
|
const actual = parseVersion(
|
|
27046
27081
|
result.stdout || result.stderr,
|
|
27047
|
-
|
|
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
|
-
`${
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
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
|
|
27966
|
-
import
|
|
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
|
|
27971
|
-
import
|
|
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 ||
|
|
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 =
|
|
28050
|
-
const candidate =
|
|
28051
|
-
if (!candidate.startsWith(`${normalizedRoot}${
|
|
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
|
|
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 =
|
|
28544
|
+
let current = path21.resolve(repoRoot);
|
|
28248
28545
|
const segments = relativePath3.split("/");
|
|
28249
28546
|
for (const [index, part] of segments.entries()) {
|
|
28250
|
-
current =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
29939
|
-
import
|
|
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 =
|
|
29979
|
-
if (relativePath3.length === 0 || relativePath3.includes("\\") ||
|
|
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}${
|
|
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
|
|
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
|
|
30029
|
-
import
|
|
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
|
|
30034
|
-
import
|
|
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) =>
|
|
30432
|
+
(value) => path25.posix.normalize(value.replaceAll("\\", "/"))
|
|
30136
30433
|
)
|
|
30137
30434
|
);
|
|
30138
|
-
const root =
|
|
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 =
|
|
30150
|
-
|
|
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 ||
|
|
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 =
|
|
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}${
|
|
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 =
|
|
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}${
|
|
30842
|
+
if (!resolved.startsWith(`${root}${path25.sep}`)) {
|
|
30546
30843
|
throw new Error("outside repository");
|
|
30547
30844
|
}
|
|
30548
|
-
bytes = captured ?? await
|
|
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
|
|
30592
|
-
|
|
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
|
|
31028
|
-
|
|
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
|
|
31180
|
-
|
|
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
|
|
31217
|
-
|
|
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
|
|
31291
|
-
import
|
|
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
|
|
31300
|
-
|
|
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
|
|
31328
|
-
import
|
|
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
|
|
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 =
|
|
31618
|
-
const absolutePath =
|
|
31619
|
-
if (!absolutePath.startsWith(`${repositoryRoot}${
|
|
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
|
|
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
|
|
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
|
-
|
|
32201
|
+
path28.relative(repoRoot, temporaryPath)
|
|
31905
32202
|
]);
|
|
31906
32203
|
};
|
|
31907
32204
|
await verifyDestinations();
|
|
31908
|
-
await mkdir8(
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
33968
|
-
import
|
|
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
|
|
34015
|
-
|
|
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
|
|
46372
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
51750
|
-
|
|
51751
|
-
else await adapters.preflightClaude(
|
|
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
|
-
|
|
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") {
|