@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.
- package/README.md +39 -12
- package/dist/adapters/cursor/runtime-entry.js +1 -0
- package/dist/adapters/shared/gate-runtime.d.ts +11 -14
- package/dist/adapters/shared/gate-runtime.js +289 -23
- package/dist/bundle/claude-runtime.mjs +4435 -708
- package/dist/bundle/codex-runtime.mjs +4448 -710
- package/dist/bundle/cursor-runtime.mjs +4437 -710
- package/dist/cli.js +333 -11
- package/dist/commands/approve.d.ts +1 -0
- package/dist/commands/approve.js +56 -26
- package/dist/commands/config.d.ts +70 -0
- package/dist/commands/config.js +577 -0
- package/dist/commands/doctor.js +13 -2
- package/dist/commands/explain.js +6 -1
- package/dist/commands/harvest.d.ts +29 -0
- package/dist/commands/harvest.js +72 -0
- package/dist/commands/health-snapshot.d.ts +6 -0
- package/dist/commands/health-snapshot.js +34 -0
- package/dist/commands/judge.d.ts +100 -0
- package/dist/commands/judge.js +304 -0
- package/dist/commands/metrics.js +34 -2
- package/dist/commands/quality.d.ts +38 -0
- package/dist/commands/quality.js +105 -0
- package/dist/commands/simulate.d.ts +1 -0
- package/dist/commands/simulate.js +7 -2
- package/dist/commands/standing-allow.d.ts +10 -0
- package/dist/commands/standing-allow.js +26 -0
- package/dist/commands/status.js +2 -2
- package/dist/commands/stdin-key.d.ts +1 -0
- package/dist/commands/stdin-key.js +8 -0
- package/dist/commands/tui.d.ts +48 -0
- package/dist/commands/tui.js +150 -0
- package/dist/config-io.js +10 -5
- package/dist/conformance/guarantee-table.js +12 -0
- package/dist/conformance/types.d.ts +2 -0
- package/dist/core/approval-replay-cli.d.ts +7 -0
- package/dist/core/approval-replay-cli.js +36 -0
- package/dist/core/approval-replay.d.ts +45 -0
- package/dist/core/approval-replay.js +141 -0
- package/dist/core/approval-service.d.ts +15 -0
- package/dist/core/approval-service.js +47 -2
- package/dist/core/approval.d.ts +24 -3
- package/dist/core/approval.js +47 -4
- package/dist/core/audit-analysis.d.ts +7 -1
- package/dist/core/audit-analysis.js +111 -0
- package/dist/core/audit-io.d.ts +2 -0
- package/dist/core/audit-io.js +14 -0
- package/dist/core/audit-metrics.d.ts +7 -0
- package/dist/core/audit-metrics.js +24 -4
- package/dist/core/audit-query.d.ts +1 -0
- package/dist/core/audit-query.js +3 -0
- package/dist/core/audit-replay-context.d.ts +35 -0
- package/dist/core/audit-replay-context.js +88 -0
- package/dist/core/audit-types.d.ts +24 -1
- package/dist/core/audit-types.js +1 -1
- package/dist/core/capability/index.d.ts +4 -3
- package/dist/core/capability/index.js +3 -2
- package/dist/core/capability/paths.d.ts +2 -1
- package/dist/core/capability/paths.js +76 -8
- package/dist/core/capability/reasons.d.ts +3 -0
- package/dist/core/capability/reasons.js +8 -0
- package/dist/core/capability/trusted-workspace-roots.d.ts +25 -0
- package/dist/core/capability/trusted-workspace-roots.js +149 -0
- package/dist/core/capability/types.d.ts +11 -1
- package/dist/core/capability-approval.d.ts +2 -1
- package/dist/core/capability-approval.js +100 -2
- package/dist/core/classify-subagent.js +2 -20
- package/dist/core/classify-tool.js +129 -30
- package/dist/core/config-layers.js +2 -1
- package/dist/core/config.d.ts +40 -2
- package/dist/core/config.js +159 -11
- package/dist/core/credential-store.d.ts +11 -0
- package/dist/core/credential-store.js +60 -0
- package/dist/core/gate-engine.js +104 -13
- package/dist/core/harvest.d.ts +53 -0
- package/dist/core/harvest.js +276 -0
- package/dist/core/index.d.ts +9 -3
- package/dist/core/index.js +6 -2
- package/dist/core/integrity.d.ts +2 -0
- package/dist/core/integrity.js +13 -0
- package/dist/core/judge-api-key.d.ts +19 -0
- package/dist/core/judge-api-key.js +74 -0
- package/dist/core/judge-cloud-consent.d.ts +13 -0
- package/dist/core/judge-cloud-consent.js +38 -0
- package/dist/core/judge-config.d.ts +41 -4
- package/dist/core/judge-config.js +263 -57
- package/dist/core/judge-doctor.d.ts +9 -1
- package/dist/core/judge-doctor.js +199 -94
- package/dist/core/judge-model-discovery.d.ts +24 -0
- package/dist/core/judge-model-discovery.js +168 -0
- package/dist/core/judge-model-policy.d.ts +5 -0
- package/dist/core/judge-model-policy.js +21 -0
- package/dist/core/judge-runtime-detection.d.ts +9 -0
- package/dist/core/judge-runtime-detection.js +68 -0
- package/dist/core/path-utils.d.ts +11 -0
- package/dist/core/path-utils.js +56 -6
- package/dist/core/reclassify.d.ts +3 -0
- package/dist/core/reclassify.js +44 -14
- package/dist/core/replay-scrub.d.ts +9 -0
- package/dist/core/replay-scrub.js +43 -0
- package/dist/core/shell-tokenizer.d.ts +2 -0
- package/dist/core/shell-tokenizer.js +82 -23
- package/dist/core/standing-allow.d.ts +50 -0
- package/dist/core/standing-allow.js +175 -0
- package/dist/core/transactional/diff-evaluator.js +1 -19
- package/dist/core/types.d.ts +17 -0
- package/dist/core/verdict/adapter.d.ts +1 -0
- package/dist/core/verdict/adapter.js +17 -2
- package/dist/core/verdict/containment.d.ts +8 -2
- package/dist/core/verdict/containment.js +45 -10
- package/dist/core/verdict/judge-audit.d.ts +1 -0
- package/dist/core/verdict/judge-audit.js +32 -1
- package/dist/core/verdict/judge-baseline.d.ts +20 -0
- package/dist/core/verdict/judge-baseline.js +48 -0
- package/dist/core/verdict/judge-broker-service.d.ts +42 -0
- package/dist/core/verdict/judge-broker-service.js +279 -0
- package/dist/core/verdict/judge-catalog.d.ts +40 -0
- package/dist/core/verdict/judge-catalog.js +148 -0
- package/dist/core/verdict/judge-cli-fingerprint.d.ts +5 -0
- package/dist/core/verdict/judge-cli-fingerprint.js +61 -0
- package/dist/core/verdict/judge-cli.d.ts +46 -0
- package/dist/core/verdict/judge-cli.js +401 -0
- package/dist/core/verdict/judge-factory.d.ts +15 -4
- package/dist/core/verdict/judge-factory.js +127 -15
- package/dist/core/verdict/judge-provider-matrix.d.ts +20 -0
- package/dist/core/verdict/judge-provider-matrix.js +66 -0
- package/dist/core/verdict/judge-runtime-config.d.ts +42 -0
- package/dist/core/verdict/judge-runtime-config.js +92 -0
- package/dist/core/verdict/judge-session-broker.d.ts +48 -0
- package/dist/core/verdict/judge-session-broker.js +195 -0
- package/dist/core/verdict/judge-session-guard.d.ts +27 -0
- package/dist/core/verdict/judge-session-guard.js +91 -0
- package/dist/core/verdict/judge-session-kill-switch.d.ts +11 -0
- package/dist/core/verdict/judge-session-kill-switch.js +43 -0
- package/dist/core/verdict/judge-session-mutex.d.ts +9 -0
- package/dist/core/verdict/judge-session-mutex.js +23 -0
- package/dist/core/verdict/judge-shadow.d.ts +24 -0
- package/dist/core/verdict/judge-shadow.js +116 -0
- package/dist/core/verdict/judge-transport.d.ts +40 -0
- package/dist/core/verdict/judge-transport.js +258 -0
- package/dist/core/verdict/judge.d.ts +33 -1
- package/dist/core/verdict/judge.js +84 -14
- package/dist/core/verdict/parser.js +6 -1
- package/dist/core/verdict/persistent-paths.d.ts +8 -0
- package/dist/core/verdict/persistent-paths.js +52 -0
- package/dist/core/verdict/types.d.ts +19 -2
- package/dist/core/verdict/verdict.js +166 -49
- package/dist/corpus/adversarial-probe.d.ts +86 -0
- package/dist/corpus/adversarial-probe.js +220 -0
- package/dist/corpus/evaluate.d.ts +45 -12
- package/dist/corpus/evaluate.js +62 -5
- package/dist/corpus/gates.d.ts +35 -0
- package/dist/corpus/gates.js +81 -0
- package/dist/corpus/judge-accuracy.d.ts +8 -0
- package/dist/corpus/judge-accuracy.js +39 -0
- package/dist/corpus/must-allow-commands.d.ts +5 -0
- package/dist/corpus/must-allow-commands.js +13 -0
- package/dist/corpus/mutators.d.ts +28 -0
- package/dist/corpus/mutators.js +151 -0
- package/dist/corpus/ratchet.d.ts +42 -0
- package/dist/corpus/ratchet.js +116 -0
- package/dist/corpus/runtime-match.d.ts +19 -0
- package/dist/corpus/runtime-match.js +45 -0
- package/dist/corpus/standing-allow-catalog.generated.d.ts +13 -0
- package/dist/corpus/standing-allow-catalog.generated.js +79 -0
- package/dist/corpus/types.d.ts +47 -0
- package/dist/corpus/types.js +157 -0
- package/dist/installer.js +69 -15
- package/dist/judge-broker-daemon.d.ts +1 -0
- package/dist/judge-broker-daemon.js +123 -0
- package/dist/services/sandbox-service.d.ts +1 -0
- package/dist/services/sandbox-service.js +2 -0
- package/dist/types.d.ts +7 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -3
- package/skills/belay/SKILL.md +15 -8
- package/skills/belay/belay-approve.md +17 -0
- package/dist/commands/init-wizard.d.ts +0 -21
- package/dist/commands/init-wizard.js +0 -63
package/dist/installer.js
CHANGED
|
@@ -7,9 +7,11 @@ import { resolveScopedPaths } from './adapters/layouts/scope.js';
|
|
|
7
7
|
import { getAdapter } from './adapters/registry.js';
|
|
8
8
|
import { dogfoodProject } from './commands/dogfood.js';
|
|
9
9
|
import { detectAdapterName, loadConfigFile, mergeAndWriteConfig, writeConfigFile, } from './config-io.js';
|
|
10
|
-
import {
|
|
10
|
+
import { appendCliAuditEvent } from './core/audit-io.js';
|
|
11
|
+
import { isFreshConfigInput, mergeConfig, normalizeConfig, } from './core/config.js';
|
|
11
12
|
import { runtimeIntegrityFiles, writeIntegrityManifest } from './core/integrity.js';
|
|
12
|
-
import { resolveInitJudgeConfig } from './core/judge-config.js';
|
|
13
|
+
import { hasValidCloudConsent, isCloudJudgeConfig, migrateImplicitLocalJudgeIfNeeded, resolveInitJudgeConfig, } from './core/judge-config.js';
|
|
14
|
+
import { resolveJudgeTransport } from './core/judge-runtime-detection.js';
|
|
13
15
|
import { bootstrapStateFiles, writeSkillArtifacts } from './installer/bootstrap.js';
|
|
14
16
|
import { writeRuntimeArtifacts } from './installer/runtime-artifacts.js';
|
|
15
17
|
import { applyInstallScope, resolveOperationScope } from './installer/scope-config.js';
|
|
@@ -104,32 +106,64 @@ function resolveAdapterName(options, repoRoot) {
|
|
|
104
106
|
}
|
|
105
107
|
return 'cursor';
|
|
106
108
|
}
|
|
107
|
-
async function
|
|
108
|
-
if (
|
|
109
|
+
async function auditJudgeMigrationIfNeeded(repoRoot, adapterName, before, after, by) {
|
|
110
|
+
if (before.providerId === after.providerId && before.provider === after.provider) {
|
|
109
111
|
return;
|
|
110
112
|
}
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
const config = await loadConfigFile(repoRoot, adapterName);
|
|
114
|
+
await appendCliAuditEvent(repoRoot, config, {
|
|
115
|
+
event: 'judge_provider_changed',
|
|
116
|
+
from: { providerId: before.providerId, provider: before.provider },
|
|
117
|
+
to: { providerId: after.providerId, provider: after.provider },
|
|
118
|
+
by,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
async function applyInitJudgeConfig(repoRoot, adapterName, options, isFreshBeforeInstall) {
|
|
122
|
+
if (options.skipJudgeWrite) {
|
|
123
|
+
return;
|
|
116
124
|
}
|
|
117
|
-
const isFresh = isFreshConfigInput(existingConfig);
|
|
118
125
|
const mergedConfig = await loadConfigFile(repoRoot, adapterName);
|
|
119
|
-
const hasExplicitJudgeFlags = options.judgeProfile ||
|
|
126
|
+
const hasExplicitJudgeFlags = options.judgeProfile ||
|
|
127
|
+
options.judgeProvider ||
|
|
128
|
+
options.judgeProviderId ||
|
|
129
|
+
options.judgeModel ||
|
|
130
|
+
options.judgeEndpoint;
|
|
120
131
|
const judge = resolveInitJudgeConfig({
|
|
121
|
-
isFresh,
|
|
132
|
+
isFresh: isFreshBeforeInstall,
|
|
122
133
|
hasExplicitJudgeFlags: Boolean(hasExplicitJudgeFlags),
|
|
123
134
|
judgeProfile: options.judgeProfile,
|
|
124
135
|
judgeProvider: options.judgeProvider,
|
|
136
|
+
judgeProviderId: options.judgeProviderId,
|
|
125
137
|
judgeModel: options.judgeModel,
|
|
126
138
|
judgeEndpoint: options.judgeEndpoint,
|
|
127
139
|
acceptCloudJudge: options.acceptCloudJudge,
|
|
140
|
+
interactiveConsent: Boolean(options.acceptCloudJudge && process.stdin.isTTY),
|
|
141
|
+
cloudConsentApprovalId: options.cloudConsentApprovalId,
|
|
128
142
|
existingJudge: mergedConfig.judge,
|
|
129
|
-
|
|
143
|
+
adapter: adapterName,
|
|
130
144
|
});
|
|
131
|
-
const
|
|
145
|
+
const migrated = options.migrateJudgeDefault === true
|
|
146
|
+
? migrateImplicitLocalJudgeIfNeeded(mergedConfig.judge, adapterName)
|
|
147
|
+
: null;
|
|
148
|
+
let finalJudge = migrated ?? judge;
|
|
149
|
+
if (options.judgeCredentialMode) {
|
|
150
|
+
finalJudge = {
|
|
151
|
+
...finalJudge,
|
|
152
|
+
credential: options.judgeCredentialMode === 'apiKey'
|
|
153
|
+
? { mode: 'apiKey', ref: 'store:judge' }
|
|
154
|
+
: { mode: 'project' },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const configWithJudge = normalizeConfig({ ...mergedConfig, version: 4, judge: finalJudge });
|
|
158
|
+
if (isCloudJudgeConfig(configWithJudge.judge) &&
|
|
159
|
+
resolveJudgeTransport(configWithJudge.judge) === 'http' &&
|
|
160
|
+
!hasValidCloudConsent(configWithJudge.judge)) {
|
|
161
|
+
process.stderr.write('Warning: Cloud judge saved without recorded consent. Tier1 cloud judge will fail closed until consent is granted (belay judge consent + belay approve, or TTY --accept-cloud-judge).\n');
|
|
162
|
+
}
|
|
132
163
|
await writeConfigFile(repoRoot, configWithJudge, adapterName);
|
|
164
|
+
if (migrated) {
|
|
165
|
+
await auditJudgeMigrationIfNeeded(repoRoot, adapterName, mergedConfig.judge, finalJudge, options.migrateJudgeDefault ? 'belay init --migrate-judge-default' : 'belay init');
|
|
166
|
+
}
|
|
133
167
|
}
|
|
134
168
|
async function refreshIntegrityManifest(repoRoot, adapterName) {
|
|
135
169
|
const layout = getAdapter(adapterName).layout;
|
|
@@ -142,8 +176,19 @@ export async function initProject(options = {}) {
|
|
|
142
176
|
const repoRoot = path.resolve(options.targetDir ?? process.cwd());
|
|
143
177
|
const adapterName = resolveAdapterName(options, repoRoot);
|
|
144
178
|
const adapter = getAdapter(adapterName);
|
|
179
|
+
const configPath = adapter.layout.configPath(repoRoot);
|
|
180
|
+
let isFreshBeforeInstall = true;
|
|
181
|
+
if (await pathExists(configPath)) {
|
|
182
|
+
try {
|
|
183
|
+
const raw = await readFile(configPath, 'utf8');
|
|
184
|
+
isFreshBeforeInstall = isFreshConfigInput(JSON.parse(raw));
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
isFreshBeforeInstall = false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
145
190
|
const result = await adapter.install(repoRoot, options);
|
|
146
|
-
await applyInitJudgeConfig(repoRoot, adapterName, options);
|
|
191
|
+
await applyInitJudgeConfig(repoRoot, adapterName, options, isFreshBeforeInstall);
|
|
147
192
|
if (options.preset) {
|
|
148
193
|
const existing = await loadConfigFile(repoRoot, adapterName);
|
|
149
194
|
const presetConfig = mergeConfig(applyConfigPreset(options.preset));
|
|
@@ -165,6 +210,15 @@ export async function upgradeProject(options = {}) {
|
|
|
165
210
|
const repoRoot = path.resolve(options.targetDir ?? process.cwd());
|
|
166
211
|
const adapterName = resolveAdapterName(options, repoRoot);
|
|
167
212
|
await getAdapter(adapterName).upgrade(repoRoot, options);
|
|
213
|
+
if (options.migrateJudgeDefault === true) {
|
|
214
|
+
const mergedConfig = await loadConfigFile(repoRoot, adapterName);
|
|
215
|
+
const migrated = migrateImplicitLocalJudgeIfNeeded(mergedConfig.judge, adapterName);
|
|
216
|
+
if (migrated) {
|
|
217
|
+
const configWithJudge = normalizeConfig({ ...mergedConfig, version: 4, judge: migrated });
|
|
218
|
+
await writeConfigFile(repoRoot, configWithJudge, adapterName);
|
|
219
|
+
await auditJudgeMigrationIfNeeded(repoRoot, adapterName, mergedConfig.judge, migrated, 'belay upgrade --migrate-judge-default');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
168
222
|
await refreshIntegrityManifest(repoRoot, adapterName);
|
|
169
223
|
return { repoRoot, adapter: adapterName };
|
|
170
224
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { createServer } from 'node:net';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { cleanupJudgeBrokerArtifacts, JUDGE_BROKER_SESSION, judgeBrokerPaths, } from './core/verdict/judge-broker-service.js';
|
|
5
|
+
import { runCliJsonWithTimeouts } from './core/verdict/judge-cli.js';
|
|
6
|
+
import { DEFAULT_JUDGE_SESSION_CONFIG, normalizeJudgeSessionConfig, resolveSessionEvalTimeoutMs, } from './core/verdict/judge-runtime-config.js';
|
|
7
|
+
import { JudgeSessionBroker, } from './core/verdict/judge-session-broker.js';
|
|
8
|
+
async function loadBrokerSessionConfig(stateDir) {
|
|
9
|
+
const sessionConfigPath = path.join(stateDir, JUDGE_BROKER_SESSION);
|
|
10
|
+
try {
|
|
11
|
+
const raw = JSON.parse(await readFile(sessionConfigPath, 'utf8'));
|
|
12
|
+
return normalizeJudgeSessionConfig({ ...raw, enabled: true });
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return normalizeJudgeSessionConfig({ ...DEFAULT_JUDGE_SESSION_CONFIG, enabled: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function main() {
|
|
19
|
+
const repoRoot = process.env.BELAY_JUDGE_BROKER_REPO_ROOT;
|
|
20
|
+
const stateDir = process.env.BELAY_JUDGE_BROKER_STATE_DIR;
|
|
21
|
+
if (!repoRoot || !stateDir) {
|
|
22
|
+
process.stderr.write('BELAY_JUDGE_BROKER_REPO_ROOT and BELAY_JUDGE_BROKER_STATE_DIR are required.\n');
|
|
23
|
+
process.exitCode = 1;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const paths = judgeBrokerPaths(stateDir);
|
|
27
|
+
await mkdir(stateDir, { recursive: true, mode: 0o700 });
|
|
28
|
+
const sessionConfig = await loadBrokerSessionConfig(stateDir);
|
|
29
|
+
const defaultEvalTimeoutMs = resolveSessionEvalTimeoutMs(sessionConfig, 25_000);
|
|
30
|
+
const broker = new JudgeSessionBroker({
|
|
31
|
+
config: sessionConfig,
|
|
32
|
+
runCommand: (invocation, timeoutMs) => runCliJsonWithTimeouts(invocation, { evalTimeoutMs: timeoutMs }),
|
|
33
|
+
});
|
|
34
|
+
let idleTimer = null;
|
|
35
|
+
let server;
|
|
36
|
+
const scheduleIdleShutdown = () => {
|
|
37
|
+
if (idleTimer) {
|
|
38
|
+
clearTimeout(idleTimer);
|
|
39
|
+
}
|
|
40
|
+
idleTimer = setTimeout(() => {
|
|
41
|
+
void shutdown('idle_timeout');
|
|
42
|
+
}, sessionConfig.maxIdleMs);
|
|
43
|
+
};
|
|
44
|
+
const shutdown = async (reason) => {
|
|
45
|
+
broker.stopAll('manual_stop');
|
|
46
|
+
server?.close();
|
|
47
|
+
await cleanupJudgeBrokerArtifacts(paths);
|
|
48
|
+
process.stderr.write(`judge broker stopped (${reason})\n`);
|
|
49
|
+
process.exit(0);
|
|
50
|
+
};
|
|
51
|
+
server = createServer((socket) => {
|
|
52
|
+
scheduleIdleShutdown();
|
|
53
|
+
let buffer = '';
|
|
54
|
+
socket.on('data', (chunk) => {
|
|
55
|
+
buffer += String(chunk);
|
|
56
|
+
const lines = buffer.split('\n');
|
|
57
|
+
buffer = lines.pop() ?? '';
|
|
58
|
+
for (const line of lines) {
|
|
59
|
+
if (!line.trim()) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
void handleLine(line, socket, broker, defaultEvalTimeoutMs).catch((error) => {
|
|
63
|
+
const id = safeMessageId(line);
|
|
64
|
+
socket.write(`${JSON.stringify({
|
|
65
|
+
type: 'error',
|
|
66
|
+
id,
|
|
67
|
+
error: error instanceof Error ? error.message : 'judge broker handler failed',
|
|
68
|
+
})}\n`);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
await unlink(paths.socketPath).catch(() => undefined);
|
|
74
|
+
await new Promise((resolve, reject) => {
|
|
75
|
+
server.once('error', reject);
|
|
76
|
+
server.listen(paths.socketPath, () => resolve());
|
|
77
|
+
});
|
|
78
|
+
const status = {
|
|
79
|
+
pid: process.pid,
|
|
80
|
+
repoRoot: path.resolve(repoRoot),
|
|
81
|
+
socketPath: paths.socketPath,
|
|
82
|
+
startedAt: new Date().toISOString(),
|
|
83
|
+
};
|
|
84
|
+
await writeFile(paths.statusPath, JSON.stringify(status), { encoding: 'utf8', mode: 0o600 });
|
|
85
|
+
await writeFile(paths.pidPath, String(process.pid), { encoding: 'utf8', mode: 0o600 });
|
|
86
|
+
process.on('SIGTERM', () => {
|
|
87
|
+
void shutdown('sigterm');
|
|
88
|
+
});
|
|
89
|
+
process.on('SIGINT', () => {
|
|
90
|
+
void shutdown('sigint');
|
|
91
|
+
});
|
|
92
|
+
scheduleIdleShutdown();
|
|
93
|
+
process.stdout.write(`judge broker listening on ${paths.socketPath} for ${repoRoot}\n`);
|
|
94
|
+
}
|
|
95
|
+
function safeMessageId(line) {
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(line);
|
|
98
|
+
return parsed.id ?? 'unknown';
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return 'unknown';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function handleLine(line, socket, broker, defaultEvalTimeoutMs) {
|
|
105
|
+
const message = JSON.parse(line);
|
|
106
|
+
if (message.type === 'stop') {
|
|
107
|
+
socket.write(`${JSON.stringify({ type: 'stopped', id: message.id })}\n`);
|
|
108
|
+
broker.stopAll('manual_stop');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (message.type === 'invalidate' && typeof message.sessionKey === 'string') {
|
|
112
|
+
broker.invalidateSession(message.sessionKey, message.reason ?? 'parse_failure');
|
|
113
|
+
socket.write(`${JSON.stringify({ type: 'invalidated', id: message.id })}\n`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (message.type !== 'evaluate' || !message.request) {
|
|
117
|
+
socket.write(`${JSON.stringify({ type: 'error', id: message.id, error: 'unsupported message' })}\n`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const result = await broker.evaluate(message.request, message.timeoutMs ?? defaultEvalTimeoutMs);
|
|
121
|
+
socket.write(`${JSON.stringify({ type: 'result', id: message.id, result })}\n`);
|
|
122
|
+
}
|
|
123
|
+
await main();
|
|
@@ -24,6 +24,7 @@ export interface SandboxStatusReport {
|
|
|
24
24
|
export declare function sandboxStatus(options?: SandboxServiceOptions): Promise<SandboxStatusReport>;
|
|
25
25
|
export declare function createCapabilityApprovalStore(repoRoot: string, config: Awaited<ReturnType<typeof loadConfigFile>>): {
|
|
26
26
|
allowlistPath: string;
|
|
27
|
+
trustedRootsPath: string;
|
|
27
28
|
loadPending(): Promise<{
|
|
28
29
|
filePath: string;
|
|
29
30
|
state: import("../types.js").ApprovalStateFile;
|
|
@@ -2,6 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { loadConfigFile, repoLocalStateDirFor } from '../config-io.js';
|
|
3
3
|
import { fsScopeAllowlistPath, loadFsScopeAllowlist } from '../core/capability/allowlist.js';
|
|
4
4
|
import { evaluateL1FullStatus, isCapabilityBrokerDemotionActive, } from '../core/capability/broker.js';
|
|
5
|
+
import { trustedWorkspaceRootsPath } from '../core/capability/trusted-workspace-roots.js';
|
|
5
6
|
import { configuredControlPlaneDir } from '../core/config.js';
|
|
6
7
|
import { verifyControlPlaneIsolation } from '../core/control-plane-isolation.js';
|
|
7
8
|
import { egressStatus } from './egress-service.js';
|
|
@@ -48,6 +49,7 @@ export function createCapabilityApprovalStore(repoRoot, config) {
|
|
|
48
49
|
const repoLocalDir = repoLocalStateDirFor(repoRoot, config);
|
|
49
50
|
return {
|
|
50
51
|
allowlistPath: fsScopeAllowlistPath(config, repoLocalDir),
|
|
52
|
+
trustedRootsPath: trustedWorkspaceRootsPath(config, repoLocalDir),
|
|
51
53
|
async loadPending() {
|
|
52
54
|
const { loadApprovalState, pendingApprovalsPath } = await import('../config-io.js');
|
|
53
55
|
const filePath = pendingApprovalsPath(repoRoot, config);
|
package/dist/types.d.ts
CHANGED
|
@@ -22,18 +22,25 @@ export interface InitOptions {
|
|
|
22
22
|
preset?: import('./presets.js').ConfigPresetName;
|
|
23
23
|
judgeProfile?: 'local-ollama' | 'cursor' | 'claude' | 'codex';
|
|
24
24
|
judgeProvider?: 'ollama' | 'openai-compatible' | 'cursor';
|
|
25
|
+
judgeProviderId?: 'ollama' | 'codex' | 'claude' | 'cursor' | 'local' | 'openai';
|
|
25
26
|
judgeModel?: string;
|
|
26
27
|
judgeEndpoint?: string;
|
|
28
|
+
judgeCredentialMode?: 'project' | 'apiKey';
|
|
27
29
|
/** Acknowledge cloud judge egress + redaction limits (R19). Required for openai-compatible provider. */
|
|
28
30
|
acceptCloudJudge?: boolean;
|
|
31
|
+
cloudConsentApprovalId?: string;
|
|
29
32
|
/** When true, skip writing judge config (used when user declines cloud consent). */
|
|
30
33
|
skipJudgeWrite?: boolean;
|
|
34
|
+
/** Opt-in: migrate implicit factory-default ollama judge to host default provider. */
|
|
35
|
+
migrateJudgeDefault?: boolean;
|
|
31
36
|
}
|
|
32
37
|
export interface UpgradeOptions {
|
|
33
38
|
targetDir?: string;
|
|
34
39
|
withSkill?: boolean;
|
|
35
40
|
adapter?: AdapterName;
|
|
36
41
|
scope?: InstallScope;
|
|
42
|
+
/** Opt-in: migrate implicit factory-default ollama judge to host default provider. */
|
|
43
|
+
migrateJudgeDefault?: boolean;
|
|
37
44
|
}
|
|
38
45
|
export interface DoctorOptions {
|
|
39
46
|
targetDir?: string;
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const PACKAGE_VERSION = "0.
|
|
1
|
+
export declare const PACKAGE_VERSION = "0.4.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by scripts/sync-version.mjs — do not edit.
|
|
2
|
-
export const PACKAGE_VERSION = '0.
|
|
2
|
+
export const PACKAGE_VERSION = '0.4.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guilz-dev/belay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Belay-style approval and audit gating for agent runtimes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,15 +48,19 @@
|
|
|
48
48
|
"agent-belay-logo.png"
|
|
49
49
|
],
|
|
50
50
|
"scripts": {
|
|
51
|
-
"build": "rm -rf dist && node scripts/sync-version.mjs && tsc -p tsconfig.build.json && node scripts/build-runtime.mjs",
|
|
51
|
+
"build": "rm -rf dist && node scripts/sync-version.mjs && tsc -p tsconfig.build.json && node scripts/generate-standing-allow-catalog.mjs && tsc -p tsconfig.build.json && node scripts/build-runtime.mjs",
|
|
52
52
|
"check:version": "node scripts/check-cli-version.mjs",
|
|
53
|
+
"dogfood": "./scripts/dev-dogfood.sh",
|
|
54
|
+
"dev-refresh": "./scripts/dev-refresh.sh",
|
|
53
55
|
"prepublishOnly": "pnpm build && node scripts/check-cli-version.mjs",
|
|
54
56
|
"lint": "biome check src package.json README.md CHANGELOG.md CONTRIBUTING.md SECURITY.md tsconfig.json tsconfig.build.json vitest.config.ts scripts docs",
|
|
55
57
|
"typecheck": "tsc --noEmit",
|
|
56
58
|
"test": "pnpm build && vitest run",
|
|
57
59
|
"test:structural": "pnpm build && vitest run src/__tests__/verdict/structural-suite.test.ts",
|
|
58
60
|
"test:stable": "pnpm build && vitest run && vitest run && vitest run",
|
|
59
|
-
"corpus": "pnpm build && node scripts/corpus.mjs"
|
|
61
|
+
"corpus": "pnpm build && node scripts/corpus.mjs",
|
|
62
|
+
"probe:adversarial": "pnpm build && node scripts/adversarial-probe.mjs",
|
|
63
|
+
"corpus:ratchet": "pnpm build && node scripts/corpus-ratchet.mjs"
|
|
60
64
|
},
|
|
61
65
|
"devDependencies": {
|
|
62
66
|
"@biomejs/biome": "2.4.15",
|
package/skills/belay/SKILL.md
CHANGED
|
@@ -29,16 +29,22 @@ you to the CLI. It does not classify commands itself.
|
|
|
29
29
|
|
|
30
30
|
## Prerequisites
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
check whether hooks are present.
|
|
32
|
+
`npx skills add` installs this skill only — the runtime gate is **not installed**
|
|
33
|
+
until hooks are configured. Run `belay config` in the project root for interactive
|
|
34
|
+
setup (or `npx @guilz-dev/belay init` for non-interactive install). Run
|
|
35
|
+
`belay doctor` to check whether hooks are present.
|
|
36
36
|
|
|
37
37
|
## When belay blocks an action
|
|
38
38
|
|
|
39
39
|
1. Read the approval ID in the deny message.
|
|
40
40
|
2. Approve once with `/belay-approve <approval-id>` or `belay approve <approval-id>`.
|
|
41
|
-
3. Retry the original action unchanged.
|
|
41
|
+
3. Retry the original action unchanged (default `approval.flow` is `one_step` for shell — the
|
|
42
|
+
approval response includes a replay hint when auto-replay is enabled).
|
|
43
|
+
|
|
44
|
+
For shell-only CLI replay after approval: `belay approve <approval-id> --replay`.
|
|
45
|
+
|
|
46
|
+
To restore the legacy two-step UX (approve, then always retry manually), set
|
|
47
|
+
`approval.flow` to `two_step` in `belay.config.json`.
|
|
42
48
|
|
|
43
49
|
For why it was blocked, use `/belay why <command>` or `belay explain --command "<command>"`.
|
|
44
50
|
For the latest pending ask, use `/belay explain` or `belay explain`.
|
|
@@ -48,16 +54,17 @@ For recovery advice after a block, use `/belay recover` or `belay recover`.
|
|
|
48
54
|
|
|
49
55
|
## Install or repair
|
|
50
56
|
|
|
51
|
-
-
|
|
52
|
-
-
|
|
57
|
+
- Interactive setup: `belay config`
|
|
58
|
+
- Non-interactive install: `npx @guilz-dev/belay init --with-skill`
|
|
53
59
|
- Health check: `belay doctor`
|
|
54
60
|
|
|
55
|
-
Do not run init or doctor implicitly from this skill — only when the user asks.
|
|
61
|
+
Do not run init, config, or doctor implicitly from this skill — only when the user asks.
|
|
56
62
|
|
|
57
63
|
## CLI mapping
|
|
58
64
|
|
|
59
65
|
| User intent | Command |
|
|
60
66
|
| --- | --- |
|
|
67
|
+
| Set up or change judge settings | `belay config` |
|
|
61
68
|
| Why was this blocked? | `belay explain --command "..."` |
|
|
62
69
|
| Explain latest pending ask | `belay explain` |
|
|
63
70
|
| Status / dogfood / audit visibility | `belay status` |
|
|
@@ -5,3 +5,20 @@ belay approve <approval-id>
|
|
|
5
5
|
```
|
|
6
6
|
|
|
7
7
|
Then retry the original action unchanged.
|
|
8
|
+
|
|
9
|
+
With default `approval.flow: one_step`, shell approvals return a replay hint from editor
|
|
10
|
+
hooks. Tool and subagent approvals still require a manual retry.
|
|
11
|
+
|
|
12
|
+
To run a denied shell command from the CLI after approval (explicit opt-in; do not also retry via hooks):
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
belay approve <approval-id> --replay
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Successful CLI replay consumes the one-shot grant. On failure, the approval stays active for one hook retry.
|
|
19
|
+
|
|
20
|
+
Restore legacy two-step UX in `belay.config.json`:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{ "approval": { "flow": "two_step" } }
|
|
24
|
+
```
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { AdapterName, InitOptions } from '../types.js';
|
|
2
|
-
export interface WizardAnswers {
|
|
3
|
-
adapter: AdapterName;
|
|
4
|
-
scope: 'project' | 'global';
|
|
5
|
-
withSkill: boolean;
|
|
6
|
-
judgeProfile: 'local-ollama' | 'cursor' | 'claude' | 'codex';
|
|
7
|
-
dogfood: boolean;
|
|
8
|
-
}
|
|
9
|
-
export declare function parseAdapter(value: string | undefined): AdapterName;
|
|
10
|
-
export declare function parseScope(value: string | undefined): 'project' | 'global';
|
|
11
|
-
export declare function parseYesNo(value: string | undefined, defaultValue: boolean): boolean;
|
|
12
|
-
export declare function parseJudgeProfile(value: string | undefined, defaultProfile: 'local-ollama' | 'cursor' | 'claude' | 'codex'): 'local-ollama' | 'cursor' | 'claude' | 'codex';
|
|
13
|
-
export declare function buildInitOptionsFromWizard(answers: WizardAnswers, targetDir?: string): InitOptions;
|
|
14
|
-
export declare function runInitWizard(options?: {
|
|
15
|
-
targetDir?: string;
|
|
16
|
-
}): Promise<{
|
|
17
|
-
repoRoot: string;
|
|
18
|
-
withSkill: boolean;
|
|
19
|
-
dogfood: boolean;
|
|
20
|
-
adapter: import("../adapters/layouts/types.js").AdapterName;
|
|
21
|
-
}>;
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { stdin as input, stdout as output } from 'node:process';
|
|
2
|
-
import readline from 'node:readline/promises';
|
|
3
|
-
import { initProject } from '../installer.js';
|
|
4
|
-
export function parseAdapter(value) {
|
|
5
|
-
const normalized = (value?.trim() || 'cursor').toLowerCase();
|
|
6
|
-
if (normalized === 'claude' || normalized === 'codex' || normalized === 'cursor') {
|
|
7
|
-
return normalized;
|
|
8
|
-
}
|
|
9
|
-
throw new Error(`Unknown adapter: ${value ?? '(empty)'}`);
|
|
10
|
-
}
|
|
11
|
-
export function parseScope(value) {
|
|
12
|
-
const normalized = (value?.trim() || 'project').toLowerCase();
|
|
13
|
-
if (normalized === 'global' || normalized === 'project') {
|
|
14
|
-
return normalized;
|
|
15
|
-
}
|
|
16
|
-
throw new Error(`Unknown scope: ${value ?? '(empty)'}`);
|
|
17
|
-
}
|
|
18
|
-
export function parseYesNo(value, defaultValue) {
|
|
19
|
-
const normalized = (value?.trim() || (defaultValue ? 'y' : 'n')).toLowerCase();
|
|
20
|
-
if (['y', 'yes', 'true', '1'].includes(normalized)) {
|
|
21
|
-
return true;
|
|
22
|
-
}
|
|
23
|
-
if (['n', 'no', 'false', '0'].includes(normalized)) {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
return defaultValue;
|
|
27
|
-
}
|
|
28
|
-
export function parseJudgeProfile(value, defaultProfile) {
|
|
29
|
-
const normalized = (value?.trim() || defaultProfile).toLowerCase();
|
|
30
|
-
if (normalized === 'local-ollama' ||
|
|
31
|
-
normalized === 'cursor' ||
|
|
32
|
-
normalized === 'claude' ||
|
|
33
|
-
normalized === 'codex') {
|
|
34
|
-
return normalized;
|
|
35
|
-
}
|
|
36
|
-
throw new Error(`Unknown judge profile: ${value ?? '(empty)'}`);
|
|
37
|
-
}
|
|
38
|
-
export function buildInitOptionsFromWizard(answers, targetDir) {
|
|
39
|
-
return {
|
|
40
|
-
targetDir,
|
|
41
|
-
adapter: answers.adapter,
|
|
42
|
-
scope: answers.scope,
|
|
43
|
-
withSkill: answers.withSkill,
|
|
44
|
-
judgeProfile: answers.judgeProfile,
|
|
45
|
-
dogfood: answers.dogfood,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
export async function runInitWizard(options = {}) {
|
|
49
|
-
const rl = readline.createInterface({ input, output });
|
|
50
|
-
try {
|
|
51
|
-
output.write('belay init wizard\n');
|
|
52
|
-
const adapter = parseAdapter(await rl.question('Adapter [cursor | claude | codex] (cursor): '));
|
|
53
|
-
const scope = parseScope(await rl.question('Install scope [project | global] (project): '));
|
|
54
|
-
const withSkill = parseYesNo(await rl.question('Install SKILL.md and slash commands? [y | n] (y): '), true);
|
|
55
|
-
// Show Tier1 judge choice explicitly so init defaults are visible in wizard UX.
|
|
56
|
-
const defaultJudgeProfile = adapter;
|
|
57
|
-
const judgeProfile = parseJudgeProfile(await rl.question(`Tier1 judge profile [cursor | claude | codex | local-ollama] (${defaultJudgeProfile}): `), defaultJudgeProfile);
|
|
58
|
-
return initProject(buildInitOptionsFromWizard({ adapter, scope, withSkill, judgeProfile, dogfood: false }, options.targetDir));
|
|
59
|
-
}
|
|
60
|
-
finally {
|
|
61
|
-
rl.close();
|
|
62
|
-
}
|
|
63
|
-
}
|