sourcecode 1.33.0__tar.gz → 1.33.2__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.0 → sourcecode-1.33.2}/PKG-INFO +3 -3
- {sourcecode-1.33.0 → sourcecode-1.33.2}/README.md +2 -2
- {sourcecode-1.33.0 → sourcecode-1.33.2}/pyproject.toml +1 -1
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/__init__.py +1 -1
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/cache.py +71 -4
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/cli.py +94 -7
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/ris.py +25 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/.gitignore +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/.ruff.toml +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/CHANGELOG.md +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/CONTRIBUTING.md +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/LICENSE +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/SECURITY.md +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/raw +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/cache.tmp_new +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/classifier.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/flow_analyzer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/license.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/progress.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/redactor.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/scanner.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/schema.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/serializer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-1.33.0 → sourcecode-1.33.2}/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.2
|
|
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.2
|
|
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.2
|
|
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
|
|
|
@@ -219,6 +219,8 @@ _SUBCOMMANDS: frozenset[str] = frozenset(
|
|
|
219
219
|
"activate",
|
|
220
220
|
# Cache observability
|
|
221
221
|
"cache",
|
|
222
|
+
# RIS bootstrap
|
|
223
|
+
"cold-start",
|
|
222
224
|
}
|
|
223
225
|
)
|
|
224
226
|
|
|
@@ -1056,6 +1058,22 @@ def main(
|
|
|
1056
1058
|
code_notes = True
|
|
1057
1059
|
architecture = True
|
|
1058
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
|
+
|
|
1059
1077
|
# ── Two-layer cache ────────────────────────────────────────────────────────
|
|
1060
1078
|
# L1 (core): (repo, commit, analysis_flags) → pre-computed view data dict
|
|
1061
1079
|
# key = core-<git_sha>-<analysis_hash>.json.gz
|
|
@@ -1200,6 +1218,23 @@ def main(
|
|
|
1200
1218
|
|
|
1201
1219
|
if _cache_hit_content is not None:
|
|
1202
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
|
+
})
|
|
1203
1238
|
write_output(_cache_hit_content, output=output)
|
|
1204
1239
|
if copy and not output:
|
|
1205
1240
|
_copy_to_clipboard(_cache_hit_content)
|
|
@@ -1838,6 +1873,7 @@ def main(
|
|
|
1838
1873
|
_allowed_changed_files: Optional[set[str]] = None
|
|
1839
1874
|
if changed_only:
|
|
1840
1875
|
from sourcecode.git_analyzer import GitAnalyzer as _GitAnalyzerEarly
|
|
1876
|
+
_git_confirmed_clean = False
|
|
1841
1877
|
try:
|
|
1842
1878
|
_gc_early = _GitAnalyzerEarly().analyze(target, depth=1, days=1)
|
|
1843
1879
|
_bad_gc = {"no_git_repo", "git_not_found", "git_timeout"}
|
|
@@ -1846,15 +1882,31 @@ def main(
|
|
|
1846
1882
|
if _uc:
|
|
1847
1883
|
# WORKTREE_UNSTAGED + WORKTREE_STAGED only; untracked excluded
|
|
1848
1884
|
_allowed_changed_files = set(_uc.staged) | set(_uc.unstaged)
|
|
1849
|
-
|
|
1885
|
+
if not _allowed_changed_files:
|
|
1886
|
+
# Git is available and confirms no uncommitted changes.
|
|
1887
|
+
# Do NOT fall back to a full scan — that would silently produce
|
|
1888
|
+
# output identical to --compact, making it impossible for the
|
|
1889
|
+
# caller to distinguish "no changes" from "changes found".
|
|
1890
|
+
_git_confirmed_clean = True
|
|
1891
|
+
else:
|
|
1892
|
+
# Git unavailable — fall back gracefully.
|
|
1850
1893
|
typer.echo(
|
|
1851
|
-
"[changed-only] git unavailable
|
|
1894
|
+
"[changed-only] git unavailable — falling back to full scan.",
|
|
1852
1895
|
err=True,
|
|
1853
1896
|
)
|
|
1854
1897
|
changed_only = False
|
|
1855
1898
|
except Exception:
|
|
1856
1899
|
typer.echo("[changed-only] git error — falling back to full scan.", err=True)
|
|
1857
1900
|
changed_only = False
|
|
1901
|
+
if _git_confirmed_clean:
|
|
1902
|
+
_nc_payload = json.dumps({
|
|
1903
|
+
"status": "working_tree_clean",
|
|
1904
|
+
"no_changes": True,
|
|
1905
|
+
"changed_files": [],
|
|
1906
|
+
"message": "No uncommitted changes detected — working tree is clean.",
|
|
1907
|
+
}, ensure_ascii=False)
|
|
1908
|
+
write_output(_nc_payload, output=output)
|
|
1909
|
+
raise typer.Exit()
|
|
1858
1910
|
|
|
1859
1911
|
# Contract pipeline — runs for mode=contract|standard|deep|hybrid (skip for raw)
|
|
1860
1912
|
_progress.update("extracting contracts")
|
|
@@ -2063,6 +2115,23 @@ def main(
|
|
|
2063
2115
|
|
|
2064
2116
|
# 6. Write output (CLI-04)
|
|
2065
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
|
+
})
|
|
2066
2135
|
write_output(content, output=output)
|
|
2067
2136
|
|
|
2068
2137
|
# Persist to two-layer cache (git SHA unchanged → re-use on next run).
|
|
@@ -2539,9 +2608,14 @@ def prepare_context_cmd(
|
|
|
2539
2608
|
if _task_include("confidence"):
|
|
2540
2609
|
out["confidence"] = output.confidence
|
|
2541
2610
|
if task != "review-pr" and _task_include("relevant_files"):
|
|
2611
|
+
_rfs = output.relevant_files
|
|
2612
|
+
if task == "generate-tests":
|
|
2613
|
+
# relevant_files goal: untested SOURCE files. Test files belong in test_gaps.
|
|
2614
|
+
# Without this filter, high-churn test files rank above untested source files.
|
|
2615
|
+
_rfs = [f for f in _rfs if getattr(f, "role", None) != "test"]
|
|
2542
2616
|
out["relevant_files"] = [
|
|
2543
2617
|
_serialize_relevant_file(f)
|
|
2544
|
-
for f in
|
|
2618
|
+
for f in _rfs
|
|
2545
2619
|
]
|
|
2546
2620
|
if _task_include("key_dependencies") and output.key_dependencies:
|
|
2547
2621
|
out["key_dependencies"] = output.key_dependencies
|
|
@@ -4207,23 +4281,36 @@ def cache_status_cmd(
|
|
|
4207
4281
|
else:
|
|
4208
4282
|
typer.echo(f"Cache dir: {stats['cache_dir']}")
|
|
4209
4283
|
typer.echo(f"Cores: {stats['cores']}")
|
|
4210
|
-
typer.echo(f"Snapshots: {stats['snapshots']}")
|
|
4211
4284
|
typer.echo(f"Views: {stats['views']}")
|
|
4212
4285
|
typer.echo(f"CAS blobs: {stats['cas_blobs']}")
|
|
4213
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']}")
|
|
4214
4295
|
|
|
4215
4296
|
|
|
4216
4297
|
@cache_app.command("clear")
|
|
4217
4298
|
def cache_clear_cmd(
|
|
4218
4299
|
path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
|
|
4219
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."),
|
|
4220
4302
|
) -> None:
|
|
4221
|
-
"""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
|
+
"""
|
|
4222
4308
|
from sourcecode import cache as _cm
|
|
4223
4309
|
target = Path(path).resolve()
|
|
4224
4310
|
if not yes:
|
|
4225
|
-
|
|
4226
|
-
|
|
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)
|
|
4227
4314
|
typer.echo(f"Removed {removed} file(s).")
|
|
4228
4315
|
|
|
4229
4316
|
|
|
@@ -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,14 +381,19 @@ 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", [])
|
|
366
387
|
result: dict = {
|
|
367
388
|
"status": "cold_start_stale" if stale else "cold_start_ready",
|
|
368
389
|
"repo_id": ris.repo_id,
|
|
369
390
|
"git_head": ris.git_head,
|
|
391
|
+
"current_git_head": current_head,
|
|
370
392
|
"stale": stale,
|
|
393
|
+
"has_uncommitted_changes": uncommitted,
|
|
371
394
|
"last_updated_at": ris.last_updated_at,
|
|
395
|
+
"cache_source": "RIS",
|
|
396
|
+
"data_scope": "RIS_BOOTSTRAP",
|
|
372
397
|
"summary": ris.compact_summary,
|
|
373
398
|
"entrypoints": ris.structural_map.get("entrypoints", []),
|
|
374
399
|
"endpoints": endpoints,
|
|
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
|
|
File without changes
|
|
File without changes
|