codecompass-mcp 3.1.0__tar.gz → 3.2.0__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.
Files changed (44) hide show
  1. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/PKG-INFO +1 -1
  2. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/codecompass_mcp.egg-info/PKG-INFO +1 -1
  3. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/graph/code_graph_client.py +42 -7
  4. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/main.py +21 -7
  5. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/pyproject.toml +1 -1
  6. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/tests/test_accuracy_fixes.py +12 -0
  7. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/LICENSE +0 -0
  8. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/README.md +0 -0
  9. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/codecompass_mcp.egg-info/SOURCES.txt +0 -0
  10. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/codecompass_mcp.egg-info/dependency_links.txt +0 -0
  11. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/codecompass_mcp.egg-info/entry_points.txt +0 -0
  12. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/codecompass_mcp.egg-info/requires.txt +0 -0
  13. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/codecompass_mcp.egg-info/top_level.txt +0 -0
  14. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/config.py +0 -0
  15. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/graph/__init__.py +0 -0
  16. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/graph/cli.py +0 -0
  17. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/graph/code_query_cli.py +0 -0
  18. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/graph/setup.py +0 -0
  19. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/ingestion/__init__.py +0 -0
  20. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/ingestion/chunker.py +0 -0
  21. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/ingestion/code_normalizer.py +0 -0
  22. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/ingestion/code_parser.py +0 -0
  23. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/ingestion/description_enricher.py +0 -0
  24. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/ingestion/entity_resolver.py +0 -0
  25. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/ingestion/file_watcher.py +0 -0
  26. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/ingestion/graph_writer.py +0 -0
  27. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/ingestion/hierarchy_builder.py +0 -0
  28. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/ingestion/reader_agent.py +0 -0
  29. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/ingestion/source_context.py +0 -0
  30. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/mcp_server.py +0 -0
  31. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/models/__init__.py +0 -0
  32. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/models/code_types.py +0 -0
  33. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/models/types.py +0 -0
  34. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/setup.cfg +0 -0
  35. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/tests/test_batch_impact.py +0 -0
  36. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/tests/test_blast_radius.py +0 -0
  37. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/tests/test_code_parser.py +0 -0
  38. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/tests/test_description_enricher.py +0 -0
  39. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/tests/test_ingestion.py +0 -0
  40. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/tests/test_lit_css_parser.py +0 -0
  41. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/tests/test_mcp_server.py +0 -0
  42. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/tests/test_php_parser.py +0 -0
  43. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/utils/__init__.py +0 -0
  44. {codecompass_mcp-3.1.0 → codecompass_mcp-3.2.0}/utils/formatting.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecompass-mcp
3
- Version: 3.1.0
3
+ Version: 3.2.0
4
4
  Summary: Structural code context for AI coding agents — a local code knowledge graph for blast radius, impact, deps, dead code, and flow tracing
5
5
  Author: CodeCompass contributors
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codecompass-mcp
3
- Version: 3.1.0
3
+ Version: 3.2.0
4
4
  Summary: Structural code context for AI coding agents — a local code knowledge graph for blast radius, impact, deps, dead code, and flow tracing
5
5
  Author: CodeCompass contributors
6
6
  License: MIT
@@ -60,18 +60,30 @@ def _strip_code_ext(path: str) -> str:
60
60
  return path
61
61
 
62
62
 
63
- def _import_resolves_to(import_str: str, from_file: str, target_file: str) -> bool:
63
+ def _import_resolves_to(import_str: str, from_file: str, target_file: str,
64
+ psr4: dict[str, str] | None = None) -> bool:
64
65
  """True if `import_str` written in `from_file` refers to `target_file`.
65
66
 
66
67
  Resolves relative CommonJS/ESM specifiers (`./response`, `../lib/response`,
67
- with an implicit `/index`) and dotted module names (`lib.response`) against
68
- the target's extension-stripped path. Package imports (`router`, `express`)
69
- never match a project file and return False.
68
+ with an implicit `/index`), dotted module names (`lib.response`), and — via a
69
+ composer PSR-4 map PHP namespaces (`GuzzleHttp.Client` `src/Client.php`)
70
+ against the target's extension-stripped path. Package imports never match a
71
+ project file and return False.
70
72
  """
71
73
  if not import_str or not target_file:
72
74
  return False
73
75
  target_base = _strip_code_ext(target_file).replace("\\", "/")
74
76
 
77
+ # PSR-4 (PHP): namespace prefix -> directory, from composer.json autoload.
78
+ if psr4:
79
+ for prefix, directory in psr4.items():
80
+ pfx = prefix + "."
81
+ if import_str.startswith(pfx):
82
+ rest = import_str[len(pfx):].replace(".", "/")
83
+ base = f"{directory.rstrip('/')}/{rest}".replace("\\", "/")
84
+ if base == target_base:
85
+ return True
86
+
75
87
  if import_str.startswith("."):
76
88
  from_dir = os.path.dirname(from_file)
77
89
  resolved = os.path.normpath(os.path.join(from_dir, import_str)).replace("\\", "/")
@@ -931,10 +943,33 @@ class LocalGraphClient:
931
943
 
932
944
  return results, target_file
933
945
 
946
+ def _psr4(self) -> dict[str, str]:
947
+ """PHP composer PSR-4 map: dotted namespace prefix -> source directory
948
+ (e.g. {'GuzzleHttp': 'src/'}), so `use GuzzleHttp\\Client` resolves to a
949
+ file. Read once from composer.json at the repo root; empty if none."""
950
+ if hasattr(self, "_psr4_cache"):
951
+ return self._psr4_cache
952
+ m: dict[str, str] = {}
953
+ repo = os.path.dirname(os.path.dirname(self.storage_path))
954
+ try:
955
+ with open(os.path.join(repo, "composer.json")) as f:
956
+ data = json.load(f)
957
+ for key in ("autoload", "autoload-dev"):
958
+ for ns, path in (data.get(key, {}).get("psr-4", {}) or {}).items():
959
+ prefix = ns.rstrip("\\").replace("\\", ".")
960
+ paths = path if isinstance(path, list) else [path]
961
+ if prefix and paths:
962
+ m[prefix] = paths[0]
963
+ except Exception:
964
+ pass
965
+ self._psr4_cache = m
966
+ return m
967
+
934
968
  def _direct_importers(self, target_file: str) -> list[str]:
935
969
  """Source files with an IMPORTS edge whose module string resolves to
936
- target_file. Handles both relative (`./response`) and dotted-module
937
- (`lib.response`) import spellings."""
970
+ target_file. Handles relative (`./response`), dotted-module
971
+ (`lib.response`), and PHP PSR-4 namespace (`GuzzleHttp\\Client`) spellings."""
972
+ psr4 = self._psr4()
938
973
  importers: list[str] = []
939
974
  for _u, v, data in self.graph.edges(data=True):
940
975
  if data.get("type") != "IMPORTS":
@@ -943,7 +978,7 @@ class LocalGraphClient:
943
978
  if not from_file:
944
979
  continue
945
980
  import_str = self.graph.nodes[v].get("name", "")
946
- if _import_resolves_to(import_str, from_file, target_file):
981
+ if _import_resolves_to(import_str, from_file, target_file, psr4):
947
982
  importers.append(from_file)
948
983
  return sorted(set(importers))
949
984
 
@@ -26,8 +26,10 @@ _CODECOMPASS_START = "<!-- codecompass-code-graph-start -->"
26
26
  _CODECOMPASS_END = "<!-- codecompass-code-graph-end -->"
27
27
  _CODECOMPASS_READ_INSTRUCTION = (
28
28
  "Orient through the code graph first: start from an entry point, see what's "
29
- "there, then trace its flow and dependencies — rather than blind grepping. "
30
- "Reach for grep/cat when you already know the string or symbol, or to verify."
29
+ "there, then trace its flow and dependencies — never use `cat`, `grep`, or "
30
+ "`rg` to search or read code content. Use the `codecompass query` commands "
31
+ "below for discovery and tracing, then read only the specific slices the "
32
+ "graph points you to."
31
33
  )
32
34
 
33
35
 
@@ -324,6 +326,7 @@ def ingest_code(repo_path: str, normalize: bool = False, dump_triples: str | Non
324
326
  def _register_project_agents_md(repo_path: str) -> None:
325
327
  """Write or update the Code graph section in the project's AGENTS.md."""
326
328
  block = f"""{_CODECOMPASS_START}
329
+ <!-- This file must stay byte-for-byte identical to its counterpart in pi-package/templates/. Run scripts/check-pi-package-sync.sh to verify. -->
327
330
  ## Code graph
328
331
 
329
332
  **{_CODECOMPASS_READ_INSTRUCTION}**
@@ -360,8 +363,18 @@ All commands default to the current directory — run them from the project root
360
363
  3. **Read** the specific slice the graph pointed you to (Read tool / `sed -n`),
361
364
  not the whole file.
362
365
 
363
- 4. **Edit** — and before editing, `--impact`/`--blast-radius` the target so you
364
- don't miss a caller or dependent. After adding/deleting files, re-ingest.
366
+ 4. **Edit** — before editing, verify the target fully so you don't break callers or dependents:
367
+ - Run `--deps <file>` to understand what the file relies on.
368
+ - Run `--flow <entry_symbol> --format json` (or `--flow-summary <entry_symbol>`) to trace the logic end-to-end.
369
+ - Run `--impact <symbol>` for every symbol you plan to change.
370
+ - Run `--blast-radius <file>` for every file you plan to change.
371
+ - Read the specific slices the graph identified.
372
+ - Then make the smallest correct change.
373
+
374
+ After any code change (edits, additions, deletions, renames, refactors), re-ingest so the graph stays current:
375
+ ```bash
376
+ codecompass ingest-code
377
+ ```
365
378
 
366
379
  ### Reading the results
367
380
 
@@ -403,8 +416,9 @@ scripts). Results are split into "likely dead" and (with `--include-entrypoints`
403
416
  "possible entry points".
404
417
 
405
418
  This is STATIC analysis: dynamic dispatch, reflection, and string-based
406
- invocation are invisible. Treat every result as a candidate — grep the name
407
- across the repo to confirm it is truly unused before deleting it.
419
+ invocation are invisible. Treat every result as a candidate — use
420
+ `codecompass query --grep <name>` or `--search <name>` to confirm it is truly
421
+ unused before deleting it.
408
422
 
409
423
  ### Project notes: `overview.md`, `memory.md`, `learnings.md`
410
424
 
@@ -432,7 +446,7 @@ doc → `memory.md`; code-comment warning to the next person → `learnings.md`.
432
446
 
433
447
  ### When to re-ingest
434
448
 
435
- - After adding, renaming, or deleting source files
449
+ - After every code change: edits, additions, deletions, renames, refactors
436
450
  - After major refactors (moved functions, renamed classes)
437
451
  - If query results look stale or incomplete
438
452
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codecompass-mcp"
3
- version = "3.1.0"
3
+ version = "3.2.0"
4
4
  description = "Structural code context for AI coding agents — a local code knowledge graph for blast radius, impact, deps, dead code, and flow tracing"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -339,6 +339,18 @@ def test_python_public_method_marked_exported():
339
339
  assert by_name.get("_private") is False, "underscore method is not exported"
340
340
 
341
341
 
342
+ def test_php_psr4_import_resolution():
343
+ from graph.code_graph_client import _import_resolves_to
344
+ psr4 = {"GuzzleHttp": "src/", "GuzzleHttp.Tests": "tests/"}
345
+ assert _import_resolves_to("GuzzleHttp.Client", "src/Pool.php", "src/Client.php", psr4)
346
+ assert _import_resolves_to("GuzzleHttp.Handler.CurlFactory", "src/x.php",
347
+ "src/Handler/CurlFactory.php", psr4)
348
+ assert _import_resolves_to("GuzzleHttp.Tests.ClientTest", "src/x.php",
349
+ "tests/ClientTest.php", psr4)
350
+ assert not _import_resolves_to("GuzzleHttp.Client", "src/Pool.php", "src/Other.php", psr4)
351
+ assert not _import_resolves_to("Vendor.Lib.Thing", "src/x.php", "src/Client.php", psr4)
352
+
353
+
342
354
  def test_inheritance_super_resolution_all_langs():
343
355
  cases = [
344
356
  (".py", "class Command:\n def invoke(self, c): pass\n"
File without changes