@neurcode-ai/cli 0.9.8 → 0.9.10
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/commands/apply.d.ts.map +1 -1
- package/dist/commands/apply.js +37 -0
- package/dist/commands/apply.js.map +1 -1
- package/dist/commands/brain.d.ts.map +1 -1
- package/dist/commands/brain.js +195 -11
- package/dist/commands/brain.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +308 -72
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +44 -1
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +6 -2
- package/dist/commands/watch.js.map +1 -1
- package/dist/services/security/SecurityGuard.js +6 -6
- package/dist/services/security/SecurityGuard.js.map +1 -1
- package/dist/services/watch/Sentinel.d.ts +2 -1
- package/dist/services/watch/Sentinel.d.ts.map +1 -1
- package/dist/services/watch/Sentinel.js +31 -4
- package/dist/services/watch/Sentinel.js.map +1 -1
- package/dist/utils/brain-context.d.ts +78 -0
- package/dist/utils/brain-context.d.ts.map +1 -0
- package/dist/utils/brain-context.js +646 -0
- package/dist/utils/brain-context.js.map +1 -0
- package/dist/utils/plan-cache.d.ts +72 -13
- package/dist/utils/plan-cache.d.ts.map +1 -1
- package/dist/utils/plan-cache.js +1086 -152
- package/dist/utils/plan-cache.js.map +1 -1
- package/package.json +4 -3
package/dist/commands/plan.js
CHANGED
|
@@ -45,6 +45,7 @@ const ROILogger_1 = require("../utils/ROILogger");
|
|
|
45
45
|
const toolbox_service_1 = require("../services/toolbox-service");
|
|
46
46
|
const plan_cache_1 = require("../utils/plan-cache");
|
|
47
47
|
const neurcode_context_1 = require("../utils/neurcode-context");
|
|
48
|
+
const brain_context_1 = require("../utils/brain-context");
|
|
48
49
|
const project_root_1 = require("../utils/project-root");
|
|
49
50
|
// Import chalk with fallback for plain strings if not available
|
|
50
51
|
let chalk;
|
|
@@ -217,6 +218,99 @@ async function ensureAssetMap(cwd) {
|
|
|
217
218
|
}
|
|
218
219
|
return map;
|
|
219
220
|
}
|
|
221
|
+
function detectIntentMode(intent) {
|
|
222
|
+
const normalized = (0, plan_cache_1.normalizeIntent)(intent);
|
|
223
|
+
if (!normalized)
|
|
224
|
+
return 'implementation';
|
|
225
|
+
const implementationSignals = [
|
|
226
|
+
/\b(add|create|implement|build|fix|refactor|update|change|write|generate|migrate|remove|delete|ship)\b/,
|
|
227
|
+
];
|
|
228
|
+
for (const pattern of implementationSignals) {
|
|
229
|
+
if (pattern.test(normalized)) {
|
|
230
|
+
return 'implementation';
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const analysisSignals = [
|
|
234
|
+
/\b(read|review|inspect|analyze|audit|check|find|search|locate|compare|list|tell me|where|whether|is there)\b/,
|
|
235
|
+
/\?$/,
|
|
236
|
+
];
|
|
237
|
+
for (const pattern of analysisSignals) {
|
|
238
|
+
if (pattern.test(normalized)) {
|
|
239
|
+
return 'analysis';
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return 'implementation';
|
|
243
|
+
}
|
|
244
|
+
function applyReadOnlyDirective(intentText) {
|
|
245
|
+
return [
|
|
246
|
+
intentText,
|
|
247
|
+
'',
|
|
248
|
+
'NEURCODE_EXECUTION_MODE: READ_ONLY_ANALYSIS',
|
|
249
|
+
'- The user asked for analysis only, not code implementation.',
|
|
250
|
+
'- Do not propose code writes or file creation.',
|
|
251
|
+
'- Prefer a compact investigation plan with target files and concrete checks.',
|
|
252
|
+
'- Treat suggested files as inspection targets.',
|
|
253
|
+
].join('\n');
|
|
254
|
+
}
|
|
255
|
+
function renderCacheMissReason(reason, bestIntentSimilarity) {
|
|
256
|
+
const reasonText = {
|
|
257
|
+
no_scope_entries: 'no cached plans exist yet for this org/project scope',
|
|
258
|
+
repo_identity_changed: 'repo identity changed for this scope',
|
|
259
|
+
repo_snapshot_changed: 'repo snapshot changed (HEAD tree differs)',
|
|
260
|
+
policy_changed: 'policy fingerprint changed',
|
|
261
|
+
neurcode_version_changed: 'neurcode version changed',
|
|
262
|
+
prompt_changed: 'prompt/context changed',
|
|
263
|
+
};
|
|
264
|
+
if (reason === 'prompt_changed' && bestIntentSimilarity > 0) {
|
|
265
|
+
return `${reasonText[reason]} (closest intent similarity ${bestIntentSimilarity.toFixed(2)})`;
|
|
266
|
+
}
|
|
267
|
+
return reasonText[reason];
|
|
268
|
+
}
|
|
269
|
+
function emitCachedPlanHit(input) {
|
|
270
|
+
if (input.touchKey) {
|
|
271
|
+
try {
|
|
272
|
+
(0, plan_cache_1.readCachedPlan)(input.cwd, input.touchKey);
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
// Ignore cache touch failures.
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const createdAtLabel = new Date(input.createdAt).toLocaleString();
|
|
279
|
+
if (input.mode === 'near' && typeof input.similarity === 'number') {
|
|
280
|
+
console.log(chalk.dim(`⚡ Using near-cached plan (similarity ${input.similarity.toFixed(2)}, created: ${createdAtLabel})\n`));
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
console.log(chalk.dim(`⚡ Using cached plan (created: ${createdAtLabel})\n`));
|
|
284
|
+
}
|
|
285
|
+
displayPlan(input.response.plan);
|
|
286
|
+
console.log(chalk.dim(`\nGenerated at: ${new Date(input.response.timestamp).toLocaleString()} (cached)`));
|
|
287
|
+
if (input.response.planId && input.response.planId !== 'unknown') {
|
|
288
|
+
console.log(chalk.bold.cyan(`\n📌 Plan ID: ${input.response.planId} (Cached)`));
|
|
289
|
+
console.log(chalk.dim(' Run \'neurcode prompt\' to generate a Cursor/AI prompt.'));
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
if (input.response.planId && input.response.planId !== 'unknown') {
|
|
293
|
+
(0, state_1.setActivePlanId)(input.response.planId);
|
|
294
|
+
(0, state_1.setLastPlanGeneratedAt)(new Date().toISOString());
|
|
295
|
+
}
|
|
296
|
+
if (input.response.sessionId) {
|
|
297
|
+
(0, state_1.setSessionId)(input.response.sessionId);
|
|
298
|
+
}
|
|
299
|
+
(0, brain_context_1.recordBrainProgressEvent)(input.cwd, {
|
|
300
|
+
orgId: input.orgId,
|
|
301
|
+
projectId: input.projectId,
|
|
302
|
+
}, {
|
|
303
|
+
type: 'plan',
|
|
304
|
+
planId: input.response.planId || undefined,
|
|
305
|
+
note: input.mode === 'near'
|
|
306
|
+
? `cache_hit=near;similarity=${typeof input.similarity === 'number' ? input.similarity.toFixed(2) : 'n/a'}`
|
|
307
|
+
: 'cache_hit=exact',
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
// ignore state write errors
|
|
312
|
+
}
|
|
313
|
+
}
|
|
220
314
|
async function planCommand(intent, options) {
|
|
221
315
|
try {
|
|
222
316
|
if (!intent || !intent.trim()) {
|
|
@@ -243,6 +337,17 @@ async function planCommand(intent, options) {
|
|
|
243
337
|
const stateProjectId = (0, state_1.getProjectId)();
|
|
244
338
|
const finalProjectIdEarly = options.projectId || stateProjectId || config.projectId;
|
|
245
339
|
const shouldUseCache = options.cache !== false && process.env.NEURCODE_PLAN_NO_CACHE !== '1';
|
|
340
|
+
const normalizedIntent = (0, plan_cache_1.normalizeIntent)(intent);
|
|
341
|
+
const intentMode = detectIntentMode(intent);
|
|
342
|
+
const isReadOnlyAnalysis = intentMode === 'analysis';
|
|
343
|
+
const policyVersionHash = (0, plan_cache_1.computePolicyVersionHash)(cwd);
|
|
344
|
+
const neurcodeVersion = (0, plan_cache_1.getNeurcodeVersion)();
|
|
345
|
+
// Create a local, gitignored context file scaffold (similar to CLAUDE.md workflows),
|
|
346
|
+
// so teams/users have a predictable place to add project-specific guidance.
|
|
347
|
+
// This runs only on interactive terminals to avoid polluting CI checkouts.
|
|
348
|
+
if (process.stdout.isTTY && !process.env.CI) {
|
|
349
|
+
(0, neurcode_context_1.ensureDefaultLocalContextFile)(cwd);
|
|
350
|
+
}
|
|
246
351
|
const staticContextEarly = (0, neurcode_context_1.loadStaticNeurcodeContext)(cwd, orgId && finalProjectIdEarly ? { orgId, projectId: finalProjectIdEarly } : undefined);
|
|
247
352
|
const ticketRef = options.issue
|
|
248
353
|
? `github_issue:${options.issue}`
|
|
@@ -251,48 +356,66 @@ async function planCommand(intent, options) {
|
|
|
251
356
|
: options.ticket
|
|
252
357
|
? `ticket:${options.ticket}`
|
|
253
358
|
: undefined;
|
|
359
|
+
const promptHashEarly = (0, plan_cache_1.computePromptHash)({
|
|
360
|
+
intent: normalizedIntent,
|
|
361
|
+
ticketRef,
|
|
362
|
+
contextHash: staticContextEarly.hash,
|
|
363
|
+
});
|
|
254
364
|
const gitFingerprint = (0, plan_cache_1.getGitRepoFingerprint)(cwd);
|
|
255
365
|
if (shouldUseCache && orgId && finalProjectIdEarly && gitFingerprint) {
|
|
256
366
|
const key = (0, plan_cache_1.computePlanCacheKey)({
|
|
257
|
-
schemaVersion:
|
|
258
|
-
apiUrl: (config.apiUrl || '').replace(/\/$/, ''),
|
|
367
|
+
schemaVersion: 2,
|
|
259
368
|
orgId,
|
|
260
369
|
projectId: finalProjectIdEarly,
|
|
261
|
-
intent: (0, plan_cache_1.normalizeIntent)(intent),
|
|
262
|
-
ticketRef,
|
|
263
|
-
contextHash: staticContextEarly.hash,
|
|
264
370
|
repo: gitFingerprint,
|
|
371
|
+
promptHash: promptHashEarly,
|
|
372
|
+
policyVersionHash,
|
|
373
|
+
neurcodeVersion,
|
|
265
374
|
});
|
|
266
375
|
const cached = (0, plan_cache_1.readCachedPlan)(cwd, key);
|
|
267
376
|
if (cached) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
try {
|
|
277
|
-
if (cached.response.planId && cached.response.planId !== 'unknown') {
|
|
278
|
-
(0, state_1.setActivePlanId)(cached.response.planId);
|
|
279
|
-
(0, state_1.setLastPlanGeneratedAt)(new Date().toISOString());
|
|
280
|
-
}
|
|
281
|
-
if (cached.response.sessionId) {
|
|
282
|
-
(0, state_1.setSessionId)(cached.response.sessionId);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
catch {
|
|
286
|
-
// ignore state write errors
|
|
287
|
-
}
|
|
377
|
+
emitCachedPlanHit({
|
|
378
|
+
cwd,
|
|
379
|
+
response: cached.response,
|
|
380
|
+
createdAt: cached.createdAt,
|
|
381
|
+
mode: 'exact',
|
|
382
|
+
orgId,
|
|
383
|
+
projectId: finalProjectIdEarly,
|
|
384
|
+
});
|
|
288
385
|
return;
|
|
289
386
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
387
|
+
const near = (0, plan_cache_1.findNearCachedPlan)(cwd, {
|
|
388
|
+
orgId,
|
|
389
|
+
projectId: finalProjectIdEarly,
|
|
390
|
+
repo: gitFingerprint,
|
|
391
|
+
intent: normalizedIntent,
|
|
392
|
+
policyVersionHash,
|
|
393
|
+
neurcodeVersion,
|
|
394
|
+
ticketRef,
|
|
395
|
+
contextHash: staticContextEarly.hash,
|
|
396
|
+
});
|
|
397
|
+
if (near) {
|
|
398
|
+
emitCachedPlanHit({
|
|
399
|
+
cwd,
|
|
400
|
+
response: near.entry.response,
|
|
401
|
+
createdAt: near.entry.createdAt,
|
|
402
|
+
mode: 'near',
|
|
403
|
+
similarity: near.intentSimilarity,
|
|
404
|
+
orgId,
|
|
405
|
+
projectId: finalProjectIdEarly,
|
|
406
|
+
touchKey: near.entry.key,
|
|
407
|
+
});
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const miss = (0, plan_cache_1.diagnosePlanCacheMiss)(cwd, {
|
|
411
|
+
orgId,
|
|
412
|
+
projectId: finalProjectIdEarly,
|
|
413
|
+
repo: gitFingerprint,
|
|
414
|
+
intent: normalizedIntent,
|
|
415
|
+
policyVersionHash,
|
|
416
|
+
neurcodeVersion,
|
|
417
|
+
});
|
|
418
|
+
console.log(chalk.dim(`🧠 Cache miss: ${renderCacheMissReason(miss.reason, miss.bestIntentSimilarity)}`));
|
|
296
419
|
}
|
|
297
420
|
// Initialize Security Guard, Ticket Service, and Project Knowledge Service
|
|
298
421
|
const { SecurityGuard } = await Promise.resolve().then(() => __importStar(require('../services/security/SecurityGuard')));
|
|
@@ -408,6 +531,10 @@ async function planCommand(intent, options) {
|
|
|
408
531
|
await initCommand();
|
|
409
532
|
return;
|
|
410
533
|
}
|
|
534
|
+
const brainScope = {
|
|
535
|
+
orgId: orgId || null,
|
|
536
|
+
projectId: finalProjectIdForGuard || null,
|
|
537
|
+
};
|
|
411
538
|
// Step B: Scan file tree (paths only, no content)
|
|
412
539
|
console.log(chalk.dim(`📂 Scanning file tree in ${cwd}...`));
|
|
413
540
|
const fileTree = scanFiles(cwd, cwd, 200);
|
|
@@ -420,43 +547,84 @@ async function planCommand(intent, options) {
|
|
|
420
547
|
// - influence plan generation
|
|
421
548
|
// - participate in the cache key (avoid stale cached plans after context edits)
|
|
422
549
|
const staticContext = (0, neurcode_context_1.loadStaticNeurcodeContext)(cwd, orgId && finalProjectIdForGuard ? { orgId, projectId: finalProjectIdForGuard } : undefined);
|
|
550
|
+
// Incremental Brain refresh: keep context in sync with human progress even when watch is not running.
|
|
551
|
+
if (orgId && finalProjectIdForGuard) {
|
|
552
|
+
try {
|
|
553
|
+
const refresh = (0, brain_context_1.refreshBrainContextFromWorkspace)(cwd, brainScope, {
|
|
554
|
+
workingTreeHash: gitFingerprint?.kind === 'git' ? gitFingerprint.workingTreeHash : undefined,
|
|
555
|
+
maxFiles: 80,
|
|
556
|
+
recordEvent: false,
|
|
557
|
+
});
|
|
558
|
+
if (refresh.refreshed && (refresh.indexed > 0 || refresh.removed > 0)) {
|
|
559
|
+
console.log(chalk.dim(`🧠 Brain refresh: indexed ${refresh.indexed}, removed ${refresh.removed}, considered ${refresh.considered}`));
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
catch {
|
|
563
|
+
// Brain refresh should never block plan generation.
|
|
564
|
+
}
|
|
565
|
+
}
|
|
423
566
|
// If we couldn't use the git-based fast path (non-git projects), try a filesystem-based cache hit
|
|
424
567
|
// after we have the file tree fingerprint.
|
|
425
568
|
if (shouldUseCache && orgId && finalProjectIdForGuard && !gitFingerprint) {
|
|
426
|
-
const fsFingerprint = (0, plan_cache_1.getFilesystemFingerprintFromTree)(fileTree);
|
|
569
|
+
const fsFingerprint = (0, plan_cache_1.getFilesystemFingerprintFromTree)(fileTree, cwd);
|
|
570
|
+
const promptHash = (0, plan_cache_1.computePromptHash)({
|
|
571
|
+
intent: normalizedIntent,
|
|
572
|
+
ticketRef,
|
|
573
|
+
contextHash: staticContext.hash,
|
|
574
|
+
});
|
|
427
575
|
const key = (0, plan_cache_1.computePlanCacheKey)({
|
|
428
|
-
schemaVersion:
|
|
429
|
-
apiUrl: (config.apiUrl || '').replace(/\/$/, ''),
|
|
576
|
+
schemaVersion: 2,
|
|
430
577
|
orgId,
|
|
431
578
|
projectId: finalProjectIdForGuard,
|
|
432
|
-
intent: (0, plan_cache_1.normalizeIntent)(intent),
|
|
433
|
-
ticketRef,
|
|
434
|
-
contextHash: staticContext.hash,
|
|
435
579
|
repo: fsFingerprint,
|
|
580
|
+
promptHash,
|
|
581
|
+
policyVersionHash,
|
|
582
|
+
neurcodeVersion,
|
|
436
583
|
});
|
|
437
584
|
const cached = (0, plan_cache_1.readCachedPlan)(cwd, key);
|
|
438
585
|
if (cached) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
586
|
+
emitCachedPlanHit({
|
|
587
|
+
cwd,
|
|
588
|
+
response: cached.response,
|
|
589
|
+
createdAt: cached.createdAt,
|
|
590
|
+
mode: 'exact',
|
|
591
|
+
orgId,
|
|
592
|
+
projectId: finalProjectIdForGuard,
|
|
593
|
+
});
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
const near = (0, plan_cache_1.findNearCachedPlan)(cwd, {
|
|
597
|
+
orgId,
|
|
598
|
+
projectId: finalProjectIdForGuard,
|
|
599
|
+
repo: fsFingerprint,
|
|
600
|
+
intent: normalizedIntent,
|
|
601
|
+
policyVersionHash,
|
|
602
|
+
neurcodeVersion,
|
|
603
|
+
ticketRef,
|
|
604
|
+
contextHash: staticContext.hash,
|
|
605
|
+
});
|
|
606
|
+
if (near) {
|
|
607
|
+
emitCachedPlanHit({
|
|
608
|
+
cwd,
|
|
609
|
+
response: near.entry.response,
|
|
610
|
+
createdAt: near.entry.createdAt,
|
|
611
|
+
mode: 'near',
|
|
612
|
+
similarity: near.intentSimilarity,
|
|
613
|
+
orgId,
|
|
614
|
+
projectId: finalProjectIdForGuard,
|
|
615
|
+
touchKey: near.entry.key,
|
|
616
|
+
});
|
|
458
617
|
return;
|
|
459
618
|
}
|
|
619
|
+
const miss = (0, plan_cache_1.diagnosePlanCacheMiss)(cwd, {
|
|
620
|
+
orgId,
|
|
621
|
+
projectId: finalProjectIdForGuard,
|
|
622
|
+
repo: fsFingerprint,
|
|
623
|
+
intent: normalizedIntent,
|
|
624
|
+
policyVersionHash,
|
|
625
|
+
neurcodeVersion,
|
|
626
|
+
});
|
|
627
|
+
console.log(chalk.dim(`🧠 Cache miss: ${renderCacheMissReason(miss.reason, miss.bestIntentSimilarity)}`));
|
|
460
628
|
}
|
|
461
629
|
// Step 2: Build enhanced intent with static context + org/project memory (before any LLM call)
|
|
462
630
|
let enhancedIntent = enrichedIntent;
|
|
@@ -469,6 +637,27 @@ async function planCommand(intent, options) {
|
|
|
469
637
|
enhancedIntent = `${enhancedIntent}\n\n${memoryTail}`;
|
|
470
638
|
}
|
|
471
639
|
}
|
|
640
|
+
if (isReadOnlyAnalysis) {
|
|
641
|
+
console.log(chalk.dim('🔎 Read-only analysis mode enabled (no-code intent detected)'));
|
|
642
|
+
enhancedIntent = applyReadOnlyDirective(enhancedIntent);
|
|
643
|
+
}
|
|
644
|
+
// Retrieval augmentation: include a repo-grounded live context pack (file summaries + recent progress events).
|
|
645
|
+
if (orgId && finalProjectIdForGuard) {
|
|
646
|
+
try {
|
|
647
|
+
const pack = (0, brain_context_1.buildBrainContextPack)(cwd, brainScope, normalizedIntent, {
|
|
648
|
+
maxFiles: 8,
|
|
649
|
+
maxEvents: 6,
|
|
650
|
+
maxBytes: 10 * 1024,
|
|
651
|
+
});
|
|
652
|
+
if (pack.text) {
|
|
653
|
+
enhancedIntent = `${enhancedIntent}\n\n${pack.text}`;
|
|
654
|
+
console.log(chalk.dim(`🧠 Brain context: ${pack.selectedFiles} relevant file summary(s), ${pack.recentEvents} recent event(s) from ${pack.totalIndexedFiles} indexed file(s)`));
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
catch {
|
|
658
|
+
// Brain context pack should never block plan generation.
|
|
659
|
+
}
|
|
660
|
+
}
|
|
472
661
|
// Step 3: Load or create asset map for context injection (toolbox summary)
|
|
473
662
|
try {
|
|
474
663
|
const map = await ensureAssetMap(cwd);
|
|
@@ -490,7 +679,14 @@ async function planCommand(intent, options) {
|
|
|
490
679
|
}
|
|
491
680
|
// Retrieval augmentation: include summaries from similar cached plans (org+project scoped)
|
|
492
681
|
if (shouldUseCache && orgId && finalProjectIdForGuard) {
|
|
493
|
-
const
|
|
682
|
+
const repoIdentityForSimilarity = gitFingerprint
|
|
683
|
+
? gitFingerprint.repoIdentity
|
|
684
|
+
: (0, plan_cache_1.getFilesystemFingerprintFromTree)(fileTree, cwd).repoIdentity;
|
|
685
|
+
const similar = (0, plan_cache_1.findSimilarCachedPlans)(cwd, {
|
|
686
|
+
orgId,
|
|
687
|
+
projectId: finalProjectIdForGuard,
|
|
688
|
+
repoIdentity: repoIdentityForSimilarity,
|
|
689
|
+
}, normalizedIntent, 3);
|
|
494
690
|
if (similar.length > 0) {
|
|
495
691
|
const memoryLines = [];
|
|
496
692
|
memoryLines.push('PROJECT MEMORY (recent Neurcode plans in this repo; use only if relevant):');
|
|
@@ -557,7 +753,7 @@ async function planCommand(intent, options) {
|
|
|
557
753
|
}
|
|
558
754
|
// Check for active sessions before creating a new one
|
|
559
755
|
const finalProjectId = options.projectId || projectId || config.projectId;
|
|
560
|
-
if (finalProjectId && process.stdout.isTTY && !process.env.CI) {
|
|
756
|
+
if (finalProjectId && process.stdout.isTTY && !process.env.CI && !isReadOnlyAnalysis) {
|
|
561
757
|
try {
|
|
562
758
|
const sessions = await client.getSessions(finalProjectId, 10);
|
|
563
759
|
const activeSessions = sessions.filter(s => s.status === 'active');
|
|
@@ -652,41 +848,64 @@ async function planCommand(intent, options) {
|
|
|
652
848
|
}
|
|
653
849
|
}
|
|
654
850
|
// Ensure we have at least some files
|
|
655
|
-
|
|
851
|
+
let filesToUse = validFiles.length > 0 ? validFiles : fileTree.slice(0, 10);
|
|
656
852
|
if (validFiles.length < selectedFiles.length) {
|
|
657
853
|
console.log(chalk.yellow(`⚠️ ${selectedFiles.length - validFiles.length} selected file(s) could not be read, using ${filesToUse.length} valid file(s)`));
|
|
658
854
|
}
|
|
855
|
+
if (isReadOnlyAnalysis && filesToUse.length > 8) {
|
|
856
|
+
filesToUse = filesToUse.slice(0, 8);
|
|
857
|
+
console.log(chalk.dim(`🔎 Analysis mode: narrowed to ${filesToUse.length} top file(s)`));
|
|
858
|
+
}
|
|
659
859
|
// Step E: Pass 2 - The Architect (generate plan with selected files)
|
|
660
860
|
console.log(chalk.dim('🤖 Generating plan with selected files...\n'));
|
|
661
861
|
const response = await client.generatePlan(enhancedIntent, filesToUse, finalProjectId, ticketMetadata, projectSummary);
|
|
862
|
+
if (orgId && finalProjectIdForGuard) {
|
|
863
|
+
try {
|
|
864
|
+
const refreshed = (0, brain_context_1.refreshBrainContextForFiles)(cwd, brainScope, filesToUse);
|
|
865
|
+
(0, brain_context_1.recordBrainProgressEvent)(cwd, brainScope, {
|
|
866
|
+
type: 'plan',
|
|
867
|
+
planId: response.planId || undefined,
|
|
868
|
+
note: `selected=${filesToUse.length};indexed=${refreshed.indexed};removed=${refreshed.removed};complexity=${response.plan.estimatedComplexity || 'unknown'}`,
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
catch {
|
|
872
|
+
// Brain progression tracking should never block plan generation.
|
|
873
|
+
}
|
|
874
|
+
}
|
|
662
875
|
// Persist in local cache for instant repeat plans.
|
|
663
876
|
if (shouldUseCache && orgId && finalProjectIdForGuard) {
|
|
664
877
|
// Recompute repo fingerprint right before writing to cache so Neurcode-managed
|
|
665
878
|
// housekeeping (e.g. `.neurcode/config.json`, `.gitignore` updates) doesn't
|
|
666
879
|
// cause immediate cache misses on the next identical run.
|
|
667
880
|
const finalGitFingerprint = (0, plan_cache_1.getGitRepoFingerprint)(cwd);
|
|
668
|
-
const repo = finalGitFingerprint || (0, plan_cache_1.getFilesystemFingerprintFromTree)(fileTree);
|
|
881
|
+
const repo = finalGitFingerprint || (0, plan_cache_1.getFilesystemFingerprintFromTree)(fileTree, cwd);
|
|
882
|
+
const promptHash = (0, plan_cache_1.computePromptHash)({
|
|
883
|
+
intent: normalizedIntent,
|
|
884
|
+
ticketRef,
|
|
885
|
+
contextHash: staticContext.hash,
|
|
886
|
+
});
|
|
669
887
|
const key = (0, plan_cache_1.computePlanCacheKey)({
|
|
670
|
-
schemaVersion:
|
|
671
|
-
apiUrl: (config.apiUrl || '').replace(/\/$/, ''),
|
|
888
|
+
schemaVersion: 2,
|
|
672
889
|
orgId,
|
|
673
890
|
projectId: finalProjectIdForGuard,
|
|
674
|
-
intent: (0, plan_cache_1.normalizeIntent)(intent),
|
|
675
|
-
ticketRef,
|
|
676
|
-
contextHash: staticContext.hash,
|
|
677
891
|
repo,
|
|
892
|
+
promptHash,
|
|
893
|
+
policyVersionHash,
|
|
894
|
+
neurcodeVersion,
|
|
678
895
|
});
|
|
679
896
|
(0, plan_cache_1.writeCachedPlan)(cwd, {
|
|
680
897
|
key,
|
|
681
898
|
input: {
|
|
682
|
-
schemaVersion:
|
|
683
|
-
apiUrl: (config.apiUrl || '').replace(/\/$/, ''),
|
|
899
|
+
schemaVersion: 2,
|
|
684
900
|
orgId,
|
|
685
901
|
projectId: finalProjectIdForGuard,
|
|
686
|
-
|
|
902
|
+
repo,
|
|
903
|
+
promptHash,
|
|
904
|
+
policyVersionHash,
|
|
905
|
+
neurcodeVersion,
|
|
906
|
+
intent: normalizedIntent,
|
|
687
907
|
ticketRef,
|
|
688
908
|
contextHash: staticContext.hash,
|
|
689
|
-
repo,
|
|
690
909
|
},
|
|
691
910
|
response,
|
|
692
911
|
});
|
|
@@ -698,7 +917,10 @@ async function planCommand(intent, options) {
|
|
|
698
917
|
// Pre-Flight Snapshot: Capture current state of files that will be MODIFIED
|
|
699
918
|
// This ensures we have a baseline to revert to if AI destroys files
|
|
700
919
|
const modifyFiles = response.plan.files.filter(f => f.action === 'MODIFY');
|
|
701
|
-
if (
|
|
920
|
+
if (isReadOnlyAnalysis) {
|
|
921
|
+
console.log(chalk.dim('\n🔎 Analysis mode: skipping pre-flight file snapshots'));
|
|
922
|
+
}
|
|
923
|
+
else if (modifyFiles.length > 0) {
|
|
702
924
|
console.log(chalk.dim(`\n📸 Capturing pre-flight snapshots for ${modifyFiles.length} file(s)...`));
|
|
703
925
|
let snapshotsSaved = 0;
|
|
704
926
|
let snapshotsFailed = 0;
|
|
@@ -746,11 +968,16 @@ async function planCommand(intent, options) {
|
|
|
746
968
|
// Step 3: Post-Generation Hallucination Check (DEEP SCAN)
|
|
747
969
|
// Scan ALL plan content for phantom packages - not just summaries, but full proposed code
|
|
748
970
|
let hasHallucinations = false;
|
|
971
|
+
let skippedHallucinationScan = false;
|
|
749
972
|
const allHallucinations = [];
|
|
750
973
|
// Check tier for hallucination scanning (PRO feature)
|
|
751
974
|
const { getUserTier } = await Promise.resolve().then(() => __importStar(require('../utils/tier')));
|
|
752
975
|
const tier = await getUserTier();
|
|
753
|
-
if (
|
|
976
|
+
if (isReadOnlyAnalysis) {
|
|
977
|
+
skippedHallucinationScan = true;
|
|
978
|
+
console.log(chalk.dim('🔎 Analysis mode: skipping hallucination package scan'));
|
|
979
|
+
}
|
|
980
|
+
else if (tier === 'FREE') {
|
|
754
981
|
console.log(chalk.yellow('\n🛡️ Hallucination Shield is a PRO feature.'));
|
|
755
982
|
console.log(chalk.dim(' Upgrade at: https://www.neurcode.com/dashboard/purchase-plan\n'));
|
|
756
983
|
}
|
|
@@ -863,9 +1090,18 @@ async function planCommand(intent, options) {
|
|
|
863
1090
|
console.log('');
|
|
864
1091
|
}
|
|
865
1092
|
else {
|
|
866
|
-
|
|
1093
|
+
if (skippedHallucinationScan) {
|
|
1094
|
+
console.log(chalk.dim('🔎 Hallucination scan skipped (read-only analysis mode)'));
|
|
1095
|
+
}
|
|
1096
|
+
else {
|
|
1097
|
+
console.log(chalk.green('✅ No hallucinations detected'));
|
|
1098
|
+
}
|
|
867
1099
|
}
|
|
868
1100
|
// Display the plan (AFTER hallucination warnings)
|
|
1101
|
+
if (isReadOnlyAnalysis) {
|
|
1102
|
+
const writableTargets = response.plan.files.filter((file) => file.action !== 'BLOCK').length;
|
|
1103
|
+
console.log(chalk.dim(`🔎 Read-only query guidance: treat listed files as inspection targets (${writableTargets} target(s)).`));
|
|
1104
|
+
}
|
|
869
1105
|
displayPlan(response.plan);
|
|
870
1106
|
console.log(chalk.dim(`\nGenerated at: ${new Date(response.timestamp).toLocaleString()}`));
|
|
871
1107
|
// Display plan ID if available
|