proteum 2.4.4 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +81 -52
  2. package/agents/project/AGENTS.md +112 -31
  3. package/agents/project/CODING_STYLE.md +2 -2
  4. package/agents/project/app-root/AGENTS.md +1 -3
  5. package/agents/project/client/AGENTS.md +5 -1
  6. package/agents/project/client/pages/AGENTS.md +21 -9
  7. package/agents/project/diagnostics.md +2 -2
  8. package/agents/project/optimizations.md +1 -1
  9. package/agents/project/root/AGENTS.md +105 -22
  10. package/agents/project/server/routes/AGENTS.md +30 -1
  11. package/agents/project/server/services/AGENTS.md +4 -0
  12. package/agents/project/tests/AGENTS.md +1 -1
  13. package/cli/commands/doctor.ts +54 -3
  14. package/cli/commands/runtime.ts +6 -0
  15. package/cli/commands/worktree.ts +116 -0
  16. package/cli/compiler/artifacts/controllers.ts +16 -15
  17. package/cli/compiler/artifacts/discovery.ts +129 -17
  18. package/cli/compiler/artifacts/routing.ts +0 -5
  19. package/cli/compiler/artifacts/services.ts +253 -76
  20. package/cli/compiler/common/controllers.ts +159 -57
  21. package/cli/compiler/common/generatedRouteModules.ts +457 -363
  22. package/cli/mcp/router.ts +47 -3
  23. package/cli/presentation/commands.ts +25 -15
  24. package/cli/runtime/commands.ts +39 -12
  25. package/cli/runtime/worktreeBootstrap.ts +608 -0
  26. package/cli/scaffold/index.ts +28 -18
  27. package/cli/scaffold/templates.ts +44 -33
  28. package/cli/utils/agents.ts +14 -1
  29. package/client/app/index.ts +22 -5
  30. package/client/services/router/index.tsx +23 -3
  31. package/client/services/router/request/api.ts +16 -6
  32. package/common/dev/contractsDoctor.ts +1 -1
  33. package/common/dev/mcpPayloads.ts +8 -1
  34. package/common/env/proteumEnv.ts +14 -2
  35. package/common/router/contracts.ts +1 -1
  36. package/common/router/definitions.ts +177 -0
  37. package/common/router/index.ts +23 -12
  38. package/common/router/pageData.ts +5 -5
  39. package/common/router/register.ts +2 -2
  40. package/common/router/request/api.ts +12 -2
  41. package/docs/agent-routing.md +5 -2
  42. package/docs/diagnostics.md +2 -0
  43. package/docs/mcp.md +6 -3
  44. package/docs/migration-2.5.md +226 -0
  45. package/eslint.js +89 -42
  46. package/package.json +1 -1
  47. package/server/app/commands.ts +5 -1
  48. package/server/app/container/console/index.ts +1 -1
  49. package/server/app/controller/index.ts +98 -40
  50. package/server/app/index.ts +120 -3
  51. package/server/app/service/index.ts +5 -1
  52. package/server/index.ts +6 -2
  53. package/server/services/router/index.ts +50 -41
  54. package/server/services/router/response/index.ts +2 -2
  55. package/tests/agents-utils.test.cjs +14 -1
  56. package/tests/cli-mcp-command.test.cjs +84 -0
  57. package/tests/client-app-error-handling.test.cjs +100 -0
  58. package/tests/definition-contracts.test.cjs +453 -0
  59. package/tests/dev-transpile-watch.test.cjs +37 -31
  60. package/tests/eslint-rules.test.cjs +185 -8
  61. package/tests/mcp.test.cjs +90 -0
  62. package/tests/scaffold-templates.test.cjs +18 -0
  63. package/tests/server-app-report-error.test.cjs +135 -0
  64. package/tests/worktree-bootstrap.test.cjs +206 -0
  65. package/types/aliases.d.ts +0 -5
  66. package/types/controller-input.test.ts +23 -17
  67. package/types/controller-request-context.test.ts +10 -11
  68. package/cli/commands/migrate.ts +0 -51
  69. package/cli/migrate/pageContract.ts +0 -516
  70. package/docs/migrate-from-2.1.3.md +0 -396
  71. package/scripts/cleanup-generated-controllers.ts +0 -62
  72. package/scripts/fix-reference-app-typing.ts +0 -490
  73. package/scripts/format-router-registrations.ts +0 -119
  74. package/scripts/migrate-explicit-controllers-and-request.ts +0 -423
  75. package/scripts/refactor-client-app-imports.ts +0 -244
  76. package/scripts/refactor-client-pages.ts +0 -587
  77. package/scripts/refactor-server-controllers.ts +0 -471
  78. package/scripts/refactor-server-runtime-aliases.ts +0 -360
  79. package/scripts/restore-client-app-import-files.ts +0 -41
@@ -2,27 +2,39 @@
2
2
 
3
3
  This is the canonical page-file contract for Proteum-based projects.
4
4
  Role: keep only page-file rules here.
5
- Keep here: `Router.page(...)` registration, SSR `data` and `render` contracts, page payload shape, and page-local typing rules.
5
+ Keep here: `definePageRoute(...)` and `defineErrorRoute(...)`, SSR `data` and `render` contracts, page payload shape, and page-local typing rules.
6
6
  Do not put here: generic component rules, server/service implementation details, or app-wide workflow already covered by broader AGENTS files.
7
7
 
8
8
  Optimization source of truth: root-level `optimizations.md`.
9
9
  Diagnostics source of truth: root-level `diagnostics.md`.
10
10
  Coding style source of truth: root-level `CODING_STYLE.md`.
11
11
 
12
- ## Router.page Usage
12
+ ## Page Definition Usage
13
13
 
14
- - Proteum scans page files for top-level `Router.page(...)` and `Router.error(...)` calls.
15
- - File path controls chunk identity and layout discovery; route path comes from the explicit `Router.page(...)` string.
16
- - The only supported page signature is `Router.page(path, options, data, render)`.
14
+ - Proteum page files default-export `definePageRoute({ path, options, data, render })` or `defineErrorRoute({ code, options, render })`.
15
+ - File path controls chunk identity and layout discovery; route URL comes from the explicit `path` value.
17
16
  - `options` is always required and must be an object.
18
- - `data` is the only nullable argument. Pass `null` when the page does not need SSR data.
19
- - Keep the `Router.page(...)` call compact instead of exploding each outer argument onto its own line.
20
- - Keep route registration at top level. Do not hide it behind helper abstractions.
17
+ - `data` is the only nullable route field. Pass `null` when the page does not need SSR data.
18
+ - Keep route metadata static and serializable. Runtime app/client references belong only inside `data` and `render`.
19
+ - Do not import `@app`, `@/client/router`, or `@client/router` in page files.
20
+
21
+ ```tsx
22
+ import { definePageRoute } from '@common/router/definitions';
23
+
24
+ export default definePageRoute({
25
+ path: '/dashboard',
26
+ options: { auth: true },
27
+ data: ({ AccountController }) => ({
28
+ account: AccountController.accountPage(),
29
+ }),
30
+ render: ({ account }) => <Dashboard account={account} />,
31
+ });
32
+ ```
21
33
 
22
34
  ## Data And Render
23
35
 
24
36
  - Route behavior belongs in the explicit `options` object, not in page data.
25
- - `data` returns one flat object or `null` is passed as the third argument when no page data is needed.
37
+ - `data` returns one flat object, or the route definition sets `data: null` when no page data is needed.
26
38
  - Returning route-option keys such as `auth`, `layout`, `static`, `redirectLogged`, or their `_`-prefixed variants from `data` is a contract error.
27
39
  - Controller fetchers and promises returned from `data` resolve before render.
28
40
  - If a page needs route data, return it from `data` and read it in `render`.
@@ -55,7 +55,7 @@ This file is the canonical source of truth for diagnostics, temporary instrument
55
55
  ## Verification And Testing
56
56
 
57
57
  - Use the cheapest trustworthy verification that matches the failing layer.
58
- - After implementing a change, verify at the smallest trustworthy layer required by the changed surface first, then run the full project `npx proteum check` before finishing. Do not default to a running app, browser MCP, or Playwright while iterating when a narrower static or request-level verification is enough.
58
+ - After implementing a change, verify at the smallest trustworthy layer required by the changed surface first, including targeted tests when behavior changed. Do not run coverage by default, and do not default to a running app, browser MCP, or Playwright while iterating when a narrower static or request-level verification is enough.
59
59
  - For compile-time or type-safety issues, start with the relevant targeted typecheck or build command. Do not run them by default for unrelated runtime, copy, docs, or local refactor changes.
60
60
  - For request/runtime issues, verify through the real page, route, generated controller call, or command on a running app.
61
61
  - Start the smallest trustworthy runtime surface first: MCP `workflow_start`, then MCP `route_candidates { projectId, query }`, MCP `orient { projectId, query }`, or MCP `explain_summary { projectId, query }` only when more owner detail is needed. If runtime health is unreachable, repair/start dev before any diagnose, trace, or perf read. Once runtime is reachable, use the relevant real URL, generated controller call, command, or MCP `diagnose { projectId, path }`. Use CLI equivalents only when MCP is unavailable or terminal evidence is required. Use browser MCP validation only when request-level verification is insufficient or the change is browser-visible.
@@ -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 full project `npx proteum check` before finishing. Narrower lint or type checks are useful while iterating, but they do not replace the final full check.
67
+ - When the touched surface can affect coding-style enforcement, run the targeted lint or typecheck command for that surface before finishing. Run the repository's non-coverage commit gate before committing, and 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.
@@ -23,7 +23,7 @@ When tradeoffs exist inside optimization work, optimize in this order:
23
23
 
24
24
  ## SSR And Page Size
25
25
 
26
- - SSR page data belongs in the explicit `Router.page(path, options, data, render)` `data` function, not in `api.fetch(...)`.
26
+ - SSR page data belongs in the explicit `definePageRoute({ path, options, data, render })` `data` function, not in `api.fetch(...)`.
27
27
  - `options` carries route behavior. `data` returns one flat object or is `null` when the page has no SSR data loader.
28
28
  - Route-option keys and `_`-prefixed route-option aliases are forbidden in page data and must live in `options`.
29
29
  - If a page needs route data, return it from `data` and read it in `render`.
@@ -21,6 +21,7 @@ Managed compact root routers must use trigger -> canonical instruction file refe
21
21
  - When a Proteum MCP client is available, first call MCP `workflow_start` with `cwd` or a known `projectId`. If it is ambiguous or returns offline app candidates, call `project_resolve { cwd }`, select the intended app root, start exactly one dev server from that app root when needed, then retry `workflow_start`. Pass the returned live `projectId` to every follow-up app-bound MCP tool. `npx proteum dev` ensures one managed machine MCP daemon is running; do not start a second managed daemon. Prefer MCP `runtime_status`, `orient`, `instructions_resolve`, `explain_summary`, `route_candidates`, `doctor`, `diagnose`, `trace_show`, `perf_request`, `logs_tail`, and `db_query` for read-only runtime/status/orientation/owner/route/trace/perf/log/database reads. Do not run CLI equivalents after a successful MCP result for the same read. Do not run broad source searches for route/page/controller ownership after MCP returns the owner. Use CLI commands when you need reproducible terminal validation, dev/build/check workflows, fallback repair, or output to share with a human.
22
22
  - MCP payloads are compact single-line `proteum-mcp-v1` JSON with capped and paginated detail. Do not expand MCP output for human readability.
23
23
  - For every non-trivial coding task, load and follow root-level `DOCUMENTATION.md` before coding.
24
+ - For bug fixes, regressions, incidents, broken public routes, auth/OAuth failures, integration failures, or production behavior fixes, load and follow root-level `DOCUMENTATION.md` before coding so the relevant fix note, regression-test docs, ADR, or explicit skip reason is handled in the same change.
24
25
  - If the user reports an issue, or the agent encounters one during exploration, implementation, verification, or runtime reproduction, load and follow root-level `diagnostics.md`.
25
26
  - If the task touches client-side files, especially `client/**` and page files, load and apply root-level `optimizations.md` only after implementation for post-implementation checking and optimization. Skip it at task start and skip it for server-only, test-only, doc-only, and non-client refactor tasks unless the user explicitly asks for optimization work.
26
27
  - If the task needs new app or artifact boilerplate, prefer `npx proteum init ...` and `npx proteum create ...` before creating files by hand. Use `--dry-run --json` when an agent needs a machine-readable plan before writing files.
@@ -63,21 +64,22 @@ Managed compact root routers must use trigger -> canonical instruction file refe
63
64
  ### Before Finishing
64
65
 
65
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.
66
- - For production changes, always add or update focused unit tests and maintain 100% whole-project Vitest unit coverage. Before claiming implementation work complete, run `npx vitest run --coverage` from the repository root and make sure global statements, branches, functions, and lines all meet the configured 100% thresholds. MCP endpoint-reference coverage, touched-path-only coverage, and focused test passes are not substitutes for whole-project Vitest coverage. Document any generated files, migrations, framework shims, unreachable defensive branches, or changes that cannot reasonably be unit-tested as explicit exceptions, and do not claim the project has full unit coverage when an exception remains.
67
- - Run the full project `npx proteum check` before finishing each feature or change. Targeted lint, typecheck, diagnose, request, browser, or E2E runs are still useful while iterating, but they do not replace the final full check. After implementing a new feature or changing existing feature behavior, always update the end-to-end coverage for that behavior and run the full Playwright test suite before finishing. For docs-only, wording-only, type-only, test-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.
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 coverage for commit-only workflows by default. 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. Continue running tests after changes, but do not run coverage by default. Reserve the non-coverage commit gate for commit workflows, and 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.
68
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>`.
69
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:
70
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.`
71
73
 
72
74
  ## Core Contracts
73
75
 
74
- - Client pages live in `client/pages/**` and register routes with top-level `Router.page(...)` or `Router.error(...)`.
75
- - Page URLs come from the explicit `Router.page('/path', ...)` call, not from the file path.
76
- - Callable app APIs live only in `server/controllers/**/*.ts` files that extend `Controller`.
76
+ - Client pages live in `client/pages/**` and default-export `definePageRoute(...)` or `defineErrorRoute(...)`.
77
+ - Page URLs come from the explicit route definition `path`, not from the file path.
78
+ - Callable app APIs live only in `server/controllers/**/*.ts` files that default-export `defineController(...)`.
77
79
  - Dev-only internal execution lives only in `commands/**/*.ts` files that extend `Commands`.
78
80
  - Manual HTTP endpoints live only in `server/routes/**`.
79
- - Controllers call `this.input(schema)` inside the method body, at most once per method.
80
- - Request-scoped state lives only on `this.request` and manual-route/router context objects.
81
+ - Controllers declare input on `defineAction({ input, handler })`; handlers receive parsed `input` in context.
82
+ - Request-scoped state lives only on action handler context and manual-route handler context objects.
81
83
  - Keep one class or one React/Preact component per file.
82
84
  - Prefer a deep tree grouped by business concern instead of long file names.
83
85
  - Use the default `*.ts` or `*.tsx` file unless an `*.ssr.ts` or `*.ssr.tsx` variant is truly required.
@@ -90,14 +92,15 @@ Managed compact root routers must use trigger -> canonical instruction file refe
90
92
  - Do not import runtime values from `@models`.
91
93
  - Do not use `@request` runtime globals.
92
94
  - Do not use `@app` on the client.
95
+ - Do not import `@app` in route, page, or controller files. Runtime app/services/router access belongs in typed callback parameters.
93
96
  - Prefer type inference rooted in the explicit application graph in `server/index.ts`.
94
97
 
95
98
  ## Surface Contracts
96
99
 
97
100
  ### App Bootstrap And Services
98
101
 
99
- - `server/index.ts` default-exports the app `Application` subclass and is the canonical type root.
100
- - Root services are public class fields instantiated with `new ServiceClass(this, config, this)`.
102
+ - `server/index.ts` default-exports `defineApplication({ services, router, models, commands })` and is the canonical type root.
103
+ - Root services are declared in the explicit `services` graph and instantiated with `new ServiceClass(app, config, app)`.
101
104
  - Typed root-service config lives in `server/config/*.ts` via `Services.config(ServiceClass, { ... })`.
102
105
  - Router plugins are instantiated explicitly inside the `Router` config `plugins` object.
103
106
  - Router plugins can subscribe to `request` and `request.finished`; `request.profiling` exists before `request` runs and carries the finalized request/API/SQL snapshot by `request.finished`.
@@ -109,6 +112,51 @@ Managed compact root routers must use trigger -> canonical instruction file refe
109
112
  - Companion client-callable entrypoints live in `server/controllers/**`.
110
113
  - `proteum create service ...` scaffolds the service file, a typed config export under `server/config/*.ts`, and the root registration in `server/index.ts`; review and adapt the generated names before committing.
111
114
 
115
+ Example app root shape; replace names with the project app type and service names:
116
+
117
+ ```ts
118
+ import { defineApplication, type Application } from '@server/app';
119
+ import Router from '@server/services/router';
120
+ import SchemaRouter from '@server/services/schema/router';
121
+ import BillingService from '@/server/services/Billing';
122
+
123
+ import * as appConfig from '@/server/config/app';
124
+
125
+ type ProjectServices = {
126
+ Billing: BillingService;
127
+ };
128
+
129
+ type ProjectRouterPlugins = {
130
+ schema: SchemaRouter;
131
+ };
132
+
133
+ export type ProjectRouter = Router<ProjectApp, ProjectRouterPlugins>;
134
+ export interface ProjectApp extends Application, ProjectServices {
135
+ Router: ProjectRouter;
136
+ }
137
+
138
+ const createProjectRouter = (app: ProjectApp): ProjectRouter =>
139
+ new Router<ProjectApp, ProjectRouterPlugins>(
140
+ app,
141
+ {
142
+ ...appConfig.routerBaseConfig,
143
+ plugins: {
144
+ schema: new SchemaRouter({}, app),
145
+ },
146
+ },
147
+ app,
148
+ );
149
+
150
+ const ProjectApplication = defineApplication<ProjectServices, ProjectRouter>({
151
+ services: (app) => ({
152
+ Billing: new BillingService(app, {}, app),
153
+ }),
154
+ router: createProjectRouter,
155
+ });
156
+
157
+ export default ProjectApplication;
158
+ ```
159
+
112
160
  ### Connected Projects
113
161
 
114
162
  - Declare connected namespaces in `proteum.config.ts` with explicit values such as `connect: { Product: { source: PRODUCT_CONNECTED_SOURCE, urlInternal: PRODUCT_URL_INTERNAL } }`.
@@ -120,13 +168,27 @@ Managed compact root routers must use trigger -> canonical instruction file refe
120
168
 
121
169
  ### Controllers
122
170
 
123
- - Files live under `server/controllers/**/*.ts` and default-export a class extending `Controller`.
124
- - Methods with bodies become generated client-callable endpoints.
125
- - Route path comes from the controller file path plus the method name.
126
- - `export const controllerPath = 'Custom/path'` can override the base path.
171
+ - Files live under `server/controllers/**/*.ts` and default-export `defineController({ path, actions })`.
172
+ - Actions declared with `defineAction(...)` become generated client-callable endpoints.
173
+ - Route path comes from the controller `path` plus the action name.
174
+ - Set `path: 'Custom/path'` on `defineController(...)` to override the base path.
127
175
  - Generated client calls use `POST`.
128
176
  - Prefer `proteum create controller ...` for new controller boilerplate, then adapt the generated method to real service calls.
129
177
 
178
+ ```ts
179
+ import { defineAction, defineController, schema } from '@server/app/controller';
180
+
181
+ export default defineController({
182
+ path: 'Billing',
183
+ actions: {
184
+ read: defineAction({
185
+ input: schema.object({ accountId: schema.string() }),
186
+ handler: ({ input }) => ({ accountId: input.accountId }),
187
+ }),
188
+ },
189
+ });
190
+ ```
191
+
130
192
  ### Commands
131
193
 
132
194
  - Files live under `commands/**/*.ts` and default-export a class extending `Commands` from `@server/app/commands`.
@@ -139,25 +201,47 @@ Managed compact root routers must use trigger -> canonical instruction file refe
139
201
 
140
202
  ### Client Pages
141
203
 
142
- - Proteum scans page files for top-level `Router.page(...)` and `Router.error(...)` calls.
143
- - File path controls chunk identity and layout discovery; route path comes from the explicit `Router.page(...)` string.
144
- - The only supported page signature is `Router.page(path, options, data, render)`.
204
+ - Proteum scans page files for default-exported `definePageRoute(...)` and `defineErrorRoute(...)` definitions.
205
+ - File path controls chunk identity and layout discovery; route path comes from the explicit definition `path` value.
206
+ - The supported page shape is `definePageRoute({ path, options, data, render })`.
145
207
  - `options` is always required. `data` is the only nullable argument and must be `null` when the page has no SSR data loader.
146
208
  - `data` returns one flat object. Route-option keys such as `auth`, `layout`, `static`, and `_static` are forbidden in page data and must live in `options`.
147
209
  - Controller fetchers and promises returned from `data` resolve before render.
148
210
  - `render` consumes resolved page data and uses generated controller methods from render args or `@/client/context`.
149
211
  - Use `api.reload(...)` or `api.set(...)` only when intentionally mutating active page data state.
150
- - Error pages use `Router.error(code, options, render)` in `client/pages/_messages/**`.
212
+ - Error pages use `defineErrorRoute({ code, options, render })` in `client/pages/_messages/**`.
151
213
  - Prefer `proteum create page ...` for new page boilerplate, then review the explicit route path, options object, and data payload.
152
214
 
215
+ ```tsx
216
+ import { definePageRoute } from '@common/router/definitions';
217
+
218
+ export default definePageRoute({
219
+ path: '/billing',
220
+ options: { auth: true },
221
+ data: ({ BillingController }) => ({ billing: BillingController.read({ accountId: 'current' }) }),
222
+ render: ({ billing }) => <BillingPage billing={billing} />,
223
+ });
224
+ ```
225
+
153
226
  ### Manual Routes
154
227
 
155
228
  - Use `server/routes/**` only for explicit HTTP behavior that should not be a generated controller action.
156
229
  - Good fits include redirects, sitemap or RSS output, OAuth callbacks, webhooks, and public resources with custom semantics.
157
- - Import server-side app services from `@app` and use route handler context for `request`, `response`, router plugins, and custom router context.
230
+ - Receive app services through `defineServerRoutes((app) => [...])` and use handler context for `request`, `response`, router plugins, and custom router context.
158
231
  - If the route is a normal app API, prefer a controller.
159
232
  - Prefer `proteum create route ...` for new manual-route boilerplate.
160
233
 
234
+ ```ts
235
+ import { defineServerRoute } from '@common/router/definitions';
236
+
237
+ export default defineServerRoute({
238
+ method: 'GET',
239
+ path: '/health',
240
+ options: {},
241
+ handler: ({ response }) => response.json({ ok: true }),
242
+ });
243
+ ```
244
+
161
245
  ### Models And Aliases
162
246
 
163
247
  - Use Prisma typings from `@models/types`.
@@ -167,19 +251,18 @@ Managed compact root routers must use trigger -> canonical instruction file refe
167
251
  - Aliases:
168
252
  - `@/client/...`, `@/server/...`, `@/common/...`: app code
169
253
  - `@client/...`, `@server/...`, `@common/...`: Proteum core modules
170
- - `@app`: server-side application services for manual routes only
171
254
  - `@generated/*`: generated app surfaces
172
255
 
173
256
  ## Verification Matrix
174
257
 
175
258
  Verify at the correct layer:
176
259
 
177
- - Default: use the cheapest trustworthy verification for the changed surface first, then run the full project `npx proteum check` before finishing.
260
+ - Default: use the cheapest trustworthy verification for the changed surface, including targeted tests for changed behavior. Do not run coverage by default during ordinary change closeout.
178
261
  - Route additions: boot the app and hit the real URL.
179
262
  - Controller changes: exercise the generated client call or generated `/api/...` endpoint.
180
263
  - SSR changes: use the browser MCP to load the real page and inspect rendered HTML plus browser console.
181
264
  - Router or plugin changes: verify request context, auth, redirects, metrics, and validation on a running app.
182
- - New features or feature-behavior changes: use the cheapest trustworthy verification while iterating, use the browser MCP for browser-visible validation, then update the relevant end-to-end coverage and finish by running the full Playwright suite plus the full project `npx proteum check`.
265
+ - 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. Save the non-coverage commit gate for commit workflows and the full `npm run check` gate for push workflows unless the user or project-local instructions explicitly ask for the full gate earlier.
183
266
  - 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.
184
267
  - 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.
185
268
  - 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.
@@ -217,7 +300,7 @@ Verify at the correct layer:
217
300
  ### Discouraged Patterns
218
301
 
219
302
  - request-scoped state inside normal service methods
220
- - hiding route registration behind abstractions that remove the top-level `Router.page(...)` call
303
+ - hiding route definitions behind abstractions that remove the default-exported `definePageRoute(...)` or `defineServerRoute(...)` contract
221
304
  - editing `.proteum` directly
222
305
 
223
306
  ## Hard Stops
@@ -11,8 +11,37 @@ Diagnostics source of truth: root-level `diagnostics.md`.
11
11
  - Use `server/routes/**` only for explicit HTTP behavior that should not be generated from controllers.
12
12
  - If the endpoint is a normal app API, prefer `server/controllers/**/*.ts`.
13
13
  - Good fits include redirects, resources, OAuth callbacks, webhooks, sitemap-like output, and custom public endpoints.
14
+ - Route files default-export `defineServerRoute({ method, path, options, handler })` or `defineServerRoutes([...])`.
15
+ - Keep `method`, `path`, and `options` static. Runtime services are received through the route factory or handler context.
16
+ - Do not import `@app` in route files. Use `defineServerRoutes((app) => [...])` when routes need app services.
17
+ - Use `expressHandler(...)` only when a route needs raw Express `req`, `res`, or `next`.
14
18
  - If a route needs a curated registry, keep server-only data in `/server/catalogs/**` and shared data in `/common/catalogs/**`.
15
19
 
20
+ Example route file; replace `ProjectApp` with the concrete app type exported from `server/index.ts`.
21
+
22
+ ```ts
23
+ import { defineServerRoute, defineServerRoutes, expressHandler } from '@common/router/definitions';
24
+ import type { ProjectApp } from '@/server/index';
25
+
26
+ export default defineServerRoutes((app: ProjectApp) => [
27
+ defineServerRoute({
28
+ method: 'GET',
29
+ path: '/health',
30
+ options: {},
31
+ handler: ({ response }) => response.json({ ok: true }),
32
+ }),
33
+ defineServerRoute({
34
+ method: 'POST',
35
+ path: '/webhook',
36
+ options: {},
37
+ handler: expressHandler((request, response) => {
38
+ app.Webhooks.handle(request.body);
39
+ response.status(204).send('');
40
+ }),
41
+ }),
42
+ ]);
43
+ ```
44
+
16
45
  ## Absolute URLs
17
46
 
18
- Use `Router.url('/relative/path')` to generate absolute URLs.
47
+ Use `context.Router.url('/relative/path')` inside handlers, or `app.Router.url('/relative/path')` inside `defineServerRoutes((app) => ...)`, to generate absolute URLs.
@@ -38,3 +38,7 @@ Diagnostics source of truth: root-level `diagnostics.md`.
38
38
 
39
39
  - Never silence caught errors.
40
40
  - If you need to wrap a failure, preserve enough detail and the original error.
41
+ - Prefer `throw error` when the current request or job should fail.
42
+ - For catch-and-continue server work, detached promises, custom Express responses, or background jobs, call `await this.app.reportError(error, request)` when a request is available, or `await this.app.reportError(error)` without one.
43
+ - Do not call `app.runHook('error', ...)` directly from app code; route caught errors through `app.reportError(...)` so HTTP-specific error hooks stay centralized.
44
+ - `console.*(error)` is not error handling and must not be the last stop for a caught error.
@@ -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 maintain 100% whole-project Vitest unit coverage. Before claiming implementation work complete, run `npx vitest run --coverage` from the repository root and make sure global statements, branches, functions, and lines all meet the configured 100% thresholds. MCP endpoint-reference coverage, touched-path-only coverage, and focused test passes are not substitutes for whole-project Vitest coverage. Document any generated files, migrations, framework shims, unreachable defensive branches, or changes that cannot reasonably be unit-tested as explicit exceptions, and do not claim the project has full unit coverage when an exception remains.
12
+ - For every production change, add or update focused unit tests and run the targeted test command that matches the changed behavior. Do not run whole-project coverage after every ordinary change by default. Use the repository's non-coverage commit gate before commit, and 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.
@@ -1,9 +1,15 @@
1
1
  import cli from '..';
2
2
  import Compiler from '../compiler';
3
3
  import { readProteumManifest } from '../compiler/common/proteumManifest';
4
+ import {
5
+ createWorktreeBootstrapDiagnostics,
6
+ getWorktreeBootstrapStatus,
7
+ type TWorktreeBootstrapDiagnostic,
8
+ } from '../runtime/worktreeBootstrap';
4
9
  import { buildContractsDoctorResponse } from '@common/dev/contractsDoctor';
5
- import { buildDoctorResponse, renderDoctorHuman, renderDoctorResponseHuman } from '@common/dev/diagnostics';
10
+ import { buildDoctorResponse, renderDoctorResponseHuman } from '@common/dev/diagnostics';
6
11
  import { compactList, printAgentResponse, printJson, truncateForAgent } from '../utils/agentOutput';
12
+ import type { TDoctorResponse } from '@common/dev/diagnostics';
7
13
 
8
14
  const allowedDoctorArgs = new Set(['contracts', 'full', 'human', 'json', 'strict']);
9
15
 
@@ -30,6 +36,31 @@ const compactDiagnostic = (diagnostic: ReturnType<typeof buildDoctorResponse>['d
30
36
  fixHint: diagnostic.fixHint ? truncateForAgent(diagnostic.fixHint) : undefined,
31
37
  });
32
38
 
39
+ const mergeBootstrapDiagnostics = ({
40
+ bootstrapDiagnostics,
41
+ response,
42
+ strict,
43
+ }: {
44
+ bootstrapDiagnostics: TWorktreeBootstrapDiagnostic[];
45
+ response: TDoctorResponse;
46
+ strict: boolean;
47
+ }): TDoctorResponse => {
48
+ if (bootstrapDiagnostics.length === 0) return response;
49
+
50
+ const diagnostics = [...response.diagnostics, ...bootstrapDiagnostics];
51
+ const errors = diagnostics.filter((diagnostic) => diagnostic.level === 'error').length;
52
+ const warnings = diagnostics.filter((diagnostic) => diagnostic.level === 'warning').length;
53
+
54
+ return {
55
+ diagnostics,
56
+ summary: {
57
+ errors,
58
+ strictFailed: response.summary.strictFailed || (strict && diagnostics.length > 0),
59
+ warnings,
60
+ },
61
+ };
62
+ };
63
+
33
64
  export const run = async (): Promise<void> => {
34
65
  validateDoctorArgs();
35
66
 
@@ -37,10 +68,25 @@ export const run = async (): Promise<void> => {
37
68
  await compiler.refreshGeneratedTypings();
38
69
 
39
70
  const manifest = readProteumManifest(cli.paths.appRoot);
40
- const response =
71
+ const rawResponse =
41
72
  cli.args.contracts === true
42
73
  ? buildContractsDoctorResponse(manifest, cli.args.strict === true)
43
74
  : buildDoctorResponse(manifest, cli.args.strict === true);
75
+ const bootstrapStatus = getWorktreeBootstrapStatus({
76
+ appRoot: cli.paths.appRoot,
77
+ proteumVersion: String(cli.packageJson.version || ''),
78
+ });
79
+ const response =
80
+ cli.args.contracts === true
81
+ ? rawResponse
82
+ : mergeBootstrapDiagnostics({
83
+ bootstrapDiagnostics: createWorktreeBootstrapDiagnostics({
84
+ appRoot: cli.paths.appRoot,
85
+ status: bootstrapStatus,
86
+ }),
87
+ response: rawResponse,
88
+ strict: cli.args.strict === true,
89
+ });
44
90
 
45
91
  if (cli.args.full === true) {
46
92
  printJson(response);
@@ -53,7 +99,12 @@ export const run = async (): Promise<void> => {
53
99
  response,
54
100
  title: 'Proteum doctor contracts',
55
101
  })
56
- : renderDoctorHuman(manifest, cli.args.strict === true),
102
+ : renderDoctorResponseHuman({
103
+ emptyMessage: 'No diagnostics were found.',
104
+ manifest,
105
+ response,
106
+ title: 'Proteum doctor',
107
+ }),
57
108
  );
58
109
  } else {
59
110
  printAgentResponse({
@@ -7,6 +7,7 @@ import cli from '..';
7
7
  import { readProteumManifest } from '../compiler/common/proteumManifest';
8
8
  import { listDevSessionInspections, writeMachineDevSessionRecord, type TDevSessionInspection } from '../runtime/devSessions';
9
9
  import { inspectDevPort, type TDevPortInspection } from '../runtime/ports';
10
+ import { compactWorktreeBootstrapStatus, getWorktreeBootstrapStatus } from '../runtime/worktreeBootstrap';
10
11
  import { printAgentResponse, printJson, quoteCommandArgument } from '../utils/agentOutput';
11
12
  import type { TDoctorResponse } from '@common/dev/diagnostics';
12
13
  import type { TProteumManifest } from '@common/dev/proteumManifest';
@@ -193,6 +194,10 @@ export const run = async () => {
193
194
  port: manifest.env.resolved.routerPort,
194
195
  })
195
196
  : undefined;
197
+ const worktreeBootstrap = getWorktreeBootstrapStatus({
198
+ appRoot: cli.paths.appRoot,
199
+ proteumVersion: String(cli.packageJson.version || ''),
200
+ });
196
201
 
197
202
  const payload = {
198
203
  appRoot: cli.paths.appRoot,
@@ -216,6 +221,7 @@ export const run = async () => {
216
221
  sessions: sessions.map(compactSession),
217
222
  health,
218
223
  configuredDevPort,
224
+ worktreeBootstrap: compactWorktreeBootstrapStatus(worktreeBootstrap),
219
225
  };
220
226
 
221
227
  if (cli.args.full === true) {
@@ -0,0 +1,116 @@
1
+ import { UsageError } from 'clipanion';
2
+
3
+ import cli from '..';
4
+ import {
5
+ compactWorktreeBootstrapStatus,
6
+ getWorktreeBootstrapStatus,
7
+ runWorktreeBootstrapCreate,
8
+ runWorktreeBootstrapInit,
9
+ } from '../runtime/worktreeBootstrap';
10
+ import { printAgentResponse, printJson } from '../utils/agentOutput';
11
+
12
+ /*----------------------------------
13
+ - HELPERS
14
+ ----------------------------------*/
15
+
16
+ const getAction = () => {
17
+ const action = typeof cli.args.action === 'string' ? cli.args.action : '';
18
+ if (action === 'init' || action === 'create') return action;
19
+
20
+ throw new UsageError('Usage: `proteum worktree init` or `proteum worktree create <target-repo-root>`.');
21
+ };
22
+
23
+ const getStringArg = (name: string) => {
24
+ const value = cli.args[name];
25
+ return typeof value === 'string' ? value.trim() : '';
26
+ };
27
+
28
+ const getBooleanArg = (name: string) => cli.args[name] === true;
29
+
30
+ const printResult = ({
31
+ data,
32
+ json,
33
+ summary,
34
+ }: {
35
+ data: object;
36
+ json: boolean;
37
+ summary: string;
38
+ }) => {
39
+ if (json) {
40
+ printJson({ ok: true, format: 'proteum-agent-v1', summary, data });
41
+ return;
42
+ }
43
+
44
+ printAgentResponse({ summary, data });
45
+ };
46
+
47
+ /*----------------------------------
48
+ - COMMAND
49
+ ----------------------------------*/
50
+
51
+ export const run = async (): Promise<void> => {
52
+ const action = getAction();
53
+ const json = getBooleanArg('json');
54
+ const source = getStringArg('source') || undefined;
55
+ const skipDeps = getBooleanArg('skipDeps');
56
+ const reason = getStringArg('reason') || undefined;
57
+
58
+ if (action === 'init') {
59
+ const result = await runWorktreeBootstrapInit({
60
+ appRoot: cli.paths.appRoot,
61
+ coreRoot: cli.paths.core.root,
62
+ json,
63
+ proteumVersion: String(cli.packageJson.version || ''),
64
+ reason,
65
+ refresh: getBooleanArg('refresh'),
66
+ skipDeps,
67
+ source,
68
+ });
69
+
70
+ printResult({
71
+ data: {
72
+ appRoot: result.appRoot,
73
+ markerFilepath: result.markerFilepath,
74
+ worktreeBootstrap: compactWorktreeBootstrapStatus(result.status),
75
+ },
76
+ json,
77
+ summary: 'Proteum worktree bootstrap completed.',
78
+ });
79
+ return;
80
+ }
81
+
82
+ const targetRepoRoot = getStringArg('target');
83
+ const branch = getStringArg('branch');
84
+ if (!source) throw new UsageError('worktree create requires --source <source-app-root>.');
85
+
86
+ const result = await runWorktreeBootstrapCreate({
87
+ appRoot: source,
88
+ base: getStringArg('base') || undefined,
89
+ branch,
90
+ coreRoot: cli.paths.core.root,
91
+ json,
92
+ proteumVersion: String(cli.packageJson.version || ''),
93
+ reason,
94
+ refresh: true,
95
+ skipDeps,
96
+ source,
97
+ targetRepoRoot,
98
+ });
99
+ const status = getWorktreeBootstrapStatus({
100
+ appRoot: result.targetAppRoot,
101
+ proteumVersion: String(cli.packageJson.version || ''),
102
+ });
103
+
104
+ printResult({
105
+ data: {
106
+ branch: result.branch,
107
+ sourceAppRoot: result.sourceAppRoot,
108
+ sourceRepoRoot: result.sourceRepoRoot,
109
+ targetAppRoot: result.targetAppRoot,
110
+ targetRepoRoot: result.targetRepoRoot,
111
+ worktreeBootstrap: compactWorktreeBootstrapStatus(status),
112
+ },
113
+ json,
114
+ summary: `Created Proteum worktree at ${result.targetRepoRoot}.`,
115
+ });
116
+ };