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.
- graphite/__init__.py +41 -0
- graphite/__main__.py +7 -0
- graphite/_cleanup_worker.py +525 -0
- graphite/activation.py +164 -0
- graphite/agent_hooks.py +577 -0
- graphite/agent_settings.py +226 -0
- graphite/analyze.py +146 -0
- graphite/answer_contract.py +420 -0
- graphite/bootstrap.py +210 -0
- graphite/buildlock.py +99 -0
- graphite/cache.py +131 -0
- graphite/channel.py +1325 -0
- graphite/cli.py +3053 -0
- graphite/cluster.py +111 -0
- graphite/config.py +209 -0
- graphite/context.py +355 -0
- graphite/daemon.py +745 -0
- graphite/daemon_health.py +733 -0
- graphite/debt.py +118 -0
- graphite/dependency_install.py +1597 -0
- graphite/detach.py +33 -0
- graphite/doctor.py +678 -0
- graphite/doctor_probes.py +2100 -0
- graphite/engine_identity.py +238 -0
- graphite/export/__init__.py +6 -0
- graphite/export/html.py +244 -0
- graphite/export/json.py +39 -0
- graphite/export/md.py +68 -0
- graphite/extract/__init__.py +4 -0
- graphite/extract/ast.py +1964 -0
- graphite/freshness.py +127 -0
- graphite/git.py +406 -0
- graphite/graph.py +117 -0
- graphite/graph_io.py +188 -0
- graphite/health.py +147 -0
- graphite/hook_entry.py +68 -0
- graphite/hookinstall.py +224 -0
- graphite/hookshim.py +86 -0
- graphite/incident_ledger.py +247 -0
- graphite/ingest.py +279 -0
- graphite/init.py +791 -0
- graphite/io.py +32 -0
- graphite/listing.py +51 -0
- graphite/llm.py +518 -0
- graphite/llm_probe.py +157 -0
- graphite/mcp.py +7 -0
- graphite/mcp_server.py +450 -0
- graphite/natural_query.py +252 -0
- graphite/overlays.py +713 -0
- graphite/probe_process.py +879 -0
- graphite/probe_workspace.py +728 -0
- graphite/process_contracts.py +22 -0
- graphite/provider_observer.py +397 -0
- graphite/query.py +646 -0
- graphite/query_plan.py +97 -0
- graphite/replacement_audit.py +291 -0
- graphite/resolve.py +660 -0
- graphite/review.py +782 -0
- graphite/routing/__init__.py +5 -0
- graphite/routing/approval.py +362 -0
- graphite/routing/classifier.py +169 -0
- graphite/routing/claude_executor.py +419 -0
- graphite/routing/claude_probe.py +102 -0
- graphite/routing/cli_identity.py +84 -0
- graphite/routing/codex_executor.py +383 -0
- graphite/routing/codex_probe.py +93 -0
- graphite/routing/context_builder.py +327 -0
- graphite/routing/contracts.py +802 -0
- graphite/routing/diff_policy.py +468 -0
- graphite/routing/edit_apply.py +166 -0
- graphite/routing/effort.py +43 -0
- graphite/routing/lifecycle.py +771 -0
- graphite/routing/lifecycle_operator.py +227 -0
- graphite/routing/lifecycle_service.py +555 -0
- graphite/routing/lifecycle_storage.py +977 -0
- graphite/routing/ollama_executor.py +341 -0
- graphite/routing/ollama_probe.py +72 -0
- graphite/routing/openrouter_executor.py +338 -0
- graphite/routing/openrouter_probe.py +188 -0
- graphite/routing/policy.py +815 -0
- graphite/routing/probe_runner.py +543 -0
- graphite/routing/process_runner.py +523 -0
- graphite/routing/profiles.py +554 -0
- graphite/routing/prompt.py +58 -0
- graphite/routing/registry.py +444 -0
- graphite/routing/route_pool.py +629 -0
- graphite/routing/route_pool_execution.py +275 -0
- graphite/routing/schema_validation.py +169 -0
- graphite/routing/service.py +1263 -0
- graphite/routing/settings.py +99 -0
- graphite/routing/shadow.py +201 -0
- graphite/routing/storage.py +4001 -0
- graphite/routing/telemetry.py +346 -0
- graphite/routing/worktree.py +259 -0
- graphite/routing/zai_edit.py +113 -0
- graphite/routing/zai_executor.py +191 -0
- graphite/routing/zai_probe.py +126 -0
- graphite/savings.py +84 -0
- graphite/ts_bridge.py +142 -0
- graphite/ts_resolver.mjs +314 -0
- graphite/typescript_activation.py +1586 -0
- graphite/usage_ledger.py +156 -0
- graphite/validation.py +148 -0
- graphite/watch.py +167 -0
- graphite/windows_job.py +368 -0
- graphite/windows_startup.py +144 -0
- graphite/windows_task.py +212 -0
- graphite_code-0.3.0.dist-info/METADATA +743 -0
- graphite_code-0.3.0.dist-info/RECORD +112 -0
- graphite_code-0.3.0.dist-info/WHEEL +4 -0
- graphite_code-0.3.0.dist-info/entry_points.txt +3 -0
- 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
|
+
}
|