proteum 2.5.3 → 2.5.5
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 +1 -1
- package/README.md +12 -11
- package/agents/project/AGENTS.md +6 -6
- package/agents/project/CODING_STYLE.md +1 -1
- package/agents/project/diagnostics.md +2 -2
- package/agents/project/root/AGENTS.md +6 -6
- package/agents/project/tests/AGENTS.md +1 -1
- package/cli/mcp/router.ts +61 -15
- package/cli/presentation/commands.ts +2 -1
- package/cli/runtime/commands.ts +1 -1
- package/cli/runtime/freshCopyPreflight.ts +767 -0
- package/cli/runtime/worktreeBootstrap.ts +99 -6
- package/docs/agent-routing.md +3 -3
- package/docs/diagnostics.md +3 -3
- package/docs/mcp.md +7 -6
- package/docs/request-tracing.md +1 -1
- package/package.json +1 -1
- package/tests/mcp.test.cjs +98 -1
- package/tests/worktree-bootstrap.test.cjs +75 -0
|
@@ -23,6 +23,13 @@ export type TWorktreeBootstrapMarker = {
|
|
|
23
23
|
copied: boolean;
|
|
24
24
|
copiedAt?: string;
|
|
25
25
|
present: boolean;
|
|
26
|
+
root?: {
|
|
27
|
+
copied: boolean;
|
|
28
|
+
copiedAt?: string;
|
|
29
|
+
filepath: string;
|
|
30
|
+
present: boolean;
|
|
31
|
+
source?: string;
|
|
32
|
+
};
|
|
26
33
|
source?: string;
|
|
27
34
|
};
|
|
28
35
|
packageLockHash?: string;
|
|
@@ -84,6 +91,11 @@ type TWorktreeBootstrapInputs = {
|
|
|
84
91
|
packageLockHash?: string;
|
|
85
92
|
proteumConfigHash?: string;
|
|
86
93
|
proteumVersion: string;
|
|
94
|
+
rootEnv?: {
|
|
95
|
+
filepath: string;
|
|
96
|
+
present: boolean;
|
|
97
|
+
required: boolean;
|
|
98
|
+
};
|
|
87
99
|
};
|
|
88
100
|
|
|
89
101
|
type TRunCaptureResult = {
|
|
@@ -159,6 +171,32 @@ const findVisibleDirectory = (startPath: string, directoryName: string) => {
|
|
|
159
171
|
}
|
|
160
172
|
};
|
|
161
173
|
|
|
174
|
+
const readJsonFile = (filepath: string) => {
|
|
175
|
+
try {
|
|
176
|
+
return fs.readJSONSync(filepath) as Record<string, unknown>;
|
|
177
|
+
} catch {
|
|
178
|
+
return {};
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const hasWorkspaceRootTooling = (workspaceRoot: string) => {
|
|
183
|
+
if (fs.existsSync(path.join(workspaceRoot, 'prisma.config.ts'))) return true;
|
|
184
|
+
|
|
185
|
+
const packageJson = readJsonFile(path.join(workspaceRoot, 'package.json'));
|
|
186
|
+
return Array.isArray(packageJson.workspaces);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const resolveWorkspaceRootEnv = (appRoot: string) => {
|
|
190
|
+
const packageLockFilepath = findNearestExistingPath(appRoot, 'package-lock.json');
|
|
191
|
+
if (!packageLockFilepath) return undefined;
|
|
192
|
+
|
|
193
|
+
const workspaceRoot = path.dirname(packageLockFilepath);
|
|
194
|
+
if (workspaceRoot === normalizePath(appRoot)) return undefined;
|
|
195
|
+
if (!hasWorkspaceRootTooling(workspaceRoot)) return undefined;
|
|
196
|
+
|
|
197
|
+
return path.join(workspaceRoot, '.env');
|
|
198
|
+
};
|
|
199
|
+
|
|
162
200
|
const hashFile = (filepath: string | undefined) => {
|
|
163
201
|
if (!filepath || !fs.existsSync(filepath)) return undefined;
|
|
164
202
|
|
|
@@ -177,6 +215,7 @@ const readMarker = (markerFilepath: string) => {
|
|
|
177
215
|
|
|
178
216
|
const readInputs = (appRoot: string, proteumVersion: string): TWorktreeBootstrapInputs => {
|
|
179
217
|
const packageLockFilepath = findNearestExistingPath(appRoot, 'package-lock.json');
|
|
218
|
+
const rootEnvFilepath = resolveWorkspaceRootEnv(appRoot);
|
|
180
219
|
|
|
181
220
|
return {
|
|
182
221
|
agentsHash: hashFile(path.join(appRoot, 'AGENTS.md')),
|
|
@@ -186,6 +225,13 @@ const readInputs = (appRoot: string, proteumVersion: string): TWorktreeBootstrap
|
|
|
186
225
|
packageLockHash: hashFile(packageLockFilepath),
|
|
187
226
|
proteumConfigHash: hashFile(path.join(appRoot, 'proteum.config.ts')),
|
|
188
227
|
proteumVersion,
|
|
228
|
+
rootEnv: rootEnvFilepath
|
|
229
|
+
? {
|
|
230
|
+
filepath: rootEnvFilepath,
|
|
231
|
+
present: fs.existsSync(rootEnvFilepath),
|
|
232
|
+
required: true,
|
|
233
|
+
}
|
|
234
|
+
: undefined,
|
|
189
235
|
};
|
|
190
236
|
};
|
|
191
237
|
|
|
@@ -217,6 +263,8 @@ const collectStaleReasons = ({
|
|
|
217
263
|
if (marker.agentsHash !== inputs.agentsHash)
|
|
218
264
|
reasons.push({ code: 'worktree-bootstrap/agents-changed', message: 'AGENTS.md changed since bootstrap.' });
|
|
219
265
|
if (!inputs.envPresent) reasons.push({ code: 'worktree-bootstrap/env-missing', message: '.env is missing.' });
|
|
266
|
+
if (inputs.rootEnv?.required && !inputs.rootEnv.present)
|
|
267
|
+
reasons.push({ code: 'worktree-bootstrap/root-env-missing', message: 'Workspace root .env is missing.' });
|
|
220
268
|
if (!inputs.manifestPresent)
|
|
221
269
|
reasons.push({ code: 'worktree-bootstrap/manifest-missing', message: '.proteum/manifest.json is missing.' });
|
|
222
270
|
if (!inputs.nodeModulesPresent && !dependenciesWereIntentionallySkipped(marker, inputs))
|
|
@@ -309,18 +357,63 @@ const resolveDependencyAction = ({
|
|
|
309
357
|
return 'up-to-date';
|
|
310
358
|
};
|
|
311
359
|
|
|
360
|
+
const resolveSourceRootEnv = (source: string) => {
|
|
361
|
+
const sourceWorkspaceRootEnv = resolveWorkspaceRootEnv(source);
|
|
362
|
+
if (sourceWorkspaceRootEnv && fs.existsSync(sourceWorkspaceRootEnv)) return sourceWorkspaceRootEnv;
|
|
363
|
+
|
|
364
|
+
const sourceEnvFilepath = path.join(path.resolve(source), '.env');
|
|
365
|
+
return fs.existsSync(sourceEnvFilepath) ? sourceEnvFilepath : undefined;
|
|
366
|
+
};
|
|
367
|
+
|
|
312
368
|
const requireSourceEnvWhenNeeded = ({ appRoot, source }: { appRoot: string; source?: string }) => {
|
|
313
369
|
const envFilepath = path.join(appRoot, '.env');
|
|
314
|
-
|
|
370
|
+
const rootEnvFilepath = resolveWorkspaceRootEnv(appRoot);
|
|
371
|
+
let copied = false;
|
|
372
|
+
let copiedAt: string | undefined;
|
|
373
|
+
let sourceForEnv: string | undefined;
|
|
315
374
|
|
|
316
|
-
if (!
|
|
375
|
+
if (!fs.existsSync(envFilepath)) {
|
|
376
|
+
if (!source) throw new Error('This worktree is missing .env. Pass --source <app-root> with a readable source .env.');
|
|
317
377
|
|
|
318
|
-
|
|
319
|
-
|
|
378
|
+
const sourceEnvFilepath = path.join(path.resolve(source), '.env');
|
|
379
|
+
if (!fs.existsSync(sourceEnvFilepath)) throw new Error(`Source .env does not exist: ${sourceEnvFilepath}`);
|
|
320
380
|
|
|
321
|
-
|
|
381
|
+
fs.copyFileSync(sourceEnvFilepath, envFilepath);
|
|
382
|
+
copied = true;
|
|
383
|
+
copiedAt = nowIso();
|
|
384
|
+
sourceForEnv = path.resolve(source);
|
|
385
|
+
}
|
|
322
386
|
|
|
323
|
-
|
|
387
|
+
const root: TWorktreeBootstrapMarker['env']['root'] | undefined = rootEnvFilepath
|
|
388
|
+
? {
|
|
389
|
+
copied: false,
|
|
390
|
+
filepath: rootEnvFilepath,
|
|
391
|
+
present: fs.existsSync(rootEnvFilepath),
|
|
392
|
+
}
|
|
393
|
+
: undefined;
|
|
394
|
+
|
|
395
|
+
if (root && !root.present) {
|
|
396
|
+
if (!source) throw new Error('This worktree is missing workspace root .env. Pass --source <app-root> with a readable source .env.');
|
|
397
|
+
|
|
398
|
+
const sourceRootEnvFilepath = resolveSourceRootEnv(source);
|
|
399
|
+
if (!sourceRootEnvFilepath) {
|
|
400
|
+
throw new Error(`Source workspace root .env does not exist and source app .env is missing: ${path.resolve(source)}`);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
fs.copyFileSync(sourceRootEnvFilepath, root.filepath);
|
|
404
|
+
root.copied = true;
|
|
405
|
+
root.copiedAt = nowIso();
|
|
406
|
+
root.present = true;
|
|
407
|
+
root.source = sourceRootEnvFilepath;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
copied,
|
|
412
|
+
...(copiedAt ? { copiedAt } : {}),
|
|
413
|
+
present: fs.existsSync(envFilepath),
|
|
414
|
+
...(root ? { root } : {}),
|
|
415
|
+
...(sourceForEnv ? { source: sourceForEnv } : {}),
|
|
416
|
+
};
|
|
324
417
|
};
|
|
325
418
|
|
|
326
419
|
const writeMarker = (appRoot: string, marker: TWorktreeBootstrapMarker) => {
|
package/docs/agent-routing.md
CHANGED
|
@@ -66,9 +66,9 @@ Use MCP for repeated reads when a client is available:
|
|
|
66
66
|
proteum mcp
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
The machine router discovers live `proteum dev` sessions and offline Proteum app roots under a cwd. `proteum dev` ensures one managed machine MCP daemon is running; terminal `proteum mcp` starts or reuses that daemon and prints a compact central MCP banner with the HTTP client URL, while MCP clients can use stdio. Agents should call MCP `workflow_start` with `cwd` or a known `projectId`, use `project_resolve { cwd }` when routing is ambiguous or offline, and pass the returned live `projectId` to every follow-up app-bound MCP tool. Offline candidates include port-inspected next actions, so agents should follow those instead of guessing the manifest default port. The router forwards to the selected dev-hosted `/__proteum/mcp` endpoint and strips routing fields before the app sees the call.
|
|
69
|
+
The machine router discovers live `proteum dev` sessions and offline Proteum app roots under a cwd. `proteum dev` ensures one managed machine MCP daemon is running; terminal `proteum mcp` starts or reuses that daemon and prints a compact central MCP banner with the HTTP client URL, while MCP clients can use stdio. Agents should call MCP `workflow_start` with `cwd` or a known `projectId`, use `project_resolve { cwd }` when routing is ambiguous or offline, resolve any returned `data.readiness.state="blocked"` fresh-copy setup actions, and pass the returned live `projectId` to every follow-up app-bound MCP tool. Offline candidates include readiness and port-inspected next actions, so agents should follow those instead of guessing the manifest default port. The router forwards to the selected dev-hosted `/__proteum/mcp` endpoint and strips routing fields before the app sees the call.
|
|
70
70
|
|
|
71
|
-
If machine MCP routing returns offline candidates, choose the intended app root and follow that candidate's next
|
|
71
|
+
If machine MCP routing returns offline candidates, choose the intended app root and follow that candidate's readiness and next actions from the app root, not from the monorepo wrapper. In `/.codex/worktrees/`, if `workflow_start` returns a worktree bootstrap block, run `npx proteum worktree init --source <source-app-root>` or the returned `--refresh` command before any runtime read. If machine MCP routing fails, run `proteum mcp status` and `proteum runtime status` from the intended app root; if no live session exists, use the exact Start Dev next action from runtime status so occupied router/HMR ports are avoided. If the same app already responds on the configured port without live tracking, use or repair that runtime instead of starting another server. Do not `curl` normal page routes to identify which app owns a port; use runtime status or Proteum dev-only endpoints. If a live session exists but runtime/MCP is unreachable, stop the listed session file first, then start dev again. Do not run diagnose, trace, or perf reads while runtime health is unreachable. Do not start a second dev server in the same worktree, and do not start a second managed MCP daemon. Then retry MCP `workflow_start`.
|
|
72
72
|
|
|
73
73
|
Prefer CLI over MCP when the result must be reproducible as a shell command, part of verification, or copied into CI/debug instructions.
|
|
74
74
|
|
|
@@ -135,6 +135,6 @@ The latest Product `/domains` benchmark used routed instructions plus the compac
|
|
|
135
135
|
The result confirms the intended routing:
|
|
136
136
|
|
|
137
137
|
- use CLI for reproducible verification and final command evidence
|
|
138
|
-
- use `workflow_start` to collapse project resolution, runtime status, instruction previews, owner summary, and first next actions into one read
|
|
138
|
+
- use `workflow_start` to collapse project resolution, fresh-copy readiness, runtime status, instruction previews, owner summary, and first next actions into one read
|
|
139
139
|
- use machine MCP with `projectId` for repeated runtime reads against an already running app
|
|
140
140
|
- use `instructions_resolve` to refresh routing instead of rereading full instruction files
|
package/docs/diagnostics.md
CHANGED
|
@@ -112,9 +112,9 @@ Default compact command output follows this shape:
|
|
|
112
112
|
|
|
113
113
|
`proteum runtime status` emits the current app manifest summary, tracked dev sessions, selected live session, MCP URL, health status, configured router/HMR port inspection, and a suggested next command. Use it before starting another dev server, and use its Start Dev command instead of probing page bodies when the default port is occupied. If it reports that the same app already responds on the configured port without a live tracked session, use or repair that runtime instead of starting a second server.
|
|
114
114
|
|
|
115
|
-
Inside `/.codex/worktrees/`, `proteum dev`, `proteum refresh`, `proteum runtime status`, `proteum verify`, and MCP `workflow_start` require a fresh `.proteum/worktree-bootstrap.json`. If the marker is missing, run `npx proteum worktree init --source <source-app-root>`. If hashes, `.env`, `.proteum/manifest.json`, `node_modules`, or the Proteum version are stale, run the returned `npx proteum worktree init --source <source-app-root> --refresh` command. `PROTEUM_ALLOW_UNBOOTSTRAPPED_WORKTREE=1` bypasses the block but remains visible in runtime status, doctor diagnostics, and MCP output.
|
|
115
|
+
Inside `/.codex/worktrees/`, `proteum dev`, `proteum refresh`, `proteum runtime status`, `proteum verify`, and MCP `workflow_start` require a fresh `.proteum/worktree-bootstrap.json`. If the marker is missing, run `npx proteum worktree init --source <source-app-root>`. If hashes, app `.env`, workspace-root `.env` for monorepos with root tooling, `.proteum/manifest.json`, `node_modules`, or the Proteum version are stale, run the returned `npx proteum worktree init --source <source-app-root> --refresh` command. `PROTEUM_ALLOW_UNBOOTSTRAPPED_WORKTREE=1` bypasses the block but remains visible in runtime status, doctor diagnostics, and MCP output.
|
|
116
116
|
|
|
117
|
-
During `proteum dev`, `/__proteum/mcp` exposes compact `workflow_start`, `runtime_status`, `orient`, `instructions_resolve`, `route_candidates`, `explain_summary`, `doctor`, `diagnose`, `trace_*`, `perf_*`, and `logs_tail` tools without spawning CLI commands for each repeated read. `proteum dev` also ensures one managed machine `proteum mcp` daemon is running. Through the machine router, call `workflow_start` with `cwd` or a known `projectId`; if routing is ambiguous or returns offline app candidates, use `project_resolve { cwd }`, follow the selected app root's port-inspected next
|
|
117
|
+
During `proteum dev`, `/__proteum/mcp` exposes compact `workflow_start`, `runtime_status`, `orient`, `instructions_resolve`, `route_candidates`, `explain_summary`, `doctor`, `diagnose`, `trace_*`, `perf_*`, and `logs_tail` tools without spawning CLI commands for each repeated read. `proteum dev` also ensures one managed machine `proteum mcp` daemon is running. Through the machine router, call `workflow_start` with `cwd` or a known `projectId`; if routing is ambiguous or returns offline app candidates, use `project_resolve { cwd }`, follow the selected app root's fresh-copy readiness and port-inspected next actions when needed, then pass the selected live `projectId` to follow-up app-bound tools. `workflow_start` returns `data.readiness` with read-only setup checks for `.env`, dependencies, generated manifest state, connected producers, Prisma/client readiness, redacted database URL shape, and local database TCP reachability.
|
|
118
118
|
|
|
119
119
|
MCP tool/resource output follows compact single-line `proteum-mcp-v1` JSON:
|
|
120
120
|
|
|
@@ -251,7 +251,7 @@ Treat these as framework contract failures first. The fix usually belongs at the
|
|
|
251
251
|
|
|
252
252
|
For AI coding agents or automation:
|
|
253
253
|
|
|
254
|
-
1. When MCP is available, call `workflow_start` with `cwd` or a known `projectId`; if routing is ambiguous or returns offline app candidates, call `project_resolve { cwd }`, select the intended app root, start dev from that app root when needed, then retry with the selected stable live `projectId`.
|
|
254
|
+
1. When MCP is available, call `workflow_start` with `cwd` or a known `projectId`; if routing is ambiguous or returns offline app candidates, call `project_resolve { cwd }`, select the intended app root, resolve any returned `data.readiness.state="blocked"` setup actions, start dev from that app root when needed, then retry with the selected stable live `projectId`.
|
|
255
255
|
2. Use the returned `projectId` for MCP `runtime_status`, `orient`, `instructions_resolve`, `route_candidates`, `explain_summary`, `doctor`, `diagnose`, `trace_show`, `perf_request`, and `logs_tail` read-only runtime, owner, route, instruction, trace, perf, and log reads.
|
|
256
256
|
3. Do not run CLI equivalents after a successful MCP result for the same read, and do not run broad source searches for ownership MCP already returned. Use CLI for fallback, `dev`, `build`, `check`, `verify`, migrations, E2E, and final terminal evidence.
|
|
257
257
|
4. Use selected instruction previews for read-only discovery and diagnostics; read full files only before edits or git writes, when returned `fullRead`/`fullReadPolicy` requires it, or when the preview is insufficient.
|
package/docs/mcp.md
CHANGED
|
@@ -28,7 +28,7 @@ The router is read-only. It does not start or stop dev servers, mutate files, re
|
|
|
28
28
|
Use this flow:
|
|
29
29
|
|
|
30
30
|
1. Call MCP `workflow_start` with `cwd` or a known `projectId`.
|
|
31
|
-
2. If the result is ambiguous or returns offline app candidates, call `project_resolve { cwd }`, pick the intended app root, start exactly one `proteum dev` server from that app root when needed, then retry `workflow_start`.
|
|
31
|
+
2. If the result is ambiguous or returns offline app candidates, call `project_resolve { cwd }`, pick the intended app root, resolve any returned `data.readiness.state="blocked"` setup actions, start exactly one `proteum dev` server from that app root when needed, then retry `workflow_start`.
|
|
32
32
|
3. Pass the returned live `projectId` to every follow-up app-bound MCP call.
|
|
33
33
|
4. After an MCP read succeeds, do not run the equivalent CLI command or broad source search for the same state; keep CLI for fallback, validation, and final terminal evidence.
|
|
34
34
|
|
|
@@ -47,7 +47,7 @@ Example tool calls:
|
|
|
47
47
|
{"tool":"db_query","arguments":{"projectId":"prj_0123abcd4567","sql":"SELECT id, email FROM User LIMIT 5","limit":5}}
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
`workflow_start` is the only app-bound bootstrap tool that may resolve from `cwd` when `projectId` is not known. It may return offline app candidates when no matching dev server is running yet. Other app-bound tools require a live `projectId`; if they omit it, the router returns a compact error that tells the agent to call `projects_list` or `project_resolve`. There is no single-project fallback, because wrong-project reads are worse than an explicit routing retry.
|
|
50
|
+
`workflow_start` is the only app-bound bootstrap tool that may resolve from `cwd` when `projectId` is not known. It may return offline app candidates when no matching dev server is running yet. Its machine-router response includes `data.readiness`, a read-only fresh-copy preflight covering app/root `.env` files, dependency install root and package manager, generated Proteum manifest state, local connected producer apps, Prisma schema/client readiness, redacted database URL shape, and local TCP database reachability when the host is local. The preflight adds exact setup commands where safe, such as copying `.env.example`, installing dependencies, running `npx proteum refresh`, generating Prisma Client, checking Prisma migration status, preflighting connected producer apps, and starting `proteum dev` on a checked port. Other app-bound tools require a live `projectId`; if they omit it, the router returns a compact error that tells the agent to call `projects_list` or `project_resolve`. There is no single-project fallback, because wrong-project reads are worse than an explicit routing retry.
|
|
51
51
|
|
|
52
52
|
When the selected app root is inside `/.codex/worktrees/`, `workflow_start` first checks `.proteum/worktree-bootstrap.json`. If the marker is missing or stale, it returns `ok: false` with a single next action such as `npx proteum worktree init --source <source-app-root>` or the same command with `--refresh`. The router does not forward to the app MCP endpoint until bootstrap is complete, unless `PROTEUM_ALLOW_UNBOOTSTRAPPED_WORKTREE=1` is set; bypasses remain visible in MCP, `runtime status`, and `doctor`.
|
|
53
53
|
|
|
@@ -79,9 +79,10 @@ If machine MCP routing fails:
|
|
|
79
79
|
1. Run `proteum mcp status`.
|
|
80
80
|
2. Run `proteum runtime status` from the intended app root. If you are in a monorepo wrapper, use the returned app candidates and exact next action instead of starting dev from the wrapper.
|
|
81
81
|
3. If the app root is inside `/.codex/worktrees/` and runtime status or workflow start reports missing/stale bootstrap, run `proteum worktree init --source <source-app-root>` or the returned `--refresh` command first.
|
|
82
|
-
4. If
|
|
83
|
-
5. If
|
|
84
|
-
6.
|
|
82
|
+
4. If `workflow_start` returns `data.readiness.state="blocked"`, run or resolve the readiness setup actions before starting dev.
|
|
83
|
+
5. If no live app session exists, use the exact Start Dev next action returned by runtime status or `workflow_start`. It checks the configured router/HMR ports and suggests an alternate free port when the manifest default is occupied.
|
|
84
|
+
6. If a live session exists but runtime/MCP is unreachable, stop the listed session file with `proteum dev stop --session-file <path>`, then start dev again.
|
|
85
|
+
7. Retry MCP `workflow_start` and use the returned `projectId`.
|
|
85
86
|
|
|
86
87
|
Offline `project_resolve` and `workflow_start` candidates also inspect configured router/HMR ports before returning `nextAction`. If the configured port already serves the same app but no live machine project is registered, the next action is runtime tracking repair, not starting a second dev server.
|
|
87
88
|
|
|
@@ -122,7 +123,7 @@ App-bound tools require `projectId` when called through `proteum mcp`:
|
|
|
122
123
|
|
|
123
124
|
| Tool | Purpose |
|
|
124
125
|
| --- | --- |
|
|
125
|
-
| `workflow_start` | One-call bootstrap with resolved project, runtime, selected instruction previews, owner summary, doctor summaries, duplicate-avoidance rules, and next actions |
|
|
126
|
+
| `workflow_start` | One-call bootstrap with resolved project, fresh-copy readiness, runtime, selected instruction previews, owner summary, doctor summaries, duplicate-avoidance rules, and next actions |
|
|
126
127
|
| `runtime_status` | Manifest summary, selected runtime, tracked sessions, health, and MCP URL |
|
|
127
128
|
| `orient` | Owner, instruction routing, connected boundaries, and next actions |
|
|
128
129
|
| `instructions_resolve` | Selected instruction files for a query, with short previews and full-read policy |
|
package/docs/request-tracing.md
CHANGED
|
@@ -33,7 +33,7 @@ proteum perf memory --since 1h --group-by controller
|
|
|
33
33
|
|
|
34
34
|
Default trace output is compact `proteum-agent-v1` JSON with counts, failed calls, error events, hot calls, and hot SQL. Use `--events` or `--full` only when raw event details, payload summaries, or SQL text are needed.
|
|
35
35
|
|
|
36
|
-
When an MCP client is available, call `workflow_start` with `cwd` or a known `projectId`, then use the returned live `projectId` with MCP `trace_latest`, `trace_show`, `perf_top`, and `perf_request` for repeated reads against the same running app. If `workflow_start` returns offline app candidates or unreachable runtime health, start or repair exactly one `proteum dev` server from the intended app root before trace or perf reads. Keep the CLI commands for reproducible terminal evidence and final verification logs.
|
|
36
|
+
When an MCP client is available, call `workflow_start` with `cwd` or a known `projectId`, then use the returned live `projectId` with MCP `trace_latest`, `trace_show`, `perf_top`, and `perf_request` for repeated reads against the same running app. If `workflow_start` returns offline app candidates, `data.readiness.state="blocked"`, or unreachable runtime health, resolve the readiness setup actions and start or repair exactly one `proteum dev` server from the intended app root before trace or perf reads. Keep the CLI commands for reproducible terminal evidence and final verification logs.
|
|
37
37
|
|
|
38
38
|
Before reproducing a bug or starting a new test pass:
|
|
39
39
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proteum",
|
|
3
3
|
"description": "LLM-first Opinionated Typescript Framework for web applications.",
|
|
4
|
-
"version": "2.5.
|
|
4
|
+
"version": "2.5.5",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/proteum.git",
|
|
7
7
|
"license": "MIT",
|
package/tests/mcp.test.cjs
CHANGED
|
@@ -99,8 +99,53 @@ const createManifest = (appRoot, overrides = {}) => ({
|
|
|
99
99
|
|
|
100
100
|
const writeProteumAppFixture = (appRoot, manifestOverrides = {}) => {
|
|
101
101
|
writeFile(path.join(appRoot, 'package.json'), '{"name":"fixture"}\n');
|
|
102
|
+
writeFile(path.join(appRoot, 'package-lock.json'), '{"lockfileVersion":3}\n');
|
|
103
|
+
writeFile(
|
|
104
|
+
path.join(appRoot, '.env'),
|
|
105
|
+
[
|
|
106
|
+
'ENV_NAME=local',
|
|
107
|
+
'ENV_PROFILE=dev',
|
|
108
|
+
`PORT=${manifestOverrides.routerPort || 3104}`,
|
|
109
|
+
`URL=http://localhost:${manifestOverrides.routerPort || 3104}`,
|
|
110
|
+
`URL_INTERNAL=http://localhost:${manifestOverrides.routerPort || 3104}`,
|
|
111
|
+
'',
|
|
112
|
+
].join('\n'),
|
|
113
|
+
);
|
|
114
|
+
fs.mkdirSync(path.join(appRoot, 'node_modules'), { recursive: true });
|
|
115
|
+
writeFile(path.join(appRoot, 'identity.config.ts'), 'export default {};\n');
|
|
116
|
+
writeFile(path.join(appRoot, 'proteum.config.ts'), 'export default {};\n');
|
|
117
|
+
writeFile(path.join(appRoot, 'client', 'AGENTS.md'), '# Client\n');
|
|
118
|
+
writeFile(path.join(appRoot, 'client', 'pages', 'AGENTS.md'), '# Pages\n');
|
|
119
|
+
writeFile(path.join(appRoot, 'server', 'AGENTS.md'), '# Server\n');
|
|
120
|
+
writeFile(path.join(appRoot, 'server', 'routes', 'AGENTS.md'), '# Routes\n');
|
|
121
|
+
writeFile(path.join(appRoot, 'AGENTS.md'), '# App\n');
|
|
122
|
+
writeFile(path.join(appRoot, 'diagnostics.md'), '# Diagnostics\n');
|
|
123
|
+
writeFile(path.join(appRoot, '.proteum', 'manifest.json'), JSON.stringify(createManifest(appRoot, manifestOverrides), null, 2));
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const writeFreshCopyFixture = (appRoot, manifestOverrides = {}) => {
|
|
127
|
+
writeFile(path.join(appRoot, 'package.json'), '{"name":"fresh-copy"}\n');
|
|
128
|
+
writeFile(path.join(appRoot, 'package-lock.json'), '{"lockfileVersion":3}\n');
|
|
129
|
+
writeFile(
|
|
130
|
+
path.join(appRoot, '.env.example'),
|
|
131
|
+
[
|
|
132
|
+
'ENV_NAME=local',
|
|
133
|
+
'ENV_PROFILE=dev',
|
|
134
|
+
'PORT=3020',
|
|
135
|
+
'URL=http://localhost:3020',
|
|
136
|
+
'URL_INTERNAL=http://localhost:3020',
|
|
137
|
+
'DATABASE_URL=mysql://user:pass@localhost:3306/app',
|
|
138
|
+
'',
|
|
139
|
+
].join('\n'),
|
|
140
|
+
);
|
|
102
141
|
writeFile(path.join(appRoot, 'identity.config.ts'), 'export default {};\n');
|
|
103
142
|
writeFile(path.join(appRoot, 'proteum.config.ts'), 'export default {};\n');
|
|
143
|
+
writeFile(
|
|
144
|
+
path.join(appRoot, 'prisma', 'schema.prisma'),
|
|
145
|
+
['generator client {', ' provider = "prisma-client-js"', ' output = "../var/prisma"', '}', '', 'datasource db {', ' provider = "mysql"', '}'].join(
|
|
146
|
+
'\n',
|
|
147
|
+
),
|
|
148
|
+
);
|
|
104
149
|
writeFile(path.join(appRoot, 'client', 'AGENTS.md'), '# Client\n');
|
|
105
150
|
writeFile(path.join(appRoot, 'client', 'pages', 'AGENTS.md'), '# Pages\n');
|
|
106
151
|
writeFile(path.join(appRoot, 'server', 'AGENTS.md'), '# Server\n');
|
|
@@ -700,7 +745,10 @@ test('machine MCP router resolves projects by cwd and bootstraps workflow withou
|
|
|
700
745
|
assert.equal(forwardedCall.name, 'workflow_start');
|
|
701
746
|
assert.deepEqual(forwardedCall.arguments, { route: '/domains', task: 'read-only runtime health pass' });
|
|
702
747
|
assert.equal(workflowPayload.data.project.projectId, productMachineRecord.projectId);
|
|
703
|
-
assert.equal(
|
|
748
|
+
assert.equal(
|
|
749
|
+
workflowPayload.nextActions.find((action) => action.tool === 'diagnose').toolArgs.projectId,
|
|
750
|
+
productMachineRecord.projectId,
|
|
751
|
+
);
|
|
704
752
|
|
|
705
753
|
await client.close();
|
|
706
754
|
await server.close();
|
|
@@ -785,6 +833,55 @@ test('machine MCP router resolves offline monorepo app candidates before dev is
|
|
|
785
833
|
await server.close();
|
|
786
834
|
});
|
|
787
835
|
|
|
836
|
+
test('machine MCP workflow_start reports fresh-copy setup blockers before dev start', async (t) => {
|
|
837
|
+
const previousRegistryDir = process.env.PROTEUM_MACHINE_DEV_SESSION_DIR;
|
|
838
|
+
const registryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-machine-fresh-copy-registry-'));
|
|
839
|
+
process.env.PROTEUM_MACHINE_DEV_SESSION_DIR = registryDir;
|
|
840
|
+
t.onTestFinished(() => {
|
|
841
|
+
if (previousRegistryDir === undefined) delete process.env.PROTEUM_MACHINE_DEV_SESSION_DIR;
|
|
842
|
+
else process.env.PROTEUM_MACHINE_DEV_SESSION_DIR = previousRegistryDir;
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-machine-fresh-copy-'));
|
|
846
|
+
const appRoot = path.join(repoRoot, 'apps', 'product');
|
|
847
|
+
writeFreshCopyFixture(appRoot, {
|
|
848
|
+
identifier: 'FreshCopyApp',
|
|
849
|
+
name: 'Fresh Copy',
|
|
850
|
+
routerPort: 3022,
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
const server = createProteumMachineMcpServer({ version: 'test' });
|
|
854
|
+
const client = new Client({ name: 'machine-mcp-test', version: '1.0.0' });
|
|
855
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
856
|
+
|
|
857
|
+
await server.connect(serverTransport);
|
|
858
|
+
await client.connect(clientTransport);
|
|
859
|
+
|
|
860
|
+
const workflow = await client.callTool({
|
|
861
|
+
name: 'workflow_start',
|
|
862
|
+
arguments: { cwd: appRoot, task: 'prepare fresh copy' },
|
|
863
|
+
});
|
|
864
|
+
const payload = JSON.parse(workflow.content[0].text);
|
|
865
|
+
const actionLabels = payload.nextActions.map((action) => action.label);
|
|
866
|
+
|
|
867
|
+
assert.equal(payload.ok, true);
|
|
868
|
+
assert.equal(payload.data.readiness.state, 'blocked');
|
|
869
|
+
assert.equal(payload.data.readiness.env.app.present, false);
|
|
870
|
+
assert.equal(payload.data.readiness.dependencies.nodeModulesPresent, false);
|
|
871
|
+
assert.equal(payload.data.readiness.database.detected, true);
|
|
872
|
+
assert.equal(payload.data.readiness.database.generatedClientPresent, false);
|
|
873
|
+
assert.equal(actionLabels.includes('Copy App Env Example'), true);
|
|
874
|
+
assert.equal(actionLabels.includes('Install Dependencies'), true);
|
|
875
|
+
assert.equal(actionLabels.includes('Generate Prisma Client'), true);
|
|
876
|
+
assert.equal(actionLabels.includes('Start Dev'), true);
|
|
877
|
+
assert.match(payload.nextActions.find((action) => action.label === 'Install Dependencies').command, /npm install/);
|
|
878
|
+
assert.match(payload.nextActions.find((action) => action.label === 'Generate Prisma Client').command, /prisma generate/);
|
|
879
|
+
assert.doesNotMatch(workflow.content[0].text, /mysql:\/\/user:pass/);
|
|
880
|
+
|
|
881
|
+
await client.close();
|
|
882
|
+
await server.close();
|
|
883
|
+
});
|
|
884
|
+
|
|
788
885
|
test('machine MCP workflow_start blocks offline unbootstrapped Codex worktrees', async (t) => {
|
|
789
886
|
const previousRegistryDir = process.env.PROTEUM_MACHINE_DEV_SESSION_DIR;
|
|
790
887
|
const registryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-machine-worktree-offline-'));
|
|
@@ -153,6 +153,81 @@ test('worktree bootstrap requires a source env only when .env is missing', async
|
|
|
153
153
|
assert.equal(result.status.blocking, false);
|
|
154
154
|
});
|
|
155
155
|
|
|
156
|
+
test('worktree bootstrap copies workspace root env for monorepo root tooling', async () => {
|
|
157
|
+
const targetRepoRoot = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-worktree-monorepo-')), '.codex', 'worktrees', 'fixture-repo');
|
|
158
|
+
const appRoot = path.join(targetRepoRoot, 'apps', 'product');
|
|
159
|
+
const sourceRepoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-worktree-source-repo-'));
|
|
160
|
+
const sourceAppRoot = path.join(sourceRepoRoot, 'apps', 'product');
|
|
161
|
+
|
|
162
|
+
writeFile(path.join(targetRepoRoot, 'package.json'), '{"workspaces":["apps/*"]}\n');
|
|
163
|
+
writeFile(path.join(targetRepoRoot, 'package-lock.json'), '{"lockfileVersion":3}\n');
|
|
164
|
+
writeFile(path.join(targetRepoRoot, 'prisma.config.ts'), 'export default {};\n');
|
|
165
|
+
fs.mkdirSync(path.join(targetRepoRoot, 'node_modules'), { recursive: true });
|
|
166
|
+
writeFile(path.join(appRoot, 'package.json'), '{"name":"fixture-product"}\n');
|
|
167
|
+
writeFile(path.join(appRoot, 'proteum.config.ts'), 'export default {};\n');
|
|
168
|
+
writeFile(path.join(appRoot, 'AGENTS.md'), '# Agents\n');
|
|
169
|
+
writeFile(path.join(appRoot, '.proteum', 'manifest.json'), '{"version":10}\n');
|
|
170
|
+
|
|
171
|
+
writeFile(path.join(sourceRepoRoot, 'package.json'), '{"workspaces":["apps/*"]}\n');
|
|
172
|
+
writeFile(path.join(sourceRepoRoot, 'package-lock.json'), '{"lockfileVersion":3}\n');
|
|
173
|
+
writeFile(path.join(sourceRepoRoot, 'prisma.config.ts'), 'export default {};\n');
|
|
174
|
+
writeFile(path.join(sourceRepoRoot, '.env'), 'DATABASE_URL=postgres://root\n');
|
|
175
|
+
writeFile(path.join(sourceAppRoot, '.env'), 'PORT=3021\n');
|
|
176
|
+
|
|
177
|
+
const result = await runWorktreeBootstrapInit({
|
|
178
|
+
appRoot,
|
|
179
|
+
coreRoot,
|
|
180
|
+
proteumVersion: 'test',
|
|
181
|
+
runDependencies: noOpDeps,
|
|
182
|
+
runRefresh: noOpRefresh,
|
|
183
|
+
runRuntimeStatus: noOpRuntime,
|
|
184
|
+
source: sourceAppRoot,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
assert.equal(fs.readFileSync(path.join(appRoot, '.env'), 'utf8'), 'PORT=3021\n');
|
|
188
|
+
assert.equal(fs.readFileSync(path.join(targetRepoRoot, '.env'), 'utf8'), 'DATABASE_URL=postgres://root\n');
|
|
189
|
+
assert.equal(result.marker.env.root.present, true);
|
|
190
|
+
assert.equal(result.marker.env.root.copied, true);
|
|
191
|
+
assert.equal(result.status.blocking, false);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('worktree bootstrap falls back to source app env for missing workspace root env', async () => {
|
|
195
|
+
const targetRepoRoot = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-worktree-root-env-fallback-')), '.codex', 'worktrees', 'fixture-repo');
|
|
196
|
+
const appRoot = path.join(targetRepoRoot, 'apps', 'api');
|
|
197
|
+
const sourceRepoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-worktree-source-repo-fallback-'));
|
|
198
|
+
const sourceAppRoot = path.join(sourceRepoRoot, 'apps', 'api');
|
|
199
|
+
|
|
200
|
+
writeFile(path.join(targetRepoRoot, 'package.json'), '{"workspaces":["apps/*"]}\n');
|
|
201
|
+
writeFile(path.join(targetRepoRoot, 'package-lock.json'), '{"lockfileVersion":3}\n');
|
|
202
|
+
writeFile(path.join(targetRepoRoot, 'prisma.config.ts'), 'export default {};\n');
|
|
203
|
+
fs.mkdirSync(path.join(targetRepoRoot, 'node_modules'), { recursive: true });
|
|
204
|
+
writeFile(path.join(appRoot, 'package.json'), '{"name":"fixture-api"}\n');
|
|
205
|
+
writeFile(path.join(appRoot, 'proteum.config.ts'), 'export default {};\n');
|
|
206
|
+
writeFile(path.join(appRoot, 'AGENTS.md'), '# Agents\n');
|
|
207
|
+
writeFile(path.join(appRoot, '.proteum', 'manifest.json'), '{"version":10}\n');
|
|
208
|
+
|
|
209
|
+
writeFile(path.join(sourceRepoRoot, 'package.json'), '{"workspaces":["apps/*"]}\n');
|
|
210
|
+
writeFile(path.join(sourceRepoRoot, 'package-lock.json'), '{"lockfileVersion":3}\n');
|
|
211
|
+
writeFile(path.join(sourceRepoRoot, 'prisma.config.ts'), 'export default {};\n');
|
|
212
|
+
writeFile(path.join(sourceAppRoot, '.env'), 'DATABASE_URL=postgres://app\nPORT=3022\n');
|
|
213
|
+
|
|
214
|
+
const result = await runWorktreeBootstrapInit({
|
|
215
|
+
appRoot,
|
|
216
|
+
coreRoot,
|
|
217
|
+
proteumVersion: 'test',
|
|
218
|
+
runDependencies: noOpDeps,
|
|
219
|
+
runRefresh: noOpRefresh,
|
|
220
|
+
runRuntimeStatus: noOpRuntime,
|
|
221
|
+
source: sourceAppRoot,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
assert.equal(fs.readFileSync(path.join(appRoot, '.env'), 'utf8'), 'DATABASE_URL=postgres://app\nPORT=3022\n');
|
|
225
|
+
assert.equal(fs.readFileSync(path.join(targetRepoRoot, '.env'), 'utf8'), 'DATABASE_URL=postgres://app\nPORT=3022\n');
|
|
226
|
+
assert.equal(result.marker.env.root.present, true);
|
|
227
|
+
assert.equal(result.marker.env.root.source, path.join(sourceAppRoot, '.env'));
|
|
228
|
+
assert.equal(result.status.blocking, false);
|
|
229
|
+
});
|
|
230
|
+
|
|
156
231
|
test('worktree bootstrap detects missing env manifest node_modules and version changes', async () => {
|
|
157
232
|
const appRoot = createCodexAppRoot();
|
|
158
233
|
writeBootstrapFixture(appRoot);
|