aiwcli 0.9.7 → 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 +49 -18
- 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_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 +87 -178
- package/dist/templates/_shared/hooks/context_monitor.py +128 -194
- package/dist/templates/_shared/hooks/file-suggestion.py +26 -23
- package/dist/templates/_shared/hooks/pre_compact.py +104 -0
- package/dist/templates/_shared/hooks/session_end.py +154 -0
- package/dist/templates/_shared/hooks/session_start.py +145 -59
- package/dist/templates/_shared/hooks/task_create_capture.py +26 -49
- package/dist/templates/_shared/hooks/task_update_capture.py +42 -100
- package/dist/templates/_shared/hooks/user_prompt_submit.py +63 -77
- 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__/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__/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/constants.py +18 -4
- 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 +49 -11
- 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 +25 -79
- 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 +64 -9
- 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/agents/CLAUDE.md +1 -1
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +57 -22
- 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 -57
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +208 -158
- 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 +35 -10
- 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 +103 -42
- 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 +210 -43
- 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 -205
- 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 -1054
- package/dist/templates/_shared/lib/context/discovery.py +0 -444
- package/dist/templates/_shared/lib/context/event_log.py +0 -308
- package/dist/templates/_shared/lib/context/plan_archive.py +0 -101
- package/dist/templates/_shared/lib/context/task_sync.py +0 -290
- 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,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,158 +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
|
-
)
|
|
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
|
|
57
51
|
|
|
58
52
|
|
|
59
53
|
def get_persistent_task_id(
|
|
60
54
|
claude_task_id: str,
|
|
61
55
|
tool_input: Dict[str, Any]
|
|
62
56
|
) -> str:
|
|
63
|
-
"""
|
|
64
|
-
Convert Claude's ephemeral task ID to persistent task ID.
|
|
65
|
-
|
|
66
|
-
If metadata.persistent_id exists, use that.
|
|
67
|
-
Otherwise, assume format "aiw-{claude_task_id}".
|
|
68
|
-
|
|
69
|
-
Args:
|
|
70
|
-
claude_task_id: Task ID from Claude (e.g., "1", "2")
|
|
71
|
-
tool_input: Tool input dict
|
|
72
|
-
|
|
73
|
-
Returns:
|
|
74
|
-
Persistent task ID (e.g., "aiw-1")
|
|
75
|
-
"""
|
|
57
|
+
"""Convert Claude's ephemeral task ID to persistent task ID."""
|
|
76
58
|
metadata = tool_input.get("metadata", {})
|
|
77
59
|
if isinstance(metadata, dict):
|
|
78
60
|
persistent_id = metadata.get("persistent_id")
|
|
79
61
|
if persistent_id:
|
|
80
62
|
return persistent_id
|
|
81
|
-
|
|
82
|
-
# Default: aiw-{id}
|
|
83
63
|
return f"aiw-{claude_task_id}"
|
|
84
64
|
|
|
85
65
|
|
|
86
66
|
@safe_hook_main("task_update_capture")
|
|
87
67
|
def main() -> int:
|
|
88
|
-
"""
|
|
89
|
-
Main hook entry point.
|
|
90
|
-
|
|
91
|
-
Returns:
|
|
92
|
-
0 on success, non-zero on failure (but hook is non-blocking)
|
|
93
|
-
"""
|
|
94
|
-
# Parse hook input
|
|
68
|
+
"""Main hook entry point."""
|
|
95
69
|
payload = load_hook_input()
|
|
96
70
|
if not payload:
|
|
97
71
|
return 0
|
|
98
72
|
|
|
99
|
-
# Validate hook type and tool name
|
|
100
73
|
if not validate_hook_event(payload, "PostToolUse", "TaskUpdate"):
|
|
101
74
|
return 0
|
|
102
75
|
|
|
103
|
-
# Extract tool input
|
|
104
76
|
tool_input = get_tool_input(payload)
|
|
105
77
|
if not tool_input:
|
|
106
|
-
|
|
78
|
+
log_warn("task_update_capture", "Invalid tool_input: not a dict")
|
|
107
79
|
return 0
|
|
108
80
|
|
|
109
|
-
# Check for skip_persistence flag
|
|
110
81
|
if check_skip_persistence(payload, "task_update_capture"):
|
|
111
82
|
return 0
|
|
112
83
|
|
|
113
|
-
# Get project root and session ID
|
|
114
84
|
project_root = project_dir(payload)
|
|
115
|
-
session_id = payload.get("session_id")
|
|
116
|
-
|
|
117
|
-
#
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
session_id=session_id,
|
|
122
|
-
hook_name="task_update_capture"
|
|
123
|
-
)
|
|
124
|
-
if not context_id:
|
|
125
|
-
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")
|
|
126
91
|
return 0
|
|
127
92
|
|
|
93
|
+
context_id = state.id
|
|
94
|
+
|
|
128
95
|
# Extract task ID
|
|
129
96
|
claude_task_id = tool_input.get("taskId")
|
|
130
97
|
if not claude_task_id:
|
|
131
|
-
|
|
98
|
+
log_warn("task_update_capture", "Missing required field: taskId")
|
|
132
99
|
return 0
|
|
133
100
|
|
|
134
|
-
# Get persistent task ID
|
|
135
101
|
persistent_task_id = get_persistent_task_id(claude_task_id, tool_input)
|
|
136
102
|
|
|
137
|
-
# Check for status change
|
|
138
103
|
status = tool_input.get("status")
|
|
139
104
|
metadata = tool_input.get("metadata", {})
|
|
140
|
-
add_blocked_by = tool_input.get("addBlockedBy", [])
|
|
141
105
|
|
|
142
|
-
# Handle different update types
|
|
143
106
|
events_recorded = []
|
|
144
107
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
events_recorded.append("task_started")
|
|
154
|
-
|
|
155
|
-
# Status: completed
|
|
156
|
-
elif status == "completed":
|
|
157
|
-
# 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
|
|
158
116
|
if isinstance(metadata, dict):
|
|
159
|
-
evidence = metadata.get("evidence", "
|
|
117
|
+
evidence = metadata.get("evidence", "")
|
|
160
118
|
work_summary = metadata.get("work_summary", "")
|
|
161
|
-
files_changed = metadata.get("files_changed"
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
files_changed = []
|
|
167
|
-
commit_ref = ""
|
|
168
|
-
|
|
169
|
-
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(
|
|
170
124
|
context_id=context_id,
|
|
171
125
|
task_id=persistent_task_id,
|
|
126
|
+
status=status,
|
|
172
127
|
evidence=evidence,
|
|
173
128
|
work_summary=work_summary,
|
|
174
|
-
files_changed=files_changed
|
|
175
|
-
|
|
176
|
-
project_root=project_root
|
|
177
|
-
)
|
|
178
|
-
if success:
|
|
179
|
-
events_recorded.append("task_completed")
|
|
180
|
-
|
|
181
|
-
# Blocked by tasks
|
|
182
|
-
if add_blocked_by and isinstance(add_blocked_by, list) and len(add_blocked_by) > 0:
|
|
183
|
-
blocked_reason = f"Blocked by tasks: {', '.join(add_blocked_by)}"
|
|
184
|
-
success = record_task_blocked(
|
|
185
|
-
context_id=context_id,
|
|
186
|
-
task_id=persistent_task_id,
|
|
187
|
-
reason=blocked_reason,
|
|
188
|
-
project_root=project_root
|
|
129
|
+
files_changed=files_changed,
|
|
130
|
+
session_id=session_id,
|
|
131
|
+
project_root=project_root,
|
|
189
132
|
)
|
|
190
133
|
if success:
|
|
191
|
-
events_recorded.append("
|
|
134
|
+
events_recorded.append(f"task_{status}")
|
|
192
135
|
|
|
193
136
|
if events_recorded:
|
|
194
|
-
|
|
137
|
+
log_info("task_update_capture", f"Recorded {', '.join(events_recorded)} for {persistent_task_id} in {context_id}")
|
|
195
138
|
else:
|
|
196
|
-
|
|
139
|
+
log_debug("task_update_capture", f"No relevant changes for {persistent_task_id}")
|
|
197
140
|
|
|
198
|
-
# Silent success (no stdout output)
|
|
199
141
|
return 0
|
|
200
142
|
|
|
201
143
|
|
|
202
144
|
if __name__ == "__main__":
|
|
203
|
-
run_hook(main)
|
|
145
|
+
run_hook(main, "task_update_capture")
|
|
@@ -28,84 +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
|
|
46
|
+
## CLAUDE.md \u2014 Persistent Memory
|
|
47
|
+
|
|
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.
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
**What to write:**
|
|
51
|
+
- Architectural choices and why alternatives were rejected
|
|
52
|
+
- Non-obvious constraints (what breaks if this changes)
|
|
53
|
+
- Workarounds with context on the underlying issue
|
|
54
|
+
- Patterns that prevent future mistakes
|
|
50
55
|
|
|
51
|
-
**
|
|
52
|
-
- Architectural choices (why this pattern over alternatives)
|
|
53
|
-
- Non-obvious constraints (why something MUST be done a certain way)
|
|
54
|
-
- Learned patterns (discovered issues that future work should avoid)
|
|
55
|
-
- Integration decisions (why components connect this way)
|
|
56
|
-
- Workarounds (temporary solutions with context on the underlying issue)
|
|
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).
|
|
57
57
|
|
|
58
|
-
**
|
|
58
|
+
**Format:**
|
|
59
59
|
|
|
60
60
|
```markdown
|
|
61
61
|
## [Topic]
|
|
62
|
-
|
|
63
62
|
**Decision:** [What was decided]
|
|
64
|
-
**Rationale:** [Why
|
|
65
|
-
**Constraint:** [What breaks if this changes]
|
|
63
|
+
**Rationale:** [Why \u2014 the non-obvious part]
|
|
66
64
|
```
|
|
67
65
|
|
|
68
|
-
**
|
|
69
|
-
|
|
70
|
-
**Example new CLAUDE.md:**
|
|
71
|
-
|
|
72
|
-
```markdown
|
|
73
|
-
# Component Name
|
|
74
|
-
|
|
75
|
-
Development decisions and patterns for this component.
|
|
76
|
-
|
|
77
|
-
## [First Decision Topic]
|
|
78
|
-
...
|
|
79
|
-
```
|
|
66
|
+
**When in doubt, write it.** A lean entry is better than a lost decision.
|
|
80
67
|
"""
|
|
81
68
|
|
|
82
69
|
|
|
83
70
|
def _update_in_flight_status(context_id: str, hook_input: dict, project_root: Path) -> None:
|
|
84
71
|
"""
|
|
85
|
-
Update context
|
|
72
|
+
Update context mode based on permission mode.
|
|
86
73
|
|
|
87
|
-
-
|
|
88
|
-
-
|
|
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)
|
|
89
77
|
"""
|
|
90
|
-
context = get_context(context_id, project_root)
|
|
91
|
-
if not context or not context.in_flight:
|
|
92
|
-
return
|
|
93
|
-
|
|
94
|
-
current_mode = context.in_flight.mode
|
|
95
78
|
permission_mode = hook_input.get("permission_mode", "default")
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
# Set status based on permission mode
|
|
99
|
-
if permission_mode == "plan":
|
|
100
|
-
if current_mode != "planning":
|
|
101
|
-
update_plan_status(context_id, "planning", project_root=project_root)
|
|
102
|
-
eprint(f"[user_prompt_submit] Set status to 'planning'")
|
|
103
|
-
elif permission_mode != "plan":
|
|
104
|
-
# Any non-plan permission mode transitions pending/planning to implementing
|
|
105
|
-
# This includes "default" (after /clear) and "acceptEdits"/"bypassPermissions"
|
|
106
|
-
if current_mode in ["pending_implementation", "planning", "none"]:
|
|
107
|
-
update_plan_status(context_id, "implementing", project_root=project_root)
|
|
108
|
-
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")
|
|
109
81
|
|
|
110
82
|
|
|
111
83
|
def main():
|
|
@@ -116,72 +88,86 @@ def main():
|
|
|
116
88
|
Uses session_id to detect first prompt vs subsequent prompts.
|
|
117
89
|
"""
|
|
118
90
|
try:
|
|
119
|
-
# Read hook input using shared utility
|
|
120
91
|
hook_input = load_hook_input()
|
|
121
|
-
|
|
122
92
|
if not hook_input:
|
|
123
93
|
return
|
|
124
94
|
|
|
125
|
-
# Get user prompt and project root
|
|
126
95
|
user_prompt = hook_input.get("prompt", "")
|
|
127
96
|
project_root = project_dir(hook_input)
|
|
128
97
|
session_id = hook_input.get("session_id", "unknown")
|
|
129
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
|
+
|
|
130
102
|
outputs: List[str] = []
|
|
131
|
-
active_context_id = None
|
|
103
|
+
active_context_id = None
|
|
132
104
|
|
|
133
105
|
# First-prompt detection: check if session_id is already bound to a context
|
|
134
106
|
existing_context = get_context_by_session_id(session_id, project_root)
|
|
135
107
|
|
|
136
108
|
if existing_context:
|
|
137
109
|
# NOT first prompt - session already bound to context
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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})
|
|
141
114
|
_update_in_flight_status(existing_context.id, hook_input, project_root)
|
|
142
115
|
active_context_id = existing_context.id
|
|
143
116
|
elif user_prompt:
|
|
144
117
|
# FIRST prompt - need context detection
|
|
145
118
|
try:
|
|
146
|
-
context_id, method, context_output
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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)})
|
|
150
124
|
|
|
151
125
|
if context_id:
|
|
152
126
|
# Bind session to context
|
|
153
|
-
|
|
154
|
-
|
|
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}'")
|
|
155
129
|
|
|
156
|
-
# Update
|
|
130
|
+
# Update mode based on permission mode
|
|
157
131
|
_update_in_flight_status(context_id, hook_input, project_root)
|
|
158
132
|
active_context_id = context_id
|
|
159
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
|
+
|
|
160
144
|
if context_output:
|
|
161
145
|
outputs.append(context_output)
|
|
162
146
|
|
|
163
147
|
except BlockRequest as e:
|
|
164
|
-
|
|
165
|
-
# This shows the context picker to the user
|
|
166
|
-
print(e.message, file=sys.stderr)
|
|
148
|
+
log_error("user_prompt_submit", e.message)
|
|
167
149
|
sys.exit(2)
|
|
168
150
|
|
|
169
|
-
# Inject CLAUDE.md reminder when in
|
|
151
|
+
# Inject CLAUDE.md reminder when in active mode
|
|
170
152
|
if active_context_id:
|
|
171
153
|
context = get_context(active_context_id, project_root)
|
|
172
|
-
if context and context.
|
|
154
|
+
if context and context.mode == "active":
|
|
173
155
|
outputs.append(f"<system-reminder>{format_claudemd_reminder()}</system-reminder>")
|
|
174
|
-
|
|
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)})
|
|
175
160
|
|
|
176
|
-
# Print output
|
|
177
161
|
if outputs:
|
|
178
162
|
print("\n\n".join(outputs))
|
|
179
163
|
|
|
180
164
|
except Exception as e:
|
|
181
|
-
eprint(f"[user_prompt_submit] ERROR: {e}")
|
|
182
165
|
import traceback
|
|
183
|
-
|
|
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)
|
|
184
169
|
|
|
185
170
|
|
|
186
171
|
if __name__ == "__main__":
|
|
187
|
-
|
|
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
|
|
Binary file
|
|
@@ -15,7 +15,7 @@ from pathlib import Path
|
|
|
15
15
|
# Directory names (relative to project root)
|
|
16
16
|
OUTPUT_DIR = "_output"
|
|
17
17
|
CONTEXTS_DIR = "contexts"
|
|
18
|
-
ARCHIVE_DIR = "
|
|
18
|
+
ARCHIVE_DIR = "_archive"
|
|
19
19
|
INDEX_FILENAME = "index.json"
|
|
20
20
|
|
|
21
21
|
# Context ID validation
|
|
@@ -255,6 +255,20 @@ def get_events_file_path(context_id: str, project_root: Path = None) -> Path:
|
|
|
255
255
|
return get_context_dir(context_id, project_root) / "events.jsonl"
|
|
256
256
|
|
|
257
257
|
|
|
258
|
+
def get_auto_state_path(context_id: str, project_root: Path = None) -> Path:
|
|
259
|
+
"""
|
|
260
|
+
Get the auto-state.json file path for a specific context.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
context_id: Context identifier
|
|
264
|
+
project_root: Project root directory (default: cwd)
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
Path to _output/contexts/{context_id}/auto-state.json
|
|
268
|
+
"""
|
|
269
|
+
return get_context_dir(context_id, project_root) / "auto-state.json"
|
|
270
|
+
|
|
271
|
+
|
|
258
272
|
def get_archive_dir(project_root: Path = None) -> Path:
|
|
259
273
|
"""
|
|
260
274
|
Get the archive directory path.
|
|
@@ -263,7 +277,7 @@ def get_archive_dir(project_root: Path = None) -> Path:
|
|
|
263
277
|
project_root: Project root directory (default: cwd)
|
|
264
278
|
|
|
265
279
|
Returns:
|
|
266
|
-
Path to _output/contexts/
|
|
280
|
+
Path to _output/contexts/_archive/
|
|
267
281
|
"""
|
|
268
282
|
return get_contexts_dir(project_root) / ARCHIVE_DIR
|
|
269
283
|
|
|
@@ -277,7 +291,7 @@ def get_archive_context_dir(context_id: str, project_root: Path = None) -> Path:
|
|
|
277
291
|
project_root: Project root directory (default: cwd)
|
|
278
292
|
|
|
279
293
|
Returns:
|
|
280
|
-
Path to _output/contexts/
|
|
294
|
+
Path to _output/contexts/_archive/{context_id}/
|
|
281
295
|
|
|
282
296
|
Raises:
|
|
283
297
|
ValueError: If context_id is invalid
|
|
@@ -294,7 +308,7 @@ def get_archive_index_path(project_root: Path = None) -> Path:
|
|
|
294
308
|
project_root: Project root directory (default: cwd)
|
|
295
309
|
|
|
296
310
|
Returns:
|
|
297
|
-
Path to _output/contexts/
|
|
311
|
+
Path to _output/contexts/_archive/index.json
|
|
298
312
|
"""
|
|
299
313
|
return get_archive_dir(project_root) / INDEX_FILENAME
|
|
300
314
|
|