aiwcli 0.10.3 → 0.11.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.
Files changed (189) hide show
  1. package/bin/run.js +1 -1
  2. package/dist/commands/clear.js +28 -131
  3. package/dist/commands/init/index.js +3 -3
  4. package/dist/lib/gitignore-manager.d.ts +32 -0
  5. package/dist/lib/gitignore-manager.js +141 -2
  6. package/dist/templates/CLAUDE.md +8 -8
  7. package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
  8. package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
  9. package/dist/templates/_shared/.claude/settings.json +7 -7
  10. package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
  11. package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
  12. package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
  13. package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
  14. package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
  15. package/dist/templates/_shared/hooks-ts/session_end.ts +104 -0
  16. package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
  17. package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
  18. package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
  19. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
  20. package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
  21. package/dist/templates/_shared/lib-ts/base/atomic-write.ts +12 -12
  22. package/dist/templates/_shared/lib-ts/base/constants.ts +22 -15
  23. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +129 -50
  24. package/dist/templates/_shared/lib-ts/base/inference.ts +28 -21
  25. package/dist/templates/_shared/lib-ts/base/logger.ts +31 -15
  26. package/dist/templates/_shared/lib-ts/base/state-io.ts +9 -7
  27. package/dist/templates/_shared/lib-ts/base/stop-words.ts +131 -131
  28. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +139 -0
  29. package/dist/templates/_shared/lib-ts/base/utils.ts +69 -69
  30. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +30 -24
  31. package/dist/templates/_shared/lib-ts/context/context-selector.ts +50 -32
  32. package/dist/templates/_shared/lib-ts/context/context-store.ts +76 -48
  33. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +61 -37
  34. package/dist/templates/_shared/lib-ts/context/task-tracker.ts +10 -6
  35. package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +11 -10
  36. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +159 -0
  37. package/dist/templates/_shared/lib-ts/templates/formatters.ts +6 -4
  38. package/dist/templates/_shared/lib-ts/types.ts +68 -55
  39. package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
  40. package/dist/templates/_shared/scripts/resume_handoff.ts +321 -0
  41. package/dist/templates/_shared/scripts/save_handoff.ts +21 -21
  42. package/dist/templates/_shared/scripts/status_line.ts +733 -0
  43. package/dist/templates/cc-native/.claude/settings.json +175 -185
  44. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
  45. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +0 -2
  46. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
  47. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
  48. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +921 -0
  49. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
  50. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +157 -0
  51. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +709 -0
  52. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
  53. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +124 -0
  54. package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
  55. package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
  56. package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
  57. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +119 -0
  58. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +162 -0
  59. package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
  60. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +249 -0
  61. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +155 -0
  62. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
  63. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +106 -0
  64. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
  65. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
  66. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +243 -0
  67. package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
  68. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +310 -0
  69. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
  70. package/dist/templates/cc-native/_cc-native/plan-review.config.json +1 -9
  71. package/oclif.manifest.json +1 -1
  72. package/package.json +1 -1
  73. package/dist/templates/_shared/hooks/__init__.py +0 -16
  74. package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  75. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  76. package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
  77. package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
  78. package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
  79. package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
  80. package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
  81. package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
  82. package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
  83. package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
  84. package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
  85. package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  86. package/dist/templates/_shared/hooks/archive_plan.py +0 -177
  87. package/dist/templates/_shared/hooks/context_monitor.py +0 -270
  88. package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
  89. package/dist/templates/_shared/hooks/pre_compact.py +0 -104
  90. package/dist/templates/_shared/hooks/session_end.py +0 -173
  91. package/dist/templates/_shared/hooks/session_start.py +0 -206
  92. package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
  93. package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
  94. package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
  95. package/dist/templates/_shared/lib/__init__.py +0 -1
  96. package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  97. package/dist/templates/_shared/lib/base/__init__.py +0 -65
  98. package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
  99. package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
  100. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  101. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  102. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  103. package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
  104. package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
  105. package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
  106. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  107. package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
  108. package/dist/templates/_shared/lib/base/constants.py +0 -358
  109. package/dist/templates/_shared/lib/base/hook_utils.py +0 -339
  110. package/dist/templates/_shared/lib/base/inference.py +0 -307
  111. package/dist/templates/_shared/lib/base/logger.py +0 -305
  112. package/dist/templates/_shared/lib/base/stop_words.py +0 -221
  113. package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
  114. package/dist/templates/_shared/lib/base/utils.py +0 -263
  115. package/dist/templates/_shared/lib/context/__init__.py +0 -102
  116. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  117. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  118. package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
  119. package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
  120. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  121. package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
  122. package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
  123. package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
  124. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  125. package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
  126. package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
  127. package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
  128. package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
  129. package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
  130. package/dist/templates/_shared/lib/context/context_selector.py +0 -508
  131. package/dist/templates/_shared/lib/context/context_store.py +0 -653
  132. package/dist/templates/_shared/lib/context/plan_manager.py +0 -303
  133. package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
  134. package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
  135. package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
  136. package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
  137. package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
  138. package/dist/templates/_shared/lib/templates/README.md +0 -206
  139. package/dist/templates/_shared/lib/templates/__init__.py +0 -36
  140. package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
  141. package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
  142. package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
  143. package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
  144. package/dist/templates/_shared/lib/templates/formatters.py +0 -146
  145. package/dist/templates/_shared/lib/templates/plan_context.py +0 -73
  146. package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
  147. package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
  148. package/dist/templates/_shared/scripts/save_handoff.py +0 -357
  149. package/dist/templates/_shared/scripts/status_line.py +0 -716
  150. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
  151. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
  152. package/dist/templates/cc-native/MIGRATION.md +0 -86
  153. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  154. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  155. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
  156. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
  157. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
  158. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
  159. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
  160. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -954
  161. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
  162. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
  163. package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
  164. package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
  165. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  166. package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
  167. package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
  168. package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
  169. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  170. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  171. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  172. package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
  173. package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
  174. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
  175. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
  176. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  177. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  178. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  179. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  180. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  181. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
  182. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
  183. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
  184. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
  185. package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
  186. package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1071
  187. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  188. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
  189. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
@@ -1,270 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Context monitor hook for proactive handoff warnings.
3
-
4
- This hook runs on PostToolUse for context-heavy tools and monitors
5
- context window usage. When context drops below a threshold, it injects
6
- a system reminder instructing Claude to wrap up and create a handoff document.
7
-
8
- Unlike UserPromptSubmit hooks, this fires DURING Claude's work,
9
- allowing proactive intervention without waiting for user input.
10
-
11
- Monitored tools (configured via settings.json matcher):
12
- - Task: Subagent responses can be huge
13
- - Read: File content loads into context
14
- - Bash: Command output can be large
15
- - WebFetch: Web content loads into context
16
-
17
- Hook input (from Claude Code):
18
- {
19
- "hook_event_name": "PostToolUse",
20
- "tool_name": "Task",
21
- "tool_input": {...},
22
- "tool_result": {...},
23
- "transcript_path": "/path/to/transcript.jsonl",
24
- "session_id": "abc123",
25
- "context_window": {
26
- "current_usage": {
27
- "cache_read_input_tokens": 0,
28
- "input_tokens": 12345,
29
- "cache_creation_input_tokens": 0,
30
- "output_tokens": 6789
31
- },
32
- "context_window_size": 200000
33
- },
34
- ...
35
- }
36
-
37
- Hook output:
38
- - Outputs JSON with additionalContext if context is low
39
- - This injects a system reminder into Claude's context
40
- - Plain stdout from PostToolUse only goes to verbose mode, not Claude
41
- - Using additionalContext ensures Claude sees and responds to the warning
42
-
43
- KNOWN LIMITATION: Context percentage won't match /context exactly.
44
- Hook JSON excludes system prompt, tools, MCP tokens. We add a baseline
45
- to compensate (~22.6k tokens typical). See:
46
- https://github.com/anthropics/claude-code/issues/13783
47
- """
48
- import sys
49
- from pathlib import Path
50
- from typing import Optional
51
-
52
- # Add parent directories to path for imports
53
- SCRIPT_DIR = Path(__file__).resolve().parent
54
- SHARED_LIB = SCRIPT_DIR.parent / "lib"
55
- sys.path.insert(0, str(SHARED_LIB.parent))
56
-
57
- from lib.base.hook_utils import emit_context, load_hook_input, get_context_percent_remaining, log_debug, log_info, log_warn, log_error, log_diagnostic
58
- from lib.base.utils import now_iso, project_dir
59
- from lib.context.context_store import (
60
- get_all_contexts,
61
- get_context_by_session_id,
62
- maybe_activate,
63
- save_state,
64
- )
65
-
66
- # Module-level flag: only save auto-state once per process lifetime
67
- _PROGRESSIVE_SAVE_MARKER = ".progressive-save-done"
68
-
69
- # Configuration
70
- SAVE_STATE_THRESHOLD = 60 # Silently save auto-state at 60% remaining
71
- HANDOFF_SUGGEST_THRESHOLD = 30 # Gentle nudge at 30% remaining (70% used)
72
- HANDOFF_PREPARE_THRESHOLD = 20 # Stronger warning at 20% remaining (80% used)
73
- CRITICAL_CONTEXT_THRESHOLD = 10 # Urgent warning at 10% remaining (90% used)
74
-
75
-
76
- def get_current_context_id(project_root: Path = None) -> Optional[str]:
77
- """Determine the current active context (most recently active)."""
78
- contexts = get_all_contexts(status="active", project_root=project_root)
79
- if contexts:
80
- return contexts[0].id
81
- return None
82
-
83
-
84
- def get_context_warning(
85
- percent_remaining: int,
86
- tokens_used: Optional[int],
87
- max_tokens: Optional[int],
88
- context_id: Optional[str],
89
- tool_name: str
90
- ) -> str:
91
- """Generate appropriate warning based on context level."""
92
- if tokens_used is not None and max_tokens is not None:
93
- tokens_used_k = tokens_used // 1000
94
- max_tokens_k = max_tokens // 1000
95
- usage_line = f"**Estimated usage**: ~{tokens_used_k}k / {max_tokens_k}k tokens"
96
- else:
97
- usage_line = f"**Estimated usage**: ~{percent_remaining}% remaining"
98
-
99
- context_line = f"\nContext ID: `{context_id}`" if context_id else ""
100
-
101
- if percent_remaining <= CRITICAL_CONTEXT_THRESHOLD:
102
- return f"""<system-reminder>
103
- ## CRITICAL CONTEXT WARNING ({percent_remaining}% remaining)
104
-
105
- {usage_line}
106
- **Triggered by**: {tool_name} tool completion
107
-
108
- **CRITICAL: Run `/handoff` now before context is compacted.**
109
- {context_line}
110
-
111
- You are about to lose context. Stop all other work and run `/handoff` immediately.
112
- </system-reminder>"""
113
-
114
- elif percent_remaining <= HANDOFF_PREPARE_THRESHOLD:
115
- return f"""<system-reminder>
116
- ## LOW CONTEXT WARNING ({percent_remaining}% remaining)
117
-
118
- {usage_line}
119
- **Triggered by**: {tool_name} tool completion
120
-
121
- **Context is getting low. Please finish your current task and run `/handoff`.**
122
- {context_line}
123
-
124
- **Actions:**
125
- 1. Complete your current atomic task (if 1-2 steps away)
126
- 2. Do NOT start new multi-step work
127
- 3. Run `/handoff` to generate a handoff document
128
- </system-reminder>"""
129
-
130
- else:
131
- return f"""<system-reminder>
132
- ## CONTEXT NOTICE ({percent_remaining}% remaining)
133
-
134
- {usage_line}
135
- **Triggered by**: {tool_name} tool completion
136
-
137
- **Consider preparing a handoff soon. When ready, run `/handoff` to generate a handoff document.**
138
- {context_line}
139
-
140
- Continue your current work, but avoid starting large new tasks.
141
- </system-reminder>"""
142
-
143
-
144
- def check_and_transition_mode(hook_input: dict) -> None:
145
- """
146
- Check if context mode needs to transition based on tool usage.
147
-
148
- Handles:
149
- - has_plan + implementation tool -> active (started implementing)
150
- - idle + implementation tool -> active
151
- """
152
- project_root = project_dir(hook_input)
153
- session_id = hook_input.get("session_id")
154
-
155
- if not session_id:
156
- return
157
-
158
- state = get_context_by_session_id(session_id, project_root)
159
- if not state:
160
- return
161
-
162
- # Implementation transitions only trigger on implementation tools
163
- implementation_tools = {"Edit", "Write", "Bash", "NotebookEdit"}
164
- tool_name = hook_input.get("tool_name", "")
165
-
166
- if tool_name not in implementation_tools:
167
- return
168
-
169
- permission_mode = hook_input.get("permission_mode", "default")
170
- maybe_activate(state.id, permission_mode, project_root=project_root, caller="context_monitor")
171
-
172
-
173
- def _try_progressive_save(hook_input: dict, percent_remaining: int) -> None:
174
- """Silently save state at SAVE_STATE_THRESHOLD (60%)."""
175
- try:
176
- session_id = hook_input.get("session_id", "")
177
- if not session_id:
178
- return
179
-
180
- project_root = project_dir(hook_input)
181
- state = get_context_by_session_id(session_id, project_root)
182
- if not state:
183
- return
184
-
185
- from lib.base.constants import get_context_dir
186
- marker_path = get_context_dir(state.id, project_root) / _PROGRESSIVE_SAVE_MARKER
187
- if marker_path.exists():
188
- try:
189
- saved_session = marker_path.read_text(encoding="utf-8").strip()
190
- if saved_session == session_id:
191
- return
192
- except OSError:
193
- pass
194
-
195
- log_info("context_monitor", f"Progressive save at {percent_remaining}% remaining")
196
-
197
- # Just update last_active and save state
198
- state.last_active = now_iso()
199
- save_state(state, project_root)
200
-
201
- try:
202
- marker_path.write_text(session_id, encoding="utf-8")
203
- except OSError:
204
- pass
205
-
206
- except Exception as e:
207
- log_warn("context_monitor", f"Progressive save error (non-fatal): {e}")
208
-
209
-
210
- def check_context_level(hook_input: dict) -> Optional[str]:
211
- """Check context level and return warning if low."""
212
- tool_name = hook_input.get("tool_name", "Unknown")
213
- percent_remaining, tokens_used, max_tokens = get_context_percent_remaining(hook_input)
214
-
215
- log_diagnostic("context_monitor", "receive", f"tool={tool_name}, pct_remaining={percent_remaining}",
216
- inputs={"tool_name": tool_name, "percent_remaining": percent_remaining,
217
- "tokens_used": tokens_used, "max_tokens": max_tokens})
218
-
219
- if percent_remaining is None:
220
- return None
221
-
222
- if percent_remaining > SAVE_STATE_THRESHOLD:
223
- return None
224
-
225
- if percent_remaining > HANDOFF_SUGGEST_THRESHOLD:
226
- _try_progressive_save(hook_input, percent_remaining)
227
- return None
228
-
229
- if tokens_used is not None and max_tokens is not None:
230
- log_info("context_monitor", f"Context: {percent_remaining}% remaining "
231
- f"(~{tokens_used//1000}k/{max_tokens//1000}k tokens)")
232
- else:
233
- log_info("context_monitor", f"Context: ~{percent_remaining}% remaining (from context.json)")
234
-
235
- project_root = project_dir(hook_input)
236
- context_id = get_current_context_id(project_root)
237
-
238
- threshold = ("critical" if percent_remaining <= CRITICAL_CONTEXT_THRESHOLD
239
- else "prepare" if percent_remaining <= HANDOFF_PREPARE_THRESHOLD
240
- else "suggest")
241
- log_diagnostic("context_monitor", "decide", f"Threshold={threshold} at {percent_remaining}%",
242
- decision=threshold, reasoning=f"{percent_remaining}% remaining",
243
- inputs={"context_id": context_id, "percent_remaining": percent_remaining})
244
-
245
- return get_context_warning(percent_remaining, tokens_used, max_tokens, context_id, tool_name)
246
-
247
-
248
- def main():
249
- """Main entry point for PostToolUse hook."""
250
- try:
251
- hook_input = load_hook_input()
252
- if not hook_input:
253
- return
254
-
255
- check_and_transition_mode(hook_input)
256
-
257
- warning = check_context_level(hook_input)
258
- if warning:
259
- emit_context(warning)
260
-
261
- except Exception as e:
262
- import traceback
263
- tb = traceback.format_exc()
264
- from lib.base.hook_utils import log_hook_error
265
- log_hook_error("context_monitor", e, "PostToolUse", traceback_str=tb)
266
-
267
-
268
- if __name__ == "__main__":
269
- from lib.base.hook_utils import run_hook
270
- run_hook(main, "context_monitor")
@@ -1,215 +0,0 @@
1
- #!/usr/bin/env python3
2
- """File suggestion hook for Claude Code.
3
-
4
- Suggests relevant files to include in context based on the current session:
5
- - Context file (context.json) for the active context
6
- - Plans from the active context's plans/ directory
7
- - Handoffs from the active context's handoffs/ directory
8
- - Reviews from the active context's reviews/ directory (including cc-native subdirectory)
9
-
10
- Hook input (from Claude Code):
11
- {
12
- "session_id": "abc123",
13
- "cwd": "/path/to/project",
14
- ...
15
- }
16
-
17
- Hook output:
18
- JSON array of file paths to suggest, or empty array if no suggestions.
19
- ["/path/to/file1.md", "/path/to/file2.md"]
20
- """
21
- import json
22
- import sys
23
- from pathlib import Path
24
- from typing import List, Optional
25
-
26
- # Add parent directories to path for imports
27
- SCRIPT_DIR = Path(__file__).resolve().parent
28
- SHARED_LIB = SCRIPT_DIR.parent / "lib"
29
- sys.path.insert(0, str(SHARED_LIB.parent))
30
-
31
- from lib.base.hook_utils import load_hook_input, log_debug, log_info, log_error
32
- from lib.base.utils import project_dir
33
- from lib.base.constants import (
34
- get_context_plans_dir,
35
- get_context_handoffs_dir,
36
- get_context_reviews_dir,
37
- get_context_file_path,
38
- )
39
- from lib.context.context_store import (
40
- get_context_by_session_id,
41
- get_all_contexts,
42
- get_context,
43
- )
44
-
45
-
46
- def get_context_files(context_id: str, project_root: Path) -> List[str]:
47
- """
48
- Get all relevant files for a context.
49
-
50
- Collects:
51
- - Context file (context.json)
52
- - Plans (most recent first)
53
- - Handoffs: index.md from subdirectories (folder-based) OR flat .md files (legacy)
54
- - Reviews: index.md from subdirectories (folder-based) OR flat review.md (legacy)
55
-
56
- Args:
57
- context_id: Context identifier
58
- project_root: Project root path
59
-
60
- Returns:
61
- List of absolute file paths, sorted by modification time (most recent first)
62
- """
63
- files = []
64
-
65
- # Get context.json file first
66
- context_file = get_context_file_path(context_id, project_root)
67
- if context_file.exists():
68
- files.append(str(context_file))
69
- log_debug("file-suggestion", f"Found context file for {context_id}")
70
-
71
- # Get plans directory
72
- plans_dir = get_context_plans_dir(context_id, project_root)
73
- if plans_dir.exists():
74
- plan_files = list(plans_dir.glob("*.md"))
75
- # Sort by modification time, most recent first
76
- plan_files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
77
- files.extend([str(p) for p in plan_files])
78
- log_debug("file-suggestion", f"Found {len(plan_files)} plans in {context_id}")
79
-
80
- # Get handoffs - prefer folder-based (index.md in subdirectories), fall back to legacy
81
- handoffs_dir = get_context_handoffs_dir(context_id, project_root)
82
- if handoffs_dir.exists():
83
- # Find handoff folders (named like YYYY-MM-DD-HHMM or YYYY-MM-DD-HHMM-N)
84
- handoff_folders = sorted(
85
- [d for d in handoffs_dir.iterdir() if d.is_dir()],
86
- key=lambda d: d.name,
87
- reverse=True # Most recent first (alphabetically sorts by date)
88
- )
89
-
90
- if handoff_folders:
91
- # Use folder-based: get index.md from most recent folder only
92
- index_file = handoff_folders[0] / "index.md"
93
- if index_file.exists():
94
- files.append(str(index_file))
95
- log_debug("file-suggestion", f"Found handoff folder: {handoff_folders[0].name}")
96
- else:
97
- # Legacy support: flat .md files directly in handoffs/
98
- legacy_handoffs = [f for f in handoffs_dir.glob("*.md") if f.is_file()]
99
- legacy_handoffs.sort(key=lambda p: p.stat().st_mtime, reverse=True)
100
- if legacy_handoffs:
101
- files.append(str(legacy_handoffs[0])) # Only most recent legacy
102
- log_debug("file-suggestion", f"Found {len(legacy_handoffs)} legacy handoffs in {context_id}")
103
-
104
- # Get reviews - prefer folder-based (index.md in subdirectories), fall back to legacy
105
- reviews_dir = get_context_reviews_dir(context_id, project_root) / "cc-native"
106
- if reviews_dir.exists():
107
- # Find review folders (named like YYYY-MM-DD-HHMM-iteration-N)
108
- review_folders = sorted(
109
- [d for d in reviews_dir.iterdir() if d.is_dir()],
110
- key=lambda d: d.name,
111
- reverse=True # Most recent first
112
- )
113
-
114
- if review_folders:
115
- # Use folder-based: get index.md from most recent folder only
116
- index_file = review_folders[0] / "index.md"
117
- if index_file.exists():
118
- files.append(str(index_file))
119
- log_debug("file-suggestion", f"Found review folder: {review_folders[0].name}")
120
- else:
121
- # Legacy support: flat review.md directly in cc-native/
122
- legacy_review = reviews_dir / "review.md"
123
- if legacy_review.exists():
124
- files.append(str(legacy_review))
125
- log_debug("file-suggestion", f"Found legacy review.md in {context_id}")
126
-
127
- return files
128
-
129
-
130
- def get_active_context_id(session_id: str, project_root: Path) -> Optional[str]:
131
- """
132
- Determine the active context for suggestions.
133
-
134
- Priority:
135
- 1. Context bound to current session_id
136
- 2. Single in-flight context (if only one exists)
137
- 3. None (no suggestions if ambiguous)
138
-
139
- Args:
140
- session_id: Current session identifier
141
- project_root: Project root path
142
-
143
- Returns:
144
- Context ID or None
145
- """
146
- # Try session_id lookup first
147
- if session_id and session_id != "unknown":
148
- context = get_context_by_session_id(session_id, project_root)
149
- if context:
150
- log_debug("file-suggestion", f"Found context by session: {context.id}")
151
- return context.id
152
-
153
- # Fall back to single active (non-idle) context
154
- active = [c for c in get_all_contexts(status="active", project_root=project_root)
155
- if c.mode != "idle"]
156
- if len(active) == 1:
157
- log_debug("file-suggestion", f"Using single active context: {active[0].id}")
158
- return active[0].id
159
-
160
- log_debug("file-suggestion", f"No unique context found (active: {len(active)})")
161
- return None
162
-
163
-
164
- def main():
165
- """
166
- Main entry point for file suggestion hook.
167
-
168
- Reads hook input from stdin, determines active context,
169
- and outputs file suggestions as JSON array.
170
- """
171
- try:
172
- # Read hook input using shared utility
173
- hook_input = load_hook_input()
174
-
175
- if not hook_input:
176
- print("[]")
177
- return
178
-
179
- # Get project root and session ID
180
- project_root = project_dir(hook_input)
181
- session_id = hook_input.get("session_id", "unknown")
182
-
183
- log_debug("file-suggestion", f"Session: {session_id[:8]}..., Project: {project_root}")
184
-
185
- # Determine active context
186
- context_id = get_active_context_id(session_id, project_root)
187
-
188
- if not context_id:
189
- print("[]")
190
- return
191
-
192
- # Collect file suggestions
193
- suggestions = get_context_files(context_id, project_root)
194
-
195
- # Limit suggestions to prevent overwhelming the context
196
- MAX_SUGGESTIONS = 10
197
- if len(suggestions) > MAX_SUGGESTIONS:
198
- log_debug("file-suggestion", f"Limiting suggestions to {MAX_SUGGESTIONS} (was {len(suggestions)})")
199
- suggestions = suggestions[:MAX_SUGGESTIONS]
200
-
201
- # Output suggestions as JSON array
202
- log_info("file-suggestion", f"Suggesting {len(suggestions)} files")
203
- print(json.dumps(suggestions))
204
-
205
- except Exception as e:
206
- import traceback
207
- tb = traceback.format_exc()
208
- from lib.base.hook_utils import log_hook_error
209
- log_hook_error("file-suggestion", e, "SessionStart", traceback_str=tb)
210
- print("[]")
211
-
212
-
213
- if __name__ == "__main__":
214
- from lib.base.hook_utils import run_hook
215
- run_hook(main, "file_suggestion")
@@ -1,104 +0,0 @@
1
- #!/usr/bin/env python3
2
- """PreCompact hook - saves auto-state before context compaction.
3
-
4
- Critical: saves state before context compaction destroys token history.
5
- After compaction, SessionStart fires with source="compact" and the
6
- restored auto-state provides continuity context.
7
-
8
- Hook input (from Claude Code):
9
- {
10
- "hook_event_name": "PreCompact",
11
- "session_id": "abc123",
12
- "transcript_path": "/path/to/transcript.jsonl",
13
- "cwd": "/path/to/project",
14
- ...
15
- }
16
-
17
- Hook output:
18
- - Silent (no stdout output needed)
19
- - Logs to stderr for debugging
20
- """
21
- import sys
22
- from pathlib import Path
23
-
24
- # Add parent directories to path for imports
25
- SCRIPT_DIR = Path(__file__).resolve().parent
26
- SHARED_LIB = SCRIPT_DIR.parent / "lib"
27
- sys.path.insert(0, str(SHARED_LIB.parent))
28
-
29
- from lib.base.hook_utils import load_hook_input, log_debug, log_info, log_error
30
- from lib.base.utils import project_dir
31
- from lib.context.context_store import get_context_by_session_id, save_state
32
-
33
-
34
- def main():
35
- """Save auto-state before compaction."""
36
- try:
37
- hook_input = load_hook_input()
38
- if not hook_input:
39
- return
40
-
41
- session_id = hook_input.get("session_id", "")
42
- transcript_path = hook_input.get("transcript_path")
43
- project_root = project_dir(hook_input)
44
-
45
- if not session_id:
46
- log_debug("pre_compact", "No session_id, skipping")
47
- return
48
-
49
- log_info("pre_compact", f"Saving state before compaction: {session_id[:8]}...")
50
-
51
- # Find context bound to this session
52
- context = get_context_by_session_id(session_id, project_root)
53
- if not context:
54
- log_debug("pre_compact", "No context bound to this session, skipping")
55
- return
56
-
57
- # Save last_session snapshot directly to state.json
58
- import subprocess
59
- git_state = {}
60
- try:
61
- branch = subprocess.run(
62
- ["git", "rev-parse", "--abbrev-ref", "HEAD"],
63
- capture_output=True, text=True, timeout=5
64
- )
65
- git_state["branch"] = branch.stdout.strip() if branch.returncode == 0 else "unknown"
66
-
67
- status = subprocess.run(
68
- ["git", "status", "--short"],
69
- capture_output=True, text=True, timeout=5
70
- )
71
- if status.returncode == 0 and status.stdout.strip():
72
- git_state["uncommitted_files"] = [
73
- line.split(None, 1)[-1] for line in status.stdout.strip().split("\n")[:10]
74
- ]
75
-
76
- log = subprocess.run(
77
- ["git", "log", "-1", "--format=%h %s"],
78
- capture_output=True, text=True, timeout=5
79
- )
80
- if log.returncode == 0:
81
- git_state["last_commit_short"] = log.stdout.strip()
82
- except Exception:
83
- pass
84
-
85
- from lib.base.utils import now_iso
86
- context.last_session = {
87
- "session_id": session_id,
88
- "saved_at": now_iso(),
89
- "save_reason": "pre_compact",
90
- "git_state": git_state,
91
- }
92
- save_state(context, project_root)
93
- log_info("pre_compact", f"State saved for {context.id}")
94
-
95
- except Exception as e:
96
- import traceback
97
- tb = traceback.format_exc()
98
- from lib.base.hook_utils import log_hook_error
99
- log_hook_error("pre_compact", e, "PreCompact", traceback_str=tb)
100
-
101
-
102
- if __name__ == "__main__":
103
- from lib.base.hook_utils import run_hook
104
- run_hook(main, "pre_compact")