1fcd811c12
8 章设计文档 HTML(架构决策/状态设计/DB Schema/记忆注入/Agent 规格/ 运维护栏/安全沙箱/前端 API 契约),全图深色内联 SVG,附 docs/index.html 索引。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2639 lines
220 KiB
HTML
2639 lines
220 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Maestro 重构全景 · 现状→目标→计划</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,600;1,400&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root{--bg:#0d0d0d;--bg2:#141414;--bg3:#1a1a1a;--border:#252525;--border2:#333;--text:#e8e8e8;--text2:#a0a0a0;--text3:#555;--cyan:#00d4d4;--cyan2:#009999;--green:#4ec94e;--green2:#2d6e2d;--amber:#f0a500;--amber2:#9c6a00;--violet:#9b7bff;--violet2:#6644cc;--red:#ff5555;--red2:#aa2222;--blue:#5599ff;--blue2:#2255cc;--code-bg:#0a0a0a;--shadow:0 4px 32px rgba(0,0,0,.7)}
|
||
.light{--bg:#f5f4ef;--bg2:#eae9e4;--bg3:#dddcd7;--border:#ccc;--border2:#bbb;--text:#111;--text2:#555;--text3:#999;--cyan:#006666;--cyan2:#004444;--green:#1a5e1a;--green2:#0d380d;--amber:#7a5200;--amber2:#5a3c00;--violet:#4422aa;--violet2:#2211aa;--red:#991111;--red2:#770000;--blue:#1133aa;--blue2:#001188;--code-bg:#e8e7e2;--shadow:0 4px 16px rgba(0,0,0,.12)}
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{font-family:'IBM Plex Mono',monospace;background:var(--bg);color:var(--text);font-size:13px;line-height:1.7}
|
||
/* topbar */
|
||
.topbar{position:sticky;top:0;z-index:100;background:var(--bg2);border-bottom:1px solid var(--border);height:48px;display:flex;align-items:center;padding:0 24px;gap:16px}
|
||
.topbar-brand{font-size:13px;font-weight:600;color:var(--cyan);letter-spacing:.06em}
|
||
.topbar-meta{font-size:11px;color:var(--text3);margin-left:auto}
|
||
.theme-btn{background:none;border:1px solid var(--border2);color:var(--text2);font-family:inherit;font-size:11px;padding:3px 10px;border-radius:4px;cursor:pointer;transition:all .15s}
|
||
.theme-btn:hover{border-color:var(--cyan);color:var(--cyan)}
|
||
/* sidebar layout */
|
||
.main-layout{display:flex;height:calc(100vh - 48px);overflow:hidden}
|
||
.sidebar{width:176px;background:var(--bg2);border-right:1px solid var(--border);flex-shrink:0;position:sticky;top:48px;height:calc(100vh - 48px);overflow-y:auto;display:flex;flex-direction:column}
|
||
.content{flex:1;overflow-y:auto}
|
||
/* tabs (now vertical) */
|
||
.section-tabs{display:flex;flex-direction:column;padding:8px 0;flex:1}
|
||
.tab{padding:11px 18px;font-size:12px;color:var(--text2);cursor:pointer;border-left:2px solid transparent;transition:all .15s;white-space:nowrap;text-align:left;line-height:1.5}
|
||
.tab:hover{color:var(--text);background:var(--bg3)}
|
||
.tab.active{color:var(--cyan);border-left-color:var(--cyan);background:rgba(0,212,212,.06)}
|
||
/* sections */
|
||
.section{display:none;max-width:1280px;margin:0 auto;padding:32px 40px}
|
||
.section.visible{display:block}
|
||
/* card */
|
||
.card{background:var(--bg2);border:1px solid var(--border);border-radius:8px;padding:20px;margin-bottom:24px}
|
||
.card-title{font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:var(--text3);margin-bottom:12px}
|
||
/* headings */
|
||
.sec-heading{font-size:22px;font-weight:600;color:var(--text);margin-bottom:6px}
|
||
.sec-desc{font-size:13px;color:var(--text2);margin-bottom:28px;line-height:1.8}
|
||
/* arch diagram */
|
||
.arch-diagram{background:var(--code-bg);border:1px solid var(--border);border-radius:8px;padding:20px;font-size:11.5px;line-height:1.9;overflow-x:auto;white-space:pre;color:var(--text2);margin-bottom:20px}
|
||
.arch-diagram .lbl{color:var(--cyan);font-weight:600}
|
||
.arch-diagram .nd{color:var(--amber)}
|
||
.arch-diagram .grp{color:var(--text3)}
|
||
.arch-diagram .new{color:var(--green);font-weight:600}
|
||
.arch-diagram .warn{color:var(--red)}
|
||
.arch-diagram .note{color:var(--text3);font-style:italic}
|
||
/* grids */
|
||
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:20px}
|
||
.grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-bottom:20px}
|
||
.grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:20px}
|
||
@media(max-width:900px){.grid-2,.grid-3,.grid-4{grid-template-columns:1fr}}
|
||
/* box */
|
||
.box{background:var(--bg3);border:1px solid var(--border);border-radius:6px;padding:12px 14px}
|
||
.box h4{font-size:10px;text-transform:uppercase;letter-spacing:.08em;color:var(--text3);margin-bottom:8px}
|
||
.box.cyan{border-left:3px solid var(--cyan)}
|
||
.box.green{border-left:3px solid var(--green)}
|
||
.box.amber{border-left:3px solid var(--amber)}
|
||
.box.violet{border-left:3px solid var(--violet)}
|
||
.box.red{border-left:3px solid var(--red)}
|
||
.box.blue{border-left:3px solid var(--blue)}
|
||
/* rows */
|
||
.row{display:flex;gap:8px;margin-bottom:4px;font-size:12px;color:var(--text2);align-items:flex-start}
|
||
.row code{color:var(--cyan);background:var(--code-bg);padding:1px 5px;border-radius:3px;font-size:11px;flex-shrink:0}
|
||
/* table */
|
||
.tbl{width:100%;border-collapse:collapse;font-size:12px;margin-bottom:16px}
|
||
.tbl th{text-align:left;padding:6px 10px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--text3);border-bottom:1px solid var(--border)}
|
||
.tbl td{padding:7px 10px;border-bottom:1px solid var(--border);color:var(--text2);vertical-align:top}
|
||
.tbl td:first-child{color:var(--text);font-weight:500}
|
||
.tbl tr:last-child td{border-bottom:none}
|
||
/* badge */
|
||
.badge{display:inline-flex;align-items:center;padding:2px 7px;border-radius:4px;font-size:10px;font-weight:600;border:1px solid;margin-right:4px}
|
||
.badge.cyan{color:var(--cyan);border-color:var(--cyan2);background:rgba(0,212,212,.07)}
|
||
.badge.green{color:var(--green);border-color:var(--green2);background:rgba(78,201,78,.07)}
|
||
.badge.amber{color:var(--amber);border-color:var(--amber2);background:rgba(240,165,0,.08)}
|
||
.badge.violet{color:var(--violet);border-color:var(--violet2);background:rgba(155,123,255,.08)}
|
||
.badge.red{color:var(--red);border-color:var(--red2);background:rgba(255,85,85,.07)}
|
||
/* change tag */
|
||
.chg{display:inline-block;font-size:10px;padding:1px 5px;border-radius:3px;font-weight:600;margin-left:4px}
|
||
.chg.add{background:rgba(78,201,78,.15);color:var(--green)}
|
||
.chg.mod{background:rgba(240,165,0,.13);color:var(--amber)}
|
||
.chg.del{background:rgba(255,85,85,.13);color:var(--red)}
|
||
/* diff panels */
|
||
.diff-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:20px}
|
||
.diff-panel{background:var(--code-bg);border:1px solid var(--border);border-radius:6px;padding:14px;overflow-x:auto}
|
||
.diff-panel h4{font-size:10px;text-transform:uppercase;letter-spacing:.1em;margin-bottom:10px;padding-bottom:6px;border-bottom:1px solid var(--border)}
|
||
.diff-panel.before h4{color:var(--red)}
|
||
.diff-panel.after h4{color:var(--green)}
|
||
.diff-panel pre{font-size:11px;line-height:1.7;color:var(--text2)}
|
||
.diff-panel pre .add{color:var(--green)}
|
||
.diff-panel pre .del{color:var(--red);opacity:.6;text-decoration:line-through}
|
||
.diff-panel pre .key{color:var(--violet)}
|
||
.diff-panel pre .cmt{color:var(--text3);font-style:italic}
|
||
/* pipeline */
|
||
.pipe-stage{display:flex;align-items:center;flex-wrap:wrap;gap:4px;margin-bottom:8px}
|
||
.pipe-box{padding:5px 10px;border-radius:4px;font-size:11px;font-weight:600;border:1px solid;white-space:nowrap}
|
||
.pipe-box.exec{color:var(--cyan);border-color:var(--cyan2);background:rgba(0,212,212,.07)}
|
||
.pipe-box.gate{color:var(--amber);border-color:var(--amber2);background:rgba(240,165,0,.1)}
|
||
.pipe-box.gate-new{color:var(--green);border-color:var(--green2);background:rgba(78,201,78,.1)}
|
||
.pipe-box.review{color:var(--violet);border-color:var(--violet2);background:rgba(155,123,255,.1)}
|
||
.pipe-box.human{color:var(--blue);border-color:var(--blue2);background:rgba(85,153,255,.1)}
|
||
.pipe-arr{color:var(--text3);margin:0 2px;font-size:14px}
|
||
/* progress bar */
|
||
.prog-bar-wrap{background:var(--bg2);border-bottom:1px solid var(--border);padding:8px 24px;display:flex;align-items:center;gap:12px}
|
||
.prog-label{font-size:11px;color:var(--text2);min-width:90px}
|
||
.prog-track{flex:1;height:5px;background:var(--border);border-radius:3px;overflow:hidden}
|
||
.prog-fill{height:100%;background:linear-gradient(90deg,var(--green),var(--cyan));border-radius:3px;transition:width .4s}
|
||
.prog-pct{font-size:11px;color:var(--green);min-width:36px;text-align:right}
|
||
/* task cards */
|
||
.task-card{background:var(--bg2);border:1px solid var(--border);border-radius:8px;margin-bottom:24px;overflow:hidden}
|
||
.task-hdr{display:flex;align-items:center;gap:12px;padding:14px 18px;border-bottom:1px solid var(--border);cursor:pointer;user-select:none}
|
||
.task-num{width:26px;height:26px;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:600;flex-shrink:0}
|
||
.t1{background:rgba(0,212,212,.12);color:var(--cyan)}
|
||
.t2{background:rgba(155,123,255,.12);color:var(--violet)}
|
||
.t3{background:rgba(240,165,0,.12);color:var(--amber)}
|
||
.t4{background:rgba(78,201,78,.12);color:var(--green)}
|
||
.t5{background:rgba(255,85,85,.12);color:var(--red)}
|
||
.t6{background:rgba(85,153,255,.12);color:var(--blue)}
|
||
.task-info{flex:1}
|
||
.task-title2{font-size:13px;font-weight:600;color:var(--text)}
|
||
.task-sub{font-size:11px;color:var(--text3);margin-top:2px}
|
||
.task-prog{font-size:11px;color:var(--text3)}
|
||
.task-prog .dn{color:var(--green)}
|
||
.caret{color:var(--text3);font-size:15px;transition:transform .2s}
|
||
.task-card.collapsed .caret{transform:rotate(-90deg)}
|
||
.task-body{padding:18px}
|
||
.task-card.collapsed .task-body{display:none}
|
||
/* steps */
|
||
.steps{list-style:none}
|
||
.step{display:grid;grid-template-columns:22px 1fr;gap:10px;margin-bottom:18px;align-items:flex-start}
|
||
.chkbox{width:18px;height:18px;border:1px solid var(--border2);border-radius:3px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:4px;transition:all .15s;background:var(--bg)}
|
||
.chkbox:hover{border-color:var(--green)}
|
||
.chkbox.checked{background:var(--green2);border-color:var(--green)}
|
||
.chkbox.checked::after{content:'✓';font-size:11px;color:var(--green)}
|
||
.step-ttl{font-size:12px;font-weight:600;margin-bottom:6px;display:flex;align-items:center;gap:6px}
|
||
.kind{font-size:10px;padding:1px 5px;border-radius:3px;font-weight:600}
|
||
.kind.test{background:rgba(155,123,255,.15);color:var(--violet)}
|
||
.kind.run{background:rgba(0,212,212,.12);color:var(--cyan)}
|
||
.kind.impl{background:rgba(240,165,0,.12);color:var(--amber)}
|
||
.kind.commit{background:rgba(78,201,78,.12);color:var(--green)}
|
||
.step-desc{font-size:12px;color:var(--text2);margin-bottom:6px;line-height:1.7}
|
||
.step-desc code,.step-con code{color:var(--cyan);background:var(--code-bg);padding:1px 4px;border-radius:2px;font-size:11px}
|
||
pre{background:var(--code-bg);border:1px solid var(--border);border-radius:6px;padding:12px 14px;overflow-x:auto;margin:6px 0;font-size:11px;line-height:1.6}
|
||
pre .kw{color:var(--violet)}
|
||
pre .str{color:var(--green)}
|
||
pre .cm{color:var(--text3);font-style:italic}
|
||
pre .fn{color:var(--cyan)}
|
||
pre .nu{color:var(--amber)}
|
||
pre .tp{color:var(--amber)}
|
||
.note{background:var(--bg3);border-left:3px solid var(--amber);border-radius:4px;padding:10px 14px;margin:8px 0;font-size:12px;color:var(--text2);line-height:1.7}
|
||
.note code{color:var(--cyan);background:var(--code-bg);padding:1px 4px;border-radius:2px;font-size:11px}
|
||
hr{border:none;border-top:1px solid var(--border);margin:24px 0}
|
||
/* design sub-nav */
|
||
.design-sticky{position:sticky;top:0;z-index:50;background:var(--bg);margin:-32px -40px 24px;padding:16px 40px 12px;border-bottom:1px solid var(--border)}
|
||
.design-sticky .sec-heading{margin-bottom:4px}
|
||
.design-sticky .sec-desc{margin-bottom:14px}
|
||
.design-nav{display:flex;gap:4px;flex-wrap:wrap;margin:0}
|
||
.design-nav a{font-size:11px;padding:5px 12px;border-radius:4px;color:var(--text2);text-decoration:none;border:1px solid var(--border);transition:all .15s;white-space:nowrap}
|
||
.design-nav a:hover{color:var(--cyan);border-color:var(--cyan)}
|
||
.design-nav a.act{color:var(--cyan);border-color:var(--cyan);background:rgba(0,212,212,.07)}
|
||
.chapter-anchor{display:block;height:0;margin-top:-2px;padding-top:2px;scroll-margin-top:150px}
|
||
.chapter-hdr{font-size:14px;font-weight:600;color:var(--text);margin:32px 0 16px;padding-bottom:8px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px}
|
||
.chapter-hdr span{font-size:10px;color:var(--cyan);border:1px solid var(--cyan2);border-radius:4px;padding:2px 8px;font-weight:600}
|
||
.chapter-hdr .phase{margin-left:auto;font-size:10px;font-weight:600;border-radius:10px;padding:2px 10px;border:1px solid}
|
||
.chapter-hdr .phase.p1{color:var(--green);border-color:var(--green2);background:rgba(78,201,78,.1)}
|
||
.chapter-hdr .phase.p2{color:var(--violet);border-color:var(--violet2);background:rgba(155,123,255,.1)}
|
||
.chapter-hdr .phase.mix{color:var(--amber);border-color:var(--amber2);background:rgba(240,165,0,.1)}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="topbar">
|
||
<span class="topbar-brand">MAESTRO · 重构全景</span>
|
||
<span class="topbar-meta">2026-06-22 · 现状 → 目标 → 计划</span>
|
||
<button class="theme-btn" id="themeBtn">切换主题</button>
|
||
</div>
|
||
|
||
|
||
<div class="main-layout">
|
||
<div class="sidebar">
|
||
<div class="section-tabs">
|
||
<div class="tab active" onclick="showSec('s-current',this)">① 现状架构</div>
|
||
<div class="tab" onclick="showSec('s-target',this)">② 重构目标</div>
|
||
<div class="tab" onclick="showSec('s-design',this)">③ 设计方案</div>
|
||
<div class="tab" onclick="showSec('s-impl',this)">④ 具体实现</div>
|
||
</div>
|
||
</div><!-- /.sidebar -->
|
||
<div class="content">
|
||
|
||
|
||
<div class="prog-bar-wrap" id="prog-wrap" style="display:none">
|
||
<span class="prog-label">实施进度</span>
|
||
<div class="prog-track"><div class="prog-fill" id="prog-fill" style="width:0%"></div></div>
|
||
<span class="prog-pct" id="prog-pct">0%</span>
|
||
</div>
|
||
|
||
<!-- ═══════════════════════════════ ① 现状架构 ═══════════════════════════════ -->
|
||
<div class="section visible" id="s-current">
|
||
<div class="sec-heading">现状架构</div>
|
||
<div class="sec-desc">Maestro 当前系统全景——进程模型、前后端、数据流、调度算法、执行管道与模型档位。</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">系统总览</div>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1180 716" style="width:100%;display:block;margin:16px 0;background:#0a0f1a;border-radius:6px;">
|
||
<text x="24" y="26" font-family="monospace" font-size="15" font-weight="bold" fill="#e8e8e8">MAESTRO 系统架构(现状)</text>
|
||
<rect x="930" y="12" width="238" height="22" fill="#11161f" stroke="#252b36" rx="4"/>
|
||
<text x="940" y="27" font-family="monospace" font-size="9.5" fill="#e07070">⚠ 现状问题点 / 重构待改</text>
|
||
<rect x="12" y="40" width="1156" height="92" fill="#0b1626" stroke="#1e4a7a" stroke-width="1" rx="4"/>
|
||
<rect x="12" y="40" width="1156" height="22" fill="#1e3a5f" rx="4"/>
|
||
<rect x="12" y="54" width="1156" height="8" fill="#1e3a5f"/>
|
||
<text x="22" y="55" font-family="monospace" font-size="11" font-weight="bold" fill="#60a5fa">用户端</text>
|
||
<rect x="28" y="70" width="200" height="50" fill="#13243d" stroke="#1e4a7a" stroke-width="1" rx="4"/>
|
||
<text x="128" y="92.5" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#93c5fd">Browser — Vanilla JS SPA</text>
|
||
<text x="128" y="105" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#7d93b0">web/index.html + app.js</text>
|
||
<rect x="244" y="70" width="180" height="50" fill="#13243d" stroke="#1e4a7a" stroke-width="1" rx="4"/>
|
||
<text x="334" y="92.5" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#93c5fd">多项目看板</text>
|
||
<text x="334" y="105" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#7d93b0">任务列表实时刷新</text>
|
||
<rect x="440" y="70" width="200" height="50" fill="#13243d" stroke="#1e4a7a" stroke-width="1" rx="4"/>
|
||
<text x="540" y="92.5" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#93c5fd">任务详情</text>
|
||
<text x="540" y="105" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#7d93b0">plan/spec/events/runs</text>
|
||
<rect x="656" y="70" width="160" height="50" fill="#13243d" stroke="#1e4a7a" stroke-width="1" rx="4"/>
|
||
<text x="736" y="92.5" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#93c5fd">审批</text>
|
||
<text x="736" y="105" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#7d93b0">approve / reject</text>
|
||
<rect x="832" y="70" width="200" height="50" fill="#2a1414" stroke="#b85450" stroke-width="1" rx="4"/>
|
||
<text x="932" y="92.5" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#e07070">⚠ 无流式输出</text>
|
||
<text x="932" y="105" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#e07070">只能看 transcript</text>
|
||
<rect x="12" y="144" width="1156" height="222" fill="#1c1708" stroke="#7a5e1e" stroke-width="1" rx="4"/>
|
||
<rect x="12" y="144" width="1156" height="22" fill="#4a3a14" rx="4"/>
|
||
<rect x="12" y="158" width="1156" height="8" fill="#4a3a14"/>
|
||
<text x="22" y="159" font-family="monospace" font-size="11" font-weight="bold" fill="#fbbf24">Daemon 进程(唯一 DB 写者)</text>
|
||
<rect x="28" y="174" width="210" height="52" fill="#2c2410" stroke="#7a5e1e" stroke-width="1" rx="4"/>
|
||
<text x="133" y="191.25" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#fcd34d">Fastify API Server :4517</text>
|
||
<text x="133" y="203.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#c9b878">REST 40+ · WS /ws</text>
|
||
<text x="133" y="216.25" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#9a8a5a">src/api/server.ts</text>
|
||
<rect x="254" y="174" width="230" height="52" fill="#2c2410" stroke="#7a5e1e" stroke-width="1" rx="4"/>
|
||
<text x="369" y="191.25" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#fcd34d">Orchestrator Loop · 每 15s</text>
|
||
<text x="369" y="203.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#c9b878">ingest → reap → claim</text>
|
||
<text x="369" y="216.25" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#9a8a5a">src/daemon/orchestrator.ts</text>
|
||
<rect x="28" y="238" width="456" height="52" fill="#2a1414" stroke="#b85450" stroke-width="1" rx="4"/>
|
||
<text x="256" y="261.5" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#e07070">⚠ rankByScore:简单优先级加总,无 CPM / 无防饥饿 / 忽略文件耦合</text>
|
||
<text x="256" y="274" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#e07070">⚠ claimOne 无 CAS → 并发可双重领取同一任务</text>
|
||
<rect x="508" y="174" width="200" height="52" fill="#2c2410" stroke="#7a5e1e" stroke-width="1" rx="4"/>
|
||
<text x="608" y="191.25" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#fcd34d">SQLite DB</text>
|
||
<text x="608" y="203.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#c9b878">~/.maestro/maestro.db</text>
|
||
<text x="608" y="216.25" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#9a8a5a">WAL + 外键</text>
|
||
<rect x="728" y="174" width="304" height="52" fill="#2c2410" stroke="#7a5e1e" stroke-width="1" rx="4"/>
|
||
<text x="880" y="191.25" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#fcd34d">tables(5 张)</text>
|
||
<text x="880" y="203.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#c9b878">projects · tasks · runs · events · approvals</text>
|
||
<text x="880" y="216.25" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#9a8a5a">task 用单一扁平 status 列建模</text>
|
||
<rect x="508" y="238" width="524" height="52" fill="#2a1414" stroke="#b85450" stroke-width="1" rx="4"/>
|
||
<text x="770" y="261.5" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#e07070">⚠ 缺列:claimed_at · task_type · scope · owned_files · version</text>
|
||
<text x="770" y="274" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#e07070">⚠ 状态用扁平 FSM(16 个混合 status),无 schedule × work_type 正交建模</text>
|
||
<rect x="12" y="378" width="1156" height="232" fill="#0a1f10" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<rect x="12" y="378" width="1156" height="22" fill="#14401f" rx="4"/>
|
||
<rect x="12" y="392" width="1156" height="8" fill="#14401f"/>
|
||
<text x="22" y="393" font-family="monospace" font-size="11" font-weight="bold" fill="#4ade80">Worker 进程(每任务独立 · 不接触 DB)</text>
|
||
<text x="24" y="414" font-family="monospace" font-size="10" font-weight="bold" fill="#4ade80">executor pipeline(复审内联、串行)</text>
|
||
<rect x="24" y="420" width="92" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="70" y="440.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">syncMain</text>
|
||
<text x="125" y="441" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="134" y="420" width="118" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="193" y="440.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">createWorktree</text>
|
||
<text x="261" y="441" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="270" y="420" width="100" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="320" y="440.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">runTask (CC)</text>
|
||
<text x="379" y="441" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="388" y="420" width="90" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="433" y="440.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">verify 可选</text>
|
||
<text x="487" y="441" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="496" y="420" width="108" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="550" y="440.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">worktreeDiff</text>
|
||
<text x="613" y="441" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="622" y="420" width="120" height="34" fill="#2a1414" stroke="#b85450" stroke-width="1" rx="4"/>
|
||
<text x="682" y="440.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#e07070">⚠ reviewCode 串行</text>
|
||
<text x="751" y="441" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="760" y="420" width="120" height="34" fill="#2a1414" stroke="#b85450" stroke-width="1" rx="4"/>
|
||
<text x="820" y="440.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#e07070">⚠ reviewSec 串行</text>
|
||
<text x="889" y="441" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="898" y="420" width="108" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="952" y="440.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">exec_review</text>
|
||
<text x="24" y="480" font-family="monospace" font-size="10" font-weight="bold" fill="#4ade80">planner pipeline(只读)</text>
|
||
<rect x="24" y="486" width="160" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="104" y="506.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">buildPlannerPrompt</text>
|
||
<text x="193" y="507" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="202" y="486" width="160" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="282" y="506.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">runPlanner (CC 只读)</text>
|
||
<text x="371" y="507" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="380" y="486" width="210" height="34" fill="#2a1414" stroke="#b85450" stroke-width="1" rx="4"/>
|
||
<text x="485" y="506.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#e07070">⚠ decompose JSON 缺 ownedFiles</text>
|
||
<text x="24" y="546" font-family="monospace" font-size="10" font-weight="bold" fill="#4ade80">conflict pipeline(无专用 agent)</text>
|
||
<rect x="24" y="552" width="120" height="30" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="84" y="570.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">git merge</text>
|
||
<text x="153" y="571" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="162" y="552" width="120" height="30" fill="#2a1414" stroke="#b85450" stroke-width="1" rx="4"/>
|
||
<text x="222" y="570.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#e07070">⚠ CC 自己猜着解</text>
|
||
<text x="291" y="571" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="300" y="552" width="100" height="30" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="350" y="570.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">双复审</text>
|
||
<rect x="12" y="622" width="1156" height="82" fill="#160c28" stroke="#5a3a8a" stroke-width="1" rx="4"/>
|
||
<rect x="12" y="622" width="1156" height="22" fill="#2d1a4f" rx="4"/>
|
||
<rect x="12" y="636" width="1156" height="8" fill="#2d1a4f"/>
|
||
<text x="22" y="637" font-family="monospace" font-size="11" font-weight="bold" fill="#a78bfa">Claude Code Agent(Headless)</text>
|
||
<rect x="28" y="648" width="240" height="46" fill="#241640" stroke="#5a3a8a" stroke-width="1" rx="4"/>
|
||
<text x="148" y="668.5" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#c4b5fd">cc.ts · @anthropic-ai/claude-code SDK</text>
|
||
<text x="148" y="681" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#9a86c4">for-await 流式(仅落 transcript)</text>
|
||
<rect x="284" y="648" width="330" height="46" fill="#241640" stroke="#5a3a8a" stroke-width="1" rx="4"/>
|
||
<text x="449" y="668.5" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#c4b5fd">executor 白名单</text>
|
||
<text x="449" y="681" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#9a86c4">Read/Edit/Write/Glob/Grep · Bash(git,test) · WebSearch</text>
|
||
<rect x="630" y="648" width="290" height="46" fill="#241640" stroke="#5a3a8a" stroke-width="1" rx="4"/>
|
||
<text x="775" y="668.5" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#c4b5fd">planner 白名单</text>
|
||
<text x="775" y="681" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#9a86c4">Read/Glob/Grep · Bash(git log/diff/show)</text>
|
||
<rect x="936" y="648" width="96" height="46" fill="none" stroke="#444" stroke-width="1" stroke-dasharray="4 3" rx="4"/>
|
||
<text x="984" y="668.5" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#64748b">src/mcp/</text>
|
||
<text x="984" y="681" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#64748b">MCP</text>
|
||
<rect x="1044" y="648" width="96" height="46" fill="none" stroke="#444" stroke-width="1" stroke-dasharray="4 3" rx="4"/>
|
||
<text x="1092" y="668.5" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#64748b">src/sync/</text>
|
||
<text x="1092" y="681" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#64748b">todo 同步</text>
|
||
</svg>
|
||
</div>
|
||
|
||
<div class="grid-2">
|
||
<div class="card">
|
||
<div class="card-title">前端(现状)</div>
|
||
<table class="tbl">
|
||
<tr><th>维度</th><th>现状</th></tr>
|
||
<tr><td>技术栈</td><td>Vanilla JS + CSS,无构建工具</td></tr>
|
||
<tr><td>入口</td><td>web/index.html 静态文件,Fastify 直接 serve</td></tr>
|
||
<tr><td>状态管理</td><td>全局 JS 变量 + 手动 DOM 操作</td></tr>
|
||
<tr><td>实时通信</td><td>WebSocket 收事件后手动更新 DOM</td></tr>
|
||
<tr><td>流式输出</td><td>无(只看完成后 transcript)</td></tr>
|
||
<tr><td>代码分割</td><td>单文件 app.js (~1500 行)</td></tr>
|
||
<tr><td>类型安全</td><td>无(纯 JS)</td></tr>
|
||
</table>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-title">进程模型(现状)</div>
|
||
<table class="tbl">
|
||
<tr><th>角色</th><th>职责</th></tr>
|
||
<tr><td>Daemon</td><td>唯一 DB 写者;API+WS 服务;Orchestrator 调度循环;ingest worker 产出</td></tr>
|
||
<tr><td>Worker</td><td>每任务独立 OS 进程;不碰 DB;纯文件通信</td></tr>
|
||
<tr><td>CC Agent</td><td>在 worker 内 SDK headless 运行;工具白名单隔离</td></tr>
|
||
<tr><td>通信</td><td>file + SIGTERM(可存活 daemon crash)</td></tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">REST API 主要路由(重构后 · 完整契约见 ③设计方案 ⑧ 前端 API 契约)</div>
|
||
<div class="grid-3">
|
||
<div class="box cyan"><h4>Projects</h4>
|
||
<div class="row"><code>GET /api/projects</code></div>
|
||
<div class="row"><code>POST /api/projects</code></div>
|
||
<div class="row"><code>GET /api/projects/:id</code></div>
|
||
<div class="row"><code>PATCH /api/projects/:id</code></div>
|
||
<div class="row"><code>DELETE /api/projects/:id</code></div>
|
||
<div class="row"><code>POST /api/projects/:id/sync</code></div>
|
||
<div class="row"><code>POST /api/projects/reorder</code></div>
|
||
</div>
|
||
<div class="box amber"><h4>Tasks</h4>
|
||
<div class="row"><code>GET /api/projects/:id/tasks</code></div>
|
||
<div class="row"><code>POST /api/projects/:id/tasks</code></div>
|
||
<div class="row"><code>GET /api/tasks/:id</code></div>
|
||
<div class="row"><code>POST /api/tasks/:id/plan</code></div>
|
||
<div class="row"><code>POST /api/tasks/:id/spec</code></div>
|
||
<div class="row"><code>POST /api/tasks/:id/decide</code></div>
|
||
<div class="row"><code>POST /api/tasks/:id/requeue</code></div>
|
||
<div class="row"><code>POST /api/tasks/:id/cancel</code></div>
|
||
<div class="row"><code>DELETE /api/tasks/:id</code></div>
|
||
<div class="row"><code>POST /api/tasks/:id/attachments</code> <span class="chg add">新</span></div>
|
||
<div class="row"><code>POST /api/tasks/:id/takeover</code> <span class="chg add">新</span></div>
|
||
</div>
|
||
<div class="box violet"><h4>Runs / Events</h4>
|
||
<div class="row"><code>GET /api/tasks/:id/runs</code></div>
|
||
<div class="row"><code>GET /api/runs/:id/transcript</code></div>
|
||
<div class="row"><code>GET /api/tasks/:id/events</code></div>
|
||
<div class="row"><code>GET /api/projects/:id/events</code></div>
|
||
<div class="row"><code>GET /api/agents</code></div>
|
||
<div class="row"><code>GET /api/usage</code></div>
|
||
<div class="row"><code>GET /api/metrics</code></div>
|
||
<div class="row"><code>GET /api/tasks/:id/stream</code> <span class="chg add">新</span></div>
|
||
<div class="row"><code>GET /api/health</code> <span class="chg add">新</span></div>
|
||
<div class="row"><code>WS /ws</code></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">数据库 Schema(现状)</div>
|
||
<div class="arch-diagram"><span class="lbl">projects</span> id · name · repo_path · autonomy · concurrency · max_retries · timeout_ms
|
||
verify_cmd · model · checks · auto_approve_plan · auto_approve_exec
|
||
|
||
<span class="lbl">tasks</span> id · project_id · parent_id · depth · title · body · complexity · status
|
||
priority · deps(JSON) · plan · spec · operations · result(JSON)
|
||
retry_baseline · next_eligible_at · last_run_error · created_at · updated_at
|
||
<span class="warn">⚠ 缺少:schedule_status · work_type · work_plan · work_history · claimed_at · scope · expected_output · version</span>
|
||
|
||
<span class="lbl">runs</span> id · task_id · kind(planner/executor/conflict) · status · worktree · branch
|
||
transcript_ref · session_id · worker_pid · last_seq · started_at · ended_at
|
||
<span class="warn">⚠ 缺少:trace_id</span>
|
||
|
||
<span class="lbl">events</span> id · project_id · task_id · type · payload(JSON) · at
|
||
<span class="lbl">approvals</span> id · task_id · gate(plan/spec/exec) · action · actor · reason · at</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">任务状态机(生命周期)</div>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1180 600" style="width:100%;display:block;margin:16px 0;">
|
||
<defs>
|
||
<marker id="fg" markerWidth="7" markerHeight="6" refX="6" refY="3" orient="auto"><polygon points="0 0,7 3,0 6" fill="#82b366"/></marker>
|
||
<marker id="fo" markerWidth="7" markerHeight="6" refX="6" refY="3" orient="auto"><polygon points="0 0,7 3,0 6" fill="#d6783b"/></marker>
|
||
<marker id="fr" markerWidth="7" markerHeight="6" refX="6" refY="3" orient="auto"><polygon points="0 0,7 3,0 6" fill="#b85450"/></marker>
|
||
<marker id="fv" markerWidth="7" markerHeight="6" refX="6" refY="3" orient="auto"><polygon points="0 0,7 3,0 6" fill="#9673a6"/></marker>
|
||
<marker id="fb" markerWidth="7" markerHeight="6" refX="6" refY="3" orient="auto"><polygon points="0 0,7 3,0 6" fill="#5b8fd6"/></marker>
|
||
<marker id="fa" markerWidth="7" markerHeight="6" refX="6" refY="3" orient="auto"><polygon points="0 0,7 3,0 6" fill="#d6b656"/></marker>
|
||
<marker id="fk" markerWidth="7" markerHeight="6" refX="6" refY="3" orient="auto"><polygon points="0 0,7 3,0 6" fill="#777777"/></marker>
|
||
</defs>
|
||
<text x="24" y="24" font-family="monospace" font-size="14" font-weight="bold" fill="#e8e8e8">Maestro 任务状态机(现状 · 扁平 FSM)</text>
|
||
<!-- edges -->
|
||
<path d="M 97 284 Q 190 195 282 107" stroke="#d6783b" stroke-width="1.5" fill="none" marker-end="url(#fo)"/>
|
||
<path d="M 107 286 Q 190 245 270 205" stroke="#5b8fd6" stroke-width="1.5" fill="none" marker-end="url(#fb)"/>
|
||
<path d="M 122 300 Q 190 300 254 300" stroke="#82b366" stroke-width="1.5" fill="none" marker-end="url(#fg)"/>
|
||
<path d="M 82 318 Q 95 420 108 522" stroke="#777777" stroke-width="1.5" fill="none" marker-end="url(#fk)"/>
|
||
<path d="M 360 90 Q 410 90 456 90" stroke="#d6783b" stroke-width="1.5" fill="none" marker-end="url(#fo)"/>
|
||
<path d="M 584 90 Q 630 90 676 90" stroke="#d6783b" stroke-width="1.5" fill="none" marker-end="url(#fo)"/>
|
||
<path d="M 484 75 Q 410 44 335 75" stroke="#b85450" stroke-width="1.5" fill="none" marker-end="url(#fr)"/>
|
||
<path d="M 696 77 Q 520 26 343 77" stroke="#d6783b" stroke-width="1.5" fill="none" marker-end="url(#fo)"/>
|
||
<path d="M 781 104 Q 949 160 1101 284" stroke="#d6783b" stroke-width="1.5" fill="none" marker-end="url(#fo)"/>
|
||
<path d="M 356 190 Q 410 190 456 190" stroke="#5b8fd6" stroke-width="1.5" fill="none" marker-end="url(#fb)"/>
|
||
<path d="M 489 206 Q 410 245 328 286" stroke="#5b8fd6" stroke-width="1.5" fill="none" marker-end="url(#fb)"/>
|
||
<path d="M 481 176 Q 410 150 337 177" stroke="#b85450" stroke-width="1.5" fill="none" marker-end="url(#fr)"/>
|
||
<path d="M 346 300 Q 406 300 462 300" stroke="#82b366" stroke-width="1.5" fill="none" marker-end="url(#fg)"/>
|
||
<path d="M 475 288 Q 406 266 336 289" stroke="#82b366" stroke-width="1.5" fill="none" marker-end="url(#fg)"/>
|
||
<path d="M 562 300 Q 619 300 667 300" stroke="#9673a6" stroke-width="1.5" fill="none" marker-end="url(#fv)"/>
|
||
<path d="M 783 300 Q 830 300 871 300" stroke="#9673a6" stroke-width="1.5" fill="none" marker-end="url(#fv)"/>
|
||
<path d="M 999 300 Q 1028 300 1078 300" stroke="#9673a6" stroke-width="1.5" fill="none" marker-end="url(#fv)"/>
|
||
<path d="M 308 318 Q 326 359 308 400" stroke="#b85450" stroke-width="1.5" fill="none" marker-end="url(#fr)"/>
|
||
<path d="M 292 400 Q 274 359 292 318" stroke="#82b366" stroke-width="1.5" fill="none" marker-end="url(#fg)"/>
|
||
<path d="M 732 318 Q 747 359 732 400" stroke="#b85450" stroke-width="1.5" fill="none" marker-end="url(#fr)"/>
|
||
<path d="M 906 316 Q 830 359 752 403" stroke="#b85450" stroke-width="1.5" fill="none" marker-end="url(#fr)"/>
|
||
<path d="M 902 285 Q 618 150 329 286" stroke="#b85450" stroke-width="1.5" fill="none" marker-end="url(#fr)"/>
|
||
<path d="M 690 406 Q 609 376 533 316" stroke="#82b366" stroke-width="1.5" fill="none" marker-end="url(#fg)"/>
|
||
<path d="M 696 432 Q 619 471 540 510" stroke="#b85450" stroke-width="1.5" fill="none" marker-end="url(#fr)"/>
|
||
<path d="M 300 108 Q 298 360 491 508" stroke="#b85450" stroke-width="1.5" fill="none" marker-end="url(#fr)"/>
|
||
<path d="M 303 208 Q 330 405 488 509" stroke="#b85450" stroke-width="1.5" fill="none" marker-end="url(#fr)"/>
|
||
<path d="M 505 506 Q 450 371 329 314" stroke="#d6783b" stroke-width="1.5" fill="none" marker-end="url(#fo)"/>
|
||
<path d="M 516 506 Q 586 219 333 105" stroke="#d6783b" stroke-width="1.5" fill="none" marker-end="url(#fo)"/>
|
||
<path d="M 512 506 Q 507 293 330 205" stroke="#d6783b" stroke-width="1.5" fill="none" marker-end="url(#fo)"/>
|
||
<path d="M 277 316 Q 205 365 133 414" stroke="#d6b656" stroke-width="1.5" fill="none" stroke-dasharray="4,3" marker-end="url(#fa)"/>
|
||
<path d="M 133 414 Q 205 365 277 316" stroke="#d6b656" stroke-width="1.5" fill="none" stroke-dasharray="4,3" marker-end="url(#fa)"/>
|
||
<!-- labels -->
|
||
<text x="175" y="185" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6783b">Hard</text>
|
||
<text x="180" y="238" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#5b8fd6">Medium</text>
|
||
<text x="190" y="293" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#82b366">Easy</text>
|
||
<text x="64" y="430" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#777777">cancel</text>
|
||
<text x="410" y="82" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6783b">拆解完成</text>
|
||
<text x="630" y="82" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6783b">accept</text>
|
||
<text x="410" y="128" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#b85450">reject</text>
|
||
<text x="520" y="30" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6783b">重拆</text>
|
||
<text x="1000" y="150" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6783b">子任务全 done</text>
|
||
<text x="410" y="182" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#5b8fd6">方案完成</text>
|
||
<text x="418" y="248" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#5b8fd6">accept</text>
|
||
<text x="410" y="225" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#b85450">reject</text>
|
||
<text x="406" y="293" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#82b366">claim</text>
|
||
<text x="406" y="330" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#82b366">退回</text>
|
||
<text x="619" y="293" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#9673a6">worker 启动</text>
|
||
<text x="830" y="293" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#9673a6">执行完成</text>
|
||
<text x="1028" y="293" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#9673a6">accept(合并)</text>
|
||
<text x="230" y="360" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#b85450">依赖未满足</text>
|
||
<text x="372" y="360" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#82b366">依赖满足</text>
|
||
<text x="660" y="360" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#b85450">执行失败</text>
|
||
<text x="850" y="360" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#b85450">失败</text>
|
||
<text x="620" y="212" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#b85450">reject(返工)</text>
|
||
<text x="600" y="350" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#82b366">重试</text>
|
||
<text x="650" y="480" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#b85450">超阈值</text>
|
||
<text x="150" y="430" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#b85450">失败超限</text>
|
||
<text x="250" y="470" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#b85450">失败超限</text>
|
||
<text x="360" y="470" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6783b">重排队</text>
|
||
<text x="600" y="430" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6783b">重新分析</text>
|
||
<text x="430" y="360" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6783b">重写方案</text>
|
||
<text x="175" y="360" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6b656">暂停</text>
|
||
<text x="225" y="380" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6b656">恢复</text>
|
||
<!-- nodes -->
|
||
<ellipse cx="80" cy="300" rx="42" ry="18" fill="#2a2a2a" stroke="#888888"/>
|
||
<text x="80" y="304" text-anchor="middle" font-family="monospace" font-size="11" fill="#aaaaaa">init</text>
|
||
<ellipse cx="300" cy="90" rx="60" ry="18" fill="#2e2014" stroke="#d6783b"/>
|
||
<text x="300" y="94" text-anchor="middle" font-family="monospace" font-size="11" fill="#f0a060">analyzing</text>
|
||
<ellipse cx="520" cy="90" rx="64" ry="18" fill="#2e2014" stroke="#d6783b"/>
|
||
<text x="520" y="94" text-anchor="middle" font-family="monospace" font-size="11" fill="#f0a060">plan_review</text>
|
||
<ellipse cx="740" cy="90" rx="64" ry="18" fill="#2e2014" stroke="#d6783b"/>
|
||
<text x="740" y="94" text-anchor="middle" font-family="monospace" font-size="11" fill="#f0a060">decomposed</text>
|
||
<ellipse cx="300" cy="190" rx="56" ry="18" fill="#14233a" stroke="#5b8fd6"/>
|
||
<text x="300" y="194" text-anchor="middle" font-family="monospace" font-size="11" fill="#7ab0ff">speccing</text>
|
||
<ellipse cx="520" cy="190" rx="64" ry="18" fill="#14233a" stroke="#5b8fd6"/>
|
||
<text x="520" y="194" text-anchor="middle" font-family="monospace" font-size="11" fill="#7ab0ff">spec_review</text>
|
||
<ellipse cx="300" cy="300" rx="46" ry="18" fill="#1a2e1a" stroke="#82b366"/>
|
||
<text x="300" y="304" text-anchor="middle" font-family="monospace" font-size="11" fill="#82b366">ready</text>
|
||
<ellipse cx="512" cy="300" rx="50" ry="18" fill="#1a2e1a" stroke="#82b366"/>
|
||
<text x="512" y="304" text-anchor="middle" font-family="monospace" font-size="11" fill="#82b366">queued</text>
|
||
<ellipse cx="725" cy="300" rx="58" ry="18" fill="#221a2e" stroke="#9673a6"/>
|
||
<text x="725" y="304" text-anchor="middle" font-family="monospace" font-size="11" fill="#c4a8d4">executing</text>
|
||
<ellipse cx="935" cy="300" rx="64" ry="18" fill="#221a2e" stroke="#9673a6"/>
|
||
<text x="935" y="304" text-anchor="middle" font-family="monospace" font-size="11" fill="#c4a8d4">exec_review</text>
|
||
<ellipse cx="1120" cy="300" rx="42" ry="18" fill="#1a472a" stroke="#2d7a40"/>
|
||
<text x="1120" y="304" text-anchor="middle" font-family="monospace" font-size="11" font-weight="bold" fill="#ffffff">done</text>
|
||
<ellipse cx="300" cy="418" rx="52" ry="18" fill="#2e1a1a" stroke="#b85450"/>
|
||
<text x="300" y="422" text-anchor="middle" font-family="monospace" font-size="11" fill="#e07070">blocked</text>
|
||
<ellipse cx="725" cy="418" rx="48" ry="18" fill="#2e1a1a" stroke="#b85450"/>
|
||
<text x="725" y="422" text-anchor="middle" font-family="monospace" font-size="11" fill="#e07070">failed</text>
|
||
<ellipse cx="512" cy="524" rx="46" ry="18" fill="#2e2014" stroke="#d6783b"/>
|
||
<text x="512" y="528" text-anchor="middle" font-family="monospace" font-size="11" fill="#f97316">stuck</text>
|
||
<ellipse cx="110" cy="430" rx="50" ry="18" fill="#2e2a14" stroke="#d6b656"/>
|
||
<text x="110" y="434" text-anchor="middle" font-family="monospace" font-size="11" fill="#d6b656">paused</text>
|
||
<ellipse cx="110" cy="540" rx="60" ry="18" fill="#2e2e2e" stroke="#555555"/>
|
||
<text x="110" y="544" text-anchor="middle" font-family="monospace" font-size="11" font-weight="bold" fill="#888888">cancelled</text>
|
||
<rect x="895" y="454" width="220" height="102" fill="#11161f" stroke="#252b36" rx="4"/>
|
||
<line x1="905" y1="467" x2="927" y2="467" stroke="#d6783b" stroke-width="2"/><text x="935" y="470" font-family="sans-serif" font-size="9" fill="#9aa4b2">Hard 拆解</text>
|
||
<line x1="905" y1="482" x2="927" y2="482" stroke="#5b8fd6" stroke-width="2"/><text x="935" y="485" font-family="sans-serif" font-size="9" fill="#9aa4b2">Medium 方案</text>
|
||
<line x1="905" y1="497" x2="927" y2="497" stroke="#82b366" stroke-width="2"/><text x="935" y="500" font-family="sans-serif" font-size="9" fill="#9aa4b2">执行调度</text>
|
||
<line x1="905" y1="512" x2="927" y2="512" stroke="#9673a6" stroke-width="2"/><text x="935" y="515" font-family="sans-serif" font-size="9" fill="#9aa4b2">Agent 运行</text>
|
||
<line x1="905" y1="527" x2="927" y2="527" stroke="#b85450" stroke-width="2"/><text x="935" y="530" font-family="sans-serif" font-size="9" fill="#9aa4b2">异常/失败</text>
|
||
<line x1="905" y1="542" x2="927" y2="542" stroke="#d6b656" stroke-width="2"/><text x="935" y="545" font-family="sans-serif" font-size="9" fill="#9aa4b2">暂停(虚线)</text>
|
||
<text x="24" y="585" font-family="sans-serif" font-size="9" fill="#64748b">注:paused 与所有活跃态双向互通;cancelled 任意态可达(略去以保主干清晰)。本图为重构前扁平 FSM,对照「重构目标」的 schedule × work_type 正交模型。</text>
|
||
</svg>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">调度算法(现状)</div>
|
||
<div class="grid-2">
|
||
<div class="box amber"><h4>评分公式</h4>
|
||
<div style="font-size:12px;color:var(--text2);line-height:2">
|
||
<code>score(t) = baseScore(priority) P0=3,P1=2,P2=1</code><br>
|
||
<code> + Σ baseScore(d) d=已完成依赖</code><br>
|
||
<code> + Σ baseScore(b) b=被本任务阻塞</code>
|
||
</div>
|
||
<div style="font-size:11px;color:var(--red);margin-top:8px">⚠ 无防饥饿 · 忽略文件耦合 · 无 CAS</div>
|
||
</div>
|
||
<div class="box"><h4>调度流程</h4>
|
||
<div class="row"><code>ingest</code> 读 outbox → 写 DB</div>
|
||
<div class="row"><code>reap</code> 心跳超时 → failAttempt</div>
|
||
<div class="row"><code>claim</code> rankByScore → startRun</div>
|
||
<div style="font-size:11px;color:var(--red);margin-top:6px">⚠ 并发 tick 可双重领取</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">执行管道(现状)</div>
|
||
<div class="pipe-stage">
|
||
<div class="pipe-box exec">syncMain</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box exec">createWorktree</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box exec">runTask (CC)</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box gate">verify 可选</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box exec">worktreeDiff</div>
|
||
</div>
|
||
<div class="pipe-stage">
|
||
<div style="width:8px"></div>
|
||
<div class="pipe-box review" style="margin-left:0">reviewCode <span style="color:var(--red);font-size:10px">串行</span></div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box review">reviewSecurity <span style="color:var(--red);font-size:10px">串行</span></div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box human">exec_review</div>
|
||
</div>
|
||
<div style="font-size:11px;color:var(--red);margin-top:6px">⚠ 串行复审(耗时翻倍) · verdict=reject 只是建议不挡人工审核 · 无分项检查闸 · 无 diff 体量限制</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">模型档位(现状)</div>
|
||
<table class="tbl">
|
||
<tr><th>角色</th><th>Easy</th><th>Medium</th><th>Hard</th></tr>
|
||
<tr><td>executor</td><td>claude-sonnet-4-6</td><td>claude-opus-4-8</td><td>claude-opus-4-8</td></tr>
|
||
<tr><td>planner</td><td>claude-sonnet-4-6</td><td>claude-opus-4-8</td><td><span style="color:var(--red)">claude-opus-4-8 ⚠</span></td></tr>
|
||
<tr><td>reviewer</td><td>claude-sonnet-4-6</td><td>claude-opus-4-8</td><td><span style="color:var(--red)">claude-opus-4-8 ⚠</span></td></tr>
|
||
<tr><td>conflict</td><td colspan="3"><span style="color:var(--red)">claude-opus-4-8 ⚠(不够强)</span></td></tr>
|
||
<tr><td>回退链</td><td colspan="3"><code>['claude-opus-4-8', 'claude-sonnet-4-6']</code>(缺 fable-5)</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
</div><!-- /s-current -->
|
||
|
||
|
||
<!-- ═══════════════════════════════ ② 重构后架构 ═══════════════════════════════ -->
|
||
<div class="section" id="s-target">
|
||
<div class="sec-heading">重构后架构</div>
|
||
<div class="sec-desc">Phase 1(本次实施)完成后的目标态。<span class="chg add">绿=新增</span> <span class="chg mod">琥珀=变更</span> <span class="chg del">红=移除</span></div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">系统总览(重构后)</div>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1180 952" style="width:100%;display:block;margin:16px 0;background:#0a0f1a;border-radius:6px;">
|
||
<text x="24" y="26" font-family="monospace" font-size="15" font-weight="bold" fill="#e8e8e8">MAESTRO 系统架构(重构后目标态)</text>
|
||
<rect x="858" y="12" width="310" height="22" fill="#11161f" stroke="#252b36" rx="4"/>
|
||
<text x="868" y="27" font-family="monospace" font-size="9.5" fill="#fdba74">★ Phase 1 新增/变更</text>
|
||
<text x="1010" y="27" font-family="monospace" font-size="9.5" fill="#64748b">★★ Phase 2 规划</text>
|
||
<rect x="12" y="40" width="1156" height="92" fill="#0b1626" stroke="#1e4a7a" stroke-width="1" rx="4"/>
|
||
<rect x="12" y="40" width="1156" height="22" fill="#1e3a5f" rx="4"/>
|
||
<rect x="12" y="54" width="1156" height="8" fill="#1e3a5f"/>
|
||
<text x="22" y="55" font-family="monospace" font-size="11" font-weight="bold" fill="#60a5fa">用户端</text>
|
||
<rect x="28" y="70" width="250" height="50" fill="#13243d" stroke="#1e4a7a" stroke-width="1" rx="4"/>
|
||
<text x="153" y="86.25" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#93c5fd">Browser — Vanilla JS SPA</text>
|
||
<text x="153" y="98.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#7d93b0">web/index.html + app.js</text>
|
||
<text x="153" y="111.25" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#6a7d8a">(Phase 1 不变)</text>
|
||
<rect x="300" y="70" width="250" height="50" fill="none" stroke="#444" stroke-width="1" stroke-dasharray="4 3" rx="4"/>
|
||
<text x="425" y="86.25" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#64748b">★★ React 18 + Vite</text>
|
||
<text x="425" y="98.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#64748b">Zustand · IBM Plex Mono</text>
|
||
<text x="425" y="111.25" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#64748b">[Phase 2]</text>
|
||
<rect x="572" y="70" width="250" height="50" fill="none" stroke="#444" stroke-width="1" stroke-dasharray="4 3" rx="4"/>
|
||
<text x="697" y="86.25" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#64748b">★★ SSE 流式 token 输出</text>
|
||
<text x="697" y="98.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#64748b">GET /api/tasks/:id/stream</text>
|
||
<text x="697" y="111.25" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#64748b">[Phase 2]</text>
|
||
<rect x="12" y="144" width="1156" height="250" fill="#1c1708" stroke="#7a5e1e" stroke-width="1" rx="4"/>
|
||
<rect x="12" y="144" width="1156" height="22" fill="#4a3a14" rx="4"/>
|
||
<rect x="12" y="158" width="1156" height="8" fill="#4a3a14"/>
|
||
<text x="22" y="159" font-family="monospace" font-size="11" font-weight="bold" fill="#fbbf24">Daemon 进程(唯一 DB 写者)</text>
|
||
<rect x="28" y="174" width="250" height="52" fill="#2c2410" stroke="#7a5e1e" stroke-width="1" rx="4"/>
|
||
<text x="153" y="191.25" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#fcd34d">Fastify API Server :4517</text>
|
||
<text x="153" y="203.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#c9b878">REST 40+ 路由 · WS /ws</text>
|
||
<text x="153" y="216.25" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#9a8a5a">src/api/server.ts(沿用)</text>
|
||
<rect x="28" y="234" width="250" height="40" fill="#2c2410" stroke="#7a5e1e" stroke-width="1" rx="4"/>
|
||
<text x="153" y="251.5" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#fcd34d">Orchestrator Loop · 每 15s</text>
|
||
<text x="153" y="264" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#c9b878">ingest → reap → claim</text>
|
||
<rect x="28" y="282" width="250" height="98" fill="#2a1c0a" stroke="#fb923c" stroke-width="1" rx="4"/>
|
||
<text x="153" y="303.5" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#fdba74">★ 调度重构(scoring.ts)</text>
|
||
<text x="153" y="316" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">rankU = CPM 最长链</text>
|
||
<text x="153" y="328.5" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">+ agingBonus(防饥饿)</text>
|
||
<text x="153" y="341" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">− couplingPenalty(文件耦合)</text>
|
||
<text x="153" y="353.5" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">★ casClaimTask:CAS 防双 claim</text>
|
||
<text x="153" y="366" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">★ 按 work_type dispatch agent</text>
|
||
<rect x="300" y="174" width="200" height="52" fill="#2c2410" stroke="#7a5e1e" stroke-width="1" rx="4"/>
|
||
<text x="400" y="191.25" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#fcd34d">SQLite DB</text>
|
||
<text x="400" y="203.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#c9b878">~/.maestro/maestro.db</text>
|
||
<text x="400" y="216.25" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#9a8a5a">WAL + 外键</text>
|
||
<rect x="520" y="174" width="640" height="52" fill="#2a1c0a" stroke="#fb923c" stroke-width="1" rx="4"/>
|
||
<text x="840" y="197.5" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#fdba74">★ tasks:schedule_status × work_type(取代扁平 status)</text>
|
||
<text x="840" y="210" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">+ work_plan / work_history 队列 · owned_files · scope · expected_output · claimed_at · version</text>
|
||
<rect x="300" y="234" width="860" height="42" fill="#2a1c0a" stroke="#fb923c" stroke-width="1" rx="4"/>
|
||
<text x="730" y="258.75" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#fdba74">★ runs.kind 扩 plan|code|review|pr|conflict|research · + trace_id ★ projects + checks / diff_max_files / agent_rules</text>
|
||
<text x="300" y="300" font-family="monospace" font-size="9" fill="#9a8a5a">⛔ 已弃用:task_type / sub_status / state_snapshot / parent_version_id(被 schedule_status + work_plan 队列取代)</text>
|
||
<rect x="300" y="312" width="860" height="68" fill="#2c2410" stroke="#7a5e1e" stroke-width="1" rx="4"/>
|
||
<text x="730" y="337.25" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#fcd34d">work_plan 队列(线性、运行时决定执行路径)</text>
|
||
<text x="730" y="349.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#c9b878">[ {type, spec, owned_files, expected_output}, … ] ← Orchestrator pop 队头 → (ready, 下一 work_type)</text>
|
||
<text x="730" y="362.25" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#9a8a5a">waiting(plan|review) 为人工闸,不占并发名额</text>
|
||
<rect x="12" y="406" width="1156" height="372" fill="#0a1f10" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<rect x="12" y="406" width="1156" height="22" fill="#14401f" rx="4"/>
|
||
<rect x="12" y="420" width="1156" height="8" fill="#14401f"/>
|
||
<text x="22" y="421" font-family="monospace" font-size="11" font-weight="bold" fill="#4ade80">Worker 进程(每任务独立 · 不接触 DB) — 按 work_type 拆独立 run</text>
|
||
<text x="24" y="442" font-family="monospace" font-size="10" font-weight="bold" fill="#4ade80">plan run(Planner Agent)</text>
|
||
<rect x="24" y="448" width="150" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="99" y="462.5" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">runPlanner</text>
|
||
<text x="99" y="475" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">maxTurns 90/60/40</text>
|
||
<text x="183" y="469" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="192" y="448" width="210" height="34" fill="#2a1c0a" stroke="#fb923c" stroke-width="1" rx="4"/>
|
||
<text x="297" y="462.5" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">★ decompose JSON</text>
|
||
<text x="297" y="475" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">+ ownedFiles / expectedOutput</text>
|
||
<text x="411" y="469" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="420" y="448" width="170" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="505" y="468.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">ingest → 子任务 work_plan</text>
|
||
<text x="24" y="514" font-family="monospace" font-size="10" font-weight="bold" fill="#4ade80">① code run(Executor Agent)</text>
|
||
<rect x="24" y="520" width="92" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="70" y="540.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">syncMain</text>
|
||
<text x="125" y="541" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="134" y="520" width="118" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="193" y="540.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">createWorktree</text>
|
||
<text x="261" y="541" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="270" y="520" width="100" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="320" y="540.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">runTask (CC)</text>
|
||
<text x="379" y="541" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="388" y="520" width="80" height="34" fill="#2a2206" stroke="#f0a500" stroke-width="1" rx="4"/>
|
||
<text x="428" y="540.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fbbf24">verify ✓</text>
|
||
<text x="477" y="541" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="486" y="520" width="108" height="34" fill="#2a1c0a" stroke="#fb923c" stroke-width="1" rx="4"/>
|
||
<text x="540" y="540.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">★ checks 分项闸</text>
|
||
<text x="603" y="541" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="612" y="520" width="108" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="666" y="540.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">worktreeDiff</text>
|
||
<text x="729" y="541" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="738" y="520" width="100" height="34" fill="#2a1c0a" stroke="#fb923c" stroke-width="1" rx="4"/>
|
||
<text x="788" y="540.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">★ diff 体量闸</text>
|
||
<text x="847" y="541" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="856" y="520" width="78" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="895" y="540.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">commit</text>
|
||
<text x="24" y="586" font-family="monospace" font-size="10" font-weight="bold" fill="#4ade80">② review run(Reviewer Agent · 独立、可选)</text>
|
||
<rect x="24" y="592" width="240" height="34" fill="#2a1c0a" stroke="#fb923c" stroke-width="1" rx="4"/>
|
||
<text x="144" y="612.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">★ Promise.all 双复审(code + security)</text>
|
||
<text x="273" y="613" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="282" y="592" width="210" height="34" fill="#2a1c0a" stroke="#fb923c" stroke-width="1" rx="4"/>
|
||
<text x="387" y="612.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">★ verdict=reject → 硬拦截(回 code)</text>
|
||
<text x="501" y="613" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="510" y="592" width="170" height="34" fill="#0f2038" stroke="#5599ff" stroke-width="1" rx="4"/>
|
||
<text x="595" y="612.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#93c5fd">waiting(review) 人审</text>
|
||
<text x="24" y="658" font-family="monospace" font-size="10" font-weight="bold" fill="#4ade80">③ pr run(PR Agent · review 通过后串联)</text>
|
||
<rect x="24" y="664" width="110" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="79" y="684.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">创建 PR</text>
|
||
<text x="143" y="685" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="152" y="664" width="120" height="34" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="212" y="684.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">--no-ff 合并</text>
|
||
<text x="281" y="685" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="290" y="664" width="170" height="34" fill="#2a2206" stroke="#f0a500" stroke-width="1" rx="4"/>
|
||
<text x="375" y="684.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fbbf24">冲突 → conflict run</text>
|
||
<text x="469" y="685" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="478" y="664" width="90" height="34" fill="#0f2038" stroke="#5599ff" stroke-width="1" rx="4"/>
|
||
<text x="523" y="684.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#93c5fd">done ✓</text>
|
||
<text x="24" y="730" font-family="monospace" font-size="10" font-weight="bold" fill="#4ade80">conflict run(Conflict Agent · 抢占调度 · opus-4-8)</text>
|
||
<rect x="24" y="736" width="160" height="30" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="104" y="754.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">git merge 制造冲突</text>
|
||
<text x="193" y="755" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="202" y="736" width="110" height="30" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="257" y="754.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">CC 解冲突</text>
|
||
<text x="321" y="755" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="330" y="736" width="100" height="30" fill="#2a1c0a" stroke="#fb923c" stroke-width="1" rx="4"/>
|
||
<text x="380" y="754.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">★ 双复审</text>
|
||
<text x="439" y="755" text-anchor="middle" font-family="monospace" font-size="13" fill="#64748b">→</text>
|
||
<rect x="448" y="736" width="90" height="30" fill="#10301a" stroke="#1e7a3e" stroke-width="1" rx="4"/>
|
||
<text x="493" y="754.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">commit</text>
|
||
<rect x="12" y="790" width="1156" height="150" fill="#160c28" stroke="#5a3a8a" stroke-width="1" rx="4"/>
|
||
<rect x="12" y="790" width="1156" height="22" fill="#2d1a4f" rx="4"/>
|
||
<rect x="12" y="804" width="1156" height="8" fill="#2d1a4f"/>
|
||
<text x="22" y="805" font-family="monospace" font-size="11" font-weight="bold" fill="#a78bfa">Claude Code Agent(Headless)</text>
|
||
<rect x="28" y="820" width="210" height="98" fill="#241640" stroke="#5a3a8a" stroke-width="1" rx="4"/>
|
||
<text x="133" y="854" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#c4b5fd">cc.ts</text>
|
||
<text x="133" y="866.5" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">@anthropic-ai/</text>
|
||
<text x="133" y="879" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">claude-code SDK</text>
|
||
<text x="133" y="891.5" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#cbd5e1">for-await 流式</text>
|
||
<rect x="258" y="820" width="340" height="98" fill="#2a1c0a" stroke="#fb923c" stroke-width="1" rx="4"/>
|
||
<text x="428" y="847.75" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#fdba74">★ 模型档位(统一最强档)</text>
|
||
<text x="428" y="860.25" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">executor: sonnet / opus / opus</text>
|
||
<text x="428" y="872.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">planner-hard · reviewer · conflict → opus-4-8</text>
|
||
<text x="428" y="885.25" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">回退链 [opus-4-8, sonnet-4-6]</text>
|
||
<text x="428" y="897.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">(fable-5 暂不可用,opus 充当最强档)</text>
|
||
<rect x="618" y="820" width="300" height="98" fill="#2a1c0a" stroke="#fb923c" stroke-width="1" rx="4"/>
|
||
<text x="768" y="854" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#fdba74">★ Agent 记忆注入 L1–L4</text>
|
||
<text x="768" y="866.5" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">L4 全局规范 · L2 项目 agent_rules</text>
|
||
<text x="768" y="879" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">L3 拆解背景(父/兄弟)· L1 上次失败</text>
|
||
<text x="768" y="891.5" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#fdba74">daemon claim 时组装进 job.json</text>
|
||
<rect x="938" y="820" width="210" height="98" fill="none" stroke="#444" stroke-width="1" stroke-dasharray="4 3" rx="4"/>
|
||
<text x="1043" y="860.25" text-anchor="middle" font-family="monospace" font-size="9.5" font-weight="bold" fill="#64748b">★★ onToken callback</text>
|
||
<text x="1043" y="872.75" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#64748b">→ EventEmitter → SSE</text>
|
||
<text x="1043" y="885.25" text-anchor="middle" font-family="monospace" font-size="9.5" fill="#64748b">[Phase 2]</text>
|
||
</svg>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">Phase 1 变更清单</div>
|
||
<table class="tbl">
|
||
<tr><th>文件</th><th>变更内容</th><th>类型</th></tr>
|
||
<tr><td>schema.sql + db.ts</td><td>tasks 状态列重构:<code>status→schedule_status</code> + <code>work_type/work_plan/work_history/scope/owned_files/expected_output/claimed_at/version</code>;runs.kind 扩 <code>review|pr|conflict</code> + trace_id;projects 加 diff_max_files/checks/agent_rules;ensureColumn 向后兼容旧库</td><td><span class="chg mod">变更</span></td></tr>
|
||
<tr><td>types.ts</td><td>新增 <code>ScheduleStatus</code> / <code>WorkType</code> / <code>WorkStep</code> 类型;Task 接口以 schedule_status × work_type + work_plan 队列取代旧 status 单态</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td>mappers.ts</td><td>TaskRow/ProjectRow 追加字段;rowToTask/rowToProject 映射</td><td><span class="chg mod">变更</span></td></tr>
|
||
<tr><td>store.ts</td><td>casClaimTask(CAS);transition→ready 清 claimed_at;requeueTask 清零;assertNoActiveRun 执行期锁</td><td><span class="chg mod">变更</span></td></tr>
|
||
<tr><td>orchestrator.ts</td><td>claimOne 接 casClaimTask;rankByScore 传 inFlight+nowMs;按 <code>work_type</code> dispatch agent(code/plan/review/pr/conflict)</td><td><span class="chg mod">变更</span></td></tr>
|
||
<tr><td>scoring.ts</td><td>rankU(CPM) + agingBonus(防饥饿) + couplingPenalty(文件耦合) + 新签名 rankByScore</td><td><span class="chg mod">变更</span></td></tr>
|
||
<tr><td>models.ts</td><td>planner-hard/reviewer/conflict → opus-4-8(最强档);回退链 [opus-4-8, sonnet-4-6]<span style="color:var(--text3)">(fable-5 暂不可用,恢复后改档位表即可)</span></td><td><span class="chg mod">变更</span></td></tr>
|
||
<tr><td>runner.ts + protocol.ts</td><td>decompose JSON 扩展 ownedFiles/expectedOutput;prompt 更新</td><td><span class="chg mod">变更</span></td></tr>
|
||
<tr><td>ingest.ts</td><td>decompose-result 映射新字段到子任务</td><td><span class="chg mod">变更</span></td></tr>
|
||
<tr><td>pipeline.ts</td><td>code run:checks 分项闸(verify 后逐项执行)+ diff 体量闸(超 diffMaxFiles 硬失败);<strong>复审从 code pipeline 拆出</strong>为独立 reviewer run;新增 pr / conflict run 分支</td><td><span class="chg add">新增</span></td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="diff-grid">
|
||
<div class="diff-panel before">
|
||
<h4>调度算法(重构前)</h4>
|
||
<pre>score(t) = <span class="key">baseScore</span>(priority)
|
||
+ Σ baseScore(d) <span class="cmt">// 已完成依赖</span>
|
||
+ Σ baseScore(b) <span class="cmt">// 被阻塞任务</span>
|
||
|
||
<span class="del">无防饥饿(低优先永远靠后)</span>
|
||
<span class="del">忽略文件耦合</span>
|
||
<span class="del">无 CAS 防双重 claim</span></pre>
|
||
</div>
|
||
<div class="diff-panel after">
|
||
<h4>调度算法(重构后)</h4>
|
||
<pre>score(t) = <span class="add">rankU(t)</span> <span class="cmt">// CPM 关键路径(最长链)</span>
|
||
+ <span class="add">agingBonus(t)</span> <span class="cmt">// 防饥饿,48h 线性到 base</span>
|
||
- <span class="add">couplingPenalty(t)</span> <span class="cmt">// 未合并 worktree 文件重叠 -0.5/个</span>
|
||
|
||
<span class="add">rankU(t) = baseScore(t.priority)</span>
|
||
<span class="add"> + max(rankU(d) | d 直接依赖我) // 取最长,非求和</span>
|
||
<span class="cmt">// 叶子无 dependent → 仅 baseScore;最长链封顶,不膨胀</span>
|
||
<span class="add">agingBonus = base × min(1, waitHours/48) // 48h 达 base</span>
|
||
<span class="add">casClaimTask: WHERE claimed_at IS NULL</span></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">执行管道(重构后)— 按 work_type 拆独立 run</div>
|
||
<div style="font-size:11px;color:var(--text2);margin:0 0 10px">每个 work_type 是一次独立 Worker run,run 间由 <code>work_plan</code> 队列串联——复审<strong>不再内联</strong>进 executor,而是独立 reviewer run;合并独立成 pr run。</div>
|
||
|
||
<div style="font-size:11px;color:var(--violet);font-weight:600;margin:2px 0 3px">① code run</div>
|
||
<div class="pipe-stage">
|
||
<div class="pipe-box exec">syncMain</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box exec">createWorktree</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box exec">runTask (CC)</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box gate">verify ✓</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box gate-new">checks分项闸 ★</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box exec">worktreeDiff</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box gate-new">diff体量闸 ★</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box exec">commit</div>
|
||
</div>
|
||
|
||
<div style="font-size:11px;color:var(--cyan);font-weight:600;margin:10px 0 3px">② review run(独立 reviewer run,可选)</div>
|
||
<div class="pipe-stage">
|
||
<div class="pipe-box review">Promise.all 双复审(code + security)★</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box gate">verdict=reject → 硬拦截(回 code)★</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box human">waiting(review) 人审</div>
|
||
</div>
|
||
|
||
<div style="font-size:11px;color:var(--green);font-weight:600;margin:10px 0 3px">③ pr run(review 通过后串联)</div>
|
||
<div class="pipe-stage">
|
||
<div class="pipe-box exec">创建 PR</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box exec">--no-ff 合并</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box gate">冲突 → conflict run</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box human">done ✓</div>
|
||
</div>
|
||
<div style="font-size:11px;color:var(--text3);margin-top:8px">★=新增硬闸 · 复审为<strong>独立 reviewer run</strong>(非 code 内联),run 内 code+security 仍 Promise.all 并行 · <code>work_plan</code> 不含 review 时整段可跳过、code 完成直接进 pr</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">模型档位(重构后)</div>
|
||
<table class="tbl">
|
||
<tr><th>角色</th><th>Easy</th><th>Medium</th><th>Hard</th><th>备注</th></tr>
|
||
<tr><td>executor</td><td>claude-sonnet-4-6</td><td>claude-opus-4-8</td><td>claude-opus-4-8</td><td>不变</td></tr>
|
||
<tr><td>planner</td><td>claude-sonnet-4-6</td><td>claude-opus-4-8</td><td><span class="chg add">claude-opus-4-8</span></td><td>Hard 升最强档</td></tr>
|
||
<tr><td>reviewer</td><td><span class="chg add">claude-opus-4-8</span></td><td><span class="chg add">claude-opus-4-8</span></td><td><span class="chg add">claude-opus-4-8</span></td><td>三档统一最强,不被 project.model 降档</td></tr>
|
||
<tr><td>conflict</td><td colspan="3"><span class="chg add">claude-opus-4-8(固定,不降档)</span></td><td></td></tr>
|
||
<tr><td>回退链</td><td colspan="4"><span class="chg add">['claude-opus-4-8', 'claude-sonnet-4-6']</span></td></tr>
|
||
</table>
|
||
<div class="note" style="margin-top:10px;font-size:11px">⚠ 设计目标是「最强档」;<code>claude-fable-5</code> 暂不可用,当前一律以 <code>claude-opus-4-8</code> 充当最强档(commit a5a3c96 已全量替换)。fable-5 恢复后只需改 <code>models.ts</code> 档位表,结构不变。</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">Phase 2 预告(本次不含)</div>
|
||
<div class="grid-4">
|
||
<div class="box"><h4>SSE 流式</h4>
|
||
<div class="row">cc.ts onToken callback</div>
|
||
<div class="row">→ daemon EventEmitter</div>
|
||
<div class="row">→ GET /api/tasks/:id/stream</div>
|
||
<div class="row" style="color:var(--text3);font-size:11px">前端实时看 token</div>
|
||
</div>
|
||
<div class="box"><h4>Hook 合约</h4>
|
||
<div class="row">.maestro/hooks.ts</div>
|
||
<div class="row">MaestroHooks 接口</div>
|
||
<div class="row">5s 超时取消</div>
|
||
</div>
|
||
<div class="box"><h4>Trace ID</h4>
|
||
<div class="row">JobSpec.traceId</div>
|
||
<div class="row">outbox 每条携带</div>
|
||
<div class="row">跨进程可观测</div>
|
||
</div>
|
||
<div class="box"><h4>React 前端</h4>
|
||
<div class="row">React 18 + Vite</div>
|
||
<div class="row">Zustand 状态管理</div>
|
||
<div class="row">IBM Plex Mono 设计</div>
|
||
<div class="row">SSE token 显示</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div><!-- /s-target -->
|
||
|
||
|
||
<!-- ═══════════════════════════════ ③ 实施计划 ═══════════════════════════════ -->
|
||
<!-- /s-plan -->
|
||
|
||
<script>
|
||
// theme
|
||
if(localStorage.getItem('maestro-theme')==='light')document.documentElement.classList.add('light');
|
||
document.getElementById('themeBtn').onclick=function(){
|
||
document.documentElement.classList.toggle('light');
|
||
localStorage.setItem('maestro-theme',document.documentElement.classList.contains('light')?'light':'dark');
|
||
};
|
||
// tabs
|
||
function showSec(id,tab){
|
||
document.querySelectorAll('.section').forEach(s=>s.classList.remove('visible'));
|
||
document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));
|
||
document.getElementById(id).classList.add('visible');
|
||
tab.classList.add('active');
|
||
document.getElementById('prog-wrap').style.display=(id==='s-design'||id==='s-impl')?'flex':'none';
|
||
setTimeout(function(){window.dispatchEvent(new Event('resize'));},80);
|
||
}
|
||
// task collapse
|
||
function toggleTask(id){document.getElementById(id).classList.toggle('collapsed');}
|
||
// steps
|
||
const STEPS={tc1:10,tc2:10,tc3:7,tc4:6,tc5:8,tc6:10};
|
||
const SK='maestro-p1-ck';
|
||
let state={};
|
||
try{state=JSON.parse(localStorage.getItem(SK)||'{}');}catch{}
|
||
function saveState(){localStorage.setItem(SK,JSON.stringify(state));}
|
||
function ck(el,tid){
|
||
el.classList.toggle('checked');
|
||
const idx=[...el.closest('ul').querySelectorAll('.chkbox')].indexOf(el);
|
||
if(!state[tid])state[tid]={};
|
||
state[tid][idx]=el.classList.contains('checked');
|
||
saveState();updateProg();
|
||
}
|
||
function updateProg(){
|
||
let total=0,done=0;
|
||
Object.entries(STEPS).forEach(([tid,max])=>{
|
||
total+=max;
|
||
const d=Object.values(state[tid]||{}).filter(Boolean).length;
|
||
done+=d;
|
||
const el=document.getElementById('dn-'+tid);
|
||
if(el)el.textContent=d;
|
||
});
|
||
const pct=total?Math.round(done/total*100):0;
|
||
document.getElementById('prog-fill').style.width=pct+'%';
|
||
document.getElementById('prog-pct').textContent=pct+'%';
|
||
}
|
||
Object.entries(STEPS).forEach(([tid])=>{
|
||
const ul=document.getElementById('sl-'+tid);
|
||
if(!ul)return;
|
||
ul.querySelectorAll('.chkbox').forEach((el,i)=>{
|
||
if((state[tid]||{})[i])el.classList.add('checked');
|
||
});
|
||
});
|
||
updateProg();
|
||
</script>
|
||
|
||
|
||
|
||
|
||
|
||
<script>
|
||
// design sub-nav active state
|
||
(function(){
|
||
const links=document.querySelectorAll('.design-nav a[data-ch]');
|
||
if(!links.length)return;
|
||
const obs=new IntersectionObserver(entries=>{
|
||
entries.forEach(e=>{
|
||
if(e.isIntersecting){
|
||
const id=e.target.id;
|
||
links.forEach(l=>l.classList.toggle('act',l.dataset.ch===id));
|
||
}
|
||
});
|
||
},{rootMargin:'-10% 0px -80% 0px'});
|
||
['d-overview','d-statechart','d-db','d-memory','d-agentspec','d-ops','d-security','d-api'].forEach(id=>{
|
||
const el=document.getElementById(id);
|
||
if(el)obs.observe(el);
|
||
});
|
||
})();
|
||
</script>
|
||
|
||
|
||
<!-- ═══════════════════════════════ ⑦ schedule × work_type 状态机 ═══════════════════════════════ -->
|
||
<!-- end #s-statechart -->
|
||
|
||
<!-- ═══════════════════ ④ 完整设计方案(合并 实施计划 + DB ER + Agent 记忆 + Statechart)═══════════════════ -->
|
||
<div class="section" id="s-design">
|
||
<div class="design-sticky">
|
||
<div class="sec-heading">完整设计方案</div>
|
||
<div class="sec-desc">Maestro 大重构的完整技术蓝图:schedule × work_type 状态机架构 · 数据库 Schema 变更 · Agent 记忆注入 · Phase 1 实施路线。</div>
|
||
|
||
<!-- 内部导航 -->
|
||
<nav class="design-nav">
|
||
<a href="#d-overview" data-ch="d-overview">① 架构决策总览</a>
|
||
<a href="#d-statechart" data-ch="d-statechart">② 状态设计</a>
|
||
<a href="#d-db" data-ch="d-db">③ 数据库 Schema</a>
|
||
<a href="#d-memory" data-ch="d-memory">④ Agent 记忆注入</a>
|
||
<a href="#d-agentspec" data-ch="d-agentspec">⑤ Agent 规格</a>
|
||
<a href="#d-ops" data-ch="d-ops">⑥ 运维与护栏</a>
|
||
<a href="#d-security" data-ch="d-security">⑦ 安全与沙箱</a>
|
||
<a href="#d-api" data-ch="d-api">⑧ 前端 API 契约</a>
|
||
</nav>
|
||
</div><!-- /.design-sticky -->
|
||
|
||
<!-- ══════════ ① 架构决策总览 ══════════ -->
|
||
<a class="chapter-anchor" id="d-overview"></a>
|
||
<div class="chapter-hdr"><span>①</span> 架构决策总览</div>
|
||
<div class="note" style="margin:0 0 16px">🗂 <strong>分期标注</strong>:各章头部 <span class="phase p1" style="margin:0">Phase 1</span> = 本期核心重构(对应 ④具体实现 T1–T6);<span class="phase p2" style="margin:0">Phase 2</span> = 能力扩展(成本预算 / 安全沙箱 / 交互接管 / SSE 等);<span class="phase mix" style="margin:0">混合</span> = 章内分属两期,以卡片内标注为准。标注为<strong>建议范围</strong>,最终以实施计划为准。</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">六大核心决策</div>
|
||
<table class="tbl">
|
||
<tr><th>#</th><th>决策</th><th>选择</th><th>关键理由</th></tr>
|
||
<tr><td><span class="badge cyan">D1</span></td><td>状态机架构</td><td><strong>schedule_status × work_type 双轴</strong>,替代平铺 FSM</td><td>schedule_status 对齐 OS 进程模型(OS 五态),描述资源调度;work_type 描述当前执行何种工作(plan/code/review/pr…);执行路径由混合模式动态决定,无需静态枚举所有路径</td></tr>
|
||
<tr><td><span class="badge amber">D2</span></td><td>任务类型扩展</td><td><strong>work_type 注册表</strong>(新类型 = 加 agent + dispatcher 分支)</td><td>核心代码零改动;schedule_status 通用状态(ready/running/waiting…)完全复用,无需改状态机</td></tr>
|
||
<tr><td><span class="badge violet">D3</span></td><td>DB 状态存储</td><td><strong>schedule_status + work_type + work_plan 队列(JSON)</strong></td><td>schedule_status / work_type 双列供 SQL 索引快速过滤与调度;work_plan 队列自带恢复位置,<strong>不引入 XState snapshot</strong></td></tr>
|
||
<tr><td><span class="badge green">D4</span></td><td>Agent 记忆</td><td><strong>四层注入</strong>(L1 失败原因 / L2 项目规范 / L3 跨任务上下文 / L4 全局规范)</td><td>worker 不碰 DB,所有记忆由 daemon claimOne 组装进 job.json;ROI 由高到低</td></tr>
|
||
<tr><td><span class="badge red">D5</span></td><td>流程硬闸</td><td>复审 <strong>reject 变硬闸</strong> + <strong>分项 checks</strong> + <strong>diff 体量闸</strong></td><td>消除"复审仅建议"漏洞;静态检查前置防止无效执行进审批</td></tr>
|
||
<tr><td><span class="badge cyan">D6</span></td><td>并发调度</td><td><strong>CAS claimed_at</strong> + <strong>couplingPenalty(文件重叠降分)</strong></td><td>防双重 claim 竞态;相同文件的任务并发执行概率降低,冲突减少</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">重构前后对比快览</div>
|
||
<div class="diff-grid">
|
||
<div class="diff-panel before">
|
||
<h4>现有设计(平铺 FSM)</h4>
|
||
<pre>• 16 状态平铺,全局 TRANSITIONS 表
|
||
• CANCEL/FAIL 每个子状态重复声明
|
||
• code + security 复审串行
|
||
• 暂停恢复需 paused_return_to 字段
|
||
• 新任务类型 = 修改核心转移表
|
||
• claimed_at 无 CAS,存在双重领取竞态
|
||
• reviewer reject 不挡流,仅建议
|
||
• agent prompt 无失败记忆 / 无规范注入</pre>
|
||
</div>
|
||
<div class="diff-panel after">
|
||
<h4>重构后(schedule × work_type + 四层记忆)</h4>
|
||
<pre class="add">• schedule_status × work_type 双轴正交状态
|
||
• work_plan 队列动态决定执行路径(混合模式)
|
||
• waiting 人工闸不占并发名额,running 才占
|
||
• (paused, work_type) 自带恢复位置,无需额外字段
|
||
• 新 work_type = 加 agent + dispatcher 分支,零状态机改动
|
||
• CAS claimed_at + couplingPenalty 调度
|
||
• reviewer reject = 硬闸,回退重执行
|
||
• L1-L4 记忆注入 + 项目/全局规范</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">进程模型铁律(不改)</div>
|
||
<div class="grid-2">
|
||
<div class="box cyan">
|
||
<h4>Daemon — 唯一 DB 写者</h4>
|
||
<div class="row">REST + WebSocket API(:4517)</div>
|
||
<div class="row">Orchestrator 调度循环(ingest → reap → claim)</div>
|
||
<div class="row">写 job.json 时组装全部记忆(L1-L4)</div>
|
||
<div class="row">读 outbox.ndjson 入 DB,状态机 send(event)</div>
|
||
</div>
|
||
<div class="box green">
|
||
<h4>Worker — 不碰 DB</h4>
|
||
<div class="row">读 runs/<runId>/job.json</div>
|
||
<div class="row">执行 agent pipeline,追加 outbox.ndjson</div>
|
||
<div class="row">任何 DB 数据 → daemon 组装进 job.json 下发</div>
|
||
<div class="row">状态机 actor 在 daemon 侧管理</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════ ② Statechart 设计 ══════════ -->
|
||
<a class="chapter-anchor" id="d-statechart"></a>
|
||
<div class="chapter-hdr"><span>②</span> 状态设计(schedule × work_type)<span class="phase p1">Phase 1</span></div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">任务提交与入口(Ingestion)</div>
|
||
<p class="lead" style="font-size:12px;margin:0 0 12px;color:var(--text2)">提交方式<strong>不变</strong>——主入口仍是<strong>网页表单</strong>。提交后任务落 <code>(init, 无 work_type)</code>,再由系统赋 work_type / 产 work_plan 进入调度。</p>
|
||
|
||
<h4 style="margin:14px 0 8px">三条入口</h4>
|
||
<table class="tbl">
|
||
<tr><th style="width:160px">入口</th><th>路径</th><th>用途</th></tr>
|
||
<tr><td>网页表单</td><td>「+ 新建任务」→ <code>POST /api/projects/:id/tasks</code></td><td>人手动提交(<strong>主入口</strong>)</td></tr>
|
||
<tr><td>MCP 工具</td><td><code>create_task</code> / <code>decompose_task</code></td><td>Claude Code 会话 / agent 编程建任务</td></tr>
|
||
<tr><td>todo.json 同步</td><td><code>src/sync/</code> 单向同步</td><td>legacy <code>/todo</code> 数据自动导入</td></tr>
|
||
<tr><td style="color:var(--text3)">GitHub issue(未来)</td><td style="color:var(--text3)">webhook 入站 → 建任务</td><td style="color:var(--text3)">TODO #1 双向同步,本期不含</td></tr>
|
||
</table>
|
||
|
||
<h4 style="margin:18px 0 8px">提交字段(极简 + 可选增强)</h4>
|
||
<table class="tbl">
|
||
<tr><th style="width:230px">字段</th><th style="width:110px">必填?</th><th>说明</th></tr>
|
||
<tr><td><code>title</code></td><td>✅ 必填</td><td>唯一硬要求</td></tr>
|
||
<tr><td><code>complexity</code></td><td>默认 <code>auto</code></td><td><code>auto</code> = LLM 异步分类回填(hard/medium/easy);也可手填</td></tr>
|
||
<tr><td><code>priority</code> / <code>deps</code></td><td>可选</td><td>缺省 P2 / 无依赖</td></tr>
|
||
<tr><td><code>owned_files</code> / <code>scope</code> / <code>expected_output</code></td><td>可选</td><td>新模型增强:喂调度 couplingPenalty + 给 planner 更准起点;<strong>缺省由 planner 补全</strong></td></tr>
|
||
</table>
|
||
<div class="note" style="margin-top:8px;font-size:11px">设计取向:<strong>提交极简(只逼 title),质量靠 planner 兜</strong>;可选字段让愿意填的人给更强的调度/拆解信号,但从不强制。</div>
|
||
|
||
<h4 style="margin:18px 0 8px">提交后流转</h4>
|
||
<div class="pipe-stage">
|
||
<div class="pipe-box human">提交</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box exec">(init, 无 work_type)</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box review">plan run:planner 产 work_plan</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box gate">(ready, 首 work_type)</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box exec">调度</div>
|
||
</div>
|
||
<div class="note" style="margin-top:6px">Hard/Medium 走 <code>plan</code>(planner 拆解 / 写 spec 产 work_plan 队列);<strong>Easy 跳过 plan</strong>,直接 <code>work_plan=[code, review, pr]</code> → ready。<code>autonomy</code>(manual / auto-easy / auto-approved)决定提交后是<strong>自动起跑</strong>还是<strong>等人放行</strong>。</div>
|
||
|
||
<h4 style="margin:18px 0 8px">附件支持(图片 / 文件)<span class="phase p2" style="margin-left:8px">Phase 2</span></h4>
|
||
<table class="tbl">
|
||
<tr><th style="width:140px">面</th><th>设计</th></tr>
|
||
<tr><td>表单</td><td>「+ 新建任务」支持<strong>拖拽 / 粘贴截图 + 上传文件</strong>(bug 截图、设计稿、规格文档、数据样本)</td></tr>
|
||
<tr><td>API / 存储</td><td><code>POST /api/tasks/:id/attachments</code>(multipart)→ 存 <code>tasks/<taskId>/attachments/</code>;<code>tasks.attachments</code> JSON 存 <code>[{name, type, path}]</code></td></tr>
|
||
<tr><td>喂给 agent</td><td>附件路径随 job.json 下发 → worker 把<strong>图片直接喂进 agent 多模态输入</strong>、文件作为 Read 目标;附件是任务上下文的一等部分(并入 ⑤ AgentContext)</td></tr>
|
||
</table>
|
||
|
||
<h4 style="margin:18px 0 8px">AI 提任务(agent-initiated)<span class="phase p2" style="margin-left:8px">Phase 2</span></h4>
|
||
<p style="font-size:12px;margin:0 0 8px;color:var(--text2)">已部分存在(MCP <code>create_task</code> + planner decompose 产子任务)。扩展为一等能力:</p>
|
||
<table class="tbl">
|
||
<tr><th style="width:140px">来源</th><th>设计</th></tr>
|
||
<tr><td>执行中衍生</td><td>agent 执行时发现额外工作 → 经 MCP 提<strong>关联任务</strong>(parent / sibling),不打断当前 run</td></tr>
|
||
<tr><td>巡检 agent</td><td>可注册的 groomer agent(work_type,呼应 ⑤ 动态扩展)周期扫 repo / backlog / issue → 提议任务</td></tr>
|
||
<tr><td>来源标记</td><td><code>tasks.source</code>(human|ai|sync|github)+ <code>created_by</code>(提交它的 agent run id),区分人提 vs AI 提</td></tr>
|
||
</table>
|
||
<div class="note" style="margin-top:8px">🛡 <strong>护栏</strong>:AI 提的任务默认进<strong>「待确认新任务」人工闸</strong>(不自动起跑),人审后才入调度——防 AI 自我增殖任务失控烧钱(呼应 ⑥ 预算护栏)。是否需人审由项目 <code>autonomy</code> + AI-task 开关决定;纯子任务拆解(planner decompose)已有 plan_review 闸覆盖。</div>
|
||
</div>
|
||
|
||
<!-- ── schedule_status 状态说明 ── -->
|
||
<div class="card">
|
||
<div class="card-title">schedule_status(调度状态,对齐 OS 进程模型)</div>
|
||
<table class="tbl">
|
||
<tr><th>状态</th><th>OS 类比</th><th>含义</th><th>可用 work_type</th></tr>
|
||
<tr><td><span class="badge cyan">init</span></td><td>New</td><td>任务刚创建,尚未确定执行路径,无 work_type</td><td>—</td></tr>
|
||
<tr><td><span class="badge green">ready</span></td><td>Ready</td><td>有 work_type,等待 Orchestrator 调度</td><td>任意</td></tr>
|
||
<tr><td><span class="badge violet">running</span></td><td>Running</td><td>Worker 正在执行当前 work_type 对应的 agent</td><td>任意</td></tr>
|
||
<tr><td><span class="badge amber">waiting</span></td><td>Blocked (I/O)</td><td>等待<strong>人工输入</strong>(审批/拒绝),无 agent 在运行</td><td>保持完成步骤的 work_type(如 plan / review)</td></tr>
|
||
<tr><td><span class="badge red">blocked</span></td><td>Blocked (deps)</td><td>等待<strong>任务依赖</strong>完成,deps 满足后自动回 ready</td><td>保持上次 work_type</td></tr>
|
||
<tr><td><span class="badge amber">paused</span></td><td>Stopped</td><td>人工暂停,恢复时回到 ready(保持 work_type)</td><td>保持上次 work_type</td></tr>
|
||
<tr><td><span class="badge red">failed</span></td><td>Zombie</td><td>当前步骤失败,退避后自动重试(回 ready);超重试上限 → stuck</td><td>同失败时 work_type</td></tr>
|
||
<tr><td><span class="badge" style="color:#f97316;border-color:#f97316">stuck</span></td><td>Stopped (wait human)</td><td>任务卡住需<strong>人工介入</strong>:①重试耗尽 ②agent 主动上报歧义/不可解 ③审批超时兜底。自动化停止,等人决策</td><td>保持卡住时 work_type</td></tr>
|
||
<tr><td style="color:var(--text3)"><span class="badge" style="color:var(--text3);border-color:var(--text3)">done</span></td><td>Terminated</td><td>work_plan 队列全部完成,终态</td><td>—</td></tr>
|
||
<tr><td style="color:var(--text3)"><span class="badge" style="color:var(--text3);border-color:var(--text3)">cancelled</span></td><td>Killed</td><td>人工取消,终态</td><td>—</td></tr>
|
||
</table>
|
||
<div class="note">✅ <strong>stuck</strong> 是一等 schedule_status 值,直接存 DB 列——可用 <code>WHERE schedule_status = 'stuck'</code> 直接查,无需 retry_count 联合判断。人工处理完成后可转 ready / done / cancelled。</div>
|
||
</div>
|
||
|
||
<!-- ── work_type 目录 ── -->
|
||
<div class="card">
|
||
<div class="card-title">work_type(工作类型,决定用哪个 Agent)</div>
|
||
<div class="grid-2">
|
||
<div>
|
||
<h4 style="color:var(--cyan);margin:0 0 10px">Agent 类型(schedule = ready → running)</h4>
|
||
<table class="tbl">
|
||
<tr><th>work_type</th><th>执行者</th><th>主要工作</th></tr>
|
||
<tr><td><code>plan</code></td><td>Planner Agent</td><td>分析任务,产出初始 work_plan(步骤队列 + 每步 spec)</td></tr>
|
||
<tr><td><code>code</code></td><td>Executor Agent</td><td>在 worktree 里实现代码变更,commit</td></tr>
|
||
<tr><td><code>review</code></td><td>Reviewer Agent</td><td>代码审查 + 安全审计(run 内 Promise.all 并行),输出 verdict。<strong>独立 run</strong>、不并入 code;<strong>可选</strong>(work_plan 可省略)</td></tr>
|
||
<tr><td><code>pr</code></td><td>PR Agent</td><td>review 通过后创建 PR + 完成合并(冲突 → conflict)</td></tr>
|
||
<tr><td><code>conflict</code></td><td>Conflict Agent</td><td>git merge 制造冲突 → CC 解 → 复审</td></tr>
|
||
<tr><td><code>research</code></td><td>Research Agent</td><td>技术调研,产出报告(无代码修改)</td></tr>
|
||
</table>
|
||
</div>
|
||
<div>
|
||
<h4 style="color:var(--amber);margin:0 0 10px">人工闸(schedule = waiting,无 Agent 运行)</h4>
|
||
<div class="box amber" style="margin-bottom:12px">
|
||
<p style="font-size:12px;color:var(--text2);margin:0"><strong>waiting 的 work_type 直接沿用上一步完成时的值</strong>,不引入新枚举——<code>waiting(plan)</code> 等人审 plan 输出,<code>waiting(review)</code> 等人审代码复审结果。</p>
|
||
</div>
|
||
<table class="tbl">
|
||
<tr><th>当前 work_type</th><th>等待内容</th><th>通过 → 下一步</th><th>拒绝 → 下一步</th></tr>
|
||
<tr><td><code>plan</code></td><td>人审 Planner 产出的 work_plan</td><td>pop 队头,进 ready(下一 work_type)</td><td>重回 ready(plan),带拒绝原因重规划</td></tr>
|
||
<tr><td><code>review</code></td><td>人审执行结果 + 复审 verdict</td><td>pop → pr 步(提 PR + 合并)→ done</td><td>ready(code),带拒绝原因重做</td></tr>
|
||
</table>
|
||
<div class="box amber" style="margin-top:12px">
|
||
<h4>为什么 waiting ≠ running</h4>
|
||
<p style="font-size:12px;color:var(--text2);margin:0">人工闸没有 worker 进程在运行,Orchestrator 不应把并发名额算在它上面;running 才占名额,waiting 不占。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── 混合模式:下一步如何决定 ── -->
|
||
<div class="card">
|
||
<div class="card-title">混合模式:下一步如何决定(Hybrid Next-Step)</div>
|
||
<div class="grid-2">
|
||
<div>
|
||
<h4 style="color:var(--cyan);margin:0 0 10px">Plan 阶段:产出初始 work_plan 队列</h4>
|
||
<pre style="font-size:11px;background:var(--code-bg);padding:12px;border-radius:6px;line-height:1.7">{
|
||
"type": "plan-complete",
|
||
"work_plan": [
|
||
{ "type": "code",
|
||
"spec": "实现 auth 模块",
|
||
"owned_files": ["src/auth/*.ts"],
|
||
"expected_output": "typecheck 通过" },
|
||
{ "type": "review" },
|
||
{ "type": "pr",
|
||
"spec": "创建 PR 到 main" }
|
||
]
|
||
}
|
||
// → waiting(plan) 等人审计划
|
||
// 人 approve → pop 队头 → ready(code)</pre>
|
||
</div>
|
||
<div>
|
||
<h4 style="color:var(--green);margin:0 0 10px">执行阶段:每步可修改剩余队列</h4>
|
||
<pre style="font-size:11px;background:var(--code-bg);padding:12px;border-radius:6px;line-height:1.7">{
|
||
"type": "step-complete",
|
||
"result": "发现 auth 复杂,拆成2步",
|
||
"update_plan": [ // ← 可选,修改剩余队列
|
||
{ "type": "code",
|
||
"spec": "先实现 session 层" },
|
||
{ "type": "code", // ← 动态插入新步骤
|
||
"spec": "再实现 token 层" },
|
||
{ "type": "review" }
|
||
]
|
||
}
|
||
// 无 update_plan → pop 队头继续
|
||
// update_plan 非空 → 替换剩余队列</pre>
|
||
</div>
|
||
</div>
|
||
|
||
<h4 style="color:var(--cyan);margin:16px 0 8px">队列为空时</h4>
|
||
<div class="grid-2">
|
||
<div class="box green">
|
||
<p style="font-size:12px;color:var(--text2);margin:0">step-complete 后 <code>work_plan</code> 队列为空 → <code>schedule_status = done</code>,任务完成。</p>
|
||
</div>
|
||
<div class="box amber">
|
||
<p style="font-size:12px;color:var(--text2);margin:0">Easy 任务无需 plan 步骤:init 时 Orchestrator 直接写入默认队列 <code>[code, review]</code>,跳过 plan。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── 完整状态转移规则 ── -->
|
||
<div class="card">
|
||
<div class="card-title">完整转移规则</div>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1160 520" style="width:100%;display:block;margin:16px 0;">
|
||
<defs>
|
||
<marker id="ag" markerWidth="7" markerHeight="6" refX="6" refY="3" orient="auto"><polygon points="0 0,7 3,0 6" fill="#82b366"/></marker>
|
||
<marker id="ao" markerWidth="7" markerHeight="6" refX="6" refY="3" orient="auto"><polygon points="0 0,7 3,0 6" fill="#d6783b"/></marker>
|
||
<marker id="ar" markerWidth="7" markerHeight="6" refX="6" refY="3" orient="auto"><polygon points="0 0,7 3,0 6" fill="#b85450"/></marker>
|
||
<marker id="ap" markerWidth="7" markerHeight="6" refX="6" refY="3" orient="auto"><polygon points="0 0,7 3,0 6" fill="#9673a6"/></marker>
|
||
</defs>
|
||
|
||
<!-- ── 主干(绿,自动) ── -->
|
||
<path d="M 146 210 L 248 210" stroke="#82b366" stroke-width="1.5" fill="none" marker-end="url(#ag)" />
|
||
<path d="M 352 210 L 464 210" stroke="#82b366" stroke-width="1.5" fill="none" marker-end="url(#ag)" />
|
||
<path d="M 576 210 L 691 210" stroke="#82b366" stroke-width="1.5" fill="none" marker-end="url(#ag)" />
|
||
<path d="M 799 210 L 917 210" stroke="#82b366" stroke-width="1.5" fill="none" marker-end="url(#ag)" />
|
||
<!-- pr merge 顶弧(绿)running→done -->
|
||
<path d="M 548 198 Q 745 100 940 198" stroke="#82b366" stroke-width="1.5" fill="none" marker-end="url(#ag)" />
|
||
<!-- retry 回流(绿)failed→ready -->
|
||
<path d="M 500 394 Q 400 308 332 228" stroke="#82b366" stroke-width="1.5" fill="none" marker-end="url(#ag)" />
|
||
<!-- deps✓(绿)blocked→ready -->
|
||
<path d="M 312 392 L 312 230" stroke="#82b366" stroke-width="1.5" fill="none" marker-end="url(#ag)" />
|
||
|
||
<!-- ── 人工(橙) ── -->
|
||
<!-- approve/reject 顶弧 waiting→ready(高过 paused) -->
|
||
<path d="M 745 192 Q 430 -120 300 192" stroke="#d6783b" stroke-width="1.5" fill="none" marker-end="url(#ao)" />
|
||
<!-- agent 上报 running→stuck -->
|
||
<path d="M 542 228 Q 650 320 722 393" stroke="#d6783b" stroke-width="1.5" fill="none" marker-end="url(#ao)" />
|
||
<!-- 人工: 重试 stuck→ready(低位绕行,避开中部交叉) -->
|
||
<path d="M 712 430 C 540 500 300 500 200 445 C 175 380 205 250 272 228" stroke="#d6783b" stroke-width="1.5" fill="none" marker-end="url(#ao)" />
|
||
<!-- 人工: 完成 stuck→done -->
|
||
<path d="M 782 396 Q 862 326 942 228" stroke="#d6783b" stroke-width="1.5" fill="none" marker-end="url(#ao)" />
|
||
<!-- 人工: 取消 stuck→cancelled -->
|
||
<path d="M 795 410 L 903 410" stroke="#d6783b" stroke-width="1.5" fill="none" marker-end="url(#ao)" />
|
||
<!-- 暂停 running→paused -->
|
||
<path d="M 502 193 Q 458 148 446 104" stroke="#d6783b" stroke-width="1.5" fill="none" marker-end="url(#ao)" />
|
||
<!-- 恢复 paused→ready -->
|
||
<path d="M 396 92 C 300 108 286 165 288 191" stroke="#d6783b" stroke-width="1.5" fill="none" marker-end="url(#ao)" />
|
||
|
||
<!-- ── 异常(红) ── -->
|
||
<path d="M 520 228 L 520 392" stroke="#b85450" stroke-width="1.5" fill="none" marker-end="url(#ar)" />
|
||
<path d="M 572 410 L 695 410" stroke="#b85450" stroke-width="1.5" fill="none" marker-end="url(#ar)" />
|
||
<path d="M 288 228 L 288 392" stroke="#b85450" stroke-width="1.5" fill="none" marker-end="url(#ar)" />
|
||
|
||
<!-- ── 自动串联(紫虚线)running 自环 ── -->
|
||
<path d="M 498 194 C 498 158 542 158 542 194" stroke="#9673a6" stroke-width="1.5" fill="none" stroke-dasharray="4,3" marker-end="url(#ap)" />
|
||
|
||
<!-- ── 节点 ── -->
|
||
<ellipse cx="100" cy="210" rx="46" ry="18" fill="#2a2a2a" stroke="#888888"/>
|
||
<text x="100" y="214" text-anchor="middle" font-family="monospace" font-size="11.5" fill="#aaaaaa">init</text>
|
||
<ellipse cx="300" cy="210" rx="52" ry="18" fill="#1a2e1a" stroke="#82b366"/>
|
||
<text x="300" y="214" text-anchor="middle" font-family="monospace" font-size="11.5" fill="#82b366">ready</text>
|
||
<ellipse cx="520" cy="210" rx="56" ry="18" fill="#221a2e" stroke="#9673a6"/>
|
||
<text x="520" y="214" text-anchor="middle" font-family="monospace" font-size="11.5" fill="#c4a8d4">running</text>
|
||
<ellipse cx="745" cy="210" rx="54" ry="18" fill="#2e2a14" stroke="#d6b656"/>
|
||
<text x="745" y="214" text-anchor="middle" font-family="monospace" font-size="11.5" fill="#d6b656">waiting</text>
|
||
<ellipse cx="965" cy="210" rx="48" ry="18" fill="#1a472a" stroke="#2d7a40"/>
|
||
<text x="965" y="214" text-anchor="middle" font-family="monospace" font-size="11.5" font-weight="bold" fill="#ffffff">done</text>
|
||
<ellipse cx="440" cy="85" rx="54" ry="18" fill="#2e2a14" stroke="#d6b656"/>
|
||
<text x="440" y="89" text-anchor="middle" font-family="monospace" font-size="11.5" fill="#d6b656">paused</text>
|
||
<ellipse cx="300" cy="410" rx="56" ry="18" fill="#2e1a1a" stroke="#b85450"/>
|
||
<text x="300" y="414" text-anchor="middle" font-family="monospace" font-size="11.5" fill="#e07070">blocked</text>
|
||
<ellipse cx="520" cy="410" rx="52" ry="18" fill="#2e1a1a" stroke="#b85450"/>
|
||
<text x="520" y="414" text-anchor="middle" font-family="monospace" font-size="11.5" fill="#e07070">failed</text>
|
||
<ellipse cx="745" cy="410" rx="50" ry="18" fill="#2e2014" stroke="#d6783b"/>
|
||
<text x="745" y="414" text-anchor="middle" font-family="monospace" font-size="11.5" fill="#f97316">stuck</text>
|
||
<ellipse cx="965" cy="410" rx="62" ry="18" fill="#2e2e2e" stroke="#555555"/>
|
||
<text x="965" y="414" text-anchor="middle" font-family="monospace" font-size="11.5" font-weight="bold" fill="#888888">cancelled</text>
|
||
|
||
<!-- ── 边标签 ── -->
|
||
<text x="197" y="202" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#666">写入队列</text>
|
||
<text x="408" y="202" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#666">claim</text>
|
||
<text x="633" y="202" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#666">plan/review done</text>
|
||
<text x="858" y="202" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#666">review approve→pr</text>
|
||
<text x="430" y="16" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6783b">approve / reject</text>
|
||
<text x="745" y="90" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#666">pr merge</text>
|
||
<text x="520" y="150" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#9673a6">code→review (auto)</text>
|
||
<text x="538" y="318" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#b85450">error</text>
|
||
<text x="392" y="338" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#666">retry</text>
|
||
<text x="633" y="402" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#b85450">retry 耗尽</text>
|
||
<text x="648" y="300" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6783b">agent 上报</text>
|
||
<text x="430" y="478" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6783b">人工: 重试</text>
|
||
<text x="878" y="318" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6783b">人工: 完成</text>
|
||
<text x="849" y="402" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6783b">人工: 取消</text>
|
||
<text x="266" y="318" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#b85450">deps⬇</text>
|
||
<text x="334" y="318" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#666">deps✓</text>
|
||
<text x="430" y="140" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6783b">暂停</text>
|
||
<text x="296" y="128" text-anchor="middle" font-family="sans-serif" font-size="8.5" fill="#d6783b">恢复</text>
|
||
|
||
<!-- ── 图例 ── -->
|
||
<line x1="1058" y1="150" x2="1090" y2="150" stroke="#82b366" stroke-width="1.5" marker-end="url(#ag)"/>
|
||
<text x="1095" y="154" font-family="sans-serif" font-size="9" fill="#82b366">自动</text>
|
||
<line x1="1058" y1="172" x2="1090" y2="172" stroke="#d6783b" stroke-width="1.5" marker-end="url(#ao)"/>
|
||
<text x="1095" y="176" font-family="sans-serif" font-size="9" fill="#d6783b">人工</text>
|
||
<line x1="1058" y1="194" x2="1090" y2="194" stroke="#b85450" stroke-width="1.5" marker-end="url(#ar)"/>
|
||
<text x="1095" y="198" font-family="sans-serif" font-size="9" fill="#b85450">异常</text>
|
||
<line x1="1058" y1="216" x2="1090" y2="216" stroke="#9673a6" stroke-dasharray="4,3" stroke-width="1.5" marker-end="url(#ap)"/>
|
||
<text x="1095" y="220" font-family="sans-serif" font-size="9" fill="#9673a6">自动串联</text>
|
||
|
||
</svg>
|
||
</div>
|
||
|
||
<!-- ── 执行路径示例 ── -->
|
||
<div class="card">
|
||
<div class="card-title">执行路径示例</div>
|
||
<div class="grid-2">
|
||
<div>
|
||
<h4 style="color:var(--amber);margin:0 0 8px">Hard 任务(需规划)</h4>
|
||
<div class="arch-diagram" style="font-size:10.5px;line-height:1.9;margin:0">
|
||
(init, -)
|
||
↓ Orchestrator 写队列 [plan, code, review, pr]
|
||
(ready, plan)
|
||
↓ worker 启动
|
||
(running, plan)
|
||
↓ plan 产出 work_plan(可拆多 code 步 / 决定免 review)
|
||
(waiting, plan)
|
||
↓ 人 approve
|
||
(ready, code) → (running, code)
|
||
↓ code agent 完成(commit;run 结束)
|
||
(running, review) <span style="color:var(--text3)">← 自动串联(独立 reviewer run,非 code 内联)</span>
|
||
↓ review 完成(run 内并行双复审 verdict)
|
||
(waiting, review)
|
||
↓ 人 approve
|
||
(running, pr) <span style="color:var(--text3)">← approve 后串联</span>
|
||
↓ pr agent 提 PR + 合并(冲突→conflict run)
|
||
done ✓
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<h4 style="color:var(--green);margin:0 0 8px">Easy 任务(跳过规划)</h4>
|
||
<div class="arch-diagram" style="font-size:10.5px;line-height:1.9;margin:0">
|
||
(init, -)
|
||
↓ Orchestrator 写队列 [code, review, pr]
|
||
(ready, code)
|
||
↓ worker 启动
|
||
(running, code)
|
||
↓ code agent 完成
|
||
(running, review) <span style="color:var(--text3)">← 自动串联(独立 reviewer run)</span>
|
||
↓ review agent 完成
|
||
(waiting, review)
|
||
↓ 人 approve
|
||
(running, pr) → 提 PR + 合并 → done ✓
|
||
</div>
|
||
<div class="box amber" style="margin-top:10px"><p style="font-size:11px;color:var(--text2);margin:0"><strong>review 可选</strong>:work_plan 不含 review 时,code 完成 → 直接 (running, pr);纯只读任务可仅 <code>[research]</code>,无 code/review/pr。</p></div>
|
||
<h4 style="color:var(--cyan);margin:16px 0 8px">动态拆步(运行中修改计划)</h4>
|
||
<div class="arch-diagram" style="font-size:10.5px;line-height:1.9;margin:0">
|
||
(running, code)
|
||
↓ agent 发现比预期复杂
|
||
↓ step-complete + update_plan:
|
||
[code(token层), review]
|
||
(ready, code) <span style="color:var(--text3)">← 新步骤(session 层)</span>
|
||
(running, code) <span style="color:var(--text3)">← 再次 code(token 层)</span>
|
||
(running, review) → (waiting, review) → done ✓
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── DB 设计 ── -->
|
||
<div class="card">
|
||
<div class="card-title">DB 字段(tasks 表关键变更)</div>
|
||
<div class="grid-2">
|
||
<div>
|
||
<table class="tbl">
|
||
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
|
||
<tr><td><code>schedule_status</code></td><td>TEXT</td><td>取代旧 <code>status</code>:init|ready|running|waiting|blocked|paused|failed|<strong>stuck</strong>|done|cancelled</td></tr>
|
||
<tr><td><code>work_type</code></td><td>TEXT</td><td>当前工作类型:plan|code|review|pr|conflict|research|null</td></tr>
|
||
<tr><td><code>work_plan</code></td><td>TEXT JSON</td><td>剩余步骤队列 <code>[{type, spec, owned_files, expected_output}]</code></td></tr>
|
||
<tr><td><code>work_history</code></td><td>TEXT JSON</td><td>已完成步骤记录(审计+重试上下文)</td></tr>
|
||
<tr><td><code>claimed_at</code></td><td>TEXT</td><td>CAS 锁,NULL=可领取,非NULL=已锁</td></tr>
|
||
</table>
|
||
</div>
|
||
<div>
|
||
<h4 style="color:var(--cyan);margin:0 0 10px">Orchestrator 关键查询</h4>
|
||
<pre style="font-size:11px;background:var(--code-bg);padding:12px;border-radius:6px;line-height:1.7">-- 可调度任务(schedule 维度)
|
||
SELECT * FROM tasks
|
||
WHERE schedule_status = 'ready'
|
||
AND claimed_at IS NULL
|
||
AND (next_eligible_at IS NULL
|
||
OR next_eligible_at <= datetime('now'))
|
||
ORDER BY score DESC;
|
||
|
||
-- 派发 agent(work_type 维度)
|
||
switch (task.work_type) {
|
||
case 'plan': return runPlannerAgent(task);
|
||
case 'code': return runExecutorAgent(task);
|
||
case 'review': return runReviewerAgent(task);
|
||
case 'pr': return runPRAgent(task);
|
||
case 'conflict':return runConflictAgent(task);
|
||
}</pre>
|
||
</div>
|
||
</div>
|
||
<div class="note" style="margin-top:12px">⚠ <strong>waiting(人工闸)不占并发名额</strong>:Orchestrator 的 <code>inflight</code> 计数只统计 <code>schedule_status=running</code> 的任务;<code>waiting</code> 的任务不计入,也不占 <code>project.concurrency</code>。</div>
|
||
</div>
|
||
|
||
<!-- ── 与旧平铺 FSM 对比 ── -->
|
||
<div class="card">
|
||
<div class="card-title">与旧设计对比</div>
|
||
<table class="tbl">
|
||
<tr><th>维度</th><th>旧设计(平铺 FSM)</th><th>新设计(schedule × work_type)</th></tr>
|
||
<tr><td>状态数量</td><td>16 个混合状态(analyzing/speccing/ready/executing/exec_review…)</td><td>10 个 schedule_status × N 个 work_type,正交组合</td></tr>
|
||
<tr><td>执行路径</td><td>静态,TRANSITIONS 表写死</td><td>动态,work_plan 队列运行时决定</td></tr>
|
||
<tr><td>新增工作类型</td><td>改 TRANSITIONS 表 + 加 status 枚举</td><td>加 work_type 枚举 + 加 agent dispatcher 分支</td></tr>
|
||
<tr><td>人工审批闸</td><td>plan_review/spec_review/exec_review 是独立 status(额外枚举)</td><td>waiting(plan) / waiting(review),直接沿用卡住时 work_type,无新枚举</td></tr>
|
||
<tr><td>stuck</td><td>逻辑态:failed + retry_count ≥ max_retries 联合推导,无独立 DB 值</td><td><strong>一等状态</strong>:独立 DB 值,直接查询;人工处理后出 ready / done / cancelled</td></tr>
|
||
<tr><td>并发占用</td><td>所有非终态都计入 inflight</td><td>只有 running 计入 inflight;waiting / stuck 不占名额</td></tr>
|
||
<tr><td>暂停恢复</td><td>需 paused_return_to 字段记录位置</td><td>(paused, work_type) 自带位置,恢复即 (ready, same work_type)</td></tr>
|
||
<tr><td>可观测性</td><td>一个 status 字段,需要枚举含义</td><td>两个字段,(running, code) 直接表达"正在写代码"</td></tr>
|
||
<tr><td>实现复杂度</td><td>中(平铺 transition 守卫)</td><td>低(两字段 + 队列 pop/push)</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- ── 并发调度 ── -->
|
||
<div class="card">
|
||
<div class="card-title">并发调度(调度单位 · CAS 防双领 · couplingPenalty 错峰)</div>
|
||
<table class="tbl">
|
||
<tr><th>维度</th><th>决策</th></tr>
|
||
<tr><td>最小调度单位</td><td><strong>task</strong>(一次一个 work_step);并发发生在不同 task 之间,不在 task 内部。work_plan 队列<strong>严格线性</strong></td></tr>
|
||
<tr><td>阶段内并行</td><td>判据 = 是否需要<strong>独立 worktree / 写权限</strong>:只读·汇总型 → agent 内 <code>Promise.all</code>(占 1 槽);需独立写 → 拆子任务交调度器(占 N 槽)</td></tr>
|
||
<tr><td>并行建模</td><td>并行 = <strong>拆子任务</strong>,<code>parent decomposed → 子全 done → done</code> 做 join;不引入队列内并行分支节点(保队列模型简洁)</td></tr>
|
||
<tr><td>防双重 claim</td><td>CAS <code>claimed_at</code>(见下);即「执行期锁」的 DB 落地</td></tr>
|
||
<tr><td>文件错峰</td><td>couplingPenalty(见下):与<strong>所有持未合并 worktree 的任务</strong>(running + waiting(review) + 带改动 failed)的 owned_files 重叠扣分,软错峰非硬禁;agingBonus 兜底防饥饿</td></tr>
|
||
<tr><td>并发预算</td><td>槽只认 <code>running</code>;agent 内并行不额外占槽,调度器并行占 N 槽,<code>project.concurrency</code> 封顶</td></tr>
|
||
</table>
|
||
|
||
<div class="grid-2" style="margin-top:16px">
|
||
<div>
|
||
<h4 style="color:var(--cyan);margin:0 0 8px">CAS 防双重 claim</h4>
|
||
<pre>-- 抢占:仅当未被锁定才成功
|
||
UPDATE tasks SET <span class="str">claimed_at</span> = :now
|
||
WHERE id = :id AND <span class="str">claimed_at IS NULL</span>;
|
||
-- changes==1 → 抢到;==0 → 已被占,放弃</pre>
|
||
<div class="row" style="color:var(--text3);margin-top:6px">单条 SQL 行级原子,无论多少路径同时领同一任务,<strong>最多一个成功</strong>。run 结束(finishRun / failAttempt / reap)置 NULL 释放。比"用 run.status 反推"更直接、可索引。</div>
|
||
</div>
|
||
<div>
|
||
<h4 style="color:var(--amber);margin:0 0 8px">couplingPenalty 文件错峰</h4>
|
||
<pre>score(t) = rankU(t) + agingBonus(t)
|
||
<span class="add">− 0.5 × | t.owned_files ∩
|
||
(未合并 worktree 任务的 owned_files 并集) |</span></pre>
|
||
<div class="row" style="color:var(--text3);margin-top:6px">重叠越多分越低 → 越靠后领 → 大概率等冲突任务先跑完。按<strong>任务级 owned_files</strong> 并集算,覆盖<strong>所有分支上有未合并提交的任务</strong>(running + waiting(review) 待合并 + 带改动 failed),<strong>不只 running</strong>——否则 code 完进 waiting 等人审时分支已有提交,只看 running 会漏算、照样撞冲突。软错峰:实在没别的可干仍会跑,agingBonus 防永久饥饿。</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h4 style="margin:18px 0 8px">阶段内并行:放调度器还是放 agent?</h4>
|
||
<table class="tbl">
|
||
<tr><th>并行的性质</th><th>放哪</th><th>占槽</th><th>例子</th></tr>
|
||
<tr><td>共享同一 worktree、只读 / 汇总(不互相写)</td><td><strong>agent 自己并行</strong>(worker 内 Promise.all)</td><td>1 槽</td><td>双复审 code + security(都只读同一 diff)</td></tr>
|
||
<tr><td>各自要写、可隔离(不同文件 / 模块)</td><td><strong>拆子任务交调度器</strong>(各自 worktree)</td><td>N 槽</td><td>feature 拆「auth 层」+「UI 层」</td></tr>
|
||
</table>
|
||
|
||
<div class="note" style="margin-top:14px">🔗 <strong>闭环</strong>:planner 拆并行子任务时须给<strong>互斥的 owned_files</strong>,它们才真并行;给重叠了 couplingPenalty 自动把它们串起来——拆解质量与调度错峰相互校正。<br>⛔ <strong>反模式</strong>:同一 worktree 内多个写手并发(git index / 文件竞态)。要并行写就必须隔离 worktree,而隔离 worktree 即「子任务」——于是天然回到调度器并行。</div>
|
||
</div>
|
||
|
||
|
||
<div class="card">
|
||
<div class="card-title">交互平面 · stuck 任务人工接管(interactive run)<span class="phase p2" style="margin-left:8px">Phase 2</span></div>
|
||
<p class="lead" style="font-size:12px;margin:0 0 12px;color:var(--text2)">自治流水线之外的<strong>第二执行平面</strong>:任务卡在 <span class="badge" style="color:#f97316;border-color:#f97316">stuck</span> 时,人可「接管」,像终端一样与带完整上下文的 agent 长对话改完,再交回自治管道。<strong>不改造自治 Worker</strong>,作为并列能力接入。</p>
|
||
|
||
<h4 style="margin:14px 0 8px">两个执行平面</h4>
|
||
<table class="tbl">
|
||
<tr><th>维度</th><th>自治平面(现有)</th><th>交互平面(新增)</th></tr>
|
||
<tr><td>触发</td><td>orchestrator 按 score 调度</td><td>人显式接管(on-demand,<code>POST /api/tasks/:id/takeover</code>)</td></tr>
|
||
<tr><td>传输</td><td><strong>纯文件</strong>(job.json / outbox / heartbeat)</td><td><strong>实时双向</strong>:<code>runs/<runId>/console.sock</code>(unix socket)↔ daemon ↔ 客户端 WS</td></tr>
|
||
<tr><td>节奏</td><td>跑完即退、可 reap</td><td>人驱动、按对话推进</td></tr>
|
||
<tr><td>重启可恢复</td><td>✅ 文件协议保证</td><td>❌ 牺牲,改由 <strong>session resume</strong> 找回</td></tr>
|
||
<tr><td>run kind</td><td>plan / code / review / pr / conflict</td><td><span class="chg add">interactive</span></td></tr>
|
||
</table>
|
||
<div class="note" style="margin-top:8px">两平面<strong>共享</strong> worktree / transcript+session / DB 状态(经 daemon)/ task 模型,只在<strong>传输 + 生命周期触发</strong>上不同——自治内核保持纯净,交互作为边界清晰的独立能力。</div>
|
||
|
||
<h4 style="margin:18px 0 8px">接管会话流程</h4>
|
||
<div class="pipe-stage">
|
||
<div class="pipe-box human">stuck</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box exec">人点接管 /takeover</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box exec">spawn interactive run(复用 worktree + resume session)</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box review">人 ↔ agent 长对话</div><div class="pipe-arr">→</div>
|
||
<div class="pipe-box human">人选出口</div>
|
||
</div>
|
||
|
||
<div class="grid-2" style="margin-top:10px">
|
||
<div class="box cyan"><h4>接管期间</h4>
|
||
<div class="row"><code>schedule_status=running</code> + <code>run.kind=interactive</code></div>
|
||
<div class="row">work_type 保持卡住时的值(不污染枚举)</div>
|
||
<div class="row">reaper 对 interactive <strong>关执行超时</strong>,heartbeat 仍发</div>
|
||
<div class="row"><strong>idle-TTL 30min</strong> 无活动 → checkpoint session id → 退出 → 回 stuck;重连即 resume(挂起/恢复无缝)</div>
|
||
</div>
|
||
<div class="box green"><h4>结束出口(人选)</h4>
|
||
<div class="row">修好 → 交回:commit WIP,回 <code>ready</code> 进下一 work_type(review/pr)</div>
|
||
<div class="row">修好 → 直接完成:走 pr / 合并 → done</div>
|
||
<div class="row">放弃 → 回 stuck / cancelled</div>
|
||
<div class="row">session id 落 <code>work_history</code> → 后续自治 run 可 resume,接管对话喂 L1–L4 记忆</div>
|
||
</div>
|
||
</div>
|
||
|
||
<h4 style="margin:18px 0 8px">关键决策(已定)</h4>
|
||
<table class="tbl">
|
||
<tr><th>决策点</th><th>取定</th></tr>
|
||
<tr><td>schedule_status</td><td><strong>复用 <code>running</code></strong> + <code>run.kind=interactive</code>,不加状态枚举;reaper 单点特判</td></tr>
|
||
<tr><td>工具权限</td><td><strong>有界可配</strong>:项目级自定义白名单,但只能在<strong>允许超集</strong>内勾选——<code>effective = 用户选 ∩ 允许超集</code>;<code>git push</code> / 部署等红线<strong>永不入超集</strong></td></tr>
|
||
<tr><td>idle-TTL</td><td><strong>30 分钟</strong>挂起 + resume 重连</td></tr>
|
||
<tr><td>并发名额</td><td><strong>占 1 个</strong> <code>project.concurrency</code>(在用 worktree + agent),可单独配小池避免抢占自治吞吐</td></tr>
|
||
</table>
|
||
|
||
<div class="note" style="margin-top:12px">📦 <strong>Schema 影响极小</strong>:<code>runs.kind</code> 扩枚举加 <code>interactive</code>(TEXT 兼容、非 work_type);session id 复用 <code>work_history</code>;<code>console.sock</code> 是 <code>runs/<runId>/</code> 下的文件——<strong>无需新增列</strong>。新增的是 daemon 的 takeover API + WS↔socket 桥接 + reaper 对 interactive 的特判 + 项目级 <code>interactive_tools</code> 配置(受允许超集约束)。</div>
|
||
</div>
|
||
|
||
|
||
<a class="chapter-anchor" id="d-db"></a>
|
||
<div class="chapter-hdr"><span>③</span> 数据库 Schema 变更<span class="phase p1">Phase 1</span></div>
|
||
|
||
<div class="card" style="border-left:3px solid var(--amber)">
|
||
<div class="card-title">★ 本方案新增 / 变更字段汇总(schedule × work_type 模型)</div>
|
||
<p class="lead" style="font-size:12px;margin:0 0 12px;color:var(--text2)">核心是 <code>tasks</code> 表的状态建模:用 <strong>schedule_status + work_type + work_plan 队列</strong> 取代旧 <code>status</code> 单字段;状态机以「队列驱动」实现(非 XState 快照),故 <code>state_snapshot / sub_status</code> 不引入。</p>
|
||
<table class="tbl">
|
||
<tr><th>表</th><th>字段</th><th>类型</th><th>用途</th><th>性质</th></tr>
|
||
<tr><td><code>tasks</code></td><td><code>schedule_status</code></td><td>TEXT</td><td>调度状态机:init|ready|running|waiting|blocked|paused|failed|<strong>stuck</strong>|done|cancelled,<strong>取代旧 status</strong></td><td><span class="chg mod">取代 status</span></td></tr>
|
||
<tr><td><code>tasks</code></td><td><code>work_type</code></td><td>TEXT</td><td>当前工作类型,决定 dispatch 哪个 agent:plan|code|review|pr|conflict|research|null</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>tasks</code></td><td><code>work_plan</code></td><td>TEXT JSON</td><td>剩余步骤队列 <code>[{type, spec, owned_files, expected_output}]</code>,运行时动态可改</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>tasks</code></td><td><code>work_history</code></td><td>TEXT JSON</td><td>已完成步骤记录(审计 + 重试上下文)</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>tasks</code></td><td><code>claimed_at</code></td><td>TEXT</td><td>CAS 锁,NULL = 可领取、非 NULL = 已锁定(防双重领取)</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>tasks</code></td><td><code>version</code></td><td>INTEGER</td><td>乐观锁版本号,执行期任务修改检测</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>tasks</code></td><td><code>scope</code></td><td>TEXT</td><td>file / module / service / cross-service 改动范围,喂调度 couplingPenalty</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>tasks</code></td><td><code>owned_files</code></td><td>TEXT JSON</td><td>任务声明的主要修改文件路径,喂 couplingPenalty 文件重叠 + diff 越界闸;planner decompose 给子任务赋值</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>tasks</code></td><td><code>expected_output</code></td><td>TEXT</td><td>任务级一句话可验证完成标准(per-step 的在 work_plan 里)</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>tasks</code></td><td><code>attachments</code></td><td>TEXT JSON</td><td>提交附件 <code>[{name, type, path}]</code>(图片/文件);存 tasks/<id>/attachments/,喂 agent 多模态/Read</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>tasks</code></td><td><code>source</code></td><td>TEXT</td><td>任务来源 human|ai|sync|github;AI 提的默认进待确认闸</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>tasks</code></td><td><code>created_by</code></td><td>TEXT</td><td>创建者(人 / 提交它的 agent run id),配合 source 审计</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>projects</code></td><td><code>agent_rules</code></td><td>TEXT</td><td>项目级 agent 专用规范(markdown),L2 记忆注入</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>projects</code></td><td><code>diff_max_files</code></td><td>INTEGER</td><td>diff 体量闸阈值(默认 100 文件)</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>projects</code></td><td><code>checks</code></td><td>TEXT JSON</td><td>分项检查命令 <code>{lint, typecheck, build}</code>…</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>projects</code></td><td><code>budget_usd</code></td><td>REAL</td><td>项目级预算上限(当期);当期 spend 超额 → 自动 paused 停领(见 ⑥ 护栏)</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>projects</code></td><td><code>budget_period</code></td><td>TEXT</td><td>预算周期 <code>day|month</code>(配合 budget_usd)</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>runs</code></td><td><code>kind</code></td><td>TEXT</td><td>对齐 work_type:plan|code|review|pr|<strong>conflict</strong>|research,外加 <strong>interactive</strong>(人工接管 run,非 work_type、不进调度队列)</td><td><span class="chg mod">扩枚举</span></td></tr>
|
||
<tr><td><code>runs</code></td><td><code>trace_id</code></td><td>TEXT</td><td>跨 run 追踪 ID,关联同任务多次运行</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>runs</code></td><td><code>usage</code></td><td>TEXT JSON</td><td>每次模型调用的用量 <code>[{model, input_tokens, output_tokens, cache_read, cache_write}]</code>,喂成本计算</td><td><span class="chg add">新增</span></td></tr>
|
||
<tr><td><code>runs</code></td><td><code>cost_usd</code></td><td>REAL</td><td>本 run 折算成本(按 模型×token 价目表);汇总到 task / project</td><td><span class="chg add">新增</span></td></tr>
|
||
</table>
|
||
<div class="note" style="margin-top:12px">⛔ <strong>已弃用(早期 XState 草案)</strong>:<code>state_snapshot</code> / <code>sub_status</code>(被 schedule_status + work_plan 队列取代)、<code>task_type</code>(任务性质由 work_plan 步骤组成表达,并入 work_type)、<code>parent_version_id</code>(无对应场景)。<br><span style="color:var(--text3)">注:<code>owned_files</code> 同时存在「任务级」(声明级,喂调度/diff 闸)与「work_plan 每步级」(执行级,当前步声明),两者并存不冲突。</span></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">Entity Relationship 图(数据库表结构与关系)</div>
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 950 874" style="width:100%;display:block;margin:16px 0;background:#0a0f1a;border-radius:6px;">
|
||
<defs>
|
||
<marker id="er-one" markerWidth="10" markerHeight="10" refX="9" refY="5" orient="auto"><line x1="8" y1="1" x2="8" y2="9" stroke="#64748b" stroke-width="1.3"/></marker>
|
||
<marker id="er-many" markerWidth="14" markerHeight="10" refX="1" refY="5" orient="auto"><line x1="1" y1="5" x2="9" y2="1" stroke="#64748b" stroke-width="1.3"/><line x1="1" y1="5" x2="9" y2="5" stroke="#64748b" stroke-width="1.3"/><line x1="1" y1="5" x2="9" y2="9" stroke="#64748b" stroke-width="1.3"/><line x1="9" y1="1" x2="9" y2="9" stroke="#64748b" stroke-width="1.3"/></marker>
|
||
<marker id="er-many-r" markerWidth="14" markerHeight="10" refX="13" refY="5" orient="auto"><line x1="13" y1="5" x2="5" y2="1" stroke="#64748b" stroke-width="1.3"/><line x1="13" y1="5" x2="5" y2="5" stroke="#64748b" stroke-width="1.3"/><line x1="13" y1="5" x2="5" y2="9" stroke="#64748b" stroke-width="1.3"/><line x1="5" y1="1" x2="5" y2="9" stroke="#64748b" stroke-width="1.3"/></marker>
|
||
</defs>
|
||
|
||
<!-- projects x=10 y=10 w=285 h=318 -->
|
||
<rect x="10" y="10" width="285" height="24" fill="#1e3a5f" rx="3"/>
|
||
<text x="152.5" y="26" text-anchor="middle" font-family="monospace" font-size="11" font-weight="bold" fill="#60a5fa">projects</text>
|
||
<rect x="10" y="34" width="285" height="294" fill="#0d1f35"/>
|
||
<text x="18" y="47" font-family="monospace" font-size="9" fill="#60a5fa">🔑 id TEXT PK</text>
|
||
<text x="18" y="60" font-family="monospace" font-size="9" fill="#94a3b8"> name TEXT</text>
|
||
<text x="18" y="73" font-family="monospace" font-size="9" fill="#94a3b8"> repo_path TEXT UNIQUE</text>
|
||
<text x="18" y="86" font-family="monospace" font-size="9" fill="#94a3b8"> default_branch TEXT</text>
|
||
<text x="18" y="99" font-family="monospace" font-size="9" fill="#94a3b8"> verify_cmd TEXT</text>
|
||
<text x="18" y="112" font-family="monospace" font-size="9" fill="#94a3b8"> autonomy TEXT</text>
|
||
<text x="18" y="125" font-family="monospace" font-size="9" fill="#94a3b8"> model TEXT</text>
|
||
<text x="18" y="138" font-family="monospace" font-size="9" fill="#94a3b8"> concurrency INTEGER</text>
|
||
<text x="18" y="151" font-family="monospace" font-size="9" fill="#94a3b8"> max_retries INTEGER</text>
|
||
<text x="18" y="164" font-family="monospace" font-size="9" fill="#94a3b8"> timeout_ms INTEGER</text>
|
||
<text x="18" y="177" font-family="monospace" font-size="9" fill="#fb923c">★ checks TEXT (JSON)</text>
|
||
<text x="18" y="190" font-family="monospace" font-size="9" fill="#fb923c">★ diff_max_files INTEGER</text>
|
||
<text x="18" y="203" font-family="monospace" font-size="9" fill="#fb923c">★ budget_usd REAL</text>
|
||
<text x="18" y="216" font-family="monospace" font-size="9" fill="#fb923c">★ budget_period TEXT</text>
|
||
<text x="18" y="229" font-family="monospace" font-size="9" fill="#94a3b8"> auto_approve_plan INTEGER</text>
|
||
<text x="18" y="242" font-family="monospace" font-size="9" fill="#94a3b8"> auto_approve_exec INTEGER</text>
|
||
<text x="18" y="255" font-family="monospace" font-size="9" fill="#fb923c">★ agent_rules TEXT</text>
|
||
<text x="18" y="268" font-family="monospace" font-size="9" fill="#94a3b8"> status TEXT</text>
|
||
<text x="18" y="281" font-family="monospace" font-size="9" fill="#94a3b8"> sort_order INTEGER</text>
|
||
<text x="18" y="294" font-family="monospace" font-size="9" fill="#94a3b8"> logo TEXT</text>
|
||
<text x="18" y="307" font-family="monospace" font-size="9" fill="#94a3b8"> last_sync_at TEXT</text>
|
||
<text x="18" y="320" font-family="monospace" font-size="9" fill="#94a3b8"> created_at TEXT</text>
|
||
<rect x="10" y="10" width="285" height="318" fill="none" stroke="#1e4a7a" stroke-width="1"/>
|
||
|
||
<!-- tasks x=340 y=10 w=300 h=370 -->
|
||
<rect x="340" y="10" width="300" height="24" fill="#2d1a4f" rx="3"/>
|
||
<text x="490" y="26" text-anchor="middle" font-family="monospace" font-size="11" font-weight="bold" fill="#a78bfa">tasks</text>
|
||
<rect x="340" y="34" width="300" height="346" fill="#1a0f2e"/>
|
||
<text x="348" y="47" font-family="monospace" font-size="9" fill="#a78bfa">🔑 id TEXT PK</text>
|
||
<text x="348" y="60" font-family="monospace" font-size="9" fill="#4ade80">🔗 project_id → projects.id</text>
|
||
<text x="348" y="73" font-family="monospace" font-size="9" fill="#4ade80">🔗 parent_id → tasks.id (可空)</text>
|
||
<text x="348" y="86" font-family="monospace" font-size="9" fill="#94a3b8"> depth INTEGER</text>
|
||
<text x="348" y="99" font-family="monospace" font-size="9" fill="#94a3b8"> title TEXT</text>
|
||
<text x="348" y="112" font-family="monospace" font-size="9" fill="#94a3b8"> complexity TEXT</text>
|
||
<text x="348" y="125" font-family="monospace" font-size="9" fill="#fb923c">★ schedule_status TEXT</text>
|
||
<text x="348" y="138" font-family="monospace" font-size="9" fill="#fb923c">★ work_type TEXT</text>
|
||
<text x="348" y="151" font-family="monospace" font-size="9" fill="#fb923c">★ work_plan TEXT (JSON)</text>
|
||
<text x="348" y="164" font-family="monospace" font-size="9" fill="#fb923c">★ work_history TEXT (JSON)</text>
|
||
<text x="348" y="177" font-family="monospace" font-size="9" fill="#94a3b8"> priority INTEGER</text>
|
||
<text x="348" y="190" font-family="monospace" font-size="9" fill="#94a3b8"> deps TEXT (JSON)</text>
|
||
<text x="348" y="203" font-family="monospace" font-size="9" fill="#fb923c">★ scope TEXT</text>
|
||
<text x="348" y="216" font-family="monospace" font-size="9" fill="#fb923c">★ owned_files TEXT (JSON)</text>
|
||
<text x="348" y="229" font-family="monospace" font-size="9" fill="#fb923c">★ expected_output TEXT</text>
|
||
<text x="348" y="242" font-family="monospace" font-size="9" fill="#fb923c">★ claimed_at TEXT</text>
|
||
<text x="348" y="255" font-family="monospace" font-size="9" fill="#fb923c">★ version INTEGER</text>
|
||
<text x="348" y="268" font-family="monospace" font-size="9" fill="#fb923c">★ attachments TEXT (JSON)</text>
|
||
<text x="348" y="281" font-family="monospace" font-size="9" fill="#fb923c">★ source TEXT</text>
|
||
<text x="348" y="294" font-family="monospace" font-size="9" fill="#fb923c">★ created_by TEXT</text>
|
||
<text x="348" y="307" font-family="monospace" font-size="9" fill="#94a3b8"> result TEXT (JSON)</text>
|
||
<text x="348" y="320" font-family="monospace" font-size="9" fill="#94a3b8"> assignee TEXT</text>
|
||
<text x="348" y="333" font-family="monospace" font-size="9" fill="#94a3b8"> retry_baseline INTEGER</text>
|
||
<text x="348" y="346" font-family="monospace" font-size="9" fill="#94a3b8"> next_eligible_at TEXT</text>
|
||
<text x="348" y="359" font-family="monospace" font-size="9" fill="#94a3b8"> source_ref TEXT</text>
|
||
<text x="348" y="372" font-family="monospace" font-size="9" fill="#94a3b8"> created_at / updated_at TEXT</text>
|
||
<rect x="340" y="10" width="300" height="370" fill="none" stroke="#4c2d8f" stroke-width="1"/>
|
||
|
||
<!-- approvals x=685 y=10 w=265 h=123 -->
|
||
<rect x="685" y="10" width="265" height="24" fill="#3d2800" rx="3"/>
|
||
<text x="817.5" y="26" text-anchor="middle" font-family="monospace" font-size="11" font-weight="bold" fill="#f59e0b">approvals</text>
|
||
<rect x="685" y="34" width="265" height="99" fill="#231800"/>
|
||
<text x="693" y="47" font-family="monospace" font-size="9" fill="#f59e0b">🔑 id TEXT PK</text>
|
||
<text x="693" y="60" font-family="monospace" font-size="9" fill="#4ade80">🔗 task_id → tasks.id</text>
|
||
<text x="693" y="73" font-family="monospace" font-size="9" fill="#94a3b8"> gate TEXT (plan|review)</text>
|
||
<text x="693" y="86" font-family="monospace" font-size="9" fill="#94a3b8"> action TEXT (accept|reject)</text>
|
||
<text x="693" y="99" font-family="monospace" font-size="9" fill="#94a3b8"> actor TEXT</text>
|
||
<text x="693" y="112" font-family="monospace" font-size="9" fill="#94a3b8"> reason TEXT</text>
|
||
<text x="693" y="125" font-family="monospace" font-size="9" fill="#94a3b8"> at TEXT</text>
|
||
<rect x="685" y="10" width="265" height="123" fill="none" stroke="#6b4400" stroke-width="1"/>
|
||
|
||
<!-- runs x=685 y=155 w=265 h=227 -->
|
||
<rect x="685" y="155" width="265" height="24" fill="#0d3320" rx="3"/>
|
||
<text x="817.5" y="171" text-anchor="middle" font-family="monospace" font-size="11" font-weight="bold" fill="#4ade80">runs</text>
|
||
<rect x="685" y="179" width="265" height="203" fill="#061a10"/>
|
||
<text x="693" y="192" font-family="monospace" font-size="9" fill="#4ade80">🔑 id TEXT PK</text>
|
||
<text x="693" y="205" font-family="monospace" font-size="9" fill="#4ade80">🔗 task_id → tasks.id</text>
|
||
<text x="693" y="218" font-family="monospace" font-size="9" fill="#94a3b8"> kind TEXT (plan|code|review|pr|conflict|research)</text>
|
||
<text x="693" y="231" font-family="monospace" font-size="9" fill="#fb923c">★ trace_id TEXT</text>
|
||
<text x="693" y="244" font-family="monospace" font-size="9" fill="#fb923c">★ usage TEXT (JSON)</text>
|
||
<text x="693" y="257" font-family="monospace" font-size="9" fill="#fb923c">★ cost_usd REAL</text>
|
||
<text x="693" y="270" font-family="monospace" font-size="9" fill="#94a3b8"> worktree TEXT</text>
|
||
<text x="693" y="283" font-family="monospace" font-size="9" fill="#94a3b8"> branch TEXT</text>
|
||
<text x="693" y="296" font-family="monospace" font-size="9" fill="#94a3b8"> status TEXT</text>
|
||
<text x="693" y="309" font-family="monospace" font-size="9" fill="#94a3b8"> started_at / ended_at TEXT</text>
|
||
<text x="693" y="322" font-family="monospace" font-size="9" fill="#94a3b8"> transcript_ref TEXT</text>
|
||
<text x="693" y="335" font-family="monospace" font-size="9" fill="#94a3b8"> claude_session_id TEXT</text>
|
||
<text x="693" y="348" font-family="monospace" font-size="9" fill="#94a3b8"> error TEXT</text>
|
||
<text x="693" y="361" font-family="monospace" font-size="9" fill="#94a3b8"> worker_pid INTEGER</text>
|
||
<text x="693" y="374" font-family="monospace" font-size="9" fill="#94a3b8"> last_seq INTEGER</text>
|
||
<rect x="685" y="155" width="265" height="227" fill="none" stroke="#0f5c33" stroke-width="1"/>
|
||
|
||
<!-- events x=10 y=350 w=285 h=110 -->
|
||
<rect x="10" y="350" width="285" height="24" fill="#3d1515" rx="3"/>
|
||
<text x="152.5" y="366" text-anchor="middle" font-family="monospace" font-size="11" font-weight="bold" fill="#f87171">events</text>
|
||
<rect x="10" y="374" width="285" height="86" fill="#210c0c"/>
|
||
<text x="18" y="387" font-family="monospace" font-size="9" fill="#f87171">🔑 id TEXT PK</text>
|
||
<text x="18" y="400" font-family="monospace" font-size="9" fill="#64748b">🔗 project_id (无 FK 约束)</text>
|
||
<text x="18" y="413" font-family="monospace" font-size="9" fill="#64748b">🔗 task_id (可空, 无 FK 约束)</text>
|
||
<text x="18" y="426" font-family="monospace" font-size="9" fill="#94a3b8"> type TEXT</text>
|
||
<text x="18" y="439" font-family="monospace" font-size="9" fill="#94a3b8"> payload TEXT (JSON)</text>
|
||
<text x="18" y="452" font-family="monospace" font-size="9" fill="#94a3b8"> at TEXT</text>
|
||
<rect x="10" y="350" width="285" height="110" fill="none" stroke="#7f1d1d" stroke-width="1"/>
|
||
|
||
<!-- ─── 关系连线(crow's foot) ─── -->
|
||
<path d="M 295 169 L 317.5 169 L 317.5 57 L 340 57" stroke="#64748b" stroke-width="1.5" fill="none" marker-start="url(#er-one)" marker-end="url(#er-many-r)"/>
|
||
<text x="317.5" y="165" text-anchor="middle" font-family="sans-serif" font-size="8" fill="#64748b">1:N</text>
|
||
<path d="M 640 97 L 662.5 97 L 662.5 57 L 685 57" stroke="#64748b" stroke-width="1.5" fill="none" marker-start="url(#er-one)" marker-end="url(#er-many-r)"/>
|
||
<text x="662.5" y="53" text-anchor="middle" font-family="sans-serif" font-size="8" fill="#64748b">1:N</text>
|
||
<path d="M 640 142 L 662.5 142 L 662.5 202 L 685 202" stroke="#64748b" stroke-width="1.5" fill="none" marker-start="url(#er-one)" marker-end="url(#er-many-r)"/>
|
||
<text x="662.5" y="138" text-anchor="middle" font-family="sans-serif" font-size="8" fill="#64748b">1:N</text>
|
||
<path d="M 152.5 328 L 152.5 350" stroke="#64748b" stroke-width="1.5" fill="none" marker-start="url(#er-one)" marker-end="url(#er-many)"/>
|
||
<text x="168.5" y="342" font-family="sans-serif" font-size="8" fill="#64748b">1:N</text>
|
||
<path d="M 490 380 L 490 405 L 295 405" stroke="#64748b" stroke-width="1.5" fill="none" stroke-dasharray="4,3" marker-end="url(#er-many)"/>
|
||
<text x="392.5" y="401" text-anchor="middle" font-family="sans-serif" font-size="8" fill="#64748b">0..1:N (可空)</text>
|
||
<path d="M 340 70 L 318 70 L 318 40 L 340 40" stroke="#64748b" stroke-width="1.5" fill="none" stroke-dasharray="3,2" marker-end="url(#er-many-r)"/>
|
||
<text x="310" y="65" text-anchor="middle" font-family="sans-serif" font-size="8" fill="#64748b">parent</text>
|
||
|
||
<!-- ─── 图例 ─── -->
|
||
<rect x="685" y="400" width="265" height="96" fill="#080d18" rx="3" stroke="#2a3448"/>
|
||
<rect x="685" y="400" width="265" height="18" fill="#131c30" rx="3"/>
|
||
<text x="817.5" y="413" text-anchor="middle" font-family="sans-serif" font-size="9" font-weight="bold" fill="#94a3b8">图 例</text>
|
||
<text x="695" y="434" font-family="sans-serif" font-size="9" fill="#fb923c">★ = 本方案新增列</text>
|
||
<text x="695" y="448" font-family="sans-serif" font-size="9" fill="#4ade80">🔗 = 外键引用 🔑 = 主键</text>
|
||
<line x1="695" y1="460" x2="729" y2="460" stroke="#64748b" stroke-width="1.5"/>
|
||
<text x="735" y="463" font-family="sans-serif" font-size="9" fill="#64748b">有 FK 约束</text>
|
||
<line x1="695" y1="476" x2="729" y2="476" stroke="#64748b" stroke-width="1.5" stroke-dasharray="4,3"/>
|
||
<text x="735" y="479" font-family="sans-serif" font-size="9" fill="#64748b">弱引用(无 FK)</text>
|
||
|
||
<!-- ─── 关键字段说明表 ─── -->
|
||
<rect x="10" y="518" width="940" height="344" fill="#080d18" rx="3" stroke="#2a3448"/>
|
||
<rect x="10" y="518" width="940" height="20" fill="#131c30" rx="3"/>
|
||
<text x="480" y="532" text-anchor="middle" font-family="sans-serif" font-size="9.5" font-weight="bold" fill="#94a3b8">关键字段说明</text>
|
||
<rect x="10" y="538" width="940" height="16" fill="#0f1a2e"/>
|
||
<text x="20" y="550" font-family="monospace" font-size="8.5" font-weight="bold" fill="#64748b">字段</text>
|
||
<text x="175" y="550" font-family="monospace" font-size="8.5" font-weight="bold" fill="#64748b">所属表</text>
|
||
<text x="280" y="550" font-family="monospace" font-size="8.5" font-weight="bold" fill="#64748b">含义</text>
|
||
<line x1="168" y1="538" x2="168" y2="862" stroke="#2a3448" stroke-width="0.5"/>
|
||
<line x1="273" y1="538" x2="273" y2="862" stroke="#2a3448" stroke-width="0.5"/>
|
||
<text x="20" y="566" font-family="monospace" font-size="8.5" fill="#a78bfa">schedule_status</text>
|
||
<text x="175" y="566" font-family="monospace" font-size="8.5" fill="#94a3b8">tasks</text>
|
||
<text x="280" y="566" font-family="monospace" font-size="8.5" fill="#94a3b8">调度状态机,取代旧 status:init|ready|running|waiting|blocked|paused|failed|stuck|done|cancelled</text>
|
||
<line x1="10" y1="569" x2="950" y2="569" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="581" font-family="monospace" font-size="8.5" fill="#a78bfa">work_type</text>
|
||
<text x="175" y="581" font-family="monospace" font-size="8.5" fill="#94a3b8">tasks</text>
|
||
<text x="280" y="581" font-family="monospace" font-size="8.5" fill="#94a3b8">当前工作类型,决定 dispatch 哪个 agent:plan|code|review|pr|conflict|research|null</text>
|
||
<line x1="10" y1="584" x2="950" y2="584" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="596" font-family="monospace" font-size="8.5" fill="#a78bfa">work_plan</text>
|
||
<text x="175" y="596" font-family="monospace" font-size="8.5" fill="#94a3b8">tasks</text>
|
||
<text x="280" y="596" font-family="monospace" font-size="8.5" fill="#94a3b8">剩余步骤队列 [{type, spec, owned_files, expected_output}],运行时动态可改</text>
|
||
<line x1="10" y1="599" x2="950" y2="599" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="611" font-family="monospace" font-size="8.5" fill="#a78bfa">work_history</text>
|
||
<text x="175" y="611" font-family="monospace" font-size="8.5" fill="#94a3b8">tasks</text>
|
||
<text x="280" y="611" font-family="monospace" font-size="8.5" fill="#94a3b8">已完成步骤记录,审计 + 重试上下文</text>
|
||
<line x1="10" y1="614" x2="950" y2="614" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="626" font-family="monospace" font-size="8.5" fill="#a78bfa">claimed_at</text>
|
||
<text x="175" y="626" font-family="monospace" font-size="8.5" fill="#94a3b8">tasks</text>
|
||
<text x="280" y="626" font-family="monospace" font-size="8.5" fill="#94a3b8">CAS 锁,NULL=可领取、非 NULL=已锁定,防 Orchestrator 双重领取</text>
|
||
<line x1="10" y1="629" x2="950" y2="629" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="641" font-family="monospace" font-size="8.5" fill="#a78bfa">version</text>
|
||
<text x="175" y="641" font-family="monospace" font-size="8.5" fill="#94a3b8">tasks</text>
|
||
<text x="280" y="641" font-family="monospace" font-size="8.5" fill="#94a3b8">乐观锁版本号,执行期任务修改检测(D6)</text>
|
||
<line x1="10" y1="644" x2="950" y2="644" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="656" font-family="monospace" font-size="8.5" fill="#a78bfa">scope</text>
|
||
<text x="175" y="656" font-family="monospace" font-size="8.5" fill="#94a3b8">tasks</text>
|
||
<text x="280" y="656" font-family="monospace" font-size="8.5" fill="#94a3b8">改动范围 file/module/service/cross-service,喂调度 couplingPenalty</text>
|
||
<line x1="10" y1="659" x2="950" y2="659" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="671" font-family="monospace" font-size="8.5" fill="#a78bfa">owned_files</text>
|
||
<text x="175" y="671" font-family="monospace" font-size="8.5" fill="#94a3b8">tasks</text>
|
||
<text x="280" y="671" font-family="monospace" font-size="8.5" fill="#94a3b8">任务声明的主要修改文件 JSON,喂 couplingPenalty 文件重叠 + diff 越界闸</text>
|
||
<line x1="10" y1="674" x2="950" y2="674" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="686" font-family="monospace" font-size="8.5" fill="#a78bfa">expected_output</text>
|
||
<text x="175" y="686" font-family="monospace" font-size="8.5" fill="#94a3b8">tasks</text>
|
||
<text x="280" y="686" font-family="monospace" font-size="8.5" fill="#94a3b8">任务级一句话可验证完成标准(per-step 的在 work_plan 内)</text>
|
||
<line x1="10" y1="689" x2="950" y2="689" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="701" font-family="monospace" font-size="8.5" fill="#60a5fa">agent_rules ★</text>
|
||
<text x="175" y="701" font-family="monospace" font-size="8.5" fill="#94a3b8">projects</text>
|
||
<text x="280" y="701" font-family="monospace" font-size="8.5" fill="#94a3b8">项目级 agent 专用规范文本,注入全部 agent prompt 头部(L2 记忆)</text>
|
||
<line x1="10" y1="704" x2="950" y2="704" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="716" font-family="monospace" font-size="8.5" fill="#60a5fa">diff_max_files</text>
|
||
<text x="175" y="716" font-family="monospace" font-size="8.5" fill="#94a3b8">projects</text>
|
||
<text x="280" y="716" font-family="monospace" font-size="8.5" fill="#94a3b8">diff 体量闸阈值(默认 100),执行后改动文件数超限即拦截</text>
|
||
<line x1="10" y1="719" x2="950" y2="719" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="731" font-family="monospace" font-size="8.5" fill="#60a5fa">checks</text>
|
||
<text x="175" y="731" font-family="monospace" font-size="8.5" fill="#94a3b8">projects</text>
|
||
<text x="280" y="731" font-family="monospace" font-size="8.5" fill="#94a3b8">分项静态检查命令 JSON {lint, typecheck, build},任一非 0 硬失败</text>
|
||
<line x1="10" y1="734" x2="950" y2="734" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="746" font-family="monospace" font-size="8.5" fill="#60a5fa">budget_usd / budget_period</text>
|
||
<text x="175" y="746" font-family="monospace" font-size="8.5" fill="#94a3b8">projects</text>
|
||
<text x="280" y="746" font-family="monospace" font-size="8.5" fill="#94a3b8">项目级预算上限(当期 day|month),超额自动 paused 停领(⑥ 护栏)</text>
|
||
<line x1="10" y1="749" x2="950" y2="749" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="761" font-family="monospace" font-size="8.5" fill="#4ade80">kind</text>
|
||
<text x="175" y="761" font-family="monospace" font-size="8.5" fill="#94a3b8">runs</text>
|
||
<text x="280" y="761" font-family="monospace" font-size="8.5" fill="#94a3b8">run 类型,对齐 work_type:plan|code|review|pr|conflict|research + interactive(人工接管,非 work_type)</text>
|
||
<line x1="10" y1="764" x2="950" y2="764" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="776" font-family="monospace" font-size="8.5" fill="#4ade80">trace_id</text>
|
||
<text x="175" y="776" font-family="monospace" font-size="8.5" fill="#94a3b8">runs</text>
|
||
<text x="280" y="776" font-family="monospace" font-size="8.5" fill="#94a3b8">跨 run 追踪 ID,关联同一任务的多次运行</text>
|
||
<line x1="10" y1="779" x2="950" y2="779" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="791" font-family="monospace" font-size="8.5" fill="#4ade80">usage / cost_usd</text>
|
||
<text x="175" y="791" font-family="monospace" font-size="8.5" fill="#94a3b8">runs</text>
|
||
<text x="280" y="791" font-family="monospace" font-size="8.5" fill="#94a3b8">每次模型调用 token 用量 + 折算成本(模型×token 价目),汇总到 task / project</text>
|
||
<line x1="10" y1="794" x2="950" y2="794" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="806" font-family="monospace" font-size="8.5" fill="#4ade80">last_seq</text>
|
||
<text x="175" y="806" font-family="monospace" font-size="8.5" fill="#94a3b8">runs</text>
|
||
<text x="280" y="806" font-family="monospace" font-size="8.5" fill="#94a3b8">outbox.ndjson 已消费行号,daemon ingest 幂等重试游标</text>
|
||
<line x1="10" y1="809" x2="950" y2="809" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="821" font-family="monospace" font-size="8.5" fill="#a78bfa">attachments</text>
|
||
<text x="175" y="821" font-family="monospace" font-size="8.5" fill="#94a3b8">tasks</text>
|
||
<text x="280" y="821" font-family="monospace" font-size="8.5" fill="#94a3b8">提交附件 [{name,type,path}](图片/文件),喂 agent 多模态 / Read</text>
|
||
<line x1="10" y1="824" x2="950" y2="824" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="836" font-family="monospace" font-size="8.5" fill="#a78bfa">source / created_by</text>
|
||
<text x="175" y="836" font-family="monospace" font-size="8.5" fill="#94a3b8">tasks</text>
|
||
<text x="280" y="836" font-family="monospace" font-size="8.5" fill="#94a3b8">任务来源 human|ai|sync|github + 创建者,AI 提的默认进待确认闸</text>
|
||
<line x1="10" y1="839" x2="950" y2="839" stroke="#1a2440" stroke-width="0.5"/>
|
||
<text x="20" y="851" font-family="monospace" font-size="8.5" fill="#f87171">payload</text>
|
||
<text x="175" y="851" font-family="monospace" font-size="8.5" fill="#94a3b8">events</text>
|
||
<text x="280" y="851" font-family="monospace" font-size="8.5" fill="#94a3b8">事件附加数据 JSON,审计日志 + WebSocket 实时推送源</text>
|
||
|
||
</svg>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">核心表说明</div>
|
||
<table class="tbl">
|
||
<thead><tr><th>表</th><th>行数量级</th><th>作用</th><th>关键约束</th></tr></thead>
|
||
<tbody>
|
||
<tr><td><code>projects</code></td><td>少量(<100)</td><td>项目配置、自动化策略、并发/超时参数</td><td>repo_path UNIQUE</td></tr>
|
||
<tr><td><code>tasks</code></td><td>中量(<10k)</td><td>任务主表,三类任务(hard/medium/easy)均在此表,以 complexity+status+run.kind 区分</td><td>FK project_id, parent_id(自引用)</td></tr>
|
||
<tr><td><code>approvals</code></td><td>中量</td><td>审批闸记录(plan/spec/exec),reject 必须带 reason</td><td>FK task_id</td></tr>
|
||
<tr><td><code>runs</code></td><td>中量(<50k)</td><td>每次 agent 运行(planner/executor/reviewer/security/conflict),含 transcript 路径和 ingest 游标</td><td>FK task_id</td></tr>
|
||
<tr><td><code>events</code></td><td>大量(仅追加)</td><td>审计日志 + WebSocket 实时推送源,无 FK 约束(高性能追加)</td><td>INDEX (project_id, at)</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">★ 本方案新增列</div>
|
||
<div class="grid-2">
|
||
<div class="box amber">
|
||
<h4>projects.agent_rules (L2)</h4>
|
||
<div class="row">TEXT,项目级 agent 专用规范正文(markdown)</div>
|
||
<div class="row">daemon 在 claimOne 写 job.json 时注入全部 4 类 agent 的 prompt 头部</div>
|
||
<div class="row">文件:schema.sql + types.ts + mappers.ts + store.ts</div>
|
||
</div>
|
||
<div class="box cyan">
|
||
<h4>非表字段(JobSpec.context)</h4>
|
||
<div class="row">protocol.ts 新增可选字段,由 daemon 组装、随 job.json 下发</div>
|
||
<div class="row">内容:parentPlan(父任务拆解意图)+ siblings(兄弟任务状态摘要)</div>
|
||
<div class="row">worker 读 job.json 即可,无需访问 DB</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════ ④ Agent 记忆注入 ══════════ -->
|
||
<a class="chapter-anchor" id="d-memory"></a>
|
||
<div class="chapter-hdr"><span>④</span> Agent 记忆注入<span class="phase mix">P1 核心(L1) · P2(L2–L4)</span></div>
|
||
|
||
<div class="card" style="border-left:3px solid var(--red)">
|
||
<div class="card-title">当前缺口</div>
|
||
<div class="grid-2">
|
||
<div>
|
||
<div class="row"><code>buildPrompt</code> executor:title/id + operations/spec/plan + 执行约束。<span style="color:var(--red)">无 lastRunError、无项目规范。</span></div>
|
||
<div class="row"><code>buildPlannerPrompt</code> planner:title/id + 草稿 + depth。<span style="color:var(--red)">无 lastRunError、无项目规范。</span></div>
|
||
<div class="row"><code>buildReviewPrompt</code> reviewer:任务要求 + 执行者自述 + diff 命令。<span style="color:var(--red)">无项目规范。</span></div>
|
||
</div>
|
||
<div>
|
||
<div class="row"><code>cc.ts:80</code> <code>settingSources:['project']</code> → SDK 自动读项目 CLAUDE.md(现已生效 ✓)</div>
|
||
<div class="row"><code>task.lastRunError</code> 字段已存在于 Task 类型,但<span style="color:var(--red)">从未注入任何 prompt</span></div>
|
||
<div class="row"><code>orchestrator</code> claim 时拿到的 task 对象<span style="color:var(--red)">不带 last_run_error</span>(仅审核 UI 子查询)</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card" style="border-left:3px solid var(--cyan)">
|
||
<div class="card-title">数据流铁律 → 决定方案形态</div>
|
||
<div class="row">worker 进程<strong>完全不碰 DB</strong>,只读 <code>runs/<runId>/job.json</code>(protocol.ts 铁律)</div>
|
||
<div class="row">所有来自 DB 的记忆,必须由 <strong>daemon 在 claimOne 写 job.json 时组装进去</strong></div>
|
||
<div class="row">JobSpec 已携带完整 task + project 对象 → lastRunError 和 agentRules 天然可随 job.json 下发</div>
|
||
<div class="row">L3 跨任务上下文 → 扩展 <code>JobSpec.context?</code> 可选字段</div>
|
||
</div>
|
||
|
||
<div class="grid-2" style="margin-bottom:20px">
|
||
<div class="box green">
|
||
<h4>L1 — 注入 lastRunError(ROI 最高)</h4>
|
||
<div class="row">现状:last_run_error 是 pendingApprovals 子查询,claim 时的 task 不带它</div>
|
||
<div class="row">改 store.ts:抽 <code>lastRunErrorOf(taskId)</code> 方法(复用现有子查询 SQL)</div>
|
||
<div class="row">改 orchestrator.ts:claimOne 写 job 前赋值 <code>task.lastRunError</code></div>
|
||
<div class="row">改 runner.ts:3 处 build*Prompt 插「## 上次失败原因(重试任务)」段</div>
|
||
<div class="row" style="color:var(--green)">5 行代码,零协议改动(字段已在 Task 上)</div>
|
||
</div>
|
||
<div class="box amber">
|
||
<h4>L2 — 项目级 agent 规范(存 DB projects 表)</h4>
|
||
<div class="row">schema.sql:projects 表加 <code>agent_rules TEXT</code> 列 + 幂等迁移</div>
|
||
<div class="row">types.ts/mappers.ts:Project 加 <code>agentRules?</code>、row ↔ 字段映射</div>
|
||
<div class="row">project 已随 job.json 下发 → worker 端 4 处 prompt 插「## 项目规范」头部</div>
|
||
<div class="row">API/Web 编辑入口后续补;本期先打通存储+注入</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid-2" style="margin-bottom:20px">
|
||
<div class="box violet">
|
||
<h4>L3 — 跨任务上下文</h4>
|
||
<div class="row">protocol.ts JobSpec 加 <code>context?: { parentPlan?: string; siblings?: {...}[] }</code></div>
|
||
<div class="row">store.ts:取 parent.plan + 同 parentId 兄弟摘要的方法</div>
|
||
<div class="row">orchestrator.ts claimOne:若 task.parentId,组装 job.context 再下发</div>
|
||
<div class="row">runner.ts buildPrompt:注入「## 拆解背景(父任务意图 + 兄弟状态)」</div>
|
||
<div class="row" style="color:var(--text3)">仅做 DB 读;不做 git 演化检索/RAG(留后续)</div>
|
||
</div>
|
||
<div class="box cyan">
|
||
<h4>L4 — 全局 agent 规范(maestro 自维护)</h4>
|
||
<div class="row"><strong>不读</strong> user 全局 ~/.claude/CLAUDE.md(避免个人习惯干扰受控执行)</div>
|
||
<div class="row"><code>settingSources:['project']</code> <strong>保持不变</strong></div>
|
||
<div class="row">maestro 维护 <code>~/.maestro/agent-global.md</code></div>
|
||
<div class="row">daemon 启动时读一次缓存,claimOne 随 job.json 下发,4 处 prompt 头部注入</div>
|
||
<div class="row" style="color:var(--text3)">文件不存在则跳过(可选,缺省空)</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">注入顺序(prompt 内记忆段排布)</div>
|
||
<div style="font-family:'IBM Plex Mono',monospace;font-size:12px;color:var(--text2);line-height:2">
|
||
<span style="color:var(--cyan)">[L4] ## 全局执行规范</span> →
|
||
<span style="color:var(--amber)">[L2] ## 项目规范(必须遵守)</span> →
|
||
<span style="color:var(--violet)">[L3] ## 拆解背景(父任务意图 + 兄弟状态)</span> →
|
||
<span style="color:var(--text)">## 任务内容</span> →
|
||
<span style="color:var(--red)">[L1] ## 上次失败原因(重试任务)</span> →
|
||
<span style="color:var(--text)">## 执行约束</span>
|
||
</div>
|
||
<div class="row" style="margin-top:8px;color:var(--text3)">项目 CLAUDE.md 由 SDK 经 settingSources:['project'] 自动进 <strong>system prompt</strong>,不在 user prompt 里重复。</div>
|
||
</div>
|
||
|
||
<a class="chapter-anchor" id="d-agentspec"></a>
|
||
<div class="chapter-hdr"><span>⑤</span> Agent 规格与注册表<span class="phase p1">Phase 1</span></div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">Agent 注册表模型(work_type → AgentSpec,可动态扩展)</div>
|
||
<p class="lead" style="font-size:12px;margin:0 0 12px;color:var(--text2)">每个 <code>work_type</code> 对应一条 <strong>AgentSpec</strong>。Orchestrator 派发 = <strong>注册表查表</strong>,而非写死 <code>switch</code>。新增一种 agent = 注册一条 AgentSpec,<strong>调度内核 / 状态机零改动</strong>。六个维度——流程 / 每步模型 / skills+subagents / prompt / 上下文 / 输出——全部声明在 AgentSpec 里。</p>
|
||
<pre><span class="kw">interface</span> <span class="tp">AgentSpec</span> {
|
||
workType: <span class="tp">WorkType</span>; <span class="cm">// 'plan'|'code'|'review'|'pr'|'conflict'|'research'|'writing'|…</span>
|
||
runKind: <span class="tp">string</span>; <span class="cm">// 落 runs.kind</span>
|
||
pickModel: (t, p) => <span class="tp">ModelId</span>; <span class="cm">// 每步档位(可按 complexity 分档)</span>
|
||
tools: <span class="tp">ToolPattern</span>[]; <span class="cm">// 工具白名单(∩ 全局允许超集;红线永不入集)</span>
|
||
skills?: <span class="tp">SkillId</span>[]; <span class="cm">// 注入的 skill(如 writing-plans / elements-of-style)</span>
|
||
subagents?: <span class="tp">AgentType</span>[]; <span class="cm">// 可调度的 subagent(如 Explore / general-purpose)</span>
|
||
needsWorktree: <span class="tp">boolean</span>; <span class="cm">// 写类 true(隔离改动)</span>
|
||
buildPrompt: (ctx: <span class="tp">AgentContext</span>) => <span class="tp">string</span>; <span class="cm">// 含 L1–L4 记忆</span>
|
||
parseOutput: (raw: <span class="tp">string</span>) => <span class="tp">StepResult</span>; <span class="cm">// 输出解析</span>
|
||
gate?: <span class="str">'plan'</span> | <span class="str">'review'</span> | <span class="kw">null</span>; <span class="cm">// 完成后的人工闸</span>
|
||
maxTurns: <span class="tp">number</span>;
|
||
preempt?: <span class="tp">boolean</span>; <span class="cm">// 抢占调度(如 conflict)</span>
|
||
}
|
||
|
||
<span class="cm">// 派发 = 查表,不是写死 switch</span>
|
||
<span class="kw">const</span> spec = <span class="tp">AGENT_REGISTRY</span>[task.work_type];
|
||
<span class="kw">await</span> spec.<span class="fn">run</span>(task); <span class="cm">// run 内:buildPrompt → cc(model,tools,skills) → parseOutput → gate</span></pre>
|
||
<p style="font-size:12px;margin:10px 0 4px;color:var(--text2)"><strong>AgentContext(上下文,daemon 在 claim 时组装进 job.json)</strong>:</p>
|
||
<pre>{
|
||
task, project, <span class="cm">// job.json 携带</span>
|
||
step, <span class="cm">// work_plan 当前步 {type, spec, owned_files, expected_output}</span>
|
||
memory: { global, <span class="cm">// L4 全局规范</span>
|
||
projectRules,<span class="cm">// L2 项目 agent_rules</span>
|
||
decompCtx, <span class="cm">// L3 父任务意图 + 兄弟状态</span>
|
||
lastError },<span class="cm">// L1 上次失败</span>
|
||
diff?, reviewerNotes? <span class="cm">// review / pr 用</span>
|
||
}</pre>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title"><span class="badge" style="color:#f0a060;border-color:#d6783b">plan</span> Planner Agent</div>
|
||
<table class="tbl">
|
||
<tr><th style="width:96px">维度</th><th>内容</th></tr>
|
||
<tr><td>触发</td><td><code>(ready, plan)</code>;Hard→decompose / Medium→spec;<strong>只读、不建 worktree</strong></td></tr>
|
||
<tr><td>流程</td><td>buildPlannerPrompt → runPlanner(CC 只读) → 解析输出 → <code>(waiting, plan)</code> 人审</td></tr>
|
||
<tr><td>模型/档</td><td>easy=<code>sonnet-4-6</code> · medium=<code>opus-4-8</code> · hard=<code>opus-4-8</code>(最强档)</td></tr>
|
||
<tr><td>skills/subagent</td><td>skill <code>superpowers:writing-plans</code>(拆解质量);subagent <code>Explore</code>(大范围只读检索代码演化)</td></tr>
|
||
<tr><td>tools</td><td>Read / Glob / Grep + Bash(git log/diff/show);maxTurns 90/60/40</td></tr>
|
||
<tr><td>prompt</td><td>title/id + spec/plan 草稿 + <code>depth</code> + 剩余可拆层(≤3) + 「产<strong>子任务表格(人审)</strong> + 末尾 fenced JSON」</td></tr>
|
||
<tr><td>上下文</td><td>L4 全局 → L2 项目规范 → L3 拆解背景 → 任务内容 → L1 上次失败;project CLAUDE.md 经 settingSources 入 system</td></tr>
|
||
<tr><td>输出</td><td>decompose: 表格 + <code>JSON{title,complexity,priority,deps,owned_files,expected_output}</code>;spec: 三段 md(改动/为什么/验收)</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title"><span class="badge violet">code</span> Executor Agent</div>
|
||
<table class="tbl">
|
||
<tr><th style="width:96px">维度</th><th>内容</th></tr>
|
||
<tr><td>触发</td><td><code>(ready, code)</code>;<strong>建 worktree</strong>(隔离写)</td></tr>
|
||
<tr><td>流程</td><td>syncMain → createWorktree → buildPrompt → runTask(CC, acceptEdits) → <strong>必须 commit</strong> → verify → checks 闸 → diff 体量闸</td></tr>
|
||
<tr><td>模型/档</td><td>easy=<code>sonnet-4-6</code> · medium/hard=<code>opus-4-8</code></td></tr>
|
||
<tr><td>skills/subagent</td><td>subagent <code>general-purpose</code> / <code>Explore</code>(并行只读调研、省主上下文);skill:项目可配领域 skill</td></tr>
|
||
<tr><td>tools</td><td>Read/Edit/Write/Glob/Grep + Bash(git <strong>无 push</strong>, test) + WebSearch;maxTurns 100</td></tr>
|
||
<tr><td>prompt</td><td>title/id + <code>operations??spec??plan</code> + owned_files 范围 + expected_output + 执行约束(禁越界 / 必 commit 含任务 ID)</td></tr>
|
||
<tr><td>上下文</td><td>L1–L4 + work_plan 当前 <code>step.spec / step.owned_files</code></td></tr>
|
||
<tr><td>输出</td><td>执行自述 + 含任务 ID 的 commit</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title"><span class="badge cyan">review</span> Reviewer Agent</div>
|
||
<table class="tbl">
|
||
<tr><th style="width:96px">维度</th><th>内容</th></tr>
|
||
<tr><td>触发</td><td><code>(ready, review)</code>;code 完成自动串联;<strong>独立 run、只读</strong>、可选</td></tr>
|
||
<tr><td>流程</td><td><code>Promise.all(reviewCode, reviewSecurity)</code> 并行 → 合并 verdict → reject 硬拦截(回 code) / pass → <code>(waiting, review)</code></td></tr>
|
||
<tr><td>模型/档</td><td>三档统一 <code>opus-4-8</code>(最强,<strong>不被 project.model 降档</strong>,保自审严格度)</td></tr>
|
||
<tr><td>skills/subagent</td><td>skill:code-review 清单 / 安全审计清单;<strong>无 subagent</strong>(聚焦审单一 diff)</td></tr>
|
||
<tr><td>tools</td><td>Read/Glob/Grep + Bash(git diff/show/log);maxTurns 40;不写、复用 code 的 worktree 只读</td></tr>
|
||
<tr><td>prompt</td><td>任务要求 + 执行者自述 + worktreeDiff + 「输出 <code>VERDICT</code> 模板(pass/reject + 理由)」</td></tr>
|
||
<tr><td>上下文</td><td>任务 spec + code 自述 + diff + L2 项目规范</td></tr>
|
||
<tr><td>输出</td><td>双 verdict + 意见;任一 reject → 硬退回 code(带意见)</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title"><span class="badge green">pr</span> PR Agent</div>
|
||
<table class="tbl">
|
||
<tr><th style="width:96px">维度</th><th>内容</th></tr>
|
||
<tr><td>触发</td><td>review 通过后 <code>(ready, pr)</code></td></tr>
|
||
<tr><td>流程</td><td>创建 PR(gh) → <code>--no-ff</code> 合并 → 冲突? → 建 conflict run : done</td></tr>
|
||
<tr><td>模型/档</td><td><code>opus-4-8</code>(PR 文案 / 合并决策;多为确定性操作)</td></tr>
|
||
<tr><td>skills/subagent</td><td>skill:PR 描述模板;无 subagent</td></tr>
|
||
<tr><td>tools</td><td>Bash(git, gh —<strong>受控</strong>:建 PR / 合并,<strong>禁强推</strong>);不建新 worktree</td></tr>
|
||
<tr><td>prompt</td><td>任务摘要 + commit 列表 + 关联 issue + PR 模板</td></tr>
|
||
<tr><td>上下文</td><td>任务 + 分支 + diff 摘要 + review 结论</td></tr>
|
||
<tr><td>输出</td><td>PR url + merge 结果(成功 → done / 冲突 → conflict run)</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title"><span class="badge red">conflict</span> Conflict Agent</div>
|
||
<table class="tbl">
|
||
<tr><th style="width:96px">维度</th><th>内容</th></tr>
|
||
<tr><td>触发</td><td>pr 合并冲突自动建(<strong>抢占调度</strong>,不排队等 score)</td></tr>
|
||
<tr><td>流程</td><td>补救 worktree <code>git merge</code> 制造冲突 → CC 解(保两边意图) → 跑测试 → commit → 双复审</td></tr>
|
||
<tr><td>模型/档</td><td><code>opus-4-8</code> 固定(最强,不随原任务复杂度降档)</td></tr>
|
||
<tr><td>skills/subagent</td><td>skill:解冲突纪律(禁丢任一方改动 / 解完验证);subagent <code>Explore</code>(查两边意图)</td></tr>
|
||
<tr><td>tools</td><td>Read/Edit/Write + Bash(<code>git merge</code> —<strong>仅此分支</strong>白名单, test)</td></tr>
|
||
<tr><td>prompt</td><td>专用解冲突模板(保留两边意图 + 禁丢改动 + 解完跑测试 + commit 沿用合并信息)</td></tr>
|
||
<tr><td>上下文</td><td>两分支 diff + 冲突文件 + 原任务意图 + L1 上次失败</td></tr>
|
||
<tr><td>输出</td><td>解冲突自述(解了哪些文件 / 取舍 / 测试结果)+ commit</td></tr>
|
||
</table>
|
||
<div class="note" style="margin-top:8px;font-size:11px">另有 <code>research</code>(纯只读调研,不写、无 gate)与 <code>interactive</code>(人工接管,见「交互平面」专题)两个 work_type,规格同样登记在 AGENT_REGISTRY。</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">动态新增 Agent(写作 / 视频发布 示例)</div>
|
||
<p class="lead" style="font-size:12px;margin:0 0 10px;color:var(--text2)"><strong>三步加一个 agent</strong>:① <code>work_type</code> / <code>runs.kind</code> 加枚举值(TEXT 兼容、无需改表结构)→ ② 写一条 AgentSpec 注册进 <code>AGENT_REGISTRY</code> → ③(可选)模型档位表 + 工具允许超集补该角色。<strong>调度器 / 状态机零改动</strong>——一旦某任务的 <code>work_plan</code> 含该 work_type,Orchestrator 自动按注册表派发。</p>
|
||
<pre><span class="cm">// 示例 1:写作 agent(后期加内容生产能力)</span>
|
||
<span class="tp">AGENT_REGISTRY</span>[<span class="str">'writing'</span>] = {
|
||
workType:<span class="str">'writing'</span>, runKind:<span class="str">'writing'</span>,
|
||
pickModel: () => <span class="str">'claude-opus-4-8'</span>,
|
||
tools:[<span class="str">'Read'</span>,<span class="str">'Write'</span>,<span class="str">'WebSearch'</span>], needsWorktree:<span class="kw">true</span>,
|
||
skills:[<span class="str">'elements-of-style:writing-clearly-and-concisely'</span>],
|
||
subagents:[<span class="str">'Explore'</span>], <span class="cm">// 查资料</span>
|
||
buildPrompt: ctx => 写作 brief + <span class="cm">L2 项目文风规范</span> + 素材,
|
||
gate:<span class="str">'review'</span>, <span class="cm">// 产出走人审</span>
|
||
maxTurns:60,
|
||
};
|
||
|
||
<span class="cm">// 示例 2:自动发视频 agent(对外副作用,须显式授权)</span>
|
||
<span class="tp">AGENT_REGISTRY</span>[<span class="str">'video-publish'</span>] = {
|
||
workType:<span class="str">'video-publish'</span>, runKind:<span class="str">'video-publish'</span>,
|
||
pickModel: () => <span class="str">'claude-opus-4-8'</span>,
|
||
tools:[<span class="str">'Read'</span>,<span class="str">'Bash(ffmpeg:*)'</span>,<span class="str">'Bash(youtube-upload:*)'</span>], <span class="cm">// 受控发布工具</span>
|
||
needsWorktree:<span class="kw">false</span>,
|
||
buildPrompt: ctx => 视频元数据 + 平台 + 发布策略,
|
||
gate:<span class="str">'review'</span>, <span class="cm">// 对外发布强制人审闸</span>
|
||
maxTurns:30,
|
||
};</pre>
|
||
<div class="note" style="margin-top:8px">🛡 <strong>红线护栏</strong>:对外产生副作用的 agent(发布 / 部署 / push)默认强制 <code>gate:'review'</code> 人工闸,且工具受<strong>允许超集</strong>约束——「未审不发」是硬约束,新 agent 不能绕过。纯生产/只读 agent(writing 草稿、research)可配 <code>gate:null</code> 自动放行。</div>
|
||
</div>
|
||
|
||
|
||
<a class="chapter-anchor" id="d-ops"></a>
|
||
<div class="chapter-hdr"><span>⑥</span> 运维与护栏(Ops & Guardrails)<span class="phase p2">Phase 2</span></div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">成本与预算治理(精确到项目 · 按 模型 × token 计费)</div>
|
||
<p class="lead" style="font-size:12px;margin:0 0 10px;color:var(--text2)">自治系统自动 spawn 付费 agent,<strong>必须有成本闸防失控烧钱</strong>。每个 run 从 Agent SDK 抓 token 用量 → 按<strong>每模型</strong>价目算成本 → 落库 → 汇总到 task / project → <strong>项目级预算闸</strong>超额自动停领。</p>
|
||
<pre><span class="cm">// 1. 每次模型调用的用量落 runs.usage(cc.ts 从 SDK message.usage 抓)</span>
|
||
runs.usage = [{ model, input_tokens, output_tokens, cache_read, cache_write }, …]
|
||
|
||
<span class="cm">// 2. 价目表($/Mtok,各模型 in/out/cache 单价不同)</span>
|
||
<span class="kw">const</span> <span class="tp">MODEL_PRICES</span> = {
|
||
<span class="str">'claude-opus-4-8'</span>: { in:15, out:75, cacheRead:1.5, cacheWrite:18.75 },
|
||
<span class="str">'claude-sonnet-4-6'</span>: { in:3, out:15, cacheRead:0.3, cacheWrite:3.75 },
|
||
<span class="str">'claude-haiku-4-5'</span>: { in:1, out:5, cacheRead:0.1, cacheWrite:1.25 },
|
||
};
|
||
<span class="cm">// 3. run 成本 = Σ 每次调用(按该次的 model 单价)</span>
|
||
cost_usd = Σ ( in_tok·P.in + out_tok·P.out + cache_read·P.cacheRead + cache_write·P.cacheWrite ) / 1e6
|
||
<span class="cm">// 4. 汇总:task.cost = Σ runs.cost_usd ; project 当期 spend = Σ task.cost(按 budget_period)</span>
|
||
|
||
<span class="cm">// 5. 项目级预算闸(Orchestrator claim 前)</span>
|
||
<span class="kw">if</span> (project.budget_usd && periodSpend(project, project.budget_period) >= project.budget_usd) {
|
||
pauseProject(project); <span class="cm">// 新任务不再领取;在途 run 跑完不打断;通知人工</span>
|
||
<span class="kw">continue</span>;
|
||
}</pre>
|
||
<table class="tbl" style="margin-top:10px">
|
||
<tr><th>维度</th><th>设计</th></tr>
|
||
<tr><td>计费粒度</td><td>per 模型调用 → per run(<code>runs.cost_usd</code>)→ per task → <strong>per project(当期)</strong></td></tr>
|
||
<tr><td>预算闸</td><td><code>projects.budget_usd</code> + <code>budget_period(day|month)</code>;超额 → project 自动 <code>paused</code> 停领,可单独配全局日上限做二级保险</td></tr>
|
||
<tr><td>消费端</td><td><code>/api/usage</code>(成本明细:项目/模型/时段)与 <code>/api/metrics</code> 由此填充,不再是空端点</td></tr>
|
||
<tr><td>价目维护</td><td>价目表为代码常量,模型变更/调价改一处;缓存命中(cacheRead)显著降本,纳入计算</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">限流与全局并发背压</div>
|
||
<table class="tbl">
|
||
<tr><th style="width:120px">机制</th><th>设计</th></tr>
|
||
<tr><td>两层并发闸</td><td><code>project.concurrency</code>(单项目)<strong>+ 新增 <code>MAESTRO_MAX_INFLIGHT</code>(全局在途上限)</strong>;claim 受 <code>min(项目闸, 全局闸)</code> —— 防多项目×并发把 API 打爆</td></tr>
|
||
<tr><td>API 429 / overloaded</td><td>cc.ts 捕获 Anthropic 限流 → run 标 <code>throttled</code>(<strong>不计任务失败</strong>、不消耗 max_retries)→ 退避后重排;与 <code>failAttempt</code> 严格区分</td></tr>
|
||
<tr><td>spawn 速率</td><td>令牌桶限制每秒新起 worker 数,避免 tick 一次性雷鸣群(thundering herd)冲击 API + 磁盘</td></tr>
|
||
<tr><td>与成本闸协同</td><td>budget 超 → <strong>停领</strong>(不再 claim);429 → <strong>慢领</strong>(退避);两者正交叠加</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">资源生命周期与 GC</div>
|
||
<table class="tbl">
|
||
<tr><th style="width:120px">资源</th><th>回收策略</th></tr>
|
||
<tr><td>worktree</td><td>task done/cancelled/reap 后删 worktree;<strong>已合并</strong>的临时分支删除,<strong>未合并</strong>分支保留待人查</td></tr>
|
||
<tr><td><code>runs/<runId>/</code></td><td>job.json / outbox / heartbeat / transcript 保留 <code>retention_days</code>(可配)→ 过期归档或删;transcript 大文件单独策略</td></tr>
|
||
<tr><td><code>events</code> 表</td><td>append-only → 定期 <strong>rollup</strong>(旧事件按 task 聚合)/ 归档到冷表,防无限增长拖慢看板查询</td></tr>
|
||
<tr><td>启动 GC</td><td>daemon 启动扫<strong>孤儿 worktree</strong>(无对应活跃 run)→ 清理;扫残留 runs 目录对账</td></tr>
|
||
<tr><td>SQLite</td><td>周期 WAL checkpoint + 低峰 VACUUM,控制 DB 文件膨胀</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">可观测性(metrics / health / trace)</div>
|
||
<table class="tbl">
|
||
<tr><th style="width:120px">面</th><th>内容</th></tr>
|
||
<tr><td><code>/api/metrics</code></td><td>队列深度(各 <code>schedule_status</code> 计数)· 吞吐(done/h)· agent 时延 + turn 数分布 · review 通过/reject 率 · <strong>成本(per project / model)</strong></td></tr>
|
||
<tr><td><code>/api/health</code></td><td>daemon 存活 + DB 可写 + 在途 worker 数 + 最近一次 tick 时间(探活/告警用)</td></tr>
|
||
<tr><td>trace_id</td><td><code>JobSpec.traceId</code> 贯穿 outbox 每条 → 串起一次任务跨多 run(plan→code→review→pr)的全链;<strong>从 Phase 2 提前为一等公民</strong>(成本/排障都依赖它聚合)</td></tr>
|
||
<tr><td>审计源</td><td><code>events</code> 表已是审计 + WS 推送源,metrics 在其上做聚合,不另起存储</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">依赖图完整性(环检测 / 死锁)</div>
|
||
<table class="tbl">
|
||
<tr><th style="width:120px">时机</th><th>设计</th></tr>
|
||
<tr><td>decompose 落库时</td><td>planner 产出 deps → 建边前跑<strong>环检测</strong>(拓扑排序 / DFS);发现环 → 拒绝该 decompose 结果 + planner 重试(带「deps 成环,请重排」意见)</td></tr>
|
||
<tr><td>运行时死锁</td><td>若存在非终态 task 但<strong>可运行集为空</strong>(全部 blocked 互等)→ 标记死锁 + 通知人工,不空转</td></tr>
|
||
<tr><td>防御(prompt)</td><td>planner prompt 明确「deps 用子任务序号引用、禁自环 / 互环」,从源头降低成环概率</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<a class="chapter-anchor" id="d-security"></a>
|
||
<div class="chapter-hdr"><span>⑦</span> 安全与沙箱(Security & Sandbox)<span class="phase p2">Phase 2</span></div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">安全模型(沙箱 · 凭证 · 脱敏 · 对外授权)</div>
|
||
<table class="tbl">
|
||
<tr><th style="width:130px">面</th><th>设计</th></tr>
|
||
<tr><td>沙箱</td><td><code>MAESTRO_SANDBOX</code>:worker 进程 OS 级<strong>写围栏</strong>(只允许写自己的 worktree + <code>runs/<runId>/</code>)+ ulimits(CPU/内存/fd);配置落 <code>sandbox.sb</code>。默认 off,<strong>生产建议 on</strong></td></tr>
|
||
<tr><td>工具红线(全局)</td><td>任何 agent 不可逾越:<strong>禁 git push / 禁部署 / 禁对外发布</strong>(除非显式授权 agent + 人审闸);实际工具 = <code>AgentSpec.tools ∩ 全局允许超集</code>,红线永不入超集</td></tr>
|
||
<tr><td>凭证</td><td>git / API 凭证<strong>不写进 worktree、不入 job.json</strong>;由 daemon 以最小权限注入 worker env,按 run 生命周期回收;解冲突/PR 的 git 操作受控、不外推</td></tr>
|
||
<tr><td>secret 脱敏</td><td><code>outbox</code> / <code>transcript</code> / <code>events</code> 落盘前过滤已知 secret 模式(token / key / 凭证),防泄漏到审计层与看板</td></tr>
|
||
<tr><td>对外副作用授权</td><td>产生外部副作用的 agent(<code>video-publish</code> / deploy)强制 <code>gate:'review'</code> 人审闸 + <strong>项目级显式开关</strong>,未授权不执行——「未审不对外」是硬约束</td></tr>
|
||
</table>
|
||
<div class="note" style="margin-top:10px">与 ⑤ Agent 规格的工具白名单、交互平面的「有界可配工具」一脉相承:<strong>能力按需授予、红线全局兜底、对外副作用强制人审</strong>。</div>
|
||
</div>
|
||
|
||
|
||
<a class="chapter-anchor" id="d-api"></a>
|
||
<div class="chapter-hdr"><span>⑧</span> 前端 API 契约(REST + WS)<span class="phase mix">P1 现有 · P2 ★新路由</span></div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">统一约定</div>
|
||
<table class="tbl">
|
||
<tr><th style="width:130px">项</th><th>约定</th></tr>
|
||
<tr><td>Base URL</td><td><code>http://127.0.0.1:4517</code>(<code>MAESTRO_PORT</code>);仅本地监听</td></tr>
|
||
<tr><td>Content-Type</td><td><code>application/json</code>;附件上传用 <code>multipart/form-data</code>;SSE 为 <code>text/event-stream</code></td></tr>
|
||
<tr><td>鉴权</td><td>本地无鉴权(仅 127.0.0.1);如需远程暴露另加(本期不含)</td></tr>
|
||
<tr><td>错误格式</td><td>统一 <code>{ "error": "<message>" }</code>;<code>StoreError</code> → <strong>400</strong>、不存在 → <strong>404</strong>、状态机非法转移 → 400、内部 → <strong>500</strong></td></tr>
|
||
<tr><td>时间字段</td><td>ISO8601 字符串(TEXT)</td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">Projects API</div>
|
||
<table class="tbl">
|
||
<tr><th style="width:62px">方法</th><th style="width:230px">路径</th><th>请求体(关键字段)</th><th>响应</th></tr>
|
||
<tr><td>GET</td><td><code>/api/projects</code></td><td>—</td><td><code>Project[]</code></td></tr>
|
||
<tr><td>POST</td><td><code>/api/projects</code></td><td><code>{name*, repoPath*, defaultBranch?, autonomy?, concurrency?, model?}</code></td><td><code>Project</code></td></tr>
|
||
<tr><td>GET</td><td><code>/api/projects/:id</code></td><td>—</td><td><code>Project</code></td></tr>
|
||
<tr><td>PATCH</td><td><code>/api/projects/:id</code></td><td>部分字段:<code>autonomy / concurrency / model / budget_usd / budget_period / checks / agent_rules / diff_max_files / verify_cmd</code></td><td><code>Project</code></td></tr>
|
||
<tr><td>DELETE</td><td><code>/api/projects/:id</code></td><td>—</td><td><code>{ok}</code></td></tr>
|
||
<tr><td>POST</td><td><code>/api/projects/:id/sync</code></td><td>—</td><td><code>{created, done, skipped}</code></td></tr>
|
||
<tr><td>POST</td><td><code>/api/projects/reorder</code></td><td><code>{order: id[]}</code></td><td><code>{ok}</code></td></tr>
|
||
<tr><td>GET</td><td><code>/api/projects/:id/tasks</code></td><td>—</td><td><code>Task[]</code>(任务树)</td></tr>
|
||
<tr><td>GET</td><td><code>/api/projects/:id/events</code></td><td>—</td><td><code>Event[]</code></td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">Tasks API</div>
|
||
<table class="tbl">
|
||
<tr><th style="width:62px">方法</th><th style="width:230px">路径</th><th>请求体(关键字段)</th><th>响应</th></tr>
|
||
<tr><td>POST</td><td><code>/api/projects/:id/tasks</code></td><td><code>{title*, complexity?(auto|hard|medium|easy), priority?, deps?, parentId?, owned_files?, scope?, expected_output?}</code></td><td><code>Task</code></td></tr>
|
||
<tr><td>GET</td><td><code>/api/tasks/:id</code></td><td>—</td><td><code>Task</code></td></tr>
|
||
<tr><td>POST</td><td><code>/api/tasks/:id/plan</code></td><td><code>{plan*}</code></td><td><code>Task</code></td></tr>
|
||
<tr><td>POST</td><td><code>/api/tasks/:id/spec</code></td><td><code>{spec*}</code></td><td><code>Task</code></td></tr>
|
||
<tr><td>POST</td><td><code>/api/tasks/:id/decide</code></td><td><code>{action: accept|reject, actor?, reason?, merge?}</code>(人工闸)</td><td><code>Task</code></td></tr>
|
||
<tr><td>POST</td><td><code>/api/tasks/:id/transition</code></td><td><code>{to*, meta?}</code>(守卫校验合法转移)</td><td><code>Task</code></td></tr>
|
||
<tr><td>POST</td><td><code>/api/tasks/:id/requeue</code></td><td>—</td><td><code>Task</code></td></tr>
|
||
<tr><td>POST</td><td><code>/api/tasks/:id/cancel</code></td><td>—</td><td><code>Task</code></td></tr>
|
||
<tr><td>DELETE</td><td><code>/api/tasks/:id</code></td><td>—</td><td><code>{ok}</code></td></tr>
|
||
<tr><td><span class="chg add">POST ★</span></td><td><code>/api/tasks/:id/attachments</code></td><td><code>multipart: files[]</code>(图片/文件)</td><td><code>{attachments:[{name,type,path}]}</code></td></tr>
|
||
<tr><td><span class="chg add">POST ★</span></td><td><code>/api/tasks/:id/takeover</code></td><td>—(起交互接管 run)</td><td><code>{runId, console}</code></td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">Runs / 观测 API</div>
|
||
<table class="tbl">
|
||
<tr><th style="width:62px">方法</th><th style="width:230px">路径</th><th>请求体 / 查询</th><th>响应</th></tr>
|
||
<tr><td>GET</td><td><code>/api/tasks/:id/runs</code></td><td>—</td><td><code>Run[]</code></td></tr>
|
||
<tr><td>GET</td><td><code>/api/runs/:id/transcript</code></td><td>—</td><td><code>text/ndjson</code>(transcript)</td></tr>
|
||
<tr><td>GET</td><td><code>/api/tasks/:id/events</code></td><td>—</td><td><code>Event[]</code></td></tr>
|
||
<tr><td><span class="chg add">GET ★</span></td><td><code>/api/tasks/:id/stream</code></td><td>—</td><td><code>text/event-stream</code>(agent token SSE,Phase 2)</td></tr>
|
||
<tr><td>GET</td><td><code>/api/agents</code></td><td>—</td><td><code>AgentSpec[]</code>(注册表)</td></tr>
|
||
<tr><td><span class="chg add">GET ★</span></td><td><code>/api/usage</code></td><td><code>?project&period</code></td><td><code>{byProject, byModel, total}</code> 成本明细</td></tr>
|
||
<tr><td><span class="chg add">GET ★</span></td><td><code>/api/metrics</code></td><td>—</td><td><code>{queueDepth, throughput, latency, reviewPassRate, cost}</code></td></tr>
|
||
<tr><td><span class="chg add">GET ★</span></td><td><code>/api/health</code></td><td>—</td><td><code>{ok, db, inflight, lastTickAt}</code></td></tr>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-title">WebSocket 事件契约(<code>/ws</code>)</div>
|
||
<p class="lead" style="font-size:12px;margin:0 0 10px;color:var(--text2)">连接 <code>ws://127.0.0.1:4517/ws</code>,连上后<strong>服务端单向推送</strong>(客户端无需发消息)。每条消息 = 一行 <code>events</code> 表记录,前端据 <code>type</code> 增量更新看板,<strong>无需轮询</strong>。</p>
|
||
<pre><span class="cm">// 消息封装(JSON)</span>
|
||
{ type, projectId, taskId, payload, at }</pre>
|
||
<table class="tbl" style="margin-top:10px">
|
||
<tr><th style="width:200px">type</th><th>触发</th><th>payload 关键</th></tr>
|
||
<tr><td><code>task.created</code></td><td>新任务(任意入口)</td><td>task</td></tr>
|
||
<tr><td><code>task.updated</code></td><td>任务字段变更</td><td>task / 变更字段</td></tr>
|
||
<tr><td><code>status.changed</code></td><td><code>schedule_status</code> 变更</td><td>from / to / work_type</td></tr>
|
||
<tr><td><code>complexity.changed</code></td><td>AUTO 复杂度回填</td><td>complexity / reason</td></tr>
|
||
<tr><td><code>run.started</code></td><td>worker 起跑</td><td>runId / kind</td></tr>
|
||
<tr><td><code>run.finished</code></td><td>run 结束</td><td>runId / result</td></tr>
|
||
<tr><td><code>approval.granted</code> / <code>approval.rejected</code></td><td>人工闸决策</td><td>gate / reason</td></tr>
|
||
<tr><td><code>merge.failed</code> / <code>merge.remediated</code></td><td>合并冲突 / 收口</td><td>branch</td></tr>
|
||
<tr><td><code>project.synced</code></td><td>todo 同步完成</td><td>created / done / skipped</td></tr>
|
||
<tr><td><span class="chg add"><code>run.progress</code> ★</span></td><td>SSE 配套(Phase 2)</td><td>runId / token 增量</td></tr>
|
||
<tr><td><span class="chg add"><code>budget.exceeded</code> ★</span></td><td>项目当期成本超预算</td><td>projectId / spend / budget</td></tr>
|
||
</table>
|
||
<div class="note" style="margin-top:8px">★ = 本次重构新增路由 / 事件。完整 <code>Task</code> / <code>Project</code> / <code>Run</code> / <code>Event</code> 结构见 ③ 数据库 Schema 的字段表(响应体即各表行的 camelCase 映射)。</div>
|
||
</div>
|
||
|
||
|
||
<!-- ══════════ ⑤ Phase 1 实施计划 ══════════ -->
|
||
</div><!-- /.section s-design -->
|
||
|
||
<div class="section" id="s-impl">
|
||
<div class="sec-heading">Phase 1 实施计划</div>
|
||
<div class="sec-desc">六大任务(T1–T6)及 checkbox 进度追踪,实施前须完成 ③ 设计方案审阅。</div>
|
||
<div style="max-width:1280px;margin:0 auto;padding:0 40px 40px">
|
||
<a class="chapter-anchor" id="d-plan"></a>
|
||
<div class="chapter-hdr"><span>⑤</span> Phase 1 实施计划</div>
|
||
|
||
<div class="card" style="border-left:3px solid var(--amber)">
|
||
<div class="card-title">全局约束</div>
|
||
<div class="grid-2">
|
||
<div>
|
||
<div class="row"><code>npm test</code> 全套测试,使用 <code>new Store(':memory:')</code>,禁止 mock</div>
|
||
<div class="row"><code>npm run typecheck</code> 类型检查</div>
|
||
<div class="row"><code>npm run build</code> 编译到 dist/</div>
|
||
</div>
|
||
<div>
|
||
<div class="row" style="color:var(--amber)">最强档模型 ID:<code>'claude-opus-4-8'</code>(fable-5 暂不可用)</div>
|
||
<div class="row" style="color:var(--red)">禁止 git push / 禁止部署</div>
|
||
<div class="row" style="color:var(--red)">禁止重复实现已有功能(见"已完成项")</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card" style="border-left:3px solid var(--green)">
|
||
<div class="card-title">已完成项(禁止重复实现)</div>
|
||
<div class="row"><code>verdict→硬闸</code> ingest.ts 149–165 行:任一 reject 退回重执行</div>
|
||
<div class="row"><code>Promise.all 并行双复审</code> pipeline.ts 154 行</div>
|
||
<div class="row"><code>syncMain</code> 执行前同步主分支 pipeline.ts 121–127 行</div>
|
||
<div class="row"><code>conflict 管道</code> runConflictPipeline pipeline.ts 211–273 行</div>
|
||
<div class="row"><code>planner maxTurns</code> 按复杂度 90/60/40 runner.ts 168 行</div>
|
||
<div class="row"><code>depth 注入</code> 防无限拆 buildPlannerPrompt 133 行</div>
|
||
<div class="row"><code>git 只读工具</code> log/diff/show runPlanner 171 行</div>
|
||
<div class="row"><code>deps 序号→taskId</code> 映射 ingest.ts 103–116 行</div>
|
||
<div class="note" style="margin-top:12px">⚠ <strong>迁移说明</strong>:以上均实现在<strong>旧扁平 FSM</strong>(analyzing/speccing/executing/exec_review)上。重构到 schedule×work_type 时<strong>业务逻辑可复用</strong>(双复审 / 解冲突 / syncMain / planner 档位与 prompt / deps 映射),但<strong>状态机接线须重指向</strong>新 schedule_status×work_type:① review 现为<strong>独立 work_type</strong>,「Promise.all 双复审」须从 executor(code) run <strong>拆出到独立 reviewer run</strong>;② 「verdict→硬闸」「exec_review 转移」重定位到 review 步的 <code>waiting(review)</code> 闸 + <code>pr</code> 步合并。属 T1–T6 的迁移内容,非推倒重写。</div>
|
||
</div>
|
||
|
||
<!-- T1 -->
|
||
<div class="task-card" id="tc1">
|
||
<div class="task-hdr" onclick="toggleTask('tc1')">
|
||
<div class="task-num t1">T1</div>
|
||
<div class="task-info"><div class="task-title2">Schema 迁移 + Task 类型扩展</div><div class="task-sub">schema.sql · db.ts · types.ts · mappers.ts · store.ts</div></div>
|
||
<div class="task-prog"><span class="dn" id="dn-tc1">0</span>/10</div>
|
||
<div class="caret">▾</div>
|
||
</div>
|
||
<div class="task-body">
|
||
<ul class="steps" id="sl-tc1">
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc1')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind test">测试</span>步骤 1:写失败测试</div>
|
||
<pre>test(<span class="str">'Task 新字段默认值'</span>, () => {
|
||
<span class="kw">const</span> store = <span class="kw">new</span> <span class="tp">Store</span>(<span class="str">':memory:'</span>);
|
||
<span class="kw">const</span> p = store.<span class="fn">createProject</span>({ name:<span class="str">'p'</span>, repoPath:<span class="str">'/tmp/t-'</span>+Math.random(), autonomy:<span class="str">'manual'</span> });
|
||
<span class="kw">const</span> t = store.<span class="fn">createTask</span>({ projectId: p.id, title:<span class="str">'feat'</span>, complexity:<span class="str">'easy'</span> });
|
||
assert.<span class="fn">strictEqual</span>(t.scheduleStatus, <span class="str">'init'</span>);
|
||
assert.<span class="fn">deepStrictEqual</span>(t.workPlan, []);
|
||
assert.<span class="fn">strictEqual</span>(t.version, <span class="nu">1</span>);
|
||
});</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc1')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind run">运行</span>步骤 2:确认失败</div>
|
||
<pre>tsx --test test/store.test.ts 2>&1 | grep -E "fail|Error" | head -5</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc1')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 3:schema.sql — tasks 加状态/队列等 9 列,runs 加 trace_id</div>
|
||
<pre> schedule_status TEXT NOT NULL DEFAULT <span class="str">'init'</span>,
|
||
work_type TEXT,
|
||
work_plan TEXT NOT NULL DEFAULT <span class="str">'[]'</span>,
|
||
work_history TEXT NOT NULL DEFAULT <span class="str">'[]'</span>,
|
||
claimed_at TEXT,
|
||
version INTEGER NOT NULL DEFAULT <span class="nu">1</span>,
|
||
scope TEXT,
|
||
owned_files TEXT NOT NULL DEFAULT <span class="str">'[]'</span>,
|
||
expected_output TEXT
|
||
<span class="cm">-- runs 表:</span>
|
||
trace_id TEXT</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc1')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 4:db.ts — openDb 末尾追加 10 个 ensureColumn</div>
|
||
<pre> <span class="fn">ensureColumn</span>(db,<span class="str">'tasks'</span>,<span class="str">'schedule_status'</span>,<span class="str">"schedule_status TEXT NOT NULL DEFAULT 'init'"</span>);
|
||
<span class="fn">ensureColumn</span>(db,<span class="str">'tasks'</span>,<span class="str">'work_type'</span>,<span class="str">'work_type TEXT'</span>);
|
||
<span class="fn">ensureColumn</span>(db,<span class="str">'tasks'</span>,<span class="str">'work_plan'</span>,<span class="str">"work_plan TEXT NOT NULL DEFAULT '[]'"</span>);
|
||
<span class="fn">ensureColumn</span>(db,<span class="str">'tasks'</span>,<span class="str">'work_history'</span>,<span class="str">"work_history TEXT NOT NULL DEFAULT '[]'"</span>);
|
||
<span class="fn">ensureColumn</span>(db,<span class="str">'tasks'</span>,<span class="str">'claimed_at'</span>,<span class="str">'claimed_at TEXT'</span>);
|
||
<span class="fn">ensureColumn</span>(db,<span class="str">'tasks'</span>,<span class="str">'version'</span>,<span class="str">'version INTEGER NOT NULL DEFAULT 1'</span>);
|
||
<span class="fn">ensureColumn</span>(db,<span class="str">'tasks'</span>,<span class="str">'scope'</span>,<span class="str">'scope TEXT'</span>);
|
||
<span class="fn">ensureColumn</span>(db,<span class="str">'tasks'</span>,<span class="str">'owned_files'</span>,<span class="str">"owned_files TEXT NOT NULL DEFAULT '[]'"</span>);
|
||
<span class="fn">ensureColumn</span>(db,<span class="str">'tasks'</span>,<span class="str">'expected_output'</span>,<span class="str">'expected_output TEXT'</span>);
|
||
<span class="fn">ensureColumn</span>(db,<span class="str">'runs'</span>,<span class="str">'trace_id'</span>,<span class="str">'trace_id TEXT'</span>);</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc1')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 5:types.ts — 新增 ScheduleStatus/WorkType/WorkStep + Task 接口字段</div>
|
||
<pre><span class="kw">export type</span> <span class="tp">ScheduleStatus</span> = <span class="str">'init'</span>|<span class="str">'ready'</span>|<span class="str">'running'</span>|<span class="str">'waiting'</span>|<span class="str">'blocked'</span>|<span class="str">'paused'</span>|<span class="str">'failed'</span>|<span class="str">'stuck'</span>|<span class="str">'done'</span>|<span class="str">'cancelled'</span>;
|
||
<span class="kw">export type</span> <span class="tp">WorkType</span> = <span class="str">'plan'</span>|<span class="str">'code'</span>|<span class="str">'review'</span>|<span class="str">'pr'</span>|<span class="str">'conflict'</span>|<span class="str">'research'</span>;
|
||
<span class="kw">export type</span> <span class="tp">TaskScope</span> = <span class="str">'file'</span>|<span class="str">'module'</span>|<span class="str">'service'</span>|<span class="str">'cross-service'</span>;
|
||
<span class="kw">export interface</span> <span class="tp">WorkStep</span> { type: <span class="tp">WorkType</span>; spec?: <span class="tp">string</span>; ownedFiles?: <span class="tp">string</span>[]; expectedOutput?: <span class="tp">string</span>; }
|
||
<span class="cm">// Task 接口追加:</span>
|
||
scheduleStatus: <span class="tp">ScheduleStatus</span>; workType: <span class="tp">WorkType</span>|<span class="kw">null</span>;
|
||
workPlan: <span class="tp">WorkStep</span>[]; workHistory: <span class="tp">WorkStep</span>[];
|
||
scope: <span class="tp">TaskScope</span>|<span class="kw">null</span>; ownedFiles: <span class="tp">string</span>[]; expectedOutput: <span class="tp">string</span>|<span class="kw">null</span>;
|
||
claimedAt: <span class="tp">string</span>|<span class="kw">null</span>; version: <span class="tp">number</span>;</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc1')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 6:mappers.ts — TaskRow 加字段 + rowToTask 映射</div>
|
||
<pre><span class="cm">// TaskRow 追加:</span>
|
||
schedule_status:<span class="tp">string</span>; work_type:<span class="tp">string</span>|<span class="kw">null</span>; work_plan:<span class="tp">string</span>; work_history:<span class="tp">string</span>; owned_files:<span class="tp">string</span>; claimed_at:<span class="tp">string</span>|<span class="kw">null</span>; version:<span class="tp">number</span>;
|
||
<span class="cm">// rowToTask 追加:</span>
|
||
scheduleStatus: r.schedule_status ?? <span class="str">'init'</span>,
|
||
workType: r.work_type ?? <span class="kw">null</span>,
|
||
workPlan: r.work_plan ? JSON.<span class="fn">parse</span>(r.work_plan) : [],
|
||
workHistory: r.work_history ? JSON.<span class="fn">parse</span>(r.work_history) : [],
|
||
ownedFiles: r.owned_files ? JSON.<span class="fn">parse</span>(r.owned_files) : [],
|
||
version: r.version ?? <span class="nu">1</span>,</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc1')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 7:store.ts — PatchTaskInput 支持新字段(workPlan/workHistory 用 JSON.stringify 序列化)</div>
|
||
<div class="step-desc">按已有参数化查询模式处理;禁止字符串拼接传值。</div>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc1')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind run">运行</span>步骤 8:npm test</div>
|
||
<pre>npm test 2>&1 | tail -15</pre>
|
||
<div class="step-desc">预期:新测试通过,旧测试不受影响。</div>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc1')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind run">运行</span>步骤 9:typecheck</div>
|
||
<pre>npm run typecheck</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc1')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind commit">提交</span>步骤 10</div>
|
||
<pre>git add src/store/schema.sql src/store/db.ts src/model/types.ts src/store/mappers.ts src/store/store.ts test/store.test.ts
|
||
git commit -m "feat(model): 新增 scheduleStatus/workType/workPlan/workHistory/scope/version 字段 + schema 迁移"</pre>
|
||
</div></li>
|
||
</ul>
|
||
</div></div>
|
||
|
||
<!-- T2 -->
|
||
<div class="task-card" id="tc2">
|
||
<div class="task-hdr" onclick="toggleTask('tc2')">
|
||
<div class="task-num t2">T2</div>
|
||
<div class="task-info"><div class="task-title2">CAS Claim + 执行期锁</div><div class="task-sub">store.ts · orchestrator.ts</div></div>
|
||
<div class="task-prog"><span class="dn" id="dn-tc2">0</span>/10</div><div class="caret">▾</div>
|
||
</div>
|
||
<div class="task-body">
|
||
<ul class="steps" id="sl-tc2">
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc2')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind test">测试</span>步骤 1:写失败测试(双重 claim + ready 清零)</div>
|
||
<pre>test(<span class="str">'CAS — 同任务不能双重领取'</span>, () => {
|
||
assert.<span class="fn">ok</span>(store.<span class="fn">casClaimTask</span>(task.id,<span class="str">'run-1'</span>));
|
||
assert.<span class="fn">ok</span>(!store.<span class="fn">casClaimTask</span>(task.id,<span class="str">'run-2'</span>));
|
||
});
|
||
test(<span class="str">'transition→ready 清空 claimed_at'</span>, () => {
|
||
store.<span class="fn">decide</span>(task.id,<span class="str">'reject'</span>,<span class="str">'human'</span>,<span class="str">'改'</span>);
|
||
assert.<span class="fn">ok</span>(store.<span class="fn">casClaimTask</span>(task.id,<span class="str">'run-3'</span>));
|
||
});</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc2')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind run">运行</span>步骤 2:确认失败</div>
|
||
<pre>tsx --test test/orchestrator.test.ts 2>&1 | grep casClaimTask | head -3</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc2')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 3:store.ts — casClaimTask</div>
|
||
<pre><span class="fn">casClaimTask</span>(taskId:<span class="tp">string</span>,_runId:<span class="tp">string</span>):<span class="tp">boolean</span> {
|
||
<span class="kw">const</span> r=<span class="kw">this</span>.db.<span class="fn">prepare</span>(
|
||
<span class="str">`UPDATE tasks SET claimed_at=?,updated_at=? WHERE id=? AND claimed_at IS NULL`</span>
|
||
).<span class="fn">run</span>(<span class="fn">now</span>(),<span class="fn">now</span>(),taskId);
|
||
<span class="kw">return</span> r.changes===<span class="nu">1</span>;
|
||
}</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc2')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 4:transition() to==='ready' 清 claimed_at;requeueTask 同步清零</div>
|
||
<pre><span class="kw">if</span>(to===<span class="str">'ready'</span>){
|
||
<span class="kw">this</span>.db.<span class="fn">prepare</span>(<span class="str">`UPDATE tasks SET status=?,claimed_at=NULL,updated_at=? WHERE id=?`</span>).<span class="fn">run</span>(to,<span class="fn">now</span>(),taskId);
|
||
}</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc2')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 5:orchestrator.claimOne — startRun 后接 casClaimTask</div>
|
||
<pre><span class="kw">if</span>(!store.<span class="fn">casClaimTask</span>(task.id,run.id)){log.<span class="fn">error</span>(<span class="str">`CAS失败`</span>);<span class="kw">return</span>;}</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc2')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 6:store.ts — assertNoActiveRun 私有方法</div>
|
||
<pre><span class="kw">private</span> <span class="fn">assertNoActiveRun</span>(taskId:<span class="tp">string</span>){
|
||
<span class="kw">const</span> {n}=<span class="kw">this</span>.db.<span class="fn">prepare</span>(<span class="str">`SELECT COUNT(*) n FROM runs WHERE task_id=? AND status='started'`</span>).<span class="fn">get</span>(taskId) <span class="kw">as</span> {n:<span class="tp">number</span>};
|
||
<span class="kw">if</span>(n><span class="nu">0</span>) <span class="kw">throw new</span> <span class="tp">StoreError</span>(<span class="str">'任务执行中,禁止修改'</span>);
|
||
}</pre>
|
||
<div class="step-desc">在 setSpec / setPlan / patchTask 入口调用 assertNoActiveRun。</div>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc2')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind test">测试</span>步骤 7:补执行期锁测试</div>
|
||
<pre>test(<span class="str">'执行中禁止修改 spec'</span>, () => {
|
||
store.<span class="fn">startRun</span>(t.id,<span class="str">'planner'</span>);
|
||
assert.<span class="fn">throws</span>(()=>store.<span class="fn">setSpec</span>(t.id,<span class="str">'x'</span>),/执行中/);
|
||
});</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc2')"></div><div class="step-con"><div class="step-ttl"><span class="kind run">运行</span>步骤 8:npm test</div><pre>npm test 2>&1 | tail -15</pre></div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc2')"></div><div class="step-con"><div class="step-ttl"><span class="kind run">运行</span>步骤 9:typecheck</div><pre>npm run typecheck</pre></div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc2')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind commit">提交</span>步骤 10</div>
|
||
<pre>git add src/store/store.ts src/daemon/orchestrator.ts test/orchestrator.test.ts
|
||
git commit -m "feat(store): CAS claimed_at 防双重 claim + 执行期锁"</pre>
|
||
</div></li>
|
||
</ul>
|
||
</div></div>
|
||
|
||
<!-- T3 -->
|
||
<div class="task-card" id="tc3">
|
||
<div class="task-hdr" onclick="toggleTask('tc3')">
|
||
<div class="task-num t3">T3</div>
|
||
<div class="task-info"><div class="task-title2">调度算法升级(rankU + agingBonus + couplingPenalty)</div><div class="task-sub">scoring.ts · orchestrator.ts</div></div>
|
||
<div class="task-prog"><span class="dn" id="dn-tc3">0</span>/7</div><div class="caret">▾</div>
|
||
</div>
|
||
<div class="task-body">
|
||
<ul class="steps" id="sl-tc3">
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc3')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind test">测试</span>步骤 1:写失败测试</div>
|
||
<pre>test(<span class="str">'agingBonus — 新建任务为 0'</span>, () =>
|
||
assert.<span class="fn">strictEqual</span>(<span class="fn">agingBonus</span>({createdAt:<span class="kw">new</span> Date().<span class="fn">toISOString</span>(),priority:<span class="nu">1</span>} <span class="kw">as</span> <span class="tp">Task</span>,Date.<span class="fn">now</span>()),<span class="nu">0</span>));
|
||
test(<span class="str">'couplingPenalty — 1个重叠=-0.5'</span>, () =>
|
||
assert.<span class="fn">strictEqual</span>(<span class="fn">couplingPenalty</span>({ownedFiles:[<span class="str">'a.ts'</span>,<span class="str">'b.ts'</span>]} <span class="kw">as</span> <span class="tp">Task</span>,[{ownedFiles:[<span class="str">'a.ts'</span>]} <span class="kw">as</span> <span class="tp">Task</span>]),<span class="nu">0.5</span>));</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc3')"></div><div class="step-con"><div class="step-ttl"><span class="kind run">运行</span>步骤 2:确认失败</div><pre>tsx --test test/orchestrator.test.ts 2>&1 | grep -E "agingBonus|coupling" | head -3</pre></div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc3')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 3:重写 scoring.ts — 导出 rankU / agingBonus / couplingPenalty / 新签名 rankByScore</div>
|
||
<pre><span class="kw">export function</span> <span class="fn">rankU</span>(id:<span class="tp">string</span>,byId:<span class="tp">Map</span><<span class="tp">string</span>,<span class="tp">Task</span>>,deps:<span class="tp">Map</span><<span class="tp">string</span>,<span class="tp">Task</span>[]>,cache:<span class="tp">Map</span><<span class="tp">string</span>,<span class="tp">number</span>>):<span class="tp">number</span>{
|
||
<span class="kw">if</span>(cache.<span class="fn">has</span>(id))<span class="kw">return</span> cache.<span class="fn">get</span>(id)!;
|
||
<span class="kw">const</span> t=byId.<span class="fn">get</span>(id); <span class="kw">if</span>(!t){cache.<span class="fn">set</span>(id,<span class="nu">0</span>);<span class="kw">return</span> <span class="nu">0</span>;}
|
||
<span class="kw">const</span> v=<span class="fn">baseScore</span>(t.priority)+(deps.<span class="fn">get</span>(id)??[]).<span class="fn">reduce</span>((s,d)=>s+<span class="fn">rankU</span>(d.id,byId,deps,cache),<span class="nu">0</span>);
|
||
cache.<span class="fn">set</span>(id,v);<span class="kw">return</span> v;
|
||
}
|
||
<span class="kw">export const</span> <span class="fn">agingBonus</span>=(t:<span class="tp">Task</span>,nowMs:<span class="tp">number</span>)=>
|
||
Math.<span class="fn">min</span>(<span class="fn">baseScore</span>(t.priority),(nowMs-Date.<span class="fn">parse</span>(t.createdAt))/<span class="nu">3_600_000</span>/<span class="nu">48</span>);
|
||
<span class="kw">export function</span> <span class="fn">couplingPenalty</span>(t:<span class="tp">Task</span>,inFlight:<span class="tp">Task</span>[]):<span class="tp">number</span>{
|
||
<span class="kw">const</span> s=<span class="kw">new</span> Set(t.ownedFiles??[]); <span class="kw">if</span>(!s.size)<span class="kw">return</span> <span class="nu">0</span>;
|
||
<span class="kw">let</span> n=<span class="nu">0</span>; <span class="kw">for</span>(<span class="kw">const</span> x <span class="kw">of</span> inFlight)<span class="kw">for</span>(<span class="kw">const</span> f <span class="kw">of</span>(x.ownedFiles??[])) <span class="kw">if</span>(s.<span class="fn">has</span>(f))n++;
|
||
<span class="kw">return</span> n*<span class="nu">0.5</span>;
|
||
}</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc3')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 4:orchestrator.ts — claimable() 传入 inFlight 列表</div>
|
||
<pre><span class="kw">const</span> inflightList=tasks.<span class="fn">filter</span>(t=>inflight.<span class="fn">has</span>(t.id));
|
||
<span class="kw">return</span> <span class="fn">rankByScore</span>(candidates,tasks,inflightList,d.<span class="fn">nowMs</span>());</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc3')"></div><div class="step-con"><div class="step-ttl"><span class="kind run">运行</span>步骤 5:npm test</div><pre>npm test 2>&1 | tail -15</pre></div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc3')"></div><div class="step-con"><div class="step-ttl"><span class="kind run">运行</span>步骤 6:typecheck</div><pre>npm run typecheck</pre></div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc3')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind commit">提交</span>步骤 7</div>
|
||
<pre>git add src/model/scoring.ts src/daemon/orchestrator.ts test/orchestrator.test.ts
|
||
git commit -m "feat(scoring): CPM rankU + agingBonus + couplingPenalty 调度算法"</pre>
|
||
</div></li>
|
||
</ul>
|
||
</div></div>
|
||
|
||
<!-- T4 -->
|
||
<div class="task-card" id="tc4">
|
||
<div class="task-hdr" onclick="toggleTask('tc4')">
|
||
<div class="task-num t4">T4</div>
|
||
<div class="task-info"><div class="task-title2">模型档位升级(统一最强档 opus-4-8)</div><div class="task-sub">models.ts</div></div>
|
||
<div class="task-prog"><span class="dn" id="dn-tc4">0</span>/6</div><div class="caret">▾</div>
|
||
</div>
|
||
<div class="task-body">
|
||
<ul class="steps" id="sl-tc4">
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc4')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind test">测试</span>步骤 1:写失败测试</div>
|
||
<pre>test(<span class="str">'planner hard = opus-4-8'</span>, () =>
|
||
assert.<span class="fn">strictEqual</span>(<span class="fn">pickModel</span>({complexity:<span class="str">'hard'</span>}<span class="kw">as</span> <span class="tp">Task</span>,{model:<span class="kw">null</span>}<span class="kw">as</span> <span class="tp">Project</span>,<span class="str">'planner'</span>),<span class="str">'claude-opus-4-8'</span>));
|
||
test(<span class="str">'reviewer 不被 project.model 降档'</span>, () =>
|
||
assert.<span class="fn">strictEqual</span>(<span class="fn">pickModel</span>({complexity:<span class="str">'easy'</span>}<span class="kw">as</span> <span class="tp">Task</span>,{model:<span class="str">'claude-sonnet-4-6'</span>}<span class="kw">as</span> <span class="tp">Project</span>,<span class="str">'reviewer'</span>),<span class="str">'claude-opus-4-8'</span>));
|
||
test(<span class="str">'MODEL_FALLBACK_CHAIN[0] = opus-4-8'</span>, () =>
|
||
assert.<span class="fn">strictEqual</span>(MODEL_FALLBACK_CHAIN[<span class="nu">0</span>],<span class="str">'claude-opus-4-8'</span>));</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc4')"></div><div class="step-con"><div class="step-ttl"><span class="kind run">运行</span>步骤 2:确认失败</div><pre>tsx --test test/models.test.ts 2>&1 | grep -E 'opus|reviewer' | head -3</pre></div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc4')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 3:models.ts — 4 处修改</div>
|
||
<pre><span class="cm">// 1. 回退链(fable-5 暂不可用,opus-4-8 为最强档)</span>
|
||
<span class="kw">export const</span> MODEL_FALLBACK_CHAIN=[<span class="str">'claude-opus-4-8'</span>,<span class="str">'claude-sonnet-4-6'</span>] <span class="kw">as const</span>;
|
||
<span class="cm">// 2. planner 分档:easy=sonnet,medium/hard=opus(hard 不再与 easy 同档)</span>
|
||
hard:[<span class="str">'MAESTRO_MODEL_PLAN_HARD'</span>,<span class="str">'claude-opus-4-8'</span>],
|
||
<span class="cm">// 3. reviewer 三档统一最强 opus-4-8(不被 project.model 降档)</span>
|
||
reviewer:{easy:[...,<span class="str">'claude-opus-4-8'</span>],medium:[...,<span class="str">'claude-opus-4-8'</span>],hard:[...,<span class="str">'claude-opus-4-8'</span>]},
|
||
<span class="cm">// 4. conflict 固定最强(pickModel 首行)</span>
|
||
<span class="kw">if</span>(role===<span class="str">'conflict'</span>)<span class="kw">return</span> <span class="str">'claude-opus-4-8'</span>;</pre>
|
||
<div class="note">⚠ fable-5 是设计目标最强档,暂不可用;当前一律用 opus-4-8 充当,fable-5 恢复后把以上 4 处的 <code>claude-opus-4-8</code> 换回 <code>claude-fable-5</code> 即可,结构不变。</div>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc4')"></div><div class="step-con"><div class="step-ttl"><span class="kind run">运行</span>步骤 4:npm test</div><pre>npm test 2>&1 | tail -15</pre></div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc4')"></div><div class="step-con"><div class="step-ttl"><span class="kind run">运行</span>步骤 5:typecheck</div><pre>npm run typecheck</pre></div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc4')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind commit">提交</span>步骤 6</div>
|
||
<pre>git add src/executor/models.ts test/models.test.ts
|
||
git commit -m "feat(models): 复审/解冲突/planner-hard 统一最强档 opus-4-8"</pre>
|
||
</div></li>
|
||
</ul>
|
||
</div></div>
|
||
|
||
<!-- T5 -->
|
||
<div class="task-card" id="tc5">
|
||
<div class="task-hdr" onclick="toggleTask('tc5')">
|
||
<div class="task-num t5">T5</div>
|
||
<div class="task-info"><div class="task-title2">Planner Decompose JSON 扩展(ownedFiles + expectedOutput)</div><div class="task-sub">runner.ts · protocol.ts · ingest.ts</div></div>
|
||
<div class="task-prog"><span class="dn" id="dn-tc5">0</span>/8</div><div class="caret">▾</div>
|
||
</div>
|
||
<div class="task-body">
|
||
<ul class="steps" id="sl-tc5">
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc5')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind test">测试</span>步骤 1:ingest decompose-result 映射测试</div>
|
||
<pre>test(<span class="str">'decompose-result — ownedFiles 落入子任务'</span>, () => {
|
||
<span class="fn">appendOutbox</span>(run.id,{type:<span class="str">'decompose-result'</span>,plan:<span class="str">'x'</span>,
|
||
subtasks:[{title:<span class="str">'类型'</span>,complexity:<span class="str">'easy'</span>,priority:<span class="nu">0</span>,deps:[],
|
||
ownedFiles:[<span class="str">'src/model/types.ts'</span>],expectedOutput:<span class="str">'typecheck通过'</span>}]});
|
||
<span class="fn">ingestRun</span>(store,noopLog,run.id);
|
||
<span class="kw">const</span> [child]=store.<span class="fn">childrenOf</span>(parent.id);
|
||
assert.<span class="fn">deepStrictEqual</span>(child.ownedFiles,[<span class="str">'src/model/types.ts'</span>]);
|
||
assert.<span class="fn">strictEqual</span>(child.expectedOutput,<span class="str">'typecheck通过'</span>);
|
||
});</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc5')"></div><div class="step-con"><div class="step-ttl"><span class="kind run">运行</span>步骤 2:确认失败</div><pre>tsx --test test/ingest.test.ts 2>&1 | grep -E "ownedFiles|expectedOutput" | head -3</pre></div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc5')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 3:protocol.ts — decompose-result 子任务加 ownedFiles? / expectedOutput?</div>
|
||
<pre>subtasks: <span class="tp">Array</span><{title:<span class="tp">string</span>;complexity:<span class="tp">Complexity</span>;priority?:<span class="tp">number</span>;deps?:<span class="tp">number</span>[];
|
||
ownedFiles?:<span class="tp">string</span>[];expectedOutput?:<span class="tp">string</span>}>;</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc5')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 4:runner.ts — buildPlannerPrompt decompose 分支加两条 bullet + 更新 JSON 示例</div>
|
||
<pre><span class="str">'- ownedFiles:主要修改文件路径 JSON string[](无则填 [])'</span>,
|
||
<span class="str">'- expectedOutput:一句话可验证完成标准(如:"typecheck通过")'</span></pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc5')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 5:ingest.ts — decompose-result deps 映射之后追加 ownedFiles/expectedOutput 映射循环</div>
|
||
<pre><span class="kw">for</span>(<span class="kw">let</span> i=<span class="nu">0</span>;i<rec.subtasks.length;i++){
|
||
<span class="kw">const</span> sub=rec.subtasks[i] <span class="kw">as</span> {ownedFiles?:<span class="tp">string</span>[];expectedOutput?:<span class="tp">string</span>};
|
||
<span class="kw">if</span>(!createdIds[i])<span class="kw">continue</span>;
|
||
<span class="kw">const</span> patch:<span class="tp">Record</span><<span class="tp">string</span>,<span class="tp">unknown</span>>={};
|
||
<span class="kw">if</span>(<span class="tp">Array</span>.<span class="fn">isArray</span>(sub.ownedFiles)&&sub.ownedFiles.length) patch.ownedFiles=sub.ownedFiles;
|
||
<span class="kw">if</span>(<span class="kw">typeof</span> sub.expectedOutput===<span class="str">'string'</span>&&sub.expectedOutput.<span class="fn">trim</span>()) patch.expectedOutput=sub.expectedOutput.<span class="fn">trim</span>();
|
||
<span class="kw">if</span>(<span class="tp">Object</span>.<span class="fn">keys</span>(patch).length) store.<span class="fn">patchTask</span>(createdIds[i],patch);
|
||
}</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc5')"></div><div class="step-con"><div class="step-ttl"><span class="kind run">运行</span>步骤 6:npm test</div><pre>npm test 2>&1 | tail -15</pre></div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc5')"></div><div class="step-con"><div class="step-ttl"><span class="kind run">运行</span>步骤 7:typecheck</div><pre>npm run typecheck</pre></div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc5')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind commit">提交</span>步骤 8</div>
|
||
<pre>git add src/executor/runner.ts src/executor/protocol.ts src/daemon/ingest.ts test/ingest.test.ts
|
||
git commit -m "feat(planner): decompose JSON 扩展 ownedFiles/expectedOutput"</pre>
|
||
</div></li>
|
||
</ul>
|
||
</div></div>
|
||
|
||
<!-- T6 -->
|
||
<div class="task-card" id="tc6">
|
||
<div class="task-hdr" onclick="toggleTask('tc6')">
|
||
<div class="task-num t6">T6</div>
|
||
<div class="task-info"><div class="task-title2">管道硬闸(checks 分项 + diff 体量)</div><div class="task-sub">pipeline.ts · schema.sql · db.ts · types.ts · mappers.ts</div></div>
|
||
<div class="task-prog"><span class="dn" id="dn-tc6">0</span>/10</div><div class="caret">▾</div>
|
||
</div>
|
||
<div class="task-body">
|
||
<ul class="steps" id="sl-tc6">
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc6')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind test">测试</span>步骤 1:写失败测试(checks + diff 体量)</div>
|
||
<pre>test(<span class="str">'checks — lint 失败阻止 exec_review'</span>, <span class="kw">async</span> () => {
|
||
job.project.checks=JSON.<span class="fn">stringify</span>({lint:<span class="str">'exit 1'</span>});
|
||
<span class="kw">const</span> emitted=[];
|
||
<span class="kw">await</span> <span class="fn">runPipeline</span>(job,mockDeps,p=>emitted.<span class="fn">push</span>(p));
|
||
assert.<span class="fn">ok</span>(emitted.<span class="fn">find</span>(e=>e.type===<span class="str">'failed'</span>&&e.error.<span class="fn">includes</span>(<span class="str">'lint'</span>)));
|
||
});
|
||
test(<span class="str">'diff 体量闸 — 超 diffMaxFiles 失败'</span>, <span class="kw">async</span> () => {
|
||
job.project.diffMaxFiles=<span class="nu">2</span>;
|
||
mockDeps.worktreeDiff=<span class="kw">async</span>()=>({diffSummary:<span class="str">' a.ts | 1\n b.ts | 1\n c.ts | 1'</span>,commits:[]});
|
||
<span class="kw">const</span> emitted=[];
|
||
<span class="kw">await</span> <span class="fn">runPipeline</span>(job,mockDeps,p=>emitted.<span class="fn">push</span>(p));
|
||
assert.<span class="fn">ok</span>(emitted.<span class="fn">find</span>(e=>e.type===<span class="str">'failed'</span>));
|
||
});</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc6')"></div><div class="step-con"><div class="step-ttl"><span class="kind run">运行</span>步骤 2:确认失败</div><pre>tsx --test test/pipeline.test.ts 2>&1 | grep -E "diff|checks" | head -3</pre></div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc6')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 3:schema.sql + db.ts — projects 加 diff_max_files</div>
|
||
<pre><span class="cm">-- schema.sql:</span>
|
||
diff_max_files INTEGER NOT NULL DEFAULT <span class="nu">100</span>
|
||
<span class="cm">-- db.ts:</span>
|
||
<span class="fn">ensureColumn</span>(db,<span class="str">'projects'</span>,<span class="str">'diff_max_files'</span>,<span class="str">'diff_max_files INTEGER NOT NULL DEFAULT 100'</span>);</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc6')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 4:types.ts + mappers.ts — Project 加 diffMaxFiles</div>
|
||
<pre><span class="cm">// types.ts:</span> diffMaxFiles: <span class="tp">number</span>;
|
||
<span class="cm">// rowToProject:</span> diffMaxFiles: r.diff_max_files ?? <span class="nu">100</span>,</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc6')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 5:PipelineDeps 加可选 runCheck 注入点(测试用)</div>
|
||
<pre>runCheck?: (cmd:<span class="tp">string</span>, cwd:<span class="tp">string</span>) => <span class="kw">void</span>;</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc6')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 6:pipeline.ts — verify 之后插入 checks 分项闸</div>
|
||
<pre><span class="kw">if</span>(job.project.checks){
|
||
<span class="kw">let</span> obj={}; <span class="kw">try</span>{obj=JSON.<span class="fn">parse</span>(job.project.checks)}<span class="kw">catch</span>{}
|
||
<span class="kw">for</span>(<span class="kw">const</span> [name,cmd] <span class="kw">of</span> Object.<span class="fn">entries</span>(obj <span class="kw">as</span> Record<string,string>)){
|
||
<span class="kw">try</span>{ (deps.runCheck??<span class="fn">defaultRunCheck</span>)(cmd,wt.dir); }
|
||
<span class="kw">catch</span>(e){ emit({type:<span class="str">'failed'</span>,error:<span class="str">`[${name}]失败`</span>,...}); <span class="kw">return</span>; }
|
||
}
|
||
}</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc6')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind impl">实现</span>步骤 7:pipeline.ts — worktreeDiff 之后插入 diff 体量闸</div>
|
||
<pre><span class="kw">const</span> maxF=job.project.diffMaxFiles??<span class="nu">100</span>;
|
||
<span class="kw">if</span>(maxF><span class="nu">0</span>){
|
||
<span class="kw">const</span> fc=diff.diffSummary.<span class="fn">split</span>(<span class="str">'\n'</span>).<span class="fn">filter</span>(l=>l.<span class="fn">includes</span>(<span class="str">'|'</span>)).length;
|
||
<span class="kw">if</span>(fc>maxF){ emit({type:<span class="str">'failed'</span>,error:<span class="str">`diff超限:${fc}>${maxF}文件`</span>,...}); <span class="kw">return</span>; }
|
||
}</pre>
|
||
</div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc6')"></div><div class="step-con"><div class="step-ttl"><span class="kind run">运行</span>步骤 8:npm test</div><pre>npm test 2>&1 | tail -15</pre></div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc6')"></div><div class="step-con"><div class="step-ttl"><span class="kind run">运行</span>步骤 9:typecheck + build</div><pre>npm run typecheck && npm run build</pre></div></li>
|
||
<li class="step"><div class="chkbox" onclick="ck(this,'tc6')"></div><div class="step-con">
|
||
<div class="step-ttl"><span class="kind commit">提交</span>步骤 10</div>
|
||
<pre>git add src/executor/pipeline.ts src/store/schema.sql src/store/db.ts src/model/types.ts src/store/mappers.ts test/pipeline.test.ts
|
||
git commit -m "feat(pipeline): checks 分项闸 + diff 体量闸 (diffMaxFiles)"</pre>
|
||
</div></li>
|
||
</ul>
|
||
</div></div>
|
||
|
||
|
||
|
||
|
||
<div class="card">
|
||
<div class="card-title">Agent 记忆注入(L1–L4)— 涉及文件</div>
|
||
<div class="grid-3">
|
||
<div>
|
||
<div class="row"><code>src/store/schema.sql</code> <span class="chg mod">改</span> projects 加 agent_rules</div>
|
||
<div class="row"><code>src/store/store.ts</code> <span class="chg mod">改</span> lastRunErrorOf + agentRules</div>
|
||
<div class="row"><code>src/model/types.ts</code> <span class="chg mod">改</span> Project.agentRules</div>
|
||
<div class="row"><code>src/store/mappers.ts</code> <span class="chg mod">改</span> agent_rules 映射</div>
|
||
</div>
|
||
<div>
|
||
<div class="row"><code>src/executor/protocol.ts</code> <span class="chg mod">改</span> JobSpec.context</div>
|
||
<div class="row"><code>src/daemon/orchestrator.ts</code> <span class="chg mod">改</span> claimOne 组装记忆</div>
|
||
<div class="row"><code>src/executor/runner.ts</code> <span class="chg mod">改</span> 3 处 build*Prompt</div>
|
||
<div class="row"><code>src/executor/reviewer.ts</code> <span class="chg mod">改</span> buildReviewPrompt</div>
|
||
</div>
|
||
<div>
|
||
<div class="row"><code>src/executor/cc.ts</code> <span class="chg add">不改</span> settingSources 保持 project</div>
|
||
<div class="row"><code>~/.maestro/agent-global.md</code> <span class="chg add">新建</span> 全局规范模板</div>
|
||
<div class="row">测试:build*Prompt 纯函数单测(有/无各记忆字段时段落正确)</div>
|
||
<div class="row">验证:failed→retry 任务 transcript 含「上次失败原因」</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div><!-- /.section s-impl -->
|
||
|
||
<!-- /s-design -->
|
||
</div><!-- /.content -->
|
||
</div><!-- /.main-layout -->
|
||
</body>
|
||
</html>
|