Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,3 @@
[submodule "mcp/che-pptx-mcp"]
path = mcp/che-pptx-mcp
url = git@github.com:PsychQuant/che-pptx-mcp.git
[submodule "cli/FastOCR"]
path = cli/FastOCR
url = https://github.com/PsychQuant/FastOCR.git
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ swift build

主 repo 以兩種方式追蹤外部 repo:

- **Submodule**(`.gitmodules`):`mcp/` 下三個 MCP server + `cli/FastOCR`。Clone 主 repo 時加 `--recurse-submodules` 會自動拉齊,或事後 `git submodule update --init --recursive`
- **Submodule**(`.gitmodules`):`mcp/` 下三個 MCP server。Clone 主 repo 時加 `--recurse-submodules` 會自動拉齊,或事後 `git submodule update --init --recursive`
- **Gitignore 忽略**(各自獨立管理):`packages/` 下的 Swift 套件、`reference/`。重建環境時在對應目錄 `git clone` 即可

| 目錄 | Git Remote | 說明 |
Expand All @@ -367,9 +367,10 @@ swift build
| `mcp/che-word-mcp` | https://github.com/PsychQuant/che-word-mcp.git | Word MCP(submodule) |
| `mcp/che-pdf-mcp` | https://github.com/PsychQuant/che-pdf-mcp.git | PDF MCP(submodule) |
| `mcp/che-pptx-mcp` | https://github.com/PsychQuant/che-pptx-mcp.git | PPTX MCP(submodule) |
| `cli/FastOCR` | https://github.com/PsychQuant/FastOCR.git | GLM-OCR PDF→Markdown CLI + 實驗 harness(submodule) |
| `reference/*` | 見 [`reference/README.md`](reference/README.md) | 外部參考 repo(docx-js、pandoc、mlx-swift-lm、swift-argument-parser)— clone-on-demand,只有 README 進版控 |

> **MeasureOCR 已遷出(2026-07-18)**:原 `cli/FastOCR`(後改名 MeasureOCR)是研究儀器而非文件工具,已遷至 `~/Developer/bestOCR/repos/measureOCR`(GitHub repo 同步改名 `PsychQuant/measureOCR`,舊 URL 自動轉址)。OCR **能力**不受影響——macdoc 的 PDF 工具照常透過 published package `packages/ocr-swift`(`PsychQuant/ocr-swift`)取用 OCR;搬走的只是 benchmark 儀器。遷移全紀錄見 bestOCR repo 的 `docs/migration-2026-07-18.md`。

## Key Files

### macdoc
Expand Down
1 change: 0 additions & 1 deletion cli/FastOCR
Submodule FastOCR deleted from aa99d1
10 changes: 5 additions & 5 deletions openspec/specs/ooxml-word-sync/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -1106,12 +1106,12 @@ code:
---
### Requirement: Sidecar persistence of snapshot and log

The orchestrator SHALL persist the operation log as `<docx-stem>.oplog.jsonl` and the most-recently-imported snapshot tree as `<docx-stem>.snapshot.json` in the same directory as the docx. The orchestrator SHALL NOT write metadata into the docx itself.
The orchestrator SHALL persist the operation log as `<docx-full-name>.oplog.jsonl` and the most-recently-imported snapshot tree as `<docx-full-name>.snapshot.json` in the same directory as the docx. For example, `report.docx` uses `report.docx.oplog.jsonl` and `report.docx.snapshot.json`. The orchestrator SHALL NOT write metadata into the docx itself. During migration, loaders SHALL fall back to the legacy stem paths (`report.oplog.jsonl` / `report.snapshot.json`) when the full-name path is absent; writers SHALL create only the full-name paths.

#### Scenario: Sidecar files created on first sync

- **WHEN** `SyncOrchestrator.bootstrapFromDocx(url:)` runs on a docx with no existing sidecar files
- **THEN** `<docx-stem>.oplog.jsonl` is created (initially empty) and `<docx-stem>.snapshot.json` is created with the initial tree snapshot
- **THEN** `<docx-full-name>.oplog.jsonl` is created (initially empty) and `<docx-full-name>.snapshot.json` is created with the initial tree snapshot

#### Scenario: docx contains zero sync metadata

Expand Down Expand Up @@ -1291,13 +1291,13 @@ The orchestrator SHALL provide `SyncOrchestrator.bootstrapFromDocx(url:)` that i

#### Scenario: Fresh docx without sidecars

- **GIVEN** `report.docx` exists with no `report.oplog.jsonl` and no `report.snapshot.json`
- **GIVEN** `report.docx` exists with no `report.docx.oplog.jsonl` and no `report.docx.snapshot.json`
- **WHEN** `bootstrapFromDocx(url:)` runs
- **THEN** sidecars are created with the docx's current state as the snapshot and an empty log; subsequent Swift mutations append to the new log

#### Scenario: Existing sidecars are reused

- **GIVEN** `report.docx`, `report.oplog.jsonl` (with prior history), and `report.snapshot.json` (from prior session) all exist
- **GIVEN** `report.docx`, `report.docx.oplog.jsonl` (with prior history), and `report.docx.snapshot.json` (from prior session) all exist
- **WHEN** `bootstrapFromDocx(url:)` runs
- **THEN** the orchestrator loads the existing log and snapshot; if the docx has changed since the snapshot's timestamp, an import diff is run to capture the intervening changes

Expand Down Expand Up @@ -1992,4 +1992,4 @@ code:
- docs/stale-triage-chain-batch2-2026-05-25.md
- .remember/logs/autonomous/save-144550.log
- .remember/logs/autonomous/save-143700.log
-->
-->
39 changes: 39 additions & 0 deletions reference/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ git clone https://github.com/python-openxml/python-docx.git
git clone https://github.com/ml-explore/mlx-swift-lm.git
git clone https://github.com/jgm/pandoc.git
git clone https://github.com/apple/swift-argument-parser.git
git clone --depth 1 https://github.com/genspark-ai/genoffice.git # 43M,只讀 source 不需要 history
# textutil-manpage.txt 已在版控中
```

Expand All @@ -22,6 +23,7 @@ git clone https://github.com/apple/swift-argument-parser.git
| `python-docx/` | git repo | https://github.com/python-openxml/python-docx | Python + lxml 的 OOXML 函式庫。**典範:tree-backed wrapper**——每個 `Document` / `Paragraph` / `Run` 包一個 lxml `_Element`,typed accessor 讀寫該 element。`word-aligned-state-sync` 的 Phase 1 (typed views as tree projections) 直接對照它。詳見 `docs/docx-libraries-comparison.md` |
| `mlx-swift-lm/` | git repo | https://github.com/ml-explore/mlx-swift-lm | Apple MLX Swift LLM runtime。`pdf-to-latex-swift` Phase 1 的 local GLM-OCR backend 用它載模型 |
| `pandoc/` | git repo | https://github.com/jgm/pandoc | Haskell 文件轉換工具。macdoc 不依賴 pandoc,純粹參考它怎麼處理邊界情況(複雜 table、field、footnote 跨段落) |
| `genoffice/` | git repo (shallow) | https://github.com/genspark-ai/genoffice | Genspark 的 AI-native office suite(Electron GUI)。**不是競品**——它沒有 MCP / CLI / public API,AI 綁自家雲端帳號;但 `packages/` 下的 engine 是純 TS、無 Electron 依賴,是**唯一同時涵蓋 docx + xlsx + pptx + pdf 的現代開源對照組**。看三件事:xlsx 缺口、patch-narrowly round-trip、pptx 功能廣度。Apache-2.0(`ee/` 另授權) |
| `swift-argument-parser/` | git repo | https://github.com/apple/swift-argument-parser | Apple 官方 CLI 解析器。`macdoc` CLI 已是使用者,這裡留一份方便查 source-level 行為(尤其是 subcommand dispatch、ExitCode) |
| `textutil-manpage.txt` | 單檔 | [textutil(1) macOS man page](https://ss64.com/mac/textutil.html) | macOS 內建 `textutil` 的 manual。`macdoc convert` 的 CLI 語法對齊 textutil(見 `.claude/rules/cli-design/textutil-compat.md`),改 CLI 前對一下 |

Expand Down Expand Up @@ -67,6 +69,43 @@ git clone https://github.com/apple/swift-argument-parser.git
- `pandoc/src/Text/Pandoc/Readers/HTML.hs` — HTML 讀取邏輯
- `pandoc/src/Text/Pandoc/Writers/Markdown.hs` — Markdown 輸出邏輯

### genoffice → xlsx 缺口 / patch-narrowly round-trip / pptx 功能廣度

先講清楚定位,免得誤判:genoffice 是**桌面 GUI 套裝軟體**(Electron,五個 app 共用 engine 層),macdoc 是 CLI + MCP 的管線工具,兩者不是同一個品類,也不互相取代。它沒有 MCP server、沒有 public API、沒有自動化 CLI,AI 走 Genspark 帳號的雲端(本機不存 API key)。

**真正有參考價值的是 `packages/` 那層**——官方描述為「All pure TypeScript, no Electron dependency」,可以完全脫離 GUI 單獨閱讀。這是目前少見的、同時涵蓋 docx + xlsx + pptx + pdf 四種格式的現代開源實作。

三個具體對照點:

**1. xlsx——macdoc 唯一缺的 OOXML 主格式**

macdoc 有 word / pptx / keynote / pdf,沒有 Excel。genoffice 的做法是 UI 用 Univer core(Apache-2.0),import/export 走 Rust sidecar(calamine + IronCalc)。
- `genoffice/packages/file-parse/src/xlsx.ts` — 解析入口,先看它把 xlsx 拆成什麼中介結構
- `genoffice/apps/sheets/` — 上層怎麼消費

注意這是「要不要做 xlsx」的判斷材料,不是「該做」的理由。先問自己的工作流有沒有 Excel 需求,不要為了對齊功能表而追賽道。

**2. patch-narrowly / byte-preserving round-trip——跟 `ooxml-swift` op log 同目標、不同解法**

genoffice 的 docx 存檔只重新產生被改動的段落,未觸碰的 block 保留原始 bytes,其餘 zip entry 逐 byte 複製。目的跟 `word-aligned-state-sync` 的 op log 一樣(存檔不破壞 Word 版面),但走的是「差異化重生」而非「op 重放」。
- `genoffice/packages/docx-engine/src/patch.ts` — 核心 patch 邏輯
- `genoffice/packages/docx-engine/src/text-patch.ts` — 文字層級的窄幅修改
- `genoffice/packages/docx-engine/src/parse.ts` / `scan.ts` — 解析與掃描(patch 的前置)
- `genoffice/packages/docx-engine/src/generate.ts` — 產生端
- `genoffice/packages/pptx-engine/src/zip.ts` — zip entry 保留策略

對照重點:python-docx 是「直接 mutate tree、沒有 op log」,genoffice 是「保留原 bytes + 窄幅重生」,macdoc 是「op log 重放」。三種解法擺在一起看,才知道 op log 的成本換到了什麼。

**3. pptx 功能廣度——`pptx-swift` 的擴充 checklist**

`genoffice/packages/pptx-engine/src/` 有 macdoc 目前沒有的項目,可當功能對照表:`smartart.ts` / `smartart-layout.ts`、`custgeom.ts`(自訂幾何)、`animation.ts`、`theme-apply.ts`、`format-brush.ts`、`slide-transfer.ts`、`table-style.ts`。

**授權注意**:Apache-2.0,但 `ee/` 目錄保留給未來的 enterprise 模組(另一套 GenOffice Enterprise License)。GenOffice / Genspark 名稱與 logo 是 Mainfunc, Inc. 商標。當**設計參考**讀沒問題;若要逐行移植實作到 Swift,先確認 Apache-2.0 的 NOTICE 保留義務與 macdoc 自身授權相容。

**成熟度警語**:clone 當下(2026-08-04)發布版是 v0.4.110,pre-1.0,354 stars / 3 watchers。`main` 的 commit history 極短且訊息形如 `Sync snapshot (2026-08-03) (#6)`——是**內部開發、定期推 snapshot 出來**的模式,不是長期公開開發史,所以看不到 PR 討論、design rationale、bug 修復的來龍去脈,只能讀最終碼。程式碼本身看起來紮實(有 CI、Vitest、ESLint、SECURITY.md),但**不要當作經過長期生產驗證的參考**——跟 pandoc、python-docx 那種十年老專案不是同一個信賴等級。

上游仍在活躍同步(上次 snapshot 距 clone 僅一天),所以這份 clone 會很快過時。要更新直接 `cd reference/genoffice && git pull`;因為是 `--depth 1` 淺 clone,必要時 `git fetch --unshallow` 才拿得到完整歷史(但如上所述,歷史本身資訊量不高)。

### swift-argument-parser → `macdoc` CLI

`macdoc` 的 `Sources/MacDocCLI/` 全部基於 `ArgumentParser`。遇到奇怪行為(subcommand completion、async command、ValidationError 訊息控制)時查:
Expand Down
Loading