@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
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Sprint epic archiving.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
Archives completed epics by moving their shard files to sprint/archive/.
|
|
5
|
+
The sprint completed file references archived epics by ID (not inlined).
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import shutil
|
|
8
9
|
from datetime import date
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Any
|
|
11
12
|
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
from pennyfarthing_scripts.common.config import get_project_root, load_yaml_config
|
|
13
|
+
from pennyfarthing_scripts.common.config import get_project_root
|
|
15
14
|
from pennyfarthing_scripts.sprint.loader import load_sprint
|
|
15
|
+
from pennyfarthing_scripts.sprint.yaml_io import (
|
|
16
|
+
_get_epic_ref,
|
|
17
|
+
_make_yaml,
|
|
18
|
+
_read_yaml_file,
|
|
19
|
+
_write_yaml_file,
|
|
20
|
+
read_sprint,
|
|
21
|
+
write_sprint,
|
|
22
|
+
)
|
|
16
23
|
|
|
17
24
|
|
|
18
25
|
def get_archive_path(project_root: Path | None = None) -> Path:
|
|
19
26
|
"""Get the archive file path for the current sprint.
|
|
20
27
|
|
|
21
|
-
Creates the archive file with a template if it doesn't exist.
|
|
22
|
-
|
|
23
28
|
Args:
|
|
24
29
|
project_root: Project root path (defaults to auto-detect)
|
|
25
30
|
|
|
@@ -34,9 +39,7 @@ def get_archive_path(project_root: Path | None = None) -> Path:
|
|
|
34
39
|
|
|
35
40
|
sprint_info = sprint_data["sprint"]
|
|
36
41
|
|
|
37
|
-
# Extract sprint identifier (YYWW format from jira_sprint_name)
|
|
38
42
|
sprint_name = sprint_info.get("jira_sprint_name", "")
|
|
39
|
-
# Extract "2604" from "TO Sprint 2604"
|
|
40
43
|
sprint_id = sprint_name.split()[-1] if sprint_name else str(sprint_info.get("number", "unknown"))
|
|
41
44
|
|
|
42
45
|
archive_path = root / "sprint" / "archive" / f"sprint-{sprint_id}-completed.yaml"
|
|
@@ -58,7 +61,6 @@ def ensure_archive_file(project_root: Path | None = None) -> Path:
|
|
|
58
61
|
if archive_path.exists():
|
|
59
62
|
return archive_path
|
|
60
63
|
|
|
61
|
-
# Create archive file with template
|
|
62
64
|
sprint_data = load_sprint(root)
|
|
63
65
|
sprint_info = sprint_data.get("sprint", {})
|
|
64
66
|
|
|
@@ -66,7 +68,7 @@ def ensure_archive_file(project_root: Path | None = None) -> Path:
|
|
|
66
68
|
sprint_id = sprint_info.get("jira_sprint_id", "")
|
|
67
69
|
goal = sprint_info.get("goal", "")
|
|
68
70
|
|
|
69
|
-
template = f"""# Sprint {sprint_name} - Completed
|
|
71
|
+
template = f"""# Sprint {sprint_name} - Completed Work
|
|
70
72
|
# Jira Sprint ID: {sprint_id}
|
|
71
73
|
# Archived: {date.today().isoformat()}
|
|
72
74
|
|
|
@@ -76,7 +78,11 @@ sprint:
|
|
|
76
78
|
jira_sprint_name: "{sprint_name}"
|
|
77
79
|
goal: {goal}
|
|
78
80
|
|
|
79
|
-
|
|
81
|
+
completed_epics:
|
|
82
|
+
# Epic shard files live in sprint/archive/epic-{{ref}}.yaml
|
|
83
|
+
|
|
84
|
+
completed_stories:
|
|
85
|
+
# Orphan stories not belonging to an epic
|
|
80
86
|
"""
|
|
81
87
|
|
|
82
88
|
archive_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -85,25 +91,92 @@ completed:
|
|
|
85
91
|
return archive_path
|
|
86
92
|
|
|
87
93
|
|
|
94
|
+
def _load_archive_file(archive_path: Path) -> dict[str, Any]:
|
|
95
|
+
"""Load the sprint archive file, handling both old and new formats.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
archive_path: Path to the archive YAML file
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Archive data dict with completed_epics and completed_stories
|
|
102
|
+
"""
|
|
103
|
+
yml = _make_yaml()
|
|
104
|
+
with open(archive_path) as f:
|
|
105
|
+
data = yml.load(f)
|
|
106
|
+
|
|
107
|
+
if data is None:
|
|
108
|
+
data = {}
|
|
109
|
+
|
|
110
|
+
# Ensure new-format keys exist
|
|
111
|
+
if "completed_epics" not in data:
|
|
112
|
+
data["completed_epics"] = []
|
|
113
|
+
if "completed_stories" not in data:
|
|
114
|
+
data["completed_stories"] = []
|
|
115
|
+
|
|
116
|
+
# Normalize: ensure lists are not None
|
|
117
|
+
if data["completed_epics"] is None:
|
|
118
|
+
data["completed_epics"] = []
|
|
119
|
+
if data["completed_stories"] is None:
|
|
120
|
+
data["completed_stories"] = []
|
|
121
|
+
|
|
122
|
+
return data
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _write_archive_file(archive_path: Path, data: dict[str, Any]) -> None:
|
|
126
|
+
"""Write the sprint archive file.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
archive_path: Path to the archive YAML file
|
|
130
|
+
data: Archive data dict
|
|
131
|
+
"""
|
|
132
|
+
import io
|
|
133
|
+
|
|
134
|
+
from ruamel.yaml.comments import CommentedMap, CommentedSeq
|
|
135
|
+
|
|
136
|
+
yml = _make_yaml()
|
|
137
|
+
|
|
138
|
+
# Build output preserving comments at the top
|
|
139
|
+
cm = CommentedMap()
|
|
140
|
+
if "sprint" in data:
|
|
141
|
+
cm["sprint"] = data["sprint"]
|
|
142
|
+
|
|
143
|
+
# completed_epics as string refs
|
|
144
|
+
epic_refs = CommentedSeq()
|
|
145
|
+
for ref in data.get("completed_epics", []):
|
|
146
|
+
epic_refs.append(ref)
|
|
147
|
+
cm["completed_epics"] = epic_refs
|
|
148
|
+
|
|
149
|
+
# completed_stories for orphans
|
|
150
|
+
stories = CommentedSeq()
|
|
151
|
+
for story in data.get("completed_stories", []):
|
|
152
|
+
stories.append(story)
|
|
153
|
+
cm["completed_stories"] = stories
|
|
154
|
+
|
|
155
|
+
stream = io.StringIO()
|
|
156
|
+
yml.dump(cm, stream)
|
|
157
|
+
output = stream.getvalue()
|
|
158
|
+
|
|
159
|
+
# Clean trailing whitespace
|
|
160
|
+
lines = output.split("\n")
|
|
161
|
+
cleaned = [line.rstrip() for line in lines]
|
|
162
|
+
result = "\n".join(cleaned).rstrip("\n") + "\n"
|
|
163
|
+
|
|
164
|
+
archive_path.write_text(result)
|
|
165
|
+
|
|
166
|
+
|
|
88
167
|
def is_epic_complete(epic: dict[str, Any]) -> tuple[bool, list[str]]:
|
|
89
168
|
"""Check if an epic is complete by examining story statuses.
|
|
90
169
|
|
|
91
|
-
An epic is complete if:
|
|
92
|
-
- It has status 'done' or 'completed', OR
|
|
93
|
-
- All of its stories have a terminal status ('done', 'completed', or 'cancelled')
|
|
94
|
-
|
|
95
170
|
Args:
|
|
96
171
|
epic: Epic dict from sprint YAML
|
|
97
172
|
|
|
98
173
|
Returns:
|
|
99
174
|
Tuple of (is_complete, list of incomplete story IDs)
|
|
100
175
|
"""
|
|
101
|
-
# Check if epic itself is marked done
|
|
102
176
|
epic_status = epic.get("status", "backlog")
|
|
103
177
|
if epic_status in ("done", "completed"):
|
|
104
178
|
return True, []
|
|
105
179
|
|
|
106
|
-
# Check all stories
|
|
107
180
|
stories = epic.get("stories", [])
|
|
108
181
|
if not stories:
|
|
109
182
|
return False, []
|
|
@@ -153,6 +226,10 @@ def archive_epic(
|
|
|
153
226
|
) -> dict[str, Any]:
|
|
154
227
|
"""Archive a completed epic.
|
|
155
228
|
|
|
229
|
+
Moves the epic shard file to sprint/archive/, adds the epic ref to the
|
|
230
|
+
sprint completed file, and removes it from the current sprint index.
|
|
231
|
+
Context files are also moved to archive.
|
|
232
|
+
|
|
156
233
|
Args:
|
|
157
234
|
epic_id: Epic ID to archive (e.g., "epic-64" or "MSSCI-12465")
|
|
158
235
|
project_root: Project root path (defaults to auto-detect)
|
|
@@ -163,16 +240,23 @@ def archive_epic(
|
|
|
163
240
|
Dict with success status and details
|
|
164
241
|
"""
|
|
165
242
|
root = project_root or get_project_root()
|
|
166
|
-
|
|
243
|
+
sprint_dir = root / "sprint"
|
|
244
|
+
archive_dir = sprint_dir / "archive"
|
|
245
|
+
archive_dir.mkdir(parents=True, exist_ok=True)
|
|
246
|
+
|
|
247
|
+
sprint_path = sprint_dir / "current-sprint.yaml"
|
|
248
|
+
sprint_data = read_sprint(sprint_path)
|
|
167
249
|
|
|
168
250
|
if not sprint_data or "epics" not in sprint_data:
|
|
169
251
|
return {"success": False, "error": "Could not load sprint data"}
|
|
170
252
|
|
|
171
|
-
# Find the epic
|
|
253
|
+
# Find the epic in merged data
|
|
172
254
|
epic = None
|
|
173
255
|
epic_index = None
|
|
174
256
|
for i, e in enumerate(sprint_data["epics"]):
|
|
175
|
-
|
|
257
|
+
eid = str(e.get("id", ""))
|
|
258
|
+
ejira = str(e.get("jira", ""))
|
|
259
|
+
if eid == epic_id or ejira == epic_id:
|
|
176
260
|
epic = e
|
|
177
261
|
epic_index = i
|
|
178
262
|
break
|
|
@@ -189,73 +273,104 @@ def archive_epic(
|
|
|
189
273
|
"incomplete_stories": incomplete,
|
|
190
274
|
}
|
|
191
275
|
|
|
276
|
+
# Determine the shard ref (filename stem)
|
|
277
|
+
epic_ref = _get_epic_ref(epic)
|
|
278
|
+
shard_file = sprint_dir / f"epic-{epic_ref}.yaml"
|
|
279
|
+
archive_shard = archive_dir / f"epic-{epic_ref}.yaml"
|
|
280
|
+
story_count = len(epic.get("stories", []))
|
|
281
|
+
total_points = sum(s.get("points", 0) for s in epic.get("stories", []))
|
|
282
|
+
|
|
192
283
|
if dry_run:
|
|
284
|
+
msg_parts = [f"Would archive {epic_id} ({story_count} stories, {total_points} pts)"]
|
|
285
|
+
if shard_file.exists():
|
|
286
|
+
msg_parts.append(f" Move: {shard_file.name} → archive/")
|
|
287
|
+
# Check for context file
|
|
288
|
+
for ctx_name in [f"context-epic-{epic_ref}.md", f"context-epic-{epic.get('id', '')}.md"]:
|
|
289
|
+
ctx_file = sprint_dir / "context" / ctx_name
|
|
290
|
+
if ctx_file.exists():
|
|
291
|
+
msg_parts.append(f" Move: context/{ctx_name} → archive/")
|
|
292
|
+
break
|
|
193
293
|
return {
|
|
194
294
|
"success": True,
|
|
195
295
|
"dry_run": True,
|
|
196
296
|
"epic": epic,
|
|
197
|
-
"
|
|
297
|
+
"stories_archived": story_count,
|
|
298
|
+
"total_points": total_points,
|
|
299
|
+
"message": "\n".join(msg_parts),
|
|
198
300
|
}
|
|
199
301
|
|
|
200
|
-
#
|
|
302
|
+
# 1. Update epic status in the shard before moving
|
|
303
|
+
if shard_file.exists():
|
|
304
|
+
shard_data = _read_yaml_file(shard_file)
|
|
305
|
+
shard_data["status"] = "done"
|
|
306
|
+
if "completed" not in shard_data:
|
|
307
|
+
shard_data["completed"] = date.today().isoformat()
|
|
308
|
+
_write_yaml_file(shard_file, shard_data)
|
|
309
|
+
# Move shard to archive
|
|
310
|
+
shutil.move(str(shard_file), str(archive_shard))
|
|
311
|
+
else:
|
|
312
|
+
# No shard file on disk — write epic data directly to archive
|
|
313
|
+
epic["status"] = "done"
|
|
314
|
+
if "completed" not in epic:
|
|
315
|
+
epic["completed"] = date.today().isoformat()
|
|
316
|
+
_write_yaml_file(archive_shard, epic)
|
|
317
|
+
|
|
318
|
+
# 2. Move context file if it exists
|
|
319
|
+
context_moved = None
|
|
320
|
+
for ctx_name in [f"context-epic-{epic_ref}.md", f"context-epic-{epic.get('id', '')}.md"]:
|
|
321
|
+
ctx_file = sprint_dir / "context" / ctx_name
|
|
322
|
+
if ctx_file.exists():
|
|
323
|
+
shutil.move(str(ctx_file), str(archive_dir / ctx_name))
|
|
324
|
+
context_moved = ctx_name
|
|
325
|
+
break
|
|
326
|
+
|
|
327
|
+
# 3. Add epic ref to sprint completed file
|
|
201
328
|
archive_path = ensure_archive_file(root)
|
|
329
|
+
archive_data = _load_archive_file(archive_path)
|
|
330
|
+
|
|
331
|
+
# Add ref if not already present
|
|
332
|
+
if epic_ref not in archive_data["completed_epics"]:
|
|
333
|
+
archive_data["completed_epics"].append(epic_ref)
|
|
334
|
+
_write_archive_file(archive_path, archive_data)
|
|
335
|
+
|
|
336
|
+
# 4. Remove epic from current-sprint.yaml index
|
|
337
|
+
# Re-read the raw index (not merged) to update refs
|
|
338
|
+
yml = _make_yaml()
|
|
339
|
+
with open(sprint_path) as f:
|
|
340
|
+
index_data = yml.load(f)
|
|
341
|
+
|
|
342
|
+
epics_list = index_data.get("epics", [])
|
|
343
|
+
# Remove the matching ref (string) or dict
|
|
344
|
+
new_epics = []
|
|
345
|
+
for item in epics_list:
|
|
346
|
+
if isinstance(item, str):
|
|
347
|
+
if item != epic_ref:
|
|
348
|
+
new_epics.append(item)
|
|
349
|
+
else:
|
|
350
|
+
item_id = str(item.get("id", ""))
|
|
351
|
+
item_jira = str(item.get("jira", ""))
|
|
352
|
+
if item_id != epic_id and item_jira != epic_id:
|
|
353
|
+
new_epics.append(item)
|
|
202
354
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
for story in epic.get("stories", []):
|
|
207
|
-
entry = {
|
|
208
|
-
"id": story.get("id") or story.get("jira"),
|
|
209
|
-
"epic": epic_jira,
|
|
210
|
-
"title": story.get("title", ""),
|
|
211
|
-
"points": story.get("points", 0),
|
|
212
|
-
"completed": story.get("completed", date.today().isoformat()),
|
|
213
|
-
}
|
|
214
|
-
if story.get("pr"):
|
|
215
|
-
entry["pr"] = story["pr"]
|
|
216
|
-
archive_entries.append(entry)
|
|
217
|
-
|
|
218
|
-
# Append to archive file
|
|
219
|
-
with open(archive_path, "a") as f:
|
|
220
|
-
# Add epic header comment
|
|
221
|
-
epic_title = epic.get("title", epic_id)
|
|
222
|
-
f.write(f"\n # {epic_id}: {epic_title} - COMPLETE\n")
|
|
223
|
-
|
|
224
|
-
for entry in archive_entries:
|
|
225
|
-
f.write(f" - id: {entry['id']}\n")
|
|
226
|
-
f.write(f" epic: {entry['epic']}\n")
|
|
227
|
-
# Escape quotes in title
|
|
228
|
-
title = entry['title'].replace('"', '\\"')
|
|
229
|
-
f.write(f' title: "{title}"\n')
|
|
230
|
-
f.write(f" points: {entry['points']}\n")
|
|
231
|
-
f.write(f" completed: {entry['completed']}\n")
|
|
232
|
-
if entry.get("pr"):
|
|
233
|
-
f.write(f" pr: {entry['pr']}\n")
|
|
234
|
-
|
|
235
|
-
# Remove epic from current-sprint.yaml
|
|
236
|
-
sprint_path = root / "sprint" / "current-sprint.yaml"
|
|
237
|
-
del sprint_data["epics"][epic_index]
|
|
238
|
-
|
|
239
|
-
# Note: We do NOT update completed_points here because stories were already
|
|
240
|
-
# marked done - their points are already counted. Archiving just moves them
|
|
241
|
-
# to the archive file without changing the accounting.
|
|
242
|
-
|
|
243
|
-
# Write updated sprint file
|
|
244
|
-
with open(sprint_path, "w") as f:
|
|
245
|
-
yaml.dump(sprint_data, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
|
|
355
|
+
from ruamel.yaml.comments import CommentedSeq
|
|
356
|
+
index_data["epics"] = CommentedSeq(new_epics)
|
|
357
|
+
_write_yaml_file(sprint_path, index_data)
|
|
246
358
|
|
|
247
359
|
result = {
|
|
248
360
|
"success": True,
|
|
249
361
|
"epic_id": epic_id,
|
|
250
|
-
"
|
|
251
|
-
"stories_archived":
|
|
252
|
-
"
|
|
253
|
-
"
|
|
362
|
+
"epic_ref": epic_ref,
|
|
363
|
+
"stories_archived": story_count,
|
|
364
|
+
"total_points": total_points,
|
|
365
|
+
"archive_shard": str(archive_shard),
|
|
366
|
+
"context_moved": context_moved,
|
|
367
|
+
"message": f"Archived {epic_id} ({story_count} stories, {total_points} pts) → archive/epic-{epic_ref}.yaml",
|
|
254
368
|
}
|
|
255
369
|
|
|
256
370
|
# Update Jira if requested
|
|
371
|
+
epic_jira = epic.get("jira", "")
|
|
257
372
|
if update_jira and epic_jira:
|
|
258
|
-
result["jira_updated"] = _update_jira_epic(epic_jira)
|
|
373
|
+
result["jira_updated"] = _update_jira_epic(str(epic_jira))
|
|
259
374
|
|
|
260
375
|
return result
|
|
261
376
|
|
|
@@ -312,7 +427,7 @@ def archive_all_completed(
|
|
|
312
427
|
results = []
|
|
313
428
|
for item in completed:
|
|
314
429
|
epic = item["epic"]
|
|
315
|
-
epic_id = epic.get("id")
|
|
430
|
+
epic_id = str(epic.get("id", ""))
|
|
316
431
|
result = archive_epic(
|
|
317
432
|
epic_id,
|
|
318
433
|
project_root=root,
|
|
@@ -321,22 +436,18 @@ def archive_all_completed(
|
|
|
321
436
|
)
|
|
322
437
|
results.append(result)
|
|
323
438
|
|
|
439
|
+
total_stories = sum(r.get("stories_archived", 0) for r in results if r.get("success"))
|
|
440
|
+
total_points = sum(r.get("total_points", 0) for r in results if r.get("success"))
|
|
441
|
+
|
|
324
442
|
return {
|
|
325
443
|
"success": all(r.get("success") for r in results),
|
|
326
444
|
"archived": results,
|
|
327
|
-
"message": f"
|
|
445
|
+
"message": f"Archived {len(results)} epics ({total_stories} stories, {total_points} pts)",
|
|
328
446
|
}
|
|
329
447
|
|
|
330
448
|
|
|
331
449
|
def main(args: list[str] | None = None) -> int:
|
|
332
|
-
"""CLI entry point for epic archiving.
|
|
333
|
-
|
|
334
|
-
Args:
|
|
335
|
-
args: Command line arguments
|
|
336
|
-
|
|
337
|
-
Returns:
|
|
338
|
-
Exit code
|
|
339
|
-
"""
|
|
450
|
+
"""CLI entry point for epic archiving."""
|
|
340
451
|
import argparse
|
|
341
452
|
import sys
|
|
342
453
|
|
|
@@ -378,14 +489,16 @@ def main(args: list[str] | None = None) -> int:
|
|
|
378
489
|
print("[DRY-RUN]", result.get("message"))
|
|
379
490
|
if "archived" in result:
|
|
380
491
|
for r in result["archived"]:
|
|
381
|
-
|
|
382
|
-
stories = len(r.get("epic", {}).get("stories", [])) if "epic" in r else r.get("stories_archived", 0)
|
|
383
|
-
print(f" Would archive: {epic_id} ({stories} stories)")
|
|
492
|
+
print(f" {r.get('message')}")
|
|
384
493
|
else:
|
|
385
494
|
print(result.get("message"))
|
|
386
495
|
if "archived" in result:
|
|
387
496
|
for r in result["archived"]:
|
|
388
|
-
print(f"
|
|
497
|
+
print(f" \u2713 {r.get('message')}")
|
|
498
|
+
elif result.get("archive_shard"):
|
|
499
|
+
print(f" Shard: {result.get('archive_shard')}")
|
|
500
|
+
if result.get("context_moved"):
|
|
501
|
+
print(f" Context: {result.get('context_moved')}")
|
|
389
502
|
return 0
|
|
390
503
|
else:
|
|
391
504
|
print(f"Failed: {result.get('error')}", file=sys.stderr)
|