@wneng/create-keel 0.2.1 → 0.3.2

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 (29) hide show
  1. package/README.md +26 -1
  2. package/dist/index.js +612 -40
  3. package/dist/index.js.map +1 -1
  4. package/package.json +2 -1
  5. package/src/standards/templates/coding-style-dart.md.eta +49 -0
  6. package/src/standards/templates/coding-style-go.md.eta +52 -0
  7. package/src/standards/templates/coding-style-java.md.eta +50 -0
  8. package/src/standards/templates/coding-style-python.md.eta +51 -0
  9. package/src/standards/templates/coding-style-rust.md.eta +52 -0
  10. package/src/standards/templates/coding-style-typescript.md.eta +50 -0
  11. package/src/standards/templates/dependency-cruiser.config.cjs.eta +57 -0
  12. package/src/standards/templates/design-tokens.ts.eta +37 -0
  13. package/src/standards/templates/module-boundaries.md.eta +82 -0
  14. package/src/standards/templates/tech-stack-agent.md.eta +35 -0
  15. package/src/standards/templates/tech-stack-miniapp.md.eta +34 -0
  16. package/src/standards/templates/tech-stack-mobile.md.eta +36 -0
  17. package/src/standards/templates/tech-stack-server.md.eta +50 -0
  18. package/src/standards/templates/tech-stack-web.md.eta +36 -0
  19. package/src/standards/templates/ui-design-system.md.eta +70 -0
  20. package/src/templates/ci-gitee/files/PULL_REQUEST_TEMPLATE.md +62 -0
  21. package/src/templates/ci-gitee/fragment.yaml +4 -1
  22. package/src/templates/ci-github/files/PULL_REQUEST_TEMPLATE.md +62 -0
  23. package/src/templates/ci-github/fragment.yaml +4 -1
  24. package/src/templates/docs-skeleton/files/README.md +3 -3
  25. package/src/templates/docs-skeleton/files/governance-checklists.md +3 -3
  26. package/src/templates/docs-skeleton/files/governance-security.md +6 -2
  27. package/src/templates/docs-skeleton/files/usage-quickstart.md +11 -0
  28. package/src/templates/root-files/files/CODEOWNERS +40 -0
  29. package/src/templates/root-files/fragment.yaml +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wneng/create-keel",
3
- "version": "0.2.1",
3
+ "version": "0.3.2",
4
4
  "description": "Scaffolder for Contract First + Vibe Coding projects (keel conventions)",
5
5
  "keywords": [
6
6
  "scaffold",
@@ -17,6 +17,7 @@
17
17
  "dist",
18
18
  "src/templates",
19
19
  "src/feature/templates",
20
+ "src/standards/templates",
20
21
  "README.md",
21
22
  "LICENSE"
22
23
  ],
@@ -0,0 +1,49 @@
1
+ # Dart 编码规范
2
+
3
+ > 真值是 `dart format` + `dart analyze`(包含 `analysis_options.yaml`);本文件解释 *为什么* 与 *如何调整*。机器校验在 CI 中 `analyze` job 阻断违规。
4
+
5
+ config: mobile/analysis_options.yaml
6
+
7
+ ## 命名规范
8
+
9
+ - 类 / 枚举 / typedef:`PascalCase`
10
+ - 方法 / 变量 / 字段:`camelCase`;常量 `lowerCamelCase` 或 `kPascalCase`(与项目内一种二选一锁死)
11
+ - 库 / 文件名:`snake_case.dart`
12
+ - 私有:下划线前缀 `_internal`
13
+
14
+ ## 格式化
15
+
16
+ - `dart format` 单一权威;评审禁讨论缩进 / 行宽
17
+ - 行宽 100;2 空格缩进;尾随逗号在多参 / 多元素结构上必加(dart format 行为依赖它)
18
+ - import 顺序:`dart:*` → `package:*` → 相对路径,每段空行
19
+
20
+ ## 错误处理
21
+
22
+ - `Exception` 子类描述业务错误;`Error` 仅用于不可恢复的 bug
23
+ - 禁止 `catch` 不绑定变量;至少 `catch (e, st)` 并 log
24
+ - 异步:`async/await` 是 happy path;`Future.wait` 时显式选择 `eagerError` 行为
25
+ - 空值:用 sound null safety;禁止 `!` 解包除非有断言注释
26
+
27
+ ## Widget / 状态管理
28
+
29
+ - StatelessWidget 优先;StatefulWidget 仅在必要时
30
+ - 状态管理库(Provider / Riverpod / Bloc):项目内统一一种,写在 ADR 中
31
+ - build 方法保持 < 50 行;超过就拆子组件
32
+
33
+ ## 依赖与模块
34
+
35
+ - `pubspec.yaml` 钉版本,与 `tech-stack-mobile.md` 对齐
36
+ - 跨 `apps/*` 走 `packages/<shared>/` 或独立 pub package;禁止直接 path import 兄弟 app
37
+
38
+ ## 注释与文档
39
+
40
+ - 公共 API 必须有 dartdoc(`///` 三斜杠)
41
+ - 复杂逻辑加 `// Why:` 段
42
+ - `// TODO` 必须带 issue 链接
43
+
44
+ ## lint 配置真值
45
+
46
+ | 工具 | 配置文件 | CI job |
47
+ |------|---------|--------|
48
+ | dart format | (内置,无配置) | `format-check` |
49
+ | dart analyze | `mobile/analysis_options.yaml` | `analyze` |
@@ -0,0 +1,52 @@
1
+ # Go 编码规范
2
+
3
+ > 真值是 `gofmt` + `golangci-lint` 配置;本文件解释 *为什么* 与 *如何调整*。机器校验在 CI 中 `lint` job 阻断违规。
4
+
5
+ config: server/.golangci.yml
6
+
7
+ ## 命名规范
8
+
9
+ - 包名:全小写单词,**无下划线 / 驼峰**;表达内容而非组织(`http` 而非 `httputil`)
10
+ - 导出符号:`PascalCase`;非导出:`camelCase`
11
+ - 错误变量:`Err<Reason>`(`ErrNotFound`);接口:以 `-er` 结尾(`Reader`)
12
+ - 测试:`Test<Func>`;fuzz:`Fuzz<Func>`;benchmark:`Benchmark<Func>`
13
+
14
+ ## 格式化
15
+
16
+ - `gofmt -s` 单一权威;评审禁讨论缩进 / 行宽
17
+ - 行宽建议 120 但不强制(gofmt 不限);import 三段(标准库 → 第三方 → 本仓库),用空行隔开
18
+ - `goimports` 自动处理;CI 中 `gofmt -l` 列出未格式化文件即阻断
19
+
20
+ ## 错误处理
21
+
22
+ - 错误是返回值,不是异常;`if err != nil { return ... }` 是常态
23
+ - 包裹错误用 `fmt.Errorf("...: %w", err)`;上层用 `errors.Is` / `errors.As` 解包
24
+ - 禁止 `_ = err` 吞错;明确处理或加注释 `// best-effort, see issue #xxx`
25
+ - panic 仅在不可恢复(程序 bug)时用;HTTP handler 不允许 panic 逃逸
26
+ - sentinel error 在包级声明:`var ErrNotFound = errors.New("not found")`
27
+
28
+ ## 并发约束
29
+
30
+ - 优先 channel;mutex 仅在 channel 不合适时用
31
+ - 禁止 goroutine 泄露;启动 goroutine 必须有明确 cancel / done 机制
32
+ - `context.Context` 是函数第一个参数;禁止存储在 struct field
33
+
34
+ ## 依赖与模块
35
+
36
+ - 第三方依赖在 `go.mod` 钉版本,与 `tech-stack-server.md` 对齐
37
+ - 主模块 `go.mod` 跟随仓库根;跨 `apps/*` 在 multi-app 模式下走 `internal/<shared>` 或独立 module
38
+
39
+ ## 注释与文档
40
+
41
+ - 包级 doc comment:`// Package <name> ...`,必须存在
42
+ - 导出符号 doc comment:`// <Name> ...` 开头(godoc 约定)
43
+ - 不写 `// TODO` 而不带 issue 链接
44
+
45
+ ## lint 配置真值
46
+
47
+ | 工具 | 配置文件 | CI job |
48
+ |------|---------|--------|
49
+ | gofmt | (内置,无配置) | `format-check` |
50
+ | golangci-lint | `server/.golangci.yml` | `lint` |
51
+ | go vet | (内置) | `vet` |
52
+ | govulncheck | (命令行运行) | `security` |
@@ -0,0 +1,50 @@
1
+ # Java 编码规范
2
+
3
+ > 真值是 Checkstyle / Spotless 配置;本文件解释 *为什么* 与 *如何调整*。机器校验在 CI 中 `checkstyle` 与 `spotless:check` 阻断违规。
4
+
5
+ config: server/checkstyle.xml
6
+
7
+ ## 命名规范
8
+
9
+ - 类 / 接口 / 枚举:`PascalCase`;接口禁止 `I` 前缀
10
+ - 方法 / 字段:`camelCase`;常量:`UPPER_SNAKE_CASE`
11
+ - 包名:全小写 `com.<org>.<product>.<module>`,禁止下划线
12
+ - 测试类:`<被测类>Test`;集成测试 `<场景>IT`
13
+
14
+ ## 格式化
15
+
16
+ - Spotless(Google Java Format AOSP 风格)单一权威;评审禁讨论分号 / 大括号位置
17
+ - 行宽 120;4 空格缩进;import 顺序:`java.*` → `javax.*` → 第三方 → `com.<org>.*`,每段空行
18
+ - 删除未使用 import(Spotless 自动)
19
+
20
+ ## 错误处理
21
+
22
+ - 检查异常仅在确实可恢复时使用;其余抛 `RuntimeException` 子类
23
+ - 禁止 `catch (Exception e)` 吞错;至少 log + rethrow 或用 sentinel 返回值
24
+ - 资源管理走 try-with-resources;禁止手写 `finally { close() }`
25
+ - API 边界统一异常→`ProblemDetails`(RFC 7807)
26
+
27
+ ## 设计约束
28
+
29
+ - POJO 走 record(Java 17+)或 Lombok `@Value`;二选一在 ADR 中记录
30
+ - 禁止字段级公共可变状态;若必须用 `volatile` / `AtomicReference`
31
+ - 依赖注入由 Spring 管理;构造函数注入,禁止字段注入
32
+
33
+ ## 依赖与模块
34
+
35
+ - 第三方依赖在 `pom.xml` 钉版本,与 `tech-stack-server.md` frontmatter 对齐
36
+ - 跨模块引用:`apps/*` 之间不允许直接 import(multi-app 模式下走 contract / 共享 packages)
37
+
38
+ ## 注释与文档
39
+
40
+ - 公共类 / 公共方法必须有 JavaDoc,含 `@param` `@return` `@throws`
41
+ - 复杂算法或决策点加 `// Why:` 段
42
+ - 禁止 `// TODO` 不带 issue 链接
43
+
44
+ ## lint 配置真值
45
+
46
+ | 工具 | 配置文件 | CI job |
47
+ |------|---------|--------|
48
+ | Checkstyle | `server/checkstyle.xml` | `checkstyle` |
49
+ | Spotless | `server/pom.xml` 中 `<plugin>spotless</plugin>` 段 | `spotless-check` |
50
+ | 编译警告 | `pom.xml` `-Werror` | `compile` |
@@ -0,0 +1,51 @@
1
+ # Python 编码规范
2
+
3
+ > 真值是 `ruff` + `mypy` 配置;本文件解释 *为什么* 与 *如何调整*。机器校验在 CI 中 `lint` 与 `typecheck` job 阻断违规。
4
+
5
+ config: server/pyproject.toml
6
+
7
+ ## 命名规范
8
+
9
+ - 模块 / 包:`snake_case`,全小写
10
+ - 类:`PascalCase`;异常类:以 `Error` 结尾(`UserNotFoundError`)
11
+ - 函数 / 变量:`snake_case`;常量:`UPPER_SNAKE_CASE`
12
+ - 私有:单下划线前缀 `_helper`;name mangling 双下划线仅在确需时
13
+
14
+ ## 格式化
15
+
16
+ - `ruff format` 单一权威(替代 black + isort);评审禁讨论引号 / 行宽
17
+ - 行宽 100;4 空格缩进;import 顺序:标准库 → 第三方 → 本地
18
+ - 删除未使用 import(ruff 自动)
19
+
20
+ ## 类型注解
21
+
22
+ - 全文件类型注解;函数签名禁止省略
23
+ - 严格 mypy:`disallow_untyped_defs = true`,`strict_optional = true`
24
+ - 优先 `pydantic.BaseModel` 描述 API 边界数据;`@dataclass(slots=True)` 描述内部值对象
25
+
26
+ ## 错误处理
27
+
28
+ - 业务异常自定义 `XxxError(BaseAppError)`;禁止 `raise Exception(...)`
29
+ - 禁止裸 `except:`;至少 `except Exception:` 并 log
30
+ - 资源管理走 `with`;禁止手写 `try / finally: f.close()`
31
+ - 异步:`asyncio.gather` 时显式选择 `return_exceptions` 行为
32
+
33
+ ## 依赖与导入
34
+
35
+ - 第三方依赖在 `pyproject.toml` 钉版本,与 `tech-stack-server.md` 对齐
36
+ - 跨 `apps/*` 引用走 `apps/_shared/` 或共享 distribution;禁止直接 `from apps.other import ...`
37
+ - 循环 import:拆模块或用 TYPE_CHECKING 延迟
38
+
39
+ ## 注释与文档
40
+
41
+ - 公共函数 / 类必须 docstring(Google 或 PEP 257 风格,仓库二选一锁死)
42
+ - 复杂决策点加 `# Why:` 段
43
+ - `# TODO` 必须带 issue 链接
44
+
45
+ ## lint 配置真值
46
+
47
+ | 工具 | 配置文件 | CI job |
48
+ |------|---------|--------|
49
+ | ruff | `server/pyproject.toml` `[tool.ruff]` 段 | `lint` |
50
+ | mypy | `server/pyproject.toml` `[tool.mypy]` 段 | `typecheck` |
51
+ | pip-audit | (命令行) | `security` |
@@ -0,0 +1,52 @@
1
+ # Rust 编码规范
2
+
3
+ > 真值是 `rustfmt` + `clippy` 配置;本文件解释 *为什么* 与 *如何调整*。机器校验在 CI 中 `clippy` job 阻断违规。
4
+
5
+ config: agent/rustfmt.toml
6
+
7
+ ## 命名规范
8
+
9
+ - crate / 模块:`snake_case`
10
+ - 类型 / trait / 枚举:`PascalCase`;变体也是 `PascalCase`
11
+ - 函数 / 变量 / 字段:`snake_case`;常量 / 静态:`UPPER_SNAKE_CASE`
12
+ - 生命周期:单字母 `'a` 起步,复杂时表义 `'session`
13
+
14
+ ## 格式化
15
+
16
+ - `cargo fmt` 单一权威;CI 中 `cargo fmt --check` 阻断
17
+ - 行宽 100(rustfmt 默认);4 空格缩进
18
+ - import 顺序:标准库 → 第三方 → 本 crate;rustfmt 处理
19
+
20
+ ## 错误处理
21
+
22
+ - 业务函数返回 `Result<T, E>`;禁止 `unwrap()` / `expect()` 在 release 路径
23
+ - 错误类型:库代码用 `thiserror`;应用代码用 `anyhow::Result`
24
+ - `?` 是 happy path;想日志或转换错误时用 `.context("...")` (`anyhow`) 或 `.map_err(...)`
25
+ - panic 仅在不可恢复(断言违反、初始化失败)时用
26
+
27
+ ## 所有权与并发
28
+
29
+ - 优先 borrow,其次 `Clone`,再次 `Arc`;`Rc` 仅在单线程
30
+ - `unsafe` 必须有 `// SAFETY: ...` 注释解释不变量
31
+ - 跨线程数据走 `Send + Sync`;共享可变状态走 `Arc<Mutex<T>>` 并尽量缩小锁粒度
32
+ - async:`tokio` 单一运行时;不要混 async-std
33
+
34
+ ## 依赖与模块
35
+
36
+ - `Cargo.toml` 钉版本(`= "x.y.z"`),与 `tech-stack-agent.md` 对齐
37
+ - workspace:跨 `apps/*` 在 multi-app 模式下走顶层 `crates/<shared>`,禁止直接 path import 兄弟 app
38
+ - 第三方 crate 引入前确认 license 与维护活跃度
39
+
40
+ ## 注释与文档
41
+
42
+ - 导出 item 必须有 `///` doc comment;crate 根写 `//!` 模块级 doc
43
+ - `// Why:` 段说明非显然的决策
44
+ - `cargo doc --no-deps` 在 CI 中验证 doc 不报错
45
+
46
+ ## lint 配置真值
47
+
48
+ | 工具 | 配置文件 | CI job |
49
+ |------|---------|--------|
50
+ | rustfmt | `agent/rustfmt.toml` | `format-check` |
51
+ | clippy | `agent/Cargo.toml` 中 `[lints.clippy]` 段 | `clippy` |
52
+ | cargo deny | `agent/deny.toml` | `security` |
@@ -0,0 +1,50 @@
1
+ # TypeScript 编码规范
2
+
3
+ > 真值是 ESLint / Prettier 配置;本文件解释 *为什么* 与 *如何调整*。机器校验在 CI 中 `eslint` job 阻断违规。
4
+
5
+ config: web/.eslintrc.cjs
6
+
7
+ ## 命名规范
8
+
9
+ - 文件:`kebab-case.ts`,组件文件除外 → `PascalCase.tsx`
10
+ - 类型 / 接口 / 类:`PascalCase`,禁止 `I` 前缀
11
+ - 变量 / 函数:`camelCase`,常量级(模块顶部全大写枚举):`UPPER_SNAKE_CASE`
12
+ - React 组件:`PascalCase`;hooks:`use<Name>`
13
+
14
+ ## 格式化
15
+
16
+ - Prettier 单一权威;本仓库不在评审里讨论分号 / 引号 / 行宽
17
+ - 行宽 100;2 空格缩进;尾随逗号 `all`
18
+ - import 顺序:node 内置 → 第三方 → 别名 (`@/...`) → 相对路径,每段空行隔开
19
+
20
+ ## 错误处理
21
+
22
+ - 业务函数返回 `Result<T, E>` 或抛出 `ScaffolderError` 子类;禁止裸 `throw new Error(...)`
23
+ - `try / catch` 必须 narrow `unknown`:`catch (e)` 后用 `instanceof` 或 type guard
24
+ - async 函数禁止吞错;`Promise.allSettled` 用于刻意聚合失败
25
+
26
+ ## 类型用法
27
+
28
+ - 严格模式:`tsc --noEmit` 在 CI 阻断;本地禁止 `// @ts-ignore`,必要时用 `@ts-expect-error` 并解释
29
+ - 优先 `interface` 描述对象形状;`type` 用于联合 / 映射 / 函数签名
30
+ - 禁止 `any`;不可避免时用 `unknown` 加 type guard
31
+
32
+ ## 依赖与导入
33
+
34
+ - 单 app:相对路径;多 app:路径别名 `@apps/<name>` 或 `@contracts/...`
35
+ - 跨 `apps/*` import 在 multi-app 模式下被 `dependency-cruiser` 阻断
36
+ - 第三方依赖必须在 `tech-stack-<env>.md` 钉版本表中存在
37
+
38
+ ## 注释与文档
39
+
40
+ - TSDoc:导出符号必须有一行说明;复杂逻辑加 `Why:` 段说明意图
41
+ - 对外 API:`@param` `@returns` `@throws` 完整
42
+ - 禁止注释掉的代码;用 git 历史
43
+
44
+ ## lint 配置真值
45
+
46
+ | 工具 | 配置文件 | CI job |
47
+ |------|---------|--------|
48
+ | ESLint | `web/.eslintrc.cjs`(前端)/ `server/.eslintrc.cjs`(后端) | `lint` |
49
+ | Prettier | `.prettierrc` | `format-check` |
50
+ | TypeScript | `tsconfig.json` | `typecheck` |
@@ -0,0 +1,57 @@
1
+ /**
2
+ * dependency-cruiser configuration — engineering-standards module boundaries.
3
+ *
4
+ * Generated by `@wneng/create-keel` (engineering-standards plan).
5
+ * Edit with care; the rules here are referenced by:
6
+ * - docs/03-工程规范与研发基础设施/module-boundaries.md (narrative truth)
7
+ * - tools/governance-lint/checks/stack-pinning.js (exit code mapping)
8
+ */
9
+
10
+ /** @type {import('dependency-cruiser').IConfiguration} */
11
+ module.exports = {
12
+ forbidden: [
13
+ {
14
+ name: 'GOV.BOUND.CROSS_APP',
15
+ severity: 'error',
16
+ comment:
17
+ 'apps/<X> must not directly import from apps/<Y>. Move shared code to contracts/ or packages/<shared>/.',
18
+ from: { path: '^(?:[^/]+)/apps/([^/]+)/' },
19
+ to: { path: '^(?:[^/]+)/apps/(?!\\1)([^/]+)/' },
20
+ },
21
+ {
22
+ name: 'GOV.BOUND.CIRCULAR',
23
+ severity: 'error',
24
+ comment: 'Circular dependencies make the build graph unstable.',
25
+ from: {},
26
+ to: { circular: true },
27
+ },
28
+ {
29
+ name: 'no-orphans',
30
+ comment: 'Orphan modules (no incoming edges) usually indicate dead code.',
31
+ severity: 'warn',
32
+ from: {
33
+ orphan: true,
34
+ pathNot: [
35
+ '(^|/)\\.(?:eslint|prettier|babel|tsconfig)',
36
+ '\\.d\\.ts$',
37
+ '(^|/)src/index\\.[jt]sx?$',
38
+ ],
39
+ },
40
+ to: {},
41
+ },
42
+ ],
43
+ options: {
44
+ doNotFollow: { path: 'node_modules' },
45
+ tsConfig: { fileName: 'tsconfig.json' },
46
+ enhancedResolveOptions: {
47
+ exportsFields: ['exports'],
48
+ conditionNames: ['import', 'require', 'node'],
49
+ },
50
+ reporterOptions: {
51
+ err: {
52
+ // Standard error format consumed by governance-lint
53
+ showRules: true,
54
+ },
55
+ },
56
+ },
57
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Design tokens — generated by `@wneng/create-keel` (engineering-standards).
3
+ *
4
+ * The exported constant *names* here MUST exactly match the token name
5
+ * column in `docs/05-前端客户端详细设计/ui-design-system.md`. The
6
+ * governance-lint `ui-token-drift` check enforces this; mismatches
7
+ * surface as `GOV.UI.TOKEN_DRIFT` in CI.
8
+ *
9
+ * Edit values, not names. Renaming a token requires a coordinated PR
10
+ * that touches both this file and the manifest's token table in the
11
+ * same commit.
12
+ */
13
+
14
+ // Colors ---------------------------------------------------------------
15
+
16
+ export const colorPrimary = '#2563EB';
17
+ export const colorSurface = '#FFFFFF';
18
+ export const colorTextDefault = '#0F172A';
19
+ export const colorTextMuted = '#64748B';
20
+ export const colorBorder = '#E2E8F0';
21
+ export const colorDanger = '#DC2626';
22
+
23
+ // Spacing --------------------------------------------------------------
24
+
25
+ export const space1 = '4px';
26
+ export const space2 = '8px';
27
+ export const space3 = '12px';
28
+ export const space4 = '16px';
29
+ export const space6 = '24px';
30
+ export const space8 = '32px';
31
+
32
+ // Typography -----------------------------------------------------------
33
+
34
+ export const fontHeading1 = { fontSize: '32px', lineHeight: 1.25, fontWeight: 600 } as const;
35
+ export const fontHeading2 = { fontSize: '24px', lineHeight: 1.3, fontWeight: 600 } as const;
36
+ export const fontBody = { fontSize: '14px', lineHeight: 1.5, fontWeight: 400 } as const;
37
+ export const fontCaption = { fontSize: '12px', lineHeight: 1.4, fontWeight: 400 } as const;
@@ -0,0 +1,82 @@
1
+ # 模块引用边界
2
+
3
+ > 本文件给出仓库内的引用方向规则。机器侧守门交给 `dependency-cruiser` / `archunit` / `gomodguard` 等工具;本文件解释**为什么**有这些边界,以及**如何**调整。
4
+
5
+ config: <% if (it.standards.multiApp) { %>dependency-cruiser.config.cjs<% } else { %>(单 app 模式无机器守门,仅本文件叙事)<% } %>
6
+
7
+ ## 总原则
8
+
9
+ 依赖**只**沿允许的方向流动。违反方向的 import / require / require_once 会导致以下后果之一:
10
+
11
+ 1. 短期:编译能过,但循环依赖让构建图不稳定
12
+ 2. 中期:模块边界腐烂,重构难度指数上升
13
+ 3. 长期:单一模块测试不再独立,整仓 CI 时长翻倍
14
+
15
+ 为防止这种漂移,本文件 + `dependency-cruiser`(multi-app 模式下)一起做硬约束。
16
+
17
+ ## 命名规范
18
+
19
+ 边界检查中用到的几个稳定标识:
20
+
21
+ - `apps/<name>/` —— 多 app 模式下的应用根
22
+ - `contracts/` —— 跨执行环境的机器可消费契约
23
+ - `packages/` —— 多 app 共享的代码(root 级,可选)
24
+
25
+ ## 错误处理
26
+
27
+ import 违规会被 dependency-cruiser 报告为以下错误码:
28
+
29
+ - `GOV.BOUND.CROSS_APP` —— `apps/A` 直接引用了 `apps/B` 的代码
30
+ - `GOV.BOUND.CIRCULAR` —— 模块循环依赖
31
+
32
+ 修复路径:把共享代码挪到 `contracts/`(机器可消费契约)或 `packages/<shared>/`(根级可选 workspace)。
33
+
34
+ ## 格式化
35
+
36
+ - 模块导出文件按 layer 命名:`index.ts` 是公共入口,`internal/` 下是私有
37
+ - 跨包 import 必须经过 `index.ts`,**禁止**深路径 `from "@app/internal/foo"`
38
+
39
+ <% if (it.standards.multiApp) { %>## 多 app 模式(当前启用)
40
+
41
+ 仓库已切换到多 app 模式,`<env>/apps/<name>/` 下每个目录是独立应用。
42
+
43
+ **禁止**:
44
+ - `apps/A/` 中的代码 import `apps/B/` 中的任何文件(任何深度)
45
+ - 跨 app 共享 utility 直接放在某个 app 内部
46
+
47
+ **允许**:
48
+ - 通过 `contracts/` 共享 schema / 错误码 / 状态机
49
+ - 通过根 `packages/<name>/` 共享代码(需要在 PR 中说明为什么不能放契约层)
50
+
51
+ 机器守门:[`dependency-cruiser.config.cjs`](../../dependency-cruiser.config.cjs) 在 CI 中阻断违规 import。
52
+
53
+ ## 调整流程
54
+
55
+ 1. 改 `dependency-cruiser.config.cjs` 中的 forbidden 规则前,必须在 ADR 中说明
56
+ 2. 同 PR 改本文件正文部分(这是双写,让 review 看到边界变化)
57
+ 3. CI 跑 dependency-cruiser;不通过就改回去
58
+ 4. 真要破例:在违规处添加 `// eslint-disable-next-line import/no-restricted-paths` 并 PR 描述给出理由
59
+ <% } else { %>## 单 app 模式(当前启用)
60
+
61
+ 仓库目前是单 app 模式,`<env>/` 下直接放一份工程文件,没有 `apps/` 子目录。
62
+
63
+ **约束**:
64
+ - 跨执行环境(如后端 server/ ↔ 前端 web/)必须通过 `contracts/` 共享类型与契约
65
+ - 不要在 server/ 中 import web/ 的文件,或反之
66
+
67
+ **没有机器守门**:单 app 模式下不强制 dependency-cruiser;这是有意为之,因为单 app 模式工程量小,引入额外工具收益不抵成本。
68
+
69
+ ## 升级到多 app 模式
70
+
71
+ 如果将来仓库需要容纳第二个应用:
72
+
73
+ 1. `mkdir <env>/apps/<原应用名>` 并 `git mv` 全部源码
74
+ 2. 同 PR 启用 `--multi-app` 选项,重新生成本文件 + `dependency-cruiser.config.cjs`
75
+ 3. 在 ADR 中记录"为什么从单 app 升级"
76
+ 4. 完整 promote / add-app 流程见 [`docs/governance/git-workflow.md`](../governance/git-workflow.md)
77
+ <% } %>
78
+
79
+ ## 关联
80
+
81
+ - 编码规范:见 `coding-style-<lang>.md` 系列
82
+ - 部署:跨 app 共享配置走 `deploy/`,不要让一个 app 依赖另一个 app 的 deploy 制品
@@ -0,0 +1,35 @@
1
+ ---
2
+ runtime: <%= it.standards.env %>
3
+ language: <%= it.standards.language %>
4
+ packageManager: <%= it.standards.packageManager %>
5
+ manifestPath: <%= it.standards.manifestPath %>
6
+ lastReviewed: <%= it.standards.lastReviewed %>
7
+ dependencies:
8
+ <% for (const dep of it.standards.dependencies) { %> - name: "<%= dep.name %>"
9
+ version: "<%= dep.version %>"
10
+ policy: <%= dep.policy %>
11
+ <% } %>---
12
+
13
+ # 桌面 / Agent 技术栈钉版本表
14
+
15
+ > frontmatter 是机器真值;governance-lint 会把上面 `dependencies` 与 `<%= it.standards.manifestPath %>` 的实际版本逐条比对。
16
+
17
+ ## 为什么钉版本
18
+
19
+ - **桌面端发布周期长**:用户桌面上的版本几个月不更新很正常;不可重现的依赖会让远程 debug 极其痛苦
20
+ - **平台二进制兼容**:Tauri / Electron 的原生模块在 Windows / macOS / Linux 表现不一致,主版本变化最容易触发签名失败
21
+
22
+ ## policy 三档
23
+
24
+ policy 语义与 [tech-stack-server.md](tech-stack-server.md) 一致。桌面端常用 `minor` 起步,对 `tokio` / `serde` 一类基础设施级依赖也只用 `minor`(Rust 生态的 SemVer 通常守得住)。
25
+
26
+ ## Rust crate 特有的考虑
27
+
28
+ - workspace 中所有 crate 的同名依赖必须钉同一版本(`Cargo.toml` 中 `[workspace.dependencies]` 段)
29
+ - 与 C / C++ 共享库通过 FFI 交互的 crate(如 `openssl-sys`):标 `major-only`,因为底层 ABI 变化会导致 panic
30
+ - `unsafe` 较多的 crate:标 `major-only`,每次升级都人工 review CHANGELOG
31
+
32
+ ## 关联
33
+
34
+ - 编码规范:[coding-style-rust.md](coding-style-rust.md)
35
+ - 依赖安全:[../governance/security.md](../governance/security.md)
@@ -0,0 +1,34 @@
1
+ ---
2
+ runtime: <%= it.standards.env %>
3
+ language: <%= it.standards.language %>
4
+ packageManager: <%= it.standards.packageManager %>
5
+ manifestPath: <%= it.standards.manifestPath %>
6
+ lastReviewed: <%= it.standards.lastReviewed %>
7
+ dependencies:
8
+ <% for (const dep of it.standards.dependencies) { %> - name: "<%= dep.name %>"
9
+ version: "<%= dep.version %>"
10
+ policy: <%= dep.policy %>
11
+ <% } %>---
12
+
13
+ # 小程序技术栈钉版本表
14
+
15
+ > frontmatter 是机器真值;governance-lint 会把上面 `dependencies` 与 `<%= it.standards.manifestPath %>` 的实际版本逐条比对。
16
+
17
+ ## 为什么钉版本
18
+
19
+ - **平台 SDK 审核期长**:宿主(微信 / 支付宝 / 抖音)的基础库版本不在本表;本表管的是 npm 侧依赖
20
+ - **小程序包大小限制**:依赖增删都影响 2 MB / 8 MB 包限制;钉版本帮助定位"哪次升级把包撑爆了"
21
+
22
+ ## policy 三档
23
+
24
+ policy 语义与 [tech-stack-server.md](tech-stack-server.md) 一致。小程序场景常用 `minor` + 严选第三方包。
25
+
26
+ ## 宿主基础库
27
+
28
+ - 微信:`miniapp/project.config.json` 中的 `libVersion` 是宿主版本,非 npm 依赖;不进本表
29
+ - 自定义组件库(如 vant-weapp):进本表,policy 标 `minor`
30
+
31
+ ## 关联
32
+
33
+ - 编码规范:[coding-style-typescript.md](coding-style-typescript.md)
34
+ - 依赖安全:[../governance/security.md](../governance/security.md)
@@ -0,0 +1,36 @@
1
+ ---
2
+ runtime: <%= it.standards.env %>
3
+ language: <%= it.standards.language %>
4
+ packageManager: <%= it.standards.packageManager %>
5
+ manifestPath: <%= it.standards.manifestPath %>
6
+ lastReviewed: <%= it.standards.lastReviewed %>
7
+ dependencies:
8
+ <% for (const dep of it.standards.dependencies) { %> - name: "<%= dep.name %>"
9
+ version: "<%= dep.version %>"
10
+ policy: <%= dep.policy %>
11
+ <% } %>---
12
+
13
+ # 移动端技术栈钉版本表
14
+
15
+ > frontmatter 是机器真值;governance-lint 会把上面 `dependencies` 与 `<%= it.standards.manifestPath %>` 的实际版本逐条比对。
16
+
17
+ ## 为什么钉版本
18
+
19
+ - **应用商店审核失败回溯**:移动端经常因第三方 SDK 更新触发审核拒绝;版本钉死能精确定位
20
+ - **多设备一致性**:CI 与开发机装出同一份依赖
21
+ - **离线场景**:移动端不像服务端能随时回滚镜像,钉版本是唯一保险
22
+
23
+ ## policy 三档
24
+
25
+ policy 语义、调整流程,与 [tech-stack-server.md](tech-stack-server.md) 一致。
26
+
27
+ ## 移动端特有的考虑
28
+
29
+ - iOS 系统 API / Android SDK 不归本表管,由 `mobile/ios/Podfile` 与 `mobile/android/build.gradle` 中的对应版本约束
30
+ - React Native / Flutter SDK 主版本升级常涉及原生模块重新链接;务必标 `major-only`
31
+ - 推送、地图、支付 SDK:标 `major-only`(合规要求强约束)
32
+
33
+ ## 关联
34
+
35
+ - 编码规范:[coding-style-<%= it.standards.language %>.md](coding-style-<%= it.standards.language %>.md)
36
+ - 依赖安全:[../governance/security.md](../governance/security.md)
@@ -0,0 +1,50 @@
1
+ ---
2
+ runtime: <%= it.standards.env %>
3
+ language: <%= it.standards.language %>
4
+ packageManager: <%= it.standards.packageManager %>
5
+ manifestPath: <%= it.standards.manifestPath %>
6
+ lastReviewed: <%= it.standards.lastReviewed %>
7
+ dependencies:
8
+ <% for (const dep of it.standards.dependencies) { %> - name: "<%= dep.name %>"
9
+ version: "<%= dep.version %>"
10
+ policy: <%= dep.policy %>
11
+ <% } %>---
12
+
13
+ # 后端技术栈钉版本表
14
+
15
+ > 这个文件的 **frontmatter 是机器真值**:CI 中的 `governance-lint` 会把上面 `dependencies` 数组与 `<%= it.standards.manifestPath %>` 的实际版本逐条对比。
16
+ >
17
+ > 本文件正文是**为什么**这样选 + **如何调整**。机器无法回答这两个问题。
18
+
19
+ ## 为什么钉版本
20
+
21
+ - **审计可追溯**:哪天升级、为什么升级、谁拍板,都留在这个文件的 git 历史里
22
+ - **可重现构建**:六个月后回到这个 commit,依赖能装出同一棵树
23
+ - **AI 不擅自升级**:scaffolder 的 governance-lint 把"未走 PR 的 minor / major 漂移"当作 CI 失败
24
+
25
+ ## policy 三档
26
+
27
+ | policy | 含义 | 何时使用 |
28
+ |--------|------|----------|
29
+ | `free` | 任意漂移都不报 | 工具链辅助包(如类型 stub),版本变化不影响业务 |
30
+ | `minor` | minor / patch 漂移 → warning;major → error | 默认。绝大多数 runtime 框架与库 |
31
+ | `major-only` | 任何漂移(含 patch)→ error | 安全敏感库(加密、token 验证、SSO) |
32
+
33
+ ## 调整流程
34
+
35
+ 1. 改 `<%= it.standards.manifestPath %>` 中的版本(或让包管理器自动 bump)
36
+ 2. 同 PR 改本文件 frontmatter 的对应行;这是**双写**有意为之,让 PR review 必须看到版本变化
37
+ 3. CI 中 `governance-lint` 验证两边一致;不一致就阻断
38
+ 4. PR 描述中说明:升级动机(功能 / 安全 / 性能)+ 兼容性影响
39
+
40
+ ## 何时**不**钉
41
+
42
+ - 仅开发期使用的 lint / format / 测试工具:可以放在 `devDependencies` 但**不**进本表
43
+ - 容器基础镜像版本:在 `deploy/Dockerfile` 里钉,本表不重复
44
+ - 系统包(apt / yum):交给运行时镜像维护者
45
+
46
+ ## 关联
47
+
48
+ - 编码规范:[coding-style-<%= it.standards.language %>.md](coding-style-<%= it.standards.language %>.md)
49
+ - 依赖安全:[../governance/security.md](../governance/security.md)
50
+ - 升级流程模板:参考最近的 PR 历史 `git log --grep="bump"`