ワンクリックで
agentiker-code-intel
agentiker-code-intel — 70 Tools, ~2100 Tests, tools/ + lsp/ Subpackages, LSP 3.18, Git-Tools, Custom-Tools, Coverage 73%
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
agentiker-code-intel — 70 Tools, ~2100 Tests, tools/ + lsp/ Subpackages, LSP 3.18, Git-Tools, Custom-Tools, Coverage 73%
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | agentiker-code-intel |
| description | agentiker-code-intel — 70 Tools, ~2100 Tests, tools/ + lsp/ Subpackages, LSP 3.18, Git-Tools, Custom-Tools, Coverage 73% |
| version | 0.6.10 |
Consolidated from systematic improvement sessions (2026-06-16 through 2026-06-25). Covers: LSP fuzzing, property-based testing, complexity refactoring, LSP tool creation, Tool-Profile-System (5 profiles), nightly watchdog cron, generate_readme updates, CI/CD (.woodpecker.yml), scripts/version_check.py, conftest.py Hermes mock infrastructure, Skill Hub-Split, analysis-plugin sync, v0.6.2: Versionierung auf 0.00.01-Schema. 69 Tools, ~1326 Tests, v0.6.10: 70 Tools, ~2100 Tests, 73% Coverage, Subpackage-Splits, Complexity-Refactoring, and all earlier patterns. Applies after plugin relocation, when adding language support, or before release.
Bug Hunts: references/bughunt-2026-06-18.md (14 Findings),
references/bughunt-2026-06-20.md (9 Findings — TOCTOU Regression, shutdown
Logging Error, fmt-Migration, close_document Race, orphan Events),
references/bughunt-2026-06-25.md (7 Findings — 3 Silent Catches gefixt).
⚠️ Stale-Finding Warning: Findings aus bughunt-2026-06-20.md teilweise veraltet
— 4/10 in v0.3.1 gefixt. Siehe references/bughunt-2026-06-20-verification-2026-06-21.md
für aktuellen Stand.
Regression: references/regression-verification-pattern.md
codebase-audit — systematic codebase assessmentcodebase-intelligence — using the code-intel toolspre-commit-workflow-code-intel — pre-commit hook setupskill-preflight — mandatory preflight checklistTrigger: scripts/health_check.py zeigt "file not found" oder LSP-Subprozesse timeoouten.
Fix (4 Stellen):
PLUGIN_DIR = Path("~/.hermes/plugins/code_intel")sys.path.insert(0, str(PLUGIN_DIR.parent))cwd=str(PLUGIN_DIR) bei subprocess.run()from code_intel.code_intel / code_intel.lsp_bridgeLSP-Subprozess: pyright braucht ["--stdio"] args; goto_definition column ≥ 16.
Trigger: code_definition/code_references fallen für Sprache auf AST-Fallback.
Fix — 4 Stellen prüfen:
_LANGUAGE_SERVERS in lsp_bridge.py_detect_language_for_lsp() in lsp_bridge.py — die lang_map muss Extension→Key abbilden_find_workspace_root() generic_markers (Cargo.toml, go.mod etc.)detect_language() in code_intel.py (extension mapping)🔴 Gatekeeper-Falle: _detect_language_for_lsp() (lsp_bridge.py ~1463) ist der GATEKEEPER. Wenn Extension hier fehlt, wird lang=None und der LSP-Pfad in ALLEN Tools stumm übersprungen (AST-Fallback, kein Log-Hinweis). Rust/Go LSP waren 6 Releases unsichtbar, weil .rs→rust/.go→go hier fehlte. Immer zuerst alle 4 Stellen prüfen.
🔴 TSX/JSX Fallstrick: tree-sitter-typescript + tree-sitter-javascript nicht installiert → code_symbols auf .tsx gibt leere Resultate (Plugin-Code ist korrekt). Fix:
~/.hermes/hermes-agent/venv/bin/pip install tree-sitter-typescript tree-sitter-javascript
Verifikation: $VENV/bin/python3 -c "from tree_sitter import Language; import tree_sitter_typescript as tsts; print('tsx:', Language(tsts.language_tsx()))"
Problem: Jede LSP-Operation hatte dupliziertes if-else-time.sleep().
Fix — zentraler Helper:
def _wait_for_document_ready(self, is_first_request: bool = False) -> None:
if self.language_id in ("typescript", "javascript", ...):
delay = 0.5 if is_first_request else 0.05
elif self.language_id == "python":
delay = 0.05 if is_first_request else 0.01
else: delay = 0.01
time.sleep(delay)
Nach open_document() NUR diesen Helper — nie time.sleep() inline. Ausnahme: workspace_symbol retry (1.0s nach leerem Resultat).
Generator: scripts/generate_readme.py. Datenquellen: plugin.yaml (Version — Single-Source-of-Truth), __init__.py (Tools), lsp_bridge.py (LSP-Sprachen), code_intel.py (AST-Sprachen), CHANGELOG.md, pytest.
🔴 Pitfall: Generator hat KEINE Validierung — sagt "is current" auch bei falschen Quellen. Nie blind vertrauen.
Quick-Check: grep "Version:" README.md | head -1 && grep -oP 'Tools \(\K\d+' README.md | head -1 && grep "Tests:" README.md
Usage: python scripts/generate_readme.py / --check (CI) / --verbose
Details: references/readme-generator-bugs.md (6 bekannte Bugs, 5 Verbesserungen)
Selective Testing:
| Geänderte Datei | Relevante Tests |
|---|---|
code_intel.py | test_code_intel*, test_code_intel_tools* |
lsp_bridge.py | test_lsp_bridge*, test_lsp_lifecycle, test_lsp_manager |
_import_graph.py | test_import_graph*, test_code_cycle_detector*, test_code_dependency_graph* |
__init__.py | test_plugin_init*, test_plugin_init_registry* |
| Mehrere Module | pytest -q -x (voll) |
| Nur Docs/MD | README-Check + Version-Consistency, keine Tests |
Pre-Commit ist blocking: README generieren → bei Erfolg stage → bei Fehler Commit-Abbruch. --no-verify zum Überspringen.
Version-Consistency Check: plugin.yaml == pyproject.toml == CHANGELOG.md
versions = {"plugin.yaml": ..., "pyproject.toml": ..., "CHANGELOG.md": ...}
if len(set(versions.values())) > 1: print("⚠️ VERSION DRIFT")
Context: ivory.green Infrastruktur verwendet Woodpecker CI (.woodpecker.yml), nicht Forgejo Actions. Server läuft auf Hetzner (kein öffentliches Web-UI).
steps:
- name: lint / typecheck / test / release (nur bei tag)
image: python:3.12
commands:
- pip install ruff pyright
- ruff check . && pyright .
- pip install uv && uv pip install --system ".[dev]"
- python -m pytest tests/ -v --tb=short
Pitfalls: Keine runs-on/uses: Syntax — direkt image: + commands:. when: auf Stufen-Ebene für bedingte Ausführung (event: tag). Secrets via Woodpecker UI, Referenz: from_secret: secret_name.
Schema: 0.{major2stell}{minor2stell}.{patch2stell}. Patch zählt +1 pro Release. Bei 0.27.99 → Minor springt auf 28 (0.28.00).
Release-Workflow:
# 1. CHANGELOG.md aktualisieren 2. Version pyproject.toml+plugin.yaml
# 3. python scripts/generate_readme.py 4. git add -A && git commit
# 5. git tag -a vX.Y.Z -m "..." 6. git push && git push --tags
| Ändern | Nicht ändern (zu invasiv) |
|---|---|
pyproject.toml: name + authors | Python-Paketname code_intel (Plugin-ID) — bricht alle Imports |
plugin.yaml: version, author, repo | Verzeichnis ~/.hermes/plugins/code_intel/ |
README.md: Titel, Fork-Notice, Credits | .git/config core.hooksPath |
CHANGELOG.md: Rename-Eintrag | ORIGIN remote (bleibt auf Upstream) |
Git Remote: ivory-git URL aktualisieren |
🔴 Critical: code_intel.py hat 18 eigene toolset="code_intel" Einträge — beide Dateien prüfen: grep -rn 'toolset="code_intel"' code_intel.py lsp_bridge.py
🔴 Test-Assertions patchen: Nach Toolset-Rename schlagen 20+ Assertions in 8 Test-Dateien fehl. Patches zwischen Code-Änderung und Hermes-Neustart ausführen.
Deep Rename (Package-Umbenennung): Siehe references/deep-rename-full.md — 10-Touchpoint-Analyse, Risk-Klassifizierung, Bulk-Replaces.
Problem: go install gopls@latest scheitert auf Go 1.25+ (Build-Fehler in tokeninternal).
Fix: sudo apt-get install -y gopls (v0.16.1+). Keine Code-Änderung nötig — _LANGUAGE_SERVERS hat bereits "go".
Fix: Nach Problembehebung alte Log-Einträge aus ~/.hermes/logs/errors.log entfernen:
head -1 "$LOGFILE" > /tmp/clean && grep -v -i "no lsp bridge\|timeout\|lsp_error\|timed out" "$LOGFILE" >> /tmp/clean && mv /tmp/clean "$LOGFILE"
Alternative: : > ~/.hermes/logs/errors.log && pkill hermes && hermes
cd ~/.hermes/plugins/code_intel
$VENV/bin/python3 -m ruff check --fix . && ruff check --unsafe-fixes --fix . && ruff check . --statistics
Sonderfälle: # noqa: E402 für bedingte Imports (Import nach pytest.importorskip()). # noqa: F401 für Verfügbarkeitsprüfungen — NUR Code, kein freier Text.
Prinzip: Funktionen extrahieren bis keine Sub-Funktion C>10 hat. Jede macht GENAU EINE Sache mit EINEM Rückgabetyp.
Extraktionsregeln: Sub-Funktion C<10, keine shared mutable state, max 4 Sub-Funktionen pro Haupt-Funktion, Duplikate zuerst eliminieren.
🔴 Orphaned-Exception Pitfall: Nach Extraktion von subprocess/I/O Code werden try/except Blöcke in der Parent verwaist → SyntaxError. Nach jeder Extraktion prüfen:
python3 -c "import ast; ast.parse(open('file.py').read()); print('OK')"
Validierung: pytest -q --tb=short && ruff check . --statistics && radon cc
Historie: references/complexity-refactoring-history.md — 11 Phasen, von 19→3 C>15 Hotspots.
Problem: Zwei separate StreamHandler-Instanzen → Byte-Level-Vermischung.
Fix: _logging.py — Singleton get_stderr_handler() + setup_logger() mit logger.propagate = False.
Problem: read_file-Output (1|content) via write_file/patch zurückgeschrieben → embedded Prefixe.
Fix: re.sub(r'^\d+\|', '', content, flags=re.MULTILINE)
Prävention: read_file-Output nie direkt als write_file/patch-Input verwenden.
Settings: suppress_health_check=[HealthCheck.function_scoped_fixture], deadline=None, max_examples=20-50.
Pitfall: .example() NUR für interaktives Erkunden. In @given-Tests data.draw() oder Schleife nutzen.
Steuerung: LSP_TEST=1 → @pytest.mark.skipif(...). Fixtures pro Sprache (py_project, ts_project, go_project).
Pitfalls: code_workspace_symbols_tool(query, path=...) NICHT (project, query=...). LSP-Init 3-5s beim ersten Test. Pro Klasse neuer tmp_path.
Design: hermes cron create --name code-intel-nightly-health --schedule "0 3 * * *" --no-agent. Watchdog: Silent bei Erfolg, loud on failure. Checks: Tests, Ruff, Health, Error-Log (>10/24h → Warnung), Git-Status (uncommitted/unpushed).
Pattern: _make_bridge() mit gemocktem _process + parametrisierte Tests mit MALFORMED_MSGS.
🔴 Real bugs found (v0.27.01): 5 Bugs — _dispatch (NoneType.get ×2), _uri_to_path (None.startswith), _dispatch (str.get), _format_definitions (KeyError). Alle durch isinstance() guards gefixt.
Fix: Jede Notification in eigene _handle_<method>(self, msg: dict) -> None extrahieren. C=24→7. Fuzz-Tests decken alle Zweige ab.
Wann patch() schlägt: Escaped Quotes (\") oder Indentation-Drift → Python via terminal:
content = open('file.py').read()
content = content.replace(old, new, 1) # count==1 vorher prüfen!
open('file.py', 'w').write(content)
🔴 @dataclass Orphaned: Neue Funktion zwischen @dataclass und class LSPBridge: → 280+ Failures. Fix: @dataclass zur Class zurückverschieben.
7-stufige Methode:
__init__.py (Tools), code_intel.py (AST), lsp_bridge.py (LSP)references/code-intel-market-landscape-2026.md, references/codegraph-comparison-2026-06-18.mdLSP 3.18 Gap Analysis: 18/26 Methoden implementiert. Siehe references/lsp-gap-analysis.md.
Problem: _find_workspace_root() bleibt beim Monorepo-package.json statt Sub-Projekt.
Fix: Zwei Durchläufe: (1) Sub-Projekt-Marker (tsconfig.json, next.config.ts, medusa-config.ts) → (2) Nächsten Nicht-Monorepo-Root (überspringt package.json mit "workspaces").
Cache: Dict mit 300s TTL.
🔴 Pitfalls: pyright/tsserver brauchen korrekten Root (falsche tsconfig); Symlinks → Path.resolve() muss reale Pfade liefern.
File: code_intel/_import_graph.py. Methoden: scan(), parse_imports(), parse_all(), find_cycles(), analyze_blast_radius(), find_hot_paths(), to_mermaid(), to_tree().
Sub-Pattern: Neues AST Tool aus ImportGraph (3 Steps):
code_intel.py — ImportGraph importieren, Methode aufrufen, JSON return🔴 Pitfall: _import_graph.py vor Plan-Erstellung prüfen — wurde als existierend angenommen aber fehlte (Phase 0 nachträglich).
Problem: tree-sitter Query.captures() entfernt in v0.23+.
Fix: QueryCursor().matches() — liefert (pattern_idx, captures_dict) Paare statt (node, name) Tupel.
Seit v0.3.0 (2026-06-20): Tools werden NICHT mehr via if registry: registry.register() auf Modulebene registriert (P0 Bug-Fix). Stattdessen:
code_tools.py definierenregistry.register() in code_tools.py einfügen__init__.py._register_ast_tools() das Schema+Handler-Paar in die _AST_TOOL_REGISTRATIONS-Liste eintragentools/analysis.py (oder entsprechendem Modul) für Re-Export hinzufügentools/__init__.py importierenDebug-Print Pitfall: print() wird von pytest ohne -vvs unterdrückt → logging.debug() verwenden.
Seit v0.3.0: Es gibt keine TOOLSETS["code_intel"]-Liste mehr und keine new_tools-Liste.
Die Registrierung läuft zentral in __init__.py._register_ast_tools().
Siehe Pattern 34 (Central Registration).
Trigger: Eine Plugin-Modul-Datei ist über 3.000 Zeilen gewachsen.
Zwei Varianten:
Variante A — Vollständige Extraktion (empfohlen für reine Infrastruktur):
tools/base.py) mit write_file erstellen. _fmt → .. _fmt)__all__ im neuen Modul definierenVariante B — Re-Export Wrapper (sicher für Tool-Funktionen):
tools/search.py) erstellenfrom code_intel.code_tools import code_search_tool__all__ = ["code_search_tool", ...]tools/__init__.py importiert aus allen wrapper-Modulen🔴 Pitfall — Off-by-One bei Zeilenbasierter Extraktion:
Bei sections = {"bridge.py": (0, 1918), "tools.py": (1919, ...)} sind die Werte
1-indexed, aber Python-Listen sind 0-indexed. lines[1918:4730] = Index 1918-4729
= 1-indexed Zeilen 1919-4730. Verwechslung führt zu abgeschnittenen Funktionen.
→ Lösung: Kommentar im Splitter: # tools.py start: line 1919 (1-indexed) = index 1918 (0-indexed)
🔴 Pitfall — Fehlende Imports in extrahierten Modulen:
Wenn du einen Zeilenbereich aus einem Monolithen extrahierst, fehlen die Imports
aus dem Header. Jedes extrahierte Modul braucht eigene from __future__ import annotations,
from typing import ..., und relative Imports (from .._fmt import ...).
🔴 Pitfall — import * exportiert keine _-prefixed Namen + bricht Tests:
Nach einem Subpackage-Split wird oft eine Re-Export-Facade (lsp_bridge.py) als
Wildcard-Import angelegt: from code_intel.lsp.bridge import *. Das exportiert
KEINE Namen mit führendem Unterstrich (_WORKSPACE_ROOT_CACHE, _handle_*).
# lsp_bridge.py — Re-Export Facade
from code_intel.lsp.bridge import * # ❌ Exportiert _WORKSPACE_ROOT_CACHE NICHT
Das killt Tests, die diese _-Symbole importieren:
# tests/test_root_discovery.py
from code_intel.lsp_bridge import _WORKSPACE_ROOT_CACHE # ❌ ImportError
Drei Fix-Optionen (priorisiert):
_-Symbole in lsp_bridge.py einzeln importieren → kein wildcard-Import nötig__all__ in bridge.py/tools.py: _WORKSPACE_ROOT_CACHE in __all__ aufnehmen → import * exportiert es danncode_intel.lsp.bridge statt code_intel.lsp_bridge) — aber das bricht die Abwärtskompatibilität, wenn andere Module auf den Pfad angewiesen sind🔴 Pitfall — monkeypatch.setattr Pfade brechen nach Split:
monkeypatch.setattr("code_intel.code_intel.funcname", ...) und
monkeypatch.setattr("code_intel.lsp_bridge.time.monotonic", ...) brechen
nach dem Split, weil:
code_intel.code_intel als Pfad nicht mehr existiert (jetzt code_tools + tools/)code_intel.lsp_bridge ist kein package mehr (sondern eine Datei)ModuleNotFoundError: No module named 'code_intel.lsp_bridge.time'Lösung: Alle monkeypatch.setattr-Pfade auf die neue Modul-Struktur umstellen:
# ALT (crasht):
monkeypatch.setattr("code_intel.code_intel._cache_key_for_path", mock)
# NEU:
monkeypatch.setattr("code_intel.code_tools._cache_key_for_path", mock)
# Noch besser: direkt auf das Ziel-Modul patchen:
from code_intel import code_tools
monkeypatch.setattr(code_tools, "_cache_key_for_path", mock)
🔴 Pitfall — KEIN Bulk-Replace bei Mock-Pfaden:
patch("code_intel.lsp_bridge.name") via Bulk-Replace durch
patch("code_intel.lsp.bridge.name") zu ersetzen ist gefährlich, weil:
patch.object(_lsp_bridge, "name")) werden fälschlich auf Submodul geändertpatch.object(_lsp_tools, "name")) werden fälschlich auf Submodul geändertJeder Mock-Pfad muss einzeln nach dem 3-Tier-System beurteilt werden
(siehe references/subpackage-split-mock-3-tier.md).
🔴 Pitfall — Production-Code nicht für Test-Kompatibilität ändern:
Wenn ein Test json.loads(tool_result)["key"] macht, aber die Tool-Funktion
fmt_ok({...}) (rich-formatiert) zurückgibt, ist die Versuchung gross,
fmt_ok durch json.dumps zu ersetzen. Nicht machen! fmt_ok ist für
User-Output, json.dumps für interne API. Stattdessen die Test-Expectation
anpassen: assert '"key": value' in str(result).
Trigger: Plugin hat module-level if registry: registry.register() Aufrufe (P0-Sicherheitslücke).
Fix (3 Schritte):
if registry: registry.register(...) Blöcke aus den Modulen löschen
grep -n "if registry: registry.register(" module.py # 21 Stellen in code_tools.py
try: from tools.registry import registry / except: registry = None Pattern_register_ast_tools() in __init__.py:
def _register_ast_tools():
from . import code_tools as ct
from tools.registry import registry
_AST_TOOL_REGISTRATIONS = [
(ct.CODE_X_SCHEMA, ct._handle_code_x),
...
]
for schema, handler in _AST_TOOL_REGISTRATIONS:
try:
registry.register(name=schema["name"], toolset="agentiker_code_intel",
schema=schema, handler=handler, check_fn=ct._check_fn, emoji="🔍")
except Exception as e:
logging.warning(f"Failed: {schema['name']}: {e}")
Vorteil: Wenn registry nicht verfügbar ist, crasht nur die Registration-Funktion,\nnicht der gesamte Plugin-Import. Ein Tool-Fehler killt nicht alle 21 Registrierungen.
🔴 Pitfall — Registry-Tests brechen wenn _register_ast_tools() nicht läuft:\nTests die import code_intel.code_tools aufrufen in der Hoffnung das\n_register_ast_tools() die Tools im MockRegistry registriert, FEHLEN — weil\n_register_ast_tools() nur vom Plugin-Einstiegspunkt (__init__.py::register())\nund nicht beim direkten code_tools-Import aufgerufen wird. Das MockRegistry\nbleibt leer.\n\nFix: Tools direkt im Test registrieren statt auf Auto-Registration zu hoffen:\n\npython\n# ❌ Bricht — _register_ast_tools() wurde nie aufgerufen\nfrom tools.registry import registry\nimport code_intel.code_tools # noqa: F401\nassert \"code_search\" in registry.get_all_tool_names() # → leer\n\n# ✅ Tools explizit registrieren\nfrom tools.registry import registry\nregistry.register(\"code_search\", toolset=\"code_intel\", schema={})\nassert \"code_search\" in registry.get_all_tool_names()\n\n\nPrävention: grep -rn \"import code_intel.code_tools\" tests/ nach jedem\nHinzufügen neuer Registry-Tests prüfen. Tests die auf get_all_tool_names()\noder get_entry() zugreifen, müssen das Tool vorher registrieren.
Trigger: LSP-Integrationstests schlagen fehl weil Mock-Namespace nicht trifft.
Nach einem Subpackage-Split gibt es 3 verschiedene Module auf die Mocks zielen können (Facade, Submodul, Tools-Modul). Falsches Targeting → Mock wird nie aufgerufen → echter LSP-Server läuft.
Ursache: mock.patch("code_intel.lsp_bridge.get_lsp_manager") zielt auf die Facade.
lsp/tools.py importiert get_lsp_manager via from .bridge import get_lsp_manager
auf Modulebene — die Referenz ist im tools-Namespace, nicht in der Facade.
→ Der Mock wirkt nicht, der echte pyright-langserver läuft trotz Mocks.
Vollständige Entscheidungsmatrix siehe: references/subpackage-split-mock-3-tier.md
(3 Tiers: Facade patch.object(), Submodul patch("...lsp.bridge.*"), Tools patch.object())
Kurzfassung:
code_intel.lsp_bridge): patch.object(_lsp_bridge, "name") — für lazy Importscode_intel.lsp.bridge): patch("code_intel.lsp.bridge.name") — für LOAD_GLOBAL in bridge.pycode_intel.lsp.tools): patch.object(_lsp_tools, "name") — für module-level ImportsWeitere Fixes (wenn Mock-Namespace nicht das Problem ist):
assert "error" in result → assert "status" in resultGating: os.environ.get("E2E_TEST") == "1". Läuft nie in CI (10x langsamer).
Kategorien: A — Real-Tool-Calls (14 Tests), B — Cross-Workflows (6), C — Lifecycle (6).
⚠️ Tool-Count: Nach jedem neuen Tool test_e2e_lifecycle.py len(tools)==N + expected-Set aktualisieren.
Details: references/e2e-testing-detailed.md
Tools: code_replace_body, code_safe_delete, code_insert_before, code_insert_after (alle AST-basiert, kein LSP).
Key Helper: _find_symbol_in_ast() — Byte-exakte Boundaries via tree-sitter. 🔴 Method Detection: _classify_symbol_kind() erkennt Methoden nicht → immer _detect_if_method() nachschalten.
Key Helper: _invalidate_cache() nach jedem Write — sonst stale Symbol-Daten.
Safety: Backup-then-Write (.bak). dry_run=True als Default.
🔴 str.replace Kollateral: str.replace() bei Ruff-Fixes matcht in ALLEN Funktionen → 46 Failures. Fix: count == 1 prüfen.
Zwei-Phasen:
\b Word-Boundary RegexSkip-Liste: {"typing", "TYPE_CHECKING", "Any", "Optional", "List", "Dict", "Set", "Tuple"}
Details: references/symbol-editing-tools-detailed.md
Problem: close_document() muss self._lock freigeben bevor es didClose sendet,
weil _send_notification → _write_message ebenfalls self._lock nimmt (Deadlock bei
nicht-reentrant Lock). Aber zwischen Lock-Release und Notification kann open_document()
für die gleiche URI laufen und das Dokument neu öffnen. Die spät eintreffende didClose
Notification killt das frisch geöffnete Dokument.
Ursprünglicher (unvollständiger) Fix — Second-Check mit zweitem Lock:
def close_document(self, file_path: str) -> None:
uri = f"file://{file_path}"
with self._lock: # Lock 1
self._open_documents.discard(uri)
self._closing_uris.add(uri)
self._send_notification("didClose", ...) # Lock frei — RACE!
with self._lock: # Lock 2 — cleanup
self._closing_uris.discard(uri)
Die Race zwischen _send_notification und Lock 2 bleibt bestehen — open_document
kann zwischen Lock 1 Release und Lock 2 Acquisition laufen.
TTL-Guard Fix — Dict mit Timestamp statt Set:
# __init__:
_closing_uris: Dict[str, float] # URI → time.monotonic() timestamp
# close_document() — kein zweiter Lock nötig:
def close_document(self, file_path: str) -> None:
uri = f"file://{file_path}"
with self._lock:
if uri not in self._open_documents:
return
self._open_documents.discard(uri)
self._closing_uris[uri] = time.monotonic() # Timestamp setzen
self._send_notification("didClose", ...)
# _closing_uris Eintrag verfällt automatisch via TTL
# open_document() — TTL-basierter Guard statt Set-Prüfung:
for _wait in range(50): # max ~0.5s spin-wait
with self._lock:
ts = self._closing_uris.get(uri)
if ts is None or (time.monotonic() - ts) > 0.5:
self._closing_uris.pop(uri, None)
break
time.sleep(0.01)
Vorteile:
_closing_uris Einträge verfallen automatisch nach 0.5spop() beim nächsten Zugriff (kein Memory-Leak)_send_notification bleibt ausserhalb des Locks (kein Deadlock mit _write_message)Pitfalls:
time.monotonic() verwenden, nicht time.time() (kein Systemzeit-Problem)Trigger: Wenn LSP-Tests sporadisch "Can't open already open document" oder
URI-Corruption (s4ore statt store) zeigen.
Fundort: Bug-Hunt 2026-06-21 — lsp/bridge.py:1083-1100.
Version: 0.4.0 | Tools: 57 (+14 seit v0.3.1) | Tests: 1307
6 neue LSP 3.18 Methoden in lsp/bridge.py (22 gesamt) + 6 Bridge-Methoden:
| LSP 3.18 Tool | Bridge-Methode | Domain |
|---|---|---|
code_completion | _text_document_completion() | Autovervollständigung |
code_code_lens | _text_document_code_lens() | Code Lenses (Run, Debug, etc.) |
code_folding_range | _text_document_folding_range() | Code-Faltung |
code_selection_range | _text_document_selection_range() | Selektionsbereiche |
code_linked_editing | _text_document_linked_editing() | Gekoppeltes Editieren (CSS-Klassen, JSX-Tags) |
code_prepare_rename | _text_document_prepare_rename() | Prepare-Rename mit Default-Verhalten |
4 neue Git-Tools in tools/git.py:
| Tool | Funktion |
|---|---|
code_todo_finder | Findet TODO/FIXME/HACK/TEMP-Kommentare im Codebase |
code_merge_conflict_finder | Findet Merge-Konflikte (<<<<<<<, =======, >>>>>>>) |
code_git_log_symbol | Git-Log für ein bestimmtes Symbol (Autor, Datum, Message) |
code_git_diff_file | Git-Diff zwischen aktuellen Änderungen und HEAD für eine Datei |
4 neue Custom-Tools in tools/custom.py:
| Tool | Funktion |
|---|---|
code_diagram_symbol | Generiert ASCII/Mermaid-Diagramm für eine Funktion oder Klasse |
code_explain | Erklärt Code-Abschnitt als natürlicher Text (Kontext-optimiert) |
code_docstring_generate | Generiert/fügt Docstrings zu undokumentierten Funktionen |
code_dependency_risk | Bewertet Abhängigkeitsrisiken (zirkuläre Imports, Breite, Tiefe) |
| Profil | Tools | Enthaltene Tool-Typen |
|---|---|---|
| all | 57 | Core + Search + LSP + Git + Custom + Analysis + Style |
| core | 16 | AST-Basis-Tools: symbols, search, definition, references, diagnostics, hover, signatures, type_definition, document_symbols, workspace_symbols, inline_hints, implementations, callers, callees, highlight, format |
| search | 10 | search, search_by_error, workspace_symbols, workspace_summary, unused_finder, duplicates, hot_paths, cycle_detector, metrics, todo_finder |
| lsp | 22 | 16 LSP-Standard + 6 LSP 3.18 (completion, code_lens, folding_range, selection_range, linked_editing, prepare_rename) |
| Kategorie | Tools | Dateien |
|---|---|---|
| AST Core | 16 | code_tools.py, tools/analysis.py |
| LSP Standard | 16 | lsp/tools.py |
| LSP 3.18 | 6 | lsp/tools_318.py |
| Search | 10 | tools/search.py |
| Git | 4 | tools/git.py |
| Custom | 4 | tools/custom.py |
| Style/Format | 1 | tools/style.py |
| Gesamt | 57 |
| Referenz | Inhalt |
|---|---|
references/lsp-tool-pattern.md | 5-Step-Guide für neue LSP-Tools |
references/terminal-output-architecture.md | 10 Display Mechanismen |
references/code-intel-market-landscape-2026.md | Competitive Landscape (14 Tools, Tier 1-4) |
references/codegraph-comparison-2026-06-18.md | CodeGraph Feature-Vergleich (42+27+17 Tools) |
references/security-hardening.md | Security für öffentliches Repo |
references/v0.3.0-restructure-patterns.md | Subpackage Split + Central Registration + LSP Test Fix Patterns |
references/lsp-gap-analysis.md | LSP 3.18 Gap (22/26 Methoden, +6 in v0.4.0) |
references/complexity-refactoring-history.md | 11 Phasen Refactoring, 19→3 C>15 |
references/historical-feature-progress.md | Feature-Plan Status, v0.28.02-v0.29.00 |
references/readme-generator-bugs.md | 6 Generator-Bugs + 5 Verbesserungen |
references/e2e-testing-detailed.md | E2E Test-Code-Beispiele + Pitfalls |
references/symbol-editing-tools-detailed.md | Symbol Editing Code + Unused Import API |
references/regression-verification-pattern.md | Regression-Check nach Threading-Fix |
references/bughunt-2026-06-18.md | 14 Bug-Hunt Findings |
references/bughunt-2026-06-20.md | 9 Bug-Hunt Findings (v0.1.13 Regressions) |
references/bughunt-2026-06-20-verification-2026-06-21.md | ✅ 4/10 Findings aus 2026-06-20 in v0.3.1 gefixt bestätigt |
references/monkeypatch-rexport-pattern.md | Monkeypatch + Re-Export Facade Fixes nach Subpackage-Split |
references/post-restructure-test-regression-2026-06-20.md | 3 Typen von Test-Regression nach Subpackage-Split |
references/subpackage-split-mock-3-tier.md | 3-Tier Mock-System: Facade/Submodul/Tools-Modul + Bulk-Replace-Fallstricke + Test-Isolation |
references/bughunt-2026-06-24-skill-triage.md | Bug-Hunt 2026-06-24: performance, silent catch, typing fixes |
| Version | Tools | Tests | Coverage | Änderung |
|---|---|---|---|---|
| 0.6.5 | 70 | ~1356 | — | 5 Complexity-Hotspots refactored (C>30 auf ~15-20), hooks.py Auslagerung |
| 0.6.6 | 70 | ~1356 | — | lsp/bridge.py Monolith Split — discovery.py extrahiert (−248 Zeilen) |
| 0.6.7 | 70 | ~1356 | — | lsp/tools_core.py Split — call_hierarchy.py + heuristics.py extrahiert |
| 0.6.8 | 70 | ~1356 | — | Coverage-Measurement eingerichtet |
| 0.6.9 | 70 | ~1924 | — | 3 grosse Test-Dateien in 8 modulare Dateien gesplittet |
| 0.6.10 | 70 | ~2100 | ~73% | Scripts aufgeräumt, xdist -n 4, Bug-Hunt (3 Silent Catches gefixt) |
code_metrics_tool (C=37→18), code_complexity_tool (C=37→22), code_duplicates_tool (C=39→10), code_pr_impact_tool (C=39→25), _ast_type_hierarchy_subtypes (C=36→10)_find_workspace_root, _find_tsconfig_root, etc.)hooks.py (C=35→~10), call_hierarchy.py (C=33→~12), ast_edit.py (C=32→~10)| Pattern | Beschreibung |
|---|---|
| Pattern 37: Complexity Refactoring (Extract-Select-Format) | 5-Step-Prozess: Complexity-Messung → Extract-Inline-Logik → Select-Sub-Expression → Format → Verify |
| Pattern 38: Coverage Campaign | Systematischer Workflow: Baseline messen → Subagenten für parallelle Test-Erstellung → Verify mit pytest --cov |