proteum 2.2.8 → 2.3.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.
Files changed (51) hide show
  1. package/AGENTS.md +5 -3
  2. package/README.md +50 -12
  3. package/agents/project/AGENTS.md +47 -10
  4. package/agents/project/CODING_STYLE.md +5 -1
  5. package/agents/project/client/AGENTS.md +2 -0
  6. package/agents/project/diagnostics.md +8 -5
  7. package/agents/project/optimizations.md +1 -0
  8. package/agents/project/root/AGENTS.md +18 -10
  9. package/agents/project/tests/AGENTS.md +6 -1
  10. package/agents/project/tests/e2e/AGENTS.md +13 -0
  11. package/agents/project/tests/e2e/REAL_WORLD_JOURNEY_TESTS.md +192 -0
  12. package/cli/commands/check.ts +21 -3
  13. package/cli/commands/configure.ts +1 -0
  14. package/cli/commands/connect.ts +40 -4
  15. package/cli/commands/diagnose.ts +136 -5
  16. package/cli/commands/doctor.ts +24 -4
  17. package/cli/commands/explain.ts +105 -6
  18. package/cli/commands/mcp.ts +16 -0
  19. package/cli/commands/orient.ts +66 -3
  20. package/cli/commands/perf.ts +118 -13
  21. package/cli/commands/runtime.ts +151 -0
  22. package/cli/commands/trace.ts +116 -21
  23. package/cli/mcp/provider.ts +365 -0
  24. package/cli/mcp/stdio.ts +16 -0
  25. package/cli/presentation/commands.ts +79 -22
  26. package/cli/presentation/devSession.ts +2 -0
  27. package/cli/runtime/commands.ts +95 -12
  28. package/cli/utils/agentOutput.ts +46 -0
  29. package/cli/utils/agents.ts +225 -48
  30. package/common/dev/inspection.ts +30 -9
  31. package/common/dev/mcpPayloads.ts +736 -0
  32. package/common/dev/mcpServer.ts +254 -0
  33. package/docs/agent-routing.md +126 -0
  34. package/docs/dev-commands.md +2 -0
  35. package/docs/dev-sessions.md +2 -1
  36. package/docs/diagnostics.md +68 -23
  37. package/docs/mcp.md +149 -0
  38. package/docs/migrate-from-2.1.3.md +15 -5
  39. package/docs/request-tracing.md +12 -6
  40. package/eslint.js +220 -0
  41. package/package.json +2 -1
  42. package/server/app/devMcp.ts +159 -0
  43. package/server/services/router/http/cache.ts +116 -0
  44. package/server/services/router/http/index.ts +94 -35
  45. package/server/services/router/index.ts +8 -11
  46. package/tests/agents-utils.test.cjs +89 -11
  47. package/tests/dev-transpile-watch.test.cjs +117 -8
  48. package/tests/eslint-rules.test.cjs +110 -0
  49. package/tests/inspection.test.cjs +67 -0
  50. package/tests/mcp.test.cjs +127 -0
  51. package/tests/router-cache-config.test.cjs +74 -0
@@ -23,12 +23,14 @@ type TConfigureProjectAgentInstructionsArgs = {
23
23
  type TAgentInstructionDefinition = {
24
24
  projectPath: string;
25
25
  ensureParentDir?: boolean;
26
+ content?: 'router' | 'source';
26
27
  };
27
28
 
28
29
  type TEnsureInstructionFilesResult = {
29
30
  blocked: string[];
30
31
  created: string[];
31
32
  overwritten: string[];
33
+ removed: string[];
32
34
  skipped: string[];
33
35
  updated: string[];
34
36
  };
@@ -40,6 +42,7 @@ export type TConfigureProjectAgentInstructionsResult = {
40
42
  monorepoRoot?: string;
41
43
  mode: 'monorepo' | 'standalone';
42
44
  overwritten: string[];
45
+ removed: string[];
43
46
  skipped: string[];
44
47
  updated: string[];
45
48
  updatedGitignores: string[];
@@ -57,29 +60,40 @@ const managedInstructionSectionStart = '<!-- proteum-instructions:start -->';
57
60
  const managedInstructionSectionEnd = '<!-- proteum-instructions:end -->';
58
61
  const managedInstructionSectionIntro = 'This section is managed by `proteum configure agents`.';
59
62
 
60
- const sharedAppAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
61
- { projectPath: 'CODING_STYLE.md' },
62
- { projectPath: 'diagnostics.md' },
63
- { projectPath: 'optimizations.md' },
64
- { projectPath: path.join('client', 'AGENTS.md') },
65
- { projectPath: path.join('client', 'pages', 'AGENTS.md') },
66
- { projectPath: path.join('server', 'services', 'AGENTS.md') },
67
- { projectPath: path.join('server', 'routes', 'AGENTS.md') },
68
- { projectPath: path.join('tests', 'e2e', 'AGENTS.md') },
63
+ const sharedRootDocumentInstructionDefinitions: TAgentInstructionDefinition[] = [
64
+ { projectPath: 'CODING_STYLE.md', content: 'source' },
65
+ { projectPath: 'diagnostics.md', content: 'source' },
66
+ { projectPath: 'optimizations.md', content: 'source' },
67
+ ];
68
+
69
+ const sharedAppAreaAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
70
+ { projectPath: path.join('client', 'AGENTS.md'), content: 'source' },
71
+ { projectPath: path.join('client', 'pages', 'AGENTS.md'), content: 'source' },
72
+ { projectPath: path.join('server', 'services', 'AGENTS.md'), content: 'source' },
73
+ { projectPath: path.join('server', 'routes', 'AGENTS.md'), content: 'source' },
74
+ ];
75
+
76
+ const sharedE2eAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
77
+ { projectPath: path.join('tests', 'e2e', 'AGENTS.md'), ensureParentDir: true, content: 'source' },
78
+ { projectPath: path.join('tests', 'e2e', 'REAL_WORLD_JOURNEY_TESTS.md'), ensureParentDir: true, content: 'source' },
69
79
  ];
70
80
 
71
81
  const standaloneAppAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
72
- { projectPath: 'AGENTS.md' },
73
- ...sharedAppAgentInstructionDefinitions,
82
+ { projectPath: 'AGENTS.md', content: 'router' },
83
+ ...sharedRootDocumentInstructionDefinitions,
84
+ ...sharedAppAreaAgentInstructionDefinitions,
85
+ ...sharedE2eAgentInstructionDefinitions,
74
86
  ];
75
87
 
76
88
  const monorepoAppAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
77
- { projectPath: 'AGENTS.md' },
78
- ...sharedAppAgentInstructionDefinitions,
89
+ { projectPath: 'AGENTS.md', content: 'router' },
90
+ ...sharedAppAreaAgentInstructionDefinitions,
79
91
  ];
80
92
 
81
93
  const monorepoRootAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
82
- { projectPath: 'AGENTS.md' },
94
+ { projectPath: 'AGENTS.md', content: 'router' },
95
+ ...sharedRootDocumentInstructionDefinitions,
96
+ ...sharedE2eAgentInstructionDefinitions,
83
97
  ];
84
98
 
85
99
  const legacyProjectInstructionGitignoreBlockStart = '# Proteum-managed instruction symlinks';
@@ -111,6 +125,7 @@ export function configureProjectAgentInstructions({
111
125
  created: [],
112
126
  mode,
113
127
  overwritten: [],
128
+ removed: [],
114
129
  skipped: [],
115
130
  updated: [],
116
131
  updatedGitignores: [],
@@ -152,7 +167,31 @@ export function configureProjectAgentInstructions({
152
167
  );
153
168
  mergeInstructionResults(result, appFiles, normalizedAppRoot);
154
169
 
155
- if (!dryRun && removeInstructionGitignoreEntries({ rootDir: normalizedAppRoot, instructionDefinitions: appInstructions }))
170
+ if (mode === 'monorepo') {
171
+ const retiredAppRootFiles = removeManagedInstructionFiles(
172
+ normalizedAppRoot,
173
+ [...sharedRootDocumentInstructionDefinitions, ...sharedE2eAgentInstructionDefinitions],
174
+ '[agents]',
175
+ path.join(coreRoot, 'agents', 'project'),
176
+ {
177
+ dryRun,
178
+ },
179
+ );
180
+ mergeInstructionResults(result, retiredAppRootFiles, normalizedAppRoot);
181
+ }
182
+
183
+ const appGitignoreCleanupInstructions =
184
+ mode === 'monorepo'
185
+ ? [...appInstructions, ...sharedRootDocumentInstructionDefinitions, ...sharedE2eAgentInstructionDefinitions]
186
+ : appInstructions;
187
+
188
+ if (
189
+ !dryRun &&
190
+ removeInstructionGitignoreEntries({
191
+ rootDir: normalizedAppRoot,
192
+ instructionDefinitions: appGitignoreCleanupInstructions,
193
+ })
194
+ )
156
195
  result.updatedGitignores.push(path.join(normalizedAppRoot, '.gitignore'));
157
196
 
158
197
  return result;
@@ -253,6 +292,7 @@ function ensureInstructionFiles(
253
292
  blocked: [],
254
293
  created: [],
255
294
  overwritten: [],
295
+ removed: [],
256
296
  skipped: [],
257
297
  updated: [],
258
298
  };
@@ -268,13 +308,21 @@ function ensureInstructionFiles(
268
308
  continue;
269
309
  }
270
310
 
311
+ const instructionContent = renderProjectInstructionContent({
312
+ instructionDefinition,
313
+ managedSourceRoot,
314
+ managedSectionContent,
315
+ });
271
316
  const existingState = inspectExistingPath({
272
317
  managedSourceRoot,
273
318
  projectFilepath,
274
319
  });
275
320
 
276
321
  if (existingState.kind === 'file') {
277
- const nextContent = upsertManagedInstructionSection(existingState.content, managedSectionContent);
322
+ const nextContent =
323
+ instructionDefinition.content === 'source'
324
+ ? instructionContent
325
+ : upsertManagedInstructionSection(existingState.content, instructionContent);
278
326
  if (nextContent === existingState.content) {
279
327
  result.skipped.push(relativeProjectPath);
280
328
  continue;
@@ -289,7 +337,7 @@ function ensureInstructionFiles(
289
337
  if (existingState.kind === 'managed-different') {
290
338
  if (!dryRun) {
291
339
  fs.removeSync(projectFilepath);
292
- fs.writeFileSync(projectFilepath, managedSectionContent);
340
+ fs.writeFileSync(projectFilepath, instructionContent);
293
341
  }
294
342
  result.updated.push(relativeProjectPath);
295
343
  logVerbose(`${logPrefix} Updated ${relativeProjectPath}`);
@@ -305,14 +353,14 @@ function ensureInstructionFiles(
305
353
  if (existingState.kind === 'blocked') {
306
354
  if (!dryRun) {
307
355
  fs.removeSync(projectFilepath);
308
- fs.writeFileSync(projectFilepath, managedSectionContent);
356
+ fs.writeFileSync(projectFilepath, instructionContent);
309
357
  }
310
358
  result.overwritten.push(relativeProjectPath);
311
359
  logVerbose(`${logPrefix} Replaced ${relativeProjectPath}`);
312
360
  continue;
313
361
  }
314
362
 
315
- if (!dryRun) fs.writeFileSync(projectFilepath, managedSectionContent);
363
+ if (!dryRun) fs.writeFileSync(projectFilepath, instructionContent);
316
364
  result.created.push(relativeProjectPath);
317
365
  logVerbose(`${logPrefix} Created ${relativeProjectPath}`);
318
366
  }
@@ -320,6 +368,74 @@ function ensureInstructionFiles(
320
368
  return result;
321
369
  }
322
370
 
371
+ function removeManagedInstructionFiles(
372
+ rootDir: string,
373
+ instructionDefinitions: TAgentInstructionDefinition[],
374
+ logPrefix: string,
375
+ managedSourceRoot: string,
376
+ {
377
+ dryRun,
378
+ }: {
379
+ dryRun: boolean;
380
+ },
381
+ ): TEnsureInstructionFilesResult {
382
+ const result: TEnsureInstructionFilesResult = {
383
+ blocked: [],
384
+ created: [],
385
+ overwritten: [],
386
+ removed: [],
387
+ skipped: [],
388
+ updated: [],
389
+ };
390
+
391
+ for (const instructionDefinition of instructionDefinitions) {
392
+ const projectFilepath = path.join(rootDir, instructionDefinition.projectPath);
393
+ const projectParentDir = path.dirname(projectFilepath);
394
+ const relativeProjectPath = path.relative(rootDir, projectFilepath) || '.';
395
+
396
+ if (!fs.existsSync(projectParentDir)) continue;
397
+
398
+ const existingState = inspectExistingPath({
399
+ managedSourceRoot,
400
+ projectFilepath,
401
+ });
402
+
403
+ if (existingState.kind === 'missing') continue;
404
+
405
+ if (existingState.kind === 'managed-different') {
406
+ if (!dryRun) fs.removeSync(projectFilepath);
407
+ result.removed.push(relativeProjectPath);
408
+ logVerbose(`${logPrefix} Removed retired app-root ${relativeProjectPath}`);
409
+ continue;
410
+ }
411
+
412
+ if (existingState.kind === 'file') {
413
+ const retainedContent = removeManagedInstructionContent(existingState.content);
414
+
415
+ if (retainedContent === undefined) {
416
+ result.skipped.push(relativeProjectPath);
417
+ continue;
418
+ }
419
+
420
+ if (retainedContent.trim() === '') {
421
+ if (!dryRun) fs.removeSync(projectFilepath);
422
+ result.removed.push(relativeProjectPath);
423
+ logVerbose(`${logPrefix} Removed retired app-root ${relativeProjectPath}`);
424
+ continue;
425
+ }
426
+
427
+ if (!dryRun) fs.writeFileSync(projectFilepath, retainedContent);
428
+ result.updated.push(relativeProjectPath);
429
+ logVerbose(`${logPrefix} Removed retired managed section from ${relativeProjectPath}`);
430
+ continue;
431
+ }
432
+
433
+ result.skipped.push(relativeProjectPath);
434
+ }
435
+
436
+ return result;
437
+ }
438
+
323
439
  function inspectExistingPath({
324
440
  managedSourceRoot,
325
441
  projectFilepath,
@@ -377,57 +493,108 @@ function mergeInstructionResults(
377
493
  ) {
378
494
  result.created.push(...next.created.map((entry) => formatResultPath(rootDir, entry)));
379
495
  result.overwritten.push(...next.overwritten.map((entry) => formatResultPath(rootDir, entry)));
496
+ result.removed.push(...next.removed.map((entry) => formatResultPath(rootDir, entry)));
380
497
  result.updated.push(...next.updated.map((entry) => formatResultPath(rootDir, entry)));
381
498
  result.skipped.push(...next.skipped.map((entry) => formatResultPath(rootDir, entry)));
382
499
  result.blocked.push(...next.blocked.map((entry) => formatResultPath(rootDir, entry)));
383
500
  }
384
501
 
385
- function renderEmbeddedProjectInstructions({ coreRoot }: TProjectInstructionArgs) {
386
- const agentSourceRoot = path.join(coreRoot, 'agents', 'project');
387
- if (!fs.existsSync(agentSourceRoot)) throw new Error(`Missing project instruction source root: ${agentSourceRoot}`);
502
+ function renderProjectInstructionContent({
503
+ instructionDefinition,
504
+ managedSourceRoot,
505
+ managedSectionContent,
506
+ }: {
507
+ instructionDefinition: TAgentInstructionDefinition;
508
+ managedSourceRoot: string;
509
+ managedSectionContent: string;
510
+ }) {
511
+ if (instructionDefinition.content !== 'source') return managedSectionContent;
512
+
513
+ return renderSingleProjectInstruction({
514
+ managedSourceRoot,
515
+ projectPath: instructionDefinition.projectPath,
516
+ });
517
+ }
388
518
 
389
- const sourceFiles = collectMarkdownFiles(agentSourceRoot).sort((a, b) => a.relativePath.localeCompare(b.relativePath));
519
+ function renderSingleProjectInstruction({
520
+ managedSourceRoot,
521
+ projectPath,
522
+ }: {
523
+ managedSourceRoot: string;
524
+ projectPath: string;
525
+ }) {
526
+ const sourceFilepath = path.join(managedSourceRoot, projectPath);
527
+ if (!fs.existsSync(sourceFilepath)) throw new Error(`Missing project instruction source file: ${sourceFilepath}`);
528
+
529
+ const content = fs.readFileSync(sourceFilepath, 'utf8');
530
+ const demotedContent = demoteMarkdownHeadings(content).trim();
390
531
  const lines = [
391
532
  managedInstructionSectionHeader,
392
533
  managedInstructionSectionStart,
393
534
  '',
394
535
  managedInstructionSectionIntro,
395
536
  '',
537
+ `## Source: ${normalizeProjectPathForGitignore(projectPath)}`,
538
+ '',
396
539
  ];
397
540
 
398
- for (const sourceFile of sourceFiles) {
399
- const content = fs.readFileSync(sourceFile.filepath, 'utf8');
400
- const demotedContent = demoteMarkdownHeadings(content).trim();
401
-
402
- lines.push(`## Source: ${sourceFile.relativePath}`, '');
403
- if (demotedContent) lines.push(demotedContent, '');
404
- }
405
-
541
+ if (demotedContent) lines.push(demotedContent, '');
406
542
  lines.push(managedInstructionSectionEnd, '');
407
543
 
408
544
  return lines.join('\n');
409
545
  }
410
546
 
411
- function collectMarkdownFiles(rootDir: string, currentDir = rootDir): { filepath: string; relativePath: string }[] {
412
- const files: { filepath: string; relativePath: string }[] = [];
413
-
414
- for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {
415
- const filepath = path.join(currentDir, entry.name);
416
-
417
- if (entry.isDirectory()) {
418
- files.push(...collectMarkdownFiles(rootDir, filepath));
419
- continue;
420
- }
547
+ function renderEmbeddedProjectInstructions({ coreRoot }: TProjectInstructionArgs) {
548
+ const agentSourceRoot = path.join(coreRoot, 'agents', 'project');
549
+ if (!fs.existsSync(agentSourceRoot)) throw new Error(`Missing project instruction source root: ${agentSourceRoot}`);
421
550
 
422
- if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
551
+ const lines = [
552
+ managedInstructionSectionHeader,
553
+ managedInstructionSectionStart,
554
+ '',
555
+ managedInstructionSectionIntro,
556
+ '',
557
+ '## Agent Routing Contract',
558
+ '',
559
+ 'Proteum CLI commands are optimized for agents. Do not load the whole instruction corpus up front.',
560
+ '',
561
+ '1. Start ambiguous, generated, connected, route, controller, file, or error work with `npx proteum orient <query>`.',
562
+ '2. Read only the files returned in `mustRead` plus the conditional docs that match the current task.',
563
+ '3. Use `npx proteum runtime status` before starting a dev server, so an existing tracked session can be reused.',
564
+ '4. Use `npx proteum diagnose <target>` for request-time issues before raw trace, perf, browser, or broad source search.',
565
+ '5. Use `--full`, `--manifest`, or `--events` only when a compact CLI response says the omitted detail is needed.',
566
+ '',
567
+ '## Always-On Safety',
568
+ '',
569
+ '- Never edit generated files under `.proteum`.',
570
+ '- Never create or edit Prisma migration files manually.',
571
+ '- Never run schema-mutating SQL such as `ALTER TABLE`, `CREATE TABLE`, `DROP TABLE`, or `CREATE INDEX`.',
572
+ '- If `schema.prisma` changes, ask the user to run `npx prisma migrate dev --config ./prisma.config.ts --name <migration name>` and wait for `continue` before validation.',
573
+ '- Do not run `git restore` or `git reset`.',
574
+ '- Keep `proteum dev` sessions tracked with explicit session files and do not replace another live session.',
575
+ '',
576
+ '## Routing Table',
577
+ '',
578
+ '- Raw errors, failing requests, traces, perf, or reproduction: read `diagnostics.md`.',
579
+ '- Implementation edits: read `CODING_STYLE.md` before editing.',
580
+ '- Client files or pages: read `client/AGENTS.md`; for page route/data/render work also read `client/pages/AGENTS.md`.',
581
+ '- Server services: read `server/services/AGENTS.md`.',
582
+ '- Manual server routes: read `server/routes/AGENTS.md`.',
583
+ '- E2E work: read `tests/e2e/AGENTS.md` and `tests/e2e/REAL_WORLD_JOURNEY_TESTS.md`.',
584
+ '- Package, runtime, build, or client-performance decisions: read `optimizations.md` after implementation or when explicitly optimizing.',
585
+ '',
586
+ '## Canonical Source Map',
587
+ '',
588
+ `- Root contract fallback: ${normalizeProjectPathForGitignore(path.join(coreRoot, 'agents', 'project', 'AGENTS.md'))}`,
589
+ `- Diagnostics fallback: ${normalizeProjectPathForGitignore(path.join(coreRoot, 'agents', 'project', 'diagnostics.md'))}`,
590
+ `- Optimization fallback: ${normalizeProjectPathForGitignore(path.join(coreRoot, 'agents', 'project', 'optimizations.md'))}`,
591
+ `- Coding style fallback: ${normalizeProjectPathForGitignore(path.join(coreRoot, 'agents', 'project', 'CODING_STYLE.md'))}`,
592
+ '',
593
+ ];
423
594
 
424
- files.push({
425
- filepath,
426
- relativePath: normalizeProjectPathForGitignore(path.relative(rootDir, filepath)),
427
- });
428
- }
595
+ lines.push(managedInstructionSectionEnd, '');
429
596
 
430
- return files;
597
+ return lines.join('\n');
431
598
  }
432
599
 
433
600
  function demoteMarkdownHeadings(content: string) {
@@ -472,6 +639,16 @@ function upsertManagedInstructionSection(content: string, managedSectionContent:
472
639
  return joinMarkdownSections([before, managedSectionContent, after]);
473
640
  }
474
641
 
642
+ function removeManagedInstructionContent(content: string) {
643
+ const managedRange = findManagedInstructionSectionRange(content) || findLegacyManagedInstructionStubRange(content);
644
+ if (!managedRange) return undefined;
645
+
646
+ const before = content.slice(0, managedRange.start);
647
+ const after = content.slice(managedRange.end);
648
+
649
+ return joinMarkdownSections([before, after]);
650
+ }
651
+
475
652
  function findManagedInstructionSectionRange(content: string) {
476
653
  const markerStartIndex = content.indexOf(managedInstructionSectionStart);
477
654
  if (markerStartIndex === -1) return undefined;
@@ -713,12 +713,25 @@ const resolveGuidanceFile = ({
713
713
  fallbackFilepath: string;
714
714
  relativePath: string;
715
715
  }) => {
716
- const localFilepath = joinPath(appRoot, relativePath);
717
- if (fileExists(localFilepath)) return { filepath: localFilepath, warning: undefined as string | undefined };
716
+ const resolvedAppRoot = resolvePath(appRoot);
717
+ const repoRoot = findRepoRoot(resolvedAppRoot);
718
+ let currentRoot = resolvedAppRoot;
719
+
720
+ while (true) {
721
+ const localFilepath = joinPath(currentRoot, relativePath);
722
+ if (fileExists(localFilepath)) return { filepath: localFilepath, warning: undefined as string | undefined };
723
+
724
+ if (currentRoot === repoRoot) break;
725
+
726
+ const parentRoot = dirnamePath(currentRoot);
727
+ if (parentRoot === currentRoot) break;
728
+
729
+ currentRoot = parentRoot;
730
+ }
718
731
 
719
732
  return {
720
733
  filepath: fallbackFilepath,
721
- warning: `Missing ${relativePath} in ${appRoot}; using ${fallbackFilepath}.`,
734
+ warning: `Missing ${relativePath} in ${appRoot} and its repository ancestors; using ${fallbackFilepath}.`,
722
735
  };
723
736
  };
724
737
 
@@ -1143,12 +1156,20 @@ export const explainOwner = (manifest: TProteumManifest, query: string): TExplai
1143
1156
  const normalizedQuery = normalizeText(query);
1144
1157
  if (!normalizedQuery) return { matches: [], normalizedQuery, query };
1145
1158
 
1146
- const matches = buildManifestEntries(manifest)
1147
- .map((entry) => {
1148
- const { score, matchedOn } = scoreOwnerMatch(query, entry);
1149
- return score > 0 ? toOwnerMatch(entry, score, matchedOn) : undefined;
1150
- })
1151
- .filter((match): match is TExplainOwnerMatch => match !== undefined)
1159
+ const entries = buildManifestEntries(manifest);
1160
+ const scoredMatches =
1161
+ normalizedQuery === '/'
1162
+ ? entries
1163
+ .filter((entry) => (entry.kind === 'route' || entry.kind === 'controller') && normalizeText(entry.label) === '/')
1164
+ .map((entry) => toOwnerMatch(entry, 200, ['/']))
1165
+ : entries
1166
+ .map((entry) => {
1167
+ const { score, matchedOn } = scoreOwnerMatch(query, entry);
1168
+ return score > 0 ? toOwnerMatch(entry, score, matchedOn) : undefined;
1169
+ })
1170
+ .filter((match): match is TExplainOwnerMatch => match !== undefined);
1171
+
1172
+ const matches = scoredMatches
1152
1173
  .sort((left, right) => right.score - left.score || left.kind.localeCompare(right.kind) || left.label.localeCompare(right.label))
1153
1174
  .slice(0, 12);
1154
1175