cmdop-coder 0.1.1__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.
- cmdop_coder/__init__.py +28 -0
- cmdop_coder/_analysis.py +191 -0
- cmdop_coder/_models.py +67 -0
- cmdop_coder/_parser.py +76 -0
- cmdop_coder/_skill.py +45 -0
- cmdop_coder-0.1.1.dist-info/METADATA +117 -0
- cmdop_coder-0.1.1.dist-info/RECORD +9 -0
- cmdop_coder-0.1.1.dist-info/WHEEL +4 -0
- cmdop_coder-0.1.1.dist-info/entry_points.txt +2 -0
cmdop_coder/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""cmdop-coder — code analysis skill using tree-sitter AST parsing."""
|
|
2
|
+
|
|
3
|
+
from cmdop_coder._analysis import analyze_file, extract_functions, find_symbol, get_outline
|
|
4
|
+
from cmdop_coder._models import (
|
|
5
|
+
AnalyzeResult,
|
|
6
|
+
FunctionInfo,
|
|
7
|
+
FunctionsResult,
|
|
8
|
+
OutlineItem,
|
|
9
|
+
OutlineResult,
|
|
10
|
+
SymbolMatch,
|
|
11
|
+
SymbolsResult,
|
|
12
|
+
)
|
|
13
|
+
from cmdop_coder._skill import skill
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"AnalyzeResult",
|
|
17
|
+
"FunctionInfo",
|
|
18
|
+
"FunctionsResult",
|
|
19
|
+
"OutlineItem",
|
|
20
|
+
"OutlineResult",
|
|
21
|
+
"SymbolMatch",
|
|
22
|
+
"SymbolsResult",
|
|
23
|
+
"analyze_file",
|
|
24
|
+
"extract_functions",
|
|
25
|
+
"find_symbol",
|
|
26
|
+
"get_outline",
|
|
27
|
+
"skill",
|
|
28
|
+
]
|
cmdop_coder/_analysis.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Code analysis functions using tree-sitter AST."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ._models import (
|
|
8
|
+
AnalyzeResult,
|
|
9
|
+
FunctionInfo,
|
|
10
|
+
FunctionsResult,
|
|
11
|
+
OutlineItem,
|
|
12
|
+
OutlineResult,
|
|
13
|
+
SymbolMatch,
|
|
14
|
+
SymbolsResult,
|
|
15
|
+
)
|
|
16
|
+
from ._parser import detect_language, parse_file
|
|
17
|
+
|
|
18
|
+
# Node types that represent function/method definitions per language
|
|
19
|
+
_FUNCTION_NODES: dict[str, list[str]] = {
|
|
20
|
+
"python": ["function_definition", "async_function_definition"],
|
|
21
|
+
"javascript": ["function_declaration", "arrow_function", "method_definition", "function_expression"],
|
|
22
|
+
"typescript": ["function_declaration", "arrow_function", "method_definition", "function_expression"],
|
|
23
|
+
"tsx": ["function_declaration", "arrow_function", "method_definition", "function_expression"],
|
|
24
|
+
"go": ["function_declaration", "method_declaration"],
|
|
25
|
+
"rust": ["function_item"],
|
|
26
|
+
"java": ["method_declaration", "constructor_declaration"],
|
|
27
|
+
"c": ["function_definition"],
|
|
28
|
+
"cpp": ["function_definition"],
|
|
29
|
+
"ruby": ["method", "singleton_method"],
|
|
30
|
+
"php": ["function_definition", "method_declaration"],
|
|
31
|
+
"swift": ["function_declaration"],
|
|
32
|
+
"kotlin": ["function_declaration"],
|
|
33
|
+
"c_sharp": ["method_declaration", "constructor_declaration"],
|
|
34
|
+
"lua": ["function_definition", "local_function"],
|
|
35
|
+
"scala": ["function_definition"],
|
|
36
|
+
"elixir": ["def", "defp"],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Node types for outline (structural elements) per language
|
|
40
|
+
_OUTLINE_NODES: dict[str, list[str]] = {
|
|
41
|
+
"python": ["import_statement", "import_from_statement", "class_definition",
|
|
42
|
+
"function_definition", "async_function_definition"],
|
|
43
|
+
"javascript": ["import_declaration", "class_declaration", "function_declaration",
|
|
44
|
+
"lexical_declaration", "variable_declaration"],
|
|
45
|
+
"typescript": ["import_declaration", "class_declaration", "function_declaration",
|
|
46
|
+
"interface_declaration", "type_alias_declaration", "enum_declaration"],
|
|
47
|
+
"tsx": ["import_declaration", "class_declaration", "function_declaration",
|
|
48
|
+
"interface_declaration", "type_alias_declaration"],
|
|
49
|
+
"go": ["import_declaration", "type_declaration", "function_declaration",
|
|
50
|
+
"method_declaration", "var_declaration", "const_declaration"],
|
|
51
|
+
"rust": ["use_declaration", "struct_item", "enum_item", "function_item",
|
|
52
|
+
"impl_item", "trait_item", "mod_item"],
|
|
53
|
+
"java": ["import_declaration", "class_declaration", "interface_declaration",
|
|
54
|
+
"method_declaration"],
|
|
55
|
+
"c": ["preproc_include", "struct_specifier", "function_definition", "type_definition"],
|
|
56
|
+
"cpp": ["preproc_include", "class_specifier", "struct_specifier",
|
|
57
|
+
"function_definition", "namespace_definition"],
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
_IDENTIFIER_TYPES = frozenset({
|
|
61
|
+
"identifier", "name", "property_identifier",
|
|
62
|
+
"type_identifier", "field_identifier",
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _node_name(node: object, source: bytes) -> str:
|
|
67
|
+
"""Extract the name identifier text from an AST node."""
|
|
68
|
+
for child in node.children: # type: ignore[union-attr]
|
|
69
|
+
if child.type in _IDENTIFIER_TYPES:
|
|
70
|
+
return source[child.start_byte:child.end_byte].decode(errors="replace")
|
|
71
|
+
raw = source[node.start_byte:node.start_byte + 60] # type: ignore[union-attr]
|
|
72
|
+
return raw.decode(errors="replace").split("\n")[0]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _collect_nodes(node: object, types: frozenset[str], depth: int = 0, max_depth: int = 20) -> list[object]:
|
|
76
|
+
"""Walk AST and collect nodes matching the given types."""
|
|
77
|
+
if depth > max_depth:
|
|
78
|
+
return []
|
|
79
|
+
results: list[object] = []
|
|
80
|
+
if node.type in types: # type: ignore[union-attr]
|
|
81
|
+
results.append(node)
|
|
82
|
+
for child in node.children: # type: ignore[union-attr]
|
|
83
|
+
results.extend(_collect_nodes(child, types, depth + 1, max_depth))
|
|
84
|
+
return results
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _first_line(node: object, source: bytes) -> str:
|
|
88
|
+
"""Return the first line of a node's source text, truncated to 120 chars."""
|
|
89
|
+
raw = source[node.start_byte:node.end_byte] # type: ignore[union-attr]
|
|
90
|
+
return raw.decode(errors="replace").split("\n")[0][:120]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def extract_functions(path: str | Path) -> FunctionsResult:
|
|
94
|
+
"""Extract function/method signatures with line numbers from a source file."""
|
|
95
|
+
tree, source, lang = parse_file(path)
|
|
96
|
+
types = frozenset(_FUNCTION_NODES.get(lang, ["function_definition", "function_declaration"]))
|
|
97
|
+
nodes = _collect_nodes(tree.root_node, types) # type: ignore[union-attr]
|
|
98
|
+
|
|
99
|
+
functions = [
|
|
100
|
+
FunctionInfo(
|
|
101
|
+
line=node.start_point[0] + 1, # type: ignore[union-attr]
|
|
102
|
+
name=_node_name(node, source),
|
|
103
|
+
signature=_first_line(node, source),
|
|
104
|
+
)
|
|
105
|
+
for node in nodes
|
|
106
|
+
]
|
|
107
|
+
return FunctionsResult(file=str(path), language=lang, count=len(functions), functions=functions)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def find_symbol(symbol: str, path: str | Path = ".") -> SymbolsResult:
|
|
111
|
+
"""Find all occurrences of a symbol across source files."""
|
|
112
|
+
p = Path(path)
|
|
113
|
+
candidates = [p] if p.is_file() else list(p.rglob("*"))
|
|
114
|
+
matches: list[SymbolMatch] = []
|
|
115
|
+
|
|
116
|
+
for f in candidates:
|
|
117
|
+
if not f.is_file() or detect_language(f) is None:
|
|
118
|
+
continue
|
|
119
|
+
try:
|
|
120
|
+
lines = f.read_bytes().decode(errors="replace").splitlines()
|
|
121
|
+
except OSError:
|
|
122
|
+
continue
|
|
123
|
+
for i, line in enumerate(lines, 1):
|
|
124
|
+
if symbol in line:
|
|
125
|
+
matches.append(SymbolMatch(file=str(f), line=i, text=line.strip()))
|
|
126
|
+
|
|
127
|
+
return SymbolsResult(symbol=symbol, count=len(matches), matches=matches)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_outline(path: str | Path) -> OutlineResult:
|
|
131
|
+
"""Get structural outline of a source file (imports, classes, functions, types)."""
|
|
132
|
+
tree, source, lang = parse_file(path)
|
|
133
|
+
types = frozenset(_OUTLINE_NODES.get(lang, []))
|
|
134
|
+
|
|
135
|
+
if not types:
|
|
136
|
+
items = _fallback_outline(tree.root_node, source) # type: ignore[union-attr]
|
|
137
|
+
return OutlineResult(file=str(path), language=lang, count=len(items), outline=items)
|
|
138
|
+
|
|
139
|
+
nodes = _collect_nodes(tree.root_node, types, max_depth=3) # type: ignore[union-attr]
|
|
140
|
+
items = [
|
|
141
|
+
OutlineItem(
|
|
142
|
+
line=node.start_point[0] + 1, # type: ignore[union-attr]
|
|
143
|
+
type=node.type, # type: ignore[union-attr]
|
|
144
|
+
name=_node_name(node, source),
|
|
145
|
+
)
|
|
146
|
+
for node in nodes
|
|
147
|
+
]
|
|
148
|
+
return OutlineResult(file=str(path), language=lang, count=len(items), outline=items)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _fallback_outline(root: object, source: bytes) -> list[OutlineItem]:
|
|
152
|
+
"""Return top-level nodes as outline when language has no specific config."""
|
|
153
|
+
return [
|
|
154
|
+
OutlineItem(
|
|
155
|
+
line=child.start_point[0] + 1, # type: ignore[union-attr]
|
|
156
|
+
type=child.type, # type: ignore[union-attr]
|
|
157
|
+
name=_first_line(child, source)[:60],
|
|
158
|
+
)
|
|
159
|
+
for child in root.children # type: ignore[union-attr]
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def analyze_file(path: str | Path) -> AnalyzeResult:
|
|
164
|
+
"""Analyze a source file: language detection, line counts, function count."""
|
|
165
|
+
p = Path(path)
|
|
166
|
+
source = p.read_bytes()
|
|
167
|
+
lang = detect_language(p)
|
|
168
|
+
lines = source.decode(errors="replace").splitlines()
|
|
169
|
+
|
|
170
|
+
blank = sum(1 for ln in lines if not ln.strip())
|
|
171
|
+
comment = sum(1 for ln in lines if ln.strip().startswith(("#", "//", "/*", "*")))
|
|
172
|
+
code = len(lines) - blank - comment
|
|
173
|
+
|
|
174
|
+
function_count: int | None = None
|
|
175
|
+
if lang and lang in _FUNCTION_NODES:
|
|
176
|
+
try:
|
|
177
|
+
function_count = extract_functions(p).count
|
|
178
|
+
except Exception:
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
return AnalyzeResult(
|
|
182
|
+
file=str(p),
|
|
183
|
+
language=lang or "unknown",
|
|
184
|
+
extension=p.suffix,
|
|
185
|
+
size_bytes=len(source),
|
|
186
|
+
total_lines=len(lines),
|
|
187
|
+
code_lines=code,
|
|
188
|
+
blank_lines=blank,
|
|
189
|
+
comment_lines=comment,
|
|
190
|
+
function_count=function_count,
|
|
191
|
+
)
|
cmdop_coder/_models.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Data models for cmdop-coder results."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FunctionInfo(BaseModel):
|
|
7
|
+
"""A single function or method extracted from source code."""
|
|
8
|
+
|
|
9
|
+
line: int
|
|
10
|
+
name: str
|
|
11
|
+
signature: str
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FunctionsResult(BaseModel):
|
|
15
|
+
"""Result of extracting functions from a file."""
|
|
16
|
+
|
|
17
|
+
file: str
|
|
18
|
+
language: str
|
|
19
|
+
count: int
|
|
20
|
+
functions: list[FunctionInfo]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SymbolMatch(BaseModel):
|
|
24
|
+
"""A single occurrence of a symbol in source code."""
|
|
25
|
+
|
|
26
|
+
file: str
|
|
27
|
+
line: int
|
|
28
|
+
text: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SymbolsResult(BaseModel):
|
|
32
|
+
"""Result of searching for a symbol across files."""
|
|
33
|
+
|
|
34
|
+
symbol: str
|
|
35
|
+
count: int
|
|
36
|
+
matches: list[SymbolMatch]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class OutlineItem(BaseModel):
|
|
40
|
+
"""A structural element in a source file outline."""
|
|
41
|
+
|
|
42
|
+
line: int
|
|
43
|
+
type: str
|
|
44
|
+
name: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class OutlineResult(BaseModel):
|
|
48
|
+
"""Result of generating a structural outline for a file."""
|
|
49
|
+
|
|
50
|
+
file: str
|
|
51
|
+
language: str
|
|
52
|
+
count: int
|
|
53
|
+
outline: list[OutlineItem]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class AnalyzeResult(BaseModel):
|
|
57
|
+
"""Statistics for a source file."""
|
|
58
|
+
|
|
59
|
+
file: str
|
|
60
|
+
language: str
|
|
61
|
+
extension: str
|
|
62
|
+
size_bytes: int
|
|
63
|
+
total_lines: int
|
|
64
|
+
code_lines: int
|
|
65
|
+
blank_lines: int
|
|
66
|
+
comment_lines: int
|
|
67
|
+
function_count: int | None = None
|
cmdop_coder/_parser.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Tree-sitter parser utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
# Language detection by file extension
|
|
8
|
+
_EXT_TO_LANG: dict[str, str] = {
|
|
9
|
+
".py": "python",
|
|
10
|
+
".js": "javascript",
|
|
11
|
+
".jsx": "javascript",
|
|
12
|
+
".ts": "typescript",
|
|
13
|
+
".tsx": "tsx",
|
|
14
|
+
".go": "go",
|
|
15
|
+
".rs": "rust",
|
|
16
|
+
".java": "java",
|
|
17
|
+
".c": "c",
|
|
18
|
+
".h": "c",
|
|
19
|
+
".cpp": "cpp",
|
|
20
|
+
".cc": "cpp",
|
|
21
|
+
".cxx": "cpp",
|
|
22
|
+
".hpp": "cpp",
|
|
23
|
+
".rb": "ruby",
|
|
24
|
+
".php": "php",
|
|
25
|
+
".swift": "swift",
|
|
26
|
+
".kt": "kotlin",
|
|
27
|
+
".cs": "c_sharp",
|
|
28
|
+
".css": "css",
|
|
29
|
+
".html": "html",
|
|
30
|
+
".json": "json",
|
|
31
|
+
".yaml": "yaml",
|
|
32
|
+
".yml": "yaml",
|
|
33
|
+
".toml": "toml",
|
|
34
|
+
".sh": "bash",
|
|
35
|
+
".bash": "bash",
|
|
36
|
+
".sql": "sql",
|
|
37
|
+
".lua": "lua",
|
|
38
|
+
".r": "r",
|
|
39
|
+
".scala": "scala",
|
|
40
|
+
".ex": "elixir",
|
|
41
|
+
".exs": "elixir",
|
|
42
|
+
".elm": "elm",
|
|
43
|
+
".hs": "haskell",
|
|
44
|
+
".ml": "ocaml",
|
|
45
|
+
".tf": "hcl",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def detect_language(path: str | Path) -> str | None:
|
|
50
|
+
"""Detect tree-sitter language name from file path."""
|
|
51
|
+
p = Path(path)
|
|
52
|
+
name = p.name.lower()
|
|
53
|
+
if name == "dockerfile" or name.startswith("dockerfile."):
|
|
54
|
+
return "dockerfile"
|
|
55
|
+
return _EXT_TO_LANG.get(p.suffix.lower())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_parser(language: str) -> object:
|
|
59
|
+
"""Return a tree-sitter parser for the given language name."""
|
|
60
|
+
try:
|
|
61
|
+
from tree_sitter_language_pack import get_parser as _get_parser # type: ignore[import]
|
|
62
|
+
return _get_parser(language)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
raise RuntimeError(f"Cannot load tree-sitter parser for {language!r}: {e}") from e
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def parse_file(path: str | Path) -> tuple[object, bytes, str]:
|
|
68
|
+
"""Parse a source file, returning (tree, source_bytes, language)."""
|
|
69
|
+
p = Path(path)
|
|
70
|
+
lang = detect_language(p)
|
|
71
|
+
if lang is None:
|
|
72
|
+
raise ValueError(f"Unsupported file type: {p.suffix!r}")
|
|
73
|
+
source = p.read_bytes()
|
|
74
|
+
parser = get_parser(lang)
|
|
75
|
+
tree = parser.parse(source) # type: ignore[union-attr]
|
|
76
|
+
return tree, source, lang
|
cmdop_coder/_skill.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""cmdop-coder CMDOP skill — code analysis with tree-sitter AST."""
|
|
2
|
+
|
|
3
|
+
from cmdop_skill import Arg, Skill
|
|
4
|
+
|
|
5
|
+
from cmdop_coder._analysis import analyze_file, extract_functions, find_symbol, get_outline
|
|
6
|
+
|
|
7
|
+
skill = Skill()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@skill.command
|
|
11
|
+
def functions(
|
|
12
|
+
path: str = Arg(help="Path to source file", required=True),
|
|
13
|
+
) -> dict:
|
|
14
|
+
"""Extract function/method signatures with line numbers."""
|
|
15
|
+
return extract_functions(path).model_dump()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@skill.command
|
|
19
|
+
def symbols(
|
|
20
|
+
symbol: str = Arg(help="Symbol name to find", required=True),
|
|
21
|
+
path: str = Arg("--path", default=".", help="File or directory to search (default: current dir)"),
|
|
22
|
+
) -> dict:
|
|
23
|
+
"""Find all occurrences of a symbol across source files."""
|
|
24
|
+
return find_symbol(symbol, path).model_dump()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@skill.command
|
|
28
|
+
def outline(
|
|
29
|
+
path: str = Arg(help="Path to source file", required=True),
|
|
30
|
+
) -> dict:
|
|
31
|
+
"""Get structural outline: imports, classes, functions, types."""
|
|
32
|
+
return get_outline(path).model_dump()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@skill.command
|
|
36
|
+
def analyze(
|
|
37
|
+
path: str = Arg(help="Path to source file", required=True),
|
|
38
|
+
) -> dict:
|
|
39
|
+
"""Analyze a file: language, line counts, function count."""
|
|
40
|
+
return analyze_file(path).model_dump()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def main() -> None:
|
|
44
|
+
"""CLI entry point."""
|
|
45
|
+
skill.run()
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cmdop-coder
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: CMDOP skill — code analysis with tree-sitter AST parsing
|
|
5
|
+
Project-URL: Homepage, https://cmdop.com/skills/cmdop-coder/
|
|
6
|
+
Author-email: CMDOP Team <team@cmdop.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Keywords: ast,cmdop,code,skill,tree-sitter
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: cmdop
|
|
18
|
+
Requires-Dist: cmdop-skill
|
|
19
|
+
Requires-Dist: pydantic>=2.0
|
|
20
|
+
Requires-Dist: tree-sitter-language-pack>=0.1
|
|
21
|
+
Requires-Dist: tree-sitter>=0.21
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# cmdop-coder
|
|
30
|
+
|
|
31
|
+
> **[CMDOP Skill](https://cmdop.com/skills/cmdop-coder/)** — install and use via [CMDOP agent](https://cmdop.com):
|
|
32
|
+
> ```
|
|
33
|
+
> cmdop-skill install cmdop-coder
|
|
34
|
+
> ```
|
|
35
|
+
|
|
36
|
+
Code analysis using tree-sitter AST parsing. Extract functions, find symbols, get structural outlines. Supports 40+ languages.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install cmdop-coder
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or as a CMDOP skill:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
cmdop-skill install path/to/cmdop-coder
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## CLI
|
|
51
|
+
|
|
52
|
+
### Extract functions
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cmdop-coder functions --path src/main.py
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"file": "src/main.py",
|
|
61
|
+
"language": "python",
|
|
62
|
+
"count": 3,
|
|
63
|
+
"functions": [
|
|
64
|
+
{"line": 5, "name": "hello", "signature": "def hello(name: str) -> str:"},
|
|
65
|
+
{"line": 9, "name": "fetch", "signature": "async def fetch(url: str) -> bytes:"}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Find symbol
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
cmdop-coder symbols --symbol MyClass --path ./src
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Structural outline
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
cmdop-coder outline --path internal/agent/core/agent.go
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### File statistics
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
cmdop-coder analyze --path service.py
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Python API
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from cmdop_coder import extract_functions, find_symbol, get_outline, analyze_file
|
|
92
|
+
|
|
93
|
+
# Extract all functions from a file
|
|
94
|
+
result = extract_functions("src/main.py")
|
|
95
|
+
for fn in result.functions:
|
|
96
|
+
print(fn.line, fn.name, fn.signature)
|
|
97
|
+
|
|
98
|
+
# Find symbol across a directory
|
|
99
|
+
matches = find_symbol("MyClass", "./src")
|
|
100
|
+
for m in matches.matches:
|
|
101
|
+
print(m.file, m.line, m.text)
|
|
102
|
+
|
|
103
|
+
# Structural outline
|
|
104
|
+
outline = get_outline("main.go")
|
|
105
|
+
for item in outline.outline:
|
|
106
|
+
print(item.line, item.type, item.name)
|
|
107
|
+
|
|
108
|
+
# File statistics
|
|
109
|
+
stats = analyze_file("service.py")
|
|
110
|
+
print(stats.language, stats.total_lines, stats.function_count)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Supported Languages
|
|
114
|
+
|
|
115
|
+
Go, Python, JavaScript, TypeScript, TSX, Rust, Java, C, C++, Ruby, PHP,
|
|
116
|
+
Swift, Kotlin, C#, CSS, HTML, JSON, YAML, TOML, Bash, SQL, Lua, Scala,
|
|
117
|
+
Elixir, Elm, Haskell, OCaml, HCL, Dockerfile and more.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
cmdop_coder/__init__.py,sha256=HuOe8Snmf-KFnBlZ4SYAUQDHJF27yHWm6q2AXQau6gQ,624
|
|
2
|
+
cmdop_coder/_analysis.py,sha256=dB3TFUTFhe-p6ZseGdFW7o1c_Eu-S82Br4PK6FCmPiY,7943
|
|
3
|
+
cmdop_coder/_models.py,sha256=sA0fBTEfm9-64KB6Qn17jX1rjGI5MIwvWNehI6pisO8,1259
|
|
4
|
+
cmdop_coder/_parser.py,sha256=lm0JOsZMaAAldA3S38An2d6RYI-ZaC_eC2EwjwXAXqo,2003
|
|
5
|
+
cmdop_coder/_skill.py,sha256=oL2KCj3z6e5Wrd3u5cHKHDHburSTInd5nuWzWpwNk7c,1254
|
|
6
|
+
cmdop_coder-0.1.1.dist-info/METADATA,sha256=u3zXfQLP4N1AjFrcbLslpVsenNS8nsJWrPx25mYccBo,2954
|
|
7
|
+
cmdop_coder-0.1.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
8
|
+
cmdop_coder-0.1.1.dist-info/entry_points.txt,sha256=ikr1Pfh1IWXbvxPXhkICjAZ2DLWx4VFqkf6EJPX-ZAI,56
|
|
9
|
+
cmdop_coder-0.1.1.dist-info/RECORD,,
|