codegraph-engine 2.1.2__py3-none-any.whl → 2.1.7__py3-none-any.whl

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 (61) hide show
  1. codegraph/__init__.py +1 -1
  2. codegraph/agent_brain.py +1961 -0
  3. codegraph/agent_capabilities.py +3500 -0
  4. codegraph/agent_rules.py +530 -0
  5. codegraph/architecture.py +48 -5
  6. codegraph/binding_resolver.py +616 -0
  7. codegraph/cli.py +345 -5
  8. codegraph/config.py +2 -0
  9. codegraph/context.py +845 -93
  10. codegraph/database/__init__.py +58 -0
  11. codegraph/database/extractor.py +2956 -0
  12. codegraph/database/interrogation.py +1085 -0
  13. codegraph/database/models.py +310 -0
  14. codegraph/epistemic.py +95 -0
  15. codegraph/errors.py +7 -0
  16. codegraph/evidence/citations.py +22 -1
  17. codegraph/evidence_contract.py +455 -0
  18. codegraph/frameworks.py +372 -11
  19. codegraph/git.py +92 -10
  20. codegraph/graph/models.py +15 -0
  21. codegraph/graph/traversal.py +680 -121
  22. codegraph/indexing/__init__.py +15 -1
  23. codegraph/indexing/classifier.py +343 -58
  24. codegraph/indexing/indexer.py +1515 -141
  25. codegraph/indexing/models.py +75 -0
  26. codegraph/indexing/parser.py +1843 -70
  27. codegraph/indexing/scanner.py +4 -1
  28. codegraph/indexing/telemetry.py +326 -0
  29. codegraph/installer.py +1652 -0
  30. codegraph/interrogation.py +708 -122
  31. codegraph/mcp/server.py +525 -107
  32. codegraph/mcp_diagnostics.py +879 -0
  33. codegraph/models.py +14 -0
  34. codegraph/monorepo.py +922 -0
  35. codegraph/observability.py +113 -2
  36. codegraph/optimizer.py +647 -42
  37. codegraph/ranking.py +61 -16
  38. codegraph/resolver.py +2006 -77
  39. codegraph/resources/monitor.py +1 -1
  40. codegraph/retrieval_policy.py +96 -17
  41. codegraph/route_composer.py +363 -0
  42. codegraph/runtime/__init__.py +32 -0
  43. codegraph/runtime/ingestor.py +672 -0
  44. codegraph/runtime/models.py +130 -0
  45. codegraph/runtime/reconciliation.py +492 -0
  46. codegraph/search/__init__.py +2 -0
  47. codegraph/search/hybrid.py +572 -53
  48. codegraph/security/__init__.py +50 -2
  49. codegraph/security/paths.py +162 -8
  50. codegraph/security/redaction.py +633 -0
  51. codegraph/semantic_decorators.py +341 -0
  52. codegraph/target_resolver.py +6 -6
  53. codegraph/tool_selection_eval.py +1158 -0
  54. codegraph_engine-2.1.7.dist-info/METADATA +617 -0
  55. codegraph_engine-2.1.7.dist-info/RECORD +84 -0
  56. {codegraph_engine-2.1.2.dist-info → codegraph_engine-2.1.7.dist-info}/licenses/LICENSE +1 -1
  57. codegraph_engine-2.1.2.dist-info/METADATA +0 -336
  58. codegraph_engine-2.1.2.dist-info/RECORD +0 -63
  59. {codegraph_engine-2.1.2.dist-info → codegraph_engine-2.1.7.dist-info}/WHEEL +0 -0
  60. {codegraph_engine-2.1.2.dist-info → codegraph_engine-2.1.7.dist-info}/entry_points.txt +0 -0
  61. {codegraph_engine-2.1.2.dist-info → codegraph_engine-2.1.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,3500 @@
1
+ """Deterministic Agent Capability Registry, Task Classification & Tool-Selection Policy.
2
+
3
+ Solves:
4
+ - Deterministic agent task classification (13 canonical categories)
5
+ - Complete 39-tool capability registry derived from actual MCP server schemas
6
+ - Capability matrix mapping task categories to CodeGraph MCP tools
7
+ - Tool-selection policy with explicit evidence guarantees and limitations
8
+ - Machine-readable capability manifest (`get_capability_manifest()`)
9
+ - Safe fallback policy preserving epistemic states (`UNKNOWN`, `POSSIBLE`, `AMBIGUOUS`)
10
+
11
+ Invariants:
12
+ - Never claim CodeGraph will always be used; AI agents retain tool-selection autonomy.
13
+ - For trivial single-file edits (`LOCAL_EDIT`), direct file inspection is preferred.
14
+ - `UNKNOWN` from CodeGraph means "static analysis could not establish the relationship",
15
+ NEVER "there is no relationship".
16
+ - `POSSIBLE` means a plausible static candidate that requires targeted source verification.
17
+ - Output is 100% deterministic with zero environment-specific absolute paths.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import re
22
+ import sqlite3
23
+ from dataclasses import dataclass
24
+ from enum import StrEnum
25
+ from typing import Any
26
+
27
+ from codegraph import __version__
28
+
29
+
30
+ class AgentTaskCategory(StrEnum):
31
+ """Canonical agent task categories for tool-selection routing."""
32
+
33
+ LOCAL_EDIT = "LOCAL_EDIT"
34
+ SYMBOL_LOOKUP = "SYMBOL_LOOKUP"
35
+ RELATIONSHIP = "RELATIONSHIP"
36
+ TRACE = "TRACE"
37
+ DEBUG = "DEBUG"
38
+ CHANGE_IMPACT = "CHANGE_IMPACT"
39
+ TEST_DISCOVERY = "TEST_DISCOVERY"
40
+ ROUTE_DISCOVERY = "ROUTE_DISCOVERY"
41
+ DIAGNOSTIC = "DIAGNOSTIC"
42
+ ARCHITECTURE = "ARCHITECTURE"
43
+ PACKAGE = "PACKAGE"
44
+ MULTI_FILE_INVESTIGATION = "MULTI_FILE_INVESTIGATION"
45
+ EXPLANATION = "EXPLANATION"
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class ToolCapabilitySpec:
50
+ """Declarative specification for a single CodeGraph MCP tool."""
51
+
52
+ tool_name: str
53
+ capability: str
54
+ description: str
55
+ task_types: tuple[str, ...]
56
+ required_inputs: tuple[str, ...]
57
+ expected_output_type: str
58
+ relationship_types_returned: tuple[str, ...]
59
+ evidence_guarantees: str
60
+ does_not_prove: str
61
+ useful_situations: tuple[str, ...]
62
+ avoid_when: tuple[str, ...]
63
+ profiles: tuple[str, ...]
64
+ optional_inputs: tuple[str, ...] = ()
65
+ minimal_invocation: str = ""
66
+ advanced_invocation: str = ""
67
+ result_fields: tuple[str, ...] = ()
68
+ typical_followup: str = ""
69
+ common_mistakes: tuple[str, ...] = ()
70
+ example_request: str = ""
71
+ example_call: str = ""
72
+ example_interpretation: str = ""
73
+
74
+ def as_dict(self) -> dict[str, object]:
75
+ return {
76
+ "tool_name": self.tool_name,
77
+ "capability": self.capability,
78
+ "description": self.description,
79
+ "task_types": list(self.task_types),
80
+ "required_inputs": list(self.required_inputs),
81
+ "optional_inputs": list(self.optional_inputs),
82
+ "expected_output_type": self.expected_output_type,
83
+ "relationship_types_returned": list(self.relationship_types_returned),
84
+ "evidence_guarantees": self.evidence_guarantees,
85
+ "does_not_prove": self.does_not_prove,
86
+ "useful_situations": list(self.useful_situations),
87
+ "avoid_when": list(self.avoid_when),
88
+ "profiles": list(self.profiles),
89
+ "minimal_invocation": self.minimal_invocation,
90
+ "advanced_invocation": self.advanced_invocation,
91
+ "result_fields": list(self.result_fields),
92
+ "typical_followup": self.typical_followup,
93
+ "common_mistakes": list(self.common_mistakes),
94
+ "example_request": self.example_request,
95
+ "example_call": self.example_call,
96
+ "example_interpretation": self.example_interpretation,
97
+ }
98
+
99
+
100
+ @dataclass(frozen=True)
101
+ class TaskCapabilityRule:
102
+ """Capability matrix entry mapping a task category to recommended CodeGraph usage."""
103
+
104
+ category: AgentTaskCategory
105
+ should_use_codegraph: bool
106
+ primary_tool: str | None
107
+ acceptable_tools: tuple[str, ...]
108
+ recommended_sequence: tuple[str, ...]
109
+ direct_inspection_acceptable: bool
110
+ rationale: str
111
+
112
+ def as_dict(self) -> dict[str, object]:
113
+ return {
114
+ "category": self.category.value,
115
+ "should_use_codegraph": self.should_use_codegraph,
116
+ "primary_tool": self.primary_tool,
117
+ "acceptable_tools": list(self.acceptable_tools),
118
+ "recommended_sequence": list(self.recommended_sequence),
119
+ "direct_inspection_acceptable": self.direct_inspection_acceptable,
120
+ "rationale": self.rationale,
121
+ }
122
+
123
+
124
+ # ---------------------------------------------------------------------------
125
+ # Explicit Agent Routing Manifest & Default Agent Profile (Phases 3 & 7)
126
+ # ---------------------------------------------------------------------------
127
+
128
+ ROUTING_MANIFEST: dict[str, str] = {
129
+ "symbol_discovery": "find_symbol",
130
+ "literal_search": "search_code",
131
+ "caller_discovery": "find_callers",
132
+ "callee_discovery": "find_callees",
133
+ "reference_discovery": "find_references",
134
+ "route_discovery": "find_routes",
135
+ "test_discovery": "find_tests",
136
+ "source_inspection": "get_file",
137
+ "context_synthesis": "get_context",
138
+ "architecture_analysis": "get_architecture",
139
+ "relationship_trace": "trace_path",
140
+ "change_impact": "get_git_impact",
141
+ "db_tables": "find_db_tables",
142
+ "db_columns": "find_db_columns",
143
+ "db_models": "find_db_models",
144
+ "db_queries": "find_db_queries",
145
+ "db_callers": "find_db_callers",
146
+ "db_writers": "find_db_writers",
147
+ "db_readers": "find_db_readers",
148
+ "db_relationships": "find_db_relationships",
149
+ "db_table_details": "get_db_table",
150
+ "db_schema": "get_db_schema",
151
+ "db_impact": "get_db_impact",
152
+ "runtime_ingest": "ingest_runtime_traces",
153
+ "runtime_trace": "get_runtime_trace",
154
+ "runtime_reconcile": "reconcile_static_runtime",
155
+ }
156
+
157
+ DEFAULT_AGENT_PROFILE_TOOLS: tuple[str, ...] = (
158
+ # DISCOVERY (7)
159
+ "find_symbol",
160
+ "search_code",
161
+ "find_references",
162
+ "find_callers",
163
+ "find_callees",
164
+ "find_tests",
165
+ "find_routes",
166
+ # DETAILS (5)
167
+ "get_symbol",
168
+ "get_file",
169
+ "get_context",
170
+ "get_architecture",
171
+ "get_git_impact",
172
+ # GRAPH (2)
173
+ "trace_path",
174
+ "trace_flow",
175
+ )
176
+
177
+
178
+ # ---------------------------------------------------------------------------
179
+ # Canonical Tool Registry (All 42 Exposed MCP Tools)
180
+ # ---------------------------------------------------------------------------
181
+
182
+ TOOL_CAPABILITY_REGISTRY: tuple[ToolCapabilitySpec, ...] = (
183
+ # 1. resolve_symbol
184
+ ToolCapabilitySpec(
185
+ tool_name="resolve_symbol",
186
+ capability="symbol_resolution",
187
+ description=(
188
+ "Resolve a symbol name, qualified name, canonical ID, or route into its canonical repository identity and file/line location. "
189
+ "Use as the first step before relationship queries when exact symbol identity is unknown or potentially ambiguous. "
190
+ "Primary input: `symbol` (also accepts `canonical_id` or `name` as compatibility aliases). "
191
+ "Returns canonical_id, ambiguity_state, and candidate alternatives. "
192
+ "Does not prove runtime execution or dynamic monkey-patching."
193
+ ),
194
+ task_types=(
195
+ AgentTaskCategory.SYMBOL_LOOKUP.value,
196
+ AgentTaskCategory.RELATIONSHIP.value,
197
+ AgentTaskCategory.DEBUG.value,
198
+ AgentTaskCategory.CHANGE_IMPACT.value,
199
+ AgentTaskCategory.TRACE.value,
200
+ ),
201
+ required_inputs=("symbol",),
202
+ optional_inputs=("canonical_id", "name"),
203
+ expected_output_type="SymbolResolutionResult",
204
+ relationship_types_returned=("DEFINES", "RESOLVES_TO"),
205
+ evidence_guarantees="AST-verified symbol definition coordinates and explicit AMBIGUOUS/UNKNOWN states.",
206
+ does_not_prove="Does not prove runtime call edges or dynamic attribute injection.",
207
+ useful_situations=(
208
+ "Locating where a class, function, or method is canonically defined",
209
+ "Disambiguating homonymous symbols across multiple modules or packages",
210
+ "Grounding a symbol name into a canonical_id before calling get_callers or trace_path",
211
+ ),
212
+ avoid_when=(
213
+ "Purely local single-file text edit where the file and line are already open",
214
+ "Repeatedly resolving the same canonical_id already returned in the current session",
215
+ ),
216
+ profiles=("core", "graph", "minimal", "developer", "full"),
217
+ minimal_invocation='resolve_symbol(symbol="AuthService")',
218
+ advanced_invocation='resolve_symbol(symbol="src.auth.service.AuthService.authenticate")',
219
+ result_fields=("status", "canonical_id", "qualified_name", "file", "start_line", "end_line", "ambiguity_state", "matches", "alternatives"),
220
+ typical_followup="get_callers, get_callees, get_references, trace_path, or get_context",
221
+ common_mistakes=(
222
+ "Calling resolve_symbol repeatedly in a loop for the same symbol",
223
+ "Choosing matches[0] blindly when ambiguity_state is AMBIGUOUS",
224
+ ),
225
+ example_request="Where is AuthService defined?",
226
+ example_call='resolve_symbol(symbol="AuthService")',
227
+ example_interpretation="Inspect canonical_id, file, and start_line..end_line; if ambiguity_state == 'AMBIGUOUS', compare alternatives by module/package.",
228
+ ),
229
+ # 2. search_symbols
230
+ ToolCapabilitySpec(
231
+ tool_name="search_symbols",
232
+ capability="symbol_discovery",
233
+ description=(
234
+ "Search indexed repository symbols by partial name or keyword with ranked relevance. "
235
+ "Use when exact symbol spelling is unknown and you need candidate symbol definitions. "
236
+ "Returns ranked symbol definitions with file paths and line spans. "
237
+ "Does not return module import graphs or call relationships."
238
+ ),
239
+ task_types=(
240
+ AgentTaskCategory.SYMBOL_LOOKUP.value,
241
+ AgentTaskCategory.MULTI_FILE_INVESTIGATION.value,
242
+ AgentTaskCategory.EXPLANATION.value,
243
+ ),
244
+ required_inputs=("query",),
245
+ optional_inputs=("top_k",),
246
+ expected_output_type="SymbolSearchResult",
247
+ relationship_types_returned=("DEFINES",),
248
+ evidence_guarantees="AST-extracted symbol declarations with source file and line numbers.",
249
+ does_not_prove="Does not prove who calls or imports the matched symbols.",
250
+ useful_situations=(
251
+ "Finding where a feature or domain concept is defined across the codebase",
252
+ "Discovering candidate symbols before calling resolve_symbol or get_context",
253
+ ),
254
+ avoid_when=(
255
+ "Discovering module imports or reverse dependencies (use get_imports or get_dependents instead)",
256
+ "Editing a single known file locally",
257
+ ),
258
+ profiles=("core", "graph", "minimal", "developer", "full"),
259
+ minimal_invocation='search_symbols(query="authenticate")',
260
+ advanced_invocation='search_symbols(query="token validator", top_k=10)',
261
+ result_fields=("status", "query", "results", "canonical_id", "qualified_name", "kind", "file", "start_line", "end_line"),
262
+ typical_followup="resolve_symbol or get_context",
263
+ common_mistakes=(
264
+ "Treating lexical symbol search matches as proof of causal call relationships",
265
+ "Using search_symbols to check module imports instead of get_imports",
266
+ ),
267
+ example_request="Find symbols related to password verification.",
268
+ example_call='search_symbols(query="verify_password", top_k=10)',
269
+ example_interpretation="Select the matching symbol definition and pass its symbol or canonical_id to get_callers or get_context.",
270
+ ),
271
+ # 3. get_symbol
272
+ ToolCapabilitySpec(
273
+ tool_name="get_symbol",
274
+ capability="symbol_inspection",
275
+ description=(
276
+ "Return authoritative signature, kind, parent scope, decorators, and bounded source snippet for a symbol. "
277
+ "Primary input: `symbol` (also accepts `canonical_id` or `name` as compatibility aliases). "
278
+ "Use when you have a symbol name or canonical_id and need its declaration metadata. "
279
+ "Does not traverse multi-hop call graphs."
280
+ ),
281
+ task_types=(
282
+ AgentTaskCategory.SYMBOL_LOOKUP.value,
283
+ AgentTaskCategory.EXPLANATION.value,
284
+ ),
285
+ required_inputs=("symbol",),
286
+ optional_inputs=("canonical_id", "name"),
287
+ expected_output_type="SymbolDetailResult",
288
+ relationship_types_returned=("DEFINES", "CONTAINS"),
289
+ evidence_guarantees="AST-verified symbol signature, decorators, and exact line span.",
290
+ does_not_prove="Does not prove downstream impact or callers outside the symbol body.",
291
+ useful_situations=(
292
+ "Inspecting a symbol's signature, return type, and decorators without reading the entire file",
293
+ ),
294
+ avoid_when=(
295
+ "Tracing multi-hop execution flows (use trace_path or get_context instead)",
296
+ ),
297
+ profiles=("agent", "core", "graph", "minimal", "developer", "full"),
298
+ minimal_invocation='get_symbol(symbol="AuthService.authenticate")',
299
+ advanced_invocation='get_symbol(canonical_id="src/auth/service.py:AuthService.authenticate")',
300
+ result_fields=("status", "canonical_id", "qualified_name", "kind", "signature", "decorators", "file", "start_line", "end_line", "snippet"),
301
+ typical_followup="get_file (if full method body beyond snippet is needed) or find_callees",
302
+ common_mistakes=(
303
+ "Calling get_symbol when multi-file callers/callees are needed (use find_callers or get_context)",
304
+ ),
305
+ example_request="What is the exact signature and decorator list of login_endpoint?",
306
+ example_call='get_symbol(symbol="login_endpoint")',
307
+ example_interpretation="Read signature, decorators, and start_line..end_line from the returned declaration record.",
308
+ ),
309
+ # 4. get_file
310
+ ToolCapabilitySpec(
311
+ tool_name="get_file",
312
+ capability="file_structure_inspection",
313
+ description=(
314
+ "Open and inspect a repository file's structural AST outline or a bounded line range (`path`, `start_line`, `end_line`, `max_lines`). "
315
+ "Use after `search_code`, `find_symbol`, or `find_routes` identifies a target file and line span across Python, HTML, Jinja, JS, TS, CSS, YAML, JSON, or Markdown. "
316
+ "Blocks path traversal, binary files, and sensitive files; does not prove cross-file callers."
317
+ ),
318
+ task_types=(
319
+ AgentTaskCategory.LOCAL_EDIT.value,
320
+ AgentTaskCategory.SYMBOL_LOOKUP.value,
321
+ AgentTaskCategory.DEBUG.value,
322
+ AgentTaskCategory.EXPLANATION.value,
323
+ ),
324
+ required_inputs=("path",),
325
+ optional_inputs=("start_line", "end_line", "max_lines", "include_content"),
326
+ expected_output_type="FileStructureResult",
327
+ relationship_types_returned=("DEFINES", "IMPORTS"),
328
+ evidence_guarantees="Bounded on-disk source lines (`start_line`..`end_line`, `truncated`) plus parser-extracted symbols and imports.",
329
+ does_not_prove="Does not prove reverse dependents across the repository.",
330
+ useful_situations=(
331
+ "Opening a targeted line range (`start_line`, `end_line`) of a Python, HTML, Jinja, JS, CSS, or config file",
332
+ "Reviewing the symbols and imports inside a module before editing",
333
+ ),
334
+ avoid_when=(
335
+ "Reading an entire huge file without `start_line`/`end_line` bounds",
336
+ ),
337
+ profiles=("agent", "core", "graph", "minimal", "developer", "full"),
338
+ minimal_invocation='get_file(path="src/auth/service.py", start_line=1, end_line=80)',
339
+ advanced_invocation='get_file(path="templates/dashboard.html", start_line=40, end_line=110, max_lines=200)',
340
+ result_fields=("status", "path", "file", "start_line", "end_line", "content", "truncated", "category", "file_category", "artifact_type", "symbols", "imports", "freshness"),
341
+ typical_followup="perform targeted code edit or call find_callers/find_callees on discovered symbols",
342
+ common_mistakes=(
343
+ "Setting include_content=True on huge files instead of passing start_line and end_line",
344
+ ),
345
+ example_request="Show lines 40 to 110 of templates/dashboard.html.",
346
+ example_call='get_file(path="templates/dashboard.html", start_line=40, end_line=110)',
347
+ example_interpretation="Inspect `content`, `start_line`, `end_line`, and `truncated` for the requested file range.",
348
+ ),
349
+ # 5. get_references
350
+ ToolCapabilitySpec(
351
+ tool_name="get_references",
352
+ capability="reference_relationship",
353
+ description=(
354
+ "Return verified and candidate references to a symbol across the repository with evidence_class labels. "
355
+ "Primary input: `symbol` (also accepts `canonical_id` or `name` as compatibility aliases). "
356
+ "Use for cross-file reference, registration, dispatch, or DI binding lookup. "
357
+ "Does not guarantee dynamic reflection targets when evidence_class is UNKNOWN or POSSIBLE."
358
+ ),
359
+ task_types=(
360
+ AgentTaskCategory.RELATIONSHIP.value,
361
+ AgentTaskCategory.CHANGE_IMPACT.value,
362
+ AgentTaskCategory.DEBUG.value,
363
+ ),
364
+ required_inputs=("symbol",),
365
+ optional_inputs=("canonical_id", "name"),
366
+ expected_output_type="ReferenceListResult",
367
+ relationship_types_returned=(
368
+ "CALLS",
369
+ "IMPORTS",
370
+ "REGISTERS",
371
+ "DISPATCHES_TO",
372
+ "EVENT_LISTENER",
373
+ "INJECTS",
374
+ "PROVIDES",
375
+ "RESOLVES_DEPENDENCY",
376
+ "REFERENCES",
377
+ ),
378
+ evidence_guarantees="Source-backed reference locations with explicit confidence and evidence_class.",
379
+ does_not_prove="Does not prove runtime execution order or unindexed external consumers.",
380
+ useful_situations=(
381
+ "Finding all usages, registrations, or DI bindings of a symbol across files",
382
+ "Checking if a symbol is referenced before refactoring or deleting it",
383
+ ),
384
+ avoid_when=(
385
+ "Purely local variable rename inside a single function scope",
386
+ ),
387
+ profiles=("core", "graph", "minimal", "developer", "full"),
388
+ minimal_invocation='get_references(symbol="command_registry")',
389
+ advanced_invocation='get_references(canonical_id="src/dispatch/registry.py:command_registry")',
390
+ result_fields=("status", "canonical_id", "references", "relationship", "evidence_class", "confidence", "file", "start_line"),
391
+ typical_followup="read_file on specific reference sites or get_context",
392
+ common_mistakes=(
393
+ "Treating REGISTERS or INJECTS references as direct CALLS edges",
394
+ "Treating POSSIBLE references as verified facts without source confirmation",
395
+ ),
396
+ example_request="Where is command_registry referenced or populated?",
397
+ example_call='get_references(symbol="command_registry")',
398
+ example_interpretation="Check each reference item's relationship (e.g., REGISTERS, DISPATCHES_TO) and evidence_class.",
399
+ ),
400
+ # 6. get_callers
401
+ ToolCapabilitySpec(
402
+ tool_name="get_callers",
403
+ capability="caller_relationship",
404
+ description=(
405
+ "Return statically verified callers of a symbol with confidence and evidence_class. "
406
+ "Primary input: `symbol` (also accepts `canonical_id` as a compatibility alias using the exact same resolution path). "
407
+ "Use for multi-file call-relationship and upstream impact questions. "
408
+ "Does not prove runtime dispatch unless evidence_class indicates framework/dataflow verification."
409
+ ),
410
+ task_types=(
411
+ AgentTaskCategory.RELATIONSHIP.value,
412
+ AgentTaskCategory.TRACE.value,
413
+ AgentTaskCategory.DEBUG.value,
414
+ AgentTaskCategory.CHANGE_IMPACT.value,
415
+ ),
416
+ required_inputs=("symbol",),
417
+ optional_inputs=("canonical_id",),
418
+ expected_output_type="CallerListResult",
419
+ relationship_types_returned=("CALLS", "POSSIBLE_CALLS", "DISPATCHES_TO", "HANDLED_BY"),
420
+ evidence_guarantees="Call-site file, line, source caller symbol, and RelationshipEvidenceClass.",
421
+ does_not_prove="Does not prove dead-code reachability at runtime or reflective eval/getattr calls.",
422
+ useful_situations=(
423
+ "Answering 'What calls function X?' across the repository",
424
+ "Tracing upstream callers during debugging or change-impact analysis",
425
+ ),
426
+ avoid_when=(
427
+ "Single-file local edit where call relationships do not matter",
428
+ ),
429
+ profiles=("core", "graph", "minimal", "developer", "full"),
430
+ minimal_invocation='get_callers(symbol="verify_password")',
431
+ advanced_invocation='get_callers(canonical_id="src/auth/security.py:verify_password")',
432
+ result_fields=("status", "canonical_id", "callers", "source", "relationship", "evidence_class", "confidence", "file", "start_line"),
433
+ typical_followup="stop if callers list answers the question, or read_file on specific call sites",
434
+ common_mistakes=(
435
+ "Claiming 'nothing calls X' when dynamic dispatch edges are marked UNKNOWN",
436
+ "Passing an ambiguous bare name without calling resolve_symbol first",
437
+ ),
438
+ example_request="What calls verify_password across the codebase?",
439
+ example_call='get_callers(symbol="verify_password")',
440
+ example_interpretation="Inspect callers list; verified callers carry relationship='CALLS' and evidence_class='AST_VERIFIED' or 'DATAFLOW_VERIFIED'.",
441
+ ),
442
+ # 7. get_callees
443
+ ToolCapabilitySpec(
444
+ tool_name="get_callees",
445
+ capability="callee_relationship",
446
+ description=(
447
+ "Return symbols called or invoked by the specified symbol with evidence_class classification. "
448
+ "Primary input: `symbol` (also accepts `canonical_id` as a compatibility alias using the exact same resolution path). "
449
+ "Use to inspect downstream dependencies invoked by a function or handler. "
450
+ "Does not resolve dynamic callbacks passed as opaque runtime arguments."
451
+ ),
452
+ task_types=(
453
+ AgentTaskCategory.RELATIONSHIP.value,
454
+ AgentTaskCategory.TRACE.value,
455
+ AgentTaskCategory.DEBUG.value,
456
+ AgentTaskCategory.EXPLANATION.value,
457
+ ),
458
+ required_inputs=("symbol",),
459
+ optional_inputs=("canonical_id",),
460
+ expected_output_type="CalleeListResult",
461
+ relationship_types_returned=("CALLS", "POSSIBLE_CALLS", "INJECTS", "RESOLVES_DEPENDENCY"),
462
+ evidence_guarantees="AST/dataflow-verified outgoing calls with exact call-site line numbers.",
463
+ does_not_prove="Does not prove external network calls or dynamically constructed strings.",
464
+ useful_situations=(
465
+ "Answering 'What does function X call?' without manually reading every imported module",
466
+ "Following execution downstream from an endpoint or task handler",
467
+ ),
468
+ avoid_when=(
469
+ "The function body is 5 lines long and already visible in context",
470
+ ),
471
+ profiles=("core", "graph", "minimal", "developer", "full"),
472
+ minimal_invocation='get_callees(symbol="process_checkout")',
473
+ advanced_invocation='get_callees(canonical_id="src/orders/service.py:process_checkout")',
474
+ result_fields=("status", "canonical_id", "callees", "target", "relationship", "evidence_class", "confidence", "file", "start_line"),
475
+ typical_followup="get_symbol or trace_path on downstream targets",
476
+ common_mistakes=(
477
+ "Assuming POSSIBLE_CALLS edges are guaranteed to execute on every code path",
478
+ ),
479
+ example_request="What does process_checkout call downstream?",
480
+ example_call='get_callees(symbol="process_checkout")',
481
+ example_interpretation="Read each outgoing edge in callees along with its target, relationship, and evidence_class.",
482
+ ),
483
+ # 8. trace_path
484
+ ToolCapabilitySpec(
485
+ tool_name="trace_path",
486
+ capability="execution_path_tracing",
487
+ description=(
488
+ "Compute deterministic multi-hop relationship paths between two symbols (`from_symbol` -> `to_symbol`). "
489
+ "Use when tracing how an entrypoint, route, or caller reaches a downstream service or database function. "
490
+ "Returns ordered hop edges with relationship types and evidence classes. "
491
+ "Does not prove runtime branch conditions along the path."
492
+ ),
493
+ task_types=(
494
+ AgentTaskCategory.TRACE.value,
495
+ AgentTaskCategory.DEBUG.value,
496
+ AgentTaskCategory.RELATIONSHIP.value,
497
+ ),
498
+ required_inputs=("from_symbol", "to_symbol"),
499
+ optional_inputs=("start_symbol", "target_symbol", "source_symbol", "max_depth"),
500
+ expected_output_type="PathTraceResult",
501
+ relationship_types_returned=(
502
+ "CALLS",
503
+ "HANDLED_BY",
504
+ "MOUNTS",
505
+ "DISPATCHES_TO",
506
+ "INJECTS",
507
+ "PROVIDES",
508
+ "RESOLVES_DEPENDENCY",
509
+ "TASK_HANDLER",
510
+ "COMMAND_HANDLER",
511
+ "EVENT_LISTENER",
512
+ ),
513
+ evidence_guarantees="Ordered multi-hop chain where every hop carries file, line, and evidence_class.",
514
+ does_not_prove="Does not prove that a conditional runtime branch is taken for a specific input payload.",
515
+ useful_situations=(
516
+ "Proving how an HTTP endpoint or CLI command reaches a downstream repository or helper",
517
+ "Connecting two symbols across multiple intermediate modules",
518
+ ),
519
+ avoid_when=(
520
+ "Only a single symbol is known and you want general context (use get_context instead)",
521
+ ),
522
+ profiles=("agent", "core", "graph", "minimal", "developer", "full"),
523
+ minimal_invocation='trace_path(from_symbol="login_endpoint", to_symbol="find_by_email")',
524
+ advanced_invocation='trace_path(from_symbol="login_endpoint", to_symbol="find_by_email", max_depth=5)',
525
+ result_fields=("status", "from_symbol", "to_symbol", "path", "paths", "hops", "evidence_class", "confidence"),
526
+ typical_followup="get_file on specific hop lines if branch logic must be verified",
527
+ common_mistakes=(
528
+ "Treating an empty path at max_depth=2 as proof of no connection without checking higher depth or dynamic dispatch",
529
+ ),
530
+ example_request="How does login_endpoint reach UserRepository.find_by_email?",
531
+ example_call='trace_path(from_symbol="login_endpoint", to_symbol="find_by_email", max_depth=4)',
532
+ example_interpretation="Inspect the ordered hops in path/paths and verify each hop's relationship and evidence_class.",
533
+ ),
534
+ # 9. get_imports
535
+ ToolCapabilitySpec(
536
+ tool_name="get_imports",
537
+ capability="module_import_inspection",
538
+ description=(
539
+ "Return parser-extracted module and symbol imports for a file or symbol (`file` or `symbol`; `canonical_id` and `path` supported as aliases). "
540
+ "Use for forward dependency and package boundary questions. "
541
+ "Does not return reverse importers (use get_dependents for reverse dependencies)."
542
+ ),
543
+ task_types=(
544
+ AgentTaskCategory.RELATIONSHIP.value,
545
+ AgentTaskCategory.ARCHITECTURE.value,
546
+ AgentTaskCategory.PACKAGE.value,
547
+ ),
548
+ required_inputs=("file",),
549
+ optional_inputs=("symbol", "canonical_id", "path"),
550
+ expected_output_type="ImportsResult",
551
+ relationship_types_returned=("IMPORTS", "DEPENDS_ON_PACKAGE", "CROSS_PACKAGE_IMPORT"),
552
+ evidence_guarantees="AST-verified import statements and resolved internal module paths.",
553
+ does_not_prove="Does not prove whether an imported symbol is actually invoked at runtime.",
554
+ useful_situations=(
555
+ "Inspecting what modules or workspace packages a file depends on",
556
+ ),
557
+ avoid_when=(
558
+ "Finding what files import this module (use get_dependents instead)",
559
+ ),
560
+ profiles=("core", "graph", "minimal", "developer", "full"),
561
+ minimal_invocation='get_imports(file="src/auth/routes.py")',
562
+ advanced_invocation='get_imports(symbol="src/auth/routes.py:login_endpoint")',
563
+ result_fields=("status", "file", "imports", "source", "target", "relationship", "evidence_class"),
564
+ typical_followup="get_dependents or get_architecture",
565
+ common_mistakes=(
566
+ "Using get_imports when looking for reverse dependents (use get_dependents)",
567
+ ),
568
+ example_request="What modules does src/auth/routes.py import?",
569
+ example_call='get_imports(file="src/auth/routes.py")',
570
+ example_interpretation="Inspect the imports list for internal module paths and cross-package imports.",
571
+ ),
572
+ # 10. get_dependents
573
+ ToolCapabilitySpec(
574
+ tool_name="get_dependents",
575
+ capability="reverse_dependency_inspection",
576
+ description=(
577
+ "Return files, symbols, and packages that import or depend on the target symbol or file (`symbol` or `file`; `canonical_id` and `path` supported as aliases). "
578
+ "Use for blast-radius, change-impact, and package boundary analysis. "
579
+ "Does not prove runtime failure without checking test and caller evidence."
580
+ ),
581
+ task_types=(
582
+ AgentTaskCategory.CHANGE_IMPACT.value,
583
+ AgentTaskCategory.RELATIONSHIP.value,
584
+ AgentTaskCategory.PACKAGE.value,
585
+ AgentTaskCategory.ARCHITECTURE.value,
586
+ ),
587
+ required_inputs=("symbol",),
588
+ optional_inputs=("canonical_id", "file", "path"),
589
+ expected_output_type="DependentsResult",
590
+ relationship_types_returned=("IMPORTS", "CALLS", "DEPENDS_ON_PACKAGE", "TESTS"),
591
+ evidence_guarantees="Verified reverse import and call edges pointing to the target.",
592
+ does_not_prove="Does not prove unindexed external repository consumers.",
593
+ useful_situations=(
594
+ "Determining which modules or packages break if a shared symbol or file signature changes",
595
+ ),
596
+ avoid_when=(
597
+ "Editing a private helper that is never exported or referenced outside one function",
598
+ ),
599
+ profiles=("core", "graph", "minimal", "developer", "full"),
600
+ minimal_invocation='get_dependents(symbol="UserRepository")',
601
+ advanced_invocation='get_dependents(file="src/auth/repository.py")',
602
+ result_fields=("status", "dependents", "source", "target", "relationship", "evidence_class", "file"),
603
+ typical_followup="find_related_tests or analyze_impact",
604
+ common_mistakes=(
605
+ "Confusing forward imports (get_imports) with reverse dependents (get_dependents)",
606
+ ),
607
+ example_request="Which modules depend on src/auth/repository.py?",
608
+ example_call='get_dependents(file="src/auth/repository.py")',
609
+ example_interpretation="Review the dependents list to see all upstream files and symbols importing the module.",
610
+ ),
611
+ # 11. list_routes
612
+ ToolCapabilitySpec(
613
+ tool_name="list_routes",
614
+ capability="route_discovery",
615
+ description=(
616
+ "Return HTTP/RPC framework routes, mounted router prefixes (`MOUNTS`), methods, and handler symbols. "
617
+ "Use when locating API endpoints, route handlers, or mounted sub-applications (FastAPI, Flask, Django, Express, NestJS). "
618
+ "Does not prove runtime middleware authentication unless traced via get_context or trace_path."
619
+ ),
620
+ task_types=(
621
+ AgentTaskCategory.ROUTE_DISCOVERY.value,
622
+ AgentTaskCategory.TRACE.value,
623
+ AgentTaskCategory.ARCHITECTURE.value,
624
+ ),
625
+ required_inputs=(),
626
+ optional_inputs=("framework", "method", "path"),
627
+ expected_output_type="RouteListResult",
628
+ relationship_types_returned=("HANDLED_BY", "ROUTE_HANDLER", "MOUNTS", "ROUTES_TO"),
629
+ evidence_guarantees="Framework-verified route paths, HTTP methods, composed mount prefixes, and handler canonical IDs.",
630
+ does_not_prove="Does not prove external API gateway or reverse-proxy rewrite rules outside the repository.",
631
+ useful_situations=(
632
+ "Answering 'Which route handles /api/v1/auth/login?'",
633
+ "Listing all API entrypoints in a service before tracing a request flow",
634
+ ),
635
+ avoid_when=(
636
+ "Working on a pure library or CLI module with no web routes",
637
+ ),
638
+ profiles=("core", "graph", "minimal", "developer", "full"),
639
+ minimal_invocation="list_routes()",
640
+ advanced_invocation='list_routes(method="POST", path="/api/v1/auth/login")',
641
+ result_fields=("status", "routes", "route_path", "http_method", "handler_name", "canonical_id", "framework", "file", "line", "evidence_class"),
642
+ typical_followup="resolve_symbol, trace_path, or get_context(intent='TRACE')",
643
+ common_mistakes=(
644
+ "Grepping for route path strings that are split across router prefix mounts instead of calling list_routes",
645
+ ),
646
+ example_request="Which handler serves POST /api/v1/auth/login?",
647
+ example_call='list_routes(method="POST", path="/api/v1/auth/login")',
648
+ example_interpretation="Read handler_name, canonical_id, and file:line from the matched route entry.",
649
+ ),
650
+ # 12. get_architecture
651
+ ToolCapabilitySpec(
652
+ tool_name="get_architecture",
653
+ capability="architecture_overview",
654
+ description=(
655
+ "Return structural repository overview including modules, workspace packages (`DEPENDS_ON_PACKAGE`), entrypoints, and layer relationships. "
656
+ "Use for high-level architecture, monorepo package boundary, and onboarding questions. "
657
+ "Does not replace symbol-level evidence for specific bug fixes."
658
+ ),
659
+ task_types=(
660
+ AgentTaskCategory.ARCHITECTURE.value,
661
+ AgentTaskCategory.PACKAGE.value,
662
+ AgentTaskCategory.EXPLANATION.value,
663
+ ),
664
+ required_inputs=(),
665
+ optional_inputs=(),
666
+ expected_output_type="ArchitectureOverviewResult",
667
+ relationship_types_returned=("IMPORTS", "DEPENDS_ON_PACKAGE", "CONTAINS_PACKAGE", "MOUNTS", "HANDLED_BY"),
668
+ evidence_guarantees="Manifest-backed workspace packages and AST-verified module dependency counts.",
669
+ does_not_prove="Does not infer package boundaries from directory names without manifest evidence.",
670
+ useful_situations=(
671
+ "Understanding overall system structure, entrypoints, and monorepo package dependencies",
672
+ ),
673
+ avoid_when=(
674
+ "Fixing a localized bug in a single already-known function",
675
+ ),
676
+ profiles=("agent", "core", "graph", "minimal", "developer", "full"),
677
+ minimal_invocation="get_architecture()",
678
+ advanced_invocation="get_architecture()",
679
+ result_fields=("status", "modules", "packages", "package_dependencies", "entrypoints", "routes", "summary"),
680
+ typical_followup="get_context(intent='ARCHITECTURE') or get_imports/get_dependents",
681
+ common_mistakes=(
682
+ "Inferring package boundaries from folder names like apps/ or packages/ when get_architecture shows no manifest",
683
+ ),
684
+ example_request="How is this monorepo structured and what are the workspace packages?",
685
+ example_call="get_architecture()",
686
+ example_interpretation="Inspect packages, package_dependencies, and entrypoints for manifest-verified boundaries.",
687
+ ),
688
+ # 13. get_git_impact
689
+ ToolCapabilitySpec(
690
+ tool_name="get_git_impact",
691
+ capability="change_impact_analysis",
692
+ description=(
693
+ "Compute deterministic change impact between Git refs (`base`..`head`), including modified symbols, downstream callers, affected packages, and covering tests. "
694
+ "Use for PR review, regression analysis, and pre-commit blast-radius checks. "
695
+ "Does not execute tests; reports static test-to-symbol coverage edges."
696
+ ),
697
+ task_types=(
698
+ AgentTaskCategory.CHANGE_IMPACT.value,
699
+ AgentTaskCategory.TEST_DISCOVERY.value,
700
+ AgentTaskCategory.DEBUG.value,
701
+ ),
702
+ required_inputs=(),
703
+ optional_inputs=("base", "head"),
704
+ expected_output_type="GitImpactResult",
705
+ relationship_types_returned=("CALLS", "TESTS", "TESTS_SYMBOL", "DEPENDS_ON_PACKAGE", "IMPORTS"),
706
+ evidence_guarantees="Git diff line mapping intersected with AST symbol spans and graph dependents.",
707
+ does_not_prove="Does not prove whether tests pass or fail at runtime.",
708
+ useful_situations=(
709
+ "Finding affected callers, packages, and tests after recent commits or before a refactor",
710
+ ),
711
+ avoid_when=(
712
+ "No Git history is relevant and you only need to look up a single symbol definition",
713
+ ),
714
+ profiles=("agent", "core", "graph", "minimal", "developer", "full"),
715
+ minimal_invocation="get_git_impact()",
716
+ advanced_invocation='get_git_impact(base="HEAD~3", head="HEAD")',
717
+ result_fields=("status", "base", "head", "changed_files", "modified_symbols", "impacted_callers", "affected_packages", "related_tests"),
718
+ typical_followup="find_tests or get_file on modified symbols",
719
+ common_mistakes=(
720
+ "Assuming get_git_impact runs pytest; it computes static impact and test selection",
721
+ ),
722
+ example_request="What symbols, packages, and tests are impacted by the latest commit?",
723
+ example_call='get_git_impact(base="HEAD~1", head="HEAD")',
724
+ example_interpretation="Inspect modified_symbols, impacted_callers, affected_packages, and related_tests.",
725
+ ),
726
+ # 14. get_context
727
+ ToolCapabilitySpec(
728
+ tool_name="get_context",
729
+ capability="bounded_repository_context",
730
+ description=(
731
+ "Compile a token-bounded, coverage-optimized ContextPacket containing verified symbols, compressed relationships, routes, DI providers, packages, and related tests. "
732
+ "Primary input: `query` (natural-language question, symbol, or task description; `task` is also supported as a compatibility alias for string or structured TaskSpec dict). "
733
+ "Budget controls: `max_tokens` (default 4000, hard cap 20000), `max_files` (default 15, hard cap 30), `max_lines` (default 500, hard cap 1500). "
734
+ "Preserves UNKNOWN, POSSIBLE, and AMBIGUOUS states explicitly. "
735
+ "Does not return full raw files; use targeted get_file or read_file only if exact omitted lines are needed afterward."
736
+ ),
737
+ task_types=(
738
+ AgentTaskCategory.DEBUG.value,
739
+ AgentTaskCategory.TRACE.value,
740
+ AgentTaskCategory.CHANGE_IMPACT.value,
741
+ AgentTaskCategory.MULTI_FILE_INVESTIGATION.value,
742
+ AgentTaskCategory.ARCHITECTURE.value,
743
+ AgentTaskCategory.EXPLANATION.value,
744
+ AgentTaskCategory.TEST_DISCOVERY.value,
745
+ AgentTaskCategory.PACKAGE.value,
746
+ ),
747
+ required_inputs=("query",),
748
+ optional_inputs=("task", "intent", "max_tokens", "max_files", "max_lines", "top_k", "plan", "mode", "explain", "resource_mode"),
749
+ expected_output_type="ContextPacket",
750
+ relationship_types_returned=(
751
+ "CALLS",
752
+ "POSSIBLE_CALLS",
753
+ "IMPORTS",
754
+ "HANDLED_BY",
755
+ "MOUNTS",
756
+ "REGISTERS",
757
+ "DISPATCHES_TO",
758
+ "EVENT_LISTENER",
759
+ "TASK_HANDLER",
760
+ "COMMAND_HANDLER",
761
+ "INJECTS",
762
+ "PROVIDES",
763
+ "RESOLVES_DEPENDENCY",
764
+ "CONFIGURES",
765
+ "DEPENDS_ON_PACKAGE",
766
+ "TESTS",
767
+ "TESTS_SYMBOL",
768
+ "TESTS_ROUTE",
769
+ "TESTS_PROVIDER",
770
+ "TESTS_EVENT_HANDLER",
771
+ ),
772
+ evidence_guarantees=(
773
+ "Bounded source snippets, compressed relationships with supporting_locations, "
774
+ "coverage_score, budget metadata (selected_tokens, candidate_tokens, selected_files, selected_lines, truncated), "
775
+ "and explicit UNKNOWN/POSSIBLE/AMBIGUOUS preservation."
776
+ ),
777
+ does_not_prove="Does not execute code or fabricate edges for unresolvable dynamic dispatch.",
778
+ useful_situations=(
779
+ "Investigating a bug, route flow, DI chain, or multi-file feature in one bounded call",
780
+ "Gathering target symbol + callers + callees + related tests + package context within a strict token/file/line budget",
781
+ ),
782
+ avoid_when=(
783
+ "Trivial one-file edit where the exact lines are already known",
784
+ ),
785
+ profiles=("agent", "graph", "minimal", "developer", "full"),
786
+ minimal_invocation='get_context(query="Debug authentication failure in login_endpoint")',
787
+ advanced_invocation='get_context(query="Trace DI chain for get_current_user", intent="DEBUG", max_tokens=4000, max_files=15, max_lines=500, explain=True)',
788
+ result_fields=("symbols", "relationships", "routes", "tests", "packages", "unknowns", "conflicts", "uncertainties", "freshness", "selected_tokens", "candidate_tokens", "selected_files", "selected_lines", "coverage_score", "truncated", "candidate_token_estimate", "selected_token_estimate", "context_reduction_pct", "coverage"),
789
+ typical_followup="get_file on specific line ranges if implementation details outside snippets are required",
790
+ common_mistakes=(
791
+ "Calling get_context multiple times with identical arguments instead of reading the returned ContextPacket",
792
+ "Ignoring the unknowns, conflicts, uncertainties, and truncated fields in the ContextPacket",
793
+ ),
794
+ example_request="How does UserRepository get injected into UserController and what tests cover it?",
795
+ example_call='get_context(query="How does UserRepository get injected into UserController", intent="DEBUG", max_tokens=4000)',
796
+ example_interpretation="Inspect symbols, INJECTS/PROVIDES relationships, tests, selected_tokens/coverage_score/truncated, and any unknowns/conflicts/uncertainties.",
797
+ ),
798
+ # 15. find_related_tests
799
+ ToolCapabilitySpec(
800
+ tool_name="find_related_tests",
801
+ capability="test_discovery",
802
+ description=(
803
+ "Return test files and test functions statically linked to a target symbol (`symbol`; `canonical_id` supported as alias) via direct calls, imports, fixtures, or route invocation. "
804
+ "Use when answering 'Which tests cover symbol X?' before or after a code change. "
805
+ "Never fabricates coverage from lexical similarity alone."
806
+ ),
807
+ task_types=(
808
+ AgentTaskCategory.TEST_DISCOVERY.value,
809
+ AgentTaskCategory.CHANGE_IMPACT.value,
810
+ AgentTaskCategory.DEBUG.value,
811
+ ),
812
+ required_inputs=("symbol",),
813
+ optional_inputs=("canonical_id", "max_results"),
814
+ expected_output_type="RelatedTestsResult",
815
+ relationship_types_returned=("TESTS", "TESTS_SYMBOL", "TESTS_ROUTE", "TESTS_PROVIDER", "TESTS_EVENT_HANDLER", "CALLS", "IMPORTS"),
816
+ evidence_guarantees="AST/fixture/route-verified links from test functions to target symbols.",
817
+ does_not_prove="Does not prove runtime line coverage percentage or assertion completeness.",
818
+ useful_situations=(
819
+ "Selecting the exact pytest/jest test functions to run after modifying a symbol",
820
+ ),
821
+ avoid_when=(
822
+ "Editing documentation or comments with no behavioral impact",
823
+ ),
824
+ profiles=("graph", "developer", "full"),
825
+ minimal_invocation='find_related_tests(symbol="AuthService.authenticate")',
826
+ advanced_invocation='find_related_tests(symbol="login_endpoint", max_results=20)',
827
+ result_fields=("test_symbol", "file", "start_line", "end_line", "relationship", "evidence_class", "confidence", "reason"),
828
+ typical_followup="read_file on the test function if test assertions need inspection",
829
+ common_mistakes=(
830
+ "Assuming empty results mean no dynamic integration test exists if tests invoke external URLs via env vars",
831
+ ),
832
+ example_request="Which tests cover BillingService.charge_card?",
833
+ example_call='find_related_tests(symbol="BillingService.charge_card")',
834
+ example_interpretation="Inspect each returned test symbol, its file path, and the linking relationship (e.g., TESTS_SYMBOL, TESTS_ROUTE).",
835
+ ),
836
+ # 16. analyze_impact
837
+ ToolCapabilitySpec(
838
+ tool_name="analyze_impact",
839
+ capability="symbol_blast_radius",
840
+ description=(
841
+ "Compute downstream callers, dependents, affected routes, and related tests if a symbol (`symbol`; `canonical_id` supported as alias) is modified. "
842
+ "Use before refactoring or changing a function/class signature. "
843
+ "Does not prove runtime failure without inspecting call sites."
844
+ ),
845
+ task_types=(
846
+ AgentTaskCategory.CHANGE_IMPACT.value,
847
+ AgentTaskCategory.TEST_DISCOVERY.value,
848
+ ),
849
+ required_inputs=("symbol",),
850
+ optional_inputs=("canonical_id", "max_depth"),
851
+ expected_output_type="SymbolImpactResult",
852
+ relationship_types_returned=("CALLS", "CALLED_BY", "IMPORTS", "HANDLED_BY", "TESTS", "DEPENDS_ON_PACKAGE"),
853
+ evidence_guarantees="Multi-hop reverse dependency traversal with hop distances and evidence classes.",
854
+ does_not_prove="Does not prove dynamic string-based reflection consumers.",
855
+ useful_situations=(
856
+ "Evaluating blast radius of a signature or behavior change to a symbol",
857
+ ),
858
+ avoid_when=(
859
+ "Adding a brand-new standalone helper with no existing callers",
860
+ ),
861
+ profiles=("graph", "developer", "full"),
862
+ minimal_invocation='analyze_impact(symbol="DatabasePool.acquire")',
863
+ advanced_invocation='analyze_impact(symbol="DatabasePool.acquire", max_depth=4)',
864
+ result_fields=("symbol", "direct_callers", "transitive_callers", "dependent_files", "affected_routes", "related_tests"),
865
+ typical_followup="find_related_tests or read_file on direct_callers",
866
+ common_mistakes=(
867
+ "Modifying a public signature without checking direct_callers and affected_routes",
868
+ ),
869
+ example_request="What breaks if we change DatabasePool.acquire?",
870
+ example_call='analyze_impact(symbol="DatabasePool.acquire", max_depth=3)',
871
+ example_interpretation="Review direct_callers, transitive_callers, affected_routes, and related_tests.",
872
+ ),
873
+ # 17. read_file
874
+ ToolCapabilitySpec(
875
+ tool_name="read_file",
876
+ capability="targeted_source_read",
877
+ description=(
878
+ "Read a bounded line range (1..500 lines) of a non-sensitive repository file. "
879
+ "Use AFTER CodeGraph tools identify the exact file and line span, or directly for trivial single-file edits. "
880
+ "Blocks path traversal and sensitive credential files. Does not compute cross-file relationships."
881
+ ),
882
+ task_types=(
883
+ AgentTaskCategory.LOCAL_EDIT.value,
884
+ AgentTaskCategory.DEBUG.value,
885
+ AgentTaskCategory.EXPLANATION.value,
886
+ ),
887
+ required_inputs=("path",),
888
+ optional_inputs=("start_line", "end_line"),
889
+ expected_output_type="FileContentSlice",
890
+ relationship_types_returned=(),
891
+ evidence_guarantees="Exact on-disk source lines within repository privacy boundaries.",
892
+ does_not_prove="Does not compute cross-file callers, DI bindings, or test links.",
893
+ useful_situations=(
894
+ "Inspecting exact implementation lines inside a symbol span identified by CodeGraph",
895
+ "Performing a simple single-file edit",
896
+ ),
897
+ avoid_when=(
898
+ "Scanning dozens of files blindly instead of calling resolve_symbol or get_context first",
899
+ ),
900
+ profiles=("minimal", "developer", "full"),
901
+ minimal_invocation='read_file(path="src/auth/service.py", start_line=1, end_line=60)',
902
+ advanced_invocation='read_file(path="src/auth/service.py", start_line=40, end_line=95)',
903
+ result_fields=("file", "start_line", "end_line", "content"),
904
+ typical_followup="perform code edit or synthesize final answer",
905
+ common_mistakes=(
906
+ "Requesting >500 lines or attempting to read .env / secret key files (blocked by security policy)",
907
+ ),
908
+ example_request="Read lines 10 to 45 of src/auth/service.py.",
909
+ example_call='read_file(path="src/auth/service.py", start_line=10, end_line=45)',
910
+ example_interpretation="Inspect exact implementation statements in content for the requested line span.",
911
+ ),
912
+ # 18. search_code
913
+ ToolCapabilitySpec(
914
+ tool_name="search_code",
915
+ capability="lexical_code_search",
916
+ description=(
917
+ "Search literal text, HTML/Jinja template IDs, UI strings, CSS selectors, config keys, or route paths across readable repository files and indexed code chunks. "
918
+ "Use instead of grep when searching for exact text, button labels, or template strings. "
919
+ "Does not prove semantic call, import, or route relationships; never creates semantic graph edges from text matches."
920
+ ),
921
+ task_types=(
922
+ AgentTaskCategory.MULTI_FILE_INVESTIGATION.value,
923
+ AgentTaskCategory.DEBUG.value,
924
+ ),
925
+ required_inputs=("query",),
926
+ optional_inputs=("top_k", "path_filter", "file_types", "include_tests", "include_configs", "max_results"),
927
+ expected_output_type="CodeChunkSearchResult",
928
+ relationship_types_returned=(),
929
+ evidence_guarantees="Ranked source and text matches with file path, exact line number, matched_text, snippet, match_type, and file_category.",
930
+ does_not_prove="Does not prove module import graphs or caller/callee relationships; never creates semantic edges.",
931
+ useful_situations=(
932
+ "Locating UI button labels, HTML/Jinja template IDs, CSS selectors, error message strings, SQL fragments, or configuration keys",
933
+ ),
934
+ avoid_when=(
935
+ "Answering who calls a function or what a module imports (use find_callers, get_callers, or get_imports)",
936
+ ),
937
+ profiles=("agent", "minimal", "developer", "full"),
938
+ minimal_invocation='search_code(query="Add Manual Entry")',
939
+ advanced_invocation='search_code(query="Add Manual Entry", file_types=["html", "jinja2"], max_results=10)',
940
+ result_fields=("path", "file", "line", "start_line", "end_line", "matched_text", "snippet", "category", "file_category", "match_type", "symbol", "score", "reason"),
941
+ typical_followup="get_file on the matched path and line range, or find_symbol on enclosing symbol",
942
+ common_mistakes=(
943
+ "Using search_code instead of find_callers/get_callers for structural symbol relationship queries",
944
+ ),
945
+ example_request="Where is the 'Add Manual Entry' button in HTML?",
946
+ example_call='search_code(query="Add Manual Entry", top_k=10)',
947
+ example_interpretation="Inspect the matched `path`, `line`, `matched_text`, and `snippet`, then open the surrounding lines with `get_file`.",
948
+ ),
949
+ # 19. find_symbol
950
+ ToolCapabilitySpec(
951
+ tool_name="find_symbol",
952
+ capability="exact_symbol_lookup",
953
+ description=(
954
+ "Find function, method, or class definitions by exact short name or qualified name (`symbol`; `name` and `canonical_id` supported as aliases). "
955
+ "Use instead of grep when locating where a class, function, or method is defined. "
956
+ "Does not return callers, callees, or ambiguity alternatives like resolve_symbol."
957
+ ),
958
+ task_types=(
959
+ AgentTaskCategory.SYMBOL_LOOKUP.value,
960
+ ),
961
+ required_inputs=("symbol",),
962
+ optional_inputs=("name", "canonical_id"),
963
+ expected_output_type="SymbolLocationList",
964
+ relationship_types_returned=("DEFINES",),
965
+ evidence_guarantees="AST-indexed symbol rows matching name or qualified_name.",
966
+ does_not_prove="Does not disambiguate routes or compute call relationships.",
967
+ useful_situations=(
968
+ "Quickly listing all definitions matching an exact symbol name",
969
+ ),
970
+ avoid_when=(
971
+ "Searching for literal UI strings or HTML template text (use search_code instead)",
972
+ ),
973
+ profiles=("agent", "minimal", "developer", "full"),
974
+ minimal_invocation='find_symbol(symbol="AuthService")',
975
+ advanced_invocation='find_symbol(symbol="src.auth.service.AuthService")',
976
+ result_fields=("symbol", "kind", "file", "start_line", "end_line"),
977
+ typical_followup="get_symbol, get_file, or find_callers",
978
+ common_mistakes=(
979
+ "Using find_symbol with partial substrings (use search_symbols or search_code for partial matching)",
980
+ ),
981
+ example_request="Where is InventoryService defined?",
982
+ example_call='find_symbol(symbol="InventoryService")',
983
+ example_interpretation="Inspect file and start_line..end_line for each definition.",
984
+ ),
985
+ # 20. find_references
986
+ ToolCapabilitySpec(
987
+ tool_name="find_references",
988
+ capability="textual_chunk_references",
989
+ description=(
990
+ "Find textual occurrences and references of a symbol name (`symbol`; `name` and `canonical_id` supported as aliases) across indexed code chunks. "
991
+ "Use when locating references or mentions of an identifier across files. "
992
+ "Does not prove semantic call edges; use get_references when full relationship classification is required."
993
+ ),
994
+ task_types=(
995
+ AgentTaskCategory.RELATIONSHIP.value,
996
+ AgentTaskCategory.MULTI_FILE_INVESTIGATION.value,
997
+ ),
998
+ required_inputs=("symbol",),
999
+ optional_inputs=("name", "canonical_id"),
1000
+ expected_output_type="ChunkReferenceList",
1001
+ relationship_types_returned=(),
1002
+ evidence_guarantees="Lexical substring matches inside indexed code chunks.",
1003
+ does_not_prove="Does not prove AST-verified call or reference semantics.",
1004
+ useful_situations=(
1005
+ "Finding all code chunks referencing an identifier across the repository",
1006
+ ),
1007
+ avoid_when=(
1008
+ "Searching for arbitrary non-symbol UI text in HTML templates (use search_code instead)",
1009
+ ),
1010
+ profiles=("agent", "developer", "full"),
1011
+ minimal_invocation='find_references(symbol="parse_bill")',
1012
+ advanced_invocation='find_references(symbol="FEATURE_FLAG_X")',
1013
+ result_fields=("file", "symbol", "start_line", "end_line"),
1014
+ typical_followup="get_file on matched chunks to verify context",
1015
+ common_mistakes=(
1016
+ "Treating find_references chunk hits as verified CALLS edges",
1017
+ ),
1018
+ example_request="Find all references to parse_bill.",
1019
+ example_call='find_references(symbol="parse_bill")',
1020
+ example_interpretation="Inspect matched files and line ranges as reference sites.",
1021
+ ),
1022
+ # 21. find_callers
1023
+ ToolCapabilitySpec(
1024
+ tool_name="find_callers",
1025
+ capability="direct_caller_query",
1026
+ description=(
1027
+ "Find functions and methods that call the specified symbol (`symbol`; `canonical_id` supported as alias) up to `max_results`. "
1028
+ "Use instead of grep or search_code when answering 'who calls X?'. "
1029
+ "Does not prove runtime execution of conditional branches; preserves POSSIBLE and UNKNOWN evidence classes."
1030
+ ),
1031
+ task_types=(
1032
+ AgentTaskCategory.RELATIONSHIP.value,
1033
+ AgentTaskCategory.DEBUG.value,
1034
+ ),
1035
+ required_inputs=("symbol",),
1036
+ optional_inputs=("canonical_id", "max_results"),
1037
+ expected_output_type="CallerEdgeList",
1038
+ relationship_types_returned=("CALLS", "POSSIBLE_CALLS"),
1039
+ evidence_guarantees="Graph edges with relationship, confidence, evidence_class, and call-site coordinates.",
1040
+ does_not_prove="Does not prove runtime execution or dynamic reflection calls.",
1041
+ useful_situations=(
1042
+ "Answering 'Who calls function X?' with a bounded result list",
1043
+ ),
1044
+ avoid_when=(
1045
+ "Single-file local edits",
1046
+ ),
1047
+ profiles=("agent", "developer", "full"),
1048
+ minimal_invocation='find_callers(symbol="place_order")',
1049
+ advanced_invocation='find_callers(symbol="verify_password", max_results=50)',
1050
+ result_fields=("source", "target", "relationship", "confidence", "evidence_class", "file", "start_line"),
1051
+ typical_followup="get_file or trace_path",
1052
+ common_mistakes=(
1053
+ "Ignoring evidence_class on POSSIBLE_CALLS rows",
1054
+ ),
1055
+ example_request="Who calls place_order?",
1056
+ example_call='find_callers(symbol="place_order", max_results=20)',
1057
+ example_interpretation="Inspect each caller's source symbol, file, line, and evidence_class.",
1058
+ ),
1059
+ # 22. find_callees
1060
+ ToolCapabilitySpec(
1061
+ tool_name="find_callees",
1062
+ capability="direct_callee_query",
1063
+ description=(
1064
+ "Find functions and methods called by the specified symbol (`symbol`; `canonical_id` supported as alias) up to `max_results`. "
1065
+ "Use instead of reading multiple files manually when answering 'what does X call?'. "
1066
+ "Does not resolve opaque runtime callbacks; preserves POSSIBLE and UNKNOWN evidence classes."
1067
+ ),
1068
+ task_types=(
1069
+ AgentTaskCategory.RELATIONSHIP.value,
1070
+ AgentTaskCategory.DEBUG.value,
1071
+ ),
1072
+ required_inputs=("symbol",),
1073
+ optional_inputs=("canonical_id", "max_results"),
1074
+ expected_output_type="CalleeEdgeList",
1075
+ relationship_types_returned=("CALLS", "POSSIBLE_CALLS"),
1076
+ evidence_guarantees="Outgoing call edges with target, confidence, evidence_class, and line numbers.",
1077
+ does_not_prove="Does not prove external service behavior or dynamic eval calls.",
1078
+ useful_situations=(
1079
+ "Answering 'What does function X call?' with a bounded result list",
1080
+ ),
1081
+ avoid_when=(
1082
+ "Single-file local edits",
1083
+ ),
1084
+ profiles=("agent", "developer", "full"),
1085
+ minimal_invocation='find_callees(symbol="place_order")',
1086
+ advanced_invocation='find_callees(symbol="login_endpoint", max_results=30)',
1087
+ result_fields=("source", "target", "relationship", "confidence", "evidence_class", "file", "start_line"),
1088
+ typical_followup="get_symbol or get_file on target callees",
1089
+ common_mistakes=(
1090
+ "Treating POSSIBLE_CALLS as guaranteed execution",
1091
+ ),
1092
+ example_request="What does place_order call?",
1093
+ example_call='find_callees(symbol="place_order", max_results=20)',
1094
+ example_interpretation="Inspect each outgoing call edge's target and evidence_class.",
1095
+ ),
1096
+ # 23. get_call_graph
1097
+ ToolCapabilitySpec(
1098
+ tool_name="get_call_graph",
1099
+ capability="neighborhood_call_graph",
1100
+ description=(
1101
+ "Compute the multi-hop call graph rooted at `symbol` (`canonical_id` supported as alias) up to `depth` and `max_results`. "
1102
+ "Use when exploring the multi-hop call neighborhood around a central service or controller. "
1103
+ "Does not prove runtime reachability for specific inputs."
1104
+ ),
1105
+ task_types=(
1106
+ AgentTaskCategory.RELATIONSHIP.value,
1107
+ AgentTaskCategory.TRACE.value,
1108
+ AgentTaskCategory.ARCHITECTURE.value,
1109
+ ),
1110
+ required_inputs=("symbol",),
1111
+ optional_inputs=("canonical_id", "depth", "max_results"),
1112
+ expected_output_type="CallGraphEdgeList",
1113
+ relationship_types_returned=("CALLS", "POSSIBLE_CALLS", "DISPATCHES_TO", "HANDLED_BY"),
1114
+ evidence_guarantees="Bounded multi-hop subgraph edges with hop depth and evidence metadata.",
1115
+ does_not_prove="Does not prove that every branch in the call graph executes on a single request.",
1116
+ useful_situations=(
1117
+ "Mapping 2-hop or 3-hop call neighborhoods around a core function",
1118
+ ),
1119
+ avoid_when=(
1120
+ "You only need direct 1-hop callers (use get_callers) or a path between two known endpoints (use trace_path)",
1121
+ ),
1122
+ profiles=("graph", "developer", "full"),
1123
+ minimal_invocation='get_call_graph(symbol="AuthService.authenticate")',
1124
+ advanced_invocation='get_call_graph(symbol="AuthService.authenticate", depth=3, max_results=50)',
1125
+ result_fields=("source", "target", "relationship", "confidence", "evidence_class", "depth", "file", "start_line"),
1126
+ typical_followup="trace_path or read_file",
1127
+ common_mistakes=(
1128
+ "Requesting large depth values when depth=2 already captures the relevant neighborhood",
1129
+ ),
1130
+ example_request="Show the 2-hop call graph around AuthService.authenticate.",
1131
+ example_call='get_call_graph(symbol="AuthService.authenticate", depth=2, max_results=50)',
1132
+ example_interpretation="Inspect the returned edges by hop depth and relationship type.",
1133
+ ),
1134
+ # 24. get_dependency_graph
1135
+ ToolCapabilitySpec(
1136
+ tool_name="get_dependency_graph",
1137
+ capability="module_dependency_graph",
1138
+ description=(
1139
+ "Return module-level `IMPORTS` graph edges across the repository or filtered to `file_path`. "
1140
+ "Use when inspecting raw module-to-module import topology. "
1141
+ "Does not prove symbol-level call invocation; prefer get_imports or get_dependents for targeted queries."
1142
+ ),
1143
+ task_types=(
1144
+ AgentTaskCategory.ARCHITECTURE.value,
1145
+ AgentTaskCategory.PACKAGE.value,
1146
+ ),
1147
+ required_inputs=(),
1148
+ optional_inputs=("file_path",),
1149
+ expected_output_type="DependencyGraphEdgeList",
1150
+ relationship_types_returned=("IMPORTS",),
1151
+ evidence_guarantees="AST-extracted module IMPORTS edges with source, target, and confidence.",
1152
+ does_not_prove="Does not prove whether imported symbols are called.",
1153
+ useful_situations=(
1154
+ "Inspecting module import edges for a specific file or across a small repository",
1155
+ ),
1156
+ avoid_when=(
1157
+ "Querying a single file's imports (prefer get_imports)",
1158
+ ),
1159
+ profiles=("full",),
1160
+ minimal_invocation='get_dependency_graph(file_path="src/auth/service.py")',
1161
+ advanced_invocation="get_dependency_graph()",
1162
+ result_fields=("source", "target", "relationship", "confidence", "evidence"),
1163
+ typical_followup="get_architecture or get_dependents",
1164
+ common_mistakes=(
1165
+ "Calling get_dependency_graph() without file_path when only one module's imports are needed",
1166
+ ),
1167
+ example_request="Show import graph edges originating from src/auth/service.py.",
1168
+ example_call='get_dependency_graph(file_path="src/auth/service.py")',
1169
+ example_interpretation="Inspect each IMPORTS edge from source to target module.",
1170
+ ),
1171
+ # 25. compile_task
1172
+ ToolCapabilitySpec(
1173
+ tool_name="compile_task",
1174
+ capability="task_normalization_and_planning",
1175
+ description=(
1176
+ "Normalize a developer question or task (`query`; `task` supported as string or TaskSpec dict alias) into a structured `TaskSpec`, ground targets against the index, detect ambiguities, and build a `RetrievalPlan`. "
1177
+ "Use before get_context when you want to inspect target resolution and ambiguity candidates prior to context retrieval. "
1178
+ "Does not retrieve source code snippets."
1179
+ ),
1180
+ task_types=(
1181
+ AgentTaskCategory.MULTI_FILE_INVESTIGATION.value,
1182
+ AgentTaskCategory.DEBUG.value,
1183
+ AgentTaskCategory.TRACE.value,
1184
+ ),
1185
+ required_inputs=("query",),
1186
+ optional_inputs=("task", "max_tokens", "resource_mode"),
1187
+ expected_output_type="CompiledTaskPlan",
1188
+ relationship_types_returned=(),
1189
+ evidence_guarantees="Deterministic TaskSpec, ambiguity list, candidate entrypoints, and RetrievalPlan.",
1190
+ does_not_prove="Does not return symbol bodies or relationship edges until get_context is called.",
1191
+ useful_situations=(
1192
+ "Pre-checking whether task targets are ambiguous before compiling a full ContextPacket",
1193
+ ),
1194
+ avoid_when=(
1195
+ "A direct call to get_context or resolve_symbol is sufficient",
1196
+ ),
1197
+ profiles=("minimal", "developer", "full"),
1198
+ minimal_invocation='compile_task(query="Trace login route to database")',
1199
+ advanced_invocation='compile_task(query="Debug AuthService", max_tokens=15000, resource_mode="CONSERVATIVE")',
1200
+ result_fields=("task_spec", "ambiguity", "ambiguities", "entry_points", "retrieval_plan", "recommended_next_step"),
1201
+ typical_followup="get_context(query=..., plan=retrieval_plan)",
1202
+ common_mistakes=(
1203
+ "Stopping after compile_task without calling get_context to retrieve actual evidence",
1204
+ ),
1205
+ example_request="Compile a retrieval plan for debugging AuthService.",
1206
+ example_call='compile_task(query="Debug AuthService")',
1207
+ example_interpretation="Check ambiguities and entry_points, then pass the query or plan to get_context.",
1208
+ ),
1209
+ # 26. plan_retrieval
1210
+ ToolCapabilitySpec(
1211
+ tool_name="plan_retrieval",
1212
+ capability="retrieval_plan_preview",
1213
+ description=(
1214
+ "Construct a deterministic `RetrievalPlan` for a question or task (`query`; `task` supported as alias) without executing graph or source retrieval. "
1215
+ "Use to inspect planned retrieval steps, token budgets, and query expansions. "
1216
+ "Does not return code snippets or relationship edges."
1217
+ ),
1218
+ task_types=(
1219
+ AgentTaskCategory.MULTI_FILE_INVESTIGATION.value,
1220
+ AgentTaskCategory.DIAGNOSTIC.value,
1221
+ ),
1222
+ required_inputs=("query",),
1223
+ optional_inputs=("task", "max_tokens", "resource_mode"),
1224
+ expected_output_type="RetrievalPlanDict",
1225
+ relationship_types_returned=(),
1226
+ evidence_guarantees="Deterministic step list, token budget allocation, and expanded queries.",
1227
+ does_not_prove="Does not execute retrieval or prove symbol relationships.",
1228
+ useful_situations=(
1229
+ "Inspecting how CodeGraph plans to allocate token budget for a complex query",
1230
+ ),
1231
+ avoid_when=(
1232
+ "Normal interactive tasks where get_context can be called directly",
1233
+ ),
1234
+ profiles=("developer", "full"),
1235
+ minimal_invocation='plan_retrieval(query="Explain payment processing flow")',
1236
+ advanced_invocation='plan_retrieval(query="Explain payment flow", max_tokens=10000, resource_mode="BALANCED")',
1237
+ result_fields=("intent", "steps", "token_budget", "expanded_queries", "ambiguities"),
1238
+ typical_followup="get_context",
1239
+ common_mistakes=(
1240
+ "Calling both plan_retrieval and compile_task redundantly before get_context",
1241
+ ),
1242
+ example_request="Preview the retrieval plan for tracing payment processing.",
1243
+ example_call='plan_retrieval(query="Trace payment processing")',
1244
+ example_interpretation="Inspect steps and expanded_queries, then invoke get_context.",
1245
+ ),
1246
+ # 27. get_recent_changes
1247
+ ToolCapabilitySpec(
1248
+ tool_name="get_recent_changes",
1249
+ capability="git_diff_file_list",
1250
+ description=(
1251
+ "Return repository files modified between two Git refs (`since`..`until`) in a read-only sandboxed query. "
1252
+ "Use when reviewing recent commits or identifying which files changed before running impact analysis. "
1253
+ "Does not compute downstream symbol callers; use get_git_impact for symbol-level blast radius."
1254
+ ),
1255
+ task_types=(
1256
+ AgentTaskCategory.CHANGE_IMPACT.value,
1257
+ AgentTaskCategory.DEBUG.value,
1258
+ ),
1259
+ required_inputs=(),
1260
+ optional_inputs=("since", "until"),
1261
+ expected_output_type="GitChangedFilesList",
1262
+ relationship_types_returned=(),
1263
+ evidence_guarantees="Git diff status and relative file paths between refs.",
1264
+ does_not_prove="Does not compute symbol-level callers or covering tests (use get_git_impact).",
1265
+ useful_situations=(
1266
+ "Listing files touched in the last N commits during debugging or code review",
1267
+ ),
1268
+ avoid_when=(
1269
+ "You need symbol-level callers and affected tests (prefer get_git_impact)",
1270
+ ),
1271
+ profiles=("developer", "full"),
1272
+ minimal_invocation="get_recent_changes()",
1273
+ advanced_invocation='get_recent_changes(since="HEAD~5", until="HEAD")',
1274
+ result_fields=("path", "status"),
1275
+ typical_followup="get_git_impact or get_file",
1276
+ common_mistakes=(
1277
+ "Calling get_recent_changes and manually reading every changed file instead of calling get_git_impact",
1278
+ ),
1279
+ example_request="Which files changed in the last 5 commits?",
1280
+ example_call='get_recent_changes(since="HEAD~5", until="HEAD")',
1281
+ example_interpretation="Inspect each changed file's path and change status.",
1282
+ ),
1283
+ # 28. get_file_history
1284
+ ToolCapabilitySpec(
1285
+ tool_name="get_file_history",
1286
+ capability="git_file_commit_history",
1287
+ description=(
1288
+ "Return recent Git commits touching a specific repository file (`path`, up to `n` commits). "
1289
+ "Use when investigating when and why a specific file was recently modified. "
1290
+ "Does not return full commit diffs or cross-file dependency impact."
1291
+ ),
1292
+ task_types=(
1293
+ AgentTaskCategory.DEBUG.value,
1294
+ AgentTaskCategory.CHANGE_IMPACT.value,
1295
+ ),
1296
+ required_inputs=("path",),
1297
+ optional_inputs=("n",),
1298
+ expected_output_type="FileCommitHistoryList",
1299
+ relationship_types_returned=(),
1300
+ evidence_guarantees="Commit hashes, authors, timestamps, and commit subjects touching `path`.",
1301
+ does_not_prove="Does not prove which specific symbol inside the file caused a regression.",
1302
+ useful_situations=(
1303
+ "Checking recent commit history on a buggy file",
1304
+ ),
1305
+ avoid_when=(
1306
+ "Static symbol relationships or current source code are sufficient",
1307
+ ),
1308
+ profiles=("developer", "full"),
1309
+ minimal_invocation='get_file_history(path="src/auth/service.py")',
1310
+ advanced_invocation='get_file_history(path="src/auth/service.py", n=5)',
1311
+ result_fields=("commit", "author", "date", "subject"),
1312
+ typical_followup="get_git_impact(base=commit, head='HEAD')",
1313
+ common_mistakes=(
1314
+ "Passing an absolute path outside the repository",
1315
+ ),
1316
+ example_request="Show the last 5 commits touching src/auth/service.py.",
1317
+ example_call='get_file_history(path="src/auth/service.py", n=5)',
1318
+ example_interpretation="Inspect the commit hashes and subjects to identify relevant recent changes.",
1319
+ ),
1320
+ # 29. analyze_change_impact
1321
+ ToolCapabilitySpec(
1322
+ tool_name="analyze_change_impact",
1323
+ capability="git_range_change_impact",
1324
+ description=(
1325
+ "Analyze changed files, modified symbols, downstream callers, and related tests between Git refs `since` and `until`. "
1326
+ "Use for commit-range impact analysis when `since`/`until` parameter naming is preferred. "
1327
+ "Does not execute tests at runtime."
1328
+ ),
1329
+ task_types=(
1330
+ AgentTaskCategory.CHANGE_IMPACT.value,
1331
+ AgentTaskCategory.TEST_DISCOVERY.value,
1332
+ ),
1333
+ required_inputs=(),
1334
+ optional_inputs=("since", "until"),
1335
+ expected_output_type="ChangeImpactReport",
1336
+ relationship_types_returned=("CALLS", "TESTS", "IMPORTS"),
1337
+ evidence_guarantees="Git diff mapped to indexed symbols, callers, and related tests.",
1338
+ does_not_prove="Does not prove runtime test pass/fail status.",
1339
+ useful_situations=(
1340
+ "Analyzing blast radius of commits between `since` and `until`",
1341
+ ),
1342
+ avoid_when=(
1343
+ "Analyzing impact of a hypothetical edit not yet in Git (use analyze_impact instead)",
1344
+ ),
1345
+ profiles=("developer", "full"),
1346
+ minimal_invocation="analyze_change_impact()",
1347
+ advanced_invocation='analyze_change_impact(since="HEAD~2", until="HEAD")',
1348
+ result_fields=("changed_files", "modified_symbols", "callers", "related_tests"),
1349
+ typical_followup="find_related_tests or read_file",
1350
+ common_mistakes=(
1351
+ "Calling both get_git_impact and analyze_change_impact on the same commit range",
1352
+ ),
1353
+ example_request="Analyze change impact between HEAD~2 and HEAD.",
1354
+ example_call='analyze_change_impact(since="HEAD~2", until="HEAD")',
1355
+ example_interpretation="Inspect modified_symbols, downstream callers, and related_tests.",
1356
+ ),
1357
+ # 30. get_repository_status
1358
+ ToolCapabilitySpec(
1359
+ tool_name="get_repository_status",
1360
+ capability="index_status_and_freshness",
1361
+ description=(
1362
+ "Return repository indexing status, generation counter, freshness state (`FRESH` or `STALE`), symbol counts, and modified/deleted file lists. "
1363
+ "Use to check whether the index is fresh before trusting cached graph facts after disk edits. "
1364
+ "Does not re-index the repository automatically."
1365
+ ),
1366
+ task_types=(
1367
+ AgentTaskCategory.DIAGNOSTIC.value,
1368
+ ),
1369
+ required_inputs=(),
1370
+ optional_inputs=(),
1371
+ expected_output_type="RepositoryStatusReport",
1372
+ relationship_types_returned=(),
1373
+ evidence_guarantees="On-disk mtime/hash comparison against indexed file records.",
1374
+ does_not_prove="Does not prove external git remote state.",
1375
+ useful_situations=(
1376
+ "Checking if freshness is FRESH or STALE after editing files",
1377
+ "Inspecting total indexed files, symbols, graph_edges, and framework_routes",
1378
+ ),
1379
+ avoid_when=(
1380
+ "Normal read-only queries when freshness is already included in tool responses",
1381
+ ),
1382
+ profiles=("minimal", "developer", "full"),
1383
+ minimal_invocation="get_repository_status()",
1384
+ advanced_invocation="get_repository_status()",
1385
+ result_fields=("repository", "generation", "freshness", "freshness_detail", "files_indexed", "symbols_indexed", "graph_edges", "framework_routes", "modified_files", "deleted_files", "parse_failed_files"),
1386
+ typical_followup="read_file on modified_files if freshness is STALE",
1387
+ common_mistakes=(
1388
+ "Presenting cached graph edges from modified_files as current truth when freshness == 'STALE'",
1389
+ ),
1390
+ example_request="Is the CodeGraph index fresh and how many symbols are indexed?",
1391
+ example_call="get_repository_status()",
1392
+ example_interpretation="Check freshness ('FRESH' vs 'STALE'), symbols_indexed, and modified_files.",
1393
+ ),
1394
+ # 31. trace_call
1395
+ ToolCapabilitySpec(
1396
+ tool_name="trace_call",
1397
+ capability="directional_call_traversal",
1398
+ description=(
1399
+ "Traverse callers, callees, or both from a single `symbol` (`canonical_id` supported as alias) up to `depth` with confidence and relationship labels. "
1400
+ "Use when exploring upstream and/or downstream call trees from one symbol without a known second endpoint. "
1401
+ "Does not prove runtime branch execution."
1402
+ ),
1403
+ task_types=(
1404
+ AgentTaskCategory.TRACE.value,
1405
+ AgentTaskCategory.RELATIONSHIP.value,
1406
+ ),
1407
+ required_inputs=("symbol",),
1408
+ optional_inputs=("canonical_id", "depth", "callers", "callees", "both"),
1409
+ expected_output_type="DirectionalCallTraceList",
1410
+ relationship_types_returned=("CALLS", "CALLED_BY", "POSSIBLE_CALLS"),
1411
+ evidence_guarantees="Directional multi-hop call traversal with hop depth and confidence.",
1412
+ does_not_prove="Does not prove runtime reachability under specific input conditions.",
1413
+ useful_situations=(
1414
+ "Tracing 2 hops of upstream callers (`callers=True`) or downstream callees (`callees=True`) from one symbol",
1415
+ ),
1416
+ avoid_when=(
1417
+ "Both start and target symbols are known (prefer trace_path)",
1418
+ ),
1419
+ profiles=("developer", "full"),
1420
+ minimal_invocation='trace_call(symbol="verify_password", depth=2, callers=True)',
1421
+ advanced_invocation='trace_call(symbol="AuthService.authenticate", depth=2, both=True)',
1422
+ result_fields=("source", "target", "relationship", "confidence", "evidence_class", "depth", "file", "start_line"),
1423
+ typical_followup="read_file on specific hops",
1424
+ common_mistakes=(
1425
+ "Using trace_call when trace_path(from_symbol, to_symbol) would directly connect two known symbols",
1426
+ ),
1427
+ example_request="Trace 2 hops of upstream callers for verify_password.",
1428
+ example_call='trace_call(symbol="verify_password", depth=2, callers=True)',
1429
+ example_interpretation="Inspect each hop's source, target, depth, and evidence_class.",
1430
+ ),
1431
+ # 32. get_project_structure
1432
+ ToolCapabilitySpec(
1433
+ tool_name="get_project_structure",
1434
+ capability="indexed_file_list",
1435
+ description=(
1436
+ "Return up to 500 indexed repository-relative source file paths in deterministic sorted order. "
1437
+ "Use for a flat inventory of indexed files when exploring a small project. "
1438
+ "Does not return package dependency graphs or entrypoints (prefer get_architecture)."
1439
+ ),
1440
+ task_types=(
1441
+ AgentTaskCategory.ARCHITECTURE.value,
1442
+ ),
1443
+ required_inputs=(),
1444
+ optional_inputs=(),
1445
+ expected_output_type="IndexedFilePathList",
1446
+ relationship_types_returned=(),
1447
+ evidence_guarantees="Sorted list of non-sensitive indexed file paths.",
1448
+ does_not_prove="Does not prove package ownership or module relationships.",
1449
+ useful_situations=(
1450
+ "Listing indexed file paths in a small repository",
1451
+ ),
1452
+ avoid_when=(
1453
+ "Understanding architecture, entrypoints, or monorepo packages (prefer get_architecture)",
1454
+ ),
1455
+ profiles=("full",),
1456
+ minimal_invocation="get_project_structure()",
1457
+ advanced_invocation="get_project_structure()",
1458
+ result_fields=("path",),
1459
+ typical_followup="get_file or get_architecture",
1460
+ common_mistakes=(
1461
+ "Calling get_project_structure instead of get_architecture for architectural questions",
1462
+ ),
1463
+ example_request="List all indexed file paths in the repository.",
1464
+ example_call="get_project_structure()",
1465
+ example_interpretation="Inspect the returned list of relative POSIX file paths.",
1466
+ ),
1467
+ # 33. get_dependencies
1468
+ ToolCapabilitySpec(
1469
+ tool_name="get_dependencies",
1470
+ capability="raw_import_table_dump",
1471
+ description=(
1472
+ "List up to 500 raw parser-extracted `(source_path, imported)` rows from the imports table. "
1473
+ "Use for bulk inspection of raw import statements across the repository. "
1474
+ "Does not resolve workspace package boundaries; prefer get_imports or get_architecture."
1475
+ ),
1476
+ task_types=(
1477
+ AgentTaskCategory.ARCHITECTURE.value,
1478
+ AgentTaskCategory.PACKAGE.value,
1479
+ ),
1480
+ required_inputs=(),
1481
+ optional_inputs=(),
1482
+ expected_output_type="RawImportRowList",
1483
+ relationship_types_returned=("IMPORTS",),
1484
+ evidence_guarantees="AST-extracted import strings per source file.",
1485
+ does_not_prove="Does not prove installed third-party package versions.",
1486
+ useful_situations=(
1487
+ "Auditing raw import strings across the repository",
1488
+ ),
1489
+ avoid_when=(
1490
+ "Inspecting imports of a single file (prefer get_imports)",
1491
+ ),
1492
+ profiles=("full",),
1493
+ minimal_invocation="get_dependencies()",
1494
+ advanced_invocation="get_dependencies()",
1495
+ result_fields=("source_path", "imported"),
1496
+ typical_followup="get_imports",
1497
+ common_mistakes=(
1498
+ "Using get_dependencies when get_imports(file=...) is much more targeted",
1499
+ ),
1500
+ example_request="List raw import statements across indexed files.",
1501
+ example_call="get_dependencies()",
1502
+ example_interpretation="Inspect source_path and imported module names.",
1503
+ ),
1504
+ # 34. get_file_symbols
1505
+ ToolCapabilitySpec(
1506
+ tool_name="get_file_symbols",
1507
+ capability="file_symbol_table",
1508
+ description=(
1509
+ "List extracted symbols (`symbol`, `kind`, `start_line`, `end_line`) in a single repository-relative file. "
1510
+ "Use for a lightweight symbol table of one file when imports and artifact metadata are not needed. "
1511
+ "Does not return cross-file relationships; prefer get_file for full file outline."
1512
+ ),
1513
+ task_types=(
1514
+ AgentTaskCategory.SYMBOL_LOOKUP.value,
1515
+ ),
1516
+ required_inputs=("path",),
1517
+ optional_inputs=(),
1518
+ expected_output_type="FileSymbolRowList",
1519
+ relationship_types_returned=("DEFINES",),
1520
+ evidence_guarantees="AST-extracted symbol names, kinds, and line spans within `path`.",
1521
+ does_not_prove="Does not return file imports or callers.",
1522
+ useful_situations=(
1523
+ "Listing symbol names and line ranges inside a single file",
1524
+ ),
1525
+ avoid_when=(
1526
+ "You also need file imports and artifact classification (prefer get_file)",
1527
+ ),
1528
+ profiles=("full",),
1529
+ minimal_invocation='get_file_symbols(path="src/auth/service.py")',
1530
+ advanced_invocation='get_file_symbols(path="src/auth/routes.py")',
1531
+ result_fields=("symbol", "kind", "start_line", "end_line"),
1532
+ typical_followup="get_symbol or read_file",
1533
+ common_mistakes=(
1534
+ "Passing a symbol name instead of a file path",
1535
+ ),
1536
+ example_request="List all symbols defined in src/auth/service.py.",
1537
+ example_call='get_file_symbols(path="src/auth/service.py")',
1538
+ example_interpretation="Inspect each symbol's qualified name, kind, and line span.",
1539
+ ),
1540
+ # 35. get_graph
1541
+ ToolCapabilitySpec(
1542
+ tool_name="get_graph",
1543
+ capability="structural_graph_edges",
1544
+ description=(
1545
+ "Return up to `limit` (1..500) parser-confirmed definition and import graph edges with evidence metadata. "
1546
+ "Use for inspecting raw structural `DEFINES` and `IMPORTS` edges in small repositories or diagnostics. "
1547
+ "Does not replace targeted relationship tools like get_callers or trace_path."
1548
+ ),
1549
+ task_types=(
1550
+ AgentTaskCategory.ARCHITECTURE.value,
1551
+ AgentTaskCategory.DIAGNOSTIC.value,
1552
+ ),
1553
+ required_inputs=(),
1554
+ optional_inputs=("limit",),
1555
+ expected_output_type="GraphEdgeList",
1556
+ relationship_types_returned=("DEFINES", "IMPORTS"),
1557
+ evidence_guarantees="Validated structural edges with source, target, relationship, and confidence.",
1558
+ does_not_prove="Does not return task-ranked context or multi-hop traces.",
1559
+ useful_situations=(
1560
+ "Sampling raw structural graph edges during diagnostics",
1561
+ ),
1562
+ avoid_when=(
1563
+ "Answering targeted symbol or route questions (use get_callers, trace_path, or get_context)",
1564
+ ),
1565
+ profiles=("developer", "full"),
1566
+ minimal_invocation="get_graph()",
1567
+ advanced_invocation="get_graph(limit=100)",
1568
+ result_fields=("source", "target", "relationship", "confidence", "evidence_class", "file", "line"),
1569
+ typical_followup="get_architecture",
1570
+ common_mistakes=(
1571
+ "Dumping get_graph(limit=500) instead of querying the specific symbol with get_callers/get_references",
1572
+ ),
1573
+ example_request="Sample the first 50 structural edges in the graph.",
1574
+ example_call="get_graph(limit=50)",
1575
+ example_interpretation="Inspect source, target, and relationship for each edge.",
1576
+ ),
1577
+ # 36. search_memory
1578
+ ToolCapabilitySpec(
1579
+ tool_name="search_memory",
1580
+ capability="local_memory_lookup",
1581
+ description=(
1582
+ "Search local SQLite repository memory notes by keyword up to `limit` (1..100). "
1583
+ "Use only to recall previously stored local session notes. "
1584
+ "Never overrides current AST or source-file evidence; does not prove current repository state."
1585
+ ),
1586
+ task_types=(
1587
+ AgentTaskCategory.EXPLANATION.value,
1588
+ ),
1589
+ required_inputs=("query",),
1590
+ optional_inputs=("limit",),
1591
+ expected_output_type="MemoryNoteList",
1592
+ relationship_types_returned=(),
1593
+ evidence_guarantees="Local key-value notes stored in the repository memory table.",
1594
+ does_not_prove="Never proves current code structure; source code and AST evidence always take precedence.",
1595
+ useful_situations=(
1596
+ "Looking up user-saved architectural notes in local memory",
1597
+ ),
1598
+ avoid_when=(
1599
+ "Verifying actual code relationships (always use AST/graph tools instead)",
1600
+ ),
1601
+ profiles=("full",),
1602
+ minimal_invocation='search_memory(query="auth")',
1603
+ advanced_invocation='search_memory(query="deployment", limit=10)',
1604
+ result_fields=("key", "value"),
1605
+ typical_followup="resolve_symbol or get_context to verify against current source",
1606
+ common_mistakes=(
1607
+ "Trusting stale memory notes over current AST evidence",
1608
+ ),
1609
+ example_request="Search saved memory notes for 'auth'.",
1610
+ example_call='search_memory(query="auth", limit=10)',
1611
+ example_interpretation="Treat returned notes as auxiliary context subordinate to live source evidence.",
1612
+ ),
1613
+ # 37. get_evidence
1614
+ ToolCapabilitySpec(
1615
+ tool_name="get_evidence",
1616
+ capability="file_evidence_extraction",
1617
+ description=(
1618
+ "Return bounded source-derived `Evidence` citations (`file`, `start_line`, `end_line`, `symbol`, `snippet`, `content_hash`) for a non-sensitive file or symbol. "
1619
+ "Use when constructing hash-verifiable code citations. "
1620
+ "Blocks sensitive files and does not compute cross-file callers."
1621
+ ),
1622
+ task_types=(
1623
+ AgentTaskCategory.SYMBOL_LOOKUP.value,
1624
+ AgentTaskCategory.EXPLANATION.value,
1625
+ ),
1626
+ required_inputs=("path",),
1627
+ optional_inputs=("symbol",),
1628
+ expected_output_type="EvidenceCitationList",
1629
+ relationship_types_returned=(),
1630
+ evidence_guarantees="Indexed chunk snippets paired with SHA-256 content hashes and line coordinates.",
1631
+ does_not_prove="Does not compute cross-file relationship edges.",
1632
+ useful_situations=(
1633
+ "Fetching verifiable evidence snippets for a symbol inside a known file",
1634
+ ),
1635
+ avoid_when=(
1636
+ "Reading arbitrary line ranges (use read_file) or sensitive files (blocked)",
1637
+ ),
1638
+ profiles=("developer", "full"),
1639
+ minimal_invocation='get_evidence(path="src/auth/service.py")',
1640
+ advanced_invocation='get_evidence(path="src/auth/service.py", symbol="AuthService.authenticate")',
1641
+ result_fields=("file", "start_line", "end_line", "symbol", "snippet", "content_hash"),
1642
+ typical_followup="verify_evidence",
1643
+ common_mistakes=(
1644
+ "Requesting evidence on sensitive files such as .env (raises SecurityError)",
1645
+ ),
1646
+ example_request="Get verifiable evidence chunks for AuthService.authenticate in src/auth/service.py.",
1647
+ example_call='get_evidence(path="src/auth/service.py", symbol="AuthService.authenticate")',
1648
+ example_interpretation="Inspect snippet, start_line..end_line, and content_hash.",
1649
+ ),
1650
+ # 38. verify_evidence
1651
+ ToolCapabilitySpec(
1652
+ tool_name="verify_evidence",
1653
+ capability="evidence_hash_verification",
1654
+ description=(
1655
+ "Verify that a cited evidence span (`file_path`, `start_line`, `end_line`) exists on disk, matches `expected_hash`, and is not stale. "
1656
+ "Use to validate whether previously retrieved evidence is still current after repository edits. "
1657
+ "Does not re-index modified files."
1658
+ ),
1659
+ task_types=(
1660
+ AgentTaskCategory.DIAGNOSTIC.value,
1661
+ AgentTaskCategory.DEBUG.value,
1662
+ ),
1663
+ required_inputs=("file_path", "start_line", "end_line"),
1664
+ optional_inputs=("expected_hash", "symbol"),
1665
+ expected_output_type="EvidenceVerificationResult",
1666
+ relationship_types_returned=(),
1667
+ evidence_guarantees="Cryptographic hash and line-bound check against current on-disk file content.",
1668
+ does_not_prove="Does not prove semantic correctness of the code inside the span.",
1669
+ useful_situations=(
1670
+ "Confirming a cited snippet has not drifted on disk before applying a patch",
1671
+ ),
1672
+ avoid_when=(
1673
+ "Freshly retrieved evidence in a read-only session where no files were modified",
1674
+ ),
1675
+ profiles=("minimal", "developer", "full"),
1676
+ minimal_invocation='verify_evidence(file_path="src/auth/service.py", start_line=1, end_line=20)',
1677
+ advanced_invocation='verify_evidence(file_path="src/auth/service.py", start_line=1, end_line=20, symbol="AuthService")',
1678
+ result_fields=("valid", "file", "start_line", "end_line", "reason", "current_hash"),
1679
+ typical_followup="read_file if valid is False",
1680
+ common_mistakes=(
1681
+ "Continuing to rely on a snippet when verify_evidence returns valid=False",
1682
+ ),
1683
+ example_request="Verify that lines 1..20 of src/auth/service.py are still valid.",
1684
+ example_call='verify_evidence(file_path="src/auth/service.py", start_line=1, end_line=20)',
1685
+ example_interpretation="Check valid boolean and reason field.",
1686
+ ),
1687
+ # 39. get_resource_status
1688
+ ToolCapabilitySpec(
1689
+ tool_name="get_resource_status",
1690
+ capability="resource_governor_diagnostics",
1691
+ description=(
1692
+ "Return current resource governor state including memory usage, pressure level, concurrency limits, and activity mode. "
1693
+ "Use for diagnosing server resource pressure or throttling behavior. "
1694
+ "Does not inspect repository source code or symbols."
1695
+ ),
1696
+ task_types=(
1697
+ AgentTaskCategory.DIAGNOSTIC.value,
1698
+ ),
1699
+ required_inputs=(),
1700
+ optional_inputs=(),
1701
+ expected_output_type="ResourceGovernorState",
1702
+ relationship_types_returned=(),
1703
+ evidence_guarantees="Live process memory and governor concurrency telemetry.",
1704
+ does_not_prove="Does not report symbol index freshness (use get_repository_status for index freshness).",
1705
+ useful_situations=(
1706
+ "Checking if the MCP server is under memory pressure or running in CONSERVATIVE mode",
1707
+ ),
1708
+ avoid_when=(
1709
+ "Answering normal code navigation or relationship questions",
1710
+ ),
1711
+ profiles=("minimal", "developer", "full"),
1712
+ minimal_invocation="get_resource_status()",
1713
+ advanced_invocation="get_resource_status()",
1714
+ result_fields=("mode", "pressure", "rss_mb", "active_tasks"),
1715
+ typical_followup="get_repository_status",
1716
+ common_mistakes=(
1717
+ "Confusing get_resource_status (memory/CPU governor) with get_repository_status (index freshness/symbol counts)",
1718
+ ),
1719
+ example_request="Check current resource governor pressure and memory mode.",
1720
+ example_call="get_resource_status()",
1721
+ example_interpretation="Inspect mode, pressure, and rss_mb.",
1722
+ ),
1723
+ # 40. find_tests
1724
+ ToolCapabilitySpec(
1725
+ tool_name="find_tests",
1726
+ capability="test_discovery",
1727
+ description=(
1728
+ "Find test files and test functions statically linked to a target symbol (`symbol`; `canonical_id` supported as alias) via direct calls, imports, fixtures, or route invocation. "
1729
+ "Use instead of grep when answering 'what tests cover symbol X?'. "
1730
+ "Does not execute tests at runtime and never fabricates coverage from lexical similarity alone."
1731
+ ),
1732
+ task_types=(
1733
+ AgentTaskCategory.TEST_DISCOVERY.value,
1734
+ AgentTaskCategory.CHANGE_IMPACT.value,
1735
+ AgentTaskCategory.DEBUG.value,
1736
+ ),
1737
+ required_inputs=("symbol",),
1738
+ optional_inputs=("canonical_id", "max_results"),
1739
+ expected_output_type="RelatedTestsResult",
1740
+ relationship_types_returned=("TESTS", "TESTS_SYMBOL", "TESTS_ROUTE", "TESTS_PROVIDER", "TESTS_EVENT_HANDLER", "CALLS", "IMPORTS"),
1741
+ evidence_guarantees="AST/fixture/route-verified links from test functions to target symbols.",
1742
+ does_not_prove="Does not prove runtime line coverage percentage or assertion completeness.",
1743
+ useful_situations=(
1744
+ "Selecting the exact pytest/jest test functions to run after modifying a symbol",
1745
+ "Answering 'What tests cover parse_bill?' with verified test links",
1746
+ ),
1747
+ avoid_when=(
1748
+ "Editing documentation or comments with no behavioral impact",
1749
+ ),
1750
+ profiles=("agent", "developer", "full"),
1751
+ minimal_invocation='find_tests(symbol="parse_bill")',
1752
+ advanced_invocation='find_tests(symbol="AuthService.authenticate", max_results=20)',
1753
+ result_fields=("test_symbol", "file", "start_line", "end_line", "relationship", "evidence_class", "confidence", "reason"),
1754
+ typical_followup="get_file on the test function if test assertions need inspection",
1755
+ common_mistakes=(
1756
+ "Guessing test coverage from filename similarity instead of calling find_tests",
1757
+ ),
1758
+ example_request="What tests cover parse_bill?",
1759
+ example_call='find_tests(symbol="parse_bill")',
1760
+ example_interpretation="Inspect each returned test symbol, its file path, and the linking relationship (e.g., TESTS_SYMBOL, TESTS_ROUTE).",
1761
+ ),
1762
+ # 41. find_routes
1763
+ ToolCapabilitySpec(
1764
+ tool_name="find_routes",
1765
+ capability="route_discovery",
1766
+ description=(
1767
+ "Find HTTP/RPC framework routes, mounted router prefixes (`MOUNTS`), methods, and handler symbols across FastAPI, Flask, Django, Express, and NestJS. "
1768
+ "Use instead of grep when locating API endpoints or route handlers. "
1769
+ "Does not prove runtime middleware authentication unless traced via trace_path or get_context."
1770
+ ),
1771
+ task_types=(
1772
+ AgentTaskCategory.ROUTE_DISCOVERY.value,
1773
+ AgentTaskCategory.TRACE.value,
1774
+ AgentTaskCategory.ARCHITECTURE.value,
1775
+ ),
1776
+ required_inputs=(),
1777
+ optional_inputs=("framework", "method", "path"),
1778
+ expected_output_type="RouteListResult",
1779
+ relationship_types_returned=("HANDLED_BY", "ROUTE_HANDLER", "MOUNTS", "ROUTES_TO"),
1780
+ evidence_guarantees="Framework-verified route paths, HTTP methods, composed mount prefixes, and handler canonical IDs.",
1781
+ does_not_prove="Does not prove external API gateway or reverse-proxy rewrite rules outside the repository.",
1782
+ useful_situations=(
1783
+ "Answering 'Where is /api/scan-bill handled?'",
1784
+ "Discovering API entrypoints before calling trace_path to the database or service layer",
1785
+ ),
1786
+ avoid_when=(
1787
+ "Working on a pure library or CLI module with no web routes",
1788
+ ),
1789
+ profiles=("agent", "developer", "full"),
1790
+ minimal_invocation="find_routes()",
1791
+ advanced_invocation='find_routes(method="POST", path="/api/scan-bill")',
1792
+ result_fields=("status", "routes", "route_path", "http_method", "handler_name", "canonical_id", "framework", "file", "line", "evidence_class"),
1793
+ typical_followup="trace_path, find_callees, or get_file on the matched handler",
1794
+ common_mistakes=(
1795
+ "Grepping for route path strings that are split across router prefix mounts instead of calling find_routes",
1796
+ ),
1797
+ example_request="Where is /api/scan-bill handled?",
1798
+ example_call='find_routes(path="/api/scan-bill")',
1799
+ example_interpretation="Read handler_name, canonical_id, and file:line from the matched route entry.",
1800
+ ),
1801
+ # 42. trace_flow
1802
+ ToolCapabilitySpec(
1803
+ tool_name="trace_flow",
1804
+ capability="bidirectional_flow_tracing",
1805
+ description=(
1806
+ "Trace multi-hop upstream callers and downstream callees around a single symbol (`symbol`; `canonical_id` supported as alias) up to `depth`. "
1807
+ "Use when exploring bidirectional execution flow around a function or handler without a known second endpoint. "
1808
+ "Does not prove runtime branch execution; use trace_path when both endpoints are known."
1809
+ ),
1810
+ task_types=(
1811
+ AgentTaskCategory.TRACE.value,
1812
+ AgentTaskCategory.RELATIONSHIP.value,
1813
+ ),
1814
+ required_inputs=("symbol",),
1815
+ optional_inputs=("canonical_id", "depth", "callers", "callees", "both"),
1816
+ expected_output_type="DirectionalCallTraceList",
1817
+ relationship_types_returned=("CALLS", "CALLED_BY", "POSSIBLE_CALLS"),
1818
+ evidence_guarantees="Bidirectional multi-hop call traversal with hop depth, confidence, and evidence_class.",
1819
+ does_not_prove="Does not prove runtime reachability under specific input conditions.",
1820
+ useful_situations=(
1821
+ "Exploring upstream callers and downstream callees around a single symbol in one call",
1822
+ ),
1823
+ avoid_when=(
1824
+ "Both start and target symbols are known (prefer trace_path)",
1825
+ ),
1826
+ profiles=("agent", "developer", "full"),
1827
+ minimal_invocation='trace_flow(symbol="place_order", depth=2)',
1828
+ advanced_invocation='trace_flow(symbol="AuthService.authenticate", depth=2, both=True)',
1829
+ result_fields=("source", "target", "relationship", "confidence", "evidence_class", "depth", "file", "start_line"),
1830
+ typical_followup="get_file on specific hops",
1831
+ common_mistakes=(
1832
+ "Using trace_flow when trace_path(from_symbol, to_symbol) would directly connect two known symbols",
1833
+ ),
1834
+ example_request="Trace the upstream and downstream call flow around place_order.",
1835
+ example_call='trace_flow(symbol="place_order", depth=2)',
1836
+ example_interpretation="Inspect each hop's source, target, depth, and evidence_class.",
1837
+ ),
1838
+ # 43. find_db_tables
1839
+ ToolCapabilitySpec(
1840
+ tool_name="find_db_tables",
1841
+ capability="database_table_discovery",
1842
+ description=(
1843
+ "Find database tables discovered across ORM models, raw SQL queries, and migrations. "
1844
+ "Use instead of grep when asking which database tables exist in this repository. "
1845
+ "Returns canonical IDs (`db.<dialect>.<schema>.<table>`), columns, ORM models, and evidence classes (`FRAMEWORK_VERIFIED`, `STATIC_VERIFIED`, `POSSIBLE`, `UNKNOWN`). "
1846
+ "Does not connect to live databases or execute SQL."
1847
+ ),
1848
+ task_types=(
1849
+ AgentTaskCategory.ARCHITECTURE.value,
1850
+ AgentTaskCategory.SYMBOL_LOOKUP.value,
1851
+ AgentTaskCategory.MULTI_FILE_INVESTIGATION.value,
1852
+ ),
1853
+ required_inputs=(),
1854
+ optional_inputs=("table", "dialect", "schema"),
1855
+ expected_output_type="DatabaseTableListResult",
1856
+ relationship_types_returned=("MAPS_TO_TABLE", "READS_TABLE", "WRITES_TABLE", "MIGRATES_TABLE", "POSSIBLE_TABLE", "UNKNOWN_TABLE"),
1857
+ evidence_guarantees="Static ORM, SQL, and migration table definitions with canonical IDs and explicit dialect/schema uncertainty.",
1858
+ does_not_prove="Does not inspect live production database catalogs or unindexed external schemas.",
1859
+ useful_situations=(
1860
+ "Answering 'Which database tables exist in this repository?'",
1861
+ "Discovering canonical table IDs before calling get_db_table or find_db_callers",
1862
+ ),
1863
+ avoid_when=(
1864
+ "Working on a repository with zero database, ORM, or SQL usage",
1865
+ ),
1866
+ profiles=("full",),
1867
+ minimal_invocation="find_db_tables()",
1868
+ advanced_invocation='find_db_tables(table="products", dialect="postgres")',
1869
+ result_fields=("status", "tables", "count"),
1870
+ typical_followup="get_db_table, find_db_callers, or find_db_writers",
1871
+ common_mistakes=(
1872
+ "Grepping for CREATE TABLE when tables are defined via SQLAlchemy/Django/Prisma ORM models",
1873
+ ),
1874
+ example_request="Which database tables exist in this repository?",
1875
+ example_call="find_db_tables()",
1876
+ example_interpretation="Inspect tables list for canonical_id, table_name, dialect, orm_models, and evidence_class.",
1877
+ ),
1878
+ # 44. find_db_columns
1879
+ ToolCapabilitySpec(
1880
+ tool_name="find_db_columns",
1881
+ capability="database_column_discovery",
1882
+ description=(
1883
+ "Find database columns, data types, nullability, primary keys, and foreign keys across tables and ORM models. "
1884
+ "Use instead of grep when locating where a table column is defined or mapped (`MAPS_TO_COLUMN`, `HAS_PRIMARY_KEY`, `FOREIGN_KEY_TO`). "
1885
+ "Returns column definitions and source coordinates. "
1886
+ "Does not inspect live database catalogs."
1887
+ ),
1888
+ task_types=(
1889
+ AgentTaskCategory.SYMBOL_LOOKUP.value,
1890
+ AgentTaskCategory.RELATIONSHIP.value,
1891
+ AgentTaskCategory.CHANGE_IMPACT.value,
1892
+ ),
1893
+ required_inputs=(),
1894
+ optional_inputs=("table", "column"),
1895
+ expected_output_type="DatabaseColumnListResult",
1896
+ relationship_types_returned=("MAPS_TO_COLUMN", "HAS_PRIMARY_KEY", "FOREIGN_KEY_TO", "HAS_UNIQUE_CONSTRAINT"),
1897
+ evidence_guarantees="ORM field-to-column mappings and SQL/migration column definitions with file:line evidence.",
1898
+ does_not_prove="Does not prove live database column values or runtime constraint violations.",
1899
+ useful_situations=(
1900
+ "Answering 'What columns exist in bills?' or 'Where is column total_amount defined?'",
1901
+ ),
1902
+ avoid_when=(
1903
+ "You already called get_db_table(table=...) which includes all columns for that table",
1904
+ ),
1905
+ profiles=("full",),
1906
+ minimal_invocation='find_db_columns(table="bills")',
1907
+ advanced_invocation='find_db_columns(table="bills", column="total_amount")',
1908
+ result_fields=("status", "columns", "count"),
1909
+ typical_followup="find_db_writers, find_db_readers, or get_db_impact",
1910
+ common_mistakes=(
1911
+ "Assuming ORM attribute name always equals SQL column name without checking MAPS_TO_COLUMN",
1912
+ ),
1913
+ example_request="What columns exist in the bills table?",
1914
+ example_call='find_db_columns(table="bills")',
1915
+ example_interpretation="Inspect each column's canonical_id, data_type, is_primary_key, foreign_key_target, and file:start_line.",
1916
+ ),
1917
+ # 45. find_db_models
1918
+ ToolCapabilitySpec(
1919
+ tool_name="find_db_models",
1920
+ capability="database_orm_model_discovery",
1921
+ description=(
1922
+ "Find ORM models (SQLAlchemy, Flask-SQLAlchemy, Django ORM, SQLModel, Prisma) and their `MAPS_TO_TABLE` mappings. "
1923
+ "Use when asking which model class maps to a database table or vice versa. "
1924
+ "Returns model name, table, canonical ID, and `FRAMEWORK_VERIFIED` evidence. "
1925
+ "Does not import or execute application model modules."
1926
+ ),
1927
+ task_types=(
1928
+ AgentTaskCategory.SYMBOL_LOOKUP.value,
1929
+ AgentTaskCategory.ARCHITECTURE.value,
1930
+ ),
1931
+ required_inputs=(),
1932
+ optional_inputs=("model", "table"),
1933
+ expected_output_type="DatabaseModelListResult",
1934
+ relationship_types_returned=("MAPS_TO_TABLE", "MAPS_TO_COLUMN", "ORM_RELATION"),
1935
+ evidence_guarantees="Framework-verified ORM model declarations and table mappings.",
1936
+ does_not_prove="Does not execute dynamic metaclass table name generators.",
1937
+ useful_situations=(
1938
+ "Finding which ORM class maps to table 'products' or listing all ORM models in the repository",
1939
+ ),
1940
+ avoid_when=(
1941
+ "Repository uses only raw SQL with no ORM layer",
1942
+ ),
1943
+ profiles=("full",),
1944
+ minimal_invocation="find_db_models()",
1945
+ advanced_invocation='find_db_models(table="products")',
1946
+ result_fields=("status", "models", "count"),
1947
+ typical_followup="get_db_table or find_symbol",
1948
+ common_mistakes=(
1949
+ "Guessing pluralized table names instead of checking MAPS_TO_TABLE via find_db_models",
1950
+ ),
1951
+ example_request="Which ORM model maps to the products table?",
1952
+ example_call='find_db_models(table="products")',
1953
+ example_interpretation="Read name, table_name, framework, file, and start_line from the matched model entry.",
1954
+ ),
1955
+ # 46. find_db_queries
1956
+ ToolCapabilitySpec(
1957
+ tool_name="find_db_queries",
1958
+ capability="database_query_discovery",
1959
+ description=(
1960
+ "Find database queries (`SELECT`, `INSERT`, `UPDATE`, `DELETE`) across ORM calls, query builders, and raw SQL strings. "
1961
+ "Use when asking which queries touch a table or what SQL a function executes. "
1962
+ "Returns `READS_TABLE`, `WRITES_TABLE`, `POSSIBLE_TABLE`, or `UNKNOWN_TABLE` with redacted snippets. "
1963
+ "Never exposes SQL literal secret parameters."
1964
+ ),
1965
+ task_types=(
1966
+ AgentTaskCategory.RELATIONSHIP.value,
1967
+ AgentTaskCategory.DEBUG.value,
1968
+ AgentTaskCategory.TRACE.value,
1969
+ ),
1970
+ required_inputs=(),
1971
+ optional_inputs=("table", "symbol", "operation"),
1972
+ expected_output_type="DatabaseQueryListResult",
1973
+ relationship_types_returned=("READS_TABLE", "WRITES_TABLE", "READS_COLUMN", "WRITES_COLUMN", "POSSIBLE_TABLE", "UNKNOWN_TABLE", "QUERIES_DATABASE"),
1974
+ evidence_guarantees="AST- and framework-verified query operations with normalized/redacted SQL snippets and uncertainty preservation.",
1975
+ does_not_prove="Does not execute SQL queries or prove query execution plans.",
1976
+ useful_situations=(
1977
+ "Listing all SELECT/INSERT/UPDATE/DELETE operations targeting a table or inside a service function",
1978
+ ),
1979
+ avoid_when=(
1980
+ "Looking for general Python function callers unrelated to database persistence",
1981
+ ),
1982
+ profiles=("full",),
1983
+ minimal_invocation='find_db_queries(table="orders")',
1984
+ advanced_invocation='find_db_queries(table="orders", operation="INSERT")',
1985
+ result_fields=("status", "queries", "count"),
1986
+ typical_followup="get_file on the query file:start_line or find_db_callers",
1987
+ common_mistakes=(
1988
+ "Treating dynamic string-interpolated SQL (`POSSIBLE_TABLE`/`UNKNOWN_TABLE`) as guaranteed static table proof",
1989
+ ),
1990
+ example_request="What database queries run against the orders table?",
1991
+ example_call='find_db_queries(table="orders")',
1992
+ example_interpretation="Inspect operation, relationship, source_symbol, columns, evidence_class, and redacted snippet.",
1993
+ ),
1994
+ # 47. find_db_callers
1995
+ ToolCapabilitySpec(
1996
+ tool_name="find_db_callers",
1997
+ capability="database_caller_discovery",
1998
+ description=(
1999
+ "Find all functions, methods, and upstream HTTP routes (`HANDLED_BY`) that read from or write to a database table. "
2000
+ "Use instead of grep when asking which code or route reaches a table. "
2001
+ "Returns direct accessors, upstream routes, relationships (`READS_TABLE`, `WRITES_TABLE`), and evidence classes. "
2002
+ "Does not prove runtime query frequency."
2003
+ ),
2004
+ task_types=(
2005
+ AgentTaskCategory.RELATIONSHIP.value,
2006
+ AgentTaskCategory.TRACE.value,
2007
+ AgentTaskCategory.CHANGE_IMPACT.value,
2008
+ ),
2009
+ required_inputs=("table",),
2010
+ optional_inputs=(),
2011
+ expected_output_type="DatabaseCallersResult",
2012
+ relationship_types_returned=("READS_TABLE", "WRITES_TABLE", "CALLS", "HANDLED_BY", "POSSIBLE_TABLE"),
2013
+ evidence_guarantees="Direct table readers/writers plus transitive upstream callers and HTTP route entrypoints.",
2014
+ does_not_prove="Does not prove runtime execution frequency unless paired with get_runtime_trace.",
2015
+ useful_situations=(
2016
+ "Answering 'Which route or service accesses the products table?'",
2017
+ ),
2018
+ avoid_when=(
2019
+ "You only want write mutations (prefer find_db_writers) or only reads (prefer find_db_readers)",
2020
+ ),
2021
+ profiles=("full",),
2022
+ minimal_invocation='find_db_callers(table="products")',
2023
+ advanced_invocation='find_db_callers(table="products")',
2024
+ result_fields=("status", "table", "direct_accessors", "upstream_routes", "count"),
2025
+ typical_followup="trace_path or get_file on the accessor symbol",
2026
+ common_mistakes=(
2027
+ "Grepping for table names in route files when routes call services that call repositories",
2028
+ ),
2029
+ example_request="Which routes and functions access the products table?",
2030
+ example_call='find_db_callers(table="products")',
2031
+ example_interpretation="Inspect direct_accessors and upstream_routes for symbol, relationship, and file:start_line.",
2032
+ ),
2033
+ # 48. find_db_writers
2034
+ ToolCapabilitySpec(
2035
+ tool_name="find_db_writers",
2036
+ capability="database_writer_discovery",
2037
+ description=(
2038
+ "Find all symbols and queries that write (`INSERT`, `UPDATE`, `DELETE`) to a database table or column (`WRITES_TABLE`, `WRITES_COLUMN`). "
2039
+ "Use when investigating data mutations, state changes, or write blast radius. "
2040
+ "Returns writer symbols, operations, file:line citations, and redacted snippets. "
2041
+ "Does not execute database transactions."
2042
+ ),
2043
+ task_types=(
2044
+ AgentTaskCategory.RELATIONSHIP.value,
2045
+ AgentTaskCategory.DEBUG.value,
2046
+ AgentTaskCategory.CHANGE_IMPACT.value,
2047
+ ),
2048
+ required_inputs=(),
2049
+ optional_inputs=("table", "column"),
2050
+ expected_output_type="DatabaseWritersResult",
2051
+ relationship_types_returned=("WRITES_TABLE", "WRITES_COLUMN"),
2052
+ evidence_guarantees="Verified INSERT/UPDATE/DELETE and ORM mutation call sites with source coordinates.",
2053
+ does_not_prove="Does not prove whether a database transaction commits or rolls back at runtime.",
2054
+ useful_situations=(
2055
+ "Answering 'Which service writes to the products table or updates inventory.quantity?'",
2056
+ ),
2057
+ avoid_when=(
2058
+ "Investigating read-only SELECT queries (use find_db_readers)",
2059
+ ),
2060
+ profiles=("full",),
2061
+ minimal_invocation='find_db_writers(table="products")',
2062
+ advanced_invocation='find_db_writers(table="inventory", column="quantity")',
2063
+ result_fields=("status", "writers", "count"),
2064
+ typical_followup="find_callers on the writer symbol or get_file on the mutation lines",
2065
+ common_mistakes=(
2066
+ "Confusing read queries with write mutations when debugging corrupted state",
2067
+ ),
2068
+ example_request="Which functions write to the products table?",
2069
+ example_call='find_db_writers(table="products")',
2070
+ example_interpretation="Inspect writers for symbol, operation (INSERT/UPDATE/DELETE), columns, file, and start_line.",
2071
+ ),
2072
+ # 49. find_db_readers
2073
+ ToolCapabilitySpec(
2074
+ tool_name="find_db_readers",
2075
+ capability="database_reader_discovery",
2076
+ description=(
2077
+ "Find all symbols and queries that read (`SELECT`, `.query()`, `.objects.filter()`, `.findMany()`) from a database table or column (`READS_TABLE`, `READS_COLUMN`). "
2078
+ "Use when tracing where table data is consumed across services and views. "
2079
+ "Returns reader symbols, operations, and evidence classes. "
2080
+ "Does not prove runtime cache hits."
2081
+ ),
2082
+ task_types=(
2083
+ AgentTaskCategory.RELATIONSHIP.value,
2084
+ AgentTaskCategory.TRACE.value,
2085
+ ),
2086
+ required_inputs=(),
2087
+ optional_inputs=("table", "column"),
2088
+ expected_output_type="DatabaseReadersResult",
2089
+ relationship_types_returned=("READS_TABLE", "READS_COLUMN", "POSSIBLE_TABLE"),
2090
+ evidence_guarantees="Verified SELECT and ORM read query sites with file and line spans.",
2091
+ does_not_prove="Does not prove whether results are served from an in-memory cache at runtime.",
2092
+ useful_situations=(
2093
+ "Answering 'What code reads from users or reads column email?'",
2094
+ ),
2095
+ avoid_when=(
2096
+ "Looking only for INSERT/UPDATE/DELETE mutations (use find_db_writers)",
2097
+ ),
2098
+ profiles=("full",),
2099
+ minimal_invocation='find_db_readers(table="users")',
2100
+ advanced_invocation='find_db_readers(table="users", column="email")',
2101
+ result_fields=("status", "readers", "count"),
2102
+ typical_followup="get_file on the reader symbol or get_db_impact",
2103
+ common_mistakes=(
2104
+ "Assuming a function that imports a model always reads from its table without checking READS_TABLE",
2105
+ ),
2106
+ example_request="Which functions read from the users table?",
2107
+ example_call='find_db_readers(table="users")',
2108
+ example_interpretation="Inspect readers for symbol, table, columns, evidence_class, and file:start_line.",
2109
+ ),
2110
+ # 50. find_db_relationships
2111
+ ToolCapabilitySpec(
2112
+ tool_name="find_db_relationships",
2113
+ capability="database_relationship_discovery",
2114
+ description=(
2115
+ "Find database schema and ORM relationships (`FOREIGN_KEY_TO`, `ORM_RELATION`, `MAPS_TO_TABLE`, `HAS_PRIMARY_KEY`, `HAS_INDEX`, `MIGRATES_TABLE`). "
2116
+ "Use when inspecting foreign keys, table joins, or model associations. "
2117
+ "Preserves explicit database relationship types and never collapses them into generic `DEPENDS_ON`."
2118
+ ),
2119
+ task_types=(
2120
+ AgentTaskCategory.RELATIONSHIP.value,
2121
+ AgentTaskCategory.ARCHITECTURE.value,
2122
+ ),
2123
+ required_inputs=(),
2124
+ optional_inputs=("table",),
2125
+ expected_output_type="DatabaseRelationshipsResult",
2126
+ relationship_types_returned=(
2127
+ "MAPS_TO_TABLE",
2128
+ "MAPS_TO_COLUMN",
2129
+ "FOREIGN_KEY_TO",
2130
+ "ORM_RELATION",
2131
+ "HAS_PRIMARY_KEY",
2132
+ "HAS_INDEX",
2133
+ "HAS_UNIQUE_CONSTRAINT",
2134
+ "HAS_CHECK_CONSTRAINT",
2135
+ "MIGRATES_TABLE",
2136
+ ),
2137
+ evidence_guarantees="Explicit schema and ORM relationship edges with canonical source/target IDs and source coordinates.",
2138
+ does_not_prove="Does not prove unindexed database triggers on external servers.",
2139
+ useful_situations=(
2140
+ "Answering 'What foreign keys or ORM relationships exist for orders?'",
2141
+ ),
2142
+ avoid_when=(
2143
+ "Looking for function-to-function call edges (use find_callers/find_callees)",
2144
+ ),
2145
+ profiles=("full",),
2146
+ minimal_invocation="find_db_relationships()",
2147
+ advanced_invocation='find_db_relationships(table="orders")',
2148
+ result_fields=("status", "relationships", "count"),
2149
+ typical_followup="get_db_table or get_db_impact",
2150
+ common_mistakes=(
2151
+ "Collapsing FOREIGN_KEY_TO or ORM_RELATION into generic DEPENDS_ON edges",
2152
+ ),
2153
+ example_request="What foreign key and ORM relationships exist on the orders table?",
2154
+ example_call='find_db_relationships(table="orders")',
2155
+ example_interpretation="Inspect source, target, relationship (e.g. FOREIGN_KEY_TO, ORM_RELATION), and evidence_class.",
2156
+ ),
2157
+ # 51. get_db_table
2158
+ ToolCapabilitySpec(
2159
+ tool_name="get_db_table",
2160
+ capability="database_table_details",
2161
+ description=(
2162
+ "Return complete structural details for a database table including columns, primary keys, foreign keys, indexes, constraints, ORM models, readers, writers, migrations, and upstream routes. "
2163
+ "Use when inspecting a specific table's schema and code usage in one call. "
2164
+ "Does not query live database servers."
2165
+ ),
2166
+ task_types=(
2167
+ AgentTaskCategory.SYMBOL_LOOKUP.value,
2168
+ AgentTaskCategory.ARCHITECTURE.value,
2169
+ AgentTaskCategory.DEBUG.value,
2170
+ ),
2171
+ required_inputs=("table",),
2172
+ optional_inputs=(),
2173
+ expected_output_type="DatabaseTableDetailResult",
2174
+ relationship_types_returned=(
2175
+ "MAPS_TO_TABLE",
2176
+ "MAPS_TO_COLUMN",
2177
+ "READS_TABLE",
2178
+ "WRITES_TABLE",
2179
+ "FOREIGN_KEY_TO",
2180
+ "HAS_PRIMARY_KEY",
2181
+ "HAS_INDEX",
2182
+ "MIGRATES_TABLE",
2183
+ "ORM_RELATION",
2184
+ ),
2185
+ evidence_guarantees="Consolidated static schema, ORM mapping, reader/writer, route, and migration evidence for the table.",
2186
+ does_not_prove="Does not return live table row counts or production data rows.",
2187
+ useful_situations=(
2188
+ "Inspecting everything known about a database table (columns, models, readers, writers, routes, migrations)",
2189
+ ),
2190
+ avoid_when=(
2191
+ "Table name is unknown (call find_db_tables first)",
2192
+ ),
2193
+ profiles=("full",),
2194
+ minimal_invocation='get_db_table(table="products")',
2195
+ advanced_invocation='get_db_table(table="products")',
2196
+ result_fields=("status", "table", "canonical_id", "dialect", "schema", "columns", "primary_keys", "foreign_keys", "indexes", "orm_models", "readers", "writers", "migrations", "upstream_routes"),
2197
+ typical_followup="get_db_impact or get_file on model/migration files",
2198
+ common_mistakes=(
2199
+ "Reading multiple migration files manually before calling get_db_table",
2200
+ ),
2201
+ example_request="Show me the schema, models, readers, and writers for the products table.",
2202
+ example_call='get_db_table(table="products")',
2203
+ example_interpretation="Inspect columns, primary_keys, foreign_keys, orm_models, readers, writers, and upstream_routes.",
2204
+ ),
2205
+ # 52. get_db_schema
2206
+ ToolCapabilitySpec(
2207
+ tool_name="get_db_schema",
2208
+ capability="database_schema_overview",
2209
+ description=(
2210
+ "Return a repository-wide database schema summary including all discovered tables, columns, ORM models, foreign keys, and migrations. "
2211
+ "Use for database architecture overviews or schema audits. "
2212
+ "Preserves `UNKNOWN` dialect and schema when not statically provable and never guesses database names."
2213
+ ),
2214
+ task_types=(
2215
+ AgentTaskCategory.ARCHITECTURE.value,
2216
+ AgentTaskCategory.EXPLANATION.value,
2217
+ ),
2218
+ required_inputs=(),
2219
+ optional_inputs=("dialect", "schema"),
2220
+ expected_output_type="DatabaseSchemaOverviewResult",
2221
+ relationship_types_returned=("MAPS_TO_TABLE", "FOREIGN_KEY_TO", "ORM_RELATION", "MIGRATES_TABLE", "READS_ENV"),
2222
+ evidence_guarantees="Repository-wide database topology with explicit dialect/schema uncertainty and redacted env metadata.",
2223
+ does_not_prove="Does not connect to external database servers or read `.env` secret values.",
2224
+ useful_situations=(
2225
+ "Understanding the complete data model, tables, foreign keys, and migration history of a repository",
2226
+ ),
2227
+ avoid_when=(
2228
+ "Only one known table needs inspection (prefer get_db_table)",
2229
+ ),
2230
+ profiles=("full",),
2231
+ minimal_invocation="get_db_schema()",
2232
+ advanced_invocation='get_db_schema(dialect="postgres", schema="public")',
2233
+ result_fields=("status", "dialects", "schemas", "tables", "orm_models", "foreign_keys", "migrations", "env_variables"),
2234
+ typical_followup="get_db_table on specific tables of interest",
2235
+ common_mistakes=(
2236
+ "Assuming postgres/public when the repository only uses generic ORM declarations (`UNKNOWN` dialect/schema)",
2237
+ ),
2238
+ example_request="Give me an overview of the entire database schema and foreign keys in this repo.",
2239
+ example_call="get_db_schema()",
2240
+ example_interpretation="Inspect tables, orm_models, foreign_keys, migrations, and env_variables.",
2241
+ ),
2242
+ # 53. get_db_impact
2243
+ ToolCapabilitySpec(
2244
+ tool_name="get_db_impact",
2245
+ capability="database_change_impact",
2246
+ description=(
2247
+ "Compute bidirectional Code <-> Database change impact for a table or column, returning affected ORM models, readers, writers, upstream HTTP routes, migrations, and statically linked tests. "
2248
+ "Use before renaming or altering a database table or column. "
2249
+ "Does not execute database migrations or tests."
2250
+ ),
2251
+ task_types=(
2252
+ AgentTaskCategory.CHANGE_IMPACT.value,
2253
+ AgentTaskCategory.TEST_DISCOVERY.value,
2254
+ AgentTaskCategory.DEBUG.value,
2255
+ ),
2256
+ required_inputs=(),
2257
+ optional_inputs=("table", "column"),
2258
+ expected_output_type="DatabaseImpactResult",
2259
+ relationship_types_returned=("MAPS_TO_TABLE", "MAPS_TO_COLUMN", "READS_TABLE", "WRITES_TABLE", "READS_COLUMN", "WRITES_COLUMN", "FOREIGN_KEY_TO", "MIGRATES_TABLE", "TESTS"),
2260
+ evidence_guarantees="Bidirectional impact across ORM models, queries, service callers, HTTP routes, migrations, and tests.",
2261
+ does_not_prove="Does not prove runtime migration locking or production table size.",
2262
+ useful_situations=(
2263
+ "Answering 'What breaks if I rename or drop column shop_id on products?'",
2264
+ ),
2265
+ avoid_when=(
2266
+ "Analyzing impact of a pure non-database helper function (use analyze_impact)",
2267
+ ),
2268
+ profiles=("full",),
2269
+ minimal_invocation='get_db_impact(table="products")',
2270
+ advanced_invocation='get_db_impact(table="products", column="shop_id")',
2271
+ result_fields=("status", "table", "column", "affected_models", "affected_readers", "affected_writers", "affected_routes", "affected_migrations", "affected_tests"),
2272
+ typical_followup="get_file on affected models/writers and find_tests",
2273
+ common_mistakes=(
2274
+ "Renaming a database column in an ORM model without checking raw SQL readers/writers via get_db_impact",
2275
+ ),
2276
+ example_request="What code, routes, and tests are impacted if products.shop_id changes?",
2277
+ example_call='get_db_impact(table="products", column="shop_id")',
2278
+ example_interpretation="Inspect affected_models, affected_readers, affected_writers, affected_routes, affected_migrations, and affected_tests.",
2279
+ ),
2280
+ # 54. ingest_runtime_traces
2281
+ ToolCapabilitySpec(
2282
+ tool_name="ingest_runtime_traces",
2283
+ capability="runtime_trace_ingestion",
2284
+ description=(
2285
+ "Ingest optional runtime observation traces (OpenTelemetry JSON, structured JSON/JSONL events, or SQL query logs) into CodeGraph's `RUNTIME_OBSERVED` layer. "
2286
+ "Use when grounding static analysis with recorded runtime traces. "
2287
+ "Automatically strips headers/cookies/bodies, redacts SQL bind parameters and secrets, and never converts runtime observations into static `AST_VERIFIED` proof."
2288
+ ),
2289
+ task_types=(
2290
+ AgentTaskCategory.TRACE.value,
2291
+ AgentTaskCategory.DEBUG.value,
2292
+ AgentTaskCategory.DIAGNOSTIC.value,
2293
+ ),
2294
+ required_inputs=(),
2295
+ optional_inputs=("source_path", "format", "payload", "max_events", "sample_rate"),
2296
+ expected_output_type="RuntimeIngestResult",
2297
+ relationship_types_returned=("CALLS", "HANDLED_BY", "READS_TABLE", "WRITES_TABLE", "QUERIES_DATABASE"),
2298
+ evidence_guarantees="Sanitized RUNTIME_OBSERVED edges with observation_count, first_seen, last_seen, and runtime_generation.",
2299
+ does_not_prove="Never proves static AST_VERIFIED relationships and never launches or instruments the user application.",
2300
+ useful_situations=(
2301
+ "Loading recorded OpenTelemetry spans, JSONL runtime events, or SQL logs before calling get_runtime_trace or reconcile_static_runtime",
2302
+ ),
2303
+ avoid_when=(
2304
+ "No runtime trace file or payload is available (static tools work without runtime data)",
2305
+ ),
2306
+ profiles=("full",),
2307
+ minimal_invocation='ingest_runtime_traces(source_path="traces/otel.json")',
2308
+ advanced_invocation='ingest_runtime_traces(source_path="traces/events.jsonl", format="jsonl", max_events=1000, sample_rate=1.0)',
2309
+ result_fields=("status", "runtime_generation", "events_ingested", "edges_recorded", "redacted_fields"),
2310
+ typical_followup="get_runtime_trace or reconcile_static_runtime",
2311
+ common_mistakes=(
2312
+ "Treating RUNTIME_OBSERVED edges as static AST_VERIFIED proof",
2313
+ ),
2314
+ example_request="Ingest the OpenTelemetry trace file traces/scan_bill.json.",
2315
+ example_call='ingest_runtime_traces(source_path="traces/scan_bill.json")',
2316
+ example_interpretation="Confirm status=='ok', events_ingested, and runtime_generation.",
2317
+ ),
2318
+ # 55. get_runtime_trace
2319
+ ToolCapabilitySpec(
2320
+ tool_name="get_runtime_trace",
2321
+ capability="runtime_trace_interrogation",
2322
+ description=(
2323
+ "Retrieve aggregated runtime execution edges (`RUNTIME_OBSERVED`) and reverse maps (`table -> runtime writers/readers`, `route -> runtime tables`) with `observation_count`, `first_seen`, `last_seen`, and `runtime_generation`. "
2324
+ "Use when asking what actually happened at runtime. "
2325
+ "Does not treat unobserved paths as impossible."
2326
+ ),
2327
+ task_types=(
2328
+ AgentTaskCategory.TRACE.value,
2329
+ AgentTaskCategory.DEBUG.value,
2330
+ ),
2331
+ required_inputs=(),
2332
+ optional_inputs=("route", "symbol", "table"),
2333
+ expected_output_type="RuntimeTraceResult",
2334
+ relationship_types_returned=("CALLS", "HANDLED_BY", "READS_TABLE", "WRITES_TABLE", "QUERIES_DATABASE"),
2335
+ evidence_guarantees="Aggregated RUNTIME_OBSERVED edges with observation counts, timestamps, and sanitized SQL templates.",
2336
+ does_not_prove="Does not prove that unobserved static branches cannot execute under other inputs.",
2337
+ useful_situations=(
2338
+ "Answering 'What happened at runtime when /api/scan-bill ran?' or 'Which runtime queries wrote to bills?'",
2339
+ ),
2340
+ avoid_when=(
2341
+ "No runtime traces have been ingested into the repository index",
2342
+ ),
2343
+ profiles=("full",),
2344
+ minimal_invocation="get_runtime_trace()",
2345
+ advanced_invocation='get_runtime_trace(route="/api/scan-bill", table="bills")',
2346
+ result_fields=("status", "edges", "observations", "reverse_maps", "count"),
2347
+ typical_followup="reconcile_static_runtime or get_file",
2348
+ common_mistakes=(
2349
+ "Claiming a branch is dead code merely because observation_count is 0 in a single trace sample",
2350
+ ),
2351
+ example_request="What happened at runtime when /api/scan-bill executed?",
2352
+ example_call='get_runtime_trace(route="/api/scan-bill")',
2353
+ example_interpretation="Inspect edges (`evidence_class='RUNTIME_OBSERVED'`), observation_count, and reverse_maps.",
2354
+ ),
2355
+ # 56. reconcile_static_runtime
2356
+ ToolCapabilitySpec(
2357
+ tool_name="reconcile_static_runtime",
2358
+ capability="static_runtime_reconciliation",
2359
+ description=(
2360
+ "Reconcile static repository edges against ingested runtime observations, classifying edges into `CONFIRMED_RUNTIME_PATH`, `STATIC_RUNTIME_CONFLICT`, `NOT_OBSERVED_AT_RUNTIME`, and `RUNTIME_ONLY_OBSERVED`. "
2361
+ "Use when comparing static code analysis with runtime behavior. "
2362
+ "Never treats `NOT_OBSERVED_AT_RUNTIME` as proof that a path cannot execute."
2363
+ ),
2364
+ task_types=(
2365
+ AgentTaskCategory.DEBUG.value,
2366
+ AgentTaskCategory.TRACE.value,
2367
+ AgentTaskCategory.DIAGNOSTIC.value,
2368
+ ),
2369
+ required_inputs=(),
2370
+ optional_inputs=("symbol", "route", "table"),
2371
+ expected_output_type="StaticRuntimeReconciliationResult",
2372
+ relationship_types_returned=("CALLS", "HANDLED_BY", "READS_TABLE", "WRITES_TABLE", "POSSIBLE_CALLS", "POSSIBLE_TABLE"),
2373
+ evidence_guarantees="Explicit four-bucket reconciliation preserving both static evidence classes and RUNTIME_OBSERVED / RUNTIME_UNOBSERVED states.",
2374
+ does_not_prove="Never overwrites static AST_VERIFIED edges and never treats NOT_OBSERVED_AT_RUNTIME as dead-code proof.",
2375
+ useful_situations=(
2376
+ "Answering 'Where do static and runtime paths differ?' or 'Which dynamic calls were observed only at runtime?'",
2377
+ ),
2378
+ avoid_when=(
2379
+ "Only static analysis is needed and no runtime traces have been ingested",
2380
+ ),
2381
+ profiles=("full",),
2382
+ minimal_invocation="reconcile_static_runtime()",
2383
+ advanced_invocation='reconcile_static_runtime(route="/api/scan-bill", table="bills")',
2384
+ result_fields=("status", "confirmed_runtime_paths", "static_runtime_conflicts", "not_observed_at_runtime", "runtime_only_observed", "summary"),
2385
+ typical_followup="get_file on runtime_only_observed or static_runtime_conflicts locations",
2386
+ common_mistakes=(
2387
+ "Deleting code in NOT_OBSERVED_AT_RUNTIME without checking if it handles rare error or admin paths",
2388
+ ),
2389
+ example_request="Where does the runtime behavior of /api/scan-bill differ from static analysis?",
2390
+ example_call='reconcile_static_runtime(route="/api/scan-bill")',
2391
+ example_interpretation="Inspect confirmed_runtime_paths, static_runtime_conflicts, not_observed_at_runtime, and runtime_only_observed.",
2392
+ ),
2393
+ )
2394
+
2395
+ _TOOL_BY_NAME: dict[str, ToolCapabilitySpec] = {t.tool_name: t for t in TOOL_CAPABILITY_REGISTRY}
2396
+
2397
+
2398
+ # ---------------------------------------------------------------------------
2399
+ # Capability Matrix (Task Category -> Tool Selection Rule)
2400
+ # ---------------------------------------------------------------------------
2401
+
2402
+ CAPABILITY_MATRIX: dict[AgentTaskCategory, TaskCapabilityRule] = {
2403
+ AgentTaskCategory.LOCAL_EDIT: TaskCapabilityRule(
2404
+ category=AgentTaskCategory.LOCAL_EDIT,
2405
+ should_use_codegraph=False,
2406
+ primary_tool=None,
2407
+ acceptable_tools=("read_file", "get_file"),
2408
+ recommended_sequence=("read_file",),
2409
+ direct_inspection_acceptable=True,
2410
+ rationale="Single-file local edits (typos, formatting, local variable renames) do not require cross-file graph queries.",
2411
+ ),
2412
+ AgentTaskCategory.SYMBOL_LOOKUP: TaskCapabilityRule(
2413
+ category=AgentTaskCategory.SYMBOL_LOOKUP,
2414
+ should_use_codegraph=True,
2415
+ primary_tool="resolve_symbol",
2416
+ acceptable_tools=("resolve_symbol", "search_symbols", "get_symbol", "find_symbol"),
2417
+ recommended_sequence=("resolve_symbol", "get_symbol"),
2418
+ direct_inspection_acceptable=False,
2419
+ rationale="Use resolve_symbol, find_symbol, or search_symbols to locate canonical symbol definitions without blind grep.",
2420
+ ),
2421
+ AgentTaskCategory.RELATIONSHIP: TaskCapabilityRule(
2422
+ category=AgentTaskCategory.RELATIONSHIP,
2423
+ should_use_codegraph=True,
2424
+ primary_tool="get_callers",
2425
+ acceptable_tools=("resolve_symbol", "get_callers", "get_callees", "get_references", "get_imports", "get_dependents", "find_callers", "find_callees", "find_references"),
2426
+ recommended_sequence=("resolve_symbol", "get_callers"),
2427
+ direct_inspection_acceptable=False,
2428
+ rationale="Use find_callers/get_callers, find_callees/get_callees, or find_references/get_references to retrieve AST/framework-verified relationships.",
2429
+ ),
2430
+ AgentTaskCategory.TRACE: TaskCapabilityRule(
2431
+ category=AgentTaskCategory.TRACE,
2432
+ should_use_codegraph=True,
2433
+ primary_tool="trace_path",
2434
+ acceptable_tools=("trace_path", "list_routes", "find_routes", "resolve_symbol", "get_context", "get_callees", "trace_call", "trace_flow"),
2435
+ recommended_sequence=("list_routes", "resolve_symbol", "trace_path"),
2436
+ direct_inspection_acceptable=False,
2437
+ rationale="Use trace_path (or find_routes/list_routes -> resolve_symbol -> trace_path) to prove multi-hop execution chains.",
2438
+ ),
2439
+ AgentTaskCategory.DEBUG: TaskCapabilityRule(
2440
+ category=AgentTaskCategory.DEBUG,
2441
+ should_use_codegraph=True,
2442
+ primary_tool="get_context",
2443
+ acceptable_tools=("get_context", "resolve_symbol", "get_callers", "get_callees", "find_related_tests", "find_tests", "search_code", "get_file"),
2444
+ recommended_sequence=("resolve_symbol", "get_context"),
2445
+ direct_inspection_acceptable=False,
2446
+ rationale="Use get_context (intent='DEBUG') to gather target, callers, callees, DI providers, and related tests in one bounded packet.",
2447
+ ),
2448
+ AgentTaskCategory.CHANGE_IMPACT: TaskCapabilityRule(
2449
+ category=AgentTaskCategory.CHANGE_IMPACT,
2450
+ should_use_codegraph=True,
2451
+ primary_tool="get_git_impact",
2452
+ acceptable_tools=("get_git_impact", "analyze_impact", "analyze_change_impact", "get_dependents", "get_callers", "find_related_tests", "find_tests", "get_context"),
2453
+ recommended_sequence=("resolve_symbol", "get_git_impact"),
2454
+ direct_inspection_acceptable=False,
2455
+ rationale="Use get_git_impact or analyze_impact to deterministically compute affected callers, packages, and tests.",
2456
+ ),
2457
+ AgentTaskCategory.TEST_DISCOVERY: TaskCapabilityRule(
2458
+ category=AgentTaskCategory.TEST_DISCOVERY,
2459
+ should_use_codegraph=True,
2460
+ primary_tool="find_related_tests",
2461
+ acceptable_tools=("find_related_tests", "find_tests", "get_git_impact", "get_context", "resolve_symbol"),
2462
+ recommended_sequence=("resolve_symbol", "find_related_tests"),
2463
+ direct_inspection_acceptable=False,
2464
+ rationale="Use find_tests/find_related_tests or get_context(intent='TEST') to locate statically linked test functions.",
2465
+ ),
2466
+ AgentTaskCategory.ROUTE_DISCOVERY: TaskCapabilityRule(
2467
+ category=AgentTaskCategory.ROUTE_DISCOVERY,
2468
+ should_use_codegraph=True,
2469
+ primary_tool="list_routes",
2470
+ acceptable_tools=("list_routes", "find_routes", "resolve_symbol", "trace_path", "get_context", "search_code"),
2471
+ recommended_sequence=("list_routes", "resolve_symbol"),
2472
+ direct_inspection_acceptable=False,
2473
+ rationale="Use find_routes/list_routes to discover HTTP endpoints, mounted router prefixes, and handler symbols.",
2474
+ ),
2475
+ AgentTaskCategory.DIAGNOSTIC: TaskCapabilityRule(
2476
+ category=AgentTaskCategory.DIAGNOSTIC,
2477
+ should_use_codegraph=True,
2478
+ primary_tool="get_repository_status",
2479
+ acceptable_tools=("get_repository_status", "get_resource_status", "verify_evidence"),
2480
+ recommended_sequence=("get_repository_status",),
2481
+ direct_inspection_acceptable=True,
2482
+ rationale="Use get_repository_status to check index freshness, symbol counts, and database health.",
2483
+ ),
2484
+ AgentTaskCategory.ARCHITECTURE: TaskCapabilityRule(
2485
+ category=AgentTaskCategory.ARCHITECTURE,
2486
+ should_use_codegraph=True,
2487
+ primary_tool="get_architecture",
2488
+ acceptable_tools=("get_architecture", "get_context", "list_routes", "find_routes", "get_imports", "get_dependents"),
2489
+ recommended_sequence=("get_architecture", "get_context"),
2490
+ direct_inspection_acceptable=False,
2491
+ rationale="Use get_architecture to inspect module boundaries, entrypoints, and workspace package dependencies.",
2492
+ ),
2493
+ AgentTaskCategory.PACKAGE: TaskCapabilityRule(
2494
+ category=AgentTaskCategory.PACKAGE,
2495
+ should_use_codegraph=True,
2496
+ primary_tool="get_context",
2497
+ acceptable_tools=("get_context", "get_architecture", "get_imports", "get_dependents"),
2498
+ recommended_sequence=("get_architecture", "get_context"),
2499
+ direct_inspection_acceptable=False,
2500
+ rationale="Use get_architecture and get_context to inspect manifest-backed workspace packages and DEPENDS_ON_PACKAGE edges.",
2501
+ ),
2502
+ AgentTaskCategory.MULTI_FILE_INVESTIGATION: TaskCapabilityRule(
2503
+ category=AgentTaskCategory.MULTI_FILE_INVESTIGATION,
2504
+ should_use_codegraph=True,
2505
+ primary_tool="get_context",
2506
+ acceptable_tools=("get_context", "search_code", "search_symbols", "resolve_symbol", "trace_path", "get_callers", "get_callees", "get_file"),
2507
+ recommended_sequence=("search_symbols", "resolve_symbol", "get_context"),
2508
+ direct_inspection_acceptable=False,
2509
+ rationale="Use get_context, search_code, or targeted graph tools first to avoid blind multi-file reading.",
2510
+ ),
2511
+ AgentTaskCategory.EXPLANATION: TaskCapabilityRule(
2512
+ category=AgentTaskCategory.EXPLANATION,
2513
+ should_use_codegraph=True,
2514
+ primary_tool="get_context",
2515
+ acceptable_tools=("get_context", "resolve_symbol", "get_symbol", "get_callees", "get_file"),
2516
+ recommended_sequence=("resolve_symbol", "get_context"),
2517
+ direct_inspection_acceptable=False,
2518
+ rationale="Use get_context (intent='EXPLAIN' or 'UNDERSTAND') to retrieve verified definitions and dependencies.",
2519
+ ),
2520
+ }
2521
+
2522
+
2523
+ # ---------------------------------------------------------------------------
2524
+ # Recommended Tool Sequences (Section 10)
2525
+ # ---------------------------------------------------------------------------
2526
+
2527
+ RECOMMENDED_TOOL_SEQUENCES: tuple[dict[str, object], ...] = (
2528
+ {
2529
+ "question": "Where is authentication implemented?",
2530
+ "category": AgentTaskCategory.MULTI_FILE_INVESTIGATION.value,
2531
+ "sequence": ["search_symbols", "resolve_symbol", "get_context"],
2532
+ "notes": "Search candidate auth symbols, resolve canonical identity, then fetch bounded context.",
2533
+ },
2534
+ {
2535
+ "question": "What calls verify_password?",
2536
+ "category": AgentTaskCategory.RELATIONSHIP.value,
2537
+ "sequence": ["resolve_symbol", "get_callers"],
2538
+ "notes": "Resolve verify_password to its canonical_id, then query verified callers.",
2539
+ },
2540
+ {
2541
+ "question": "Which route reaches login?",
2542
+ "category": AgentTaskCategory.TRACE.value,
2543
+ "sequence": ["list_routes", "resolve_symbol", "trace_path"],
2544
+ "notes": "List routes to identify the endpoint handler, resolve symbols, and trace the execution path.",
2545
+ },
2546
+ {
2547
+ "question": "What tests are affected if UserService changes?",
2548
+ "category": AgentTaskCategory.TEST_DISCOVERY.value,
2549
+ "sequence": ["resolve_symbol", "find_related_tests"],
2550
+ "notes": "Resolve UserService and query statically linked test functions (or get_git_impact for commit diffs).",
2551
+ },
2552
+ {
2553
+ "question": "How does this dependency get injected?",
2554
+ "category": AgentTaskCategory.DEBUG.value,
2555
+ "sequence": ["resolve_symbol", "get_context"],
2556
+ "notes": "Resolve target symbol and compile context to inspect INJECTS, PROVIDES, and RESOLVES_DEPENDENCY edges.",
2557
+ },
2558
+ {
2559
+ "question": "Where is the upload bill button in HTML?",
2560
+ "category": AgentTaskCategory.MULTI_FILE_INVESTIGATION.value,
2561
+ "sequence": ["search_code", "get_file"],
2562
+ "notes": "Use search_code for literal UI/HTML/template text discovery, then open the matched line range with get_file.",
2563
+ },
2564
+ {
2565
+ "question": "Fix a typo in the docstring of format_date in utils/dates.py",
2566
+ "category": AgentTaskCategory.LOCAL_EDIT.value,
2567
+ "sequence": ["read_file"],
2568
+ "notes": "Single-file local edit: direct file inspection is sufficient; do not call CodeGraph.",
2569
+ },
2570
+ )
2571
+
2572
+
2573
+ # ---------------------------------------------------------------------------
2574
+ # MCP Profile Recommendations (Section 21)
2575
+ # ---------------------------------------------------------------------------
2576
+
2577
+ MCP_PROFILE_RECOMMENDATIONS: dict[str, dict[str, object]] = {
2578
+ "agent": {
2579
+ "profile": "agent",
2580
+ "tool_count": 14,
2581
+ "recommended_for": "Focused AI coding agent workflow across discovery, file inspection, context synthesis, and graph tracing",
2582
+ "included_tools": sorted(DEFAULT_AGENT_PROFILE_TOOLS),
2583
+ },
2584
+ "core": {
2585
+ "profile": "core",
2586
+ "tool_count": 13,
2587
+ "recommended_for": "Simple symbol lookup, relationship queries, and low-overhead interrogation sessions",
2588
+ "included_tools": [
2589
+ "get_architecture",
2590
+ "get_callees",
2591
+ "get_callers",
2592
+ "get_dependents",
2593
+ "get_file",
2594
+ "get_git_impact",
2595
+ "get_imports",
2596
+ "get_references",
2597
+ "get_symbol",
2598
+ "list_routes",
2599
+ "resolve_symbol",
2600
+ "search_symbols",
2601
+ "trace_path",
2602
+ ],
2603
+ },
2604
+ "graph": {
2605
+ "profile": "graph",
2606
+ "tool_count": 17,
2607
+ "recommended_for": "Tracing, change-impact, test discovery, and architecture sessions",
2608
+ "included_tools": [
2609
+ "analyze_impact",
2610
+ "find_related_tests",
2611
+ "get_architecture",
2612
+ "get_call_graph",
2613
+ "get_callees",
2614
+ "get_callers",
2615
+ "get_context",
2616
+ "get_dependents",
2617
+ "get_file",
2618
+ "get_git_impact",
2619
+ "get_imports",
2620
+ "get_references",
2621
+ "get_symbol",
2622
+ "list_routes",
2623
+ "resolve_symbol",
2624
+ "search_symbols",
2625
+ "trace_path",
2626
+ ],
2627
+ },
2628
+ "minimal": {
2629
+ "profile": "minimal",
2630
+ "tool_count": 21,
2631
+ "recommended_for": "Context-heavy coding sessions needing core interrogation plus get_context and read_file",
2632
+ "included_tools": [
2633
+ "compile_task",
2634
+ "find_symbol",
2635
+ "get_architecture",
2636
+ "get_callees",
2637
+ "get_callers",
2638
+ "get_context",
2639
+ "get_dependents",
2640
+ "get_file",
2641
+ "get_git_impact",
2642
+ "get_imports",
2643
+ "get_references",
2644
+ "get_repository_status",
2645
+ "get_resource_status",
2646
+ "get_symbol",
2647
+ "list_routes",
2648
+ "read_file",
2649
+ "resolve_symbol",
2650
+ "search_code",
2651
+ "search_symbols",
2652
+ "trace_path",
2653
+ "verify_evidence",
2654
+ ],
2655
+ },
2656
+ "developer": {
2657
+ "profile": "developer",
2658
+ "tool_count": 34,
2659
+ "recommended_for": "Full interactive development with impact analysis, test linking, and git history",
2660
+ "included_tools": [
2661
+ "analyze_change_impact",
2662
+ "analyze_impact",
2663
+ "compile_task",
2664
+ "find_callees",
2665
+ "find_callers",
2666
+ "find_references",
2667
+ "find_related_tests",
2668
+ "find_symbol",
2669
+ "get_architecture",
2670
+ "get_call_graph",
2671
+ "get_callees",
2672
+ "get_callers",
2673
+ "get_context",
2674
+ "get_dependents",
2675
+ "get_evidence",
2676
+ "get_file",
2677
+ "get_file_history",
2678
+ "get_git_impact",
2679
+ "get_graph",
2680
+ "get_imports",
2681
+ "get_recent_changes",
2682
+ "get_references",
2683
+ "get_repository_status",
2684
+ "get_resource_status",
2685
+ "get_symbol",
2686
+ "list_routes",
2687
+ "plan_retrieval",
2688
+ "read_file",
2689
+ "resolve_symbol",
2690
+ "search_code",
2691
+ "search_symbols",
2692
+ "trace_call",
2693
+ "trace_path",
2694
+ "verify_evidence",
2695
+ ],
2696
+ },
2697
+ "full": {
2698
+ "profile": "full",
2699
+ "tool_count": 56,
2700
+ "recommended_for": "Complete diagnostic, database intelligence, runtime reconciliation, benchmark, and repository administration sessions",
2701
+ "included_tools": [
2702
+ "analyze_change_impact",
2703
+ "analyze_impact",
2704
+ "compile_task",
2705
+ "find_callees",
2706
+ "find_callers",
2707
+ "find_db_callers",
2708
+ "find_db_columns",
2709
+ "find_db_models",
2710
+ "find_db_queries",
2711
+ "find_db_readers",
2712
+ "find_db_relationships",
2713
+ "find_db_tables",
2714
+ "find_db_writers",
2715
+ "find_references",
2716
+ "find_related_tests",
2717
+ "find_routes",
2718
+ "find_symbol",
2719
+ "find_tests",
2720
+ "get_architecture",
2721
+ "get_call_graph",
2722
+ "get_callees",
2723
+ "get_callers",
2724
+ "get_context",
2725
+ "get_db_impact",
2726
+ "get_db_schema",
2727
+ "get_db_table",
2728
+ "get_dependencies",
2729
+ "get_dependency_graph",
2730
+ "get_dependents",
2731
+ "get_evidence",
2732
+ "get_file",
2733
+ "get_file_history",
2734
+ "get_file_symbols",
2735
+ "get_git_impact",
2736
+ "get_graph",
2737
+ "get_imports",
2738
+ "get_project_structure",
2739
+ "get_recent_changes",
2740
+ "get_references",
2741
+ "get_repository_status",
2742
+ "get_resource_status",
2743
+ "get_runtime_trace",
2744
+ "get_symbol",
2745
+ "ingest_runtime_traces",
2746
+ "list_routes",
2747
+ "plan_retrieval",
2748
+ "read_file",
2749
+ "reconcile_static_runtime",
2750
+ "resolve_symbol",
2751
+ "search_code",
2752
+ "search_memory",
2753
+ "search_symbols",
2754
+ "trace_call",
2755
+ "trace_flow",
2756
+ "trace_path",
2757
+ "verify_evidence",
2758
+ ],
2759
+ },
2760
+ }
2761
+
2762
+
2763
+ # ---------------------------------------------------------------------------
2764
+ # Deterministic Task Classification & Tool Selection
2765
+ # ---------------------------------------------------------------------------
2766
+
2767
+
2768
+ @dataclass(frozen=True)
2769
+ class TaskClassificationResult:
2770
+ """Result of classifying an agent task prompt."""
2771
+
2772
+ category: AgentTaskCategory
2773
+ should_use_codegraph: bool
2774
+ primary_tool: str | None
2775
+ acceptable_tools: tuple[str, ...]
2776
+ recommended_sequence: tuple[str, ...]
2777
+ direct_inspection_acceptable: bool
2778
+ confidence: str
2779
+ rationale: str
2780
+
2781
+ def as_dict(self) -> dict[str, object]:
2782
+ return {
2783
+ "category": self.category.value,
2784
+ "should_use_codegraph": self.should_use_codegraph,
2785
+ "primary_tool": self.primary_tool,
2786
+ "acceptable_tools": list(self.acceptable_tools),
2787
+ "recommended_sequence": list(self.recommended_sequence),
2788
+ "direct_inspection_acceptable": self.direct_inspection_acceptable,
2789
+ "confidence": self.confidence,
2790
+ "rationale": self.rationale,
2791
+ }
2792
+
2793
+
2794
+ _LOCAL_EDIT_PATTERNS = re.compile(
2795
+ r"\b(typo|docstring|comment|rename local variable|format|indent|whitespace|"
2796
+ r"fix spelling|add a log line|change string literal|update constant in)\b",
2797
+ re.IGNORECASE,
2798
+ )
2799
+ _FILE_INSPECTION_PATTERNS = re.compile(
2800
+ r"\b(show lines|read lines|open lines|lines\s+\d+\s*(?:to|-|\.\.)\s*\d+\s+of)\b",
2801
+ re.IGNORECASE,
2802
+ )
2803
+ _LITERAL_SEARCH_PATTERNS = re.compile(
2804
+ r"\b(button in (?:the )?html|in (?:the )?html|in (?:the )?template|in (?:the )?jinja|"
2805
+ r"html template|ui text|button label|css selector|css class|literal string|"
2806
+ r"error message string|search text)\b",
2807
+ re.IGNORECASE,
2808
+ )
2809
+ _DIAGNOSTIC_PATTERNS = re.compile(
2810
+ r"\b(index status|repository status|is codegraph indexed|database health|freshness|resource status|mcp doctor)\b",
2811
+ re.IGNORECASE,
2812
+ )
2813
+ _ROUTE_PATTERNS = re.compile(
2814
+ r"(\b(?:which route|list routes|find routes|http endpoint|api endpoint|mounted router|url path|route handler)\b|"
2815
+ r"\bwhere is\s+/[A-Za-z0-9_./{}:-]+\s+handled\b|"
2816
+ r"(?:GET|POST|PUT|DELETE|PATCH)\s+/|"
2817
+ r"(?<![@\w])/api/)",
2818
+ re.IGNORECASE,
2819
+ )
2820
+ _TRACE_PATTERNS = re.compile(
2821
+ r"\b(trace|execution path|path from|reach(?:es)?|flow from|from route to|call chain from|"
2822
+ r"end-to-end flow|how does request reach)\b",
2823
+ re.IGNORECASE,
2824
+ )
2825
+ _TEST_PATTERNS = re.compile(
2826
+ r"\b(which tests|what tests|related tests|test coverage|tests cover|tests for|tests affected|"
2827
+ r"find tests|regression tests)\b",
2828
+ re.IGNORECASE,
2829
+ )
2830
+ _CHANGE_IMPACT_PATTERNS = re.compile(
2831
+ r"\b(change impact|blast radius|git impact|what changed in git|could it break|affected if|"
2832
+ r"what breaks if|if .* changes|impact of changing|recent commits|diff impact|refactoring impact)\b",
2833
+ re.IGNORECASE,
2834
+ )
2835
+ _PACKAGE_PATTERNS = re.compile(
2836
+ r"\b(monorepo|workspace package|package boundary|package dependencies|cross-package|"
2837
+ r"DEPENDS_ON_PACKAGE|packages depend|which package owns)\b",
2838
+ re.IGNORECASE,
2839
+ )
2840
+ _ARCHITECTURE_PATTERNS = re.compile(
2841
+ r"\b(architecture|system overview|high-level structure|module boundaries|layer overview|"
2842
+ r"project structure|architectural layers|repository structured|how is .* structured)\b",
2843
+ re.IGNORECASE,
2844
+ )
2845
+ _RELATIONSHIP_PATTERNS = re.compile(
2846
+ r"\b(what calls|who calls|callers of|callees of|what does .* call|references to|"
2847
+ r"find all references to|where is .* referenced|who imports|what imports|dependents of|"
2848
+ r"registered handlers|event listeners for|dispatches to)\b",
2849
+ re.IGNORECASE,
2850
+ )
2851
+ _DEBUG_PATTERNS = re.compile(
2852
+ r"\b(debug|why is .* failing|investigate bug|exception in|error when|null pointer|"
2853
+ r"dependency inject(?:ed|ion)|how does .* get injected|provider for|Depends\(|@inject)\b",
2854
+ re.IGNORECASE,
2855
+ )
2856
+ _SYMBOL_LOOKUP_PATTERNS = re.compile(
2857
+ r"\b(where is .* defined|find symbol|locate class|locate function|resolve symbol|"
2858
+ r"definition of|signature of)\b",
2859
+ re.IGNORECASE,
2860
+ )
2861
+ _MULTI_FILE_PATTERNS = re.compile(
2862
+ r"\b(where is .* implemented|across the repository|across files|multi-file|"
2863
+ r"how do .* interact|investigate how)\b",
2864
+ re.IGNORECASE,
2865
+ )
2866
+ _RUNTIME_RECONCILE_PATTERNS = re.compile(
2867
+ r"\b(reconcile|static and runtime|static vs runtime|differ from static|runtime conflict|not observed at runtime|runtime only)\b",
2868
+ re.IGNORECASE,
2869
+ )
2870
+ _RUNTIME_INGEST_PATTERNS = re.compile(
2871
+ r"\b(ingest .* trace|load .* trace|import .* trace|ingest runtime)\b",
2872
+ re.IGNORECASE,
2873
+ )
2874
+ _RUNTIME_TRACE_PATTERNS = re.compile(
2875
+ r"\b(at runtime|happened at runtime|runtime trace|runtime execution|observed at runtime|when .* ran)\b",
2876
+ re.IGNORECASE,
2877
+ )
2878
+ _DB_TABLES_PATTERNS = re.compile(
2879
+ r"\b(which tables|what tables|database tables|tables exist|list tables|find tables)\b",
2880
+ re.IGNORECASE,
2881
+ )
2882
+ _DB_COLUMNS_PATTERNS = re.compile(
2883
+ r"\b(what columns|which columns|columns exist|columns in|column .* defined)\b",
2884
+ re.IGNORECASE,
2885
+ )
2886
+ _DB_MODELS_PATTERNS = re.compile(
2887
+ r"\b(which orm|what orm|orm model|models map|model maps to)\b",
2888
+ re.IGNORECASE,
2889
+ )
2890
+ _DB_WRITERS_PATTERNS = re.compile(
2891
+ r"\b(writes to|write to|inserts into|updates table|deletes from|mutates table|which route writes|which service writes)\b",
2892
+ re.IGNORECASE,
2893
+ )
2894
+ _DB_READERS_PATTERNS = re.compile(
2895
+ r"\b(reads from|read from|selects from|queries table|which service reads)\b",
2896
+ re.IGNORECASE,
2897
+ )
2898
+ _DB_IMPACT_PATTERNS = re.compile(
2899
+ r"\b(drop column|rename column|alter table|database impact|table impact|column impact)\b",
2900
+ re.IGNORECASE,
2901
+ )
2902
+ _DB_SCHEMA_PATTERNS = re.compile(
2903
+ r"\b(database schema|db schema|schema overview|foreign keys in)\b",
2904
+ re.IGNORECASE,
2905
+ )
2906
+
2907
+
2908
+ _INDEX_STOPWORDS: frozenset[str] = frozenset({
2909
+ "a", "an", "and", "are", "as", "at", "be", "by", "check", "code", "do",
2910
+ "does", "examine", "explain", "file", "find", "for", "from", "function",
2911
+ "get", "how", "in", "inspect", "into", "investigate", "is", "it", "look",
2912
+ "lookup", "method", "of", "on", "or", "review", "see", "show", "tell",
2913
+ "the", "this", "to", "understand", "use", "used", "using", "what", "when",
2914
+ "where", "which", "who", "why", "with", "work", "works",
2915
+ })
2916
+
2917
+
2918
+ def _classify_via_repository_index(
2919
+ clean: str,
2920
+ con: sqlite3.Connection,
2921
+ ) -> TaskClassificationResult | None:
2922
+ """Ground bare identifiers, routes, and packages in the prompt against the SQLite index."""
2923
+ from codegraph.target_resolver import TargetType, resolve_target
2924
+
2925
+ # 1. Check route-like tokens against indexed framework_routes
2926
+ route_tokens = re.findall(r"(?<![A-Za-z0-9_])(/[A-Za-z0-9_./{}:-]+)", clean)
2927
+ for r_tok in route_tokens:
2928
+ try:
2929
+ row = con.execute(
2930
+ "SELECT route_path, handler_name FROM framework_routes "
2931
+ "WHERE route_path=? OR normalized_route=? OR endpoint_id=? LIMIT 1",
2932
+ (r_tok, r_tok, r_tok),
2933
+ ).fetchone()
2934
+ if row is not None:
2935
+ return _rule_to_result(
2936
+ CAPABILITY_MATRIX[AgentTaskCategory.ROUTE_DISCOVERY],
2937
+ confidence="HIGH",
2938
+ )
2939
+ except sqlite3.OperationalError:
2940
+ pass
2941
+
2942
+ # 2. Extract identifier and package tokens
2943
+ raw_tokens = re.findall(
2944
+ r"(@[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+|[A-Za-z_][A-Za-z0-9_.]*)",
2945
+ clean,
2946
+ )
2947
+ candidate_tokens = [
2948
+ tok for tok in raw_tokens if tok.lower() not in _INDEX_STOPWORDS and len(tok) >= 2
2949
+ ]
2950
+ if not candidate_tokens:
2951
+ return None
2952
+
2953
+ # 3. Check package edges in graph_edges
2954
+ for tok in candidate_tokens:
2955
+ try:
2956
+ pkg_row = con.execute(
2957
+ "SELECT 1 FROM graph_edges "
2958
+ "WHERE relationship IN ('DEPENDS_ON_PACKAGE', 'PACKAGE_IMPORTS', "
2959
+ "'CROSS_PACKAGE_IMPORT', 'CONTAINS_PACKAGE') "
2960
+ "AND (source=? OR target=? OR source LIKE ? OR target LIKE ?) LIMIT 1",
2961
+ (tok, tok, f"%:{tok}", f"%:{tok}"),
2962
+ ).fetchone()
2963
+ if pkg_row is not None:
2964
+ return _rule_to_result(
2965
+ CAPABILITY_MATRIX[AgentTaskCategory.PACKAGE],
2966
+ confidence="HIGH",
2967
+ )
2968
+ except sqlite3.OperationalError:
2969
+ pass
2970
+
2971
+ # 4. Resolve symbols via first-class TargetResolver
2972
+ resolved_symbols = []
2973
+ seen_canonical: set[str] = set()
2974
+ for tok in candidate_tokens:
2975
+ res = resolve_target(tok, con)
2976
+ if res.is_resolved() and res.canonical_id and res.canonical_id not in seen_canonical:
2977
+ seen_canonical.add(res.canonical_id)
2978
+ resolved_symbols.append(res)
2979
+
2980
+ if not resolved_symbols:
2981
+ return None
2982
+
2983
+ # Check if any resolved symbol is a test or in a test file
2984
+ for res in resolved_symbols:
2985
+ f_path = (res.file_path or "").lower()
2986
+ q_name = (res.qualified_name or "").lower()
2987
+ if (
2988
+ res.target_type == TargetType.TEST
2989
+ or "test_" in f_path
2990
+ or "/tests/" in f_path
2991
+ or f_path.startswith("tests/")
2992
+ or q_name.startswith("test_")
2993
+ or ".test_" in q_name
2994
+ ):
2995
+ return _rule_to_result(
2996
+ CAPABILITY_MATRIX[AgentTaskCategory.TEST_DISCOVERY],
2997
+ confidence="HIGH",
2998
+ )
2999
+
3000
+ # Check if two or more distinct repository symbols are referenced -> TRACE
3001
+ if len(resolved_symbols) >= 2:
3002
+ return _rule_to_result(
3003
+ CAPABILITY_MATRIX[AgentTaskCategory.TRACE],
3004
+ confidence="HIGH",
3005
+ )
3006
+
3007
+ # Check if single resolved symbol participates in DI (INJECTS / PROVIDES)
3008
+ for res in resolved_symbols:
3009
+ try:
3010
+ di_row = con.execute(
3011
+ "SELECT 1 FROM graph_edges "
3012
+ "WHERE relationship IN ('INJECTS', 'PROVIDES') "
3013
+ "AND (source=? OR target=? OR source=? OR target=?) LIMIT 1",
3014
+ (
3015
+ res.canonical_id or "",
3016
+ res.canonical_id or "",
3017
+ res.qualified_name or "",
3018
+ res.qualified_name or "",
3019
+ ),
3020
+ ).fetchone()
3021
+ if di_row is not None:
3022
+ return _rule_to_result(
3023
+ CAPABILITY_MATRIX[AgentTaskCategory.DEBUG],
3024
+ confidence="HIGH",
3025
+ )
3026
+ except sqlite3.OperationalError:
3027
+ pass
3028
+
3029
+ # Single resolved symbol -> SYMBOL_LOOKUP
3030
+ return _rule_to_result(
3031
+ CAPABILITY_MATRIX[AgentTaskCategory.SYMBOL_LOOKUP],
3032
+ confidence="HIGH",
3033
+ )
3034
+
3035
+
3036
+ def classify_agent_task(
3037
+ prompt: str,
3038
+ file_count_hint: int | None = None,
3039
+ con: sqlite3.Connection | None = None,
3040
+ ) -> TaskClassificationResult:
3041
+ """Deterministically classify a developer or agent task into one of the 13 categories.
3042
+
3043
+ When `con` (an indexed SQLite connection) is provided, prompts that do not match
3044
+ explicit lexical intent patterns are grounded against indexed routes, packages,
3045
+ DI edges, test symbols, and canonical symbol definitions in sub-millisecond time.
3046
+ """
3047
+ clean = (prompt or "").strip()
3048
+ if not clean:
3049
+ rule = CAPABILITY_MATRIX[AgentTaskCategory.EXPLANATION]
3050
+ return _rule_to_result(rule, confidence="LOW")
3051
+
3052
+ # 1. Explicit single-file local edit check
3053
+ if _LOCAL_EDIT_PATTERNS.search(clean) and not _RELATIONSHIP_PATTERNS.search(clean):
3054
+ if file_count_hint is None or file_count_hint <= 1:
3055
+ return _rule_to_result(CAPABILITY_MATRIX[AgentTaskCategory.LOCAL_EDIT], confidence="HIGH")
3056
+
3057
+ # 1b. Explicit bounded line-range file opening (e.g. "Show lines 40 to 110 of dashboard.html")
3058
+ if _FILE_INSPECTION_PATTERNS.search(clean):
3059
+ file_rule = TaskCapabilityRule(
3060
+ category=AgentTaskCategory.SYMBOL_LOOKUP,
3061
+ should_use_codegraph=True,
3062
+ primary_tool="get_file",
3063
+ acceptable_tools=("get_file", "read_file"),
3064
+ recommended_sequence=("get_file",),
3065
+ direct_inspection_acceptable=True,
3066
+ rationale="Use get_file(path, start_line, end_line) to open targeted line ranges of repository files.",
3067
+ )
3068
+ return _rule_to_result(file_rule, confidence="HIGH")
3069
+
3070
+ # 1c. Explicit literal text / HTML / template / UI string search
3071
+ if _LITERAL_SEARCH_PATTERNS.search(clean):
3072
+ lit_rule = TaskCapabilityRule(
3073
+ category=AgentTaskCategory.MULTI_FILE_INVESTIGATION,
3074
+ should_use_codegraph=True,
3075
+ primary_tool="search_code",
3076
+ acceptable_tools=("search_code", "get_file"),
3077
+ recommended_sequence=("search_code", "get_file"),
3078
+ direct_inspection_acceptable=False,
3079
+ rationale="Use search_code for literal text, HTML/Jinja template strings, UI labels, and config keys, then get_file to inspect lines.",
3080
+ )
3081
+ return _rule_to_result(lit_rule, confidence="HIGH")
3082
+
3083
+ # 2. Diagnostic / health check
3084
+ if _DIAGNOSTIC_PATTERNS.search(clean):
3085
+ return _rule_to_result(CAPABILITY_MATRIX[AgentTaskCategory.DIAGNOSTIC], confidence="HIGH")
3086
+
3087
+ # 3. Trace check (especially route -> handler -> service or path between symbols)
3088
+ if _TRACE_PATTERNS.search(clean):
3089
+ return _rule_to_result(CAPABILITY_MATRIX[AgentTaskCategory.TRACE], confidence="HIGH")
3090
+
3091
+ # 4. Route discovery check
3092
+ if _ROUTE_PATTERNS.search(clean):
3093
+ return _rule_to_result(CAPABILITY_MATRIX[AgentTaskCategory.ROUTE_DISCOVERY], confidence="HIGH")
3094
+
3095
+ # 5. Test discovery check (prioritized when explicitly asking about tests)
3096
+ if _TEST_PATTERNS.search(clean) and not _CHANGE_IMPACT_PATTERNS.search(clean):
3097
+ return _rule_to_result(CAPABILITY_MATRIX[AgentTaskCategory.TEST_DISCOVERY], confidence="HIGH")
3098
+
3099
+ # 6. Change impact check
3100
+ if _CHANGE_IMPACT_PATTERNS.search(clean):
3101
+ if _TEST_PATTERNS.search(clean) and "git" not in clean.lower() and "commit" not in clean.lower():
3102
+ return _rule_to_result(CAPABILITY_MATRIX[AgentTaskCategory.TEST_DISCOVERY], confidence="HIGH")
3103
+ return _rule_to_result(CAPABILITY_MATRIX[AgentTaskCategory.CHANGE_IMPACT], confidence="HIGH")
3104
+
3105
+ # 7. Package / workspace boundary check
3106
+ if _PACKAGE_PATTERNS.search(clean):
3107
+ return _rule_to_result(CAPABILITY_MATRIX[AgentTaskCategory.PACKAGE], confidence="HIGH")
3108
+
3109
+ # 8. Architecture overview check
3110
+ if _ARCHITECTURE_PATTERNS.search(clean):
3111
+ return _rule_to_result(CAPABILITY_MATRIX[AgentTaskCategory.ARCHITECTURE], confidence="HIGH")
3112
+
3113
+ # 9. Relationship check (callers, callees, references, imports, dependents, events, registries)
3114
+ if _RELATIONSHIP_PATTERNS.search(clean):
3115
+ rule = CAPABILITY_MATRIX[AgentTaskCategory.RELATIONSHIP]
3116
+ lower = clean.lower()
3117
+ if "callee" in lower or re.search(r"what does .+ call", lower):
3118
+ custom_rule = TaskCapabilityRule(
3119
+ category=AgentTaskCategory.RELATIONSHIP,
3120
+ should_use_codegraph=True,
3121
+ primary_tool="get_callees",
3122
+ acceptable_tools=rule.acceptable_tools,
3123
+ recommended_sequence=("resolve_symbol", "get_callees"),
3124
+ direct_inspection_acceptable=False,
3125
+ rationale=rule.rationale,
3126
+ )
3127
+ return _rule_to_result(custom_rule, confidence="HIGH")
3128
+ if "reference" in lower or "registered" in lower or "event listener" in lower or "dispatch" in lower:
3129
+ custom_rule = TaskCapabilityRule(
3130
+ category=AgentTaskCategory.RELATIONSHIP,
3131
+ should_use_codegraph=True,
3132
+ primary_tool="get_references",
3133
+ acceptable_tools=rule.acceptable_tools,
3134
+ recommended_sequence=("resolve_symbol", "get_references"),
3135
+ direct_inspection_acceptable=False,
3136
+ rationale=rule.rationale,
3137
+ )
3138
+ return _rule_to_result(custom_rule, confidence="HIGH")
3139
+ if "what imports" in lower or "who imports" in lower or "dependents of" in lower:
3140
+ custom_rule = TaskCapabilityRule(
3141
+ category=AgentTaskCategory.RELATIONSHIP,
3142
+ should_use_codegraph=True,
3143
+ primary_tool="get_dependents",
3144
+ acceptable_tools=rule.acceptable_tools,
3145
+ recommended_sequence=("resolve_symbol", "get_dependents"),
3146
+ direct_inspection_acceptable=False,
3147
+ rationale=rule.rationale,
3148
+ )
3149
+ return _rule_to_result(custom_rule, confidence="HIGH")
3150
+ return _rule_to_result(rule, confidence="HIGH")
3151
+
3152
+ # 10. Debug / DI / bug investigation check
3153
+ if _DEBUG_PATTERNS.search(clean):
3154
+ return _rule_to_result(CAPABILITY_MATRIX[AgentTaskCategory.DEBUG], confidence="HIGH")
3155
+
3156
+ # 11. Symbol lookup check
3157
+ if _SYMBOL_LOOKUP_PATTERNS.search(clean):
3158
+ return _rule_to_result(CAPABILITY_MATRIX[AgentTaskCategory.SYMBOL_LOOKUP], confidence="HIGH")
3159
+
3160
+ # 12. Optional SQLite index-grounded classification (when con is provided)
3161
+ if con is not None:
3162
+ indexed_result = _classify_via_repository_index(clean, con)
3163
+ if indexed_result is not None:
3164
+ return indexed_result
3165
+
3166
+ # 13. Multi-file investigation check
3167
+ if _MULTI_FILE_PATTERNS.search(clean) or (file_count_hint is not None and file_count_hint > 1):
3168
+ return _rule_to_result(CAPABILITY_MATRIX[AgentTaskCategory.MULTI_FILE_INVESTIGATION], confidence="MEDIUM")
3169
+
3170
+ # 14. Default: EXPLANATION
3171
+ return _rule_to_result(CAPABILITY_MATRIX[AgentTaskCategory.EXPLANATION], confidence="MEDIUM")
3172
+
3173
+
3174
+ def select_agent_tool(
3175
+ prompt: str,
3176
+ con: sqlite3.Connection | None = None,
3177
+ ) -> dict[str, object]:
3178
+ """Route a natural-language agent question to its canonical `ROUTING_MANIFEST` tool and sequence.
3179
+
3180
+ Uses the clean `find_*` / `get_*` / `trace_*` agent-profile vocabulary defined in `ROUTING_MANIFEST`.
3181
+ """
3182
+ cls = classify_agent_task(prompt, con=con)
3183
+ clean = (prompt or "").strip().lower()
3184
+
3185
+ if _RUNTIME_RECONCILE_PATTERNS.search(clean):
3186
+ routing_key = "runtime_reconcile"
3187
+ primary = ROUTING_MANIFEST["runtime_reconcile"]
3188
+ seq: tuple[str, ...] = ("reconcile_static_runtime", "get_file")
3189
+ acceptable: tuple[str, ...] = ("reconcile_static_runtime", "get_runtime_trace")
3190
+ elif _RUNTIME_INGEST_PATTERNS.search(clean):
3191
+ routing_key = "runtime_ingest"
3192
+ primary = ROUTING_MANIFEST["runtime_ingest"]
3193
+ seq = ("ingest_runtime_traces", "get_runtime_trace")
3194
+ acceptable = ("ingest_runtime_traces",)
3195
+ elif _RUNTIME_TRACE_PATTERNS.search(clean):
3196
+ routing_key = "runtime_trace"
3197
+ primary = ROUTING_MANIFEST["runtime_trace"]
3198
+ seq = ("get_runtime_trace", "reconcile_static_runtime")
3199
+ acceptable = ("get_runtime_trace", "reconcile_static_runtime")
3200
+ elif _DB_TABLES_PATTERNS.search(clean):
3201
+ routing_key = "db_tables"
3202
+ primary = ROUTING_MANIFEST["db_tables"]
3203
+ seq = ("find_db_tables", "get_db_table")
3204
+ acceptable = ("find_db_tables", "get_db_schema", "get_db_table")
3205
+ elif _DB_COLUMNS_PATTERNS.search(clean):
3206
+ routing_key = "db_columns"
3207
+ primary = ROUTING_MANIFEST["db_columns"]
3208
+ seq = ("find_db_columns", "get_db_table")
3209
+ acceptable = ("find_db_columns", "get_db_table")
3210
+ elif _DB_MODELS_PATTERNS.search(clean):
3211
+ routing_key = "db_models"
3212
+ primary = ROUTING_MANIFEST["db_models"]
3213
+ seq = ("find_db_models", "get_db_table")
3214
+ acceptable = ("find_db_models", "get_db_table")
3215
+ elif _DB_WRITERS_PATTERNS.search(clean):
3216
+ routing_key = "db_writers"
3217
+ primary = ROUTING_MANIFEST["db_writers"]
3218
+ seq = ("find_db_writers", "find_db_callers")
3219
+ acceptable = ("find_db_writers", "find_db_callers", "find_db_queries")
3220
+ elif _DB_READERS_PATTERNS.search(clean):
3221
+ routing_key = "db_readers"
3222
+ primary = ROUTING_MANIFEST["db_readers"]
3223
+ seq = ("find_db_readers", "find_db_callers")
3224
+ acceptable = ("find_db_readers", "find_db_callers", "find_db_queries")
3225
+ elif _DB_IMPACT_PATTERNS.search(clean):
3226
+ routing_key = "db_impact"
3227
+ primary = ROUTING_MANIFEST["db_impact"]
3228
+ seq = ("get_db_impact", "find_tests")
3229
+ acceptable = ("get_db_impact", "analyze_impact")
3230
+ elif _DB_SCHEMA_PATTERNS.search(clean):
3231
+ routing_key = "db_schema"
3232
+ primary = ROUTING_MANIFEST["db_schema"]
3233
+ seq = ("get_db_schema", "get_db_table")
3234
+ acceptable = ("get_db_schema", "find_db_tables", "find_db_relationships")
3235
+ elif _FILE_INSPECTION_PATTERNS.search(clean):
3236
+ routing_key = "source_inspection"
3237
+ primary = ROUTING_MANIFEST["source_inspection"]
3238
+ seq = ("get_file",)
3239
+ acceptable = ("get_file", "read_file")
3240
+ elif _LITERAL_SEARCH_PATTERNS.search(clean):
3241
+ routing_key = "literal_search"
3242
+ primary = ROUTING_MANIFEST["literal_search"]
3243
+ seq = ("search_code", "get_file")
3244
+ acceptable = ("search_code",)
3245
+ elif cls.category == AgentTaskCategory.ARCHITECTURE:
3246
+ routing_key = "architecture_analysis"
3247
+ primary = ROUTING_MANIFEST["architecture_analysis"]
3248
+ seq = ("get_architecture",)
3249
+ acceptable = ("get_architecture", "get_context")
3250
+ elif cls.category == AgentTaskCategory.SYMBOL_LOOKUP:
3251
+ routing_key = "symbol_discovery"
3252
+ primary = ROUTING_MANIFEST["symbol_discovery"]
3253
+ seq = ("find_symbol", "get_symbol")
3254
+ acceptable = ("find_symbol", "resolve_symbol", "get_symbol", "search_symbols")
3255
+ elif cls.category == AgentTaskCategory.RELATIONSHIP:
3256
+ if cls.primary_tool == "get_callees":
3257
+ routing_key = "callee_discovery"
3258
+ primary = ROUTING_MANIFEST["callee_discovery"]
3259
+ seq = ("find_callees",)
3260
+ acceptable = ("find_callees", "get_callees")
3261
+ elif cls.primary_tool == "get_references":
3262
+ routing_key = "reference_discovery"
3263
+ primary = ROUTING_MANIFEST["reference_discovery"]
3264
+ seq = ("find_references",)
3265
+ acceptable = ("find_references", "get_references")
3266
+ else:
3267
+ routing_key = "caller_discovery"
3268
+ primary = ROUTING_MANIFEST["caller_discovery"]
3269
+ seq = ("find_callers",)
3270
+ acceptable = ("find_callers", "get_callers")
3271
+ elif cls.category == AgentTaskCategory.TRACE:
3272
+ routing_key = "relationship_trace"
3273
+ primary = ROUTING_MANIFEST["relationship_trace"]
3274
+ if "route" in clean or "api" in clean or "endpoint" in clean:
3275
+ seq = ("find_routes", "trace_path")
3276
+ else:
3277
+ seq = ("trace_path",)
3278
+ acceptable = ("trace_path", "find_routes", "list_routes", "trace_flow")
3279
+ elif cls.category == AgentTaskCategory.ROUTE_DISCOVERY:
3280
+ routing_key = "route_discovery"
3281
+ primary = ROUTING_MANIFEST["route_discovery"]
3282
+ seq = ("find_routes", "get_file")
3283
+ acceptable = ("find_routes", "list_routes", "search_code")
3284
+ elif cls.category == AgentTaskCategory.TEST_DISCOVERY:
3285
+ routing_key = "test_discovery"
3286
+ primary = ROUTING_MANIFEST["test_discovery"]
3287
+ seq = ("find_tests",)
3288
+ acceptable = ("find_tests", "find_related_tests")
3289
+ elif cls.category == AgentTaskCategory.CHANGE_IMPACT:
3290
+ routing_key = "change_impact"
3291
+ primary = ROUTING_MANIFEST["change_impact"]
3292
+ seq = ("get_git_impact", "find_tests")
3293
+ acceptable = ("get_git_impact", "analyze_impact", "analyze_change_impact")
3294
+ else:
3295
+ routing_key = "context_synthesis"
3296
+ primary = ROUTING_MANIFEST["context_synthesis"]
3297
+ seq = ("get_context", "get_file")
3298
+ acceptable = ("get_context",)
3299
+
3300
+ return {
3301
+ "prompt": prompt,
3302
+ "category": cls.category.value,
3303
+ "routing_key": routing_key,
3304
+ "selected_tool": primary,
3305
+ "recommended_sequence": list(seq),
3306
+ "acceptable_tools": list(acceptable),
3307
+ "confidence": cls.confidence,
3308
+ }
3309
+
3310
+
3311
+ def _rule_to_result(rule: TaskCapabilityRule, confidence: str) -> TaskClassificationResult:
3312
+ return TaskClassificationResult(
3313
+ category=rule.category,
3314
+ should_use_codegraph=rule.should_use_codegraph,
3315
+ primary_tool=rule.primary_tool,
3316
+ acceptable_tools=rule.acceptable_tools,
3317
+ recommended_sequence=rule.recommended_sequence,
3318
+ direct_inspection_acceptable=rule.direct_inspection_acceptable,
3319
+ confidence=confidence,
3320
+ rationale=rule.rationale,
3321
+ )
3322
+
3323
+
3324
+ # ---------------------------------------------------------------------------
3325
+ # Epistemic & Fallback Policy (Section 11)
3326
+ # ---------------------------------------------------------------------------
3327
+
3328
+
3329
+ @dataclass(frozen=True)
3330
+ class FallbackDecision:
3331
+ """Structured decision for when and how an agent may fall back to direct repository tools."""
3332
+
3333
+ fallback_allowed: bool
3334
+ fallback_reason: str
3335
+ epistemic_status: str # VERIFIED | POSSIBLE | UNKNOWN | AMBIGUOUS | STALE | DISCONNECTED
3336
+ relationship_exists_claim_allowed: bool
3337
+ no_relationship_claim_allowed: bool
3338
+ required_agent_action: str
3339
+
3340
+ def as_dict(self) -> dict[str, object]:
3341
+ return {
3342
+ "fallback_allowed": self.fallback_allowed,
3343
+ "fallback_reason": self.fallback_reason,
3344
+ "epistemic_status": self.epistemic_status,
3345
+ "relationship_exists_claim_allowed": self.relationship_exists_claim_allowed,
3346
+ "no_relationship_claim_allowed": self.no_relationship_claim_allowed,
3347
+ "required_agent_action": self.required_agent_action,
3348
+ }
3349
+
3350
+
3351
+ def evaluate_fallback_policy(
3352
+ mcp_state: str = "CONNECTED",
3353
+ evidence_class: str | None = None,
3354
+ ambiguity_state: str = "CLEAR",
3355
+ freshness: str = "FRESH",
3356
+ ) -> FallbackDecision:
3357
+ """Evaluate safe fallback behavior while strictly preserving epistemic invariants."""
3358
+ state_upper = mcp_state.upper()
3359
+ if state_upper in ("DISCONNECTED", "UNAVAILABLE", "SERVER_FAILED", "TOOLS_UNAVAILABLE"):
3360
+ return FallbackDecision(
3361
+ fallback_allowed=True,
3362
+ fallback_reason=f"CodeGraph MCP is {state_upper}",
3363
+ epistemic_status="DISCONNECTED",
3364
+ relationship_exists_claim_allowed=False,
3365
+ no_relationship_claim_allowed=False,
3366
+ required_agent_action="Use direct repository search/file reads and verify claims manually.",
3367
+ )
3368
+
3369
+ if freshness.upper() == "STALE":
3370
+ return FallbackDecision(
3371
+ fallback_allowed=True,
3372
+ fallback_reason="CodeGraph index is STALE relative to working tree modifications",
3373
+ epistemic_status="STALE",
3374
+ relationship_exists_claim_allowed=False,
3375
+ no_relationship_claim_allowed=False,
3376
+ required_agent_action="Verify modified files directly with read_file or re-index before relying on stale edges.",
3377
+ )
3378
+
3379
+ amb_upper = ambiguity_state.upper()
3380
+ if amb_upper in ("AMBIGUOUS", "CONFLICT"):
3381
+ return FallbackDecision(
3382
+ fallback_allowed=True,
3383
+ fallback_reason=f"Target resolution is {amb_upper} with multiple candidate symbols",
3384
+ epistemic_status="AMBIGUOUS",
3385
+ relationship_exists_claim_allowed=False,
3386
+ no_relationship_claim_allowed=False,
3387
+ required_agent_action=(
3388
+ "Inspect candidate alternatives returned by CodeGraph; never arbitrarily pick one without disambiguating."
3389
+ ),
3390
+ )
3391
+
3392
+ ev_upper = (evidence_class or "AST_VERIFIED").upper()
3393
+ if ev_upper == "UNKNOWN":
3394
+ return FallbackDecision(
3395
+ fallback_allowed=True,
3396
+ fallback_reason="Static analysis returned UNKNOWN (dynamic dispatch or unresolvable target)",
3397
+ epistemic_status="UNKNOWN",
3398
+ relationship_exists_claim_allowed=False,
3399
+ no_relationship_claim_allowed=False, # CRITICAL: UNKNOWN != "there is no relationship"
3400
+ required_agent_action=(
3401
+ "Treat UNKNOWN as 'static analysis could not establish the relationship' (NOT 'no relationship exists'). "
3402
+ "Use targeted read_file on the call site to inspect dynamic behavior."
3403
+ ),
3404
+ )
3405
+
3406
+ if ev_upper == "POSSIBLE":
3407
+ return FallbackDecision(
3408
+ fallback_allowed=True,
3409
+ fallback_reason="Static analysis returned POSSIBLE candidate relationship",
3410
+ epistemic_status="POSSIBLE",
3411
+ relationship_exists_claim_allowed=False, # Must not claim as verified fact without source check
3412
+ no_relationship_claim_allowed=False,
3413
+ required_agent_action=(
3414
+ "Report relationship as POSSIBLE (candidate) and perform targeted read_file on cited lines to confirm."
3415
+ ),
3416
+ )
3417
+
3418
+ return FallbackDecision(
3419
+ fallback_allowed=False,
3420
+ fallback_reason="Verified static evidence returned by CodeGraph",
3421
+ epistemic_status="VERIFIED",
3422
+ relationship_exists_claim_allowed=True,
3423
+ no_relationship_claim_allowed=False,
3424
+ required_agent_action=(
3425
+ "Rely on verified CodeGraph evidence; use targeted read_file only if line-by-line implementation details are needed."
3426
+ ),
3427
+ )
3428
+
3429
+
3430
+ # ---------------------------------------------------------------------------
3431
+ # Deterministic Capability Manifest (Section 5 & Section 19)
3432
+ # ---------------------------------------------------------------------------
3433
+
3434
+
3435
+ def get_capability_manifest() -> dict[str, Any]:
3436
+ """Return a deterministic, environment-independent capability manifest for AI coding agents."""
3437
+ capabilities = sorted({spec.capability for spec in TOOL_CAPABILITY_REGISTRY})
3438
+ tool_mappings = {
3439
+ spec.tool_name: spec.as_dict()
3440
+ for spec in sorted(TOOL_CAPABILITY_REGISTRY, key=lambda s: s.tool_name)
3441
+ }
3442
+ task_mappings = {
3443
+ cat.value: CAPABILITY_MATRIX[cat].as_dict()
3444
+ for cat in sorted(CAPABILITY_MATRIX.keys(), key=lambda c: c.value)
3445
+ }
3446
+
3447
+ return {
3448
+ "schema_version": "1.0",
3449
+ "server_name": "codegraph",
3450
+ "version": __version__,
3451
+ "summary": (
3452
+ "CodeGraph is strongest for repository relationships, database intelligence, runtime reconciliation, "
3453
+ "architecture, routes, tests, DI, packages, change impact, literal repository text search, and bounded file inspection."
3454
+ ),
3455
+ "selection_principle": (
3456
+ "Use CodeGraph when repository relationships, database schemas/queries, runtime traces, multi-file reasoning, or literal text/template search make it materially useful. "
3457
+ "For simple single-file local edits, direct file inspection is acceptable."
3458
+ ),
3459
+ "routing_manifest": dict(ROUTING_MANIFEST),
3460
+ "default_agent_profile_tools": list(DEFAULT_AGENT_PROFILE_TOOLS),
3461
+ "capabilities": capabilities,
3462
+ "profiles": {k: MCP_PROFILE_RECOMMENDATIONS[k] for k in sorted(MCP_PROFILE_RECOMMENDATIONS.keys())},
3463
+ "tool_mappings": tool_mappings,
3464
+ "task_mappings": task_mappings,
3465
+ "recommended_sequences": [dict(seq) for seq in RECOMMENDED_TOOL_SEQUENCES],
3466
+ "recommended_usage_order": [
3467
+ "1. Classify the task (LOCAL_EDIT vs literal text/template search vs database/runtime vs symbol/relationship/multi-file task).",
3468
+ "2. If LOCAL_EDIT on a single known file, use direct file inspection (get_file or read_file).",
3469
+ "3. If searching for literal text, HTML/Jinja IDs, UI strings, CSS selectors, or config keys, call search_code.",
3470
+ "4. If querying database tables, columns, ORM models, or SQL queries, call find_db_tables, get_db_table, find_db_callers, find_db_writers, or get_db_impact.",
3471
+ "5. If symbol identity is needed, call find_symbol or resolve_symbol (or search_symbols if spelling is unknown).",
3472
+ "6. Call the smallest targeted CodeGraph tool (find_callers/get_callers, find_callees/get_callees, trace_path, find_routes/list_routes, find_tests/find_related_tests, get_git_impact, or get_context).",
3473
+ "7. Inspect structured evidence and epistemic labels (AST_VERIFIED, STATIC_VERIFIED, FRAMEWORK_VERIFIED, DATAFLOW_VERIFIED, RUNTIME_OBSERVED, RUNTIME_UNOBSERVED, POSSIBLE, UNKNOWN, AMBIGUOUS).",
3474
+ "8. Use targeted get_file(path, start_line, end_line) on specific line ranges when exact source lines are needed.",
3475
+ ],
3476
+ "evidence_semantics": {
3477
+ "AST_VERIFIED": "Directly proven by syntax tree definition, import, or call.",
3478
+ "STATIC_VERIFIED": "Directly proven by static SQL DDL/DML parsing or migration schema definitions.",
3479
+ "FRAMEWORK_VERIFIED": "Proven by deterministic framework semantics (FastAPI/Flask/Django/Express routes, ORM models, Celery tasks, Click commands, DI).",
3480
+ "DATAFLOW_VERIFIED": "Proven by conservative local variable, container, or registry binding resolution.",
3481
+ "RUNTIME_OBSERVED": "Observed in an ingested runtime trace (OpenTelemetry, JSONL events, or SQL query logs); never converted into static AST_VERIFIED proof.",
3482
+ "RUNTIME_UNOBSERVED": "Static relationship was not observed in the ingested runtime trace sample; never proves the path is impossible.",
3483
+ "POSSIBLE": "Plausible static candidate (e.g., conditional binding, dynamic SQL table interpolation, or interface match); never treat as verified fact without source confirmation.",
3484
+ "UNKNOWN": "Static analysis could not establish the target (e.g., dynamic getattr/eval or opaque SQL string). UNKNOWN never means 'no relationship exists'.",
3485
+ "AMBIGUOUS": "Multiple symbols or tables match the target name; inspect alternatives rather than guessing.",
3486
+ "STALE": "File on disk was modified since indexing; verify with get_file/read_file or re-index.",
3487
+ },
3488
+ "limitations": [
3489
+ "Does not execute repository code, launch user applications, or connect to live databases.",
3490
+ "Does not resolve arbitrary runtime string reflection (eval, getattr with dynamic strings) without runtime trace ingestion.",
3491
+ "Does not see external repositories or unindexed third-party registry source code.",
3492
+ "Does not replace reading exact function body lines when editing complex business logic.",
3493
+ ],
3494
+ }
3495
+
3496
+
3497
+ def get_tool_capability(tool_name: str) -> ToolCapabilitySpec | None:
3498
+ """Look up the declarative capability spec for a tool by name."""
3499
+ return _TOOL_BY_NAME.get(tool_name)
3500
+