@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/README.md CHANGED
@@ -34,8 +34,8 @@ policy to maintain.
34
34
  | **Claude Code** | Supported | `.claude/settings.json` | `.claude/belay.config.json` |
35
35
  | **Codex** | Experimental | `.codex/config.toml` | `.codex/belay.config.json` |
36
36
 
37
- Pick the adapter at install time with `--adapter cursor|claude|codex` (or let
38
- `init-wizard` prompt). Hosts use different hook event names, but Belay registers
37
+ Pick the adapter at install time with `--adapter cursor|claude|codex` (or use
38
+ `belay config` interactively). Hosts use different hook event names, but Belay registers
39
39
  the same runners (`belay-tool-gate`, `belay-before-submit`, `belay-audit`) at
40
40
  equivalent lifecycle points:
41
41
 
@@ -68,8 +68,8 @@ audit instead of guessing.
68
68
  ## Quick start
69
69
 
70
70
  ```bash
71
- # Interactive setup (prompts for adapter, scope, skill, mode)
72
- npx @guilz-dev/belay init-wizard
71
+ # Interactive setup (adapter, scope, skill, judge provider, credentials)
72
+ belay config
73
73
 
74
74
  # Or non-interactive
75
75
  npx @guilz-dev/belay init --adapter claude # Claude Code
@@ -131,6 +131,9 @@ Belay is a layered hook gate, not a static denylist. Higher layers are opt-in.
131
131
  - L3 command lists are **not security boundaries** by themselves — see
132
132
  [docs/ops/semver-policy.md](./docs/ops/semver-policy.md) and
133
133
  [docs/guarantee-table.md](./docs/guarantee-table.md).
134
+ - At default L3, **local-recoverable mutations outside the repo** pass after Tier1
135
+ (e.g. IDE plan files). To deny repo-external writes at the OS boundary, enable L1-full
136
+ (`sandbox.runtime` ≠ `none`). Tier1 requires a working judge (local Ollama by default).
134
137
  - Adversarial resistance requires the full L1 stack:
135
138
  `belay init --preset l1-full-recommended`, verified with `belay sandbox status`.
136
139
 
@@ -236,20 +239,37 @@ Notable settings:
236
239
  current OS user. `upgrade` migrates repo-local approvals in; disabling merges
237
240
  them back. File-mutation tools and shell redirects cannot write control-plane
238
241
  paths while it is enabled.
239
- - **Cloud judge** — for `judge.provider: "openai-compatible"`, set
240
- `judge.endpoint` and provide `BELAY_JUDGE_API_KEY` (or `OPENAI_API_KEY`), or
241
- opt in with
242
- `belay init --judge-provider openai-compatible --judge-endpoint <url> --accept-cloud-judge`.
243
- Fresh installs default to local Ollama (`local-ollama`).
242
+ - **Cloud judge** — configure with `belay config` (interactive) or `belay config set judge.providerId <id>`.
243
+ Providers: `ollama`, `codex`, `claude`, `cursor`. **Provider** is the vendor/service
244
+ (`judge.providerId`); **driver** is the API compatibility layer (`judge.provider`:
245
+ `ollama`, `openai-compatible`, or `anthropic`); **host** is where hooks install
246
+ (`config.adapter`: `cursor`, `claude`, `codex`). Set `judge.endpoint` when needed;
247
+ credentials via `belay config credential mode project|apiKey` or env vars. Record egress
248
+ consent during `belay config` or via `belay judge consent` → `belay approve` →
249
+ `belay judge use … --cloud-consent-approval-id`. Cloud providers can use native CLI
250
+ transport without `judge.endpoint` when the host CLI is available (`codex-cli`,
251
+ `cursor-cli`, `claude-cli`); HTTP transport still requires endpoint and recorded consent.
252
+ Fresh installs default to the
253
+ host-matched provider (`cursor` → `cursor`, etc.). `belay judge use` remains available
254
+ as a secondary path.
255
+
256
+ Legacy `judge.model: auto` in config files is normalized to the provider catalog default on load
257
+ (with a warning); new `auto` values are rejected on CLI, `belay config set`, and `belay judge use`.
258
+ On an installed repo, interactive `belay config` defaults to judge-only setup without re-running
259
+ `init`. Model discovery is covered by unit tests with mocks; set `BELAY_LIVE_CLI_DISCOVERY=1` locally
260
+ for optional live CLI probes. Cloud egress consent is enforced for HTTP transport; native CLI
261
+ transport uses the host session and does not require `judge.endpoint`. `BELAY_JUDGE_MODEL_RESOLVED`
262
+ applies only under Vitest (test overrides).
244
263
 
245
264
  ## Command reference
246
265
 
247
266
  ```bash
248
267
  belay init [--adapter cursor|claude|codex] [--scope project|global]
249
268
  [--preset strict|standard|audit-first|l1-full-recommended]
250
- [--with-skill] [--dogfood]
251
- belay init-wizard # interactive install
252
- belay upgrade # refresh hooks + runtime, migrate config
269
+ [--migrate-judge-default] [--with-skill] [--dogfood]
270
+ belay config # interactive setup (primary)
271
+ belay config list|get|set|unset|judge|credential
272
+ belay upgrade [--migrate-judge-default] # refresh hooks + runtime, migrate config
253
273
  belay dogfood [--enforce] # toggle audit / enforce mode
254
274
  belay doctor [--fix] # check (and repair) floor health
255
275
  belay status # show install scope / skill-only state
@@ -263,6 +283,13 @@ belay egress <start|stop|status|env>
263
283
  belay sandbox status
264
284
  belay approve <approval-id> [--scope once|domain|path]
265
285
  belay revoke <approval-id>
286
+ belay judge status
287
+ belay judge list
288
+ belay judge use <ollama|codex|claude|cursor> [--model <id>] [--endpoint <url>]
289
+ [--accept-cloud] [--cloud-consent-approval-id <id>]
290
+ [--credential project|apiKey] [--key-stdin] [--key-env <NAME>]
291
+ belay judge test
292
+ belay judge consent <provider-id> [--endpoint <url>]
266
293
  ```
267
294
 
268
295
  ## Coexisting with existing hooks
@@ -4,7 +4,7 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import { recordApproval } from '../../core/approval-service.js';
6
6
  import { issueApprovalToken } from '../../core/approval-token.js';
7
- import { fsScopeAllowlistPath, isCapabilityBrokerDemotionActive, loadFsScopeAllowlistSync, shouldSkipBrokerApprovedOnce, } from '../../core/capability/index.js';
7
+ import { fsScopeAllowlistPath, isCapabilityBrokerDemotionActive, loadFsScopeAllowlistSync, shouldSkipBrokerApprovedOnce, shouldSkipBrokerApprovedRecord, } from '../../core/capability/index.js';
8
8
  import { resolveLayeredConfig, teamConfigPath } from '../../core/config-layers.js';
9
9
  import { classifyResultToGateVerdict, unnormalizedGateVerdict, } from '../../core/gate-contract.js';
10
10
  import { classifyGatedActionAsync, extractAgentAssessment, GateNormalizationError, gateEnabledForAction, normalizeGatedAction, } from '../../core/gate-engine.js';
@@ -322,10 +322,18 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
322
322
  });
323
323
  }
324
324
  const brokerActive = isCapabilityBrokerDemotionActive(ctx.config);
325
- const approved = TRANSACTIONAL_APPROVAL_BYPASS_REASONS.has(result.reason) ||
326
- shouldSkipBrokerApprovedOnce(brokerActive, result.reason)
327
- ? null
328
- : await consumeApprovedApproval(ctx, deps, kind, result.fingerprint);
325
+ let approved = null;
326
+ if (!TRANSACTIONAL_APPROVAL_BYPASS_REASONS.has(result.reason) &&
327
+ !shouldSkipBrokerApprovedOnce(brokerActive, result.reason)) {
328
+ const approvedState = await deps.loadApprovals(ctx, 'approved-approvals.json');
329
+ approvedState.state = compactApprovals(approvedState.state);
330
+ const matchedApproval = approvedState.state.approvals.find((entry) => entry.kind === kind &&
331
+ entry.fingerprint === result.fingerprint &&
332
+ entry.repoRoot === ctx.repoRoot);
333
+ if (!shouldSkipBrokerApprovedRecord(brokerActive, matchedApproval?.reason)) {
334
+ approved = await consumeApprovedApproval(ctx, deps, kind, result.fingerprint);
335
+ }
336
+ }
329
337
  if (approved) {
330
338
  await deps.appendAudit(ctx, {
331
339
  ...gateBase,