openxiangda-cli 2.0.0-alpha.86 → 2.0.0-alpha.90

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 (78) hide show
  1. package/README.md +2 -2
  2. package/bin/run.js +1 -0
  3. package/dist/commands/skill.d.ts +26 -0
  4. package/dist/commands/skill.d.ts.map +1 -0
  5. package/dist/commands/skill.js +43 -0
  6. package/dist/commands/skill.js.map +1 -0
  7. package/dist/create-workspace.d.ts.map +1 -1
  8. package/dist/create-workspace.js +3 -3
  9. package/dist/create-workspace.js.map +1 -1
  10. package/dist/toolchain-capsule.d.ts.map +1 -1
  11. package/dist/toolchain-capsule.js +4 -8
  12. package/dist/toolchain-capsule.js.map +1 -1
  13. package/package.json +6 -7
  14. package/template/AGENTS.md +2 -1
  15. package/template/README.md +5 -5
  16. package/template/apps/server/package.json +1 -1
  17. package/template/apps/server/src/app.module.ts +14 -1
  18. package/template/apps/server/src/main.ts +1 -1
  19. package/template/apps/server/test/smoke.test.ts +3 -1
  20. package/template/apps/web/e2e/client-fixture.ts +5 -0
  21. package/template/apps/web/e2e/data-api-live-fixture.tsx +19 -14
  22. package/template/apps/web/e2e/field-protocol-fixture.tsx +87 -20
  23. package/template/apps/web/e2e/field-protocol.spec.ts +86 -0
  24. package/template/apps/web/e2e/resources.spec.ts +20 -181
  25. package/template/apps/web/field-protocol.e2e.html +3 -0
  26. package/template/apps/web/package.json +2 -5
  27. package/template/apps/web/src/main.tsx +12 -60
  28. package/template/apps/web/test/contracts.test.ts +52 -104
  29. package/template/openxiangda.config.ts +2 -2
  30. package/template/package.json +1 -3
  31. package/template/packages/contracts/src/generated.ts +27 -0
  32. package/template/scripts/verify-template-budget.mjs +2 -2
  33. package/template/apps/web/src/AuthoritativeSelector.tsx +0 -661
  34. package/template/apps/web/src/FilePreviewPage.tsx +0 -324
  35. package/template/apps/web/src/RoleSubjectSelect.tsx +0 -128
  36. package/template/apps/web/src/Shell.tsx +0 -445
  37. package/template/apps/web/src/appearance.tsx +0 -147
  38. package/template/apps/web/src/components/platform-fields/AddressField.tsx +0 -338
  39. package/template/apps/web/src/components/platform-fields/AttachmentFileList.tsx +0 -245
  40. package/template/apps/web/src/components/platform-fields/CascadeField.tsx +0 -49
  41. package/template/apps/web/src/components/platform-fields/DateTimeField.tsx +0 -148
  42. package/template/apps/web/src/components/platform-fields/JsonField.tsx +0 -77
  43. package/template/apps/web/src/components/platform-fields/LocationField.tsx +0 -164
  44. package/template/apps/web/src/components/platform-fields/PlatformDirectoryPicker.tsx +0 -673
  45. package/template/apps/web/src/components/platform-fields/ResourceReferenceField.tsx +0 -67
  46. package/template/apps/web/src/components/platform-fields/RichTextField.tsx +0 -196
  47. package/template/apps/web/src/components/platform-fields/SignatureField.tsx +0 -315
  48. package/template/apps/web/src/components/platform-fields/SubtableField.tsx +0 -553
  49. package/template/apps/web/src/components/platform-fields/address-value.ts +0 -80
  50. package/template/apps/web/src/components/platform-fields/cascade-value.ts +0 -66
  51. package/template/apps/web/src/components/platform-fields/directory-value.ts +0 -53
  52. package/template/apps/web/src/components/platform-fields/field-form-codec.ts +0 -98
  53. package/template/apps/web/src/components/platform-fields/location-value.ts +0 -87
  54. package/template/apps/web/src/components/platform-fields/resource-query.ts +0 -131
  55. package/template/apps/web/src/components/platform-fields/rich-text-value.ts +0 -59
  56. package/template/apps/web/src/components/platform-fields/subtable-value.ts +0 -187
  57. package/template/apps/web/src/components/resource/GeneratedResourceCrud.tsx +0 -1184
  58. package/template/apps/web/src/components/resource/ResourceBatchActions.tsx +0 -326
  59. package/template/apps/web/src/components/resource/StandardResourcePages.tsx +0 -392
  60. package/template/apps/web/src/components/resource/SurfaceFields.tsx +0 -915
  61. package/template/apps/web/src/components/resource/generated-resource-definition.ts +0 -18
  62. package/template/apps/web/src/components/resource/resource-import.ts +0 -205
  63. package/template/apps/web/src/data-provider.ts +0 -72
  64. package/template/apps/web/src/platform-client.ts +0 -708
  65. package/template/apps/web/src/runtime-meta.ts +0 -75
  66. package/template/apps/web/src/runtime.tsx +0 -227
  67. package/template/apps/web/src/styles.css +0 -1941
  68. package/template/apps/web/test/address-field.test.ts +0 -66
  69. package/template/apps/web/test/cascade-value.test.ts +0 -68
  70. package/template/apps/web/test/directory-value.test.ts +0 -55
  71. package/template/apps/web/test/field-bounds.test.ts +0 -17
  72. package/template/apps/web/test/field-form-codec.test.ts +0 -85
  73. package/template/apps/web/test/location-field.test.ts +0 -83
  74. package/template/apps/web/test/resource-import.test.ts +0 -143
  75. package/template/apps/web/test/resource-query.test.ts +0 -144
  76. package/template/apps/web/test/rich-json-field.test.ts +0 -53
  77. package/template/apps/web/test/signature-serial-field.test.ts +0 -40
  78. package/template/apps/web/test/subtable-value.test.ts +0 -144
@@ -1,67 +1,19 @@
1
- import { Refine } from '@refinedev/core';
2
1
  import React from 'react';
3
2
  import ReactDOM from 'react-dom/client';
4
- import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
5
- import { resourceDefinitions } from '../../../packages/contracts/src/generated.js';
6
- import { applicationProvider } from './data-provider';
7
- import { AppearanceProvider } from './appearance';
8
- import { GeneratedResourcePage } from './components/resource/GeneratedResourceCrud';
9
- import { FilePreviewPage } from './FilePreviewPage';
10
- import { EmptyApplicationPage } from './Shell';
11
- import { RuntimeBoundary } from './runtime';
12
- import { applicationBasename } from './runtime-meta';
13
- import { useGlobalRequestLoading } from './platform-client';
14
- import './styles.css';
15
-
16
- const codes = Object.keys(resourceDefinitions);
17
- const firstPath = codes[0] ? `/${codes[0]}` : '/';
18
- const adminEntry = codes.length ? (
19
- <Navigate replace to={firstPath} />
20
- ) : (
21
- <EmptyApplicationPage />
22
- );
23
-
24
- function GlobalRequestLoading() {
25
- const loading = useGlobalRequestLoading();
26
- return loading ? (
27
- <div
28
- aria-label="页面请求进行中"
29
- aria-valuetext="正在加载"
30
- className="oxa-global-request-loading"
31
- role="progressbar"
32
- >
33
- <span />
34
- </div>
35
- ) : null;
36
- }
37
-
38
- const resourceRoutes = codes.flatMap(resourceCode => [
39
- <Route key={`${resourceCode}-list`} path={`/${resourceCode}`} element={<GeneratedResourcePage resourceCode={resourceCode} />} />,
40
- <Route key={`${resourceCode}-new`} path={`/${resourceCode}/new`} element={<GeneratedResourcePage mode="create" resourceCode={resourceCode} />} />,
41
- <Route key={`${resourceCode}-detail`} path={`/${resourceCode}/:id`} element={<GeneratedResourcePage mode="detail" resourceCode={resourceCode} />} />,
42
- <Route key={`${resourceCode}-edit`} path={`/${resourceCode}/:id/edit`} element={<GeneratedResourcePage mode="edit" resourceCode={resourceCode} />} />,
43
- <Route key={`${resourceCode}-mobile-list`} path={`/m/${resourceCode}`} element={<GeneratedResourcePage resourceCode={resourceCode} variant="mobile" />} />,
44
- <Route key={`${resourceCode}-mobile-new`} path={`/m/${resourceCode}/new`} element={<GeneratedResourcePage mode="create" resourceCode={resourceCode} variant="mobile" />} />,
45
- <Route key={`${resourceCode}-mobile-detail`} path={`/m/${resourceCode}/:id`} element={<GeneratedResourcePage mode="detail" resourceCode={resourceCode} variant="mobile" />} />,
46
- <Route key={`${resourceCode}-mobile-edit`} path={`/m/${resourceCode}/:id/edit`} element={<GeneratedResourcePage mode="edit" resourceCode={resourceCode} variant="mobile" />} />,
47
- ]);
3
+ import { OpenXiangdaApplication } from 'openxiangda/react';
4
+ import {
5
+ appCode,
6
+ appName,
7
+ resourceDefinitions,
8
+ } from '../../../packages/contracts/src/generated.js';
9
+ import 'openxiangda/react/styles.css';
48
10
 
49
11
  ReactDOM.createRoot(document.getElementById('root')!).render(
50
12
  <React.StrictMode>
51
- <AppearanceProvider>
52
- <GlobalRequestLoading />
53
- <RuntimeBoundary>
54
- <BrowserRouter basename={applicationBasename()}>
55
- <Refine dataProvider={applicationProvider} resources={codes.map(code => ({ name: code, list: `/${code}`, create: `/${code}/new`, edit: `/${code}/:id/edit`, show: `/${code}/:id` }))}>
56
- <Routes>
57
- <Route path="/" element={adminEntry} />
58
- <Route path="/admin" element={adminEntry} />
59
- <Route path="/files/:resourceCode/:fileId/preview" element={<FilePreviewPage />} />
60
- {resourceRoutes}
61
- </Routes>
62
- </Refine>
63
- </BrowserRouter>
64
- </RuntimeBoundary>
65
- </AppearanceProvider>
13
+ <OpenXiangdaApplication
14
+ appCode={appCode}
15
+ appName={appName}
16
+ resourceDefinitions={resourceDefinitions}
17
+ />
66
18
  </React.StrictMode>,
67
19
  );
@@ -1,124 +1,72 @@
1
1
  import assert from 'node:assert/strict';
2
- import { readFileSync } from 'node:fs';
2
+ import { readFileSync, readdirSync } from 'node:fs';
3
3
  import test from 'node:test';
4
- import { resourceDefinitions, resourceCodes } from '../../../packages/contracts/src/generated.js';
5
- import { readRuntimeMount, resolveApplicationBasename } from '../src/runtime-meta';
4
+ import { readRuntimeMount, resolveApplicationBasename } from 'openxiangda/core';
5
+ import {
6
+ resourceCodes,
7
+ resourceDefinitions,
8
+ } from '../../../packages/contracts/src/generated.js';
6
9
 
7
- test('clean template is resource-driven and contains no HGY fixture', () => {
8
- const config = readFileSync(new URL('../../../openxiangda.config.ts', import.meta.url), 'utf8');
10
+ test('clean template contains only application declarations and one React entry', () => {
11
+ const config = readFileSync(
12
+ new URL('../../../openxiangda.config.ts', import.meta.url),
13
+ 'utf8'
14
+ );
9
15
  const source = readFileSync(new URL('../src/main.tsx', import.meta.url), 'utf8');
16
+ const productionSources = readdirSync(new URL('../src', import.meta.url), {
17
+ withFileTypes: true,
18
+ })
19
+ .filter(entry => entry.isFile())
20
+ .map(entry => entry.name)
21
+ .sort();
22
+ assert.deepEqual(productionSources, ['main.tsx']);
10
23
  assert.deepEqual(Object.keys(resourceDefinitions).sort(), [...resourceCodes].sort());
11
24
  assert.match(config, /data:\s*\{\s*resources:/);
12
- assert.match(source, /Object\.keys\(resourceDefinitions\)/);
13
- const legacyMarkers = ['instruments', 'colleges', '仪器' + '资源管理', '学院' + '字典', 'HGY', 'Instrument'];
25
+ assert.match(source, /OpenXiangdaApplication/);
26
+ assert.match(source, /from 'openxiangda\/react'/);
27
+ assert.match(source, /openxiangda\/react\/styles\.css/);
28
+ const legacyMarkers = [
29
+ 'instruments',
30
+ 'colleges',
31
+ '仪器' + '资源管理',
32
+ '学院' + '字典',
33
+ 'HGY',
34
+ 'Instrument',
35
+ ];
14
36
  assert.doesNotMatch(`${config}\n${source}`, new RegExp(legacyMarkers.join('|')));
15
37
  });
16
38
 
17
- test('standard surfaces keep authoritative directory/resource selectors and mobile routes', () => {
18
- const surface = readFileSync(new URL('../src/components/resource/SurfaceFields.tsx', import.meta.url), 'utf8');
19
- const selector = readFileSync(new URL('../src/AuthoritativeSelector.tsx', import.meta.url), 'utf8');
20
- const directoryPicker = readFileSync(new URL('../src/components/platform-fields/PlatformDirectoryPicker.tsx', import.meta.url), 'utf8');
21
- const directoryValue = readFileSync(new URL('../src/components/platform-fields/directory-value.ts', import.meta.url), 'utf8');
22
- const resourceSelector = readFileSync(new URL('../src/components/platform-fields/ResourceReferenceField.tsx', import.meta.url), 'utf8');
23
- const crud = readFileSync(new URL('../src/components/resource/GeneratedResourceCrud.tsx', import.meta.url), 'utf8');
24
- assert.match(surface, /PlatformDirectoryPicker/);
25
- assert.match(resourceSelector, /source="resource"/);
26
- assert.match(resourceSelector, /MobileAuthoritativeSelector/);
27
- assert.match(surface, /oxa-mobile-file-field/);
28
- assert.match(surface, /mobilePending/);
29
- assert.match(surface, /上传中/);
30
- assert.match(surface, /重试/);
31
- assert.match(surface, /AttachmentFileList files=\{refs\}/);
32
- assert.match(selector, /searchResource/);
33
- assert.doesNotMatch(selector, /resolveResource/);
34
- assert.match(selector, /business fields never use this path/);
35
- assert.match(selector, /storedReferenceOptions/);
36
- assert.doesNotMatch(directoryPicker, /resolveDirectory/);
37
- assert.match(directoryValue, /entry\.snapshot/);
38
- assert.match(selector, /ResourceReferenceValue/);
39
- assert.match(selector, /role="dialog"/);
40
- assert.match(selector, /oxa-mobile-reference-sheet/);
41
- assert.match(crud, /MobileResourceListPage/);
42
- assert.match(crud, /fieldWritable/);
43
- assert.match(crud, /exportResourceRows/);
44
- assert.doesNotMatch(crud, /rows\.push|pageSize = 500|csvCell/);
45
- assert.match(crud, /数据读取失败/);
46
- assert.match(crud, /state\.list\.query\.refetch/);
47
- assert.match(crud, /key=\{pageKey\}/);
48
- assert.match(crud, /setQuery\(\{\}\)/);
49
- const client = readFileSync(new URL('../src/platform-client.ts', import.meta.url), 'utf8');
50
- const resourceQuery = readFileSync(new URL('../src/components/platform-fields/resource-query.ts', import.meta.url), 'utf8');
51
- assert.match(client, /declaredFields\.has\(requestedSort\.field\)/);
52
- assert.match(client, /SCHEMA_VERSIONS\.dataFieldSourceQuery/);
53
- assert.match(client, /SCHEMA_VERSIONS\.dataExportRequest/);
54
- assert.match(client, /`\$\{base\}\/export`/);
55
- assert.match(client, /accept', 'text\/csv'/);
56
- assert.match(client, /\/source\/query/);
57
- assert.doesNotMatch(client, /operator:\s*'ilike'|operator:\s*'cs'|filters:/);
58
- assert.match(resourceQuery, /if \(!declaredFields\.has\(fieldCode\)\) continue/);
59
- assert.match(client, /\/api\/auth\/logout/);
60
- assert.match(client, /x-openxiangda-role-session-id/);
61
- assert.match(client, /requestApplicationApi/);
62
- assert.doesNotMatch(client, /\/native\/current-user/);
63
- const runtime = readFileSync(new URL('../src/runtime.tsx', import.meta.url), 'utf8');
64
- assert.match(runtime, /selection_required/);
65
- assert.match(runtime, /identityEpoch/);
66
- assert.doesNotMatch(runtime, /focused_role|authorization-mode/);
67
- });
68
-
69
- test('admin appearance is a bounded Ant Design theme with compact route chrome', () => {
70
- const appearance = readFileSync(new URL('../src/appearance.tsx', import.meta.url), 'utf8');
71
- const main = readFileSync(new URL('../src/main.tsx', import.meta.url), 'utf8');
72
- const shell = readFileSync(new URL('../src/Shell.tsx', import.meta.url), 'utf8');
73
- assert.match(appearance, /'system' \| 'light' \| 'dark'/);
74
- assert.match(appearance, /antdTheme\.darkAlgorithm/);
75
- assert.match(appearance, /antdTheme\.defaultAlgorithm/);
76
- assert.match(appearance, /ConfigProvider/);
77
- assert.match(appearance, /openxiangda\.admin\.appearance/);
78
- assert.match(main, /<AppearanceProvider>/);
79
- assert.match(main, /oxa-global-request-loading/);
80
- assert.match(main, /path="\/admin"/);
81
- assert.match(shell, /openxiangda\.admin\.route-history/);
82
- assert.match(shell, /MAX_HISTORY_ITEMS = 8/);
83
- assert.match(shell, /<Segmented/);
84
- assert.match(shell, /跟随系统/);
85
- assert.match(shell, /退出登录/);
86
- assert.match(shell, /切换应用角色/);
87
- assert.match(shell, /<Header className="oxa-topbar">\s*<Breadcrumb/);
88
- assert.doesNotMatch(shell, /oxa-topbar-page/);
89
- });
90
-
91
- test('standard desktop CRUD exposes compact filters, table tools, and lazy audit history', () => {
92
- const pages = readFileSync(new URL('../src/components/resource/StandardResourcePages.tsx', import.meta.url), 'utf8');
93
- const crud = readFileSync(new URL('../src/components/resource/GeneratedResourceCrud.tsx', import.meta.url), 'utf8');
94
- assert.match(pages, /className="oxa-list-surface"/);
95
- assert.doesNotMatch(pages, /className="oxa-page-heading"/);
96
- assert.match(crud, /filtersExpanded/);
97
- assert.match(crud, /更多筛选/);
98
- assert.match(crud, /columnMenu/);
99
- assert.match(crud, /densityMenu/);
100
- assert.match(crud, /列设置/);
101
- assert.match(crud, /密度/);
102
- assert.match(crud, /ResourceImportButton/);
103
- assert.match(crud, /ResourceBatchActions/);
104
- const batch = readFileSync(new URL('../src/components/resource/ResourceBatchActions.tsx', import.meta.url), 'utf8');
105
- assert.match(batch, /transactNativeData/);
106
- assert.match(batch, /批量修改/);
107
- assert.match(batch, /批量删除/);
108
- assert.match(batch, /确认导入/);
109
- assert.match(crud, /!auditExpanded/);
110
- assert.match(crud, /<Collapse/);
111
- assert.match(crud, /查看创建、更新和删除的字段变化/);
39
+ test('application manifests expose one direct OpenXiangda dependency', () => {
40
+ for (const relative of [
41
+ '../../../package.json',
42
+ '../package.json',
43
+ '../../server/package.json',
44
+ ]) {
45
+ const manifest = JSON.parse(
46
+ readFileSync(new URL(relative, import.meta.url), 'utf8')
47
+ ) as Record<string, Record<string, string>>;
48
+ const dependencies = Object.entries({
49
+ ...(manifest.dependencies || {}),
50
+ ...(manifest.devDependencies || {}),
51
+ ...(manifest.peerDependencies || {}),
52
+ }).filter(([name]) =>
53
+ name === 'openxiangda' || name.startsWith('openxiangda-')
54
+ );
55
+ assert.deepEqual(
56
+ dependencies.map(([name]) => name),
57
+ dependencies.length ? ['openxiangda'] : []
58
+ );
59
+ }
112
60
  });
113
61
 
114
62
  test('runtime metadata stays mount-scoped', () => {
115
63
  const mount = readRuntimeMount(
116
- (name) =>
64
+ name =>
117
65
  ({
118
66
  'openxiangda-runtime-base': '/view/example-app/',
119
67
  'openxiangda-app-code': 'example-app',
120
68
  'openxiangda-environment': 'preproduction',
121
- }[name])
69
+ })[name]
122
70
  );
123
71
  assert.equal(resolveApplicationBasename(mount), '/view/example-app');
124
72
  });
@@ -1,4 +1,4 @@
1
- import { defineOpenXiangdaApp } from 'openxiangda-devkit-core';
1
+ import { defineOpenXiangdaApp } from 'openxiangda/config';
2
2
 
3
3
  /** Clean application scaffold: declare resources and fields, then generate CRUD. */
4
4
  export default defineOpenXiangdaApp({
@@ -25,6 +25,6 @@ export default defineOpenXiangdaApp({
25
25
  authorizationTransitions: [],
26
26
  },
27
27
  data: { resources: [] },
28
- events: { subscriptions: [], timers: [] },
28
+ events: { schemas: [], subscriptions: [], timers: [], dateTriggers: [] },
29
29
  workflows: { definitions: [], bindings: [], activations: [], providers: [], editableParameters: [] },
30
30
  });
@@ -22,9 +22,7 @@
22
22
  "build": "pnpm --recursive build"
23
23
  },
24
24
  "devDependencies": {
25
- "openxiangda-cli": "2.0.0-alpha.86",
26
- "openxiangda-contracts": "2.0.0-alpha.40",
27
- "openxiangda-devkit-core": "2.0.0-alpha.53",
25
+ "openxiangda": "2.0.0-alpha.2",
28
26
  "typescript": "5.9.3"
29
27
  }
30
28
  }
@@ -1,6 +1,7 @@
1
1
  // Generated by OpenXiangda 2.0 Native compiler. Do not edit by hand.
2
2
  export const compilerContractVersion = "native-2" as const;
3
3
  export const appCode = "openxiangda-application" as const;
4
+ export const appName = "OpenXiangda 应用" as const;
4
5
  export const resourceCodes = [] as const;
5
6
  export const resourceSurfaces = {} as const;
6
7
  export const resourceDefinitions = {} as const;
@@ -17,6 +18,13 @@ export const appOperations = {} as const;
17
18
  export const appRoutes = {} as const;
18
19
  export const eventTypes = [] as const;
19
20
  export const eventSubscriptionCodes = [] as const;
21
+ export const eventHandlerManifest = {
22
+ "schemaVersion": "openxiangda.event-handler-manifest/v2",
23
+ "appCode": "openxiangda-application",
24
+ "handlers": []
25
+ } as const;
26
+ export const eventHandlers = {} as const;
27
+ export const eventSchemas = [] as const;
20
28
  export const workflowCodes = [] as const;
21
29
 
22
30
  export type ResourceCode = (typeof resourceCodes)[number];
@@ -25,8 +33,27 @@ export type AppOperation = (typeof appOperations)[keyof typeof appOperations];
25
33
  export type AppRoute = (typeof appRoutes)[keyof typeof appRoutes];
26
34
  export type EventType = (typeof eventTypes)[number];
27
35
  export type EventSubscriptionCode = (typeof eventSubscriptionCodes)[number];
36
+ export type EventTypesForSubscription<TCode extends EventSubscriptionCode> = never;
28
37
  export type WorkflowCode = (typeof workflowCodes)[number];
29
38
 
39
+ export interface OpenXiangdaCloudEvent<TType extends EventType = EventType, TData extends Record<string, unknown> = Record<string, unknown>> {
40
+ specversion: "1.0";
41
+ id: string;
42
+ source: string;
43
+ type: TType;
44
+ subject?: string;
45
+ time: string;
46
+ datacontenttype: "application/json";
47
+ dataschema?: string;
48
+ data: TData;
49
+ tenantid: string;
50
+ appcode: typeof appCode;
51
+ environment: string;
52
+ traceid?: string;
53
+ schemaversion: string;
54
+ }
55
+ export type EventHandlerInput<TCode extends EventSubscriptionCode> = OpenXiangdaCloudEvent<EventTypesForSubscription<TCode>>;
56
+
30
57
  export interface LabeledValue {
31
58
  label: string;
32
59
  value: string;
@@ -20,7 +20,7 @@ function visit(directory) {
20
20
  }
21
21
  visit(root);
22
22
  const bytes = files.reduce((total, file) => total + statSync(file).size, 0);
23
- if (files.length > 80) throw new Error(`TEMPLATE_SOURCE_FILE_BUDGET_EXCEEDED:${files.length}>80`);
23
+ if (files.length > 50) throw new Error(`TEMPLATE_SOURCE_FILE_BUDGET_EXCEEDED:${files.length}>50`);
24
24
  if (bytes > 500_000) throw new Error(`TEMPLATE_SOURCE_BYTE_BUDGET_EXCEEDED:${bytes}>500000`);
25
25
  const productionFiles = files.filter(file => {
26
26
  const name = relative(root, file).replaceAll('\\', '/');
@@ -39,7 +39,7 @@ const agents = readFileSync(resolve(root, 'AGENTS.md'), 'utf8');
39
39
  if (!agents.includes('pnpm openxiangda')) throw new Error('TEMPLATE_AGENT_PINNED_CLI_MISSING');
40
40
  if (
41
41
  !agents.includes("union of that user's application roles") ||
42
- !agents.includes('opaque authorization context')
42
+ !agents.includes('does not transmit a RoleSession header')
43
43
  ) {
44
44
  throw new Error('TEMPLATE_AGENT_ROLE_UNION_BOUNDARY_MISSING');
45
45
  }