@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,33 +1,70 @@
1
- import type { BelayJudgeConfig } from './config.js';
1
+ import type { AdapterName } from '../adapters/layouts/types.js';
2
+ import type { BelayJudgeConfig, JudgeProviderId } from './config.js';
3
+ import { isJudgeProviderId } from './verdict/judge-catalog.js';
2
4
  export type JudgeProfileName = 'local-ollama' | 'cursor' | 'claude' | 'codex';
3
5
  export declare const JUDGE_PROFILE_LOCAL_OLLAMA: BelayJudgeConfig;
4
6
  export declare const JUDGE_PROFILE_CURSOR: BelayJudgeConfig;
5
7
  export declare const JUDGE_PROFILE_CLAUDE: BelayJudgeConfig;
6
8
  export declare const JUDGE_PROFILE_CODEX: BelayJudgeConfig;
7
9
  export declare const JUDGE_PROFILES: Record<JudgeProfileName, BelayJudgeConfig>;
10
+ /** Default Tier1 judge catalog id for a host adapter (fresh init, no explicit judge flags). */
11
+ export declare function defaultJudgeProviderForAdapter(adapter: AdapterName): JudgeProviderId;
8
12
  export interface ResolveJudgeConfigInput {
9
13
  judgeProfile?: JudgeProfileName;
10
14
  judgeProvider?: 'ollama' | 'openai-compatible' | 'cursor';
15
+ judgeProviderId?: JudgeProviderId | string;
11
16
  judgeModel?: string;
12
17
  judgeEndpoint?: string;
13
18
  existingJudge?: BelayJudgeConfig;
14
19
  }
15
20
  export declare function resolveJudgeConfig(input?: ResolveJudgeConfigInput): BelayJudgeConfig;
16
21
  export declare class CloudJudgeConsentRequiredError extends Error {
17
- constructor();
22
+ constructor(details?: {
23
+ consent?: boolean;
24
+ key?: boolean;
25
+ localFallback?: boolean;
26
+ });
18
27
  }
19
28
  export declare class JudgeEndpointRequiredError extends Error {
20
- constructor();
29
+ constructor(providerId?: JudgeProviderId);
21
30
  }
31
+ export declare function isCloudJudgeConfig(judge: BelayJudgeConfig): boolean;
32
+ export declare function hasValidCloudConsent(judge: BelayJudgeConfig): boolean;
33
+ export declare function isImplicitLocalJudge(judge: BelayJudgeConfig): boolean;
34
+ export declare function migrateImplicitLocalJudgeIfNeeded(existingJudge: BelayJudgeConfig, adapter: AdapterName): BelayJudgeConfig | null;
22
35
  export declare function assertJudgeEndpoint(judge: BelayJudgeConfig): void;
23
36
  export declare function resolveInitJudgeConfig(input: {
24
37
  isFresh: boolean;
25
38
  hasExplicitJudgeFlags: boolean;
26
39
  judgeProfile?: JudgeProfileName;
27
40
  judgeProvider?: 'ollama' | 'openai-compatible' | 'cursor';
41
+ judgeProviderId?: JudgeProviderId | string;
28
42
  judgeModel?: string;
29
43
  judgeEndpoint?: string;
30
44
  acceptCloudJudge?: boolean;
45
+ interactiveConsent?: boolean;
46
+ cloudConsentApprovalId?: string;
31
47
  existingJudge?: BelayJudgeConfig;
32
- defaultJudgeProfile?: JudgeProfileName;
48
+ adapter?: AdapterName;
33
49
  }): BelayJudgeConfig;
50
+ export declare function applyCloudConsent(judge: BelayJudgeConfig, params: {
51
+ by: string;
52
+ }): BelayJudgeConfig;
53
+ export interface JudgeUseOptions {
54
+ providerId: JudgeProviderId | string;
55
+ model?: string;
56
+ endpoint?: string;
57
+ timeoutMs?: number;
58
+ acceptCloud?: boolean;
59
+ cloudConsentApprovalId?: string;
60
+ credentialMode?: 'project' | 'apiKey';
61
+ keyEnv?: string;
62
+ interactiveTTY?: boolean;
63
+ interactiveConsentApproved?: boolean;
64
+ }
65
+ export declare function resolveJudgeUsePatch(existing: BelayJudgeConfig, options: JudgeUseOptions): {
66
+ judge: BelayJudgeConfig;
67
+ warnings: string[];
68
+ errors: string[];
69
+ };
70
+ export { isJudgeProviderId };
@@ -1,101 +1,307 @@
1
1
  import { normalizeJudgeProvider } from './config.js';
2
- export const JUDGE_PROFILE_LOCAL_OLLAMA = {
3
- provider: 'ollama',
4
- model: 'gemma4:e2b',
5
- endpoint: 'http://localhost:11434',
6
- timeoutMs: 25000,
7
- keepAlive: '30m',
8
- };
9
- export const JUDGE_PROFILE_CURSOR = {
10
- provider: 'openai-compatible',
11
- model: 'auto',
12
- endpoint: 'https://api.openai.com/v1',
13
- timeoutMs: 8000,
14
- keepAlive: null,
15
- };
16
- export const JUDGE_PROFILE_CLAUDE = {
17
- ...JUDGE_PROFILE_CURSOR,
18
- };
19
- export const JUDGE_PROFILE_CODEX = {
20
- ...JUDGE_PROFILE_CURSOR,
2
+ import { rejectDeprecatedJudgeModelAuto } from './judge-model-policy.js';
3
+ import { detectJudgeRuntimeCapabilities } from './judge-runtime-detection.js';
4
+ import { catalogRequiresEndpoint, getJudgeProviderSpec, isCloudProviderId, isJudgeProviderId, JUDGE_PROVIDER_IDS, normalizeLegacyProviderId, resolveJudgeFromCatalog, } from './verdict/judge-catalog.js';
5
+ const PROFILE_TO_PROVIDER_ID = {
6
+ 'local-ollama': 'ollama',
7
+ cursor: 'cursor',
8
+ claude: 'claude',
9
+ codex: 'codex',
21
10
  };
11
+ export const JUDGE_PROFILE_LOCAL_OLLAMA = resolveJudgeFromCatalog({
12
+ providerId: 'ollama',
13
+ });
14
+ export const JUDGE_PROFILE_CURSOR = resolveJudgeFromCatalog({
15
+ providerId: 'cursor',
16
+ });
17
+ export const JUDGE_PROFILE_CLAUDE = resolveJudgeFromCatalog({
18
+ providerId: 'claude',
19
+ });
20
+ export const JUDGE_PROFILE_CODEX = resolveJudgeFromCatalog({
21
+ providerId: 'codex',
22
+ });
22
23
  export const JUDGE_PROFILES = {
23
24
  'local-ollama': JUDGE_PROFILE_LOCAL_OLLAMA,
24
25
  cursor: JUDGE_PROFILE_CURSOR,
25
26
  claude: JUDGE_PROFILE_CLAUDE,
26
27
  codex: JUDGE_PROFILE_CODEX,
27
28
  };
28
- export function resolveJudgeConfig(input = {}) {
29
+ /** Default Tier1 judge catalog id for a host adapter (fresh init, no explicit judge flags). */
30
+ export function defaultJudgeProviderForAdapter(adapter) {
31
+ if (adapter === 'cursor') {
32
+ return 'cursor';
33
+ }
34
+ if (adapter === 'claude') {
35
+ return 'claude';
36
+ }
37
+ if (adapter === 'codex') {
38
+ return 'codex';
39
+ }
40
+ return 'cursor';
41
+ }
42
+ function warnDeprecated(message) {
43
+ process.stderr.write(`Warning: ${message}\n`);
44
+ }
45
+ function resolveProviderIdFromFlags(input) {
46
+ if (input.judgeProviderId) {
47
+ const normalized = normalizeLegacyProviderId(String(input.judgeProviderId));
48
+ if (normalized) {
49
+ return normalized;
50
+ }
51
+ }
29
52
  if (input.judgeProvider) {
30
- const provider = normalizeJudgeProvider(input.judgeProvider);
31
- const base = provider === 'openai-compatible' ? openAiCompatibleBase(input) : JUDGE_PROFILE_LOCAL_OLLAMA;
32
- return {
33
- ...base,
34
- model: input.judgeModel ?? base.model,
35
- endpoint: input.judgeEndpoint?.trim() || base.endpoint,
36
- };
53
+ const rawProvider = input.judgeProvider;
54
+ const provider = normalizeJudgeProvider(rawProvider);
55
+ if (rawProvider === 'cursor') {
56
+ return 'cursor';
57
+ }
58
+ if (provider === 'ollama') {
59
+ return 'ollama';
60
+ }
61
+ return 'codex';
62
+ }
63
+ return null;
64
+ }
65
+ export function resolveJudgeConfig(input = {}) {
66
+ if (input.judgeModel !== undefined) {
67
+ rejectDeprecatedJudgeModelAuto(input.judgeModel);
68
+ }
69
+ const providerIdFromFlags = resolveProviderIdFromFlags(input);
70
+ if (providerIdFromFlags) {
71
+ return resolveJudgeFromCatalog({
72
+ providerId: providerIdFromFlags,
73
+ model: input.judgeModel,
74
+ endpoint: input.judgeEndpoint,
75
+ });
37
76
  }
38
77
  if (input.judgeProfile) {
39
- const profile = JUDGE_PROFILES[input.judgeProfile];
40
- return {
41
- ...profile,
42
- model: input.judgeModel ?? profile.model,
43
- endpoint: input.judgeEndpoint?.trim() || profile.endpoint,
44
- };
78
+ warnDeprecated(`--judge-profile is deprecated; use belay config set judge.providerId <provider-id> (${JUDGE_PROVIDER_IDS.join(', ')}).`);
79
+ const providerId = PROFILE_TO_PROVIDER_ID[input.judgeProfile];
80
+ return resolveJudgeFromCatalog({
81
+ providerId,
82
+ model: input.judgeModel,
83
+ endpoint: input.judgeEndpoint,
84
+ });
45
85
  }
46
86
  if (input.existingJudge) {
47
87
  return { ...input.existingJudge };
48
88
  }
49
89
  return { ...JUDGE_PROFILE_LOCAL_OLLAMA };
50
90
  }
51
- function openAiCompatibleBase(input) {
52
- return {
53
- provider: 'openai-compatible',
54
- model: input.judgeModel ?? 'auto',
55
- timeoutMs: 8000,
56
- endpoint: input.judgeEndpoint?.trim() ?? null,
57
- keepAlive: null,
58
- };
59
- }
60
91
  export class CloudJudgeConsentRequiredError extends Error {
61
- constructor() {
62
- super('Cloud judge sends redacted shell commands to an external endpoint and requires an API key in BELAY_JUDGE_API_KEY or OPENAI_API_KEY. ' +
63
- 'Pass --accept-cloud-judge to confirm, or use --judge-profile local-ollama for local-only Tier1.');
92
+ constructor(details) {
93
+ const parts = [];
94
+ if (details?.consent !== false) {
95
+ parts.push('Cloud judge requires recorded consent (interactive TTY with --accept-cloud, or judge_cloud_consent capability approval).');
96
+ }
97
+ if (details?.key !== false) {
98
+ parts.push('Set provider API keys via env or belay config credential set --key-stdin.');
99
+ }
100
+ if (details?.localFallback !== false) {
101
+ parts.push('Use belay config set judge.providerId ollama for local-only Tier1.');
102
+ }
103
+ super(parts.join(' '));
64
104
  this.name = 'CloudJudgeConsentRequiredError';
65
105
  }
66
106
  }
67
107
  export class JudgeEndpointRequiredError extends Error {
68
- constructor() {
69
- super('openai-compatible judge requires --judge-endpoint (or judge.endpoint in config). No default cloud base URL is applied.');
108
+ constructor(providerId) {
109
+ super(providerId
110
+ ? `${providerId} judge requires --endpoint (or judge.endpoint in config).`
111
+ : 'openai-compatible judge requires --judge-endpoint (or judge.endpoint in config). No default cloud base URL is applied.');
70
112
  this.name = 'JudgeEndpointRequiredError';
71
113
  }
72
114
  }
73
- function isCloudJudgeConfig(judge) {
74
- return judge.provider === 'openai-compatible';
115
+ export function isCloudJudgeConfig(judge) {
116
+ if (judge.providerId) {
117
+ return isCloudProviderId(judge.providerId);
118
+ }
119
+ return judge.provider === 'openai-compatible' || judge.provider === 'anthropic';
120
+ }
121
+ export function hasValidCloudConsent(judge) {
122
+ if (!judge.cloudConsent?.accepted) {
123
+ return false;
124
+ }
125
+ if (!judge.endpoint?.trim()) {
126
+ return false;
127
+ }
128
+ const providerId = judge.providerId && normalizeLegacyProviderId(judge.providerId)
129
+ ? normalizeLegacyProviderId(judge.providerId)
130
+ : judge.provider === 'ollama'
131
+ ? 'ollama'
132
+ : 'codex';
133
+ return (judge.cloudConsent.endpoint === judge.endpoint.trim() &&
134
+ normalizeLegacyProviderId(judge.cloudConsent.providerId) === providerId);
135
+ }
136
+ export function isImplicitLocalJudge(judge) {
137
+ const providerId = judge.providerId && normalizeLegacyProviderId(judge.providerId)
138
+ ? normalizeLegacyProviderId(judge.providerId)
139
+ : judge.provider === 'ollama'
140
+ ? 'ollama'
141
+ : null;
142
+ if (providerId !== 'ollama') {
143
+ return false;
144
+ }
145
+ if (judge.cloudConsent?.accepted) {
146
+ return false;
147
+ }
148
+ const factoryDefault = resolveJudgeFromCatalog({ providerId: 'ollama' });
149
+ const endpoint = judge.endpoint?.trim() ?? factoryDefault.endpoint;
150
+ const defaultEndpoint = factoryDefault.endpoint?.trim();
151
+ const endpointMatches = !judge.endpoint ||
152
+ endpoint === defaultEndpoint ||
153
+ endpoint === 'http://127.0.0.1:11434' ||
154
+ endpoint === 'http://localhost:11434';
155
+ return judge.provider === 'ollama' && judge.model === factoryDefault.model && endpointMatches;
156
+ }
157
+ export function migrateImplicitLocalJudgeIfNeeded(existingJudge, adapter) {
158
+ if (!isImplicitLocalJudge(existingJudge)) {
159
+ return null;
160
+ }
161
+ return applyFreshInitDefaults(resolveJudgeFromCatalog({ providerId: defaultJudgeProviderForAdapter(adapter) }));
75
162
  }
76
163
  export function assertJudgeEndpoint(judge) {
77
- if (judge.provider === 'openai-compatible' && !judge.endpoint?.trim()) {
78
- throw new JudgeEndpointRequiredError();
164
+ const providerId = judge.providerId && normalizeLegacyProviderId(judge.providerId)
165
+ ? normalizeLegacyProviderId(judge.providerId)
166
+ : judge.provider === 'ollama'
167
+ ? 'ollama'
168
+ : 'codex';
169
+ const transport = judge.endpoint?.trim()
170
+ ? 'http'
171
+ : (detectJudgeRuntimeCapabilities(providerId).cliTransport ?? 'http');
172
+ if (catalogRequiresEndpoint(providerId, { transport }) && !judge.endpoint?.trim()) {
173
+ throw new JudgeEndpointRequiredError(providerId);
79
174
  }
80
175
  }
176
+ function applyFreshInitDefaults(judge) {
177
+ const endpoint = process.env.BELAY_JUDGE_ENDPOINT?.trim();
178
+ return {
179
+ ...judge,
180
+ ...(endpoint ? { endpoint } : {}),
181
+ credential: { mode: 'project' },
182
+ };
183
+ }
81
184
  export function resolveInitJudgeConfig(input) {
82
185
  if (input.hasExplicitJudgeFlags) {
83
186
  const judge = resolveJudgeConfig({
84
187
  judgeProfile: input.judgeProfile,
85
188
  judgeProvider: input.judgeProvider,
189
+ judgeProviderId: input.judgeProviderId,
86
190
  judgeModel: input.judgeModel,
87
191
  judgeEndpoint: input.judgeEndpoint,
88
192
  });
89
- if (isCloudJudgeConfig(judge) && !input.acceptCloudJudge) {
90
- throw new CloudJudgeConsentRequiredError();
91
- }
92
193
  assertJudgeEndpoint(judge);
194
+ if (isCloudJudgeConfig(judge) && judge.endpoint?.trim()) {
195
+ if (input.cloudConsentApprovalId) {
196
+ return applyCloudConsent(judge, {
197
+ by: `capability-approval:${input.cloudConsentApprovalId}`,
198
+ });
199
+ }
200
+ if (input.acceptCloudJudge && input.interactiveConsent) {
201
+ return applyCloudConsent(judge, { by: 'tty' });
202
+ }
203
+ }
93
204
  return judge;
94
205
  }
95
206
  if (!input.isFresh && input.existingJudge) {
96
- const judge = resolveJudgeConfig({ existingJudge: input.existingJudge });
97
- assertJudgeEndpoint(judge);
207
+ return resolveJudgeConfig({ existingJudge: input.existingJudge });
208
+ }
209
+ const adapter = input.adapter ?? 'cursor';
210
+ const judge = resolveJudgeConfig({ judgeProviderId: defaultJudgeProviderForAdapter(adapter) });
211
+ return applyFreshInitDefaults(judge);
212
+ }
213
+ export function applyCloudConsent(judge, params) {
214
+ const providerId = judge.providerId && normalizeLegacyProviderId(judge.providerId)
215
+ ? normalizeLegacyProviderId(judge.providerId)
216
+ : 'codex';
217
+ const endpoint = judge.endpoint?.trim();
218
+ if (!endpoint) {
98
219
  return judge;
99
220
  }
100
- return resolveJudgeConfig({ judgeProfile: input.defaultJudgeProfile ?? 'cursor' });
221
+ const cloudConsent = {
222
+ accepted: true,
223
+ at: new Date().toISOString(),
224
+ providerId,
225
+ endpoint,
226
+ by: params.by,
227
+ };
228
+ return { ...judge, cloudConsent };
229
+ }
230
+ export function resolveJudgeUsePatch(existing, options) {
231
+ const warnings = [];
232
+ const errors = [];
233
+ const normalizedId = normalizeLegacyProviderId(String(options.providerId));
234
+ if (!normalizedId) {
235
+ errors.push(`Unknown judge provider id: ${options.providerId}`);
236
+ return { judge: existing, warnings, errors };
237
+ }
238
+ const spec = getJudgeProviderSpec(normalizedId);
239
+ if (!spec) {
240
+ errors.push(`Unknown judge provider id: ${options.providerId}`);
241
+ return { judge: existing, warnings, errors };
242
+ }
243
+ let judge = resolveJudgeFromCatalog({
244
+ providerId: normalizedId,
245
+ model: options.model ?? (existing.providerId === normalizedId ? existing.model : undefined),
246
+ endpoint: options.endpoint !== undefined
247
+ ? options.endpoint
248
+ : existing.providerId === normalizedId
249
+ ? existing.endpoint
250
+ : undefined,
251
+ timeoutMs: options.timeoutMs ?? existing.timeoutMs,
252
+ keepAlive: existing.keepAlive,
253
+ });
254
+ if (catalogRequiresEndpoint(normalizedId, {
255
+ transport: judge.endpoint?.trim()
256
+ ? 'http'
257
+ : (detectJudgeRuntimeCapabilities(normalizedId).cliTransport ?? 'unavailable'),
258
+ }) &&
259
+ !judge.endpoint?.trim()) {
260
+ errors.push(`${normalizedId} requires --endpoint for HTTP transport.`);
261
+ }
262
+ if (options.credentialMode === 'apiKey') {
263
+ judge = {
264
+ ...judge,
265
+ credential: options.keyEnv
266
+ ? { mode: 'apiKey', ref: `env:${options.keyEnv}` }
267
+ : { mode: 'apiKey', ref: 'store:judge' },
268
+ };
269
+ }
270
+ else if (options.credentialMode === 'project') {
271
+ judge = { ...judge, credential: { mode: 'project' } };
272
+ }
273
+ else if (existing.credential) {
274
+ judge = { ...judge, credential: existing.credential };
275
+ }
276
+ const needsNewConsent = spec.isCloud &&
277
+ (!existing.cloudConsent?.accepted ||
278
+ existing.cloudConsent.endpoint !== judge.endpoint?.trim() ||
279
+ normalizeLegacyProviderId(existing.cloudConsent.providerId) !== normalizedId);
280
+ if (spec.isCloud && needsNewConsent) {
281
+ if (options.cloudConsentApprovalId) {
282
+ judge = applyCloudConsent(judge, {
283
+ by: `capability-approval:${options.cloudConsentApprovalId}`,
284
+ });
285
+ }
286
+ else if (options.acceptCloud &&
287
+ options.interactiveTTY &&
288
+ options.interactiveConsentApproved) {
289
+ judge = applyCloudConsent(judge, { by: 'tty' });
290
+ }
291
+ else if (options.acceptCloud && options.interactiveTTY) {
292
+ warnings.push('Cloud consent not recorded (--accept-cloud requires confirmation).');
293
+ }
294
+ else if (options.acceptCloud && !options.interactiveTTY) {
295
+ warnings.push('Cloud consent not recorded in non-interactive mode. Pass --cloud-consent-approval-id after judge_cloud_consent approval.');
296
+ }
297
+ else if (!existing.cloudConsent?.accepted) {
298
+ warnings.push('Cloud judge will remain disabled until consent is recorded.');
299
+ }
300
+ }
301
+ else if (existing.cloudConsent?.accepted && spec.isCloud) {
302
+ judge = { ...judge, cloudConsent: existing.cloudConsent };
303
+ }
304
+ return { judge, warnings, errors };
101
305
  }
306
+ // Re-export for callers that validate provider ids
307
+ export { isJudgeProviderId };
@@ -1,7 +1,12 @@
1
1
  import type { BelayConfigV4 } from './config.js';
2
+ import { type CheckJudgeModelPresenceResult, type JudgeModelDiscoveryDeps } from './judge-model-discovery.js';
2
3
  export interface JudgeDoctorResult {
3
4
  issues: string[];
4
5
  warnings: string[];
5
6
  notes: string[];
7
+ modelCheck?: CheckJudgeModelPresenceResult;
6
8
  }
7
- export declare function diagnoseJudge(config: BelayConfigV4): Promise<JudgeDoctorResult>;
9
+ export interface DiagnoseJudgeOptions {
10
+ discoveryDeps?: JudgeModelDiscoveryDeps;
11
+ }
12
+ export declare function diagnoseJudge(config: BelayConfigV4, repoRoot?: string, options?: DiagnoseJudgeOptions): Promise<JudgeDoctorResult>;