proteum 2.2.9 → 2.4.1
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 +10 -4
- package/README.md +58 -15
- package/agents/project/AGENTS.md +53 -10
- package/agents/project/DOCUMENTATION.md +1326 -0
- package/agents/project/app-root/AGENTS.md +2 -2
- package/agents/project/diagnostics.md +12 -7
- package/agents/project/optimizations.md +1 -0
- package/agents/project/root/AGENTS.md +24 -9
- package/agents/project/tests/AGENTS.md +7 -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/dev.ts +148 -25
- package/cli/commands/diagnose.ts +138 -5
- package/cli/commands/doctor.ts +24 -4
- package/cli/commands/explain.ts +134 -6
- package/cli/commands/mcp.ts +133 -0
- package/cli/commands/orient.ts +93 -3
- package/cli/commands/perf.ts +118 -13
- package/cli/commands/runtime.ts +234 -0
- package/cli/commands/trace.ts +116 -21
- package/cli/mcp/router.ts +1010 -0
- package/cli/presentation/commands.ts +93 -26
- package/cli/presentation/devSession.ts +2 -0
- package/cli/presentation/help.ts +1 -1
- package/cli/runtime/commands.ts +215 -24
- package/cli/runtime/devSessions.ts +328 -2
- package/cli/runtime/mcpDaemon.ts +288 -0
- package/cli/runtime/ports.ts +151 -0
- package/cli/utils/agentOutput.ts +46 -0
- package/cli/utils/agents.ts +194 -51
- package/cli/utils/appRoots.ts +232 -0
- package/common/dev/diagnostics.ts +1 -1
- package/common/dev/inspection.ts +22 -7
- package/common/dev/mcpPayloads.ts +1150 -0
- package/common/dev/mcpServer.ts +287 -0
- package/docs/agent-routing.md +137 -0
- package/docs/dev-commands.md +2 -0
- package/docs/dev-sessions.md +4 -1
- package/docs/diagnostics.md +70 -24
- package/docs/mcp.md +206 -0
- package/docs/migrate-from-2.1.3.md +14 -6
- package/docs/request-tracing.md +12 -6
- package/package.json +11 -3
- package/server/app/devMcp.ts +204 -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/server/services/router/request/ip.test.cjs +0 -1
- package/tests/agents-utils.test.cjs +92 -14
- package/tests/cli-mcp-command.test.cjs +262 -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 +117 -9
- package/tests/eslint-rules.test.cjs +0 -1
- package/tests/inspection.test.cjs +66 -0
- package/tests/mcp.test.cjs +873 -0
- package/tests/router-cache-config.test.cjs +73 -0
- package/vitest.config.mjs +9 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import net from 'net';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import got from 'got';
|
|
6
|
+
|
|
7
|
+
export type TProteumRuntimePortProbe = {
|
|
8
|
+
app?: {
|
|
9
|
+
appRoot?: string;
|
|
10
|
+
identifier?: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
};
|
|
13
|
+
available: boolean;
|
|
14
|
+
error?: string;
|
|
15
|
+
listening: boolean;
|
|
16
|
+
matchesApp: boolean;
|
|
17
|
+
port: number;
|
|
18
|
+
proteum: boolean;
|
|
19
|
+
publicUrl: string;
|
|
20
|
+
statusCode?: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type TDevPortInspection = {
|
|
24
|
+
canStartOnConfiguredPort: boolean;
|
|
25
|
+
hmr: {
|
|
26
|
+
available: boolean;
|
|
27
|
+
port: number;
|
|
28
|
+
};
|
|
29
|
+
recommendedPort?: number;
|
|
30
|
+
router: TProteumRuntimePortProbe;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const normalizePath = (value: string) => {
|
|
34
|
+
const resolved = path.resolve(value);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
return path.normalize(fs.realpathSync(resolved));
|
|
38
|
+
} catch {
|
|
39
|
+
return path.normalize(resolved);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const isTcpPortAvailable = async (port: number) =>
|
|
44
|
+
await new Promise<boolean>((resolve) => {
|
|
45
|
+
const server = net.createServer();
|
|
46
|
+
|
|
47
|
+
server.once('error', () => {
|
|
48
|
+
resolve(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
server.once('listening', () => {
|
|
52
|
+
server.close(() => resolve(true));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
server.listen(port, '127.0.0.1');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const areTcpPortsAvailable = async (ports: number[]) => {
|
|
59
|
+
const availability = await Promise.all(ports.map((port) => isTcpPortAvailable(port)));
|
|
60
|
+
return availability.every(Boolean);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const findAvailableDevPort = async (startPort: number, { maxOffset = 30 }: { maxOffset?: number } = {}) => {
|
|
64
|
+
const normalizedStartPort = Math.max(1, Math.floor(startPort));
|
|
65
|
+
|
|
66
|
+
for (let port = normalizedStartPort; port <= normalizedStartPort + maxOffset; port += 1) {
|
|
67
|
+
if (await areTcpPortsAvailable([port, port + 1])) return port;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return undefined;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const probeProteumRuntimePort = async ({
|
|
74
|
+
appRoot,
|
|
75
|
+
port,
|
|
76
|
+
}: {
|
|
77
|
+
appRoot: string;
|
|
78
|
+
port: number;
|
|
79
|
+
}): Promise<TProteumRuntimePortProbe> => {
|
|
80
|
+
const publicUrl = `http://localhost:${port}`;
|
|
81
|
+
const available = await isTcpPortAvailable(port);
|
|
82
|
+
|
|
83
|
+
if (available) {
|
|
84
|
+
return {
|
|
85
|
+
available: true,
|
|
86
|
+
listening: false,
|
|
87
|
+
matchesApp: false,
|
|
88
|
+
port,
|
|
89
|
+
proteum: false,
|
|
90
|
+
publicUrl,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const response = await got(`http://127.0.0.1:${port}/__proteum/explain?section=app`, {
|
|
96
|
+
responseType: 'json',
|
|
97
|
+
retry: { limit: 0 },
|
|
98
|
+
throwHttpErrors: false,
|
|
99
|
+
timeout: { request: 700 },
|
|
100
|
+
});
|
|
101
|
+
const body = response.body as { app?: { root?: unknown; identity?: { identifier?: unknown; name?: unknown } } };
|
|
102
|
+
const root = typeof body.app?.root === 'string' ? body.app.root : undefined;
|
|
103
|
+
const identifier = typeof body.app?.identity?.identifier === 'string' ? body.app.identity.identifier : undefined;
|
|
104
|
+
const name = typeof body.app?.identity?.name === 'string' ? body.app.identity.name : undefined;
|
|
105
|
+
const proteum = response.statusCode < 400 && Boolean(root || identifier || name);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
app: proteum ? { appRoot: root, identifier, name } : undefined,
|
|
109
|
+
available: false,
|
|
110
|
+
error: proteum ? undefined : `Proteum explain endpoint returned HTTP ${response.statusCode}.`,
|
|
111
|
+
listening: true,
|
|
112
|
+
matchesApp: Boolean(root && normalizePath(root) === normalizePath(appRoot)),
|
|
113
|
+
port,
|
|
114
|
+
proteum,
|
|
115
|
+
publicUrl,
|
|
116
|
+
statusCode: response.statusCode,
|
|
117
|
+
};
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return {
|
|
120
|
+
available: false,
|
|
121
|
+
error: error instanceof Error ? error.message : String(error),
|
|
122
|
+
listening: true,
|
|
123
|
+
matchesApp: false,
|
|
124
|
+
port,
|
|
125
|
+
proteum: false,
|
|
126
|
+
publicUrl,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const inspectDevPort = async ({
|
|
132
|
+
appRoot,
|
|
133
|
+
port,
|
|
134
|
+
}: {
|
|
135
|
+
appRoot: string;
|
|
136
|
+
port: number;
|
|
137
|
+
}): Promise<TDevPortInspection> => {
|
|
138
|
+
const router = await probeProteumRuntimePort({ appRoot, port });
|
|
139
|
+
const hmr = {
|
|
140
|
+
port: port + 1,
|
|
141
|
+
available: await isTcpPortAvailable(port + 1),
|
|
142
|
+
};
|
|
143
|
+
const canStartOnConfiguredPort = router.available && hmr.available;
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
canStartOnConfiguredPort,
|
|
147
|
+
hmr,
|
|
148
|
+
recommendedPort: canStartOnConfiguredPort ? port : await findAvailableDevPort(port + 1),
|
|
149
|
+
router,
|
|
150
|
+
};
|
|
151
|
+
};
|
|
@@ -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
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
import fs from 'fs-extra';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { logVerbose } from '../runtime/verbose';
|
|
9
|
+
import { createStartDevCommand, findProteumAppRootsUnder, readProteumAppRootSummary } from './appRoots';
|
|
9
10
|
|
|
10
11
|
/*----------------------------------
|
|
11
12
|
- TYPES
|
|
12
13
|
----------------------------------*/
|
|
13
14
|
|
|
14
|
-
type TProjectInstructionArgs = { coreRoot: string };
|
|
15
|
+
type TProjectInstructionArgs = { appRoot?: string; coreRoot: string; includeMonorepoRegistry?: boolean; monorepoRoot?: string };
|
|
15
16
|
type TConfigureProjectAgentInstructionsArgs = {
|
|
16
17
|
appRoot: string;
|
|
17
18
|
coreRoot: string;
|
|
@@ -23,6 +24,7 @@ type TConfigureProjectAgentInstructionsArgs = {
|
|
|
23
24
|
type TAgentInstructionDefinition = {
|
|
24
25
|
projectPath: string;
|
|
25
26
|
ensureParentDir?: boolean;
|
|
27
|
+
content?: 'router' | 'source';
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
type TEnsureInstructionFilesResult = {
|
|
@@ -60,33 +62,41 @@ const managedInstructionSectionEnd = '<!-- proteum-instructions:end -->';
|
|
|
60
62
|
const managedInstructionSectionIntro = 'This section is managed by `proteum configure agents`.';
|
|
61
63
|
|
|
62
64
|
const sharedRootDocumentInstructionDefinitions: TAgentInstructionDefinition[] = [
|
|
63
|
-
{ projectPath: '
|
|
64
|
-
{ projectPath: '
|
|
65
|
-
{ projectPath: '
|
|
65
|
+
{ projectPath: 'DOCUMENTATION.md', content: 'source' },
|
|
66
|
+
{ projectPath: 'CODING_STYLE.md', content: 'source' },
|
|
67
|
+
{ projectPath: 'diagnostics.md', content: 'source' },
|
|
68
|
+
{ projectPath: 'optimizations.md', content: 'source' },
|
|
66
69
|
];
|
|
67
70
|
|
|
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
|
-
|
|
71
|
+
const sharedAppAreaAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
|
|
72
|
+
{ projectPath: path.join('client', 'AGENTS.md'), content: 'source' },
|
|
73
|
+
{ projectPath: path.join('client', 'pages', 'AGENTS.md'), content: 'source' },
|
|
74
|
+
{ projectPath: path.join('server', 'services', 'AGENTS.md'), content: 'source' },
|
|
75
|
+
{ projectPath: path.join('server', 'routes', 'AGENTS.md'), content: 'source' },
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const sharedTestAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
|
|
79
|
+
{ projectPath: path.join('tests', 'AGENTS.md'), ensureParentDir: true, content: 'source' },
|
|
80
|
+
{ projectPath: path.join('tests', 'e2e', 'AGENTS.md'), ensureParentDir: true, content: 'source' },
|
|
81
|
+
{ projectPath: path.join('tests', 'e2e', 'REAL_WORLD_JOURNEY_TESTS.md'), ensureParentDir: true, content: 'source' },
|
|
74
82
|
];
|
|
75
83
|
|
|
76
84
|
const standaloneAppAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
|
|
77
|
-
{ projectPath: 'AGENTS.md' },
|
|
85
|
+
{ projectPath: 'AGENTS.md', content: 'router' },
|
|
78
86
|
...sharedRootDocumentInstructionDefinitions,
|
|
79
|
-
...
|
|
87
|
+
...sharedAppAreaAgentInstructionDefinitions,
|
|
88
|
+
...sharedTestAgentInstructionDefinitions,
|
|
80
89
|
];
|
|
81
90
|
|
|
82
91
|
const monorepoAppAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
|
|
83
|
-
{ projectPath: 'AGENTS.md' },
|
|
84
|
-
...
|
|
92
|
+
{ projectPath: 'AGENTS.md', content: 'router' },
|
|
93
|
+
...sharedAppAreaAgentInstructionDefinitions,
|
|
85
94
|
];
|
|
86
95
|
|
|
87
96
|
const monorepoRootAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
|
|
88
|
-
{ projectPath: 'AGENTS.md' },
|
|
97
|
+
{ projectPath: 'AGENTS.md', content: 'router' },
|
|
89
98
|
...sharedRootDocumentInstructionDefinitions,
|
|
99
|
+
...sharedTestAgentInstructionDefinitions,
|
|
90
100
|
];
|
|
91
101
|
|
|
92
102
|
const legacyProjectInstructionGitignoreBlockStart = '# Proteum-managed instruction symlinks';
|
|
@@ -123,7 +133,20 @@ export function configureProjectAgentInstructions({
|
|
|
123
133
|
updated: [],
|
|
124
134
|
updatedGitignores: [],
|
|
125
135
|
};
|
|
126
|
-
const
|
|
136
|
+
const appEmbeddedInstructions = renderEmbeddedProjectInstructions({
|
|
137
|
+
appRoot: normalizedAppRoot,
|
|
138
|
+
coreRoot,
|
|
139
|
+
monorepoRoot: normalizedMonorepoRoot,
|
|
140
|
+
});
|
|
141
|
+
const rootEmbeddedInstructions =
|
|
142
|
+
mode === 'monorepo'
|
|
143
|
+
? renderEmbeddedProjectInstructions({
|
|
144
|
+
appRoot: normalizedAppRoot,
|
|
145
|
+
coreRoot,
|
|
146
|
+
includeMonorepoRegistry: true,
|
|
147
|
+
monorepoRoot: normalizedMonorepoRoot,
|
|
148
|
+
})
|
|
149
|
+
: appEmbeddedInstructions;
|
|
127
150
|
|
|
128
151
|
if (mode === 'monorepo' && normalizedMonorepoRoot) {
|
|
129
152
|
result.monorepoRoot = normalizedMonorepoRoot;
|
|
@@ -134,7 +157,7 @@ export function configureProjectAgentInstructions({
|
|
|
134
157
|
rootInstructions,
|
|
135
158
|
'[agents]',
|
|
136
159
|
path.join(coreRoot, 'agents', 'project'),
|
|
137
|
-
|
|
160
|
+
rootEmbeddedInstructions,
|
|
138
161
|
{
|
|
139
162
|
dryRun,
|
|
140
163
|
overwriteBlockedPaths: normalizedOverwriteBlockedPaths,
|
|
@@ -152,7 +175,7 @@ export function configureProjectAgentInstructions({
|
|
|
152
175
|
appInstructions,
|
|
153
176
|
'[agents]',
|
|
154
177
|
path.join(coreRoot, 'agents', 'project'),
|
|
155
|
-
|
|
178
|
+
appEmbeddedInstructions,
|
|
156
179
|
{
|
|
157
180
|
dryRun,
|
|
158
181
|
overwriteBlockedPaths: normalizedOverwriteBlockedPaths,
|
|
@@ -163,7 +186,7 @@ export function configureProjectAgentInstructions({
|
|
|
163
186
|
if (mode === 'monorepo') {
|
|
164
187
|
const retiredAppRootFiles = removeManagedInstructionFiles(
|
|
165
188
|
normalizedAppRoot,
|
|
166
|
-
sharedRootDocumentInstructionDefinitions,
|
|
189
|
+
[...sharedRootDocumentInstructionDefinitions, ...sharedTestAgentInstructionDefinitions],
|
|
167
190
|
'[agents]',
|
|
168
191
|
path.join(coreRoot, 'agents', 'project'),
|
|
169
192
|
{
|
|
@@ -174,7 +197,9 @@ export function configureProjectAgentInstructions({
|
|
|
174
197
|
}
|
|
175
198
|
|
|
176
199
|
const appGitignoreCleanupInstructions =
|
|
177
|
-
mode === 'monorepo'
|
|
200
|
+
mode === 'monorepo'
|
|
201
|
+
? [...appInstructions, ...sharedRootDocumentInstructionDefinitions, ...sharedTestAgentInstructionDefinitions]
|
|
202
|
+
: appInstructions;
|
|
178
203
|
|
|
179
204
|
if (
|
|
180
205
|
!dryRun &&
|
|
@@ -299,13 +324,21 @@ function ensureInstructionFiles(
|
|
|
299
324
|
continue;
|
|
300
325
|
}
|
|
301
326
|
|
|
327
|
+
const instructionContent = renderProjectInstructionContent({
|
|
328
|
+
instructionDefinition,
|
|
329
|
+
managedSourceRoot,
|
|
330
|
+
managedSectionContent,
|
|
331
|
+
});
|
|
302
332
|
const existingState = inspectExistingPath({
|
|
303
333
|
managedSourceRoot,
|
|
304
334
|
projectFilepath,
|
|
305
335
|
});
|
|
306
336
|
|
|
307
337
|
if (existingState.kind === 'file') {
|
|
308
|
-
const nextContent =
|
|
338
|
+
const nextContent =
|
|
339
|
+
instructionDefinition.content === 'source'
|
|
340
|
+
? instructionContent
|
|
341
|
+
: upsertManagedInstructionSection(existingState.content, instructionContent);
|
|
309
342
|
if (nextContent === existingState.content) {
|
|
310
343
|
result.skipped.push(relativeProjectPath);
|
|
311
344
|
continue;
|
|
@@ -320,7 +353,7 @@ function ensureInstructionFiles(
|
|
|
320
353
|
if (existingState.kind === 'managed-different') {
|
|
321
354
|
if (!dryRun) {
|
|
322
355
|
fs.removeSync(projectFilepath);
|
|
323
|
-
fs.writeFileSync(projectFilepath,
|
|
356
|
+
fs.writeFileSync(projectFilepath, instructionContent);
|
|
324
357
|
}
|
|
325
358
|
result.updated.push(relativeProjectPath);
|
|
326
359
|
logVerbose(`${logPrefix} Updated ${relativeProjectPath}`);
|
|
@@ -336,14 +369,14 @@ function ensureInstructionFiles(
|
|
|
336
369
|
if (existingState.kind === 'blocked') {
|
|
337
370
|
if (!dryRun) {
|
|
338
371
|
fs.removeSync(projectFilepath);
|
|
339
|
-
fs.writeFileSync(projectFilepath,
|
|
372
|
+
fs.writeFileSync(projectFilepath, instructionContent);
|
|
340
373
|
}
|
|
341
374
|
result.overwritten.push(relativeProjectPath);
|
|
342
375
|
logVerbose(`${logPrefix} Replaced ${relativeProjectPath}`);
|
|
343
376
|
continue;
|
|
344
377
|
}
|
|
345
378
|
|
|
346
|
-
if (!dryRun) fs.writeFileSync(projectFilepath,
|
|
379
|
+
if (!dryRun) fs.writeFileSync(projectFilepath, instructionContent);
|
|
347
380
|
result.created.push(relativeProjectPath);
|
|
348
381
|
logVerbose(`${logPrefix} Created ${relativeProjectPath}`);
|
|
349
382
|
}
|
|
@@ -482,52 +515,162 @@ function mergeInstructionResults(
|
|
|
482
515
|
result.blocked.push(...next.blocked.map((entry) => formatResultPath(rootDir, entry)));
|
|
483
516
|
}
|
|
484
517
|
|
|
485
|
-
function
|
|
486
|
-
|
|
487
|
-
|
|
518
|
+
function renderProjectInstructionContent({
|
|
519
|
+
instructionDefinition,
|
|
520
|
+
managedSourceRoot,
|
|
521
|
+
managedSectionContent,
|
|
522
|
+
}: {
|
|
523
|
+
instructionDefinition: TAgentInstructionDefinition;
|
|
524
|
+
managedSourceRoot: string;
|
|
525
|
+
managedSectionContent: string;
|
|
526
|
+
}) {
|
|
527
|
+
if (instructionDefinition.content !== 'source') return managedSectionContent;
|
|
528
|
+
|
|
529
|
+
return renderSingleProjectInstruction({
|
|
530
|
+
managedSourceRoot,
|
|
531
|
+
projectPath: instructionDefinition.projectPath,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function renderSingleProjectInstruction({
|
|
536
|
+
managedSourceRoot,
|
|
537
|
+
projectPath,
|
|
538
|
+
}: {
|
|
539
|
+
managedSourceRoot: string;
|
|
540
|
+
projectPath: string;
|
|
541
|
+
}) {
|
|
542
|
+
const sourceFilepath = path.join(managedSourceRoot, projectPath);
|
|
543
|
+
if (!fs.existsSync(sourceFilepath)) throw new Error(`Missing project instruction source file: ${sourceFilepath}`);
|
|
488
544
|
|
|
489
|
-
const
|
|
545
|
+
const content = fs.readFileSync(sourceFilepath, 'utf8');
|
|
546
|
+
const demotedContent = demoteMarkdownHeadings(content).trim();
|
|
490
547
|
const lines = [
|
|
491
548
|
managedInstructionSectionHeader,
|
|
492
549
|
managedInstructionSectionStart,
|
|
493
550
|
'',
|
|
494
551
|
managedInstructionSectionIntro,
|
|
495
552
|
'',
|
|
553
|
+
`## Source: ${normalizeProjectPathForGitignore(projectPath)}`,
|
|
554
|
+
'',
|
|
496
555
|
];
|
|
497
556
|
|
|
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
|
-
|
|
557
|
+
if (demotedContent) lines.push(demotedContent, '');
|
|
506
558
|
lines.push(managedInstructionSectionEnd, '');
|
|
507
559
|
|
|
508
560
|
return lines.join('\n');
|
|
509
561
|
}
|
|
510
562
|
|
|
511
|
-
function
|
|
512
|
-
|
|
563
|
+
function renderMonorepoAppRegistry({
|
|
564
|
+
appRoot,
|
|
565
|
+
monorepoRoot,
|
|
566
|
+
}: {
|
|
567
|
+
appRoot?: string;
|
|
568
|
+
monorepoRoot?: string;
|
|
569
|
+
}) {
|
|
570
|
+
if (!monorepoRoot || !appRoot || path.resolve(monorepoRoot) === path.resolve(appRoot)) return [];
|
|
571
|
+
|
|
572
|
+
const appRoots = findProteumAppRootsUnder(monorepoRoot);
|
|
573
|
+
if (appRoots.length === 0) return [];
|
|
513
574
|
|
|
514
|
-
|
|
515
|
-
const filepath = path.join(currentDir, entry.name);
|
|
575
|
+
const summaries = appRoots.map((candidate) => readProteumAppRootSummary(candidate, monorepoRoot));
|
|
516
576
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
577
|
+
return [
|
|
578
|
+
'## Known Proteum Apps',
|
|
579
|
+
'',
|
|
580
|
+
'This is a monorepo root wrapper. Do not start `npx proteum dev` from this root; start it from one app root below.',
|
|
581
|
+
'',
|
|
582
|
+
...summaries.map((summary) => {
|
|
583
|
+
const marker = path.resolve(summary.appRoot) === path.resolve(appRoot) ? ' (current configured app)' : '';
|
|
584
|
+
const port = summary.manifest?.routerPort ? `, default port ${summary.manifest.routerPort}` : '';
|
|
585
|
+
const command = createStartDevCommand({
|
|
586
|
+
appRoot: summary.appRoot,
|
|
587
|
+
baseRoot: monorepoRoot,
|
|
588
|
+
port: summary.manifest?.routerPort,
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
return `- ${summary.relativeAppRoot || summary.appRoot}${marker}${port}: ${command}`;
|
|
592
|
+
}),
|
|
593
|
+
'',
|
|
594
|
+
];
|
|
595
|
+
}
|
|
521
596
|
|
|
522
|
-
|
|
597
|
+
function renderEmbeddedProjectInstructions({ appRoot, coreRoot, includeMonorepoRegistry = false, monorepoRoot }: TProjectInstructionArgs) {
|
|
598
|
+
const agentSourceRoot = path.join(coreRoot, 'agents', 'project');
|
|
599
|
+
if (!fs.existsSync(agentSourceRoot)) throw new Error(`Missing project instruction source root: ${agentSourceRoot}`);
|
|
523
600
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
601
|
+
const lines = [
|
|
602
|
+
managedInstructionSectionHeader,
|
|
603
|
+
managedInstructionSectionStart,
|
|
604
|
+
'',
|
|
605
|
+
managedInstructionSectionIntro,
|
|
606
|
+
'',
|
|
607
|
+
'## Agent Routing Contract',
|
|
608
|
+
'',
|
|
609
|
+
'Proteum CLI and MCP outputs are optimized for agents. Do not load the whole instruction corpus up front.',
|
|
610
|
+
'',
|
|
611
|
+
'Detailed Proteum contracts are intentionally split into the files listed in the routing table below. They are not deleted; load only the file that matches the current task, or use MCP `workflow_start` / `instructions_resolve { projectId }` to get the routed set.',
|
|
612
|
+
'',
|
|
613
|
+
'1. When a Proteum MCP client is available, call MCP `workflow_start` first. Pass `cwd` when `projectId` is not known, or pass the stable `projectId` from `projects_list` when it is known.',
|
|
614
|
+
'2. Use the `projectId` returned by live `workflow_start` for every follow-up app-bound MCP tool. If `workflow_start` is ambiguous or returns offline candidates, call MCP `project_resolve { cwd }`, select the intended app root, follow its port-inspected next action when needed, then retry `workflow_start`.',
|
|
615
|
+
'3. After `projectId` is selected, use MCP `runtime_status`, `orient`, `instructions_resolve`, `explain_summary`, `route_candidates`, `doctor`, `diagnose`, `trace_show`, `perf_request`, and `logs_tail` for read-only runtime, owner, instruction, route, trace, perf, and log reads.',
|
|
616
|
+
'4. Do not run CLI equivalents after a successful MCP result for the same read. Do not run broad source searches for route/page/controller ownership after `workflow_start`, `orient`, or `explain_summary` already returned the owner.',
|
|
617
|
+
'5. Treat selected instruction previews returned by MCP as the instruction source for read-only discovery and diagnostics. Read full files only before edits or git writes, when the returned `fullRead`/`fullReadPolicy` requires it, or when the preview is insufficient.',
|
|
618
|
+
'6. Use `npx proteum runtime status` before starting a dev server only when MCP runtime status is unavailable, so an existing tracked session can be reused and the configured router/HMR ports can be checked without probing page bodies. If it says health is unreachable, do not run `diagnose`, `trace`, or `perf`; stop/repair/start the dev session first.',
|
|
619
|
+
'7. During `npx proteum dev`, Proteum ensures one managed machine MCP daemon is running and routes app-bound reads to the read-only runtime endpoint at `/__proteum/mcp` instead of spawning equivalent CLI diagnostics.',
|
|
620
|
+
'8. If machine MCP routing fails, run `npx proteum mcp status` and `npx proteum runtime status`; if no live session exists, use the exact next action from MCP offline routing or runtime status instead of assuming the manifest default port. If the same app already responds on the configured port without live tracking, use or repair that runtime instead of starting another server.',
|
|
621
|
+
'9. If a live session exists but runtime/MCP is unreachable, stop the listed session file first, then start dev again. Do not start a second dev server in the same worktree or a second managed MCP daemon. Then retry MCP `workflow_start`.',
|
|
622
|
+
'10. Use MCP `diagnose { projectId, path }` for request-time issues before raw trace, perf, browser, or broad source search; use `npx proteum diagnose <target>` only as fallback or final terminal evidence.',
|
|
623
|
+
'11. Use `route_candidates`, `explain_summary`, or `npx proteum explain owner <query>` to pick routes. Do not run `npx proteum explain --routes --full` unless compact route/owner tools explicitly cannot answer the raw route-array question.',
|
|
624
|
+
'12. Use `--full`, `--manifest`, `--events`, or MCP `detail: "full"` only when compact output says the omitted detail is needed.',
|
|
625
|
+
'',
|
|
626
|
+
'CLI remains the reproducible surface for `dev`, `build`, `check`, `verify`, migrations, and final command evidence. MCP remains read-only and returns compact `proteum-mcp-v1` JSON.',
|
|
627
|
+
'',
|
|
628
|
+
...(includeMonorepoRegistry ? renderMonorepoAppRegistry({ appRoot, monorepoRoot }) : []),
|
|
629
|
+
'## Always-On Safety',
|
|
630
|
+
'',
|
|
631
|
+
'- Never edit generated files under `.proteum`.',
|
|
632
|
+
'- Never create or edit Prisma migration files manually.',
|
|
633
|
+
'- Never run schema-mutating SQL such as `ALTER TABLE`, `CREATE TABLE`, `DROP TABLE`, or `CREATE INDEX`.',
|
|
634
|
+
'- 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.',
|
|
635
|
+
'- For production changes, add or update focused unit tests for touched behavior when applicable, targeting 100% meaningful coverage for changed production paths.',
|
|
636
|
+
'- Do not run `git restore` or `git reset`.',
|
|
637
|
+
'- Keep `proteum dev` sessions tracked with explicit session files and do not replace another live session.',
|
|
638
|
+
'',
|
|
639
|
+
'## Triggered Instruction Reads',
|
|
640
|
+
'',
|
|
641
|
+
'Keep this root file as a router. MCP-selected previews are enough for read-only discovery and diagnostics. Read the referenced full instruction file only before edits or git writes, when `fullRead`/`fullReadPolicy` requires it, or when the preview is insufficient.',
|
|
642
|
+
'',
|
|
643
|
+
'- Git lifecycle (`commit`, `and commit`, `stage`, `push`, `PR`, pull request): read Root contract fallback before any git write.',
|
|
644
|
+
'- Before finishing production code changes: read Root contract fallback, `CODING_STYLE.md`, `tests/AGENTS.md`, and any touched area `AGENTS.md`.',
|
|
645
|
+
'- Runtime-visible, request-time, router, SSR, browser, or controller behavior: read Root contract fallback and `diagnostics.md` for verification routing.',
|
|
646
|
+
'- Non-trivial feature, product, business-rule, UX, copy, or docs changes: read `DOCUMENTATION.md` before editing.',
|
|
647
|
+
'- Implementation edits: read `CODING_STYLE.md` before editing, plus the matching area file from the routing table.',
|
|
648
|
+
'',
|
|
649
|
+
'## Routing Table',
|
|
650
|
+
'',
|
|
651
|
+
'- Non-trivial coding tasks, feature docs, product intent, acceptance criteria, or docs updates: read `DOCUMENTATION.md`.',
|
|
652
|
+
'- Raw errors, failing requests, traces, perf, or reproduction: read `diagnostics.md`.',
|
|
653
|
+
'- Implementation edits: read `CODING_STYLE.md` before editing.',
|
|
654
|
+
'- Client files or pages: read `client/AGENTS.md`; for page route/data/render work also read `client/pages/AGENTS.md`.',
|
|
655
|
+
'- Server services: read `server/services/AGENTS.md`.',
|
|
656
|
+
'- Manual server routes: read `server/routes/AGENTS.md`.',
|
|
657
|
+
'- Unit tests, integration tests, or test-area work: read `tests/AGENTS.md`.',
|
|
658
|
+
'- E2E work: read `tests/AGENTS.md`, `tests/e2e/AGENTS.md`, and `tests/e2e/REAL_WORLD_JOURNEY_TESTS.md`.',
|
|
659
|
+
'- Package, runtime, build, or client-performance decisions: read `optimizations.md` after implementation or when explicitly optimizing.',
|
|
660
|
+
'',
|
|
661
|
+
'## Canonical Source Map',
|
|
662
|
+
'',
|
|
663
|
+
`- Root contract fallback: ${normalizeProjectPathForGitignore(path.join(coreRoot, 'agents', 'project', 'AGENTS.md'))}`,
|
|
664
|
+
`- Documentation fallback: ${normalizeProjectPathForGitignore(path.join(coreRoot, 'agents', 'project', 'DOCUMENTATION.md'))}`,
|
|
665
|
+
`- Diagnostics fallback: ${normalizeProjectPathForGitignore(path.join(coreRoot, 'agents', 'project', 'diagnostics.md'))}`,
|
|
666
|
+
`- Optimization fallback: ${normalizeProjectPathForGitignore(path.join(coreRoot, 'agents', 'project', 'optimizations.md'))}`,
|
|
667
|
+
`- Coding style fallback: ${normalizeProjectPathForGitignore(path.join(coreRoot, 'agents', 'project', 'CODING_STYLE.md'))}`,
|
|
668
|
+
'',
|
|
669
|
+
];
|
|
670
|
+
|
|
671
|
+
lines.push(managedInstructionSectionEnd, '');
|
|
529
672
|
|
|
530
|
-
return
|
|
673
|
+
return lines.join('\n');
|
|
531
674
|
}
|
|
532
675
|
|
|
533
676
|
function demoteMarkdownHeadings(content: string) {
|