@ultime/codecraft 1.2.0 → 1.2.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.1 - 2026-10-27
4
+
5
+ - 修复 pack 自动判定:识别 Nuxt 2 的 `pages/`、Nuxt 3 的 `app/`、uni-app 的 `manifest.json + pages.json`、Taro / uni-app 依赖,以及 workspace 内的嵌套应用。
6
+ - 新增 `trae-cn` 安装目标(`~/.trae-cn/skills`);`--agent` 顺序改为 generic / claude / cursor / trae / trae-cn,不再默认优先 Trae。
7
+ - 扫描 0 个源文件时输出警告并以 2 退出,避免把空扫描当成通过。
8
+ - README 补充 agent 列表、`--dest`、`Files: 0` 与 Trae CN 的说明。
9
+
3
10
  ## 1.2.0 - 2026-10-27
4
11
 
5
12
  - 新增 `ignore` 例外配置(A12):`rule` / `file` / `reason` 必填,`expires` 可选;支持 `*` / `**` / `?` 路径匹配;安全规则 `A21` 不可豁免。
package/README.md CHANGED
@@ -58,15 +58,19 @@ npx skills-npm setup
58
58
  npx @ultime/codecraft install
59
59
  ```
60
60
 
61
- 自动检测当前项目的 agent,把 `SKILL.md`、`docs/`、`scenarios/`、`prompts/` 复制到 `.agents/skills/codecraft` 和对应 agent 目录(`.claude/skills`、`.trae/skills`、`.cursor/skills`)。
61
+ 自动检测当前项目的 agent,把 `SKILL.md`、`docs/`、`scenarios/`、`prompts/` 复制到 `.agents/skills/codecraft` 和对应 agent 目录(`.claude/skills`、`.cursor/skills`、`.trae/skills`、`.trae-cn/skills`)。不传 `--agent` 时只按检测结果安装,不优先任何一家。
62
62
 
63
63
  ```bash
64
- npx @ultime/codecraft install --agent claude # 指定 agent,可逗号分隔
65
- npx @ultime/codecraft install --global --agent claude # 装到用户目录
66
- npx @ultime/codecraft install --dry-run # 只看计划
67
- npx @ultime/codecraft install --force # 覆盖已有目录
64
+ npx @ultime/codecraft install --agent generic # 指定 agent,可逗号分隔
65
+ npx @ultime/codecraft install --global --agent claude # 装到用户目录
66
+ npx @ultime/codecraft install --global --agent trae-cn # Trae CN 的全局目录
67
+ npx @ultime/codecraft install --global --dest .trae-cn/skills # 自定义目录
68
+ npx @ultime/codecraft install --dry-run # 只看计划
69
+ npx @ultime/codecraft install --force # 覆盖已有目录
68
70
  ```
69
71
 
72
+ 可选 agent:`generic` / `claude` / `cursor` / `trae` / `trae-cn`;未知目录用 `--dest`。
73
+
70
74
  ### 方式 C:手动指针
71
75
 
72
76
  不复制文件,只在项目规则里留一行:
@@ -264,9 +268,11 @@ ROADMAP.md 后续版本计划与交接说明
264
268
  ## FAQ
265
269
 
266
270
  - **静态检查误报怎么办?** 在 `codecraft.config.json` 的 `ignore` 里登记 `rule` + `file` + `reason`,必要时加 `expires`;不要直接删规则。
271
+ - **`Files: 0` 是什么意思?** 没有扫到源文件,命令会输出警告并以 2 退出;用 `--pack app|library` 指定正确类型,或检查 `--root`。
272
+ - **Trae CN 装到哪?** 用 `--agent trae-cn`(`~/.trae-cn/skills`);未知目录用 `--dest`。
267
273
  - **`check` / `audit` / `plan` 有什么区别?** `check` 只跑内置规则;`audit` 额外跑 config 里的 gates;`plan` 只读,把 findings 转成计划,默认不跑 gates。
268
274
  - **为什么不自动跑 typecheck / test?** 门禁由项目提供,Codecraft 只编排,避免第二套实现。
269
- - **monorepo 为什么被判成 library?** 有 `packages/` 或根 `package.json` 即按 library;可用 `--pack` 显式覆盖。
275
+ - **monorepo 为什么被判成 app / library?** 有 `pages/`、`app/`、`manifest.json + pages.json`、Taro / uni-app 依赖或嵌套应用时按 `app`;有 `packages/` 或 `src/` 时按 `library`;仍不确定就显式传 `--pack`。
270
276
  - **支持哪些文件?** 只扫 JS/TS 相关扩展名与 `package.json`;其它语言不覆盖。
271
277
 
272
278
  ## 边界
package/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.0
1
+ 1.2.1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultime/codecraft",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "面向 JS/TS 的代码规范与架构设计(前端默认,Node 后端可复用结构规则),作为可安装的 skill 包。",
5
5
  "keywords": [
6
6
  "ai",
package/src/cli.mjs CHANGED
@@ -24,9 +24,9 @@ Options:
24
24
  --root <path> Project root (default: cwd)
25
25
  --pack app|library|design-system|auto Rule pack (default: auto)
26
26
  --config <path> Config file (default: codecraft.config.json)
27
- --agent auto|claude|trae|cursor|generic Install target, comma-separated (default: auto)
27
+ --agent auto|generic|claude|cursor|trae|trae-cn Install target, comma-separated (default: auto)
28
28
  --global Install into the home directory
29
- --dest <path> Explicit destination (relative to root / home)
29
+ --dest <path> Explicit destination (relative to root / home), e.g. .trae-cn/skills
30
30
  --force Replace an existing codecraft skill directory
31
31
  --dry-run Show the install plan without writing
32
32
  --no-gates Skip gates in audit / plan
@@ -154,6 +154,11 @@ export function main(argv) {
154
154
  const findings = result.findings.map((finding) => ({ ...finding, severity }))
155
155
  const filtered = applyIgnores(findings, config.config.ignore ?? [])
156
156
  const activeFindings = filtered.findings
157
+ const warnings = []
158
+ if (result.files === 0 && result.findings.length === 0) {
159
+ warnings.push(`0 source files scanned under ${root}; pass --pack app|library or check --root`)
160
+ console.error(`codecraft: warning: ${warnings[0]}`)
161
+ }
157
162
  const gates = shouldRunGates(command, args)
158
163
  ? runGates(root, config.config.gates ?? [], { failFast: args.failFast })
159
164
  : []
@@ -170,6 +175,7 @@ export function main(argv) {
170
175
  findings: activeFindings.length,
171
176
  ignored: filtered.ignored.length,
172
177
  expired: filtered.expired.length,
178
+ warnings,
173
179
  plan: plan.length,
174
180
  gates: gates.length,
175
181
  }
@@ -207,6 +213,7 @@ export function main(argv) {
207
213
  const failed =
208
214
  activeFindings.some((finding) => finding.severity === 'error') ||
209
215
  gates.some((gate) => gate.exitCode !== 0)
216
+ if (warnings.length) return 2
210
217
  return failed ? 1 : 0
211
218
  }
212
219
 
@@ -281,13 +288,83 @@ function parseArgs(argv) {
281
288
  return args
282
289
  }
283
290
 
284
- function detectPack(root) {
285
- if (isDir(path.join(root, 'apps')) || isDir(path.join(root, 'src', 'pages'))) return 'app'
291
+ export function detectPack(root) {
292
+ if (isDir(path.join(root, 'apps'))) return 'app'
293
+ if (hasAppMarkers(root)) return 'app'
286
294
  if (isDir(path.join(root, 'packages'))) return 'library'
295
+ if (hasNestedApp(root)) return 'app'
296
+ if (isDir(path.join(root, 'src'))) return 'library'
287
297
  if (fs.existsSync(path.join(root, 'package.json'))) return 'library'
288
298
  return 'app'
289
299
  }
290
300
 
301
+ const APP_DIR_MARKERS = ['src/pages', 'pages', 'app', 'src/app']
302
+ const APP_FILE_MARKERS = [
303
+ 'nuxt.config.js',
304
+ 'nuxt.config.ts',
305
+ 'nuxt.config.mjs',
306
+ 'app.config.js',
307
+ 'app.config.ts',
308
+ 'src/app.config.js',
309
+ 'src/app.config.ts',
310
+ 'taro.config.js',
311
+ 'taro.config.ts',
312
+ ]
313
+ const APP_DEPS = ['nuxt', '@tarojs/taro', '@dcloudio/uni-app', 'uni-app', 'next']
314
+
315
+ function hasAppMarkers(root) {
316
+ if (APP_DIR_MARKERS.some((dir) => isDir(path.join(root, dir)))) return true
317
+ if (APP_FILE_MARKERS.some((file) => isFile(path.join(root, file)))) return true
318
+ if (hasUniAppFiles(root)) return true
319
+ return hasAppDependency(root)
320
+ }
321
+
322
+ function hasNestedApp(root) {
323
+ let entries
324
+ try {
325
+ entries = fs.readdirSync(root, { withFileTypes: true })
326
+ } catch {
327
+ return false
328
+ }
329
+ for (const entry of entries) {
330
+ if (!entry.isDirectory()) continue
331
+ if (['node_modules', 'dist', 'build', 'target', '.git', '.nuxt', '.output', 'coverage'].includes(entry.name)) {
332
+ continue
333
+ }
334
+ const child = path.join(root, entry.name)
335
+ if (APP_DIR_MARKERS.some((dir) => isDir(path.join(child, dir)))) return true
336
+ if (APP_FILE_MARKERS.some((file) => isFile(path.join(child, file)))) return true
337
+ if (hasUniAppFiles(child)) return true
338
+ if (hasAppDependency(child)) return true
339
+ }
340
+ return false
341
+ }
342
+
343
+ function hasUniAppFiles(root) {
344
+ const atRoot = isFile(path.join(root, 'manifest.json')) && isFile(path.join(root, 'pages.json'))
345
+ const atSrc = isFile(path.join(root, 'src', 'manifest.json')) && isFile(path.join(root, 'src', 'pages.json'))
346
+ return atRoot || atSrc
347
+ }
348
+
349
+ function hasAppDependency(root) {
350
+ let manifest
351
+ try {
352
+ manifest = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))
353
+ } catch {
354
+ return false
355
+ }
356
+ const deps = { ...(manifest.dependencies ?? {}), ...(manifest.devDependencies ?? {}) }
357
+ return APP_DEPS.some((dep) => dep in deps)
358
+ }
359
+
360
+ function isFile(target) {
361
+ try {
362
+ return fs.statSync(target).isFile()
363
+ } catch {
364
+ return false
365
+ }
366
+ }
367
+
291
368
  function fail(message) {
292
369
  console.error(`codecraft: ${message}\n\n${HELP}`)
293
370
  process.exit(2)
@@ -482,6 +559,7 @@ function header(meta) {
482
559
  `- Findings: ${meta.findings}`,
483
560
  `- Ignored: ${meta.ignored ?? 0}`,
484
561
  `- Expired exceptions: ${meta.expired ?? 0}`,
562
+ `- Warnings: ${meta.warnings?.length ?? 0}`,
485
563
  `- Gates: ${meta.gates}`,
486
564
  '',
487
565
  ]
package/src/install.mjs CHANGED
@@ -8,17 +8,19 @@ const SKILL_FILES = ['SKILL.md']
8
8
  const SKILL_DIRS = ['docs', 'scenarios', 'prompts']
9
9
 
10
10
  const AGENT_DIRS = {
11
+ generic: '.agents/skills',
11
12
  claude: '.claude/skills',
12
- trae: '.trae/skills',
13
13
  cursor: '.cursor/skills',
14
- generic: '.agents/skills',
14
+ trae: '.trae/skills',
15
+ 'trae-cn': '.trae-cn/skills',
15
16
  }
16
17
 
17
18
  const AGENT_MARKERS = {
19
+ generic: ['.agents'],
18
20
  claude: ['.claude', 'CLAUDE.md'],
19
- trae: ['.trae'],
20
21
  cursor: ['.cursor'],
21
- generic: ['.agents'],
22
+ trae: ['.trae'],
23
+ 'trae-cn': ['.trae-cn'],
22
24
  }
23
25
 
24
26
  export const AGENTS = Object.keys(AGENT_DIRS)