@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.
Files changed (178) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -1
  3. package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
  4. package/packages/core/dist/cli/commands/init.js +7 -6
  5. package/packages/core/dist/cli/commands/init.js.map +1 -1
  6. package/packages/core/dist/cli/utils/symlinks.d.ts +7 -0
  7. package/packages/core/dist/cli/utils/symlinks.d.ts.map +1 -1
  8. package/packages/core/dist/cli/utils/symlinks.js +25 -0
  9. package/packages/core/dist/cli/utils/symlinks.js.map +1 -1
  10. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +1 -1
  11. package/pennyfarthing-dist/scripts/core/prime.sh +23 -0
  12. package/pennyfarthing-dist/scripts/core/run.sh +5 -5
  13. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +2 -2
  14. package/pennyfarthing-dist/scripts/git/release.sh +2 -2
  15. package/pennyfarthing-dist/scripts/health/drift-detection.sh +1 -1
  16. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +2 -2
  17. package/pennyfarthing-dist/scripts/hooks/pre-push.sh +2 -2
  18. package/pennyfarthing-dist/scripts/hooks/session-stop.sh +1 -1
  19. package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +1 -1
  20. package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +1 -1
  21. package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +1 -1
  22. package/pennyfarthing-dist/scripts/lib/common.sh +1 -1
  23. package/pennyfarthing-dist/scripts/lib/find-root.sh +4 -4
  24. package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +1 -1
  25. package/pennyfarthing-dist/scripts/misc/add_short_names.py +2 -2
  26. package/pennyfarthing-dist/scripts/misc/backlog.sh +2 -2
  27. package/pennyfarthing-dist/scripts/misc/deploy.sh +2 -2
  28. package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +4 -4
  29. package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +2 -2
  30. package/pennyfarthing-dist/scripts/misc/run-ci.sh +1 -1
  31. package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +2 -2
  32. package/pennyfarthing-dist/scripts/sprint/archive-story.sh +1 -1
  33. package/pennyfarthing-dist/scripts/sprint/available-stories.sh +1 -1
  34. package/pennyfarthing-dist/scripts/sprint/check-story.sh +1 -1
  35. package/pennyfarthing-dist/scripts/sprint/get-epic-field.sh +1 -1
  36. package/pennyfarthing-dist/scripts/sprint/get-story-field.sh +1 -1
  37. package/pennyfarthing-dist/scripts/sprint/import_epic_to_future.py +2 -2
  38. package/pennyfarthing-dist/scripts/sprint/list-future.sh +1 -1
  39. package/pennyfarthing-dist/scripts/sprint/new-sprint.sh +1 -1
  40. package/pennyfarthing-dist/scripts/sprint/promote-epic.sh +1 -1
  41. package/pennyfarthing-dist/scripts/sprint/sprint-common.sh +3 -3
  42. package/pennyfarthing-dist/scripts/sprint/sprint-info.sh +1 -1
  43. package/pennyfarthing-dist/scripts/sprint/sprint-metrics.sh +2 -2
  44. package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +2 -2
  45. package/pennyfarthing-dist/scripts/workflow/check.py +2 -2
  46. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +1 -1
  47. package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +1 -1
  48. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +2 -2
  49. package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +1 -1
  50. package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +1 -1
  51. package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +1 -1
  52. package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +1 -1
  53. package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +1 -1
  54. package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +1 -1
  55. package/pennyfarthing_scripts/__init__.py +17 -0
  56. package/pennyfarthing_scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  57. package/pennyfarthing_scripts/__pycache__/__init__.cpython-314.pyc +0 -0
  58. package/pennyfarthing_scripts/__pycache__/config.cpython-314.pyc +0 -0
  59. package/pennyfarthing_scripts/__pycache__/jira.cpython-314.pyc +0 -0
  60. package/pennyfarthing_scripts/__pycache__/jira_epic_creation.cpython-314.pyc +0 -0
  61. package/pennyfarthing_scripts/__pycache__/jira_sync.cpython-314.pyc +0 -0
  62. package/pennyfarthing_scripts/__pycache__/jira_sync_story.cpython-314.pyc +0 -0
  63. package/pennyfarthing_scripts/__pycache__/sprint.cpython-314.pyc +0 -0
  64. package/pennyfarthing_scripts/__pycache__/workflow.cpython-311.pyc +0 -0
  65. package/pennyfarthing_scripts/__pycache__/workflow.cpython-314.pyc +0 -0
  66. package/pennyfarthing_scripts/bellmode_hook.py +154 -0
  67. package/pennyfarthing_scripts/brownfield/__init__.py +35 -0
  68. package/pennyfarthing_scripts/brownfield/__main__.py +7 -0
  69. package/pennyfarthing_scripts/brownfield/__pycache__/__init__.cpython-314.pyc +0 -0
  70. package/pennyfarthing_scripts/brownfield/__pycache__/__main__.cpython-314.pyc +0 -0
  71. package/pennyfarthing_scripts/brownfield/__pycache__/cli.cpython-314.pyc +0 -0
  72. package/pennyfarthing_scripts/brownfield/__pycache__/discover.cpython-314.pyc +0 -0
  73. package/pennyfarthing_scripts/brownfield/cli.py +131 -0
  74. package/pennyfarthing_scripts/brownfield/discover.py +753 -0
  75. package/pennyfarthing_scripts/common/__init__.py +49 -0
  76. package/pennyfarthing_scripts/common/__pycache__/__init__.cpython-314.pyc +0 -0
  77. package/pennyfarthing_scripts/common/__pycache__/config.cpython-314.pyc +0 -0
  78. package/pennyfarthing_scripts/common/__pycache__/output.cpython-314.pyc +0 -0
  79. package/pennyfarthing_scripts/common/config.py +65 -0
  80. package/pennyfarthing_scripts/common/output.py +180 -0
  81. package/pennyfarthing_scripts/config.py +21 -0
  82. package/pennyfarthing_scripts/git/__init__.py +29 -0
  83. package/pennyfarthing_scripts/git/__pycache__/__init__.cpython-314.pyc +0 -0
  84. package/pennyfarthing_scripts/git/__pycache__/create_branches.cpython-314.pyc +0 -0
  85. package/pennyfarthing_scripts/git/__pycache__/status_all.cpython-314.pyc +0 -0
  86. package/pennyfarthing_scripts/git/create_branches.py +439 -0
  87. package/pennyfarthing_scripts/git/status_all.py +310 -0
  88. package/pennyfarthing_scripts/hooks.py +455 -0
  89. package/pennyfarthing_scripts/jira/__init__.py +93 -0
  90. package/pennyfarthing_scripts/jira/__main__.py +10 -0
  91. package/pennyfarthing_scripts/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  92. package/pennyfarthing_scripts/jira/__pycache__/__main__.cpython-314.pyc +0 -0
  93. package/pennyfarthing_scripts/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  94. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  95. package/pennyfarthing_scripts/jira/__pycache__/cli.cpython-314.pyc +0 -0
  96. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  97. package/pennyfarthing_scripts/jira/__pycache__/compat.cpython-314.pyc +0 -0
  98. package/pennyfarthing_scripts/jira/__pycache__/epic.cpython-314.pyc +0 -0
  99. package/pennyfarthing_scripts/jira/__pycache__/mappings.cpython-314.pyc +0 -0
  100. package/pennyfarthing_scripts/jira/__pycache__/models.cpython-314.pyc +0 -0
  101. package/pennyfarthing_scripts/jira/__pycache__/story.cpython-314.pyc +0 -0
  102. package/pennyfarthing_scripts/jira/__pycache__/sync.cpython-314.pyc +0 -0
  103. package/pennyfarthing_scripts/jira/bidirectional.py +561 -0
  104. package/pennyfarthing_scripts/jira/claim.py +211 -0
  105. package/pennyfarthing_scripts/jira/cli.py +150 -0
  106. package/pennyfarthing_scripts/jira/client.py +613 -0
  107. package/pennyfarthing_scripts/jira/epic.py +176 -0
  108. package/pennyfarthing_scripts/jira/story.py +219 -0
  109. package/pennyfarthing_scripts/jira/sync.py +350 -0
  110. package/pennyfarthing_scripts/jira_bidirectional_sync.py +37 -0
  111. package/pennyfarthing_scripts/jira_epic_creation.py +30 -0
  112. package/pennyfarthing_scripts/jira_sync.py +36 -0
  113. package/pennyfarthing_scripts/jira_sync_story.py +30 -0
  114. package/pennyfarthing_scripts/output.py +37 -0
  115. package/pennyfarthing_scripts/preflight/__init__.py +17 -0
  116. package/pennyfarthing_scripts/preflight/__main__.py +10 -0
  117. package/pennyfarthing_scripts/preflight/__pycache__/__init__.cpython-314.pyc +0 -0
  118. package/pennyfarthing_scripts/preflight/__pycache__/__main__.cpython-314.pyc +0 -0
  119. package/pennyfarthing_scripts/preflight/__pycache__/cli.cpython-314.pyc +0 -0
  120. package/pennyfarthing_scripts/preflight/__pycache__/finish.cpython-314.pyc +0 -0
  121. package/pennyfarthing_scripts/preflight/cli.py +141 -0
  122. package/pennyfarthing_scripts/preflight/finish.py +382 -0
  123. package/pennyfarthing_scripts/pretooluse_hook.py +142 -0
  124. package/pennyfarthing_scripts/prime/__init__.py +38 -0
  125. package/pennyfarthing_scripts/prime/__main__.py +8 -0
  126. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  127. package/pennyfarthing_scripts/prime/__pycache__/__main__.cpython-314.pyc +0 -0
  128. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  129. package/pennyfarthing_scripts/prime/__pycache__/loader.cpython-314.pyc +0 -0
  130. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  131. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  132. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  133. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  134. package/pennyfarthing_scripts/prime/cli.py +220 -0
  135. package/pennyfarthing_scripts/prime/loader.py +239 -0
  136. package/pennyfarthing_scripts/sprint/__init__.py +66 -0
  137. package/pennyfarthing_scripts/sprint/__main__.py +10 -0
  138. package/pennyfarthing_scripts/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  139. package/pennyfarthing_scripts/sprint/__pycache__/__main__.cpython-314.pyc +0 -0
  140. package/pennyfarthing_scripts/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  141. package/pennyfarthing_scripts/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  142. package/pennyfarthing_scripts/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  143. package/pennyfarthing_scripts/sprint/__pycache__/status.cpython-314.pyc +0 -0
  144. package/pennyfarthing_scripts/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  145. package/pennyfarthing_scripts/sprint/__pycache__/work.cpython-314.pyc +0 -0
  146. package/pennyfarthing_scripts/sprint/archive.py +108 -0
  147. package/pennyfarthing_scripts/sprint/cli.py +124 -0
  148. package/pennyfarthing_scripts/sprint/loader.py +193 -0
  149. package/pennyfarthing_scripts/sprint/status.py +122 -0
  150. package/pennyfarthing_scripts/sprint/validator.py +405 -0
  151. package/pennyfarthing_scripts/sprint/work.py +192 -0
  152. package/pennyfarthing_scripts/story/__init__.py +67 -0
  153. package/pennyfarthing_scripts/story/__main__.py +10 -0
  154. package/pennyfarthing_scripts/story/cli.py +105 -0
  155. package/pennyfarthing_scripts/story/create.py +167 -0
  156. package/pennyfarthing_scripts/story/size.py +113 -0
  157. package/pennyfarthing_scripts/story/template.py +151 -0
  158. package/pennyfarthing_scripts/swebench.py +216 -0
  159. package/pennyfarthing_scripts/tests/__init__.py +1 -0
  160. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  161. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  162. package/pennyfarthing_scripts/tests/__pycache__/test_brownfield.cpython-314-pytest-9.0.2.pyc +0 -0
  163. package/pennyfarthing_scripts/tests/__pycache__/test_git_utils.cpython-314-pytest-9.0.2.pyc +0 -0
  164. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  165. package/pennyfarthing_scripts/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  166. package/pennyfarthing_scripts/tests/conftest.py +106 -0
  167. package/pennyfarthing_scripts/tests/test_brownfield.py +842 -0
  168. package/pennyfarthing_scripts/tests/test_cli_modules.py +245 -0
  169. package/pennyfarthing_scripts/tests/test_common.py +180 -0
  170. package/pennyfarthing_scripts/tests/test_git_utils.py +866 -0
  171. package/pennyfarthing_scripts/tests/test_jira_package.py +334 -0
  172. package/pennyfarthing_scripts/tests/test_package_structure.py +372 -0
  173. package/pennyfarthing_scripts/tests/test_prime.py +397 -0
  174. package/pennyfarthing_scripts/tests/test_sprint_package.py +236 -0
  175. package/pennyfarthing_scripts/tests/test_sprint_validator.py +675 -0
  176. package/pennyfarthing_scripts/tests/test_story_package.py +156 -0
  177. package/pennyfarthing_scripts/welcome_hook.py +157 -0
  178. 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())