proteum 2.3.0 → 2.4.2
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 +8 -3
- package/README.md +20 -15
- package/agents/project/AGENTS.md +16 -10
- package/agents/project/DOCUMENTATION.md +1326 -0
- package/agents/project/app-root/AGENTS.md +2 -2
- package/agents/project/diagnostics.md +10 -9
- package/agents/project/optimizations.md +1 -1
- package/agents/project/root/AGENTS.md +15 -8
- package/agents/project/server/services/AGENTS.md +1 -0
- package/agents/project/tests/AGENTS.md +1 -0
- package/cli/commands/db.ts +160 -0
- package/cli/commands/dev.ts +148 -25
- package/cli/commands/diagnose.ts +2 -0
- package/cli/commands/explain.ts +38 -9
- package/cli/commands/mcp.ts +126 -9
- package/cli/commands/orient.ts +44 -17
- package/cli/commands/runtime.ts +100 -17
- package/cli/mcp/router.ts +1028 -0
- package/cli/presentation/commands.ts +56 -25
- package/cli/presentation/help.ts +1 -1
- package/cli/runtime/commands.ts +163 -21
- package/cli/runtime/devSessions.ts +328 -2
- package/cli/runtime/mcpDaemon.ts +288 -0
- package/cli/runtime/ports.ts +151 -0
- package/cli/utils/agents.ts +94 -17
- package/cli/utils/appRoots.ts +232 -0
- package/common/dev/database.ts +226 -0
- package/common/dev/diagnostics.ts +1 -1
- package/common/dev/inspection.ts +8 -1
- package/common/dev/mcpPayloads.ts +456 -17
- package/common/dev/mcpServer.ts +51 -0
- package/docs/agent-routing.md +32 -21
- package/docs/dev-commands.md +1 -1
- package/docs/dev-sessions.md +3 -1
- package/docs/diagnostics.md +21 -20
- package/docs/mcp.md +114 -50
- package/docs/migrate-from-2.1.3.md +3 -5
- package/docs/request-tracing.md +3 -3
- package/package.json +10 -3
- package/server/app/devDiagnostics.ts +92 -0
- package/server/app/devMcp.ts +55 -0
- package/server/services/prisma/mariadb.ts +7 -3
- package/server/services/router/http/index.ts +25 -0
- package/server/services/router/request/ip.test.cjs +0 -1
- package/tests/agents-utils.test.cjs +58 -3
- package/tests/cli-mcp-command.test.cjs +327 -0
- package/tests/codex-mcp-usage.test.cjs +307 -0
- package/tests/dev-sessions.test.cjs +113 -0
- package/tests/dev-transpile-watch.test.cjs +0 -1
- package/tests/eslint-rules.test.cjs +0 -1
- package/tests/inspection.test.cjs +0 -1
- package/tests/mcp.test.cjs +769 -2
- package/tests/router-cache-config.test.cjs +0 -1
- package/vitest.config.mjs +9 -0
- package/cli/mcp/provider.ts +0 -365
- package/cli/mcp/stdio.ts +0 -16
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { TDevConsoleLogLevel, TDevConsoleLogsResponse } from './console';
|
|
2
|
+
import type { TDatabaseReadQueryResponse } from './database';
|
|
2
3
|
import type { TDoctorResponse } from './diagnostics';
|
|
3
4
|
import { buildExplainSummaryItems } from './diagnostics';
|
|
4
|
-
import type
|
|
5
|
+
import { explainOwner, type TDiagnoseResponse, type TExplainOwnerResponse, type TOrientResponse } from './inspection';
|
|
5
6
|
import type { TPerfRequestResponse, TPerfTopResponse } from './performance';
|
|
6
7
|
import type { TProteumManifest } from './proteumManifest';
|
|
7
8
|
import type { TRequestTrace } from './requestTrace';
|
|
@@ -38,6 +39,7 @@ type TNodeFs = {
|
|
|
38
39
|
|
|
39
40
|
type TNodePath = {
|
|
40
41
|
dirname: (filepath: string) => string;
|
|
42
|
+
isAbsolute: (filepath: string) => boolean;
|
|
41
43
|
join: (...segments: string[]) => string;
|
|
42
44
|
relative: (from: string, to: string) => string;
|
|
43
45
|
resolve: (...segments: string[]) => string;
|
|
@@ -72,6 +74,95 @@ export const truncateForMcp = (value: string, max = maxTextLength) =>
|
|
|
72
74
|
|
|
73
75
|
export const compactList = <TValue>(values: TValue[], limit: number) => values.slice(0, Math.max(0, limit));
|
|
74
76
|
|
|
77
|
+
export type TTriggeredInstructionRead = {
|
|
78
|
+
file: string;
|
|
79
|
+
reason: string;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const matchesInstructionTrigger = (query: string, pattern: RegExp) => pattern.test(query);
|
|
83
|
+
|
|
84
|
+
const resolveRootContractFallbackFile = (rootAgentsFile?: string) => {
|
|
85
|
+
if (fs === undefined || path === undefined || !rootAgentsFile || !fileExists(rootAgentsFile)) return undefined;
|
|
86
|
+
|
|
87
|
+
const content = fs.readFileSync(rootAgentsFile, 'utf8');
|
|
88
|
+
const match = content.match(/Root contract fallback:\s+(.+?)\s*$/m);
|
|
89
|
+
const candidate = match?.[1]?.trim();
|
|
90
|
+
if (!candidate) return undefined;
|
|
91
|
+
|
|
92
|
+
const filepath = path.isAbsolute(candidate) ? candidate : path.resolve(path.dirname(rootAgentsFile), candidate);
|
|
93
|
+
return fileExists(filepath) ? filepath : undefined;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export const resolveTriggeredInstructionReads = ({
|
|
97
|
+
codingStyle,
|
|
98
|
+
diagnostics,
|
|
99
|
+
documentation,
|
|
100
|
+
optimizations,
|
|
101
|
+
query,
|
|
102
|
+
rootAgentsFile,
|
|
103
|
+
}: {
|
|
104
|
+
codingStyle?: string;
|
|
105
|
+
diagnostics?: string;
|
|
106
|
+
documentation?: string;
|
|
107
|
+
optimizations?: string;
|
|
108
|
+
query: string;
|
|
109
|
+
rootAgentsFile?: string;
|
|
110
|
+
}) => {
|
|
111
|
+
const normalizedQuery = query.toLowerCase();
|
|
112
|
+
const reads = new Map<string, TTriggeredInstructionRead>();
|
|
113
|
+
const addRead = (file: string | undefined, reason: string) => {
|
|
114
|
+
if (!file || !fileExists(file) || reads.has(file)) return;
|
|
115
|
+
reads.set(file, { file, reason });
|
|
116
|
+
};
|
|
117
|
+
const rootContract = resolveRootContractFallbackFile(rootAgentsFile);
|
|
118
|
+
const looksLikeGitLifecycle = matchesInstructionTrigger(
|
|
119
|
+
normalizedQuery,
|
|
120
|
+
/\b(commit|stage|push)\b|\band commit\b|\bpr\b|pull[- ]requests?|git add|git commit/,
|
|
121
|
+
);
|
|
122
|
+
const looksLikeFinishLifecycle = matchesInstructionTrigger(
|
|
123
|
+
normalizedQuery,
|
|
124
|
+
/\b(finish|finishing|done|complete|completion|final|validate|validation|verify|verification)\b/,
|
|
125
|
+
);
|
|
126
|
+
const looksLikeRuntimeVisible = matchesInstructionTrigger(
|
|
127
|
+
normalizedQuery,
|
|
128
|
+
/\b(runtime|request-time|request time|router|ssr|browser-visible|browser visible|controller|diagnose|trace|perf|repro|reproduction|failing|error|bug)\b/,
|
|
129
|
+
);
|
|
130
|
+
const looksLikeImplementationEdit = matchesInstructionTrigger(
|
|
131
|
+
normalizedQuery,
|
|
132
|
+
/\b(implement|change|edit|update|modify|fix|add|remove|refactor|increase|decrease|code)\b/,
|
|
133
|
+
);
|
|
134
|
+
const looksLikeProductOrDocs = matchesInstructionTrigger(
|
|
135
|
+
normalizedQuery,
|
|
136
|
+
/\b(feature|product|business|acceptance|docs|documentation|ux|copy|onboarding|pricing|commercial|semantics)\b/,
|
|
137
|
+
);
|
|
138
|
+
const looksLikeOptimization = matchesInstructionTrigger(
|
|
139
|
+
normalizedQuery,
|
|
140
|
+
/\b(optimize|optimization|performance|package|dependency|build|bundle)\b/,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
if (looksLikeGitLifecycle) {
|
|
144
|
+
addRead(rootContract, 'Git lifecycle trigger; read the canonical root contract before any git write.');
|
|
145
|
+
}
|
|
146
|
+
if (looksLikeFinishLifecycle) {
|
|
147
|
+
addRead(rootContract, 'Finish or verification trigger; read the canonical root lifecycle contract.');
|
|
148
|
+
}
|
|
149
|
+
if (looksLikeRuntimeVisible) {
|
|
150
|
+
addRead(rootContract, 'Runtime-visible behavior trigger; read the canonical root verification contract.');
|
|
151
|
+
addRead(diagnostics, 'Runtime, request, trace, perf, reproduction, or error trigger.');
|
|
152
|
+
}
|
|
153
|
+
if (looksLikeImplementationEdit) {
|
|
154
|
+
addRead(codingStyle, 'Implementation edit trigger; read coding style before editing.');
|
|
155
|
+
}
|
|
156
|
+
if (looksLikeProductOrDocs) {
|
|
157
|
+
addRead(documentation, 'Feature, product, business-rule, UX, copy, or docs trigger.');
|
|
158
|
+
}
|
|
159
|
+
if (looksLikeOptimization) {
|
|
160
|
+
addRead(optimizations, 'Package, build, runtime, performance, or optimization trigger.');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return [...reads.values()];
|
|
164
|
+
};
|
|
165
|
+
|
|
75
166
|
export const createMcpPayload = <TData extends object>({
|
|
76
167
|
data,
|
|
77
168
|
nextActions,
|
|
@@ -178,6 +269,23 @@ export const compactOrientationResponse = (response: TOrientResponse) => {
|
|
|
178
269
|
const summary = topOwner
|
|
179
270
|
? `${response.query} -> ${topOwner.kind} ${topOwner.label} (${topOwner.scopeLabel})`
|
|
180
271
|
: `${response.query} -> no manifest owner matched`;
|
|
272
|
+
const topPath =
|
|
273
|
+
topOwner && (topOwner.kind === 'route' || topOwner.kind === 'controller') && topOwner.label.startsWith('/')
|
|
274
|
+
? topOwner.label
|
|
275
|
+
: response.query.startsWith('/')
|
|
276
|
+
? response.query
|
|
277
|
+
: undefined;
|
|
278
|
+
const triggered = resolveTriggeredInstructionReads({
|
|
279
|
+
codingStyle: response.guidance.codingStyle,
|
|
280
|
+
diagnostics: response.guidance.diagnostics,
|
|
281
|
+
documentation: response.guidance.documentation,
|
|
282
|
+
optimizations: response.guidance.optimizations,
|
|
283
|
+
query: response.normalizedQuery || response.query,
|
|
284
|
+
rootAgentsFile:
|
|
285
|
+
path !== undefined && response.app.repoRoot !== response.app.appRoot
|
|
286
|
+
? path.join(response.app.repoRoot, 'AGENTS.md')
|
|
287
|
+
: response.guidance.agents,
|
|
288
|
+
});
|
|
181
289
|
|
|
182
290
|
return createMcpPayload({
|
|
183
291
|
summary,
|
|
@@ -190,8 +298,19 @@ export const compactOrientationResponse = (response: TOrientResponse) => {
|
|
|
190
298
|
totalReturned: response.owner.matches.length,
|
|
191
299
|
},
|
|
192
300
|
instructions: {
|
|
193
|
-
mustRead: [
|
|
301
|
+
mustRead: [
|
|
302
|
+
...new Set([
|
|
303
|
+
response.guidance.agents,
|
|
304
|
+
...response.guidance.areaAgents,
|
|
305
|
+
...triggered.map((entry) => entry.file),
|
|
306
|
+
]),
|
|
307
|
+
],
|
|
308
|
+
triggered,
|
|
194
309
|
readWhen: [
|
|
310
|
+
{
|
|
311
|
+
file: response.guidance.documentation,
|
|
312
|
+
when: 'Non-trivial coding tasks that need the smallest `/docs` pack and post-change docs updates.',
|
|
313
|
+
},
|
|
195
314
|
{
|
|
196
315
|
file: response.guidance.diagnostics,
|
|
197
316
|
when: 'Raw errors, failing requests, traces, perf regressions, or reproduction work.',
|
|
@@ -214,11 +333,39 @@ export const compactOrientationResponse = (response: TOrientResponse) => {
|
|
|
214
333
|
},
|
|
215
334
|
warnings: response.warnings,
|
|
216
335
|
},
|
|
217
|
-
nextActions:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
336
|
+
nextActions: [
|
|
337
|
+
...(topOwner
|
|
338
|
+
? [
|
|
339
|
+
{
|
|
340
|
+
label: 'Explain Summary',
|
|
341
|
+
tool: 'explain_summary',
|
|
342
|
+
toolArgs: { query: response.query },
|
|
343
|
+
reason: 'Use MCP owner summary before broad manifest or source searches.',
|
|
344
|
+
},
|
|
345
|
+
]
|
|
346
|
+
: []),
|
|
347
|
+
...(topPath
|
|
348
|
+
? [
|
|
349
|
+
{
|
|
350
|
+
label: 'Diagnose Route',
|
|
351
|
+
tool: 'diagnose',
|
|
352
|
+
toolArgs: { path: topPath, query: response.query },
|
|
353
|
+
reason: 'Use the compact runtime diagnosis before CLI diagnose, raw traces, or browser work.',
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
label: 'Perf Request',
|
|
357
|
+
tool: 'perf_request',
|
|
358
|
+
toolArgs: { query: topPath },
|
|
359
|
+
reason: 'Use the compact request waterfall before raw perf detail.',
|
|
360
|
+
},
|
|
361
|
+
]
|
|
362
|
+
: []),
|
|
363
|
+
...response.nextSteps.map((step) => ({
|
|
364
|
+
command: step.command,
|
|
365
|
+
label: step.label,
|
|
366
|
+
reason: step.reason,
|
|
367
|
+
})),
|
|
368
|
+
],
|
|
222
369
|
});
|
|
223
370
|
};
|
|
224
371
|
|
|
@@ -233,6 +380,12 @@ export const compactExplainSummary = ({
|
|
|
233
380
|
}) => {
|
|
234
381
|
if (owner) {
|
|
235
382
|
const topOwner = owner.matches[0];
|
|
383
|
+
const topPath =
|
|
384
|
+
topOwner && (topOwner.kind === 'route' || topOwner.kind === 'controller') && topOwner.label.startsWith('/')
|
|
385
|
+
? topOwner.label
|
|
386
|
+
: query && query.startsWith('/')
|
|
387
|
+
? query
|
|
388
|
+
: undefined;
|
|
236
389
|
return createMcpPayload({
|
|
237
390
|
summary: topOwner
|
|
238
391
|
? `${query || owner.query} -> ${topOwner.kind} ${topOwner.label} (${topOwner.scopeLabel})`
|
|
@@ -247,6 +400,22 @@ export const compactExplainSummary = ({
|
|
|
247
400
|
},
|
|
248
401
|
manifest: summarizeManifest(manifest),
|
|
249
402
|
},
|
|
403
|
+
nextActions: topPath
|
|
404
|
+
? [
|
|
405
|
+
{
|
|
406
|
+
label: 'Diagnose Route',
|
|
407
|
+
tool: 'diagnose',
|
|
408
|
+
toolArgs: { path: topPath, query: query || owner.query },
|
|
409
|
+
reason: 'Use compact runtime diagnosis before CLI diagnose or raw trace detail.',
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
label: 'Perf Request',
|
|
413
|
+
tool: 'perf_request',
|
|
414
|
+
toolArgs: { query: topPath },
|
|
415
|
+
reason: 'Use compact request waterfall before raw perf detail.',
|
|
416
|
+
},
|
|
417
|
+
]
|
|
418
|
+
: undefined,
|
|
250
419
|
});
|
|
251
420
|
}
|
|
252
421
|
|
|
@@ -320,6 +489,7 @@ export const compactDiagnoseResponse = (response: TDiagnoseResponse) => {
|
|
|
320
489
|
instructions: response.orientation
|
|
321
490
|
? {
|
|
322
491
|
mustRead: [...new Set([response.orientation.guidance.agents, ...response.orientation.guidance.areaAgents])],
|
|
492
|
+
documentation: response.orientation.guidance.documentation,
|
|
323
493
|
diagnostics: response.orientation.guidance.diagnostics,
|
|
324
494
|
codingStyle: response.orientation.guidance.codingStyle,
|
|
325
495
|
optimizations: response.orientation.guidance.optimizations,
|
|
@@ -564,6 +734,30 @@ export const compactLogsResponse = ({
|
|
|
564
734
|
: undefined,
|
|
565
735
|
});
|
|
566
736
|
|
|
737
|
+
export const compactDatabaseReadQueryResponse = (response: TDatabaseReadQueryResponse) =>
|
|
738
|
+
createMcpPayload({
|
|
739
|
+
summary: `${response.kind.toUpperCase()} returned ${response.rows.length}/${response.rowCount} rows in ${response.elapsedMs} ms${response.limited ? ` (limited to ${response.limit})` : ''}.`,
|
|
740
|
+
data: {
|
|
741
|
+
kind: response.kind,
|
|
742
|
+
elapsedMs: response.elapsedMs,
|
|
743
|
+
rowCount: response.rowCount,
|
|
744
|
+
returnedRowCount: response.rows.length,
|
|
745
|
+
limit: response.limit,
|
|
746
|
+
limited: response.limited,
|
|
747
|
+
columns: response.columns,
|
|
748
|
+
rows: response.rows,
|
|
749
|
+
},
|
|
750
|
+
omitted: response.limited
|
|
751
|
+
? [
|
|
752
|
+
{
|
|
753
|
+
reason: `Rows are capped at ${response.limit}. Raise the limit up to 500 or make the read query narrower if more detail is needed.`,
|
|
754
|
+
tool: 'db_query',
|
|
755
|
+
toolArgs: { sql: response.sql, limit: Math.min(response.limit * 2, 500) },
|
|
756
|
+
},
|
|
757
|
+
]
|
|
758
|
+
: undefined,
|
|
759
|
+
});
|
|
760
|
+
|
|
567
761
|
const readPreview = (filepath: string) => {
|
|
568
762
|
if (fs === undefined) return undefined;
|
|
569
763
|
try {
|
|
@@ -618,6 +812,28 @@ const resolveDocumentFile = ({
|
|
|
618
812
|
return nearestRoot ? path.join(nearestRoot, relativeFilepath) : undefined;
|
|
619
813
|
};
|
|
620
814
|
|
|
815
|
+
const fullInstructionReadPolicy = {
|
|
816
|
+
default: 'Use selected previews as the instruction source for read-only discovery and diagnostics.',
|
|
817
|
+
requiredWhen: [
|
|
818
|
+
'editing files governed by the selected scope',
|
|
819
|
+
'performing git writes such as stage, commit, push, or PR work',
|
|
820
|
+
'changing schema, auth, runtime, generated contracts, or framework integration behavior',
|
|
821
|
+
'the compact preview is insufficient for the current decision',
|
|
822
|
+
],
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
const inferInstructionReadMode = (reason: string) =>
|
|
826
|
+
/git lifecycle|implementation edit|finish or verification|schema|migration/i.test(reason)
|
|
827
|
+
? 'full-before-action'
|
|
828
|
+
: 'preview-first';
|
|
829
|
+
|
|
830
|
+
const createSelectedInstruction = (file: string, reason: string) => ({
|
|
831
|
+
file,
|
|
832
|
+
fullRead: inferInstructionReadMode(reason),
|
|
833
|
+
preview: readPreview(file),
|
|
834
|
+
reason,
|
|
835
|
+
});
|
|
836
|
+
|
|
621
837
|
export const resolveInstructionRouting = ({
|
|
622
838
|
appRoot,
|
|
623
839
|
query = '',
|
|
@@ -627,7 +843,7 @@ export const resolveInstructionRouting = ({
|
|
|
627
843
|
}) => {
|
|
628
844
|
const normalizedQuery = query.trim();
|
|
629
845
|
const repoRoot = findLikelyRepoRoot(appRoot);
|
|
630
|
-
const selected = new Map<string,
|
|
846
|
+
const selected = new Map<string, ReturnType<typeof createSelectedInstruction>>();
|
|
631
847
|
const readWhen: Array<{ file?: string; when: string }> = [];
|
|
632
848
|
const addInstruction = (relativeFilepath: string, reason: string, preferAppRoot = true) => {
|
|
633
849
|
if (path === undefined) return;
|
|
@@ -635,7 +851,7 @@ export const resolveInstructionRouting = ({
|
|
|
635
851
|
for (const root of [...new Set(roots)]) {
|
|
636
852
|
const filepath = path.join(root, relativeFilepath);
|
|
637
853
|
if (!fileExists(filepath)) continue;
|
|
638
|
-
selected.set(filepath,
|
|
854
|
+
selected.set(filepath, createSelectedInstruction(filepath, reason));
|
|
639
855
|
return;
|
|
640
856
|
}
|
|
641
857
|
};
|
|
@@ -644,14 +860,16 @@ export const resolveInstructionRouting = ({
|
|
|
644
860
|
readWhen.push({ file: filepath, when });
|
|
645
861
|
};
|
|
646
862
|
const lowerQuery = normalizedQuery.toLowerCase();
|
|
647
|
-
const
|
|
863
|
+
const looksLikeRoutePath = /(^|\s)\/[a-z0-9_./:-]*/i.test(lowerQuery);
|
|
864
|
+
const looksLikePage = looksLikeRoutePath || lowerQuery.includes('client/pages') || lowerQuery.includes('.tsx');
|
|
648
865
|
const looksLikeClient = looksLikePage || lowerQuery.includes('client/') || lowerQuery.includes('component') || lowerQuery.includes('island');
|
|
649
866
|
const looksLikeServerRoute =
|
|
650
867
|
lowerQuery.includes('server/routes') ||
|
|
651
868
|
lowerQuery.includes('route') ||
|
|
652
869
|
lowerQuery.includes('sitemap') ||
|
|
653
870
|
lowerQuery.includes('rss') ||
|
|
654
|
-
|
|
871
|
+
/^\/api(\/|$)/.test(lowerQuery) ||
|
|
872
|
+
/\s\/api(\/|$)/.test(lowerQuery);
|
|
655
873
|
const looksLikeService =
|
|
656
874
|
lowerQuery.includes('server/services') ||
|
|
657
875
|
lowerQuery.includes('.controller') ||
|
|
@@ -670,6 +888,23 @@ export const resolveInstructionRouting = ({
|
|
|
670
888
|
addInstruction('tests/e2e/REAL_WORLD_JOURNEY_TESTS.md', 'Real-world journey coverage may be in scope.');
|
|
671
889
|
}
|
|
672
890
|
|
|
891
|
+
const appAgentsFile = resolveDocumentFile({ appRoot, repoRoot, relativeFilepath: 'AGENTS.md' });
|
|
892
|
+
const repoAgentsFile = path !== undefined && repoRoot !== appRoot ? path.join(repoRoot, 'AGENTS.md') : undefined;
|
|
893
|
+
for (const triggered of resolveTriggeredInstructionReads({
|
|
894
|
+
codingStyle: resolveDocumentFile({ appRoot, repoRoot, relativeFilepath: 'CODING_STYLE.md' }),
|
|
895
|
+
diagnostics: resolveDocumentFile({ appRoot, repoRoot, relativeFilepath: 'diagnostics.md' }),
|
|
896
|
+
documentation: resolveDocumentFile({ appRoot, repoRoot, relativeFilepath: 'DOCUMENTATION.md' }),
|
|
897
|
+
optimizations: resolveDocumentFile({ appRoot, repoRoot, relativeFilepath: 'optimizations.md' }),
|
|
898
|
+
query: normalizedQuery,
|
|
899
|
+
rootAgentsFile: repoAgentsFile && fileExists(repoAgentsFile) ? repoAgentsFile : appAgentsFile,
|
|
900
|
+
})) {
|
|
901
|
+
selected.set(triggered.file, createSelectedInstruction(triggered.file, triggered.reason));
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
addReadWhen(
|
|
905
|
+
'DOCUMENTATION.md',
|
|
906
|
+
'Read before non-trivial coding tasks to choose the smallest `/docs` pack and update docs after changes.',
|
|
907
|
+
);
|
|
673
908
|
addReadWhen('diagnostics.md', 'Read for raw errors, failing requests, traces, perf regressions, or reproduction work.');
|
|
674
909
|
addReadWhen('CODING_STYLE.md', 'Read before editing implementation files.');
|
|
675
910
|
addReadWhen('optimizations.md', 'Read for client-side implementation, packages, build, runtime, or performance work.');
|
|
@@ -683,6 +918,7 @@ export const resolveInstructionRouting = ({
|
|
|
683
918
|
repoRoot,
|
|
684
919
|
selected: selectedFiles,
|
|
685
920
|
readWhen,
|
|
921
|
+
fullReadPolicy: fullInstructionReadPolicy,
|
|
686
922
|
missingRuntime:
|
|
687
923
|
selectedFiles.length === 0
|
|
688
924
|
? 'No tracked instruction files were found. Run `proteum configure agents` or start `proteum dev` to refresh managed instructions.'
|
|
@@ -691,6 +927,213 @@ export const resolveInstructionRouting = ({
|
|
|
691
927
|
});
|
|
692
928
|
};
|
|
693
929
|
|
|
930
|
+
const chooseWorkflowOwnerQuery = ({
|
|
931
|
+
file,
|
|
932
|
+
query,
|
|
933
|
+
route,
|
|
934
|
+
}: {
|
|
935
|
+
file?: string;
|
|
936
|
+
query?: string;
|
|
937
|
+
route?: string;
|
|
938
|
+
}) => [route, file, query].map((value) => value?.trim()).find((value): value is string => Boolean(value));
|
|
939
|
+
|
|
940
|
+
const chooseWorkflowInstructionQuery = ({
|
|
941
|
+
file,
|
|
942
|
+
query,
|
|
943
|
+
route,
|
|
944
|
+
task,
|
|
945
|
+
}: {
|
|
946
|
+
file?: string;
|
|
947
|
+
query?: string;
|
|
948
|
+
route?: string;
|
|
949
|
+
task?: string;
|
|
950
|
+
}) =>
|
|
951
|
+
[task, query, route, file]
|
|
952
|
+
.map((value) => value?.trim())
|
|
953
|
+
.filter((value): value is string => Boolean(value))
|
|
954
|
+
.join(' ');
|
|
955
|
+
|
|
956
|
+
const isReachableHealth = (health: object | undefined) => {
|
|
957
|
+
if (!health || !('reachable' in health)) return true;
|
|
958
|
+
|
|
959
|
+
return (health as { reachable?: unknown }).reachable === true;
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
const createRuntimeDownNextAction = () => ({
|
|
963
|
+
label: 'Start Dev',
|
|
964
|
+
command: 'proteum dev --session-file var/run/proteum/dev/agents/<task>.json --port <free-port>',
|
|
965
|
+
reason: 'Runtime is not reachable; start or repair one tracked dev session before diagnose, trace, or perf reads.',
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
export const compactWorkflowStartResponse = ({
|
|
969
|
+
contracts,
|
|
970
|
+
doctor,
|
|
971
|
+
file,
|
|
972
|
+
health,
|
|
973
|
+
manifest,
|
|
974
|
+
owner,
|
|
975
|
+
query,
|
|
976
|
+
route,
|
|
977
|
+
runtime,
|
|
978
|
+
task,
|
|
979
|
+
}: {
|
|
980
|
+
contracts: TDoctorResponse;
|
|
981
|
+
doctor: TDoctorResponse;
|
|
982
|
+
file?: string;
|
|
983
|
+
health?: object;
|
|
984
|
+
manifest: TProteumManifest;
|
|
985
|
+
owner?: TExplainOwnerResponse;
|
|
986
|
+
query?: string;
|
|
987
|
+
route?: string;
|
|
988
|
+
runtime?: object;
|
|
989
|
+
task?: string;
|
|
990
|
+
}) => {
|
|
991
|
+
const ownerQuery = chooseWorkflowOwnerQuery({ file, query, route });
|
|
992
|
+
const instructionQuery = chooseWorkflowInstructionQuery({ file, query, route, task });
|
|
993
|
+
const instructions = resolveInstructionRouting({
|
|
994
|
+
appRoot: manifest.app.root,
|
|
995
|
+
query: instructionQuery,
|
|
996
|
+
});
|
|
997
|
+
const topOwner = owner?.matches[0];
|
|
998
|
+
const topPath =
|
|
999
|
+
topOwner && (topOwner.kind === 'route' || topOwner.kind === 'controller') && topOwner.label.startsWith('/')
|
|
1000
|
+
? topOwner.label
|
|
1001
|
+
: route && route.startsWith('/')
|
|
1002
|
+
? route
|
|
1003
|
+
: ownerQuery && ownerQuery.startsWith('/')
|
|
1004
|
+
? ownerQuery
|
|
1005
|
+
: undefined;
|
|
1006
|
+
const runtimeReachable = isReachableHealth(health);
|
|
1007
|
+
|
|
1008
|
+
return createMcpPayload({
|
|
1009
|
+
summary: `${manifest.app.identity.identifier}: workflow start${ownerQuery ? ` for ${ownerQuery}` : ''}; ${instructions.data.selected.length} instruction file${instructions.data.selected.length === 1 ? '' : 's'}`,
|
|
1010
|
+
data: {
|
|
1011
|
+
workflow: {
|
|
1012
|
+
task: task?.trim() || undefined,
|
|
1013
|
+
query: ownerQuery,
|
|
1014
|
+
route: route?.trim() || undefined,
|
|
1015
|
+
file: file?.trim() || undefined,
|
|
1016
|
+
},
|
|
1017
|
+
runtime: {
|
|
1018
|
+
appRoot: manifest.app.root,
|
|
1019
|
+
manifest: summarizeManifest(manifest),
|
|
1020
|
+
runtime,
|
|
1021
|
+
health,
|
|
1022
|
+
},
|
|
1023
|
+
instructions: {
|
|
1024
|
+
selected: compactList(instructions.data.selected, 8),
|
|
1025
|
+
readWhen: compactList(instructions.data.readWhen, 6),
|
|
1026
|
+
fullReadPolicy: fullInstructionReadPolicy,
|
|
1027
|
+
totalSelected: instructions.data.selected.length,
|
|
1028
|
+
},
|
|
1029
|
+
owner: owner
|
|
1030
|
+
? {
|
|
1031
|
+
query: owner.query,
|
|
1032
|
+
normalizedQuery: owner.normalizedQuery,
|
|
1033
|
+
top: topOwner ? compactOwnerMatch(topOwner) : undefined,
|
|
1034
|
+
matches: compactList(owner.matches, 5).map(compactOwnerMatch),
|
|
1035
|
+
totalReturned: owner.matches.length,
|
|
1036
|
+
}
|
|
1037
|
+
: undefined,
|
|
1038
|
+
diagnostics: {
|
|
1039
|
+
doctor: doctor.summary,
|
|
1040
|
+
contracts: contracts.summary,
|
|
1041
|
+
},
|
|
1042
|
+
duplicateAvoidance: [
|
|
1043
|
+
'If owner.top resolves a route or file, do not run broad source searches for the same owner.',
|
|
1044
|
+
'If this runtime block is present, do not run CLI runtime status for the same app.',
|
|
1045
|
+
'If diagnose succeeds for this path or request, do not rerun CLI diagnose for the same read.',
|
|
1046
|
+
'Open full traces, logs, or instruction files only when compact output says the omitted detail is needed.',
|
|
1047
|
+
],
|
|
1048
|
+
},
|
|
1049
|
+
nextActions: [
|
|
1050
|
+
...(!runtimeReachable ? [createRuntimeDownNextAction()] : []),
|
|
1051
|
+
...(topPath && runtimeReachable
|
|
1052
|
+
? [
|
|
1053
|
+
{
|
|
1054
|
+
label: 'Diagnose Route',
|
|
1055
|
+
tool: 'diagnose',
|
|
1056
|
+
toolArgs: { path: topPath, query: ownerQuery || topPath },
|
|
1057
|
+
reason: 'Use compact runtime diagnosis before CLI diagnose, raw traces, browser work, or broad source search.',
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
label: 'Perf Request',
|
|
1061
|
+
tool: 'perf_request',
|
|
1062
|
+
toolArgs: { query: topPath },
|
|
1063
|
+
reason: 'Use the compact request waterfall before raw perf detail.',
|
|
1064
|
+
},
|
|
1065
|
+
]
|
|
1066
|
+
: []),
|
|
1067
|
+
...(!ownerQuery && instructionQuery
|
|
1068
|
+
? [
|
|
1069
|
+
{
|
|
1070
|
+
label: 'Orient Query',
|
|
1071
|
+
tool: 'orient',
|
|
1072
|
+
toolArgs: { query: instructionQuery },
|
|
1073
|
+
reason: 'Use MCP orientation only if the workflow bootstrap did not include a concrete owner query.',
|
|
1074
|
+
},
|
|
1075
|
+
]
|
|
1076
|
+
: []),
|
|
1077
|
+
],
|
|
1078
|
+
omitted: [
|
|
1079
|
+
{
|
|
1080
|
+
reason: 'Full instruction files are omitted. Use selected previews for read-only work; read full files only when the fullReadPolicy requires it.',
|
|
1081
|
+
tool: 'instructions_resolve',
|
|
1082
|
+
toolArgs: { query: instructionQuery },
|
|
1083
|
+
},
|
|
1084
|
+
],
|
|
1085
|
+
});
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
export const compactRouteCandidatesResponse = ({
|
|
1089
|
+
limit = 8,
|
|
1090
|
+
manifest,
|
|
1091
|
+
query,
|
|
1092
|
+
}: {
|
|
1093
|
+
limit?: number;
|
|
1094
|
+
manifest: TProteumManifest;
|
|
1095
|
+
query: string;
|
|
1096
|
+
}) => {
|
|
1097
|
+
const owner = explainOwner(manifest, query);
|
|
1098
|
+
const routeMatches = owner.matches.filter((match) => match.kind === 'route');
|
|
1099
|
+
|
|
1100
|
+
return createMcpPayload({
|
|
1101
|
+
summary:
|
|
1102
|
+
routeMatches.length === 0
|
|
1103
|
+
? `${query} -> no route candidates`
|
|
1104
|
+
: `${query} -> ${routeMatches.length} route candidate${routeMatches.length === 1 ? '' : 's'}`,
|
|
1105
|
+
data: {
|
|
1106
|
+
query,
|
|
1107
|
+
normalizedQuery: owner.normalizedQuery,
|
|
1108
|
+
candidates: compactList(routeMatches, limit).map(compactOwnerMatch),
|
|
1109
|
+
returned: Math.min(routeMatches.length, limit),
|
|
1110
|
+
totalMatches: routeMatches.length,
|
|
1111
|
+
manifest: summarizeManifest(manifest),
|
|
1112
|
+
},
|
|
1113
|
+
nextActions:
|
|
1114
|
+
routeMatches.length > 0
|
|
1115
|
+
? [
|
|
1116
|
+
{
|
|
1117
|
+
label: 'Explain Top Route',
|
|
1118
|
+
tool: 'explain_summary',
|
|
1119
|
+
toolArgs: { query: routeMatches[0].label },
|
|
1120
|
+
reason: 'Inspect the top route owner without dumping raw route arrays.',
|
|
1121
|
+
},
|
|
1122
|
+
]
|
|
1123
|
+
: undefined,
|
|
1124
|
+
omitted:
|
|
1125
|
+
routeMatches.length > limit
|
|
1126
|
+
? [
|
|
1127
|
+
{
|
|
1128
|
+
reason: `Route candidates are capped at ${limit}. Refine the query before requesting raw route arrays.`,
|
|
1129
|
+
tool: 'route_candidates',
|
|
1130
|
+
toolArgs: { query, limit: Math.min(50, limit * 2) },
|
|
1131
|
+
},
|
|
1132
|
+
]
|
|
1133
|
+
: undefined,
|
|
1134
|
+
});
|
|
1135
|
+
};
|
|
1136
|
+
|
|
694
1137
|
export const buildRuntimeStatusPayload = ({
|
|
695
1138
|
appRoot,
|
|
696
1139
|
health,
|
|
@@ -717,7 +1160,7 @@ export const buildRuntimeStatusPayload = ({
|
|
|
717
1160
|
sessions,
|
|
718
1161
|
health,
|
|
719
1162
|
},
|
|
720
|
-
nextActions: runtime
|
|
1163
|
+
nextActions: runtime && isReachableHealth(health)
|
|
721
1164
|
? [
|
|
722
1165
|
{
|
|
723
1166
|
label: 'Diagnose Root',
|
|
@@ -727,10 +1170,6 @@ export const buildRuntimeStatusPayload = ({
|
|
|
727
1170
|
},
|
|
728
1171
|
]
|
|
729
1172
|
: [
|
|
730
|
-
|
|
731
|
-
label: 'Start Dev',
|
|
732
|
-
command: 'proteum dev --session-file var/run/proteum/dev/agents/<task>.json --port <free-port>',
|
|
733
|
-
reason: 'Create a tracked dev session before request-time diagnostics.',
|
|
734
|
-
},
|
|
1173
|
+
createRuntimeDownNextAction(),
|
|
735
1174
|
],
|
|
736
1175
|
});
|