Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
fafcb56
Restructure report and unit modules
pixge Jan 11, 2026
af9f93b
Group modules into scoped packages
pixge Jan 11, 2026
1da4f98
Update docs and launch config for new CLI
pixge Jan 11, 2026
4bf6267
Add status filters and issue indicators to viewer
pixge Jan 11, 2026
32ae825
Refine unresolved call detection
pixge Jan 11, 2026
beae8de
Reduce noise in CSA and indirection analysis
pixge Jan 11, 2026
5da4edb
Treat self calls as resolved for indirection
pixge Jan 11, 2026
ce2a75b
Ignore self calls in unresolved list
pixge Jan 11, 2026
458d1fe
Refine collector visitors
pixge Jan 11, 2026
734be67
Merge pull request #4 from pixge/codex/refactor-node-visitors-for-cla…
pixge Jan 11, 2026
3e7e6ec
Refactor analyzers and report builder
pixge Jan 11, 2026
dac6098
Merge pull request #5 from pixge/codex/refactor-analyzers-and-report-…
pixge Jan 11, 2026
e5ae002
Adds initial scaffolding and setup
pixge Jan 11, 2026
808edec
Improve indirection alias resolution and tests
pixge Jan 11, 2026
ccf4bc6
Merge pull request #6 from pixge/codex/introduce-intermediate-data-st…
pixge Jan 11, 2026
20604a6
Refactor report builder aggregation
pixge Jan 11, 2026
16d6abd
Merge pull request #7 from pixge/codex/extract-helper-functions-and-v…
pixge Jan 11, 2026
c7ce951
Refactor condition helpers for BPS
pixge Jan 11, 2026
35937a1
Merge pull request #8 from pixge/codex/refactor-complex-if-statements…
pixge Jan 11, 2026
ee28ed0
Refactor condition checks in collectors
pixge Jan 11, 2026
63c72bd
Merge pull request #9 from pixge/codex/refactor-json-unit-calls-for-b…
pixge Jan 11, 2026
43b9f86
Refactor indirection analysis helpers
pixge Jan 11, 2026
1b94399
Merge pull request #10 from pixge/codex/refactor-low-bps-units-functions
pixge Jan 11, 2026
90998d3
Adds prompts for code writing and refactoring
pixge Jan 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
codra.egg-info/

result.json

viewer/node_modules/
6 changes: 4 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"module": "codra.cli",
"module": "codra.cli.main",
"args": [
"${workspaceFolder}/codra",
"--threshold-csa", "5",
"--threshold-id", "2",
"--threshold-bps", "0.7"
"--threshold-bps", "0.7",
"--log-level", "DEBUG",
"--output", "${workspaceFolder}/report.json"
],
"console": "integratedTerminal",
"justMyCode": false
Expand Down
50 changes: 50 additions & 0 deletions PROMT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
1) Prompt for Writing New Code (optimize CSA, ID, BPS)
Prompt:

You are writing Python code that will be analyzed using three metrics:

CSA (complexity / symbol usage),

ID (indirection depth / call graph complexity),

BPS (branching/condition penalty; complex conditions reduce the score).

Write code that optimizes all three metrics:

Keep functions small, single-purpose, and avoid deep call chains (low ID).

Minimize external symbol usage and keep dependencies localized (low CSA).

Keep if conditions simple; avoid chained boolean logic and function calls inside conditions (high BPS).

Precompute values before conditionals, and use helper functions to isolate logic.

Prefer clear, flat control flow (early returns) over nested branches.

Produce clean, readable, and maintainable code that adheres to these constraints.




2) Prompt for Refactoring Existing Code (improve CSA, ID, BPS)
Prompt:

You are refactoring existing Python code to improve CSA, ID, and BPS metrics:

CSA improves by reducing external symbol usage and simplifying symbol dependencies.

ID improves by shortening call chains and reducing indirection depth.

BPS improves by simplifying conditional expressions and removing calls inside conditions.

Refactor the code while preserving behavior:

Extract complex condition logic into small helpers and precompute values outside if statements.

Split large functions into smaller ones only if it does not create deeper call chains.

Reduce dependency on external symbols by localizing logic and data.

Flatten control flow and avoid unnecessary layers of abstraction.

Keep the output behavior identical, add tests only when needed, and explain how the changes improve CSA, ID, and BPS.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@ pip install -e .
Analizza un percorso e stampa il report JSON su stdout:

```bash
python3 -m codra.cli /percorso/progetto
python3 -m codra.cli.main /percorso/progetto
```

Esempio con soglie:

```bash
python3 -m codra.cli /percorso/progetto --threshold-csa 10 --threshold-id 2 --threshold-bps 0.7
python3 -m codra.cli.main /percorso/progetto --threshold-csa 10 --threshold-id 2 --threshold-bps 0.7
```

Esempio con log e file di output:

```bash
python3 -m codra.cli.main /percorso/progetto --log-level DEBUG --output report.json
```

## Viewer React (opzionale)
Expand Down
22 changes: 11 additions & 11 deletions codra/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from .bps_analyzer import BpsAnalyzer
from .bps_result import BpsResult
from .csa_analyzer import CsaAnalyzer
from .csa_result import CsaResult
from .directory_scanner import DirectoryScanner
from .indirection_analyzer import IndirectionAnalyzer
from .indirection_result import IndirectionResult
from .report import Report
from .report_builder import ReportBuilder
from .report_serializer import ReportSerializer
from .unit_definition import UnitDefinition
from .bps.analyzer import BpsAnalyzer
from .bps.result import BpsResult
from .csa.analyzer import CsaAnalyzer
from .csa.result import CsaResult
from .directory.scanner import DirectoryScanner
from .indirection.analyzer import IndirectionAnalyzer
from .indirection.result import IndirectionResult
from .report.builder import ReportBuilder
from .report.model import Report
from .report.serializer import ReportSerializer
from .unit.definition import UnitDefinition

__all__ = [
"BpsAnalyzer",
Expand Down
Empty file added codra/alias/__init__.py
Empty file.
8 changes: 5 additions & 3 deletions codra/alias_collector.py → codra/alias/collector.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
from __future__ import annotations

import ast
from ast import NodeVisitor
from dataclasses import dataclass, field


@dataclass
class AliasCollector(ast.NodeVisitor):
class AliasCollector(NodeVisitor):
"""AST visitor collecting top-level alias assignments via NodeVisitor."""
aliases: dict[str, str] = field(default_factory=dict)
depth: int = 0

def collect(self, tree: ast.AST) -> dict[str, str]:
self.visit(tree)
super().visit(tree)
return dict(self.aliases)

def visit_Module(self, node: ast.Module) -> None:
self.depth += 1
for statement in node.body:
self.visit(statement)
super().visit(statement)
self.depth -= 1

def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
Expand Down
Empty file added codra/bps/__init__.py
Empty file.
58 changes: 58 additions & 0 deletions codra/bps/analyzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from __future__ import annotations

import ast
from dataclasses import dataclass

from ..condition.collector import ConditionMetricsCollector
from ..function.symbol_collector import FunctionSymbolCollector
from ..if_.collector import IfCollector
from ..unit.node import UnitNode
from ..unit.node_collector import UnitNodeCollector
from .result import BpsResult


@dataclass
class BpsAnalyzer:
def analyze_file(self, file_path: str) -> list[BpsResult]:
tree = self._parse_file(file_path)
unit_nodes = self._collect_units(tree, file_path)
scores = [self._collect_bps(unit_node) for unit_node in unit_nodes]
return self._build_results(unit_nodes, scores)

def _parse_file(self, file_path: str) -> ast.AST:
with open(file_path, "r", encoding="utf-8") as handle:
source = handle.read()
return ast.parse(source, filename=file_path)

def _collect_units(self, tree: ast.AST, file_path: str) -> list[UnitNode]:
unit_collector = UnitNodeCollector(file_path=file_path)
unit_collector.visit(tree)
return unit_collector.units

def _collect_bps(self, unit_node: UnitNode) -> float:
usage = FunctionSymbolCollector().collect(unit_node.node)
local_names = usage.locals
if_nodes = IfCollector().collect(unit_node.node)
if not if_nodes:
return 1.0
scores: list[float] = []
for if_node in if_nodes:
metrics = ConditionMetricsCollector(local_names=local_names).collect(
if_node.test
)
penalty = (
metrics.bool_ops
+ metrics.compare_ops
+ 2 * metrics.calls
+ 2 * metrics.external_refs
)
scores.append(1.0 / (1.0 + penalty))
return sum(scores) / len(scores)

def _build_results(
self, unit_nodes: list[UnitNode], scores: list[float]
) -> list[BpsResult]:
return [
BpsResult(unit=unit_node.definition, bps=bps)
for unit_node, bps in zip(unit_nodes, scores, strict=True)
]
2 changes: 1 addition & 1 deletion codra/bps_result.py → codra/bps/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from dataclasses import dataclass

from .unit_definition import UnitDefinition
from ..unit.definition import UnitDefinition


@dataclass(frozen=True)
Expand Down
43 changes: 0 additions & 43 deletions codra/bps_analyzer.py

This file was deleted.

Empty file added codra/call/__init__.py
Empty file.
11 changes: 8 additions & 3 deletions codra/call_collector.py → codra/call/collector.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
from __future__ import annotations

import ast
from ast import NodeVisitor
from dataclasses import dataclass, field


@dataclass
class CallCollector(ast.NodeVisitor):
class CallCollector(NodeVisitor):
"""AST visitor collecting call expressions via NodeVisitor."""
calls: list[str] = field(default_factory=list)

def collect(self, node: ast.AST) -> list[str]:
for statement in node.body:
self.visit(statement)
super().visit(statement)
return list(self.calls)

def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
Expand All @@ -25,4 +27,7 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None:
def visit_Call(self, node: ast.Call) -> None:
if isinstance(node.func, ast.Name):
self.calls.append(node.func.id)
self.generic_visit(node)
elif isinstance(node.func, ast.Attribute):
if isinstance(node.func.value, ast.Name) and node.func.value.id == "self":
self.calls.append(f"self.{node.func.attr}")
super().generic_visit(node)
75 changes: 0 additions & 75 deletions codra/cli.py

This file was deleted.

Empty file added codra/cli/__init__.py
Empty file.
13 changes: 13 additions & 0 deletions codra/cli/args.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from __future__ import annotations

from dataclasses import dataclass


@dataclass(frozen=True)
class CliArgs:
path: str
threshold_csa: int | None
threshold_id: int | None
threshold_bps: float | None
log_level: str
output: str | None
12 changes: 12 additions & 0 deletions codra/cli/dependencies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from __future__ import annotations

from dataclasses import dataclass

from ..report.builder import ReportBuilder
from ..report.serializer import ReportSerializer


@dataclass(frozen=True)
class CliDependencies:
builder: ReportBuilder
serializer: ReportSerializer
Loading