@neurcode-ai/cli 0.16.5 → 0.16.7

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.
Files changed (56) hide show
  1. package/LICENSE +201 -0
  2. package/dist/commands/brain.d.ts.map +1 -1
  3. package/dist/commands/brain.js +151 -0
  4. package/dist/commands/brain.js.map +1 -1
  5. package/dist/commands/eval.d.ts +19 -0
  6. package/dist/commands/eval.d.ts.map +1 -0
  7. package/dist/commands/eval.js +246 -0
  8. package/dist/commands/eval.js.map +1 -0
  9. package/dist/commands/onboard.d.ts +29 -0
  10. package/dist/commands/onboard.d.ts.map +1 -0
  11. package/dist/commands/onboard.js +247 -0
  12. package/dist/commands/onboard.js.map +1 -0
  13. package/dist/commands/runtime-sync.d.ts.map +1 -1
  14. package/dist/commands/runtime-sync.js +22 -0
  15. package/dist/commands/runtime-sync.js.map +1 -1
  16. package/dist/commands/session-hook.d.ts.map +1 -1
  17. package/dist/commands/session-hook.js +221 -102
  18. package/dist/commands/session-hook.js.map +1 -1
  19. package/dist/commands/session.d.ts.map +1 -1
  20. package/dist/commands/session.js +31 -0
  21. package/dist/commands/session.js.map +1 -1
  22. package/dist/index.js +9 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/runtime-build.json +5 -5
  25. package/dist/utils/guided-eval.d.ts +251 -0
  26. package/dist/utils/guided-eval.d.ts.map +1 -0
  27. package/dist/utils/guided-eval.js +894 -0
  28. package/dist/utils/guided-eval.js.map +1 -0
  29. package/dist/utils/local-repo-brain.d.ts +158 -0
  30. package/dist/utils/local-repo-brain.d.ts.map +1 -0
  31. package/dist/utils/local-repo-brain.js +854 -0
  32. package/dist/utils/local-repo-brain.js.map +1 -0
  33. package/dist/utils/structural-understanding.d.ts +61 -1
  34. package/dist/utils/structural-understanding.d.ts.map +1 -1
  35. package/dist/utils/structural-understanding.js +534 -1
  36. package/dist/utils/structural-understanding.js.map +1 -1
  37. package/package.json +9 -11
  38. package/.telemetry-bundle/dist/__tests__/harvest-verify.test.d.ts +0 -1
  39. package/.telemetry-bundle/dist/__tests__/harvest-verify.test.js +0 -86
  40. package/.telemetry-bundle/dist/contracts.d.ts +0 -58
  41. package/.telemetry-bundle/dist/contracts.js +0 -8
  42. package/.telemetry-bundle/dist/harvest-verify.d.ts +0 -9
  43. package/.telemetry-bundle/dist/harvest-verify.js +0 -128
  44. package/.telemetry-bundle/dist/index.d.ts +0 -10
  45. package/.telemetry-bundle/dist/index.js +0 -22
  46. package/.telemetry-bundle/dist/precision/leaderboards.d.ts +0 -20
  47. package/.telemetry-bundle/dist/precision/leaderboards.js +0 -72
  48. package/.telemetry-bundle/dist/reader.d.ts +0 -5
  49. package/.telemetry-bundle/dist/reader.js +0 -46
  50. package/.telemetry-bundle/dist/stable-json.d.ts +0 -5
  51. package/.telemetry-bundle/dist/stable-json.js +0 -24
  52. package/.telemetry-bundle/dist/store.d.ts +0 -10
  53. package/.telemetry-bundle/dist/store.js +0 -52
  54. package/.telemetry-bundle/dist/trust-scoring.d.ts +0 -20
  55. package/.telemetry-bundle/dist/trust-scoring.js +0 -58
  56. package/.telemetry-bundle/package.json +0 -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
- if (!nudge)
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, nudge.nudgeKey))
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.headline,
248
+ message: (nudge ?? reuseNudge)?.headline ?? 'Structural advisory recorded.',
209
249
  detail: {
210
- nudgeVersion: nudge.nudgeVersion,
211
- nudgeKey: nudge.nudgeKey,
212
- severity: nudge.severity,
213
- consequenceClass: nudge.consequenceClass,
214
- operatorAction: nudge.operatorAction,
215
- reviewFocus: nudge.reviewFocus,
216
- artifactHash: nudge.artifactHash,
217
- impact: nudge.impact ? {
218
- rank: nudge.impact.rank,
219
- score: nudge.impact.score,
220
- file: nudge.impact.file,
221
- symbol: nudge.impact.symbol,
222
- summary: nudge.impact.summary,
223
- findingTypes: nudge.impact.findingTypes,
224
- findingRanks: nudge.impact.findingRanks,
225
- findingCount: nudge.impact.findingCount,
226
- productionConsumerCount: nudge.impact.productionConsumerCount,
227
- testConsumerCount: nudge.impact.testConsumerCount,
228
- reachableProductionConsumerCount: nudge.impact.reachableProductionConsumerCount,
229
- externalProductionConsumerCount: nudge.impact.externalProductionConsumerCount,
230
- changedProductionConsumerCount: nudge.impact.changedProductionConsumerCount,
231
- sensitiveConsumerCount: nudge.impact.sensitiveConsumerCount,
232
- approvalRequiredConsumerCount: nudge.impact.approvalRequiredConsumerCount,
233
- runtimeGovernanceConsumerCount: nudge.impact.runtimeGovernanceConsumerCount,
234
- productionFiles: nudge.impact.productionFiles,
235
- changedProductionFiles: nudge.impact.changedProductionFiles,
236
- testFiles: nudge.impact.testFiles,
237
- sensitiveFiles: nudge.impact.sensitiveFiles,
238
- approvalRequiredFiles: nudge.impact.approvalRequiredFiles,
239
- runtimeGovernanceFiles: nudge.impact.runtimeGovernanceFiles,
240
- highFanout: nudge.impact.highFanout,
241
- architectureRelevant: nudge.impact.architectureRelevant,
242
- reasonCodes: nudge.impact.reasonCodes,
243
- provenance: nudge.impact.provenance,
244
- } : null,
245
- finding: {
246
- rank: nudge.finding.rank,
247
- score: nudge.finding.score,
248
- findingType: nudge.finding.findingType,
249
- file: nudge.finding.file,
250
- symbol: nudge.finding.symbol,
251
- summary: nudge.finding.summary,
252
- consumerCount: nudge.finding.consumerCount,
253
- nonTestConsumerCount: nudge.finding.nonTestConsumerCount,
254
- testConsumerCount: nudge.finding.testConsumerCount,
255
- externalConsumerCount: nudge.finding.externalConsumerCount,
256
- externalConsumerFiles: nudge.finding.externalConsumerFiles,
257
- consumerSummary: nudge.finding.consumerSummary,
258
- reasonCodes: nudge.finding.reasonCodes,
259
- },
260
- topImpacts: nudges
261
- .map((item) => item.impact)
262
- .filter((impact) => Boolean(impact))
263
- .map((impact) => ({
264
- rank: impact.rank,
265
- score: impact.score,
266
- file: impact.file,
267
- symbol: impact.symbol,
268
- summary: impact.summary,
269
- findingTypes: impact.findingTypes,
270
- findingCount: impact.findingCount,
271
- reachableProductionConsumerCount: impact.reachableProductionConsumerCount,
272
- externalProductionConsumerCount: impact.externalProductionConsumerCount,
273
- changedProductionConsumerCount: impact.changedProductionConsumerCount,
274
- productionFiles: impact.productionFiles,
275
- changedProductionFiles: impact.changedProductionFiles,
276
- sensitiveConsumerCount: impact.sensitiveConsumerCount,
277
- approvalRequiredConsumerCount: impact.approvalRequiredConsumerCount,
278
- runtimeGovernanceConsumerCount: impact.runtimeGovernanceConsumerCount,
279
- highFanout: impact.highFanout,
280
- architectureRelevant: impact.architectureRelevant,
281
- reasonCodes: impact.reasonCodes,
282
- })),
283
- topFindings: nudges.map((item) => ({
284
- nudgeKey: item.nudgeKey,
285
- severity: item.severity,
286
- consequenceClass: item.consequenceClass,
287
- operatorAction: item.operatorAction,
288
- reviewFocus: item.reviewFocus,
289
- findingType: item.finding.findingType,
290
- file: item.finding.file,
291
- symbol: item.finding.symbol,
292
- externalConsumerCount: item.finding.externalConsumerCount,
293
- externalConsumerFiles: item.finding.externalConsumerFiles,
294
- consumerSummary: item.finding.consumerSummary,
295
- reasonCodes: item.finding.reasonCodes,
296
- })),
297
- provenance: nudge.provenance,
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(result.message, {
1404
+ denyPreToolUse(enrichedMessage, {
1321
1405
  ...(result.approvalContext ? { approvalContext: result.approvalContext } : {}),
1322
1406
  ...(result.blockType
1323
1407
  ? {
1324
- blockContext: blockContext({
1325
- blockType: result.blockType,
1326
- filePath,
1327
- message: result.message,
1328
- suggestedApprovalPath: result.approvalContext?.suggestedApprovalPath,
1329
- owners: result.owners,
1330
- proposalId: pendingScopeAmendmentProposalId,
1331
- runtimeMode: runtimeMode(session),
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: {