plan-code-lifecycle.html 12.2 KB
<!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) =&gt; <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/ &lt;your-repo&gt;/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>