@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
package/dist/cli.js CHANGED
@@ -13,6 +13,7 @@ import { revokeApproval } from './commands/revoke.js';
13
13
  import { formatSimulateReport, simulateProject } from './commands/simulate.js';
14
14
  import { formatStatusReport, statusProject } from './commands/status.js';
15
15
  import { loadConfigFile } from './config-io.js';
16
+ import { rejectDeprecatedJudgeModelAuto } from './core/judge-model-policy.js';
16
17
  import { initProject, upgradeProject } from './installer.js';
17
18
  import { egressEnv, egressStatus, formatEgressStatusReport, startEgressProxy, stopEgressProxy, } from './services/egress-service.js';
18
19
  import { formatSandboxStatusReport, sandboxStatus } from './services/sandbox-service.js';
@@ -64,6 +65,7 @@ function parseArgs(argv) {
64
65
  continue;
65
66
  }
66
67
  if (token === '--judge-profile') {
68
+ process.stderr.write('Warning: --judge-profile is deprecated; use belay config set judge.providerId <id> after init.\n');
67
69
  const next = rest[index + 1];
68
70
  if (!next || !['local-ollama', 'cursor', 'claude', 'codex'].includes(next)) {
69
71
  throw new Error('--judge-profile requires local-ollama, cursor, claude, or codex.');
@@ -93,8 +95,9 @@ function parseArgs(argv) {
93
95
  if (token === '--judge-model') {
94
96
  const next = rest[index + 1];
95
97
  if (!next) {
96
- throw new Error('--judge-model requires a model id or auto.');
98
+ throw new Error('--judge-model requires a model id.');
97
99
  }
100
+ rejectDeprecatedJudgeModelAuto(next);
98
101
  options.judgeModel = next;
99
102
  index += 1;
100
103
  continue;
@@ -103,6 +106,54 @@ function parseArgs(argv) {
103
106
  options.acceptCloudJudge = true;
104
107
  continue;
105
108
  }
109
+ if (token === '--migrate-judge-default') {
110
+ options.migrateJudgeDefault = true;
111
+ continue;
112
+ }
113
+ if (token === '--accept-cloud') {
114
+ options.acceptCloud = true;
115
+ continue;
116
+ }
117
+ if (token === '--cloud-consent-approval-id') {
118
+ const next = rest[index + 1];
119
+ if (!next) {
120
+ throw new Error('--cloud-consent-approval-id requires an approval id.');
121
+ }
122
+ options.cloudConsentApprovalId = next;
123
+ index += 1;
124
+ continue;
125
+ }
126
+ if (token === '--credential') {
127
+ const next = rest[index + 1];
128
+ if (!next || !['project', 'apiKey'].includes(next)) {
129
+ throw new Error('--credential requires project or apiKey.');
130
+ }
131
+ options.credentialMode = next;
132
+ index += 1;
133
+ continue;
134
+ }
135
+ if (token === '--key-stdin') {
136
+ options.keyStdin = true;
137
+ continue;
138
+ }
139
+ if (token === '--key-env') {
140
+ const next = rest[index + 1];
141
+ if (!next) {
142
+ throw new Error('--key-env requires an environment variable name.');
143
+ }
144
+ options.keyEnv = next;
145
+ index += 1;
146
+ continue;
147
+ }
148
+ if (token === '--timeout') {
149
+ const next = rest[index + 1];
150
+ if (!next) {
151
+ throw new Error('--timeout requires milliseconds.');
152
+ }
153
+ options.judgeTimeoutMs = Number(next);
154
+ index += 1;
155
+ continue;
156
+ }
106
157
  if (token === '--json') {
107
158
  options.json = true;
108
159
  continue;
@@ -316,6 +367,69 @@ function parseArgs(argv) {
316
367
  }
317
368
  throw new Error('sandbox requires subcommand: status');
318
369
  }
370
+ if (command === 'judge' && !options.judgeSubcommand) {
371
+ if (token === 'status' ||
372
+ token === 'list' ||
373
+ token === 'use' ||
374
+ token === 'test' ||
375
+ token === 'consent') {
376
+ options.judgeSubcommand = token;
377
+ continue;
378
+ }
379
+ throw new Error('judge requires subcommand: status, list, use, test, or consent');
380
+ }
381
+ if (command === 'config' && !options.configSubcommand) {
382
+ if (token === 'list' ||
383
+ token === 'get' ||
384
+ token === 'set' ||
385
+ token === 'unset' ||
386
+ token === 'credential' ||
387
+ token === 'judge') {
388
+ options.configSubcommand = token;
389
+ continue;
390
+ }
391
+ throw new Error('config requires subcommand: list, get, set, unset, credential, or judge');
392
+ }
393
+ if (command === 'config' &&
394
+ options.configSubcommand === 'credential' &&
395
+ !options.credentialAction) {
396
+ if (token === 'mode' || token === 'set' || token === 'clear') {
397
+ options.credentialAction = token;
398
+ continue;
399
+ }
400
+ throw new Error('config credential requires action: mode, set, or clear');
401
+ }
402
+ if (command === 'config' &&
403
+ options.configSubcommand === 'credential' &&
404
+ options.credentialAction === 'mode' &&
405
+ !options.credentialMode) {
406
+ if (token === 'project' || token === 'apiKey') {
407
+ options.credentialMode = token;
408
+ continue;
409
+ }
410
+ throw new Error('config credential mode requires project or apiKey');
411
+ }
412
+ if (command === 'config' &&
413
+ (options.configSubcommand === 'get' ||
414
+ options.configSubcommand === 'set' ||
415
+ options.configSubcommand === 'unset') &&
416
+ !options.configKey) {
417
+ options.configKey = token;
418
+ continue;
419
+ }
420
+ if (command === 'config' &&
421
+ options.configSubcommand === 'set' &&
422
+ options.configKey &&
423
+ !options.configValue) {
424
+ options.configValue = token;
425
+ continue;
426
+ }
427
+ if (command === 'judge' &&
428
+ (options.judgeSubcommand === 'use' || options.judgeSubcommand === 'consent') &&
429
+ !options.judgeUseProvider) {
430
+ options.judgeUseProvider = token;
431
+ continue;
432
+ }
319
433
  if ((command === 'revoke' || command === 'approve') && !options.approvalId) {
320
434
  options.approvalId = token;
321
435
  continue;
@@ -329,10 +443,18 @@ function printHelp() {
329
443
  process.stdout.write(`${c}
330
444
 
331
445
  Usage:
332
- ${c} init [--target <dir>] [--adapter cursor|claude|codex] [--scope project|global] [--preset strict|standard|audit-first|l1-full-recommended] [--judge-profile local-ollama|cursor|claude|codex] [--judge-provider ollama|openai-compatible] [--judge-model <id|auto>] [--judge-endpoint <url>] [--accept-cloud-judge] [--with-skill] [--dogfood]
333
- ${c} init-wizard [--target <dir>]
446
+ ${c} init [--target <dir>] [--adapter cursor|claude|codex] [--scope project|global] [--preset strict|standard|audit-first|l1-full-recommended] [--judge-profile local-ollama|cursor|claude|codex] [--judge-provider ollama|openai-compatible] [--judge-model <id>] [--judge-endpoint <url>] [--accept-cloud-judge] [--migrate-judge-default] [--with-skill] [--dogfood]
447
+ ${c} config [--target <dir>] [--json]
448
+ ${c} config list|get|set|unset|judge [--target <dir>] [--json]
449
+ ${c} config get <judge.path> [--target <dir>] [--json]
450
+ ${c} config set <judge.path> <value> [--target <dir>]
451
+ ${c} config unset <judge.path> [--target <dir>]
452
+ ${c} config credential mode <project|apiKey> [--target <dir>]
453
+ ${c} config credential set [--key-stdin] [--key-env <NAME>] [--target <dir>]
454
+ ${c} config credential clear [--target <dir>]
455
+ (--adapter selects host; fresh init picks matching judge providerId: cursor/claude/codex)
334
456
  (--dogfood runs after --preset and sets mode: audit, overriding preset enforce mode)
335
- ${c} upgrade [--target <dir>] [--adapter cursor|claude|codex] [--scope project|global] [--with-skill]
457
+ ${c} upgrade [--target <dir>] [--adapter cursor|claude|codex] [--scope project|global] [--with-skill] [--migrate-judge-default]
336
458
  ${c} dogfood [--target <dir>] [--adapter cursor|claude|codex] [--enforce] [--force]
337
459
  ${c} doctor [--target <dir>] [--adapter cursor|claude|codex] [--json] [--fix] [--dry-run]
338
460
  ${c} metrics [--target <dir>] [--json]
@@ -345,6 +467,9 @@ Usage:
345
467
  ${c} explain [--target <dir>] [--cwd <dir>] [--kind shell|tool|subagent] [--tool <name>] [--payload-json <json>] [--command <text>] [--json] [-- <command>]
346
468
  ${c} egress <start|stop|status|env> [--target <dir>] [--json]
347
469
  ${c} sandbox status [--target <dir>] [--json]
470
+ ${c} judge <status|list|use|test|consent> [--target <dir>] [--json]
471
+ ${c} judge use <ollama|codex|claude|cursor> [--model <id>] [--endpoint <url>] [--timeout <ms>] [--accept-cloud] [--cloud-consent-approval-id <id>] [--credential project|apiKey] [--key-stdin] [--key-env <NAME>]
472
+ ${c} judge consent <ollama|codex|claude|cursor> [--endpoint <url>]
348
473
  ${c} approve <approval-id> [--scope once|domain|path] [--path <path>] [--token <signed-token>] [--target <dir>]
349
474
  ${c} revoke <approval-id> [--target <dir>]
350
475
  `);
@@ -360,10 +485,10 @@ async function main() {
360
485
  process.stdout.write(`${PACKAGE_VERSION}\n`);
361
486
  return;
362
487
  }
363
- if (command === 'init-wizard') {
364
- const { runInitWizard } = await import('./commands/init-wizard.js');
365
- const result = await runInitWizard({ targetDir: options.targetDir });
366
- process.stdout.write(`Initialized belay in ${result.repoRoot}${result.withSkill ? ' (with skill)' : ''}.\n`);
488
+ const DEPRECATED_COMMANDS = new Set(['init-wizard']);
489
+ if (DEPRECATED_COMMANDS.has(command)) {
490
+ process.stderr.write(`${command} is removed. Use \`belay config\` for interactive setup, or \`belay init\` for non-interactive install.\n`);
491
+ process.exitCode = 1;
367
492
  return;
368
493
  }
369
494
  if (command === 'init') {
@@ -379,6 +504,7 @@ async function main() {
379
504
  judgeModel: options.judgeModel,
380
505
  judgeEndpoint: options.judgeEndpoint,
381
506
  acceptCloudJudge: options.acceptCloudJudge,
507
+ migrateJudgeDefault: options.migrateJudgeDefault,
382
508
  });
383
509
  const extras = [
384
510
  `adapter=${result.adapter}`,
@@ -405,6 +531,7 @@ async function main() {
405
531
  withSkill: options.withSkill,
406
532
  adapter: options.adapter,
407
533
  scope: options.installScope,
534
+ migrateJudgeDefault: options.migrateJudgeDefault,
408
535
  });
409
536
  const upgraded = await loadConfigFile(result.repoRoot, result.adapter);
410
537
  if (upgraded.policy.modelAssist.enabled) {
@@ -413,6 +540,69 @@ async function main() {
413
540
  process.stdout.write(`Upgraded belay (${result.adapter}) in ${result.repoRoot}.\n`);
414
541
  return;
415
542
  }
543
+ if (command === 'judge') {
544
+ const { runJudgeCommand } = await import('./commands/judge.js');
545
+ if (!options.judgeSubcommand) {
546
+ throw new Error('judge requires subcommand: status, list, use, or test');
547
+ }
548
+ const result = await runJudgeCommand({
549
+ targetDir: options.targetDir,
550
+ json: options.json,
551
+ subcommand: options.judgeSubcommand,
552
+ providerId: options.judgeUseProvider,
553
+ model: options.judgeModel,
554
+ endpoint: options.judgeEndpoint,
555
+ timeoutMs: options.judgeTimeoutMs,
556
+ acceptCloud: options.acceptCloud,
557
+ cloudConsentApprovalId: options.cloudConsentApprovalId,
558
+ credentialMode: options.credentialMode,
559
+ keyStdin: options.keyStdin,
560
+ keyEnv: options.keyEnv,
561
+ });
562
+ if (options.json && typeof result === 'object') {
563
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
564
+ }
565
+ else {
566
+ process.stdout.write(`${String(result)}\n`);
567
+ }
568
+ return;
569
+ }
570
+ if (command === 'config') {
571
+ if (options.configSubcommand === 'credential') {
572
+ if (!options.credentialAction) {
573
+ throw new Error('config credential requires action: mode, set, or clear');
574
+ }
575
+ const { runBelayConfigCredential } = await import('./commands/config.js');
576
+ const result = await runBelayConfigCredential({
577
+ targetDir: options.targetDir,
578
+ action: options.credentialAction,
579
+ mode: options.credentialMode,
580
+ keyStdin: options.keyStdin,
581
+ keyEnv: options.keyEnv,
582
+ });
583
+ process.stdout.write(`${String(result)}\n`);
584
+ return;
585
+ }
586
+ const { runBelayConfig } = await import('./commands/config.js');
587
+ const result = await runBelayConfig({
588
+ targetDir: options.targetDir,
589
+ subcommand: options.configSubcommand,
590
+ path: options.configKey,
591
+ value: options.configValue,
592
+ json: options.json,
593
+ });
594
+ if (options.json && typeof result === 'object') {
595
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
596
+ }
597
+ else if (typeof result === 'object' && result && 'repoRoot' in result) {
598
+ const initResult = result;
599
+ process.stdout.write(`Initialized belay in ${initResult.repoRoot}${initResult.withSkill ? ' (with skill)' : ''}.\n`);
600
+ }
601
+ else {
602
+ process.stdout.write(`${String(result)}\n`);
603
+ }
604
+ return;
605
+ }
416
606
  if (command === 'doctor') {
417
607
  const report = await doctorProject({
418
608
  targetDir: options.targetDir,
@@ -1,6 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { approvedApprovalsPath, loadApprovalState, loadConfigFile, pendingApprovalsPath, saveApprovalState, } from '../config-io.js';
3
3
  import { recordApproval } from '../core/approval-service.js';
4
+ import { JUDGE_CLOUD_CONSENT_REASON } from '../core/capability/reasons.js';
4
5
  import { recordCapabilityApproval } from '../core/capability-approval.js';
5
6
  import { recordEgressApproval } from '../core/egress-approval.js';
6
7
  import { createEgressApprovalStore } from '../services/egress-service.js';
@@ -10,6 +11,16 @@ export async function approvePending(options) {
10
11
  const config = await loadConfigFile(repoRoot);
11
12
  const pending = await loadApprovalState(repoRoot, 'pending-approvals.json', config);
12
13
  const match = pending.approvals.find((approval) => approval.approvalId === options.approvalId);
14
+ if (match?.reason === JUDGE_CLOUD_CONSENT_REASON) {
15
+ const result = await recordCapabilityApproval({
16
+ approvalId: options.approvalId,
17
+ config,
18
+ token: options.token,
19
+ requireSignedToken: config.approvalSigning.required,
20
+ store: createCapabilityApprovalStore(repoRoot, config),
21
+ });
22
+ return { ok: result.ok, message: result.message };
23
+ }
13
24
  if (match?.kind === 'egress') {
14
25
  const result = await recordEgressApproval({
15
26
  approvalId: options.approvalId,
@@ -0,0 +1,70 @@
1
+ import { type JudgeProviderId } from '../core/verdict/judge-catalog.js';
2
+ import type { AdapterName, InitOptions } from '../types.js';
3
+ export declare const BELAY_CONFIG_SUBCOMMANDS: readonly ["list", "get", "set", "unset", "credential", "judge"];
4
+ export type BelayConfigSubcommand = (typeof BELAY_CONFIG_SUBCOMMANDS)[number];
5
+ export interface BelayConfigOptions {
6
+ targetDir?: string;
7
+ subcommand?: BelayConfigSubcommand;
8
+ path?: string;
9
+ value?: string;
10
+ json?: boolean;
11
+ }
12
+ export interface BelayConfigCredentialOptions {
13
+ targetDir?: string;
14
+ action: 'mode' | 'set' | 'clear';
15
+ mode?: 'project' | 'apiKey';
16
+ keyStdin?: boolean;
17
+ keyEnv?: string;
18
+ }
19
+ export interface ConfigWizardAnswers {
20
+ adapter: AdapterName;
21
+ scope: 'project' | 'global';
22
+ withSkill: boolean;
23
+ judgeProviderId: JudgeProviderId;
24
+ judgeCredentialMode?: 'project' | 'apiKey';
25
+ judgeEndpoint?: string;
26
+ acceptCloud: boolean;
27
+ dogfood: boolean;
28
+ }
29
+ export declare function parseAdapter(value: string | undefined): AdapterName;
30
+ export declare function parseScope(value: string | undefined): 'project' | 'global';
31
+ export declare function parseYesNo(value: string | undefined, defaultValue: boolean): boolean;
32
+ export declare function parseJudgeProviderId(value: string | undefined, defaultId: JudgeProviderId | string): JudgeProviderId;
33
+ export declare function buildInitOptionsFromConfigAnswers(answers: ConfigWizardAnswers, targetDir?: string): InitOptions;
34
+ export declare function runBelayConfigCredential(options: BelayConfigCredentialOptions): Promise<string>;
35
+ export interface BelayConfigInteractiveOptions {
36
+ targetDir?: string;
37
+ /** @internal test helper — canned readline answers in order */
38
+ prompts?: string[];
39
+ /** Suppress the interactive header when nested under runBelayConfigInteractive */
40
+ skipBanner?: boolean;
41
+ }
42
+ export declare function resolveBelayConfigInteractiveMode(repoRoot: string): Promise<'full' | 'judge-only'>;
43
+ export declare function runBelayConfigFullInteractive(options?: BelayConfigInteractiveOptions): Promise<{
44
+ repoRoot: string;
45
+ withSkill: boolean;
46
+ dogfood: boolean;
47
+ adapter: AdapterName;
48
+ }>;
49
+ export declare function runBelayConfigJudgeOnlyInteractive(options?: BelayConfigInteractiveOptions): Promise<{
50
+ repoRoot: string;
51
+ adapter: AdapterName;
52
+ }>;
53
+ export declare function runBelayConfigInteractive(options?: BelayConfigInteractiveOptions): Promise<{
54
+ repoRoot: string;
55
+ adapter: AdapterName;
56
+ }>;
57
+ export declare function runBelayConfig(options?: BelayConfigOptions): Promise<string | Record<string, unknown> | {
58
+ providerId: import("../core/config.js").JudgeProviderId | import("../core/config.js").DeprecatedJudgeProviderId | undefined;
59
+ provider: import("../core/config.js").JudgeProvider;
60
+ endpoint: string | null;
61
+ model: string;
62
+ modelResolved: string;
63
+ transport: import("../core/verdict/judge.js").Tier1JudgeTransport;
64
+ credential: import("../core/judge-api-key.js").ResolvedJudgeCredential;
65
+ cloudConsent: import("../core/config.js").JudgeCloudConsent | null;
66
+ cloudJudgeActive: boolean;
67
+ } | {
68
+ repoRoot: string;
69
+ adapter: AdapterName;
70
+ }>;