@guilz-dev/belay 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +39 -12
  2. package/dist/adapters/shared/gate-runtime.js +13 -5
  3. package/dist/bundle/claude-runtime.mjs +1659 -371
  4. package/dist/bundle/codex-runtime.mjs +1658 -371
  5. package/dist/bundle/cursor-runtime.mjs +1659 -371
  6. package/dist/cli.js +198 -8
  7. package/dist/commands/approve.js +11 -0
  8. package/dist/commands/config.d.ts +70 -0
  9. package/dist/commands/config.js +494 -0
  10. package/dist/commands/doctor.js +2 -1
  11. package/dist/commands/health-snapshot.d.ts +6 -0
  12. package/dist/commands/health-snapshot.js +34 -0
  13. package/dist/commands/judge.d.ts +90 -0
  14. package/dist/commands/judge.js +285 -0
  15. package/dist/commands/status.js +2 -2
  16. package/dist/commands/stdin-key.d.ts +1 -0
  17. package/dist/commands/stdin-key.js +8 -0
  18. package/dist/conformance/guarantee-table.js +12 -0
  19. package/dist/conformance/types.d.ts +2 -0
  20. package/dist/core/audit-io.d.ts +2 -0
  21. package/dist/core/audit-io.js +14 -0
  22. package/dist/core/capability/index.d.ts +2 -2
  23. package/dist/core/capability/index.js +2 -2
  24. package/dist/core/capability/paths.d.ts +1 -0
  25. package/dist/core/capability/paths.js +74 -6
  26. package/dist/core/capability/reasons.d.ts +3 -0
  27. package/dist/core/capability/reasons.js +8 -0
  28. package/dist/core/classify-tool.js +97 -27
  29. package/dist/core/config-layers.js +2 -1
  30. package/dist/core/config.d.ts +22 -2
  31. package/dist/core/config.js +110 -11
  32. package/dist/core/credential-store.d.ts +11 -0
  33. package/dist/core/credential-store.js +60 -0
  34. package/dist/core/gate-engine.js +104 -13
  35. package/dist/core/integrity.d.ts +2 -0
  36. package/dist/core/integrity.js +13 -0
  37. package/dist/core/judge-api-key.d.ts +19 -0
  38. package/dist/core/judge-api-key.js +74 -0
  39. package/dist/core/judge-cloud-consent.d.ts +13 -0
  40. package/dist/core/judge-cloud-consent.js +38 -0
  41. package/dist/core/judge-config.d.ts +41 -4
  42. package/dist/core/judge-config.js +263 -57
  43. package/dist/core/judge-doctor.d.ts +6 -1
  44. package/dist/core/judge-doctor.js +147 -96
  45. package/dist/core/judge-model-discovery.d.ts +24 -0
  46. package/dist/core/judge-model-discovery.js +168 -0
  47. package/dist/core/judge-model-policy.d.ts +5 -0
  48. package/dist/core/judge-model-policy.js +21 -0
  49. package/dist/core/judge-runtime-detection.d.ts +9 -0
  50. package/dist/core/judge-runtime-detection.js +68 -0
  51. package/dist/core/transactional/diff-evaluator.js +1 -19
  52. package/dist/core/types.d.ts +2 -0
  53. package/dist/core/verdict/adapter.d.ts +1 -0
  54. package/dist/core/verdict/adapter.js +7 -1
  55. package/dist/core/verdict/containment.d.ts +5 -0
  56. package/dist/core/verdict/containment.js +32 -2
  57. package/dist/core/verdict/judge-catalog.d.ts +40 -0
  58. package/dist/core/verdict/judge-catalog.js +148 -0
  59. package/dist/core/verdict/judge-cli.d.ts +23 -0
  60. package/dist/core/verdict/judge-cli.js +280 -0
  61. package/dist/core/verdict/judge-factory.d.ts +15 -4
  62. package/dist/core/verdict/judge-factory.js +117 -14
  63. package/dist/core/verdict/judge.d.ts +20 -1
  64. package/dist/core/verdict/judge.js +83 -14
  65. package/dist/core/verdict/persistent-paths.d.ts +8 -0
  66. package/dist/core/verdict/persistent-paths.js +52 -0
  67. package/dist/core/verdict/types.d.ts +6 -2
  68. package/dist/core/verdict/verdict.js +161 -47
  69. package/dist/installer.js +66 -15
  70. package/dist/types.d.ts +7 -0
  71. package/dist/version.d.ts +1 -1
  72. package/dist/version.js +1 -1
  73. package/package.json +1 -1
  74. package/skills/belay/SKILL.md +8 -7
  75. package/dist/commands/init-wizard.d.ts +0 -21
  76. package/dist/commands/init-wizard.js +0 -63
@@ -1,6 +1,8 @@
1
1
  import path from 'node:path';
2
2
  import { matchesSensitivePath } from '../glob.js';
3
3
  import { canonicalPath, pathWithinRoot, relativeWithinRepo, resolveMutationTarget, } from '../path-utils.js';
4
+ import { isOutsideRepoSecretCredentialPath } from './persistent-paths.js';
5
+ export { isOutsideRepoSecretCredentialPath, isPersistentAgentPath } from './persistent-paths.js';
4
6
  function expandHome(token) {
5
7
  if (token === '~' || token.startsWith('~/')) {
6
8
  const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
@@ -46,12 +48,40 @@ export function isHighStakesPath(resolvedPath, repoRoot, sensitivePaths, protect
46
48
  return true;
47
49
  }
48
50
  const relative = relativeWithinRepo(repoRoot, resolvedPath);
49
- const checkPath = relative ?? resolvedPath;
50
- if (matchesSensitivePath(checkPath.replaceAll('\\', '/'), sensitivePaths)) {
51
+ if (relative !== null && matchesSensitivePath(relative.replaceAll('\\', '/'), sensitivePaths)) {
52
+ return true;
53
+ }
54
+ if (relative === null && isOutsideRepoSecretCredentialPath(resolvedPath)) {
51
55
  return true;
52
56
  }
53
57
  return protectedRoots.some((root) => pathWithinRoot(root, resolvedPath));
54
58
  }
59
+ const DESTRUCTIVE_MUTATION_HEADS = new Set([
60
+ 'rm',
61
+ 'shred',
62
+ 'truncate',
63
+ 'dd',
64
+ 'chmod',
65
+ 'chown',
66
+ 'mv',
67
+ 'cp',
68
+ ]);
69
+ /** ADR-002 M3: destructive shell head on git/sensitive path (not broad category ask). */
70
+ export function isDestructiveMutationHead(head) {
71
+ return DESTRUCTIVE_MUTATION_HEADS.has(head);
72
+ }
73
+ export function touchesProtectedRoot(resolvedPath, protectedRoots) {
74
+ return protectedRoots.some((root) => pathWithinRoot(root, resolvedPath) || root === resolvedPath);
75
+ }
76
+ export function isDestructiveHighStakesMutation(head, resolvedPath, repoRoot, sensitivePaths, protectedRoots = []) {
77
+ if (touchesProtectedRoot(resolvedPath, protectedRoots)) {
78
+ return true;
79
+ }
80
+ if (!isDestructiveMutationHead(head)) {
81
+ return false;
82
+ }
83
+ return isHighStakesPath(resolvedPath, repoRoot, sensitivePaths, []);
84
+ }
55
85
  export function analyzePathTargets(params) {
56
86
  const signals = [];
57
87
  if (!params.trustedCwd || !params.cwd) {
@@ -0,0 +1,40 @@
1
+ import type { BelayJudgeConfig, JudgeProvider } from '../config.js';
2
+ export type JudgeProviderId = 'ollama' | 'codex' | 'claude' | 'cursor';
3
+ /** Read-time aliases for legacy configs; never written on fresh init. */
4
+ export declare const LEGACY_PROVIDER_ID_ALIASES: Record<string, JudgeProviderId>;
5
+ /** Removed catalog ids; load emits a warning and best-effort canonical mapping. */
6
+ export declare const REMOVED_JUDGE_PROVIDER_IDS: readonly ["openrouter", "custom"];
7
+ export declare function isRemovedProviderId(id: string): boolean;
8
+ export declare function warnRemovedProviderId(id: string): void;
9
+ export interface JudgeProviderSpec {
10
+ id: JudgeProviderId;
11
+ driver: JudgeProvider;
12
+ defaultEndpoint: string | null;
13
+ defaultModel: string;
14
+ apiKeyEnvVars: string[];
15
+ isCloud: boolean;
16
+ }
17
+ export declare const JUDGE_CATALOG: Record<JudgeProviderId, JudgeProviderSpec>;
18
+ export declare const JUDGE_PROVIDER_IDS: JudgeProviderId[];
19
+ export declare function normalizeLegacyProviderId(id: string): JudgeProviderId | null;
20
+ export declare function getJudgeProviderSpec(id: string): JudgeProviderSpec | null;
21
+ export declare function isJudgeProviderId(value: string): boolean;
22
+ export declare function inferProviderIdFromConfig(judge: Partial<BelayJudgeConfig>): JudgeProviderId;
23
+ export interface ResolveJudgeFromCatalogInput {
24
+ providerId: JudgeProviderId | string;
25
+ model?: string;
26
+ endpoint?: string | null;
27
+ timeoutMs?: number;
28
+ keepAlive?: string | null;
29
+ }
30
+ export declare function resolveJudgeFromCatalog(input: ResolveJudgeFromCatalogInput): BelayJudgeConfig;
31
+ export interface JudgeProviderCapabilities {
32
+ requiresConsent: boolean;
33
+ requiresEndpoint: boolean;
34
+ credentialPolicy: Array<'project' | 'apiKey'>;
35
+ }
36
+ export declare function catalogRequiresEndpoint(providerId: JudgeProviderId | string, opts?: {
37
+ transport?: string;
38
+ }): boolean;
39
+ export declare function getJudgeProviderCapabilities(providerId: JudgeProviderId | string): JudgeProviderCapabilities | null;
40
+ export declare function isCloudProviderId(providerId: JudgeProviderId | string | undefined): boolean;
@@ -0,0 +1,148 @@
1
+ /** Read-time aliases for legacy configs; never written on fresh init. */
2
+ export const LEGACY_PROVIDER_ID_ALIASES = {
3
+ local: 'ollama',
4
+ openai: 'codex',
5
+ };
6
+ /** Removed catalog ids; load emits a warning and best-effort canonical mapping. */
7
+ export const REMOVED_JUDGE_PROVIDER_IDS = ['openrouter', 'custom'];
8
+ export function isRemovedProviderId(id) {
9
+ return REMOVED_JUDGE_PROVIDER_IDS.includes(id);
10
+ }
11
+ export function warnRemovedProviderId(id) {
12
+ process.stderr.write(`Warning: judge.providerId "${id}" was removed; migrate with belay config set judge.providerId <ollama|codex|claude|cursor>.\n`);
13
+ }
14
+ export const JUDGE_CATALOG = {
15
+ ollama: {
16
+ id: 'ollama',
17
+ driver: 'ollama',
18
+ defaultEndpoint: 'http://localhost:11434',
19
+ defaultModel: 'gemma4:e2b',
20
+ apiKeyEnvVars: [],
21
+ isCloud: false,
22
+ },
23
+ codex: {
24
+ id: 'codex',
25
+ driver: 'openai-compatible',
26
+ defaultEndpoint: null,
27
+ defaultModel: 'gpt-5.3-codex-high',
28
+ apiKeyEnvVars: ['OPENAI_API_KEY', 'BELAY_JUDGE_API_KEY'],
29
+ isCloud: true,
30
+ },
31
+ claude: {
32
+ id: 'claude',
33
+ driver: 'anthropic',
34
+ defaultEndpoint: null,
35
+ defaultModel: 'claude-sonnet-4-6',
36
+ apiKeyEnvVars: ['ANTHROPIC_API_KEY', 'BELAY_JUDGE_API_KEY'],
37
+ isCloud: true,
38
+ },
39
+ cursor: {
40
+ id: 'cursor',
41
+ driver: 'openai-compatible',
42
+ defaultEndpoint: null,
43
+ defaultModel: 'composer-2.5',
44
+ apiKeyEnvVars: ['CURSOR_API_KEY', 'BELAY_JUDGE_API_KEY'],
45
+ isCloud: true,
46
+ },
47
+ };
48
+ export const JUDGE_PROVIDER_IDS = Object.keys(JUDGE_CATALOG);
49
+ export function normalizeLegacyProviderId(id) {
50
+ if (id in JUDGE_CATALOG) {
51
+ return id;
52
+ }
53
+ return LEGACY_PROVIDER_ID_ALIASES[id] ?? null;
54
+ }
55
+ export function getJudgeProviderSpec(id) {
56
+ const normalized = normalizeLegacyProviderId(id);
57
+ if (!normalized) {
58
+ return null;
59
+ }
60
+ return JUDGE_CATALOG[normalized];
61
+ }
62
+ export function isJudgeProviderId(value) {
63
+ return normalizeLegacyProviderId(value) !== null;
64
+ }
65
+ export function inferProviderIdFromConfig(judge) {
66
+ if (judge.providerId) {
67
+ const normalized = normalizeLegacyProviderId(judge.providerId);
68
+ if (normalized) {
69
+ return normalized;
70
+ }
71
+ }
72
+ if (judge.provider === 'ollama') {
73
+ return 'ollama';
74
+ }
75
+ if (judge.provider === 'anthropic') {
76
+ return 'claude';
77
+ }
78
+ const endpoint = judge.endpoint?.trim().toLowerCase() ?? '';
79
+ if (endpoint.includes('openrouter.ai')) {
80
+ return 'codex';
81
+ }
82
+ if (endpoint.includes('api.openai.com')) {
83
+ return 'codex';
84
+ }
85
+ if (endpoint) {
86
+ return 'codex';
87
+ }
88
+ if (judge.provider === 'openai-compatible') {
89
+ const model = judge.model?.trim().toLowerCase() ?? '';
90
+ if (model.includes('composer')) {
91
+ return 'cursor';
92
+ }
93
+ }
94
+ return 'codex';
95
+ }
96
+ export function resolveJudgeFromCatalog(input) {
97
+ const providerId = normalizeLegacyProviderId(String(input.providerId));
98
+ if (!providerId) {
99
+ throw new Error(`Unknown judge provider id: ${input.providerId}`);
100
+ }
101
+ const spec = JUDGE_CATALOG[providerId];
102
+ const model = input.model?.trim() || spec.defaultModel;
103
+ const endpoint = input.endpoint !== undefined ? input.endpoint?.trim() || null : spec.defaultEndpoint;
104
+ return {
105
+ provider: spec.driver,
106
+ providerId: spec.id,
107
+ model,
108
+ endpoint,
109
+ timeoutMs: input.timeoutMs ?? (spec.isCloud ? 8000 : 25000),
110
+ keepAlive: spec.driver === 'ollama' ? (input.keepAlive ?? '30m') : null,
111
+ };
112
+ }
113
+ export function catalogRequiresEndpoint(providerId, opts) {
114
+ const normalized = normalizeLegacyProviderId(String(providerId));
115
+ if (!normalized || normalized === 'ollama') {
116
+ return false;
117
+ }
118
+ const transport = opts?.transport;
119
+ if (!transport) {
120
+ return false;
121
+ }
122
+ if (transport.endsWith('-cli') || transport === 'ollama-http') {
123
+ return false;
124
+ }
125
+ return transport === 'http';
126
+ }
127
+ export function getJudgeProviderCapabilities(providerId) {
128
+ const normalized = normalizeLegacyProviderId(String(providerId));
129
+ if (!normalized) {
130
+ return null;
131
+ }
132
+ const spec = JUDGE_CATALOG[normalized];
133
+ return {
134
+ requiresConsent: spec.isCloud,
135
+ requiresEndpoint: catalogRequiresEndpoint(normalized, { transport: 'http' }),
136
+ credentialPolicy: ['project', 'apiKey'],
137
+ };
138
+ }
139
+ export function isCloudProviderId(providerId) {
140
+ if (!providerId) {
141
+ return false;
142
+ }
143
+ const normalized = normalizeLegacyProviderId(providerId);
144
+ if (!normalized) {
145
+ return false;
146
+ }
147
+ return JUDGE_CATALOG[normalized].isCloud;
148
+ }
@@ -0,0 +1,23 @@
1
+ import type { ScrubOptions } from '../types.js';
2
+ import { parseTier1Json, type TracedTier1Judge } from './judge.js';
3
+ import type { JudgeProviderId } from './judge-catalog.js';
4
+ export interface CliJudgeOptions {
5
+ providerId: JudgeProviderId;
6
+ modelRequested: string;
7
+ modelResolved: string;
8
+ timeoutMs?: number;
9
+ sensitivePaths: string[];
10
+ scrubOptions: ScrubOptions;
11
+ runCliCommand?: CliJudgeRunCommand;
12
+ }
13
+ export interface CliInvocation {
14
+ binary: string;
15
+ args: string[];
16
+ stdin?: string;
17
+ }
18
+ export type CliJudgeRunCommand = (invocation: CliInvocation, timeoutMs: number) => Promise<string>;
19
+ export declare function buildCliInvocation(providerId: Exclude<JudgeProviderId, 'ollama'>, prompt: string, model: string): CliInvocation;
20
+ export declare function parseCliJudgeOutput(providerId: Exclude<JudgeProviderId, 'ollama'>, raw: string): ReturnType<typeof parseTier1Json>;
21
+ export declare function createCodexCliJudge(options: Omit<CliJudgeOptions, 'providerId'>): TracedTier1Judge;
22
+ export declare function createCursorCliJudge(options: Omit<CliJudgeOptions, 'providerId'>): TracedTier1Judge;
23
+ export declare function createClaudeCliJudge(options: Omit<CliJudgeOptions, 'providerId'>): TracedTier1Judge;
@@ -0,0 +1,280 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { buildTier1Prompt, parseTier1Json, prescanInterpreterCode, } from './judge.js';
3
+ import { scrubOutboundForJudge } from './judge-outbound.js';
4
+ const CLI_BINARIES = {
5
+ codex: 'codex',
6
+ cursor: 'cursor-agent',
7
+ claude: 'claude',
8
+ };
9
+ function failClosedVerdict(reason) {
10
+ return {
11
+ local_recoverable: false,
12
+ destroys_outside_repo: true,
13
+ destroys_history_or_secrets: true,
14
+ external_change: true,
15
+ reason,
16
+ };
17
+ }
18
+ function transportForProvider(providerId) {
19
+ return `${providerId}-cli`;
20
+ }
21
+ function traceProvider(providerId) {
22
+ if (providerId === 'claude') {
23
+ return 'anthropic';
24
+ }
25
+ return 'openai-compatible';
26
+ }
27
+ function initialTrace(options) {
28
+ return {
29
+ provider: traceProvider(options.providerId),
30
+ modelRequested: options.modelRequested,
31
+ modelResolved: options.modelResolved,
32
+ latencyMs: 0,
33
+ transport: transportForProvider(options.providerId),
34
+ };
35
+ }
36
+ export function buildCliInvocation(providerId, prompt, model) {
37
+ if (providerId === 'codex') {
38
+ return {
39
+ binary: CLI_BINARIES[providerId],
40
+ args: [
41
+ 'exec',
42
+ '--json',
43
+ '--skip-git-repo-check',
44
+ '--sandbox',
45
+ 'read-only',
46
+ '--model',
47
+ model,
48
+ '-',
49
+ ],
50
+ stdin: prompt,
51
+ };
52
+ }
53
+ if (providerId === 'cursor') {
54
+ return {
55
+ binary: CLI_BINARIES[providerId],
56
+ args: [
57
+ '--print',
58
+ '--output-format',
59
+ 'json',
60
+ '--mode',
61
+ 'ask',
62
+ '--sandbox',
63
+ 'enabled',
64
+ '--trust',
65
+ '--model',
66
+ model,
67
+ prompt,
68
+ ],
69
+ };
70
+ }
71
+ return {
72
+ binary: CLI_BINARIES[providerId],
73
+ args: [
74
+ '-p',
75
+ '--output-format',
76
+ 'json',
77
+ '--permission-mode',
78
+ 'plan',
79
+ '--tools',
80
+ '',
81
+ '--bare',
82
+ '--model',
83
+ model,
84
+ ],
85
+ stdin: prompt,
86
+ };
87
+ }
88
+ function tryParseJson(raw) {
89
+ try {
90
+ return JSON.parse(raw);
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ }
96
+ function extractVerdictFromJsonValue(value) {
97
+ if (typeof value === 'string') {
98
+ return parseTier1Json(value.trim());
99
+ }
100
+ if (Array.isArray(value)) {
101
+ for (let index = value.length - 1; index >= 0; index -= 1) {
102
+ const verdict = extractVerdictFromJsonValue(value[index]);
103
+ if (verdict) {
104
+ return verdict;
105
+ }
106
+ }
107
+ return null;
108
+ }
109
+ if (!value || typeof value !== 'object') {
110
+ return null;
111
+ }
112
+ const record = value;
113
+ for (const key of ['result', 'content', 'text', 'message', 'output_text', 'final']) {
114
+ if (!(key in record)) {
115
+ continue;
116
+ }
117
+ const verdict = extractVerdictFromJsonValue(record[key]);
118
+ if (verdict) {
119
+ return verdict;
120
+ }
121
+ }
122
+ for (const candidate of Object.values(record).reverse()) {
123
+ const verdict = extractVerdictFromJsonValue(candidate);
124
+ if (verdict) {
125
+ return verdict;
126
+ }
127
+ }
128
+ return null;
129
+ }
130
+ function parseCliJsonLines(raw) {
131
+ const lines = raw
132
+ .split(/\r?\n/)
133
+ .map((line) => line.trim())
134
+ .filter(Boolean);
135
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
136
+ const parsed = tryParseJson(lines[index] ?? '');
137
+ if (!parsed) {
138
+ continue;
139
+ }
140
+ const verdict = extractVerdictFromJsonValue(parsed);
141
+ if (verdict) {
142
+ return verdict;
143
+ }
144
+ }
145
+ return null;
146
+ }
147
+ export function parseCliJudgeOutput(providerId, raw) {
148
+ const direct = parseTier1Json(raw);
149
+ if (direct) {
150
+ return direct;
151
+ }
152
+ const parsed = tryParseJson(raw);
153
+ if (parsed) {
154
+ const envelopeVerdict = extractVerdictFromJsonValue(parsed);
155
+ if (envelopeVerdict) {
156
+ return envelopeVerdict;
157
+ }
158
+ }
159
+ if (providerId === 'codex') {
160
+ return parseCliJsonLines(raw);
161
+ }
162
+ return parseCliJsonLines(raw);
163
+ }
164
+ async function runCliJson(invocation, timeoutMs) {
165
+ const { binary, args, stdin } = invocation;
166
+ return new Promise((resolve, reject) => {
167
+ const child = spawn(binary, args, {
168
+ stdio: ['pipe', 'pipe', 'pipe'],
169
+ });
170
+ let stdout = '';
171
+ let stderr = '';
172
+ const timer = setTimeout(() => {
173
+ child.kill('SIGTERM');
174
+ reject(new Error(`${binary} timed out`));
175
+ }, timeoutMs);
176
+ child.stdout.on('data', (chunk) => {
177
+ stdout += String(chunk);
178
+ });
179
+ child.stderr.on('data', (chunk) => {
180
+ stderr += String(chunk);
181
+ });
182
+ if (stdin !== undefined) {
183
+ child.stdin.write(stdin);
184
+ }
185
+ child.stdin.end();
186
+ child.on('error', (error) => {
187
+ clearTimeout(timer);
188
+ reject(error);
189
+ });
190
+ child.on('close', (code) => {
191
+ clearTimeout(timer);
192
+ if (code === 0 && stdout.trim()) {
193
+ resolve(stdout.trim());
194
+ return;
195
+ }
196
+ reject(new Error(stderr.trim() || `${binary} exited with code ${code ?? 'unknown'}`));
197
+ });
198
+ });
199
+ }
200
+ function createCliJudge(options) {
201
+ const transport = transportForProvider(options.providerId);
202
+ const timeoutMs = options.timeoutMs ?? 25000;
203
+ const providerId = options.providerId;
204
+ const runCommand = options.runCliCommand ?? runCliJson;
205
+ const judge = {
206
+ lastTrace: initialTrace(options),
207
+ async evaluate(input) {
208
+ const started = Date.now();
209
+ const prescan = input.innerCode ? prescanInterpreterCode(input.innerCode) : null;
210
+ if (prescan?.destroys_history_or_secrets) {
211
+ judge.lastTrace = {
212
+ provider: traceProvider(options.providerId),
213
+ modelRequested: options.modelRequested,
214
+ modelResolved: options.modelResolved,
215
+ latencyMs: Date.now() - started,
216
+ transport,
217
+ };
218
+ return prescan;
219
+ }
220
+ const scrubbed = scrubOutboundForJudge(input.text, {
221
+ sensitivePaths: options.sensitivePaths,
222
+ scrubOptions: options.scrubOptions,
223
+ });
224
+ if (!scrubbed.ok) {
225
+ judge.lastTrace = {
226
+ provider: 'fallback',
227
+ modelRequested: options.modelRequested,
228
+ modelResolved: options.modelResolved,
229
+ latencyMs: Date.now() - started,
230
+ fallbackReason: scrubbed.reason,
231
+ };
232
+ return failClosedVerdict('outbound_scrub_failed');
233
+ }
234
+ const prompt = buildTier1Prompt(scrubbed.text);
235
+ try {
236
+ const raw = await runCommand(buildCliInvocation(providerId, prompt, options.modelResolved), timeoutMs);
237
+ const parsed = parseCliJudgeOutput(providerId, raw);
238
+ if (!parsed) {
239
+ judge.lastTrace = {
240
+ provider: 'fallback',
241
+ modelRequested: options.modelRequested,
242
+ modelResolved: options.modelResolved,
243
+ latencyMs: Date.now() - started,
244
+ fallbackReason: `${providerId}_cli_parse_error`,
245
+ };
246
+ return failClosedVerdict(`${providerId}_cli_parse_error`);
247
+ }
248
+ judge.lastTrace = {
249
+ provider: traceProvider(options.providerId),
250
+ modelRequested: options.modelRequested,
251
+ modelResolved: options.modelResolved,
252
+ latencyMs: Date.now() - started,
253
+ transport,
254
+ outboundRedacted: true,
255
+ };
256
+ return parsed;
257
+ }
258
+ catch {
259
+ judge.lastTrace = {
260
+ provider: 'fallback',
261
+ modelRequested: options.modelRequested,
262
+ modelResolved: options.modelResolved,
263
+ latencyMs: Date.now() - started,
264
+ fallbackReason: `${providerId}_cli_unavailable`,
265
+ };
266
+ return failClosedVerdict(`${providerId}_cli_unavailable`);
267
+ }
268
+ },
269
+ };
270
+ return judge;
271
+ }
272
+ export function createCodexCliJudge(options) {
273
+ return createCliJudge({ ...options, providerId: 'codex' });
274
+ }
275
+ export function createCursorCliJudge(options) {
276
+ return createCliJudge({ ...options, providerId: 'cursor' });
277
+ }
278
+ export function createClaudeCliJudge(options) {
279
+ return createCliJudge({ ...options, providerId: 'claude' });
280
+ }
@@ -9,17 +9,28 @@ export declare function loadPinnedJudgeModels(): Promise<{
9
9
  ciPin: string;
10
10
  };
11
11
  }>;
12
- export declare function resolveCloudModel(requested: string, pinned: {
12
+ export declare function resolveJudgeModel(judge: BelayJudgeConfig): {
13
+ requested: string;
14
+ resolved: string;
15
+ };
16
+ /** @deprecated Use resolveJudgeModel */
17
+ export declare const resolveCloudModel: (requested: string, _pinned: {
18
+ autoResolved: string;
19
+ }) => {
20
+ requested: string;
21
+ resolved: string;
22
+ };
23
+ /** @deprecated Use resolveJudgeModel */
24
+ export declare const resolveCursorModel: (requested: string, _pinned: {
13
25
  autoResolved: string;
14
- }): {
26
+ }) => {
15
27
  requested: string;
16
28
  resolved: string;
17
29
  };
18
- /** @deprecated Use resolveCloudModel */
19
- export declare const resolveCursorModel: typeof resolveCloudModel;
20
30
  export declare function createJudgeFromConfig(config: BelayConfigV4, options?: {
21
31
  pinnedModels?: {
22
32
  autoResolved: string;
23
33
  };
34
+ repoRoot?: string;
24
35
  }): TracedTier1Judge;
25
36
  export declare function judgeConfigSummary(judge: BelayJudgeConfig): string;