codrspot-processor-mcp 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 (100) hide show
  1. codepreproc_client/__init__.py +14 -0
  2. codepreproc_client/__main__.py +9 -0
  3. codepreproc_client/layer1_business/__init__.py +0 -0
  4. codepreproc_client/layer1_business/allowed_scope_cache.py +62 -0
  5. codepreproc_client/layer1_business/api_client/__init__.py +0 -0
  6. codepreproc_client/layer1_business/api_client/lease_client.py +34 -0
  7. codepreproc_client/layer1_business/api_client/net_guard.py +98 -0
  8. codepreproc_client/layer1_business/api_client/promote_client.py +59 -0
  9. codepreproc_client/layer1_business/api_client/reasoning_client.py +163 -0
  10. codepreproc_client/layer1_business/api_client/registry_client.py +119 -0
  11. codepreproc_client/layer1_business/api_client/superkg_client.py +327 -0
  12. codepreproc_client/layer1_business/cli/__init__.py +0 -0
  13. codepreproc_client/layer1_business/cli/init.py +578 -0
  14. codepreproc_client/layer1_business/cli/main.py +78 -0
  15. codepreproc_client/layer1_business/config.py +104 -0
  16. codepreproc_client/layer1_business/git_utils.py +67 -0
  17. codepreproc_client/layer1_business/license/__init__.py +0 -0
  18. codepreproc_client/layer1_business/license/fingerprint.py +61 -0
  19. codepreproc_client/layer1_business/license/jwt_client.py +60 -0
  20. codepreproc_client/layer1_business/license/project_lock.py +44 -0
  21. codepreproc_client/layer1_business/local_registry.py +40 -0
  22. codepreproc_client/layer1_business/manifest/__init__.py +0 -0
  23. codepreproc_client/layer1_business/manifest/credentials.py +35 -0
  24. codepreproc_client/layer1_business/mcp/__init__.py +0 -0
  25. codepreproc_client/layer1_business/mcp/action_monitor.py +246 -0
  26. codepreproc_client/layer1_business/mcp/lifecycle.py +649 -0
  27. codepreproc_client/layer1_business/mcp/server.py +656 -0
  28. codepreproc_client/layer1_business/mcp/tools.py +1665 -0
  29. codepreproc_client/layer1_business/project_registry.py +99 -0
  30. codepreproc_client/layer2_tooling/__init__.py +0 -0
  31. codepreproc_client/layer2_tooling/condensation/__init__.py +0 -0
  32. codepreproc_client/layer2_tooling/condensation/ast_graph.py +426 -0
  33. codepreproc_client/layer2_tooling/condensation/bm25_store.py +132 -0
  34. codepreproc_client/layer2_tooling/condensation/chunking/__init__.py +0 -0
  35. codepreproc_client/layer2_tooling/condensation/chunking/ast_chunker.py +313 -0
  36. codepreproc_client/layer2_tooling/condensation/chunking/languages.py +238 -0
  37. codepreproc_client/layer2_tooling/condensation/embed_worker.py +128 -0
  38. codepreproc_client/layer2_tooling/condensation/embeddings.py +452 -0
  39. codepreproc_client/layer2_tooling/condensation/extract/__init__.py +0 -0
  40. codepreproc_client/layer2_tooling/condensation/extract/compactor.py +103 -0
  41. codepreproc_client/layer2_tooling/condensation/extract/providers/__init__.py +0 -0
  42. codepreproc_client/layer2_tooling/condensation/extract/providers/base.py +38 -0
  43. codepreproc_client/layer2_tooling/condensation/indexer.py +945 -0
  44. codepreproc_client/layer2_tooling/condensation/ppl/__init__.py +0 -0
  45. codepreproc_client/layer2_tooling/condensation/ppl/emitter.py +162 -0
  46. codepreproc_client/layer2_tooling/condensation/ppl/parser.py +352 -0
  47. codepreproc_client/layer2_tooling/condensation/qdrant_store.py +387 -0
  48. codepreproc_client/layer2_tooling/condensation/scanning/__init__.py +0 -0
  49. codepreproc_client/layer2_tooling/condensation/scanning/manifest_scanner.py +72 -0
  50. codepreproc_client/layer2_tooling/condensation/scanning/md_scanner.py +97 -0
  51. codepreproc_client/layer2_tooling/condensation/scanning/repo_mapper.py +495 -0
  52. codepreproc_client/layer2_tooling/condensation/session_vector_store.py +69 -0
  53. codepreproc_client/layer2_tooling/coupling/__init__.py +0 -0
  54. codepreproc_client/layer2_tooling/coupling/domain.py +263 -0
  55. codepreproc_client/layer2_tooling/coupling/graph.py +255 -0
  56. codepreproc_client/layer2_tooling/coupling/layer2_gate.py +189 -0
  57. codepreproc_client/layer2_tooling/coupling/projector.py +139 -0
  58. codepreproc_client/layer2_tooling/coupling/resolver.py +150 -0
  59. codepreproc_client/layer2_tooling/materialization/__init__.py +0 -0
  60. codepreproc_client/layer2_tooling/materialization/pipeline/__init__.py +0 -0
  61. codepreproc_client/layer2_tooling/materialization/pipeline/execute_semantic.py +1249 -0
  62. codepreproc_client/layer2_tooling/materialization/pipeline/filesystem_reorg.py +575 -0
  63. codepreproc_client/layer2_tooling/materialization/pipeline/patch_generator.py +127 -0
  64. codepreproc_client/layer2_tooling/materialization/pipeline/patch_validator.py +101 -0
  65. codepreproc_client/layer2_tooling/materialization/semantic/__init__.py +19 -0
  66. codepreproc_client/layer2_tooling/materialization/semantic/anchors.py +61 -0
  67. codepreproc_client/layer2_tooling/materialization/semantic/applicability.py +75 -0
  68. codepreproc_client/layer2_tooling/materialization/semantic/edit_planner.py +264 -0
  69. codepreproc_client/layer2_tooling/materialization/semantic/materializer.py +121 -0
  70. codepreproc_client/layer2_tooling/materialization/semantic/merger.py +73 -0
  71. codepreproc_client/layer2_tooling/materialization/semantic/ops/__init__.py +0 -0
  72. codepreproc_client/layer2_tooling/materialization/semantic/ops/add_import.py +36 -0
  73. codepreproc_client/layer2_tooling/materialization/semantic/ops/append_argument.py +37 -0
  74. codepreproc_client/layer2_tooling/materialization/semantic/ops/insert_literal.py +24 -0
  75. codepreproc_client/layer2_tooling/materialization/semantic/ops/remove_import.py +33 -0
  76. codepreproc_client/layer2_tooling/materialization/semantic/ops/remove_statement_unique.py +14 -0
  77. codepreproc_client/layer2_tooling/materialization/semantic/ops/rename_symbol_local.py +16 -0
  78. codepreproc_client/layer2_tooling/materialization/semantic/region_resolver.py +229 -0
  79. codepreproc_client/layer2_tooling/materialization/semantic/synthesizer.py +186 -0
  80. codepreproc_client/layer2_tooling/materialization/semantic/target_locator.py +94 -0
  81. codepreproc_client/layer2_tooling/materialization/semantic/validator.py +322 -0
  82. codepreproc_client/layer2_tooling/materialization/snippet/__init__.py +0 -0
  83. codepreproc_client/layer2_tooling/materialization/snippet/assembler.py +503 -0
  84. codepreproc_client/layer2_tooling/materialization/snippet/loader.py +187 -0
  85. codepreproc_client/layer2_tooling/retrieval/__init__.py +0 -0
  86. codepreproc_client/layer2_tooling/retrieval/graph_walk.py +47 -0
  87. codepreproc_client/layer2_tooling/retrieval/hybrid.py +95 -0
  88. codepreproc_client/layer2_tooling/retrieval/rerank_worker.py +86 -0
  89. codepreproc_client/layer2_tooling/retrieval/reranker.py +213 -0
  90. codepreproc_client/layer2_tooling/watcher/__init__.py +0 -0
  91. codepreproc_client/layer2_tooling/watcher/fs_watcher.py +3 -0
  92. codrspot_processor_mcp-0.1.0.dist-info/METADATA +396 -0
  93. codrspot_processor_mcp-0.1.0.dist-info/RECORD +100 -0
  94. codrspot_processor_mcp-0.1.0.dist-info/WHEEL +5 -0
  95. codrspot_processor_mcp-0.1.0.dist-info/entry_points.txt +3 -0
  96. codrspot_processor_mcp-0.1.0.dist-info/licenses/LICENSE +23 -0
  97. codrspot_processor_mcp-0.1.0.dist-info/top_level.txt +2 -0
  98. contracts/__init__.py +0 -0
  99. contracts/dtos.py +1239 -0
  100. contracts/ir.py +102 -0
@@ -0,0 +1,327 @@
1
+ """Client for the SuperKG proxy: POST /v1/superkg/* (contracts/openapi.yaml).
2
+
3
+ Real network calls, not a direct connection to SuperKG - codepreproc-server
4
+ is the only thing that holds the real SuperKG URL/credentials
5
+ (layer3_core/superkg/connector.py on the server). This crosses the same
6
+ JWT-gated boundary as promote/registry; identity (tenant_id/user_id) is
7
+ derived server-side from the JWT, never sent by the client. Every method
8
+ takes `project_id` because authorization is scoped per-project (see
9
+ layer2_infra/api/authz.py::superkg_system_authorized on the server).
10
+
11
+ All public methods are safe to call without awaiting result checks - they
12
+ absorb exceptions and return None/[]/no-op on any failure so the rest of
13
+ codepreproc is unaffected when SuperKG (or the server) is unavailable.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ from dataclasses import dataclass, field
20
+ from typing import TYPE_CHECKING, Any, Awaitable, Callable
21
+
22
+ import httpx
23
+
24
+ from codepreproc_client.layer1_business.api_client.net_guard import assert_safe_to_send
25
+ from contracts.dtos import (
26
+ SuperkgBootstrapRequest,
27
+ SuperkgChunkMetadata,
28
+ SuperkgDecisionRequest,
29
+ SuperkgEnrichRequest,
30
+ SuperkgIngestRequest,
31
+ SuperkgLogActivityRequest,
32
+ SuperkgQueryRequest,
33
+ SuperkgSessionCloseRequest,
34
+ SuperkgSessionOpenRequest,
35
+ SuperkgTransitionRequest,
36
+ )
37
+
38
+ if TYPE_CHECKING:
39
+ from contracts.dtos import CodeChunk
40
+ from contracts.ir import BusinessLogicDiff
41
+
42
+ JWTProvider = Callable[[str], Awaitable[str]] # project_id -> bearer token
43
+
44
+
45
+ @dataclass
46
+ class KGScore:
47
+ kg_score: float = 0.0
48
+ signals: dict[str, Any] = field(default_factory=dict)
49
+
50
+
51
+ @dataclass
52
+ class EnrichResult:
53
+ trace_id: str
54
+ kg_scores: dict[str, KGScore] = field(default_factory=dict)
55
+ pattern_suggestions: list[dict[str, Any]] = field(default_factory=list)
56
+ warnings: list[dict[str, Any]] = field(default_factory=list)
57
+
58
+ _log = logging.getLogger("codepreproc.superkg_client")
59
+
60
+
61
+ class SuperKGClient:
62
+ """Thin async wrapper around codepreproc-server's `/v1/superkg/*` proxy."""
63
+
64
+ def __init__(
65
+ self,
66
+ base_url: str,
67
+ jwt_provider: JWTProvider,
68
+ enabled: bool = True,
69
+ timeout: float = 10.0,
70
+ enrich_timeout: float = 0.8,
71
+ ) -> None:
72
+ self.enabled = enabled
73
+ self._base = base_url.rstrip("/")
74
+ self._jwt_provider = jwt_provider
75
+ self._timeout = timeout
76
+ self._enrich_timeout = enrich_timeout
77
+
78
+ # ------------------------------------------------------------------
79
+ # Internal helpers
80
+ # ------------------------------------------------------------------
81
+
82
+ async def _headers(self, project_id: str) -> dict[str, str]:
83
+ jwt = await self._jwt_provider(project_id)
84
+ return {"Authorization": f"Bearer {jwt}"}
85
+
86
+ async def _post(
87
+ self, path: str, project_id: str, request, *, kind: str | None = None, timeout: float | None = None
88
+ ) -> dict | None:
89
+ if not self.enabled:
90
+ return None
91
+ assert_safe_to_send(request)
92
+ payload = request.model_dump(mode="json")
93
+ if kind is not None:
94
+ payload["kind"] = kind
95
+ try:
96
+ headers = await self._headers(project_id)
97
+ async with httpx.AsyncClient(base_url=self._base, timeout=timeout or self._timeout) as client:
98
+ resp = await client.post(f"/v1/superkg{path}", json=payload, headers=headers)
99
+ resp.raise_for_status()
100
+ return resp.json()
101
+ except Exception as exc:
102
+ _log.debug("SuperKG proxy POST %s failed: %s", path, exc)
103
+ return None
104
+
105
+ async def _delete(self, path: str, project_id: str, request) -> None:
106
+ if not self.enabled:
107
+ return
108
+ assert_safe_to_send(request)
109
+ try:
110
+ headers = await self._headers(project_id)
111
+ async with httpx.AsyncClient(base_url=self._base, timeout=self._timeout) as client:
112
+ resp = await client.request(
113
+ "DELETE", f"/v1/superkg{path}", json=request.model_dump(mode="json"), headers=headers
114
+ )
115
+ resp.raise_for_status()
116
+ except Exception as exc:
117
+ _log.debug("SuperKG proxy DELETE %s failed: %s", path, exc)
118
+
119
+ # ------------------------------------------------------------------
120
+ # Project bootstrap
121
+ # ------------------------------------------------------------------
122
+
123
+ async def bootstrap(self, project_id: str, name: str, description: str) -> str | None:
124
+ """Register a project as a SuperKG system. Returns the system_id or None."""
125
+ data = await self._post(
126
+ "/bootstrap", project_id, SuperkgBootstrapRequest(project_id=project_id, name=name, description=description)
127
+ )
128
+ return data.get("system_id") if data else None
129
+
130
+ # ------------------------------------------------------------------
131
+ # Sessions
132
+ # ------------------------------------------------------------------
133
+
134
+ async def open_session(self, project_id: str, system_id: str, context: str) -> str | None:
135
+ """Open a work session in SuperKG. Returns session_id or None."""
136
+ data = await self._post(
137
+ "/session", project_id, SuperkgSessionOpenRequest(project_id=project_id, system_id=system_id, context=context)
138
+ )
139
+ return data.get("session_id") if data else None
140
+
141
+ async def close_session(self, project_id: str, session_id: str) -> None:
142
+ """Close a SuperKG session (fire-and-forget)."""
143
+ await self._delete(
144
+ "/session", project_id, SuperkgSessionCloseRequest(project_id=project_id, session_id=session_id)
145
+ )
146
+
147
+ # ------------------------------------------------------------------
148
+ # Decisions & activity
149
+ # ------------------------------------------------------------------
150
+
151
+ async def record_decision(
152
+ self,
153
+ project_id: str,
154
+ system_id: str,
155
+ session_id: str | None,
156
+ intent: str,
157
+ plan_summary: dict,
158
+ bld: "BusinessLogicDiff | None" = None,
159
+ expected_sequence: int | None = None,
160
+ ) -> None:
161
+ """Record a semantic edit decision in SuperKG (fire-and-forget)."""
162
+ req = SuperkgDecisionRequest(
163
+ project_id=project_id,
164
+ system_id=system_id,
165
+ session_id=session_id,
166
+ intent=intent,
167
+ plan_summary=plan_summary,
168
+ bld=bld.model_dump(mode="json") if bld is not None else None,
169
+ expected_sequence=expected_sequence,
170
+ )
171
+ await self._post("/activity", project_id, req, kind="decision")
172
+
173
+ async def log_activity(self, project_id: str, system_id: str, activity_type: str, details: dict) -> None:
174
+ """Log a work activity event in SuperKG (fire-and-forget)."""
175
+ req = SuperkgLogActivityRequest(
176
+ project_id=project_id, system_id=system_id, activity_type=activity_type, details=details
177
+ )
178
+ await self._post("/activity", project_id, req, kind="activity")
179
+
180
+ async def transition_status(
181
+ self, project_id: str, system_id: str, entity_id: str, entity_type: str, new_status: str
182
+ ) -> None:
183
+ """Apply a state transition to an entity (fire-and-forget)."""
184
+ req = SuperkgTransitionRequest(
185
+ project_id=project_id,
186
+ system_id=system_id,
187
+ entity_id=entity_id,
188
+ entity_type=entity_type,
189
+ new_status=new_status,
190
+ )
191
+ await self._post("/activity", project_id, req, kind="transition")
192
+
193
+ # ------------------------------------------------------------------
194
+ # Queries
195
+ # ------------------------------------------------------------------
196
+
197
+ async def get_snapshot(self, project_id: str, system_id: str) -> dict | None:
198
+ """Return a consistent snapshot of the system state."""
199
+ data = await self._post(
200
+ "/query", project_id, SuperkgQueryRequest(project_id=project_id, system_id=system_id, kind="snapshot")
201
+ )
202
+ return data.get("data") if data else None
203
+
204
+ async def get_context(self, project_id: str, system_id: str, mode: str = "narrative") -> dict | None:
205
+ """Return LLM-ready context for a system in the requested mode.
206
+
207
+ Modes: reflexive, comparative, narrative, consolidation, simulation.
208
+ """
209
+ data = await self._post(
210
+ "/query",
211
+ project_id,
212
+ SuperkgQueryRequest(project_id=project_id, system_id=system_id, kind="context", mode=mode),
213
+ )
214
+ return data.get("data") if data else None
215
+
216
+ async def get_insights(self, project_id: str, system_id: str) -> list[dict]:
217
+ """List insights for a system."""
218
+ data = await self._post(
219
+ "/query", project_id, SuperkgQueryRequest(project_id=project_id, system_id=system_id, kind="insights")
220
+ )
221
+ return data.get("items", []) if data else []
222
+
223
+ async def get_patterns(self, project_id: str, system_id: str) -> list[dict]:
224
+ """List observed patterns across all systems."""
225
+ data = await self._post(
226
+ "/query", project_id, SuperkgQueryRequest(project_id=project_id, system_id=system_id, kind="patterns")
227
+ )
228
+ return data.get("items", []) if data else []
229
+
230
+ # ------------------------------------------------------------------
231
+ # Ingest (granular entity registration)
232
+ # ------------------------------------------------------------------
233
+
234
+ async def ingest_component(self, project_id: str, system_id: str, file_path: str, language: str) -> str | None:
235
+ """Register a source file as a SuperKG component. Returns component_id or None."""
236
+ data = await self._post(
237
+ "/ingest",
238
+ project_id,
239
+ SuperkgIngestRequest(
240
+ project_id=project_id, system_id=system_id, kind="component", file_path=file_path, language=language
241
+ ),
242
+ )
243
+ return data.get("id") if data else None
244
+
245
+ async def ingest_object(
246
+ self,
247
+ project_id: str,
248
+ system_id: str,
249
+ component_id: str | None,
250
+ chunk: "CodeChunk",
251
+ ) -> None:
252
+ """Register a CodeChunk's metadata as a SuperKG object (fire-and-forget).
253
+
254
+ Raw source (`chunk.body`) is never sent - net_guard.py forbids a
255
+ `body` field from crossing the client->server boundary.
256
+ """
257
+ metadata = SuperkgChunkMetadata(
258
+ chunk_id=chunk.chunk_id,
259
+ project_id=chunk.project_id,
260
+ file_path=chunk.file_path,
261
+ language=chunk.language,
262
+ symbol=chunk.symbol,
263
+ kind=chunk.kind,
264
+ start_line=chunk.start_line,
265
+ end_line=chunk.end_line,
266
+ signature=chunk.signature,
267
+ git_sha=chunk.git_sha,
268
+ content_hash=chunk.content_hash,
269
+ )
270
+ await self._post(
271
+ "/ingest",
272
+ project_id,
273
+ SuperkgIngestRequest(
274
+ project_id=project_id, system_id=system_id, kind="object", component_id=component_id, chunk=metadata
275
+ ),
276
+ )
277
+
278
+ async def ingest_repo_map(
279
+ self, project_id: str, system_id: str, repo_map: dict[str, Any], output_path: str | None = None
280
+ ) -> None:
281
+ """Register a generated repository map as project-level knowledge."""
282
+ await self._post(
283
+ "/ingest",
284
+ project_id,
285
+ SuperkgIngestRequest(
286
+ project_id=project_id,
287
+ system_id=system_id,
288
+ kind="repo_map",
289
+ repo_map=repo_map,
290
+ output_path=output_path,
291
+ ),
292
+ )
293
+
294
+ # ------------------------------------------------------------------
295
+ # Enrichment
296
+ # ------------------------------------------------------------------
297
+
298
+ async def enrich(self, project_id: str, system_id: str, query: str, candidates: list[dict]) -> EnrichResult | None:
299
+ """Call the enrich proxy with top reranked candidates. Returns None on timeout/failure/disabled."""
300
+ data = await self._post(
301
+ "/enrich",
302
+ project_id,
303
+ SuperkgEnrichRequest(project_id=project_id, system_id=system_id, query=query, candidates=candidates),
304
+ timeout=self._enrich_timeout,
305
+ )
306
+ if not data:
307
+ return None
308
+ raw_scores = data.get("kg_scores", {})
309
+ kg_scores = {
310
+ cid: KGScore(kg_score=float(v.get("kg_score", 0.0)), signals=v.get("signals", {}))
311
+ for cid, v in raw_scores.items()
312
+ }
313
+ return EnrichResult(
314
+ trace_id=data.get("trace_id", ""),
315
+ kg_scores=kg_scores,
316
+ pattern_suggestions=data.get("pattern_suggestions", []),
317
+ warnings=data.get("warnings", []),
318
+ )
319
+
320
+ # ------------------------------------------------------------------
321
+ # Health
322
+ # ------------------------------------------------------------------
323
+
324
+ async def health(self) -> dict | None:
325
+ """SuperKG proxy health isn't exposed as a distinct endpoint - reachability is
326
+ implied by any successful call above; callers should not depend on this."""
327
+ return None
File without changes