code-context-control 2.119.0__py3-none-any.whl → 2.121.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.
- cli/c3.py +38 -1
- cli/commands/parser.py +13 -0
- cli/mcp_server.py +10 -7
- cli/tools/agent.py +6 -8
- cli/tools/compress.py +62 -6
- cli/tools/read.py +24 -3
- cli/tools/search.py +14 -17
- {code_context_control-2.119.0.dist-info → code_context_control-2.121.0.dist-info}/METADATA +1 -1
- {code_context_control-2.119.0.dist-info → code_context_control-2.121.0.dist-info}/RECORD +20 -18
- services/agents.py +4 -23
- services/bench/map_eval.py +999 -0
- services/compressor.py +4 -1
- services/file_map.py +353 -0
- services/file_memory.py +114 -156
- services/parser.py +134 -36
- services/telemetry.py +135 -0
- {code_context_control-2.119.0.dist-info → code_context_control-2.121.0.dist-info}/WHEEL +0 -0
- {code_context_control-2.119.0.dist-info → code_context_control-2.121.0.dist-info}/entry_points.txt +0 -0
- {code_context_control-2.119.0.dist-info → code_context_control-2.121.0.dist-info}/licenses/LICENSE +0 -0
- {code_context_control-2.119.0.dist-info → code_context_control-2.121.0.dist-info}/top_level.txt +0 -0
cli/c3.py
CHANGED
|
@@ -92,7 +92,7 @@ console = Console() if HAS_RICH else None
|
|
|
92
92
|
# Config
|
|
93
93
|
CONFIG_DIR = ".c3"
|
|
94
94
|
CONFIG_FILE = ".c3/config.json"
|
|
95
|
-
__version__ = "2.
|
|
95
|
+
__version__ = "2.121.0"
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
def _compress_file_cli(compressor, path, mode="smart", **kw):
|
|
@@ -1570,6 +1570,42 @@ def cmd_shell_eval(args):
|
|
|
1570
1570
|
sys.exit(1)
|
|
1571
1571
|
|
|
1572
1572
|
|
|
1573
|
+
def cmd_map_eval(args):
|
|
1574
|
+
"""Run the file-map gold suite and compare it to its baseline.
|
|
1575
|
+
|
|
1576
|
+
Renders the hand-annotated fixtures through the renderer the MCP tool
|
|
1577
|
+
uses and grades symbol recall, signatures, ranges and determinism. Exit
|
|
1578
|
+
status 1 when the baseline is violated (a must_pass case failed, an
|
|
1579
|
+
aggregate fell under its floor or rose over its ceiling, a complete map
|
|
1580
|
+
grew). See docs/map-eval.md.
|
|
1581
|
+
"""
|
|
1582
|
+
from services.bench import map_eval as me
|
|
1583
|
+
|
|
1584
|
+
try:
|
|
1585
|
+
report = me.run_suite(args.suite, baseline=args.baseline)
|
|
1586
|
+
except FileNotFoundError as e:
|
|
1587
|
+
raise RuntimeError(str(e))
|
|
1588
|
+
|
|
1589
|
+
if args.update_baseline:
|
|
1590
|
+
path = args.baseline or me.BUNDLED_BASELINES.get(report.suite)
|
|
1591
|
+
if not path:
|
|
1592
|
+
raise RuntimeError(
|
|
1593
|
+
f"suite {report.suite!r} has no bundled baseline; pass --baseline PATH")
|
|
1594
|
+
floors = json.loads(args.floors) if args.floors else None
|
|
1595
|
+
ceilings = json.loads(args.ceilings) if args.ceilings else None
|
|
1596
|
+
me.write_baseline(report, path, floors=floors, ceilings=ceilings)
|
|
1597
|
+
report.baseline_violations, report.baseline_warnings = me.compare_to_baseline(
|
|
1598
|
+
report, me.load_baseline(path))
|
|
1599
|
+
print(f"baseline written: {path}")
|
|
1600
|
+
|
|
1601
|
+
if args.json:
|
|
1602
|
+
print(json.dumps(report.to_dict(), indent=2))
|
|
1603
|
+
else:
|
|
1604
|
+
print(report.render())
|
|
1605
|
+
if report.baseline_violations:
|
|
1606
|
+
sys.exit(1)
|
|
1607
|
+
|
|
1608
|
+
|
|
1573
1609
|
def _benchmark_extract_preview(full_path: Path, compressor: CodeCompressor, pattern: str = "", max_lines: int = 50) -> str:
|
|
1574
1610
|
"""Approximate c3_filter behavior for local benchmarking without MCP startup."""
|
|
1575
1611
|
import re as _re
|
|
@@ -9261,6 +9297,7 @@ def main():
|
|
|
9261
9297
|
"stats": cmd_stats,
|
|
9262
9298
|
"search-eval": cmd_search_eval,
|
|
9263
9299
|
"shell-eval": cmd_shell_eval,
|
|
9300
|
+
"map-eval": cmd_map_eval,
|
|
9264
9301
|
"benchmark": cmd_benchmark,
|
|
9265
9302
|
"session-benchmark": cmd_session_benchmark,
|
|
9266
9303
|
"benchmark-e2e": cmd_benchmark_e2e,
|
cli/commands/parser.py
CHANGED
|
@@ -76,6 +76,19 @@ def build_parser(version: str, parse_cli_ide_arg):
|
|
|
76
76
|
help='With --update-baseline: JSON object of metric ceilings, e.g. \'{"bytes_p95": 18432}\'')
|
|
77
77
|
p_shell_eval.add_argument("--json", action="store_true", help="Emit the full report as JSON")
|
|
78
78
|
|
|
79
|
+
p_map_eval = subparsers.add_parser(
|
|
80
|
+
"map-eval", help="Run the file-map gold suite (docs/map-eval.md)")
|
|
81
|
+
p_map_eval.add_argument("--suite", default="fixture",
|
|
82
|
+
help="'fixture' (hand-annotated sources, CI gate) or a .jsonl path")
|
|
83
|
+
p_map_eval.add_argument("--baseline", default=None, help="Baseline JSON to compare against / write")
|
|
84
|
+
p_map_eval.add_argument("--update-baseline", action="store_true",
|
|
85
|
+
help="Write aggregates + per-case status to the baseline (floors/ceilings are kept)")
|
|
86
|
+
p_map_eval.add_argument("--floors", default=None,
|
|
87
|
+
help='With --update-baseline: JSON object of metric floors, e.g. \'{"symbol_recall_mean": 0.9}\'')
|
|
88
|
+
p_map_eval.add_argument("--ceilings", default=None,
|
|
89
|
+
help='With --update-baseline: JSON object of metric ceilings, e.g. \'{"tokens_p95": 4000}\'')
|
|
90
|
+
p_map_eval.add_argument("--json", action="store_true", help="Emit the full report as JSON")
|
|
91
|
+
|
|
79
92
|
p_compress = subparsers.add_parser("compress", help="Compress a file")
|
|
80
93
|
p_compress.add_argument("file", help="File to compress")
|
|
81
94
|
p_compress.add_argument("--mode", choices=["map", "dense_map", "smart", "diff"], default="smart")
|
cli/mcp_server.py
CHANGED
|
@@ -85,7 +85,7 @@ def _build_instructions(ide_name: str) -> str:
|
|
|
85
85
|
return (
|
|
86
86
|
"C3 — local code intelligence. Route by goal:\n"
|
|
87
87
|
" FIND candidates → c3_search\n"
|
|
88
|
-
" MAP file
|
|
88
|
+
" MAP a file → c3_read(file_path) with no symbols, then READ → c3_read(symbols=…); batch maps → c3_compress\n"
|
|
89
89
|
" EDIT code → c3_edit (always; it logs to the ledger automatically)\n"
|
|
90
90
|
" VALIDATE after every edit → c3_validate\n"
|
|
91
91
|
" BLAST RADIUS before shared-symbol edits → c3_impact\n"
|
|
@@ -583,8 +583,10 @@ async def c3_memory(action: str, query: str = "", fact: str = "",
|
|
|
583
583
|
@mcp.tool()
|
|
584
584
|
async def c3_read(file_path: str, symbols: Any = None, lines: Any = None,
|
|
585
585
|
include_docstrings: bool = True, ctx: Context = None) -> str:
|
|
586
|
-
"""READ
|
|
587
|
-
file_path: single or comma-separated. symbols:
|
|
586
|
+
"""READ source, or MAP a file when called with only file_path (read-only, plan-mode safe).
|
|
587
|
+
file_path: single or comma-separated. symbols: names as the map prints them (Class.method ok).
|
|
588
|
+
lines: int, [start,end], or list of ranges. No symbols/lines = one line per symbol with
|
|
589
|
+
signature and [La-Lb] range at ~10% of the file's tokens; then read the symbols you need."""
|
|
588
590
|
path_err = validate_file_path(file_path)
|
|
589
591
|
if path_err:
|
|
590
592
|
return f"[c3_read:error] {path_err}"
|
|
@@ -597,10 +599,11 @@ async def c3_read(file_path: str, symbols: Any = None, lines: Any = None,
|
|
|
597
599
|
|
|
598
600
|
|
|
599
601
|
@mcp.tool()
|
|
600
|
-
async def c3_compress(file_path: str, mode: str = "
|
|
601
|
-
"""MAP file
|
|
602
|
-
|
|
603
|
-
|
|
602
|
+
async def c3_compress(file_path: str, mode: str = "map", ctx: Context = None) -> str:
|
|
603
|
+
"""MAP a file — one line per symbol with full signature and [La-Lb] range, ~10% of the
|
|
604
|
+
file's tokens (read-only, plan-mode safe). Same map as c3_read(file_path) with no symbols;
|
|
605
|
+
use this form for a comma-separated batch (up to 10). mode: map (default); other modes
|
|
606
|
+
are legacy."""
|
|
604
607
|
path_err = validate_file_path(file_path)
|
|
605
608
|
if path_err:
|
|
606
609
|
return f"[c3_compress:error] {path_err}"
|
cli/tools/agent.py
CHANGED
|
@@ -877,11 +877,10 @@ def _parallel_compress(files: list, svc, steps_log: list,
|
|
|
877
877
|
maps = {}
|
|
878
878
|
|
|
879
879
|
def compress_one(fp):
|
|
880
|
-
full_path = str(Path(svc.project_path) / fp)
|
|
881
880
|
try:
|
|
882
|
-
|
|
883
|
-
if
|
|
884
|
-
return fp,
|
|
881
|
+
fmap = svc.file_memory.get_or_build_map(fp.replace("\\", "/"))
|
|
882
|
+
if fmap and not fmap.startswith("[file_map]"):
|
|
883
|
+
return fp, fmap
|
|
885
884
|
except Exception:
|
|
886
885
|
pass
|
|
887
886
|
return fp, None
|
|
@@ -936,11 +935,10 @@ def _parallel_validate_and_compress(files: list, svc, steps_log: list):
|
|
|
936
935
|
return "validate", fp, "ERROR", str(e)
|
|
937
936
|
|
|
938
937
|
def compress_one(fp):
|
|
939
|
-
full_path = str(Path(svc.project_path) / fp)
|
|
940
938
|
try:
|
|
941
|
-
|
|
942
|
-
if
|
|
943
|
-
return "compress", fp,
|
|
939
|
+
fmap = svc.file_memory.get_or_build_map(fp.replace("\\", "/"))
|
|
940
|
+
if fmap and not fmap.startswith("[file_map]"):
|
|
941
|
+
return "compress", fp, fmap
|
|
944
942
|
except Exception:
|
|
945
943
|
pass
|
|
946
944
|
return "compress", fp, None
|
cli/tools/compress.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
"""c3_compress —
|
|
2
|
-
|
|
1
|
+
"""c3_compress — the canonical file map (services/file_map.render_map), one file or a batch.
|
|
2
|
+
|
|
3
|
+
mode='map' (default) is the same text c3_read(file_path) serves. The other
|
|
4
|
+
modes (dense_map, smart, diff, bug_scan, ast) are legacy and retire in 2.122.0."""
|
|
3
5
|
|
|
4
6
|
import json
|
|
5
7
|
import shutil
|
|
@@ -14,6 +16,48 @@ from core import count_tokens
|
|
|
14
16
|
from services import access_guard
|
|
15
17
|
|
|
16
18
|
|
|
19
|
+
def map_detail(svc, rel: str, requested_mode: str, *, cache_hit: bool,
|
|
20
|
+
symbols: int = 0, fallback: str = "") -> dict:
|
|
21
|
+
"""Telemetry `detail` for a map served from file_memory (C0, 2.120.0).
|
|
22
|
+
|
|
23
|
+
backend names the code path, parser the extractor that built the record
|
|
24
|
+
(tree_sitter | regex | generic, "" for a pre-2.120.0 record), cache_hit
|
|
25
|
+
whether the record was already fresh. `symbols` is how many the caller
|
|
26
|
+
asked for and `fallback` why a read served a map instead of source.
|
|
27
|
+
Flat so telemetry.aggregate_tool_telemetry can fold it without a schema.
|
|
28
|
+
"""
|
|
29
|
+
record = None
|
|
30
|
+
try:
|
|
31
|
+
record = svc.file_memory.get(rel)
|
|
32
|
+
except Exception:
|
|
33
|
+
record = None
|
|
34
|
+
if not isinstance(record, dict):
|
|
35
|
+
record = None
|
|
36
|
+
detail = {
|
|
37
|
+
"requested_mode": requested_mode,
|
|
38
|
+
"backend": "file_memory",
|
|
39
|
+
"parser": str((record or {}).get("parser") or ""),
|
|
40
|
+
"cache_hit": bool(cache_hit),
|
|
41
|
+
"sections": len((record or {}).get("sections") or []),
|
|
42
|
+
}
|
|
43
|
+
if symbols:
|
|
44
|
+
detail["symbols"] = int(symbols)
|
|
45
|
+
if fallback:
|
|
46
|
+
detail["fallback"] = fallback
|
|
47
|
+
return detail
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def compressor_detail(res: dict, requested_mode: str) -> dict:
|
|
51
|
+
"""Telemetry `detail` for a CodeCompressor result (smart/diff/bug_scan…)."""
|
|
52
|
+
backend = "large_fast_path" if res.get("fast_path") else "compressor"
|
|
53
|
+
return {
|
|
54
|
+
"requested_mode": requested_mode,
|
|
55
|
+
"backend": backend,
|
|
56
|
+
"actual_mode": str(res.get("mode") or ""),
|
|
57
|
+
"cache_hit": bool(res.get("cache_hit")),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
17
61
|
def _run_memory_mcp_cli(args: list, cwd: str, timeout: int = 30) -> tuple:
|
|
18
62
|
"""Run codebase-memory-mcp CLI and return (success, output_or_error)."""
|
|
19
63
|
binary = shutil.which("codebase-memory-mcp")
|
|
@@ -150,6 +194,8 @@ def _compress_single(file_path: str, mode: str, svc, finalize, maybe_facts) -> s
|
|
|
150
194
|
return ("[file_map:error] not found. To create a new file use "
|
|
151
195
|
"c3_edit(file_path=..., old_string='', new_string=<content>).")
|
|
152
196
|
rel = str(full.resolve().relative_to(Path(svc.project_path).resolve())).replace("\\", "/")
|
|
197
|
+
# C0 measurement: was the record already fresh before this call?
|
|
198
|
+
cache_hit = not svc.file_memory.needs_update(rel)
|
|
153
199
|
queued = svc.file_memory.drain_queue()
|
|
154
200
|
completed = []
|
|
155
201
|
failed = []
|
|
@@ -174,8 +220,14 @@ def _compress_single(file_path: str, mode: str, svc, finalize, maybe_facts) -> s
|
|
|
174
220
|
raw_tokens = None
|
|
175
221
|
map_tokens = 0
|
|
176
222
|
try:
|
|
177
|
-
|
|
223
|
+
raw_text = full.read_text(encoding="utf-8", errors="replace")
|
|
224
|
+
raw_tokens = count_tokens(raw_text)
|
|
178
225
|
map_tokens = count_tokens(res)
|
|
226
|
+
if map_tokens >= raw_tokens and not res.startswith("[file_map]"):
|
|
227
|
+
# A map that costs more than the file is worth nothing
|
|
228
|
+
# (docs/file-map.md § Small files).
|
|
229
|
+
res = f"[compress:{file_path}] whole file — smaller than its map\n" + raw_text
|
|
230
|
+
map_tokens = count_tokens(res)
|
|
179
231
|
summary = mode
|
|
180
232
|
except Exception:
|
|
181
233
|
summary = "mapped"
|
|
@@ -183,7 +235,8 @@ def _compress_single(file_path: str, mode: str, svc, finalize, maybe_facts) -> s
|
|
|
183
235
|
finalize, svc, "c3_compress", {"file_path": file_path, "mode": mode},
|
|
184
236
|
res, summary,
|
|
185
237
|
raw_tokens=raw_tokens, optimized_tokens=map_tokens or None,
|
|
186
|
-
response_tokens=map_tokens
|
|
238
|
+
response_tokens=map_tokens,
|
|
239
|
+
detail=map_detail(svc, rel, mode, cache_hit=cache_hit))
|
|
187
240
|
|
|
188
241
|
try:
|
|
189
242
|
res = svc.compressor.compress_file(str(full), mode)
|
|
@@ -197,7 +250,8 @@ def _compress_single(file_path: str, mode: str, svc, finalize, maybe_facts) -> s
|
|
|
197
250
|
finalize, svc, "c3_compress", {"file_path": file_path},
|
|
198
251
|
resp + maybe_facts(svc, Path(file_path).name), mode,
|
|
199
252
|
raw_tokens=res.get('original_tokens'),
|
|
200
|
-
optimized_tokens=res.get('compressed_tokens')
|
|
253
|
+
optimized_tokens=res.get('compressed_tokens'),
|
|
254
|
+
detail=compressor_detail(res, mode))
|
|
201
255
|
|
|
202
256
|
|
|
203
257
|
def _compress_batch(paths: list, mode: str, svc, finalize, maybe_facts) -> str:
|
|
@@ -288,4 +342,6 @@ def _compress_batch(paths: list, mode: str, svc, finalize, maybe_facts) -> str:
|
|
|
288
342
|
{"file_path": ",".join(paths), "mode": mode, "batch": True},
|
|
289
343
|
body, f"batch {total_ok}/{len(paths)}",
|
|
290
344
|
raw_tokens=total_raw if measured else None,
|
|
291
|
-
optimized_tokens=total_opt if measured else None
|
|
345
|
+
optimized_tokens=total_opt if measured else None,
|
|
346
|
+
detail={"requested_mode": mode, "backend": "batch",
|
|
347
|
+
"files": len(paths), "ok": total_ok})
|
cli/tools/read.py
CHANGED
|
@@ -8,6 +8,7 @@ from typing import Any
|
|
|
8
8
|
|
|
9
9
|
from cli.tools import _grants
|
|
10
10
|
from cli.tools._helpers import finalize_with_tokens, maybe_related_facts
|
|
11
|
+
from cli.tools.compress import map_detail
|
|
11
12
|
from core import count_tokens
|
|
12
13
|
from services import access_guard
|
|
13
14
|
|
|
@@ -301,16 +302,25 @@ def handle_read(file_path: str, symbols: Any = None, lines: Any = None,
|
|
|
301
302
|
ranges.extend(main_ranges)
|
|
302
303
|
|
|
303
304
|
if not ranges and symbols:
|
|
305
|
+
map_cached = not svc.file_memory.needs_update(rel_path)
|
|
304
306
|
file_map = svc.file_memory.get_or_build_map(rel_path)
|
|
305
307
|
resp = f"[read:{file_path}] symbols not found: {symbols}. Showing file map:\n{file_map}"
|
|
306
308
|
map_tok = count_tokens(file_map)
|
|
307
309
|
return finalize_with_tokens(
|
|
308
310
|
finalize, svc, "c3_read", {"file": file_path, "symbols": symbols},
|
|
309
311
|
resp, f"{full_file_tokens()}->{map_tok}tok",
|
|
310
|
-
raw_tokens=full_file_tokens(), optimized_tokens=map_tok
|
|
312
|
+
raw_tokens=full_file_tokens(), optimized_tokens=map_tok,
|
|
313
|
+
detail=map_detail(svc, rel_path, "read", cache_hit=map_cached,
|
|
314
|
+
symbols=len(symbols), fallback="symbols_not_found"))
|
|
311
315
|
|
|
312
316
|
if not ranges:
|
|
317
|
+
map_cached = not svc.file_memory.needs_update(rel_path)
|
|
313
318
|
file_map = svc.file_memory.get_or_build_map(rel_path)
|
|
319
|
+
if count_tokens(file_map) >= full_file_tokens():
|
|
320
|
+
# A map that costs more than the file is worth nothing: serve the
|
|
321
|
+
# file (docs/file-map.md § Small files).
|
|
322
|
+
file_map = (f"[read:{file_path}] whole file — smaller than its map\n"
|
|
323
|
+
+ raw_text)
|
|
314
324
|
resp = (file_map
|
|
315
325
|
+ "\n[map only — pass lines=[start,end] or symbols=[...] for exact source]"
|
|
316
326
|
+ maybe_related_facts(svc, rel_path, top_k=3, context="read"))
|
|
@@ -319,7 +329,9 @@ def handle_read(file_path: str, symbols: Any = None, lines: Any = None,
|
|
|
319
329
|
finalize, svc, "c3_read", {"file": file_path},
|
|
320
330
|
resp, f"{full_file_tokens()}->{map_tok}tok",
|
|
321
331
|
raw_tokens=full_file_tokens(), optimized_tokens=map_tok,
|
|
322
|
-
response_tokens=map_tok
|
|
332
|
+
response_tokens=map_tok,
|
|
333
|
+
detail=map_detail(svc, rel_path, "read", cache_hit=map_cached,
|
|
334
|
+
fallback="map_only"))
|
|
323
335
|
else:
|
|
324
336
|
# Sort and merge overlapping ranges
|
|
325
337
|
ranges.sort()
|
|
@@ -359,7 +371,16 @@ def handle_read(file_path: str, symbols: Any = None, lines: Any = None,
|
|
|
359
371
|
resp = final_content + maybe_related_facts(svc, rel_path, top_k=3, context="read")
|
|
360
372
|
tokens = count_tokens(resp)
|
|
361
373
|
summary = f"{full_file_tokens()}->{tokens}tok" if tokens < full_file_tokens() else f"{tokens}tok"
|
|
374
|
+
# C0 measurement: what the read asked for and how much source it took.
|
|
375
|
+
read_detail = {
|
|
376
|
+
"backend": "source",
|
|
377
|
+
"symbols": len(symbols) if symbols else 0,
|
|
378
|
+
"by_lines": bool(lines),
|
|
379
|
+
"ranges": len(ranges),
|
|
380
|
+
"lines_served": sum(e - s_ + 1 for s_, e in ranges),
|
|
381
|
+
"file_lines": len(content_lines),
|
|
382
|
+
}
|
|
362
383
|
return finalize_with_tokens(
|
|
363
384
|
finalize, svc, "c3_read", {"file": file_path, "symbols": symbols}, resp, summary,
|
|
364
385
|
raw_tokens=full_file_tokens(), optimized_tokens=tokens,
|
|
365
|
-
response_tokens=tokens)
|
|
386
|
+
response_tokens=tokens, detail=read_detail)
|
cli/tools/search.py
CHANGED
|
@@ -13,6 +13,7 @@ from pathlib import Path
|
|
|
13
13
|
from cli.tools._helpers import finalize_with_tokens, show_token_ratios
|
|
14
14
|
from core import count_tokens
|
|
15
15
|
from services import access_guard
|
|
16
|
+
from services.file_map import render_map
|
|
16
17
|
from services.indexer import DEFAULT_CODE_EXTS
|
|
17
18
|
from services.lexical_index import Filters, doc_kind
|
|
18
19
|
from services.scanner import SKIP_DIRS, iter_files
|
|
@@ -473,21 +474,15 @@ def _first_file_map(rel: str, svc) -> str:
|
|
|
473
474
|
watcher_active = (hasattr(svc, "watcher") and svc.watcher._observer.is_alive())
|
|
474
475
|
if not watcher_active and svc.file_memory.needs_update(rel):
|
|
475
476
|
svc.file_memory.update(rel)
|
|
476
|
-
|
|
477
|
+
record = svc.file_memory.get(rel)
|
|
478
|
+
if not record:
|
|
479
|
+
return ""
|
|
480
|
+
# Same renderer as c3_read/c3_compress, shortened to the cap by
|
|
481
|
+
# dropping symbol lines from the end (docs/file-map.md).
|
|
482
|
+
fmap = render_map(record, max_tokens=_MAP_TOKEN_CAP)
|
|
477
483
|
if not fmap:
|
|
478
484
|
return ""
|
|
479
|
-
|
|
480
|
-
return f"\n {fmap.replace(chr(10), chr(10) + ' ')}"
|
|
481
|
-
# Truncate large maps: keep first N lines
|
|
482
|
-
truncated = []
|
|
483
|
-
tok = 0
|
|
484
|
-
for fl in fmap.split("\n"):
|
|
485
|
-
tok += count_tokens(fl)
|
|
486
|
-
if tok > _MAP_TOKEN_CAP:
|
|
487
|
-
break
|
|
488
|
-
truncated.append(fl)
|
|
489
|
-
truncated.append(" [map truncated]")
|
|
490
|
-
return f"\n {chr(10).join(truncated).replace(chr(10), chr(10) + ' ')}"
|
|
485
|
+
return f"\n {fmap.replace(chr(10), chr(10) + ' ')}"
|
|
491
486
|
except Exception:
|
|
492
487
|
return ""
|
|
493
488
|
|
|
@@ -720,11 +715,13 @@ def _append_prefetch(resp: str, query: str, top_k: int, svc) -> str:
|
|
|
720
715
|
uncached.append(fp)
|
|
721
716
|
|
|
722
717
|
def compress_one(fp):
|
|
718
|
+
# The canonical map from file_memory — the same text c3_read serves
|
|
719
|
+
# (compressor.compress_file never had a "map" mode; it rendered a
|
|
720
|
+
# structure-only summary under that name).
|
|
723
721
|
try:
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
return fp, result["compressed"]
|
|
722
|
+
fmap = svc.file_memory.get_or_build_map(fp.replace("\\", "/"))
|
|
723
|
+
if fmap and not fmap.startswith("[file_map]"):
|
|
724
|
+
return fp, fmap
|
|
728
725
|
except Exception:
|
|
729
726
|
pass
|
|
730
727
|
return fp, None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-context-control
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.121.0
|
|
4
4
|
Summary: Local MCP code-intelligence for AI coding tools: surgical search/read/edit, agent-config version history, path-level access + masking guards, and a multi-project hub.
|
|
5
5
|
Author-email: Dimitri Tselenchuk <dtselenc@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
cli/__init__.py,sha256=ec66drCZGNMRU4V6ov0zVhYZph1us12Vn8OvG_LJyRY,22
|
|
2
2
|
cli/_hook_utils.py,sha256=e48F6FQloFReUIMtmWsih2t54j9SROzAaHtMkEuttDs,20489
|
|
3
3
|
cli/_shell_writes.py,sha256=XzUNlUDljmg-wZxK03xHnRe36WeQxe4tpO9HKVFwP5o,5090
|
|
4
|
-
cli/c3.py,sha256=
|
|
4
|
+
cli/c3.py,sha256=33z6xHxi3hdsM-zyi4WAM8QGW9rjFuuas5rpc357tMA,416811
|
|
5
5
|
cli/docs.html,sha256=X9M7qC7LWi8pPJNF-4yEM57jhuZ3WeTESECbdDOeNaA,143172
|
|
6
6
|
cli/edits.html,sha256=UjAhoCmBmQ89cklGvJqzC6eyNP2tc8H6T-e01DVkLvE,43418
|
|
7
7
|
cli/hook_access_guard.py,sha256=ba8-un6G9Ap8HCzx7nyhVq3c_G4sOtiVXRy0BzzX8-Y,23349
|
|
@@ -23,7 +23,7 @@ cli/hook_terse_advisor.py,sha256=W_zaTfX2akgX-ifuUTsLu2H1atM8XmWt6jpPoeU7Pm0,625
|
|
|
23
23
|
cli/hub_server.py,sha256=4BxvRHCGNc87jOtWankOVjQLYNk-GhWI2OY1harC8ro,184548
|
|
24
24
|
cli/hub_ui.html,sha256=nnGqWhWGCLfNisGUnoAlQFcBr6wPNsq-AEOR7uFtVkE,2942
|
|
25
25
|
cli/mcp_proxy.py,sha256=r-Qhlie7gCtotex8yQUudCyBLRUK-IW6cNIW5RhBW0o,17648
|
|
26
|
-
cli/mcp_server.py,sha256=
|
|
26
|
+
cli/mcp_server.py,sha256=MC5adrvIoENLBhV3y4OCrgHHySb-EAfqVngx9MZvZOQ,64241
|
|
27
27
|
cli/progress.py,sha256=qTkNy0howOD3hXraMo4ok3dYTpBW5yG_tenKfF4UDM0,1470
|
|
28
28
|
cli/server.py,sha256=bsHvKGPXsBJi_MDLiw0IAEq8RWKIG56q72c7OKKTLug,173324
|
|
29
29
|
cli/ui.html,sha256=xcdt74nlFEXx-0Bx6-Okw-WSVZPAXL0iukxU0ytI6CA,5694
|
|
@@ -31,7 +31,7 @@ cli/ui_legacy.html,sha256=cI8tC6RKmE2NIJOcsu7CY-zT4VznjcbD6NTjxb_fvUY,378460
|
|
|
31
31
|
cli/ui_nano.html,sha256=UAwQ6bbTOXAoGq191AZ7slhngR9edJSa3IhqpynveDg,27740
|
|
32
32
|
cli/commands/__init__.py,sha256=0Z8MABNzwSFJGT4Xv9R5AJVR8XxraTsuVTz5b0bShmo,38
|
|
33
33
|
cli/commands/common.py,sha256=zkxr0zW9KB600BlarJ2VXf7UOp2ak21R6uXZKdGvpyg,14413
|
|
34
|
-
cli/commands/parser.py,sha256=
|
|
34
|
+
cli/commands/parser.py,sha256=d4nL6_UPeY4_Cb4ecAWjNyZBVvfdO450WFJavA2E9Fc,65353
|
|
35
35
|
cli/guide/access.html,sha256=BelnEc4p7XgcH_ysTlMXL9ZHCD0O4CPLq3h5rvqPIh8,14790
|
|
36
36
|
cli/guide/bitbucket.html,sha256=6zmHIv2-G314_HDqMCQV-ZAIy0-co0gaFs0_k2Q1kYc,35491
|
|
37
37
|
cli/guide/credentials.html,sha256=801vO_cCqG7oHsa73yAGSoBDGZ4mcGsdx6D2uS1dwCQ,58196
|
|
@@ -74,11 +74,11 @@ cli/hub_ui/components/topbar.js,sha256=vi23ybRUBrvsOLRR2L3pQem6VdkCde3qGEIKpgizM
|
|
|
74
74
|
cli/tools/__init__.py,sha256=HO6eVKzDm1KPqsKTHY6lfIsnFnv3C7Bd9o3Q_V88idg,127
|
|
75
75
|
cli/tools/_grants.py,sha256=xJwEZWC9r4exq0OuabQy-fFMZFayUYZe0Eub7NS3ikQ,4974
|
|
76
76
|
cli/tools/_helpers.py,sha256=ER9zMOyxYmNZk72tkJTtRyNA9lGnnV7iHNRqyE1Sb7Q,8060
|
|
77
|
-
cli/tools/agent.py,sha256=
|
|
77
|
+
cli/tools/agent.py,sha256=RmEEh5HhCZLvYu0gSRcwA_NENpd4iIYJW3exBzT8kVs,47983
|
|
78
78
|
cli/tools/artifacts.py,sha256=SjazShDFIzOpAZW7uMv9itBktXnc6pqQKZeaen8GgzI,9379
|
|
79
79
|
cli/tools/bitbucket.py,sha256=cni8Y1EoiNm9jcHEpzi2dgKFIT1-4ct0WOhpTs7ac3k,35812
|
|
80
80
|
cli/tools/ci.py,sha256=fQ03RENywS5gqHjH26Fa4YiraBTvchlPcPSnVYwwKBs,19444
|
|
81
|
-
cli/tools/compress.py,sha256=
|
|
81
|
+
cli/tools/compress.py,sha256=3uvtjH1OWCzM5XKpfth7xRnJjlMxUq-LZkql8KG7SvQ,15362
|
|
82
82
|
cli/tools/credentials.py,sha256=R8Errpa41vsq0Z_sb7K-fmJxVovzXDYUyniITLrFC24,19033
|
|
83
83
|
cli/tools/delegate.py,sha256=yA2-ph53Y8XAJMpz7R5LwqXshRRD8bYljgN244RbHWA,62012
|
|
84
84
|
cli/tools/edit.py,sha256=xnRRM2UUybvGvfSzXzIjWmt6_zNaCbkupiK1z-8K4jE,30127
|
|
@@ -92,8 +92,8 @@ cli/tools/locks.py,sha256=FvuNxToU-5ZE2m_C6fTrn1jKERoxR4cbtxNb_b8mXqA,5882
|
|
|
92
92
|
cli/tools/memory.py,sha256=yDDRsEngeFcjb6nXUrhRZXuboasXfixJeyltKhDbZD4,24935
|
|
93
93
|
cli/tools/override.py,sha256=LiZFgoDN-35NXVwldnYrcCzfBl8MhQQ3tKVVkOG5d_o,9217
|
|
94
94
|
cli/tools/project.py,sha256=MWzi7tBTQ03p_6PavmG9ZLXZHO0IhjKEJNyB0oZWTFc,24248
|
|
95
|
-
cli/tools/read.py,sha256=
|
|
96
|
-
cli/tools/search.py,sha256=
|
|
95
|
+
cli/tools/read.py,sha256=vQP-tfm3Mpx0y2vObPBOUeHsCdANacb_onzYJc0v-m8,17641
|
|
96
|
+
cli/tools/search.py,sha256=BZh3E7YggjCWOC1lJz0YNcRsdDLWsJiEmONtgUW2rEo,32397
|
|
97
97
|
cli/tools/session.py,sha256=ailQiRqXhi9NcBB3XS3Dv72GEkEZaYivB0bqDlhmgTA,5202
|
|
98
98
|
cli/tools/shell.py,sha256=Zcsocl8IXeL9DSQNewq_fXECSufTccUdwc-H3S2Alto,58818
|
|
99
99
|
cli/tools/shell_job.py,sha256=_ilFBL3ulmqx8mwQJFUG6xW5dN-4NHqe0EZfRJL2e-4,16451
|
|
@@ -123,7 +123,7 @@ cli/ui/components/sessions.js,sha256=FIKtil76B8tCkAmcFV7hlj6GQ_DCJK2jCzvEmdK7NBE
|
|
|
123
123
|
cli/ui/components/settings.js,sha256=ATbAjBlVIwCNpxq7s191b49a_INQV38iwmySqtJLYwY,79066
|
|
124
124
|
cli/ui/components/sidebar.js,sha256=K2ym2kUgpbyG-EBA_wBIIiqQY8qTatsiBZwzseMWuIQ,9939
|
|
125
125
|
cli/ui/components/tasks.js,sha256=vyKQ3uwoppMwvdEaHlhWXW4oWcAisx4NveqzMhsYqHo,38438
|
|
126
|
-
code_context_control-2.
|
|
126
|
+
code_context_control-2.121.0.dist-info/licenses/LICENSE,sha256=l8Kh5QCNWNvR6kIt8L0BUZvc2LAFiHv2c-FnsGnUZf4,11301
|
|
127
127
|
core/__init__.py,sha256=TSDCEcM4V7gcZVM3w2ykJaqEUch4Dkon-rivV17T73s,2501
|
|
128
128
|
core/codex_patch.py,sha256=CxvT_rQIwPNTRJZj9EHJmx0ZdOeSLxjiixuLI3Z7lPc,2107
|
|
129
129
|
core/config.py,sha256=YmkcZwedz_lfDM0ZuI_f4xpe98ixPLcSQFcTCHgRiRg,19206
|
|
@@ -181,7 +181,7 @@ services/access_telemetry.py,sha256=9pOCLS9LgDtM7Z4cCEyl7osxhj-m3wsPubcXolBKuV4,
|
|
|
181
181
|
services/activity_log.py,sha256=PNGeEYlw1Aux-mRU4pKCQdbsSuzLvOg7MYiq-cb4n8g,4473
|
|
182
182
|
services/agent_base.py,sha256=a-gdSd_jtZtbjXo1WS8CnWCagXgKaGZd5ShcG6s0kT4,4809
|
|
183
183
|
services/agent_locks.py,sha256=nRAPuRSoet7BG9oOsNyA5WhlNy-UMhpsq6z5ggIPmLA,22421
|
|
184
|
-
services/agents.py,sha256=
|
|
184
|
+
services/agents.py,sha256=oKJS6yUxA49RO9cKvUk59QipIxQoYVvEnl1S46nWpPc,83571
|
|
185
185
|
services/artifact_defs.py,sha256=4bJAcg2maJCtMhY3ub_IxZg49bVg1mD_B5Z4Bn5NF0o,9805
|
|
186
186
|
services/artifact_store.py,sha256=vCHpOzOcZmR5VF_BxcvIh6G0rPMl07c1W6Wdu2OkVVA,31098
|
|
187
187
|
services/atomic_json.py,sha256=8-TXLXDv6raPCCjbyU13M-zbJmsnFRp1TsAP4kLG174,5661
|
|
@@ -203,7 +203,7 @@ services/circuit_breaker.py,sha256=ES6PpjL40gfDhtK88GeS6GFzsz5EOEEkZsB-DzX_tbw,3
|
|
|
203
203
|
services/claude_md.py,sha256=jXvr0orZOkPYrVxEkf67beAj8EKPbB_M99mmxxMsPg4,61217
|
|
204
204
|
services/codex_integration.py,sha256=dXlaVbQJrIrlZw5hZcDjhZVli3AvumnOTQTkl-_I-rg,7708
|
|
205
205
|
services/codex_transcript.py,sha256=TtxKiNkZ3vsWpaQRczaIGsA7wd8r24z_65Nnso2BNj0,3658
|
|
206
|
-
services/compressor.py,sha256=
|
|
206
|
+
services/compressor.py,sha256=oX5Tnn42syY_08_SWukEkRamwTH7qwQ-T6TGIlZNgJY,36148
|
|
207
207
|
services/context_snapshot.py,sha256=2f_bxY3xX4ZC653ncYno8YSeSTFSavwBjrorytHA0Ns,16879
|
|
208
208
|
services/conversation_store.py,sha256=RKA0nk09VAI8Ip935kSVlQdF-cQEzFLrgHbZiEJ7G1E,37518
|
|
209
209
|
services/cred_audit.py,sha256=tdxnRzbB0c8nJ7dqA9HOJrcOeRWivEzq6yhwu6ksmZI,10240
|
|
@@ -217,7 +217,8 @@ services/edit_ledger.py,sha256=Tj8Q14o19VHQHpofRHdbZKKcVs17v-ctZ3LOeVTqvdQ,29362
|
|
|
217
217
|
services/embedding_index.py,sha256=c85kAkekL7r-liiFEwqi7nJAQr4ZbJA36h0oMdiTurc,25156
|
|
218
218
|
services/enforcement_policy.py,sha256=bB-lI7EZwWT5MhoNtkBjjixN4h7zAYUtVBWPMJ6aONo,17196
|
|
219
219
|
services/error_reporting.py,sha256=HZ3ru8i5RLf8nq2R4iRnTs5sm1blUxknSbv5hdxuxs0,4139
|
|
220
|
-
services/
|
|
220
|
+
services/file_map.py,sha256=t5Ua4abJDuo-EZSX793MWiXg4uMqNAk1KxZrOffIm48,14500
|
|
221
|
+
services/file_memory.py,sha256=AckiClEKJeVJqCPoCTrqkCMDfHruVWhz2UZn3Df_7bU,33788
|
|
221
222
|
services/git_context.py,sha256=FFdA8Y46op4DYsUnZsXLTqxIjgxb0q_d-hkFTbosC8Q,9518
|
|
222
223
|
services/hub_service.py,sha256=z2MZ2Rg9NLNXxQ17e05XGF_f6qSTDWLYtoC3WpuQfGU,2634
|
|
223
224
|
services/index_store.py,sha256=SWHHKQB_wYww1ubtKgkLo5FFEqaWhPbw0OAN8x3gk9Q,16453
|
|
@@ -249,7 +250,7 @@ services/override_grants.py,sha256=BQVwD6WCxVORsUaqDWNToIalz2mStnJRMIR-SOxQzuQ,3
|
|
|
249
250
|
services/override_policy.py,sha256=VC5H-FETUz4u6f4JM_q5jqT2HIb1MiFzhpdJyuBG72I,22369
|
|
250
251
|
services/override_requests.py,sha256=H9lSUbfOjkjE1muztaQ58InqqsqhtnKIT-pF2VzvWkg,31148
|
|
251
252
|
services/override_wake.py,sha256=HjSOXQTDi0irgeMyrTqngL7xLcSOOIgYsVW9yxod4ps,10363
|
|
252
|
-
services/parser.py,sha256=
|
|
253
|
+
services/parser.py,sha256=4pklWjWICIVohRLghylS727idWCWyrUBWks1Xh4526o,62782
|
|
253
254
|
services/project_manager.py,sha256=uQh-GUvH5cgV5Ce9XYPINlQeqH6NvNdW8zrNFYRbBkw,37855
|
|
254
255
|
services/project_runtime.py,sha256=yyftX48BFjq4B2KcR4KGszDAAa4xR6gn6j0SgTyro1s,11603
|
|
255
256
|
services/protocol.py,sha256=CWZ0BWWd-jPui-11hZlvGgkNk18gMFhhLWbp-BP35Kk,12861
|
|
@@ -270,7 +271,7 @@ services/shell_jobs.py,sha256=p95Sr_lCA04ZkZh3UezeIxxw72_ClDzASaH-T9CV8CA,48581
|
|
|
270
271
|
services/shell_output.py,sha256=lLvENKqS9N7-hsVw9M7fWUXhBVPaaGRmU9dRx3Vr4Dk,42558
|
|
271
272
|
services/subprojects.py,sha256=pjGbuWLqC81QxYMai9ZbNxmTJTuwzM7QNiyAqeZG-Yg,41218
|
|
272
273
|
services/task_store.py,sha256=QoyhLcAyd-JZxn1nMfnZ11tTLgFqY-xwmlx1q8w_1p8,49477
|
|
273
|
-
services/telemetry.py,sha256=
|
|
274
|
+
services/telemetry.py,sha256=nueiwm8rR-OjTInBt0G4wAr7Qit5nUI92I5B2EIowZE,26128
|
|
274
275
|
services/text_index.py,sha256=r3o4CobTG9jAO9PWazgbWYLY9oi_FgEJ3xwEXrF4KM0,2783
|
|
275
276
|
services/time_tracker.py,sha256=sZNz8QAYpWhCBwfmvopVyxpMb8H-CrBT25C0_VOT1LY,11056
|
|
276
277
|
services/tool_classifier.py,sha256=Fgvq0ZcpnCskwtO8a3YI1MiecPNnw6UbPyJQIUwgfiQ,6512
|
|
@@ -280,6 +281,7 @@ services/vector_store.py,sha256=satc4MJanC1Wsu_iveMCUXG6K2fd0Ke87JSyy2BMikw,1304
|
|
|
280
281
|
services/watcher.py,sha256=xsMlXy4VxX5_CXGQDfmOeZrvlU42S2mGubZgTheCS_s,7859
|
|
281
282
|
services/win_subprocess.py,sha256=nt8Uwc2PrqsRW3-jcViEsoKj56GudW8TV3CQwryx8y4,4271
|
|
282
283
|
services/bench/__init__.py,sha256=WLEJIWJeaUj6FnH2nDO1qWugJDKfOKeM6WvKLSreYjI,231
|
|
284
|
+
services/bench/map_eval.py,sha256=i0M3hBJQM9wXX4dMB_2LjVKoFtXzFUKapoqjGB_8tcE,43456
|
|
283
285
|
services/bench/search_eval.py,sha256=HjVDoIs_uTvNhbT_YjhNjCPEweUG5goaUDaipOcjuF4,29234
|
|
284
286
|
services/bench/shell_eval.py,sha256=YW4qFO6JwmHuv-7_aaQG4QcDpXqnSEZ5uqFDigzTo_8,26723
|
|
285
287
|
services/bench/external/__init__.py,sha256=XgPS99ztx9igMd6x-1bLykcaePGXctkb6ujQ1MLgKAs,608
|
|
@@ -303,8 +305,8 @@ tui/screens/search_view.py,sha256=MMHjVdlk3HZSuDBSvq8IGrqv_Mh5Us6YqXQ80bcWSMk,19
|
|
|
303
305
|
tui/screens/session_view.py,sha256=eZ1eDwHTvPOck1wCCviixtOaCxIkBT_95ytNNNriGNA,5991
|
|
304
306
|
tui/screens/stats.py,sha256=p81PjzdaIv7hllb8f45-rlVe4lJZwSdIMqu7e86_u5s,6223
|
|
305
307
|
tui/screens/ui_view.py,sha256=1QJCgLh2YfgWIpvzRG1KOGXYEaOYX6ojN61Azjf2oX0,2125
|
|
306
|
-
code_context_control-2.
|
|
307
|
-
code_context_control-2.
|
|
308
|
-
code_context_control-2.
|
|
309
|
-
code_context_control-2.
|
|
310
|
-
code_context_control-2.
|
|
308
|
+
code_context_control-2.121.0.dist-info/METADATA,sha256=bLL-hxMFWw55dYOgBSwJqDy_Uq73ZXh1ZUmievzSMOU,27067
|
|
309
|
+
code_context_control-2.121.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
310
|
+
code_context_control-2.121.0.dist-info/entry_points.txt,sha256=7kX_WUsDCF2hbXzvbNyscyaBb9AeA-DJY5v_5hN0DlU,93
|
|
311
|
+
code_context_control-2.121.0.dist-info/top_level.txt,sha256=wRt41zBybVF3qAiNXHz9BURbkKvUvfhmWWtKMhaw6eE,29
|
|
312
|
+
code_context_control-2.121.0.dist-info/RECORD,,
|
services/agents.py
CHANGED
|
@@ -923,29 +923,10 @@ class FileMemoryAgent(BackgroundAgent):
|
|
|
923
923
|
completed = []
|
|
924
924
|
failed = []
|
|
925
925
|
for rel_path in files_to_process:
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
#
|
|
929
|
-
|
|
930
|
-
record = self.file_memory.get(rel_path)
|
|
931
|
-
section_names = ""
|
|
932
|
-
if record:
|
|
933
|
-
names = [s.get("name", "") for s in record.get("sections", [])
|
|
934
|
-
if s.get("type") not in ("import", "decorator")]
|
|
935
|
-
section_names = ", ".join(names[:10])
|
|
936
|
-
|
|
937
|
-
if section_names:
|
|
938
|
-
ai_summary = self._ai_generate(
|
|
939
|
-
f"File: {rel_path}\n"
|
|
940
|
-
f"Symbols: {section_names}\n\n"
|
|
941
|
-
f"Describe the purpose of this file and its main symbols in 1-2 concise sentences. "
|
|
942
|
-
f"Focus on what they do, not how they are implemented.",
|
|
943
|
-
system="You are a senior architect summarizing code for an AI assistant. "
|
|
944
|
-
"Be technical, concise, and highlight the main responsibilities of the symbols.",
|
|
945
|
-
max_tokens=100,
|
|
946
|
-
)
|
|
947
|
-
|
|
948
|
-
result = self.file_memory.update(rel_path, ai_summary=ai_summary)
|
|
926
|
+
# No generated summary: it was produced from symbol names alone
|
|
927
|
+
# and half of the stored ones were cut mid-sentence (2026-09-06).
|
|
928
|
+
# The map carries symbols and ranges only (docs/file-map.md).
|
|
929
|
+
result = self.file_memory.update(rel_path)
|
|
949
930
|
if result:
|
|
950
931
|
updated_count += 1
|
|
951
932
|
completed.append(rel_path)
|