sourcecode 1.33.1__tar.gz → 1.33.3__tar.gz
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-1.33.1 → sourcecode-1.33.3}/PKG-INFO +3 -3
- {sourcecode-1.33.1 → sourcecode-1.33.3}/README.md +2 -2
- {sourcecode-1.33.1 → sourcecode-1.33.3}/pyproject.toml +1 -1
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/cache.py +71 -4
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/cli.py +155 -6
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/mcp/server.py +61 -6
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/ris.py +40 -9
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/serializer.py +7 -7
- {sourcecode-1.33.1 → sourcecode-1.33.3}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/.gitignore +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/.ruff.toml +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/CHANGELOG.md +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/CONTRIBUTING.md +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/LICENSE +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/SECURITY.md +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/raw +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/cache.tmp_new +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/license.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.33.1 → sourcecode-1.33.3}/src/sourcecode/workspace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 1.33.
|
|
3
|
+
Version: 1.33.3
|
|
4
4
|
Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Keywords: agents,ai,codebase,context,developer-tools,llm
|
|
@@ -39,7 +39,7 @@ Description-Content-Type: text/markdown
|
|
|
39
39
|
|
|
40
40
|
**Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
|
|
41
41
|
|
|
42
|
-

|
|
43
43
|

|
|
44
44
|
|
|
45
45
|
---
|
|
@@ -113,7 +113,7 @@ pipx install sourcecode
|
|
|
113
113
|
|
|
114
114
|
```bash
|
|
115
115
|
sourcecode version
|
|
116
|
-
# sourcecode 1.33.
|
|
116
|
+
# sourcecode 1.33.3
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Persistent structural context and ultra-fast repeated analysis for AI coding agents.**
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|
|
|
8
8
|
---
|
|
@@ -76,7 +76,7 @@ pipx install sourcecode
|
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
78
|
sourcecode version
|
|
79
|
-
# sourcecode 1.33.
|
|
79
|
+
# sourcecode 1.33.3
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
---
|
|
@@ -57,6 +57,7 @@ import hashlib
|
|
|
57
57
|
import json
|
|
58
58
|
import os
|
|
59
59
|
import re
|
|
60
|
+
import subprocess
|
|
60
61
|
from datetime import datetime, timezone
|
|
61
62
|
from pathlib import Path
|
|
62
63
|
from typing import Any, Optional
|
|
@@ -110,6 +111,24 @@ _CORE_RE = re.compile(r"^core-([0-9a-f]+)-[0-9a-f]+\.json\.gz$")
|
|
|
110
111
|
_VIEW_RE = re.compile(r"^view-([0-9a-f]{16})-[0-9a-f]+\.json\.gz$")
|
|
111
112
|
|
|
112
113
|
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
# Internal helpers
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
def _get_git_head(repo_root: Path) -> str:
|
|
119
|
+
"""Return short git HEAD SHA, or '' on any error."""
|
|
120
|
+
try:
|
|
121
|
+
r = subprocess.run(
|
|
122
|
+
["git", "-C", str(repo_root), "rev-parse", "--short", "HEAD"],
|
|
123
|
+
capture_output=True, text=True, timeout=2,
|
|
124
|
+
)
|
|
125
|
+
if r.returncode == 0:
|
|
126
|
+
return r.stdout.strip()
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
return ""
|
|
130
|
+
|
|
131
|
+
|
|
113
132
|
# ---------------------------------------------------------------------------
|
|
114
133
|
# Public API — location helpers
|
|
115
134
|
# ---------------------------------------------------------------------------
|
|
@@ -138,15 +157,51 @@ def cache_dir(repo_root: Path) -> Path:
|
|
|
138
157
|
def status(repo_root: Path) -> dict[str, Any]:
|
|
139
158
|
"""Return a stats dict describing the current cache state for *repo_root*.
|
|
140
159
|
|
|
141
|
-
Keys: ``cache_dir``, ``cores``, ``
|
|
142
|
-
``total_size_bytes``, ``total_size_mb
|
|
160
|
+
Keys: ``cache_dir``, ``cores``, ``views``, ``cas_blobs``,
|
|
161
|
+
``total_size_bytes``, ``total_size_mb``, ``ris_exists``, ``ris_git_head``,
|
|
162
|
+
``ris_last_updated_at``, ``ris_is_stale``, ``current_git_head``.
|
|
163
|
+
|
|
164
|
+
Note: ``snapshots`` is a legacy v1 field — always 0 in v2 (kept for
|
|
165
|
+
backward compatibility; v2 writes ``core-*`` and ``view-*`` files only).
|
|
143
166
|
"""
|
|
144
167
|
cache_d = cache_dir(repo_root)
|
|
168
|
+
current_head = _get_git_head(repo_root)
|
|
169
|
+
|
|
170
|
+
# RIS metadata (lazy import to avoid circular dependency)
|
|
171
|
+
ris_fields: dict[str, Any]
|
|
172
|
+
try:
|
|
173
|
+
from sourcecode.ris import load_ris as _load_ris # noqa: PLC0415
|
|
174
|
+
_ris = _load_ris(repo_root)
|
|
175
|
+
if _ris is not None:
|
|
176
|
+
_ris_stale = bool(current_head and _ris.git_head and current_head != _ris.git_head)
|
|
177
|
+
ris_fields = {
|
|
178
|
+
"ris_exists": True,
|
|
179
|
+
"ris_git_head": _ris.git_head,
|
|
180
|
+
"ris_last_updated_at": _ris.last_updated_at,
|
|
181
|
+
"ris_is_stale": _ris_stale,
|
|
182
|
+
}
|
|
183
|
+
else:
|
|
184
|
+
ris_fields = {
|
|
185
|
+
"ris_exists": False,
|
|
186
|
+
"ris_git_head": None,
|
|
187
|
+
"ris_last_updated_at": None,
|
|
188
|
+
"ris_is_stale": False,
|
|
189
|
+
}
|
|
190
|
+
except Exception:
|
|
191
|
+
ris_fields = {
|
|
192
|
+
"ris_exists": False,
|
|
193
|
+
"ris_git_head": None,
|
|
194
|
+
"ris_last_updated_at": None,
|
|
195
|
+
"ris_is_stale": False,
|
|
196
|
+
}
|
|
197
|
+
|
|
145
198
|
if not cache_d.exists():
|
|
146
199
|
return {
|
|
147
200
|
"cache_dir": str(cache_d),
|
|
148
201
|
"cores": 0, "snapshots": 0, "views": 0, "cas_blobs": 0,
|
|
149
202
|
"total_size_bytes": 0, "total_size_mb": 0.0,
|
|
203
|
+
"current_git_head": current_head,
|
|
204
|
+
**ris_fields,
|
|
150
205
|
}
|
|
151
206
|
cores = list(cache_d.glob("core-*.json.gz"))
|
|
152
207
|
snapshots = list(cache_d.glob("snapshot-*.json.gz"))
|
|
@@ -162,11 +217,18 @@ def status(repo_root: Path) -> dict[str, Any]:
|
|
|
162
217
|
"cas_blobs": len(cas_blobs),
|
|
163
218
|
"total_size_bytes": total_bytes,
|
|
164
219
|
"total_size_mb": round(total_bytes / (1024 * 1024), 2),
|
|
220
|
+
"current_git_head": current_head,
|
|
221
|
+
**ris_fields,
|
|
165
222
|
}
|
|
166
223
|
|
|
167
224
|
|
|
168
|
-
def clear(repo_root: Path) -> int:
|
|
169
|
-
"""Delete
|
|
225
|
+
def clear(repo_root: Path, *, clear_ris: bool = False) -> int:
|
|
226
|
+
"""Delete cache files for *repo_root*. Returns the number of files removed.
|
|
227
|
+
|
|
228
|
+
By default, RIS (``ris.json.gz``) is preserved across clears — it is the
|
|
229
|
+
persistent structural index used by cold-start bootstrapping. Pass
|
|
230
|
+
``clear_ris=True`` (CLI: ``--include-ris``) to also delete the RIS.
|
|
231
|
+
"""
|
|
170
232
|
cache_d = cache_dir(repo_root)
|
|
171
233
|
if not cache_d.exists():
|
|
172
234
|
return 0
|
|
@@ -180,6 +242,11 @@ def clear(repo_root: Path) -> int:
|
|
|
180
242
|
for f in cas_d.glob("*.gz"):
|
|
181
243
|
_safe_unlink(f)
|
|
182
244
|
removed += 1
|
|
245
|
+
if clear_ris:
|
|
246
|
+
ris_file = cache_d / "ris.json.gz"
|
|
247
|
+
if ris_file.exists():
|
|
248
|
+
_safe_unlink(ris_file)
|
|
249
|
+
removed += 1
|
|
183
250
|
return removed
|
|
184
251
|
|
|
185
252
|
|
|
@@ -1058,6 +1058,22 @@ def main(
|
|
|
1058
1058
|
code_notes = True
|
|
1059
1059
|
architecture = True
|
|
1060
1060
|
|
|
1061
|
+
def _inject_cache_meta(raw: str, meta: dict) -> str:
|
|
1062
|
+
"""Inject ``_cache`` provenance block into a JSON dict string.
|
|
1063
|
+
|
|
1064
|
+
Parses *raw* as JSON, adds ``_cache`` key, re-serialises. Returns *raw*
|
|
1065
|
+
unchanged on any parse failure or non-dict JSON (YAML pass-through, etc.).
|
|
1066
|
+
"""
|
|
1067
|
+
try:
|
|
1068
|
+
import json as _jm
|
|
1069
|
+
obj = _jm.loads(raw)
|
|
1070
|
+
if isinstance(obj, dict):
|
|
1071
|
+
obj["_cache"] = meta
|
|
1072
|
+
return _jm.dumps(obj, indent=2, ensure_ascii=False)
|
|
1073
|
+
except Exception:
|
|
1074
|
+
pass
|
|
1075
|
+
return raw
|
|
1076
|
+
|
|
1061
1077
|
# ── Two-layer cache ────────────────────────────────────────────────────────
|
|
1062
1078
|
# L1 (core): (repo, commit, analysis_flags) → pre-computed view data dict
|
|
1063
1079
|
# key = core-<git_sha>-<analysis_hash>.json.gz
|
|
@@ -1202,6 +1218,23 @@ def main(
|
|
|
1202
1218
|
|
|
1203
1219
|
if _cache_hit_content is not None:
|
|
1204
1220
|
from sourcecode.serializer import write_output
|
|
1221
|
+
if format == "json":
|
|
1222
|
+
try:
|
|
1223
|
+
from sourcecode.ris import _has_uncommitted_changes as _huc
|
|
1224
|
+
_uncommitted = _huc(target)
|
|
1225
|
+
except Exception:
|
|
1226
|
+
_uncommitted = False
|
|
1227
|
+
_hit_source = "L2_view" if (_view_key and _core_hash) else "L1_core"
|
|
1228
|
+
_data_scope = "COMPACT" if compact else ("AGENT" if agent else "FULL")
|
|
1229
|
+
_cache_hit_content = _inject_cache_meta(_cache_hit_content, {
|
|
1230
|
+
"cache_source": _hit_source,
|
|
1231
|
+
"git_head_at_generation": _git_sha,
|
|
1232
|
+
"current_git_head": _git_sha,
|
|
1233
|
+
"is_stale": False,
|
|
1234
|
+
"has_uncommitted_changes": _uncommitted,
|
|
1235
|
+
"generated_at": None,
|
|
1236
|
+
"data_scope": _data_scope,
|
|
1237
|
+
})
|
|
1205
1238
|
write_output(_cache_hit_content, output=output)
|
|
1206
1239
|
if copy and not output:
|
|
1207
1240
|
_copy_to_clipboard(_cache_hit_content)
|
|
@@ -2082,6 +2115,23 @@ def main(
|
|
|
2082
2115
|
|
|
2083
2116
|
# 6. Write output (CLI-04)
|
|
2084
2117
|
_progress.finish()
|
|
2118
|
+
if format == "json":
|
|
2119
|
+
try:
|
|
2120
|
+
from sourcecode.ris import _has_uncommitted_changes as _huc_fresh
|
|
2121
|
+
_uncommitted_fresh = _huc_fresh(target)
|
|
2122
|
+
except Exception:
|
|
2123
|
+
_uncommitted_fresh = False
|
|
2124
|
+
import datetime as _dt
|
|
2125
|
+
_data_scope_fresh = "COMPACT" if compact else ("AGENT" if agent else "FULL")
|
|
2126
|
+
content = _inject_cache_meta(content, {
|
|
2127
|
+
"cache_source": "fresh",
|
|
2128
|
+
"git_head_at_generation": _git_sha,
|
|
2129
|
+
"current_git_head": _git_sha,
|
|
2130
|
+
"is_stale": False,
|
|
2131
|
+
"has_uncommitted_changes": _uncommitted_fresh,
|
|
2132
|
+
"generated_at": _dt.datetime.now(_dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
2133
|
+
"data_scope": _data_scope_fresh,
|
|
2134
|
+
})
|
|
2085
2135
|
write_output(content, output=output)
|
|
2086
2136
|
|
|
2087
2137
|
# Persist to two-layer cache (git SHA unchanged → re-use on next run).
|
|
@@ -4231,23 +4281,36 @@ def cache_status_cmd(
|
|
|
4231
4281
|
else:
|
|
4232
4282
|
typer.echo(f"Cache dir: {stats['cache_dir']}")
|
|
4233
4283
|
typer.echo(f"Cores: {stats['cores']}")
|
|
4234
|
-
typer.echo(f"Snapshots: {stats['snapshots']}")
|
|
4235
4284
|
typer.echo(f"Views: {stats['views']}")
|
|
4236
4285
|
typer.echo(f"CAS blobs: {stats['cas_blobs']}")
|
|
4237
4286
|
typer.echo(f"Total size: {stats['total_size_mb']} MB")
|
|
4287
|
+
# RIS section
|
|
4288
|
+
if stats.get("ris_exists"):
|
|
4289
|
+
_stale_tag = " [STALE]" if stats.get("ris_is_stale") else ""
|
|
4290
|
+
typer.echo(f"RIS: exists HEAD={stats.get('ris_git_head', '?')}{_stale_tag} updated={stats.get('ris_last_updated_at', '?')}")
|
|
4291
|
+
else:
|
|
4292
|
+
typer.echo("RIS: none (run analysis to build)")
|
|
4293
|
+
if stats.get("current_git_head"):
|
|
4294
|
+
typer.echo(f"Current HEAD:{stats['current_git_head']}")
|
|
4238
4295
|
|
|
4239
4296
|
|
|
4240
4297
|
@cache_app.command("clear")
|
|
4241
4298
|
def cache_clear_cmd(
|
|
4242
4299
|
path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
|
|
4243
4300
|
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
|
|
4301
|
+
include_ris: bool = typer.Option(False, "--include-ris", help="Also delete the RIS snapshot (ris.json.gz). By default, RIS is preserved across clears."),
|
|
4244
4302
|
) -> None:
|
|
4245
|
-
"""Delete
|
|
4303
|
+
"""Delete cached snapshots for a repository.
|
|
4304
|
+
|
|
4305
|
+
By default, RIS (ris.json.gz) is preserved — it is the persistent structural
|
|
4306
|
+
index used for cold-start bootstrapping. Use --include-ris to also clear it.
|
|
4307
|
+
"""
|
|
4246
4308
|
from sourcecode import cache as _cm
|
|
4247
4309
|
target = Path(path).resolve()
|
|
4248
4310
|
if not yes:
|
|
4249
|
-
|
|
4250
|
-
|
|
4311
|
+
_ris_note = " (including RIS)" if include_ris else " (RIS preserved — use --include-ris to also clear it)"
|
|
4312
|
+
typer.confirm(f"Delete all cache files for {target}{_ris_note}?", abort=True)
|
|
4313
|
+
removed = _cm.clear(target, clear_ris=include_ris)
|
|
4251
4314
|
typer.echo(f"Removed {removed} file(s).")
|
|
4252
4315
|
|
|
4253
4316
|
|
|
@@ -4257,7 +4320,11 @@ def cache_warm_cmd(
|
|
|
4257
4320
|
compact: bool = typer.Option(True, "--compact/--no-compact", help="Warm compact view (default: on)."),
|
|
4258
4321
|
agent: bool = typer.Option(False, "--agent", help="Also warm agent view."),
|
|
4259
4322
|
) -> None:
|
|
4260
|
-
"""Pre-populate the cache by running a fresh analysis.
|
|
4323
|
+
"""Pre-populate the cache by running a fresh analysis.
|
|
4324
|
+
|
|
4325
|
+
Runs a full analysis to populate L1/L2 caches and rebuild the RIS
|
|
4326
|
+
(Repository Intelligence Snapshot). Useful after a merge/pull in CI.
|
|
4327
|
+
"""
|
|
4261
4328
|
import shutil as _shutil
|
|
4262
4329
|
import subprocess as _sub
|
|
4263
4330
|
import sys as _sys
|
|
@@ -4271,7 +4338,7 @@ def cache_warm_cmd(
|
|
|
4271
4338
|
cmd.append("--agent")
|
|
4272
4339
|
result = _sub.run(cmd, capture_output=True, text=True)
|
|
4273
4340
|
if result.returncode == 0:
|
|
4274
|
-
typer.echo("Cache warmed.", err=True)
|
|
4341
|
+
typer.echo("Cache warmed (L1/L2 + RIS rebuilt).", err=True)
|
|
4275
4342
|
else:
|
|
4276
4343
|
typer.echo(f"Warm failed (exit {result.returncode}).", err=True)
|
|
4277
4344
|
if result.stderr:
|
|
@@ -4279,6 +4346,88 @@ def cache_warm_cmd(
|
|
|
4279
4346
|
raise typer.Exit(code=result.returncode)
|
|
4280
4347
|
|
|
4281
4348
|
|
|
4349
|
+
@cache_app.command("freshness")
|
|
4350
|
+
def cache_freshness_cmd(
|
|
4351
|
+
path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
|
|
4352
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON."),
|
|
4353
|
+
) -> None:
|
|
4354
|
+
"""Report RIS freshness relative to the current git HEAD.
|
|
4355
|
+
|
|
4356
|
+
Answers: is the cached snapshot current? How many commits behind is it?
|
|
4357
|
+
|
|
4358
|
+
\b
|
|
4359
|
+
Output fields:
|
|
4360
|
+
fresh — True when RIS HEAD matches current HEAD and no uncommitted changes
|
|
4361
|
+
current_git_head — Current repo HEAD (short SHA)
|
|
4362
|
+
ris_git_head — HEAD stored in RIS when it was last built
|
|
4363
|
+
delta_commits — Number of commits between ris_git_head and HEAD (0 = in sync)
|
|
4364
|
+
has_uncommitted_changes — Working tree has staged/unstaged changes
|
|
4365
|
+
ris_exists — False when no RIS has been built yet
|
|
4366
|
+
ris_last_updated_at — ISO-8601 timestamp of last RIS write
|
|
4367
|
+
"""
|
|
4368
|
+
import json as _json
|
|
4369
|
+
import subprocess as _sub
|
|
4370
|
+
from sourcecode import cache as _cm
|
|
4371
|
+
from sourcecode.ris import _has_uncommitted_changes as _huc
|
|
4372
|
+
from sourcecode.ris import load_ris as _lris
|
|
4373
|
+
|
|
4374
|
+
target = Path(path).resolve()
|
|
4375
|
+
current_head = _cm._get_git_head(target)
|
|
4376
|
+
ris = _lris(target)
|
|
4377
|
+
|
|
4378
|
+
if ris is None:
|
|
4379
|
+
result: dict = {
|
|
4380
|
+
"fresh": False,
|
|
4381
|
+
"ris_exists": False,
|
|
4382
|
+
"current_git_head": current_head,
|
|
4383
|
+
"ris_git_head": None,
|
|
4384
|
+
"delta_commits": None,
|
|
4385
|
+
"has_uncommitted_changes": _huc(target),
|
|
4386
|
+
"ris_last_updated_at": None,
|
|
4387
|
+
}
|
|
4388
|
+
else:
|
|
4389
|
+
ris_head = ris.git_head
|
|
4390
|
+
head_matches = bool(current_head and ris_head and current_head == ris_head)
|
|
4391
|
+
uncommitted = _huc(target)
|
|
4392
|
+
|
|
4393
|
+
# Count commits between ris_head and current HEAD
|
|
4394
|
+
delta = None
|
|
4395
|
+
if ris_head and current_head and ris_head != current_head:
|
|
4396
|
+
try:
|
|
4397
|
+
_r = _sub.run(
|
|
4398
|
+
["git", "-C", str(target), "rev-list", "--count", f"{ris_head}..HEAD"],
|
|
4399
|
+
capture_output=True, text=True, timeout=5,
|
|
4400
|
+
)
|
|
4401
|
+
if _r.returncode == 0:
|
|
4402
|
+
delta = int(_r.stdout.strip())
|
|
4403
|
+
except Exception:
|
|
4404
|
+
pass
|
|
4405
|
+
elif head_matches:
|
|
4406
|
+
delta = 0
|
|
4407
|
+
|
|
4408
|
+
result = {
|
|
4409
|
+
"fresh": head_matches and not uncommitted,
|
|
4410
|
+
"ris_exists": True,
|
|
4411
|
+
"current_git_head": current_head,
|
|
4412
|
+
"ris_git_head": ris_head,
|
|
4413
|
+
"delta_commits": delta,
|
|
4414
|
+
"has_uncommitted_changes": uncommitted,
|
|
4415
|
+
"ris_last_updated_at": ris.last_updated_at,
|
|
4416
|
+
}
|
|
4417
|
+
|
|
4418
|
+
if json_output:
|
|
4419
|
+
typer.echo(_json.dumps(result, indent=2, ensure_ascii=False))
|
|
4420
|
+
else:
|
|
4421
|
+
_fresh_tag = "FRESH" if result["fresh"] else "STALE"
|
|
4422
|
+
typer.echo(f"Status: {_fresh_tag}")
|
|
4423
|
+
typer.echo(f"Current HEAD: {result['current_git_head'] or '(unknown)'}")
|
|
4424
|
+
typer.echo(f"RIS HEAD: {result.get('ris_git_head') or '(none)'}")
|
|
4425
|
+
if result.get("delta_commits") is not None:
|
|
4426
|
+
typer.echo(f"Delta: {result['delta_commits']} commit(s) behind")
|
|
4427
|
+
typer.echo(f"Uncommitted: {result['has_uncommitted_changes']}")
|
|
4428
|
+
typer.echo(f"RIS updated: {result.get('ris_last_updated_at') or 'never'}")
|
|
4429
|
+
|
|
4430
|
+
|
|
4282
4431
|
# ── Entry point ───────────────────────────────────────────────────────────────
|
|
4283
4432
|
|
|
4284
4433
|
def main_entry() -> None:
|
|
@@ -215,7 +215,10 @@ def get_agent_context(repo_path: str = ".", git_context: bool = False) -> dict:
|
|
|
215
215
|
|
|
216
216
|
@mcp.tool()
|
|
217
217
|
def get_endpoints(repo_path: str = ".") -> dict:
|
|
218
|
-
"""REST API endpoint surface extraction from Java source files.
|
|
218
|
+
"""REST API endpoint surface extraction from Java source files. JAVA ONLY.
|
|
219
|
+
|
|
220
|
+
Do NOT call this on non-Java repositories — it will return empty results.
|
|
221
|
+
Use get_compact_context or get_agent_context for non-Java repos.
|
|
219
222
|
|
|
220
223
|
Maps to: sourcecode endpoints <repo_path>
|
|
221
224
|
Returns: endpoints list with method, path, controller, handler fields;
|
|
@@ -230,7 +233,7 @@ def get_endpoints(repo_path: str = ".") -> dict:
|
|
|
230
233
|
Supports Spring MVC (@GetMapping etc.) and JAX-RS (@GET/@POST etc.).
|
|
231
234
|
Security annotations detected: @RolesAllowed, @PermitAll, @DenyAll,
|
|
232
235
|
@Authenticated, @PreAuthorize, @Secured, @SecurityRequirement, @M3FiltroSeguridad.
|
|
233
|
-
repo_path: absolute path to the repository (default: current working directory).
|
|
236
|
+
repo_path: absolute path to the Java repository (default: current working directory).
|
|
234
237
|
"""
|
|
235
238
|
_raw = repo_path
|
|
236
239
|
try:
|
|
@@ -275,25 +278,43 @@ def get_module_context(repo_path: str = ".", module: str = "") -> dict:
|
|
|
275
278
|
)
|
|
276
279
|
|
|
277
280
|
|
|
281
|
+
def _auto_since(repo_path: str) -> str:
|
|
282
|
+
"""Detect best merge-base for delta: origin/main > origin/master > HEAD~1."""
|
|
283
|
+
import subprocess as _sp
|
|
284
|
+
for base in ("origin/main", "origin/master"):
|
|
285
|
+
try:
|
|
286
|
+
r = _sp.run(
|
|
287
|
+
["git", "-C", repo_path, "merge-base", "HEAD", base],
|
|
288
|
+
capture_output=True, text=True, timeout=5,
|
|
289
|
+
)
|
|
290
|
+
if r.returncode == 0 and r.stdout.strip():
|
|
291
|
+
return r.stdout.strip()
|
|
292
|
+
except Exception:
|
|
293
|
+
pass
|
|
294
|
+
return "HEAD~1"
|
|
295
|
+
|
|
296
|
+
|
|
278
297
|
@mcp.tool()
|
|
279
|
-
def get_delta(repo_path: str = ".", since: str = "
|
|
298
|
+
def get_delta(repo_path: str = ".", since: str = "") -> dict:
|
|
280
299
|
"""Incremental context: git-changed files since a reference commit.
|
|
281
300
|
|
|
282
301
|
Maps to: sourcecode prepare-context delta <repo_path> --since <since>
|
|
283
302
|
repo_path: absolute path to the repository (default: current working directory).
|
|
284
303
|
since: git ref to diff against (e.g. HEAD~3, main, origin/main).
|
|
304
|
+
If empty or omitted, auto-detects merge-base with origin/main (or
|
|
305
|
+
origin/master). Falls back to HEAD~1 if no remote branch found.
|
|
306
|
+
Pass "HEAD~1" explicitly to force single-commit diff.
|
|
285
307
|
"""
|
|
286
308
|
_raw = repo_path
|
|
287
309
|
try:
|
|
288
310
|
if not isinstance(repo_path, str):
|
|
289
311
|
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
290
|
-
if not isinstance(since, str) or not since.strip():
|
|
291
|
-
return _err("since must be a non-empty git ref", "INVALID_ARGUMENT")
|
|
292
312
|
repo_path = _normalize_repo_path(repo_path)
|
|
293
313
|
_path_err = _check_repo_path(repo_path)
|
|
294
314
|
if _path_err is not None:
|
|
295
315
|
return _path_err
|
|
296
|
-
|
|
316
|
+
_since = since.strip() if isinstance(since, str) and since.strip() else _auto_since(repo_path)
|
|
317
|
+
return _execute(["prepare-context", "delta", repo_path, "--since", _since])
|
|
297
318
|
except Exception as exc:
|
|
298
319
|
return _err(
|
|
299
320
|
f"Internal error: {type(exc).__name__}: {exc} — repo_path recibido: {_raw}",
|
|
@@ -301,6 +322,40 @@ def get_delta(repo_path: str = ".", since: str = "HEAD~1") -> dict:
|
|
|
301
322
|
)
|
|
302
323
|
|
|
303
324
|
|
|
325
|
+
@mcp.tool()
|
|
326
|
+
def check_freshness(repo_path: str = ".") -> dict:
|
|
327
|
+
"""Report RIS freshness relative to the current git HEAD.
|
|
328
|
+
|
|
329
|
+
Answers instantly: is the cached snapshot current? How many commits behind?
|
|
330
|
+
Use before deciding whether to call get_compact_context for a refresh.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
fresh (bool) — True when RIS HEAD == current HEAD and no uncommitted changes
|
|
334
|
+
current_git_head (str) — Current repo HEAD (short SHA)
|
|
335
|
+
ris_git_head (str|null) — HEAD stored in RIS at last build
|
|
336
|
+
delta_commits (int|null) — Commits between ris_git_head and HEAD (0 = in sync)
|
|
337
|
+
has_uncommitted_changes — Working tree has staged or unstaged changes
|
|
338
|
+
ris_exists (bool) — False when no RIS built yet
|
|
339
|
+
ris_last_updated_at (str) — ISO-8601 timestamp of last RIS write
|
|
340
|
+
|
|
341
|
+
repo_path: absolute path to the repository (default: current working directory).
|
|
342
|
+
"""
|
|
343
|
+
_raw = repo_path
|
|
344
|
+
try:
|
|
345
|
+
if not isinstance(repo_path, str):
|
|
346
|
+
return _err("repo_path must be a string", "INVALID_ARGUMENT")
|
|
347
|
+
repo_path = _normalize_repo_path(repo_path)
|
|
348
|
+
_path_err = _check_repo_path(repo_path)
|
|
349
|
+
if _path_err is not None:
|
|
350
|
+
return _path_err
|
|
351
|
+
return _execute(["cache", "freshness", repo_path, "--json"])
|
|
352
|
+
except Exception as exc:
|
|
353
|
+
return _err(
|
|
354
|
+
f"Internal error: {type(exc).__name__}: {exc} — repo_path: {_raw}",
|
|
355
|
+
"INTERNAL_ERROR",
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
304
359
|
@mcp.tool()
|
|
305
360
|
def get_ir_summary(repo_path: str = ".") -> dict:
|
|
306
361
|
"""Deterministic symbol-level IR summary for Java repositories. Java only.
|
|
@@ -349,6 +349,26 @@ def _current_git_head(repo_root: Path) -> str:
|
|
|
349
349
|
return ""
|
|
350
350
|
|
|
351
351
|
|
|
352
|
+
def _has_uncommitted_changes(repo_root: Path) -> bool:
|
|
353
|
+
"""Return True if working tree has staged or unstaged changes.
|
|
354
|
+
|
|
355
|
+
Uses ``git status --porcelain`` — any non-empty output means the working
|
|
356
|
+
tree diverges from HEAD. Returns False on any error (non-git dirs, etc.).
|
|
357
|
+
"""
|
|
358
|
+
try:
|
|
359
|
+
result = subprocess.run(
|
|
360
|
+
["git", "-C", str(repo_root), "status", "--porcelain"],
|
|
361
|
+
capture_output=True,
|
|
362
|
+
text=True,
|
|
363
|
+
timeout=2,
|
|
364
|
+
)
|
|
365
|
+
if result.returncode == 0:
|
|
366
|
+
return bool(result.stdout.strip())
|
|
367
|
+
except Exception:
|
|
368
|
+
pass
|
|
369
|
+
return False
|
|
370
|
+
|
|
371
|
+
|
|
352
372
|
def get_cold_start_context(repo_root: Path) -> dict:
|
|
353
373
|
"""Return a lightweight bootstrap object from the persisted RIS.
|
|
354
374
|
|
|
@@ -361,28 +381,39 @@ def get_cold_start_context(repo_root: Path) -> dict:
|
|
|
361
381
|
|
|
362
382
|
current_head = _current_git_head(repo_root)
|
|
363
383
|
stale = bool(current_head and ris.git_head and current_head != ris.git_head)
|
|
384
|
+
uncommitted = _has_uncommitted_changes(repo_root)
|
|
364
385
|
|
|
365
386
|
endpoints = ris.api_surface.get("endpoints", [])
|
|
387
|
+
_is_java = (
|
|
388
|
+
(repo_root / "pom.xml").exists()
|
|
389
|
+
or (repo_root / "build.gradle").exists()
|
|
390
|
+
or (repo_root / "build.gradle.kts").exists()
|
|
391
|
+
)
|
|
392
|
+
# api_surface_complete: False when this is a Java repo but endpoints are absent.
|
|
393
|
+
# An empty list does NOT mean "no endpoints exist" — it means the endpoint
|
|
394
|
+
# index has not been built yet. Agents must call get_endpoints to populate.
|
|
395
|
+
_api_complete = not _is_java or bool(endpoints)
|
|
366
396
|
result: dict = {
|
|
367
397
|
"status": "cold_start_stale" if stale else "cold_start_ready",
|
|
368
398
|
"repo_id": ris.repo_id,
|
|
369
399
|
"git_head": ris.git_head,
|
|
400
|
+
"current_git_head": current_head,
|
|
370
401
|
"stale": stale,
|
|
402
|
+
"has_uncommitted_changes": uncommitted,
|
|
371
403
|
"last_updated_at": ris.last_updated_at,
|
|
404
|
+
"cache_source": "RIS",
|
|
405
|
+
"data_scope": "RIS_BOOTSTRAP",
|
|
406
|
+
"api_surface_complete": _api_complete,
|
|
372
407
|
"summary": ris.compact_summary,
|
|
373
408
|
"entrypoints": ris.structural_map.get("entrypoints", []),
|
|
374
409
|
"endpoints": endpoints,
|
|
375
410
|
"hotspots": ris.git_context_snapshot.get("hotspots", []),
|
|
376
411
|
}
|
|
377
|
-
if not endpoints:
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
result["endpoints_hint"] = (
|
|
383
|
-
"Java repo detected but no endpoint index found. "
|
|
384
|
-
"Call get_endpoints (or: sourcecode endpoints <path>) to populate."
|
|
385
|
-
)
|
|
412
|
+
if not endpoints and _is_java:
|
|
413
|
+
result["endpoints_hint"] = (
|
|
414
|
+
"Java repo detected but no endpoint index found. "
|
|
415
|
+
"Call get_endpoints (or: sourcecode endpoints <path>) to populate."
|
|
416
|
+
)
|
|
386
417
|
return result
|
|
387
418
|
except Exception:
|
|
388
419
|
return {"status": "no_ris"}
|
|
@@ -631,9 +631,12 @@ def _bootstrap_structured(eps: list) -> "Optional[dict[str, Any]]":
|
|
|
631
631
|
if security:
|
|
632
632
|
result["security"] = security
|
|
633
633
|
if controllers:
|
|
634
|
-
#
|
|
634
|
+
# Each controller file generates one EntryPoint regardless of how many
|
|
635
|
+
# handler methods it contains. controller_classes == len(controllers)
|
|
636
|
+
# always (deduplicated by path in _scan_java_file_for_entry_points).
|
|
637
|
+
# "methods" is therefore removed from the note — use `sourcecode endpoints`
|
|
638
|
+
# for per-method HTTP surface.
|
|
635
639
|
controller_classes = len({c["path"] for c in controllers})
|
|
636
|
-
controller_methods = len(controllers)
|
|
637
640
|
|
|
638
641
|
# Extract all DDD module names from controller paths and group by domain area.
|
|
639
642
|
# Path pattern: .../ddd/{module}/infrastructure/rest/*Controller.java
|
|
@@ -655,9 +658,8 @@ def _bootstrap_structured(eps: list) -> "Optional[dict[str, Any]]":
|
|
|
655
658
|
module_names.append(module)
|
|
656
659
|
|
|
657
660
|
_ctrl_note = (
|
|
658
|
-
f"{
|
|
659
|
-
f"
|
|
660
|
-
f" (use 'sourcecode endpoints' for full surface)"
|
|
661
|
+
f"{controller_classes} controller classes detected"
|
|
662
|
+
f" (use 'sourcecode endpoints' for per-method HTTP surface)"
|
|
661
663
|
)
|
|
662
664
|
if len(module_names) > 30:
|
|
663
665
|
# Group by first path segment under ddd/ (inferred domain area)
|
|
@@ -678,14 +680,12 @@ def _bootstrap_structured(eps: list) -> "Optional[dict[str, Any]]":
|
|
|
678
680
|
domain_groups[domain_prefix or "other"].append(module)
|
|
679
681
|
result["controllers"] = {
|
|
680
682
|
"classes": controller_classes,
|
|
681
|
-
"methods": controller_methods,
|
|
682
683
|
"note": _ctrl_note,
|
|
683
684
|
"modules": {k: sorted(v) for k, v in sorted(domain_groups.items())},
|
|
684
685
|
}
|
|
685
686
|
else:
|
|
686
687
|
result["controllers"] = {
|
|
687
688
|
"classes": controller_classes,
|
|
688
|
-
"methods": controller_methods,
|
|
689
689
|
"note": _ctrl_note,
|
|
690
690
|
"modules": sorted(module_names),
|
|
691
691
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|