- 首頁
- Documentation
- 設定
- 從 pi-mono 移植:實務合併指南
從 pi-mono 移植:實務合併指南
本指南是一份可重複使用的檢查清單,用於將 pi-mono 的變更移植到本儲存庫。 適用於任何合併情境:單一檔案、功能分支或完整的版本同步。
提交: b21b42d032919de2f2e6920a76fa9a37c3920c0a
日期: 2026-03-22
每次同步後更新此區段;不要重複使用先前的範圍。
開始新的同步時,從此提交開始產生補丁:
git format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch0) 定義範圍
Section titled “0) 定義範圍”- 確認上游參考(提交、標籤或 PR)。
- 列出您計劃涉及的套件或資料夾。
- 決定哪些功能在範圍內,哪些是有意跳過的。
1) 安全地搬移程式碼
Section titled “1) 安全地搬移程式碼”- 優先選擇乾淨、聚焦的差異,而非整批複製。
- 避免複製建置產物或自動產生的檔案。
- 如果上游新增了檔案,請明確加入並審閱內容。
2) 匹配匯入副檔名慣例
Section titled “2) 匹配匯入副檔名慣例”大多數執行時期 TypeScript 原始碼在內部匯入中省略 .js,但某些測試/效能測試入口點保留 .js 以確保 ESM
執行時期相容性。請遵循本地套件的現有風格;不要全面移除副檔名。
- 在
packages/coding-agent執行時期原始碼中,除非匯入非 TS 資源,否則保持內部匯入無副檔名。 - 在
packages/tui/test和packages/natives/bench中,如果周圍檔案已使用.js,則保留。 - 當工具要求時保留實際副檔名(例如
.json、.css、.md文字嵌入)。 - 範例:
import { x } from "./foo.js";→import { x } from "./foo";(僅當套件慣例為無副檔名時)。
3) 替換匯入範圍
Section titled “3) 替換匯入範圍”上游使用不同的套件範圍。請一致地替換它們。
- 將舊範圍替換為此處使用的本地範圍。
- 範例(根據您實際移植的套件進行調整):
@mariozechner/pi-coding-agent→@f5-sales-demo/xcsh@mariozechner/pi-agent-core→@f5-sales-demo/pi-agent-core@mariozechner/pi-tui→@f5-sales-demo/pi-tui@mariozechner/pi-ai→@f5-sales-demo/pi-ai
4) 在 Bun API 優於 Node 時使用 Bun API
Section titled “4) 在 Bun API 優於 Node 時使用 Bun API”我們在 Bun 上執行。僅在 Bun 提供更好替代方案時才替換 Node API。
應該替換:
- 程序產生:
child_process.spawn→ Bun Shell$用於簡單命令,Bun.spawn/Bun.spawnSync用於串流或長時間執行的工作 - 檔案 I/O:
fs.readFileSync→Bun.file().text()/Bun.write() - HTTP 客戶端:
node-fetch、axios→ 原生fetch - 加密雜湊:
node:crypto→ Web Crypto 或Bun.hash - SQLite:
better-sqlite3→bun:sqlite - 環境變數載入:
dotenv→ Bun 自動載入.env
不應該替換(這些在 Bun 中運作正常):
os.homedir()— 不要替換為Bun.env.HOME、Bun.env.HOME或字面值"~"os.tmpdir()— 不要替換為Bun.env.TMPDIR || "/tmp"或硬編碼路徑fs.mkdtempSync()— 不要替換為手動路徑建構path.join()、path.resolve()等 — 這些沒問題
匯入風格: 使用 node: 前綴搭配命名空間匯入(不要從 node:fs 或 node:path 使用具名匯入)。
額外的 Bun 慣例:
- 對於短的非串流命令,優先使用 Bun Shell
$;僅在需要串流 I/O 或程序控制時使用Bun.spawn。 - 檔案使用
Bun.file()/Bun.write(),目錄使用node:fs/promises。 - 避免
Bun.file().exists()檢查;在 try/catch 中使用isEnoent處理。 - 優先使用
Bun.sleep(ms)而非setTimeout包裝。
錯誤:
// BROKEN: env vars may be undefined, "~" is not expandedconst home = Bun.env.HOME || "~";const tmp = Bun.env.TMPDIR || "/tmp";正確:
import * as os from "node:os";import * as fs from "node:fs";import * as path from "node:path";
const configDir = path.join(os.homedir(), ".config", "myapp");const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "myapp-"));5) 優先使用 Bun 嵌入(不複製)
Section titled “5) 優先使用 Bun 嵌入(不複製)”不要在建置時複製執行時期資源或供應商檔案。
- 如果上游將資源複製到 dist 資料夾,請替換為 Bun 友善的嵌入方式。
- 提示詞是靜態
.md檔案;使用 Bun 文字匯入(with { type: "text" })和 Handlebars,而非內嵌提示詞字串。 - 使用
import.meta.dir+Bun.file載入相鄰的非文字資源。 - 將資源保留在儲存庫中,讓打包器包含它們。
- 除非使用者明確要求,否則消除複製腳本。
- 如果上游在執行時期讀取打包的備用檔案,請用 Bun 文字嵌入匯入替換檔案系統讀取。
- 範例(Codex 指令備用):
const FALLBACK_PROMPT_PATH = join(import.meta.dir, "codex-instructions.md");-> 移除import FALLBACK_INSTRUCTIONS from "./codex-instructions.md" with { type: "text" };- 使用
return FALLBACK_INSTRUCTIONS;而非readFileSync(FALLBACK_PROMPT_PATH, "utf8")
- 範例(Codex 指令備用):
6) 謹慎移植 package.json
Section titled “6) 謹慎移植 package.json”將 package.json 視為合約。有意圖地合併。
- 除非移植需要變更,否則保留現有的
name、version、type、exports和bin。 - 將 npm/node 腳本替換為 Bun 等效項(例如
bun check、bun test)。 - 確保依賴項使用正確的範圍。
- 不要為修復型別錯誤而降級依賴項;應該升級。
- 驗證工作區套件連結和
peerDependencies。
7) 對齊程式碼風格和工具
Section titled “7) 對齊程式碼風格和工具”- 保持現有的格式化慣例。
- 除非必要,不要引入
any。 - 避免動態匯入和內嵌型別匯入;僅使用頂層匯入。
- 永遠不要在程式碼中建構提示詞;提示詞是靜態
.md檔案,使用 Handlebars 渲染。 - 在 coding-agent 中,永遠不要使用
console.log/console.warn/console.error;使用來自@f5-sales-demo/pi-utils的logger。 - 使用
Promise.withResolvers()而非new Promise((resolve, reject) => ...)。 - 不要在類別欄位或方法上使用
private/protected/public關鍵字。 使用 ES#私有欄位進行封裝;可存取的成員保持不加關鍵字。唯一的例外是建構子參數屬性(constructor(private readonly x: T)),TypeScript 要求使用關鍵字。移植使用private foo或protected bar的上游程式碼時,轉換為#foo(私有)或裸bar(可存取)。 - 優先使用現有的輔助函式和工具,而非新的臨時程式碼。
- 保留本儲存庫中已做的 Bun 優先基礎設施變更:
- 執行時期為 Bun(沒有 Node 入口點)。
- 套件管理器為 Bun(沒有 npm 鎖定檔)。
- 重量級 Node API(
child_process、readline)已替換為 Bun 等效項。 - 輕量級 Node API(
os.homedir、os.tmpdir、fs.mkdtempSync、path.*)保留。 - CLI shebang 使用
bun(不是node,不是tsx)。 - 套件直接使用原始碼檔案(沒有 TypeScript 建置步驟)。
- CI 工作流程使用 Bun 進行安裝/檢查/測試。
8) 移除舊的相容性層
Section titled “8) 移除舊的相容性層”除非有要求,否則移除上游的相容性墊片。
- 刪除已被替換的舊 API。
- 直接更新所有呼叫端使用新 API。
- 不要保留
*_v2或並行版本。
9) 更新文件和參考
Section titled “9) 更新文件和參考”- 適當替換 pi-mono 儲存庫連結。
- 更新範例以使用 Bun 和正確的套件範圍。
- 確保 README 說明仍與當前儲存庫行為一致。
10) 驗證移植結果
Section titled “10) 驗證移植結果”變更後執行標準檢查:
bun check
如果儲存庫已有與您的變更無關的失敗檢查,請指出。
測試使用 Bun 的執行器(不是 Vitest),但僅在明確要求時才執行 bun test。
11) 保護已改善的功能(回歸陷阱清單)
Section titled “11) 保護已改善的功能(回歸陷阱清單)”如果您已在本地改善了行為,請將這些視為不可妥協的。移植前,記錄下 改善之處並添加明確檢查,以免在合併中遺失。
- 凍結預期行為:為每項改善添加簡短的「前/後」備註(輸入、輸出、 預設值、邊界情況)。這可防止無聲回退。
- 映射舊 → 新 API:如果上游重新命名了概念(hooks → extensions、custom tools → tools 等), 確保每個舊入口點仍正確連接。遺漏一個旗標或匯出就等於失去功能。
- 驗證匯出:檢查
package.json的exports、公開型別和桶形檔案。上游移植經常 忘記重新匯出本地新增項目。 - 涵蓋非正常路徑:如果您修復了錯誤處理、逾時或備用邏輯,請添加測試或 至少一份手動檢查清單來驗證這些路徑。
- 檢查預設值和配置合併順序:改善通常存在於預設值中。確認新的預設值 沒有回退(例如新的配置優先順序、停用的功能、工具清單)。
- 審核環境/shell 行為:如果您修復了執行或沙箱化,驗證新路徑仍使用您的 清理過的環境,且沒有重新引入別名/函式覆蓋。
- 重新執行目標範例:保留一組最小的「已知良好」範例,並在移植後執行 (CLI 旗標、擴充套件註冊、工具執行)。
12) 偵測並處理重構過的程式碼
Section titled “12) 偵測並處理重構過的程式碼”移植檔案前,檢查上游是否進行了大幅重構:
# Compare the file you're about to port against what you have locallygit diff HEAD upstream/main -- path/to/file.ts如果差異顯示檔案被重構(不僅僅是修補):
- 新的抽象、重新命名的概念、合併的模組、變更的資料流
那麼您必須在移植前徹底閱讀新的實作。盲目合併重構程式碼會導致功能遺失,因為:
注意:互動模式最近被拆分為 controllers/utils/types。回移相關變更時,請將更新移植到我們建立的個別檔案中,並確保 interactive-mode.ts 的串接保持同步。
-
預設值無聲變更 - 新變數
defaultFoo = [a, b]可能取代了原本回傳[a, b, c, d, e]的舊getAllFoo()。 -
API 選項被丟棄 - 當系統合併時(例如
hooks+customTools→extensions),舊選項可能無法正確連接到新實作。 -
程式碼路徑變得陳舊 - 重新命名的概念(例如
hookMessage→custom)需要在每個 switch 陳述式、型別守衛和處理器中更新——不僅僅是定義處。 -
上下文/能力縮減 - 舊 API 可能暴露了
{ logger, typebox, pi },而新 API 忘記包含。
語義化移植流程
Section titled “語義化移植流程”當上游重構了一個模組時:
-
閱讀舊實作 - 了解它做了什麼、接受哪些選項、暴露了什麼。
-
閱讀新實作 - 了解新的抽象以及它們如何對映到舊行為。
-
驗證功能對等 - 對於舊程式碼中的每項能力,確認新程式碼保留了它或明確移除了它。
-
搜尋遺漏 - 搜尋可能在 switch 陳述式、處理器、UI 元件中遺漏的舊名稱/概念。
-
測試邊界 - CLI 旗標、SDK 選項、事件處理器、預設值——這些是回歸藏身之處。
# Find all uses of an old concept that may need updatingrg "oldConceptName" --type ts
# Compare default values between versionsgit show upstream/main:path/to/file.ts | rg "default|DEFAULT"
# Check if all enum/union values have handlersrg "case \"" path/to/file.ts13) 快速審核檢查清單
Section titled “13) 快速審核檢查清單”在完成前用此作為最後一輪檢查:
14) 提交訊息格式
Section titled “14) 提交訊息格式”提交回移時,遵循儲存庫格式 <type>(scope): <past-tense description> 並在標題中保留提交範圍。
fix(coding-agent): backported pi-mono changes (<from>..<to>)
packages/<package>:- <type>: <description>- <type>: <description> (#<issue> by @<contributor>)
packages/<other-package>:- <type>: <description>範例:
fix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)
packages/ai:- fix: handle "sensitive" stop reason from Anthropic API- fix: normalize tool call IDs with special characters for Responses API- fix: add overflow detection for Bedrock, MiniMax, Kimi providers- fix: 429 status is rate limiting, not context overflow
packages/tui:- fix: refactored autocomplete state tracking- fix: file autocomplete should not trigger on empty text- fix: configurable autocomplete max visible items- fix: improved table column width calculation with word-aware wrapping
packages/coding-agent:- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)- fix: resolve macOS NFD and curly quote variants in file paths規則:
- 按套件分組變更
- 使用慣例提交類型(
fix、feat、refactor、perf、docs) - 對外部貢獻包含上游 issue/PR 編號和貢獻者歸屬
- 標題中的提交範圍有助於追蹤同步點
15) 刻意的分歧
Section titled “15) 刻意的分歧”我們的分支有與上游不同的架構決策。不要移植以下上游模式:
| 上游 | 我們的分支 | 原因 |
|---|---|---|
FooterDataProvider 類別 | StatusLineComponent | 更簡單、整合的狀態列 |
ctx.ui.setHeader() / ctx.ui.setFooter() | 非 TUI 模式中為存根 | 在 TUI 中實作,其他地方為無操作 |
ctx.ui.setEditorComponent() | 非 TUI 模式中為存根 | 在 TUI 中實作,其他地方為無操作 |
InteractiveModeOptions 選項物件 | 位置建構子參數(選項型別仍匯出) | 保持建構子簽名;上游新增欄位時更新型別 |
| 上游 | 我們的分支 |
|---|---|
extension-input.ts | hook-input.ts |
extension-selector.ts | hook-selector.ts |
ExtensionInputComponent | HookInputComponent |
ExtensionSelectorComponent | HookSelectorComponent |
API 命名
Section titled “API 命名”| 上游 | 我們的分支 | 備註 |
|---|---|---|
sessionManager.appendSessionInfo(name) | sessionManager.setSessionName(name) | 我們全面使用 sessionName |
sessionManager.getSessionName() | sessionManager.getSessionName() | 相同(我們統一以匹配上游的 RPC) |
agent.sessionName / setSessionName() | agent.sessionName / setSessionName() | 相同 |
| 上游 | 我們的分支 | 原因 |
|---|---|---|
clipboard.ts + clipboard-image.ts(工具檔案) | @f5-sales-demo/pi-natives 剪貼簿模組 | 合併至 N-API 原生實作 |
| 上游 | 我們的分支 |
|---|---|
vitest 搭配 vi.mock() | bun:test 搭配 bun 的 vi |
node:test 斷言 | expect() 匹配器 |
| 上游 | 我們的分支 | 備註 |
|---|---|---|
createTool(cwd: string, options?) | createTools(session: ToolSession) 透過 BUILTIN_TOOLS 註冊表 | 工具工廠接受 ToolSession 且可回傳 null |
每個工具的 *Operations 介面 | 每個工具的介面保留(FindOperations、GrepOperations) | 用於 SSH/遠端覆蓋 |
到處使用 Node.js fs/promises | 檔案用 Bun.file()/Bun.write();目錄用 node:fs/promises | 當 Bun API 能簡化時優先使用 |
| 上游 | 我們的分支 | 備註 |
|---|---|---|
proper-lockfile + auth.json | agent.db(bun:sqlite) | 憑證專門儲存在 agent.db 中 |
| 每個提供者單一憑證 | 多憑證搭配輪詢選取 | 保留工作階段親和性和退避邏輯 |
| 上游 | 我們的分支 |
|---|---|
jiti 用於 TypeScript 載入 | 原生 Bun import() |
pkg.pi 清單欄位 | pkg.xcsh ?? pkg.pi(優先使用我們的命名空間) |
跳過這些上游功能
Section titled “跳過這些上游功能”移植時,完全跳過這些檔案/功能:
footer-data-provider.ts— 我們使用 StatusLineComponentclipboard-image.ts— 剪貼簿在@f5-sales-demo/pi-nativesN-API 模組中- GitHub 工作流程檔案 — 我們有自己的 CI
models.generated.ts— 自動產生的,在本地重新產生(改為 models.json)
我們新增的功能(保留這些)
Section titled “我們新增的功能(保留這些)”這些存在於我們的分支中但不在上游。永遠不要覆蓋:
- 互動模式中的
StatusLineComponent - 帶工作階段親和性的多憑證認證
- 基於能力的探索系統(
defineCapability、registerProvider、loadCapability、skillCapability等) - MCP/Exa/SSH 整合
- LSP 寫入穿透用於儲存時格式化
- Bash 攔截(
checkBashInterception) - 讀取工具中的模糊路徑建議