error-investigator 1.0.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 (63) hide show
  1. error_investigator-1.0.0/LICENSE +21 -0
  2. error_investigator-1.0.0/PKG-INFO +681 -0
  3. error_investigator-1.0.0/README.md +654 -0
  4. error_investigator-1.0.0/pyproject.toml +51 -0
  5. error_investigator-1.0.0/setup.cfg +4 -0
  6. error_investigator-1.0.0/src/error_investigator.egg-info/PKG-INFO +681 -0
  7. error_investigator-1.0.0/src/error_investigator.egg-info/SOURCES.txt +61 -0
  8. error_investigator-1.0.0/src/error_investigator.egg-info/dependency_links.txt +1 -0
  9. error_investigator-1.0.0/src/error_investigator.egg-info/entry_points.txt +2 -0
  10. error_investigator-1.0.0/src/error_investigator.egg-info/requires.txt +6 -0
  11. error_investigator-1.0.0/src/error_investigator.egg-info/top_level.txt +1 -0
  12. error_investigator-1.0.0/src/investigator/__init__.py +17 -0
  13. error_investigator-1.0.0/src/investigator/cli.py +59 -0
  14. error_investigator-1.0.0/src/investigator/cli_format.py +123 -0
  15. error_investigator-1.0.0/src/investigator/error_parser.py +81 -0
  16. error_investigator-1.0.0/src/investigator/fastapi.py +258 -0
  17. error_investigator-1.0.0/src/investigator/investigation.py +17 -0
  18. error_investigator-1.0.0/src/investigator/investigation_result.py +80 -0
  19. error_investigator-1.0.0/src/investigator/investigator.py +39 -0
  20. error_investigator-1.0.0/src/investigator/io_utils.py +27 -0
  21. error_investigator-1.0.0/src/investigator/knowledge/__init__.py +0 -0
  22. error_investigator-1.0.0/src/investigator/knowledge/httpx_connect_error.yaml +27 -0
  23. error_investigator-1.0.0/src/investigator/knowledge/json_decode_error.yaml +29 -0
  24. error_investigator-1.0.0/src/investigator/knowledge/pydantic_validation_error.yaml +28 -0
  25. error_investigator-1.0.0/src/investigator/knowledge/python_attribute_error.yaml +28 -0
  26. error_investigator-1.0.0/src/investigator/knowledge/python_connection_error.yaml +27 -0
  27. error_investigator-1.0.0/src/investigator/knowledge/python_file_not_found_error.yaml +28 -0
  28. error_investigator-1.0.0/src/investigator/knowledge/python_index_error.yaml +28 -0
  29. error_investigator-1.0.0/src/investigator/knowledge/python_key_error.yaml +31 -0
  30. error_investigator-1.0.0/src/investigator/knowledge/python_module_not_found_error.yaml +28 -0
  31. error_investigator-1.0.0/src/investigator/knowledge/python_permission_error.yaml +28 -0
  32. error_investigator-1.0.0/src/investigator/knowledge/python_timeout_error.yaml +27 -0
  33. error_investigator-1.0.0/src/investigator/knowledge/python_type_error.yaml +28 -0
  34. error_investigator-1.0.0/src/investigator/knowledge/python_value_error.yaml +30 -0
  35. error_investigator-1.0.0/src/investigator/knowledge/requests_connection_error.yaml +29 -0
  36. error_investigator-1.0.0/src/investigator/knowledge/requests_timeout_error.yaml +28 -0
  37. error_investigator-1.0.0/src/investigator/knowledge/sqlalchemy_data_error.yaml +31 -0
  38. error_investigator-1.0.0/src/investigator/knowledge/sqlalchemy_integrity_error.yaml +29 -0
  39. error_investigator-1.0.0/src/investigator/knowledge/sqlalchemy_multiple_results_found.yaml +29 -0
  40. error_investigator-1.0.0/src/investigator/knowledge/sqlalchemy_no_result_found.yaml +29 -0
  41. error_investigator-1.0.0/src/investigator/knowledge/sqlalchemy_operational_error.yaml +31 -0
  42. error_investigator-1.0.0/src/investigator/knowledge_loader.py +147 -0
  43. error_investigator-1.0.0/src/investigator/knowledge_lookup.py +107 -0
  44. error_investigator-1.0.0/src/investigator/sdk.py +82 -0
  45. error_investigator-1.0.0/tests/test_cli.py +385 -0
  46. error_investigator-1.0.0/tests/test_cli_format.py +151 -0
  47. error_investigator-1.0.0/tests/test_edge_cases.py +295 -0
  48. error_investigator-1.0.0/tests/test_edge_cases_adversarial.py +407 -0
  49. error_investigator-1.0.0/tests/test_end_to_end_known.py +138 -0
  50. error_investigator-1.0.0/tests/test_end_to_end_unknown.py +115 -0
  51. error_investigator-1.0.0/tests/test_error_parser.py +77 -0
  52. error_investigator-1.0.0/tests/test_error_parser_contract.py +147 -0
  53. error_investigator-1.0.0/tests/test_fastapi.py +607 -0
  54. error_investigator-1.0.0/tests/test_investigation.py +67 -0
  55. error_investigator-1.0.0/tests/test_investigation_result.py +146 -0
  56. error_investigator-1.0.0/tests/test_investigator.py +143 -0
  57. error_investigator-1.0.0/tests/test_io_utils.py +173 -0
  58. error_investigator-1.0.0/tests/test_kb_collisions.py +151 -0
  59. error_investigator-1.0.0/tests/test_kb_expansion.py +81 -0
  60. error_investigator-1.0.0/tests/test_knowledge_loader.py +238 -0
  61. error_investigator-1.0.0/tests/test_knowledge_lookup.py +152 -0
  62. error_investigator-1.0.0/tests/test_public_api.py +45 -0
  63. error_investigator-1.0.0/tests/test_sdk.py +297 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Head2On
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.
@@ -0,0 +1,681 @@
1
+ Metadata-Version: 2.4
2
+ Name: error-investigator
3
+ Version: 1.0.0
4
+ Summary: A CLI developer tool for investigating API errors
5
+ Author-email: ISHAN KUMAR <ishankumar063@gmail.com>
6
+ License: MIT
7
+ Keywords: cli,developer-tools,error-analysis,debugging,python
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Debuggers
17
+ Classifier: Topic :: Software Development :: Quality Assurance
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: typer
22
+ Requires-Dist: pyyaml
23
+ Provides-Extra: fastapi
24
+ Requires-Dist: fastapi>=0.100.0; extra == "fastapi"
25
+ Requires-Dist: httpx>=0.24.0; extra == "fastapi"
26
+ Dynamic: license-file
27
+
28
+ # Error Investigator
29
+
30
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
31
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
32
+ [![Tests: 296 passed](https://img.shields.io/badge/tests-296%20passed-brightgreen.svg)]()
33
+ [![Coverage: 100%](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)]()
34
+
35
+ A fast, deterministic CLI developer tool that investigates Python and API errors by parsing tracebacks, matching them against a curated knowledge base, and returning structured diagnoses with root causes and actionable fixes.
36
+
37
+ **Status:** Deterministic offline engine — zero external network requests in the shipped tool.
38
+
39
+ ---
40
+
41
+ ## Key Features
42
+
43
+ - **Deterministic & Instant:** Powered by an offline, indexed knowledge base. Zero latency, zero external network requests, completely reproducible.
44
+ - **Honest by Design:** If an error is unknown, it returns `Status: UNKNOWN`. The tool **never fabricates** a diagnosis.
45
+ - **Dual Interaction Modes:**
46
+ - **Interactive TTY:** Paste multiline tracebacks directly with early directory validation and simple `DONE` submission.
47
+ - **Piped Mode:** Pipe stderr or log files directly (`python app.py 2>&1 | investigate`) with silent, prompt-free execution.
48
+ - **Extensible Knowledge Base:** Bring custom error catalogs via `--kb-dir <path>` with automatic schema validation and collision detection.
49
+ - **100% Tested:** Comprehensive test suite with 100% statement coverage across all modules.
50
+
51
+ ---
52
+
53
+ ## How It Works
54
+
55
+ ```text
56
+ Raw Error String / Traceback
57
+ │
58
+ ▼
59
+ ┌──────────────────────────────────────────────┐
60
+ │ parse_error() │
61
+ │ • Extracts error_type, message, file, line │
62
+ └──────────────────────┬───────────────────────┘
63
+ ▼
64
+ ParsedError
65
+ │
66
+ ▼
67
+ ┌──────────────────────────────────────────────┐
68
+ │ lookup() │
69
+ │ • Matches primary error_type or aliases │
70
+ │ • Validates custom KB directory upfront │
71
+ └──────────────────────┬───────────────────────┘
72
+ ▼
73
+ KnowledgeEntry | None
74
+ │
75
+ ┌─────────────┴─────────────┐
76
+ ▼ ▼
77
+ [Match Found] [No Match]
78
+ Status: KNOWN Status: UNKNOWN
79
+ Structured root causes Honest fallback
80
+ & proven fixes (never fabricated)
81
+ │ │
82
+ └─────────────┬─────────────┘
83
+ ▼
84
+ format_result()
85
+ │
86
+ ▼
87
+ Terminal Output
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Installation
93
+
94
+ ### Prerequisites
95
+
96
+ - Python 3.10 or higher
97
+ - Git
98
+
99
+ ### Install with pip (Recommended)
100
+
101
+ ```bash
102
+ pip install error-investigator
103
+ ```
104
+
105
+ With optional FastAPI middleware support:
106
+
107
+ ```bash
108
+ pip install "error-investigator[fastapi]"
109
+ ```
110
+
111
+ ### Install from Source
112
+
113
+ ```bash
114
+ git clone https://github.com/Head2On/api-error-investigator.git
115
+ cd api-error-investigator
116
+
117
+ # Create and activate virtual environment
118
+ python -m venv .venv
119
+
120
+ # On Linux / macOS:
121
+ source .venv/bin/activate
122
+
123
+ # On Windows (PowerShell):
124
+ .venv\Scripts\Activate.ps1
125
+
126
+ # Install in editable mode
127
+ pip install -e .
128
+ ```
129
+
130
+ Verify the installation:
131
+
132
+ ```bash
133
+ investigate --help
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Quickstart & Usage
139
+
140
+ ### 1. Interactive Mode
141
+
142
+ Run `investigate` without arguments:
143
+
144
+ ```bash
145
+ investigate
146
+ ```
147
+
148
+ Paste your traceback, type `DONE` on a new line (case-insensitive), and press **Enter** (or send EOF with `Ctrl+D` / `Ctrl+Z`):
149
+
150
+ ```text
151
+ Paste your error below.
152
+ (Type 'DONE' on a new blank line and press Enter to submit):
153
+
154
+ ValueError: invalid literal for int() with base 10: 'abc'
155
+ DONE
156
+ ```
157
+
158
+ #### Sample Output (Known Error)
159
+
160
+ ```text
161
+ Error: ValueError
162
+ Status: KNOWN
163
+
164
+ Summary:
165
+ A function received a value of the correct type but with an invalid content.
166
+
167
+ Explanation:
168
+ ValueError means the argument had the right type, but the value itself was not
169
+ acceptable to the function. It's not a type error (which would mean the wrong type was
170
+ passed) -- it's a content error. Built-in functions, int(), float(), datetime parsing,
171
+ and many libraries raise it for malformed input.
172
+
173
+ Common Causes:
174
+ - Calling int("abc") or float("12.3.4") on non-numeric strings.
175
+ - Unpacking a sequence with the wrong number of elements.
176
+ - Passing a negative number to a function that requires non-negative.
177
+ - Parsing a malformed date/time or UUID string.
178
+ - Iterating over a closed file or reading from a closed resource.
179
+
180
+ Fixes:
181
+ - Read the error message -- Python's ValueError almost always states what's wrong.
182
+ - Wrap the call in try/except and validate input before passing it in.
183
+ - If parsing user input, use a parser that returns a result type instead (e.g.
184
+ Pydantic).
185
+ - Check the argument at the call site, not deep inside the function.
186
+ - For unpacking errors, print or log the value to see how many items it actually has.
187
+
188
+ Documentation:
189
+ https://docs.python.org/3/library/exceptions.html#ValueError
190
+
191
+ Related Errors:
192
+ - TypeError
193
+ - KeyError
194
+ ```
195
+
196
+ #### Sample Output (Unknown Error)
197
+
198
+ ```text
199
+ Error: SomeUnregisteredCustomError
200
+ Status: UNKNOWN
201
+
202
+ The Knowledge Base has no matching entry for this error. No diagnosis was produced.
203
+ ```
204
+
205
+ ---
206
+
207
+ ### 2. Piped Mode (Scripts, CI, Logs)
208
+
209
+ In piped mode (when stdin is not a TTY), `investigate` runs silently to EOF without interactive prompts or sentinels:
210
+
211
+ ```bash
212
+ # Pipe directly from a failing script
213
+ python script.py 2>&1 | investigate
214
+
215
+ # Pipe from a saved traceback or log file
216
+ cat traceback.log | investigate
217
+
218
+ # Pipe from PowerShell
219
+ Get-Content error.log | investigate
220
+
221
+ # Pipe a single error string directly
222
+ echo "KeyError: 'user_id'" | investigate
223
+ ```
224
+
225
+ ---
226
+
227
+ ### 3. Custom Knowledge Base (`--kb-dir`)
228
+
229
+ Use your team's custom knowledge base directory instead of the built-in catalog:
230
+
231
+ ```bash
232
+ investigate --kb-dir ./custom_kb
233
+ ```
234
+
235
+ Piped mode works with custom knowledge bases as well:
236
+
237
+ ```bash
238
+ python app.py 2>&1 | investigate --kb-dir ./custom_kb
239
+ ```
240
+
241
+ > [!NOTE]
242
+ > **Early Validation:** If `--kb-dir` does not exist, is not a directory, contains no YAML files, has malformed YAML, or contains identifier collisions, `investigate` prints an error and exits **before** prompting for input.
243
+
244
+ ---
245
+
246
+ ## CLI Reference
247
+
248
+ ```text
249
+ Usage: investigate [OPTIONS]
250
+
251
+ Error Investigator — a CLI developer tool for investigating Python and API
252
+ errors.
253
+
254
+ ╭─ Options ────────────────────────────────────────────────────────────────────╮
255
+ │ --kb-dir <path> Path to a custom knowledge base directory. │
256
+ │ --help Show this message and exit. │
257
+ ╰──────────────────────────────────────────────────────────────────────────────╯
258
+ ```
259
+
260
+ ### Exit Codes
261
+
262
+ | Code | Meaning | Reason |
263
+ | :---: | :--- | :--- |
264
+ | **`0`** | **Success** | Investigation completed normally (for both `KNOWN` and `UNKNOWN` errors). |
265
+ | **`1`** | **Execution Error** | Empty input received, or custom knowledge base failed validation (nonexistent path, empty folder, invalid YAML, or identifier collision). |
266
+ | **`2`** | **Invocation Error** | Invalid CLI usage (e.g. unrecognized options or unexpected positional arguments). |
267
+
268
+ ---
269
+
270
+ ## Knowledge Base Schema
271
+
272
+ Each knowledge base entry is a YAML file placed in the knowledge directory.
273
+
274
+ ### YAML Specification
275
+
276
+ ```yaml
277
+ error_type: my_service.errors.UserNotFoundError
278
+ aliases:
279
+ - UserNotFoundError
280
+ - AccountNotFound
281
+ category: database
282
+ subcategory: entity_lookup # optional (string or null)
283
+ summary: Raised when a user record cannot be found in the database.
284
+ explanation: >
285
+ Occurs when querying for a user ID that does not exist in the database,
286
+ or when an account has been soft-deleted and filtered out by active tenant rules.
287
+ common_causes:
288
+ - The requested user_id does not exist in the users table.
289
+ - The record is marked as deleted (deleted_at is not NULL).
290
+ - The query is scoped to the wrong tenant ID.
291
+ fixes:
292
+ - Check if user_id is correct before executing the query.
293
+ - Use get_or_none() or handle UserNotFoundError explicitly.
294
+ - Verify tenant isolation filter context.
295
+ docs_url: https://docs.example.com/errors/user-not-found # optional (string or null)
296
+ related_errors:
297
+ - AuthenticationError
298
+ ```
299
+
300
+ ### Schema Rules & Invariants
301
+
302
+ 1. **Required Fields:**
303
+ - `error_type` *(string)*: Primary fully-qualified error class identifier.
304
+ - `category` *(string)*: High-level domain category (e.g., `database`, `python`, `network`).
305
+ - `summary` *(string)*: High-level summary of the issue.
306
+ - `explanation` *(string)*: Detailed explanation of the error context.
307
+ - `common_causes` *(list of strings)*: Typical root causes.
308
+ - `fixes` *(list of strings)*: Practical, actionable remedies.
309
+ - `aliases` *(list of strings)*: Alternative names (e.g. unqualified class names).
310
+ - `related_errors` *(list of strings)*: Associated error types.
311
+ 2. **Optional Fields:**
312
+ - `subcategory` *(string or null)*: Granular subcategory.
313
+ - `docs_url` *(string or null)*: Official documentation link.
314
+ 3. **Collision Invariant:**
315
+ No identifier may appear as the `error_type` or `alias` of more than one entry or more than once within a single entry across the entire knowledge base. Any overlap triggers an upfront `KnowledgeEntryError`.
316
+
317
+ ---
318
+
319
+ ## Built-in Knowledge Base
320
+
321
+ Out of the box, `investigate` includes curated diagnostics for:
322
+
323
+ | Error Type | Category | Aliases |
324
+ |---|---|---|
325
+ | **Python builtins** | | |
326
+ | `ValueError` | `python` | — |
327
+ | `KeyError` | `python` | — |
328
+ | `AttributeError` | `python` | — |
329
+ | `TypeError` | `python` | — |
330
+ | `IndexError` | `python` | — |
331
+ | `ModuleNotFoundError` | `python` | — |
332
+ | **Filesystem** | | |
333
+ | `FileNotFoundError` | `filesystem` | — |
334
+ | `PermissionError` | `filesystem` | — |
335
+ | **Network** | | |
336
+ | `ConnectionError` | `network` | — |
337
+ | `TimeoutError` | `network` | — |
338
+ | `requests.exceptions.ConnectionError` | `network` | `requests.ConnectionError` |
339
+ | `requests.exceptions.Timeout` | `network` | `requests.Timeout` |
340
+ | `httpx.ConnectError` | `network` | — |
341
+ | **Data** | | |
342
+ | `json.JSONDecodeError` | `data` | `JSONDecodeError` |
343
+ | `pydantic.ValidationError` | `data` | — |
344
+ | **Database** | | |
345
+ | `sqlalchemy.exc.IntegrityError` | `database` | `IntegrityError` |
346
+ | `sqlalchemy.exc.OperationalError` | `database` | `OperationalError` |
347
+ | `sqlalchemy.exc.DataError` | `database` | `DataError` |
348
+ | `sqlalchemy.exc.NoResultFound` | `database` | `NoResultFound` |
349
+ | `sqlalchemy.exc.MultipleResultsFound` | `database` | `MultipleResultsFound` |
350
+
351
+ ---
352
+
353
+ ## Python SDK & Programmatic API
354
+
355
+ Error Investigator provides both high-level runtime utilities in `investigator.sdk` and direct parsing functions in `investigator`.
356
+
357
+ ### 1. Investigating Caught Exceptions (`investigate_exception`)
358
+
359
+ Use `investigate_exception` to inspect an active or caught exception instance directly without manually extracting tracebacks.
360
+
361
+ - **Pure Function:** Zero side effects — never prints, logs, or re-raises.
362
+ - **Traceback-Aware:** Analyzes active exception tracebacks, chained exceptions (`from e`), and unraised exceptions (a never-raised exception is formatted as `TypeName: message` and investigated normally).
363
+ - **Custom KB Support:** Supports custom `--kb-dir` catalogs via the `kb_dir` keyword argument.
364
+
365
+ ```python
366
+ from investigator.sdk import investigate_exception
367
+
368
+ try:
369
+ int("invalid")
370
+ except ValueError as exc:
371
+ result = investigate_exception(exc)
372
+
373
+ if result.status == "known":
374
+ assert result.investigation is not None
375
+ print(f"Error Type: {result.parsed_error['error_type']}")
376
+ print(f"Summary: {result.investigation.summary}")
377
+ print("Recommended Fixes:")
378
+ for fix in result.investigation.fixes:
379
+ print(f" • {fix}")
380
+ else:
381
+ print("Uncataloged error. No diagnosis produced.")
382
+ ```
383
+
384
+ ---
385
+
386
+ ### 2. Automated Exception Capture (`capture_exception`)
387
+
388
+ `capture_exception` acts as both a **context manager** and a **decorator** to monitor code blocks or functions. When an `Exception` is raised, it automatically investigates the error, outputs formatted diagnosis to `sys.stderr` (or a custom sink), and optionally re-raises.
389
+
390
+ #### As a Context Manager
391
+
392
+ ```python
393
+ from investigator.sdk import capture_exception
394
+
395
+ # Default: prints formatted diagnosis to stderr and re-raises the original exception
396
+ with capture_exception():
397
+ data = load_data()
398
+
399
+ # Suppress exception after diagnosing (reraise=False)
400
+ with capture_exception(reraise=False):
401
+ risky_operation()
402
+
403
+ # Route diagnoses to a custom sink (e.g. logger, monitoring, or telemetry)
404
+ def log_sink(result):
405
+ my_logger.error("Investigated failure: %s", result.parsed_error["error_type"])
406
+ if result.investigation:
407
+ my_logger.info("Summary: %s", result.investigation.summary)
408
+
409
+ with capture_exception(sink=log_sink):
410
+ process_order()
411
+ ```
412
+
413
+ #### As a Function Decorator
414
+
415
+ Works as either a bare decorator or parameterized with options:
416
+
417
+ ```python
418
+ from investigator.sdk import capture_exception
419
+
420
+ # Bare decorator: diagnoses to stderr and re-raises on exception
421
+ @capture_exception
422
+ def query_user(user_id: int):
423
+ return db.query(User).filter_by(id=user_id).one()
424
+
425
+ # Parameterized decorator: custom sink and suppression
426
+ @capture_exception(reraise=False, sink=custom_sink)
427
+ def background_sync():
428
+ sync_remote_records()
429
+
430
+ # With custom knowledge base directory
431
+ @capture_exception(kb_dir="./custom_kb")
432
+ def calculate_metrics():
433
+ compute_heavy_stats()
434
+ ```
435
+
436
+ > [!NOTE]
437
+ > **Safe for Repeated Calls:** Decorated functions are reentrant and safe for repeated calls across the application lifecycle.
438
+
439
+ ---
440
+
441
+ ### 3. Direct Traceback String Investigation (`investigate`)
442
+
443
+ For investigating raw traceback strings from external logs, worker queues, or stdin streams:
444
+
445
+ ```python
446
+ from investigator import investigate
447
+
448
+ traceback_text = """
449
+ Traceback (most recent call last):
450
+ File "main.py", line 12, in <module>
451
+ user = users[user_id]
452
+ KeyError: 'user_id'
453
+ """
454
+
455
+ result = investigate(traceback_text)
456
+
457
+ if result.status == "known":
458
+ assert result.investigation is not None
459
+ print(f"Summary: {result.investigation.summary}")
460
+ ```
461
+
462
+ ---
463
+
464
+ ### 4. Custom Knowledge Base & Error Handling
465
+
466
+ When providing a custom knowledge base directory (`kb_dir`) to `investigate_exception` or `capture_exception`, the directory is validated upfront (checking path existence, directory type, presence of YAML files, schema integrity, and identifier collisions). If validation fails, `KnowledgeEntryError` is raised immediately.
467
+
468
+ ```python
469
+ from pathlib import Path
470
+ from investigator.sdk import KnowledgeEntryError, investigate_exception
471
+
472
+ try:
473
+ result = investigate_exception(exc, kb_dir=Path("./invalid_or_missing_kb"))
474
+ except KnowledgeEntryError as err:
475
+ print(f"Custom knowledge base error: {err}")
476
+ ```
477
+
478
+ ---
479
+
480
+ ### 5. SDK Reference & Invariants
481
+
482
+ | Component | Signature / Usage | Description |
483
+ | :--- | :--- | :--- |
484
+ | **`investigate_exception`** | `investigate_exception(exc, *, kb_dir=None) -> InvestigationResult` | Pure function returning structured diagnosis. Propagates `KnowledgeEntryError` on invalid `kb_dir`. |
485
+ | **`capture_exception`** | `@capture_exception`<br>`with capture_exception(*, reraise=True, sink=None, kb_dir=None):` | Context manager & decorator. Formats diagnosis to `sys.stderr` by default and preserves traceback on re-raise. |
486
+ | **`KnowledgeEntryError`** | `from investigator.sdk import KnowledgeEntryError` | Re-exported exception raised when custom knowledge base fails schema or collision validation. |
487
+
488
+ #### Product Invariants
489
+ - **Non-Interference with System Signals:** `KeyboardInterrupt` and `SystemExit` are **never** caught or intercepted by `capture_exception`; they pass through cleanly.
490
+ - **Sink Customization:** Supplying a callable `sink` bypasses `sys.stderr` emission entirely, directing the `InvestigationResult` directly into your logging or telemetry pipeline.
491
+
492
+ ---
493
+
494
+ ## FastAPI & Starlette Middleware
495
+
496
+ Error Investigator provides `InvestigatorMiddleware` for FastAPI and Starlette applications, enabling automatic error interception, request context extraction, credential sanitization, and structured diagnosis routing.
497
+
498
+ Install with FastAPI support:
499
+
500
+ ```bash
501
+ pip install error-investigator[fastapi]
502
+ ```
503
+
504
+ ### 1. Basic Setup (Default Re-Raise)
505
+
506
+ By default (`reraise=True`), unhandled route exceptions are caught, investigated, rendered to `sys.stderr`, and re-raised for your standard exception handlers:
507
+
508
+ ```python
509
+ from fastapi import FastAPI
510
+ from investigator.fastapi import InvestigatorMiddleware
511
+
512
+ app = FastAPI()
513
+ app.add_middleware(InvestigatorMiddleware)
514
+
515
+ @app.get("/users/{user_id}")
516
+ def get_user(user_id: int):
517
+ # If this raises a KeyError or ValueError, it will be automatically diagnosed
518
+ return database[user_id]
519
+ ```
520
+
521
+ ### 2. Suppressed Mode & Response Exposure (`reraise=False`)
522
+
523
+ To catch unhandled exceptions and return a clean HTTP 500 response without crashing the server process, set `reraise=False`:
524
+
525
+ ```python
526
+ app.add_middleware(InvestigatorMiddleware, reraise=False)
527
+ ```
528
+
529
+ By default, the returned JSON response is strictly minimal to prevent internal implementation details from leaking to clients:
530
+
531
+ ```json
532
+ {
533
+ "error": "InternalServerError"
534
+ }
535
+ ```
536
+ Headers:
537
+ `X-Error-Investigated: true`
538
+ `X-Error-Status: known` (or `unknown`)
539
+
540
+ #### Opt-In Diagnostic Body (`expose_diagnosis=True`)
541
+
542
+ For development environments or trusted internal APIs, pass `expose_diagnosis=True` to include the full structured diagnosis directly in the JSON response:
543
+
544
+ ```python
545
+ app.add_middleware(InvestigatorMiddleware, reraise=False, expose_diagnosis=True)
546
+ ```
547
+
548
+ ```json
549
+ {
550
+ "error": "InternalServerError",
551
+ "status": "known",
552
+ "message": "invalid literal for int() with base 10: 'abc'",
553
+ "investigation": {
554
+ "summary": "A function received a value of the correct type but with an invalid content.",
555
+ "explanation": "ValueError means the argument had the right type, but the value itself was not acceptable...",
556
+ "common_causes": [
557
+ "Calling int('abc') on non-numeric strings.",
558
+ "Parsing a malformed date/time string."
559
+ ],
560
+ "fixes": [
561
+ "Wrap the call in try/except and validate input before passing it in.",
562
+ "Check the argument at the call site."
563
+ ],
564
+ "docs_url": "https://docs.python.org/3/library/exceptions.html#ValueError",
565
+ "related_errors": ["TypeError", "KeyError"]
566
+ }
567
+ }
568
+ ```
569
+
570
+ ### 3. Custom Telemetry & Sinks
571
+
572
+ You can route diagnoses and immutable `RequestContext` instances directly to your logging or monitoring pipeline:
573
+
574
+ ```python
575
+ def my_sink(result, context):
576
+ logger.error(
577
+ "Request failed: %s %s (status %d) -> %s",
578
+ context.method,
579
+ context.path,
580
+ context.status_code,
581
+ result.parsed_error["error_type"],
582
+ )
583
+
584
+ app.add_middleware(InvestigatorMiddleware, sink=my_sink)
585
+ ```
586
+
587
+ The sink dispatcher supports:
588
+ - **1-argument sinks:** `sink(result)`
589
+ - **2-argument sinks:** `sink(result, context)`
590
+ - **Async sinks:** `async def sink(result, context): await send_alert(...)`
591
+
592
+ ### 4. Automatic Sanitization (`sanitize=True`)
593
+
594
+ Enabled by default, sanitization protects sensitive data before logging, printing, or sending responses:
595
+ - **Headers Redacted:** `Authorization`, `Cookie`, `X-Api-Key`, and any header containing `auth`, `token`, `secret`, `key`, or `password` are replaced with `"[REDACTED]"`.
596
+ - **Query Parameters Redacted:** Sensitive query values like `?token=...`, `?password=...`, and `?secret=...` are masked.
597
+ - **Traceback Message Scrubbing:** Credentials in exception messages (`password='...'` or `Bearer ...`) are scrubbed before reaching sinks or terminal output.
598
+
599
+ To disable sanitization:
600
+ ```python
601
+ app.add_middleware(InvestigatorMiddleware, sanitize=False)
602
+ ```
603
+
604
+ ### 5. Custom Knowledge Base
605
+
606
+ Teams can supply custom knowledge catalogs via `kb_dir`:
607
+
608
+ ```python
609
+ app.add_middleware(InvestigatorMiddleware, kb_dir="./custom_kb")
610
+ ```
611
+
612
+ If the custom directory contains invalid schema or identifier collisions, `KnowledgeEntryError` is raised upfront on application startup.
613
+
614
+ ---
615
+
616
+ ## Architecture & Codebase Layout
617
+
618
+ ```text
619
+ src/investigator/
620
+ ├── __init__.py # Public API exports (investigate, InvestigationResult, etc.)
621
+ ├── cli.py # CLI application and Typer command wiring
622
+ ├── cli_format.py # Terminal output presentation (88-col wrap, hanging indents)
623
+ ├── error_parser.py # Traceback parser extracting structured ParsedError
624
+ ├── fastapi.py # FastAPI / Starlette middleware (InvestigatorMiddleware)
625
+ ├── investigation.py # Investigation dataclass
626
+ ├── investigation_result.py # Result envelope and status invariants
627
+ ├── investigator.py # Pipeline orchestration: parse → lookup → result
628
+ ├── io_utils.py # Multiline stdin capture (interactive TTY & pipe support)
629
+ ├── knowledge_loader.py # YAML loader, schema validation, and collision detection
630
+ ├── knowledge_lookup.py # In-memory index and lookup engine
631
+ ├── sdk.py # Programmatic Python SDK (investigate_exception, capture_exception)
632
+ └── knowledge/ # Built-in curated YAML knowledge entries
633
+ ```
634
+
635
+ ### Layering Boundary
636
+
637
+ The codebase enforces strict, unidirectional module layering:
638
+ - **CLI Layer:** [`cli.py`](src/investigator/cli.py) handles terminal I/O, invokes the loader for upfront `--kb-dir` validation, and triggers the investigator.
639
+ - **Middleware Layer:** [`fastapi.py`](src/investigator/fastapi.py) provides ASGI middleware for web frameworks.
640
+ - **SDK Layer:** [`sdk.py`](src/investigator/sdk.py) provides programmatic developer integration (`investigate_exception`, `capture_exception`).
641
+ - **Orchestration Layer:** [`investigator.py`](src/investigator/investigator.py) coordinates parsing and lookup.
642
+ - **Lookup Layer:** [`knowledge_lookup.py`](src/investigator/knowledge_lookup.py) manages in-memory indexing and query matching.
643
+ - **Loader Layer:** [`knowledge_loader.py`](src/investigator/knowledge_loader.py) reads YAML, validates schema constraints, and detects collisions.
644
+
645
+ ---
646
+
647
+ ## Development & Testing
648
+
649
+ ### Running Tests
650
+
651
+ Run the full pytest suite with coverage analysis:
652
+
653
+ ```bash
654
+ pytest --cov=investigator --cov-report=term-missing
655
+ ```
656
+
657
+ ### Current Status
658
+
659
+ - **296 passed tests**
660
+ - **100% statement coverage** across all source files
661
+ - Strict test isolation (cache resets, interactive TTY simulation, edge-case and adversarial checks)
662
+
663
+ ---
664
+
665
+ ## Roadmap
666
+
667
+ - **Chunk A** - Core deterministic investigator ✅
668
+ - **Chunk B** - External investigation engine ⏭️ deferred
669
+ - **Chunk C** - Developer integration ✅
670
+ - C1: CLI productization
671
+ - C2: Python SDK
672
+ - C3: FastAPI middleware
673
+ - C4: Knowledge base expansion (20 entries)
674
+ - **Chunks D-G** - Future direction (backend service, infrastructure,
675
+ production hardening, release) - not currently planned
676
+
677
+ ---
678
+
679
+ ## License
680
+
681
+ This project is licensed under the [MIT License](LICENSE).