session-to-obsidian 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ name: ci
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ jobs:
7
+ test:
8
+ runs-on: ubuntu-latest
9
+ strategy:
10
+ matrix:
11
+ python-version: ["3.10", "3.12"]
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: ${{ matrix.python-version }}
17
+ - run: pip install pytest -e .
18
+ - run: pytest -q
@@ -0,0 +1,19 @@
1
+ name: release
2
+ # 推一個版本 tag(例如 v0.1.0)就自動 build + 發佈到 PyPI。
3
+ # 用 PyPI Trusted Publishing(OIDC),不需要任何 token/secret。
4
+ on:
5
+ push:
6
+ tags: ["v*"]
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ id-token: write # Trusted Publishing 需要;PyPI 用它驗證身分,免 token
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.12"
17
+ - run: pip install build
18
+ - run: python -m build
19
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ venv/
8
+ .pytest_cache/
9
+ .DS_Store
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. Format follows
4
+ [Keep a Changelog](https://keepachangelog.com/), and this project adheres to
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-06-19
10
+
11
+ Initial release.
12
+
13
+ ### Added
14
+ - `s2o import <file> --vault <dir>` — import a Claude Code or GitHub Copilot
15
+ Chat session into Obsidian as a topic-organized recap note + topic-grouped
16
+ Canvas, and update a cross-session `INDEX.md`.
17
+ - Auto source detection: Claude native jsonl, Copilot Chat append-log (replayed).
18
+ - Semantic topic clustering via the local `claude` CLI (headless, no API key);
19
+ `--cluster time` fallback with no LLM.
20
+ - Bilingual output (`--lang en|zh-TW`, default `en`; `S2O_LANG` env).
21
+ - Frontmatter `originSessionId` for dedupe — re-importing a session replaces it.
22
+ - `s2o index` to rebuild the index.
23
+
24
+ [Unreleased]: https://github.com/timo9378/session-to-obsidian/compare/v0.1.0...HEAD
25
+ [0.1.0]: https://github.com/timo9378/session-to-obsidian/releases/tag/v0.1.0
@@ -0,0 +1,40 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest! This is a small, dependency-free tool — contributions that keep it simple are very welcome.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ git clone https://github.com/timo9378/session-to-obsidian
9
+ cd session-to-obsidian
10
+ pip install pytest -e . # or use a venv
11
+ pytest
12
+ ```
13
+
14
+ ## Project layout
15
+
16
+ ```
17
+ s2o/
18
+ parse.py jsonl → steps (no LLM)
19
+ adapters/ source detection + restore (copilot append-log replay, claude native)
20
+ cluster.py ask-list → {title, topics}; backends: claude_cli (host Claude), time
21
+ render.py steps + topics → .md (TOC + topic sections) + .canvas
22
+ index.py sessions/ → INDEX.md
23
+ slugs.py title → cross-platform filename slug
24
+ i18n.py output labels + clustering prompt (en / zh-TW)
25
+ cli.py orchestration (import / index)
26
+ ```
27
+
28
+ Design rationale lives in [SPEC.md](SPEC.md) — read it before changing clustering or rendering.
29
+
30
+ ## Guidelines
31
+
32
+ - **No runtime dependencies.** Standard library only. The LLM step shells out to the `claude` CLI.
33
+ - Add a test for any bug you fix or behavior you add (`tests/test_s2o.py`).
34
+ - The hard-won bits (Copilot append-log replay, heading-demote + fence-balancing, topic clustering contract) are covered by tests — keep them green.
35
+ - Adding a new source (e.g. another AI IDE)? Write an adapter in `s2o/adapters/` returning `(records, meta)` and wire it into `detect()`.
36
+ - Adding a language? Extend `LABELS` and `_RULES_NAME` in `s2o/i18n.py`.
37
+
38
+ ## Reporting issues
39
+
40
+ Include the source type (Claude / Copilot), the command you ran, and what the output looked like vs what you expected.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 timo9378
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,93 @@
1
+ Metadata-Version: 2.4
2
+ Name: session-to-obsidian
3
+ Version: 0.1.0
4
+ Summary: 把 Claude Code / GitHub Copilot 對話 session 變成 Obsidian 裡依語意主題分區、可翻找、可給 Claude compact 後回想的回顧筆記
5
+ Project-URL: Homepage, https://github.com/timo9378/session-to-obsidian
6
+ Project-URL: Repository, https://github.com/timo9378/session-to-obsidian
7
+ Project-URL: Issues, https://github.com/timo9378/session-to-obsidian/issues
8
+ Project-URL: Changelog, https://github.com/timo9378/session-to-obsidian/blob/main/CHANGELOG.md
9
+ Author: timo9378
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: claude-code,copilot,knowledge-management,llm,obsidian
13
+ Classifier: Environment :: Console
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Utilities
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+
21
+ # session-to-obsidian
22
+
23
+ [![CI](https://github.com/timo9378/session-to-obsidian/actions/workflows/ci.yml/badge.svg)](https://github.com/timo9378/session-to-obsidian/actions/workflows/ci.yml)
24
+ [![PyPI](https://img.shields.io/pypi/v/session-to-obsidian.svg)](https://pypi.org/project/session-to-obsidian/)
25
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
26
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
27
+
28
+ Turn a **Claude Code** or **GitHub Copilot Chat** session into an Obsidian note that is organized **by topic**, easy to browse, good for writing from, and useful as a memory layer your AI can recall after a context compaction.
29
+
30
+ 繁體中文說明見 **[README.zh-TW.md](README.zh-TW.md)**.
31
+
32
+ Not a verbatim log dump. It splits one session into **coherent topic threads** (not time slices). Under each topic: *what you asked + the assistant's full narrative + the tools/files/screenshots involved*, with a table of contents on top — plus a cross-session `INDEX.md`.
33
+
34
+ ## Why
35
+
36
+ One AI coding session often does several unrelated things (fix a bug, tweak some UI, detour into something else). Later you want to find "how did I solve that thing" — and a timeline is useless, because what you remember is the **topic**, not the timestamp. This tool uses an LLM to cluster steps into topics so the note is organized the way you remember it.
37
+
38
+ The first audience is the author's own future self (a tool you keep using); open source is a bonus.
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pipx install session-to-obsidian
44
+ ```
45
+
46
+ Topic clustering calls your **local `claude` CLI** (Claude Code) in headless mode — no API key, uses your existing subscription. No Claude Code? It still works, clustering just degrades to time-slicing (see below).
47
+
48
+ ## Usage
49
+
50
+ ```bash
51
+ # A Claude Code session (jsonl lives in ~/.claude/projects/<proj>/<uuid>.jsonl)
52
+ s2o import ~/.claude/projects/myproj/abc123.jsonl --vault ~/Obsidian
53
+
54
+ # A Copilot Chat session (VS Code's chatSessions/<uuid>.jsonl append-log)
55
+ s2o import "~/…/workspaceStorage/<hash>/chatSessions/<uuid>.jsonl" --vault ~/Obsidian
56
+
57
+ # Output language (labels + generated titles). Default: en
58
+ s2o import <file> --vault ~/Obsidian --lang zh-TW
59
+
60
+ # No LLM — pure time-segmentation (offline / no Claude Code)
61
+ s2o import <file> --vault ~/Obsidian --cluster time
62
+
63
+ # Rebuild the index only
64
+ s2o index --vault ~/Obsidian
65
+ ```
66
+
67
+ Source (Claude native / Copilot append-log) is auto-detected. Output goes to `<vault>/90-Meta/sessions/<date>-<source>-<title>/` with a `.md` (recap) and `.canvas` (topic-grouped node graph). Set `S2O_VAULT` to skip `--vault`; `S2O_LANG` to set a default language. Re-importing the same session replaces the previous note (deduped by an `originSessionId` stored in frontmatter).
68
+
69
+ ## How it works
70
+
71
+ ```
72
+ detect source → adapter restores steps → extract "ask + files + first-line gist"
73
+ → cluster (LLM) → render .md/.canvas → update INDEX.md
74
+ ```
75
+
76
+ Clustering only feeds the LLM **the ask list + each step's touched filenames + the assistant's first line** (never the full transcript), so token use is bounded. Compound asks go to their dominant topic; topics may be non-contiguous in time; each step keeps its original number.
77
+
78
+ ## Limitations
79
+
80
+ - **Topic clustering needs the local `claude` CLI** (Claude Code). Without it, use `--cluster time`.
81
+ - Images are not extracted by default (`--images` to enable). By Obsidian convention images live under `_attachments/` (gitignore them; sync via Syncthing to avoid bloating git).
82
+ - Topic names / titles are LLM-generated; quality depends on the model. Re-run a single session to regenerate.
83
+
84
+ ## Development
85
+
86
+ ```bash
87
+ pip install pytest -e .
88
+ pytest
89
+ ```
90
+
91
+ ## License
92
+
93
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,73 @@
1
+ # session-to-obsidian
2
+
3
+ [![CI](https://github.com/timo9378/session-to-obsidian/actions/workflows/ci.yml/badge.svg)](https://github.com/timo9378/session-to-obsidian/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/session-to-obsidian.svg)](https://pypi.org/project/session-to-obsidian/)
5
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
+
8
+ Turn a **Claude Code** or **GitHub Copilot Chat** session into an Obsidian note that is organized **by topic**, easy to browse, good for writing from, and useful as a memory layer your AI can recall after a context compaction.
9
+
10
+ 繁體中文說明見 **[README.zh-TW.md](README.zh-TW.md)**.
11
+
12
+ Not a verbatim log dump. It splits one session into **coherent topic threads** (not time slices). Under each topic: *what you asked + the assistant's full narrative + the tools/files/screenshots involved*, with a table of contents on top — plus a cross-session `INDEX.md`.
13
+
14
+ ## Why
15
+
16
+ One AI coding session often does several unrelated things (fix a bug, tweak some UI, detour into something else). Later you want to find "how did I solve that thing" — and a timeline is useless, because what you remember is the **topic**, not the timestamp. This tool uses an LLM to cluster steps into topics so the note is organized the way you remember it.
17
+
18
+ The first audience is the author's own future self (a tool you keep using); open source is a bonus.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pipx install session-to-obsidian
24
+ ```
25
+
26
+ Topic clustering calls your **local `claude` CLI** (Claude Code) in headless mode — no API key, uses your existing subscription. No Claude Code? It still works, clustering just degrades to time-slicing (see below).
27
+
28
+ ## Usage
29
+
30
+ ```bash
31
+ # A Claude Code session (jsonl lives in ~/.claude/projects/<proj>/<uuid>.jsonl)
32
+ s2o import ~/.claude/projects/myproj/abc123.jsonl --vault ~/Obsidian
33
+
34
+ # A Copilot Chat session (VS Code's chatSessions/<uuid>.jsonl append-log)
35
+ s2o import "~/…/workspaceStorage/<hash>/chatSessions/<uuid>.jsonl" --vault ~/Obsidian
36
+
37
+ # Output language (labels + generated titles). Default: en
38
+ s2o import <file> --vault ~/Obsidian --lang zh-TW
39
+
40
+ # No LLM — pure time-segmentation (offline / no Claude Code)
41
+ s2o import <file> --vault ~/Obsidian --cluster time
42
+
43
+ # Rebuild the index only
44
+ s2o index --vault ~/Obsidian
45
+ ```
46
+
47
+ Source (Claude native / Copilot append-log) is auto-detected. Output goes to `<vault>/90-Meta/sessions/<date>-<source>-<title>/` with a `.md` (recap) and `.canvas` (topic-grouped node graph). Set `S2O_VAULT` to skip `--vault`; `S2O_LANG` to set a default language. Re-importing the same session replaces the previous note (deduped by an `originSessionId` stored in frontmatter).
48
+
49
+ ## How it works
50
+
51
+ ```
52
+ detect source → adapter restores steps → extract "ask + files + first-line gist"
53
+ → cluster (LLM) → render .md/.canvas → update INDEX.md
54
+ ```
55
+
56
+ Clustering only feeds the LLM **the ask list + each step's touched filenames + the assistant's first line** (never the full transcript), so token use is bounded. Compound asks go to their dominant topic; topics may be non-contiguous in time; each step keeps its original number.
57
+
58
+ ## Limitations
59
+
60
+ - **Topic clustering needs the local `claude` CLI** (Claude Code). Without it, use `--cluster time`.
61
+ - Images are not extracted by default (`--images` to enable). By Obsidian convention images live under `_attachments/` (gitignore them; sync via Syncthing to avoid bloating git).
62
+ - Topic names / titles are LLM-generated; quality depends on the model. Re-run a single session to regenerate.
63
+
64
+ ## Development
65
+
66
+ ```bash
67
+ pip install pytest -e .
68
+ pytest
69
+ ```
70
+
71
+ ## License
72
+
73
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,69 @@
1
+ # session-to-obsidian
2
+
3
+ [![CI](https://github.com/timo9378/session-to-obsidian/actions/workflows/ci.yml/badge.svg)](https://github.com/timo9378/session-to-obsidian/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/session-to-obsidian.svg)](https://pypi.org/project/session-to-obsidian/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+
7
+ 把一場 **Claude Code** 或 **GitHub Copilot Chat** 的對話 session,變成 Obsidian 裡**依語意主題分區、可快速翻找、可給 Claude compact 後回想、可拿來寫文章**的回顧筆記。
8
+
9
+ > English: **[README.md](README.md)**
10
+
11
+ 不是逐字 log dump。它把一場 session 拆成**連貫的主題串**(不是時間段),每條主題底下是「我問 + 助手完整敘述 + 用到的工具/檔案/截圖」,頂部附目錄,並維護一份跨 session 的 `INDEX.md` 索引。
12
+
13
+ ## 為什麼
14
+
15
+ AI coding 一場 session 常常一次解好幾件事(修個 bug、順手改 UI、中間插一段別的)。事後想翻「那個問題當時怎麼解的」,時間軸沒用——你記得的是**主題**,不是幾點做的。這工具用 LLM 把步驟分到主題,讓筆記照你記憶的方式組織。第一受眾是作者本人;開源是順手。
16
+
17
+ ## 安裝
18
+
19
+ ```bash
20
+ pipx install session-to-obsidian
21
+ ```
22
+
23
+ 語意分群預設呼叫你**本機的 `claude` CLI**(Claude Code)——零 API 金鑰、吃你現有訂閱。沒裝 Claude Code 也能用,分群退化成時間分段。
24
+
25
+ ## 用法
26
+
27
+ ```bash
28
+ # Claude Code session(jsonl 在 ~/.claude/projects/<專案>/<uuid>.jsonl)
29
+ s2o import ~/.claude/projects/myproj/abc123.jsonl --vault ~/Obsidian
30
+
31
+ # Copilot Chat session(VS Code 的 chatSessions/<uuid>.jsonl append-log)
32
+ s2o import "~/…/workspaceStorage/<hash>/chatSessions/<uuid>.jsonl" --vault ~/Obsidian
33
+
34
+ # 輸出語言(標籤 + 產生的標題),預設 en
35
+ s2o import <file> --vault ~/Obsidian --lang zh-TW
36
+
37
+ # 不靠 LLM,純時間分段(離線 / 無 Claude Code)
38
+ s2o import <file> --vault ~/Obsidian --cluster time
39
+
40
+ # 只重建索引
41
+ s2o index --vault ~/Obsidian
42
+ ```
43
+
44
+ 來源自動偵測。輸出到 `<vault>/90-Meta/sessions/<日期>-<來源>-<標題>/`(`.md` + `.canvas`)。`S2O_VAULT` 可省 `--vault`,`S2O_LANG` 設預設語言。重匯同一場會自動取代舊版(靠 frontmatter 的 `originSessionId` 去重)。
45
+
46
+ ## 它怎麼運作
47
+
48
+ ```
49
+ 偵測來源 → adapter 還原步驟 → 抽「提問+檔名+助手首句」→ 分群(LLM)→ 渲染 .md/.canvas → 更新 INDEX.md
50
+ ```
51
+
52
+ 分群只餵**提問清單 + 該步檔名 + 助手首句**(不餵全文),token 有界。複合提問歸主導主題、主題可跨時間非連續、每步保留原始步號。
53
+
54
+ ## 限制
55
+
56
+ - **語意分群需要本機 `claude` CLI**(Claude Code)。沒有就 `--cluster time`。
57
+ - 圖片預設不抽(`--images` 開啟);圖走 `_attachments/`(自行 gitignore + Syncthing,避免 git 變肥)。
58
+ - 主題命名/標題由 LLM 產,品質視模型而定;不滿意可重跑單場。
59
+
60
+ ## 開發
61
+
62
+ ```bash
63
+ pip install pytest -e .
64
+ pytest
65
+ ```
66
+
67
+ ## License
68
+
69
+ MIT — 見 [LICENSE](LICENSE)。
@@ -0,0 +1,58 @@
1
+ # session-to-obsidian — 專案規格(SPEC)
2
+
3
+ 把 Claude Code session(jsonl)轉成 Obsidian 裡**可快速翻找、可給 Claude compact 後回想、可拿來寫文章**的結構化紀錄。
4
+
5
+ ## 1. 目的 / 受眾
6
+ - **單場回顧 + 寫文章素材**:翻找「某問題當時怎麼解的」,加速回憶;`.md` 是主力。
7
+ - **Claude 跨 compact 的記憶層**:context 被壓縮後,Claude 能回去查前面做了什麼/怎麼解 → 這份結構化紀錄是持久記憶。
8
+ - **受眾**:AI coding agent 使用者 ∩ Obsidian 使用者(交集窄)。**第一受眾是作者本人**(永久使用者容器);開源是順手,不以採用率為成敗。
9
+
10
+ ## 2. 形態 / 發佈
11
+ - **一個自有專案**,發佈成 **Claude Code skill/plugin(SKILL.md + 腳本)**。
12
+ - **語意那步用 host 的 Claude(零外部 LLM 依賴)**;想跑 standalone/cron 再選配外部/地端 LLM(hybrid)。
13
+ - 「skill」只是呼叫格式,骨子是專案。skill 不穩的疑慮 → 把 agent 的工作限到**只有「主題分群」**,其餘全是死板腳本。
14
+
15
+ ## 3. 分工(誰做、要不要 LLM)
16
+ | 步驟 | 誰 | LLM |
17
+ |---|---|---|
18
+ | 解析 jsonl → 步驟/提問/工具/檔案/截圖/時間 | 腳本 | ❌ |
19
+ | **把步驟分到主題串 + 命名** | host agent(隔離 sub-agent) | ✅ 唯一一處 |
20
+ | 組 `.md`(目錄 + 主題分區 + 提問當小標 + 完整作法 + 截圖) | 腳本 | ❌ |
21
+ | 建圖 (b) 單場主題級 / (c) 跨 session | 腳本(有標籤就純機械) | ❌ |
22
+ | 儲存(git 文字 / Syncthing 圖) | 腳本 | ❌ |
23
+
24
+ ## 4. Token 控制(主題分群)
25
+ - 原則 = **不餵全 transcript**,但給「便宜且高鑑別度」的結構訊號。實測(2026-06-19,dcfce9f1):
26
+ **只餵提問清單會壞** —— context-dependent 的提問(「這是連結」「放到 X 後要 run…」)光看字面無法歸線,
27
+ agent 會把它併進前一條主題、還生出空主題。**修法 = 提問 + 該步碰的檔名(basename,上限 6)+ 助手首句 gist(上限 160 字)**,
28
+ 量仍有界(每步 ≈ 一兩百字),救回 context-dependent 步驟。`--dump-intents` 已內建這三項。
29
+ - 跑在**隔離 sub-agent(Task)**:不吃主 session context、不害它提早 compact。各場 sub-agent 讀自己的 dump、寫自己的 topics.json。
30
+ - 分群契約:每步恰好一主題(不重不漏,render 端驗證);**主題可非連續**(隔時收尾步歸回原線);複合提問不拆、歸主導線;
31
+ 主題按最早步號排;步號保留原始編號(主題視圖仍可還原時序)。tiny 場(≤5步同一事)→ 1 主題,別硬切。
32
+
33
+ ## 5. 輸出
34
+ - **`<slug>.md`**:每步「我問 + 助手完整敘述 + 工具/檔案 + 截圖」,依主題分區 + 頂部目錄。**核心價值**。導覽靠 Obsidian 原生 Outline/搜尋。
35
+ - **(b) 單場主題級節點圖**:主題串當節點(非每步、非檔案 hub —— 那是毛球教訓)。對「一場多主題」的 session 有用。
36
+ - **(c) 跨 session 圖**:每場一節點。關聯訊號(由強到弱):**共用檔案(權重,設門檻)+ 同專案分簇 + 共用主題標籤(白拿)**;**embedding 語意相似 = opt-in**(新依賴);模糊字串搜尋最弱、不用。避免毛球 → 只用強邊。
37
+
38
+ ## 6. 儲存架構(已定案)
39
+ - **git 只放文字**(.md/.canvas)→ 永不肥。
40
+ - **圖走 Syncthing 的 vault `_attachments/`**(gitignore)→ server↔手機,免 git bloat。
41
+ - 全圖原檔私有封存;文章要用的圖才逐張走 blog pipeline 公開(session 截圖含敏感,別全公開)。
42
+ - 手機:termux SSH(免 PAT)或 Obsidian-Git(HTTPS+PAT);**手機 pull-only**(多寫手會打架);Android 共用儲存 `git config core.filemode false`。
43
+
44
+ ## 7. 未解 / 待做(下次開工)
45
+ - ✅ **主題分群 step**(2026-06-19 完成):`--dump-intents`(提問+files+gist)→ 隔離 sub-agent 分群+命名+總標題 → `--topics` render。20 場已重匯。
46
+ - ✅ **.md 導覽**:頂部 TOC(wikilink heading 連結)+ 主題分區 + 原始步號;助手敘述的 ATX 標題 demote 成粗體、fence 強制配對 → Obsidian Outline 只剩 主題/步驟。
47
+ - ✅ **手刻 mesh 砍掉**(候選3降範圍);單場 `.canvas` 改主題分組彩色框。
48
+ - **(b)(c) 圖的呈現形式**:仍未做。傾向原生 graph(linked notes,scoped local graph)而非手刻布局。跨 session(c)未動。
49
+ - ✅ **檔名帶標題**(2026-06-19):session 資料夾/檔名 = `<date>-<source>-<標題slug>`(跨平台 sanitize + 截長),側欄一眼看出主題,不再是 uuid。
50
+ - ✅ **recall 入口**(2026-06-19):`build_index.py` 掃 `sessions/*/*.md` 產 `INDEX.md`(全 session + 主題地圖,按月分組)。
51
+ 雙受眾:人一頁掃完、Claude compact 後讀這張當 recall 地圖(找到 → 開 .md → Outline 跳主題)。慣例已寫進 vault `CLAUDE.md`。
52
+ 進階(未做):`/recall <query>` skill 直接回傳命中主題(屬候選5打包範圍)。
53
+ - **打包成 skill/plugin**(SKILL.md 把 extract→cluster→render 串起來)+ 選配 standalone CLI。骨架已成形(三段拆分),待寫 SKILL.md。
54
+
55
+ ## 現況(2026-06-19)
56
+ `session_to_obsidian.py`:解析 → `--dump-intents`(提問+files+gist 給分群 agent)→ `--topics`(吃 {title,topics} render 主題分區 .md + TOC + 主題分組 canvas);沒給 `--topics` 退回時間分段。`NOIMG=1` 純文字。
57
+ **20 場全部以主題分群重匯完成**(9 copilot + 11 Claude),coverage/headings/fence parity 全驗證通過。dump/topics 暫存 `/tmp/s2o/`。mesh 已移除。
58
+ 待:SKILL.md 打包、recall 入口、(b)(c) 圖、選配重抽圖(目前 text-only)。
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "session-to-obsidian"
7
+ version = "0.1.0"
8
+ description = "把 Claude Code / GitHub Copilot 對話 session 變成 Obsidian 裡依語意主題分區、可翻找、可給 Claude compact 後回想的回顧筆記"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "timo9378" }]
14
+ keywords = ["claude-code", "copilot", "obsidian", "llm", "knowledge-management"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Environment :: Console",
20
+ "Topic :: Utilities",
21
+ ]
22
+ dependencies = [] # 純標準函式庫;語意分群靠本機 `claude` CLI(選用)
23
+
24
+ [project.scripts]
25
+ s2o = "s2o.cli:main"
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/timo9378/session-to-obsidian"
29
+ Repository = "https://github.com/timo9378/session-to-obsidian"
30
+ Issues = "https://github.com/timo9378/session-to-obsidian/issues"
31
+ Changelog = "https://github.com/timo9378/session-to-obsidian/blob/main/CHANGELOG.md"
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["s2o"]
@@ -0,0 +1,2 @@
1
+ """session-to-obsidian:把 Claude Code / Copilot session 變成 Obsidian 主題回顧。"""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,29 @@
1
+ """來源偵測 + 載入。回傳 (records[Claude格式], meta{date,title})。"""
2
+ from __future__ import annotations
3
+ import json
4
+ from . import claude, copilot
5
+
6
+
7
+ def detect(src_path) -> str:
8
+ """嗅第一行:Copilot append-log 首行有 kind 鍵;Claude 原生是 message/type 訊息。"""
9
+ with open(src_path, encoding="utf-8") as f:
10
+ for line in f:
11
+ if not line.strip():
12
+ continue
13
+ try:
14
+ o = json.loads(line)
15
+ except Exception:
16
+ return "claude"
17
+ return "copilot" if "kind" in o else "claude"
18
+ return "claude"
19
+
20
+
21
+ def load(src_path, source: str = "auto"):
22
+ if source == "auto":
23
+ source = detect(src_path)
24
+ mod = {"claude": claude, "copilot": copilot}.get(source)
25
+ if mod is None:
26
+ raise ValueError(f"未知來源:{source}(支援 claude / copilot / auto)")
27
+ records, meta = mod.load(src_path)
28
+ meta["source"] = source
29
+ return records, meta
@@ -0,0 +1,15 @@
1
+ """Claude Code 原生 session(jsonl)— 本來就是 Claude 格式,讀進來即可。"""
2
+ from __future__ import annotations
3
+ from ..parse import read_jsonl, session_id
4
+
5
+
6
+ def load(src_path):
7
+ """→ (records, meta)。meta.date = 第一筆 timestamp;session_id 供去重;title 留空。"""
8
+ records = read_jsonl(src_path)
9
+ date = ""
10
+ for r in records:
11
+ ts = r.get("timestamp")
12
+ if ts:
13
+ date = ts
14
+ break
15
+ return records, {"date": date, "title": "", "session_id": session_id(records)}