@h-ai/cli 0.1.0-alpha.66 → 0.1.0-alpha.68

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 (198) hide show
  1. package/README.en.md +86 -241
  2. package/README.ja.md +86 -241
  3. package/README.md +86 -241
  4. package/dist/{src-RDQ2EIBT.js → chunk-FI56GONY.js} +32 -2669
  5. package/dist/chunk-FI56GONY.js.map +1 -0
  6. package/dist/chunk-MBPB37FR.js +1348 -0
  7. package/dist/chunk-MBPB37FR.js.map +1 -0
  8. package/dist/{chunk-QYX6V7H2.js → chunk-ZFNBUF77.js} +347 -276
  9. package/dist/chunk-ZFNBUF77.js.map +1 -0
  10. package/dist/cli-config-templates-W2LLPMB6.js +5 -0
  11. package/dist/{cli-config-templates-3XSFIXBP.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
  12. package/dist/{cli-skill-templates-TSYEQTCY.js → cli-skill-templates-US5CCAJ4.js} +3 -3
  13. package/dist/{cli-skill-templates-TSYEQTCY.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
  14. package/dist/index.js +1304 -532
  15. package/dist/index.js.map +1 -1
  16. package/dist/metafile-esm.json +1 -0
  17. package/dist/src-DSJJXM2F.js +3849 -0
  18. package/dist/src-DSJJXM2F.js.map +1 -0
  19. package/package.json +9 -8
  20. package/templates/README.md +48 -145
  21. package/templates/apps/admin/messages/ja-JP.json +38 -0
  22. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
  23. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
  24. package/templates/apps/api/.dockerignore.hbs +14 -7
  25. package/templates/apps/api/.gitignore.hbs +7 -0
  26. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
  27. package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
  28. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
  29. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +33 -8
  30. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
  31. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
  32. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -2
  33. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
  34. package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
  35. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
  36. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
  37. package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
  38. package/templates/apps/api/hai.build.json.hbs +1 -1
  39. package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
  40. package/templates/apps/api/package.json.hbs +6 -1
  41. package/templates/apps/api/playwright.config.ts.hbs +15 -7
  42. package/templates/apps/fullstack/.dockerignore.hbs +14 -9
  43. package/templates/apps/fullstack/.gitignore.hbs +7 -0
  44. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +24 -6
  45. package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
  46. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
  47. package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
  48. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
  49. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +2 -2
  50. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +23 -5
  51. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
  52. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
  53. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
  54. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
  55. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -6
  56. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
  57. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +2 -2
  58. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +30 -9
  59. package/templates/apps/fullstack/hai.build.json.hbs +2 -2
  60. package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
  61. package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
  62. package/templates/apps/fullstack/package.json.hbs +3 -1
  63. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
  64. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
  65. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
  66. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
  67. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +32 -8
  68. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
  69. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
  70. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
  71. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
  72. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
  73. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
  74. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
  75. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
  76. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
  77. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
  78. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
  79. package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
  80. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +40 -14
  81. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
  82. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
  83. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +6 -6
  84. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
  85. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +4 -4
  86. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
  87. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +13 -14
  88. package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
  89. package/templates/apps/h5/messages/ja-JP.json +40 -0
  90. package/templates/apps/h5/src/routes/+layout.svelte.hbs +16 -5
  91. package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
  92. package/templates/apps/mobile-app/src/App.svelte.hbs +15 -5
  93. package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
  94. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
  95. package/templates/apps/website/messages/ja-JP.json +32 -0
  96. package/templates/apps/website/src/routes/+layout.svelte.hbs +13 -5
  97. package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
  98. package/templates/base/.dockerignore.hbs +14 -8
  99. package/templates/base/.gitignore.hbs +7 -0
  100. package/templates/base/e2e/app.spec.ts.hbs +23 -8
  101. package/templates/base/hai.build.json.hbs +1 -1
  102. package/templates/base/{Dockerfile.hbs → images/app.Dockerfile.hbs} +19 -17
  103. package/templates/base/package.json.hbs +11 -3
  104. package/templates/base/playwright.config.ts +15 -4
  105. package/templates/base/src/hooks.server.ts.hbs +15 -6
  106. package/templates/base/src/lib/server/init.ts.hbs +139 -30
  107. package/templates/base/vite.config.ts.hbs +5 -3
  108. package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
  109. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
  110. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
  111. package/templates/i18n/project.inlang/settings.json +1 -1
  112. package/templates/shared/.gitattributes +6 -0
  113. package/templates/shared/e2e-env.ts +98 -0
  114. package/templates/shared/run-e2e.mjs +51 -12
  115. package/templates/skills/AGENTS.md +7 -3
  116. package/templates/skills/bridges/admin/AGENTS.md +6 -2
  117. package/templates/skills/bridges/api/AGENTS.md +3 -1
  118. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +5 -1
  119. package/templates/skills/bridges/h5/AGENTS.md +6 -2
  120. package/templates/skills/bridges/mobile-app/AGENTS.md +6 -2
  121. package/templates/skills/bridges/website/AGENTS.md +6 -2
  122. package/templates/skills/hai-ai/SKILL.md +8 -2
  123. package/templates/skills/hai-ai/guide.md +134 -60
  124. package/templates/skills/hai-ai/reference.md +51 -11
  125. package/templates/skills/hai-api-client/SKILL.md +15 -160
  126. package/templates/skills/hai-api-client/reference.md +178 -0
  127. package/templates/skills/hai-api-contract/SKILL.md +16 -273
  128. package/templates/skills/hai-api-contract/reference.md +280 -0
  129. package/templates/skills/hai-app-create/SKILL.md +6 -2
  130. package/templates/skills/hai-app-create/development.md +24 -8
  131. package/templates/skills/hai-app-create/reference.md +7 -5
  132. package/templates/skills/hai-app-review/SKILL.md +7 -3
  133. package/templates/skills/hai-app-review/differential.md +34 -0
  134. package/templates/skills/hai-app-tests/SKILL.md +6 -3
  135. package/templates/skills/hai-app-tests/reference.md +5 -5
  136. package/templates/skills/hai-app-tests/validation.md +38 -0
  137. package/templates/skills/hai-audit/SKILL.md +24 -147
  138. package/templates/skills/hai-audit/reference.md +148 -0
  139. package/templates/skills/hai-build/SKILL.md +14 -8
  140. package/templates/skills/hai-build/reference.md +16 -13
  141. package/templates/skills/hai-cache/SKILL.md +20 -181
  142. package/templates/skills/hai-cache/reference.md +229 -0
  143. package/templates/skills/hai-capacitor/SKILL.md +7 -4
  144. package/templates/skills/hai-capacitor/reference.md +15 -9
  145. package/templates/skills/hai-ci/SKILL.md +2 -0
  146. package/templates/skills/hai-core/SKILL.md +7 -3
  147. package/templates/skills/hai-core/reference.md +25 -5
  148. package/templates/skills/hai-crypto/SKILL.md +22 -250
  149. package/templates/skills/hai-crypto/reference.md +310 -0
  150. package/templates/skills/hai-datapipe/SKILL.md +16 -195
  151. package/templates/skills/hai-datapipe/reference.md +204 -0
  152. package/templates/skills/hai-deploy/SKILL.md +24 -54
  153. package/templates/skills/hai-deploy/middleware.md +51 -43
  154. package/templates/skills/hai-framework-sync/SKILL.md +2 -2
  155. package/templates/skills/hai-framework-sync/sync.md +1 -1
  156. package/templates/skills/hai-iam/SKILL.md +25 -11
  157. package/templates/skills/hai-iam/reference.md +181 -154
  158. package/templates/skills/hai-kit/SKILL.md +35 -174
  159. package/templates/skills/hai-kit/reference.md +190 -0
  160. package/templates/skills/hai-payment/SKILL.md +33 -263
  161. package/templates/skills/hai-payment/reference.md +295 -0
  162. package/templates/skills/hai-pr-review/SKILL.md +21 -88
  163. package/templates/skills/hai-reach/SKILL.md +6 -3
  164. package/templates/skills/hai-reach/reference.md +83 -22
  165. package/templates/skills/hai-reldb/SKILL.md +6 -3
  166. package/templates/skills/hai-reldb/reference.md +68 -25
  167. package/templates/skills/hai-scheduler/SKILL.md +18 -195
  168. package/templates/skills/hai-scheduler/reference.md +216 -0
  169. package/templates/skills/hai-serv/SKILL.md +25 -10
  170. package/templates/skills/hai-serv/reference.md +123 -87
  171. package/templates/skills/hai-spec-implement/SKILL.md +5 -1
  172. package/templates/skills/hai-spec-implement/evaluation.md +18 -0
  173. package/templates/skills/hai-spec-implement/handoff.md +47 -0
  174. package/templates/skills/hai-spec-implement/templates.md +5 -0
  175. package/templates/skills/hai-spec-implement/workflow.md +5 -3
  176. package/templates/skills/hai-spec-review/SKILL.md +4 -0
  177. package/templates/skills/hai-storage/SKILL.md +23 -238
  178. package/templates/skills/hai-storage/reference.md +282 -0
  179. package/templates/skills/hai-ui/SKILL.md +17 -3
  180. package/templates/skills/hai-ui/guide.md +3 -3
  181. package/templates/skills/hai-ui/reference.md +26 -11
  182. package/templates/skills/hai-ui-design/SKILL.md +31 -0
  183. package/templates/skills/hai-ui-design/patterns.md +26 -0
  184. package/templates/skills/hai-ui-review/SKILL.md +30 -0
  185. package/templates/skills/hai-ui-review/checklist.md +22 -0
  186. package/templates/skills/hai-vecdb/SKILL.md +14 -10
  187. package/templates/skills/hai-vecdb/reference.md +33 -3
  188. package/dist/chunk-FWN5B54L.js +0 -468
  189. package/dist/chunk-FWN5B54L.js.map +0 -1
  190. package/dist/chunk-QYX6V7H2.js.map +0 -1
  191. package/dist/cli-config-templates-3XSFIXBP.js +0 -5
  192. package/dist/src-RDQ2EIBT.js.map +0 -1
  193. package/templates/apps/api/Dockerfile.hbs +0 -35
  194. package/templates/apps/fullstack/Dockerfile.hbs +0 -57
  195. /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
  196. /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
  197. /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
  198. /package/templates/base/{docker → images/web}/nginx.conf.hbs +0 -0
@@ -0,0 +1,280 @@
1
+ # hai-api-contract 参考
2
+
3
+ > `@h-ai/api-contract` 是 hai-framework 的公共 HTTP API 契约包,使用 oRPC contract + Zod v4 作为唯一接口真相源。本包只描述接口边界,不包含任何 procedure 实现。
4
+
5
+ ## 运行环境
6
+
7
+ 独立 contract 包发布前验证实际声明构建。`ContractProcedureBuilderWithInputOutput`、`ContractProcedureBuilderWithOutput`、`Schema` 直接复用上游公共类型,由 `@h-ai/api-contract` 类型转出,供推导声明与显式 `import type` 使用;不手写仿造类型或导入 node_modules 内部路径。
8
+
9
+ > ✅ **契约定义包,无服务端业务依赖;Zod/oRPC schema 构造仍有运行时代码。** 可在客户端、服务端、测试环境直接引用。
10
+
11
+ ## 适用场景
12
+
13
+ - 定义新领域的 oRPC contract(输入/输出 Schema + HTTP 路由元数据)
14
+ - 组合多个领域 contract 为应用级 contract
15
+ - 向 `@h-ai/serv` 提供 contract(装配为 HTTP API)
16
+ - 向 `@h-ai/api-client` 提供 contract(生成类型安全客户端)
17
+ - 复用内置领域 contract(iam/storage/ai/payment)
18
+ - 复用公共 Schema 工厂(`apiContract.haiResultSchema`、分页 Schema)
19
+
20
+ ## 使用步骤
21
+
22
+ ### 1. 组合应用级 contract
23
+
24
+ ```typescript
25
+ import { apiContract } from '@h-ai/api-contract'
26
+
27
+ // 按需组合,传 false/undefined 的领域不会出现在 contract 或 OpenAPI spec 中
28
+ export const myContract = apiContract.create({
29
+ iam: apiContract.iam,
30
+ storage: apiContract.storage,
31
+ ai: apiContract.ai,
32
+ payment: false, // 禁用领域
33
+ })
34
+
35
+ // myContract.iam.auth.login
36
+ // myContract.storage.presignedUrls.createUpload
37
+ // myContract.ai.chats.createCompletion
38
+ ```
39
+
40
+ ### 2. 组合自定义 contract
41
+
42
+ ```typescript
43
+ import { apiContract } from '@h-ai/api-contract'
44
+
45
+ export const myContract = apiContract.create({
46
+ iam: apiContract.iam,
47
+ storage: apiContract.storage,
48
+ payment: false, // 禁用领域(不会出现在 contract 或 OpenAPI spec 中)
49
+ })
50
+ ```
51
+
52
+ ### 3. 定义新领域 contract
53
+
54
+ ```typescript
55
+ import { apiContract } from '@h-ai/api-contract'
56
+ import { z } from 'zod'
57
+
58
+ // 输入 Schema
59
+ const WidgetCreateInputSchema = z.object({
60
+ name: z.string().min(1),
61
+ description: z.string().optional(),
62
+ })
63
+
64
+ // 同一 contract 内两个接口复用,因此保留为文件私有常量,不从 schemas 导出。
65
+ const WidgetOutputSchema = apiContract.haiResultSchema(z.object({
66
+ id: z.string(),
67
+ name: z.string(),
68
+ createdAt: z.string(),
69
+ }))
70
+
71
+ // 领域 contract
72
+ export const widgetContract = {
73
+ widget: {
74
+ create: apiContract
75
+ .route({
76
+ method: 'POST',
77
+ path: '/widgets',
78
+ operationId: 'widget.create',
79
+ summary: 'Create widget',
80
+ tags: ['widget'],
81
+ })
82
+ .input(WidgetCreateInputSchema)
83
+ .output(WidgetOutputSchema),
84
+
85
+ getById: apiContract
86
+ .route({
87
+ method: 'GET',
88
+ path: '/widgets/{id}',
89
+ operationId: 'widget.getById',
90
+ summary: 'Get widget by id',
91
+ tags: ['widget'],
92
+ })
93
+ .input(z.object({ id: z.string() }))
94
+ .output(WidgetOutputSchema),
95
+ },
96
+ }
97
+ ```
98
+
99
+ ### 4. 将新 contract 挂入应用级 contract
100
+
101
+ ```typescript
102
+ import { apiContract } from '@h-ai/api-contract'
103
+ import { widgetContract } from './widget-contract.js'
104
+
105
+ export const appContract = apiContract.create({
106
+ iam: apiContract.iam,
107
+ widget: widgetContract,
108
+ })
109
+ ```
110
+
111
+ ### 5. 传给 `@h-ai/serv`(服务端)
112
+
113
+ ```typescript
114
+ import { serv } from '@h-ai/serv'
115
+ import { appContract } from './app-contract.js'
116
+ import { createMyProcedures } from './procedures/widget.js'
117
+
118
+ const app = serv.createApp({
119
+ contract: appContract,
120
+ procedures: {
121
+ iam: createIamProcedures({ iam }),
122
+ widget: createMyProcedures(),
123
+ },
124
+ http: { apiPrefix: '/api/v1' },
125
+ })
126
+ ```
127
+
128
+ ## 核心 API
129
+
130
+ ### `apiContract.create(options)` — 组合领域 contract
131
+
132
+ ```typescript
133
+ import { apiContract } from '@h-ai/api-contract'
134
+
135
+ const contract = apiContract.create({
136
+ iam: apiContract.iam, // 启用:纳入 contract
137
+ storage: false, // 禁用:不出现在 OpenAPI spec 中
138
+ custom: myContract, // 自定义领域
139
+ })
140
+ // 类型自动收窄,被禁用的 key 不出现在 contract 类型中
141
+ ```
142
+
143
+ ### 内置领域 contract
144
+
145
+ | 访问路径 | 领域 | 功能概述 |
146
+ | --- | --- | --- |
147
+ | `apiContract.iam` | IAM | auth(登录/登出/OTP/刷新/注册/改密码)、users、roles、permissions Admin CRUD |
148
+ | `apiContract.storage` | Storage | presignedUrls(上传/下载/批量)、files(元数据查询/删除) |
149
+ | `apiContract.ai` | AI | chats(非流式补全/消息/历史)、memories(召回/列表)、sessions(列表) |
150
+ | `apiContract.payment` | Payment | orders、subscriptions |
151
+
152
+ ### 内置 contract 路径示例
153
+
154
+ ```typescript
155
+ const myContract = apiContract.create({ iam: apiContract.iam, storage: apiContract.storage, ai: apiContract.ai })
156
+
157
+ // IAM
158
+ myContract.iam.auth.login // POST /auth/login
159
+ myContract.iam.auth.logout // POST /auth/logout
160
+ myContract.iam.users.list // GET /iam/users
161
+ myContract.iam.roles.create // POST /iam/roles
162
+
163
+ // Storage
164
+ myContract.storage.presignedUrls.createUpload // POST /storage/presigned-urls/upload
165
+
166
+ // AI
167
+ myContract.ai.chats.createCompletion // POST /ai/chats/completions,非流式 JSON
168
+ ```
169
+
170
+ ### `apiContract.haiResultSchema(dataSchema)` — 标准输出包装
171
+
172
+ 所有 HTTP 输出必须通过 `apiContract.haiResultSchema` 包装为 `HaiResult<T>`,与 `@h-ai/core` 的 `ok()/err()` 返回结构一致:
173
+
174
+ ```typescript
175
+ import { apiContract } from '@h-ai/api-contract'
176
+ import { z } from 'zod'
177
+
178
+ const getWidget = apiContract
179
+ .route({ method: 'GET', path: '/widgets/{id}' })
180
+ .output(apiContract.haiResultSchema(z.object({
181
+ id: z.string(),
182
+ name: z.string(),
183
+ })))
184
+
185
+ // 等价于:
186
+ // { success: true, data: { id: string, name: string } }
187
+ // | { success: false, error: HaiError }
188
+ ```
189
+
190
+ ### `apiContract.pathOf(procedure)` — 读取业务路径
191
+
192
+ ```typescript
193
+ const refreshPath = apiContract.pathOf(apiContract.iam.auth.refresh)
194
+ // '/auth/refresh'
195
+ ```
196
+
197
+ middleware、client 与 E2E 需要业务路径时从 contract 元数据读取,不维护第二份 route 常量。服务端挂载时再与 `_serv.yml` 的 `http.apiPrefix` 拼接。
198
+
199
+ ### 公共 Schema 工具
200
+
201
+ | 访问路径 | 说明 |
202
+ | --- | --- |
203
+ | `apiContract.haiResultSchema(dataSchema)` | `HaiResult<T>` 输出包装 |
204
+ | `apiContract.voidResultSchema` | `HaiResult<void>` 空结果包装 |
205
+ | `apiContract.paginatedSchema(itemSchema)` | 分页列表输出 Schema |
206
+ | `HaiErrorSchema` | HaiError 公共字段 Schema |
207
+ | `PaginationQuerySchema` | 分页参数 Schema(可选 page/pageSize,pageSize 上限 100;默认值由服务端处理) |
208
+
209
+ ## Contract 定义规范
210
+
211
+ ### 必须遵循
212
+
213
+ - **输出必须包装为 `apiContract.haiResultSchema`** — 客户端依赖 `success` 判断成功/失败
214
+ - **业务 path 直接写在对应 `*-contract.ts`** — 禁止新增 `*-routes.ts` 或业务路径常量
215
+ - **一次性 `*OutputSchema` 直接内联** — 不在 schemas 文件声明或导出
216
+ - **同一 contract 内重复使用的输出可设私有常量** — 不得为复用扩大公共导出
217
+ - **公共 HTTP 传输配置以配置文件为准** — API 前缀、健康检查、OpenAPI/docs、密钥协商路径与跨端 Header 放在 service 的 `_serv.yml`,不放 contract 包
218
+ - **schemas 只保留真实复用结构** — 至少被多个接口或 contract/procedure 等不同层共同消费
219
+ - **`operationId` 全局唯一** — 格式为 `domain.resource.action`,如 `iam.auth.login`
220
+ - **`tags` 至少包含领域名** — 供 OpenAPI 文档分组
221
+ - **输入 Schema 用 Zod v4** — 与 `@orpc/zod/zod4` 的 `ZodToJsonSchemaConverter` 配套
222
+ - **Contract 不含业务逻辑** — 只有 Schema + 路由元数据,没有 `async function`
223
+
224
+ ### 禁止
225
+
226
+ - ❌ Contract 文件中 `import` 任何业务模块(`@h-ai/iam`、`@h-ai/storage` 等)
227
+ - ❌ 在 contract 中使用 `z.any()` 或 `z.unknown()` 作为输出类型
228
+ - ❌ 重复定义已存在的领域 contract
229
+ - ❌ 导出只被单个 contract 接口使用的 `*OutputSchema`
230
+ - ❌ 用独立 `*-routes.ts` 维护业务接口路径
231
+
232
+ ### 文件职责
233
+
234
+ | 文件 | 只负责 |
235
+ | --- | --- |
236
+ | `*-contract.ts` | 业务接口 path、method、输入与输出边界 |
237
+ | `*-schemas.ts` | 跨接口或跨层复用的输入、实体、数据结构 |
238
+ | service `config/_serv.yml` | API 前缀、基础设施端点、跨端 Header 与加密 transport 配置 |
239
+
240
+ ## 常见模式
241
+
242
+ ### 带分页的列表接口
243
+
244
+ ```typescript
245
+ import { apiContract, PaginationQuerySchema } from '@h-ai/api-contract'
246
+ import { z } from 'zod'
247
+
248
+ const WidgetSchema = z.object({ id: z.string(), name: z.string() })
249
+
250
+ const listWidgets = apiContract
251
+ .route({ method: 'GET', path: '/widgets', operationId: 'widget.list', tags: ['widget'] })
252
+ .input(PaginationQuerySchema.extend({ keyword: z.string().optional() }))
253
+ .output(apiContract.haiResultSchema(apiContract.paginatedSchema(WidgetSchema)))
254
+ ```
255
+
256
+ ### 路径参数
257
+
258
+ ```typescript
259
+ // 路径中的 {id} 对应 input 中的 id 字段
260
+ const getWidget = apiContract
261
+ .route({ method: 'GET', path: '/widgets/{id}', operationId: 'widget.getById', tags: ['widget'] })
262
+ .input(z.object({ id: z.string() }))
263
+ .output(apiContract.haiResultSchema(WidgetSchema))
264
+ ```
265
+
266
+ ### 无输入的端点
267
+
268
+ ```typescript
269
+ const versionContract = apiContract
270
+ .route({ method: 'GET', path: '/system/version', operationId: 'system.version', tags: ['system'] })
271
+ .output(apiContract.haiResultSchema(z.object({ version: z.string() })))
272
+ ```
273
+
274
+ ## 测试
275
+
276
+ ```bash
277
+ pnpm --filter @h-ai/api-contract test
278
+ pnpm --filter @h-ai/api-contract typecheck
279
+ pnpm --filter @h-ai/api-contract lint
280
+ ```
@@ -17,6 +17,8 @@ description: "在 hai 应用中新增、修改或修复功能,按需求复用
17
17
 
18
18
  ## 使用路径
19
19
 
20
+ 范围、验收、复用依据和证据按 [工作交接契约](../hai-spec-implement/handoff.md) 贯穿实现、测试和审查;小改动使用简短记录,文档驱动任务沿既有任务记录续跑。
21
+
20
22
  业务以用户当前明确要求及其指定文档为准;模板规则、生成代码与测试只描述初始方案。需求改变角色、流程、限额或架构时,在已有授权范围内同步调整冲突实现及过期 AGENTS/skills/README,不要求把需求改回模板。具体判定见 [development.md](development.md#需求与模板的关系)。
21
23
 
22
24
  从需求/设计文档开始生成项目或功能前,使用 [hai-spec-review](../hai-spec-review/SKILL.md) 检查文档是否足以确定实现;修正影响行为的缺项、歧义和技术错误后再进入生成。普通小改动按本次范围确认规则,不要求重审无关文档。
@@ -28,13 +30,15 @@ description: "在 hai 应用中新增、修改或修复功能,按需求复用
28
30
  1. 读取 [development.md](development.md):先按真实需求匹配框架能力、已安装版本与应用架构,再确定改动。适用能力包括同一模块已有 feature、配置和扩展点,不以导入包作为复用完成的证据。
29
31
  2. 读取所选模块的 skill 与实际公开类型,复用现有服务和组件。新增依赖或直接使用第三方 SDK 前说明已有能力的具体缺口;无需安装无关模块。
30
32
  3. 新增或改变行为时用有意义的目标用例验证,仍适用的回归保持通过;被明确新需求替代的模板断言同步更新并记录依据。按下方参考实现,使用 [hai-app-review](../hai-app-review/SKILL.md) 检查改动涉及的安全与质量边界。
31
- 4. 运行当前项目已定义的门禁,报告行为证据、能力选择及未验证项。纯文案、格式和文档变化按影响验证,不强制新增测试。
33
+ 4. 按 [分轮验证与集中修复](../hai-app-tests/validation.md) 运行已有门禁:一轮先收集所有可执行结果,再归并修复、复测失败及受影响项;报告行为证据、能力选择及未验证项。纯文案、格式和文档变化按影响验证,不强制新增测试。
32
34
 
33
35
  ## 按需参考
34
36
 
37
+ 界面新建或明显改变信息结构时先用已交付的 hai-ui-design 确定页面模式、状态和组件映射;使用 hai-ui 的当前版本契约实现,以 hai-ui-review 的真实页面检查收尾。窄范围修复直接复用既有设计。界面方案与最终实现的差异需回写适用设计记录。
38
+
35
39
  新增新手引导或帮助说明时读取 [UI 引导指南](../hai-ui/guide.md),复用 fullstack 默认的共享 guide-store、locale 和稳定 data-guide 锚点。
36
40
 
37
- 详细配置、API 签名、错误码和范本在 [reference.md](reference.md)。按下列标题定位所需小节,不默认通读;用例中的业务变量需结合当前应用补齐。
41
+ 按任务读取 [reference.md](reference.md) 的对应小节;签名以当前源码或安装版本的公开类型为准,不默认加载全部资料。示例中的业务变量由应用提供。
38
42
 
39
43
  - 适用场景
40
44
  - 行为开发与验证
@@ -14,6 +14,7 @@
14
14
 
15
15
  - 读取当前 `AGENTS.md`、README、`package.json`、lockfile、初始化入口和已有相似功能;用 `rg` 查实现、引用、测试。保留已有改动,沿用项目分层、命名、ESLint、i18n 与测试入口。
16
16
  - 确认架构:SvelteKit 使用 `kit` 和文件路由;API workspace 使用 contract/service;fullstack 使用 contract/serv/shared 与所选前端;Vite SPA/原生端通过 typed API 客户端调用服务。不要把服务端目录、DB、密钥或 Node 初始化复制到浏览器。
17
+ - 独立 mobile 模板选择 IAM/crypto 时包含前端 crypto,默认启用 typed client 加密 transport;无 crypto 的客户端保留明确的明文 API 模式。只有匹配的明文服务才设置 `PUBLIC_API_TRANSPORT=off`;`PUBLIC_API_KEY_EXCHANGE_PATH` 只覆盖公开协商子路径。握手失败不降级,关闭时先销毁 client transport,再关闭自身初始化的 crypto;服务端 ring 私钥不进入客户端。
17
18
  - 从所属 workspace 的依赖与 lockfile 确认 `@h-ai/*` 实际解析版本和 npm/local 模式。模板 skill 提供导航,**当前安装包的 `package.json.exports`、公开 `.d.ts` 与随包文档决定可用 API**;本地框架源码只在版本匹配或显式联调时作实现证据。
18
19
  - 若 skill 与安装版本不符,以公开类型和实际行为核验。先使用已支持能力;任务要求升级时按 [hai-framework-sync](../hai-framework-sync/SKILL.md) 发现目标、比较差异并完成迁移验证。不能直接复制未发布源码的 API,也不能为绕过差异添加虚构兼容层。
19
20
 
@@ -36,6 +37,8 @@
36
37
 
37
38
  YAML 使用 `${HAI_DB_TYPE:sqlite}` 等显式引用,冒号后是未配置时的本地默认值。应用仍通过 `core.config.validate(name, ModuleConfigSchema)` 和模块初始化校验类型、枚举、范围和必填项;插值本身不能代替校验。只开启实际使用的模块,未启用模块的整份 YAML 保留注释。
38
39
 
40
+ 生产禁止使用进程内或节点本地状态。CLI 的 `.env.prod` 按已启用模块选择 PostgreSQL(保留明确选择的 MySQL)、Redis、S3、Qdrant,默认 Compose DNS 为 `db/cache/storage/vecdb`,容器外运行用私有环境覆盖连接地址。必须私有注入密码、S3/模型凭据及受保护 Qdrant 的 API Key。AI 硬依赖 reldb+vecdb,按数据库→向量库→AI 初始化,失败逆序释放并检查关闭结果,清理失败保留重试义务。生产 Reach 必须私有注入真实 `HAI_REACH_PROVIDERS` JSON;公共空数组有意拒绝启动,不能沿用 console。`hai add ai` 补包与配置,但现有业务入口仍须接入相同生命周期。
41
+
39
42
  #### 命名和类型
40
43
 
41
44
  - `HAI_<配置名>_<字段路径>`:`_db.yml` 的 `type` 对应 `HAI_DB_TYPE`;`_ai.yml` 的 `llm.apiKey` 对应 `HAI_AI_LLM_APIKEY`。camelCase 不拆词,数组使用从零开始的索引,如 `HAI_AI_AUDIO_MODELS_0_BASEURL`。
@@ -53,9 +56,11 @@ YAML 使用 `${HAI_DB_TYPE:sqlite}` 等显式引用,冒号后是未配置时
53
56
 
54
57
  所有入口优先级一致:**进程环境 > `.env.local` > `.env.<dev|test|prod>` > `.env`**。Vite 配置在 Node 阶段通过 `core.config.loadEnvFiles({ mode, envDir })` 读取,检查 `success` 后注入进程;`development`/`production` 分别映射到 `dev`/`prod`。Node 服务按 `.env`、环境文件、`.env.local` 的顺序传入 `--env-file`,后者覆盖前者,已有进程环境最高。`core.init` 只读取已注入的变量,不自行读取 dotenv。
55
58
 
59
+ 托管 `test:e2e` 是明确的隔离测试入口:服务子进程使用本轮绝对数据目录和完整模块配置,不继承 ambient 模块变量,也不读取用户 `.env.local`。Svelte 仅在 `HAI_E2E=1` 的测试分支跳过 dotenv;API 测试直接执行已构建产物。普通开发与生产的上述优先级和共享后端守卫保持不变,transport 使用测试独立可信 ring 与实际 registry。
60
+
56
61
  `prod` 是文件模式名,对应 `HAI_CORE_ENV=production`;环境枚举为 `development | test | production | staging`。SvelteKit adapter-node 产物使用 `node --env-file=.env --env-file=.env.prod --env-file-if-exists=.env.local build/index.js`。文件路径相对进程工作目录,公共文件随项目提交;容器通过部署平台或 `--env-file` 注入目标环境值。浏览器只接收公开白名单,构建产物和镜像不包含 `.env.local`。
57
62
 
58
- Fullstack 的环境文件在工程根;service 脚本通过 `../../.env*` 加载,Web/App/Desktop 的 Vite `envDir` 指向根目录。API workspace 的环境文件在 `apps/<name>-service/`。独立应用在各自根目录加载。浏览器配置在构建期注入时,改环境参数后须重新构建;fullstack 的公开应用元数据由 service `app.info` 返回。
63
+ Fullstack 的环境文件在工程根;service 脚本通过 `../../.env*` 加载,Web/App/Desktop 的 Vite 自动 dotenv 加载关闭(`envDir: false`),开发和预览由 `core.config.loadEnvFiles` 从工程根读取,构建不读取部署环境文件。API workspace 的环境文件在 `apps/<name>-service/`。独立应用在各自根目录加载。静态 Web 通过 `runtime-config.js` 获取公开地址,部署环境覆盖无需重建镜像;原生包在复制静态资源前配置该入口。修改编译进客户端的默认值时仍需重新构建;fullstack 的公开应用元数据由 service `app.info` 返回。
59
64
 
60
65
  #### 迁移已有项目
61
66
 
@@ -68,13 +73,12 @@ Fullstack 的环境文件在工程根;service 脚本通过 `../../.env*` 加
68
73
  | 变量 | 说明与取值约束 |
69
74
  | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
70
75
  | `HAI_CORE_ENV` | `development \| test \| production \| staging` |
71
- | `HAI_CORE_DEBUG` | `true \| false` |
72
76
  | `HAI_CORE_LOGGING_LEVEL` | `trace \| debug \| info \| warn \| error \| fatal` |
73
77
  | `HAI_CORE_LOGGING_FORMAT` | `json \| pretty` |
74
78
  | `HAI_DB_TYPE` | `sqlite \| postgresql \| mysql` |
75
79
  | `HAI_DB_DATABASE` | SQLite 文件路径或 `:memory:`;网络数据库为数据库名 |
76
80
  | `HAI_DB_HOST` / `HAI_DB_PORT` / `HAI_DB_USER` / `HAI_DB_PASSWORD` | 主机、整数端口 1–65535、用户名、敏感密码;PostgreSQL 常用 5432,MySQL 常用 3306 |
77
- | `HAI_DB_URL` / `HAI_DB_SSL` | 非空连接 URL 优先于拆分字段;TLS 为布尔值、require/prefer/allow/disable 或 TLS 对象 |
81
+ | `HAI_DB_URL` / `HAI_DB_SSL` | 非空连接 URL 优先于拆分字段;MySQL TLS 仅 false 或严格对象({} 默认验证证书链和 DNS 身份);PostgreSQL 接受其布尔/字符串模式或对象 |
78
82
  | `HAI_CACHE_TYPE` / `HAI_CACHE_DB` | `memory \| redis`;DB 为整数 0–15;memory 仅单进程使用 |
79
83
  | `HAI_STORAGE_TYPE` | `local \| s3`;local 要求 root,s3 要求 bucket、region、accessKeyId、secretAccessKey |
80
84
  | `HAI_SERV_SERVER_HOST` / `HAI_SERV_SERVER_PORT` | HTTP 主机和整数端口 1–65535;部署平台 HOST/PORT 可覆盖 |
@@ -106,7 +110,7 @@ HAI_DB_USER=my_app
106
110
  HAI_DB_SSL=require
107
111
  ```
108
112
 
109
- 切换到 MySQL 时相应设置 `HAI_DB_TYPE=mysql`、端口和凭据。确保项目已安装所选 Provider 的驱动;数据库数据迁移另行执行,切换配置不会搬迁数据。缓存切到 Redis、存储切到 S3 时同样填写对应连接字段。
113
+ 切换到 MySQL 时相应设置 `HAI_DB_TYPE=mysql`、端口和凭据,并把 `HAI_DB_SSL=require` 改为严格 TLS 对象,例如 `HAI_DB_SSL='{}'`(默认校验证书链和 DNS 身份);自签 CA 使用 `ca` PEM,mTLS 的 `cert` / `key` PEM 必须成对。私钥仅放被忽略的 `.env.local` 或目标进程环境。确保项目已安装所选 Provider 的驱动;数据库数据迁移另行执行,切换配置不会搬迁数据。缓存切到 Redis、存储切到 S3 时同样填写对应连接字段。
110
114
 
111
115
  ## CLI 安装与命令
112
116
 
@@ -123,9 +127,15 @@ hai create my-app
123
127
 
124
128
  已有项目先进入项目根目录,再按需要执行 `hai add <module>`、`hai init` 或 `hai generate <type> <name>`。已安装 CLI 时,相同安装命令可更新到当前发布版本;需要指定版本时使用 `@h-ai/cli@<version>`。命令和模板以实际安装的 CLI 为准,不能把未发布的框架源码能力当作已安装包能力。
125
129
 
130
+ `hai generate page` 只写 SvelteKit 文件路由;`api` 按目标应用生成 SvelteKit 端点或 serv 的共享 contract/procedure 并接入已有聚合入口。API/fullstack 根自动选择唯一后端,多个候选用 `--workspace <包名或相对路径>`,也可进入目标 workspace。纯 Vite 不生成虚假文件路由;serv 不接受 `--output`,SvelteKit 输出限定 `src/routes` 内,定制 `kit.files` 时拒绝猜测;SvelteKit API 目标须已安装 `@h-ai/kit` 与 `zod`。不要删除模板 `@hai-generator` 聚合标记;自定义入口没有标记时手动沿现有架构接入。生成前统一校验路径和覆盖冲突,失败回滚本次修改;`--force` 不能覆盖已有业务领域注册。
131
+
132
+ CLI 用户消息通过现有中英字典输出。初始语言按非空的 `HAI_CORE_DEFAULTLOCALE` → `LC_ALL` → `LC_MESSAGES` → `LANG` 选择,`en`/`zh` 地区变体映射到现有语言,`C`/`POSIX` 为英文;未知语言保留 core 当前语言(默认中文)。CLI 内部命令响应 `core.i18n.setGlobalLocale('en-US')` 的同进程切换;包入口仍为命令行入口,不提供库调用 API。`hai generate` 省略类型/名称会交互,取消不写文件;生成源码的中文注释与用户名称不翻译。
133
+
134
+ `create` / `add` / `init` 的校验、选择项、模块/模板名称与描述、错误前缀及完成提示也使用当前 CLI locale;目录标签延迟求值。CLI 语言不修改生成应用的 `core.defaultLocale`。生成的中文 README、YAML/源码注释与外部工具原始错误保留原样;不要把这些正文机械替换成终端翻译。
135
+
126
136
  ## 将需求映射到现有能力
127
137
 
128
- 应用元信息只有一份配置来源:名称、版本、环境、默认语言与日志设置来自 `_core.yml`,使用 `core.config` 与 `core.logger`;Node 启动时不通过固定 `logging` 覆盖 YAML。SvelteKit 服务端 layout 和 fullstack `app.info` 仅公开必要元信息,不能下发整个 core 或模块配置。独立 Vite mobile-app 在 Node 构建阶段经 core 加载 YAML/环境覆盖并注入公开字段,配置变化需重新构建。环境覆盖使用 `HAI_CORE_NAME`、`HAI_CORE_VERSION`、`HAI_CORE_ENV`、`HAI_CORE_LOGGING_LEVEL` 等既有规则;容器明确配置生产环境,不能用另一个状态来源代替 `core.env`。
138
+ 应用元信息只有一份配置来源:名称、版本、环境、默认语言与日志设置来自 `_core.yml`,使用 `core.config` 与 `core.logger`;Node 启动时不通过固定 `logging` 覆盖 YAML。SvelteKit 服务端 layout 和 fullstack `app.info` 仅公开必要元信息,不能下发整个 core 或模块配置。独立 Vite 客户端经 Node/core 加载 YAML 默认值,Web 部署通过 `runtime-config.js` 应用公开白名单覆盖;只修改部署覆盖无需重建,修改构建默认值则须重新构建。环境覆盖使用 `HAI_CORE_NAME`、`HAI_CORE_VERSION`、`HAI_CORE_ENV`、`HAI_CORE_LOGGING_LEVEL` 等既有规则;容器明确配置生产环境,不能用另一个状态来源代替 `core.env`。
129
139
 
130
140
  开发前,对本次涉及的需求简述“需求 → 现有应用实现/框架能力 → 已安装版本与公开契约依据 → 最小业务补充/修改位置 → 成功、权限与失败验收”。简单任务可用几句话;跨模块任务用表格。查找顺序:已有应用实现 → 框架公开能力和配置 → 最小业务补充。
131
141
 
@@ -174,7 +184,7 @@ Fullstack 创建时在被 Git 与容器构建忽略的根 `.env.local` 写入独
174
184
  | HaiResult 与错误 | HaiResult 型业务 API 先判断 `success`,服务层保留错误;HTTP 边界沿实际包络/状态契约并隐藏内部敏感细节。显式抛错、流迭代与 SvelteKit 控制流按各自契约 | 依赖失败不回退为空列表/成功消息;流失败与业务状态失败有反馈,用户输入得到保留 |
175
185
  | 初始化与清理 | 只对有生命周期的模块按依赖 init → use → close;检查每次结果,关键依赖失败停止启动,清理已创建资源,关闭反序 | 初始化失败不能继续执行业务;关闭/取消后连接、监听器与任务退出 |
176
186
  | 写入与重试 | 多步一致性用事务或明确补偿;重试/回调/任务定义幂等;跨节点业务状态使用共享存储 | 中途失败无不一致写入;重复请求/回调不产生重复副作用 |
177
- | 风格与可维护性 | 禁止 `any`、无说明的 `as unknown as T`、`console.log`、硬编码密钥;注释中文、日志英文且脱敏;文案使用现有 i18n 并同步中英文;遵循项目实际 lint 配置 | typecheck/lint 通过;人工核对脱敏、文本、分层与不受 lint 覆盖的规则 |
187
+ | 风格与可维护性 | 禁止 `any`、无说明的 `as unknown as T`、`console.log`、硬编码密钥;注释中文、日志英文且脱敏;文案使用现有 i18n 并同步中英日文;遵循项目实际 lint 配置 | typecheck/lint 通过;人工核对脱敏、文本、分层与不受 lint 覆盖的规则 |
178
188
 
179
189
  不把所有函数改成 HaiResult:纯工具、getter、工厂、客户端/流及控制流按实际签名。不把所有模块加上 init/close。应用服务根据已有业务契约返回 HaiResult,HTTP/UI 层做适当转换。
180
190
 
@@ -209,9 +219,15 @@ export function pageOffset(query: PageQuery): number {
209
219
 
210
220
  ## 验证与交付
211
221
 
212
- 所有测试临时工程、镜像、容器、辅助进程、日志、截图与报告须在 finally/teardown 自动清理,成功、失败和取消均执行;只清理本次创建的资源,不提交验收产物。
222
+ 测试临时工程、镜像、容器、辅助进程与数据须在 finally/teardown 自动清理,成功、失败和取消均执行;只清理本次创建的资源。日志、截图与报告保留到分析和交付结束再清理,不提交验收产物。
213
223
 
214
224
  - 对新增/改变行为按 [hai-app-tests](../hai-app-tests/SKILL.md) 写有意义的测试;目标用例先因行为缺失失败,既有回归保持通过。UI/路由改动覆盖真实流程、异步失败与权限路径;纯文案/文档按影响验证。
215
- - 从当前 `package.json` 选择实际脚本,优先 workspace filter;按 `typecheck → lint → build(构建/配置/跨包变更)→ test → 相关 E2E` 验证,PowerShell 使用 `pnpm.cmd`。不能调用框架仓库私有命令替代应用检查。
225
+ - 从当前 `package.json` 选择实际脚本,优先 workspace filter,PowerShell 使用 `pnpm.cmd`。多项门禁按 [分轮验证与集中修复](../hai-app-tests/validation.md) 先脚本化收集结果和日志,再集中修复、复测失败及受影响项;普通失败不立即从头重跑。不能调用框架仓库私有命令替代应用检查。
216
226
  - typecheck/lint 只覆盖部分质量规则;权限、事务、降级、真实 provider、浏览器、容器和原生交付各自需要适用行为证据。缺环境或密钥时报告未验证,不能用 mock 冒充真实集成。
217
227
  - 完成说明:实际复用能力与自定义缺口、修改路径、已执行命令/行为结果、同步的 contract/调用方/配置/i18n/文档、未验证项及原因。无需无关模块的“覆盖率”清单。
228
+
229
+ `_core.yml` 只接受 name/version/env/defaultLocale 元数据与 logging;不提供 debug、全局ID设置、features或supportedLocales。业务开关放app.yml并用业务Schema校验;语言列表按实际翻译工程维护,日志用logging.level。core默认语言字段不会自动注册翻译。Logger导入保留LANG/LC_ALL/PYTHONIOENCODING,终端语言优先级不能被编码初始化覆盖。
230
+
231
+ 框架环境目录从真实入口生成;JS/TS(含 Svelte script)中的 JSDoc 示例不构成新运行变量。Schema 字段注释仍生成用途说明,YAML/模板可选注释和字符串内生成代码引用继续保留;不能只在示例里写变量就声称已被实现消费。
232
+
233
+ 公开导入以安装版本的 package.json.exports 为准:只使用已声明且有真实产物的根/子路径,不导入 src/dist 或内部 Provider/Deps。条件入口可以只提供配置和类型;IAM 浏览器入口不含服务端 iam 对象。Svelte 默认组件和明确的 selective/type-only 导出均合法,不能为形式统一扩大公共 API。
@@ -40,7 +40,7 @@
40
40
  ### Step 4:重构与验证(Refactor 阶段)
41
41
 
42
42
  1. 参照 `hai-app-review` 审查代码规范
43
- 2. 依当前 scripts 顺序执行 `typecheck → lint → build(构建/配置/跨包变更)→ test`,优先 workspace filter,PowerShell 使用 `pnpm.cmd`
43
+ 2. 按 [分轮验证与集中修复](../hai-app-tests/validation.md) 收集适用门禁,普通失败不中断独立检查,汇总后集中修复、再验证;优先 workspace filter,PowerShell 使用 `pnpm.cmd`
44
44
  3. UI/路由/端到端改动执行相应 E2E;报告失败路径与安全验收结果,未运行的边界说明原因
45
45
 
46
46
  ## 目录结构约定
@@ -80,6 +80,7 @@ src/
80
80
  users/+server.ts
81
81
  messages/ # i18n 消息文件
82
82
  en-US.json
83
+ ja-JP.json
83
84
  zh-CN.json
84
85
  config/ # 模块配置文件
85
86
  _core.yml
@@ -300,7 +301,7 @@ interface CreateArticleInput {
300
301
  </div>
301
302
  ```
302
303
 
303
- `Article` 放在客户端可用的共享类型位置,`article_edit` 在中英文 messages 中新增后由项目生成;实际已安装 `@h-ai/ui` 有适用 Card/Button 时使用其公开组件和 Props,本例只展示业务 props、事件与文案边界。
304
+ `Article` 放在客户端可用的共享类型位置,`article_edit` 在中英日文 messages 中新增后由项目生成;实际已安装 `@h-ai/ui` 有适用 Card/Button 时使用其公开组件和 Props,本例只展示业务 props、事件与文案边界。
304
305
 
305
306
  ### 组件规则
306
307
 
@@ -334,13 +335,14 @@ core.init() → crypto.init() → reldb.init() → cache.init() → iam.init()
334
335
 
335
336
  仅涉及容器构建/部署变更时使用本节;普通功能开发无需每次启动镜像。
336
337
 
337
- - 保持脚手架根目录 `Dockerfile`、`.dockerignore` 与 `package.json` 中 `build` / `compile` 命令与 hai.build.json同步。
338
+ - 保持脚手架 `images/*.Dockerfile`、`.dockerignore` 与 `package.json` 中 `build` / `compile` 命令与 hai.build.json同步。保留 `.gitattributes`,让 `.dockerignore`、Dockerfile 和 POSIX 脚本统一使用 LF,保持跨平台输入一致并确保 Linux 脚本可运行。
338
339
  - Podman 多阶段构建保持 `--format docker --jobs=1`;前者保留 `HEALTHCHECK`,后者避免部分 Podman 5.8/Buildah 组合在并行 stage 构建时触发内部 semaphore panic。
339
- - 先复制 lockfile 与 workspace 配置执行 `pnpm fetch`,再复制源码并离线安装、构建;避免源码变化重复下载依赖,同时兼容新增 workspace。
340
+ - 先复制 lockfile、workspace 配置与构建器准备的 `images/dependencies/` 安装输入执行 `pnpm fetch --ignore-scripts`,再复制源码并离线安装、构建;fetch、安装和 `pnpm pm deploy` 共用 pnpm store,安装与生产依赖整理共用 SQLite 驱动缓存,避免重复下载,兼容新增 workspace、本地归档与补丁。
341
+ - 默认容器面向 CPU 运行或远程 AI 服务,Dockerfile 设置 `ONNXRUNTIME_NODE_INSTALL_CUDA=skip`,跳过额外 CUDA/TensorRT 下载;使用宿主机 GPU 时定制镜像和运行参数,不修改本机 npm 或全局 AI/GPU 配置。
340
342
  - SvelteKit 服务使用 Node adapter;纯 Vite 前端使用带 SPA fallback 的静态镜像;fullstack 的 Service 与 Web 分别交付,Service 直接监听且不增加转发进程。
341
343
  - Web 的 `PUBLIC_*` 部署配置在容器启动时生成静态运行时配置,不作为 build arg 烘焙,确保同一镜像可跨环境部署。
342
344
  - 配置文件随镜像提供,并允许只读挂载覆盖;密钥只通过环境变量或 `--env-file` 注入。
343
- - 普通构建不携带 `data/`;持续写入使用运行期 volume;需要预置数据时显式维护独立 Dockerfile target 和 named build context,不把本机数据加入默认镜像。
345
+ - 普通构建不携带 `data/`;持续写入使用运行期 volume;需要预置数据时显式维护独立 Dockerfile 和明确的 build context,不把本机数据加入默认镜像。
344
346
  - 数据库和本地存储的持续写入必须落到 volume,不能依赖镜像可写层。
345
347
  - 完成前实际启动镜像,验证健康端点、非 root 用户、环境变量覆盖、配置挂载和预置数据读取。
346
348
 
@@ -17,6 +17,8 @@ description: "审查 hai 应用的框架复用、安全边界、返回契约、
17
17
 
18
18
  ## 审查路径
19
19
 
20
+ 对代码差异读取 [差异审查](differential.md):固定 base/head,先核对需求符合性,再按真实风险检查历史约束、直接与关键间接调用方。完整应用审查使用同样的需求追踪与发现复核,说明实际覆盖范围。发现、修复和验收沿 [工作交接契约](../hai-spec-implement/handoff.md) 记录。
21
+
20
22
  1. 读取当前项目入口规则、用户指定需求、package scripts、lockfile 与差异,确认 SvelteKit、API workspace、fullstack 或 Vite/原生架构。业务以当前明确需求为准;模板角色、流程、限额和生成代码是初始实现,不能据此否定新需求或要求恢复已被替代的行为。
21
23
  2. 按 [development.md](../hai-app-create/development.md) 对需求核对实际框架能力、当前安装版本及适用安全边界;只读所涉及模块 skill、公开类型和相关调用方。
22
24
  3. 沿真实路径检查输入 → 身份/资源权限 → 服务/副作用 → 结果 → UI。先报告能导致安全或行为错误的问题,再检查风格与维护性。
@@ -55,24 +57,26 @@ typecheck/lint 不能证明越权、租户隔离、事务、错误状态或资
55
57
  - 禁止 `any`、无说明的 `as unknown as T`、`console.log`、硬编码密钥;外部 `unknown` 先校验/缩窄。必要第三方断言有真实依据。
56
58
  - 遵循当前 ESLint、TypeScript、目录与命名规则,函数返回类型按真实契约标注;不把所有返回类型都强制成 HaiResult。
57
59
  - 注释中文,结构化日志英文并脱敏。读操作用 debug,写操作成功用 info,失败按业务/系统性质用 warn/error;避免记录整份请求、凭据或未经脱敏的 error/context。
58
- - 用户可见文案沿现有 i18n 入口,同步中英文;包括错误、空态、降级、校验和状态文案。禁止手改 Paraglide 生成文件;框架组件内置翻译沿公开 Props 使用。
60
+ - 用户可见文案沿现有 i18n 入口,同步中英日文;包括错误、空态、降级、校验和状态文案。禁止手改 Paraglide 生成文件;框架组件内置翻译沿公开 Props 使用。
59
61
  - 业务逻辑不塞入 UI/hook;优先 early return、按职责拆分复杂函数。遵守实际 lint 的复杂度/长度限制,不为了数字拆出无意义转发链。
60
62
  - 循环串行/并发按依赖、事务、限流与资源限制选择;列表分页/流式处理,避免热路径同步 I/O、重复连接和无界并发。
61
63
 
62
64
  ## 交互与行为验证
63
65
 
66
+ 界面设计与真实交互的详细审查由已交付的 hai-ui-review 提供;应用审查仍负责端到端业务与服务端权限,复用已有发现和证据,避免重复执行相同检查。
67
+
64
68
  涉及 UI 时沿真实用户路径走查,确认适用的:
65
69
 
66
70
  - Svelte 5 Runes、props 事件、SSR/浏览器边界及项目样式系统;优先复用 `@h-ai/ui` 的公开组件。
67
71
  - Loading/Empty/Error/Success 四态、表单校验/错误焦点、长任务进度、失败后输入恢复与重试。
68
- - 桌面/移动布局、长文本与中英文展开、主题对比、键盘/触摸/IME、焦点与可访问性;危险操作有符合产品目标的确认/撤销。
72
+ - 桌面/移动布局、长文本与中英日文展开、主题对比、键盘/触摸/IME、焦点与可访问性;危险操作有符合产品目标的确认/撤销。
69
73
  - 并发操作、网络失败、权限变化与返回导航不导致假成功、丢失输入或死路。
70
74
 
71
75
  新增/改变行为有对应的有效单元/集成/E2E 用例;关键接口覆盖正常、失败及适用权限/边界路径。已有代码是否遵循 TDD 不能仅从最终 diff 推断,需当时执行证据;测试数量与机械打勾不代替行为验证。
72
76
 
73
77
  ## 门禁与输出
74
78
 
75
- 从当前 package.json 选择实际 scripts,优先 workspace filter;PowerShell 使用 `pnpm.cmd`。按 typecheck → lint → build(构建/配置/跨包变更)→ test → 相关 E2E 执行。浏览器、真实 provider、容器和原生交付各自报告实际结果。
79
+ 从当前 package.json 选择实际 scripts,优先 workspace filter;PowerShell 使用 `pnpm.cmd`。按 [分轮验证与集中修复](../hai-app-tests/validation.md) 先收集全部可执行门禁及日志,再归并根因、集中修复、验证失败及受影响项;普通失败不中断独立检查。浏览器、真实 provider、容器和原生交付各自报告实际结果。
76
80
 
77
81
  交付说明需求/范围、带路径与影响的发现、已复用能力和必要缺口、已修正项、已执行门禁/行为证据、同步的 contract/调用方/i18n/配置/文档,以及未验证项和原因。只读审查报告问题与验证建议;不能凭审查清单宣称所有测试通过。
78
82
 
@@ -0,0 +1,34 @@
1
+ # 差异审查
2
+
3
+ 应用改动自查、PR/提交/分支审查按需读取。本方法借鉴 [Trail of Bits differential-review](https://github.com/trailofbits/skills/tree/main/plugins/differential-review),按实际风险选择审查深度,不以 diff 行数决定安全性。
4
+
5
+ ## 1. 固定比较对象
6
+
7
+ PR 使用其实际 base/head SHA,分支使用已确认集成分支与目标分支的 merge-base。记录比较命令和两端 SHA;区分提交间差异、staged、unstaged 和未跟踪文件。审查期间 head 变化时,补审新增差异并重新核验受影响结论,不沿用旧行号声称已审查当前版本。
8
+
9
+ 先列 `git diff --name-status --find-renames <base> <head>`、统计和完整 diff,再按需读取文件上下文、公开类型、测试、配置及调用方。不能仅凭截断 diff 做全量结论。无 Git、远程权限不足或缺少 base 时,说明实际可读范围,继续可进行的文件级审查。
10
+
11
+ ## 2. 先找可能造成较大后果的变化
12
+
13
+ | 变化 | 必查内容 |
14
+ | --- | --- |
15
+ | 登录、鉴权、租户、资源 ID、输入校验 | 谁能调用、能操作谁的数据、校验移除后替代约束是否真实存在 |
16
+ | 支付、上传、外部请求、AI 工具、任务 | 验签、目的地/大小限制、幂等、未知结果、重试/取消和外部副作用 |
17
+ | 状态、事务、迁移、共享缓存 | 并发/跨节点约束、失败回滚、存量数据和兼容调用方 |
18
+ | 公共 API、配置、默认值、导出、模板 | 正反向调用、公开类型、目标平台和生成应用是否同步 |
19
+ | UI 和内容渲染 | 服务端权限、HTML/URL 边界、交互丢失、键盘、焦点和语言;不能自动判为低风险 |
20
+ | CI、依赖、部署脚本 | 按 [hai-ci](../hai-ci/SKILL.md) 核对执行来源、权限、密钥与发布条件 |
21
+
22
+ 对高风险符号用 `rg` 查直接调用方及关键间接路径,列出实际依赖范围;动态注册、远端消费者无法确定时说明限制,引用数量不代表完整影响。对删除的校验、锁、事务和失败处理读相关 `git log` / `git blame`,判断是否移除了此前修复的约束。没有替代实现的“只是重构”仍需验证行为。
23
+
24
+ ## 3. 两次核对
25
+
26
+ 第一次核对需求符合性:逐条检查当前目标验收,区分完整实现、已有满足、未实现和证据不足。第二次沿输入 → 身份/资源 → 服务/副作用 → 结果 → UI 检查正确性与集成。只读当前风险需要的模块资料,普通改动不升级为无关的全面安全审计。
27
+
28
+ 把可疑点转为具体场景,例如“普通用户提交其他租户的资源 ID,更新语句未包含租户条件”,再核对上游守卫、服务和数据库约束能否实际阻止它。验证新/改变行为对应的成功与失败用例;缺少测试表示验证缺口,不直接证明漏洞,也不能仅因此提高业务问题严重程度。
29
+
30
+ ## 4. 报告与复核
31
+
32
+ 采用 [交接契约](../hai-spec-implement/handoff.md) 的发现格式。每条引用当前差异行及必要上下文,说明改动引入还是既有问题、触发条件、具体后果、证据充分程度、最小修正和验证方式。排除不成立的线索,不将已否定建议继续列为风险。
33
+
34
+ 结尾记录已覆盖文件/路径、未读或被截断部分、未执行验证和原因、需要交付前解决的事项。无发现只说明本次范围未确认问题,不等同于全系统无缺陷或 CI 已通过。
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: hai-app-tests
3
- description: "编写或修复 hai 应用的 Vitest/Playwright 测试,验证业务边界和完整用户流程。"
3
+ description: "为 hai 应用编写或修复 Vitest/Playwright 测试,组织分轮验证、汇总失败和集中修复,验证需求与完整用户流程。"
4
4
  ---
5
5
 
6
6
  # hai-app-tests
@@ -9,7 +9,7 @@ description: "编写或修复 hai 应用的 Vitest/Playwright 测试,验证业
9
9
 
10
10
  | 项目 | 契约 |
11
11
  | --- | --- |
12
- | 能力 | 编写或修复 hai 应用的 Vitest/Playwright 测试,验证业务边界和完整用户流程 |
12
+ | 能力 | 编写或修复 Vitest/Playwright 测试,组织质量门禁与业务流程的分轮验证 |
13
13
  | 适用场景 | 需求验收、缺陷回归或应用测试改进 |
14
14
  | 输入 | 验收条件、被测公共入口、现有测试和运行环境 |
15
15
  | 输出 | 可复现用例及明确区分静态、mock、浏览器/外部服务的结果 |
@@ -17,16 +17,19 @@ description: "编写或修复 hai 应用的 Vitest/Playwright 测试,验证业
17
17
 
18
18
  ## 使用路径
19
19
 
20
+ 先复用 [工作交接契约](../hai-spec-implement/handoff.md) 中的验收编号和本轮范围。结果关联提交与未提交差异、命令/操作、依赖与环境、退出码和证据位置;源码/配置/依赖或环境变化后按影响使旧结果失效并复测。测试通过只是相应验收的证据,整体验收仍逐项核对需求。
21
+
20
22
  - 先选单元/集成/浏览器层,复用测试 fixture,验证用户可观察结果。
21
23
  - 验收以用户当前明确要求和指定业务文档为准;模板测试不是不可变需求。规则已改变时同步更新或替换过期断言,记录新需求依据并覆盖新的授权/失败边界;保留仍适用的回归,不能用“保持旧测试通过”强留冲突业务。
22
24
  - 按 [development.md](../hai-app-create/development.md) 的真实行为与安全边界选测试;新增/改变业务行为、缺陷修复有目标用例,纯文案/格式/文档及无行为变化的可逆低影响修改不补镜像测试。
23
25
  - 不要求每个功能同时新增单元与 E2E;复用已有用例,选择能验证变化的最小适用层,关键跨层用户流程仍需端到端证据。
24
26
  - 新用例已通过时先检查是否已有能力;不能为达成 Red 人为破坏旧测试。
27
+ - 执行多项门禁前读取 [分轮验证与集中修复](validation.md):一轮先脚本化收集所有可执行检查及日志,普通失败不立即改代码;汇总去重后集中修复,先定向复测失败、解除阻塞和直接受影响项,全部通过后再扩大验收。定向回归未通过时不重跑整套门禁。只有真实前置阻塞才暂停依赖检查;最终必需门禁均须对最终代码有效通过。小范围 TDD Red → Green 保留。
25
28
  - 外部服务未运行必须报告;mock/Testcontainers/浏览器/真实付费 Provider 的结果分开表述。
26
29
 
27
30
  ## 按需参考
28
31
 
29
- 详细配置、API 签名、错误码和范本在 [reference.md](reference.md)。按下列标题定位所需小节,不默认通读;用例中的业务变量需结合当前应用补齐。
32
+ 按任务读取 [reference.md](reference.md) 的对应小节;签名以当前源码或安装版本的公开类型为准,不默认加载全部资料。示例中的业务变量由应用提供。
30
33
 
31
34
  - 适用场景
32
35
  - 行为验证与 TDD 工作流
@@ -52,7 +52,7 @@
52
52
 
53
53
  1. 参照 `hai-app-review` 技能审查代码规范
54
54
  2. 重构后执行受影响测试与必要回归,确认已有行为保持通过
55
- 3. 从当前 scripts 选择门禁,按 `typecheck → lint → build(构建/配置/跨包变更)→ test → 相关 E2E` 执行;PowerShell 使用 `pnpm.cmd`
55
+ 3. 多项门禁按 [分轮验证与集中修复](validation.md) 收集一轮结果,汇总后集中修复;先定向确认失败和直接受影响项均已解决,再执行较完整验收。定向回归未通过时继续小范围修复,不从头重复全量门禁。PowerShell 使用 `pnpm.cmd`
56
56
 
57
57
  ## 测试目录约定
58
58
 
@@ -210,16 +210,16 @@ function resolvePortFromBaseUrl(urlString: string, fallbackPort: string): string
210
210
 
211
211
  export default defineConfig({
212
212
  testDir: './e2e',
213
- fullyParallel: false,
213
+ fullyParallel: true,
214
214
  forbidOnly: !!process.env.CI,
215
- retries: process.env.CI ? 2 : 0,
215
+ retries: 0,
216
216
  workers: 2,
217
217
  reporter: 'list',
218
218
  timeout: 30_000,
219
219
  use: {
220
220
  baseURL,
221
221
  channel: 'chrome',
222
- trace: 'on-first-retry',
222
+ trace: 'retain-on-failure',
223
223
  screenshot: 'only-on-failure',
224
224
  },
225
225
  webServer: {
@@ -436,7 +436,7 @@ await expect(page).toHaveURL('/admin/articles')
436
436
 
437
437
  ## 质量门禁
438
438
 
439
- 从当前 package.json 确认实际脚本和 workspace 名称,按变更影响选择;不要为格式/文档改动运行无关 provider 或新增镜像测试。记录执行范围、mock/真实环境、退出结果与未验证原因。
439
+ 从当前 package.json 确认实际脚本和 workspace 名称,按 [分轮验证与集中修复](validation.md) 组织以下适用检查;不要为格式/文档改动运行无关 provider 或新增镜像测试。逐轮记录范围、状态/退出码、日志、mock/真实环境与未验证原因,完整日志按需读取。
440
440
 
441
441
  ```bash
442
442
  # 运行单元测试