環境可移植性改善方案 — 架構設計

環境可移植性改善方案 — 架構設計

Author: architect
Date: 2026-02-27
Status: 設計完成,待 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。替換成本可控,因為入口點唯一。


二、方案設計

設計原則

  1. 向後相容第一 — 現有 WSL 環境必須零中斷
  2. 最小侵入 — 每個 phase 獨立可交付,不互相依賴
  3. env var + 智慧預設 — 新機器用 env var 配置,舊機器用 fallback 維持不動
  4. 不過度設計 — 解決當前問題,不為假想未來設計

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/ 不存在或為空)

  1. 建立所有必要目錄(20+ subdirs)
  2. 生成 genesis.md(預填 Chapter 0 框架,留白讓使用者編輯)
  3. 生成最小 identity.json(name: “New Bot”, 空 traits)
  4. 生成空的 vitals.json、milestones.json
  5. 生成 4 個核心 agent configs(programmer, architect, reviewer, secretary)— 使用內嵌模板
  6. 建立空的事件流檔案(.jsonl 們)
  7. 建立 agent-tasks/queue.json(空 queue)
  8. 執行 Identity Fingerprint 初始化

模式 2:遷移模式(soul/genesis.md 已存在)

  1. 檢查目錄完整性:列出缺少的子目錄和檔案
  2. 補建缺失的目錄
  3. 不覆蓋任何已存在的檔案
  4. 回報修復了什麼

模式 3:校驗模式(–check)

  1. 檢查所有必要檔案和目錄是否存在
  2. 驗證 JSON 檔案格式正確
  3. 驗證 Identity Fingerprint 一致性
  4. 輸出健康報告
  5. 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):

1
2
3
4
# Path configuration (optional — defaults use process.cwd())
# PROJECT_ROOT=/mnt/d/gitcode/mybotteam
# WORKTREE_BASE=/home/arc/worktrees
# HEXO_DIR=/path/to/blog

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
// scripts/install-all.js — Install dependencies for sub-projects
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}/...`);
// Uses execFileSync for safety (no shell injection)
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
# docker-compose.yml
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
// src/claude/llm-provider.ts — 未來的抽象介面

interface LLMProvider {
ask(prompt: string, opts: LLMOptions): Promise<LLMResponse>;
isAvailable(): Promise<boolean>;
}

class ClaudeCLIProvider implements LLMProvider {
// 現有的 spawn('claude') 邏輯
}

class AnthropicSDKProvider implements LLMProvider {
// 直接使用 anthropic-ai/sdk
// 無工具審批、無 MCP — 純 text-in text-out
}

路由邏輯:

  • 需要工具使用 → 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
# 1. Clone repo
git clone https://github.com/your/repo.git
cd repo

# 2. 安裝依賴(自動安裝 blog/ 和 report/)
npm install

# 3. 初始化 soul(互動式,引導填寫必要資訊)
npm run setup

# 4. 設定環境(複製範本,填 BOT_TOKEN)
cp .env.example .env
# edit .env

# 5. 健康檢查
npm run doctor

# 6. 啟動
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(不跨平台)