drydock-cli 3.0.61__tar.gz → 3.0.62__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 (79) hide show
  1. {drydock_cli-3.0.61/drydock_cli.egg-info → drydock_cli-3.0.62}/PKG-INFO +7 -4
  2. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/README.md +4 -3
  3. drydock_cli-3.0.62/drydock/extract.py +94 -0
  4. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/graphrag.py +13 -5
  5. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/tuning.py +1 -1
  6. {drydock_cli-3.0.61 → drydock_cli-3.0.62/drydock_cli.egg-info}/PKG-INFO +7 -4
  7. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock_cli.egg-info/SOURCES.txt +2 -0
  8. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock_cli.egg-info/requires.txt +3 -0
  9. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/pyproject.toml +4 -1
  10. drydock_cli-3.0.62/tests/test_extract.py +71 -0
  11. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/LICENSE +0 -0
  12. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/NOTICE +0 -0
  13. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/__init__.py +0 -0
  14. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/__main__.py +0 -0
  15. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/agent.py +0 -0
  16. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/bash_safety.py +0 -0
  17. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/cli.py +0 -0
  18. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/compaction.py +0 -0
  19. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/config.py +0 -0
  20. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/detect.py +0 -0
  21. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/gittools.py +0 -0
  22. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/guards.py +0 -0
  23. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/loop_detect.py +0 -0
  24. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/mcp.py +0 -0
  25. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/providers.py +0 -0
  26. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/skills.py +0 -0
  27. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/tool_registry.py +0 -0
  28. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/tools/__init__.py +0 -0
  29. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/tui/__init__.py +0 -0
  30. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/tui/app.py +0 -0
  31. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/tui/approval.py +0 -0
  32. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/tui/messages.py +0 -0
  33. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/tui/widgets.py +0 -0
  34. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock/web.py +0 -0
  35. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock_cli.egg-info/dependency_links.txt +0 -0
  36. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock_cli.egg-info/entry_points.txt +0 -0
  37. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/drydock_cli.egg-info/top_level.txt +0 -0
  38. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/setup.cfg +0 -0
  39. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_approval.py +0 -0
  40. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_back_command.py +0 -0
  41. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_bash_output_bounding.py +0 -0
  42. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_bash_process_group.py +0 -0
  43. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_bash_safety.py +0 -0
  44. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_bash_timeout_network.py +0 -0
  45. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_cli_agents.py +0 -0
  46. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_compact_command.py +0 -0
  47. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_compaction.py +0 -0
  48. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_config.py +0 -0
  49. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_config_migration.py +0 -0
  50. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_context_limit_config.py +0 -0
  51. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_detect.py +0 -0
  52. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_dispatch.py +0 -0
  53. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_empty_response.py +0 -0
  54. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_failure_loop.py +0 -0
  55. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_first_run_setup.py +0 -0
  56. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_gittools.py +0 -0
  57. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_graphrag.py +0 -0
  58. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_guards_and_tools.py +0 -0
  59. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_hallucinated_tools.py +0 -0
  60. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_leaked_tool_call.py +0 -0
  61. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_loop_detect.py +0 -0
  62. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_mcp.py +0 -0
  63. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_oneshot_unreachable.py +0 -0
  64. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_plan_autocontinue.py +0 -0
  65. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_providers_unreachable.py +0 -0
  66. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_read_index.py +0 -0
  67. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_runaway_repetition.py +0 -0
  68. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_skills.py +0 -0
  69. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_stop.py +0 -0
  70. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_streaming_newlines.py +0 -0
  71. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_subagent.py +0 -0
  72. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_system_prompt_help.py +0 -0
  73. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_todo.py +0 -0
  74. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_tool_arg_parsing.py +0 -0
  75. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_tools_undo.py +0 -0
  76. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_tui.py +0 -0
  77. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_tuning.py +0 -0
  78. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_vision_input.py +0 -0
  79. {drydock_cli-3.0.61 → drydock_cli-3.0.62}/tests/test_web_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.61
3
+ Version: 3.0.62
4
4
  Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
5
5
  Author: Frank Bobe III
6
6
  License-Expression: Apache-2.0
@@ -18,6 +18,8 @@ Requires-Dist: pytest; extra == "dev"
18
18
  Requires-Dist: pytest-timeout; extra == "dev"
19
19
  Requires-Dist: ruff; extra == "dev"
20
20
  Requires-Dist: pyright; extra == "dev"
21
+ Provides-Extra: pdf
22
+ Requires-Dist: pypdf>=4; extra == "pdf"
21
23
  Dynamic: license-file
22
24
 
23
25
  # ⚓ Drydock
@@ -103,9 +105,10 @@ Typed into the prompt. The agent also knows these, so you can just **ask it**
103
105
 
104
106
  Once built, the agent **automatically** retrieves from it (read-only `Knowledge`
105
107
  tool) when a question touches your material. Ingests text formats
106
- (`.md .txt .py .js .json .yaml .sql …`); convert PDFs/Word to text first
107
- (e.g. `pdftotext file.pdf file.txt`). The index is a single JSON at
108
- `<project>/.drydock/graphrag.json` clean-room, stdlib-only, no embeddings.
108
+ (`.md .txt .py .js .json .yaml .sql …`) **plus PDF and Word (`.docx`)**. `.docx`
109
+ needs nothing extra; PDF uses the `pdftotext` binary (poppler) if present, else
110
+ `pip install drydock-cli[pdf]` (pypdf). The index is a single JSON at
111
+ `<project>/.drydock/graphrag.json` — clean-room, no embeddings.
109
112
 
110
113
  ### Custom skills
111
114
 
@@ -81,9 +81,10 @@ Typed into the prompt. The agent also knows these, so you can just **ask it**
81
81
 
82
82
  Once built, the agent **automatically** retrieves from it (read-only `Knowledge`
83
83
  tool) when a question touches your material. Ingests text formats
84
- (`.md .txt .py .js .json .yaml .sql …`); convert PDFs/Word to text first
85
- (e.g. `pdftotext file.pdf file.txt`). The index is a single JSON at
86
- `<project>/.drydock/graphrag.json` clean-room, stdlib-only, no embeddings.
84
+ (`.md .txt .py .js .json .yaml .sql …`) **plus PDF and Word (`.docx`)**. `.docx`
85
+ needs nothing extra; PDF uses the `pdftotext` binary (poppler) if present, else
86
+ `pip install drydock-cli[pdf]` (pypdf). The index is a single JSON at
87
+ `<project>/.drydock/graphrag.json` — clean-room, no embeddings.
87
88
 
88
89
  ### Custom skills
89
90
 
@@ -0,0 +1,94 @@
1
+ """Document text extraction for GraphRAG ingestion — PDF + Word (.docx).
2
+
3
+ Kept clean-room and dependency-light:
4
+ • .docx is a zip of XML, so we read it with STDLIB only (zipfile + ElementTree)
5
+ — no dependency.
6
+ • .pdf uses whatever the system already has: the `pdftotext` binary (poppler)
7
+ if present, else the optional `pypdf` package (`pip install drydock-cli[pdf]`).
8
+
9
+ Returns None when a document can't be read (unsupported, corrupt, or no PDF
10
+ backend available) so ingestion skips it cleanly instead of failing.
11
+
12
+ All logic original to Drydock.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import subprocess
17
+ import xml.etree.ElementTree as ET
18
+ import zipfile
19
+ from pathlib import Path
20
+
21
+ # Extensions we can pull text out of (beyond the plain-text formats GraphRAG
22
+ # already reads directly).
23
+ EXTRACTABLE_EXT = {".pdf", ".docx"}
24
+
25
+ _W = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}"
26
+
27
+
28
+ def _extract_docx(path: str | Path) -> str | None:
29
+ """Pull paragraph text from a .docx (stdlib only). Each <w:p> is a paragraph;
30
+ its text lives in <w:t> runs."""
31
+ try:
32
+ with zipfile.ZipFile(path) as z:
33
+ xml = z.read("word/document.xml")
34
+ except (zipfile.BadZipFile, KeyError, OSError):
35
+ return None
36
+ try:
37
+ root = ET.fromstring(xml)
38
+ except ET.ParseError:
39
+ return None
40
+ paras: list[str] = []
41
+ for p in root.iter(f"{_W}p"):
42
+ runs = [t.text for t in p.iter(f"{_W}t") if t.text]
43
+ if runs:
44
+ paras.append("".join(runs))
45
+ text = "\n\n".join(paras).strip()
46
+ return text or None
47
+
48
+
49
+ def _extract_pdf(path: str | Path) -> str | None:
50
+ """Extract text from a PDF: try the `pdftotext` binary first, then `pypdf`.
51
+ Returns None if neither is available or the PDF has no extractable text."""
52
+ # 1) pdftotext (poppler) — fast and common on Linux/macOS.
53
+ try:
54
+ r = subprocess.run(
55
+ ["pdftotext", "-q", str(path), "-"],
56
+ capture_output=True, text=True, timeout=120,
57
+ )
58
+ if r.returncode == 0 and r.stdout.strip():
59
+ return r.stdout
60
+ except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
61
+ pass
62
+ # 2) pypdf (optional dependency: drydock-cli[pdf]).
63
+ try:
64
+ import pypdf
65
+
66
+ reader = pypdf.PdfReader(str(path))
67
+ text = "\n\n".join((pg.extract_text() or "") for pg in reader.pages).strip()
68
+ return text or None
69
+ except Exception: # noqa: BLE001 — optional dep missing / unreadable PDF
70
+ return None
71
+
72
+
73
+ def extract_document(path: str | Path) -> str | None:
74
+ """Extract text from a supported document (.pdf/.docx), or None."""
75
+ ext = Path(path).suffix.lower()
76
+ if ext == ".docx":
77
+ return _extract_docx(path)
78
+ if ext == ".pdf":
79
+ return _extract_pdf(path)
80
+ return None
81
+
82
+
83
+ def pdf_backend_available() -> bool:
84
+ """Whether a PDF can be extracted at all (pdftotext binary or pypdf)."""
85
+ from shutil import which
86
+
87
+ if which("pdftotext"):
88
+ return True
89
+ try:
90
+ import pypdf # noqa: F401
91
+
92
+ return True
93
+ except ImportError:
94
+ return False
@@ -26,6 +26,8 @@ import json
26
26
  import os
27
27
  import re
28
28
  from collections import Counter, defaultdict
29
+
30
+ from drydock import extract
29
31
  from pathlib import Path
30
32
 
31
33
  # Files we ingest as text. Everything else (binaries, images) is skipped.
@@ -106,7 +108,8 @@ def _iter_text_files(paths: list[str]):
106
108
  dirs[:] = [d for d in dirs if d not in _SKIP_DIRS]
107
109
  for f in files:
108
110
  fp = Path(root) / f
109
- if fp.suffix.lower() in _TEXT_EXT or fp.suffix == "":
111
+ ext = fp.suffix.lower()
112
+ if ext in _TEXT_EXT or ext in extract.EXTRACTABLE_EXT or fp.suffix == "":
110
113
  yield fp
111
114
 
112
115
 
@@ -119,10 +122,15 @@ def _ingest_files(paths, cwd, chunks, entity_chunks, edges, skip_sources):
119
122
  rel = os.path.relpath(str(fp), cwd)
120
123
  if rel in skip_sources:
121
124
  continue
122
- try:
123
- text = fp.read_text("utf-8", "ignore")
124
- except OSError:
125
- continue
125
+ if fp.suffix.lower() in extract.EXTRACTABLE_EXT:
126
+ text = extract.extract_document(fp) # PDF/Word → text (or None)
127
+ if not text:
128
+ continue # unreadable / no PDF backend — skip cleanly
129
+ else:
130
+ try:
131
+ text = fp.read_text("utf-8", "ignore")
132
+ except OSError:
133
+ continue
126
134
  if not text.strip():
127
135
  continue
128
136
  added += 1
@@ -217,7 +217,7 @@ _DRYDOCK_COMMANDS_HELP = (
217
217
  "folder), `/graphrag add <path>` to add more, `/graphrag query <q>` to test "
218
218
  "it, `/graphrag status`, `/graphrag clear`. Once built, you automatically use "
219
219
  "the `Knowledge` tool to draw on it. It ingests text formats (md/txt/code/"
220
- "json/yaml/…); PDFs/Word must be converted to text first.\n"
220
+ "json/yaml/…) plus PDF and Word (.docx) directly.\n"
221
221
  "- Custom skills (reusable `/<name>` prompts): `/skills new <name> <prompt "
222
222
  "text>` creates one (use $ARGS in the prompt for trailing input); `/skills` "
223
223
  "lists them; then they run it as `/<name>`.\n"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drydock-cli
3
- Version: 3.0.61
3
+ Version: 3.0.62
4
4
  Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
5
5
  Author: Frank Bobe III
6
6
  License-Expression: Apache-2.0
@@ -18,6 +18,8 @@ Requires-Dist: pytest; extra == "dev"
18
18
  Requires-Dist: pytest-timeout; extra == "dev"
19
19
  Requires-Dist: ruff; extra == "dev"
20
20
  Requires-Dist: pyright; extra == "dev"
21
+ Provides-Extra: pdf
22
+ Requires-Dist: pypdf>=4; extra == "pdf"
21
23
  Dynamic: license-file
22
24
 
23
25
  # ⚓ Drydock
@@ -103,9 +105,10 @@ Typed into the prompt. The agent also knows these, so you can just **ask it**
103
105
 
104
106
  Once built, the agent **automatically** retrieves from it (read-only `Knowledge`
105
107
  tool) when a question touches your material. Ingests text formats
106
- (`.md .txt .py .js .json .yaml .sql …`); convert PDFs/Word to text first
107
- (e.g. `pdftotext file.pdf file.txt`). The index is a single JSON at
108
- `<project>/.drydock/graphrag.json` clean-room, stdlib-only, no embeddings.
108
+ (`.md .txt .py .js .json .yaml .sql …`) **plus PDF and Word (`.docx`)**. `.docx`
109
+ needs nothing extra; PDF uses the `pdftotext` binary (poppler) if present, else
110
+ `pip install drydock-cli[pdf]` (pypdf). The index is a single JSON at
111
+ `<project>/.drydock/graphrag.json` — clean-room, no embeddings.
109
112
 
110
113
  ### Custom skills
111
114
 
@@ -10,6 +10,7 @@ drydock/cli.py
10
10
  drydock/compaction.py
11
11
  drydock/config.py
12
12
  drydock/detect.py
13
+ drydock/extract.py
13
14
  drydock/gittools.py
14
15
  drydock/graphrag.py
15
16
  drydock/guards.py
@@ -47,6 +48,7 @@ tests/test_context_limit_config.py
47
48
  tests/test_detect.py
48
49
  tests/test_dispatch.py
49
50
  tests/test_empty_response.py
51
+ tests/test_extract.py
50
52
  tests/test_failure_loop.py
51
53
  tests/test_first_run_setup.py
52
54
  tests/test_gittools.py
@@ -6,3 +6,6 @@ pytest
6
6
  pytest-timeout
7
7
  ruff
8
8
  pyright
9
+
10
+ [pdf]
11
+ pypdf>=4
@@ -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.61"
10
+ version = "3.0.62"
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"
@@ -29,6 +29,9 @@ drydock = "drydock.cli:main"
29
29
 
30
30
  [project.optional-dependencies]
31
31
  dev = ["pytest", "pytest-timeout", "ruff", "pyright"]
32
+ # PDF ingestion fallback when the `pdftotext` binary (poppler) isn't installed.
33
+ # .docx needs nothing extra (stdlib zip/xml). `pip install drydock-cli[pdf]`.
34
+ pdf = ["pypdf>=4"]
32
35
 
33
36
  [tool.ruff]
34
37
  target-version = "py312"
@@ -0,0 +1,71 @@
1
+ """PDF + Word (.docx) document extraction for GraphRAG ingestion."""
2
+ from __future__ import annotations
3
+
4
+ import zipfile
5
+
6
+ import pytest
7
+
8
+ from drydock import extract, graphrag
9
+
10
+
11
+ def _make_docx(path, text):
12
+ doc = ('<?xml version="1.0"?><w:document xmlns:w='
13
+ '"http://schemas.openxmlformats.org/wordprocessingml/2006/main">'
14
+ f'<w:body><w:p><w:r><w:t>{text}</w:t></w:r></w:p></w:body></w:document>')
15
+ with zipfile.ZipFile(path, "w") as z:
16
+ z.writestr("word/document.xml", doc)
17
+
18
+
19
+ def _make_pdf(path, text):
20
+ objs = [b"<< /Type /Catalog /Pages 2 0 R >>",
21
+ b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
22
+ b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R "
23
+ b"/Resources << /Font << /F1 5 0 R >> >> >>"]
24
+ stream = b"BT /F1 18 Tf 72 700 Td (" + text.encode() + b") Tj ET"
25
+ objs.append(b"<< /Length " + str(len(stream)).encode() + b" >>\nstream\n" + stream + b"\nendstream")
26
+ objs.append(b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>")
27
+ out = b"%PDF-1.4\n"; offs = []
28
+ for i, o in enumerate(objs, 1):
29
+ offs.append(len(out)); out += str(i).encode() + b" 0 obj\n" + o + b"\nendobj\n"
30
+ x = len(out); out += b"xref\n0 " + str(len(objs) + 1).encode() + b"\n0000000000 65535 f \n"
31
+ for o in offs:
32
+ out += ("%010d 00000 n \n" % o).encode()
33
+ out += (b"trailer\n<< /Size " + str(len(objs) + 1).encode() + b" /Root 1 0 R >>\n"
34
+ b"startxref\n" + str(x).encode() + b"\n%%EOF")
35
+ path.write_bytes(out)
36
+
37
+
38
+ def test_docx_extraction(tmp_path):
39
+ p = tmp_path / "d.docx"
40
+ _make_docx(p, "The Nimbus service uses the Falcon-Token header.")
41
+ assert extract.extract_document(p) == "The Nimbus service uses the Falcon-Token header."
42
+
43
+
44
+ def test_pdf_extraction(tmp_path):
45
+ if not extract.pdf_backend_available():
46
+ pytest.skip("no PDF backend (pdftotext/pypdf) available")
47
+ p = tmp_path / "m.pdf"
48
+ _make_pdf(p, "Refunds via the Zephyr gateway.")
49
+ out = extract.extract_document(p)
50
+ assert out and "Zephyr gateway" in out
51
+
52
+
53
+ def test_unsupported_and_corrupt_return_none(tmp_path):
54
+ assert extract.extract_document(tmp_path / "x.png") is None
55
+ bad = tmp_path / "bad.docx"; bad.write_text("not a zip")
56
+ assert extract.extract_document(bad) is None
57
+
58
+
59
+ def test_graphrag_ingests_docx_and_pdf(tmp_path):
60
+ _make_docx(tmp_path / "spec.docx", "The Nimbus API uses the Falcon-Token header for auth.")
61
+ if extract.pdf_backend_available():
62
+ _make_pdf(tmp_path / "manual.pdf", "Refunds are issued via the Zephyr gateway.")
63
+ store = tmp_path / ".drydock" / "graphrag.json"
64
+ graphrag.build_index(["."], store, cwd=str(tmp_path))
65
+ idx = graphrag.load_index(store)
66
+ srcs = graphrag.sources(idx)
67
+ assert "spec.docx" in srcs
68
+ assert graphrag.query_index(idx, "Falcon-Token header")["chunks"][0]["source"] == "spec.docx"
69
+ if extract.pdf_backend_available():
70
+ assert "manual.pdf" in srcs
71
+ assert graphrag.query_index(idx, "refunds Zephyr gateway")["chunks"][0]["source"] == "manual.pdf"
File without changes
File without changes
File without changes