aiwcli 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/run.js +1 -1
- package/dist/commands/clear.d.ts +11 -6
- package/dist/commands/clear.js +229 -381
- package/dist/commands/init/index.d.ts +1 -17
- package/dist/commands/init/index.js +22 -107
- package/dist/lib/gitignore-manager.d.ts +32 -0
- package/dist/lib/gitignore-manager.js +141 -2
- package/dist/lib/template-installer.d.ts +7 -12
- package/dist/lib/template-installer.js +69 -193
- package/dist/lib/template-settings-reconstructor.d.ts +35 -0
- package/dist/lib/template-settings-reconstructor.js +130 -0
- 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 +104 -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 +138 -0
- package/dist/templates/_shared/lib-ts/base/constants.ts +306 -0
- package/dist/templates/_shared/lib-ts/base/git-state.ts +58 -0
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +439 -0
- package/dist/templates/_shared/lib-ts/base/inference.ts +252 -0
- package/dist/templates/_shared/lib-ts/base/logger.ts +250 -0
- package/dist/templates/_shared/lib-ts/base/state-io.ts +116 -0
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +184 -0
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +162 -0
- package/dist/templates/_shared/lib-ts/base/utils.ts +184 -0
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +438 -0
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +515 -0
- package/dist/templates/_shared/lib-ts/context/context-store.ts +707 -0
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +316 -0
- package/dist/templates/_shared/lib-ts/context/task-tracker.ts +185 -0
- package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +216 -0
- package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +159 -0
- package/dist/templates/_shared/lib-ts/package.json +21 -0
- package/dist/templates/_shared/lib-ts/templates/formatters.ts +104 -0
- package/dist/templates/_shared/{lib/templates/plan_context.py → lib-ts/templates/plan-context.ts} +14 -22
- package/dist/templates/_shared/lib-ts/tsconfig.json +13 -0
- package/dist/templates/_shared/lib-ts/types.ts +164 -0
- package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
- package/dist/templates/_shared/scripts/resume_handoff.ts +321 -0
- package/dist/templates/_shared/scripts/save_handoff.ts +359 -0
- package/dist/templates/_shared/scripts/status_line.ts +733 -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/ARCH-EVOLUTION.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/ARCH-PATTERNS.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/ARCH-STRUCTURE.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-CHAIN-TRACER.md → ASSUMPTION-TRACER.md} +6 -10
- package/dist/templates/cc-native/_cc-native/agents/CLARITY-AUDITOR.md +6 -10
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +74 -3
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-FEASIBILITY.md +67 -0
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-GAPS.md +71 -0
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-ORDERING.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/CONSTRAINT-VALIDATOR.md +73 -0
- package/dist/templates/cc-native/_cc-native/agents/DESIGN-ADR-VALIDATOR.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/DESIGN-SCALE-MATCHER.md +65 -0
- package/dist/templates/cc-native/_cc-native/agents/DEVILS-ADVOCATE.md +6 -9
- package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-PHILOSOPHY.md +87 -0
- package/dist/templates/cc-native/_cc-native/agents/HANDOFF-READINESS.md +5 -9
- package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY-DETECTOR.md → HIDDEN-COMPLEXITY.md} +6 -10
- package/dist/templates/cc-native/_cc-native/agents/INCREMENTAL-DELIVERY.md +67 -0
- package/dist/templates/cc-native/_cc-native/agents/PLAN-ORCHESTRATOR.md +91 -18
- package/dist/templates/cc-native/_cc-native/agents/RISK-DEPENDENCY.md +63 -0
- package/dist/templates/cc-native/_cc-native/agents/RISK-FMEA.md +67 -0
- package/dist/templates/cc-native/_cc-native/agents/RISK-PREMORTEM.md +72 -0
- package/dist/templates/cc-native/_cc-native/agents/RISK-REVERSIBILITY.md +75 -0
- package/dist/templates/cc-native/_cc-native/agents/SCOPE-BOUNDARY.md +78 -0
- package/dist/templates/cc-native/_cc-native/agents/SIMPLICITY-GUARDIAN.md +5 -9
- package/dist/templates/cc-native/_cc-native/agents/SKEPTIC.md +16 -12
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-BEHAVIOR-AUDITOR.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-CHARACTERIZATION.md +72 -0
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-FIRST-VALIDATOR.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-PYRAMID-ANALYZER.md +62 -0
- package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-COSTS.md +68 -0
- package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-STAKEHOLDERS.md +66 -0
- package/dist/templates/cc-native/_cc-native/agents/VERIFY-COVERAGE.md +75 -0
- package/dist/templates/cc-native/_cc-native/agents/VERIFY-STRENGTH.md +70 -0
- 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 +921 -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 +157 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +709 -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 +124 -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/debug.ts +80 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +119 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +162 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +249 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +155 -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 +106 -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 +243 -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 +310 -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 +12 -16
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/lib/template-merger.d.ts +0 -47
- package/dist/lib/template-merger.js +0 -162
- 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 -169
- 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 -341
- package/dist/templates/_shared/lib/base/inference.py +0 -318
- package/dist/templates/_shared/lib/base/logger.py +0 -291
- package/dist/templates/_shared/lib/base/stop_words.py +0 -213
- package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
- package/dist/templates/_shared/lib/base/utils.py +0 -242
- 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 -204
- 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/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 -701
- 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/agents/ACCESSIBILITY-TESTER.md +0 -79
- package/dist/templates/cc-native/_cc-native/agents/ARCHITECT-REVIEWER.md +0 -48
- package/dist/templates/cc-native/_cc-native/agents/CODE-REVIEWER.md +0 -70
- package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-CHECKER.md +0 -59
- package/dist/templates/cc-native/_cc-native/agents/CONTEXT-EXTRACTOR.md +0 -92
- package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-REVIEWER.md +0 -51
- package/dist/templates/cc-native/_cc-native/agents/FEASIBILITY-ANALYST.md +0 -57
- package/dist/templates/cc-native/_cc-native/agents/FRESH-PERSPECTIVE.md +0 -54
- package/dist/templates/cc-native/_cc-native/agents/INCENTIVE-MAPPER.md +0 -61
- package/dist/templates/cc-native/_cc-native/agents/PENETRATION-TESTER.md +0 -79
- package/dist/templates/cc-native/_cc-native/agents/PERFORMANCE-ENGINEER.md +0 -75
- package/dist/templates/cc-native/_cc-native/agents/PRECEDENT-FINDER.md +0 -70
- package/dist/templates/cc-native/_cc-native/agents/REVERSIBILITY-ANALYST.md +0 -61
- package/dist/templates/cc-native/_cc-native/agents/RISK-ASSESSOR.md +0 -58
- package/dist/templates/cc-native/_cc-native/agents/SECOND-ORDER-ANALYST.md +0 -61
- package/dist/templates/cc-native/_cc-native/agents/STAKEHOLDER-ADVOCATE.md +0 -55
- package/dist/templates/cc-native/_cc-native/agents/TRADE-OFF-ILLUMINATOR.md +0 -204
- 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 -869
- 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 -1027
- 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
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Plan archival hook for ExitPlanMode PermissionRequest event.
|
|
3
|
-
|
|
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.
|
|
7
|
-
|
|
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
|
|
13
|
-
|
|
14
|
-
Usage in .claude/settings.json:
|
|
15
|
-
{
|
|
16
|
-
"hooks": {
|
|
17
|
-
"PermissionRequest": [{
|
|
18
|
-
"matcher": "ExitPlanMode",
|
|
19
|
-
"hooks": [{
|
|
20
|
-
"type": "command",
|
|
21
|
-
"command": "python .aiwcli/_shared/hooks/archive_plan.py",
|
|
22
|
-
"timeout": 5000
|
|
23
|
-
}]
|
|
24
|
-
}]
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
"""
|
|
28
|
-
import re
|
|
29
|
-
import sys
|
|
30
|
-
from pathlib import Path
|
|
31
|
-
from typing import Optional
|
|
32
|
-
|
|
33
|
-
# Add parent directories to path for imports
|
|
34
|
-
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
35
|
-
SHARED_LIB = SCRIPT_DIR.parent / "lib"
|
|
36
|
-
sys.path.insert(0, str(SHARED_LIB.parent))
|
|
37
|
-
|
|
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
|
|
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
|
|
43
|
-
|
|
44
|
-
# Import debug cleanup function from cc-native lib
|
|
45
|
-
_cc_native_lib = SCRIPT_DIR.parent / "_cc-native" / "lib"
|
|
46
|
-
sys.path.insert(0, str(_cc_native_lib))
|
|
47
|
-
try:
|
|
48
|
-
from debug import cleanup_debug_folder
|
|
49
|
-
except ImportError:
|
|
50
|
-
def cleanup_debug_folder(context_path):
|
|
51
|
-
pass
|
|
52
|
-
|
|
53
|
-
|
|
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."""
|
|
56
|
-
tool_input = hook_input.get("tool_input", {})
|
|
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", "")
|
|
60
|
-
|
|
61
|
-
plan_path = None
|
|
62
|
-
|
|
63
|
-
# For ExitPlanMode, extract from tool result
|
|
64
|
-
if tool_name == "ExitPlanMode" and tool_result:
|
|
65
|
-
plan_path = extract_plan_path_from_result(tool_result)
|
|
66
|
-
if plan_path:
|
|
67
|
-
log_info("archive_plan", f"Extracted plan path from result: {plan_path}")
|
|
68
|
-
|
|
69
|
-
# Check tool_input for plan path
|
|
70
|
-
if not plan_path:
|
|
71
|
-
plan_path = tool_input.get("plan_path") or tool_input.get("planPath")
|
|
72
|
-
|
|
73
|
-
# Search standard locations
|
|
74
|
-
if not plan_path:
|
|
75
|
-
log_debug("archive_plan", "No plan_path found, searching standard locations...")
|
|
76
|
-
claude_plans_dir = Path.home() / ".claude" / "plans"
|
|
77
|
-
if claude_plans_dir.exists():
|
|
78
|
-
claude_plans = sorted(
|
|
79
|
-
claude_plans_dir.glob("*.md"),
|
|
80
|
-
key=lambda p: p.stat().st_mtime,
|
|
81
|
-
reverse=True,
|
|
82
|
-
)
|
|
83
|
-
if claude_plans:
|
|
84
|
-
plan_path = str(claude_plans[0])
|
|
85
|
-
|
|
86
|
-
if not plan_path:
|
|
87
|
-
for fallback in [
|
|
88
|
-
project_root / "_output" / "cc-native" / "plans" / "current-plan.md",
|
|
89
|
-
project_root / "_output" / "plans" / "current-plan.md",
|
|
90
|
-
project_root / "plan.md",
|
|
91
|
-
]:
|
|
92
|
-
if fallback.exists():
|
|
93
|
-
plan_path = str(fallback)
|
|
94
|
-
break
|
|
95
|
-
|
|
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")
|
|
104
|
-
return
|
|
105
|
-
|
|
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
|
|
115
|
-
|
|
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
|
|
128
|
-
plan_file = Path(plan_path)
|
|
129
|
-
if not plan_file.is_absolute():
|
|
130
|
-
plan_file = project_root / plan_path
|
|
131
|
-
|
|
132
|
-
log_debug("archive_plan", f"Resolved plan file: {plan_file}")
|
|
133
|
-
|
|
134
|
-
if not plan_file.exists():
|
|
135
|
-
log_error("archive_plan", f"Plan file not found: {plan_file}")
|
|
136
|
-
return
|
|
137
|
-
|
|
138
|
-
# Find context by session ID
|
|
139
|
-
session_id = hook_input.get("session_id", "unknown")
|
|
140
|
-
state = get_context_by_session_id(session_id, project_root)
|
|
141
|
-
|
|
142
|
-
if not state:
|
|
143
|
-
log_warn("archive_plan", "Could not determine context for session")
|
|
144
|
-
return
|
|
145
|
-
|
|
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
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
if archived_path:
|
|
154
|
-
# Clean up debug logs
|
|
155
|
-
try:
|
|
156
|
-
context_path = get_context_dir(context_id, project_root)
|
|
157
|
-
cleanup_debug_folder(context_path)
|
|
158
|
-
except Exception as e:
|
|
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}")
|
|
163
|
-
else:
|
|
164
|
-
log_error("archive_plan", f"Could not archive plan for '{context_id}'")
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if __name__ == "__main__":
|
|
168
|
-
from lib.base.hook_utils import run_hook
|
|
169
|
-
run_hook(on_plan_archive, "archive_plan")
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Context monitor hook for proactive handoff warnings.
|
|
3
|
-
|
|
4
|
-
This hook runs on PostToolUse for context-heavy tools and monitors
|
|
5
|
-
context window usage. When context drops below a threshold, it injects
|
|
6
|
-
a system reminder instructing Claude to wrap up and create a handoff document.
|
|
7
|
-
|
|
8
|
-
Unlike UserPromptSubmit hooks, this fires DURING Claude's work,
|
|
9
|
-
allowing proactive intervention without waiting for user input.
|
|
10
|
-
|
|
11
|
-
Monitored tools (configured via settings.json matcher):
|
|
12
|
-
- Task: Subagent responses can be huge
|
|
13
|
-
- Read: File content loads into context
|
|
14
|
-
- Bash: Command output can be large
|
|
15
|
-
- WebFetch: Web content loads into context
|
|
16
|
-
|
|
17
|
-
Hook input (from Claude Code):
|
|
18
|
-
{
|
|
19
|
-
"hook_event_name": "PostToolUse",
|
|
20
|
-
"tool_name": "Task",
|
|
21
|
-
"tool_input": {...},
|
|
22
|
-
"tool_result": {...},
|
|
23
|
-
"transcript_path": "/path/to/transcript.jsonl",
|
|
24
|
-
"session_id": "abc123",
|
|
25
|
-
"context_window": {
|
|
26
|
-
"current_usage": {
|
|
27
|
-
"cache_read_input_tokens": 0,
|
|
28
|
-
"input_tokens": 12345,
|
|
29
|
-
"cache_creation_input_tokens": 0,
|
|
30
|
-
"output_tokens": 6789
|
|
31
|
-
},
|
|
32
|
-
"context_window_size": 200000
|
|
33
|
-
},
|
|
34
|
-
...
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
Hook output:
|
|
38
|
-
- Outputs JSON with additionalContext if context is low
|
|
39
|
-
- This injects a system reminder into Claude's context
|
|
40
|
-
- Plain stdout from PostToolUse only goes to verbose mode, not Claude
|
|
41
|
-
- Using additionalContext ensures Claude sees and responds to the warning
|
|
42
|
-
|
|
43
|
-
KNOWN LIMITATION: Context percentage won't match /context exactly.
|
|
44
|
-
Hook JSON excludes system prompt, tools, MCP tokens. We add a baseline
|
|
45
|
-
to compensate (~22.6k tokens typical). See:
|
|
46
|
-
https://github.com/anthropics/claude-code/issues/13783
|
|
47
|
-
"""
|
|
48
|
-
import sys
|
|
49
|
-
from pathlib import Path
|
|
50
|
-
from typing import Optional
|
|
51
|
-
|
|
52
|
-
# Add parent directories to path for imports
|
|
53
|
-
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
54
|
-
SHARED_LIB = SCRIPT_DIR.parent / "lib"
|
|
55
|
-
sys.path.insert(0, str(SHARED_LIB.parent))
|
|
56
|
-
|
|
57
|
-
from lib.base.hook_utils import emit_context, load_hook_input, get_context_percent_remaining, log_debug, log_info, log_warn, log_error, log_diagnostic
|
|
58
|
-
from lib.base.utils import now_iso, project_dir
|
|
59
|
-
from lib.context.context_store import (
|
|
60
|
-
get_all_contexts,
|
|
61
|
-
get_context_by_session_id,
|
|
62
|
-
maybe_activate,
|
|
63
|
-
save_state,
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
# Module-level flag: only save auto-state once per process lifetime
|
|
67
|
-
_PROGRESSIVE_SAVE_MARKER = ".progressive-save-done"
|
|
68
|
-
|
|
69
|
-
# Configuration
|
|
70
|
-
SAVE_STATE_THRESHOLD = 60 # Silently save auto-state at 60% remaining
|
|
71
|
-
HANDOFF_SUGGEST_THRESHOLD = 30 # Gentle nudge at 30% remaining (70% used)
|
|
72
|
-
HANDOFF_PREPARE_THRESHOLD = 20 # Stronger warning at 20% remaining (80% used)
|
|
73
|
-
CRITICAL_CONTEXT_THRESHOLD = 10 # Urgent warning at 10% remaining (90% used)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def get_current_context_id(project_root: Path = None) -> Optional[str]:
|
|
77
|
-
"""Determine the current active context (most recently active)."""
|
|
78
|
-
contexts = get_all_contexts(status="active", project_root=project_root)
|
|
79
|
-
if contexts:
|
|
80
|
-
return contexts[0].id
|
|
81
|
-
return None
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def get_context_warning(
|
|
85
|
-
percent_remaining: int,
|
|
86
|
-
tokens_used: Optional[int],
|
|
87
|
-
max_tokens: Optional[int],
|
|
88
|
-
context_id: Optional[str],
|
|
89
|
-
tool_name: str
|
|
90
|
-
) -> str:
|
|
91
|
-
"""Generate appropriate warning based on context level."""
|
|
92
|
-
if tokens_used is not None and max_tokens is not None:
|
|
93
|
-
tokens_used_k = tokens_used // 1000
|
|
94
|
-
max_tokens_k = max_tokens // 1000
|
|
95
|
-
usage_line = f"**Estimated usage**: ~{tokens_used_k}k / {max_tokens_k}k tokens"
|
|
96
|
-
else:
|
|
97
|
-
usage_line = f"**Estimated usage**: ~{percent_remaining}% remaining"
|
|
98
|
-
|
|
99
|
-
context_line = f"\nContext ID: `{context_id}`" if context_id else ""
|
|
100
|
-
|
|
101
|
-
if percent_remaining <= CRITICAL_CONTEXT_THRESHOLD:
|
|
102
|
-
return f"""<system-reminder>
|
|
103
|
-
## CRITICAL CONTEXT WARNING ({percent_remaining}% remaining)
|
|
104
|
-
|
|
105
|
-
{usage_line}
|
|
106
|
-
**Triggered by**: {tool_name} tool completion
|
|
107
|
-
|
|
108
|
-
**CRITICAL: Run `/handoff` now before context is compacted.**
|
|
109
|
-
{context_line}
|
|
110
|
-
|
|
111
|
-
You are about to lose context. Stop all other work and run `/handoff` immediately.
|
|
112
|
-
</system-reminder>"""
|
|
113
|
-
|
|
114
|
-
elif percent_remaining <= HANDOFF_PREPARE_THRESHOLD:
|
|
115
|
-
return f"""<system-reminder>
|
|
116
|
-
## LOW CONTEXT WARNING ({percent_remaining}% remaining)
|
|
117
|
-
|
|
118
|
-
{usage_line}
|
|
119
|
-
**Triggered by**: {tool_name} tool completion
|
|
120
|
-
|
|
121
|
-
**Context is getting low. Please finish your current task and run `/handoff`.**
|
|
122
|
-
{context_line}
|
|
123
|
-
|
|
124
|
-
**Actions:**
|
|
125
|
-
1. Complete your current atomic task (if 1-2 steps away)
|
|
126
|
-
2. Do NOT start new multi-step work
|
|
127
|
-
3. Run `/handoff` to generate a handoff document
|
|
128
|
-
</system-reminder>"""
|
|
129
|
-
|
|
130
|
-
else:
|
|
131
|
-
return f"""<system-reminder>
|
|
132
|
-
## CONTEXT NOTICE ({percent_remaining}% remaining)
|
|
133
|
-
|
|
134
|
-
{usage_line}
|
|
135
|
-
**Triggered by**: {tool_name} tool completion
|
|
136
|
-
|
|
137
|
-
**Consider preparing a handoff soon. When ready, run `/handoff` to generate a handoff document.**
|
|
138
|
-
{context_line}
|
|
139
|
-
|
|
140
|
-
Continue your current work, but avoid starting large new tasks.
|
|
141
|
-
</system-reminder>"""
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def check_and_transition_mode(hook_input: dict) -> None:
|
|
145
|
-
"""
|
|
146
|
-
Check if context mode needs to transition based on tool usage.
|
|
147
|
-
|
|
148
|
-
Handles:
|
|
149
|
-
- has_plan + implementation tool -> active (started implementing)
|
|
150
|
-
- idle + implementation tool -> active
|
|
151
|
-
"""
|
|
152
|
-
project_root = project_dir(hook_input)
|
|
153
|
-
session_id = hook_input.get("session_id")
|
|
154
|
-
|
|
155
|
-
if not session_id:
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
state = get_context_by_session_id(session_id, project_root)
|
|
159
|
-
if not state:
|
|
160
|
-
return
|
|
161
|
-
|
|
162
|
-
# Implementation transitions only trigger on implementation tools
|
|
163
|
-
implementation_tools = {"Edit", "Write", "Bash", "NotebookEdit"}
|
|
164
|
-
tool_name = hook_input.get("tool_name", "")
|
|
165
|
-
|
|
166
|
-
if tool_name not in implementation_tools:
|
|
167
|
-
return
|
|
168
|
-
|
|
169
|
-
permission_mode = hook_input.get("permission_mode", "default")
|
|
170
|
-
maybe_activate(state.id, permission_mode, project_root=project_root, caller="context_monitor")
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
def _try_progressive_save(hook_input: dict, percent_remaining: int) -> None:
|
|
174
|
-
"""Silently save state at SAVE_STATE_THRESHOLD (60%)."""
|
|
175
|
-
try:
|
|
176
|
-
session_id = hook_input.get("session_id", "")
|
|
177
|
-
if not session_id:
|
|
178
|
-
return
|
|
179
|
-
|
|
180
|
-
project_root = project_dir(hook_input)
|
|
181
|
-
state = get_context_by_session_id(session_id, project_root)
|
|
182
|
-
if not state:
|
|
183
|
-
return
|
|
184
|
-
|
|
185
|
-
from lib.base.constants import get_context_dir
|
|
186
|
-
marker_path = get_context_dir(state.id, project_root) / _PROGRESSIVE_SAVE_MARKER
|
|
187
|
-
if marker_path.exists():
|
|
188
|
-
try:
|
|
189
|
-
saved_session = marker_path.read_text(encoding="utf-8").strip()
|
|
190
|
-
if saved_session == session_id:
|
|
191
|
-
return
|
|
192
|
-
except OSError:
|
|
193
|
-
pass
|
|
194
|
-
|
|
195
|
-
log_info("context_monitor", f"Progressive save at {percent_remaining}% remaining")
|
|
196
|
-
|
|
197
|
-
# Just update last_active and save state
|
|
198
|
-
state.last_active = now_iso()
|
|
199
|
-
save_state(state, project_root)
|
|
200
|
-
|
|
201
|
-
try:
|
|
202
|
-
marker_path.write_text(session_id, encoding="utf-8")
|
|
203
|
-
except OSError:
|
|
204
|
-
pass
|
|
205
|
-
|
|
206
|
-
except Exception as e:
|
|
207
|
-
log_warn("context_monitor", f"Progressive save error (non-fatal): {e}")
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
def check_context_level(hook_input: dict) -> Optional[str]:
|
|
211
|
-
"""Check context level and return warning if low."""
|
|
212
|
-
tool_name = hook_input.get("tool_name", "Unknown")
|
|
213
|
-
percent_remaining, tokens_used, max_tokens = get_context_percent_remaining(hook_input)
|
|
214
|
-
|
|
215
|
-
log_diagnostic("context_monitor", "receive", f"tool={tool_name}, pct_remaining={percent_remaining}",
|
|
216
|
-
inputs={"tool_name": tool_name, "percent_remaining": percent_remaining,
|
|
217
|
-
"tokens_used": tokens_used, "max_tokens": max_tokens})
|
|
218
|
-
|
|
219
|
-
if percent_remaining is None:
|
|
220
|
-
return None
|
|
221
|
-
|
|
222
|
-
if percent_remaining > SAVE_STATE_THRESHOLD:
|
|
223
|
-
return None
|
|
224
|
-
|
|
225
|
-
if percent_remaining > HANDOFF_SUGGEST_THRESHOLD:
|
|
226
|
-
_try_progressive_save(hook_input, percent_remaining)
|
|
227
|
-
return None
|
|
228
|
-
|
|
229
|
-
if tokens_used is not None and max_tokens is not None:
|
|
230
|
-
log_info("context_monitor", f"Context: {percent_remaining}% remaining "
|
|
231
|
-
f"(~{tokens_used//1000}k/{max_tokens//1000}k tokens)")
|
|
232
|
-
else:
|
|
233
|
-
log_info("context_monitor", f"Context: ~{percent_remaining}% remaining (from context.json)")
|
|
234
|
-
|
|
235
|
-
project_root = project_dir(hook_input)
|
|
236
|
-
context_id = get_current_context_id(project_root)
|
|
237
|
-
|
|
238
|
-
threshold = ("critical" if percent_remaining <= CRITICAL_CONTEXT_THRESHOLD
|
|
239
|
-
else "prepare" if percent_remaining <= HANDOFF_PREPARE_THRESHOLD
|
|
240
|
-
else "suggest")
|
|
241
|
-
log_diagnostic("context_monitor", "decide", f"Threshold={threshold} at {percent_remaining}%",
|
|
242
|
-
decision=threshold, reasoning=f"{percent_remaining}% remaining",
|
|
243
|
-
inputs={"context_id": context_id, "percent_remaining": percent_remaining})
|
|
244
|
-
|
|
245
|
-
return get_context_warning(percent_remaining, tokens_used, max_tokens, context_id, tool_name)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def main():
|
|
249
|
-
"""Main entry point for PostToolUse hook."""
|
|
250
|
-
try:
|
|
251
|
-
hook_input = load_hook_input()
|
|
252
|
-
if not hook_input:
|
|
253
|
-
return
|
|
254
|
-
|
|
255
|
-
check_and_transition_mode(hook_input)
|
|
256
|
-
|
|
257
|
-
warning = check_context_level(hook_input)
|
|
258
|
-
if warning:
|
|
259
|
-
emit_context(warning)
|
|
260
|
-
|
|
261
|
-
except Exception as e:
|
|
262
|
-
import traceback
|
|
263
|
-
tb = traceback.format_exc()
|
|
264
|
-
from lib.base.hook_utils import log_hook_error
|
|
265
|
-
log_hook_error("context_monitor", e, "PostToolUse", traceback_str=tb)
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if __name__ == "__main__":
|
|
269
|
-
from lib.base.hook_utils import run_hook
|
|
270
|
-
run_hook(main, "context_monitor")
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""File suggestion hook for Claude Code.
|
|
3
|
-
|
|
4
|
-
Suggests relevant files to include in context based on the current session:
|
|
5
|
-
- Context file (context.json) for the active context
|
|
6
|
-
- Plans from the active context's plans/ directory
|
|
7
|
-
- Handoffs from the active context's handoffs/ directory
|
|
8
|
-
- Reviews from the active context's reviews/ directory (including cc-native subdirectory)
|
|
9
|
-
|
|
10
|
-
Hook input (from Claude Code):
|
|
11
|
-
{
|
|
12
|
-
"session_id": "abc123",
|
|
13
|
-
"cwd": "/path/to/project",
|
|
14
|
-
...
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
Hook output:
|
|
18
|
-
JSON array of file paths to suggest, or empty array if no suggestions.
|
|
19
|
-
["/path/to/file1.md", "/path/to/file2.md"]
|
|
20
|
-
"""
|
|
21
|
-
import json
|
|
22
|
-
import sys
|
|
23
|
-
from pathlib import Path
|
|
24
|
-
from typing import List, Optional
|
|
25
|
-
|
|
26
|
-
# Add parent directories to path for imports
|
|
27
|
-
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
28
|
-
SHARED_LIB = SCRIPT_DIR.parent / "lib"
|
|
29
|
-
sys.path.insert(0, str(SHARED_LIB.parent))
|
|
30
|
-
|
|
31
|
-
from lib.base.hook_utils import load_hook_input, log_debug, log_info, log_error
|
|
32
|
-
from lib.base.utils import project_dir
|
|
33
|
-
from lib.base.constants import (
|
|
34
|
-
get_context_plans_dir,
|
|
35
|
-
get_context_handoffs_dir,
|
|
36
|
-
get_context_reviews_dir,
|
|
37
|
-
get_context_file_path,
|
|
38
|
-
)
|
|
39
|
-
from lib.context.context_store import (
|
|
40
|
-
get_context_by_session_id,
|
|
41
|
-
get_all_contexts,
|
|
42
|
-
get_context,
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def get_context_files(context_id: str, project_root: Path) -> List[str]:
|
|
47
|
-
"""
|
|
48
|
-
Get all relevant files for a context.
|
|
49
|
-
|
|
50
|
-
Collects:
|
|
51
|
-
- Context file (context.json)
|
|
52
|
-
- Plans (most recent first)
|
|
53
|
-
- Handoffs: index.md from subdirectories (folder-based) OR flat .md files (legacy)
|
|
54
|
-
- Reviews: index.md from subdirectories (folder-based) OR flat review.md (legacy)
|
|
55
|
-
|
|
56
|
-
Args:
|
|
57
|
-
context_id: Context identifier
|
|
58
|
-
project_root: Project root path
|
|
59
|
-
|
|
60
|
-
Returns:
|
|
61
|
-
List of absolute file paths, sorted by modification time (most recent first)
|
|
62
|
-
"""
|
|
63
|
-
files = []
|
|
64
|
-
|
|
65
|
-
# Get context.json file first
|
|
66
|
-
context_file = get_context_file_path(context_id, project_root)
|
|
67
|
-
if context_file.exists():
|
|
68
|
-
files.append(str(context_file))
|
|
69
|
-
log_debug("file-suggestion", f"Found context file for {context_id}")
|
|
70
|
-
|
|
71
|
-
# Get plans directory
|
|
72
|
-
plans_dir = get_context_plans_dir(context_id, project_root)
|
|
73
|
-
if plans_dir.exists():
|
|
74
|
-
plan_files = list(plans_dir.glob("*.md"))
|
|
75
|
-
# Sort by modification time, most recent first
|
|
76
|
-
plan_files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
|
77
|
-
files.extend([str(p) for p in plan_files])
|
|
78
|
-
log_debug("file-suggestion", f"Found {len(plan_files)} plans in {context_id}")
|
|
79
|
-
|
|
80
|
-
# Get handoffs - prefer folder-based (index.md in subdirectories), fall back to legacy
|
|
81
|
-
handoffs_dir = get_context_handoffs_dir(context_id, project_root)
|
|
82
|
-
if handoffs_dir.exists():
|
|
83
|
-
# Find handoff folders (named like YYYY-MM-DD-HHMM or YYYY-MM-DD-HHMM-N)
|
|
84
|
-
handoff_folders = sorted(
|
|
85
|
-
[d for d in handoffs_dir.iterdir() if d.is_dir()],
|
|
86
|
-
key=lambda d: d.name,
|
|
87
|
-
reverse=True # Most recent first (alphabetically sorts by date)
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
if handoff_folders:
|
|
91
|
-
# Use folder-based: get index.md from most recent folder only
|
|
92
|
-
index_file = handoff_folders[0] / "index.md"
|
|
93
|
-
if index_file.exists():
|
|
94
|
-
files.append(str(index_file))
|
|
95
|
-
log_debug("file-suggestion", f"Found handoff folder: {handoff_folders[0].name}")
|
|
96
|
-
else:
|
|
97
|
-
# Legacy support: flat .md files directly in handoffs/
|
|
98
|
-
legacy_handoffs = [f for f in handoffs_dir.glob("*.md") if f.is_file()]
|
|
99
|
-
legacy_handoffs.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
|
100
|
-
if legacy_handoffs:
|
|
101
|
-
files.append(str(legacy_handoffs[0])) # Only most recent legacy
|
|
102
|
-
log_debug("file-suggestion", f"Found {len(legacy_handoffs)} legacy handoffs in {context_id}")
|
|
103
|
-
|
|
104
|
-
# Get reviews - prefer folder-based (index.md in subdirectories), fall back to legacy
|
|
105
|
-
reviews_dir = get_context_reviews_dir(context_id, project_root) / "cc-native"
|
|
106
|
-
if reviews_dir.exists():
|
|
107
|
-
# Find review folders (named like YYYY-MM-DD-HHMM-iteration-N)
|
|
108
|
-
review_folders = sorted(
|
|
109
|
-
[d for d in reviews_dir.iterdir() if d.is_dir()],
|
|
110
|
-
key=lambda d: d.name,
|
|
111
|
-
reverse=True # Most recent first
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
if review_folders:
|
|
115
|
-
# Use folder-based: get index.md from most recent folder only
|
|
116
|
-
index_file = review_folders[0] / "index.md"
|
|
117
|
-
if index_file.exists():
|
|
118
|
-
files.append(str(index_file))
|
|
119
|
-
log_debug("file-suggestion", f"Found review folder: {review_folders[0].name}")
|
|
120
|
-
else:
|
|
121
|
-
# Legacy support: flat review.md directly in cc-native/
|
|
122
|
-
legacy_review = reviews_dir / "review.md"
|
|
123
|
-
if legacy_review.exists():
|
|
124
|
-
files.append(str(legacy_review))
|
|
125
|
-
log_debug("file-suggestion", f"Found legacy review.md in {context_id}")
|
|
126
|
-
|
|
127
|
-
return files
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def get_active_context_id(session_id: str, project_root: Path) -> Optional[str]:
|
|
131
|
-
"""
|
|
132
|
-
Determine the active context for suggestions.
|
|
133
|
-
|
|
134
|
-
Priority:
|
|
135
|
-
1. Context bound to current session_id
|
|
136
|
-
2. Single in-flight context (if only one exists)
|
|
137
|
-
3. None (no suggestions if ambiguous)
|
|
138
|
-
|
|
139
|
-
Args:
|
|
140
|
-
session_id: Current session identifier
|
|
141
|
-
project_root: Project root path
|
|
142
|
-
|
|
143
|
-
Returns:
|
|
144
|
-
Context ID or None
|
|
145
|
-
"""
|
|
146
|
-
# Try session_id lookup first
|
|
147
|
-
if session_id and session_id != "unknown":
|
|
148
|
-
context = get_context_by_session_id(session_id, project_root)
|
|
149
|
-
if context:
|
|
150
|
-
log_debug("file-suggestion", f"Found context by session: {context.id}")
|
|
151
|
-
return context.id
|
|
152
|
-
|
|
153
|
-
# Fall back to single active (non-idle) context
|
|
154
|
-
active = [c for c in get_all_contexts(status="active", project_root=project_root)
|
|
155
|
-
if c.mode != "idle"]
|
|
156
|
-
if len(active) == 1:
|
|
157
|
-
log_debug("file-suggestion", f"Using single active context: {active[0].id}")
|
|
158
|
-
return active[0].id
|
|
159
|
-
|
|
160
|
-
log_debug("file-suggestion", f"No unique context found (active: {len(active)})")
|
|
161
|
-
return None
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def main():
|
|
165
|
-
"""
|
|
166
|
-
Main entry point for file suggestion hook.
|
|
167
|
-
|
|
168
|
-
Reads hook input from stdin, determines active context,
|
|
169
|
-
and outputs file suggestions as JSON array.
|
|
170
|
-
"""
|
|
171
|
-
try:
|
|
172
|
-
# Read hook input using shared utility
|
|
173
|
-
hook_input = load_hook_input()
|
|
174
|
-
|
|
175
|
-
if not hook_input:
|
|
176
|
-
print("[]")
|
|
177
|
-
return
|
|
178
|
-
|
|
179
|
-
# Get project root and session ID
|
|
180
|
-
project_root = project_dir(hook_input)
|
|
181
|
-
session_id = hook_input.get("session_id", "unknown")
|
|
182
|
-
|
|
183
|
-
log_debug("file-suggestion", f"Session: {session_id[:8]}..., Project: {project_root}")
|
|
184
|
-
|
|
185
|
-
# Determine active context
|
|
186
|
-
context_id = get_active_context_id(session_id, project_root)
|
|
187
|
-
|
|
188
|
-
if not context_id:
|
|
189
|
-
print("[]")
|
|
190
|
-
return
|
|
191
|
-
|
|
192
|
-
# Collect file suggestions
|
|
193
|
-
suggestions = get_context_files(context_id, project_root)
|
|
194
|
-
|
|
195
|
-
# Limit suggestions to prevent overwhelming the context
|
|
196
|
-
MAX_SUGGESTIONS = 10
|
|
197
|
-
if len(suggestions) > MAX_SUGGESTIONS:
|
|
198
|
-
log_debug("file-suggestion", f"Limiting suggestions to {MAX_SUGGESTIONS} (was {len(suggestions)})")
|
|
199
|
-
suggestions = suggestions[:MAX_SUGGESTIONS]
|
|
200
|
-
|
|
201
|
-
# Output suggestions as JSON array
|
|
202
|
-
log_info("file-suggestion", f"Suggesting {len(suggestions)} files")
|
|
203
|
-
print(json.dumps(suggestions))
|
|
204
|
-
|
|
205
|
-
except Exception as e:
|
|
206
|
-
import traceback
|
|
207
|
-
tb = traceback.format_exc()
|
|
208
|
-
from lib.base.hook_utils import log_hook_error
|
|
209
|
-
log_hook_error("file-suggestion", e, "SessionStart", traceback_str=tb)
|
|
210
|
-
print("[]")
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if __name__ == "__main__":
|
|
214
|
-
from lib.base.hook_utils import run_hook
|
|
215
|
-
run_hook(main, "file_suggestion")
|