@pennyfarthing/core 7.8.2 → 7.9.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.
Files changed (210) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/packages/core/dist/cli/commands/init.d.ts.map +1 -1
  4. package/packages/core/dist/cli/commands/init.js +8 -7
  5. package/packages/core/dist/cli/commands/init.js.map +1 -1
  6. package/packages/core/dist/cli/cyclist-migration.test.js +16 -13
  7. package/packages/core/dist/cli/cyclist-migration.test.js.map +1 -1
  8. package/packages/core/dist/cli/utils/files.d.ts +5 -4
  9. package/packages/core/dist/cli/utils/files.d.ts.map +1 -1
  10. package/packages/core/dist/cli/utils/files.js +8 -6
  11. package/packages/core/dist/cli/utils/files.js.map +1 -1
  12. package/packages/core/dist/cli/utils/symlinks.d.ts +7 -0
  13. package/packages/core/dist/cli/utils/symlinks.d.ts.map +1 -1
  14. package/packages/core/dist/cli/utils/symlinks.js +25 -0
  15. package/packages/core/dist/cli/utils/symlinks.js.map +1 -1
  16. package/packages/core/dist/cli/utils/themes.d.ts +1 -1
  17. package/packages/core/dist/cli/utils/themes.d.ts.map +1 -1
  18. package/packages/core/dist/scripts/run-ci.test.js +1 -1
  19. package/packages/core/dist/scripts/run-ci.test.js.map +1 -1
  20. package/pennyfarthing-dist/agents/README.md +25 -17
  21. package/pennyfarthing-dist/agents/architect.md +3 -11
  22. package/pennyfarthing-dist/agents/dev.md +2 -2
  23. package/pennyfarthing-dist/agents/devops.md +3 -11
  24. package/pennyfarthing-dist/agents/handoff.md +4 -4
  25. package/pennyfarthing-dist/agents/orchestrator.md +2 -4
  26. package/pennyfarthing-dist/agents/pm.md +4 -11
  27. package/pennyfarthing-dist/agents/reviewer-preflight.md +4 -3
  28. package/pennyfarthing-dist/agents/reviewer.md +2 -8
  29. package/pennyfarthing-dist/agents/sm-handoff.md +3 -3
  30. package/pennyfarthing-dist/agents/sm-setup.md +1 -1
  31. package/pennyfarthing-dist/agents/sm.md +5 -29
  32. package/pennyfarthing-dist/agents/tea.md +2 -2
  33. package/pennyfarthing-dist/agents/tech-writer.md +3 -12
  34. package/pennyfarthing-dist/agents/testing-runner.md +8 -8
  35. package/pennyfarthing-dist/agents/ux-designer.md +3 -12
  36. package/pennyfarthing-dist/commands/git-cleanup.md +29 -53
  37. package/pennyfarthing-dist/commands/party-mode.md +20 -10
  38. package/pennyfarthing-dist/commands/work.md +6 -105
  39. package/pennyfarthing-dist/guides/agent-behavior.md +19 -7
  40. package/pennyfarthing-dist/personas/themes/1984.yaml +0 -12
  41. package/pennyfarthing-dist/personas/themes/a-team.yaml +0 -10
  42. package/pennyfarthing-dist/personas/themes/agatha-christie.yaml +0 -10
  43. package/pennyfarthing-dist/personas/themes/alice-in-wonderland.yaml +0 -10
  44. package/pennyfarthing-dist/personas/themes/all-stars.yaml +0 -10
  45. package/pennyfarthing-dist/personas/themes/ancient-philosophers.yaml +0 -12
  46. package/pennyfarthing-dist/personas/themes/ancient-strategists.yaml +0 -12
  47. package/pennyfarthing-dist/personas/themes/arcane.yaml +0 -10
  48. package/pennyfarthing-dist/personas/themes/arthurian-mythos.yaml +0 -13
  49. package/pennyfarthing-dist/personas/themes/avatar-the-last-airbender.yaml +0 -10
  50. package/pennyfarthing-dist/personas/themes/babylon-5.yaml +0 -10
  51. package/pennyfarthing-dist/personas/themes/battlestar-galactica.yaml +0 -10
  52. package/pennyfarthing-dist/personas/themes/better-call-saul.yaml +0 -10
  53. package/pennyfarthing-dist/personas/themes/big-lebowski.yaml +0 -10
  54. package/pennyfarthing-dist/personas/themes/black-sails.yaml +0 -10
  55. package/pennyfarthing-dist/personas/themes/blade-runner.yaml +0 -10
  56. package/pennyfarthing-dist/personas/themes/bobiverse.yaml +0 -10
  57. package/pennyfarthing-dist/personas/themes/breaking-bad.yaml +0 -12
  58. package/pennyfarthing-dist/personas/themes/catch-22.yaml +0 -12
  59. package/pennyfarthing-dist/personas/themes/classical-composers.yaml +0 -12
  60. package/pennyfarthing-dist/personas/themes/count-of-monte-cristo.yaml +0 -12
  61. package/pennyfarthing-dist/personas/themes/cowboy-bebop.yaml +0 -12
  62. package/pennyfarthing-dist/personas/themes/deadwood.yaml +0 -10
  63. package/pennyfarthing-dist/personas/themes/dickens.yaml +0 -12
  64. package/pennyfarthing-dist/personas/themes/discworld.yaml +0 -10
  65. package/pennyfarthing-dist/personas/themes/doctor-who.yaml +0 -10
  66. package/pennyfarthing-dist/personas/themes/don-quixote.yaml +0 -12
  67. package/pennyfarthing-dist/personas/themes/dune.yaml +0 -10
  68. package/pennyfarthing-dist/personas/themes/enlightenment-thinkers.yaml +0 -12
  69. package/pennyfarthing-dist/personas/themes/expeditionary-force.yaml +0 -10
  70. package/pennyfarthing-dist/personas/themes/fargo.yaml +0 -12
  71. package/pennyfarthing-dist/personas/themes/film-auteurs.yaml +0 -12
  72. package/pennyfarthing-dist/personas/themes/firefly.yaml +0 -12
  73. package/pennyfarthing-dist/personas/themes/foundation.yaml +0 -10
  74. package/pennyfarthing-dist/personas/themes/futurama.yaml +0 -12
  75. package/pennyfarthing-dist/personas/themes/game-of-thrones.yaml +0 -10
  76. package/pennyfarthing-dist/personas/themes/gilligans-island.yaml +0 -12
  77. package/pennyfarthing-dist/personas/themes/gothic-literature.yaml +0 -12
  78. package/pennyfarthing-dist/personas/themes/great-gatsby.yaml +0 -12
  79. package/pennyfarthing-dist/personas/themes/greek-mythology.yaml +0 -13
  80. package/pennyfarthing-dist/personas/themes/hannibal.yaml +0 -10
  81. package/pennyfarthing-dist/personas/themes/harry-potter.yaml +0 -12
  82. package/pennyfarthing-dist/personas/themes/his-dark-materials.yaml +0 -10
  83. package/pennyfarthing-dist/personas/themes/historical-figures.yaml +0 -10
  84. package/pennyfarthing-dist/personas/themes/hitchhikers-guide.yaml +0 -12
  85. package/pennyfarthing-dist/personas/themes/house-md.yaml +0 -12
  86. package/pennyfarthing-dist/personas/themes/imperial-radch.yaml +0 -10
  87. package/pennyfarthing-dist/personas/themes/inspector-morse.yaml +0 -10
  88. package/pennyfarthing-dist/personas/themes/jane-austen.yaml +0 -10
  89. package/pennyfarthing-dist/personas/themes/jazz-legends.yaml +0 -12
  90. package/pennyfarthing-dist/personas/themes/justified.yaml +0 -10
  91. package/pennyfarthing-dist/personas/themes/legion-of-doom.yaml +0 -10
  92. package/pennyfarthing-dist/personas/themes/les-miserables.yaml +0 -10
  93. package/pennyfarthing-dist/personas/themes/lord-of-the-rings.yaml +0 -12
  94. package/pennyfarthing-dist/personas/themes/lovecraft-mythos.yaml +0 -13
  95. package/pennyfarthing-dist/personas/themes/mad-max.yaml +0 -10
  96. package/pennyfarthing-dist/personas/themes/mad-men.yaml +0 -10
  97. package/pennyfarthing-dist/personas/themes/marvel-mcu.yaml +0 -10
  98. package/pennyfarthing-dist/personas/themes/mash.yaml +0 -12
  99. package/pennyfarthing-dist/personas/themes/mass-effect.yaml +0 -10
  100. package/pennyfarthing-dist/personas/themes/military-commanders.yaml +0 -12
  101. package/pennyfarthing-dist/personas/themes/moby-dick.yaml +0 -12
  102. package/pennyfarthing-dist/personas/themes/monty-python.yaml +0 -10
  103. package/pennyfarthing-dist/personas/themes/neuromancer.yaml +0 -10
  104. package/pennyfarthing-dist/personas/themes/norse-mythology.yaml +0 -12
  105. package/pennyfarthing-dist/personas/themes/parks-and-rec.yaml +0 -12
  106. package/pennyfarthing-dist/personas/themes/peaky-blinders.yaml +0 -10
  107. package/pennyfarthing-dist/personas/themes/princess-bride.yaml +0 -10
  108. package/pennyfarthing-dist/personas/themes/renaissance-masters.yaml +0 -12
  109. package/pennyfarthing-dist/personas/themes/rome.yaml +0 -10
  110. package/pennyfarthing-dist/personas/themes/russian-masters.yaml +0 -12
  111. package/pennyfarthing-dist/personas/themes/sandman.yaml +0 -10
  112. package/pennyfarthing-dist/personas/themes/scientific-revolutionaries.yaml +0 -12
  113. package/pennyfarthing-dist/personas/themes/shakespeare.yaml +0 -10
  114. package/pennyfarthing-dist/personas/themes/sherlock-holmes.yaml +0 -10
  115. package/pennyfarthing-dist/personas/themes/snow-crash.yaml +0 -10
  116. package/pennyfarthing-dist/personas/themes/software-pioneers.yaml +0 -10
  117. package/pennyfarthing-dist/personas/themes/star-trek-tng.yaml +0 -11
  118. package/pennyfarthing-dist/personas/themes/star-trek-tos.yaml +0 -10
  119. package/pennyfarthing-dist/personas/themes/star-wars.yaml +0 -10
  120. package/pennyfarthing-dist/personas/themes/succession.yaml +0 -10
  121. package/pennyfarthing-dist/personas/themes/superfriends.yaml +0 -10
  122. package/pennyfarthing-dist/personas/themes/ted-lasso.yaml +0 -11
  123. package/pennyfarthing-dist/personas/themes/the-americans.yaml +0 -10
  124. package/pennyfarthing-dist/personas/themes/the-crown.yaml +0 -10
  125. package/pennyfarthing-dist/personas/themes/the-expanse.yaml +0 -10
  126. package/pennyfarthing-dist/personas/themes/the-good-place.yaml +0 -11
  127. package/pennyfarthing-dist/personas/themes/the-matrix.yaml +0 -15
  128. package/pennyfarthing-dist/personas/themes/the-odyssey.yaml +0 -10
  129. package/pennyfarthing-dist/personas/themes/the-office.yaml +0 -11
  130. package/pennyfarthing-dist/personas/themes/the-simpsons.yaml +0 -12
  131. package/pennyfarthing-dist/personas/themes/the-sopranos.yaml +0 -10
  132. package/pennyfarthing-dist/personas/themes/the-wire.yaml +0 -12
  133. package/pennyfarthing-dist/personas/themes/the-witcher.yaml +0 -10
  134. package/pennyfarthing-dist/personas/themes/twin-peaks.yaml +0 -10
  135. package/pennyfarthing-dist/personas/themes/vorkosigan-saga.yaml +0 -10
  136. package/pennyfarthing-dist/personas/themes/watchmen.yaml +0 -10
  137. package/pennyfarthing-dist/personas/themes/west-wing.yaml +0 -10
  138. package/pennyfarthing-dist/personas/themes/world-explorers.yaml +0 -12
  139. package/pennyfarthing-dist/personas/themes/wwii-leaders.yaml +0 -12
  140. package/pennyfarthing-dist/personas/themes/x-files.yaml +0 -10
  141. package/pennyfarthing-dist/scripts/core/agent-session.sh +13 -14
  142. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +1 -1
  143. package/pennyfarthing-dist/scripts/core/prime.sh +17 -2
  144. package/pennyfarthing-dist/scripts/core/run.sh +5 -5
  145. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +2 -2
  146. package/pennyfarthing-dist/scripts/git/release.sh +2 -2
  147. package/pennyfarthing-dist/scripts/health/drift-detection.sh +1 -1
  148. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +2 -2
  149. package/pennyfarthing-dist/scripts/hooks/pre-push.sh +2 -2
  150. package/pennyfarthing-dist/scripts/hooks/session-stop.sh +1 -1
  151. package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +1 -1
  152. package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +1 -1
  153. package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +1 -1
  154. package/pennyfarthing-dist/scripts/lib/common.sh +1 -1
  155. package/pennyfarthing-dist/scripts/lib/find-root.sh +4 -4
  156. package/pennyfarthing-dist/scripts/maintenance/migrate-theme-schema.mjs +102 -0
  157. package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +1 -1
  158. package/pennyfarthing-dist/scripts/misc/add_short_names.py +2 -2
  159. package/pennyfarthing-dist/scripts/misc/backlog.sh +2 -2
  160. package/pennyfarthing-dist/scripts/misc/deploy.sh +2 -2
  161. package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +4 -4
  162. package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +2 -2
  163. package/pennyfarthing-dist/scripts/misc/run-ci.sh +1 -1
  164. package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +2 -2
  165. package/pennyfarthing-dist/scripts/sprint/archive-story.sh +6 -2
  166. package/pennyfarthing-dist/scripts/sprint/available-stories.sh +1 -1
  167. package/pennyfarthing-dist/scripts/sprint/check-story.sh +1 -1
  168. package/pennyfarthing-dist/scripts/sprint/get-epic-field.sh +1 -1
  169. package/pennyfarthing-dist/scripts/sprint/get-story-field.sh +1 -1
  170. package/pennyfarthing-dist/scripts/sprint/import_epic_to_future.py +2 -2
  171. package/pennyfarthing-dist/scripts/sprint/list-future.sh +1 -1
  172. package/pennyfarthing-dist/scripts/sprint/new-sprint.sh +1 -1
  173. package/pennyfarthing-dist/scripts/sprint/promote-epic.sh +1 -1
  174. package/pennyfarthing-dist/scripts/sprint/sprint-common.sh +3 -3
  175. package/pennyfarthing-dist/scripts/sprint/sprint-info.sh +1 -1
  176. package/pennyfarthing-dist/scripts/sprint/sprint-metrics.sh +2 -2
  177. package/pennyfarthing-dist/scripts/theme/compute_theme_tiers.py +2 -2
  178. package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +3 -2
  179. package/pennyfarthing-dist/scripts/workflow/check.py +2 -2
  180. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +1 -1
  181. package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +1 -1
  182. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +2 -2
  183. package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +1 -1
  184. package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +1 -1
  185. package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +1 -1
  186. package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +1 -1
  187. package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +1 -1
  188. package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +1 -1
  189. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-01-analyze.md +18 -0
  190. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-03-execute.md +18 -4
  191. package/pennyfarthing-dist/workflows/git-cleanup/steps/step-05-complete.md +13 -5
  192. package/pennyfarthing_scripts/jira/__pycache__/claim.cpython-314.pyc +0 -0
  193. package/pennyfarthing_scripts/jira/__pycache__/client.cpython-314.pyc +0 -0
  194. package/pennyfarthing_scripts/jira/client.py +1 -1
  195. package/pennyfarthing_scripts/prime/__init__.py +98 -11
  196. package/pennyfarthing_scripts/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  197. package/pennyfarthing_scripts/prime/__pycache__/cli.cpython-314.pyc +0 -0
  198. package/pennyfarthing_scripts/prime/__pycache__/models.cpython-314.pyc +0 -0
  199. package/pennyfarthing_scripts/prime/__pycache__/persona.cpython-314.pyc +0 -0
  200. package/pennyfarthing_scripts/prime/__pycache__/session.cpython-314.pyc +0 -0
  201. package/pennyfarthing_scripts/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  202. package/pennyfarthing_scripts/prime/cli.py +208 -53
  203. package/pennyfarthing_scripts/prime/models.py +169 -0
  204. package/pennyfarthing_scripts/prime/persona.py +288 -0
  205. package/pennyfarthing_scripts/prime/session.py +183 -0
  206. package/pennyfarthing_scripts/prime/workflow.py +275 -0
  207. package/pennyfarthing_scripts/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  208. package/pennyfarthing_scripts/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  209. package/pennyfarthing_scripts/tests/__pycache__/test_prime.cpython-314-pytest-9.0.2.pyc +0 -0
  210. package/pennyfarthing_scripts/tests/test_prime.py +653 -0
@@ -1,19 +1,25 @@
1
1
  """
2
- Prime CLI - Load essential project context at agent activation.
2
+ Prime CLI - Unified agent bootstrap for Pennyfarthing.
3
3
 
4
4
  Usage:
5
5
  python -m pennyfarthing_scripts.prime [options]
6
6
 
7
7
  Options:
8
- --agent <name> Load agent definition and sidecar
9
- --minimal Skip all context (fastest)
10
- --full Include domain docs from .claude/project/
11
- --quiet Suppress section headers
8
+ --agent <name> Load agent definition and sidecar
9
+ --minimal Skip all context (fastest)
10
+ --full Include domain docs from .claude/project/
11
+ --quiet Suppress section headers
12
+ --json Output JSON (for Cyclist integration)
13
+ --no-persona Skip persona loading
14
+ --no-workflow Skip workflow detection
15
+ --no-register Skip session registration
16
+ --session-id ID Use explicit session ID
12
17
  """
13
18
 
14
19
  from __future__ import annotations
15
20
 
16
21
  import argparse
22
+ import json
17
23
  import sys
18
24
  from pathlib import Path
19
25
 
@@ -26,6 +32,16 @@ from pennyfarthing_scripts.prime.loader import (
26
32
  load_sidecars,
27
33
  load_sprint_context,
28
34
  )
35
+ from pennyfarthing_scripts.prime.models import PrimeResult, WorkflowState
36
+ from pennyfarthing_scripts.prime.persona import (
37
+ format_persona_output,
38
+ get_crew_manifest,
39
+ get_user_title,
40
+ is_character_voice_enabled,
41
+ load_persona,
42
+ )
43
+ from pennyfarthing_scripts.prime.session import cleanup_old_sessions, register_session
44
+ from pennyfarthing_scripts.prime.workflow import check_redirect, detect_workflow_state
29
45
 
30
46
 
31
47
  def _print_header(title: str, quiet: bool) -> None:
@@ -40,28 +56,73 @@ def _print_header(title: str, quiet: bool) -> None:
40
56
  print(f"# {title}")
41
57
 
42
58
 
59
+ def _format_workflow_state_text(result: PrimeResult) -> str:
60
+ """Format workflow state as text output.
61
+
62
+ Args:
63
+ result: PrimeResult with workflow status
64
+
65
+ Returns:
66
+ Formatted text block
67
+ """
68
+ if not result.workflow_status:
69
+ return ""
70
+
71
+ ws = result.workflow_status
72
+ lines = [
73
+ f"state: {ws.state.value}",
74
+ ]
75
+
76
+ if ws.story_id:
77
+ lines.append(f"story_id: {ws.story_id}")
78
+ if ws.phase:
79
+ lines.append(f"phase: {ws.phase}")
80
+ if ws.phase_owner:
81
+ lines.append(f"phase_owner: {ws.phase_owner}")
82
+ if ws.workflow:
83
+ lines.append(f"workflow: {ws.workflow}")
84
+ if ws.backlog_count > 0:
85
+ lines.append(f"backlog_count: {ws.backlog_count}")
86
+
87
+ return "\n".join(lines)
88
+
89
+
43
90
  def prime(
44
91
  agent_name: str | None = None,
45
92
  minimal: bool = False,
46
93
  full: bool = False,
47
94
  quiet: bool = False,
95
+ json_output: bool = False,
96
+ no_persona: bool = False,
97
+ no_workflow: bool = False,
98
+ no_register: bool = False,
99
+ session_id: str | None = None,
48
100
  project_root: Path | None = None,
49
101
  ) -> int:
50
102
  """Load and print context.
51
103
 
52
104
  Loads context in priority order (optimized for attention):
53
- 1. Agent definition (HIGHEST PRIORITY)
54
- 2. Behavior guide
55
- 3. Sprint context
56
- 4. Session context (header + last assessment)
57
- 5. Sidecars (patterns, gotchas, decisions - LOWEST PRIORITY)
58
- 6. Domain docs (--full only)
105
+ 1. Workflow State (HIGHEST PRIORITY - routing decision)
106
+ 2. Agent definition
107
+ 3. Persona (character voice)
108
+ 4. Behavior guide
109
+ 5. Crew manifest (handoff reference)
110
+ 6. Sprint context
111
+ 7. Session context (header + last assessment)
112
+ 8. Sidecars (patterns, gotchas, decisions)
113
+ 9. Domain docs (--full only)
114
+ 10. Redirect marker (if wrong agent)
59
115
 
60
116
  Args:
61
117
  agent_name: Name of agent to load context for
62
118
  minimal: If True, skip all context (fastest)
63
119
  full: If True, include domain docs
64
120
  quiet: If True, suppress section headers
121
+ json_output: If True, output JSON instead of text
122
+ no_persona: If True, skip persona loading
123
+ no_workflow: If True, skip workflow detection
124
+ no_register: If True, skip session registration
125
+ session_id: Explicit session ID (generated if not provided)
65
126
  project_root: Project root path (auto-detected if not provided)
66
127
 
67
128
  Returns:
@@ -69,81 +130,137 @@ def prime(
69
130
  """
70
131
  # Stop immediately for minimal mode
71
132
  if minimal:
133
+ if json_output:
134
+ print(json.dumps({"minimal": True}))
72
135
  return 0
73
136
 
74
137
  root = project_root or get_project_root()
75
138
 
139
+ # Build result for JSON output
140
+ result = PrimeResult(agent_name=agent_name or "")
141
+
76
142
  # ==========================================================================
77
- # PRIORITY 1: Agent definition (HIGHEST ATTENTION ZONE)
143
+ # Session registration (if enabled)
144
+ # ==========================================================================
145
+ if agent_name and not no_register:
146
+ # Clean up old sessions first
147
+ cleanup_old_sessions(root)
148
+ # Register this session
149
+ session_info = register_session(agent_name, session_id, root)
150
+ result.session_id = session_info.session_id
151
+
152
+ # ==========================================================================
153
+ # PRIORITY 1: Workflow State (HIGHEST ATTENTION ZONE - routing decision)
154
+ # ==========================================================================
155
+ if not no_workflow:
156
+ workflow_status = detect_workflow_state(root)
157
+ result.workflow_status = workflow_status
158
+
159
+ # Check for redirect
160
+ if agent_name and workflow_status.state == WorkflowState.IN_PROGRESS_STATE:
161
+ redirect = check_redirect(workflow_status, agent_name)
162
+ if redirect:
163
+ result.redirect_to, result.redirect_reason = redirect
164
+
165
+ if not json_output:
166
+ _print_header("Workflow State", quiet)
167
+ print(_format_workflow_state_text(result))
168
+
169
+ # ==========================================================================
170
+ # PRIORITY 2: Agent definition
78
171
  # ==========================================================================
79
172
  if agent_name:
80
173
  agent_content = load_agent_definition(agent_name, root)
81
- if agent_content:
174
+ if agent_content and not json_output:
82
175
  _print_header(f"Agent Definition: {agent_name}", quiet)
83
176
  print(agent_content)
84
177
 
85
178
  # ==========================================================================
86
- # PRIORITY 2: Agent behavior guide
179
+ # PRIORITY 3: Persona (if enabled)
87
180
  # ==========================================================================
88
- if agent_name:
181
+ if agent_name and not no_persona and is_character_voice_enabled(root):
182
+ persona, theme = load_persona(agent_name, root)
183
+ if persona and theme:
184
+ result.persona = persona
185
+ result.theme = theme
186
+
187
+ if not json_output:
188
+ crew = get_crew_manifest(root)
189
+ result.crew = crew
190
+ user_title = get_user_title(root)
191
+ _print_header(f"Persona: {persona.character} ({agent_name})", quiet)
192
+ print(format_persona_output(persona, theme, agent_name, crew, user_title))
193
+
194
+ # ==========================================================================
195
+ # PRIORITY 4: Agent behavior guide
196
+ # ==========================================================================
197
+ if agent_name and not json_output:
89
198
  guide_content = load_behavior_guide(root)
90
199
  if guide_content:
91
200
  _print_header("Agent Behavior Guide", quiet)
92
201
  print(guide_content)
93
202
 
94
203
  # ==========================================================================
95
- # PRIORITY 3: Sprint context
204
+ # PRIORITY 5: Sprint context
96
205
  # ==========================================================================
97
- sprint_content = load_sprint_context(root)
98
- if sprint_content:
99
- _print_header("Sprint Context", quiet)
100
- print(sprint_content)
206
+ if not json_output:
207
+ sprint_content = load_sprint_context(root)
208
+ if sprint_content:
209
+ _print_header("Sprint Context", quiet)
210
+ print(sprint_content)
101
211
 
102
212
  # ==========================================================================
103
- # PRIORITY 4: Session context
213
+ # PRIORITY 6: Session context
104
214
  # ==========================================================================
105
- session_result = load_session_context(root)
106
- if session_result:
107
- filename, header, assessment = session_result
108
- _print_header(f"Active Session: {filename}", quiet)
109
-
110
- # Print header
111
- if header:
112
- print(header)
113
-
114
- # Print last assessment with separator
115
- if assessment:
116
- print()
117
- print("---")
118
- print(assessment)
215
+ if not json_output:
216
+ session_result = load_session_context(root)
217
+ if session_result:
218
+ filename, header, assessment = session_result
219
+ _print_header(f"Active Session: {filename}", quiet)
220
+
221
+ # Print header
222
+ if header:
223
+ print(header)
224
+
225
+ # Print last assessment with separator
226
+ if assessment:
227
+ print()
228
+ print("---")
229
+ print(assessment)
119
230
 
120
231
  # ==========================================================================
121
- # PRIORITY 5: Sidecars (LOWEST PRIORITY)
232
+ # PRIORITY 7: Sidecars (LOWEST PRIORITY)
122
233
  # ==========================================================================
123
- if agent_name:
234
+ if agent_name and not json_output:
124
235
  sidecars = load_sidecars(agent_name, root)
125
236
  for filename, content in sidecars.items():
126
237
  _print_header(f"Agent Sidecar: {filename}", quiet)
127
238
  print(content)
128
239
 
129
240
  # ==========================================================================
130
- # PRIORITY 6: Domain docs (--full only)
241
+ # PRIORITY 8: Domain docs (--full only)
131
242
  # ==========================================================================
132
- if full:
243
+ if full and not json_output:
133
244
  domain_docs = load_domain_docs(root)
134
245
  for filename, content in domain_docs:
135
246
  _print_header(filename, quiet)
136
247
  print(content)
137
248
 
138
249
  # ==========================================================================
139
- # FINAL: Activation directive
250
+ # PRIORITY 9: Redirect marker (if wrong agent activated)
140
251
  # ==========================================================================
141
- if agent_name:
252
+ if result.redirect_to and not json_output:
142
253
  print()
143
- print("<activation-directive>")
144
- print("Context loaded. Do NOT manually read agent definition files.")
145
- print("Your agent file has been loaded above. Proceed with your first action.")
146
- print("</activation-directive>")
254
+ print("=" * 60)
255
+ print(f"REDIRECT: You ({agent_name}) should hand off to {result.redirect_to}")
256
+ print(f"Reason: {result.redirect_reason}")
257
+ print("=" * 60)
258
+
259
+ # ==========================================================================
260
+ # JSON output
261
+ # ==========================================================================
262
+ if json_output:
263
+ print(json.dumps(result.to_dict(), indent=2))
147
264
 
148
265
  return 0
149
266
 
@@ -159,22 +276,29 @@ def main(args: list[str] | None = None) -> int:
159
276
  """
160
277
  parser = argparse.ArgumentParser(
161
278
  prog="prime",
162
- description="Load essential project context at agent activation",
279
+ description="Unified agent bootstrap for Pennyfarthing",
163
280
  formatter_class=argparse.RawDescriptionHelpFormatter,
164
281
  epilog="""
165
282
  Loads context in priority order (optimized for attention):
166
- 1. Agent definition (HIGHEST PRIORITY)
167
- 2. Behavior guide
168
- 3. Sprint context
169
- 4. Session context (header + last assessment)
170
- 5. Sidecars (patterns, gotchas, decisions - LOWEST)
171
- 6. Domain docs (--full only)
283
+ 1. Workflow State (HIGHEST - routing decision)
284
+ 2. Agent definition
285
+ 3. Persona (character voice)
286
+ 4. Behavior guide
287
+ 5. Crew manifest (handoff reference)
288
+ 6. Sprint context
289
+ 7. Session context (header + last assessment)
290
+ 8. Sidecars (patterns, gotchas, decisions)
291
+ 9. Domain docs (--full only)
292
+ 10. Redirect marker (if wrong agent)
172
293
 
173
294
  Examples:
174
- prime --agent dev
295
+ prime --agent sm
296
+ prime --agent dev --json
175
297
  prime --agent tea --full
176
298
  prime --minimal
177
299
  prime --quiet --agent sm
300
+ prime --agent dev --no-persona
301
+ prime --agent dev --session-id abc123
178
302
  """,
179
303
  )
180
304
 
@@ -198,6 +322,32 @@ Examples:
198
322
  action="store_true",
199
323
  help="Suppress section headers",
200
324
  )
325
+ parser.add_argument(
326
+ "--json",
327
+ action="store_true",
328
+ dest="json_output",
329
+ help="Output JSON (for Cyclist integration)",
330
+ )
331
+ parser.add_argument(
332
+ "--no-persona",
333
+ action="store_true",
334
+ help="Skip persona loading (disable character voice)",
335
+ )
336
+ parser.add_argument(
337
+ "--no-workflow",
338
+ action="store_true",
339
+ help="Skip workflow state detection",
340
+ )
341
+ parser.add_argument(
342
+ "--no-register",
343
+ action="store_true",
344
+ help="Skip session registration",
345
+ )
346
+ parser.add_argument(
347
+ "--session-id",
348
+ metavar="ID",
349
+ help="Use explicit session ID",
350
+ )
201
351
 
202
352
  parsed = parser.parse_args(args)
203
353
 
@@ -207,6 +357,11 @@ Examples:
207
357
  minimal=parsed.minimal,
208
358
  full=parsed.full,
209
359
  quiet=parsed.quiet,
360
+ json_output=parsed.json_output,
361
+ no_persona=parsed.no_persona,
362
+ no_workflow=parsed.no_workflow,
363
+ no_register=parsed.no_register,
364
+ session_id=parsed.session_id,
210
365
  )
211
366
  except FileNotFoundError as e:
212
367
  print(f"Error: {e}", file=sys.stderr)
@@ -0,0 +1,169 @@
1
+ """
2
+ Data models for Prime v2.
3
+
4
+ Provides structured types for workflow state, persona, and session data.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from enum import Enum
11
+ from typing import Any
12
+
13
+
14
+ class WorkflowState(Enum):
15
+ """Workflow states detected by prime."""
16
+
17
+ FINISH_STATE = "FINISH_STATE"
18
+ IN_PROGRESS_STATE = "IN_PROGRESS_STATE"
19
+ NEW_WORK_STATE = "NEW_WORK_STATE"
20
+ EMPTY_BACKLOG_STATE = "EMPTY_BACKLOG_STATE"
21
+
22
+
23
+ @dataclass
24
+ class WorkflowStatus:
25
+ """Result of workflow state detection.
26
+
27
+ Attributes:
28
+ state: Current workflow state
29
+ story_id: Active story ID if in progress/finish
30
+ phase: Current workflow phase (setup, red, green, review, finish)
31
+ phase_owner: Agent that owns the current phase
32
+ workflow: Workflow name (tdd, trivial, bdd, etc.)
33
+ backlog_count: Number of stories in backlog (for NEW_WORK_STATE)
34
+ session_file: Path to active session file if exists
35
+ """
36
+
37
+ state: WorkflowState
38
+ story_id: str | None = None
39
+ phase: str | None = None
40
+ phase_owner: str | None = None
41
+ workflow: str | None = None
42
+ backlog_count: int = 0
43
+ session_file: str | None = None
44
+
45
+ def to_dict(self) -> dict[str, Any]:
46
+ """Convert to dictionary for JSON serialization."""
47
+ return {
48
+ "state": self.state.value,
49
+ "story_id": self.story_id,
50
+ "phase": self.phase,
51
+ "phase_owner": self.phase_owner,
52
+ "workflow": self.workflow,
53
+ "backlog_count": self.backlog_count,
54
+ "session_file": self.session_file,
55
+ }
56
+
57
+
58
+ @dataclass
59
+ class Persona:
60
+ """Agent persona from theme configuration.
61
+
62
+ Attributes:
63
+ character: Character name (e.g., "Camina Drummer")
64
+ style: Communication style description
65
+ role: Role description
66
+ quote: Optional signature quote
67
+ trait: Optional personality trait
68
+ quirk: Optional personality quirk
69
+ motto: Optional character motto
70
+ helper_name: Optional helper/assistant name
71
+ helper_style: Optional helper communication style
72
+ """
73
+
74
+ character: str
75
+ style: str
76
+ role: str
77
+ quote: str | None = None
78
+ trait: str | None = None
79
+ quirk: str | None = None
80
+ motto: str | None = None
81
+ helper_name: str | None = None
82
+ helper_style: str | None = None
83
+
84
+ def to_dict(self) -> dict[str, Any]:
85
+ """Convert to dictionary for JSON serialization."""
86
+ result = {
87
+ "character": self.character,
88
+ "style": self.style,
89
+ "role": self.role,
90
+ }
91
+ if self.quote:
92
+ result["quote"] = self.quote
93
+ if self.trait:
94
+ result["trait"] = self.trait
95
+ if self.quirk:
96
+ result["quirk"] = self.quirk
97
+ if self.motto:
98
+ result["motto"] = self.motto
99
+ if self.helper_name:
100
+ result["helper_name"] = self.helper_name
101
+ if self.helper_style:
102
+ result["helper_style"] = self.helper_style
103
+ return result
104
+
105
+
106
+ @dataclass
107
+ class CrewMember:
108
+ """A crew member in the theme manifest.
109
+
110
+ Attributes:
111
+ role: Agent role (sm, tea, dev, etc.)
112
+ character: Character name
113
+ """
114
+
115
+ role: str
116
+ character: str
117
+
118
+
119
+ @dataclass
120
+ class SessionInfo:
121
+ """Session registration information.
122
+
123
+ Attributes:
124
+ session_id: Unique session identifier
125
+ agent_name: Name of the registered agent
126
+ file_path: Path to the session file
127
+ """
128
+
129
+ session_id: str
130
+ agent_name: str
131
+ file_path: str
132
+
133
+
134
+ @dataclass
135
+ class PrimeResult:
136
+ """Complete result from prime() for JSON output.
137
+
138
+ Attributes:
139
+ agent_name: Requested agent name
140
+ workflow_status: Current workflow state
141
+ persona: Loaded persona (if enabled)
142
+ theme: Active theme name
143
+ redirect_to: Agent to redirect to (if wrong agent activated)
144
+ redirect_reason: Reason for redirect
145
+ session_id: Session ID (if registered)
146
+ crew: List of crew members for handoff reference
147
+ """
148
+
149
+ agent_name: str
150
+ workflow_status: WorkflowStatus | None = None
151
+ persona: Persona | None = None
152
+ theme: str | None = None
153
+ redirect_to: str | None = None
154
+ redirect_reason: str | None = None
155
+ session_id: str | None = None
156
+ crew: list[CrewMember] = field(default_factory=list)
157
+
158
+ def to_dict(self) -> dict[str, Any]:
159
+ """Convert to dictionary for JSON serialization."""
160
+ return {
161
+ "agent_name": self.agent_name,
162
+ "workflow_status": self.workflow_status.to_dict() if self.workflow_status else None,
163
+ "persona": self.persona.to_dict() if self.persona else None,
164
+ "theme": self.theme,
165
+ "redirect_to": self.redirect_to,
166
+ "redirect_reason": self.redirect_reason,
167
+ "session_id": self.session_id,
168
+ "crew": [{"role": c.role, "character": c.character} for c in self.crew],
169
+ }