Skip to content

Commit 74ddc2c

Browse files
web-flowclaude
andcommitted
EpisodicRAG v5.10.0: source_files 追加を CLI 化し、JSON 書き出しを LF に固定する
/digest Pattern 1 Step 3 は SGD を Edit ツールで手編集していた。同じ文書の Step 7 が 「直接 Edit は事故りやすい」と言う矛盾があり、実運用(2026-09-08 /mydigest)でも 手元の書き出しが CRLF を混入させた。既存の ShadowGrandDigestManager.add_files_to_shadow に委譲する薄い CLI(add_shadow_sources)を足し、手編集経路を閉じる。 save_json は Path.open("w") に newline="\n" が無く、Windows では全 JSON 出力が CRLF に なっていた(記憶リポの .gitattributes eol=lf が commit 時に吸収していたため履歴は無傷)。 newline="\n" を明示し、Linux CI でも回帰を捕まえるよう Path.open の引数を検査する テストを併設する。 TDD: red(ImportError / CRLF 検出)→ green。ruff 0 / mypy Success 302 files / pytest 2381 passed(not slow and not performance)。 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 2027cc1 commit 74ddc2c

16 files changed

Lines changed: 667 additions & 26 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"name": "EpisodicRAG",
1313
"source": "./EpisodicRAG",
1414
"description": "Hierarchical Episodic Memory Generator (8-layer, 100-year span)",
15-
"version": "5.9.0",
15+
"version": "5.10.0",
1616
"author": {
1717
"name": "Weave @ EpisodicRAG"
1818
},

EpisodicRAG/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "EpisodicRAG",
3-
"version": "5.9.0",
3+
"version": "5.10.0",
44
"description": "Hierarchical Episodic Memory Generator (8-layer, 100-year span)",
55
"author": {
66
"name": "Weave @ EpisodicRAG"

EpisodicRAG/CHANGELOG.en.md

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- Last synced: 2026-07-02 -->
1+
<!-- Last synced: 2026-09-08 -->
22
English | [日本語](CHANGELOG.md)
33

44
# Changelog
@@ -12,14 +12,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212

1313
## Table of Contents
1414

15-
- [v5.x](#590---2026-08-26)
15+
- [v5.x](#5100---2026-09-08)
1616
- [v4.x](#410---2025-12-03)
1717
- [v3.x](#330---2025-11-29)
1818
- [Archive (v2.x and earlier)](#archive-v2x-and-earlier)
1919
- [Versioning Rules](#versioning-rules)
2020

2121
---
2222

23+
## [5.10.0] - 2026-09-08
24+
25+
Moves `/digest` Pattern 1 Step 3 (appending to `source_files`) from hand-editing to a script, and pins JSON output line endings to LF.
26+
27+
### Added
28+
29+
- **`interfaces/add_shadow_sources.py` (`ShadowSourceAdder`)** — CLI that appends file names to the SGD
30+
`source_files`: `python -m interfaces.add_shadow_sources <level> <filename>...`. Already-registered
31+
names are skipped (idempotent) and duplicates within one call are added once. When the level
32+
already holds an analysis, the four fields (digest_type / keywords / abstract / impression) are
33+
left alone; in the PLACEHOLDER state (right after a finalize) they are refreshed to placeholder
34+
text sized by the file count — the same `FileAppender` behavior as `update_shadow_for_new_loops`,
35+
and Step 7 overwrites them anyway. The append itself delegates to the existing
36+
`ShadowGrandDigestManager.add_files_to_shadow`; the CLI is a thin wrapper. Empty names and names
37+
containing a path separator raise `EpisodicRAGError`
38+
- `commands/digest.md` Pattern 1 Step 3 now calls this CLI instead of the Edit tool. The same document
39+
warned in Step 7 that direct edits of the SGD are error-prone while Step 3 still prescribed one;
40+
that contradiction is gone
41+
42+
### Fixed
43+
44+
- **`save_json` now writes LF only**`Path.open("w")` lacked `newline="\n"`, so Windows text mode
45+
turned every `\n` into `\r\n`. ShadowGrandDigest, Provisional and last_digest_times were all written
46+
as CRLF, and the memory repository warned `CRLF will be replaced by LF` on every touch (history was
47+
intact because `.gitattributes` `eol=lf` normalizes at commit; only the working copy was CRLF).
48+
Two regression tests: no `\r\n` in the output bytes (red only on Windows) and `Path.open` being
49+
called with `newline="\n"` (catches regressions on Linux CI too)
50+
- **`index_writer.apply_index` and `tools/check_footer.fix_footer` pinned to LF the same way** — the
51+
former rewrites auto-memory's MEMORY.md (untracked by git, so nothing normalizes it and CRLF would
52+
stick), the latter is the footer fixer behind `/doc-check`. Each got the same pair of regression
53+
tests as `save_json` (`apply_index` previously had none)
54+
55+
### Notes
56+
57+
- The installed (Daily Use) copy keeps the old code until the plugin is updated; `/digest` Step 3
58+
switches to the CLI path after that
59+
60+
### Verification
61+
62+
ruff 0 / ruff format 302 files already formatted / mypy Success (302 files, the same invocation CI uses) /
63+
pytest 2386 passed, 2 skipped (`-m "not slow and not performance"`)
64+
2365
## [5.9.0] - 2026-08-26
2466

2567
Static checking aligned with the workspace baseline. No behavior change.

EpisodicRAG/CHANGELOG.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
## 目次 / Table of Contents
1313

14-
- [v5.x](#590---2026-08-26)
14+
- [v5.x](#5100---2026-09-08)
1515
- [v4.x](#410---2025-12-03)
1616
- [v3.x](#330---2025-11-29)
1717
- [Archive (v2.x以前)](#archive-v2x-and-earlier)
1818
- [バージョニング規則](#バージョニング規則)
1919

2020
---
2121

22+
## [5.10.0] - 2026-09-08
23+
24+
`/digest` Pattern 1 Step 3(source_files 追加)を手編集からスクリプトへ寄せ、JSON 書き出しの改行を LF に固定する。
25+
26+
### Added
27+
28+
- **`interfaces/add_shadow_sources.py``ShadowSourceAdder`** — SGD の `source_files` へファイル名を追加する CLI。
29+
`python -m interfaces.add_shadow_sources <level> <filename>...`。既登録は skip(冪等)、
30+
呼び出し内の重複は 1 回だけ。既存分析がある場合は 4 要素(digest_type / keywords / abstract /
31+
impression)に触れず、PLACEHOLDER 状態(finalize 直後)では件数に応じたプレースホルダー文へ更新する
32+
`update_shadow_for_new_loops` と同じ `FileAppender` の挙動。Step 7 が上書きする前提)。追加処理は既存の
33+
`ShadowGrandDigestManager.add_files_to_shadow` へ委譲し、CLI は薄いラッパー。
34+
空文字・パス区切りを含む名前は `EpisodicRAGError`
35+
- `commands/digest.md` Pattern 1 Step 3 を Edit ツールの手編集から上記 CLI へ置換。同じ文書の Step 7 が
36+
「SGD の直接 Edit は事故りやすい」と言いながら Step 3 だけ手編集を指示していた矛盾を解消
37+
38+
### Fixed
39+
40+
- **`save_json` の改行を LF に固定**`Path.open("w")``newline="\n"` が無く、Windows の text mode
41+
既定で `\n``\r\n` に変換されていた。ShadowGrandDigest / Provisional / last_digest_times の
42+
書き出しが全て CRLF になり、記憶リポ側で毎回 `CRLF will be replaced by LF` の警告が出ていた
43+
`.gitattributes``eol=lf` が commit 時に正規化するため履歴は無傷、working copy だけが CRLF)。
44+
回帰テストは二本:出力バイトに `\r\n` が無いこと(Windows でのみ red になる)と、
45+
`Path.open``newline="\n"` で呼ばれること(Linux CI でも回帰を捕まえる網)
46+
- **`index_writer.apply_index``tools/check_footer.fix_footer``write_text` も同型で LF に固定**
47+
前者は auto-memory の MEMORY.md 書き戻し(git 非追跡ゆえ正規化されず CRLF のまま残る)、後者は
48+
`/doc-check` 系の footer 修正。`save_json` と同じ二本立ての回帰テストを各々に付けた
49+
`apply_index` は従来テスト無し)
50+
51+
### Notes
52+
53+
- インストール済み(Daily Use)側はプラグイン更新までは旧コードのまま。`/digest` の Step 3 は
54+
更新後に CLI 経路へ切り替わる
55+
56+
### 検証
57+
58+
ruff 0 / ruff format 302 files already formatted / mypy Success(302 files、CI と同じ起動形)/
59+
pytest 2386 passed, 2 skipped(`-m "not slow and not performance"`
60+
2261
## [5.9.0] - 2026-08-26
2362

2463
静的チェックをワークスペース基準へ揃える。挙動の変更は無い。

EpisodicRAG/commands/digest.md

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ TodoWrite items for Pattern 1:
117117
|------|---------|-------------------|
118118
| 1 | パス情報・新規Loop確認 | `python -m interfaces.digest_entry` |
119119
| 2 | SGD読み込み | `essences_path`のShadowGrandDigest.txtを読み込む |
120-
| 3 | source_files追加 | SGDの`weekly.overall_digest.source_files`に新規Loopファイル名を追加 |
120+
| 3 | source_files追加 | `python -m interfaces.add_shadow_sources weekly <ファイル名...>` |
121121
| 4 | DigestAnalyzer起動 | Step 3のLoopファイル別に`Task(DigestAnalyzer)`を並列起動 |
122122
| 5 | 分析結果受信 | 各DigestAnalyzerからlong/short分析結果を受け取る |
123123
| 6 | Provisional保存 | 分析結果を一時ファイル経由でProvisionalにアペンド(`save_provisional_digest`|
@@ -157,28 +157,24 @@ python -m interfaces.digest_entry --output json
157157

158158
**確認ポイント**:
159159
- `weekly.overall_digest.source_files`の現在のリスト
160-
- 次のStepで追加するファイル名の重複がないこと
160+
- 次のStepで追加するファイル名の重複がないこと(既登録は Step 3 の CLI が skip するため、ここは目視確認のみ)
161161

162162
---
163163

164164
#### Step 3: source_files追加
165165

166-
**対象ファイル**: `{essences_path}/ShadowGrandDigest.txt`
166+
**実行ディレクトリ**: `{plugin_root}/scripts`
167167

168-
**操作**: Editツールで`weekly.overall_digest.source_files`配列に新規Loopファイル名を追加
168+
**コマンド**:
169+
```bash
170+
python -m interfaces.add_shadow_sources weekly "L00260_タイトル.txt" ["L00261_タイトル.txt" ...]
171+
```
169172

170-
**追加形式**: `"L00260_タイトル.txt"` (フルファイル名)
173+
**追加形式**: フルファイル名(`{loops_path}` からの相対パスやディレクトリを付けない)。Step 1 出力の `new_loops` は番号+タイトル(拡張子なし)なので `.txt` を補う
171174

172-
**フォーマット規約**: 配列要素は1行ずつ記述する(インライン形式 `["a.txt", "b.txt"]` は避ける)
175+
**動作**: 既登録のファイル名は skip(冪等)。既存分析がある場合は `digest_type` / `keywords` / `abstract` / `impression` に触れない。PLACEHOLDER 状態(finalize 直後)では件数に応じたプレースホルダー文へ更新される(Step 7 が上書きするので問題ない)。改行は LF で書き出される
173176

174-
****:
175-
```json
176-
"source_files": [
177-
"L00258_既存.txt",
178-
"L00259_既存.txt",
179-
"L00260_新規追加.txt"
180-
]
181-
```
177+
**注意**: SGDの直接Edit(exact-match置換)は長文日本語で事故りやすく、手元の書き出しは改行コードも壊しうるため使用しないこと(Step 7 と同じ理由)
182178

183179
---
184180

EpisodicRAG/docs/dev/api/interfaces.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ from interfaces import (
4040
8. [CLI共通ヘルパー](#cli共通ヘルパーinterfacescli_helperspy) *(v4.1.0+)*
4141
9. [UpdateDigestTimes CLI](#updatedigesttimes-cliupdate_digest_timespy) *(v5.0.0+)*
4242
10. [OverallDigestUpdater(update_shadow_overall.py)](#overalldigestupdaterupdate_shadow_overallpy)
43-
11. [ShadowStateChecker(内部CLI)](#shadowstatechecker内部cli)
44-
12. [DigestReadinessChecker(digest_readiness.py)](#digestreadinesscheckerdigest_readinesspy) *(v5.1.0+)*
43+
11. [ShadowSourceAdder(add_shadow_sources.py)](#shadowsourceadderadd_shadow_sourcespy) *(v5.10.0+)*
44+
12. [ShadowStateChecker(内部CLI)](#shadowstatechecker内部cli)
45+
13. [DigestReadinessChecker(digest_readiness.py)](#digestreadinesscheckerdigest_readinesspy) *(v5.1.0+)*
4546

4647
---
4748

@@ -572,6 +573,40 @@ cat payload.json | python -m interfaces.update_shadow_overall monthly --stdin
572573

573574
---
574575

576+
## ShadowSourceAdder(add_shadow_sources.py)
577+
578+
ShadowGrandDigest の `source_files` にファイル名を追加。既登録は skip(冪等)。
579+
既存分析がある場合は digest_type / keywords / abstract / impression に触れない。
580+
PLACEHOLDER 状態(finalize 直後)では件数に応じたプレースホルダー文へ更新する
581+
`update_shadow_for_new_loops` と同じ `FileAppender` の挙動)。
582+
583+
> **背景**: `/digest` Pattern 1 Step 3 は SGD の source_files を Edit ツールで直接編集していた。
584+
> 長文日本語 JSON の手編集は事故りやすく、手元の書き出しは改行コードも壊しうる。
585+
> 追加処理は `ShadowGrandDigestManager.add_files_to_shadow` に委譲し、CLI は薄いラッパーに留める。
586+
587+
```python
588+
class ShadowSourceAdder:
589+
def __init__(self, config: Optional[DigestConfig] = None): ...
590+
def add_sources(self, level: str, filenames: List[str]) -> List[str]: ...
591+
```
592+
593+
| メソッド | 説明 |
594+
|---------|------|
595+
| `add_sources(level, filenames) -> List[str]` | ファイル名を検証して追加・保存し、実際に追加された名前を指定順で返す。呼び出し内の重複は 1 回だけ追加 |
596+
597+
**使用例(CLI)**:
598+
599+
```bash
600+
cd scripts
601+
python -m interfaces.add_shadow_sources weekly "L00592_講義は顧客の仕様書.txt"
602+
python -m interfaces.add_shadow_sources weekly L00592_a.txt L00593_b.txt
603+
```
604+
605+
**バリデーション**: 空文字・パス区切りを含む名前は `EpisodicRAGError`(CLI では exit 1)。
606+
ファイルの実在は検証しない。エラー時 SGD は変更されない。
607+
608+
---
609+
575610
## ShadowStateChecker(内部CLI)
576611

577612
Shadow状態判定CLI。`__all__`にエクスポートされていない内部CLI。直接インポートせず、CLIとして使用。

EpisodicRAG/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "episodicrag"
7-
version = "5.9.0"
7+
version = "5.10.0"
88
description = "EpisodicRAG - Hierarchical digest generation for long-term memory"
99
requires-python = ">=3.10"
1010
license = {text = "MIT"}

EpisodicRAG/scripts/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ from domain.validators import validate_type, is_valid_dict, is_valid_list
145145
| `digest_entry.py` | - | Digestエントリーポイント |
146146
| `digest_readiness.py` | - | Digest準備状態チェック |
147147
| `update_digest_times.py` | - | Digestタイムスタンプ更新 |
148+
| `update_shadow_overall.py` | `OverallDigestUpdater` | SGD overall_digest 4要素の更新(source_files 不変) |
149+
| `add_shadow_sources.py` | `ShadowSourceAdder` | SGD source_files へのファイル名追加(冪等。既存分析は保持、PLACEHOLDER は件数で更新)*(v5.10.0+)* |
148150
| `provisional/` | - | Provisionalマージ処理(`file_manager`, `input_loader`, `merger`, `validator`|
149151

150152
```python

EpisodicRAG/scripts/infrastructure/auto_dream/index_writer.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,5 +98,6 @@ def apply_index(memory_dir: Path, text: str) -> Path:
9898
書き込んだ MEMORY.md のパス
9999
"""
100100
index_path = memory_dir / _INDEX_FILENAME
101-
index_path.write_text(text, encoding="utf-8")
101+
# newline="\n": Windows の text mode 既定は \n→\r\n 変換。MEMORY.md は LF 正典
102+
index_path.write_text(text, encoding="utf-8", newline="\n")
102103
return index_path

EpisodicRAG/scripts/infrastructure/json_repository/operations.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ def save_json(file_path: Path, data: dict[str, Any], indent: int = 2) -> None:
123123
formatter = get_error_formatter()
124124
try:
125125
file_path.parent.mkdir(parents=True, exist_ok=True)
126-
with file_path.open("w", encoding="utf-8") as f:
126+
# newline="\n": Windows の text mode 既定は \n→\r\n 変換。記憶リポは LF 正典
127+
with file_path.open("w", encoding="utf-8", newline="\n") as f:
127128
json.dump(data, f, ensure_ascii=False, indent=indent)
128129
except OSError as e:
129130
raise FileIOError(formatter.file.file_io_error("write", file_path, e)) from e

0 commit comments

Comments
 (0)