@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,173 @@
|
|
|
1
|
+
"""Sprint epic add command.
|
|
2
|
+
|
|
3
|
+
Adds a new epic to the current sprint, creating a shard file
|
|
4
|
+
and updating the index.
|
|
5
|
+
|
|
6
|
+
Provides:
|
|
7
|
+
- add_epic(sprint_path, epic_id, title, ...) -> dict
|
|
8
|
+
- epic_add_command (Click command for CLI registration)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
from ruamel.yaml.comments import CommentedMap, CommentedSeq
|
|
16
|
+
|
|
17
|
+
from pennyfarthing_scripts.sprint.yaml_io import (
|
|
18
|
+
EPIC_KEY_ORDER,
|
|
19
|
+
_read_yaml_file,
|
|
20
|
+
_write_yaml_file,
|
|
21
|
+
_get_epic_ref,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def add_epic(
|
|
26
|
+
sprint_path: Path,
|
|
27
|
+
epic_id: str,
|
|
28
|
+
title: str,
|
|
29
|
+
*,
|
|
30
|
+
priority: str = "P1",
|
|
31
|
+
status: str = "backlog",
|
|
32
|
+
repos: str = "pennyfarthing",
|
|
33
|
+
jira: str | None = None,
|
|
34
|
+
description: str | None = None,
|
|
35
|
+
) -> dict[str, Any]:
|
|
36
|
+
"""Add a new epic to the current sprint.
|
|
37
|
+
|
|
38
|
+
Creates a shard file for the epic and adds its reference to the index.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
sprint_path: Path to sprint index YAML file
|
|
42
|
+
epic_id: Epic ID (e.g., "epic-85" or "MSSCI-14400")
|
|
43
|
+
title: Epic title
|
|
44
|
+
priority: Priority (default: P1)
|
|
45
|
+
status: Initial status (default: backlog)
|
|
46
|
+
repos: Repository scope (default: pennyfarthing)
|
|
47
|
+
jira: Optional Jira epic key
|
|
48
|
+
description: Optional epic description
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Dict with success status and epic_id or error
|
|
52
|
+
"""
|
|
53
|
+
sprint_dir = sprint_path.parent
|
|
54
|
+
|
|
55
|
+
# Build epic as CommentedMap with canonical key ordering
|
|
56
|
+
epic = CommentedMap()
|
|
57
|
+
fields: dict[str, Any] = {
|
|
58
|
+
"id": epic_id,
|
|
59
|
+
"type": "epic",
|
|
60
|
+
"title": title,
|
|
61
|
+
"priority": priority,
|
|
62
|
+
"status": status,
|
|
63
|
+
"repos": repos,
|
|
64
|
+
"stories": CommentedSeq(),
|
|
65
|
+
}
|
|
66
|
+
if description is not None:
|
|
67
|
+
fields["description"] = description
|
|
68
|
+
if jira is not None:
|
|
69
|
+
fields["jira"] = jira
|
|
70
|
+
|
|
71
|
+
# Insert keys in EPIC_KEY_ORDER, then any extras
|
|
72
|
+
for key in EPIC_KEY_ORDER:
|
|
73
|
+
if key in fields:
|
|
74
|
+
epic[key] = fields[key]
|
|
75
|
+
for key in fields:
|
|
76
|
+
if key not in EPIC_KEY_ORDER:
|
|
77
|
+
epic[key] = fields[key]
|
|
78
|
+
|
|
79
|
+
# Determine the shard reference
|
|
80
|
+
ref = _get_epic_ref(epic)
|
|
81
|
+
shard_file = sprint_dir / f"epic-{ref}.yaml"
|
|
82
|
+
|
|
83
|
+
if shard_file.exists():
|
|
84
|
+
return {
|
|
85
|
+
"success": False,
|
|
86
|
+
"error": f"Epic shard file already exists: {shard_file.name}",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Read current index
|
|
90
|
+
data = _read_yaml_file(sprint_path)
|
|
91
|
+
epics = data.get("epics", [])
|
|
92
|
+
|
|
93
|
+
# Check for duplicate refs
|
|
94
|
+
for existing in epics:
|
|
95
|
+
existing_str = str(existing) if isinstance(existing, str) else str(existing.get("id", ""))
|
|
96
|
+
if existing_str == ref or existing_str == epic_id:
|
|
97
|
+
return {
|
|
98
|
+
"success": False,
|
|
99
|
+
"error": f"Epic '{epic_id}' already exists in the sprint",
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Detect format: sharded (string refs) or monolithic (full dicts)
|
|
103
|
+
is_sharded = bool(epics) and isinstance(epics[0], str)
|
|
104
|
+
|
|
105
|
+
if is_sharded or not epics:
|
|
106
|
+
# Sharded format: write shard file and add string ref to index
|
|
107
|
+
_write_yaml_file(shard_file, epic)
|
|
108
|
+
|
|
109
|
+
if not isinstance(epics, CommentedSeq):
|
|
110
|
+
epics = CommentedSeq(epics)
|
|
111
|
+
epics.append(ref)
|
|
112
|
+
data["epics"] = epics
|
|
113
|
+
_write_yaml_file(sprint_path, data)
|
|
114
|
+
else:
|
|
115
|
+
# Monolithic format: add full epic dict to index
|
|
116
|
+
if not isinstance(epics, CommentedSeq):
|
|
117
|
+
epics = CommentedSeq(epics)
|
|
118
|
+
epics.append(epic)
|
|
119
|
+
data["epics"] = epics
|
|
120
|
+
_write_yaml_file(sprint_path, data)
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
"success": True,
|
|
124
|
+
"epic_id": epic_id,
|
|
125
|
+
"ref": ref,
|
|
126
|
+
"shard_file": shard_file.name if is_sharded or not epics else None,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@click.command("epic-add")
|
|
131
|
+
@click.argument("epic_id", type=str)
|
|
132
|
+
@click.argument("title", type=str)
|
|
133
|
+
@click.option("--priority", type=click.Choice(["P0", "P1", "P2", "P3"]), default="P1")
|
|
134
|
+
@click.option("--status", type=click.Choice(["backlog", "ready", "in_progress"]), default="backlog")
|
|
135
|
+
@click.option("--repos", default="pennyfarthing")
|
|
136
|
+
@click.option("--jira", "jira_id", type=str, default=None, help="Jira epic key (MSSCI-NNNNN)")
|
|
137
|
+
@click.option("--description", "-d", type=str, default=None, help="Epic description")
|
|
138
|
+
@click.option("--sprint-file", type=click.Path(), default=None, help="Path to sprint YAML file")
|
|
139
|
+
def epic_add_command(
|
|
140
|
+
epic_id: str,
|
|
141
|
+
title: str,
|
|
142
|
+
priority: str,
|
|
143
|
+
status: str,
|
|
144
|
+
repos: str,
|
|
145
|
+
jira_id: str | None,
|
|
146
|
+
description: str | None,
|
|
147
|
+
sprint_file: str | None,
|
|
148
|
+
) -> None:
|
|
149
|
+
"""Add a new epic to the current sprint."""
|
|
150
|
+
if sprint_file is None:
|
|
151
|
+
from pennyfarthing_scripts.common.config import get_project_root
|
|
152
|
+
path = get_project_root() / "sprint" / "current-sprint.yaml"
|
|
153
|
+
else:
|
|
154
|
+
path = Path(sprint_file)
|
|
155
|
+
|
|
156
|
+
result = add_epic(
|
|
157
|
+
sprint_path=path,
|
|
158
|
+
epic_id=epic_id,
|
|
159
|
+
title=title,
|
|
160
|
+
priority=priority,
|
|
161
|
+
status=status,
|
|
162
|
+
repos=repos,
|
|
163
|
+
jira=jira_id,
|
|
164
|
+
description=description,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if result["success"]:
|
|
168
|
+
msg = f"Added epic {result['epic_id']}: {title}"
|
|
169
|
+
if result.get("shard_file"):
|
|
170
|
+
msg += f" ({result['shard_file']})"
|
|
171
|
+
click.echo(msg)
|
|
172
|
+
else:
|
|
173
|
+
raise click.ClickException(result["error"])
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Sprint YAML parsing utilities for Pennyfarthing scripts.
|
|
3
3
|
|
|
4
4
|
Provides access to sprint/current-sprint.yaml data.
|
|
5
|
+
Supports sharded per-epic format (epic-{ref}.yaml shard files).
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
from pathlib import Path
|
|
@@ -10,9 +11,47 @@ from typing import Any
|
|
|
10
11
|
from pennyfarthing_scripts.common.config import get_project_root, load_yaml_config
|
|
11
12
|
|
|
12
13
|
|
|
14
|
+
def _merge_epic_shards(data: dict[str, Any], sprint_dir: Path) -> dict[str, Any]:
|
|
15
|
+
"""Merge sharded epic files into the sprint data structure.
|
|
16
|
+
|
|
17
|
+
When the epics list contains strings (shard references like "MSSCI-14298"
|
|
18
|
+
or "epic-40"), load each epic-{ref}.yaml and replace the string with
|
|
19
|
+
the full epic dict.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
data: Sprint data with possible string refs in epics
|
|
23
|
+
sprint_dir: Directory containing the shard files
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Sprint data with full epic dicts
|
|
27
|
+
"""
|
|
28
|
+
epics = data.get("epics", [])
|
|
29
|
+
if not epics or not isinstance(epics[0], str):
|
|
30
|
+
return data
|
|
31
|
+
|
|
32
|
+
merged_epics = []
|
|
33
|
+
for ref in epics:
|
|
34
|
+
if not isinstance(ref, str):
|
|
35
|
+
merged_epics.append(ref)
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
epic_file = sprint_dir / f"epic-{ref}.yaml"
|
|
39
|
+
if epic_file.exists():
|
|
40
|
+
epic_data = load_yaml_config(epic_file)
|
|
41
|
+
if epic_data is not None:
|
|
42
|
+
merged_epics.append(epic_data)
|
|
43
|
+
|
|
44
|
+
data["epics"] = merged_epics
|
|
45
|
+
return data
|
|
46
|
+
|
|
47
|
+
|
|
13
48
|
def load_sprint(project_root: Path | None = None) -> dict[str, Any] | None:
|
|
14
49
|
"""Load sprint data from project root.
|
|
15
50
|
|
|
51
|
+
Supports both monolithic and sharded epic formats. When epics are
|
|
52
|
+
string references, the corresponding epic-{ref}.yaml files are
|
|
53
|
+
loaded and merged transparently.
|
|
54
|
+
|
|
16
55
|
Args:
|
|
17
56
|
project_root: Project root path (defaults to auto-detect)
|
|
18
57
|
|
|
@@ -20,8 +59,13 @@ def load_sprint(project_root: Path | None = None) -> dict[str, Any] | None:
|
|
|
20
59
|
Sprint data as dict, or None if not found
|
|
21
60
|
"""
|
|
22
61
|
root = project_root or get_project_root()
|
|
23
|
-
|
|
24
|
-
|
|
62
|
+
sprint_dir = root / "sprint"
|
|
63
|
+
sprint_path = sprint_dir / "current-sprint.yaml"
|
|
64
|
+
data = load_yaml_config(sprint_path)
|
|
65
|
+
if data is None:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
return _merge_epic_shards(data, sprint_dir)
|
|
25
69
|
|
|
26
70
|
|
|
27
71
|
def find_epic(sprint_data: dict[str, Any], epic_num: str) -> dict[str, Any] | None:
|
|
@@ -5,6 +5,7 @@ Story: MSSCI-14256 - Sprint story add command
|
|
|
5
5
|
This module provides:
|
|
6
6
|
- generate_story_id(sprint_data, epic) -> str
|
|
7
7
|
- add_story(sprint_path, epic_id, title, points, ...) -> dict
|
|
8
|
+
- add_initiative_story(initiative_slug, title, points, ...) -> dict
|
|
8
9
|
- story_add_command (Click command for CLI registration)
|
|
9
10
|
"""
|
|
10
11
|
|
|
@@ -144,44 +145,218 @@ def add_story(
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
|
|
148
|
+
def _generate_initiative_story_id(init_data: dict[str, Any], slug: str) -> str:
|
|
149
|
+
"""Generate the next standalone story ID for an initiative.
|
|
150
|
+
|
|
151
|
+
Uses the pattern {slug-prefix}-{N} where slug-prefix is derived from
|
|
152
|
+
the initiative slug (e.g., "technical-debt" -> "td", "quality-scale" -> "qs").
|
|
153
|
+
Only counts existing stories that share the same prefix.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
init_data: Initiative YAML data
|
|
157
|
+
slug: Initiative slug (e.g., "technical-debt")
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Next story ID string (e.g., "td-2")
|
|
161
|
+
"""
|
|
162
|
+
# Build prefix from initiative slug initials
|
|
163
|
+
parts = slug.split("-")
|
|
164
|
+
prefix = "".join(p[0] for p in parts if p)
|
|
165
|
+
|
|
166
|
+
stories = init_data.get("standalone_stories", [])
|
|
167
|
+
max_seq = 0
|
|
168
|
+
for story in stories:
|
|
169
|
+
story_id = str(story.get("id", ""))
|
|
170
|
+
# Only count stories with matching prefix
|
|
171
|
+
if not story_id.startswith(f"{prefix}-"):
|
|
172
|
+
continue
|
|
173
|
+
suffix = story_id[len(prefix) + 1:]
|
|
174
|
+
try:
|
|
175
|
+
seq = int(suffix)
|
|
176
|
+
if seq > max_seq:
|
|
177
|
+
max_seq = seq
|
|
178
|
+
except ValueError:
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
return f"{prefix}-{max_seq + 1}"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def add_initiative_story(
|
|
185
|
+
initiative_slug: str,
|
|
186
|
+
title: str,
|
|
187
|
+
points: int,
|
|
188
|
+
*,
|
|
189
|
+
story_type: str | None = None,
|
|
190
|
+
priority: str = "P1",
|
|
191
|
+
workflow: str = "tdd",
|
|
192
|
+
jira: str | None = None,
|
|
193
|
+
repos: str = "pennyfarthing",
|
|
194
|
+
) -> dict[str, Any]:
|
|
195
|
+
"""Add a standalone story to an initiative YAML file.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
initiative_slug: Initiative slug (e.g., "technical-debt")
|
|
199
|
+
title: Story title
|
|
200
|
+
points: Story points
|
|
201
|
+
story_type: Optional story type (feature, bug, chore, refactor)
|
|
202
|
+
priority: Priority (default: P1)
|
|
203
|
+
workflow: Workflow (default: tdd)
|
|
204
|
+
jira: Optional Jira key
|
|
205
|
+
repos: Repos (default: pennyfarthing)
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Dict with success status and story_id or error
|
|
209
|
+
"""
|
|
210
|
+
from pennyfarthing_scripts.common.config import get_project_root
|
|
211
|
+
|
|
212
|
+
root = get_project_root()
|
|
213
|
+
init_path = root / "sprint" / f"initiative-{initiative_slug}.yaml"
|
|
214
|
+
|
|
215
|
+
if not init_path.exists():
|
|
216
|
+
available = [
|
|
217
|
+
f.stem.replace("initiative-", "")
|
|
218
|
+
for f in (root / "sprint").glob("initiative-*.yaml")
|
|
219
|
+
]
|
|
220
|
+
return {
|
|
221
|
+
"success": False,
|
|
222
|
+
"error": f"Initiative '{initiative_slug}' not found. Available: {', '.join(sorted(available))}",
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# Use ruamel.yaml to preserve block scalars and formatting
|
|
226
|
+
from ruamel.yaml import YAML as RuamelYAML
|
|
227
|
+
|
|
228
|
+
ryml = RuamelYAML()
|
|
229
|
+
ryml.preserve_quotes = True
|
|
230
|
+
ryml.default_flow_style = False
|
|
231
|
+
ryml.indent(mapping=2, sequence=4, offset=2)
|
|
232
|
+
ryml.width = 4096
|
|
233
|
+
|
|
234
|
+
with open(init_path) as f:
|
|
235
|
+
init_data = ryml.load(f)
|
|
236
|
+
|
|
237
|
+
if not init_data:
|
|
238
|
+
return {"success": False, "error": f"Empty initiative file: {init_path}"}
|
|
239
|
+
|
|
240
|
+
story_id = _generate_initiative_story_id(init_data, initiative_slug)
|
|
241
|
+
|
|
242
|
+
story: dict[str, Any] = {
|
|
243
|
+
"id": story_id,
|
|
244
|
+
"title": title,
|
|
245
|
+
"points": points,
|
|
246
|
+
"priority": priority,
|
|
247
|
+
"status": "backlog",
|
|
248
|
+
"repos": repos,
|
|
249
|
+
"workflow": workflow,
|
|
250
|
+
}
|
|
251
|
+
if jira is not None:
|
|
252
|
+
story["jira"] = jira
|
|
253
|
+
if story_type is not None:
|
|
254
|
+
story["type"] = story_type
|
|
255
|
+
|
|
256
|
+
if "standalone_stories" not in init_data:
|
|
257
|
+
init_data["standalone_stories"] = []
|
|
258
|
+
init_data["standalone_stories"].append(story)
|
|
259
|
+
|
|
260
|
+
# Update total_points
|
|
261
|
+
current_total = init_data.get("total_points", 0) or 0
|
|
262
|
+
init_data["total_points"] = current_total + points
|
|
263
|
+
|
|
264
|
+
with open(init_path, "w") as f:
|
|
265
|
+
ryml.dump(init_data, f)
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
"success": True,
|
|
269
|
+
"story_id": story_id,
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
147
273
|
@click.command("add")
|
|
148
|
-
@click.argument("epic_id", type=str)
|
|
149
|
-
@click.argument("title", type=str)
|
|
150
|
-
@click.argument("points", type=int)
|
|
274
|
+
@click.argument("epic_id", type=str, required=False)
|
|
275
|
+
@click.argument("title", type=str, required=False)
|
|
276
|
+
@click.argument("points", type=int, required=False)
|
|
151
277
|
@click.option("--type", "story_type", type=click.Choice(["feature", "bug", "chore", "refactor"]), default="feature")
|
|
152
278
|
@click.option("--priority", type=click.Choice(["P0", "P1", "P2", "P3"]), default="P1")
|
|
153
279
|
@click.option("--workflow", type=click.Choice(["tdd", "trivial", "bdd"]), default="tdd")
|
|
154
280
|
@click.option("--jira", "jira_id", type=str, default=None)
|
|
155
281
|
@click.option("--sprint-file", type=click.Path(), default=None, help="Path to sprint YAML file")
|
|
282
|
+
@click.option("--initiative", type=str, default=None, help="Add as standalone story to initiative (e.g., technical-debt)")
|
|
283
|
+
@click.option("--repos", type=str, default="pennyfarthing", help="Repos (default: pennyfarthing)")
|
|
156
284
|
def story_add_command(
|
|
157
|
-
epic_id: str,
|
|
158
|
-
title: str,
|
|
159
|
-
points: int,
|
|
285
|
+
epic_id: str | None,
|
|
286
|
+
title: str | None,
|
|
287
|
+
points: int | None,
|
|
160
288
|
story_type: str,
|
|
161
289
|
priority: str,
|
|
162
290
|
workflow: str,
|
|
163
291
|
jira_id: str | None,
|
|
164
292
|
sprint_file: str | None,
|
|
293
|
+
initiative: str | None,
|
|
294
|
+
repos: str,
|
|
165
295
|
) -> None:
|
|
166
|
-
"""Add a new story to an epic.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
title
|
|
177
|
-
points
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
296
|
+
"""Add a new story to an epic or initiative.
|
|
297
|
+
|
|
298
|
+
\b
|
|
299
|
+
Epic mode (default):
|
|
300
|
+
pf sprint story add <EPIC_ID> <TITLE> <POINTS>
|
|
301
|
+
|
|
302
|
+
Initiative mode (--initiative):
|
|
303
|
+
pf sprint story add --initiative <SLUG> <TITLE> <POINTS>
|
|
304
|
+
"""
|
|
305
|
+
if initiative:
|
|
306
|
+
# Initiative mode: first positional arg is title, second is points
|
|
307
|
+
# epic_id absorbs the title, title absorbs points (as str)
|
|
308
|
+
if epic_id is None:
|
|
309
|
+
raise click.ClickException("TITLE is required")
|
|
310
|
+
init_title = epic_id
|
|
311
|
+
if title is None:
|
|
312
|
+
raise click.ClickException("POINTS is required")
|
|
313
|
+
try:
|
|
314
|
+
init_points = int(title)
|
|
315
|
+
except ValueError:
|
|
316
|
+
raise click.ClickException(f"POINTS must be an integer, got '{title}'")
|
|
317
|
+
|
|
318
|
+
result = add_initiative_story(
|
|
319
|
+
initiative_slug=initiative,
|
|
320
|
+
title=init_title,
|
|
321
|
+
points=init_points,
|
|
322
|
+
story_type=story_type if story_type != "feature" else None,
|
|
323
|
+
priority=priority,
|
|
324
|
+
workflow=workflow,
|
|
325
|
+
jira=jira_id,
|
|
326
|
+
repos=repos,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
if result["success"]:
|
|
330
|
+
click.echo(f"Added story {result['story_id']}: {init_title} [{init_points}pts] to initiative {initiative}")
|
|
331
|
+
else:
|
|
332
|
+
raise click.ClickException(result["error"])
|
|
186
333
|
else:
|
|
187
|
-
|
|
334
|
+
# Epic mode: all three positional args required
|
|
335
|
+
if epic_id is None:
|
|
336
|
+
raise click.ClickException("EPIC_ID is required")
|
|
337
|
+
if title is None:
|
|
338
|
+
raise click.ClickException("TITLE is required")
|
|
339
|
+
if points is None:
|
|
340
|
+
raise click.ClickException("POINTS is required")
|
|
341
|
+
|
|
342
|
+
if sprint_file is None:
|
|
343
|
+
from pennyfarthing_scripts.common.config import get_project_root
|
|
344
|
+
path = get_project_root() / "sprint" / "current-sprint.yaml"
|
|
345
|
+
else:
|
|
346
|
+
path = Path(sprint_file)
|
|
347
|
+
|
|
348
|
+
result = add_story(
|
|
349
|
+
sprint_path=path,
|
|
350
|
+
epic_id=epic_id,
|
|
351
|
+
title=title,
|
|
352
|
+
points=points,
|
|
353
|
+
story_type=story_type if story_type != "feature" else None,
|
|
354
|
+
priority=priority,
|
|
355
|
+
workflow=workflow,
|
|
356
|
+
jira=jira_id,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
if result["success"]:
|
|
360
|
+
click.echo(f"Added story {result['story_id']}: {title} [{points}pts]")
|
|
361
|
+
else:
|
|
362
|
+
raise click.ClickException(result["error"])
|