proteum 2.5.2 → 2.5.4
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 +4 -4
- 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 +4 -4
- package/agents/project/tests/AGENTS.md +1 -1
- package/cli/commands/configure.ts +5 -5
- package/cli/commands/dev.ts +3 -3
- package/cli/presentation/commands.ts +4 -2
- package/cli/presentation/help.ts +1 -1
- package/cli/runtime/commands.ts +1 -1
- package/cli/runtime/worktreeBootstrap.ts +99 -6
- package/cli/utils/agents.ts +270 -1
- package/docs/diagnostics.md +1 -1
- package/package.json +1 -1
- package/tests/agents-utils.test.cjs +72 -0
- package/tests/worktree-bootstrap.test.cjs +75 -0
package/AGENTS.md
CHANGED
|
@@ -65,7 +65,7 @@ npx prisma migrate dev --config ./prisma.config.ts --name <migration name>
|
|
|
65
65
|
- Keep the developer-facing contract synchronized when framework work changes CLI commands, profiler capabilities, or the `proteum dev` banner. Update the live surfaces together in the same pass: CLI command/help definitions, profiler panels and dev-only endpoints, banner text/examples, and the most relevant agent docs that describe them, especially `AGENTS.md`, `agents/project/AGENTS.md`, `agents/project/root/AGENTS.md`, `agents/project/app-root/AGENTS.md`, `agents/project/diagnostics.md`, and any narrower `agents/project/**/AGENTS.md` file that mentions the changed workflow.
|
|
66
66
|
- Proteum MCP contract: `proteum mcp` is the machine-scope router agents register once, and `proteum dev` exposes each app runtime at `/__proteum/mcp`. `proteum dev` ensures one managed machine MCP daemon is running; do not start a second managed daemon. Agents should start with MCP `workflow_start` using `cwd` or a known `projectId`; ambiguous routing or offline app candidates use `project_resolve { cwd }`, and follow-up live app tools require the returned `projectId`. Dev-hosted app tools are already rooted to their own runtime. Keep MCP tools/resources compact, typed, capped, paginated for full trace detail, and read-only unless a future task explicitly expands the mutation contract. The database diagnostic exception is still read-only: MCP `db_query` and CLI `proteum db query` allow one capped `SELECT`, `SHOW`, or `EXPLAIN` statement only and return rows plus elapsed milliseconds. MCP payloads are compact single-line `proteum-mcp-v1` JSON, not pretty-printed human output. Do not implement MCP tools as thin CLI process wrappers when the data is available through manifest readers, tracked sessions, or dev runtime registries.
|
|
67
67
|
- Keep the same-system trace contract explicit when request instrumentation changes: `TRACE_*` controls the retained dev trace store plus the trace/perf CLI, dev-only HTTP endpoints, and bottom profiler, while `ENABLE_PROFILER` enables the reduced request-local `request.profiling` snapshot and `request.finished` hook payload without retaining finished requests globally unless dev trace is also enabled.
|
|
68
|
-
- Current CLI banner contract: only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the welcome banner. Terminal `proteum mcp` may print a compact central MCP ready banner when it starts or reuses the managed daemon. Only `proteum dev` clears the interactive terminal before rendering, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys in its session UI, and reports connected app names plus successful connected `/ping` checks in the ready banner. Every `proteum dev` start ensures tracked instruction files contain the current managed `# Proteum Instructions` section before the dev loop begins.
|
|
68
|
+
- Current CLI banner contract: only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the welcome banner. Terminal `proteum mcp` may print a compact central MCP ready banner when it starts or reuses the managed daemon. Only `proteum dev` clears the interactive terminal before rendering, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys in its session UI, and reports connected app names plus successful connected `/ping` checks in the ready banner. Every `proteum dev` start ensures tracked instruction files contain the current managed `# Proteum Instructions` section and `CLAUDE.md` symlinks point to sibling `AGENTS.md` files before the dev loop begins.
|
|
69
69
|
- Keep core changes aligned with the explicit controller/page architecture in `agents/project/root/AGENTS.md` and its standalone composition in `agents/project/AGENTS.md`.
|
|
70
70
|
- Prefer removing framework magic when the same result can be expressed with explicit contracts, generated code, or typed context.
|
|
71
71
|
- Apply the pruning rules from `agents/project/optimizations.md`, especially for webpack plugins, Babel plugins, aliases, helpers, runtime services, and npm packages that are not meaningfully used by both apps.
|
package/README.md
CHANGED
|
@@ -403,7 +403,7 @@ Proteum ships with a compact CLI focused on the real app lifecycle:
|
|
|
403
403
|
| `proteum e2e` | Run Playwright with Proteum-managed `E2E_*` values instead of shell-leading env assignments |
|
|
404
404
|
| `proteum verify` | Validate targeted changed-file checks, focused owner/request/browser workflows, or the full framework reference-app pass |
|
|
405
405
|
| `proteum init` | Scaffold a new Proteum app with built-in deterministic templates |
|
|
406
|
-
| `proteum configure agents` | Interactively configure tracked Proteum instruction files
|
|
406
|
+
| `proteum configure agents` | Interactively configure tracked Proteum instruction files and Claude aliases |
|
|
407
407
|
| `proteum create` | Scaffold a page, controller, command, route, or root service inside an app |
|
|
408
408
|
| `proteum worktree` | Create or initialize Codex worktrees with a machine-readable bootstrap marker |
|
|
409
409
|
|
|
@@ -419,7 +419,7 @@ proteum build --prod --analyze
|
|
|
419
419
|
proteum build --prod --analyze --analyze-serve --analyze-port auto
|
|
420
420
|
```
|
|
421
421
|
|
|
422
|
-
Only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the banner. `proteum dev` is the only command that clears the interactive terminal before rendering its live session UI, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys, and prints connected app names plus successful connected `/ping` checks in the server-ready banner. Every `proteum dev` start ensures tracked Proteum instruction files contain the current managed `# Proteum Instructions` section before the dev loop begins.
|
|
422
|
+
Only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the banner. `proteum dev` is the only command that clears the interactive terminal before rendering its live session UI, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys, and prints connected app names plus successful connected `/ping` checks in the server-ready banner. Every `proteum dev` start ensures tracked Proteum instruction files contain the current managed `# Proteum Instructions` section and `CLAUDE.md` symlinks point to sibling `AGENTS.md` files before the dev loop begins.
|
|
423
423
|
|
|
424
424
|
Useful inspection commands:
|
|
425
425
|
|
|
@@ -467,11 +467,11 @@ proteum create controller Founder/projects --method list
|
|
|
467
467
|
proteum create service Conversion/Plans
|
|
468
468
|
```
|
|
469
469
|
|
|
470
|
-
`proteum configure agents` writes a compact managed `# Proteum Instructions` router plus the task-specific instruction files that router points to. Standalone mode writes root documents into the app root; monorepo mode writes shared root documents such as `AGENTS.md`, `DOCUMENTATION.md`, `CODING_STYLE.md`, `diagnostics.md`, and `optimizations.md` into the chosen monorepo root and keeps only app-local instruction files in the Proteum app root. It preserves content outside managed sections and asks before replacing directories or
|
|
470
|
+
`proteum configure agents` writes a compact managed `# Proteum Instructions` router plus the task-specific instruction files that router points to. Standalone mode writes root documents into the app root; monorepo mode writes shared root documents such as `AGENTS.md`, `DOCUMENTATION.md`, `CODING_STYLE.md`, `diagnostics.md`, and `optimizations.md` into the chosen monorepo root and keeps only app-local instruction files in the Proteum app root. For each generated `AGENTS.md`, it creates a sibling `CLAUDE.md` symlink pointing to `AGENTS.md`. It preserves content outside managed sections and asks before replacing directories, foreign symlinks, or unrelated files. If you decline, that path is left untouched.
|
|
471
471
|
|
|
472
472
|
Every `proteum dev` start runs the same idempotent instruction check. It updates missing or stale managed sections automatically and prompts only when a blocked path would need to be replaced.
|
|
473
473
|
|
|
474
|
-
`proteum worktree init` writes `.proteum/worktree-bootstrap.json` for app roots under `/.codex/worktrees/`. The marker records `.env` copy status, refresh and dependency results, runtime status, key file hashes, and the active Proteum version. `proteum dev`, `proteum refresh`, `proteum runtime status`, `proteum verify`, and MCP `workflow_start` block inside Codex worktrees until the marker is fresh. Run `npx proteum worktree init --source <source-app-root>` for a new worktree, or add `--refresh` when stale state is reported. `PROTEUM_ALLOW_UNBOOTSTRAPPED_WORKTREE=1` bypasses the block but remains visible in runtime status, doctor, and MCP output.
|
|
474
|
+
`proteum worktree init` writes `.proteum/worktree-bootstrap.json` for app roots under `/.codex/worktrees/`. The marker records `.env` copy status, refresh and dependency results, runtime status, key file hashes, and the active Proteum version. In monorepos with root tooling such as `prisma.config.ts` or npm workspaces, bootstrap also ensures the workspace-root `.env` exists, copying the source root `.env` when available or falling back to the source app `.env`. `proteum dev`, `proteum refresh`, `proteum runtime status`, `proteum verify`, and MCP `workflow_start` block inside Codex worktrees until the marker is fresh. Run `npx proteum worktree init --source <source-app-root>` for a new worktree, or add `--refresh` when stale state is reported. `PROTEUM_ALLOW_UNBOOTSTRAPPED_WORKTREE=1` bypasses the block but remains visible in runtime status, doctor, and MCP output.
|
|
475
475
|
|
|
476
476
|
`proteum connect`, `proteum explain`, `proteum doctor`, and `proteum diagnose` share the same generated manifest and contract state. `proteum perf` uses the same dev request-trace store as the profiler `Perf` tab. `proteum runtime status` also inspects the configured router/HMR ports and returns an exact Start Dev action, so agents do not need to `curl` page routes to identify port owners. `proteum dev` exposes the app-root MCP contract at `/__proteum/mcp` and ensures one managed machine MCP daemon is running; `proteum mcp` is the machine-scope router agents register once. Agents should start with MCP `workflow_start`, use offline candidates to choose the correct app root when no dev server is live, then route repeated reads by the returned live `projectId`. For the full diagnostics and tracing model, see [docs/diagnostics.md](docs/diagnostics.md), [docs/mcp.md](docs/mcp.md), and [docs/request-tracing.md](docs/request-tracing.md).
|
|
477
477
|
|
package/agents/project/AGENTS.md
CHANGED
|
@@ -53,7 +53,7 @@ Managed compact root routers must use trigger -> canonical instruction file refe
|
|
|
53
53
|
|
|
54
54
|
[optional body]
|
|
55
55
|
```
|
|
56
|
-
Then treat `commit` as conversation-wide and cross-project, not task-scoped.
|
|
56
|
+
Then treat `commit` as conversation-wide and cross-project, not task-scoped. Do not run any check, lint, typecheck, test, coverage, or commit gate as part of this exact `commit` workflow unless the user explicitly asks for those checks in the same request. Identify every affected git repository or worktree touched during that span, stage all conversation-related changed files in each affected repository or worktree with `git add` while still avoiding unrelated pre-existing user changes or incidental untracked files, and create one `git commit` per affected repository or worktree. Do not omit linked local dependencies, framework repos, connected projects, or producer apps when they were changed to make the delivered behavior actually work. Do not stop at only suggesting the message.
|
|
57
57
|
After providing a commit message or after creating a commit, immediately follow it with this exact prompt and obey it:
|
|
58
58
|
`Explain in short minimalistic and few bullet points what we changed in this thread, like you would do to your grandma. Start with a verb in the past.`
|
|
59
59
|
|
|
@@ -74,13 +74,13 @@ Managed compact root routers must use trigger -> canonical instruction file refe
|
|
|
74
74
|
- If the current app depends on local `file:` connected projects, boot every connected producer app too, each with its own task-scoped session file and free port, and run every one of those `proteum dev` processes with elevated permissions outside the sandbox before starting or verifying the consumer app.
|
|
75
75
|
- During `npx proteum dev`, the app exposes the read-only Proteum MCP runtime endpoint at `/__proteum/mcp`; use it for repeated agent reads instead of spawning equivalent diagnostics commands. For route/page/controller ownership, prefer MCP `workflow_start`, `route_candidates { projectId, query }`, or `explain_summary { projectId, query }` over broad `npx proteum explain --routes --controllers --full` dumps.
|
|
76
76
|
- For browser validation, use the browser MCP against the running app. Keep Playwright inside `npx proteum e2e --port <port>` for targeted/full end-to-end suites. Bootstrap protected browser MCP state with `npx proteum session`; bootstrap protected E2E runs with `npx proteum e2e --session-email <email> --session-role <role>`.
|
|
77
|
-
- Current CLI banner contract: only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the welcome banner. Terminal `proteum mcp` may print a compact central MCP ready banner when it starts or reuses the managed daemon. Only `proteum dev` clears the interactive terminal before rendering, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys in its session UI, and reports connected app names plus successful connected `/ping` checks in the ready banner. Every `proteum dev` start ensures tracked instruction files contain the current managed `# Proteum Instructions` section before the dev loop begins.
|
|
77
|
+
- Current CLI banner contract: only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the welcome banner. Terminal `proteum mcp` may print a compact central MCP ready banner when it starts or reuses the managed daemon. Only `proteum dev` clears the interactive terminal before rendering, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys in its session UI, and reports connected app names plus successful connected `/ping` checks in the ready banner. Every `proteum dev` start ensures tracked instruction files contain the current managed `# Proteum Instructions` section and `CLAUDE.md` symlinks point to sibling `AGENTS.md` files before the dev loop begins.
|
|
78
78
|
|
|
79
79
|
### Before Finishing
|
|
80
80
|
|
|
81
81
|
- Before finishing, re-check touched files against root-level `CODING_STYLE.md` and any narrower area `AGENTS.md` that applied to the edit. Re-check against root-level `optimizations.md` only for touched client-side files. Re-check against root-level `diagnostics.md` only if the task involved an issue, diagnosis, runtime reproduction, or verification failure.
|
|
82
82
|
- Before finishing a production code change, re-check root-level `DOCUMENTATION.md` update rules. If behavior changed, a bug was fixed, a decision changed, or an important route, auth/OAuth, or integration issue was addressed, update the relevant docs before committing or explicitly explain why no docs update was needed.
|
|
83
|
-
- Run targeted tests and checks that match the changed surface before finishing each feature or change. When the repository defines `proteum.verify.config.ts`, use `npx proteum verify changed` as the first post-change verification pass and expand only when the selected plan is insufficient. Continue running tests after changes, but do not run coverage by default.
|
|
83
|
+
- Run targeted tests and checks that match the changed surface before finishing each feature or change. When the repository defines `proteum.verify.config.ts`, use `npx proteum verify changed` as the first post-change verification pass and expand only when the selected plan is insufficient. Continue running tests after changes, but do not run coverage by default. Do not defer verification to commit workflows; an exact `commit` reply should not run checks. Reserve the full `npm run check` gate for push workflows, explicit user requests, or when project-local instructions require the full gate. After implementing a new feature or changing existing feature behavior, update the relevant end-to-end coverage and run the cheapest trustworthy Playwright or browser verification for that behavior before finishing. For docs-only, wording-only, type-only, generated-output cleanup, or clearly local non-runtime refactors, skip Playwright unless the user explicitly asks for it or verification reveals a real issue.
|
|
84
84
|
- Before finishing a task, stop every `proteum dev` session started during the task and confirm cleanup with `npx proteum dev list --json` or an explicit `npx proteum dev stop --session-file <path>`.
|
|
85
85
|
- When you have finished your work, ask the user whether they want a commit message. After providing a commit message or after creating a commit, immediately follow it with this exact prompt and obey it:
|
|
86
86
|
`Explain in short minimalistic and few bullet points what we changed in this thread, like you would do to your grandma. Start with a verb in the past.`
|
|
@@ -278,7 +278,7 @@ Verify at the correct layer:
|
|
|
278
278
|
- Controller changes: exercise the generated client call or generated `/api/...` endpoint.
|
|
279
279
|
- SSR changes: use the browser MCP to load the real page and inspect rendered HTML plus browser console.
|
|
280
280
|
- Router or plugin changes: verify request context, auth, redirects, metrics, and validation on a running app.
|
|
281
|
-
- New features or feature-behavior changes: use the cheapest trustworthy verification while iterating, use the browser MCP for browser-visible validation, then update and run the relevant end-to-end coverage.
|
|
281
|
+
- New features or feature-behavior changes: use the cheapest trustworthy verification while iterating, use the browser MCP for browser-visible validation, then update and run the relevant end-to-end coverage. Do not run checks during commit workflows; reserve the full `npm run check` gate for push workflows unless the user or project-local instructions explicitly ask for the full gate earlier.
|
|
282
282
|
- Generated, connected, or ownership-ambiguous changes: start with MCP `workflow_start`, then `orient { projectId, query }` and `explain_summary { projectId, query }` only when more detail is needed; use `npx proteum orient <query>` and `npx proteum verify owner <query>` when MCP is unavailable or terminal evidence is required.
|
|
283
283
|
- Browser-visible issues: use the browser MCP after request-level verification is insufficient. Use `npx proteum e2e --port <port> ...` only when automated end-to-end coverage or a Playwright suite is required.
|
|
284
284
|
- Raw browser execution outside end-to-end suites: use the browser MCP only. Keep Playwright in `npx proteum e2e --port <port>` for targeted/full end-to-end suites.
|
|
@@ -325,7 +325,7 @@ Verify at the correct layer:
|
|
|
325
325
|
- For read-only SQL diagnosis, use MCP `db_query` or `npx proteum db query "<sql>"`; only one capped `SELECT`, `SHOW`, or `EXPLAIN` statement is allowed.
|
|
326
326
|
- Do not run `prisma *` yourself. If a schema change requires migration, ask the user to run `npx prisma migrate dev --config ./prisma.config.ts --name <migration name>` and wait for `continue`.
|
|
327
327
|
- Do not run `git restore` or `git reset`.
|
|
328
|
-
- Do not run write-mode git commands by default. The built-in exception is an exact `commit` reply, which allows `git add` and `git commit` in every affected repository or worktree touched during the whole conversation
|
|
328
|
+
- Do not run write-mode git commands by default. The built-in exception is an exact `commit` reply, which allows `git add` and `git commit` in every affected repository or worktree touched during the whole conversation. An exact `commit` reply does not allow check, lint, typecheck, test, coverage, or commit-gate commands unless the user explicitly requests them in the same message. Any other write-mode git action requires an explicit user request. Before any explicit push, run `npm run check` in every affected repository or worktree that defines it and report any blocker instead of pushing through a failed check.
|
|
329
329
|
|
|
330
330
|
## Appendix
|
|
331
331
|
|
|
@@ -423,7 +423,7 @@ Agents working in generated Proteum projects must use this delivery workflow for
|
|
|
423
423
|
4. Targeted validation: refresh generated framework contracts after route, page, controller, service, command, or config changes, then run the targeted tests/checks that match the changed surface.
|
|
424
424
|
5. Validate unit + E2E: run the relevant unit tests and real-world journey E2E checks before calling the work complete.
|
|
425
425
|
|
|
426
|
-
Unit test expectation: production changes must always add or update focused unit tests and run the targeted unit or integration tests that match the changed behavior. Do not run coverage after every change by default. Reserve whole-project coverage for the repository's full `npm run check` gate during push workflows or when the user explicitly requests it; do not run
|
|
426
|
+
Unit test expectation: production changes must always add or update focused unit tests and run the targeted unit or integration tests that match the changed behavior. Do not run coverage after every change by default. Reserve whole-project coverage for the repository's full `npm run check` gate during push workflows or when the user explicitly requests it; do not run checks for commit-only workflows unless the user explicitly requests them. Any excluded generated files, migrations, framework shims, unreachable defensive branches, or changes that cannot reasonably be unit-tested must be documented in the completion note.
|
|
427
427
|
|
|
428
428
|
E2E expectation: real-world journeys must follow the project-local instructions in `tests/e2e/REAL_WORLD_JOURNEY_TESTS.md`. These tests should model complete user workflows, role transitions, permissions, state changes, and cross-view consistency rather than isolated happy paths.
|
|
429
429
|
|
|
@@ -8,7 +8,7 @@ This file is the source of truth for codex coding style instructions in Proteum-
|
|
|
8
8
|
- Write clean, consistent, readable code with a tab size of 4.
|
|
9
9
|
- Keep functions and methods short.
|
|
10
10
|
- Every time possible, create reusable functions and components instead of repeating.
|
|
11
|
-
- Before finishing a feature or change, review touched files against this document and run targeted lint/typecheck/tests for the changed surface. When the repository defines `proteum.verify.config.ts`, use `npx proteum verify changed` as the first post-change verification pass. Do not run coverage by default after ordinary changes.
|
|
11
|
+
- Before finishing a feature or change, review touched files against this document and run targeted lint/typecheck/tests for the changed surface. When the repository defines `proteum.verify.config.ts`, use `npx proteum verify changed` as the first post-change verification pass. Do not run coverage by default after ordinary changes. Do not run check, lint, typecheck, test, coverage, or commit-gate commands as part of a commit-only workflow; run the full `npm run check` gate before pushing or when the user explicitly asks for it. Coding-style regressions are defects, not optional cleanup.
|
|
12
12
|
|
|
13
13
|
## Type safety
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@ This file is the canonical source of truth for diagnostics, temporary instrument
|
|
|
18
18
|
|
|
19
19
|
- For long-lived dev reproductions, always request elevated permissions and run `npx proteum dev` outside the sandbox. Use an explicit task/thread-scoped session file, inspect `npx proteum runtime status` first, then use its exact next action so occupied router/HMR ports and untracked same-app runtimes are handled without page-body probes. After the server is ready, print the live server URL as a clickable Markdown link.
|
|
20
20
|
- Use `--replace-existing` only when restarting the exact session file started by the current thread/task. Never replace another live session that belongs to a user, another thread, or an unknown owner.
|
|
21
|
-
- Only the bare `npx proteum build` and bare `npx proteum dev` commands print the welcome banner and active Proteum installation method. Any extra argument or option skips the welcome banner. Terminal `npx proteum mcp` may print a compact central MCP ready banner when it starts or reuses the managed daemon. Only `npx proteum dev` clears the interactive terminal before rendering and reports connected app names plus successful connected `/ping` checks in the ready banner; keep that in mind when capturing or comparing command logs during diagnosis. Every `npx proteum dev` start ensures tracked instruction files contain the current managed `# Proteum Instructions` section before the dev loop begins.
|
|
21
|
+
- Only the bare `npx proteum build` and bare `npx proteum dev` commands print the welcome banner and active Proteum installation method. Any extra argument or option skips the welcome banner. Terminal `npx proteum mcp` may print a compact central MCP ready banner when it starts or reuses the managed daemon. Only `npx proteum dev` clears the interactive terminal before rendering and reports connected app names plus successful connected `/ping` checks in the ready banner; keep that in mind when capturing or comparing command logs during diagnosis. Every `npx proteum dev` start ensures tracked instruction files contain the current managed `# Proteum Instructions` section and `CLAUDE.md` symlinks point to sibling `AGENTS.md` files before the dev loop begins.
|
|
22
22
|
- During `npx proteum dev`, the running app exposes the read-only Proteum MCP transport at `/__proteum/mcp`. Use it for runtime-adjacent agent reads instead of repeatedly spawning equivalent CLI diagnostics.
|
|
23
23
|
- If machine MCP routing fails, run `npx proteum mcp status` and `npx proteum runtime status` from the intended app root. If no live session exists, use the exact MCP offline or runtime-status next action 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 start a second dev server in the same worktree, and do not start a second managed MCP daemon. Do not run diagnose, trace, or perf reads while runtime health is unreachable. Then retry MCP `workflow_start` and use the returned `projectId`.
|
|
24
24
|
- For ownership or repo discovery questions, start with MCP `workflow_start`; use MCP `route_candidates { projectId, query }`, MCP `orient { projectId, query }`, and MCP `explain_summary { projectId, query }` only when the bootstrap owner summary is insufficient. Use `npx proteum orient <query>` or `npx proteum explain owner <query>` only when MCP is unavailable or terminal evidence is required.
|
|
@@ -64,7 +64,7 @@ This file is the canonical source of truth for diagnostics, temporary instrument
|
|
|
64
64
|
- For browser regressions, prefer a browser MCP repro first and add targeted Playwright E2E coverage only when the user asks for automated coverage, when a stable regression path needs automation, or when browser MCP verification is insufficient.
|
|
65
65
|
- Only the final verifier agent should usually run browser flows. Earlier agents should stay on `orient`, `verify owner`, `verify request`, `diagnose`, and command-level checks unless browser execution is the only trustworthy reproducer.
|
|
66
66
|
- Treat server startup failures, runtime errors, browser console errors or warnings, and Playwright failures as blocking unless they are clearly unrelated to the change.
|
|
67
|
-
- When the touched surface can affect coding-style enforcement, run the targeted lint or typecheck command for that surface before finishing.
|
|
67
|
+
- When the touched surface can affect coding-style enforcement, run the targeted lint or typecheck command for that surface before finishing. Do not run check, lint, typecheck, test, coverage, or commit-gate commands as part of a commit-only workflow. Run the full `npm run check` gate before pushing or when explicitly requested.
|
|
68
68
|
- If the task started any long-lived `proteum dev` server, stop it explicitly with `npx proteum dev stop --session-file <path>` or `npx proteum dev stop --all --stale`, then confirm the remaining tracked sessions with `npx proteum dev list --json`.
|
|
69
69
|
- Add `data-testid` when stable selectors are missing instead of relying on brittle text or DOM-shape selectors.
|
|
70
70
|
- If an isolated test misses prerequisite state, run the smallest broader scope that reproduces the real setup.
|
|
@@ -59,14 +59,14 @@ Managed compact root routers must use trigger -> canonical instruction file refe
|
|
|
59
59
|
- If the current app depends on local `file:` connected projects, boot every connected producer app too, each with its own task-scoped session file and free port, and run every one of those `proteum dev` processes with elevated permissions outside the sandbox before starting or verifying the consumer app.
|
|
60
60
|
- During `npx proteum dev`, the app exposes the read-only Proteum MCP runtime endpoint at `/__proteum/mcp`; use it for repeated agent reads instead of spawning equivalent diagnostics commands. For route/page/controller ownership, prefer MCP `workflow_start`, `route_candidates { projectId, query }`, or `explain_summary { projectId, query }` over broad `npx proteum explain --routes --controllers --full` dumps.
|
|
61
61
|
- For browser validation, use the browser MCP against the running app. Keep Playwright inside `npx proteum e2e --port <port>` for targeted/full end-to-end suites. Bootstrap protected browser MCP state with `npx proteum session`; bootstrap protected E2E runs with `npx proteum e2e --session-email <email> --session-role <role>`.
|
|
62
|
-
- Current CLI banner contract: only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the welcome banner. Terminal `proteum mcp` may print a compact central MCP ready banner when it starts or reuses the managed daemon. Only `proteum dev` clears the interactive terminal before rendering, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys in its session UI, and reports connected app names plus successful connected `/ping` checks in the ready banner. Every `proteum dev` start ensures tracked instruction files contain the current managed `# Proteum Instructions` section before the dev loop begins.
|
|
62
|
+
- Current CLI banner contract: only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the welcome banner. Terminal `proteum mcp` may print a compact central MCP ready banner when it starts or reuses the managed daemon. Only `proteum dev` clears the interactive terminal before rendering, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys in its session UI, and reports connected app names plus successful connected `/ping` checks in the ready banner. Every `proteum dev` start ensures tracked instruction files contain the current managed `# Proteum Instructions` section and `CLAUDE.md` symlinks point to sibling `AGENTS.md` files before the dev loop begins.
|
|
63
63
|
|
|
64
64
|
### Before Finishing
|
|
65
65
|
|
|
66
66
|
- Before finishing, re-check touched files against root-level `CODING_STYLE.md` and any narrower area `AGENTS.md` that applied to the edit. Re-check against root-level `optimizations.md` only for touched client-side files. Re-check against root-level `diagnostics.md` only if the task involved an issue, diagnosis, runtime reproduction, or verification failure.
|
|
67
67
|
- Before finishing a production code change, re-check root-level `DOCUMENTATION.md` update rules. If behavior changed, a bug was fixed, a decision changed, or an important route, auth/OAuth, or integration issue was addressed, update the relevant docs before committing or explicitly explain why no docs update was needed.
|
|
68
|
-
- For production changes, always add or update focused unit tests and run the targeted unit or integration tests that match the changed behavior. Do not run coverage after every ordinary change by default. Reserve whole-project coverage for the repository's full `npm run check` gate during push workflows or when the user explicitly requests it; do not run
|
|
69
|
-
- Run targeted tests and checks that match the changed surface before finishing each feature or change. When the repository defines `proteum.verify.config.ts`, use `npx proteum verify changed` as the first post-change verification pass and expand only when the selected plan is insufficient. Continue running tests after changes, but do not run coverage by default.
|
|
68
|
+
- For production changes, always add or update focused unit tests and run the targeted unit or integration tests that match the changed behavior. Do not run coverage after every ordinary change by default. Reserve whole-project coverage for the repository's full `npm run check` gate during push workflows or when the user explicitly requests it; do not run checks for commit-only workflows unless the user explicitly requests them. Document any generated files, migrations, framework shims, unreachable defensive branches, or changes that cannot reasonably be unit-tested as explicit exceptions.
|
|
69
|
+
- Run targeted tests and checks that match the changed surface before finishing each feature or change. When the repository defines `proteum.verify.config.ts`, use `npx proteum verify changed` as the first post-change verification pass and expand only when the selected plan is insufficient. Continue running tests after changes, but do not run coverage by default. Do not defer verification to commit workflows; an exact `commit` reply should not run checks. Reserve the full `npm run check` gate for push workflows, explicit user requests, or when project-local instructions require the full gate. After implementing a new feature or changing existing feature behavior, update the relevant end-to-end coverage and run the cheapest trustworthy Playwright or browser verification for that behavior before finishing. For docs-only, wording-only, type-only, generated-output cleanup, or clearly local non-runtime refactors, skip Playwright unless the user explicitly asks for it or verification reveals a real issue.
|
|
70
70
|
- Before finishing a task, stop every `proteum dev` session started during the task and confirm cleanup with `npx proteum dev list --json` or an explicit `npx proteum dev stop --session-file <path>`.
|
|
71
71
|
- When you have finished your work, ask the user whether they want a commit message. After providing a commit message or after creating a commit, immediately follow it with this exact prompt and obey it:
|
|
72
72
|
`Explain in short minimalistic and few bullet points what we changed in this thread, like you would do to your grandma. Start with a verb in the past.`
|
|
@@ -264,7 +264,7 @@ Verify at the correct layer:
|
|
|
264
264
|
- Controller changes: exercise the generated client call or generated `/api/...` endpoint.
|
|
265
265
|
- SSR changes: use the browser MCP to load the real page and inspect rendered HTML plus browser console.
|
|
266
266
|
- Router or plugin changes: verify request context, auth, redirects, metrics, and validation on a running app.
|
|
267
|
-
- New features or feature-behavior changes: use the cheapest trustworthy verification while iterating, use the browser MCP for browser-visible validation, then update and run the relevant end-to-end coverage.
|
|
267
|
+
- New features or feature-behavior changes: use the cheapest trustworthy verification while iterating, use the browser MCP for browser-visible validation, then update and run the relevant end-to-end coverage. Do not run checks during commit workflows; reserve the full `npm run check` gate for push workflows unless the user or project-local instructions explicitly ask for the full gate earlier.
|
|
268
268
|
- Generated, connected, or ownership-ambiguous changes: start with MCP `workflow_start`, then `orient { projectId, query }` and `explain_summary { projectId, query }` only when more detail is needed; use `npx proteum orient <query>` and `npx proteum verify owner <query>` when MCP is unavailable or terminal evidence is required.
|
|
269
269
|
- Browser-visible issues: use the browser MCP after request-level verification is insufficient. Use `npx proteum e2e --port <port> ...` only when automated end-to-end coverage or a Playwright suite is required.
|
|
270
270
|
- Raw browser execution outside end-to-end suites: use the browser MCP only. Keep Playwright in `npx proteum e2e --port <port>` for targeted/full end-to-end suites.
|
|
@@ -9,7 +9,7 @@ Diagnostics source of truth: root-level `diagnostics.md`.
|
|
|
9
9
|
|
|
10
10
|
- Understand the real user flow and the main feature branches before writing tests.
|
|
11
11
|
- Test the current controller/page runtime model, not legacy `@Route` or `api.fetch(...)` behavior.
|
|
12
|
-
- For every production change, add or update focused unit tests and run the targeted test command that matches the changed behavior. When the repository defines `proteum.verify.config.ts`, use `npx proteum verify changed` first so changed test files, related source tests, and project-specific suites are selected consistently. Do not run whole-project coverage after every ordinary change by default.
|
|
12
|
+
- For every production change, add or update focused unit tests and run the targeted test command that matches the changed behavior. When the repository defines `proteum.verify.config.ts`, use `npx proteum verify changed` first so changed test files, related source tests, and project-specific suites are selected consistently. Do not run whole-project coverage after every ordinary change by default. Do not run checks as part of a commit-only workflow; use `npm run check` as the full gate before push or when the user explicitly asks for it, and document any generated files, migrations, framework shims, unreachable defensive branches, or changes that cannot reasonably be unit-tested as explicit exceptions.
|
|
13
13
|
- Verify routing, controllers, SSR, and router plugins against a running app when behavior depends on real request handling.
|
|
14
14
|
- After implementing a new feature or changing existing feature behavior, update the end-to-end coverage for that behavior and run the full Playwright suite before finishing. Prefer `npx proteum e2e --port <port>` for Playwright runs so base URLs and auth tokens are passed through Proteum-managed child env instead of shell-leading environment assignments. Use a browser MCP repro against a running app during iteration when it is the fastest trustworthy loop.
|
|
15
15
|
- Exercise real URLs, generated controller calls, or real browser flows instead of re-deriving framework internals in tests.
|
|
@@ -74,7 +74,7 @@ const promptBlockedOverwritePaths = async (blockedPaths: string[]) => {
|
|
|
74
74
|
console.info(await renderWarning('Proteum found existing paths that block managed instruction updates.'));
|
|
75
75
|
console.info(
|
|
76
76
|
[
|
|
77
|
-
'Choose whether to overwrite each path with a
|
|
77
|
+
'Choose whether to overwrite each path with a Proteum-managed instruction path:',
|
|
78
78
|
...blockedPaths.map((entry) => `- ${entry}`),
|
|
79
79
|
].join('\n'),
|
|
80
80
|
);
|
|
@@ -163,7 +163,7 @@ export const runConfigureAgentsWizard = async ({
|
|
|
163
163
|
: undefined;
|
|
164
164
|
console.info(
|
|
165
165
|
[
|
|
166
|
-
await renderTitle('PROTEUM CONFIGURE AGENTS', 'Configure tracked Proteum instruction files.'),
|
|
166
|
+
await renderTitle('PROTEUM CONFIGURE AGENTS', 'Configure tracked Proteum instruction files and Claude aliases.'),
|
|
167
167
|
renderRows([{ label: 'app', value: appRoot === process.cwd() ? '.' : appRoot }]),
|
|
168
168
|
].join('\n\n'),
|
|
169
169
|
);
|
|
@@ -201,8 +201,8 @@ export const runConfigureAgentsWizard = async ({
|
|
|
201
201
|
await renderStep(
|
|
202
202
|
'[1/1]',
|
|
203
203
|
isMonorepo
|
|
204
|
-
? `Writing monorepo-aware instruction files using ${monorepoRoot}.`
|
|
205
|
-
: 'Writing standalone instruction files.',
|
|
204
|
+
? `Writing monorepo-aware instruction files and Claude aliases using ${monorepoRoot}.`
|
|
205
|
+
: 'Writing standalone instruction files and Claude aliases.',
|
|
206
206
|
),
|
|
207
207
|
);
|
|
208
208
|
|
|
@@ -214,7 +214,7 @@ export const runConfigureAgentsWizard = async ({
|
|
|
214
214
|
});
|
|
215
215
|
const sections = renderConfigureResultSections(result);
|
|
216
216
|
|
|
217
|
-
console.info(await renderSuccess('Proteum-managed instruction files are configured.'));
|
|
217
|
+
console.info(await renderSuccess('Proteum-managed instruction files and Claude aliases are configured.'));
|
|
218
218
|
|
|
219
219
|
if (sections.length > 0) console.info(`\n${sections.join('\n\n')}`);
|
|
220
220
|
};
|
package/cli/commands/dev.ts
CHANGED
|
@@ -153,7 +153,7 @@ const promptBlockedAgentInstructionOverwrites = async (blockedPaths: string[]) =
|
|
|
153
153
|
if (cli.args.json === true || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
154
154
|
throw new UsageError(
|
|
155
155
|
[
|
|
156
|
-
'Proteum could not update managed instruction
|
|
156
|
+
'Proteum could not update managed instruction paths because existing paths are blocked:',
|
|
157
157
|
...blockedPaths.map((entry) => `- ${entry}`),
|
|
158
158
|
'Run `proteum configure agents` in an interactive terminal to choose which paths can be replaced.',
|
|
159
159
|
].join('\n'),
|
|
@@ -163,7 +163,7 @@ const promptBlockedAgentInstructionOverwrites = async (blockedPaths: string[]) =
|
|
|
163
163
|
console.info(await renderWarning('Proteum found existing paths that block managed instruction updates.'));
|
|
164
164
|
console.info(
|
|
165
165
|
[
|
|
166
|
-
'Choose whether to overwrite each blocked path with a
|
|
166
|
+
'Choose whether to overwrite each blocked path with a Proteum-managed instruction path:',
|
|
167
167
|
...blockedPaths.map((entry) => `- ${entry}`),
|
|
168
168
|
].join('\n'),
|
|
169
169
|
);
|
|
@@ -212,7 +212,7 @@ const ensureProjectAgentInstructions = async () => {
|
|
|
212
212
|
|
|
213
213
|
throw new UsageError(
|
|
214
214
|
[
|
|
215
|
-
'Proteum could not update all managed instruction
|
|
215
|
+
'Proteum could not update all managed instruction paths because these paths were left blocked:',
|
|
216
216
|
...result.blocked.map((entry) => `- ${entry}`),
|
|
217
217
|
].join('\n'),
|
|
218
218
|
);
|
|
@@ -116,7 +116,7 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
116
116
|
configure: {
|
|
117
117
|
name: 'configure',
|
|
118
118
|
category: 'Project scaffolding',
|
|
119
|
-
summary: 'Interactively configure tracked Proteum instruction files
|
|
119
|
+
summary: 'Interactively configure tracked Proteum instruction files and Claude aliases for an app.',
|
|
120
120
|
usage: 'proteum configure agents',
|
|
121
121
|
bestFor:
|
|
122
122
|
'Creating or switching the tracked instruction layout intentionally while keeping Proteum-owned instructions embedded in managed sections.',
|
|
@@ -128,8 +128,9 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
128
128
|
],
|
|
129
129
|
notes: [
|
|
130
130
|
'This command is interactive. It asks whether the current Proteum app belongs to a monorepo and, if so, which ancestor path should receive the reusable root instruction files.',
|
|
131
|
-
'Standalone mode writes tracked instruction files into the current Proteum app root.',
|
|
131
|
+
'Standalone mode writes tracked instruction files into the current Proteum app root and creates `CLAUDE.md` symlinks beside each `AGENTS.md`.',
|
|
132
132
|
'Monorepo mode writes reusable root documents such as `AGENTS.md`, `DOCUMENTATION.md`, `CODING_STYLE.md`, `diagnostics.md`, and `optimizations.md` into the chosen monorepo root, then writes only app-root and area instruction files into the current Proteum app root.',
|
|
133
|
+
'Every generated `CLAUDE.md` is a sibling symlink pointing to `AGENTS.md`.',
|
|
133
134
|
'Every managed instruction file contains a `# Proteum Instructions` section with the full embedded Proteum project instruction corpus.',
|
|
134
135
|
'Existing content outside `# Proteum Instructions` is preserved. Directories and foreign symlinks are replaced only after confirmation.',
|
|
135
136
|
],
|
|
@@ -165,6 +166,7 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
165
166
|
notes: [
|
|
166
167
|
'Bootstrap writes `.proteum/worktree-bootstrap.json` with hashes, step timestamps, dependency status, runtime status, and Proteum version.',
|
|
167
168
|
'When `.env` is missing, `--source` is required and must point to an app root with a readable `.env`.',
|
|
169
|
+
'For monorepos with root tooling, bootstrap also ensures the workspace-root `.env` exists from the source root or source app env.',
|
|
168
170
|
'Guarded commands block inside `/.codex/worktrees/` until bootstrap is complete or explicitly bypassed with `PROTEUM_ALLOW_UNBOOTSTRAPPED_WORKTREE=1`.',
|
|
169
171
|
'`worktree create` preserves the source app root path relative to the source repository root, so monorepo app roots are bootstrapped in the matching target location.',
|
|
170
172
|
],
|
package/cli/presentation/help.ts
CHANGED
|
@@ -139,7 +139,7 @@ export const renderCliOverview = async ({
|
|
|
139
139
|
indent: ' ',
|
|
140
140
|
nextIndent: ' ',
|
|
141
141
|
}),
|
|
142
|
-
wrapText('Before the dev loop starts, `proteum dev` ensures tracked instruction files contain the current managed `# Proteum Instructions` section.', {
|
|
142
|
+
wrapText('Before the dev loop starts, `proteum dev` ensures tracked instruction files contain the current managed `# Proteum Instructions` section and `CLAUDE.md` symlinks point to sibling `AGENTS.md` files.', {
|
|
143
143
|
indent: ' ',
|
|
144
144
|
nextIndent: ' ',
|
|
145
145
|
}),
|
package/cli/runtime/commands.ts
CHANGED
|
@@ -175,7 +175,7 @@ class WorktreeCommand extends ProteumCommand {
|
|
|
175
175
|
|
|
176
176
|
public static usage = buildUsage('worktree');
|
|
177
177
|
|
|
178
|
-
public source = Option.String('--source', { description: 'Source Proteum app root used for .env copy and worktree creation.' });
|
|
178
|
+
public source = Option.String('--source', { description: 'Source Proteum app root used for app/root .env copy and worktree creation.' });
|
|
179
179
|
public branch = Option.String('--branch', { description: 'Branch name created by `worktree create`.' });
|
|
180
180
|
public base = Option.String('--base', { description: 'Base ref used by `worktree create`.' });
|
|
181
181
|
public refresh = Option.Boolean('--refresh', false, { description: 'Refresh an existing stale bootstrap marker.' });
|
|
@@ -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/cli/utils/agents.ts
CHANGED
|
@@ -60,6 +60,9 @@ const managedInstructionSectionHeader = '# Proteum Instructions';
|
|
|
60
60
|
const managedInstructionSectionStart = '<!-- proteum-instructions:start -->';
|
|
61
61
|
const managedInstructionSectionEnd = '<!-- proteum-instructions:end -->';
|
|
62
62
|
const managedInstructionSectionIntro = 'This section is managed by `proteum configure agents`.';
|
|
63
|
+
const agentInstructionFilename = 'AGENTS.md';
|
|
64
|
+
const claudeInstructionFilename = 'CLAUDE.md';
|
|
65
|
+
const claudeInstructionPointerContent = `@${agentInstructionFilename}`;
|
|
63
66
|
|
|
64
67
|
const sharedRootDocumentInstructionDefinitions: TAgentInstructionDefinition[] = [
|
|
65
68
|
{ projectPath: 'DOCUMENTATION.md', content: 'source' },
|
|
@@ -243,6 +246,45 @@ function getRootAgentInstructionDefinitions() {
|
|
|
243
246
|
return monorepoRootAgentInstructionDefinitions.map((instructionDefinition) => ({ ...instructionDefinition }));
|
|
244
247
|
}
|
|
245
248
|
|
|
249
|
+
function createEmptyInstructionResult(): TEnsureInstructionFilesResult {
|
|
250
|
+
return {
|
|
251
|
+
blocked: [],
|
|
252
|
+
created: [],
|
|
253
|
+
overwritten: [],
|
|
254
|
+
removed: [],
|
|
255
|
+
skipped: [],
|
|
256
|
+
updated: [],
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function mergeEnsureInstructionResults(result: TEnsureInstructionFilesResult, next: TEnsureInstructionFilesResult) {
|
|
261
|
+
result.created.push(...next.created);
|
|
262
|
+
result.overwritten.push(...next.overwritten);
|
|
263
|
+
result.removed.push(...next.removed);
|
|
264
|
+
result.updated.push(...next.updated);
|
|
265
|
+
result.skipped.push(...next.skipped);
|
|
266
|
+
result.blocked.push(...next.blocked);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function getManagedInstructionProjectPaths(instructionDefinitions: TAgentInstructionDefinition[]) {
|
|
270
|
+
const projectPaths: string[] = [];
|
|
271
|
+
|
|
272
|
+
for (const instructionDefinition of instructionDefinitions) {
|
|
273
|
+
projectPaths.push(instructionDefinition.projectPath);
|
|
274
|
+
|
|
275
|
+
const claudeProjectPath = getClaudeCompanionProjectPath(instructionDefinition.projectPath);
|
|
276
|
+
if (claudeProjectPath) projectPaths.push(claudeProjectPath);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return projectPaths;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function getClaudeCompanionProjectPath(projectPath: string) {
|
|
283
|
+
if (path.basename(projectPath).toLowerCase() !== agentInstructionFilename.toLowerCase()) return undefined;
|
|
284
|
+
|
|
285
|
+
return path.join(path.dirname(projectPath), claudeInstructionFilename);
|
|
286
|
+
}
|
|
287
|
+
|
|
246
288
|
function removeInstructionGitignoreEntries({
|
|
247
289
|
rootDir,
|
|
248
290
|
instructionDefinitions,
|
|
@@ -254,7 +296,7 @@ function removeInstructionGitignoreEntries({
|
|
|
254
296
|
if (!pathEntryExists(gitignoreFilepath)) return false;
|
|
255
297
|
|
|
256
298
|
const managedEntries = new Set(
|
|
257
|
-
instructionDefinitions.map((
|
|
299
|
+
getManagedInstructionProjectPaths(instructionDefinitions).map((projectPath) => normalizeGitignoreEntry(projectPath)),
|
|
258
300
|
);
|
|
259
301
|
const lines = fs.readFileSync(gitignoreFilepath, 'utf8').split(/\r?\n/);
|
|
260
302
|
const filteredLines: string[] = [];
|
|
@@ -341,12 +383,28 @@ function ensureInstructionFiles(
|
|
|
341
383
|
: upsertManagedInstructionSection(existingState.content, instructionContent);
|
|
342
384
|
if (nextContent === existingState.content) {
|
|
343
385
|
result.skipped.push(relativeProjectPath);
|
|
386
|
+
ensureClaudeCompanionIfNeeded({
|
|
387
|
+
dryRun,
|
|
388
|
+
instructionDefinition,
|
|
389
|
+
logPrefix,
|
|
390
|
+
overwriteBlockedPaths,
|
|
391
|
+
result,
|
|
392
|
+
rootDir,
|
|
393
|
+
});
|
|
344
394
|
continue;
|
|
345
395
|
}
|
|
346
396
|
|
|
347
397
|
if (!dryRun) fs.writeFileSync(projectFilepath, nextContent);
|
|
348
398
|
result.updated.push(relativeProjectPath);
|
|
349
399
|
logVerbose(`${logPrefix} Updated ${relativeProjectPath}`);
|
|
400
|
+
ensureClaudeCompanionIfNeeded({
|
|
401
|
+
dryRun,
|
|
402
|
+
instructionDefinition,
|
|
403
|
+
logPrefix,
|
|
404
|
+
overwriteBlockedPaths,
|
|
405
|
+
result,
|
|
406
|
+
rootDir,
|
|
407
|
+
});
|
|
350
408
|
continue;
|
|
351
409
|
}
|
|
352
410
|
|
|
@@ -357,6 +415,14 @@ function ensureInstructionFiles(
|
|
|
357
415
|
}
|
|
358
416
|
result.updated.push(relativeProjectPath);
|
|
359
417
|
logVerbose(`${logPrefix} Updated ${relativeProjectPath}`);
|
|
418
|
+
ensureClaudeCompanionIfNeeded({
|
|
419
|
+
dryRun,
|
|
420
|
+
instructionDefinition,
|
|
421
|
+
logPrefix,
|
|
422
|
+
overwriteBlockedPaths,
|
|
423
|
+
result,
|
|
424
|
+
rootDir,
|
|
425
|
+
});
|
|
360
426
|
continue;
|
|
361
427
|
}
|
|
362
428
|
|
|
@@ -373,12 +439,28 @@ function ensureInstructionFiles(
|
|
|
373
439
|
}
|
|
374
440
|
result.overwritten.push(relativeProjectPath);
|
|
375
441
|
logVerbose(`${logPrefix} Replaced ${relativeProjectPath}`);
|
|
442
|
+
ensureClaudeCompanionIfNeeded({
|
|
443
|
+
dryRun,
|
|
444
|
+
instructionDefinition,
|
|
445
|
+
logPrefix,
|
|
446
|
+
overwriteBlockedPaths,
|
|
447
|
+
result,
|
|
448
|
+
rootDir,
|
|
449
|
+
});
|
|
376
450
|
continue;
|
|
377
451
|
}
|
|
378
452
|
|
|
379
453
|
if (!dryRun) fs.writeFileSync(projectFilepath, instructionContent);
|
|
380
454
|
result.created.push(relativeProjectPath);
|
|
381
455
|
logVerbose(`${logPrefix} Created ${relativeProjectPath}`);
|
|
456
|
+
ensureClaudeCompanionIfNeeded({
|
|
457
|
+
dryRun,
|
|
458
|
+
instructionDefinition,
|
|
459
|
+
logPrefix,
|
|
460
|
+
overwriteBlockedPaths,
|
|
461
|
+
result,
|
|
462
|
+
rootDir,
|
|
463
|
+
});
|
|
382
464
|
}
|
|
383
465
|
|
|
384
466
|
return result;
|
|
@@ -422,6 +504,13 @@ function removeManagedInstructionFiles(
|
|
|
422
504
|
if (!dryRun) fs.removeSync(projectFilepath);
|
|
423
505
|
result.removed.push(relativeProjectPath);
|
|
424
506
|
logVerbose(`${logPrefix} Removed retired app-root ${relativeProjectPath}`);
|
|
507
|
+
removeClaudeCompanionIfNeeded({
|
|
508
|
+
dryRun,
|
|
509
|
+
instructionDefinition,
|
|
510
|
+
logPrefix,
|
|
511
|
+
result,
|
|
512
|
+
rootDir,
|
|
513
|
+
});
|
|
425
514
|
continue;
|
|
426
515
|
}
|
|
427
516
|
|
|
@@ -437,6 +526,13 @@ function removeManagedInstructionFiles(
|
|
|
437
526
|
if (!dryRun) fs.removeSync(projectFilepath);
|
|
438
527
|
result.removed.push(relativeProjectPath);
|
|
439
528
|
logVerbose(`${logPrefix} Removed retired app-root ${relativeProjectPath}`);
|
|
529
|
+
removeClaudeCompanionIfNeeded({
|
|
530
|
+
dryRun,
|
|
531
|
+
instructionDefinition,
|
|
532
|
+
logPrefix,
|
|
533
|
+
result,
|
|
534
|
+
rootDir,
|
|
535
|
+
});
|
|
440
536
|
continue;
|
|
441
537
|
}
|
|
442
538
|
|
|
@@ -452,6 +548,179 @@ function removeManagedInstructionFiles(
|
|
|
452
548
|
return result;
|
|
453
549
|
}
|
|
454
550
|
|
|
551
|
+
function ensureClaudeCompanionIfNeeded({
|
|
552
|
+
dryRun,
|
|
553
|
+
instructionDefinition,
|
|
554
|
+
logPrefix,
|
|
555
|
+
overwriteBlockedPaths,
|
|
556
|
+
result,
|
|
557
|
+
rootDir,
|
|
558
|
+
}: {
|
|
559
|
+
dryRun: boolean;
|
|
560
|
+
instructionDefinition: TAgentInstructionDefinition;
|
|
561
|
+
logPrefix: string;
|
|
562
|
+
overwriteBlockedPaths: Set<string>;
|
|
563
|
+
result: TEnsureInstructionFilesResult;
|
|
564
|
+
rootDir: string;
|
|
565
|
+
}) {
|
|
566
|
+
const claudeProjectPath = getClaudeCompanionProjectPath(instructionDefinition.projectPath);
|
|
567
|
+
if (!claudeProjectPath) return;
|
|
568
|
+
|
|
569
|
+
mergeEnsureInstructionResults(
|
|
570
|
+
result,
|
|
571
|
+
ensureClaudeInstructionSymlink({
|
|
572
|
+
claudeProjectPath,
|
|
573
|
+
dryRun,
|
|
574
|
+
logPrefix,
|
|
575
|
+
overwriteBlockedPaths,
|
|
576
|
+
rootDir,
|
|
577
|
+
}),
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function ensureClaudeInstructionSymlink({
|
|
582
|
+
claudeProjectPath,
|
|
583
|
+
dryRun,
|
|
584
|
+
logPrefix,
|
|
585
|
+
overwriteBlockedPaths,
|
|
586
|
+
rootDir,
|
|
587
|
+
}: {
|
|
588
|
+
claudeProjectPath: string;
|
|
589
|
+
dryRun: boolean;
|
|
590
|
+
logPrefix: string;
|
|
591
|
+
overwriteBlockedPaths: Set<string>;
|
|
592
|
+
rootDir: string;
|
|
593
|
+
}): TEnsureInstructionFilesResult {
|
|
594
|
+
const result = createEmptyInstructionResult();
|
|
595
|
+
const claudeFilepath = path.join(rootDir, claudeProjectPath);
|
|
596
|
+
const claudeParentDir = path.dirname(claudeFilepath);
|
|
597
|
+
const relativeClaudePath = path.relative(rootDir, claudeFilepath) || '.';
|
|
598
|
+
|
|
599
|
+
if (!fs.existsSync(claudeParentDir)) {
|
|
600
|
+
result.skipped.push(relativeClaudePath);
|
|
601
|
+
return result;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const existingState = inspectExistingClaudePath(claudeFilepath);
|
|
605
|
+
|
|
606
|
+
if (existingState.kind === 'correct') {
|
|
607
|
+
result.skipped.push(relativeClaudePath);
|
|
608
|
+
return result;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (existingState.kind === 'missing') {
|
|
612
|
+
if (!dryRun) fs.symlinkSync(agentInstructionFilename, claudeFilepath);
|
|
613
|
+
result.created.push(relativeClaudePath);
|
|
614
|
+
logVerbose(`${logPrefix} Created ${relativeClaudePath}`);
|
|
615
|
+
return result;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (existingState.kind === 'managed-different') {
|
|
619
|
+
if (!dryRun) {
|
|
620
|
+
fs.removeSync(claudeFilepath);
|
|
621
|
+
fs.symlinkSync(agentInstructionFilename, claudeFilepath);
|
|
622
|
+
}
|
|
623
|
+
result.updated.push(relativeClaudePath);
|
|
624
|
+
logVerbose(`${logPrefix} Updated ${relativeClaudePath}`);
|
|
625
|
+
return result;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const normalizedClaudeFilepath = normalizeAbsolutePath(claudeFilepath);
|
|
629
|
+
if (!overwriteBlockedPaths.has(normalizedClaudeFilepath)) {
|
|
630
|
+
result.blocked.push(relativeClaudePath);
|
|
631
|
+
return result;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (!dryRun) {
|
|
635
|
+
fs.removeSync(claudeFilepath);
|
|
636
|
+
fs.symlinkSync(agentInstructionFilename, claudeFilepath);
|
|
637
|
+
}
|
|
638
|
+
result.overwritten.push(relativeClaudePath);
|
|
639
|
+
logVerbose(`${logPrefix} Replaced ${relativeClaudePath}`);
|
|
640
|
+
|
|
641
|
+
return result;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function removeClaudeCompanionIfNeeded({
|
|
645
|
+
dryRun,
|
|
646
|
+
instructionDefinition,
|
|
647
|
+
logPrefix,
|
|
648
|
+
result,
|
|
649
|
+
rootDir,
|
|
650
|
+
}: {
|
|
651
|
+
dryRun: boolean;
|
|
652
|
+
instructionDefinition: TAgentInstructionDefinition;
|
|
653
|
+
logPrefix: string;
|
|
654
|
+
result: TEnsureInstructionFilesResult;
|
|
655
|
+
rootDir: string;
|
|
656
|
+
}) {
|
|
657
|
+
const claudeProjectPath = getClaudeCompanionProjectPath(instructionDefinition.projectPath);
|
|
658
|
+
if (!claudeProjectPath) return;
|
|
659
|
+
|
|
660
|
+
mergeEnsureInstructionResults(
|
|
661
|
+
result,
|
|
662
|
+
removeClaudeInstructionSymlink({
|
|
663
|
+
claudeProjectPath,
|
|
664
|
+
dryRun,
|
|
665
|
+
logPrefix,
|
|
666
|
+
rootDir,
|
|
667
|
+
}),
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function removeClaudeInstructionSymlink({
|
|
672
|
+
claudeProjectPath,
|
|
673
|
+
dryRun,
|
|
674
|
+
logPrefix,
|
|
675
|
+
rootDir,
|
|
676
|
+
}: {
|
|
677
|
+
claudeProjectPath: string;
|
|
678
|
+
dryRun: boolean;
|
|
679
|
+
logPrefix: string;
|
|
680
|
+
rootDir: string;
|
|
681
|
+
}): TEnsureInstructionFilesResult {
|
|
682
|
+
const result = createEmptyInstructionResult();
|
|
683
|
+
const claudeFilepath = path.join(rootDir, claudeProjectPath);
|
|
684
|
+
const relativeClaudePath = path.relative(rootDir, claudeFilepath) || '.';
|
|
685
|
+
|
|
686
|
+
if (!pathEntryExists(claudeFilepath)) return result;
|
|
687
|
+
|
|
688
|
+
const existingState = inspectExistingClaudePath(claudeFilepath);
|
|
689
|
+
if (existingState.kind === 'correct' || existingState.kind === 'managed-different') {
|
|
690
|
+
if (!dryRun) fs.removeSync(claudeFilepath);
|
|
691
|
+
result.removed.push(relativeClaudePath);
|
|
692
|
+
logVerbose(`${logPrefix} Removed retired app-root ${relativeClaudePath}`);
|
|
693
|
+
return result;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (existingState.kind === 'blocked') result.skipped.push(relativeClaudePath);
|
|
697
|
+
|
|
698
|
+
return result;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function inspectExistingClaudePath(claudeFilepath: string) {
|
|
702
|
+
if (!pathEntryExists(claudeFilepath)) return { kind: 'missing' as const };
|
|
703
|
+
|
|
704
|
+
const stats = fs.lstatSync(claudeFilepath);
|
|
705
|
+
const claudeParentDir = path.dirname(claudeFilepath);
|
|
706
|
+
const agentFilepath = path.join(claudeParentDir, agentInstructionFilename);
|
|
707
|
+
|
|
708
|
+
if (stats.isSymbolicLink()) {
|
|
709
|
+
const rawTarget = fs.readlinkSync(claudeFilepath);
|
|
710
|
+
const resolvedTarget = path.resolve(claudeParentDir, rawTarget);
|
|
711
|
+
if (normalizeAbsolutePath(resolvedTarget) !== normalizeAbsolutePath(agentFilepath)) return { kind: 'blocked' as const };
|
|
712
|
+
|
|
713
|
+
return rawTarget === agentInstructionFilename ? { kind: 'correct' as const } : { kind: 'managed-different' as const };
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
if (!stats.isFile()) return { kind: 'blocked' as const };
|
|
717
|
+
|
|
718
|
+
const content = fs.readFileSync(claudeFilepath, 'utf8');
|
|
719
|
+
if (content.trim() === claudeInstructionPointerContent) return { kind: 'managed-different' as const };
|
|
720
|
+
|
|
721
|
+
return { kind: 'blocked' as const };
|
|
722
|
+
}
|
|
723
|
+
|
|
455
724
|
function inspectExistingPath({
|
|
456
725
|
managedSourceRoot,
|
|
457
726
|
projectFilepath,
|
package/docs/diagnostics.md
CHANGED
|
@@ -112,7 +112,7 @@ 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
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 action when needed, then pass the selected live `projectId` to follow-up app-bound tools.
|
|
118
118
|
|
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.4",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/proteum.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -15,6 +15,23 @@ const writeFile = (filepath, content) => {
|
|
|
15
15
|
fs.writeFileSync(filepath, content);
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
const assertClaudeSymlink = (root, relativeDir = '') => {
|
|
19
|
+
const linkPath = path.join(root, relativeDir, 'CLAUDE.md');
|
|
20
|
+
const stats = fs.lstatSync(linkPath);
|
|
21
|
+
|
|
22
|
+
assert.equal(stats.isSymbolicLink(), true, `${linkPath} should be a symlink`);
|
|
23
|
+
assert.equal(fs.readlinkSync(linkPath), 'AGENTS.md');
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const pathEntryExists = (filepath) => {
|
|
27
|
+
try {
|
|
28
|
+
fs.lstatSync(filepath);
|
|
29
|
+
return true;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
18
35
|
const makeTempRoot = () => fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-agents-'));
|
|
19
36
|
|
|
20
37
|
const createCoreFixture = () => {
|
|
@@ -53,6 +70,7 @@ const createAppFixture = () => {
|
|
|
53
70
|
'node_modules',
|
|
54
71
|
'# Proteum-managed instruction files',
|
|
55
72
|
'/AGENTS.md',
|
|
73
|
+
'/CLAUDE.md',
|
|
56
74
|
'/CODING_STYLE.md',
|
|
57
75
|
'/DOCUMENTATION.md',
|
|
58
76
|
'# End Proteum-managed instruction files',
|
|
@@ -137,9 +155,17 @@ test('standalone configure creates tracked instruction files with routing contra
|
|
|
137
155
|
assert.equal(fs.existsSync(path.join(appRoot, 'tests', 'e2e', 'REAL_WORLD_JOURNEY_TESTS.md')), true);
|
|
138
156
|
assert.match(fs.readFileSync(path.join(appRoot, 'tests', 'e2e', 'REAL_WORLD_JOURNEY_TESTS.md'), 'utf8'), /Journey rule/);
|
|
139
157
|
assert.doesNotMatch(fs.readFileSync(path.join(appRoot, 'tests', 'e2e', 'REAL_WORLD_JOURNEY_TESTS.md'), 'utf8'), /## Source: CODING_STYLE\.md/);
|
|
158
|
+
assertClaudeSymlink(appRoot);
|
|
159
|
+
assertClaudeSymlink(appRoot, 'client');
|
|
160
|
+
assertClaudeSymlink(appRoot, 'client/pages');
|
|
161
|
+
assertClaudeSymlink(appRoot, 'server/routes');
|
|
162
|
+
assertClaudeSymlink(appRoot, 'server/services');
|
|
163
|
+
assertClaudeSymlink(appRoot, 'tests');
|
|
164
|
+
assertClaudeSymlink(appRoot, 'tests/e2e');
|
|
140
165
|
assert.doesNotMatch(agentsContent, /Before reading or applying instructions from this file/);
|
|
141
166
|
assert.doesNotMatch(gitignoreContent, /Proteum-managed instruction files/);
|
|
142
167
|
assert.doesNotMatch(gitignoreContent, /^\/AGENTS\.md$/m);
|
|
168
|
+
assert.doesNotMatch(gitignoreContent, /^\/CLAUDE\.md$/m);
|
|
143
169
|
assert.doesNotMatch(gitignoreContent, /^\/DOCUMENTATION\.md$/m);
|
|
144
170
|
});
|
|
145
171
|
|
|
@@ -255,13 +281,20 @@ test('monorepo configure writes root and app instruction files', () => {
|
|
|
255
281
|
assert.match(fs.readFileSync(path.join(monorepoRoot, 'tests', 'e2e', 'AGENTS.md'), 'utf8'), /## Source: tests\/e2e\/AGENTS\.md/);
|
|
256
282
|
assert.match(fs.readFileSync(path.join(monorepoRoot, 'tests', 'e2e', 'REAL_WORLD_JOURNEY_TESTS.md'), 'utf8'), /## Source: tests\/e2e\/REAL_WORLD_JOURNEY_TESTS\.md/);
|
|
257
283
|
assert.doesNotMatch(fs.readFileSync(path.join(monorepoRoot, 'tests', 'e2e', 'REAL_WORLD_JOURNEY_TESTS.md'), 'utf8'), /## Source: CODING_STYLE\.md/);
|
|
284
|
+
assertClaudeSymlink(monorepoRoot);
|
|
285
|
+
assertClaudeSymlink(monorepoRoot, 'tests');
|
|
286
|
+
assertClaudeSymlink(monorepoRoot, 'tests/e2e');
|
|
258
287
|
assert.equal(fs.existsSync(path.join(appRoot, 'tests', 'AGENTS.md')), false);
|
|
259
288
|
assert.equal(fs.existsSync(path.join(appRoot, 'tests', 'e2e', 'AGENTS.md')), false);
|
|
289
|
+
assert.equal(pathEntryExists(path.join(appRoot, 'tests', 'CLAUDE.md')), false);
|
|
290
|
+
assert.equal(pathEntryExists(path.join(appRoot, 'tests', 'e2e', 'CLAUDE.md')), false);
|
|
260
291
|
const appAgentsContent = fs.readFileSync(path.join(appRoot, 'AGENTS.md'), 'utf8');
|
|
261
292
|
assert.match(appAgentsContent, /## Agent Routing Contract/);
|
|
262
293
|
assert.doesNotMatch(appAgentsContent, /## Known Proteum Apps/);
|
|
263
294
|
assert.doesNotMatch(appAgentsContent, /Do not start `npx proteum dev` from this root/);
|
|
264
295
|
assert.match(fs.readFileSync(path.join(appRoot, 'client', 'AGENTS.md'), 'utf8'), /## Source: client\/AGENTS\.md/);
|
|
296
|
+
assertClaudeSymlink(appRoot);
|
|
297
|
+
assertClaudeSymlink(appRoot, 'client');
|
|
265
298
|
assert.equal(fs.existsSync(path.join(appRoot, 'CODING_STYLE.md')), false);
|
|
266
299
|
assert.equal(fs.existsSync(path.join(appRoot, 'DOCUMENTATION.md')), false);
|
|
267
300
|
assert.equal(fs.existsSync(path.join(appRoot, 'diagnostics.md')), false);
|
|
@@ -331,6 +364,45 @@ test('configure migrates legacy managed symlinks to tracked files', () => {
|
|
|
331
364
|
assert.equal(result.updated.some((entry) => entry.endsWith('/AGENTS.md')), true);
|
|
332
365
|
assert.equal(stats.isSymbolicLink(), false);
|
|
333
366
|
assert.match(content, /# Proteum Instructions/);
|
|
367
|
+
assertClaudeSymlink(appRoot);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test('configure migrates one-line Claude pointer files to symlinks', () => {
|
|
371
|
+
const coreRoot = createCoreFixture();
|
|
372
|
+
const appRoot = createAppFixture();
|
|
373
|
+
const linkPath = path.join(appRoot, 'CLAUDE.md');
|
|
374
|
+
|
|
375
|
+
writeFile(linkPath, '@AGENTS.md\n');
|
|
376
|
+
|
|
377
|
+
const result = configureProjectAgentInstructions({ appRoot, coreRoot });
|
|
378
|
+
|
|
379
|
+
assert.equal(result.updated.some((entry) => entry.endsWith('/CLAUDE.md')), true);
|
|
380
|
+
assertClaudeSymlink(appRoot);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test('configure reports blocked Claude companion paths unless overwrite is allowed', () => {
|
|
384
|
+
const coreRoot = createCoreFixture();
|
|
385
|
+
const appRoot = createAppFixture();
|
|
386
|
+
const blockedPath = path.join(appRoot, 'CLAUDE.md');
|
|
387
|
+
|
|
388
|
+
writeFile(blockedPath, '# Local Claude Notes\n\n- Keep this local rule.\n');
|
|
389
|
+
|
|
390
|
+
const preview = configureProjectAgentInstructions({ appRoot, coreRoot, dryRun: true });
|
|
391
|
+
assert.equal(preview.blocked.some((entry) => entry.endsWith('/CLAUDE.md')), true);
|
|
392
|
+
|
|
393
|
+
const blockedResult = configureProjectAgentInstructions({ appRoot, coreRoot });
|
|
394
|
+
assert.equal(blockedResult.blocked.some((entry) => entry.endsWith('/CLAUDE.md')), true);
|
|
395
|
+
assert.equal(fs.lstatSync(blockedPath).isFile(), true);
|
|
396
|
+
assert.match(fs.readFileSync(blockedPath, 'utf8'), /Keep this local rule/);
|
|
397
|
+
|
|
398
|
+
const result = configureProjectAgentInstructions({
|
|
399
|
+
appRoot,
|
|
400
|
+
coreRoot,
|
|
401
|
+
overwriteBlockedPaths: [blockedPath],
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
assert.equal(result.overwritten.some((entry) => entry.endsWith('/CLAUDE.md')), true);
|
|
405
|
+
assertClaudeSymlink(appRoot);
|
|
334
406
|
});
|
|
335
407
|
|
|
336
408
|
test('configure reports blocked paths unless overwrite is allowed', () => {
|
|
@@ -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);
|