aiwcli 0.9.8 → 0.10.1
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 +114 -60
- package/dist/templates/_shared/hooks/session_start.py +127 -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 +47 -81
- 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 +207 -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 +317 -0
- package/dist/templates/_shared/lib/context/context_selector.py +508 -0
- package/dist/templates/_shared/lib/context/context_store.py +653 -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 +22 -37
- 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 +31 -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 +37 -14
- 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 +54 -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 +76 -89
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +163 -131
- 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,29 @@ 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
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def format_claudemd_reminder() -> str:
|
|
45
|
-
"""Generate reminder to update directory-specific CLAUDE.md files."""
|
|
46
|
-
return """
|
|
47
|
-
## CLAUDE.md Decision Capture
|
|
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.
|
|
52
|
-
|
|
53
|
-
**Keep files lean (progressive disclosure):** Each CLAUDE.md should contain only decisions relevant at that directory level. Verbose files become noise that degrades future task quality — every entry competes for attention in the context window. Capture only non-obvious rationale, not descriptions of what the code does.
|
|
54
|
-
|
|
55
|
-
**What to capture** (only when decisions have non-obvious rationale):
|
|
56
|
-
- Architectural choices and their alternatives
|
|
57
|
-
- Non-obvious constraints (what breaks if this changes)
|
|
58
|
-
- Workarounds with context on the underlying issue
|
|
59
|
-
- Learned patterns that prevent future mistakes
|
|
60
|
-
|
|
61
|
-
**Format:**
|
|
62
|
-
|
|
63
|
-
```markdown
|
|
64
|
-
## [Topic]
|
|
65
|
-
**Decision:** [What was decided]
|
|
66
|
-
**Rationale:** [Why — the non-obvious part]
|
|
67
|
-
```
|
|
68
|
-
"""
|
|
40
|
+
from lib.context.context_selector import determine_context, BlockRequest
|
|
69
41
|
|
|
70
42
|
|
|
71
43
|
def _update_in_flight_status(context_id: str, hook_input: dict, project_root: Path) -> None:
|
|
72
44
|
"""
|
|
73
|
-
Update context
|
|
45
|
+
Update context mode based on permission mode.
|
|
74
46
|
|
|
75
|
-
-
|
|
76
|
-
-
|
|
47
|
+
- permission_mode == "plan": no-op (planning is runtime-only, not persisted)
|
|
48
|
+
- permission_mode != "plan" and mode == "idle": set to "active"
|
|
49
|
+
- permission_mode != "plan" and mode == "has_plan": set to "active" (plan was accepted)
|
|
77
50
|
"""
|
|
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
51
|
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})")
|
|
52
|
+
log_debug("user_prompt_submit", f"context_id={context_id}, permission_mode={permission_mode}")
|
|
53
|
+
maybe_activate(context_id, permission_mode, project_root=project_root, caller="user_prompt_submit")
|
|
97
54
|
|
|
98
55
|
|
|
99
56
|
def main():
|
|
@@ -104,70 +61,79 @@ def main():
|
|
|
104
61
|
Uses session_id to detect first prompt vs subsequent prompts.
|
|
105
62
|
"""
|
|
106
63
|
try:
|
|
107
|
-
# Read hook input using shared utility
|
|
108
64
|
hook_input = load_hook_input()
|
|
109
|
-
|
|
110
65
|
if not hook_input:
|
|
111
66
|
return
|
|
112
67
|
|
|
113
|
-
# Get user prompt and project root
|
|
114
68
|
user_prompt = hook_input.get("prompt", "")
|
|
115
69
|
project_root = project_dir(hook_input)
|
|
116
70
|
session_id = hook_input.get("session_id", "unknown")
|
|
117
71
|
|
|
72
|
+
log_diagnostic("user_prompt_submit", "receive", f"session={session_id[:8]}, prompt_len={len(user_prompt)}",
|
|
73
|
+
inputs={"session_id": session_id[:12], "prompt_length": len(user_prompt)})
|
|
74
|
+
|
|
118
75
|
outputs: List[str] = []
|
|
119
|
-
active_context_id = None
|
|
76
|
+
active_context_id = None
|
|
120
77
|
|
|
121
78
|
# First-prompt detection: check if session_id is already bound to a context
|
|
122
79
|
existing_context = get_context_by_session_id(session_id, project_root)
|
|
123
80
|
|
|
124
81
|
if existing_context:
|
|
125
82
|
# NOT first prompt - session already bound to context
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
83
|
+
log_debug("user_prompt_submit", f"Session {session_id[:8]}... already bound to {existing_context.id}")
|
|
84
|
+
log_diagnostic("user_prompt_submit", "decide", f"Session already bound to {existing_context.id}",
|
|
85
|
+
decision="session_match", reasoning="session_id found in existing context",
|
|
86
|
+
inputs={"context_id": existing_context.id, "mode": existing_context.mode})
|
|
129
87
|
_update_in_flight_status(existing_context.id, hook_input, project_root)
|
|
130
88
|
active_context_id = existing_context.id
|
|
131
89
|
elif user_prompt:
|
|
132
90
|
# FIRST prompt - need context detection
|
|
133
91
|
try:
|
|
134
|
-
context_id, method, context_output = determine_context(user_prompt,
|
|
135
|
-
|
|
92
|
+
context_id, method, context_output = determine_context(user_prompt, session_id, project_root)
|
|
93
|
+
log_info("user_prompt_submit", f"Context: {method} -> {context_id}")
|
|
94
|
+
log_diagnostic("user_prompt_submit", "decide", f"Context detected via {method}: {context_id}",
|
|
95
|
+
decision=method, reasoning=f"determine_context returned method={method}",
|
|
96
|
+
inputs={"context_id": context_id, "has_output": bool(context_output)})
|
|
136
97
|
|
|
137
98
|
if context_id:
|
|
138
99
|
# Bind session to context
|
|
139
|
-
|
|
140
|
-
|
|
100
|
+
bind_session(context_id, session_id, project_root)
|
|
101
|
+
log_info("user_prompt_submit", f"Bound session {session_id[:8]}... to context '{context_id}'")
|
|
141
102
|
|
|
142
|
-
# Update
|
|
103
|
+
# Update mode based on permission mode
|
|
143
104
|
_update_in_flight_status(context_id, hook_input, project_root)
|
|
144
105
|
active_context_id = context_id
|
|
145
106
|
|
|
107
|
+
# Clear handoff_path after it's been injected via context_selector
|
|
108
|
+
try:
|
|
109
|
+
ctx = get_context(context_id, project_root)
|
|
110
|
+
if ctx and ctx.handoff_path:
|
|
111
|
+
ctx.handoff_path = None
|
|
112
|
+
save_state(ctx, project_root)
|
|
113
|
+
log_debug("user_prompt_submit", f"Cleared handoff_path for {context_id}")
|
|
114
|
+
except Exception as e:
|
|
115
|
+
log_warn("user_prompt_submit", f"Failed to clear handoff_path: {e}")
|
|
116
|
+
|
|
146
117
|
if context_output:
|
|
147
118
|
outputs.append(context_output)
|
|
148
119
|
|
|
149
120
|
except BlockRequest as e:
|
|
150
|
-
|
|
151
|
-
# This shows the context picker to the user
|
|
152
|
-
print(e.message, file=sys.stderr)
|
|
121
|
+
log_error("user_prompt_submit", e.message)
|
|
153
122
|
sys.exit(2)
|
|
154
123
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
context = get_context(active_context_id, project_root)
|
|
158
|
-
if context and context.in_flight and context.in_flight.mode == "implementing":
|
|
159
|
-
outputs.append(f"<system-reminder>{format_claudemd_reminder()}</system-reminder>")
|
|
160
|
-
eprint(f"[user_prompt_submit] Injected CLAUDE.md reminder (mode=implementing)")
|
|
124
|
+
log_diagnostic("user_prompt_submit", "result", f"context={active_context_id}, outputs={len(outputs)}",
|
|
125
|
+
inputs={"active_context_id": active_context_id, "output_count": len(outputs)})
|
|
161
126
|
|
|
162
|
-
# Print output
|
|
163
127
|
if outputs:
|
|
164
128
|
print("\n\n".join(outputs))
|
|
165
129
|
|
|
166
130
|
except Exception as e:
|
|
167
|
-
eprint(f"[user_prompt_submit] ERROR: {e}")
|
|
168
131
|
import traceback
|
|
169
|
-
|
|
132
|
+
tb = traceback.format_exc()
|
|
133
|
+
from lib.base.hook_utils import log_hook_error
|
|
134
|
+
log_hook_error("user_prompt_submit", e, "UserPromptSubmit", traceback_str=tb)
|
|
170
135
|
|
|
171
136
|
|
|
172
137
|
if __name__ == "__main__":
|
|
173
|
-
|
|
138
|
+
from lib.base.hook_utils import run_hook
|
|
139
|
+
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
|