plan-code-lifecycle.html
12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Code Lifecycle · LemonBoss PRD</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,500;9..144,700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="assets/shell.css" />
</head>
<body data-page="plan-code-lifecycle.html">
<div class="shell">
<aside id="sidebar"></aside>
<main class="page">
<header class="page-head">
<div class="crumb"><span class="pill">08 · PLAN</span><span class="sep">/</span><span>Code Lifecycle</span></div>
<h1>生成代码的去向</h1>
<p class="deck">Coder 生成的 <code>units/{id}/code/**</code> shipped 后<b>去哪</b>?继续在沙箱里?合到主仓库?开 PR?本页给出 v1 决策与升级路径。</p>
</header>
<article class="body">
<section>
<h2>① 三种归宿</h2>
<table>
<thead><tr><th>方案</th><th>位置</th><th>优</th><th>劣</th></tr></thead>
<tbody>
<tr><td><b>A · 留沙箱(v1 默认)</b></td><td><code>data/companies/{cid}/projects/{pid}/units/**</code></td><td>零集成成本;可被复现脚本读;隔离强</td><td>不能直接运行在 LemonBoss 主进程;需手动导出</td></tr>
<tr><td><b>B · 手动 export 导出</b></td><td>人类点「导出」按钮 → 下载 zip</td><td>简单;人类可控;适合外部交付</td><td>没有 git 历史;不接 CI/CD</td></tr>
<tr><td><b>C · 自动开 GitHub PR</b></td><td>主仓库的 feature branch</td><td>真集成;CI 自动跑;review 流程</td><td>需 GitHub token / 权限;branch 命名冲突;merge 策略复杂</td></tr>
</tbody>
</table>
<div class="callout ok">
<b>v1 决策:A + B。</b>C 留到 v2 视真实需要再说。本套文档对外宣称"生成代码",但 v1 不承诺自动合并到生产仓库。
</div>
</section>
<section>
<h2>② 沙箱代码的生命周期</h2>
<svg viewBox="0 0 1100 320" width="100%" role="img">
<defs><marker id="arrCL" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 Z" fill="#0E0E10"/></marker></defs>
<g font-family="JetBrains Mono" font-size="11" fill="#0E0E10">
<rect x="40" y="40" width="160" height="50" rx="6" fill="#fff" stroke="#0E0E10"/><text x="120" y="65" text-anchor="middle">生成期</text><text x="120" y="80" text-anchor="middle" font-size="9">Coder 写</text>
<rect x="240" y="40" width="160" height="50" rx="6" fill="#FFF6CC" stroke="#8a6b00"/><text x="320" y="65" text-anchor="middle">测试期</text><text x="320" y="80" text-anchor="middle" font-size="9">Tester 跑</text>
<rect x="440" y="40" width="160" height="50" rx="6" fill="#F7E8D2" stroke="#C77C2C"/><text x="520" y="65" text-anchor="middle">集成期</text><text x="520" y="80" text-anchor="middle" font-size="9">Integrator</text>
<rect x="640" y="40" width="160" height="50" rx="6" fill="#E2F2EA" stroke="#0E8C5A"/><text x="720" y="65" text-anchor="middle">shipped</text><text x="720" y="80" text-anchor="middle" font-size="9">v1 默认: 留这</text>
<rect x="840" y="40" width="220" height="50" rx="6" fill="#ECE4FB" stroke="#6E48C3"/><text x="950" y="65" text-anchor="middle">manual export / archive</text>
<rect x="40" y="170" width="240" height="60" rx="6" fill="#fff" stroke="#0E0E10"/>
<text x="160" y="195" text-anchor="middle" font-weight="700">导出动作(人类触发)</text>
<text x="160" y="213" text-anchor="middle" font-size="9">POST /api/devloop/.../export</text>
<rect x="320" y="170" width="240" height="60" rx="6" fill="#fff" stroke="#0E0E10"/>
<text x="440" y="195" text-anchor="middle" font-weight="700">打包成 zip</text>
<text x="440" y="213" text-anchor="middle" font-size="9">含全部 units/*/code/</text>
<rect x="600" y="170" width="240" height="60" rx="6" fill="#fff" stroke="#0E0E10"/>
<text x="720" y="195" text-anchor="middle" font-weight="700">下载 / 解压</text>
<text x="720" y="213" text-anchor="middle" font-size="9">人类决定怎么集成</text>
<rect x="880" y="170" width="180" height="60" rx="6" fill="#ECE4FB" stroke="#6E48C3"/>
<text x="970" y="195" text-anchor="middle">git merge / npm publish / Docker build</text>
<g stroke="#0E0E10" fill="none" marker-end="url(#arrCL)">
<line x1="200" y1="65" x2="235" y2="65"/>
<line x1="400" y1="65" x2="435" y2="65"/>
<line x1="600" y1="65" x2="635" y2="65"/>
<line x1="800" y1="65" x2="835" y2="65"/>
<line x1="720" y1="90" x2="160" y2="165"/>
<line x1="280" y1="200" x2="315" y2="200"/>
<line x1="560" y1="200" x2="595" y2="200"/>
<line x1="840" y1="200" x2="875" y2="200"/>
</g>
</g>
</svg>
</section>
<section>
<h2>③ Export API · v1 实现</h2>
<pre class="code shape"><span class="c">// server/routes/devloop.js · 新增</span>
router.<span class="n">post</span>(<span class="s">'/projects/:pid/export'</span>, requireAuth, <span class="k">async</span> (req, res) => <span class="p">{</span>
<span class="k">const</span> <span class="p">{</span> pid <span class="p">}</span> = req.params;
<span class="k">const</span> <span class="p">{</span> include = <span class="s">'code'</span> <span class="p">}</span> = req.body; <span class="c">// code / code+tests / all</span>
<span class="k">const</span> archiver = <span class="n">require</span>(<span class="s">'archiver'</span>);
res.<span class="n">setHeader</span>(<span class="s">'Content-Type'</span>, <span class="s">'application/zip'</span>);
res.<span class="n">setHeader</span>(<span class="s">'Content-Disposition'</span>, <span class="s">`attachment; filename="</span><span class="v">${pid}</span><span class="s">.zip"`</span>);
<span class="k">const</span> zip = <span class="n">archiver</span>(<span class="s">'zip'</span>);
zip.<span class="n">pipe</span>(res);
<span class="k">const</span> root = <span class="n">projectsDir</span>(pid);
<span class="k">if</span> (include.<span class="n">includes</span>(<span class="s">'code'</span>)) zip.<span class="n">directory</span>(path.<span class="n">join</span>(root, <span class="s">'units'</span>), <span class="s">'units'</span>);
<span class="k">if</span> (include.<span class="n">includes</span>(<span class="s">'tests'</span>)) zip.<span class="n">directory</span>(path.<span class="n">join</span>(root, <span class="s">'integration'</span>),<span class="s">'integration'</span>);
<span class="k">if</span> (include.<span class="n">includes</span>(<span class="s">'docs'</span>)) <span class="p">{</span>
zip.<span class="n">directory</span>(path.<span class="n">join</span>(root, <span class="s">'prd'</span>), <span class="s">'prd'</span>);
zip.<span class="n">directory</span>(path.<span class="n">join</span>(root, <span class="s">'sdd'</span>), <span class="s">'sdd'</span>);
<span class="p">}</span>
<span class="c">// 附 README 说明项目结构</span>
zip.<span class="n">append</span>(<span class="n">buildReadme</span>(pid), <span class="p">{</span> name: <span class="s">'README.md'</span> <span class="p">}</span>);
<span class="k">await</span> zip.<span class="n">finalize</span>();
<span class="p">}</span>);</pre>
<p class="sig">archiver 在 LemonBoss 既有依赖(<code>"archiver": "^7.0.1"</code>),零新增包。</p>
</section>
<section>
<h2>④ 导出包里的 README 模板</h2>
<pre class="code"># Project {pid}
由 LemonBoss DevLoop v1 生成 · {timestamp}
## 结构
```
units/
├── todo-be/code/ 后端代码
├── todo-be/tests/ Tester 写的 vitest
├── todo-be/contract.json 接口契约
├── todo-fe/...
integration/spec.json 端到端用例
prd/ 原始需求文档 (HTML 站点)
sdd/ 架构设计文档 (HTML 站点)
```
## 跑起来
```bash
cd units/todo-be
npm install # 各 Unit 独立装包
PORT=3010 node code/server.js
# 另起前端 (如有)
cd units/todo-fe
npm install
npm run dev
```
## 测试
```bash
cd units/todo-be
vitest run # 跑 Tester 写的 spec
```
## 集成
```bash
# 在项目根
node integration/runner.js # 启动所有 be + 跑 spec.json
```
## 合到主仓库(手动)
没有自动 PR 流程。建议:
1. cp -r units/*/code/ <your-repo>/services/
2. 整理目录结构、合并 package.json
3. 跑 lint / 格式化(DevLoop 不强制风格)
4. 自己开 PR
## License
继承 LemonBoss 主仓库。</pre>
</section>
<section>
<h2>⑤ 沙箱代码的保留期</h2>
<p>shipped 项目的 <code>units/</code> 默认<b>永久保留</b>,因为:</p>
<ul>
<li>占用极小(中项目 ~30MB 含日志)</li>
<li>反推决策需要原始代码 + coder.log</li>
<li>memory 系统会引用具体 Unit 实现做风格参考</li>
</ul>
<p>清理策略(写在 <code>project.json</code>):</p>
<table class="dense">
<thead><tr><th>字段</th><th>默认</th><th>含义</th></tr></thead>
<tbody>
<tr><td><code>compressOldRuns</code></td><td>false</td><td>14 天前的 runs/*/stdout.txt 自动 gzip</td></tr>
<tr><td><code>keepCodeMonths</code></td><td>12</td><td>shipped 12 个月后归档 code/ 到 history/archived-units/</td></tr>
<tr><td><code>autoExportOnShip</code></td><td>false</td><td>shipped 时自动生成 export.zip 放项目目录</td></tr>
</tbody>
</table>
</section>
<section>
<h2>⑥ v2 路径 · 自动 PR</h2>
<p>v2 会加 <code>POST .../export-to-github</code>,参数:</p>
<pre class="code"><span class="p">{</span>
<span class="n">"repo"</span>: <span class="s">"yibo/lemonboss-services"</span>,
<span class="n">"branch"</span>: <span class="s">"devloop/p-731042"</span>,
<span class="n">"target_path"</span>: <span class="s">"services/todo/"</span>,
<span class="n">"commit_message"</span>: <span class="s">"feat: add TODO API + UI (via DevLoop)"</span>,
<span class="n">"open_pr"</span>: <span class="v">true</span>,
<span class="n">"github_token"</span>: <span class="s">"${secret}"</span>
<span class="p">}</span></pre>
<p>逻辑:调 gh CLI / @octokit/rest 创建 branch + 提交 + 开 PR。<b>v1 不做</b>。</p>
</section>
<section>
<h2>⑦ 跨项目共享 utils</h2>
<p>常见担忧:5 个项目都写一遍 JWT 中间件,沙箱内重复实现。v1 接受这个浪费;v2 视情况引入 <code>data/companies/{cid}/shared/</code>,约定:</p>
<ul>
<li>Architect 检测多 Unit 共享 util 时,建议拆 <code>shared-*</code> Unit</li>
<li>shared Unit 的 contract.json 跨项目稳定</li>
<li>Coder 读 shared 时<b>只读 contract</b>,不复制实现</li>
</ul>
<p class="sig">v1 简化:每个项目重复实现 utils。memory 系统会记"这次写了 JWT mw, 下次可以照样",但不会自动拆 shared。</p>
</section>
<section>
<h2>⑧ 关联</h2>
<ul>
<li><a href="data-layout.html">data-layout</a> · units/ 在文件系统里的位置</li>
<li><a href="plan-migration.html">plan-migration</a> · 各 Phase 何时开始关心代码归宿</li>
<li><a href="plan-reproducibility.html">复现指南</a> · 外部 Claude Code 怎么从沙箱代码 + docs 复现整套</li>
</ul>
</section>
</article>
<nav class="prev-next"></nav>
<footer class="site"><div><b>Section</b>08 · Plan</div><div><b>Page</b>plan-code-lifecycle.html</div></footer>
</main>
</div>
<script src="assets/shell.js"></script>
</body>
</html>