@ultime/codecraft 1.1.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,20 @@
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
+
10
+ ## 1.2.0 - 2026-10-27
11
+
12
+ - 新增 `ignore` 例外配置(A12):`rule` / `file` / `reason` 必填,`expires` 可选;支持 `*` / `**` / `?` 路径匹配;安全规则 `A21` 不可豁免。
13
+ - 命中例外的 findings 不再影响退出码,但仍在报告的 `Ignored` 表和 JSON 的 `ignored` / `exceptions` 中可见;过期例外汇总到 `Expired Exceptions`。
14
+ - 错误信息补充下一步:root 不存在、config 非法、pack 不匹配、未知命令。
15
+ - README 增加 FAQ 与版本口径;`docs/04` 与配置示例补充 `ignore`。
16
+ - 文档明确范围:JS/TS、默认前端、示例原生优先、Node 后端只复用结构规则;框架示例只保留 Vue 3 / React,其它框架留扩展位。
17
+
3
18
  ## 1.1.0 - 2026-10-27
4
19
 
5
20
  - 新增根 `SKILL.md`,兼容 Agent Skills / `skills-npm`;安装 `@ultime/codecraft` 后可被自动发现。
package/CONTRIBUTING.md CHANGED
@@ -19,9 +19,20 @@
19
19
  ## 验证
20
20
 
21
21
  ```bash
22
- node --test tests/*.test.mjs
22
+ npm test
23
+ npm pack --dry-run
23
24
  node bin/codecraft.mjs check --root <项目>
24
25
  ```
25
26
 
26
27
  - 新增 / 修改规则必须补测试;没有反例的规则降级为「参考」。
27
28
  - 不提交密钥、客户名、内部路径;发现疑似凭据立即停止并提示轮换。
29
+
30
+ ## 版本号
31
+
32
+ 只按 semver,不按「感觉重要」升 major:
33
+
34
+ - **major**:破坏性变更——规则 ID 语义变更、配置格式不兼容、JSON 输出结构变化。
35
+ - **minor**:新能力——CLI 选项、检查规则、适配器、模板、向后兼容的配置字段。
36
+ - **patch**:修复、文档、提示词、测试、CI,或无行为变化的内部调整。
37
+ - `2.0` 不设时间表;没有破坏性变更就不进 major。
38
+ - 发布前同时改 `VERSION`、`package.json`、`CHANGELOG.md`,并跑 `npm test` 与 `npm pack --dry-run`。
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Codecraft(代码手艺)
2
2
 
3
- > 语言无关的代码规范,作为可安装的 skill 包。
4
- > 让 AI 和人在新项目、旧项目、评审中按同一套约束工作,并能按版本引入。
3
+ > 面向 JS/TS 的代码规范与架构设计,作为可安装的 skill 包。
4
+ > 默认 Web 前端;示例原生优先(TS / 原生 Web API),框架只给 Vue 3 / React;Node 后端只复用结构规则;其它语言不在范围。
5
5
 
6
6
  - 版本:见 [VERSION](./VERSION)
7
7
  - npm:[@ultime/codecraft](https://www.npmjs.com/package/@ultime/codecraft)
@@ -21,6 +21,26 @@
21
21
 
22
22
  > 第一次用:直接看 [安装](#安装) 和 [第一次使用](#第一次使用)。
23
23
 
24
+ ## 范围与分层
25
+
26
+ | 项 | 内容 |
27
+ |---|---|
28
+ | 覆盖 | JS/TS 项目;Web 前端为默认场景(示例原生优先,框架仅 Vue 3 / React) |
29
+ | 可复用 | Node 后端:模块边界、依赖方向、内外分离、生成物、门禁与文档规则 |
30
+ | 不适用 | Page / Component 等前端族;数据库、缓存、并发、事务等后端专项 |
31
+ | 不在范围 | Java / Go / Python 等其它语言;后端专项另立体系 |
32
+
33
+ 内容分三层,按需取用:
34
+
35
+ | 层 | 回答什么 | 位置 |
36
+ |---|---|---|
37
+ | **规则** | 什么不能变 | [`docs/00-constitution.md`](./docs/00-constitution.md) |
38
+ | **形状** | 边界与结构长什么样 | [`docs/06-architecture.md`](./docs/06-architecture.md)、[`docs/01-family-cards.md`](./docs/01-family-cards.md) |
39
+ | **示例** | 原生怎么落,框架怎么对应 | 族卡的形状、[`templates/`](./templates/)、[`docs/05-patterns.md`](./docs/05-patterns.md) |
40
+
41
+ > 族卡的形状默认原生(HTML + TS);框架示例只给 Vue 3 / React,其它框架在族卡 §12 登记,不预建。
42
+
43
+
24
44
  ## 安装
25
45
 
26
46
  ### 方式 A:npm + skills-npm(推荐,多项目 / 团队)
@@ -38,15 +58,19 @@ npx skills-npm setup
38
58
  npx @ultime/codecraft install
39
59
  ```
40
60
 
41
- 自动检测当前项目的 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` 时只按检测结果安装,不优先任何一家。
42
62
 
43
63
  ```bash
44
- npx @ultime/codecraft install --agent claude # 指定 agent,可逗号分隔
45
- npx @ultime/codecraft install --global --agent claude # 装到用户目录
46
- npx @ultime/codecraft install --dry-run # 只看计划
47
- 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 # 覆盖已有目录
48
70
  ```
49
71
 
72
+ 可选 agent:`generic` / `claude` / `cursor` / `trae` / `trae-cn`;未知目录用 `--dest`。
73
+
50
74
  ### 方式 C:手动指针
51
75
 
52
76
  不复制文件,只在项目规则里留一行:
@@ -175,7 +199,7 @@ codecraft install --dry-run
175
199
  | `library` | A2 / A11 / A21 | 包不 import app、相对路径不逃逸、workspace 依赖必须声明;生成物必须有“勿手改 / 派生”标记;密钥扫描 |
176
200
  | `design-system` | A2 / A11 / A21 | library 规则 + 包内公开文档不得引用内部 `docs/` |
177
201
 
178
- `--pack auto` 的判定顺序:
202
+ `--pack auto` 的判定顺序(基于 JS/TS 生态的目录与 `package.json`):
179
203
 
180
204
  1. 有 `apps/` 或 `src/pages` → `app`
181
205
  2. 有 `packages/` 或根 `package.json` → `library`
@@ -195,12 +219,22 @@ A4(单一收口)属于结构判断,由 review 与项目门禁负责,不
195
219
  "gates": [
196
220
  { "name": "typecheck", "run": "pnpm typecheck", "timeoutMs": 120000 },
197
221
  { "name": "test", "run": "pnpm test", "timeoutMs": 120000 }
222
+ ],
223
+ "ignore": [
224
+ {
225
+ "rule": "A17",
226
+ "file": "src/pages/legacy/**",
227
+ "reason": "legacy page; migrate in 2026-Q4",
228
+ "expires": "2026-12-31"
229
+ }
198
230
  ]
199
231
  }
200
232
  ```
201
233
 
202
234
  - `pack`:`app` / `library` / `design-system` / `auto`。
203
235
  - `gates`:数组;每项必须有一个非空的 `run` 字符串,可选 `name`、`timeoutMs`(默认 `120000`)。
236
+ - `ignore`:例外登记(A12)。每项必须有 `rule` / `file` / `reason`,`expires` 可选(`YYYY-MM-DD`);`file` 支持 `*` / `**` / `?`。安全规则 `A21` 不可豁免。
237
+ - 命中的 findings 不参与退出码,但仍出现在报告的 `Ignored` 表与 JSON 的 `ignored`;过期的条目不生效,列在 `Expired Exceptions`。
204
238
  - gate 在 `--root` 下通过 shell 执行,注入 `CI=1`;失败时报告尾部 40 行 / 4000 字符。
205
239
  - `check` 不跑 gates;`audit` 默认跑;`plan` 只在 `--with-gates` 时跑。
206
240
  - 通用规则内置,业务门禁只编排、不复制。构建指引见 [`docs/04-gates.md`](./docs/04-gates.md)。
@@ -223,13 +257,28 @@ ROADMAP.md 后续版本计划与交接说明
223
257
  ## 版本与稳定承诺
224
258
 
225
259
  - semver;当前版本见 [VERSION](./VERSION)。
260
+ - **major**:只用于破坏性变更——规则 ID 语义变更、配置格式不兼容、JSON 输出结构变化。
261
+ - **minor**:新能力——CLI 选项、检查规则、适配器、模板、向后兼容的配置字段。
262
+ - **patch**:修复、文档、提示词、测试、CI,或无行为变化的内部调整。
263
+ - 不按「感觉重要」升 major;`2.0` 不设时间表。
226
264
  - 规则 ID `A1–A21` 稳定;增删规则属于 minor 以上的变更。
227
265
  - 报告始终携带版本号;跨版本对比以 JSON 为准。
228
266
  - 后续计划:[ROADMAP.md](https://github.com/ultime-space/skills/blob/main/codecraft/ROADMAP.md)。
229
267
 
268
+ ## FAQ
269
+
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`。
273
+ - **`check` / `audit` / `plan` 有什么区别?** `check` 只跑内置规则;`audit` 额外跑 config 里的 gates;`plan` 只读,把 findings 转成计划,默认不跑 gates。
274
+ - **为什么不自动跑 typecheck / test?** 门禁由项目提供,Codecraft 只编排,避免第二套实现。
275
+ - **monorepo 为什么被判成 app / library?** 有 `pages/`、`app/`、`manifest.json + pages.json`、Taro / uni-app 依赖或嵌套应用时按 `app`;有 `packages/` 或 `src/` 时按 `library`;仍不确定就显式传 `--pack`。
276
+ - **支持哪些文件?** 只扫 JS/TS 相关扩展名与 `package.json`;其它语言不覆盖。
277
+
230
278
  ## 边界
231
279
 
232
280
  - 静态检查是文本级:能覆盖高置信模式,可能漏报或误报;最终以项目门禁与 review 为准。
281
+ - 机检只扫描 JS/TS 相关文件(`.ts` / `.tsx` / `.js` / `.jsx` / `.mjs` / `.cjs` / `.vue` / `.svelte`)与 `package.json`,其它语言不覆盖。
233
282
  - 不内置任何业务规则、框架约定或公司内部门禁。
234
283
  - 不读取、不上传代码;所有检查在本地完成。
235
284
 
package/SKILL.md CHANGED
@@ -1,16 +1,39 @@
1
1
  ---
2
2
  name: codecraft
3
- description: 语言无关的代码规范(Codecraft)。用于项目代码审计与分析、架构设计、重构评审、代码规范检查和脚手架。Language-agnostic code standard for auditing, reviewing, designing, and scaffolding code.
3
+ description: 面向 JS/TS 的代码规范与架构设计(Codecraft)。默认 Web 前端,Node 后端可复用结构规则;用于审计、架构设计、重构评审、规范检查与脚手架。Code standard and architecture design for JS/TS projects (frontend-first; structural rules reusable for Node backends).
4
4
  ---
5
5
 
6
6
  # Codecraft(代码手艺)
7
7
 
8
+ > 方向:JS/TS。默认场景是 Web 前端;示例原生优先(TS / 原生 Web API),框架只给 Vue 3 / React;Node 后端只复用结构规则;其它语言不在范围。
9
+
10
+ ## 范围
11
+
12
+ | 项 | 内容 |
13
+ |---|---|
14
+ | 覆盖 | JS/TS 项目;页面、组件、行为、状态、传输、类型、包、脚本、测试 |
15
+ | 可复用 | Node 后端:模块边界、依赖方向、内外分离、生成物、门禁与文档规则 |
16
+ | 不适用 | 前端专属族(Page / Component);后端数据库、缓存、并发、事务等专项规范 |
17
+ | 不在范围 | Java / Go / Python 等其它语言;后端专项另立体系,本 skill 不承诺覆盖 |
18
+
19
+ > 示例顺序:原生 → Vue 3 / React;其它框架不预建,先在 `docs/01-family-cards.md` §12 登记。
20
+
21
+ ## 读法(按任务路由)
22
+
23
+ | 任务 | 先读 | 产出 |
24
+ |---|---|---|
25
+ | 审计 / 评审现有代码 | 宪法 + 相关族卡 | 报告(只读) |
26
+ | 设计架构 / 拆边界 | `docs/06-architecture.md` + 宪法 | 计划 / ADR(只读) |
27
+ | 新项目 / 脚手架 | `06` + `scenarios/new-project.md` | 骨架计划;必要时 `init` |
28
+ | 选型(模式 / 库) | `docs/05-patterns.md` | 选型与代价 |
29
+ | 改代码 | 宪法 + 相关族卡 + 项目门禁 | 变更 + 验证 |
30
+
8
31
  ## 先读
9
32
 
10
33
  1. 项目 `AGENTS.md` / `rules/`
11
34
  2. `docs/00-constitution.md`
12
- 3. 只读相关族卡:`docs/01-family-cards.md`
13
- 4. 设计架构时:`docs/06-architecture.md`
35
+ 3. 设计架构时:`docs/06-architecture.md`
36
+ 4. 只读相关族卡:`docs/01-family-cards.md`(前端项目才读 Page / Component)
14
37
  5. 需要选型时:`docs/05-patterns.md`
15
38
  6. 选一个场景:`scenarios/`
16
39
  7. 按 `docs/02-ai-contract.md` 执行
@@ -37,6 +60,7 @@ description: 语言无关的代码规范(Codecraft)。用于项目代码审
37
60
  - 把业务逻辑放进 Transport 或 Page。
38
61
  - 内部代码 import `external/` 或生成类型。
39
62
  - 手改生成物。
63
+ - 把前端族规则套到 Node 后端(Page / Component / UI 不适用)。
40
64
  - 写敏感信息;发现即停止、脱敏、提示轮换。
41
65
  - 安全红线登记例外。
42
66
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.0
1
+ 1.2.1
@@ -1,10 +1,12 @@
1
1
  ---
2
2
  name: codecraft
3
- description: 语言无关的代码规范(Codecraft)。用于项目代码审计与分析、架构设计、重构评审、代码规范检查和脚手架。Language-agnostic code standard for auditing, reviewing, designing, and scaffolding code.
3
+ description: 面向 JS/TS 的代码规范与架构设计(Codecraft)。默认 Web 前端,Node 后端可复用结构规则;用于审计、架构设计、重构评审、规范检查与脚手架。
4
4
  ---
5
5
 
6
6
  # Codecraft(代码手艺)
7
7
 
8
+ > 方向:JS/TS(默认前端)。后端只复用结构规则;Page / Component 等前端族不套用到后端。
9
+
8
10
  ## 先读
9
11
 
10
12
  1. 项目 `AGENTS.md` / `rules/`
@@ -1,5 +1,7 @@
1
1
  # Codecraft Adapter(通用)
2
2
 
3
+ > 方向:JS/TS(默认前端)。Node 后端只复用模块边界与依赖规则;Page / Component 不适用。
4
+
3
5
  当任务涉及写代码、重构、评审、审计或脚手架时:
4
6
 
5
7
  1. 读项目 `AGENTS.md` / `rules/`。
@@ -1,10 +1,12 @@
1
1
  ---
2
2
  name: codecraft
3
- description: 语言无关的代码规范(Codecraft)。用于项目代码审计与分析、架构设计、重构评审、代码规范检查和脚手架。Language-agnostic code standard for auditing, reviewing, designing, and scaffolding code.
3
+ description: 面向 JS/TS 的代码规范与架构设计(Codecraft)。默认 Web 前端,Node 后端可复用结构规则;用于审计、架构设计、重构评审、规范检查与脚手架。
4
4
  ---
5
5
 
6
6
  # Codecraft(代码手艺)
7
7
 
8
+ > 方向:JS/TS(默认前端)。后端只复用结构规则;Page / Component 等前端族不套用到后端。
9
+
8
10
  ## 先读
9
11
 
10
12
  1. 项目 `AGENTS.md` / `rules/`
@@ -11,5 +11,13 @@
11
11
  "run": "pnpm test",
12
12
  "timeoutMs": 120000
13
13
  }
14
+ ],
15
+ "ignore": [
16
+ {
17
+ "rule": "A17",
18
+ "file": "src/pages/legacy/**",
19
+ "reason": "legacy page; migrate in 2026-Q4",
20
+ "expires": "2026-12-31"
21
+ }
14
22
  ]
15
23
  }
@@ -1,11 +1,23 @@
1
- # 代码宪法 v1(语言无关)
1
+ # 代码宪法 v1(JS/TS)
2
2
 
3
3
  > 2026-10-27 · v1 稳定版。
4
- > 用途:给人和 AI 的共同约束;语言无关,具体语法见《代码族卡 v1》。
4
+ > 用途:给人和 AI 的共同约束;适用于 JS/TS 项目,默认前端场景。
5
5
  > 读者:团队 / AI(Design Specification)。
6
6
  > 术语:English(中文)。
7
7
 
8
- ## 0. 第一性
8
+ ## 0. 范围与读法
9
+
10
+ | 项 | 内容 |
11
+ |---|---|
12
+ | 覆盖 | JS/TS;Web 前端为默认场景 |
13
+ | 可复用 | Node 后端:M1–M5 与 A1–A21 中的结构、边界与安全规则 |
14
+ | 部分适用 | A5 / A6 / A8 的验收列是 TS / 前端写法(`assertNever` / `satisfies` / `computed`);其它实现只要证明同一不变量即可 |
15
+ | 示例顺序 | 原生(TS / Web API)→ Vue 3 / React → 其它框架(不预建,族卡 §12 登记) |
16
+ | 不在范围 | Page / Component 等前端族;数据库、缓存、并发、事务;其它语言 |
17
+
18
+ > 读法:§1 元判据 → §2 不变量 → 按主题查 §3 → 族形查《代码族卡 v1》。
19
+
20
+ ## 0.1 第一性
9
21
 
10
22
  **可预测性(Predictability)**:同一类东西长得一样;同一类改动只动一处;错误看得见;文档不会撒谎。
11
23
 
@@ -237,7 +249,7 @@ repo/
237
249
  |---|---|---|---|
238
250
  | **Transport(传输)** | 网络 / HTTP / 取消 | 有远程调用时 | 自研 / 第三方 |
239
251
  | **Wrapper(包装)** | 外部 → 内部转换 | 有外部契约时 | 自研 / 生成 |
240
- | **Behavior(行为)** | 页面无关行为 | 有复用 / 复杂逻辑时 | composable / hook / service |
252
+ | **Behavior(行为)** | 页面无关行为 | 有复用 / 复杂逻辑时 | 纯函数 / 状态机;需要时再加 composable / hook / service |
241
253
  | **State(状态)** | 状态建模与转移 | 有异步 / 多态时 | 联合类型 / 状态机 |
242
254
  | **Concurrency(并发控制)** | 去重 / 共享 in-flight / 取消 | 有并发请求时 | 自研 / 第三方 / 不做 |
243
255
  | **Observability(可观测性)** | 日志 / 追踪 / 请求序 | 需要排障时 | console / APM / 不做 |
@@ -306,5 +318,5 @@ repo/
306
318
 
307
319
  ## 7. 待定
308
320
 
309
- - 第一个语言方言是 TS/JS,还是同时写 Java/Go?
310
321
  - 每条规则的 owner 和到期日。
322
+ - 后端(Node)专项是否独立成体系;当前只复用本宪法的结构与安全规则。
@@ -1,7 +1,10 @@
1
- # 代码族卡 v1(11 个环节)
1
+ # 代码族卡 v1(11 个环节 · JS/TS)
2
2
 
3
3
  > 2026-10-27 · v1 稳定版。
4
4
  > 用途:把《代码宪法 v1》落到每个代码族(环节);不同族可以不同形状,族内必须同形。
5
+ > 形状与示例原生优先:TS + 原生 Web API(DOM / fetch / CustomEvent)为默认;框架示例只给 Vue 3 / React。
6
+ > 其它框架不预建,先在 §12 扩展位登记。
7
+ > 前端专用族:Page / Component;Node 后端只复用结构与边界类族。
5
8
  > 术语:English(中文)。
6
9
 
7
10
  ## 0. 卡模板
@@ -10,11 +13,29 @@
10
13
 
11
14
  > 族是「环节」,不是必须模块;项目没有这个职责时,这一族可以不存在。
12
15
 
13
- ## 1. Page(页面)
16
+ ## 0.1 适用范围
17
+
18
+ | 族 | 前端 | Node 后端 | 备注 |
19
+ |---|---|---|---|
20
+ | Page | ✅ | ❌ | 后端无对应物,不要硬造 |
21
+ | Component | ✅ | ❌ | 同上 |
22
+ | Behavior | ✅ | ⚠️ | 对应 domain service;core 不依赖框架仍成立 |
23
+ | Transport | ✅ | ✅ | 后端是 client / adapter |
24
+ | Wrapper | ✅ | ✅ | 防腐层,规则一致 |
25
+ | Type | ✅ | ✅ | 联合类型等验收项按语言替换 |
26
+ | Package | ✅ | ✅ | 包边界、版本、public API 规则通用 |
27
+ | State | ✅ | ⚠️ | 状态机 / 领域状态可用;computed 换成同义派生 |
28
+ | Util | ✅ | ✅ | 纯函数,规则一致 |
29
+ | Script | ✅ | ✅ | 脚本通用 |
30
+ | Test | ✅ | ✅ | 分层测试策略通用 |
31
+
32
+ > ✅ 直接用;⚠️ 思路可用、形状要按项目重写;❌ 不适用。
33
+
34
+ ## 1. Page(页面)· 前端专用
14
35
 
15
36
  - **职责**:组合根;路由入口;只做编排、布局、状态展示;不承载业务规则。
16
- - **形状**:`pages/<domain>/index.vue` + `index.config.ts`;页面级类 `page-*`;页面骨架固定为顶栏 / 内容 / 底栏 / 弹层四插槽(组件名按项目命名)。
17
- - **命名**:目录 kebab-case;组件 PascalCase;页面级类 `page-*`(标作用域,不做装饰)。
37
+ - **形状**:`pages/<domain>/index.html` + `index.ts`(原生);`index.vue`(Vue);`index.tsx`(React)。页面级类 `page-*`;页面骨架固定为顶栏 / 内容 / 底栏 / 弹层四插槽(组件名按项目命名)。
38
+ - **命名**:目录 kebab-case;页面级类 `page-*`(标作用域,不做装饰);组件名原生用 kebab-case 自定义元素,Vue / React 用 PascalCase。
18
39
  - **契约**:接收路由参数;调用 Behavior Layer(行为层);渲染 loading / empty / error / success;不直接 import api。
19
40
  - **状态与错误**:页面状态由 Behavior Layer 提供;页面只持有纯 UI 状态(弹窗、tab);错误必须可见。
20
41
  - **测试**:集成 / 视觉回归;纯 UI 不写单元测试。
@@ -22,22 +43,22 @@
22
43
  - **反例**:页面直接 import api;页面里写请求与转换;多个互斥布尔。
23
44
  - **验收**:lint「pages 不 import `@/api`」;路由 / 页面清单;视觉回归。
24
45
 
25
- ## 2. Behavior Layer(行为层)
46
+ ## 2. Behavior Layer(行为层)· 前端为主
26
47
 
27
48
  - **职责**:页面无关的行为;状态转移、校验、编排;不放 UI、不放平台细节。
28
- - **形状**:Vue3 `composables/<domain>/<verb>.ts`;React `hooks/<domain>/<verb>.ts`;需要跨框架时拆 `behavior/<domain>/core.ts` + `useXxx.ts`。
29
- - **命名**:`useXxx` 适配器;`xxxCore` / `xxxBehavior` 核心;一个文件一个动词;同族一种前缀。
49
+ - **形状**:默认 `behavior/<domain>/<verb>.ts`(纯 TS,不依赖框架);需要框架绑定时再加薄适配 `useXxx.ts`(Vue composable / React hook)。
50
+ - **命名**:`xxxCore` / `xxxBehavior` 核心;`useXxx` 适配器;一个文件一个动词;同族一种前缀。
30
51
  - **契约**:入参 / 选项明确;返回状态 + 动作;不藏模块级单例;core 不 import 框架。
31
- - **状态与错误**:状态用联合类型;派生用 computed / selector;预期失败用 Result / 域变体。
52
+ - **状态与错误**:状态用联合类型;派生用纯函数(Vue `computed` / React selector);预期失败用 Result / 域变体。
32
53
  - **测试**:core 属性测试;适配器集成测试。
33
- - **正例**:`useList` / `refreshList(state, fetchPage)` / `useFilterList`。
34
- - **反例**:composable 直接 toast / 跳转;模块级可变状态;为一次性页面逻辑硬抽 composable。
35
- - **验收**:依赖规则「behavior core 不 import vue / react / uni / taro」;状态转移测试;`any` lint。
54
+ - **正例**:`listBehavior` + `useList`(适配层);`refreshList(state, fetchPage)`。
55
+ - **反例**:适配层直接 toast / 跳转;模块级可变状态;为一次性页面逻辑硬抽 composable。
56
+ - **验收**:依赖规则「behavior core 不 import vue / react 或其它框架运行时」;状态转移测试;`any` lint。
36
57
 
37
58
  ## 3. Transport(传输层)
38
59
 
39
60
  - **职责**:网络、HTTP、取消;原样透传;不解释业务 code。401 / 日志是可选环节;外部契约转换在 Wrapper(包装层)。
40
- - **形状**:`api/` 或 `request/`;`adapter/` / `interceptors/` / `modules/`;`pending/` / `console/` 是可选环节,不是必须目录。
61
+ - **形状**:`api/` 或 `request/`;`adapter/` / `interceptors/` / `modules/`;默认用原生 `fetch` + `AbortController`,框架封装只作可选实现;`pending/` / `console/` 是可选环节,不是必须目录。
41
62
  - **命名**:API 文件 snake_case 且与 URL 同名;一 URL 一文件。新代码推荐 `api/<domain>/<action>.ts` + `fetchXxx` / `getXxx`;旧项目可保留 `api_` 前缀(防重名、可搜索、标层);同族不要混用。
42
63
  - **契约**:返回 raw response 或传输层结果;不产出域类型;错误只做传输层归一。
43
64
  - **状态与错误**:无 UI 状态;401 单一收口;超时 / 取消 / 网络错误;不弹业务 toast。
@@ -99,12 +120,12 @@
99
120
  - **反例**:`utils` 包;工具包 import 应用;包内混入业务页面。
100
121
  - **验收**:`boundaries`;包 build / test;`exports` 检查;兼容矩阵。
101
122
 
102
- ## 7. Component(组件)
123
+ ## 7. Component(组件)· 前端专用
103
124
 
104
125
  - **职责**:UI 展示与交互;只消费 props / emit;不取数、不放业务规则;可复用、可组合。
105
- - **形状**:目录式 `components/<Name>/index.vue` + `index.scss`;全局原语放 `app-ui/`;页面私有组件放页面目录。
106
- - **命名**:PascalCase 目录 / 组件;`app-` 用于全局原语;角色后缀 `XxxCard / XxxList / XxxForm / XxxModal / XxxPage`;类型 `XxxProps / XxxEmits`。
107
- - **契约**:props 下行、emit 上行;不 import api / behavior;不读业务 store(仅全局身份 / 权限例外);样式 scoped / BEM;可访问性(键盘 / ARIA / 对比度)。
126
+ - **形状**:`components/<Name>/index.html` + `index.css`(+ 可选 `index.ts`);Vue `index.vue`;React `index.tsx`;全局原语放 `app-ui/`;页面私有组件放页面目录。
127
+ - **命名**:原生自定义元素 kebab-case;Vue / React 组件 PascalCase;`app-` 用于全局原语;角色后缀 `XxxCard / XxxList / XxxForm / XxxModal / XxxPage`;类型 `XxxProps / XxxEmits`(Vue / React)。
128
+ - **契约**:attributes / properties 下行、`CustomEvent` 上行(Vue / React 对应 props / emit);不 import api / behavior;不读业务 store(仅全局身份 / 权限例外);样式 scoped / BEM;可访问性(键盘 / ARIA / 对比度)。
108
129
  - **状态与错误**:只持有纯 UI 状态(open / hover / focus);loading / empty / error 由 props 传入;不吞错。
109
130
  - **测试**:示例页 / Storybook;交互测试;视觉回归;a11y。
110
131
  - **正例**:页面骨架组件、`XxxCard`、`XxxGrid`。
@@ -114,12 +135,12 @@
114
135
  ## 8. State(状态)
115
136
 
116
137
  - **职责**:领域状态建模;状态是数据,不是布尔堆;转移通过事件 / 动作。
117
- - **形状**:`types/<domain>` 的联合类型;`behavior/<domain>/core.ts` 的 reducer / state machine;store 只存数据 + setter。
138
+ - **形状**:默认纯 TS:`types/<domain>` 的联合类型 + `behavior/<domain>/core.ts` 的 reducer / state machine;store 只存数据 + setter,框架只做订阅与派生。
118
139
  - **命名**:`XxxState` / `XxxStatus` / `XxxEvent` / `XxxAction`;通用 `AsyncState / ListState` 放 `types/common`。
119
- - **契约**:状态用联合类型;非法状态不可表示;`success` 有 data,`error` 有 error;派生用 selector / computed。
140
+ - **契约**:状态用联合类型;非法状态不可表示;`success` 有 data,`error` 有 error;派生用纯函数(Vue `computed` / React selector)。
120
141
  - **错误**:错误分类;预期失败用 Result / 域变体;空 catch 为零。
121
142
  - **测试**:状态转移表 / 属性测试;关键流程。
122
- - **正例**:`useList` 的 status;auth machine;form state。
143
+ - **正例**:`listBehavior` 的 status;auth machine;form state。
123
144
  - **反例**:`loading / error / data` 三个布尔;store 放请求逻辑;派生状态另存一份。
124
145
  - **验收**:`assertNever`;`satisfies Record<Union, T>`;状态转移测试。
125
146
 
@@ -150,7 +171,7 @@
150
171
  ## 11. Test(测试)
151
172
 
152
173
  - **职责**:保护契约与关键路径;不是追覆盖率。
153
- - **形状**:`tests/` 或 `__tests__/`;fixture 单独;命名 `<unit>.test.ts`。
174
+ - **形状**:`tests/` 或 `__tests__/`;fixture 单独;命名 `<unit>.test.ts`;默认 `node:test`,组件交互 / 视觉测试再按项目选工具。
154
175
  - **命名**:`describe('<unit> · <behavior>')`、`it('does X when Y')`;中英文随项目,保持一致。
155
176
  - **契约**:
156
177
  - Pure core → 属性测试 / 单元测试;
@@ -166,4 +187,6 @@
166
187
 
167
188
  ## 12. 扩展位
168
189
 
169
- 新的族先在这里登记,不预建结构。
190
+ 当前收录的框架示例:Vue 3、React;其它框架不预建目录与示例,先在这里登记。
191
+
192
+ 登记时补四项:入口形状、状态派生方式、组件契约(props / events)、测试工具。
@@ -2,15 +2,16 @@
2
2
 
3
3
  > 2026-10-27 · v1 稳定版。
4
4
  > 用途:所有模型 / agent 生成或修改代码前先读;与《代码宪法 v1》《代码族卡 v1》配套。
5
+ > 范围:JS/TS 项目,默认前端。示例原生优先(TS / Web API),框架只给 Vue 3 / React;Node 后端只套用结构与安全规则,Page / Component 等前端族不适用。
5
6
  > 术语:English(中文)。
6
7
 
7
8
  ## 1. 先读
8
9
 
9
- 1. 《代码宪法 v1》
10
- 2. 相关《代码族卡 v1》
11
- 3. 设计架构时:`06-architecture.md`(档位 / 七步法 / 边界 / ADR)
12
- 4. 需要选型时:`05-patterns.md`(按力选,不按名字选)
13
- 5. 项目覆盖:`AGENTS.md` / `rules/` / `README`
10
+ 1. 项目覆盖:`AGENTS.md` / `rules/` / `README`
11
+ 2. 《代码宪法 v1》
12
+ 3. 设计或评审架构:`06-architecture.md`(档位 / 七步法 / 边界 / ADR)
13
+ 4. 只读相关《代码族卡 v1》(前端项目才读 Page / Component)
14
+ 5. 需要选型时:`05-patterns.md`(按力选,不按名字选)
14
15
  6. 相关契约 / 类型 / 测试 / 示例
15
16
 
16
17
  ## 2. 禁止
@@ -57,7 +58,7 @@
57
58
 
58
59
  ## 6. Review Checklist(评审清单)
59
60
 
60
- - [ ] 族形正确(Page / Behavior / Transport)
61
+ - [ ] 族形正确(Page / Behavior / Transport;Node 后端跳过 Page / Component)
61
62
  - [ ] 依赖向内
62
63
  - [ ] 状态穷尽
63
64
  - [ ] 错误可见
@@ -1,6 +1,7 @@
1
1
  # 风格总图(一页)
2
2
 
3
3
  > 一页看完全套约束的形状;细则见 `00-constitution.md`,字段见 `01-family-cards.md`。
4
+ > 范围:默认 JS/TS 前端项目;结构与判据可迁移,页面 / 组件相关条目只适用于前端。
4
5
  > 读法:先看第 1 节「不变的」和第 2 节「变的」,其余是判据与代价。
5
6
 
6
7
  ## 一句话
package/docs/04-gates.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Gates(两层门禁)
2
2
 
3
3
  > Codecraft 只内置“规范性通用门禁”;业务门禁由项目实现,Codecraft 负责编排与汇总。
4
+ > 示例命令按 JS/TS 生态(`pnpm`)书写;gate 契约本身与语言无关,Node 项目同样适用。
4
5
 
5
6
  ## 1. 两层模型
6
7
 
@@ -30,12 +31,16 @@
30
31
  "gates": [
31
32
  { "name": "typecheck", "run": "pnpm typecheck", "timeoutMs": 120000 },
32
33
  { "name": "test", "run": "pnpm test", "timeoutMs": 120000 }
34
+ ],
35
+ "ignore": [
36
+ { "rule": "A17", "file": "src/pages/legacy/**", "reason": "legacy page", "expires": "2026-12-31" }
33
37
  ]
34
38
  }
35
39
  ```
36
40
 
37
41
  - `pack`:`app` / `library` / `design-system` / `auto`;CLI `--pack` 优先。
38
42
  - `gates`:数组;每项必须有 `run`,可选 `name` / `timeoutMs`。
43
+ - `ignore`:例外登记(A12);`rule` / `file` / `reason` 必填,`expires` 可选。过期条目不生效,报告中单列;安全规则 `A21` 不可豁免。
39
44
  - 配置文件默认 `codecraft.config.json`;可用 `--config` 指定。
40
45
 
41
46
  ## 4. 按项目类型选门禁
@@ -1,6 +1,7 @@
1
1
  # 设计模式工具箱(可选)
2
2
 
3
3
  > 用途:AI 与人的「可选工具」,不是必背清单;按力(forces)选,不按名字选。
4
+ > 范围:模式本身与语言无关;第 4 / 5 节分别是 JS/TS 惯用与前端 / UI 专用。
4
5
  > 术语:English(中文)。
5
6
  > 原则:**先有痛点,再选模式;没有代价说明的模式,不许用。**
6
7
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  > 用途:系统级 / 模块级 / 演进级的决策方法;代码级模式见 `05-patterns.md`。
4
4
  > 原则:**方法先于模式;约束先于图;边界先于抽象。**
5
+ > 范围:方法本身与语言无关,可用于 JS/TS 前端与 Node 后端;文中目录与框架取舍以 JS/TS 为准。
5
6
  > 术语:English(中文)。
6
7
 
7
8
  ## 0. 先分开六个词
@@ -104,6 +105,7 @@ src/
104
105
  ```
105
106
 
106
107
  - 依赖只能向下;同层不互相 import;跨模块只走 public API。
108
+ - 结构默认与框架无关:原生能表达的先写原生,Vue / React 只作页面与组件的落点。
107
109
  - `shared/api` + adapter + interceptors 是承重墙,不塞进 feature。
108
110
  - server state 与 client state 分开:server state 用 query 层;client state 用 store / 状态机。
109
111
  - 测试分层:纯 core 单测 → feature 行为测试 → 页面 smoke → API 契约测试。
@@ -1,6 +1,7 @@
1
1
  # AI 使用提示词(Prompts)
2
2
 
3
3
  > 用途:给使用者一份可以直接复制的说法,让 AI 按 Codecraft 的模式工作。
4
+ > 范围:提示词按 JS/TS 项目写;Node 后端只复用审计 / 架构 / 评审类提示词。
4
5
  > 读法:先看「最小开场」,再按场景复制;提示词不替代规则,规则正文仍在 `docs/`。
5
6
  > 关系:入口见 `adapters/`;输出格式见 `prompts/output.md`;约束见 `02-ai-contract.md`。
6
7
 
@@ -1,5 +1,7 @@
1
1
  # Golden Tasks(评估任务)
2
2
 
3
+ > 任务默认按 JS/TS 前端项目设计;Node 后端只复用审计 / 架构 / 评审类任务,数据库、缓存、并发不在本批评估内。
4
+
3
5
  用于验证 skill 是否真的按规范工作。每条任务记录:结果、版本、误报 / 漏报。
4
6
 
5
7
  ## 1. 新项目架构计划
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ultime/codecraft",
3
- "version": "1.1.0",
4
- "description": "Language-agnostic code standard as an installable skill package.",
3
+ "version": "1.2.1",
4
+ "description": "面向 JS/TS 的代码规范与架构设计(前端默认,Node 后端可复用结构规则),作为可安装的 skill 包。",
5
5
  "keywords": [
6
6
  "ai",
7
7
  "code-standard",
package/src/cli.mjs CHANGED
@@ -4,6 +4,7 @@ import { check } from './check.mjs'
4
4
  import { loadConfig, runGates } from './gates.mjs'
5
5
  import { buildPlan, summarizePlan } from './plan.mjs'
6
6
  import { applyInitPlan, buildInitPlan } from './init.mjs'
7
+ import { applyIgnores } from './ignore.mjs'
7
8
  import { applyInstallPlan, buildInstallPlan, parseAgents } from './install.mjs'
8
9
 
9
10
  const PACKAGE = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
@@ -23,9 +24,9 @@ Options:
23
24
  --root <path> Project root (default: cwd)
24
25
  --pack app|library|design-system|auto Rule pack (default: auto)
25
26
  --config <path> Config file (default: codecraft.config.json)
26
- --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)
27
28
  --global Install into the home directory
28
- --dest <path> Explicit destination (relative to root / home)
29
+ --dest <path> Explicit destination (relative to root / home), e.g. .trae-cn/skills
29
30
  --force Replace an existing codecraft skill directory
30
31
  --dry-run Show the install plan without writing
31
32
  --no-gates Skip gates in audit / plan
@@ -59,13 +60,13 @@ export function main(argv) {
59
60
 
60
61
  const command = args.command || 'check'
61
62
  if (!['check', 'audit', 'plan', 'init', 'install'].includes(command)) {
62
- console.error(`codecraft: unknown command "${command}"`)
63
+ console.error(`codecraft: unknown command "${command}"\n next: run codecraft --help`)
63
64
  return 2
64
65
  }
65
66
 
66
67
  const root = path.resolve(process.cwd(), args.root)
67
68
  if (!fs.existsSync(root)) {
68
- console.error(`codecraft: root not found: ${root}`)
69
+ console.error(`codecraft: root not found: ${root}\n next: pass --root <project> or run from the project directory`)
69
70
  return 2
70
71
  }
71
72
 
@@ -115,13 +116,15 @@ export function main(argv) {
115
116
 
116
117
  const pack = resolvePack(args.pack, config.config.pack, root)
117
118
  if (!pack) {
118
- console.error('codecraft: invalid config pack; expected app, library or auto')
119
+ console.error(
120
+ `codecraft: invalid config pack "${config.config.pack}"\n next: use app, library, design-system or auto`,
121
+ )
119
122
  return 2
120
123
  }
121
124
 
122
125
  if (command === 'init') {
123
126
  if (args.apply && !args.name) {
124
- console.error('codecraft: init --apply requires --name <name>')
127
+ console.error('codecraft: init --apply requires --name <name>\n next: add --name <demo>')
125
128
  return 2
126
129
  }
127
130
  const plan = buildInitPlan(root, pack === 'design-system' ? 'library' : pack, args.name)
@@ -149,10 +152,17 @@ export function main(argv) {
149
152
  const result = check(root, { pack })
150
153
  const severity = args.ci ? 'error' : args.severity
151
154
  const findings = result.findings.map((finding) => ({ ...finding, severity }))
155
+ const filtered = applyIgnores(findings, config.config.ignore ?? [])
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
+ }
152
162
  const gates = shouldRunGates(command, args)
153
163
  ? runGates(root, config.config.gates ?? [], { failFast: args.failFast })
154
164
  : []
155
- const plan = command === 'plan' ? buildPlan(findings) : []
165
+ const plan = command === 'plan' ? buildPlan(activeFindings) : []
156
166
  const meta = {
157
167
  version: VERSION,
158
168
  mode: command,
@@ -162,7 +172,10 @@ export function main(argv) {
162
172
  severity,
163
173
  rules: pack === 'app' ? ['A17', 'A21'] : ['A2', 'A11', 'A21'],
164
174
  files: result.files,
165
- findings: findings.length,
175
+ findings: activeFindings.length,
176
+ ignored: filtered.ignored.length,
177
+ expired: filtered.expired.length,
178
+ warnings,
166
179
  plan: plan.length,
167
180
  gates: gates.length,
168
181
  }
@@ -172,12 +185,16 @@ export function main(argv) {
172
185
  JSON.stringify(
173
186
  {
174
187
  ...meta,
175
- findings,
188
+ findings: activeFindings,
189
+ ignored: filtered.ignored,
190
+ exceptions: filtered.exceptions,
176
191
  plan,
177
192
  gates,
178
193
  summary: {
179
194
  files: result.files,
180
- findings: findings.length,
195
+ findings: activeFindings.length,
196
+ ignored: filtered.ignored.length,
197
+ expired: filtered.expired.length,
181
198
  plan: plan.length,
182
199
  gates: gates.length,
183
200
  gateFailures: gates.filter((gate) => gate.exitCode !== 0).length,
@@ -188,12 +205,15 @@ export function main(argv) {
188
205
  ),
189
206
  )
190
207
  } else if (command === 'plan') {
191
- console.log(formatPlan(meta, findings, plan, gates))
208
+ console.log(formatPlan(meta, activeFindings, plan, gates))
192
209
  } else {
193
- console.log(formatReport(meta, findings, gates))
210
+ console.log(formatReport(meta, activeFindings, filtered.ignored, filtered.expired, gates))
194
211
  }
195
212
 
196
- const failed = findings.some((finding) => finding.severity === 'error') || gates.some((gate) => gate.exitCode !== 0)
213
+ const failed =
214
+ activeFindings.some((finding) => finding.severity === 'error') ||
215
+ gates.some((gate) => gate.exitCode !== 0)
216
+ if (warnings.length) return 2
197
217
  return failed ? 1 : 0
198
218
  }
199
219
 
@@ -268,19 +288,89 @@ function parseArgs(argv) {
268
288
  return args
269
289
  }
270
290
 
271
- function detectPack(root) {
272
- 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'
273
294
  if (isDir(path.join(root, 'packages'))) return 'library'
295
+ if (hasNestedApp(root)) return 'app'
296
+ if (isDir(path.join(root, 'src'))) return 'library'
274
297
  if (fs.existsSync(path.join(root, 'package.json'))) return 'library'
275
298
  return 'app'
276
299
  }
277
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
+
278
368
  function fail(message) {
279
369
  console.error(`codecraft: ${message}\n\n${HELP}`)
280
370
  process.exit(2)
281
371
  }
282
372
 
283
- function formatReport(meta, findings, gates) {
373
+ function formatReport(meta, findings, ignored, expired, gates) {
284
374
  const lines = header(meta)
285
375
 
286
376
  if (!findings.length) {
@@ -292,6 +382,35 @@ function formatReport(meta, findings, gates) {
292
382
  }
293
383
  }
294
384
 
385
+ if (ignored.length) {
386
+ lines.push(
387
+ '',
388
+ '## Ignored',
389
+ '',
390
+ '| Rule | File | Line | Reason | Expires |',
391
+ '|---|---|---|---|---|',
392
+ )
393
+ for (const finding of ignored) {
394
+ lines.push(
395
+ `| ${finding.rule} | ${finding.file} | ${finding.line} | ${escapeCell(finding.reason)} | ${finding.expires ?? '—'} |`,
396
+ )
397
+ }
398
+ }
399
+
400
+ if (expired.length) {
401
+ lines.push(
402
+ '',
403
+ '## Expired Exceptions',
404
+ '',
405
+ '| Rule | File | Expires | Reason |',
406
+ '|---|---|---|---|',
407
+ )
408
+ for (const exception of expired) {
409
+ lines.push(`| ${exception.rule} | ${exception.file} | ${exception.expires} | ${escapeCell(exception.reason)} |`)
410
+ }
411
+ lines.push('', '- Expired exceptions no longer suppress findings; renew the date or remove the entry.')
412
+ }
413
+
295
414
  lines.push(...gateLines(gates))
296
415
  return lines.join('\n')
297
416
  }
@@ -438,6 +557,9 @@ function header(meta) {
438
557
  `- Severity: ${meta.severity}`,
439
558
  `- Files: ${meta.files}`,
440
559
  `- Findings: ${meta.findings}`,
560
+ `- Ignored: ${meta.ignored ?? 0}`,
561
+ `- Expired exceptions: ${meta.expired ?? 0}`,
562
+ `- Warnings: ${meta.warnings?.length ?? 0}`,
441
563
  `- Gates: ${meta.gates}`,
442
564
  '',
443
565
  ]
package/src/gates.mjs CHANGED
@@ -1,30 +1,33 @@
1
1
  import fs from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { spawnSync } from 'node:child_process'
4
+ import { validateIgnore } from './ignore.mjs'
4
5
 
5
6
  export function loadConfig(root, configPath) {
6
7
  const file = configPath ? path.resolve(root, configPath) : path.join(root, 'codecraft.config.json')
7
8
 
8
- if (!fs.existsSync(file)) return { file: null, config: {} }
9
+ if (!fs.existsSync(file)) return { file: null, config: { ignore: [] } }
9
10
 
10
11
  let config
11
12
  try {
12
13
  config = JSON.parse(fs.readFileSync(file, 'utf8'))
13
14
  } catch (error) {
14
- throw new Error(`invalid config ${file}: ${error.message}`)
15
+ throw new Error(
16
+ `invalid config ${file}: ${error.message} (fix the JSON syntax, or remove the file to use defaults)`,
17
+ )
15
18
  }
16
19
 
17
20
  if (config.gates != null && !Array.isArray(config.gates)) {
18
- throw new Error(`invalid config ${file}: "gates" must be an array`)
21
+ throw new Error(`invalid config ${file}: "gates" must be an array (each item: { name?, run, timeoutMs? })`)
19
22
  }
20
23
 
21
24
  for (const gate of config.gates ?? []) {
22
25
  if (!gate || typeof gate.run !== 'string' || !gate.run.trim()) {
23
- throw new Error(`invalid config ${file}: each gate needs a "run" string`)
26
+ throw new Error(`invalid config ${file}: each gate needs a "run" string (e.g. "pnpm test")`)
24
27
  }
25
28
  }
26
29
 
27
- return { file, config }
30
+ return { file, config: { ...config, ignore: validateIgnore(config.ignore, file) } }
28
31
  }
29
32
 
30
33
  export function runGates(root, gates, options = {}) {
package/src/ignore.mjs ADDED
@@ -0,0 +1,145 @@
1
+ const DATE = /^\d{4}-\d{2}-\d{2}$/
2
+
3
+ /**
4
+ * 校验并规范化 ignore 条目。
5
+ * 每条必须有 rule / file / reason;expires 可选(YYYY-MM-DD)。
6
+ */
7
+ export function validateIgnore(entries, configFile = 'config') {
8
+ if (entries == null) return []
9
+ if (!Array.isArray(entries)) {
10
+ throw new Error(
11
+ `invalid config ${configFile}: "ignore" must be an array (each item: { rule, file, reason, expires? })`,
12
+ )
13
+ }
14
+
15
+ return entries.map((entry, index) => {
16
+ const at = `ignore[${index}]`
17
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
18
+ throw new Error(`invalid config ${configFile}: ${at} must be an object`)
19
+ }
20
+
21
+ const rule = typeof entry.rule === 'string' ? entry.rule.trim() : ''
22
+ if (!/^A\d+$/.test(rule)) {
23
+ throw new Error(`invalid config ${configFile}: ${at}.rule must be a rule id like "A17"`)
24
+ }
25
+ if (rule === 'A21') {
26
+ throw new Error(
27
+ `invalid config ${configFile}: ${at}.rule A21 is a security rule and cannot be ignored (safety red line)`,
28
+ )
29
+ }
30
+
31
+ const file = typeof entry.file === 'string' ? entry.file.trim() : ''
32
+ if (!file) {
33
+ throw new Error(`invalid config ${configFile}: ${at}.file is required (path or glob, e.g. "src/pages/legacy/**")`)
34
+ }
35
+
36
+ const reason = typeof entry.reason === 'string' ? entry.reason.trim() : ''
37
+ if (!reason) {
38
+ throw new Error(`invalid config ${configFile}: ${at}.reason is required (A12: every exception needs a reason)`)
39
+ }
40
+
41
+ const expires = entry.expires == null || entry.expires === '' ? null : String(entry.expires).trim()
42
+ if (expires && !isDate(expires)) {
43
+ throw new Error(`invalid config ${configFile}: ${at}.expires must be YYYY-MM-DD`)
44
+ }
45
+
46
+ return { rule, file, reason, expires }
47
+ })
48
+ }
49
+
50
+ /**
51
+ * 把 ignore 应用到 findings 上。
52
+ * 过期条目不生效,单独返回,供报告提示续期或删除。
53
+ */
54
+ export function applyIgnores(findings, exceptions, options = {}) {
55
+ const today = options.today ?? localDate()
56
+ const active = []
57
+ const expired = []
58
+
59
+ for (const exception of exceptions) {
60
+ if (exception.expires && exception.expires < today) expired.push(exception)
61
+ else active.push(exception)
62
+ }
63
+
64
+ const kept = []
65
+ const ignored = []
66
+
67
+ for (const finding of findings) {
68
+ const matched = active.find(
69
+ (exception) => exception.rule === finding.rule && matchPath(exception.file, finding.file),
70
+ )
71
+ if (matched) ignored.push({ ...finding, reason: matched.reason, expires: matched.expires })
72
+ else kept.push(finding)
73
+ }
74
+
75
+ return {
76
+ findings: kept,
77
+ ignored,
78
+ exceptions: [
79
+ ...active.map((exception) => ({ ...exception, status: 'active' })),
80
+ ...expired.map((exception) => ({ ...exception, status: 'expired' })),
81
+ ],
82
+ expired,
83
+ }
84
+ }
85
+
86
+ /** 支持 * / ** / ? 的路径匹配;不含通配符时按精确路径或目录前缀匹配。 */
87
+ export function matchPath(pattern, file) {
88
+ const normalizedPattern = normalize(pattern)
89
+ const normalizedFile = normalize(file)
90
+
91
+ if (!/[*?]/.test(normalizedPattern)) {
92
+ return (
93
+ normalizedFile === normalizedPattern ||
94
+ (normalizedPattern.endsWith('/') && normalizedFile.startsWith(normalizedPattern))
95
+ )
96
+ }
97
+
98
+ return globToRegExp(normalizedPattern).test(normalizedFile)
99
+ }
100
+
101
+ function normalize(value) {
102
+ return String(value)
103
+ .replace(/\\/g, '/')
104
+ .replace(/^\.\//, '')
105
+ }
106
+
107
+ function globToRegExp(pattern) {
108
+ let source = '^'
109
+ for (let index = 0; index < pattern.length; index += 1) {
110
+ const char = pattern[index]
111
+ if (char === '*') {
112
+ if (pattern[index + 1] === '*') {
113
+ index += 1
114
+ if (pattern[index + 1] === '/') {
115
+ source += '(?:.*/)?'
116
+ index += 1
117
+ } else {
118
+ source += '.*'
119
+ }
120
+ } else {
121
+ source += '[^/]*'
122
+ }
123
+ } else if (char === '?') {
124
+ source += '[^/]'
125
+ } else if ('\\^$+?.()|{}[]'.includes(char)) {
126
+ source += `\\${char}`
127
+ } else {
128
+ source += char
129
+ }
130
+ }
131
+ return new RegExp(`${source}$`)
132
+ }
133
+
134
+ function isDate(value) {
135
+ if (!DATE.test(value)) return false
136
+ const parsed = new Date(`${value}T00:00:00Z`)
137
+ return !Number.isNaN(parsed.getTime()) && parsed.toISOString().slice(0, 10) === value
138
+ }
139
+
140
+ function localDate() {
141
+ const now = new Date()
142
+ const month = String(now.getMonth() + 1).padStart(2, '0')
143
+ const day = String(now.getDate()).padStart(2, '0')
144
+ return `${now.getFullYear()}-${month}-${day}`
145
+ }
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)
@@ -8,7 +8,7 @@
8
8
  |---|---|
9
9
  | `src/pages` | 页面:一个页面一个编排入口,只编排 |
10
10
  | `src/components` | 展示与交互组件;原语薄、业务组件按域 |
11
- | `src/behavior` | 页面无关行为(hooks / composables) |
11
+ | `src/behavior` | 页面无关行为:默认纯 TS core,框架绑定作薄适配(hooks / composables) |
12
12
  | `src/api` | 请求收口;唯一可接触 `external/` 的层 |
13
13
  | `src/adapters` | 平台适配(请求 / 存储 / 路由 / 导出) |
14
14
  | `src/types` | 内部类型:通用层与业务层 |
@@ -17,6 +17,7 @@
17
17
  ## 规则
18
18
 
19
19
  - `pages / components / behavior` 不 import `external/` 或 `generated/`。
20
+ - 原生能表达的先写原生;Vue / React 只作页面与组件的落点。
20
21
  - 外部类型只在 `api` / `adapters` 内转成内部类型。
21
22
  - 鉴权失效、错误提示、分页各一处收口。
22
23
  - 状态用联合类型;非法状态不可表示。