@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
|
@@ -10,9 +10,6 @@ thisStepFile: './step-05-import-to-future.md'
|
|
|
10
10
|
workflowFile: '{workflow_path}/workflow.yaml'
|
|
11
11
|
outputFile: '{planning_artifacts}/epics.md'
|
|
12
12
|
futureYaml: '{project_root}/sprint/future.yaml'
|
|
13
|
-
|
|
14
|
-
# Script References
|
|
15
|
-
importScript: '{project_root}/.pennyfarthing/scripts/sprint/import-epic-to-future.sh'
|
|
16
13
|
---
|
|
17
14
|
|
|
18
15
|
<purpose>
|
|
@@ -21,12 +18,13 @@ To import the validated and complete epics and stories from the epics.md documen
|
|
|
21
18
|
|
|
22
19
|
<instructions>
|
|
23
20
|
1. Determine the initiative name from the epics document (prompt user if not obvious)
|
|
24
|
-
2.
|
|
25
|
-
3.
|
|
26
|
-
4.
|
|
27
|
-
5.
|
|
28
|
-
6.
|
|
29
|
-
7.
|
|
21
|
+
2. Read current future.yaml to find the next epic number (highest epic-N + 1)
|
|
22
|
+
3. Read the validated epics.md output and construct the YAML structure
|
|
23
|
+
4. Display a preview to the user showing epic numbers, initiative structure, and story IDs
|
|
24
|
+
5. Get user confirmation that the preview looks correct
|
|
25
|
+
6. If confirmed, append the new initiative and epics to future.yaml using yq
|
|
26
|
+
7. Verify the import by checking that epic appears in future.yaml with correct numbering
|
|
27
|
+
8. Display completion message with epic number, initiative name, and story count
|
|
30
28
|
</instructions>
|
|
31
29
|
|
|
32
30
|
<output>
|
|
@@ -72,34 +70,53 @@ Look at the epics document title and ask user:
|
|
|
72
70
|
|
|
73
71
|
Wait for user confirmation or alternative name.
|
|
74
72
|
|
|
75
|
-
### 2.
|
|
73
|
+
### 2. Determine Next Epic Number
|
|
76
74
|
|
|
77
|
-
|
|
75
|
+
Read `sprint/future.yaml` and find the highest `epic-N` ID currently in use:
|
|
78
76
|
|
|
79
77
|
```bash
|
|
80
|
-
.
|
|
78
|
+
yq '.future.initiatives[].epics[].id' sprint/future.yaml | grep -oE 'epic-[0-9]+' | sed 's/epic-//' | sort -n | tail -1
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The next epic gets `epic-{N+1}`. Stories use `{N+1}-{story_number}` format.
|
|
82
|
+
|
|
83
|
+
### 3. Construct and Preview
|
|
84
|
+
|
|
85
|
+
Read the validated epics.md output file. For each epic, construct the YAML structure:
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
- id: epic-{N}
|
|
89
|
+
title: "Epic: {epic_title}"
|
|
90
|
+
points: {total_points}
|
|
91
|
+
priority: {priority}
|
|
92
|
+
repos: {repo}
|
|
93
|
+
stories:
|
|
94
|
+
- id: "{N}-1"
|
|
95
|
+
title: "{story_title}"
|
|
96
|
+
points: {points}
|
|
97
|
+
type: {type}
|
|
98
|
+
status: backlog
|
|
99
|
+
workflow: {workflow}
|
|
100
|
+
priority: {priority}
|
|
101
|
+
repos: {repo}
|
|
81
102
|
```
|
|
82
103
|
|
|
83
|
-
Display the preview
|
|
104
|
+
Display the full preview to the user showing:
|
|
84
105
|
- Next epic number that will be assigned
|
|
85
106
|
- Initiative structure
|
|
86
107
|
- All stories with IDs
|
|
87
108
|
|
|
88
|
-
###
|
|
109
|
+
### 4. Confirm and Apply
|
|
89
110
|
|
|
90
111
|
Ask user: "Does this look correct? [Y] Yes, import to future.yaml / [N] No, make changes"
|
|
91
112
|
|
|
92
113
|
**If Y:**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
.pennyfarthing/scripts/sprint/import-epic-to-future.sh {outputFile} "{initiative_name}"
|
|
97
|
-
```
|
|
114
|
+
Append the new initiative and epics to `sprint/future.yaml` using yq or direct YAML editing.
|
|
98
115
|
|
|
99
116
|
**If N:**
|
|
100
|
-
Ask what changes are needed and help user adjust before re-
|
|
117
|
+
Ask what changes are needed and help user adjust before re-applying.
|
|
101
118
|
|
|
102
|
-
###
|
|
119
|
+
### 5. Verify Import
|
|
103
120
|
|
|
104
121
|
After successful import, verify by showing:
|
|
105
122
|
|
|
@@ -112,7 +129,7 @@ Confirm:
|
|
|
112
129
|
- Epic number is correct
|
|
113
130
|
- Stories have proper IDs
|
|
114
131
|
|
|
115
|
-
###
|
|
132
|
+
### 6. Complete Workflow
|
|
116
133
|
|
|
117
134
|
Display completion message:
|
|
118
135
|
|
|
@@ -126,7 +143,7 @@ Display completion message:
|
|
|
126
143
|
|
|
127
144
|
Next steps:
|
|
128
145
|
- Use `/sprint` to view the backlog
|
|
129
|
-
- Use `promote
|
|
146
|
+
- Use `pf sprint epic promote` to move to a sprint when ready
|
|
130
147
|
```
|
|
131
148
|
|
|
132
149
|
## SUCCESS CRITERIA:
|
|
@@ -138,8 +155,8 @@ Next steps:
|
|
|
138
155
|
|
|
139
156
|
## FAILURE MODES:
|
|
140
157
|
|
|
141
|
-
- ❌ Import script not found - check .pennyfarthing symlinks
|
|
142
158
|
- ❌ future.yaml not found - ensure sprint/ directory exists
|
|
143
|
-
- ❌ Duplicate epic number -
|
|
159
|
+
- ❌ Duplicate epic number - check existing IDs before assigning
|
|
160
|
+
- ❌ yq not installed - required for YAML manipulation (`brew install yq`)
|
|
144
161
|
|
|
145
162
|
**Master Rule:** The workflow is not complete until epics are in future.yaml and accessible via sprint commands.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -45,6 +45,21 @@ from pennyfarthing_scripts.jira.cli import jira
|
|
|
45
45
|
|
|
46
46
|
cli.add_command(jira)
|
|
47
47
|
|
|
48
|
+
# Import and register deadcode group
|
|
49
|
+
from pennyfarthing_scripts.deadcode.cli import deadcode
|
|
50
|
+
|
|
51
|
+
cli.add_command(deadcode)
|
|
52
|
+
|
|
53
|
+
# Import and register theme group
|
|
54
|
+
from pennyfarthing_scripts.theme.cli import theme
|
|
55
|
+
|
|
56
|
+
cli.add_command(theme)
|
|
57
|
+
|
|
58
|
+
# Import and register healthscore group
|
|
59
|
+
from pennyfarthing_scripts.healthscore.cli import healthscore
|
|
60
|
+
|
|
61
|
+
cli.add_command(healthscore)
|
|
62
|
+
|
|
48
63
|
|
|
49
64
|
@cli.group()
|
|
50
65
|
def agent():
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Code marker analysis — TODO, FIXME, HACK, XXX detection with git blame.
|
|
3
|
+
|
|
4
|
+
Story 80-1: Python codemarkers module.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pennyfarthing_scripts.codemarkers.models import (
|
|
8
|
+
CodeMarker,
|
|
9
|
+
CodeMarkersResult,
|
|
10
|
+
MarkerSummary,
|
|
11
|
+
)
|
|
12
|
+
from pennyfarthing_scripts.codemarkers.analyze import analyze_repo
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"CodeMarker",
|
|
16
|
+
"CodeMarkersResult",
|
|
17
|
+
"MarkerSummary",
|
|
18
|
+
"analyze_repo",
|
|
19
|
+
]
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core code marker analysis engine.
|
|
3
|
+
|
|
4
|
+
Greps source files for TODO/FIXME/HACK/XXX markers, runs git blame for
|
|
5
|
+
author and age data, and computes staleness.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import fnmatch
|
|
12
|
+
import re
|
|
13
|
+
import time
|
|
14
|
+
from collections import defaultdict
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from pennyfarthing_scripts.codemarkers.models import (
|
|
19
|
+
CodeMarker,
|
|
20
|
+
CodeMarkersResult,
|
|
21
|
+
MarkerSummary,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Marker types to detect (case-sensitive, uppercase only)
|
|
25
|
+
MARKER_PATTERN = re.compile(r"\b(TODO|FIXME|HACK|XXX)\b")
|
|
26
|
+
|
|
27
|
+
# Default file patterns to exclude from analysis
|
|
28
|
+
DEFAULT_EXCLUDES = [
|
|
29
|
+
"node_modules/*",
|
|
30
|
+
"dist/*",
|
|
31
|
+
"build/*",
|
|
32
|
+
"*.lock",
|
|
33
|
+
"*.min.js",
|
|
34
|
+
"*.min.css",
|
|
35
|
+
"*.map",
|
|
36
|
+
"package-lock.json",
|
|
37
|
+
"pnpm-lock.yaml",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
# Binary file extensions to skip
|
|
41
|
+
_BINARY_EXTENSIONS = frozenset({
|
|
42
|
+
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".svg",
|
|
43
|
+
".woff", ".woff2", ".ttf", ".eot", ".otf",
|
|
44
|
+
".zip", ".gz", ".tar", ".bz2", ".7z", ".rar",
|
|
45
|
+
".pdf", ".doc", ".docx", ".xls", ".xlsx",
|
|
46
|
+
".exe", ".dll", ".so", ".dylib", ".o", ".a",
|
|
47
|
+
".pyc", ".pyo", ".class", ".jar",
|
|
48
|
+
".mp3", ".mp4", ".wav", ".avi", ".mov",
|
|
49
|
+
".sqlite", ".db",
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _should_exclude(path: str, patterns: list[str]) -> bool:
|
|
54
|
+
"""Check if a file path matches any exclusion pattern."""
|
|
55
|
+
for pattern in patterns:
|
|
56
|
+
if fnmatch.fnmatch(path, pattern):
|
|
57
|
+
return True
|
|
58
|
+
# Also check the basename for patterns like "*.lock"
|
|
59
|
+
if fnmatch.fnmatch(path.split("/")[-1], pattern):
|
|
60
|
+
return True
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _grep_markers(root: Path, excludes: list[str]) -> list[dict]:
|
|
65
|
+
"""Scan files under root for TODO/FIXME/HACK/XXX markers.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
root: Directory to scan recursively
|
|
69
|
+
excludes: Glob patterns for files/dirs to skip
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
List of dicts: {path, line, marker_type, text}
|
|
73
|
+
"""
|
|
74
|
+
results: list[dict] = []
|
|
75
|
+
|
|
76
|
+
for file_path in sorted(root.rglob("*")):
|
|
77
|
+
if not file_path.is_file():
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
# Skip binary files by extension
|
|
81
|
+
if file_path.suffix.lower() in _BINARY_EXTENSIONS:
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
# Get relative path for exclusion matching
|
|
85
|
+
rel_path = str(file_path.relative_to(root))
|
|
86
|
+
|
|
87
|
+
if _should_exclude(rel_path, excludes):
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
content = file_path.read_text(encoding="utf-8", errors="strict")
|
|
92
|
+
except (UnicodeDecodeError, OSError):
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
for line_num, line_text in enumerate(content.split("\n"), start=1):
|
|
96
|
+
match = MARKER_PATTERN.search(line_text)
|
|
97
|
+
if match:
|
|
98
|
+
results.append({
|
|
99
|
+
"path": rel_path,
|
|
100
|
+
"line": line_num,
|
|
101
|
+
"marker_type": match.group(1),
|
|
102
|
+
"text": line_text.strip(),
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
return results
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _parse_blame_porcelain(output: str, line: int) -> dict:
|
|
109
|
+
"""Parse git blame --porcelain output for a specific line.
|
|
110
|
+
|
|
111
|
+
Extracts author name and author-time (Unix timestamp).
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
output: Raw porcelain output from git blame
|
|
115
|
+
line: Line number to extract (used for context)
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Dict with 'author' and 'author_time' keys, or empty dict
|
|
119
|
+
"""
|
|
120
|
+
if not output.strip():
|
|
121
|
+
return {}
|
|
122
|
+
|
|
123
|
+
author = ""
|
|
124
|
+
author_time = 0
|
|
125
|
+
|
|
126
|
+
for raw_line in output.split("\n"):
|
|
127
|
+
if raw_line.startswith("author "):
|
|
128
|
+
author = raw_line[7:]
|
|
129
|
+
elif raw_line.startswith("author-time "):
|
|
130
|
+
try:
|
|
131
|
+
author_time = int(raw_line[12:])
|
|
132
|
+
except ValueError:
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
if not author:
|
|
136
|
+
return {}
|
|
137
|
+
|
|
138
|
+
return {"author": author, "author_time": author_time}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
async def _run_git_command(args: list[str], cwd: Path) -> tuple[str, str, int]:
|
|
142
|
+
"""Run a git command asynchronously.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
args: Git command arguments (without 'git')
|
|
146
|
+
cwd: Working directory
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
(stdout, stderr, return_code)
|
|
150
|
+
"""
|
|
151
|
+
proc = await asyncio.create_subprocess_exec(
|
|
152
|
+
"git",
|
|
153
|
+
*args,
|
|
154
|
+
cwd=cwd,
|
|
155
|
+
stdout=asyncio.subprocess.PIPE,
|
|
156
|
+
stderr=asyncio.subprocess.PIPE,
|
|
157
|
+
)
|
|
158
|
+
stdout, stderr = await proc.communicate()
|
|
159
|
+
return (
|
|
160
|
+
stdout.decode("utf-8", errors="replace").strip(),
|
|
161
|
+
stderr.decode("utf-8", errors="replace").strip(),
|
|
162
|
+
proc.returncode or 0,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
async def _batch_blame_file(
|
|
167
|
+
repo_path: Path, file_path: str, lines: list[int]
|
|
168
|
+
) -> dict[int, dict]:
|
|
169
|
+
"""Blame an entire file once and extract data for requested lines.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
repo_path: Root of the git repository
|
|
173
|
+
file_path: Relative path to file within repo
|
|
174
|
+
lines: Line numbers to extract blame data for
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Dict mapping line number -> {author, author_time}
|
|
178
|
+
"""
|
|
179
|
+
stdout, stderr, rc = await _run_git_command(
|
|
180
|
+
["blame", "--porcelain", file_path], repo_path
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
if rc != 0:
|
|
184
|
+
return {}
|
|
185
|
+
|
|
186
|
+
# Parse porcelain: each block starts with "<hash> <orig_line> <final_line> <count>"
|
|
187
|
+
results: dict[int, dict] = {}
|
|
188
|
+
current_line = 0
|
|
189
|
+
current_author = ""
|
|
190
|
+
current_time = 0
|
|
191
|
+
lines_set = set(lines)
|
|
192
|
+
|
|
193
|
+
for raw_line in stdout.split("\n"):
|
|
194
|
+
# Header line: hash orig_line final_line [count]
|
|
195
|
+
# Detect by checking that parts[1] is a digit (orig_line number)
|
|
196
|
+
parts = raw_line.split(" ")
|
|
197
|
+
if len(parts) >= 3 and len(parts[0]) >= 6 and parts[1].isdigit():
|
|
198
|
+
try:
|
|
199
|
+
current_line = int(parts[2])
|
|
200
|
+
except (ValueError, IndexError):
|
|
201
|
+
pass
|
|
202
|
+
current_author = ""
|
|
203
|
+
current_time = 0
|
|
204
|
+
elif raw_line.startswith("author "):
|
|
205
|
+
current_author = raw_line[7:]
|
|
206
|
+
elif raw_line.startswith("author-time "):
|
|
207
|
+
try:
|
|
208
|
+
current_time = int(raw_line[12:])
|
|
209
|
+
except ValueError:
|
|
210
|
+
pass
|
|
211
|
+
elif raw_line.startswith("\t"):
|
|
212
|
+
# Content line — end of this block
|
|
213
|
+
if current_line in lines_set:
|
|
214
|
+
results[current_line] = {
|
|
215
|
+
"author": current_author,
|
|
216
|
+
"author_time": current_time,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return results
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
async def analyze_repo(
|
|
223
|
+
name: str,
|
|
224
|
+
path: Path,
|
|
225
|
+
days: int = 90,
|
|
226
|
+
excludes: list[str] | None = None,
|
|
227
|
+
) -> CodeMarkersResult:
|
|
228
|
+
"""Analyze a repository for code markers.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
name: Display name for the repository
|
|
232
|
+
path: Path to the git repository
|
|
233
|
+
days: Stale threshold in days
|
|
234
|
+
excludes: Additional file patterns to exclude
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
CodeMarkersResult with markers and summary
|
|
238
|
+
"""
|
|
239
|
+
resolved = Path(path).resolve()
|
|
240
|
+
|
|
241
|
+
if not resolved.exists():
|
|
242
|
+
return CodeMarkersResult(
|
|
243
|
+
success=False,
|
|
244
|
+
repo_name=name,
|
|
245
|
+
repo_path=str(resolved),
|
|
246
|
+
stale_threshold_days=days,
|
|
247
|
+
error=f"Path not found: {resolved}",
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
all_excludes = DEFAULT_EXCLUDES + (excludes or [])
|
|
251
|
+
|
|
252
|
+
# Grep for markers
|
|
253
|
+
raw_markers = _grep_markers(resolved, all_excludes)
|
|
254
|
+
|
|
255
|
+
if not raw_markers:
|
|
256
|
+
return CodeMarkersResult(
|
|
257
|
+
success=True,
|
|
258
|
+
repo_name=name,
|
|
259
|
+
repo_path=str(resolved),
|
|
260
|
+
stale_threshold_days=days,
|
|
261
|
+
markers=[],
|
|
262
|
+
summary=MarkerSummary(),
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# Group markers by file for batch blame
|
|
266
|
+
by_file: dict[str, list[dict]] = defaultdict(list)
|
|
267
|
+
for m in raw_markers:
|
|
268
|
+
by_file[m["path"]].append(m)
|
|
269
|
+
|
|
270
|
+
# Blame each file once
|
|
271
|
+
now = time.time()
|
|
272
|
+
markers: list[CodeMarker] = []
|
|
273
|
+
stale_count = 0
|
|
274
|
+
type_counts: dict[str, int] = defaultdict(int)
|
|
275
|
+
|
|
276
|
+
for file_path, file_markers in by_file.items():
|
|
277
|
+
line_numbers = [m["line"] for m in file_markers]
|
|
278
|
+
blame_data = await _batch_blame_file(resolved, file_path, line_numbers)
|
|
279
|
+
|
|
280
|
+
for m in file_markers:
|
|
281
|
+
blame = blame_data.get(m["line"], {})
|
|
282
|
+
author = blame.get("author", "")
|
|
283
|
+
author_time = blame.get("author_time", 0)
|
|
284
|
+
|
|
285
|
+
# Compute age
|
|
286
|
+
if author_time > 0:
|
|
287
|
+
age_days = (now - author_time) / 86400
|
|
288
|
+
date_str = datetime.fromtimestamp(
|
|
289
|
+
author_time, tz=timezone.utc
|
|
290
|
+
).isoformat()
|
|
291
|
+
else:
|
|
292
|
+
age_days = 0.0
|
|
293
|
+
date_str = ""
|
|
294
|
+
|
|
295
|
+
is_stale = age_days > days
|
|
296
|
+
|
|
297
|
+
marker = CodeMarker(
|
|
298
|
+
path=m["path"],
|
|
299
|
+
line=m["line"],
|
|
300
|
+
marker_type=m["marker_type"],
|
|
301
|
+
text=m["text"],
|
|
302
|
+
author=author,
|
|
303
|
+
date=date_str,
|
|
304
|
+
age_days=round(age_days, 1),
|
|
305
|
+
is_stale=is_stale,
|
|
306
|
+
)
|
|
307
|
+
markers.append(marker)
|
|
308
|
+
|
|
309
|
+
type_counts[m["marker_type"]] += 1
|
|
310
|
+
if is_stale:
|
|
311
|
+
stale_count += 1
|
|
312
|
+
|
|
313
|
+
summary = MarkerSummary(
|
|
314
|
+
total_markers=len(markers),
|
|
315
|
+
stale_markers=stale_count,
|
|
316
|
+
by_type=dict(type_counts),
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
return CodeMarkersResult(
|
|
320
|
+
success=True,
|
|
321
|
+
repo_name=name,
|
|
322
|
+
repo_path=str(resolved),
|
|
323
|
+
stale_threshold_days=days,
|
|
324
|
+
markers=markers,
|
|
325
|
+
summary=summary,
|
|
326
|
+
)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI commands for code marker analysis.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
pf codemarkers analyze [OPTIONS]
|
|
6
|
+
pf codemarkers stale [OPTIONS]
|
|
7
|
+
pf codemarkers summary [OPTIONS]
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.group()
|
|
19
|
+
def codemarkers():
|
|
20
|
+
"""Code marker detection (TODO, FIXME, HACK, XXX).
|
|
21
|
+
|
|
22
|
+
\b
|
|
23
|
+
Commands:
|
|
24
|
+
analyze - Full marker analysis with blame data
|
|
25
|
+
stale - Show only stale markers (older than threshold)
|
|
26
|
+
summary - Summary counts by marker type
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _common_options(fn):
|
|
32
|
+
"""Shared options for all codemarkers commands."""
|
|
33
|
+
fn = click.option("--repo", help="Analyze a single named repo from repos.yaml")(fn)
|
|
34
|
+
fn = click.option("--path", "repo_path", type=click.Path(), help="Analyze a standalone repo path")(fn)
|
|
35
|
+
fn = click.option("--days", default=90, show_default=True, help="Stale threshold in days")(fn)
|
|
36
|
+
fn = click.option("--top", default=20, show_default=True, help="Number of top results to show")(fn)
|
|
37
|
+
fn = click.option("--format", "fmt", type=click.Choice(["table", "json", "csv"]), default="table", show_default=True)(fn)
|
|
38
|
+
fn = click.option("--output", "output_file", type=click.Path(), help="Write output to file")(fn)
|
|
39
|
+
fn = click.option("--exclude", multiple=True, help="Additional exclude patterns (repeatable)")(fn)
|
|
40
|
+
return fn
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _run_analysis(repo: str | None, repo_path: str | None, days: int, exclude: tuple):
|
|
44
|
+
"""Run analysis and return result."""
|
|
45
|
+
from pennyfarthing_scripts.codemarkers.analyze import analyze_repo
|
|
46
|
+
from pennyfarthing_scripts.common.config import get_project_root
|
|
47
|
+
|
|
48
|
+
excludes = list(exclude) if exclude else None
|
|
49
|
+
|
|
50
|
+
if repo_path:
|
|
51
|
+
p = Path(repo_path).resolve()
|
|
52
|
+
return asyncio.run(analyze_repo(p.name, p, days, excludes))
|
|
53
|
+
elif repo:
|
|
54
|
+
project_root = get_project_root()
|
|
55
|
+
from pennyfarthing_scripts.common.config import load_yaml_config
|
|
56
|
+
repos_yaml = load_yaml_config(project_root / ".pennyfarthing" / "repos.yaml")
|
|
57
|
+
if repos_yaml and repo in repos_yaml:
|
|
58
|
+
cfg = repos_yaml[repo]
|
|
59
|
+
rpath = cfg.get("path", repo) if isinstance(cfg, dict) else str(cfg)
|
|
60
|
+
return asyncio.run(
|
|
61
|
+
analyze_repo(repo, project_root / rpath, days, excludes)
|
|
62
|
+
)
|
|
63
|
+
else:
|
|
64
|
+
candidate = project_root / repo
|
|
65
|
+
if candidate.exists():
|
|
66
|
+
return asyncio.run(analyze_repo(repo, candidate, days, excludes))
|
|
67
|
+
raise click.ClickException(f"Repo not found: {repo}")
|
|
68
|
+
else:
|
|
69
|
+
project_root = get_project_root()
|
|
70
|
+
return asyncio.run(
|
|
71
|
+
analyze_repo(project_root.name, project_root, days, excludes)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _output_result(result, fmt: str, output_file: str | None, top: int, mode: str):
|
|
76
|
+
"""Format and output the analysis result."""
|
|
77
|
+
from pennyfarthing_scripts.codemarkers.formatters import (
|
|
78
|
+
export_csv,
|
|
79
|
+
export_json,
|
|
80
|
+
format_marker_table,
|
|
81
|
+
format_summary,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if fmt == "json":
|
|
85
|
+
text = export_json(result)
|
|
86
|
+
elif fmt == "csv":
|
|
87
|
+
text = export_csv(result.markers[:top])
|
|
88
|
+
else:
|
|
89
|
+
format_summary(result)
|
|
90
|
+
if mode == "stale":
|
|
91
|
+
stale = [m for m in result.markers if m.is_stale]
|
|
92
|
+
text = format_marker_table(stale, top)
|
|
93
|
+
else:
|
|
94
|
+
text = format_marker_table(result.markers, top)
|
|
95
|
+
|
|
96
|
+
if output_file:
|
|
97
|
+
Path(output_file).write_text(text)
|
|
98
|
+
click.echo(f"Output written to {output_file}", err=True)
|
|
99
|
+
else:
|
|
100
|
+
click.echo(text)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@codemarkers.command()
|
|
104
|
+
@_common_options
|
|
105
|
+
def analyze(repo, repo_path, days, top, fmt, output_file, exclude):
|
|
106
|
+
"""Full marker analysis with blame data."""
|
|
107
|
+
result = _run_analysis(repo, repo_path, days, exclude)
|
|
108
|
+
_output_result(result, fmt, output_file, top, "analyze")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@codemarkers.command()
|
|
112
|
+
@_common_options
|
|
113
|
+
def stale(repo, repo_path, days, top, fmt, output_file, exclude):
|
|
114
|
+
"""Show only stale markers (older than threshold)."""
|
|
115
|
+
result = _run_analysis(repo, repo_path, days, exclude)
|
|
116
|
+
_output_result(result, fmt, output_file, top, "stale")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@codemarkers.command()
|
|
120
|
+
@_common_options
|
|
121
|
+
def summary(repo, repo_path, days, top, fmt, output_file, exclude):
|
|
122
|
+
"""Summary counts by marker type."""
|
|
123
|
+
result = _run_analysis(repo, repo_path, days, exclude)
|
|
124
|
+
if fmt == "json":
|
|
125
|
+
from pennyfarthing_scripts.codemarkers.formatters import export_json
|
|
126
|
+
click.echo(export_json(result))
|
|
127
|
+
else:
|
|
128
|
+
from pennyfarthing_scripts.codemarkers.formatters import format_summary
|
|
129
|
+
format_summary(result, file=click.get_text_stream("stdout"))
|