@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
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
3
|
+
import readline from 'node:readline/promises';
|
|
4
|
+
import { loadConfigFile, repoLocalStateDirFor, resolveAdapterName, writeConfigFile, } from '../config-io.js';
|
|
5
|
+
import { appendCliAuditEvent } from '../core/audit-io.js';
|
|
6
|
+
import { belayStateDir, normalizeJudgeConfig } from '../core/config.js';
|
|
7
|
+
import { clearJudgeCredentialStore, writeJudgeCredentialStore } from '../core/credential-store.js';
|
|
8
|
+
import { refreshIntegrityIfPinned } from '../core/integrity.js';
|
|
9
|
+
import { defaultJudgeProviderForAdapter, hasValidCloudConsent, isCloudJudgeConfig, resolveJudgeUsePatch, } from '../core/judge-config.js';
|
|
10
|
+
import { rejectDeprecatedJudgeModelAuto } from '../core/judge-model-policy.js';
|
|
11
|
+
import { resolveJudgeTransport } from '../core/judge-runtime-detection.js';
|
|
12
|
+
import { isJudgeProviderId, JUDGE_PROVIDER_IDS, normalizeLegacyProviderId, } from '../core/verdict/judge-catalog.js';
|
|
13
|
+
import { initProject } from '../installer.js';
|
|
14
|
+
import { isBelayFloorInstalled } from './health-snapshot.js';
|
|
15
|
+
import { judgeStatus } from './judge.js';
|
|
16
|
+
import { readKeyFromStdin } from './stdin-key.js';
|
|
17
|
+
import { confirmPrompt, intro, isInteractiveTTY, selectPrompt } from './tui.js';
|
|
18
|
+
export const BELAY_CONFIG_SUBCOMMANDS = [
|
|
19
|
+
'list',
|
|
20
|
+
'get',
|
|
21
|
+
'set',
|
|
22
|
+
'unset',
|
|
23
|
+
'credential',
|
|
24
|
+
'judge',
|
|
25
|
+
];
|
|
26
|
+
const JUDGE_CONFIG_PATHS = [
|
|
27
|
+
'judge.providerId',
|
|
28
|
+
'judge.provider',
|
|
29
|
+
'judge.model',
|
|
30
|
+
'judge.endpoint',
|
|
31
|
+
'judge.timeoutMs',
|
|
32
|
+
'judge.credential.mode',
|
|
33
|
+
'judge.credential.ref',
|
|
34
|
+
];
|
|
35
|
+
export function parseAdapter(value) {
|
|
36
|
+
const normalized = (value?.trim() || 'cursor').toLowerCase();
|
|
37
|
+
if (normalized === 'claude' || normalized === 'codex' || normalized === 'cursor') {
|
|
38
|
+
return normalized;
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`Unknown adapter: ${value ?? '(empty)'}`);
|
|
41
|
+
}
|
|
42
|
+
export function parseScope(value) {
|
|
43
|
+
const normalized = (value?.trim() || 'project').toLowerCase();
|
|
44
|
+
if (normalized === 'global' || normalized === 'project') {
|
|
45
|
+
return normalized;
|
|
46
|
+
}
|
|
47
|
+
throw new Error(`Unknown scope: ${value ?? '(empty)'}`);
|
|
48
|
+
}
|
|
49
|
+
export function parseYesNo(value, defaultValue) {
|
|
50
|
+
const normalized = (value?.trim() || (defaultValue ? 'y' : 'n')).toLowerCase();
|
|
51
|
+
if (['y', 'yes', 'true', '1'].includes(normalized)) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
if (['n', 'no', 'false', '0'].includes(normalized)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return defaultValue;
|
|
58
|
+
}
|
|
59
|
+
export function parseJudgeProviderId(value, defaultId) {
|
|
60
|
+
const normalized = (value?.trim() || defaultId).toLowerCase();
|
|
61
|
+
const canonical = normalizeLegacyProviderId(normalized);
|
|
62
|
+
if (canonical) {
|
|
63
|
+
return canonical;
|
|
64
|
+
}
|
|
65
|
+
throw new Error(`Unknown judge provider: ${value ?? '(empty)'}`);
|
|
66
|
+
}
|
|
67
|
+
export function buildInitOptionsFromConfigAnswers(answers, targetDir) {
|
|
68
|
+
return {
|
|
69
|
+
targetDir,
|
|
70
|
+
adapter: answers.adapter,
|
|
71
|
+
scope: answers.scope,
|
|
72
|
+
withSkill: answers.withSkill,
|
|
73
|
+
judgeProviderId: answers.judgeProviderId,
|
|
74
|
+
judgeEndpoint: answers.judgeEndpoint,
|
|
75
|
+
judgeCredentialMode: answers.judgeCredentialMode,
|
|
76
|
+
acceptCloudJudge: answers.acceptCloud,
|
|
77
|
+
dogfood: answers.dogfood,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function assertJudgeConfigPath(pathKey) {
|
|
81
|
+
if (!pathKey?.startsWith('judge.')) {
|
|
82
|
+
throw new Error(`belay config only supports judge.* paths (got ${pathKey ?? '(empty)'}).`);
|
|
83
|
+
}
|
|
84
|
+
return pathKey;
|
|
85
|
+
}
|
|
86
|
+
function getJudgeField(judge, pathKey) {
|
|
87
|
+
if (pathKey === 'judge.providerId')
|
|
88
|
+
return judge.providerId ?? null;
|
|
89
|
+
if (pathKey === 'judge.provider')
|
|
90
|
+
return judge.provider;
|
|
91
|
+
if (pathKey === 'judge.model')
|
|
92
|
+
return judge.model;
|
|
93
|
+
if (pathKey === 'judge.endpoint')
|
|
94
|
+
return judge.endpoint ?? null;
|
|
95
|
+
if (pathKey === 'judge.timeoutMs')
|
|
96
|
+
return judge.timeoutMs;
|
|
97
|
+
if (pathKey === 'judge.credential.mode')
|
|
98
|
+
return judge.credential?.mode ?? null;
|
|
99
|
+
if (pathKey === 'judge.credential.ref')
|
|
100
|
+
return judge.credential?.ref ?? null;
|
|
101
|
+
throw new Error(`Unknown judge config path: ${pathKey}`);
|
|
102
|
+
}
|
|
103
|
+
function listJudgeFields(judge) {
|
|
104
|
+
const entries = {};
|
|
105
|
+
for (const key of JUDGE_CONFIG_PATHS) {
|
|
106
|
+
entries[key] = getJudgeField(judge, key);
|
|
107
|
+
}
|
|
108
|
+
return entries;
|
|
109
|
+
}
|
|
110
|
+
function warnCloudConsentIfNeeded(judge) {
|
|
111
|
+
if (isCloudJudgeConfig(judge) &&
|
|
112
|
+
resolveJudgeTransport(judge) === 'http' &&
|
|
113
|
+
!hasValidCloudConsent(judge)) {
|
|
114
|
+
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');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function persistJudge(repoRoot, config, judge, adapter) {
|
|
118
|
+
const updated = { ...config, judge: normalizeJudgeConfig(judge) };
|
|
119
|
+
await writeConfigFile(repoRoot, updated, adapter);
|
|
120
|
+
await refreshIntegrityIfPinned(repoRoot, updated);
|
|
121
|
+
return updated;
|
|
122
|
+
}
|
|
123
|
+
async function applyJudgeSet(repoRoot, config, pathKey, rawValue) {
|
|
124
|
+
const adapter = resolveAdapterName(config);
|
|
125
|
+
const value = rawValue.trim();
|
|
126
|
+
if (pathKey === 'judge.providerId') {
|
|
127
|
+
if (!isJudgeProviderId(value)) {
|
|
128
|
+
throw new Error(`Unknown judge provider id: ${value}`);
|
|
129
|
+
}
|
|
130
|
+
const patch = resolveJudgeUsePatch(config.judge, { providerId: value });
|
|
131
|
+
if (patch.errors.length > 0) {
|
|
132
|
+
throw new Error(patch.errors.join(' '));
|
|
133
|
+
}
|
|
134
|
+
const updated = await persistJudge(repoRoot, config, patch.judge, adapter);
|
|
135
|
+
warnCloudConsentIfNeeded(updated.judge);
|
|
136
|
+
return updated.judge;
|
|
137
|
+
}
|
|
138
|
+
if (pathKey === 'judge.model') {
|
|
139
|
+
rejectDeprecatedJudgeModelAuto(value);
|
|
140
|
+
const judge = normalizeJudgeConfig({ ...config.judge, model: value });
|
|
141
|
+
await persistJudge(repoRoot, config, judge, adapter);
|
|
142
|
+
return judge;
|
|
143
|
+
}
|
|
144
|
+
if (pathKey === 'judge.endpoint') {
|
|
145
|
+
const endpoint = value === 'null' || value === '' ? null : value;
|
|
146
|
+
const judge = normalizeJudgeConfig({ ...config.judge, endpoint });
|
|
147
|
+
await persistJudge(repoRoot, config, judge, adapter);
|
|
148
|
+
return judge;
|
|
149
|
+
}
|
|
150
|
+
if (pathKey === 'judge.timeoutMs') {
|
|
151
|
+
const timeoutMs = Number(value);
|
|
152
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
153
|
+
throw new Error('judge.timeoutMs must be a positive number.');
|
|
154
|
+
}
|
|
155
|
+
const judge = normalizeJudgeConfig({ ...config.judge, timeoutMs });
|
|
156
|
+
await persistJudge(repoRoot, config, judge, adapter);
|
|
157
|
+
return judge;
|
|
158
|
+
}
|
|
159
|
+
if (pathKey === 'judge.provider') {
|
|
160
|
+
throw new Error('judge.provider is derived from providerId; use judge.providerId instead.');
|
|
161
|
+
}
|
|
162
|
+
if (pathKey === 'judge.credential.mode') {
|
|
163
|
+
if (value !== 'project' && value !== 'apiKey') {
|
|
164
|
+
throw new Error('judge.credential.mode must be project or apiKey.');
|
|
165
|
+
}
|
|
166
|
+
const judge = normalizeJudgeConfig({
|
|
167
|
+
...config.judge,
|
|
168
|
+
credential: value === 'project' ? { mode: 'project' } : { mode: 'apiKey', ref: 'store:judge' },
|
|
169
|
+
});
|
|
170
|
+
await persistJudge(repoRoot, config, judge, adapter);
|
|
171
|
+
return judge;
|
|
172
|
+
}
|
|
173
|
+
if (pathKey === 'judge.credential.ref') {
|
|
174
|
+
if (value !== 'store:judge' && !value.startsWith('env:')) {
|
|
175
|
+
throw new Error('judge.credential.ref must be store:judge or env:NAME.');
|
|
176
|
+
}
|
|
177
|
+
const judge = normalizeJudgeConfig({
|
|
178
|
+
...config.judge,
|
|
179
|
+
credential: { mode: 'apiKey', ref: value },
|
|
180
|
+
});
|
|
181
|
+
await persistJudge(repoRoot, config, judge, adapter);
|
|
182
|
+
return judge;
|
|
183
|
+
}
|
|
184
|
+
throw new Error(`Unknown judge config path: ${pathKey}`);
|
|
185
|
+
}
|
|
186
|
+
async function applyJudgeUnset(repoRoot, config, pathKey) {
|
|
187
|
+
const adapter = resolveAdapterName(config);
|
|
188
|
+
if (pathKey === 'judge.endpoint') {
|
|
189
|
+
const judge = normalizeJudgeConfig({ ...config.judge, endpoint: null });
|
|
190
|
+
await persistJudge(repoRoot, config, judge, adapter);
|
|
191
|
+
return judge;
|
|
192
|
+
}
|
|
193
|
+
if (pathKey === 'judge.credential.ref') {
|
|
194
|
+
const judge = normalizeJudgeConfig({
|
|
195
|
+
...config.judge,
|
|
196
|
+
credential: config.judge.credential?.mode === 'apiKey' ? { mode: 'apiKey' } : undefined,
|
|
197
|
+
});
|
|
198
|
+
await persistJudge(repoRoot, config, judge, adapter);
|
|
199
|
+
return judge;
|
|
200
|
+
}
|
|
201
|
+
throw new Error(`Cannot unset ${pathKey}; only judge.endpoint and judge.credential.ref are unsettable.`);
|
|
202
|
+
}
|
|
203
|
+
async function appendConfigAudit(repoRoot, config, event) {
|
|
204
|
+
const adapter = resolveAdapterName(config);
|
|
205
|
+
const updated = await loadConfigFile(repoRoot, adapter);
|
|
206
|
+
await appendCliAuditEvent(repoRoot, updated, event);
|
|
207
|
+
}
|
|
208
|
+
export async function runBelayConfigCredential(options) {
|
|
209
|
+
const repoRoot = path.resolve(options.targetDir ?? process.cwd());
|
|
210
|
+
const config = await loadConfigFile(repoRoot);
|
|
211
|
+
const adapter = resolveAdapterName(config);
|
|
212
|
+
if (options.action === 'mode') {
|
|
213
|
+
if (options.mode !== 'project' && options.mode !== 'apiKey') {
|
|
214
|
+
throw new Error('credential mode requires project or apiKey.');
|
|
215
|
+
}
|
|
216
|
+
const judge = options.mode === 'project'
|
|
217
|
+
? normalizeJudgeConfig({ ...config.judge, credential: { mode: 'project' } })
|
|
218
|
+
: normalizeJudgeConfig({
|
|
219
|
+
...config.judge,
|
|
220
|
+
credential: options.keyEnv
|
|
221
|
+
? { mode: 'apiKey', ref: `env:${options.keyEnv}` }
|
|
222
|
+
: { mode: 'apiKey', ref: 'store:judge' },
|
|
223
|
+
});
|
|
224
|
+
await persistJudge(repoRoot, config, judge, adapter);
|
|
225
|
+
await appendConfigAudit(repoRoot, config, {
|
|
226
|
+
event: 'judge_config_credential',
|
|
227
|
+
action: 'mode',
|
|
228
|
+
mode: options.mode,
|
|
229
|
+
by: 'belay config credential mode',
|
|
230
|
+
});
|
|
231
|
+
return options.mode === 'project'
|
|
232
|
+
? 'Credential mode set to project.'
|
|
233
|
+
: 'Credential mode set to apiKey.';
|
|
234
|
+
}
|
|
235
|
+
if (options.action === 'set') {
|
|
236
|
+
let key;
|
|
237
|
+
if (options.keyStdin) {
|
|
238
|
+
key = await readKeyFromStdin();
|
|
239
|
+
if (!key) {
|
|
240
|
+
throw new Error('--key-stdin requires a non-empty API key on stdin.');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (options.keyEnv) {
|
|
244
|
+
const judge = normalizeJudgeConfig({
|
|
245
|
+
...config.judge,
|
|
246
|
+
credential: { mode: 'apiKey', ref: `env:${options.keyEnv}` },
|
|
247
|
+
});
|
|
248
|
+
await persistJudge(repoRoot, config, judge, adapter);
|
|
249
|
+
await appendConfigAudit(repoRoot, config, {
|
|
250
|
+
event: 'judge_config_credential',
|
|
251
|
+
action: 'set',
|
|
252
|
+
ref: `env:${options.keyEnv}`,
|
|
253
|
+
by: 'belay config credential set',
|
|
254
|
+
});
|
|
255
|
+
return `Credential ref set to env:${options.keyEnv}.`;
|
|
256
|
+
}
|
|
257
|
+
if (!key) {
|
|
258
|
+
throw new Error('credential set requires --key-stdin or --key-env.');
|
|
259
|
+
}
|
|
260
|
+
const stateDir = belayStateDir(config, repoLocalStateDirFor(repoRoot, config));
|
|
261
|
+
await writeJudgeCredentialStore(stateDir, key);
|
|
262
|
+
const judge = normalizeJudgeConfig({
|
|
263
|
+
...config.judge,
|
|
264
|
+
credential: { mode: 'apiKey', ref: 'store:judge' },
|
|
265
|
+
});
|
|
266
|
+
await persistJudge(repoRoot, config, judge, adapter);
|
|
267
|
+
await appendConfigAudit(repoRoot, config, {
|
|
268
|
+
event: 'judge_config_credential',
|
|
269
|
+
action: 'set',
|
|
270
|
+
ref: 'store:judge',
|
|
271
|
+
by: 'belay config credential set',
|
|
272
|
+
});
|
|
273
|
+
return 'API key stored in belay credential store.';
|
|
274
|
+
}
|
|
275
|
+
if (options.action === 'clear') {
|
|
276
|
+
const stateDir = belayStateDir(config, repoLocalStateDirFor(repoRoot, config));
|
|
277
|
+
await clearJudgeCredentialStore(stateDir);
|
|
278
|
+
const judge = normalizeJudgeConfig({
|
|
279
|
+
...config.judge,
|
|
280
|
+
credential: config.judge.credential?.mode === 'project' ? { mode: 'project' } : undefined,
|
|
281
|
+
});
|
|
282
|
+
await persistJudge(repoRoot, config, judge, adapter);
|
|
283
|
+
await appendConfigAudit(repoRoot, config, {
|
|
284
|
+
event: 'judge_config_credential',
|
|
285
|
+
action: 'clear',
|
|
286
|
+
by: 'belay config credential clear',
|
|
287
|
+
});
|
|
288
|
+
return 'Stored API key cleared.';
|
|
289
|
+
}
|
|
290
|
+
throw new Error('credential requires action: mode, set, or clear.');
|
|
291
|
+
}
|
|
292
|
+
function formatSelectPrompt(options) {
|
|
293
|
+
const values = options.choices.map((choice) => choice.value).join(' | ');
|
|
294
|
+
return `${options.message} [${values}] (${options.defaultValue}): `;
|
|
295
|
+
}
|
|
296
|
+
function formatConfirmPrompt(message, defaultValue) {
|
|
297
|
+
return `${message} [y | n] (${defaultValue ? 'y' : 'n'}): `;
|
|
298
|
+
}
|
|
299
|
+
function parseSelectAnswer(raw, options) {
|
|
300
|
+
const normalized = raw.trim().toLowerCase();
|
|
301
|
+
if (!normalized) {
|
|
302
|
+
return options.defaultValue;
|
|
303
|
+
}
|
|
304
|
+
const found = options.choices.find((choice) => {
|
|
305
|
+
if (choice.value.toLowerCase() === normalized) {
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
return choice.label?.trim().toLowerCase() === normalized;
|
|
309
|
+
});
|
|
310
|
+
if (!found) {
|
|
311
|
+
throw new Error(`Invalid choice for ${options.message}: ${raw}`);
|
|
312
|
+
}
|
|
313
|
+
return found.value;
|
|
314
|
+
}
|
|
315
|
+
async function askTextLazy(message) {
|
|
316
|
+
const rl = readline.createInterface({ input, output });
|
|
317
|
+
try {
|
|
318
|
+
return (await rl.question(message)).trimEnd();
|
|
319
|
+
}
|
|
320
|
+
finally {
|
|
321
|
+
rl.close();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function createTestPrompter(prompts) {
|
|
325
|
+
let index = 0;
|
|
326
|
+
const nextPrompt = (message) => {
|
|
327
|
+
if (index >= prompts.length) {
|
|
328
|
+
throw new Error(`unexpected config prompt: ${message}`);
|
|
329
|
+
}
|
|
330
|
+
return prompts[index++];
|
|
331
|
+
};
|
|
332
|
+
return {
|
|
333
|
+
askText: async (message) => nextPrompt(message),
|
|
334
|
+
askSelect: async (options, parseRaw) => {
|
|
335
|
+
const raw = nextPrompt(formatSelectPrompt(options));
|
|
336
|
+
return parseRaw ? parseRaw(raw, options) : parseSelectAnswer(raw, options);
|
|
337
|
+
},
|
|
338
|
+
askConfirm: async (message, defaultValue) => parseYesNo(nextPrompt(formatConfirmPrompt(message, defaultValue)), defaultValue),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function createReadlinePrompter() {
|
|
342
|
+
return {
|
|
343
|
+
askText: askTextLazy,
|
|
344
|
+
askSelect: async (options, parseRaw) => {
|
|
345
|
+
const raw = await askTextLazy(formatSelectPrompt(options));
|
|
346
|
+
return parseRaw ? parseRaw(raw, options) : parseSelectAnswer(raw, options);
|
|
347
|
+
},
|
|
348
|
+
askConfirm: async (message, defaultValue) => parseYesNo(await askTextLazy(formatConfirmPrompt(message, defaultValue)), defaultValue),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function createTuiPrompter() {
|
|
352
|
+
return {
|
|
353
|
+
askText: askTextLazy,
|
|
354
|
+
askSelect: selectPrompt,
|
|
355
|
+
askConfirm: confirmPrompt,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
async function withConfigWizardPrompter(options, fn) {
|
|
359
|
+
if (options.prompts) {
|
|
360
|
+
return fn(createTestPrompter(options.prompts));
|
|
361
|
+
}
|
|
362
|
+
if (isInteractiveTTY()) {
|
|
363
|
+
return fn(createTuiPrompter());
|
|
364
|
+
}
|
|
365
|
+
return fn(createReadlinePrompter());
|
|
366
|
+
}
|
|
367
|
+
function writeConfigWizardBanner(options, title) {
|
|
368
|
+
if (options.skipBanner) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (isInteractiveTTY()) {
|
|
372
|
+
intro(title);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
output.write(`${title}\n`);
|
|
376
|
+
}
|
|
377
|
+
async function collectCloudJudgeWizardAnswers(prompter, judgeProviderId) {
|
|
378
|
+
if (judgeProviderId === 'ollama') {
|
|
379
|
+
return { acceptCloud: false };
|
|
380
|
+
}
|
|
381
|
+
const judgeCredentialMode = (await prompter.askConfirm('Use project env for credentials?', true))
|
|
382
|
+
? 'project'
|
|
383
|
+
: 'apiKey';
|
|
384
|
+
const optionalEndpoint = (await prompter.askText('Judge endpoint URL (optional): ')).trim();
|
|
385
|
+
const judgeEndpoint = optionalEndpoint || undefined;
|
|
386
|
+
if (judgeCredentialMode === 'apiKey') {
|
|
387
|
+
const key = await prompter.askText('Paste API key (hidden input not available in all shells): ');
|
|
388
|
+
if (key.trim()) {
|
|
389
|
+
process.env.BELAY_CONFIG_WIZARD_JUDGE_KEY = key.trim();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
let acceptCloud = false;
|
|
393
|
+
if (judgeEndpoint) {
|
|
394
|
+
acceptCloud = await prompter.askConfirm('Accept cloud judge egress (redacted commands leave the repo)?', false);
|
|
395
|
+
}
|
|
396
|
+
return { judgeCredentialMode, judgeEndpoint, acceptCloud };
|
|
397
|
+
}
|
|
398
|
+
export async function resolveBelayConfigInteractiveMode(repoRoot) {
|
|
399
|
+
try {
|
|
400
|
+
return (await isBelayFloorInstalled({ targetDir: repoRoot })) ? 'judge-only' : 'full';
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
return 'full';
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
async function runBelayConfigFullWithWizard(prompter, options) {
|
|
407
|
+
writeConfigWizardBanner(options, 'belay config');
|
|
408
|
+
const adapter = await prompter.askSelect({
|
|
409
|
+
message: 'Adapter',
|
|
410
|
+
defaultValue: 'cursor',
|
|
411
|
+
choices: [{ value: 'cursor' }, { value: 'claude' }, { value: 'codex' }],
|
|
412
|
+
});
|
|
413
|
+
const scope = await prompter.askSelect({
|
|
414
|
+
message: 'Install scope',
|
|
415
|
+
defaultValue: 'project',
|
|
416
|
+
choices: [{ value: 'project' }, { value: 'global' }],
|
|
417
|
+
});
|
|
418
|
+
const withSkill = await prompter.askConfirm('Install SKILL.md and slash commands?', true);
|
|
419
|
+
const defaultJudgeProviderId = defaultJudgeProviderForAdapter(adapter);
|
|
420
|
+
const judgeProviderId = await prompter.askSelect({
|
|
421
|
+
message: 'Judge provider',
|
|
422
|
+
defaultValue: defaultJudgeProviderId,
|
|
423
|
+
choices: JUDGE_PROVIDER_IDS.map((providerId) => ({ value: providerId })),
|
|
424
|
+
}, (raw, selectOptions) => parseJudgeProviderId(raw, selectOptions.defaultValue));
|
|
425
|
+
const { judgeCredentialMode, judgeEndpoint, acceptCloud } = await collectCloudJudgeWizardAnswers(prompter, judgeProviderId);
|
|
426
|
+
const initOptions = buildInitOptionsFromConfigAnswers({
|
|
427
|
+
adapter,
|
|
428
|
+
scope,
|
|
429
|
+
withSkill,
|
|
430
|
+
judgeProviderId,
|
|
431
|
+
judgeCredentialMode,
|
|
432
|
+
judgeEndpoint,
|
|
433
|
+
acceptCloud,
|
|
434
|
+
dogfood: false,
|
|
435
|
+
}, options.targetDir);
|
|
436
|
+
const result = await initProject(initOptions);
|
|
437
|
+
if (process.env.BELAY_CONFIG_WIZARD_JUDGE_KEY && judgeCredentialMode === 'apiKey') {
|
|
438
|
+
const config = await loadConfigFile(result.repoRoot, result.adapter);
|
|
439
|
+
const stateDir = belayStateDir(config, repoLocalStateDirFor(result.repoRoot, config));
|
|
440
|
+
await writeJudgeCredentialStore(stateDir, process.env.BELAY_CONFIG_WIZARD_JUDGE_KEY);
|
|
441
|
+
delete process.env.BELAY_CONFIG_WIZARD_JUDGE_KEY;
|
|
442
|
+
}
|
|
443
|
+
return result;
|
|
444
|
+
}
|
|
445
|
+
async function runBelayConfigJudgeOnlyWithWizard(prompter, options, repoRoot, config, adapter) {
|
|
446
|
+
writeConfigWizardBanner(options, 'belay config (judge only)');
|
|
447
|
+
const defaultJudgeProviderId = defaultJudgeProviderForAdapter(adapter);
|
|
448
|
+
const judgeProviderId = await prompter.askSelect({
|
|
449
|
+
message: 'Judge provider',
|
|
450
|
+
defaultValue: defaultJudgeProviderId,
|
|
451
|
+
choices: JUDGE_PROVIDER_IDS.map((providerId) => ({ value: providerId })),
|
|
452
|
+
}, (raw, selectOptions) => parseJudgeProviderId(raw, selectOptions.defaultValue));
|
|
453
|
+
const { judgeCredentialMode, judgeEndpoint, acceptCloud } = await collectCloudJudgeWizardAnswers(prompter, judgeProviderId);
|
|
454
|
+
const patch = resolveJudgeUsePatch(config.judge, {
|
|
455
|
+
providerId: judgeProviderId,
|
|
456
|
+
endpoint: judgeEndpoint,
|
|
457
|
+
credentialMode: judgeCredentialMode,
|
|
458
|
+
acceptCloud: acceptCloud && Boolean(judgeEndpoint),
|
|
459
|
+
// `prompts` is a scripted stand-in for interactive responses in tests.
|
|
460
|
+
interactiveTTY: isInteractiveTTY() || Boolean(options.prompts),
|
|
461
|
+
interactiveConsentApproved: acceptCloud && Boolean(judgeEndpoint),
|
|
462
|
+
});
|
|
463
|
+
if (patch.errors.length > 0) {
|
|
464
|
+
throw new Error(patch.errors.join(' '));
|
|
465
|
+
}
|
|
466
|
+
for (const warning of patch.warnings) {
|
|
467
|
+
process.stderr.write(`Warning: ${warning}\n`);
|
|
468
|
+
}
|
|
469
|
+
const updated = await persistJudge(repoRoot, config, patch.judge, adapter);
|
|
470
|
+
warnCloudConsentIfNeeded(updated.judge);
|
|
471
|
+
if (process.env.BELAY_CONFIG_WIZARD_JUDGE_KEY && judgeCredentialMode === 'apiKey') {
|
|
472
|
+
const stateDir = belayStateDir(updated, repoLocalStateDirFor(repoRoot, updated));
|
|
473
|
+
await writeJudgeCredentialStore(stateDir, process.env.BELAY_CONFIG_WIZARD_JUDGE_KEY);
|
|
474
|
+
delete process.env.BELAY_CONFIG_WIZARD_JUDGE_KEY;
|
|
475
|
+
}
|
|
476
|
+
await appendConfigAudit(repoRoot, updated, {
|
|
477
|
+
event: 'judge_config_interactive',
|
|
478
|
+
providerId: updated.judge.providerId,
|
|
479
|
+
credentialMode: updated.judge.credential?.mode ?? null,
|
|
480
|
+
by: 'belay config interactive (judge only)',
|
|
481
|
+
});
|
|
482
|
+
return { repoRoot, adapter };
|
|
483
|
+
}
|
|
484
|
+
export async function runBelayConfigFullInteractive(options = {}) {
|
|
485
|
+
return withConfigWizardPrompter(options, (prompter) => runBelayConfigFullWithWizard(prompter, options));
|
|
486
|
+
}
|
|
487
|
+
export async function runBelayConfigJudgeOnlyInteractive(options = {}) {
|
|
488
|
+
const repoRoot = path.resolve(options.targetDir ?? process.cwd());
|
|
489
|
+
const config = await loadConfigFile(repoRoot);
|
|
490
|
+
const adapter = resolveAdapterName(config);
|
|
491
|
+
return withConfigWizardPrompter(options, (prompter) => runBelayConfigJudgeOnlyWithWizard(prompter, options, repoRoot, config, adapter));
|
|
492
|
+
}
|
|
493
|
+
export async function runBelayConfigInteractive(options = {}) {
|
|
494
|
+
const repoRoot = path.resolve(options.targetDir ?? process.cwd());
|
|
495
|
+
const mode = await resolveBelayConfigInteractiveMode(repoRoot);
|
|
496
|
+
if (mode === 'judge-only') {
|
|
497
|
+
if (options.prompts) {
|
|
498
|
+
const judgeOnly = parseYesNo(options.prompts[0] ?? '', true);
|
|
499
|
+
const remainingPrompts = options.prompts.slice(1);
|
|
500
|
+
if (judgeOnly) {
|
|
501
|
+
return runBelayConfigJudgeOnlyInteractive({
|
|
502
|
+
...options,
|
|
503
|
+
prompts: remainingPrompts,
|
|
504
|
+
skipBanner: true,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
return runBelayConfigFullInteractive({
|
|
508
|
+
...options,
|
|
509
|
+
prompts: remainingPrompts,
|
|
510
|
+
skipBanner: true,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
return withConfigWizardPrompter(options, async (prompter) => {
|
|
514
|
+
writeConfigWizardBanner(options, 'belay config');
|
|
515
|
+
const judgeOnly = await prompter.askConfirm('Configure judge only?', true);
|
|
516
|
+
if (!judgeOnly) {
|
|
517
|
+
return runBelayConfigFullWithWizard(prompter, { ...options, skipBanner: true });
|
|
518
|
+
}
|
|
519
|
+
const config = await loadConfigFile(repoRoot);
|
|
520
|
+
const adapter = resolveAdapterName(config);
|
|
521
|
+
return runBelayConfigJudgeOnlyWithWizard(prompter, { ...options, skipBanner: true }, repoRoot, config, adapter);
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
return runBelayConfigFullInteractive(options);
|
|
525
|
+
}
|
|
526
|
+
export async function runBelayConfig(options = {}) {
|
|
527
|
+
if (!options.subcommand) {
|
|
528
|
+
return runBelayConfigInteractive({ targetDir: options.targetDir });
|
|
529
|
+
}
|
|
530
|
+
const repoRoot = path.resolve(options.targetDir ?? process.cwd());
|
|
531
|
+
const config = await loadConfigFile(repoRoot);
|
|
532
|
+
if (options.subcommand === 'list') {
|
|
533
|
+
const entries = listJudgeFields(config.judge);
|
|
534
|
+
if (options.json) {
|
|
535
|
+
return entries;
|
|
536
|
+
}
|
|
537
|
+
return Object.entries(entries)
|
|
538
|
+
.map(([key, value]) => `${key}: ${value === null ? '(null)' : String(value)}`)
|
|
539
|
+
.join('\n');
|
|
540
|
+
}
|
|
541
|
+
if (options.subcommand === 'get') {
|
|
542
|
+
const pathKey = assertJudgeConfigPath(options.path);
|
|
543
|
+
const value = getJudgeField(config.judge, pathKey);
|
|
544
|
+
if (options.json) {
|
|
545
|
+
return { path: pathKey, value };
|
|
546
|
+
}
|
|
547
|
+
return value === null ? '(null)' : String(value);
|
|
548
|
+
}
|
|
549
|
+
if (options.subcommand === 'set') {
|
|
550
|
+
const pathKey = assertJudgeConfigPath(options.path);
|
|
551
|
+
if (options.value === undefined) {
|
|
552
|
+
throw new Error('belay config set requires <path> <value>.');
|
|
553
|
+
}
|
|
554
|
+
await applyJudgeSet(repoRoot, config, pathKey, options.value);
|
|
555
|
+
await appendConfigAudit(repoRoot, config, {
|
|
556
|
+
event: 'judge_config_set',
|
|
557
|
+
path: pathKey,
|
|
558
|
+
value: options.value,
|
|
559
|
+
by: 'belay config set',
|
|
560
|
+
});
|
|
561
|
+
return `Set ${pathKey} = ${options.value}`;
|
|
562
|
+
}
|
|
563
|
+
if (options.subcommand === 'unset') {
|
|
564
|
+
const pathKey = assertJudgeConfigPath(options.path);
|
|
565
|
+
await applyJudgeUnset(repoRoot, config, pathKey);
|
|
566
|
+
await appendConfigAudit(repoRoot, config, {
|
|
567
|
+
event: 'judge_config_unset',
|
|
568
|
+
path: pathKey,
|
|
569
|
+
by: 'belay config unset',
|
|
570
|
+
});
|
|
571
|
+
return `Unset ${pathKey}`;
|
|
572
|
+
}
|
|
573
|
+
if (options.subcommand === 'judge') {
|
|
574
|
+
return judgeStatus({ targetDir: repoRoot, json: options.json });
|
|
575
|
+
}
|
|
576
|
+
throw new Error(`Unknown config subcommand: ${options.subcommand}`);
|
|
577
|
+
}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -10,7 +10,7 @@ import { approvedApprovalsPath, belayStateDir, detectAdapterName, loadLayeredCon
|
|
|
10
10
|
import { detectFenceDrift, summarizeAuditVisibility } from '../core/audit-summary.js';
|
|
11
11
|
import { defaultControlPlaneDir } from '../core/config.js';
|
|
12
12
|
import { verifyIntegrityManifest } from '../core/integrity.js';
|
|
13
|
-
import { diagnoseJudge } from '../core/judge-doctor.js';
|
|
13
|
+
import { diagnoseJudge, stopJudgeSessionBrokers } from '../core/judge-doctor.js';
|
|
14
14
|
import { getManagedHookEntries } from '../defaults.js';
|
|
15
15
|
import { resolveNodeBinary } from '../node-resolution.js';
|
|
16
16
|
import { egressStatus } from '../services/egress-service.js';
|
|
@@ -56,6 +56,7 @@ export async function doctorProject(options = {}) {
|
|
|
56
56
|
let corePath = path.join(activeLayout.runtimeDir(repoRoot), 'core.mjs');
|
|
57
57
|
if (!existsSync(configPath)) {
|
|
58
58
|
issues.push(`Missing config: ${configPath}`);
|
|
59
|
+
notes.push('No belay config found. Run `belay config` for interactive setup, or `belay init` for non-interactive install.');
|
|
59
60
|
}
|
|
60
61
|
else {
|
|
61
62
|
try {
|
|
@@ -238,6 +239,16 @@ export async function doctorProject(options = {}) {
|
|
|
238
239
|
else {
|
|
239
240
|
notes.push('No orphan approval cleanup actions were needed.');
|
|
240
241
|
}
|
|
242
|
+
const judgeStateDir = belayStateDir(loadedConfig, repoLocalDir);
|
|
243
|
+
if (options.dryRun !== true) {
|
|
244
|
+
const stoppedBrokers = await stopJudgeSessionBrokers(repoRoot, judgeStateDir);
|
|
245
|
+
if (stoppedBrokers > 0) {
|
|
246
|
+
notes.push(`Stopped judge session broker artifacts for ${repoRoot}.`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
notes.push('Dry run: would stop judge session broker and clear kill switch if present.');
|
|
251
|
+
}
|
|
241
252
|
}
|
|
242
253
|
let dogfood = null;
|
|
243
254
|
if (loadedConfig) {
|
|
@@ -310,7 +321,7 @@ export async function doctorProject(options = {}) {
|
|
|
310
321
|
}
|
|
311
322
|
if (health.skillOnly) {
|
|
312
323
|
warnings.push('Skill-only install detected: belay SKILL.md is present but hook floor is missing or incomplete. ' +
|
|
313
|
-
'This is advisory only — enforcement requires hooks. Run `
|
|
324
|
+
'This is advisory only — enforcement requires hooks. Run `belay config` (or `belay init`) ' +
|
|
314
325
|
'then `belay doctor` to verify the floor.');
|
|
315
326
|
notes.push(`Skill path: ${health.skillPath}`);
|
|
316
327
|
}
|
package/dist/commands/explain.js
CHANGED
|
@@ -11,11 +11,16 @@ export async function explainCommand(options) {
|
|
|
11
11
|
const latest = [...pending.approvals].sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0];
|
|
12
12
|
const inputKind = latest.inputKind ??
|
|
13
13
|
(latest.kind === 'egress' || latest.kind === 'capability' ? 'shell' : latest.kind);
|
|
14
|
+
const payload = latest.payloadJson !== undefined
|
|
15
|
+
? JSON.parse(latest.payloadJson)
|
|
16
|
+
: undefined;
|
|
14
17
|
const classified = await classifyForReport({
|
|
15
18
|
targetDir: repoRoot,
|
|
16
|
-
cwd: options.cwd,
|
|
19
|
+
cwd: latest.cwd ?? options.cwd,
|
|
17
20
|
kind: inputKind,
|
|
18
21
|
command: latest.input ?? latest.summary,
|
|
22
|
+
toolName: latest.toolName,
|
|
23
|
+
payload,
|
|
19
24
|
});
|
|
20
25
|
return {
|
|
21
26
|
repoRoot: classified.repoRoot,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AuditRecord } from '../core/audit-types.js';
|
|
2
|
+
import { type HarvestReport, type HarvestReviewOutcome } from '../core/harvest.js';
|
|
3
|
+
export interface HarvestListOptions {
|
|
4
|
+
targetDir?: string;
|
|
5
|
+
since?: string;
|
|
6
|
+
until?: string;
|
|
7
|
+
json?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface HarvestApplyOptions {
|
|
10
|
+
targetDir?: string;
|
|
11
|
+
command: string;
|
|
12
|
+
outcome: HarvestReviewOutcome;
|
|
13
|
+
reason?: string;
|
|
14
|
+
corpusPath?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function harvestListProject(options?: HarvestListOptions): Promise<HarvestReport>;
|
|
17
|
+
export declare function harvestReportFromRecords(records: AuditRecord[], options?: {
|
|
18
|
+
since?: string;
|
|
19
|
+
until?: string;
|
|
20
|
+
allowPatterns?: string[];
|
|
21
|
+
}): HarvestReport;
|
|
22
|
+
export declare function formatHarvestReport(report: HarvestReport): string;
|
|
23
|
+
export declare function harvestApplyProject(options: HarvestApplyOptions): Promise<{
|
|
24
|
+
ok: boolean;
|
|
25
|
+
message: string;
|
|
26
|
+
corpusPath: string;
|
|
27
|
+
}>;
|
|
28
|
+
/** Parse audit ndjson for tests without full project layout. */
|
|
29
|
+
export declare function harvestReportFromNdjson(raw: string): HarvestReport;
|