Skip to content

Commit ccee60f

Browse files
jgravelleclaude
andcommitted
v1.108.82: exclude org_savings.db from list_repos
The team-SKU org-rollup store (org_savings.db, written by org/store.py) lives in ~/.code-index/ alongside per-repo index .db files, but _NON_REPO_DB_FILES only excluded telemetry.db. So list_repos enumerated org_savings.db as a phantom local/org_savings repo (symbol_count 0, empty source_root) — an un-removable sym-0 card in the jMunch Console cockpit whose delete-index failed (no matching index), surfaced misleadingly as "needs jcodemunch-mcp >= 1.108.50". - _NON_REPO_DB_FILES now also excludes org_savings.db, so the org-rollup store is no longer phantom-resolved as a repo (nor schema-clobbered by _connect()). tests/test_telemetry_db_skip.py::test_list_repos_skips_org_savings_db. No INDEX_VERSION bump. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent eadd8b3 commit ccee60f

5 files changed

Lines changed: 53 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,27 @@
22

33
All notable changes to jcodemunch-mcp are documented here.
44

5+
## [1.108.82] - 2026-06-24 - Exclude org_savings.db from list_repos
6+
7+
The team-SKU org-rollup store (`org_savings.db`, written by `org/store.py`) lives
8+
in `~/.code-index/` alongside per-repo index `.db` files, but `list_repos` only
9+
excluded `telemetry.db`. So `org_savings.db` was enumerated as a phantom
10+
`local/org_savings` repo (symbol_count 0, empty source_root) — it showed an
11+
un-removable sym-0 card in the jMunch Console cockpit, and `delete-index` on it
12+
failed (no matching index), which the Console reported as a misleading
13+
"needs jcodemunch-mcp >= 1.108.50".
14+
15+
### Changed
16+
17+
- **`_NON_REPO_DB_FILES` now also excludes `org_savings.db`** (alongside
18+
`telemetry.db`), so the org-rollup store is no longer phantom-resolved as a
19+
repo (and never gets the code-index schema auto-initialised onto it by
20+
`_connect()`).
21+
22+
### Tests
23+
24+
- `tests/test_telemetry_db_skip.py::test_list_repos_skips_org_savings_db`.
25+
526
## [1.108.81] - 2026-06-24 - Lazy git identity probe in get_watch_status / list-repos
627

728
`list-repos` (and `get_watch_status`) grew slow on hosts with many indexed repos

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# jcodemunch-mcp — Project Brief
22

33
## Current State
4+
- **Version:** 1.108.82 — Exclude `org_savings.db` from `list_repos`. The team-SKU org-rollup store (`org_savings.db`, written by `org/store.py`) sits in `~/.code-index/` next to per-repo index `.db` files, but `_NON_REPO_DB_FILES` only excluded `telemetry.db` → `list_repos` enumerated it as a phantom `local/org_savings` repo (sym 0, empty source_root, display==repo_id). In the jMunch Console it showed an un-removable card; `delete-index local/org_savings` failed (no matching `local-org_savings.db` index → non-zero exit), which the Console surfaced as the misleading "needs jcodemunch-mcp >= 1.108.50". Fix: `_NON_REPO_DB_FILES = frozenset({"telemetry.db", "org_savings.db"})` (1-line). NO INDEX_VERSION bump. New `tests/test_telemetry_db_skip.py::test_list_repos_skips_org_savings_db`; storage/sqlite_store suites green. Files: `storage/sqlite_store.py`. Console-side companion: the Console's delete-failure message now surfaces jcm's real error instead of the hard-coded version hint (jmunch-console). See [[project_jmunch_console]], [[project_org_rollup_sku]].
45
- **Version:** 1.108.81 — Lazy git identity probe in `get_watch_status` / `list-repos`. `list-repos` (and `get_watch_status`) slowed to 60s+ on many-repo hosts because the per-folder loop resolved each repo's reindex-state key up front via `_reindex_key` → `_local_repo_id` → `resolve_index_identity` — a **git identity probe ≈1 subprocess/repo** (~0.2s each here, worse in worktree-heavy trees; cf. the #303 resolve_repo perf history). But reindex_state is **in-memory only** (populated by a watcher/server process); a cold `list-repos` CLI (the jMunch Console cockpit shells exactly this) has none, so every probe resolved a key only to read an empty default. Fix: new `reindex_state.has_any_reindex_state()` (`bool(_repo_states)` under lock); `get_watch_status` computes `track_reindex = has_any_reindex_state()` ONCE before the loop and skips `_reindex_key`/`get_reindex_status` entirely when False, substituting the default `{index_stale:False, reindex_in_progress:False, stale_since_ms:None}` per repo. **Output shape unchanged** (cold path already returned these defaults — just after paying for N git probes); a watcher/server process WITH state still resolves keys so #353 crash-loop visibility holds. `process_locks.inspect` (OpenProcess, ~0.01s/10) and `service_status` (~0.26s) were measured cheap; the git probe was the sole per-repo scaler. NO INDEX_VERSION bump. New `tests/test_v1_108_81.py` (2: probe skipped + get_reindex_status not consulted when no state; keys resolved when state present); `test_v1_108_78.py`/`test_watch_all.py`/`test_reindex_state.py`/`test_watcher_lock.py` still green. Files: `reindex_state.py`, `tools/get_watch_status.py`. Motivated by the jMunch Console showing fixtures because its 20s CLI timeout lost to this; see [[project_jmunch_console]].
56
- **Version:** 1.108.80 — Dataclass/Pydantic field extraction in outlines (#355, @mmashwani; closes the last issue of the #351-355 batch). `get_file_outline` listed a dataclass as a bare `class` row → agents mistook it for the complete contract. Now **field-centric Python classes emit their fields as `kind="field"` child symbols.** Detection (`_is_field_centric_class` in `parser/extractor.py`): decorator final-name in {dataclass, attrs s/define/frozen/…} (handles call form `@dataclass(frozen=True)` + dotted `@pydantic.dataclasses.dataclass` via `_decorator_final_name`) OR a base class named `BaseModel`/`BaseSettings` (Pydantic, via `_node_final_name` on the class's `argument_list`). `_extract_python_class_fields` walks the class `block` for annotated `assignment` nodes (`left`/`type`/`right` field accessors), emits a `field` symbol per field (name, signature=full `name: type = default` text, line, parent=class id, content_hash). **`ClassVar[...]` skipped** (not a data field); **plain non-field-centric classes untouched** (no constant→field conflation — the reporter's explicit concern). Methods/class symbol unaffected, no dup. Fields flow into outline + `search_symbols(kind="field")` + `get_symbol_source`. Additive, NO INDEX_VERSION bump (re-index picks up fields). Also fixed `summarizer/file_summarize.py` heuristic counting every class child as a method → now `(N methods, M fields)`. Integration point: in `_walk_tree` after the class symbol is appended. New `tests/test_v1_108_80.py` (9: dataclass/frozen/pydantic/attrs extracted, ClassVar skipped, plain untouched, no method dup, outline integration, summary count). Full suite **4939 passed/7 skipped**. Files: `parser/extractor.py`, `summarizer/file_summarize.py`, `LANGUAGE_SUPPORT.md`. **mmashwani #351-355 batch now FULLY CLEARED.**
67
- **Version:** 1.108.79 — Structured credential-file classifier (#351 broad redesign, deferred from .78). Clean-room build in our idiom, validated against the reporter's 85-case oracle ([[feedback_clean_room_innovation]] — NOT the prototype dropped in). New `secret_classifier.py`: pure `classify_secret_file(path, *, disabled_groups, extra_secret_path_patterns, allow_patterns) -> SecretFileDecision(is_secret, reason, group, matched_pattern, confidence, normalized_path)`. `security.is_secret_file` stays the boolean public API, delegates + translates the `exclude_secret_patterns` config (group-slug→disable group; legacy `*secret*`→disable broad+store; other glob→per-pattern allow). **Groups (precedence):** path_specific_credentials (`.aws/credentials`/`.kube/config`/`.docker/config.json`/gcloud-ADC/azure/cargo/gem/composer — a PATH not a basename) → exact_credential_names (expanded: client_secret*/oauth2*/ADC/firebase-adminsdk/token.json/*.agekey) → key_material_directories (private-key ext under keys/certs/ssl/tls/pki — NOT public certs) → credential_extensions (+`*.p8`/`*.ppk`) → secret_store_data (segments expanded to credential/credentials/creds/vault; compound suffixes `*.tfstate.backup`/`*.tfvars.json`/`*.auto.tfvars`) → broad_secret_basename (**token-boundary** `(^|[._-])secrets?($|[._-])` → `prod-secrets.yaml` matches, `secretariat.csv`/`prodsecret.yaml`/`mysecretstuff.csv` do NOT; source+doc+template exempt). **Behavior corrections (all safe-direction):** public SSH keys (`*.pub`) + public certs (`*.crt`/`*.cer`/`*.der`, even under secrets/) NO LONGER secret; template/example/sample/tmpl/dist fixtures exempt; substring-`secret` data files no longer skipped; path-specific cred stores now caught. **Reconciled `tests/test_security.py`:** `id_rsa.pub`→not-secret, `k8s/secrets/tls.crt`→not-secret (public cert), substring cases→not-secret; dropped the unused `SECRET_PATTERNS` import. New `tests/test_secret_classifier.py` (82, oracle-mirrored). `SECURITY.md` rewritten for the group model. Full suite **4930 passed/7 skipped**. **#351 now fully resolved** (core in .78 + broad classifier here). Files: new `secret_classifier.py`; `security.py` (delegates, old `SECRET_PATTERNS`/`_SECRET_*` constants removed), `tests/test_security.py`, `SECURITY.md`.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "jcodemunch-mcp"
3-
version = "1.108.81"
3+
version = "1.108.82"
44
description = "Token-efficient MCP server for source code exploration via tree-sitter AST parsing"
55
readme = "README.md"
66
requires-python = ">=3.10"

src/jcodemunch_mcp/storage/sqlite_store.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,10 @@
194194
# SQLite files in ~/.code-index/ that are NOT per-repo indexes — list_repos
195195
# must skip these so they don't get phantom-resolved as repos (and worse,
196196
# get auto-initialised with the code-index schema by _connect()).
197-
_NON_REPO_DB_FILES = frozenset({"telemetry.db"})
197+
# org_savings.db is the team-SKU org-rollup store (see org/store.py); without
198+
# this it surfaced as a bogus `local/org_savings` card (sym 0, no source_root)
199+
# that could never be deleted.
200+
_NON_REPO_DB_FILES = frozenset({"telemetry.db", "org_savings.db"})
198201

199202
# Keys stored in the meta table
200203
_META_KEYS = [

tests/test_telemetry_db_skip.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,29 @@ def test_bare_name_resolution_survives_telemetry_db(tmp_path):
113113
owner, name = resolve_bare_name("myproj", storage_path=str(store_path))
114114
assert owner == "local"
115115
assert name.startswith("myproj-")
116+
117+
118+
def test_list_repos_skips_org_savings_db(tmp_path):
119+
"""The team-SKU org-rollup store (org_savings.db) must not surface as a
120+
phantom `local/org_savings` repo — it isn't a code index and could never be
121+
deleted, so it showed an un-removable sym-0 card in the console cockpit."""
122+
store_path = tmp_path / "store"
123+
store_path.mkdir()
124+
# A minimal org_savings.db (any schema; list_repos must skip it by name).
125+
conn = sqlite3.connect(str(store_path / "org_savings.db"))
126+
try:
127+
conn.execute("CREATE TABLE IF NOT EXISTS org_savings (org_id TEXT, seat_id TEXT, day TEXT)")
128+
conn.commit()
129+
finally:
130+
conn.close()
131+
132+
project = tmp_path / "myproj"
133+
project.mkdir()
134+
(project / "main.py").write_text("def hello(): return 1\n", encoding="utf-8")
135+
index_folder(str(project), use_ai_summaries=False, storage_path=str(store_path))
136+
137+
repos = IndexStore(base_path=str(store_path)).list_repos()
138+
assert not any("org_savings" in (r.get("repo") or "") for r in repos), (
139+
f"org_savings.db leaked into list_repos: {repos}"
140+
)
141+
assert any(r.get("display_name") == "myproj" for r in repos)

0 commit comments

Comments
 (0)