proteum 2.2.6 → 2.2.8

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 (42) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +4 -4
  3. package/agents/project/AGENTS.md +2 -1
  4. package/agents/project/app-root/AGENTS.md +1 -1
  5. package/agents/project/diagnostics.md +1 -1
  6. package/agents/project/root/AGENTS.md +2 -1
  7. package/cli/commands/configure.ts +14 -35
  8. package/cli/commands/dev.ts +105 -52
  9. package/cli/compiler/artifacts/controllers.ts +30 -4
  10. package/cli/compiler/artifacts/manifest.ts +1 -5
  11. package/cli/compiler/artifacts/services.ts +67 -29
  12. package/cli/presentation/commands.ts +9 -9
  13. package/cli/presentation/help.ts +1 -1
  14. package/cli/scaffold/index.ts +2 -5
  15. package/cli/scaffold/templates.ts +3 -10
  16. package/cli/utils/agents.ts +281 -199
  17. package/client/dev/profiler/ApexChart.tsx +4 -3
  18. package/common/dev/serverHotReload.ts +26 -25
  19. package/package.json +1 -1
  20. package/server/app/commands.ts +11 -16
  21. package/server/app/commandsManager.ts +5 -1
  22. package/server/app/controller/index.ts +68 -16
  23. package/server/app/devCommands.ts +3 -3
  24. package/server/app/devDiagnostics.ts +2 -2
  25. package/server/app/index.ts +19 -8
  26. package/server/app/service/container.ts +22 -19
  27. package/server/app/service/index.ts +33 -13
  28. package/server/app.tsconfig.json +0 -1
  29. package/server/services/auth/index.ts +12 -6
  30. package/server/services/auth/router/index.ts +12 -14
  31. package/server/services/auth/router/request.ts +34 -13
  32. package/server/services/disks/driver.ts +1 -1
  33. package/server/services/disks/index.ts +11 -8
  34. package/server/services/email/index.ts +1 -1
  35. package/server/services/prisma/Facet.ts +6 -5
  36. package/server/services/router/index.ts +8 -7
  37. package/server/services/router/request/validation/zod.ts +2 -0
  38. package/server/services/router/response/index.ts +9 -9
  39. package/server/services/router/service.ts +12 -8
  40. package/tests/agents-utils.test.cjs +207 -0
  41. package/tests/dev-transpile-watch.test.cjs +513 -0
  42. package/types/global/vendors.d.ts +70 -0
package/AGENTS.md CHANGED
@@ -58,7 +58,7 @@ npx prisma migrate dev --config ./prisma.config.ts --name <migration name>
58
58
  - Inspect how both apps currently use the touched feature, runtime, API, compiler behavior, or generated output before proposing or implementing changes.
59
59
  - Keep the developer-facing contract synchronized when framework work changes CLI commands, profiler capabilities, or the `proteum dev` banner. Update the live surfaces together in the same pass: CLI command/help definitions, profiler panels and dev-only endpoints, banner text/examples, and the most relevant agent docs that describe them, especially `AGENTS.md`, `agents/project/AGENTS.md`, `agents/project/root/AGENTS.md`, `agents/project/app-root/AGENTS.md`, `agents/project/diagnostics.md`, and any narrower `agents/project/**/AGENTS.md` file that mentions the changed workflow.
60
60
  - Keep the same-system trace contract explicit when request instrumentation changes: `TRACE_*` controls the retained dev trace store plus the trace/perf CLI, dev-only HTTP endpoints, and bottom profiler, while `ENABLE_PROFILER` enables the reduced request-local `request.profiling` snapshot and `request.finished` hook payload without retaining finished requests globally unless dev trace is also enabled.
61
- - Current CLI banner contract: only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the banner. Only `proteum dev` clears the interactive terminal before rendering, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys in its session UI, and reports connected app names plus successful connected `/ping` checks in the ready banner. When the app root is missing `AGENTS.md`, the bare interactive `proteum dev` start offers to launch `proteum configure agents` before the dev loop begins.
61
+ - Current CLI banner contract: only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the banner. Only `proteum dev` clears the interactive terminal before rendering, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys in its session UI, and reports connected app names plus successful connected `/ping` checks in the ready banner. Every `proteum dev` start ensures tracked instruction files contain the current managed `# Proteum Instructions` section before the dev loop begins.
62
62
  - Keep core changes aligned with the explicit controller/page architecture in `agents/project/root/AGENTS.md` and its standalone composition in `agents/project/AGENTS.md`.
63
63
  - Prefer removing framework magic when the same result can be expressed with explicit contracts, generated code, or typed context.
64
64
  - Apply the pruning rules from `agents/project/optimizations.md`, especially for webpack plugins, Babel plugins, aliases, helpers, runtime services, and npm packages that are not meaningfully used by both apps.
package/README.md CHANGED
@@ -347,7 +347,7 @@ Proteum ships with a compact CLI focused on the real app lifecycle:
347
347
  | `proteum e2e` | Run Playwright with Proteum-managed `E2E_*` values instead of shell-leading env assignments |
348
348
  | `proteum verify` | Validate framework-facing workflows across one or more running dev apps; `framework-change` is the built-in cross-reference-app check |
349
349
  | `proteum init` | Scaffold a new Proteum app with built-in deterministic templates |
350
- | `proteum configure agents` | Interactively configure Proteum-managed instruction symlinks and confirm overwrites for standalone or monorepo apps |
350
+ | `proteum configure agents` | Interactively configure tracked Proteum instruction files for standalone or monorepo apps |
351
351
  | `proteum create` | Scaffold a page, controller, command, route, or root service inside an app |
352
352
 
353
353
  Recommended daily workflow:
@@ -361,7 +361,7 @@ proteum build --prod --analyze
361
361
  proteum build --prod --analyze --analyze-serve --analyze-port auto
362
362
  ```
363
363
 
364
- Only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the banner. `proteum dev` is the only command that clears the interactive terminal before rendering its live session UI, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys, and prints connected app names plus successful connected `/ping` checks in the server-ready banner. When the app root is missing `AGENTS.md`, the interactive `proteum dev` start offers to launch `proteum configure agents` before the dev loop begins.
364
+ Only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the banner. `proteum dev` is the only command that clears the interactive terminal before rendering its live session UI, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys, and prints connected app names plus successful connected `/ping` checks in the server-ready banner. Every `proteum dev` start ensures tracked Proteum instruction files contain the current managed `# Proteum Instructions` section before the dev loop begins.
365
365
 
366
366
  Useful inspection commands:
367
367
 
@@ -404,9 +404,9 @@ proteum create controller Founder/projects --method list
404
404
  proteum create service Conversion/Plans
405
405
  ```
406
406
 
407
- `proteum configure agents` asks before replacing any existing non-managed instruction file or foreign symlink. 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. It preserves content outside that section and asks before replacing directories or foreign symlinks. If you decline, that path is left untouched.
408
408
 
409
- Bare interactive `proteum dev` reuses that same wizard when the app root is missing `AGENTS.md`; declining the prompt continues the dev start without writing files.
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
 
411
411
  `proteum connect`, `proteum explain`, `proteum doctor`, and `proteum diagnose` share the same generated manifest and contract state. `proteum perf` uses the same dev request-trace store as the profiler `Perf` tab. For the full diagnostics and tracing model, see [docs/diagnostics.md](docs/diagnostics.md) and [docs/request-tracing.md](docs/request-tracing.md).
412
412
 
@@ -21,6 +21,7 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
21
21
  - If the user pastes raw errors without asking for a fix, do not implement changes yet. First run the task-safe local reproduction path: identify the likely app, route, command, or request from the error, boot or reuse the relevant dev server with the elevated-permissions workflow in `Task Lifecycle`, reproduce the failing surface locally, and inspect server output, browser console output, diagnostics, traces, or the smallest relevant command result. If the error does not identify enough context to reproduce, say what is missing and use the available local evidence before guessing. Then list likely causes and, for each one, give probability, why, and how to fix it. After this, every time you implement a fix:
22
22
  - test, re-run analysis and give a comparison table of before and after
23
23
  - re-print the complete list of suggested fixes, but strike the ones we already implemented or not necessary anymore
24
+ - If the user asks to implement a feature, first inspect the relevant existing surface and state any implementation problem, pain point, attention point, or question you see. If a concern is blocking, or it can materially change product behavior, API shape, architecture, data model, cost, privacy, security, or UX, ask before editing; otherwise state the assumption and continue implementing.
24
25
  - If the task is ambiguous, generated, connected, or multi-repo, start with `npx proteum orient <query>` before reading large parts of the codebase.
25
26
  - 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`.
26
27
  - 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.
@@ -57,7 +58,7 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
57
58
  - Use `--replace-existing` only when restarting the exact session file started by the current thread/task. Never replace another live session that belongs to a user, another thread, or an unknown owner.
58
59
  - If the current app depends on local `file:` connected projects, boot every connected producer app too, each with its own task-scoped session file and free port, and run every one of those `proteum dev` processes with elevated permissions outside the sandbox before starting or verifying the consumer app.
59
60
  - For browser validation, use the browser MCP against the running app. Keep Playwright inside `npx proteum e2e --port <port>` for targeted/full end-to-end suites. Bootstrap protected browser MCP state with `npx proteum session`; bootstrap protected E2E runs with `npx proteum e2e --session-email <email> --session-role <role>`.
60
- - Current CLI banner contract: only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the banner. Only `proteum dev` clears the interactive terminal before rendering, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys in its session UI, and reports connected app names plus successful connected `/ping` checks in the ready banner. When the app root is missing `AGENTS.md`, the bare interactive `proteum dev` start offers to launch `proteum configure agents` before the dev loop begins.
61
+ - Current CLI banner contract: only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the banner. Only `proteum dev` clears the interactive terminal before rendering, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys in its session UI, and reports connected app names plus successful connected `/ping` checks in the ready banner. Every `proteum dev` start ensures tracked instruction files contain the current managed `# Proteum Instructions` section before the dev loop begins.
61
62
 
62
63
  ### Before Finishing
63
64
 
@@ -12,5 +12,5 @@ Do not put here: reusable Proteum architecture contracts, shared verification ru
12
12
  - Run `npx proteum refresh`.
13
13
  - Read and acknowledge the applicable `AGENTS.md` files.
14
14
  - Run `npm i`.
15
- - Run the dev server with the task-safe elevated-permissions launch workflow from the reusable root `AGENTS.md`, keep it running so user can see the results by himself, and print the live server URL as a clickable Markdown link. If the bare interactive `proteum dev` start offers to launch `proteum configure agents`, finish that wizard before continuing.
15
+ - Run the dev server with the task-safe elevated-permissions launch workflow from the reusable root `AGENTS.md`, keep it running so user can see the results by himself, and print the live server URL as a clickable Markdown link. If `proteum dev` reports blocked instruction paths, resolve them before continuing.
16
16
  - If the task changes UX, copy, onboarding, pricing, product semantics, or commercial positioning, read the relevant files under `./docs/` first, especially `docs/PERSONAS.md`, `docs/PRODUCT.md`, and `docs/MARKETING.md` when they exist. If a dev server is already running, print the live dev server URL as a clickable Markdown link.
@@ -15,7 +15,7 @@ This file is the canonical source of truth for diagnostics, temporary instrument
15
15
 
16
16
  - For long-lived dev reproductions, always request elevated permissions and run `npx proteum dev` outside the sandbox. Use an explicit task/thread-scoped session file, inspect `npx proteum dev list --json` plus current listeners first, for example with `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.
17
17
  - Use `--replace-existing` only when restarting the exact session file started by the current thread/task. Never replace another live session that belongs to a user, another thread, or an unknown owner.
18
- - Only the bare `npx proteum build` and bare `npx proteum dev` commands print the welcome banner and active Proteum installation method. Any extra argument or option skips the banner. Only `npx proteum dev` clears the interactive terminal before rendering and reports connected app names plus successful connected `/ping` checks in the ready banner; keep that in mind when capturing or comparing command logs during diagnosis. When the app root is missing `AGENTS.md`, the bare interactive `npx proteum dev` start offers to launch `npx proteum configure agents` before the dev loop begins.
18
+ - Only the bare `npx proteum build` and bare `npx proteum dev` commands print the welcome banner and active Proteum installation method. Any extra argument or option skips the banner. Only `npx proteum dev` clears the interactive terminal before rendering and reports connected app names plus successful connected `/ping` checks in the ready banner; keep that in mind when capturing or comparing command logs during diagnosis. Every `npx proteum dev` start ensures tracked instruction files contain the current managed `# Proteum Instructions` section before the dev loop begins.
19
19
  - For ownership or repo discovery questions, start with `npx proteum orient <query>` instead of jumping straight into source searches.
20
20
  - For request-time issues in dev, start with `npx proteum diagnose <path> --port <port>` when you have a concrete failing route, page, controller path, or request target. It combines owner lookup, manifest diagnostics, contract diagnostics, matching trace data, and buffered server logs in one pass.
21
21
  - Prefer focused verification before global checks: `npx proteum verify owner <query>`, `npx proteum verify request <path>`, and only then browser MCP validation when the bug is browser-visible. Use `npx proteum e2e --port <port> ...` only when automated end-to-end coverage or a Playwright suite is required.
@@ -12,6 +12,7 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
12
12
  ## Fast Triggers
13
13
 
14
14
  - If the user pastes raw errors without asking for a fix, do not implement changes yet. First run the task-safe local reproduction path: identify the likely app, route, command, or request from the error, boot or reuse the relevant dev server with the elevated-permissions workflow in `Task Lifecycle`, reproduce the failing surface locally, and inspect server output, browser console output, diagnostics, traces, or the smallest relevant command result. If the error does not identify enough context to reproduce, say what is missing and use the available local evidence before guessing. Then list likely causes and, for each one, give probability, why, and how to fix it.
15
+ - If the user asks to implement a feature, first inspect the relevant existing surface and state any implementation problem, pain point, attention point, or question you see. If a concern is blocking, or it can materially change product behavior, API shape, architecture, data model, cost, privacy, security, or UX, ask before editing; otherwise state the assumption and continue implementing.
15
16
  - If the task is ambiguous, generated, connected, or multi-repo, start with `npx proteum orient <query>` before reading large parts of the codebase.
16
17
  - 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`.
17
18
  - 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.
@@ -47,7 +48,7 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
47
48
  - Use `--replace-existing` only when restarting the exact session file started by the current thread/task. Never replace another live session that belongs to a user, another thread, or an unknown owner.
48
49
  - If the current app depends on local `file:` connected projects, boot every connected producer app too, each with its own task-scoped session file and free port, and run every one of those `proteum dev` processes with elevated permissions outside the sandbox before starting or verifying the consumer app.
49
50
  - For browser validation, use the browser MCP against the running app. Keep Playwright inside `npx proteum e2e --port <port>` for targeted/full end-to-end suites. Bootstrap protected browser MCP state with `npx proteum session`; bootstrap protected E2E runs with `npx proteum e2e --session-email <email> --session-role <role>`.
50
- - Current CLI banner contract: only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the banner. Only `proteum dev` clears the interactive terminal before rendering, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys in its session UI, and reports connected app names plus successful connected `/ping` checks in the ready banner. When the app root is missing `AGENTS.md`, the bare interactive `proteum dev` start offers to launch `proteum configure agents` before the dev loop begins.
51
+ - Current CLI banner contract: only the bare `proteum build` and bare `proteum dev` commands print the welcome banner and include the active Proteum installation method. Any extra argument or option skips the banner. Only `proteum dev` clears the interactive terminal before rendering, exposes `CTRL+R` reload plus `CTRL+C` shutdown hotkeys in its session UI, and reports connected app names plus successful connected `/ping` checks in the ready banner. Every `proteum dev` start ensures tracked instruction files contain the current managed `# Proteum Instructions` section before the dev loop begins.
51
52
 
52
53
  ### Before Finishing
53
54
 
@@ -13,39 +13,18 @@ import cli from '..';
13
13
  import { renderRows } from '../presentation/layout';
14
14
  import { isLikelyProteumAppRoot } from '../presentation/commands';
15
15
  import { renderStep, renderSuccess, renderTitle, renderWarning } from '../presentation/ink';
16
- import { configureProjectAgentInstructions, type TConfigureProjectAgentInstructionsResult } from '../utils/agents';
16
+ import {
17
+ configureProjectAgentInstructions,
18
+ findLikelyRepoRoot,
19
+ isInsideDirectory,
20
+ resolveCanonicalPath,
21
+ type TConfigureProjectAgentInstructionsResult,
22
+ } from '../utils/agents';
17
23
 
18
24
  /*----------------------------------
19
25
  - HELPERS
20
26
  ----------------------------------*/
21
27
 
22
- const findLikelyRepoRoot = (startPath: string) => {
23
- let currentPath = path.resolve(startPath);
24
-
25
- while (true) {
26
- if (fs.existsSync(path.join(currentPath, '.git'))) return currentPath;
27
-
28
- const parentPath = path.dirname(currentPath);
29
- if (parentPath === currentPath) return undefined;
30
- currentPath = parentPath;
31
- }
32
- };
33
-
34
- const resolveCanonicalPath = (inputPath: string) => {
35
- const resolvedPath = path.resolve(inputPath);
36
-
37
- try {
38
- return fs.realpathSync(resolvedPath);
39
- } catch {
40
- return resolvedPath;
41
- }
42
- };
43
-
44
- const isInsideDirectory = ({ child, parent }: { child: string; parent: string }) => {
45
- const relativePath = path.relative(parent, child);
46
- return relativePath !== '' && !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
47
- };
48
-
49
28
  const assertProteumAppRoot = (appRoot: string) => {
50
29
  if (isLikelyProteumAppRoot(appRoot)) return;
51
30
 
@@ -92,10 +71,10 @@ const promptMonorepoRoot = async ({
92
71
  const promptBlockedOverwritePaths = async (blockedPaths: string[]) => {
93
72
  if (blockedPaths.length === 0) return [];
94
73
 
95
- console.info(await renderWarning('Proteum found existing non-managed instruction paths.'));
74
+ console.info(await renderWarning('Proteum found existing paths that block managed instruction updates.'));
96
75
  console.info(
97
76
  [
98
- 'Choose whether to overwrite each path with a Proteum-managed instruction stub:',
77
+ 'Choose whether to overwrite each path with a tracked Proteum instruction file:',
99
78
  ...blockedPaths.map((entry) => `- ${entry}`),
100
79
  ].join('\n'),
101
80
  );
@@ -145,7 +124,7 @@ const renderConfigureResultSections = (result: TConfigureProjectAgentInstruction
145
124
  if (result.blocked.length > 0)
146
125
  sections.push(
147
126
  [
148
- 'Skipped existing non-managed paths:',
127
+ 'Skipped blocked paths:',
149
128
  ...result.blocked.map((entry) => `- ${entry}`),
150
129
  ].join('\n'),
151
130
  );
@@ -183,7 +162,7 @@ export const runConfigureAgentsWizard = async ({
183
162
  : undefined;
184
163
  console.info(
185
164
  [
186
- await renderTitle('PROTEUM CONFIGURE AGENTS', 'Configure Proteum-managed instruction stubs.'),
165
+ await renderTitle('PROTEUM CONFIGURE AGENTS', 'Configure tracked Proteum instruction files.'),
187
166
  renderRows([{ label: 'app', value: appRoot === process.cwd() ? '.' : appRoot }]),
188
167
  ].join('\n\n'),
189
168
  );
@@ -221,8 +200,8 @@ export const runConfigureAgentsWizard = async ({
221
200
  await renderStep(
222
201
  '[1/1]',
223
202
  isMonorepo
224
- ? `Writing monorepo-aware instruction stubs using ${monorepoRoot}.`
225
- : 'Writing standalone instruction stubs.',
203
+ ? `Writing monorepo-aware instruction files using ${monorepoRoot}.`
204
+ : 'Writing standalone instruction files.',
226
205
  ),
227
206
  );
228
207
 
@@ -234,7 +213,7 @@ export const runConfigureAgentsWizard = async ({
234
213
  });
235
214
  const sections = renderConfigureResultSections(result);
236
215
 
237
- console.info(await renderSuccess('Proteum-managed instruction stubs are configured.'));
216
+ console.info(await renderSuccess('Proteum-managed instruction files are configured.'));
238
217
 
239
218
  if (sections.length > 0) console.info(`\n${sections.join('\n\n')}`);
240
219
  };
@@ -22,7 +22,6 @@ import {
22
22
 
23
23
  // Configs
24
24
  import Compiler from '../compiler';
25
- import { regex } from '../compiler/common';
26
25
  import { createDevEventServer } from './devEvents';
27
26
  import { renderDevSession, renderServerReadyBanner, renderDevShutdownBanner } from '../presentation/devSession';
28
27
  import { clearInteractiveConsole } from '../presentation/welcome';
@@ -41,8 +40,7 @@ import {
41
40
  } from '../runtime/devSessions';
42
41
  import { resolveFrameworkInstallInfo } from '../paths';
43
42
  import { logVerbose } from '../runtime/verbose';
44
- import { inspectProjectAgentFiles } from '../utils/agents';
45
- import { runConfigureAgentsWizard } from './configure';
43
+ import { configureProjectAgentInstructions, resolveProjectAgentMonorepoRoot } from '../utils/agents';
46
44
 
47
45
  // Core
48
46
  import { app, App } from '../app';
@@ -61,6 +59,7 @@ const hotReloadableServerPathPatterns = [
61
59
  /^server\/services\/.+\.controller\.[jt]sx?$/,
62
60
  ];
63
61
  const hotReloadableRoots = [() => app.paths.root, () => cli.paths.core.root];
62
+ const transpileSourceWatchPattern = /\.(ts|tsx|js|jsx|css|less|scss)$/;
64
63
 
65
64
  /*----------------------------------
66
65
  - STATE
@@ -144,43 +143,74 @@ const createIgnoredWatchMatcher = (outputPaths: string[]) => (watchPath: string)
144
143
  return ignoredWatchPathPatterns.test(normalizedWatchPath);
145
144
  };
146
145
 
147
- const promptToConfigureAgentsIfMissing = async () => {
148
- if (cli.args.json === true) return;
149
- if (!process.stdin.isTTY || !process.stdout.isTTY) return;
150
-
151
- const inspection = inspectProjectAgentFiles({ appRoot: app.paths.root });
152
- if (!inspection.missing.includes('AGENTS.md')) return;
146
+ const promptBlockedAgentInstructionOverwrites = async (blockedPaths: string[]) => {
147
+ if (blockedPaths.length === 0) return [];
148
+ if (cli.args.json === true || !process.stdin.isTTY || !process.stdout.isTTY) {
149
+ throw new UsageError(
150
+ [
151
+ 'Proteum could not update managed instruction files because existing paths are blocked:',
152
+ ...blockedPaths.map((entry) => `- ${entry}`),
153
+ 'Run `proteum configure agents` in an interactive terminal to choose which paths can be replaced.',
154
+ ].join('\n'),
155
+ );
156
+ }
153
157
 
154
- console.info(await renderWarning('Proteum could not find the app-root `AGENTS.md` instruction file.'));
158
+ console.info(await renderWarning('Proteum found existing paths that block managed instruction updates.'));
155
159
  console.info(
156
160
  [
157
- 'Missing standard AGENTS files:',
158
- ...inspection.missing.map((entry) => `- ${entry}`),
159
- '',
160
- 'Run `proteum configure agents` now before starting the dev server?',
161
+ 'Choose whether to overwrite each blocked path with a tracked Proteum instruction file:',
162
+ ...blockedPaths.map((entry) => `- ${entry}`),
161
163
  ].join('\n'),
162
164
  );
163
165
 
164
- const response = await prompts(
165
- {
166
- type: 'confirm',
167
- name: 'value',
168
- message: 'Run the configure-agents wizard now?',
169
- initial: true,
170
- },
171
- {
172
- onCancel: () => {
173
- throw new UsageError('Cancelled `proteum dev`.');
166
+ const overwriteBlockedPaths: string[] = [];
167
+
168
+ for (const blockedPath of blockedPaths) {
169
+ const response = await prompts(
170
+ {
171
+ type: 'confirm',
172
+ name: 'value',
173
+ message: `Overwrite ${blockedPath}?`,
174
+ initial: false,
174
175
  },
175
- },
176
- );
176
+ {
177
+ onCancel: () => {
178
+ throw new UsageError('Cancelled `proteum dev`.');
179
+ },
180
+ },
181
+ );
182
+
183
+ if (response.value === true) overwriteBlockedPaths.push(blockedPath);
184
+ }
177
185
 
178
- if (response.value !== true) return;
186
+ return overwriteBlockedPaths;
187
+ };
188
+
189
+ const ensureProjectAgentInstructions = async () => {
190
+ const monorepoRoot = resolveProjectAgentMonorepoRoot(app.paths.root);
191
+ const preview = configureProjectAgentInstructions({
192
+ appRoot: app.paths.root,
193
+ coreRoot: cli.paths.core.root,
194
+ dryRun: true,
195
+ monorepoRoot,
196
+ });
197
+ const overwriteBlockedPaths = await promptBlockedAgentInstructionOverwrites(preview.blocked);
179
198
 
180
- await runConfigureAgentsWizard({
199
+ const result = configureProjectAgentInstructions({
181
200
  appRoot: app.paths.root,
182
201
  coreRoot: cli.paths.core.root,
202
+ monorepoRoot,
203
+ overwriteBlockedPaths,
183
204
  });
205
+
206
+ if (result.blocked.length === 0) return;
207
+
208
+ throw new UsageError(
209
+ [
210
+ 'Proteum could not update all managed instruction files because these paths were left blocked:',
211
+ ...result.blocked.map((entry) => `- ${entry}`),
212
+ ].join('\n'),
213
+ );
184
214
  };
185
215
 
186
216
  const getDevAppName = (app: App) =>
@@ -530,11 +560,11 @@ type TIndexedSourceWatchEvent = 'change' | 'rename';
530
560
  type TIndexedSourceWatchCompilerName = 'server' | 'client';
531
561
  type TIndexedSourceWatchInvalidateTarget = 'all' | TIndexedSourceWatchCompilerName;
532
562
  type TIndexedSourceWatchRule = {
533
- compilerName: TIndexedSourceWatchCompilerName;
563
+ compilerNames: TIndexedSourceWatchCompilerName[];
534
564
  rootPath: string;
535
565
  relativePathPattern: RegExp;
536
566
  eventTypes: TIndexedSourceWatchEvent[];
537
- invalidateTarget: TIndexedSourceWatchInvalidateTarget;
567
+ invalidateTargets: TIndexedSourceWatchInvalidateTarget[];
538
568
  };
539
569
  type TNamedWatching = { compiler: { name?: string }; invalidate: () => void };
540
570
  type TMultiWatchingLike = TDevWatching & { watchings?: TNamedWatching[] };
@@ -553,26 +583,26 @@ const resolveIndexedSourceWatchRules = (): TIndexedSourceWatchRule[] => {
553
583
 
554
584
  return [
555
585
  {
556
- compilerName: 'server',
586
+ compilerNames: ['server'],
557
587
  rootPath: app.paths.root,
558
588
  relativePathPattern: /^commands(?:\/|$)/,
559
589
  eventTypes: ['rename'],
560
- invalidateTarget: 'all',
590
+ invalidateTargets: ['all'],
561
591
  },
562
592
  {
563
- compilerName: 'server',
593
+ compilerNames: ['server'],
564
594
  rootPath: cli.paths.core.root,
565
595
  relativePathPattern: /^commands(?:\/|$)/,
566
596
  eventTypes: ['rename'],
567
- invalidateTarget: 'all',
597
+ invalidateTargets: ['all'],
568
598
  },
569
599
  ...transpileWatchRoots.map(
570
600
  (rootPath): TIndexedSourceWatchRule => ({
571
- compilerName: 'server',
601
+ compilerNames: ['client', 'server'],
572
602
  rootPath,
573
- relativePathPattern: regex.scripts,
603
+ relativePathPattern: transpileSourceWatchPattern,
574
604
  eventTypes: ['change', 'rename'],
575
- invalidateTarget: 'server',
605
+ invalidateTargets: ['client', 'server'],
576
606
  }),
577
607
  ),
578
608
  ];
@@ -587,6 +617,12 @@ const findCompilerWatching = (
587
617
  return childWatchings?.find((childWatching) => childWatching.compiler.name === compilerName);
588
618
  };
589
619
 
620
+ const formatInvalidateTargets = (invalidateTargets: TIndexedSourceWatchCompilerName[]) => {
621
+ if (invalidateTargets.length === 1) return invalidateTargets[0];
622
+ if (invalidateTargets.length === 2) return `${invalidateTargets[0]} and ${invalidateTargets[1]}`;
623
+ return `${invalidateTargets.slice(0, -1).join(', ')}, and ${invalidateTargets[invalidateTargets.length - 1]}`;
624
+ };
625
+
590
626
  const closeFsWatcher = async (watcher: FSWatcher) => {
591
627
  await new Promise<void>((resolve) => {
592
628
  watcher.once('close', () => resolve());
@@ -618,7 +654,7 @@ const createIndexedSourceWatching = ({
618
654
 
619
655
  if (pendingInvalidateTargets.has('all')) {
620
656
  pendingInvalidateTargets.clear();
621
- logVerbose('Indexed source files changed. Invalidating the dev compiler to refresh generated artifacts.');
657
+ logVerbose('Indexed source files changed. Invalidating all dev compilers to refresh generated artifacts.');
622
658
  watching.invalidate();
623
659
  return;
624
660
  }
@@ -630,40 +666,57 @@ const createIndexedSourceWatching = ({
630
666
 
631
667
  if (invalidateTargets.length === 0) return;
632
668
 
633
- logVerbose('Transpiled source files changed. Invalidating the server compiler to refresh mutable package code.');
669
+ const compilerWatchings: TNamedWatching[] = [];
670
+
634
671
  for (const invalidateTarget of invalidateTargets) {
635
672
  const compilerWatching = findCompilerWatching(watching, invalidateTarget);
636
673
 
637
674
  if (!compilerWatching) {
675
+ logVerbose('Transpiled source files changed. Invalidating all dev compilers to refresh mutable package code.');
638
676
  watching.invalidate();
639
677
  return;
640
678
  }
641
679
 
680
+ compilerWatchings.push(compilerWatching);
681
+ }
682
+
683
+ logVerbose(
684
+ `Transpiled source files changed. Invalidating ${formatInvalidateTargets(
685
+ invalidateTargets,
686
+ )} compilers to refresh mutable package code.`,
687
+ );
688
+ for (const compilerWatching of compilerWatchings) {
642
689
  compilerWatching.invalidate();
643
690
  }
644
691
  };
645
692
 
646
693
  const queueInvalidate = ({
647
- compilerName,
694
+ compilerNames,
648
695
  filepath,
649
- invalidateTarget,
696
+ invalidateTargets,
650
697
  }: {
651
- compilerName: TIndexedSourceWatchCompilerName;
698
+ compilerNames: TIndexedSourceWatchCompilerName[];
652
699
  filepath: string;
653
- invalidateTarget: TIndexedSourceWatchInvalidateTarget;
700
+ invalidateTargets: TIndexedSourceWatchInvalidateTarget[];
654
701
  }) => {
655
702
  const normalizedFilepath = normalizeWatchPath(filepath);
656
- const queueKey = `${invalidateTarget}:${compilerName}:${normalizedFilepath}`;
703
+ const queueKey = `${invalidateTargets.join(',')}:${compilerNames.join(',')}:${normalizedFilepath}`;
657
704
  const queuedAt = recentQueuedChanges.get(queueKey);
658
705
 
659
706
  if (queuedAt !== undefined && Date.now() - queuedAt < 250) return;
660
707
 
661
708
  recentQueuedChanges.set(queueKey, Date.now());
662
- const changedFiles = pendingChanges.get(compilerName) || new Set<string>();
663
709
 
664
- changedFiles.add(normalizedFilepath);
665
- pendingChanges.set(compilerName, changedFiles);
666
- pendingInvalidateTargets.add(invalidateTarget);
710
+ for (const compilerName of compilerNames) {
711
+ const changedFiles = pendingChanges.get(compilerName) || new Set<string>();
712
+
713
+ changedFiles.add(normalizedFilepath);
714
+ pendingChanges.set(compilerName, changedFiles);
715
+ }
716
+
717
+ for (const invalidateTarget of invalidateTargets) {
718
+ pendingInvalidateTargets.add(invalidateTarget);
719
+ }
667
720
 
668
721
  if (invalidateTimer) return;
669
722
  invalidateTimer = setTimeout(flushInvalidate, 40);
@@ -682,9 +735,9 @@ const createIndexedSourceWatching = ({
682
735
  if (!watchRule.eventTypes.includes(normalizedEventType) && relativePath) return;
683
736
 
684
737
  queueInvalidate({
685
- compilerName: watchRule.compilerName,
738
+ compilerNames: watchRule.compilerNames,
686
739
  filepath: relativePath ? path.join(rootPath, relativePath) : rootPath,
687
- invalidateTarget: watchRule.invalidateTarget,
740
+ invalidateTargets: watchRule.invalidateTargets,
688
741
  });
689
742
  }),
690
743
  );
@@ -755,7 +808,7 @@ const runDevLoop = async () => {
755
808
  // - Node modules except 5HTP core (framework dev mode)
756
809
  // - Generated files during runtime (cause infinite loop. Ex: models.d.ts)
757
810
  // - Webpack output folders (`./dev`, legacy `./bin`)
758
- ignored: ignoredWatchMatcher,
811
+ ignored: ignoredWatchMatcher as never,
759
812
 
760
813
  //aggregateTimeout: 1000,
761
814
  },
@@ -880,6 +933,6 @@ export const run = async () => {
880
933
  return;
881
934
  }
882
935
 
883
- await promptToConfigureAgentsIfMissing();
936
+ await ensureProjectAgentInstructions();
884
937
  await runDevLoop();
885
938
  };
@@ -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 = (
@@ -5,7 +5,6 @@ import app from '../../app';
5
5
  import cli from '../..';
6
6
  import { inspectProteumEnv } from '../../../common/env/proteumEnv';
7
7
  import { reservedRouteOptionKeys, routeOptionKeys } from '../../../common/router/pageData';
8
- import { getProjectInstructionGitignoreEntries } from '../../utils/agents';
9
8
  import {
10
9
  TProteumManifest,
11
10
  TProteumManifestCommand,
@@ -40,10 +39,7 @@ const collectManifestDiagnostics = ({
40
39
  routes: TProteumManifest['routes'];
41
40
  }) => {
42
41
  const diagnostics: TProteumManifestDiagnostic[] = [];
43
- const expectedGitignoreEntries = [
44
- ...requiredGitignoreEntries,
45
- ...getProjectInstructionGitignoreEntries({ coreRoot: cli.paths.core.root }),
46
- ];
42
+ const expectedGitignoreEntries = [...requiredGitignoreEntries];
47
43
 
48
44
  const pushDiagnostic = (diagnostic: TProteumManifestDiagnostic) => {
49
45
  diagnostics.push(diagnostic);