sdd-overview.html
12.6 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
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1" />
<title>SDD MVCP 总览 · 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="sdd-overview.html">
<div class="shell">
<aside id="sidebar"></aside>
<main class="page">
<header class="page-head">
<div class="crumb"><span class="pill">03 · SDD</span><span class="sep">/</span><span>MVCP 总览</span></div>
<h1>SDD · MVCP 总览</h1>
<p class="deck">SDD(System Design Document)用 <b>MVCP 四象限</b>组织:<b>M</b>odel · <b>V</b>iew · <b>C</b>ontrol · <b>P</b>rocess。每个象限是一篇独立页,每个定义带 <b>name / path / shape / contract / tests</b> 五字段。Claude Code 用 cheerio 即可机械抽取。</p>
</header>
<article class="body">
<section>
<h2>为什么是 MVCP 而不是 MVC</h2>
<p>传统 MVC 三分对一个有大量「长进程 / 异步循环」的系统不够用。DevLoop 系统里:</p>
<ul>
<li>L1/L2/L3 是<b>长时运行的状态机</b></li>
<li>每个 Agent 是<b>长寿子进程</b>(ACP session 复用)</li>
<li>SSE Broker 是<b>持续推送通道</b></li>
</ul>
<p>这些都不属于 Controller(即时响应请求),也不属于 Model(数据)。给它们一个独立象限 <b>Process</b> 才有归处。</p>
</section>
<section>
<h2>四象限</h2>
<svg viewBox="0 0 1100 480" width="100%" role="img" aria-label="MVCP 四象限">
<defs>
<linearGradient id="mg" x1="0" y1="0" x2="1" y2="1"><stop offset="0%" stop-color="#0E8C5A" stop-opacity=".12"/><stop offset="100%" stop-color="#0E8C5A" stop-opacity=".02"/></linearGradient>
<linearGradient id="vg" x1="0" y1="0" x2="1" y2="1"><stop offset="0%" stop-color="#6E48C3" stop-opacity=".12"/><stop offset="100%" stop-color="#6E48C3" stop-opacity=".02"/></linearGradient>
<linearGradient id="cg" x1="0" y1="0" x2="1" y2="1"><stop offset="0%" stop-color="#C77C2C" stop-opacity=".12"/><stop offset="100%" stop-color="#C77C2C" stop-opacity=".02"/></linearGradient>
<linearGradient id="pg" x1="0" y1="0" x2="1" y2="1"><stop offset="0%" stop-color="#C7341A" stop-opacity=".12"/><stop offset="100%" stop-color="#C7341A" stop-opacity=".02"/></linearGradient>
</defs>
<g font-family="JetBrains Mono" font-size="12" fill="#0E0E10">
<rect x="40" y="40" width="480" height="200" rx="8" fill="url(#mg)" stroke="#0E8C5A"/>
<text x="60" y="68" fill="#0E8C5A" font-weight="700" font-size="14">M · MODEL</text>
<text x="60" y="90" font-size="11">数据结构 · 文档 schema · 落盘格式</text>
<text x="60" y="115" font-size="11">• prd.html · sdd.html 结构</text>
<text x="60" y="135" font-size="11">• units.json · project.json · contract.json</text>
<text x="60" y="155" font-size="11">• runs/result.json · triage.json</text>
<text x="60" y="175" font-size="11">• Unit 六态机字典</text>
<text x="60" y="195" font-size="11">• 全部 TypeBox schema</text>
<text x="60" y="220" fill="#0E8C5A" font-size="11" font-weight="700">→ sdd-model.html</text>
<rect x="540" y="40" width="480" height="200" rx="8" fill="url(#vg)" stroke="#6E48C3"/>
<text x="560" y="68" fill="#6E48C3" font-weight="700" font-size="14">V · VIEW</text>
<text x="560" y="90" font-size="11">UI 组件 · 路由 · i18n</text>
<text x="560" y="115" font-size="11">• DevLoopDashboard.vue 组件树</text>
<text x="560" y="135" font-size="11">• 7 个子组件 props/slots/emits</text>
<text x="560" y="155" font-size="11">• router 路由表</text>
<text x="560" y="175" font-size="11">• i18n keys 全清单</text>
<text x="560" y="195" font-size="11">• 设计 token 引用</text>
<text x="560" y="220" fill="#6E48C3" font-size="11" font-weight="700">→ sdd-view.html</text>
<rect x="40" y="260" width="480" height="200" rx="8" fill="url(#cg)" stroke="#C77C2C"/>
<text x="60" y="288" fill="#C77C2C" font-weight="700" font-size="14">C · CONTROL</text>
<text x="60" y="310" font-size="11">即时响应:API · 服务方法 · 路由 handler</text>
<text x="60" y="335" font-size="11">• REST 端点全签名(7 条)</text>
<text x="60" y="355" font-size="11">• LoopController 公共方法</text>
<text x="60" y="375" font-size="11">• DocStore / UnitRegistry / SSEBroker 方法</text>
<text x="60" y="395" font-size="11">• Agent callAgent 调用约定</text>
<text x="60" y="415" font-size="11">• 中间件(auth · jsonGuard · sanitizer)</text>
<text x="60" y="440" fill="#C77C2C" font-size="11" font-weight="700">→ sdd-control.html</text>
<rect x="540" y="260" width="480" height="200" rx="8" fill="url(#pg)" stroke="#C7341A"/>
<text x="560" y="288" fill="#C7341A" font-weight="700" font-size="14">P · PROCESS</text>
<text x="560" y="310" font-size="11">长进程:状态机 · Loop · 后台任务</text>
<text x="560" y="335" font-size="11">• L1 / L2 / L3 状态机详尽转换</text>
<text x="560" y="355" font-size="11">• Agent 生命周期 (启动/复用/回收)</text>
<text x="560" y="375" font-size="11">• SSE 订阅 / 心跳 / 重连</text>
<text x="560" y="395" font-size="11">• retryWithReflection 算法</text>
<text x="560" y="415" font-size="11">• 清理 / 归档 / 预算守护</text>
<text x="560" y="440" fill="#C7341A" font-size="11" font-weight="700">→ sdd-process.html</text>
</g>
</svg>
</section>
<section>
<h2>每个定义的「五字段标准」</h2>
<p>所有 MVCP 页里的每条定义都强制遵循下表结构,便于 Claude Code 用同一套抽取器处理:</p>
<table>
<thead><tr><th>字段</th><th>类型</th><th>含义</th><th>抽取选择器</th></tr></thead>
<tbody>
<tr><td><b>name</b></td><td>string · 必填</td><td>唯一标识符 · 命名空间.对象名</td><td><code>section[data-def-name]</code></td></tr>
<tr><td><b>path</b></td><td>string · 必填</td><td>预期落盘路径或文件位置</td><td><code>[data-def-name] code.path</code></td></tr>
<tr><td><b>shape</b></td><td>code · 必填</td><td>TypeBox / TS interface / Vue defineProps / SQL DDL</td><td><code>[data-def-name] pre.shape</code></td></tr>
<tr><td><b>contract</b></td><td>list · 选填</td><td>前置 / 后置条件 / 不变式</td><td><code>[data-def-name] ul.contract li</code></td></tr>
<tr><td><b>tests</b></td><td>list · 选填</td><td>验收用例(Tester Agent 直接转 vitest)</td><td><code>[data-def-name] ul.tests li</code></td></tr>
</tbody>
</table>
<h3>HTML 模板示例</h3>
<pre class="code"><span class="k"><section</span> <span class="n">data-def-name</span>=<span class="s">"DocStore.writePRD"</span> <span class="n">data-mvcp</span>=<span class="s">"control"</span><span class="k">></span>
<span class="k"><h3></span><span class="v">DocStore.writePRD(pid, html)</span><span class="k"></h3></span>
<span class="k"><code</span> <span class="n">class</span>=<span class="s">"path"</span><span class="k">></span><span class="v">server/services/doc-store.js</span><span class="k"></code></span>
<span class="k"><pre</span> <span class="n">class</span>=<span class="s">"shape"</span><span class="k">></span><span class="v">
async function writePRD(pid: string, html: string): Promise<{rev: number}>
</span><span class="k"></pre></span>
<span class="k"><ul</span> <span class="n">class</span>=<span class="s">"contract"</span><span class="k">></span>
<span class="k"><li></span><span class="v">前置:html 必须包含顶级 article[data-doc="prd"]</span><span class="k"></li></span>
<span class="k"><li></span><span class="v">后置:旧版本归档到 history/prd-rev-{rev}.html</span><span class="k"></li></span>
<span class="k"></ul></span>
<span class="k"><ul</span> <span class="n">class</span>=<span class="s">"tests"</span><span class="k">></span>
<span class="k"><li></span><span class="v">写入后 readPRD 能读回完全一致内容</span><span class="k"></li></span>
<span class="k"><li></span><span class="v">非法 HTML 抛 ValidationError</span><span class="k"></li></span>
<span class="k"></ul></span>
<span class="k"></section></span></pre>
<p class="sig">本套文档因美观需要不完全遵守这个最严格的 HTML 模板,但每条定义至少含 name + path + shape。完全机械化版会在 P1 实施时由 Architect Agent 产出。</p>
</section>
<section>
<h2>抽取流程</h2>
<pre class="code"><span class="c">// Claude Code 抽取 build-manifest 的伪代码</span>
<span class="k">const</span> manifest = <span class="p">{</span> model: <span class="p">[]</span>, view: <span class="p">[]</span>, control: <span class="p">[]</span>, process: <span class="p">[]</span> <span class="p">}</span>;
<span class="k">for</span> (<span class="k">const</span> mvcp <span class="k">of</span> <span class="p">[</span><span class="s">'model'</span>, <span class="s">'view'</span>, <span class="s">'control'</span>, <span class="s">'process'</span><span class="p">]</span>) <span class="p">{</span>
<span class="k">const</span> html = <span class="k">await</span> <span class="n">fetch</span>(<span class="s">`sdd-</span><span class="v">${mvcp}</span><span class="s">.html`</span>).<span class="n">then</span>(r => r.<span class="n">text</span>());
<span class="k">const</span> $ = cheerio.<span class="n">load</span>(html);
$(<span class="s">'[data-def-name]'</span>).<span class="n">each</span>((_, el) => <span class="p">{</span>
manifest[mvcp].<span class="n">push</span>(<span class="p">{</span>
name: $(el).<span class="n">attr</span>(<span class="s">'data-def-name'</span>),
path: $(el).<span class="n">find</span>(<span class="s">'code.path'</span>).<span class="n">first</span>().<span class="n">text</span>(),
shape: $(el).<span class="n">find</span>(<span class="s">'pre.shape'</span>).<span class="n">text</span>(),
contract: $(el).<span class="n">find</span>(<span class="s">'ul.contract li'</span>).<span class="n">map</span>((_,li) => $(li).<span class="n">text</span>()).<span class="n">get</span>(),
tests: $(el).<span class="n">find</span>(<span class="s">'ul.tests li'</span>).<span class="n">map</span>((_,li) => $(li).<span class="n">text</span>()).<span class="n">get</span>(),
<span class="p">}</span>);
<span class="p">}</span>);
<span class="p">}</span>
<span class="n">fs</span>.<span class="n">writeFileSync</span>(<span class="s">'build-manifest.json'</span>, <span class="n">JSON.stringify</span>(manifest, <span class="v">null</span>, <span class="v">2</span>));</pre>
<p>结果是一份 <code>build-manifest.json</code>,能直接喂给 Architect Agent 生成 units.json + Unit spec 文件。</p>
</section>
<section>
<h2>四象限对应 Agent</h2>
<table class="dense">
<thead><tr><th>象限</th><th>谁负责写</th><th>谁负责消费</th></tr></thead>
<tbody>
<tr><td>Model</td><td>Architect</td><td>Coder(读 schema 生成数据访问代码)· DocStore(自身实现)</td></tr>
<tr><td>View</td><td>Architect</td><td>Coder(fe 类 Unit 直接对应)</td></tr>
<tr><td>Control</td><td>Architect</td><td>Coder(be 类 Unit)· Integrator(跑端点)</td></tr>
<tr><td>Process</td><td>Architect</td><td>所有 Agent 都要读懂 Process 才知道自己在生命周期哪一步</td></tr>
</tbody>
</table>
</section>
</article>
<nav class="prev-next"></nav>
<footer class="site"><div><b>Section</b>03 · SDD</div><div><b>Page</b>sdd-overview.html</div></footer>
</main>
</div>
<script src="assets/shell.js"></script>
</body>
</html>