sourcecode 1.32.1__py3-none-any.whl → 1.32.2__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.
- sourcecode/__init__.py +1 -1
- sourcecode/cli.py +56 -1
- sourcecode/mcp/server.py +36 -0
- sourcecode/ris.py +372 -0
- {sourcecode-1.32.1.dist-info → sourcecode-1.32.2.dist-info}/METADATA +1 -1
- {sourcecode-1.32.1.dist-info → sourcecode-1.32.2.dist-info}/RECORD +9 -8
- {sourcecode-1.32.1.dist-info → sourcecode-1.32.2.dist-info}/WHEEL +0 -0
- {sourcecode-1.32.1.dist-info → sourcecode-1.32.2.dist-info}/entry_points.txt +0 -0
- {sourcecode-1.32.1.dist-info → sourcecode-1.32.2.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -140,7 +140,21 @@ def _check_pipeline_coherence(sm: "SourceMap") -> list[str]: # type: ignore[nam
|
|
|
140
140
|
|
|
141
141
|
return issues
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
def _build_help_text() -> str:
|
|
144
|
+
"""Build --help text dynamically based on current license state."""
|
|
145
|
+
try:
|
|
146
|
+
from sourcecode.license import is_pro as _is_pro
|
|
147
|
+
except Exception:
|
|
148
|
+
_is_pro = False
|
|
149
|
+
|
|
150
|
+
if _is_pro:
|
|
151
|
+
plan_badge = "[bold green]● Pro[/bold green]"
|
|
152
|
+
else:
|
|
153
|
+
plan_badge = "[yellow]Free[/yellow] · [dim]sourcecode activate <key>[/dim] to unlock Pro"
|
|
154
|
+
|
|
155
|
+
text = f"""\
|
|
156
|
+
[bold]sourcecode[/bold] {plan_badge}
|
|
157
|
+
|
|
144
158
|
Deterministic codebase context for AI coding agents.
|
|
145
159
|
|
|
146
160
|
[bold]Primary usage:[/bold]
|
|
@@ -164,6 +178,26 @@ Deterministic codebase context for AI coding agents.
|
|
|
164
178
|
version
|
|
165
179
|
"""
|
|
166
180
|
|
|
181
|
+
if not _is_pro:
|
|
182
|
+
text += """\
|
|
183
|
+
|
|
184
|
+
[dim bold]Locked (Pro):[/dim bold]
|
|
185
|
+
[dim]impact blast radius before any change[/dim]
|
|
186
|
+
[dim]modernize (full) dead zones, tangles, full coupling[/dim]
|
|
187
|
+
[dim]fix-bug (full) complete risk-ranked file list[/dim]
|
|
188
|
+
[dim]review-pr (expanded) CI-grade PR review[/dim]
|
|
189
|
+
[dim]prepare-context delta incremental context for CI/CD[/dim]
|
|
190
|
+
[dim]prepare-context generate-tests test gap analysis[/dim]
|
|
191
|
+
[dim]--full removes all truncation limits[/dim]
|
|
192
|
+
|
|
193
|
+
[dim cyan]→ sourcecode activate <key>[/dim cyan]
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
return text
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
_HELP = _build_help_text()
|
|
200
|
+
|
|
167
201
|
# Known subcommand names — tokens matching these are routed as subcommands,
|
|
168
202
|
# not consumed as a repository path.
|
|
169
203
|
_SUBCOMMANDS: frozenset[str] = frozenset(
|
|
@@ -344,6 +378,10 @@ def _get_command_with_preprocessing(typer_instance: Any) -> Any:
|
|
|
344
378
|
cmd = _orig_get_command(typer_instance)
|
|
345
379
|
if typer_instance is not app:
|
|
346
380
|
return cmd # only wrap the root app, not telemetry_app etc.
|
|
381
|
+
|
|
382
|
+
# Refresh help text at invocation time so it reflects current license state.
|
|
383
|
+
cmd.help = _build_help_text()
|
|
384
|
+
|
|
347
385
|
_orig_cmd_main = cmd.main
|
|
348
386
|
|
|
349
387
|
def _cmd_main(args: Optional[list[str]] = None, **kwargs: Any) -> Any:
|
|
@@ -2011,6 +2049,15 @@ def main(
|
|
|
2011
2049
|
except Exception:
|
|
2012
2050
|
pass # non-fatal: cache write failure
|
|
2013
2051
|
|
|
2052
|
+
# Update RIS with aggregated snapshot data (non-fatal side-effect).
|
|
2053
|
+
if not no_cache and not _pipeline_error and _core_key:
|
|
2054
|
+
try:
|
|
2055
|
+
from sourcecode.serializer import core_view as _ris_core_view
|
|
2056
|
+
from sourcecode.ris import maybe_update_ris as _ris_update
|
|
2057
|
+
_ris_update(target, _ris_core_view(sm), _git_sha)
|
|
2058
|
+
except Exception:
|
|
2059
|
+
pass
|
|
2060
|
+
|
|
2014
2061
|
if _pipeline_error:
|
|
2015
2062
|
raise typer.Exit(code=2)
|
|
2016
2063
|
|
|
@@ -3147,6 +3194,14 @@ def endpoints_cmd(
|
|
|
3147
3194
|
raise typer.Exit(code=1)
|
|
3148
3195
|
|
|
3149
3196
|
data = _extract_java_endpoints(target)
|
|
3197
|
+
|
|
3198
|
+
# Update RIS api_surface section (non-fatal side-effect).
|
|
3199
|
+
try:
|
|
3200
|
+
from sourcecode.ris import update_ris_api_surface as _ris_ep
|
|
3201
|
+
_ris_ep(target, data)
|
|
3202
|
+
except Exception:
|
|
3203
|
+
pass
|
|
3204
|
+
|
|
3150
3205
|
output = _serialize_dict(data, format)
|
|
3151
3206
|
|
|
3152
3207
|
if output_path is not None:
|
sourcecode/mcp/server.py
CHANGED
|
@@ -109,6 +109,42 @@ def _check_repo_path(path: str) -> "CallToolResult | None":
|
|
|
109
109
|
return None
|
|
110
110
|
|
|
111
111
|
|
|
112
|
+
@mcp.tool()
|
|
113
|
+
def get_cold_start_context(repo_path: str = ".") -> dict:
|
|
114
|
+
"""Instant session bootstrap from persisted Repository Intelligence Snapshot (RIS).
|
|
115
|
+
|
|
116
|
+
CALL THIS FIRST at the start of every MCP session. Returns cached structural
|
|
117
|
+
context built from prior analysis runs — zero re-analysis cost.
|
|
118
|
+
|
|
119
|
+
status values:
|
|
120
|
+
"cold_start_ready" — RIS exists and matches the current git HEAD.
|
|
121
|
+
"cold_start_stale" — RIS exists but HEAD has changed since last analysis.
|
|
122
|
+
Data is still useful; run get_compact_context to refresh.
|
|
123
|
+
"no_ris" — No RIS yet for this repo; run get_compact_context first.
|
|
124
|
+
|
|
125
|
+
Returns: status, repo_id, git_head, stale (bool), last_updated_at,
|
|
126
|
+
summary (compact snapshot), entrypoints, endpoints, hotspots.
|
|
127
|
+
|
|
128
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
129
|
+
"""
|
|
130
|
+
_raw = repo_path
|
|
131
|
+
try:
|
|
132
|
+
if not isinstance(repo_path, str):
|
|
133
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
134
|
+
repo_path = _normalize_repo_path(repo_path)
|
|
135
|
+
_path_err = _check_repo_path(repo_path)
|
|
136
|
+
if _path_err is not None:
|
|
137
|
+
return _path_err
|
|
138
|
+
from pathlib import Path as _Path
|
|
139
|
+
from sourcecode.ris import get_cold_start_context as _gcs
|
|
140
|
+
return _ok(_gcs(_Path(repo_path)))
|
|
141
|
+
except Exception as exc:
|
|
142
|
+
return _err(
|
|
143
|
+
f"Internal error: {type(exc).__name__}: {exc} — repo_path: {_raw}",
|
|
144
|
+
"INTERNAL_ERROR",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
112
148
|
@mcp.tool()
|
|
113
149
|
def get_compact_context(repo_path: str = ".", git_context: bool = False) -> dict:
|
|
114
150
|
"""Compact human/LLM summary of a repository (~1000-3000 tokens). USE THIS FIRST.
|
sourcecode/ris.py
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"""Repository Intelligence Snapshot (RIS) — persistent repo-level semantic state.
|
|
2
|
+
|
|
3
|
+
RIS aggregates outputs from per-commit L1/L2 snapshots into a single artifact
|
|
4
|
+
that survives across git commits. It enables cold-start bootstrapping for MCP
|
|
5
|
+
sessions: on first tool call, agents receive structural context immediately
|
|
6
|
+
without triggering re-analysis.
|
|
7
|
+
|
|
8
|
+
Storage layout:
|
|
9
|
+
~/.sourcecode/cache/<repo_id>/ris.json.gz
|
|
10
|
+
|
|
11
|
+
Build lifecycle:
|
|
12
|
+
1. After every successful analysis run, ``maybe_update_ris`` is called with
|
|
13
|
+
the L1 core_dict (already in scope in cli.py after ``write_core``).
|
|
14
|
+
2. If the git HEAD matches the stored RIS → only compact/agent/git sections
|
|
15
|
+
are refreshed (the API surface from ``endpoints`` is preserved).
|
|
16
|
+
3. If the git HEAD changed → all sections are rebuilt from the new core_dict
|
|
17
|
+
(api_surface preserved if the caller does not provide updated endpoints).
|
|
18
|
+
4. ``get_cold_start_context`` reads the RIS and returns a lightweight
|
|
19
|
+
bootstrap object. It is safe to call from MCP on every session init.
|
|
20
|
+
|
|
21
|
+
RIS is NOT a command cache. It stores a semantic model of the repository
|
|
22
|
+
derived from existing snapshot outputs, not raw command results.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import gzip
|
|
27
|
+
import json
|
|
28
|
+
import subprocess
|
|
29
|
+
from dataclasses import asdict, dataclass
|
|
30
|
+
from datetime import datetime, timezone
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Optional
|
|
33
|
+
|
|
34
|
+
RIS_SCHEMA_VERSION: str = "1.0"
|
|
35
|
+
_RIS_FILENAME: str = "ris.json.gz"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Schema
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class RepositoryIntelligenceSnapshot:
|
|
44
|
+
repo_id: str
|
|
45
|
+
created_at: str
|
|
46
|
+
last_updated_at: str
|
|
47
|
+
git_head: str
|
|
48
|
+
version: str
|
|
49
|
+
structural_map: dict
|
|
50
|
+
api_surface: dict
|
|
51
|
+
dependency_graph: dict
|
|
52
|
+
compact_summary: dict
|
|
53
|
+
agent_index: dict
|
|
54
|
+
git_context_snapshot: dict
|
|
55
|
+
metadata: dict
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
# Storage helpers
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
def _ris_path(repo_root: Path) -> Path:
|
|
63
|
+
from sourcecode.cache import cache_dir as _cache_dir
|
|
64
|
+
return _cache_dir(repo_root) / _RIS_FILENAME
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def load_ris(repo_root: Path) -> Optional[RepositoryIntelligenceSnapshot]:
|
|
68
|
+
"""Load RIS from disk. Returns None on any error (missing, corrupt, stale schema)."""
|
|
69
|
+
try:
|
|
70
|
+
p = _ris_path(repo_root)
|
|
71
|
+
if not p.exists():
|
|
72
|
+
return None
|
|
73
|
+
raw = gzip.decompress(p.read_bytes())
|
|
74
|
+
d = json.loads(raw)
|
|
75
|
+
if not isinstance(d, dict) or d.get("version") != RIS_SCHEMA_VERSION:
|
|
76
|
+
return None
|
|
77
|
+
return RepositoryIntelligenceSnapshot(
|
|
78
|
+
repo_id=d.get("repo_id", ""),
|
|
79
|
+
created_at=d.get("created_at", ""),
|
|
80
|
+
last_updated_at=d.get("last_updated_at", ""),
|
|
81
|
+
git_head=d.get("git_head", ""),
|
|
82
|
+
version=d.get("version", ""),
|
|
83
|
+
structural_map=d.get("structural_map", {}),
|
|
84
|
+
api_surface=d.get("api_surface", {}),
|
|
85
|
+
dependency_graph=d.get("dependency_graph", {}),
|
|
86
|
+
compact_summary=d.get("compact_summary", {}),
|
|
87
|
+
agent_index=d.get("agent_index", {}),
|
|
88
|
+
git_context_snapshot=d.get("git_context_snapshot", {}),
|
|
89
|
+
metadata=d.get("metadata", {}),
|
|
90
|
+
)
|
|
91
|
+
except Exception:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def save_ris(repo_root: Path, ris: RepositoryIntelligenceSnapshot) -> None:
|
|
96
|
+
"""Write RIS to disk as gzip-compressed JSON. Never raises."""
|
|
97
|
+
try:
|
|
98
|
+
p = _ris_path(repo_root)
|
|
99
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
100
|
+
raw = json.dumps(asdict(ris), ensure_ascii=False, separators=(",", ":"))
|
|
101
|
+
p.write_bytes(gzip.compress(raw.encode("utf-8"), compresslevel=6))
|
|
102
|
+
except Exception:
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
# Data extraction from L1 core_dict
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
def _extract_structural_map(agent_data: dict) -> dict:
|
|
111
|
+
"""Extract structural map from agent_view output."""
|
|
112
|
+
entry_points = agent_data.get("entry_points", [])
|
|
113
|
+
|
|
114
|
+
layers = []
|
|
115
|
+
domains = []
|
|
116
|
+
arch = agent_data.get("architecture")
|
|
117
|
+
if isinstance(arch, dict):
|
|
118
|
+
layers = arch.get("layers") or []
|
|
119
|
+
domains = arch.get("domains") or []
|
|
120
|
+
|
|
121
|
+
controllers: list = []
|
|
122
|
+
services: list = []
|
|
123
|
+
for layer in layers:
|
|
124
|
+
if not isinstance(layer, dict):
|
|
125
|
+
continue
|
|
126
|
+
name = (layer.get("name") or "").lower()
|
|
127
|
+
files = layer.get("files") or []
|
|
128
|
+
if any(kw in name for kw in ("controller", "rest", "api", "endpoint")):
|
|
129
|
+
controllers.extend(files)
|
|
130
|
+
elif any(kw in name for kw in ("service", "business", "domain", "use_case")):
|
|
131
|
+
services.extend(files)
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
"entrypoints": entry_points,
|
|
135
|
+
"controllers": controllers,
|
|
136
|
+
"services": services,
|
|
137
|
+
"modules": domains,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _extract_api_surface(agent_data: dict) -> dict:
|
|
142
|
+
"""Extract API surface from agent_view output (Java security surface if present)."""
|
|
143
|
+
endpoints: list = []
|
|
144
|
+
controllers_index: list = []
|
|
145
|
+
|
|
146
|
+
signals = agent_data.get("signals") or {}
|
|
147
|
+
sec = signals.get("security_surface") or {}
|
|
148
|
+
if isinstance(sec, dict):
|
|
149
|
+
raw_endpoints = sec.get("endpoints") or []
|
|
150
|
+
if isinstance(raw_endpoints, list):
|
|
151
|
+
endpoints = raw_endpoints
|
|
152
|
+
controllers_index = sec.get("controllers") or []
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
"endpoints": endpoints,
|
|
156
|
+
"controllers_index": controllers_index,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _extract_dependency_graph(compact_data: dict, agent_data: dict) -> dict:
|
|
161
|
+
"""Extract dependency graph summary from view outputs."""
|
|
162
|
+
nodes = agent_data.get("key_dependencies") or compact_data.get("key_dependencies") or []
|
|
163
|
+
summary = compact_data.get("dependency_summary") or {}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
"nodes": nodes,
|
|
167
|
+
"edges": [],
|
|
168
|
+
"summary": summary,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _extract_git_context_snapshot(agent_data: dict) -> dict:
|
|
173
|
+
"""Extract git context from agent_view output."""
|
|
174
|
+
gc = agent_data.get("git_context") or {}
|
|
175
|
+
if not isinstance(gc, dict):
|
|
176
|
+
return {"last_commits": [], "hotspots": []}
|
|
177
|
+
return {
|
|
178
|
+
"last_commits": gc.get("recent_commits") or [],
|
|
179
|
+
"hotspots": gc.get("top_hotspots") or [],
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
# Build / update
|
|
185
|
+
# ---------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
def _now_iso() -> str:
|
|
188
|
+
return datetime.now(timezone.utc).isoformat()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _build_from_core(
|
|
192
|
+
repo_root: Path,
|
|
193
|
+
core_dict: dict,
|
|
194
|
+
git_head: str,
|
|
195
|
+
existing_api_surface: Optional[dict] = None,
|
|
196
|
+
) -> RepositoryIntelligenceSnapshot:
|
|
197
|
+
from sourcecode.cache import repo_id as _repo_id_fn
|
|
198
|
+
|
|
199
|
+
compact_data = core_dict.get("_compact") or {}
|
|
200
|
+
agent_data = core_dict.get("_agent") or {}
|
|
201
|
+
|
|
202
|
+
# Strip file_relevance — large and not needed for cold-start
|
|
203
|
+
agent_index = {k: v for k, v in agent_data.items() if k != "file_relevance"}
|
|
204
|
+
|
|
205
|
+
now = _now_iso()
|
|
206
|
+
return RepositoryIntelligenceSnapshot(
|
|
207
|
+
repo_id=_repo_id_fn(repo_root),
|
|
208
|
+
created_at=now,
|
|
209
|
+
last_updated_at=now,
|
|
210
|
+
git_head=git_head,
|
|
211
|
+
version=RIS_SCHEMA_VERSION,
|
|
212
|
+
structural_map=_extract_structural_map(agent_data),
|
|
213
|
+
api_surface=existing_api_surface or _extract_api_surface(agent_data),
|
|
214
|
+
dependency_graph=_extract_dependency_graph(compact_data, agent_data),
|
|
215
|
+
compact_summary=compact_data,
|
|
216
|
+
agent_index=agent_index,
|
|
217
|
+
git_context_snapshot=_extract_git_context_snapshot(agent_data),
|
|
218
|
+
metadata={
|
|
219
|
+
"snapshot_source": "existing_snapshot_system",
|
|
220
|
+
"confidence": float(
|
|
221
|
+
1.0 if agent_data else (0.5 if compact_data else 0.0)
|
|
222
|
+
),
|
|
223
|
+
"partial": not bool(agent_data),
|
|
224
|
+
},
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def maybe_update_ris(repo_root: Path, core_dict: dict, git_head: str) -> None:
|
|
229
|
+
"""Update (or create) the RIS artifact from an L1 core_dict.
|
|
230
|
+
|
|
231
|
+
Called from cli.py after every successful write_core. Never raises.
|
|
232
|
+
"""
|
|
233
|
+
try:
|
|
234
|
+
if not isinstance(core_dict, dict):
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
existing = load_ris(repo_root)
|
|
238
|
+
|
|
239
|
+
if existing is not None and existing.git_head == git_head and git_head:
|
|
240
|
+
# Same commit — refresh analysis sections, preserve api_surface
|
|
241
|
+
compact_data = core_dict.get("_compact") or {}
|
|
242
|
+
agent_data = core_dict.get("_agent") or {}
|
|
243
|
+
agent_index = {k: v for k, v in agent_data.items() if k != "file_relevance"}
|
|
244
|
+
|
|
245
|
+
updated = RepositoryIntelligenceSnapshot(
|
|
246
|
+
repo_id=existing.repo_id,
|
|
247
|
+
created_at=existing.created_at,
|
|
248
|
+
last_updated_at=_now_iso(),
|
|
249
|
+
git_head=git_head,
|
|
250
|
+
version=RIS_SCHEMA_VERSION,
|
|
251
|
+
structural_map=_extract_structural_map(agent_data) if agent_data else existing.structural_map,
|
|
252
|
+
api_surface=existing.api_surface, # preserve explicit endpoint data
|
|
253
|
+
dependency_graph=_extract_dependency_graph(compact_data, agent_data) if compact_data else existing.dependency_graph,
|
|
254
|
+
compact_summary=compact_data if compact_data else existing.compact_summary,
|
|
255
|
+
agent_index=agent_index if agent_index else existing.agent_index,
|
|
256
|
+
git_context_snapshot=_extract_git_context_snapshot(agent_data) if agent_data else existing.git_context_snapshot,
|
|
257
|
+
metadata={
|
|
258
|
+
"snapshot_source": "existing_snapshot_system",
|
|
259
|
+
"confidence": float(1.0 if agent_data else (0.5 if compact_data else 0.0)),
|
|
260
|
+
"partial": not bool(agent_data),
|
|
261
|
+
},
|
|
262
|
+
)
|
|
263
|
+
save_ris(repo_root, updated)
|
|
264
|
+
else:
|
|
265
|
+
# New commit or first build — rebuild all sections (preserve api_surface if available)
|
|
266
|
+
existing_api = existing.api_surface if existing is not None else None
|
|
267
|
+
ris = _build_from_core(repo_root, core_dict, git_head, existing_api_surface=existing_api)
|
|
268
|
+
if existing is not None:
|
|
269
|
+
# Preserve creation timestamp
|
|
270
|
+
ris = RepositoryIntelligenceSnapshot(
|
|
271
|
+
**{**asdict(ris), "created_at": existing.created_at}
|
|
272
|
+
)
|
|
273
|
+
save_ris(repo_root, ris)
|
|
274
|
+
except Exception:
|
|
275
|
+
pass
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def update_ris_api_surface(repo_root: Path, endpoints_data: dict) -> None:
|
|
279
|
+
"""Update the api_surface section from an ``endpoints`` command output.
|
|
280
|
+
|
|
281
|
+
Called from endpoints_cmd after _extract_java_endpoints(). Never raises.
|
|
282
|
+
"""
|
|
283
|
+
try:
|
|
284
|
+
if not isinstance(endpoints_data, dict):
|
|
285
|
+
return
|
|
286
|
+
endpoints = endpoints_data.get("endpoints") or []
|
|
287
|
+
existing = load_ris(repo_root)
|
|
288
|
+
if existing is None:
|
|
289
|
+
# No RIS yet — create a minimal stub so api_surface is persisted
|
|
290
|
+
from sourcecode.cache import repo_id as _repo_id_fn
|
|
291
|
+
now = _now_iso()
|
|
292
|
+
existing = RepositoryIntelligenceSnapshot(
|
|
293
|
+
repo_id=_repo_id_fn(repo_root),
|
|
294
|
+
created_at=now,
|
|
295
|
+
last_updated_at=now,
|
|
296
|
+
git_head="",
|
|
297
|
+
version=RIS_SCHEMA_VERSION,
|
|
298
|
+
structural_map={},
|
|
299
|
+
api_surface={},
|
|
300
|
+
dependency_graph={},
|
|
301
|
+
compact_summary={},
|
|
302
|
+
agent_index={},
|
|
303
|
+
git_context_snapshot={},
|
|
304
|
+
metadata={"snapshot_source": "existing_snapshot_system", "confidence": 0.0, "partial": True},
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Build controllers_index from endpoint controller fields
|
|
308
|
+
ctrl_set: set[str] = set()
|
|
309
|
+
for ep in endpoints:
|
|
310
|
+
if isinstance(ep, dict):
|
|
311
|
+
ctrl = ep.get("controller") or ep.get("controller_class") or ""
|
|
312
|
+
if ctrl:
|
|
313
|
+
ctrl_set.add(ctrl)
|
|
314
|
+
|
|
315
|
+
updated_api = {
|
|
316
|
+
"endpoints": endpoints,
|
|
317
|
+
"controllers_index": sorted(ctrl_set),
|
|
318
|
+
}
|
|
319
|
+
updated = RepositoryIntelligenceSnapshot(
|
|
320
|
+
**{**asdict(existing), "api_surface": updated_api, "last_updated_at": _now_iso()}
|
|
321
|
+
)
|
|
322
|
+
save_ris(repo_root, updated)
|
|
323
|
+
except Exception:
|
|
324
|
+
pass
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
# ---------------------------------------------------------------------------
|
|
328
|
+
# Cold-start context
|
|
329
|
+
# ---------------------------------------------------------------------------
|
|
330
|
+
|
|
331
|
+
def _current_git_head(repo_root: Path) -> str:
|
|
332
|
+
"""Return current HEAD SHA. Returns '' on any error or non-git directory."""
|
|
333
|
+
try:
|
|
334
|
+
result = subprocess.run(
|
|
335
|
+
["git", "-C", str(repo_root), "rev-parse", "HEAD"],
|
|
336
|
+
capture_output=True,
|
|
337
|
+
text=True,
|
|
338
|
+
timeout=2,
|
|
339
|
+
)
|
|
340
|
+
if result.returncode == 0:
|
|
341
|
+
return result.stdout.strip()
|
|
342
|
+
except Exception:
|
|
343
|
+
pass
|
|
344
|
+
return ""
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def get_cold_start_context(repo_root: Path) -> dict:
|
|
348
|
+
"""Return a lightweight bootstrap object from the persisted RIS.
|
|
349
|
+
|
|
350
|
+
Never raises. Returns ``{"status": "no_ris"}`` when no RIS exists.
|
|
351
|
+
"""
|
|
352
|
+
try:
|
|
353
|
+
ris = load_ris(repo_root)
|
|
354
|
+
if ris is None:
|
|
355
|
+
return {"status": "no_ris"}
|
|
356
|
+
|
|
357
|
+
current_head = _current_git_head(repo_root)
|
|
358
|
+
stale = bool(current_head and ris.git_head and current_head != ris.git_head)
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
"status": "cold_start_stale" if stale else "cold_start_ready",
|
|
362
|
+
"repo_id": ris.repo_id,
|
|
363
|
+
"git_head": ris.git_head,
|
|
364
|
+
"stale": stale,
|
|
365
|
+
"last_updated_at": ris.last_updated_at,
|
|
366
|
+
"summary": ris.compact_summary,
|
|
367
|
+
"entrypoints": ris.structural_map.get("entrypoints", []),
|
|
368
|
+
"endpoints": ris.api_surface.get("endpoints", []),
|
|
369
|
+
"hotspots": ris.git_context_snapshot.get("hotspots", []),
|
|
370
|
+
}
|
|
371
|
+
except Exception:
|
|
372
|
+
return {"status": "no_ris"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=tfPDIqlBjWLMfih5Bz8aKlWeo9EJQJnFkI8wjOakF7w,103
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=XffluXKzJUXrMtjEiAOnSNPZnztdIcts17T9ouHeID0,10521
|
|
3
3
|
sourcecode/architecture_analyzer.py,sha256=qh749a7ykPtGmQI1MR9y6j8TtL_jBdVYFx9YRsLqOMw,44121
|
|
4
4
|
sourcecode/architecture_summary.py,sha256=z34_6v7cSwy98cof2UVciGho7SCrZ93tiqMmq5WNzRQ,20405
|
|
@@ -6,7 +6,7 @@ sourcecode/ast_extractor.py,sha256=_btmeOJIe3t-NicF94D5ZAesa2YIJ0_QNExGnbHxGFE,5
|
|
|
6
6
|
sourcecode/cache.py,sha256=pBrPdpPrOgpXHHQO670U3aUfVf5N3A3obsTKgiZtN4I,23030
|
|
7
7
|
sourcecode/canonical_ir.py,sha256=_HM3AUmKSdna9u4dCoU6rpgSA6HdF8gzOKZykIUCNGY,23277
|
|
8
8
|
sourcecode/classifier.py,sha256=2lYoSH3vOTkXZYPU7Go2WIet1-IuNzTWVhc-ULnXtgw,8024
|
|
9
|
-
sourcecode/cli.py,sha256=
|
|
9
|
+
sourcecode/cli.py,sha256=l3PJ9pK6JQ_JUxYSD3xnpvCIvR9kvOvbEEvHvqfij9A,165743
|
|
10
10
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
11
11
|
sourcecode/confidence_analyzer.py,sha256=_jckZSxksV-OU38vbkxfVNBnWCtlCq8Vwfg23x1uspA,19054
|
|
12
12
|
sourcecode/context_scorer.py,sha256=QpChSpsmaAYz91rXA4Ue5xzQmNz_ZboZN09YOHScq1U,14679
|
|
@@ -35,6 +35,7 @@ sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
|
|
|
35
35
|
sourcecode/relevance_scorer.py,sha256=MYF4FFkveAQps9SmTeTlh6ODiBz2F--_hWNeHMLtUHQ,8405
|
|
36
36
|
sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
|
|
37
37
|
sourcecode/repository_ir.py,sha256=-NjBQUT7zyya4ng8Hq0-ChoiHZkUif9lr-Q878gmj8M,153163
|
|
38
|
+
sourcecode/ris.py,sha256=iy1O04SiQ3cFIK5Qn8iHt3fZTiXIgbXvWkjbBG0fTBo,14000
|
|
38
39
|
sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
|
|
39
40
|
sourcecode/scanner.py,sha256=WdOQ78mMzjR1NjmKTlbxdgwinnCTfAhxCVLBEFQiFHU,8899
|
|
40
41
|
sourcecode/schema.py,sha256=aHNXDf8LGyUC8ZDE_VS9kiskC2-Oswhi_WnpdGy6HDw,24897
|
|
@@ -66,7 +67,7 @@ sourcecode/detectors/terraform.py,sha256=cxORPR_zVLOJpHlh4e9JnFpkQsn_UnqMMom5yG6
|
|
|
66
67
|
sourcecode/detectors/tooling.py,sha256=8CKbtxwQoABP-WyBRNmdAmHDOvAH57AR1cF4UKuWEdQ,2074
|
|
67
68
|
sourcecode/mcp/__init__.py,sha256=XU4HfRGbdid8wdUA0x_4f7uKZD1z3mv_XUY_WU_T9Mw,179
|
|
68
69
|
sourcecode/mcp/runner.py,sha256=YSw2DXEICau6mCBr3Gfia3D_tKxMbRvIIXEh4cHC1SY,1390
|
|
69
|
-
sourcecode/mcp/server.py,sha256=
|
|
70
|
+
sourcecode/mcp/server.py,sha256=jUCDNjJKUIhse52M8-Lp63CDxlm21xOYUOVlv3z6_uk,25556
|
|
70
71
|
sourcecode/mcp/onboarding/__init__.py,sha256=sj2PWqEBmMc4zBNkomg89WtL0M6S7A9yb7_wAuSWNP4,66
|
|
71
72
|
sourcecode/mcp/onboarding/applier.py,sha256=B9CneieWTpaDSDIyW3S5nrlRlBpvfqUcgi93-mm_ApQ,2135
|
|
72
73
|
sourcecode/mcp/onboarding/backup.py,sha256=ihqGOR8QTX8HASRSEDyfFyXr5bkXrygPHamv4p9KTmk,1452
|
|
@@ -78,8 +79,8 @@ sourcecode/telemetry/consent.py,sha256=wLMvGNJeSSyZoNkQXpoUioY6mMv4Qdvuw7S9jAEWn
|
|
|
78
79
|
sourcecode/telemetry/events.py,sha256=oEvvulfsv5GIDWG2174gSS6tNB95w38AIYiYeifGKlE,2294
|
|
79
80
|
sourcecode/telemetry/filters.py,sha256=Asa71oRl7q3Wt_FMwuufIZJFzSYdgRNKS8LHCIyFeYE,4805
|
|
80
81
|
sourcecode/telemetry/transport.py,sha256=KJeIPCPWMdmbCP3ySGs2iUlia34U6vWne2dZsUezesw,1560
|
|
81
|
-
sourcecode-1.32.
|
|
82
|
-
sourcecode-1.32.
|
|
83
|
-
sourcecode-1.32.
|
|
84
|
-
sourcecode-1.32.
|
|
85
|
-
sourcecode-1.32.
|
|
82
|
+
sourcecode-1.32.2.dist-info/METADATA,sha256=PwMFojaNy2nH80htoS7Q_-p-AQ2GSwiTr49Mv8EGXJE,31100
|
|
83
|
+
sourcecode-1.32.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
84
|
+
sourcecode-1.32.2.dist-info/entry_points.txt,sha256=ex3F9rmbXeyDIoFQHtkEqTsKSaJow8F0LrVu8XfIktQ,57
|
|
85
|
+
sourcecode-1.32.2.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
86
|
+
sourcecode-1.32.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|