aiwcli 0.9.7 → 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.
- package/bin/run.js +5 -2
- package/dist/lib/claude-settings-types.d.ts +2 -0
- package/dist/templates/CLAUDE.md +49 -18
- package/dist/templates/_shared/.claude/settings.json +4 -0
- 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 +87 -178
- package/dist/templates/_shared/hooks/context_monitor.py +128 -194
- package/dist/templates/_shared/hooks/file-suggestion.py +26 -23
- package/dist/templates/_shared/hooks/pre_compact.py +104 -0
- package/dist/templates/_shared/hooks/session_end.py +154 -0
- package/dist/templates/_shared/hooks/session_start.py +145 -59
- package/dist/templates/_shared/hooks/task_create_capture.py +26 -49
- package/dist/templates/_shared/hooks/task_update_capture.py +42 -100
- package/dist/templates/_shared/hooks/user_prompt_submit.py +63 -77
- package/dist/templates/_shared/lib/base/__init__.py +16 -0
- package/dist/templates/_shared/lib/base/__pycache__/__init__.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__/utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/constants.py +18 -4
- package/dist/templates/_shared/lib/base/hook_utils.py +199 -11
- package/dist/templates/_shared/lib/base/inference.py +121 -0
- package/dist/templates/_shared/lib/base/logger.py +291 -0
- package/dist/templates/_shared/lib/base/utils.py +49 -11
- package/dist/templates/_shared/lib/context/__init__.py +72 -80
- package/dist/templates/_shared/lib/context/__pycache__/__init__.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__/plan_manager.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 +316 -0
- package/dist/templates/_shared/lib/context/context_selector.py +491 -0
- package/dist/templates/_shared/lib/context/context_store.py +636 -0
- package/dist/templates/_shared/lib/context/plan_manager.py +204 -0
- package/dist/templates/_shared/lib/context/task_tracker.py +188 -0
- 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 +14 -40
- package/dist/templates/_shared/lib/templates/README.md +5 -13
- package/dist/templates/_shared/lib/templates/__init__.py +2 -6
- 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__/plan_context.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/plan_context.py +25 -79
- 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 +39 -19
- package/dist/templates/_shared/scripts/status_line.py +701 -0
- package/dist/templates/_shared/workflows/handoff.md +9 -3
- package/dist/templates/cc-native/.claude/settings.json +64 -9
- package/dist/templates/cc-native/CC-NATIVE-README.md +25 -28
- package/dist/templates/cc-native/MIGRATION.md +1 -1
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +14 -39
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +1 -1
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +57 -22
- 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 +57 -57
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +208 -158
- package/dist/templates/cc-native/_cc-native/hooks/plan_accepted.py +127 -0
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +81 -0
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +26 -25
- package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +35 -10
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.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/debug.py +37 -22
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +103 -42
- 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 +26 -21
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +12 -7
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +12 -7
- package/dist/templates/cc-native/_cc-native/lib/state.py +31 -16
- package/dist/templates/cc-native/_cc-native/lib/utils.py +210 -43
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +1 -2
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/templates/_shared/hooks/context_enforcer.py +0 -625
- package/dist/templates/_shared/hooks/task_create_atomicity.py +0 -205
- package/dist/templates/_shared/lib/context/cache.py +0 -444
- package/dist/templates/_shared/lib/context/context_extractor.py +0 -115
- package/dist/templates/_shared/lib/context/context_manager.py +0 -1054
- package/dist/templates/_shared/lib/context/discovery.py +0 -444
- package/dist/templates/_shared/lib/context/event_log.py +0 -308
- package/dist/templates/_shared/lib/context/plan_archive.py +0 -101
- package/dist/templates/_shared/lib/context/task_sync.py +0 -290
- package/dist/templates/_shared/lib/templates/persona_questions.py +0 -113
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/async_archive.py +0 -68
- 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 ?
|
|
21
|
+
args: hasCommand || isHelpOrVersion ? resolvedArgs : ['launch', ...resolvedArgs],
|
|
19
22
|
})
|
package/dist/templates/CLAUDE.md
CHANGED
|
@@ -17,24 +17,52 @@ Include `_output/{method}/` in template `.gitignore`.
|
|
|
17
17
|
|
|
18
18
|
## Directory Structure
|
|
19
19
|
|
|
20
|
+
Each template installs into `.aiwcli/` (method files) and `.{ide}/` (IDE integration). The `_shared/` template provides cross-method infrastructure used by all methods.
|
|
21
|
+
|
|
20
22
|
```
|
|
21
|
-
packages/cli/src/templates/
|
|
22
|
-
├──
|
|
23
|
-
│ ├──
|
|
24
|
-
│ └──
|
|
25
|
-
├──
|
|
26
|
-
├──
|
|
27
|
-
├──
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
packages/cli/src/templates/
|
|
24
|
+
├── _shared/ # Cross-method infrastructure (installed by all methods)
|
|
25
|
+
│ ├── hooks/ # Shared hook scripts (context, tasks, sessions)
|
|
26
|
+
│ └── lib/ # Shared Python libraries
|
|
27
|
+
│ ├── base/ # Core: atomic_write, constants, inference, utils
|
|
28
|
+
│ ├── context/ # Context CRUD, selection, formatting, plans, tasks
|
|
29
|
+
│ ├── handoff/ # Session handoff document generation
|
|
30
|
+
│ └── templates/ # Output formatters, plan context templates
|
|
31
|
+
│
|
|
32
|
+
├── cc-native/ # CC-Native method template
|
|
33
|
+
│ ├── _cc-native/ # Method-specific hooks, lib, agents, workflows, scripts
|
|
34
|
+
│ ├── .claude/ # Claude Code: settings.json, commands/, agents/
|
|
35
|
+
│ ├── .windsurf/ # Windsurf: workflows/
|
|
36
|
+
│ └── .gitignore
|
|
37
|
+
│
|
|
38
|
+
├── gsd/ # GSD method template
|
|
39
|
+
│ ├── .aiwcli/_gsd/ # Templates, workflows, hooks, config, docs
|
|
40
|
+
│ ├── .claude/ # Claude Code: settings.json, commands/, agents/
|
|
41
|
+
│ ├── .windsurf/ # Windsurf: workflows/
|
|
42
|
+
│ ├── GSD-README.md
|
|
43
|
+
│ ├── TEMPLATE-SCHEMA.md
|
|
44
|
+
│ └── MIGRATION.md
|
|
45
|
+
│
|
|
46
|
+
├── bmad/ # BMAD method template
|
|
47
|
+
│ ├── .aiwcli/_bmad/ # Agents, workflows, teams, testarch, config
|
|
48
|
+
│ ├── .claude/ # Claude Code: settings.json, commands/
|
|
49
|
+
│ └── ...
|
|
50
|
+
│
|
|
51
|
+
├── planning-with-files/ # Planning-with-Files method template
|
|
52
|
+
│ ├── .claude/ # Claude Code: settings.json, skills/
|
|
53
|
+
│ ├── .windsurf/ # Windsurf: workflows/, scripts/
|
|
54
|
+
│ └── ...
|
|
55
|
+
│
|
|
56
|
+
└── CLAUDE.md # This file
|
|
30
57
|
```
|
|
31
58
|
|
|
32
59
|
### Tier Details
|
|
33
60
|
|
|
34
61
|
| Tier | Location | Purpose |
|
|
35
62
|
|------|----------|---------|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
63
|
+
| Shared | `_shared/` | Cross-method hooks and libraries (context management, task tracking, sessions) |
|
|
64
|
+
| Method | `_{method}/` or `.aiwcli/_{method}/` | Method-specific templates, workflows, hooks, config |
|
|
65
|
+
| IDE | `.{ide}/` | IDE-specific command stubs, settings, workflow definitions |
|
|
38
66
|
| Config | `.{ide}/settings.json` | Hooks, model prefs, method settings (merged on install) |
|
|
39
67
|
|
|
40
68
|
---
|
|
@@ -57,7 +85,7 @@ When multiple templates install, settings.json files merge:
|
|
|
57
85
|
|
|
58
86
|
## Hooks
|
|
59
87
|
|
|
60
|
-
**Location:** `.
|
|
88
|
+
**Location:** Hooks live in `.aiwcli/_shared/hooks/` (cross-method) and `.aiwcli/_{method}/hooks/` (method-specific). They are configured in `.{ide}/settings.json`, not placed in IDE directories.
|
|
61
89
|
|
|
62
90
|
**Configuration:**
|
|
63
91
|
```json
|
|
@@ -65,14 +93,14 @@ When multiple templates install, settings.json files merge:
|
|
|
65
93
|
"hooks": {
|
|
66
94
|
"PostToolUse": [{
|
|
67
95
|
"matcher": "Write",
|
|
68
|
-
"hooks": [{ "type": "command", "command": "python .
|
|
96
|
+
"hooks": [{ "type": "command", "command": "python .aiwcli/_cc-native/hooks/cc-native-plan-review.py", "timeout": 300000 }]
|
|
69
97
|
}]
|
|
70
98
|
}
|
|
71
99
|
}
|
|
72
100
|
```
|
|
73
101
|
|
|
74
102
|
**Requirements:**
|
|
75
|
-
- Prefix with method name (e.g., `
|
|
103
|
+
- Prefix method-specific hooks with method name (e.g., `cc-native-plan-review.py`)
|
|
76
104
|
- Use relative paths from project root
|
|
77
105
|
- Write outputs to `_output/{method}/`
|
|
78
106
|
- Specify timeouts
|
|
@@ -119,8 +147,8 @@ Load and execute `_{method}/workflows/{name}.md`.
|
|
|
119
147
|
| Reference Type | Pattern |
|
|
120
148
|
|----------------|---------|
|
|
121
149
|
| Templates | `_{method}/templates/FILE.md.template` |
|
|
122
|
-
| Workflows (Claude) | `/gsd:
|
|
123
|
-
| Workflows (Windsurf) | `
|
|
150
|
+
| Workflows (Claude) | `/gsd:workflow-name` (maps to `.claude/commands/gsd/workflow-name.md`) |
|
|
151
|
+
| Workflows (Windsurf) | `workflow-name` from method workflows |
|
|
124
152
|
| Outputs | `_output/{method}/{subdir}/FILE.md` |
|
|
125
153
|
|
|
126
154
|
---
|
|
@@ -143,8 +171,8 @@ Load and execute `_{method}/workflows/{name}.md`.
|
|
|
143
171
|
|
|
144
172
|
**New Template:**
|
|
145
173
|
- [ ] Create `_{method}/` with `templates/` and `workflows/`
|
|
146
|
-
- [ ] Create `.claude/commands/{method}/` stubs
|
|
147
|
-
- [ ] Create `.windsurf/workflows/{method}/` stubs
|
|
174
|
+
- [ ] Create `.claude/commands/{method}/` stubs (Claude Code)
|
|
175
|
+
- [ ] Create `.windsurf/workflows/{method}/` stubs (Windsurf)
|
|
148
176
|
- [ ] Add `.gitignore` with `_output/{method}/`
|
|
149
177
|
- [ ] Create `{METHOD}-README.md`, `TEMPLATE-SCHEMA.md`, `MIGRATION.md`
|
|
150
178
|
- [ ] Configure method-namespaced settings in `.claude/settings.json`
|
|
@@ -165,6 +193,7 @@ Load and execute `_{method}/workflows/{name}.md`.
|
|
|
165
193
|
- Keep canonical workflows in `_{method}/workflows/`
|
|
166
194
|
- Use relative paths from project root
|
|
167
195
|
- Document changes in TEMPLATE-SCHEMA.md
|
|
196
|
+
- Place hooks in `.aiwcli/` directories, wire them in `.{ide}/settings.json`
|
|
168
197
|
|
|
169
198
|
**Avoid:**
|
|
170
199
|
- Outputs in project root
|
|
@@ -172,3 +201,5 @@ Load and execute `_{method}/workflows/{name}.md`.
|
|
|
172
201
|
- Hooks without method prefix
|
|
173
202
|
- Full workflows in IDE command files
|
|
174
203
|
- Hardcoded paths without method namespace
|
|
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.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Plan archival hook for ExitPlanMode
|
|
2
|
+
"""Plan archival hook for ExitPlanMode PermissionRequest event.
|
|
3
3
|
|
|
4
|
-
This hook runs when ExitPlanMode
|
|
5
|
-
the tool
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
"
|
|
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.
|
|
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
|
|
51
|
+
pass
|
|
55
52
|
|
|
56
53
|
|
|
57
|
-
def
|
|
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
|
|
120
|
-
if
|
|
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
|
-
|
|
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
|
-
#
|
|
69
|
+
# Check tool_input for plan path
|
|
132
70
|
if not plan_path:
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
|
|
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
|
-
|
|
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
|
|
138
|
+
# Find context by session ID
|
|
206
139
|
session_id = hook_input.get("session_id", "unknown")
|
|
207
|
-
|
|
140
|
+
state = get_context_by_session_id(session_id, project_root)
|
|
208
141
|
|
|
209
|
-
if not
|
|
210
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
164
|
+
log_error("archive_plan", f"Could not archive plan for '{context_id}'")
|
|
249
165
|
|
|
250
166
|
|
|
251
167
|
if __name__ == "__main__":
|
|
252
|
-
|
|
253
|
-
|
|
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")
|