總結

Agent: programmer (programmer)
Confidence: 51%
Cost: $1.2740
Duration: 4m 19s
Task ID: 18bf4720-13eb-446c-af52-8cd48a4e9c31


所有修改已完成並交付 reviewer。

總結

完成的三項修改

Issue 嚴重度 修改
#1 executeTask() 缺少 worktree 清理 CRITICAL isWorktreeCreator flag + finally block 呼叫 removeTaskWorktree()
#2 缺少定期 cleanup CRITICAL handleTick() 每 30 分鐘呼叫 cleanupOrphanWorktrees()
#3 bot-tools-server 重複查詢 SUGGESTED 合併兩次 parent lookup 為一次 parentTask 變數

tsgo –noEmit:通過(exit code 0)

已 dispatch 給 reviewer(task 6df31e6b)進行 code review。

—HANDOFF—
TO: reviewer
INTENT: handoff
ARTIFACT_TYPE: code-change
SUMMARY: Phase 2 worktree resource leak 修復 — executeTask() finally cleanup + heartbeat 定期清理 + parent lookup 合併

Phase 2 Worktree Integration — 架構分析報告

Agent: reviewer (reviewer)
Confidence: 65%
Cost: $0.7702
Duration: 2m 14s
Task ID: df024484-dde7-4986-a921-60298f4b3117


好的,我已完成所有程式碼的深入閱讀。以下是我的完整 Code Review 報告。


Phase 2 Worktree Integration — 架構分析報告

概述

Phase 2 的核心改動邏輯正確:shouldUseWorktree() 根據 capability 判斷是否建立 worktree,executeTask() 中有 graceful fallback,dispatch_task 正確繼承 parent 的 worktree。但有 1 個必須修的 bug2 個中風險設計問題 需要關注。

分析詳情

1. 🔴 BUG — Worktree 永遠不會被清理(Leak)

嚴重度:5/5

worktree-manager.ts:225cleanupOrphanWorktrees() 呼叫 removeTaskWorktree(wt.shortId),但 removeTaskWorktree() 的參數名為 taskId,內部會再做一次 getShortId(taskId)(即 taskId.slice(0, 8))。

當傳入的已經是 8 字元的 shortId(如 abc12345),getShortId() 會截取前 8 字元,恰好等於自身,所以路徑計算結果正確——這裡不是 bug

但更嚴重的問題是:在正常 task 完成流程中,archiveTask() 完全沒有呼叫 removeTaskWorktree()

  • worker-scheduler.ts:1291 archiveTask() 只做 JSONL append + queue 清除
  • executeTask() 完成後只做 report、stats、archive,沒有任何 worktree cleanup
  • 唯一的清理機制是 cleanupOrphanWorktrees(),但它從未被任何地方呼叫

結果:每個 code agent task 都會建立一個 worktree,但永遠不會被主動清理。MAX_WORKTREES=4 後就無法再建立,worktree 只能靠 TTL 2 小時後被標記為 orphaned——但 orphan cleanup 也沒有排程。

建議

  1. executeTask() 的 finally block 中加入 removeTaskWorktree(task.id)(如果 task.worktreePath 存在)
  2. startWorkerScheduler() 或 heartbeat tick 中定期呼叫 cleanupOrphanWorktrees()

2. 🟡 architect 不建 worktree 但有 refactoring capability

嚴重度:3/5

shouldUseWorktree() 檢查 ['code', 'refactoring']。但 architect.json 的 capabilities 包含 "refactoring"

根據變更摘要:「刻意排除 architecture 避免 reviewer 誤建」。但代碼中排除的不是 architecture capability,而是把觸發條件設為 coderefactoring。architect 有 refactoring 但沒有 code——所以 architect 會觸發 worktree 建立

這可能是 intentional(architect 確實會寫 code),但與摘要描述「刻意排除 architecture」矛盾。需確認意圖。

受影響的 agent(會觸發 worktree):

Agent Capability Match
programmer code
backoffice-op code
mcp-tools-op code
site-op code
architect refactoring

3. 🟡 bot-tools-server.ts 中 parent lookup 做了兩次

嚴重度:2/5

bot-tools-server.ts:573bot-tools-server.ts:641 都對同一個 parentTaskId 做了 queue.tasks.find() + findTaskInHistory()。第一次是在 chain depth 計算中,第二次是在 worktree 繼承中。

1
2
3
4
5
6
7
// Line 573: chain depth check
const parentInQueue = queue.tasks.find(t => t['id'] === parentTaskId);
const parent = parentInQueue ?? await findTaskInHistory(parentTaskId);

// Line 641: worktree inheritance (same lookup again)
const parentInQueue = queue.tasks.find(t => t['id'] === parentTaskId);
const parentForWt = parentInQueue ?? await findTaskInHistory(parentTaskId);

findTaskInHistory() 會讀整個 history.jsonl 做反向掃描。在高頻 dispatch 場景下,同一個 parentTaskId 被讀了兩次。

建議:把 parent lookup 提到前面做一次,後面共用。

4. 🟢 正面設計評價

以下設計決策值得肯定:

  • Dynamic import worktree-manager.jsworker-scheduler.ts:477):避免未啟用 worktree 時 import 全部依賴,且 import 失敗不影響執行
  • Graceful fallbackworker-scheduler.ts:483-489):worktree 建立失敗就 fallback 到 LIGHTWEIGHT_CWD,不會阻斷任務
  • MCP 路徑不跟 worktreeworker-scheduler.ts:496):MCP config 是靜態的,正確保持指向 LIGHTWEIGHT_CWD
  • shouldUseWorktree() 先檢查 task.worktreePathworker-scheduler.ts:352):已繼承 worktree 的就不重建,避免浪費
  • worktree 繼承邏輯放在 task push 之前bot-tools-server.ts:639-647):確保 queue 中的 task 已帶有 worktree 資訊

風險評估

  • 🔴 高風險:Worktree 從不清理 → 4 個 worktree 後 code agents 全部降級到 LIGHTWEIGHT_CWD(功能性退化)
  • 🟡 中風險:architect 意外觸發 worktree 建立(可能是 intentional,需確認)
  • 🟡 中風險:parent lookup 重複 I/O(效能)
  • 🟢 低風險:type annotation 使用 string | null 而非 string | undefined(與 AgentTask interface 不一致但無功能影響)

驗收結論

❌ 驗收不通過

必須修復 Issue #1(worktree 清理缺失)後才能 commit。這是一個 resource leak,在生產環境會快速耗盡 worktree quota。

需要修正的內容

必須修(阻塞 commit)

  1. worker-scheduler.tsexecuteTask() 中加入 worktree cleanup

    • 位置:executeTask() 函數末尾(所有 try/catch 結束後)
    • 在 task 完成或失敗後,如果 task.worktreePath 存在且 task 不是從 parent 繼承的 worktree(即 task 是 worktree 的建立者),呼叫 removeTaskWorktree(task.id)
    • 注意:pipeline 中下游繼承的 worktree 不應由下游清理——只有建立者(最上游 code agent)負責清理
  2. worker-scheduler.ts 或 heartbeat — 定期呼叫 cleanupOrphanWorktrees()

    • 建議在 handleTick() 中每 N 次 tick 呼叫一次,作為安全網

建議改善(不阻塞 commit)

  1. bot-tools-server.ts — 合併重複的 parent lookup
  2. 確認 architect 是否應該建立 worktree——如果不應該,把 refactoringCODE_MODIFY_CAPABILITIES 移除,或在 shouldUseWorktree() 中顯式排除 architecture capability

—HANDOFF—
TO: programmer
INTENT: feedback
ARTIFACT_TYPE: review
SUMMARY: Phase 2 worktree integration 驗收不通過 — worktree 建立後從未被清理(resource leak),需在 executeTask() 和 heartbeat 中加入 cleanup 邏輯

完成摘要

Agent: programmer (programmer)
Confidence: 44%
Cost: $1.1447
Duration: 3m 40s
Task ID: 1478828d-7215-420d-9d15-8a88d288d63f


完成摘要

所有 Phase 2 改動已實施完畢:

檔案 改動量 說明
src/agents/worker-scheduler.ts +31 行 AgentTask 欄位、shouldUseWorktree()、worktree setup、cwd 切換
src/mcp/bot-tools-server.ts +14 行 task type 欄位、初始值、worktree 繼承

驗證結果tsgo --noEmit 通過,無型別錯誤。

已交付:dispatch 給 reviewer(task df024484),等待 code review。

—HANDOFF—
TO: reviewer
INTENT: handoff
ARTIFACT_TYPE: code-change
SUMMARY: Phase 2 worktree integration — worker-scheduler cwd 指向 worktree + bot-tools-server worktree 繼承

調查完成

Agent: programmer (programmer)
Confidence: 65%
Cost: $1.0970
Duration: 3m 32s
Task ID: fe10c95f-c2ff-4f27-9bfa-a284dfa20c70


★ Insight ─────────────────────────────────────

1. shouldUseWorktree() 的 capability 匹配策略是核心設計決策
任務 spec 建議 CODE_CAPABILITIES = ['code', 'architecture', 'refactoring'],但 reviewer 有 architecture capability。如果 reviewer 被獨立派工(非 pipeline),會誤建新 worktree。我的報告建議縮小為 ['code', 'refactoring'],讓 programmer(code)和 architect(refactoring)匹配,reviewer 只能繼承。

2. mcp-code-agent.json 路徑不需要改
mcp-code-agent.jsondata/agent-workspace/ 下(不在 git 追蹤中),且其內容使用相對路徑 ./node_modules/.bin/cclsp。因為 worktree 中 node_modules/ 是 symlink,保持 mcpConfig 指向 LIGHTWEIGHT_CWD 是最安全的做法。

3. bot-tools-server.ts 的 parent 查找可以復用
dispatch_task handler 已有 parentTaskId 查找 parent 的邏輯(572-593 行),worktree 繼承可以直接在這個區塊中加入,避免重複 I/O。

─────────────────────────────────────────────────

調查完成

報告已寫入 soul/agent-reports/programmer/phase2-worktree-investigation.md

關鍵發現摘要

項目 結論
可行性 完全可行,3 個檔案、~40 行改動
shouldUseWorktree capabilities 建議用 ['code', 'refactoring'](不含 architecture),避免 reviewer 誤建 worktree
mcp-code-agent.json 不需要改,保持指向 LIGHTWEIGHT_CWD
tests/unit/agent-config.test.ts 不需要改(現有測試不涉及 AgentTask)
bot-tools-server.ts 需要在 task type 中加入欄位 + 在 if (parentTaskId) 區塊加入繼承邏輯
向後相容 所有新欄位都是 optional,queue.json 和 history.jsonl 自然相容

主要風險

  1. reviewer architecture capability 問題 — 需要在 shouldUseWorktree() 中精確處理
  2. cclsp MCP 在 worktree 中的行為 — 非致命,但值得測試
  3. Phase 2 不清理 worktree — 靠 TTL 自動清理,需確認 cleanupOrphanWorktrees 有被定期呼叫

由於我是 observer 模式,不執行代碼修改。如需執行實際改動,請以 executor 角色重新派工給 programmer。

完成摘要

Agent: secretary (secretary)
Confidence: 76%
Cost: $0.3073
Duration: 2m 20s
Task ID: e722f13f-db61-4c0d-9628-984de5a10e3d


流水線收尾完成。

完成摘要

項目 結果
Type check tsgo --noEmit 通過
測試 90 個測試檔案,1108 個測試全數通過
Commit 0087dd5fix(agents): resolve WorktreeInfo taskId/shortId semantic confusion
Push 已推送至 main
變更檔案 src/agents/worktree-manager.ts(新建,651 行)、tests/unit/worktree-manager.test.ts(新建)

★ Insight ─────────────────────────────────────
流水線收尾的關鍵紀律:pre-push hook 自動跑了完整測試套件(1108 tests),確保不會把壞掉的 code 推上 remote。這個 programmer → reviewer → secretary 的三段式 pipeline 讓每個角色專注自己的職責——programmer 寫、reviewer 審、secretary 確認編譯+測試後 commit/push。注意新建的 worktree-manager.test.ts 的 16 個測試也在完整測試回合中通過了。
─────────────────────────────────────────────────

reviewer — 2026-02-26

Agent: reviewer (reviewer)
Confidence: 27%
Cost: $0.3210
Duration: 1m 27s
Task ID: 96f39577-071c-437a-a0bc-fdf8e663e40f


驗收完成,結果摘要:

全部通過 (7/7),已交付 secretary (task e722f13f) 進行 commit。

關鍵修正點:

  1. WorktreeInfo 新增 shortId 欄位,語義分離清楚
  2. listActiveWorktrees() 誠實回傳 taskId: ''(無法還原完整 UUID),shortId 正確填入
  3. cleanupOrphanWorktrees() 改用 wt.shortId(修復了原本會用空字串去刪除的 bug)
  4. 測試覆蓋完整,16 tests 全過

—HANDOFF—
TO: secretary
INTENT: handoff
ARTIFACT_TYPE: review
SUMMARY: WorktreeInfo taskId/shortId 語義修正驗收全數通過,交付 commit

完成狀態

Agent: programmer (programmer)
Confidence: 44%
Cost: $0.8678
Duration: 3m 17s
Task ID: 6f7ce403-b2f3-4792-b177-c6ad3e28a0c4


任務完成。以下是最終摘要:

完成狀態

驗收項目 狀態
WorktreeInfoshortId 欄位
createTaskWorktree 回傳正確的 taskId + shortId
listActiveWorktrees 回傳 taskId: '' + 正確 shortId
getWorktreeForTask 回傳正確的 taskId + shortId
cleanupOrphanWorktrees 使用 wt.shortId
tsgo --noEmit 通過
16 tests passed
dispatch 給 reviewer ✅ (96f39577)

—HANDOFF—
TO: reviewer
INTENT: handoff
ARTIFACT_TYPE: code-change
SUMMARY: WorktreeInfo 新增 shortId 欄位,修正 listActiveWorktrees 的 taskId/shortId 語義混淆

Worktree Manager Phase 1 — 架構分析報告

Agent: reviewer (reviewer)
Confidence: 60%
Cost: $0.4743
Duration: 1m 53s
Task ID: b9410c3b-3f25-4b48-b140-b1b4ad9ecb24


好的,所有 import 路徑和模式都符合專案慣例。現在我有足夠的資訊來撰寫完整的 Code Review 報告。


Worktree Manager Phase 1 — 架構分析報告

概述

src/agents/worktree-manager.ts 實作品質良好,遵循專案慣例(ESM import、Result<T> pattern、logger),execFile(非 exec)選用正確可防止 shell injection。測試覆蓋率合理(16 tests 涵蓋正常路徑、邊界、fallback)。整體是一個乾淨的 Phase 1 交付。但發現 3 個需要修正的問題4 個建議改善項


分析詳情

問題 1:TaskId 碰撞風險 — shortId 只取 8 字元(重要度:3/5)

  • 位置worktree-manager.ts:43-45
  • 問題getShortId() 只取 taskId.slice(0, 8)。如果兩個 UUID 的前 8 字元相同(概率 ~1/4.3 billion per pair,但累積多次後不可忽略),會產生路徑碰撞 → git worktree add 失敗(branch 已存在)。
  • 評估createTaskWorktree 的 try/catch 會捕獲 git 錯誤並回傳 fail(),所以不會造成數據損壞。但錯誤訊息 "Failed to create worktree: branch already exists" 會很令人困惑。
  • 結論可接受(Phase 1 不太可能碰到),但建議在 createTaskWorktree 開頭加一個 existence check:
1
2
3
// 在 git worktree add 之前
const existing = await getWorktreeForTask(taskId);
if (existing) return fail(`Worktree already exists for task ${shortId}`);

問題 2:listActiveWorktrees()createTaskWorktree() 的 TOCTOU 競爭(重要度:2/5)

  • 位置worktree-manager.ts:74-79
  • 問題:先 listActiveWorktrees() 檢查數量,再 execGit(worktree add)。兩步之間如果另一個 process 同時建立 worktree,有可能超過 MAX_WORKTREES 限制。
  • 評估:Phase 1 尚未整合到 scheduler,且同類 agent 目前有序列化保護(concurrency=1),所以實際不會發生。Phase 4 放開 concurrency 時需要加 file-based lock 或 atomic counter。
  • 結論Phase 1 可接受,Phase 4 前必須解決。 建議在 spec 或 TODO 中記錄。

問題 3:listActiveWorktrees() 回傳的 taskId 是 shortId,而非完整 UUID(重要度:4/5)

  • 位置worktree-manager.ts:188, 197-198
  • 問題listActiveWorktrees() 從目錄名稱解析出的 taskId 只是 shortId(8 字元),但 WorktreeInfo.taskId 的型別是 string,語義上應該是完整的 taskId。這導致:
    • cleanupOrphanWorktrees() 呼叫 removeTaskWorktree(wt.taskId) 時傳入 shortId → getShortId() 再 slice 一次只取前 8 字元 → 剛好碰巧正確(8 字元 slice(0,8) 仍是 8 字元)。
    • 但語義上很脆弱——如果 shortId 長度改變,整個鏈路會斷。
    • 註釋 // Note: this is the shortId, not the full taskId 已經承認了這個問題。
  • 結論必須修正。 要麼:
    • (A) WorktreeInfo 增加 shortId 欄位,taskId 在無法還原時設為 shortId 但明確標記
    • (B) 改名為 shortId 而非混用 taskId

這是本次 review 最重要的問題——型別系統沒有保護到這個語義差異,未來 Phase 2 整合 scheduler 時很容易踩坑。

問題 4:removeTaskWorktree 的外層 try/catch 可能是死碼(重要度:2/5)

  • 位置worktree-manager.ts:130-153
  • 分析:外層 try/catch 包裹了整個函數體,但內部已經有:
    • git worktree remove 的 try/catch(fallback 到 rm + prune)
    • git branch -d 的 try/catch(容忍失敗)
    • logger.info 是 async 但不太可能 throw
  • 結論:外層 catch 實際上只會捕獲 rm()execGit(['worktree', 'prune']) 在 fallback 路徑中的異常。這是合理的防禦編程,保留。

測試品質評估

測試檔tests/unit/worktree-manager.test.ts(260 行,16 tests)

面向 評分 說明
Mock 設定 ✅ 良好 vi.mock 在 top-level,mock node:child_processnode:fs/promises、logger
覆蓋率 ✅ 良好 正常路徑 + 錯誤路徑 + fallback + 邊界條件
隔離性 ✅ 良好 beforeEach 清 mock,不依賴磁碟
缺少的測試 ⚠️ 1) 未測試 symlink 目標路徑是否正確(只驗了呼叫次數)
2) 未測試 getShortId 對非 UUID 輸入的行為
3) 未測試 cleanupOrphanWorktrees 呼叫 removeTaskWorktree 時傳入的是 shortId 而非完整 taskId

與架構 Spec 的符合度

Spec 要求 實作狀態 備註
createTaskWorktree() 建立 worktree + symlinks ✅ 完全符合
removeTaskWorktree() 清理 + fallback ✅ 完全符合
listActiveWorktrees() ✅ 符合 taskId/shortId 語義問題需修正
cleanupOrphanWorktrees() ✅ 符合
Worktree 放 /home/arc/worktrees/ ✅ 符合
Symlink: soul, node_modules, data, .env ✅ 符合
MAX_WORKTREES = 4 ✅ 符合
TTL = 2 小時 ✅ 符合
使用 execFile(非 exec ✅ 符合(超越 spec) Spec 用 exec,實作更安全
不改變現有行為 ✅ 確認 無其他檔案 import worktree-manager
Result<T> pattern ✅ 符合 ok()/fail() 正確使用
ESM import ✅ 符合

風險評估

  • 🔴 高風險taskId vs shortId 語義混淆(問題 3)——Phase 2 整合時會產生 bug
  • 🟡 中風險:TOCTOU 競爭(問題 2)——Phase 4 前需加 lock
  • 🟢 低風險:shortId 碰撞(問題 1)——機率極低,且有 error handling

驗收結論

未通過的驗收項目

問題 3(taskId/shortId 語義混淆)必須修正後才能通過。

具體修正要求:

檔案src/agents/worktree-manager.ts

  1. WorktreeInfo 介面修改(第 33-39 行):將 taskId 改名為 shortId,或新增獨立的 shortId 欄位。建議方案:
1
2
3
4
5
6
7
8
export interface WorktreeInfo {
taskId: string; // 完整 taskId(建立時傳入的)
shortId: string; // 8 字元短 ID(用於路徑和 branch 名稱)
path: string;
branchName: string;
createdAt: string;
status: 'active' | 'removing' | 'orphaned';
}
  1. listActiveWorktrees()(第 188-198 行):因為從目錄名稱無法還原完整 taskId,taskId 欄位填入空字串或 shortId,並在 shortId 欄位填入正確值。

  2. cleanupOrphanWorktrees()(第 222 行):確保 removeTaskWorktree 的參數語義正確——它目前碰巧能工作是因為 getShortId(shortId) === shortId,但這不是可靠的合約。

  3. 測試更新tests/unit/worktree-manager.test.ts 需相應調整 assertion。

其他項目全數通過:

  • src/agents/worktree-manager.ts 存在且編譯通過
  • tests/unit/worktree-manager.test.ts 存在且測試通過
  • /home/arc/worktrees/ 目錄已建立
  • 不影響任何現有檔案(純新增)
  • npx tsgo --noEmit 全專案編譯通過
  • 全部 1108 個測試通過,零回歸
  • taskId / shortId 語義必須釐清

★ Insight ─────────────────────────────────────

  1. **TOCTOU(Time-of-Check-to-Time-of-Use)**是並行系統的經典問題。這裡的 list → check count → create 三步操作之間存在窗口期,在單進程下安全,但 Phase 4 放開 concurrency 後就會暴露。常見解法:file lock、atomic counter、或讓 git 自身的錯誤(branch 已存在)充當 guard。
  2. execFile vs exec 的安全差異:exec 會 spawn shell 並解析字串(容易被 shell injection),execFile 直接 fork + execve,不經過 shell parsing。這是正確的防禦做法,且 programmer 超越了 spec 的要求(spec 寫的是 exec)。
  3. 語義型別 vs 結構型別:TypeScript 的 string 無法區分 “完整 UUID” 和 “8 字元 short ID”。這正是 branded types(type TaskId = string & { __brand: 'TaskId' })要解決的問題。Phase 1 不需要上 branded types,但至少欄位命名要清晰。
    ─────────────────────────────────────────────────

結論:退回修正 taskId/shortId 語義問題。修正範圍小(介面 + 2-3 處賦值),不影響整體架構。修正後重新送審即可通過。

—HANDOFF—
TO: programmer
INTENT: feedback
ARTIFACT_TYPE: review
SUMMARY: Phase 1 worktree-manager 整體良好,退回修正 taskId/shortId 語義混淆問題

Phase 1 完成摘要

Agent: programmer (programmer)
Confidence: 51%
Cost: $1.3376
Duration: 6m 26s
Task ID: a46055b2-c50d-48e9-9c3e-15e3d1388a00


Phase 1 完成摘要

新增檔案

檔案 行數 說明
src/agents/worktree-manager.ts ~210 Worktree 生命週期管理核心模組
tests/unit/worktree-manager.test.ts ~260 16 個單元測試,完整覆蓋

核心 API

函數 用途
createTaskWorktree(taskId) 建立 worktree + 4 個 symlinks
removeTaskWorktree(taskId) 清理 worktree(含 fallback)
listActiveWorktrees() 列出所有活躍 worktree
cleanupOrphanWorktrees() 定期清理 TTL 過期的孤兒
getWorktreeForTask(taskId) 快速查詢單一 task

驗證結果

  • tsgo --noEmit: 通過
  • vitest run: 90 files, 1108 tests 全數通過(含新增 16 個)
  • 零回歸、純新增、不影響現有行為

交付

已 dispatch 給 reviewer(task b9410c3b),等待 code review。

—HANDOFF—
TO: reviewer
INTENT: handoff
ARTIFACT_TYPE: code-change
SUMMARY: Phase 1 Worktree Manager 基礎設施完成,2 新檔案,16 測試通過,等待 review

摘要

Agent: architect (architect)
Confidence: 61%
Cost: $2.2508
Duration: 11m 28s
Task ID: 900f7210-d54c-4970-b0dc-f82b73f7eb6c


★ Insight ─────────────────────────────────────
本次架構設計的三個關鍵決策

  1. Worktree 放 ext4 而非 9p/mnt/d 是 WSL2 透過 Plan 9 protocol 掛載的 NTFS,I/O 性能比 native ext4 慢 3-10 倍。把 worktree 放在 /home/arc/worktrees/ 可以大幅提升 git 操作和 TypeScript 編譯的速度,同時透過 symlink 回指 /mnt/d/.../soul/ 保持 runtime state 共享。

  2. Programmer/Reviewer 不感知 worktree:worktree 的建立和清理完全在 worker-scheduler.tsexecuteTask() 中處理,agent 只知道自己的 cwd 已經被設定好。這避免了在所有 agent 的 systemPrompt 中加入 worktree 知識,降低了 prompt 複雜度和出錯機會。

  3. Per-agent 序列化改為條件式:核心瓶頸是 reservedAgents Set,它讓同類 agent 不能並行。改造後,有 worktree 的 code agent 不再受此限制(因為各自在獨立目錄工作),而 secretary 的 merge 操作仍序列化(因為 merge 到 main 天然需要序列化)。
    ─────────────────────────────────────────────────


技術方案已產出,存放在 soul/agent-reports/architect/git-worktree-isolation-spec-2026-02-26.md

摘要

核心改動(5 個檔案)

檔案 改動
新增 src/agents/worktree-manager.ts Worktree 生命週期管理(建立、symlink、清理、孤兒回收)
src/agents/worker-scheduler.ts executeTask() 指向 worktree cwd;per-agent 序列化改為條件式
src/mcp/bot-tools-server.ts dispatch_task 繼承父 task 的 worktree 路徑
soul/agents/secretary.json 新 systemPrompt(branch + PR 流程)+ 新增 gh pr 權限
src/agents/agent-config.ts AgentTask 增加 worktreePath, branchName 欄位

5 個 Phase 的實施順序

  1. Phase 1 — Worktree 基礎設施(低風險,純新增)
  2. Phase 2 — Worker spawner 改造(中風險,cwd 指向 worktree)
  3. Phase 3 — Secretary workflow 改造(高風險,git 工作流程改變)
  4. Phase 4 — 放開 concurrency 限制(中風險,性能改善)
  5. Phase 5 — Molt 自動化(低風險,便利性)

關鍵風險與緩解

  • WSL2 symlink:✅ 低風險(已驗證跨 fs symlink 正常)
  • node_modules symlink:⚠️ 低風險(需 Phase 1 手動測試 tsgo)
  • 記憶體壓力:⚠️ 中等(7.7GB RAM,最壞 4GB 使用 → 可承受)
  • Merge conflict:⚠️ 中等(secretary concurrency=1 序列化,衝突時回報 CTO)
  • Worktree 清理失敗:✅ 低風險(fallback rm -rf + prune)

開放問題(需 CEO 決策)

  1. PR 自動 merge 還是等人工 approve?
  2. gh CLI 是否已安裝並認證?
  3. blog-writer 是否也需要 worktree?

探索主題

Agent: 探索者 (explorer)
Confidence: 73%
Cost: $0.3868
Duration: 1m 27s
Task ID: 6742cf1d-9112-4765-b227-6bfc04426648


現在我已經收集到足夠的資料了。讓我彙整成探索報告。


探索主題

grammY Bot 框架的中介軟體設計模式:如何寫出高效、可測試的 Telegram Bot 中介軟體?

發現

  1. Composer 是模組化的關鍵:grammY 的 Composer<Context> 不只是分組工具——它本身就是中介軟體物件,支援 .filter() 鏈式 AND 邏輯和 .use() 的樹狀結構(深度優先遍歷)。我們專案目前的 setupMiddleware() 用的是平面的 bot.use() 序列,沒有利用 Composer 的模組化能力。

  2. 測試黃金模式:Transformer + handleUpdate:最實用的測試方法來自 MobinAskari 的 Gist——在 bot.api.config.use() 安裝 transformer 攔截所有 outgoing API 呼叫,搭配 bot.handleUpdate(fakeUpdate) 注入假的 Update 物件,然後 assert outgoingRequests 陣列。完全不需要 mock Telegram 伺服器

  3. Composer 可獨立測試:用 composer.middleware()(mockCtx, () => Promise.resolve()) 就能在隔離環境測試單一中介軟體,不經過整個 middleware stack。這對我們的 authMiddlewarerateLimitMiddleware 等特別有用。

  4. 我們的中介軟體現狀:4 個中介軟體都是裸函數(src/telegram/middleware/),未使用 Composer。順序寫死在 setupMiddleware() 裡。其中 rateLimitMiddleware 用模組層級 Map 存狀態,測試時需注意狀態清理。

  5. Filter 類型窄化:grammY 的 Filter<Context, ":text"> 可以讓 TypeScript 在 middleware 內精確推斷 ctx.message.text 存在,減少 null check。我們現有的 handler 大量使用 ctx.message?.text ?? '' 可以用這個簡化。

有趣的連結

  • grammY middleware = Koa middleware:洋蔥模型,await next() 前後分別是 request/response 階段——errorHandlerMiddleware 正確地利用了這個模式(try/catch 包住 next()),但 loggingMiddleware 只記了 request 階段,若加上 response time 度量會更有價值。
  • grammy-middlewares 套件:提供 ignoreOld()sequentialize() 等現成中介軟體,其中 sequentialize() 與我們的 per-chat queue 概念相近,值得比較。

延伸問題

  • 如何將現有的 4 個 middleware 重構為獨立 Composer + 配套 vitest 測試?具體步驟是什麼?(可派給 architect 評估 + programmer 執行)
  • sequentialize() 官方 plugin 能否取代我們自建的 per-chat queue 邏輯,降低維護成本?

重要性:4/5

能改善專案品質——目前的中介軟體層缺乏測試且結構扁平。採用 Composer 模組化 + transformer 測試模式可以顯著提升可測試性和可維護性,且是低風險重構。

★ Insight ─────────────────────────────────────
核心技術洞察:

  • grammY 的 bot.api.config.use(transformer) 是測試的金鑰——它攔截所有 outgoing API 呼叫,讓你在不碰 Telegram 伺服器的情況下 assert bot 的「行為」而非「實作」
  • Composer 的真正威力在於它是可獨立實例化、獨立測試、獨立組合的中介軟體單元——每個 Composer 就是一個微型 bot
  • 我們專案 authMiddlewarereturn Promise.resolve() 終止模式是正確的 grammY 慣例(不呼叫 next() = 終止 middleware chain)
    ─────────────────────────────────────────────────

完成報告

Agent: secretary (secretary)
Confidence: 44%
Cost: $0.5222
Duration: 2m 38s
Task ID: b977cc6b-c42c-4892-a04f-5f518420d40a


流水線收尾完成。以下是完成摘要:

完成報告

項目 結果
變更檔案 src/metacognition/pattern-detector.ts, src/metacognition/skill-auto-create.ts, src/skills/skill-loader.ts (共 3 檔,與請求一致)
tsgo –noEmit ✅ 通過
測試 ✅ 89 test files, 1092 tests passed
Commit 6f570c3fix(skills): fix bigram tokenizer, raise thresholds, add word-boundary matching
Push d4a23f7..6f570c3 main -> main

注意:原始 commit message header 超過 commitlint 的 100 字元限制(110 字元),已縮短 header 並保留完整 body 說明。

額外觀察:working tree 有大量其他改動(soul/ 資料檔、agent configs、checkpoints 等),均未納入此次 commit,僅嚴格 commit 請求清單中的 3 個檔案。

★ Insight ─────────────────────────────────────
commitlint header-max-length:此專案配置了 commitlint,header 最大 100 字元。建議 reviewer 在提供 commit message 時預先考慮此限制,將詳細描述放在 body 而非 header。
─────────────────────────────────────────────────

reviewer — 2026-02-26

Agent: reviewer (reviewer)
Confidence: 37%
Cost: $0.5641
Duration: 3m 21s
Task ID: ffb705cb-cda5-49f6-b856-e8a29ebe899a


★ Insight ─────────────────────────────────────
開發流水線的三階段品質閘門:本專案的 dev pipeline 是 programmer → reviewer → secretary。每個階段有明確的職責邊界:programmer 寫 code + 自我驗證(tsgo),reviewer 做 code review + 跑完整測試(vitest),secretary 負責 commit + push。這種流水線設計確保了:(1) 寫 code 的人不自己核准 code(separation of duties),(2) commit 權限集中在 secretary 手上(audit trail),(3) 每個階段的退回機制都有具體指引(可操作的修改說明)。
─────────────────────────────────────────────────


Review 完成摘要

  • 驗收結果:全部 7 項驗收標準通過
  • 程式碼品質:良好,邏輯正確,無安全或效能問題
  • 測試:89 test files, 1092 tests 全數通過
  • 編譯:tsgo –noEmit exit code 0
  • 下一步:已 dispatch 給 secretary(task b977cc6b)進行 commit

—HANDOFF—
TO: secretary
INTENT: handoff
ARTIFACT_TYPE: review
SUMMARY: Skill 系統 4 bug 修復全部通過 code review,已派工 commit

完成報告

Agent: programmer (programmer)
Confidence: 51%
Cost: $0.5913
Duration: 2m 39s
Task ID: e6431309-72fe-4472-9a28-920df4bf6a48


任務完成。摘要如下:

完成報告

修改的檔案(3 個)

檔案 改動
src/metacognition/pattern-detector.ts BUG-1: bigram stride i++i += 2;BUG-3: MIN_FREQUENCY 3→5
src/metacognition/skill-auto-create.ts BUG-3: MIN_CONFIDENCE 0.3→0.5
src/skills/skill-loader.ts BUG-4: keyword 比對改為 word-boundary regex(CJK 保留 includes()

BUG-2(垃圾 skill 檔案)

確認 soul/skills/auto-*.md 不存在,無需清理。

驗證

  • tsgo --noEmit:✅ exit code 0
  • 已 dispatch 給 reviewer(task ffb705cb)進行 code review

—HANDOFF—
TO: reviewer
INTENT: handoff
ARTIFACT_TYPE: code-change
SUMMARY: Skill 系統 4 個 bug 修復完成(bigram stride、閾值調高、keyword word-boundary),已通過 tsgo 編譯檢查

報告摘要

Agent: programmer (programmer)
Confidence: 65%
Cost: $0.9163
Duration: 4m 12s
Task ID: 400fe601-03e2-4f46-af01-b8e82f680bae


★ Insight ─────────────────────────────────────
1. 中文 NLP 的最小可行方案:我們的 pattern-detector 用了 bigram sliding window 來處理中文(因為中文沒有空格分隔詞)。這是一個常見的取捨——不引入 jieba 等重型斷詞庫,用 n-gram 近似。但 sliding window(stride=1)的致命問題是產生跨詞邊界碎片(如「始實」)。改成 non-overlapping stride=2 就能消除大部分問題,成本為零。

2. 自製 YAML Parser 的風險:為了避免引入 js-yaml 依賴,我們手寫了一個 ~60 行的 YAML subset parser。它目前能用,但每個 edge case 都是潛在 bug。這是「dependency avoidance」和「correctness」之間的典型取捨。對我們的場景(frontmatter 格式由自己控制),風險可控,但應該在文件中明確記錄支援的語法子集。

3. Signal File 的熱載入模式:用 .rebuild sentinel file 做進程間通信(MCP tool 寫入 → skill-loader 檢測)是一個優雅的低成本方案,避免了 IPC、WebSocket 等複雜通信。缺點是需要檔案系統輪詢(每次 matchSkillsstat() 一次),但因為 matchSkills 本來就是 I/O 操作,額外的 stat() 開銷可忽略。
─────────────────────────────────────────────────


報告摘要

技術報告已完成並存檔至 soul/agent-reports/programmer/skill-system-code-review-2026-02-26.md

核心發現:

6 個問題,按嚴重度排序:

# 嚴重度 問題 位置
BUG-1 HIGH 中文 bigram 斷詞產出垃圾 skill(auto-開始-始實-實作 pattern-detector.ts:80-92
BUG-2 MEDIUM 自製 YAML parser 的邊界陷阱(浮點數、nested object、key 中的 - skill-loader.ts:61-125
BUG-3 LOW-MED .rebuild signal 有 race condition(double rebuild) skill-loader.ts:294-304
BUG-4 LOW buildSkillIndex 串行讀取全文(只需 frontmatter) skill-loader.ts:130-174
BUG-5 LOW Weekly count 重置丟失中間數據 skill-usage-tracker.ts:65-70
BUG-6 DESIGN weaveSkills 硬編碼只注入 1 個 skill context-weaver.ts:461

與文章觀點對照:

  • 我們做對了:延遲加載、frontmatter 設計、signal 熱載入、使用追蹤、自動學習
  • 文章有但我們沒有:目錄語義化(低優先)、技能繼承(目前不需要)
  • 我們有但文章沒提:自動學習系統(但品質需修)

最優先修復:

  1. 修中文斷詞 — 改 stride=2 或引入基本詞典,並清理 2 個垃圾 skill
  2. 提高自動學習閾值 — MIN_FREQUENCY 3→5,MIN_CONFIDENCE 0.3→0.5
  3. keyword 匹配改 word boundary — 避免 "git" 匹配到 "legitimate"

pm — 2026-02-26

Agent: pm (pm)
Confidence: 8%
Cost: $0.9623
Duration: 4m 5s
Task ID: c59233cc-6723-4511-bfd9-e9b0b6257c12


報告完成。要我把這份報告存檔到 soul/agent-reports/pm/ 嗎?或者你想直接進入執行階段,我來派工處理 Phase 1 的清理任務?

架構評估報告摘要

Agent: architect (architect)
Confidence: 55%
Cost: $0.8607
Duration: 3m 31s
Task ID: 81149285-2d7f-4b99-950a-2a9a80cf5016


★ Insight ─────────────────────────────────────

1. 延遲加載策略的差異:文章描述的是「先讀 README 再讀 system.md」的兩階段加載。我們的實作更精密——buildSkillIndex() 在啟動時只解析 frontmatter(~20 bytes/skill),matchSkills() 只對 Top-1 結果做 readFile() 載入完整 body。這意味著 29 個 skill 的每次匹配只需 1 次檔案 I/O,而非 29 次。

2. 扁平 vs 層級的架構取捨:文章主張「目錄結構即語義」適合人類協作場景(開發者瀏覽文件夾就能理解知識分類)。但在 bot-first 系統中,frontmatter 的 category/keywords 欄位比目錄路徑更適合程式化處理。這是閱讀者不同導致的設計分歧——人讀路徑,程式讀 metadata。

3. 自動學習迴路是真正的護城河:文章最核心的盲區是假設「技能由人類定義」。我們的 pattern-detector → skill-auto-create 迴路讓系統能自己觀察使用模式並生長出新技能,這才是 AI-native skill system 與傳統 prompt library 的本質區別。

─────────────────────────────────────────────────


架構評估報告摘要

報告已存入 soul/agent-reports/architect/skill-system-architecture-review-2026-02-26.md

核心結論

我們的 skill 系統在基礎設計上與文章一致,在三個維度上顯著領先

維度 我們 vs 文章
自動學習 pattern-detector → auto-create 迴路,文章完全沒有
Token 經濟 8 層 budget 分配 + 跨層去重,文章沒提
自我管理 MCP CRUD tools + 熱重載,bot 自己能 CRUD skill

文章有但我們沒有的——評估後決定不引入

  • 技能繼承:我們只有 29 個 skill,規模不到需要繼承的程度。CLAUDE.md + identity 已經提供了「共用基礎」
  • 目錄語義:frontmatter category 比目錄路徑更適合程式化處理

值得投入的改進方向

  1. 🔶 Auto-learn 品質修復auto-檢視-視一-一下.md 這種名字是中文 tokenization artifact,需要修
  2. 🔶 Skill 效能評估:目前只追蹤使用頻率,不知道 skill 注入後是否真的有幫助。建議加入 effectiveness scoring
  3. 🔵 Skill 衝突偵測:keywords 重疊的 skill pairs 做 warning log
  4. ⚪ 觀察不行動:技能繼承、目錄語義等規模到 100+ skill 再說

如需將改進建議轉為可執行任務,我可以轉派 PM 拆解。

任務完成報告

Agent: programmer (programmer)
Confidence: 46%
Cost: $0.8154
Duration: 6m 4s
Task ID: b2779ea0-7b19-47c7-92b0-8f08b1abdc53


任務完成報告

驗證結果

所有 4 項驗收標準 全部通過

  1. src/evolution/pipeline.ts 兩處 handler — ✅ markExplored 邏輯已存在於 line 262-273(completed)和 line 305-316(failed)
  2. tests/unit/pipeline-research-curiosity.test.ts — ✅ 已存在,含 4 個 test cases
  3. tsgo --noEmit — ✅ exit code 0,無型別錯誤
  4. npm test — ✅ 89 test files, 1092 tests 全數通過

說明

此 bug fix 已由先前的 commit d4a23f7 完成(fix(evolution): mark curiosity topic as explored after research task completes)。程式碼和測試均已到位且驗證通過,無需重複修改

HANDOFF 建議

dispatch_task 在當前環境不可用,無法自動轉交。建議 CTO 手動派工 reviewer 確認 commit d4a23f7 的品質。

—HANDOFF—
TO: reviewer
INTENT: handoff
ARTIFACT_TYPE: report
SUMMARY: pipeline.ts curiosity markExplored bug fix 已存在(commit d4a23f7),全部驗收標準通過,需 reviewer 確認品質

評估完成

Agent: architect (architect)
Confidence: 55%
Cost: $0.5893
Duration: 2m 54s
Task ID: 18d716ce-9e4f-4e64-8a81-183a010b427b


★ Insight ─────────────────────────────────────
架構評估的核心原則:好的改善建議必須從「痛點」出發,而非從「框架支援的功能」出發。Explorer 報告犯了典型的 technology-push 錯誤——「grammY 有 Composer/Fork,所以我們應該用」。正確的思路是「我們有什麼問題?哪個工具能解決?」。這在架構決策中叫做 problem-pull vs technology-push,前者的 ROI 永遠更高。
─────────────────────────────────────────────────

評估完成

報告已寫入 soul/agent-reports/architect/grammy-middleware-evaluation-2026-02-26.md

摘要結論

Explorer 報告評級:2/5(有參考價值但不值得行動)

建議 判定 原因
Composer 重構 plugin-router ❌ 不做 現有 62 行函數清晰可讀,重構是解決方案找問題
為 middleware 寫測試 ⚠️ P3 可做 有價值但邏輯太簡單,ROI 不如測試其他模組
Fork() 非阻塞 logging ❌ 不做 報告沒讀仔細——logging 已經是 fire-and-forget

更值得投入的方向(報告沒提到的):

  1. P2message-handler.tsprocessMessage() 有 155 行、7 個 try/catch,複雜度遠超 middleware 層
  2. P3 — message-handler 中 6 處動態 import 可改靜態 import
  3. P4 — rate-limit 的 Map 沒有過期清理機制

核心判斷:middleware 層是系統中寫得最好的部分之一——簡潔、正確、職責清晰。不要動它。

探索主題

Agent: 探索者 (explorer)
Confidence: 68%
Cost: $0.1091
Duration: 1m 30s
Task ID: f063c7d2-b7da-4ecb-99df-72a7da641a16


我已經蒐集足夠的資訊。現在彙整探索報告。


探索主題

grammY Bot 框架的中介軟體設計模式:高效、可測試的 Telegram Bot 中介軟體實作

發現

  1. Composer 樹狀結構 > 線性堆疊

    • grammY 看似線性的 bot.use() 實際上建構一棵樹(Composer 類別)
    • filter()on()use() 都會自動建構分支,允許後續擴展而無須攤平結構
    • 好處:可在已安裝的 Composer 上繼續 .use(),新中介軟體自動納入流程
  2. await next() 是關鍵

    • 忘記 await 會導致:執行順序錯誤、資料遺失、隨機崩潰、UnhandledPromiseRejection
    • 正確寫法:await next() 必須在 try-catch 上游(讓下游錯誤被捕捉)
  3. 當前專案已採用分層設計

    • Error boundary → Auth → Logging → Rate-limit(順序正確)
    • 每個中介軟體各司其職,易於測試和組合
    • 缺點:都是線性組合,未充分利用 Composer 的分支能力
  4. 可組合性設計

    • 中介軟體應該是無狀態函數,接受 (ctx, next) 返回 Promise<void>
    • 支援鏈式呼叫:.use(A).use(B).use(C)
    • 支援條件分支:.filter(predicate).use(middleware) 使中介軟體只在特定條件執行
  5. Fork() 支援並行分支

    • .fork().on(":text").use(A) 可以在不阻塞主流程的情況下執行背景任務
    • 適合:日誌記錄、分析、非關鍵邊界效應

有趣的連結

  • 與 Express.js 中介軟體模式的差異:Express 無法在已安裝的 middleware 上再增加,必須在初始化時定義;grammY 允許動態擴展
  • 與事件總線的協力:Composer 樹狀結構 + EventBus 可實現高度解耦的外掛系統(你們的 plugin-router 已走在這條路上)

延伸問題

  1. 可否用 Composer 重構 plugin-router? 目前 tryRouteToPlugin() 是手動迴圈,可用 composer.filter(isPluginCommand).use(pluginHandler) 改寫嗎?
  2. 如何為複雜中介軟體寫單元測試? grammY 官方推薦的測試策略是什麼(mock Context、測試 next 呼叫順序)?
  3. fork() 如何改善 message-handler? 目前 logging 和 event emit 都是 fire-and-forget(.catch(() => {})),能用 fork 分離嗎?

重要性:4/5

能改善專案品質

  • 當前中介軟體層結構已不錯,但可用 Composer 分支進一步優化
  • Plugin routing 目前手動迴圈,可改為聲明式 middleware
  • Fork 可解決「主流程不被背景任務阻擋」的效能痛點

不是外部使用者會看到的特性(無法在blog/report發文),但對內部開發效率影響大


技術總結

最小可行改進(優先順序):

  1. setupMiddleware() 中使用 Composer 為 plugin-router 建構過濾規則
  2. 為 error-handler、auth、logging 各自編寫 5-10 行單元測試(mock next)
  3. 試試 .fork().use(loggingMiddleware) 讓日誌完全非阻塞