@neurcode-ai/cli 0.20.6 → 0.20.8
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/dist/api-client.d.ts +37 -4
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js.map +1 -1
- package/dist/commands/ops.d.ts +38 -0
- package/dist/commands/ops.d.ts.map +1 -1
- package/dist/commands/ops.js +120 -3
- package/dist/commands/ops.js.map +1 -1
- package/dist/commands/pilot.d.ts.map +1 -1
- package/dist/commands/pilot.js +81 -0
- package/dist/commands/pilot.js.map +1 -1
- package/dist/runtime-build.json +4 -4
- package/dist/utils/pilot-evidence-io.d.ts +52 -0
- package/dist/utils/pilot-evidence-io.d.ts.map +1 -0
- package/dist/utils/pilot-evidence-io.js +251 -0
- package/dist/utils/pilot-evidence-io.js.map +1 -0
- package/dist/utils/pilot-evidence-pack.d.ts +280 -0
- package/dist/utils/pilot-evidence-pack.d.ts.map +1 -0
- package/dist/utils/pilot-evidence-pack.js +630 -0
- package/dist/utils/pilot-evidence-pack.js.map +1 -0
- package/package.json +8 -7
- package/LICENSE +0 -201
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pilot Evidence Pack — pure builders (Iteration 10).
|
|
4
|
+
*
|
|
5
|
+
* After a pilot, a founder needs to hand engineering managers, principal
|
|
6
|
+
* engineers, security reviewers, and procurement/IT a single, shareable packet
|
|
7
|
+
* that explains what the Neurcode runtime control plane actually did — without a
|
|
8
|
+
* live walkthrough and without leaking a single line of source.
|
|
9
|
+
*
|
|
10
|
+
* This module is the source-free, deterministic core. It takes already-parsed,
|
|
11
|
+
* source-free inputs (extracted by the thin `neurcode pilot export` command from
|
|
12
|
+
* `.neurcode/sessions/*.change-record.json`, `.neurcode/admission/*.json`, and
|
|
13
|
+
* `.neurcode/pilot-metrics.json`) and turns them into:
|
|
14
|
+
*
|
|
15
|
+
* 1. {@link buildPilotEvidencePack} — a machine-readable manifest
|
|
16
|
+
* (`neurcode.pilot-evidence-pack.v1`).
|
|
17
|
+
* 2. {@link renderPilotEvidencePackMarkdown} / {@link renderPilotEvidencePackHtml}
|
|
18
|
+
* — the human-readable executive packet.
|
|
19
|
+
*
|
|
20
|
+
* Hard rules (shared with utils/enterprise-eval-report.ts + utils/guided-eval.ts):
|
|
21
|
+
* - Source-free. Only paths, owners, symbol names, counts, verdicts, hashes,
|
|
22
|
+
* and tier labels are read or emitted. We NEVER copy source, diffs, patch
|
|
23
|
+
* bodies, raw prompts, secrets, or the admission record's natural-language
|
|
24
|
+
* `intentSummary` / `goal` prose — intent is represented by its hash and
|
|
25
|
+
* categories only. {@link assertPilotEvidencePackSourceFree} is the backstop.
|
|
26
|
+
* - Honest tiers. Deterministic path/approval/hash facts are separated from
|
|
27
|
+
* advisory inference; trust posture is reported truthfully (self-attested vs
|
|
28
|
+
* backend-signed) and never overclaims enforcement.
|
|
29
|
+
* - Deterministic. {@link computePilotEvidencePackHash} excludes wall-clock
|
|
30
|
+
* timestamps so the same input yields the same `contentHash`.
|
|
31
|
+
*
|
|
32
|
+
* Everything here is pure (no filesystem or network I/O).
|
|
33
|
+
*/
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.FORBIDDEN_PROSE_KEYS = exports.PILOT_EVIDENCE_PACK_SCHEMA_VERSION = void 0;
|
|
36
|
+
exports.classifyRiskFamily = classifyRiskFamily;
|
|
37
|
+
exports.isDependencyManifest = isDependencyManifest;
|
|
38
|
+
exports.buildPilotEvidencePack = buildPilotEvidencePack;
|
|
39
|
+
exports.computePilotEvidencePackHash = computePilotEvidencePackHash;
|
|
40
|
+
exports.assertPilotEvidencePackSourceFree = assertPilotEvidencePackSourceFree;
|
|
41
|
+
exports.renderPilotEvidencePackMarkdown = renderPilotEvidencePackMarkdown;
|
|
42
|
+
exports.renderPilotEvidencePackHtml = renderPilotEvidencePackHtml;
|
|
43
|
+
const node_crypto_1 = require("node:crypto");
|
|
44
|
+
const telemetry_1 = require("@neurcode-ai/telemetry");
|
|
45
|
+
const enterprise_eval_report_1 = require("./enterprise-eval-report");
|
|
46
|
+
exports.PILOT_EVIDENCE_PACK_SCHEMA_VERSION = 'neurcode.pilot-evidence-pack.v1';
|
|
47
|
+
/**
|
|
48
|
+
* Keys that would carry intent/prompt/task prose. The pilot evidence pack is
|
|
49
|
+
* stricter than the admission record: even though the admission record's
|
|
50
|
+
* `intentSummary` passes the project's source-free gate (it is intent, not
|
|
51
|
+
* source), an executive packet for a security reviewer must not echo task text.
|
|
52
|
+
* Mirror this set in scripts/source-free-leak-scan.mjs (SOURCE_LIKE_KEYS).
|
|
53
|
+
*/
|
|
54
|
+
exports.FORBIDDEN_PROSE_KEYS = new Set([
|
|
55
|
+
'intentSummary',
|
|
56
|
+
'intentText',
|
|
57
|
+
'taskText',
|
|
58
|
+
'goalText',
|
|
59
|
+
'rawPrompt',
|
|
60
|
+
'prompt',
|
|
61
|
+
'rawChat',
|
|
62
|
+
'chat',
|
|
63
|
+
'planProse',
|
|
64
|
+
'commandBody',
|
|
65
|
+
]);
|
|
66
|
+
const PACK_EXCLUDES = [
|
|
67
|
+
'source code',
|
|
68
|
+
'diff hunks',
|
|
69
|
+
'patch bodies',
|
|
70
|
+
'raw prompts',
|
|
71
|
+
'raw chat',
|
|
72
|
+
'natural-language intent / task text',
|
|
73
|
+
'secrets',
|
|
74
|
+
'private file contents',
|
|
75
|
+
];
|
|
76
|
+
// ── Classifiers ───────────────────────────────────────────────────────────────
|
|
77
|
+
const RISK_FAMILY_RULES = [
|
|
78
|
+
{ family: 'billing_payments', test: /billing|payment|charge|invoice|subscription|refund|stripe/i },
|
|
79
|
+
{ family: 'auth_identity_secrets', test: /\bauth\b|login|session|credential|secret|token|password|oauth|sso|\bkey(s)?\b|vault/i },
|
|
80
|
+
{ family: 'database_migrations', test: /migration|\bdb\b|database|schema|\bsql\b/i },
|
|
81
|
+
{ family: 'infrastructure', test: /terraform|infra|deploy|k8s|kubernetes|helm|docker|ansible|cloudformation|\.tf\b/i },
|
|
82
|
+
{ family: 'ci_release', test: /\.github|workflow|pipeline|\bci\b|release|publish/i },
|
|
83
|
+
{ family: 'security', test: /security|crypto|permission|rbac|policy/i },
|
|
84
|
+
];
|
|
85
|
+
/** Map a coarse glob / path to a stable risk-family bucket. Source-free. */
|
|
86
|
+
function classifyRiskFamily(surface) {
|
|
87
|
+
for (const rule of RISK_FAMILY_RULES) {
|
|
88
|
+
if (rule.test.test(surface))
|
|
89
|
+
return rule.family;
|
|
90
|
+
}
|
|
91
|
+
return 'other_protected';
|
|
92
|
+
}
|
|
93
|
+
const DEPENDENCY_MANIFEST_BASENAMES = new Set([
|
|
94
|
+
'package.json',
|
|
95
|
+
'package-lock.json',
|
|
96
|
+
'npm-shrinkwrap.json',
|
|
97
|
+
'pnpm-lock.yaml',
|
|
98
|
+
'yarn.lock',
|
|
99
|
+
'requirements.txt',
|
|
100
|
+
'pipfile',
|
|
101
|
+
'pipfile.lock',
|
|
102
|
+
'poetry.lock',
|
|
103
|
+
'pyproject.toml',
|
|
104
|
+
'go.mod',
|
|
105
|
+
'go.sum',
|
|
106
|
+
'cargo.toml',
|
|
107
|
+
'cargo.lock',
|
|
108
|
+
'gemfile',
|
|
109
|
+
'gemfile.lock',
|
|
110
|
+
'build.gradle',
|
|
111
|
+
'build.gradle.kts',
|
|
112
|
+
'pom.xml',
|
|
113
|
+
'composer.json',
|
|
114
|
+
'composer.lock',
|
|
115
|
+
]);
|
|
116
|
+
function basename(path) {
|
|
117
|
+
const parts = path.split('/');
|
|
118
|
+
return (parts[parts.length - 1] || path).toLowerCase();
|
|
119
|
+
}
|
|
120
|
+
/** True when a repo-relative path is a recognized dependency manifest / lockfile. */
|
|
121
|
+
function isDependencyManifest(path) {
|
|
122
|
+
return DEPENDENCY_MANIFEST_BASENAMES.has(basename(path));
|
|
123
|
+
}
|
|
124
|
+
// ── Builder ───────────────────────────────────────────────────────────────────
|
|
125
|
+
function normalizeTrust(level) {
|
|
126
|
+
if (!level)
|
|
127
|
+
return 'other';
|
|
128
|
+
const l = level.toLowerCase();
|
|
129
|
+
if (l.includes('backend_signed') || l === 'backend_signed')
|
|
130
|
+
return 'backendSigned';
|
|
131
|
+
if (l.includes('self_attested') || l === 'self_attested' || l === 'self-attested')
|
|
132
|
+
return 'selfAttested';
|
|
133
|
+
return 'other';
|
|
134
|
+
}
|
|
135
|
+
function uniqueSorted(values) {
|
|
136
|
+
return Array.from(new Set(values)).sort();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Build the source-free pilot evidence pack from already-parsed inputs. The
|
|
140
|
+
* returned object carries a stable {@link PilotEvidencePack.contentHash}; the
|
|
141
|
+
* caller should still run {@link assertPilotEvidencePackSourceFree} before
|
|
142
|
+
* writing or printing (defense in depth — the harness asserts the same).
|
|
143
|
+
*/
|
|
144
|
+
function buildPilotEvidencePack(input) {
|
|
145
|
+
const sessions = [...input.sessions].sort((a, b) => a.sessionId.localeCompare(b.sessionId));
|
|
146
|
+
const admissions = [...input.admissions].sort((a, b) => a.sessionId.localeCompare(b.sessionId));
|
|
147
|
+
const admissionBySession = new Map(admissions.map((a) => [a.sessionId, a]));
|
|
148
|
+
// Completeness.
|
|
149
|
+
const missingArtifacts = [];
|
|
150
|
+
if (sessions.length === 0)
|
|
151
|
+
missingArtifacts.push('session change-records (.neurcode/sessions/*.change-record.json)');
|
|
152
|
+
if (admissions.length === 0)
|
|
153
|
+
missingArtifacts.push('admission records (.neurcode/admission/*.json)');
|
|
154
|
+
if (!input.metrics)
|
|
155
|
+
missingArtifacts.push('pilot metrics rollup (.neurcode/pilot-metrics.json)');
|
|
156
|
+
const completenessNotes = [];
|
|
157
|
+
if (!input.brainReadiness) {
|
|
158
|
+
completenessNotes.push('Repository brain readiness was not included (run `neurcode brain readiness` to add structural coverage).');
|
|
159
|
+
}
|
|
160
|
+
completenessNotes.push('Runtime Safety Kernel plan-drift fields are surfaced from exported records, not persisted change-records; plan activity below is derived from session plan events and amendments.');
|
|
161
|
+
let status;
|
|
162
|
+
if (sessions.length === 0 && admissions.length === 0)
|
|
163
|
+
status = 'empty';
|
|
164
|
+
else if (sessions.length > 0 && admissions.length > 0 && input.metrics)
|
|
165
|
+
status = 'complete';
|
|
166
|
+
else
|
|
167
|
+
status = 'partial';
|
|
168
|
+
// Verdict + trust distribution.
|
|
169
|
+
const verdictCounts = {};
|
|
170
|
+
for (const s of sessions) {
|
|
171
|
+
const v = s.verdict || 'unrecorded';
|
|
172
|
+
verdictCounts[v] = (verdictCounts[v] ?? 0) + 1;
|
|
173
|
+
}
|
|
174
|
+
const trustPosture = { selfAttested: 0, backendSigned: 0, other: 0 };
|
|
175
|
+
for (const s of sessions)
|
|
176
|
+
trustPosture[normalizeTrust(s.trustLevel)] += 1;
|
|
177
|
+
for (const a of admissions) {
|
|
178
|
+
if (sessions.some((s) => s.sessionId === a.sessionId))
|
|
179
|
+
continue; // avoid double counting
|
|
180
|
+
trustPosture[normalizeTrust(a.trustLevel)] += 1;
|
|
181
|
+
}
|
|
182
|
+
// Risk families (from admission surfaces + session blocked boundaries).
|
|
183
|
+
const familySurfaces = new Map();
|
|
184
|
+
const addSurface = (surface) => {
|
|
185
|
+
if (!surface)
|
|
186
|
+
return;
|
|
187
|
+
const family = classifyRiskFamily(surface);
|
|
188
|
+
if (!familySurfaces.has(family))
|
|
189
|
+
familySurfaces.set(family, new Set());
|
|
190
|
+
familySurfaces.get(family).add(surface);
|
|
191
|
+
};
|
|
192
|
+
for (const a of admissions) {
|
|
193
|
+
for (const p of a.paths.approvalRequiredSurfaces)
|
|
194
|
+
addSurface(p);
|
|
195
|
+
for (const p of a.paths.blocked)
|
|
196
|
+
addSurface(p);
|
|
197
|
+
for (const p of a.paths.denied)
|
|
198
|
+
addSurface(p);
|
|
199
|
+
}
|
|
200
|
+
for (const s of sessions)
|
|
201
|
+
for (const p of s.blockedBoundaries)
|
|
202
|
+
addSurface(p);
|
|
203
|
+
const blockedRiskFamilies = Array.from(familySurfaces.entries())
|
|
204
|
+
.map(([family, set]) => ({
|
|
205
|
+
family,
|
|
206
|
+
surfaceCount: set.size,
|
|
207
|
+
sampleSurfaces: uniqueSorted(Array.from(set)).slice(0, 5),
|
|
208
|
+
}))
|
|
209
|
+
.sort((a, b) => b.surfaceCount - a.surfaceCount || a.family.localeCompare(b.family));
|
|
210
|
+
// Approvals.
|
|
211
|
+
const approvedExactFromSessions = sessions.reduce((n, s) => n + s.approvals.approvedExactPathCount, 0);
|
|
212
|
+
const approvedExactFromAdmissions = admissions.reduce((n, a) => n + a.counts.approvedExactPaths, 0);
|
|
213
|
+
const approvals = {
|
|
214
|
+
sessionsRequiringApproval: sessions.filter((s) => s.approvals.approvalRequired).length,
|
|
215
|
+
exactPathOnlySessions: sessions.filter((s) => s.approvals.exactPathApprovalOnly).length,
|
|
216
|
+
approvedExactPathTotal: approvedExactFromSessions > 0 ? approvedExactFromSessions : approvedExactFromAdmissions,
|
|
217
|
+
neighborDenyObservedSessions: sessions.filter((s) => s.approvals.neighborSensitiveBlocked).length,
|
|
218
|
+
blockedPathTotal: admissions.reduce((n, a) => n + a.counts.blockedPaths, 0),
|
|
219
|
+
deniedPathTotal: admissions.reduce((n, a) => n + a.counts.deniedPaths, 0),
|
|
220
|
+
};
|
|
221
|
+
// Plan drift (derived honestly from persisted plan activity).
|
|
222
|
+
const planEventTotal = sessions.reduce((n, s) => n + s.counts.planEvents, 0);
|
|
223
|
+
const pendingAmendmentTotal = sessions.reduce((n, s) => n + s.plan.pendingAmendmentCount, 0);
|
|
224
|
+
const planTimelineTotal = sessions.reduce((n, s) => n + s.plan.timelineCount, 0);
|
|
225
|
+
const sessionsWithPlanActivity = sessions.filter((s) => s.counts.planEvents > 0 || s.plan.timelineCount > 0 || s.plan.pendingAmendmentCount > 0).length;
|
|
226
|
+
const planDrift = {
|
|
227
|
+
planEventTotal,
|
|
228
|
+
pendingAmendmentTotal,
|
|
229
|
+
planTimelineTotal,
|
|
230
|
+
sessionsWithPlanActivity,
|
|
231
|
+
note: sessionsWithPlanActivity === 0
|
|
232
|
+
? 'No plan revisions or pending amendments were recorded across the pilot sessions.'
|
|
233
|
+
: `${sessionsWithPlanActivity} session(s) recorded plan activity (events/timeline/amendments). Definitive plan-drift verdicts require the Runtime Safety Kernel surface on exported records.`,
|
|
234
|
+
};
|
|
235
|
+
// Dependency changes (governed counts + object hashes, never contents).
|
|
236
|
+
const depFiles = new Map();
|
|
237
|
+
for (const a of admissions) {
|
|
238
|
+
for (const entry of a.manifest.delta) {
|
|
239
|
+
if (!isDependencyManifest(entry.path))
|
|
240
|
+
continue;
|
|
241
|
+
const objectHash = entry.newObjectId ?? entry.oldObjectId ?? null;
|
|
242
|
+
depFiles.set(`${entry.path}::${objectHash ?? ''}`, {
|
|
243
|
+
path: entry.path,
|
|
244
|
+
changeType: entry.changeType,
|
|
245
|
+
objectHash,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const dependencyFiles = Array.from(depFiles.values()).sort((a, b) => a.path.localeCompare(b.path));
|
|
250
|
+
const dependencyChanges = {
|
|
251
|
+
governedChangeCount: dependencyFiles.length,
|
|
252
|
+
files: dependencyFiles,
|
|
253
|
+
note: 'Counts and git object hashes for dependency manifests / lockfiles only — file contents are never included.',
|
|
254
|
+
};
|
|
255
|
+
// Evidence hashes (join session record hashes with admission manifest hashes).
|
|
256
|
+
const evidenceHashes = sessions.map((s) => {
|
|
257
|
+
const a = admissionBySession.get(s.sessionId);
|
|
258
|
+
return {
|
|
259
|
+
sessionId: s.sessionId,
|
|
260
|
+
recordHash: s.hashes.recordHash,
|
|
261
|
+
replayHash: s.hashes.replayHash ?? a?.integrity.replayHash ?? null,
|
|
262
|
+
deltaHash: a?.manifest.deltaHash ?? null,
|
|
263
|
+
coverageSetHash: a?.manifest.coverageSetHash ?? null,
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
// Admission-only sessions (no matching change-record) still contribute hashes.
|
|
267
|
+
for (const a of admissions) {
|
|
268
|
+
if (sessions.some((s) => s.sessionId === a.sessionId))
|
|
269
|
+
continue;
|
|
270
|
+
evidenceHashes.push({
|
|
271
|
+
sessionId: a.sessionId,
|
|
272
|
+
recordHash: null,
|
|
273
|
+
replayHash: a.integrity.replayHash,
|
|
274
|
+
deltaHash: a.manifest.deltaHash,
|
|
275
|
+
coverageSetHash: a.manifest.coverageSetHash,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
evidenceHashes.sort((a, b) => a.sessionId.localeCompare(b.sessionId));
|
|
279
|
+
const governedEditChecks = sessions.reduce((n, s) => n + s.counts.events, 0);
|
|
280
|
+
// Summary headline.
|
|
281
|
+
const headline = status === 'empty'
|
|
282
|
+
? 'No governed pilot sessions were recorded yet — run a governed session, then re-export.'
|
|
283
|
+
: `${sessions.length} governed session(s), ${blockedRiskFamilies.length} blocked risk family(ies), ${approvals.approvedExactPathTotal} exact-path approval(s); ${trustPosture.backendSigned} backend-signed / ${trustPosture.selfAttested} self-attested; source-free.`;
|
|
284
|
+
const summary = {
|
|
285
|
+
sessionCount: sessions.length,
|
|
286
|
+
admissionRecordCount: admissions.length,
|
|
287
|
+
verdictCounts,
|
|
288
|
+
governedEditChecks,
|
|
289
|
+
blockedPathTotal: approvals.blockedPathTotal,
|
|
290
|
+
deniedPathTotal: approvals.deniedPathTotal,
|
|
291
|
+
approvedExactPathTotal: approvals.approvedExactPathTotal,
|
|
292
|
+
riskFamilyCount: blockedRiskFamilies.length,
|
|
293
|
+
dependencyChangeCount: dependencyChanges.governedChangeCount,
|
|
294
|
+
trustPosture,
|
|
295
|
+
headline,
|
|
296
|
+
};
|
|
297
|
+
// What stayed local.
|
|
298
|
+
const allSourceFree = admissions.length === 0 || admissions.every((a) => a.integrity.sourceFree);
|
|
299
|
+
const whatStayedLocal = {
|
|
300
|
+
statement: 'Source code never left this machine. This pack aggregates source-free facts (paths, owners, counts, verdicts, and hashes) computed locally by the Neurcode runtime control plane.',
|
|
301
|
+
facts: [
|
|
302
|
+
`${admissions.length} admission record(s) report sourceFree=${allSourceFree ? 'true' : 'mixed'}.`,
|
|
303
|
+
'No source code, diffs, patch bodies, raw prompts, chat, or secrets are included.',
|
|
304
|
+
'Natural-language task / intent text is represented only by its hash and categories.',
|
|
305
|
+
`Evidence trust posture: ${trustPosture.backendSigned} backend-signed, ${trustPosture.selfAttested} self-attested${trustPosture.other ? `, ${trustPosture.other} other` : ''}.`,
|
|
306
|
+
],
|
|
307
|
+
};
|
|
308
|
+
// Limitations (honest, audience-aware).
|
|
309
|
+
const limitations = [
|
|
310
|
+
'Neurcode is a runtime control plane for AI coding agents — it is not an AppSec/SAST scanner or a code-review bot. This pack does not prove the source code is correct, secure, or free of vulnerabilities.',
|
|
311
|
+
'Deterministic facts (paths, approvals, counts, hashes) are separated from advisory inference (reuse / architecture signals), which can produce false positives.',
|
|
312
|
+
];
|
|
313
|
+
if (trustPosture.selfAttested > 0) {
|
|
314
|
+
limitations.push('Self-attested records are honest local claims that a governed session produced these effects — not cryptographic proof. Configure the backend signing key to upgrade to verifiable receipts.');
|
|
315
|
+
}
|
|
316
|
+
if (!input.metrics) {
|
|
317
|
+
limitations.push('No local pilot-metrics rollup was found, so verify-run / pass-rate trends are omitted.');
|
|
318
|
+
}
|
|
319
|
+
if (status !== 'complete') {
|
|
320
|
+
limitations.push(`This is an INCOMPLETE pilot export (completeness: ${status}). Missing: ${missingArtifacts.join('; ') || 'none'}.`);
|
|
321
|
+
}
|
|
322
|
+
const truthTiers = {
|
|
323
|
+
deterministic: [
|
|
324
|
+
'Session counts, ids, statuses, and verdicts',
|
|
325
|
+
'Blocked / denied path counts and approval-required surfaces',
|
|
326
|
+
'Exact-path approval and neighbor-deny facts',
|
|
327
|
+
'Dependency manifest change counts and git object hashes',
|
|
328
|
+
'Evidence hashes (record / replay / delta / coverage-set)',
|
|
329
|
+
],
|
|
330
|
+
advisory: [
|
|
331
|
+
'Risk-family bucketing of protected surfaces (keyword classification)',
|
|
332
|
+
'Reuse advisory counts (structural fingerprints, not semantic proof)',
|
|
333
|
+
'Plan-activity signals (events / amendments) as a drift proxy',
|
|
334
|
+
],
|
|
335
|
+
};
|
|
336
|
+
const packWithoutHash = {
|
|
337
|
+
schemaVersion: exports.PILOT_EVIDENCE_PACK_SCHEMA_VERSION,
|
|
338
|
+
generatedAt: input.generatedAt,
|
|
339
|
+
contentHash: '',
|
|
340
|
+
cli: { version: input.cliVersion },
|
|
341
|
+
repo: { rootHash: input.repoRootHash, name: input.repoName },
|
|
342
|
+
completeness: { status, missingArtifacts, notes: completenessNotes },
|
|
343
|
+
summary,
|
|
344
|
+
sessions: sessions.map((s) => ({
|
|
345
|
+
sessionId: s.sessionId,
|
|
346
|
+
status: s.status,
|
|
347
|
+
verdict: s.verdict,
|
|
348
|
+
scopeMode: s.scopeMode,
|
|
349
|
+
trustLevel: s.trustLevel,
|
|
350
|
+
intentHash: s.intentHash,
|
|
351
|
+
intentCategories: s.intentCategories,
|
|
352
|
+
counts: s.counts,
|
|
353
|
+
approvedExactPathCount: s.approvals.approvedExactPathCount,
|
|
354
|
+
neighborSensitiveBlocked: s.approvals.neighborSensitiveBlocked,
|
|
355
|
+
planEvents: s.counts.planEvents,
|
|
356
|
+
pendingAmendments: s.plan.pendingAmendmentCount,
|
|
357
|
+
reuseAdvisoryCount: s.reuseAdvisoryCount,
|
|
358
|
+
})),
|
|
359
|
+
blockedRiskFamilies,
|
|
360
|
+
approvals,
|
|
361
|
+
planDrift,
|
|
362
|
+
dependencyChanges,
|
|
363
|
+
evidenceHashes,
|
|
364
|
+
brainReadiness: input.brainReadiness ?? null,
|
|
365
|
+
metrics: input.metrics,
|
|
366
|
+
whatStayedLocal,
|
|
367
|
+
limitations,
|
|
368
|
+
truthTiers,
|
|
369
|
+
privacy: { sourceFree: true, excludes: PACK_EXCLUDES },
|
|
370
|
+
};
|
|
371
|
+
return { ...packWithoutHash, contentHash: computePilotEvidencePackHash(packWithoutHash) };
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Compute the stable content hash of a pack: sha256 over the sorted-key
|
|
375
|
+
* serialization with `generatedAt` (and the hash field itself) removed, so the
|
|
376
|
+
* same input always yields the same hash regardless of generation time.
|
|
377
|
+
*/
|
|
378
|
+
function computePilotEvidencePackHash(pack) {
|
|
379
|
+
const clone = JSON.parse(JSON.stringify(pack));
|
|
380
|
+
delete clone.generatedAt;
|
|
381
|
+
delete clone.contentHash;
|
|
382
|
+
return (0, node_crypto_1.createHash)('sha256').update((0, telemetry_1.stableStringify)(clone)).digest('hex');
|
|
383
|
+
}
|
|
384
|
+
// ── Source-free backstop ──────────────────────────────────────────────────────
|
|
385
|
+
function scanForbiddenKeys(value, path, found) {
|
|
386
|
+
if (Array.isArray(value)) {
|
|
387
|
+
value.forEach((item, i) => scanForbiddenKeys(item, `${path}[${i}]`, found));
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
if (!value || typeof value !== 'object')
|
|
391
|
+
return;
|
|
392
|
+
for (const [key, child] of Object.entries(value)) {
|
|
393
|
+
if (exports.FORBIDDEN_PROSE_KEYS.has(key))
|
|
394
|
+
found.push(`${path}.${key}`);
|
|
395
|
+
scanForbiddenKeys(child, `${path}.${key}`, found);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Throw if a would-be pilot evidence artifact carries source/diff/secret shapes
|
|
400
|
+
* (delegated to the shared enterprise-eval scan) or any prose-intent key.
|
|
401
|
+
*/
|
|
402
|
+
function assertPilotEvidencePackSourceFree(value, label = 'pilot evidence pack') {
|
|
403
|
+
(0, enterprise_eval_report_1.assertEnterpriseEvalSourceFree)(value, label);
|
|
404
|
+
const found = [];
|
|
405
|
+
scanForbiddenKeys(value, 'root', found);
|
|
406
|
+
if (found.length > 0) {
|
|
407
|
+
throw new Error(`${label} failed source-free scan: forbidden prose key(s) ${found.join(', ')}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// ── Renderers ─────────────────────────────────────────────────────────────────
|
|
411
|
+
function pct(value) {
|
|
412
|
+
return `${Math.round(value * 100)}%`;
|
|
413
|
+
}
|
|
414
|
+
function renderPilotEvidencePackMarkdown(pack) {
|
|
415
|
+
const lines = [];
|
|
416
|
+
lines.push('# Neurcode Pilot Evidence Pack');
|
|
417
|
+
lines.push('');
|
|
418
|
+
lines.push(`Generated: ${pack.generatedAt}`);
|
|
419
|
+
lines.push(`Repository: ${pack.repo.name ?? 'n/a'} (root hash ${pack.repo.rootHash ?? 'n/a'} — no source)`);
|
|
420
|
+
lines.push(`CLI: ${pack.cli.version ?? 'n/a'} · Schema: ${pack.schemaVersion} · Content hash: ${pack.contentHash}`);
|
|
421
|
+
lines.push(`Completeness: ${pack.completeness.status.toUpperCase()}`);
|
|
422
|
+
lines.push('');
|
|
423
|
+
lines.push('_Audience: engineering manager, principal engineer, security reviewer, procurement/IT. Source-free by construction._');
|
|
424
|
+
lines.push('');
|
|
425
|
+
lines.push('## Pilot summary');
|
|
426
|
+
lines.push('');
|
|
427
|
+
lines.push(`- ${pack.summary.headline}`);
|
|
428
|
+
lines.push(`- Governed sessions: ${pack.summary.sessionCount} · admission records: ${pack.summary.admissionRecordCount}`);
|
|
429
|
+
lines.push(`- Governed edit checks: ${pack.summary.governedEditChecks}`);
|
|
430
|
+
lines.push(`- Blocked paths: ${pack.summary.blockedPathTotal} · denied paths: ${pack.summary.deniedPathTotal} · exact-path approvals: ${pack.summary.approvedExactPathTotal}`);
|
|
431
|
+
lines.push(`- Blocked risk families: ${pack.summary.riskFamilyCount} · governed dependency changes: ${pack.summary.dependencyChangeCount}`);
|
|
432
|
+
const vc = Object.entries(pack.summary.verdictCounts);
|
|
433
|
+
lines.push(`- Verdicts: ${vc.length ? vc.map(([v, n]) => `${v}: ${n}`).join(', ') : 'none recorded'}`);
|
|
434
|
+
lines.push('');
|
|
435
|
+
lines.push('## Sessions');
|
|
436
|
+
lines.push('');
|
|
437
|
+
if (pack.sessions.length === 0) {
|
|
438
|
+
lines.push('_No governed sessions recorded._');
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
lines.push('| Session | Status | Verdict | Scope | Trust | Checks | Approvals | Plan events |');
|
|
442
|
+
lines.push('|---|---|---|---|---|---|---|---|');
|
|
443
|
+
for (const s of pack.sessions) {
|
|
444
|
+
lines.push(`| ${s.sessionId} | ${s.status ?? '—'} | ${s.verdict ?? '—'} | ${s.scopeMode ?? '—'} | ${s.trustLevel ?? '—'} | ${s.counts.events} | ${s.approvedExactPathCount} exact${s.neighborSensitiveBlocked ? ' · neighbor-deny' : ''} | ${s.planEvents} |`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
lines.push('');
|
|
448
|
+
lines.push('## Blocked risk families');
|
|
449
|
+
lines.push('');
|
|
450
|
+
lines.push('_Protected surfaces that required approval or were blocked, bucketed by family (advisory classification of coarse, source-free globs)._');
|
|
451
|
+
lines.push('');
|
|
452
|
+
if (pack.blockedRiskFamilies.length === 0) {
|
|
453
|
+
lines.push('_No blocked or approval-required surfaces recorded._');
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
lines.push('| Risk family | Surfaces | Examples |');
|
|
457
|
+
lines.push('|---|---|---|');
|
|
458
|
+
for (const f of pack.blockedRiskFamilies) {
|
|
459
|
+
lines.push(`| ${f.family} | ${f.surfaceCount} | ${f.sampleSurfaces.join(', ') || '—'} |`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
lines.push('');
|
|
463
|
+
lines.push('## Approvals');
|
|
464
|
+
lines.push('');
|
|
465
|
+
lines.push(`- Sessions requiring approval: ${pack.approvals.sessionsRequiringApproval}`);
|
|
466
|
+
lines.push(`- Exact-path-only approval sessions: ${pack.approvals.exactPathOnlySessions}`);
|
|
467
|
+
lines.push(`- Exact paths approved (total): ${pack.approvals.approvedExactPathTotal}`);
|
|
468
|
+
lines.push(`- Sessions where a neighbor stayed blocked after approval: ${pack.approvals.neighborDenyObservedSessions}`);
|
|
469
|
+
lines.push(`- Blocked paths: ${pack.approvals.blockedPathTotal} · denied paths: ${pack.approvals.deniedPathTotal}`);
|
|
470
|
+
lines.push('');
|
|
471
|
+
lines.push('## Plan drift');
|
|
472
|
+
lines.push('');
|
|
473
|
+
lines.push(`- Plan events: ${pack.planDrift.planEventTotal} · timeline entries: ${pack.planDrift.planTimelineTotal} · pending amendments: ${pack.planDrift.pendingAmendmentTotal}`);
|
|
474
|
+
lines.push(`- Sessions with plan activity: ${pack.planDrift.sessionsWithPlanActivity}`);
|
|
475
|
+
lines.push(`- ${pack.planDrift.note}`);
|
|
476
|
+
lines.push('');
|
|
477
|
+
lines.push('## Dependency changes');
|
|
478
|
+
lines.push('');
|
|
479
|
+
lines.push(`_${pack.dependencyChanges.note}_`);
|
|
480
|
+
lines.push('');
|
|
481
|
+
if (pack.dependencyChanges.files.length === 0) {
|
|
482
|
+
lines.push('_No governed dependency-manifest changes recorded._');
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
lines.push('| Manifest | Change | Object hash |');
|
|
486
|
+
lines.push('|---|---|---|');
|
|
487
|
+
for (const f of pack.dependencyChanges.files) {
|
|
488
|
+
lines.push(`| ${f.path} | ${f.changeType} | ${f.objectHash ?? '—'} |`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
lines.push('');
|
|
492
|
+
lines.push('## Evidence hashes');
|
|
493
|
+
lines.push('');
|
|
494
|
+
if (pack.evidenceHashes.length === 0) {
|
|
495
|
+
lines.push('_No evidence hashes recorded._');
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
lines.push('| Session | Record hash | Replay hash | Delta hash | Coverage-set hash |');
|
|
499
|
+
lines.push('|---|---|---|---|---|');
|
|
500
|
+
for (const h of pack.evidenceHashes) {
|
|
501
|
+
lines.push(`| ${h.sessionId} | ${h.recordHash ?? '—'} | ${h.replayHash ?? '—'} | ${h.deltaHash ?? '—'} | ${h.coverageSetHash ?? '—'} |`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
lines.push('');
|
|
505
|
+
if (pack.metrics) {
|
|
506
|
+
lines.push('## Local governance metrics');
|
|
507
|
+
lines.push('');
|
|
508
|
+
lines.push(`- Window: ${pack.metrics.periodDays} day(s) · verify runs: ${pack.metrics.totalVerifyRuns}`);
|
|
509
|
+
lines.push(`- Blocking caught: ${pack.metrics.totalBlockingCaught} (AST-verified: ${pack.metrics.totalStructuralCaught})`);
|
|
510
|
+
lines.push(`- Pass rate: ${pct(pack.metrics.averagePassRate)} · suppression rate: ${pct(pack.metrics.suppressionRate)} · AI debt trend: ${pack.metrics.aiDebtTrend}`);
|
|
511
|
+
lines.push('');
|
|
512
|
+
}
|
|
513
|
+
lines.push('## What stayed local');
|
|
514
|
+
lines.push('');
|
|
515
|
+
lines.push(`- ${pack.whatStayedLocal.statement}`);
|
|
516
|
+
for (const f of pack.whatStayedLocal.facts)
|
|
517
|
+
lines.push(`- ${f}`);
|
|
518
|
+
lines.push('');
|
|
519
|
+
lines.push('## Limitations & completeness');
|
|
520
|
+
lines.push('');
|
|
521
|
+
for (const l of pack.limitations)
|
|
522
|
+
lines.push(`- ${l}`);
|
|
523
|
+
if (pack.completeness.missingArtifacts.length > 0) {
|
|
524
|
+
lines.push(`- Missing artifacts: ${pack.completeness.missingArtifacts.join('; ')}`);
|
|
525
|
+
}
|
|
526
|
+
for (const n of pack.completeness.notes)
|
|
527
|
+
lines.push(`- ${n}`);
|
|
528
|
+
lines.push('');
|
|
529
|
+
lines.push(`_Source-free: paths, owners, counts, verdicts, and hashes only. Excludes: ${pack.privacy.excludes.join(', ')}._`);
|
|
530
|
+
lines.push('');
|
|
531
|
+
return lines.join('\n');
|
|
532
|
+
}
|
|
533
|
+
function escapeHtml(value) {
|
|
534
|
+
return value
|
|
535
|
+
.replace(/&/g, '&')
|
|
536
|
+
.replace(/</g, '<')
|
|
537
|
+
.replace(/>/g, '>')
|
|
538
|
+
.replace(/"/g, '"');
|
|
539
|
+
}
|
|
540
|
+
function renderPilotEvidencePackHtml(pack) {
|
|
541
|
+
const esc = escapeHtml;
|
|
542
|
+
const rows = (headers, data) => {
|
|
543
|
+
const head = `<tr>${headers.map((h) => `<th>${esc(h)}</th>`).join('')}</tr>`;
|
|
544
|
+
const body = data.map((r) => `<tr>${r.map((c) => `<td>${esc(c)}</td>`).join('')}</tr>`).join('');
|
|
545
|
+
return `<table>${head}${body}</table>`;
|
|
546
|
+
};
|
|
547
|
+
const ul = (items) => `<ul>${items.map((i) => `<li>${esc(i)}</li>`).join('')}</ul>`;
|
|
548
|
+
const parts = [];
|
|
549
|
+
parts.push('<!doctype html>');
|
|
550
|
+
parts.push('<html lang="en"><head><meta charset="utf-8">');
|
|
551
|
+
parts.push('<meta name="viewport" content="width=device-width, initial-scale=1">');
|
|
552
|
+
parts.push('<title>Neurcode Pilot Evidence Pack</title>');
|
|
553
|
+
parts.push('<style>body{font:15px/1.5 -apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;max-width:920px;margin:2rem auto;padding:0 1rem;color:#1a1a1a}h1{margin-bottom:.25rem}h2{margin-top:2rem;border-bottom:1px solid #e2e2e2;padding-bottom:.25rem}table{border-collapse:collapse;width:100%;margin:.5rem 0;font-size:14px}th,td{border:1px solid #d9d9d9;padding:.35rem .5rem;text-align:left}th{background:#f4f4f5}code,.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.meta{color:#555;font-size:13px}.note{color:#555;font-style:italic}.tag{display:inline-block;background:#eef;border-radius:4px;padding:0 .4rem;font-size:12px}</style>');
|
|
554
|
+
parts.push('</head><body>');
|
|
555
|
+
parts.push('<h1>Neurcode Pilot Evidence Pack</h1>');
|
|
556
|
+
parts.push(`<p class="meta">Generated: ${esc(pack.generatedAt)}<br>`);
|
|
557
|
+
parts.push(`Repository: ${esc(pack.repo.name ?? 'n/a')} (root hash <span class="mono">${esc(pack.repo.rootHash ?? 'n/a')}</span> — no source)<br>`);
|
|
558
|
+
parts.push(`CLI: ${esc(pack.cli.version ?? 'n/a')} · Schema: <span class="mono">${esc(pack.schemaVersion)}</span> · Content hash: <span class="mono">${esc(pack.contentHash)}</span><br>`);
|
|
559
|
+
parts.push(`Completeness: <span class="tag">${esc(pack.completeness.status.toUpperCase())}</span></p>`);
|
|
560
|
+
parts.push('<p class="note">Audience: engineering manager, principal engineer, security reviewer, procurement/IT. Source-free by construction.</p>');
|
|
561
|
+
parts.push('<h2>Pilot summary</h2>');
|
|
562
|
+
parts.push(ul([
|
|
563
|
+
pack.summary.headline,
|
|
564
|
+
`Governed sessions: ${pack.summary.sessionCount} · admission records: ${pack.summary.admissionRecordCount}`,
|
|
565
|
+
`Governed edit checks: ${pack.summary.governedEditChecks}`,
|
|
566
|
+
`Blocked paths: ${pack.summary.blockedPathTotal} · denied paths: ${pack.summary.deniedPathTotal} · exact-path approvals: ${pack.summary.approvedExactPathTotal}`,
|
|
567
|
+
`Blocked risk families: ${pack.summary.riskFamilyCount} · governed dependency changes: ${pack.summary.dependencyChangeCount}`,
|
|
568
|
+
]));
|
|
569
|
+
parts.push('<h2>Sessions</h2>');
|
|
570
|
+
parts.push(pack.sessions.length === 0
|
|
571
|
+
? '<p class="note">No governed sessions recorded.</p>'
|
|
572
|
+
: rows(['Session', 'Status', 'Verdict', 'Scope', 'Trust', 'Checks', 'Exact approvals', 'Plan events'], pack.sessions.map((s) => [
|
|
573
|
+
s.sessionId,
|
|
574
|
+
s.status ?? '—',
|
|
575
|
+
s.verdict ?? '—',
|
|
576
|
+
s.scopeMode ?? '—',
|
|
577
|
+
s.trustLevel ?? '—',
|
|
578
|
+
String(s.counts.events),
|
|
579
|
+
`${s.approvedExactPathCount}${s.neighborSensitiveBlocked ? ' (neighbor-deny)' : ''}`,
|
|
580
|
+
String(s.planEvents),
|
|
581
|
+
])));
|
|
582
|
+
parts.push('<h2>Blocked risk families</h2>');
|
|
583
|
+
parts.push(pack.blockedRiskFamilies.length === 0
|
|
584
|
+
? '<p class="note">No blocked or approval-required surfaces recorded.</p>'
|
|
585
|
+
: rows(['Risk family', 'Surfaces', 'Examples'], pack.blockedRiskFamilies.map((f) => [f.family, String(f.surfaceCount), f.sampleSurfaces.join(', ') || '—'])));
|
|
586
|
+
parts.push('<h2>Approvals</h2>');
|
|
587
|
+
parts.push(ul([
|
|
588
|
+
`Sessions requiring approval: ${pack.approvals.sessionsRequiringApproval}`,
|
|
589
|
+
`Exact-path-only approval sessions: ${pack.approvals.exactPathOnlySessions}`,
|
|
590
|
+
`Exact paths approved (total): ${pack.approvals.approvedExactPathTotal}`,
|
|
591
|
+
`Sessions where a neighbor stayed blocked after approval: ${pack.approvals.neighborDenyObservedSessions}`,
|
|
592
|
+
`Blocked paths: ${pack.approvals.blockedPathTotal} · denied paths: ${pack.approvals.deniedPathTotal}`,
|
|
593
|
+
]));
|
|
594
|
+
parts.push('<h2>Plan drift</h2>');
|
|
595
|
+
parts.push(ul([
|
|
596
|
+
`Plan events: ${pack.planDrift.planEventTotal} · timeline entries: ${pack.planDrift.planTimelineTotal} · pending amendments: ${pack.planDrift.pendingAmendmentTotal}`,
|
|
597
|
+
`Sessions with plan activity: ${pack.planDrift.sessionsWithPlanActivity}`,
|
|
598
|
+
pack.planDrift.note,
|
|
599
|
+
]));
|
|
600
|
+
parts.push('<h2>Dependency changes</h2>');
|
|
601
|
+
parts.push(`<p class="note">${esc(pack.dependencyChanges.note)}</p>`);
|
|
602
|
+
parts.push(pack.dependencyChanges.files.length === 0
|
|
603
|
+
? '<p class="note">No governed dependency-manifest changes recorded.</p>'
|
|
604
|
+
: rows(['Manifest', 'Change', 'Object hash'], pack.dependencyChanges.files.map((f) => [f.path, f.changeType, f.objectHash ?? '—'])));
|
|
605
|
+
parts.push('<h2>Evidence hashes</h2>');
|
|
606
|
+
parts.push(pack.evidenceHashes.length === 0
|
|
607
|
+
? '<p class="note">No evidence hashes recorded.</p>'
|
|
608
|
+
: rows(['Session', 'Record hash', 'Replay hash', 'Delta hash', 'Coverage-set hash'], pack.evidenceHashes.map((h) => [h.sessionId, h.recordHash ?? '—', h.replayHash ?? '—', h.deltaHash ?? '—', h.coverageSetHash ?? '—'])));
|
|
609
|
+
if (pack.metrics) {
|
|
610
|
+
parts.push('<h2>Local governance metrics</h2>');
|
|
611
|
+
parts.push(ul([
|
|
612
|
+
`Window: ${pack.metrics.periodDays} day(s) · verify runs: ${pack.metrics.totalVerifyRuns}`,
|
|
613
|
+
`Blocking caught: ${pack.metrics.totalBlockingCaught} (AST-verified: ${pack.metrics.totalStructuralCaught})`,
|
|
614
|
+
`Pass rate: ${pct(pack.metrics.averagePassRate)} · suppression rate: ${pct(pack.metrics.suppressionRate)} · AI debt trend: ${pack.metrics.aiDebtTrend}`,
|
|
615
|
+
]));
|
|
616
|
+
}
|
|
617
|
+
parts.push('<h2>What stayed local</h2>');
|
|
618
|
+
parts.push(ul([pack.whatStayedLocal.statement, ...pack.whatStayedLocal.facts]));
|
|
619
|
+
parts.push('<h2>Limitations & completeness</h2>');
|
|
620
|
+
const limitationItems = [...pack.limitations];
|
|
621
|
+
if (pack.completeness.missingArtifacts.length > 0) {
|
|
622
|
+
limitationItems.push(`Missing artifacts: ${pack.completeness.missingArtifacts.join('; ')}`);
|
|
623
|
+
}
|
|
624
|
+
limitationItems.push(...pack.completeness.notes);
|
|
625
|
+
parts.push(ul(limitationItems));
|
|
626
|
+
parts.push(`<p class="note">Source-free: paths, owners, counts, verdicts, and hashes only. Excludes: ${esc(pack.privacy.excludes.join(', '))}.</p>`);
|
|
627
|
+
parts.push('</body></html>');
|
|
628
|
+
return parts.join('\n');
|
|
629
|
+
}
|
|
630
|
+
//# sourceMappingURL=pilot-evidence-pack.js.map
|