環境可移植性改善方案 — 架構設計 Author : architectDate : 2026-02-27Status : 設計完成,待 CTO 審批Priority : P0-P3(分階段實施)
一、問題分析 1.1 現狀評估
指標
我們的系統
PicoClaw(對照組)
首次部署時間
30-60 分鐘
不到 1 分鐘
依賴項數量
Node.js + 三次 npm install + Claude CLI + .env
Single binary + config
硬編碼路徑
3 處 WSL 專屬路徑
0
初始化腳本
無
內建 init
容器化
無
Dockerfile 內建
1.2 五大短板驗證結果 經過原始碼全面審查,CTO 的分析結論全部正確,但影響範圍有部分修正:
P0:硬編碼路徑(確認 — 影響範圍精確) 受影響檔案:共 2 個檔案、3 處硬編碼
檔案
行號
硬編碼值
用途
src/agents/worktree-manager.ts
L19
/mnt/d/gitcode/mybotteam
Git 操作的 PROJECT_ROOT
src/agents/worktree-manager.ts
L20
/home/arc/worktrees
Worktree 基礎目錄
.mcp.json
L19
/mnt/d/gitcode/mybotteam/blog
Hexo MCP server 目錄
正面發現:其餘所有 src/ 模組一致使用 process.cwd() + join() — 這是良好的設計基礎。
P1:無初始化腳本(確認 — 比預期更嚴重) 啟動門檻:soul/genesis.md 不存在會導致 process.exit(1)(src/index.ts L38-45)
需手動建立的結構:
核心:genesis.md、identity.json、vitals.json、milestones.json
Agent configs:21 個 JSON 檔(soul/agents/)
Skills:28 個 MD 檔(soul/skills/)
事件流:narrative.jsonl、reflections.jsonl、diary.jsonl、dreams.jsonl
演化系統:evolution/capabilities.json、evolution/goals.json 等
任務系統:agent-tasks/queue.json、agent-tasks/history.jsonl
20+ 子目錄
自動建立的(無需手動):soul/memory/ 由 initMemoryDir() 啟動時自動建立
P2:三重 npm install(確認) 三個獨立 package.json:
./package.json(root — 51 個依賴)
./blog/package.json(blog — 15 個依賴)
./report/package.json(report — 12 個依賴)
無 npm workspaces、無 postinstall hook。
P2:缺少容器化(確認) 無 Dockerfile、無 docker-compose.yml。
P3:Claude CLI 硬依賴(確認 — 但抽象良好) 所有 LLM 呼叫集中在 askClaudeCode()(src/claude/claude-code.ts),這是單一入口點。用 spawn(‘claude’, args) 方式呼叫。Session 管理通過 session-store.ts。預飛檢查通過 preflight.ts。替換成本可控,因為入口點唯一。
二、方案設計 設計原則
向後相容第一 — 現有 WSL 環境必須零中斷
最小侵入 — 每個 phase 獨立可交付,不互相依賴
env var + 智慧預設 — 新機器用 env var 配置,舊機器用 fallback 維持不動
不過度設計 — 解決當前問題,不為假想未來設計
Phase 1:P0 硬編碼修復 + P1 初始化腳本(高優先、低風險) 預估工時:1-2 個 agent task 影響範圍:2 個原始碼檔案 + 1 個新腳本
1A. 修復 worktree-manager.ts 硬編碼 變更範圍:src/agents/worktree-manager.ts L19-20
Before:
1 2 const PROJECT_ROOT = '/mnt/d/gitcode/mybotteam' ;const WORKTREE_BASE = '/home/arc/worktrees' ;
After:
1 2 3 4 import { homedir } from 'node:os' ;const PROJECT_ROOT = process.env .PROJECT_ROOT || process.cwd ();const WORKTREE_BASE = process.env .WORKTREE_BASE || join (homedir (), 'worktrees' );
技術細節:
引入 homedir() from node:os
PROJECT_ROOT fallback 到 process.cwd() — 因為 bot 永遠從專案根目錄啟動
WORKTREE_BASE fallback 到 ~/worktrees — 比硬編碼 /home/arc 更通用
保留 _constants export 讓測試可以驗證實際值
在 .env.example 新增 PROJECT_ROOT 和 WORKTREE_BASE(有註解說明 optional)
向後相容分析:
Arc 的 WSL 環境:如果不設 env var,process.cwd() = /mnt/d/gitcode/mybotteam(跟原本一樣)
WORKTREE_BASE 需要 Arc 在 .env 明確設定 WORKTREE_BASE=/home/arc/worktrees(或接受新預設 ~/worktrees)
風險評估:WSL2 的 homedir() 回傳 /home/arc,所以 ~/worktrees = /home/arc/worktrees,完全相容
影響的模組:
createTaskWorktree() — 使用 PROJECT_ROOT 和 WORKTREE_BASE
execGit() — 使用 PROJECT_ROOT 作為 cwd
listActiveWorktrees() — 使用 WORKTREE_BASE
所有函數的行為不變,只是常數來源改變
1B. 修復 .mcp.json 硬編碼 變更範圍:.mcp.json L19
Before:
1 "HEXO_DIR" : "/mnt/d/gitcode/mybotteam/blog"
After(方案 A — 推薦):
1 "HEXO_DIR" : "${HEXO_DIR}"
配合 .env 新增:
1 HEXO_DIR=/mnt/d/gitcode/mybotteam/blog
技術考量:
.mcp.json 的 env var interpolation 取決於 Claude CLI 是否支援 ${} 語法
替代方案 B:改 MCP server 端讀取 process.cwd()。但 hexo MCP server 是外部 npm 套件,不適合改。
替代方案 C:將 .mcp.json 改為 .mcp.json.template + 生成腳本。增加複雜度。
決策:先測試方案 A。如果 Claude CLI 不支援 env interpolation,fallback 到方案 C(setup script 生成 .mcp.json)。
1C. 建立 scripts/init-soul.ts 初始化腳本 新增檔案:scripts/init-soul.ts 新增 npm script:setup: tsx scripts/init-soul.ts
功能規格:
1 2 3 npm run setup # 交互模式 npm run setup -- --minimal # 最小安裝(僅核心檔案) npm run setup -- --check # 校驗模式(不修改,只檢查)
三個模式:
模式 1:全新安裝(soul/ 不存在或為空)
建立所有必要目錄(20+ subdirs)
生成 genesis.md(預填 Chapter 0 框架,留白讓使用者編輯)
生成最小 identity.json(name: “New Bot”, 空 traits)
生成空的 vitals.json、milestones.json
生成 4 個核心 agent configs(programmer, architect, reviewer, secretary)— 使用內嵌模板
建立空的事件流檔案(.jsonl 們)
建立 agent-tasks/queue.json(空 queue)
執行 Identity Fingerprint 初始化
模式 2:遷移模式(soul/genesis.md 已存在)
檢查目錄完整性:列出缺少的子目錄和檔案
補建缺失的目錄
不覆蓋任何已存在的檔案
回報修復了什麼
模式 3:校驗模式(–check)
檢查所有必要檔案和目錄是否存在
驗證 JSON 檔案格式正確
驗證 Identity Fingerprint 一致性
輸出健康報告
Exit code: 0 = 健康, 1 = 有問題
Soul 目錄結構定義(內嵌在腳本中):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const SOUL_DIRS = [ 'soul' , 'soul/agents' , 'soul/agent-reports' , 'soul/agent-tasks' , 'soul/agent-tasks/pipelines' , 'soul/checkpoints' , 'soul/config' , 'soul/daily-reports' , 'soul/evolution' , 'soul/exploration-reports' , 'soul/explorations' , 'soul/knowledge' , 'soul/knowledge/entries' , 'soul/knowledge/archive' , 'soul/logs' , 'soul/market-research' , 'soul/memory' , 'soul/metrics' , 'soul/narrative-archive' , 'soul/reports' , 'soul/skills' , 'soul/staging' , 'soul/teams' , 'soul/blog' , ] as const ;
核心檔案定義:
1 2 3 4 5 6 7 8 9 10 11 12 const CORE_FILES = { 'soul/genesis.md' : GENESIS_TEMPLATE , 'soul/identity.json' : MINIMAL_IDENTITY , 'soul/vitals.json' : MINIMAL_VITALS , 'soul/milestones.json' : '[]' , 'soul/users.json' : '{}' , 'soul/sessions.json' : '{}' , 'soul/schedules.json' : '{}' , 'soul/learning-patterns.json' : '{}' , 'soul/research-index.json' : '{}' , 'soul/agent-tasks/queue.json' : '{"tasks":[],"version":1}' , } as const ;
空 JSONL 事件流:
1 2 3 4 5 6 7 8 9 10 const EMPTY_JSONL = [ 'soul/narrative.jsonl' , 'soul/reflections.jsonl' , 'soul/diary.jsonl' , 'soul/dreams.jsonl' , 'soul/agent-tasks/history.jsonl' , 'soul/agent-tasks/shared-knowledge.jsonl' , 'soul/evolution/changelog.jsonl' , 'soul/evolution/intentions.jsonl' , ];
1D. 更新 .env.example 新增以下 env vars(均為 optional):
Phase 2:P2 依賴安裝簡化 + 開發體驗改善(中優先) 預估工時:1 個 agent task 影響範圍:package.json + 新增 2 個腳本
2A. 新增 postinstall 腳本 變更範圍:package.json scripts
方案:在 root package.json 新增 postinstall hook,自動安裝子專案依賴。
1 2 3 4 5 6 { "scripts" : { "postinstall" : "node scripts/install-all.js" , "setup" : "tsx scripts/init-soul.ts" } }
scripts/install-all.js(純 Node.js,不依賴 tsx — 因為 postinstall 時 tsx 可能還沒裝好):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import { existsSync } from 'node:fs' ;import { join } from 'node:path' ;const SUBPROJECTS = ['blog' , 'report' ];for (const dir of SUBPROJECTS ) { const pkgPath = join (process.cwd (), dir, 'package.json' ); if (existsSync (pkgPath)) { console .log (`Installing dependencies for ${dir} /...` ); const { execFileSync } = await import ('node:child_process' ); execFileSync ('npm' , ['install' ], { cwd : join (process.cwd (), dir), stdio : 'inherit' }); } }
為什麼不用 npm workspaces:
blog/ 和 report/ 是 Hexo 專案,有自己的 Hexo 版本需求
依賴樹差異太大(bot 用 grammy/zod,blog 用 hexo 全家桶)
Workspace hoisting 可能導致 Hexo 插件找不到依賴
成本效益不合:改 workspaces 要重寫 blog/report 的 scripts,收益很小
結論:postinstall hook 是最小侵入方案
2B. 環境健康檢查工具 新增 npm script:doctor: tsx scripts/doctor.ts
功能示例輸出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ npm run doctor Environment Health Check ------------------------ Node.js v22.0.0 (>=20.0.0) OK npm v10.5.0 OK Claude CLI v1.5.0 (claude --version) OK Git v2.43.0 OK soul/genesis.md exists OK soul/identity.json valid JSON OK soul/agents/ 21 agent configs OK .env exists and BOT_TOKEN set OK blog/node_modules installed OK report/node_modules installed OK WORKTREE_BASE not set (using default) WARN ANTHROPIC_API_KEY not set INFO Status: Runnable (1 warning, 1 info)
Phase 3:P2 容器化 + P3 LLM 抽象層(低優先、高複雜度) 預估工時:多個 agent task 影響範圍:新增檔案為主,最小現有程式碼變更
3A. 部分容器化(blog/report 建構環境) 策略:不容器化 bot 本體(Claude CLI OAuth 阻礙),只容器化無狀態的建構任務。
新增檔案:docker/blog-builder.Dockerfile、docker-compose.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 services: blog-builder: build: context: . dockerfile: docker/blog-builder.Dockerfile volumes: - ./blog:/app/blog command: npm run build report-builder: build: context: . dockerfile: docker/report-builder.Dockerfile volumes: - ./report:/app/report command: npm run build
使用場景:
CI/CD 中的 blog 建構 — 不依賴本地 Node.js 版本
新開發者只需 Docker 即可建構 blog,無需安裝 Hexo 全套
不容器化 bot 本體的原因:
Claude CLI 需要互動式 OAuth 認證(目前無法在 Docker 中完成)
Bot 需要存取 soul/ 持久化狀態(volume mount 可解決但增加複雜度)
.env 中的 Telegram token 等秘密需要 Docker secrets 管理
結論:等 Anthropic 提供 headless auth 方案後再評估
3B. LLM 呼叫抽象層(長期方向) 目標:讓系統能同時支援 Claude CLI 和 API 直連模式。
現狀分析:
askClaudeCode() 是唯一入口點 — 非常好的起點
用 CLI 模式的原因:工具審批機制、session 管理、MCP 整合
用 API 模式的優點:無需 CLI 安裝、Docker 友好、更快的啟動
設計方向(概念層級):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface LLMProvider { ask (prompt : string , opts : LLMOptions ): Promise <LLMResponse >; isAvailable (): Promise <boolean >; } class ClaudeCLIProvider implements LLMProvider { } class AnthropicSDKProvider implements LLMProvider { }
路由邏輯:
需要工具使用 → CLI provider
純文字生成(日記、摘要、分類)→ SDK provider(更快)
CLI 不可用 → fallback 到 SDK provider
Phase 3B 不急的原因:
現有系統 100% 依賴 Claude CLI 且運作穩定
Anthropic SDK 的 tool use API 還在快速演進中
工具審批機制是安全架構的核心,不宜輕易繞過
建議:等 Claude CLI 支援 headless auth 或 API key 認證後再啟動
三、實作路線圖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Week 1 Phase 1A + 1B(硬編碼修復) - 改 worktree-manager.ts(1A) - 改 .mcp.json + .env.example(1B/1D) - 測試:現有 worktree 流程不壞 Week 1-2 Phase 1C(init-soul 腳本) - 實作 scripts/init-soul.ts - 實作 --check 校驗模式 - 測試:全新目錄 -> npm install -> npm run setup -> npm start Week 2 Phase 2A + 2B(依賴簡化) - 實作 scripts/install-all.js + postinstall hook - 實作 scripts/doctor.ts - 測試:rm -rf node_modules -> npm install -> 三個目錄都裝好 Week 3+ Phase 3A(選做) - blog/report Dockerfile - docker-compose.yml Backlog Phase 3B(LLM 抽象層) - 等 Anthropic CLI 支援 headless auth
四、風險評估 Phase 1 風險
風險
機率
影響
緩解措施
process.cwd() 在 worktree 內不是專案根目錄
低
高
Worktree agent 永遠從根目錄啟動,且有 symlink 到 soul/、node_modules/
.mcp.json 不支援 env var interpolation
中
中
實測 Claude CLI 行為;如不支援則改用 setup 腳本生成
init-soul 覆蓋使用者已編輯的 genesis.md
低
極高
遷移模式永不覆蓋已存在檔案
homedir() 在某些環境回傳異常值
低
低
env var 有明確覆蓋路徑
Phase 2 風險
風險
機率
影響
緩解措施
postinstall 在 CI 中重複執行浪費時間
中
低
檢查 node_modules 已存在就跳過
postinstall 在 npm ci 時也觸發
中
低
npm ci 本來就該裝全部依賴,行為正確
Phase 3 風險
風險
機率
影響
緩解措施
Docker volume mount 造成 soul/ 權限問題
中
中
只容器化無狀態建構,不碰 soul/
LLM 抽象層引入 regression
中
高
暫不執行,等 CLI headless auth
五、改善後的預期效果 首次部署流程(改善後) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 git clone https://github.com/your/repo.git cd reponpm install npm run setup cp .env.example .env npm run doctor npm start
預估時間:5-10 分鐘(vs 現在 30-60 分鐘)
可移植性改善
指標
Before
After Phase 1
After Phase 2
WSL 硬編碼
3 處
0 處
0 處
初始化步驟
手動 20+ 步
npm run setup
npm run setup
npm install 次數
3 次手動
3 次手動
1 次自動
環境檢查
無
–check 模式
npm run doctor
預估部署時間
30-60 min
10-15 min
5-10 min
六、附錄:Agent Config 最小模板 以下是 init-soul 會生成的 4 個核心 agent 配置:
programmer.json (template):
1 2 3 4 5 6 7 8 9 10 11 12 { "name" : "programmer" , "description" : "Software engineer" , "enabled" : true , "schedule" : "manual" , "model" : "claude-sonnet-4-6" , "maxTurns" : 30 , "timeout" : 900000 , "dailyCostLimit" : 5.0 , "notifyChat" : true , "budgetLocked" : false }
architect.json (template):
1 2 3 4 5 6 7 8 9 10 11 12 { "name" : "architect" , "description" : "System architect" , "enabled" : true , "schedule" : "manual" , "model" : "claude-opus-4-6" , "maxTurns" : 25 , "timeout" : 900000 , "dailyCostLimit" : 5.0 , "notifyChat" : true , "budgetLocked" : false }
reviewer.json (template):
1 2 3 4 5 6 7 8 9 10 11 12 { "name" : "reviewer" , "description" : "Code reviewer" , "enabled" : true , "schedule" : "manual" , "model" : "claude-sonnet-4-6" , "maxTurns" : 15 , "timeout" : 600000 , "dailyCostLimit" : 3.0 , "notifyChat" : true , "budgetLocked" : false }
secretary.json (template):
1 2 3 4 5 6 7 8 9 10 11 12 { "name" : "secretary" , "description" : "Secretary for admin tasks" , "enabled" : true , "schedule" : "manual" , "model" : "claude-haiku-4-5-20251001" , "maxTurns" : 10 , "timeout" : 300000 , "dailyCostLimit" : 1.0 , "notifyChat" : true , "budgetLocked" : false }
七、決策記錄
決策
選擇
理由
替代方案(已排除)
Worktree 路徑
env var + process.cwd() fallback
零中斷遷移
讀 git config(過於複雜)
.mcp.json
env var interpolation
最小改動
template 生成(增加工具鏈)
子專案依賴
postinstall hook
最小侵入
npm workspaces(破壞性太大)
容器化
只容器化建構任務
CLI OAuth 無法容器化
全容器化(目前不可行)
LLM 抽象
暫不實施
等 headless auth
立即抽象(過度設計)
Soul template
內嵌在腳本中
無額外目錄管理
獨立 template/ 目錄(過度)
init-soul 語言
TypeScript (tsx)
與專案一致
Shell script(不跨平台)