graphite-code 0.3.0__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 (112) hide show
  1. graphite/__init__.py +41 -0
  2. graphite/__main__.py +7 -0
  3. graphite/_cleanup_worker.py +525 -0
  4. graphite/activation.py +164 -0
  5. graphite/agent_hooks.py +577 -0
  6. graphite/agent_settings.py +226 -0
  7. graphite/analyze.py +146 -0
  8. graphite/answer_contract.py +420 -0
  9. graphite/bootstrap.py +210 -0
  10. graphite/buildlock.py +99 -0
  11. graphite/cache.py +131 -0
  12. graphite/channel.py +1325 -0
  13. graphite/cli.py +3053 -0
  14. graphite/cluster.py +111 -0
  15. graphite/config.py +209 -0
  16. graphite/context.py +355 -0
  17. graphite/daemon.py +745 -0
  18. graphite/daemon_health.py +733 -0
  19. graphite/debt.py +118 -0
  20. graphite/dependency_install.py +1597 -0
  21. graphite/detach.py +33 -0
  22. graphite/doctor.py +678 -0
  23. graphite/doctor_probes.py +2100 -0
  24. graphite/engine_identity.py +238 -0
  25. graphite/export/__init__.py +6 -0
  26. graphite/export/html.py +244 -0
  27. graphite/export/json.py +39 -0
  28. graphite/export/md.py +68 -0
  29. graphite/extract/__init__.py +4 -0
  30. graphite/extract/ast.py +1964 -0
  31. graphite/freshness.py +127 -0
  32. graphite/git.py +406 -0
  33. graphite/graph.py +117 -0
  34. graphite/graph_io.py +188 -0
  35. graphite/health.py +147 -0
  36. graphite/hook_entry.py +68 -0
  37. graphite/hookinstall.py +224 -0
  38. graphite/hookshim.py +86 -0
  39. graphite/incident_ledger.py +247 -0
  40. graphite/ingest.py +279 -0
  41. graphite/init.py +791 -0
  42. graphite/io.py +32 -0
  43. graphite/listing.py +51 -0
  44. graphite/llm.py +518 -0
  45. graphite/llm_probe.py +157 -0
  46. graphite/mcp.py +7 -0
  47. graphite/mcp_server.py +450 -0
  48. graphite/natural_query.py +252 -0
  49. graphite/overlays.py +713 -0
  50. graphite/probe_process.py +879 -0
  51. graphite/probe_workspace.py +728 -0
  52. graphite/process_contracts.py +22 -0
  53. graphite/provider_observer.py +397 -0
  54. graphite/query.py +646 -0
  55. graphite/query_plan.py +97 -0
  56. graphite/replacement_audit.py +291 -0
  57. graphite/resolve.py +660 -0
  58. graphite/review.py +782 -0
  59. graphite/routing/__init__.py +5 -0
  60. graphite/routing/approval.py +362 -0
  61. graphite/routing/classifier.py +169 -0
  62. graphite/routing/claude_executor.py +419 -0
  63. graphite/routing/claude_probe.py +102 -0
  64. graphite/routing/cli_identity.py +84 -0
  65. graphite/routing/codex_executor.py +383 -0
  66. graphite/routing/codex_probe.py +93 -0
  67. graphite/routing/context_builder.py +327 -0
  68. graphite/routing/contracts.py +802 -0
  69. graphite/routing/diff_policy.py +468 -0
  70. graphite/routing/edit_apply.py +166 -0
  71. graphite/routing/effort.py +43 -0
  72. graphite/routing/lifecycle.py +771 -0
  73. graphite/routing/lifecycle_operator.py +227 -0
  74. graphite/routing/lifecycle_service.py +555 -0
  75. graphite/routing/lifecycle_storage.py +977 -0
  76. graphite/routing/ollama_executor.py +341 -0
  77. graphite/routing/ollama_probe.py +72 -0
  78. graphite/routing/openrouter_executor.py +338 -0
  79. graphite/routing/openrouter_probe.py +188 -0
  80. graphite/routing/policy.py +815 -0
  81. graphite/routing/probe_runner.py +543 -0
  82. graphite/routing/process_runner.py +523 -0
  83. graphite/routing/profiles.py +554 -0
  84. graphite/routing/prompt.py +58 -0
  85. graphite/routing/registry.py +444 -0
  86. graphite/routing/route_pool.py +629 -0
  87. graphite/routing/route_pool_execution.py +275 -0
  88. graphite/routing/schema_validation.py +169 -0
  89. graphite/routing/service.py +1263 -0
  90. graphite/routing/settings.py +99 -0
  91. graphite/routing/shadow.py +201 -0
  92. graphite/routing/storage.py +4001 -0
  93. graphite/routing/telemetry.py +346 -0
  94. graphite/routing/worktree.py +259 -0
  95. graphite/routing/zai_edit.py +113 -0
  96. graphite/routing/zai_executor.py +191 -0
  97. graphite/routing/zai_probe.py +126 -0
  98. graphite/savings.py +84 -0
  99. graphite/ts_bridge.py +142 -0
  100. graphite/ts_resolver.mjs +314 -0
  101. graphite/typescript_activation.py +1586 -0
  102. graphite/usage_ledger.py +156 -0
  103. graphite/validation.py +148 -0
  104. graphite/watch.py +167 -0
  105. graphite/windows_job.py +368 -0
  106. graphite/windows_startup.py +144 -0
  107. graphite/windows_task.py +212 -0
  108. graphite_code-0.3.0.dist-info/METADATA +743 -0
  109. graphite_code-0.3.0.dist-info/RECORD +112 -0
  110. graphite_code-0.3.0.dist-info/WHEEL +4 -0
  111. graphite_code-0.3.0.dist-info/entry_points.txt +3 -0
  112. graphite_code-0.3.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,252 @@
1
+ """Deterministic natural-language front-end for canonical queries.
2
+
3
+ `graphite query --natural "<question>"` runs a fixed, grammar-based intent
4
+ parser: an ordered table of anchored regular expressions, first match wins.
5
+ Recognized question shapes translate into canonical query plans (executed
6
+ through the same validated path as structured queries) or into a suggestion
7
+ for the canonical command that answers them (impact/context/tests). Anything
8
+ else falls back to deterministic ranked search, whose results serve as
9
+ clarification candidates.
10
+
11
+ Canonical-safe by construction: no providers, no network, no configuration
12
+ knobs — the grammar below is the entire capability, and it is published via
13
+ `graphite capabilities` so agents never have to guess phrasings.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import re
18
+ from dataclasses import dataclass
19
+ from typing import Any
20
+
21
+ import networkx as nx
22
+
23
+ from .query import _VERB_INDEX, execute_plan, search_graph
24
+ from .query_plan import make_plan
25
+
26
+ RESULT_SCHEMA_VERSION = 1
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class NaturalRule:
31
+ """One grammar rule: intent plus the anchored pattern that recognizes it."""
32
+
33
+ intent: str # query operation name, or "impact" | "tests" | "context"
34
+ kind: str # "operation" (translates to a plan) | "command" (suggests a CLI command)
35
+ template: str # human-readable form published via capabilities
36
+ pattern: re.Pattern[str]
37
+
38
+
39
+ def _rule(intent: str, kind: str, template: str, pattern: str) -> NaturalRule:
40
+ return NaturalRule(intent, kind, template, re.compile(pattern))
41
+
42
+
43
+ # Ordered, first match wins; every pattern is fullmatched against the
44
+ # normalized (lowercased, squashed, unpunctuated) question.
45
+ NATURAL_RULES: tuple[NaturalRule, ...] = (
46
+ _rule("callers", "operation", "who calls <symbol>", r"(?:who|what) calls (?P<node>.+)"),
47
+ _rule("callers", "operation", "callers of <symbol>", r"(?:list |show )?callers of (?P<node>.+)"),
48
+ _rule("callers", "operation", "who uses <symbol>", r"(?:who|what) uses (?P<node>.+)"),
49
+ _rule("calls", "operation", "what does <symbol> call", r"what does (?P<node>.+?) call"),
50
+ _rule("calls", "operation", "callees of <symbol>", r"(?:list |show )?callees of (?P<node>.+)"),
51
+ _rule(
52
+ "depends-on", "operation", "what does <node> depend on",
53
+ r"what does (?P<node>.+?) depend on",
54
+ ),
55
+ _rule(
56
+ "depends-on", "operation", "dependencies of <node>",
57
+ r"(?:list |show )?dependencies of (?P<node>.+)",
58
+ ),
59
+ _rule(
60
+ "depends-on", "operation", "what does <node> import",
61
+ r"what does (?P<node>.+?) import",
62
+ ),
63
+ _rule("imported-by", "operation", "what depends on <node>", r"what depends on (?P<node>.+)"),
64
+ _rule("imported-by", "operation", "who imports <node>", r"(?:who|what) imports (?P<node>.+)"),
65
+ _rule(
66
+ "imported-by", "operation", "dependents of <node>",
67
+ r"(?:list |show )?dependents of (?P<node>.+)",
68
+ ),
69
+ _rule(
70
+ "imported-by", "operation", "where is <node> imported",
71
+ r"where is (?P<node>.+?) (?:imported|used)",
72
+ ),
73
+ _rule(
74
+ "reaches", "operation", "can <a> reach <b>",
75
+ r"can (?P<source>.+?) reach (?P<target>.+)",
76
+ ),
77
+ _rule(
78
+ "reaches", "operation", "is there a call path from <a> to <b>",
79
+ r"is there a call path from (?P<source>.+?) to (?P<target>.+)",
80
+ ),
81
+ _rule(
82
+ "reaches", "operation", "does <a> call <b>",
83
+ r"does (?P<source>.+?) (?:eventually |transitively )?call (?P<target>.+)",
84
+ ),
85
+ _rule(
86
+ "path", "operation", "path from <a> to <b>",
87
+ r"(?:shortest |show |find )*path from (?P<source>.+?) to (?P<target>.+)",
88
+ ),
89
+ _rule(
90
+ "path", "operation", "is there a path from <a> to <b>",
91
+ r"is there a path from (?P<source>.+?) to (?P<target>.+)",
92
+ ),
93
+ _rule(
94
+ "path", "operation", "how does <a> reach <b>",
95
+ r"how does (?P<source>.+?) reach (?P<target>.+)",
96
+ ),
97
+ _rule(
98
+ "stats", "operation", "graph stats",
99
+ r"(?:graph )?(?:stats|statistics|overview|summary)",
100
+ ),
101
+ _rule("stats", "operation", "how big is the graph", r"how big is the graph"),
102
+ _rule(
103
+ "impact", "command", "what breaks if i change <path>",
104
+ r"what (?:breaks|is affected|gets affected) (?:if|when) (?:i )?(?:change|modify|edit|touch) (?P<node>.+)",
105
+ ),
106
+ _rule(
107
+ "impact", "command", "impact of changing <path>",
108
+ r"impact of (?:changing |modifying |editing )?(?P<node>.+)",
109
+ ),
110
+ _rule("impact", "command", "blast radius of <path>", r"blast radius (?:of|for) (?P<node>.+)"),
111
+ _rule(
112
+ "tests", "command", "what tests cover <path>",
113
+ r"(?:what|which) tests (?:cover|exercise|test) (?P<node>.+)",
114
+ ),
115
+ _rule(
116
+ "tests", "command", "tests for <path>",
117
+ r"(?:what |which )?tests (?:for|to run for) (?P<node>.+)",
118
+ ),
119
+ _rule(
120
+ "context", "command", "context for <node>",
121
+ r"(?:show |give |get )?(?:me )?context (?:for|of|about) (?P<node>.+)",
122
+ ),
123
+ _rule("context", "command", "tell me about <node>", r"tell me about (?P<node>.+)"),
124
+ )
125
+
126
+ _COMMAND_SUGGESTIONS: dict[str, tuple[str, str]] = {
127
+ "impact": ("impact", "impact analysis runs as its own canonical command"),
128
+ "tests": ("impact", "run graphite impact; its likely_tests field lists tests to run"),
129
+ "context": ("context", "context runs as its own canonical command"),
130
+ }
131
+
132
+ _ARTICLES = ("the ", "a ", "an ", "this ", "my ", "our ")
133
+ _DESCRIPTORS = (" function", " method", " class", " file", " module", " node", " symbol")
134
+
135
+ _STOPWORDS = frozenset(
136
+ "a an and are can could did do does for from give how i in is it list me my of on or our "
137
+ "please should show tell that the this to was we what when where which who whom whose why "
138
+ "will with would you your about find".split()
139
+ )
140
+
141
+
142
+ def _normalize(question: str) -> str:
143
+ text = re.sub(r"\s+", " ", question.strip().lower()).strip("\"'")
144
+ return re.sub(r"[?!.]+$", "", text).strip()
145
+
146
+
147
+ def _clean_target(text: str) -> str:
148
+ cleaned = text.strip().strip("`\"'")
149
+ if cleaned in {article.strip() for article in _ARTICLES}:
150
+ return ""
151
+ for article in _ARTICLES:
152
+ if cleaned.startswith(article):
153
+ cleaned = cleaned[len(article):]
154
+ break
155
+ for descriptor in _DESCRIPTORS:
156
+ if cleaned.endswith(descriptor):
157
+ cleaned = cleaned[: -len(descriptor)]
158
+ break
159
+ return cleaned.strip().strip("`\"'")
160
+
161
+
162
+ def natural_catalog() -> list[dict[str, Any]]:
163
+ """Machine-readable grammar listing, grouped by intent in rule order."""
164
+ catalog: list[dict[str, Any]] = []
165
+ for rule in NATURAL_RULES:
166
+ entry = next((e for e in catalog if e["intent"] == rule.intent), None)
167
+ if entry is None:
168
+ entry = {"intent": rule.intent, "kind": rule.kind, "templates": []}
169
+ catalog.append(entry)
170
+ entry["templates"].append(rule.template)
171
+ return catalog
172
+
173
+
174
+ def _search_fallback(question: str, normalized: str) -> dict[str, Any]:
175
+ terms = " ".join(w for w in normalized.split() if w not in _STOPWORDS)
176
+ if not terms:
177
+ return {
178
+ "schema_version": RESULT_SCHEMA_VERSION,
179
+ "error": "could not extract search terms from question",
180
+ "error_code": "natural_no_terms",
181
+ "suggestions": [
182
+ "Ask about a concrete symbol, file, or path",
183
+ "Run `graphite capabilities --json` to list the supported question grammar",
184
+ ],
185
+ }
186
+ return {
187
+ "schema_version": RESULT_SCHEMA_VERSION,
188
+ "natural": {"question": question, "intent": "search", "pattern": None},
189
+ "suggestion": {
190
+ "command": ["graphite", "search", terms],
191
+ "reason": "no grammar pattern matched; ranked search results serve as clarification candidates",
192
+ },
193
+ }
194
+
195
+
196
+ def translate_natural(question: str) -> dict[str, Any]:
197
+ """Deterministically translate a question, without touching any graph.
198
+
199
+ Returns one of: {natural, plan} for verb intents; {natural, suggestion} for
200
+ command intents and the search fallback; or an error dict with a stable
201
+ error_code. Never raises, never does inference.
202
+ """
203
+ normalized = _normalize(question)
204
+ if not normalized:
205
+ return {
206
+ "schema_version": RESULT_SCHEMA_VERSION,
207
+ "error": "empty query",
208
+ "error_code": "empty_query",
209
+ }
210
+ for rule in NATURAL_RULES:
211
+ m = rule.pattern.fullmatch(normalized)
212
+ if m is None:
213
+ continue
214
+ captured = {key: _clean_target(value) for key, value in m.groupdict().items()}
215
+ if any(not value for value in captured.values()):
216
+ break # degenerate capture (e.g. "who calls the"); fail closed to search
217
+ natural = {"question": question, "intent": rule.intent, "pattern": rule.template}
218
+ if rule.kind == "command":
219
+ command, reason = _COMMAND_SUGGESTIONS[rule.intent]
220
+ return {
221
+ "schema_version": RESULT_SCHEMA_VERSION,
222
+ "natural": natural,
223
+ "suggestion": {
224
+ "command": ["graphite", command, captured["node"]],
225
+ "reason": reason,
226
+ },
227
+ }
228
+ spec = _VERB_INDEX[rule.intent]
229
+ extracted = [captured[key] for key in ("node", "source", "target") if key in captured]
230
+ targets = list(zip(spec.roles, extracted))
231
+ plan = make_plan(spec.name, targets, dict(spec.limits))
232
+ return {"schema_version": RESULT_SCHEMA_VERSION, "natural": natural, "plan": plan}
233
+ return _search_fallback(question, normalized)
234
+
235
+
236
+ def answer_natural(g: nx.DiGraph, question: str) -> dict[str, Any]:
237
+ """Translate and answer: execute the plan, or run the search fallback.
238
+
239
+ Verb-intent errors (node_not_found with candidates, no_path, ...) pass
240
+ through the normal query envelope with the natural interpretation and plan
241
+ attached, so a failed resolution still explains itself.
242
+ """
243
+ translated = translate_natural(question)
244
+ if "error" in translated:
245
+ return translated
246
+ if "plan" in translated:
247
+ result = execute_plan(g, translated["plan"])
248
+ return {**result, "plan": translated["plan"], "natural": translated["natural"]}
249
+ if translated["natural"]["intent"] == "search":
250
+ terms = translated["suggestion"]["command"][2]
251
+ return {**translated, "search": search_graph(g, terms)}
252
+ return translated