aiwcli 0.9.8 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/bin/run.js +5 -2
  2. package/dist/lib/claude-settings-types.d.ts +2 -0
  3. package/dist/templates/CLAUDE.md +3 -3
  4. package/dist/templates/_shared/.claude/settings.json +4 -0
  5. package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  6. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  7. package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
  8. package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
  9. package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
  10. package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
  11. package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
  12. package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
  13. package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
  14. package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
  15. package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  16. package/dist/templates/_shared/hooks/archive_plan.py +87 -178
  17. package/dist/templates/_shared/hooks/context_monitor.py +104 -247
  18. package/dist/templates/_shared/hooks/file-suggestion.py +26 -23
  19. package/dist/templates/_shared/hooks/pre_compact.py +47 -32
  20. package/dist/templates/_shared/hooks/session_end.py +103 -60
  21. package/dist/templates/_shared/hooks/session_start.py +110 -81
  22. package/dist/templates/_shared/hooks/task_create_capture.py +26 -50
  23. package/dist/templates/_shared/hooks/task_update_capture.py +42 -115
  24. package/dist/templates/_shared/hooks/user_prompt_submit.py +61 -61
  25. package/dist/templates/_shared/lib/base/__init__.py +16 -0
  26. package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
  27. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  28. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  29. package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
  30. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  31. package/dist/templates/_shared/lib/base/hook_utils.py +199 -11
  32. package/dist/templates/_shared/lib/base/inference.py +121 -0
  33. package/dist/templates/_shared/lib/base/logger.py +291 -0
  34. package/dist/templates/_shared/lib/base/utils.py +42 -9
  35. package/dist/templates/_shared/lib/context/__init__.py +72 -80
  36. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  37. package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
  38. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  39. package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
  40. package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
  41. package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
  42. package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
  43. package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
  44. package/dist/templates/_shared/lib/context/context_formatter.py +316 -0
  45. package/dist/templates/_shared/lib/context/context_selector.py +491 -0
  46. package/dist/templates/_shared/lib/context/context_store.py +636 -0
  47. package/dist/templates/_shared/lib/context/plan_manager.py +204 -0
  48. package/dist/templates/_shared/lib/context/task_tracker.py +188 -0
  49. package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
  50. package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
  51. package/dist/templates/_shared/lib/handoff/document_generator.py +14 -40
  52. package/dist/templates/_shared/lib/templates/README.md +5 -13
  53. package/dist/templates/_shared/lib/templates/__init__.py +2 -6
  54. package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
  55. package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
  56. package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
  57. package/dist/templates/_shared/lib/templates/plan_context.py +1 -38
  58. package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
  59. package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
  60. package/dist/templates/_shared/scripts/save_handoff.py +39 -19
  61. package/dist/templates/_shared/scripts/status_line.py +701 -0
  62. package/dist/templates/_shared/workflows/handoff.md +9 -3
  63. package/dist/templates/cc-native/.claude/settings.json +41 -8
  64. package/dist/templates/cc-native/CC-NATIVE-README.md +25 -28
  65. package/dist/templates/cc-native/MIGRATION.md +1 -1
  66. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +14 -39
  67. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +49 -21
  68. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  69. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  70. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
  71. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
  72. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
  73. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
  74. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +57 -55
  75. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +163 -131
  76. package/dist/templates/cc-native/_cc-native/hooks/plan_accepted.py +127 -0
  77. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +81 -0
  78. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +26 -25
  79. package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +6 -4
  80. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  81. package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
  82. package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
  83. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  84. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  85. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  86. package/dist/templates/cc-native/_cc-native/lib/debug.py +37 -22
  87. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +34 -29
  88. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  89. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  90. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  91. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  92. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  93. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +26 -21
  94. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +12 -7
  95. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +12 -7
  96. package/dist/templates/cc-native/_cc-native/lib/state.py +31 -16
  97. package/dist/templates/cc-native/_cc-native/lib/utils.py +207 -40
  98. package/dist/templates/cc-native/_cc-native/plan-review.config.json +1 -2
  99. package/oclif.manifest.json +1 -1
  100. package/package.json +1 -1
  101. package/dist/templates/_shared/hooks/context_enforcer.py +0 -625
  102. package/dist/templates/_shared/hooks/task_create_atomicity.py +0 -177
  103. package/dist/templates/_shared/lib/context/auto_state.py +0 -167
  104. package/dist/templates/_shared/lib/context/cache.py +0 -444
  105. package/dist/templates/_shared/lib/context/context_extractor.py +0 -115
  106. package/dist/templates/_shared/lib/context/context_manager.py +0 -1057
  107. package/dist/templates/_shared/lib/context/discovery.py +0 -554
  108. package/dist/templates/_shared/lib/context/event_log.py +0 -316
  109. package/dist/templates/_shared/lib/context/plan_archive.py +0 -101
  110. package/dist/templates/_shared/lib/context/task_sync.py +0 -407
  111. package/dist/templates/_shared/lib/templates/persona_questions.py +0 -113
  112. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  113. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
  114. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
  115. package/dist/templates/cc-native/_cc-native/lib/async_archive.py +0 -68
  116. package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +0 -98
package/bin/run.js CHANGED
@@ -11,9 +11,12 @@ import {execute} from '@oclif/core'
11
11
  const args = process.argv.slice(2)
12
12
  const firstArg = args[0] ?? ''
13
13
  const hasCommand = args.length > 0 && !firstArg.startsWith('-')
14
- const isHelpOrVersion = firstArg === '--help' || firstArg === '-h' || firstArg === '--version'
14
+ const isHelpOrVersion = firstArg === '--help' || firstArg === '-h' || firstArg === '--version' || firstArg === '-v'
15
+
16
+ // Map -v to --version since oclif doesn't natively understand -v
17
+ const resolvedArgs = firstArg === '-v' ? ['--version', ...args.slice(1)] : args
15
18
 
16
19
  await execute({
17
20
  dir: import.meta.url,
18
- args: hasCommand || isHelpOrVersion ? args : ['launch', ...args],
21
+ args: hasCommand || isHelpOrVersion ? resolvedArgs : ['launch', ...resolvedArgs],
19
22
  })
@@ -6,6 +6,8 @@
6
6
  * Hook command configuration
7
7
  */
8
8
  export interface HookCommand {
9
+ /** If true, runs in background without blocking */
10
+ async?: boolean;
9
11
  /** Command to execute */
10
12
  command: string;
11
13
  /** Optional timeout in seconds */
@@ -25,13 +25,12 @@ packages/cli/src/templates/
25
25
  │ ├── hooks/ # Shared hook scripts (context, tasks, sessions)
26
26
  │ └── lib/ # Shared Python libraries
27
27
  │ ├── base/ # Core: atomic_write, constants, inference, utils
28
- │ ├── context/ # Context management, event sourcing, discovery
28
+ │ ├── context/ # Context CRUD, selection, formatting, plans, tasks
29
29
  │ ├── handoff/ # Session handoff document generation
30
30
  │ └── templates/ # Output formatters, plan context templates
31
31
 
32
32
  ├── cc-native/ # CC-Native method template
33
33
  │ ├── _cc-native/ # Method-specific hooks, lib, agents, workflows, scripts
34
- │ ├── _shared/ # Copy of shared infrastructure (installed together)
35
34
  │ ├── .claude/ # Claude Code: settings.json, commands/, agents/
36
35
  │ ├── .windsurf/ # Windsurf: workflows/
37
36
  │ └── .gitignore
@@ -61,7 +60,7 @@ packages/cli/src/templates/
61
60
 
62
61
  | Tier | Location | Purpose |
63
62
  |------|----------|---------|
64
- | Shared | `_shared/` | Cross-method hooks and libraries (context management, task sync, sessions) |
63
+ | Shared | `_shared/` | Cross-method hooks and libraries (context management, task tracking, sessions) |
65
64
  | Method | `_{method}/` or `.aiwcli/_{method}/` | Method-specific templates, workflows, hooks, config |
66
65
  | IDE | `.{ide}/` | IDE-specific command stubs, settings, workflow definitions |
67
66
  | Config | `.{ide}/settings.json` | Hooks, model prefs, method settings (merged on install) |
@@ -203,3 +202,4 @@ Load and execute `_{method}/workflows/{name}.md`.
203
202
  - Full workflows in IDE command files
204
203
  - Hardcoded paths without method namespace
205
204
  - Putting hook scripts directly in IDE directories (`.claude/hooks/`)
205
+ - Creating `_shared/` directories inside method templates (e.g., `cc-native/_shared/`). All shared code lives in `packages/cli/src/templates/_shared/`. Method templates reference shared code via sys.path at runtime, not by copying.
@@ -1,4 +1,8 @@
1
1
  {
2
+ "statusLine": {
3
+ "type": "command",
4
+ "command": "python .aiwcli/_shared/scripts/status_line.py"
5
+ },
2
6
  "fileSuggestion": {
3
7
  "type": "command",
4
8
  "command": "python .aiwcli/_shared/hooks/file-suggestion.py"
@@ -1,21 +1,20 @@
1
1
  #!/usr/bin/env python3
2
- """Plan archival hook for ExitPlanMode PostToolUse event.
2
+ """Plan archival hook for ExitPlanMode PermissionRequest event.
3
3
 
4
- This hook runs when ExitPlanMode completes, extracting the plan path from
5
- the tool result and archiving it to the active context.
4
+ This hook runs when ExitPlanMode is requested (BEFORE user accepts/rejects),
5
+ extracting the plan path from the tool input and archiving it to the
6
+ context's plans/ folder. It does NOT modify state.json plan fields or mode.
6
7
 
7
- Actions:
8
- 1. Detect ExitPlanMode PostToolUse event
9
- 2. Extract plan path from tool result
10
- 3. Check if plan already archived (avoid duplicates)
11
- 4. Determine active context
12
- 5. Archive plan to context's plans folder
13
- 6. Set context in_flight.mode = "pending_implementation"
8
+ Separation of concerns:
9
+ - archive_plan.py (PermissionRequest) -> archives file only, no state.json changes
10
+ - plan_accepted.py (PostToolUse) -> assigns plan fields (hash/signature/path) to state.json
11
+ - session_end.py (SessionEnd) -> transitions active -> has_plan when plan is assigned
12
+ - context_selector.py -> matches plan content, transitions has_plan -> active
14
13
 
15
14
  Usage in .claude/settings.json:
16
15
  {
17
16
  "hooks": {
18
- "PostToolUse": [{
17
+ "PermissionRequest": [{
19
18
  "matcher": "ExitPlanMode",
20
19
  "hooks": [{
21
20
  "type": "command",
@@ -26,7 +25,6 @@ Usage in .claude/settings.json:
26
25
  }
27
26
  }
28
27
  """
29
- import json
30
28
  import re
31
29
  import sys
32
30
  from pathlib import Path
@@ -37,12 +35,11 @@ SCRIPT_DIR = Path(__file__).resolve().parent
37
35
  SHARED_LIB = SCRIPT_DIR.parent / "lib"
38
36
  sys.path.insert(0, str(SHARED_LIB.parent))
39
37
 
40
- from lib.base.hook_utils import load_hook_input
41
- from lib.context.plan_archive import archive_plan_to_context
42
- from lib.context.context_manager import get_all_contexts
43
- from lib.context.context_extractor import extract_context_id_for_session
44
- from lib.base.utils import eprint, project_dir
38
+ from lib.base.hook_utils import load_hook_input, log_debug, log_info, log_warn, log_error
39
+ from lib.base.utils import project_dir
45
40
  from lib.base.constants import get_context_dir
41
+ from lib.context.context_store import get_context_by_session_id
42
+ from lib.context.plan_manager import archive_plan, extract_plan_path_from_result
46
43
 
47
44
  # Import debug cleanup function from cc-native lib
48
45
  _cc_native_lib = SCRIPT_DIR.parent / "_cc-native" / "lib"
@@ -51,210 +48,122 @@ try:
51
48
  from debug import cleanup_debug_folder
52
49
  except ImportError:
53
50
  def cleanup_debug_folder(context_path):
54
- pass # Fallback if debug module not available
51
+ pass
55
52
 
56
53
 
57
- def extract_plan_path_from_result(tool_result: str) -> Optional[str]:
58
- """
59
- Extract plan path from ExitPlanMode tool result.
60
-
61
- Looks for pattern: "Your plan has been saved to: <path>"
62
- """
63
- match = re.search(r'Your plan has been saved to:\s*(.+\.md)', tool_result)
64
- if match:
65
- return match.group(1).strip()
66
- return None
67
-
68
-
69
- def on_plan_archive():
70
- """
71
- Plan archival hook - archives plan when exiting plan mode.
72
-
73
- Called from PostToolUse on ExitPlanMode - extracts plan path from result
74
- and archives to the active context.
75
- """
76
- # Read hook input using shared utility
77
- hook_input = load_hook_input()
78
- if not hook_input:
79
- eprint("[archive_plan] No valid JSON input")
80
- return
81
-
82
- hook_event = hook_input.get("hook_event_name", "unknown")
83
- tool_name = hook_input.get("tool_name", "")
84
- print(f"[archive_plan] Hook triggered: {hook_event}")
85
- print(f"[archive_plan] Tool name: {tool_name}")
86
- print(f"[archive_plan] Hook input keys: {list(hook_input.keys())}")
87
-
88
- # Special handling for ExitPlanMode - don't check permission_mode
89
- is_exit_plan_mode = (hook_event == "PostToolUse" and tool_name == "ExitPlanMode")
90
-
91
- if is_exit_plan_mode:
92
- print("[archive_plan] ExitPlanMode detected, proceeding with archival")
93
- else:
94
- # Check if we're in plan mode for other hooks
95
- permission_mode = hook_input.get("permission_mode", "default")
96
- print(f"[archive_plan] Permission mode: {permission_mode}")
97
-
98
- if permission_mode != "plan":
99
- print("[archive_plan] Not in plan mode, skipping archival")
100
- return
101
-
102
- # Prevent infinite loops from stop_hook_active
103
- if hook_input.get("stop_hook_active", False):
104
- print("[archive_plan] Stop hook already active, skipping to prevent loop")
105
- return
106
-
107
- print(f"[archive_plan] Proceeding with archival via {hook_event}")
108
-
109
- # Get project root from hook input or environment
110
- project_root = project_dir(hook_input)
111
-
112
- # Get plan path from hook input
54
+ def _find_plan_path(hook_input: dict, project_root: Path) -> Optional[str]:
55
+ """Find the plan file path from hook input or standard locations."""
113
56
  tool_input = hook_input.get("tool_input", {})
114
57
  tool_result = hook_input.get("tool_result", "")
58
+ hook_event = hook_input.get("hook_event_name", "")
59
+ tool_name = hook_input.get("tool_name", "")
115
60
 
116
- # Try to find plan path in various locations
117
61
  plan_path = None
118
62
 
119
- # For ExitPlanMode, extract plan path from tool result first
120
- if is_exit_plan_mode and tool_result:
63
+ # For ExitPlanMode, extract from tool result
64
+ if tool_name == "ExitPlanMode" and tool_result:
121
65
  plan_path = extract_plan_path_from_result(tool_result)
122
66
  if plan_path:
123
- print(f"[archive_plan] Extracted plan path from ExitPlanMode result: {plan_path}")
124
-
125
- # Check if plan path is directly provided in tool_input
126
- if not plan_path and "plan_path" in tool_input:
127
- plan_path = tool_input["plan_path"]
128
- elif not plan_path and "planPath" in tool_input:
129
- plan_path = tool_input["planPath"]
67
+ log_info("archive_plan", f"Extracted plan path from result: {plan_path}")
130
68
 
131
- # If not found yet, search standard locations
69
+ # Check tool_input for plan path
132
70
  if not plan_path:
133
- print("[archive_plan] No plan_path found, searching standard locations...")
134
- # Look for plan in common locations
135
- possible_paths = []
71
+ plan_path = tool_input.get("plan_path") or tool_input.get("planPath")
136
72
 
137
- # Check Claude Code plan directory first (~/.claude/plans/)
73
+ # Search standard locations
74
+ if not plan_path:
75
+ log_debug("archive_plan", "No plan_path found, searching standard locations...")
138
76
  claude_plans_dir = Path.home() / ".claude" / "plans"
139
- print(f"[archive_plan] Checking Claude plans dir: {claude_plans_dir}")
140
77
  if claude_plans_dir.exists():
141
- # Find any .md file in Claude plans directory
142
- claude_plans = list(claude_plans_dir.glob("*.md"))
143
- print(f"[archive_plan] Found {len(claude_plans)} .md files in Claude plans dir")
144
- # Sort by modification time, newest first
78
+ claude_plans = sorted(
79
+ claude_plans_dir.glob("*.md"),
80
+ key=lambda p: p.stat().st_mtime,
81
+ reverse=True,
82
+ )
145
83
  if claude_plans:
146
- claude_plans.sort(key=lambda p: p.stat().st_mtime, reverse=True)
147
- possible_paths.extend(claude_plans)
148
- for p in claude_plans[:3]: # Show first 3
149
- print(f"[archive_plan] - {p}")
84
+ plan_path = str(claude_plans[0])
150
85
 
151
- # Existing fallback paths
152
- possible_paths.extend([
86
+ if not plan_path:
87
+ for fallback in [
153
88
  project_root / "_output" / "cc-native" / "plans" / "current-plan.md",
154
89
  project_root / "_output" / "plans" / "current-plan.md",
155
90
  project_root / "plan.md",
156
- ])
157
-
158
- for path in possible_paths:
159
- if path.exists():
160
- plan_path = str(path)
91
+ ]:
92
+ if fallback.exists():
93
+ plan_path = str(fallback)
161
94
  break
162
95
 
163
- if not plan_path:
164
- eprint("[archive_plan] Could not determine plan path")
165
- # Don't block - let ExitPlanMode proceed
166
- print("[archive_plan] Could not find plan file in any of these locations:")
167
- print(f" - ~/.claude/plans/*.md")
168
- print(f" - {project_root}/_output/cc-native/plans/current-plan.md")
169
- print(f" - {project_root}/_output/plans/current-plan.md")
170
- print(f" - {project_root}/plan.md")
171
- print("Plan archival skipped: no plan path found")
96
+ return plan_path
97
+
98
+
99
+ def on_plan_archive():
100
+ """Archive plan on PermissionRequest:ExitPlanMode — file archival only, no state.json changes."""
101
+ hook_input = load_hook_input()
102
+ if not hook_input:
103
+ log_warn("archive_plan", "No valid JSON input")
172
104
  return
173
105
 
174
- print(f"[archive_plan] Found plan at: {plan_path}")
106
+ hook_event = hook_input.get("hook_event_name", "unknown")
107
+ tool_name = hook_input.get("tool_name", "")
108
+
109
+ log_info("archive_plan", f"Hook triggered: {hook_event}, tool: {tool_name}")
110
+
111
+ # Only handle PermissionRequest for ExitPlanMode
112
+ if not (hook_event == "PermissionRequest" and tool_name == "ExitPlanMode"):
113
+ log_debug("archive_plan", "Skipping: not PermissionRequest:ExitPlanMode")
114
+ return
175
115
 
176
- # Resolve plan path relative to project root
116
+ if hook_input.get("stop_hook_active", False):
117
+ log_debug("archive_plan", "Stop hook active, skipping")
118
+ return
119
+
120
+ project_root = project_dir(hook_input)
121
+ plan_path = _find_plan_path(hook_input, project_root)
122
+
123
+ if not plan_path:
124
+ log_warn("archive_plan", "Could not find plan file, skipping archival")
125
+ return
126
+
127
+ # Resolve plan path
177
128
  plan_file = Path(plan_path)
178
129
  if not plan_file.is_absolute():
179
- # Ensure we have a valid project_root
180
- if project_root is None:
181
- project_root = project_dir()
182
130
  plan_file = project_root / plan_path
183
- else:
184
- # On Windows, check if absolute path is on a different drive than project_root
185
- # In that case, use the absolute path as-is
186
- if sys.platform == 'win32':
187
- try:
188
- # Check if drives match (e.g., C: vs D:)
189
- plan_drive = plan_file.drive.upper() if plan_file.drive else None
190
- project_drive = project_root.drive.upper() if hasattr(project_root, 'drive') and project_root.drive else None
191
- if plan_drive and project_drive and plan_drive != project_drive:
192
- # Different drives - use absolute path as-is
193
- pass # plan_file is already set correctly
194
- except Exception:
195
- pass # Fall through to use plan_file as-is
196
-
197
- print(f"[archive_plan] Resolved plan file path: {plan_file}")
131
+
132
+ log_debug("archive_plan", f"Resolved plan file: {plan_file}")
198
133
 
199
134
  if not plan_file.exists():
200
- eprint(f"[archive_plan] Plan file not found: {plan_file}")
201
- print(f"[archive_plan] ERROR: File does not exist at resolved path")
202
- print(f"Plan archival skipped: file not found ({plan_path})")
135
+ log_error("archive_plan", f"Plan file not found: {plan_file}")
203
136
  return
204
137
 
205
- # Find context by session ID using shared extractor
138
+ # Find context by session ID
206
139
  session_id = hook_input.get("session_id", "unknown")
207
- context_id = extract_context_id_for_session(session_id, project_root, "archive_plan")
140
+ state = get_context_by_session_id(session_id, project_root)
208
141
 
209
- if not context_id:
210
- eprint("[archive_plan] Could not determine context for session")
211
- print("Plan archival failed: no context found for this session")
142
+ if not state:
143
+ log_warn("archive_plan", "Could not determine context for session")
212
144
  return
213
145
 
214
- # Check if plan was already archived (avoid duplicates)
215
- contexts = get_all_contexts(status="active", project_root=project_root)
216
- for ctx in contexts:
217
- if ctx.id == context_id:
218
- if ctx.in_flight and ctx.in_flight.mode == "pending_implementation":
219
- print(f"[archive_plan] Plan already archived for context '{context_id}', skipping")
220
- return
221
- break
222
-
223
- # Archive the plan
224
- archived_path, plan_hash = archive_plan_to_context(
225
- str(plan_file),
226
- context_id,
227
- project_root
146
+ context_id = state.id
147
+
148
+ # Archive the plan file (returns path, hash, signature)
149
+ archived_path, plan_hash, plan_signature = archive_plan(
150
+ str(plan_file), context_id, project_root
228
151
  )
229
152
 
230
153
  if archived_path:
231
- # Clean up debug logs before completing archive
154
+ # Clean up debug logs
232
155
  try:
233
156
  context_path = get_context_dir(context_id, project_root)
234
157
  cleanup_debug_folder(context_path)
235
- print(f"[archive_plan] Cleaned up debug logs for context: {context_id}")
236
158
  except Exception as e:
237
- print(f"[archive_plan] Warning: could not clean debug folder: {e}")
238
-
239
- print(f"")
240
- print(f"[archive_plan] SUCCESS!")
241
- print(f"[archive_plan] Plan archived to context: {context_id}")
242
- print(f"[archive_plan] Archived path: {archived_path}")
243
- print(f"[archive_plan] Source path: {plan_file}")
244
- print(f"[archive_plan] Hash: {plan_hash}")
245
- print(f"")
246
- print("After /clear, SessionStart will auto-continue this context for implementation.")
159
+ log_warn("archive_plan", f"could not clean debug folder: {e}")
160
+
161
+ log_info("archive_plan", f"SUCCESS: archived plan for {context_id}")
162
+ log_debug("archive_plan", f"Path: {archived_path}, hash: {plan_hash}")
247
163
  else:
248
- print(f"[archive_plan] FAILED: Could not archive plan for context '{context_id}'")
164
+ log_error("archive_plan", f"Could not archive plan for '{context_id}'")
249
165
 
250
166
 
251
167
  if __name__ == "__main__":
252
- try:
253
- on_plan_archive()
254
- except Exception as e:
255
- # Log errors to stderr
256
- eprint(f"[archive_plan] Error: {e}")
257
- import traceback
258
- eprint(traceback.format_exc())
259
- # Exit cleanly so hook doesn't block
260
- sys.exit(0)
168
+ from lib.base.hook_utils import run_hook
169
+ run_hook(on_plan_archive, "archive_plan")