ragjson 0.1.4__tar.gz → 0.2.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.
Files changed (40) hide show
  1. {ragjson-0.1.4 → ragjson-0.2.0}/.gitignore +2 -1
  2. {ragjson-0.1.4 → ragjson-0.2.0}/CLAUDE.md +0 -22
  3. ragjson-0.1.4/README.md → ragjson-0.2.0/PKG-INFO +58 -22
  4. ragjson-0.1.4/PKG-INFO → ragjson-0.2.0/README.md +46 -35
  5. {ragjson-0.1.4 → ragjson-0.2.0}/pyproject.toml +3 -3
  6. {ragjson-0.1.4 → ragjson-0.2.0}/src/ragjson/__init__.py +1 -1
  7. {ragjson-0.1.4 → ragjson-0.2.0}/src/ragjson/cli.py +20 -20
  8. ragjson-0.2.0/src/ragjson/commands/__init__.py +15 -0
  9. ragjson-0.2.0/src/ragjson/commands/_helpers.py +90 -0
  10. ragjson-0.2.0/src/ragjson/commands/destroy.py +38 -0
  11. ragjson-0.2.0/src/ragjson/commands/export.py +37 -0
  12. ragjson-0.2.0/src/ragjson/commands/index.py +183 -0
  13. ragjson-0.2.0/src/ragjson/commands/status.py +76 -0
  14. ragjson-0.2.0/src/ragjson/commands/update.py +250 -0
  15. {ragjson-0.1.4 → ragjson-0.2.0}/src/ragjson/config.py +27 -0
  16. ragjson-0.2.0/src/ragjson/embed/__init__.py +5 -0
  17. {ragjson-0.1.4/src/ragjson → ragjson-0.2.0/src/ragjson/embed}/embedding.py +13 -0
  18. ragjson-0.2.0/src/ragjson/index/__init__.py +0 -0
  19. ragjson-0.2.0/src/ragjson/index/backend.py +133 -0
  20. ragjson-0.2.0/src/ragjson/index/zvec_backend.py +121 -0
  21. ragjson-0.2.0/src/ragjson/parser/__init__.py +20 -0
  22. ragjson-0.2.0/src/ragjson/parser/backend.py +64 -0
  23. ragjson-0.2.0/src/ragjson/parser/markitdown_parser.py +44 -0
  24. ragjson-0.2.0/src/ragjson/splitter/__init__.py +18 -0
  25. ragjson-0.2.0/src/ragjson/splitter/backend.py +69 -0
  26. ragjson-0.2.0/src/ragjson/splitter/markdown.py +24 -0
  27. ragjson-0.2.0/src/ragjson/splitter/overlap.py +34 -0
  28. ragjson-0.2.0/src/ragjson/splitter/recursive.py +19 -0
  29. ragjson-0.2.0/src/ragjson/store/__init__.py +66 -0
  30. {ragjson-0.1.4/src/ragjson → ragjson-0.2.0/src/ragjson/store}/atomic_ops.py +15 -42
  31. ragjson-0.2.0/src/ragjson/store/mushroom.py +38 -0
  32. {ragjson-0.1.4/src/ragjson → ragjson-0.2.0/src/ragjson/store}/ragjson_file.py +1 -1
  33. ragjson-0.1.4/docs/design/mushroom-sync.md +0 -378
  34. ragjson-0.1.4/src/ragjson/commands.py +0 -549
  35. ragjson-0.1.4/src/ragjson/doc_parser.py +0 -45
  36. ragjson-0.1.4/src/ragjson/splitter.py +0 -82
  37. {ragjson-0.1.4 → ragjson-0.2.0}/.python-version +0 -0
  38. {ragjson-0.1.4 → ragjson-0.2.0}/docs/RAG-Storage-Spec.md +0 -0
  39. {ragjson-0.1.4 → ragjson-0.2.0}/src/ragjson/__main__.py +0 -0
  40. {ragjson-0.1.4 → ragjson-0.2.0}/src/ragjson/utils.py +0 -0
@@ -9,4 +9,5 @@ wheels/
9
9
  # Virtual environments
10
10
  .venv
11
11
 
12
- data/
12
+ data/
13
+ docs/rag.comfig
@@ -23,30 +23,8 @@ ragjson — 遵循 RAG-Storage-Spec 规范的文件系统 RAG 知识库构建工
23
23
  - 蘑菇文件(`.mushroom`):`rag update` 生成的变更通知(JSONL),供外部索引工具消费
24
24
  - 删除机制:源文件删除时 rename `.ragjson` → `.ragjson.deleted`(非状态转换)
25
25
 
26
- ## 命令
27
-
28
- | 命令 | 说明 |
29
- |------|------|
30
- | `rag status [path] [-r]` | 查看知识库状态 |
31
- | `rag update [path] [-r] [-j N]` | 增量更新(含删除/恢复检测 + 蘑菇生成) |
32
- | `rag cleanup [path] [-r]` | 清理 .tmp 和 .ragjson.deleted |
33
- | `rag destroy [path] [-r] [-y]` | 销毁 .rag/ 目录 |
34
- | `rag export <output> [path] [-r] [-y]` | 导出 .ragjson 为 tar.gz |
35
26
 
36
27
  ## 规范文档
37
28
 
38
29
  - `docs/RAG-Storage-Spec.md` — 协议规范(v2.2)
39
- - `docs/design/mushroom-sync.md` — 蘑菇 + zvec 索引集成设计
40
-
41
- ## 开发
42
-
43
- ```bash
44
- # 安装
45
- uv pip install -e ".[all]"
46
-
47
- # 运行
48
- rag --help
49
30
 
50
- # 构建
51
- uv build
52
- ```
@@ -1,3 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: ragjson
3
+ Version: 0.2.0
4
+ Summary: 遵循RAG-Storage-Spec的知识库构建工具
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: langchain-text-splitters>=0.3
7
+ Requires-Dist: markitdown[all]
8
+ Requires-Dist: openai>=1.0
9
+ Provides-Extra: index
10
+ Requires-Dist: zvec>=0.5; extra == 'index'
11
+ Description-Content-Type: text/markdown
12
+
1
13
  # ragjson
2
14
 
3
15
  遵循 [RAG-Storage-Spec](docs/RAG-Storage-Spec.md) 规范的基于文件系统的 RAG 知识库构建工具。
@@ -18,7 +30,7 @@
18
30
 
19
31
  ```bash
20
32
  # 全局安装为独立命令(推荐,无需 venv)
21
- uv tool install ragjson[all]
33
+ uv tool install ragjson
22
34
  ```
23
35
 
24
36
  或在项目虚拟环境中使用:
@@ -26,25 +38,17 @@ uv tool install ragjson[all]
26
38
  ```bash
27
39
  uv venv
28
40
  source .venv/bin/activate
29
- uv pip install ragjson[all]
41
+ uv pip install ragjson
30
42
  ```
31
43
 
32
44
  本地开发模式(从源码安装):
33
45
 
34
46
  ```bash
35
47
  uv venv
36
- uv pip install -e ".[all]"
48
+ uv pip install -e .
37
49
  source .venv/bin/activate
38
50
  ```
39
51
 
40
- 可选的 extras 组合:
41
-
42
- | extras | 说明 |
43
- |--------|------|
44
- | `ragjson` | 基础安装(切片 + Embedding) |
45
- | `ragjson[markitdown]` | 增加 markitdown 文档解析 |
46
- | `ragjson[all]` | 全部依赖 |
47
-
48
52
  ## 配置
49
53
 
50
54
  在用户 home 目录下创建 `~/.rag.config` 文件(JSON 格式):
@@ -60,6 +64,10 @@ source .venv/bin/activate
60
64
  "splitter": {
61
65
  "chunk_size": 1000,
62
66
  "chunk_overlap": 200
67
+ },
68
+ "vector_database": {
69
+ "db_url": "zvec:/data/vectordb",
70
+ "index_type": "flat"
63
71
  }
64
72
  }
65
73
  ```
@@ -72,6 +80,8 @@ source .venv/bin/activate
72
80
  | `embedding_model.dimension` | 向量维度 |
73
81
  | `splitter.chunk_size` | 切片最大字符数 |
74
82
  | `splitter.chunk_overlap` | 切片重叠字符数 |
83
+ | `vector_database.db_url` | 向量数据库目录(collection 在其下的 `zvec-db1/`) |
84
+ | `vector_database.index_type` | 索引类型:`flat` / `hnsw` |
75
85
 
76
86
  ## 使用方法
77
87
 
@@ -119,15 +129,6 @@ rag update ./docs -r -j 4
119
129
  | `-j N` | 并行处理的目录数 | CPU 核心数 |
120
130
  | `--splitter` | 切片器类型:`recursive` / `markdown` | `recursive` |
121
131
 
122
- ### 清理
123
-
124
- 清除残留的 `.tmp` 临时文件和 `.ragjson.deleted` 文件:
125
-
126
- ```bash
127
- rag cleanup ./docs
128
- rag cleanup ./docs -r # 递归清理
129
- ```
130
-
131
132
  ### 销毁
132
133
 
133
134
  删除 `.rag/` 目录及所有元数据(高危操作,需确认):
@@ -147,6 +148,26 @@ rag export backup.tar.gz # 仅当前目录
147
148
  rag export backup.tar.gz -r # 递归包含子目录
148
149
  ```
149
150
 
151
+ ### 索引同步
152
+
153
+ 消费 `.mushroom` 变更通知文件,将向量同步到向量数据库(需安装 `ragjson[index]`):
154
+
155
+ ```bash
156
+ # 同步当前目录
157
+ rag index
158
+
159
+ # 递归同步子目录
160
+ rag index -r
161
+ ```
162
+
163
+ 向量数据库路径和索引类型在 `~/.rag.config` 的 `vector_database` 节配置。
164
+
165
+ 安装索引依赖:
166
+
167
+ ```bash
168
+ uv pip install ragjson[index]
169
+ ```
170
+
150
171
  ## 目录结构
151
172
 
152
173
  执行 `rag update` 后的目录结构示例:
@@ -204,10 +225,25 @@ INITIAL → CHUNKING → CHUNKED → EMBEDDING → EMBEDDED
204
225
 
205
226
  (源文件变更) ←─────────────────── INITIAL (回退)
206
227
 
207
- 源文件删除:任意状态 → rename .ragjson.deleted(cleanup 物理删除)
228
+ 源文件删除:任意状态 → rename .ragjson → .ragjson.deleted
208
229
  源文件恢复:.ragjson.deleted → rename .ragjson → (update 检测 MD5)
209
230
  ```
210
231
 
232
+ ## 项目架构
233
+
234
+ ```
235
+ src/ragjson/
236
+ ├── commands/ # CLI 命令(status, update, destroy, export, index)
237
+ ├── embed/ # Embedding 生成(OpenAI 兼容 API)
238
+ ├── index/ # 向量索引(zvec backend,可扩展)
239
+ ├── parser/ # 文档解析(markitdown backend,可扩展)
240
+ ├── splitter/ # 文本切片(recursive / markdown)
241
+ ├── store/ # 存储层(ragjson 文件、原子操作、mushroom)
242
+ ├── config.py # ~/.rag.config 配置加载
243
+ ├── utils.py # 工具函数(MD5、向量编码、颜色输出)
244
+ └── cli.py # CLI 入口
245
+ ```
246
+
211
247
  ## 开发
212
248
 
213
249
  ```bash
@@ -217,7 +253,7 @@ cd ragjson
217
253
 
218
254
  # 创建虚拟环境并安装开发版本
219
255
  uv venv
220
- uv pip install -e ".[all]"
256
+ uv pip install -e ".[index]"
221
257
  source .venv/bin/activate
222
258
 
223
259
  # 运行 CLI
@@ -1,16 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: ragjson
3
- Version: 0.1.4
4
- Summary: 遵循RAG-Storage-Spec的知识库构建工具
5
- Requires-Python: >=3.12
6
- Requires-Dist: langchain-text-splitters>=0.3
7
- Requires-Dist: openai>=1.0
8
- Provides-Extra: all
9
- Requires-Dist: markitdown[all]; extra == 'all'
10
- Provides-Extra: markitdown
11
- Requires-Dist: markitdown[all]; extra == 'markitdown'
12
- Description-Content-Type: text/markdown
13
-
14
1
  # ragjson
15
2
 
16
3
  遵循 [RAG-Storage-Spec](docs/RAG-Storage-Spec.md) 规范的基于文件系统的 RAG 知识库构建工具。
@@ -31,7 +18,7 @@ Description-Content-Type: text/markdown
31
18
 
32
19
  ```bash
33
20
  # 全局安装为独立命令(推荐,无需 venv)
34
- uv tool install ragjson[all]
21
+ uv tool install ragjson
35
22
  ```
36
23
 
37
24
  或在项目虚拟环境中使用:
@@ -39,25 +26,17 @@ uv tool install ragjson[all]
39
26
  ```bash
40
27
  uv venv
41
28
  source .venv/bin/activate
42
- uv pip install ragjson[all]
29
+ uv pip install ragjson
43
30
  ```
44
31
 
45
32
  本地开发模式(从源码安装):
46
33
 
47
34
  ```bash
48
35
  uv venv
49
- uv pip install -e ".[all]"
36
+ uv pip install -e .
50
37
  source .venv/bin/activate
51
38
  ```
52
39
 
53
- 可选的 extras 组合:
54
-
55
- | extras | 说明 |
56
- |--------|------|
57
- | `ragjson` | 基础安装(切片 + Embedding) |
58
- | `ragjson[markitdown]` | 增加 markitdown 文档解析 |
59
- | `ragjson[all]` | 全部依赖 |
60
-
61
40
  ## 配置
62
41
 
63
42
  在用户 home 目录下创建 `~/.rag.config` 文件(JSON 格式):
@@ -73,6 +52,10 @@ source .venv/bin/activate
73
52
  "splitter": {
74
53
  "chunk_size": 1000,
75
54
  "chunk_overlap": 200
55
+ },
56
+ "vector_database": {
57
+ "db_url": "zvec:/data/vectordb",
58
+ "index_type": "flat"
76
59
  }
77
60
  }
78
61
  ```
@@ -85,6 +68,8 @@ source .venv/bin/activate
85
68
  | `embedding_model.dimension` | 向量维度 |
86
69
  | `splitter.chunk_size` | 切片最大字符数 |
87
70
  | `splitter.chunk_overlap` | 切片重叠字符数 |
71
+ | `vector_database.db_url` | 向量数据库目录(collection 在其下的 `zvec-db1/`) |
72
+ | `vector_database.index_type` | 索引类型:`flat` / `hnsw` |
88
73
 
89
74
  ## 使用方法
90
75
 
@@ -132,15 +117,6 @@ rag update ./docs -r -j 4
132
117
  | `-j N` | 并行处理的目录数 | CPU 核心数 |
133
118
  | `--splitter` | 切片器类型:`recursive` / `markdown` | `recursive` |
134
119
 
135
- ### 清理
136
-
137
- 清除残留的 `.tmp` 临时文件和 `.ragjson.deleted` 文件:
138
-
139
- ```bash
140
- rag cleanup ./docs
141
- rag cleanup ./docs -r # 递归清理
142
- ```
143
-
144
120
  ### 销毁
145
121
 
146
122
  删除 `.rag/` 目录及所有元数据(高危操作,需确认):
@@ -160,6 +136,26 @@ rag export backup.tar.gz # 仅当前目录
160
136
  rag export backup.tar.gz -r # 递归包含子目录
161
137
  ```
162
138
 
139
+ ### 索引同步
140
+
141
+ 消费 `.mushroom` 变更通知文件,将向量同步到向量数据库(需安装 `ragjson[index]`):
142
+
143
+ ```bash
144
+ # 同步当前目录
145
+ rag index
146
+
147
+ # 递归同步子目录
148
+ rag index -r
149
+ ```
150
+
151
+ 向量数据库路径和索引类型在 `~/.rag.config` 的 `vector_database` 节配置。
152
+
153
+ 安装索引依赖:
154
+
155
+ ```bash
156
+ uv pip install ragjson[index]
157
+ ```
158
+
163
159
  ## 目录结构
164
160
 
165
161
  执行 `rag update` 后的目录结构示例:
@@ -217,10 +213,25 @@ INITIAL → CHUNKING → CHUNKED → EMBEDDING → EMBEDDED
217
213
 
218
214
  (源文件变更) ←─────────────────── INITIAL (回退)
219
215
 
220
- 源文件删除:任意状态 → rename .ragjson.deleted(cleanup 物理删除)
216
+ 源文件删除:任意状态 → rename .ragjson → .ragjson.deleted
221
217
  源文件恢复:.ragjson.deleted → rename .ragjson → (update 检测 MD5)
222
218
  ```
223
219
 
220
+ ## 项目架构
221
+
222
+ ```
223
+ src/ragjson/
224
+ ├── commands/ # CLI 命令(status, update, destroy, export, index)
225
+ ├── embed/ # Embedding 生成(OpenAI 兼容 API)
226
+ ├── index/ # 向量索引(zvec backend,可扩展)
227
+ ├── parser/ # 文档解析(markitdown backend,可扩展)
228
+ ├── splitter/ # 文本切片(recursive / markdown)
229
+ ├── store/ # 存储层(ragjson 文件、原子操作、mushroom)
230
+ ├── config.py # ~/.rag.config 配置加载
231
+ ├── utils.py # 工具函数(MD5、向量编码、颜色输出)
232
+ └── cli.py # CLI 入口
233
+ ```
234
+
224
235
  ## 开发
225
236
 
226
237
  ```bash
@@ -230,7 +241,7 @@ cd ragjson
230
241
 
231
242
  # 创建虚拟环境并安装开发版本
232
243
  uv venv
233
- uv pip install -e ".[all]"
244
+ uv pip install -e ".[index]"
234
245
  source .venv/bin/activate
235
246
 
236
247
  # 运行 CLI
@@ -7,18 +7,18 @@ packages = ["src/ragjson"]
7
7
 
8
8
  [project]
9
9
  name = "ragjson"
10
- version = "0.1.4"
10
+ version = "0.2.0"
11
11
  description = "遵循RAG-Storage-Spec的知识库构建工具"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.12"
14
14
  dependencies = [
15
15
  "langchain-text-splitters>=0.3",
16
16
  "openai>=1.0",
17
+ "markitdown[all]",
17
18
  ]
18
19
 
19
20
  [project.optional-dependencies]
20
- markitdown = ["markitdown[all]"]
21
- all = ["markitdown[all]"]
21
+ index = ["zvec>=0.5"]
22
22
 
23
23
  [project.scripts]
24
24
  rag = "ragjson.cli:main"
@@ -1,4 +1,4 @@
1
1
  """ragjson - 遵循RAG-Storage-Spec的知识库构建工具"""
2
2
 
3
- __version__ = "0.1.4"
3
+ __version__ = "0.2.0"
4
4
  RAGJSON_VERSION = "2.2"
@@ -70,23 +70,6 @@ def main() -> None:
70
70
  help="Text splitter type (default: recursive)",
71
71
  )
72
72
 
73
- # --- rag cleanup [path] [-r] ---
74
- cleanup_parser = subparsers.add_parser(
75
- "cleanup",
76
- help="Clean .tmp files and remove .ragjson.deleted files",
77
- )
78
- cleanup_parser.add_argument(
79
- "path",
80
- nargs="?",
81
- default=".",
82
- help="Directory path to clean up (default: current directory)",
83
- )
84
- cleanup_parser.add_argument(
85
- "-r", "--recursive",
86
- action="store_true",
87
- help="Recursively process subdirectories",
88
- )
89
-
90
73
  # --- rag destroy [path] [-r] ---
91
74
  destroy_parser = subparsers.add_parser(
92
75
  "destroy",
@@ -135,6 +118,23 @@ def main() -> None:
135
118
  help="Skip overwrite confirmation prompt",
136
119
  )
137
120
 
121
+ # --- rag index [path] [-r] ---
122
+ index_parser = subparsers.add_parser(
123
+ "index",
124
+ help="Consume .mushroom files and sync vectors to database",
125
+ )
126
+ index_parser.add_argument(
127
+ "path",
128
+ nargs="?",
129
+ default=".",
130
+ help="Directory path to scan for .mushroom files (default: current directory)",
131
+ )
132
+ index_parser.add_argument(
133
+ "-r", "--recursive",
134
+ action="store_true",
135
+ help="Recursively scan subdirectories for .mushroom files",
136
+ )
137
+
138
138
  # Parse and dispatch
139
139
  args = parser.parse_args()
140
140
 
@@ -144,9 +144,9 @@ def main() -> None:
144
144
 
145
145
  # Dispatch to command implementations
146
146
  from ragjson.commands import (
147
- cmd_cleanup,
148
147
  cmd_destroy,
149
148
  cmd_export,
149
+ cmd_index,
150
150
  cmd_status,
151
151
  cmd_update,
152
152
  )
@@ -160,12 +160,12 @@ def main() -> None:
160
160
  jobs=args.jobs,
161
161
  splitter_type=args.splitter,
162
162
  )
163
- elif args.command == "cleanup":
164
- cmd_cleanup(args.path, args.recursive)
165
163
  elif args.command == "destroy":
166
164
  cmd_destroy(args.path, args.recursive, args.yes)
167
165
  elif args.command == "export":
168
166
  cmd_export(args.path, args.output, args.recursive, args.yes)
167
+ elif args.command == "index":
168
+ cmd_index(args.path, args.recursive)
169
169
  else:
170
170
  parser.print_help()
171
171
  sys.exit(1)
@@ -0,0 +1,15 @@
1
+ """CLI command implementations — re-exported from submodules."""
2
+
3
+ from ragjson.commands.destroy import cmd_destroy
4
+ from ragjson.commands.export import cmd_export
5
+ from ragjson.commands.index import cmd_index
6
+ from ragjson.commands.status import cmd_status
7
+ from ragjson.commands.update import cmd_update
8
+
9
+ __all__ = [
10
+ "cmd_destroy",
11
+ "cmd_export",
12
+ "cmd_index",
13
+ "cmd_status",
14
+ "cmd_update",
15
+ ]
@@ -0,0 +1,90 @@
1
+ """Shared helper functions for CLI commands."""
2
+
3
+ from pathlib import Path
4
+
5
+ from ragjson.parser import is_supported_file
6
+ from ragjson.store import (
7
+ STATUS_CHUNKED,
8
+ STATUS_CHUNKING,
9
+ STATUS_EMBEDDED,
10
+ STATUS_EMBEDDING,
11
+ STATUS_INITIAL,
12
+ is_source_changed,
13
+ ragjson_path_for,
14
+ read_ragjson,
15
+ )
16
+
17
+
18
+ def collect_dirs(base: Path, recursive: bool) -> list[Path]:
19
+ """Collect directories to scan, optionally recursive."""
20
+ dirs = [base]
21
+ if recursive:
22
+ for child in sorted(base.rglob("*")):
23
+ if child.is_dir() and child.name != ".rag":
24
+ dirs.append(child)
25
+ return dirs
26
+
27
+
28
+ def list_supported_files(scan_dir: Path) -> list[Path]:
29
+ """List supported files directly in a directory (not recursive)."""
30
+ files = []
31
+ for child in sorted(scan_dir.iterdir()):
32
+ if child.is_file() and is_supported_file(child):
33
+ files.append(child)
34
+ return files
35
+
36
+
37
+ def files_needing_update(scan_dir: Path) -> list[Path]:
38
+ """Find files in a directory that need processing.
39
+
40
+ A file needs update if:
41
+ - No corresponding .ragjson exists
42
+ - The .ragjson status is INITIAL (or stuck intermediate state)
43
+ - The source MD5 has changed
44
+ """
45
+ result = []
46
+
47
+ for f in list_supported_files(scan_dir):
48
+ ragjson_path = ragjson_path_for(scan_dir, f.name)
49
+ data = read_ragjson(ragjson_path)
50
+
51
+ if data is None:
52
+ result.append(f)
53
+ elif data.processing.status == STATUS_EMBEDDED:
54
+ if is_source_changed(data, f):
55
+ result.append(f)
56
+ elif data.processing.status in (STATUS_CHUNKING, STATUS_EMBEDDING):
57
+ # Stuck intermediate state — needs recovery
58
+ result.append(f)
59
+ elif data.processing.status in (STATUS_INITIAL, STATUS_CHUNKED):
60
+ result.append(f)
61
+
62
+ return result
63
+
64
+
65
+ def has_deletions(scan_dir: Path) -> bool:
66
+ """Check if any .ragjson files have missing source files (need deletion)."""
67
+ rag_dir = scan_dir / ".rag"
68
+ if not rag_dir.exists():
69
+ return False
70
+
71
+ for rj_path in rag_dir.glob("*.ragjson"):
72
+ source_name = rj_path.name.removesuffix(".ragjson")
73
+ source_path = scan_dir / source_name
74
+ if not source_path.exists():
75
+ return True
76
+ return False
77
+
78
+
79
+ def has_recoveries(scan_dir: Path) -> bool:
80
+ """Check if any .ragjson.deleted files have their source files back."""
81
+ rag_dir = scan_dir / ".rag"
82
+ if not rag_dir.exists():
83
+ return False
84
+
85
+ for deleted_path in rag_dir.glob("*.ragjson.deleted"):
86
+ source_name = deleted_path.name.removesuffix(".ragjson.deleted")
87
+ source_path = scan_dir / source_name
88
+ if source_path.exists():
89
+ return True
90
+ return False
@@ -0,0 +1,38 @@
1
+ """rag destroy [path] [-r] [-y] — remove .rag/ directories."""
2
+
3
+ import shutil
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ from ragjson.commands._helpers import collect_dirs
8
+
9
+
10
+ def cmd_destroy(path: str, recursive: bool = False, yes: bool = False) -> None:
11
+ """Remove .rag/ directory and all its contents (DESTRUCTIVE)."""
12
+ target = Path(path).resolve()
13
+ if not target.is_dir():
14
+ print(f"Error: Path is not a directory: {target}", file=sys.stderr)
15
+ sys.exit(1)
16
+
17
+ dirs_to_scan = collect_dirs(target, recursive)
18
+ rag_dirs = [d / ".rag" for d in dirs_to_scan if (d / ".rag").exists()]
19
+
20
+ if not rag_dirs:
21
+ print("No .rag/ directories found.")
22
+ return
23
+
24
+ print(f"WARNING: This will permanently delete {len(rag_dirs)} .rag/ directory(ies):")
25
+ for rag_dir in rag_dirs:
26
+ print(f" {rag_dir}")
27
+
28
+ if not yes:
29
+ answer = input("\nAre you sure? [y/N] ")
30
+ if answer.lower() not in ("y", "yes"):
31
+ print("Aborted.")
32
+ return
33
+
34
+ for rag_dir in rag_dirs:
35
+ shutil.rmtree(rag_dir)
36
+ print(f" Destroyed: {rag_dir}")
37
+
38
+ print(f"Total: destroyed {len(rag_dirs)} .rag/ directory(ies).")
@@ -0,0 +1,37 @@
1
+ """rag export <output> [path] [-r] [-y] — export .ragjson to tar.gz."""
2
+
3
+ import sys
4
+ import tarfile
5
+ from pathlib import Path
6
+
7
+
8
+ def cmd_export(path: str, output: str, recursive: bool = False, yes: bool = False) -> None:
9
+ """Export .ragjson files into a tar.gz archive."""
10
+ target = Path(path).resolve()
11
+ if not target.is_dir():
12
+ print(f"Error: Path is not a directory: {target}", file=sys.stderr)
13
+ sys.exit(1)
14
+
15
+ output_path = Path(output).resolve()
16
+
17
+ if output_path.exists() and not yes:
18
+ answer = input(f"File already exists: {output_path}\nOverwrite? [y/N] ")
19
+ if answer.lower() not in ("y", "yes"):
20
+ print("Aborted.")
21
+ return
22
+
23
+ if recursive:
24
+ ragjson_files = list(target.rglob(".rag/*.ragjson"))
25
+ else:
26
+ ragjson_files = list((target / ".rag").glob("*.ragjson"))
27
+
28
+ if not ragjson_files:
29
+ print("No .ragjson files found to export.")
30
+ return
31
+
32
+ with tarfile.open(output_path, "w:gz") as tar:
33
+ for rj_file in ragjson_files:
34
+ arcname = rj_file.relative_to(target)
35
+ tar.add(rj_file, arcname=arcname)
36
+
37
+ print(f"Exported {len(ragjson_files)} ragjson file(s) to: {output_path}")