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/commands/diagnose.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { TRequestTraceErrorResponse, TRequestTraceArmResponse } from '../..
|
|
|
12
12
|
import type { TDevSessionErrorResponse, TDevSessionStartResponse } from '../../common/dev/session';
|
|
13
13
|
import { summarizeTraceForDiagnose } from '@common/dev/inspection';
|
|
14
14
|
import { readProteumManifest } from '../compiler/common/proteumManifest';
|
|
15
|
+
import { compactList, printAgentResponse, printJson, quoteCommandArgument, truncateForAgent } from '../utils/agentOutput';
|
|
15
16
|
|
|
16
17
|
const normalizeBaseUrl = (value: string) => value.replace(/\/+$/, '');
|
|
17
18
|
const truncate = (value: string, max = 160) => (value.length <= max ? value : `${value.slice(0, max)}...`);
|
|
@@ -237,6 +238,132 @@ const renderHuman = (manifest: ReturnType<typeof readProteumManifest>, response:
|
|
|
237
238
|
renderLogs(response.serverLogs),
|
|
238
239
|
].join('\n');
|
|
239
240
|
|
|
241
|
+
const compactOwnerMatch = (match: TDiagnoseResponse['owner']['matches'][number]) => ({
|
|
242
|
+
kind: match.kind,
|
|
243
|
+
label: match.label,
|
|
244
|
+
score: match.score,
|
|
245
|
+
scope: match.scopeLabel,
|
|
246
|
+
origin: match.originHint,
|
|
247
|
+
source: match.source,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const compactDiagnostic = (diagnostic: TDiagnoseResponse['doctor']['diagnostics'][number]) => ({
|
|
251
|
+
level: diagnostic.level,
|
|
252
|
+
code: diagnostic.code,
|
|
253
|
+
message: truncateForAgent(diagnostic.message),
|
|
254
|
+
filepath: diagnostic.filepath,
|
|
255
|
+
sourceLocation: diagnostic.sourceLocation,
|
|
256
|
+
fixHint: diagnostic.fixHint ? truncateForAgent(diagnostic.fixHint) : undefined,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const compactRequest = (request: TDiagnoseResponse['request']) =>
|
|
260
|
+
request
|
|
261
|
+
? {
|
|
262
|
+
id: request.id,
|
|
263
|
+
method: request.method,
|
|
264
|
+
path: request.path,
|
|
265
|
+
statusCode: request.statusCode,
|
|
266
|
+
durationMs: request.durationMs,
|
|
267
|
+
capture: request.capture,
|
|
268
|
+
user: request.user,
|
|
269
|
+
errorMessage: request.errorMessage,
|
|
270
|
+
counts: {
|
|
271
|
+
calls: request.calls.length,
|
|
272
|
+
events: request.events.length,
|
|
273
|
+
sqlQueries: request.sqlQueries.length,
|
|
274
|
+
droppedEvents: request.droppedEvents,
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
: undefined;
|
|
278
|
+
|
|
279
|
+
const buildDiagnoseFullDetailCommand = ({
|
|
280
|
+
hitPath,
|
|
281
|
+
query,
|
|
282
|
+
}: {
|
|
283
|
+
hitPath: string;
|
|
284
|
+
query: string;
|
|
285
|
+
}) =>
|
|
286
|
+
[
|
|
287
|
+
'proteum diagnose',
|
|
288
|
+
quoteCommandArgument(query),
|
|
289
|
+
hitPath ? `--hit ${quoteCommandArgument(hitPath)}` : '',
|
|
290
|
+
typeof cli.args.port === 'string' && cli.args.port ? `--port ${cli.args.port}` : '',
|
|
291
|
+
typeof cli.args.url === 'string' && cli.args.url ? `--url ${quoteCommandArgument(cli.args.url)}` : '',
|
|
292
|
+
'--full',
|
|
293
|
+
]
|
|
294
|
+
.filter(Boolean)
|
|
295
|
+
.join(' ');
|
|
296
|
+
|
|
297
|
+
const printCompactDiagnose = ({
|
|
298
|
+
hitPath,
|
|
299
|
+
query,
|
|
300
|
+
response,
|
|
301
|
+
}: {
|
|
302
|
+
hitPath: string;
|
|
303
|
+
query: string;
|
|
304
|
+
response: TDiagnoseResponse;
|
|
305
|
+
}) => {
|
|
306
|
+
const request = compactRequest(response.request);
|
|
307
|
+
const traceSummary = summarizeTraceForDiagnose(response.request);
|
|
308
|
+
const doctorSummary = `${response.doctor.summary.errors} doctor errors/${response.doctor.summary.warnings} warnings`;
|
|
309
|
+
const contractsSummary = `${response.contracts.summary.errors} contract errors/${response.contracts.summary.warnings} warnings`;
|
|
310
|
+
const summary = `${response.query || query || 'request'}: ${traceSummary}; ${doctorSummary}; ${contractsSummary}`;
|
|
311
|
+
const nextActions = response.orientation?.nextSteps || [];
|
|
312
|
+
const requestId = response.request?.id;
|
|
313
|
+
|
|
314
|
+
printAgentResponse({
|
|
315
|
+
summary,
|
|
316
|
+
data: {
|
|
317
|
+
query: response.query,
|
|
318
|
+
request,
|
|
319
|
+
owner: {
|
|
320
|
+
top: response.owner.matches[0] ? compactOwnerMatch(response.owner.matches[0]) : undefined,
|
|
321
|
+
matches: compactList(response.owner.matches, 4).map(compactOwnerMatch),
|
|
322
|
+
totalReturned: response.owner.matches.length,
|
|
323
|
+
},
|
|
324
|
+
suspects: compactList(response.suspects, 6),
|
|
325
|
+
chain: compactList(response.chain || [], 8),
|
|
326
|
+
diagnostics: {
|
|
327
|
+
doctor: {
|
|
328
|
+
summary: response.doctor.summary,
|
|
329
|
+
top: compactList(response.doctor.diagnostics, 6).map(compactDiagnostic),
|
|
330
|
+
total: response.doctor.diagnostics.length,
|
|
331
|
+
},
|
|
332
|
+
contracts: {
|
|
333
|
+
summary: response.contracts.summary,
|
|
334
|
+
top: compactList(response.contracts.diagnostics, 6).map(compactDiagnostic),
|
|
335
|
+
total: response.contracts.diagnostics.length,
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
logs: compactList(response.serverLogs.logs, 10).map((entry) => ({
|
|
339
|
+
level: entry.level,
|
|
340
|
+
time: entry.time,
|
|
341
|
+
text: truncateForAgent(entry.text),
|
|
342
|
+
})),
|
|
343
|
+
instructions: response.orientation
|
|
344
|
+
? {
|
|
345
|
+
mustRead: [...new Set([response.orientation.guidance.agents, ...response.orientation.guidance.areaAgents])],
|
|
346
|
+
diagnostics: response.orientation.guidance.diagnostics,
|
|
347
|
+
codingStyle: response.orientation.guidance.codingStyle,
|
|
348
|
+
optimizations: response.orientation.guidance.optimizations,
|
|
349
|
+
}
|
|
350
|
+
: undefined,
|
|
351
|
+
},
|
|
352
|
+
nextActions,
|
|
353
|
+
fullDetailCommand: buildDiagnoseFullDetailCommand({ hitPath, query }),
|
|
354
|
+
omitted: [
|
|
355
|
+
...(requestId
|
|
356
|
+
? [
|
|
357
|
+
{
|
|
358
|
+
reason: 'Full request events, API call payloads, and SQL text are omitted from the default diagnose response.',
|
|
359
|
+
command: `proteum trace show ${quoteCommandArgument(requestId)} --events`,
|
|
360
|
+
},
|
|
361
|
+
]
|
|
362
|
+
: []),
|
|
363
|
+
],
|
|
364
|
+
});
|
|
365
|
+
};
|
|
366
|
+
|
|
240
367
|
const resolveManifest = async () => {
|
|
241
368
|
try {
|
|
242
369
|
return readProteumManifest(cli.paths.appRoot);
|
|
@@ -258,7 +385,6 @@ export const run = async () => {
|
|
|
258
385
|
const method = typeof cli.args.method === 'string' && cli.args.method ? cli.args.method.trim().toUpperCase() : 'GET';
|
|
259
386
|
const logsLevel = typeof cli.args.logsLevel === 'string' && cli.args.logsLevel ? cli.args.logsLevel.trim() : 'warn';
|
|
260
387
|
const logsLimit = typeof cli.args.logsLimit === 'string' && cli.args.logsLimit ? cli.args.logsLimit.trim() : '40';
|
|
261
|
-
const shouldPrintJson = cli.args.json === true;
|
|
262
388
|
const hitPath = hit || (target.startsWith('/') ? target : '');
|
|
263
389
|
const query = target || hitPath;
|
|
264
390
|
let parsedDataJson: unknown;
|
|
@@ -308,11 +434,16 @@ export const run = async () => {
|
|
|
308
434
|
const diagnose = await requestJson<TDiagnoseResponse>(
|
|
309
435
|
`/__proteum/diagnose?${new URLSearchParams(diagnoseRequest).toString()}`,
|
|
310
436
|
);
|
|
311
|
-
if (
|
|
312
|
-
|
|
437
|
+
if (cli.args.full === true) {
|
|
438
|
+
printJson(diagnose.body);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (cli.args.human === true) {
|
|
443
|
+
const manifest = await resolveManifest();
|
|
444
|
+
console.log(renderHuman(manifest, diagnose.body));
|
|
313
445
|
return;
|
|
314
446
|
}
|
|
315
447
|
|
|
316
|
-
|
|
317
|
-
console.log(renderHuman(manifest, diagnose.body));
|
|
448
|
+
printCompactDiagnose({ hitPath, query, response: diagnose.body });
|
|
318
449
|
};
|
package/cli/commands/doctor.ts
CHANGED
|
@@ -3,8 +3,9 @@ import Compiler from '../compiler';
|
|
|
3
3
|
import { readProteumManifest } from '../compiler/common/proteumManifest';
|
|
4
4
|
import { buildContractsDoctorResponse } from '@common/dev/contractsDoctor';
|
|
5
5
|
import { buildDoctorResponse, renderDoctorHuman, renderDoctorResponseHuman } from '@common/dev/diagnostics';
|
|
6
|
+
import { compactList, printAgentResponse, printJson, truncateForAgent } from '../utils/agentOutput';
|
|
6
7
|
|
|
7
|
-
const allowedDoctorArgs = new Set(['contracts', 'json', 'strict']);
|
|
8
|
+
const allowedDoctorArgs = new Set(['contracts', 'full', 'human', 'json', 'strict']);
|
|
8
9
|
|
|
9
10
|
const validateDoctorArgs = () => {
|
|
10
11
|
const enabledArgs = Object.entries(cli.args)
|
|
@@ -20,6 +21,15 @@ const validateDoctorArgs = () => {
|
|
|
20
21
|
}
|
|
21
22
|
};
|
|
22
23
|
|
|
24
|
+
const compactDiagnostic = (diagnostic: ReturnType<typeof buildDoctorResponse>['diagnostics'][number]) => ({
|
|
25
|
+
level: diagnostic.level,
|
|
26
|
+
code: diagnostic.code,
|
|
27
|
+
message: truncateForAgent(diagnostic.message),
|
|
28
|
+
filepath: diagnostic.filepath,
|
|
29
|
+
sourceLocation: diagnostic.sourceLocation,
|
|
30
|
+
fixHint: diagnostic.fixHint ? truncateForAgent(diagnostic.fixHint) : undefined,
|
|
31
|
+
});
|
|
32
|
+
|
|
23
33
|
export const run = async (): Promise<void> => {
|
|
24
34
|
validateDoctorArgs();
|
|
25
35
|
|
|
@@ -32,9 +42,9 @@ export const run = async (): Promise<void> => {
|
|
|
32
42
|
? buildContractsDoctorResponse(manifest, cli.args.strict === true)
|
|
33
43
|
: buildDoctorResponse(manifest, cli.args.strict === true);
|
|
34
44
|
|
|
35
|
-
if (cli.args.
|
|
36
|
-
|
|
37
|
-
} else {
|
|
45
|
+
if (cli.args.full === true) {
|
|
46
|
+
printJson(response);
|
|
47
|
+
} else if (cli.args.human === true) {
|
|
38
48
|
console.log(
|
|
39
49
|
cli.args.contracts === true
|
|
40
50
|
? renderDoctorResponseHuman({
|
|
@@ -45,6 +55,16 @@ export const run = async (): Promise<void> => {
|
|
|
45
55
|
})
|
|
46
56
|
: renderDoctorHuman(manifest, cli.args.strict === true),
|
|
47
57
|
);
|
|
58
|
+
} else {
|
|
59
|
+
printAgentResponse({
|
|
60
|
+
summary: `${cli.args.contracts === true ? 'Doctor contracts' : 'Doctor'}: ${response.summary.errors} errors, ${response.summary.warnings} warnings`,
|
|
61
|
+
data: {
|
|
62
|
+
summary: response.summary,
|
|
63
|
+
diagnostics: compactList(response.diagnostics, 10).map(compactDiagnostic),
|
|
64
|
+
totalDiagnostics: response.diagnostics.length,
|
|
65
|
+
},
|
|
66
|
+
fullDetailCommand: `proteum doctor${cli.args.contracts === true ? ' --contracts' : ''} --full`,
|
|
67
|
+
});
|
|
48
68
|
}
|
|
49
69
|
|
|
50
70
|
if (cli.args.strict === true && response.diagnostics.length > 0) {
|
package/cli/commands/explain.ts
CHANGED
|
@@ -2,14 +2,16 @@ import cli from '..';
|
|
|
2
2
|
import Compiler from '../compiler';
|
|
3
3
|
import { readProteumManifest } from '../compiler/common/proteumManifest';
|
|
4
4
|
import {
|
|
5
|
+
buildExplainSummaryItems,
|
|
5
6
|
explainSectionNames,
|
|
6
7
|
pickExplainManifestSections,
|
|
7
8
|
renderExplainHuman,
|
|
8
9
|
type TExplainSectionName,
|
|
9
10
|
} from '@common/dev/diagnostics';
|
|
10
11
|
import { explainOwner } from '@common/dev/inspection';
|
|
12
|
+
import { compactList, printAgentResponse, printJson, quoteCommandArgument } from '../utils/agentOutput';
|
|
11
13
|
|
|
12
|
-
const allowedExplainArgs = new Set(['json', 'all', ...explainSectionNames]);
|
|
14
|
+
const allowedExplainArgs = new Set(['json', 'all', 'full', 'human', 'manifest', ...explainSectionNames]);
|
|
13
15
|
|
|
14
16
|
const validateExplainArgs = () => {
|
|
15
17
|
const enabledArgs = Object.entries(cli.args)
|
|
@@ -31,6 +33,93 @@ const getSelectedSections = (): TExplainSectionName[] => {
|
|
|
31
33
|
return explainSectionNames.filter((sectionName) => cli.args[sectionName] === true);
|
|
32
34
|
};
|
|
33
35
|
|
|
36
|
+
const compactOwnerMatch = (match: ReturnType<typeof explainOwner>['matches'][number]) => ({
|
|
37
|
+
kind: match.kind,
|
|
38
|
+
label: match.label,
|
|
39
|
+
score: match.score,
|
|
40
|
+
scope: match.scopeLabel,
|
|
41
|
+
origin: match.originHint,
|
|
42
|
+
source: match.source,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const hasExplicitDetailSelection = (selectedSections: TExplainSectionName[]) =>
|
|
46
|
+
cli.args.full === true || cli.args.manifest === true || cli.args.all === true || selectedSections.length > 0;
|
|
47
|
+
|
|
48
|
+
const printCompactOwner = (ownerQuery: string, response: ReturnType<typeof explainOwner>) => {
|
|
49
|
+
const topOwner = response.matches[0];
|
|
50
|
+
|
|
51
|
+
printAgentResponse({
|
|
52
|
+
summary: topOwner
|
|
53
|
+
? `${ownerQuery} -> ${topOwner.kind} ${topOwner.label} (${topOwner.scopeLabel})`
|
|
54
|
+
: `${ownerQuery} -> no manifest owner matched`,
|
|
55
|
+
data: {
|
|
56
|
+
query: ownerQuery,
|
|
57
|
+
normalizedQuery: response.normalizedQuery,
|
|
58
|
+
top: topOwner ? compactOwnerMatch(topOwner) : undefined,
|
|
59
|
+
matches: compactList(response.matches, 6).map(compactOwnerMatch),
|
|
60
|
+
totalReturned: response.matches.length,
|
|
61
|
+
},
|
|
62
|
+
nextActions: [
|
|
63
|
+
{
|
|
64
|
+
label: 'Orient',
|
|
65
|
+
command: `proteum orient ${quoteCommandArgument(ownerQuery)}`,
|
|
66
|
+
reason: 'Resolve the owner together with the relevant instruction files and next command.',
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
fullDetailCommand: `proteum explain owner ${quoteCommandArgument(ownerQuery)} --full`,
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const printCompactExplain = (manifest: ReturnType<typeof readProteumManifest>) => {
|
|
74
|
+
const errors = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'error').length;
|
|
75
|
+
const warnings = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'warning').length;
|
|
76
|
+
const requiredEnvProvided = manifest.env.requiredVariables.filter((variable) => variable.provided).length;
|
|
77
|
+
|
|
78
|
+
printAgentResponse({
|
|
79
|
+
summary: `${manifest.app.identity.identifier}: ${manifest.controllers.length} controllers, ${manifest.routes.client.length + manifest.routes.server.length} routes, ${errors} errors, ${warnings} warnings`,
|
|
80
|
+
data: {
|
|
81
|
+
app: {
|
|
82
|
+
root: manifest.app.root,
|
|
83
|
+
coreRoot: manifest.app.coreRoot,
|
|
84
|
+
identifier: manifest.app.identity.identifier,
|
|
85
|
+
name: manifest.app.identity.name,
|
|
86
|
+
},
|
|
87
|
+
counts: {
|
|
88
|
+
commands: manifest.commands.length,
|
|
89
|
+
connectedProjects: manifest.connectedProjects.length,
|
|
90
|
+
controllers: manifest.controllers.length,
|
|
91
|
+
diagnostics: manifest.diagnostics.length,
|
|
92
|
+
layouts: manifest.layouts.length,
|
|
93
|
+
routesClient: manifest.routes.client.length,
|
|
94
|
+
routesServer: manifest.routes.server.length,
|
|
95
|
+
servicesApp: manifest.services.app.length,
|
|
96
|
+
servicesRouterPlugins: manifest.services.routerPlugins.length,
|
|
97
|
+
},
|
|
98
|
+
diagnostics: { errors, warnings },
|
|
99
|
+
env: {
|
|
100
|
+
requiredProvided: requiredEnvProvided,
|
|
101
|
+
requiredTotal: manifest.env.requiredVariables.length,
|
|
102
|
+
routerPort: manifest.env.resolved.routerPort,
|
|
103
|
+
},
|
|
104
|
+
summaryItems: buildExplainSummaryItems(manifest),
|
|
105
|
+
},
|
|
106
|
+
nextActions: [
|
|
107
|
+
{
|
|
108
|
+
label: 'Orient Target',
|
|
109
|
+
command: 'proteum orient <route|file|controller|error>',
|
|
110
|
+
reason: 'Use orient for task-specific owner, instruction, and next-command routing.',
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
fullDetailCommand: 'proteum explain --manifest',
|
|
114
|
+
omitted: [
|
|
115
|
+
{
|
|
116
|
+
reason: 'Full manifest sections are omitted from the default agent summary.',
|
|
117
|
+
command: 'proteum explain --manifest',
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
|
|
34
123
|
export const run = async (): Promise<void> => {
|
|
35
124
|
validateExplainArgs();
|
|
36
125
|
|
|
@@ -42,8 +131,13 @@ export const run = async (): Promise<void> => {
|
|
|
42
131
|
|
|
43
132
|
if (ownerQuery) {
|
|
44
133
|
const response = explainOwner(manifest, ownerQuery);
|
|
45
|
-
if (cli.args.
|
|
46
|
-
|
|
134
|
+
if (cli.args.full === true || cli.args.manifest === true) {
|
|
135
|
+
printJson(response);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (cli.args.human !== true) {
|
|
140
|
+
printCompactOwner(ownerQuery, response);
|
|
47
141
|
return;
|
|
48
142
|
}
|
|
49
143
|
|
|
@@ -64,10 +158,15 @@ export const run = async (): Promise<void> => {
|
|
|
64
158
|
|
|
65
159
|
const selectedSections = getSelectedSections();
|
|
66
160
|
|
|
67
|
-
if (
|
|
68
|
-
|
|
161
|
+
if (hasExplicitDetailSelection(selectedSections)) {
|
|
162
|
+
printJson(pickExplainManifestSections(manifest, cli.args.manifest === true ? [...explainSectionNames] : selectedSections));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (cli.args.human === true) {
|
|
167
|
+
console.log(renderExplainHuman(manifest, selectedSections));
|
|
69
168
|
return;
|
|
70
169
|
}
|
|
71
170
|
|
|
72
|
-
|
|
171
|
+
printCompactExplain(manifest);
|
|
73
172
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import cli from '..';
|
|
2
|
+
import { CliProteumMcpProvider } from '../mcp/provider';
|
|
3
|
+
import { startProteumMcpStdioServer } from '../mcp/stdio';
|
|
4
|
+
|
|
5
|
+
export const run = async () => {
|
|
6
|
+
const provider = new CliProteumMcpProvider({
|
|
7
|
+
appRoot: cli.paths.appRoot,
|
|
8
|
+
sessionFilePath: typeof cli.args.sessionFile === 'string' && cli.args.sessionFile ? cli.args.sessionFile : undefined,
|
|
9
|
+
url: typeof cli.args.url === 'string' && cli.args.url ? cli.args.url : undefined,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
await startProteumMcpStdioServer({
|
|
13
|
+
provider,
|
|
14
|
+
version: String(cli.packageJson.version || ''),
|
|
15
|
+
});
|
|
16
|
+
};
|
package/cli/commands/orient.ts
CHANGED
|
@@ -8,6 +8,7 @@ import Compiler from '../compiler';
|
|
|
8
8
|
import { readProteumManifest } from '../compiler/common/proteumManifest';
|
|
9
9
|
import { buildOrientationResponse, type TOrientResponse } from '@common/dev/inspection';
|
|
10
10
|
import type { TProteumManifest } from '@common/dev/proteumManifest';
|
|
11
|
+
import { compactList, printAgentResponse, printJson, quoteCommandArgument } from '../utils/agentOutput';
|
|
11
12
|
|
|
12
13
|
const normalizeBaseUrl = (value: string) => value.replace(/\/+$/, '');
|
|
13
14
|
const dedupe = <TValue>(values: TValue[]) => [...new Set(values)];
|
|
@@ -153,6 +154,63 @@ const renderHuman = (response: TOrientResponse) =>
|
|
|
153
154
|
...(response.warnings.length === 0 ? ['- none'] : response.warnings.map((warning) => `- ${warning}`)),
|
|
154
155
|
].join('\n');
|
|
155
156
|
|
|
157
|
+
const compactOwnerMatch = (match: TOrientResponse['owner']['matches'][number]) => ({
|
|
158
|
+
kind: match.kind,
|
|
159
|
+
label: match.label,
|
|
160
|
+
score: match.score,
|
|
161
|
+
scope: match.scopeLabel,
|
|
162
|
+
origin: match.originHint,
|
|
163
|
+
source: match.source,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const buildInstructionPlan = (response: TOrientResponse) => ({
|
|
167
|
+
mustRead: [...new Set([response.guidance.agents, ...response.guidance.areaAgents])],
|
|
168
|
+
readWhen: [
|
|
169
|
+
{
|
|
170
|
+
file: response.guidance.diagnostics,
|
|
171
|
+
when: 'Read only for raw errors, failing requests, traces, perf regressions, or reproduction work.',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
file: response.guidance.codingStyle,
|
|
175
|
+
when: 'Read before editing implementation files.',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
file: response.guidance.optimizations,
|
|
179
|
+
when: 'Read after client-side implementation or when the task explicitly concerns packages, build, runtime, or performance.',
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const printCompactOrient = (response: TOrientResponse) => {
|
|
185
|
+
const topOwner = response.owner.matches[0];
|
|
186
|
+
const summary = topOwner
|
|
187
|
+
? `${response.query} -> ${topOwner.kind} ${topOwner.label} (${topOwner.scopeLabel})`
|
|
188
|
+
: `${response.query} -> no manifest owner matched`;
|
|
189
|
+
|
|
190
|
+
printAgentResponse({
|
|
191
|
+
summary,
|
|
192
|
+
data: {
|
|
193
|
+
query: response.query,
|
|
194
|
+
app: response.app,
|
|
195
|
+
owner: {
|
|
196
|
+
top: topOwner ? compactOwnerMatch(topOwner) : undefined,
|
|
197
|
+
matches: compactList(response.owner.matches, 4).map(compactOwnerMatch),
|
|
198
|
+
totalReturned: response.owner.matches.length,
|
|
199
|
+
},
|
|
200
|
+
instructions: buildInstructionPlan(response),
|
|
201
|
+
connected: {
|
|
202
|
+
imports: compactList(response.connected.imports, 4),
|
|
203
|
+
producers: compactList(response.connected.producers, 3),
|
|
204
|
+
totalImports: response.connected.imports.length,
|
|
205
|
+
totalProducers: response.connected.producers.length,
|
|
206
|
+
},
|
|
207
|
+
warnings: response.warnings,
|
|
208
|
+
},
|
|
209
|
+
nextActions: response.nextSteps,
|
|
210
|
+
fullDetailCommand: `proteum orient ${quoteCommandArgument(response.query)} --full`,
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
|
|
156
214
|
export const run = async () => {
|
|
157
215
|
const query = typeof cli.args.query === 'string' ? cli.args.query.trim() : '';
|
|
158
216
|
if (!query) throw new UsageError('A query is required. Example: proteum orient /api/Auth/CurrentUser');
|
|
@@ -160,10 +218,15 @@ export const run = async () => {
|
|
|
160
218
|
const manifest = await resolveManifest();
|
|
161
219
|
const response = buildOrientationResponse(manifest, query);
|
|
162
220
|
|
|
163
|
-
if (cli.args.
|
|
164
|
-
|
|
221
|
+
if (cli.args.full === true) {
|
|
222
|
+
printJson(response);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (cli.args.human === true) {
|
|
227
|
+
console.log(renderHuman(response));
|
|
165
228
|
return;
|
|
166
229
|
}
|
|
167
230
|
|
|
168
|
-
|
|
231
|
+
printCompactOrient(response);
|
|
169
232
|
};
|
package/cli/commands/perf.ts
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import { UsageError } from 'clipanion';
|
|
5
5
|
|
|
6
6
|
import cli from '..';
|
|
7
|
+
import { compactList, printAgentResponse, printJson, quoteCommandArgument, truncateForAgent } from '../utils/agentOutput';
|
|
7
8
|
import type {
|
|
8
9
|
TPerfCompareResponse,
|
|
9
10
|
TPerfMemoryResponse,
|
|
@@ -102,10 +103,6 @@ const requestJson = async <TResponse>(pathname: string) => {
|
|
|
102
103
|
);
|
|
103
104
|
};
|
|
104
105
|
|
|
105
|
-
const printJson = (value: object) => {
|
|
106
|
-
console.log(JSON.stringify(value, null, 2));
|
|
107
|
-
};
|
|
108
|
-
|
|
109
106
|
const renderWindow = (label: string, value: { startedAt: string; finishedAt: string; requestCount: number; availableRequestCount: number }) =>
|
|
110
107
|
`${label}=${value.requestCount}/${value.availableRequestCount} traces ${value.startedAt}..${value.finishedAt}`;
|
|
111
108
|
|
|
@@ -180,9 +177,113 @@ const renderRequest = (response: TPerfRequestResponse) =>
|
|
|
180
177
|
)),
|
|
181
178
|
].join('\n');
|
|
182
179
|
|
|
180
|
+
const compactTopLikeRow = (row: TPerfTopResponse['rows'][number]) => ({
|
|
181
|
+
label: row.label,
|
|
182
|
+
requestCount: row.requestCount,
|
|
183
|
+
avgDurationMs: row.avgDurationMs,
|
|
184
|
+
p95DurationMs: row.p95DurationMs,
|
|
185
|
+
maxDurationMs: row.maxDurationMs,
|
|
186
|
+
avgCpuMs: row.avgCpuMs,
|
|
187
|
+
avgSqlDurationMs: row.avgSqlDurationMs,
|
|
188
|
+
avgRenderDurationMs: row.avgRenderDurationMs,
|
|
189
|
+
avgHeapDeltaBytes: row.avgHeapDeltaBytes,
|
|
190
|
+
slowestRequestId: row.slowestRequestId,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const compactSql = (query: TPerfRequestResponse['request']['hottestSqlQueries'][number]) => ({
|
|
194
|
+
callerLabel: query.callerLabel,
|
|
195
|
+
operation: query.operation,
|
|
196
|
+
model: query.model,
|
|
197
|
+
fingerprint: query.fingerprint,
|
|
198
|
+
durationMs: query.durationMs,
|
|
199
|
+
query: truncateForAgent(query.query, 140),
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const printCompactTop = (response: TPerfTopResponse) => {
|
|
203
|
+
printAgentResponse({
|
|
204
|
+
summary: `Perf top ${response.groupBy}: ${response.summary.requestCount} requests, ${response.summary.errorCount} errors, p95=${formatDuration(response.summary.p95DurationMs)}`,
|
|
205
|
+
data: {
|
|
206
|
+
groupBy: response.groupBy,
|
|
207
|
+
window: response.window,
|
|
208
|
+
summary: response.summary,
|
|
209
|
+
rows: compactList(response.rows, 8).map(compactTopLikeRow),
|
|
210
|
+
totalRows: response.rows.length,
|
|
211
|
+
},
|
|
212
|
+
fullDetailCommand: 'proteum perf top --full',
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const printCompactCompare = (response: TPerfCompareResponse) => {
|
|
217
|
+
printAgentResponse({
|
|
218
|
+
summary: `Perf compare ${response.groupBy}: ${response.rows.length} rows`,
|
|
219
|
+
data: {
|
|
220
|
+
groupBy: response.groupBy,
|
|
221
|
+
baseline: response.baseline,
|
|
222
|
+
target: response.target,
|
|
223
|
+
rows: compactList(response.rows, 8),
|
|
224
|
+
totalRows: response.rows.length,
|
|
225
|
+
},
|
|
226
|
+
fullDetailCommand: 'proteum perf compare --full',
|
|
227
|
+
});
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const printCompactMemory = (response: TPerfMemoryResponse) => {
|
|
231
|
+
printAgentResponse({
|
|
232
|
+
summary: `Perf memory ${response.groupBy}: ${response.rows.length} rows`,
|
|
233
|
+
data: {
|
|
234
|
+
groupBy: response.groupBy,
|
|
235
|
+
window: response.window,
|
|
236
|
+
rows: compactList(response.rows, 8),
|
|
237
|
+
totalRows: response.rows.length,
|
|
238
|
+
},
|
|
239
|
+
fullDetailCommand: 'proteum perf memory --full',
|
|
240
|
+
});
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const printCompactRequest = (response: TPerfRequestResponse) => {
|
|
244
|
+
printAgentResponse({
|
|
245
|
+
summary: `${response.request.requestId}: ${response.request.method} ${response.request.path} total=${formatDuration(response.request.totalDurationMs)} cpu=${formatDuration(response.request.cpuTotalMs)} sql=${formatDuration(response.request.sqlDurationMs)}`,
|
|
246
|
+
data: {
|
|
247
|
+
request: {
|
|
248
|
+
requestId: response.request.requestId,
|
|
249
|
+
method: response.request.method,
|
|
250
|
+
path: response.request.path,
|
|
251
|
+
statusCode: response.request.statusCode,
|
|
252
|
+
routeLabel: response.request.routeLabel,
|
|
253
|
+
controllerLabel: response.request.controllerLabel,
|
|
254
|
+
totalDurationMs: response.request.totalDurationMs,
|
|
255
|
+
cpuTotalMs: response.request.cpuTotalMs,
|
|
256
|
+
sqlDurationMs: response.request.sqlDurationMs,
|
|
257
|
+
callDurationMs: response.request.callDurationMs,
|
|
258
|
+
renderDurationMs: response.request.renderDurationMs,
|
|
259
|
+
selfDurationMs: response.request.selfDurationMs,
|
|
260
|
+
heapDeltaBytes: response.request.heapDeltaBytes,
|
|
261
|
+
},
|
|
262
|
+
stages: compactList(response.request.stages, 8),
|
|
263
|
+
hotCalls: compactList(response.request.hottestCalls, 6),
|
|
264
|
+
chain: compactList(response.request.chain || [], 8),
|
|
265
|
+
hotSql: compactList(response.request.hottestSqlQueries, 6).map(compactSql),
|
|
266
|
+
},
|
|
267
|
+
nextActions: [
|
|
268
|
+
{
|
|
269
|
+
label: 'Diagnose Request',
|
|
270
|
+
command: `proteum diagnose ${quoteCommandArgument(response.request.path)}`,
|
|
271
|
+
reason: 'Combine this request with owner, diagnostics, suspects, and logs.',
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
label: 'Trace Events',
|
|
275
|
+
command: `proteum trace show ${quoteCommandArgument(response.request.requestId)} --events`,
|
|
276
|
+
reason: 'Open raw event detail only if the compact waterfall is insufficient.',
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
fullDetailCommand: `proteum perf request ${quoteCommandArgument(response.request.requestId)} --full`,
|
|
280
|
+
});
|
|
281
|
+
};
|
|
282
|
+
|
|
183
283
|
export const run = async () => {
|
|
184
284
|
const action = getAction();
|
|
185
|
-
const
|
|
285
|
+
const shouldPrintFull = cli.args.full === true;
|
|
286
|
+
const shouldPrintHuman = cli.args.human === true;
|
|
186
287
|
const groupBy = typeof cli.args.groupBy === 'string' && cli.args.groupBy ? cli.args.groupBy : 'path';
|
|
187
288
|
const limit =
|
|
188
289
|
typeof cli.args.limit === 'string' && cli.args.limit ? Math.max(1, Number.parseInt(cli.args.limit, 10) || 12) : 12;
|
|
@@ -192,12 +293,13 @@ export const run = async () => {
|
|
|
192
293
|
const response = await requestJson<TPerfTopResponse>(
|
|
193
294
|
`/__proteum/perf/top?${new URLSearchParams({ groupBy, limit: String(limit), since }).toString()}`,
|
|
194
295
|
);
|
|
195
|
-
if (
|
|
296
|
+
if (shouldPrintFull) {
|
|
196
297
|
printJson(response);
|
|
197
298
|
return;
|
|
198
299
|
}
|
|
199
300
|
|
|
200
|
-
console.log(renderTop(response));
|
|
301
|
+
if (shouldPrintHuman) console.log(renderTop(response));
|
|
302
|
+
else printCompactTop(response);
|
|
201
303
|
return;
|
|
202
304
|
}
|
|
203
305
|
|
|
@@ -212,12 +314,13 @@ export const run = async () => {
|
|
|
212
314
|
target: targetWindow,
|
|
213
315
|
}).toString()}`,
|
|
214
316
|
);
|
|
215
|
-
if (
|
|
317
|
+
if (shouldPrintFull) {
|
|
216
318
|
printJson(response);
|
|
217
319
|
return;
|
|
218
320
|
}
|
|
219
321
|
|
|
220
|
-
console.log(renderCompare(response));
|
|
322
|
+
if (shouldPrintHuman) console.log(renderCompare(response));
|
|
323
|
+
else printCompactCompare(response);
|
|
221
324
|
return;
|
|
222
325
|
}
|
|
223
326
|
|
|
@@ -226,12 +329,13 @@ export const run = async () => {
|
|
|
226
329
|
const response = await requestJson<TPerfMemoryResponse>(
|
|
227
330
|
`/__proteum/perf/memory?${new URLSearchParams({ groupBy, limit: String(limit), since }).toString()}`,
|
|
228
331
|
);
|
|
229
|
-
if (
|
|
332
|
+
if (shouldPrintFull) {
|
|
230
333
|
printJson(response);
|
|
231
334
|
return;
|
|
232
335
|
}
|
|
233
336
|
|
|
234
|
-
console.log(renderMemory(response));
|
|
337
|
+
if (shouldPrintHuman) console.log(renderMemory(response));
|
|
338
|
+
else printCompactMemory(response);
|
|
235
339
|
return;
|
|
236
340
|
}
|
|
237
341
|
|
|
@@ -241,10 +345,11 @@ export const run = async () => {
|
|
|
241
345
|
const response = await requestJson<TPerfRequestResponse>(
|
|
242
346
|
`/__proteum/perf/request?${new URLSearchParams({ query: requestTarget }).toString()}`,
|
|
243
347
|
);
|
|
244
|
-
if (
|
|
348
|
+
if (shouldPrintFull) {
|
|
245
349
|
printJson(response);
|
|
246
350
|
return;
|
|
247
351
|
}
|
|
248
352
|
|
|
249
|
-
console.log(renderRequest(response));
|
|
353
|
+
if (shouldPrintHuman) console.log(renderRequest(response));
|
|
354
|
+
else printCompactRequest(response);
|
|
250
355
|
};
|