aiwcli 0.10.3 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) 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 +107 -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/git-state.ts +1 -1
  24. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +129 -50
  25. package/dist/templates/_shared/lib-ts/base/inference.ts +28 -21
  26. package/dist/templates/_shared/lib-ts/base/logger.ts +15 -2
  27. package/dist/templates/_shared/lib-ts/base/state-io.ts +9 -7
  28. package/dist/templates/_shared/lib-ts/base/stop-words.ts +131 -131
  29. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +142 -0
  30. package/dist/templates/_shared/lib-ts/base/utils.ts +69 -69
  31. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +30 -24
  32. package/dist/templates/_shared/lib-ts/context/context-selector.ts +50 -32
  33. package/dist/templates/_shared/lib-ts/context/context-store.ts +76 -48
  34. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +43 -23
  35. package/dist/templates/_shared/lib-ts/context/task-tracker.ts +10 -6
  36. package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +11 -10
  37. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +158 -0
  38. package/dist/templates/_shared/lib-ts/templates/formatters.ts +6 -4
  39. package/dist/templates/_shared/lib-ts/types.ts +68 -55
  40. package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
  41. package/dist/templates/_shared/scripts/resume_handoff.ts +345 -0
  42. package/dist/templates/_shared/scripts/save_handoff.ts +3 -3
  43. package/dist/templates/_shared/scripts/status_line.ts +687 -0
  44. package/dist/templates/cc-native/.claude/settings.json +175 -185
  45. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
  46. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +0 -2
  47. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
  48. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
  49. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +1027 -0
  50. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
  51. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +156 -0
  52. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +792 -0
  53. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
  54. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +144 -0
  55. package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
  56. package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
  57. package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +115 -0
  58. package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
  59. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +120 -0
  60. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +168 -0
  61. package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
  62. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +250 -0
  63. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +275 -0
  64. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
  65. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +107 -0
  66. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
  67. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
  68. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +240 -0
  69. package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
  70. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +385 -0
  71. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
  72. package/dist/templates/cc-native/_cc-native/plan-review.config.json +14 -1
  73. package/oclif.manifest.json +1 -1
  74. package/package.json +2 -2
  75. package/dist/templates/_shared/hooks/__init__.py +0 -16
  76. package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  77. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  78. package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
  79. package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
  80. package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
  81. package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
  82. package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
  83. package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
  84. package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
  85. package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
  86. package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
  87. package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  88. package/dist/templates/_shared/hooks/archive_plan.py +0 -177
  89. package/dist/templates/_shared/hooks/context_monitor.py +0 -270
  90. package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
  91. package/dist/templates/_shared/hooks/pre_compact.py +0 -104
  92. package/dist/templates/_shared/hooks/session_end.py +0 -173
  93. package/dist/templates/_shared/hooks/session_start.py +0 -206
  94. package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
  95. package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
  96. package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
  97. package/dist/templates/_shared/lib/__init__.py +0 -1
  98. package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  99. package/dist/templates/_shared/lib/base/__init__.py +0 -65
  100. package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
  101. package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
  102. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  103. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  104. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  105. package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
  106. package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
  107. package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
  108. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  109. package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
  110. package/dist/templates/_shared/lib/base/constants.py +0 -358
  111. package/dist/templates/_shared/lib/base/hook_utils.py +0 -339
  112. package/dist/templates/_shared/lib/base/inference.py +0 -307
  113. package/dist/templates/_shared/lib/base/logger.py +0 -305
  114. package/dist/templates/_shared/lib/base/stop_words.py +0 -221
  115. package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
  116. package/dist/templates/_shared/lib/base/utils.py +0 -263
  117. package/dist/templates/_shared/lib/context/__init__.py +0 -102
  118. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  119. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  120. package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
  121. package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
  122. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  123. package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
  124. package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
  125. package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
  126. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  127. package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
  128. package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
  129. package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
  130. package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
  131. package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
  132. package/dist/templates/_shared/lib/context/context_selector.py +0 -508
  133. package/dist/templates/_shared/lib/context/context_store.py +0 -653
  134. package/dist/templates/_shared/lib/context/plan_manager.py +0 -303
  135. package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
  136. package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
  137. package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
  138. package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
  139. package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
  140. package/dist/templates/_shared/lib/templates/README.md +0 -206
  141. package/dist/templates/_shared/lib/templates/__init__.py +0 -36
  142. package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
  143. package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
  144. package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
  145. package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
  146. package/dist/templates/_shared/lib/templates/formatters.py +0 -146
  147. package/dist/templates/_shared/lib/templates/plan_context.py +0 -73
  148. package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
  149. package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
  150. package/dist/templates/_shared/scripts/save_handoff.py +0 -357
  151. package/dist/templates/_shared/scripts/status_line.py +0 -716
  152. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
  153. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
  154. package/dist/templates/cc-native/MIGRATION.md +0 -86
  155. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  156. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  157. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
  158. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
  159. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
  160. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
  161. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
  162. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -954
  163. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
  164. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
  165. package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
  166. package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
  167. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  168. package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
  169. package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
  170. package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
  171. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  172. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  173. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  174. package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
  175. package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
  176. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
  177. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
  178. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  179. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  180. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  181. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  182. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  183. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
  184. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
  185. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
  186. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
  187. package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
  188. package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1071
  189. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  190. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
  191. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
@@ -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")
@@ -1,173 +0,0 @@
1
- #!/usr/bin/env python3
2
- """SessionEnd hook - saves session state to state.json.
3
-
4
- Fires when session terminates (quit, /clear, logout). Saves last_session
5
- data directly to state.json for restoration on next session.
6
-
7
- Hook input (from Claude Code):
8
- {
9
- "hook_event_name": "SessionEnd",
10
- "session_id": "abc123",
11
- "source": "prompt_input_exit", # or "clear", "logout", "compact"
12
- "transcript_path": "/path/to/transcript.jsonl",
13
- "cwd": "/path/to/project",
14
- ...
15
- }
16
-
17
- Hook output:
18
- - Silent (no stdout output needed for SessionEnd)
19
- - Logs to stderr for debugging
20
- """
21
- import hashlib
22
- import subprocess
23
- import sys
24
- from pathlib import Path
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_warn, log_error, log_diagnostic
32
- from lib.base.utils import now_iso, project_dir
33
- from lib.context.context_store import get_context_by_session_id, save_state
34
- from lib.context.plan_manager import find_latest_plan, normalize_plan_content, generate_plan_id, extract_plan_anchors
35
-
36
-
37
- def _get_git_state(project_root: Path) -> dict:
38
- """Capture current git state for restoration."""
39
- git_state = {}
40
- try:
41
- result = subprocess.run(
42
- ["git", "rev-parse", "--abbrev-ref", "HEAD"],
43
- capture_output=True, text=True, cwd=str(project_root), timeout=5,
44
- )
45
- if result.returncode == 0:
46
- git_state["branch"] = result.stdout.strip()
47
-
48
- result = subprocess.run(
49
- ["git", "diff", "--name-only"],
50
- capture_output=True, text=True, cwd=str(project_root), timeout=5,
51
- )
52
- if result.returncode == 0:
53
- files = [f for f in result.stdout.strip().split("\n") if f]
54
- git_state["uncommitted_files"] = files
55
-
56
- result = subprocess.run(
57
- ["git", "log", "-1", "--oneline"],
58
- capture_output=True, text=True, cwd=str(project_root), timeout=5,
59
- )
60
- if result.returncode == 0:
61
- git_state["last_commit_short"] = result.stdout.strip()
62
- except Exception as e:
63
- log_warn("session_end", f"Git state capture error (non-fatal): {e}")
64
-
65
- return git_state
66
-
67
-
68
- def main():
69
- """Save session state to state.json."""
70
- try:
71
- hook_input = load_hook_input()
72
- if not hook_input:
73
- return
74
-
75
- session_id = hook_input.get("session_id", "")
76
- source = hook_input.get("source", "other")
77
- transcript_path = hook_input.get("transcript_path")
78
- project_root = project_dir(hook_input)
79
-
80
- if not session_id:
81
- log_debug("session_end", "No session_id, skipping")
82
- return
83
-
84
- log_info("session_end", f"Session ending: {session_id[:8]}... reason={source}")
85
- log_diagnostic("session_end", "receive", f"session={session_id[:8]}, source={source}",
86
- inputs={"session_id": session_id[:12], "source": source})
87
-
88
- # Find context bound to this session
89
- state = get_context_by_session_id(session_id, project_root)
90
- if not state:
91
- log_debug("session_end", "No context bound to this session, skipping")
92
- return
93
-
94
- log_info("session_end", f"Found context: {state.id}")
95
-
96
- # Capture git state
97
- git_state = _get_git_state(project_root)
98
-
99
- # Save last_session directly to state.json
100
- state.last_session = {
101
- "session_id": session_id,
102
- "save_reason": source,
103
- "saved_at": now_iso(),
104
- "transcript_path": transcript_path,
105
- "git_state": git_state,
106
- }
107
- state.last_active = now_iso()
108
-
109
- # Only assign plan fields and stage if NOT in plan mode.
110
- # If permission_mode == "plan", ExitPlanMode was rejected (user pressed Escape),
111
- # so we should not stage the archived plan for the next session.
112
- permission_mode = hook_input.get("permission_mode", "default")
113
-
114
- if permission_mode != "plan":
115
- # Fallback: assign plan fields if PostToolUse:ExitPlanMode didn't fire.
116
- # When ExitPlanMode triggers /clear, the session terminates before PostToolUse
117
- # hooks can run, so plan_accepted.py never fires. Detect this by checking
118
- # for an archived plan that hasn't been assigned yet.
119
- if not state.plan_hash:
120
- latest_plan_path = find_latest_plan(state.id, project_root)
121
- if latest_plan_path:
122
- try:
123
- content = Path(latest_plan_path).read_text(encoding="utf-8")
124
- normalized = normalize_plan_content(content)
125
- state.plan_hash = hashlib.sha256(normalized.encode("utf-8")).hexdigest()[:12]
126
- state.plan_path = latest_plan_path
127
- state.plan_signature = content[:200]
128
- state.plan_id = generate_plan_id()
129
- state.plan_anchors = extract_plan_anchors(content)
130
- state.plan_consumed = False
131
- log_info("session_end", f"Fallback: assigned archived plan for {state.id} (hash: {state.plan_hash})")
132
- except Exception as e:
133
- log_warn("session_end", f"Fallback plan assignment failed: {e}")
134
-
135
- # If a plan is assigned, not yet consumed, and mode is active, stage it for next session
136
- if state.plan_hash and state.mode == "active" and not state.plan_consumed:
137
- state.mode = "has_plan"
138
- log_info("session_end", f"Staged plan for next session: {state.id} -> has_plan")
139
- elif state.plan_hash and state.mode == "active" and state.plan_consumed:
140
- log_debug("session_end", f"Plan already consumed for {state.id}, not re-staging")
141
- log_diagnostic("session_end", "decide", f"Skipping re-stage for {state.id}",
142
- decision="skip_restage", reasoning="plan_hash exists but plan_consumed=True",
143
- inputs={"plan_hash": state.plan_hash, "plan_consumed": True})
144
- else:
145
- log_info("session_end", f"Plan mode active (rejected), skipping plan staging for {state.id}")
146
-
147
- # Handoff staging (mirrors plan staging above)
148
- # Note: if plan already set has_plan, mode != "active" so handoff check skips (plan takes priority)
149
- if state.handoff_path and state.mode == "active" and not state.handoff_consumed:
150
- state.mode = "has_handoff"
151
- log_info("session_end", f"Staged handoff for next session: {state.id} -> has_handoff")
152
- elif state.handoff_path and state.mode == "active" and state.handoff_consumed:
153
- log_debug("session_end", f"Handoff already consumed for {state.id}, not re-staging")
154
-
155
- if save_state(state, project_root):
156
- log_info("session_end", f"Saved last_session for {state.id}")
157
- log_diagnostic("session_end", "result", f"Saved state for {state.id}",
158
- decision="saved", inputs={"context_id": state.id, "mode": state.mode,
159
- "has_plan_hash": bool(state.plan_hash),
160
- "git_files": len(git_state.get("uncommitted_files", []))})
161
- else:
162
- log_error("session_end", f"Failed to save state for {state.id}")
163
-
164
- except Exception as e:
165
- import traceback
166
- tb = traceback.format_exc()
167
- from lib.base.hook_utils import log_hook_error
168
- log_hook_error("session_end", e, "SessionEnd", traceback_str=tb)
169
-
170
-
171
- if __name__ == "__main__":
172
- from lib.base.hook_utils import run_hook
173
- run_hook(main, "session_end")