feat: 同步引擎+Agent配置+依赖自动落位+看板大改(预览/md渲染/筛选/徽章组)

后端:
- src/sync/todo-sync.ts: todo.json 单向同步引擎(导入=首次同步,幂等,source_ref 映射,
  subs 复杂度修正为 easy,旧侧 done 历史事实优先 forceDone)
- 依赖自动落位:ready 意图按 deps 落位 blocked,依赖全 done 自动放行,
  daemon 启动 reconcileDeps 对账,手动绕过会弹回
- 新 API: PATCH projects/:id(autonomy/concurrency)、POST :id/sync、GET /api/agents、
  PATCH tasks/:id(title/priority/complexity 重置)
- daemon 定时同步(MAESTRO_SYNC_INTERVAL 默认 300s) + project.synced 事件
- priority 语义翻转: P0 最高/P1 默认/P2 最低,取值限 0..2,排序/映射/MCP/CLI 全跟进
- 静态服务发 no-cache 头(修浏览器吃旧 CSS/JS)
- schema 迁移: tasks.source_ref / projects.last_sync_at(ensureColumn 平滑升级旧库)

看板:
- 全屏预览模式(94vh 读完整方案+就地裁决,Esc/遮罩/裁决自动关闭)
- 产出 markdown 渲染为 HTML(零依赖渲染器,转义优先)
- 任务树筛选(复杂度/状态分组/关键字)+ 顶栏徽章组(待审批/可执行/执行中,hover 展开)
- 依赖可视化:详情 DEPS 区块 + 行内⛓等依赖 + 锚点跳转定位
- 按钮收敛:提交评审/编辑产出移除(CC 经 MCP 操作),界面只留用户动作
- Agent 执行面板 + 项目配置(并发/工作模式)+ 同步按钮

测试:21 个全过(新增 sync 幂等/迁移/patch/依赖落位/对账幂等)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-12 23:58:28 +08:00
parent c6e66baa19
commit fa472a06a7
18 changed files with 1942 additions and 368 deletions
+48
View File
@@ -1,12 +1,56 @@
import { statSync } from 'node:fs';
import type { FastifyInstance } from 'fastify';
import { Store } from '../store/index.js';
import { buildServer, attachWebSocket } from '../api/server.js';
import { registerStatic } from '../api/static.js';
import { loadConfig } from './config.js';
import { syncProject, todoJsonPath } from '../sync/todo-sync.js';
/**
* 定时同步:每轮对 active 项目检查 todo.json 的 mtime,比 last_sync_at 新才 sync。
* 间隔由 MAESTRO_SYNC_INTERVAL(秒)控制,默认 300,0=关闭。错误只记日志,不崩 daemon。
*/
function startSyncLoop(store: Store, app: FastifyInstance): NodeJS.Timeout | null {
const intervalSec = Number(process.env.MAESTRO_SYNC_INTERVAL ?? 300);
if (!Number.isFinite(intervalSec) || intervalSec <= 0) {
app.log.info('定时 todo.json 同步已关闭(MAESTRO_SYNC_INTERVAL=0');
return null;
}
const tick = (): void => {
try {
for (const p of store.listProjects()) {
if (p.status !== 'active') continue;
let mtimeIso: string;
try {
mtimeIso = statSync(todoJsonPath(p.repoPath)).mtime.toISOString();
} catch {
continue; // 无 todo.json:跳过
}
if (p.lastSyncAt && mtimeIso <= p.lastSyncAt) continue;
try {
const r = syncProject(store, p.id);
app.log.info(
`定时同步「${p.name}」:created=${r.created} doneAdvanced=${r.doneAdvanced} skipped=${r.skipped} warnings=${r.warnings.length}`,
);
} catch (e) {
app.log.error(`定时同步「${p.name}」失败:${(e as Error).message}`);
}
}
} catch (e) {
app.log.error(`定时同步轮询失败:${(e as Error).message}`);
}
};
const timer = setInterval(tick, intervalSec * 1000);
timer.unref();
app.log.info(`定时 todo.json 同步已启用:每 ${intervalSec}s 一轮`);
return timer;
}
/** maestrod:核心 daemon。Phase 1 = Store + REST/WS API(手动驱动;编排器在 Phase 2 接入)。 */
async function main(): Promise<void> {
const cfg = loadConfig();
const store = new Store(cfg.dbFile);
const rec = store.reconcileDeps(); // 启动对账:ready↔blocked 按依赖纠正存量数据
const app = buildServer({ store, logger: true });
registerStatic(app); // Web 看板(web/ 静态文件)
@@ -14,9 +58,13 @@ async function main(): Promise<void> {
await app.listen({ host: cfg.host, port: cfg.port });
app.log.info(`maestrod 就绪 · db=${cfg.dbFile} · http://${cfg.host}:${cfg.port} · ws ${cfg.host}:${cfg.port}/ws`);
if (rec.blocked || rec.released) app.log.info(`依赖对账:转入等依赖 ${rec.blocked} · 放行可执行 ${rec.released}`);
const syncTimer = startSyncLoop(store, app);
const shutdown = async (): Promise<void> => {
app.log.info('收到退出信号,关闭中…');
if (syncTimer) clearInterval(syncTimer);
await app.close();
store.close();
process.exit(0);