drydock-cli 3.0.59__tar.gz → 3.0.60__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.
- {drydock_cli-3.0.59/drydock_cli.egg-info → drydock_cli-3.0.60}/PKG-INFO +1 -1
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/cli.py +14 -6
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/graphrag.py +47 -13
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/tui/app.py +35 -13
- {drydock_cli-3.0.59 → drydock_cli-3.0.60/drydock_cli.egg-info}/PKG-INFO +1 -1
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/pyproject.toml +1 -1
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_graphrag.py +32 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/LICENSE +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/NOTICE +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/README.md +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/__init__.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/__main__.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/agent.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/compaction.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/config.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/detect.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/gittools.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/guards.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/mcp.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/providers.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/skills.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/tools/__init__.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/tui/widgets.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/tuning.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock/web.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock_cli.egg-info/SOURCES.txt +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/setup.cfg +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_approval.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_back_command.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_bash_output_bounding.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_bash_process_group.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_bash_timeout_network.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_compact_command.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_compaction.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_config.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_config_migration.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_context_limit_config.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_detect.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_dispatch.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_empty_response.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_failure_loop.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_first_run_setup.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_gittools.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_guards_and_tools.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_mcp.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_oneshot_unreachable.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_read_index.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_runaway_repetition.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_skills.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_stop.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_streaming_newlines.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_subagent.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_todo.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_tool_arg_parsing.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_tui.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_tuning.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_vision_input.py +0 -0
- {drydock_cli-3.0.59 → drydock_cli-3.0.60}/tests/test_web_tools.py +0 -0
|
@@ -198,17 +198,23 @@ def handle_command(cmd: str, state: AgentState, config: dict) -> bool:
|
|
|
198
198
|
parts = raw.split() # raw = case-preserved, so paths survive
|
|
199
199
|
sub = parts[1].lower() if len(parts) > 1 else ""
|
|
200
200
|
rest = " ".join(parts[2:]).strip()
|
|
201
|
-
if sub
|
|
202
|
-
print_colored(f" Building knowledge base from {rest} …", "dim")
|
|
201
|
+
if sub in ("build", "add") and rest:
|
|
203
202
|
try:
|
|
204
|
-
|
|
203
|
+
fn = graphrag.build_index if sub == "build" else graphrag.add_to_index
|
|
204
|
+
stats = fn([rest], store, cwd=cwd)
|
|
205
205
|
print_colored(
|
|
206
|
-
f" ✓ {stats['files']} files · {stats['chunks']} chunks · "
|
|
206
|
+
f" ✓ {sub}: +{stats['files']} files · {stats['chunks']} chunks · "
|
|
207
207
|
f"{stats['entities']} entities · {stats['edges']} edges → {store}",
|
|
208
208
|
"green",
|
|
209
209
|
)
|
|
210
210
|
except Exception as e: # noqa: BLE001
|
|
211
|
-
print_colored(f" graphrag
|
|
211
|
+
print_colored(f" graphrag {sub} failed: {e}", "red")
|
|
212
|
+
elif sub == "query" and rest:
|
|
213
|
+
index = graphrag.load_index(store)
|
|
214
|
+
if index is None:
|
|
215
|
+
print_colored(" No knowledge base. Build: /graphrag build <path>", "dim")
|
|
216
|
+
else:
|
|
217
|
+
print(graphrag.format_results(graphrag.query_index(index, rest, k=3), rest))
|
|
212
218
|
elif sub == "clear":
|
|
213
219
|
store.unlink(missing_ok=True)
|
|
214
220
|
print_colored(" Knowledge base cleared.", "green")
|
|
@@ -217,9 +223,11 @@ def handle_command(cmd: str, state: AgentState, config: dict) -> bool:
|
|
|
217
223
|
if index is None:
|
|
218
224
|
print_colored(" No knowledge base. Build: /graphrag build <path>", "dim")
|
|
219
225
|
else:
|
|
226
|
+
srcs = graphrag.sources(index)
|
|
220
227
|
print_colored(
|
|
221
228
|
f" Knowledge base: {len(index.get('chunks', []))} chunks · "
|
|
222
|
-
f"{len(index.get('entities', {}))} entities (
|
|
229
|
+
f"{len(index.get('entities', {}))} entities · {len(srcs)} sources "
|
|
230
|
+
f"({store})", "cyan")
|
|
223
231
|
return True
|
|
224
232
|
elif cmd in ("/quit", "/exit"):
|
|
225
233
|
raise KeyboardInterrupt
|
|
@@ -110,26 +110,23 @@ def _iter_text_files(paths: list[str]):
|
|
|
110
110
|
yield fp
|
|
111
111
|
|
|
112
112
|
|
|
113
|
-
def
|
|
114
|
-
"""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
"""
|
|
118
|
-
chunks: list[dict] = []
|
|
119
|
-
entity_chunks: dict[str, set[int]] = defaultdict(set)
|
|
120
|
-
edges: dict[str, Counter] = defaultdict(Counter)
|
|
121
|
-
files = 0
|
|
122
|
-
|
|
113
|
+
def _ingest_files(paths, cwd, chunks, entity_chunks, edges, skip_sources):
|
|
114
|
+
"""Chunk + extract + graph every text file under paths into the (mutable)
|
|
115
|
+
accumulators, skipping any source already present. Returns files added."""
|
|
116
|
+
added = 0
|
|
123
117
|
for fp in _iter_text_files([str(Path(cwd) / p) if not os.path.isabs(p) else p
|
|
124
118
|
for p in paths]):
|
|
119
|
+
rel = os.path.relpath(str(fp), cwd)
|
|
120
|
+
if rel in skip_sources:
|
|
121
|
+
continue
|
|
125
122
|
try:
|
|
126
123
|
text = fp.read_text("utf-8", "ignore")
|
|
127
124
|
except OSError:
|
|
128
125
|
continue
|
|
129
126
|
if not text.strip():
|
|
130
127
|
continue
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
added += 1
|
|
129
|
+
skip_sources.add(rel) # don't double-ingest the same file in one call
|
|
133
130
|
for body in _chunk_text(text):
|
|
134
131
|
cid = len(chunks)
|
|
135
132
|
ents = extract_entities(body)
|
|
@@ -140,7 +137,10 @@ def build_index(paths: list[str], store_path: str | Path, *, cwd: str = ".") ->
|
|
|
140
137
|
for b in ents[i + 1:]:
|
|
141
138
|
edges[a][b] += 1
|
|
142
139
|
edges[b][a] += 1
|
|
140
|
+
return added
|
|
141
|
+
|
|
143
142
|
|
|
143
|
+
def _save(store_path, chunks, entity_chunks, edges, files_added):
|
|
144
144
|
index = {
|
|
145
145
|
"version": 1,
|
|
146
146
|
"chunks": chunks,
|
|
@@ -151,13 +151,47 @@ def build_index(paths: list[str], store_path: str | Path, *, cwd: str = ".") ->
|
|
|
151
151
|
sp.parent.mkdir(parents=True, exist_ok=True)
|
|
152
152
|
sp.write_text(json.dumps(index), encoding="utf-8")
|
|
153
153
|
return {
|
|
154
|
-
"files":
|
|
154
|
+
"files": files_added,
|
|
155
155
|
"chunks": len(chunks),
|
|
156
156
|
"entities": len(entity_chunks),
|
|
157
157
|
"edges": sum(len(v) for v in edges.values()) // 2,
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
|
|
161
|
+
def build_index(paths: list[str], store_path: str | Path, *, cwd: str = ".") -> dict:
|
|
162
|
+
"""Build (or REBUILD from scratch) the knowledge graph from paths; persist to
|
|
163
|
+
store_path. Returns {files, chunks, entities, edges}."""
|
|
164
|
+
chunks: list[dict] = []
|
|
165
|
+
entity_chunks: dict[str, set[int]] = defaultdict(set)
|
|
166
|
+
edges: dict[str, Counter] = defaultdict(Counter)
|
|
167
|
+
added = _ingest_files(paths, cwd, chunks, entity_chunks, edges, set())
|
|
168
|
+
return _save(store_path, chunks, entity_chunks, edges, added)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def add_to_index(paths: list[str], store_path: str | Path, *, cwd: str = ".") -> dict:
|
|
172
|
+
"""Incrementally ADD documents to an existing index (build it if none yet).
|
|
173
|
+
Files already indexed (by relative path) are skipped — clear+build to refresh
|
|
174
|
+
changed files. Returns {files (added), chunks, entities, edges} totals."""
|
|
175
|
+
existing = load_index(store_path)
|
|
176
|
+
if existing is None:
|
|
177
|
+
return build_index(paths, store_path, cwd=cwd)
|
|
178
|
+
chunks: list[dict] = list(existing.get("chunks", []))
|
|
179
|
+
entity_chunks: dict[str, set[int]] = defaultdict(set)
|
|
180
|
+
for e, cids in existing.get("entities", {}).items():
|
|
181
|
+
entity_chunks[e] = set(cids)
|
|
182
|
+
edges: dict[str, Counter] = defaultdict(Counter)
|
|
183
|
+
for a, nbrs in existing.get("edges", {}).items():
|
|
184
|
+
edges[a] = Counter(nbrs)
|
|
185
|
+
skip = {c["source"] for c in chunks}
|
|
186
|
+
added = _ingest_files(paths, cwd, chunks, entity_chunks, edges, skip)
|
|
187
|
+
return _save(store_path, chunks, entity_chunks, edges, added)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def sources(index: dict) -> list[str]:
|
|
191
|
+
"""The distinct source files in an index, sorted."""
|
|
192
|
+
return sorted({c["source"] for c in index.get("chunks", [])})
|
|
193
|
+
|
|
194
|
+
|
|
161
195
|
def load_index(store_path: str | Path) -> dict | None:
|
|
162
196
|
sp = Path(store_path)
|
|
163
197
|
if not sp.exists():
|
|
@@ -490,8 +490,8 @@ class DrydockApp(App):
|
|
|
490
490
|
" /stop stop the running turn (or press Esc)\n"
|
|
491
491
|
" /status session model, cwd, turns, tokens\n"
|
|
492
492
|
" /compact shrink old context to free up the window\n"
|
|
493
|
-
" /graphrag
|
|
494
|
-
"
|
|
493
|
+
" /graphrag ingest docs into a knowledge base the agent can use\n"
|
|
494
|
+
" build <path> · add <path> · query <q> · status · clear\n"
|
|
495
495
|
" /skills list your reusable /<name> skills\n"
|
|
496
496
|
" /loop /loop <count> <prompt> — repeat a prompt (Esc stops)\n"
|
|
497
497
|
" /mcp list connected MCP servers and their tools\n"
|
|
@@ -634,33 +634,52 @@ class DrydockApp(App):
|
|
|
634
634
|
sub = parts[0].lower() if parts else ""
|
|
635
635
|
rest = parts[1].strip() if len(parts) > 1 else ""
|
|
636
636
|
|
|
637
|
-
if sub
|
|
637
|
+
if sub in ("build", "add"):
|
|
638
638
|
if not rest:
|
|
639
|
-
self._info("usage: /graphrag
|
|
639
|
+
self._info(f"usage: /graphrag {sub} <path> (a file or directory of docs/code)")
|
|
640
640
|
return
|
|
641
|
-
|
|
641
|
+
verb = "Rebuilding" if sub == "build" else "Ingesting into"
|
|
642
|
+
self._info(f"{verb} knowledge base from {rest} …")
|
|
642
643
|
try:
|
|
643
|
-
|
|
644
|
+
fn = graphrag.build_index if sub == "build" else graphrag.add_to_index
|
|
645
|
+
stats = fn([rest], store, cwd=cwd)
|
|
644
646
|
except Exception as e: # noqa: BLE001 — surface, never crash the TUI
|
|
645
|
-
self._mount(ErrorMessage(f"graphrag
|
|
647
|
+
self._mount(ErrorMessage(f"graphrag {sub} failed: {e}"))
|
|
648
|
+
return
|
|
649
|
+
if sub == "add" and stats["files"] == 0:
|
|
650
|
+
self._info(f"No new documents under {rest} (already indexed, or no text found).")
|
|
646
651
|
return
|
|
647
652
|
if not stats["chunks"]:
|
|
648
653
|
self._info(f"No text found under {rest}. Nothing was indexed.")
|
|
649
654
|
return
|
|
655
|
+
verb2 = "built" if sub == "build" else f"updated (+{stats['files']} new files)"
|
|
650
656
|
self._info(
|
|
651
|
-
f"✓ Knowledge base
|
|
652
|
-
f"{stats['
|
|
653
|
-
f"
|
|
654
|
-
"use the Knowledge tool to draw on it."
|
|
657
|
+
f"✓ Knowledge base {verb2}: {stats['chunks']} chunks · "
|
|
658
|
+
f"{stats['entities']} entities · {stats['edges']} edges.\n"
|
|
659
|
+
f"Stored at {store}. The agent draws on it via the Knowledge tool."
|
|
655
660
|
)
|
|
661
|
+
elif sub == "query":
|
|
662
|
+
if not rest:
|
|
663
|
+
self._info("usage: /graphrag query <question> (test what the KB returns)")
|
|
664
|
+
return
|
|
665
|
+
index = graphrag.load_index(store)
|
|
666
|
+
if index is None:
|
|
667
|
+
self._info("No knowledge base yet. Build one: /graphrag build <path>")
|
|
668
|
+
return
|
|
669
|
+
res = graphrag.query_index(index, rest, k=3)
|
|
670
|
+
self._info(graphrag.format_results(res, rest))
|
|
656
671
|
elif sub in ("", "status"):
|
|
657
672
|
index = graphrag.load_index(store)
|
|
658
673
|
if index is None:
|
|
659
674
|
self._info("No knowledge base yet. Build one: /graphrag build <path>")
|
|
660
675
|
else:
|
|
676
|
+
srcs = graphrag.sources(index)
|
|
677
|
+
shown = "\n".join(f" · {s}" for s in srcs[:20])
|
|
678
|
+
more = f"\n … +{len(srcs) - 20} more" if len(srcs) > 20 else ""
|
|
661
679
|
self._info(
|
|
662
680
|
f"Knowledge base: {len(index.get('chunks', []))} chunks · "
|
|
663
|
-
f"{len(index.get('entities', {}))} entities
|
|
681
|
+
f"{len(index.get('entities', {}))} entities · {len(srcs)} sources "
|
|
682
|
+
f"({store}).\n{shown}{more}"
|
|
664
683
|
)
|
|
665
684
|
elif sub == "clear":
|
|
666
685
|
try:
|
|
@@ -669,7 +688,10 @@ class DrydockApp(App):
|
|
|
669
688
|
except OSError as e:
|
|
670
689
|
self._mount(ErrorMessage(f"could not clear: {e}"))
|
|
671
690
|
else:
|
|
672
|
-
self._info(
|
|
691
|
+
self._info(
|
|
692
|
+
"usage: /graphrag build <path> · add <path> · query <question> "
|
|
693
|
+
"· status · clear"
|
|
694
|
+
)
|
|
673
695
|
|
|
674
696
|
def _persist_config(self) -> None:
|
|
675
697
|
"""Save the persistable settings (model/provider/base_url/… — save_file
|
|
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|
|
7
7
|
# PyPI distribution name is drydock-cli (the established install name, continued
|
|
8
8
|
# from the retired v2 fork); the import package + CLI command stay `drydock`.
|
|
9
9
|
name = "drydock-cli"
|
|
10
|
-
version = "3.0.
|
|
10
|
+
version = "3.0.60"
|
|
11
11
|
description = "Drydock — a local, provider-agnostic terminal coding agent for local LLMs"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.11"
|
|
@@ -73,3 +73,35 @@ import pytest # noqa: E402
|
|
|
73
73
|
def _make_corpus(tmp_path):
|
|
74
74
|
_corpus(tmp_path)
|
|
75
75
|
yield
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_add_to_index_is_incremental(tmp_path):
|
|
79
|
+
store = tmp_path / ".drydock" / "graphrag.json"
|
|
80
|
+
(tmp_path / "one.md").write_text("The Alpha service uses the Beacon token.")
|
|
81
|
+
s1 = graphrag.build_index(["one.md"], store, cwd=str(tmp_path))
|
|
82
|
+
assert s1["files"] == 1
|
|
83
|
+
# add a second doc — existing chunks preserved, new ones appended
|
|
84
|
+
(tmp_path / "two.md").write_text("The Gamma module writes to the Delta table.")
|
|
85
|
+
s2 = graphrag.add_to_index(["two.md"], store, cwd=str(tmp_path))
|
|
86
|
+
assert s2["files"] == 1 # only the NEW file counted
|
|
87
|
+
assert s2["chunks"] > s1["chunks"] # grew
|
|
88
|
+
idx = graphrag.load_index(store)
|
|
89
|
+
assert set(graphrag.sources(idx)) == {"one.md", "two.md"}
|
|
90
|
+
# both docs are queryable
|
|
91
|
+
assert graphrag.query_index(idx, "Beacon token")["chunks"][0]["source"] == "one.md"
|
|
92
|
+
assert graphrag.query_index(idx, "Delta table")["chunks"][0]["source"] == "two.md"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_add_skips_already_indexed(tmp_path):
|
|
96
|
+
store = tmp_path / ".drydock" / "graphrag.json"
|
|
97
|
+
(tmp_path / "one.md").write_text("Alpha Beacon content here.")
|
|
98
|
+
graphrag.build_index(["one.md"], store, cwd=str(tmp_path))
|
|
99
|
+
s = graphrag.add_to_index(["one.md"], store, cwd=str(tmp_path)) # same file again
|
|
100
|
+
assert s["files"] == 0 # nothing re-ingested
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_add_builds_when_no_index_yet(tmp_path):
|
|
104
|
+
store = tmp_path / ".drydock" / "graphrag.json"
|
|
105
|
+
(tmp_path / "x.md").write_text("Some Echo content.")
|
|
106
|
+
s = graphrag.add_to_index(["x.md"], store, cwd=str(tmp_path))
|
|
107
|
+
assert s["files"] == 1 and graphrag.load_index(store) is not None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|