@h-ai/cli 0.1.0-alpha.63 → 0.1.0-alpha.65
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 +20 -4
- package/README.ja.md +20 -4
- package/README.md +28 -12
- package/dist/chunk-47V5OT22.js +5639 -0
- package/dist/chunk-47V5OT22.js.map +1 -0
- package/dist/{chunk-X3RSYNBG.js → chunk-IPG3QNYK.js} +62 -4
- package/dist/chunk-IPG3QNYK.js.map +1 -0
- package/dist/cli-config-templates-SJ2MBIR7.js +5 -0
- package/dist/{cli-config-templates-QRF6B5T4.js.map → cli-config-templates-SJ2MBIR7.js.map} +1 -1
- package/dist/{cli-skill-templates-EZFW7D43.js → cli-skill-templates-A5VIR5J6.js} +3 -3
- package/dist/{cli-skill-templates-EZFW7D43.js.map → cli-skill-templates-A5VIR5J6.js.map} +1 -1
- package/dist/index.js +524 -66
- package/dist/index.js.map +1 -1
- package/dist/{src-DUGLX4YB.js → src-6PPBKQ6Q.js} +666 -290
- package/dist/src-6PPBKQ6Q.js.map +1 -0
- package/package.json +9 -7
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +4 -2
- package/templates/apps/api/.dockerignore.hbs +5 -0
- package/templates/apps/api/.gitignore.hbs +4 -4
- package/templates/apps/api/Dockerfile.hbs +11 -1
- package/templates/apps/api/apps/{{projectName}}-contract/package.json.hbs +2 -0
- package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +2 -2
- package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +4 -4
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +9 -2
- package/templates/apps/api/package.json.hbs +9 -0
- package/templates/apps/fullstack/.dockerignore.hbs +5 -3
- package/templates/apps/fullstack/.gitignore.hbs +22 -9
- package/templates/apps/fullstack/Dockerfile.hbs +11 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +6 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +7 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +5 -0
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +7 -3
- package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +6 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +7 -3
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +93 -1
- package/templates/apps/fullstack/package.json.hbs +12 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +2 -0
- package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-contract/tests/contract.test.ts.hbs +8 -14
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +6 -4
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -8
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +9 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +4 -4
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +6 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +2 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +19 -13
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +46 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +17 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +15 -5
- package/templates/apps/fullstack/playwright.config.ts.hbs +6 -1
- package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +0 -1
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +13 -13
- package/templates/apps/mobile-app/src/App.svelte.hbs +46 -117
- package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +58 -0
- package/templates/apps/mobile-app/src/lib/pages/profile.svelte.hbs +13 -0
- package/templates/apps/mobile-app/src/lib/pages/tasks.svelte.hbs +16 -0
- package/templates/base/.dockerignore.hbs +5 -0
- package/templates/base/.gitignore.hbs +18 -4
- package/templates/base/Dockerfile.hbs +17 -1
- package/templates/base/e2e/app.spec.ts.hbs +14 -0
- package/templates/base/package.json.hbs +14 -2
- package/templates/base/tests/scaffold.test.ts.hbs +9 -3
- package/templates/base/vite.config.ts.hbs +6 -4
- package/templates/shared/hash-router.svelte.ts +83 -0
- package/templates/skills/AGENTS.md +15 -1
- package/templates/skills/bridges/admin/AGENTS.md +17 -1
- package/templates/skills/bridges/api/AGENTS.md +17 -3
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +35 -11
- package/templates/skills/bridges/h5/AGENTS.md +17 -1
- package/templates/skills/bridges/mobile-app/AGENTS.md +19 -3
- package/templates/skills/bridges/website/AGENTS.md +17 -1
- package/templates/skills/hai-ai/reference.md +2 -2
- package/templates/skills/hai-app-create/SKILL.md +5 -3
- package/templates/skills/hai-app-create/development.md +126 -2
- package/templates/skills/hai-app-create/reference.md +2 -2
- package/templates/skills/hai-app-review/SKILL.md +2 -2
- package/templates/skills/hai-app-tests/SKILL.md +2 -1
- package/templates/skills/hai-app-tests/reference.md +1 -1
- package/templates/skills/hai-core/SKILL.md +2 -1
- package/templates/skills/hai-deploy/SKILL.md +42 -34
- package/templates/skills/hai-deploy/middleware.md +94 -0
- package/templates/skills/hai-framework-sync/SKILL.md +1 -1
- package/templates/skills/hai-framework-sync/sync.md +1 -1
- package/templates/skills/hai-reach/reference.md +8 -8
- package/templates/skills/hai-spec-implement/SKILL.md +12 -9
- package/templates/skills/hai-spec-implement/changes.md +67 -0
- package/templates/skills/hai-spec-implement/templates.md +25 -3
- package/templates/skills/hai-spec-implement/workflow.md +7 -6
- package/templates/skills/hai-spec-review/SKILL.md +14 -5
- package/templates/skills/hai-spec-review/checklist.md +4 -3
- package/templates/skills/hai-storage/SKILL.md +2 -0
- package/templates/skills/hai-ui/SKILL.md +4 -0
- package/dist/chunk-WW2MIO6T.js +0 -992
- package/dist/chunk-WW2MIO6T.js.map +0 -1
- package/dist/chunk-X3RSYNBG.js.map +0 -1
- package/dist/cli-config-templates-QRF6B5T4.js +0 -5
- package/dist/src-DUGLX4YB.js.map +0 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/ProfileSettings.svelte.hbs +0 -57
- /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/AuditManagement.svelte.hbs → pages/audit.svelte.hbs} +0 -0
- /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/RoleManagement.svelte.hbs → pages/roles.svelte.hbs} +0 -0
- /package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/{components/UserManagement.svelte.hbs → pages/users.svelte.hbs} +0 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script lang='ts'>
|
|
2
2
|
{{#if features.iam}}
|
|
3
3
|
import type { IamPublicConfig } from '@h-ai/api-contract'
|
|
4
|
+
{{/if}}
|
|
5
|
+
import type { Component } from 'svelte'
|
|
6
|
+
{{#if features.iam}}
|
|
4
7
|
import { apiClient } from '@h-ai/api-client'
|
|
5
8
|
{{/if}}
|
|
6
9
|
import { applyTheme, getSavedTheme, setGlobalLocale } from '@h-ai/ui'
|
|
@@ -8,9 +11,11 @@
|
|
|
8
11
|
import { AuthPanel } from '@h-ai/ui/components/scenes/iam/index'
|
|
9
12
|
{{/if}}
|
|
10
13
|
import { onMount } from 'svelte'
|
|
11
|
-
|
|
14
|
+
|
|
15
|
+
import { closeApi, initApi, usesNativeTokenStorage } from './lib/api.js'
|
|
12
16
|
import { appConfig, initClientCore } from './lib/app-config.js'
|
|
13
17
|
import { initCapacitor, isNativeApp } from './lib/capacitor.js'
|
|
18
|
+
import { HashRouter } from './lib/navigation/hash-router.svelte.js'
|
|
14
19
|
import * as m from './lib/paraglide/messages.js'
|
|
15
20
|
import { getLocale } from './lib/paraglide/runtime.js'
|
|
16
21
|
|
|
@@ -31,11 +36,12 @@
|
|
|
31
36
|
}
|
|
32
37
|
{{/if}}
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
const pages = import.meta.glob<{ default: Component<{ nativeRuntime: boolean, nativeTokenStorage: boolean }> }>('./lib/pages/**/*.svelte')
|
|
40
|
+
const router = new HashRouter(Object.keys(pages), '/home')
|
|
41
|
+
const pageComponent = $derived(pages[router.file]())
|
|
35
42
|
|
|
36
43
|
let booting = $state(true)
|
|
37
44
|
let bootError = $state<string | null>(null)
|
|
38
|
-
let activeTab = $state<TabId>('home')
|
|
39
45
|
let nativeRuntime = $state(false)
|
|
40
46
|
let nativeTokenStorage = $state(false)
|
|
41
47
|
|
|
@@ -74,136 +80,59 @@
|
|
|
74
80
|
}
|
|
75
81
|
}
|
|
76
82
|
|
|
83
|
+
/** 切换底部导航;例如 handleTabChange('tasks') 会写入 #/tasks。 */
|
|
77
84
|
function handleTabChange(id: string): void {
|
|
78
|
-
|
|
79
|
-
activeTab = id
|
|
85
|
+
router.navigate(`/${id}`)
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
onMount(() => {
|
|
89
|
+
const stopRouting = router.start()
|
|
83
90
|
{{#if features.iam}}
|
|
84
91
|
resetToken = new URL(location.href).searchParams.get('token') ?? ''
|
|
85
92
|
{{/if}}
|
|
86
93
|
void bootstrap()
|
|
87
94
|
|
|
88
95
|
return () => {
|
|
96
|
+
stopRouting()
|
|
89
97
|
void closeApi()
|
|
90
98
|
}
|
|
91
99
|
})
|
|
92
100
|
</script>
|
|
93
101
|
|
|
94
|
-
|
|
95
|
-
<div class='hai-mobile-
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<span class='icon-[tabler--alert-triangle] text-error text-4xl'></span>
|
|
107
|
-
<h1 class='text-lg font-bold'>{m.boot_failed_title()}</h1>
|
|
108
|
-
<p class='text-base-content/60 text-sm'>{bootError}</p>
|
|
109
|
-
<Button variant='primary' size='sm' onclick={() => location.reload()}>{m.common_retry()}</Button>
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
102
|
+
<div class='hai-mobile-viewport'>
|
|
103
|
+
<div class='hai-mobile-shell max-w-lg'>
|
|
104
|
+
<AppBar title={appConfig.name} safeArea fixed={false} class='bg-base-100/95 backdrop-blur border-base-content/8' />
|
|
105
|
+
<main class='hai-mobile-main'>
|
|
106
|
+
{#if booting}
|
|
107
|
+
<div class='grid min-h-64 place-items-center'><span class='loading loading-spinner loading-lg'></span></div>
|
|
108
|
+
{:else if bootError}
|
|
109
|
+
<section class='flex min-h-64 flex-col items-center justify-center gap-3 p-6 text-center'>
|
|
110
|
+
<h1 class='text-lg font-bold'>{m.boot_failed_title()}</h1>
|
|
111
|
+
<p class='text-base-content/60 text-sm'>{bootError}</p>
|
|
112
|
+
<Button variant='primary' size='sm' onclick={() => location.reload()}>{m.common_retry()}</Button>
|
|
113
|
+
</section>
|
|
113
114
|
{{#if features.iam}}
|
|
114
|
-
{:else if !authenticated}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
{:else if !authenticated}
|
|
116
|
+
<AuthPanel config={authConfig} actions={authActions} {resetToken} initialMode={resetToken ? 'reset' : 'login'} onauthenticated={handleAuthenticated} />
|
|
117
|
+
{{/if}}
|
|
118
|
+
{:else}
|
|
119
|
+
{#await pageComponent}
|
|
120
|
+
<div class='grid min-h-64 place-items-center'><span class='loading loading-spinner'></span></div>
|
|
121
|
+
{:then { default: Page }}
|
|
122
|
+
<Page {nativeRuntime} {nativeTokenStorage} />
|
|
123
|
+
{/await}
|
|
124
|
+
{/if}
|
|
125
|
+
</main>
|
|
126
|
+
<footer class='border-t border-base-content/8 bg-base-100 p-2 text-center text-xs text-base-content/50'>
|
|
127
|
+
{appConfig.name} · {appConfig.version} · {appConfig.env}
|
|
128
|
+
</footer>
|
|
129
|
+
{{#if features.iam}}
|
|
130
|
+
{#if authenticated && !booting && !bootError}
|
|
131
|
+
{{/if}}
|
|
132
|
+
<BottomNav items={navItems} active={router.path.slice(1)} onchange={handleTabChange} safeArea centered maxWidth='lg' />
|
|
133
|
+
{{#if features.iam}}
|
|
134
|
+
{/if}
|
|
118
135
|
{{/if}}
|
|
119
|
-
{:else}
|
|
120
|
-
<div class='hai-mobile-viewport'>
|
|
121
|
-
<div class='hai-mobile-shell max-w-lg'>
|
|
122
|
-
<AppBar title={appConfig.name} safeArea fixed={false} class='bg-base-100/95 backdrop-blur border-base-content/8' />
|
|
123
|
-
|
|
124
|
-
<main class='hai-mobile-main'>
|
|
125
|
-
{#if activeTab === 'home'}
|
|
126
|
-
<section class='space-y-4 p-4 pb-6'>
|
|
127
|
-
<div class='bg-primary text-primary-content rounded-2xl p-5 shadow-sm'>
|
|
128
|
-
<p class='text-sm opacity-75'>{m.hero_eyebrow()}</p>
|
|
129
|
-
<h1 class='mt-1 text-2xl font-bold'>{m.hero_title()}</h1>
|
|
130
|
-
<p class='mt-2 text-sm opacity-80'>{m.hero_subtitle()}</p>
|
|
131
|
-
</div>
|
|
132
|
-
|
|
133
|
-
<div class='grid grid-cols-2 gap-3'>
|
|
134
|
-
<Card padding='sm' shadow='sm'>
|
|
135
|
-
<div class='flex flex-col gap-2'>
|
|
136
|
-
<span class='icon-[tabler--device-mobile-code] text-primary text-2xl'></span>
|
|
137
|
-
<span class='text-sm font-medium'>{m.feature_native_title()}</span>
|
|
138
|
-
<span class='text-base-content/60 text-xs'>{m.feature_native_desc()}</span>
|
|
139
|
-
</div>
|
|
140
|
-
</Card>
|
|
141
|
-
<Card padding='sm' shadow='sm'>
|
|
142
|
-
<div class='flex flex-col gap-2'>
|
|
143
|
-
<span class='icon-[tabler--api-app] text-primary text-2xl'></span>
|
|
144
|
-
<span class='text-sm font-medium'>{m.feature_api_title()}</span>
|
|
145
|
-
<span class='text-base-content/60 text-xs'>{m.feature_api_desc()}</span>
|
|
146
|
-
</div>
|
|
147
|
-
</Card>
|
|
148
|
-
</div>
|
|
149
|
-
|
|
150
|
-
<Card title={m.status_title()} shadow='sm'>
|
|
151
|
-
<p class='mb-3 text-xs text-base-content/50'>{appConfig.name} · {appConfig.version} · {appConfig.env}</p>
|
|
152
|
-
<div class='grid grid-cols-2 gap-3 text-sm'>
|
|
153
|
-
<div>
|
|
154
|
-
<p class='text-base-content/50'>{m.status_api_label()}</p>
|
|
155
|
-
<p class='font-medium break-all'>{resolveApiBase()}</p>
|
|
156
|
-
</div>
|
|
157
|
-
<div>
|
|
158
|
-
<p class='text-base-content/50'>{m.status_runtime_label()}</p>
|
|
159
|
-
<Badge variant={nativeRuntime ? 'success' : 'warning'} size='sm'>
|
|
160
|
-
{nativeRuntime ? m.status_runtime_native() : m.status_runtime_web()}
|
|
161
|
-
</Badge>
|
|
162
|
-
</div>
|
|
163
|
-
<div class='col-span-2'>
|
|
164
|
-
<p class='text-base-content/50'>{m.status_token_label()}</p>
|
|
165
|
-
<p class='font-medium'>{nativeTokenStorage ? m.status_token_native() : m.status_token_memory()}</p>
|
|
166
|
-
</div>
|
|
167
|
-
</div>
|
|
168
|
-
</Card>
|
|
169
|
-
</section>
|
|
170
|
-
{:else if activeTab === 'tasks'}
|
|
171
|
-
<section class='space-y-4 p-4 pb-6'>
|
|
172
|
-
<Card title={m.tasks_title()} shadow='sm'>
|
|
173
|
-
<div class='space-y-3'>
|
|
174
|
-
{#each [m.tasks_sync(), m.tasks_polish(), m.tasks_release()] as task (task)}
|
|
175
|
-
<div class='bg-base-200 rounded-xl flex items-center gap-3 p-3'>
|
|
176
|
-
<span class='icon-[tabler--circle-check] text-success text-xl'></span>
|
|
177
|
-
<span class='text-sm font-medium'>{task}</span>
|
|
178
|
-
</div>
|
|
179
|
-
{/each}
|
|
180
|
-
</div>
|
|
181
|
-
</Card>
|
|
182
|
-
</section>
|
|
183
|
-
{:else}
|
|
184
|
-
<section class='space-y-4 p-4 pb-6'>
|
|
185
|
-
<Card title={m.profile_title()} shadow='sm'>
|
|
186
|
-
<div class='space-y-3 text-sm'>
|
|
187
|
-
<div class='bg-base-200 rounded-xl p-3'>{m.profile_language()}</div>
|
|
188
|
-
<div class='bg-base-200 rounded-xl p-3'>{m.profile_theme()}</div>
|
|
189
|
-
<div class='bg-base-200 rounded-xl p-3'>{m.profile_secure()}</div>
|
|
190
|
-
</div>
|
|
191
|
-
</Card>
|
|
192
|
-
</section>
|
|
193
|
-
{/if}
|
|
194
|
-
</main>
|
|
195
|
-
|
|
196
|
-
<BottomNav
|
|
197
|
-
items={navItems}
|
|
198
|
-
active={activeTab}
|
|
199
|
-
onchange={handleTabChange}
|
|
200
|
-
safeArea
|
|
201
|
-
centered
|
|
202
|
-
maxWidth='lg'
|
|
203
|
-
class='border-base-content/8 shadow-[0_-12px_32px_rgba(15,23,42,0.08)]'
|
|
204
|
-
/>
|
|
205
|
-
</div>
|
|
206
136
|
</div>
|
|
207
|
-
|
|
208
|
-
|
|
137
|
+
</div>
|
|
209
138
|
<ToastContainer />
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script lang='ts'>
|
|
2
|
+
import { resolveApiBase } from '../api.js'
|
|
3
|
+
import { appConfig } from '../app-config.js'
|
|
4
|
+
import * as m from '../paraglide/messages.js'
|
|
5
|
+
/** 首页展示的平台运行状态;例如 { nativeRuntime: true, nativeTokenStorage: true }。 */
|
|
6
|
+
interface Props {
|
|
7
|
+
/** 是否运行于原生壳。 */
|
|
8
|
+
nativeRuntime: boolean
|
|
9
|
+
/** 是否使用原生安全凭据存储。 */
|
|
10
|
+
nativeTokenStorage: boolean
|
|
11
|
+
}
|
|
12
|
+
const { nativeRuntime, nativeTokenStorage }: Props = $props()
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<section class='space-y-4 p-4 pb-6'>
|
|
16
|
+
<div class='bg-primary text-primary-content rounded-2xl p-5 shadow-sm'>
|
|
17
|
+
<p class='text-sm opacity-75'>{m.hero_eyebrow()}</p>
|
|
18
|
+
<h1 class='mt-1 text-2xl font-bold'>{m.hero_title()}</h1>
|
|
19
|
+
<p class='mt-2 text-sm opacity-80'>{m.hero_subtitle()}</p>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class='grid grid-cols-2 gap-3'>
|
|
23
|
+
<Card padding='sm' shadow='sm'>
|
|
24
|
+
<div class='flex flex-col gap-2'>
|
|
25
|
+
<span class='icon-[tabler--device-mobile-code] text-primary text-2xl'></span>
|
|
26
|
+
<span class='text-sm font-medium'>{m.feature_native_title()}</span>
|
|
27
|
+
<span class='text-base-content/60 text-xs'>{m.feature_native_desc()}</span>
|
|
28
|
+
</div>
|
|
29
|
+
</Card>
|
|
30
|
+
<Card padding='sm' shadow='sm'>
|
|
31
|
+
<div class='flex flex-col gap-2'>
|
|
32
|
+
<span class='icon-[tabler--api-app] text-primary text-2xl'></span>
|
|
33
|
+
<span class='text-sm font-medium'>{m.feature_api_title()}</span>
|
|
34
|
+
<span class='text-base-content/60 text-xs'>{m.feature_api_desc()}</span>
|
|
35
|
+
</div>
|
|
36
|
+
</Card>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<Card title={m.status_title()} shadow='sm'>
|
|
40
|
+
<p class='mb-3 text-xs text-base-content/50'>{appConfig.name} · {appConfig.version} · {appConfig.env}</p>
|
|
41
|
+
<div class='grid grid-cols-2 gap-3 text-sm'>
|
|
42
|
+
<div>
|
|
43
|
+
<p class='text-base-content/50'>{m.status_api_label()}</p>
|
|
44
|
+
<p class='font-medium break-all'>{resolveApiBase()}</p>
|
|
45
|
+
</div>
|
|
46
|
+
<div>
|
|
47
|
+
<p class='text-base-content/50'>{m.status_runtime_label()}</p>
|
|
48
|
+
<Badge variant={nativeRuntime ? 'success' : 'warning'} size='sm'>
|
|
49
|
+
{nativeRuntime ? m.status_runtime_native() : m.status_runtime_web()}
|
|
50
|
+
</Badge>
|
|
51
|
+
</div>
|
|
52
|
+
<div class='col-span-2'>
|
|
53
|
+
<p class='text-base-content/50'>{m.status_token_label()}</p>
|
|
54
|
+
<p class='font-medium'>{nativeTokenStorage ? m.status_token_native() : m.status_token_memory()}</p>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</Card>
|
|
58
|
+
</section>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script lang='ts'>
|
|
2
|
+
import * as m from '../paraglide/messages.js'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<section class='space-y-4 p-4 pb-6'>
|
|
6
|
+
<Card title={m.profile_title()} shadow='sm'>
|
|
7
|
+
<div class='space-y-3 text-sm'>
|
|
8
|
+
<div class='bg-base-200 rounded-xl p-3'>{m.profile_language()}</div>
|
|
9
|
+
<div class='bg-base-200 rounded-xl p-3'>{m.profile_theme()}</div>
|
|
10
|
+
<div class='bg-base-200 rounded-xl p-3'>{m.profile_secure()}</div>
|
|
11
|
+
</div>
|
|
12
|
+
</Card>
|
|
13
|
+
</section>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang='ts'>
|
|
2
|
+
import * as m from '../paraglide/messages.js'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<section class='space-y-4 p-4 pb-6'>
|
|
6
|
+
<Card title={m.tasks_title()} shadow='sm'>
|
|
7
|
+
<div class='space-y-3'>
|
|
8
|
+
{#each [m.tasks_sync(), m.tasks_polish(), m.tasks_release()] as task (task)}
|
|
9
|
+
<div class='bg-base-200 rounded-xl flex items-center gap-3 p-3'>
|
|
10
|
+
<span class='icon-[tabler--circle-check] text-success text-xl'></span>
|
|
11
|
+
<span class='text-sm font-medium'>{task}</span>
|
|
12
|
+
</div>
|
|
13
|
+
{/each}
|
|
14
|
+
</div>
|
|
15
|
+
</Card>
|
|
16
|
+
</section>
|
|
@@ -1,19 +1,33 @@
|
|
|
1
|
+
# 构建产物目录
|
|
1
2
|
node_modules
|
|
2
3
|
{{#unless isSvelteOnlyApp}}
|
|
3
4
|
.svelte-kit
|
|
4
5
|
{{/unless}}
|
|
5
6
|
build
|
|
6
7
|
dist
|
|
8
|
+
.turbo
|
|
7
9
|
android
|
|
8
10
|
ios
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
**/src/lib/paraglide
|
|
12
|
+
**/src-tauri/gen/
|
|
13
|
+
**/src-tauri/target/
|
|
14
|
+
**/hash-core/target/
|
|
15
|
+
|
|
16
|
+
# 日志测试目录
|
|
12
17
|
*.log
|
|
13
|
-
.DS_Store
|
|
14
18
|
test-results/
|
|
15
19
|
playwright-report/
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# 系统文件
|
|
23
|
+
.DS_Store
|
|
24
|
+
|
|
25
|
+
# 本地数据目录
|
|
16
26
|
**/data/
|
|
17
27
|
|
|
18
28
|
# hai deploy 产物(compose / .env.runtime / 镜像 tar / sidecar 凭据)
|
|
19
29
|
.hai/
|
|
30
|
+
|
|
31
|
+
# 本机私有配置;公共 .env 与 dev/test/prod 环境文件纳入版本控制。
|
|
32
|
+
.env.local
|
|
33
|
+
.env.*.local
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# syntax=docker/dockerfile:1.7
|
|
2
2
|
{{#if isSvelteOnlyApp}}
|
|
3
3
|
FROM node:24-bookworm-slim AS build
|
|
4
|
+
ARG SQLITE_PREBUILD_MIRROR
|
|
5
|
+
ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
|
|
6
|
+
ARG HAI_DEPLOY_ENVIRONMENT=prod
|
|
7
|
+
ARG HAI_CORE_ENV=production
|
|
8
|
+
ENV HAI_DEPLOY_ENVIRONMENT=$HAI_DEPLOY_ENVIRONMENT
|
|
9
|
+
ENV HAI_CORE_ENV=$HAI_CORE_ENV
|
|
4
10
|
|
|
5
11
|
ENV PNPM_HOME=/pnpm
|
|
6
12
|
ENV PATH=$PNPM_HOME:$PATH
|
|
@@ -24,6 +30,12 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
|
24
30
|
CMD wget -q -O /dev/null http://127.0.0.1:8080/health || exit 1
|
|
25
31
|
{{else}}
|
|
26
32
|
FROM node:24-bookworm-slim AS build
|
|
33
|
+
ARG SQLITE_PREBUILD_MIRROR
|
|
34
|
+
ENV npm_config_better_sqlite3_binary_host_mirror=$SQLITE_PREBUILD_MIRROR
|
|
35
|
+
ARG HAI_DEPLOY_ENVIRONMENT=prod
|
|
36
|
+
ARG HAI_CORE_ENV=production
|
|
37
|
+
ENV HAI_DEPLOY_ENVIRONMENT=$HAI_DEPLOY_ENVIRONMENT
|
|
38
|
+
ENV HAI_CORE_ENV=$HAI_CORE_ENV
|
|
27
39
|
|
|
28
40
|
ENV PNPM_HOME=/pnpm
|
|
29
41
|
ENV PATH=$PNPM_HOME:$PATH
|
|
@@ -36,12 +48,16 @@ RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm fetch
|
|
|
36
48
|
COPY . .
|
|
37
49
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm install --offline --frozen-lockfile --ignore-scripts
|
|
38
50
|
RUN pnpm build
|
|
51
|
+
RUN if [ -d node_modules/better-sqlite3 ]; then cd node_modules/better-sqlite3; for attempt in 1 2 3; do node -e "require('node:child_process').execFileSync(process.execPath,[require.resolve('prebuild-install/bin.js'),'--verbose'],{stdio:'inherit'})" && break; [ "$attempt" = 3 ] && exit 1; sleep 3; done; fi
|
|
39
52
|
RUN --mount=type=cache,id={{projectName}}-pnpm,target=/pnpm/store pnpm pm deploy --filter {{projectName}} --prod /prod/app
|
|
40
53
|
|
|
41
54
|
FROM node:24-bookworm-slim AS runtime-base
|
|
55
|
+
ARG HAI_DEPLOY_ENVIRONMENT=prod
|
|
56
|
+
ARG HAI_CORE_ENV=production
|
|
42
57
|
|
|
43
58
|
ENV NODE_ENV=production
|
|
44
|
-
ENV
|
|
59
|
+
ENV HAI_DEPLOY_ENVIRONMENT=$HAI_DEPLOY_ENVIRONMENT
|
|
60
|
+
ENV HAI_CORE_ENV=$HAI_CORE_ENV
|
|
45
61
|
ENV HOST=0.0.0.0
|
|
46
62
|
ENV PORT=3000
|
|
47
63
|
WORKDIR /app
|
|
@@ -21,14 +21,26 @@ test('home page renders from browser', async ({ page }) => {
|
|
|
21
21
|
await expect(page).toHaveTitle(process.env.HAI_CORE_NAME ?? '{{projectName}}')
|
|
22
22
|
await expect(pageErrors).toEqual([])
|
|
23
23
|
await expect(page.locator('main, section').first()).toBeVisible()
|
|
24
|
+
await expect(page.locator('header').first()).toBeVisible()
|
|
25
|
+
await expect(page.locator('footer').first()).toBeAttached()
|
|
24
26
|
{{#if_eq appType 'h5'}}
|
|
25
27
|
// 在手机宽度验证导航真正切换路由,避免样式失效但首页渲染测试仍通过。
|
|
26
28
|
const nav = page.locator('nav')
|
|
27
29
|
await nav.getByRole('button', { name: /发现|Discover/i, exact: true }).click()
|
|
28
30
|
await expect(page).toHaveURL(/\/discover$/)
|
|
31
|
+
await page.reload()
|
|
32
|
+
await expect(page).toHaveURL(/\/discover$/)
|
|
33
|
+
await expect(page.getByRole('heading', { name: /发现|Discover/i })).toBeVisible()
|
|
29
34
|
await nav.getByRole('button', { name: /首页|Home/i, exact: true }).click()
|
|
30
35
|
await expect(page).toHaveURL(/\/$/)
|
|
31
36
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
|
|
37
|
+
{{/if_eq}}
|
|
38
|
+
{{#if_eq appType 'website'}}
|
|
39
|
+
await page.goto('/about')
|
|
40
|
+
await page.reload()
|
|
41
|
+
await expect(page).toHaveURL(/\/about$/)
|
|
42
|
+
await expect(page.locator('header')).toBeVisible()
|
|
43
|
+
await expect(page.locator('footer')).toBeVisible()
|
|
32
44
|
{{/if_eq}}
|
|
33
45
|
const evidence = process.env.HAI_AUTH_EVIDENCE_DIR || path.resolve('test-results/auth-screenshots')
|
|
34
46
|
await mkdir(evidence, { recursive: true })
|
|
@@ -50,6 +62,8 @@ test('configured IAM registration and password login', async ({ page, context })
|
|
|
50
62
|
const authUrl = '{{#if isCapacitorApp}}/{{else}}/auth/login{{/if}}'
|
|
51
63
|
await page.goto(authUrl)
|
|
52
64
|
await expect(page.locator('#login-username')).toBeVisible()
|
|
65
|
+
await expect(page.locator('header').first()).toBeVisible()
|
|
66
|
+
await expect(page.locator('footer')).toBeVisible()
|
|
53
67
|
await page.screenshot({ path: path.join(evidence, '{{projectName}}-login.png'), fullPage: true })
|
|
54
68
|
await page.getByRole('{{#if isCapacitorApp}}button{{else}}link{{/if}}', { name: /立即注册|Register now/i }).click()
|
|
55
69
|
await expect(page.locator('input[name="username"]')).toBeVisible()
|
|
@@ -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.
|
|
4
|
+
"version": "0.1.0-alpha.65",
|
|
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,11 +21,20 @@
|
|
|
21
21
|
"paraglide:compile": "paraglide-js compile --project ./project.inlang --outdir ./src/lib/paraglide",
|
|
22
22
|
{{/if}}
|
|
23
23
|
"dev": "vite dev",
|
|
24
|
+
"dev:test": "vite dev --mode test",
|
|
25
|
+
"dev:prod": "vite dev --mode prod",
|
|
24
26
|
"build": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}vite build",
|
|
27
|
+
"build:prod": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}vite build --mode prod",
|
|
28
|
+
{{#unless isSvelteOnlyApp}}
|
|
29
|
+
"start": "node --env-file=.env --env-file=.env.prod --env-file-if-exists=.env.local build/index.js",
|
|
30
|
+
"start:test": "node --env-file=.env --env-file=.env.test --env-file-if-exists=.env.local build/index.js",
|
|
31
|
+
"start:prod": "pnpm start",
|
|
32
|
+
{{/unless}}
|
|
25
33
|
"preview": "vite preview",
|
|
26
|
-
"check": "
|
|
34
|
+
"check": "pnpm typecheck",
|
|
27
35
|
"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
36
|
"lint": "eslint .",
|
|
37
|
+
"lint:fix": "eslint . --fix",
|
|
29
38
|
"test": "{{#if hasI18n}}pnpm paraglide:compile && {{/if}}vitest run",
|
|
30
39
|
"test:watch": "vitest",
|
|
31
40
|
"test:e2e": "playwright test",
|
|
@@ -79,6 +88,9 @@
|
|
|
79
88
|
{{/if}}
|
|
80
89
|
},
|
|
81
90
|
"devDependencies": {
|
|
91
|
+
{{#if isSvelteOnlyApp}}
|
|
92
|
+
"@h-ai/core": "{{haiPackageSpecifier}}",
|
|
93
|
+
{{/if}}
|
|
82
94
|
"@antfu/eslint-config": "{{versions.antfuEslintConfig}}",
|
|
83
95
|
"@playwright/test": "{{versions.playwright}}",
|
|
84
96
|
{{#if isCapacitorApp}}
|
|
@@ -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
|
|
47
|
-
expect(
|
|
48
|
-
|
|
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,13 @@ import { sveltekit } from '@sveltejs/kit/vite'
|
|
|
23
21
|
{{#if hasUi}}
|
|
24
22
|
import tailwindcss from '@tailwindcss/vite'
|
|
25
23
|
{{/if}}
|
|
26
|
-
import { defineConfig
|
|
24
|
+
import { defineConfig } from 'vite'
|
|
27
25
|
|
|
28
26
|
export default defineConfig(({ mode }) => {
|
|
29
|
-
const
|
|
27
|
+
const envResult = core.config.loadEnvFiles({ mode, envDir: process.cwd() })
|
|
28
|
+
if (!envResult.success)
|
|
29
|
+
throw new Error(envResult.error.message)
|
|
30
|
+
const env = envResult.data
|
|
30
31
|
Object.assign(process.env, env)
|
|
31
32
|
{{#if isSvelteOnlyApp}}
|
|
32
33
|
// 浏览器无法读取 YAML;构建时通过 core 解析,仅注入明确可公开的字段。
|
|
@@ -42,6 +43,7 @@ export default defineConfig(({ mode }) => {
|
|
|
42
43
|
{{/if}}
|
|
43
44
|
|
|
44
45
|
return {
|
|
46
|
+
envDir: false,
|
|
45
47
|
envPrefix: ['VITE_', 'PUBLIC_'],
|
|
46
48
|
{{#if isSvelteOnlyApp}}
|
|
47
49
|
define: { __APP_CORE_CONFIG__: JSON.stringify(appConfig) },
|
|
@@ -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
|
+
}
|
|
@@ -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
|
-
|
|
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。未运行项说明原因。
|