@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
|
@@ -42,6 +42,7 @@ const diff_parser_1 = require("@neurcode-ai/diff-parser");
|
|
|
42
42
|
const structural_understanding_1 = require("../utils/structural-understanding");
|
|
43
43
|
const consequence_nudges_1 = require("../utils/consequence-nudges");
|
|
44
44
|
const agent_guard_supervisor_1 = require("../utils/agent-guard-supervisor");
|
|
45
|
+
const local_repo_brain_1 = require("../utils/local-repo-brain");
|
|
45
46
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
46
47
|
/** Read the full hook JSON from stdin, or return {} on any error. */
|
|
47
48
|
function readHookInput() {
|
|
@@ -146,6 +147,41 @@ function sessionAlreadyEmittedNudge(session, nudgeKey) {
|
|
|
146
147
|
typeof event.detail === 'object' &&
|
|
147
148
|
event.detail['nudgeKey'] === nudgeKey);
|
|
148
149
|
}
|
|
150
|
+
function reuseNudgeKey(artifactHash, finding) {
|
|
151
|
+
return [
|
|
152
|
+
'reuse',
|
|
153
|
+
artifactHash,
|
|
154
|
+
finding.changed.file,
|
|
155
|
+
finding.changed.name,
|
|
156
|
+
finding.existing.file,
|
|
157
|
+
finding.existing.name,
|
|
158
|
+
finding.matchType,
|
|
159
|
+
].join(':');
|
|
160
|
+
}
|
|
161
|
+
function formatReuseNudge(finding) {
|
|
162
|
+
return finding.message;
|
|
163
|
+
}
|
|
164
|
+
function reuseNudgeFromArtifact(artifactHash, findings) {
|
|
165
|
+
const [finding] = findings;
|
|
166
|
+
if (!finding)
|
|
167
|
+
return null;
|
|
168
|
+
return {
|
|
169
|
+
nudgeVersion: 'reuse-v1',
|
|
170
|
+
nudgeKey: reuseNudgeKey(artifactHash, finding),
|
|
171
|
+
severity: 'medium',
|
|
172
|
+
headline: formatReuseNudge(finding),
|
|
173
|
+
consequenceClass: 'repo-reuse',
|
|
174
|
+
operatorAction: 'Review the existing helper before merging; reuse or intentionally explain the duplicate.',
|
|
175
|
+
reviewFocus: [finding.changed.file, finding.existing.file],
|
|
176
|
+
artifactHash,
|
|
177
|
+
reuseFinding: finding,
|
|
178
|
+
surfacedReuseFindings: findings.slice(0, 3),
|
|
179
|
+
provenance: 'deterministic-static',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function isReuseNudge(nudge) {
|
|
183
|
+
return nudge.nudgeVersion === 'reuse-v1';
|
|
184
|
+
}
|
|
149
185
|
function readWorkingDiff(repoRoot) {
|
|
150
186
|
return (0, child_process_1.execFileSync)('git', ['-C', repoRoot, 'diff', '--no-ext-diff', 'HEAD'], {
|
|
151
187
|
encoding: 'utf8',
|
|
@@ -168,10 +204,12 @@ async function maybeRecordConsequenceNudge(repoRoot, session) {
|
|
|
168
204
|
});
|
|
169
205
|
const nudges = (0, consequence_nudges_1.selectInFlowConsequenceNudges)(artifact, { max: 3 });
|
|
170
206
|
const [nudge] = nudges;
|
|
171
|
-
|
|
207
|
+
const reuseNudge = reuseNudgeFromArtifact(artifact.artifactHash, artifact.reuseFindings);
|
|
208
|
+
const selectedNudge = nudge ?? reuseNudge;
|
|
209
|
+
if (!selectedNudge)
|
|
172
210
|
return null;
|
|
173
211
|
const latest = (0, governance_runtime_1.loadSession)(repoRoot, session.sessionId) || session;
|
|
174
|
-
if (sessionAlreadyEmittedNudge(latest,
|
|
212
|
+
if (sessionAlreadyEmittedNudge(latest, selectedNudge.nudgeKey))
|
|
175
213
|
return null;
|
|
176
214
|
const artifactPath = (0, structural_understanding_1.writeStructuralUnderstanding)(repoRoot, session.sessionId, artifact);
|
|
177
215
|
(0, governance_runtime_1.appendEvent)(repoRoot, session.sessionId, {
|
|
@@ -188,6 +226,8 @@ async function maybeRecordConsequenceNudge(repoRoot, session) {
|
|
|
188
226
|
changedFiles: artifact.changedFiles,
|
|
189
227
|
changedSymbols: artifact.changedSymbols,
|
|
190
228
|
digest: artifact.digest,
|
|
229
|
+
repoSymbolIndex: artifact.repoSymbolIndex,
|
|
230
|
+
reuseFindings: artifact.reuseFindings,
|
|
191
231
|
boundaryImpact: artifact.boundaryImpact,
|
|
192
232
|
suppressedArtifacts: artifact.suppressedArtifacts,
|
|
193
233
|
consequenceUnderstanding: {
|
|
@@ -205,103 +245,120 @@ async function maybeRecordConsequenceNudge(repoRoot, session) {
|
|
|
205
245
|
(0, governance_runtime_1.appendEvent)(repoRoot, session.sessionId, {
|
|
206
246
|
type: 'consequence_nudge',
|
|
207
247
|
ts: new Date().toISOString(),
|
|
208
|
-
message: nudge.
|
|
248
|
+
message: (nudge ?? reuseNudge)?.headline ?? 'Structural advisory recorded.',
|
|
209
249
|
detail: {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
250
|
+
...(nudge
|
|
251
|
+
? {
|
|
252
|
+
nudgeVersion: nudge.nudgeVersion,
|
|
253
|
+
nudgeKey: nudge.nudgeKey,
|
|
254
|
+
severity: nudge.severity,
|
|
255
|
+
consequenceClass: nudge.consequenceClass,
|
|
256
|
+
operatorAction: nudge.operatorAction,
|
|
257
|
+
reviewFocus: nudge.reviewFocus,
|
|
258
|
+
artifactHash: nudge.artifactHash,
|
|
259
|
+
impact: nudge.impact ? {
|
|
260
|
+
rank: nudge.impact.rank,
|
|
261
|
+
score: nudge.impact.score,
|
|
262
|
+
file: nudge.impact.file,
|
|
263
|
+
symbol: nudge.impact.symbol,
|
|
264
|
+
summary: nudge.impact.summary,
|
|
265
|
+
findingTypes: nudge.impact.findingTypes,
|
|
266
|
+
findingRanks: nudge.impact.findingRanks,
|
|
267
|
+
findingCount: nudge.impact.findingCount,
|
|
268
|
+
productionConsumerCount: nudge.impact.productionConsumerCount,
|
|
269
|
+
testConsumerCount: nudge.impact.testConsumerCount,
|
|
270
|
+
reachableProductionConsumerCount: nudge.impact.reachableProductionConsumerCount,
|
|
271
|
+
externalProductionConsumerCount: nudge.impact.externalProductionConsumerCount,
|
|
272
|
+
changedProductionConsumerCount: nudge.impact.changedProductionConsumerCount,
|
|
273
|
+
sensitiveConsumerCount: nudge.impact.sensitiveConsumerCount,
|
|
274
|
+
approvalRequiredConsumerCount: nudge.impact.approvalRequiredConsumerCount,
|
|
275
|
+
runtimeGovernanceConsumerCount: nudge.impact.runtimeGovernanceConsumerCount,
|
|
276
|
+
productionFiles: nudge.impact.productionFiles,
|
|
277
|
+
changedProductionFiles: nudge.impact.changedProductionFiles,
|
|
278
|
+
testFiles: nudge.impact.testFiles,
|
|
279
|
+
sensitiveFiles: nudge.impact.sensitiveFiles,
|
|
280
|
+
approvalRequiredFiles: nudge.impact.approvalRequiredFiles,
|
|
281
|
+
runtimeGovernanceFiles: nudge.impact.runtimeGovernanceFiles,
|
|
282
|
+
highFanout: nudge.impact.highFanout,
|
|
283
|
+
architectureRelevant: nudge.impact.architectureRelevant,
|
|
284
|
+
reasonCodes: nudge.impact.reasonCodes,
|
|
285
|
+
provenance: nudge.impact.provenance,
|
|
286
|
+
} : null,
|
|
287
|
+
finding: {
|
|
288
|
+
rank: nudge.finding.rank,
|
|
289
|
+
score: nudge.finding.score,
|
|
290
|
+
findingType: nudge.finding.findingType,
|
|
291
|
+
file: nudge.finding.file,
|
|
292
|
+
symbol: nudge.finding.symbol,
|
|
293
|
+
summary: nudge.finding.summary,
|
|
294
|
+
consumerCount: nudge.finding.consumerCount,
|
|
295
|
+
nonTestConsumerCount: nudge.finding.nonTestConsumerCount,
|
|
296
|
+
testConsumerCount: nudge.finding.testConsumerCount,
|
|
297
|
+
externalConsumerCount: nudge.finding.externalConsumerCount,
|
|
298
|
+
externalConsumerFiles: nudge.finding.externalConsumerFiles,
|
|
299
|
+
consumerSummary: nudge.finding.consumerSummary,
|
|
300
|
+
reasonCodes: nudge.finding.reasonCodes,
|
|
301
|
+
},
|
|
302
|
+
topImpacts: nudges
|
|
303
|
+
.map((item) => item.impact)
|
|
304
|
+
.filter((impact) => Boolean(impact))
|
|
305
|
+
.map((impact) => ({
|
|
306
|
+
rank: impact.rank,
|
|
307
|
+
score: impact.score,
|
|
308
|
+
file: impact.file,
|
|
309
|
+
symbol: impact.symbol,
|
|
310
|
+
summary: impact.summary,
|
|
311
|
+
findingTypes: impact.findingTypes,
|
|
312
|
+
findingCount: impact.findingCount,
|
|
313
|
+
reachableProductionConsumerCount: impact.reachableProductionConsumerCount,
|
|
314
|
+
externalProductionConsumerCount: impact.externalProductionConsumerCount,
|
|
315
|
+
changedProductionConsumerCount: impact.changedProductionConsumerCount,
|
|
316
|
+
productionFiles: impact.productionFiles,
|
|
317
|
+
changedProductionFiles: impact.changedProductionFiles,
|
|
318
|
+
sensitiveConsumerCount: impact.sensitiveConsumerCount,
|
|
319
|
+
approvalRequiredConsumerCount: impact.approvalRequiredConsumerCount,
|
|
320
|
+
runtimeGovernanceConsumerCount: impact.runtimeGovernanceConsumerCount,
|
|
321
|
+
highFanout: impact.highFanout,
|
|
322
|
+
architectureRelevant: impact.architectureRelevant,
|
|
323
|
+
reasonCodes: impact.reasonCodes,
|
|
324
|
+
})),
|
|
325
|
+
topFindings: nudges.map((item) => ({
|
|
326
|
+
nudgeKey: item.nudgeKey,
|
|
327
|
+
severity: item.severity,
|
|
328
|
+
consequenceClass: item.consequenceClass,
|
|
329
|
+
operatorAction: item.operatorAction,
|
|
330
|
+
reviewFocus: item.reviewFocus,
|
|
331
|
+
findingType: item.finding.findingType,
|
|
332
|
+
file: item.finding.file,
|
|
333
|
+
symbol: item.finding.symbol,
|
|
334
|
+
externalConsumerCount: item.finding.externalConsumerCount,
|
|
335
|
+
externalConsumerFiles: item.finding.externalConsumerFiles,
|
|
336
|
+
consumerSummary: item.finding.consumerSummary,
|
|
337
|
+
reasonCodes: item.finding.reasonCodes,
|
|
338
|
+
})),
|
|
339
|
+
provenance: nudge.provenance,
|
|
340
|
+
}
|
|
341
|
+
: reuseNudge
|
|
342
|
+
? {
|
|
343
|
+
nudgeVersion: reuseNudge.nudgeVersion,
|
|
344
|
+
nudgeKey: reuseNudge.nudgeKey,
|
|
345
|
+
severity: reuseNudge.severity,
|
|
346
|
+
consequenceClass: reuseNudge.consequenceClass,
|
|
347
|
+
operatorAction: reuseNudge.operatorAction,
|
|
348
|
+
reviewFocus: reuseNudge.reviewFocus,
|
|
349
|
+
artifactHash: reuseNudge.artifactHash,
|
|
350
|
+
reuseFinding: reuseNudge.reuseFinding,
|
|
351
|
+
topReuseFindings: reuseNudge.surfacedReuseFindings,
|
|
352
|
+
provenance: reuseNudge.provenance,
|
|
353
|
+
}
|
|
354
|
+
: {}),
|
|
298
355
|
killSwitch: 'NEURCODE_DISABLE_CONSEQUENCE_NUDGES=1',
|
|
299
356
|
},
|
|
300
357
|
});
|
|
301
358
|
const refreshed = (0, governance_runtime_1.loadSession)(repoRoot, session.sessionId);
|
|
302
359
|
if (refreshed)
|
|
303
360
|
await (0, runtime_live_1.publishRuntimeLiveStatus)(repoRoot, refreshed);
|
|
304
|
-
return nudge;
|
|
361
|
+
return nudge ?? reuseNudge;
|
|
305
362
|
}
|
|
306
363
|
catch (error) {
|
|
307
364
|
diagnostic(`consequence nudge skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1313,23 +1370,63 @@ async function handleCheck(cmdCwd) {
|
|
|
1313
1370
|
const consequenceNudge = result.verdict === 'block'
|
|
1314
1371
|
? null
|
|
1315
1372
|
: await maybeRecordConsequenceNudge(repoRoot, session);
|
|
1373
|
+
// ── Repo brain facts (P0/P1) ─────────────────────────────────────────────
|
|
1374
|
+
// Load source-free repo context for the blocked/warned path. Best-effort:
|
|
1375
|
+
// if the artifact is missing the message is unchanged and recovery guidance
|
|
1376
|
+
// is appended instead.
|
|
1377
|
+
let repoBrainFileFacts = null;
|
|
1378
|
+
let repoBrainMeta = {
|
|
1379
|
+
artifactHash: null,
|
|
1380
|
+
generatedAt: null,
|
|
1381
|
+
status: 'missing',
|
|
1382
|
+
};
|
|
1383
|
+
try {
|
|
1384
|
+
const brainCtx = (0, local_repo_brain_1.getRepoBrainContext)(repoRoot, [filePath]);
|
|
1385
|
+
repoBrainMeta = { artifactHash: brainCtx.artifactHash, generatedAt: brainCtx.generatedAt, status: brainCtx.status };
|
|
1386
|
+
repoBrainFileFacts = brainCtx.files[0] ?? null;
|
|
1387
|
+
}
|
|
1388
|
+
catch {
|
|
1389
|
+
// Never let brain context loading break the enforcement path.
|
|
1390
|
+
}
|
|
1316
1391
|
// ── Emit hook response ────────────────────────────────────────────────────
|
|
1317
1392
|
if (result.verdict === 'block') {
|
|
1393
|
+
// Enrich deny message with source-free repo facts (P1).
|
|
1394
|
+
const brainSuffix = repoBrainFileFacts
|
|
1395
|
+
? (0, local_repo_brain_1.formatRepoBrainFactsForMessage)(repoBrainFileFacts)
|
|
1396
|
+
: repoBrainMeta.status === 'missing'
|
|
1397
|
+
? 'Run `neurcode brain index` for local source-free repo context.'
|
|
1398
|
+
: '';
|
|
1399
|
+
const enrichedMessage = brainSuffix
|
|
1400
|
+
? `${result.message} | ${brainSuffix}`
|
|
1401
|
+
: result.message;
|
|
1318
1402
|
// Include machine-readable approvalContext when the block is approval-required,
|
|
1319
1403
|
// so the agent can surface a structured approval request to the human.
|
|
1320
|
-
denyPreToolUse(
|
|
1404
|
+
denyPreToolUse(enrichedMessage, {
|
|
1321
1405
|
...(result.approvalContext ? { approvalContext: result.approvalContext } : {}),
|
|
1322
1406
|
...(result.blockType
|
|
1323
1407
|
? {
|
|
1324
|
-
blockContext:
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1408
|
+
blockContext: {
|
|
1409
|
+
...blockContext({
|
|
1410
|
+
blockType: result.blockType,
|
|
1411
|
+
filePath,
|
|
1412
|
+
message: enrichedMessage,
|
|
1413
|
+
suggestedApprovalPath: result.approvalContext?.suggestedApprovalPath,
|
|
1414
|
+
owners: result.owners,
|
|
1415
|
+
proposalId: pendingScopeAmendmentProposalId,
|
|
1416
|
+
runtimeMode: runtimeMode(session),
|
|
1417
|
+
}),
|
|
1418
|
+
...(repoBrainFileFacts ? {
|
|
1419
|
+
repoBrainFacts: {
|
|
1420
|
+
sensitiveKinds: repoBrainFileFacts.sensitiveKinds,
|
|
1421
|
+
module: repoBrainFileFacts.module,
|
|
1422
|
+
hotspot: repoBrainFileFacts.hotspot,
|
|
1423
|
+
ownerBoundary: repoBrainFileFacts.ownerBoundary,
|
|
1424
|
+
reuseAdvisories: repoBrainFileFacts.reuseAdvisories,
|
|
1425
|
+
artifactHash: repoBrainMeta.artifactHash,
|
|
1426
|
+
generatedAt: repoBrainMeta.generatedAt,
|
|
1427
|
+
},
|
|
1428
|
+
} : { repoBrainStatus: 'missing', repoBrainRecovery: 'neurcode brain index' }),
|
|
1429
|
+
},
|
|
1333
1430
|
}
|
|
1334
1431
|
: {}),
|
|
1335
1432
|
});
|
|
@@ -1347,6 +1444,28 @@ async function handleCheck(cmdCwd) {
|
|
|
1347
1444
|
}) + '\n');
|
|
1348
1445
|
process.exit(0);
|
|
1349
1446
|
}
|
|
1447
|
+
if (consequenceNudge && isReuseNudge(consequenceNudge)) {
|
|
1448
|
+
process.stdout.write(JSON.stringify({
|
|
1449
|
+
hookSpecificOutput: {
|
|
1450
|
+
hookEventName: 'PreToolUse',
|
|
1451
|
+
permissionDecision: 'allow',
|
|
1452
|
+
reason: consequenceNudge.headline,
|
|
1453
|
+
reuseNudge: {
|
|
1454
|
+
nudgeVersion: consequenceNudge.nudgeVersion,
|
|
1455
|
+
nudgeKey: consequenceNudge.nudgeKey,
|
|
1456
|
+
severity: consequenceNudge.severity,
|
|
1457
|
+
consequenceClass: consequenceNudge.consequenceClass,
|
|
1458
|
+
operatorAction: consequenceNudge.operatorAction,
|
|
1459
|
+
reviewFocus: consequenceNudge.reviewFocus,
|
|
1460
|
+
artifactHash: consequenceNudge.artifactHash,
|
|
1461
|
+
finding: consequenceNudge.reuseFinding,
|
|
1462
|
+
surfacedFindingLimit: 3,
|
|
1463
|
+
topFindings: consequenceNudge.surfacedReuseFindings,
|
|
1464
|
+
},
|
|
1465
|
+
},
|
|
1466
|
+
}) + '\n');
|
|
1467
|
+
process.exit(0);
|
|
1468
|
+
}
|
|
1350
1469
|
if (consequenceNudge) {
|
|
1351
1470
|
process.stdout.write(JSON.stringify({
|
|
1352
1471
|
hookSpecificOutput: {
|