@nerviq/cli 1.13.0 → 1.15.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nerviq/cli",
3
- "version": "1.13.0",
3
+ "version": "1.15.0",
4
4
  "description": "The intelligent nervous system for AI coding agents — 2,441 checks (8 platforms × ~300 governance rules), 10 languages, 62 domain packs. Audit, align, and amplify.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -379,6 +379,7 @@ function buildTopNextActions(failed, limit = 5, outcomeSummaryByKey = {}, option
379
379
  sourceUrl,
380
380
  module: CATEGORY_MODULES[category] || category,
381
381
  fix,
382
+ remediation_command: getRemediationCommand(key, category, options.platform),
382
383
  priorityScore,
383
384
  why: ACTION_RATIONALES[key] || fix,
384
385
  risk: riskFromImpact(impact),
@@ -399,6 +400,51 @@ function buildTopNextActions(failed, limit = 5, outcomeSummaryByKey = {}, option
399
400
  });
400
401
  }
401
402
 
403
+ /**
404
+ * Map check keys/categories to a shell command an agent can run to fix the issue.
405
+ * Returns null when no automated fix is available.
406
+ */
407
+ function getRemediationCommand(key, category, platform) {
408
+ const plat = platform || 'claude';
409
+
410
+ // Key-specific remediation commands
411
+ const KEY_COMMANDS = {
412
+ claudeMd: 'npx @nerviq/cli setup',
413
+ agentsMd: 'npx @nerviq/cli setup --platform codex',
414
+ geminiMd: 'npx @nerviq/cli setup --platform gemini',
415
+ copilotInstructions: 'npx @nerviq/cli setup --platform copilot',
416
+ cursorRules: 'npx @nerviq/cli setup --platform cursor',
417
+ windsurfRules: 'npx @nerviq/cli setup --platform windsurf',
418
+ aiderConfig: 'npx @nerviq/cli setup --platform aider',
419
+ opencodeConfig: 'npx @nerviq/cli setup --platform opencode',
420
+ settingsPermissions: 'npx @nerviq/cli plan --only permissions',
421
+ permissionDeny: 'npx @nerviq/cli plan --only permissions',
422
+ noBypassPermissions: 'npx @nerviq/cli plan --only permissions',
423
+ secretsProtection: 'npx @nerviq/cli plan --only permissions',
424
+ verificationLoop: `npx @nerviq/cli augment --platform ${plat}`,
425
+ lintCommand: `npx @nerviq/cli augment --platform ${plat}`,
426
+ testCommand: `npx @nerviq/cli augment --platform ${plat}`,
427
+ buildCommand: `npx @nerviq/cli augment --platform ${plat}`,
428
+ hookExists: 'npx @nerviq/cli plan --only hooks',
429
+ preCommitHook: 'npx @nerviq/cli plan --only hooks',
430
+ commandsExist: 'npx @nerviq/cli plan --only commands',
431
+ mcpServers: 'npx @nerviq/cli plan --mcp-pack context7',
432
+ };
433
+
434
+ if (KEY_COMMANDS[key]) return KEY_COMMANDS[key];
435
+
436
+ // Category-level fallback
437
+ const CATEGORY_COMMANDS = {
438
+ memory: `npx @nerviq/cli setup --platform ${plat}`,
439
+ security: `npx @nerviq/cli plan --only permissions --platform ${plat}`,
440
+ automation: `npx @nerviq/cli plan --only hooks --platform ${plat}`,
441
+ workflow: `npx @nerviq/cli plan --only commands --platform ${plat}`,
442
+ tools: `npx @nerviq/cli plan --mcp-pack context7 --platform ${plat}`,
443
+ };
444
+
445
+ return CATEGORY_COMMANDS[category] || `npx @nerviq/cli augment --platform ${plat}`;
446
+ }
447
+
402
448
  function getNextScoreMilestone(score) {
403
449
  return SCORE_MILESTONES.find((milestone) => score < milestone) || null;
404
450
  }
package/src/audit.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Audit engine - evaluates project against NERVIQ technique database.
3
3
  */
4
4
 
5
- const { TECHNIQUES: CLAUDE_TECHNIQUES, STACKS, STACK_CATEGORY_DETECTORS } = require('./techniques');
5
+ const { TECHNIQUES: CLAUDE_TECHNIQUES, STACKS, STACK_CATEGORY_DETECTORS } = require('./techniques');
6
6
  const { ProjectContext } = require('./context');
7
7
  const { CODEX_TECHNIQUES } = require('./codex/techniques');
8
8
  const { detectCodexDomainPacks } = require('./codex/domain-packs');
@@ -23,28 +23,28 @@ const { AiderProjectContext } = require('./aider/context');
23
23
  const { OPENCODE_TECHNIQUES } = require('./opencode/techniques');
24
24
  const { OpenCodeProjectContext } = require('./opencode/context');
25
25
  const { getBadgeMarkdown } = require('./badge');
26
- const { sendInsights, getLocalInsights } = require('./insights');
27
- const { getRecommendationOutcomeSummary } = require('./activity');
28
- const { getFeedbackSummary } = require('./feedback');
29
- const { formatSarif } = require('./formatters/sarif');
30
- const { formatOtelMetrics } = require('./formatters/otel');
31
- const { collectAuditTerminology, formatTerminologyLines } = require('./terminology');
32
- const { loadPlugins, mergePluginChecks } = require('./plugins');
33
- const { detectDeprecationWarnings } = require('./deprecation');
34
- const { buildWorkspaceHint, formatCount, guardSkippedInstructionFiles, inspectInstructionFiles } = require('./audit/instruction-files');
35
- const {
36
- WEIGHTS,
37
- buildScoreCoaching,
38
- buildTopNextActions,
39
- confidenceLabel,
40
- computeCategoryScores,
41
- getFpFeedbackMultiplier,
42
- getQuickWins,
43
- getRecommendationPriorityScore,
44
- inferSuggestedNextCommand,
45
- } = require('./audit/recommendations');
46
- const { version: packageVersion } = require('../package.json');
47
- const { t } = require('./i18n');
26
+ const { sendInsights, getLocalInsights } = require('./insights');
27
+ const { getRecommendationOutcomeSummary } = require('./activity');
28
+ const { getFeedbackSummary } = require('./feedback');
29
+ const { formatSarif } = require('./formatters/sarif');
30
+ const { formatOtelMetrics } = require('./formatters/otel');
31
+ const { collectAuditTerminology, formatTerminologyLines } = require('./terminology');
32
+ const { loadPlugins, mergePluginChecks } = require('./plugins');
33
+ const { detectDeprecationWarnings } = require('./deprecation');
34
+ const { buildWorkspaceHint, formatCount, guardSkippedInstructionFiles, inspectInstructionFiles } = require('./audit/instruction-files');
35
+ const {
36
+ WEIGHTS,
37
+ buildScoreCoaching,
38
+ buildTopNextActions,
39
+ confidenceLabel,
40
+ computeCategoryScores,
41
+ getFpFeedbackMultiplier,
42
+ getQuickWins,
43
+ getRecommendationPriorityScore,
44
+ inferSuggestedNextCommand,
45
+ } = require('./audit/recommendations');
46
+ const { version: packageVersion } = require('../package.json');
47
+ const { t } = require('./i18n');
48
48
 
49
49
  const COLORS = {
50
50
  reset: '\x1b[0m',
@@ -68,10 +68,10 @@ function progressBar(score, max = 100, width = 20) {
68
68
  return colorize('█'.repeat(filled), color) + colorize('░'.repeat(empty), 'dim');
69
69
  }
70
70
 
71
- function formatLocation(file, line) {
72
- if (!file) return null;
73
- return line ? `${file}:${line}` : file;
74
- }
71
+ function formatLocation(file, line) {
72
+ if (!file) return null;
73
+ return line ? `${file}:${line}` : file;
74
+ }
75
75
 
76
76
  function getAuditSpec(platform = 'claude') {
77
77
  if (platform === 'codex') {
@@ -153,7 +153,7 @@ function getAuditSpec(platform = 'claude') {
153
153
  };
154
154
  }
155
155
 
156
- function getPlatformScopeNote(spec, ctx) {
156
+ function getPlatformScopeNote(spec, ctx) {
157
157
  if (spec.platform !== 'codex') {
158
158
  return null;
159
159
  }
@@ -242,7 +242,7 @@ function printLiteAudit(result, dir) {
242
242
  console.log(colorize(` Found: ${result.detectedConfigFiles.join(', ')}`, 'dim'));
243
243
  }
244
244
  console.log('');
245
- console.log(` ${t('audit.score', { score: colorize(`${result.score}/100`, 'bold'), passed: result.passed, total: result.passed + result.failed })}`);
245
+ console.log(` ${t('audit.score', { score: colorize(`${result.score}/100`, 'bold'), passed: result.passed, total: result.passed + result.failed })}`);
246
246
 
247
247
  // Score explanation line (lite mode only)
248
248
  const _critCount = (result.results || []).filter(r => r.passed === false && r.impact === 'critical').length;
@@ -265,14 +265,14 @@ function printLiteAudit(result, dir) {
265
265
  scoreExplanation = t('audit.basic', { category: weakestCategory });
266
266
  } else {
267
267
  scoreExplanation = t('audit.early');
268
- }
269
- console.log(colorize(` ${scoreExplanation}`, 'dim'));
270
- if (result.scoreCoaching) {
271
- console.log(colorize(` Milestone: ${result.scoreCoaching.summary}`, 'magenta'));
272
- }
273
- console.log(colorize(' Score type: live repo audit (current files only, not snapshot history or benchmark projection).', 'dim'));
274
-
275
- if (result.platformScopeNote) {
268
+ }
269
+ console.log(colorize(` ${scoreExplanation}`, 'dim'));
270
+ if (result.scoreCoaching) {
271
+ console.log(colorize(` Milestone: ${result.scoreCoaching.summary}`, 'magenta'));
272
+ }
273
+ console.log(colorize(' Score type: live repo audit (current files only, not snapshot history or benchmark projection).', 'dim'));
274
+
275
+ if (result.platformScopeNote) {
276
276
  console.log(colorize(` Scope: ${result.platformScopeNote.message}`, 'dim'));
277
277
  }
278
278
  if (result.workspaceHint && result.workspaceHint.workspaces.length > 0) {
@@ -284,11 +284,11 @@ function printLiteAudit(result, dir) {
284
284
  console.log(colorize(` - ${item.title}: ${item.message}`, 'dim'));
285
285
  });
286
286
  }
287
- if (result.largeInstructionFiles && result.largeInstructionFiles.length > 0) {
288
- result.largeInstructionFiles.slice(0, 2).forEach((item) => {
289
- console.log(colorize(` Large file: ${item.file} (~${formatCount(item.tokenCount)} tokens)`, 'yellow'));
290
- });
291
- }
287
+ if (result.largeInstructionFiles && result.largeInstructionFiles.length > 0) {
288
+ result.largeInstructionFiles.slice(0, 2).forEach((item) => {
289
+ console.log(colorize(` Large file: ${item.file} (~${formatCount(item.tokenCount)} tokens)`, 'yellow'));
290
+ });
291
+ }
292
292
  console.log('');
293
293
 
294
294
  if (result.failed === 0) {
@@ -320,23 +320,23 @@ function printLiteAudit(result, dir) {
320
320
  console.log('');
321
321
  let usagePatterns;
322
322
  try { usagePatterns = require('./usage-patterns'); } catch { usagePatterns = null; }
323
- result.liteSummary.topNextActions.forEach((item, index) => {
324
- const tier = item.impact === 'critical' ? '🔴' : item.impact === 'high' ? '🟡' : '🔵';
325
- const suppressed = usagePatterns && usagePatterns.getPriorityAdjustment(dir, item.key) === 'suppress';
326
- const suffix = suppressed ? colorize(' (suppressed)', 'dim') : '';
327
- console.log(` ${index + 1}. ${tier} ${colorize(item.name, 'bold')}${suffix}`);
328
- console.log(colorize(` ${item.fix}`, 'dim'));
329
- });
330
- console.log('');
331
- const liteTerminology = formatTerminologyLines(collectAuditTerminology(result));
332
- if (liteTerminology.length > 0) {
333
- liteTerminology.forEach((line) => {
334
- const color = line.startsWith(' Terms used here:') ? 'blue' : 'dim';
335
- console.log(colorize(line, color));
336
- });
337
- console.log('');
338
- }
339
- console.log(` Ready? Run: ${colorize(result.suggestedNextCommand, 'bold')}`);
323
+ result.liteSummary.topNextActions.forEach((item, index) => {
324
+ const tier = item.impact === 'critical' ? '🔴' : item.impact === 'high' ? '🟡' : '🔵';
325
+ const suppressed = usagePatterns && usagePatterns.getPriorityAdjustment(dir, item.key) === 'suppress';
326
+ const suffix = suppressed ? colorize(' (suppressed)', 'dim') : '';
327
+ console.log(` ${index + 1}. ${tier} ${colorize(item.name, 'bold')}${suffix}`);
328
+ console.log(colorize(` ${item.fix}`, 'dim'));
329
+ });
330
+ console.log('');
331
+ const liteTerminology = formatTerminologyLines(collectAuditTerminology(result));
332
+ if (liteTerminology.length > 0) {
333
+ liteTerminology.forEach((line) => {
334
+ const color = line.startsWith(' Terms used here:') ? 'blue' : 'dim';
335
+ console.log(colorize(line, color));
336
+ });
337
+ console.log('');
338
+ }
339
+ console.log(` Ready? Run: ${colorize(result.suggestedNextCommand, 'bold')}`);
340
340
  if (result.platform === 'codex') {
341
341
  console.log(colorize(' Note: Codex now supports no-write advisory flows via augment and suggest-only before setup/apply.', 'dim'));
342
342
  }
@@ -387,7 +387,7 @@ async function audit(options) {
387
387
  'supply-chain', 'api-versioning', 'caching', 'rate-limiting', 'feature-flags',
388
388
  'docs-quality', 'monorepo', 'performance-budget', 'realtime', 'graphql',
389
389
  'testing-strategy', 'code-quality', 'api-design', 'database', 'authentication',
390
- 'monitoring', 'dependency-management', 'cost-optimization',
390
+ 'monitoring', 'dependency-management', 'cost-optimization', 'devops',
391
391
  ]);
392
392
  const includeGenericQuality = options.verbose;
393
393
 
@@ -425,12 +425,12 @@ async function audit(options) {
425
425
  key: 'largeInstructionFile',
426
426
  id: null,
427
427
  name: 'Large instruction file warning',
428
- category: 'performance',
429
- impact: 'medium',
430
- rating: null,
431
- fix: 'Split oversized instruction files so they stay under ~12,000 tokens, and keep any single instruction file below ~240,000 tokens.',
432
- sourceUrl: null,
433
- confidence: 'high',
428
+ category: 'performance',
429
+ impact: 'medium',
430
+ rating: null,
431
+ fix: 'Split oversized instruction files so they stay under ~12,000 tokens, and keep any single instruction file below ~240,000 tokens.',
432
+ sourceUrl: null,
433
+ confidence: 'high',
434
434
  file: largeInstructionFiles[0].file,
435
435
  line: null,
436
436
  passed: null,
@@ -498,13 +498,13 @@ async function audit(options) {
498
498
  ...largeInstructionFiles.map((item) => ({
499
499
  kind: 'large-instruction-file',
500
500
  severity: item.severity,
501
- message: item.message,
502
- file: item.file,
503
- lineCount: item.lineCount,
504
- byteCount: item.byteCount,
505
- tokenCount: item.tokenCount,
506
- skipped: item.skipped,
507
- })),
501
+ message: item.message,
502
+ file: item.file,
503
+ lineCount: item.lineCount,
504
+ byteCount: item.byteCount,
505
+ tokenCount: item.tokenCount,
506
+ skipped: item.skipped,
507
+ })),
508
508
  ...deprecationWarnings.map((item) => ({
509
509
  kind: 'deprecated-feature',
510
510
  severity: 'warning',
@@ -514,7 +514,7 @@ async function audit(options) {
514
514
  const recommendedDomainPacks = spec.platform === 'codex'
515
515
  ? detectCodexDomainPacks(ctx, stacks, getCodexDomainPackSignals(ctx))
516
516
  : [];
517
- const result = {
517
+ const result = {
518
518
  platform: spec.platform,
519
519
  platformLabel: spec.platformLabel,
520
520
  platformVersion: spec.platformVersion,
@@ -537,18 +537,18 @@ async function audit(options) {
537
537
  deprecatedReason: r.deprecatedReason || null,
538
538
  sunsetDate: r.sunsetDate || null,
539
539
  })),
540
- categoryScores,
541
- scoreCoaching: buildScoreCoaching({
542
- score,
543
- earnedPoints: earnedScore,
544
- maxPoints: maxScore,
545
- failed,
546
- outcomeSummaryByKey: outcomeSummary.byKey,
547
- platform: spec.platform,
548
- fpFeedbackByKey: fpFeedback.byKey,
549
- }),
550
- quickWins: quickWins.map(({ key, name, impact, fix, category, sourceUrl }) => ({ key, name, impact, category, fix, sourceUrl })),
551
- topNextActions,
540
+ categoryScores,
541
+ scoreCoaching: buildScoreCoaching({
542
+ score,
543
+ earnedPoints: earnedScore,
544
+ maxPoints: maxScore,
545
+ failed,
546
+ outcomeSummaryByKey: outcomeSummary.byKey,
547
+ platform: spec.platform,
548
+ fpFeedbackByKey: fpFeedback.byKey,
549
+ }),
550
+ quickWins: quickWins.map(({ key, name, impact, fix, category, sourceUrl }) => ({ key, name, impact, category, fix, sourceUrl })),
551
+ topNextActions,
552
552
  recommendationOutcomes: {
553
553
  totalEntries: outcomeSummary.totalEntries,
554
554
  keysTracked: outcomeSummary.keys,
@@ -578,12 +578,12 @@ async function audit(options) {
578
578
  result.detectedConfigFiles = configFiles;
579
579
 
580
580
  result.suggestedNextCommand = inferSuggestedNextCommand(result);
581
- result.liteSummary = {
582
- topNextActions: topNextActions.slice(0, 3),
583
- nextCommand: result.suggestedNextCommand,
584
- platformCaveats: platformCaveats.slice(0, 2),
585
- scoreCoaching: result.scoreCoaching,
586
- };
581
+ result.liteSummary = {
582
+ topNextActions: topNextActions.slice(0, 3),
583
+ nextCommand: result.suggestedNextCommand,
584
+ platformCaveats: platformCaveats.slice(0, 2),
585
+ scoreCoaching: result.scoreCoaching,
586
+ };
587
587
 
588
588
  // Silent mode: skip all output, just return result
589
589
  if (silent) {
@@ -642,13 +642,13 @@ async function audit(options) {
642
642
  console.log('');
643
643
  }
644
644
 
645
- if (largeInstructionFiles.length > 0) {
646
- console.log(colorize(' Large instruction files', 'yellow'));
647
- for (const item of largeInstructionFiles) {
648
- const sizeKb = Number.isFinite(item.byteCount) ? Math.round(item.byteCount / 1024) : '?';
649
- console.log(colorize(` ${item.file} (~${formatCount(item.tokenCount)} tokens, ${item.lineCount || '?'} lines, ${sizeKb}KB)`, 'bold'));
650
- console.log(colorize(` → ${item.message}`, 'dim'));
651
- }
645
+ if (largeInstructionFiles.length > 0) {
646
+ console.log(colorize(' Large instruction files', 'yellow'));
647
+ for (const item of largeInstructionFiles) {
648
+ const sizeKb = Number.isFinite(item.byteCount) ? Math.round(item.byteCount / 1024) : '?';
649
+ console.log(colorize(` ${item.file} (~${formatCount(item.tokenCount)} tokens, ${item.lineCount || '?'} lines, ${sizeKb}KB)`, 'bold'));
650
+ console.log(colorize(` → ${item.message}`, 'dim'));
651
+ }
652
652
  console.log('');
653
653
  }
654
654
 
@@ -678,18 +678,18 @@ async function audit(options) {
678
678
  console.log('');
679
679
 
680
680
  // Score
681
- console.log(` ${progressBar(score)} ${colorize(`${score}/100`, 'bold')}`);
682
- if (isScaffolded && scaffoldedPassed.length > 0) {
683
- console.log(colorize(` Organic: ${organicScore}/100 (without nerviq generated files)`, 'dim'));
684
- }
685
- if (result.scoreCoaching) {
686
- const fastestPath = result.scoreCoaching.recommendedNames.slice(0, 3).join(', ');
687
- console.log(colorize(` Milestone: ${result.scoreCoaching.summary}`, 'magenta'));
688
- if (fastestPath) {
689
- console.log(colorize(` Fastest path: ${fastestPath}`, 'dim'));
690
- }
691
- }
692
- console.log('');
681
+ console.log(` ${progressBar(score)} ${colorize(`${score}/100`, 'bold')}`);
682
+ if (isScaffolded && scaffoldedPassed.length > 0) {
683
+ console.log(colorize(` Organic: ${organicScore}/100 (without nerviq generated files)`, 'dim'));
684
+ }
685
+ if (result.scoreCoaching) {
686
+ const fastestPath = result.scoreCoaching.recommendedNames.slice(0, 3).join(', ');
687
+ console.log(colorize(` Milestone: ${result.scoreCoaching.summary}`, 'magenta'));
688
+ if (fastestPath) {
689
+ console.log(colorize(` Fastest path: ${fastestPath}`, 'dim'));
690
+ }
691
+ }
692
+ console.log('');
693
693
 
694
694
  // Passed
695
695
  if (passed.length > 0) {
@@ -755,8 +755,8 @@ async function audit(options) {
755
755
  }
756
756
 
757
757
  // Top next actions
758
- if (topNextActions.length > 0) {
759
- console.log(colorize(' ⚡ Top 5 Next Actions', 'magenta'));
758
+ if (topNextActions.length > 0) {
759
+ console.log(colorize(' ⚡ Top 5 Next Actions', 'magenta'));
760
760
  for (let i = 0; i < topNextActions.length; i++) {
761
761
  const item = topNextActions[i];
762
762
  console.log(` ${i + 1}. ${colorize(item.name, 'bold')}`);
@@ -772,20 +772,20 @@ async function audit(options) {
772
772
  console.log(colorize(` Feedback: accepted ${item.feedback.accepted}, rejected ${item.feedback.rejected}, positive ${item.feedback.positive}, negative ${item.feedback.negative}${avgDelta}`, 'dim'));
773
773
  }
774
774
  console.log(colorize(` Fix: ${item.fix}`, 'dim'));
775
- }
776
- console.log('');
777
- }
778
-
779
- const terminology = formatTerminologyLines(collectAuditTerminology(result));
780
- if (terminology.length > 0) {
781
- terminology.forEach((line) => {
782
- const color = line.startsWith(' Terms used here:') ? 'blue' : 'dim';
783
- console.log(colorize(line, color));
784
- });
785
- console.log('');
786
- }
787
-
788
- // Summary
775
+ }
776
+ console.log('');
777
+ }
778
+
779
+ const terminology = formatTerminologyLines(collectAuditTerminology(result));
780
+ if (terminology.length > 0) {
781
+ terminology.forEach((line) => {
782
+ const color = line.startsWith(' Terms used here:') ? 'blue' : 'dim';
783
+ console.log(colorize(line, color));
784
+ });
785
+ console.log('');
786
+ }
787
+
788
+ // Summary
789
789
  console.log(colorize(' ─────────────────────────────────────', 'dim'));
790
790
  const deprecatedNote = deprecated.length > 0 ? colorize(`, ${deprecated.length} deprecated`, 'dim') : '';
791
791
  console.log(` ${colorize(`${passed.length}/${applicable.length}`, 'bold')} checks passing${skipped.length > 0 ? colorize(` (${skipped.length} not applicable${deprecatedNote})`, 'dim') : (deprecatedNote ? colorize(` (${deprecatedNote})`, 'dim') : '')}`);
package/src/context.js CHANGED
@@ -74,10 +74,32 @@ class ProjectContext {
74
74
 
75
75
  /**
76
76
  * Return the contents of the project's CLAUDE.md (root or .claude/ location).
77
+ * If CLAUDE.md contains only a reference to another file (e.g., "AGENTS.md"),
78
+ * follows that reference and returns the referenced file's content appended.
77
79
  * @returns {string|null} File content or null if not found.
78
80
  */
79
81
  claudeMdContent() {
80
- return this.fileContent('CLAUDE.md') || this.fileContent('.claude/CLAUDE.md');
82
+ const raw = this.fileContent('CLAUDE.md') || this.fileContent('.claude/CLAUDE.md');
83
+ if (!raw) return null;
84
+
85
+ // If the file is very short and looks like a file reference, follow it.
86
+ // Pattern: a single line that is just a filename (e.g., "AGENTS.md" or "docs/CODING.md")
87
+ const trimmed = raw.trim();
88
+ if (trimmed.length < 200 && /^[a-zA-Z0-9_./-]+\.(md|txt|rst)$/m.test(trimmed)) {
89
+ const lines = trimmed.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
90
+ let combined = raw;
91
+ for (const line of lines) {
92
+ if (/^[a-zA-Z0-9_./-]+\.(md|txt|rst)$/.test(line)) {
93
+ const referenced = this.fileContent(line);
94
+ if (referenced) {
95
+ combined += '\n' + referenced;
96
+ }
97
+ }
98
+ }
99
+ return combined;
100
+ }
101
+
102
+ return raw;
81
103
  }
82
104
 
83
105
  /**