Resource · Public beta
Heptabase → Obsidian Migration Guide
A portable migration specification and guide designed for inspection, verification, and reuse.
- Updated
- Sep 25, 2026
- Version
- 1.0.1-beta
- License
- CC BY 4.0
Public Edition beta。 本指南整理自實際遷移與修復經驗,目前是 beta 版。它不是一鍵搬家工具,也不應直接套用到未盤點的資料。遇到規格沒涵蓋的情況,歡迎回報問題。
這份指南要解決什麼
把 Heptabase 搬到 Obsidian,不只是把 Markdown 複製到另一個資料夾。真正困難的是同時保留:
- 卡片正文與 metadata;
- Whiteboard 的空間配置與巢狀關係;
- MindMap 的文字、階層與 edges;
- Journal 的唯一正文與多個白板 instances;
- 圖片、附件、highlight、transcript 與 references;
- 已經在 Obsidian 裡新增或修改的使用者內容。
這份 Public Edition 提供一套可檢查、可回復、可以分階段驗證的遷移方法。它是寫給 AI Agent 執行、也讓人可以檢查的規格,不是一鍵 converter。案例數字只代表特定來源快照,不是其他 Vault 的固定目標。
先判斷你是哪一種遷移
| 情境 | 正確做法 |
|---|---|
| Fresh Vault | 在隔離的 test vault 重建並驗證,凍結成唯讀 canonical migration source,再從副本建立日常使用的 Active Vault。 |
| Existing Active Vault | 先辨識舊 migration layer、使用者新增內容、使用者修改內容與 Active-preferred files,再選擇 replace、import 或 semantic merge。不可整包覆蓋。 |
Canonical Migration Source 是已完成結構驗證、保持唯讀的重建來源;Active Vault 才是持續使用的 production vault。原始 Heptabase export、canonical source 與 Active Vault 是三個不同角色,都需要各自保存。
四個不可妥協的原則
- 先盤點,再轉換。 不先假設 export schema,也不以舊版本欄位名稱取代實際檢查。
- Low value 不等於刪除。 不轉成正式筆記的資料仍需保留原始來源與 audit evidence。
- 路徑存在不等於遷移成功。 Reference resolved、embed rendered、結構關係與實際可用性必須分開驗證。
- 先忠實重建,再整理可用性。 不要在還沒建立可回復基準前,就清理 ID、改 taxonomy 或覆蓋舊內容。
交給 AI Agent:第一段指令
使用者通常會貼上 README 的第一段指令(中文)。它要求的五項輸出,對應到本指南是:
- Source inventory(有哪些資料、各有多少)
- Migration Decision Table(建議轉換、封存或跳過,以及原因)
- 需要使用者決定的項目
- 主要風險
- POC 建議(先試哪一張白板,以及為什麼)
第一輪只分析。「不要修改任何檔案」指的是不修改 Heptabase export 與 test vault;Agent 可以在工作資料夾裡另建 _migration_work/,放 scripts、hash manifest 與報告:
我的搬家資料夾/
Heptabase-Data-Backup-…/ ← 原始匯出,唯讀
Obsidian_Test_Vault/ ← 測試 Vault,POC 通過前不寫入
_migration_work/ ← Agent 的 scripts、manifests、報告
使用者多半不寫程式。回報時用使用者的語言,先講結論和需要他決定的事;技術細節放在 _migration_work/ 的報告裡,並告訴使用者報告在哪裡。
選 POC 白板
POC 應挑一張中等大小(大約 30–80 個物件)、元素齊全的白板:有 Card、圖片、文字、Connection、MindMap,最好還有 nested Whiteboard。不要挑最大的白板,太大會讓 POC 難以逐項檢查。如果 Journal 或 PDF 不在這張白板上,另外各做一個小 POC。
確認 Inventory 與 POC 後才進入批次轉換。
POC 的最低驗證
- Canvas 是合法 JSON,node IDs 唯一,edge endpoints 都存在。
- 每個 source instance 都對應到一個 Canvas node,數量對帳;例外逐一列出。
- 卡片正文與來源 ProseMirror 文字比對,沒有遺失。
- 圖片檔與來源檔內容相同(hash),不是只有路徑存在。
- Nested whiteboard 在 parent Canvas 裡有對應的 file node。
- MindMap 的節點數、階層與來源一致。
- 請使用者在 Obsidian 實際打開:左下角「開啟其他 Vault」→「開啟資料夾作為 Vault」→ 選 test vault,再打開 POC 的
.canvas。
第一步:凍結來源並建立 Inventory
至少保存以下資料:
- 完整 Heptabase export 與
All-Data.json; - Markdown、assets 及其他附件;
- 來源 snapshot ID、匯出時間與工具版本;
- 每個檔案的相對路徑、大小與 SHA-256;
- 各 data type 的數量、active/deleted 狀態與抽樣結果;
All-Data.json裡的VERSION與DB_SCHEMA_VERSION(本指南的實測欄位來自 Heptabase1.103.0、DB schema133)。
來源應設為唯讀或使用不可變快照。Hash manifest 放在來源目錄之外(例如 _migration_work/),避免 manifest 自己改變計算範圍。
Windows 先檢查長路徑。 Heptabase export 可能有超過 260 字元的完整路徑(實測 6 個)。第一次掃描前就要處理,不要等到程式當掉:Python 讀取時使用 \\?\ 前綴,或把工作資料夾放在較短的路徑。不要替使用者修改系統設定(例如 LongPathsEnabled);需要時說明原因,請使用者自己決定。
Markdown export 不是完整的語意來源
All-Data.json 是必要來源,不是選配。正文以 All-Data.json 的 ProseMirror content 為準;Markdown export 主要用來取得 binary(圖片、PDF、影音)與交叉比對。
| 資料來源 | 主要用途 |
|---|---|
All-Data.json |
正文(ProseMirror)、object type、ID、relation、placement、fileId、semantic structure |
| Markdown/native export | 圖片與附件的 binary、交叉比對、使用者可直接閱讀的檔案 |
不要把 Markdown export 當成正文來源,原因是它很難可靠地對回 source ID:
.md檔沒有 ID,檔名由標題轉換而來,部分特殊字元會被替換。- 沒有標題的卡片會被命名為
A wonderful new card N(實測 569 個)。 - 同名卡片會加數字後綴;部分
.md的內容和All-Data.json不一致。 - 垃圾桶裡的卡片(
isTrashed: true)也會被匯出(實測 3,312 個.md,其中約 830 張在垃圾桶)。 Card Library/的.md放在同一層,但圖片放在各自的-assets子資料夾(實測 539 個)。
Native export 也可能有 Mindmap/(只有階層大綱,沒有座標與節點 ID)和 Text Element/。它們可以拿來核對,但 MindMap 結構、nested whiteboard 的 parent → child 關係、textElements 與 mediaElements 的座標,只有 All-Data.json 有。只靠 Markdown export,很容易得到一個「看起來差不多」但少了結構的 Vault。
不要假設 Whiteboard elements 一定巢狀在 whiteBoardList。實測 export 中,cardInstances、textElements、mediaElements、highlightElements、journalInstances、sections、connections 與 mindMapInstances 可能是頂層陣列,再用 whiteboardId 關聯 Whiteboard。缺 key、型別不符、孤立引用與重複 ID 都應明確報告,不能默默變成空陣列。
實測的 schema(Heptabase 1.103.0/DB schema 133)
以下是一份實際匯出的欄位,作為起點,不是保證。欄位會隨版本改變,寫 join 邏輯前一定要先實際檢查。
| 陣列 | 重點欄位 | 關聯 |
|---|---|---|
cardList |
title、content(ProseMirror)、isTrashed |
Card 本體 |
whiteBoardList |
name、isTrashed |
Whiteboard 本體 |
cardInstances |
cardId、whiteboardId、x、y、width、height、isFolded、foldedHeight |
Card 放在白板上的位置 |
mediaCards/mediaCardInstances |
fileId、type、transcript/cardId、whiteboardId |
Media card 與它的位置 |
pdfCards/pdfCardInstances |
fileId、title/pdfCardId、whiteboardId |
PDF card 與它的位置 |
textElements |
content、whiteboardId、座標 |
白板上的文字,直接帶位置 |
mediaElements |
fileId、whiteboardId、座標 |
白板上的圖片,直接帶位置 |
sections/sectionObjectRelations |
title、座標/sectionId、objectId |
分區與分區裡的物件 |
connections |
beginId、beginObjectType、endId、endObjectType、beginStyle、endStyle、description |
連線 |
whiteboardInstances |
whiteboardId(child)、containerId(parent)、containerType、isChild、座標 |
Nested whiteboard |
mindMaps/mindMapInstances |
layout/mindMapId、whiteboardId、座標、boundingBoxRelativeX/Y |
MindMap 與它的位置 |
mindMapNodes |
mindMapId、parentId、childNodeIds、side、isCollapsed |
MindMap 階層 |
mindMapTextNodes/mindMapCardNodes |
content/cardId |
節點內容;ID 與 mindMapNodes 共用 |
journalList/journalInstances |
date、content/journalDate、whiteboardId |
Journal 與它的位置 |
files |
id(即 fileId)、name、size、type |
附件的 metadata |
幾個容易出錯的地方:
- 連線端點的型別名稱和陣列名稱不一樣。 實測
beginObjectType/endObjectType的值有cardInstance、textElement、imageElement(對應mediaElements)、mindMapTextNode、mindMapCardNode、highlightElementInstance、pdfCardInstance、section。先列出所有出現過的值,再逐一對應。 - 有些 ID 是刻意共用的。
mindMapNodes和mindMapTextNodes/mindMapCardNodes用同一個 node ID,actionItems和actionItemAdds也是。檢查重複 ID 要在同一張表內做,不要跨表報告。 - Card 被丟進垃圾桶,instance 不一定跟著消失。 使用中的白板可能還有指向已刪除或不存在卡片的 instance,要列出來問使用者。
第二步:建立 Migration Decision Table
每一種 data type 都要回答:誰建立、是否刻意維護、是否有使用證據、關係價值、內容是否唯一、轉換品質,以及資料量與維護成本。
| Value/來源狀態 | 預設行為 |
|---|---|
| High value | Convert:轉換並保留內容、關係與 metadata。 |
| Medium/Ambiguous | Ask:先提出映射方式、風險與建議,只詢問真正需要決定的項目。 |
| Low value | Archive only:保留來源,不主動變成正式 Obsidian 內容。 |
| Source deleted/system artifact | Skip active migration:不恢復成正式內容,但保留排除原因與 audit evidence。 |
決策表至少包含 Data type / Count / Value / Recommended action / Reason。Count 必須標明範圍與單位,區分 unique content、instances、relationships 和 active/deleted;未知值寫「待盤點」,不要填成 0。
決策表要涵蓋 All-Data.json 裡每一個非空的陣列,不只 Card 和 Whiteboard。容易被漏掉的有:sections、connections、PDF cards、media cards、sources(例如 Readwise 匯入)、insights(Heptabase AI 自動產生的洞察,不是使用者寫的)、chats、templates、collections、tabs、actionItems。使用者價值不明的系統資料,預設 Archive only。
Journal 也要分開盤點:instance 可能指向沒有正文的日期,也可能有空白日記或匯出日期之後的日期。這些都列成例外,不要默默略過。
第三步:定義目標 Vault 邊界
建議讓資料夾主要表達來源、系統或 workflow,而不是讓 AI 自動推測內容分類:
Active Vault/
heptabase/ # 已核准的 Heptabase migration layer
daily/ # Journal / Daily Notes
_local/ # Obsidian-native 使用者內容
_meta/ # 必須留在 production 的 metadata preservation artifacts
Readwise/
ai-workspace/
.obsidian/
heptabase/ 內的子資料夾(例如 cards/、sources/、media/)是遷移時的設計選擇,不是 Heptabase 的原始結構;Heptabase export 的 Card Library 把 .md 放在同一層,附件放在各自的 -assets 子資料夾。分流規則要寫下來,才能重現。
重建用的 manifests、來源快照與 audit artifacts 應留在 migration workspace,不要因歷史範例而自動塞進 production Vault。每個 Whiteboard 可以保留自己的 Markdown、Canvas 與 assets 子資料夾,但移動任何檔案後,必須更新整個 Vault 的 references,而不只更新同一個白板或只更新 Canvas。
第四步:決定每種物件的 Obsidian 表示方式
先用 All-Data.json 確認 source object type、source ID 與 instance relationship,再決定 representation。不要只根據 Canvas 上看起來像文字或圖片來判斷。
| Heptabase object | 建議的 Obsidian representation |
|---|---|
| Card | Markdown note;Canvas 使用 file node 引用。 |
| 沒有標題的 Card | 讀取 content JSON 的 type 判斷;image Card 轉成含 embed 的 Markdown note 與 Canvas file node。不可因為 title 空白就略過或變成空白。 |
| Whiteboard | .canvas。 |
| Nested whiteboard | Parent Canvas 中指向 child Canvas 的 file node,保留 placement 與 geometry。 |
| MindMap | Canvas text/file nodes 加 edges,保留文字、階層與關係。 |
URL-only 或一般 textElement |
Canvas text node,保留可讀、可點擊的 URL。 |
含圖片或媒體的 rich textElement |
Markdown file node,保留內容與 embed order。 |
| Journal | 正文只存一份 daily/YYYY-MM-DD.md;各白板 instance 用 Canvas file node 引用。 |
| Highlight | 將 ProseMirror 文字與註解轉成可讀 Markdown;真正空白的才移除。 |
| Media card | Binary 可取得時保留媒體與 metadata;無法取得時留下可讀的缺失說明與 audit evidence。 |
| Tag/Property | Frontmatter;不要批次覆寫使用者已有的 metadata。 |
沒有標題,不等於沒有內容
Card 的 title 可以是空的,但 content 仍可能是一張完整的圖片:{"type":"image","attrs":{"fileId":"..."}}。以 title 是否存在來決定要不要轉換,會靜默遺失內容。實測中,17 個原本被轉成 *(empty card)* 的 Canvas nodes,回查後全部是可以復原的 image Card;另有 3 張來源真的沒有內容,才保留 empty representation。
注意「沒有標題的 image Card」和 mediaCards 是兩種不同的東西:前者是 cardList 裡 title 空白、content 是圖片的卡片,圖片通常在 export 裡找得到;後者是獨立的 media card,binary 常常不在 export 裡(見「Placeholder 是警訊」)。
檔名的預設規則
第一次建立 Obsidian 檔名,和事後 rename 是兩件事。下面是建議預設,POC 時先給使用者看,同意後再批次套用:
- 有標題:用標題當檔名。
- 沒有標題:依內容命名,例如取正文第一行的前 30 個字;圖片卡用「未命名圖片卡」加 6 碼 source ID。不要用 UUID 當主要檔名。
- Windows 與 Obsidian 不允許的字元(
\ / : * ? " < > |),以及會干擾連結的# ^ [ ]:換成全形字元或移除。 - 同名碰撞:加 6 碼 source ID 後綴,不要覆寫。
- 全部寫進 mapping log:source ID、原標題、最後的檔名、套用的規則。
Canvas 的最低資料
{
"id": "stable-node-id",
"type": "file|text|group",
"x": 0,
"y": 0,
"width": 420,
"height": 240,
"file": "vault/relative/path.md",
"text": "Markdown text"
}
JSON Canvas 並不要求 Tab 縮排。舊案例曾把 Canvas 開啟失敗歸因於 space indentation,但這不是通用規格。真正應驗證的是合法 JSON、欄位型別、唯一 node IDs、有效 edge endpoints、有限座標與正確的 Vault-relative paths。
幾個要事先決定、並寫進報告的對應:
- 摺疊的卡片(
isFolded: true):Canvas 沒有摺疊狀態。選擇用完整height(內容完整可見)或foldedHeight(外觀接近原本),全部一致。 - 箭頭樣式:
connections的beginStyle/endStyle對應 Canvas edge 的fromEnd/toEnd(arrow或none)。 - 連線文字:
description轉成 edge 的label。
MindMap 與 nested whiteboards
MindMap 不能因為目標格式不同就直接 skip。實測的來源結構(欄位名稱仍要先實際檢查):
mindMaps:MindMap 本體;mindMapInstances:它放在哪張白板、外框座標。mindMapNodes:階層,用parentId與childNodeIds表示;沒有獨立的 edges 陣列,edges 要從 parent → child 推出來。mindMapTextNodes/mindMapCardNodes:節點的文字(ProseMirror)或指向的 Card,ID 與mindMapNodes相同。
每個 instance 使用穩定 ID(instance ID + node ID)轉成 Canvas nodes 與 edges,edges 保留 fromNode/toNode/fromSide/toSide。節點本身沒有座標,用 instance 的 x/y/width/height 當外框,內部採可重現的樹狀 layout。boundingBoxRelativeX/Y 的確切意義尚未確認,不要單憑推測換算。
事先告訴使用者:MindMap 搬過去後會重新排版,外觀和 Heptabase 不同,但節點、文字與階層應該一致。 否則使用者很可能以為搬壞了。
Nested whiteboard 的關係記在 whiteboardInstances:whiteboardId 是 child,containerId 是 parent,containerType 為 whiteboard 時才是白板裡的白板(實測另有 map,是放在總覽地圖上的位置)。實測中同一張 child 可能同時放在兩張以上的 parent 裡,也可能有 containerId 指向不存在的白板,兩者都要列出來。
Nested whiteboard 的 fidelity 不只包括 child Canvas 檔案存在,還包括 parent → child relationship、child 在 parent 中的位置、child 自身內容與最終 reference。把所有 Canvas 攤平成互不相干的檔案,仍然是結構遺失;而且這種遺失不會產生任何 broken reference。
Journal
同一天的 Journal 正文只保存一份,多個白板 instances 都指向同一份 daily note。Manifest 應記錄 instance ID、日期、daily path、whiteboard ID、geometry 與 folded state。Unique dates、正文檔案數和 instances 數量要分開對帳。
第五步:用可回復的 Pipeline 執行
- Inspect export schema、來源狀態與 data types。
- 建立 Inventory 與 Migration Decision Table。
- 在隔離 test vault 重建內容、關係與空間結構。
- 驗證結構、references、metadata、assets 與代表性案例。
- 凍結通過結構驗證的 canonical source。
- 從副本建立 production candidate,才開始 usability cleanup。
- 若已有 Active Vault,先 audit user-created、user-modified 與 Active-preferred files。
- 建立完整可還原 backup 與 replace/import/merge mapping。
- 只取代已確認未修改的 migration layer;先保護使用者內容。
- 對 user-modified notes 做 semantic merge,不用新版直接覆蓋。
- 更新 Canvas、Markdown links、wikilinks、embeds 與 attachment references。任何 move/rename/資料夾重組之後都要重做這一步,範圍是整個 Vault 的筆記,不只 Canvas。
- 執行分層程式驗證與 Obsidian UI 抽查。
- 比較 canonical source 與 Active Vault 的相對路徑差集。
- 只有證據充分的 residuals 才移到 Vault 外 quarantine。
- 再跑一次完整驗證,保存 counts、hashes、例外與 quarantine report。
常見技術陷阱
ProseMirror 不是純文字
Heptabase 富文本使用 ProseMirror JSON。必須遞迴處理;空 paragraph 不一定有 content,不要用固定深度索引。
不只要取文字,還要把每一種 node 轉成對應的 Markdown。先列出資料裡實際出現過的所有 node 與 mark 類型,再逐一決定轉法,未處理的類型要報告,不要默默丟掉。實測出現過:
- Nodes:
paragraph、heading、bullet_list_item、numbered_list_item、todo_list_item、toggle_list_item、blockquote、code_block、horizontal_rule、table/table_row/table_cell/table_header、image、video、audio、file、math_inline/math_display、date、mention、card、whiteboard、pdf_card、image_card、video_card、highlight_element、section、chat、embed、hard_break。 - Marks:
strong、em、underline、strike、code、link、highlight、color、anchor。
兩個特別要處理的情況:
- 內部連結寫成網址。 指向其他卡片的連結可能是
https://app.heptabase.com/<space>/card/<id>(實測 553 個),要依 source ID 轉成 Obsidian 的 wikilink。 - 圖片直接內嵌在正文裡。 部分圖片以 base64
data:image/...存在content中(實測 16 個),要取出成獨立檔案再 embed。
Connection 的 description 也是 ProseMirror,轉成 Canvas edge 的 label 文字。
欄位名稱不能用猜的
各種 instance 指向來源物件的欄位名稱並不一致。實測中,mediaCardInstances 指向 media card 的欄位是 cardId,不是 mediaCardId;textElements 直接帶 whiteboardId,不經過 instance table。每種 instance 都要先實際檢查欄位,再寫 join 邏輯。
Placeholder 是警訊,不是終點
*(empty card)* 這類 placeholder 或 fallback,不代表轉換成功,而是在問:為什麼這個 source object 最後只能變成 placeholder?每個 placeholder 都要回查來源,分成「可以復原」與「來源真的沒有內容」。
同樣地,缺圖要分開兩種情況:Migration 漏掉的,與來源 export 本來就沒有 binary 的。若 source object、placement 與 fileId 都在,但 export 裡確實沒有檔案,標記為 source-unrecoverable 並保留 object 與位置,不要讓 Agent 反覆重試不存在的檔案。
獨立的 mediaCards 特別要先盤點比例。實測中,146 張圖片類 media card,以 files 表的大小比對,在 export 裡一張都找不到對應檔案,影片與音訊類也一樣;另有 78 張沒有 files 紀錄。遇到這種情況要在 Inventory 階段就告訴使用者,並建議他:重要的幾張可以回 Heptabase 手動另存。
媒體檔名可能碰撞
大量來源檔可能都叫 image.png,export 後才被加上數字後綴。export 的檔名裡也沒有 fileId。
建議的對應方式:
- 從
files表用fileId取得name與size。 - 在 export 裡找 size 相同的檔案當候選;再用檔名、所在的
-assets資料夾與卡片標題縮小範圍。 - 只剩一個候選時才算對上。多個候選時,比較內容 hash:內容完全相同就可以共用;內容不同就列為 ambiguous,不能直接選第一個。
- 找不到候選時,標記
source-unrecoverable。
重複的 Canvas
多輪遷移容易留下重複或過期的 Canvas。判斷方法是比對 Canvas node IDs 與 All-Data.json 的 source instance IDs:完全對不上的,是其他輪次留下的產物,應在備份後移除。不要用「檔名帶數字後綴」當作刪除條件。
路徑正規化與重新命名要分開
Canvas 內使用 / 與 Vault-relative paths。先以 reference syntax 解決 encoding 問題,rename 是最後手段。所有 move/rename 都保存 mapping log,至少包含來源 ID、old/new path、原因、規則版本、碰撞處理、受影響 references 與驗證結果。
Windows 長路徑支援取決於系統、API 與應用程式。\\?\ 可以協助某些工具讀取來源,但不能寫進 Canvas 或 Markdown references。縮短路徑時保留可辨識前綴、副檔名與穩定 hash,並檢查碰撞。
Markdown 圖片路徑含 )
實測 image embed 路徑含 ) 時,angle-bracket destination 仍可能無法正常 render。可嘗試將 ) 編碼為 %29、空格編碼為 %20,但最後仍需在 Obsidian 實際確認圖片顯示。Reference resolved 不等於 embed rendered。
PowerShell 的方括號
PowerShell 會把 [] 當 wildcard。驗證真實路徑時使用:
Test-Path -LiteralPath $path
否則可能把存在的檔案誤報為 broken reference。
Block IDs 與 transcripts
不要把每個來源 UUID 都變成使用者可見的 ^UUID。先建立 definitions 與 references 的雙向索引,只保留確實被 block link 或結構依賴使用的 anchors。實測中,保留全部 anchors 會產生超過六萬個可見 ID,但真正被引用的只有幾百個。
Transcript 應依來源順序轉成可讀、含 timestamp 的 Markdown;failed、processing 或 unavailable 狀態顯示清楚的 placeholder。Raw JSON 與 technical IDs 留在 audit layer,不進 normal reading content。
Existing Active Vault:不能直接覆蓋
先將檔案分類:
| 分類 | 處理原則 |
|---|---|
| old-migration-duplicate | 確認未被使用者修改後才可由新版取代。 |
| old-only-user-created | 保留;必要時搬到 _local/ 並更新 references。 |
| same-file-identical | 保留,不需要重寫。 |
| same-file-user-modified | 使用 baseline、Active 與新 reconstruction 做三方 semantic merge。 |
| attachments-old-only | 保留並檢查 Active references。 |
.obsidian 與 templates |
保留 Active 設定,不可整包覆蓋。 |
| Active-preferred | 明確記錄理由與 hash,保留 Active version。 |
Semantic merge 要保留使用者新增、修訂與刪除意圖,同時整合新版恢復的來源內容、frontmatter、附件和 links。缺 baseline 或無法可靠判斷時,保留雙方版本與差異供人工決定,不猜測使用者意圖。
較好的新版結構,應以 selective backport 回植已確認的改進(例如 nested whiteboards、MindMap 結構、漏掉的 image Cards),同時保留 Active Vault 既有的資料夾慣例、Journal 用法與使用者內容。遷移完成後,Active Vault 就是使用者的工作區;之後的每次修正都是 reconciliation,不是 overwrite。
Cleanup 優先移到 Vault 外 quarantine,不直接永久刪除。Quarantine report 應保存原路徑、新路徑、原因、來源判斷、hash、時間與 reference 影響。
四層驗證
| 層次 | 要回答的問題 |
|---|---|
| 內容完整 | 原始內容有沒有留下? |
| 關係完整 | Card、Whiteboard、MindMap 之間的關係有沒有留下? |
| 空間與結構 | 位置、階層與 nesting 是否合理保留? |
| 實際可用 | 在 Obsidian 裡是不是真的能讀、能開、能點? |
程式 audit 與實際 UI 使用,是兩個不同的驗收層級,要分開記錄。
內容完整
- Source snapshot、範圍、counts、paths 和 hashes 已保存。
- 每個 data type 都有來源數量、輸出數量與例外分類。
- Canonical source 在 import/merge 前後保持不變。
- 每個 placeholder 都已回查來源,並分類為「已復原」或「來源確實沒有內容」。
- Recoverable media 已恢復;unrecoverable 項目有來源證據且未被捏造或靜默刪除。
- User-created、user-modified 與 Active-preferred files 都有明確處理紀錄。
- Tags、Properties 與其他使用者 metadata 沒有被批次覆寫。
關係完整
- 所有 Canvas file nodes 指向 Vault 內存在的 mapped target。
- Markdown links、wikilinks、images、attachments 與 block links 分別驗證。
- 任何 rename/move/資料夾重組之後,全部筆記內的 references 都已依 mapping 更新,不只 Canvas。
- MindMap 的 node text、edges、hierarchy 與 relationships 已對帳。
- Journal instances 指向正確且唯一的 daily notes。
空間與結構
- 所有 Canvas 都是合法 JSON,node IDs 唯一,edges endpoints 有效。
- Nested whiteboard containment、placement 與 child contents 已對帳。
- Canvas node 的 type 與來源 object type 一致(圖片不是 text placeholder)。
- 重複或過期的 Canvas 已用 source ID 比對並處理。
實際可用
- 圖片與附件不只路徑存在,也能在 Obsidian 正常 render/開啟。
- 筆記正文沒有大量不必要的 UUID anchors 或 raw JSON。
- 抽查代表性白板:在 Obsidian 實際點擊卡片、連結與嵌入內容。
收尾
- Invalid Canvas JSON = 0。
- Canvas broken file refs = 0。
- 筆記內 Markdown links/wikilinks 的 broken 數量 = 0,或每一個都已分類說明。
- Unresolved recoverable items = 0。
- 已知無法恢復的項目逐筆分類並保留 evidence。
- Active-only residuals 全部分類;沒有未判定就刪除的檔案。
Zero broken references 並不能證明 structural fidelity。 如果一個 relation 根本沒被建立,就不會出現 broken reference:child Canvas、MindMap edges、geometry、內容順序或 media rendering 遺失時,即使每個 path 都存在,遷移仍未完成。
反過來也要注意,「Canvas broken refs = 0」只涵蓋 Canvas。實測中,最終驗收時 Canvas 全數正常,但資料夾重組只更新了 Canvas 內的路徑,筆記裡仍有約 1,700 個連結指向已不存在的舊資料夾;補做全 Vault 的筆記連結掃描後才發現並修復。
完成的定義
一次可信的遷移至少要能回答:
- 哪個來源 snapshot 被處理?
- 每一種資料被轉換、封存或排除的理由是什麼?
- 哪些資訊被完整保留,哪些只能部分保留?
- 哪些項目無法恢復,證據在哪裡?
- 使用者在 Active Vault 的修改是否仍在?
- 如果結果不理想,是否能回復到遷移前狀態?
Migration success 不是「所有檔案都存在」,而是內容、關係、空間結構與可用性都被保存,所有例外也誠實可追溯。這份 Guide 真正要回答的問題是:怎麼知道你真的搬到了你以為自己搬到的東西?
變更紀錄
- 1.0.1-beta(2026-09-25):依一次盲測修訂(一個不知道先前過程的 AI Agent,只靠 README 與本指南完成 Inventory 與一張白板的 POC)。新增:工作資料夾與報告位置、POC 選法與最低驗證、實測 schema 表、正文以
All-Data.json為準、檔名預設規則、fileId對應方式、ProseMirror 類型清單、Windows 長路徑提前檢查。更正:MindMap 欄位名稱、native export 其實可能有Mindmap/與Text Element/、Card Library 不是完全扁平、media card 缺檔的規模。 - 1.0.0-beta(2026-09-25):第一個公開版本。
版本、來源與授權
- Public Edition:
1.0.1-beta(依盲測結果修訂;變更見文末) - 技術來源:Heptabase → Obsidian Migration Guide v3.2 / field-tested specification(2026-09-21)
- 技術來源 SHA-256:
25F67AB318F56DD75F2FF24FB51AEFCF1E552CC6984CC54E7727ADD76E4B2DE8 - Public Edition 更新日期:2026-09-25
- 授權:Creative Commons Attribution 4.0 International(CC BY 4.0)
你可以依 CC BY 4.0 分享與改作這份 Public Edition,但必須標示作者 Cindy Young、作品名稱、授權方式,並說明是否做過修改。案例中的個人資料、原始 Vault、第三方圖片與軟體本身不因本文件授權而自動改變其權利狀態。