@h-ai/cli 0.1.0-alpha.60 → 0.1.0-alpha.62
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 +25 -1
- package/README.ja.md +25 -1
- package/README.md +25 -1
- package/dist/{chunk-P6X63D3I.js → chunk-WW2MIO6T.js} +377 -124
- package/dist/chunk-WW2MIO6T.js.map +1 -0
- package/dist/{chunk-WWG3U5ZD.js → chunk-X3RSYNBG.js} +8 -3
- package/dist/chunk-X3RSYNBG.js.map +1 -0
- package/dist/cli-config-templates-QRF6B5T4.js +5 -0
- package/dist/{cli-config-templates-EFABFSCY.js.map → cli-config-templates-QRF6B5T4.js.map} +1 -1
- package/dist/{cli-skill-templates-2KDUACT6.js → cli-skill-templates-EZFW7D43.js} +3 -3
- package/dist/{cli-skill-templates-2KDUACT6.js.map → cli-skill-templates-EZFW7D43.js.map} +1 -1
- package/dist/index.js +31 -10
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/templates/README.md +11 -4
- package/templates/apps/api/.gitignore.hbs +1 -0
- package/templates/apps/api/e2e/capture-evidence.ts.hbs +23 -0
- package/templates/apps/api/playwright.config.ts.hbs +1 -0
- package/templates/apps/fullstack/.gitignore.hbs +1 -0
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +123 -4
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +8 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_ai.yml.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_audit.yml.hbs +1 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_cache.yml.hbs +1 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +7 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_db.yml.hbs +1 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_deploy.yml.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_iam.yml.hbs +1 -67
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_payment.yml.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +31 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_scheduler.yml.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +10 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_storage.yml.hbs +1 -3
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_vecdb.yml.hbs +1 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +1 -21
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +7 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/en-US.json.hbs +19 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/zh-CN.json.hbs +19 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +7 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +5 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/ProfileSettings.svelte.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/guide-store.ts.hbs +55 -0
- package/templates/apps/fullstack/playwright.config.ts.hbs +21 -10
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +35 -23
- package/templates/base/.gitignore.hbs +1 -2
- package/templates/base/e2e/app.spec.ts.hbs +49 -3
- package/templates/base/package.json.hbs +1 -1
- package/templates/skills/AGENTS.md +6 -0
- package/templates/skills/bridges/admin/AGENTS.md +6 -0
- package/templates/skills/bridges/api/AGENTS.md +6 -0
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +6 -0
- package/templates/skills/bridges/h5/AGENTS.md +6 -0
- package/templates/skills/bridges/mobile-app/AGENTS.md +6 -0
- package/templates/skills/bridges/website/AGENTS.md +6 -0
- package/templates/skills/copilot-instructions.md +3 -9
- package/templates/skills/hai-app-create/SKILL.md +6 -0
- package/templates/skills/hai-app-create/development.md +10 -6
- package/templates/skills/hai-app-create/reference.md +10 -0
- package/templates/skills/hai-audit/SKILL.md +1 -1
- package/templates/skills/hai-build/SKILL.md +1 -0
- package/templates/skills/hai-build/reference.md +1 -1
- package/templates/skills/hai-ci/SKILL.md +1 -1
- package/templates/skills/hai-framework-sync/SKILL.md +67 -70
- package/templates/skills/hai-framework-sync/sync.md +49 -0
- package/templates/skills/hai-iam/SKILL.md +5 -0
- package/templates/skills/hai-iam/reference.md +88 -7
- package/templates/skills/hai-pr-review/SKILL.md +1 -1
- package/templates/skills/hai-reach/SKILL.md +1 -1
- package/templates/skills/hai-reach/reference.md +4 -5
- package/templates/skills/hai-serv/SKILL.md +1 -1
- package/templates/skills/hai-serv/reference.md +9 -6
- package/templates/skills/hai-spec-implement/SKILL.md +41 -0
- package/templates/skills/hai-spec-implement/templates.md +90 -0
- package/templates/skills/hai-spec-implement/workflow.md +53 -0
- package/templates/skills/hai-spec-review/SKILL.md +44 -0
- package/templates/skills/hai-spec-review/checklist.md +70 -0
- package/templates/skills/hai-storage/SKILL.md +9 -2
- package/templates/skills/hai-ui/SKILL.md +3 -1
- package/templates/skills/hai-ui/guide.md +79 -0
- package/templates/skills/hai-ui/reference.md +13 -0
- package/templates/skills/opencode.json +1 -1
- package/dist/chunk-P6X63D3I.js.map +0 -1
- package/dist/chunk-WWG3U5ZD.js.map +0 -1
- package/dist/cli-config-templates-EFABFSCY.js +0 -5
- package/templates/skills/bridges/admin/copilot-instructions.md +0 -11
- package/templates/skills/bridges/api/copilot-instructions.md +0 -13
- package/templates/skills/bridges/fullstack/copilot-instructions.md.hbs +0 -19
- package/templates/skills/bridges/h5/copilot-instructions.md +0 -11
- package/templates/skills/bridges/mobile-app/copilot-instructions.md +0 -11
- package/templates/skills/bridges/website/copilot-instructions.md +0 -11
|
@@ -83,5 +83,23 @@
|
|
|
83
83
|
"audit_system": "系统",
|
|
84
84
|
"audit_details": "审计日志详情",
|
|
85
85
|
"audit_date_invalid": "结束日期不能早于开始日期。",
|
|
86
|
-
"api_not_configured": "尚未配置服务连接,请联系应用管理员。"
|
|
86
|
+
"api_not_configured": "尚未配置服务连接,请联系应用管理员。",
|
|
87
|
+
"guide_overview_title": "快速认识管理后台",
|
|
88
|
+
"guide_overview_description": "用几个简短步骤了解工作区、个人设置和帮助入口。你可以随时退出,也可以从帮助中心重新查看。",
|
|
89
|
+
"guide_content_title": "工作区",
|
|
90
|
+
"guide_content_description": "这里展示当前页面的内容。通过导航菜单切换功能,页面会根据你的权限提供可用操作。",
|
|
91
|
+
"guide_theme_title": "外观切换",
|
|
92
|
+
"guide_theme_description": "点击这里快速切换亮色与暗色外观。更多主题可以在设置页面中选择。",
|
|
93
|
+
"guide_profile_title": "个人资料",
|
|
94
|
+
"guide_profile_description": "点击头像打开个人资料和设置入口,管理头像、个人信息与密码。",
|
|
95
|
+
"guide_help_title": "帮助中心",
|
|
96
|
+
"guide_help_description": "遇到问题时从这里搜索使用说明,或重新开始任意操作导览。",
|
|
97
|
+
"guide_profile_tour_title": "完善个人资料",
|
|
98
|
+
"guide_profile_tour_description": "了解个人信息、偏好和密码设置的位置。引导只说明操作,不会自动提交修改。",
|
|
99
|
+
"guide_profile_form_description": "在这里查看或编辑个人资料,选择头像并保存修改。上传文件需符合页面显示的格式与大小要求。",
|
|
100
|
+
"guide_preferences_description": "选择语言和主题后保存,界面与帮助内容会使用所选语言和外观。",
|
|
101
|
+
"guide_password_title": "修改密码",
|
|
102
|
+
"guide_password_description": "输入当前密码、新密码和确认密码,按页面提示提交。修改前请确认新密码符合密码规则。",
|
|
103
|
+
"guide_overview_article": "## 从哪里开始\n\n- 使用导航菜单打开需要的功能。可见菜单和操作由当前权限决定。\n- 工作区显示列表、详情或设置内容。按页面提示完成操作,提交前确认信息。\n- 点击头像访问个人资料与设置。\n- 点击帮助入口搜索说明或重新查看导览。\n\n## 首次引导\n\n选择“稍后提醒”会暂缓一天;选择“不再提醒”会保留你的选择。完成导览后不会重复提示,仍可手动重看。",
|
|
104
|
+
"guide_profile_article": "## 个人资料与偏好\n\n打开个人资料页面,查看或修改个人信息与头像。头像需符合页面显示的文件要求,保存后查看结果提示。外观和语言可在设置中调整。\n\n## 密码\n\n修改密码需要当前密码、新密码和确认密码。按页面显示的密码规则填写,提交后查看结果。\n\n点击“开始引导”可以直接进入对应页面。导览不会自动保存表单或修改账户。"
|
|
87
105
|
}
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
import { Alert, Skeleton } from '@h-ai/ui/components/compounds/index'
|
|
4
4
|
import { Button } from '@h-ai/ui/components/primitives/index'
|
|
5
5
|
import { AuthPanel } from '@h-ai/ui/components/scenes/iam/index'
|
|
6
|
-
import {
|
|
6
|
+
import { GuideHost } from '@h-ai/ui/guide'
|
|
7
|
+
import { onMount, untrack } from 'svelte'
|
|
7
8
|
import { managementAuthActions } from '../api/api-client.js'
|
|
8
9
|
import * as m from '../paraglide/messages.js'
|
|
9
10
|
import { ManagementStore } from '../stores/management-store.svelte.js'
|
|
11
|
+
import { createManagementGuide } from '../stores/guide-store.js'
|
|
10
12
|
import AppShell from './AppShell.svelte'
|
|
11
13
|
|
|
12
14
|
interface Props {
|
|
@@ -16,6 +18,8 @@
|
|
|
16
18
|
}
|
|
17
19
|
const { platform, apiOptions = {}, onLocaleChange }: Props = $props()
|
|
18
20
|
const store = new ManagementStore()
|
|
21
|
+
// 宿主平台在组件生命周期中固定,引导控制器只创建一次。
|
|
22
|
+
const guide = untrack(() => createManagementGuide(store, platform))
|
|
19
23
|
let resetToken = $state('')
|
|
20
24
|
async function authenticated() {
|
|
21
25
|
await store.authenticated()
|
|
@@ -67,7 +71,7 @@
|
|
|
67
71
|
{/if}
|
|
68
72
|
</main>
|
|
69
73
|
{:else}
|
|
70
|
-
<AppShell {platform} {store}>
|
|
74
|
+
<AppShell {platform} {store} onhelp={() => guide.openHelp()}>
|
|
71
75
|
<div class='space-y-5'>
|
|
72
76
|
{#if store.error}<Alert variant='error' data-testid='error'>{store.error}</Alert>{/if}
|
|
73
77
|
{#if store.notice}<Alert variant='success' data-testid='notice'>{store.notice}</Alert>{/if}
|
|
@@ -78,5 +82,6 @@
|
|
|
78
82
|
{/await}
|
|
79
83
|
</div>
|
|
80
84
|
</AppShell>
|
|
85
|
+
<GuideHost {guide} locale={store.locale} scope={store.user.id} autoStart='overview' />
|
|
81
86
|
{/if}
|
|
82
87
|
{/key}
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
platform: 'web' | 'app' | 'desktop' | 'miniapp'
|
|
9
9
|
store: ManagementStore
|
|
10
10
|
children: Snippet
|
|
11
|
+
onhelp: () => void
|
|
11
12
|
}
|
|
12
|
-
const { platform, store, children }: Props = $props()
|
|
13
|
+
const { platform, store, children, onhelp }: Props = $props()
|
|
13
14
|
let menuOpen = $state(false)
|
|
14
15
|
const items = $derived([
|
|
15
16
|
...(store.isAdmin
|
|
@@ -54,9 +55,10 @@
|
|
|
54
55
|
<span class="truncate text-sm font-medium">{title}</span>
|
|
55
56
|
</div>
|
|
56
57
|
<div class="flex shrink-0 items-center gap-1.5 sm:gap-3">
|
|
58
|
+
<Button variant="ghost" size="sm" onclick={onhelp} data-guide="admin-help" data-testid="admin-help" ariaLabel={m.guide_help_title()}><span class="icon-[tabler--help-circle] size-4"></span><span class="hidden sm:inline">{m.guide_help_title()}</span></Button>
|
|
57
59
|
<Badge variant="ghost" size="sm" data-testid="app-environment">{store.info?.env}</Badge>
|
|
58
60
|
{#if store.user}
|
|
59
|
-
<BareButton class="flex items-center gap-2 rounded-lg px-1 py-1 hover:bg-base-content/5 sm:px-2" ariaLabel={m.settings_title()} onclick={() => navigate('settings')}>
|
|
61
|
+
<BareButton class="flex items-center gap-2 rounded-lg px-1 py-1 hover:bg-base-content/5 sm:px-2" ariaLabel={m.settings_title()} onclick={() => navigate('settings')} data-guide="admin-profile">
|
|
60
62
|
<Avatar name={store.user.displayName || store.user.username} src={store.user.avatarUrl} size="sm" />
|
|
61
63
|
<span class="hidden max-w-36 truncate text-sm sm:block">{store.user.displayName || store.user.username}</span>
|
|
62
64
|
</BareButton>
|
|
@@ -64,7 +66,7 @@
|
|
|
64
66
|
<Button variant="ghost" size="sm" loading={store.saving} onclick={() => void store.logout()} data-testid="logout">{m.logout()}</Button>
|
|
65
67
|
</div>
|
|
66
68
|
</header>
|
|
67
|
-
<main class="mx-auto w-full max-w-7xl flex-1 p-4 pb-8 sm:p-6 lg:p-8">{@render children()}</main>
|
|
69
|
+
<main class="mx-auto w-full max-w-7xl flex-1 p-4 pb-8 sm:p-6 lg:p-8" data-guide="admin-content">{@render children()}</main>
|
|
68
70
|
<footer class="border-t border-base-content/8 px-6 py-3 text-center text-xs text-base-content/35">{store.info?.name} · v{store.info?.version}</footer>
|
|
69
71
|
</div>
|
|
70
72
|
</div>
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
</div>
|
|
33
33
|
<p class='mt-4 text-xs leading-6 text-base-content/50'>{m.avatar_hint()}</p>
|
|
34
34
|
</Card>
|
|
35
|
-
<Card title={m.profile_title()} bordered shadow={false}>
|
|
35
|
+
<Card title={m.profile_title()} bordered shadow={false} data-guide='admin-profile-form'>
|
|
36
36
|
<UserProfile user={store.user} editable alwaysEditable fields={['avatar', 'displayName', 'email']} avatarAccept='image/png,image/jpeg,image/webp' avatarHint={m.avatar_hint()} loading={store.saving} onsubmit={data => store.saveProfile(data)} onavatarchange={file => void store.uploadAvatar(file)} data-testid='profile-form' />
|
|
37
37
|
</Card>
|
|
38
38
|
</div>
|
|
39
|
-
<Card title={m.preferences_title()} bordered shadow={false}>
|
|
39
|
+
<Card title={m.preferences_title()} bordered shadow={false} data-guide='admin-preferences'>
|
|
40
40
|
<p class='mb-5 text-sm text-base-content/50'>{m.preferences_description()}</p>
|
|
41
41
|
<div class='grid gap-6 sm:grid-cols-2'>
|
|
42
42
|
<div class='space-y-2'>
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
</div>
|
|
50
50
|
</div>
|
|
51
51
|
</Card>
|
|
52
|
-
<Card title={m.password_title()} bordered shadow={false}>
|
|
52
|
+
<Card title={m.password_title()} bordered shadow={false} data-guide='admin-password'>
|
|
53
53
|
<p class='mb-5 text-sm text-base-content/50'>{m.password_rules()}</p>
|
|
54
54
|
<div class='max-w-xl'><ChangePasswordForm loading={store.saving} passwordPolicy={store.authConfig?.password} minPasswordLength={store.authConfig?.password.minLength ?? 8} onsubmit={data => store.changePassword(data)} data-testid='password-form' /></div>
|
|
55
55
|
</Card>
|
package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/guide-store.ts.hbs
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { NavAdapter } from '@h-ai/ui'
|
|
2
|
+
import type { ManagementStore } from './management-store.svelte.js'
|
|
3
|
+
import { createGuide } from '@h-ai/ui/guide'
|
|
4
|
+
import * as m from '../paraglide/messages.js'
|
|
5
|
+
|
|
6
|
+
/** Web/App/Desktop 共用内容;宿主只提供当前页面和平台持久化方式。 */
|
|
7
|
+
export function createManagementGuide(store: ManagementStore, platform: 'web' | 'app' | 'desktop') {
|
|
8
|
+
const nav: NavAdapter = {
|
|
9
|
+
get pathname() {
|
|
10
|
+
return `/${store.page}`
|
|
11
|
+
},
|
|
12
|
+
navigate(route) {
|
|
13
|
+
if (route === '/settings')
|
|
14
|
+
return store.navigate('settings')
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
return createGuide({
|
|
18
|
+
nav,
|
|
19
|
+
persistKey: '{{projectName}}-guide',
|
|
20
|
+
persistence: platform === 'web' ? 'cookie' : 'localStorage',
|
|
21
|
+
tours: [{
|
|
22
|
+
id: 'overview',
|
|
23
|
+
title: () => m.guide_overview_title(),
|
|
24
|
+
description: () => m.guide_overview_description(),
|
|
25
|
+
steps: [
|
|
26
|
+
{ target: 'admin-content', title: () => m.guide_content_title(), description: () => m.guide_content_description() },
|
|
27
|
+
{ target: 'admin-profile', title: () => m.guide_profile_title(), description: () => m.guide_profile_description() },
|
|
28
|
+
{ target: 'admin-help', title: () => m.guide_help_title(), description: () => m.guide_help_description() },
|
|
29
|
+
],
|
|
30
|
+
}, {
|
|
31
|
+
id: 'profile',
|
|
32
|
+
title: () => m.guide_profile_tour_title(),
|
|
33
|
+
description: () => m.guide_profile_tour_description(),
|
|
34
|
+
route: '/settings',
|
|
35
|
+
steps: [
|
|
36
|
+
{ target: 'admin-profile-form', title: () => m.guide_profile_title(), description: () => m.guide_profile_form_description() },
|
|
37
|
+
{ target: 'admin-preferences', title: () => m.preferences_title(), description: () => m.guide_preferences_description() },
|
|
38
|
+
{ target: 'admin-password', title: () => m.guide_password_title(), description: () => m.guide_password_description() },
|
|
39
|
+
],
|
|
40
|
+
}],
|
|
41
|
+
articles: [{
|
|
42
|
+
id: 'overview',
|
|
43
|
+
title: () => m.guide_overview_title(),
|
|
44
|
+
description: () => m.guide_overview_description(),
|
|
45
|
+
content: () => m.guide_overview_article(),
|
|
46
|
+
tourId: 'overview',
|
|
47
|
+
}, {
|
|
48
|
+
id: 'profile',
|
|
49
|
+
title: () => m.guide_profile_tour_title(),
|
|
50
|
+
description: () => m.guide_profile_tour_description(),
|
|
51
|
+
content: () => m.guide_profile_article(),
|
|
52
|
+
tourId: 'profile',
|
|
53
|
+
}],
|
|
54
|
+
})
|
|
55
|
+
}
|
|
@@ -4,8 +4,16 @@ import { defineConfig } from '@playwright/test'
|
|
|
4
4
|
const defaultBaseURL = '{{#if fullstack.e2eFrontend}}http://127.0.0.1:{{fullstack.e2eFrontend.port}}{{else}}http://127.0.0.1:3000{{/if}}'
|
|
5
5
|
const defaultServiceBaseURL = 'http://127.0.0.1:3000'
|
|
6
6
|
const baseURL = process.env.BASE_URL || defaultBaseURL
|
|
7
|
-
const
|
|
8
|
-
const
|
|
7
|
+
const primaryFrontend = '{{fullstack.e2eFrontend.target}}'
|
|
8
|
+
const frontends: { target: string, name: string, url: string }[] = [
|
|
9
|
+
{{#each fullstack.frontendApps}}
|
|
10
|
+
{
|
|
11
|
+
target: '{{target}}',
|
|
12
|
+
name: '{{packageName}}',
|
|
13
|
+
url: process.env.HAI_E2E_{{target}}_URL || 'http://127.0.0.1:{{port}}',
|
|
14
|
+
},
|
|
15
|
+
{{/each}}
|
|
16
|
+
]
|
|
9
17
|
const serviceBaseURL = process.env.SERVICE_BASE_URL || defaultServiceBaseURL
|
|
10
18
|
const serviceHost = resolveHostFromBaseUrl(serviceBaseURL, '127.0.0.1')
|
|
11
19
|
const servicePort = resolvePortFromBaseUrl(serviceBaseURL, '3000')
|
|
@@ -65,13 +73,16 @@ export default defineConfig({
|
|
|
65
73
|
url: `${serviceBaseURL}/health`,
|
|
66
74
|
reuseExistingServer: false,
|
|
67
75
|
timeout: 120_000,
|
|
68
|
-
}
|
|
69
|
-
{
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
},
|
|
77
|
+
...frontends.map((frontend) => {
|
|
78
|
+
const url = frontend.target === primaryFrontend ? baseURL : frontend.url
|
|
79
|
+
return {
|
|
80
|
+
name: frontend.name,
|
|
81
|
+
command: `pnpm --filter ${frontend.name} exec vite preview --host ${resolveHostFromBaseUrl(url, '127.0.0.1')} --port ${resolvePortFromBaseUrl(url, '3000')} --strictPort`,
|
|
82
|
+
url,
|
|
83
|
+
reuseExistingServer: false,
|
|
84
|
+
timeout: 120_000,
|
|
85
|
+
}
|
|
86
|
+
}),
|
|
76
87
|
],
|
|
77
88
|
})
|
|
@@ -3,16 +3,20 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* H5 应用根布局
|
|
5
5
|
*/
|
|
6
|
+
import type { BottomNavItem } from '@h-ai/ui'
|
|
6
7
|
import type { Snippet } from 'svelte'
|
|
7
8
|
{{#if hasI18n}}
|
|
8
9
|
import { browser } from '$app/environment'
|
|
9
10
|
{{/if}}
|
|
11
|
+
import { goto } from '$app/navigation'
|
|
10
12
|
import { resolve } from '$app/paths'
|
|
11
13
|
import { page } from '$app/state'
|
|
12
14
|
{{#if hasI18n}}
|
|
13
15
|
import * as m from '$lib/paraglide/messages'
|
|
14
16
|
import { getLocale } from '$lib/paraglide/runtime.js'
|
|
15
|
-
import { setGlobalLocale } from '@h-ai/ui'
|
|
17
|
+
import { BottomNav, setGlobalLocale } from '@h-ai/ui'
|
|
18
|
+
{{else}}
|
|
19
|
+
import { BottomNav } from '@h-ai/ui'
|
|
16
20
|
{{/if}}
|
|
17
21
|
import '../app.css'
|
|
18
22
|
|
|
@@ -22,17 +26,27 @@
|
|
|
22
26
|
|
|
23
27
|
const { children }: Props = $props()
|
|
24
28
|
|
|
25
|
-
const navItems = [
|
|
29
|
+
const navItems: (BottomNavItem & { path: '/' | '/discover' | '/profile' })[] = [
|
|
26
30
|
{{#if hasI18n}}
|
|
27
|
-
{ path: '/', label: m.nav_home(),
|
|
28
|
-
{ path: '/discover', label: m.nav_discover(),
|
|
29
|
-
{ path: '/profile', label: m.nav_profile(),
|
|
31
|
+
{ id: 'home', path: '/', label: m.nav_home(), iconClass: 'icon-[tabler--home]' },
|
|
32
|
+
{ id: 'discover', path: '/discover', label: m.nav_discover(), iconClass: 'icon-[tabler--compass]' },
|
|
33
|
+
{ id: 'profile', path: '/profile', label: m.nav_profile(), iconClass: 'icon-[tabler--user]' },
|
|
30
34
|
{{else}}
|
|
31
|
-
{ path: '/', label: '首页',
|
|
32
|
-
{ path: '/discover', label: '发现',
|
|
33
|
-
{ path: '/profile', label: '我的',
|
|
35
|
+
{ id: 'home', path: '/', label: '首页', iconClass: 'icon-[tabler--home]' },
|
|
36
|
+
{ id: 'discover', path: '/discover', label: '发现', iconClass: 'icon-[tabler--compass]' },
|
|
37
|
+
{ id: 'profile', path: '/profile', label: '我的', iconClass: 'icon-[tabler--user]' },
|
|
34
38
|
{{/if}}
|
|
35
|
-
]
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
const activeNav = $derived(navItems.find(item => item.path === page.url.pathname)?.id)
|
|
42
|
+
// 认证页面独立显示;业务页面复用框架导航和移动端安全区域样式。
|
|
43
|
+
const isAuthPage = $derived(page.url.pathname.startsWith('/auth'))
|
|
44
|
+
|
|
45
|
+
function handleNavigation(id: string) {
|
|
46
|
+
const item = navItems.find(item => item.id === id)
|
|
47
|
+
if (item)
|
|
48
|
+
void goto(resolve(item.path, {}))
|
|
49
|
+
}
|
|
36
50
|
{{#if hasI18n}}
|
|
37
51
|
|
|
38
52
|
if (browser) {
|
|
@@ -46,17 +60,15 @@
|
|
|
46
60
|
<title>{page.data.appConfig.name}</title>
|
|
47
61
|
</svelte:head>
|
|
48
62
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
</nav>
|
|
62
|
-
</div>
|
|
63
|
+
{#if isAuthPage}
|
|
64
|
+
{@render children()}
|
|
65
|
+
{:else}
|
|
66
|
+
<div class='hai-mobile-viewport'>
|
|
67
|
+
<div class='hai-mobile-shell max-w-lg'>
|
|
68
|
+
<main class='hai-mobile-main'>
|
|
69
|
+
{@render children()}
|
|
70
|
+
</main>
|
|
71
|
+
<BottomNav items={navItems} active={activeNav} onchange={handleNavigation} centered maxWidth='lg' safeArea />
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
{/if}
|
|
@@ -1,24 +1,48 @@
|
|
|
1
|
-
{{#if features.iam}}
|
|
2
1
|
import { mkdir } from 'node:fs/promises'
|
|
3
2
|
import path from 'node:path'
|
|
4
|
-
{{/if}}
|
|
5
3
|
import process from 'node:process'
|
|
6
4
|
import { expect, test } from '@playwright/test'
|
|
7
5
|
|
|
6
|
+
{{#if_eq appType 'h5'}}
|
|
7
|
+
test.use({ viewport: { width: 390, height: 844 } })
|
|
8
|
+
{{/if_eq}}
|
|
9
|
+
{{#if isCapacitorApp}}
|
|
10
|
+
test.use({ viewport: { width: 390, height: 844 } })
|
|
11
|
+
{{/if}}
|
|
12
|
+
|
|
8
13
|
/** 首页从真实浏览器页面发起,覆盖基础渲染链路。 */
|
|
9
14
|
test('home page renders from browser', async ({ page }) => {
|
|
15
|
+
{{#if_eq appType 'h5'}}
|
|
16
|
+
await page.setViewportSize({ width: 390, height: 844 })
|
|
17
|
+
{{/if_eq}}
|
|
10
18
|
const pageErrors: string[] = []
|
|
11
19
|
page.on('pageerror', error => pageErrors.push(error.message))
|
|
12
20
|
await page.goto('/')
|
|
13
21
|
await expect(page).toHaveTitle(process.env.HAI_CORE_NAME ?? '{{projectName}}')
|
|
14
22
|
await expect(pageErrors).toEqual([])
|
|
15
23
|
await expect(page.locator('main, section').first()).toBeVisible()
|
|
24
|
+
{{#if_eq appType 'h5'}}
|
|
25
|
+
// 在手机宽度验证导航真正切换路由,避免样式失效但首页渲染测试仍通过。
|
|
26
|
+
const nav = page.locator('nav')
|
|
27
|
+
await nav.getByRole('button', { name: /发现|Discover/i, exact: true }).click()
|
|
28
|
+
await expect(page).toHaveURL(/\/discover$/)
|
|
29
|
+
await nav.getByRole('button', { name: /首页|Home/i, exact: true }).click()
|
|
30
|
+
await expect(page).toHaveURL(/\/$/)
|
|
31
|
+
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
|
|
32
|
+
{{/if_eq}}
|
|
33
|
+
const evidence = process.env.HAI_AUTH_EVIDENCE_DIR || path.resolve('test-results/auth-screenshots')
|
|
34
|
+
await mkdir(evidence, { recursive: true })
|
|
35
|
+
await page.evaluate(() => document.fonts.ready)
|
|
36
|
+
await page.screenshot({ path: path.join(evidence, '{{projectName}}-home.png'), fullPage: true, animations: 'disabled' })
|
|
16
37
|
})
|
|
17
38
|
{{#if features.iam}}
|
|
18
39
|
|
|
19
40
|
/** 实际注册和密码登录;截图只保存空白表单。 */
|
|
20
41
|
test('configured IAM registration and password login', async ({ page, context }) => {
|
|
21
42
|
test.setTimeout(90_000)
|
|
43
|
+
{{#if_eq appType 'h5'}}
|
|
44
|
+
await page.setViewportSize({ width: 390, height: 844 })
|
|
45
|
+
{{/if_eq}}
|
|
22
46
|
const evidence = process.env.HAI_AUTH_EVIDENCE_DIR || path.resolve('test-results/auth-screenshots')
|
|
23
47
|
await mkdir(evidence, { recursive: true })
|
|
24
48
|
const username = `scaf${Date.now()}`
|
|
@@ -93,7 +117,29 @@ test('configured IAM registration and password login', async ({ page, context })
|
|
|
93
117
|
expect(Boolean(reset?.token)).toBe(true)
|
|
94
118
|
await page.getByRole('button', { name: /已有重置令牌|I have a reset token/i }).click()
|
|
95
119
|
await page.screenshot({ path: path.join(evidence, '{{projectName}}-reset.png'), fullPage: true })
|
|
96
|
-
|
|
120
|
+
const resetUrl = `{{#if isCapacitorApp}}/{{else}}/auth/reset-password{{/if}}?token=${encodeURIComponent(reset?.token ?? '')}`
|
|
121
|
+
{{#if_eq appType 'h5'}}
|
|
122
|
+
// 延迟客户端脚本,确定性覆盖 SSR 页面已显示、事件绑定尚未完成的窗口。
|
|
123
|
+
let resumeClient = () => {}
|
|
124
|
+
const clientBoot = new Promise<void>((resolve) => {
|
|
125
|
+
resumeClient = resolve
|
|
126
|
+
})
|
|
127
|
+
await page.route('**/*.js', async (route) => {
|
|
128
|
+
await clientBoot
|
|
129
|
+
await route.continue()
|
|
130
|
+
})
|
|
131
|
+
try {
|
|
132
|
+
await page.goto(resetUrl, { waitUntil: 'commit' })
|
|
133
|
+
await expect(page.locator('input[type="password"]').nth(0)).toBeDisabled()
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
resumeClient()
|
|
137
|
+
// 等待正在恢复的路由完成,避免提前移除拦截导致 continue 被重复处理。
|
|
138
|
+
await page.unrouteAll({ behavior: 'wait' })
|
|
139
|
+
}
|
|
140
|
+
{{else}}
|
|
141
|
+
await page.goto(resetUrl)
|
|
142
|
+
{{/if_eq}}
|
|
97
143
|
await expect(page.locator('input[type="password"]').nth(0)).toBeVisible()
|
|
98
144
|
await page.locator('input[type="password"]').nth(0).fill('ScaffoldReset456')
|
|
99
145
|
await page.locator('input[type="password"]').nth(1).fill('ScaffoldReset456')
|
|
@@ -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.62",
|
|
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}}",
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
## 行为契约
|
|
6
6
|
|
|
7
|
+
需求/设计文档审查或从文档生成代码时,读取 [hai-spec-review](.agents/skills/hai-spec-review/SKILL.md),严格确认完备性、歧义、技术正确性与命名一致性;影响行为的问题未解决前不判定文档可生成。普通小改动不重审无关文档。
|
|
8
|
+
|
|
9
|
+
升级 hai-framework 到最新/指定版本、比较版本差异或同步框架 skills 时,先读 [框架升级与同步 Skill](.agents/skills/hai-framework-sync/SKILL.md),执行版本发现、差异分析、用户选择/阻塞处理、迁移与验证;仅要求比较时不修改应用。
|
|
10
|
+
|
|
11
|
+
根据文档规划、实施、继续或返修任务时,读取 [hai-spec-implement](.agents/skills/hai-spec-implement/SKILL.md):审查后按可验收功能和依赖拆分,维护 docs 下的清单及 docs/tasks 下的任务信息,每项独立分支 TDD、验证并本地合并;返修复用原任务与分支。仅规划不实施,指定任务先核验依赖。
|
|
12
|
+
|
|
7
13
|
普通新增、修改、修复任务开始前,先读 [应用开发 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 树。
|
|
8
14
|
|
|
9
15
|
- 对当前需求逐项检索 hai-framework 能力和已有实现,确认适用的公开 API 后优先复用;未安装模块先核对依赖与配置,不能因 Skill 已存在就假定已启用。框架不适用时说明具体限制与选用方案,不为覆盖清单引入无关模块。
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
## 行为契约
|
|
6
6
|
|
|
7
|
+
需求/设计文档审查或从文档生成代码时,读取 [hai-spec-review](.agents/skills/hai-spec-review/SKILL.md),严格确认完备性、歧义、技术正确性与命名一致性;影响行为的问题未解决前不判定文档可生成。普通小改动不重审无关文档。
|
|
8
|
+
|
|
9
|
+
升级 hai-framework 到最新/指定版本、比较版本差异或同步框架 skills 时,先读 [框架升级与同步 Skill](.agents/skills/hai-framework-sync/SKILL.md),执行版本发现、差异分析、用户选择/阻塞处理、迁移与验证;仅要求比较时不修改应用。
|
|
10
|
+
|
|
11
|
+
根据文档规划、实施、继续或返修任务时,读取 [hai-spec-implement](.agents/skills/hai-spec-implement/SKILL.md):审查后按可验收功能和依赖拆分,维护 docs 下的清单及 docs/tasks 下的任务信息,每项独立分支 TDD、验证并本地合并;返修复用原任务与分支。仅规划不实施,指定任务先核验依赖。
|
|
12
|
+
|
|
7
13
|
普通新增、修改、修复任务开始前,先读 [应用开发 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 树。
|
|
8
14
|
|
|
9
15
|
- 对当前需求逐项检索 hai-framework 能力和已有实现,确认适用的公开 API 后优先复用;未安装模块先核对依赖与配置,不能因 Skill 已存在就假定已启用。框架不适用时说明具体限制与选用方案,不为覆盖清单引入无关模块。
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
## 行为契约
|
|
6
6
|
|
|
7
|
+
需求/设计文档审查或从文档生成代码时,读取 [hai-spec-review](.agents/skills/hai-spec-review/SKILL.md),严格确认完备性、歧义、技术正确性与命名一致性;影响行为的问题未解决前不判定文档可生成。普通小改动不重审无关文档。
|
|
8
|
+
|
|
9
|
+
升级 hai-framework 到最新/指定版本、比较版本差异或同步框架 skills 时,先读 [框架升级与同步 Skill](.agents/skills/hai-framework-sync/SKILL.md),执行版本发现、差异分析、用户选择/阻塞处理、迁移与验证;仅要求比较时不修改应用。
|
|
10
|
+
|
|
11
|
+
根据文档规划、实施、继续或返修任务时,读取 [hai-spec-implement](.agents/skills/hai-spec-implement/SKILL.md):审查后按可验收功能和依赖拆分,维护 docs 下的清单及 docs/tasks 下的任务信息,每项独立分支 TDD、验证并本地合并;返修复用原任务与分支。仅规划不实施,指定任务先核验依赖。
|
|
12
|
+
|
|
7
13
|
普通新增、修改、修复任务开始前,先读 [应用开发 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 树。
|
|
8
14
|
|
|
9
15
|
- 对当前需求逐项检索 hai-framework 能力和已有实现,确认适用的公开 API 后优先复用;未安装模块先核对依赖与配置,不能因 Skill 已存在就假定已启用。框架不适用时说明具体限制与选用方案,不为覆盖清单引入无关模块。
|
|
@@ -4,6 +4,12 @@ Fullstack 多包工程入口。先读 README、根 workspace 脚本与相关 pac
|
|
|
4
4
|
|
|
5
5
|
## 行为契约
|
|
6
6
|
|
|
7
|
+
需求/设计文档审查或从文档生成代码时,读取 [hai-spec-review](.agents/skills/hai-spec-review/SKILL.md),严格确认完备性、歧义、技术正确性与命名一致性;影响行为的问题未解决前不判定文档可生成。普通小改动不重审无关文档。
|
|
8
|
+
|
|
9
|
+
升级 hai-framework 到最新/指定版本、比较版本差异或同步框架 skills 时,先读 [框架升级与同步 Skill](.agents/skills/hai-framework-sync/SKILL.md),执行版本发现、差异分析、用户选择/阻塞处理、迁移与验证;仅要求比较时不修改应用。
|
|
10
|
+
|
|
11
|
+
根据文档规划、实施、继续或返修任务时,读取 [hai-spec-implement](.agents/skills/hai-spec-implement/SKILL.md):审查后按可验收功能和依赖拆分,维护 docs 下的清单及 docs/tasks 下的任务信息,每项独立分支 TDD、验证并本地合并;返修复用原任务与分支。仅规划不实施,指定任务先核验依赖。
|
|
12
|
+
|
|
7
13
|
普通新增、修改、修复任务开始前,先读 [应用开发 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 树。
|
|
8
14
|
|
|
9
15
|
- 对当前需求逐项检索 hai-framework 能力和已有实现,确认适用的公开 API 后优先复用;未安装模块先核对依赖与配置,不能因 Skill 已存在就假定已启用。框架不适用时说明具体限制与选用方案,不为覆盖清单引入无关模块。
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
## 行为契约
|
|
6
6
|
|
|
7
|
+
需求/设计文档审查或从文档生成代码时,读取 [hai-spec-review](.agents/skills/hai-spec-review/SKILL.md),严格确认完备性、歧义、技术正确性与命名一致性;影响行为的问题未解决前不判定文档可生成。普通小改动不重审无关文档。
|
|
8
|
+
|
|
9
|
+
升级 hai-framework 到最新/指定版本、比较版本差异或同步框架 skills 时,先读 [框架升级与同步 Skill](.agents/skills/hai-framework-sync/SKILL.md),执行版本发现、差异分析、用户选择/阻塞处理、迁移与验证;仅要求比较时不修改应用。
|
|
10
|
+
|
|
11
|
+
根据文档规划、实施、继续或返修任务时,读取 [hai-spec-implement](.agents/skills/hai-spec-implement/SKILL.md):审查后按可验收功能和依赖拆分,维护 docs 下的清单及 docs/tasks 下的任务信息,每项独立分支 TDD、验证并本地合并;返修复用原任务与分支。仅规划不实施,指定任务先核验依赖。
|
|
12
|
+
|
|
7
13
|
普通新增、修改、修复任务开始前,先读 [应用开发 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 树。
|
|
8
14
|
|
|
9
15
|
- 对当前需求逐项检索 hai-framework 能力和已有实现,确认适用的公开 API 后优先复用;未安装模块先核对依赖与配置,不能因 Skill 已存在就假定已启用。框架不适用时说明具体限制与选用方案,不为覆盖清单引入无关模块。
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
## 行为契约
|
|
6
6
|
|
|
7
|
+
需求/设计文档审查或从文档生成代码时,读取 [hai-spec-review](.agents/skills/hai-spec-review/SKILL.md),严格确认完备性、歧义、技术正确性与命名一致性;影响行为的问题未解决前不判定文档可生成。普通小改动不重审无关文档。
|
|
8
|
+
|
|
9
|
+
升级 hai-framework 到最新/指定版本、比较版本差异或同步框架 skills 时,先读 [框架升级与同步 Skill](.agents/skills/hai-framework-sync/SKILL.md),执行版本发现、差异分析、用户选择/阻塞处理、迁移与验证;仅要求比较时不修改应用。
|
|
10
|
+
|
|
11
|
+
根据文档规划、实施、继续或返修任务时,读取 [hai-spec-implement](.agents/skills/hai-spec-implement/SKILL.md):审查后按可验收功能和依赖拆分,维护 docs 下的清单及 docs/tasks 下的任务信息,每项独立分支 TDD、验证并本地合并;返修复用原任务与分支。仅规划不实施,指定任务先核验依赖。
|
|
12
|
+
|
|
7
13
|
普通新增、修改、修复任务开始前,先读 [应用开发 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 树。
|
|
8
14
|
|
|
9
15
|
- 对当前需求逐项检索 hai-framework 能力和已有实现,确认适用的公开 API 后优先复用;未安装模块先核对依赖与配置,不能因 Skill 已存在就假定已启用。框架不适用时说明具体限制与选用方案,不为覆盖清单引入无关模块。
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
## 行为契约
|
|
6
6
|
|
|
7
|
+
需求/设计文档审查或从文档生成代码时,读取 [hai-spec-review](.agents/skills/hai-spec-review/SKILL.md),严格确认完备性、歧义、技术正确性与命名一致性;影响行为的问题未解决前不判定文档可生成。普通小改动不重审无关文档。
|
|
8
|
+
|
|
9
|
+
升级 hai-framework 到最新/指定版本、比较版本差异或同步框架 skills 时,先读 [框架升级与同步 Skill](.agents/skills/hai-framework-sync/SKILL.md),执行版本发现、差异分析、用户选择/阻塞处理、迁移与验证;仅要求比较时不修改应用。
|
|
10
|
+
|
|
11
|
+
根据文档规划、实施、继续或返修任务时,读取 [hai-spec-implement](.agents/skills/hai-spec-implement/SKILL.md):审查后按可验收功能和依赖拆分,维护 docs 下的清单及 docs/tasks 下的任务信息,每项独立分支 TDD、验证并本地合并;返修复用原任务与分支。仅规划不实施,指定任务先核验依赖。
|
|
12
|
+
|
|
7
13
|
普通新增、修改、修复任务开始前,先读 [应用开发 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 树。
|
|
8
14
|
|
|
9
15
|
- 对当前需求逐项检索 hai-framework 能力和已有实现,确认适用的公开 API 后优先复用;未安装模块先核对依赖与配置,不能因 Skill 已存在就假定已启用。框架不适用时说明具体限制与选用方案,不为覆盖清单引入无关模块。
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
# GitHub Copilot
|
|
1
|
+
# GitHub Copilot 入口
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
先读取共享入口 [AGENTS.md](../AGENTS.md),按其中的项目架构、行为契约、质量门禁和完成条件执行任务。模块与工作流按需读取 `.agents/skills/` 的正文和相邻参考文件。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## 质量门禁
|
|
8
|
-
|
|
9
|
-
按 AGENTS.md 和实际 package.json 选择 typecheck、lint、build、test、test:e2e;最终说明已执行结果、未运行原因及同步文档/依赖方。
|
|
10
|
-
|
|
11
|
-
普通新增、修改、修复任务先读 [应用开发 Skill](../.agents/skills/hai-app-create/SKILL.md) 和 [开发规范与框架能力路由](../.agents/skills/hai-app-create/development.md),依照 AGENTS.md 的默认规则复用框架并完成安全与质量验证。
|
|
5
|
+
项目规则统一在 `AGENTS.md` 维护;本文件仅提供工具入口。
|
|
@@ -17,6 +17,10 @@ description: "在 hai 应用中新增、修改或修复功能,按需求复用
|
|
|
17
17
|
|
|
18
18
|
## 使用路径
|
|
19
19
|
|
|
20
|
+
从需求/设计文档开始生成项目或功能前,使用 [hai-spec-review](../hai-spec-review/SKILL.md) 检查文档是否足以确定实现;修正影响行为的缺项、歧义和技术错误后再进入生成。普通小改动按本次范围确认规则,不要求重审无关文档。
|
|
21
|
+
|
|
22
|
+
需要按文档规划/执行任务或返修已交付任务时,使用 [hai-spec-implement](../hai-spec-implement/SKILL.md) 管理 docs 清单、任务记录、TDD 和任务分支;本 skill 负责其中的具体应用实现,复用记录中的范围与验收。
|
|
23
|
+
|
|
20
24
|
需要新建项目、添加模块、补齐配置或生成代码时,先按 [CLI 安装与命令](development.md#cli-安装与命令) 全局安装 `@h-ai/cli`,再执行 `hai` 命令。
|
|
21
25
|
|
|
22
26
|
1. 读取 [development.md](development.md):先按真实需求匹配框架能力、已安装版本与应用架构,再确定改动。适用能力包括同一模块已有 feature、配置和扩展点,不以导入包作为复用完成的证据。
|
|
@@ -26,6 +30,8 @@ description: "在 hai 应用中新增、修改或修复功能,按需求复用
|
|
|
26
30
|
|
|
27
31
|
## 按需参考
|
|
28
32
|
|
|
33
|
+
新增新手引导或帮助说明时读取 [UI 引导指南](../hai-ui/guide.md),复用 fullstack 默认的共享 guide-store、locale 和稳定 data-guide 锚点。
|
|
34
|
+
|
|
29
35
|
详细配置、API 签名、错误码和范本在 [reference.md](reference.md)。按下列标题定位所需小节,不默认通读;用例中的业务变量需结合当前应用补齐。
|
|
30
36
|
|
|
31
37
|
- 适用场景
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
## 先确认应用与版本
|
|
6
6
|
|
|
7
|
+
- 新手引导与帮助中心使用 `@h-ai/ui/guide`,按 [UI 引导指南](../hai-ui/guide.md) 接入。fullstack 的共享管理界面已有默认帮助入口;业务内容放共享 guide-store,使用应用 i18n 和稳定 data-guide 锚点,不重复实现浮层。
|
|
8
|
+
|
|
7
9
|
- 读取当前 `AGENTS.md`、README、`package.json`、lockfile、初始化入口和已有相似功能;用 `rg` 查实现、引用、测试。保留已有改动,沿用项目分层、命名、ESLint、i18n 与测试入口。
|
|
8
10
|
- 确认架构:SvelteKit 使用 `kit` 和文件路由;API workspace 使用 contract/service;fullstack 使用 contract/serv/shared 与所选前端;Vite SPA/原生端通过 typed API 客户端调用服务。不要把服务端目录、DB、密钥或 Node 初始化复制到浏览器。
|
|
9
11
|
- 从所属 workspace 的依赖与 lockfile 确认 `@h-ai/*` 实际解析版本和 npm/local 模式。模板 skill 提供导航,**当前安装包的 `package.json.exports`、公开 `.d.ts` 与随包文档决定可用 API**;本地框架源码只在版本匹配或显式联调时作实现证据。
|
|
10
|
-
- 若 skill
|
|
12
|
+
- 若 skill 与安装版本不符,以公开类型和实际行为核验。先使用已支持能力;任务要求升级时按 [hai-framework-sync](../hai-framework-sync/SKILL.md) 发现目标、比较差异并完成迁移验证。不能直接复制未发布源码的 API,也不能为绕过差异添加虚构兼容层。
|
|
11
13
|
|
|
12
14
|
## CLI 安装与命令
|
|
13
15
|
|
|
@@ -28,7 +30,7 @@ hai create my-app
|
|
|
28
30
|
|
|
29
31
|
应用元信息只有一份配置来源:名称、版本、环境、默认语言与日志设置来自 `_core.yml`,使用 `core.config` 与 `core.logger`;Node 启动时不通过固定 `logging` 覆盖 YAML。SvelteKit 服务端 layout 和 fullstack `app.info` 仅公开必要元信息,不能下发整个 core 或模块配置。独立 Vite mobile-app 在 Node 构建阶段经 core 加载 YAML/环境覆盖并注入公开字段,配置变化需重新构建。环境覆盖使用 `HAI_CORE_NAME`、`HAI_CORE_VERSION`、`HAI_CORE_ENV`、`HAI_CORE_LOGGING_LEVEL` 等既有规则;容器明确配置生产环境,不能用另一个状态来源代替 `core.env`。
|
|
30
32
|
|
|
31
|
-
开发前,对本次涉及的需求简述“需求 → 现有应用实现/框架能力 →
|
|
33
|
+
开发前,对本次涉及的需求简述“需求 → 现有应用实现/框架能力 → 已安装版本与公开契约依据 → 最小业务补充/修改位置 → 成功、权限与失败验收”。简单任务可用几句话;跨模块任务用表格。查找顺序:已有应用实现 → 框架公开能力和配置 → 最小业务补充。
|
|
32
34
|
|
|
33
35
|
下表技能名用于能力导航;先确认 `.agents/skills/<name>/SKILL.md` 存在再读取。CLI 按应用类型/已选模块交付技能,缺目录不代表框架没有该能力:继续查已安装包的公开 exports/types;确需补充模块/技能时按当前 CLI 帮助和项目依赖策略处理,不能跳过需求匹配或猜测不存在的 API。
|
|
34
36
|
|
|
@@ -37,20 +39,20 @@ hai create my-app
|
|
|
37
39
|
| 配置、日志、ID、结果/错误、i18n | `hai-core` | getter、logger、ID 是直接值;ID 不作为密钥 |
|
|
38
40
|
| 数据库查询、CRUD、事务、分页 | `hai-reldb` | 先检查现有 CRUD/Repository 与事务支持,不另建 DB 连接层 |
|
|
39
41
|
| 缓存、TTL、共享锁 | `hai-cache` | 区分 memory 与共享后端;单进程状态不能承担跨节点防重 |
|
|
40
|
-
| 注册、登录、会话、角色/权限 | `hai-iam` |
|
|
42
|
+
| 注册、登录、会话、角色/权限 | `hai-iam` | 优先配置内置认证能力;受保护请求使用 iam.auth.verifyToken,serv 注入 IAM 时自动使用;批量用户与角色筛选复用公开 API;业务资源/租户授权仍由应用检查 |
|
|
41
43
|
| SvelteKit Hook、守卫、校验、响应 | `hai-kit` | 只用于 SvelteKit;沿用已有认证/transport 管道 |
|
|
42
44
|
| 共享输入输出和 HTTP contract | `hai-api-contract` | 扩展已有 contract 与 schema,不维护第二份业务路径 |
|
|
43
45
|
| API 运行时、内置 feature、route guard | `hai-serv` | 先检查内置 procedures/features 与 route auth/permission;不另写平行服务器 |
|
|
44
46
|
| 浏览器/原生 API、刷新会话 | `hai-api-client` | 已支持接口复用 typed client、认证与 transport;额外 fetch/刷新封装需有缺口依据 |
|
|
45
47
|
| 页面、表单、表格、弹窗、移动 UI | `hai-ui` | 按实际导出选择场景/组合/原子组件;应用补业务交互 |
|
|
46
|
-
| 文件上传、下载、元数据、预签名 URL | `hai-storage` | 检查已有 storage feature
|
|
48
|
+
| 文件上传、下载、元数据、预签名 URL | `hai-storage` | 检查已有 storage feature;大文件用流式读写,checksum 按实际字节计算,ifNoneMatch 拒覆盖;资源授权、大小/类型/路径限制不可省略 |
|
|
47
49
|
| 加密、签名、哈希 | `hai-crypto` | 复用公开 API 与密钥配置,不自创加密或硬编码密钥 |
|
|
48
50
|
| LLM、流式、Embedding、RAG、Agent/MCP | `hai-ai` | 先检查内置能力与 provider 配置,再补应用编排 |
|
|
49
51
|
| 文本清洗、分块、管线 | `hai-datapipe` | 纯函数无生命周期;分块不代表已经生成向量 |
|
|
50
52
|
| 向量集合与检索 | `hai-vecdb` | 后端、维度与过滤条件使用实际契约 |
|
|
51
|
-
| 邮件/短信/通知触达 | `hai-reach` |
|
|
53
|
+
| 邮件/短信/通知触达 | `hai-reach` | 模板已绑定 provider 时只传 template/to/vars;直接发送才指定 provider;投递状态不等于 HTTP 成功 |
|
|
52
54
|
| 支付、订单、订阅、回调 | `hai-payment` | 验签、金额/订单绑定、幂等与状态迁移仍需业务验收 |
|
|
53
|
-
| 操作审计 | `hai-audit` |
|
|
55
|
+
| 操作审计 | `hai-audit` | 复用审计记录/查询与 PaginatedResult 元数据;应用决定脱敏、保留期及失败策略 |
|
|
54
56
|
| cron、持久任务、重试、手工触发 | `hai-scheduler` | 复用任务模型;明确取消、幂等、共享锁及可信代码边界 |
|
|
55
57
|
| 原生设备与安全 token 存储 | `hai-capacitor` | 仅在适用平台启用;桌面/小程序依当前项目适配 |
|
|
56
58
|
|
|
@@ -62,6 +64,8 @@ Fullstack 创建时在被 Git 与容器构建忽略的根 `.env` 写入独立随
|
|
|
62
64
|
|
|
63
65
|
## 落实契约与安全边界
|
|
64
66
|
|
|
67
|
+
沿现有服务层按顺序落实 schema → 当前授权与操作权限 → 从资源 ID 查所有者/范围 → 需要时核验对应操作凭据 → 存储/通知等副作用 → HaiResult 与审计。框架身份授权不能替代业务资源范围,数据库实体/存储 key 不交给客户端决定。文件失败、checksum 不匹配、不可覆盖冲突及通知失败/延迟分别向调用方反馈,并按业务一致性要求事务回滚或补偿;不能静默返回成功。
|
|
68
|
+
|
|
65
69
|
按本次真实输入、资源和副作用选取下面检查,并把适用项变成可观察验收。不能用“已遵循安全规范”或 lint 通过替代行为证据。
|
|
66
70
|
|
|
67
71
|
| 边界 | 实现要求 | 验收证据 |
|