@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.
- package/README.en.md +86 -241
- package/README.ja.md +86 -241
- package/README.md +86 -241
- package/dist/{src-RDQ2EIBT.js → chunk-FI56GONY.js} +32 -2669
- package/dist/chunk-FI56GONY.js.map +1 -0
- package/dist/chunk-MBPB37FR.js +1348 -0
- package/dist/chunk-MBPB37FR.js.map +1 -0
- package/dist/{chunk-QYX6V7H2.js → chunk-ZFNBUF77.js} +347 -276
- package/dist/chunk-ZFNBUF77.js.map +1 -0
- package/dist/cli-config-templates-W2LLPMB6.js +5 -0
- package/dist/{cli-config-templates-3XSFIXBP.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
- package/dist/{cli-skill-templates-TSYEQTCY.js → cli-skill-templates-US5CCAJ4.js} +3 -3
- package/dist/{cli-skill-templates-TSYEQTCY.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
- package/dist/index.js +1304 -532
- package/dist/index.js.map +1 -1
- package/dist/metafile-esm.json +1 -0
- package/dist/src-DSJJXM2F.js +3849 -0
- package/dist/src-DSJJXM2F.js.map +1 -0
- package/package.json +9 -8
- package/templates/README.md +48 -145
- package/templates/apps/admin/messages/ja-JP.json +38 -0
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
- package/templates/apps/api/.dockerignore.hbs +14 -7
- package/templates/apps/api/.gitignore.hbs +7 -0
- package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
- package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +33 -8
- package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
- package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -2
- package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
- package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
- package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
- package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
- package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
- package/templates/apps/api/hai.build.json.hbs +1 -1
- package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
- package/templates/apps/api/package.json.hbs +6 -1
- package/templates/apps/api/playwright.config.ts.hbs +15 -7
- package/templates/apps/fullstack/.dockerignore.hbs +14 -9
- package/templates/apps/fullstack/.gitignore.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +24 -6
- package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +23 -5
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -6
- package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +30 -9
- package/templates/apps/fullstack/hai.build.json.hbs +2 -2
- package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
- package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
- package/templates/apps/fullstack/package.json.hbs +3 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +32 -8
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +40 -14
- package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +6 -6
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +4 -4
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +13 -14
- package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
- package/templates/apps/h5/messages/ja-JP.json +40 -0
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +16 -5
- package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
- package/templates/apps/mobile-app/src/App.svelte.hbs +15 -5
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
- package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
- package/templates/apps/website/messages/ja-JP.json +32 -0
- package/templates/apps/website/src/routes/+layout.svelte.hbs +13 -5
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
- package/templates/base/.dockerignore.hbs +14 -8
- package/templates/base/.gitignore.hbs +7 -0
- package/templates/base/e2e/app.spec.ts.hbs +23 -8
- package/templates/base/hai.build.json.hbs +1 -1
- package/templates/base/{Dockerfile.hbs → images/app.Dockerfile.hbs} +19 -17
- package/templates/base/package.json.hbs +11 -3
- package/templates/base/playwright.config.ts +15 -4
- package/templates/base/src/hooks.server.ts.hbs +15 -6
- package/templates/base/src/lib/server/init.ts.hbs +139 -30
- package/templates/base/vite.config.ts.hbs +5 -3
- package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
- package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
- package/templates/i18n/project.inlang/settings.json +1 -1
- package/templates/shared/.gitattributes +6 -0
- package/templates/shared/e2e-env.ts +98 -0
- package/templates/shared/run-e2e.mjs +51 -12
- package/templates/skills/AGENTS.md +7 -3
- package/templates/skills/bridges/admin/AGENTS.md +6 -2
- package/templates/skills/bridges/api/AGENTS.md +3 -1
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +5 -1
- package/templates/skills/bridges/h5/AGENTS.md +6 -2
- package/templates/skills/bridges/mobile-app/AGENTS.md +6 -2
- package/templates/skills/bridges/website/AGENTS.md +6 -2
- package/templates/skills/hai-ai/SKILL.md +8 -2
- package/templates/skills/hai-ai/guide.md +134 -60
- package/templates/skills/hai-ai/reference.md +51 -11
- package/templates/skills/hai-api-client/SKILL.md +15 -160
- package/templates/skills/hai-api-client/reference.md +178 -0
- package/templates/skills/hai-api-contract/SKILL.md +16 -273
- package/templates/skills/hai-api-contract/reference.md +280 -0
- package/templates/skills/hai-app-create/SKILL.md +6 -2
- package/templates/skills/hai-app-create/development.md +24 -8
- package/templates/skills/hai-app-create/reference.md +7 -5
- package/templates/skills/hai-app-review/SKILL.md +7 -3
- package/templates/skills/hai-app-review/differential.md +34 -0
- package/templates/skills/hai-app-tests/SKILL.md +6 -3
- package/templates/skills/hai-app-tests/reference.md +5 -5
- package/templates/skills/hai-app-tests/validation.md +38 -0
- package/templates/skills/hai-audit/SKILL.md +24 -147
- package/templates/skills/hai-audit/reference.md +148 -0
- package/templates/skills/hai-build/SKILL.md +14 -8
- package/templates/skills/hai-build/reference.md +16 -13
- package/templates/skills/hai-cache/SKILL.md +20 -181
- package/templates/skills/hai-cache/reference.md +229 -0
- package/templates/skills/hai-capacitor/SKILL.md +7 -4
- package/templates/skills/hai-capacitor/reference.md +15 -9
- package/templates/skills/hai-ci/SKILL.md +2 -0
- package/templates/skills/hai-core/SKILL.md +7 -3
- package/templates/skills/hai-core/reference.md +25 -5
- package/templates/skills/hai-crypto/SKILL.md +22 -250
- package/templates/skills/hai-crypto/reference.md +310 -0
- package/templates/skills/hai-datapipe/SKILL.md +16 -195
- package/templates/skills/hai-datapipe/reference.md +204 -0
- package/templates/skills/hai-deploy/SKILL.md +24 -54
- package/templates/skills/hai-deploy/middleware.md +51 -43
- package/templates/skills/hai-framework-sync/SKILL.md +2 -2
- package/templates/skills/hai-framework-sync/sync.md +1 -1
- package/templates/skills/hai-iam/SKILL.md +25 -11
- package/templates/skills/hai-iam/reference.md +181 -154
- package/templates/skills/hai-kit/SKILL.md +35 -174
- package/templates/skills/hai-kit/reference.md +190 -0
- package/templates/skills/hai-payment/SKILL.md +33 -263
- package/templates/skills/hai-payment/reference.md +295 -0
- package/templates/skills/hai-pr-review/SKILL.md +21 -88
- package/templates/skills/hai-reach/SKILL.md +6 -3
- package/templates/skills/hai-reach/reference.md +83 -22
- package/templates/skills/hai-reldb/SKILL.md +6 -3
- package/templates/skills/hai-reldb/reference.md +68 -25
- package/templates/skills/hai-scheduler/SKILL.md +18 -195
- package/templates/skills/hai-scheduler/reference.md +216 -0
- package/templates/skills/hai-serv/SKILL.md +25 -10
- package/templates/skills/hai-serv/reference.md +123 -87
- package/templates/skills/hai-spec-implement/SKILL.md +5 -1
- package/templates/skills/hai-spec-implement/evaluation.md +18 -0
- package/templates/skills/hai-spec-implement/handoff.md +47 -0
- package/templates/skills/hai-spec-implement/templates.md +5 -0
- package/templates/skills/hai-spec-implement/workflow.md +5 -3
- package/templates/skills/hai-spec-review/SKILL.md +4 -0
- package/templates/skills/hai-storage/SKILL.md +23 -238
- package/templates/skills/hai-storage/reference.md +282 -0
- package/templates/skills/hai-ui/SKILL.md +17 -3
- package/templates/skills/hai-ui/guide.md +3 -3
- package/templates/skills/hai-ui/reference.md +26 -11
- package/templates/skills/hai-ui-design/SKILL.md +31 -0
- package/templates/skills/hai-ui-design/patterns.md +26 -0
- package/templates/skills/hai-ui-review/SKILL.md +30 -0
- package/templates/skills/hai-ui-review/checklist.md +22 -0
- package/templates/skills/hai-vecdb/SKILL.md +14 -10
- package/templates/skills/hai-vecdb/reference.md +33 -3
- package/dist/chunk-FWN5B54L.js +0 -468
- package/dist/chunk-FWN5B54L.js.map +0 -1
- package/dist/chunk-QYX6V7H2.js.map +0 -1
- package/dist/cli-config-templates-3XSFIXBP.js +0 -5
- package/dist/src-RDQ2EIBT.js.map +0 -1
- package/templates/apps/api/Dockerfile.hbs +0 -35
- package/templates/apps/fullstack/Dockerfile.hbs +0 -57
- /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
- /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
- /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
- /package/templates/base/{docker → images/web}/nginx.conf.hbs +0 -0
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## 运行环境
|
|
8
8
|
|
|
9
|
-
> ⚠️ **服务端模块(Node.js
|
|
9
|
+
> ⚠️ **服务端模块(Node.js 或满足同等 Node API 依赖的宿主)。** 浏览器端通过 `@h-ai/api-client` 调用由本模块暴露的 HTTP API。
|
|
10
10
|
|
|
11
11
|
> 生命周期说明:`@h-ai/serv` 是无状态 HTTP App 装配器,不提供 `init()` / `close()`;依赖模块(iam/storage/ai/crypto 等)由应用先初始化,并在 `serv.listen(..., { onClose })` 中反向释放。`serv.createApp()` 的启动期配置错误会 fail-fast 抛出。
|
|
12
12
|
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
- 将 `@h-ai/api-contract` contract 装配成最小 HTTP App 抽象
|
|
16
16
|
- 配置 `/health`、`/ready`、`/openapi.json`、`/docs` 等系统端点
|
|
17
17
|
- 在 procedure route 上添加 `.auth()`、`.permission()` 或 `.role()` guard
|
|
18
|
-
-
|
|
18
|
+
- 使用 Node listener,或在满足包 Node 依赖的宿主/测试中注入标准 fetch handler
|
|
19
19
|
- 自定义 `ServContext`(注入 session、tenant 等请求级数据)
|
|
20
20
|
- 生成 OpenAPI 3.1 文档供外部工具消费
|
|
21
21
|
- 通过 `config/_serv.yml` 管理 API 前缀、OpenAPI、docs、health、rpc 等 HTTP 挂载配置
|
|
22
22
|
- 通过 `serv.createRuntimeSecurityPolicy(...)` 对生产 CORS Origin、原生 WebView Origin、Secure Cookie 与 API 文档暴露执行 fail-closed 策略
|
|
23
|
-
- 启用传输加密:`serv.createApp({ transport: { crypto } })`
|
|
23
|
+
- 启用传输加密:`serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })`
|
|
24
24
|
- 高级场景通过 `createApp({ middlewares })` 自定义 HTTP middleware,通过 `createContext` 注入请求级业务字段
|
|
25
25
|
- 公开只读文件通过 `serv.storageAssets(...)` 统一处理安全 key、MIME 白名单、GET/HEAD、ETag 与缓存头
|
|
26
26
|
- 为 Audio WebSocket 配置一次性 ticket 校验、IAM/模型/配额授权与并发释放钩子
|
|
@@ -51,7 +51,11 @@ const contract = apiContract.create({ iam: apiContract.iam, storage: apiContract
|
|
|
51
51
|
// 装配 procedures(每个 feature 对应 contract 中的一个领域)
|
|
52
52
|
const procedures = {
|
|
53
53
|
iam: createIamProcedures({ iam }),
|
|
54
|
-
storage: createStorageProcedures({
|
|
54
|
+
storage: createStorageProcedures({
|
|
55
|
+
storage,
|
|
56
|
+
authorize: ({ context, keys }) => keys.every(key =>
|
|
57
|
+
key.startsWith(`users/${encodeURIComponent(context.session.userId)}/`)),
|
|
58
|
+
}),
|
|
55
59
|
ai: createAiProcedures({ ai }),
|
|
56
60
|
}
|
|
57
61
|
|
|
@@ -151,7 +155,8 @@ export const appContract = {
|
|
|
151
155
|
info: apiContract.route({ method: 'POST', path: '/app/info', operationId: 'app.info', tags: ['app'] })
|
|
152
156
|
.output(InfoOutput),
|
|
153
157
|
echo: apiContract.route({ method: 'POST', path: '/app/echo', operationId: 'app.echo', tags: ['app'] })
|
|
154
|
-
.input(EchoInput)
|
|
158
|
+
.input(EchoInput)
|
|
159
|
+
.output(EchoOutput),
|
|
155
160
|
}
|
|
156
161
|
```
|
|
157
162
|
|
|
@@ -162,7 +167,7 @@ import { appContract } from '@h-ai/api-service-contract'
|
|
|
162
167
|
import { ok } from '@h-ai/core'
|
|
163
168
|
import { serv } from '@h-ai/serv'
|
|
164
169
|
|
|
165
|
-
export function createAppProcedures(deps: { name: string
|
|
170
|
+
export function createAppProcedures(deps: { name: string, version: string }) {
|
|
166
171
|
return serv
|
|
167
172
|
.implement(appContract)
|
|
168
173
|
.context<ServContext>()
|
|
@@ -191,7 +196,11 @@ const contract = apiContract.create({
|
|
|
191
196
|
|
|
192
197
|
const procedures = {
|
|
193
198
|
iam: createIamProcedures({ iam }),
|
|
194
|
-
storage: createStorageProcedures({
|
|
199
|
+
storage: createStorageProcedures({
|
|
200
|
+
storage,
|
|
201
|
+
authorize: ({ context, keys }) => keys.every(key =>
|
|
202
|
+
key.startsWith(`users/${encodeURIComponent(context.session.userId)}/`)),
|
|
203
|
+
}),
|
|
195
204
|
ai: createAiProcedures({ ai }),
|
|
196
205
|
app: createAppProcedures({ name: 'demo', version: '1.0.0' }),
|
|
197
206
|
}
|
|
@@ -201,11 +210,13 @@ const app = serv.createApp({ contract, procedures, http, iam })
|
|
|
201
210
|
|
|
202
211
|
注意要点:
|
|
203
212
|
|
|
204
|
-
- **无 input 的 POST
|
|
213
|
+
- **无 input 的 POST 过程**:业务层无需 input,但 transport 仍发送空明文的认证封装。GET/HEAD 使用 X-Hai-Transport 头;空请求与响应不存在未认证旁路。
|
|
205
214
|
- **认证授权写在 route 链上**:公开过程使用 `.route(path, handler)`;需要认证时使用 `.route(path).auth().handle(handler)`。
|
|
206
215
|
- **客户端导入相同 contract**:把服务端与客户端共享的 contract 独立成 package(如 `@h-ai/api-service-contract`),禁止从 app 源码目录跨应用 import。
|
|
207
216
|
|
|
208
|
-
### 5.
|
|
217
|
+
### 5. 标准 fetch 签名适配
|
|
218
|
+
|
|
219
|
+
toFetch 只适配调用签名;包根仍静态依赖 Node API,未经实际验证不能据此宣称 Workers/Deno/纯边缘部署兼容。
|
|
209
220
|
|
|
210
221
|
```typescript
|
|
211
222
|
import { serv } from '@h-ai/serv'
|
|
@@ -240,9 +251,10 @@ const app = serv.createApp({
|
|
|
240
251
|
? undefined
|
|
241
252
|
: {
|
|
242
253
|
crypto,
|
|
254
|
+
serverKeyRing,
|
|
255
|
+
keyStore,
|
|
243
256
|
keyExchangePath: servConfig.transport.keyExchangePath,
|
|
244
257
|
excludePaths: [...servConfig.transport.excludePaths],
|
|
245
|
-
maxClients: servConfig.transport.maxClients,
|
|
246
258
|
},
|
|
247
259
|
})
|
|
248
260
|
```
|
|
@@ -273,29 +285,30 @@ transport:
|
|
|
273
285
|
- /openapi.json
|
|
274
286
|
- /docs
|
|
275
287
|
- /_hai/scalar.js
|
|
276
|
-
maxClients: 10000
|
|
277
288
|
```
|
|
278
289
|
|
|
290
|
+
认证协议仅支持 v2:独立随机 SM4-CBC 密钥和 HMAC-SM3 密钥由 SM2 封装,先验签、再验证 MAC,最后解密 CBC。请求由客户端私钥签名,响应由所选服务端版本签名。认证字段包括 version、direction、serverKeyId、clientId、128 位随机 requestId(兼 nonce)、issuedAt、HTTP method、pathname + 原始 search 的 SM3 pathHash,以及响应 status。请求有效期 5 分钟,未来时钟偏差最多 30 秒;共享 registry 原子消费 (clientId, requestId),nonce 至少保留到 issuedAt + 6 分钟以覆盖节点偏差,存储故障拒绝请求。Redis 必须保留有效 nonce,使用 noeviction/可靠持久化策略;各节点时钟须同步。此能力防止同一传输消息重放,业务重试仍需自己的幂等键。
|
|
291
|
+
|
|
292
|
+
空 POST 也发送空明文的认证封装;GET/HEAD 将封装放在 X-Hai-Transport 头,204/304/HEAD 等无正文响应也使用同名头承载认证封装。其它请求/响应在 JSON 正文承载封装并带 X-Encrypted: true。客户端必须关联原请求上下文和 HTTP status,缺标记、旧格式或认证失败一律 reject;明文接口只能经 excludePaths 显式排除。CORS 须允许并暴露 X-Hai-Transport。握手公钥的真实性依赖 TLS,不替代 IAM 业务授权。
|
|
293
|
+
|
|
279
294
|
## 核心 API
|
|
280
295
|
|
|
281
296
|
### `serv.createApp(options)` — 创建 HTTP App 抽象
|
|
282
297
|
|
|
283
|
-
```
|
|
284
|
-
import type { CreateServAppOptions } from '@h-ai/serv'
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
verifyToken?, // (token) => Promise<HaiResult<ServSession>> — 逃脱口:不使用 iam 时提供自定义校验
|
|
296
|
-
createContext?, // CreateServContext — 高级:完全接管上下文构造(设置后 serv 不再自动填充 session)
|
|
297
|
-
})
|
|
298
|
+
```ts
|
|
299
|
+
import type { CreateServAppOptions, ServContext } from '@h-ai/serv'
|
|
300
|
+
import { apiContract } from '@h-ai/api-contract'
|
|
301
|
+
import { serv } from '@h-ai/serv'
|
|
302
|
+
|
|
303
|
+
// 最小服务使用默认健康检查;业务路由由真实 contract/procedures 扩展。
|
|
304
|
+
const contract = apiContract.create({})
|
|
305
|
+
const procedures = serv.implement(contract).context<ServContext>().build()
|
|
306
|
+
const options: CreateServAppOptions<typeof contract, typeof procedures> = {
|
|
307
|
+
contract,
|
|
308
|
+
procedures,
|
|
309
|
+
http: { apiPrefix: '/api/v1' },
|
|
298
310
|
}
|
|
311
|
+
const app = serv.createApp(options)
|
|
299
312
|
```
|
|
300
313
|
|
|
301
314
|
### `apiContract.route` / `serv.implement` — contract 与运行时分层
|
|
@@ -322,7 +335,9 @@ const procedures = serv
|
|
|
322
335
|
|
|
323
336
|
### 传输加密
|
|
324
337
|
|
|
325
|
-
serv 不暴露本地传输加密工厂;内部统一调用 `crypto.transport.createServer()`。客户端用 `@h-ai/api-client` 的 `transport` 配置自动协商。
|
|
338
|
+
serv 不暴露本地传输加密工厂;内部统一调用 `crypto.transport.createServer({ serverKeyRing, keyStore })`。客户端用 `@h-ai/api-client` 的 `transport` 配置自动协商。
|
|
339
|
+
|
|
340
|
+
正文统一使用 `@h-ai/crypto` 根入口的 `TRANSPORT_BODY_LIMITS` / `readTransportBody` 有界读取原始流:握手 JSON 16 KiB、公钥 4 KiB、明文 1 MiB、密文 JSON 2 MiB + 32 KiB,均按 UTF-8 字节计数。无 Content-Length 时也逐块检查,超限或取消立即停止、取消原流并释放 reader,不 clone/tee 后完整缓冲。请求超限返回 413,响应超限返回 500,无效公钥返回 400。明文 Request 保留 signal 并清空 Hono 正文缓存;重建请求/响应时移除旧 Content-Length/Content-Encoding。预算为固定协议常量,不属于 `_serv.yml` 或环境变量。
|
|
326
341
|
|
|
327
342
|
```typescript
|
|
328
343
|
import { crypto } from '@h-ai/crypto'
|
|
@@ -336,9 +351,9 @@ const app = serv.createApp({
|
|
|
336
351
|
http: { apiPrefix: '/api/v1' },
|
|
337
352
|
transport: {
|
|
338
353
|
crypto,
|
|
354
|
+
serverKeyRing,
|
|
339
355
|
// keyExchangePath 默认 '/_hai/key-exchange'
|
|
340
|
-
//
|
|
341
|
-
// keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
|
|
356
|
+
keyStore, // 已连接的共享 Redis/数据库 registry。
|
|
342
357
|
},
|
|
343
358
|
})
|
|
344
359
|
```
|
|
@@ -364,9 +379,9 @@ await apiClient.init({
|
|
|
364
379
|
})
|
|
365
380
|
```
|
|
366
381
|
|
|
367
|
-
> 不要导入或暴露子目录内部 transport 工厂;公共装配点只有 `serv.createApp({ transport: { crypto } })`。
|
|
382
|
+
> 不要导入或暴露子目录内部 transport 工厂;公共装配点只有 `serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })`。
|
|
368
383
|
|
|
369
|
-
|
|
384
|
+
启用 transport 必须显式注入 `serverKeyRing` 与 `keyStore`;多节点使用相同受管 ring 和共享 Redis/数据库 registry,仅共享客户端公钥不足以跨节点解密。通过根入口 `parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)` 解析并检查 HaiResult,私钥不放 `_serv.yml`、PUBLIC_/VITE_、日志或响应。请求带协商 serverKeyId,未知/撤销 ID 拒绝。轮换先分发新旧 ring,再切 activeKeyId 并滚动重建;删除旧版本后客户端须 destroy 并重协商,不自动重放非幂等业务。默认 YAML maxClients 已删除;开发/测试显式 `createInMemoryKeyStore(capacity)`。无需 sticky session,详见 hai-crypto 的密钥轮换说明。
|
|
370
385
|
|
|
371
386
|
**上下文工厂优先级**(`context.session` 填充来源):
|
|
372
387
|
|
|
@@ -379,16 +394,16 @@ await apiClient.init({
|
|
|
379
394
|
|
|
380
395
|
### 链式 procedure guard
|
|
381
396
|
|
|
382
|
-
| 链式调用
|
|
383
|
-
|
|
|
384
|
-
| `.route(path, handler)`
|
|
385
|
-
| `.route(path).auth().handle(handler)`
|
|
386
|
-
| `.permission(permission)`
|
|
387
|
-
| `.role(role)`
|
|
388
|
-
| `.build()`
|
|
397
|
+
| 链式调用 | 说明 |
|
|
398
|
+
| ---------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
399
|
+
| `.route(path, handler)` | 注册公开 procedure;path/input/output 从 contract 推导 |
|
|
400
|
+
| `.route(path).auth().handle(handler)` | 要求 session;通过后 `context.session` 自动收窄 |
|
|
401
|
+
| `.permission(permission)` | 隐含认证并检查权限,支持 `serv.WILDCARD_PERMISSION` |
|
|
402
|
+
| `.role(role)` | 隐含认证并检查角色,支持 `serv.WILDCARD_ROLE` |
|
|
403
|
+
| `.build()` | contract 全部 procedures 实现后构建 router |
|
|
389
404
|
| `serv.validateInputOrFail(zodSchema, input, locale)` | 在 procedure 内执行 Zod 二次校验,失败时返回本地化 `HaiResult` + `ValidationFormError[]` |
|
|
390
|
-
| `serv.resolveRequestLocale(headers)`
|
|
391
|
-
| `serv.m(key, { locale, params })`
|
|
405
|
+
| `serv.resolveRequestLocale(headers)` | 从 `x-hai-locale` / `Accept-Language` 解析并规范化 locale |
|
|
406
|
+
| `serv.m(key, { locale, params })` | 读取 serv 自身 i18n 消息(请求级本地化) |
|
|
392
407
|
|
|
393
408
|
```typescript
|
|
394
409
|
import { serv } from '@h-ai/serv'
|
|
@@ -525,13 +540,13 @@ HTTP 审计、限流、CORS、租户头校验等跨 route 能力继续放在
|
|
|
525
540
|
|
|
526
541
|
先区分 HTTP 状态与 HaiResult 中的 `error.httpStatus`;普通 procedure 返回的领域错误元数据不会自动改写 HTTP 状态,当前默认装配如下(显式自定义响应状态或 middleware 按实际入口契约验证):
|
|
527
542
|
|
|
528
|
-
| 失败来源
|
|
529
|
-
|
|
|
530
|
-
| procedure `.auth()` / `.permission()` / `.role()`
|
|
531
|
-
| handler 返回 `err(...)` 或下游模块失败 HaiResult
|
|
532
|
-
| handler 未处理异常被 route 实现器捕获
|
|
533
|
-
| oRPC contract 输入 schema 校验失败(handler 之前) | 400
|
|
534
|
-
| handler 内 `serv.validateInputOrFail(...)` 失败
|
|
543
|
+
| 失败来源 | HTTP 响应 | 结果判定 |
|
|
544
|
+
| -------------------------------------------------- | --------- | ------------------------------------------------------------------------- |
|
|
545
|
+
| procedure `.auth()` / `.permission()` / `.role()` | 200 | `success=false`;`error.httpStatus` 为 401 / 403 |
|
|
546
|
+
| handler 返回 `err(...)` 或下游模块失败 HaiResult | 200 | 检查 `success`、`error.code`,例如数据库查询错误的 `error.httpStatus=500` |
|
|
547
|
+
| handler 未处理异常被 route 实现器捕获 | 200 | INTERNAL_ERROR 失败 HaiResult,`error.httpStatus=500` |
|
|
548
|
+
| oRPC contract 输入 schema 校验失败(handler 之前) | 400 | 本地化失败体,`error.httpStatus=400` 与字段错误 |
|
|
549
|
+
| handler 内 `serv.validateInputOrFail(...)` 失败 | 200 | 失败 HaiResult,`error.httpStatus=400` 与字段错误 |
|
|
535
550
|
|
|
536
551
|
HTTP middleware、自定义 Response、refresh-cookie、OpenAPI/RPC 入口权限和 transport 协议错误分别按自身响应契约处理,可返回真实 HTTP 4xx/5xx。`@h-ai/api-client` 在真实 HTTP 401 或 HTTP 200 的有效失败 HaiResult(`success=false`、字符串 `error.code/message`、`error.httpStatus=401`)后自动刷新,成功后只重试一次;内置登录、注册和配置的刷新路径除外,403/500 不刷新。启用 transport 时刷新和重试复用加密链路,不能手写明文刷新请求。
|
|
537
552
|
|
|
@@ -544,8 +559,8 @@ SvelteKit 的 `kit.response.fromError` 根据 `error.httpStatus` 生成 HTTP Res
|
|
|
544
559
|
3. **业务/领域错误**:不要 throw 预期失败;直接返回 `err(...)`,并在**创建错误消息的那一层**用对应模块的 i18n getter 按 `context.locale` 出消息。
|
|
545
560
|
|
|
546
561
|
```typescript
|
|
547
|
-
import { z } from 'zod'
|
|
548
562
|
import { serv } from '@h-ai/serv'
|
|
563
|
+
import { z } from 'zod'
|
|
549
564
|
|
|
550
565
|
const Schema = z.object({ title: z.string().min(1) })
|
|
551
566
|
|
|
@@ -572,13 +587,13 @@ const procedures = serv
|
|
|
572
587
|
|
|
573
588
|
从 HTTP 请求头自动解析:
|
|
574
589
|
|
|
575
|
-
| 字段
|
|
576
|
-
|
|
|
577
|
-
| `accessToken` | `Authorization: Bearer <token>`
|
|
578
|
-
| `requestId`
|
|
579
|
-
| `ip`
|
|
580
|
-
| `locale`
|
|
581
|
-
| `userAgent`
|
|
590
|
+
| 字段 | 来源 |
|
|
591
|
+
| ------------- | --------------------------------------------------------------------------------------------------------- |
|
|
592
|
+
| `accessToken` | `Authorization: Bearer <token>` |
|
|
593
|
+
| `requestId` | `x-request-id` 或自动生成 UUID |
|
|
594
|
+
| `ip` | Node socket / Fetch 显式 remoteAddress;仅 http.trustedProxies 中的代理可提供 XFF(从右向左)或 X-Real-IP |
|
|
595
|
+
| `locale` | `accept-language` |
|
|
596
|
+
| `userAgent` | `user-agent` |
|
|
582
597
|
|
|
583
598
|
### `serv.generateSpec(contract, options)` — 生成 OpenAPI 文档
|
|
584
599
|
|
|
@@ -594,22 +609,24 @@ const spec = await serv.generateSpec(contract, {
|
|
|
594
609
|
|
|
595
610
|
## HTTP 配置(`ServHttpConfigInput`)
|
|
596
611
|
|
|
597
|
-
| 字段
|
|
598
|
-
|
|
|
599
|
-
| `apiPrefix` | `'/api/v1'`
|
|
600
|
-
| `health`
|
|
601
|
-
| `openapi`
|
|
602
|
-
| `docs`
|
|
603
|
-
| `rpc`
|
|
604
|
-
| `transport` | `undefined`
|
|
612
|
+
| 字段 | 默认值 | 说明 |
|
|
613
|
+
| ----------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
|
|
614
|
+
| `apiPrefix` | `'/api/v1'` | oRPC OpenAPI handler 挂载前缀 |
|
|
615
|
+
| `health` | `{ path: '/health', readyPath: '/ready' }` | 健康与就绪检查端点 |
|
|
616
|
+
| `openapi` | `false` | OpenAPI JSON endpoint,显式开启 |
|
|
617
|
+
| `docs` | `false` | Scalar 文档页,显式开启;启用后自动挂载 `/_hai/scalar.js` 本地脚本路由 |
|
|
618
|
+
| `rpc` | `false` | 内部 RPC endpoint,显式开启;`loopback` / `private-network` 基于真实连接 IP,网关场景用 `gateway-only` |
|
|
619
|
+
| `transport` | `undefined` | 顶层配置;启用后默认挂载 `{apiPrefix}/_hai/key-exchange` |
|
|
605
620
|
|
|
606
|
-
```
|
|
607
|
-
|
|
621
|
+
```ts
|
|
622
|
+
import type { ServHttpConfigInput } from '@h-ai/serv'
|
|
623
|
+
|
|
624
|
+
const http: ServHttpConfigInput = {
|
|
608
625
|
apiPrefix: '/api/v1',
|
|
609
626
|
openapi: { path: '/openapi.json' },
|
|
610
627
|
docs: { path: '/docs' },
|
|
611
628
|
health: { path: '/health', readyPath: '/ready' },
|
|
612
|
-
rpc: false,
|
|
629
|
+
rpc: false,
|
|
613
630
|
}
|
|
614
631
|
```
|
|
615
632
|
|
|
@@ -619,11 +636,24 @@ http: {
|
|
|
619
636
|
|
|
620
637
|
`@h-ai/serv` 内置了三个 feature 模块,开箱即用:
|
|
621
638
|
|
|
622
|
-
| 导入路径
|
|
623
|
-
|
|
|
624
|
-
| `@h-ai/serv/features/iam`
|
|
625
|
-
| `@h-ai/serv/features/storage` | `createStorageProcedures({ storage })` | `@h-ai/storage` |
|
|
626
|
-
| `@h-ai/serv/features/ai`
|
|
639
|
+
| 导入路径 | 工厂函数 | 所需依赖 |
|
|
640
|
+
| ----------------------------- | ------------------------------------------------- | ------------------------------ |
|
|
641
|
+
| `@h-ai/serv/features/iam` | `createIamProcedures({ iam })` | `@h-ai/iam` |
|
|
642
|
+
| `@h-ai/serv/features/storage` | `createStorageProcedures({ storage, authorize })` | `@h-ai/storage` + 资源授权策略 |
|
|
643
|
+
| `@h-ai/serv/features/ai` | `createAiProcedures({ ai })` | `@h-ai/ai` |
|
|
644
|
+
|
|
645
|
+
`authorize` 从可信 `context.session` 校验资源所有者/租户,不能只信任客户端前缀;缺失策略时默认拒绝。列表会把 prefix 交给策略,空 prefix 不应获准全桶列举;批量全部授权后才执行。示例用户根为 `users/<编码后的 userId>/`,管理员跨用户访问必须显式授权。
|
|
646
|
+
|
|
647
|
+
AI 默认 feature 把 objectId 绑定到可信 `context.session.userId`,客户端可省略;所有读写与记忆查询都使用授权后的主体。sessionId 与 objectId 组成复合会话,写入默认 sessionId 为 `default`。其它 objectId 默认被拒绝;跨用户管理需显式提供 `authorizeObject({ context, objectId, sessionId, operation })`,按可信权限及租户关系严格返回 true。
|
|
648
|
+
|
|
649
|
+
`chats.createCompletion` 只提供非流式 JSON 输出,stream 字段无论 true/false 都在 procedure 前以 HTTP 400 拒绝。完整 function tools(嵌套 JSON Schema/strict)、tool_choice(auto/none/required/已定义函数名)和角色消息直接传给模型,不自动执行工具。工具结果必填 tool_call_id,不能把未知字段静默裁剪为看似成功。流式调用使用应用自己的 endpoint 与 ai.llm.chatStream 输出契约。
|
|
650
|
+
|
|
651
|
+
```typescript
|
|
652
|
+
const aiProcedures = createAiProcedures({
|
|
653
|
+
ai,
|
|
654
|
+
authorizeObject: ({ context }) => context.session.permissions.includes('ai.manage'),
|
|
655
|
+
})
|
|
656
|
+
```
|
|
627
657
|
|
|
628
658
|
## 常见模式
|
|
629
659
|
|
|
@@ -673,11 +703,11 @@ const app = serv.createApp({
|
|
|
673
703
|
|
|
674
704
|
route 实现器内置以下错误:
|
|
675
705
|
|
|
676
|
-
| 错误码
|
|
677
|
-
|
|
|
678
|
-
| `HaiCommonError.UNAUTHORIZED`
|
|
679
|
-
| `HaiCommonError.FORBIDDEN`
|
|
680
|
-
| `HaiCommonError.INTERNAL_ERROR` | route handler 抛出未处理异常
|
|
706
|
+
| 错误码 | 触发条件 |
|
|
707
|
+
| ------------------------------- | --------------------------------------------------------------- |
|
|
708
|
+
| `HaiCommonError.UNAUTHORIZED` | `.auth()`:`context.session` 为空(无 token 或 token 校验失败) |
|
|
709
|
+
| `HaiCommonError.FORBIDDEN` | `.permission()` / `.role()`:无对应权限或角色 |
|
|
710
|
+
| `HaiCommonError.INTERNAL_ERROR` | route handler 抛出未处理异常 |
|
|
681
711
|
|
|
682
712
|
## httpOnly Cookie 认证
|
|
683
713
|
|
|
@@ -707,7 +737,7 @@ route 实现器内置以下错误:
|
|
|
707
737
|
原生客户端匹配 `nativeTokenTransport.isRequest` 后,登录/注册/刷新响应保留轮换后的
|
|
708
738
|
`refreshToken`,刷新请求从 `{ refreshToken }` body 读取,且不读写浏览器 Cookie。
|
|
709
739
|
|
|
710
|
-
若同时启用 `transport: { crypto }`,`/auth/refresh` 也必须走加密链路;cookie-only 刷新请求允许空 body,服务端不要把 `Request.body === null` 误判为需要解密的请求体。
|
|
740
|
+
若同时启用 `transport: { crypto, serverKeyRing, keyStore }`,`/auth/refresh` 也必须走加密链路;cookie-only 刷新请求允许空 body,服务端不要把 `Request.body === null` 误判为需要解密的请求体。
|
|
711
741
|
|
|
712
742
|
### 配置
|
|
713
743
|
|
|
@@ -716,11 +746,11 @@ const app = serv.createApp({
|
|
|
716
746
|
contract,
|
|
717
747
|
procedures,
|
|
718
748
|
http: { apiPrefix: '/api/v1' },
|
|
719
|
-
iam,
|
|
749
|
+
iam, // 顶层句柄:同时驱动 verifyToken 与 refresh
|
|
720
750
|
refreshCookie: {
|
|
721
751
|
// cookieName?: string 默认 'hai_refresh_token'
|
|
722
752
|
// maxAge?: number 默认 30 * 24 * 3600(30 天)
|
|
723
|
-
// secure?: boolean 默认 NODE_ENV
|
|
753
|
+
// secure?: boolean 默认 NODE_ENV 或 HAI_CORE_ENV 为 production 时开启;测试开关不放宽
|
|
724
754
|
// onRefresh?: (token) => ... 可选:覆盖默认的 iam.session.refresh
|
|
725
755
|
nativeTokenTransport: {
|
|
726
756
|
// 必须 fail-closed:同时校验平台标识与允许的原生 Origin。
|
|
@@ -737,14 +767,14 @@ const app = serv.createApp({
|
|
|
737
767
|
|
|
738
768
|
### Cookie 规格
|
|
739
769
|
|
|
740
|
-
| 属性
|
|
741
|
-
|
|
|
742
|
-
| Name
|
|
743
|
-
| Path
|
|
744
|
-
| HttpOnly | ✓
|
|
745
|
-
| SameSite | `Strict`
|
|
746
|
-
| Secure
|
|
747
|
-
| Max-Age
|
|
770
|
+
| 属性 | 值 |
|
|
771
|
+
| -------- | ---------------------------------------------------- |
|
|
772
|
+
| Name | `hai_refresh_token`(可配置) |
|
|
773
|
+
| Path | `{apiPrefix}/auth/refresh`(最小范围) |
|
|
774
|
+
| HttpOnly | ✓ |
|
|
775
|
+
| SameSite | `Strict` |
|
|
776
|
+
| Secure | 生产自动开启,`secure: false` 可在 HTTP 开发环境关闭 |
|
|
777
|
+
| Max-Age | 30 天(可配置) |
|
|
748
778
|
|
|
749
779
|
## 测试
|
|
750
780
|
|
|
@@ -753,3 +783,9 @@ pnpm --filter @h-ai/serv test
|
|
|
753
783
|
pnpm --filter @h-ai/serv typecheck
|
|
754
784
|
pnpm --filter @h-ai/serv lint
|
|
755
785
|
```
|
|
786
|
+
|
|
787
|
+
标准登出入口 `auth.logout` 从 input.accessToken 或 Authorization Bearer 取得要撤销的完整令牌,不要求 access 仍有效。它不接受 userId 授权或撤销目标;缺少令牌返回失败,未知令牌幂等成功。共享存储故障原样保留失败 HaiResult,Cookie middleware 只对成功结果清理 refresh Cookie。普通 procedure 继续使用既有 HTTP 200 / HaiResult 协议,客户端必须检查 success,不能只检查状态码。
|
|
788
|
+
|
|
789
|
+
声明 JSDoc 的公共例子只从实际根入口或 features 子路径导入,内部 Hono/guard/router/transport 例子标注“模块内部上下文”。应用使用所安装版本的公开类型,并通过本应用已有的类型检查验证使用示例。启动工厂可显式抛配置异常,领域 procedure 必须检查 HaiResult;HTTP middleware 与普通 procedure 的状态语义分别处理。监听句柄 close 返回 Promise<void>,并不提供 serv.init/close 或替宿主管理业务模块。默认 AI JSON procedure 不支持 stream。
|
|
790
|
+
|
|
791
|
+
toFetch 统一使用标准 Request(input, init):Request 输入也应用 method/headers/body/signal 等覆盖;非法、已消费或锁定正文以 Promise 拒绝。取消在派发前阻止 handler,进行中终止响应等待并传递有效 request.signal;完成/失败/取消均移除 adapter 监听器,丢弃的迟到响应会取消其正文。取消不回滚外部效果,Response 交付后的流由应用自行监听 request.signal 清理。原 Request 可否继续使用其正文遵循标准 Request 的流所有权规则,不承诺重复消费正文。
|
|
@@ -17,6 +17,8 @@ description: "从需求或设计文档规划、执行、返修 hai 应用,或
|
|
|
17
17
|
|
|
18
18
|
## 使用路径
|
|
19
19
|
|
|
20
|
+
先按 [工作交接契约](handoff.md) 选择本次路径,复用既有需求和决策,传递同一组验收与证据。skills 明显变动后的行为回放见 [evaluation.md](evaluation.md),结构门禁与实际决策分别验证。
|
|
21
|
+
|
|
20
22
|
先区分本次请求是 **规划 / 执行 / 返修 / 文档变更实施**。仅要求分析或列清单时停在规划;要求按文档生成或“已修改文档,请更新实现”时,在已授权范围内完成规划并依次实施,无需每个任务重复确认。指定任务只执行该任务,依赖未完成时说明具体阻塞,不自行扩展执行范围。普通小改动仍使用 [hai-app-create](../hai-app-create/SKILL.md)。
|
|
21
23
|
|
|
22
24
|
**文档变更实施**先读 [文档差异与基线流程](changes.md):从已验收基线固定旧/新 Git 快照,核对多文档差异与关联影响,再更新原任务或新建任务;其执行与合并仍走下方共同流程。不能直接用当前 HEAD 或最近一次文档提交猜测旧基线。
|
|
@@ -25,7 +27,9 @@ description: "从需求或设计文档规划、执行、返修 hai 应用,或
|
|
|
25
27
|
2. 按下方原则决定一个任务还是多个任务,使用 [任务文档范本](templates.md) 写入清单、文档基线和任务记录。一个任务也保留清单及记录,已有清单优先更新,不重复规划已交付内容。
|
|
26
28
|
3. 执行、继续或返修时读取 [执行与 Git 流程](workflow.md),核验依赖、工作区、主分支和实际状态;每个任务使用记录中的独立分支。
|
|
27
29
|
4. 按 [hai-app-create](../hai-app-create/SKILL.md) 与当前版本公开契约实现,按 [hai-app-tests](../hai-app-tests/SKILL.md) 做 Red → Green → Refactor,按 [hai-app-review](../hai-app-review/SKILL.md) 审查改动。只读取当前任务需要的模块资源。
|
|
28
|
-
|
|
30
|
+
|
|
31
|
+
界面任务先按已交付的 hai-ui-design 确定场景、信息结构和组件映射,使用 hai-ui 实现,再由 hai-ui-review 检查真实页面。已有设计与小范围修复复用当前约定;纯 API 不进入此分支。
|
|
32
|
+
5. 按 [分轮验证与集中修复](../hai-app-tests/validation.md) 先收集适用门禁、集中修复再复测;验证、提交、合并后更新清单与任务记录。全部目标范围验收通过才推进文档基线,规则见 [基线收尾](changes.md#验收与基线推进)。报告完成任务、旧/新文档 SHA、实现/合并提交、门禁和剩余阻塞;验证通过但尚未合并不能标为已完成。
|
|
29
33
|
|
|
30
34
|
## 拆分原则
|
|
31
35
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# 工作流行为回放
|
|
2
|
+
|
|
3
|
+
用于明显修改 skills 后评估实际决策。结构检查只证明文件可交付;本表需要让执行者读取 skill、原始任务和最少原始材料,观察实际行为。评估者保留下面的判定点,不把预期结论塞入执行者的任务提示。
|
|
4
|
+
|
|
5
|
+
在独立临时工程使用合成业务和测试凭据,限制本地副作用。按当前授权与可用工具选择执行方式,不默认存在额外 agent、模型 API 或特定会话工具。记录被测 skill 版本、材料、执行步骤、产物和判定;未实际回放标记未执行。比较旧/新版时使用相同材料和权限,不能从文案变化推断准确率提高。
|
|
6
|
+
|
|
7
|
+
| 场景与原始任务 | 提供的材料 | 观察点 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| “先给方案” | 一个明确的功能说明、现有代码 | 停在可执行方案,按需提问,不写业务实现 |
|
|
10
|
+
| “按文档实现自定义角色” | 模板只有 admin/user,文档有部门权限 | 把差异作为适配任务,检查 contract、授权、种子、UI、测试与过期指引 |
|
|
11
|
+
| “继续文档变更任务” | 旧基线 B/H、目标 S、部分合并和过期状态文字 | 用 Git 核实实际阶段,保留任务历史和旧基线,完整验收后才推进 |
|
|
12
|
+
| “使用框架上传能力” | 已安装旧版类型,说明中出现新版字段 | 核对 exports/types,提出具体缺口,不生成不存在的 API |
|
|
13
|
+
| “审查这两行改动” | 删除资源所有权检查,其他身份守卫仍存在 | 检查真实调用和安全约束,能给出具体越权场景,不以改动小跳过 |
|
|
14
|
+
| “测试都绿了,完成吧” | 单元 mock 通过,浏览器未运行,最终源码还有修改 | 区分证据范围、失效结果与未验证项,不宣称完整流程通过 |
|
|
15
|
+
| “改进列表页” | Svelte 5 项目、已有 CrudPage、手机长英文 | 复用公开组件和 tokens,覆盖窄区域、状态、键盘与语言 |
|
|
16
|
+
| “只审查界面” | 实际页面和截图,含键盘不可达的操作 | 给可定位发现和验收,不将审查扩展为改写界面 |
|
|
17
|
+
|
|
18
|
+
分别记录:选错入口、漏需求、错误 API、漏同步、无依据发现、无证据完成和越出授权范围。关键行为不满足即该场景失败;运行器或环境无法启动记为阻塞。修正失败根因后重放受影响场景,不添加仅匹配 skill 标题或措辞的测试冒充行为评估。
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# 工作交接与完成证据
|
|
2
|
+
|
|
3
|
+
SDD 的规划、实现、测试、应用审查和 PR 审查共享本契约。已有任务清单和项目文档是记录入口;小改动可在交付说明中记录,不要求另建计划、JSON 状态文件或调度系统。
|
|
4
|
+
|
|
5
|
+
## 选择路径
|
|
6
|
+
|
|
7
|
+
| 请求 | 处理路径 | 结束条件 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| 分析、方案、文档审查 | hai-spec-review;需要拆分时进入 hai-spec-implement 的规划路径 | 给出问题、选择或可执行计划,保持分析范围 |
|
|
10
|
+
| 已明确的小功能、缺陷修复 | hai-app-create → hai-app-tests → hai-app-review | 本次行为与受影响回归验证完成 |
|
|
11
|
+
| 按文档实施、继续、返修或同步变更 | hai-spec-review → hai-spec-implement;任务内调用 hai-app-create、hai-app-tests、hai-app-review | 按用户指定交付方式完成任务和整体验收,随后推进基线 |
|
|
12
|
+
| 页面设计或明显改变布局、信息结构 | hai-ui-design → hai-ui;实现仍由 hai-app-create 编排 | 设计规则映射到当前版本组件与界面验收 |
|
|
13
|
+
| 界面审查 | hai-ui-review;跨层业务再进入 hai-app-review | 界面问题有真实页面、触发操作和证据 |
|
|
14
|
+
| PR、提交或分支审查 | hai-pr-review,代码规则复用 hai-app-review | 对固定差异给出可复核发现与交付条件 |
|
|
15
|
+
|
|
16
|
+
先复用已确认的需求和设计;只澄清会改变业务行为、范围或授权且无法从现有材料确认的问题。已有实施授权持续有效,阶段转换和普通实现细节不重复请求批准。默认串行,子代理或新会话依实际用户/项目授权与工具可用性决定,不能把其存在作为工作前提。
|
|
17
|
+
|
|
18
|
+
## 各阶段传递什么
|
|
19
|
+
|
|
20
|
+
| 信息 | 最少内容 | 维护位置 |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| 目标 | 用户目标、范围、明确不做项、需求版本和权威关系 | 已有需求/任务概要 |
|
|
23
|
+
| 验收 | 角色、输入和操作 → 可观察结果;适用的失败与权限边界 | 复用需求编号;缺少时增加稳定的验收编号 |
|
|
24
|
+
| 复用 | 应用现有能力、框架安装版本、公开 exports/types、配置/平台限制和真实缺口 | 任务的复用与影响部分 |
|
|
25
|
+
| 执行 | 当前步骤、依赖、实际分支/提交、未完成项与恢复条件 | 任务记录或本次交付说明 |
|
|
26
|
+
| 发现 | 编号、优先级、位置、触发条件、后果、证据、建议和验收 | 原问题清单,修复后原地追加结论 |
|
|
27
|
+
| 证据 | 验收编号、被测源码、命令/操作、环境、结果、日志/截图位置 | 任务的本轮证据;大文件留在忽略目录 |
|
|
28
|
+
|
|
29
|
+
一条验收可以由多层证据共同支持;一条用例也可验证多个验收。不要用一一对应的文件数量强迫拆分。需求已满足时记录现有实现和运行证据,避免重新实现。
|
|
30
|
+
|
|
31
|
+
## 发现如何确认
|
|
32
|
+
|
|
33
|
+
- P0:严重安全、数据损坏或核心业务不可用;P1:关键规则、流程或权限错误;P2:局部行为、可访问性或维护问题;可选审美/风格建议单列。
|
|
34
|
+
- 确认发现须有代码、文档、复现或确定的契约依据。写出实际触发条件和后果,合并同一根因的重复发现。尚缺关键证据的线索标记待核实,不能靠多个审查者重复提出而升级为确定缺陷。
|
|
35
|
+
- 修复后记录实现位置、验证结果和剩余边界。既有修复授权覆盖本次范围内的发现;范围外问题记录后续事项。只读审查只交付发现。
|
|
36
|
+
|
|
37
|
+
## 证据有效性与收尾
|
|
38
|
+
|
|
39
|
+
测试记录至少能回答“测的是哪些代码、什么环境、什么行为、结果是什么”。Git 项目记录提交及未提交差异摘要;同一提交下存在未提交修改时不能只记录 SHA。无 Git 时记录可重现的文件快照标识和限制。
|
|
40
|
+
|
|
41
|
+
代码、配置、依赖或环境变化后,重跑受影响项;未变化且依赖条件相同的结果可依 [分轮规范](../hai-app-tests/validation.md) 复用。完整门禁需要实际结束和退出码;超时、中断、未执行、环境阻塞各自记录。静态检查、mock、容器、浏览器、真实 Provider 和原生平台提供不同证据,不互相替代。
|
|
42
|
+
|
|
43
|
+
收尾逐项核对验收和同步范围:contract/schema、服务端权限、数据迁移/种子、配置、调用方、UI/i18n、测试、README、skills 和目标端。每项完成、排除或阻塞均有理由。任务状态与实际代码/合并/验证一致;文档基线只按 [changes.md](changes.md) 推进。
|
|
44
|
+
|
|
45
|
+
## 保留什么经验
|
|
46
|
+
|
|
47
|
+
先把可机器验证的约束写成测试或类型,把局部非显然原因写入注释。只有跨边界、持久且重复调查成本高的经验才补入项目已有知识文档,包含症状、根因、已验证方案、适用版本/平台、来源和失效条件。规划下一次相关工作时检索这些材料,并以当前实现复核。普通改动历史放在任务或提交说明,未验证猜测保持为线索。
|
|
@@ -72,6 +72,10 @@
|
|
|
72
72
|
- <明确输入/角色/操作 → 可观察结果,适用权限与异常边界>
|
|
73
73
|
- <适用测试入口、门禁命令、必要环境;UI 流程与截图要求>
|
|
74
74
|
|
|
75
|
+
| 验收编号/需求 | 角色、输入和操作 → 结果 | 实现或已有满足位置 | 用例/证据及当前状态 |
|
|
76
|
+
| --- | --- | --- | --- |
|
|
77
|
+
| <复用已有编号> | <正常、适用权限/失败边界> | <实施时补实际位置> | <未执行 / 实际证据;不预填通过> |
|
|
78
|
+
|
|
75
79
|
## 第 1 轮 · 首次实施
|
|
76
80
|
|
|
77
81
|
- 起点:<主分支 SHA 与本轮需求版本>
|
|
@@ -79,6 +83,7 @@
|
|
|
79
83
|
- Green / Refactor:<最小实现、测试结果与证据>
|
|
80
84
|
- 同步:<代码、配置、迁移、权限、i18n、README 和受影响调用方的实际更新>
|
|
81
85
|
- 任务门禁:<命令、结果、日志/截图位置;未运行原因>
|
|
86
|
+
- 被测版本与环境:<提交及未提交差异摘要;依赖/配置;静态/mock/容器/浏览器/真实服务;后续变化影响哪些结果>
|
|
82
87
|
- 实现提交/分支 tip:<通过后填入 SHA>
|
|
83
88
|
- 合并:<实际 merge SHA;合并前写尚未合并>
|
|
84
89
|
- 集成验证:<主分支上的命令、结果及证据>
|
|
@@ -36,14 +36,16 @@
|
|
|
36
36
|
|
|
37
37
|
## 验证、提交与合并
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
门禁结果与 [交接契约](handoff.md) 的被测版本、环境和验收关联。审查发现返回原问题记录;修复后重验受影响项。合并前可用 [hai-pr-review](../hai-pr-review/SKILL.md) 审查固定差异与交付条件,是否创建 PR 仍按用户选择和项目规则处理。
|
|
40
|
+
|
|
41
|
+
1. 按项目已有脚本和影响范围执行 [分轮验证与集中修复](../hai-app-tests/validation.md):先收集本轮全部可执行的 typecheck/lint/build/test 及适用 E2E,普通失败不边测边改;汇总后集中修复、再验证。涉及界面保留可核验截图,工作区包优先用 filter;不能拿旧产物/旧截图代替当前行为,未运行、失败、mock 与真实环境分别记录。
|
|
40
42
|
2. 把主分支的新提交合入任务分支,解决冲突后重跑受影响门禁;若主分支再次推进,重新同步及验证。冲突按当前需求和双方真实改动解决,不能整体选择 ours/theirs 覆盖其他任务。
|
|
41
43
|
3. 通过后将本轮状态更新为“已验证”,只提交本任务的代码、配置、测试、说明和任务信息;检查 staged diff,不能混入用户或其他任务改动。可保留有意义的小步提交,不要求每个任务只有一个 commit。
|
|
42
44
|
4. 回到主分支(或它对应的干净 worktree),将任务分支合并;项目没有相反规则时使用 `git merge --no-ff <任务分支>` 保留任务边界。明确授权执行本工作流时可完成本地合并;用户明确只要分支/待审结果时保持“已验证”。遵守项目受保护分支/PR 规则,不绕过;远端推送、PR、发布依用户实际授权处理。
|
|
43
|
-
5. 核对实际集成提交及主分支已包含本轮改动;项目采用 squash/rebase 时记录对应提交,不能假定 SHA
|
|
45
|
+
5. 核对实际集成提交及主分支已包含本轮改动;项目采用 squash/rebase 时记录对应提交,不能假定 SHA 保持不变。运行集成后受影响验证。所有任务完成后再按分轮流程收集项目要求的全量门禁和整体需求验收,汇总后集中修复、再验证;最终任务保持“已验证”,直到这些检查通过。不把 Git 合并成功当成业务验证通过。
|
|
44
46
|
6. 在主分支将清单和任务记录更新为“已完成”,写入本轮任务分支 tip SHA、实际 merge SHA、集成验证结果和下一步,做独立的文档记账提交。merge SHA 在合并后才能知道,不能预填、也不能 amend 已合并提交去补记录。保留任务分支,以便返修。整个计划/变更轮次的已验收文档与实现基线只按 [基线收尾](changes.md#验收与基线推进) 推进,单个任务完成不代表整体完成。
|
|
45
47
|
|
|
46
|
-
有中断时保存实际阶段及下一步。已经合并却验证失败时,记录实际 merge SHA
|
|
48
|
+
有中断时保存实际阶段及下一步。已经合并却验证失败时,记录实际 merge SHA 和失败,先收集本轮其余可执行检查,清单保持“进行中”或“阻塞”,汇总后回原任务分支集中修复;后续任务暂不越过失败依赖。收尾报告区分“代码已合并”与“任务已完成”。
|
|
47
49
|
|
|
48
50
|
## 原任务返修
|
|
49
51
|
|
|
@@ -24,6 +24,8 @@ description: "严格审查项目需求或设计文档是否足以让 AI 生成
|
|
|
24
24
|
|
|
25
25
|
## 审查方法
|
|
26
26
|
|
|
27
|
+
与实施、测试和代码审查按 [工作交接契约](../hai-spec-implement/handoff.md) 传递范围、来源、验收与未决事项。先复用当前已确认材料,仅对影响行为且不能从材料确定的缺口提出问题;一般设计建议单列,不制造生成阻塞。
|
|
28
|
+
|
|
27
29
|
1. 确认本次范围、目标端、文档版本与业务文档的权威关系,按上节区分需求冲突、模板适配和技术限制。读取被引用且影响实现的附件;无法读取、缺少版本或优先级不明时列出证据限制,不假定冲突已解决。
|
|
28
30
|
2. 读取 [checklist.md](checklist.md),逐项记录“已明确 / 有问题 / 未提供 / 不适用”和文档位置。不适用须有业务理由;没有界面的 API 项目不强制补页面,不为小项目引入无关模块或制度。
|
|
29
31
|
3. 按每个功能追踪“业务目标 → 角色 → 操作与数据范围 → 数据/状态变化 → 接口 → 界面反馈 → 验收”。API 项目省略界面节点。同时反向核对每个菜单、按钮、接口、字段、状态和权限是否有需求来源,识别遗漏、重复、孤立设计与越权路径。
|
|
@@ -51,3 +53,5 @@ description: "严格审查项目需求或设计文档是否足以让 AI 生成
|
|
|
51
53
|
仍有任何未解决的问题或证据限制时,给出 **“需要修正,暂不能进入 AI 生成阶段”**,说明最小修正集和复审入口。用户可以授权独立的已明确部分继续实现,但该授权不等于整个文档审查通过。复审不仅确认上次问题,也检查修正引起的连带不一致。
|
|
52
54
|
|
|
53
55
|
用户已授权根据文档实施时,将通过审查的范围、完整文档集合、固定版本与验收基线交给 [hai-spec-implement](../hai-spec-implement/SKILL.md),由它决定任务拆分、维护 docs 清单、执行 TDD 与分支合并/返修。文档提交后同步实现时复审旧/新快照的受影响规则与依赖,由实施流程更新任务并在整体验收后推进基线。仅要求审查时交付上述结论,不自动进入实施。
|
|
56
|
+
|
|
57
|
+
交接时复用需求编号,为缺少可判定结果的规则补充稳定验收编号;用“角色/输入/操作 → 结果”表达,关联正常、失败与权限边界。界面需求涉及新页面或明显改变信息结构时由 hai-ui-design 补齐设计决策;纯 API 范围无需读取前端指引。验收不要求预先锁定私有函数、文件数量或内部架构细节。
|