aiwcli 0.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 (204) hide show
  1. package/README.md +1248 -0
  2. package/bin/dev.cmd +3 -0
  3. package/bin/dev.js +16 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +19 -0
  6. package/dist/commands/branch.d.ts +45 -0
  7. package/dist/commands/branch.js +488 -0
  8. package/dist/commands/clean.d.ts +34 -0
  9. package/dist/commands/clean.js +186 -0
  10. package/dist/commands/clear.d.ts +51 -0
  11. package/dist/commands/clear.js +835 -0
  12. package/dist/commands/init/index.d.ts +107 -0
  13. package/dist/commands/init/index.js +565 -0
  14. package/dist/commands/launch.d.ts +21 -0
  15. package/dist/commands/launch.js +108 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/lib/base-command.d.ts +114 -0
  19. package/dist/lib/base-command.js +153 -0
  20. package/dist/lib/bmad-installer.d.ts +38 -0
  21. package/dist/lib/bmad-installer.js +145 -0
  22. package/dist/lib/claude-settings-types.d.ts +102 -0
  23. package/dist/lib/claude-settings-types.js +5 -0
  24. package/dist/lib/config.d.ts +25 -0
  25. package/dist/lib/config.js +46 -0
  26. package/dist/lib/debug.d.ts +39 -0
  27. package/dist/lib/debug.js +74 -0
  28. package/dist/lib/env-compat.d.ts +26 -0
  29. package/dist/lib/env-compat.js +35 -0
  30. package/dist/lib/errors.d.ts +126 -0
  31. package/dist/lib/errors.js +145 -0
  32. package/dist/lib/generic-merge.d.ts +74 -0
  33. package/dist/lib/generic-merge.js +105 -0
  34. package/dist/lib/git/branch.d.ts +67 -0
  35. package/dist/lib/git/branch.js +155 -0
  36. package/dist/lib/git/index.d.ts +11 -0
  37. package/dist/lib/git/index.js +13 -0
  38. package/dist/lib/git/safety-checks.d.ts +44 -0
  39. package/dist/lib/git/safety-checks.js +102 -0
  40. package/dist/lib/git/types.d.ts +31 -0
  41. package/dist/lib/git/types.js +6 -0
  42. package/dist/lib/git/worktree.d.ts +67 -0
  43. package/dist/lib/git/worktree.js +220 -0
  44. package/dist/lib/gitignore-manager.d.ts +10 -0
  45. package/dist/lib/gitignore-manager.js +60 -0
  46. package/dist/lib/hooks-merger.d.ts +28 -0
  47. package/dist/lib/hooks-merger.js +94 -0
  48. package/dist/lib/ide-path-resolver.d.ts +102 -0
  49. package/dist/lib/ide-path-resolver.js +129 -0
  50. package/dist/lib/index.d.ts +13 -0
  51. package/dist/lib/index.js +22 -0
  52. package/dist/lib/output.d.ts +51 -0
  53. package/dist/lib/output.js +76 -0
  54. package/dist/lib/paths.d.ts +66 -0
  55. package/dist/lib/paths.js +136 -0
  56. package/dist/lib/quiet.d.ts +12 -0
  57. package/dist/lib/quiet.js +17 -0
  58. package/dist/lib/settings-hierarchy.d.ts +42 -0
  59. package/dist/lib/settings-hierarchy.js +105 -0
  60. package/dist/lib/spawn.d.ts +105 -0
  61. package/dist/lib/spawn.js +157 -0
  62. package/dist/lib/spinner.d.ts +19 -0
  63. package/dist/lib/spinner.js +34 -0
  64. package/dist/lib/stdin.d.ts +48 -0
  65. package/dist/lib/stdin.js +60 -0
  66. package/dist/lib/template-installer.d.ts +92 -0
  67. package/dist/lib/template-installer.js +375 -0
  68. package/dist/lib/template-linter.d.ts +49 -0
  69. package/dist/lib/template-linter.js +173 -0
  70. package/dist/lib/template-merger.d.ts +47 -0
  71. package/dist/lib/template-merger.js +173 -0
  72. package/dist/lib/template-resolver.d.ts +20 -0
  73. package/dist/lib/template-resolver.js +60 -0
  74. package/dist/lib/terminal.d.ts +102 -0
  75. package/dist/lib/terminal.js +245 -0
  76. package/dist/lib/tty-detection.d.ts +62 -0
  77. package/dist/lib/tty-detection.js +83 -0
  78. package/dist/lib/user-utils.d.ts +5 -0
  79. package/dist/lib/user-utils.js +23 -0
  80. package/dist/lib/version.d.ts +99 -0
  81. package/dist/lib/version.js +144 -0
  82. package/dist/lib/watch-templates.d.ts +6 -0
  83. package/dist/lib/watch-templates.js +73 -0
  84. package/dist/lib/windsurf-hooks-hierarchy.d.ts +30 -0
  85. package/dist/lib/windsurf-hooks-hierarchy.js +66 -0
  86. package/dist/lib/windsurf-hooks-merger.d.ts +26 -0
  87. package/dist/lib/windsurf-hooks-merger.js +53 -0
  88. package/dist/lib/windsurf-hooks-types.d.ts +33 -0
  89. package/dist/lib/windsurf-hooks-types.js +5 -0
  90. package/dist/templates/CLAUDE.md +174 -0
  91. package/dist/templates/_shared/.claude/commands/handoff.md +14 -0
  92. package/dist/templates/_shared/.claude/settings.json +61 -0
  93. package/dist/templates/_shared/.codex/workflows/handoff.md +14 -0
  94. package/dist/templates/_shared/.windsurf/workflows/handoff.md +14 -0
  95. package/dist/templates/_shared/hooks/__init__.py +16 -0
  96. package/dist/templates/_shared/hooks/archive_plan.py +270 -0
  97. package/dist/templates/_shared/hooks/context_enforcer.py +621 -0
  98. package/dist/templates/_shared/hooks/context_monitor.py +322 -0
  99. package/dist/templates/_shared/hooks/file-suggestion.py +188 -0
  100. package/dist/templates/_shared/hooks/task_create_capture.py +194 -0
  101. package/dist/templates/_shared/hooks/task_update_capture.py +254 -0
  102. package/dist/templates/_shared/hooks/user_prompt_submit.py +157 -0
  103. package/dist/templates/_shared/lib/__init__.py +1 -0
  104. package/dist/templates/_shared/lib/base/__init__.py +49 -0
  105. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  106. package/dist/templates/_shared/lib/base/atomic_write.py +180 -0
  107. package/dist/templates/_shared/lib/base/constants.py +299 -0
  108. package/dist/templates/_shared/lib/base/inference.py +189 -0
  109. package/dist/templates/_shared/lib/base/utils.py +216 -0
  110. package/dist/templates/_shared/lib/context/__init__.py +119 -0
  111. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  112. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  113. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  114. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  115. package/dist/templates/_shared/lib/context/cache.py +446 -0
  116. package/dist/templates/_shared/lib/context/context_manager.py +1171 -0
  117. package/dist/templates/_shared/lib/context/discovery.py +486 -0
  118. package/dist/templates/_shared/lib/context/event_log.py +308 -0
  119. package/dist/templates/_shared/lib/context/plan_archive.py +247 -0
  120. package/dist/templates/_shared/lib/context/task_sync.py +367 -0
  121. package/dist/templates/_shared/lib/handoff/__init__.py +22 -0
  122. package/dist/templates/_shared/lib/handoff/document_generator.py +307 -0
  123. package/dist/templates/_shared/lib/templates/README.md +215 -0
  124. package/dist/templates/_shared/lib/templates/__init__.py +40 -0
  125. package/dist/templates/_shared/lib/templates/formatters.py +147 -0
  126. package/dist/templates/_shared/lib/templates/plan_context.py +119 -0
  127. package/dist/templates/_shared/scripts/save_handoff.py +99 -0
  128. package/dist/templates/_shared/workflows/handoff.md +212 -0
  129. package/dist/templates/cc-native/.claude/agents/cc-native/ACCESSIBILITY-TESTER.md +80 -0
  130. package/dist/templates/cc-native/.claude/agents/cc-native/ARCHITECT-REVIEWER.md +75 -0
  131. package/dist/templates/cc-native/.claude/agents/cc-native/ASSUMPTION-CHAIN-TRACER.md +239 -0
  132. package/dist/templates/cc-native/.claude/agents/cc-native/CLARITY-AUDITOR.md +109 -0
  133. package/dist/templates/cc-native/.claude/agents/cc-native/CODE-REVIEWER.md +71 -0
  134. package/dist/templates/cc-native/.claude/agents/cc-native/COMPLETENESS-CHECKER.md +104 -0
  135. package/dist/templates/cc-native/.claude/agents/cc-native/CONTEXT-EXTRACTOR.md +93 -0
  136. package/dist/templates/cc-native/.claude/agents/cc-native/DEVILS-ADVOCATE.md +223 -0
  137. package/dist/templates/cc-native/.claude/agents/cc-native/DOCUMENTATION-REVIEWER.md +73 -0
  138. package/dist/templates/cc-native/.claude/agents/cc-native/FEASIBILITY-ANALYST.md +93 -0
  139. package/dist/templates/cc-native/.claude/agents/cc-native/FRESH-PERSPECTIVE.md +103 -0
  140. package/dist/templates/cc-native/.claude/agents/cc-native/HANDOFF-READINESS.md +145 -0
  141. package/dist/templates/cc-native/.claude/agents/cc-native/HIDDEN-COMPLEXITY-DETECTOR.md +248 -0
  142. package/dist/templates/cc-native/.claude/agents/cc-native/INCENTIVE-MAPPER.md +235 -0
  143. package/dist/templates/cc-native/.claude/agents/cc-native/PENETRATION-TESTER.md +80 -0
  144. package/dist/templates/cc-native/.claude/agents/cc-native/PERFORMANCE-ENGINEER.md +76 -0
  145. package/dist/templates/cc-native/.claude/agents/cc-native/PLAN-ORCHESTRATOR.md +141 -0
  146. package/dist/templates/cc-native/.claude/agents/cc-native/PRECEDENT-FINDER.md +240 -0
  147. package/dist/templates/cc-native/.claude/agents/cc-native/REVERSIBILITY-ANALYST.md +211 -0
  148. package/dist/templates/cc-native/.claude/agents/cc-native/RISK-ASSESSOR.md +101 -0
  149. package/dist/templates/cc-native/.claude/agents/cc-native/SECOND-ORDER-ANALYST.md +197 -0
  150. package/dist/templates/cc-native/.claude/agents/cc-native/SIMPLICITY-GUARDIAN.md +97 -0
  151. package/dist/templates/cc-native/.claude/agents/cc-native/SKEPTIC.md +349 -0
  152. package/dist/templates/cc-native/.claude/agents/cc-native/STAKEHOLDER-ADVOCATE.md +106 -0
  153. package/dist/templates/cc-native/.claude/agents/cc-native/TRADE-OFF-ILLUMINATOR.md +205 -0
  154. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +8 -0
  155. package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -0
  156. package/dist/templates/cc-native/.claude/settings.json +119 -0
  157. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -0
  158. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +8 -0
  159. package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -0
  160. package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -0
  161. package/dist/templates/cc-native/CC-NATIVE-README.md +192 -0
  162. package/dist/templates/cc-native/MIGRATION.md +86 -0
  163. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +331 -0
  164. package/dist/templates/cc-native/_cc-native/docs/PERMISSION_REQUEST_VERIFICATION.md +147 -0
  165. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  166. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  167. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
  168. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  169. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
  170. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +150 -0
  171. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +746 -0
  172. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +339 -0
  173. package/dist/templates/cc-native/_cc-native/lib/__init__.py +57 -0
  174. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  175. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  176. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  177. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  178. package/dist/templates/cc-native/_cc-native/lib/async_archive.py +68 -0
  179. package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +98 -0
  180. package/dist/templates/cc-native/_cc-native/lib/constants.py +45 -0
  181. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +273 -0
  182. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +28 -0
  183. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  184. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  185. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  186. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  187. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  188. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +164 -0
  189. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +89 -0
  190. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +119 -0
  191. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +103 -0
  192. package/dist/templates/cc-native/_cc-native/lib/state.py +251 -0
  193. package/dist/templates/cc-native/_cc-native/lib/utils.py +830 -0
  194. package/dist/templates/cc-native/_cc-native/plan-review.config.json +76 -0
  195. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  196. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +151 -0
  197. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +134 -0
  198. package/dist/templates/cc-native/_cc-native/workflows/specdev.md +9 -0
  199. package/dist/types/exit-codes.d.ts +11 -0
  200. package/dist/types/exit-codes.js +10 -0
  201. package/dist/types/index.d.ts +5 -0
  202. package/dist/types/index.js +7 -0
  203. package/oclif.manifest.json +405 -0
  204. package/package.json +109 -0
@@ -0,0 +1,486 @@
1
+ """SessionStart discovery utilities for context management.
2
+
3
+ Provides functions for discovering contexts at session start and
4
+ formatting output for Claude to display context choices.
5
+
6
+ Used by:
7
+ - SessionStart hook to show available contexts
8
+ - Plan handoff flow to auto-continue implementation
9
+ """
10
+ from pathlib import Path
11
+ from typing import List, Optional, Tuple
12
+
13
+ from datetime import datetime
14
+
15
+ from .context_manager import (
16
+ Context,
17
+ get_all_contexts,
18
+ get_context_with_pending_plan,
19
+ get_context_with_in_flight_work,
20
+ )
21
+ from .event_log import get_current_state, get_pending_tasks, Task
22
+ from ..base.utils import parse_iso_timestamp
23
+ from ..templates.formatters import get_status_icon, format_continuation_header, get_mode_display
24
+
25
+
26
+ def discover_contexts_for_session(
27
+ project_root: Path = None
28
+ ) -> Tuple[List[Context], Optional[Context]]:
29
+ """
30
+ SessionStart discovery.
31
+
32
+ Returns:
33
+ Tuple of:
34
+ - List of active contexts sorted by last_active (recent first)
35
+ - Context with pending plan implementation (if any)
36
+ """
37
+ active_contexts = get_all_contexts(status="active", project_root=project_root)
38
+ pending_plan_context = get_context_with_pending_plan(project_root)
39
+
40
+ return active_contexts, pending_plan_context
41
+
42
+
43
+ def get_in_flight_context(project_root: Path = None) -> Optional[Context]:
44
+ """
45
+ Get context with any in-flight work (plan, handoff, etc.).
46
+
47
+ Priority order:
48
+ 1. handoff_pending - highest priority (user was interrupted)
49
+ 2. pending_implementation - plan ready for implementation
50
+ 3. implementing - implementation in progress
51
+ 4. planning - actively planning
52
+
53
+ Args:
54
+ project_root: Project root directory
55
+
56
+ Returns:
57
+ Context with in-flight work, or None
58
+ """
59
+ contexts = get_all_contexts(status="active", project_root=project_root)
60
+
61
+ # Sort by in-flight priority
62
+ priority_order = {
63
+ "handoff_pending": 0,
64
+ "pending_implementation": 1,
65
+ "implementing": 2,
66
+ "planning": 3,
67
+ "none": 99,
68
+ }
69
+
70
+ # Only auto-continue for high-priority modes (not "implementing", "planning" or "none")
71
+ actionable_modes = {"handoff_pending", "pending_implementation"}
72
+
73
+ in_flight_contexts = [
74
+ c for c in contexts
75
+ if c.in_flight and c.in_flight.mode in actionable_modes
76
+ ]
77
+
78
+ if not in_flight_contexts:
79
+ return None
80
+
81
+ # Return highest priority, with secondary sort by last_active (most recent) for determinism
82
+ in_flight_contexts.sort(
83
+ key=lambda c: (
84
+ priority_order.get(c.in_flight.mode, 99),
85
+ -(parse_iso_timestamp(c.last_active) or datetime.min).timestamp() if c.last_active else 0
86
+ )
87
+ )
88
+ return in_flight_contexts[0]
89
+
90
+
91
+ def format_context_list(contexts: List[Context]) -> str:
92
+ """
93
+ Format contexts for display to user in SessionStart.
94
+
95
+ Shows context name, summary, status, and last activity time.
96
+
97
+ Args:
98
+ contexts: List of contexts to format
99
+
100
+ Returns:
101
+ Formatted markdown string for display
102
+ """
103
+ if not contexts:
104
+ return "No active contexts found."
105
+
106
+ lines = ["## Active Contexts\n"]
107
+
108
+ for i, ctx in enumerate(contexts, 1):
109
+ # Format last active time
110
+ time_str = _format_relative_time(ctx.last_active)
111
+
112
+ # Build status indicator
113
+ status_indicator = ""
114
+ if ctx.in_flight and ctx.in_flight.mode != "none":
115
+ mode_display = get_mode_display(ctx.in_flight.mode)
116
+ if mode_display:
117
+ status_indicator = f" {mode_display}"
118
+
119
+ lines.append(f"**{i}. {ctx.id}**{status_indicator}")
120
+ lines.append(f" {ctx.summary}")
121
+ if ctx.method:
122
+ lines.append(f" Method: {ctx.method} | Last active: {time_str}")
123
+ else:
124
+ lines.append(f" Last active: {time_str}")
125
+ lines.append("")
126
+
127
+ return "\n".join(lines)
128
+
129
+
130
+ def format_pending_plan_continuation(context: Context) -> str:
131
+ """
132
+ Format output for plan handoff scenario.
133
+
134
+ This is shown when SessionStart detects a context with
135
+ plan.status = "pending_implementation". Provides Claude
136
+ with instructions to continue implementation.
137
+
138
+ Args:
139
+ context: Context with pending plan implementation
140
+
141
+ Returns:
142
+ Formatted instructions for Claude
143
+ """
144
+ lines = [
145
+ format_continuation_header("context", context.id),
146
+ "",
147
+ f"**Summary:** {context.summary}",
148
+ "",
149
+ ]
150
+
151
+ # Add plan info
152
+ if context.in_flight and context.in_flight.artifact_path:
153
+ lines.append(f"**Plan pending implementation:**")
154
+ lines.append(f"`{context.in_flight.artifact_path}`")
155
+ lines.append("")
156
+
157
+ # Add pending tasks if any
158
+ tasks = get_pending_tasks(context.id)
159
+ if tasks:
160
+ lines.append("**Previous tasks:**")
161
+ for task in tasks:
162
+ status_icon = get_status_icon(task.status)
163
+ lines.append(f" {status_icon} {task.subject}")
164
+ lines.append("")
165
+
166
+ lines.extend([
167
+ "---",
168
+ "",
169
+ "**Instructions:**",
170
+ "1. Read the plan file above",
171
+ "2. Use TaskCreate to restore any pending tasks from the plan",
172
+ "3. Begin implementing the approved plan",
173
+ "",
174
+ "The context has been loaded. You may begin implementation.",
175
+ ])
176
+
177
+ return "\n".join(lines)
178
+
179
+
180
+ def format_handoff_continuation(context: Context) -> str:
181
+ """
182
+ Format output for handoff continuation scenario.
183
+
184
+ This is shown when SessionStart detects a context with
185
+ in_flight.mode = "handoff_pending".
186
+
187
+ Args:
188
+ context: Context with handoff pending
189
+
190
+ Returns:
191
+ Formatted instructions for Claude
192
+ """
193
+ lines = [
194
+ format_continuation_header("resuming", context.id),
195
+ "",
196
+ f"**Summary:** {context.summary}",
197
+ "",
198
+ ]
199
+
200
+ # Add handoff document link
201
+ if context.in_flight and context.in_flight.handoff_path:
202
+ lines.append(f"**Handoff document:**")
203
+ lines.append(f"`{context.in_flight.handoff_path}`")
204
+ lines.append("")
205
+
206
+ lines.extend([
207
+ "---",
208
+ "",
209
+ "**Instructions:**",
210
+ "1. Read the handoff document above",
211
+ "2. Use TaskCreate to restore pending tasks",
212
+ "3. Continue where the previous session left off",
213
+ "",
214
+ "The context has been loaded. You may continue.",
215
+ ])
216
+
217
+ return "\n".join(lines)
218
+
219
+
220
+ def format_implementation_continuation(context: Context) -> str:
221
+ """
222
+ Format output for ongoing implementation scenario.
223
+
224
+ This is shown when SessionStart detects a context with
225
+ in_flight.mode = "implementing".
226
+
227
+ Args:
228
+ context: Context with implementation in progress
229
+
230
+ Returns:
231
+ Formatted instructions for Claude
232
+ """
233
+ lines = [
234
+ format_continuation_header("implementing", context.id),
235
+ "",
236
+ f"**Summary:** {context.summary}",
237
+ "",
238
+ ]
239
+
240
+ # Add plan info
241
+ if context.in_flight and context.in_flight.artifact_path:
242
+ lines.append(f"**Plan being implemented:**")
243
+ lines.append(f"`{context.in_flight.artifact_path}`")
244
+ lines.append("")
245
+
246
+ # Add pending tasks
247
+ tasks = get_pending_tasks(context.id)
248
+ if tasks:
249
+ lines.append("**Pending tasks:**")
250
+ for task in tasks:
251
+ status_icon = get_status_icon(task.status)
252
+ lines.append(f" {status_icon} {task.subject}")
253
+ lines.append("")
254
+
255
+ lines.extend([
256
+ "---",
257
+ "",
258
+ "**Instructions:**",
259
+ "1. Review the plan and pending tasks above",
260
+ "2. Use TaskCreate to restore pending tasks",
261
+ "3. Continue implementing",
262
+ "",
263
+ "The context has been loaded. You may continue.",
264
+ ])
265
+
266
+ return "\n".join(lines)
267
+
268
+
269
+ def format_context_picker_prompt() -> str:
270
+ """
271
+ Format the prompt asking user which context to continue.
272
+
273
+ Returns:
274
+ Prompt string for user
275
+ """
276
+ return (
277
+ "\nWhich context would you like to continue?\n"
278
+ "(Say the name/number, or 'new' to start fresh)"
279
+ )
280
+
281
+
282
+ def format_ready_for_new_work() -> str:
283
+ """
284
+ Format output when no active contexts exist.
285
+
286
+ Returns:
287
+ Ready message for user
288
+ """
289
+ return "No active contexts. Ready for new work."
290
+
291
+
292
+ def parse_context_choice_from_prompt(prompt: str, contexts: List[Context]) -> Optional[str]:
293
+ """
294
+ Parse context selection from user prompt.
295
+
296
+ Looks for patterns like:
297
+ - "continue feature-auth" or "resume feature-auth"
298
+ - "1" or "2" (number selection)
299
+ - Context ID mentioned in prompt
300
+
301
+ Args:
302
+ prompt: User's prompt text
303
+ contexts: Available contexts to match against
304
+
305
+ Returns:
306
+ Context ID if match found, None otherwise
307
+ """
308
+ if not prompt or not contexts:
309
+ return None
310
+
311
+ prompt_lower = prompt.lower().strip()
312
+
313
+ # Check for number selection (1, 2, 3, etc.)
314
+ # Match single digit at start or "option 1", "number 1", etc.
315
+ import re
316
+ number_match = re.match(r'^(\d+)$', prompt_lower)
317
+ if number_match:
318
+ idx = int(number_match.group(1)) - 1 # 1-indexed
319
+ if 0 <= idx < len(contexts):
320
+ return contexts[idx].id
321
+
322
+ # Check for "continue X" or "resume X" patterns
323
+ continue_match = re.match(r'^(?:continue|resume|work on|back to)\s+(.+)$', prompt_lower)
324
+ if continue_match:
325
+ target = continue_match.group(1).strip()
326
+ # Try to match against context IDs
327
+ for ctx in contexts:
328
+ if ctx.id.lower() == target or target in ctx.id.lower():
329
+ return ctx.id
330
+
331
+ # Check if any context ID appears in the prompt
332
+ for ctx in contexts:
333
+ if ctx.id.lower() in prompt_lower:
334
+ return ctx.id
335
+
336
+ return None
337
+
338
+
339
+ def format_context_selection_required(contexts: List[Context]) -> str:
340
+ """
341
+ Format urgent picker prompt when multiple contexts require selection.
342
+
343
+ Used by context enforcer hook when context cannot be auto-determined.
344
+
345
+ Args:
346
+ contexts: Available contexts to choose from
347
+
348
+ Returns:
349
+ Formatted system reminder with context choices
350
+ """
351
+ lines = [
352
+ "## Context Selection Required",
353
+ "",
354
+ "Multiple active contexts exist. Please indicate which to continue:",
355
+ "",
356
+ ]
357
+
358
+ for i, ctx in enumerate(contexts, 1):
359
+ time_str = _format_relative_time(ctx.last_active)
360
+
361
+ # Add status indicator for in-flight work
362
+ status = ""
363
+ if ctx.in_flight and ctx.in_flight.mode != "none":
364
+ mode_display = get_mode_display(ctx.in_flight.mode)
365
+ if mode_display:
366
+ status = f" {mode_display}"
367
+
368
+ lines.append(f"{i}. **{ctx.id}**{status} - {ctx.summary} [{time_str}]")
369
+
370
+ lines.extend([
371
+ "",
372
+ "Say the number/name, or describe your new work (a context will be created).",
373
+ ])
374
+
375
+ return "\n".join(lines)
376
+
377
+
378
+ def format_active_context_reminder(context: Context) -> str:
379
+ """
380
+ Format system reminder for active context.
381
+
382
+ Used by context enforcer hook to inject context awareness.
383
+
384
+ Args:
385
+ context: Active context
386
+
387
+ Returns:
388
+ Formatted system reminder
389
+ """
390
+ time_str = _format_relative_time(context.last_active)
391
+
392
+ # Build mode display
393
+ mode_display = "Active"
394
+ if context.in_flight and context.in_flight.mode != "none":
395
+ # Get mode display and strip brackets for this usage
396
+ mode_str = get_mode_display(context.in_flight.mode)
397
+ if mode_str:
398
+ # Remove brackets from "[Planning]" to get "Planning"
399
+ mode_display = mode_str.strip("[]")
400
+
401
+ lines = [
402
+ f"## Active Context: {context.id}",
403
+ "",
404
+ f"**Summary:** {context.summary}",
405
+ f"**Mode:** {mode_display}",
406
+ f"**Last Active:** {time_str}",
407
+ "",
408
+ f'All work belongs to context "{context.id}".',
409
+ "Tasks created with TaskCreate will be persisted to this context.",
410
+ ]
411
+
412
+ return "\n".join(lines)
413
+
414
+
415
+ def format_context_created(context: Context) -> str:
416
+ """
417
+ Format notification that a new context was auto-created.
418
+
419
+ Args:
420
+ context: Newly created context
421
+
422
+ Returns:
423
+ Formatted system reminder
424
+ """
425
+ lines = [
426
+ f"## Context Created: {context.id}",
427
+ "",
428
+ f"**Summary:** {context.summary}",
429
+ "",
430
+ "A new context has been created for this work.",
431
+ "Tasks created with TaskCreate will be persisted to this context.",
432
+ ]
433
+
434
+ return "\n".join(lines)
435
+
436
+
437
+ def _format_relative_time(iso_timestamp: Optional[str]) -> str:
438
+ """
439
+ Format ISO timestamp as relative time string.
440
+
441
+ Args:
442
+ iso_timestamp: ISO format timestamp string
443
+
444
+ Returns:
445
+ Relative time string like "2 hours ago" or "yesterday"
446
+ """
447
+ if not iso_timestamp:
448
+ return "unknown"
449
+
450
+ dt = parse_iso_timestamp(iso_timestamp)
451
+ if not dt:
452
+ return iso_timestamp[:16] # Fallback: show date/time portion
453
+
454
+ now = datetime.now()
455
+
456
+ # Handle timezone-aware vs naive datetime comparison
457
+ # If dt is timezone-aware, convert to naive for comparison
458
+ if dt.tzinfo is not None:
459
+ try:
460
+ # Convert to local time and strip timezone
461
+ dt = dt.replace(tzinfo=None)
462
+ except Exception:
463
+ return iso_timestamp[:16] # Fallback on error
464
+
465
+ diff = now - dt
466
+
467
+ if diff.days == 0:
468
+ hours = diff.seconds // 3600
469
+ if hours == 0:
470
+ minutes = diff.seconds // 60
471
+ if minutes == 0:
472
+ return "just now"
473
+ elif minutes == 1:
474
+ return "1 minute ago"
475
+ else:
476
+ return f"{minutes} minutes ago"
477
+ elif hours == 1:
478
+ return "1 hour ago"
479
+ else:
480
+ return f"{hours} hours ago"
481
+ elif diff.days == 1:
482
+ return "yesterday"
483
+ elif diff.days < 7:
484
+ return f"{diff.days} days ago"
485
+ else:
486
+ return dt.strftime("%Y-%m-%d")