proteum 2.4.3 → 2.5.0

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 (74) hide show
  1. package/README.md +60 -55
  2. package/agents/project/AGENTS.md +112 -31
  3. package/agents/project/CODING_STYLE.md +2 -2
  4. package/agents/project/app-root/AGENTS.md +1 -3
  5. package/agents/project/client/AGENTS.md +1 -1
  6. package/agents/project/client/pages/AGENTS.md +21 -9
  7. package/agents/project/diagnostics.md +2 -2
  8. package/agents/project/optimizations.md +1 -1
  9. package/agents/project/root/AGENTS.md +105 -22
  10. package/agents/project/server/routes/AGENTS.md +30 -1
  11. package/agents/project/tests/AGENTS.md +1 -1
  12. package/cli/commands/doctor.ts +54 -3
  13. package/cli/commands/runtime.ts +6 -0
  14. package/cli/commands/worktree.ts +116 -0
  15. package/cli/compiler/artifacts/controllers.ts +16 -15
  16. package/cli/compiler/artifacts/discovery.ts +129 -17
  17. package/cli/compiler/artifacts/routing.ts +0 -5
  18. package/cli/compiler/artifacts/services.ts +253 -76
  19. package/cli/compiler/common/controllers.ts +159 -57
  20. package/cli/compiler/common/generatedRouteModules.ts +457 -363
  21. package/cli/mcp/router.ts +47 -3
  22. package/cli/presentation/commands.ts +25 -15
  23. package/cli/runtime/commands.ts +39 -12
  24. package/cli/runtime/worktreeBootstrap.ts +608 -0
  25. package/cli/scaffold/index.ts +28 -18
  26. package/cli/scaffold/templates.ts +44 -33
  27. package/cli/utils/agents.ts +14 -1
  28. package/client/services/router/index.tsx +23 -3
  29. package/client/services/router/request/api.ts +14 -4
  30. package/common/dev/contractsDoctor.ts +1 -1
  31. package/common/dev/mcpPayloads.ts +8 -1
  32. package/common/env/proteumEnv.ts +14 -2
  33. package/common/router/contracts.ts +1 -1
  34. package/common/router/definitions.ts +177 -0
  35. package/common/router/index.ts +23 -12
  36. package/common/router/pageData.ts +5 -5
  37. package/common/router/register.ts +2 -2
  38. package/common/router/request/api.ts +12 -2
  39. package/docs/agent-routing.md +5 -2
  40. package/docs/diagnostics.md +2 -0
  41. package/docs/mcp.md +6 -3
  42. package/eslint.js +36 -1
  43. package/package.json +1 -1
  44. package/server/app/commands.ts +5 -1
  45. package/server/app/container/console/http-client-error-context.test.cjs +10 -1
  46. package/server/app/container/console/index.ts +2 -1
  47. package/server/app/controller/index.ts +98 -40
  48. package/server/app/index.ts +92 -1
  49. package/server/app/service/index.ts +5 -1
  50. package/server/index.ts +6 -2
  51. package/server/services/router/index.ts +47 -38
  52. package/server/services/router/response/index.ts +2 -2
  53. package/tests/agents-utils.test.cjs +14 -1
  54. package/tests/cli-mcp-command.test.cjs +84 -0
  55. package/tests/definition-contracts.test.cjs +453 -0
  56. package/tests/dev-transpile-watch.test.cjs +37 -28
  57. package/tests/eslint-rules.test.cjs +39 -1
  58. package/tests/mcp.test.cjs +90 -0
  59. package/tests/worktree-bootstrap.test.cjs +206 -0
  60. package/types/aliases.d.ts +0 -5
  61. package/types/controller-input.test.ts +23 -17
  62. package/types/controller-request-context.test.ts +10 -11
  63. package/cli/commands/migrate.ts +0 -51
  64. package/cli/migrate/pageContract.ts +0 -516
  65. package/docs/migrate-from-2.1.3.md +0 -396
  66. package/scripts/cleanup-generated-controllers.ts +0 -62
  67. package/scripts/fix-reference-app-typing.ts +0 -490
  68. package/scripts/format-router-registrations.ts +0 -119
  69. package/scripts/migrate-explicit-controllers-and-request.ts +0 -423
  70. package/scripts/refactor-client-app-imports.ts +0 -244
  71. package/scripts/refactor-client-pages.ts +0 -587
  72. package/scripts/refactor-server-controllers.ts +0 -471
  73. package/scripts/refactor-server-runtime-aliases.ts +0 -360
  74. package/scripts/restore-client-app-import-files.ts +0 -41
package/cli/mcp/router.ts CHANGED
@@ -37,6 +37,11 @@ import {
37
37
  type TProteumAppRootSummary,
38
38
  } from '../utils/appRoots';
39
39
  import { inspectDevPort, type TDevPortInspection } from '../runtime/ports';
40
+ import {
41
+ compactWorktreeBootstrapStatus,
42
+ createWorktreeBootstrapMcpBlockResponse,
43
+ getWorktreeBootstrapStatus,
44
+ } from '../runtime/worktreeBootstrap';
40
45
 
41
46
  type TDevMcpClient = {
42
47
  callTool: (input: { arguments?: Record<string, unknown>; name: string }) => Promise<CallToolResult>;
@@ -134,12 +139,17 @@ const createOfflineNextAction = async ({
134
139
  baseRoot,
135
140
  portInspection,
136
141
  summary,
142
+ version,
137
143
  }: {
138
144
  appRoot: string;
139
145
  baseRoot?: string;
140
146
  portInspection?: TDevPortInspection;
141
147
  summary: TProteumAppRootSummary;
148
+ version: string;
142
149
  }) => {
150
+ const bootstrapStatus = getWorktreeBootstrapStatus({ appRoot, proteumVersion: version });
151
+ if (bootstrapStatus.blocking) return bootstrapStatus.nextAction;
152
+
143
153
  if (!summary.manifest) {
144
154
  return {
145
155
  label: 'Check Runtime Status',
@@ -178,10 +188,12 @@ const compactOfflineProject = async ({
178
188
  appRoot,
179
189
  baseRoot,
180
190
  matchReason,
191
+ version,
181
192
  }: {
182
193
  appRoot: string;
183
194
  baseRoot?: string;
184
195
  matchReason: string;
196
+ version: string;
185
197
  }): Promise<TOfflineProject> => {
186
198
  const summary = readProteumAppRootSummary(appRoot, baseRoot);
187
199
  const portInspection = summary.manifest
@@ -196,7 +208,7 @@ const compactOfflineProject = async ({
196
208
  devPort: portInspection,
197
209
  live: false,
198
210
  matchReason,
199
- nextAction: await createOfflineNextAction({ appRoot: summary.appRoot, baseRoot, portInspection, summary }),
211
+ nextAction: await createOfflineNextAction({ appRoot: summary.appRoot, baseRoot, portInspection, summary, version }),
200
212
  projectId: await resolveProteumProjectId(summary.appRoot),
201
213
  state: 'offline',
202
214
  };
@@ -297,7 +309,7 @@ export const createProteumMachineMcpServer = ({ createDevMcpClient, version }: T
297
309
  if (!matches.has(record.projectId)) matches.set(record.projectId, { project: compactProjectMatch(record, reason), record });
298
310
  };
299
311
  const addOfflineMatch = async (appRoot: string, reason: string, baseRoot?: string) => {
300
- const offline = await compactOfflineProject({ appRoot, baseRoot, matchReason: reason });
312
+ const offline = await compactOfflineProject({ appRoot, baseRoot, matchReason: reason, version });
301
313
  if (!matches.has(offline.projectId)) matches.set(offline.projectId, { offline, project: offline });
302
314
  };
303
315
  const normalizedProjectId = typeof projectId === 'string' ? projectId.trim() : '';
@@ -386,16 +398,38 @@ export const createProteumMachineMcpServer = ({ createDevMcpClient, version }: T
386
398
  return { error: null, record: inspection.record };
387
399
  };
388
400
 
401
+ const augmentForwardedPayload = (result: CallToolResult, record: TMachineDevSessionRecord) => {
402
+ if (result.content[0]?.type !== 'text') return result;
403
+
404
+ try {
405
+ const payload = JSON.parse(result.content[0].text);
406
+ const bootstrapStatus = getWorktreeBootstrapStatus({ appRoot: record.appRoot, proteumVersion: version });
407
+
408
+ return jsonToolResult({
409
+ ...payload,
410
+ data: {
411
+ ...(payload.data || {}),
412
+ worktreeBootstrap: compactWorktreeBootstrapStatus(bootstrapStatus),
413
+ },
414
+ });
415
+ } catch {
416
+ return result;
417
+ }
418
+ };
419
+
389
420
  const forwardTool = async (name: string, input: Record<string, unknown>) => {
390
421
  const resolution = await resolveProject(input.projectId);
391
422
  if (!resolution.record) return resolution.error;
392
423
 
393
424
  try {
394
425
  const client = await getClient(resolution.record);
395
- return await client.callTool({
426
+ const result = await client.callTool({
396
427
  arguments: stripProjectRouting(input),
397
428
  name,
398
429
  });
430
+
431
+ if (name === 'runtime_status' || name === 'doctor') return augmentForwardedPayload(result, resolution.record);
432
+ return result;
399
433
  } catch (error) {
400
434
  await closeClient(resolution.record);
401
435
  return errorToolResult(`Could not reach Proteum dev MCP for ${resolution.record.projectId}.`, {
@@ -407,6 +441,9 @@ export const createProteumMachineMcpServer = ({ createDevMcpClient, version }: T
407
441
  };
408
442
 
409
443
  const createOfflineWorkflowStartResult = (offline: TOfflineProject, input: Record<string, unknown>) => {
444
+ const bootstrapStatus = getWorktreeBootstrapStatus({ appRoot: offline.appRoot, proteumVersion: version });
445
+ if (bootstrapStatus.blocking) return jsonToolResult(createWorktreeBootstrapMcpBlockResponse(bootstrapStatus, offline), true);
446
+
410
447
  let manifest: ReturnType<typeof readProteumManifest>;
411
448
  try {
412
449
  manifest = readProteumManifest(offline.appRoot);
@@ -458,6 +495,7 @@ export const createProteumMachineMcpServer = ({ createDevMcpClient, version }: T
458
495
  ...payload,
459
496
  data: {
460
497
  project: offline,
498
+ worktreeBootstrap: compactWorktreeBootstrapStatus(bootstrapStatus),
461
499
  ...payload.data,
462
500
  },
463
501
  nextActions: [
@@ -497,6 +535,11 @@ export const createProteumMachineMcpServer = ({ createDevMcpClient, version }: T
497
535
  });
498
536
  }
499
537
 
538
+ const bootstrapStatus = getWorktreeBootstrapStatus({ appRoot: record.appRoot, proteumVersion: version });
539
+ if (bootstrapStatus.blocking) {
540
+ return jsonToolResult(createWorktreeBootstrapMcpBlockResponse(bootstrapStatus, compactProject(record)), true);
541
+ }
542
+
500
543
  try {
501
544
  const client = await getClient(record);
502
545
  const result = await client.callTool({
@@ -525,6 +568,7 @@ export const createProteumMachineMcpServer = ({ createDevMcpClient, version }: T
525
568
  ...payload,
526
569
  data: {
527
570
  project: compactProject(record),
571
+ worktreeBootstrap: compactWorktreeBootstrapStatus(bootstrapStatus),
528
572
  ...payload.data,
529
573
  },
530
574
  ...(routedNextActions && routedNextActions.length > 0 ? { nextActions: routedNextActions } : {}),
@@ -7,7 +7,7 @@ export const proteumCommandNames = [
7
7
  'init',
8
8
  'create',
9
9
  'configure',
10
- 'migrate',
10
+ 'worktree',
11
11
  'dev',
12
12
  'refresh',
13
13
  'build',
@@ -61,7 +61,7 @@ export const proteumCommandGroups: Array<{ title: string; names: TProteumCommand
61
61
  { title: 'Daily workflow', names: ['dev', 'refresh', 'build'] },
62
62
  { title: 'Quality gates', names: ['typecheck', 'lint', 'check', 'e2e'] },
63
63
  { title: 'Manifest and contracts', names: ['connect', 'doctor', 'explain', 'orient', 'diagnose', 'perf', 'db', 'runtime', 'mcp', 'trace', 'command', 'session', 'verify'] },
64
- { title: 'Project scaffolding', names: ['init', 'configure', 'create', 'migrate'] },
64
+ { title: 'Project scaffolding', names: ['init', 'configure', 'create', 'worktree'] },
65
65
  ];
66
66
 
67
67
  export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> = {
@@ -135,28 +135,38 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
135
135
  ],
136
136
  status: 'experimental',
137
137
  },
138
- migrate: {
139
- name: 'migrate',
138
+ worktree: {
139
+ name: 'worktree',
140
140
  category: 'Project scaffolding',
141
- summary: 'Rewrite legacy page registrations to the explicit Router.page(path, options, data, render) contract.',
142
- usage: 'proteum migrate page-contract [--cwd <path>] [--dry-run] [--json]',
141
+ summary: 'Initialize or create Codex worktrees with a Proteum bootstrap marker.',
142
+ usage:
143
+ 'proteum worktree <init|create> [target-repo-root] --source <app-root> [--branch <branch>] [--base <ref>] [--refresh] [--skip-deps --reason <text>] [--json]',
143
144
  bestFor:
144
- 'Upgrading existing Proteum apps to the single explicit page contract without hand-editing every `client/pages/**` file.',
145
+ 'Making newly created Codex worktrees explicit and machine-checkable before agents run refresh, runtime, dev, verify, or MCP workflow reads.',
145
146
  examples: [
146
- { description: 'Rewrite the current app in place', command: 'proteum migrate page-contract' },
147
147
  {
148
- description: 'Preview the migration without writing files',
149
- command: 'proteum migrate page-contract --dry-run --json',
148
+ description: 'Initialize the current Codex worktree from a source app root',
149
+ command: 'proteum worktree init --source /path/to/main-app',
150
+ },
151
+ {
152
+ description: 'Refresh a stale bootstrap marker',
153
+ command: 'proteum worktree init --source /path/to/main-app --refresh',
154
+ },
155
+ {
156
+ description: 'Create a new Git worktree and bootstrap the matching Proteum app root',
157
+ command:
158
+ 'proteum worktree create /path/to/.codex/worktrees/feature --source /path/to/main-app --branch feature/worktree-bootstrap',
150
159
  },
151
160
  {
152
- description: 'Migrate another app root from the current shell',
153
- command: 'proteum migrate page-contract --cwd /path/to/app',
161
+ description: 'Record an intentional dependency-install skip',
162
+ command: 'proteum worktree init --source /path/to/main-app --skip-deps --reason "dependencies are shared by the parent workspace"',
154
163
  },
155
164
  ],
156
165
  notes: [
157
- 'This migration rewrites supported legacy Router.page signatures to the explicit 4-argument form.',
158
- 'If a page data function cannot be analyzed safely, Proteum leaves the file unchanged and reports a manual-fix location.',
159
- 'Run `proteum typecheck` and `proteum build --strict` after the rewrite.',
166
+ 'Bootstrap writes `.proteum/worktree-bootstrap.json` with hashes, step timestamps, dependency status, runtime status, and Proteum version.',
167
+ 'When `.env` is missing, `--source` is required and must point to an app root with a readable `.env`.',
168
+ 'Guarded commands block inside `/.codex/worktrees/` until bootstrap is complete or explicitly bypassed with `PROTEUM_ALLOW_UNBOOTSTRAPPED_WORKTREE=1`.',
169
+ '`worktree create` preserves the source app root path relative to the source repository root, so monorepo app roots are bootstrapped in the matching target location.',
160
170
  ],
161
171
  status: 'experimental',
162
172
  },
@@ -4,6 +4,7 @@ import path from 'path';
4
4
  import cli, { type TArgsObject } from '../context';
5
5
  import { applyLegacyBooleanArgs, assertNoLegacyArgs } from './argv';
6
6
  import { buildUsage, ProteumCommand, runCommandModule } from './command';
7
+ import { createWorktreeBootstrapBlockResponse, getWorktreeBootstrapStatus } from './worktreeBootstrap';
7
8
  import { createStartDevCommand, quoteShellPath, resolveProteumAppRootContext } from '../utils/appRoots';
8
9
  import { printJson } from '../utils/agentOutput';
9
10
 
@@ -68,6 +69,19 @@ const printNonAppRootResponse = ({
68
69
 
69
70
  const isCurrentWorkdirProteumAppRoot = () => resolveProteumAppRootContext(String(cli.args.workdir || process.cwd())).isAppRoot;
70
71
 
72
+ const blockIfWorktreeBootstrapRequired = () => {
73
+ const status = getWorktreeBootstrapStatus({
74
+ appRoot: cli.paths.appRoot,
75
+ proteumVersion: String(cli.packageJson.version || ''),
76
+ });
77
+
78
+ if (!status.blocking) return false;
79
+
80
+ printJson(createWorktreeBootstrapBlockResponse(status));
81
+ process.exitCode = 1;
82
+ return true;
83
+ };
84
+
71
85
  class InitCommand extends ProteumCommand {
72
86
  public static paths = [['init']];
73
87
 
@@ -156,26 +170,35 @@ class ConfigureCommand extends ProteumCommand {
156
170
  }
157
171
  }
158
172
 
159
- class MigrateCommand extends ProteumCommand {
160
- public static paths = [['migrate']];
173
+ class WorktreeCommand extends ProteumCommand {
174
+ public static paths = [['worktree']];
161
175
 
162
- public static usage = buildUsage('migrate');
176
+ public static usage = buildUsage('worktree');
163
177
 
164
- public cwd = Option.String('--cwd', { description: 'Run the migration against another Proteum app root.' });
165
- public dryRun = Option.Boolean('--dry-run', false, { description: 'Print the rewrite plan without writing files.' });
166
- public json = Option.Boolean('--json', false, { description: 'Print machine-readable migration output.' });
178
+ public source = Option.String('--source', { description: 'Source Proteum app root used for .env copy and worktree creation.' });
179
+ public branch = Option.String('--branch', { description: 'Branch name created by `worktree create`.' });
180
+ public base = Option.String('--base', { description: 'Base ref used by `worktree create`.' });
181
+ public refresh = Option.Boolean('--refresh', false, { description: 'Refresh an existing stale bootstrap marker.' });
182
+ public skipDeps = Option.Boolean('--skip-deps', false, { description: 'Skip dependency install while recording an explicit reason.' });
183
+ public reason = Option.String('--reason', { description: 'Reason required when --skip-deps is used.' });
184
+ public json = Option.Boolean('--json', false, { description: 'Print machine-readable worktree bootstrap output.' });
167
185
  public args = Option.Rest();
168
186
 
169
187
  public async execute() {
170
- const [action = ''] = this.args;
188
+ const [action = '', target = ''] = this.args;
171
189
 
172
190
  this.setCliArgs({
173
191
  action,
174
- workdir: this.cwd ?? '',
175
- dryRun: this.dryRun,
192
+ base: this.base ?? '',
193
+ branch: this.branch ?? '',
176
194
  json: this.json,
195
+ reason: this.reason ?? '',
196
+ refresh: this.refresh,
197
+ skipDeps: this.skipDeps,
198
+ source: this.source ?? '',
199
+ target,
177
200
  });
178
- await runCommandModule(() => import('../commands/migrate'));
201
+ await runCommandModule(() => import('../commands/worktree'));
179
202
  }
180
203
  }
181
204
 
@@ -222,6 +245,7 @@ class DevCommand extends ProteumCommand {
222
245
  printNonAppRootResponse({ commandName: 'dev', cwd: String(cli.args.workdir || process.cwd()) });
223
246
  return 1;
224
247
  }
248
+ if (action !== 'stop' && blockIfWorktreeBootstrapRequired()) return 1;
225
249
  await runCommandModule(() => import('../commands/dev'));
226
250
  }
227
251
  }
@@ -236,6 +260,7 @@ class RefreshCommand extends ProteumCommand {
236
260
  public async execute() {
237
261
  assertNoLegacyArgs('refresh', this.legacyArgs);
238
262
  this.setCliArgs();
263
+ if (blockIfWorktreeBootstrapRequired()) return 1;
239
264
  await runCommandModule(() => import('../commands/refresh'));
240
265
  }
241
266
  }
@@ -776,6 +801,7 @@ class RuntimeCommand extends ProteumCommand {
776
801
  return 1;
777
802
  }
778
803
 
804
+ if (blockIfWorktreeBootstrapRequired()) return 1;
779
805
  await runCommandModule(() => import('../commands/runtime'));
780
806
  }
781
807
  }
@@ -869,6 +895,7 @@ class VerifyCommand extends ProteumCommand {
869
895
  websitePort: this.websitePort ?? '',
870
896
  });
871
897
 
898
+ if (blockIfWorktreeBootstrapRequired()) return 1;
872
899
  await runCommandModule(() => import('../commands/verify'));
873
900
  }
874
901
  }
@@ -877,7 +904,7 @@ export const registeredCommands = {
877
904
  init: InitCommand,
878
905
  create: CreateCommand,
879
906
  configure: ConfigureCommand,
880
- migrate: MigrateCommand,
907
+ worktree: WorktreeCommand,
881
908
  dev: DevCommand,
882
909
  refresh: RefreshCommand,
883
910
  build: BuildCommand,
@@ -913,7 +940,7 @@ export const createCli = (version: string) => {
913
940
  clipanion.register(InitCommand);
914
941
  clipanion.register(CreateCommand);
915
942
  clipanion.register(ConfigureCommand);
916
- clipanion.register(MigrateCommand);
943
+ clipanion.register(WorktreeCommand);
917
944
  clipanion.register(DevCommand);
918
945
  clipanion.register(RefreshCommand);
919
946
  clipanion.register(BuildCommand);