arcgraph 0.1.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 (165) hide show
  1. arcgraph/__init__.py +41 -0
  2. arcgraph/_build_provenance.json +10 -0
  3. arcgraph/adapters/__init__.py +6 -0
  4. arcgraph/adapters/arq_adapter.py +369 -0
  5. arcgraph/adapters/celery_adapter.py +440 -0
  6. arcgraph/adapters/common.py +436 -0
  7. arcgraph/adapters/django_adapter.py +765 -0
  8. arcgraph/adapters/fastapi_adapter.py +415 -0
  9. arcgraph/adapters/logging_adapter.py +335 -0
  10. arcgraph/adapters/mcp_adapter.py +100 -0
  11. arcgraph/adapters/pydantic_adapter.py +290 -0
  12. arcgraph/adapters/pytest_adapter.py +221 -0
  13. arcgraph/adapters/pytest_collection.py +96 -0
  14. arcgraph/adapters/pytest_fixtures.py +371 -0
  15. arcgraph/adapters/pytest_types.py +251 -0
  16. arcgraph/adapters/registry.py +34 -0
  17. arcgraph/adapters/service_container_adapter.py +548 -0
  18. arcgraph/adapters/sqlalchemy_adapter.py +446 -0
  19. arcgraph/adapters/typer_click_adapter.py +292 -0
  20. arcgraph/analyzers/__init__.py +1 -0
  21. arcgraph/analyzers/bindings.py +782 -0
  22. arcgraph/analyzers/calls/__init__.py +6 -0
  23. arcgraph/analyzers/calls/analyzer.py +2921 -0
  24. arcgraph/analyzers/calls/constants.py +702 -0
  25. arcgraph/analyzers/calls/context.py +298 -0
  26. arcgraph/analyzers/calls/lexical.py +237 -0
  27. arcgraph/analyzers/coverage.py +826 -0
  28. arcgraph/analyzers/exports.py +46 -0
  29. arcgraph/analyzers/har_trace.py +323 -0
  30. arcgraph/analyzers/imports.py +190 -0
  31. arcgraph/analyzers/otel_trace.py +352 -0
  32. arcgraph/analyzers/precise_references.py +1199 -0
  33. arcgraph/analyzers/python_scopes.py +56 -0
  34. arcgraph/analyzers/runtime_trace.py +516 -0
  35. arcgraph/analyzers/similarity.py +430 -0
  36. arcgraph/analyzers/symbols.py +363 -0
  37. arcgraph/analyzers/type_unions.py +89 -0
  38. arcgraph/analyzers/types.py +2293 -0
  39. arcgraph/assets/__init__.py +1 -0
  40. arcgraph/assets/workbench/__init__.py +1 -0
  41. arcgraph/assets/workbench/index.html +2398 -0
  42. arcgraph/assets/workbench/vendor/LICENSE.d3.txt +15 -0
  43. arcgraph/assets/workbench/vendor/d3.v7.min.js +2 -0
  44. arcgraph/change/__init__.py +10 -0
  45. arcgraph/change/baseline.py +145 -0
  46. arcgraph/change/changed_regions.py +728 -0
  47. arcgraph/change/contracts.py +927 -0
  48. arcgraph/change/errors.py +88 -0
  49. arcgraph/change/evidence.py +289 -0
  50. arcgraph/change/graph_delta.py +1422 -0
  51. arcgraph/change/identities.py +1277 -0
  52. arcgraph/change/paths.py +234 -0
  53. arcgraph/change/pins.py +426 -0
  54. arcgraph/change/planner.py +504 -0
  55. arcgraph/change/protected_surfaces.py +328 -0
  56. arcgraph/change/provider.py +46 -0
  57. arcgraph/change/service.py +1030 -0
  58. arcgraph/change/source_provider.py +311 -0
  59. arcgraph/change/store.py +1138 -0
  60. arcgraph/change/trusted_runners.py +154 -0
  61. arcgraph/change/verdicts.py +141 -0
  62. arcgraph/change/verification_plan.py +113 -0
  63. arcgraph/change/verifier.py +579 -0
  64. arcgraph/core/__init__.py +1 -0
  65. arcgraph/core/assurance.py +343 -0
  66. arcgraph/core/binding_metrics.py +75 -0
  67. arcgraph/core/cleanup.py +701 -0
  68. arcgraph/core/confidence_profiles.py +108 -0
  69. arcgraph/core/evidence_manifest.py +862 -0
  70. arcgraph/core/expression_kinds.py +11 -0
  71. arcgraph/core/force_graph_export.py +1125 -0
  72. arcgraph/core/freshness.py +94 -0
  73. arcgraph/core/graph_store.py +1099 -0
  74. arcgraph/core/ids.py +206 -0
  75. arcgraph/core/language_tiers.py +150 -0
  76. arcgraph/core/merge.py +649 -0
  77. arcgraph/core/metadata.py +30 -0
  78. arcgraph/core/operation_lock.py +218 -0
  79. arcgraph/core/passes.py +142 -0
  80. arcgraph/core/payload_policy.py +708 -0
  81. arcgraph/core/precise_references.py +124 -0
  82. arcgraph/core/query_engine.py +3078 -0
  83. arcgraph/core/recovery.py +18 -0
  84. arcgraph/core/scanner.py +1454 -0
  85. arcgraph/core/schemas.py +700 -0
  86. arcgraph/core/semantic.py +263 -0
  87. arcgraph/core/semantic_metrics.py +619 -0
  88. arcgraph/core/sharing_retry.py +84 -0
  89. arcgraph/core/structural.py +260 -0
  90. arcgraph/core/target_resolver.py +486 -0
  91. arcgraph/core/type_metrics.py +67 -0
  92. arcgraph/core/unresolved_classification.py +471 -0
  93. arcgraph/core/utils.py +100 -0
  94. arcgraph/core/visual_contract.py +240 -0
  95. arcgraph/core/visual_slice_engine.py +933 -0
  96. arcgraph/interfaces/__init__.py +1 -0
  97. arcgraph/interfaces/agent_capabilities.py +493 -0
  98. arcgraph/interfaces/agent_help.py +313 -0
  99. arcgraph/interfaces/benchmark.py +548 -0
  100. arcgraph/interfaces/ci.py +1992 -0
  101. arcgraph/interfaces/cli.py +2799 -0
  102. arcgraph/interfaces/cli_change.py +690 -0
  103. arcgraph/interfaces/cli_feedback.py +176 -0
  104. arcgraph/interfaces/cli_ops.py +69 -0
  105. arcgraph/interfaces/cli_support.py +113 -0
  106. arcgraph/interfaces/cli_trial.py +42 -0
  107. arcgraph/interfaces/cli_visual.py +601 -0
  108. arcgraph/interfaces/cli_workspace.py +300 -0
  109. arcgraph/interfaces/client_setup.py +481 -0
  110. arcgraph/interfaces/docs.py +1255 -0
  111. arcgraph/interfaces/human_output.py +409 -0
  112. arcgraph/interfaces/local_state.py +577 -0
  113. arcgraph/interfaces/mcp_server.py +457 -0
  114. arcgraph/interfaces/mcp_tools.py +1209 -0
  115. arcgraph/interfaces/metrics.py +571 -0
  116. arcgraph/interfaces/ops.py +330 -0
  117. arcgraph/interfaces/precision.py +1670 -0
  118. arcgraph/interfaces/project_probe.py +75 -0
  119. arcgraph/interfaces/reports.py +1121 -0
  120. arcgraph/interfaces/stdio_encoding.py +29 -0
  121. arcgraph/interfaces/trace.py +592 -0
  122. arcgraph/interfaces/trial_feedback.py +519 -0
  123. arcgraph/interfaces/trial_setup.py +535 -0
  124. arcgraph/interfaces/visual_server.py +354 -0
  125. arcgraph/interfaces/visual_slice.py +168 -0
  126. arcgraph/interfaces/visual_smoke.py +666 -0
  127. arcgraph/interfaces/workbench.py +576 -0
  128. arcgraph/interfaces/workspace.py +1513 -0
  129. arcgraph/pipeline/__init__.py +1 -0
  130. arcgraph/pipeline/contracts.py +122 -0
  131. arcgraph/pipeline/external_frontend.py +451 -0
  132. arcgraph/pipeline/external_l3_frontends.py +368 -0
  133. arcgraph/pipeline/external_protocol.py +358 -0
  134. arcgraph/pipeline/frontends.py +437 -0
  135. arcgraph/pipeline/indexer.py +661 -0
  136. arcgraph/pipeline/openapi_frontend.py +708 -0
  137. arcgraph/pipeline/python_frontend.py +298 -0
  138. arcgraph/pipeline/reindexer.py +1333 -0
  139. arcgraph/pipeline/scip_frontend.py +726 -0
  140. arcgraph/pipeline/typescript_extractor/declarations.mjs +183 -0
  141. arcgraph/pipeline/typescript_extractor/edges.mjs +2794 -0
  142. arcgraph/pipeline/typescript_extractor/graph_primitives.mjs +189 -0
  143. arcgraph/pipeline/typescript_extractor/module_resolution.mjs +877 -0
  144. arcgraph/pipeline/typescript_extractor/runtime.mjs +56 -0
  145. arcgraph/pipeline/typescript_extractor/similarity.mjs +619 -0
  146. arcgraph/pipeline/typescript_extractor/similarity_runner.mjs +19 -0
  147. arcgraph/pipeline/typescript_extractor/sources.mjs +23 -0
  148. arcgraph/pipeline/typescript_extractor.mjs +177 -0
  149. arcgraph/pipeline/typescript_frameworks.py +947 -0
  150. arcgraph/pipeline/typescript_frontend.py +656 -0
  151. arcgraph/pipeline/typescript_references.mjs +167 -0
  152. arcgraph/providers/__init__.py +27 -0
  153. arcgraph/providers/context_provider.py +1258 -0
  154. arcgraph/providers/flow_payload.py +117 -0
  155. arcgraph/providers/memory_connector.py +216 -0
  156. arcgraph/providers/orchestrator.py +475 -0
  157. arcgraph/providers/relationship_payload.py +102 -0
  158. arcgraph/providers/risk_provider.py +843 -0
  159. arcgraph/providers/why_provider.py +349 -0
  160. arcgraph/version_info.py +246 -0
  161. arcgraph-0.1.0.dist-info/METADATA +676 -0
  162. arcgraph-0.1.0.dist-info/RECORD +165 -0
  163. arcgraph-0.1.0.dist-info/WHEEL +4 -0
  164. arcgraph-0.1.0.dist-info/entry_points.txt +2 -0
  165. arcgraph-0.1.0.dist-info/licenses/LICENSE +203 -0
@@ -0,0 +1,330 @@
1
+ """Operational helpers for ArcGraph rebuild and stale-index automation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ import time
7
+ from typing import Any, Callable
8
+
9
+ from arcgraph.pipeline.indexer import ArcGraphIndexer
10
+ from arcgraph.core.cleanup import (
11
+ DEFAULT_BUILD_RETENTION,
12
+ prune_arcgraph_output,
13
+ )
14
+ from arcgraph.core.query_engine import QueryEngine, SchemaVersionError
15
+ from arcgraph.core.scanner import SourceRoot, SourceRootDetection
16
+ from arcgraph.core.schemas import SCHEMA_VERSION
17
+ from arcgraph.pipeline.reindexer import ArcGraphReindexer, FullBuildRequired
18
+
19
+
20
+ def rebuild_index(
21
+ *,
22
+ repo_root: Path,
23
+ output_dir: Path,
24
+ source_roots: list[SourceRoot],
25
+ if_stale: bool = False,
26
+ scip_index_path: str | Path | None = None,
27
+ pyright_export_path: str | Path | None = None,
28
+ runtime_trace_path: str | Path | None = None,
29
+ coverage_path: str | Path | None = None,
30
+ enable_v2_call_resolution: bool = True,
31
+ source_root_detection: SourceRootDetection | None = None,
32
+ ) -> dict[str, Any]:
33
+ before, reason = _current_status(output_dir)
34
+ freshness = before.get("freshness", {}) if before else {}
35
+ should_rebuild = (
36
+ not if_stale
37
+ or before is None
38
+ or freshness.get("stale")
39
+ or before.get("schema_version") != SCHEMA_VERSION
40
+ )
41
+
42
+ if not should_rebuild:
43
+ return {
44
+ "schema_version": SCHEMA_VERSION,
45
+ "status": "skipped",
46
+ "action": "none",
47
+ "reason": "Current index is fresh.",
48
+ "freshness": freshness,
49
+ "before": before,
50
+ }
51
+
52
+ metadata, build_dir = ArcGraphIndexer(
53
+ repo_root,
54
+ output_dir,
55
+ source_roots,
56
+ scip_index_path=scip_index_path,
57
+ pyright_export_path=pyright_export_path,
58
+ runtime_trace_path=runtime_trace_path,
59
+ coverage_path=coverage_path,
60
+ enable_v2_call_resolution=enable_v2_call_resolution,
61
+ source_root_detection=source_root_detection,
62
+ ).build()
63
+ payload = metadata.model_dump(mode="json")
64
+ return {
65
+ "schema_version": SCHEMA_VERSION,
66
+ "status": "available",
67
+ "action": "rebuilt",
68
+ "reason": reason or "Forced rebuild requested.",
69
+ "freshness": {"status": "fresh", "stale": False},
70
+ "before": before,
71
+ "metadata": payload,
72
+ "build_dir": str(build_dir),
73
+ }
74
+
75
+
76
+ def stale_alert(
77
+ query_engine: QueryEngine, *, fail_on_stale: bool = False
78
+ ) -> dict[str, Any]:
79
+ current = query_engine.current()
80
+ freshness = current.get("freshness", {})
81
+ stale = bool(freshness.get("stale"))
82
+ payload = {
83
+ "schema_version": SCHEMA_VERSION,
84
+ "index_version": current.get("index_version"),
85
+ "status": "warn" if stale else "pass",
86
+ "freshness": freshness,
87
+ "capabilities": current.get("capabilities", {}),
88
+ "warnings": ["ArcGraph index is stale."] if stale else [],
89
+ }
90
+ if stale and fail_on_stale:
91
+ payload["_exit_code"] = 1
92
+ return payload
93
+
94
+
95
+ def sync_index(
96
+ *,
97
+ repo_root: Path,
98
+ output_dir: Path,
99
+ if_stale: bool = False,
100
+ ) -> dict[str, Any]:
101
+ """Explicitly publish an incremental index and preserve the old pointer on failure."""
102
+
103
+ started = time.monotonic()
104
+ before, reason = _current_status(output_dir)
105
+ if before is None:
106
+ return _sync_failure(
107
+ before=None,
108
+ reason=reason or "No current index exists.",
109
+ elapsed_ms=_elapsed_ms(started),
110
+ command="arcgraph build",
111
+ )
112
+ freshness = before.get("freshness", {})
113
+ if if_stale and not freshness.get("stale"):
114
+ return {
115
+ "schema_version": SCHEMA_VERSION,
116
+ "status": "skipped",
117
+ "action": "none",
118
+ "published": False,
119
+ "reason": "Current index is fresh.",
120
+ "before_index_version": before.get("index_version"),
121
+ "index_version": before.get("index_version"),
122
+ "freshness": freshness,
123
+ "elapsed_ms": _elapsed_ms(started),
124
+ }
125
+ try:
126
+ result = ArcGraphReindexer(repo_root, output_dir).reindex_changed(
127
+ cleanup_after_publish=False
128
+ )
129
+ except FullBuildRequired as exc:
130
+ return _sync_failure(
131
+ before=before,
132
+ reason=f"{type(exc).__name__}: {exc}",
133
+ elapsed_ms=_elapsed_ms(started),
134
+ command="arcgraph build",
135
+ )
136
+ except (OSError, RuntimeError, ValueError) as exc:
137
+ return _sync_failure(
138
+ before=before,
139
+ reason=f"{type(exc).__name__}: {exc}",
140
+ elapsed_ms=_elapsed_ms(started),
141
+ command="arcgraph sync --if-stale",
142
+ )
143
+
144
+ after, after_reason = _current_status(output_dir)
145
+ if after is None:
146
+ return _sync_failure(
147
+ before=before,
148
+ reason=after_reason or "Published index could not be reopened.",
149
+ elapsed_ms=_elapsed_ms(started),
150
+ command="arcgraph build",
151
+ )
152
+ return {
153
+ "schema_version": SCHEMA_VERSION,
154
+ "status": "available",
155
+ "action": result.get("status", "reindexed"),
156
+ "published": after.get("index_version") != before.get("index_version"),
157
+ "before_index_version": before.get("index_version"),
158
+ "index_version": after.get("index_version"),
159
+ "freshness": after.get("freshness", {}),
160
+ "changed": result.get("changed", {}),
161
+ "cleanup": prune_arcgraph_output(
162
+ output_dir,
163
+ keep_builds=DEFAULT_BUILD_RETENTION,
164
+ dry_run=True,
165
+ ),
166
+ "elapsed_ms": _elapsed_ms(started),
167
+ }
168
+
169
+
170
+ def watch_index(
171
+ *,
172
+ repo_root: Path,
173
+ output_dir: Path,
174
+ poll_interval_seconds: float = 0.25,
175
+ debounce_seconds: float = 0.5,
176
+ max_cycles: int | None = None,
177
+ stop_requested: Callable[[], bool] | None = None,
178
+ stop_reason: Callable[[], str] | None = None,
179
+ on_event: Callable[[dict[str, Any]], None] | None = None,
180
+ ) -> dict[str, Any]:
181
+ """Poll freshness and debounce explicit incremental publications."""
182
+
183
+ poll_interval_seconds = max(0.05, min(poll_interval_seconds, 5.0))
184
+ debounce_seconds = max(0.0, min(debounce_seconds, 10.0))
185
+ cycles = 0
186
+ stale_since: float | None = None
187
+ events: list[dict[str, Any]] = []
188
+ event_count = 0
189
+ stopped = "max_cycles"
190
+ stop_requested_fn = stop_requested
191
+ stop_reason_fn = stop_reason
192
+
193
+ def record_event(event: dict[str, Any]) -> None:
194
+ nonlocal event_count
195
+ public_event = dict(event)
196
+ public_event.pop("_exit_code", None)
197
+ event_count += 1
198
+ events.append(public_event)
199
+ if len(events) > 20:
200
+ del events[0]
201
+ if on_event is not None:
202
+ on_event(
203
+ {
204
+ "schema_version": SCHEMA_VERSION,
205
+ "type": "watch_event",
206
+ "event_index": event_count,
207
+ "event": public_event,
208
+ }
209
+ )
210
+
211
+ try:
212
+ while max_cycles is None or cycles < max_cycles:
213
+ if stop_requested_fn is not None and stop_requested_fn():
214
+ stopped = (
215
+ stop_reason_fn() if stop_reason_fn is not None else "requested"
216
+ )
217
+ break
218
+ cycles += 1
219
+ current, reason = _current_status(output_dir)
220
+ if current is None:
221
+ record_event(
222
+ _sync_failure(
223
+ before=None,
224
+ reason=reason or "No current index exists.",
225
+ elapsed_ms=0,
226
+ command="arcgraph build",
227
+ )
228
+ )
229
+ stopped = "index_unavailable"
230
+ break
231
+ now = time.monotonic()
232
+ if current.get("freshness", {}).get("stale"):
233
+ stale_since = stale_since or now
234
+ if now - stale_since >= debounce_seconds:
235
+ event = sync_index(
236
+ repo_root=repo_root,
237
+ output_dir=output_dir,
238
+ if_stale=True,
239
+ )
240
+ record_event(event)
241
+ if event.get("status") == "partial":
242
+ stopped = "sync_failed"
243
+ break
244
+ stale_since = None
245
+ else:
246
+ stale_since = None
247
+ if max_cycles is None or cycles < max_cycles:
248
+ time.sleep(poll_interval_seconds)
249
+ except KeyboardInterrupt:
250
+ stopped = "keyboard_interrupt"
251
+ abnormal_stop = stopped in {"index_unavailable", "sync_failed"}
252
+ payload = {
253
+ "schema_version": SCHEMA_VERSION,
254
+ "status": "partial" if abnormal_stop else "stopped",
255
+ "action": "watched",
256
+ "cycles": cycles,
257
+ "poll_interval_seconds": poll_interval_seconds,
258
+ "debounce_seconds": debounce_seconds,
259
+ "events": events,
260
+ "event_summary": {
261
+ "total": event_count,
262
+ "returned": len(events),
263
+ "limit": 20,
264
+ "truncated": event_count > len(events),
265
+ "omitted": max(0, event_count - len(events)),
266
+ },
267
+ "cleanup_policy": {
268
+ "automatic_deletion": False,
269
+ "detail": "Watch publishes indexes but only reports dry-run cleanup candidates.",
270
+ },
271
+ "stopped": stopped,
272
+ }
273
+ if abnormal_stop:
274
+ payload["_exit_code"] = 1
275
+ return payload
276
+
277
+
278
+ def _sync_failure(
279
+ *,
280
+ before: dict[str, Any] | None,
281
+ reason: str,
282
+ elapsed_ms: int,
283
+ command: str,
284
+ ) -> dict[str, Any]:
285
+ return {
286
+ "schema_version": SCHEMA_VERSION,
287
+ "status": "partial",
288
+ "action": "kept_previous_index",
289
+ "published": False,
290
+ "reason": reason,
291
+ "before_index_version": (before or {}).get("index_version"),
292
+ "index_version": (before or {}).get("index_version"),
293
+ "freshness": (before or {}).get("freshness", {}),
294
+ "recovery_action": {
295
+ "kind": "refresh_index",
296
+ "command": command,
297
+ "automatic": False,
298
+ },
299
+ "elapsed_ms": elapsed_ms,
300
+ "_exit_code": 1,
301
+ }
302
+
303
+
304
+ def _elapsed_ms(started: float) -> int:
305
+ return max(0, round((time.monotonic() - started) * 1000))
306
+
307
+
308
+ def prune_output(
309
+ *,
310
+ output_dir: Path,
311
+ keep_builds: int = DEFAULT_BUILD_RETENTION,
312
+ include_input_artifacts: bool = False,
313
+ apply: bool = False,
314
+ ) -> dict[str, Any]:
315
+ return prune_arcgraph_output(
316
+ output_dir,
317
+ keep_builds=keep_builds,
318
+ include_input_artifacts=include_input_artifacts,
319
+ dry_run=not apply,
320
+ )
321
+
322
+
323
+ def _current_status(output_dir: Path) -> tuple[dict[str, Any] | None, str | None]:
324
+ try:
325
+ current = QueryEngine(output_dir).current()
326
+ except FileNotFoundError:
327
+ return None, "No current index exists."
328
+ except SchemaVersionError as exc:
329
+ return None, str(exc)
330
+ return current, None