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
graphite/activation.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Registry of repositories currently open in a coding agent.
|
|
2
|
+
|
|
3
|
+
The daemon supervises exactly the repositories with a live marker here, and
|
|
4
|
+
leaves every other repository untouched -- no snapshot, no build. Markers are
|
|
5
|
+
written by agent hooks (see ``agent_hooks``) and by interactive graphite CLI
|
|
6
|
+
invocations, and expire on a TTL because no agent reliably signals session end.
|
|
7
|
+
|
|
8
|
+
Every function is fail-open: an IO, permission, or parse problem yields a no-op
|
|
9
|
+
rather than an exception. Activation is bookkeeping and must never be able to
|
|
10
|
+
break an agent session or a daemon cycle.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import hashlib
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import tempfile
|
|
19
|
+
import time
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
ACTIVATION_TTL_SECONDS: float = 3600.0
|
|
25
|
+
ENV_STATE_DIR = "GRAPHITE_STATE_DIR"
|
|
26
|
+
ENV_DAEMON_CHILD = "GRAPHITE_DAEMON_CHILD"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class ActivationRecord:
|
|
31
|
+
"""One repository that is open right now."""
|
|
32
|
+
|
|
33
|
+
root: Path
|
|
34
|
+
agent: str
|
|
35
|
+
first_seen: float
|
|
36
|
+
last_seen: float
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def state_dir() -> Path:
|
|
40
|
+
"""User-scoped state directory, deliberately not tied to a daemon base path."""
|
|
41
|
+
override = os.environ.get(ENV_STATE_DIR)
|
|
42
|
+
if override:
|
|
43
|
+
return Path(override)
|
|
44
|
+
if sys.platform == "win32":
|
|
45
|
+
base = os.environ.get("LOCALAPPDATA") or str(Path.home() / "AppData" / "Local")
|
|
46
|
+
return Path(base) / "graphite"
|
|
47
|
+
return Path.home() / ".local" / "state" / "graphite"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def active_dir() -> Path:
|
|
51
|
+
return state_dir() / "active"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def marker_path(root: Path) -> Path:
|
|
55
|
+
digest = hashlib.sha256(str(Path(root).resolve()).encode("utf-8")).hexdigest()[:16]
|
|
56
|
+
return active_dir() / f"{digest}.json"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_daemon_child() -> bool:
|
|
60
|
+
"""True inside a build the daemon spawned.
|
|
61
|
+
|
|
62
|
+
The daemon runs ``-m graphite build`` inside the repo it is supervising. If
|
|
63
|
+
that invocation refreshed the marker, an activated repo would renew its own
|
|
64
|
+
activation forever and never expire.
|
|
65
|
+
"""
|
|
66
|
+
return bool(os.environ.get(ENV_DAEMON_CHILD))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def mark_active(root: Path, agent: str = "unknown", *, now: float | None = None) -> Path | None:
|
|
70
|
+
"""Create or refresh this repository's marker. Returns None on any no-op."""
|
|
71
|
+
if is_daemon_child():
|
|
72
|
+
return None
|
|
73
|
+
stamp = time.time() if now is None else now
|
|
74
|
+
try:
|
|
75
|
+
resolved = Path(root).resolve()
|
|
76
|
+
if not resolved.is_dir():
|
|
77
|
+
return None
|
|
78
|
+
path = marker_path(resolved)
|
|
79
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
first_seen = stamp
|
|
81
|
+
existing = _read_marker(path)
|
|
82
|
+
if existing is not None:
|
|
83
|
+
candidate = existing.get("first_seen")
|
|
84
|
+
if isinstance(candidate, (int, float)):
|
|
85
|
+
first_seen = min(float(candidate), stamp)
|
|
86
|
+
payload = {
|
|
87
|
+
"root": str(resolved),
|
|
88
|
+
"agent": agent,
|
|
89
|
+
"first_seen": first_seen,
|
|
90
|
+
"last_seen": stamp,
|
|
91
|
+
}
|
|
92
|
+
_atomic_write(path, payload)
|
|
93
|
+
return path
|
|
94
|
+
except Exception:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def read_active(
|
|
99
|
+
*, ttl_seconds: float = ACTIVATION_TTL_SECONDS, now: float | None = None
|
|
100
|
+
) -> list[ActivationRecord]:
|
|
101
|
+
"""Live activation records. Expired/corrupt/orphaned markers are deleted."""
|
|
102
|
+
stamp = time.time() if now is None else now
|
|
103
|
+
records: list[ActivationRecord] = []
|
|
104
|
+
try:
|
|
105
|
+
entries = sorted(active_dir().glob("*.json"))
|
|
106
|
+
except Exception:
|
|
107
|
+
return []
|
|
108
|
+
for path in entries:
|
|
109
|
+
payload = _read_marker(path)
|
|
110
|
+
if payload is None:
|
|
111
|
+
_discard(path)
|
|
112
|
+
continue
|
|
113
|
+
try:
|
|
114
|
+
root = Path(str(payload["root"]))
|
|
115
|
+
last_seen = float(payload["last_seen"])
|
|
116
|
+
first_seen = float(payload.get("first_seen", last_seen))
|
|
117
|
+
agent = str(payload.get("agent", "unknown"))
|
|
118
|
+
except Exception:
|
|
119
|
+
_discard(path)
|
|
120
|
+
continue
|
|
121
|
+
# Clamp a future timestamp so clock skew cannot pin a repo active.
|
|
122
|
+
if last_seen > stamp:
|
|
123
|
+
last_seen = stamp
|
|
124
|
+
if stamp - last_seen > ttl_seconds:
|
|
125
|
+
_discard(path)
|
|
126
|
+
continue
|
|
127
|
+
if not root.is_dir():
|
|
128
|
+
_discard(path)
|
|
129
|
+
continue
|
|
130
|
+
records.append(
|
|
131
|
+
ActivationRecord(root=root, agent=agent, first_seen=first_seen, last_seen=last_seen)
|
|
132
|
+
)
|
|
133
|
+
return records
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _read_marker(path: Path) -> dict[str, Any] | None:
|
|
137
|
+
try:
|
|
138
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
139
|
+
except Exception:
|
|
140
|
+
return None
|
|
141
|
+
return payload if isinstance(payload, dict) else None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _atomic_write(path: Path, payload: dict[str, Any]) -> None:
|
|
145
|
+
"""Temp-file + replace, so a concurrent reader never sees a partial marker."""
|
|
146
|
+
handle, tmp_name = tempfile.mkstemp(dir=str(path.parent), suffix=".tmp")
|
|
147
|
+
tmp = Path(tmp_name)
|
|
148
|
+
try:
|
|
149
|
+
with os.fdopen(handle, "w", encoding="utf-8") as fh:
|
|
150
|
+
json.dump(payload, fh)
|
|
151
|
+
os.replace(tmp, path)
|
|
152
|
+
except Exception:
|
|
153
|
+
try:
|
|
154
|
+
tmp.unlink(missing_ok=True)
|
|
155
|
+
except Exception:
|
|
156
|
+
pass
|
|
157
|
+
raise
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _discard(path: Path) -> None:
|
|
161
|
+
try:
|
|
162
|
+
path.unlink(missing_ok=True)
|
|
163
|
+
except Exception:
|
|
164
|
+
pass
|