proteum 2.5.0 → 2.5.2

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 (40) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +46 -19
  3. package/agents/project/AGENTS.md +9 -7
  4. package/agents/project/CODING_STYLE.md +1 -1
  5. package/agents/project/client/AGENTS.md +5 -1
  6. package/agents/project/diagnostics.md +1 -1
  7. package/agents/project/root/AGENTS.md +9 -7
  8. package/agents/project/server/services/AGENTS.md +4 -0
  9. package/agents/project/tests/AGENTS.md +1 -1
  10. package/cli/commands/verify.ts +117 -4
  11. package/cli/compiler/artifacts/controllerHelper.ts +66 -0
  12. package/cli/compiler/artifacts/controllers.ts +3 -0
  13. package/cli/compiler/artifacts/services.ts +14 -8
  14. package/cli/compiler/common/generatedRouteModules.ts +270 -53
  15. package/cli/presentation/commands.ts +11 -1
  16. package/cli/runtime/commands.ts +6 -0
  17. package/cli/scaffold/templates.ts +14 -6
  18. package/cli/utils/agents.ts +1 -1
  19. package/cli/verification/changed.ts +460 -0
  20. package/client/app/index.ts +22 -5
  21. package/client/services/router/index.tsx +1 -1
  22. package/client/services/router/request/api.ts +2 -2
  23. package/common/applicationConfig.ts +177 -0
  24. package/common/applicationConfigLoader.ts +33 -1
  25. package/common/dev/contractsDoctor.ts +16 -0
  26. package/config.ts +5 -1
  27. package/docs/migration-2.5.md +269 -0
  28. package/eslint.js +96 -50
  29. package/package.json +1 -1
  30. package/server/app/index.ts +28 -2
  31. package/server/services/router/index.ts +3 -3
  32. package/tests/cli-mcp-command.test.cjs +14 -0
  33. package/tests/client-app-error-handling.test.cjs +100 -0
  34. package/tests/contracts-doctor.test.cjs +98 -0
  35. package/tests/definition-contracts.test.cjs +129 -0
  36. package/tests/dev-transpile-watch.test.cjs +3 -6
  37. package/tests/eslint-rules.test.cjs +246 -7
  38. package/tests/scaffold-templates.test.cjs +43 -0
  39. package/tests/server-app-report-error.test.cjs +135 -0
  40. package/tests/verify-changed.test.cjs +200 -0
package/AGENTS.md CHANGED
@@ -46,8 +46,8 @@ npx prisma migrate dev --config ./prisma.config.ts --name <migration name>
46
46
  [optional body]
47
47
  ```
48
48
  If the user replies exactly `commit`, treat it as conversation-wide and cross-project, not task-scoped. Identify every affected git repository or worktree touched since the last `commit` and, if there has been no prior `commit`, since the beginning of the whole conversation. In each affected repository or worktree, stage all conversation-related changed files with `git add` while still excluding unrelated pre-existing user changes or incidental untracked files, then create one `git commit`. Do not omit linked local dependencies, framework repos, connected projects, or producer apps when they were changed to make the delivered behavior actually work.
49
- After providing a commit message or after creating a commit, immediately follow it with this exact prompt and obey it:
50
- `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.`
49
+ When the user asks to push, explain the currently unpushed commits in short, minimalistic bullet points in plain language, like you would do to your grandma. Start each bullet with a verb in the past.
50
+ When an optional commit body is useful, write it as a short, minimalistic bullet list explaining what changed in this thread in plain language, like you would do to your grandma. Start each bullet with a verb in the past. Do not print a separate prompt asking for that explanation.
51
51
 
52
52
  ## Core Changes
53
53
 
package/README.md CHANGED
@@ -140,7 +140,7 @@ Use this for linked or workspace-local TypeScript packages that ship source file
140
140
 
141
141
  ## Example: Server Bootstrap
142
142
 
143
- Proteum app services are declared explicitly through typed config exports plus a concrete `Application` subclass.
143
+ Proteum app services and router plugins are declared explicitly through typed config exports plus a default-exported `defineApplication(...)` definition object.
144
144
 
145
145
  ```ts
146
146
  // server/config/user.ts
@@ -167,27 +167,47 @@ export const routerBaseConfig = {
167
167
 
168
168
  ```ts
169
169
  // server/index.ts
170
- import { defineApplication } from '@server/app';
170
+ import { defineApplication, type Application } from '@server/app';
171
171
  import Router from '@server/services/router';
172
172
  import SchemaRouter from '@server/services/schema/router';
173
173
  import Users from '@/server/services/Users';
174
174
  import * as userConfig from '@/server/config/user';
175
175
 
176
- export default defineApplication({
177
- services: (app) => ({
178
- Users: new Users(app, userConfig.usersConfig, app),
179
- Router: new Router(
180
- app,
181
- {
182
- ...userConfig.routerBaseConfig,
183
- plugins: {
184
- schema: new SchemaRouter({}, app),
185
- },
176
+ type MyAppServices = {
177
+ Users: Users;
178
+ };
179
+
180
+ type MyRouterPlugins = {
181
+ schema: SchemaRouter;
182
+ };
183
+
184
+ export type MyRouter = Router<MyApp, MyRouterPlugins>;
185
+ export interface MyApp extends Application, MyAppServices {
186
+ Router: MyRouter;
187
+ }
188
+
189
+ const createRouter = (app: MyApp): MyRouter =>
190
+ new Router<MyApp, MyRouterPlugins>(
191
+ app,
192
+ {
193
+ ...userConfig.routerBaseConfig,
194
+ plugins: {
195
+ schema: new SchemaRouter({}, app),
186
196
  },
187
- app
188
- ),
189
- }),
197
+ },
198
+ app
199
+ );
200
+
201
+ const createServices = (app: MyApp): MyAppServices => ({
202
+ Users: new Users(app, userConfig.usersConfig, app),
203
+ });
204
+
205
+ const MyApplication = defineApplication({
206
+ services: createServices,
207
+ router: createRouter,
190
208
  });
209
+
210
+ export default MyApplication;
191
211
  ```
192
212
 
193
213
  Proteum reads `server/index.ts` as the source of truth for installed root services and router plugins, and reads `server/config/*.ts` `Services.config(...)` exports for typed config such as service priority overrides.
@@ -233,7 +253,7 @@ Default public asset validators depend on the environment: dev disables `ETag` a
233
253
  Proteum pages are explicit SSR entrypoints.
234
254
 
235
255
  ```tsx
236
- import { definePageRoute } from '@common/router';
256
+ import { definePageRoute } from '@common/router/definitions';
237
257
 
238
258
  export default definePageRoute({
239
259
  path: '/',
@@ -263,7 +283,7 @@ What happens here:
263
283
  Proteum controllers are explicit request entrypoints.
264
284
 
265
285
  ```ts
266
- import { defineAction, defineController, schema } from '@server/app/controller';
286
+ import { defineAction, defineController, schema } from '@generated/server/controller';
267
287
 
268
288
  export default defineController({
269
289
  path: 'Auth',
@@ -381,7 +401,7 @@ Proteum ships with a compact CLI focused on the real app lifecycle:
381
401
  | `proteum command` | Run a dev-only internal command locally or against a running dev server |
382
402
  | `proteum session` | Mint a dev-only auth session token and Playwright-ready cookie payload |
383
403
  | `proteum e2e` | Run Playwright with Proteum-managed `E2E_*` values instead of shell-leading env assignments |
384
- | `proteum verify` | Validate framework-facing workflows across one or more running dev apps; `framework-change` is the built-in cross-reference-app check |
404
+ | `proteum verify` | Validate targeted changed-file checks, focused owner/request/browser workflows, or the full framework reference-app pass |
385
405
  | `proteum init` | Scaffold a new Proteum app with built-in deterministic templates |
386
406
  | `proteum configure agents` | Interactively configure tracked Proteum instruction files for standalone or monorepo apps |
387
407
  | `proteum create` | Scaffold a page, controller, command, route, or root service inside an app |
@@ -393,6 +413,7 @@ Recommended daily workflow:
393
413
  proteum dev
394
414
  proteum refresh
395
415
  proteum check
416
+ proteum verify changed --dry-run
396
417
  proteum build --prod
397
418
  proteum build --prod --analyze
398
419
  proteum build --prod --analyze --analyze-serve --analyze-port auto
@@ -653,6 +674,12 @@ npx proteum check
653
674
  npx proteum build --prod
654
675
  ```
655
676
 
677
+ ## Migrating To 2.5
678
+
679
+ Proteum 2.5 removes the old contextual route/controller magic. Apps migrate by replacing ambient `@app` imports, top-level `Router.*(...)` route calls, controller classes, and `Application` subclasses with explicit definition objects and typed runtime callback parameters.
680
+
681
+ Use [the 2.5 migration guide](docs/migration-2.5.md) for the full checklist.
682
+
656
683
  ## Repository Structure
657
684
 
658
685
  This repository is organized around the same explicit framework surface it exposes:
@@ -661,7 +688,7 @@ This repository is organized around the same explicit framework surface it expos
661
688
  - `client/`: client runtime, page registration, islands, and router behavior
662
689
  - `server/`: controller base classes, services, runtime, and SSR server behavior
663
690
  - `common/`: shared router contracts, models, request/response types, and utilities
664
- - `doc/`: focused design notes and internal documentation
691
+ - `docs/`: focused design notes and internal documentation
665
692
  - `agents/`: agent-specific conventions and scaffolding used in Proteum-based projects
666
693
 
667
694
  ## Status
@@ -80,7 +80,7 @@ Managed compact root routers must use trigger -> canonical instruction file refe
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. 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.
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. 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.
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.`
@@ -161,10 +161,12 @@ const createProjectRouter = (app: ProjectApp): ProjectRouter =>
161
161
  app,
162
162
  );
163
163
 
164
- const ProjectApplication = defineApplication<ProjectServices, ProjectRouter>({
165
- services: (app) => ({
166
- Billing: new BillingService(app, {}, app),
167
- }),
164
+ const createProjectServices = (app: ProjectApp): ProjectServices => ({
165
+ Billing: new BillingService(app, {}, app),
166
+ });
167
+
168
+ const ProjectApplication = defineApplication({
169
+ services: createProjectServices,
168
170
  router: createProjectRouter,
169
171
  });
170
172
 
@@ -190,7 +192,7 @@ export default ProjectApplication;
190
192
  - Prefer `proteum create controller ...` for new controller boilerplate, then adapt the generated method to real service calls.
191
193
 
192
194
  ```ts
193
- import { defineAction, defineController, schema } from '@server/app/controller';
195
+ import { defineAction, defineController, schema } from '@generated/server/controller';
194
196
 
195
197
  export default defineController({
196
198
  path: 'Billing',
@@ -271,7 +273,7 @@ export default defineServerRoute({
271
273
 
272
274
  Verify at the correct layer:
273
275
 
274
- - 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.
276
+ - Default: use the cheapest trustworthy verification for the changed surface, including targeted tests for changed behavior. When `proteum.verify.config.ts` exists, start with `npx proteum verify changed`. Do not run coverage by default during ordinary change closeout.
275
277
  - Route additions: boot the app and hit the real URL.
276
278
  - Controller changes: exercise the generated client call or generated `/api/...` endpoint.
277
279
  - SSR changes: use the browser MCP to load the real page and inspect rendered HTML plus browser console.
@@ -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. Do not run coverage by default after ordinary changes. Run the repository's non-coverage commit gate before committing, and 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.
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. Run the repository's non-coverage commit gate before committing, and 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
 
@@ -25,7 +25,11 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
25
25
  - Never depend on legacy `@app` imports on the client.
26
26
  - Errors from controller calls should never be silently swallowed. Rethrow or surface them clearly.
27
27
  - Caught frontend errors must always preserve the original failure. Never write `catch {}`, `.catch(() => ...)`, or a catch handler that only shows a generic toast/state without using the caught error.
28
- - Valid frontend error handling includes rethrowing the error, passing it to `useContext().app.handleError(error)` or `context.app.handleError(error)`, logging/reporting it with the original error, or surfacing original detail such as `error.message` in user-visible feedback.
28
+ - Valid terminal frontend error handling is `throw error`, `useContext().app.handleError(error)`, or `context.app.handleError(error)`.
29
+ - Do not normalize caught values in app code before calling `handleError`; the app handles `unknown` values and returns a displayable message.
30
+ - If the app customizes `handleError`, keep the signature `handleError(error: unknown, fallbackMessage?: string): string`.
31
+ - Toasts and form errors are local feedback only; use `setError(context.app.handleError(error, fallbackMessage))` or rethrow the caught error.
32
+ - `console.*(error)` is not error handling and must not be the last stop for a caught error.
29
33
 
30
34
  ## Design
31
35
 
@@ -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, 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.
58
+ - After implementing a change, verify at the smallest trustworthy layer required by the changed surface first, including targeted tests when behavior changed. When the project defines `proteum.verify.config.ts`, prefer `npx proteum verify changed` for the first post-edit verification plan. 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.
@@ -66,7 +66,7 @@ Managed compact root routers must use trigger -> canonical instruction file refe
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
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.
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. 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.
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.`
@@ -147,10 +147,12 @@ const createProjectRouter = (app: ProjectApp): ProjectRouter =>
147
147
  app,
148
148
  );
149
149
 
150
- const ProjectApplication = defineApplication<ProjectServices, ProjectRouter>({
151
- services: (app) => ({
152
- Billing: new BillingService(app, {}, app),
153
- }),
150
+ const createProjectServices = (app: ProjectApp): ProjectServices => ({
151
+ Billing: new BillingService(app, {}, app),
152
+ });
153
+
154
+ const ProjectApplication = defineApplication({
155
+ services: createProjectServices,
154
156
  router: createProjectRouter,
155
157
  });
156
158
 
@@ -176,7 +178,7 @@ export default ProjectApplication;
176
178
  - Prefer `proteum create controller ...` for new controller boilerplate, then adapt the generated method to real service calls.
177
179
 
178
180
  ```ts
179
- import { defineAction, defineController, schema } from '@server/app/controller';
181
+ import { defineAction, defineController, schema } from '@generated/server/controller';
180
182
 
181
183
  export default defineController({
182
184
  path: 'Billing',
@@ -257,7 +259,7 @@ export default defineServerRoute({
257
259
 
258
260
  Verify at the correct layer:
259
261
 
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.
262
+ - Default: use the cheapest trustworthy verification for the changed surface, including targeted tests for changed behavior. When `proteum.verify.config.ts` exists, start with `npx proteum verify changed`. Do not run coverage by default during ordinary change closeout.
261
263
  - Route additions: boot the app and hit the real URL.
262
264
  - Controller changes: exercise the generated client call or generated `/api/...` endpoint.
263
265
  - SSR changes: use the browser MCP to load the real page and inspect rendered HTML plus browser console.
@@ -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 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.
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. 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.
@@ -5,7 +5,6 @@ import path from 'path';
5
5
  import { UsageError } from 'clipanion';
6
6
 
7
7
  import cli from '..';
8
- import Compiler from '../compiler';
9
8
  import Paths from '../paths';
10
9
  import { readProteumManifest } from '../compiler/common/proteumManifest';
11
10
  import { buildContractsDoctorResponse } from '@common/dev/contractsDoctor';
@@ -14,6 +13,13 @@ import { buildOrientationResponse, type TDiagnoseChainItem, type TDiagnoseRespon
14
13
  import type { TProteumManifest, TProteumManifestDiagnostic } from '@common/dev/proteumManifest';
15
14
  import type { TDevCommandRunResponse } from '@common/dev/commands';
16
15
  import type { TDevSessionErrorResponse, TDevSessionStartResponse } from '@common/dev/session';
16
+ import {
17
+ renderChangedVerificationPlan,
18
+ runChangedVerification,
19
+ type TChangedVerificationCheck,
20
+ type TChangedVerificationExecution,
21
+ type TChangedVerificationSkippedCheck,
22
+ } from '../verification/changed';
17
23
 
18
24
  type TVerifySeverity = 'error' | 'warning';
19
25
  type TVerifyStepStatus = 'failed' | 'info' | 'passed';
@@ -23,7 +29,7 @@ type TVerifyFinding = {
23
29
  blocking: boolean;
24
30
  code: string;
25
31
  message: string;
26
- source: 'browser' | 'contracts' | 'doctor' | 'framework-change' | 'request' | 'command';
32
+ source: 'browser' | 'changed' | 'contracts' | 'doctor' | 'framework-change' | 'request' | 'command';
27
33
  filepath?: string;
28
34
  sourceLocation?: { line?: number; column?: number };
29
35
  relatedFilepaths?: string[];
@@ -51,12 +57,20 @@ type TVerifyResult = {
51
57
  action: string;
52
58
  target?: string;
53
59
  orientation?: TOrientResponse;
60
+ changedFiles?: string[];
61
+ configFilepath?: string;
62
+ dryRun?: boolean;
63
+ executions?: TChangedVerificationExecution[];
54
64
  introducedFindings: TVerifyFinding[];
55
65
  preExistingFindings: TVerifyFinding[];
66
+ selectedChecks?: TChangedVerificationCheck[];
67
+ skippedChecks?: TChangedVerificationSkippedCheck[];
56
68
  verificationSteps: TVerifyStep[];
57
69
  result: {
58
70
  ok: boolean;
59
71
  strictGlobal: boolean;
72
+ failedChecks?: number;
73
+ selectedChecks?: number;
60
74
  introducedBlockingFindings: number;
61
75
  preExistingBlockingFindings: number;
62
76
  blockingFindings: number;
@@ -267,6 +281,7 @@ const ensureServer = async ({
267
281
  };
268
282
 
269
283
  const resolveLocalManifest = async () => {
284
+ const { default: Compiler } = await import('../compiler');
270
285
  const compiler = new Compiler('dev');
271
286
  await compiler.refreshGeneratedTypings();
272
287
  return readProteumManifest(cli.paths.appRoot);
@@ -365,9 +380,16 @@ const classifyDiagnostics = ({
365
380
  const finalizeResult = ({
366
381
  action,
367
382
  apps,
383
+ changedFiles,
384
+ configFilepath,
385
+ dryRun,
386
+ executions,
368
387
  introducedFindings,
369
388
  orientation,
370
389
  preExistingFindings,
390
+ selectedChecks,
391
+ skippedChecks,
392
+ changedResult,
371
393
  strictGlobal,
372
394
  target,
373
395
  verificationSteps,
@@ -375,9 +397,16 @@ const finalizeResult = ({
375
397
  action: string;
376
398
  target?: string;
377
399
  orientation?: TOrientResponse;
400
+ changedFiles?: string[];
401
+ configFilepath?: string;
402
+ dryRun?: boolean;
403
+ executions?: TChangedVerificationExecution[];
378
404
  apps?: TVerifyAppResult[];
379
405
  introducedFindings: TVerifyFinding[];
380
406
  preExistingFindings: TVerifyFinding[];
407
+ selectedChecks?: TChangedVerificationCheck[];
408
+ skippedChecks?: TChangedVerificationSkippedCheck[];
409
+ changedResult?: { failedChecks: number; selectedChecks: number };
381
410
  verificationSteps: TVerifyStep[];
382
411
  strictGlobal: boolean;
383
412
  }): TVerifyResult => {
@@ -389,13 +418,20 @@ const finalizeResult = ({
389
418
  action,
390
419
  ...(target ? { target } : {}),
391
420
  ...(orientation ? { orientation } : {}),
421
+ ...(changedFiles ? { changedFiles } : {}),
422
+ ...(configFilepath ? { configFilepath } : {}),
423
+ ...(dryRun !== undefined ? { dryRun } : {}),
424
+ ...(executions ? { executions } : {}),
392
425
  ...(apps ? { apps } : {}),
393
426
  introducedFindings,
394
427
  preExistingFindings,
428
+ ...(selectedChecks ? { selectedChecks } : {}),
429
+ ...(skippedChecks ? { skippedChecks } : {}),
395
430
  verificationSteps,
396
431
  result: {
397
432
  ok,
398
433
  strictGlobal,
434
+ ...(changedResult ? { failedChecks: changedResult.failedChecks, selectedChecks: changedResult.selectedChecks } : {}),
399
435
  introducedBlockingFindings,
400
436
  preExistingBlockingFindings,
401
437
  blockingFindings: introducedBlockingFindings + preExistingBlockingFindings,
@@ -437,6 +473,24 @@ const renderFrameworkApps = (apps: TVerifyAppResult[]) =>
437
473
  ])
438
474
  .join('\n');
439
475
 
476
+ const renderChangedChecks = (result: TVerifyResult) => {
477
+ if (!result.changedFiles || !result.selectedChecks || !result.skippedChecks) return '';
478
+
479
+ return [
480
+ 'Changed Verification',
481
+ `- config=${result.configFilepath || 'none'}`,
482
+ `- dryRun=${result.dryRun === true}`,
483
+ `- changedFiles=${result.changedFiles.length}`,
484
+ `- selectedChecks=${result.selectedChecks.length}`,
485
+ `- skippedChecks=${result.skippedChecks.length}`,
486
+ ...result.selectedChecks.map(
487
+ (check) =>
488
+ `- [${check.scope}] ${check.id} cwd=${check.cwd} command=${check.command} reason=${check.reasons.join('; ')}`,
489
+ ),
490
+ ...result.skippedChecks.map((check) => `- [skipped] ${check.id} reason=${check.reason}`),
491
+ ].join('\n');
492
+ };
493
+
440
494
  const renderHuman = (result: TVerifyResult) =>
441
495
  [
442
496
  `Proteum verify ${result.action}${result.target ? ` ${result.target}` : ''}`,
@@ -448,6 +502,7 @@ const renderHuman = (result: TVerifyResult) =>
448
502
  ]
449
503
  : []),
450
504
  ...(result.apps ? [renderFrameworkApps(result.apps)] : []),
505
+ ...(result.changedFiles ? ['', renderChangedChecks(result)] : []),
451
506
  '',
452
507
  renderSteps(result.verificationSteps),
453
508
  '',
@@ -1075,6 +1130,62 @@ const collectAppResult = async ({
1075
1130
  };
1076
1131
  };
1077
1132
 
1133
+ const runChangedVerify = async () => {
1134
+ const base = typeof cli.args.base === 'string' && cli.args.base.trim() ? cli.args.base.trim() : undefined;
1135
+ const changed = await runChangedVerification({
1136
+ base,
1137
+ cwd: String(cli.args.workdir || process.cwd()),
1138
+ dryRun: cli.args.dryRun === true,
1139
+ onPlan: (plan) => {
1140
+ if (cli.args.json !== true) console.log(renderChangedVerificationPlan(plan));
1141
+ },
1142
+ staged: cli.args.staged === true,
1143
+ });
1144
+ const introducedFindings: TVerifyFinding[] = changed.executions
1145
+ .filter((execution) => execution.status === 'failed')
1146
+ .map((execution) => ({
1147
+ severity: 'error',
1148
+ blocking: true,
1149
+ code: 'changed/check-failed',
1150
+ message: `Verification check "${execution.checkId}" failed with exit code ${execution.exitCode ?? 'unknown'}.`,
1151
+ source: 'changed',
1152
+ details: [`command=${execution.command}`, `cwd=${execution.cwd}`, `durationMs=${execution.durationMs}`],
1153
+ }));
1154
+
1155
+ return finalizeResult({
1156
+ action: 'changed',
1157
+ changedFiles: changed.changedFiles,
1158
+ configFilepath: changed.configFilepath,
1159
+ dryRun: changed.dryRun,
1160
+ executions: changed.executions,
1161
+ introducedFindings,
1162
+ preExistingFindings: [],
1163
+ selectedChecks: changed.selectedChecks,
1164
+ skippedChecks: changed.skippedChecks,
1165
+ changedResult: changed.result,
1166
+ strictGlobal: false,
1167
+ verificationSteps: [
1168
+ {
1169
+ label: 'Discover Changed Files',
1170
+ status: 'passed',
1171
+ details: [`files=${changed.changedFiles.length}`, `gitRoot=${changed.gitRoot}`],
1172
+ },
1173
+ {
1174
+ label: 'Plan Targeted Checks',
1175
+ status: 'passed',
1176
+ details: [`selected=${changed.selectedChecks.length}`, `skipped=${changed.skippedChecks.length}`],
1177
+ },
1178
+ {
1179
+ label: changed.dryRun ? 'Skip Execution' : 'Run Targeted Checks',
1180
+ status: changed.result.ok ? 'passed' : 'failed',
1181
+ details: changed.dryRun
1182
+ ? ['dryRun=true']
1183
+ : [`executions=${changed.executions.length}`, `failed=${changed.result.failedChecks}`],
1184
+ },
1185
+ ],
1186
+ });
1187
+ };
1188
+
1078
1189
  const runFrameworkChangeVerify = async () => {
1079
1190
  const websiteRoute = typeof cli.args.route === 'string' && cli.args.route ? cli.args.route : '/';
1080
1191
  const apps = {
@@ -1208,7 +1319,9 @@ export const run = async () => {
1208
1319
  const target = typeof cli.args.target === 'string' ? cli.args.target.trim() : '';
1209
1320
  let result: TVerifyResult;
1210
1321
 
1211
- if (action === 'framework-change') {
1322
+ if (action === 'changed') {
1323
+ result = await runChangedVerify();
1324
+ } else if (action === 'framework-change') {
1212
1325
  result = await runFrameworkChangeVerify();
1213
1326
  } else if (action === 'owner') {
1214
1327
  if (!target) throw new UsageError('`proteum verify owner` requires a query.');
@@ -1220,7 +1333,7 @@ export const run = async () => {
1220
1333
  if (!target) throw new UsageError('`proteum verify browser` requires a path or absolute URL.');
1221
1334
  result = await runBrowserVerify(target);
1222
1335
  } else {
1223
- throw new UsageError(`Unsupported verify action "${action}". Expected framework-change, owner, request, or browser.`);
1336
+ throw new UsageError(`Unsupported verify action "${action}". Expected changed, framework-change, owner, request, or browser.`);
1224
1337
  }
1225
1338
 
1226
1339
  if (cli.args.json === true) {
@@ -0,0 +1,66 @@
1
+ export const createTypedControllerHelperContent = (appIdentifier: string) => `/*----------------------------------
2
+ - GENERATED FILE
3
+ ----------------------------------*/
4
+
5
+ // This file is generated by Proteum from server/index.ts.
6
+ // Do not edit it manually.
7
+
8
+ import {
9
+ defineAction as defineBaseAction,
10
+ defineController,
11
+ schema,
12
+ } from '@server/app/controller';
13
+ import type {
14
+ TControllerActionContext,
15
+ TControllerActionDefinition,
16
+ TControllerActionInput,
17
+ TControllerActionResult,
18
+ TControllerDefinition,
19
+ TValidationSchema,
20
+ TValidationShape,
21
+ z,
22
+ } from '@server/app/controller';
23
+
24
+ export { defineController, schema };
25
+ export type {
26
+ TControllerActionDefinition,
27
+ TControllerActionInput,
28
+ TControllerActionResult,
29
+ TControllerDefinition,
30
+ TValidationSchema,
31
+ TValidationShape,
32
+ z,
33
+ };
34
+
35
+ export type TApplication = import('@/server/index').${appIdentifier};
36
+ export type TControllerRequestServices = import('@/server/index').TControllerRequestServices;
37
+ export type TTypedControllerActionContext<TInput = undefined> = TControllerActionContext<
38
+ TInput,
39
+ TApplication,
40
+ TControllerRequestServices
41
+ >;
42
+
43
+ export function defineAction<TSchema extends TValidationSchema, TResult>(
44
+ definition: {
45
+ input: TSchema;
46
+ handler: (context: TTypedControllerActionContext<z.output<TSchema>>) => TResult;
47
+ },
48
+ ): TControllerActionDefinition<z.output<TSchema>, TResult, TApplication, TControllerRequestServices>;
49
+ export function defineAction<TShape extends TValidationShape, TResult>(
50
+ definition: {
51
+ input: TShape;
52
+ handler: (context: TTypedControllerActionContext<z.output<z.ZodObject<TShape>>>) => TResult;
53
+ },
54
+ ): TControllerActionDefinition<z.output<z.ZodObject<TShape>>, TResult, TApplication, TControllerRequestServices>;
55
+ export function defineAction<TResult>(
56
+ definition: {
57
+ handler: (context: TTypedControllerActionContext<undefined>) => TResult;
58
+ },
59
+ ): TControllerActionDefinition<undefined, TResult, TApplication, TControllerRequestServices>;
60
+ export function defineAction(definition: {
61
+ input?: TValidationSchema | TValidationShape;
62
+ handler: (context: TTypedControllerActionContext<any>) => unknown;
63
+ }) {
64
+ return defineBaseAction(definition as any);
65
+ }
66
+ `;
@@ -5,6 +5,7 @@ import cli from '../..';
5
5
  import { indexControllers, printControllerTree, type TControllerFileMeta } from '../common/controllers';
6
6
  import { TProteumManifestController } from '../common/proteumManifest';
7
7
  import writeIfChanged from '../writeIfChanged';
8
+ import { createTypedControllerHelperContent } from './controllerHelper';
8
9
  import { resolveConnectedProjectContracts, writeConnectedProjectContract } from './connectedProjects';
9
10
  import { normalizeAbsolutePath } from './shared';
10
11
 
@@ -319,6 +320,8 @@ export default controllers;
319
320
  `,
320
321
  );
321
322
 
323
+ writeIfChanged(path.join(app.paths.server.generated, 'controller.ts'), createTypedControllerHelperContent(app.identity.identifier));
324
+
322
325
  return {
323
326
  connectedProjects: connectedProjectContracts,
324
327
  controllers: [...manifestControllers, ...connectedManifestControllers],