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
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