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.
- package/bin/run.js +1 -1
- package/dist/commands/clear.js +28 -131
- package/dist/commands/init/index.js +3 -3
- package/dist/lib/gitignore-manager.d.ts +32 -0
- package/dist/lib/gitignore-manager.js +141 -2
- package/dist/templates/CLAUDE.md +8 -8
- package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
- package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
- package/dist/templates/_shared/.claude/settings.json +7 -7
- package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
- package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
- package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
- package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
- package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
- package/dist/templates/_shared/hooks-ts/session_end.ts +107 -0
- package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
- package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
- package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
- package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
- package/dist/templates/_shared/lib-ts/base/atomic-write.ts +12 -12
- package/dist/templates/_shared/lib-ts/base/constants.ts +22 -15
- package/dist/templates/_shared/lib-ts/base/git-state.ts +1 -1
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +129 -50
- package/dist/templates/_shared/lib-ts/base/inference.ts +28 -21
- package/dist/templates/_shared/lib-ts/base/logger.ts +15 -2
- package/dist/templates/_shared/lib-ts/base/state-io.ts +9 -7
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +131 -131
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +142 -0
- package/dist/templates/_shared/lib-ts/base/utils.ts +69 -69
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +30 -24
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +50 -32
- package/dist/templates/_shared/lib-ts/context/context-store.ts +76 -48
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +43 -23
- package/dist/templates/_shared/lib-ts/context/task-tracker.ts +10 -6
- package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +11 -10
- package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +158 -0
- package/dist/templates/_shared/lib-ts/templates/formatters.ts +6 -4
- package/dist/templates/_shared/lib-ts/types.ts +68 -55
- package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
- package/dist/templates/_shared/scripts/resume_handoff.ts +345 -0
- package/dist/templates/_shared/scripts/save_handoff.ts +3 -3
- package/dist/templates/_shared/scripts/status_line.ts +687 -0
- package/dist/templates/cc-native/.claude/settings.json +175 -185
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +0 -2
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +1027 -0
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +156 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +792 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +144 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +115 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +120 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +168 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +250 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +275 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +107 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +240 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +385 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +14 -1
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
- package/dist/templates/_shared/hooks/__init__.py +0 -16
- package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/archive_plan.py +0 -177
- package/dist/templates/_shared/hooks/context_monitor.py +0 -270
- package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
- package/dist/templates/_shared/hooks/pre_compact.py +0 -104
- package/dist/templates/_shared/hooks/session_end.py +0 -173
- package/dist/templates/_shared/hooks/session_start.py +0 -206
- package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
- package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
- package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
- package/dist/templates/_shared/lib/__init__.py +0 -1
- package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__init__.py +0 -65
- 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__/logger.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/atomic_write.py +0 -180
- package/dist/templates/_shared/lib/base/constants.py +0 -358
- package/dist/templates/_shared/lib/base/hook_utils.py +0 -339
- package/dist/templates/_shared/lib/base/inference.py +0 -307
- package/dist/templates/_shared/lib/base/logger.py +0 -305
- package/dist/templates/_shared/lib/base/stop_words.py +0 -221
- package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
- package/dist/templates/_shared/lib/base/utils.py +0 -263
- package/dist/templates/_shared/lib/context/__init__.py +0 -102
- 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_formatter.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/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__/plan_manager.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/__pycache__/task_tracker.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
- package/dist/templates/_shared/lib/context/context_selector.py +0 -508
- package/dist/templates/_shared/lib/context/context_store.py +0 -653
- package/dist/templates/_shared/lib/context/plan_manager.py +0 -303
- package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
- package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
- package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
- package/dist/templates/_shared/lib/templates/README.md +0 -206
- package/dist/templates/_shared/lib/templates/__init__.py +0 -36
- 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/formatters.py +0 -146
- package/dist/templates/_shared/lib/templates/plan_context.py +0 -73
- package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/save_handoff.py +0 -357
- package/dist/templates/_shared/scripts/status_line.py +0 -716
- package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/MIGRATION.md +0 -86
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -954
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
- package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
- package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
- 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/constants.py +0 -45
- package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
- 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 +0 -215
- package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
- package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
- package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1071
- package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
- 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")
|