From e9e389822e4234fdabba580c5b08c192fac8cf4e Mon Sep 17 00:00:00 2001 From: yanghl Date: Wed, 22 Jul 2026 20:58:19 +0800 Subject: [PATCH] perf(coding): tag 全量预取 + 默认分支 memo,砍掉纯查询类子代理会话 --- workflows/coding.mjs | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 18 deletions(-) diff --git a/workflows/coding.mjs b/workflows/coding.mjs index e5b4ed6..9eedc72 100644 --- a/workflows/coding.mjs +++ b/workflows/coding.mjs @@ -1986,6 +1986,65 @@ function checkTagExistsPromptM(tagName) { ].join('\n') } +// ── 完成态 tag 全量预取(省会话优化 1a)─────────────────────────────────────── +// 动机:req-done/ / fe-code-done/ / milestone/ 的存在性查询原本**每次一个子代理** +// (featureLoop 入口 dedup 每 REQ 一次、phase1 dedup 每 FE 一次、runMilestone 每模块一次)。 +// Workflow 脚本自身无 fs / 无 shell(沙箱限制),git 只能经子代理跑——省不掉"手",但可以省"次数": +// 开跑时**一次**拉回全表,之后全部变成内存查表。tag 是仓库级 ref(worktree 间共享),故一份全局 +// 快照对主根与所有 lane 同时成立。 +// +// 一致性:运行中新打的 tag 由 JS 在创建成功后 noteTag() 同步进快照——脚本是唯一写入方, +// 单线程 await 串行记账,不会漏。预取失败(子代理 null / 返回不合形)→ tagSnapshot 保持 null, +// 全部调用点自动回退到"每次一个子代理查"的现行路径,语义一字不差、不新增 halt 点。 +const TAG_LIST_SCHEMA = { type:'object', additionalProperties:false, + required:['tags'], properties:{ tags:{ type:'array', items:{ type:'string' } } } } + +function listAllTagsPromptM() { + return [ + '# 列出全部完成态 tag(一次性预取,供上层内存查表)', + microStepContract(ROOT), + '', + `跑 \`git -C ${ROOT} tag -l "req-done/*" "fe-code-done/*" "milestone/*"\`(\`git tag -l\` 接受多个 pattern 作位置参数,一次列全三类)。`, + '把 stdout 按行切分,去掉空行与行首尾空白,原样返回**完整 tag 名**(含 `req-done/` 等前缀),不要截断前缀、不要排序、不要去重加工。', + '一个 tag 都没有(仓库尚无完成态)→ 返回空数组 `{ "tags": [] }`,这是合法结果,不是失败。', + '## 输出(TAG_LIST_SCHEMA)', + '- `{ "tags": ["req-done/REQ-001", "milestone/order_mgmt", ...] }`', + ].join('\n') +} + +let tagSnapshot = null // Set | null(null = 未预取 / 预取失败 → 回退逐项查) + +// 预取一次。失败只 log 不抛——回退路径完全等价于本优化之前的行为。 +async function primeTagSnapshot() { + if (tagSnapshot) return + const r = await agent(listAllTagsPromptM(), {label:'tags:prefetch', phase:'Router', schema: TAG_LIST_SCHEMA}) + if (!r || !Array.isArray(r.tags)) { + log('tag 预取失败(子代理无返回 / 结果不合形)——回退逐项 tag 查询,行为同优化前') + return + } + tagSnapshot = new Set(r.tags.map(t => String(t).trim()).filter(Boolean)) + log(`tag 预取: ${tagSnapshot.size} 个完成态 tag 进入内存快照(后续 dedup 查询零子代理开销)`) +} + +// 查表。返回 null = 快照不可用,调用方须回退到子代理查询。 +function tagKnown(name) { return tagSnapshot ? tagSnapshot.has(name) : null } + +// 记账:tag 创建成功后调用,保持快照与仓库一致(快照不可用时是 no-op)。 +function noteTag(name) { if (tagSnapshot) tagSnapshot.add(name) } + +// ── 默认分支 memo(省会话优化 1b)───────────────────────────────────────────── +// `refs/heads/main|master` 是整程不变量且全仓共享,但原本有 5 个调用点各起一次子代理探测 +// (runBranchSetup / runMilestone / runCrossModule / prepareParallelWave / fe-overlap), +// 每个模块至少查 3 次。首次探测结果整程复用;探测失败**不缓存**,下次调用重试, +// 保持原有 agentR 的 halt 语义不变。 +let defaultBranchMemo = null +async function getDefaultBranch(label) { + if (defaultBranchMemo) return defaultBranchMemo + const def = await agentR(detectDefaultBranchPromptM(), {label, phase: 'Milestone', schema: DEFAULT_BRANCH_SCHEMA}) + if (def?.branch) defaultBranchMemo = def + return def +} + function createTagPromptM(phaseId, fe) { return [ `# 打 annotated tag \`milestone/${phaseId}\``, @@ -2171,7 +2230,7 @@ async function runBranchSetup(module, c) { const branch = fe ? 'frontend-phase' : `module-${id}` const lbl = (k) => `branch:${k}:${id}` - const def = await agentR(detectDefaultBranchPromptM(), {label: lbl('default'), phase: 'Milestone', schema: DEFAULT_BRANCH_SCHEMA}) + const def = await getDefaultBranch(lbl('default')) if (c.lane != null) { // lane 模式:先确保 lane worktree 存在且在目标分支(幂等三分支见 prompt),树才存在、脏树检查才有意义。 @@ -2244,8 +2303,8 @@ async function runMilestone(module) { log(`milestone: ${phaseId} 自动提交脏工作树残留(${rec.detail || ''})`) } - // step 2: detect default branch - const def = await agentR(detectDefaultBranchPromptM(), {label: lbl('default'), phase: 'Milestone', schema: DEFAULT_BRANCH_SCHEMA}) + // step 2: detect default branch(memo,整程只探测一次——见 getDefaultBranch) + const def = await getDefaultBranch(lbl('default')) // step 3: merge (idempotent — skip if already an ancestor) // merge 冲突保持**硬 halt**:自动 abort/stash/改文件均不安全,把树留给人工(设计原则不变)。 @@ -2298,9 +2357,12 @@ async function runMilestone(module) { // else: 已是 targetTag → 静默跳过(resume 幂等) // step 6: annotated tag (idempotent — tag exists 时静默跳过) - const tag = await agentR(checkTagExistsPromptM(targetTag), {label: lbl('tag?'), phase: 'Milestone', schema: EXISTS_SCHEMA}) - if (!tag.exists) { - await runAction(g => createTagPromptM(phaseId, fe) + g, {site:`milestone-tag:${phaseId}`, grp:'Milestone', label: lbl('tag')}) + const snapTag = tagKnown(targetTag) // 1a:快照可用则零子代理 + const tagExists = snapTag !== null ? snapTag + : (await agentR(checkTagExistsPromptM(targetTag), {label: lbl('tag?'), phase: 'Milestone', schema: EXISTS_SCHEMA})).exists + if (!tagExists) { + const tr = await runAction(g => createTagPromptM(phaseId, fe) + g, {site:`milestone-tag:${phaseId}`, grp:'Milestone', label: lbl('tag')}) + if (tr?.success !== false) noteTag(targetTag) // 1a 记账(同上,显式判 success) } log(`milestone: ${phaseId} → ${targetTag}`) @@ -2313,7 +2375,7 @@ async function runCrossModule(module, c) { const id = module?.id ?? '' const lbl = (k) => `xmod:${k}:${id}` - const def = await agentR(detectDefaultBranchPromptM(), {label: lbl('default'), phase: 'Milestone', schema: DEFAULT_BRANCH_SCHEMA}) + const def = await getDefaultBranch(lbl('default')) const changed = await agentR(collectCrossModuleChangedPromptM(def.branch, c), {label: lbl('diff'), phase: 'Milestone', schema: CHANGED_FILES_SCHEMA}) if (!changed.files.length) { @@ -2424,9 +2486,14 @@ async function featureLoop(items, phase, c, feStage = 'all') { if (useSpecPrefetch && items.length >= 2) { // 直用 agent() 而非 agentR:thunk 内 throw 会被 parallel() 折成 null 丢失诊断;null 槽位留给 // 主链 agentR 重查,那里的 halt 语义与现行串行路径逐字一致。 - const checks = await parallel(items.map(id => () => - agent(checkReqDoneTagPromptM(id, c), {label:`donecheck:${phase}:${id}`, phase: grp, schema: EXISTS_SCHEMA}))) - checks.forEach((r, i) => { if (r) doneById.set(items[i], r.exists) }) + // 1a:快照可用 → 全批 dedup 零子代理(原本每项一次,items 多时这里就是一整排会话)。 + if (tagSnapshot) { + items.forEach(id => doneById.set(id, tagKnown(`req-done/${id}`))) + } else { + const checks = await parallel(items.map(id => () => + agent(checkReqDoneTagPromptM(id, c), {label:`donecheck:${phase}:${id}`, phase: grp, schema: EXISTS_SCHEMA}))) + checks.forEach((r, i) => { if (r) doneById.set(items[i], r.exists) }) + } // 只对"确认未完成"的项预生成;tag check 失败(缺席)的项不预生成——其完成态未知,预生成可能 // 白烧一次 spec 子代理,且主链重查后若已完成会整段 skip。 const todo = items.filter(id => doneById.get(id) === false) @@ -2462,6 +2529,8 @@ async function featureLoop(items, phase, c, feStage = 'all') { const isDone = async (id) => { const cached = doneById.get(id) if (cached !== undefined) return cached + const snap = tagKnown(`req-done/${id}`) // 1a:快照可用则零子代理 + if (snap !== null) return snap const done = await agentR(checkReqDoneTagPromptM(id, c), {label:`donecheck:${phase}:${id}`, phase: grp, schema: EXISTS_SCHEMA}) return done.exists } @@ -2517,15 +2586,27 @@ async function featureLoop(items, phase, c, feStage = 'all') { } // approve 后落地 dedup 真值:req-done/ tag(失败经仲裁重试,确不可恢复才 halt)。 // tag 全仓共享,恒以模块 ctx c 打(Phase F 在合并回模块分支**之后**才打,见 featureBatchRun 批后收口)。 - const tagDone = async (id) => runAction(g => createReqDoneTagPromptM(id, phase, c) + g, { - site:`req-done-tag:${phase}:${id}`, grp, label:`reqdone:${phase}:${id}`, dec: c.decisions, root: c.root, - }) + const tagDone = async (id) => { + const r = await runAction(g => createReqDoneTagPromptM(id, phase, c) + g, { + site:`req-done-tag:${phase}:${id}`, grp, label:`reqdone:${phase}:${id}`, dec: c.decisions, root: c.root, + }) + // 1a 记账:保持内存快照与仓库一致。显式判 success——当前 runAction 未开 allowContinue, + // 走到这里恒为真;若日后给本调用点加了 allowContinue,失败放行时**绝不能**误记为已打 tag。 + if (r?.success !== false) noteTag(`req-done/${id}`) + return r + } // overlap phase1 中间完成态 tag:fe-code-done/(组件代码 + jsdom 绿)。 - const tagFeCodeDone = async (id) => runAction(g => createFeCodeDoneTagPromptM(id, c) + g, { - site:`fe-code-done-tag:${id}`, grp, label:`fecodedone:${id}`, dec: c.decisions, root: c.root, - }) + const tagFeCodeDone = async (id) => { + const r = await runAction(g => createFeCodeDoneTagPromptM(id, c) + g, { + site:`fe-code-done-tag:${id}`, grp, label:`fecodedone:${id}`, dec: c.decisions, root: c.root, + }) + if (r?.success !== false) noteTag(`fe-code-done/${id}`) // 1a 记账(同上,显式判 success) + return r + } // phase1 入口 dedup:fe-code-done/ 已存在 → 本 FE phase1 已完成,跳过(phase2 用 req-done dedup,走 isDone)。 const isPhase1Done = async (id) => { + const snap = tagKnown(`fe-code-done/${id}`) // 1a:快照可用则零子代理 + if (snap !== null) return snap const r = await agentR(checkFeCodeDoneTagPromptM(id, c), {label:`fecodecheck:${phase}:${id}`, phase: grp, schema: EXISTS_SCHEMA}) return r.exists } @@ -3150,6 +3231,9 @@ async function runBehaviorGate(feItems, c) { } phase('Router') +// 完成态 tag 全量预取(1a):放在 Router 之前——其后 featureLoop 的每 REQ / 每 FE dedup、 +// runMilestone 的 tag 存在性查询全部走内存快照。失败静默回退逐项查(见 primeTagSnapshot)。 +await primeTagSnapshot() // Router 语义断言(feItems/reqs 互斥)+ id 形状硬约束(防 shell 注入:id 直接拼入 `git ... ${id}`)。 // id 形状(assertSafeId)是**安全护栏**——失败立即硬 halt,绝不重试绕过。 // reqs/feItems 互斥违例可由仲裁带 guidance 重跑 router 纠正(绝对上限 ADJUDICATE_MAX)。 @@ -3552,7 +3636,7 @@ async function prepareParallelWave(wave) { return null } try { - const def = await agentR(detectDefaultBranchPromptM(), { label: 'wave:default', phase: 'Milestone', schema: DEFAULT_BRANCH_SCHEMA }) + const def = await getDefaultBranch('wave:default') const mc = makeCtx() // (1) 主根复位(D2 + 补 3c):module-* 分支必须未被主根持有(worktree add 才不 fatal),且兄弟 // milestone 的 merge 要落在默认分支主根上。脏树恢复的目标分支 = **当前 HEAD 分支**(传默认分支 @@ -3717,7 +3801,7 @@ if (overlapOn) { // 后端全部 done + phase1 done → phase2:reconcile(合 default→frontend-phase + 移除 phase1 worktree)→ // 标准 runModule(feStage='e2e')(主根全栈,skeleton 幂等跳过,behaviorGate/testGate/milestone 原样)。 phase('Frontend') - const def = await agentR(detectDefaultBranchPromptM(), { label:'fe-overlap:default', phase:'Milestone', schema: DEFAULT_BRANCH_SCHEMA }) + const def = await getDefaultBranch('fe-overlap:default') await bestEffortAction(removeLaneWorktreePromptM(laneRoot('frontend-phase')), { label:'fe-overlap:rm-worktree', phase:'Milestone', okMsg:'fe-overlap: 已移除 phase1 worktree', failTag:'fe-overlap phase1 worktree 移除' }) const rec = await agentR(mergeDefaultIntoFrontendPromptM('frontend-phase', def.branch), -- libgit2 0.22.2