@neurcode-ai/cli 0.16.5 → 0.16.6
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/.telemetry-bundle/dist/index.js +0 -0
- package/LICENSE +201 -0
- package/dist/commands/brain.d.ts.map +1 -1
- package/dist/commands/brain.js +151 -0
- package/dist/commands/brain.js.map +1 -1
- package/dist/commands/eval.d.ts +19 -0
- package/dist/commands/eval.d.ts.map +1 -0
- package/dist/commands/eval.js +246 -0
- package/dist/commands/eval.js.map +1 -0
- package/dist/commands/onboard.d.ts +29 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +247 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/runtime-sync.d.ts.map +1 -1
- package/dist/commands/runtime-sync.js +22 -0
- package/dist/commands/runtime-sync.js.map +1 -1
- package/dist/commands/session-hook.d.ts.map +1 -1
- package/dist/commands/session-hook.js +221 -102
- package/dist/commands/session-hook.js.map +1 -1
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +31 -0
- package/dist/commands/session.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime-build.json +5 -5
- package/dist/utils/guided-eval.d.ts +251 -0
- package/dist/utils/guided-eval.d.ts.map +1 -0
- package/dist/utils/guided-eval.js +880 -0
- package/dist/utils/guided-eval.js.map +1 -0
- package/dist/utils/local-repo-brain.d.ts +158 -0
- package/dist/utils/local-repo-brain.d.ts.map +1 -0
- package/dist/utils/local-repo-brain.js +854 -0
- package/dist/utils/local-repo-brain.js.map +1 -0
- package/dist/utils/structural-understanding.d.ts +61 -1
- package/dist/utils/structural-understanding.d.ts.map +1 -1
- package/dist/utils/structural-understanding.js +534 -1
- package/dist/utils/structural-understanding.js.map +1 -1
- package/package.json +7 -8
|
@@ -0,0 +1,880 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Guided Evaluation Runner — shared state engine.
|
|
4
|
+
*
|
|
5
|
+
* One source-free model that turns the Enterprise Evaluation from a static
|
|
6
|
+
* checklist into a progress-aware guided flow. The CLI `neurcode eval`
|
|
7
|
+
* command group, the `demo:guided-enterprise-eval` harness, and (by mirror)
|
|
8
|
+
* the dashboard all key off the same step ids and truth tiers defined here.
|
|
9
|
+
*
|
|
10
|
+
* Hard rules:
|
|
11
|
+
* - Source-free. We only ever read/emit paths, owners, symbol names, counts,
|
|
12
|
+
* verdicts, hashes, and truth-tier labels. Never source, diffs, prompts,
|
|
13
|
+
* secrets, or private file contents. {@link assertGuidedEvalSourceFree}
|
|
14
|
+
* is the backstop run before any report is written.
|
|
15
|
+
* - Honest. Every step carries exactly one truth tier. A step we cannot
|
|
16
|
+
* measure is `not_evaluated`, never silently "done".
|
|
17
|
+
* - Read-only against the user's repo. Nothing here mutates user source.
|
|
18
|
+
* The only writer is {@link scaffoldEvalFixture}, used solely for the
|
|
19
|
+
* explicit `--fixture` safe-demo mode.
|
|
20
|
+
*
|
|
21
|
+
* Keep the step ids / labels / tiers in lockstep with the dashboard mirror at
|
|
22
|
+
* `web/dashboard/src/lib/guidedEval.ts` and the truth taxonomy at
|
|
23
|
+
* `scripts/lib/truth-taxonomy.mjs` + `web/dashboard/src/lib/truthTaxonomy.ts`.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.GUIDED_EVAL_REPORT_SCHEMA_VERSION = exports.GUIDED_EVAL_STEPS = exports.GUIDED_EVAL_AGENTS = exports.GUIDED_EVAL_TRUTH_TIERS = exports.GUIDED_EVAL_SCHEMA_VERSION = void 0;
|
|
27
|
+
exports.normalizeGuidedEvalAgent = normalizeGuidedEvalAgent;
|
|
28
|
+
exports.enforcementForAgent = enforcementForAgent;
|
|
29
|
+
exports.enforcementLabel = enforcementLabel;
|
|
30
|
+
exports.hashRepoIdentity = hashRepoIdentity;
|
|
31
|
+
exports.gatherGuidedEvalContext = gatherGuidedEvalContext;
|
|
32
|
+
exports.stepCommand = stepCommand;
|
|
33
|
+
exports.buildGuidedEvalState = buildGuidedEvalState;
|
|
34
|
+
exports.findSourceLeaks = findSourceLeaks;
|
|
35
|
+
exports.assertGuidedEvalSourceFree = assertGuidedEvalSourceFree;
|
|
36
|
+
exports.buildGuidedEvalReport = buildGuidedEvalReport;
|
|
37
|
+
exports.renderGuidedEvalReportMarkdown = renderGuidedEvalReportMarkdown;
|
|
38
|
+
exports.scaffoldEvalFixture = scaffoldEvalFixture;
|
|
39
|
+
const node_child_process_1 = require("node:child_process");
|
|
40
|
+
const node_crypto_1 = require("node:crypto");
|
|
41
|
+
const node_fs_1 = require("node:fs");
|
|
42
|
+
const node_path_1 = require("node:path");
|
|
43
|
+
const governance_runtime_1 = require("@neurcode-ai/governance-runtime");
|
|
44
|
+
const runtime_state_1 = require("./runtime-state");
|
|
45
|
+
const brain_cache_1 = require("./brain-cache");
|
|
46
|
+
const local_repo_brain_1 = require("./local-repo-brain");
|
|
47
|
+
const runtime_connection_1 = require("./runtime-connection");
|
|
48
|
+
const runtime_evidence_1 = require("./runtime-evidence");
|
|
49
|
+
const admission_artifact_1 = require("./admission-artifact");
|
|
50
|
+
exports.GUIDED_EVAL_SCHEMA_VERSION = 'neurcode.guided-eval.v1';
|
|
51
|
+
exports.GUIDED_EVAL_TRUTH_TIERS = {
|
|
52
|
+
deterministic: {
|
|
53
|
+
label: 'Deterministic fact',
|
|
54
|
+
proves: 'Proves the stated structural fact. Does not prove the change is correct or safe.',
|
|
55
|
+
},
|
|
56
|
+
backend_signed: {
|
|
57
|
+
label: 'Backend-signed evidence',
|
|
58
|
+
proves: 'Verifies the record was issued under the configured Neurcode signing key and was not altered. Does not prove source correctness or vulnerability absence.',
|
|
59
|
+
},
|
|
60
|
+
advisory: {
|
|
61
|
+
label: 'Advisory inference',
|
|
62
|
+
proves: 'Surfaces a likely signal worth a human look. Not a deterministic guarantee; can be a false positive.',
|
|
63
|
+
},
|
|
64
|
+
not_evaluated: {
|
|
65
|
+
label: 'Not evaluated / unknown',
|
|
66
|
+
proves: 'Proves nothing. Stated explicitly so a gap is never mistaken for a clean result.',
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
exports.GUIDED_EVAL_AGENTS = [
|
|
70
|
+
'claude',
|
|
71
|
+
'codex',
|
|
72
|
+
'cursor',
|
|
73
|
+
'vscode',
|
|
74
|
+
'copilot',
|
|
75
|
+
'action',
|
|
76
|
+
];
|
|
77
|
+
function normalizeGuidedEvalAgent(value) {
|
|
78
|
+
if (typeof value === 'string' && exports.GUIDED_EVAL_AGENTS.includes(value)) {
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
return 'claude';
|
|
82
|
+
}
|
|
83
|
+
function enforcementForAgent(agent) {
|
|
84
|
+
if (agent === 'claude')
|
|
85
|
+
return 'hard_hook';
|
|
86
|
+
if (agent === 'action')
|
|
87
|
+
return 'post_pr';
|
|
88
|
+
return 'supervised';
|
|
89
|
+
}
|
|
90
|
+
function enforcementLabel(enforcement) {
|
|
91
|
+
switch (enforcement) {
|
|
92
|
+
case 'hard_hook':
|
|
93
|
+
return 'Hard pre-write deny (Claude hooks where installed and healthy)';
|
|
94
|
+
case 'supervised':
|
|
95
|
+
return 'Cooperative guard + source-free supervisor evidence';
|
|
96
|
+
case 'post_pr':
|
|
97
|
+
return 'Post-PR advisory routing (no live pre-write enforcement)';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The canonical eleven evaluation checkpoints, in lifecycle order. The dashboard
|
|
102
|
+
* renders the same ids; the harness asserts parity.
|
|
103
|
+
*/
|
|
104
|
+
exports.GUIDED_EVAL_STEPS = [
|
|
105
|
+
{
|
|
106
|
+
id: 'cli_installed',
|
|
107
|
+
title: 'CLI installed',
|
|
108
|
+
truthTier: 'deterministic',
|
|
109
|
+
reportKey: 'cli_installed',
|
|
110
|
+
summary: 'The Neurcode CLI is on PATH and prints a version.',
|
|
111
|
+
appliesTo: ['hard_hook', 'supervised', 'post_pr'],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'repo_detected',
|
|
115
|
+
title: 'Repo detected',
|
|
116
|
+
truthTier: 'deterministic',
|
|
117
|
+
reportKey: 'repo_detected',
|
|
118
|
+
summary: 'A git repository with a HEAD commit is resolved to govern against.',
|
|
119
|
+
appliesTo: ['hard_hook', 'supervised', 'post_pr'],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: 'repo_brain_indexed',
|
|
123
|
+
title: 'Repo brain indexed',
|
|
124
|
+
truthTier: 'deterministic',
|
|
125
|
+
reportKey: 'repo_brain_indexed',
|
|
126
|
+
summary: 'A structural map of files, owners, symbols, and sensitive surfaces exists.',
|
|
127
|
+
appliesTo: ['hard_hook', 'supervised', 'post_pr'],
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'runtime_active',
|
|
131
|
+
title: 'Runtime active',
|
|
132
|
+
truthTier: 'deterministic',
|
|
133
|
+
reportKey: 'runtime_active',
|
|
134
|
+
summary: 'The local runtime is activated for the repo and paired.',
|
|
135
|
+
appliesTo: ['hard_hook', 'supervised'],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 'session_live',
|
|
139
|
+
title: 'Governed session live',
|
|
140
|
+
truthTier: 'deterministic',
|
|
141
|
+
reportKey: 'governed_session',
|
|
142
|
+
summary: 'At least one governed agent session has run (or is live).',
|
|
143
|
+
appliesTo: ['hard_hook', 'supervised'],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'block_observed',
|
|
147
|
+
title: 'Boundary block observed',
|
|
148
|
+
truthTier: 'deterministic',
|
|
149
|
+
reportKey: 'last_block',
|
|
150
|
+
summary: 'A protected-boundary write was blocked before it landed.',
|
|
151
|
+
appliesTo: ['hard_hook', 'supervised'],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: 'exact_approval_observed',
|
|
155
|
+
title: 'Exact-path approval observed',
|
|
156
|
+
truthTier: 'deterministic',
|
|
157
|
+
reportKey: 'exact_approval',
|
|
158
|
+
summary: 'An approval granted exactly one path — scope did not widen.',
|
|
159
|
+
appliesTo: ['hard_hook', 'supervised'],
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: 'neighbor_contained',
|
|
163
|
+
title: 'Neighbor containment',
|
|
164
|
+
truthTier: 'deterministic',
|
|
165
|
+
reportKey: 'neighbor_contained',
|
|
166
|
+
summary: 'A file adjacent to the approved path stayed blocked.',
|
|
167
|
+
appliesTo: ['hard_hook', 'supervised'],
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'ai_change_record_exported',
|
|
171
|
+
title: 'AI Change Record exported',
|
|
172
|
+
truthTier: 'deterministic',
|
|
173
|
+
reportKey: 'ai_change_record',
|
|
174
|
+
summary: 'A source-free AI Change Record / admission record was exported.',
|
|
175
|
+
appliesTo: ['hard_hook', 'supervised', 'post_pr'],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: 'backend_receipt_verified',
|
|
179
|
+
title: 'Backend receipt verified',
|
|
180
|
+
truthTier: 'backend_signed',
|
|
181
|
+
reportKey: 'backend_receipt',
|
|
182
|
+
summary: 'The record verifies against a signed receipt under the configured key.',
|
|
183
|
+
appliesTo: ['hard_hook', 'supervised', 'post_pr'],
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 'action_report_available',
|
|
187
|
+
title: 'Action report available',
|
|
188
|
+
truthTier: 'deterministic',
|
|
189
|
+
reportKey: 'action_report',
|
|
190
|
+
summary: 'The GitHub Action workflow is wired to render the source-free PR report.',
|
|
191
|
+
appliesTo: ['hard_hook', 'supervised', 'post_pr'],
|
|
192
|
+
optional: true,
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
/** Source-free repo identity: a hash of the absolute path, never its contents. */
|
|
196
|
+
function hashRepoIdentity(repoRoot) {
|
|
197
|
+
return (0, node_crypto_1.createHash)('sha256').update(repoRoot).digest('hex').slice(0, 16);
|
|
198
|
+
}
|
|
199
|
+
function safeCliVersion() {
|
|
200
|
+
try {
|
|
201
|
+
// Resolve the CLI's own package version without spawning a subprocess.
|
|
202
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
203
|
+
const pkg = require('../../package.json');
|
|
204
|
+
return typeof pkg?.version === 'string' ? pkg.version : null;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function isActionWorkflowConfigured(repoRoot) {
|
|
211
|
+
const candidates = [
|
|
212
|
+
(0, node_path_1.join)(repoRoot, '.github', 'workflows', 'neurcode.yml'),
|
|
213
|
+
(0, node_path_1.join)(repoRoot, '.github', 'workflows', 'neurcode.yaml'),
|
|
214
|
+
];
|
|
215
|
+
if (candidates.some((p) => (0, node_fs_1.existsSync)(p)))
|
|
216
|
+
return true;
|
|
217
|
+
// Any workflow that references the Neurcode Action counts.
|
|
218
|
+
const dir = (0, node_path_1.join)(repoRoot, '.github', 'workflows');
|
|
219
|
+
if (!(0, node_fs_1.existsSync)(dir))
|
|
220
|
+
return false;
|
|
221
|
+
try {
|
|
222
|
+
for (const file of (0, node_fs_1.readdirSync)(dir)) {
|
|
223
|
+
if (!/\.ya?ml$/.test(file))
|
|
224
|
+
continue;
|
|
225
|
+
const body = (0, node_fs_1.readFileSync)((0, node_path_1.join)(dir, file), 'utf8');
|
|
226
|
+
if (/neurcode-actions|@neurcode-ai\/action/i.test(body))
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// best-effort
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
function latestAiChangeRecord(repoRoot) {
|
|
236
|
+
const out = {
|
|
237
|
+
sessionId: null,
|
|
238
|
+
trustLevel: null,
|
|
239
|
+
receiptPresent: false,
|
|
240
|
+
receiptVerified: false,
|
|
241
|
+
};
|
|
242
|
+
const dir = (0, node_path_1.join)(repoRoot, '.neurcode-ai-record');
|
|
243
|
+
if (!(0, node_fs_1.existsSync)(dir))
|
|
244
|
+
return out;
|
|
245
|
+
let newestMtime = -1;
|
|
246
|
+
try {
|
|
247
|
+
for (const file of (0, node_fs_1.readdirSync)(dir)) {
|
|
248
|
+
if (!file.endsWith('.json') || file.endsWith('.receipt.json'))
|
|
249
|
+
continue;
|
|
250
|
+
const p = (0, node_path_1.join)(dir, file);
|
|
251
|
+
const mtime = (0, node_fs_1.statSync)(p).mtimeMs;
|
|
252
|
+
if (mtime <= newestMtime)
|
|
253
|
+
continue;
|
|
254
|
+
try {
|
|
255
|
+
const envelope = JSON.parse((0, node_fs_1.readFileSync)(p, 'utf8'));
|
|
256
|
+
const record = envelope?.record ?? envelope;
|
|
257
|
+
const sessionId = record?.session?.sessionId ?? file.replace(/\.json$/, '');
|
|
258
|
+
const trustLevel = typeof envelope?.trustLevel === 'string'
|
|
259
|
+
? envelope.trustLevel
|
|
260
|
+
: record?.integrity?.trustLevel ?? null;
|
|
261
|
+
const receiptPresent = Boolean(envelope?.receipt)
|
|
262
|
+
|| (0, node_fs_1.existsSync)((0, node_path_1.join)(dir, file.replace(/\.json$/, '.receipt.json')));
|
|
263
|
+
const verification = envelope?.verification;
|
|
264
|
+
const receiptVerified = trustLevel === 'backend_signed_verified'
|
|
265
|
+
|| verification?.trustLevel === 'backend_signed_verified';
|
|
266
|
+
out.sessionId = sessionId;
|
|
267
|
+
out.trustLevel = trustLevel;
|
|
268
|
+
out.receiptPresent = receiptPresent;
|
|
269
|
+
out.receiptVerified = Boolean(receiptVerified);
|
|
270
|
+
newestMtime = mtime;
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
// skip corrupt envelope
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
// best-effort
|
|
279
|
+
}
|
|
280
|
+
return out;
|
|
281
|
+
}
|
|
282
|
+
function latestAdmissionSessionId(repoRoot) {
|
|
283
|
+
const dir = (0, admission_artifact_1.admissionDir)(repoRoot);
|
|
284
|
+
if (!(0, node_fs_1.existsSync)(dir))
|
|
285
|
+
return null;
|
|
286
|
+
try {
|
|
287
|
+
const candidates = (0, node_fs_1.readdirSync)(dir)
|
|
288
|
+
.filter((f) => f.endsWith('.json'))
|
|
289
|
+
.map((f) => {
|
|
290
|
+
try {
|
|
291
|
+
return { sessionId: f.replace(/\.json$/, ''), mtime: (0, node_fs_1.statSync)((0, node_path_1.join)(dir, f)).mtimeMs };
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
.filter((c) => c !== null)
|
|
298
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
299
|
+
return candidates[0]?.sessionId ?? null;
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function eventTimeMs(event) {
|
|
306
|
+
const parsed = Date.parse(event.ts);
|
|
307
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Deterministic neighbor-containment signal: a session approved exactly one
|
|
311
|
+
* path yet still blocked a different path. That proves the approval did not
|
|
312
|
+
* silently widen scope. Source-free — only paths and verdicts are read.
|
|
313
|
+
*/
|
|
314
|
+
function deriveNeighborContainment(session) {
|
|
315
|
+
const approvedPaths = session.contract.approvedPaths ?? [];
|
|
316
|
+
if (approvedPaths.length === 0)
|
|
317
|
+
return { contained: false, approvedPath: null, neighborPath: null };
|
|
318
|
+
const approvedSet = new Set(approvedPaths);
|
|
319
|
+
const blocks = session.events
|
|
320
|
+
.filter((e) => e.type === 'check_block' && e.filePath && !approvedSet.has(e.filePath))
|
|
321
|
+
.sort((a, b) => eventTimeMs(a) - eventTimeMs(b));
|
|
322
|
+
const neighbor = blocks[blocks.length - 1]?.filePath ?? null;
|
|
323
|
+
return {
|
|
324
|
+
contained: neighbor !== null,
|
|
325
|
+
approvedPath: approvedPaths[0] ?? null,
|
|
326
|
+
neighborPath: neighbor,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function gatherRepoBrain(repoRoot) {
|
|
330
|
+
const empty = {
|
|
331
|
+
status: 'not_evaluated',
|
|
332
|
+
recoveryCommand: 'neurcode brain index',
|
|
333
|
+
filesIndexed: null,
|
|
334
|
+
sensitiveSurfaces: [],
|
|
335
|
+
ownerBoundaries: [],
|
|
336
|
+
reuseAdvisories: [],
|
|
337
|
+
highFanOutSymbols: [],
|
|
338
|
+
reviewFirst: [],
|
|
339
|
+
};
|
|
340
|
+
let artifact;
|
|
341
|
+
try {
|
|
342
|
+
artifact = (0, local_repo_brain_1.readLocalRepoBrain)(repoRoot);
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
return empty;
|
|
346
|
+
}
|
|
347
|
+
if (!artifact)
|
|
348
|
+
return empty;
|
|
349
|
+
const sensitiveSurfaces = Array.from(new Set((artifact.hotspots || [])
|
|
350
|
+
.filter((h) => (h.sensitiveKinds || []).length > 0)
|
|
351
|
+
.map((h) => h.file))).slice(0, 8);
|
|
352
|
+
const ownerBoundaries = (artifact.ownerBoundaries || [])
|
|
353
|
+
.slice(0, 8)
|
|
354
|
+
.map((b) => ({ pattern: b.pattern, owners: b.owners }));
|
|
355
|
+
const reuseAdvisories = (artifact.reuseFindings || [])
|
|
356
|
+
.slice(0, 8)
|
|
357
|
+
.map((r) => ({
|
|
358
|
+
symbolName: r.symbolName,
|
|
359
|
+
files: r.files.slice(0, 4),
|
|
360
|
+
severity: r.severity,
|
|
361
|
+
confidence: r.confidence,
|
|
362
|
+
}));
|
|
363
|
+
const highFanOutSymbols = [...(artifact.hotspots || [])]
|
|
364
|
+
.sort((a, b) => b.importFanIn - a.importFanIn || b.symbolCount - a.symbolCount)
|
|
365
|
+
.slice(0, 6)
|
|
366
|
+
.map((h) => ({ file: h.file, importFanIn: h.importFanIn, symbolCount: h.symbolCount }));
|
|
367
|
+
const reviewFirst = [...(artifact.hotspots || [])]
|
|
368
|
+
.sort((a, b) => b.score - a.score)
|
|
369
|
+
.slice(0, 6)
|
|
370
|
+
.map((h) => h.file);
|
|
371
|
+
return {
|
|
372
|
+
status: 'measured',
|
|
373
|
+
recoveryCommand: 'neurcode brain index',
|
|
374
|
+
filesIndexed: artifact.summary?.filesIndexed ?? null,
|
|
375
|
+
sensitiveSurfaces,
|
|
376
|
+
ownerBoundaries,
|
|
377
|
+
reuseAdvisories,
|
|
378
|
+
highFanOutSymbols,
|
|
379
|
+
reviewFirst,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Read-only inspection of the repo's local governance state. Every probe is
|
|
384
|
+
* defensive — a missing/corrupt artifact degrades to "not done", never throws.
|
|
385
|
+
*/
|
|
386
|
+
function gatherGuidedEvalContext(repoRoot, options = {}) {
|
|
387
|
+
const agent = options.agent ?? 'claude';
|
|
388
|
+
const enforcement = enforcementForAgent(agent);
|
|
389
|
+
const generatedAt = options.generatedAt ?? new Date().toISOString();
|
|
390
|
+
const runtimeState = (0, runtime_state_1.detectRuntimeState)(repoRoot);
|
|
391
|
+
const brainIndexed = (() => {
|
|
392
|
+
try {
|
|
393
|
+
if ((0, brain_cache_1.loadBrainCacheManifest)(repoRoot))
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
// ignore
|
|
398
|
+
}
|
|
399
|
+
try {
|
|
400
|
+
return (0, local_repo_brain_1.readLocalRepoBrain)(repoRoot) !== null;
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
})();
|
|
406
|
+
const runtimeConnection = (() => {
|
|
407
|
+
try {
|
|
408
|
+
return (0, runtime_connection_1.loadRuntimeConnection)(repoRoot);
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
})();
|
|
414
|
+
const runtimeActive = runtimeState.hasNeurcodeDir && (Boolean(runtimeConnection) || runtimeState.hasIntentPack);
|
|
415
|
+
const activeSession = (() => {
|
|
416
|
+
try {
|
|
417
|
+
return (0, governance_runtime_1.loadActiveSession)(repoRoot);
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
})();
|
|
423
|
+
const sessions = (() => {
|
|
424
|
+
try {
|
|
425
|
+
return (0, runtime_evidence_1.listRuntimeSessions)(repoRoot);
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
return [];
|
|
429
|
+
}
|
|
430
|
+
})();
|
|
431
|
+
let blockCount = 0;
|
|
432
|
+
let approvalCount = 0;
|
|
433
|
+
let lastBlockPath = null;
|
|
434
|
+
let lastBlockTime = -1;
|
|
435
|
+
let exactApprovalPath = null;
|
|
436
|
+
let exactApprovalOnly = false;
|
|
437
|
+
let neighborContained = false;
|
|
438
|
+
let neighborBlockedPath = null;
|
|
439
|
+
for (const record of sessions) {
|
|
440
|
+
blockCount += record.blockCount;
|
|
441
|
+
approvalCount += record.approvalCount;
|
|
442
|
+
for (const event of record.session.events) {
|
|
443
|
+
if (event.type === 'check_block' && event.filePath) {
|
|
444
|
+
const t = eventTimeMs(event);
|
|
445
|
+
if (t >= lastBlockTime) {
|
|
446
|
+
lastBlockTime = t;
|
|
447
|
+
lastBlockPath = event.filePath;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const approvedPaths = record.session.contract.approvedPaths ?? [];
|
|
452
|
+
if (approvedPaths.length > 0 && !exactApprovalPath) {
|
|
453
|
+
exactApprovalPath = approvedPaths[0];
|
|
454
|
+
exactApprovalOnly = approvedPaths.length === 1;
|
|
455
|
+
}
|
|
456
|
+
if (!neighborContained) {
|
|
457
|
+
const neighbor = deriveNeighborContainment(record.session);
|
|
458
|
+
if (neighbor.contained) {
|
|
459
|
+
neighborContained = true;
|
|
460
|
+
neighborBlockedPath = neighbor.neighborPath;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const acr = latestAiChangeRecord(repoRoot);
|
|
465
|
+
const admissionSessionId = latestAdmissionSessionId(repoRoot);
|
|
466
|
+
const facts = {
|
|
467
|
+
cliInstalled: true,
|
|
468
|
+
cliVersion: safeCliVersion(),
|
|
469
|
+
isGitRepo: runtimeState.isGitRepo,
|
|
470
|
+
hasHeadCommit: runtimeState.hasHeadCommit,
|
|
471
|
+
brainIndexed,
|
|
472
|
+
runtimeActive,
|
|
473
|
+
activeSessionId: activeSession?.sessionId ?? null,
|
|
474
|
+
sessionCount: sessions.length,
|
|
475
|
+
blockCount,
|
|
476
|
+
approvalCount,
|
|
477
|
+
lastBlockPath,
|
|
478
|
+
exactApprovalPath,
|
|
479
|
+
exactApprovalOnly,
|
|
480
|
+
neighborContained,
|
|
481
|
+
neighborBlockedPath,
|
|
482
|
+
aiChangeRecordSessionId: acr.sessionId ?? admissionSessionId,
|
|
483
|
+
aiChangeRecordTrustLevel: acr.trustLevel,
|
|
484
|
+
receiptPresent: acr.receiptPresent,
|
|
485
|
+
receiptVerified: acr.receiptVerified,
|
|
486
|
+
actionWorkflowConfigured: isActionWorkflowConfigured(repoRoot),
|
|
487
|
+
repoBrain: gatherRepoBrain(repoRoot),
|
|
488
|
+
};
|
|
489
|
+
return {
|
|
490
|
+
schemaVersion: exports.GUIDED_EVAL_SCHEMA_VERSION,
|
|
491
|
+
generatedAt,
|
|
492
|
+
repoRoot,
|
|
493
|
+
repoRootHash: hashRepoIdentity(repoRoot),
|
|
494
|
+
agent,
|
|
495
|
+
enforcement,
|
|
496
|
+
mode: options.mode ?? 'real',
|
|
497
|
+
facts,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
// ── Agent-aware next-command recipes ──────────────────────────────────────────
|
|
501
|
+
function cliAgentToken(agent) {
|
|
502
|
+
if (agent === 'action')
|
|
503
|
+
return 'claude';
|
|
504
|
+
return agent;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* The single command an evaluator should run to make progress on a step, for
|
|
508
|
+
* the selected agent. Source-free, copy-pasteable, no destructive edits — in
|
|
509
|
+
* real-repo mode the "trigger a block" guidance is described, not auto-run.
|
|
510
|
+
*/
|
|
511
|
+
function stepCommand(stepId, agent, mode) {
|
|
512
|
+
const token = cliAgentToken(agent);
|
|
513
|
+
const isHardHook = agent === 'claude';
|
|
514
|
+
const isAction = agent === 'action';
|
|
515
|
+
const fixtureDir = mode === 'fixture' ? ' --dir .neurcode/eval/fixture' : '';
|
|
516
|
+
const activationDir = mode === 'fixture' ? '--dir .neurcode/eval/fixture' : '--dir .';
|
|
517
|
+
switch (stepId) {
|
|
518
|
+
case 'cli_installed':
|
|
519
|
+
return 'npx -y @neurcode-ai/cli@latest --version';
|
|
520
|
+
case 'repo_detected':
|
|
521
|
+
return mode === 'fixture'
|
|
522
|
+
? 'neurcode eval start --fixture'
|
|
523
|
+
: 'cd <your-repo> && git status';
|
|
524
|
+
case 'repo_brain_indexed':
|
|
525
|
+
return `neurcode brain index${fixtureDir}`;
|
|
526
|
+
case 'runtime_active':
|
|
527
|
+
return isAction
|
|
528
|
+
? '# Add .github/workflows/neurcode.yml (see Action report step)'
|
|
529
|
+
: `neurcode activate ${token} ${activationDir}`;
|
|
530
|
+
case 'session_live':
|
|
531
|
+
return isHardHook
|
|
532
|
+
? '# Open Claude Code in the repo and give one bounded goal.\n# Hooks run session start/check/finish automatically.'
|
|
533
|
+
: `neurcode agent guard start ${token} --goal "<bounded task>" --plan "<source-free plan>"`;
|
|
534
|
+
case 'block_observed':
|
|
535
|
+
return isHardHook
|
|
536
|
+
? '# In Claude Code, ask it to edit a CODEOWNERS-protected file.\n# The write is denied before landing; check Runtime Control Plane.'
|
|
537
|
+
: `neurcode agent check src/billing/charge.py --agent ${token}`;
|
|
538
|
+
case 'exact_approval_observed':
|
|
539
|
+
return '# Runtime Control Plane → Approval Ledger → Approve the exact path\n# Or: neurcode approve <approval-id>';
|
|
540
|
+
case 'neighbor_contained':
|
|
541
|
+
return isHardHook
|
|
542
|
+
? '# Ask the agent to also edit src/billing/refund.py — it stays blocked.'
|
|
543
|
+
: `neurcode agent check src/billing/refund.py --agent ${token}`;
|
|
544
|
+
case 'ai_change_record_exported':
|
|
545
|
+
return 'neurcode session export-admission <session-id> --explain';
|
|
546
|
+
case 'backend_receipt_verified':
|
|
547
|
+
return 'neurcode session verify-record \\\n --record .neurcode-ai-record/<session-id>.json';
|
|
548
|
+
case 'action_report_available':
|
|
549
|
+
return '# .github/workflows/neurcode.yml\nuses: sujit-jaunjal/neurcode-actions@v0.3.0-rc.5';
|
|
550
|
+
default:
|
|
551
|
+
return 'neurcode eval status';
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function deriveStepStatus(def, ctx) {
|
|
555
|
+
const f = ctx.facts;
|
|
556
|
+
const applies = def.appliesTo.includes(ctx.enforcement);
|
|
557
|
+
if (!applies) {
|
|
558
|
+
return {
|
|
559
|
+
status: 'not_applicable',
|
|
560
|
+
fact: ctx.enforcement === 'post_pr'
|
|
561
|
+
? 'Not applicable for the post-PR Action posture (no live pre-write session).'
|
|
562
|
+
: 'Not applicable for this agent posture.',
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
switch (def.id) {
|
|
566
|
+
case 'cli_installed':
|
|
567
|
+
return f.cliInstalled
|
|
568
|
+
? { status: 'done', fact: `Neurcode CLI ${f.cliVersion ?? ''} resolved.`.trim() }
|
|
569
|
+
: { status: 'pending', fact: 'CLI not detected on PATH.' };
|
|
570
|
+
case 'repo_detected':
|
|
571
|
+
if (f.isGitRepo && f.hasHeadCommit)
|
|
572
|
+
return { status: 'done', fact: 'Git repository with a HEAD commit detected.' };
|
|
573
|
+
if (f.isGitRepo)
|
|
574
|
+
return { status: 'attention', fact: 'Git repo found but no HEAD commit yet — commit a baseline.' };
|
|
575
|
+
return { status: 'pending', fact: 'No git repository detected in this directory.' };
|
|
576
|
+
case 'repo_brain_indexed':
|
|
577
|
+
return f.brainIndexed
|
|
578
|
+
? {
|
|
579
|
+
status: 'done',
|
|
580
|
+
fact: f.repoBrain.filesIndexed != null
|
|
581
|
+
? `Repo brain indexed (${f.repoBrain.filesIndexed} files).`
|
|
582
|
+
: 'Repo brain index present.',
|
|
583
|
+
}
|
|
584
|
+
: { status: 'pending', fact: 'Repo brain not indexed yet.' };
|
|
585
|
+
case 'runtime_active':
|
|
586
|
+
return f.runtimeActive
|
|
587
|
+
? { status: 'done', fact: 'Local runtime activated and paired for this repo.' }
|
|
588
|
+
: { status: 'pending', fact: 'Runtime not activated for this repo.' };
|
|
589
|
+
case 'session_live':
|
|
590
|
+
if (f.activeSessionId)
|
|
591
|
+
return { status: 'done', fact: `Governed session live: ${f.activeSessionId}.` };
|
|
592
|
+
if (f.sessionCount > 0)
|
|
593
|
+
return { status: 'done', fact: `${f.sessionCount} governed session(s) recorded.` };
|
|
594
|
+
return { status: 'pending', fact: 'No governed session has run yet.' };
|
|
595
|
+
case 'block_observed':
|
|
596
|
+
return f.blockCount > 0
|
|
597
|
+
? {
|
|
598
|
+
status: 'done',
|
|
599
|
+
fact: `Boundary block observed${f.lastBlockPath ? ` on ${f.lastBlockPath}` : ''} (${f.blockCount} total).`,
|
|
600
|
+
}
|
|
601
|
+
: { status: 'pending', fact: 'No boundary block observed yet.' };
|
|
602
|
+
case 'exact_approval_observed':
|
|
603
|
+
if (f.approvalCount > 0 && f.exactApprovalPath) {
|
|
604
|
+
return {
|
|
605
|
+
status: f.exactApprovalOnly ? 'done' : 'attention',
|
|
606
|
+
fact: `Approved exactly: ${f.exactApprovalPath}${f.exactApprovalOnly ? '' : ' (more than one path approved — review scope)'}.`,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
return { status: 'pending', fact: 'No exact-path approval observed yet.' };
|
|
610
|
+
case 'neighbor_contained':
|
|
611
|
+
if (f.neighborContained) {
|
|
612
|
+
return {
|
|
613
|
+
status: 'done',
|
|
614
|
+
fact: `Neighbor stayed blocked${f.neighborBlockedPath ? `: ${f.neighborBlockedPath}` : ''} after approval.`,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
if (f.approvalCount > 0) {
|
|
618
|
+
return { status: 'pending', fact: 'Approval recorded; try editing an adjacent file to confirm it stays blocked.' };
|
|
619
|
+
}
|
|
620
|
+
return { status: 'pending', fact: 'Run the approval step first, then test a neighbor file.' };
|
|
621
|
+
case 'ai_change_record_exported':
|
|
622
|
+
return f.aiChangeRecordSessionId
|
|
623
|
+
? { status: 'done', fact: `Source-free record exported for ${f.aiChangeRecordSessionId}.` }
|
|
624
|
+
: { status: 'pending', fact: 'No AI Change Record / admission record exported yet.' };
|
|
625
|
+
case 'backend_receipt_verified':
|
|
626
|
+
if (f.receiptVerified) {
|
|
627
|
+
return { status: 'done', fact: `Receipt verified (trust: ${f.aiChangeRecordTrustLevel ?? 'backend_signed_verified'}).` };
|
|
628
|
+
}
|
|
629
|
+
if (f.receiptPresent)
|
|
630
|
+
return { status: 'attention', fact: 'Receipt present but not verified — run verify-record.' };
|
|
631
|
+
return { status: 'pending', fact: 'No signed receipt yet. Export with --signed against the backend.' };
|
|
632
|
+
case 'action_report_available':
|
|
633
|
+
if (f.actionWorkflowConfigured)
|
|
634
|
+
return { status: 'done', fact: 'Neurcode Action workflow is configured.' };
|
|
635
|
+
if (ctx.enforcement === 'post_pr')
|
|
636
|
+
return { status: 'pending', fact: 'Add the Neurcode Action workflow to render PR reports.' };
|
|
637
|
+
return { status: 'not_applicable', fact: 'Optional: add the Action for post-PR routing (not required for this agent).' };
|
|
638
|
+
default:
|
|
639
|
+
return { status: 'pending', fact: '' };
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
function buildGuidedEvalState(ctx) {
|
|
643
|
+
const steps = exports.GUIDED_EVAL_STEPS.map((def) => {
|
|
644
|
+
const { status, fact } = deriveStepStatus(def, ctx);
|
|
645
|
+
return {
|
|
646
|
+
id: def.id,
|
|
647
|
+
title: def.title,
|
|
648
|
+
truthTier: def.truthTier,
|
|
649
|
+
reportKey: def.reportKey,
|
|
650
|
+
status,
|
|
651
|
+
fact,
|
|
652
|
+
command: stepCommand(def.id, ctx.agent, ctx.mode),
|
|
653
|
+
optional: def.optional === true,
|
|
654
|
+
};
|
|
655
|
+
});
|
|
656
|
+
const applicableSteps = steps.filter((s) => s.status !== 'not_applicable');
|
|
657
|
+
const done = applicableSteps.filter((s) => s.status === 'done').length;
|
|
658
|
+
const pending = applicableSteps.filter((s) => s.status === 'pending').length;
|
|
659
|
+
const attention = applicableSteps.filter((s) => s.status === 'attention').length;
|
|
660
|
+
const notApplicable = steps.length - applicableSteps.length;
|
|
661
|
+
// A step that is optional and pending does not block completion.
|
|
662
|
+
const blocking = applicableSteps.filter((s) => !s.optional && (s.status === 'pending' || s.status === 'attention'));
|
|
663
|
+
const complete = blocking.length === 0 && applicableSteps.length > 0;
|
|
664
|
+
const percent = applicableSteps.length === 0 ? 0 : Math.round((done / applicableSteps.length) * 100);
|
|
665
|
+
const next = steps.find((s) => !s.optional && (s.status === 'pending' || s.status === 'attention'))
|
|
666
|
+
?? steps.find((s) => s.optional && s.status === 'pending')
|
|
667
|
+
?? null;
|
|
668
|
+
const nextAction = next
|
|
669
|
+
? {
|
|
670
|
+
stepId: next.id,
|
|
671
|
+
title: next.title,
|
|
672
|
+
command: next.command,
|
|
673
|
+
why: next.fact,
|
|
674
|
+
}
|
|
675
|
+
: null;
|
|
676
|
+
return {
|
|
677
|
+
schemaVersion: exports.GUIDED_EVAL_SCHEMA_VERSION,
|
|
678
|
+
generatedAt: ctx.generatedAt,
|
|
679
|
+
repoRootHash: ctx.repoRootHash,
|
|
680
|
+
agent: ctx.agent,
|
|
681
|
+
enforcement: ctx.enforcement,
|
|
682
|
+
enforcementLabel: enforcementLabel(ctx.enforcement),
|
|
683
|
+
mode: ctx.mode,
|
|
684
|
+
steps,
|
|
685
|
+
summary: { applicable: applicableSteps.length, done, pending, attention, notApplicable, complete, percent },
|
|
686
|
+
nextAction,
|
|
687
|
+
sourceFree: true,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
// ── Source-free guarantee ─────────────────────────────────────────────────────
|
|
691
|
+
// Anchored patterns only, so markdown tables (`|---|`) never false-positive.
|
|
692
|
+
const FORBIDDEN_SOURCE_PATTERNS = [
|
|
693
|
+
'diff --git',
|
|
694
|
+
'@@ -',
|
|
695
|
+
'\n+++ b/',
|
|
696
|
+
'\n--- a/',
|
|
697
|
+
'-----BEGIN',
|
|
698
|
+
'PRIVATE KEY',
|
|
699
|
+
'sk-ant-',
|
|
700
|
+
'sk-proj-',
|
|
701
|
+
/\bghp_[A-Za-z0-9]{16,}\b/,
|
|
702
|
+
/\bgho_[A-Za-z0-9]{16,}\b/,
|
|
703
|
+
/\bAKIA[0-9A-Z]{16}\b/,
|
|
704
|
+
];
|
|
705
|
+
function findSourceLeaks(text) {
|
|
706
|
+
const leaks = [];
|
|
707
|
+
for (const pattern of FORBIDDEN_SOURCE_PATTERNS) {
|
|
708
|
+
if (typeof pattern === 'string') {
|
|
709
|
+
if (text.includes(pattern))
|
|
710
|
+
leaks.push(pattern);
|
|
711
|
+
}
|
|
712
|
+
else if (pattern.test(text)) {
|
|
713
|
+
leaks.push(pattern.source);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return leaks;
|
|
717
|
+
}
|
|
718
|
+
/** Throw if a would-be artifact contains source/diff/secret shapes. */
|
|
719
|
+
function assertGuidedEvalSourceFree(value, label = 'guided-eval artifact') {
|
|
720
|
+
const text = typeof value === 'string' ? value : JSON.stringify(value);
|
|
721
|
+
const leaks = findSourceLeaks(text);
|
|
722
|
+
if (leaks.length > 0) {
|
|
723
|
+
throw new Error(`${label} failed source-free scan: ${leaks.join(', ')}`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
// ── Shareable evaluation report (P3) ──────────────────────────────────────────
|
|
727
|
+
exports.GUIDED_EVAL_REPORT_SCHEMA_VERSION = 'neurcode.guided-eval-report.v1';
|
|
728
|
+
const WHAT_THIS_DOES_NOT_PROVE = [
|
|
729
|
+
'It does not prove the source code is correct, secure, or free of vulnerabilities.',
|
|
730
|
+
'A verified receipt confirms issuance under the configured signing key and integrity — not source correctness.',
|
|
731
|
+
'Advisory reuse/architecture findings can be false positives and must be read as advisory.',
|
|
732
|
+
'"Not evaluated" steps were genuinely not measured in this run.',
|
|
733
|
+
];
|
|
734
|
+
const REPORT_EXCLUDES = ['source code', 'diff hunks', 'patch bodies', 'raw prompts', 'secrets', 'private file contents'];
|
|
735
|
+
/**
|
|
736
|
+
* Build the shareable, source-free evaluation report from a derived state +
|
|
737
|
+
* gathered context. The returned object is asserted source-free by the caller
|
|
738
|
+
* before it is written or surfaced.
|
|
739
|
+
*/
|
|
740
|
+
function buildGuidedEvalReport(state, ctx) {
|
|
741
|
+
const f = ctx.facts;
|
|
742
|
+
return {
|
|
743
|
+
schemaVersion: exports.GUIDED_EVAL_REPORT_SCHEMA_VERSION,
|
|
744
|
+
generatedAt: ctx.generatedAt,
|
|
745
|
+
agent: state.agent,
|
|
746
|
+
enforcement: state.enforcement,
|
|
747
|
+
enforcementLabel: state.enforcementLabel,
|
|
748
|
+
mode: state.mode,
|
|
749
|
+
repo: { rootHash: ctx.repoRootHash },
|
|
750
|
+
result: {
|
|
751
|
+
complete: state.summary.complete,
|
|
752
|
+
percent: state.summary.percent,
|
|
753
|
+
done: state.summary.done,
|
|
754
|
+
applicable: state.summary.applicable,
|
|
755
|
+
},
|
|
756
|
+
steps: state.steps.map((s) => ({
|
|
757
|
+
id: s.id,
|
|
758
|
+
title: s.title,
|
|
759
|
+
reportKey: s.reportKey,
|
|
760
|
+
truthTier: s.truthTier,
|
|
761
|
+
truthTierLabel: exports.GUIDED_EVAL_TRUTH_TIERS[s.truthTier].label,
|
|
762
|
+
status: s.status,
|
|
763
|
+
fact: s.fact,
|
|
764
|
+
})),
|
|
765
|
+
facts: {
|
|
766
|
+
boundary: { lastBlockPath: f.lastBlockPath, blockCount: f.blockCount },
|
|
767
|
+
approval: { exactApprovalPath: f.exactApprovalPath, exactApprovalOnly: f.exactApprovalOnly, approvalCount: f.approvalCount },
|
|
768
|
+
neighbor: { contained: f.neighborContained, neighborBlockedPath: f.neighborBlockedPath },
|
|
769
|
+
aiChangeRecord: { sessionId: f.aiChangeRecordSessionId, trustLevel: f.aiChangeRecordTrustLevel },
|
|
770
|
+
backendReceipt: { present: f.receiptPresent, verified: f.receiptVerified },
|
|
771
|
+
actionReport: { configured: f.actionWorkflowConfigured },
|
|
772
|
+
},
|
|
773
|
+
repoBrain: f.repoBrain,
|
|
774
|
+
truthTaxonomy: Object.fromEntries(Object.keys(exports.GUIDED_EVAL_TRUTH_TIERS).map((k) => [k, exports.GUIDED_EVAL_TRUTH_TIERS[k].label])),
|
|
775
|
+
whatThisDoesNotProve: WHAT_THIS_DOES_NOT_PROVE,
|
|
776
|
+
privacy: { sourceFree: true, excludes: REPORT_EXCLUDES },
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
const STATUS_GLYPH = {
|
|
780
|
+
done: '✓',
|
|
781
|
+
pending: '·',
|
|
782
|
+
attention: '!',
|
|
783
|
+
not_applicable: '–',
|
|
784
|
+
};
|
|
785
|
+
/** Render the report as a source-free shareable markdown artifact. */
|
|
786
|
+
function renderGuidedEvalReportMarkdown(report) {
|
|
787
|
+
const lines = [];
|
|
788
|
+
lines.push('# Guided Enterprise Evaluation — Report');
|
|
789
|
+
lines.push('');
|
|
790
|
+
lines.push(`Generated: ${report.generatedAt}`);
|
|
791
|
+
lines.push(`Agent: ${report.agent} · Enforcement: ${report.enforcementLabel}`);
|
|
792
|
+
lines.push(`Mode: ${report.mode} · Repo identity: ${report.repo.rootHash} (path hash — no source)`);
|
|
793
|
+
lines.push(`Result: ${report.result.done}/${report.result.applicable} applicable steps done (${report.result.percent}%)${report.result.complete ? ' — complete' : ''}`);
|
|
794
|
+
lines.push('');
|
|
795
|
+
lines.push('## Steps');
|
|
796
|
+
lines.push('');
|
|
797
|
+
lines.push('| | Step | Tier | Status | Fact |');
|
|
798
|
+
lines.push('|---|---|---|---|---|');
|
|
799
|
+
for (const s of report.steps) {
|
|
800
|
+
lines.push(`| ${STATUS_GLYPH[s.status]} | ${s.title} | ${s.truthTierLabel} | ${s.status} | ${s.fact} |`);
|
|
801
|
+
}
|
|
802
|
+
lines.push('');
|
|
803
|
+
lines.push('## Boundary, approval, and containment facts');
|
|
804
|
+
lines.push('');
|
|
805
|
+
lines.push(`- Last block: ${report.facts.boundary.lastBlockPath ?? 'none'} (${report.facts.boundary.blockCount} total)`);
|
|
806
|
+
lines.push(`- Exact approval: ${report.facts.approval.exactApprovalPath ?? 'none'} (exact-only: ${report.facts.approval.exactApprovalOnly ? 'yes' : 'no'})`);
|
|
807
|
+
lines.push(`- Neighbor contained: ${report.facts.neighbor.contained ? 'yes' : 'no'}${report.facts.neighbor.neighborBlockedPath ? ` (${report.facts.neighbor.neighborBlockedPath})` : ''}`);
|
|
808
|
+
lines.push(`- AI Change Record: ${report.facts.aiChangeRecord.sessionId ?? 'none'} (trust: ${report.facts.aiChangeRecord.trustLevel ?? 'n/a'})`);
|
|
809
|
+
lines.push(`- Backend receipt: present ${report.facts.backendReceipt.present ? 'yes' : 'no'}, verified ${report.facts.backendReceipt.verified ? 'yes' : 'no'}`);
|
|
810
|
+
lines.push(`- Action report: ${report.facts.actionReport.configured ? 'configured' : 'not configured'}`);
|
|
811
|
+
lines.push('');
|
|
812
|
+
lines.push('## Repo brain findings');
|
|
813
|
+
lines.push('');
|
|
814
|
+
if (report.repoBrain.status === 'measured') {
|
|
815
|
+
lines.push(`- Files indexed: ${report.repoBrain.filesIndexed ?? 'n/a'}`);
|
|
816
|
+
lines.push(`- Sensitive surfaces: ${report.repoBrain.sensitiveSurfaces.join(', ') || 'none'}`);
|
|
817
|
+
lines.push(`- Owner boundaries: ${report.repoBrain.ownerBoundaries.map((b) => `${b.pattern} → ${b.owners.join('/')}`).join('; ') || 'none'}`);
|
|
818
|
+
lines.push(`- High fan-out symbols: ${report.repoBrain.highFanOutSymbols.map((h) => `${h.file} (${h.importFanIn} callers)`).join('; ') || 'none'}`);
|
|
819
|
+
lines.push(`- Reuse advisories: ${report.repoBrain.reuseAdvisories.map((r) => `${r.symbolName ?? 'symbol'} [${r.severity}]`).join('; ') || 'none'}`);
|
|
820
|
+
lines.push(`- What to review first: ${report.repoBrain.reviewFirst.join(', ') || 'none'}`);
|
|
821
|
+
}
|
|
822
|
+
else {
|
|
823
|
+
lines.push(`- Not evaluated in this run — run \`${report.repoBrain.recoveryCommand}\` on the target repo to populate.`);
|
|
824
|
+
}
|
|
825
|
+
lines.push('');
|
|
826
|
+
lines.push('## What this does NOT prove');
|
|
827
|
+
lines.push('');
|
|
828
|
+
for (const item of report.whatThisDoesNotProve)
|
|
829
|
+
lines.push(`- ${item}`);
|
|
830
|
+
lines.push('');
|
|
831
|
+
lines.push(`_Source-free: paths, owners, symbol names, hashes, verdicts, and tiers only. Excludes: ${report.privacy.excludes.join(', ')}._`);
|
|
832
|
+
lines.push('');
|
|
833
|
+
return lines.join('\n');
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Create a controlled, source-free demo fixture under `.neurcode/eval/fixture/`
|
|
837
|
+
* so an evaluator can safely run the "trigger a block / approve / neighbor"
|
|
838
|
+
* steps WITHOUT touching their real source. The fixture is its own git repo
|
|
839
|
+
* (the parent `.neurcode/*` is gitignored), with a CODEOWNERS boundary and
|
|
840
|
+
* placeholder files that carry no secrets and no real logic.
|
|
841
|
+
*/
|
|
842
|
+
function scaffoldEvalFixture(repoRoot) {
|
|
843
|
+
const dir = (0, node_path_1.join)(repoRoot, '.neurcode', 'eval', 'fixture');
|
|
844
|
+
const alreadyExisted = (0, node_fs_1.existsSync)((0, node_path_1.join)(dir, 'CODEOWNERS'));
|
|
845
|
+
const files = [
|
|
846
|
+
[
|
|
847
|
+
'README.md',
|
|
848
|
+
'# Neurcode evaluation fixture\n\nA safe, throwaway repo for the guided enterprise evaluation.\nEdits here never touch your real source. Run the governed steps against this directory.\n',
|
|
849
|
+
],
|
|
850
|
+
[
|
|
851
|
+
'CODEOWNERS',
|
|
852
|
+
'# Boundary fixtures for the guided evaluation.\nsrc/billing/ @payments-team\nconfig/secrets/ @security-platform\n',
|
|
853
|
+
],
|
|
854
|
+
['src/tasks/export_task.txt', 'fixture: a non-sensitive task file you may freely edit\n'],
|
|
855
|
+
['src/billing/charge.txt', 'fixture: billing boundary placeholder (owned by @payments-team)\n'],
|
|
856
|
+
['src/billing/refund.txt', 'fixture: neighbor of charge, also a billing boundary\n'],
|
|
857
|
+
];
|
|
858
|
+
for (const [rel, content] of files) {
|
|
859
|
+
const p = (0, node_path_1.join)(dir, rel);
|
|
860
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(p, '..'), { recursive: true });
|
|
861
|
+
if (!(0, node_fs_1.existsSync)(p))
|
|
862
|
+
(0, node_fs_1.writeFileSync)(p, content, 'utf8');
|
|
863
|
+
}
|
|
864
|
+
// Initialise as its own git repo so brain/activate/verify have a HEAD.
|
|
865
|
+
if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(dir, '.git'))) {
|
|
866
|
+
try {
|
|
867
|
+
(0, node_child_process_1.execSync)('git init -q && git add -A && git -c user.email=eval@neurcode.local -c user.name=neurcode-eval commit -q -m "eval fixture baseline"', { cwd: dir, stdio: 'ignore' });
|
|
868
|
+
}
|
|
869
|
+
catch {
|
|
870
|
+
// git may be unavailable; the fixture files still exist for inspection.
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
return {
|
|
874
|
+
dir,
|
|
875
|
+
relativeDir: '.neurcode/eval/fixture',
|
|
876
|
+
created: !alreadyExisted,
|
|
877
|
+
files: files.map(([rel]) => rel),
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
//# sourceMappingURL=guided-eval.js.map
|