@wneng/create-keel 0.4.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/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/templates/ci-gitee/files/PULL_REQUEST_TEMPLATE.md +15 -2
- package/src/templates/ci-github/files/PULL_REQUEST_TEMPLATE.md +15 -2
- package/src/templates/docs-skeleton/files/governance-change-tiers.md +135 -76
- package/src/templates/root-files/files/AGENTS.md +29 -15
- package/src/templates/welcome-html/files/WELCOME.md +39 -0
- package/src/templates/welcome-html/files/keel-README.md +28 -0
- package/src/templates/welcome-html/files/welcome.html +644 -0
- package/src/templates/welcome-html/fragment.yaml +14 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>欢迎使用 keel — <%= it.options.projectName %></title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #fafafa;
|
|
10
|
+
--fg: #1f2328;
|
|
11
|
+
--fg-muted: #6e7781;
|
|
12
|
+
--accent: #0969da;
|
|
13
|
+
--accent-soft: #ddf4ff;
|
|
14
|
+
--border: #d0d7de;
|
|
15
|
+
--code-bg: #f6f8fa;
|
|
16
|
+
--code-fg: #24292f;
|
|
17
|
+
--warn-bg: #fff8c5;
|
|
18
|
+
--warn-border: #d4a72c;
|
|
19
|
+
--ok-bg: #dafbe1;
|
|
20
|
+
--ok-border: #2da44e;
|
|
21
|
+
--nav-w: 260px;
|
|
22
|
+
}
|
|
23
|
+
@media (prefers-color-scheme: dark) {
|
|
24
|
+
:root {
|
|
25
|
+
--bg: #0d1117;
|
|
26
|
+
--fg: #e6edf3;
|
|
27
|
+
--fg-muted: #8d96a0;
|
|
28
|
+
--accent: #58a6ff;
|
|
29
|
+
--accent-soft: #0d2d5a;
|
|
30
|
+
--border: #30363d;
|
|
31
|
+
--code-bg: #161b22;
|
|
32
|
+
--code-fg: #e6edf3;
|
|
33
|
+
--warn-bg: #3a2a00;
|
|
34
|
+
--warn-border: #d4a72c;
|
|
35
|
+
--ok-bg: #0d2818;
|
|
36
|
+
--ok-border: #2da44e;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
* { box-sizing: border-box; }
|
|
40
|
+
html, body { margin: 0; padding: 0; }
|
|
41
|
+
body {
|
|
42
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
|
|
43
|
+
"Microsoft YaHei", "Noto Sans CJK SC", Helvetica, Arial, sans-serif;
|
|
44
|
+
font-size: 15px;
|
|
45
|
+
line-height: 1.65;
|
|
46
|
+
color: var(--fg);
|
|
47
|
+
background: var(--bg);
|
|
48
|
+
}
|
|
49
|
+
.layout { display: flex; min-height: 100vh; }
|
|
50
|
+
nav.sidebar {
|
|
51
|
+
width: var(--nav-w);
|
|
52
|
+
flex-shrink: 0;
|
|
53
|
+
border-right: 1px solid var(--border);
|
|
54
|
+
padding: 24px 20px;
|
|
55
|
+
position: sticky;
|
|
56
|
+
top: 0;
|
|
57
|
+
height: 100vh;
|
|
58
|
+
overflow-y: auto;
|
|
59
|
+
background: var(--bg);
|
|
60
|
+
}
|
|
61
|
+
nav.sidebar h1 {
|
|
62
|
+
font-size: 18px;
|
|
63
|
+
margin: 0 0 4px 0;
|
|
64
|
+
}
|
|
65
|
+
nav.sidebar .subtitle {
|
|
66
|
+
font-size: 12px;
|
|
67
|
+
color: var(--fg-muted);
|
|
68
|
+
margin-bottom: 20px;
|
|
69
|
+
}
|
|
70
|
+
nav.sidebar ol {
|
|
71
|
+
list-style: none;
|
|
72
|
+
padding: 0;
|
|
73
|
+
margin: 0;
|
|
74
|
+
counter-reset: section;
|
|
75
|
+
}
|
|
76
|
+
nav.sidebar li { margin: 4px 0; }
|
|
77
|
+
nav.sidebar a {
|
|
78
|
+
display: block;
|
|
79
|
+
padding: 6px 10px;
|
|
80
|
+
border-radius: 6px;
|
|
81
|
+
text-decoration: none;
|
|
82
|
+
color: var(--fg);
|
|
83
|
+
font-size: 14px;
|
|
84
|
+
}
|
|
85
|
+
nav.sidebar a:hover { background: var(--accent-soft); }
|
|
86
|
+
nav.sidebar a.active {
|
|
87
|
+
background: var(--accent-soft);
|
|
88
|
+
color: var(--accent);
|
|
89
|
+
font-weight: 600;
|
|
90
|
+
}
|
|
91
|
+
main {
|
|
92
|
+
flex: 1;
|
|
93
|
+
padding: 40px 56px;
|
|
94
|
+
max-width: 920px;
|
|
95
|
+
}
|
|
96
|
+
section { margin-bottom: 56px; scroll-margin-top: 24px; }
|
|
97
|
+
section h2 {
|
|
98
|
+
font-size: 22px;
|
|
99
|
+
margin: 0 0 12px 0;
|
|
100
|
+
border-bottom: 1px solid var(--border);
|
|
101
|
+
padding-bottom: 8px;
|
|
102
|
+
}
|
|
103
|
+
section h3 {
|
|
104
|
+
font-size: 17px;
|
|
105
|
+
margin: 24px 0 8px 0;
|
|
106
|
+
}
|
|
107
|
+
p { margin: 12px 0; }
|
|
108
|
+
code {
|
|
109
|
+
background: var(--code-bg);
|
|
110
|
+
color: var(--code-fg);
|
|
111
|
+
padding: 1px 6px;
|
|
112
|
+
border-radius: 4px;
|
|
113
|
+
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
114
|
+
font-size: 13px;
|
|
115
|
+
}
|
|
116
|
+
pre {
|
|
117
|
+
background: var(--code-bg);
|
|
118
|
+
color: var(--code-fg);
|
|
119
|
+
padding: 12px 16px;
|
|
120
|
+
border-radius: 6px;
|
|
121
|
+
overflow-x: auto;
|
|
122
|
+
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
123
|
+
font-size: 13px;
|
|
124
|
+
line-height: 1.5;
|
|
125
|
+
}
|
|
126
|
+
pre code { background: transparent; padding: 0; }
|
|
127
|
+
table {
|
|
128
|
+
border-collapse: collapse;
|
|
129
|
+
width: 100%;
|
|
130
|
+
margin: 12px 0;
|
|
131
|
+
font-size: 14px;
|
|
132
|
+
}
|
|
133
|
+
th, td {
|
|
134
|
+
border: 1px solid var(--border);
|
|
135
|
+
padding: 8px 10px;
|
|
136
|
+
text-align: left;
|
|
137
|
+
vertical-align: top;
|
|
138
|
+
}
|
|
139
|
+
th { background: var(--code-bg); font-weight: 600; }
|
|
140
|
+
a { color: var(--accent); }
|
|
141
|
+
a:hover { text-decoration: underline; }
|
|
142
|
+
.callout {
|
|
143
|
+
border-left: 3px solid var(--accent);
|
|
144
|
+
background: var(--accent-soft);
|
|
145
|
+
padding: 10px 14px;
|
|
146
|
+
margin: 16px 0;
|
|
147
|
+
border-radius: 0 6px 6px 0;
|
|
148
|
+
}
|
|
149
|
+
.callout.warn {
|
|
150
|
+
border-left-color: var(--warn-border);
|
|
151
|
+
background: var(--warn-bg);
|
|
152
|
+
}
|
|
153
|
+
.callout.ok {
|
|
154
|
+
border-left-color: var(--ok-border);
|
|
155
|
+
background: var(--ok-bg);
|
|
156
|
+
}
|
|
157
|
+
ul, ol { padding-left: 24px; }
|
|
158
|
+
li { margin: 4px 0; }
|
|
159
|
+
.badge {
|
|
160
|
+
display: inline-block;
|
|
161
|
+
padding: 2px 8px;
|
|
162
|
+
border-radius: 10px;
|
|
163
|
+
font-size: 12px;
|
|
164
|
+
background: var(--code-bg);
|
|
165
|
+
color: var(--fg-muted);
|
|
166
|
+
margin-right: 6px;
|
|
167
|
+
}
|
|
168
|
+
.badge.tier-0, .badge.tier-1, .badge.tier-2 {
|
|
169
|
+
background: var(--ok-bg); color: var(--ok-border);
|
|
170
|
+
}
|
|
171
|
+
.badge.tier-3 {
|
|
172
|
+
background: var(--accent-soft); color: var(--accent);
|
|
173
|
+
}
|
|
174
|
+
.badge.tier-4 {
|
|
175
|
+
background: var(--warn-bg); color: var(--warn-border);
|
|
176
|
+
}
|
|
177
|
+
.meta {
|
|
178
|
+
color: var(--fg-muted);
|
|
179
|
+
font-size: 13px;
|
|
180
|
+
}
|
|
181
|
+
details summary {
|
|
182
|
+
cursor: pointer;
|
|
183
|
+
font-weight: 600;
|
|
184
|
+
padding: 8px 0;
|
|
185
|
+
}
|
|
186
|
+
details > *:not(summary) { margin-left: 20px; }
|
|
187
|
+
hr {
|
|
188
|
+
border: none;
|
|
189
|
+
border-top: 1px solid var(--border);
|
|
190
|
+
margin: 24px 0;
|
|
191
|
+
}
|
|
192
|
+
@media (max-width: 800px) {
|
|
193
|
+
.layout { flex-direction: column; }
|
|
194
|
+
nav.sidebar {
|
|
195
|
+
width: 100%; height: auto; position: static;
|
|
196
|
+
border-right: none; border-bottom: 1px solid var(--border);
|
|
197
|
+
}
|
|
198
|
+
main { padding: 24px; }
|
|
199
|
+
}
|
|
200
|
+
</style>
|
|
201
|
+
</head>
|
|
202
|
+
<body>
|
|
203
|
+
<div class="layout">
|
|
204
|
+
<nav class="sidebar">
|
|
205
|
+
<h1>📘 keel 速查</h1>
|
|
206
|
+
<div class="subtitle">
|
|
207
|
+
<%= it.options.projectName %><br>
|
|
208
|
+
由 @wneng/create-keel <%= it.scaffolderVersion %> 生成
|
|
209
|
+
</div>
|
|
210
|
+
<ol>
|
|
211
|
+
<li><a href="#welcome">1. 欢迎</a></li>
|
|
212
|
+
<li><a href="#layout">2. 目录速查</a></li>
|
|
213
|
+
<li><a href="#concepts">3. 关键概念</a></li>
|
|
214
|
+
<li><a href="#what-to-do">4. 我要做什么</a></li>
|
|
215
|
+
<li><a href="#commands">5. 常用命令</a></li>
|
|
216
|
+
<li><a href="#ai">6. AI 协作</a></li>
|
|
217
|
+
<li><a href="#more">7. 去哪查更多</a></li>
|
|
218
|
+
<li><a href="#faq">8. 常见问题</a></li>
|
|
219
|
+
</ol>
|
|
220
|
+
</nav>
|
|
221
|
+
|
|
222
|
+
<main>
|
|
223
|
+
<section id="welcome">
|
|
224
|
+
<h2>1. 欢迎</h2>
|
|
225
|
+
<p>
|
|
226
|
+
本项目由 <strong>keel</strong> 框架生成,采用 <strong>Contract First + Vibe Coding</strong>
|
|
227
|
+
的协作方式。一句话:
|
|
228
|
+
</p>
|
|
229
|
+
<div class="callout">
|
|
230
|
+
<strong>契约(API / 事件 / 字典 / 错误码)</strong>是真值。代码、文档、测试都围绕契约展开;
|
|
231
|
+
改契约必须走 SemVer 与 CHANGELOG。AI 与人类协作时,AGENTS.md 是总纲。
|
|
232
|
+
</div>
|
|
233
|
+
<p>
|
|
234
|
+
keel 帮你省掉这些重复劳动:搭目录、配 lint / format / typecheck / 安全扫描、
|
|
235
|
+
装迁移工具、PR 模板、AI 工具的对接(Kiro / Cursor / Claude Code / Codex)、
|
|
236
|
+
多团队协作的边界与责任分配。
|
|
237
|
+
</p>
|
|
238
|
+
<p>
|
|
239
|
+
<strong>第一次进项目,按下面顺序看:</strong>
|
|
240
|
+
§2 看目录布局 → §3 学三个核心概念 → §4 找对应你当前任务的入口 → §5 跑常用命令。
|
|
241
|
+
</p>
|
|
242
|
+
</section>
|
|
243
|
+
|
|
244
|
+
<section id="layout">
|
|
245
|
+
<h2>2. 目录速查</h2>
|
|
246
|
+
<p>顶层目录每一项一句话即可知道是干嘛的。机器目录写代码,人类目录写文档。</p>
|
|
247
|
+
<table>
|
|
248
|
+
<tr><th>目录</th><th>面向</th><th>放什么</th></tr>
|
|
249
|
+
<tr><td><code>contracts/</code></td><td>机器 / AI</td><td>API(OpenAPI)、事件(AsyncAPI)、字典、错误码、状态机——所有跨模块的真值</td></tr>
|
|
250
|
+
<tr><td><code>docs/</code></td><td>人类</td><td>需求、架构决策、详细设计、治理细则、过程文档</td></tr>
|
|
251
|
+
<% if (it.options.backend !== 'none') { %><tr><td><code>server/</code></td><td>机器</td><td>后端实现(<%= it.options.backend %>)<% if (it.options.database !== 'none') { %>,含 <code>db/</code> 数据库迁移与种子<% } %></td></tr>
|
|
252
|
+
<% } %><% if (it.options.frontend !== 'none') { %><tr><td><code>web/</code></td><td>机器</td><td>前端实现(<%= it.options.frontend %>)</td></tr>
|
|
253
|
+
<% } %><% if (it.options.mobile !== 'none') { %><tr><td><code>mobile/</code></td><td>机器</td><td>移动端(<%= it.options.mobile %>)</td></tr>
|
|
254
|
+
<% } %><% if (it.options.miniapp !== 'none') { %><tr><td><code>miniapp/</code></td><td>机器</td><td>小程序(<%= it.options.miniapp %>)</td></tr>
|
|
255
|
+
<% } %><% if (it.options.agent !== 'none') { %><tr><td><code>agent/</code></td><td>机器</td><td>桌面 / CLI 客户端(<%= it.options.agent %>)</td></tr>
|
|
256
|
+
<% } %><tr><td><code>ops/</code></td><td>机器</td><td>基础设施即代码(即便没有业务也要存在)</td></tr>
|
|
257
|
+
<tr><td><code>deploy/</code></td><td>机器</td><td>应用打包与发布(Dockerfile、Helm、流水线)</td></tr>
|
|
258
|
+
<tr><td><code>tools/</code></td><td>机器</td><td>结构化、可复用的开发工具(小产品级别)</td></tr>
|
|
259
|
+
<tr><td><code>scripts/</code></td><td>机器</td><td>薄、零散、日常维护脚本</td></tr>
|
|
260
|
+
<% if (it.options.ai === 'kiro') { %><tr><td><code>.kiro/</code></td><td>AI</td><td>Kiro IDE 的 steering / specs / hooks</td></tr>
|
|
261
|
+
<% } else if (it.options.ai === 'cursor') { %><tr><td><code>.cursor/</code></td><td>AI</td><td>Cursor 项目规则</td></tr>
|
|
262
|
+
<% } else if (it.options.ai === 'claude-code') { %><tr><td><code>CLAUDE.md</code> / <code>.claude/</code></td><td>AI</td><td>Claude Code 上下文 + slash-commands</td></tr>
|
|
263
|
+
<% } else if (it.options.ai === 'codex') { %><tr><td><code>.codex/</code></td><td>AI</td><td>Codex 项目备注</td></tr>
|
|
264
|
+
<% } %><tr><td><code>.keel/</code></td><td>用户</td><td>本指南、keel 元数据(你正在看的就在这里)</td></tr>
|
|
265
|
+
</table>
|
|
266
|
+
<div class="callout">
|
|
267
|
+
每个目录的细则见 <code>AGENTS.md</code> §1(顶层职责)和 <code>docs/governance/</code>(深度规则)。
|
|
268
|
+
</div>
|
|
269
|
+
</section>
|
|
270
|
+
|
|
271
|
+
<section id="concepts">
|
|
272
|
+
<h2>3. 关键概念</h2>
|
|
273
|
+
|
|
274
|
+
<h3>Contract First(契约优先)</h3>
|
|
275
|
+
<p>
|
|
276
|
+
所有跨模块的接口、事件、字典、错误码统一放在 <code>contracts/</code>。
|
|
277
|
+
任何代码改动如果触及契约,<strong>先冻结契约再写代码</strong>,并在同一 PR 里更新
|
|
278
|
+
<code>contracts/CHANGELOG.md</code>。
|
|
279
|
+
</p>
|
|
280
|
+
|
|
281
|
+
<h3>Tier(变更影响面,6 档,AI 自动判)</h3>
|
|
282
|
+
<p>每个改动从 0 到 5 选一档。AI 看你改了什么文件、是否动 contracts、是否跨服务,自动给建议。</p>
|
|
283
|
+
<table>
|
|
284
|
+
<tr><th>Tier</th><th>例子</th><th>动契约?</th></tr>
|
|
285
|
+
<tr><td><span class="badge tier-0">0</span> trivial</td><td>typo / 格式化 / 升级 patch / 补测试</td><td>不</td></tr>
|
|
286
|
+
<tr><td><span class="badge tier-1">1</span> bugfix</td><td>修 NPE、改 SQL、修 UI 错位</td><td>不</td></tr>
|
|
287
|
+
<tr><td><span class="badge tier-2">2</span> small change</td><td>加按钮 / 排序 / 文案</td><td>通常不</td></tr>
|
|
288
|
+
<tr><td><span class="badge tier-3">3</span> contract change</td><td>加 API / 字段 / 错误码 / 事件</td><td><strong>必须</strong></td></tr>
|
|
289
|
+
<tr><td><span class="badge tier-4">4</span> arch / breaking</td><td>跨服务、改鉴权、删字段</td><td><strong>必须</strong> + ADR</td></tr>
|
|
290
|
+
<tr><td><span class="badge">5</span> spike</td><td>探索性、用 <code>spike/*</code> 分支</td><td>合入前补</td></tr>
|
|
291
|
+
</table>
|
|
292
|
+
|
|
293
|
+
<h3>Doc Weight(文档投入,3 档,作者选)</h3>
|
|
294
|
+
<p>
|
|
295
|
+
与 Tier 正交:<strong>Tier</strong> 是变更客观属性,<strong>Doc Weight</strong> 是团队这次愿意投多少笔墨在 markdown 上。
|
|
296
|
+
AI 推荐默认值,作者可在 PR 模板里覆盖。
|
|
297
|
+
</p>
|
|
298
|
+
<table>
|
|
299
|
+
<tr><th>档</th><th>包含</th><th>常用场景</th></tr>
|
|
300
|
+
<tr><td><strong>lite</strong></td><td>PR 描述(动机 + 方案 + 影响面)+ 契约 diff(Tier 3+)+ ADR(Tier 4)</td><td>Tier 0/1/2/3 默认;Tier 4 简单情况</td></tr>
|
|
301
|
+
<tr><td><strong>standard</strong></td><td>lite + 简短的后端 / 前端设计章节(每份 2-3 段)</td><td>Tier 4 默认;Tier 3 复杂情况</td></tr>
|
|
302
|
+
<tr><td><strong>full</strong></td><td>standard + PRD(<code>docs/01/</code>) + UI 稿(如涉及前端)</td><td>来自产品需求 / 跨多团队 / 跨季度的项目</td></tr>
|
|
303
|
+
</table>
|
|
304
|
+
<div class="callout warn">
|
|
305
|
+
<strong>关键</strong>:Tier 3(加 API 字段)默认 <code>lite</code>——<strong>契约 diff 本身就是设计真值</strong>,
|
|
306
|
+
额外写中文设计文档反而易和契约漂移。仅当业务复杂度高(多状态机、跨服务、跨团队)才升 standard / full。
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
<h3>Adoption Tier(工程规范采纳档,<code>--engineering-standards</code>)</h3>
|
|
310
|
+
<p>
|
|
311
|
+
与 Doc Weight 是不同的"档"。Adoption Tier 是 <strong>项目级</strong>选择(在创建项目时一次性决定):
|
|
312
|
+
<code>lite</code> 只发编码风格 markdown;<code>standard</code> 加技术栈钉版本;<code>full</code> 加 UI 设计系统。
|
|
313
|
+
本项目当前是 <code>standard</code>(默认),见 <code>docs/03-工程规范与研发基础设施/</code>。
|
|
314
|
+
</p>
|
|
315
|
+
|
|
316
|
+
<% if (it.options.ai !== 'none') { %><h3>AI Access Mode(5 档,AI 触达规则)</h3>
|
|
317
|
+
<p>
|
|
318
|
+
每个 docs 子目录用 <code>_ai-policy.yaml</code> 声明 AI 可不可以读 / 写:
|
|
319
|
+
<code>strict</code>(必同步)、<code>generated</code>(AI 主动改人审)、<code>suggest</code>(默认;提示但不自动改)、
|
|
320
|
+
<code>on-demand</code>(说出关键词才动)、<code>read-only</code>。完整规则见
|
|
321
|
+
<a href="../docs/governance/ai-access-modes.md"><code>docs/governance/ai-access-modes.md</code></a>。
|
|
322
|
+
</p>
|
|
323
|
+
<% } %>
|
|
324
|
+
</section>
|
|
325
|
+
|
|
326
|
+
<section id="what-to-do">
|
|
327
|
+
<h2>4. 我要做什么</h2>
|
|
328
|
+
<p>按你当前任务找入口:</p>
|
|
329
|
+
|
|
330
|
+
<details open>
|
|
331
|
+
<summary>🔧 修一个 bug(不动行为,不动 API)</summary>
|
|
332
|
+
<p><span class="badge tier-1">Tier 1</span> <span class="badge">lite</span></p>
|
|
333
|
+
<ul>
|
|
334
|
+
<li>直接改代码 + 加回归测试</li>
|
|
335
|
+
<li>PR 描述包含:重现步骤 / 根因 / 修复点 / 测试</li>
|
|
336
|
+
<li>不需要改 <code>contracts/</code> 和详细设计</li>
|
|
337
|
+
</ul>
|
|
338
|
+
</details>
|
|
339
|
+
|
|
340
|
+
<details>
|
|
341
|
+
<summary>➕ 加一个新 API 端点 / 字段(兼容新增)</summary>
|
|
342
|
+
<p><span class="badge tier-3">Tier 3</span> <span class="badge">默认 lite</span></p>
|
|
343
|
+
<ol>
|
|
344
|
+
<li>改 <code>contracts/openapi/api.yaml</code> 加端点 / 字段</li>
|
|
345
|
+
<li>改 <code>contracts/CHANGELOG.md</code> 记录 MINOR bump</li>
|
|
346
|
+
<li>实现代码</li>
|
|
347
|
+
<li>PR 描述引用契约锚点(<code>api.yaml#/paths/~1users/post</code>)</li>
|
|
348
|
+
</ol>
|
|
349
|
+
<p><strong>不需要</strong>额外写 <code>docs/04</code> 或 <code>docs/05</code> 设计文档;契约 diff 本身就是设计。</p>
|
|
350
|
+
</details>
|
|
351
|
+
|
|
352
|
+
<details>
|
|
353
|
+
<summary>♻️ 重构鉴权 / 改数据模型 / 删除字段</summary>
|
|
354
|
+
<p><span class="badge tier-4">Tier 4</span> <span class="badge">standard 起步</span></p>
|
|
355
|
+
<ol>
|
|
356
|
+
<li>先在 <code>docs/02-系统方案与架构/</code> 写 ADR(<code>adr-NNNN-<slug>.md</code>)</li>
|
|
357
|
+
<li>讨论 / 评审通过后改 <code>contracts/</code></li>
|
|
358
|
+
<li>写迁移与回滚预案</li>
|
|
359
|
+
<li>实现 + 测试</li>
|
|
360
|
+
</ol>
|
|
361
|
+
</details>
|
|
362
|
+
|
|
363
|
+
<details>
|
|
364
|
+
<summary>🧪 我想试新方案,不确定能不能用</summary>
|
|
365
|
+
<p><span class="badge">Tier 5 spike</span> <span class="badge">lite</span></p>
|
|
366
|
+
<ul>
|
|
367
|
+
<li>建 <code>spike/<topic></code> 分支,自由实验</li>
|
|
368
|
+
<li>结论清楚后:采纳 → 重新走 Tier 1-4 流程并合入;不采纳 → 文档进 <code>docs/过程文档/spike-investigations/</code></li>
|
|
369
|
+
<li><code>spike/*</code> 不能直接合入 <code>main</code></li>
|
|
370
|
+
</ul>
|
|
371
|
+
</details>
|
|
372
|
+
|
|
373
|
+
<% if (it.options.database !== 'none') { %><details>
|
|
374
|
+
<summary>🗄️ 加一张表 / 改一个字段(数据库变更)</summary>
|
|
375
|
+
<p><span class="badge tier-3">Tier 3</span>(加字段)或 <span class="badge tier-4">Tier 4</span>(删字段 / 改类型)</p>
|
|
376
|
+
<ul>
|
|
377
|
+
<li>在 <code>server/db/migrations/</code> 加新迁移文件</li>
|
|
378
|
+
<li><strong>已合入 main 的迁移文件不允许修改</strong>,改 schema 写新文件</li>
|
|
379
|
+
<li>本项目用 <%= it.options.database %>,迁移工具见 <code>server/db/README.md</code></li>
|
|
380
|
+
<li>字典数据要同步 <code>contracts/dictionaries/</code></li>
|
|
381
|
+
</ul>
|
|
382
|
+
</details>
|
|
383
|
+
<% } %>
|
|
384
|
+
|
|
385
|
+
<details>
|
|
386
|
+
<summary>📦 升级一个依赖</summary>
|
|
387
|
+
<ul>
|
|
388
|
+
<li><strong>patch</strong>(4.17.20 → 4.17.21):<span class="badge tier-0">Tier 0</span></li>
|
|
389
|
+
<li><strong>minor</strong>(4.17 → 4.18):<span class="badge tier-2">Tier 2</span>,看 changelog</li>
|
|
390
|
+
<li><strong>major</strong>(3.x → 4.x):<span class="badge tier-4">Tier 4</span>,要 ADR + 兼容性测试</li>
|
|
391
|
+
<li>所有情况都要同步 <code>docs/03-工程规范与研发基础设施/tech-stack-server.md</code> 的钉版本表(如启用 standard / full 工程规范)</li>
|
|
392
|
+
</ul>
|
|
393
|
+
</details>
|
|
394
|
+
</section>
|
|
395
|
+
|
|
396
|
+
<section id="commands">
|
|
397
|
+
<h2>5. 常用命令</h2>
|
|
398
|
+
|
|
399
|
+
<% if (it.options.backend === 'node') { %><h3>后端(Node + TypeScript)</h3>
|
|
400
|
+
<pre><code>cd server
|
|
401
|
+
npm install
|
|
402
|
+
npm run typecheck # tsc --noEmit
|
|
403
|
+
npm run lint # eslint
|
|
404
|
+
npm run format:check # prettier
|
|
405
|
+
npm test # vitest / jest(按你加的)<% if (it.options.database === 'mysql' || it.options.database === 'postgres') { %>
|
|
406
|
+
npm run db:migrate # Knex 升到最新迁移
|
|
407
|
+
npm run db:rollback # 回滚最近一组
|
|
408
|
+
npm run db:seed:prod # 装字典数据
|
|
409
|
+
npm run db:seed:dev # 装开发样例数据<% } %><% if (it.options.database === 'elasticsearch') { %>
|
|
410
|
+
npm run es:apply # 应用 ES index template<% } %></code></pre>
|
|
411
|
+
<% } else if (it.options.backend === 'java') { %><h3>后端(Java + Spring Boot)</h3>
|
|
412
|
+
<pre><code>cd server
|
|
413
|
+
mvn -B verify # 编译 + 测试 + Checkstyle + Spotless<% if (it.options.database === 'mysql' || it.options.database === 'postgres') { %>
|
|
414
|
+
mvn flyway:migrate # 跑迁移
|
|
415
|
+
mvn flyway:validate # 校验代码 vs DB 历史一致<% } %>
|
|
416
|
+
mvn spring-boot:run # 启动应用</code></pre>
|
|
417
|
+
<% } else if (it.options.backend === 'python') { %><h3>后端(Python + FastAPI)</h3>
|
|
418
|
+
<pre><code>cd server
|
|
419
|
+
python -m venv .venv
|
|
420
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
421
|
+
pip install -e ".[dev]"
|
|
422
|
+
uvicorn app.main:app --reload --port 8000
|
|
423
|
+
# 浏览自动 OpenAPI UI: http://127.0.0.1:8000/docs
|
|
424
|
+
pytest -q
|
|
425
|
+
ruff check .
|
|
426
|
+
mypy .<% if (it.options.database === 'mysql' || it.options.database === 'postgres') { %>
|
|
427
|
+
alembic upgrade head # 跑迁移
|
|
428
|
+
alembic downgrade -1 # 回滚一版<% } %><% if (it.options.database === 'elasticsearch') { %>
|
|
429
|
+
python db/apply_templates.py # 应用 ES index template<% } %></code></pre>
|
|
430
|
+
<% } else if (it.options.backend === 'go') { %><h3>后端(Go)</h3>
|
|
431
|
+
<pre><code>cd server
|
|
432
|
+
go mod download
|
|
433
|
+
make build
|
|
434
|
+
make test
|
|
435
|
+
make lint # golangci-lint<% if (it.options.database === 'mysql' || it.options.database === 'postgres') { %>
|
|
436
|
+
make db-up # golang-migrate 升迁移
|
|
437
|
+
make db-down # 回滚一条
|
|
438
|
+
make db-create name=xxx # 新建迁移文件<% } %><% if (it.options.database === 'elasticsearch') { %>
|
|
439
|
+
make es-apply # 应用 ES index template<% } %></code></pre>
|
|
440
|
+
<% } %>
|
|
441
|
+
|
|
442
|
+
<% if (it.options.frontend === 'react' || it.options.frontend === 'vue') { %><h3>前端(<%= it.options.frontend %>)</h3>
|
|
443
|
+
<pre><code>cd web
|
|
444
|
+
npm install
|
|
445
|
+
npm run dev # vite 开发服务器
|
|
446
|
+
npm run build # 生产打包
|
|
447
|
+
npm run typecheck # tsc --noEmit
|
|
448
|
+
npm run lint
|
|
449
|
+
npm run format:check
|
|
450
|
+
npm test # 如果加了 vitest</code></pre>
|
|
451
|
+
<% } %>
|
|
452
|
+
|
|
453
|
+
<h3>治理 / 全局</h3>
|
|
454
|
+
<pre><code># 检查 AGENTS.md / governance / role 目录 / 多应用 / 钉版本一致性
|
|
455
|
+
node tools/governance-lint/index.js --strict<% if (it.options.contract === 'rest' || it.options.contract === 'rest+events') { %>
|
|
456
|
+
|
|
457
|
+
# 校验 OpenAPI 契约
|
|
458
|
+
npx --yes @stoplight/spectral-cli lint contracts/openapi/api.yaml<% } %><% if (it.options.contract === 'rest+events' || it.options.contract === 'events-only') { %>
|
|
459
|
+
|
|
460
|
+
# 校验 AsyncAPI 契约
|
|
461
|
+
npx --yes @stoplight/spectral-cli lint contracts/asyncapi/asyncapi.yaml<% } %>
|
|
462
|
+
|
|
463
|
+
# 校验所有 JSON Schema
|
|
464
|
+
for s in contracts/**/*.schema.json; do
|
|
465
|
+
npx --yes ajv-cli compile -s "$s"
|
|
466
|
+
done</code></pre>
|
|
467
|
+
</section>
|
|
468
|
+
|
|
469
|
+
<section id="ai">
|
|
470
|
+
<h2>6. AI 协作</h2>
|
|
471
|
+
<% if (it.options.ai === 'kiro') { %><p>本项目集成 <strong>Kiro IDE</strong>。AI 协作的关键文件:</p>
|
|
472
|
+
<table>
|
|
473
|
+
<tr><th>文件 / 目录</th><th>用途</th></tr>
|
|
474
|
+
<tr><td><code>AGENTS.md</code></td><td>所有 AI 工具的总纲(也包括 Kiro)</td></tr>
|
|
475
|
+
<tr><td><code>.kiro/steering/*.md</code></td><td>Kiro 默认加载的"任务级"上下文</td></tr>
|
|
476
|
+
<tr><td><code>.kiro/specs/<feature>/</code></td><td>每个新特性的 requirements / design / tasks 三件套</td></tr>
|
|
477
|
+
<tr><td><code>.kiro/hooks/</code></td><td>事件触发的自动化(保存触发跑测试等)</td></tr>
|
|
478
|
+
</table>
|
|
479
|
+
<% } else if (it.options.ai === 'cursor') { %><p>本项目集成 <strong>Cursor</strong>。AI 协作的关键文件:</p>
|
|
480
|
+
<ul>
|
|
481
|
+
<li><code>AGENTS.md</code>:所有 AI 工具的总纲</li>
|
|
482
|
+
<li><code>.cursor/rules/*.md</code>:Cursor 的项目级规则</li>
|
|
483
|
+
</ul>
|
|
484
|
+
<% } else if (it.options.ai === 'claude-code') { %><p>本项目集成 <strong>Claude Code</strong>。AI 协作的关键文件:</p>
|
|
485
|
+
<ul>
|
|
486
|
+
<li><code>AGENTS.md</code>:所有 AI 工具的总纲</li>
|
|
487
|
+
<li><code>CLAUDE.md</code>:Claude Code 启动时自动加载的项目上下文</li>
|
|
488
|
+
<li><code>.claude/commands/*.md</code>:自定义 slash-commands</li>
|
|
489
|
+
</ul>
|
|
490
|
+
<% } else if (it.options.ai === 'codex') { %><p>本项目集成 <strong>Codex</strong>。AI 协作的关键文件:</p>
|
|
491
|
+
<ul>
|
|
492
|
+
<li><code>AGENTS.md</code>:所有 AI 工具的总纲</li>
|
|
493
|
+
<li><code>.codex/project.md</code>:Codex 项目备注</li>
|
|
494
|
+
</ul>
|
|
495
|
+
<% } else { %><p>本项目暂未集成具体 AI 工具。<code>AGENTS.md</code> 仍是总纲;
|
|
496
|
+
日后启用 Kiro / Cursor / Claude Code / Codex 等任意工具,它们都会自动读 <code>AGENTS.md</code>。</p>
|
|
497
|
+
<% } %>
|
|
498
|
+
|
|
499
|
+
<h3>AI 协作的规则</h3>
|
|
500
|
+
<ul>
|
|
501
|
+
<li>AI 生成的 commit 用 <code>feat(ai):</code> / <code>chore(ai):</code> / <code>fix(ai):</code> 前缀</li>
|
|
502
|
+
<li>PR 打 <code>ai-generated</code> 标签</li>
|
|
503
|
+
<li>Tier 3+ 改动 PR 描述必须引用契约锚点</li>
|
|
504
|
+
<li>Tier 4 必须引用 ADR</li>
|
|
505
|
+
</ul>
|
|
506
|
+
</section>
|
|
507
|
+
|
|
508
|
+
<section id="more">
|
|
509
|
+
<h2>7. 去哪查更多</h2>
|
|
510
|
+
<h3>给 AI(路径触发表)</h3>
|
|
511
|
+
<p>
|
|
512
|
+
<a href="../AGENTS.md"><code>AGENTS.md</code></a> §6 列出每条路径修改时必读的文档。
|
|
513
|
+
AI 在执行任务前会自动按这张表加载对应规则。
|
|
514
|
+
</p>
|
|
515
|
+
|
|
516
|
+
<h3>给人(文档门户)</h3>
|
|
517
|
+
<p>
|
|
518
|
+
<a href="../docs/README.md"><code>docs/README.md</code></a> 是文档总入口,含目录地图与归档规则。
|
|
519
|
+
</p>
|
|
520
|
+
|
|
521
|
+
<h3>治理细则</h3>
|
|
522
|
+
<table>
|
|
523
|
+
<tr><th>主题</th><th>文件</th></tr>
|
|
524
|
+
<tr><td>变更分级(Tier + Doc Weight)</td><td><a href="../docs/governance/change-tiers.md"><code>change-tiers.md</code></a></td></tr>
|
|
525
|
+
<tr><td>CI 校验矩阵</td><td><a href="../docs/governance/ci.md"><code>ci.md</code></a></td></tr>
|
|
526
|
+
<tr><td>依赖安全 / SBOM / 应急响应</td><td><a href="../docs/governance/security.md"><code>security.md</code></a></td></tr>
|
|
527
|
+
<tr><td>Git 工作流 / PR / CODEOWNERS</td><td><a href="../docs/governance/git-workflow.md"><code>git-workflow.md</code></a></td></tr>
|
|
528
|
+
<tr><td>数据库目录 / 迁移工具 / SQL 归属</td><td><a href="../docs/governance/database.md"><code>database.md</code></a></td></tr>
|
|
529
|
+
<tr><td>AI 访问模式</td><td><a href="../docs/governance/ai-access-modes.md"><code>ai-access-modes.md</code></a></td></tr>
|
|
530
|
+
<tr><td>完整 checklist</td><td><a href="../docs/governance/checklists.md"><code>checklists.md</code></a></td></tr>
|
|
531
|
+
</table>
|
|
532
|
+
</section>
|
|
533
|
+
|
|
534
|
+
<section id="faq">
|
|
535
|
+
<h2>8. 常见问题</h2>
|
|
536
|
+
|
|
537
|
+
<details>
|
|
538
|
+
<summary>我能直接改 <code>contracts/openapi/api.yaml</code> 吗?</summary>
|
|
539
|
+
<p>
|
|
540
|
+
能,但属于 <span class="badge tier-3">Tier 3</span>。同 PR 必须更新 <code>contracts/CHANGELOG.md</code>(CI 强制)。
|
|
541
|
+
PR 描述要引用契约锚点(如 <code>api.yaml#/paths/~1users/post</code>)。
|
|
542
|
+
删除字段或不兼容修改是 <span class="badge tier-4">Tier 4</span>,需要 ADR。
|
|
543
|
+
</p>
|
|
544
|
+
</details>
|
|
545
|
+
|
|
546
|
+
<details>
|
|
547
|
+
<summary>提了 PR 但 CI 报 <code>@TBD</code>?</summary>
|
|
548
|
+
<p>
|
|
549
|
+
根目录 <code>CODEOWNERS</code> 还有 <code>@TBD</code> 占位符。CI 在 governance-lint 这一步会拒绝
|
|
550
|
+
含 <code>@TBD</code> 的 CODEOWNERS。把 <code>@TBD</code> 替换成真实的 Git 账号即可。
|
|
551
|
+
</p>
|
|
552
|
+
</details>
|
|
553
|
+
|
|
554
|
+
<details>
|
|
555
|
+
<summary>Tier 怎么定?我每次都要查这表?</summary>
|
|
556
|
+
<p>
|
|
557
|
+
不用。AI 在 PR 描述里会根据 <code>git diff</code> 自动建议 Tier 与 Doc Weight。
|
|
558
|
+
你看不合适直接勾选别的。Reviewer 也会按现实情况调。
|
|
559
|
+
</p>
|
|
560
|
+
</details>
|
|
561
|
+
|
|
562
|
+
<details>
|
|
563
|
+
<summary>Doc Weight 选 lite,reviewer 让我补 04 设计文档怎么办?</summary>
|
|
564
|
+
<p>
|
|
565
|
+
指给他看 <a href="../docs/governance/change-tiers.md"><code>change-tiers.md</code></a> §3.2:Tier 3
|
|
566
|
+
默认 lite,契约 diff 本身就是设计;只在多状态机 / 跨服务 / 跨团队时升 standard。
|
|
567
|
+
Reviewer 不应仅因"没写设计文档"打回 Tier 3 + lite 的 PR。
|
|
568
|
+
</p>
|
|
569
|
+
</details>
|
|
570
|
+
|
|
571
|
+
<% if (it.options.database !== 'none') { %><details>
|
|
572
|
+
<summary>我误改了已合入 main 的迁移文件,怎么办?</summary>
|
|
573
|
+
<p>
|
|
574
|
+
回退那个文件到原内容(<code>git checkout main -- <path></code>),然后写一个新迁移文件做你想做的事。
|
|
575
|
+
原因:迁移工具记录的是文件 hash,改既有迁移会让任何已部署环境的 <code>migrate</code> 校验失败。
|
|
576
|
+
</p>
|
|
577
|
+
</details>
|
|
578
|
+
<% } %>
|
|
579
|
+
|
|
580
|
+
<details>
|
|
581
|
+
<summary>我刚加完代码 commit 时被 governance-lint 拦下来了?</summary>
|
|
582
|
+
<p>
|
|
583
|
+
看终端里 <code>governance-lint</code> 输出的具体错误码。常见的:
|
|
584
|
+
</p>
|
|
585
|
+
<ul>
|
|
586
|
+
<li><code>GOV.STACK.DRIFT.MAJOR/MINOR</code>:技术栈钉版本与运行时清单漂移;同步两边</li>
|
|
587
|
+
<li><code>missing required key</code>:<code>_ai-policy.yaml</code> 字段不全</li>
|
|
588
|
+
<li><code>last-reviewed missing/stale</code>:governance markdown 的 frontmatter 过期</li>
|
|
589
|
+
</ul>
|
|
590
|
+
<p>详见 <code>docs/governance/ci.md</code>。</p>
|
|
591
|
+
</details>
|
|
592
|
+
|
|
593
|
+
<details>
|
|
594
|
+
<summary>我想加一个新角色目录(比如 <code>docs/12-XXX/</code>)</summary>
|
|
595
|
+
<p>
|
|
596
|
+
这是结构改动,需要 <span class="badge tier-4">Tier 4</span> + 新 ADR。原因:role 目录与 AI 触达模式、双闸门、
|
|
597
|
+
PR 模板都耦合,结构调整要走 <code>docs/02-系统方案与架构/adr-NNNN-XXX.md</code>。
|
|
598
|
+
</p>
|
|
599
|
+
</details>
|
|
600
|
+
</section>
|
|
601
|
+
|
|
602
|
+
<hr>
|
|
603
|
+
<p class="meta">
|
|
604
|
+
本文件由 <code>@wneng/create-keel</code> <%= it.scaffolderVersion %> 在
|
|
605
|
+
<%= it.generatedAt.slice(0, 10) %> 生成。可以自由编辑;如要让 AI 帮你重新生成,
|
|
606
|
+
在 <code>.keel/welcome.html</code> 顶部加一行 <code><!-- regen-on-update --></code>。
|
|
607
|
+
</p>
|
|
608
|
+
</main>
|
|
609
|
+
</div>
|
|
610
|
+
|
|
611
|
+
<script>
|
|
612
|
+
// 平滑滚动 + 当前章节高亮
|
|
613
|
+
const links = document.querySelectorAll('nav.sidebar a');
|
|
614
|
+
const sections = document.querySelectorAll('section[id]');
|
|
615
|
+
|
|
616
|
+
links.forEach((a) => {
|
|
617
|
+
a.addEventListener('click', (e) => {
|
|
618
|
+
const id = a.getAttribute('href');
|
|
619
|
+
if (id && id.startsWith('#')) {
|
|
620
|
+
const target = document.querySelector(id);
|
|
621
|
+
if (target) {
|
|
622
|
+
e.preventDefault();
|
|
623
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
624
|
+
history.replaceState(null, '', id);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
// IntersectionObserver: 当前 section 进入视口 → 高亮对应 nav link
|
|
631
|
+
const obs = new IntersectionObserver((entries) => {
|
|
632
|
+
entries.forEach((entry) => {
|
|
633
|
+
if (!entry.isIntersecting) return;
|
|
634
|
+
const id = entry.target.id;
|
|
635
|
+
links.forEach((a) => {
|
|
636
|
+
a.classList.toggle('active', a.getAttribute('href') === '#' + id);
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
}, { rootMargin: '-30% 0px -60% 0px' });
|
|
640
|
+
|
|
641
|
+
sections.forEach((s) => obs.observe(s));
|
|
642
|
+
</script>
|
|
643
|
+
</body>
|
|
644
|
+
</html>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
name: welcome-html
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
appliesWhen: {}
|
|
4
|
+
priority: 5
|
|
5
|
+
files:
|
|
6
|
+
- from: files/welcome.html
|
|
7
|
+
to: .keel/welcome.html
|
|
8
|
+
render: true
|
|
9
|
+
- from: files/keel-README.md
|
|
10
|
+
to: .keel/README.md
|
|
11
|
+
render: true
|
|
12
|
+
- from: files/WELCOME.md
|
|
13
|
+
to: WELCOME.md
|
|
14
|
+
render: true
|