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,275 @@
1
+ """Single-use automatic execution coordinator for approved route pools."""
2
+ from __future__ import annotations
3
+
4
+ from collections.abc import Callable
5
+ from dataclasses import dataclass, field
6
+ from typing import Any, ClassVar, Protocol
7
+
8
+ from .approval import ApprovalAuthority, ApprovalError, SignedApproval
9
+ from .contracts import PublicRecord
10
+ from .route_pool import (
11
+ ApprovedRoutePool,
12
+ RouteAttemptEvidence,
13
+ RouteAuthority,
14
+ RoutePoolError,
15
+ RouteSelection,
16
+ select_route,
17
+ )
18
+
19
+
20
+ @dataclass(frozen=True, slots=True)
21
+ class RouteExecutionEvidence(PublicRecord):
22
+ candidate_id: str
23
+ candidate_digest: str
24
+ attempt_ordinal: int
25
+ outcome_category: str
26
+ input_tokens: int
27
+ output_tokens: int
28
+ duration_ms: int
29
+ cost_microunits: int | None
30
+
31
+ _public_fields: ClassVar[tuple[str, ...]] = (
32
+ "candidate_id",
33
+ "candidate_digest",
34
+ "attempt_ordinal",
35
+ "outcome_category",
36
+ "input_tokens",
37
+ "output_tokens",
38
+ "duration_ms",
39
+ "cost_microunits",
40
+ )
41
+
42
+ def __post_init__(self) -> None:
43
+ if (
44
+ not isinstance(self.candidate_id, str)
45
+ or not self.candidate_id
46
+ or "\x00" in self.candidate_id
47
+ or len(self.candidate_id) > 256
48
+ or any(character.isspace() for character in self.candidate_id)
49
+ or not isinstance(self.candidate_digest, str)
50
+ or len(self.candidate_digest) != 64
51
+ or any(
52
+ character not in "0123456789abcdef"
53
+ for character in self.candidate_digest
54
+ )
55
+ or self.outcome_category
56
+ not in {
57
+ "succeeded",
58
+ "capacity_unavailable",
59
+ "provider_process_failure",
60
+ "provider_unavailable",
61
+ "provider_protocol",
62
+ "timeout",
63
+ "cancelled",
64
+ }
65
+ or isinstance(self.attempt_ordinal, bool)
66
+ or not isinstance(self.attempt_ordinal, int)
67
+ or not 1 <= self.attempt_ordinal <= 2
68
+ ):
69
+ raise RoutePoolError("route_evidence_invalid")
70
+ for value, maximum in (
71
+ (self.input_tokens, 262_144),
72
+ (self.output_tokens, 32_768),
73
+ (self.duration_ms, 86_400_000),
74
+ ):
75
+ if (
76
+ isinstance(value, bool)
77
+ or not isinstance(value, int)
78
+ or not 0 <= value <= maximum
79
+ ):
80
+ raise RoutePoolError("route_evidence_invalid")
81
+ if self.cost_microunits is not None and (
82
+ isinstance(self.cost_microunits, bool)
83
+ or not isinstance(self.cost_microunits, int)
84
+ or self.cost_microunits < 0
85
+ or self.cost_microunits > 10**12
86
+ ):
87
+ raise RoutePoolError("route_evidence_invalid")
88
+
89
+
90
+ @dataclass(frozen=True, slots=True)
91
+ class RouteExecutionResult:
92
+ candidate_id: str
93
+ candidate_digest: str
94
+ attempt_ordinal: int
95
+ output: str = field(repr=False, compare=False)
96
+ input_tokens: int
97
+ output_tokens: int
98
+ duration_ms: int
99
+ cost_microunits: int | None
100
+
101
+
102
+ class RouteAttemptFailure(RuntimeError):
103
+ """Trusted adapter failure carrying sanitized evidence only."""
104
+
105
+ def __init__(self, evidence: RouteAttemptEvidence) -> None:
106
+ if not isinstance(evidence, RouteAttemptEvidence):
107
+ raise RoutePoolError("route_attempt_invalid")
108
+ self.evidence = evidence
109
+ super().__init__(evidence.failure_category)
110
+
111
+
112
+ class AuthorityLoader(Protocol):
113
+ def __call__(self) -> tuple[RouteAuthority, ...]: ...
114
+
115
+
116
+ class RouteRunner(Protocol):
117
+ def __call__(self, selection: RouteSelection) -> RouteExecutionResult: ...
118
+
119
+
120
+ EvidenceSink = Callable[[RouteExecutionEvidence], Any]
121
+
122
+
123
+ def _load_authority(loader: AuthorityLoader) -> tuple[RouteAuthority, ...]:
124
+ try:
125
+ authorities = loader()
126
+ except Exception:
127
+ raise RoutePoolError("route_authority_unavailable") from None
128
+ if not isinstance(authorities, tuple):
129
+ raise RoutePoolError("route_authority_invalid")
130
+ return authorities
131
+
132
+
133
+ def _persist_evidence(
134
+ sink: EvidenceSink,
135
+ evidence: RouteExecutionEvidence,
136
+ ) -> None:
137
+ try:
138
+ sink(evidence)
139
+ except Exception:
140
+ raise RoutePoolError("route_evidence_unavailable") from None
141
+
142
+
143
+ def _failure_evidence(
144
+ selection: RouteSelection,
145
+ failure: RouteAttemptEvidence,
146
+ ) -> RouteExecutionEvidence:
147
+ if (
148
+ failure.candidate_id != selection.candidate.candidate_id
149
+ or failure.candidate_digest != selection.candidate.digest
150
+ or failure.attempt_ordinal != selection.attempt_ordinal
151
+ ):
152
+ raise RoutePoolError("route_attempt_invalid")
153
+ return RouteExecutionEvidence(
154
+ candidate_id=failure.candidate_id,
155
+ candidate_digest=failure.candidate_digest,
156
+ attempt_ordinal=failure.attempt_ordinal,
157
+ outcome_category=failure.failure_category,
158
+ input_tokens=failure.input_tokens,
159
+ output_tokens=failure.output_tokens,
160
+ duration_ms=failure.duration_ms,
161
+ cost_microunits=failure.cost_microunits,
162
+ )
163
+
164
+
165
+ def _validate_result(
166
+ pool: ApprovedRoutePool,
167
+ selection: RouteSelection,
168
+ result: RouteExecutionResult,
169
+ ) -> RouteExecutionEvidence:
170
+ if (
171
+ not isinstance(result, RouteExecutionResult)
172
+ or result.candidate_id != selection.candidate.candidate_id
173
+ or result.candidate_digest != selection.candidate.digest
174
+ or result.attempt_ordinal != selection.attempt_ordinal
175
+ or not isinstance(result.output, str)
176
+ or not result.output
177
+ or "\x00" in result.output
178
+ or len(result.output) > 1_048_576
179
+ ):
180
+ raise RoutePoolError("route_result_invalid")
181
+ values = (
182
+ (result.input_tokens, selection.remaining_input_tokens),
183
+ (result.output_tokens, selection.remaining_output_tokens),
184
+ (result.duration_ms, selection.remaining_duration_ms),
185
+ )
186
+ if any(
187
+ isinstance(value, bool)
188
+ or not isinstance(value, int)
189
+ or value < 0
190
+ or value > maximum
191
+ for value, maximum in values
192
+ ):
193
+ raise RoutePoolError("route_pool_budget_exhausted")
194
+ if pool.max_cost_microunits is None:
195
+ if result.cost_microunits is not None:
196
+ raise RoutePoolError("route_pool_cost_invalid")
197
+ elif (
198
+ isinstance(result.cost_microunits, bool)
199
+ or not isinstance(result.cost_microunits, int)
200
+ or result.cost_microunits < 0
201
+ or selection.remaining_cost_microunits is None
202
+ or result.cost_microunits > selection.remaining_cost_microunits
203
+ ):
204
+ raise RoutePoolError("route_pool_budget_exhausted")
205
+ return RouteExecutionEvidence(
206
+ result.candidate_id,
207
+ result.candidate_digest,
208
+ result.attempt_ordinal,
209
+ "succeeded",
210
+ result.input_tokens,
211
+ result.output_tokens,
212
+ result.duration_ms,
213
+ result.cost_microunits,
214
+ )
215
+
216
+
217
+ def execute_approved_route_pool(
218
+ *,
219
+ pool: ApprovedRoutePool,
220
+ signed_approval: SignedApproval,
221
+ approval_authority: ApprovalAuthority,
222
+ authority_loader: AuthorityLoader,
223
+ runner: RouteRunner,
224
+ evidence_sink: EvidenceSink,
225
+ repository_quota_tokens: int,
226
+ machine_quota_tokens: int,
227
+ now: Callable[[], int],
228
+ ) -> RouteExecutionResult:
229
+ """Consume one pool and automatically advance once on safe capacity failure."""
230
+ if (
231
+ not isinstance(pool, ApprovedRoutePool)
232
+ or not isinstance(approval_authority, ApprovalAuthority)
233
+ or not callable(authority_loader)
234
+ or not callable(runner)
235
+ or not callable(evidence_sink)
236
+ or not callable(now)
237
+ ):
238
+ raise RoutePoolError("route_execution_invalid")
239
+ try:
240
+ approval_authority.consume(
241
+ signed_approval,
242
+ pool,
243
+ repository_quota_tokens=repository_quota_tokens,
244
+ machine_quota_tokens=machine_quota_tokens,
245
+ )
246
+ except ApprovalError:
247
+ raise
248
+ attempts: list[RouteAttemptEvidence] = []
249
+ while len(attempts) < pool.max_attempts:
250
+ try:
251
+ current_time = now()
252
+ except Exception:
253
+ raise RoutePoolError("route_pool_time_invalid") from None
254
+ selection = select_route(
255
+ pool,
256
+ _load_authority(authority_loader),
257
+ tuple(attempts),
258
+ now=current_time,
259
+ )
260
+ try:
261
+ result = runner(selection)
262
+ except RouteAttemptFailure as exc:
263
+ failure = exc.evidence
264
+ evidence = _failure_evidence(selection, failure)
265
+ _persist_evidence(evidence_sink, evidence)
266
+ attempts.append(failure)
267
+ if len(attempts) >= pool.max_attempts:
268
+ raise RoutePoolError("route_pool_attempts_exhausted") from None
269
+ continue
270
+ except Exception:
271
+ raise RoutePoolError("route_attempt_failed") from None
272
+ evidence = _validate_result(pool, selection, result)
273
+ _persist_evidence(evidence_sink, evidence)
274
+ return result
275
+ raise RoutePoolError("route_pool_attempts_exhausted")
@@ -0,0 +1,169 @@
1
+ """Minimal in-house JSON-Schema validator for governed structured output.
2
+
3
+ The governed output schemas are sha256-pinned constants (not attacker input), so this
4
+ validator's job is to enforce the constraints those schemas express, not to police keyword
5
+ novelty. It therefore fails closed ONLY on combinator keywords (anyOf/oneOf/allOf/not/$ref/
6
+ patternProperties/...) -- keywords that change what "valid" means and that this validator
7
+ cannot safely approximate. All other (refinement/annotation) keywords are tolerated:
8
+ matches_schema enforces the ones it understands and ignores the rest, which can only
9
+ under-enforce a single constraint, never accept a structurally wrong shape. Zero third-party
10
+ dependencies by design.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import re
15
+
16
+ # Keywords that combine/redirect subschemas and thus change what "valid" means. This
17
+ # validator cannot approximate them, so a schema using any of them fails closed at
18
+ # is_supported_schema -> the executor rejects it with request_invalid before any call,
19
+ # rather than being silently under-validated.
20
+ _COMBINATORS = frozenset({
21
+ "anyOf", "oneOf", "allOf", "not", "$ref", "$dynamicRef",
22
+ "if", "then", "else", "patternProperties", "propertyNames",
23
+ "dependencies", "dependentSchemas", "dependentRequired",
24
+ "unevaluatedProperties", "unevaluatedItems", "contains",
25
+ "prefixItems", "additionalItems",
26
+ })
27
+
28
+
29
+ def is_supported_schema(schema: object) -> bool:
30
+ """True unless the schema uses a combinator keyword this validator cannot safely
31
+ approximate (or a non-bool additionalProperties, which is a schema-valued shape
32
+ change). Refinement/annotation keywords are tolerated. Governed schemas are pinned
33
+ constants, so this gate catches a shape we would mis-validate, not unknown-but-harmless
34
+ keys."""
35
+ if not isinstance(schema, dict) or not schema:
36
+ return False
37
+ if not _COMBINATORS.isdisjoint(schema):
38
+ return False
39
+ if not isinstance(schema.get("additionalProperties", False), bool):
40
+ return False
41
+ pattern = schema.get("pattern")
42
+ if isinstance(pattern, str):
43
+ try:
44
+ re.compile(pattern)
45
+ except re.error:
46
+ return False
47
+ properties = schema.get("properties")
48
+ if properties is not None:
49
+ if not isinstance(properties, dict):
50
+ return False
51
+ if not all(isinstance(k, str) and is_supported_schema(v) for k, v in properties.items()):
52
+ return False
53
+ items = schema.get("items")
54
+ if items is not None and not is_supported_schema(items):
55
+ return False
56
+ return True
57
+
58
+
59
+ def matches_schema(value: object, schema: object) -> bool:
60
+ """True iff value conforms to every constraint in schema that this validator
61
+ understands; unknown refinement keywords are ignored (never over-reject).
62
+
63
+ Precondition: schema must already have passed is_supported_schema -- callers gate the
64
+ schema once, then match many values against it. Given such a schema this never raises.
65
+ Untrusted VALUES are always handled safely: a non-conforming (or wrong-typed) value
66
+ returns False, never raises.
67
+ """
68
+ if not isinstance(schema, dict):
69
+ return False
70
+ type_value = schema.get("type")
71
+ if type_value is not None and not _matches_type(value, type_value):
72
+ return False
73
+ if "const" in schema and not _json_equal(value, schema["const"]):
74
+ return False
75
+ if "enum" in schema and not any(_json_equal(value, member) for member in schema["enum"]):
76
+ return False
77
+ if isinstance(value, str) and not _matches_string(value, schema):
78
+ return False
79
+ if isinstance(value, dict) and _is_object_schema(schema) and not _matches_object(value, schema):
80
+ return False
81
+ if isinstance(value, list) and _is_array_schema(schema) and not _matches_array(value, schema):
82
+ return False
83
+ return True
84
+
85
+
86
+ def _is_object_schema(schema: dict) -> bool:
87
+ return schema.get("type") == "object" or any(
88
+ key in schema for key in ("properties", "required", "additionalProperties")
89
+ )
90
+
91
+
92
+ def _is_array_schema(schema: dict) -> bool:
93
+ return schema.get("type") == "array" or any(
94
+ key in schema for key in ("items", "minItems", "maxItems")
95
+ )
96
+
97
+
98
+ def _matches_string(value: str, schema: dict) -> bool:
99
+ pattern = schema.get("pattern")
100
+ if isinstance(pattern, str) and re.fullmatch(pattern, value) is None:
101
+ return False
102
+ maximum = schema.get("maxLength")
103
+ if _is_bound(maximum) and len(value) > maximum:
104
+ return False
105
+ minimum = schema.get("minLength")
106
+ if _is_bound(minimum) and len(value) < minimum:
107
+ return False
108
+ return True
109
+
110
+
111
+ def _matches_object(value: dict, schema: dict) -> bool:
112
+ properties = schema.get("properties", {})
113
+ if not isinstance(properties, dict):
114
+ return False
115
+ if not all(key in value for key in schema.get("required", [])):
116
+ return False
117
+ if schema.get("additionalProperties", False) is False and not set(value) <= set(properties):
118
+ return False
119
+ return all(
120
+ matches_schema(value[key], subschema)
121
+ for key, subschema in properties.items()
122
+ if key in value
123
+ )
124
+
125
+
126
+ def _matches_array(value: list, schema: dict) -> bool:
127
+ minimum = schema.get("minItems")
128
+ if _is_bound(minimum) and len(value) < minimum:
129
+ return False
130
+ maximum = schema.get("maxItems")
131
+ if _is_bound(maximum) and len(value) > maximum:
132
+ return False
133
+ items = schema.get("items")
134
+ if isinstance(items, dict):
135
+ return all(matches_schema(element, items) for element in value)
136
+ return True
137
+
138
+
139
+ def _is_bound(value: object) -> bool:
140
+ return isinstance(value, int) and not isinstance(value, bool) and value >= 0
141
+
142
+
143
+ def _matches_type(value: object, type_value: object) -> bool:
144
+ if isinstance(type_value, list):
145
+ return any(_matches_type(value, member) for member in type_value)
146
+ if type_value == "string":
147
+ return isinstance(value, str)
148
+ if type_value == "integer":
149
+ return isinstance(value, int) and not isinstance(value, bool)
150
+ if type_value == "number":
151
+ return isinstance(value, (int, float)) and not isinstance(value, bool)
152
+ if type_value == "boolean":
153
+ return isinstance(value, bool)
154
+ if type_value == "null":
155
+ return value is None
156
+ if type_value == "object":
157
+ return isinstance(value, dict)
158
+ if type_value == "array":
159
+ return isinstance(value, list)
160
+ return False
161
+
162
+
163
+ def _json_equal(value: object, target: object) -> bool:
164
+ """Equality that does not conflate True==1, 1==1.0, or 1.0==1 (JSON-kind aware)."""
165
+ if isinstance(value, bool) or isinstance(target, bool):
166
+ return value is target
167
+ if isinstance(value, (int, float)) and isinstance(target, (int, float)):
168
+ return type(value) is type(target) and value == target
169
+ return value == target