@pennyfarthing/core 7.8.1 → 7.8.4
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 +1 -1
- package/package.json +2 -1
- package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
- package/packages/core/dist/cli/commands/init.js +7 -6
- package/packages/core/dist/cli/commands/init.js.map +1 -1
- package/packages/core/dist/cli/utils/symlinks.d.ts +7 -0
- package/packages/core/dist/cli/utils/symlinks.d.ts.map +1 -1
- package/packages/core/dist/cli/utils/symlinks.js +25 -0
- package/packages/core/dist/cli/utils/symlinks.js.map +1 -1
- package/pennyfarthing-dist/scripts/core/phase-check-start.sh +1 -1
- package/pennyfarthing-dist/scripts/core/prime.sh +23 -0
- package/pennyfarthing-dist/scripts/core/run.sh +5 -5
- package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +2 -2
- package/pennyfarthing-dist/scripts/git/release.sh +2 -2
- package/pennyfarthing-dist/scripts/health/drift-detection.sh +1 -1
- package/pennyfarthing-dist/scripts/hooks/post-merge.sh +2 -2
- package/pennyfarthing-dist/scripts/hooks/pre-push.sh +2 -2
- package/pennyfarthing-dist/scripts/hooks/session-stop.sh +1 -1
- package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +1 -1
- package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +1 -1
- package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +1 -1
- package/pennyfarthing-dist/scripts/lib/common.sh +1 -1
- package/pennyfarthing-dist/scripts/lib/find-root.sh +4 -4
- package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +1 -1
- package/pennyfarthing-dist/scripts/misc/add_short_names.py +2 -2
- package/pennyfarthing-dist/scripts/misc/backlog.sh +2 -2
- package/pennyfarthing-dist/scripts/misc/deploy.sh +2 -2
- package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +4 -4
- package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +2 -2
- package/pennyfarthing-dist/scripts/misc/run-ci.sh +1 -1
- package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +2 -2
- package/pennyfarthing-dist/scripts/sprint/archive-story.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/available-stories.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/check-story.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/get-epic-field.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/get-story-field.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/import_epic_to_future.py +2 -2
- package/pennyfarthing-dist/scripts/sprint/list-future.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/new-sprint.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/promote-epic.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/sprint-common.sh +3 -3
- package/pennyfarthing-dist/scripts/sprint/sprint-info.sh +1 -1
- package/pennyfarthing-dist/scripts/sprint/sprint-metrics.sh +2 -2
- package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +2 -2
- package/pennyfarthing-dist/scripts/workflow/check.py +2 -2
- package/pennyfarthing-dist/scripts/workflow/finish-story.sh +1 -1
- package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +1 -1
- package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +2 -2
- package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +1 -1
- package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +1 -1
- package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +1 -1
- package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +1 -1
- package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +1 -1
- package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +1 -1
- package/pennyfarthing_scripts/__init__.py +17 -0
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
- package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/bellmode_hook.py +154 -0
- package/pennyfarthing_scripts/brownfield/__init__.py +35 -0
- package/pennyfarthing_scripts/brownfield/__main__.py +7 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/brownfield/cli.py +131 -0
- package/pennyfarthing_scripts/brownfield/discover.py +753 -0
- package/pennyfarthing_scripts/common/__init__.py +49 -0
- package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/common/config.py +65 -0
- package/pennyfarthing_scripts/common/output.py +180 -0
- package/pennyfarthing_scripts/config.py +21 -0
- package/pennyfarthing_scripts/git/__init__.py +29 -0
- package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/git/create_branches.py +439 -0
- package/pennyfarthing_scripts/git/status_all.py +310 -0
- package/pennyfarthing_scripts/hooks.py +455 -0
- package/pennyfarthing_scripts/jira/__init__.py +93 -0
- package/pennyfarthing_scripts/jira/__main__.py +10 -0
- package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/claim.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__/compat.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/jira/bidirectional.py +561 -0
- package/pennyfarthing_scripts/jira/claim.py +211 -0
- package/pennyfarthing_scripts/jira/cli.py +150 -0
- package/pennyfarthing_scripts/jira/client.py +613 -0
- package/pennyfarthing_scripts/jira/epic.py +176 -0
- package/pennyfarthing_scripts/jira/story.py +219 -0
- package/pennyfarthing_scripts/jira/sync.py +350 -0
- package/pennyfarthing_scripts/jira_bidirectional_sync.py +37 -0
- package/pennyfarthing_scripts/jira_epic_creation.py +30 -0
- package/pennyfarthing_scripts/jira_sync.py +36 -0
- package/pennyfarthing_scripts/jira_sync_story.py +30 -0
- package/pennyfarthing_scripts/output.py +37 -0
- package/pennyfarthing_scripts/preflight/__init__.py +17 -0
- package/pennyfarthing_scripts/preflight/__main__.py +10 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/preflight/cli.py +141 -0
- package/pennyfarthing_scripts/preflight/finish.py +382 -0
- package/pennyfarthing_scripts/pretooluse_hook.py +142 -0
- package/pennyfarthing_scripts/prime/__init__.py +38 -0
- package/pennyfarthing_scripts/prime/__main__.py +8 -0
- package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/loader.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__/session.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/prime/cli.py +220 -0
- package/pennyfarthing_scripts/prime/loader.py +239 -0
- package/pennyfarthing_scripts/sprint/__init__.py +66 -0
- package/pennyfarthing_scripts/sprint/__main__.py +10 -0
- package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/sprint/__pycache__/status.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/archive.py +108 -0
- package/pennyfarthing_scripts/sprint/cli.py +124 -0
- package/pennyfarthing_scripts/sprint/loader.py +193 -0
- package/pennyfarthing_scripts/sprint/status.py +122 -0
- package/pennyfarthing_scripts/sprint/validator.py +405 -0
- package/pennyfarthing_scripts/sprint/work.py +192 -0
- package/pennyfarthing_scripts/story/__init__.py +67 -0
- package/pennyfarthing_scripts/story/__main__.py +10 -0
- package/pennyfarthing_scripts/story/cli.py +105 -0
- package/pennyfarthing_scripts/story/create.py +167 -0
- package/pennyfarthing_scripts/story/size.py +113 -0
- package/pennyfarthing_scripts/story/template.py +151 -0
- package/pennyfarthing_scripts/swebench.py +216 -0
- package/pennyfarthing_scripts/tests/__init__.py +1 -0
- package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing_scripts/tests/__pycache__/test_prime.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/conftest.py +106 -0
- package/pennyfarthing_scripts/tests/test_brownfield.py +842 -0
- package/pennyfarthing_scripts/tests/test_cli_modules.py +245 -0
- package/pennyfarthing_scripts/tests/test_common.py +180 -0
- package/pennyfarthing_scripts/tests/test_git_utils.py +866 -0
- package/pennyfarthing_scripts/tests/test_jira_package.py +334 -0
- package/pennyfarthing_scripts/tests/test_package_structure.py +372 -0
- package/pennyfarthing_scripts/tests/test_prime.py +397 -0
- package/pennyfarthing_scripts/tests/test_sprint_package.py +236 -0
- package/pennyfarthing_scripts/tests/test_sprint_validator.py +675 -0
- package/pennyfarthing_scripts/tests/test_story_package.py +156 -0
- package/pennyfarthing_scripts/welcome_hook.py +157 -0
- package/pennyfarthing_scripts/workflow.py +183 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Story CLI - Fan-out CLI for story operations.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python -m pennyfarthing_scripts.story <subcommand> [args]
|
|
6
|
+
|
|
7
|
+
Subcommands:
|
|
8
|
+
size Show sizing guidelines
|
|
9
|
+
template Show story templates
|
|
10
|
+
create Create a new story
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def size(args: list[str]) -> int:
|
|
18
|
+
"""Show sizing guidelines."""
|
|
19
|
+
from pennyfarthing_scripts.story.size import main as size_main
|
|
20
|
+
return size_main(args)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def template(args: list[str]) -> int:
|
|
24
|
+
"""Show story templates."""
|
|
25
|
+
from pennyfarthing_scripts.story.template import main as template_main
|
|
26
|
+
return template_main(args)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def create(args: list[str]) -> int:
|
|
30
|
+
"""Create a new story."""
|
|
31
|
+
from pennyfarthing_scripts.story.create import main as create_main
|
|
32
|
+
return create_main(args)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Subcommand registry
|
|
36
|
+
SUBCOMMANDS = {
|
|
37
|
+
"size": size,
|
|
38
|
+
"template": template,
|
|
39
|
+
"create": create,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def cli(args: list[str] | None = None) -> int:
|
|
44
|
+
"""Main CLI entry point.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
args: Command line arguments (defaults to sys.argv[1:])
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Exit code
|
|
51
|
+
"""
|
|
52
|
+
if args is None:
|
|
53
|
+
args = sys.argv[1:]
|
|
54
|
+
|
|
55
|
+
parser = argparse.ArgumentParser(
|
|
56
|
+
prog="story",
|
|
57
|
+
description="Story CLI for Pennyfarthing",
|
|
58
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
59
|
+
epilog="""
|
|
60
|
+
Subcommands:
|
|
61
|
+
size [points] Show sizing guidelines (optionally for specific points)
|
|
62
|
+
template [type] Show story templates (optionally for specific type)
|
|
63
|
+
create Create a new story
|
|
64
|
+
|
|
65
|
+
Examples:
|
|
66
|
+
story size
|
|
67
|
+
story size 3
|
|
68
|
+
story template feature
|
|
69
|
+
story create MSSCI-12000 "Add feature" 3 --type feature
|
|
70
|
+
""",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"subcommand",
|
|
75
|
+
nargs="?",
|
|
76
|
+
choices=list(SUBCOMMANDS.keys()),
|
|
77
|
+
help="Subcommand to run",
|
|
78
|
+
)
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"args",
|
|
81
|
+
nargs=argparse.REMAINDER,
|
|
82
|
+
help="Arguments for subcommand",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
parsed = parser.parse_args(args)
|
|
86
|
+
|
|
87
|
+
if not parsed.subcommand:
|
|
88
|
+
parser.print_help()
|
|
89
|
+
return 0
|
|
90
|
+
|
|
91
|
+
handler = SUBCOMMANDS.get(parsed.subcommand)
|
|
92
|
+
if handler:
|
|
93
|
+
return handler(parsed.args)
|
|
94
|
+
else:
|
|
95
|
+
print(f"Unknown subcommand: {parsed.subcommand}", file=sys.stderr)
|
|
96
|
+
return 1
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def main(args: list[str] | None = None) -> int:
|
|
100
|
+
"""Alias for cli()."""
|
|
101
|
+
return cli(args)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
sys.exit(cli())
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Story creation utilities.
|
|
3
|
+
|
|
4
|
+
Provides functions for creating new stories.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from pennyfarthing_scripts.story.size import SIZING_GUIDELINES
|
|
10
|
+
from pennyfarthing_scripts.story.template import format_template
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate_story_yaml(
|
|
14
|
+
epic_id: str,
|
|
15
|
+
title: str,
|
|
16
|
+
points: int,
|
|
17
|
+
story_type: str = "feature",
|
|
18
|
+
priority: str = "P2",
|
|
19
|
+
workflow: str | None = None,
|
|
20
|
+
) -> str:
|
|
21
|
+
"""Generate YAML block for a new story.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
epic_id: Parent epic ID
|
|
25
|
+
title: Story title
|
|
26
|
+
points: Story points
|
|
27
|
+
story_type: Story type (feature, bug, refactor, chore)
|
|
28
|
+
priority: Priority (P0, P1, P2, P3)
|
|
29
|
+
workflow: Workflow override (defaults based on points)
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
YAML block string
|
|
33
|
+
"""
|
|
34
|
+
# Determine workflow based on points if not specified
|
|
35
|
+
if workflow is None:
|
|
36
|
+
sizing = SIZING_GUIDELINES.get(points, {})
|
|
37
|
+
workflow = sizing.get("workflow", "tdd")
|
|
38
|
+
|
|
39
|
+
# Generate story ID placeholder
|
|
40
|
+
story_id = f"{epic_id}-NEW"
|
|
41
|
+
|
|
42
|
+
return format_template(
|
|
43
|
+
story_type,
|
|
44
|
+
id=story_id,
|
|
45
|
+
title=title,
|
|
46
|
+
points=points,
|
|
47
|
+
priority=priority,
|
|
48
|
+
workflow=workflow,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def validate_points(points: int) -> dict[str, Any]:
|
|
53
|
+
"""Validate story points.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
points: Story points value
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Dict with valid status and message
|
|
60
|
+
"""
|
|
61
|
+
valid_points = [1, 2, 3, 5, 8, 13]
|
|
62
|
+
|
|
63
|
+
if points not in valid_points:
|
|
64
|
+
return {
|
|
65
|
+
"valid": False,
|
|
66
|
+
"warning": f"Points {points} not in standard sequence: {valid_points}",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if points >= 13:
|
|
70
|
+
return {
|
|
71
|
+
"valid": True,
|
|
72
|
+
"warning": "Consider splitting: 13+ point stories are typically too large",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {"valid": True}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def create_story(
|
|
79
|
+
epic_id: str,
|
|
80
|
+
title: str,
|
|
81
|
+
points: int,
|
|
82
|
+
story_type: str = "feature",
|
|
83
|
+
priority: str = "P2",
|
|
84
|
+
workflow: str | None = None,
|
|
85
|
+
*,
|
|
86
|
+
dry_run: bool = False,
|
|
87
|
+
) -> dict[str, Any]:
|
|
88
|
+
"""Create a new story.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
epic_id: Parent epic ID
|
|
92
|
+
title: Story title
|
|
93
|
+
points: Story points
|
|
94
|
+
story_type: Story type
|
|
95
|
+
priority: Priority
|
|
96
|
+
workflow: Workflow override
|
|
97
|
+
dry_run: If True, don't write to file
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Dict with success status and story YAML
|
|
101
|
+
"""
|
|
102
|
+
# Validate points
|
|
103
|
+
validation = validate_points(points)
|
|
104
|
+
|
|
105
|
+
yaml_block = generate_story_yaml(
|
|
106
|
+
epic_id=epic_id,
|
|
107
|
+
title=title,
|
|
108
|
+
points=points,
|
|
109
|
+
story_type=story_type,
|
|
110
|
+
priority=priority,
|
|
111
|
+
workflow=workflow,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
"success": True,
|
|
116
|
+
"dry_run": dry_run,
|
|
117
|
+
"yaml": yaml_block,
|
|
118
|
+
"warning": validation.get("warning"),
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def main(args: list[str] | None = None) -> int:
|
|
123
|
+
"""CLI entry point for story create.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
args: Command line arguments
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Exit code
|
|
130
|
+
"""
|
|
131
|
+
import argparse
|
|
132
|
+
|
|
133
|
+
parser = argparse.ArgumentParser(description="Create a new story")
|
|
134
|
+
parser.add_argument("epic_id", help="Parent epic ID")
|
|
135
|
+
parser.add_argument("title", help="Story title")
|
|
136
|
+
parser.add_argument("points", type=int, help="Story points")
|
|
137
|
+
parser.add_argument("--type", choices=["feature", "bug", "refactor", "chore"], default="feature")
|
|
138
|
+
parser.add_argument("--priority", choices=["P0", "P1", "P2", "P3"], default="P2")
|
|
139
|
+
parser.add_argument("--workflow", help="Override workflow")
|
|
140
|
+
parser.add_argument("--dry-run", action="store_true", help="Show what would be created")
|
|
141
|
+
|
|
142
|
+
parsed = parser.parse_args(args)
|
|
143
|
+
|
|
144
|
+
result = create_story(
|
|
145
|
+
epic_id=parsed.epic_id,
|
|
146
|
+
title=parsed.title,
|
|
147
|
+
points=parsed.points,
|
|
148
|
+
story_type=parsed.type,
|
|
149
|
+
priority=parsed.priority,
|
|
150
|
+
workflow=parsed.workflow,
|
|
151
|
+
dry_run=parsed.dry_run,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if result.get("warning"):
|
|
155
|
+
print(f"Warning: {result['warning']}")
|
|
156
|
+
print("")
|
|
157
|
+
|
|
158
|
+
print("Add this to sprint/current-sprint.yaml under the epic's stories:")
|
|
159
|
+
print("")
|
|
160
|
+
print(result["yaml"])
|
|
161
|
+
|
|
162
|
+
return 0
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
import sys
|
|
167
|
+
sys.exit(main())
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Story sizing utilities.
|
|
3
|
+
|
|
4
|
+
Provides guidelines and helpers for sizing stories.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Sizing guidelines
|
|
11
|
+
SIZING_GUIDELINES = {
|
|
12
|
+
1: {
|
|
13
|
+
"scale": "Trivial",
|
|
14
|
+
"complexity": "Single file, minimal testing",
|
|
15
|
+
"examples": ["Config change", "Typo fix", "Simple bug fix"],
|
|
16
|
+
"workflow": "trivial",
|
|
17
|
+
},
|
|
18
|
+
2: {
|
|
19
|
+
"scale": "Trivial",
|
|
20
|
+
"complexity": "Few files, some testing",
|
|
21
|
+
"examples": ["Small fix", "Minor enhancement"],
|
|
22
|
+
"workflow": "trivial",
|
|
23
|
+
},
|
|
24
|
+
3: {
|
|
25
|
+
"scale": "Small",
|
|
26
|
+
"complexity": "Few files, some testing",
|
|
27
|
+
"examples": ["Validation", "Single component"],
|
|
28
|
+
"workflow": "tdd",
|
|
29
|
+
},
|
|
30
|
+
5: {
|
|
31
|
+
"scale": "Medium",
|
|
32
|
+
"complexity": "Multiple files, comprehensive testing",
|
|
33
|
+
"examples": ["New page", "API endpoint"],
|
|
34
|
+
"workflow": "tdd",
|
|
35
|
+
},
|
|
36
|
+
8: {
|
|
37
|
+
"scale": "Large",
|
|
38
|
+
"complexity": "Significant scope, extensive testing",
|
|
39
|
+
"examples": ["Integration", "Major refactor"],
|
|
40
|
+
"workflow": "tdd",
|
|
41
|
+
},
|
|
42
|
+
13: {
|
|
43
|
+
"scale": "SPLIT",
|
|
44
|
+
"complexity": "Too complex for single story",
|
|
45
|
+
"examples": ["Break into smaller stories"],
|
|
46
|
+
"workflow": None,
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_sizing_guidelines(points: int | None = None) -> dict[int, dict[str, Any]]:
|
|
52
|
+
"""Get sizing guidelines.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
points: Optional specific point value to get
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Dict of sizing guidelines
|
|
59
|
+
"""
|
|
60
|
+
if points is not None:
|
|
61
|
+
if points in SIZING_GUIDELINES:
|
|
62
|
+
return {points: SIZING_GUIDELINES[points]}
|
|
63
|
+
return {}
|
|
64
|
+
return SIZING_GUIDELINES
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def format_size_info(size_info: dict[int, dict[str, Any]]) -> str:
|
|
68
|
+
"""Format sizing info as human-readable string.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
size_info: Sizing info dict
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Formatted string
|
|
75
|
+
"""
|
|
76
|
+
lines = []
|
|
77
|
+
|
|
78
|
+
for points, info in sorted(size_info.items()):
|
|
79
|
+
lines.append(f"{points} points - {info['scale']}")
|
|
80
|
+
lines.append(f" Complexity: {info['complexity']}")
|
|
81
|
+
lines.append(f" Examples: {', '.join(info['examples'])}")
|
|
82
|
+
if info.get('workflow'):
|
|
83
|
+
lines.append(f" Workflow: {info['workflow']}")
|
|
84
|
+
lines.append("")
|
|
85
|
+
|
|
86
|
+
return "\n".join(lines)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main(args: list[str] | None = None) -> int:
|
|
90
|
+
"""CLI entry point for story size.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
args: Command line arguments
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Exit code
|
|
97
|
+
"""
|
|
98
|
+
import argparse
|
|
99
|
+
|
|
100
|
+
parser = argparse.ArgumentParser(description="Story sizing guidelines")
|
|
101
|
+
parser.add_argument("points", nargs="?", type=int, help="Specific point value")
|
|
102
|
+
|
|
103
|
+
parsed = parser.parse_args(args)
|
|
104
|
+
|
|
105
|
+
guidelines = get_sizing_guidelines(parsed.points)
|
|
106
|
+
print(format_size_info(guidelines))
|
|
107
|
+
|
|
108
|
+
return 0
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
if __name__ == "__main__":
|
|
112
|
+
import sys
|
|
113
|
+
sys.exit(main())
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Story templates.
|
|
3
|
+
|
|
4
|
+
Provides templates for different story types.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Story templates
|
|
11
|
+
TEMPLATES = {
|
|
12
|
+
"feature": {
|
|
13
|
+
"type": "feature",
|
|
14
|
+
"template": """- id: {id}
|
|
15
|
+
title: "{title}"
|
|
16
|
+
status: backlog
|
|
17
|
+
points: {points}
|
|
18
|
+
priority: {priority}
|
|
19
|
+
workflow: {workflow}
|
|
20
|
+
acceptance_criteria:
|
|
21
|
+
- [ ] Feature is implemented
|
|
22
|
+
- [ ] Tests pass
|
|
23
|
+
- [ ] Documentation updated
|
|
24
|
+
""",
|
|
25
|
+
"description": "New feature implementation",
|
|
26
|
+
},
|
|
27
|
+
"bug": {
|
|
28
|
+
"type": "bug",
|
|
29
|
+
"template": """- id: {id}
|
|
30
|
+
title: "[BUG] {title}"
|
|
31
|
+
status: backlog
|
|
32
|
+
points: {points}
|
|
33
|
+
priority: {priority}
|
|
34
|
+
workflow: {workflow}
|
|
35
|
+
acceptance_criteria:
|
|
36
|
+
- [ ] Bug is fixed
|
|
37
|
+
- [ ] Root cause identified
|
|
38
|
+
- [ ] Regression test added
|
|
39
|
+
""",
|
|
40
|
+
"description": "Bug fix",
|
|
41
|
+
},
|
|
42
|
+
"refactor": {
|
|
43
|
+
"type": "refactor",
|
|
44
|
+
"template": """- id: {id}
|
|
45
|
+
title: "[REFACTOR] {title}"
|
|
46
|
+
status: backlog
|
|
47
|
+
points: {points}
|
|
48
|
+
priority: {priority}
|
|
49
|
+
workflow: {workflow}
|
|
50
|
+
acceptance_criteria:
|
|
51
|
+
- [ ] Code is refactored
|
|
52
|
+
- [ ] Tests still pass
|
|
53
|
+
- [ ] No behavior change
|
|
54
|
+
""",
|
|
55
|
+
"description": "Code refactoring",
|
|
56
|
+
},
|
|
57
|
+
"chore": {
|
|
58
|
+
"type": "chore",
|
|
59
|
+
"template": """- id: {id}
|
|
60
|
+
title: "[CHORE] {title}"
|
|
61
|
+
status: backlog
|
|
62
|
+
points: {points}
|
|
63
|
+
priority: {priority}
|
|
64
|
+
workflow: trivial
|
|
65
|
+
acceptance_criteria:
|
|
66
|
+
- [ ] Task completed
|
|
67
|
+
""",
|
|
68
|
+
"description": "Maintenance task",
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_template(template_type: str) -> dict[str, Any] | None:
|
|
74
|
+
"""Get a specific template.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
template_type: Template type (feature, bug, refactor, chore)
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Template dict or None if not found
|
|
81
|
+
"""
|
|
82
|
+
return TEMPLATES.get(template_type)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_all_templates() -> dict[str, dict[str, Any]]:
|
|
86
|
+
"""Get all available templates.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Dict of all templates
|
|
90
|
+
"""
|
|
91
|
+
return TEMPLATES
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def format_template(template_type: str, **kwargs: Any) -> str:
|
|
95
|
+
"""Format a template with provided values.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
template_type: Template type
|
|
99
|
+
**kwargs: Values to fill in template
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Formatted template string
|
|
103
|
+
"""
|
|
104
|
+
template = get_template(template_type)
|
|
105
|
+
if not template:
|
|
106
|
+
template = get_template("feature") # Default
|
|
107
|
+
|
|
108
|
+
template_str = template["template"]
|
|
109
|
+
return template_str.format(**kwargs)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def main(args: list[str] | None = None) -> int:
|
|
113
|
+
"""CLI entry point for story template.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
args: Command line arguments
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Exit code
|
|
120
|
+
"""
|
|
121
|
+
import argparse
|
|
122
|
+
|
|
123
|
+
parser = argparse.ArgumentParser(description="Story templates")
|
|
124
|
+
parser.add_argument(
|
|
125
|
+
"type",
|
|
126
|
+
nargs="?",
|
|
127
|
+
choices=list(TEMPLATES.keys()),
|
|
128
|
+
help="Template type",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
parsed = parser.parse_args(args)
|
|
132
|
+
|
|
133
|
+
if parsed.type:
|
|
134
|
+
template = get_template(parsed.type)
|
|
135
|
+
if template:
|
|
136
|
+
print(f"Type: {template['type']}")
|
|
137
|
+
print(f"Description: {template['description']}")
|
|
138
|
+
print("")
|
|
139
|
+
print("Template:")
|
|
140
|
+
print(template["template"])
|
|
141
|
+
else:
|
|
142
|
+
print("Available templates:")
|
|
143
|
+
for name, template in TEMPLATES.items():
|
|
144
|
+
print(f" {name}: {template['description']}")
|
|
145
|
+
|
|
146
|
+
return 0
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
import sys
|
|
151
|
+
sys.exit(main())
|