cmdop-coder 0.1.1__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.
- cmdop_coder-0.1.1/.gitignore +43 -0
- cmdop_coder-0.1.1/Makefile +32 -0
- cmdop_coder-0.1.1/PKG-INFO +117 -0
- cmdop_coder-0.1.1/README.md +89 -0
- cmdop_coder-0.1.1/pyproject.toml +61 -0
- cmdop_coder-0.1.1/skill/config.py +6 -0
- cmdop_coder-0.1.1/skill/readme.md +24 -0
- cmdop_coder-0.1.1/src/cmdop_coder/__init__.py +28 -0
- cmdop_coder-0.1.1/src/cmdop_coder/_analysis.py +191 -0
- cmdop_coder-0.1.1/src/cmdop_coder/_models.py +67 -0
- cmdop_coder-0.1.1/src/cmdop_coder/_parser.py +76 -0
- cmdop_coder-0.1.1/src/cmdop_coder/_skill.py +45 -0
- cmdop_coder-0.1.1/tests/conftest.py +24 -0
- cmdop_coder-0.1.1/tests/test_analysis.py +210 -0
- cmdop_coder-0.1.1/tests/test_models.py +115 -0
- cmdop_coder-0.1.1/tests/test_parser.py +71 -0
- cmdop_coder-0.1.1/tests/test_skill.py +133 -0
- cmdop_coder-0.1.1/uv.lock +1107 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Logs
|
|
2
|
+
.benchmarks
|
|
3
|
+
|
|
4
|
+
# Dependencies
|
|
5
|
+
node_modules/
|
|
6
|
+
.pnpm-store/
|
|
7
|
+
**/node_modules/
|
|
8
|
+
|
|
9
|
+
# Python
|
|
10
|
+
__pycache__/
|
|
11
|
+
*.py[cod]
|
|
12
|
+
*$py.class
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
|
|
17
|
+
# Go
|
|
18
|
+
*.exe
|
|
19
|
+
*.exe~
|
|
20
|
+
*.dll
|
|
21
|
+
*.so
|
|
22
|
+
*.dylib
|
|
23
|
+
|
|
24
|
+
# IDE
|
|
25
|
+
.idea/
|
|
26
|
+
.vscode/
|
|
27
|
+
*.swp
|
|
28
|
+
*.swo
|
|
29
|
+
*~
|
|
30
|
+
|
|
31
|
+
# OS
|
|
32
|
+
.DS_Store
|
|
33
|
+
Thumbs.db
|
|
34
|
+
|
|
35
|
+
# Build outputs
|
|
36
|
+
dist/
|
|
37
|
+
build/
|
|
38
|
+
*.egg-info/
|
|
39
|
+
|
|
40
|
+
# Temporary files
|
|
41
|
+
*.log
|
|
42
|
+
*.tmp
|
|
43
|
+
.env.local
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
PYTHON := uv run --python .venv python
|
|
2
|
+
|
|
3
|
+
.PHONY: help install test lint install-skill publish release
|
|
4
|
+
|
|
5
|
+
help:
|
|
6
|
+
@echo "cmdop-coder"
|
|
7
|
+
@echo ""
|
|
8
|
+
@echo " make install Create venv and install in development mode"
|
|
9
|
+
@echo " make test Run tests"
|
|
10
|
+
@echo " make lint Run linter"
|
|
11
|
+
@echo " make install-skill Register skill via cmdop-skill install"
|
|
12
|
+
@echo " make publish Publish skill to CMDOP marketplace"
|
|
13
|
+
@echo " make release Bump + build + upload to PyPI"
|
|
14
|
+
|
|
15
|
+
install:
|
|
16
|
+
uv venv
|
|
17
|
+
uv pip install --python .venv -e '.[dev]'
|
|
18
|
+
|
|
19
|
+
test:
|
|
20
|
+
$(PYTHON) -m pytest tests/ -v
|
|
21
|
+
|
|
22
|
+
lint:
|
|
23
|
+
$(PYTHON) -m ruff check src/
|
|
24
|
+
|
|
25
|
+
install-skill:
|
|
26
|
+
cmdop-skill install .
|
|
27
|
+
|
|
28
|
+
publish:
|
|
29
|
+
cmdop-skill publish --path .
|
|
30
|
+
|
|
31
|
+
release:
|
|
32
|
+
cmdop-skill release .
|
|
@@ -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,89 @@
|
|
|
1
|
+
# cmdop-coder
|
|
2
|
+
|
|
3
|
+
> **[CMDOP Skill](https://cmdop.com/skills/cmdop-coder/)** — install and use via [CMDOP agent](https://cmdop.com):
|
|
4
|
+
> ```
|
|
5
|
+
> cmdop-skill install cmdop-coder
|
|
6
|
+
> ```
|
|
7
|
+
|
|
8
|
+
Code analysis using tree-sitter AST parsing. Extract functions, find symbols, get structural outlines. Supports 40+ languages.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install cmdop-coder
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or as a CMDOP skill:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cmdop-skill install path/to/cmdop-coder
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## CLI
|
|
23
|
+
|
|
24
|
+
### Extract functions
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cmdop-coder functions --path src/main.py
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"file": "src/main.py",
|
|
33
|
+
"language": "python",
|
|
34
|
+
"count": 3,
|
|
35
|
+
"functions": [
|
|
36
|
+
{"line": 5, "name": "hello", "signature": "def hello(name: str) -> str:"},
|
|
37
|
+
{"line": 9, "name": "fetch", "signature": "async def fetch(url: str) -> bytes:"}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Find symbol
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
cmdop-coder symbols --symbol MyClass --path ./src
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Structural outline
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cmdop-coder outline --path internal/agent/core/agent.go
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### File statistics
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
cmdop-coder analyze --path service.py
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Python API
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from cmdop_coder import extract_functions, find_symbol, get_outline, analyze_file
|
|
64
|
+
|
|
65
|
+
# Extract all functions from a file
|
|
66
|
+
result = extract_functions("src/main.py")
|
|
67
|
+
for fn in result.functions:
|
|
68
|
+
print(fn.line, fn.name, fn.signature)
|
|
69
|
+
|
|
70
|
+
# Find symbol across a directory
|
|
71
|
+
matches = find_symbol("MyClass", "./src")
|
|
72
|
+
for m in matches.matches:
|
|
73
|
+
print(m.file, m.line, m.text)
|
|
74
|
+
|
|
75
|
+
# Structural outline
|
|
76
|
+
outline = get_outline("main.go")
|
|
77
|
+
for item in outline.outline:
|
|
78
|
+
print(item.line, item.type, item.name)
|
|
79
|
+
|
|
80
|
+
# File statistics
|
|
81
|
+
stats = analyze_file("service.py")
|
|
82
|
+
print(stats.language, stats.total_lines, stats.function_count)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Supported Languages
|
|
86
|
+
|
|
87
|
+
Go, Python, JavaScript, TypeScript, TSX, Rust, Java, C, C++, Ruby, PHP,
|
|
88
|
+
Swift, Kotlin, C#, CSS, HTML, JSON, YAML, TOML, Bash, SQL, Lua, Scala,
|
|
89
|
+
Elixir, Elm, Haskell, OCaml, HCL, Dockerfile and more.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cmdop-coder"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "CMDOP skill — code analysis with tree-sitter AST parsing"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "CMDOP Team", email = "team@cmdop.com" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["cmdop", "skill", "code", "ast", "tree-sitter"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
dependencies = [
|
|
27
|
+
"cmdop",
|
|
28
|
+
"cmdop-skill",
|
|
29
|
+
"pydantic>=2.0",
|
|
30
|
+
"tree-sitter>=0.21",
|
|
31
|
+
"tree-sitter-language-pack>=0.1",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=7.0.0",
|
|
37
|
+
"pytest-asyncio>=0.21.0",
|
|
38
|
+
"pytest-cov>=4.0.0",
|
|
39
|
+
"ruff>=0.1.0",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
cmdop-coder = "cmdop_coder._skill:main"
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Homepage = "https://cmdop.com/skills/cmdop-coder/"
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.wheel]
|
|
49
|
+
packages = ["src/cmdop_coder"]
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
target-version = "py310"
|
|
53
|
+
line-length = 100
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint]
|
|
56
|
+
select = ["E", "F", "I", "N", "W", "UP", "B", "C4", "SIM"]
|
|
57
|
+
ignore = ["E501"]
|
|
58
|
+
|
|
59
|
+
[tool.pytest.ini_options]
|
|
60
|
+
asyncio_mode = "auto"
|
|
61
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# cmdop-coder
|
|
2
|
+
|
|
3
|
+
Code analysis skill using tree-sitter AST parsing. Supports 40+ languages.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
- `functions --path <FILE>` — extract function/method signatures with line numbers
|
|
8
|
+
- `symbols --symbol <NAME> [--path <DIR>]` — find all occurrences of a symbol
|
|
9
|
+
- `outline --path <FILE>` — structural outline (imports, classes, functions, types)
|
|
10
|
+
- `analyze --path <FILE>` — file statistics (lines, complexity, language)
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
cmdop-coder functions --path src/main.py
|
|
16
|
+
cmdop-coder symbols --symbol MyClass --path ./src
|
|
17
|
+
cmdop-coder outline --path internal/agent/core/agent.go
|
|
18
|
+
cmdop-coder analyze --path package.json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Supported Languages
|
|
22
|
+
|
|
23
|
+
Go, Python, JavaScript, TypeScript, Rust, Java, C, C++, Ruby, PHP, Swift,
|
|
24
|
+
Kotlin, C#, CSS, HTML, JSON, YAML, TOML, Bash, SQL, and more.
|
|
@@ -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
|
+
]
|
|
@@ -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
|
+
)
|
|
@@ -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
|