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

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 (122) hide show
  1. package/README.en.md +31 -9
  2. package/README.ja.md +30 -8
  3. package/README.md +38 -16
  4. package/dist/{chunk-X3RSYNBG.js → chunk-FWN5B54L.js} +94 -4
  5. package/dist/chunk-FWN5B54L.js.map +1 -0
  6. package/dist/chunk-QYX6V7H2.js +5613 -0
  7. package/dist/chunk-QYX6V7H2.js.map +1 -0
  8. package/dist/cli-config-templates-3XSFIXBP.js +5 -0
  9. package/dist/{cli-config-templates-QRF6B5T4.js.map → cli-config-templates-3XSFIXBP.js.map} +1 -1
  10. package/dist/{cli-skill-templates-EZFW7D43.js → cli-skill-templates-TSYEQTCY.js} +3 -3
  11. package/dist/{cli-skill-templates-EZFW7D43.js.map → cli-skill-templates-TSYEQTCY.js.map} +1 -1
  12. package/dist/index.js +733 -210
  13. package/dist/index.js.map +1 -1
  14. package/dist/{src-DUGLX4YB.js → src-RDQ2EIBT.js} +1054 -660
  15. package/dist/{src-DUGLX4YB.js.map → src-RDQ2EIBT.js.map} +1 -1
  16. package/package.json +9 -7
  17. package/templates/apps/admin/src/routes/+layout.svelte.hbs +4 -2
  18. package/templates/apps/api/.dockerignore.hbs +4 -0
  19. package/templates/apps/api/.gitignore.hbs +6 -5
  20. package/templates/apps/api/Dockerfile.hbs +5 -8
  21. package/templates/apps/api/apps/{{projectName}}-contract/package.json.hbs +2 -0
  22. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +2 -2
  23. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +2 -2
  24. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +4 -4
  25. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +8 -2
  26. package/templates/apps/api/hai.build.json.hbs +1 -0
  27. package/templates/apps/api/package.json.hbs +13 -7
  28. package/templates/apps/fullstack/.dockerignore.hbs +4 -3
  29. package/templates/apps/fullstack/.gitignore.hbs +24 -10
  30. package/templates/apps/fullstack/Dockerfile.hbs +6 -8
  31. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +0 -3
  32. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +5 -1
  33. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +8 -4
  34. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +3 -6
  35. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +6 -2
  36. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +73 -2
  37. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +2 -2
  38. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +8 -4
  39. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +2 -6
  40. package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +5 -1
  41. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +8 -4
  42. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +24 -1
  43. package/templates/apps/fullstack/hai.build.json.hbs +7 -0
  44. package/templates/apps/fullstack/package.json.hbs +16 -11
  45. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +2 -2
  46. package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +2 -0
  47. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +1 -1
  48. package/templates/apps/fullstack/packages/{{projectName}}-contract/tests/contract.test.ts.hbs +8 -14
  49. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +8 -6
  50. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -8
  51. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +3 -3
  52. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +8 -2
  53. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +4 -4
  54. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +4 -2
  55. package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +2 -0
  56. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +14 -12
  57. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +1 -1
  58. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +17 -3
  59. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +15 -5
  60. package/templates/apps/fullstack/playwright.config.ts.hbs +6 -1
  61. package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +0 -1
  62. package/templates/apps/h5/src/routes/+layout.svelte.hbs +13 -13
  63. package/templates/apps/mobile-app/index.html.hbs +1 -0
  64. package/templates/apps/mobile-app/public/runtime-config.js +1 -0
  65. package/templates/apps/mobile-app/src/App.svelte.hbs +46 -117
  66. package/templates/apps/mobile-app/src/lib/api.ts.hbs +2 -1
  67. package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +3 -2
  68. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +58 -0
  69. package/templates/apps/mobile-app/src/lib/pages/profile.svelte.hbs +13 -0
  70. package/templates/apps/mobile-app/src/lib/pages/tasks.svelte.hbs +16 -0
  71. package/templates/base/.dockerignore.hbs +4 -0
  72. package/templates/base/.gitignore.hbs +20 -5
  73. package/templates/base/Dockerfile.hbs +10 -11
  74. package/templates/base/docker/nginx.conf.hbs +6 -0
  75. package/templates/base/e2e/app.spec.ts.hbs +14 -0
  76. package/templates/base/hai.build.json.hbs +1 -0
  77. package/templates/base/package.json.hbs +25 -17
  78. package/templates/base/svelte.config.js.hbs +2 -0
  79. package/templates/base/tests/scaffold.test.ts.hbs +9 -3
  80. package/templates/base/vite.config.ts.hbs +11 -5
  81. package/templates/shared/.gitattributes +5 -0
  82. package/templates/shared/container-runtime-config.sh +30 -0
  83. package/templates/shared/hash-router.svelte.ts +83 -0
  84. package/templates/shared/run-e2e.mjs +65 -0
  85. package/templates/shared/vite-runtime-config.ts +54 -0
  86. package/templates/skills/AGENTS.md +15 -1
  87. package/templates/skills/bridges/admin/AGENTS.md +17 -1
  88. package/templates/skills/bridges/api/AGENTS.md +17 -3
  89. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +35 -11
  90. package/templates/skills/bridges/h5/AGENTS.md +17 -1
  91. package/templates/skills/bridges/mobile-app/AGENTS.md +19 -3
  92. package/templates/skills/bridges/website/AGENTS.md +17 -1
  93. package/templates/skills/hai-ai/reference.md +2 -2
  94. package/templates/skills/hai-app-create/SKILL.md +5 -3
  95. package/templates/skills/hai-app-create/development.md +128 -2
  96. package/templates/skills/hai-app-create/reference.md +4 -4
  97. package/templates/skills/hai-app-review/SKILL.md +2 -2
  98. package/templates/skills/hai-app-tests/SKILL.md +2 -1
  99. package/templates/skills/hai-app-tests/reference.md +1 -1
  100. package/templates/skills/hai-build/SKILL.md +16 -1
  101. package/templates/skills/hai-core/SKILL.md +2 -1
  102. package/templates/skills/hai-deploy/SKILL.md +52 -252
  103. package/templates/skills/hai-deploy/middleware.md +88 -0
  104. package/templates/skills/hai-framework-sync/SKILL.md +1 -1
  105. package/templates/skills/hai-framework-sync/sync.md +1 -1
  106. package/templates/skills/hai-reach/reference.md +8 -8
  107. package/templates/skills/hai-spec-implement/SKILL.md +12 -9
  108. package/templates/skills/hai-spec-implement/changes.md +67 -0
  109. package/templates/skills/hai-spec-implement/templates.md +25 -3
  110. package/templates/skills/hai-spec-implement/workflow.md +7 -6
  111. package/templates/skills/hai-spec-review/SKILL.md +14 -5
  112. package/templates/skills/hai-spec-review/checklist.md +4 -3
  113. package/templates/skills/hai-storage/SKILL.md +2 -0
  114. package/templates/skills/hai-ui/reference.md +2 -0
  115. package/dist/chunk-WW2MIO6T.js +0 -992
  116. package/dist/chunk-WW2MIO6T.js.map +0 -1
  117. package/dist/chunk-X3RSYNBG.js.map +0 -1
  118. package/dist/cli-config-templates-QRF6B5T4.js +0 -5
  119. /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/AuditManagement.svelte.hbs → pages/audit.svelte.hbs} +0 -0
  120. /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/RoleManagement.svelte.hbs → pages/roles.svelte.hbs} +0 -0
  121. /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/ProfileSettings.svelte.hbs → pages/settings.svelte.hbs} +0 -0
  122. /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/UserManagement.svelte.hbs → pages/users.svelte.hbs} +0 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "engines": { "node": "^22.22.2 || ^24.15.0 || >=26.0.0" },
3
3
  "name": "{{projectName}}",
4
- "version": "0.1.0-alpha.64",
4
+ "version": "0.1.0-alpha.66",
5
5
  "private": true,
6
6
  "packageManager": "pnpm@12.11.2",
7
7
  "description": "{{#if_eq appType 'admin'}}Enterprise management system built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'website'}}Enterprise website built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'h5'}}Mobile H5 app built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'api'}}REST API service built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'mobile-app'}}Mobile app built with hai Agent Framework{{/if_eq}}",
@@ -21,33 +21,37 @@
21
21
  "paraglide:compile": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide",
22
22
  {{/if}}
23
23
  "dev": "vite dev",
24
- "build": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}vite build",
24
+ "dev:test": "vite dev --mode test",
25
+ "dev:prod": "vite dev --mode prod",
26
+ "compile": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}vite build",
27
+ "build": "hai build",
28
+ "deploy": "hai deploy",
29
+ {{#unless isSvelteOnlyApp}}
30
+ "start": "node --env-file=.env --env-file=.env.prod --env-file-if-exists=.env.local build/index.js",
31
+ "start:test": "node --env-file=.env --env-file=.env.test --env-file-if-exists=.env.local build/index.js",
32
+ "start:prod": "pnpm start",
33
+ {{/unless}}
25
34
  "preview": "vite preview",
26
- "check": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}{{#if isSvelteOnlyApp}}svelte-check --tsconfig ./tsconfig.json{{else}}svelte-kit sync && svelte-check --tsconfig ./tsconfig.json{{/if}}",
35
+ "check": "pnpm typecheck",
27
36
  "typecheck": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}{{#if isSvelteOnlyApp}}svelte-check --tsconfig ./tsconfig.json{{else}}svelte-kit sync && svelte-check --tsconfig ./tsconfig.json{{/if}}",
28
37
  "lint": "eslint .",
38
+ "lint:fix": "eslint . --fix",
29
39
  "test": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}vitest run",
30
40
  "test:watch": "vitest",
31
- "test:e2e": "playwright test",
32
- "clean": "{{#if isSvelteOnlyApp}}rimraf dist build android ios .turbo src/lib/paraglide{{else}}rimraf .svelte-kit build .turbo{{/if}}",
33
- "docker:build": "docker build --target runtime -t {{projectName}}:latest .",
34
- {{#unless isSvelteOnlyApp}}
35
- "docker:build:data": "docker build --target runtime-with-data --build-context embedded-data=./data -t {{projectName}}:latest .",
36
- {{/unless}}
37
- "podman:build": "podman build --format docker --jobs=1 --target runtime -t {{projectName}}:latest ."{{#unless isSvelteOnlyApp}},
38
- "podman:build:data": "podman build --format docker --jobs=1 --target runtime-with-data --build-context embedded-data=./data -t {{projectName}}:latest ."{{/unless}}{{#if isCapacitorApp}},
41
+ "test:e2e": "node scripts/run-e2e.mjs",
42
+ "clean": "{{#if isSvelteOnlyApp}}rimraf dist build android ios .turbo src/lib/paraglide{{else}}rimraf .svelte-kit build .turbo{{/if}}"{{#if isCapacitorApp}},
39
43
  "cap:add:android": "cap add android",
40
44
  "cap:add:ios": "cap add ios",
41
- "cap:sync": "pnpm build && cap sync",
42
- "cap:sync:android": "pnpm build && cap sync android",
43
- "cap:sync:ios": "pnpm build && cap sync ios",
45
+ "cap:sync": "pnpm compile && cap sync",
46
+ "cap:sync:android": "pnpm compile && cap sync android",
47
+ "cap:sync:ios": "pnpm compile && cap sync ios",
44
48
  "cap:android": "cap open android",
45
49
  "cap:ios": "cap open ios",
46
50
  "cap:run:android": "cap run android",
47
51
  "cap:run:ios": "cap run ios",
48
- "cap:build:android:debug": "pnpm build && cap sync android && cd android && gradlew.bat assembleDebug",
49
- "cap:build:android:release": "pnpm build && cap sync android && cap build android --androidreleasetype APK",
50
- "cap:build:ios": "pnpm build && cap sync ios && cap build ios"{{/if}}
52
+ "cap:build:android:debug": "pnpm compile && cap sync android && cd android && gradlew.bat assembleDebug",
53
+ "cap:build:android:release": "pnpm compile && cap sync android && cap build android --androidreleasetype APK",
54
+ "cap:build:ios": "pnpm compile && cap sync ios && cap build ios"{{/if}}
51
55
  },
52
56
  "dependencies": {
53
57
  {{#if isSvelteOnlyApp}}
@@ -79,6 +83,10 @@
79
83
  {{/if}}
80
84
  },
81
85
  "devDependencies": {
86
+ "@h-ai/cli": "{{haiPackageSpecifier}}",
87
+ {{#if isSvelteOnlyApp}}
88
+ "@h-ai/core": "{{haiPackageSpecifier}}",
89
+ {{/if}}
82
90
  "@antfu/eslint-config": "{{versions.antfuEslintConfig}}",
83
91
  "@playwright/test": "{{versions.playwright}}",
84
92
  {{#if isCapacitorApp}}
@@ -56,6 +56,8 @@ const config = {
56
56
  runes: true,
57
57
  },
58
58
  kit: {
59
+ // 环境文件由 Vite 的 core loader 统一加载;Kit 不再二次读取根目录私有配置。
60
+ env: { dir: './.svelte-kit' },
59
61
  {{#if isCapacitorApp}}
60
62
  adapter: adapter({
61
63
  pages: 'build',
@@ -3,6 +3,9 @@ import type { CoreConfig } from '@h-ai/core'
3
3
  {{/unless}}
4
4
  import { readFile } from 'node:fs/promises'
5
5
  import { join } from 'node:path'
6
+ {{#unless isSvelteOnlyApp}}
7
+ import process from 'node:process'
8
+ {{/unless}}
6
9
  import { fileURLToPath } from 'node:url'
7
10
  {{#unless isSvelteOnlyApp}}
8
11
  import { core } from '@h-ai/core'
@@ -43,9 +46,12 @@ describe('generated scaffold', () => {
43
46
  {{#unless isCapacitorApp}}
44
47
 
45
48
  it('ships an application config loaded at startup', async () => {
46
- const coreConfig = await readFile(join(rootDir, 'config/_core.yml'), 'utf-8')
47
- expect(coreConfig).toContain('name: {{projectName}}')
48
- expect(coreConfig).toContain('defaultLocale: {{defaultLocale}}')
49
+ const result = core.config.load<CoreConfig>('core', join(rootDir, 'config/_core.yml'))
50
+ expect(result.success).toBe(true)
51
+ if (!result.success)
52
+ return
53
+ expect(result.data.name).toBe(process.env.HAI_CORE_NAME ?? '{{projectName}}')
54
+ expect(result.data.defaultLocale).toBe(process.env.HAI_CORE_DEFAULTLOCALE ?? '{{defaultLocale}}')
49
55
  })
50
56
  {{/unless}}
51
57
  {{#unless isSvelteOnlyApp}}
@@ -6,9 +6,7 @@ import type { CoreConfig } from '@h-ai/core'
6
6
  {{/if}}
7
7
  import path from 'node:path'
8
8
  import process from 'node:process'
9
- {{#if isSvelteOnlyApp}}
10
9
  import { core } from '@h-ai/core'
11
- {{/if}}
12
10
  {{#if hasUi}}
13
11
  import { haiOptimizeExclude, haiPrebundledDeps } from '@h-ai/ui/vite'
14
12
  {{/if}}
@@ -23,10 +21,16 @@ import { sveltekit } from '@sveltejs/kit/vite'
23
21
  {{#if hasUi}}
24
22
  import tailwindcss from '@tailwindcss/vite'
25
23
  {{/if}}
26
- import { defineConfig, loadEnv } from 'vite'
24
+ import { defineConfig } from 'vite'
25
+ {{#if isSvelteOnlyApp}}
26
+ import { publicRuntimeConfig } from './scripts/vite-runtime-config.js'
27
+ {{/if}}
27
28
 
28
- export default defineConfig(({ mode }) => {
29
- const env = loadEnv(mode, process.cwd(), '')
29
+ export default defineConfig(({ mode, command }) => {
30
+ const envResult = command === 'build' ? { success: true as const, data: {} as Record<string, string> } : core.config.loadEnvFiles({ mode, envDir: process.cwd() })
31
+ if (!envResult.success)
32
+ throw new Error(envResult.error.message)
33
+ const env = envResult.data
30
34
  Object.assign(process.env, env)
31
35
  {{#if isSvelteOnlyApp}}
32
36
  // 浏览器无法读取 YAML;构建时通过 core 解析,仅注入明确可公开的字段。
@@ -42,12 +46,14 @@ export default defineConfig(({ mode }) => {
42
46
  {{/if}}
43
47
 
44
48
  return {
49
+ envDir: false,
45
50
  envPrefix: ['VITE_', 'PUBLIC_'],
46
51
  {{#if isSvelteOnlyApp}}
47
52
  define: { __APP_CORE_CONFIG__: JSON.stringify(appConfig) },
48
53
  {{/if}}
49
54
  plugins: [
50
55
  {{#if isSvelteOnlyApp}}
56
+ publicRuntimeConfig({ apiBase: process.env.PUBLIC_API_BASE, core: appConfig }),
51
57
  svelte(),
52
58
  {{else}}
53
59
  sveltekit(),
@@ -0,0 +1,5 @@
1
+ # Windows 构建机交付 Linux 启动脚本时必须保留 LF,避免 /bin/sh 解释器失效。
2
+ *.sh text eol=lf
3
+ *.sh.hbs text eol=lf
4
+ *.envsh text eol=lf
5
+ *.envsh.hbs text eol=lf
@@ -0,0 +1,30 @@
1
+ #!/bin/sh
2
+ set -eu
3
+
4
+ # 只编码公开白名单;Base64 不会把环境值中的引号或换行解释为 JavaScript。
5
+ encode() { printf '%s' "$1" | base64 | tr -d '\n'; }
6
+ mkdir -p /tmp/hai-runtime-config
7
+ cat > /tmp/hai-runtime-config/runtime-config.js <<EOF
8
+ {
9
+ const decode = value => new TextDecoder().decode(Uint8Array.from(atob(value), character => character.charCodeAt(0)));
10
+ const nonempty = object => Object.fromEntries(Object.entries(object).filter(([, value]) => value !== ''));
11
+ const core = nonempty({
12
+ name: decode('$(encode "${HAI_CORE_NAME:-}")'),
13
+ version: decode('$(encode "${HAI_CORE_VERSION:-}")'),
14
+ env: decode('$(encode "${HAI_CORE_ENV:-}")'),
15
+ defaultLocale: decode('$(encode "${HAI_CORE_DEFAULTLOCALE:-}")')
16
+ });
17
+ const logging = nonempty({
18
+ level: decode('$(encode "${HAI_CORE_LOGGING_LEVEL:-}")'),
19
+ format: decode('$(encode "${HAI_CORE_LOGGING_FORMAT:-}")')
20
+ });
21
+ if (Object.keys(logging).length) core.logging = logging;
22
+ const config = nonempty({
23
+ apiBase: decode('$(encode "${PUBLIC_API_BASE:-}")'),
24
+ apiTransport: decode('$(encode "${PUBLIC_API_TRANSPORT:-}")'),
25
+ apiKeyExchangePath: decode('$(encode "${PUBLIC_API_KEY_EXCHANGE_PATH:-}")')
26
+ });
27
+ if (Object.keys(core).length) config.core = core;
28
+ globalThis.__HAI_PUBLIC_CONFIG__ = { ...globalThis.__HAI_PUBLIC_CONFIG__, ...config };
29
+ }
30
+ EOF
@@ -0,0 +1,83 @@
1
+ import { SvelteMap, SvelteURL } from 'svelte/reactivity'
2
+
3
+ /**
4
+ * 页面目录驱动的 hash 导航;仅管理 URL,不包含业务权限或页面加载逻辑。
5
+ * @example
6
+ * ```ts
7
+ * const pages = import.meta.glob('../pages/*.svelte')
8
+ * const router = new HashRouter(Object.keys(pages), '/home')
9
+ * const stop = router.start(() => void loadCurrentPage())
10
+ * router.navigate('/reports/weekly?period=month')
11
+ * stop()
12
+ * ```
13
+ */
14
+ export class HashRouter {
15
+ /** 当前页面路径,例如 `/reports/weekly`。 */
16
+ path = $state('')
17
+ /** 当前页面查询串,例如 `?period=month`。 */
18
+ search = $state('')
19
+ private readonly files = new SvelteMap<string, string>()
20
+
21
+ private readonly fallback: string
22
+
23
+ /**
24
+ * 建立页面文件到 URL 的映射,新增页面无需维护路由表。
25
+ * @param pageFiles - import.meta.glob 返回的文件名
26
+ * @param fallback - 空路径和未知页面的回退路径
27
+ * @example new HashRouter(Object.keys(import.meta.glob('../pages/*.svelte')), '/home')
28
+ */
29
+ constructor(pageFiles: string[], fallback: string) {
30
+ this.fallback = fallback
31
+ for (const file of pageFiles) {
32
+ const route = file.replace(/^.*\/pages/, '').replace(/\.svelte$/, '').replace(/\/index$/, '') || '/'
33
+ this.files.set(route, file)
34
+ }
35
+ if (!this.files.has(fallback))
36
+ throw new Error('Hash router fallback page is missing')
37
+ this.path = fallback
38
+ }
39
+
40
+ /** 当前路径对应的页面文件;可用于索引 import.meta.glob 的惰性加载器。 */
41
+ get file(): string {
42
+ return this.files.get(this.path) ?? this.files.get(this.fallback)!
43
+ }
44
+
45
+ /**
46
+ * 恢复地址栏状态并监听链接、前进与后退;卸载时调用返回的清理函数。
47
+ * @param onChange - URL 导航完成后的业务通知
48
+ * @returns 移除事件监听器的函数
49
+ * @example const stop = router.start(() => void store.navigate(router.path.slice(1)))
50
+ */
51
+ start(onChange: () => void = () => {}): () => void {
52
+ const synchronize = () => {
53
+ this.navigate(location.hash.slice(1), true)
54
+ onChange()
55
+ }
56
+ synchronize()
57
+ window.addEventListener('hashchange', synchronize)
58
+ return () => {
59
+ window.removeEventListener('hashchange', synchronize)
60
+ }
61
+ }
62
+
63
+ /**
64
+ * 导航到页面;未知路径回退,查询串随 hash 保留并可在刷新后恢复。
65
+ * @param target - 以 `/` 开头的本地页面路径和查询串
66
+ * @param replace - 是否替换当前历史项
67
+ * @example router.navigate('/settings?section=profile')
68
+ */
69
+ navigate(target: string, replace = false): void {
70
+ const local = target.startsWith('/') && !target.startsWith('//') && !target.includes('\\')
71
+ const url = new SvelteURL(local ? target : this.fallback, 'https://hash.invalid')
72
+ const known = this.files.has(url.pathname)
73
+ this.path = known ? url.pathname : this.fallback
74
+ this.search = known ? url.search : ''
75
+ const hash = `#${this.path}${this.search}`
76
+ if (location.hash !== hash) {
77
+ if (replace)
78
+ history.replaceState(history.state, '', hash)
79
+ else
80
+ history.pushState(history.state, '', hash)
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,65 @@
1
+ /** 运行浏览器验收,并在报告写完后清理本次输出;失败和中断同样清理。 */
2
+ import { spawn } from 'node:child_process'
3
+ import { mkdirSync, mkdtempSync, rmSync } from 'node:fs'
4
+ import { createRequire } from 'node:module'
5
+ import { tmpdir } from 'node:os'
6
+ import { join, resolve } from 'node:path'
7
+ import process from 'node:process'
8
+
9
+ const cwd = process.cwd()
10
+ const require = createRequire(resolve(cwd, 'package.json'))
11
+ const cli = require.resolve('@playwright/test/cli')
12
+ const dataParent = resolve(process.env.HAI_E2E_DATA_ROOT || tmpdir())
13
+ mkdirSync(dataParent, { recursive: true })
14
+ const dataRoot = mkdtempSync(join(dataParent, 'hai-browser-e2e-'))
15
+ const groups = [[]]
16
+ for (const argument of process.argv.slice(2)) {
17
+ if (argument === '--next')
18
+ groups.push([])
19
+ else
20
+ groups.at(-1).push(argument)
21
+ }
22
+ let child
23
+ let interrupted = false
24
+ function stop() {
25
+ if (interrupted)
26
+ return
27
+ interrupted = true
28
+ if (process.platform === 'win32' && child?.pid && child.exitCode === null) {
29
+ // Windows 的 kill 不传递 SIGTERM;同时结束本次 Playwright 创建的浏览器与服务进程。
30
+ spawn('taskkill', ['/pid', String(child.pid), '/t', '/f'], { windowsHide: true, stdio: 'ignore' })
31
+ .once('error', () => child?.kill())
32
+ }
33
+ else {
34
+ child?.kill()
35
+ }
36
+ }
37
+ process.once('SIGINT', stop)
38
+ process.once('SIGTERM', stop)
39
+ try {
40
+ for (const args of groups) {
41
+ const code = await new Promise((resolve, reject) => {
42
+ child = spawn(process.execPath, [cli, 'test', ...args], {
43
+ cwd,
44
+ windowsHide: true,
45
+ stdio: 'inherit',
46
+ env: { ...process.env, HAI_AUTH_EVIDENCE_DIR: join(cwd, 'test-results/evidence'), HAI_E2E_DATA_ROOT: dataRoot },
47
+ })
48
+ child.once('error', reject)
49
+ child.once('exit', resolve)
50
+ })
51
+ if (code !== 0 || interrupted) {
52
+ process.exitCode = typeof code === 'number' && code !== 0 ? code : 1
53
+ break
54
+ }
55
+ }
56
+ }
57
+ finally {
58
+ process.removeListener('SIGINT', stop)
59
+ process.removeListener('SIGTERM', stop)
60
+ // 固定为当前测试工程中的标准输出目录,禁止删除调用方任意指定的路径。
61
+ for (const name of ['test-results', 'playwright-report'])
62
+ rmSync(join(cwd, name), { recursive: true, force: true, maxRetries: 3, retryDelay: 100 })
63
+ // Playwright 退出后服务进程已关闭,此时删除 SQLite / storage,避免 Windows 文件锁。
64
+ rmSync(dataRoot, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 })
65
+ }
@@ -0,0 +1,54 @@
1
+ /** Vite 开发与预览的公开运行配置;同一份静态构建可连接不同环境的 API。 */
2
+ import type { CoreConfig } from '@h-ai/core'
3
+ import type { IncomingMessage, ServerResponse } from 'node:http'
4
+ import type { Plugin } from 'vite'
5
+
6
+ /** 只允许浏览器可见的地址、开关和品牌字段,禁止传入 process.env 或完整服务配置。 */
7
+ export interface PublicRuntimeConfig {
8
+ /** 包含 API 前缀的公开地址,例如 https://example.test/api/v1。 */
9
+ apiBase?: string
10
+ /** 客户端传输开关,与服务端一致。 */
11
+ apiTransport?: string
12
+ /** 公开密钥交换端点。 */
13
+ apiKeyExchangePath?: string
14
+ /** 客户端公开元数据;不含密钥或连接参数。 */
15
+ core?: Pick<CoreConfig, 'name' | 'version' | 'env' | 'defaultLocale'> & { logging?: Pick<NonNullable<CoreConfig['logging']>, 'level' | 'format'> }
16
+ }
17
+
18
+ /**
19
+ * 在加载应用前提供无缓存的公开配置;构建只复制空的占位文件。
20
+ * @example `plugins: [publicRuntimeConfig({ apiBase: process.env.PUBLIC_API_BASE })]`
21
+ */
22
+ export function publicRuntimeConfig(config: PublicRuntimeConfig): Plugin {
23
+ const metadata = config.core
24
+ // 明确投影字段,JavaScript 调用方即使误传完整配置也不能公开连接参数或密钥。
25
+ const publicConfig: PublicRuntimeConfig = {
26
+ apiBase: config.apiBase,
27
+ apiTransport: config.apiTransport,
28
+ apiKeyExchangePath: config.apiKeyExchangePath,
29
+ core: metadata
30
+ ? {
31
+ name: metadata.name,
32
+ version: metadata.version,
33
+ env: metadata.env,
34
+ defaultLocale: metadata.defaultLocale,
35
+ logging: metadata.logging ? { level: metadata.logging.level, format: metadata.logging.format } : undefined,
36
+ }
37
+ : undefined,
38
+ }
39
+ const body = `globalThis.__HAI_PUBLIC_CONFIG__ = { ...globalThis.__HAI_PUBLIC_CONFIG__, ...${JSON.stringify(publicConfig)} };\n`
40
+ const respond = (_request: IncomingMessage, response: ServerResponse): void => {
41
+ response.setHeader('Content-Type', 'application/javascript; charset=utf-8')
42
+ response.setHeader('Cache-Control', 'no-store')
43
+ response.end(body)
44
+ }
45
+ return {
46
+ name: 'hai-public-runtime-config',
47
+ configureServer(server) {
48
+ server.middlewares.use('/runtime-config.js', respond)
49
+ },
50
+ configurePreviewServer(server) {
51
+ server.middlewares.use('/runtime-config.js', respond)
52
+ },
53
+ }
54
+ }
@@ -2,13 +2,20 @@
2
2
 
3
3
  > 通用 AI 编程助手入口。优先阅读 `README.md`、现有测试与 `.agents/skills/`;本文件同时作为 `hai add` / AI 支持回填时的 generic fallback。
4
4
 
5
+ ## 需求与模板
6
+
7
+ - 业务行为以用户当前明确要求及其指定的需求/设计文档为准。本文件、README、skills、示例、生成代码及其测试中的模板默认行为是起点,不是不可变的业务约束;角色代码、权限模型、页面流程、字段、限额、存储方式与架构均可按实际需求调整。
8
+ - 需求与模板不同,审查时记录为模板适配项,不据此判定需求错误或阻塞审查,也不要求用户额外授权“覆盖模板”;实施时在已有授权范围内修改或移除冲突逻辑,并同步 contract、鉴权、数据迁移、界面、配置、测试、文档及本文件中的过期规则。
9
+ - 未说明的行为可参考现有实现并注明假设,不能升级为强制需求。只有业务要求自身矛盾、与已确认项目决策的优先级不明,或存在真实技术/安全障碍时才提出具体问题;已明确的规则不重复要求选择。
10
+ - 业务规则与技术契约分别判断:可用 API 以当前安装版本的公开类型为准;输入校验、服务端授权、资源隔离和密钥保护仍须落实。需求涉及替换技术方案时说明影响并同步迁移,不能用保留模板默认值代替解决问题。
11
+
5
12
  ## 行为契约
6
13
 
7
14
  需求/设计文档审查或从文档生成代码时,读取 [hai-spec-review](.agents/skills/hai-spec-review/SKILL.md),严格确认完备性、歧义、技术正确性与命名一致性;影响行为的问题未解决前不判定文档可生成。普通小改动不重审无关文档。
8
15
 
9
16
  升级 hai-framework 到最新/指定版本、比较版本差异或同步框架 skills 时,先读 [框架升级与同步 Skill](.agents/skills/hai-framework-sync/SKILL.md),执行版本发现、差异分析、用户选择/阻塞处理、迁移与验证;仅要求比较时不修改应用。
10
17
 
11
- 根据文档规划、实施、继续或返修任务时,读取 [hai-spec-implement](.agents/skills/hai-spec-implement/SKILL.md):审查后按可验收功能和依赖拆分,维护 docs 下的清单及 docs/tasks 下的任务信息,每项独立分支 TDD、验证并本地合并;返修复用原任务与分支。仅规划不实施,指定任务先核验依赖。
18
+ 根据文档规划、实施、继续、返修或按已提交文档变更同步实现时,读取 [hai-spec-implement](.agents/skills/hai-spec-implement/SKILL.md):维护 docs 清单与任务记录,比较已验收与目标文档 Git 快照,更新原任务或新建任务,每项独立分支 TDD、验证并本地合并;全部差异与整体验收通过后才推进基线。返修复用原任务与分支。仅规划不实施,指定任务先核验依赖。
12
19
 
13
20
  普通新增、修改、修复任务开始前,先读 [应用开发 Skill](.agents/skills/hai-app-create/SKILL.md) 和 [开发规范与框架能力路由](.agents/skills/hai-app-create/development.md);再按任务读取模块 Skill 及其相邻参考文件。架构/构建、审查、测试分别读取 hai-build、hai-app-review、hai-app-tests,不加载整个 skill 树。
14
21
 
@@ -36,6 +43,13 @@
36
43
  3. 改动公共类型、配置、模板或共享逻辑时,同步更新所有调用方与文档。
37
44
  4. 每次改动后按影响范围运行质量门禁并记录结果。
38
45
 
46
+ ## 代码注释与示例
47
+
48
+ - 尽可能补充有信息量的中文 JSDoc:所有 export 的函数/类/方法、核心算法与业务流程、所有数据库表/列、接口/type/schema 及其字段都必须说明用途、单位、约束和关联关系。核心逻辑解释决策原因、边界、权限与失败路径,不逐行复述代码。
49
+ - 函数按实际契约写 @param、@returns,仅确实抛错时写 @throws;异步、HaiResult 与事务失败行为须说明。类型/表结构说明字段含义、可空性、默认值、主外键和唯一约束。
50
+ - 上述函数和结构的 JSDoc 均提供可理解的 @example:函数给调用及结果判断,结构给合法数据,表给典型行或查询示例。省略上下文要标明,不使用真实凭据。示例随实现同步,不能只堆叠没有信息量的注释。
51
+ - 调用及结构示例见 [.agents/skills/hai-app-create/development.md](.agents/skills/hai-app-create/development.md) 的 JSDoc 注释一节。
52
+
39
53
  ## 质量门禁
40
54
 
41
55
  先核对 package.json,按影响范围执行已有脚本;文档检查引用,UI/路由变更再运行 E2E。未运行项说明原因。
@@ -2,13 +2,20 @@
2
2
 
3
3
  > 管理后台项目 AI 编程助手入口。优先结合 `README.md`、`.agents/skills/`、`src/routes/`、`src/lib/server/` 与 `messages/*` 工作。
4
4
 
5
+ ## 需求与模板
6
+
7
+ - 业务行为以用户当前明确要求及其指定的需求/设计文档为准。本文件、README、skills、示例、生成代码及其测试中的模板默认行为是起点,不是不可变的业务约束;角色代码、权限模型、页面流程、字段、限额、存储方式与架构均可按实际需求调整。
8
+ - 需求与模板不同,审查时记录为模板适配项,不据此判定需求错误或阻塞审查,也不要求用户额外授权“覆盖模板”;实施时在已有授权范围内修改或移除冲突逻辑,并同步 contract、鉴权、数据迁移、界面、配置、测试、文档及本文件中的过期规则。
9
+ - 未说明的行为可参考现有实现并注明假设,不能升级为强制需求。只有业务要求自身矛盾、与已确认项目决策的优先级不明,或存在真实技术/安全障碍时才提出具体问题;已明确的规则不重复要求选择。
10
+ - 业务规则与技术契约分别判断:可用 API 以当前安装版本的公开类型为准;输入校验、服务端授权、资源隔离和密钥保护仍须落实。需求涉及替换技术方案时说明影响并同步迁移,不能用保留模板默认值代替解决问题。
11
+
5
12
  ## 行为契约
6
13
 
7
14
  需求/设计文档审查或从文档生成代码时,读取 [hai-spec-review](.agents/skills/hai-spec-review/SKILL.md),严格确认完备性、歧义、技术正确性与命名一致性;影响行为的问题未解决前不判定文档可生成。普通小改动不重审无关文档。
8
15
 
9
16
  升级 hai-framework 到最新/指定版本、比较版本差异或同步框架 skills 时,先读 [框架升级与同步 Skill](.agents/skills/hai-framework-sync/SKILL.md),执行版本发现、差异分析、用户选择/阻塞处理、迁移与验证;仅要求比较时不修改应用。
10
17
 
11
- 根据文档规划、实施、继续或返修任务时,读取 [hai-spec-implement](.agents/skills/hai-spec-implement/SKILL.md):审查后按可验收功能和依赖拆分,维护 docs 下的清单及 docs/tasks 下的任务信息,每项独立分支 TDD、验证并本地合并;返修复用原任务与分支。仅规划不实施,指定任务先核验依赖。
18
+ 根据文档规划、实施、继续、返修或按已提交文档变更同步实现时,读取 [hai-spec-implement](.agents/skills/hai-spec-implement/SKILL.md):维护 docs 清单与任务记录,比较已验收与目标文档 Git 快照,更新原任务或新建任务,每项独立分支 TDD、验证并本地合并;全部差异与整体验收通过后才推进基线。返修复用原任务与分支。仅规划不实施,指定任务先核验依赖。
12
19
 
13
20
  普通新增、修改、修复任务开始前,先读 [应用开发 Skill](.agents/skills/hai-app-create/SKILL.md) 和 [开发规范与框架能力路由](.agents/skills/hai-app-create/development.md);再按任务读取模块 Skill 及其相邻参考文件。架构/构建、审查、测试分别读取 hai-build、hai-app-review、hai-app-tests,不加载整个 skill 树。
14
21
 
@@ -42,6 +49,15 @@
42
49
  2. 变更权限、登录、会话、导航或 CRUD 页面时,优先扩展现有 service / component,而不是临时散落逻辑。
43
50
  3. 修改页面、API、认证流程后,同步测试、README 和 i18n。
44
51
 
52
+ 页面统一继承根布局的 header/footer;新增页面沿用 SvelteKit 自动文件路由,刷新定位及服务端 load/鉴权保持一致。
53
+
54
+ ## 代码注释与示例
55
+
56
+ - 尽可能补充有信息量的中文 JSDoc:所有 export 的函数/类/方法、核心算法与业务流程、所有数据库表/列、接口/type/schema 及其字段都必须说明用途、单位、约束和关联关系。核心逻辑解释决策原因、边界、权限与失败路径,不逐行复述代码。
57
+ - 函数按实际契约写 @param、@returns,仅确实抛错时写 @throws;异步、HaiResult 与事务失败行为须说明。类型/表结构说明字段含义、可空性、默认值、主外键和唯一约束。
58
+ - 上述函数和结构的 JSDoc 均提供可理解的 @example:函数给调用及结果判断,结构给合法数据,表给典型行或查询示例。省略上下文要标明,不使用真实凭据。示例随实现同步,不能只堆叠没有信息量的注释。
59
+ - 调用及结构示例见 [.agents/skills/hai-app-create/development.md](.agents/skills/hai-app-create/development.md) 的 JSDoc 注释一节。
60
+
45
61
  ## 质量门禁
46
62
 
47
63
  先核对 package.json,按影响范围执行已有脚本;文档检查引用,UI/路由变更再运行 E2E。未运行项说明原因。
@@ -2,13 +2,20 @@
2
2
 
3
3
  > API 服务项目 AI 编程助手入口。优先结合 `README.md`、`.agents/skills/`、`apps/*-contract/src/**`、`apps/*-service/src/**` 与测试工作。
4
4
 
5
+ ## 需求与模板
6
+
7
+ - 业务行为以用户当前明确要求及其指定的需求/设计文档为准。本文件、README、skills、示例、生成代码及其测试中的模板默认行为是起点,不是不可变的业务约束;角色代码、权限模型、页面流程、字段、限额、存储方式与架构均可按实际需求调整。
8
+ - 需求与模板不同,审查时记录为模板适配项,不据此判定需求错误或阻塞审查,也不要求用户额外授权“覆盖模板”;实施时在已有授权范围内修改或移除冲突逻辑,并同步 contract、鉴权、数据迁移、界面、配置、测试、文档及本文件中的过期规则。
9
+ - 未说明的行为可参考现有实现并注明假设,不能升级为强制需求。只有业务要求自身矛盾、与已确认项目决策的优先级不明,或存在真实技术/安全障碍时才提出具体问题;已明确的规则不重复要求选择。
10
+ - 业务规则与技术契约分别判断:可用 API 以当前安装版本的公开类型为准;输入校验、服务端授权、资源隔离和密钥保护仍须落实。需求涉及替换技术方案时说明影响并同步迁移,不能用保留模板默认值代替解决问题。
11
+
5
12
  ## 行为契约
6
13
 
7
14
  需求/设计文档审查或从文档生成代码时,读取 [hai-spec-review](.agents/skills/hai-spec-review/SKILL.md),严格确认完备性、歧义、技术正确性与命名一致性;影响行为的问题未解决前不判定文档可生成。普通小改动不重审无关文档。
8
15
 
9
16
  升级 hai-framework 到最新/指定版本、比较版本差异或同步框架 skills 时,先读 [框架升级与同步 Skill](.agents/skills/hai-framework-sync/SKILL.md),执行版本发现、差异分析、用户选择/阻塞处理、迁移与验证;仅要求比较时不修改应用。
10
17
 
11
- 根据文档规划、实施、继续或返修任务时,读取 [hai-spec-implement](.agents/skills/hai-spec-implement/SKILL.md):审查后按可验收功能和依赖拆分,维护 docs 下的清单及 docs/tasks 下的任务信息,每项独立分支 TDD、验证并本地合并;返修复用原任务与分支。仅规划不实施,指定任务先核验依赖。
18
+ 根据文档规划、实施、继续、返修或按已提交文档变更同步实现时,读取 [hai-spec-implement](.agents/skills/hai-spec-implement/SKILL.md):维护 docs 清单与任务记录,比较已验收与目标文档 Git 快照,更新原任务或新建任务,每项独立分支 TDD、验证并本地合并;全部差异与整体验收通过后才推进基线。返修复用原任务与分支。仅规划不实施,指定任务先核验依赖。
12
19
 
13
20
  普通新增、修改、修复任务开始前,先读 [应用开发 Skill](.agents/skills/hai-app-create/SKILL.md) 和 [开发规范与框架能力路由](.agents/skills/hai-app-create/development.md);再按任务读取模块 Skill 及其相邻参考文件。架构/构建、审查、测试分别读取 hai-build、hai-app-review、hai-app-tests,不加载整个 skill 树。
14
21
 
@@ -30,9 +37,9 @@
30
37
  ## 架构边界
31
38
 
32
39
  - API 输入输出必须由 `apps/*-contract/src/**` 中的 Zod schema 与 contract 定义统一约束。
33
- - Service 通过 `@h-ai/serv` + `@h-ai/api-contract` 装配 HTTP App;不要退回到 SvelteKit API routes 架构。
40
+ - 当前 Service 通过 `@h-ai/serv` + `@h-ai/api-contract` 装配 HTTP App;保留此架构时不混入 SvelteKit API routes,需求要求迁移时同步入口、contract、调用方与脚本。
34
41
  - Typed client / contract / service 三者保持同一份路径与类型定义,不要复制粘贴接口。
35
- - 不生成用户页面;不要引入 UI 专属依赖或页面文案。
42
+ - 纯 API 需求不生成用户页面或引入 UI 专属依赖;需求增加前端时,按实际架构补充应用及其文案和验证。
36
43
  - 配置和密钥来自 `apps/*-service/config/` 与环境变量,禁止硬编码。
37
44
 
38
45
  ## 工作流程
@@ -41,6 +48,13 @@
41
48
  2. 修改 contract、响应格式、安全策略或配置时,同步 README、typed client 调用方与测试。
42
49
  3. 按改动范围验证,修复本次引入的失败;已有问题与未运行项明确报告。
43
50
 
51
+ ## 代码注释与示例
52
+
53
+ - 尽可能补充有信息量的中文 JSDoc:所有 export 的函数/类/方法、核心算法与业务流程、所有数据库表/列、接口/type/schema 及其字段都必须说明用途、单位、约束和关联关系。核心逻辑解释决策原因、边界、权限与失败路径,不逐行复述代码。
54
+ - 函数按实际契约写 @param、@returns,仅确实抛错时写 @throws;异步、HaiResult 与事务失败行为须说明。类型/表结构说明字段含义、可空性、默认值、主外键和唯一约束。
55
+ - 上述函数和结构的 JSDoc 均提供可理解的 @example:函数给调用及结果判断,结构给合法数据,表给典型行或查询示例。省略上下文要标明,不使用真实凭据。示例随实现同步,不能只堆叠没有信息量的注释。
56
+ - 调用及结构示例见 [.agents/skills/hai-app-create/development.md](.agents/skills/hai-app-create/development.md) 的 JSDoc 注释一节。
57
+
44
58
  ## 质量门禁
45
59
 
46
60
  先核对 package.json,按影响范围执行已有脚本;文档检查引用,UI/路由变更再运行 E2E。未运行项说明原因。
@@ -2,13 +2,20 @@
2
2
 
3
3
  Fullstack 多包工程入口。先读 README、根 workspace 脚本与相关 package/app;模块用法按需读取 `.agents/skills/*/SKILL.md` 及其引用。
4
4
 
5
+ ## 需求与模板
6
+
7
+ - 业务行为以用户当前明确要求及其指定的需求/设计文档为准。本文件、README、skills、示例、生成代码及其测试中的模板默认行为是起点,不是不可变的业务约束;角色代码、权限模型、页面流程、字段、限额、存储方式与架构均可按实际需求调整。
8
+ - 需求与模板不同,审查时记录为模板适配项,不据此判定需求错误或阻塞审查,也不要求用户额外授权“覆盖模板”;实施时在已有授权范围内修改或移除冲突逻辑,并同步 contract、鉴权、数据迁移、界面、配置、测试、文档及本文件中的过期规则。
9
+ - 未说明的行为可参考现有实现并注明假设,不能升级为强制需求。只有业务要求自身矛盾、与已确认项目决策的优先级不明,或存在真实技术/安全障碍时才提出具体问题;已明确的规则不重复要求选择。
10
+ - 业务规则与技术契约分别判断:可用 API 以当前安装版本的公开类型为准;输入校验、服务端授权、资源隔离和密钥保护仍须落实。需求涉及替换技术方案时说明影响并同步迁移,不能用保留模板默认值代替解决问题。
11
+
5
12
  ## 行为契约
6
13
 
7
14
  需求/设计文档审查或从文档生成代码时,读取 [hai-spec-review](.agents/skills/hai-spec-review/SKILL.md),严格确认完备性、歧义、技术正确性与命名一致性;影响行为的问题未解决前不判定文档可生成。普通小改动不重审无关文档。
8
15
 
9
16
  升级 hai-framework 到最新/指定版本、比较版本差异或同步框架 skills 时,先读 [框架升级与同步 Skill](.agents/skills/hai-framework-sync/SKILL.md),执行版本发现、差异分析、用户选择/阻塞处理、迁移与验证;仅要求比较时不修改应用。
10
17
 
11
- 根据文档规划、实施、继续或返修任务时,读取 [hai-spec-implement](.agents/skills/hai-spec-implement/SKILL.md):审查后按可验收功能和依赖拆分,维护 docs 下的清单及 docs/tasks 下的任务信息,每项独立分支 TDD、验证并本地合并;返修复用原任务与分支。仅规划不实施,指定任务先核验依赖。
18
+ 根据文档规划、实施、继续、返修或按已提交文档变更同步实现时,读取 [hai-spec-implement](.agents/skills/hai-spec-implement/SKILL.md):维护 docs 清单与任务记录,比较已验收与目标文档 Git 快照,更新原任务或新建任务,每项独立分支 TDD、验证并本地合并;全部差异与整体验收通过后才推进基线。返修复用原任务与分支。仅规划不实施,指定任务先核验依赖。
12
19
 
13
20
  普通新增、修改、修复任务开始前,先读 [应用开发 Skill](.agents/skills/hai-app-create/SKILL.md) 和 [开发规范与框架能力路由](.agents/skills/hai-app-create/development.md);再按任务读取模块 Skill 及其相邻参考文件。架构/构建、审查、测试分别读取 hai-build、hai-app-review、hai-app-tests,不加载整个 skill 树。
14
21
 
@@ -21,7 +28,11 @@ Fullstack 多包工程入口。先读 README、根 workspace 脚本与相关 pac
21
28
  - 修改前检索实现、调用方、测试和文档,优先复用现有 contract、procedure、共享组件与脚本。
22
29
  - 跨包变更先明确受影响的 contract、serv、shared、前端及交付流程,再同步调用方;不必输出固定问卷。
23
30
 
24
- ## 架构边界
31
+ ## 初始架构
32
+
33
+ - 页面统一继承公共 header/footer;新增 SPA 页面放 `packages/{{projectName}}-shared/src/lib/pages/**/*.svelte`,由 `HashRouter` 自动发现。菜单只维护业务入口,不维护路由表。
34
+
35
+ 以下为生成时的目录和分层;需求调整架构时同步相关包、脚本与说明,按调整后的实际结构工作。
25
36
 
26
37
  | 路径 | 职责 |
27
38
  | --- | --- |
@@ -33,20 +44,33 @@ Fullstack 多包工程入口。先读 README、根 workspace 脚本与相关 pac
33
44
  | `apps/{{projectName}}-desktop` | Svelte 5 + Vite + Tauri v2 桌面端,按选择生成 |
34
45
  | `apps/{{projectName}}-miniapp` | 选择 miniapp 时生成的预留说明与文案,当前不是可运行应用 |
35
46
 
36
- - 前端负责 UI 与请求编排;后端业务放 serv,共享能力放 shared。本样板默认不用 `@h-ai/kit`,不要混入 SvelteKit load / route 结构。
47
+ - 前端负责 UI 与请求编排;后端业务放 serv,共享能力放 shared。保留当前 Vite SPA 架构时,不混入 SvelteKit load / route 结构;需要迁移时成套调整。
37
48
  - 用户文本按 shared 跨端文案与各 app 专属文案分工,同步现有 locale。
38
49
  - 本样板默认启用 IAM、reldb、cache、audit、storage 和 crypto,复用现有初始化与关闭顺序;新增模块前先检查依赖、配置与公开契约。
39
50
  - 应用名称、版本、环境、日志与 locale 配置通过 core 统一获取;浏览器显示来自服务端 `app.info`,不另写硬编码副本或将私有配置注入客户端。
40
51
 
41
- ## 管理与安全边界
52
+ ## 初始管理业务
53
+
54
+ 这些是可运行示例的默认行为,按实际需求选择保留、修改或移除;不是 hai-framework 对所有应用的要求。
55
+
56
+ - 初始使用 `admin` / `user` 两种角色:管理员进入用户、角色和审计管理,普通用户进入个人设置。角色代码、数量、权限粒度及可访问页面由业务需求决定;更换模型时同步服务端授权、资源范围与各端入口。
57
+ - 初始角色页维护说明并显示人数,用户页分配角色;示例防止管理员自删除、自禁用、自降级及移除最后管理员。是否采用这些流程和保护由需求确定,保留的业务不变量需覆盖并发管理请求。
58
+ - 默认头像接受 PNG/JPEG/WebP,最大 512 KiB;普通用户更新自己的头像,管理员可替用户更新。格式、限额和操作主体可调整,contract、客户端和服务端保持一致;仍需验证文件内容及资源访问权限,storage key 由服务端生成。
59
+ - 默认用户与审计列表使用页码分页:`page >= 1`、`pageSize` 为 1–100、每页 20 条。分页方式、筛选和上限可按需求调整,同步 schema、procedure、store 和测试。
60
+ - 默认将语言与主题偏好保存到当前用户 metadata,审计采用操作字段和资源标识。偏好来源、审计字段、保留期及失败策略按需求确定,敏感数据按实际用途脱敏。
61
+
62
+ ## 当前传输与安全契约
63
+
64
+ - JSON 业务请求与响应经 typed api-client 和 serv 的 crypto transport 加解密,包含登录与刷新;健康/就绪检查和公开头像二进制资源使用既有例外。新增 API 沿用当前管道;需求调整传输方案时同步客户端、服务端及测试,不能静默绕过仍生效的安全契约。
65
+ - Web 使用内存 access token 和 HttpOnly refresh Cookie;App 原生端装配 `createCapacitorTokenStorage`,浏览器预览沿用 Cookie;Desktop 原生端使用内存 TokenStorage,重启后重新登录。原生 body 刷新通道同时验证 `X-HAI-Token-Transport: body`、端对应的 `x-request-client` 与受信完整 Origin,刷新请求沿用这些头。调整会话方案时保持凭据保护和通道校验;敏感 token 不写 localStorage 或普通配置文件。
66
+ - 服务端依据实际权限模型验证每个受保护操作,个人操作绑定当前会话,代他人操作验证对应权限;不能只靠隐藏导航保护资源。SQL 动态值参数化,日志与审计不泄露密码、token 或密钥。
67
+
68
+ ## 代码注释与示例
42
69
 
43
- - `admin` / `user` 是固定角色代码。管理员进入用户、角色和审计管理;普通用户只进入个人设置。服务端为每个管理操作验证角色,个人资料、密码和头像按当前会话绑定用户,不能只靠导航隐藏保护权限。
44
- - 角色管理更新说明并显示人数,用户管理负责角色分配;保留当前管理员的自删除、自禁用、自降级及最后管理员保护,并验证并发管理请求。
45
- - JSON 业务请求与响应统一经 typed api-client 和 serv 的 crypto transport 加解密,包含登录与刷新;健康/就绪检查和公开头像二进制资源遵循已有例外,不能为新增业务 API 绕过传输加密。
46
- - Web 使用内存 access token 和 HttpOnly refresh Cookie;App 原生端装配 `createCapacitorTokenStorage`,浏览器预览沿用 Cookie;Desktop 原生端使用内存 TokenStorage,重启后重新登录。原生 body 刷新通道同时验证 `X-HAI-Token-Transport: body`、端对应的 `x-request-client` 与受信完整 Origin,刷新请求沿用这些头。敏感 token 不写 localStorage,不擅自将其持久化到普通配置文件。
47
- - 头像接受 PNG/JPEG/WebP,最大 512 KiB;contract、客户端与服务端校验保持一致。服务端验证实际图片数据并生成 storage key;普通用户仅更新自己的头像,管理员可替用户更新。
48
- - 用户与审计查询按 contract 分页,`page >= 1`、`pageSize` 为 1–100;页面默认每页 20 条。新增筛选同步 schema、procedure、store 和测试,SQL 值参数化。
49
- - 语言与主题偏好保存到当前用户 metadata,客户端只应用已支持的值。审计仅记录操作字段和资源标识,不记录密码、token、头像内容或联系信息。
70
+ - 尽可能补充有信息量的中文 JSDoc:所有 export 的函数/类/方法、核心算法与业务流程、所有数据库表/列、接口/type/schema 及其字段都必须说明用途、单位、约束和关联关系。核心逻辑解释决策原因、边界、权限与失败路径,不逐行复述代码。
71
+ - 函数按实际契约写 @param、@returns,仅确实抛错时写 @throws;异步、HaiResult 与事务失败行为须说明。类型/表结构说明字段含义、可空性、默认值、主外键和唯一约束。
72
+ - 上述函数和结构的 JSDoc 均提供可理解的 @example:函数给调用及结果判断,结构给合法数据,表给典型行或查询示例。省略上下文要标明,不使用真实凭据。示例随实现同步,不能只堆叠没有信息量的注释。
73
+ - 调用及结构示例见 [.agents/skills/hai-app-create/development.md](.agents/skills/hai-app-create/development.md) 的 JSDoc 注释一节。
50
74
 
51
75
  ## 质量门禁
52
76