proteum 2.5.9 → 2.5.11

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 (41) hide show
  1. package/AGENTS.md +4 -4
  2. package/agents/project/AGENTS.md +131 -91
  3. package/agents/project/CODING_STYLE.md +69 -40
  4. package/agents/project/DOCUMENTATION.md +19 -2
  5. package/agents/project/client/AGENTS.md +0 -1
  6. package/agents/project/diagnostics.md +6 -5
  7. package/agents/project/optimizations.md +1 -7
  8. package/agents/project/server/services/AGENTS.md +1 -3
  9. package/agents/project/tests/AGENTS.md +3 -3
  10. package/cli/commands/docs.ts +223 -0
  11. package/cli/commands/session.ts +36 -5
  12. package/cli/commands/verify.ts +6 -1
  13. package/cli/compiler/client/index.ts +11 -4
  14. package/cli/compiler/common/uiSingletons.ts +76 -0
  15. package/cli/compiler/server/index.ts +34 -12
  16. package/cli/presentation/commands.ts +20 -1
  17. package/cli/runtime/commands.ts +20 -0
  18. package/cli/scaffold/index.ts +3 -0
  19. package/cli/scaffold/templates.ts +62 -6
  20. package/cli/utils/agents.ts +2 -2
  21. package/cli/verification/changed.ts +21 -0
  22. package/client/dev/profiler/index.tsx +761 -455
  23. package/common/dev/mcpPayloads.ts +86 -8
  24. package/common/dev/session.ts +32 -0
  25. package/common/errors/index.tsx +0 -1
  26. package/docAnchors.js +135 -0
  27. package/docs/agent-routing.md +2 -2
  28. package/eslint.js +264 -1
  29. package/package.json +1 -1
  30. package/server/app/container/console/index.ts +0 -17
  31. package/server/services/router/http/index.ts +130 -33
  32. package/tests/agents-utils.test.cjs +0 -4
  33. package/tests/dev-session-login-url.test.cjs +33 -0
  34. package/tests/doc-anchors.test.cjs +115 -0
  35. package/tests/docs-check.test.cjs +138 -0
  36. package/tests/eslint-rules.test.cjs +235 -2
  37. package/tests/mcp.test.cjs +109 -0
  38. package/tests/ui-singletons.test.cjs +104 -0
  39. package/tests/verify-changed.test.cjs +51 -3
  40. package/agents/project/app-root/AGENTS.md +0 -14
  41. package/agents/project/root/AGENTS.md +0 -399
@@ -10,6 +10,11 @@ import { type Configuration } from '@rspack/core';
10
10
  import cli from '@cli';
11
11
  import createCommonConfig, { TCompileMode, TCompileOutputTarget, regex } from '../common';
12
12
  import { toRspackAliases } from '../common/rspackAliases';
13
+ import {
14
+ isUiSingletonRequest,
15
+ resolveUiSingletonAliases,
16
+ resolveUiSingletonServerExternalRequest,
17
+ } from '../common/uiSingletons';
13
18
  import { resolveServerExternalRequest } from './externals';
14
19
 
15
20
  // Type
@@ -51,6 +56,9 @@ const getDevGeneratedRuntimeEntries = (app: App) => ({
51
56
  __proteum_dev_routes: [app.paths.server.generated + '/routes.ts'],
52
57
  __proteum_dev_controllers: [app.paths.server.generated + '/controllers.ts'],
53
58
  });
59
+ const resolveFromAppOrCore = (request: string) => cli.paths.resolveRequest(request, { preferApp: true });
60
+ const resolvePackageRootFromAppOrCore = (packageName: string) =>
61
+ cli.paths.resolvePackageRoot(packageName, { preferApp: true });
54
62
  const normalizeModulePath = (value?: string) => (value || '').replace(/\\/g, '/');
55
63
  const getFrameworkSourceRoot = () => {
56
64
  const installedCoreRoot = cli.paths.framework.installedRoot
@@ -120,6 +128,13 @@ export default function createCompiler(
120
128
  const rspackAliases = toRspackAliases(resolvedAliases);
121
129
  rspackAliases['proteum'] = frameworkSourceRoot;
122
130
  rspackAliases['@/client/router$'] = frameworkSourceRoot + '/client/router.ts';
131
+ Object.assign(
132
+ rspackAliases,
133
+ resolveUiSingletonAliases({
134
+ resolvePackageRoot: resolvePackageRootFromAppOrCore,
135
+ resolveRequest: resolveFromAppOrCore,
136
+ }),
137
+ );
123
138
 
124
139
  debug &&
125
140
  console.log(
@@ -160,20 +175,27 @@ export default function createCompiler(
160
175
 
161
176
  // node_modules
162
177
  function ({ context, request }, callback) {
178
+ if (request === undefined) return callback();
179
+
180
+ const uiSingletonExternalRequest = resolveUiSingletonServerExternalRequest(request, resolveFromAppOrCore);
181
+ if (uiSingletonExternalRequest !== undefined) {
182
+ return callback(undefined, 'commonjs ' + uiSingletonExternalRequest);
183
+ }
184
+
163
185
  const shouldCompile =
164
- request !== undefined &&
165
186
  // Local files
166
- (request[0] === '.' ||
167
- request[0] === '/' ||
168
- // Aliased modules
169
- app.aliases.server.containsAlias(request) ||
170
- // TODO: proteum.conf: compile: include
171
- app.isTranspileModuleRequest(request) ||
172
- // Compile proteum modules
173
- request.startsWith('proteum') ||
174
- // React-based UI packages must pass through the alias layer on the server,
175
- // otherwise SSR can mix real React packages with the Preact compat runtime.
176
- serverReactCompatCompilePrefixes.some((prefix) => request.startsWith(prefix)));
187
+ request[0] === '.' ||
188
+ request[0] === '/' ||
189
+ // Aliased modules
190
+ app.aliases.server.containsAlias(request) ||
191
+ // TODO: proteum.conf: compile: include
192
+ app.isTranspileModuleRequest(request) ||
193
+ // Compile proteum modules
194
+ request.startsWith('proteum') ||
195
+ // React/Preact singleton packages and React-based UI packages must pass through the alias layer on the server,
196
+ // otherwise SSR can mix real React packages with the Preact compat runtime.
197
+ isUiSingletonRequest(request) ||
198
+ serverReactCompatCompilePrefixes.some((prefix) => request.startsWith(prefix));
177
199
 
178
200
  //console.log('isNodeModule', request, isNodeModule);
179
201
 
@@ -14,6 +14,7 @@ export const proteumCommandNames = [
14
14
  'typecheck',
15
15
  'lint',
16
16
  'check',
17
+ 'docs',
17
18
  'e2e',
18
19
  'connect',
19
20
  'doctor',
@@ -301,6 +302,19 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
301
302
  notes: ['This command executes refresh, typecheck, then lint in that order.', 'From a monorepo wrapper root, check runs once per discovered Proteum app.'],
302
303
  status: 'stable',
303
304
  },
305
+ docs: {
306
+ name: 'docs',
307
+ category: 'Quality gates',
308
+ summary: 'Check that code and documentation still point at each other.',
309
+ usage: 'proteum docs check',
310
+ bestFor: 'Confirming doc anchors resolve and that fix notes and feature packs are reachable from code.',
311
+ examples: [{ description: 'Check every doc anchor in the repository', command: 'proteum docs check' }],
312
+ notes: [
313
+ 'Only an anchor that no longer resolves fails the command; missing coverage is reported as a backlog.',
314
+ 'Anchors are the `@docs`, `@adr`, `@fix`, and `@rule` tags described in `CODING_STYLE.md`.',
315
+ ],
316
+ status: 'stable',
317
+ },
304
318
  e2e: {
305
319
  name: 'e2e',
306
320
  category: 'Quality gates',
@@ -594,7 +608,7 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
594
608
  name: 'session',
595
609
  category: 'Manifest and contracts',
596
610
  summary: 'Mint a dev-only auth session token and cookie payload for a known user.',
597
- usage: 'proteum session <email> [--role <role>] [--port <port>|--url <baseUrl>] [--json]',
611
+ usage: 'proteum session <email> [--role <role>] [--redirect <path>] [--port <port>|--url <baseUrl>] [--json]',
598
612
  bestFor:
599
613
  'Starting browser or API automation from an authenticated state without driving the login UI, while still using the app-configured auth service.',
600
614
  examples: [
@@ -606,11 +620,16 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
606
620
  description: 'Mint a GOD session for unique.domains and print machine-readable cookie data',
607
621
  command: 'proteum session god@example.com --role GOD --json',
608
622
  },
623
+ {
624
+ description: 'Print a browser login URL that sets the cookie and opens a protected page',
625
+ command: 'proteum session admin@example.com --port 3101 --redirect /admin --json',
626
+ },
609
627
  ],
610
628
  notes: [
611
629
  'Sessions are available only in dev mode and use the auth service registered on the current app router.',
612
630
  'You must provide the target user email explicitly; Proteum does not guess your admin account universally across apps.',
613
631
  'The command returns a token plus Playwright-ready cookie JSON so agents can inject the session into a browser context directly.',
632
+ 'The command also returns a browser login URL that works on localhost dev servers, sets the same session cookie, and redirects to a local path.',
614
633
  'Without `--port` or `--url`, Proteum refreshes generated artifacts, builds the dev output, starts a temporary local dev server, creates the session, prints the payload, and exits.',
615
634
  ],
616
635
  status: 'experimental',
@@ -352,6 +352,22 @@ class CheckCommand extends ProteumCommand {
352
352
  }
353
353
  }
354
354
 
355
+ class DocsCommand extends ProteumCommand {
356
+ public static paths = [['docs']];
357
+
358
+ public static usage = buildUsage('docs');
359
+
360
+ public args = Option.Rest();
361
+
362
+ public async execute() {
363
+ const [action = '', ...restArgs] = this.args;
364
+
365
+ assertNoLegacyArgs('docs', restArgs);
366
+ this.setCliArgs({ action });
367
+ await runCommandModule(() => import('../commands/docs'));
368
+ }
369
+ }
370
+
355
371
  class E2eCommand extends ProteumCommand {
356
372
  public static paths = [['e2e']];
357
373
 
@@ -617,6 +633,7 @@ class SessionCommand extends ProteumCommand {
617
633
  public role = Option.String('--role', { description: 'Require the resolved user to have the given role.' });
618
634
  public port = Option.String('--port', { description: 'Target an existing dev server on the given port.' });
619
635
  public url = Option.String('--url', { description: 'Target an existing dev server at the given base URL.' });
636
+ public redirect = Option.String('--redirect', { description: 'Local path used by the browser login URL.' });
620
637
  public json = Option.Boolean('--json', false, { description: 'Print JSON output.' });
621
638
  public args = Option.Rest();
622
639
 
@@ -628,6 +645,7 @@ class SessionCommand extends ProteumCommand {
628
645
  role: this.role ?? '',
629
646
  port: this.port ?? '',
630
647
  url: this.url ?? '',
648
+ redirect: this.redirect ?? '',
631
649
  json: this.json,
632
650
  });
633
651
 
@@ -917,6 +935,7 @@ export const registeredCommands = {
917
935
  typecheck: TypecheckCommand,
918
936
  lint: LintCommand,
919
937
  check: CheckCommand,
938
+ docs: DocsCommand,
920
939
  e2e: E2eCommand,
921
940
  connect: ConnectCommand,
922
941
  doctor: DoctorCommand,
@@ -953,6 +972,7 @@ export const createCli = (version: string) => {
953
972
  clipanion.register(TypecheckCommand);
954
973
  clipanion.register(LintCommand);
955
974
  clipanion.register(CheckCommand);
975
+ clipanion.register(DocsCommand);
956
976
  clipanion.register(E2eCommand);
957
977
  clipanion.register(ConnectCommand);
958
978
  clipanion.register(DoctorCommand);
@@ -556,6 +556,9 @@ export const runCreateScaffold = async () => {
556
556
 
557
557
  result.notes.push(...plan.notes);
558
558
  result.nextSteps.push(...plan.nextSteps);
559
+ result.nextSteps.push(
560
+ 'Adapt the generated code to the real feature and record non-obvious decisions as why-comments per `CODING_STYLE.md` (`Decision and context comments`).',
561
+ );
559
562
  printResult(result);
560
563
  };
561
564
 
@@ -22,7 +22,15 @@ export const createPageTemplate = ({
22
22
  routePath: string;
23
23
  heading: string;
24
24
  message: string;
25
- }) => `import { definePageRoute } from '@common/router/definitions';
25
+ }) => `/*----------------------------------
26
+ - DEPENDANCES
27
+ ----------------------------------*/
28
+
29
+ import { definePageRoute } from '@common/router/definitions';
30
+
31
+ /*----------------------------------
32
+ - PAGE
33
+ ----------------------------------*/
26
34
 
27
35
  export default definePageRoute({
28
36
  path: ${JSON.stringify(routePath)},
@@ -53,7 +61,15 @@ export const createControllerTemplate = ({
53
61
  appIdentifier: string;
54
62
  className: string;
55
63
  methodName: string;
56
- }) => `import { defineAction, defineController } from '@generated/server/controller';
64
+ }) => `/*----------------------------------
65
+ - DEPENDANCES
66
+ ----------------------------------*/
67
+
68
+ import { defineAction, defineController } from '@generated/server/controller';
69
+
70
+ /*----------------------------------
71
+ - CONTROLEUR
72
+ ----------------------------------*/
57
73
 
58
74
  export default defineController({
59
75
  actions: {
@@ -74,11 +90,23 @@ export const createCommandTemplate = ({
74
90
  }: {
75
91
  className: string;
76
92
  methodName: string;
77
- }) => `import { Commands } from '@server/app/commands';
93
+ }) => `/*----------------------------------
94
+ - DEPENDANCES
95
+ ----------------------------------*/
96
+
97
+ import { Commands } from '@server/app/commands';
78
98
  import type AppApplication from '@/server/index';
79
99
 
100
+ /*----------------------------------
101
+ - TYPES
102
+ ----------------------------------*/
103
+
80
104
  type App = InstanceType<typeof AppApplication>;
81
105
 
106
+ /*----------------------------------
107
+ - COMMANDS
108
+ ----------------------------------*/
109
+
82
110
  export default class ${className} extends Commands<App> {
83
111
  public async ${methodName}() {
84
112
  return {
@@ -95,7 +123,15 @@ export const createRouteTemplate = ({
95
123
  }: {
96
124
  httpMethod: string;
97
125
  routePath: string;
98
- }) => `import { defineServerRoute } from '@common/router/definitions';
126
+ }) => `/*----------------------------------
127
+ - DEPENDANCES
128
+ ----------------------------------*/
129
+
130
+ import { defineServerRoute } from '@common/router/definitions';
131
+
132
+ /*----------------------------------
133
+ - ROUTES
134
+ ----------------------------------*/
99
135
 
100
136
  export default defineServerRoute({
101
137
  method: ${JSON.stringify(httpMethod.toUpperCase())},
@@ -115,12 +151,24 @@ export const createServiceTemplate = ({
115
151
  }: {
116
152
  appIdentifier: string;
117
153
  className: string;
118
- }) => `import Service from '@server/app/service';
154
+ }) => `/*----------------------------------
155
+ - DEPENDANCES
156
+ ----------------------------------*/
157
+
158
+ import Service from '@server/app/service';
159
+
160
+ /*----------------------------------
161
+ - TYPES
162
+ ----------------------------------*/
119
163
 
120
164
  export type Config = {
121
165
  debug?: boolean;
122
166
  };
123
167
 
168
+ /*----------------------------------
169
+ - SERVICE
170
+ ----------------------------------*/
171
+
124
172
  export default class ${className} extends Service<Config, {}, ${appIdentifier}, ${appIdentifier}> {
125
173
  public async health() {
126
174
  return {
@@ -138,9 +186,17 @@ export const createServiceConfigTemplate = ({
138
186
  configExportName: string;
139
187
  serviceImportPath: string;
140
188
  serviceImportName: string;
141
- }) => `import { Services } from '@server/app';
189
+ }) => `/*----------------------------------
190
+ - DEPENDANCES
191
+ ----------------------------------*/
192
+
193
+ import { Services } from '@server/app';
142
194
  import ${serviceImportName} from ${JSON.stringify(serviceImportPath)};
143
195
 
196
+ /*----------------------------------
197
+ - CONFIG
198
+ ----------------------------------*/
199
+
144
200
  export const ${configExportName} = Services.config(${serviceImportName}, {});
145
201
  `;
146
202
 
@@ -1053,11 +1053,11 @@ function renderEmbeddedProjectInstructions({
1053
1053
  '- Worktree Preflight (`cwd` inside `/.codex/worktrees/`, newly created Proteum worktree, or before editing in a Codex worktree): read Root contract fallback, run `npx proteum worktree init --source <source-app-root>` when the bootstrap marker is missing, run `npx proteum worktree init --source <source-app-root> --refresh` when Proteum reports stale bootstrap state, use `--skip-deps --reason "..."` only for intentional dependency skips, then run `npx proteum runtime status`; for runtime-visible work start or reuse one tracked `npx proteum dev` session using the Task Lifecycle launch workflow.',
1054
1054
  '- Git lifecycle (`commit`, `and commit`, `stage`, `push`, `PR`, pull request): read Root contract fallback before any git write.',
1055
1055
  '- Before git writes after a bug fix, behavior change, decision change, or docs-relevant production change: read `DOCUMENTATION.md` and verify required docs, fix notes, or ADRs were updated or explicitly skipped with a reason.',
1056
- '- Before finishing production code changes: read Root contract fallback, `DOCUMENTATION.md`, `CODING_STYLE.md`, `tests/AGENTS.md`, and any touched area `AGENTS.md`.',
1056
+ '- Before finishing production code changes: read Root contract fallback, `DOCUMENTATION.md`, `CODING_STYLE.md`, `tests/AGENTS.md`, and any touched area `AGENTS.md`. Run the `CODING_STYLE.md` self-check on the final diff; a non-obvious decision, workaround, or bug fix without a why-comment is a defect to fix before finishing.',
1057
1057
  '- Runtime-visible, request-time, router, SSR, browser, or controller behavior: read Root contract fallback and `diagnostics.md` for verification routing.',
1058
1058
  '- Bug fixes, regressions, incidents, broken public routes, auth/OAuth failures, integration failures, or production behavior fixes: read `DOCUMENTATION.md` before editing and update the relevant fix/regression docs when required.',
1059
1059
  '- Non-trivial feature, product, business-rule, UX, copy, or docs changes: read `DOCUMENTATION.md` before editing.',
1060
- '- Implementation edits: read `CODING_STYLE.md` before editing, plus the matching area file from the routing table.',
1060
+ '- Implementation edits: read `CODING_STYLE.md` before editing, plus the matching area file from the routing table. While editing, record non-obvious decisions, workarounds, and constraints as why-comments per its `Decision and context comments` section.',
1061
1061
  '',
1062
1062
  '## Routing Table',
1063
1063
  '',
@@ -144,6 +144,13 @@ const isDocsOnlyFile = (filepath: string) =>
144
144
  filepath.startsWith('agents/') ||
145
145
  docsOnlyExtensions.has(path.extname(filepath));
146
146
 
147
+ /**
148
+ * A change that can break the code-to-documentation link: either the corpus
149
+ * moved under the anchors, or a source file that may carry anchors changed.
150
+ */
151
+ const isDocAnchorRelevantFile = (filepath: string) =>
152
+ filepath.startsWith('docs/') || isRelatedSourceFile(filepath);
153
+
147
154
  const normalizeGlob = (glob: string) => normalizePath(glob.trim());
148
155
 
149
156
  const escapeRegExp = (value: string) => value.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
@@ -350,6 +357,20 @@ export const buildChangedVerificationPlan = ({
350
357
  if (check) addCheck({ check, selectedChecks });
351
358
  }
352
359
 
360
+ const docAnchorFiles = files.filter(isDocAnchorRelevantFile);
361
+ if (docAnchorFiles.length > 0) {
362
+ const check = createSuiteCheck({
363
+ configRoot: gitRoot,
364
+ files: docAnchorFiles,
365
+ id: 'builtin:doc-anchors',
366
+ reason: 'Changed docs or source files should keep doc anchors resolvable.',
367
+ scope: 'static',
368
+ source: 'builtin',
369
+ suite: 'npx proteum docs check',
370
+ });
371
+ if (check) addCheck({ check, selectedChecks });
372
+ }
373
+
353
374
  for (const rule of config.rules || []) {
354
375
  const matchedFiles = matchFiles(files, rule.match);
355
376
  if (matchedFiles.length === 0) continue;