pcina 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.
- pcina-0.1.1/.gitignore +13 -0
- pcina-0.1.1/LICENSE +21 -0
- pcina-0.1.1/PKG-INFO +156 -0
- pcina-0.1.1/README.md +129 -0
- pcina-0.1.1/docs/adr/ADR-001-tool-scope-engine.md +55 -0
- pcina-0.1.1/docs/adr/ADR-002-scoring-model.md +143 -0
- pcina-0.1.1/docs/agentic-loop-goal.md +80 -0
- pcina-0.1.1/docs/dev-tasks.md +80 -0
- pcina-0.1.1/docs/field-triage.md +55 -0
- pcina-0.1.1/pcina.toml +12 -0
- pcina-0.1.1/pyproject.toml +40 -0
- pcina-0.1.1/pytest.ini +3 -0
- pcina-0.1.1/src/pcina/__init__.py +12 -0
- pcina-0.1.1/src/pcina/analyzer.py +485 -0
- pcina-0.1.1/src/pcina/budget.py +66 -0
- pcina-0.1.1/src/pcina/cli.py +246 -0
- pcina-0.1.1/src/pcina/config.py +55 -0
- pcina-0.1.1/src/pcina/diff.py +82 -0
- pcina-0.1.1/src/pcina/files.py +50 -0
- pcina-0.1.1/src/pcina/finding.py +54 -0
- pcina-0.1.1/src/pcina/metrics/__init__.py +35 -0
- pcina-0.1.1/src/pcina/metrics/clever.py +36 -0
- pcina-0.1.1/src/pcina/metrics/complexity.py +39 -0
- pcina-0.1.1/src/pcina/metrics/deadcode.py +98 -0
- pcina-0.1.1/src/pcina/metrics/hardcode.py +37 -0
- pcina-0.1.1/src/pcina/metrics/nesting.py +40 -0
- pcina-0.1.1/src/pcina/metrics/paranoia.py +52 -0
- pcina-0.1.1/src/pcina/metrics/size.py +57 -0
- pcina-0.1.1/src/pcina/metrics/yagni.py +35 -0
- pcina-0.1.1/src/pcina/outputs/__init__.py +1 -0
- pcina-0.1.1/src/pcina/outputs/json_out.py +59 -0
- pcina-0.1.1/src/pcina/outputs/md.py +76 -0
- pcina-0.1.1/src/pcina/outputs/term.py +116 -0
- pcina-0.1.1/src/pcina/scoring.py +135 -0
- pcina-0.1.1/tests/__init__.py +0 -0
- pcina-0.1.1/tests/conftest.py +22 -0
- pcina-0.1.1/tests/fixtures/clean/__init__.py +1 -0
- pcina-0.1.1/tests/fixtures/clean/api.py +28 -0
- pcina-0.1.1/tests/fixtures/clean/core.py +49 -0
- pcina-0.1.1/tests/fixtures/neuroslop/__init__.py +0 -0
- pcina-0.1.1/tests/fixtures/neuroslop/api.py +126 -0
- pcina-0.1.1/tests/fixtures/neuroslop/core.py +34 -0
- pcina-0.1.1/tests/test_analyzer.py +66 -0
- pcina-0.1.1/tests/test_budgets.py +48 -0
- pcina-0.1.1/tests/test_cli.py +119 -0
- pcina-0.1.1/tests/test_field_regressions.py +147 -0
- pcina-0.1.1/tests/test_metrics.py +94 -0
- pcina-0.1.1/tests/test_outputs.py +69 -0
- pcina-0.1.1/tests/test_scoring.py +75 -0
pcina-0.1.1/.gitignore
ADDED
pcina-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 alexkay28
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pcina-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pcina
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Python Code-pattern Investigator & Nonsense Analyzer - KISS/YAGNI harness for AI-generated code
|
|
5
|
+
Project-URL: Homepage, https://github.com/AlexKay28/pcina
|
|
6
|
+
Project-URL: Repository, https://github.com/AlexKay28/pcina
|
|
7
|
+
Project-URL: Issues, https://github.com/AlexKay28/pcina/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/AlexKay28/pcina/blob/main/docs/agentic-loop-goal.md
|
|
9
|
+
Author: alexkay28
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: ai-generated-code,ast,code-quality,dead-code,kiss,linter,static-analysis,yagni
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
|
+
Classifier: Topic :: Software Development :: Testing
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# pcina
|
|
29
|
+
|
|
30
|
+
Automatic checker for **KISS/YAGNI paradigm alignment**. pcina parses your Python code
|
|
31
|
+
(pure `ast`, one pass) and rates it per aspect out of 10 — mechanizing the ethos of the
|
|
32
|
+
classic KISS/YAGNI advice ("write the minimum code a new reader understands in one
|
|
33
|
+
pass; build nothing speculative") into a single fast command. It targets the
|
|
34
|
+
over-defensive, over-abstracted, over-nested output that both AI agents and humans
|
|
35
|
+
produce — without importing anyone's opinion on formatting or typing.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
Pure stdlib. Python >= 3.10. Zero runtime dependencies.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install pcina # from PyPI
|
|
43
|
+
# or
|
|
44
|
+
pipx install pcina # isolated CLI tool
|
|
45
|
+
# or straight from GitHub
|
|
46
|
+
pip install git+https://github.com/AlexKay28/pcina.git
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Use it from Python too — scores and findings are plain data:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from pcina.analyzer import analyze
|
|
53
|
+
from pcina.metrics import findings_for
|
|
54
|
+
from pcina.scoring import score
|
|
55
|
+
|
|
56
|
+
project = analyze(["src/"], [])
|
|
57
|
+
findings = findings_for(project)
|
|
58
|
+
print(score(project, findings)["overall"]) # e.g. 9.48
|
|
59
|
+
for f in findings:
|
|
60
|
+
if f.severity in ("warn", "error"):
|
|
61
|
+
print(f.severity, f.aspect, f"{f.file}:{f.line}", f.message)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## Quickstart
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pcina scan src/ # advisory report
|
|
69
|
+
pcina check src/ # budget gate: exit 1 on violations (CI)
|
|
70
|
+
pcina report --md src/ # markdown report (drops into an agent context)
|
|
71
|
+
pcina baseline --save src/ # snapshot findings
|
|
72
|
+
pcina baseline --compare src/ # delta vs snapshot
|
|
73
|
+
pcina scan --json src/ # stable pcina/1 JSON schema
|
|
74
|
+
pcina scan --diff main src/ # only findings on lines changed vs BASE (git)
|
|
75
|
+
pcina scan --exclude 'tests/*' src/
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## The 8 aspects
|
|
79
|
+
|
|
80
|
+
| Aspect | Principle | Meaning |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| complexity | KISS | a reader should understand a function in one pass |
|
|
83
|
+
| paranoia | KISS | validate at trust boundaries, not inside internal helpers |
|
|
84
|
+
| nesting | KISS | flat beats nested — guard clauses, early return |
|
|
85
|
+
| hardcode | KISS | no magic values forcing the reader to reconstruct facts |
|
|
86
|
+
| clever | KISS | if a reader must mentally execute the line, rewrite it |
|
|
87
|
+
| deadcode | YAGNI | unused code was never (or no longer) needed |
|
|
88
|
+
| yagni | YAGNI | no speculative abstraction for hypothetical futures |
|
|
89
|
+
| size | KISS | one outcome per function; a docstring is the 30-second test |
|
|
90
|
+
|
|
91
|
+
## Terminal output (real run)
|
|
92
|
+
|
|
93
|
+
`pcina scan src/` on pcina's own source:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
== pcina scan: src/ (22 files, 974 LLOC) ==
|
|
97
|
+
|
|
98
|
+
complexity : 9.9/10 paranoia : 10.0/10
|
|
99
|
+
nesting : 10.0/10 hardcode : 10.0/10
|
|
100
|
+
deadcode : 9.9/10 yagni : 10.0/10
|
|
101
|
+
clever : 10.0/10 size : 10.0/10
|
|
102
|
+
|
|
103
|
+
src/pcina/analyzer.py:365 [warn][nesting] nesting depth 5 - invert conditions, return early
|
|
104
|
+
fix: guard clauses: invert the outer conditions and return
|
|
105
|
+
src/pcina/cli.py:138 [warn][complexity] function _cmd_baseline complexity cc=15
|
|
106
|
+
fix: split the function or name complex conditions as predicates
|
|
107
|
+
|
|
108
|
+
statistics:
|
|
109
|
+
loc=1485 lloc=974 files=22 functions=107 classes=11
|
|
110
|
+
docstring_coverage=79% comment_ratio=2%
|
|
111
|
+
|
|
112
|
+
Your code has been rated at 9.82/10
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Finding lines are single-line, greppable: `file:line [sev][aspect] message`.
|
|
116
|
+
|
|
117
|
+
## Known false positives (honest)
|
|
118
|
+
|
|
119
|
+
**Dynamic dispatch.** The deadcode aspect flags "no callers found" for functions wired
|
|
120
|
+
up dynamically — CLI entry points, plugin registries, `__all__` re-exports. Static
|
|
121
|
+
analysis cannot see those callers. **Verify before deleting anything** pcina marks
|
|
122
|
+
as a deletion candidate.
|
|
123
|
+
|
|
124
|
+
pcina already excludes from deadcode analysis: dunders (`__init__`, `__call__`, …),
|
|
125
|
+
contents of `__init__.py` (import surface), functions named `main`, and test files.
|
|
126
|
+
Typing imports used only in annotations, class bases and cross-module re-exports
|
|
127
|
+
count as uses — all verified on real-world repos (see `docs/field-triage.md`).
|
|
128
|
+
|
|
129
|
+
**Intentional import markers.** Two conventions from the wider tooling
|
|
130
|
+
ecosystem (Ruff/Vulture) that pcina honors:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from pkg import thing as thing # redundant alias = explicit re-export marker
|
|
134
|
+
import encodings.idna # noqa: F401 # noqa = intentional, do not report
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
## Exit codes (contract)
|
|
139
|
+
|
|
140
|
+
| Code | Meaning |
|
|
141
|
+
|---|---|
|
|
142
|
+
| 0 | clean — no budget violations |
|
|
143
|
+
| 1 | budget violation (`pcina check`) |
|
|
144
|
+
| 2 | error / CLI usage problem |
|
|
145
|
+
|
|
146
|
+
Scores themselves are advisory; `pcina check` fails only on explicit budget
|
|
147
|
+
violations, never on the rating.
|
|
148
|
+
|
|
149
|
+
## Roadmap
|
|
150
|
+
|
|
151
|
+
v0.1.1 "real-world proof" cycle — install from GitHub, proven on foreign repos,
|
|
152
|
+
CI-gated: see [docs/agentic-loop-goal.md](docs/agentic-loop-goal.md).
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT
|
pcina-0.1.1/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# pcina
|
|
2
|
+
|
|
3
|
+
Automatic checker for **KISS/YAGNI paradigm alignment**. pcina parses your Python code
|
|
4
|
+
(pure `ast`, one pass) and rates it per aspect out of 10 — mechanizing the ethos of the
|
|
5
|
+
classic KISS/YAGNI advice ("write the minimum code a new reader understands in one
|
|
6
|
+
pass; build nothing speculative") into a single fast command. It targets the
|
|
7
|
+
over-defensive, over-abstracted, over-nested output that both AI agents and humans
|
|
8
|
+
produce — without importing anyone's opinion on formatting or typing.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
Pure stdlib. Python >= 3.10. Zero runtime dependencies.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install pcina # from PyPI
|
|
16
|
+
# or
|
|
17
|
+
pipx install pcina # isolated CLI tool
|
|
18
|
+
# or straight from GitHub
|
|
19
|
+
pip install git+https://github.com/AlexKay28/pcina.git
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Use it from Python too — scores and findings are plain data:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from pcina.analyzer import analyze
|
|
26
|
+
from pcina.metrics import findings_for
|
|
27
|
+
from pcina.scoring import score
|
|
28
|
+
|
|
29
|
+
project = analyze(["src/"], [])
|
|
30
|
+
findings = findings_for(project)
|
|
31
|
+
print(score(project, findings)["overall"]) # e.g. 9.48
|
|
32
|
+
for f in findings:
|
|
33
|
+
if f.severity in ("warn", "error"):
|
|
34
|
+
print(f.severity, f.aspect, f"{f.file}:{f.line}", f.message)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## Quickstart
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pcina scan src/ # advisory report
|
|
42
|
+
pcina check src/ # budget gate: exit 1 on violations (CI)
|
|
43
|
+
pcina report --md src/ # markdown report (drops into an agent context)
|
|
44
|
+
pcina baseline --save src/ # snapshot findings
|
|
45
|
+
pcina baseline --compare src/ # delta vs snapshot
|
|
46
|
+
pcina scan --json src/ # stable pcina/1 JSON schema
|
|
47
|
+
pcina scan --diff main src/ # only findings on lines changed vs BASE (git)
|
|
48
|
+
pcina scan --exclude 'tests/*' src/
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## The 8 aspects
|
|
52
|
+
|
|
53
|
+
| Aspect | Principle | Meaning |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| complexity | KISS | a reader should understand a function in one pass |
|
|
56
|
+
| paranoia | KISS | validate at trust boundaries, not inside internal helpers |
|
|
57
|
+
| nesting | KISS | flat beats nested — guard clauses, early return |
|
|
58
|
+
| hardcode | KISS | no magic values forcing the reader to reconstruct facts |
|
|
59
|
+
| clever | KISS | if a reader must mentally execute the line, rewrite it |
|
|
60
|
+
| deadcode | YAGNI | unused code was never (or no longer) needed |
|
|
61
|
+
| yagni | YAGNI | no speculative abstraction for hypothetical futures |
|
|
62
|
+
| size | KISS | one outcome per function; a docstring is the 30-second test |
|
|
63
|
+
|
|
64
|
+
## Terminal output (real run)
|
|
65
|
+
|
|
66
|
+
`pcina scan src/` on pcina's own source:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
== pcina scan: src/ (22 files, 974 LLOC) ==
|
|
70
|
+
|
|
71
|
+
complexity : 9.9/10 paranoia : 10.0/10
|
|
72
|
+
nesting : 10.0/10 hardcode : 10.0/10
|
|
73
|
+
deadcode : 9.9/10 yagni : 10.0/10
|
|
74
|
+
clever : 10.0/10 size : 10.0/10
|
|
75
|
+
|
|
76
|
+
src/pcina/analyzer.py:365 [warn][nesting] nesting depth 5 - invert conditions, return early
|
|
77
|
+
fix: guard clauses: invert the outer conditions and return
|
|
78
|
+
src/pcina/cli.py:138 [warn][complexity] function _cmd_baseline complexity cc=15
|
|
79
|
+
fix: split the function or name complex conditions as predicates
|
|
80
|
+
|
|
81
|
+
statistics:
|
|
82
|
+
loc=1485 lloc=974 files=22 functions=107 classes=11
|
|
83
|
+
docstring_coverage=79% comment_ratio=2%
|
|
84
|
+
|
|
85
|
+
Your code has been rated at 9.82/10
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Finding lines are single-line, greppable: `file:line [sev][aspect] message`.
|
|
89
|
+
|
|
90
|
+
## Known false positives (honest)
|
|
91
|
+
|
|
92
|
+
**Dynamic dispatch.** The deadcode aspect flags "no callers found" for functions wired
|
|
93
|
+
up dynamically — CLI entry points, plugin registries, `__all__` re-exports. Static
|
|
94
|
+
analysis cannot see those callers. **Verify before deleting anything** pcina marks
|
|
95
|
+
as a deletion candidate.
|
|
96
|
+
|
|
97
|
+
pcina already excludes from deadcode analysis: dunders (`__init__`, `__call__`, …),
|
|
98
|
+
contents of `__init__.py` (import surface), functions named `main`, and test files.
|
|
99
|
+
Typing imports used only in annotations, class bases and cross-module re-exports
|
|
100
|
+
count as uses — all verified on real-world repos (see `docs/field-triage.md`).
|
|
101
|
+
|
|
102
|
+
**Intentional import markers.** Two conventions from the wider tooling
|
|
103
|
+
ecosystem (Ruff/Vulture) that pcina honors:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from pkg import thing as thing # redundant alias = explicit re-export marker
|
|
107
|
+
import encodings.idna # noqa: F401 # noqa = intentional, do not report
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
## Exit codes (contract)
|
|
112
|
+
|
|
113
|
+
| Code | Meaning |
|
|
114
|
+
|---|---|
|
|
115
|
+
| 0 | clean — no budget violations |
|
|
116
|
+
| 1 | budget violation (`pcina check`) |
|
|
117
|
+
| 2 | error / CLI usage problem |
|
|
118
|
+
|
|
119
|
+
Scores themselves are advisory; `pcina check` fails only on explicit budget
|
|
120
|
+
violations, never on the rating.
|
|
121
|
+
|
|
122
|
+
## Roadmap
|
|
123
|
+
|
|
124
|
+
v0.1.1 "real-world proof" cycle — install from GitHub, proven on foreign repos,
|
|
125
|
+
CI-gated: see [docs/agentic-loop-goal.md](docs/agentic-loop-goal.md).
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# ADR-001: Tool scope, engine and integration model
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted (2026-10-30)
|
|
4
|
+
- **Deciders:** user (alexkay28), builder agent
|
|
5
|
+
- **Context:** AI agents write large volumes of over-defensive, over-abstracted, over-nested
|
|
6
|
+
code ("neuroslop"). Existing SOTA tools (Ruff, Radon, Vulture, Pylint, wemake-python-styleguide)
|
|
7
|
+
each emit per-rule findings but none answer *"how bloated is this code, per aspect, with one
|
|
8
|
+
fast command"* in a format both a human and an agent can act on. The user runs the
|
|
9
|
+
`alexkay/pythont-dry-code-devevelopment@1.1.0` skill, whose KISS/YAGNI smell taxonomy
|
|
10
|
+
(paranoid raises, deep nesting, magic values, speculative abstraction, shadowed names,
|
|
11
|
+
clever-code towers) pcina must mechanize.
|
|
12
|
+
- **Core mission (user-confirmed 2026-10-30):** pcina plays the role of an **automatic checker
|
|
13
|
+
for YAGNI and KISS paradigm alignment**. This is the backbone and the actual goal of the
|
|
14
|
+
library. Every metric, finding, score and budget must justify itself as enforcing KISS
|
|
15
|
+
(minimum code a new reader understands in one pass, flat over nested, no cleverness
|
|
16
|
+
towers, no magic values, no paranoia at non-boundary call sites) or YAGNI (nothing
|
|
17
|
+
speculative: no unused/dead code, no zero-caller abstractions, no "in case we need it
|
|
18
|
+
later" layers). Findings that cannot be traced to KISS or YAGNI do not belong in pcina;
|
|
19
|
+
general style/typing/lint coverage is explicitly out of scope (Ruff's job). Principle map:
|
|
20
|
+
|
|
21
|
+
| Aspect | Principle | Principle anchor |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| complexity | KISS | understands in one pass: limited branching per responsibility |
|
|
24
|
+
| paranoia | KISS | keep validation only at trust boundaries; internal paranoia is cognitive noise |
|
|
25
|
+
| nesting | KISS | flat is better than nested (guard clauses, early return) |
|
|
26
|
+
| hardcode | KISS | magic values force the reader to reconstruct domain facts |
|
|
27
|
+
| clever | KISS | if a reader must mentally execute the line, rewrite it |
|
|
28
|
+
| deadcode | YAGNI | unused code was never (or no longer) needed |
|
|
29
|
+
| yagni | YAGNI | speculative abstraction for hypothetical futures |
|
|
30
|
+
| size | KISS | one outcome per function; docs == the 30-second reconstruction test |
|
|
31
|
+
- **Decision:**
|
|
32
|
+
1. pcina is a **single pure-stdlib `ast` analyzer** (one parse per file, one visitor pass)
|
|
33
|
+
with **zero runtime dependencies**. Ruff/Vulture are optional future backends, never core.
|
|
34
|
+
2. Metric families (v1, all included): `complexity`, `paranoia`, `nesting`, `hardcode`,
|
|
35
|
+
`deadcode`, `yagni`, `clever`, `size` (docs/params/LOC). Findings attach to code
|
|
36
|
+
**sections** (module → class → function) and are advisory review candidates.
|
|
37
|
+
3. CLI: `scan` (report), `check` (budget gate, exit 1 on violation), `report`
|
|
38
|
+
(markdown), `baseline` (snapshot/delta). `--diff BASE` filters to changed lines.
|
|
39
|
+
4. Three output formats: human terminal (color), `--json` (stable schema v1, agent
|
|
40
|
+
target), `--md` (drops into an agent context per skill step 6).
|
|
41
|
+
5. Every report ends with the **pylint-style rating line, per-aspect scores and a
|
|
42
|
+
statistics block** (spec in ADR-002).
|
|
43
|
+
6. Validation heuristics distinguish **trust boundaries** (entry points, IO, config,
|
|
44
|
+
`__init__`, decorated endpoints, CLI `main`) from internal code; repeated defensive
|
|
45
|
+
validation inside internal helpers is flagged, boundary validation is not.
|
|
46
|
+
7. Acceptance: pcina must rate its own source ≥ 9.0/10 and produce zero `error` findings
|
|
47
|
+
on itself (self-application gate), zero warn/error on the `clean/` fixture, and the
|
|
48
|
+
expected finding set on the `neuroslop/` fixture.
|
|
49
|
+
- **Consequences:** we own all semantics (no Radon dependency, which is largely
|
|
50
|
+
unmaintained) at the cost of writing our own reference scanner for dead-code. The tool
|
|
51
|
+
stays installable everywhere via stdlib only, and fast (target: 100k LOC < 5 s,
|
|
52
|
+
parallel parse via `concurrent.futures`).
|
|
53
|
+
- **Alternatives rejected:** wrapping external tools (slow, dependency-heavy, unsuitable
|
|
54
|
+
for inline agent loops); Ruff-plugin form (Ruff cannot express aggregate per-aspect
|
|
55
|
+
scoring and section grouping the user requires).
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# ADR-002: Scoring model and statistics calculation (internal_spec)
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted (2026-10-30)
|
|
4
|
+
- **Deciders:** user (alexkay28), builder agent
|
|
5
|
+
- **Context:** the user requires a pylint-like evaluation experience:
|
|
6
|
+
an overall rating (`Your code has been rated at 9.48/10`), per-aspect scores, and a
|
|
7
|
+
statistics block — but Pylint's own score mixes unrelated message categories and
|
|
8
|
+
penalizes convention noise heavily. Semgrep's lesson: output must be plain,
|
|
9
|
+
greppable, line-oriented so both humans and shell/awk-ish consumers can use it.
|
|
10
|
+
- **Decision:** the model below is the **normative internal specification**. Scores are
|
|
11
|
+
advisory signals, never CI gates (gates are budget violations, see ADR-001).
|
|
12
|
+
|
|
13
|
+
## 1. Base quantities
|
|
14
|
+
|
|
15
|
+
| Quantity | Definition (normative) |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `LOC` | physical non-blank lines of the analyzed sources |
|
|
18
|
+
| `LLOC` | logical lines: count of `ast.stmt` nodes, excluding `FunctionDef`/`AsyncFunctionDef`/`ClassDef` **signature lines only** (their bodies count). Comments and docstrings excluded |
|
|
19
|
+
| `N_FUN`, `N_CLS` | counts of analyzed function/class definitions |
|
|
20
|
+
| `DOC_COVERED` | functions/classes/modules with a non-empty docstring |
|
|
21
|
+
|
|
22
|
+
## 2. Severity and finding weights
|
|
23
|
+
|
|
24
|
+
Finding severity (per ADR-001 metric table):
|
|
25
|
+
|
|
26
|
+
| Severity | Meaning | Base weight `w` |
|
|
27
|
+
|---|---|---:|
|
|
28
|
+
| `error` | near-certain defect or strong bloat (bare except, shadowed module import, unused-export) | 3.0 |
|
|
29
|
+
| `warn` | smell needing review | 1.0 |
|
|
30
|
+
| `info` | advisory observation | 0.2 |
|
|
31
|
+
|
|
32
|
+
## 3. Aspect scores
|
|
33
|
+
|
|
34
|
+
Every finding belongs to exactly one aspect: `complexity`, `paranoia`, `nesting`,
|
|
35
|
+
`hardcode`, `deadcode`, `yagni`, `clever`, `size`.
|
|
36
|
+
|
|
37
|
+
For aspect `a` over scope with `LLOC` statements:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
P_a = Σ (w(severity_i) * aspect_penalty_mult[a] * fix_loc_mult_i) for findings i in a
|
|
41
|
+
score_a = clamp(0, 10 - K * P_a / max(LLOC, 50))
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- `K = 10` (so weighted penalty equal to LLOC ⇒ score 0; pylint's "10 - …" shape).
|
|
45
|
+
- `max(LLOC, 50)`: small files must not produce wild scores from 2 findings.
|
|
46
|
+
- `fix_loc_mult_i` = 1.0 (flat for v1; reserved for severity scaling by lines touched).
|
|
47
|
+
- `aspect_penalty_mult` — because aspects differ in natural frequency:
|
|
48
|
+
|
|
49
|
+
| Aspect | mult | Rationale |
|
|
50
|
+
|---|---:|---|
|
|
51
|
+
| complexity | 1.5 | one bad function is a real comprehension cost |
|
|
52
|
+
| paranoia | 1.0 | frequent; most common AI smell |
|
|
53
|
+
| nesting | 1.5 | strong readability damage |
|
|
54
|
+
| hardcode | 0.5 | mostly cosmetic |
|
|
55
|
+
| deadcode | 1.5 | deleted code is pure win |
|
|
56
|
+
| yagni | 1.0 | speculative layers |
|
|
57
|
+
| clever | 1.0 | localized cleverness |
|
|
58
|
+
| size | 0.5 | docs/params are softer signals |
|
|
59
|
+
|
|
60
|
+
These multipliers are software calibration constants: they live in
|
|
61
|
+
`pcina/scoring.py` as a data table, are printed via `pcina report --show-weights`,
|
|
62
|
+
and must never be duplicated elsewhere.
|
|
63
|
+
|
|
64
|
+
## 4. Overall rating
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
P = Σ P_a over all aspects
|
|
68
|
+
rating = clamp(0, 10 - 10 * P / max(LLOC, 50)) # one decimal, pylint-style line
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The overall rating is **not** a quality verdict (per skill: tool scores guide review).
|
|
72
|
+
`pcina check` never fails on the rating — only on explicit budget violations.
|
|
73
|
+
|
|
74
|
+
## 5. Statistics block (normative fields)
|
|
75
|
+
|
|
76
|
+
Report must always end with (term and `--md` formats; same fields under `statistics`
|
|
77
|
+
in JSON):
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
statistics:
|
|
81
|
+
loc=520 lloc=231 files=9 functions=47 classes=8
|
|
82
|
+
docstring_coverage=72% comment_ratio=11%
|
|
83
|
+
avg_cc=4.2 max_cc=17 p75_cc=6
|
|
84
|
+
raises=31 try_except=12 bare_excepts=0
|
|
85
|
+
dead_candidates=5 yagni_candidates=3 magic_values=14
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Definitions:
|
|
89
|
+
|
|
90
|
+
- `avg_cc`, `max_cc`, `p75_cc`: cyclomatic complexity over functions (radon CC semantics:
|
|
91
|
+
+1 per `if/elif/for/while/except/with/assert/comprehension/bool-op`).
|
|
92
|
+
- `comment_ratio` = comment lines / LOC (as `%`).
|
|
93
|
+
- `docstring_coverage` = documented defs+modules / total defs+modules (as `%`).
|
|
94
|
+
- Counters (`raises`, …) = raw pattern occurrences as defined per metric module.
|
|
95
|
+
|
|
96
|
+
## 6. Human output template (term format)
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
== pcina scan: src/ (9 files, 231 LLOC) ==
|
|
100
|
+
|
|
101
|
+
complexity : 9.3/10 paranoia : 4.2/10
|
|
102
|
+
nesting : 8.8/10 hardcode : 6.1/10
|
|
103
|
+
deadcode : 9.9/10 yagni : 9.0/10
|
|
104
|
+
clever : 9.7/10 size : 9.5/10
|
|
105
|
+
|
|
106
|
+
src/api.py:42 [warn][paranoia] 6 'raise' guards in internal helper 'load_user_args' — validation belongs to trust boundary
|
|
107
|
+
fix: keep one boundary check in caller; delete redundant guards
|
|
108
|
+
...
|
|
109
|
+
|
|
110
|
+
statistics:
|
|
111
|
+
loc=... lloc=... (see §5)
|
|
112
|
+
|
|
113
|
+
Your code has been rated at 8.42/10
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- Finding lines are single-line `file:line [sev][aspect] message` — greppable,
|
|
117
|
+
`awk`/`grep`-friendly (`grep '\[warn\]'`), mirroring semgrep's usability lesson.
|
|
118
|
+
- The rating line is machine-matchable: `^Your code has been rated at ([0-9.]+)/10$`.
|
|
119
|
+
- With `--compare-baseline` the rating line gains `(previous: X.XX/10, +Δ)`, pylint-style.
|
|
120
|
+
|
|
121
|
+
## 7. JSON schema v1 (frozen at v1.0.0)
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"schema": "pcina/1",
|
|
126
|
+
"scope": {"roots": ["src"], "files": 9, "loc": 520, "lloc": 231},
|
|
127
|
+
"scores": {"overall": 8.42, "aspects": {"complexity": 9.3, "...": 0}},
|
|
128
|
+
"statistics": {"functions": 47, "...": 0},
|
|
129
|
+
"findings": [
|
|
130
|
+
{"aspect": "paranoia", "severity": "warn", "file": "src/api.py", "line": 42,
|
|
131
|
+
"section": "api:load_user_args", "metric": "raise-guard-burst",
|
|
132
|
+
"message": "...", "fix": "..."}
|
|
133
|
+
],
|
|
134
|
+
"budget_violations": []
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Additive-only changes allowed within schema version; renames require `pcina/2`.
|
|
139
|
+
|
|
140
|
+
- **Consequences:** deterministic, testable scores (snapshot tests on fixtures);
|
|
141
|
+
calibration is one data table; small-file floor of 50 LLOC prevents noise spikes.
|
|
142
|
+
Risk: multipliers are judgment calls — mitigated by fixture calibration tests
|
|
143
|
+
(clean fixture must rate ≥ 9.0, neuroslop fixture ≤ 6.0).
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Agentic loop goal — v0.1.1 "real-world proof" cycle
|
|
2
|
+
|
|
3
|
+
- **Status:** Active (2026-10-30)
|
|
4
|
+
- **Loop:** `/loop 5m [investigate - plan - todo - implement - check - apply pcina on itself - check - fix - log]`
|
|
5
|
+
- **Session protocol (unchanged):** plan in main session, spawn subagent "developers"
|
|
6
|
+
with strict file ownership and normative ADRs as contracts; main session keeps
|
|
7
|
+
headroom for common sense and verification only.
|
|
8
|
+
|
|
9
|
+
## Vision (why this cycle exists)
|
|
10
|
+
|
|
11
|
+
v0.1 closed the self-application loop: 8 KISS/YAGNI metric families, scoring
|
|
12
|
+
(ADR-002), CLI with scan/check/report/baseline, 42 tests green, self-rated
|
|
13
|
+
**9.82/10 with zero errors**. A tool that scores itself is credible; a tool nobody
|
|
14
|
+
else can install and run is a diary entry. This cycle upgrades pcina from a
|
|
15
|
+
verified prototype to a **shippable, externally-consumable tool proven on code it
|
|
16
|
+
did not write** — without breaking its own backbone rule: every change must
|
|
17
|
+
enforce KISS or YAGNI (ADR-001 principle map).
|
|
18
|
+
|
|
19
|
+
## Goal statement (v0.1.1)
|
|
20
|
+
|
|
21
|
+
Any developer (or AI agent) must be able to `pipx install pcina` (or from GitHub),
|
|
22
|
+
run `pcina scan` on an arbitrary Python repo in one command, get per-aspect
|
|
23
|
+
scores with actionable findings, and gate CI with `pcina check` — and pcina's
|
|
24
|
+
own self-score must stay >= 9.0 with zero errors.
|
|
25
|
+
|
|
26
|
+
## Success criteria (all must be true at cycle end)
|
|
27
|
+
|
|
28
|
+
1. **README.md exists** at repo root: what/why (anti-"neuroslop" KISS/YAGNI checker),
|
|
29
|
+
install, quickstart, aspect table (from ADR-001), exit-code contract, output
|
|
30
|
+
format samples (term + json + md). Written for someone who has never seen an ADR.
|
|
31
|
+
2. **Runs clean on foreign code:** `pcina scan` (and `--json`) executes without
|
|
32
|
+
crash on >= 3 real external repos (e.g. local clones chosen by size/variety —
|
|
33
|
+
must include one repo that is deliberately over-engineered). Triage every crash
|
|
34
|
+
and every obviously-bogus finding found this way; either fix the metric or
|
|
35
|
+
document the exclusion — no silent ignoring.
|
|
36
|
+
3. **CI gate:** a GitHub Actions workflow (pure stdlib, no pip deps) running pytest +
|
|
37
|
+
`pcina check src/` with budgets configured so the gate fails on error findings.
|
|
38
|
+
The repo's own main branch must pass it.
|
|
39
|
+
4. **False-positive budget shrinks:** the two bug classes found by self-scan
|
|
40
|
+
(decorators, `__all__`) get regression tests in `tests/`; any new FP found on
|
|
41
|
+
foreign code gets a test too. "Known FP list" documented in README honestly.
|
|
42
|
+
5. **Self-discipline maintained:** pcina self-score >= 9.0, zero errors, pytest green,
|
|
43
|
+
`pyproject.toml` version bumped to 0.1.1, everything pushed to GitHub `main`.
|
|
44
|
+
|
|
45
|
+
## Non-goals (say no in this cycle)
|
|
46
|
+
|
|
47
|
+
- No new metric families (beyond the 8) — reach comes before depth.
|
|
48
|
+
- No Ruff/Vulture backends, no LLM integration, no config language growth.
|
|
49
|
+
- No refactoring round for the accepted warns in analyzer/diff/files/cli
|
|
50
|
+
(tracked, but roadmap belongs to v0.2), unless a foreign-code crash forces it.
|
|
51
|
+
|
|
52
|
+
## Out-of-scope guard (from ADR-001, restated)
|
|
53
|
+
|
|
54
|
+
Findings that cannot be traced to KISS or YAGNI do not belong in pcina. General
|
|
55
|
+
style/typing/lint coverage stays out — Ruff's job.
|
|
56
|
+
|
|
57
|
+
## Suggested developer split (subagents with file ownership)
|
|
58
|
+
|
|
59
|
+
- **dev-R:** `README.md` (+ nothing else). Input: both ADRs + `--help` output.
|
|
60
|
+
- **dev-F (field testing):** no src ownership — drives `pcina scan --json` on 3+
|
|
61
|
+
foreign repos, writes `docs/field-triage.md` with crash log + FP triage. Fixes
|
|
62
|
+
assigned back to owners.
|
|
63
|
+
- **dev-Ci:** `.github/workflows/ci.yml` + minimal `pcina.toml` budgets for src/.
|
|
64
|
+
- **dev-M (metric fixes):** `src/pcina/metrics/*` + `tests/` regression tests
|
|
65
|
+
for every FP dev-F reports.
|
|
66
|
+
- Owners of analyzer/scoring/cli stay as in docs/dev-tasks.md for crash fixes.
|
|
67
|
+
|
|
68
|
+
## Cycle log
|
|
69
|
+
|
|
70
|
+
- 2026-10-30 (main session I): goal defined after v0.1 verification closed (9.82/10 self-score).
|
|
71
|
+
- 2026-10-30 (main session II): COMPLETE — all 5 success criteria met:
|
|
72
|
+
1. README.md shipped (dev-R subagent; install via git+ URL, real output sample, honest FP section).
|
|
73
|
+
2. Field runs: requests (9.55), flask (9.68), poetry (9.59) — zero crashes, valid pcina/1 JSON; poetry (over-engineered target) shows ~6x findings density.
|
|
74
|
+
3. CI gate: DESOPED by user decision (2026-10-30) — "we don't need ci now". The blocker was account-level Actions scheduling on a private repo (no runner assigned, 3s, no logs — billing/quota side). The `pcina.toml` baseline budget gate stays as a LOCAL gate (`pcina check src/` -> exit 0); workflow was removed. Re-add CI in a later cycle when the repo goes public or billing is sorted.
|
|
75
|
+
4. Five FP classes fixed (annotations, class bases, cross-module re-exports, `__init__.py` surface + latent INIT_BASENAME bug, noqa markers) — each has a regression test in tests/test_field_regressions.py (52 tests total, incl. over-suppression guard and self-score floor >= 9.0). Industry conventions adopted after web research: Ruff F401 (redundant alias, ignore-init-module-imports), Vulture (#216 annotation refs, noqa, confidence laddering).
|
|
76
|
+
5. Version 0.1.1, pushed to origin/main; pytest 52 passed; self-scan 9.80/10 zero errors; `pcina check` exit 0.
|
|
77
|
+
|
|
78
|
+
Every loop iteration appends one line to this file under "Cycle log"
|
|
79
|
+
(timestamp, what was investigated/implemented, self-score, verdict).
|
|
80
|
+
Full triage: docs/field-triage.md.
|