proteum 2.2.9 → 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 (45) hide show
  1. package/AGENTS.md +3 -2
  2. package/README.md +49 -11
  3. package/agents/project/AGENTS.md +43 -5
  4. package/agents/project/diagnostics.md +6 -2
  5. package/agents/project/optimizations.md +1 -0
  6. package/agents/project/root/AGENTS.md +14 -5
  7. package/agents/project/tests/AGENTS.md +6 -0
  8. package/agents/project/tests/e2e/AGENTS.md +13 -0
  9. package/agents/project/tests/e2e/REAL_WORLD_JOURNEY_TESTS.md +192 -0
  10. package/cli/commands/connect.ts +40 -4
  11. package/cli/commands/diagnose.ts +136 -5
  12. package/cli/commands/doctor.ts +24 -4
  13. package/cli/commands/explain.ts +105 -6
  14. package/cli/commands/mcp.ts +16 -0
  15. package/cli/commands/orient.ts +66 -3
  16. package/cli/commands/perf.ts +118 -13
  17. package/cli/commands/runtime.ts +151 -0
  18. package/cli/commands/trace.ts +116 -21
  19. package/cli/mcp/provider.ts +365 -0
  20. package/cli/mcp/stdio.ts +16 -0
  21. package/cli/presentation/commands.ts +77 -20
  22. package/cli/presentation/devSession.ts +2 -0
  23. package/cli/runtime/commands.ts +95 -12
  24. package/cli/utils/agentOutput.ts +46 -0
  25. package/cli/utils/agents.ts +116 -49
  26. package/common/dev/inspection.ts +14 -6
  27. package/common/dev/mcpPayloads.ts +736 -0
  28. package/common/dev/mcpServer.ts +254 -0
  29. package/docs/agent-routing.md +126 -0
  30. package/docs/dev-commands.md +2 -0
  31. package/docs/dev-sessions.md +2 -1
  32. package/docs/diagnostics.md +68 -23
  33. package/docs/mcp.md +149 -0
  34. package/docs/migrate-from-2.1.3.md +15 -5
  35. package/docs/request-tracing.md +12 -6
  36. package/package.json +2 -1
  37. package/server/app/devMcp.ts +159 -0
  38. package/server/services/router/http/cache.ts +116 -0
  39. package/server/services/router/http/index.ts +94 -35
  40. package/server/services/router/index.ts +8 -11
  41. package/tests/agents-utils.test.cjs +36 -13
  42. package/tests/dev-transpile-watch.test.cjs +117 -8
  43. package/tests/inspection.test.cjs +67 -0
  44. package/tests/mcp.test.cjs +127 -0
  45. package/tests/router-cache-config.test.cjs +74 -0
@@ -324,14 +324,16 @@ class ConnectCommand extends ProteumCommand {
324
324
  public controllers = Option.Boolean('--controllers', false, {
325
325
  description: 'Include imported connected controllers in the output.',
326
326
  });
327
- public json = Option.Boolean('--json', false, { description: 'Print JSON output.' });
327
+ public json = Option.Boolean('--json', false, { description: 'Compatibility flag; compact JSON is the default output.' });
328
+ public full = Option.Boolean('--full', false, { description: 'Print the full connect payload.' });
329
+ public human = Option.Boolean('--human', false, { description: 'Print the legacy human-readable report.' });
328
330
  public strict = Option.Boolean('--strict', false, { description: 'Exit with failure if any connect diagnostics exist.' });
329
331
  public legacyArgs = Option.Rest();
330
332
 
331
333
  public async execute() {
332
- const args = { controllers: this.controllers, json: this.json, strict: this.strict } satisfies TArgsObject;
334
+ const args = { controllers: this.controllers, full: this.full, human: this.human, json: this.json, strict: this.strict } satisfies TArgsObject;
333
335
 
334
- applyLegacyBooleanArgs('connect', this.legacyArgs, ['controllers', 'json', 'strict'], args);
336
+ applyLegacyBooleanArgs('connect', this.legacyArgs, ['controllers', 'full', 'human', 'json', 'strict'], args);
335
337
  this.setCliArgs(args);
336
338
  await runCommandModule(() => import('../commands/connect'));
337
339
  }
@@ -345,14 +347,16 @@ class DoctorCommand extends ProteumCommand {
345
347
  public contracts = Option.Boolean('--contracts', false, {
346
348
  description: 'Run contract-focused diagnostics for generated artifacts and manifest-owned source files.',
347
349
  });
348
- public json = Option.Boolean('--json', false, { description: 'Print JSON output.' });
350
+ public json = Option.Boolean('--json', false, { description: 'Compatibility flag; compact JSON is the default output.' });
351
+ public full = Option.Boolean('--full', false, { description: 'Print the full doctor payload.' });
352
+ public human = Option.Boolean('--human', false, { description: 'Print the legacy human-readable report.' });
349
353
  public strict = Option.Boolean('--strict', false, { description: 'Exit with failure if any diagnostics exist.' });
350
354
  public legacyArgs = Option.Rest();
351
355
 
352
356
  public async execute() {
353
- const args = { contracts: this.contracts, json: this.json, strict: this.strict } satisfies TArgsObject;
357
+ const args = { contracts: this.contracts, full: this.full, human: this.human, json: this.json, strict: this.strict } satisfies TArgsObject;
354
358
 
355
- applyLegacyBooleanArgs('doctor', this.legacyArgs, ['contracts', 'json', 'strict'], args);
359
+ applyLegacyBooleanArgs('doctor', this.legacyArgs, ['contracts', 'full', 'human', 'json', 'strict'], args);
356
360
  this.setCliArgs(args);
357
361
  await runCommandModule(() => import('../commands/doctor'));
358
362
  }
@@ -363,7 +367,10 @@ class ExplainCommand extends ProteumCommand {
363
367
 
364
368
  public static usage = buildUsage('explain');
365
369
 
366
- public json = Option.Boolean('--json', false, { description: 'Print JSON output.' });
370
+ public json = Option.Boolean('--json', false, { description: 'Compatibility flag; compact JSON is the default output.' });
371
+ public full = Option.Boolean('--full', false, { description: 'Print the full selected machine-readable detail.' });
372
+ public human = Option.Boolean('--human', false, { description: 'Print the legacy human-readable report.' });
373
+ public manifest = Option.Boolean('--manifest', false, { description: 'Print the full generated manifest.' });
367
374
  public all = Option.Boolean('--all', false, { description: 'Include every explain section.' });
368
375
  public app = Option.Boolean('--app', false, { description: 'Include the app section.' });
369
376
  public conventions = Option.Boolean('--conventions', false, { description: 'Include the conventions section.' });
@@ -384,6 +391,9 @@ class ExplainCommand extends ProteumCommand {
384
391
  if (mode === 'owner') {
385
392
  this.setCliArgs({
386
393
  json: this.json,
394
+ full: this.full,
395
+ human: this.human,
396
+ manifest: this.manifest,
387
397
  ownerQuery: restArgs.join(' ').trim(),
388
398
  });
389
399
  await runCommandModule(() => import('../commands/explain'));
@@ -392,6 +402,9 @@ class ExplainCommand extends ProteumCommand {
392
402
 
393
403
  const args = {
394
404
  json: this.json,
405
+ full: this.full,
406
+ human: this.human,
407
+ manifest: this.manifest,
395
408
  all: this.all,
396
409
  app: this.app,
397
410
  conventions: this.conventions,
@@ -408,7 +421,7 @@ class ExplainCommand extends ProteumCommand {
408
421
  applyLegacyBooleanArgs(
409
422
  'explain',
410
423
  this.args,
411
- ['json', 'all', 'app', 'conventions', 'env', 'connected', 'services', 'controllers', 'commands', 'routes', 'layouts', 'diagnostics'],
424
+ ['json', 'full', 'human', 'manifest', 'all', 'app', 'conventions', 'env', 'connected', 'services', 'controllers', 'commands', 'routes', 'layouts', 'diagnostics'],
412
425
  args,
413
426
  );
414
427
  this.setCliArgs(args);
@@ -423,7 +436,9 @@ class OrientCommand extends ProteumCommand {
423
436
 
424
437
  public port = Option.String('--port', { description: 'Target an existing dev server on the given port.' });
425
438
  public url = Option.String('--url', { description: 'Target an existing dev server at the given base URL.' });
426
- public json = Option.Boolean('--json', false, { description: 'Print JSON output.' });
439
+ public json = Option.Boolean('--json', false, { description: 'Compatibility flag; compact JSON is the default output.' });
440
+ public full = Option.Boolean('--full', false, { description: 'Print the full orientation payload.' });
441
+ public human = Option.Boolean('--human', false, { description: 'Print the legacy human-readable report.' });
427
442
  public args = Option.Rest();
428
443
 
429
444
  public async execute() {
@@ -431,6 +446,8 @@ class OrientCommand extends ProteumCommand {
431
446
 
432
447
  this.setCliArgs({
433
448
  json: this.json,
449
+ full: this.full,
450
+ human: this.human,
434
451
  port: this.port ?? '',
435
452
  query,
436
453
  url: this.url ?? '',
@@ -447,7 +464,10 @@ class TraceCommand extends ProteumCommand {
447
464
 
448
465
  public port = Option.String('--port', { description: 'Override the router port used to query the running dev server.' });
449
466
  public url = Option.String('--url', { description: 'Override the full base URL used to query the running dev server.' });
450
- public json = Option.Boolean('--json', false, { description: 'Print JSON output.' });
467
+ public json = Option.Boolean('--json', false, { description: 'Compatibility flag; compact JSON is the default output.' });
468
+ public full = Option.Boolean('--full', false, { description: 'Print the full trace response.' });
469
+ public events = Option.Boolean('--events', false, { description: 'Include full event, call, SQL, and payload detail.' });
470
+ public human = Option.Boolean('--human', false, { description: 'Print the legacy human-readable trace report.' });
451
471
  public capture = Option.String('--capture', { description: 'Capture mode used by `proteum trace arm`.' });
452
472
  public output = Option.String('--output', { description: 'Output filepath used by `proteum trace export`.' });
453
473
  public args = Option.Rest();
@@ -461,6 +481,9 @@ class TraceCommand extends ProteumCommand {
461
481
  port: this.port ?? '',
462
482
  url: this.url ?? '',
463
483
  json: this.json,
484
+ full: this.full,
485
+ events: this.events,
486
+ human: this.human,
464
487
  capture: this.capture ?? '',
465
488
  output: this.output ?? '',
466
489
  });
@@ -526,7 +549,9 @@ class DiagnoseCommand extends ProteumCommand {
526
549
 
527
550
  public port = Option.String('--port', { description: 'Target an existing dev server on the given port.' });
528
551
  public url = Option.String('--url', { description: 'Target an existing dev server at the given base URL.' });
529
- public json = Option.Boolean('--json', false, { description: 'Print JSON output.' });
552
+ public json = Option.Boolean('--json', false, { description: 'Compatibility flag; compact JSON is the default output.' });
553
+ public full = Option.Boolean('--full', false, { description: 'Print the full diagnose payload.' });
554
+ public human = Option.Boolean('--human', false, { description: 'Print the legacy human-readable report.' });
530
555
  public hit = Option.String('--hit', { description: 'Issue one HTTP request before diagnosing. Defaults to the target path when it starts with /.' });
531
556
  public method = Option.String('--method', { description: 'HTTP method used with `--hit`.' });
532
557
  public dataJson = Option.String('--data-json', { description: 'JSON request body used with `--hit`.' });
@@ -547,6 +572,8 @@ class DiagnoseCommand extends ProteumCommand {
547
572
  dataJson: this.dataJson ?? '',
548
573
  hit: this.hit ?? '',
549
574
  json: this.json,
575
+ full: this.full,
576
+ human: this.human,
550
577
  logsLevel: this.logsLevel ?? '',
551
578
  logsLimit: this.logsLimit ?? '',
552
579
  method: this.method ?? '',
@@ -568,7 +595,9 @@ class PerfCommand extends ProteumCommand {
568
595
 
569
596
  public port = Option.String('--port', { description: 'Target an existing dev server on the given port.' });
570
597
  public url = Option.String('--url', { description: 'Target an existing dev server at the given base URL.' });
571
- public json = Option.Boolean('--json', false, { description: 'Print JSON output.' });
598
+ public json = Option.Boolean('--json', false, { description: 'Compatibility flag; compact JSON is the default output.' });
599
+ public full = Option.Boolean('--full', false, { description: 'Print the full perf payload.' });
600
+ public human = Option.Boolean('--human', false, { description: 'Print the legacy human-readable report.' });
572
601
  public since = Option.String('--since', { description: 'Window used by `top` and `memory`, for example `today`, `yesterday`, or `1h`.' });
573
602
  public baseline = Option.String('--baseline', { description: 'Baseline window used by `compare`.' });
574
603
  public target = Option.String('--target', { description: 'Target window used by `compare`.' });
@@ -584,6 +613,8 @@ class PerfCommand extends ProteumCommand {
584
613
  baseline: this.baseline ?? '',
585
614
  groupBy: this.groupBy ?? '',
586
615
  json: this.json,
616
+ full: this.full,
617
+ human: this.human,
587
618
  limit: this.limit ?? '',
588
619
  port: this.port ?? '',
589
620
  since: this.since ?? '',
@@ -596,6 +627,54 @@ class PerfCommand extends ProteumCommand {
596
627
  }
597
628
  }
598
629
 
630
+ class RuntimeCommand extends ProteumCommand {
631
+ public static paths = [['runtime']];
632
+
633
+ public static usage = buildUsage('runtime');
634
+
635
+ public full = Option.Boolean('--full', false, { description: 'Print full tracked-session and health detail.' });
636
+ public sessionFile = Option.String('--session-file', {
637
+ description: 'Inspect one explicit dev session file instead of the app registry.',
638
+ });
639
+ public args = Option.Rest();
640
+
641
+ public async execute() {
642
+ const [action = 'status'] = this.args;
643
+
644
+ this.setCliArgs({
645
+ action,
646
+ full: this.full,
647
+ sessionFile: this.sessionFile ?? '',
648
+ });
649
+
650
+ await runCommandModule(() => import('../commands/runtime'));
651
+ }
652
+ }
653
+
654
+ class McpCommand extends ProteumCommand {
655
+ public static paths = [['mcp']];
656
+
657
+ public static usage = buildUsage('mcp');
658
+
659
+ public cwd = Option.String('--cwd', { description: 'Run the MCP server against another Proteum app root.' });
660
+ public sessionFile = Option.String('--session-file', {
661
+ description: 'Inspect one explicit dev session file when resolving runtime data.',
662
+ });
663
+ public url = Option.String('--url', { description: 'Use a running Proteum dev server as the live runtime data source.' });
664
+ public legacyArgs = Option.Rest();
665
+
666
+ public async execute() {
667
+ assertNoLegacyArgs('mcp', this.legacyArgs);
668
+ this.setCliArgs({
669
+ sessionFile: this.sessionFile ?? '',
670
+ url: this.url ?? '',
671
+ workdir: this.cwd ?? '',
672
+ });
673
+
674
+ await runCommandModule(() => import('../commands/mcp'));
675
+ }
676
+ }
677
+
599
678
  class VerifyCommand extends ProteumCommand {
600
679
  public static paths = [['verify']];
601
680
 
@@ -672,6 +751,8 @@ export const registeredCommands = {
672
751
  orient: OrientCommand,
673
752
  diagnose: DiagnoseCommand,
674
753
  perf: PerfCommand,
754
+ runtime: RuntimeCommand,
755
+ mcp: McpCommand,
675
756
  trace: TraceCommand,
676
757
  command: CommandCommand,
677
758
  session: SessionCommand,
@@ -705,6 +786,8 @@ export const createCli = (version: string) => {
705
786
  clipanion.register(OrientCommand);
706
787
  clipanion.register(DiagnoseCommand);
707
788
  clipanion.register(PerfCommand);
789
+ clipanion.register(RuntimeCommand);
790
+ clipanion.register(McpCommand);
708
791
  clipanion.register(TraceCommand);
709
792
  clipanion.register(CommandCommand);
710
793
  clipanion.register(SessionCommand);
@@ -0,0 +1,46 @@
1
+ /*----------------------------------
2
+ - TYPES
3
+ ----------------------------------*/
4
+
5
+ export type TAgentNextAction = {
6
+ command: string;
7
+ label: string;
8
+ reason?: string;
9
+ };
10
+
11
+ export type TAgentOmittedDetail = {
12
+ command: string;
13
+ reason: string;
14
+ };
15
+
16
+ export type TAgentResponse<TData extends object> = {
17
+ ok: true;
18
+ format: 'proteum-agent-v1';
19
+ summary: string;
20
+ data: TData;
21
+ nextActions?: TAgentNextAction[];
22
+ omitted?: TAgentOmittedDetail[];
23
+ fullDetailCommand?: string;
24
+ };
25
+
26
+ /*----------------------------------
27
+ - HELPERS
28
+ ----------------------------------*/
29
+
30
+ export const truncateForAgent = (value: string, max = 220) => (value.length <= max ? value : `${value.slice(0, max)}...`);
31
+
32
+ export const compactList = <TValue>(values: TValue[], limit: number) => values.slice(0, Math.max(0, limit));
33
+
34
+ export const printJson = (value: object) => {
35
+ console.log(JSON.stringify(value, null, 2));
36
+ };
37
+
38
+ export const printAgentResponse = <TData extends object>(response: Omit<TAgentResponse<TData>, 'format' | 'ok'>) => {
39
+ printJson({
40
+ ok: true,
41
+ format: 'proteum-agent-v1',
42
+ ...response,
43
+ });
44
+ };
45
+
46
+ export const quoteCommandArgument = (value: string) => JSON.stringify(value);
@@ -23,6 +23,7 @@ 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 = {
@@ -60,33 +61,39 @@ const managedInstructionSectionEnd = '<!-- proteum-instructions:end -->';
60
61
  const managedInstructionSectionIntro = 'This section is managed by `proteum configure agents`.';
61
62
 
62
63
  const sharedRootDocumentInstructionDefinitions: TAgentInstructionDefinition[] = [
63
- { projectPath: 'CODING_STYLE.md' },
64
- { projectPath: 'diagnostics.md' },
65
- { projectPath: 'optimizations.md' },
64
+ { projectPath: 'CODING_STYLE.md', content: 'source' },
65
+ { projectPath: 'diagnostics.md', content: 'source' },
66
+ { projectPath: 'optimizations.md', content: 'source' },
66
67
  ];
67
68
 
68
- const sharedAreaAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
69
- { projectPath: path.join('client', 'AGENTS.md') },
70
- { projectPath: path.join('client', 'pages', 'AGENTS.md') },
71
- { projectPath: path.join('server', 'services', 'AGENTS.md') },
72
- { projectPath: path.join('server', 'routes', 'AGENTS.md') },
73
- { projectPath: path.join('tests', 'e2e', 'AGENTS.md') },
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' },
74
79
  ];
75
80
 
76
81
  const standaloneAppAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
77
- { projectPath: 'AGENTS.md' },
82
+ { projectPath: 'AGENTS.md', content: 'router' },
78
83
  ...sharedRootDocumentInstructionDefinitions,
79
- ...sharedAreaAgentInstructionDefinitions,
84
+ ...sharedAppAreaAgentInstructionDefinitions,
85
+ ...sharedE2eAgentInstructionDefinitions,
80
86
  ];
81
87
 
82
88
  const monorepoAppAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
83
- { projectPath: 'AGENTS.md' },
84
- ...sharedAreaAgentInstructionDefinitions,
89
+ { projectPath: 'AGENTS.md', content: 'router' },
90
+ ...sharedAppAreaAgentInstructionDefinitions,
85
91
  ];
86
92
 
87
93
  const monorepoRootAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
88
- { projectPath: 'AGENTS.md' },
94
+ { projectPath: 'AGENTS.md', content: 'router' },
89
95
  ...sharedRootDocumentInstructionDefinitions,
96
+ ...sharedE2eAgentInstructionDefinitions,
90
97
  ];
91
98
 
92
99
  const legacyProjectInstructionGitignoreBlockStart = '# Proteum-managed instruction symlinks';
@@ -163,7 +170,7 @@ export function configureProjectAgentInstructions({
163
170
  if (mode === 'monorepo') {
164
171
  const retiredAppRootFiles = removeManagedInstructionFiles(
165
172
  normalizedAppRoot,
166
- sharedRootDocumentInstructionDefinitions,
173
+ [...sharedRootDocumentInstructionDefinitions, ...sharedE2eAgentInstructionDefinitions],
167
174
  '[agents]',
168
175
  path.join(coreRoot, 'agents', 'project'),
169
176
  {
@@ -174,7 +181,9 @@ export function configureProjectAgentInstructions({
174
181
  }
175
182
 
176
183
  const appGitignoreCleanupInstructions =
177
- mode === 'monorepo' ? [...appInstructions, ...sharedRootDocumentInstructionDefinitions] : appInstructions;
184
+ mode === 'monorepo'
185
+ ? [...appInstructions, ...sharedRootDocumentInstructionDefinitions, ...sharedE2eAgentInstructionDefinitions]
186
+ : appInstructions;
178
187
 
179
188
  if (
180
189
  !dryRun &&
@@ -299,13 +308,21 @@ function ensureInstructionFiles(
299
308
  continue;
300
309
  }
301
310
 
311
+ const instructionContent = renderProjectInstructionContent({
312
+ instructionDefinition,
313
+ managedSourceRoot,
314
+ managedSectionContent,
315
+ });
302
316
  const existingState = inspectExistingPath({
303
317
  managedSourceRoot,
304
318
  projectFilepath,
305
319
  });
306
320
 
307
321
  if (existingState.kind === 'file') {
308
- const nextContent = upsertManagedInstructionSection(existingState.content, managedSectionContent);
322
+ const nextContent =
323
+ instructionDefinition.content === 'source'
324
+ ? instructionContent
325
+ : upsertManagedInstructionSection(existingState.content, instructionContent);
309
326
  if (nextContent === existingState.content) {
310
327
  result.skipped.push(relativeProjectPath);
311
328
  continue;
@@ -320,7 +337,7 @@ function ensureInstructionFiles(
320
337
  if (existingState.kind === 'managed-different') {
321
338
  if (!dryRun) {
322
339
  fs.removeSync(projectFilepath);
323
- fs.writeFileSync(projectFilepath, managedSectionContent);
340
+ fs.writeFileSync(projectFilepath, instructionContent);
324
341
  }
325
342
  result.updated.push(relativeProjectPath);
326
343
  logVerbose(`${logPrefix} Updated ${relativeProjectPath}`);
@@ -336,14 +353,14 @@ function ensureInstructionFiles(
336
353
  if (existingState.kind === 'blocked') {
337
354
  if (!dryRun) {
338
355
  fs.removeSync(projectFilepath);
339
- fs.writeFileSync(projectFilepath, managedSectionContent);
356
+ fs.writeFileSync(projectFilepath, instructionContent);
340
357
  }
341
358
  result.overwritten.push(relativeProjectPath);
342
359
  logVerbose(`${logPrefix} Replaced ${relativeProjectPath}`);
343
360
  continue;
344
361
  }
345
362
 
346
- if (!dryRun) fs.writeFileSync(projectFilepath, managedSectionContent);
363
+ if (!dryRun) fs.writeFileSync(projectFilepath, instructionContent);
347
364
  result.created.push(relativeProjectPath);
348
365
  logVerbose(`${logPrefix} Created ${relativeProjectPath}`);
349
366
  }
@@ -482,52 +499,102 @@ function mergeInstructionResults(
482
499
  result.blocked.push(...next.blocked.map((entry) => formatResultPath(rootDir, entry)));
483
500
  }
484
501
 
485
- function renderEmbeddedProjectInstructions({ coreRoot }: TProjectInstructionArgs) {
486
- const agentSourceRoot = path.join(coreRoot, 'agents', 'project');
487
- 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
+ }
488
518
 
489
- 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();
490
531
  const lines = [
491
532
  managedInstructionSectionHeader,
492
533
  managedInstructionSectionStart,
493
534
  '',
494
535
  managedInstructionSectionIntro,
495
536
  '',
537
+ `## Source: ${normalizeProjectPathForGitignore(projectPath)}`,
538
+ '',
496
539
  ];
497
540
 
498
- for (const sourceFile of sourceFiles) {
499
- const content = fs.readFileSync(sourceFile.filepath, 'utf8');
500
- const demotedContent = demoteMarkdownHeadings(content).trim();
501
-
502
- lines.push(`## Source: ${sourceFile.relativePath}`, '');
503
- if (demotedContent) lines.push(demotedContent, '');
504
- }
505
-
541
+ if (demotedContent) lines.push(demotedContent, '');
506
542
  lines.push(managedInstructionSectionEnd, '');
507
543
 
508
544
  return lines.join('\n');
509
545
  }
510
546
 
511
- function collectMarkdownFiles(rootDir: string, currentDir = rootDir): { filepath: string; relativePath: string }[] {
512
- const files: { filepath: string; relativePath: string }[] = [];
513
-
514
- for (const entry of fs.readdirSync(currentDir, { withFileTypes: true })) {
515
- const filepath = path.join(currentDir, entry.name);
516
-
517
- if (entry.isDirectory()) {
518
- files.push(...collectMarkdownFiles(rootDir, filepath));
519
- continue;
520
- }
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}`);
521
550
 
522
- 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
+ ];
523
594
 
524
- files.push({
525
- filepath,
526
- relativePath: normalizeProjectPathForGitignore(path.relative(rootDir, filepath)),
527
- });
528
- }
595
+ lines.push(managedInstructionSectionEnd, '');
529
596
 
530
- return files;
597
+ return lines.join('\n');
531
598
  }
532
599
 
533
600
  function demoteMarkdownHeadings(content: string) {
@@ -1156,12 +1156,20 @@ export const explainOwner = (manifest: TProteumManifest, query: string): TExplai
1156
1156
  const normalizedQuery = normalizeText(query);
1157
1157
  if (!normalizedQuery) return { matches: [], normalizedQuery, query };
1158
1158
 
1159
- const matches = buildManifestEntries(manifest)
1160
- .map((entry) => {
1161
- const { score, matchedOn } = scoreOwnerMatch(query, entry);
1162
- return score > 0 ? toOwnerMatch(entry, score, matchedOn) : undefined;
1163
- })
1164
- .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
1165
1173
  .sort((left, right) => right.score - left.score || left.kind.localeCompare(right.kind) || left.label.localeCompare(right.label))
1166
1174
  .slice(0, 12);
1167
1175