docline 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.
Files changed (119) hide show
  1. docline-0.1.0/LICENSE +21 -0
  2. docline-0.1.0/PKG-INFO +277 -0
  3. docline-0.1.0/README.md +253 -0
  4. docline-0.1.0/pyproject.toml +57 -0
  5. docline-0.1.0/setup.cfg +4 -0
  6. docline-0.1.0/src/docline/__init__.py +1 -0
  7. docline-0.1.0/src/docline/__main__.py +16 -0
  8. docline-0.1.0/src/docline/_tools/__init__.py +9 -0
  9. docline-0.1.0/src/docline/_tools/docling_worker.py +404 -0
  10. docline-0.1.0/src/docline/app.py +1048 -0
  11. docline-0.1.0/src/docline/app_models.py +201 -0
  12. docline-0.1.0/src/docline/cli.py +595 -0
  13. docline-0.1.0/src/docline/config.py +57 -0
  14. docline-0.1.0/src/docline/dependencies.py +86 -0
  15. docline-0.1.0/src/docline/elt/__init__.py +1 -0
  16. docline-0.1.0/src/docline/elt/config.py +202 -0
  17. docline-0.1.0/src/docline/elt/execute.py +608 -0
  18. docline-0.1.0/src/docline/elt/manifest_models.py +93 -0
  19. docline-0.1.0/src/docline/elt/models.py +82 -0
  20. docline-0.1.0/src/docline/elt/orchestrate.py +47 -0
  21. docline-0.1.0/src/docline/elt/paths.py +74 -0
  22. docline-0.1.0/src/docline/elt/source_keys.py +79 -0
  23. docline-0.1.0/src/docline/fetch/__init__.py +0 -0
  24. docline-0.1.0/src/docline/fetch/crawl.py +557 -0
  25. docline-0.1.0/src/docline/fetch/html_extract.py +290 -0
  26. docline-0.1.0/src/docline/fetch/html_normalize.py +92 -0
  27. docline-0.1.0/src/docline/fetch/http.py +159 -0
  28. docline-0.1.0/src/docline/fetch/models.py +43 -0
  29. docline-0.1.0/src/docline/fetch/sitemap.py +272 -0
  30. docline-0.1.0/src/docline/fetch/staging.py +166 -0
  31. docline-0.1.0/src/docline/fetch/url_canonical.py +127 -0
  32. docline-0.1.0/src/docline/fetch/url_policy.py +92 -0
  33. docline-0.1.0/src/docline/mcp/__init__.py +1 -0
  34. docline-0.1.0/src/docline/mcp/exceptions.py +7 -0
  35. docline-0.1.0/src/docline/mcp/server.py +113 -0
  36. docline-0.1.0/src/docline/paths.py +145 -0
  37. docline-0.1.0/src/docline/process/__init__.py +5 -0
  38. docline-0.1.0/src/docline/process/assemble.py +162 -0
  39. docline-0.1.0/src/docline/process/ast_lint.py +65 -0
  40. docline-0.1.0/src/docline/process/batch_dispatch.py +243 -0
  41. docline-0.1.0/src/docline/process/canonical_url.py +149 -0
  42. docline-0.1.0/src/docline/process/correction.py +62 -0
  43. docline-0.1.0/src/docline/process/cross_doc_links.py +179 -0
  44. docline-0.1.0/src/docline/process/docfx_includes.py +126 -0
  45. docline-0.1.0/src/docline/process/docfx_normalize.py +144 -0
  46. docline-0.1.0/src/docline/process/docfx_tabs.py +105 -0
  47. docline-0.1.0/src/docline/process/fidelity_scorer.py +870 -0
  48. docline-0.1.0/src/docline/process/hashing.py +22 -0
  49. docline-0.1.0/src/docline/process/heading_validation.py +241 -0
  50. docline-0.1.0/src/docline/process/identity.py +37 -0
  51. docline-0.1.0/src/docline/process/manifest.py +84 -0
  52. docline-0.1.0/src/docline/process/metadata.py +86 -0
  53. docline-0.1.0/src/docline/process/ocr_cap.py +99 -0
  54. docline-0.1.0/src/docline/process/output.py +25 -0
  55. docline-0.1.0/src/docline/process/output_contract.py +449 -0
  56. docline-0.1.0/src/docline/process/page_range.py +199 -0
  57. docline-0.1.0/src/docline/process/pdf_batch.py +568 -0
  58. docline-0.1.0/src/docline/process/pdf_triage.py +1114 -0
  59. docline-0.1.0/src/docline/process/prompts.py +57 -0
  60. docline-0.1.0/src/docline/process/quality_metrics.py +213 -0
  61. docline-0.1.0/src/docline/process/quarantine.py +66 -0
  62. docline-0.1.0/src/docline/process/segment.py +211 -0
  63. docline-0.1.0/src/docline/process/toc_parser.py +122 -0
  64. docline-0.1.0/src/docline/process/transcripts.py +134 -0
  65. docline-0.1.0/src/docline/progress.py +279 -0
  66. docline-0.1.0/src/docline/quarantine_viewer.py +125 -0
  67. docline-0.1.0/src/docline/readers/__init__.py +1 -0
  68. docline-0.1.0/src/docline/readers/documents.py +54 -0
  69. docline-0.1.0/src/docline/readers/docx.py +692 -0
  70. docline-0.1.0/src/docline/readers/github.py +207 -0
  71. docline-0.1.0/src/docline/readers/limits.py +106 -0
  72. docline-0.1.0/src/docline/readers/mistral.py +201 -0
  73. docline-0.1.0/src/docline/readers/openapi/__init__.py +11 -0
  74. docline-0.1.0/src/docline/readers/openapi/convert.py +339 -0
  75. docline-0.1.0/src/docline/readers/openapi/detect.py +124 -0
  76. docline-0.1.0/src/docline/readers/openapi/errors.py +29 -0
  77. docline-0.1.0/src/docline/readers/openapi/loader.py +176 -0
  78. docline-0.1.0/src/docline/readers/openapi/reader.py +313 -0
  79. docline-0.1.0/src/docline/readers/openapi/render.py +418 -0
  80. docline-0.1.0/src/docline/readers/openapi/resolve.py +192 -0
  81. docline-0.1.0/src/docline/readers/pdf.py +940 -0
  82. docline-0.1.0/src/docline/readers/pdf_splitter.py +139 -0
  83. docline-0.1.0/src/docline/readers/picture_sink.py +113 -0
  84. docline-0.1.0/src/docline/readers/text.py +47 -0
  85. docline-0.1.0/src/docline/readers/transcripts.py +215 -0
  86. docline-0.1.0/src/docline/router.py +46 -0
  87. docline-0.1.0/src/docline/runtime/__init__.py +30 -0
  88. docline-0.1.0/src/docline/runtime/ocr_budget.py +168 -0
  89. docline-0.1.0/src/docline/runtime/resource_probe.py +369 -0
  90. docline-0.1.0/src/docline/schema/__init__.py +0 -0
  91. docline-0.1.0/src/docline/schema/export.py +43 -0
  92. docline-0.1.0/src/docline/schema/library.py +200 -0
  93. docline-0.1.0/src/docline/schema/models.py +71 -0
  94. docline-0.1.0/src/docline/types.py +36 -0
  95. docline-0.1.0/src/docline.egg-info/PKG-INFO +277 -0
  96. docline-0.1.0/src/docline.egg-info/SOURCES.txt +117 -0
  97. docline-0.1.0/src/docline.egg-info/dependency_links.txt +1 -0
  98. docline-0.1.0/src/docline.egg-info/entry_points.txt +2 -0
  99. docline-0.1.0/src/docline.egg-info/requires.txt +14 -0
  100. docline-0.1.0/src/docline.egg-info/top_level.txt +1 -0
  101. docline-0.1.0/tests/test_backlog_persistence_contract.py +169 -0
  102. docline-0.1.0/tests/test_ci_workflow_lint.py +95 -0
  103. docline-0.1.0/tests/test_cli_fetch.py +91 -0
  104. docline-0.1.0/tests/test_cli_ingest.py +312 -0
  105. docline-0.1.0/tests/test_cli_process.py +67 -0
  106. docline-0.1.0/tests/test_cli_verbosity.py +245 -0
  107. docline-0.1.0/tests/test_compare_merge_gap.py +191 -0
  108. docline-0.1.0/tests/test_execute_fetch.py +170 -0
  109. docline-0.1.0/tests/test_execute_process_progress.py +165 -0
  110. docline-0.1.0/tests/test_graphtor_ingestion_contract.py +156 -0
  111. docline-0.1.0/tests/test_graphtor_real_binary_integration.py +94 -0
  112. docline-0.1.0/tests/test_markitdown_dependency.py +45 -0
  113. docline-0.1.0/tests/test_ocr_memory_calibration.py +280 -0
  114. docline-0.1.0/tests/test_pa3_script_flags.py +257 -0
  115. docline-0.1.0/tests/test_pdf_mode_flag.py +83 -0
  116. docline-0.1.0/tests/test_posixify_path.py +90 -0
  117. docline-0.1.0/tests/test_progress.py +324 -0
  118. docline-0.1.0/tests/test_router.py +84 -0
  119. docline-0.1.0/tests/test_seam_lint.py +126 -0
docline-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Derek Williams
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.
docline-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,277 @@
1
+ Metadata-Version: 2.4
2
+ Name: docline
3
+ Version: 0.1.0
4
+ Summary: Document ingestion and normalization pipeline
5
+ Author: SoftwareSalt
6
+ Project-URL: Homepage, https://github.com/softwaresalt/docline
7
+ Project-URL: Repository, https://github.com/softwaresalt/docline
8
+ Requires-Python: >=3.12
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: beautifulsoup4<5,>=4.12
12
+ Requires-Dist: defusedxml<1,>=0.7
13
+ Requires-Dist: markdown-it-py<5,>=4
14
+ Requires-Dist: markitdown[pdf]<0.2,>=0.1.6
15
+ Requires-Dist: psutil<8,>=5.9
16
+ Requires-Dist: pydantic<3,>=2
17
+ Requires-Dist: pypdf<6,>=4
18
+ Requires-Dist: PyYAML<7,>=6
19
+ Provides-Extra: pdf
20
+ Requires-Dist: docling<3,>=2; extra == "pdf"
21
+ Provides-Extra: mistral
22
+ Requires-Dist: httpx<1,>=0.27; extra == "mistral"
23
+ Dynamic: license-file
24
+
25
+ # docline
26
+
27
+ A document to markdown ingestion and normalization pipeline CLI tool and MCP server.
28
+
29
+ ## Quick start: ingest a local docs repo
30
+
31
+ The fastest way to convert a cloned Microsoft Learn (or any DocFx-style)
32
+ docs repository into graphtor-ready Markdown is the `ingest local-dir`
33
+ command:
34
+
35
+ ```powershell
36
+ # Clone a docs repo (one-time)
37
+ git clone https://github.com/MicrosoftDocs/powerbi-docs.git E:\Source\powerbi-docs
38
+
39
+ # Convert the whole tree in one command
40
+ docline ingest local-dir E:\Source\powerbi-docs\powerbi-docs --output .elt\output\powerbi
41
+ ```
42
+
43
+ Each output file carries graphtor-ready frontmatter (`chunk_strategy`,
44
+ `content_sha256`, `doc_type`, `title`, `source_path`, `source`,
45
+ `docline.source_frontmatter`, `docline.cross_doc_links`), preserves source
46
+ directory structure, and follows TOC.yml ingest order when present.
47
+
48
+ The CLI mirrors the equivalent `.elt/config/<name>.sources.yaml` manifest
49
+ form for parity:
50
+
51
+ ```yaml
52
+ # .elt/config/powerbi.sources.yaml
53
+ sources:
54
+ - id: powerbi
55
+ type: local
56
+ path: E:\Source\powerbi-docs\powerbi-docs
57
+ include: ["**/*.md", "**/TOC.yml"]
58
+ ```
59
+
60
+ Both surfaces produce identical staging + processing output because they
61
+ share the same `execute_source_configs` and `execute_process` code paths.
62
+
63
+ Other Microsoft Learn docs sources that work out of the box:
64
+
65
+ | Content | Repo | Subpath |
66
+ |---|---|---|
67
+ | Power BI | `MicrosoftDocs/powerbi-docs` | `powerbi-docs/` |
68
+ | Microsoft Fabric | `MicrosoftDocs/fabric-docs` | repo root |
69
+ | DAX language reference | `MicrosoftDocs/query-docs` | `query-languages/dax/` |
70
+ | Power Query M language reference | `MicrosoftDocs/query-docs` | `query-languages/m/` |
71
+ | Analysis Services | `MicrosoftDocs/bi-shared-docs` | `docs/analysis-services/` |
72
+
73
+ Useful flags:
74
+
75
+ * `--include PATTERN` (repeatable) — glob to include, default `**/*.md` + `**/TOC.yml`
76
+ * `--exclude PATTERN` (repeatable) — glob to exclude
77
+ * `--staging-dir PATH` — keep staging artifacts under a known path (default: tempdir, removed after run)
78
+ * `--keep-staging` — retain staging directory for debugging
79
+ * `--allow-heading-disorder` — bypass strict H1→H2→H3 validation (for legacy authoring)
80
+
81
+ ## Console output and progress
82
+
83
+ `docline fetch` and `docline process` print a single JSON result line to
84
+ **stdout** at the end of a run. Live progress is written separately to
85
+ **stderr**, so the JSON result contract is unchanged in every mode and scripts
86
+ that parse (or pipe) stdout are never affected.
87
+
88
+ Control the stderr progress with a mutually-exclusive verbosity pair on both
89
+ commands (default is normal):
90
+
91
+ * `-q` / `--quiet` — no progress output (the JSON result still prints).
92
+ * *(default)* — a concise, throttled percentage/count line, updated in place on
93
+ a TTY and written as plain newline-terminated lines when redirected.
94
+ * `-v` / `--verbose` — one line per page (fetch) or file (process), including
95
+ the URL or path, followed by a **final completion line** (the last event
96
+ repeated as a permanent line). `docline fetch` additionally emits a count-only
97
+ line with the authoritative number of pages actually staged. These trailing
98
+ lines are expected — they mark completion, not duplicated work.
99
+
100
+ On an interactive terminal, in-place progress updates are coordinated with log
101
+ output: any warning logged mid-run clears the active progress line first and is
102
+ printed on its own line, and the progress line redraws on the next update (so
103
+ warnings never corrupt the live percentage). Piped/redirected output uses plain
104
+ newline-terminated lines and is unaffected.
105
+
106
+ Progress metrics:
107
+
108
+ * **`docline process`** reports `files_done / total`, where `total` is the
109
+ global file count summed across every completed staging job, so the bar stays
110
+ monotonic across multi-job runs.
111
+ * **`docline fetch --execute`** reports *budget-consumed* pages against the
112
+ crawl's `max_pages` budget (web-crawl sources only). Because the budget is a
113
+ ceiling, the crawl may finish early — so completion reports the authoritative
114
+ count of pages actually staged rather than a forced 100%.
115
+
116
+ ## Documentation
117
+
118
+ * [docline → graphtor-docs ingestion contract](docs/design-docs/graphtor-docs-ingestion-contract.md) —
119
+ the stable v1 contract surface that downstream consumers ingest.
120
+ * [BaseFrontmatter JSON Schema export workflow](docs/design-docs/schema-export-workflow.md) —
121
+ how to regenerate the exported JSON Schema and how `graphtor-docs` consumes it.
122
+ * [Document ingestion and validation pipeline design](docs/design-docs/DocumentIngestion&ValidationPipelineDesign.md)
123
+
124
+ ## OpenAPI / Swagger source type
125
+
126
+ `docline process` ingests OpenAPI 3.x specifications as a first-class source
127
+ type. A staged `.json`, `.yaml`, or `.yml` file is recognized by content-sniff
128
+ (its root declares `openapi: 3.x` or `swagger: 2.0`), so plain config files such
129
+ as `docfx.json` are never misclassified.
130
+
131
+ Rather than flattening the spec through the PDF pipeline, docline traverses the
132
+ typed object model and renders deterministic Markdown:
133
+
134
+ * one document per **operation** at `operations/{operationId}.md`
135
+ (`doc_type: openapi_operation`), with `Parameters`, `Request body`,
136
+ `Responses`, and `Security` sections;
137
+ * one document per named **component schema** at `schemas/{name}.md`
138
+ (`doc_type: openapi_schema`), with a properties table.
139
+
140
+ Each `$ref` to a component schema is emitted as a relative Markdown link, so the
141
+ existing cross-doc link harvester records every `operation → schema` reference as
142
+ a typed graph edge under the `docline.cross_doc_links` frontmatter namespace.
143
+
144
+ ### v1 scope
145
+
146
+ The first release is intentionally narrow:
147
+
148
+ * OpenAPI **3.x** (Swagger 2.0 is detected but not yet rendered);
149
+ * a **single spec** (one file, or a directory for one service);
150
+ * **per-operation** granularity;
151
+ * **local** `#/components/*` `$ref` resolution only — external and split-file
152
+ refs are left unresolved (never fetched), because resolving them is a security
153
+ boundary deferred to a follow-up.
154
+
155
+ ### Usage
156
+
157
+ Stage a spec and run the compute-bound pass:
158
+
159
+ ```bash
160
+ docline ingest local-dir ./azure-rest-api-specs/specification/... \
161
+ --output ./out --include "**/*.json"
162
+ ```
163
+
164
+ The source directory is a positional argument. The MCP `process` tool produces
165
+ identical output; both surfaces share `execute_process`.
166
+
167
+ ## PDF processing modes
168
+
169
+ `docline process` supports two PDF processing modes via the `--pdf-mode`
170
+ flag.
171
+
172
+ ### `--pdf-mode auto` (default — recommended for technical PDFs)
173
+
174
+ Runs `docling` on every page. Best AST-aware quality (headings, tables,
175
+ code blocks, embedding-chunk-friendly section sizes) for technical
176
+ reference documents — Microsoft / AWS / Kubernetes / framework
177
+ documentation, vendor manuals, scientific papers with dense layout.
178
+
179
+ * Pros: highest structural fidelity; ideal for graph DBs, vector
180
+ embedding stores, and LLM context windows.
181
+ * Cons: ~15-30 sec per page wall-clock; a 3,400-page reference manual
182
+ takes ~4-9 hours.
183
+
184
+ ### `--pdf-mode triage` (opt-in — for prose-dominated corpora)
185
+
186
+ Runs a heuristic baseline ([`markitdown`](https://github.com/microsoft/markitdown)
187
+ or `pypdf`) across the whole PDF, scores each page for likely fidelity
188
+ loss, and only invokes `docling` on flagged page ranges. Designed for
189
+ documents that are mostly free-form prose (novels, articles, simple
190
+ documentation, chat exports).
191
+
192
+ * Pros: ~5-10× faster than `auto` on prose corpora.
193
+ * Cons: on technical reference PDFs, triage either over-fires (large
194
+ pages flagged, wall-clock approaches `auto`) or under-fires (table
195
+ pages flattened to broken text). See the
196
+ [2026-06-08 extraction strategy study](docs/decisions/2026-06-08-extraction-strategy-study.md)
197
+ for empirical results: docling wins 14/15 sampled cosmos ranges on
198
+ AST quality.
199
+
200
+ ### Choosing a mode
201
+
202
+ * **Technical reference PDFs** (vendor docs, framework docs, scientific
203
+ papers): use `--pdf-mode auto`.
204
+ * **Prose corpora** (novels, articles, transcripts): use `--pdf-mode triage`
205
+ to trade some structural fidelity for speed.
206
+ * **Source markdown available** (Microsoft Learn, AWS Docs, K8s,
207
+ React, Python, most OSS): a future feature (**026-F**) will add a
208
+ `--source-mode` flag that bypasses PDF extraction entirely; see the
209
+ [source-MD ingestion extension](docs/decisions/2026-06-08-source-md-ingestion-extension.md).
210
+
211
+ ### Choosing a PDF engine
212
+
213
+ `--pdf-engine` selects *which* layout extractor runs and is orthogonal to
214
+ `--pdf-mode` (which selects whole-document vs. triage). Four choices: `auto`
215
+ (default), `docling`, `mistral_ocr`, and `heuristic`.
216
+
217
+ | Engine | Strengths | Trade-offs | Requirements |
218
+ |---|---|---|---|
219
+ | `auto` (default) | Prefers `docling`; transparently degrades to `heuristic` on failure so one hostile PDF can't abort a batch | Never selects `mistral_ocr` | `docline[pdf]` for docling, else heuristic |
220
+ | `docling` | Best structural fidelity on headings and dense layout; deterministic; fully local/offline | ~360 pages/hour (031-S bench) | `docline[pdf]` extra (raises if missing) |
221
+ | `mistral_ocr` | Wins decisively on tables (mean +33.9% vs. docling, 8/4/2 wins); ~3,732 pages/hour (~10× docling) | Heading depth ~8.4% weaker than docling; per-page cloud cost (~$1 / 1,000 pages); network + credentials; non-deterministic | `docline[mistral]` + `AZURE_AI_FOUNDRY_KEY`/`_ENDPOINT` or `MISTRAL_API_KEY` |
222
+ | `heuristic` | Fastest; no model download; fully local | Flattens tables and complex layout to plain text | None (built in) |
223
+
224
+ **Fidelity vs. throughput — recommended engine by corpus class:**
225
+
226
+ | Corpus class | Throughput priority | Cost sensitivity | Recommended `--pdf-engine` |
227
+ |---|---|---|---|
228
+ | Technical reference (vendor/framework docs, manuals) | Low | Any | `docling` (or `auto`) |
229
+ | Table-heavy (financial reports, spec sheets, data appendices) | Low | Low | `mistral_ocr` |
230
+ | Table-heavy at scale | High | Tolerant of cloud cost | `mistral_ocr` |
231
+ | Scientific papers (dense layout + tables) | Low | Any | `docling`; `mistral_ocr` if tables dominate |
232
+ | Forms / invoices (scanned, OCR-dependent) | Any | Tolerant | `mistral_ocr` (OCR strength) — re-validate per corpus |
233
+ | Prose (novels, articles, transcripts) | High | Any | `heuristic` (or `--pdf-mode triage`) |
234
+ | Offline / air-gapped / deterministic required | Any | Any | `docling` or `heuristic` (never cloud) |
235
+
236
+ Evidence: the
237
+ [031-S Mistral OCR spike](docs/closure/031-S-mistral-ocr-spike.md)
238
+ (PROMOTE-AS-PEER — tables mean +33.9%, ~10× throughput, headings −8.4%) and
239
+ the [2026-06-08 extraction strategy study](docs/decisions/2026-06-08-extraction-strategy-study.md)
240
+ (docling wins 14/15 sampled technical-reference ranges on AST quality).
241
+ `mistral_ocr` is opt-in only and is never auto-selected. Azure Document
242
+ Intelligence was evaluated and removed in 031-S; see
243
+ [029-S](docs/closure/029-S-adi-spike.md) for the historical record.
244
+
245
+ ### Compute device (`DOCLINE_ACCELERATOR`)
246
+
247
+ The `docling` engine auto-detects an available accelerator (CUDA, MPS, or
248
+ XPU) and falls back to CPU otherwise, so GPU hosts are used without
249
+ configuration. Set `DOCLINE_ACCELERATOR` to pin the device explicitly:
250
+
251
+ | Value | Effect |
252
+ |---|---|
253
+ | unset or `auto` | docling's default auto-detection (unchanged behavior) |
254
+ | `cpu` | Force CPU — the escape hatch when an auto-detected GPU is unreliable |
255
+ | `cuda` / `mps` / `xpu` | Pin the named accelerator |
256
+
257
+ The variable applies to both the single-file CLI path and the batched
258
+ docling worker. An unrecognized value fails fast with a configuration error.
259
+
260
+ ### Calibration & QA
261
+
262
+ `docline process --pdf-mode triage --triage-report-only` emits a
263
+ per-page TSV with both fidelity-signal scores and AST-aware quality
264
+ metrics (heading count, section count, table cell count, structural
265
+ density per 1k chars, median section size). See
266
+ [`docs/compound/2026-06-08-ast-fidelity-metrics.md`](docs/compound/2026-06-08-ast-fidelity-metrics.md)
267
+ for the decision rule on interpreting these metrics.
268
+
269
+ ### Triage output retention
270
+
271
+ When `--pdf-mode triage` is used, per-page baseline PDFs
272
+ (`baseline-NNNN.pdf`) and coalesced splice PDFs / outputs
273
+ (`splice-AAAA-BBBB.{pdf,md}`) are preserved by default under
274
+ `{output_dir}/splices/` for offline calibration and diagnostic
275
+ inspection. Plan accordingly for disk usage: a 3,400-page PDF produces
276
+ ~3,400 per-page PDFs totaling ~150 MB.
277
+
@@ -0,0 +1,253 @@
1
+ # docline
2
+
3
+ A document to markdown ingestion and normalization pipeline CLI tool and MCP server.
4
+
5
+ ## Quick start: ingest a local docs repo
6
+
7
+ The fastest way to convert a cloned Microsoft Learn (or any DocFx-style)
8
+ docs repository into graphtor-ready Markdown is the `ingest local-dir`
9
+ command:
10
+
11
+ ```powershell
12
+ # Clone a docs repo (one-time)
13
+ git clone https://github.com/MicrosoftDocs/powerbi-docs.git E:\Source\powerbi-docs
14
+
15
+ # Convert the whole tree in one command
16
+ docline ingest local-dir E:\Source\powerbi-docs\powerbi-docs --output .elt\output\powerbi
17
+ ```
18
+
19
+ Each output file carries graphtor-ready frontmatter (`chunk_strategy`,
20
+ `content_sha256`, `doc_type`, `title`, `source_path`, `source`,
21
+ `docline.source_frontmatter`, `docline.cross_doc_links`), preserves source
22
+ directory structure, and follows TOC.yml ingest order when present.
23
+
24
+ The CLI mirrors the equivalent `.elt/config/<name>.sources.yaml` manifest
25
+ form for parity:
26
+
27
+ ```yaml
28
+ # .elt/config/powerbi.sources.yaml
29
+ sources:
30
+ - id: powerbi
31
+ type: local
32
+ path: E:\Source\powerbi-docs\powerbi-docs
33
+ include: ["**/*.md", "**/TOC.yml"]
34
+ ```
35
+
36
+ Both surfaces produce identical staging + processing output because they
37
+ share the same `execute_source_configs` and `execute_process` code paths.
38
+
39
+ Other Microsoft Learn docs sources that work out of the box:
40
+
41
+ | Content | Repo | Subpath |
42
+ |---|---|---|
43
+ | Power BI | `MicrosoftDocs/powerbi-docs` | `powerbi-docs/` |
44
+ | Microsoft Fabric | `MicrosoftDocs/fabric-docs` | repo root |
45
+ | DAX language reference | `MicrosoftDocs/query-docs` | `query-languages/dax/` |
46
+ | Power Query M language reference | `MicrosoftDocs/query-docs` | `query-languages/m/` |
47
+ | Analysis Services | `MicrosoftDocs/bi-shared-docs` | `docs/analysis-services/` |
48
+
49
+ Useful flags:
50
+
51
+ * `--include PATTERN` (repeatable) — glob to include, default `**/*.md` + `**/TOC.yml`
52
+ * `--exclude PATTERN` (repeatable) — glob to exclude
53
+ * `--staging-dir PATH` — keep staging artifacts under a known path (default: tempdir, removed after run)
54
+ * `--keep-staging` — retain staging directory for debugging
55
+ * `--allow-heading-disorder` — bypass strict H1→H2→H3 validation (for legacy authoring)
56
+
57
+ ## Console output and progress
58
+
59
+ `docline fetch` and `docline process` print a single JSON result line to
60
+ **stdout** at the end of a run. Live progress is written separately to
61
+ **stderr**, so the JSON result contract is unchanged in every mode and scripts
62
+ that parse (or pipe) stdout are never affected.
63
+
64
+ Control the stderr progress with a mutually-exclusive verbosity pair on both
65
+ commands (default is normal):
66
+
67
+ * `-q` / `--quiet` — no progress output (the JSON result still prints).
68
+ * *(default)* — a concise, throttled percentage/count line, updated in place on
69
+ a TTY and written as plain newline-terminated lines when redirected.
70
+ * `-v` / `--verbose` — one line per page (fetch) or file (process), including
71
+ the URL or path, followed by a **final completion line** (the last event
72
+ repeated as a permanent line). `docline fetch` additionally emits a count-only
73
+ line with the authoritative number of pages actually staged. These trailing
74
+ lines are expected — they mark completion, not duplicated work.
75
+
76
+ On an interactive terminal, in-place progress updates are coordinated with log
77
+ output: any warning logged mid-run clears the active progress line first and is
78
+ printed on its own line, and the progress line redraws on the next update (so
79
+ warnings never corrupt the live percentage). Piped/redirected output uses plain
80
+ newline-terminated lines and is unaffected.
81
+
82
+ Progress metrics:
83
+
84
+ * **`docline process`** reports `files_done / total`, where `total` is the
85
+ global file count summed across every completed staging job, so the bar stays
86
+ monotonic across multi-job runs.
87
+ * **`docline fetch --execute`** reports *budget-consumed* pages against the
88
+ crawl's `max_pages` budget (web-crawl sources only). Because the budget is a
89
+ ceiling, the crawl may finish early — so completion reports the authoritative
90
+ count of pages actually staged rather than a forced 100%.
91
+
92
+ ## Documentation
93
+
94
+ * [docline → graphtor-docs ingestion contract](docs/design-docs/graphtor-docs-ingestion-contract.md) —
95
+ the stable v1 contract surface that downstream consumers ingest.
96
+ * [BaseFrontmatter JSON Schema export workflow](docs/design-docs/schema-export-workflow.md) —
97
+ how to regenerate the exported JSON Schema and how `graphtor-docs` consumes it.
98
+ * [Document ingestion and validation pipeline design](docs/design-docs/DocumentIngestion&ValidationPipelineDesign.md)
99
+
100
+ ## OpenAPI / Swagger source type
101
+
102
+ `docline process` ingests OpenAPI 3.x specifications as a first-class source
103
+ type. A staged `.json`, `.yaml`, or `.yml` file is recognized by content-sniff
104
+ (its root declares `openapi: 3.x` or `swagger: 2.0`), so plain config files such
105
+ as `docfx.json` are never misclassified.
106
+
107
+ Rather than flattening the spec through the PDF pipeline, docline traverses the
108
+ typed object model and renders deterministic Markdown:
109
+
110
+ * one document per **operation** at `operations/{operationId}.md`
111
+ (`doc_type: openapi_operation`), with `Parameters`, `Request body`,
112
+ `Responses`, and `Security` sections;
113
+ * one document per named **component schema** at `schemas/{name}.md`
114
+ (`doc_type: openapi_schema`), with a properties table.
115
+
116
+ Each `$ref` to a component schema is emitted as a relative Markdown link, so the
117
+ existing cross-doc link harvester records every `operation → schema` reference as
118
+ a typed graph edge under the `docline.cross_doc_links` frontmatter namespace.
119
+
120
+ ### v1 scope
121
+
122
+ The first release is intentionally narrow:
123
+
124
+ * OpenAPI **3.x** (Swagger 2.0 is detected but not yet rendered);
125
+ * a **single spec** (one file, or a directory for one service);
126
+ * **per-operation** granularity;
127
+ * **local** `#/components/*` `$ref` resolution only — external and split-file
128
+ refs are left unresolved (never fetched), because resolving them is a security
129
+ boundary deferred to a follow-up.
130
+
131
+ ### Usage
132
+
133
+ Stage a spec and run the compute-bound pass:
134
+
135
+ ```bash
136
+ docline ingest local-dir ./azure-rest-api-specs/specification/... \
137
+ --output ./out --include "**/*.json"
138
+ ```
139
+
140
+ The source directory is a positional argument. The MCP `process` tool produces
141
+ identical output; both surfaces share `execute_process`.
142
+
143
+ ## PDF processing modes
144
+
145
+ `docline process` supports two PDF processing modes via the `--pdf-mode`
146
+ flag.
147
+
148
+ ### `--pdf-mode auto` (default — recommended for technical PDFs)
149
+
150
+ Runs `docling` on every page. Best AST-aware quality (headings, tables,
151
+ code blocks, embedding-chunk-friendly section sizes) for technical
152
+ reference documents — Microsoft / AWS / Kubernetes / framework
153
+ documentation, vendor manuals, scientific papers with dense layout.
154
+
155
+ * Pros: highest structural fidelity; ideal for graph DBs, vector
156
+ embedding stores, and LLM context windows.
157
+ * Cons: ~15-30 sec per page wall-clock; a 3,400-page reference manual
158
+ takes ~4-9 hours.
159
+
160
+ ### `--pdf-mode triage` (opt-in — for prose-dominated corpora)
161
+
162
+ Runs a heuristic baseline ([`markitdown`](https://github.com/microsoft/markitdown)
163
+ or `pypdf`) across the whole PDF, scores each page for likely fidelity
164
+ loss, and only invokes `docling` on flagged page ranges. Designed for
165
+ documents that are mostly free-form prose (novels, articles, simple
166
+ documentation, chat exports).
167
+
168
+ * Pros: ~5-10× faster than `auto` on prose corpora.
169
+ * Cons: on technical reference PDFs, triage either over-fires (large
170
+ pages flagged, wall-clock approaches `auto`) or under-fires (table
171
+ pages flattened to broken text). See the
172
+ [2026-06-08 extraction strategy study](docs/decisions/2026-06-08-extraction-strategy-study.md)
173
+ for empirical results: docling wins 14/15 sampled cosmos ranges on
174
+ AST quality.
175
+
176
+ ### Choosing a mode
177
+
178
+ * **Technical reference PDFs** (vendor docs, framework docs, scientific
179
+ papers): use `--pdf-mode auto`.
180
+ * **Prose corpora** (novels, articles, transcripts): use `--pdf-mode triage`
181
+ to trade some structural fidelity for speed.
182
+ * **Source markdown available** (Microsoft Learn, AWS Docs, K8s,
183
+ React, Python, most OSS): a future feature (**026-F**) will add a
184
+ `--source-mode` flag that bypasses PDF extraction entirely; see the
185
+ [source-MD ingestion extension](docs/decisions/2026-06-08-source-md-ingestion-extension.md).
186
+
187
+ ### Choosing a PDF engine
188
+
189
+ `--pdf-engine` selects *which* layout extractor runs and is orthogonal to
190
+ `--pdf-mode` (which selects whole-document vs. triage). Four choices: `auto`
191
+ (default), `docling`, `mistral_ocr`, and `heuristic`.
192
+
193
+ | Engine | Strengths | Trade-offs | Requirements |
194
+ |---|---|---|---|
195
+ | `auto` (default) | Prefers `docling`; transparently degrades to `heuristic` on failure so one hostile PDF can't abort a batch | Never selects `mistral_ocr` | `docline[pdf]` for docling, else heuristic |
196
+ | `docling` | Best structural fidelity on headings and dense layout; deterministic; fully local/offline | ~360 pages/hour (031-S bench) | `docline[pdf]` extra (raises if missing) |
197
+ | `mistral_ocr` | Wins decisively on tables (mean +33.9% vs. docling, 8/4/2 wins); ~3,732 pages/hour (~10× docling) | Heading depth ~8.4% weaker than docling; per-page cloud cost (~$1 / 1,000 pages); network + credentials; non-deterministic | `docline[mistral]` + `AZURE_AI_FOUNDRY_KEY`/`_ENDPOINT` or `MISTRAL_API_KEY` |
198
+ | `heuristic` | Fastest; no model download; fully local | Flattens tables and complex layout to plain text | None (built in) |
199
+
200
+ **Fidelity vs. throughput — recommended engine by corpus class:**
201
+
202
+ | Corpus class | Throughput priority | Cost sensitivity | Recommended `--pdf-engine` |
203
+ |---|---|---|---|
204
+ | Technical reference (vendor/framework docs, manuals) | Low | Any | `docling` (or `auto`) |
205
+ | Table-heavy (financial reports, spec sheets, data appendices) | Low | Low | `mistral_ocr` |
206
+ | Table-heavy at scale | High | Tolerant of cloud cost | `mistral_ocr` |
207
+ | Scientific papers (dense layout + tables) | Low | Any | `docling`; `mistral_ocr` if tables dominate |
208
+ | Forms / invoices (scanned, OCR-dependent) | Any | Tolerant | `mistral_ocr` (OCR strength) — re-validate per corpus |
209
+ | Prose (novels, articles, transcripts) | High | Any | `heuristic` (or `--pdf-mode triage`) |
210
+ | Offline / air-gapped / deterministic required | Any | Any | `docling` or `heuristic` (never cloud) |
211
+
212
+ Evidence: the
213
+ [031-S Mistral OCR spike](docs/closure/031-S-mistral-ocr-spike.md)
214
+ (PROMOTE-AS-PEER — tables mean +33.9%, ~10× throughput, headings −8.4%) and
215
+ the [2026-06-08 extraction strategy study](docs/decisions/2026-06-08-extraction-strategy-study.md)
216
+ (docling wins 14/15 sampled technical-reference ranges on AST quality).
217
+ `mistral_ocr` is opt-in only and is never auto-selected. Azure Document
218
+ Intelligence was evaluated and removed in 031-S; see
219
+ [029-S](docs/closure/029-S-adi-spike.md) for the historical record.
220
+
221
+ ### Compute device (`DOCLINE_ACCELERATOR`)
222
+
223
+ The `docling` engine auto-detects an available accelerator (CUDA, MPS, or
224
+ XPU) and falls back to CPU otherwise, so GPU hosts are used without
225
+ configuration. Set `DOCLINE_ACCELERATOR` to pin the device explicitly:
226
+
227
+ | Value | Effect |
228
+ |---|---|
229
+ | unset or `auto` | docling's default auto-detection (unchanged behavior) |
230
+ | `cpu` | Force CPU — the escape hatch when an auto-detected GPU is unreliable |
231
+ | `cuda` / `mps` / `xpu` | Pin the named accelerator |
232
+
233
+ The variable applies to both the single-file CLI path and the batched
234
+ docling worker. An unrecognized value fails fast with a configuration error.
235
+
236
+ ### Calibration & QA
237
+
238
+ `docline process --pdf-mode triage --triage-report-only` emits a
239
+ per-page TSV with both fidelity-signal scores and AST-aware quality
240
+ metrics (heading count, section count, table cell count, structural
241
+ density per 1k chars, median section size). See
242
+ [`docs/compound/2026-06-08-ast-fidelity-metrics.md`](docs/compound/2026-06-08-ast-fidelity-metrics.md)
243
+ for the decision rule on interpreting these metrics.
244
+
245
+ ### Triage output retention
246
+
247
+ When `--pdf-mode triage` is used, per-page baseline PDFs
248
+ (`baseline-NNNN.pdf`) and coalesced splice PDFs / outputs
249
+ (`splice-AAAA-BBBB.{pdf,md}`) are preserved by default under
250
+ `{output_dir}/splices/` for offline calibration and diagnostic
251
+ inspection. Plan accordingly for disk usage: a 3,400-page PDF produces
252
+ ~3,400 per-page PDFs totaling ~150 MB.
253
+
@@ -0,0 +1,57 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "docline"
7
+ version = "0.1.0"
8
+ description = "Document ingestion and normalization pipeline"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ authors = [
12
+ { name = "SoftwareSalt" },
13
+ ]
14
+ urls = { Homepage = "https://github.com/softwaresalt/docline", Repository = "https://github.com/softwaresalt/docline" }
15
+ dependencies = [
16
+ "beautifulsoup4>=4.12,<5",
17
+ "defusedxml>=0.7,<1",
18
+ "markdown-it-py>=4,<5",
19
+ "markitdown[pdf]>=0.1.6,<0.2",
20
+ "psutil>=5.9,<8",
21
+ "pydantic>=2,<3",
22
+ "pypdf>=4,<6",
23
+ "PyYAML>=6,<7",
24
+ ]
25
+
26
+ [dependency-groups]
27
+ dev = [
28
+ "ruff>=0.15,<1",
29
+ "pyright>=1.1.400,<2",
30
+ "pytest>=9,<10",
31
+ "build>=1.4,<2",
32
+ ]
33
+
34
+ [project.scripts]
35
+ docline = "docline.cli:main"
36
+
37
+ [project.optional-dependencies]
38
+ pdf = ["docling>=2,<3"]
39
+ mistral = ["httpx>=0.27,<1"]
40
+
41
+ [tool.setuptools.packages.find]
42
+ where = ["src"]
43
+
44
+ [tool.pytest.ini_options]
45
+ testpaths = ["tests"]
46
+ addopts = "-v"
47
+ markers = [
48
+ "integration: marks end-to-end integration coverage",
49
+ "graphtor_integration: opt-in integration test that round-trips docline output through a graphtor-docs ingestion simulator",
50
+ ]
51
+
52
+ [tool.ruff]
53
+ line-length = 100
54
+ target-version = "py312"
55
+
56
+ [tool.ruff.lint]
57
+ select = ["E", "F", "I", "UP"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ """docline — document ingestion and normalization pipeline."""
@@ -0,0 +1,16 @@
1
+ """Package entrypoint for ``python -m docline``."""
2
+
3
+ from docline.cli import main as cli_main
4
+
5
+
6
+ def main() -> int:
7
+ """Run the package entrypoint.
8
+
9
+ Returns:
10
+ Process exit code.
11
+ """
12
+ return cli_main()
13
+
14
+
15
+ if __name__ == "__main__":
16
+ raise SystemExit(main())
@@ -0,0 +1,9 @@
1
+ """Internal subprocess CLIs invoked by the docline batch processor.
2
+
3
+ These tools are not part of the public docline CLI surface; they exist
4
+ so the batch processor can run docling (which loads heavy PyTorch
5
+ models and can OOM with hard-to-catch C-level errors) in an isolated
6
+ child process. If a child crashes, the OS reaps it cleanly and the
7
+ parent records a non-zero exit code, downgrading that chunk to the
8
+ heuristic engine.
9
+ """