aiwcli 0.9.1 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/archive_plan.py +28 -38
- package/dist/templates/_shared/hooks/context_enforcer.py +71 -21
- package/dist/templates/_shared/hooks/context_monitor.py +4 -8
- package/dist/templates/_shared/hooks/file-suggestion.py +4 -10
- package/dist/templates/_shared/hooks/session_start.py +103 -0
- package/dist/templates/_shared/hooks/task_create_atomicity.py +205 -0
- package/dist/templates/_shared/hooks/task_create_capture.py +83 -146
- package/dist/templates/_shared/hooks/task_update_capture.py +116 -167
- package/dist/templates/_shared/hooks/user_prompt_submit.py +62 -22
- package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/hook_utils.py +169 -0
- package/dist/templates/_shared/lib/base/inference.py +20 -35
- package/dist/templates/_shared/lib/base/stop_words.py +158 -0
- package/dist/templates/_shared/lib/base/utils.py +3 -2
- package/dist/templates/_shared/lib/context/__init__.py +9 -2
- 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_extractor.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__/discovery.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/context_extractor.py +115 -0
- package/dist/templates/_shared/lib/context/context_manager.py +2 -2
- package/dist/templates/_shared/lib/context/discovery.py +4 -4
- package/dist/templates/_shared/lib/context/task_sync.py +5 -82
- package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/persona_questions.py +113 -0
- package/dist/templates/_shared/lib/templates/plan_context.py +13 -27
- package/dist/templates/cc-native/.claude/agents/cc-native/ACCESSIBILITY-TESTER.md +1 -1
- package/dist/templates/cc-native/.claude/agents/cc-native/ARCHITECT-REVIEWER.md +21 -48
- package/dist/templates/cc-native/.claude/agents/cc-native/ASSUMPTION-CHAIN-TRACER.md +26 -204
- package/dist/templates/cc-native/.claude/agents/cc-native/CLARITY-AUDITOR.md +25 -76
- package/dist/templates/cc-native/.claude/agents/cc-native/CODE-REVIEWER.md +1 -1
- package/dist/templates/cc-native/.claude/agents/cc-native/COMPLETENESS-CHECKER.md +32 -77
- package/dist/templates/cc-native/.claude/agents/cc-native/CONTEXT-EXTRACTOR.md +1 -1
- package/dist/templates/cc-native/.claude/agents/cc-native/DEVILS-ADVOCATE.md +26 -189
- package/dist/templates/cc-native/.claude/agents/cc-native/DOCUMENTATION-REVIEWER.md +30 -52
- package/dist/templates/cc-native/.claude/agents/cc-native/FEASIBILITY-ANALYST.md +27 -63
- package/dist/templates/cc-native/.claude/agents/cc-native/FRESH-PERSPECTIVE.md +32 -81
- package/dist/templates/cc-native/.claude/agents/cc-native/HANDOFF-READINESS.md +25 -106
- package/dist/templates/cc-native/.claude/agents/cc-native/HIDDEN-COMPLEXITY-DETECTOR.md +24 -209
- package/dist/templates/cc-native/.claude/agents/cc-native/INCENTIVE-MAPPER.md +26 -200
- package/dist/templates/cc-native/.claude/agents/cc-native/PENETRATION-TESTER.md +1 -1
- package/dist/templates/cc-native/.claude/agents/cc-native/PERFORMANCE-ENGINEER.md +1 -1
- package/dist/templates/cc-native/.claude/agents/cc-native/PLAN-ORCHESTRATOR.md +1 -1
- package/dist/templates/cc-native/.claude/agents/cc-native/PRECEDENT-FINDER.md +36 -206
- package/dist/templates/cc-native/.claude/agents/cc-native/REVERSIBILITY-ANALYST.md +27 -177
- package/dist/templates/cc-native/.claude/agents/cc-native/RISK-ASSESSOR.md +23 -66
- package/dist/templates/cc-native/.claude/agents/cc-native/SECOND-ORDER-ANALYST.md +26 -162
- package/dist/templates/cc-native/.claude/agents/cc-native/SIMPLICITY-GUARDIAN.md +29 -59
- package/dist/templates/cc-native/.claude/agents/cc-native/SKEPTIC.md +28 -312
- package/dist/templates/cc-native/.claude/agents/cc-native/STAKEHOLDER-ADVOCATE.md +23 -74
- package/dist/templates/cc-native/.claude/agents/cc-native/TRADE-OFF-ILLUMINATOR.md +1 -1
- package/dist/templates/cc-native/.claude/settings.json +21 -0
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +211 -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__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +65 -12
- package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +240 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.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/debug.py +124 -0
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +1 -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/agent.py +34 -1
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +7 -1
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/templates/cc-native/_cc-native/docs/PERMISSION_REQUEST_VERIFICATION.md +0 -147
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -27,47 +27,31 @@ Usage in .claude/settings.json:
|
|
|
27
27
|
}
|
|
28
28
|
"""
|
|
29
29
|
import json
|
|
30
|
+
import re
|
|
30
31
|
import sys
|
|
31
32
|
from pathlib import Path
|
|
32
33
|
from typing import Optional
|
|
33
34
|
|
|
34
35
|
# Add parent directories to path for imports
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
sys.path.insert(0, str(
|
|
36
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
37
|
+
SHARED_LIB = SCRIPT_DIR.parent / "lib"
|
|
38
|
+
sys.path.insert(0, str(SHARED_LIB.parent))
|
|
38
39
|
|
|
40
|
+
from lib.base.hook_utils import load_hook_input
|
|
39
41
|
from lib.context.plan_archive import archive_plan_to_context
|
|
40
42
|
from lib.context.context_manager import get_all_contexts
|
|
43
|
+
from lib.context.context_extractor import extract_context_id_for_session
|
|
41
44
|
from lib.base.utils import eprint, project_dir
|
|
45
|
+
from lib.base.constants import get_context_dir
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
Returns:
|
|
53
|
-
Context ID or None if not found
|
|
54
|
-
"""
|
|
55
|
-
contexts = get_all_contexts(status="active", project_root=project_root)
|
|
56
|
-
|
|
57
|
-
# Primary strategy: Find context with matching session_id
|
|
58
|
-
for ctx in contexts:
|
|
59
|
-
if ctx.in_flight and ctx.in_flight.session_ids and session_id in ctx.in_flight.session_ids:
|
|
60
|
-
eprint(f"[archive_plan] Found context by session: {ctx.id}")
|
|
61
|
-
return ctx.id
|
|
62
|
-
|
|
63
|
-
# Fallback: If only one context is planning, assume it's the one
|
|
64
|
-
planning_contexts = [c for c in contexts if c.in_flight and c.in_flight.mode == "planning"]
|
|
65
|
-
if len(planning_contexts) == 1:
|
|
66
|
-
eprint(f"[archive_plan] Fallback: Single planning context: {planning_contexts[0].id}")
|
|
67
|
-
return planning_contexts[0].id
|
|
68
|
-
|
|
69
|
-
eprint(f"[archive_plan] Could not find context for session {session_id}")
|
|
70
|
-
return None
|
|
47
|
+
# Import debug cleanup function from cc-native lib
|
|
48
|
+
_cc_native_lib = SCRIPT_DIR.parent / "_cc-native" / "lib"
|
|
49
|
+
sys.path.insert(0, str(_cc_native_lib))
|
|
50
|
+
try:
|
|
51
|
+
from debug import cleanup_debug_folder
|
|
52
|
+
except ImportError:
|
|
53
|
+
def cleanup_debug_folder(context_path):
|
|
54
|
+
pass # Fallback if debug module not available
|
|
71
55
|
|
|
72
56
|
|
|
73
57
|
def extract_plan_path_from_result(tool_result: str) -> Optional[str]:
|
|
@@ -76,7 +60,6 @@ def extract_plan_path_from_result(tool_result: str) -> Optional[str]:
|
|
|
76
60
|
|
|
77
61
|
Looks for pattern: "Your plan has been saved to: <path>"
|
|
78
62
|
"""
|
|
79
|
-
import re
|
|
80
63
|
match = re.search(r'Your plan has been saved to:\s*(.+\.md)', tool_result)
|
|
81
64
|
if match:
|
|
82
65
|
return match.group(1).strip()
|
|
@@ -90,10 +73,9 @@ def on_plan_archive():
|
|
|
90
73
|
Called from PostToolUse on ExitPlanMode - extracts plan path from result
|
|
91
74
|
and archives to the active context.
|
|
92
75
|
"""
|
|
93
|
-
# Read hook input
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
except json.JSONDecodeError:
|
|
76
|
+
# Read hook input using shared utility
|
|
77
|
+
hook_input = load_hook_input()
|
|
78
|
+
if not hook_input:
|
|
97
79
|
eprint("[archive_plan] No valid JSON input")
|
|
98
80
|
return
|
|
99
81
|
|
|
@@ -220,9 +202,9 @@ def on_plan_archive():
|
|
|
220
202
|
print(f"Plan archival skipped: file not found ({plan_path})")
|
|
221
203
|
return
|
|
222
204
|
|
|
223
|
-
# Find context by session ID
|
|
205
|
+
# Find context by session ID using shared extractor
|
|
224
206
|
session_id = hook_input.get("session_id", "unknown")
|
|
225
|
-
context_id =
|
|
207
|
+
context_id = extract_context_id_for_session(session_id, project_root, "archive_plan")
|
|
226
208
|
|
|
227
209
|
if not context_id:
|
|
228
210
|
eprint("[archive_plan] Could not determine context for session")
|
|
@@ -246,6 +228,14 @@ def on_plan_archive():
|
|
|
246
228
|
)
|
|
247
229
|
|
|
248
230
|
if archived_path:
|
|
231
|
+
# Clean up debug logs before completing archive
|
|
232
|
+
try:
|
|
233
|
+
context_path = get_context_dir(context_id, project_root)
|
|
234
|
+
cleanup_debug_folder(context_path)
|
|
235
|
+
print(f"[archive_plan] Cleaned up debug logs for context: {context_id}")
|
|
236
|
+
except Exception as e:
|
|
237
|
+
print(f"[archive_plan] Warning: could not clean debug folder: {e}")
|
|
238
|
+
|
|
249
239
|
print(f"")
|
|
250
240
|
print(f"[archive_plan] SUCCESS!")
|
|
251
241
|
print(f"[archive_plan] Plan archived to context: {context_id}")
|
|
@@ -49,6 +49,7 @@ SCRIPT_DIR = Path(__file__).resolve().parent
|
|
|
49
49
|
SHARED_LIB = SCRIPT_DIR.parent / "lib"
|
|
50
50
|
sys.path.insert(0, str(SHARED_LIB.parent))
|
|
51
51
|
|
|
52
|
+
from lib.base.hook_utils import load_hook_input
|
|
52
53
|
from lib.base.subprocess_utils import is_internal_call
|
|
53
54
|
from lib.context.context_manager import (
|
|
54
55
|
Context,
|
|
@@ -65,7 +66,7 @@ from lib.context.discovery import (
|
|
|
65
66
|
format_context_created,
|
|
66
67
|
format_pending_plan_continuation,
|
|
67
68
|
format_implementation_continuation,
|
|
68
|
-
|
|
69
|
+
format_relative_time,
|
|
69
70
|
)
|
|
70
71
|
from lib.templates.formatters import get_mode_display
|
|
71
72
|
from lib.base.utils import eprint, project_dir
|
|
@@ -251,7 +252,7 @@ def format_context_picker_stderr(contexts: List[Context]) -> str:
|
|
|
251
252
|
|
|
252
253
|
implementing_count = 0
|
|
253
254
|
for i, ctx in enumerate(contexts, 1):
|
|
254
|
-
time_str =
|
|
255
|
+
time_str = format_relative_time(ctx.last_active)
|
|
255
256
|
|
|
256
257
|
# Check if context is in implementing mode (selectable)
|
|
257
258
|
is_implementing = ctx.in_flight and ctx.in_flight.mode == "implementing"
|
|
@@ -299,13 +300,18 @@ def format_context_picker_stderr(contexts: List[Context]) -> str:
|
|
|
299
300
|
return "\n".join(lines)
|
|
300
301
|
|
|
301
302
|
|
|
302
|
-
def format_command_feedback(
|
|
303
|
+
def format_command_feedback(
|
|
304
|
+
ended_contexts: List[Context],
|
|
305
|
+
selected_context: Optional[Context],
|
|
306
|
+
remaining_prompt: Optional[str] = None
|
|
307
|
+
) -> str:
|
|
303
308
|
"""
|
|
304
309
|
Format feedback about what context operations were performed.
|
|
305
310
|
|
|
306
311
|
Args:
|
|
307
312
|
ended_contexts: Contexts that were ended/completed
|
|
308
313
|
selected_context: Context that was selected (if any)
|
|
314
|
+
remaining_prompt: User's actual request after caret command (if any)
|
|
309
315
|
|
|
310
316
|
Returns:
|
|
311
317
|
Formatted feedback message
|
|
@@ -331,13 +337,21 @@ def format_command_feedback(ended_contexts: List[Context], selected_context: Opt
|
|
|
331
337
|
if mode_str:
|
|
332
338
|
mode_display = mode_str.strip("[]")
|
|
333
339
|
|
|
334
|
-
time_str =
|
|
340
|
+
time_str = format_relative_time(selected_context.last_active)
|
|
335
341
|
lines.append(f"**Mode:** {mode_display}")
|
|
336
342
|
lines.append(f"**Last Active:** {time_str}")
|
|
337
343
|
lines.append("")
|
|
338
344
|
lines.append(f'All work belongs to context "{selected_context.id}".')
|
|
339
345
|
lines.append("Tasks created with TaskCreate will be persisted to this context.")
|
|
340
346
|
|
|
347
|
+
# Add user's actual request if provided after caret command
|
|
348
|
+
if remaining_prompt and remaining_prompt.strip():
|
|
349
|
+
lines.append("")
|
|
350
|
+
lines.append("---")
|
|
351
|
+
lines.append("")
|
|
352
|
+
lines.append("**User's actual request:**")
|
|
353
|
+
lines.append(f"> {remaining_prompt}")
|
|
354
|
+
|
|
341
355
|
return "\n".join(lines)
|
|
342
356
|
|
|
343
357
|
|
|
@@ -345,7 +359,7 @@ def determine_context(
|
|
|
345
359
|
user_prompt: str,
|
|
346
360
|
project_root: Path = None,
|
|
347
361
|
session_id: str = None
|
|
348
|
-
) -> Tuple[Optional[str], str, Optional[str]]:
|
|
362
|
+
) -> Tuple[Optional[str], str, Optional[str], Optional[str]]:
|
|
349
363
|
"""
|
|
350
364
|
Determine which context this prompt belongs to.
|
|
351
365
|
|
|
@@ -355,6 +369,7 @@ def determine_context(
|
|
|
355
369
|
- method: How context was determined (session_match, in_flight, caret_select,
|
|
356
370
|
auto_created, single_context, blocked)
|
|
357
371
|
- output: System reminder to inject, or None
|
|
372
|
+
- remaining_prompt: Actual user request after caret command, or None
|
|
358
373
|
|
|
359
374
|
Raises:
|
|
360
375
|
BlockRequest: When request should be blocked to show picker to user
|
|
@@ -362,7 +377,7 @@ def determine_context(
|
|
|
362
377
|
# 0. Skip context creation for internal subprocess calls (orchestrator, agents)
|
|
363
378
|
if is_internal_call():
|
|
364
379
|
eprint("[context_enforcer] Skipping: internal subprocess call")
|
|
365
|
-
return (None, "skip_internal", None)
|
|
380
|
+
return (None, "skip_internal", None, None)
|
|
366
381
|
|
|
367
382
|
# 1. Check if session already belongs to a context (HIGHEST PRIORITY)
|
|
368
383
|
# This prevents context switching on subsequent prompts - one context per session
|
|
@@ -373,11 +388,20 @@ def determine_context(
|
|
|
373
388
|
return (
|
|
374
389
|
session_context.id,
|
|
375
390
|
"session_match",
|
|
376
|
-
format_active_context_reminder(session_context)
|
|
391
|
+
format_active_context_reminder(session_context),
|
|
392
|
+
None
|
|
377
393
|
)
|
|
378
394
|
|
|
379
395
|
# 2. Check for bare "^" - show context picker
|
|
380
396
|
if user_prompt.strip() == "^":
|
|
397
|
+
# Pre-transition: Move any pending_implementation contexts to implementing
|
|
398
|
+
# This ensures they appear selectable when user opens the picker
|
|
399
|
+
in_flight = get_all_in_flight_contexts(project_root)
|
|
400
|
+
for ctx in in_flight:
|
|
401
|
+
if ctx.in_flight and ctx.in_flight.mode == "pending_implementation":
|
|
402
|
+
update_plan_status(ctx.id, "implementing", project_root=project_root)
|
|
403
|
+
eprint(f"[context_enforcer] Pre-transitioned {ctx.id} to implementing (bare caret)")
|
|
404
|
+
|
|
381
405
|
contexts = get_all_contexts(status="active", project_root=project_root)
|
|
382
406
|
if not contexts:
|
|
383
407
|
raise BlockRequest(
|
|
@@ -407,7 +431,7 @@ def determine_context(
|
|
|
407
431
|
|
|
408
432
|
# Don't auto-create for greetings or help commands
|
|
409
433
|
if any(prompt_lower.startswith(p) or prompt_lower == p for p in skip_patterns):
|
|
410
|
-
return (None, "no_context_needed", None)
|
|
434
|
+
return (None, "no_context_needed", None, None)
|
|
411
435
|
|
|
412
436
|
# Auto-create context from prompt
|
|
413
437
|
try:
|
|
@@ -419,11 +443,12 @@ def determine_context(
|
|
|
419
443
|
return (
|
|
420
444
|
new_context.id,
|
|
421
445
|
"auto_created",
|
|
422
|
-
format_context_created(new_context)
|
|
446
|
+
format_context_created(new_context),
|
|
447
|
+
None
|
|
423
448
|
)
|
|
424
449
|
except Exception as e:
|
|
425
450
|
eprint(f"[context_enforcer] Failed to create context: {e}")
|
|
426
|
-
return (None, "creation_failed", None)
|
|
451
|
+
return (None, "creation_failed", None, None)
|
|
427
452
|
|
|
428
453
|
elif len(in_flight_contexts) == 1:
|
|
429
454
|
# Single in-flight context - auto-select it
|
|
@@ -431,6 +456,15 @@ def determine_context(
|
|
|
431
456
|
mode = ctx.in_flight.mode if ctx.in_flight else "none"
|
|
432
457
|
eprint(f"[context_enforcer] Auto-selected single in-flight context: {ctx.id} (mode={mode})")
|
|
433
458
|
|
|
459
|
+
# Auto-transition pending_implementation to implementing
|
|
460
|
+
# This ensures state updates immediately when context is selected,
|
|
461
|
+
# rather than waiting for _update_in_flight_status() which has conditions
|
|
462
|
+
if mode == "pending_implementation":
|
|
463
|
+
update_plan_status(ctx.id, "implementing", project_root=project_root)
|
|
464
|
+
ctx.in_flight.mode = "implementing" # Update local copy for display
|
|
465
|
+
mode = "implementing" # Update local var for formatter selection
|
|
466
|
+
eprint(f"[context_enforcer] Transitioned {ctx.id} to implementing")
|
|
467
|
+
|
|
434
468
|
# Use mode-specific formatter for better continuation context
|
|
435
469
|
if mode == "pending_implementation":
|
|
436
470
|
output = format_pending_plan_continuation(ctx)
|
|
@@ -439,7 +473,7 @@ def determine_context(
|
|
|
439
473
|
else:
|
|
440
474
|
output = format_active_context_reminder(ctx)
|
|
441
475
|
|
|
442
|
-
return (ctx.id, "auto_selected", output)
|
|
476
|
+
return (ctx.id, "auto_selected", output, None)
|
|
443
477
|
|
|
444
478
|
else:
|
|
445
479
|
# Multiple in-flight contexts - block and show picker
|
|
@@ -455,7 +489,7 @@ def _handle_caret_command(
|
|
|
455
489
|
user_prompt: str,
|
|
456
490
|
contexts: List[Context],
|
|
457
491
|
project_root: Path
|
|
458
|
-
) -> Tuple[Optional[str], str, Optional[str]]:
|
|
492
|
+
) -> Tuple[Optional[str], str, Optional[str], Optional[str]]:
|
|
459
493
|
"""
|
|
460
494
|
Handle explicit caret commands (^E, ^S, ^0, ^N).
|
|
461
495
|
|
|
@@ -465,7 +499,7 @@ def _handle_caret_command(
|
|
|
465
499
|
project_root: Project root directory
|
|
466
500
|
|
|
467
501
|
Returns:
|
|
468
|
-
Tuple of (context_id, method, output)
|
|
502
|
+
Tuple of (context_id, method, output, remaining_prompt)
|
|
469
503
|
|
|
470
504
|
Raises:
|
|
471
505
|
BlockRequest: When command is invalid or selection needed
|
|
@@ -505,7 +539,8 @@ def _handle_caret_command(
|
|
|
505
539
|
return (
|
|
506
540
|
new_context.id,
|
|
507
541
|
"caret_new",
|
|
508
|
-
format_context_created(new_context)
|
|
542
|
+
format_context_created(new_context),
|
|
543
|
+
None
|
|
509
544
|
)
|
|
510
545
|
except Exception as e:
|
|
511
546
|
eprint(f"[context_enforcer] Failed to create context: {e}")
|
|
@@ -542,7 +577,8 @@ def _handle_caret_command(
|
|
|
542
577
|
return (
|
|
543
578
|
new_context.id,
|
|
544
579
|
"caret_new",
|
|
545
|
-
output
|
|
580
|
+
output,
|
|
581
|
+
None
|
|
546
582
|
)
|
|
547
583
|
except Exception as e:
|
|
548
584
|
eprint(f"[context_enforcer] Failed to create context: {e}")
|
|
@@ -552,11 +588,24 @@ def _handle_caret_command(
|
|
|
552
588
|
if cmd.select:
|
|
553
589
|
selected_ctx = contexts[cmd.select - 1] # 1-indexed
|
|
554
590
|
eprint(f"[context_enforcer] Caret-selected context: {selected_ctx.id}")
|
|
555
|
-
|
|
591
|
+
|
|
592
|
+
# Auto-transition pending_implementation to implementing
|
|
593
|
+
mode = selected_ctx.in_flight.mode if selected_ctx.in_flight else "none"
|
|
594
|
+
if mode == "pending_implementation":
|
|
595
|
+
update_plan_status(selected_ctx.id, "implementing", project_root=project_root)
|
|
596
|
+
selected_ctx.in_flight.mode = "implementing"
|
|
597
|
+
eprint(f"[context_enforcer] Transitioned {selected_ctx.id} to implementing")
|
|
598
|
+
|
|
599
|
+
output = format_command_feedback(
|
|
600
|
+
ended_contexts,
|
|
601
|
+
selected_ctx,
|
|
602
|
+
cmd.remaining_prompt if cmd.remaining_prompt else None
|
|
603
|
+
)
|
|
556
604
|
return (
|
|
557
605
|
selected_ctx.id,
|
|
558
606
|
"caret_select",
|
|
559
|
-
output
|
|
607
|
+
output,
|
|
608
|
+
cmd.remaining_prompt if cmd.remaining_prompt else None
|
|
560
609
|
)
|
|
561
610
|
|
|
562
611
|
# Only ended contexts, no selection - refresh context list and block
|
|
@@ -588,11 +637,10 @@ def main():
|
|
|
588
637
|
In production, use user_prompt_submit.py as the unified entry point.
|
|
589
638
|
"""
|
|
590
639
|
try:
|
|
591
|
-
|
|
592
|
-
if not
|
|
640
|
+
hook_input = load_hook_input()
|
|
641
|
+
if not hook_input:
|
|
593
642
|
return
|
|
594
643
|
|
|
595
|
-
hook_input = json.loads(input_data)
|
|
596
644
|
user_prompt = hook_input.get("prompt", "")
|
|
597
645
|
if not user_prompt:
|
|
598
646
|
return
|
|
@@ -600,8 +648,10 @@ def main():
|
|
|
600
648
|
project_root = project_dir(hook_input)
|
|
601
649
|
|
|
602
650
|
try:
|
|
603
|
-
context_id, method, output = determine_context(user_prompt, project_root)
|
|
651
|
+
context_id, method, output, remaining_prompt = determine_context(user_prompt, project_root)
|
|
604
652
|
eprint(f"[context_enforcer] Method: {method}, Context: {context_id}")
|
|
653
|
+
if remaining_prompt:
|
|
654
|
+
eprint(f"[context_enforcer] Remaining prompt: {remaining_prompt[:50]}...")
|
|
605
655
|
|
|
606
656
|
if output:
|
|
607
657
|
print(output)
|
|
@@ -55,6 +55,7 @@ SCRIPT_DIR = Path(__file__).resolve().parent
|
|
|
55
55
|
SHARED_LIB = SCRIPT_DIR.parent / "lib"
|
|
56
56
|
sys.path.insert(0, str(SHARED_LIB.parent))
|
|
57
57
|
|
|
58
|
+
from lib.base.hook_utils import load_hook_input
|
|
58
59
|
from lib.base.utils import eprint, project_dir
|
|
59
60
|
from lib.context.context_manager import (
|
|
60
61
|
get_all_contexts,
|
|
@@ -301,15 +302,10 @@ def main():
|
|
|
301
302
|
and prints system reminder if context is low.
|
|
302
303
|
"""
|
|
303
304
|
try:
|
|
304
|
-
# Read hook input
|
|
305
|
-
|
|
305
|
+
# Read hook input using shared utility
|
|
306
|
+
hook_input = load_hook_input()
|
|
306
307
|
|
|
307
|
-
if not
|
|
308
|
-
return
|
|
309
|
-
|
|
310
|
-
try:
|
|
311
|
-
hook_input = json.loads(input_data)
|
|
312
|
-
except json.JSONDecodeError:
|
|
308
|
+
if not hook_input:
|
|
313
309
|
return
|
|
314
310
|
|
|
315
311
|
# Always check for mode transitions on implementation tools
|
|
@@ -28,6 +28,7 @@ SCRIPT_DIR = Path(__file__).resolve().parent
|
|
|
28
28
|
SHARED_LIB = SCRIPT_DIR.parent / "lib"
|
|
29
29
|
sys.path.insert(0, str(SHARED_LIB.parent))
|
|
30
30
|
|
|
31
|
+
from lib.base.hook_utils import load_hook_input
|
|
31
32
|
from lib.base.utils import eprint, project_dir
|
|
32
33
|
from lib.base.constants import (
|
|
33
34
|
get_context_plans_dir,
|
|
@@ -167,17 +168,10 @@ def main():
|
|
|
167
168
|
and outputs file suggestions as JSON array.
|
|
168
169
|
"""
|
|
169
170
|
try:
|
|
170
|
-
# Read hook input
|
|
171
|
-
|
|
171
|
+
# Read hook input using shared utility
|
|
172
|
+
hook_input = load_hook_input()
|
|
172
173
|
|
|
173
|
-
if not
|
|
174
|
-
print("[]")
|
|
175
|
-
return
|
|
176
|
-
|
|
177
|
-
try:
|
|
178
|
-
hook_input = json.loads(input_data)
|
|
179
|
-
except json.JSONDecodeError:
|
|
180
|
-
eprint("[file-suggestion] Failed to parse input JSON")
|
|
174
|
+
if not hook_input:
|
|
181
175
|
print("[]")
|
|
182
176
|
return
|
|
183
177
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""SessionStart hook for mode transitions after /clear.
|
|
3
|
+
|
|
4
|
+
This hook fires when a new session starts. It handles the critical transition
|
|
5
|
+
from `pending_implementation` to `implementing` when a session starts after
|
|
6
|
+
/clear with bypass permissions.
|
|
7
|
+
|
|
8
|
+
The flow is:
|
|
9
|
+
1. User approves plan (ExitPlanMode) -> mode = pending_implementation
|
|
10
|
+
2. User clicks "yes and clear and bypass permissions"
|
|
11
|
+
3. SessionStart fires with source="clear" and permission_mode="bypassPermissions"
|
|
12
|
+
4. This hook transitions mode to "implementing"
|
|
13
|
+
|
|
14
|
+
Without this hook, the mode stays stuck at pending_implementation because
|
|
15
|
+
UserPromptSubmit may not receive the correct permission_mode after /clear.
|
|
16
|
+
|
|
17
|
+
Hook input:
|
|
18
|
+
{
|
|
19
|
+
"hook_event_name": "SessionStart",
|
|
20
|
+
"session_id": "abc123",
|
|
21
|
+
"source": "clear", # or "startup", "resume", "compact"
|
|
22
|
+
"permission_mode": "bypassPermissions",
|
|
23
|
+
"model": "...",
|
|
24
|
+
...
|
|
25
|
+
}
|
|
26
|
+
"""
|
|
27
|
+
import sys
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
# Add parent directories to path for imports
|
|
31
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
32
|
+
SHARED_LIB = SCRIPT_DIR.parent / "lib"
|
|
33
|
+
sys.path.insert(0, str(SHARED_LIB.parent))
|
|
34
|
+
|
|
35
|
+
from lib.base.hook_utils import load_hook_input
|
|
36
|
+
from lib.base.utils import eprint, project_dir
|
|
37
|
+
from lib.context.context_manager import (
|
|
38
|
+
get_all_in_flight_contexts,
|
|
39
|
+
update_plan_status,
|
|
40
|
+
update_context_session_id,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def main():
|
|
45
|
+
"""
|
|
46
|
+
Handle mode transitions on session start.
|
|
47
|
+
|
|
48
|
+
When source is "clear" and permission_mode is "bypassPermissions" or "acceptEdits",
|
|
49
|
+
transition any pending_implementation context to implementing.
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
# Read hook input using shared utility
|
|
53
|
+
hook_input = load_hook_input()
|
|
54
|
+
|
|
55
|
+
if not hook_input:
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
source = hook_input.get("source", "unknown")
|
|
59
|
+
permission_mode = hook_input.get("permission_mode", "default")
|
|
60
|
+
session_id = hook_input.get("session_id", "unknown")
|
|
61
|
+
project_root = project_dir(hook_input)
|
|
62
|
+
|
|
63
|
+
eprint(f"[session_start] source={source}, permission_mode={permission_mode}, session={session_id[:8]}...")
|
|
64
|
+
|
|
65
|
+
# Only handle /clear with bypass/accept permissions
|
|
66
|
+
if source != "clear":
|
|
67
|
+
eprint(f"[session_start] Skipping: source is '{source}', not 'clear'")
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
if permission_mode == "plan":
|
|
71
|
+
eprint(f"[session_start] Skipping: permission_mode is 'plan' (in planning mode)")
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
# Find contexts in pending_implementation mode
|
|
75
|
+
in_flight_contexts = get_all_in_flight_contexts(project_root)
|
|
76
|
+
pending_contexts = [
|
|
77
|
+
ctx for ctx in in_flight_contexts
|
|
78
|
+
if ctx.in_flight and ctx.in_flight.mode == "pending_implementation"
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
if not pending_contexts:
|
|
82
|
+
eprint("[session_start] No pending_implementation contexts found")
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
# Transition each pending context to implementing
|
|
86
|
+
for ctx in pending_contexts:
|
|
87
|
+
eprint(f"[session_start] Transitioning {ctx.id} from pending_implementation to implementing")
|
|
88
|
+
update_plan_status(ctx.id, "implementing", project_root=project_root)
|
|
89
|
+
|
|
90
|
+
# Also bind this session to the context
|
|
91
|
+
update_context_session_id(ctx.id, session_id, project_root)
|
|
92
|
+
eprint(f"[session_start] Bound session {session_id[:8]}... to context {ctx.id}")
|
|
93
|
+
|
|
94
|
+
eprint(f"[session_start] Transitioned {len(pending_contexts)} context(s) to implementing")
|
|
95
|
+
|
|
96
|
+
except Exception as e:
|
|
97
|
+
eprint(f"[session_start] ERROR: {e}")
|
|
98
|
+
import traceback
|
|
99
|
+
eprint(traceback.format_exc())
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
main()
|