@teamix-evo/mcp 0.3.0 → 0.4.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.
- package/README.md +1 -1
- package/dist/cli.js +225 -178
- package/dist/cli.js.map +1 -1
- package/dist/data/adr/0001-three-layer-alignment.md +54 -0
- package/dist/data/adr/0002-package-naming.md +50 -0
- package/dist/data/adr/0003-update-strategy-tri-state.md +62 -0
- package/dist/data/adr/0004-cli-command-structure.md +61 -0
- package/dist/data/adr/0005-ui-no-variant.md +67 -0
- package/dist/data/adr/0006-ui-upgrade-no-baseline.md +67 -0
- package/dist/data/adr/0007-governance-docs-at-root.md +62 -0
- package/dist/data/adr/0008-eslint-visual-rules-warn-baseline.md +110 -0
- package/dist/data/adr/0009-registry-mcp-protocol-layer.md +87 -0
- package/dist/data/adr/0010-design-default-and-variants.md +319 -0
- package/dist/data/adr/0011-mcp-single-package-multi-group.md +169 -0
- package/dist/data/adr/0012-lint-shared-core.md +215 -0
- package/dist/data/adr/0013-skills-source-mirror.md +154 -0
- package/dist/data/adr/0014-ui-biz-ui-templates-tier.md +274 -0
- package/dist/data/adr/0015-skill-description-trigger-contract.md +122 -0
- package/dist/data/adr/0016-design-md-brand-charter.md +93 -0
- package/dist/data/adr/0017-mcp-design-group.md +107 -0
- package/dist/data/adr/0018-ai-context-routing.md +112 -0
- package/dist/data/adr/0019-project-upgrade-flow.md +156 -0
- package/dist/data/adr/0020-design-to-tokens-skill-fusion.md +139 -0
- package/dist/data/adr/0021-semantic-color-api-unification.md +99 -0
- package/dist/data/adr/0022-preferences-css-boundary.md +75 -0
- package/dist/data/adr/0023-cursor-pointer-explicit-in-component-source.md +70 -0
- package/dist/data/adr/0024-scoped-css-radix-state-conflict.md +99 -0
- package/dist/data/adr/0025-component-props-explicit-declaration.md +144 -0
- package/dist/data/adr/0026-component-level-token-alias.md +107 -0
- package/dist/data/adr/0027-component-visual-token-alignment.md +127 -0
- package/dist/data/adr/0028-ui-component-categorization.md +111 -0
- package/dist/data/adr/0029-input-split-and-prefix-suffix-removal.md +62 -0
- package/dist/data/adr/0030-skill-uni-manager-uplift.md +56 -0
- package/dist/data/adr/0031-skill-templates-decoupling.md +77 -0
- package/dist/data/adr/0032-opentrek-v4.1-brand-token-alignment.md +129 -0
- package/dist/data/adr/0033-entry-skill-global-only-scope.md +64 -0
- package/dist/data/adr/0034-skills-cli-verb-alignment.md +61 -0
- package/dist/data/adr/0035-skills-update-scope-and-lock-gates.md +69 -0
- package/dist/data/adr/README.md +75 -0
- package/dist/data/adr/_template.md +36 -0
- package/dist/index.d.ts +64 -59
- package/dist/index.js +232 -186
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# 0012. ESLint / Stylelint 双发布包共享 workspace 内部内核 `@teamix-evo/lint-core`
|
|
2
|
+
|
|
3
|
+
- **Status**: Accepted (2026-05-18) → **完整落地 2026-05-19**(eslint-config + stylelint-config 双消费方接入完成,共享内核闭环成立;详见文末 §Implementation 落地记录)
|
|
4
|
+
- **Date**: 2026-05-18
|
|
5
|
+
- **Region**: 0100–0999 协议与工具
|
|
6
|
+
- **Related ADR**: [0008 ESLint 视觉规则 warn 基线](0008-eslint-visual-rules-warn-baseline.md)
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
[ADR 0008](0008-eslint-visual-rules-warn-baseline.md) 落地 6 条 ESLint 规则({% raw %}`no-color-literal` / `no-arbitrary-tw-value` / `no-raw-color-scale` / `no-large-radius` / `no-relative-ui-import` / `icon-from-lucide`{% endraw %})后,[PLAN §12.5 v0.6](../../PLAN.md#125-v06--闸层补强--协议层雏形12-18-人日) 规划新建 `packages/stylelint-config/` 把同样的 token-discipline 推到 CSS 层。
|
|
11
|
+
|
|
12
|
+
两侧规则的**领域知识 80% 重合**:
|
|
13
|
+
|
|
14
|
+
| 共享的"原料" | ESLint 用法 | Stylelint 用法 |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| 颜色字面量正则(hex / rgb / hsl / oklch / oklab / hwb) | 扫 JSX `className`、`style` 属性、模板字符串 | 扫 CSS `Declaration` 的 `value` |
|
|
17
|
+
| 语义 token 白名单 | 排除 `bg-background` / `text-foreground` 等合法 className | 排除 `var(--background)` 等合法 var() 引用 |
|
|
18
|
+
| 圆角档位(`['none','sm','md','lg','full']`) | 检查 Tailwind 类 `rounded-3xl` 越界 | 检查 CSS `border-radius: 24px` 越界 |
|
|
19
|
+
| Tailwind arbitrary value 解析(`bg-[#fff]` / `mt-[7px]`) | 直接禁用 | n/a(CSS 侧由 `no-color-literal` 等覆盖) |
|
|
20
|
+
| 错误文案 / 恢复指南 | 通过 ESLint `messages` 输出 | 通过 Stylelint `messages` 输出 |
|
|
21
|
+
|
|
22
|
+
如果各自在两个包里**复制粘贴维护**,后果是确定的:
|
|
23
|
+
|
|
24
|
+
1. **token 白名单**(语义 token 名)改一处,另一边漏改 → JSX 通过、CSS 报错(或反之)
|
|
25
|
+
2. 报错文案不一致 → 同一类违规在 IDE 红波浪 / lint 报告里看到两套话术
|
|
26
|
+
3. 测试 fixtures 重复 → 改算法时维护两份用例
|
|
27
|
+
4. **P2 single source 实质塌陷** —— 这是仓内最容易漂移的点
|
|
28
|
+
|
|
29
|
+
但如果合成一个包同时输出 ESLint preset + Stylelint preset:
|
|
30
|
+
|
|
31
|
+
1. peer dep 冲突:eslint v9 与 stylelint v16 / postcss 的依赖链交错
|
|
32
|
+
2. 业务侧装机时被迫装入不需要的另一边(只用 React 不写 CSS 的项目也要拖 stylelint 进来)
|
|
33
|
+
3. 违反行业惯例:`eslint-config-airbnb` / `stylelint-config-standard` 业界都是分包,业务侧 `extends` 一眼能看出工具
|
|
34
|
+
|
|
35
|
+
## Options Considered
|
|
36
|
+
|
|
37
|
+
| 选项 | 优点 | 缺点 |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| ① 两包零共享(各自维护规则) | 简单;peer 解耦 | token 白名单 / 文案 / 算法跨包漂移,违反 P2 |
|
|
40
|
+
| ② 单包同时发 ESLint + Stylelint preset | 单点维护 | peer 冲突;业务侧装机被迫绑双工具;违反行业惯例 |
|
|
41
|
+
| **③ 双发布包 + workspace 内部 `@teamix-evo/lint-core`(本决策)** | token 白名单 / matcher / 文案单点维护;peer 解耦;符合行业惯例;消费方仅装需要的那一侧 | 需要工程约束:lint-core **不发布** + tsup `bundle: true` 把 lint-core 内联进发布产物 |
|
|
42
|
+
|
|
43
|
+
## Decision
|
|
44
|
+
|
|
45
|
+
### 1. 包形态(三个包)
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
packages/lint-core/ # workspace internal,不发布 npm
|
|
49
|
+
└── src/
|
|
50
|
+
├── matchers/
|
|
51
|
+
│ ├── color-literals.ts # 纯函数:matchHex(s) / matchRgb(s) / matchOklch(s) ...
|
|
52
|
+
│ ├── tailwind-arbitrary.ts # 解析 bg-[#fff] / mt-[7px] / rounded-[12px]
|
|
53
|
+
│ ├── radius-class.ts # rounded-{none|sm|md|lg|full|<arbitrary>}
|
|
54
|
+
│ └── icon-import.ts # 从 import source 判断是否 lucide-react
|
|
55
|
+
├── data/
|
|
56
|
+
│ ├── token-allowlist.ts # 允许的语义 token 名(--background / --foreground / ...)
|
|
57
|
+
│ ├── radius-tiers.ts # ['none','sm','md','lg','full']
|
|
58
|
+
│ └── motion-tiers.ts
|
|
59
|
+
├── docs/
|
|
60
|
+
│ └── error-templates.ts # 共享报错文案与恢复指南(两侧一致)
|
|
61
|
+
└── index.ts # 统一导出 matcher / data / docs
|
|
62
|
+
|
|
63
|
+
packages/eslint-config/ # 发布:@teamix-evo/eslint-config
|
|
64
|
+
├── deps: @teamix-evo/lint-core (workspace:*)
|
|
65
|
+
└── src/rules/
|
|
66
|
+
├── no-color-literal.ts # 包成 ESLint rule(读 lint-core 的 matcher)
|
|
67
|
+
├── no-raw-color-scale.ts
|
|
68
|
+
└── ...
|
|
69
|
+
|
|
70
|
+
packages/stylelint-config/ # v0.7 落地:@teamix-evo/stylelint-config
|
|
71
|
+
├── deps: @teamix-evo/lint-core (workspace:*)
|
|
72
|
+
└── src/rules/
|
|
73
|
+
├── no-color-literal.ts # 包成 Stylelint rule(读 lint-core 的 matcher)
|
|
74
|
+
└── ...
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2. 发布形态:lint-core **不发布**,内联打包进两个 config 包
|
|
78
|
+
|
|
79
|
+
`lint-core` 的所有导出**仅纯函数 + 数据 + 文案**,无 ESLint / Stylelint 运行时依赖。两个 config 包通过 tsup `bundle: true` 把 lint-core 代码内联进发布产物:
|
|
80
|
+
|
|
81
|
+
| 包 | npm 看得见 | 业务侧装机时 |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| `@teamix-evo/eslint-config` | ✅ 公开发布 | `npm install -D` |
|
|
84
|
+
| `@teamix-evo/stylelint-config` | ✅ 公开发布(v0.7) | `npm install -D` |
|
|
85
|
+
| `@teamix-evo/lint-core` | ❌ `private: true`,不发 | 业务侧看不见,代码已内联 |
|
|
86
|
+
|
|
87
|
+
业务侧 `node_modules` 里**只看到** `@teamix-evo/eslint-config` 与 `@teamix-evo/stylelint-config`;`@teamix-evo/lint-core` 不出现 —— 避免"那是什么?"的认知负担。
|
|
88
|
+
|
|
89
|
+
### 3. lint-core 的 API 形态(纯函数 + 数据)
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// packages/lint-core/src/matchers/color-literals.ts
|
|
93
|
+
export function matchHex(value: string): { match: string; index: number } | null { /* ... */ }
|
|
94
|
+
export function matchRgb(value: string): { match: string; index: number } | null { /* ... */ }
|
|
95
|
+
export function matchOklch(value: string): { match: string; index: number } | null { /* ... */ }
|
|
96
|
+
export function matchAnyColorLiteral(value: string): MatchResult | null {
|
|
97
|
+
return matchHex(value) ?? matchRgb(value) ?? matchHsl(value) ?? matchOklch(value) ?? matchOklab(value) ?? matchHwb(value);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// packages/lint-core/src/data/token-allowlist.ts
|
|
101
|
+
export const SEMANTIC_TOKEN_ALLOWLIST: ReadonlyArray<string> = [
|
|
102
|
+
'background', 'foreground', 'primary', 'primary-foreground',
|
|
103
|
+
'secondary', 'secondary-foreground', 'muted', 'muted-foreground',
|
|
104
|
+
'accent', 'accent-foreground', 'destructive', 'destructive-foreground',
|
|
105
|
+
'border', 'input', 'ring',
|
|
106
|
+
// v0.6 ADR 0008 还款新增:
|
|
107
|
+
'success', 'warning', 'info',
|
|
108
|
+
// ...
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
// packages/lint-core/src/docs/error-templates.ts
|
|
112
|
+
export const errorColorLiteral = (literal: string, suggestion?: string) => ({
|
|
113
|
+
zh: `禁止颜色字面量 \`${literal}\`。请改用语义 token...${suggestion ? '建议:' + suggestion : ''}`,
|
|
114
|
+
en: `Color literal \`${literal}\` is forbidden. Use semantic tokens...`,
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
ESLint / Stylelint rule 各自包装:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// packages/eslint-config/src/rules/no-color-literal.ts
|
|
122
|
+
import { matchAnyColorLiteral } from '@teamix-evo/lint-core';
|
|
123
|
+
import { errorColorLiteral } from '@teamix-evo/lint-core';
|
|
124
|
+
|
|
125
|
+
export default createRule({
|
|
126
|
+
name: 'no-color-literal',
|
|
127
|
+
meta: { type: 'problem', messages: { violation: errorColorLiteral('').zh } },
|
|
128
|
+
create(context) {
|
|
129
|
+
return {
|
|
130
|
+
Literal(node) {
|
|
131
|
+
const m = matchAnyColorLiteral(String(node.value));
|
|
132
|
+
if (m) context.report({ node, messageId: 'violation' });
|
|
133
|
+
},
|
|
134
|
+
// ...
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// packages/stylelint-config/src/rules/no-color-literal.ts
|
|
140
|
+
import stylelint from 'stylelint';
|
|
141
|
+
import { matchAnyColorLiteral, errorColorLiteral } from '@teamix-evo/lint-core';
|
|
142
|
+
|
|
143
|
+
export default stylelint.createPlugin('@teamix-evo/no-color-literal', () => (root, result) => {
|
|
144
|
+
root.walkDecls((decl) => {
|
|
145
|
+
const m = matchAnyColorLiteral(decl.value);
|
|
146
|
+
if (m) stylelint.utils.report({ message: errorColorLiteral(m.match).zh, node: decl, result });
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### 4. 测试策略
|
|
152
|
+
|
|
153
|
+
| 层 | 测试位置 | 内容 |
|
|
154
|
+
| --- | --- | --- |
|
|
155
|
+
| matcher 纯函数 | `packages/lint-core/tests/` | 单元测试覆盖每个 matcher 的正反例(不依赖 ESLint / Stylelint rule-tester) |
|
|
156
|
+
| ESLint rule 包装 | `packages/eslint-config/tests/` | 用 `@typescript-eslint/rule-tester`(已就位) |
|
|
157
|
+
| Stylelint rule 包装 | `packages/stylelint-config/tests/`(v0.7) | 用 `jest-preset-stylelint` 或 stylelint 内置 testRule |
|
|
158
|
+
|
|
159
|
+
### 5. v0.6 与 v0.7 的实施切分
|
|
160
|
+
|
|
161
|
+
- **v0.6**:抽 `lint-core`、改造 `eslint-config` 改用 lint-core,**Stylelint 不在 v0.6 上线**(避免本次重构面太大)
|
|
162
|
+
- **v0.7**:落地 `stylelint-config`(v0.7 协议层升级阶段做)
|
|
163
|
+
|
|
164
|
+
## Consequences
|
|
165
|
+
|
|
166
|
+
- **Positive**:
|
|
167
|
+
- **token 白名单单点维护** —— 改 `data/token-allowlist.ts` 一行,ESLint / Stylelint 同步生效;ADR 0008 v0.6 还款新增的 success / warning / info 白名单在此唯一登记
|
|
168
|
+
- **报错文案两侧一致** —— `bg-[#fff]` 在 JSX 和 CSS 里报的都是同一段恢复指南
|
|
169
|
+
- **测试用例可在 lint-core 单测纯函数** —— 不依赖任何 lint 框架的 rule-tester,启动快
|
|
170
|
+
- **行业惯例符合** —— 业务侧 `extends: '@teamix-evo/eslint-config'` 一眼可读,与 airbnb / standard 同形
|
|
171
|
+
- **业务侧解耦** —— 只用 React 不写 CSS 的项目仅装 eslint-config,不被迫拖 stylelint
|
|
172
|
+
- **Negative**:
|
|
173
|
+
- 需要工程约束:lint-core 必须 `private: true`,且两个 config 的 tsup config 必须开启 `bundle: true`(否则发布产物会出现 `@teamix-evo/lint-core` 找不到)—— 用 CI 脚本校验
|
|
174
|
+
- workspace 内部包数量 +1 —— pnpm-workspace.yaml 多一行,但视觉成本可忽略
|
|
175
|
+
- **Trade-off**:
|
|
176
|
+
- 用"工程约束(private + bundle)"换"双侧规则共享原料 + 业界惯例符合 + 装机解耦"
|
|
177
|
+
|
|
178
|
+
## Implementation note
|
|
179
|
+
|
|
180
|
+
落地工作量(v0.6,共 ~2 人日,Stylelint 实际规则推到 v0.7):
|
|
181
|
+
|
|
182
|
+
1. 写本 ADR(0.2)
|
|
183
|
+
2. `packages/lint-core/` workspace 包基建 + 抽出 matcher / data / docs(0.8)
|
|
184
|
+
3. 改造 `packages/eslint-config/` 改用 lint-core(0.5)
|
|
185
|
+
4. tsup config 加 `bundle: true` + CI 校验脚本(0.3)
|
|
186
|
+
5. ADR 0008 v0.6 还款的 token 新增同步登记到 `lint-core/data/token-allowlist.ts`(0.2)
|
|
187
|
+
|
|
188
|
+
### v0.7 stylelint-config 落地记录(2026-05-19)
|
|
189
|
+
|
|
190
|
+
`packages/stylelint-config/` 上线,镜像 eslint-config 结构。
|
|
191
|
+
|
|
192
|
+
**3 条规则**:
|
|
193
|
+
|
|
194
|
+
- `no-color-literal` — CSS Declaration 值禁 hex / rgb / hsl / oklch / oklab / hwb;复用 lint-core `matchHex` + `matchColorFunction`,**与 ESLint 同源同正则**。
|
|
195
|
+
- `prefer-token-radius` — `border-radius` 系列属性必须用 `var(--radius*)` token / `0` / 关键字 / `calc(var(...))`;原 px / rem / em / 百分比阻塞;新增 lint-core `analyseBorderRadiusValue` matcher 处理 composite values。
|
|
196
|
+
- `no-unknown-token` — `var(--xxx)` 引用必须在 `SEMANTIC_TOKEN_ALLOWLIST` 内;自动 strip Tailwind v4 `--color-*` 前缀;built-in `--radix-*` 透传;secondary option `allowedPrefixes` 让消费方加项目级前缀(如 `['my-app-']`)。新增 lint-core `extractVarReferences` matcher。
|
|
197
|
+
|
|
198
|
+
**lint-core 同步**:加 2 个 CSS matcher 文件(`css-var-refs.ts` + `css-radius.ts`)+ 11 单测(总 43);`index.ts` export 扩展;build 后 dist 体积无显著膨胀。
|
|
199
|
+
|
|
200
|
+
**双 preset**:`presets/ui` + `presets/consumer`,同等规则;均带 `ignoreFiles` 排除 `tokens.{generated,theme,overrides}.css`(token 定义文件本身就是写 raw 值的合法位置)。
|
|
201
|
+
|
|
202
|
+
**bundle 验证**:`pnpm --filter @teamix-evo/stylelint-config check:bundled` ✅ 通过 — `@teamix-evo/lint-core` 完全内联进 dist/,消费方 node_modules 不可见。
|
|
203
|
+
|
|
204
|
+
**测试**:24 个 stylelint-config 单测(`runRule(code, ruleName, options)` helper 包装 `stylelint.lint` API 直跑);385 全仓 tests / 13 包 typecheck 全绿。
|
|
205
|
+
|
|
206
|
+
**lint-core 闭环成立**:eslint-config + stylelint-config 共享 5 matcher(matchHex / matchColorFunction / extractVarReferences / analyseBorderRadiusValue / SEMANTIC_TOKEN_ALLOWLIST)+ 4 数据集。任何后续新增 token 只需改 `lint-core/data/token-allowlist.ts` 一处,**两侧规则同步生效**。
|
|
207
|
+
|
|
208
|
+
**遗留**:消费方装机后真实跑一次 stylelint 看噪音,留 v0.8 业务接入时跑通(packages/docs 暂未试装)。
|
|
209
|
+
|
|
210
|
+
## Source
|
|
211
|
+
|
|
212
|
+
- [ADR 0008](0008-eslint-visual-rules-warn-baseline.md):6 条 ESLint 规则的现状,本 ADR 抽其内核
|
|
213
|
+
- [PLAN §12.5 v0.6](../../PLAN.md#125-v06--闸层补强--协议层雏形12-18-人日):"新建 `packages/stylelint-config/` + token 字面量禁用规则"
|
|
214
|
+
- 2026-05-18 用户讨论:["lint-core 输出 eslint 和 stylelint 是怎么输出的?"](rules-as-data 内核 + 双发布点)
|
|
215
|
+
- [`docs/principles.md` P2 / P5](../principles.md):P2 single source / P5 machine knowledge > retrievable
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# 0013. Skills 采用 source-mirror 模型:`.teamix-evo/skills/` 为源,IDE 路径为工具镜像
|
|
2
|
+
|
|
3
|
+
- **Status**: Proposed
|
|
4
|
+
- **Date**: 2026-05-18
|
|
5
|
+
- **Region**: 0100–0999 协议与工具
|
|
6
|
+
- **Related ADR**: [0003 资源升级三态语义](0003-update-strategy-tri-state.md)
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
[Phase 7](../../PLAN.md#phase-7--teamix-evoskills-包--双-ide-适配23-人日) 的 IdeAdapter 把 SKILL.md 安装到业务侧两个 IDE 路径:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
.qoder/skills/<id>/SKILL.md # Qoder 强制读这个路径
|
|
14
|
+
.claude/skills/<id>/SKILL.md # Claude Code 强制读这个路径
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
[v0.7 路线图](../../PLAN.md#126-v07--协议层升级15-22-人日)还要加:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
.cursor/rules/<id>.mdc # Cursor 强制读这个路径(且 frontmatter 格式略不同)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
IDE 的入口路径**是写死的协议**,不可以在工具侧改成"统一一个位置"。如果只往 IDE 路径写文件 —— 这是当前实际行为 —— **P2 single source 立刻塌陷**:
|
|
24
|
+
|
|
25
|
+
| 痛点 | 后果 |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| 同 SKILL.md 物理重复 2-N 份 | 用户改 `.qoder/skills/foo/SKILL.md` 一份,`.claude` 那份还是旧的,行为漂移 |
|
|
28
|
+
| `teamix-evo skills upgrade` 无法判断"哪份是工具最近一次产出 / 哪份是用户改过" | 升级算法无锚点 |
|
|
29
|
+
| 卸载流程要遍历每个 IDE 路径删 | 漏一个 IDE 就遗留垃圾 |
|
|
30
|
+
| `.cursor/rules/*.mdc` 与 `.claude/skills/*/SKILL.md` 是两种格式 | 没有唯一可信"原文",frontmatter 转译只能靠 ad-hoc 比较 |
|
|
31
|
+
| AI 上下文 / MCP server / AGENTS.md 引用谁? | 选 `.qoder` 不公平,选 `.claude` 也不公平,选哪个都绑死 IDE |
|
|
32
|
+
|
|
33
|
+
ADR 0003 的三态语义里 `regenerable` 表达的是"工具拥有,用户不应直接改";IDE 路径下的文件**应当全部是 regenerable**——它们是工具产出的镜像。**真正的源,应该在工具自己控制的目录里**。
|
|
34
|
+
|
|
35
|
+
## Options Considered
|
|
36
|
+
|
|
37
|
+
| 选项 | 优点 | 缺点 |
|
|
38
|
+
| --- | --- | --- |
|
|
39
|
+
| ① 直接写 IDE 路径,无源(当前行为) | 实现最简 | 副本物理重复;升级 / 卸载 / AI 引用 / 漂移检测全都缺锚点;违反 P2 |
|
|
40
|
+
| ② 用 OS 符号链接(symlink)统一指向单源 | 单点可控 | Windows symlink 不稳;权限 / 跨盘 / 反斜杠路径多个角点;大多数 git / IDE 对 symlink 行为不一致 |
|
|
41
|
+
| ③ Hard link | 物理一致 | git 不跟踪 hard link;跨盘失败;实际效果不可预期 |
|
|
42
|
+
| **④ source-mirror:`.teamix-evo/skills/` 为唯一源,工具按需镜像到 IDE 路径(本决策)** | 跨平台稳定;工具自己掌控镜像生命周期;升级 / 卸载 / 漂移检测都有锚点;AI 上下文引用源 | 物理上文件仍是 N+1 份(1 源 + N IDE 镜像),但**逻辑上单一来源** |
|
|
43
|
+
|
|
44
|
+
## Decision
|
|
45
|
+
|
|
46
|
+
### 1. 路径拓扑
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
packages/skills/<id>/SKILL.md
|
|
50
|
+
│ teamix-evo skills add <id>
|
|
51
|
+
▼
|
|
52
|
+
.teamix-evo/skills/<id>/SKILL.md ← 源(consumer 拥有的工具产出,regenerable)
|
|
53
|
+
│ teamix-evo skills sync(隐式 / 装机时 / IDE 切换时)
|
|
54
|
+
▼
|
|
55
|
+
.qoder/skills/<id>/SKILL.md ← IDE 镜像 1(regenerable)
|
|
56
|
+
.claude/skills/<id>/SKILL.md ← IDE 镜像 2(regenerable)
|
|
57
|
+
.cursor/rules/<id>.mdc ← IDE 镜像 3(transformed,frontmatter 适配)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. 路径语义对照表
|
|
61
|
+
|
|
62
|
+
| 路径 | 谁拥有 | updateStrategy | 用户可改吗 | git 是否跟踪 |
|
|
63
|
+
| --- | --- | --- | --- | --- |
|
|
64
|
+
| `.teamix-evo/skills/<id>/SKILL.md` | 工具(consumer 侧的源) | regenerable | ❌(改了被覆盖) | ✅(让团队成员同步装机) |
|
|
65
|
+
| `.qoder/skills/<id>/SKILL.md` | 工具(IDE 镜像) | regenerable | ❌ | ⚠️ 由消费方决定;推荐 .gitignore(从源 sync 即可) |
|
|
66
|
+
| `.claude/skills/<id>/SKILL.md` | 工具(IDE 镜像) | regenerable | ❌ | ⚠️ 同上 |
|
|
67
|
+
| `.cursor/rules/<id>.mdc` | 工具(IDE 镜像 + frontmatter 转译) | regenerable | ❌ | ⚠️ 同上 |
|
|
68
|
+
|
|
69
|
+
> `.teamix-evo/skills/` 进 git;IDE 路径推荐放 `.gitignore`(因为可从源完全重建)。这一推荐写入 `create-teamix-evo` 的 base 模板的 `.gitignore`(v0.6 落地)。
|
|
70
|
+
|
|
71
|
+
### 3. `.teamix-evo/skills/` 的实质价值
|
|
72
|
+
|
|
73
|
+
1. **diff 基准**:升级时对比 `packages/skills/<id>/`(上游) 与 `.teamix-evo/skills/<id>/`(consumer 侧已装版本),判断有无变化(IDE 镜像可能已被异步更新或被某 IDE 写工具误改,不可靠)
|
|
74
|
+
2. **lock 信息**:`.teamix-evo/skills/manifest.lock.json` 记录"装了哪些 skill / 哪个版本 / 装到哪些 IDE"
|
|
75
|
+
3. **统一卸载**:`teamix-evo skills remove <id>` 知道要从哪几个 IDE 路径删干净
|
|
76
|
+
4. **AI 上下文锚点**:MCP server / AGENTS.md / `teamix-evo skills` 子命令引用一个稳定路径,不绑定特定 IDE
|
|
77
|
+
5. **无 IDE 装机也合法**:消费方装了 skill 但还没接 IDE,源仍存在于 `.teamix-evo/skills/`
|
|
78
|
+
|
|
79
|
+
### 4. 命令族
|
|
80
|
+
|
|
81
|
+
| 命令 | 行为 |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| `teamix-evo skills add <id>` | 1) 复制 `packages/skills/<id>/` → `.teamix-evo/skills/<id>/`;2) 调用 `sync` 镜像到 IDE |
|
|
84
|
+
| `teamix-evo skills sync` | 把 `.teamix-evo/skills/<id>/` 重新镜像到所有已配置的 IDE 路径(检测 `.qoder/` `.claude/` `.cursor/` 是否存在) |
|
|
85
|
+
| `teamix-evo skills upgrade <id>` | diff 上游 vs consumer 源,语义合并(无 baseline,见 ADR 0006 同源思想),写回源 + sync |
|
|
86
|
+
| `teamix-evo skills remove <id>` | 从源 + 所有 IDE 镜像删干净 |
|
|
87
|
+
| `teamix-evo skills list` | 读 `.teamix-evo/skills/manifest.lock.json` 输出 |
|
|
88
|
+
| `teamix-evo skills doctor` | 检测 IDE 镜像与源是否漂移(被人手改);提示 `sync` 恢复 |
|
|
89
|
+
|
|
90
|
+
### 5. `manifest.lock.json` Schema
|
|
91
|
+
|
|
92
|
+
```jsonc
|
|
93
|
+
// .teamix-evo/skills/manifest.lock.json
|
|
94
|
+
{
|
|
95
|
+
"skills": {
|
|
96
|
+
"teamix-evo-manage": {
|
|
97
|
+
"version": "0.5.0",
|
|
98
|
+
"from": "@teamix-evo/skills",
|
|
99
|
+
"installedAt": "2026-05-18T...",
|
|
100
|
+
"mirroredTo": [
|
|
101
|
+
".qoder/skills/teamix-evo-manage/",
|
|
102
|
+
".claude/skills/teamix-evo-manage/"
|
|
103
|
+
// 若装机时检测到 .cursor 也存在则加 ".cursor/rules/teamix-evo-manage.mdc"
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 6. Cursor frontmatter 转译(无固定版本号,业务需要时再做 — 2026-05-19 用户决策)
|
|
111
|
+
|
|
112
|
+
`.cursor/rules/<id>.mdc` 与 SKILL.md 格式不同:Cursor 的 `.mdc` 格式有特定 frontmatter(`description` / `globs` / `alwaysApply`)。**当 teamix-evo 决定支持 Cursor 时**,`sync` 命令对 `.cursor/` 镜像走转译流程:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
SKILL.md (source, 通用 frontmatter) → MDC transformer → .cursor/rules/<id>.mdc
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**当前状态(2026-05-19)**:teamix-evo 所有包**仅支持 Claude + Qoder 双 IDE**;Cursor 适配**无固定版本号**,推迟到业务真有 Cursor 用户提需求时再起新 ADR 演进(届时落地的内容:① `SkillIdeSchema` 加 `'cursor'` 枚举 ② `SkillsLockSchema` 的 `mirroredTo` 自然支持 ③ MDC transformer 设计 ④ Cursor IdeAdapter ⑤ `create-teamix-evo` `.gitignore` 加 `.cursor/rules/`)。
|
|
119
|
+
|
|
120
|
+
本 ADR 仅锁定"Cursor 镜像由 sync 命令以转译形式产出,不是 raw copy"这一**预留原则** —— 真要落地时按本节模式扩展即可,不需要重新设计源-镜像架构。
|
|
121
|
+
|
|
122
|
+
## Consequences
|
|
123
|
+
|
|
124
|
+
- **Positive**:
|
|
125
|
+
- **P2 single source 真正成立** —— `.teamix-evo/skills/` 是唯一可信源,所有 IDE 镜像皆派生
|
|
126
|
+
- **跨平台稳定** —— 不依赖 symlink / hard link,纯文件复制,Windows / mac / Linux 行为一致
|
|
127
|
+
- **升级 / 卸载 / 漂移检测都有锚点** —— 工具侧拥有完整生命周期信息
|
|
128
|
+
- **AI 引用稳定路径** —— AGENTS.md / MCP server / skill MD 互引时,引用 `.teamix-evo/skills/`,不绑 IDE
|
|
129
|
+
- **Cursor 装机零额外成本** —— 同一源转译出 `.mdc`,新增 IDE 加一个 transformer 即可
|
|
130
|
+
- **Negative**:
|
|
131
|
+
- 物理上文件 N+1 份 —— 但**逻辑上单一**,通过 `sync` / `doctor` 维护一致性
|
|
132
|
+
- 业务侧 git 策略需明示 —— `.teamix-evo/skills/` 入库 + IDE 路径 .gitignore;`create-teamix-evo` 生成的 `.gitignore` 模板默认已配
|
|
133
|
+
- 增加 `sync` / `doctor` 两个命令 —— CLI 命令族略胖,但语义清晰
|
|
134
|
+
- **Trade-off**:
|
|
135
|
+
- 用"sync 显式调用 + git 策略约定"换"P2 真正成立 + 跨平台稳定 + 工具掌控生命周期 + Cursor 等新 IDE 接入零基础设施代价"
|
|
136
|
+
|
|
137
|
+
## Implementation note
|
|
138
|
+
|
|
139
|
+
落地工作量(v0.6,共 ~1.5 人日;`.cursor/` 镜像转译推到 v0.7):
|
|
140
|
+
|
|
141
|
+
1. 写本 ADR(0.2)
|
|
142
|
+
2. `packages/cli/` 修改 `runSkillsAdd`:写入 `.teamix-evo/skills/` 后调 `sync`(0.3)
|
|
143
|
+
3. 新增 `teamix-evo skills sync` 命令(0.3)
|
|
144
|
+
4. 新增 `teamix-evo skills doctor` 命令(漂移检测)(0.3)
|
|
145
|
+
5. `manifest.lock.json` schema + Zod + 单测(0.2)
|
|
146
|
+
6. `create-teamix-evo` base 模板 `.gitignore` 加 `.qoder/skills/` / `.claude/skills/` /(预留)`.cursor/rules/`(0.1)
|
|
147
|
+
7. 文档:`packages/skills/AGENTS.md` + `packages/cli/README.md`(0.1)
|
|
148
|
+
|
|
149
|
+
## Source
|
|
150
|
+
|
|
151
|
+
- [PLAN §7 Phase 7](../../PLAN.md#phase-7--teamix-evoskills-包--双-ide-适配23-人日):skills 包 + 双 IDE IdeAdapter
|
|
152
|
+
- 2026-05-18 用户讨论:[".teamix-evo/skills 跟 .qoder/skills 等是什么区别?"](source-mirror 模型推演)
|
|
153
|
+
- [`docs/principles.md` P2](../principles.md#p2--single-source-of-truth--no-copies-no-caches):副本是漂移之源;本 ADR 是该原则在 skills 装机层的具象
|
|
154
|
+
- [ADR 0006](0006-ui-upgrade-no-baseline.md):"无 baseline 语义合并"是 ui 侧的同型动作;skill upgrade 借鉴
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# 0014. UI 包分三层:`@teamix-evo/ui` / `@teamix-evo/biz-ui` / `@teamix-evo/templates`
|
|
2
|
+
|
|
3
|
+
- **Status**: Proposed
|
|
4
|
+
- **Date**: 2026-05-18
|
|
5
|
+
- **Region**: 0100–0999 协议与工具
|
|
6
|
+
- **Related ADR**: [0001 三层对齐](0001-three-layer-alignment.md) / [0005 UI 不分 variant](0005-ui-no-variant.md)(本 ADR 与之对照:ui 不分 variant,但 biz-ui 必须分) / [0010 design 默认+变体](0010-design-default-and-variants.md)
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
当前 [`packages/ui/`](../../packages/ui/) 集中了 89 个组件,边界是 [P4 / ADR 0001](0001-three-layer-alignment.md) 锁定的"shadcn ∪ antd 合集"。但有两类需求逐步浮现:
|
|
11
|
+
|
|
12
|
+
### a) 业务组件(business components)
|
|
13
|
+
|
|
14
|
+
例如 `OrgPicker`、`ApprovalCard`、`AuditLogTable`、`TenantSwitcher`。它们看起来"通用"(几乎所有 B 端产品都需要),但**实现都绑业务规则**:
|
|
15
|
+
|
|
16
|
+
- 租户列表 API / 用户对象结构 / 登出流程 / 审批状态机定义
|
|
17
|
+
- 字段语义、提交格式、权限边界
|
|
18
|
+
|
|
19
|
+
把这些塞进 `packages/ui/` 会破坏 [ADR 0005 "UI 不分 variant"](0005-ui-no-variant.md) 的前提 —— ui 之所以不分 variant,是因为它**纯 UI、纯能力、品牌通过 token 注入**;业务组件不满足"品牌通过 token 注入即可"。
|
|
20
|
+
|
|
21
|
+
### b) 页面模板(page templates)
|
|
22
|
+
|
|
23
|
+
例如 `DashboardLayout`、`ListDetailPage`、`SettingsPage`、`FormWizardPage`。它们的特征:
|
|
24
|
+
|
|
25
|
+
- 多 component 组合 + 路由 + 状态(引入 react-router 等 peer dep)
|
|
26
|
+
- 业务侧装机后通常**改成自家的**;`frozen` 默认会阻碍这种用法
|
|
27
|
+
- 其 manifest 含"用了哪些 component"的合成依赖图,是 L3 Patterns 知识的载体
|
|
28
|
+
|
|
29
|
+
塞进 `packages/ui/` 会:
|
|
30
|
+
|
|
31
|
+
- 污染 ui 的依赖盘(reactor-router 等不该是 ui 的 peer)
|
|
32
|
+
- 弱化 ui "shadcn ∪ antd 合集"边界
|
|
33
|
+
- 装机语义混乱(template add 一次装一页面 + 改路由,而 component add 一次装一组件)
|
|
34
|
+
|
|
35
|
+
### 可选边界 — 上一轮讨论中曾考虑
|
|
36
|
+
|
|
37
|
+
> 上一轮曾考虑把业务组件塞进 [ADR 0010](0010-design-default-and-variants.md) 的 `packages/design/variants/<name>/business/`,后被用户在 2026-05-18 讨论中否决:
|
|
38
|
+
>
|
|
39
|
+
> 1. **包的输出类型应该单一** —— design 永远只输出静态知识(MD / JSON / token),代码包(ui / biz-ui / templates)只输出 TSX。一旦 design 输出 TSX,build pipeline / lint / 测试范围 / 发布流程都要分两套
|
|
40
|
+
> 2. **ESLint preset 应用边界** —— `@teamix-evo/eslint-config/presets/ui` 的规则对 biz 组件完全适用(都是 cva / lucide / 语义 token / 无相对路径 import);若 biz 组件在 design 子目录,preset glob 要扩到 design,**design 内部 lint 配置变得不一致**
|
|
41
|
+
> 3. **CLI 命令对称** —— `teamix-evo ui add` / `teamix-evo biz-ui add` / `teamix-evo templates add` 是同形动作,共享源码注入实现;塞 design 会让"design business add" 变成 ui 安装器在 design 命令空间下的伪装
|
|
42
|
+
> 4. **变体语义不混乱** —— design 的变体是"知识变体"(差异在文档/token);biz-ui 的变体是"组件变体"(差异在 TSX 实现)。两者通过名字关联,不应物理同位置
|
|
43
|
+
|
|
44
|
+
## Options Considered
|
|
45
|
+
|
|
46
|
+
| 选项 | 评价 |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| A. 全部塞 `packages/ui/`(component / block / template / business 不分) | 包定义崩塌(P4 边界破坏 + 业务依赖污染);ADR 0005 不分 variant 的前提失效 |
|
|
49
|
+
| B. business 塞 `packages/design/variants/<name>/business/` + templates 塞 ui | design 输出类型不单一(知识 + TSX);ESLint preset 边界崩;CLI 命令不对称 |
|
|
50
|
+
| **C. 三层独立分包(本决策)**:ui(generic)+ biz-ui(variant-aware,业务)+ templates(generic 页面模板) | 包输出类型单一;ESLint preset glob 自然扩展;CLI 命令对称;变体语义清晰 |
|
|
51
|
+
| D. 全部细分(component / block / template / business 各自独立包) | 4 包过度工程;block 与 component 同样 generic 同样规则,无理由分包 |
|
|
52
|
+
|
|
53
|
+
## Decision
|
|
54
|
+
|
|
55
|
+
### 1. 三层四类的最终归属
|
|
56
|
+
|
|
57
|
+
| 粒度 | 例子 | 是否绑业务 | 是否绑变体 | 包归属 |
|
|
58
|
+
| --- | --- | --- | --- | --- |
|
|
59
|
+
| **component** | Button / Input / Dialog / Select | ❌ | ❌ | `@teamix-evo/ui` |
|
|
60
|
+
| **block** | FilterBar / EmptyState / PageHeader / CommandBar | ❌ | ❌ | `@teamix-evo/ui` |
|
|
61
|
+
| **template** | DashboardLayout / ListDetailPage / SettingsPage / FormWizardPage | ❌(layout 不绑业务流程) | ✅(必绑 variant,2026-05-18 用户改动) | `@teamix-evo/templates`(新) |
|
|
62
|
+
| **business** | OrgPicker / ApprovalCard / AuditLogTable / TenantSwitcher | ✅ | ✅(必绑 variant) | `@teamix-evo/biz-ui`(新) |
|
|
63
|
+
|
|
64
|
+
### 2. 包结构
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
packages/ui/ # @teamix-evo/ui — 现状,扩 entry type 到 component+block
|
|
68
|
+
└── src/components/...
|
|
69
|
+
|
|
70
|
+
packages/biz-ui/ # 🆕 @teamix-evo/biz-ui — 业务 ui,变体感知
|
|
71
|
+
└── variants/ # 仅有 variants/(无 default)
|
|
72
|
+
├── _template/ # minimal 模板(同 ADR 0010 风格)
|
|
73
|
+
├── opentrek/
|
|
74
|
+
│ ├── org-picker/
|
|
75
|
+
│ ├── approval-card/
|
|
76
|
+
│ └── manifest.json
|
|
77
|
+
└── uni-manager/ # 空骨架,展示多变体接入路径
|
|
78
|
+
└── manifest.json
|
|
79
|
+
|
|
80
|
+
packages/templates/ # 🆕 @teamix-evo/templates — 页面模板,变体感知(2026-05-18 用户改动)
|
|
81
|
+
└── variants/ # 镜像 biz-ui 形态:仅有 variants/(无 default)
|
|
82
|
+
├── _template/ # minimal 起点
|
|
83
|
+
│ ├── _empty-shell/ # 通用占位页(新变体 fork 时拷过来改)
|
|
84
|
+
│ └── manifest.json
|
|
85
|
+
├── opentrek/
|
|
86
|
+
│ ├── dashboard-layout/
|
|
87
|
+
│ ├── list-detail-page/
|
|
88
|
+
│ ├── form-wizard-page/
|
|
89
|
+
│ ├── settings-page/
|
|
90
|
+
│ └── manifest.json
|
|
91
|
+
└── uni-manager/ # 空骨架
|
|
92
|
+
└── manifest.json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 3. biz-ui **没有 default/**(关键决策)
|
|
96
|
+
|
|
97
|
+
> 上一轮曾考虑给 biz-ui 加 `default/`(放 TenantSwitcher / UserAvatar 等"几乎所有 B 端都有"的组件),用户 2026-05-18 反推:**这违反了边界 — block 已经覆盖了"通用 UI 抽象",带业务的就该绑变体。**
|
|
98
|
+
|
|
99
|
+
- 真正不绑业务的部分(下拉选择 + 头像渲染) → 升级为 `ui` 的 block
|
|
100
|
+
- 带业务规则的部分(租户列表来源 / 切换 API / 登出流程) → 必绑 variant
|
|
101
|
+
|
|
102
|
+
**biz-ui 仅有 `variants/`**;没有 default 时,CLI 装 biz 必须指定变体:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
teamix-evo biz-ui add org-picker --variant opentrek
|
|
106
|
+
# 若 .teamix-evo/design/pack.lock.json 已锁定变体,可省略 --variant
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
边界从而清晰:
|
|
110
|
+
|
|
111
|
+
| 包 | 边界 | 装机时选变体? |
|
|
112
|
+
| --- | --- | --- |
|
|
113
|
+
| `ui` | 纯 UI 能力(component + block),不绑业务,不绑变体(品牌通过 token 注入) | ❌ |
|
|
114
|
+
| `biz-ui` | 业务 UI 实现,**绑业务 + 绑变体** | ✅ |
|
|
115
|
+
| `templates` | 页面骨架,**绑变体**(2026-05-18 改动);slot 模式仍可在变体内使用 | ✅ |
|
|
116
|
+
|
|
117
|
+
### 4. templates 与 biz-ui 同形:variant-aware,镜像 `variants/<name>/` 结构(2026-05-18 用户改动)
|
|
118
|
+
|
|
119
|
+
> 旧版本曾论证"templates 变体无关 + 通过 slot 接 biz-ui"。2026-05-18 用户决定:**templates 也按 variant 产出**,与 biz-ui 同形(`variants/` 仅,无 default)。
|
|
120
|
+
|
|
121
|
+
**理由**:
|
|
122
|
+
|
|
123
|
+
- **变体语义对称**:design / biz-ui / templates 三包通过同一变体名(`opentrek` / `uni-manager`)关联;装机选定变体后,三包**统一按该变体输出**,装机产物一目了然
|
|
124
|
+
- **页面专属合成**:某些 page 在不同变体下是**完全不同的合成**(opentrek 的 `AdminConsoleLayout` 内嵌了变体专属 widget;uni-manager 同名 layout 长完全不一样),变体无关无法表达这种差异
|
|
125
|
+
- **slot 模式仍兼容**:在变体内 page 仍可保留 slot/prop 接 biz-ui — 这是变体内部的实现选择,不影响包级变体感知
|
|
126
|
+
|
|
127
|
+
**取舍**:
|
|
128
|
+
|
|
129
|
+
- 代价:真正"通用"的 page shape(如 `EmptyAppShell` / `BlankAuthLayout`)在多个 variant 间会**物理重复**(各 variant 独立维护一份)
|
|
130
|
+
- 缓解:`_template/` 起 minimal 模板,新变体 fork 时几乎零成本拿到一组通用 page shape 起点;若未来通用 templates 真的负担过重,再考虑加 `default/`(对照 design 形态)
|
|
131
|
+
- 这与 ADR 0005 的"ui 不分 variant"形成对称表达:**ui 纯能力 = 不绑;biz-ui 业务 = 必绑;templates 页面合成 = 必绑**
|
|
132
|
+
|
|
133
|
+
slot 模式示意(在变体内仍可用):
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
// packages/templates/variants/opentrek/list-detail-page/index.tsx
|
|
137
|
+
<ListDetailPage
|
|
138
|
+
filterBar={<MyOrgFilter />} // 业务侧自填 or biz-ui#opentrek 提供
|
|
139
|
+
detail={<MyOrgDetail />}
|
|
140
|
+
list={<MyOrgList />}
|
|
141
|
+
/>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 5. UiEntryType 不加 'business'
|
|
145
|
+
|
|
146
|
+
业务通过**包归属**表达,不需要在 entry type 上区分:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// packages/registry/src/schema/manifest.ts
|
|
150
|
+
export const UiEntryTypeSchema = z.enum([
|
|
151
|
+
'component', // ui 包
|
|
152
|
+
'block', // 🆕 ui 包(扩)
|
|
153
|
+
'template', // 🆕 templates 包
|
|
154
|
+
]);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
biz-ui 的 entry 直接用 `component` 或 `block`(技术上和 ui 同质),只是**包归属不同 + manifest 含 variant 字段**。
|
|
158
|
+
|
|
159
|
+
### 6. biz-ui / templates manifest 增量 — `variant` 字段(2026-05-18 改动:templates 也加)
|
|
160
|
+
|
|
161
|
+
biz-ui 与 templates 的 entry 都必须声明 variant:
|
|
162
|
+
|
|
163
|
+
```jsonc
|
|
164
|
+
// biz-ui 示例
|
|
165
|
+
{
|
|
166
|
+
"name": "org-picker",
|
|
167
|
+
"type": "component",
|
|
168
|
+
"variant": "opentrek", // 必填,与 design 变体名同步
|
|
169
|
+
"files": [/* ... */],
|
|
170
|
+
"dependencies": ["@radix-ui/react-popover"],
|
|
171
|
+
"registryDependencies": ["popover", "input"] // 引用 ui 包的 entries
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// templates 示例(2026-05-18 改动)
|
|
175
|
+
{
|
|
176
|
+
"name": "list-detail-page",
|
|
177
|
+
"type": "template",
|
|
178
|
+
"variant": "opentrek", // 必填,与 design / biz-ui 变体名同步
|
|
179
|
+
"files": [/* ... */],
|
|
180
|
+
"dependencies": ["react-router-dom"],
|
|
181
|
+
"registryDependencies": ["table", "card", "filter-bar"]
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### 7. 软关联 — design `pack.json` 的 `linked.biz-ui` / `linked.templates`(2026-05-18 改动:templates 也加)
|
|
186
|
+
|
|
187
|
+
design `pack.json` 含 `linked.biz-ui` 与 `linked.templates` 字段,装机时校验存在(见 [ADR 0010](0010-design-default-and-variants.md) §7 schema):
|
|
188
|
+
|
|
189
|
+
```jsonc
|
|
190
|
+
// packages/design/variants/opentrek/pack.json
|
|
191
|
+
{
|
|
192
|
+
"name": "opentrek",
|
|
193
|
+
"displayName": "OpenTrek",
|
|
194
|
+
"extends": "default",
|
|
195
|
+
"linked": {
|
|
196
|
+
"biz-ui": "@teamix-evo/biz-ui#opentrek", // 装 design opentrek 时提示同步装
|
|
197
|
+
"templates": "@teamix-evo/templates#opentrek" // 同上
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### 8. CLI 命令族(2026-05-18 改动:templates 加 --variant)
|
|
203
|
+
|
|
204
|
+
| 命令 | 装哪类 | 装到哪 |
|
|
205
|
+
| --- | --- | --- |
|
|
206
|
+
| `teamix-evo ui add <id>` | component / block | `src/components/ui/<id>.tsx`(frozen) |
|
|
207
|
+
| `teamix-evo biz-ui add <id> --variant <name>` | component(包归属 biz-ui) | `src/components/business/<id>.tsx`(frozen) |
|
|
208
|
+
| `teamix-evo templates add <id> --variant <name>` | template(包归属 templates) | `src/pages/<id>/` 或 `src/templates/<id>/`(frozen) |
|
|
209
|
+
|
|
210
|
+
> 若 `.teamix-evo/design/pack.lock.json` 已锁定变体,biz-ui add / templates add 可省略 `--variant`。
|
|
211
|
+
|
|
212
|
+
### 9. ESLint preset 扩展
|
|
213
|
+
|
|
214
|
+
`@teamix-evo/eslint-config/presets/ui` 与 `presets/consumer` 的 glob 自然扩展到 biz-ui / templates:
|
|
215
|
+
|
|
216
|
+
```js
|
|
217
|
+
// 工程仓:packages/biz-ui/eslint.config.js + packages/templates/eslint.config.js
|
|
218
|
+
import uiPreset from '@teamix-evo/eslint-config/presets/ui';
|
|
219
|
+
export default [
|
|
220
|
+
{ ignores: [/* ... */] },
|
|
221
|
+
...uiPreset,
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
// consumer 侧:src/components/{ui,business}/**, src/{pages,templates}/** 都进 consumer preset
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### 10. 变体一致性的反熵(2026-05-18 改动:校验范围扩到三包)
|
|
228
|
+
|
|
229
|
+
design / biz-ui / templates **三包共享同一变体名空间**(协议级 ID,新增 / 重命名 走 ADR)。v0.9 [drift-scan](../../PLAN.md#128-v09--反熵层落地18-28-人日) 加校验:
|
|
230
|
+
|
|
231
|
+
- 所有出现在 `packages/design/variants/<name>/` 的 name,必须在 `packages/biz-ui/variants/<name>/` 与 `packages/templates/variants/<name>/` 都有对应目录(可空)
|
|
232
|
+
- 反向同理(任何一包独有的变体名都视为漂移)
|
|
233
|
+
- 不一致时 `teamix-evo doctor` 报告 + CI 失败
|
|
234
|
+
|
|
235
|
+
v0.6 阶段先用手工 check 脚本(`scripts/check-variant-sync.ts`)兜底,扫三包变体目录交集 / 差集。
|
|
236
|
+
|
|
237
|
+
## Consequences
|
|
238
|
+
|
|
239
|
+
- **Positive**:
|
|
240
|
+
- **包输出类型单一** —— ui / biz-ui / templates 都输出 TSX;design 永远只输出静态知识。每个包的 build / lint / 测试 / 发布流程一致
|
|
241
|
+
- **ESLint preset 全包覆盖零成本** —— `@teamix-evo/eslint-config/presets/ui` 一份配置,glob 自然扩到 biz-ui / templates
|
|
242
|
+
- **CLI 命令对称** —— ui add / biz-ui add / templates add 共享源码注入实现,行为一致
|
|
243
|
+
- **变体语义对称** —— design / biz-ui / templates 三包共享同一变体名空间(opentrek / uni-manager / ...),装机选定变体后三包统一按该变体输出 — 装机产物可预期(2026-05-18 改动后)
|
|
244
|
+
- **业务消费方可裁剪** —— 只用通用 ui + 自家手写业务组件 + design 知识的项目,完全不装 biz-ui / templates
|
|
245
|
+
- **ADR 0005 边界保留并对称表达** —— ui 不绑(纯能力);biz-ui 必绑(业务);templates 必绑(页面合成),三种粒度对应三种边界
|
|
246
|
+
- **Negative**:
|
|
247
|
+
- 包数 +2(biz-ui / templates),pnpm-workspace.yaml 多两行,装机文档多两个命令
|
|
248
|
+
- 变体名跨**三包**同步成本 —— v0.6 手工 check 三包变体目录;v0.9 doctor 落地
|
|
249
|
+
- 通用 page shape(`EmptyAppShell` 等)在多变体间物理重复 —— `_template/` minimal 起点缓解,但仍不如单一 default 优雅
|
|
250
|
+
- **Trade-off**:
|
|
251
|
+
- 用"+2 包 + 三包变体名同步成本 + 通用 page 物理重复"换"包输出类型单一 + lint 全覆盖 + CLI 对称 + 三包变体语义统一 + 业务可裁剪"
|
|
252
|
+
- 用"templates 必绑变体的物理重复"换"变体专属页面合成的表达力 + 三包变体语义对称 + 装机产物可预期"
|
|
253
|
+
|
|
254
|
+
## Implementation note
|
|
255
|
+
|
|
256
|
+
落地工作量(v0.6,共 ~2.2 人日;2026-05-18 改动后较原方案 +0.2):
|
|
257
|
+
|
|
258
|
+
1. 写本 ADR(0.3)
|
|
259
|
+
2. `packages/biz-ui/` 包基建 + manifest schema(`variant` 字段)+ 1-2 个示例(从现 OpenTrek `business/` 抽出)(0.7)
|
|
260
|
+
3. `packages/templates/` 包基建 + `variants/{_template, opentrek, uni-manager}/` 三目录 + opentrek 下 1 个示例(`list-detail-page` 空骨架)(0.7,2026-05-18 改动:+0.2)
|
|
261
|
+
4. `packages/registry/src/schema/manifest.ts` 加 'block' / 'template' 到 `UiEntryTypeSchema` + biz-ui/templates entry 加 `variant` 字段(0.2)
|
|
262
|
+
5. CLI 加 `biz-ui add` / `templates add --variant` 子命令(0.5,复用 ui 安装器逻辑)
|
|
263
|
+
6. ESLint preset glob 自然扩展(配置层只需 `eslint.config.js` 引用 preset)(0.1)
|
|
264
|
+
7. `scripts/check-variant-sync.ts` 手工兜底脚本,扫 design / biz-ui / templates 三包变体目录交集 / 差集(0.2)
|
|
265
|
+
8. 文档:`packages/biz-ui/AGENTS.md` + `packages/templates/AGENTS.md`(0.5)
|
|
266
|
+
|
|
267
|
+
## Source
|
|
268
|
+
|
|
269
|
+
- [ADR 0001](0001-three-layer-alignment.md):三层对齐;本 ADR 落实"工程 = shadcn"在三个粒度的具体形态
|
|
270
|
+
- [ADR 0005](0005-ui-no-variant.md):ui 不分 variant 的前提保留;biz-ui / templates 必须分 variant 形成对照
|
|
271
|
+
- [ADR 0010](0010-design-default-and-variants.md):design 变体模型;本 ADR 共用变体名空间;`linked.biz-ui` / `linked.templates` 字段对接 design pack manifest
|
|
272
|
+
- 2026-05-18 用户讨论(轮 1):["业务组件和页面模板,放到 ui 下还是新建包?"](biz-ui 独立包论证 + biz-ui 不需要 default/ 反推)
|
|
273
|
+
- 2026-05-18 用户改动(轮 2):**templates 改为 variant-aware**,与 biz-ui 同形(仅 variants/,无 default);三包共享同一变体名空间;manifest 加 `variant` 字段;CLI `templates add` 加 `--variant`;变体一致性反熵校验范围由两包扩到三包
|
|
274
|
+
- [`packages/design/library/opentrek/business/`](../../packages/design/library/opentrek/business/):现 OpenTrek 业务上下文目录,本 ADR 决策迁出至 biz-ui
|