Skip to content

Commit a51c220

Browse files
chore: v0.3.6 — dedup Claude Code registration + contradiction walk
- Extract register_with_claude_code(spec) helper. cli/main.py's install_mcp command and setup_wizard's claude registration step had near-identical blocks for ~/.claude/settings.json I/O. Each caller now constructs the spec dict (HTTP vs stdio) and delegates the load/setdefault/write to the shared helper. - AlertPresenter._check_contradictions now delegates to GraphQueries.contradiction_map(). Removes ~25 lines of duplicated graph traversal. Alert message text now resolves titles consistently from the same source as the graph view. No API change. Patch bump from v0.3.5. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f7539e7 commit a51c220

8 files changed

Lines changed: 53 additions & 66 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.3.6] — 2026-05-03
11+
12+
### Changed
13+
14+
- **Deduplicated Claude Code registration**`cli/main.py::install_mcp` and `cli/setup_wizard.py` had near-identical blocks loading `~/.claude/settings.json`, setting `mcpServers["cortex"]`, and writing back. Extracted to `cli/_helpers.py::register_with_claude_code(spec)`. Each call site now constructs the `spec` dict (HTTP vs stdio) and delegates the file I/O. No behavior change.
15+
- **AlertPresenter delegates to GraphQueries for contradictions**`AlertPresenter._check_contradictions` and `GraphQueries.contradiction_map` walked the graph identically; the alert version was just rewrapping the pair output. AlertPresenter now calls `GraphQueries.contradiction_map()` and formats the result. Removes ~25 lines of duplicated traversal logic; no behavior change beyond a cosmetic shift in alert message text wording (titles now resolved consistently from the same source as the graph view).
16+
1017
## [0.3.5] — 2026-05-03
1118

1219
### Fixed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "abbacus-cortex"
3-
version = "0.3.5"
3+
version = "0.3.6"
44
description = "Cognitive knowledge system with formal ontology, reasoning, and intelligence serving"
55
readme = "README.md"
66
authors = [

src/cortex/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Cortex — Cognitive knowledge system."""
22

3-
__version__ = "0.3.5"
3+
__version__ = "0.3.6"

src/cortex/cli/_helpers.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77

88
from __future__ import annotations
99

10+
import json
11+
from pathlib import Path
12+
from typing import Any
13+
1014
import typer
1115

1216
from cortex.core.config import CortexConfig
@@ -21,3 +25,20 @@ def open_store_or_exit(config: CortexConfig) -> Store:
2125
except StoreLockedError as e:
2226
typer.secho(str(e), fg=typer.colors.RED, err=True)
2327
raise typer.Exit(1) from e
28+
29+
30+
def register_with_claude_code(spec: dict[str, Any]) -> Path:
31+
"""Set ``mcpServers["cortex"] = spec`` in ``~/.claude/settings.json``.
32+
33+
Preserves other entries in the file. Creates the file (and parent
34+
directory) if missing. Returns the path written.
35+
"""
36+
settings_path = Path.home() / ".claude" / "settings.json"
37+
settings: dict[str, Any] = {}
38+
if settings_path.exists():
39+
settings = json.loads(settings_path.read_text())
40+
mcp_servers = settings.setdefault("mcpServers", {})
41+
mcp_servers["cortex"] = spec
42+
settings_path.parent.mkdir(parents=True, exist_ok=True)
43+
settings_path.write_text(json.dumps(settings, indent=2) + "\n")
44+
return settings_path

src/cortex/cli/main.py

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import typer
1414

15-
from cortex.cli._helpers import open_store_or_exit
15+
from cortex.cli._helpers import open_store_or_exit, register_with_claude_code
1616
from cortex.core.config import CortexConfig, load_config
1717
from cortex.core.constants import KNOWLEDGE_TYPES
1818
from cortex.core.errors import StoreLockedError
@@ -775,16 +775,6 @@ def register(
775775
this is the pre-Phase-2 behavior, where Claude Code spawns its own
776776
Cortex stdio child process per session.
777777
"""
778-
import json
779-
780-
settings_path = Path.home() / ".claude" / "settings.json"
781-
settings: dict[str, Any] = {}
782-
783-
if settings_path.exists():
784-
settings = json.loads(settings_path.read_text())
785-
786-
mcp_servers = settings.setdefault("mcpServers", {})
787-
788778
if legacy_stdio:
789779
import shutil
790780

@@ -796,25 +786,14 @@ def register(
796786
cmd = sys.executable
797787
args = ["-m", "cortex.transport.mcp"]
798788

799-
mcp_servers["cortex"] = {
800-
"command": cmd,
801-
"args": args,
802-
"env": {},
803-
}
804-
settings_path.parent.mkdir(parents=True, exist_ok=True)
805-
settings_path.write_text(json.dumps(settings, indent=2) + "\n")
806-
789+
spec = {"command": cmd, "args": args, "env": {}}
790+
settings_path = register_with_claude_code(spec)
807791
typer.echo(f"Registered Cortex MCP (stdio) at {settings_path}")
808792
typer.echo(f" Command: {cmd} {' '.join(args)}")
809793
else:
810794
config = load_config()
811-
mcp_servers["cortex"] = {
812-
"type": "http",
813-
"url": config.mcp_server_url,
814-
}
815-
settings_path.parent.mkdir(parents=True, exist_ok=True)
816-
settings_path.write_text(json.dumps(settings, indent=2) + "\n")
817-
795+
spec = {"type": "http", "url": config.mcp_server_url}
796+
settings_path = register_with_claude_code(spec)
818797
typer.echo(f"Registered Cortex MCP (http) at {settings_path}")
819798
typer.echo(f" URL: {config.mcp_server_url}")
820799
typer.echo(

src/cortex/cli/setup_wizard.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import typer
1919

20-
from cortex.cli._helpers import open_store_or_exit
20+
from cortex.cli._helpers import open_store_or_exit, register_with_claude_code
2121
from cortex.core.config import CortexConfig, load_config
2222
from cortex.core.logging import setup_logging
2323
from cortex.db.store import Store
@@ -370,17 +370,7 @@ def _step_services(config: CortexConfig, env_updates: dict[str, str], auto: bool
370370
)
371371
if register_cc:
372372
try:
373-
settings_path = Path.home() / ".claude" / "settings.json"
374-
settings: dict[str, Any] = {}
375-
if settings_path.exists():
376-
settings = json.loads(settings_path.read_text())
377-
mcp_servers = settings.setdefault("mcpServers", {})
378-
mcp_servers["cortex"] = {
379-
"type": "http",
380-
"url": config.mcp_server_url,
381-
}
382-
settings_path.parent.mkdir(parents=True, exist_ok=True)
383-
settings_path.write_text(json.dumps(settings, indent=2) + "\n")
373+
register_with_claude_code({"type": "http", "url": config.mcp_server_url})
384374
_echo(f" Registered with Claude Code ({config.mcp_server_url})")
385375
except Exception as e:
386376
_echo(f" Registration failed: {e}")

src/cortex/retrieval/presenters.py

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from cortex.core.logging import get_logger
1616
from cortex.db.store import Store
17+
from cortex.retrieval.graph import GraphQueries
1718
from cortex.services.llm import LLMClient
1819

1920
logger = get_logger("retrieval.presenters")
@@ -302,31 +303,20 @@ def render(self) -> list[dict[str, Any]]:
302303

303304
def _check_contradictions(self) -> list[dict[str, Any]]:
304305
"""Find active contradiction relationships."""
305-
alerts = []
306-
seen: set[tuple[str, str]] = set()
307-
308-
all_objects = self.store.list_objects(limit=500)
309-
for obj in all_objects:
310-
obj_id = obj.get("id", "")
311-
rels = self.store.get_relationships(obj_id)
312-
for rel in rels:
313-
if rel["rel_type"] == "contradicts":
314-
pair = tuple(sorted([obj_id, rel["other_id"]]))
315-
if pair not in seen:
316-
seen.add(pair)
317-
alerts.append(
318-
{
319-
"type": "contradiction",
320-
"severity": "high",
321-
"message": (
322-
f"Contradiction between "
323-
f"'{obj.get('title', obj_id[:8])}' "
324-
f"and object {rel['other_id'][:8]}"
325-
),
326-
"object_ids": list(pair),
327-
}
328-
)
329-
return alerts
306+
pairs = GraphQueries(self.store).contradiction_map()
307+
return [
308+
{
309+
"type": "contradiction",
310+
"severity": "high",
311+
"message": (
312+
f"Contradiction between "
313+
f"'{p['title_a'] or p['object_a'][:8]}' "
314+
f"and object {p['object_b'][:8]}"
315+
),
316+
"object_ids": [p["object_a"], p["object_b"]],
317+
}
318+
for p in pairs
319+
]
330320

331321
def _check_patterns(self) -> list[dict[str, Any]]:
332322
"""Detect repeated entity mentions in fixes (systemic issues)."""

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)