proteum 2.2.7 → 2.2.9

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 (43) hide show
  1. package/AGENTS.md +2 -1
  2. package/README.md +1 -1
  3. package/agents/project/AGENTS.md +4 -5
  4. package/agents/project/CODING_STYLE.md +5 -1
  5. package/agents/project/client/AGENTS.md +2 -0
  6. package/agents/project/diagnostics.md +2 -3
  7. package/agents/project/root/AGENTS.md +4 -5
  8. package/agents/project/tests/AGENTS.md +0 -1
  9. package/cli/commands/check.ts +21 -3
  10. package/cli/commands/configure.ts +1 -0
  11. package/cli/compiler/artifacts/controllers.ts +30 -4
  12. package/cli/compiler/artifacts/services.ts +67 -29
  13. package/cli/presentation/commands.ts +2 -2
  14. package/cli/scaffold/templates.ts +2 -3
  15. package/cli/utils/agents.ts +114 -4
  16. package/client/dev/profiler/ApexChart.tsx +4 -3
  17. package/common/dev/inspection.ts +16 -3
  18. package/common/dev/serverHotReload.ts +26 -25
  19. package/eslint.js +220 -0
  20. package/package.json +1 -1
  21. package/server/app/commands.ts +11 -16
  22. package/server/app/commandsManager.ts +5 -1
  23. package/server/app/controller/index.ts +68 -16
  24. package/server/app/devCommands.ts +3 -3
  25. package/server/app/devDiagnostics.ts +2 -2
  26. package/server/app/index.ts +19 -8
  27. package/server/app/service/container.ts +22 -19
  28. package/server/app/service/index.ts +33 -13
  29. package/server/app.tsconfig.json +0 -1
  30. package/server/services/auth/index.ts +12 -6
  31. package/server/services/auth/router/index.ts +12 -14
  32. package/server/services/auth/router/request.ts +34 -13
  33. package/server/services/disks/driver.ts +1 -1
  34. package/server/services/disks/index.ts +11 -8
  35. package/server/services/email/index.ts +1 -1
  36. package/server/services/prisma/Facet.ts +6 -5
  37. package/server/services/router/index.ts +8 -7
  38. package/server/services/router/request/validation/zod.ts +2 -0
  39. package/server/services/router/response/index.ts +9 -9
  40. package/server/services/router/service.ts +12 -8
  41. package/tests/agents-utils.test.cjs +55 -0
  42. package/tests/eslint-rules.test.cjs +110 -0
  43. package/types/global/vendors.d.ts +70 -0
package/AGENTS.md CHANGED
@@ -31,11 +31,12 @@ After those optimization concerns, preserve explicit, typed, machine-readable co
31
31
  cd <worktree path>
32
32
  npx prisma migrate dev --config ./prisma.config.ts --name <migration name>
33
33
  ```
34
+ - After initializing any new framework worktree, immediately boot the required reference app dev servers with the elevated-permissions workflow below. Keep those servers running across subsequent changes and validation until the user explicitly asks to stop them, or until a retry, port change, stale-session cleanup, or conflicting live session requires a controlled stop.
34
35
  - After implementing a framework feature or change, do not stop at code edits. Boot both reference apps, exercise browser-visible flows with the browser MCP or use the smallest real Proteum surface, run the relevant `proteum` diagnostics or perf commands, and confirm there is no meaningful regression in runtime behavior, performance, load size, SEO output, or coding-style expectations before finishing.
35
36
  - When starting a long-lived reference app dev server for framework work, always request elevated permissions and run `npx proteum dev` outside the sandbox. Use an explicit thread-scoped session file such as `var/run/proteum/dev/framework-<app>-<task>.json`, inspect tracked sessions plus current listeners first, for example with `npx proteum dev list --json` and `lsof -nP -iTCP -sTCP:LISTEN`, then choose a port that is not currently used before starting `npx proteum dev --session-file <path> --port <port>`. After the server is ready, print the live server URL as a clickable Markdown link such as `[http://localhost:3100](http://localhost:3100)`.
36
37
  - Do not use `--replace-existing` unless you are 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.
37
38
  - When a reference app uses local `file:` connected projects for the affected flow, boot every connected producer app as well, each on its own free port and thread-scoped session file, and run every one of those `proteum dev` processes with elevated permissions outside the sandbox before starting or validating the consumer app.
38
- - Before retrying a boot on the same app, changing ports, or finishing the task, stop every framework-started dev session with `npx proteum dev stop --session-file <path>` or `npx proteum dev stop --all --stale`.
39
+ - Before retrying a boot on the same app, changing ports, handling a conflicting live session, or when the user explicitly asks to stop servers, stop the relevant framework-started dev session with `npx proteum dev stop --session-file <path>` or clean stale sessions with `npx proteum dev stop --all --stale`. Do not stop healthy framework-started dev sessions merely because the current task is finished.
39
40
  - If the task changed the dev workflow itself, verify the final cleanup path with `npx proteum dev list --json` before finishing.
40
41
  - When you have finished your work, summarize in one top-level short (up to 100 characters) sentence ALL the changes you made since the beginning of the WHOLE conversation. Strictly use the Conventional Commits specification:
41
42
  ```
package/README.md CHANGED
@@ -404,7 +404,7 @@ proteum create controller Founder/projects --method list
404
404
  proteum create service Conversion/Plans
405
405
  ```
406
406
 
407
- `proteum configure agents` embeds the full Proteum project instruction corpus into the managed `# Proteum Instructions` section of each tracked instruction file. It preserves content outside that section and asks before replacing directories or foreign symlinks. If you decline, that path is left untouched.
407
+ `proteum configure agents` embeds the full Proteum project instruction corpus into the managed `# Proteum Instructions` section of each tracked instruction file. Standalone mode writes root documents into the app root; monorepo mode writes shared root documents such as `AGENTS.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 foreign symlinks. If you decline, that path is left untouched.
408
408
 
409
409
  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.
410
410
 
@@ -63,8 +63,7 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
63
63
  ### Before Finishing
64
64
 
65
65
  - 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
- - Do not default to project-wide typecheck or `npx proteum check` after every change. 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
- - After implementing UI-visible changes, once the required tests or verification pass, use the browser MCP to open the changed routes or states, capture screenshots focused on the changed areas, inspect them for obvious visual defects, and include or reference those screenshots in the final response as proof of the completed UI change.
66
+ - 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.
68
67
  - 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
68
  - 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
69
  `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.`
@@ -174,13 +173,12 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
174
173
 
175
174
  Verify at the correct layer:
176
175
 
177
- - Default: use the cheapest trustworthy verification for the changed surface first, then escalate only if the changed surface justifies it.
176
+ - Default: use the cheapest trustworthy verification for the changed surface first, then run the full project `npx proteum check` before finishing.
178
177
  - Route additions: boot the app and hit the real URL.
179
178
  - Controller changes: exercise the generated client call or generated `/api/...` endpoint.
180
179
  - SSR changes: use the browser MCP to load the real page and inspect rendered HTML plus browser console.
181
180
  - 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.
183
- - UI-visible changes: after the required tests or verification, use the browser MCP to screenshot the changed areas and confirm the screenshots match the intended result.
181
+ - 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`.
184
182
  - Generated, connected, or ownership-ambiguous changes: start with `npx proteum orient <query>` and prefer `npx proteum verify owner <query>` before broad global checks.
185
183
  - 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.
186
184
  - 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.
@@ -203,6 +201,7 @@ Verify at the correct layer:
203
201
  - Do not create nested `catalogs/` folders under pages, components, services, tests, or other feature folders.
204
202
  - Keep strong TypeScript typings across the project.
205
203
  - Do not introduce `any` or `unknown`, including through casts, helper aliases, or fallback generic defaults.
204
+ - Do not use `Reflect.get`, bracket access, broad `in` checks, or local loose reader helpers to bypass missing typings for app-owned data; fix the type contract or normalize once with a typed adapter at the boundary.
206
205
  - Fix typing issues only on code you wrote.
207
206
  - Never cast with `as any` or `as unknown`; fix the contract or add an explicit typed adapter.
208
207
 
@@ -8,7 +8,11 @@ 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 the smallest relevant project lint or check command when available; coding-style regressions are defects, not optional cleanup.
11
+ - Before finishing a feature or change, review touched files against this document and run the full project `npx proteum check`; coding-style regressions are defects, not optional cleanup.
12
+
13
+ ## Type safety
14
+
15
+ - Do not use `Reflect.get`, bracket access, broad `in` checks, or local loose reader helpers to bypass missing typings for app-owned data; fix the type contract or normalize once with a typed adapter at the boundary.
12
16
 
13
17
  ## Formatting
14
18
 
@@ -24,6 +24,8 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
24
24
  - Prefer generated app surfaces over direct `.proteum` implementation imports.
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
+ - 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 `Router.app.handleError(error)`, logging/reporting it with the original error, or surfacing original detail such as `error.message` in user-visible feedback.
27
29
 
28
30
  ## Design
29
31
 
@@ -50,17 +50,16 @@ This file is the canonical source of truth for diagnostics, temporary instrument
50
50
  ## Verification And Testing
51
51
 
52
52
  - Use the cheapest trustworthy verification that matches the failing layer.
53
- - After implementing a change, verify only at the smallest trustworthy layer required by the changed surface. Do not default to a running app, browser MCP, project-wide typecheck, `npx proteum check`, or Playwright when a narrower static or request-level verification is enough.
53
+ - 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.
54
54
  - 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.
55
55
  - For request/runtime issues, verify through the real page, route, generated controller call, or command on a running app.
56
56
  - Start the smallest trustworthy runtime surface first: `npx proteum orient <query>`, then the relevant real URL, generated controller call, command, or `npx proteum diagnose <path> --port <port>`. Use browser MCP validation only when request-level verification is insufficient or the change is browser-visible.
57
57
  - When automated browser assertions or suite coverage are required, use `npx proteum e2e --port <port>` for targeted or full Playwright suites. Do not use direct Playwright for browser validation outside the E2E wrapper, and do not launch raw browser automation against a shared persistent profile.
58
58
  - Focused verification should treat unrelated global diagnostics as visible but non-blocking by default. Use `--strict-global` only when the task explicitly requires broad clean-room validation.
59
59
  - 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.
60
- - For UI-visible fixes, after the required tests or verification pass, use the browser MCP to capture focused screenshots of the changed areas and inspect them before finalizing.
61
60
  - 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.
62
61
  - Treat server startup failures, runtime errors, browser console errors or warnings, and Playwright failures as blocking unless they are clearly unrelated to the change.
63
- - When the touched surface can affect coding-style enforcement, run the smallest relevant static check. Do not default to `npx proteum check`; prefer a narrower lint or type check only when the changed surface or an observed issue calls for it.
62
+ - 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.
64
63
  - 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`.
65
64
  - Add `data-testid` when stable selectors are missing instead of relying on brittle text or DOM-shape selectors.
66
65
  - If an isolated test misses prerequisite state, run the smallest broader scope that reproduces the real setup.
@@ -53,8 +53,7 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
53
53
  ### Before Finishing
54
54
 
55
55
  - 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.
56
- - Do not default to project-wide typecheck or `npx proteum check` after every change. 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.
57
- - After implementing UI-visible changes, once the required tests or verification pass, use the browser MCP to open the changed routes or states, capture screenshots focused on the changed areas, inspect them for obvious visual defects, and include or reference those screenshots in the final response as proof of the completed UI change.
56
+ - 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.
58
57
  - 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>`.
59
58
  - 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:
60
59
  `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.`
@@ -164,13 +163,12 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
164
163
 
165
164
  Verify at the correct layer:
166
165
 
167
- - Default: use the cheapest trustworthy verification for the changed surface first, then escalate only if the changed surface justifies it.
166
+ - Default: use the cheapest trustworthy verification for the changed surface first, then run the full project `npx proteum check` before finishing.
168
167
  - Route additions: boot the app and hit the real URL.
169
168
  - Controller changes: exercise the generated client call or generated `/api/...` endpoint.
170
169
  - SSR changes: use the browser MCP to load the real page and inspect rendered HTML plus browser console.
171
170
  - Router or plugin changes: verify request context, auth, redirects, metrics, and validation on a running app.
172
- - 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.
173
- - UI-visible changes: after the required tests or verification, use the browser MCP to screenshot the changed areas and confirm the screenshots match the intended result.
171
+ - 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`.
174
172
  - Generated, connected, or ownership-ambiguous changes: start with `npx proteum orient <query>` and prefer `npx proteum verify owner <query>` before broad global checks.
175
173
  - 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.
176
174
  - 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.
@@ -193,6 +191,7 @@ Verify at the correct layer:
193
191
  - Do not create nested `catalogs/` folders under pages, components, services, tests, or other feature folders.
194
192
  - Keep strong TypeScript typings across the project.
195
193
  - Do not introduce `any` or `unknown`, including through casts, helper aliases, or fallback generic defaults.
194
+ - Do not use `Reflect.get`, bracket access, broad `in` checks, or local loose reader helpers to bypass missing typings for app-owned data; fix the type contract or normalize once with a typed adapter at the boundary.
196
195
  - Fix typing issues only on code you wrote.
197
196
  - Never cast with `as any` or `as unknown`; fix the contract or add an explicit typed adapter.
198
197
 
@@ -11,7 +11,6 @@ Diagnostics source of truth: root-level `diagnostics.md`.
11
11
  - Test the current controller/page runtime model, not legacy `@Route` or `api.fetch(...)` behavior.
12
12
  - Verify routing, controllers, SSR, and router plugins against a running app when behavior depends on real request handling.
13
13
  - 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.
14
- - For UI-visible feature changes, after the required Playwright run passes, use the browser MCP to capture focused screenshots of the changed areas and inspect them for visual correctness before finishing.
15
14
  - Exercise real URLs, generated controller calls, or real browser flows instead of re-deriving framework internals in tests.
16
15
  - Organize end-to-end tests following the Crosspath platform layout under `tests/e2e/**`.
17
16
  - Put runnable scenario entrypoints in `tests/e2e/features/**`, `tests/e2e/specs/<domain>/**`, or `tests/e2e/journeys/**` depending on scope.
@@ -19,19 +19,37 @@ export const run = async (): Promise<void> => {
19
19
 
20
20
  console.info(
21
21
  [
22
- await renderTitle('PROTEUM CHECK', 'Refreshing contracts, running TypeScript, then running ESLint.'),
22
+ await renderTitle('PROTEUM CHECK', 'Refreshing contracts, then running TypeScript and ESLint.'),
23
23
  renderRows([{ label: 'app', value: cli.paths.appRoot === process.cwd() ? '.' : cli.paths.appRoot }]),
24
24
  ].join('\n\n'),
25
25
  );
26
+
26
27
  if (hasAppConfig()) {
27
28
  console.info(await renderStep('[1/3]', 'Refreshing generated typings.'));
28
29
  await refreshGeneratedTypings();
29
30
  } else {
30
31
  console.info(await renderStep('[1/3]', 'Skipping generated typings: no Proteum app config found.'));
31
32
  }
33
+
34
+ const failures: Error[] = [];
35
+
32
36
  console.info(await renderStep('[2/3]', 'Running TypeScript typechecking.'));
33
- await runAppTypecheck();
37
+ try {
38
+ await runAppTypecheck();
39
+ } catch (error) {
40
+ failures.push(error instanceof Error ? error : new Error(String(error)));
41
+ }
42
+
34
43
  console.info(await renderStep('[3/3]', 'Running ESLint.'));
35
- await runAppLint();
44
+ try {
45
+ await runAppLint();
46
+ } catch (error) {
47
+ failures.push(error instanceof Error ? error : new Error(String(error)));
48
+ }
49
+
50
+ if (failures.length > 0) {
51
+ throw new AggregateError(failures, 'Proteum check failed. See TypeScript and ESLint output above.');
52
+ }
53
+
36
54
  console.info(await renderSuccess('All checks passed.'));
37
55
  };
@@ -119,6 +119,7 @@ const renderConfigureResultSections = (result: TConfigureProjectAgentInstruction
119
119
  if (result.updated.length > 0) sections.push(['Updated:', ...result.updated.map((entry) => `- ${entry}`)].join('\n'));
120
120
  if (result.overwritten.length > 0)
121
121
  sections.push(['Overwritten:', ...result.overwritten.map((entry) => `- ${entry}`)].join('\n'));
122
+ if (result.removed.length > 0) sections.push(['Removed:', ...result.removed.map((entry) => `- ${entry}`)].join('\n'));
122
123
  if (result.updatedGitignores.length > 0)
123
124
  sections.push(['Updated .gitignore:', ...result.updatedGitignores.map((entry) => `- ${entry}`)].join('\n'));
124
125
  if (result.blocked.length > 0)
@@ -120,12 +120,14 @@ export const generateControllerArtifacts = async () => {
120
120
  const connectedManifestControllers: TProteumManifestController[] = [];
121
121
 
122
122
  connectedProjectContracts.forEach(({ namespace, cachedContractFilepath, contract, sourceKind, sourceValue, typeImportModuleSpecifier, typingMode }) => {
123
+ let connectedTypeName: string | null = null;
124
+
123
125
  if (typingMode === 'local-typed' && typeImportModuleSpecifier) {
124
- const typeName = `ConnectedControllers_${namespace.replace(/[^A-Za-z0-9_$]+/g, '_')}`;
126
+ connectedTypeName = `ConnectedControllers_${namespace.replace(/[^A-Za-z0-9_$]+/g, '_')}`;
125
127
  connectedControllerTypeImports.push(
126
- `import type { TConnectedControllers as ${typeName} } from ${JSON.stringify(typeImportModuleSpecifier)};`,
128
+ `import type { TConnectedControllers as ${connectedTypeName} } from ${JSON.stringify(typeImportModuleSpecifier)};`,
127
129
  );
128
- typeTree[namespace] = JSON.stringify({ rawType: typeName });
130
+ typeTree[namespace] = JSON.stringify({ rawType: connectedTypeName });
129
131
  } else {
130
132
  typeTree[namespace] = JSON.stringify({ runtimeOnly: true });
131
133
  }
@@ -164,7 +166,9 @@ export const generateControllerArtifacts = async () => {
164
166
  hasInput: controller.hasInput,
165
167
  httpPath: controller.httpPath,
166
168
  methodName: controller.methodName,
167
- resultType: 'unknown',
169
+ resultType: connectedTypeName
170
+ ? `TConnectedControllerResult<${connectedTypeName}, ${JSON.stringify(controller.clientAccessor)}>`
171
+ : 'unknown',
168
172
  }),
169
173
  );
170
174
  });
@@ -228,6 +232,28 @@ type TControllerResult<TController, TMethod extends keyof TController> =
228
232
 
229
233
  type TControllerFetcher<TController, TMethod extends keyof TController> = TFetcher<TControllerResult<TController, TMethod>>;
230
234
 
235
+ type TConnectedFallbackValue =
236
+ | string
237
+ | number
238
+ | boolean
239
+ | null
240
+ | TConnectedFallbackValue[]
241
+ | { [key: string]: TConnectedFallbackValue | undefined };
242
+
243
+ type TConnectedControllerLeaf<TControllerTree, TAccessor extends string> =
244
+ TAccessor extends \`${'${infer THead}.${infer TTail}'}\`
245
+ ? THead extends keyof TControllerTree
246
+ ? TConnectedControllerLeaf<TControllerTree[THead], TTail>
247
+ : undefined
248
+ : TAccessor extends keyof TControllerTree
249
+ ? TControllerTree[TAccessor]
250
+ : undefined;
251
+
252
+ type TConnectedControllerResult<TControllerTree, TAccessor extends string> =
253
+ TConnectedControllerLeaf<TControllerTree, TAccessor> extends (...args: infer TArgs) => TFetcher<infer TResult>
254
+ ? Awaited<TResult>
255
+ : TConnectedFallbackValue;
256
+
231
257
  export type TControllers = ${printControllerTree(typeTree, typeLeaf)};
232
258
 
233
259
  export const createControllers = (
@@ -711,6 +711,68 @@ ${classMembers.join('\n')}
711
711
  };
712
712
  };
713
713
 
714
+ const isNamespaceImportDeclaration = (statement: ts.ImportDeclaration) =>
715
+ statement.importClause?.namedBindings !== undefined && ts.isNamespaceImport(statement.importClause.namedBindings);
716
+
717
+ const isClientServerStubSupportStatement = (statement: ts.Statement, appClassIdentifier: string) => {
718
+ if (ts.isTypeAliasDeclaration(statement)) {
719
+ return !new Set([
720
+ `${appClassIdentifier}Disks`,
721
+ `${appClassIdentifier}Router`,
722
+ `${appClassIdentifier}RouterPlugins`,
723
+ ]).has(statement.name.text);
724
+ }
725
+
726
+ return ts.isInterfaceDeclaration(statement) || ts.isModuleDeclaration(statement);
727
+ };
728
+
729
+ const createClientServerIndexDeclaration = (rootServices: TParsedService[]) => {
730
+ const sourceFile = createSourceFile(getAppServerEntryFilepath());
731
+ const appClass = getDefaultExportClassDeclaration(sourceFile);
732
+ const serviceTypesByRegisteredName = new Map(
733
+ rootServices.map((service) => [service.registeredName, `import("${service.importPath}").default`] as const),
734
+ );
735
+
736
+ const imports = sourceFile.statements
737
+ .filter((statement): statement is ts.ImportDeclaration => ts.isImportDeclaration(statement))
738
+ .filter((statement) => !isNamespaceImportDeclaration(statement))
739
+ .map((statement) => statement.getText(sourceFile));
740
+ const supportStatements = sourceFile.statements
741
+ .slice(0, sourceFile.statements.indexOf(appClass))
742
+ .filter((statement) => isClientServerStubSupportStatement(statement, app.identity.identifier))
743
+ .map((statement) => statement.getText(sourceFile));
744
+ const heritageClauses = appClass.heritageClauses?.map((clause) => clause.getText(sourceFile)).join(' ');
745
+ const properties = appClass.members
746
+ .filter((member): member is ts.PropertyDeclaration => ts.isPropertyDeclaration(member))
747
+ .filter((member) => !isPrivateOrProtectedInstanceMember(member))
748
+ .map((property) => {
749
+ const propertyName = getPropertyNameText(property.name);
750
+ if (!propertyName) return undefined;
751
+
752
+ const propertyType =
753
+ propertyName === 'Disks'
754
+ ? 'object'
755
+ : propertyName === 'Router'
756
+ ? `${app.identity.identifier}Router`
757
+ : property.type?.getText(sourceFile) || serviceTypesByRegisteredName.get(propertyName);
758
+ if (!propertyType) return undefined;
759
+
760
+ return ` public ${propertyName}: ${propertyType};`;
761
+ })
762
+ .filter((property): property is string => property !== undefined);
763
+
764
+ return `${imports.join('\n')}
765
+
766
+ ${supportStatements.join('\n\n')}
767
+
768
+ type ${app.identity.identifier}Router = Router<${app.identity.identifier}>;
769
+
770
+ export default class ${app.identity.identifier}${heritageClauses ? ` ${heritageClauses}` : ''} {
771
+ ${properties.join('\n')}
772
+ }
773
+ `;
774
+ };
775
+
714
776
  const resolveManifestService = (service: TParsedService, parent: string): TProteumManifestService => ({
715
777
  kind: 'service',
716
778
  registeredName: service.registeredName,
@@ -730,6 +792,11 @@ export const generateServiceArtifacts = () => {
730
792
  const routerPluginServices = routerPlugins.map((service) => resolveManifestService(service, 'Router.plugins'));
731
793
  const commandServiceStubs = createCommandServiceStubDeclarations(rootServices);
732
794
 
795
+ writeIfChanged(
796
+ path.join(app.paths.client.generated, 'server-index.d.ts'),
797
+ createClientServerIndexDeclaration(rootServices),
798
+ );
799
+
733
800
  writeIfChanged(
734
801
  path.join(app.paths.client.generated, 'services.d.ts'),
735
802
  `declare type ${appClassIdentifier} = import("@/server/index").default;
@@ -737,18 +804,9 @@ export const generateServiceArtifacts = () => {
737
804
  declare module "@app" {
738
805
 
739
806
  import { ${appClassIdentifier} as ${appClassIdentifier}Client } from "@/client";
740
- import ${appClassIdentifier}Server from "@/server/index";
741
807
 
742
808
  export const Router: ${appClassIdentifier}Client['Router'];
743
809
 
744
- ${rootServices
745
- .map((service) =>
746
- service.registeredName !== 'Router'
747
- ? `export const ${service.registeredName}: ${appClassIdentifier}Server["${service.registeredName}"];`
748
- : '',
749
- )
750
- .join('\n')}
751
-
752
810
  }
753
811
 
754
812
  declare module '@models/types' {
@@ -894,26 +952,6 @@ declare module "@app" {
894
952
  export = ServerServices
895
953
  }
896
954
 
897
- declare module '@server/app' {
898
-
899
- import { Application } from "@server/app";
900
- import { Environment } from "@server/app";
901
- import { ServicesContainer } from "@server/app/service/container";
902
-
903
- abstract class ApplicationWithServices extends Application<
904
- ServicesContainer<InstalledServices>
905
- > {}
906
-
907
- export interface Exported {
908
- Application: typeof ApplicationWithServices,
909
- Environment: Environment,
910
- }
911
-
912
- const foo: Exported;
913
-
914
- export = foo;
915
- }
916
-
917
955
  declare module '@common/errors' {
918
956
 
919
957
  export * from '@common/errors/index';
@@ -124,9 +124,9 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
124
124
  },
125
125
  ],
126
126
  notes: [
127
- '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 `AGENTS.md` file.',
127
+ '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.',
128
128
  'Standalone mode writes tracked instruction files into the current Proteum app root.',
129
- 'Monorepo mode writes the reusable root `AGENTS.md` into the chosen monorepo root and the app-root instruction files into the current Proteum app root.',
129
+ 'Monorepo mode writes reusable root documents such as `AGENTS.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.',
130
130
  'Every managed instruction file contains a `# Proteum Instructions` section with the full embedded Proteum project instruction corpus.',
131
131
  'Existing content outside `# Proteum Instructions` is preserved. Directories and foreign symlinks are replaced only after confirmation.',
132
132
  ],
@@ -191,6 +191,7 @@ export const createClientTsconfigTemplate = (paths: TTsconfigTemplatePaths) => `
191
191
  "@server/*": [${JSON.stringify(paths.frameworkServer)}],
192
192
 
193
193
  "@/client/context": ["./.proteum/client/context.ts"],
194
+ "@/server/index": ["./.proteum/client/server-index.d.ts"],
194
195
  "@generated/client/*": ["./.proteum/client/*"],
195
196
  "@generated/common/*": ["./.proteum/common/*"],
196
197
  "@generated/server/*": ["./.proteum/server/*"],
@@ -207,8 +208,7 @@ export const createClientTsconfigTemplate = (paths: TTsconfigTemplatePaths) => `
207
208
  ".",
208
209
  "../var/typings",
209
210
  ${JSON.stringify(paths.frameworkTypesGlobal)},
210
- "../.proteum/client/services.d.ts",
211
- "../server/index.ts"
211
+ "../.proteum/client/services.d.ts"
212
212
  ]
213
213
  }
214
214
  `;
@@ -228,7 +228,6 @@ export const createServerTsconfigTemplate = (paths: TTsconfigTemplatePaths) => `
228
228
  "@common/*": [${JSON.stringify(paths.frameworkCommon)}],
229
229
  "@server/*": [${JSON.stringify(paths.frameworkServer)}],
230
230
 
231
- "@/client/context": ["./.proteum/client/context.ts"],
232
231
  "@generated/client/*": ["./.proteum/client/*"],
233
232
  "@generated/common/*": ["./.proteum/common/*"],
234
233
  "@generated/server/*": ["./.proteum/server/*"],
@@ -29,6 +29,7 @@ type TEnsureInstructionFilesResult = {
29
29
  blocked: string[];
30
30
  created: string[];
31
31
  overwritten: string[];
32
+ removed: string[];
32
33
  skipped: string[];
33
34
  updated: string[];
34
35
  };
@@ -40,6 +41,7 @@ export type TConfigureProjectAgentInstructionsResult = {
40
41
  monorepoRoot?: string;
41
42
  mode: 'monorepo' | 'standalone';
42
43
  overwritten: string[];
44
+ removed: string[];
43
45
  skipped: string[];
44
46
  updated: string[];
45
47
  updatedGitignores: string[];
@@ -57,10 +59,13 @@ const managedInstructionSectionStart = '<!-- proteum-instructions:start -->';
57
59
  const managedInstructionSectionEnd = '<!-- proteum-instructions:end -->';
58
60
  const managedInstructionSectionIntro = 'This section is managed by `proteum configure agents`.';
59
61
 
60
- const sharedAppAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
62
+ const sharedRootDocumentInstructionDefinitions: TAgentInstructionDefinition[] = [
61
63
  { projectPath: 'CODING_STYLE.md' },
62
64
  { projectPath: 'diagnostics.md' },
63
65
  { projectPath: 'optimizations.md' },
66
+ ];
67
+
68
+ const sharedAreaAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
64
69
  { projectPath: path.join('client', 'AGENTS.md') },
65
70
  { projectPath: path.join('client', 'pages', 'AGENTS.md') },
66
71
  { projectPath: path.join('server', 'services', 'AGENTS.md') },
@@ -70,16 +75,18 @@ const sharedAppAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
70
75
 
71
76
  const standaloneAppAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
72
77
  { projectPath: 'AGENTS.md' },
73
- ...sharedAppAgentInstructionDefinitions,
78
+ ...sharedRootDocumentInstructionDefinitions,
79
+ ...sharedAreaAgentInstructionDefinitions,
74
80
  ];
75
81
 
76
82
  const monorepoAppAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
77
83
  { projectPath: 'AGENTS.md' },
78
- ...sharedAppAgentInstructionDefinitions,
84
+ ...sharedAreaAgentInstructionDefinitions,
79
85
  ];
80
86
 
81
87
  const monorepoRootAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
82
88
  { projectPath: 'AGENTS.md' },
89
+ ...sharedRootDocumentInstructionDefinitions,
83
90
  ];
84
91
 
85
92
  const legacyProjectInstructionGitignoreBlockStart = '# Proteum-managed instruction symlinks';
@@ -111,6 +118,7 @@ export function configureProjectAgentInstructions({
111
118
  created: [],
112
119
  mode,
113
120
  overwritten: [],
121
+ removed: [],
114
122
  skipped: [],
115
123
  updated: [],
116
124
  updatedGitignores: [],
@@ -152,7 +160,29 @@ export function configureProjectAgentInstructions({
152
160
  );
153
161
  mergeInstructionResults(result, appFiles, normalizedAppRoot);
154
162
 
155
- if (!dryRun && removeInstructionGitignoreEntries({ rootDir: normalizedAppRoot, instructionDefinitions: appInstructions }))
163
+ if (mode === 'monorepo') {
164
+ const retiredAppRootFiles = removeManagedInstructionFiles(
165
+ normalizedAppRoot,
166
+ sharedRootDocumentInstructionDefinitions,
167
+ '[agents]',
168
+ path.join(coreRoot, 'agents', 'project'),
169
+ {
170
+ dryRun,
171
+ },
172
+ );
173
+ mergeInstructionResults(result, retiredAppRootFiles, normalizedAppRoot);
174
+ }
175
+
176
+ const appGitignoreCleanupInstructions =
177
+ mode === 'monorepo' ? [...appInstructions, ...sharedRootDocumentInstructionDefinitions] : appInstructions;
178
+
179
+ if (
180
+ !dryRun &&
181
+ removeInstructionGitignoreEntries({
182
+ rootDir: normalizedAppRoot,
183
+ instructionDefinitions: appGitignoreCleanupInstructions,
184
+ })
185
+ )
156
186
  result.updatedGitignores.push(path.join(normalizedAppRoot, '.gitignore'));
157
187
 
158
188
  return result;
@@ -253,6 +283,7 @@ function ensureInstructionFiles(
253
283
  blocked: [],
254
284
  created: [],
255
285
  overwritten: [],
286
+ removed: [],
256
287
  skipped: [],
257
288
  updated: [],
258
289
  };
@@ -320,6 +351,74 @@ function ensureInstructionFiles(
320
351
  return result;
321
352
  }
322
353
 
354
+ function removeManagedInstructionFiles(
355
+ rootDir: string,
356
+ instructionDefinitions: TAgentInstructionDefinition[],
357
+ logPrefix: string,
358
+ managedSourceRoot: string,
359
+ {
360
+ dryRun,
361
+ }: {
362
+ dryRun: boolean;
363
+ },
364
+ ): TEnsureInstructionFilesResult {
365
+ const result: TEnsureInstructionFilesResult = {
366
+ blocked: [],
367
+ created: [],
368
+ overwritten: [],
369
+ removed: [],
370
+ skipped: [],
371
+ updated: [],
372
+ };
373
+
374
+ for (const instructionDefinition of instructionDefinitions) {
375
+ const projectFilepath = path.join(rootDir, instructionDefinition.projectPath);
376
+ const projectParentDir = path.dirname(projectFilepath);
377
+ const relativeProjectPath = path.relative(rootDir, projectFilepath) || '.';
378
+
379
+ if (!fs.existsSync(projectParentDir)) continue;
380
+
381
+ const existingState = inspectExistingPath({
382
+ managedSourceRoot,
383
+ projectFilepath,
384
+ });
385
+
386
+ if (existingState.kind === 'missing') continue;
387
+
388
+ if (existingState.kind === 'managed-different') {
389
+ if (!dryRun) fs.removeSync(projectFilepath);
390
+ result.removed.push(relativeProjectPath);
391
+ logVerbose(`${logPrefix} Removed retired app-root ${relativeProjectPath}`);
392
+ continue;
393
+ }
394
+
395
+ if (existingState.kind === 'file') {
396
+ const retainedContent = removeManagedInstructionContent(existingState.content);
397
+
398
+ if (retainedContent === undefined) {
399
+ result.skipped.push(relativeProjectPath);
400
+ continue;
401
+ }
402
+
403
+ if (retainedContent.trim() === '') {
404
+ if (!dryRun) fs.removeSync(projectFilepath);
405
+ result.removed.push(relativeProjectPath);
406
+ logVerbose(`${logPrefix} Removed retired app-root ${relativeProjectPath}`);
407
+ continue;
408
+ }
409
+
410
+ if (!dryRun) fs.writeFileSync(projectFilepath, retainedContent);
411
+ result.updated.push(relativeProjectPath);
412
+ logVerbose(`${logPrefix} Removed retired managed section from ${relativeProjectPath}`);
413
+ continue;
414
+ }
415
+
416
+ result.skipped.push(relativeProjectPath);
417
+ }
418
+
419
+ return result;
420
+ }
421
+
323
422
  function inspectExistingPath({
324
423
  managedSourceRoot,
325
424
  projectFilepath,
@@ -377,6 +476,7 @@ function mergeInstructionResults(
377
476
  ) {
378
477
  result.created.push(...next.created.map((entry) => formatResultPath(rootDir, entry)));
379
478
  result.overwritten.push(...next.overwritten.map((entry) => formatResultPath(rootDir, entry)));
479
+ result.removed.push(...next.removed.map((entry) => formatResultPath(rootDir, entry)));
380
480
  result.updated.push(...next.updated.map((entry) => formatResultPath(rootDir, entry)));
381
481
  result.skipped.push(...next.skipped.map((entry) => formatResultPath(rootDir, entry)));
382
482
  result.blocked.push(...next.blocked.map((entry) => formatResultPath(rootDir, entry)));
@@ -472,6 +572,16 @@ function upsertManagedInstructionSection(content: string, managedSectionContent:
472
572
  return joinMarkdownSections([before, managedSectionContent, after]);
473
573
  }
474
574
 
575
+ function removeManagedInstructionContent(content: string) {
576
+ const managedRange = findManagedInstructionSectionRange(content) || findLegacyManagedInstructionStubRange(content);
577
+ if (!managedRange) return undefined;
578
+
579
+ const before = content.slice(0, managedRange.start);
580
+ const after = content.slice(managedRange.end);
581
+
582
+ return joinMarkdownSections([before, after]);
583
+ }
584
+
475
585
  function findManagedInstructionSectionRange(content: string) {
476
586
  const markerStartIndex = content.indexOf(managedInstructionSectionStart);
477
587
  if (markerStartIndex === -1) return undefined;