aiwcli 0.9.8 → 0.10.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/bin/run.js +5 -2
- package/dist/lib/claude-settings-types.d.ts +2 -0
- package/dist/templates/CLAUDE.md +3 -3
- package/dist/templates/_shared/.claude/settings.json +4 -0
- package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- 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__/pre_compact.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_end.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_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 +87 -178
- package/dist/templates/_shared/hooks/context_monitor.py +104 -247
- package/dist/templates/_shared/hooks/file-suggestion.py +26 -23
- package/dist/templates/_shared/hooks/pre_compact.py +47 -32
- package/dist/templates/_shared/hooks/session_end.py +103 -60
- package/dist/templates/_shared/hooks/session_start.py +110 -81
- package/dist/templates/_shared/hooks/task_create_capture.py +26 -50
- package/dist/templates/_shared/hooks/task_update_capture.py +42 -115
- package/dist/templates/_shared/hooks/user_prompt_submit.py +61 -61
- package/dist/templates/_shared/lib/base/__init__.py +16 -0
- package/dist/templates/_shared/lib/base/__pycache__/__init__.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__/logger.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 +199 -11
- package/dist/templates/_shared/lib/base/inference.py +121 -0
- package/dist/templates/_shared/lib/base/logger.py +291 -0
- package/dist/templates/_shared/lib/base/utils.py +42 -9
- package/dist/templates/_shared/lib/context/__init__.py +72 -80
- package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_formatter.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__/context_selector.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_store.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_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/context_formatter.py +316 -0
- package/dist/templates/_shared/lib/context/context_selector.py +491 -0
- package/dist/templates/_shared/lib/context/context_store.py +636 -0
- package/dist/templates/_shared/lib/context/plan_manager.py +204 -0
- package/dist/templates/_shared/lib/context/task_tracker.py +188 -0
- package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/document_generator.py +14 -40
- package/dist/templates/_shared/lib/templates/README.md +5 -13
- package/dist/templates/_shared/lib/templates/__init__.py +2 -6
- 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__/plan_context.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/plan_context.py +1 -38
- package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/save_handoff.py +39 -19
- package/dist/templates/_shared/scripts/status_line.py +701 -0
- package/dist/templates/_shared/workflows/handoff.md +9 -3
- package/dist/templates/cc-native/.claude/settings.json +41 -8
- package/dist/templates/cc-native/CC-NATIVE-README.md +25 -28
- package/dist/templates/cc-native/MIGRATION.md +1 -1
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +14 -39
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +49 -21
- 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__/mark_questions_asked.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.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/add_plan_context.py +57 -55
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +163 -131
- package/dist/templates/cc-native/_cc-native/hooks/plan_accepted.py +127 -0
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +81 -0
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +26 -25
- package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +6 -4
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -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/__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/debug.py +37 -22
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +34 -29
- 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 +26 -21
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +12 -7
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +12 -7
- package/dist/templates/cc-native/_cc-native/lib/state.py +31 -16
- package/dist/templates/cc-native/_cc-native/lib/utils.py +207 -40
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +1 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/templates/_shared/hooks/context_enforcer.py +0 -625
- package/dist/templates/_shared/hooks/task_create_atomicity.py +0 -177
- package/dist/templates/_shared/lib/context/auto_state.py +0 -167
- package/dist/templates/_shared/lib/context/cache.py +0 -444
- package/dist/templates/_shared/lib/context/context_extractor.py +0 -115
- package/dist/templates/_shared/lib/context/context_manager.py +0 -1057
- package/dist/templates/_shared/lib/context/discovery.py +0 -554
- package/dist/templates/_shared/lib/context/event_log.py +0 -316
- package/dist/templates/_shared/lib/context/plan_archive.py +0 -101
- package/dist/templates/_shared/lib/context/task_sync.py +0 -407
- package/dist/templates/_shared/lib/templates/persona_questions.py +0 -113
- 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__/test_permission_request.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/async_archive.py +0 -68
- package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +0 -98
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"""PostToolUse hook - captures TaskCreate operations for persistence.
|
|
3
3
|
|
|
4
4
|
This hook runs after Claude uses the TaskCreate tool and automatically
|
|
5
|
-
records the task
|
|
5
|
+
records the task in the context's state.json.
|
|
6
6
|
|
|
7
7
|
Hook input (from Claude Code):
|
|
8
8
|
{
|
|
@@ -38,95 +38,71 @@ from lib.base.hook_utils import (
|
|
|
38
38
|
check_skip_persistence,
|
|
39
39
|
safe_hook_main,
|
|
40
40
|
run_hook,
|
|
41
|
+
log_debug,
|
|
42
|
+
log_info,
|
|
43
|
+
log_warn,
|
|
44
|
+
log_error,
|
|
41
45
|
)
|
|
42
|
-
from lib.base.utils import
|
|
43
|
-
from lib.context.
|
|
44
|
-
from lib.context.
|
|
46
|
+
from lib.base.utils import project_dir
|
|
47
|
+
from lib.context.context_store import get_context_by_session_id
|
|
48
|
+
from lib.context.task_tracker import add_task, generate_next_task_id
|
|
45
49
|
|
|
46
50
|
|
|
47
51
|
@safe_hook_main("task_create_capture")
|
|
48
52
|
def main() -> int:
|
|
49
|
-
"""
|
|
50
|
-
Main hook entry point.
|
|
51
|
-
|
|
52
|
-
Returns:
|
|
53
|
-
0 on success, non-zero on failure (but hook is non-blocking)
|
|
54
|
-
"""
|
|
55
|
-
# Parse hook input
|
|
53
|
+
"""Main hook entry point."""
|
|
56
54
|
payload = load_hook_input()
|
|
57
55
|
if not payload:
|
|
58
56
|
return 0
|
|
59
57
|
|
|
60
|
-
# Validate hook type and tool name
|
|
61
58
|
if not validate_hook_event(payload, "PostToolUse", "TaskCreate"):
|
|
62
59
|
return 0
|
|
63
60
|
|
|
64
|
-
# Extract tool input
|
|
65
61
|
tool_input = get_tool_input(payload)
|
|
66
62
|
if not tool_input:
|
|
67
|
-
|
|
63
|
+
log_warn("task_create_capture", "Invalid tool_input: not a dict")
|
|
68
64
|
return 0
|
|
69
65
|
|
|
70
|
-
# Check for skip_persistence flag
|
|
71
66
|
if check_skip_persistence(payload, "task_create_capture"):
|
|
72
67
|
return 0
|
|
73
68
|
|
|
74
|
-
# Extract tool response (contains task ID assigned by Claude)
|
|
75
|
-
tool_response = payload.get("tool_response", {})
|
|
76
|
-
if not isinstance(tool_response, dict):
|
|
77
|
-
eprint("[task_create_capture] Invalid tool_response: not a dict")
|
|
78
|
-
return 0
|
|
79
|
-
|
|
80
|
-
# Get project root and session ID
|
|
81
69
|
project_root = project_dir(payload)
|
|
82
|
-
session_id = payload.get("session_id")
|
|
70
|
+
session_id = payload.get("session_id", "")
|
|
83
71
|
|
|
84
|
-
#
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
session_id=session_id,
|
|
89
|
-
hook_name="task_create_capture",
|
|
90
|
-
check_persistent_id=True # TaskCreate uses persistent_id for context hints
|
|
91
|
-
)
|
|
92
|
-
if not context_id:
|
|
93
|
-
eprint("[task_create_capture] No context available - skipping persistence")
|
|
94
|
-
eprint("[task_create_capture] Task will be ephemeral until context is created")
|
|
72
|
+
# Find context by session ID
|
|
73
|
+
state = get_context_by_session_id(session_id, project_root)
|
|
74
|
+
if not state:
|
|
75
|
+
log_debug("task_create_capture", "No context available - skipping persistence")
|
|
95
76
|
return 0
|
|
96
77
|
|
|
78
|
+
context_id = state.id
|
|
79
|
+
|
|
97
80
|
# Extract task data
|
|
98
81
|
subject = tool_input.get("subject", "")
|
|
99
82
|
if not subject:
|
|
100
|
-
|
|
83
|
+
log_warn("task_create_capture", "Missing required field: subject")
|
|
101
84
|
return 0
|
|
102
85
|
|
|
103
86
|
description = tool_input.get("description", "")
|
|
104
87
|
active_form = tool_input.get("activeForm", "")
|
|
105
88
|
|
|
106
|
-
#
|
|
107
|
-
|
|
108
|
-
# We need a persistent ID that survives sessions
|
|
109
|
-
persistent_task_id = generate_next_task_id(context_id, project_root)
|
|
110
|
-
|
|
111
|
-
# Record the task creation event
|
|
112
|
-
success = record_task_created(
|
|
89
|
+
# Add task to state.json
|
|
90
|
+
task = add_task(
|
|
113
91
|
context_id=context_id,
|
|
114
|
-
task_id=persistent_task_id,
|
|
115
92
|
subject=subject,
|
|
116
93
|
description=description,
|
|
117
94
|
active_form=active_form,
|
|
118
|
-
session_id=session_id
|
|
119
|
-
project_root=project_root
|
|
95
|
+
session_id=session_id,
|
|
96
|
+
project_root=project_root,
|
|
120
97
|
)
|
|
121
98
|
|
|
122
|
-
if
|
|
123
|
-
|
|
99
|
+
if task:
|
|
100
|
+
log_info("task_create_capture", f"Recorded task: {task['id']} in {context_id}")
|
|
124
101
|
else:
|
|
125
|
-
|
|
102
|
+
log_error("task_create_capture", f"Failed to add task in {context_id}")
|
|
126
103
|
|
|
127
|
-
# Silent success (no stdout output)
|
|
128
104
|
return 0
|
|
129
105
|
|
|
130
106
|
|
|
131
107
|
if __name__ == "__main__":
|
|
132
|
-
run_hook(main)
|
|
108
|
+
run_hook(main, "task_create_capture")
|
|
@@ -2,13 +2,7 @@
|
|
|
2
2
|
"""PostToolUse hook - captures TaskUpdate operations for persistence.
|
|
3
3
|
|
|
4
4
|
This hook runs after Claude uses the TaskUpdate tool and automatically
|
|
5
|
-
records the
|
|
6
|
-
status change.
|
|
7
|
-
|
|
8
|
-
Status mappings:
|
|
9
|
-
- status: "in_progress" -> record_task_started()
|
|
10
|
-
- status: "completed" -> record_task_completed()
|
|
11
|
-
- blockedBy added -> record_task_blocked()
|
|
5
|
+
records the update in the context's state.json.
|
|
12
6
|
|
|
13
7
|
Hook input (from Claude Code):
|
|
14
8
|
{
|
|
@@ -46,173 +40,106 @@ from lib.base.hook_utils import (
|
|
|
46
40
|
check_skip_persistence,
|
|
47
41
|
safe_hook_main,
|
|
48
42
|
run_hook,
|
|
43
|
+
log_debug,
|
|
44
|
+
log_info,
|
|
45
|
+
log_warn,
|
|
46
|
+
log_error,
|
|
49
47
|
)
|
|
50
|
-
from lib.base.utils import
|
|
51
|
-
from lib.context.
|
|
52
|
-
from lib.context.
|
|
53
|
-
record_task_started,
|
|
54
|
-
record_task_completed,
|
|
55
|
-
record_task_blocked,
|
|
56
|
-
record_task_deleted,
|
|
57
|
-
)
|
|
48
|
+
from lib.base.utils import project_dir
|
|
49
|
+
from lib.context.context_store import get_context_by_session_id
|
|
50
|
+
from lib.context.task_tracker import update_task, delete_task
|
|
58
51
|
|
|
59
52
|
|
|
60
53
|
def get_persistent_task_id(
|
|
61
54
|
claude_task_id: str,
|
|
62
55
|
tool_input: Dict[str, Any]
|
|
63
56
|
) -> str:
|
|
64
|
-
"""
|
|
65
|
-
Convert Claude's ephemeral task ID to persistent task ID.
|
|
66
|
-
|
|
67
|
-
If metadata.persistent_id exists, use that.
|
|
68
|
-
Otherwise, assume format "aiw-{claude_task_id}".
|
|
69
|
-
|
|
70
|
-
Args:
|
|
71
|
-
claude_task_id: Task ID from Claude (e.g., "1", "2")
|
|
72
|
-
tool_input: Tool input dict
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
Persistent task ID (e.g., "aiw-1")
|
|
76
|
-
"""
|
|
57
|
+
"""Convert Claude's ephemeral task ID to persistent task ID."""
|
|
77
58
|
metadata = tool_input.get("metadata", {})
|
|
78
59
|
if isinstance(metadata, dict):
|
|
79
60
|
persistent_id = metadata.get("persistent_id")
|
|
80
61
|
if persistent_id:
|
|
81
62
|
return persistent_id
|
|
82
|
-
|
|
83
|
-
# Default: aiw-{id}
|
|
84
63
|
return f"aiw-{claude_task_id}"
|
|
85
64
|
|
|
86
65
|
|
|
87
66
|
@safe_hook_main("task_update_capture")
|
|
88
67
|
def main() -> int:
|
|
89
|
-
"""
|
|
90
|
-
Main hook entry point.
|
|
91
|
-
|
|
92
|
-
Returns:
|
|
93
|
-
0 on success, non-zero on failure (but hook is non-blocking)
|
|
94
|
-
"""
|
|
95
|
-
# Parse hook input
|
|
68
|
+
"""Main hook entry point."""
|
|
96
69
|
payload = load_hook_input()
|
|
97
70
|
if not payload:
|
|
98
71
|
return 0
|
|
99
72
|
|
|
100
|
-
# Validate hook type and tool name
|
|
101
73
|
if not validate_hook_event(payload, "PostToolUse", "TaskUpdate"):
|
|
102
74
|
return 0
|
|
103
75
|
|
|
104
|
-
# Extract tool input
|
|
105
76
|
tool_input = get_tool_input(payload)
|
|
106
77
|
if not tool_input:
|
|
107
|
-
|
|
78
|
+
log_warn("task_update_capture", "Invalid tool_input: not a dict")
|
|
108
79
|
return 0
|
|
109
80
|
|
|
110
|
-
# Check for skip_persistence flag
|
|
111
81
|
if check_skip_persistence(payload, "task_update_capture"):
|
|
112
82
|
return 0
|
|
113
83
|
|
|
114
|
-
# Get project root and session ID
|
|
115
84
|
project_root = project_dir(payload)
|
|
116
|
-
session_id = payload.get("session_id")
|
|
117
|
-
|
|
118
|
-
#
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
session_id=session_id,
|
|
123
|
-
hook_name="task_update_capture"
|
|
124
|
-
)
|
|
125
|
-
if not context_id:
|
|
126
|
-
eprint("[task_update_capture] No context available - skipping persistence")
|
|
85
|
+
session_id = payload.get("session_id", "")
|
|
86
|
+
|
|
87
|
+
# Find context by session ID
|
|
88
|
+
state = get_context_by_session_id(session_id, project_root)
|
|
89
|
+
if not state:
|
|
90
|
+
log_debug("task_update_capture", "No context available - skipping persistence")
|
|
127
91
|
return 0
|
|
128
92
|
|
|
93
|
+
context_id = state.id
|
|
94
|
+
|
|
129
95
|
# Extract task ID
|
|
130
96
|
claude_task_id = tool_input.get("taskId")
|
|
131
97
|
if not claude_task_id:
|
|
132
|
-
|
|
98
|
+
log_warn("task_update_capture", "Missing required field: taskId")
|
|
133
99
|
return 0
|
|
134
100
|
|
|
135
|
-
# Get persistent task ID
|
|
136
101
|
persistent_task_id = get_persistent_task_id(claude_task_id, tool_input)
|
|
137
102
|
|
|
138
|
-
# Check for status change
|
|
139
103
|
status = tool_input.get("status")
|
|
140
104
|
metadata = tool_input.get("metadata", {})
|
|
141
|
-
add_blocked_by = tool_input.get("addBlockedBy", [])
|
|
142
105
|
|
|
143
|
-
# Handle different update types
|
|
144
106
|
events_recorded = []
|
|
145
107
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if success:
|
|
155
|
-
events_recorded.append("task_started")
|
|
156
|
-
|
|
157
|
-
# Status: completed
|
|
158
|
-
elif status == "completed":
|
|
159
|
-
# Extract rich completion context from metadata
|
|
108
|
+
if status == "deleted":
|
|
109
|
+
if delete_task(context_id, persistent_task_id, project_root):
|
|
110
|
+
events_recorded.append("task_deleted")
|
|
111
|
+
elif status:
|
|
112
|
+
# Extract completion metadata
|
|
113
|
+
evidence = ""
|
|
114
|
+
work_summary = ""
|
|
115
|
+
files_changed = None
|
|
160
116
|
if isinstance(metadata, dict):
|
|
161
|
-
evidence = metadata.get("evidence", "
|
|
117
|
+
evidence = metadata.get("evidence", "")
|
|
162
118
|
work_summary = metadata.get("work_summary", "")
|
|
163
|
-
files_changed = metadata.get("files_changed"
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
files_changed = []
|
|
169
|
-
commit_ref = ""
|
|
170
|
-
|
|
171
|
-
success = record_task_completed(
|
|
119
|
+
files_changed = metadata.get("files_changed")
|
|
120
|
+
if files_changed and not isinstance(files_changed, list):
|
|
121
|
+
files_changed = None
|
|
122
|
+
|
|
123
|
+
success = update_task(
|
|
172
124
|
context_id=context_id,
|
|
173
125
|
task_id=persistent_task_id,
|
|
126
|
+
status=status,
|
|
174
127
|
evidence=evidence,
|
|
175
128
|
work_summary=work_summary,
|
|
176
|
-
files_changed=files_changed
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
project_root=project_root
|
|
180
|
-
)
|
|
181
|
-
if success:
|
|
182
|
-
events_recorded.append("task_completed")
|
|
183
|
-
|
|
184
|
-
# Status: deleted
|
|
185
|
-
elif status == "deleted":
|
|
186
|
-
success = record_task_deleted(
|
|
187
|
-
context_id=context_id,
|
|
188
|
-
task_id=persistent_task_id,
|
|
189
|
-
session_id=session_id or "",
|
|
190
|
-
project_root=project_root
|
|
191
|
-
)
|
|
192
|
-
if success:
|
|
193
|
-
events_recorded.append("task_deleted")
|
|
194
|
-
|
|
195
|
-
# Blocked by tasks
|
|
196
|
-
if add_blocked_by and isinstance(add_blocked_by, list) and len(add_blocked_by) > 0:
|
|
197
|
-
blocked_reason = f"Blocked by tasks: {', '.join(add_blocked_by)}"
|
|
198
|
-
success = record_task_blocked(
|
|
199
|
-
context_id=context_id,
|
|
200
|
-
task_id=persistent_task_id,
|
|
201
|
-
reason=blocked_reason,
|
|
202
|
-
session_id=session_id or "",
|
|
203
|
-
project_root=project_root
|
|
129
|
+
files_changed=files_changed,
|
|
130
|
+
session_id=session_id,
|
|
131
|
+
project_root=project_root,
|
|
204
132
|
)
|
|
205
133
|
if success:
|
|
206
|
-
events_recorded.append("
|
|
134
|
+
events_recorded.append(f"task_{status}")
|
|
207
135
|
|
|
208
136
|
if events_recorded:
|
|
209
|
-
|
|
137
|
+
log_info("task_update_capture", f"Recorded {', '.join(events_recorded)} for {persistent_task_id} in {context_id}")
|
|
210
138
|
else:
|
|
211
|
-
|
|
139
|
+
log_debug("task_update_capture", f"No relevant changes for {persistent_task_id}")
|
|
212
140
|
|
|
213
|
-
# Silent success (no stdout output)
|
|
214
141
|
return 0
|
|
215
142
|
|
|
216
143
|
|
|
217
144
|
if __name__ == "__main__":
|
|
218
|
-
run_hook(main)
|
|
145
|
+
run_hook(main, "task_update_capture")
|
|
@@ -28,72 +28,56 @@ 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
|
|
32
|
-
from lib.base.utils import
|
|
33
|
-
from lib.context.
|
|
34
|
-
update_context_session_id,
|
|
35
|
-
update_plan_status,
|
|
31
|
+
from lib.base.hook_utils import load_hook_input, log_debug, log_info, log_warn, log_error, log_diagnostic
|
|
32
|
+
from lib.base.utils import project_dir
|
|
33
|
+
from lib.context.context_store import (
|
|
36
34
|
get_context,
|
|
37
35
|
get_context_by_session_id,
|
|
36
|
+
bind_session,
|
|
37
|
+
maybe_activate,
|
|
38
|
+
save_state,
|
|
38
39
|
)
|
|
39
|
-
|
|
40
|
-
# Import the enforcement module
|
|
41
|
-
from hooks.context_enforcer import determine_context, BlockRequest
|
|
40
|
+
from lib.context.context_selector import determine_context, BlockRequest
|
|
42
41
|
|
|
43
42
|
|
|
44
43
|
def format_claudemd_reminder() -> str:
|
|
45
44
|
"""Generate reminder to update directory-specific CLAUDE.md files."""
|
|
46
45
|
return """
|
|
47
|
-
## CLAUDE.md
|
|
48
|
-
|
|
49
|
-
**Placement rule:** CLAUDE.md files cascade — subdirectories inherit from parents. Default to updating the nearest existing CLAUDE.md. Only create a new one at a semantic boundary where responsibility genuinely shifts (package roots with their own manifest, technology boundaries, domain boundaries, integration points).
|
|
50
|
-
|
|
51
|
-
**Before creating a new CLAUDE.md, ask:** "Do the rules here actually differ from the parent?" If not, update the parent instead.
|
|
46
|
+
## CLAUDE.md \u2014 Persistent Memory
|
|
52
47
|
|
|
53
|
-
|
|
48
|
+
CLAUDE.md files are this project's persistent memory across sessions. **After making a significant decision or learning something non-obvious during this task, write it to the nearest CLAUDE.md.** If you don't write it, it's lost when this session ends.
|
|
54
49
|
|
|
55
|
-
**What to
|
|
56
|
-
- Architectural choices and
|
|
50
|
+
**What to write:**
|
|
51
|
+
- Architectural choices and why alternatives were rejected
|
|
57
52
|
- Non-obvious constraints (what breaks if this changes)
|
|
58
53
|
- Workarounds with context on the underlying issue
|
|
59
|
-
-
|
|
54
|
+
- Patterns that prevent future mistakes
|
|
55
|
+
|
|
56
|
+
**Placement:** CLAUDE.md files cascade \u2014 subdirectories inherit from parents. Update the nearest existing one. Only create a new one at a genuine semantic boundary (package root, technology boundary, domain boundary).
|
|
60
57
|
|
|
61
58
|
**Format:**
|
|
62
59
|
|
|
63
60
|
```markdown
|
|
64
61
|
## [Topic]
|
|
65
62
|
**Decision:** [What was decided]
|
|
66
|
-
**Rationale:** [Why
|
|
63
|
+
**Rationale:** [Why \u2014 the non-obvious part]
|
|
67
64
|
```
|
|
65
|
+
|
|
66
|
+
**When in doubt, write it.** A lean entry is better than a lost decision.
|
|
68
67
|
"""
|
|
69
68
|
|
|
70
69
|
|
|
71
70
|
def _update_in_flight_status(context_id: str, hook_input: dict, project_root: Path) -> None:
|
|
72
71
|
"""
|
|
73
|
-
Update context
|
|
72
|
+
Update context mode based on permission mode.
|
|
74
73
|
|
|
75
|
-
-
|
|
76
|
-
-
|
|
74
|
+
- permission_mode == "plan": no-op (planning is runtime-only, not persisted)
|
|
75
|
+
- permission_mode != "plan" and mode == "idle": set to "active"
|
|
76
|
+
- permission_mode != "plan" and mode == "has_plan": set to "active" (plan was accepted)
|
|
77
77
|
"""
|
|
78
|
-
context = get_context(context_id, project_root)
|
|
79
|
-
if not context or not context.in_flight:
|
|
80
|
-
return
|
|
81
|
-
|
|
82
|
-
current_mode = context.in_flight.mode
|
|
83
78
|
permission_mode = hook_input.get("permission_mode", "default")
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
# Set status based on permission mode
|
|
87
|
-
if permission_mode == "plan":
|
|
88
|
-
if current_mode != "planning":
|
|
89
|
-
update_plan_status(context_id, "planning", project_root=project_root)
|
|
90
|
-
eprint(f"[user_prompt_submit] Set status to 'planning'")
|
|
91
|
-
elif permission_mode != "plan":
|
|
92
|
-
# Any non-plan permission mode transitions pending/planning to implementing
|
|
93
|
-
# This includes "default" (after /clear) and "acceptEdits"/"bypassPermissions"
|
|
94
|
-
if current_mode in ["pending_implementation", "planning", "none"]:
|
|
95
|
-
update_plan_status(context_id, "implementing", project_root=project_root)
|
|
96
|
-
eprint(f"[user_prompt_submit] Set status to 'implementing' (permission_mode={permission_mode})")
|
|
79
|
+
log_debug("user_prompt_submit", f"context_id={context_id}, permission_mode={permission_mode}")
|
|
80
|
+
maybe_activate(context_id, permission_mode, project_root=project_root, caller="user_prompt_submit")
|
|
97
81
|
|
|
98
82
|
|
|
99
83
|
def main():
|
|
@@ -104,70 +88,86 @@ def main():
|
|
|
104
88
|
Uses session_id to detect first prompt vs subsequent prompts.
|
|
105
89
|
"""
|
|
106
90
|
try:
|
|
107
|
-
# Read hook input using shared utility
|
|
108
91
|
hook_input = load_hook_input()
|
|
109
|
-
|
|
110
92
|
if not hook_input:
|
|
111
93
|
return
|
|
112
94
|
|
|
113
|
-
# Get user prompt and project root
|
|
114
95
|
user_prompt = hook_input.get("prompt", "")
|
|
115
96
|
project_root = project_dir(hook_input)
|
|
116
97
|
session_id = hook_input.get("session_id", "unknown")
|
|
117
98
|
|
|
99
|
+
log_diagnostic("user_prompt_submit", "receive", f"session={session_id[:8]}, prompt_len={len(user_prompt)}",
|
|
100
|
+
inputs={"session_id": session_id[:12], "prompt_length": len(user_prompt)})
|
|
101
|
+
|
|
118
102
|
outputs: List[str] = []
|
|
119
|
-
active_context_id = None
|
|
103
|
+
active_context_id = None
|
|
120
104
|
|
|
121
105
|
# First-prompt detection: check if session_id is already bound to a context
|
|
122
106
|
existing_context = get_context_by_session_id(session_id, project_root)
|
|
123
107
|
|
|
124
108
|
if existing_context:
|
|
125
109
|
# NOT first prompt - session already bound to context
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
110
|
+
log_debug("user_prompt_submit", f"Session {session_id[:8]}... already bound to {existing_context.id}")
|
|
111
|
+
log_diagnostic("user_prompt_submit", "decide", f"Session already bound to {existing_context.id}",
|
|
112
|
+
decision="session_match", reasoning="session_id found in existing context",
|
|
113
|
+
inputs={"context_id": existing_context.id, "mode": existing_context.mode})
|
|
129
114
|
_update_in_flight_status(existing_context.id, hook_input, project_root)
|
|
130
115
|
active_context_id = existing_context.id
|
|
131
116
|
elif user_prompt:
|
|
132
117
|
# FIRST prompt - need context detection
|
|
133
118
|
try:
|
|
134
|
-
context_id, method, context_output = determine_context(user_prompt,
|
|
135
|
-
|
|
119
|
+
context_id, method, context_output = determine_context(user_prompt, session_id, project_root)
|
|
120
|
+
log_info("user_prompt_submit", f"Context: {method} -> {context_id}")
|
|
121
|
+
log_diagnostic("user_prompt_submit", "decide", f"Context detected via {method}: {context_id}",
|
|
122
|
+
decision=method, reasoning=f"determine_context returned method={method}",
|
|
123
|
+
inputs={"context_id": context_id, "has_output": bool(context_output)})
|
|
136
124
|
|
|
137
125
|
if context_id:
|
|
138
126
|
# Bind session to context
|
|
139
|
-
|
|
140
|
-
|
|
127
|
+
bind_session(context_id, session_id, project_root)
|
|
128
|
+
log_info("user_prompt_submit", f"Bound session {session_id[:8]}... to context '{context_id}'")
|
|
141
129
|
|
|
142
|
-
# Update
|
|
130
|
+
# Update mode based on permission mode
|
|
143
131
|
_update_in_flight_status(context_id, hook_input, project_root)
|
|
144
132
|
active_context_id = context_id
|
|
145
133
|
|
|
134
|
+
# Clear handoff_path after it's been injected via context_selector
|
|
135
|
+
try:
|
|
136
|
+
ctx = get_context(context_id, project_root)
|
|
137
|
+
if ctx and ctx.handoff_path:
|
|
138
|
+
ctx.handoff_path = None
|
|
139
|
+
save_state(ctx, project_root)
|
|
140
|
+
log_debug("user_prompt_submit", f"Cleared handoff_path for {context_id}")
|
|
141
|
+
except Exception as e:
|
|
142
|
+
log_warn("user_prompt_submit", f"Failed to clear handoff_path: {e}")
|
|
143
|
+
|
|
146
144
|
if context_output:
|
|
147
145
|
outputs.append(context_output)
|
|
148
146
|
|
|
149
147
|
except BlockRequest as e:
|
|
150
|
-
|
|
151
|
-
# This shows the context picker to the user
|
|
152
|
-
print(e.message, file=sys.stderr)
|
|
148
|
+
log_error("user_prompt_submit", e.message)
|
|
153
149
|
sys.exit(2)
|
|
154
150
|
|
|
155
|
-
# Inject CLAUDE.md reminder when in
|
|
151
|
+
# Inject CLAUDE.md reminder when in active mode
|
|
156
152
|
if active_context_id:
|
|
157
153
|
context = get_context(active_context_id, project_root)
|
|
158
|
-
if context and context.
|
|
154
|
+
if context and context.mode == "active":
|
|
159
155
|
outputs.append(f"<system-reminder>{format_claudemd_reminder()}</system-reminder>")
|
|
160
|
-
|
|
156
|
+
log_debug("user_prompt_submit", "Injected CLAUDE.md reminder (mode=active)")
|
|
157
|
+
|
|
158
|
+
log_diagnostic("user_prompt_submit", "result", f"context={active_context_id}, outputs={len(outputs)}",
|
|
159
|
+
inputs={"active_context_id": active_context_id, "output_count": len(outputs)})
|
|
161
160
|
|
|
162
|
-
# Print output
|
|
163
161
|
if outputs:
|
|
164
162
|
print("\n\n".join(outputs))
|
|
165
163
|
|
|
166
164
|
except Exception as e:
|
|
167
|
-
eprint(f"[user_prompt_submit] ERROR: {e}")
|
|
168
165
|
import traceback
|
|
169
|
-
|
|
166
|
+
tb = traceback.format_exc()
|
|
167
|
+
from lib.base.hook_utils import log_hook_error
|
|
168
|
+
log_hook_error("user_prompt_submit", e, "UserPromptSubmit", traceback_str=tb)
|
|
170
169
|
|
|
171
170
|
|
|
172
171
|
if __name__ == "__main__":
|
|
173
|
-
|
|
172
|
+
from lib.base.hook_utils import run_hook
|
|
173
|
+
run_hook(main, "user_prompt_submit")
|
|
@@ -23,6 +23,15 @@ from .utils import (
|
|
|
23
23
|
sanitize_title,
|
|
24
24
|
generate_context_id,
|
|
25
25
|
)
|
|
26
|
+
from .logger import (
|
|
27
|
+
hook_log,
|
|
28
|
+
log_debug,
|
|
29
|
+
log_info,
|
|
30
|
+
log_warn,
|
|
31
|
+
log_error,
|
|
32
|
+
log_hook_error,
|
|
33
|
+
set_context_path,
|
|
34
|
+
)
|
|
26
35
|
|
|
27
36
|
__all__ = [
|
|
28
37
|
"atomic_write",
|
|
@@ -46,4 +55,11 @@ __all__ = [
|
|
|
46
55
|
"sanitize_filename",
|
|
47
56
|
"sanitize_title",
|
|
48
57
|
"generate_context_id",
|
|
58
|
+
"hook_log",
|
|
59
|
+
"log_debug",
|
|
60
|
+
"log_info",
|
|
61
|
+
"log_warn",
|
|
62
|
+
"log_error",
|
|
63
|
+
"log_hook_error",
|
|
64
|
+
"set_context_path",
|
|
49
65
|
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|