@guilz-dev/belay 0.2.0 → 0.4.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 (180) hide show
  1. package/README.md +39 -12
  2. package/dist/adapters/cursor/runtime-entry.js +1 -0
  3. package/dist/adapters/shared/gate-runtime.d.ts +11 -14
  4. package/dist/adapters/shared/gate-runtime.js +289 -23
  5. package/dist/bundle/claude-runtime.mjs +4435 -708
  6. package/dist/bundle/codex-runtime.mjs +4448 -710
  7. package/dist/bundle/cursor-runtime.mjs +4437 -710
  8. package/dist/cli.js +333 -11
  9. package/dist/commands/approve.d.ts +1 -0
  10. package/dist/commands/approve.js +56 -26
  11. package/dist/commands/config.d.ts +70 -0
  12. package/dist/commands/config.js +577 -0
  13. package/dist/commands/doctor.js +13 -2
  14. package/dist/commands/explain.js +6 -1
  15. package/dist/commands/harvest.d.ts +29 -0
  16. package/dist/commands/harvest.js +72 -0
  17. package/dist/commands/health-snapshot.d.ts +6 -0
  18. package/dist/commands/health-snapshot.js +34 -0
  19. package/dist/commands/judge.d.ts +100 -0
  20. package/dist/commands/judge.js +304 -0
  21. package/dist/commands/metrics.js +34 -2
  22. package/dist/commands/quality.d.ts +38 -0
  23. package/dist/commands/quality.js +105 -0
  24. package/dist/commands/simulate.d.ts +1 -0
  25. package/dist/commands/simulate.js +7 -2
  26. package/dist/commands/standing-allow.d.ts +10 -0
  27. package/dist/commands/standing-allow.js +26 -0
  28. package/dist/commands/status.js +2 -2
  29. package/dist/commands/stdin-key.d.ts +1 -0
  30. package/dist/commands/stdin-key.js +8 -0
  31. package/dist/commands/tui.d.ts +48 -0
  32. package/dist/commands/tui.js +150 -0
  33. package/dist/config-io.js +10 -5
  34. package/dist/conformance/guarantee-table.js +12 -0
  35. package/dist/conformance/types.d.ts +2 -0
  36. package/dist/core/approval-replay-cli.d.ts +7 -0
  37. package/dist/core/approval-replay-cli.js +36 -0
  38. package/dist/core/approval-replay.d.ts +45 -0
  39. package/dist/core/approval-replay.js +141 -0
  40. package/dist/core/approval-service.d.ts +15 -0
  41. package/dist/core/approval-service.js +47 -2
  42. package/dist/core/approval.d.ts +24 -3
  43. package/dist/core/approval.js +47 -4
  44. package/dist/core/audit-analysis.d.ts +7 -1
  45. package/dist/core/audit-analysis.js +111 -0
  46. package/dist/core/audit-io.d.ts +2 -0
  47. package/dist/core/audit-io.js +14 -0
  48. package/dist/core/audit-metrics.d.ts +7 -0
  49. package/dist/core/audit-metrics.js +24 -4
  50. package/dist/core/audit-query.d.ts +1 -0
  51. package/dist/core/audit-query.js +3 -0
  52. package/dist/core/audit-replay-context.d.ts +35 -0
  53. package/dist/core/audit-replay-context.js +88 -0
  54. package/dist/core/audit-types.d.ts +24 -1
  55. package/dist/core/audit-types.js +1 -1
  56. package/dist/core/capability/index.d.ts +4 -3
  57. package/dist/core/capability/index.js +3 -2
  58. package/dist/core/capability/paths.d.ts +2 -1
  59. package/dist/core/capability/paths.js +76 -8
  60. package/dist/core/capability/reasons.d.ts +3 -0
  61. package/dist/core/capability/reasons.js +8 -0
  62. package/dist/core/capability/trusted-workspace-roots.d.ts +25 -0
  63. package/dist/core/capability/trusted-workspace-roots.js +149 -0
  64. package/dist/core/capability/types.d.ts +11 -1
  65. package/dist/core/capability-approval.d.ts +2 -1
  66. package/dist/core/capability-approval.js +100 -2
  67. package/dist/core/classify-subagent.js +2 -20
  68. package/dist/core/classify-tool.js +129 -30
  69. package/dist/core/config-layers.js +2 -1
  70. package/dist/core/config.d.ts +40 -2
  71. package/dist/core/config.js +159 -11
  72. package/dist/core/credential-store.d.ts +11 -0
  73. package/dist/core/credential-store.js +60 -0
  74. package/dist/core/gate-engine.js +104 -13
  75. package/dist/core/harvest.d.ts +53 -0
  76. package/dist/core/harvest.js +276 -0
  77. package/dist/core/index.d.ts +9 -3
  78. package/dist/core/index.js +6 -2
  79. package/dist/core/integrity.d.ts +2 -0
  80. package/dist/core/integrity.js +13 -0
  81. package/dist/core/judge-api-key.d.ts +19 -0
  82. package/dist/core/judge-api-key.js +74 -0
  83. package/dist/core/judge-cloud-consent.d.ts +13 -0
  84. package/dist/core/judge-cloud-consent.js +38 -0
  85. package/dist/core/judge-config.d.ts +41 -4
  86. package/dist/core/judge-config.js +263 -57
  87. package/dist/core/judge-doctor.d.ts +9 -1
  88. package/dist/core/judge-doctor.js +199 -94
  89. package/dist/core/judge-model-discovery.d.ts +24 -0
  90. package/dist/core/judge-model-discovery.js +168 -0
  91. package/dist/core/judge-model-policy.d.ts +5 -0
  92. package/dist/core/judge-model-policy.js +21 -0
  93. package/dist/core/judge-runtime-detection.d.ts +9 -0
  94. package/dist/core/judge-runtime-detection.js +68 -0
  95. package/dist/core/path-utils.d.ts +11 -0
  96. package/dist/core/path-utils.js +56 -6
  97. package/dist/core/reclassify.d.ts +3 -0
  98. package/dist/core/reclassify.js +44 -14
  99. package/dist/core/replay-scrub.d.ts +9 -0
  100. package/dist/core/replay-scrub.js +43 -0
  101. package/dist/core/shell-tokenizer.d.ts +2 -0
  102. package/dist/core/shell-tokenizer.js +82 -23
  103. package/dist/core/standing-allow.d.ts +50 -0
  104. package/dist/core/standing-allow.js +175 -0
  105. package/dist/core/transactional/diff-evaluator.js +1 -19
  106. package/dist/core/types.d.ts +17 -0
  107. package/dist/core/verdict/adapter.d.ts +1 -0
  108. package/dist/core/verdict/adapter.js +17 -2
  109. package/dist/core/verdict/containment.d.ts +8 -2
  110. package/dist/core/verdict/containment.js +45 -10
  111. package/dist/core/verdict/judge-audit.d.ts +1 -0
  112. package/dist/core/verdict/judge-audit.js +32 -1
  113. package/dist/core/verdict/judge-baseline.d.ts +20 -0
  114. package/dist/core/verdict/judge-baseline.js +48 -0
  115. package/dist/core/verdict/judge-broker-service.d.ts +42 -0
  116. package/dist/core/verdict/judge-broker-service.js +279 -0
  117. package/dist/core/verdict/judge-catalog.d.ts +40 -0
  118. package/dist/core/verdict/judge-catalog.js +148 -0
  119. package/dist/core/verdict/judge-cli-fingerprint.d.ts +5 -0
  120. package/dist/core/verdict/judge-cli-fingerprint.js +61 -0
  121. package/dist/core/verdict/judge-cli.d.ts +46 -0
  122. package/dist/core/verdict/judge-cli.js +401 -0
  123. package/dist/core/verdict/judge-factory.d.ts +15 -4
  124. package/dist/core/verdict/judge-factory.js +127 -15
  125. package/dist/core/verdict/judge-provider-matrix.d.ts +20 -0
  126. package/dist/core/verdict/judge-provider-matrix.js +66 -0
  127. package/dist/core/verdict/judge-runtime-config.d.ts +42 -0
  128. package/dist/core/verdict/judge-runtime-config.js +92 -0
  129. package/dist/core/verdict/judge-session-broker.d.ts +48 -0
  130. package/dist/core/verdict/judge-session-broker.js +195 -0
  131. package/dist/core/verdict/judge-session-guard.d.ts +27 -0
  132. package/dist/core/verdict/judge-session-guard.js +91 -0
  133. package/dist/core/verdict/judge-session-kill-switch.d.ts +11 -0
  134. package/dist/core/verdict/judge-session-kill-switch.js +43 -0
  135. package/dist/core/verdict/judge-session-mutex.d.ts +9 -0
  136. package/dist/core/verdict/judge-session-mutex.js +23 -0
  137. package/dist/core/verdict/judge-shadow.d.ts +24 -0
  138. package/dist/core/verdict/judge-shadow.js +116 -0
  139. package/dist/core/verdict/judge-transport.d.ts +40 -0
  140. package/dist/core/verdict/judge-transport.js +258 -0
  141. package/dist/core/verdict/judge.d.ts +33 -1
  142. package/dist/core/verdict/judge.js +84 -14
  143. package/dist/core/verdict/parser.js +6 -1
  144. package/dist/core/verdict/persistent-paths.d.ts +8 -0
  145. package/dist/core/verdict/persistent-paths.js +52 -0
  146. package/dist/core/verdict/types.d.ts +19 -2
  147. package/dist/core/verdict/verdict.js +166 -49
  148. package/dist/corpus/adversarial-probe.d.ts +86 -0
  149. package/dist/corpus/adversarial-probe.js +220 -0
  150. package/dist/corpus/evaluate.d.ts +45 -12
  151. package/dist/corpus/evaluate.js +62 -5
  152. package/dist/corpus/gates.d.ts +35 -0
  153. package/dist/corpus/gates.js +81 -0
  154. package/dist/corpus/judge-accuracy.d.ts +8 -0
  155. package/dist/corpus/judge-accuracy.js +39 -0
  156. package/dist/corpus/must-allow-commands.d.ts +5 -0
  157. package/dist/corpus/must-allow-commands.js +13 -0
  158. package/dist/corpus/mutators.d.ts +28 -0
  159. package/dist/corpus/mutators.js +151 -0
  160. package/dist/corpus/ratchet.d.ts +42 -0
  161. package/dist/corpus/ratchet.js +116 -0
  162. package/dist/corpus/runtime-match.d.ts +19 -0
  163. package/dist/corpus/runtime-match.js +45 -0
  164. package/dist/corpus/standing-allow-catalog.generated.d.ts +13 -0
  165. package/dist/corpus/standing-allow-catalog.generated.js +79 -0
  166. package/dist/corpus/types.d.ts +47 -0
  167. package/dist/corpus/types.js +157 -0
  168. package/dist/installer.js +69 -15
  169. package/dist/judge-broker-daemon.d.ts +1 -0
  170. package/dist/judge-broker-daemon.js +123 -0
  171. package/dist/services/sandbox-service.d.ts +1 -0
  172. package/dist/services/sandbox-service.js +2 -0
  173. package/dist/types.d.ts +7 -0
  174. package/dist/version.d.ts +1 -1
  175. package/dist/version.js +1 -1
  176. package/package.json +7 -3
  177. package/skills/belay/SKILL.md +15 -8
  178. package/skills/belay/belay-approve.md +17 -0
  179. package/dist/commands/init-wizard.d.ts +0 -21
  180. package/dist/commands/init-wizard.js +0 -63
package/dist/cli.js CHANGED
@@ -6,13 +6,17 @@ import { auditProject, formatAuditReport } from './commands/audit.js';
6
6
  import { doctorProject, formatDoctorReport } from './commands/doctor.js';
7
7
  import { dogfoodProject, formatDogfoodResult } from './commands/dogfood.js';
8
8
  import { explainCommand, formatExplainReport } from './commands/explain.js';
9
+ import { formatHarvestReport, harvestApplyProject, harvestListProject } from './commands/harvest.js';
9
10
  import { formatMetricsReport, metricsProject } from './commands/metrics.js';
11
+ import { formatQualityReport, qualityCheck } from './commands/quality.js';
10
12
  import { formatRecoverReport, recoverProject } from './commands/recover.js';
11
13
  import { formatReport, reportProject } from './commands/report.js';
12
14
  import { revokeApproval } from './commands/revoke.js';
13
15
  import { formatSimulateReport, simulateProject } from './commands/simulate.js';
16
+ import { revokeStandingAllow } from './commands/standing-allow.js';
14
17
  import { formatStatusReport, statusProject } from './commands/status.js';
15
18
  import { loadConfigFile } from './config-io.js';
19
+ import { rejectDeprecatedJudgeModelAuto } from './core/judge-model-policy.js';
16
20
  import { initProject, upgradeProject } from './installer.js';
17
21
  import { egressEnv, egressStatus, formatEgressStatusReport, startEgressProxy, stopEgressProxy, } from './services/egress-service.js';
18
22
  import { formatSandboxStatusReport, sandboxStatus } from './services/sandbox-service.js';
@@ -64,6 +68,7 @@ function parseArgs(argv) {
64
68
  continue;
65
69
  }
66
70
  if (token === '--judge-profile') {
71
+ process.stderr.write('Warning: --judge-profile is deprecated; use belay config set judge.providerId <id> after init.\n');
67
72
  const next = rest[index + 1];
68
73
  if (!next || !['local-ollama', 'cursor', 'claude', 'codex'].includes(next)) {
69
74
  throw new Error('--judge-profile requires local-ollama, cursor, claude, or codex.');
@@ -93,8 +98,9 @@ function parseArgs(argv) {
93
98
  if (token === '--judge-model') {
94
99
  const next = rest[index + 1];
95
100
  if (!next) {
96
- throw new Error('--judge-model requires a model id or auto.');
101
+ throw new Error('--judge-model requires a model id.');
97
102
  }
103
+ rejectDeprecatedJudgeModelAuto(next);
98
104
  options.judgeModel = next;
99
105
  index += 1;
100
106
  continue;
@@ -103,6 +109,61 @@ function parseArgs(argv) {
103
109
  options.acceptCloudJudge = true;
104
110
  continue;
105
111
  }
112
+ if (token === '--migrate-judge-default') {
113
+ options.migrateJudgeDefault = true;
114
+ continue;
115
+ }
116
+ if (token === '--accept-cloud') {
117
+ options.acceptCloud = true;
118
+ continue;
119
+ }
120
+ if (token === '--cloud-consent-approval-id') {
121
+ const next = rest[index + 1];
122
+ if (!next) {
123
+ throw new Error('--cloud-consent-approval-id requires an approval id.');
124
+ }
125
+ options.cloudConsentApprovalId = next;
126
+ index += 1;
127
+ continue;
128
+ }
129
+ if (token === '--credential') {
130
+ const next = rest[index + 1];
131
+ if (!next || !['project', 'apiKey'].includes(next)) {
132
+ throw new Error('--credential requires project or apiKey.');
133
+ }
134
+ options.credentialMode = next;
135
+ index += 1;
136
+ continue;
137
+ }
138
+ if (token === '--key-stdin') {
139
+ options.keyStdin = true;
140
+ continue;
141
+ }
142
+ if (token === '--key-env') {
143
+ const next = rest[index + 1];
144
+ if (!next) {
145
+ throw new Error('--key-env requires an environment variable name.');
146
+ }
147
+ options.keyEnv = next;
148
+ index += 1;
149
+ continue;
150
+ }
151
+ if (token === '--timeout') {
152
+ const next = rest[index + 1];
153
+ if (!next) {
154
+ throw new Error('--timeout requires milliseconds.');
155
+ }
156
+ options.judgeTimeoutMs = Number(next);
157
+ index += 1;
158
+ continue;
159
+ }
160
+ if (token === '--live-probe') {
161
+ if (command !== 'judge') {
162
+ throw new Error('--live-probe is only valid for judge test.');
163
+ }
164
+ options.judgeLiveProbe = true;
165
+ continue;
166
+ }
106
167
  if (token === '--json') {
107
168
  options.json = true;
108
169
  continue;
@@ -127,6 +188,24 @@ function parseArgs(argv) {
127
188
  index += 1;
128
189
  continue;
129
190
  }
191
+ if (token === '--outcome') {
192
+ const next = rest[index + 1];
193
+ if (!next || !['provably-benign', 'accepted-benign', 'reject'].includes(next)) {
194
+ throw new Error('--outcome requires provably-benign, accepted-benign, or reject.');
195
+ }
196
+ options.harvestOutcome = next;
197
+ index += 1;
198
+ continue;
199
+ }
200
+ if (token === '--corpus') {
201
+ const next = rest[index + 1];
202
+ if (!next) {
203
+ throw new Error('--corpus requires a path.');
204
+ }
205
+ options.corpusPath = next;
206
+ index += 1;
207
+ continue;
208
+ }
130
209
  if (token === '--kind') {
131
210
  const next = rest[index + 1];
132
211
  if (!next) {
@@ -201,11 +280,18 @@ function parseArgs(argv) {
201
280
  index += 1;
202
281
  continue;
203
282
  }
283
+ if (token === '--replay') {
284
+ if (command !== 'approve') {
285
+ throw new Error('--replay is only valid for approve.');
286
+ }
287
+ options.approveReplay = true;
288
+ continue;
289
+ }
204
290
  if (token === '--scope') {
205
291
  const next = rest[index + 1];
206
292
  if (command === 'approve') {
207
- if (!next || !['once', 'domain', 'path'].includes(next)) {
208
- throw new Error('--scope requires once, domain, or path.');
293
+ if (!next || !['once', 'domain', 'path', 'workspace-root'].includes(next)) {
294
+ throw new Error('--scope requires once, domain, path, or workspace-root.');
209
295
  }
210
296
  options.approveScope = next;
211
297
  }
@@ -264,6 +350,9 @@ function parseArgs(argv) {
264
350
  if (command === 'recover') {
265
351
  options.recoverCommand = next;
266
352
  }
353
+ else if (command === 'harvest') {
354
+ options.harvestCommand = next;
355
+ }
267
356
  else {
268
357
  options.explainCommand = next;
269
358
  }
@@ -316,6 +405,84 @@ function parseArgs(argv) {
316
405
  }
317
406
  throw new Error('sandbox requires subcommand: status');
318
407
  }
408
+ if (command === 'standing-allow' && !options.standingAllowSubcommand) {
409
+ if (token === 'revoke') {
410
+ options.standingAllowSubcommand = token;
411
+ continue;
412
+ }
413
+ throw new Error('standing-allow requires subcommand: revoke');
414
+ }
415
+ if (command === 'harvest' && !options.harvestSubcommand) {
416
+ if (token === 'list' || token === 'apply') {
417
+ options.harvestSubcommand = token;
418
+ continue;
419
+ }
420
+ throw new Error('harvest requires subcommand: list or apply');
421
+ }
422
+ if (command === 'judge' && !options.judgeSubcommand) {
423
+ if (token === 'status' ||
424
+ token === 'list' ||
425
+ token === 'use' ||
426
+ token === 'test' ||
427
+ token === 'bench' ||
428
+ token === 'consent') {
429
+ options.judgeSubcommand = token;
430
+ continue;
431
+ }
432
+ throw new Error('judge requires subcommand: status, list, use, test, bench, or consent');
433
+ }
434
+ if (command === 'config' && !options.configSubcommand) {
435
+ if (token === 'list' ||
436
+ token === 'get' ||
437
+ token === 'set' ||
438
+ token === 'unset' ||
439
+ token === 'credential' ||
440
+ token === 'judge') {
441
+ options.configSubcommand = token;
442
+ continue;
443
+ }
444
+ throw new Error('config requires subcommand: list, get, set, unset, credential, or judge');
445
+ }
446
+ if (command === 'config' &&
447
+ options.configSubcommand === 'credential' &&
448
+ !options.credentialAction) {
449
+ if (token === 'mode' || token === 'set' || token === 'clear') {
450
+ options.credentialAction = token;
451
+ continue;
452
+ }
453
+ throw new Error('config credential requires action: mode, set, or clear');
454
+ }
455
+ if (command === 'config' &&
456
+ options.configSubcommand === 'credential' &&
457
+ options.credentialAction === 'mode' &&
458
+ !options.credentialMode) {
459
+ if (token === 'project' || token === 'apiKey') {
460
+ options.credentialMode = token;
461
+ continue;
462
+ }
463
+ throw new Error('config credential mode requires project or apiKey');
464
+ }
465
+ if (command === 'config' &&
466
+ (options.configSubcommand === 'get' ||
467
+ options.configSubcommand === 'set' ||
468
+ options.configSubcommand === 'unset') &&
469
+ !options.configKey) {
470
+ options.configKey = token;
471
+ continue;
472
+ }
473
+ if (command === 'config' &&
474
+ options.configSubcommand === 'set' &&
475
+ options.configKey &&
476
+ !options.configValue) {
477
+ options.configValue = token;
478
+ continue;
479
+ }
480
+ if (command === 'judge' &&
481
+ (options.judgeSubcommand === 'use' || options.judgeSubcommand === 'consent') &&
482
+ !options.judgeUseProvider) {
483
+ options.judgeUseProvider = token;
484
+ continue;
485
+ }
319
486
  if ((command === 'revoke' || command === 'approve') && !options.approvalId) {
320
487
  options.approvalId = token;
321
488
  continue;
@@ -329,13 +496,22 @@ function printHelp() {
329
496
  process.stdout.write(`${c}
330
497
 
331
498
  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>]
499
+ ${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]
500
+ ${c} config [--target <dir>] [--json]
501
+ ${c} config list|get|set|unset|judge [--target <dir>] [--json]
502
+ ${c} config get <judge.path> [--target <dir>] [--json]
503
+ ${c} config set <judge.path> <value> [--target <dir>]
504
+ ${c} config unset <judge.path> [--target <dir>]
505
+ ${c} config credential mode <project|apiKey> [--target <dir>]
506
+ ${c} config credential set [--key-stdin] [--key-env <NAME>] [--target <dir>]
507
+ ${c} config credential clear [--target <dir>]
508
+ (--adapter selects host; fresh init picks matching judge providerId: cursor/claude/codex)
334
509
  (--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]
510
+ ${c} upgrade [--target <dir>] [--adapter cursor|claude|codex] [--scope project|global] [--with-skill] [--migrate-judge-default]
336
511
  ${c} dogfood [--target <dir>] [--adapter cursor|claude|codex] [--enforce] [--force]
337
512
  ${c} doctor [--target <dir>] [--adapter cursor|claude|codex] [--json] [--fix] [--dry-run]
338
513
  ${c} metrics [--target <dir>] [--json]
514
+ ${c} quality [--target <dir>] [--corpus <path>] [--json]
339
515
  ${c} report [--target <dir>] [--since <iso>] [--until <iso>] [--limit <n>] [--json]
340
516
  ${c} recover [--target <dir>] [--since <iso>] [--fingerprint <fp>] [--command "<text>"] [--limit <n>] [--json]
341
517
  (--limit picks the Nth recover candidate after priority ranking: local_mutation first, then recency; 1 = highest priority, default 1)
@@ -345,8 +521,15 @@ Usage:
345
521
  ${c} explain [--target <dir>] [--cwd <dir>] [--kind shell|tool|subagent] [--tool <name>] [--payload-json <json>] [--command <text>] [--json] [-- <command>]
346
522
  ${c} egress <start|stop|status|env> [--target <dir>] [--json]
347
523
  ${c} sandbox status [--target <dir>] [--json]
348
- ${c} approve <approval-id> [--scope once|domain|path] [--path <path>] [--token <signed-token>] [--target <dir>]
524
+ ${c} judge <status|list|use|test|bench|consent> [--target <dir>] [--json]
525
+ ${c} judge test [--target <dir>] [--json] [--live-probe]
526
+ ${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>]
527
+ ${c} judge consent <ollama|codex|claude|cursor> [--endpoint <url>]
528
+ ${c} approve <approval-id> [--replay] [--scope once|domain|path|workspace-root] [--path <path>] [--token <signed-token>] [--target <dir>]
349
529
  ${c} revoke <approval-id> [--target <dir>]
530
+ ${c} standing-allow revoke --fingerprint <fp> [--kind shell|tool|subagent] [--target <dir>]
531
+ ${c} harvest list [--target <dir>] [--since <iso>] [--until <iso>] [--json]
532
+ ${c} harvest apply --command "<text>" --outcome provably-benign|accepted-benign|reject [--reason <r>] [--corpus <path>] [--target <dir>]
350
533
  `);
351
534
  }
352
535
  async function main() {
@@ -360,10 +543,10 @@ async function main() {
360
543
  process.stdout.write(`${PACKAGE_VERSION}\n`);
361
544
  return;
362
545
  }
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`);
546
+ const DEPRECATED_COMMANDS = new Set(['init-wizard']);
547
+ if (DEPRECATED_COMMANDS.has(command)) {
548
+ process.stderr.write(`${command} is removed. Use \`belay config\` for interactive setup, or \`belay init\` for non-interactive install.\n`);
549
+ process.exitCode = 1;
367
550
  return;
368
551
  }
369
552
  if (command === 'init') {
@@ -379,6 +562,7 @@ async function main() {
379
562
  judgeModel: options.judgeModel,
380
563
  judgeEndpoint: options.judgeEndpoint,
381
564
  acceptCloudJudge: options.acceptCloudJudge,
565
+ migrateJudgeDefault: options.migrateJudgeDefault,
382
566
  });
383
567
  const extras = [
384
568
  `adapter=${result.adapter}`,
@@ -405,6 +589,7 @@ async function main() {
405
589
  withSkill: options.withSkill,
406
590
  adapter: options.adapter,
407
591
  scope: options.installScope,
592
+ migrateJudgeDefault: options.migrateJudgeDefault,
408
593
  });
409
594
  const upgraded = await loadConfigFile(result.repoRoot, result.adapter);
410
595
  if (upgraded.policy.modelAssist.enabled) {
@@ -413,6 +598,73 @@ async function main() {
413
598
  process.stdout.write(`Upgraded belay (${result.adapter}) in ${result.repoRoot}.\n`);
414
599
  return;
415
600
  }
601
+ if (command === 'judge') {
602
+ const { runJudgeCommand } = await import('./commands/judge.js');
603
+ if (!options.judgeSubcommand) {
604
+ throw new Error('judge requires subcommand: status, list, use, or test');
605
+ }
606
+ if (options.judgeLiveProbe && options.judgeSubcommand !== 'test') {
607
+ throw new Error('--live-probe is only valid for judge test.');
608
+ }
609
+ const result = await runJudgeCommand({
610
+ targetDir: options.targetDir,
611
+ json: options.json,
612
+ subcommand: options.judgeSubcommand,
613
+ providerId: options.judgeUseProvider,
614
+ model: options.judgeModel,
615
+ endpoint: options.judgeEndpoint,
616
+ timeoutMs: options.judgeTimeoutMs,
617
+ acceptCloud: options.acceptCloud,
618
+ cloudConsentApprovalId: options.cloudConsentApprovalId,
619
+ credentialMode: options.credentialMode,
620
+ keyStdin: options.keyStdin,
621
+ keyEnv: options.keyEnv,
622
+ liveProbe: options.judgeLiveProbe,
623
+ });
624
+ if (options.json && typeof result === 'object') {
625
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
626
+ }
627
+ else {
628
+ process.stdout.write(`${String(result)}\n`);
629
+ }
630
+ return;
631
+ }
632
+ if (command === 'config') {
633
+ if (options.configSubcommand === 'credential') {
634
+ if (!options.credentialAction) {
635
+ throw new Error('config credential requires action: mode, set, or clear');
636
+ }
637
+ const { runBelayConfigCredential } = await import('./commands/config.js');
638
+ const result = await runBelayConfigCredential({
639
+ targetDir: options.targetDir,
640
+ action: options.credentialAction,
641
+ mode: options.credentialMode,
642
+ keyStdin: options.keyStdin,
643
+ keyEnv: options.keyEnv,
644
+ });
645
+ process.stdout.write(`${String(result)}\n`);
646
+ return;
647
+ }
648
+ const { runBelayConfig } = await import('./commands/config.js');
649
+ const result = await runBelayConfig({
650
+ targetDir: options.targetDir,
651
+ subcommand: options.configSubcommand,
652
+ path: options.configKey,
653
+ value: options.configValue,
654
+ json: options.json,
655
+ });
656
+ if (options.json && typeof result === 'object') {
657
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
658
+ }
659
+ else if (typeof result === 'object' && result && 'repoRoot' in result) {
660
+ const initResult = result;
661
+ process.stdout.write(`Initialized belay in ${initResult.repoRoot}${initResult.withSkill ? ' (with skill)' : ''}.\n`);
662
+ }
663
+ else {
664
+ process.stdout.write(`${String(result)}\n`);
665
+ }
666
+ return;
667
+ }
416
668
  if (command === 'doctor') {
417
669
  const report = await doctorProject({
418
670
  targetDir: options.targetDir,
@@ -489,6 +741,21 @@ async function main() {
489
741
  }
490
742
  return;
491
743
  }
744
+ if (command === 'quality') {
745
+ const report = await qualityCheck({
746
+ targetDir: options.targetDir,
747
+ corpusDir: options.corpusPath,
748
+ json: options.json,
749
+ });
750
+ if (options.json) {
751
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
752
+ }
753
+ else {
754
+ process.stdout.write(`${formatQualityReport(report)}\n`);
755
+ }
756
+ process.exitCode = report.ok ? 0 : 1;
757
+ return;
758
+ }
492
759
  if (command === 'report') {
493
760
  const report = await reportProject({
494
761
  targetDir: options.targetDir,
@@ -505,6 +772,42 @@ async function main() {
505
772
  }
506
773
  return;
507
774
  }
775
+ if (command === 'harvest') {
776
+ if (options.harvestSubcommand === 'list') {
777
+ const report = await harvestListProject({
778
+ targetDir: options.targetDir,
779
+ since: options.since,
780
+ until: options.until,
781
+ json: options.json,
782
+ });
783
+ if (options.json) {
784
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
785
+ }
786
+ else {
787
+ process.stdout.write(`${formatHarvestReport(report)}\n`);
788
+ }
789
+ return;
790
+ }
791
+ if (options.harvestSubcommand === 'apply') {
792
+ if (!options.harvestCommand) {
793
+ throw new Error('harvest apply requires --command.');
794
+ }
795
+ if (!options.harvestOutcome) {
796
+ throw new Error('harvest apply requires --outcome.');
797
+ }
798
+ const result = await harvestApplyProject({
799
+ targetDir: options.targetDir,
800
+ command: options.harvestCommand,
801
+ outcome: options.harvestOutcome,
802
+ reason: options.reason,
803
+ corpusPath: options.corpusPath,
804
+ });
805
+ process.stdout.write(`${result.message}\n`);
806
+ process.exitCode = result.ok ? 0 : 1;
807
+ return;
808
+ }
809
+ throw new Error('harvest requires subcommand: list or apply');
810
+ }
508
811
  if (command === 'recover') {
509
812
  const report = await recoverProject({
510
813
  targetDir: options.targetDir,
@@ -614,6 +917,7 @@ async function main() {
614
917
  token: options.approvalToken,
615
918
  scope: options.approveScope,
616
919
  scopePath: options.approvePath,
920
+ replay: options.approveReplay,
617
921
  });
618
922
  process.stdout.write(`${result.message}\n`);
619
923
  process.exitCode = result.ok ? 0 : 1;
@@ -631,6 +935,24 @@ async function main() {
631
935
  process.exitCode = result.ok ? 0 : 1;
632
936
  return;
633
937
  }
938
+ if (command === 'standing-allow') {
939
+ if (options.standingAllowSubcommand === 'revoke') {
940
+ if (!options.fingerprint) {
941
+ throw new Error('standing-allow revoke requires --fingerprint.');
942
+ }
943
+ const result = await revokeStandingAllow({
944
+ targetDir: options.targetDir,
945
+ fingerprint: options.fingerprint,
946
+ kind: options.kind === 'tool' || options.kind === 'subagent' || options.kind === 'shell'
947
+ ? options.kind
948
+ : undefined,
949
+ });
950
+ process.stdout.write(`${result.message}\n`);
951
+ process.exitCode = result.ok ? 0 : 1;
952
+ return;
953
+ }
954
+ throw new Error('standing-allow requires subcommand: revoke');
955
+ }
634
956
  throw new Error(`Unknown command: ${command}`);
635
957
  }
636
958
  catch (error) {
@@ -7,6 +7,7 @@ export interface ApproveOptions {
7
7
  token?: string;
8
8
  scope?: ApproveScope;
9
9
  scopePath?: string;
10
+ replay?: boolean;
10
11
  }
11
12
  export declare function approvePending(options: ApproveOptions): Promise<{
12
13
  ok: boolean;
@@ -1,6 +1,8 @@
1
1
  import path from 'node:path';
2
- import { approvedApprovalsPath, loadApprovalState, loadConfigFile, pendingApprovalsPath, saveApprovalState, } from '../config-io.js';
3
- import { recordApproval } from '../core/approval-service.js';
2
+ import { loadApprovalState, loadConfigFile } from '../config-io.js';
3
+ import { canAutoReplay, getExecutionLeaseMs, replayShellCommand } from '../core/approval-replay.js';
4
+ import { consumeApprovedAfterCliReplay, createGateApprovalStore, recordApproval, } from '../core/approval-service.js';
5
+ import { JUDGE_CLOUD_CONSENT_REASON } from '../core/capability/reasons.js';
4
6
  import { recordCapabilityApproval } from '../core/capability-approval.js';
5
7
  import { recordEgressApproval } from '../core/egress-approval.js';
6
8
  import { createEgressApprovalStore } from '../services/egress-service.js';
@@ -10,6 +12,16 @@ export async function approvePending(options) {
10
12
  const config = await loadConfigFile(repoRoot);
11
13
  const pending = await loadApprovalState(repoRoot, 'pending-approvals.json', config);
12
14
  const match = pending.approvals.find((approval) => approval.approvalId === options.approvalId);
15
+ if (match?.reason === JUDGE_CLOUD_CONSENT_REASON) {
16
+ const result = await recordCapabilityApproval({
17
+ approvalId: options.approvalId,
18
+ config,
19
+ token: options.token,
20
+ requireSignedToken: config.approvalSigning.required,
21
+ store: createCapabilityApprovalStore(repoRoot, config),
22
+ });
23
+ return { ok: result.ok, message: result.message };
24
+ }
13
25
  if (match?.kind === 'egress') {
14
26
  const result = await recordEgressApproval({
15
27
  approvalId: options.approvalId,
@@ -21,11 +33,11 @@ export async function approvePending(options) {
21
33
  });
22
34
  return { ok: result.ok, message: result.message };
23
35
  }
24
- if (options.scope === 'path') {
36
+ if (options.scope === 'path' || options.scope === 'workspace-root') {
25
37
  const result = await recordCapabilityApproval({
26
38
  approvalId: options.approvalId,
27
39
  config,
28
- scope: 'path',
40
+ scope: options.scope,
29
41
  scopePath: options.scopePath,
30
42
  token: options.token,
31
43
  requireSignedToken: config.approvalSigning.required,
@@ -33,33 +45,51 @@ export async function approvePending(options) {
33
45
  });
34
46
  return { ok: result.ok, message: result.message };
35
47
  }
48
+ const approvalStore = createGateApprovalStore(repoRoot, config);
36
49
  const result = await recordApproval({
37
50
  approvalId: options.approvalId,
38
51
  config,
39
52
  token: options.token,
40
53
  requireSignedToken: config.approvalSigning.required,
41
- store: {
42
- async loadPending() {
43
- const filePath = pendingApprovalsPath(repoRoot, config);
44
- return {
45
- filePath,
46
- state: await loadApprovalState(repoRoot, 'pending-approvals.json', config),
47
- };
48
- },
49
- async loadApproved() {
50
- const filePath = approvedApprovalsPath(repoRoot, config);
51
- return {
52
- filePath,
53
- state: await loadApprovalState(repoRoot, 'approved-approvals.json', config),
54
- };
55
- },
56
- async writePending(_filePath, state) {
57
- await saveApprovalState(repoRoot, 'pending-approvals.json', state, config);
58
- },
59
- async writeApproved(_filePath, state) {
60
- await saveApprovalState(repoRoot, 'approved-approvals.json', state, config);
61
- },
62
- },
54
+ store: approvalStore,
63
55
  });
56
+ if (!result.ok) {
57
+ return { ok: result.ok, message: result.message };
58
+ }
59
+ if (options.replay) {
60
+ const approval = result.approval;
61
+ if (!approval) {
62
+ return { ok: false, message: 'Approval recorded but replay envelope is missing.' };
63
+ }
64
+ if (!canAutoReplay(config, approval.kind)) {
65
+ return {
66
+ ok: false,
67
+ message: 'Replay is not enabled for this approval kind. Retry the original action manually or enable approval.autoReplayScopes.',
68
+ };
69
+ }
70
+ if (approval.kind !== 'shell' || !approval.input) {
71
+ return {
72
+ ok: false,
73
+ message: 'CLI replay is only supported for shell approvals. Retry the original tool or subagent action manually.',
74
+ };
75
+ }
76
+ const replayResult = await replayShellCommand(approval.input, approval.cwd ?? repoRoot, getExecutionLeaseMs(config));
77
+ const output = [replayResult.stdout, replayResult.stderr].filter(Boolean).join('\n').trim();
78
+ if (replayResult.exitCode === 0) {
79
+ await consumeApprovedAfterCliReplay({
80
+ approvalId: options.approvalId,
81
+ store: approvalStore,
82
+ });
83
+ return {
84
+ ok: true,
85
+ message: `Belay replay succeeded for ${options.approvalId}. Do not retry via hooks; the one-shot grant was consumed.${output ? `\n${output}` : ''}`,
86
+ };
87
+ }
88
+ const timeoutNote = replayResult.timedOut ? ' Replay timed out.' : '';
89
+ return {
90
+ ok: false,
91
+ message: `Belay replay failed for ${options.approvalId} (exit ${replayResult.exitCode}).${timeoutNote} Approval remains active for one hook retry.${output ? `\n${output}` : ''}`,
92
+ };
93
+ }
64
94
  return { ok: result.ok, message: result.message };
65
95
  }
@@ -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
+ }>;