codedebrief 0.11.0__py3-none-any.whl

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 (48) hide show
  1. codedebrief/__init__.py +12 -0
  2. codedebrief/analysis/__init__.py +16 -0
  3. codedebrief/analysis/common.py +527 -0
  4. codedebrief/analysis/discovery.py +100 -0
  5. codedebrief/analysis/languages/__init__.py +6 -0
  6. codedebrief/analysis/languages/_common.py +68 -0
  7. codedebrief/analysis/languages/c.py +96 -0
  8. codedebrief/analysis/languages/cpp.py +146 -0
  9. codedebrief/analysis/languages/csharp.py +137 -0
  10. codedebrief/analysis/languages/go.py +157 -0
  11. codedebrief/analysis/languages/java.py +158 -0
  12. codedebrief/analysis/languages/php.py +83 -0
  13. codedebrief/analysis/languages/ruby.py +75 -0
  14. codedebrief/analysis/languages/rust.py +96 -0
  15. codedebrief/analysis/project.py +373 -0
  16. codedebrief/analysis/python.py +939 -0
  17. codedebrief/analysis/registry.py +320 -0
  18. codedebrief/analysis/treesitter.py +884 -0
  19. codedebrief/analysis/typescript.py +1019 -0
  20. codedebrief/artifacts.py +49 -0
  21. codedebrief/cli.py +585 -0
  22. codedebrief/config.py +226 -0
  23. codedebrief/doctor.py +175 -0
  24. codedebrief/install.py +441 -0
  25. codedebrief/mcp_server.py +2720 -0
  26. codedebrief/model.py +189 -0
  27. codedebrief/py.typed +1 -0
  28. codedebrief/quality.py +392 -0
  29. codedebrief/query.py +641 -0
  30. codedebrief/render/__init__.py +6 -0
  31. codedebrief/render/assets/generated/codedebrief-viewer-runtime.iife.js +10 -0
  32. codedebrief/render/assets/panels.js +462 -0
  33. codedebrief/render/assets/shell.js +1649 -0
  34. codedebrief/render/assets/styles.css +1715 -0
  35. codedebrief/render/assets/tree.js +616 -0
  36. codedebrief/render/html.py +191 -0
  37. codedebrief/render/markdown.py +153 -0
  38. codedebrief/render/payload.py +326 -0
  39. codedebrief/render/snapshot.py +769 -0
  40. codedebrief/schema/codedebrief.schema.json +449 -0
  41. codedebrief/util.py +65 -0
  42. codedebrief/validation.py +214 -0
  43. codedebrief-0.11.0.dist-info/METADATA +426 -0
  44. codedebrief-0.11.0.dist-info/RECORD +48 -0
  45. codedebrief-0.11.0.dist-info/WHEEL +4 -0
  46. codedebrief-0.11.0.dist-info/entry_points.txt +2 -0
  47. codedebrief-0.11.0.dist-info/licenses/LICENSE +176 -0
  48. codedebrief-0.11.0.dist-info/licenses/NOTICE +9 -0
@@ -0,0 +1,68 @@
1
+ """Shared helpers for the tree-sitter language profiles.
2
+
3
+ Centralizes the byte-slice text, named-children, and directory-as-module helpers every
4
+ profile needs, plus the definition walker shared by class/method profiles, to avoid
5
+ copy-paste drift across the language modules.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Callable, Iterable
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ from codedebrief.analysis.treesitter import LanguageProfile, TSDefinition
15
+
16
+
17
+ def text(node: Any | None, source: bytes) -> str:
18
+ if node is None:
19
+ return ""
20
+ return source[node.start_byte : node.end_byte].decode("utf-8", "replace")
21
+
22
+
23
+ def named(node: Any | None) -> Iterable[Any]:
24
+ return (child for child in node.children if child.is_named) if node is not None else ()
25
+
26
+
27
+ def module_name(relative: str) -> str:
28
+ """The directory as a dotted module name, so files in one package share a namespace."""
29
+ return Path(relative).parent.as_posix().replace("/", ".").strip(".")
30
+
31
+
32
+ def container_definitions(
33
+ containers: frozenset[str],
34
+ methods: frozenset[str],
35
+ *,
36
+ name_field: str = "name",
37
+ body_field: str = "body",
38
+ ) -> Callable[[Any, bytes, str, LanguageProfile], Iterable[TSDefinition]]:
39
+ """A `definitions()` for languages whose functions live inside class/module containers.
40
+
41
+ Recurses into each container, tagging found methods with the container name as owner,
42
+ and also yields matching top-level functions.
43
+ """
44
+
45
+ def definitions(
46
+ root: Any, source: bytes, relative: str, profile: LanguageProfile
47
+ ) -> Iterable[TSDefinition]:
48
+ yield from _walk(root, source, "")
49
+
50
+ def _walk(node: Any, source: bytes, owner: str) -> Iterable[TSDefinition]:
51
+ if node.type in containers:
52
+ name = text(node.child_by_field_name(name_field), source) or owner
53
+ body = node.child_by_field_name(body_field) or node.child_by_field_name(
54
+ "declaration_list"
55
+ )
56
+ for child in named(body if body is not None else node):
57
+ yield from _walk(child, source, name)
58
+ return
59
+ if node.type in methods:
60
+ name = text(node.child_by_field_name(name_field), source)
61
+ body = node.child_by_field_name(body_field)
62
+ if name and body is not None:
63
+ yield TSDefinition(name=name, node=node, body=body, owner=owner)
64
+ return
65
+ for child in named(node):
66
+ yield from _walk(child, source, owner)
67
+
68
+ return definitions
@@ -0,0 +1,96 @@
1
+ """C language profile for the profile-driven tree-sitter analyzer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import tree_sitter_c
10
+
11
+ from codedebrief.analysis.languages._common import module_name, text
12
+ from codedebrief.analysis.treesitter import (
13
+ LanguageProfile,
14
+ TreeSitterAnalyzer,
15
+ TSDefinition,
16
+ )
17
+ from codedebrief.config import CodeDebriefConfig
18
+
19
+
20
+ def _definitions(
21
+ root: Any, source: bytes, relative: str, profile: LanguageProfile
22
+ ) -> Iterable[TSDefinition]:
23
+ for node in root.children:
24
+ if node.type != "function_definition":
25
+ continue
26
+ name = _function_name(node.child_by_field_name("declarator"), source)
27
+ body = node.child_by_field_name("body")
28
+ if name and body is not None:
29
+ yield TSDefinition(name=name, node=node, body=body, owner="")
30
+
31
+
32
+ def _function_name(declarator: Any | None, source: bytes) -> str:
33
+ """The identifier inside a (possibly pointer-wrapped) function_declarator."""
34
+ node = declarator
35
+ while node is not None:
36
+ if node.type == "identifier":
37
+ return text(node, source)
38
+ inner = node.child_by_field_name("declarator")
39
+ if inner is None:
40
+ break
41
+ node = inner
42
+ if node is not None:
43
+ ident = next((c for c in node.children if c.type == "identifier"), None)
44
+ return text(ident, source)
45
+ return ""
46
+
47
+
48
+ def _classify(
49
+ definition: TSDefinition, relative: str, source: str, config: CodeDebriefConfig
50
+ ) -> tuple[str, str, bool]:
51
+ override = config.entrypoint_override(f"{relative}:{definition.name}")
52
+ if definition.name == "main":
53
+ return "c", "main", override if override is not None else True
54
+ is_static = any(
55
+ c.type == "storage_class_specifier" and c.text.decode() == "static"
56
+ for c in definition.node.children
57
+ )
58
+ public = config.include_public_functions and not is_static
59
+ return "generic", "function", override if override is not None else public
60
+
61
+
62
+ def _is_test(relative: str, name: str) -> bool:
63
+ # Anchor to path SEGMENTS (a `test`/`tests` directory or a test_*.c / *_test.c file),
64
+ # not a substring of the whole path: `latest/` or `contest.c` must not count, and a
65
+ # real function named `test_harness` outside a test file must not be misclassified.
66
+ lowered = relative.lower()
67
+ segments = lowered.split("/")
68
+ filename = segments[-1]
69
+ return (
70
+ any(segment in {"test", "tests"} for segment in segments[:-1])
71
+ or filename.startswith("test_")
72
+ or filename.endswith(("_test.c", "_test.h"))
73
+ )
74
+
75
+
76
+ C_PROFILE = LanguageProfile(
77
+ language="c",
78
+ grammar_loader=tree_sitter_c.language,
79
+ function_types=frozenset({"function_definition"}),
80
+ definitions=_definitions,
81
+ classify=_classify,
82
+ is_test=_is_test,
83
+ module_name=module_name,
84
+ block_types=frozenset({"compound_statement"}),
85
+ switch_types=frozenset({"switch_statement"}),
86
+ switch_value_field="condition",
87
+ case_types=frozenset({"case_statement"}),
88
+ default_when_no_value=True,
89
+ case_fall_through=True,
90
+ loop_types=frozenset({"for_statement", "while_statement", "do_statement"}),
91
+ assignment_types=frozenset({"declaration"}),
92
+ )
93
+
94
+
95
+ def build_analyzer(root: Path, config: CodeDebriefConfig) -> TreeSitterAnalyzer:
96
+ return TreeSitterAnalyzer(root, config, C_PROFILE)
@@ -0,0 +1,146 @@
1
+ """C++ language profile for the profile-driven tree-sitter analyzer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import tree_sitter_cpp
10
+
11
+ from codedebrief.analysis.languages._common import module_name, text
12
+ from codedebrief.analysis.treesitter import (
13
+ LanguageProfile,
14
+ TreeSitterAnalyzer,
15
+ TSDefinition,
16
+ )
17
+ from codedebrief.config import CodeDebriefConfig
18
+
19
+
20
+ def _definitions(
21
+ root: Any, source: bytes, relative: str, profile: LanguageProfile
22
+ ) -> Iterable[TSDefinition]:
23
+ yield from _walk(root, source, "")
24
+
25
+
26
+ def _walk(node: Any, source: bytes, owner: str) -> Iterable[TSDefinition]:
27
+ if node.type in {"class_specifier", "struct_specifier", "namespace_definition"}:
28
+ name = text(node.child_by_field_name("name"), source)
29
+ next_owner = ".".join(part for part in (owner, name) if part)
30
+ body = node.child_by_field_name("body")
31
+ for child in body.children if body is not None else node.children:
32
+ if child.is_named:
33
+ yield from _walk(child, source, next_owner)
34
+ return
35
+ if node.type == "function_definition":
36
+ name, explicit_owner = _qualified_function_name(
37
+ node.child_by_field_name("declarator"), source
38
+ )
39
+ body = node.child_by_field_name("body")
40
+ if name and body is not None:
41
+ yield TSDefinition(name=name, node=node, body=body, owner=explicit_owner or owner)
42
+ return
43
+ for child in node.children:
44
+ if child.is_named:
45
+ yield from _walk(child, source, owner)
46
+
47
+
48
+ def _qualified_function_name(declarator: Any | None, source: bytes) -> tuple[str, str]:
49
+ """Return (name, owner) from a possibly nested/qualified function declarator."""
50
+ target = declarator
51
+ while target is not None:
52
+ inner = target.child_by_field_name("declarator")
53
+ if inner is None:
54
+ break
55
+ target = inner
56
+ identifiers = _identifier_texts(target, source)
57
+ if not identifiers:
58
+ identifiers = _identifier_texts(declarator, source)
59
+ if not identifiers:
60
+ return "", ""
61
+ return identifiers[-1], ".".join(identifiers[:-1])
62
+
63
+
64
+ def _identifier_texts(node: Any | None, source: bytes) -> list[str]:
65
+ if node is None:
66
+ return []
67
+ values: list[str] = []
68
+ stack = [node]
69
+ while stack:
70
+ current = stack.pop()
71
+ if current.type in {"identifier", "field_identifier", "type_identifier", "destructor_name"}:
72
+ values.append(text(current, source).lstrip("~"))
73
+ continue
74
+ stack.extend(reversed(current.children))
75
+ return [value for value in values if value]
76
+
77
+
78
+ def _classify(
79
+ definition: TSDefinition, relative: str, source: str, config: CodeDebriefConfig
80
+ ) -> tuple[str, str, bool]:
81
+ owner_prefix = f"{definition.owner}." if definition.owner else ""
82
+ override = config.entrypoint_override(f"{relative}:{owner_prefix}{definition.name}")
83
+ if definition.name == "main" and not definition.owner:
84
+ return "cpp", "main", override if override is not None else True
85
+ public = config.include_public_functions and not _has_static_storage(definition.node)
86
+ return (
87
+ "generic",
88
+ "method" if definition.owner else "function",
89
+ (override if override is not None else public),
90
+ )
91
+
92
+
93
+ def _has_static_storage(node: Any) -> bool:
94
+ return any(
95
+ child.type == "storage_class_specifier" and child.text.decode() == "static"
96
+ for child in node.children
97
+ )
98
+
99
+
100
+ def _is_test(relative: str, name: str) -> bool:
101
+ lowered = relative.lower()
102
+ segments = lowered.split("/")
103
+ filename = segments[-1]
104
+ return (
105
+ any(segment in {"test", "tests"} for segment in segments[:-1])
106
+ or filename.startswith("test_")
107
+ or filename.endswith(
108
+ (
109
+ "_test.cc",
110
+ "_test.cpp",
111
+ "_test.cxx",
112
+ "_test.hh",
113
+ "_test.hpp",
114
+ "_test.hxx",
115
+ )
116
+ )
117
+ )
118
+
119
+
120
+ CPP_PROFILE = LanguageProfile(
121
+ language="cpp",
122
+ grammar_loader=tree_sitter_cpp.language,
123
+ function_types=frozenset({"function_definition"}),
124
+ definitions=_definitions,
125
+ classify=_classify,
126
+ is_test=_is_test,
127
+ module_name=module_name,
128
+ block_types=frozenset({"compound_statement"}),
129
+ switch_types=frozenset({"switch_statement"}),
130
+ switch_value_field="condition",
131
+ case_types=frozenset({"case_statement"}),
132
+ default_when_no_value=True,
133
+ case_fall_through=True,
134
+ loop_types=frozenset(
135
+ {"for_statement", "while_statement", "do_statement", "range_based_for_statement"}
136
+ ),
137
+ throw_types=frozenset({"throw_statement"}),
138
+ call_types=frozenset({"call_expression"}),
139
+ try_type="try_statement",
140
+ catch_types=frozenset({"catch_clause"}),
141
+ assignment_types=frozenset({"declaration"}),
142
+ )
143
+
144
+
145
+ def build_analyzer(root: Path, config: CodeDebriefConfig) -> TreeSitterAnalyzer:
146
+ return TreeSitterAnalyzer(root, config, CPP_PROFILE)
@@ -0,0 +1,137 @@
1
+ """C# language profile for the profile-driven tree-sitter analyzer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import tree_sitter_c_sharp
9
+
10
+ from codedebrief.analysis.common import DEFAULT as DEFAULT_LABEL
11
+ from codedebrief.analysis.languages._common import container_definitions, module_name, named, text
12
+ from codedebrief.analysis.treesitter import (
13
+ CaseInfo,
14
+ LanguageProfile,
15
+ TreeSitterAnalyzer,
16
+ TSDefinition,
17
+ )
18
+ from codedebrief.config import CodeDebriefConfig
19
+
20
+ _CONTAINERS = frozenset(
21
+ {
22
+ "class_declaration",
23
+ "struct_declaration",
24
+ "record_declaration",
25
+ "interface_declaration",
26
+ "namespace_declaration",
27
+ "file_scoped_namespace_declaration",
28
+ }
29
+ )
30
+ _METHODS = frozenset({"method_declaration", "constructor_declaration", "local_function_statement"})
31
+ _ROUTE_TAGS = ("HttpGet", "HttpPost", "Route", "HttpPut", "HttpDelete")
32
+
33
+
34
+ def _modifiers(node: Any, source: bytes) -> str:
35
+ kinds = {"modifier", "attribute_list"}
36
+ return " ".join(text(c, source) for c in node.children if c.type in kinds)
37
+
38
+
39
+ def _classify(
40
+ definition: TSDefinition, relative: str, source: str, config: CodeDebriefConfig
41
+ ) -> tuple[str, str, bool]:
42
+ override = config.entrypoint_override(f"{relative}:{definition.owner}.{definition.name}")
43
+ modifiers = _modifiers(definition.node, source.encode("utf-8"))
44
+ if definition.name == "Main":
45
+ return "csharp", "main", override if override is not None else True
46
+ if any(tag in modifiers for tag in _ROUTE_TAGS):
47
+ return "aspnet", "route", override if override is not None else True
48
+ public = config.include_public_functions and "public" in modifiers
49
+ return "generic", "method", override if override is not None else public
50
+
51
+
52
+ def _is_test(relative: str, name: str) -> bool:
53
+ # Anchor to path SEGMENTS and the C# *Test.cs / *Tests.cs class-file convention, not a
54
+ # substring of the whole path or a bare `Test`-prefixed method name (`TestRunner`,
55
+ # `TestData`...) - those are real methods. The file suffix is matched case-sensitively
56
+ # so `Latest.cs` (a real class) does not look like a test.
57
+ segments = relative.split("/")
58
+ return any(segment.lower() in {"test", "tests"} for segment in segments[:-1]) or segments[
59
+ -1
60
+ ].endswith(("Test.cs", "Tests.cs"))
61
+
62
+
63
+ def _import_map(root: Any, source: bytes, relative: str) -> dict[str, str]:
64
+ mapping: dict[str, str] = {}
65
+ for directive in root.children:
66
+ if directive.type != "using_directive":
67
+ continue
68
+ specifier = _using_specifier(directive, source)
69
+ if not specifier:
70
+ continue
71
+ alias = directive.child_by_field_name("name")
72
+ is_static = any(child.type == "static" for child in directive.children)
73
+ if alias is not None:
74
+ mapping[text(alias, source)] = f"{specifier}:"
75
+ elif is_static:
76
+ mapping[f"__static_using__:{specifier}"] = f"{specifier}:"
77
+ else:
78
+ mapping[f"__namespace_using__:{specifier}"] = f"{specifier}:"
79
+ return mapping
80
+
81
+
82
+ def _using_specifier(directive: Any, source: bytes) -> str:
83
+ for child in directive.children:
84
+ if child.type in {"qualified_name", "alias_qualified_name"}:
85
+ return text(child, source)
86
+ alias = directive.child_by_field_name("name")
87
+ for child in directive.children:
88
+ if child.type == "identifier" and child is not alias:
89
+ value = text(child, source)
90
+ if value != "static":
91
+ return value
92
+ return ""
93
+
94
+
95
+ def _switch_cases(switch_node: Any, source: bytes, profile: LanguageProfile) -> list[CaseInfo]:
96
+ body = switch_node.child_by_field_name("body")
97
+ cases: list[CaseInfo] = []
98
+ for section in named(body):
99
+ if section.type != "switch_section":
100
+ continue
101
+ labels = [c for c in named(section) if "pattern" in c.type or "switch_label" in c.type]
102
+ statements = [c for c in named(section) if c not in labels]
103
+ values = [text(label, source) for label in labels]
104
+ if not values:
105
+ cases.append(CaseInfo(DEFAULT_LABEL, True, [], statements))
106
+ else:
107
+ cases.append(CaseInfo(", ".join(values), False, values, statements))
108
+ return cases
109
+
110
+
111
+ CSHARP_PROFILE = LanguageProfile(
112
+ language="csharp",
113
+ grammar_loader=tree_sitter_c_sharp.language,
114
+ function_types=_METHODS,
115
+ definitions=container_definitions(_CONTAINERS, _METHODS),
116
+ classify=_classify,
117
+ is_test=_is_test,
118
+ module_name=module_name,
119
+ import_map=_import_map,
120
+ dependency_module_suffixes=(".cs",),
121
+ dependency_package_directories=True,
122
+ switch_types=frozenset({"switch_statement"}),
123
+ switch_value_field="value",
124
+ switch_cases=_switch_cases,
125
+ loop_types=frozenset({"for_statement", "foreach_statement", "while_statement", "do_statement"}),
126
+ throw_types=frozenset({"throw_statement"}),
127
+ call_types=frozenset({"invocation_expression"}),
128
+ try_type="try_statement",
129
+ catch_types=frozenset({"catch_clause"}),
130
+ finally_types=frozenset({"finally_clause"}),
131
+ assignment_types=frozenset({"local_declaration_statement", "assignment_expression"}),
132
+ nested_def_types=frozenset({"lambda_expression", "anonymous_method_expression"}),
133
+ )
134
+
135
+
136
+ def build_analyzer(root: Path, config: CodeDebriefConfig) -> TreeSitterAnalyzer:
137
+ return TreeSitterAnalyzer(root, config, CSHARP_PROFILE)
@@ -0,0 +1,157 @@
1
+ """Go language profile for the profile-driven tree-sitter analyzer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections import deque
6
+ from collections.abc import Iterable
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ import tree_sitter_go
11
+
12
+ from codedebrief.analysis.languages._common import module_name, text
13
+ from codedebrief.analysis.treesitter import (
14
+ LanguageProfile,
15
+ TreeSitterAnalyzer,
16
+ TSDefinition,
17
+ )
18
+ from codedebrief.config import CodeDebriefConfig
19
+ from codedebrief.model import Flow
20
+
21
+ _TEST_PREFIXES = ("Test", "Benchmark", "Example", "Fuzz")
22
+
23
+
24
+ def _definitions(
25
+ root: Any, source: bytes, relative: str, profile: LanguageProfile
26
+ ) -> Iterable[TSDefinition]:
27
+ for node in root.children:
28
+ if node.type == "function_declaration":
29
+ name = text(node.child_by_field_name("name"), source)
30
+ body = node.child_by_field_name("body")
31
+ if name and body is not None:
32
+ yield TSDefinition(name=name, node=node, body=body, owner="")
33
+ elif node.type == "method_declaration":
34
+ name = text(node.child_by_field_name("name"), source)
35
+ body = node.child_by_field_name("body")
36
+ owner = _receiver_type(node.child_by_field_name("receiver"), source)
37
+ if name and body is not None:
38
+ yield TSDefinition(name=name, node=node, body=body, owner=owner)
39
+
40
+
41
+ def _receiver_type(receiver: Any | None, source: bytes) -> str:
42
+ if receiver is None:
43
+ return ""
44
+ stack = [receiver]
45
+ while stack:
46
+ current = stack.pop()
47
+ if current.type == "type_identifier":
48
+ return text(current, source)
49
+ stack.extend(current.children)
50
+ return ""
51
+
52
+
53
+ def _import_map(root: Any, source: bytes, relative: str) -> dict[str, str]:
54
+ mapping: dict[str, str] = {}
55
+ for declaration in root.children:
56
+ if declaration.type != "import_declaration":
57
+ continue
58
+ for spec in _import_specs(declaration):
59
+ path = _import_path(spec, source)
60
+ if not path:
61
+ continue
62
+ module = path.replace("/", ".").strip(".")
63
+ if not module:
64
+ continue
65
+ alias = _import_alias(spec, source)
66
+ if alias in {"_", "."}:
67
+ mapping[f"__side_effect_import__:{module}"] = f"{module}:"
68
+ continue
69
+ binding = alias or path.rsplit("/", 1)[-1]
70
+ if binding:
71
+ mapping[binding] = f"{module}:"
72
+ return mapping
73
+
74
+
75
+ def _import_specs(declaration: Any) -> Iterable[Any]:
76
+ stack = deque(declaration.children)
77
+ while stack:
78
+ current = stack.popleft()
79
+ if current.type == "import_spec":
80
+ yield current
81
+ continue
82
+ stack.extendleft(reversed(current.children))
83
+
84
+
85
+ def _import_path(spec: Any, source: bytes) -> str:
86
+ literal = next(
87
+ (
88
+ child
89
+ for child in spec.children
90
+ if child.type in {"interpreted_string_literal", "raw_string_literal"}
91
+ ),
92
+ None,
93
+ )
94
+ return text(literal, source).strip('"`')
95
+
96
+
97
+ def _import_alias(spec: Any, source: bytes) -> str:
98
+ for child in spec.children:
99
+ if child.type in {"package_identifier", "blank_identifier", "dot"}:
100
+ return text(child, source)
101
+ return ""
102
+
103
+
104
+ def _classify(
105
+ definition: TSDefinition, relative: str, source: str, config: CodeDebriefConfig
106
+ ) -> tuple[str, str, bool]:
107
+ owner_prefix = f"{definition.owner}." if definition.owner else ""
108
+ override = config.entrypoint_override(f"{relative}:{owner_prefix}{definition.name}")
109
+ exported = definition.name[:1].isupper()
110
+ if definition.name == "main" and not definition.owner:
111
+ return "go", "main", override if override is not None else True
112
+ entry_kind = "method" if definition.owner else "function"
113
+ public = config.include_public_functions and exported
114
+ return "generic", entry_kind, override if override is not None else public
115
+
116
+
117
+ def _is_test(relative: str, name: str) -> bool:
118
+ return relative.endswith("_test.go") or name.startswith(_TEST_PREFIXES)
119
+
120
+
121
+ def _dependency_path_filter(relative: str) -> bool:
122
+ return not relative.endswith("_test.go")
123
+
124
+
125
+ def _entry_label(flow: Flow) -> str:
126
+ prefix = {"main": "Main", "test": "Test"}.get(flow.entry_kind)
127
+ return f"{prefix}: {flow.name}" if prefix else flow.name
128
+
129
+
130
+ GO_PROFILE = LanguageProfile(
131
+ language="go",
132
+ grammar_loader=tree_sitter_go.language,
133
+ function_types=frozenset({"function_declaration", "method_declaration"}),
134
+ definitions=_definitions,
135
+ classify=_classify,
136
+ is_test=_is_test,
137
+ module_name=module_name,
138
+ import_map=_import_map,
139
+ dependency_module_suffixes=(".go",),
140
+ dependency_package_directories=True,
141
+ dependency_path_filter=_dependency_path_filter,
142
+ entry_label=_entry_label,
143
+ switch_types=frozenset({"expression_switch_statement", "type_switch_statement"}),
144
+ switch_body_field=None,
145
+ case_types=frozenset({"expression_case", "type_case"}),
146
+ default_types=frozenset({"default_case", "communication_case"}),
147
+ case_value_field="value",
148
+ loop_types=frozenset({"for_statement"}),
149
+ throw_types=frozenset(),
150
+ assignment_types=frozenset({"short_var_declaration", "assignment_statement"}),
151
+ assignment_target_field="left",
152
+ nested_def_types=frozenset({"func_literal"}),
153
+ )
154
+
155
+
156
+ def build_analyzer(root: Path, config: CodeDebriefConfig) -> TreeSitterAnalyzer:
157
+ return TreeSitterAnalyzer(root, config, GO_PROFILE)