codecompass-mcp 2.3.0__tar.gz → 2.4.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.3.0 → codecompass_mcp-2.4.0}/PKG-INFO +1 -1
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/codecompass_mcp.egg-info/PKG-INFO +1 -1
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/codecompass_mcp.egg-info/SOURCES.txt +1 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/graph/code_graph_client.py +2 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/graph/code_query_cli.py +5 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/ingestion/code_normalizer.py +2 -1
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/ingestion/code_parser.py +356 -39
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/models/code_types.py +2 -1
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/pyproject.toml +1 -1
- codecompass_mcp-2.4.0/tests/test_php_parser.py +217 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/LICENSE +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/README.md +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/codecompass_mcp.egg-info/dependency_links.txt +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/codecompass_mcp.egg-info/entry_points.txt +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/codecompass_mcp.egg-info/requires.txt +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/codecompass_mcp.egg-info/top_level.txt +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/config.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/graph/__init__.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/graph/cli.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/graph/setup.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/ingestion/__init__.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/ingestion/chunker.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/ingestion/entity_resolver.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/ingestion/file_watcher.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/ingestion/graph_writer.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/ingestion/hierarchy_builder.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/ingestion/reader_agent.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/ingestion/source_context.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/main.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/models/__init__.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/models/types.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/setup.cfg +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/tests/test_batch_impact.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/tests/test_blast_radius.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/tests/test_code_parser.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/tests/test_ingestion.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/tests/test_lit_css_parser.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.0}/utils/__init__.py +0 -0
- {codecompass_mcp-2.3.0 → codecompass_mcp-2.4.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.4.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.4.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
|
|
@@ -17,11 +17,13 @@ from models.code_types import CodeTriple, FileNode, FolderNode
|
|
|
17
17
|
_EXT_TO_LANGUAGE = {
|
|
18
18
|
".py": "python",
|
|
19
19
|
".js": "javascript",
|
|
20
|
+
".jsx": "javascript",
|
|
20
21
|
".ts": "typescript",
|
|
21
22
|
".tsx": "typescript",
|
|
22
23
|
".html": "html",
|
|
23
24
|
".css": "css",
|
|
24
25
|
".scss": "scss",
|
|
26
|
+
".php": "php",
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
_ALLOWED_REL_TYPES = frozenset({
|
|
@@ -44,7 +44,8 @@ Your job:
|
|
|
44
44
|
POSTS_TO, INCLUDES, USED_BY, OVERRIDES, RAISES, RETURNS_TYPE.
|
|
45
45
|
3. Correct entity types if obviously wrong.
|
|
46
46
|
Allowed types: function, class, module, css_selector, html_element,
|
|
47
|
-
scss_mixin, scss_variable, endpoint, css_class, file
|
|
47
|
+
scss_mixin, scss_variable, endpoint, css_class, file, interface, trait,
|
|
48
|
+
enum, property, constant.
|
|
48
49
|
|
|
49
50
|
Return the same JSON array with corrections applied.
|
|
50
51
|
Do NOT add, remove, or reorder triples.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Parses source files locally (no API calls) into typed CodeTriples.
|
|
4
4
|
Uses direct AST node walking — compatible with any tree-sitter version.
|
|
5
5
|
|
|
6
|
-
Supports: .py, .js, .ts, .tsx, .html, .css, .scss
|
|
6
|
+
Supports: .py, .js, .jsx, .ts, .tsx, .html, .css, .scss, .php
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
@@ -21,7 +21,7 @@ from models.code_types import CodeTriple
|
|
|
21
21
|
# Constants
|
|
22
22
|
# ---------------------------------------------------------------------------
|
|
23
23
|
|
|
24
|
-
SUPPORTED_EXTENSIONS = {".py", ".js", ".ts", ".tsx", ".html", ".css", ".scss", ".php"}
|
|
24
|
+
SUPPORTED_EXTENSIONS = {".py", ".js", ".jsx", ".ts", ".tsx", ".html", ".css", ".scss", ".php"}
|
|
25
25
|
|
|
26
26
|
# Relation types
|
|
27
27
|
DEFINED_IN = "DEFINED_IN"
|
|
@@ -47,6 +47,23 @@ TYPE_SCSS_VARIABLE = "scss_variable"
|
|
|
47
47
|
TYPE_ENDPOINT = "endpoint"
|
|
48
48
|
TYPE_CSS_CLASS = "css_class"
|
|
49
49
|
TYPE_FILE = "file"
|
|
50
|
+
TYPE_INTERFACE = "interface"
|
|
51
|
+
TYPE_TRAIT = "trait"
|
|
52
|
+
TYPE_ENUM = "enum"
|
|
53
|
+
TYPE_PROPERTY = "property"
|
|
54
|
+
TYPE_CONSTANT = "constant"
|
|
55
|
+
|
|
56
|
+
# PHP pseudo-class references that never resolve to a real, distinct entity.
|
|
57
|
+
_PHP_SELF_REFERENTIAL = {"self", "static", "parent"}
|
|
58
|
+
|
|
59
|
+
# Container node types that own PHP class-body members (properties, consts,
|
|
60
|
+
# trait uses), mapped to the entity type of the container itself.
|
|
61
|
+
_PHP_CONTAINER_TYPES = {
|
|
62
|
+
"class_declaration": TYPE_CLASS,
|
|
63
|
+
"trait_declaration": TYPE_TRAIT,
|
|
64
|
+
"interface_declaration": TYPE_INTERFACE,
|
|
65
|
+
"enum_declaration": TYPE_ENUM,
|
|
66
|
+
}
|
|
50
67
|
|
|
51
68
|
# Regex patterns for CSS/SCSS source scanning
|
|
52
69
|
_CSS_VAR_RE = re.compile(r'var\(\s*(--[\w-]+)')
|
|
@@ -103,7 +120,7 @@ def _load_html_parser() -> tuple[Parser, Language]:
|
|
|
103
120
|
|
|
104
121
|
def _load_php_parser() -> tuple[Parser, Language]:
|
|
105
122
|
import tree_sitter_php as tsphp
|
|
106
|
-
return _make_parser(tsphp.
|
|
123
|
+
return _make_parser(tsphp.language_php)
|
|
107
124
|
|
|
108
125
|
|
|
109
126
|
def _load_css_parser() -> tuple[Parser, Language]:
|
|
@@ -114,6 +131,7 @@ def _load_css_parser() -> tuple[Parser, Language]:
|
|
|
114
131
|
_PARSER_LOADERS: dict[str, Callable[[], tuple[Parser, Language]]] = {
|
|
115
132
|
".py": _load_python_parser,
|
|
116
133
|
".js": _load_javascript_parser,
|
|
134
|
+
".jsx": _load_javascript_parser,
|
|
117
135
|
".ts": _load_typescript_parser,
|
|
118
136
|
".tsx": _load_tsx_parser,
|
|
119
137
|
".html": _load_html_parser,
|
|
@@ -218,7 +236,7 @@ def _extract_python(root: Node, source: bytes, file_path: str) -> list[CodeTripl
|
|
|
218
236
|
to_entity=_text(base),
|
|
219
237
|
to_type=TYPE_CLASS,
|
|
220
238
|
source_file=file_path,
|
|
221
|
-
line_number=_line(
|
|
239
|
+
line_number=_line(base),
|
|
222
240
|
))
|
|
223
241
|
|
|
224
242
|
case "import_statement":
|
|
@@ -299,68 +317,366 @@ def _extract_python_callee(call_node: Node) -> str | None:
|
|
|
299
317
|
# ---------------------------------------------------------------------------
|
|
300
318
|
# PHP extraction
|
|
301
319
|
# ---------------------------------------------------------------------------
|
|
320
|
+
#
|
|
321
|
+
# Node-type names below were verified against tree_sitter_php 0.24.1 by
|
|
322
|
+
# parsing representative snippets and inspecting the resulting AST directly
|
|
323
|
+
# (the grammar ships no node-types.json in the pip package). Names differ
|
|
324
|
+
# substantially from other PHP tree-sitter grammar versions found online —
|
|
325
|
+
# e.g. classes are `class_declaration` (not `class_definition`), function
|
|
326
|
+
# calls are `function_call_expression` (not `function_call`), and name
|
|
327
|
+
# fields are typed `name` (not `identifier`).
|
|
328
|
+
|
|
329
|
+
def _php_name(node: Node) -> str | None:
|
|
330
|
+
"""Direct `name` child — used for the declared name of most PHP nodes."""
|
|
331
|
+
found = _child_of_type(node, "name")
|
|
332
|
+
return _text(found) if found else None
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _php_basename(node: Node) -> str:
|
|
336
|
+
"""Strip a namespace prefix: `App\\Models\\Foo` -> `Foo`. Also handles a
|
|
337
|
+
bare `name` node (no backslash) and a leading-backslash FQN (`\\Foo`)."""
|
|
338
|
+
return _text(node).rsplit("\\", 1)[-1]
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _php_dotted(node: Node) -> str:
|
|
342
|
+
"""Namespace-qualified name -> dotted form, matching the module-name
|
|
343
|
+
convention used elsewhere (`App\\Models\\Foo` -> `App.Models.Foo`)."""
|
|
344
|
+
return _text(node).lstrip("\\").replace("\\", ".")
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _php_var_name(node: Node) -> str:
|
|
348
|
+
"""`variable_name` node text (`$name`) -> bare name (`name`)."""
|
|
349
|
+
return _text(node).lstrip("$")
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _php_extends_targets(clause: Node) -> list[Node]:
|
|
353
|
+
"""`name`/`qualified_name` children of a base_clause or interface clause.
|
|
354
|
+
|
|
355
|
+
Handles both single-target class `extends` and multi-target interface
|
|
356
|
+
`extends`/`implements` (comma-separated siblings, not a nested list).
|
|
357
|
+
"""
|
|
358
|
+
return [c for c in clause.children if c.type in ("name", "qualified_name")]
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _enclosing_php_container(node: Node) -> tuple[str, str] | None:
|
|
362
|
+
"""Walk up to the nearest enclosing class/trait/interface/enum.
|
|
363
|
+
|
|
364
|
+
Used to attach properties, constants, and trait-uses to their owner.
|
|
365
|
+
"""
|
|
366
|
+
current = node.parent
|
|
367
|
+
while current:
|
|
368
|
+
entity_type = _PHP_CONTAINER_TYPES.get(current.type)
|
|
369
|
+
if entity_type:
|
|
370
|
+
name = _php_name(current)
|
|
371
|
+
if name:
|
|
372
|
+
return name, entity_type
|
|
373
|
+
current = current.parent
|
|
374
|
+
return None
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _enclosing_php_scope(node: Node) -> tuple[str, str] | None:
|
|
378
|
+
"""Walk up from node to find the nearest enclosing function/method/closure.
|
|
379
|
+
|
|
380
|
+
Closures (anonymous_function, arrow_function) only count as a scope if
|
|
381
|
+
assigned to a variable (`$fn = function() {...}`), whose name is then
|
|
382
|
+
used as the caller entity; unnamed inline closures are transparent and
|
|
383
|
+
resolution continues further up the tree.
|
|
384
|
+
"""
|
|
385
|
+
current = node.parent
|
|
386
|
+
while current:
|
|
387
|
+
if current.type in ("function_definition", "method_declaration"):
|
|
388
|
+
name = _php_name(current)
|
|
389
|
+
if name:
|
|
390
|
+
return name, TYPE_FUNCTION
|
|
391
|
+
elif current.type in ("anonymous_function", "arrow_function"):
|
|
392
|
+
parent = current.parent
|
|
393
|
+
if parent and parent.type == "assignment_expression" and parent.children:
|
|
394
|
+
lhs = parent.children[0]
|
|
395
|
+
if lhs.type == "variable_name":
|
|
396
|
+
return _php_var_name(lhs), TYPE_FUNCTION
|
|
397
|
+
current = current.parent
|
|
398
|
+
return None
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _extract_php_namespace_use(node: Node) -> list[tuple[str, Node]]:
|
|
402
|
+
"""Resolve every imported target of a `use ...;` statement to a dotted
|
|
403
|
+
name plus the node to attribute the line number to.
|
|
404
|
+
|
|
405
|
+
Handles plain (`use App\\Foo;`), aliased (`use App\\Foo as F;`), grouped
|
|
406
|
+
(`use App\\{Bar, Baz as Z};`), and `use function`/`use const` forms.
|
|
407
|
+
"""
|
|
408
|
+
results: list[tuple[str, Node]] = []
|
|
409
|
+
group = _child_of_type(node, "namespace_use_group")
|
|
410
|
+
if group is not None:
|
|
411
|
+
prefix_node = _child_of_type(node, "namespace_name")
|
|
412
|
+
prefix = _text(prefix_node) if prefix_node else ""
|
|
413
|
+
for clause in _children_of_type(group, "namespace_use_clause"):
|
|
414
|
+
target = _child_of_type(clause, "qualified_name") or _child_of_type(clause, "name")
|
|
415
|
+
if target:
|
|
416
|
+
full = f"{prefix}\\{_text(target)}" if prefix else _text(target)
|
|
417
|
+
results.append((full.lstrip("\\").replace("\\", "."), target))
|
|
418
|
+
else:
|
|
419
|
+
for clause in _children_of_type(node, "namespace_use_clause"):
|
|
420
|
+
target = _child_of_type(clause, "qualified_name") or _child_of_type(clause, "name")
|
|
421
|
+
if target:
|
|
422
|
+
results.append((_php_dotted(target), target))
|
|
423
|
+
return results
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _php_include_target(node: Node) -> Node | None:
|
|
427
|
+
"""The literal string argument of a require/include expression, if any.
|
|
428
|
+
|
|
429
|
+
Dynamic paths (`require __DIR__ . '/x.php';`) have no plain `string`
|
|
430
|
+
child and are skipped — no static target to record.
|
|
431
|
+
"""
|
|
432
|
+
return _child_of_type(node, "string")
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _php_string_text(string_node: Node) -> str:
|
|
436
|
+
frag = _child_of_type(string_node, "string_content")
|
|
437
|
+
return _text(frag) if frag else _text(string_node).strip("'\"")
|
|
438
|
+
|
|
302
439
|
|
|
303
440
|
def _extract_php(root: Node, source: bytes, file_path: str) -> list[CodeTriple]:
|
|
304
441
|
module_name = _module_name_from_path(file_path)
|
|
305
442
|
triples: list[CodeTriple] = []
|
|
306
443
|
|
|
444
|
+
def defined_in(name: str, entity_type: str, line: int) -> None:
|
|
445
|
+
triples.append(CodeTriple(
|
|
446
|
+
from_entity=name,
|
|
447
|
+
from_type=entity_type,
|
|
448
|
+
relation_type=DEFINED_IN,
|
|
449
|
+
to_entity=module_name,
|
|
450
|
+
to_type=TYPE_MODULE,
|
|
451
|
+
source_file=file_path,
|
|
452
|
+
line_number=line,
|
|
453
|
+
))
|
|
454
|
+
|
|
455
|
+
def inherits(from_name: str, from_type: str, to_node: Node, to_type: str) -> None:
|
|
456
|
+
basename = _php_basename(to_node)
|
|
457
|
+
if basename and basename.lower() not in _PHP_SELF_REFERENTIAL:
|
|
458
|
+
triples.append(CodeTriple(
|
|
459
|
+
from_entity=from_name,
|
|
460
|
+
from_type=from_type,
|
|
461
|
+
relation_type=INHERITS,
|
|
462
|
+
to_entity=basename,
|
|
463
|
+
to_type=to_type,
|
|
464
|
+
source_file=file_path,
|
|
465
|
+
line_number=_line(to_node),
|
|
466
|
+
))
|
|
467
|
+
|
|
468
|
+
def calls(callee: str, callee_type: str, line: int, scope_node: Node) -> None:
|
|
469
|
+
if not callee or not _is_meaningful_callee(callee):
|
|
470
|
+
return
|
|
471
|
+
scope = _enclosing_php_scope(scope_node)
|
|
472
|
+
caller, caller_type = scope if scope else (module_name, TYPE_MODULE)
|
|
473
|
+
triples.append(CodeTriple(
|
|
474
|
+
from_entity=caller,
|
|
475
|
+
from_type=caller_type,
|
|
476
|
+
relation_type=CALLS,
|
|
477
|
+
to_entity=callee,
|
|
478
|
+
to_type=callee_type,
|
|
479
|
+
source_file=file_path,
|
|
480
|
+
line_number=line,
|
|
481
|
+
))
|
|
482
|
+
|
|
307
483
|
for node in _walk(root):
|
|
308
484
|
match node.type:
|
|
485
|
+
# -- Functions -----------------------------------------------
|
|
309
486
|
case "function_definition":
|
|
310
|
-
name =
|
|
487
|
+
name = _php_name(node)
|
|
488
|
+
if name:
|
|
489
|
+
defined_in(name, TYPE_FUNCTION, _line(node))
|
|
490
|
+
|
|
491
|
+
case "method_declaration":
|
|
492
|
+
name = _php_name(node)
|
|
311
493
|
if name:
|
|
494
|
+
defined_in(name, TYPE_FUNCTION, _line(node))
|
|
495
|
+
|
|
496
|
+
case "anonymous_function" | "arrow_function":
|
|
497
|
+
parent = node.parent
|
|
498
|
+
if parent and parent.type == "assignment_expression" and parent.children:
|
|
499
|
+
lhs = parent.children[0]
|
|
500
|
+
if lhs.type == "variable_name":
|
|
501
|
+
defined_in(_php_var_name(lhs), TYPE_FUNCTION, _line(node))
|
|
502
|
+
|
|
503
|
+
# -- Classes / interfaces / traits / enums --------------------
|
|
504
|
+
case "class_declaration":
|
|
505
|
+
name = _php_name(node)
|
|
506
|
+
if not name:
|
|
507
|
+
continue
|
|
508
|
+
defined_in(name, TYPE_CLASS, _line(node))
|
|
509
|
+
base_clause = _child_of_type(node, "base_clause")
|
|
510
|
+
if base_clause:
|
|
511
|
+
for target in _php_extends_targets(base_clause):
|
|
512
|
+
inherits(name, TYPE_CLASS, target, TYPE_CLASS)
|
|
513
|
+
iface_clause = _child_of_type(node, "class_interface_clause")
|
|
514
|
+
if iface_clause:
|
|
515
|
+
for target in _php_extends_targets(iface_clause):
|
|
516
|
+
inherits(name, TYPE_CLASS, target, TYPE_INTERFACE)
|
|
517
|
+
|
|
518
|
+
case "interface_declaration":
|
|
519
|
+
name = _php_name(node)
|
|
520
|
+
if not name:
|
|
521
|
+
continue
|
|
522
|
+
defined_in(name, TYPE_INTERFACE, _line(node))
|
|
523
|
+
base_clause = _child_of_type(node, "base_clause")
|
|
524
|
+
if base_clause:
|
|
525
|
+
for target in _php_extends_targets(base_clause):
|
|
526
|
+
inherits(name, TYPE_INTERFACE, target, TYPE_INTERFACE)
|
|
527
|
+
|
|
528
|
+
case "trait_declaration":
|
|
529
|
+
name = _php_name(node)
|
|
530
|
+
if name:
|
|
531
|
+
defined_in(name, TYPE_TRAIT, _line(node))
|
|
532
|
+
|
|
533
|
+
case "enum_declaration":
|
|
534
|
+
name = _php_name(node)
|
|
535
|
+
if not name:
|
|
536
|
+
continue
|
|
537
|
+
defined_in(name, TYPE_ENUM, _line(node))
|
|
538
|
+
iface_clause = _child_of_type(node, "class_interface_clause")
|
|
539
|
+
if iface_clause:
|
|
540
|
+
for target in _php_extends_targets(iface_clause):
|
|
541
|
+
inherits(name, TYPE_ENUM, target, TYPE_INTERFACE)
|
|
542
|
+
|
|
543
|
+
case "enum_case":
|
|
544
|
+
container = _enclosing_php_container(node)
|
|
545
|
+
name = _php_name(node)
|
|
546
|
+
if name and container:
|
|
547
|
+
container_name, container_type = container
|
|
312
548
|
triples.append(CodeTriple(
|
|
313
549
|
from_entity=name,
|
|
314
|
-
from_type=
|
|
550
|
+
from_type=TYPE_CONSTANT,
|
|
315
551
|
relation_type=DEFINED_IN,
|
|
316
|
-
to_entity=
|
|
317
|
-
to_type=
|
|
552
|
+
to_entity=container_name,
|
|
553
|
+
to_type=container_type,
|
|
318
554
|
source_file=file_path,
|
|
319
555
|
line_number=_line(node),
|
|
320
556
|
))
|
|
321
557
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
# Extract base classes (extends)
|
|
329
|
-
extends_node = _child_of_type(node, "extends_clause")
|
|
330
|
-
if extends_node:
|
|
331
|
-
base_node = _child_of_type(extends_node, "type_name") or _child_of_type(extends_node, "identifier")
|
|
332
|
-
if base_node:
|
|
558
|
+
# -- Trait usage inside a class/trait body --------------------
|
|
559
|
+
case "use_declaration":
|
|
560
|
+
container = _enclosing_php_container(node)
|
|
561
|
+
if container:
|
|
562
|
+
container_name, container_type = container
|
|
563
|
+
for target in _children_of_type(node, "name"):
|
|
333
564
|
triples.append(CodeTriple(
|
|
334
|
-
from_entity=
|
|
335
|
-
from_type=
|
|
565
|
+
from_entity=container_name,
|
|
566
|
+
from_type=container_type,
|
|
336
567
|
relation_type=INHERITS,
|
|
337
|
-
to_entity=_text(
|
|
338
|
-
to_type=
|
|
568
|
+
to_entity=_text(target),
|
|
569
|
+
to_type=TYPE_TRAIT,
|
|
339
570
|
source_file=file_path,
|
|
340
|
-
line_number=_line(
|
|
571
|
+
line_number=_line(target),
|
|
341
572
|
))
|
|
342
573
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
scope = _enclosing_scope(node) # Reuse python-style scope walker for now
|
|
347
|
-
caller = scope or module_name
|
|
348
|
-
caller_type = TYPE_FUNCTION if scope else TYPE_MODULE
|
|
574
|
+
# -- Namespace imports -----------------------------------------
|
|
575
|
+
case "namespace_use_declaration":
|
|
576
|
+
for dotted, target_node in _extract_php_namespace_use(node):
|
|
349
577
|
triples.append(CodeTriple(
|
|
350
|
-
from_entity=
|
|
351
|
-
from_type=
|
|
352
|
-
relation_type=
|
|
353
|
-
to_entity=
|
|
354
|
-
to_type=
|
|
578
|
+
from_entity=module_name,
|
|
579
|
+
from_type=TYPE_MODULE,
|
|
580
|
+
relation_type=IMPORTS,
|
|
581
|
+
to_entity=dotted,
|
|
582
|
+
to_type=TYPE_MODULE,
|
|
355
583
|
source_file=file_path,
|
|
356
|
-
line_number=_line(
|
|
584
|
+
line_number=_line(target_node),
|
|
357
585
|
))
|
|
358
586
|
|
|
359
|
-
|
|
587
|
+
# -- require/include -------------------------------------------
|
|
588
|
+
case "require_once_expression" | "require_expression" | "include_expression" | "include_once_expression":
|
|
589
|
+
str_node = _php_include_target(node)
|
|
590
|
+
if str_node:
|
|
591
|
+
triples.append(CodeTriple(
|
|
592
|
+
from_entity=module_name,
|
|
593
|
+
from_type=TYPE_MODULE,
|
|
594
|
+
relation_type=INCLUDES,
|
|
595
|
+
to_entity=_php_string_text(str_node),
|
|
596
|
+
to_type=TYPE_MODULE,
|
|
597
|
+
source_file=file_path,
|
|
598
|
+
line_number=_line(str_node),
|
|
599
|
+
))
|
|
600
|
+
|
|
601
|
+
# -- Properties / constructor-promoted properties ---------------
|
|
602
|
+
case "property_declaration":
|
|
603
|
+
container = _enclosing_php_container(node)
|
|
604
|
+
if container:
|
|
605
|
+
container_name, container_type = container
|
|
606
|
+
for element in _children_of_type(node, "property_element"):
|
|
607
|
+
var_node = _child_of_type(element, "variable_name")
|
|
608
|
+
if var_node:
|
|
609
|
+
triples.append(CodeTriple(
|
|
610
|
+
from_entity=_php_var_name(var_node),
|
|
611
|
+
from_type=TYPE_PROPERTY,
|
|
612
|
+
relation_type=DEFINED_IN,
|
|
613
|
+
to_entity=container_name,
|
|
614
|
+
to_type=container_type,
|
|
615
|
+
source_file=file_path,
|
|
616
|
+
line_number=_line(var_node),
|
|
617
|
+
))
|
|
618
|
+
|
|
619
|
+
case "property_promotion_parameter":
|
|
620
|
+
container = _enclosing_php_container(node)
|
|
621
|
+
var_node = _child_of_type(node, "variable_name")
|
|
622
|
+
if container and var_node:
|
|
623
|
+
container_name, container_type = container
|
|
624
|
+
triples.append(CodeTriple(
|
|
625
|
+
from_entity=_php_var_name(var_node),
|
|
626
|
+
from_type=TYPE_PROPERTY,
|
|
627
|
+
relation_type=DEFINED_IN,
|
|
628
|
+
to_entity=container_name,
|
|
629
|
+
to_type=container_type,
|
|
630
|
+
source_file=file_path,
|
|
631
|
+
line_number=_line(var_node),
|
|
632
|
+
))
|
|
633
|
+
|
|
634
|
+
# -- Constants (class/interface/trait/enum or top-level) -------
|
|
635
|
+
case "const_declaration":
|
|
636
|
+
container = _enclosing_php_container(node)
|
|
637
|
+
for element in _children_of_type(node, "const_element"):
|
|
638
|
+
name_node = _child_of_type(element, "name")
|
|
639
|
+
if not name_node:
|
|
640
|
+
continue
|
|
641
|
+
to_entity, to_type = container if container else (module_name, TYPE_MODULE)
|
|
642
|
+
triples.append(CodeTriple(
|
|
643
|
+
from_entity=_text(name_node),
|
|
644
|
+
from_type=TYPE_CONSTANT,
|
|
645
|
+
relation_type=DEFINED_IN,
|
|
646
|
+
to_entity=to_entity,
|
|
647
|
+
to_type=to_type,
|
|
648
|
+
source_file=file_path,
|
|
649
|
+
line_number=_line(name_node),
|
|
650
|
+
))
|
|
651
|
+
|
|
652
|
+
# -- Calls -------------------------------------------------------
|
|
653
|
+
case "function_call_expression":
|
|
654
|
+
callee_node = _child_of_type(node, "name") or _child_of_type(node, "qualified_name")
|
|
655
|
+
if callee_node:
|
|
656
|
+
calls(_php_basename(callee_node), TYPE_FUNCTION, _line(node), node)
|
|
360
657
|
|
|
658
|
+
case "member_call_expression" | "nullsafe_member_call_expression":
|
|
659
|
+
callee_node = _child_of_type(node, "name")
|
|
660
|
+
if callee_node:
|
|
661
|
+
calls(_text(callee_node), TYPE_FUNCTION, _line(node), node)
|
|
361
662
|
|
|
362
|
-
|
|
363
|
-
|
|
663
|
+
case "scoped_call_expression":
|
|
664
|
+
names = _children_of_type(node, "name")
|
|
665
|
+
if names:
|
|
666
|
+
calls(_text(names[-1]), TYPE_FUNCTION, _line(node), node)
|
|
667
|
+
|
|
668
|
+
case "object_creation_expression":
|
|
669
|
+
class_node = None
|
|
670
|
+
for child in node.children:
|
|
671
|
+
if child.type in ("name", "qualified_name"):
|
|
672
|
+
class_node = child
|
|
673
|
+
break
|
|
674
|
+
if class_node:
|
|
675
|
+
basename = _php_basename(class_node)
|
|
676
|
+
if basename.lower() not in _PHP_SELF_REFERENTIAL:
|
|
677
|
+
calls(basename, TYPE_CLASS, _line(node), node)
|
|
678
|
+
|
|
679
|
+
return triples
|
|
364
680
|
|
|
365
681
|
# ---------------------------------------------------------------------------
|
|
366
682
|
# JavaScript / TypeScript extraction
|
|
@@ -723,6 +1039,7 @@ def _extract_lit_css_tokens(source_text: str, file_path: str) -> list[CodeTriple
|
|
|
723
1039
|
_EXTRACTORS: dict[str, Callable] = {
|
|
724
1040
|
".py": _extract_python,
|
|
725
1041
|
".js": _extract_javascript,
|
|
1042
|
+
".jsx": _extract_javascript,
|
|
726
1043
|
".ts": _extract_javascript,
|
|
727
1044
|
".tsx": _extract_javascript,
|
|
728
1045
|
".html": _extract_html,
|
|
@@ -8,7 +8,8 @@ class CodeTriple:
|
|
|
8
8
|
"""A single typed relationship extracted from source code."""
|
|
9
9
|
|
|
10
10
|
from_entity: str
|
|
11
|
-
from_type: str # function | class | module | css_selector | html_element | scss_mixin | scss_variable
|
|
11
|
+
from_type: str # function | class | module | css_selector | html_element | scss_mixin | scss_variable |
|
|
12
|
+
# interface | trait | enum | property | constant
|
|
12
13
|
relation_type: str # CALLS | IMPORTS | INHERITS | DEFINED_IN | STYLES | HAS_CLASS | POSTS_TO | INCLUDES | USED_BY
|
|
13
14
|
to_entity: str
|
|
14
15
|
to_type: str
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codecompass-mcp"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.4.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,217 @@
|
|
|
1
|
+
"""Tests for PHP ingestion entity coverage."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
|
|
8
|
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
9
|
+
|
|
10
|
+
from ingestion.code_parser import parse_file
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _parse(content: str) -> list:
|
|
14
|
+
with tempfile.NamedTemporaryFile(suffix=".php", mode="w", delete=False) as f:
|
|
15
|
+
f.write(content)
|
|
16
|
+
path = f.name
|
|
17
|
+
try:
|
|
18
|
+
return parse_file(path, os.path.dirname(path))
|
|
19
|
+
finally:
|
|
20
|
+
os.unlink(path)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _rels(triples, relation_type=None, from_type=None, to_type=None):
|
|
24
|
+
out = triples
|
|
25
|
+
if relation_type is not None:
|
|
26
|
+
out = [t for t in out if t.relation_type == relation_type]
|
|
27
|
+
if from_type is not None:
|
|
28
|
+
out = [t for t in out if t.from_type == from_type]
|
|
29
|
+
if to_type is not None:
|
|
30
|
+
out = [t for t in out if t.to_type == to_type]
|
|
31
|
+
return out
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# The parser must actually run — regression guard for the broken loader
|
|
36
|
+
# (tsphp.language vs tsphp.language_php) that silently no-op'd every .php file.
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
def test_php_file_parses_without_error():
|
|
40
|
+
triples = _parse("<?php\nfunction foo() { return 1; }\n")
|
|
41
|
+
assert triples, "PHP parsing should produce triples, not silently fail"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# Functions, classes, inheritance
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
def test_top_level_function_indexed():
|
|
49
|
+
triples = _parse("<?php\nfunction topLevel($x) { return $x; }\n")
|
|
50
|
+
defs = _rels(triples, relation_type="DEFINED_IN", from_type="function")
|
|
51
|
+
assert any(t.from_entity == "topLevel" for t in defs)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_class_extends_indexed():
|
|
55
|
+
triples = _parse("<?php\nclass Foo extends Bar {}\n")
|
|
56
|
+
inh = _rels(triples, relation_type="INHERITS", to_type="class")
|
|
57
|
+
assert any(t.from_entity == "Foo" and t.to_entity == "Bar" for t in inh)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_class_implements_indexed():
|
|
61
|
+
triples = _parse("<?php\nclass Foo implements A, B {}\n")
|
|
62
|
+
inh = _rels(triples, relation_type="INHERITS", to_type="interface")
|
|
63
|
+
targets = {t.to_entity for t in inh}
|
|
64
|
+
assert {"A", "B"} <= targets
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
# Methods, properties, constructor promotion, constants
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
def test_class_method_indexed():
|
|
72
|
+
src = "<?php\nclass Foo {\n public function bar() { return 1; }\n}\n"
|
|
73
|
+
triples = _parse(src)
|
|
74
|
+
defs = _rels(triples, relation_type="DEFINED_IN", from_type="function")
|
|
75
|
+
assert any(t.from_entity == "bar" for t in defs)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_property_declaration_indexed():
|
|
79
|
+
src = "<?php\nclass Foo {\n protected static ?string $name = \"x\";\n}\n"
|
|
80
|
+
triples = _parse(src)
|
|
81
|
+
props = _rels(triples, relation_type="DEFINED_IN", from_type="property")
|
|
82
|
+
assert any(t.from_entity == "name" and t.to_entity == "Foo" for t in props)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_constructor_promoted_property_indexed():
|
|
86
|
+
src = "<?php\nclass Foo {\n public function __construct(private readonly int $id) {}\n}\n"
|
|
87
|
+
triples = _parse(src)
|
|
88
|
+
props = _rels(triples, relation_type="DEFINED_IN", from_type="property")
|
|
89
|
+
assert any(t.from_entity == "id" and t.to_entity == "Foo" for t in props)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_class_constant_indexed():
|
|
93
|
+
src = "<?php\nclass Foo {\n public const MAX = 10;\n}\n"
|
|
94
|
+
triples = _parse(src)
|
|
95
|
+
consts = _rels(triples, relation_type="DEFINED_IN", from_type="constant")
|
|
96
|
+
assert any(t.from_entity == "MAX" and t.to_entity == "Foo" for t in consts)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_top_level_constant_indexed():
|
|
100
|
+
triples = _parse("<?php\nconst MAX = 10;\n")
|
|
101
|
+
consts = _rels(triples, relation_type="DEFINED_IN", from_type="constant")
|
|
102
|
+
assert any(t.from_entity == "MAX" and t.to_type == "module" for t in consts)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# Interfaces, traits, enums
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def test_interface_declaration_indexed():
|
|
110
|
+
triples = _parse("<?php\ninterface Payable {\n public function pay(): void;\n}\n")
|
|
111
|
+
defs = _rels(triples, relation_type="DEFINED_IN", from_type="interface")
|
|
112
|
+
assert any(t.from_entity == "Payable" for t in defs)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_interface_extends_multiple_indexed():
|
|
116
|
+
triples = _parse("<?php\ninterface Multi extends X, Y {}\n")
|
|
117
|
+
inh = _rels(triples, relation_type="INHERITS", from_type="interface", to_type="interface")
|
|
118
|
+
targets = {t.to_entity for t in inh}
|
|
119
|
+
assert {"X", "Y"} <= targets
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_trait_declaration_and_use_indexed():
|
|
123
|
+
src = "<?php\ntrait Loggable {\n public function log() {}\n}\nclass Foo {\n use Loggable;\n}\n"
|
|
124
|
+
triples = _parse(src)
|
|
125
|
+
trait_defs = _rels(triples, relation_type="DEFINED_IN", from_type="trait")
|
|
126
|
+
assert any(t.from_entity == "Loggable" for t in trait_defs)
|
|
127
|
+
uses = _rels(triples, relation_type="INHERITS", to_type="trait")
|
|
128
|
+
assert any(t.from_entity == "Foo" and t.to_entity == "Loggable" for t in uses)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_enum_and_case_indexed():
|
|
132
|
+
src = "<?php\nenum Status: string {\n case Active = 'active';\n}\n"
|
|
133
|
+
triples = _parse(src)
|
|
134
|
+
enum_defs = _rels(triples, relation_type="DEFINED_IN", from_type="enum")
|
|
135
|
+
assert any(t.from_entity == "Status" for t in enum_defs)
|
|
136
|
+
cases = _rels(triples, relation_type="DEFINED_IN", from_type="constant", to_type="enum")
|
|
137
|
+
assert any(t.from_entity == "Active" and t.to_entity == "Status" for t in cases)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
# Namespaces / imports / includes
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
def test_namespace_use_indexed():
|
|
145
|
+
triples = _parse("<?php\nuse App\\Contracts\\Payable;\n")
|
|
146
|
+
imports = _rels(triples, relation_type="IMPORTS")
|
|
147
|
+
assert any(t.to_entity == "App.Contracts.Payable" for t in imports)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_namespace_use_group_indexed():
|
|
151
|
+
triples = _parse("<?php\nuse App\\{Bar, Baz};\n")
|
|
152
|
+
imports = _rels(triples, relation_type="IMPORTS")
|
|
153
|
+
targets = {t.to_entity for t in imports}
|
|
154
|
+
assert {"App.Bar", "App.Baz"} <= targets
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_require_once_indexed():
|
|
158
|
+
triples = _parse("<?php\nrequire_once 'bootstrap.php';\n")
|
|
159
|
+
includes = _rels(triples, relation_type="INCLUDES")
|
|
160
|
+
assert any(t.to_entity == "bootstrap.php" for t in includes)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
# Calls: plain, method, static, new, closures
|
|
165
|
+
# ---------------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
def test_plain_function_call_indexed():
|
|
168
|
+
triples = _parse("<?php\nfunction outer() { inner(); }\nfunction inner() {}\n")
|
|
169
|
+
calls = _rels(triples, relation_type="CALLS")
|
|
170
|
+
assert any(t.from_entity == "outer" and t.to_entity == "inner" for t in calls)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_method_call_indexed():
|
|
174
|
+
src = "<?php\nfunction run() {\n $obj = new Foo();\n $obj->save();\n}\n"
|
|
175
|
+
triples = _parse(src)
|
|
176
|
+
calls = _rels(triples, relation_type="CALLS")
|
|
177
|
+
assert any(t.to_entity == "save" and t.to_type == "function" for t in calls)
|
|
178
|
+
assert any(t.to_entity == "Foo" and t.to_type == "class" for t in calls)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_static_call_indexed():
|
|
182
|
+
src = "<?php\nfunction run() {\n BaseModel::make();\n}\n"
|
|
183
|
+
triples = _parse(src)
|
|
184
|
+
calls = _rels(triples, relation_type="CALLS")
|
|
185
|
+
assert any(t.to_entity == "make" for t in calls)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_self_new_not_recorded_as_class_entity():
|
|
189
|
+
src = "<?php\nclass Foo {\n public static function make() {\n return new static();\n }\n}\n"
|
|
190
|
+
triples = _parse(src)
|
|
191
|
+
calls = _rels(triples, relation_type="CALLS", to_type="class")
|
|
192
|
+
assert not any(t.to_entity.lower() in ("self", "static", "parent") for t in calls)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_closure_assigned_to_variable_indexed():
|
|
196
|
+
src = "<?php\nfunction run() {\n $fn = function($x) { return $x; };\n}\n"
|
|
197
|
+
triples = _parse(src)
|
|
198
|
+
defs = _rels(triples, relation_type="DEFINED_IN", from_type="function")
|
|
199
|
+
assert any(t.from_entity == "fn" for t in defs)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_arrow_function_assigned_to_variable_indexed():
|
|
203
|
+
src = "<?php\nfunction run() {\n $arrow = fn($x) => $x + 1;\n}\n"
|
|
204
|
+
triples = _parse(src)
|
|
205
|
+
defs = _rels(triples, relation_type="DEFINED_IN", from_type="function")
|
|
206
|
+
assert any(t.from_entity == "arrow" for t in defs)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# ---------------------------------------------------------------------------
|
|
210
|
+
# No regression on other extractors
|
|
211
|
+
# ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
def test_php_does_not_emit_html_relations():
|
|
214
|
+
triples = _parse("<?php\nclass Foo {}\n")
|
|
215
|
+
rel_types = {t.relation_type for t in triples}
|
|
216
|
+
assert "HAS_CLASS" not in rel_types
|
|
217
|
+
assert "STYLES" not in rel_types
|
|
File without changes
|
|
File without changes
|
{codecompass_mcp-2.3.0 → codecompass_mcp-2.4.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
|