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.
- package/AGENTS.md +3 -2
- package/README.md +49 -11
- package/agents/project/AGENTS.md +43 -5
- package/agents/project/diagnostics.md +6 -2
- package/agents/project/optimizations.md +1 -0
- package/agents/project/root/AGENTS.md +14 -5
- package/agents/project/tests/AGENTS.md +6 -0
- package/agents/project/tests/e2e/AGENTS.md +13 -0
- package/agents/project/tests/e2e/REAL_WORLD_JOURNEY_TESTS.md +192 -0
- package/cli/commands/connect.ts +40 -4
- package/cli/commands/diagnose.ts +136 -5
- package/cli/commands/doctor.ts +24 -4
- package/cli/commands/explain.ts +105 -6
- package/cli/commands/mcp.ts +16 -0
- package/cli/commands/orient.ts +66 -3
- package/cli/commands/perf.ts +118 -13
- package/cli/commands/runtime.ts +151 -0
- package/cli/commands/trace.ts +116 -21
- package/cli/mcp/provider.ts +365 -0
- package/cli/mcp/stdio.ts +16 -0
- package/cli/presentation/commands.ts +77 -20
- package/cli/presentation/devSession.ts +2 -0
- package/cli/runtime/commands.ts +95 -12
- package/cli/utils/agentOutput.ts +46 -0
- package/cli/utils/agents.ts +116 -49
- package/common/dev/inspection.ts +14 -6
- package/common/dev/mcpPayloads.ts +736 -0
- package/common/dev/mcpServer.ts +254 -0
- package/docs/agent-routing.md +126 -0
- package/docs/dev-commands.md +2 -0
- package/docs/dev-sessions.md +2 -1
- package/docs/diagnostics.md +68 -23
- package/docs/mcp.md +149 -0
- package/docs/migrate-from-2.1.3.md +15 -5
- package/docs/request-tracing.md +12 -6
- package/package.json +2 -1
- package/server/app/devMcp.ts +159 -0
- package/server/services/router/http/cache.ts +116 -0
- package/server/services/router/http/index.ts +94 -35
- package/server/services/router/index.ts +8 -11
- package/tests/agents-utils.test.cjs +36 -13
- package/tests/dev-transpile-watch.test.cjs +117 -8
- package/tests/inspection.test.cjs +67 -0
- package/tests/mcp.test.cjs +127 -0
- package/tests/router-cache-config.test.cjs +74 -0
package/cli/runtime/commands.ts
CHANGED
|
@@ -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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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);
|
package/cli/utils/agents.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
...
|
|
84
|
+
...sharedAppAreaAgentInstructionDefinitions,
|
|
85
|
+
...sharedE2eAgentInstructionDefinitions,
|
|
80
86
|
];
|
|
81
87
|
|
|
82
88
|
const monorepoAppAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
|
|
83
|
-
{ projectPath: 'AGENTS.md' },
|
|
84
|
-
...
|
|
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'
|
|
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 =
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
486
|
-
|
|
487
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
512
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
525
|
-
filepath,
|
|
526
|
-
relativePath: normalizeProjectPathForGitignore(path.relative(rootDir, filepath)),
|
|
527
|
-
});
|
|
528
|
-
}
|
|
595
|
+
lines.push(managedInstructionSectionEnd, '');
|
|
529
596
|
|
|
530
|
-
return
|
|
597
|
+
return lines.join('\n');
|
|
531
598
|
}
|
|
532
599
|
|
|
533
600
|
function demoteMarkdownHeadings(content: string) {
|
package/common/dev/inspection.ts
CHANGED
|
@@ -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
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
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
|
|