@h-ai/cli 0.1.0-alpha.56 → 0.1.0-alpha.57

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 (69) hide show
  1. package/README.en.md +205 -0
  2. package/README.ja.md +205 -0
  3. package/README.md +16 -0
  4. package/dist/chunk-MJGUBOAV.js +391 -0
  5. package/dist/chunk-MJGUBOAV.js.map +1 -0
  6. package/dist/{cli-skill-templates-ZIWYMS4Y.js → cli-skill-templates-ZYXES3NT.js} +3 -3
  7. package/dist/{cli-skill-templates-ZIWYMS4Y.js.map → cli-skill-templates-ZYXES3NT.js.map} +1 -1
  8. package/dist/index.js +32 -69
  9. package/dist/index.js.map +1 -1
  10. package/package.json +7 -7
  11. package/templates/apps/api/.gitignore.hbs +13 -0
  12. package/templates/apps/api/.npmrc.hbs +2 -0
  13. package/templates/apps/api/e2e/api-service.spec.ts.hbs +1 -1
  14. package/templates/apps/api/{eslint.config.js → eslint.config.js.hbs} +6 -8
  15. package/templates/apps/api/package.json.hbs +1 -1
  16. package/templates/apps/fullstack/.gitignore.hbs +16 -0
  17. package/templates/apps/fullstack/.npmrc.hbs +2 -0
  18. package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +5 -8
  19. package/templates/apps/fullstack/apps/{{projectName}}-desktop/eslint.config.js.hbs +5 -8
  20. package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +5 -8
  21. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +1 -1
  22. package/templates/apps/fullstack/{eslint.config.js → eslint.config.js.hbs} +6 -8
  23. package/templates/apps/fullstack/package.json.hbs +1 -1
  24. package/templates/base/.npmrc.hbs +15 -0
  25. package/templates/base/eslint-rules.js +45 -0
  26. package/templates/base/eslint.config.js.hbs +6 -8
  27. package/templates/base/package.json.hbs +1 -1
  28. package/templates/skills/AGENTS.md +9 -8
  29. package/templates/skills/CLAUDE.md +4 -0
  30. package/templates/skills/bridges/admin/AGENTS.md +8 -0
  31. package/templates/skills/bridges/admin/CLAUDE.md +4 -0
  32. package/templates/skills/bridges/admin/copilot-instructions.md +2 -0
  33. package/templates/skills/bridges/api/AGENTS.md +8 -1
  34. package/templates/skills/bridges/api/CLAUDE.md +4 -0
  35. package/templates/skills/bridges/api/copilot-instructions.md +2 -0
  36. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +8 -2
  37. package/templates/skills/bridges/fullstack/CLAUDE.md.hbs +4 -0
  38. package/templates/skills/bridges/fullstack/copilot-instructions.md.hbs +2 -0
  39. package/templates/skills/bridges/h5/AGENTS.md +8 -0
  40. package/templates/skills/bridges/h5/CLAUDE.md +4 -0
  41. package/templates/skills/bridges/h5/copilot-instructions.md +2 -0
  42. package/templates/skills/bridges/mobile-app/AGENTS.md +8 -0
  43. package/templates/skills/bridges/mobile-app/CLAUDE.md +4 -0
  44. package/templates/skills/bridges/mobile-app/copilot-instructions.md +2 -0
  45. package/templates/skills/bridges/website/AGENTS.md +8 -0
  46. package/templates/skills/bridges/website/CLAUDE.md +4 -0
  47. package/templates/skills/bridges/website/copilot-instructions.md +2 -0
  48. package/templates/skills/claude-skill.md.hbs +7 -0
  49. package/templates/skills/copilot-instructions.md +2 -0
  50. package/templates/skills/hai-api-client/SKILL.md +2 -0
  51. package/templates/skills/hai-app-create/SKILL.md +9 -7
  52. package/templates/skills/hai-app-create/development.md +66 -0
  53. package/templates/skills/hai-app-create/reference.md +44 -32
  54. package/templates/skills/hai-app-review/SKILL.md +50 -144
  55. package/templates/skills/hai-app-tests/SKILL.md +3 -1
  56. package/templates/skills/hai-app-tests/reference.md +47 -27
  57. package/templates/skills/hai-build/SKILL.md +5 -4
  58. package/templates/skills/hai-build/reference.md +52 -32
  59. package/templates/skills/hai-ci/SKILL.md +8 -15
  60. package/templates/skills/hai-framework-sync/SKILL.md +8 -1
  61. package/templates/skills/hai-pr-review/SKILL.md +5 -4
  62. package/templates/skills/hai-reldb/SKILL.md +1 -0
  63. package/templates/skills/hai-reldb/reference.md +3 -0
  64. package/templates/skills/hai-scheduler/SKILL.md +3 -0
  65. package/templates/skills/hai-serv/SKILL.md +1 -0
  66. package/templates/skills/hai-serv/reference.md +16 -2
  67. package/templates/skills/hai-storage/SKILL.md +3 -1
  68. package/dist/chunk-WUYLYCW2.js +0 -237
  69. package/dist/chunk-WUYLYCW2.js.map +0 -1
@@ -19,6 +19,7 @@ description: "使用 @h-ai/serv 装配 contract/procedures、认证守卫、Open
19
19
 
20
20
  - 先确定 Node listen 还是 Fetch adapter,再从共享 contract 装配 procedures;不要直接暴露 Hono。
21
21
  - 认证绑定 route 的 auth/permission/role;启动配置错误与正常请求领域错误分别处理。
22
+ - 普通 procedure 的失败 HaiResult 默认通过 HTTP 200 返回,必须检查 success 与 error.code;error.httpStatus 是领域错误元数据。contract 输入校验失败为 HTTP 400,HTTP middleware、刷新 cookie 等响应另按自身契约处理,不能混为同一种状态映射。
22
23
  - 浏览器 Cookie、CORS、apiPrefix、transport 须与 api-client 同步;语音 ticket 单独核验。
23
24
 
24
25
  ## 按需参考
@@ -374,7 +374,7 @@ await apiClient.init({
374
374
  3. `iam.session.verifyToken`(推荐:传入 `iam` 后自动启用)
375
375
  4. 默认 `parseRequestContext`:仅解析请求元数据,`session` 为 `undefined`
376
376
 
377
- > ⚠️ `verifyToken` 会在每次请求调用(不缓存);verifyToken 失败或抛错统一收敛为 `session=undefined`,由 route 的 `.auth()` / `.permission()` / `.role()` 统一返回 401。
377
+ > ⚠️ `verifyToken` 会在每次请求调用(不缓存);verifyToken 失败或抛错统一收敛为 `session=undefined`,由 route 的 `.auth()` / `.permission()` / `.role()` 返回失败 HaiResult,`error.httpStatus=401`;普通 procedure 的 HTTP 响应默认仍为 200。
378
378
 
379
379
  ### 链式 procedure guard
380
380
 
@@ -403,7 +403,7 @@ const procedures = serv
403
403
  .build()
404
404
  ```
405
405
 
406
- - `.permission()` / `.role()` 隐含 `.auth()`;未登录返回 401,授权不足返回 403。
406
+ - `.permission()` / `.role()` 隐含 `.auth()`;未登录返回 `error.httpStatus=401` 的失败 HaiResult,授权不足为 403。普通 procedure 的 HTTP 响应默认仍为 200,必须检查 `success` 与 `error.code`。
407
407
  - 同一路由声明多个 permission/role 时必须全部满足。
408
408
  - handler 的未处理异常由 router 统一转换成 INTERNAL_ERROR HaiResult。
409
409
  - 重复/未知路径会被拒绝;遗漏 contract procedure 时 `.build()` 在类型上不可用,运行时也会校验。
@@ -522,6 +522,20 @@ HTTP 审计、限流、CORS、租户头校验等跨 route 能力继续放在
522
522
 
523
523
  ### 正常业务请求中的错误与 i18n
524
524
 
525
+ 先区分 HTTP 状态与 HaiResult 中的 `error.httpStatus`;普通 procedure 返回的领域错误元数据不会自动改写 HTTP 状态,当前默认装配如下(显式自定义响应状态或 middleware 按实际入口契约验证):
526
+
527
+ | 失败来源 | HTTP 响应 | 结果判定 |
528
+ | --- | --- | --- |
529
+ | procedure `.auth()` / `.permission()` / `.role()` | 200 | `success=false`;`error.httpStatus` 为 401 / 403 |
530
+ | handler 返回 `err(...)` 或下游模块失败 HaiResult | 200 | 检查 `success`、`error.code`,例如数据库查询错误的 `error.httpStatus=500` |
531
+ | handler 未处理异常被 route 实现器捕获 | 200 | INTERNAL_ERROR 失败 HaiResult,`error.httpStatus=500` |
532
+ | oRPC contract 输入 schema 校验失败(handler 之前) | 400 | 本地化失败体,`error.httpStatus=400` 与字段错误 |
533
+ | handler 内 `serv.validateInputOrFail(...)` 失败 | 200 | 失败 HaiResult,`error.httpStatus=400` 与字段错误 |
534
+
535
+ HTTP middleware、自定义 Response、refresh-cookie、OpenAPI/RPC 入口权限和 transport 协议错误分别按自身响应契约处理,可返回真实 HTTP 4xx/5xx。`@h-ai/api-client` 的自动刷新只由真实 HTTP 401 触发,不会因 HTTP 200 失败包络中的 `error.httpStatus=401` 自动刷新。
536
+
537
+ SvelteKit 的 `kit.response.fromError` 根据 `error.httpStatus` 生成 HTTP Response;`kit.response.fromResult` 使用显式错误码映射,未命中默认 400。这些响应转换规则不能套用到 serv 普通 procedure。
538
+
525
539
  按三层处理:
526
540
 
527
541
  1. **oRPC contract 输入校验**:进入 handler 前自动发生;`serv.createApp()` 会把默认英文 Zod 错误改写为本地化 `errors[]`。
@@ -25,7 +25,7 @@ description: "使用 @h-ai/storage 访问本地/S3 文件、目录和预签名
25
25
 
26
26
  - 文件上传与下载
27
27
  - 预签名 URL 生成(客户端直传)
28
- - S3 兼容存储对接(AWS S3、MinIO、阿里云 OSS 等)
28
+ - S3 兼容存储对接(AWS S3、MinIO、RustFS、阿里云 OSS 等)
29
29
  - 本地开发文件存储
30
30
  - 目录前缀清理与批量文件删除
31
31
 
@@ -57,6 +57,8 @@ operationLog:
57
57
  约定环境变量优先于 YAML,例如 `HAI_STORAGE_ROOT`、
58
58
  `HAI_STORAGE_ACCESSKEYID`、`HAI_STORAGE_SECRETACCESSKEY`。
59
59
 
60
+ MinIO、RustFS 使用同一个 S3 Provider:配置 `type: s3`、S3 API `endpoint`(通常为 9000 端口)和 `forcePathStyle: true`,并预先创建 `bucket`;不要新增 `type: minio` 或 `type: rustfs`。
61
+
60
62
  `operationLog` 在 Local/S3 Provider 的真实 file/dir/presign 操作处输出日志,不要在 `storage-main.ts` 或调用方重复包装。文件内容不会写入日志,上传数据只记录字节长度;`maxLength` 用于截断序列化后的参数,`level` 默认 `debug`。
61
63
 
62
64
  ### 2. 初始化与关闭
@@ -1,237 +0,0 @@
1
- #!/usr/bin/env node
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
4
- import fse from 'fs-extra';
5
- import Handlebars from 'handlebars';
6
-
7
- var MODULE_SKILL_MAP = {
8
- "db": "hai-reldb",
9
- "reldb": "hai-reldb",
10
- "cache": "hai-cache",
11
- "iam": "hai-iam",
12
- "storage": "hai-storage",
13
- "ai": "hai-ai",
14
- "crypto": "hai-crypto",
15
- "payment": "hai-payment",
16
- "vecdb": "hai-vecdb",
17
- "datapipe": "hai-datapipe",
18
- "reach": "hai-reach",
19
- "scheduler": "hai-scheduler",
20
- "audit": "hai-audit",
21
- "deploy": "hai-deploy",
22
- "kit": "hai-kit",
23
- "ui": "hai-ui",
24
- "api-client": "hai-api-client",
25
- "capacitor": "hai-capacitor"
26
- };
27
- var SKILL_EXCLUSIONS_BY_APP_TYPE = {
28
- "admin": ["hai-serv", "hai-api-contract", "hai-api-client", "hai-capacitor"],
29
- "website": ["hai-serv", "hai-api-contract", "hai-api-client", "hai-capacitor"],
30
- "h5": ["hai-serv", "hai-api-contract", "hai-api-client", "hai-capacitor"],
31
- "api": ["hai-ui", "hai-kit", "hai-capacitor"],
32
- "mobile-app": ["hai-core", "hai-kit", "hai-serv", "hai-api-contract"],
33
- "fullstack": ["hai-kit"]
34
- };
35
- var WORKFLOW_SKILLS = [
36
- "hai-build",
37
- "hai-app-create",
38
- "hai-app-review",
39
- "hai-app-tests"
40
- ];
41
- var PACKAGE_SKILL_MAP = {
42
- "@h-ai/api-contract": "hai-api-contract",
43
- "@h-ai/api-client": "hai-api-client",
44
- "@h-ai/core": "hai-core",
45
- "@h-ai/serv": "hai-serv",
46
- "@h-ai/kit": "hai-kit",
47
- "@h-ai/ui": "hai-ui",
48
- "@h-ai/reldb": "hai-reldb",
49
- "@h-ai/cache": "hai-cache",
50
- "@h-ai/iam": "hai-iam",
51
- "@h-ai/storage": "hai-storage",
52
- "@h-ai/ai": "hai-ai",
53
- "@h-ai/crypto": "hai-crypto",
54
- "@h-ai/payment": "hai-payment",
55
- "@h-ai/vecdb": "hai-vecdb",
56
- "@h-ai/datapipe": "hai-datapipe",
57
- "@h-ai/reach": "hai-reach",
58
- "@h-ai/scheduler": "hai-scheduler",
59
- "@h-ai/audit": "hai-audit",
60
- "@h-ai/deploy": "hai-deploy",
61
- "@h-ai/capacitor": "hai-capacitor"
62
- };
63
- var SKILL_TARGET_DIRS = [".agents/skills"];
64
- var BRIDGE_FILES = [
65
- {
66
- source: "copilot-instructions.md",
67
- destination: ".github/copilot-instructions.md"
68
- },
69
- {
70
- source: "CLAUDE.md",
71
- destination: "CLAUDE.md"
72
- },
73
- {
74
- source: "AGENTS.md",
75
- destination: "AGENTS.md"
76
- },
77
- {
78
- source: "opencode.json",
79
- destination: "opencode.json"
80
- }
81
- ];
82
- var BRIDGE_PROFILE_REGEX = /^[a-z0-9-]+$/;
83
- async function listTemplateSkillNames(templatesDir) {
84
- const entries = await fse.readdir(templatesDir, { withFileTypes: true });
85
- const skillNames = [];
86
- for (const entry of entries) {
87
- if (!entry.isDirectory() || !entry.name.startsWith("hai-")) {
88
- continue;
89
- }
90
- const skillFilePath = path.join(templatesDir, entry.name, "SKILL.md");
91
- if (await fse.pathExists(skillFilePath)) {
92
- skillNames.push(entry.name);
93
- }
94
- }
95
- return skillNames.sort((a, b) => a.localeCompare(b));
96
- }
97
- function resolveCompatibleSkillNames(skillNames, appType) {
98
- const excludedSkills = new Set(appType ? SKILL_EXCLUSIONS_BY_APP_TYPE[appType] ?? [] : []);
99
- return skillNames.filter((skillName) => !excludedSkills.has(skillName));
100
- }
101
- function getSkillTemplatesDir() {
102
- const currentDir = path.dirname(fileURLToPath(import.meta.url));
103
- const distPath = path.resolve(currentDir, "..", "templates", "skills");
104
- if (fse.pathExistsSync(distPath)) {
105
- return distPath;
106
- }
107
- return path.resolve(currentDir, "..", "..", "templates", "skills");
108
- }
109
- async function copySkill(templatesDir, skillName, projectPath, targetDirs = SKILL_TARGET_DIRS, overwrite = true) {
110
- const src = path.join(templatesDir, skillName);
111
- if (!await fse.pathExists(src)) {
112
- return [];
113
- }
114
- const copiedFiles = [];
115
- for (const targetDir of targetDirs) {
116
- const dest = path.join(projectPath, targetDir, skillName);
117
- const skillFilePath = path.join(dest, "SKILL.md");
118
- if (!overwrite && await fse.pathExists(skillFilePath)) {
119
- continue;
120
- }
121
- await fse.copy(src, dest, { overwrite: true });
122
- copiedFiles.push(`${targetDir}/${skillName}/SKILL.md`);
123
- }
124
- return copiedFiles;
125
- }
126
- async function copySkills(templatesDir, skillNames, projectPath, targetDirs = SKILL_TARGET_DIRS, overwrite = true) {
127
- const copiedFiles = [];
128
- for (const skillName of new Set(skillNames)) {
129
- copiedFiles.push(...await copySkill(templatesDir, skillName, projectPath, targetDirs, overwrite));
130
- }
131
- return copiedFiles;
132
- }
133
- async function copyBridgeFiles(templatesDir, projectPath, overwrite = true, appType, projectName) {
134
- const copiedFiles = [];
135
- for (const file of BRIDGE_FILES) {
136
- const src = await resolveBridgeSource(templatesDir, file.source, appType, projectName !== void 0);
137
- const dest = path.join(projectPath, file.destination);
138
- if (!src) {
139
- continue;
140
- }
141
- if (!overwrite && await fse.pathExists(dest)) {
142
- continue;
143
- }
144
- await fse.ensureDir(path.dirname(dest));
145
- if (src.endsWith(".hbs")) {
146
- const template = await fse.readFile(src, "utf8");
147
- const rendered = Handlebars.compile(template, { noEscape: true })({ projectName });
148
- await fse.writeFile(dest, rendered, "utf8");
149
- } else {
150
- await fse.copy(src, dest, { overwrite });
151
- }
152
- copiedFiles.push(file.destination);
153
- }
154
- return copiedFiles;
155
- }
156
- async function resolveBridgeSource(templatesDir, source, appType, allowTemplate = false) {
157
- const profile = appType && BRIDGE_PROFILE_REGEX.test(appType) ? appType : "generic";
158
- const candidates = [
159
- ...allowTemplate ? [path.join(templatesDir, "bridges", profile, `${source}.hbs`)] : [],
160
- path.join(templatesDir, "bridges", profile, source),
161
- path.join(templatesDir, "bridges", "generic", source),
162
- path.join(templatesDir, source)
163
- ];
164
- for (const candidate of candidates) {
165
- if (await fse.pathExists(candidate)) {
166
- return candidate;
167
- }
168
- }
169
- return null;
170
- }
171
- async function generateSkillFiles(projectPath, _features, appType, projectName) {
172
- const templatesDir = getSkillTemplatesDir();
173
- if (!await fse.pathExists(templatesDir)) {
174
- return [];
175
- }
176
- const copiedFiles = [];
177
- const templateSkillNames = await listTemplateSkillNames(templatesDir);
178
- const compatibleSkillNames = resolveCompatibleSkillNames(templateSkillNames, appType);
179
- copiedFiles.push(...await copySkills(templatesDir, compatibleSkillNames, projectPath));
180
- copiedFiles.push(...await copyBridgeFiles(templatesDir, projectPath, true, appType, projectName));
181
- return copiedFiles;
182
- }
183
- async function generateModuleSkillFile(projectPath, moduleId) {
184
- const skillName = MODULE_SKILL_MAP[moduleId];
185
- if (!skillName) {
186
- return null;
187
- }
188
- const templatesDir = getSkillTemplatesDir();
189
- if (!await fse.pathExists(templatesDir)) {
190
- return null;
191
- }
192
- const missingTargetDirs = [];
193
- for (const targetDir of SKILL_TARGET_DIRS) {
194
- const skillFilePath = path.join(projectPath, targetDir, skillName, "SKILL.md");
195
- if (!await fse.pathExists(skillFilePath)) {
196
- missingTargetDirs.push(targetDir);
197
- }
198
- }
199
- const copiedBridgeFiles = await copyBridgeFiles(templatesDir, projectPath, false);
200
- if (missingTargetDirs.length === 0) {
201
- return copiedBridgeFiles.length > 0 ? copiedBridgeFiles : null;
202
- }
203
- const copiedSkillFiles = await copySkill(templatesDir, skillName, projectPath, missingTargetDirs);
204
- const copiedFiles = [...copiedBridgeFiles, ...copiedSkillFiles];
205
- if (copiedFiles.length === 0) {
206
- return null;
207
- }
208
- return copiedFiles;
209
- }
210
- function resolveSkillNamesForPackages(packageNames) {
211
- const skillNames = [...WORKFLOW_SKILLS];
212
- for (const packageName of packageNames) {
213
- const skillName = PACKAGE_SKILL_MAP[packageName];
214
- if (skillName) {
215
- skillNames.push(skillName);
216
- }
217
- }
218
- return [...new Set(skillNames)];
219
- }
220
- async function generateProjectAiSupport(projectPath, skillNames, options = {}) {
221
- const templatesDir = getSkillTemplatesDir();
222
- if (!await fse.pathExists(templatesDir)) {
223
- return [];
224
- }
225
- const {
226
- overwriteSkills = true,
227
- overwriteBridgeFiles = true
228
- } = options;
229
- const copiedFiles = [];
230
- copiedFiles.push(...await copySkills(templatesDir, skillNames, projectPath, SKILL_TARGET_DIRS, overwriteSkills));
231
- copiedFiles.push(...await copyBridgeFiles(templatesDir, projectPath, overwriteBridgeFiles, options.appType));
232
- return copiedFiles;
233
- }
234
-
235
- export { generateModuleSkillFile, generateProjectAiSupport, generateSkillFiles, resolveCompatibleSkillNames, resolveSkillNamesForPackages };
236
- //# sourceMappingURL=chunk-WUYLYCW2.js.map
237
- //# sourceMappingURL=chunk-WUYLYCW2.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/cli-skill-templates.ts"],"names":[],"mappings":"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","file":"chunk-WUYLYCW2.js","sourcesContent":["/**\n * @h-ai/cli — Skill 模板生成\n *\n * 将 Skill 模板文件复制到用户项目中,供 AI 编程助手使用。\n * @module cli-skill-templates\n */\n\nimport type { AppType } from '../cli-types.js'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport fse from 'fs-extra'\nimport Handlebars from 'handlebars'\n\n/**\n * 模块名到 Skill 目录名的映射\n */\nconst MODULE_SKILL_MAP: Record<string, string> = {\n 'db': 'hai-reldb',\n 'reldb': 'hai-reldb',\n 'cache': 'hai-cache',\n 'iam': 'hai-iam',\n 'storage': 'hai-storage',\n 'ai': 'hai-ai',\n 'crypto': 'hai-crypto',\n 'payment': 'hai-payment',\n 'vecdb': 'hai-vecdb',\n 'datapipe': 'hai-datapipe',\n 'reach': 'hai-reach',\n 'scheduler': 'hai-scheduler',\n 'audit': 'hai-audit',\n 'deploy': 'hai-deploy',\n 'kit': 'hai-kit',\n 'ui': 'hai-ui',\n 'api-client': 'hai-api-client',\n 'capacitor': 'hai-capacitor',\n}\n\n/**\n * 各 appType 互斥的 Skill。\n *\n * 生成样板工程时,默认复制 templates/skills 下所有 hai-* Skill,\n * 仅排除与当前样板技术栈明显冲突的少数 Skill。\n */\nconst SKILL_EXCLUSIONS_BY_APP_TYPE: Partial<Record<AppType, string[]>> = {\n 'admin': ['hai-serv', 'hai-api-contract', 'hai-api-client', 'hai-capacitor'],\n 'website': ['hai-serv', 'hai-api-contract', 'hai-api-client', 'hai-capacitor'],\n 'h5': ['hai-serv', 'hai-api-contract', 'hai-api-client', 'hai-capacitor'],\n 'api': ['hai-ui', 'hai-kit', 'hai-capacitor'],\n 'mobile-app': ['hai-core', 'hai-kit', 'hai-serv', 'hai-api-contract'],\n 'fullstack': ['hai-kit'],\n}\n\nconst WORKFLOW_SKILLS = [\n 'hai-build',\n 'hai-app-create',\n 'hai-app-review',\n 'hai-app-tests',\n]\n\nconst PACKAGE_SKILL_MAP: Record<string, string> = {\n '@h-ai/api-contract': 'hai-api-contract',\n '@h-ai/api-client': 'hai-api-client',\n '@h-ai/core': 'hai-core',\n '@h-ai/serv': 'hai-serv',\n '@h-ai/kit': 'hai-kit',\n '@h-ai/ui': 'hai-ui',\n '@h-ai/reldb': 'hai-reldb',\n '@h-ai/cache': 'hai-cache',\n '@h-ai/iam': 'hai-iam',\n '@h-ai/storage': 'hai-storage',\n '@h-ai/ai': 'hai-ai',\n '@h-ai/crypto': 'hai-crypto',\n '@h-ai/payment': 'hai-payment',\n '@h-ai/vecdb': 'hai-vecdb',\n '@h-ai/datapipe': 'hai-datapipe',\n '@h-ai/reach': 'hai-reach',\n '@h-ai/scheduler': 'hai-scheduler',\n '@h-ai/audit': 'hai-audit',\n '@h-ai/deploy': 'hai-deploy',\n '@h-ai/capacitor': 'hai-capacitor',\n}\n\nconst SKILL_TARGET_DIRS = ['.agents/skills'] as const\n\nconst BRIDGE_FILES = [\n {\n source: 'copilot-instructions.md',\n destination: '.github/copilot-instructions.md',\n },\n {\n source: 'CLAUDE.md',\n destination: 'CLAUDE.md',\n },\n {\n source: 'AGENTS.md',\n destination: 'AGENTS.md',\n },\n {\n source: 'opencode.json',\n destination: 'opencode.json',\n },\n] as const\n\nconst BRIDGE_PROFILE_REGEX = /^[a-z0-9-]+$/\n\nasync function listTemplateSkillNames(templatesDir: string): Promise<string[]> {\n const entries = await fse.readdir(templatesDir, { withFileTypes: true })\n const skillNames: string[] = []\n\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.startsWith('hai-')) {\n continue\n }\n\n const skillFilePath = path.join(templatesDir, entry.name, 'SKILL.md')\n if (await fse.pathExists(skillFilePath)) {\n skillNames.push(entry.name)\n }\n }\n\n return skillNames.sort((a, b) => a.localeCompare(b))\n}\n\nexport function resolveCompatibleSkillNames(\n skillNames: string[],\n appType?: AppType,\n): string[] {\n const excludedSkills = new Set(appType ? SKILL_EXCLUSIONS_BY_APP_TYPE[appType] ?? [] : [])\n return skillNames.filter(skillName => !excludedSkills.has(skillName))\n}\n\n/**\n * 获取 templates/skills/ 目录的绝对路径\n *\n * 兼容两种运行环境:\n * - 构建后:dist/cli-skill-templates.js → ../templates/skills\n * - 测试时:src/commands/cli-skill-templates.ts → ../../templates/skills\n */\nfunction getSkillTemplatesDir(): string {\n const currentDir = path.dirname(fileURLToPath(import.meta.url))\n const distPath = path.resolve(currentDir, '..', 'templates', 'skills')\n if (fse.pathExistsSync(distPath)) {\n return distPath\n }\n return path.resolve(currentDir, '..', '..', 'templates', 'skills')\n}\n\n/**\n * 复制单个 Skill 目录到项目\n *\n * @param templatesDir - Skill 模板根目录\n * @param skillName - Skill 目录名(如 'hai-reldb')\n * @param projectPath - 用户项目根目录\n */\nasync function copySkill(\n templatesDir: string,\n skillName: string,\n projectPath: string,\n targetDirs: readonly string[] = SKILL_TARGET_DIRS,\n overwrite = true,\n): Promise<string[]> {\n const src = path.join(templatesDir, skillName)\n\n if (!(await fse.pathExists(src))) {\n return []\n }\n\n const copiedFiles: string[] = []\n\n for (const targetDir of targetDirs) {\n const dest = path.join(projectPath, targetDir, skillName)\n const skillFilePath = path.join(dest, 'SKILL.md')\n\n if (!overwrite && await fse.pathExists(skillFilePath)) {\n continue\n }\n\n await fse.copy(src, dest, { overwrite: true })\n copiedFiles.push(`${targetDir}/${skillName}/SKILL.md`)\n }\n\n return copiedFiles\n}\n\nasync function copySkills(\n templatesDir: string,\n skillNames: string[],\n projectPath: string,\n targetDirs: readonly string[] = SKILL_TARGET_DIRS,\n overwrite = true,\n): Promise<string[]> {\n const copiedFiles: string[] = []\n\n for (const skillName of new Set(skillNames)) {\n copiedFiles.push(...await copySkill(templatesDir, skillName, projectPath, targetDirs, overwrite))\n }\n\n return copiedFiles\n}\n\n/**\n * 复制桥接文件到项目\n *\n * @param templatesDir - Skill 模板根目录\n * @param projectPath - 用户项目根目录\n */\nasync function copyBridgeFiles(\n templatesDir: string,\n projectPath: string,\n overwrite = true,\n appType?: string,\n projectName?: string,\n): Promise<string[]> {\n const copiedFiles: string[] = []\n\n for (const file of BRIDGE_FILES) {\n const src = await resolveBridgeSource(templatesDir, file.source, appType, projectName !== undefined)\n const dest = path.join(projectPath, file.destination)\n\n if (!src) {\n continue\n }\n\n if (!overwrite && await fse.pathExists(dest)) {\n continue\n }\n\n await fse.ensureDir(path.dirname(dest))\n if (src.endsWith('.hbs')) {\n const template = await fse.readFile(src, 'utf8')\n const rendered = Handlebars.compile(template, { noEscape: true })({ projectName })\n await fse.writeFile(dest, rendered, 'utf8')\n }\n else {\n await fse.copy(src, dest, { overwrite })\n }\n copiedFiles.push(file.destination)\n }\n\n return copiedFiles\n}\n\nasync function resolveBridgeSource(\n templatesDir: string,\n source: string,\n appType?: string,\n allowTemplate = false,\n): Promise<string | null> {\n const profile = appType && BRIDGE_PROFILE_REGEX.test(appType) ? appType : 'generic'\n const candidates = [\n ...(allowTemplate ? [path.join(templatesDir, 'bridges', profile, `${source}.hbs`)] : []),\n path.join(templatesDir, 'bridges', profile, source),\n path.join(templatesDir, 'bridges', 'generic', source),\n path.join(templatesDir, source),\n ]\n\n for (const candidate of candidates) {\n if (await fse.pathExists(candidate)) {\n return candidate\n }\n }\n\n return null\n}\n\n/**\n * 为新项目生成完整的 Skill 文件。\n *\n * 默认复制 templates/skills 下所有 hai-* Skill,再按 appType 排除互斥项。\n *\n * @param projectPath - 用户项目根目录\n * @param _features - 预留参数;保留签名兼容 createProject 调用方\n * @param appType - 应用类型(api 类型不复制 UI Skill)\n */\nexport async function generateSkillFiles(\n projectPath: string,\n _features: string[],\n appType?: string,\n projectName?: string,\n): Promise<string[]> {\n const templatesDir = getSkillTemplatesDir()\n\n if (!(await fse.pathExists(templatesDir))) {\n return []\n }\n\n const copiedFiles: string[] = []\n\n const templateSkillNames = await listTemplateSkillNames(templatesDir)\n const compatibleSkillNames = resolveCompatibleSkillNames(templateSkillNames, appType as AppType | undefined)\n\n copiedFiles.push(...await copySkills(templatesDir, compatibleSkillNames, projectPath))\n\n // 复制桥接文件\n copiedFiles.push(...await copyBridgeFiles(templatesDir, projectPath, true, appType, projectName))\n\n return copiedFiles\n}\n\n/**\n * 为已有项目添加单个模块的 Skill 文件\n *\n * 在 `hai add <module>` 时调用,仅复制对应模块的 Skill 文件。\n *\n * @param projectPath - 用户项目根目录\n * @param moduleId - 模块标识(如 'db'、'iam')\n * @returns 复制的文件路径,若模块无对应 Skill 则返回 null\n */\nexport async function generateModuleSkillFile(\n projectPath: string,\n moduleId: string,\n): Promise<string[] | null> {\n const skillName = MODULE_SKILL_MAP[moduleId]\n if (!skillName) {\n return null\n }\n\n const templatesDir = getSkillTemplatesDir()\n if (!(await fse.pathExists(templatesDir))) {\n return null\n }\n\n const missingTargetDirs: string[] = []\n\n for (const targetDir of SKILL_TARGET_DIRS) {\n const skillFilePath = path.join(projectPath, targetDir, skillName, 'SKILL.md')\n if (!(await fse.pathExists(skillFilePath))) {\n missingTargetDirs.push(targetDir)\n }\n }\n\n const copiedBridgeFiles = await copyBridgeFiles(templatesDir, projectPath, false)\n if (missingTargetDirs.length === 0) {\n return copiedBridgeFiles.length > 0 ? copiedBridgeFiles : null\n }\n\n const copiedSkillFiles = await copySkill(templatesDir, skillName, projectPath, missingTargetDirs)\n const copiedFiles = [...copiedBridgeFiles, ...copiedSkillFiles]\n\n if (copiedFiles.length === 0) {\n return null\n }\n\n return copiedFiles\n}\n\nexport function resolveSkillNamesForPackages(packageNames: string[]): string[] {\n const skillNames = [...WORKFLOW_SKILLS]\n\n for (const packageName of packageNames) {\n const skillName = PACKAGE_SKILL_MAP[packageName]\n if (skillName) {\n skillNames.push(skillName)\n }\n }\n\n return [...new Set(skillNames)]\n}\n\nexport async function generateProjectAiSupport(\n projectPath: string,\n skillNames: string[],\n options: {\n overwriteSkills?: boolean\n overwriteBridgeFiles?: boolean\n appType?: string\n } = {},\n): Promise<string[]> {\n const templatesDir = getSkillTemplatesDir()\n\n if (!(await fse.pathExists(templatesDir))) {\n return []\n }\n\n const {\n overwriteSkills = true,\n overwriteBridgeFiles = true,\n } = options\n\n const copiedFiles: string[] = []\n copiedFiles.push(...await copySkills(templatesDir, skillNames, projectPath, SKILL_TARGET_DIRS, overwriteSkills))\n copiedFiles.push(...await copyBridgeFiles(templatesDir, projectPath, overwriteBridgeFiles, options.appType))\n\n return copiedFiles\n}\n"]}