aiwcli 0.9.6 → 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/init/index.d.ts +0 -8
- package/dist/commands/init/index.js +5 -35
- package/dist/lib/index.d.ts +3 -4
- package/dist/lib/index.js +3 -5
- package/dist/lib/settings-hierarchy.js +5 -16
- package/dist/lib/template-installer.d.ts +9 -0
- package/dist/lib/template-installer.js +3 -14
- package/dist/lib/template-merger.js +1 -12
- package/dist/lib/windsurf-hooks-hierarchy.js +2 -13
- package/dist/templates/CLAUDE.md +49 -18
- 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__/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/context_enforcer.py +21 -71
- package/dist/templates/_shared/hooks/context_monitor.py +78 -1
- package/dist/templates/_shared/hooks/pre_compact.py +89 -0
- package/dist/templates/_shared/hooks/session_end.py +111 -0
- package/dist/templates/_shared/hooks/session_start.py +104 -47
- package/dist/templates/_shared/hooks/task_create_atomicity.py +33 -61
- package/dist/templates/_shared/hooks/task_create_capture.py +1 -0
- package/dist/templates/_shared/hooks/task_update_capture.py +15 -0
- package/dist/templates/_shared/hooks/user_prompt_submit.py +13 -27
- package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.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 +0 -24
- package/dist/templates/_shared/lib/base/stop_words.py +23 -0
- package/dist/templates/_shared/lib/base/utils.py +9 -4
- package/dist/templates/_shared/lib/context/__init__.py +0 -8
- package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/auto_state.py +167 -0
- package/dist/templates/_shared/lib/context/context_manager.py +6 -3
- package/dist/templates/_shared/lib/context/discovery.py +167 -57
- package/dist/templates/_shared/lib/context/event_log.py +8 -0
- package/dist/templates/_shared/lib/context/plan_archive.py +0 -146
- package/dist/templates/_shared/lib/context/task_sync.py +160 -43
- package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/plan_context.py +24 -41
- package/dist/templates/cc-native/.claude/settings.json +32 -10
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +1 -1
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +8 -1
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +1 -8
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +65 -47
- package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +29 -6
- package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -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__/atomic_write.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/orchestrator.py +71 -15
- 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 +6 -4
- package/dist/templates/cc-native/_cc-native/lib/state.py +11 -9
- package/dist/templates/cc-native/_cc-native/lib/utils.py +26 -109
- package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""SessionStart hook for mode transitions
|
|
2
|
+
"""SessionStart hook for mode transitions and post-compaction restore.
|
|
3
3
|
|
|
4
|
-
This hook fires when a new session starts. It handles
|
|
5
|
-
from `pending_implementation` to `implementing` when a session starts after
|
|
6
|
-
/clear with bypass permissions.
|
|
4
|
+
This hook fires when a new session starts. It handles:
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
2. User clicks "yes and clear and bypass permissions"
|
|
11
|
-
3. SessionStart fires with source="clear" and permission_mode="bypassPermissions"
|
|
12
|
-
4. This hook transitions mode to "implementing"
|
|
6
|
+
1. Mode transition from `pending_implementation` to `implementing` when
|
|
7
|
+
a session starts after /clear with bypass permissions.
|
|
13
8
|
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
2. Post-compaction restore: when source="compact", the session is already
|
|
10
|
+
bound to a context. Load auto-state and inject rich restoration context
|
|
11
|
+
so Claude can continue seamlessly after compaction.
|
|
16
12
|
|
|
17
13
|
Hook input:
|
|
18
14
|
{
|
|
@@ -24,6 +20,7 @@ Hook input:
|
|
|
24
20
|
...
|
|
25
21
|
}
|
|
26
22
|
"""
|
|
23
|
+
import json
|
|
27
24
|
import sys
|
|
28
25
|
from pathlib import Path
|
|
29
26
|
|
|
@@ -36,22 +33,106 @@ from lib.base.hook_utils import load_hook_input
|
|
|
36
33
|
from lib.base.utils import eprint, project_dir
|
|
37
34
|
from lib.context.context_manager import (
|
|
38
35
|
get_all_in_flight_contexts,
|
|
36
|
+
get_context_by_session_id,
|
|
39
37
|
update_plan_status,
|
|
40
38
|
update_context_session_id,
|
|
41
39
|
)
|
|
40
|
+
from lib.context.auto_state import load_auto_state
|
|
41
|
+
from lib.context.discovery import (
|
|
42
|
+
_build_restore_sections,
|
|
43
|
+
find_plan_path,
|
|
44
|
+
format_relative_time,
|
|
45
|
+
)
|
|
46
|
+
from lib.context.task_sync import generate_task_summary
|
|
42
47
|
|
|
43
48
|
|
|
44
|
-
def
|
|
49
|
+
def _handle_clear_transition(hook_input, session_id, project_root):
|
|
50
|
+
"""Handle /clear mode transitions (existing behavior)."""
|
|
51
|
+
permission_mode = hook_input.get("permission_mode", "default")
|
|
52
|
+
|
|
53
|
+
if permission_mode == "plan":
|
|
54
|
+
eprint("[session_start] Skipping: permission_mode is 'plan' (in planning mode)")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
in_flight_contexts = get_all_in_flight_contexts(project_root)
|
|
58
|
+
if not in_flight_contexts:
|
|
59
|
+
eprint("[session_start] No in-flight contexts found")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
pending_contexts = [
|
|
63
|
+
ctx for ctx in in_flight_contexts
|
|
64
|
+
if ctx.in_flight and ctx.in_flight.mode == "pending_implementation"
|
|
65
|
+
]
|
|
66
|
+
for ctx in pending_contexts:
|
|
67
|
+
eprint(f"[session_start] Transitioning {ctx.id} from pending_implementation to implementing")
|
|
68
|
+
update_plan_status(ctx.id, "implementing", project_root=project_root)
|
|
69
|
+
update_context_session_id(ctx.id, session_id, project_root)
|
|
70
|
+
eprint(f"[session_start] Bound session {session_id[:8]}... to context {ctx.id}")
|
|
71
|
+
|
|
72
|
+
if pending_contexts:
|
|
73
|
+
eprint(f"[session_start] Transitioned {len(pending_contexts)} context(s) to implementing")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _handle_compact_restore(hook_input, session_id, project_root):
|
|
77
|
+
"""
|
|
78
|
+
Handle post-compaction restore.
|
|
79
|
+
|
|
80
|
+
After compaction, the session is already bound to a context.
|
|
81
|
+
Load auto-state and inject rich restoration context via additionalContext.
|
|
45
82
|
"""
|
|
46
|
-
|
|
83
|
+
context = get_context_by_session_id(session_id, project_root)
|
|
84
|
+
if not context:
|
|
85
|
+
eprint("[session_start] No context bound to session after compact")
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
context_id = context.id
|
|
89
|
+
eprint(f"[session_start] Post-compaction restore for context: {context_id}")
|
|
90
|
+
|
|
91
|
+
# Build restoration context
|
|
92
|
+
mode_display = "Active"
|
|
93
|
+
if context.in_flight and context.in_flight.mode != "none":
|
|
94
|
+
mode_display = context.in_flight.mode.replace("_", " ").title()
|
|
95
|
+
|
|
96
|
+
lines = [
|
|
97
|
+
f"## Resuming Context After Compaction: {context_id}",
|
|
98
|
+
"",
|
|
99
|
+
f"**Summary:** {context.summary}",
|
|
100
|
+
f"**Mode:** {mode_display}",
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
# Add restore sections (auto-state, tasks, git)
|
|
104
|
+
restore = _build_restore_sections(context, project_root)
|
|
105
|
+
if restore:
|
|
106
|
+
lines.append(restore)
|
|
107
|
+
|
|
108
|
+
lines.extend([
|
|
109
|
+
"",
|
|
110
|
+
"---",
|
|
111
|
+
"",
|
|
112
|
+
"**Instructions:**",
|
|
113
|
+
"Context was compacted to free memory. Your previous conversation has been summarized.",
|
|
114
|
+
"1. Review the previous work above",
|
|
115
|
+
"2. Continue from where you left off",
|
|
116
|
+
])
|
|
117
|
+
|
|
118
|
+
restore_context = "\n".join(lines)
|
|
119
|
+
|
|
120
|
+
# Output as additionalContext so Claude sees it
|
|
121
|
+
output = {
|
|
122
|
+
"hookSpecificOutput": {
|
|
123
|
+
"additionalContext": restore_context
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
print(json.dumps(output, ensure_ascii=False))
|
|
127
|
+
eprint(f"[session_start] Injected post-compaction restore context for {context_id}")
|
|
128
|
+
|
|
47
129
|
|
|
48
|
-
|
|
49
|
-
|
|
130
|
+
def main():
|
|
131
|
+
"""
|
|
132
|
+
Handle mode transitions and post-compaction restore on session start.
|
|
50
133
|
"""
|
|
51
134
|
try:
|
|
52
|
-
# Read hook input using shared utility
|
|
53
135
|
hook_input = load_hook_input()
|
|
54
|
-
|
|
55
136
|
if not hook_input:
|
|
56
137
|
return
|
|
57
138
|
|
|
@@ -62,36 +143,12 @@ def main():
|
|
|
62
143
|
|
|
63
144
|
eprint(f"[session_start] source={source}, permission_mode={permission_mode}, session={session_id[:8]}...")
|
|
64
145
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
eprint(f"[session_start] Skipping: permission_mode is 'plan' (in planning mode)")
|
|
72
|
-
return
|
|
73
|
-
|
|
74
|
-
# Find contexts in pending_implementation mode
|
|
75
|
-
in_flight_contexts = get_all_in_flight_contexts(project_root)
|
|
76
|
-
pending_contexts = [
|
|
77
|
-
ctx for ctx in in_flight_contexts
|
|
78
|
-
if ctx.in_flight and ctx.in_flight.mode == "pending_implementation"
|
|
79
|
-
]
|
|
80
|
-
|
|
81
|
-
if not pending_contexts:
|
|
82
|
-
eprint("[session_start] No pending_implementation contexts found")
|
|
83
|
-
return
|
|
84
|
-
|
|
85
|
-
# Transition each pending context to implementing
|
|
86
|
-
for ctx in pending_contexts:
|
|
87
|
-
eprint(f"[session_start] Transitioning {ctx.id} from pending_implementation to implementing")
|
|
88
|
-
update_plan_status(ctx.id, "implementing", project_root=project_root)
|
|
89
|
-
|
|
90
|
-
# Also bind this session to the context
|
|
91
|
-
update_context_session_id(ctx.id, session_id, project_root)
|
|
92
|
-
eprint(f"[session_start] Bound session {session_id[:8]}... to context {ctx.id}")
|
|
93
|
-
|
|
94
|
-
eprint(f"[session_start] Transitioned {len(pending_contexts)} context(s) to implementing")
|
|
146
|
+
if source == "clear":
|
|
147
|
+
_handle_clear_transition(hook_input, session_id, project_root)
|
|
148
|
+
elif source == "compact":
|
|
149
|
+
_handle_compact_restore(hook_input, session_id, project_root)
|
|
150
|
+
else:
|
|
151
|
+
eprint(f"[session_start] No action for source='{source}'")
|
|
95
152
|
|
|
96
153
|
except Exception as e:
|
|
97
154
|
eprint(f"[session_start] ERROR: {e}")
|
|
@@ -34,64 +34,47 @@ from lib.base.inference import inference
|
|
|
34
34
|
# - Direct imperative instructions
|
|
35
35
|
# - Explicit JSON output format
|
|
36
36
|
|
|
37
|
-
ASSESSMENT_SYSTEM_PROMPT = """
|
|
37
|
+
ASSESSMENT_SYSTEM_PROMPT = """Assess whether a task description is self-contained enough for a subagent with zero conversation history to execute it.
|
|
38
38
|
|
|
39
|
-
##
|
|
39
|
+
## What Makes a Good Task
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
**Forkable Task:** Can be delegated to a subagent with ZERO conversation history and still be completed successfully.
|
|
44
|
-
|
|
45
|
-
## Signs of Non-Atomic Tasks
|
|
46
|
-
|
|
47
|
-
Look for these indicators:
|
|
48
|
-
- Contextual references: "the file above", "as discussed", "the mentioned function", "this bug"
|
|
49
|
-
- Vague descriptions assuming prior knowledge: "fix the bug", "update it", "finish the work"
|
|
50
|
-
- Missing specifics: which file? what function? what expected behavior? what error?
|
|
51
|
-
- Pronouns without antecedents: "it", "they", "the issue" without explicit definition
|
|
52
|
-
|
|
53
|
-
## Signs of Atomic Tasks
|
|
54
|
-
|
|
55
|
-
Well-specified tasks include:
|
|
41
|
+
A well-specified task includes:
|
|
56
42
|
- Explicit file paths: "Edit src/utils/parser.py"
|
|
57
|
-
- Specific function names: "Modify
|
|
58
|
-
- Clear expected behavior: "
|
|
59
|
-
-
|
|
43
|
+
- Specific function/component names: "Modify validate_input()"
|
|
44
|
+
- Clear expected behavior: "Return 404 when user not found"
|
|
45
|
+
- Concrete error context: "TypeError on line 45 when input is None"
|
|
60
46
|
|
|
61
|
-
##
|
|
47
|
+
## What Makes a Poor Task
|
|
62
48
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
49
|
+
Watch for context-dependent references:
|
|
50
|
+
- Dangling references: "the file above", "as discussed", "this bug"
|
|
51
|
+
- Vague actions: "fix the bug", "update it", "finish the work"
|
|
52
|
+
- Pronouns without antecedents: "it", "they", "the issue"
|
|
53
|
+
- Missing specifics: which file? what function? what behavior?
|
|
67
54
|
|
|
68
|
-
|
|
69
|
-
Subject: "Fix null pointer in user lookup"
|
|
70
|
-
Description: "In src/services/user.py, the get_user_by_id() function raises TypeError when user_id is None. Add null check at line 23 that returns None early instead of calling database.query()."
|
|
71
|
-
Assessment: Atomic (file path, function name, specific error, exact fix location, expected behavior)
|
|
55
|
+
## Examples
|
|
72
56
|
|
|
73
|
-
**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Assessment: NOT fully atomic (missing: which file contains the form? what validation rules? where to display error?)
|
|
57
|
+
**Atomic** — Subject: "Fix null pointer in user lookup"
|
|
58
|
+
Description: "In src/services/user.py, get_user_by_id() raises TypeError when user_id is None. Add null check at line 23 to return None instead of calling database.query()."
|
|
59
|
+
Why: file path, function, error, fix location, expected behavior.
|
|
77
60
|
|
|
78
|
-
|
|
61
|
+
**Not atomic** — Subject: "Fix the bug"
|
|
62
|
+
Description: "The issue we discussed earlier needs to be resolved"
|
|
63
|
+
Why: no file, no function, no error details, references conversation history.
|
|
79
64
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
"forkable": true/false,
|
|
84
|
-
"issues": ["specific issue 1", "specific issue 2"],
|
|
85
|
-
"recommendation": "brief actionable suggestion if issues exist, or 'Task is well-specified' if good"
|
|
86
|
-
}"""
|
|
65
|
+
**Partially atomic** — Subject: "Add validation to form"
|
|
66
|
+
Description: "Add email validation to the signup form. Return error if invalid."
|
|
67
|
+
Why: missing which file, what validation rules, where to display error.
|
|
87
68
|
|
|
88
|
-
|
|
69
|
+
## Output
|
|
89
70
|
|
|
90
|
-
|
|
71
|
+
Respond with JSON only:
|
|
72
|
+
{"atomic": true/false, "forkable": true/false, "issues": ["issue 1", "issue 2"], "recommendation": "actionable suggestion or 'Well-specified'"}"""
|
|
91
73
|
|
|
74
|
+
ASSESSMENT_USER_TEMPLATE = """**Subject:** {subject}
|
|
92
75
|
**Description:** {description}
|
|
93
76
|
|
|
94
|
-
|
|
77
|
+
Could a subagent with zero conversation history execute this task?"""
|
|
95
78
|
|
|
96
79
|
|
|
97
80
|
@safe_hook_main("task_create_atomicity")
|
|
@@ -167,33 +150,22 @@ def main() -> int:
|
|
|
167
150
|
|
|
168
151
|
# Build context message based on assessment
|
|
169
152
|
if atomic and forkable:
|
|
170
|
-
|
|
171
|
-
context_msg = "Task Assessment: Well-specified and forkable."
|
|
153
|
+
context_msg = "Task assessment: well-specified and ready for delegation."
|
|
172
154
|
else:
|
|
173
|
-
#
|
|
174
|
-
|
|
175
|
-
if not atomic:
|
|
176
|
-
status_parts.append("NOT ATOMIC")
|
|
177
|
-
if not forkable:
|
|
178
|
-
status_parts.append("NOT FORKABLE")
|
|
155
|
+
# Constructive guidance — what to add, not what's wrong
|
|
156
|
+
issues_text = "\n".join(f"- {issue}" for issue in issues) if issues else ""
|
|
179
157
|
|
|
180
|
-
|
|
158
|
+
context_msg = f"""**Enrich this task for subagent delegation**
|
|
181
159
|
|
|
182
|
-
|
|
160
|
+
A subagent receiving this task will have no conversation history. To make it actionable:
|
|
183
161
|
|
|
184
|
-
This task may lack sufficient context for independent execution by a subagent.
|
|
185
|
-
|
|
186
|
-
**Issues detected:**
|
|
187
162
|
{issues_text}
|
|
188
163
|
|
|
189
|
-
**
|
|
190
|
-
|
|
191
|
-
Consider adding specific file paths, function names, expected behaviors, or error details before creating this task."""
|
|
164
|
+
**Suggested enrichment:** {recommendation}"""
|
|
192
165
|
|
|
193
166
|
# Output hook response with additionalContext
|
|
194
167
|
out = {
|
|
195
168
|
"hookSpecificOutput": {
|
|
196
|
-
"hookEventName": "PreToolUse",
|
|
197
169
|
"additionalContext": context_msg
|
|
198
170
|
}
|
|
199
171
|
}
|
|
@@ -53,6 +53,7 @@ from lib.context.task_sync import (
|
|
|
53
53
|
record_task_started,
|
|
54
54
|
record_task_completed,
|
|
55
55
|
record_task_blocked,
|
|
56
|
+
record_task_deleted,
|
|
56
57
|
)
|
|
57
58
|
|
|
58
59
|
|
|
@@ -147,6 +148,7 @@ def main() -> int:
|
|
|
147
148
|
success = record_task_started(
|
|
148
149
|
context_id=context_id,
|
|
149
150
|
task_id=persistent_task_id,
|
|
151
|
+
session_id=session_id or "",
|
|
150
152
|
project_root=project_root
|
|
151
153
|
)
|
|
152
154
|
if success:
|
|
@@ -173,11 +175,23 @@ def main() -> int:
|
|
|
173
175
|
work_summary=work_summary,
|
|
174
176
|
files_changed=files_changed if isinstance(files_changed, list) else [],
|
|
175
177
|
commit_ref=commit_ref,
|
|
178
|
+
session_id=session_id or "",
|
|
176
179
|
project_root=project_root
|
|
177
180
|
)
|
|
178
181
|
if success:
|
|
179
182
|
events_recorded.append("task_completed")
|
|
180
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
|
+
|
|
181
195
|
# Blocked by tasks
|
|
182
196
|
if add_blocked_by and isinstance(add_blocked_by, list) and len(add_blocked_by) > 0:
|
|
183
197
|
blocked_reason = f"Blocked by tasks: {', '.join(add_blocked_by)}"
|
|
@@ -185,6 +199,7 @@ def main() -> int:
|
|
|
185
199
|
context_id=context_id,
|
|
186
200
|
task_id=persistent_task_id,
|
|
187
201
|
reason=blocked_reason,
|
|
202
|
+
session_id=session_id or "",
|
|
188
203
|
project_root=project_root
|
|
189
204
|
)
|
|
190
205
|
if success:
|
|
@@ -46,36 +46,24 @@ def format_claudemd_reminder() -> str:
|
|
|
46
46
|
return """
|
|
47
47
|
## CLAUDE.md Decision Capture
|
|
48
48
|
|
|
49
|
-
|
|
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
50
|
|
|
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)
|
|
51
|
+
**Before creating a new CLAUDE.md, ask:** "Do the rules here actually differ from the parent?" If not, update the parent instead.
|
|
57
52
|
|
|
58
|
-
**
|
|
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.
|
|
59
54
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
**Constraint:** [What breaks if this changes]
|
|
66
|
-
```
|
|
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
|
|
67
60
|
|
|
68
|
-
**
|
|
69
|
-
|
|
70
|
-
**Example new CLAUDE.md:**
|
|
61
|
+
**Format:**
|
|
71
62
|
|
|
72
63
|
```markdown
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
## [First Decision Topic]
|
|
78
|
-
...
|
|
64
|
+
## [Topic]
|
|
65
|
+
**Decision:** [What was decided]
|
|
66
|
+
**Rationale:** [Why — the non-obvious part]
|
|
79
67
|
```
|
|
80
68
|
"""
|
|
81
69
|
|
|
@@ -143,10 +131,8 @@ def main():
|
|
|
143
131
|
elif user_prompt:
|
|
144
132
|
# FIRST prompt - need context detection
|
|
145
133
|
try:
|
|
146
|
-
context_id, method, context_output
|
|
134
|
+
context_id, method, context_output = determine_context(user_prompt, project_root, session_id)
|
|
147
135
|
eprint(f"[user_prompt_submit] Context: {method} -> {context_id}")
|
|
148
|
-
if remaining_prompt:
|
|
149
|
-
eprint(f"[user_prompt_submit] Actual request: {remaining_prompt[:50]}...")
|
|
150
136
|
|
|
151
137
|
if context_id:
|
|
152
138
|
# Bind session to context
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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
|
|
|
@@ -19,30 +19,6 @@ from .utils import eprint
|
|
|
19
19
|
F = TypeVar('F', bound=Callable[..., Any])
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def setup_hook_paths() -> Path:
|
|
23
|
-
"""
|
|
24
|
-
Setup import paths for hooks.
|
|
25
|
-
|
|
26
|
-
Call this at module level in hook scripts to ensure
|
|
27
|
-
the shared lib directory is in sys.path.
|
|
28
|
-
|
|
29
|
-
Returns:
|
|
30
|
-
Path to the lib directory
|
|
31
|
-
|
|
32
|
-
Example:
|
|
33
|
-
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
34
|
-
SHARED_LIB = SCRIPT_DIR.parent / "lib"
|
|
35
|
-
sys.path.insert(0, str(SHARED_LIB.parent))
|
|
36
|
-
"""
|
|
37
|
-
# This function exists mainly for documentation
|
|
38
|
-
# Actual path setup must happen at module level before imports
|
|
39
|
-
hook_dir = Path(__file__).resolve().parent.parent.parent / "hooks"
|
|
40
|
-
lib_dir = hook_dir.parent / "lib"
|
|
41
|
-
if str(lib_dir.parent) not in sys.path:
|
|
42
|
-
sys.path.insert(0, str(lib_dir.parent))
|
|
43
|
-
return lib_dir
|
|
44
|
-
|
|
45
|
-
|
|
46
22
|
def load_hook_input() -> Optional[Dict[str, Any]]:
|
|
47
23
|
"""
|
|
48
24
|
Load and parse JSON from stdin.
|
|
@@ -187,4 +187,27 @@ STOP_WORDS = {
|
|
|
187
187
|
# FRAGMENT WORDS (artifacts from contractions/tokenization)
|
|
188
188
|
# ========================================================================
|
|
189
189
|
're', 'pl', 'aiw', 've', 'll', 'doesn', 't', 's',
|
|
190
|
+
|
|
191
|
+
# ========================================================================
|
|
192
|
+
# CORPUS-DERIVED SHORT NOISE (2026-02 analysis of 131 docs)
|
|
193
|
+
# ========================================================================
|
|
194
|
+
# Contractions with punctuation stripped (I'm -> im, etc.)
|
|
195
|
+
'im', 'ive', 'id', 'ill', 'youre', 'youve', 'youll',
|
|
196
|
+
'hes', 'shes', 'weve', 'theyre', 'theyve', 'dont', 'doesnt',
|
|
197
|
+
'didnt', 'wont', 'wouldnt', 'cant', 'couldnt', 'shouldnt', 'isnt',
|
|
198
|
+
'arent', 'wasnt', 'werent', 'hasnt', 'havent', 'hadnt', 'lets',
|
|
199
|
+
'thats', 'whats', 'heres', 'theres', 'whos',
|
|
200
|
+
|
|
201
|
+
# Filler/noise from corpus (>10% frequency, non-action)
|
|
202
|
+
'etc', 'up', 'as', 'cc',
|
|
203
|
+
|
|
204
|
+
# Number words (common in plans but don't identify task)
|
|
205
|
+
'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten',
|
|
206
|
+
|
|
207
|
+
# Single letters (artifacts from lists, paths, variables)
|
|
208
|
+
'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
|
|
209
|
+
'q', 'r', 'u', 'v', 'w', 'x', 'y', 'z',
|
|
210
|
+
|
|
211
|
+
# Short adverbs/fillers from review
|
|
212
|
+
'too', 'yes', 'ok', 'okay',
|
|
190
213
|
}
|
|
@@ -138,22 +138,27 @@ def generate_context_id(summary: str, existing_ids: Optional[set] = None) -> str
|
|
|
138
138
|
"""
|
|
139
139
|
Generate a context ID from a summary string.
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
Prepends a YYMMDD-HHMM local-time timestamp for visual
|
|
142
|
+
distinguishability, then filters stop words and slugifies.
|
|
142
143
|
|
|
143
144
|
Args:
|
|
144
145
|
summary: Context summary text
|
|
145
146
|
existing_ids: Optional set of existing context IDs to avoid
|
|
146
147
|
|
|
147
148
|
Returns:
|
|
148
|
-
Unique context ID string
|
|
149
|
+
Unique context ID string (e.g. '260205-1700-add-user-auth')
|
|
149
150
|
"""
|
|
151
|
+
# Timestamp prefix using local time, to the minute
|
|
152
|
+
timestamp = datetime.now().strftime("%y%m%d-%H%M")
|
|
153
|
+
|
|
150
154
|
if not summary or not summary.strip():
|
|
151
|
-
base_id = "context"
|
|
155
|
+
base_id = f"{timestamp}-context"
|
|
152
156
|
else:
|
|
153
157
|
# Use stop word filter, limit to 12 words
|
|
154
158
|
from .stop_words import STOP_WORDS
|
|
155
159
|
words = [w for w in summary.lower().split() if w not in STOP_WORDS and len(w) > 1][:12]
|
|
156
|
-
|
|
160
|
+
slug = sanitize_title(' '.join(words), max_len=50)
|
|
161
|
+
base_id = f"{timestamp}-{slug}"
|
|
157
162
|
|
|
158
163
|
if not existing_ids:
|
|
159
164
|
return base_id
|
|
@@ -50,10 +50,6 @@ from .task_sync import (
|
|
|
50
50
|
)
|
|
51
51
|
from .plan_archive import (
|
|
52
52
|
archive_plan_to_context,
|
|
53
|
-
get_active_context_for_plan,
|
|
54
|
-
create_context_from_plan,
|
|
55
|
-
mark_plan_implementation_started,
|
|
56
|
-
mark_plan_completed,
|
|
57
53
|
)
|
|
58
54
|
from .context_extractor import (
|
|
59
55
|
extract_context_id,
|
|
@@ -108,10 +104,6 @@ __all__ = [
|
|
|
108
104
|
"generate_next_task_id",
|
|
109
105
|
# Plan Archive
|
|
110
106
|
"archive_plan_to_context",
|
|
111
|
-
"get_active_context_for_plan",
|
|
112
|
-
"create_context_from_plan",
|
|
113
|
-
"mark_plan_implementation_started",
|
|
114
|
-
"mark_plan_completed",
|
|
115
107
|
# Context Extractor
|
|
116
108
|
"extract_context_id",
|
|
117
109
|
"extract_context_id_for_session",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|