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,99 @@
1
+ """Conservative environment-backed settings for development routing."""
2
+ from __future__ import annotations
3
+
4
+ import math
5
+ import os
6
+ from dataclasses import asdict, dataclass
7
+ from typing import Mapping
8
+
9
+
10
+ class RoutingSettingsError(ValueError):
11
+ """A stable routing settings validation failure."""
12
+
13
+
14
+ def _integer(env: Mapping[str, str], name: str, default: int, minimum: int, maximum: int) -> int:
15
+ raw = env.get(f"GRAPHITE_ROUTE_{name.upper()}")
16
+ if raw is None:
17
+ return default
18
+ try:
19
+ value = int(raw, 10)
20
+ except (TypeError, ValueError) as exc:
21
+ raise RoutingSettingsError(f"{name}_invalid") from exc
22
+ if value < minimum or value > maximum:
23
+ raise RoutingSettingsError(f"{name}_invalid")
24
+ return value
25
+
26
+
27
+ def _float(env: Mapping[str, str], name: str, default: float, minimum: float, maximum: float) -> float:
28
+ raw = env.get(f"GRAPHITE_ROUTE_{name.upper()}")
29
+ if raw is None:
30
+ return default
31
+ try:
32
+ value = float(raw)
33
+ except (TypeError, ValueError) as exc:
34
+ raise RoutingSettingsError(f"{name}_invalid") from exc
35
+ if not math.isfinite(value) or value < minimum or value > maximum:
36
+ raise RoutingSettingsError(f"{name}_invalid")
37
+ return value
38
+
39
+
40
+ def _boolean(env: Mapping[str, str], name: str, default: bool) -> bool:
41
+ raw = env.get(f"GRAPHITE_ROUTE_{name.upper()}")
42
+ if raw is None:
43
+ return default
44
+ normalized = raw.strip().casefold()
45
+ if normalized in {"1", "true", "yes", "on"}:
46
+ return True
47
+ if normalized in {"0", "false", "no", "off"}:
48
+ return False
49
+ raise RoutingSettingsError(f"{name}_invalid")
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class RoutingSettings:
54
+ max_context_bytes: int = 262_144
55
+ max_context_files: int = 32
56
+ max_input_tokens: int = 32_768
57
+ max_output_tokens: int = 4_096
58
+ request_timeout_seconds: float = 180.0
59
+ max_concurrency: int = 1
60
+ repository_quota_tokens: int = 2_000_000
61
+ machine_quota_tokens: int = 10_000_000
62
+ shadow_rate_percent: int = 0
63
+ shadow_quota_tokens: int = 200_000
64
+ approval_ttl_seconds: int = 300
65
+ retention_days: int = 90
66
+ aggregate_opt_in: bool = False
67
+ max_changed_files: int = 64
68
+ max_changed_bytes: int = 1_048_576
69
+
70
+ @classmethod
71
+ def from_env(cls, overrides: Mapping[str, str] | None = None) -> "RoutingSettings":
72
+ env = dict(os.environ)
73
+ if overrides:
74
+ env.update(overrides)
75
+ settings = cls(
76
+ max_context_bytes=_integer(env, "max_context_bytes", 262_144, 16_384, 4_194_304),
77
+ max_context_files=_integer(env, "max_context_files", 32, 1, 128),
78
+ max_input_tokens=_integer(env, "max_input_tokens", 32_768, 256, 262_144),
79
+ max_output_tokens=_integer(env, "max_output_tokens", 4_096, 1, 32_768),
80
+ request_timeout_seconds=_float(env, "request_timeout_seconds", 180.0, 5.0, 600.0),
81
+ max_concurrency=_integer(env, "max_concurrency", 1, 1, 1),
82
+ repository_quota_tokens=_integer(env, "repository_quota_tokens", 2_000_000, 10_000, 100_000_000),
83
+ machine_quota_tokens=_integer(env, "machine_quota_tokens", 10_000_000, 10_000, 1_000_000_000),
84
+ shadow_rate_percent=_integer(env, "shadow_rate_percent", 0, 0, 10),
85
+ shadow_quota_tokens=_integer(env, "shadow_quota_tokens", 200_000, 0, 10_000_000),
86
+ approval_ttl_seconds=_integer(env, "approval_ttl_seconds", 300, 30, 3_600),
87
+ retention_days=_integer(env, "retention_days", 90, 1, 3_650),
88
+ aggregate_opt_in=_boolean(env, "aggregate_opt_in", False),
89
+ max_changed_files=_integer(env, "max_changed_files", 64, 1, 10_000),
90
+ max_changed_bytes=_integer(
91
+ env, "max_changed_bytes", 1_048_576, 1_024, 104_857_600
92
+ ),
93
+ )
94
+ if settings.machine_quota_tokens < settings.repository_quota_tokens:
95
+ raise RoutingSettingsError("machine_quota_tokens_invalid")
96
+ return settings
97
+
98
+ def to_dict(self) -> dict[str, int | float | bool]:
99
+ return asdict(self)
@@ -0,0 +1,201 @@
1
+ """Deterministic, separately authorized cross-provider review policy."""
2
+ from __future__ import annotations
3
+
4
+ import secrets
5
+ from dataclasses import dataclass, replace
6
+
7
+ from .contracts import (
8
+ CapabilitySnapshot,
9
+ CliApprovalManifest,
10
+ PermissionMode,
11
+ ProviderId,
12
+ RiskTier,
13
+ )
14
+ from .storage import RepositoryStore
15
+
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class ReviewCandidate:
19
+ snapshot: CapabilitySnapshot
20
+ reserved_tokens: int
21
+ independently_eligible: bool
22
+
23
+ def __post_init__(self) -> None:
24
+ if not isinstance(self.snapshot, CapabilitySnapshot):
25
+ raise ValueError("review_snapshot_invalid")
26
+ if self.snapshot.profile.permission_mode is not PermissionMode.READ_ONLY:
27
+ raise ValueError("review_permission_invalid")
28
+ if (
29
+ isinstance(self.reserved_tokens, bool)
30
+ or not isinstance(self.reserved_tokens, int)
31
+ or not 2 <= self.reserved_tokens <= 294_912
32
+ ):
33
+ raise ValueError("reserved_tokens_invalid")
34
+ if not isinstance(self.independently_eligible, bool):
35
+ raise ValueError("eligibility_invalid")
36
+
37
+
38
+ @dataclass(frozen=True, slots=True)
39
+ class ReviewPlan:
40
+ candidate: ReviewCandidate
41
+ primary_diff_hash: str
42
+ authority: str = "separate_single_use_approval_required"
43
+
44
+
45
+ @dataclass(frozen=True, slots=True)
46
+ class BlindedComparison:
47
+ comparison_id: str
48
+ label_a_hash: str
49
+ label_b_hash: str
50
+
51
+ def to_dict(self) -> dict[str, str]:
52
+ return {
53
+ "comparison_id": self.comparison_id,
54
+ "label_a_hash": self.label_a_hash,
55
+ "label_b_hash": self.label_b_hash,
56
+ }
57
+
58
+
59
+ def plan_cross_provider_review(
60
+ *,
61
+ primary_provider: ProviderId,
62
+ primary_diff_hash: str,
63
+ candidates: tuple[ReviewCandidate, ...],
64
+ risk: RiskTier,
65
+ ) -> ReviewPlan | None:
66
+ """Select one other-provider reviewer only for high-risk primary work."""
67
+ provider = ProviderId(primary_provider)
68
+ if RiskTier(risk) is not RiskTier.HIGH:
69
+ return None
70
+ if (
71
+ not isinstance(primary_diff_hash, str)
72
+ or len(primary_diff_hash) != 64
73
+ or any(character not in "0123456789abcdef" for character in primary_diff_hash)
74
+ ):
75
+ raise ValueError("primary_diff_hash_invalid")
76
+ eligible = sorted(
77
+ (
78
+ candidate
79
+ for candidate in candidates
80
+ if candidate.independently_eligible
81
+ and candidate.snapshot.profile.provider is not provider
82
+ ),
83
+ key=lambda item: (
84
+ item.reserved_tokens,
85
+ item.snapshot.profile.provider.value,
86
+ item.snapshot.profile.requested_model,
87
+ item.snapshot.profile.supported_efforts[0].value,
88
+ item.snapshot.digest,
89
+ ),
90
+ )
91
+ return None if not eligible else ReviewPlan(eligible[0], primary_diff_hash)
92
+
93
+
94
+ def make_review_manifest(
95
+ primary: CliApprovalManifest,
96
+ plan: ReviewPlan,
97
+ *,
98
+ approval_id: str,
99
+ decision_id: str,
100
+ worktree_id: str,
101
+ issued_at: int,
102
+ expires_at: int,
103
+ nonce: str,
104
+ ) -> CliApprovalManifest:
105
+ """Derive a separately signable read-only manifest without primary authority reuse."""
106
+ snapshot = plan.candidate.snapshot
107
+ if (
108
+ approval_id == primary.approval_id
109
+ or decision_id == primary.decision_id
110
+ or worktree_id == primary.worktree_id
111
+ or nonce == primary.nonce
112
+ or snapshot.profile.provider is primary.provider
113
+ ):
114
+ raise ValueError("review_approval_not_independent")
115
+ output = min(primary.max_output_tokens, plan.candidate.reserved_tokens - 1)
116
+ input_tokens = plan.candidate.reserved_tokens - output
117
+ return replace(
118
+ primary,
119
+ approval_id=approval_id,
120
+ decision_id=decision_id,
121
+ provider=snapshot.profile.provider,
122
+ requested_model=snapshot.profile.requested_model,
123
+ effective_model=snapshot.profile.effective_model,
124
+ effort=snapshot.profile.supported_efforts[0],
125
+ cli_executable_sha256=snapshot.identity.executable_sha256,
126
+ cli_version=snapshot.identity.cli_version,
127
+ adapter_protocol_version=snapshot.identity.adapter_protocol_version,
128
+ capability_snapshot_digest=snapshot.digest,
129
+ context_manifest_hash=plan.primary_diff_hash,
130
+ worktree_id=worktree_id,
131
+ permission_mode=PermissionMode.READ_ONLY,
132
+ max_input_tokens=input_tokens,
133
+ max_output_tokens=output,
134
+ policy_version="review-1",
135
+ issued_at=issued_at,
136
+ expires_at=expires_at,
137
+ nonce=nonce,
138
+ )
139
+
140
+
141
+ def create_blinded_comparison(
142
+ store: RepositoryStore,
143
+ *,
144
+ comparison_id: str,
145
+ primary_execution_id: str,
146
+ shadow_execution_id: str,
147
+ primary_hash: str,
148
+ shadow_hash: str,
149
+ created_at: int,
150
+ swap: bool | None = None,
151
+ ) -> BlindedComparison:
152
+ label_a_is_shadow = bool(secrets.randbelow(2)) if swap is None else bool(swap)
153
+ label_a_hash, label_b_hash = (
154
+ (shadow_hash, primary_hash)
155
+ if label_a_is_shadow
156
+ else (primary_hash, shadow_hash)
157
+ )
158
+ store.create_blind_comparison(
159
+ comparison_id=comparison_id,
160
+ primary_execution_id=primary_execution_id,
161
+ shadow_execution_id=shadow_execution_id,
162
+ label_a_hash=label_a_hash,
163
+ label_b_hash=label_b_hash,
164
+ label_a_is_shadow=label_a_is_shadow,
165
+ created_at=created_at,
166
+ )
167
+ return BlindedComparison(comparison_id, label_a_hash, label_b_hash)
168
+
169
+
170
+ def record_pairwise_verdict(
171
+ store: RepositoryStore, comparison_id: str, verdict: str, *, recorded_at: int
172
+ ) -> None:
173
+ if not store.record_blind_verdict(comparison_id, verdict, recorded_at):
174
+ raise ValueError("pairwise_verdict_invalid")
175
+
176
+
177
+ def reveal_comparison(store: RepositoryStore, comparison_id: str) -> dict[str, object]:
178
+ row = store.blind_comparison(comparison_id)
179
+ if row is None:
180
+ raise ValueError("pairwise_comparison_missing")
181
+ verdict = row["verdict"]
182
+ if verdict is None:
183
+ raise ValueError("pairwise_verdict_missing")
184
+ winner: str | None = None
185
+ if verdict in {"a", "b"}:
186
+ a_is_shadow = bool(row["label_a_is_shadow"])
187
+ winner_is_shadow = a_is_shadow if verdict == "a" else not a_is_shadow
188
+ winner = str(
189
+ row["shadow_execution_id"]
190
+ if winner_is_shadow
191
+ else row["primary_execution_id"]
192
+ )
193
+ return {
194
+ "comparison_id": comparison_id,
195
+ "verdict": verdict,
196
+ "winner_execution_id": winner,
197
+ "primary_execution_id": row["primary_execution_id"],
198
+ "shadow_execution_id": row["shadow_execution_id"],
199
+ "provenance": "pairwise",
200
+ "autonomy_admissible": False,
201
+ }