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,232 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import type { Dirent } from 'fs';
|
|
4
|
+
|
|
5
|
+
import { readProteumManifest, type TProteumManifest } from '../compiler/common/proteumManifest';
|
|
6
|
+
|
|
7
|
+
export type TProteumAppRootSummary = {
|
|
8
|
+
appRoot: string;
|
|
9
|
+
hasManifest: boolean;
|
|
10
|
+
manifest?: {
|
|
11
|
+
counts: {
|
|
12
|
+
connectedProjects: number;
|
|
13
|
+
controllers: number;
|
|
14
|
+
routes: number;
|
|
15
|
+
};
|
|
16
|
+
diagnostics: {
|
|
17
|
+
errors: number;
|
|
18
|
+
warnings: number;
|
|
19
|
+
};
|
|
20
|
+
identifier: string;
|
|
21
|
+
name: string;
|
|
22
|
+
routerPort: number;
|
|
23
|
+
};
|
|
24
|
+
manifestError?: string;
|
|
25
|
+
packageManager: 'npm' | 'pnpm' | 'yarn' | 'unknown';
|
|
26
|
+
relativeAppRoot?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const proteumAppRootRequiredEntries = ['package.json', 'identity.config.ts', 'proteum.config.ts', 'client', 'server'];
|
|
30
|
+
const ignoredSearchDirectories = new Set([
|
|
31
|
+
'.cache',
|
|
32
|
+
'.git',
|
|
33
|
+
'.proteum',
|
|
34
|
+
'bin',
|
|
35
|
+
'coverage',
|
|
36
|
+
'dev',
|
|
37
|
+
'node_modules',
|
|
38
|
+
'playwright-report',
|
|
39
|
+
'test-results',
|
|
40
|
+
'var',
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const resolveExistingPath = (value: string) => {
|
|
44
|
+
const resolved = path.resolve(value);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
return fs.realpathSync(resolved);
|
|
48
|
+
} catch {
|
|
49
|
+
return resolved;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const pathEntryExists = (filepath: string) => {
|
|
54
|
+
try {
|
|
55
|
+
fs.lstatSync(filepath);
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const isDirectory = (filepath: string) => {
|
|
63
|
+
try {
|
|
64
|
+
return fs.statSync(filepath).isDirectory();
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const resolveSearchRoot = (value: string) => {
|
|
71
|
+
const resolved = resolveExistingPath(value);
|
|
72
|
+
if (isDirectory(resolved)) return resolved;
|
|
73
|
+
return path.dirname(resolved);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const isProteumAppRoot = (workdir: string) =>
|
|
77
|
+
proteumAppRootRequiredEntries.every((entry) => pathEntryExists(path.join(workdir, entry)));
|
|
78
|
+
|
|
79
|
+
export const findNearestProteumAppRoot = (startPath: string) => {
|
|
80
|
+
let currentPath = resolveSearchRoot(startPath);
|
|
81
|
+
|
|
82
|
+
while (true) {
|
|
83
|
+
if (isProteumAppRoot(currentPath)) return currentPath;
|
|
84
|
+
|
|
85
|
+
const parentPath = path.dirname(currentPath);
|
|
86
|
+
if (parentPath === currentPath) return undefined;
|
|
87
|
+
currentPath = parentPath;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const findProteumAppRootsUnder = (root: string, { maxDepth = 5 }: { maxDepth?: number } = {}) => {
|
|
92
|
+
const searchRoot = resolveSearchRoot(root);
|
|
93
|
+
const appRoots: string[] = [];
|
|
94
|
+
const seen = new Set<string>();
|
|
95
|
+
|
|
96
|
+
const visit = (directory: string, depth: number) => {
|
|
97
|
+
const canonicalDirectory = resolveExistingPath(directory);
|
|
98
|
+
if (seen.has(canonicalDirectory)) return;
|
|
99
|
+
seen.add(canonicalDirectory);
|
|
100
|
+
|
|
101
|
+
if (isProteumAppRoot(canonicalDirectory)) {
|
|
102
|
+
appRoots.push(canonicalDirectory);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (depth >= maxDepth) return;
|
|
107
|
+
|
|
108
|
+
let entries: Dirent[];
|
|
109
|
+
try {
|
|
110
|
+
entries = fs.readdirSync(canonicalDirectory, { withFileTypes: true });
|
|
111
|
+
} catch {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
if (!entry.isDirectory()) continue;
|
|
117
|
+
if (ignoredSearchDirectories.has(entry.name)) continue;
|
|
118
|
+
visit(path.join(canonicalDirectory, entry.name), depth + 1);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
visit(searchRoot, 0);
|
|
123
|
+
|
|
124
|
+
return appRoots.sort((left, right) => left.localeCompare(right));
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const findPackageManager = (appRoot: string): TProteumAppRootSummary['packageManager'] => {
|
|
128
|
+
let currentPath = path.resolve(appRoot);
|
|
129
|
+
|
|
130
|
+
while (true) {
|
|
131
|
+
if (pathEntryExists(path.join(currentPath, 'package-lock.json'))) return 'npm';
|
|
132
|
+
if (pathEntryExists(path.join(currentPath, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
133
|
+
if (pathEntryExists(path.join(currentPath, 'yarn.lock'))) return 'yarn';
|
|
134
|
+
|
|
135
|
+
if (pathEntryExists(path.join(currentPath, '.git'))) return 'unknown';
|
|
136
|
+
|
|
137
|
+
const parentPath = path.dirname(currentPath);
|
|
138
|
+
if (parentPath === currentPath) return 'unknown';
|
|
139
|
+
currentPath = parentPath;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const summarizeManifest = (manifest: TProteumManifest): NonNullable<TProteumAppRootSummary['manifest']> => {
|
|
144
|
+
const errors = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'error').length;
|
|
145
|
+
const warnings = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'warning').length;
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
counts: {
|
|
149
|
+
connectedProjects: manifest.connectedProjects.length,
|
|
150
|
+
controllers: manifest.controllers.length,
|
|
151
|
+
routes: manifest.routes.client.length + manifest.routes.server.length,
|
|
152
|
+
},
|
|
153
|
+
diagnostics: { errors, warnings },
|
|
154
|
+
identifier: manifest.app.identity.identifier,
|
|
155
|
+
name: manifest.app.identity.name,
|
|
156
|
+
routerPort: manifest.env.resolved.routerPort,
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const readProteumAppRootSummary = (appRoot: string, baseRoot?: string): TProteumAppRootSummary => {
|
|
161
|
+
const normalizedAppRoot = resolveExistingPath(appRoot);
|
|
162
|
+
const relativeAppRoot = baseRoot ? path.relative(resolveExistingPath(baseRoot), normalizedAppRoot) || '.' : undefined;
|
|
163
|
+
const summary: TProteumAppRootSummary = {
|
|
164
|
+
appRoot: normalizedAppRoot,
|
|
165
|
+
hasManifest: false,
|
|
166
|
+
packageManager: findPackageManager(normalizedAppRoot),
|
|
167
|
+
relativeAppRoot,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const manifest = readProteumManifest(normalizedAppRoot);
|
|
172
|
+
summary.hasManifest = true;
|
|
173
|
+
summary.manifest = summarizeManifest(manifest);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
summary.manifestError = error instanceof Error ? error.message : String(error);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return summary;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const resolveProteumAppRootContext = (cwd: string) => {
|
|
182
|
+
const normalizedCwd = resolveSearchRoot(cwd);
|
|
183
|
+
const nearestAppRoot = findNearestProteumAppRoot(normalizedCwd);
|
|
184
|
+
const appRoots = nearestAppRoot ? [nearestAppRoot] : findProteumAppRootsUnder(normalizedCwd);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
cwd: normalizedCwd,
|
|
188
|
+
isAppRoot: nearestAppRoot === normalizedCwd,
|
|
189
|
+
isWrapper: !nearestAppRoot && appRoots.length > 0,
|
|
190
|
+
nearestAppRoot,
|
|
191
|
+
appRoots,
|
|
192
|
+
appCandidates: appRoots.map((appRoot) => readProteumAppRootSummary(appRoot, normalizedCwd)),
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export const quoteShellPath = (value: string) => JSON.stringify(value);
|
|
197
|
+
|
|
198
|
+
const createAppScopedCommand = ({
|
|
199
|
+
appRoot,
|
|
200
|
+
baseRoot,
|
|
201
|
+
command,
|
|
202
|
+
}: {
|
|
203
|
+
appRoot: string;
|
|
204
|
+
baseRoot?: string;
|
|
205
|
+
command: string;
|
|
206
|
+
}) => {
|
|
207
|
+
const relativeAppRoot = baseRoot ? path.relative(resolveExistingPath(baseRoot), resolveExistingPath(appRoot)) || '.' : '';
|
|
208
|
+
|
|
209
|
+
if (!relativeAppRoot || relativeAppRoot === '.') return command;
|
|
210
|
+
return `cd ${quoteShellPath(relativeAppRoot)} && ${command}`;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export const createStartDevCommand = ({
|
|
214
|
+
appRoot,
|
|
215
|
+
baseRoot,
|
|
216
|
+
port,
|
|
217
|
+
}: {
|
|
218
|
+
appRoot: string;
|
|
219
|
+
baseRoot?: string;
|
|
220
|
+
port?: number;
|
|
221
|
+
}) => {
|
|
222
|
+
const command = `npx proteum dev --session-file var/run/proteum/dev/agents/<task>.json --port ${port || '<free-port>'}`;
|
|
223
|
+
|
|
224
|
+
return createAppScopedCommand({ appRoot, baseRoot, command });
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export const createRuntimeStatusCommand = ({ appRoot, baseRoot }: { appRoot: string; baseRoot?: string }) =>
|
|
228
|
+
createAppScopedCommand({
|
|
229
|
+
appRoot,
|
|
230
|
+
baseRoot,
|
|
231
|
+
command: 'npx proteum runtime status',
|
|
232
|
+
});
|
|
@@ -125,7 +125,7 @@ export const buildExplainSummaryItems = (manifest: TProteumManifest) => {
|
|
|
125
125
|
`Routes: ${manifest.routes.client.length} client, ${manifest.routes.server.length} server`,
|
|
126
126
|
`Layouts: ${manifest.layouts.length}`,
|
|
127
127
|
`Diagnostics: ${errorsCount} errors, ${warningsCount} warnings`,
|
|
128
|
-
'Use `proteum explain --
|
|
128
|
+
'Use `proteum explain --manifest` for the full manifest or pass section flags with `--full` when raw arrays are required.',
|
|
129
129
|
];
|
|
130
130
|
};
|
|
131
131
|
|
package/common/dev/inspection.ts
CHANGED
|
@@ -61,6 +61,7 @@ export type TTraceAttributionResponse = {
|
|
|
61
61
|
|
|
62
62
|
export type TOrientGuidance = {
|
|
63
63
|
agents: string;
|
|
64
|
+
documentation: string;
|
|
64
65
|
diagnostics: string;
|
|
65
66
|
optimizations: string;
|
|
66
67
|
codingStyle: string;
|
|
@@ -791,6 +792,11 @@ const resolveGuidance = ({
|
|
|
791
792
|
fallbackFilepath: joinPath(fallbackRoot, 'diagnostics.md'),
|
|
792
793
|
relativePath: 'diagnostics.md',
|
|
793
794
|
});
|
|
795
|
+
const documentation = resolveGuidanceFile({
|
|
796
|
+
appRoot: manifest.app.root,
|
|
797
|
+
fallbackFilepath: joinPath(fallbackRoot, 'DOCUMENTATION.md'),
|
|
798
|
+
relativePath: 'DOCUMENTATION.md',
|
|
799
|
+
});
|
|
794
800
|
const optimizations = resolveGuidanceFile({
|
|
795
801
|
appRoot: manifest.app.root,
|
|
796
802
|
fallbackFilepath: joinPath(fallbackRoot, 'optimizations.md'),
|
|
@@ -802,13 +808,14 @@ const resolveGuidance = ({
|
|
|
802
808
|
relativePath: 'CODING_STYLE.md',
|
|
803
809
|
});
|
|
804
810
|
|
|
805
|
-
for (const warning of [agents.warning, diagnostics.warning, optimizations.warning, codingStyle.warning]) {
|
|
811
|
+
for (const warning of [agents.warning, documentation.warning, diagnostics.warning, optimizations.warning, codingStyle.warning]) {
|
|
806
812
|
if (warning) warnings.push(warning);
|
|
807
813
|
}
|
|
808
814
|
|
|
809
815
|
return {
|
|
810
816
|
guidance: {
|
|
811
817
|
agents: agents.filepath,
|
|
818
|
+
documentation: documentation.filepath,
|
|
812
819
|
diagnostics: diagnostics.filepath,
|
|
813
820
|
optimizations: optimizations.filepath,
|
|
814
821
|
codingStyle: codingStyle.filepath,
|
|
@@ -1156,12 +1163,20 @@ export const explainOwner = (manifest: TProteumManifest, query: string): TExplai
|
|
|
1156
1163
|
const normalizedQuery = normalizeText(query);
|
|
1157
1164
|
if (!normalizedQuery) return { matches: [], normalizedQuery, query };
|
|
1158
1165
|
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1166
|
+
const entries = buildManifestEntries(manifest);
|
|
1167
|
+
const scoredMatches =
|
|
1168
|
+
normalizedQuery === '/'
|
|
1169
|
+
? entries
|
|
1170
|
+
.filter((entry) => (entry.kind === 'route' || entry.kind === 'controller') && normalizeText(entry.label) === '/')
|
|
1171
|
+
.map((entry) => toOwnerMatch(entry, 200, ['/']))
|
|
1172
|
+
: entries
|
|
1173
|
+
.map((entry) => {
|
|
1174
|
+
const { score, matchedOn } = scoreOwnerMatch(query, entry);
|
|
1175
|
+
return score > 0 ? toOwnerMatch(entry, score, matchedOn) : undefined;
|
|
1176
|
+
})
|
|
1177
|
+
.filter((match): match is TExplainOwnerMatch => match !== undefined);
|
|
1178
|
+
|
|
1179
|
+
const matches = scoredMatches
|
|
1165
1180
|
.sort((left, right) => right.score - left.score || left.kind.localeCompare(right.kind) || left.label.localeCompare(right.label))
|
|
1166
1181
|
.slice(0, 12);
|
|
1167
1182
|
|