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.
- package/README.md +1248 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +16 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +19 -0
- package/dist/commands/branch.d.ts +45 -0
- package/dist/commands/branch.js +488 -0
- package/dist/commands/clean.d.ts +34 -0
- package/dist/commands/clean.js +186 -0
- package/dist/commands/clear.d.ts +51 -0
- package/dist/commands/clear.js +835 -0
- package/dist/commands/init/index.d.ts +107 -0
- package/dist/commands/init/index.js +565 -0
- package/dist/commands/launch.d.ts +21 -0
- package/dist/commands/launch.js +108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/base-command.d.ts +114 -0
- package/dist/lib/base-command.js +153 -0
- package/dist/lib/bmad-installer.d.ts +38 -0
- package/dist/lib/bmad-installer.js +145 -0
- package/dist/lib/claude-settings-types.d.ts +102 -0
- package/dist/lib/claude-settings-types.js +5 -0
- package/dist/lib/config.d.ts +25 -0
- package/dist/lib/config.js +46 -0
- package/dist/lib/debug.d.ts +39 -0
- package/dist/lib/debug.js +74 -0
- package/dist/lib/env-compat.d.ts +26 -0
- package/dist/lib/env-compat.js +35 -0
- package/dist/lib/errors.d.ts +126 -0
- package/dist/lib/errors.js +145 -0
- package/dist/lib/generic-merge.d.ts +74 -0
- package/dist/lib/generic-merge.js +105 -0
- package/dist/lib/git/branch.d.ts +67 -0
- package/dist/lib/git/branch.js +155 -0
- package/dist/lib/git/index.d.ts +11 -0
- package/dist/lib/git/index.js +13 -0
- package/dist/lib/git/safety-checks.d.ts +44 -0
- package/dist/lib/git/safety-checks.js +102 -0
- package/dist/lib/git/types.d.ts +31 -0
- package/dist/lib/git/types.js +6 -0
- package/dist/lib/git/worktree.d.ts +67 -0
- package/dist/lib/git/worktree.js +220 -0
- package/dist/lib/gitignore-manager.d.ts +10 -0
- package/dist/lib/gitignore-manager.js +60 -0
- package/dist/lib/hooks-merger.d.ts +28 -0
- package/dist/lib/hooks-merger.js +94 -0
- package/dist/lib/ide-path-resolver.d.ts +102 -0
- package/dist/lib/ide-path-resolver.js +129 -0
- package/dist/lib/index.d.ts +13 -0
- package/dist/lib/index.js +22 -0
- package/dist/lib/output.d.ts +51 -0
- package/dist/lib/output.js +76 -0
- package/dist/lib/paths.d.ts +66 -0
- package/dist/lib/paths.js +136 -0
- package/dist/lib/quiet.d.ts +12 -0
- package/dist/lib/quiet.js +17 -0
- package/dist/lib/settings-hierarchy.d.ts +42 -0
- package/dist/lib/settings-hierarchy.js +105 -0
- package/dist/lib/spawn.d.ts +105 -0
- package/dist/lib/spawn.js +157 -0
- package/dist/lib/spinner.d.ts +19 -0
- package/dist/lib/spinner.js +34 -0
- package/dist/lib/stdin.d.ts +48 -0
- package/dist/lib/stdin.js +60 -0
- package/dist/lib/template-installer.d.ts +92 -0
- package/dist/lib/template-installer.js +375 -0
- package/dist/lib/template-linter.d.ts +49 -0
- package/dist/lib/template-linter.js +173 -0
- package/dist/lib/template-merger.d.ts +47 -0
- package/dist/lib/template-merger.js +173 -0
- package/dist/lib/template-resolver.d.ts +20 -0
- package/dist/lib/template-resolver.js +60 -0
- package/dist/lib/terminal.d.ts +102 -0
- package/dist/lib/terminal.js +245 -0
- package/dist/lib/tty-detection.d.ts +62 -0
- package/dist/lib/tty-detection.js +83 -0
- package/dist/lib/user-utils.d.ts +5 -0
- package/dist/lib/user-utils.js +23 -0
- package/dist/lib/version.d.ts +99 -0
- package/dist/lib/version.js +144 -0
- package/dist/lib/watch-templates.d.ts +6 -0
- package/dist/lib/watch-templates.js +73 -0
- package/dist/lib/windsurf-hooks-hierarchy.d.ts +30 -0
- package/dist/lib/windsurf-hooks-hierarchy.js +66 -0
- package/dist/lib/windsurf-hooks-merger.d.ts +26 -0
- package/dist/lib/windsurf-hooks-merger.js +53 -0
- package/dist/lib/windsurf-hooks-types.d.ts +33 -0
- package/dist/lib/windsurf-hooks-types.js +5 -0
- package/dist/templates/CLAUDE.md +174 -0
- package/dist/templates/_shared/.claude/commands/handoff.md +14 -0
- package/dist/templates/_shared/.claude/settings.json +61 -0
- package/dist/templates/_shared/.codex/workflows/handoff.md +14 -0
- package/dist/templates/_shared/.windsurf/workflows/handoff.md +14 -0
- package/dist/templates/_shared/hooks/__init__.py +16 -0
- package/dist/templates/_shared/hooks/archive_plan.py +270 -0
- package/dist/templates/_shared/hooks/context_enforcer.py +621 -0
- package/dist/templates/_shared/hooks/context_monitor.py +322 -0
- package/dist/templates/_shared/hooks/file-suggestion.py +188 -0
- package/dist/templates/_shared/hooks/task_create_capture.py +194 -0
- package/dist/templates/_shared/hooks/task_update_capture.py +254 -0
- package/dist/templates/_shared/hooks/user_prompt_submit.py +157 -0
- package/dist/templates/_shared/lib/__init__.py +1 -0
- package/dist/templates/_shared/lib/base/__init__.py +49 -0
- package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/atomic_write.py +180 -0
- package/dist/templates/_shared/lib/base/constants.py +299 -0
- package/dist/templates/_shared/lib/base/inference.py +189 -0
- package/dist/templates/_shared/lib/base/utils.py +216 -0
- package/dist/templates/_shared/lib/context/__init__.py +119 -0
- package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/cache.py +446 -0
- package/dist/templates/_shared/lib/context/context_manager.py +1171 -0
- package/dist/templates/_shared/lib/context/discovery.py +486 -0
- package/dist/templates/_shared/lib/context/event_log.py +308 -0
- package/dist/templates/_shared/lib/context/plan_archive.py +247 -0
- package/dist/templates/_shared/lib/context/task_sync.py +367 -0
- package/dist/templates/_shared/lib/handoff/__init__.py +22 -0
- package/dist/templates/_shared/lib/handoff/document_generator.py +307 -0
- package/dist/templates/_shared/lib/templates/README.md +215 -0
- package/dist/templates/_shared/lib/templates/__init__.py +40 -0
- package/dist/templates/_shared/lib/templates/formatters.py +147 -0
- package/dist/templates/_shared/lib/templates/plan_context.py +119 -0
- package/dist/templates/_shared/scripts/save_handoff.py +99 -0
- package/dist/templates/_shared/workflows/handoff.md +212 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ACCESSIBILITY-TESTER.md +80 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ARCHITECT-REVIEWER.md +75 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/ASSUMPTION-CHAIN-TRACER.md +239 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CLARITY-AUDITOR.md +109 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CODE-REVIEWER.md +71 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/COMPLETENESS-CHECKER.md +104 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/CONTEXT-EXTRACTOR.md +93 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/DEVILS-ADVOCATE.md +223 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/DOCUMENTATION-REVIEWER.md +73 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/FEASIBILITY-ANALYST.md +93 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/FRESH-PERSPECTIVE.md +103 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/HANDOFF-READINESS.md +145 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/HIDDEN-COMPLEXITY-DETECTOR.md +248 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/INCENTIVE-MAPPER.md +235 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PENETRATION-TESTER.md +80 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PERFORMANCE-ENGINEER.md +76 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PLAN-ORCHESTRATOR.md +141 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/PRECEDENT-FINDER.md +240 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/REVERSIBILITY-ANALYST.md +211 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/RISK-ASSESSOR.md +101 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SECOND-ORDER-ANALYST.md +197 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SIMPLICITY-GUARDIAN.md +97 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/SKEPTIC.md +349 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/STAKEHOLDER-ADVOCATE.md +106 -0
- package/dist/templates/cc-native/.claude/agents/cc-native/TRADE-OFF-ILLUMINATOR.md +205 -0
- package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +8 -0
- package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -0
- package/dist/templates/cc-native/.claude/settings.json +119 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -0
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -0
- package/dist/templates/cc-native/CC-NATIVE-README.md +192 -0
- package/dist/templates/cc-native/MIGRATION.md +86 -0
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +331 -0
- package/dist/templates/cc-native/_cc-native/docs/PERMISSION_REQUEST_VERIFICATION.md +147 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +150 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +746 -0
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +339 -0
- package/dist/templates/cc-native/_cc-native/lib/__init__.py +57 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/async_archive.py +68 -0
- package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +98 -0
- package/dist/templates/cc-native/_cc-native/lib/constants.py +45 -0
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +273 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +28 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +164 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +89 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +119 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +103 -0
- package/dist/templates/cc-native/_cc-native/lib/state.py +251 -0
- package/dist/templates/cc-native/_cc-native/lib/utils.py +830 -0
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +76 -0
- package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +151 -0
- package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +134 -0
- package/dist/templates/cc-native/_cc-native/workflows/specdev.md +9 -0
- package/dist/types/exit-codes.d.ts +11 -0
- package/dist/types/exit-codes.js +10 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +7 -0
- package/oclif.manifest.json +405 -0
- 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")
|