codecompass-mcp 2.4.0__tar.gz → 2.5.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.
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/PKG-INFO +1 -1
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/codecompass_mcp.egg-info/PKG-INFO +1 -1
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/codecompass_mcp.egg-info/SOURCES.txt +2 -0
- codecompass_mcp-2.5.0/ingestion/description_enricher.py +189 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/main.py +81 -9
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/pyproject.toml +1 -1
- codecompass_mcp-2.5.0/tests/test_description_enricher.py +95 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/LICENSE +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/README.md +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/codecompass_mcp.egg-info/dependency_links.txt +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/codecompass_mcp.egg-info/entry_points.txt +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/codecompass_mcp.egg-info/requires.txt +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/codecompass_mcp.egg-info/top_level.txt +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/config.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/graph/__init__.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/graph/cli.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/graph/code_graph_client.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/graph/code_query_cli.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/graph/setup.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/ingestion/__init__.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/ingestion/chunker.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/ingestion/code_normalizer.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/ingestion/code_parser.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/ingestion/entity_resolver.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/ingestion/file_watcher.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/ingestion/graph_writer.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/ingestion/hierarchy_builder.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/ingestion/reader_agent.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/ingestion/source_context.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/models/__init__.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/models/code_types.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/models/types.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/setup.cfg +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/tests/test_batch_impact.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/tests/test_blast_radius.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/tests/test_code_parser.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/tests/test_ingestion.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/tests/test_lit_css_parser.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/tests/test_php_parser.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/utils/__init__.py +0 -0
- {codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/utils/formatting.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecompass-mcp
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.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: 2.
|
|
3
|
+
Version: 2.5.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
|
|
@@ -18,6 +18,7 @@ ingestion/__init__.py
|
|
|
18
18
|
ingestion/chunker.py
|
|
19
19
|
ingestion/code_normalizer.py
|
|
20
20
|
ingestion/code_parser.py
|
|
21
|
+
ingestion/description_enricher.py
|
|
21
22
|
ingestion/entity_resolver.py
|
|
22
23
|
ingestion/file_watcher.py
|
|
23
24
|
ingestion/graph_writer.py
|
|
@@ -30,6 +31,7 @@ models/types.py
|
|
|
30
31
|
tests/test_batch_impact.py
|
|
31
32
|
tests/test_blast_radius.py
|
|
32
33
|
tests/test_code_parser.py
|
|
34
|
+
tests/test_description_enricher.py
|
|
33
35
|
tests/test_ingestion.py
|
|
34
36
|
tests/test_lit_css_parser.py
|
|
35
37
|
tests/test_php_parser.py
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Description enrichment for the code knowledge graph — agent-swarm edition.
|
|
2
|
+
|
|
3
|
+
Tree-sitter gives us structure; this pass fills in the meaning. Rather than
|
|
4
|
+
calling any single vendor's API directly, it stages the work as plain files
|
|
5
|
+
and hands control back to whichever coding agent is driving the CLI (Claude
|
|
6
|
+
Code, Codex, Gemini, or anything else). That agent dispatches its own native
|
|
7
|
+
sub-agents — one per batch — to write descriptions, then `describe --apply`
|
|
8
|
+
merges the results into the graph. No API key, no vendor lock-in.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from graph.code_graph_client import get_client
|
|
18
|
+
from ingestion.source_context import extract_entity_context
|
|
19
|
+
|
|
20
|
+
DEFAULT_BATCH_SIZE = 15
|
|
21
|
+
DESCRIBE_SUBDIR = os.path.join(".codecompass", "describe")
|
|
22
|
+
|
|
23
|
+
_INSTRUCTIONS_TEMPLATE = """\
|
|
24
|
+
# Description enrichment — agent swarm task
|
|
25
|
+
|
|
26
|
+
{num_entities} entities across {num_batches} batch file(s) in this directory
|
|
27
|
+
need a one-sentence description written for the code knowledge graph.
|
|
28
|
+
|
|
29
|
+
## What to do
|
|
30
|
+
|
|
31
|
+
1. For each `batch_XXXX.json` file here, dispatch one sub-agent (in parallel).
|
|
32
|
+
Give each sub-agent this task:
|
|
33
|
+
|
|
34
|
+
> Read `{describe_dir}/batch_XXXX.json`. It is a JSON list of entities,
|
|
35
|
+
> each with `id`, `name`, `kind`, `file`, `signature`, `docstring`, and
|
|
36
|
+
> `snippet`. For every entity, write ONE clear sentence describing what
|
|
37
|
+
> it does or its role in the codebase. If signature/docstring/snippet are
|
|
38
|
+
> empty, infer a sensible description from the name, kind, and file path.
|
|
39
|
+
> Write the result to `{describe_dir}/batch_XXXX.result.json` as a JSON
|
|
40
|
+
> object mapping each entity's `id` to its description string. Output
|
|
41
|
+
> nothing else — no markdown fences, no commentary, just the file.
|
|
42
|
+
|
|
43
|
+
2. Once every `batch_XXXX.result.json` file exists, run:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
codecompass describe {repo_path} --apply
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This merges every result file into the graph and cleans up this directory.
|
|
50
|
+
|
|
51
|
+
## Batches
|
|
52
|
+
|
|
53
|
+
{batch_list}
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _entity_context(repo_path: str, node_id: str, attr: dict[str, Any]) -> dict[str, Any]:
|
|
58
|
+
"""Build the sub-agent payload for a single entity."""
|
|
59
|
+
name = attr.get("name", "")
|
|
60
|
+
file = attr.get("file", "")
|
|
61
|
+
ctx = extract_entity_context(repo_path, file, name) if file else {}
|
|
62
|
+
return {
|
|
63
|
+
"id": node_id,
|
|
64
|
+
"name": name,
|
|
65
|
+
"kind": attr.get("kind", ""),
|
|
66
|
+
"file": file,
|
|
67
|
+
"signature": ctx.get("signature", ""),
|
|
68
|
+
"docstring": ctx.get("docstring", ""),
|
|
69
|
+
"snippet": ctx.get("snippet", ""),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _describe_dir(repo_path: str) -> str:
|
|
74
|
+
return os.path.join(os.path.abspath(repo_path), DESCRIBE_SUBDIR)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def prepare_describe_batches(
|
|
78
|
+
repo_path: str, batch_size: int = DEFAULT_BATCH_SIZE, force: bool = False
|
|
79
|
+
) -> dict[str, Any]:
|
|
80
|
+
"""Stage entities needing descriptions into batch files plus an instructions doc.
|
|
81
|
+
|
|
82
|
+
Returns {"num_entities", "num_batches", "describe_dir", "instructions_path"}.
|
|
83
|
+
Writing no batches (num_entities == 0) leaves no files behind.
|
|
84
|
+
|
|
85
|
+
Raises RuntimeError if a previous run already has result files staged —
|
|
86
|
+
re-running would silently discard that in-progress work. Pass force=True
|
|
87
|
+
to overwrite anyway.
|
|
88
|
+
"""
|
|
89
|
+
repo_path = os.path.abspath(repo_path)
|
|
90
|
+
project = os.path.basename(repo_path)
|
|
91
|
+
describe_dir = _describe_dir(repo_path)
|
|
92
|
+
|
|
93
|
+
if not force and os.path.isdir(describe_dir):
|
|
94
|
+
existing_results = [f for f in os.listdir(describe_dir) if f.endswith(".result.json")]
|
|
95
|
+
if existing_results:
|
|
96
|
+
raise RuntimeError(
|
|
97
|
+
f"{len(existing_results)} result file(s) already staged at {describe_dir} "
|
|
98
|
+
"from a previous describe run. Run `codecompass describe "
|
|
99
|
+
f"{repo_path} --apply` to finish it first, or re-run with force=True "
|
|
100
|
+
"to discard them and restage."
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
client = get_client(repo_path)
|
|
104
|
+
try:
|
|
105
|
+
entities = [
|
|
106
|
+
(node_id, attr)
|
|
107
|
+
for node_id, attr in client.graph.nodes(data=True)
|
|
108
|
+
if attr.get("type") == "Entity"
|
|
109
|
+
and attr.get("project") == project
|
|
110
|
+
and attr.get("file")
|
|
111
|
+
]
|
|
112
|
+
finally:
|
|
113
|
+
client.close()
|
|
114
|
+
|
|
115
|
+
result = {
|
|
116
|
+
"num_entities": len(entities),
|
|
117
|
+
"num_batches": 0,
|
|
118
|
+
"describe_dir": describe_dir,
|
|
119
|
+
"instructions_path": None,
|
|
120
|
+
}
|
|
121
|
+
if not entities:
|
|
122
|
+
return result
|
|
123
|
+
|
|
124
|
+
os.makedirs(describe_dir, exist_ok=True)
|
|
125
|
+
|
|
126
|
+
batches = [entities[i : i + batch_size] for i in range(0, len(entities), batch_size)]
|
|
127
|
+
batch_names = []
|
|
128
|
+
for idx, batch in enumerate(batches):
|
|
129
|
+
batch_name = f"batch_{idx:04d}.json"
|
|
130
|
+
payload = [_entity_context(repo_path, node_id, attr) for node_id, attr in batch]
|
|
131
|
+
with open(os.path.join(describe_dir, batch_name), "w", encoding="utf-8") as f:
|
|
132
|
+
json.dump(payload, f, ensure_ascii=False, indent=2)
|
|
133
|
+
batch_names.append(batch_name)
|
|
134
|
+
|
|
135
|
+
instructions_path = os.path.join(describe_dir, "INSTRUCTIONS.md")
|
|
136
|
+
with open(instructions_path, "w", encoding="utf-8") as f:
|
|
137
|
+
f.write(
|
|
138
|
+
_INSTRUCTIONS_TEMPLATE.format(
|
|
139
|
+
num_entities=len(entities),
|
|
140
|
+
num_batches=len(batches),
|
|
141
|
+
describe_dir=describe_dir,
|
|
142
|
+
repo_path=repo_path,
|
|
143
|
+
batch_list="\n".join(f"- {name}" for name in batch_names),
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
result["num_batches"] = len(batches)
|
|
148
|
+
result["instructions_path"] = instructions_path
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def apply_describe_results(repo_path: str) -> int:
|
|
153
|
+
"""Merge every `batch_*.result.json` file into the graph and clean up.
|
|
154
|
+
|
|
155
|
+
Returns the number of nodes updated. Raises FileNotFoundError if no
|
|
156
|
+
staged batch directory exists (nothing to apply).
|
|
157
|
+
"""
|
|
158
|
+
repo_path = os.path.abspath(repo_path)
|
|
159
|
+
describe_dir = _describe_dir(repo_path)
|
|
160
|
+
if not os.path.isdir(describe_dir):
|
|
161
|
+
raise FileNotFoundError(
|
|
162
|
+
f"No staged description batches found at {describe_dir}. "
|
|
163
|
+
"Run `codecompass describe <repo_path>` first."
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
descriptions: dict[str, str] = {}
|
|
167
|
+
result_files = sorted(f for f in os.listdir(describe_dir) if f.endswith(".result.json"))
|
|
168
|
+
for name in result_files:
|
|
169
|
+
with open(os.path.join(describe_dir, name), encoding="utf-8") as f:
|
|
170
|
+
try:
|
|
171
|
+
descriptions.update(json.load(f))
|
|
172
|
+
except json.JSONDecodeError:
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
client = get_client(repo_path)
|
|
176
|
+
try:
|
|
177
|
+
updated = 0
|
|
178
|
+
for node_id, description in descriptions.items():
|
|
179
|
+
if node_id in client.graph:
|
|
180
|
+
client.graph.nodes[node_id]["description"] = description
|
|
181
|
+
updated += 1
|
|
182
|
+
client.save()
|
|
183
|
+
finally:
|
|
184
|
+
client.close()
|
|
185
|
+
|
|
186
|
+
import shutil
|
|
187
|
+
|
|
188
|
+
shutil.rmtree(describe_dir, ignore_errors=True)
|
|
189
|
+
return updated
|
|
@@ -92,32 +92,41 @@ def init_project(repo_path: str) -> None:
|
|
|
92
92
|
_register_project_agents_md(repo_path)
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
_GITIGNORE_ENTRIES = [
|
|
96
|
+
(".codecompass/describe/", "# CodeCompass transient describe-swarm staging dir"),
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
|
|
95
100
|
def _ensure_gitignore(repo_path: str) -> None:
|
|
96
|
-
"""Add
|
|
97
|
-
entry = ".codecompass/graph.json"
|
|
101
|
+
"""Add generated/transient CodeCompass artifacts to .gitignore (notes are kept)."""
|
|
98
102
|
gitignore_path = os.path.join(repo_path, ".gitignore")
|
|
99
103
|
|
|
100
104
|
lines = []
|
|
101
105
|
if os.path.exists(gitignore_path):
|
|
102
106
|
with open(gitignore_path) as f:
|
|
103
107
|
lines = f.read().splitlines()
|
|
104
|
-
|
|
105
|
-
|
|
108
|
+
|
|
109
|
+
missing = [(entry, comment) for entry, comment in _GITIGNORE_ENTRIES
|
|
110
|
+
if not any(line.strip() == entry for line in lines)]
|
|
111
|
+
if not missing:
|
|
112
|
+
return
|
|
106
113
|
|
|
107
114
|
with open(gitignore_path, "a") as f:
|
|
108
115
|
if lines and lines[-1].strip():
|
|
109
116
|
f.write("\n")
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
for entry, comment in missing:
|
|
118
|
+
f.write(comment + "\n")
|
|
119
|
+
f.write(entry + "\n")
|
|
112
120
|
|
|
113
121
|
|
|
114
|
-
def ingest_code(repo_path: str, normalize: bool = False, dump_triples: str | None = None) -> None:
|
|
122
|
+
def ingest_code(repo_path: str, normalize: bool = False, dump_triples: str | None = None, describe: bool = False) -> None:
|
|
115
123
|
"""Ingest a codebase into the local code knowledge graph.
|
|
116
124
|
|
|
117
125
|
Phase 1: Walk the repo and write the Project → Folder → File skeleton.
|
|
118
126
|
Phase 2: Parse every source file with tree-sitter into CodeTriples.
|
|
119
127
|
Phase 3: Normalize entity names via Haiku (only if --normalize is passed).
|
|
120
128
|
Phase 4: Write all triples to the local graph.json.
|
|
129
|
+
Phase 5 (optional): Stage entity descriptions for an agent swarm to fill in.
|
|
121
130
|
"""
|
|
122
131
|
import json
|
|
123
132
|
|
|
@@ -185,6 +194,20 @@ def ingest_code(repo_path: str, normalize: bool = False, dump_triples: str | Non
|
|
|
185
194
|
)
|
|
186
195
|
_register_project_agents_md(repo_path)
|
|
187
196
|
|
|
197
|
+
if describe:
|
|
198
|
+
from ingestion.description_enricher import prepare_describe_batches
|
|
199
|
+
console.print("[dim]Phase 5/5 — Staging descriptions for the agent swarm…[/]")
|
|
200
|
+
staged = prepare_describe_batches(repo_path)
|
|
201
|
+
if staged["num_entities"] == 0:
|
|
202
|
+
console.print("[dim] Nothing to describe.[/]")
|
|
203
|
+
else:
|
|
204
|
+
console.print(
|
|
205
|
+
f"[bold green]Staged[/] {staged['num_entities']} entities in "
|
|
206
|
+
f"{staged['num_batches']} batch(es). Read {staged['instructions_path']} "
|
|
207
|
+
"and dispatch a sub-agent per batch, then run "
|
|
208
|
+
f"`codecompass describe {repo_path} --apply`."
|
|
209
|
+
)
|
|
210
|
+
|
|
188
211
|
|
|
189
212
|
def _register_project_agents_md(repo_path: str) -> None:
|
|
190
213
|
"""Write or update the Code graph section in the project's AGENTS.md."""
|
|
@@ -286,6 +309,18 @@ doc → `memory.md`; code-comment warning to the next person → `learnings.md`.
|
|
|
286
309
|
- After adding, renaming, or deleting source files
|
|
287
310
|
- After major refactors (moved functions, renamed classes)
|
|
288
311
|
- If query results look stale or incomplete
|
|
312
|
+
|
|
313
|
+
### Description enrichment — user-triggered ONLY
|
|
314
|
+
|
|
315
|
+
`codecompass describe` (and `ingest-code --describe`) stage entity descriptions
|
|
316
|
+
for an agent swarm to fill in (see `.codecompass/describe/INSTRUCTIONS.md` when
|
|
317
|
+
staged). This is expensive and **must only run when the user explicitly asks**
|
|
318
|
+
for descriptions to be added or refreshed (e.g. "describe this codebase",
|
|
319
|
+
"add descriptions", "enrich the graph").
|
|
320
|
+
|
|
321
|
+
**Do NOT run `describe` automatically** after re-ingesting, editing files, or
|
|
322
|
+
any other routine step — routine re-ingestion is `codecompass ingest-code`
|
|
323
|
+
with no `--describe` flag.
|
|
289
324
|
{_CODECOMPASS_END}"""
|
|
290
325
|
|
|
291
326
|
agents_md_path = os.path.join(repo_path, "AGENTS.md")
|
|
@@ -356,7 +391,8 @@ def main():
|
|
|
356
391
|
usage = (
|
|
357
392
|
f"[bold]Usage:[/]\n"
|
|
358
393
|
f" {prog} init [italic]<repo_path>[/]\n"
|
|
359
|
-
f" {prog} ingest-code [italic]<repo_path>[/] [--normalize] [--dump-triples [italic]<out.json>[/]]\n"
|
|
394
|
+
f" {prog} ingest-code [italic]<repo_path>[/] [--normalize] [--dump-triples [italic]<out.json>[/]] [--describe]\n"
|
|
395
|
+
f" {prog} describe [italic]<repo_path>[/] [--batch-size N] [--force] [--apply]\n"
|
|
360
396
|
f" {prog} query [italic]<--flag> <arg> <repo_path>[/]\n"
|
|
361
397
|
f" {prog} load-triples [italic]<triples.json> <repo_path>[/]\n"
|
|
362
398
|
f" {prog} watch [italic]<repo_path>[/]"
|
|
@@ -377,12 +413,48 @@ def main():
|
|
|
377
413
|
non_flag_args = [a for a in args if not a.startswith("--")]
|
|
378
414
|
repo_path = non_flag_args[0] if non_flag_args else "."
|
|
379
415
|
normalize = "--normalize" in args
|
|
416
|
+
describe = "--describe" in args
|
|
380
417
|
dump_triples = None
|
|
381
418
|
if "--dump-triples" in args:
|
|
382
419
|
idx = args.index("--dump-triples")
|
|
383
420
|
if idx + 1 < len(args):
|
|
384
421
|
dump_triples = args[idx + 1]
|
|
385
|
-
ingest_code(repo_path, normalize=normalize, dump_triples=dump_triples)
|
|
422
|
+
ingest_code(repo_path, normalize=normalize, dump_triples=dump_triples, describe=describe)
|
|
423
|
+
|
|
424
|
+
elif command == "describe":
|
|
425
|
+
args = sys.argv[2:]
|
|
426
|
+
repo_path = "."
|
|
427
|
+
batch_size = 15
|
|
428
|
+
apply = "--apply" in args
|
|
429
|
+
force = "--force" in args
|
|
430
|
+
i = 0
|
|
431
|
+
while i < len(args):
|
|
432
|
+
a = args[i]
|
|
433
|
+
if a == "--batch-size" and i + 1 < len(args):
|
|
434
|
+
batch_size = int(args[i + 1])
|
|
435
|
+
i += 2
|
|
436
|
+
elif a in ("--apply", "--force"):
|
|
437
|
+
i += 1
|
|
438
|
+
else:
|
|
439
|
+
repo_path = a
|
|
440
|
+
i += 1
|
|
441
|
+
|
|
442
|
+
if apply:
|
|
443
|
+
from ingestion.description_enricher import apply_describe_results
|
|
444
|
+
updated = apply_describe_results(repo_path)
|
|
445
|
+
console.print(f"[bold green]Applied[/] descriptions for {updated} entities.")
|
|
446
|
+
else:
|
|
447
|
+
from ingestion.description_enricher import prepare_describe_batches
|
|
448
|
+
staged = prepare_describe_batches(repo_path, batch_size=batch_size, force=force)
|
|
449
|
+
if staged["num_entities"] == 0:
|
|
450
|
+
console.print("[dim]Nothing to describe.[/]")
|
|
451
|
+
else:
|
|
452
|
+
console.print(
|
|
453
|
+
f"[bold green]Staged[/] {staged['num_entities']} entities in "
|
|
454
|
+
f"{staged['num_batches']} batch(es) at {staged['describe_dir']}.\n"
|
|
455
|
+
f"Read {staged['instructions_path']} and dispatch a sub-agent per "
|
|
456
|
+
f"batch, then run `codecompass describe {repo_path} --apply`."
|
|
457
|
+
)
|
|
386
458
|
|
|
387
459
|
elif command == "load-triples":
|
|
388
460
|
args = sys.argv[2:]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codecompass-mcp"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.5.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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from graph.code_graph_client import get_client
|
|
6
|
+
from ingestion.description_enricher import apply_describe_results, prepare_describe_batches
|
|
7
|
+
from models.code_types import FileNode
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _seed_entity(repo_path, project, entity_id, name="greet"):
|
|
11
|
+
src_dir = repo_path / "src"
|
|
12
|
+
src_dir.mkdir(exist_ok=True)
|
|
13
|
+
(src_dir / "greet.py").write_text('def greet(name):\n return f"Hello {name}"\n')
|
|
14
|
+
|
|
15
|
+
client = get_client(str(repo_path))
|
|
16
|
+
client.merge_project_node(f"{project}:project", project, str(repo_path))
|
|
17
|
+
file_node = FileNode(path="src/greet.py", name="greet.py", extension=".py", depth=1)
|
|
18
|
+
client.merge_file_node(f"{project}:file:src/greet.py", file_node, project)
|
|
19
|
+
client.graph.add_node(
|
|
20
|
+
entity_id,
|
|
21
|
+
type="Entity",
|
|
22
|
+
name=name,
|
|
23
|
+
kind="function:python",
|
|
24
|
+
entity_type="function",
|
|
25
|
+
language="python",
|
|
26
|
+
file="src/greet.py",
|
|
27
|
+
project=project,
|
|
28
|
+
description="old generic description",
|
|
29
|
+
)
|
|
30
|
+
client.save()
|
|
31
|
+
client.close()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_prepare_describe_batches_writes_batch_and_instructions(tmp_path):
|
|
35
|
+
project = "demo"
|
|
36
|
+
repo_path = tmp_path / project
|
|
37
|
+
repo_path.mkdir()
|
|
38
|
+
entity_id = f"{project}:greet"
|
|
39
|
+
_seed_entity(repo_path, project, entity_id)
|
|
40
|
+
|
|
41
|
+
staged = prepare_describe_batches(str(repo_path), batch_size=15)
|
|
42
|
+
|
|
43
|
+
assert staged["num_entities"] == 1
|
|
44
|
+
assert staged["num_batches"] == 1
|
|
45
|
+
describe_dir = repo_path / ".codecompass" / "describe"
|
|
46
|
+
assert (describe_dir / "batch_0000.json").exists()
|
|
47
|
+
assert (describe_dir / "INSTRUCTIONS.md").exists()
|
|
48
|
+
|
|
49
|
+
batch = json.loads((describe_dir / "batch_0000.json").read_text())
|
|
50
|
+
assert batch[0]["id"] == entity_id
|
|
51
|
+
assert batch[0]["name"] == "greet"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_prepare_describe_batches_noop_when_nothing_to_describe(tmp_path):
|
|
55
|
+
project = "empty"
|
|
56
|
+
repo_path = tmp_path / project
|
|
57
|
+
repo_path.mkdir()
|
|
58
|
+
client = get_client(str(repo_path))
|
|
59
|
+
client.merge_project_node(f"{project}:project", project, str(repo_path))
|
|
60
|
+
client.save()
|
|
61
|
+
client.close()
|
|
62
|
+
|
|
63
|
+
staged = prepare_describe_batches(str(repo_path))
|
|
64
|
+
|
|
65
|
+
assert staged["num_entities"] == 0
|
|
66
|
+
assert not (repo_path / ".codecompass" / "describe").exists()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_apply_describe_results_updates_nodes_and_cleans_up(tmp_path):
|
|
70
|
+
project = "demo"
|
|
71
|
+
repo_path = tmp_path / project
|
|
72
|
+
repo_path.mkdir()
|
|
73
|
+
entity_id = f"{project}:greet"
|
|
74
|
+
_seed_entity(repo_path, project, entity_id)
|
|
75
|
+
|
|
76
|
+
staged = prepare_describe_batches(str(repo_path), batch_size=15)
|
|
77
|
+
describe_dir = repo_path / ".codecompass" / "describe"
|
|
78
|
+
result_path = describe_dir / "batch_0000.result.json"
|
|
79
|
+
result_path.write_text(json.dumps({entity_id: "Greets a user by name."}))
|
|
80
|
+
|
|
81
|
+
updated = apply_describe_results(str(repo_path))
|
|
82
|
+
assert updated == 1
|
|
83
|
+
|
|
84
|
+
client = get_client(str(repo_path))
|
|
85
|
+
assert client.graph.nodes[entity_id]["description"] == "Greets a user by name."
|
|
86
|
+
client.close()
|
|
87
|
+
|
|
88
|
+
assert not describe_dir.exists()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_apply_describe_results_without_staged_batches_raises(tmp_path):
|
|
92
|
+
repo_path = tmp_path / "nothing-staged"
|
|
93
|
+
repo_path.mkdir()
|
|
94
|
+
with pytest.raises(FileNotFoundError, match="No staged description batches"):
|
|
95
|
+
apply_describe_results(str(repo_path))
|
|
File without changes
|
|
File without changes
|
{codecompass_mcp-2.4.0 → codecompass_mcp-2.5.0}/codecompass_mcp.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|