@pennyfarthing/core 10.0.3 → 10.1.0
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.
- package/README.md +9 -7
- package/package.json +7 -1
- package/packages/core/dist/cli/commands/cyclist.d.ts +5 -1
- package/packages/core/dist/cli/commands/cyclist.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/cyclist.js +4 -4
- package/packages/core/dist/cli/commands/cyclist.js.map +1 -1
- package/packages/core/dist/cli/commands/cyclist.test.js +2 -2
- package/packages/core/dist/cli/commands/cyclist.test.js.map +1 -1
- package/packages/core/dist/cli/commands/doctor-legacy.test.js +17 -16
- package/packages/core/dist/cli/commands/doctor-legacy.test.js.map +1 -1
- package/packages/core/dist/cli/commands/doctor.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/doctor.js +251 -4
- package/packages/core/dist/cli/commands/doctor.js.map +1 -1
- package/packages/core/dist/cli/commands/init.d.ts +7 -0
- package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/init.js +43 -7
- package/packages/core/dist/cli/commands/init.js.map +1 -1
- package/packages/core/dist/cli/commands/update.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/update.js +26 -0
- package/packages/core/dist/cli/commands/update.js.map +1 -1
- package/packages/core/dist/cli/index.js +1 -1
- package/packages/core/dist/cli/index.js.map +1 -1
- package/packages/core/dist/cli/ocean-profiles.test.js +1 -1
- package/packages/core/dist/cli/ocean-profiles.test.js.map +1 -1
- package/packages/core/dist/cli/utils/files.d.ts +10 -0
- package/packages/core/dist/cli/utils/files.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/files.js +35 -0
- package/packages/core/dist/cli/utils/files.js.map +1 -1
- package/packages/core/dist/cli/utils/python.d.ts +22 -0
- package/packages/core/dist/cli/utils/python.d.ts.map +1 -0
- package/packages/core/dist/cli/utils/python.js +102 -0
- package/packages/core/dist/cli/utils/python.js.map +1 -0
- package/packages/core/dist/cli/utils/settings.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/settings.js +10 -0
- package/packages/core/dist/cli/utils/settings.js.map +1 -1
- package/packages/core/dist/scripts/generate-report.d.ts.map +1 -1
- package/packages/core/dist/scripts/generate-report.js +11 -7
- package/packages/core/dist/scripts/generate-report.js.map +1 -1
- package/packages/core/dist/scripts/generate-spider-report.d.ts.map +1 -1
- package/packages/core/dist/scripts/generate-spider-report.js +12 -8
- package/packages/core/dist/scripts/generate-spider-report.js.map +1 -1
- package/packages/core/dist/scripts/generate-spider.d.ts.map +1 -1
- package/packages/core/dist/scripts/generate-spider.js +6 -4
- package/packages/core/dist/scripts/generate-spider.js.map +1 -1
- package/packages/core/dist/scripts/generate-spider.test.js +2 -2
- package/packages/core/dist/scripts/generate-spider.test.js.map +1 -1
- package/pennyfarthing-dist/agents/README.md +1 -3
- package/pennyfarthing-dist/agents/architect.md +0 -6
- package/pennyfarthing-dist/agents/devops.md +0 -6
- package/pennyfarthing-dist/agents/orchestrator.md +0 -6
- package/pennyfarthing-dist/agents/pm.md +1 -7
- package/pennyfarthing-dist/agents/sm-finish.md +1 -1
- package/pennyfarthing-dist/agents/sm-setup.md +2 -2
- package/pennyfarthing-dist/agents/sm.md +4 -11
- package/pennyfarthing-dist/commands/architect.md +11 -3
- package/pennyfarthing-dist/commands/close-epic.md +24 -131
- package/pennyfarthing-dist/commands/create-theme.md +14 -24
- package/pennyfarthing-dist/commands/dev.md +11 -3
- package/pennyfarthing-dist/commands/devops.md +11 -3
- package/pennyfarthing-dist/commands/health-check.md +1 -3
- package/pennyfarthing-dist/commands/help.md +8 -12
- package/pennyfarthing-dist/commands/list-themes.md +14 -16
- package/pennyfarthing-dist/commands/orchestrator.md +11 -3
- package/pennyfarthing-dist/commands/parallel-work.md +1 -3
- package/pennyfarthing-dist/commands/pm.md +11 -3
- package/pennyfarthing-dist/commands/prime.md +6 -6
- package/pennyfarthing-dist/commands/repo-status.md +2 -2
- package/pennyfarthing-dist/commands/reviewer.md +11 -3
- package/pennyfarthing-dist/commands/run-ci.md +1 -1
- package/pennyfarthing-dist/commands/set-theme.md +14 -51
- package/pennyfarthing-dist/commands/setup.md +1 -1
- package/pennyfarthing-dist/commands/show-theme.md +14 -16
- package/pennyfarthing-dist/commands/sm.md +11 -3
- package/pennyfarthing-dist/commands/sprint.md +8 -8
- package/pennyfarthing-dist/commands/tea.md +11 -3
- package/pennyfarthing-dist/commands/tech-writer.md +11 -3
- package/pennyfarthing-dist/commands/theme-maker.md +14 -671
- package/pennyfarthing-dist/commands/theme.md +95 -0
- package/pennyfarthing-dist/commands/ux-designer.md +11 -3
- package/pennyfarthing-dist/commands/work.md +3 -5
- package/pennyfarthing-dist/guides/agent-coordination.md +11 -13
- package/pennyfarthing-dist/guides/agent-template-tactical.md +2 -3
- package/pennyfarthing-dist/guides/command-tag-taxonomy.md +212 -0
- package/pennyfarthing-dist/guides/hooks.md +5 -5
- package/pennyfarthing-dist/guides/patterns/fan-out-fan-in-pattern.md +3 -3
- package/pennyfarthing-dist/guides/patterns/helper-delegation-pattern.md +9 -59
- package/pennyfarthing-dist/guides/patterns/tdd-flow-pattern.md +4 -5
- package/pennyfarthing-dist/guides/prime.md +2 -2
- package/pennyfarthing-dist/guides/skill-schema.md +25 -26
- package/pennyfarthing-dist/guides/xml-tags.md +2 -2
- package/pennyfarthing-dist/scripts/README.md +2 -2
- package/pennyfarthing-dist/scripts/core/agent-session.sh +6 -2
- package/pennyfarthing-dist/scripts/core/prime.sh +8 -10
- package/pennyfarthing-dist/scripts/git/git-status-all.sh +1 -1
- package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +8 -6
- package/pennyfarthing-dist/scripts/git/worktree-manager.sh +3 -3
- package/pennyfarthing-dist/scripts/hooks/post-merge.sh +14 -12
- package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +4 -3
- package/pennyfarthing-dist/scripts/hooks/pre-push.sh +11 -5
- package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +1 -1
- package/pennyfarthing-dist/scripts/misc/README.md +1 -1
- package/pennyfarthing-dist/scripts/misc/repo-utils.sh +3 -3
- package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +1 -2
- package/pennyfarthing-dist/scripts/sprint/README.md +32 -17
- package/pennyfarthing-dist/scripts/story/README.md +1 -1
- package/pennyfarthing-dist/scripts/test/test-setup.sh +1 -1
- package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +5 -5
- package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +3 -79
- package/pennyfarthing-dist/scripts/theme/README.md +1 -1
- package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -1
- package/pennyfarthing-dist/scripts/workflow/finish-story.sh +62 -17
- package/pennyfarthing-dist/skills/dev-patterns/SKILL.md +2 -2
- package/pennyfarthing-dist/skills/skill-registry.yaml +41 -28
- package/pennyfarthing-dist/skills/sprint/skill.md +386 -68
- package/pennyfarthing-dist/skills/story/skill.md +14 -206
- package/pennyfarthing-dist/skills/theme/skill.md +290 -75
- package/pennyfarthing-dist/skills/theme-creation/SKILL.md +23 -166
- package/pennyfarthing-dist/skills/workflow/skill.md +4 -4
- package/pennyfarthing-dist/templates/agent-scopes.yaml.template +0 -11
- package/pennyfarthing-dist/templates/auto-load-sm.sh.template +14 -0
- package/pennyfarthing-dist/templates/settings.local.json.template +9 -0
- package/pennyfarthing-dist/workflows/2party-tdd.yaml +399 -0
- package/pennyfarthing-dist/workflows/epics-and-stories/steps/step-05-import-to-future.md +42 -25
- package/pennyfarthing-dist/workflows/git-cleanup.yaml +1 -1
- package/pennyfarthing-dist/workflows/project-setup/steps/step-10-complete.md +1 -1
- package/pennyfarthing_scripts/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/hooks.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/schema_validation_hook.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/cli.py +15 -0
- package/pennyfarthing_scripts/codemarkers/__init__.py +19 -0
- package/pennyfarthing_scripts/codemarkers/__main__.py +6 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/codemarkers/analyze.py +326 -0
- package/pennyfarthing_scripts/codemarkers/cli.py +129 -0
- package/pennyfarthing_scripts/codemarkers/formatters.py +89 -0
- package/pennyfarthing_scripts/codemarkers/models.py +45 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/themes.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/config.py +2 -1
- package/pennyfarthing_scripts/complexity/__init__.py +15 -0
- package/pennyfarthing_scripts/complexity/__main__.py +6 -0
- package/pennyfarthing_scripts/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/complexity/analyze.py +207 -0
- package/pennyfarthing_scripts/complexity/cli.py +78 -0
- package/pennyfarthing_scripts/complexity/formatters.py +64 -0
- package/pennyfarthing_scripts/complexity/models.py +32 -0
- package/pennyfarthing_scripts/deadcode/__init__.py +6 -0
- package/pennyfarthing_scripts/deadcode/__main__.py +6 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/deadcode/analyze.py +323 -0
- package/pennyfarthing_scripts/deadcode/cli.py +163 -0
- package/pennyfarthing_scripts/deadcode/formatters.py +106 -0
- package/pennyfarthing_scripts/deadcode/models.py +54 -0
- package/pennyfarthing_scripts/dependencies/__init__.py +20 -0
- package/pennyfarthing_scripts/dependencies/__main__.py +5 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/dependencies/analyze.py +155 -0
- package/pennyfarthing_scripts/dependencies/cli.py +72 -0
- package/pennyfarthing_scripts/dependencies/formatters.py +63 -0
- package/pennyfarthing_scripts/dependencies/models.py +39 -0
- package/pennyfarthing_scripts/healthscore/__init__.py +21 -0
- package/pennyfarthing_scripts/healthscore/__main__.py +6 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/healthscore/analyze.py +161 -0
- package/pennyfarthing_scripts/healthscore/cli.py +76 -0
- package/pennyfarthing_scripts/healthscore/formatters.py +46 -0
- package/pennyfarthing_scripts/healthscore/models.py +44 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/hotspots/analyze.py +28 -1
- package/pennyfarthing_scripts/hotspots/cli.py +11 -9
- package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/operations.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/bidirectional.py +42 -15
- package/pennyfarthing_scripts/jira/cli.py +78 -1
- package/pennyfarthing_scripts/jira/client.py +28 -0
- package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/tiers.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/workflow.py +5 -3
- package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/archive.py +63 -6
- package/pennyfarthing_scripts/sprint/archive_epic.py +198 -85
- package/pennyfarthing_scripts/sprint/cli.py +1565 -65
- package/pennyfarthing_scripts/sprint/epic_add.py +173 -0
- package/pennyfarthing_scripts/sprint/loader.py +46 -2
- package/pennyfarthing_scripts/sprint/story_add.py +202 -27
- package/pennyfarthing_scripts/sprint/story_finish.py +211 -0
- package/pennyfarthing_scripts/sprint/validate_cmd.py +44 -5
- package/pennyfarthing_scripts/sprint/validator.py +13 -3
- package/pennyfarthing_scripts/sprint/work.py +43 -3
- package/pennyfarthing_scripts/sprint/yaml_io.py +124 -15
- package/pennyfarthing_scripts/tests/__pycache__/test_codemarkers.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_healthscore.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_package.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_add.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_story_update.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_validate_cmd.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_yaml_io.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/test_codemarkers.py +682 -0
- package/pennyfarthing_scripts/tests/test_healthscore.py +524 -0
- package/pennyfarthing_scripts/tests/test_sprint_package.py +166 -0
- package/pennyfarthing_scripts/tests/test_yaml_io.py +117 -0
- package/pennyfarthing_scripts/theme/__init__.py +5 -0
- package/pennyfarthing_scripts/theme/__main__.py +6 -0
- package/pennyfarthing_scripts/theme/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/theme/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/theme/cli.py +286 -0
- package/scripts/README.md +53 -0
- package/scripts/postinstall.cjs +34 -0
- package/pennyfarthing-dist/agents/workflow-status-check.md +0 -96
- package/pennyfarthing-dist/scripts/sprint/archive-story.sh +0 -133
- package/pennyfarthing-dist/scripts/sprint/available-stories.sh +0 -91
- package/pennyfarthing-dist/scripts/sprint/check-story.sh +0 -158
- package/pennyfarthing-dist/scripts/sprint/get-epic-field.sh +0 -52
- package/pennyfarthing-dist/scripts/sprint/get-story-field.sh +0 -63
- package/pennyfarthing-dist/scripts/sprint/list-future.sh +0 -145
- package/pennyfarthing-dist/scripts/sprint/new-sprint.sh +0 -110
- package/pennyfarthing-dist/scripts/sprint/promote-epic.sh +0 -148
- package/pennyfarthing-dist/scripts/sprint/sprint-common.sh +0 -415
- package/pennyfarthing-dist/scripts/sprint/sprint-info.sh +0 -33
- package/pennyfarthing-dist/scripts/sprint/sprint-metrics.sh +0 -230
- package/pennyfarthing-dist/scripts/sprint/sprint-status.sh +0 -134
- package/pennyfarthing-dist/scripts/sprint/validate-sprint-yaml.sh +0 -139
- package/pennyfarthing-dist/skills/sprint/scripts/archive-story.sh +0 -101
- package/pennyfarthing-dist/skills/sprint/scripts/available-stories.sh +0 -97
- package/pennyfarthing-dist/skills/sprint/scripts/check-story.sh +0 -164
- package/pennyfarthing-dist/skills/sprint/scripts/create-jira-epic.sh +0 -23
- package/pennyfarthing-dist/skills/sprint/scripts/new-sprint.sh +0 -116
- package/pennyfarthing-dist/skills/sprint/scripts/promote-epic.sh +0 -164
- package/pennyfarthing-dist/skills/sprint/scripts/sprint-info.sh +0 -39
- package/pennyfarthing-dist/skills/sprint/scripts/sprint-status.sh +0 -147
- package/pennyfarthing-dist/skills/sprint/scripts/sync-epic-jira.sh +0 -23
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Output formatters for code marker analysis results.
|
|
3
|
+
|
|
4
|
+
Supports table, JSON, and CSV output — no external dependencies.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import csv
|
|
10
|
+
import io
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
from dataclasses import asdict
|
|
14
|
+
from typing import TextIO
|
|
15
|
+
|
|
16
|
+
from pennyfarthing_scripts.codemarkers.models import (
|
|
17
|
+
CodeMarker,
|
|
18
|
+
CodeMarkersResult,
|
|
19
|
+
MarkerSummary,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def format_marker_table(markers: list[CodeMarker], top_n: int = 20) -> str:
|
|
24
|
+
"""Format code markers as a column-aligned table."""
|
|
25
|
+
if not markers:
|
|
26
|
+
return " No markers found."
|
|
27
|
+
|
|
28
|
+
items = markers[:top_n]
|
|
29
|
+
|
|
30
|
+
hdr = f"{'Type':>6} {'Age':>6} {'Stale':>5} {'Author':>12} {'Line':>5} File"
|
|
31
|
+
sep = f"{'------':>6} {'------':>6} {'-----':>5} {'------------':>12} {'-----':>5} ----"
|
|
32
|
+
|
|
33
|
+
lines = [hdr, sep]
|
|
34
|
+
for m in items:
|
|
35
|
+
stale = "YES" if m.is_stale else ""
|
|
36
|
+
age = f"{m.age_days:.0f}d" if m.age_days > 0 else ""
|
|
37
|
+
author = (m.author[:12] if len(m.author) > 12 else m.author) if m.author else ""
|
|
38
|
+
lines.append(
|
|
39
|
+
f"{m.marker_type:>6} {age:>6} {stale:>5} {author:>12} {m.line:>5} {m.path}"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return "\n".join(lines)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def format_summary(result: CodeMarkersResult, file: TextIO = sys.stderr) -> None:
|
|
46
|
+
"""Print a summary header for a single repo result."""
|
|
47
|
+
from pennyfarthing_scripts.common.output import header, info
|
|
48
|
+
|
|
49
|
+
header(f" Code Markers: {result.repo_name}", char="=", width=60, file=file)
|
|
50
|
+
info(f"Path: {result.repo_path}", file=file)
|
|
51
|
+
info(f"Stale threshold: {result.stale_threshold_days} days", file=file)
|
|
52
|
+
if result.summary:
|
|
53
|
+
info(f"Total markers: {result.summary.total_markers}", file=file)
|
|
54
|
+
info(f"Stale markers: {result.summary.stale_markers}", file=file)
|
|
55
|
+
for mtype, count in sorted(result.summary.by_type.items()):
|
|
56
|
+
info(f" {mtype}: {count}", file=file)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def export_json(result: CodeMarkersResult) -> str:
|
|
60
|
+
"""Serialize result to JSON string."""
|
|
61
|
+
return json.dumps(asdict(result), indent=2, default=str)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def export_csv(markers: list[CodeMarker]) -> str:
|
|
65
|
+
"""Export markers as CSV."""
|
|
66
|
+
buf = io.StringIO()
|
|
67
|
+
writer = csv.writer(buf)
|
|
68
|
+
writer.writerow([
|
|
69
|
+
"path",
|
|
70
|
+
"line",
|
|
71
|
+
"marker_type",
|
|
72
|
+
"text",
|
|
73
|
+
"author",
|
|
74
|
+
"date",
|
|
75
|
+
"age_days",
|
|
76
|
+
"is_stale",
|
|
77
|
+
])
|
|
78
|
+
for m in markers:
|
|
79
|
+
writer.writerow([
|
|
80
|
+
m.path,
|
|
81
|
+
m.line,
|
|
82
|
+
m.marker_type,
|
|
83
|
+
m.text,
|
|
84
|
+
m.author,
|
|
85
|
+
m.date,
|
|
86
|
+
m.age_days,
|
|
87
|
+
m.is_stale,
|
|
88
|
+
])
|
|
89
|
+
return buf.getvalue()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data models for code marker analysis results.
|
|
3
|
+
|
|
4
|
+
Follows ADR-0008 result pattern — structured dataclasses with success/error fields.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class CodeMarker:
|
|
14
|
+
"""A single code marker (TODO, FIXME, HACK, XXX) found in source."""
|
|
15
|
+
|
|
16
|
+
path: str
|
|
17
|
+
line: int
|
|
18
|
+
marker_type: str # TODO, FIXME, HACK, XXX
|
|
19
|
+
text: str # Full comment text
|
|
20
|
+
author: str = "" # From git blame
|
|
21
|
+
date: str = "" # ISO date from git blame
|
|
22
|
+
age_days: float = 0.0 # Computed from blame date
|
|
23
|
+
is_stale: bool = False # age_days > stale_threshold
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class MarkerSummary:
|
|
28
|
+
"""Aggregate counts of markers by type."""
|
|
29
|
+
|
|
30
|
+
total_markers: int = 0
|
|
31
|
+
stale_markers: int = 0
|
|
32
|
+
by_type: dict[str, int] = field(default_factory=dict)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class CodeMarkersResult:
|
|
37
|
+
"""Analysis result for a single repository."""
|
|
38
|
+
|
|
39
|
+
success: bool
|
|
40
|
+
repo_name: str
|
|
41
|
+
repo_path: str
|
|
42
|
+
stale_threshold_days: int
|
|
43
|
+
markers: list[CodeMarker] = field(default_factory=list)
|
|
44
|
+
summary: MarkerSummary | None = None
|
|
45
|
+
error: str | None = None
|
|
Binary file
|
|
Binary file
|
|
@@ -57,7 +57,8 @@ def get_project_root(start_dir: Path | None = None) -> Path:
|
|
|
57
57
|
check = check.parent
|
|
58
58
|
|
|
59
59
|
raise FileNotFoundError(
|
|
60
|
-
"Could not find project root (no pennyfarthing-dist/ or .pennyfarthing/ directory found)"
|
|
60
|
+
"Could not find project root (no pennyfarthing-dist/ or .pennyfarthing/ directory found).\n"
|
|
61
|
+
"If this is a fresh clone, run: just setup"
|
|
61
62
|
)
|
|
62
63
|
|
|
63
64
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Code complexity analysis for TypeScript/JavaScript projects.
|
|
3
|
+
|
|
4
|
+
Wraps eslint with complexity rules to extract per-file metrics:
|
|
5
|
+
cyclomatic complexity, function length, nesting depth, and line counts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pennyfarthing_scripts.complexity.models import FileComplexity, ComplexityResult
|
|
9
|
+
from pennyfarthing_scripts.complexity.analyze import analyze_complexity
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"FileComplexity",
|
|
13
|
+
"ComplexityResult",
|
|
14
|
+
"analyze_complexity",
|
|
15
|
+
]
|
|
Binary file
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core complexity analysis engine.
|
|
3
|
+
|
|
4
|
+
Wraps eslint --format json with complexity/max-depth/max-lines-per-function rules.
|
|
5
|
+
Parses output into per-file metrics following ADR-0008 result pattern.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import fnmatch
|
|
12
|
+
import json
|
|
13
|
+
import re
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from pennyfarthing_scripts.complexity.models import FileComplexity, ComplexityResult
|
|
17
|
+
|
|
18
|
+
# Regex patterns for extracting numeric values from ESLint messages
|
|
19
|
+
COMPLEXITY_RE = re.compile(r"complexity of (\d+)")
|
|
20
|
+
MAX_DEPTH_RE = re.compile(r"too deeply \((\d+)\)")
|
|
21
|
+
MAX_LINES_RE = re.compile(r"too many lines \((\d+)\)")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _find_eslint(target_path: Path) -> Path | None:
|
|
25
|
+
"""Find eslint binary in node_modules/.bin/.
|
|
26
|
+
|
|
27
|
+
Walks up from target_path looking for node_modules/.bin/eslint.
|
|
28
|
+
"""
|
|
29
|
+
candidate = target_path / "node_modules" / ".bin" / "eslint"
|
|
30
|
+
if candidate.exists():
|
|
31
|
+
return candidate
|
|
32
|
+
|
|
33
|
+
for parent in target_path.parents:
|
|
34
|
+
candidate = parent / "node_modules" / ".bin" / "eslint"
|
|
35
|
+
if candidate.exists():
|
|
36
|
+
return candidate
|
|
37
|
+
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _parse_eslint_output(output: str, target_path: Path) -> list[FileComplexity]:
|
|
42
|
+
"""Parse eslint JSON output into FileComplexity models.
|
|
43
|
+
|
|
44
|
+
Extracts per-file metrics from ESLint rule violations:
|
|
45
|
+
- complexity rule → cyclomatic complexity per function
|
|
46
|
+
- max-depth rule → nesting depth
|
|
47
|
+
- max-lines-per-function rule → function line count
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
data = json.loads(output)
|
|
51
|
+
except (json.JSONDecodeError, TypeError):
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
if not data:
|
|
55
|
+
return []
|
|
56
|
+
|
|
57
|
+
target_str = str(target_path)
|
|
58
|
+
if not target_str.endswith("/"):
|
|
59
|
+
target_str += "/"
|
|
60
|
+
|
|
61
|
+
files = []
|
|
62
|
+
for file_entry in data:
|
|
63
|
+
file_path = file_entry.get("filePath", "")
|
|
64
|
+
messages = file_entry.get("messages", [])
|
|
65
|
+
|
|
66
|
+
# Make path relative to target directory
|
|
67
|
+
if file_path.startswith(target_str):
|
|
68
|
+
rel_path = file_path[len(target_str):]
|
|
69
|
+
else:
|
|
70
|
+
rel_path = file_path
|
|
71
|
+
|
|
72
|
+
complexities = []
|
|
73
|
+
depths = []
|
|
74
|
+
line_counts = []
|
|
75
|
+
|
|
76
|
+
for msg in messages:
|
|
77
|
+
rule_id = msg.get("ruleId", "")
|
|
78
|
+
message = msg.get("message", "")
|
|
79
|
+
|
|
80
|
+
if rule_id == "complexity":
|
|
81
|
+
m = COMPLEXITY_RE.search(message)
|
|
82
|
+
if m:
|
|
83
|
+
complexities.append(int(m.group(1)))
|
|
84
|
+
|
|
85
|
+
elif rule_id == "max-depth":
|
|
86
|
+
m = MAX_DEPTH_RE.search(message)
|
|
87
|
+
if m:
|
|
88
|
+
depths.append(int(m.group(1)))
|
|
89
|
+
|
|
90
|
+
elif rule_id == "max-lines-per-function":
|
|
91
|
+
m = MAX_LINES_RE.search(message)
|
|
92
|
+
if m:
|
|
93
|
+
line_counts.append(int(m.group(1)))
|
|
94
|
+
|
|
95
|
+
function_count = len(complexities)
|
|
96
|
+
avg_complexity = (
|
|
97
|
+
sum(complexities) / len(complexities) if complexities else 0.0
|
|
98
|
+
)
|
|
99
|
+
max_nesting = max(depths) if depths else 0
|
|
100
|
+
longest_fn = max(line_counts) if line_counts else 0
|
|
101
|
+
|
|
102
|
+
files.append(FileComplexity(
|
|
103
|
+
path=rel_path,
|
|
104
|
+
total_lines=0, # populated by _count_file_lines
|
|
105
|
+
longest_function=longest_fn,
|
|
106
|
+
avg_cyclomatic_complexity=round(avg_complexity, 1),
|
|
107
|
+
max_nesting_depth=max_nesting,
|
|
108
|
+
function_count=function_count,
|
|
109
|
+
))
|
|
110
|
+
|
|
111
|
+
return files
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
async def _run_eslint(eslint_bin: Path, target_path: Path) -> tuple[str, str, int]:
|
|
115
|
+
"""Run eslint subprocess with complexity rules enabled."""
|
|
116
|
+
proc = await asyncio.create_subprocess_exec(
|
|
117
|
+
str(eslint_bin),
|
|
118
|
+
str(target_path),
|
|
119
|
+
"--format", "json",
|
|
120
|
+
"--no-config-lookup",
|
|
121
|
+
"--rule", 'complexity: ["warn", 1]',
|
|
122
|
+
"--rule", 'max-depth: ["warn", 1]',
|
|
123
|
+
"--rule", 'max-lines-per-function: ["warn", 1]',
|
|
124
|
+
stdout=asyncio.subprocess.PIPE,
|
|
125
|
+
stderr=asyncio.subprocess.PIPE,
|
|
126
|
+
)
|
|
127
|
+
stdout, stderr = await proc.communicate()
|
|
128
|
+
return (
|
|
129
|
+
stdout.decode("utf-8", errors="replace"),
|
|
130
|
+
stderr.decode("utf-8", errors="replace"),
|
|
131
|
+
proc.returncode or 0,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
async def _count_file_lines(file_path: Path) -> int:
|
|
136
|
+
"""Count lines in a file."""
|
|
137
|
+
try:
|
|
138
|
+
content = file_path.read_bytes()
|
|
139
|
+
return content.count(b"\n") + (1 if content and not content.endswith(b"\n") else 0)
|
|
140
|
+
except OSError:
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _should_exclude(path: str, patterns: list[str]) -> bool:
|
|
145
|
+
"""Check if a file path matches any exclusion pattern."""
|
|
146
|
+
for pattern in patterns:
|
|
147
|
+
if fnmatch.fnmatch(path, pattern):
|
|
148
|
+
return True
|
|
149
|
+
# Check basename
|
|
150
|
+
if fnmatch.fnmatch(path.split("/")[-1], pattern):
|
|
151
|
+
return True
|
|
152
|
+
# Check if any parent directory matches (e.g. node_modules/* matches node_modules/lib/foo.ts)
|
|
153
|
+
parts = path.split("/")
|
|
154
|
+
for i in range(len(parts)):
|
|
155
|
+
partial = "/".join(parts[: i + 1])
|
|
156
|
+
if fnmatch.fnmatch(partial, pattern):
|
|
157
|
+
return True
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
async def analyze_complexity(
|
|
162
|
+
target_path: Path,
|
|
163
|
+
excludes: list[str] | None = None,
|
|
164
|
+
) -> ComplexityResult:
|
|
165
|
+
"""Analyze complexity of files in the target directory."""
|
|
166
|
+
resolved = target_path.resolve()
|
|
167
|
+
|
|
168
|
+
eslint_bin = _find_eslint(resolved)
|
|
169
|
+
if eslint_bin is None:
|
|
170
|
+
return ComplexityResult(
|
|
171
|
+
success=False,
|
|
172
|
+
target_path=str(resolved),
|
|
173
|
+
error="eslint not found. Install with: npm install -D eslint",
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
stdout, stderr, rc = await _run_eslint(eslint_bin, resolved)
|
|
177
|
+
|
|
178
|
+
# Try resolved path first, fall back to original (handles /tmp → /private/tmp on macOS)
|
|
179
|
+
files = _parse_eslint_output(stdout, resolved)
|
|
180
|
+
if not files or (files and files[0].path.startswith("/")):
|
|
181
|
+
alt_files = _parse_eslint_output(stdout, target_path)
|
|
182
|
+
if alt_files and (not files or not alt_files[0].path.startswith("/")):
|
|
183
|
+
files = alt_files
|
|
184
|
+
|
|
185
|
+
if not files:
|
|
186
|
+
return ComplexityResult(
|
|
187
|
+
success=True,
|
|
188
|
+
target_path=str(resolved),
|
|
189
|
+
file_count=0,
|
|
190
|
+
files=[],
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Apply exclude filters
|
|
194
|
+
if excludes:
|
|
195
|
+
files = [f for f in files if not _should_exclude(f.path, excludes)]
|
|
196
|
+
|
|
197
|
+
# Count lines for each file
|
|
198
|
+
for fc in files:
|
|
199
|
+
full_path = resolved / fc.path
|
|
200
|
+
fc.total_lines = await _count_file_lines(full_path)
|
|
201
|
+
|
|
202
|
+
return ComplexityResult(
|
|
203
|
+
success=True,
|
|
204
|
+
target_path=str(resolved),
|
|
205
|
+
file_count=len(files),
|
|
206
|
+
files=files,
|
|
207
|
+
)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI commands for complexity analysis.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python -m pennyfarthing_scripts.complexity analyze [OPTIONS]
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.group()
|
|
17
|
+
def complexity():
|
|
18
|
+
"""Code complexity analysis.
|
|
19
|
+
|
|
20
|
+
\b
|
|
21
|
+
Commands:
|
|
22
|
+
analyze - Analyze code complexity metrics
|
|
23
|
+
"""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _common_options(fn):
|
|
28
|
+
"""Shared options for complexity commands."""
|
|
29
|
+
fn = click.option("--path", "target_path", type=click.Path(exists=True),
|
|
30
|
+
help="Directory to analyze")(fn)
|
|
31
|
+
fn = click.option("--format", "fmt", type=click.Choice(["table", "json", "csv"]),
|
|
32
|
+
default="table", show_default=True)(fn)
|
|
33
|
+
fn = click.option("--top", default=20, show_default=True,
|
|
34
|
+
help="Number of top results")(fn)
|
|
35
|
+
fn = click.option("--output", "output_file", type=click.Path(),
|
|
36
|
+
help="Write output to file")(fn)
|
|
37
|
+
fn = click.option("--exclude", multiple=True,
|
|
38
|
+
help="Exclude patterns (repeatable)")(fn)
|
|
39
|
+
return fn
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _run_analysis(target_path: str | None, exclude: tuple) -> "ComplexityResult":
|
|
43
|
+
"""Run analysis and return result."""
|
|
44
|
+
from pennyfarthing_scripts.complexity.analyze import analyze_complexity
|
|
45
|
+
|
|
46
|
+
excludes = list(exclude) if exclude else None
|
|
47
|
+
p = Path(target_path).resolve() if target_path else Path(".").resolve()
|
|
48
|
+
return asyncio.run(analyze_complexity(p, excludes))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _output_result(result, fmt: str, output_file: str | None, top: int):
|
|
52
|
+
"""Format and output the analysis result."""
|
|
53
|
+
from pennyfarthing_scripts.complexity.formatters import (
|
|
54
|
+
export_csv,
|
|
55
|
+
export_json,
|
|
56
|
+
format_file_table,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if fmt == "json":
|
|
60
|
+
text = export_json(result)
|
|
61
|
+
elif fmt == "csv":
|
|
62
|
+
text = export_csv(result.files[:top])
|
|
63
|
+
else:
|
|
64
|
+
text = format_file_table(result.files, top)
|
|
65
|
+
|
|
66
|
+
if output_file:
|
|
67
|
+
Path(output_file).write_text(text)
|
|
68
|
+
click.echo(f"Output written to {output_file}", err=True)
|
|
69
|
+
else:
|
|
70
|
+
click.echo(text)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@complexity.command()
|
|
74
|
+
@_common_options
|
|
75
|
+
def analyze(target_path, fmt, top, output_file, exclude):
|
|
76
|
+
"""Analyze code complexity."""
|
|
77
|
+
result = _run_analysis(target_path, exclude)
|
|
78
|
+
_output_result(result, fmt, output_file, top)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Output formatters for complexity analysis results.
|
|
3
|
+
|
|
4
|
+
Supports table, JSON, and CSV output — no external dependencies.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import csv
|
|
10
|
+
import io
|
|
11
|
+
import json
|
|
12
|
+
from dataclasses import asdict
|
|
13
|
+
|
|
14
|
+
from pennyfarthing_scripts.complexity.models import FileComplexity, ComplexityResult
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def format_file_table(files: list[FileComplexity], top_n: int = 20) -> str:
|
|
18
|
+
"""Format complexity results as column-aligned table."""
|
|
19
|
+
if not files:
|
|
20
|
+
return " No complexity results found."
|
|
21
|
+
|
|
22
|
+
items = files[:top_n]
|
|
23
|
+
|
|
24
|
+
hdr = f"{'Complexity':>11} {'Longest Fn':>11} {'Nesting':>8} {'Functions':>10} {'Lines':>6} File"
|
|
25
|
+
sep = f"{'-----------':>11} {'-----------':>11} {'--------':>8} {'----------':>10} {'------':>6} ----"
|
|
26
|
+
|
|
27
|
+
lines = [hdr, sep]
|
|
28
|
+
for f in items:
|
|
29
|
+
lines.append(
|
|
30
|
+
f"{f.avg_cyclomatic_complexity:>11.1f} {f.longest_function:>11} "
|
|
31
|
+
f"{f.max_nesting_depth:>8} {f.function_count:>10} "
|
|
32
|
+
f"{f.total_lines:>6} {f.path}"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
return "\n".join(lines)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def export_json(result: ComplexityResult) -> str:
|
|
39
|
+
"""Serialize result to JSON string."""
|
|
40
|
+
return json.dumps(asdict(result), indent=2, default=str)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def export_csv(files: list[FileComplexity]) -> str:
|
|
44
|
+
"""Export file complexity data as CSV."""
|
|
45
|
+
buf = io.StringIO()
|
|
46
|
+
writer = csv.writer(buf)
|
|
47
|
+
writer.writerow([
|
|
48
|
+
"path",
|
|
49
|
+
"total_lines",
|
|
50
|
+
"longest_function",
|
|
51
|
+
"avg_cyclomatic_complexity",
|
|
52
|
+
"max_nesting_depth",
|
|
53
|
+
"function_count",
|
|
54
|
+
])
|
|
55
|
+
for f in files:
|
|
56
|
+
writer.writerow([
|
|
57
|
+
f.path,
|
|
58
|
+
f.total_lines,
|
|
59
|
+
f.longest_function,
|
|
60
|
+
f.avg_cyclomatic_complexity,
|
|
61
|
+
f.max_nesting_depth,
|
|
62
|
+
f.function_count,
|
|
63
|
+
])
|
|
64
|
+
return buf.getvalue()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data models for complexity analysis results.
|
|
3
|
+
|
|
4
|
+
Follows ADR-0008 result pattern — structured dataclasses with success/error fields.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class FileComplexity:
|
|
14
|
+
"""Complexity metrics for a single file."""
|
|
15
|
+
|
|
16
|
+
path: str
|
|
17
|
+
total_lines: int = 0
|
|
18
|
+
longest_function: int = 0
|
|
19
|
+
avg_cyclomatic_complexity: float = 0.0
|
|
20
|
+
max_nesting_depth: int = 0
|
|
21
|
+
function_count: int = 0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class ComplexityResult:
|
|
26
|
+
"""Analysis result following ADR-0008 pattern."""
|
|
27
|
+
|
|
28
|
+
success: bool
|
|
29
|
+
target_path: str = ""
|
|
30
|
+
file_count: int = 0
|
|
31
|
+
files: list[FileComplexity] = field(default_factory=list)
|
|
32
|
+
error: str | None = None
|
|
Binary file
|