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
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
"""Plan lifecycle management — archival, lookup, and path extraction.
|
|
2
|
-
|
|
3
|
-
Provides pure-data operations on plan files:
|
|
4
|
-
- archive_plan: copy plan to context plans/ folder, compute hash + signature
|
|
5
|
-
- find_latest_plan: locate the most relevant plan for a context
|
|
6
|
-
- extract_plan_path_from_result: parse plan path from ExitPlanMode output
|
|
7
|
-
|
|
8
|
-
This module does NOT modify mode or state.json. The calling hook
|
|
9
|
-
(e.g. archive_plan.py) is responsible for updating mode via
|
|
10
|
-
context_store.update_mode() after archival succeeds.
|
|
11
|
-
"""
|
|
12
|
-
import hashlib
|
|
13
|
-
import re
|
|
14
|
-
import uuid
|
|
15
|
-
from datetime import datetime
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
from typing import List, Optional, Tuple
|
|
18
|
-
|
|
19
|
-
from ..base.atomic_write import atomic_write
|
|
20
|
-
from ..base.constants import get_context_dir, get_context_plans_dir
|
|
21
|
-
from ..base.logger import log_debug, log_info, log_warn, log_error
|
|
22
|
-
from ..base.utils import sanitize_title
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# ---------------------------------------------------------------------------
|
|
26
|
-
# Plan archival
|
|
27
|
-
# ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
def archive_plan(
|
|
30
|
-
plan_path: str,
|
|
31
|
-
context_id: str,
|
|
32
|
-
project_root: Path = None,
|
|
33
|
-
) -> Tuple[Optional[str], Optional[str], Optional[str]]:
|
|
34
|
-
"""Archive a plan file to the context's plans/ folder.
|
|
35
|
-
|
|
36
|
-
Copies the plan content to:
|
|
37
|
-
_output/contexts/{context_id}/plans/{date}-{slug}.md
|
|
38
|
-
|
|
39
|
-
Computes a content hash and signature for change detection and
|
|
40
|
-
fallback matching after /clear.
|
|
41
|
-
|
|
42
|
-
Does NOT modify state.json or mode — the calling hook handles that
|
|
43
|
-
via context_store.update_mode().
|
|
44
|
-
|
|
45
|
-
Args:
|
|
46
|
-
plan_path: Path to the source plan file.
|
|
47
|
-
context_id: Target context identifier.
|
|
48
|
-
project_root: Project root directory (default: from env / cwd).
|
|
49
|
-
|
|
50
|
-
Returns:
|
|
51
|
-
(archived_path, plan_hash, plan_signature) on success.
|
|
52
|
-
(None, None, None) on any error.
|
|
53
|
-
"""
|
|
54
|
-
plan_file = Path(plan_path)
|
|
55
|
-
if not plan_file.exists():
|
|
56
|
-
log_warn("plan_manager", f"Plan file not found: {plan_path}")
|
|
57
|
-
return None, None, None
|
|
58
|
-
|
|
59
|
-
# Read plan content
|
|
60
|
-
try:
|
|
61
|
-
content = plan_file.read_text(encoding="utf-8")
|
|
62
|
-
except Exception as e:
|
|
63
|
-
log_error("plan_manager", f"Failed to read plan: {e}")
|
|
64
|
-
return None, None, None
|
|
65
|
-
|
|
66
|
-
# Compute hash and signature
|
|
67
|
-
plan_hash = hashlib.sha256(content.encode("utf-8")).hexdigest()[:12]
|
|
68
|
-
plan_signature = content[:200]
|
|
69
|
-
|
|
70
|
-
# Ensure plans directory exists
|
|
71
|
-
plans_dir = get_context_plans_dir(context_id, project_root)
|
|
72
|
-
plans_dir.mkdir(parents=True, exist_ok=True)
|
|
73
|
-
|
|
74
|
-
# Generate archive filename: YYYY-MM-DD-<slug>.md
|
|
75
|
-
date_str = datetime.now().strftime("%Y-%m-%d")
|
|
76
|
-
slug = sanitize_title(plan_file.stem, max_len=30)
|
|
77
|
-
archive_name = f"{date_str}-{slug}.md"
|
|
78
|
-
archive_path = plans_dir / archive_name
|
|
79
|
-
|
|
80
|
-
# Handle filename collisions with counter suffix
|
|
81
|
-
counter = 2
|
|
82
|
-
while archive_path.exists():
|
|
83
|
-
archive_name = f"{date_str}-{slug}-{counter}.md"
|
|
84
|
-
archive_path = plans_dir / archive_name
|
|
85
|
-
counter += 1
|
|
86
|
-
|
|
87
|
-
# Write archived plan atomically
|
|
88
|
-
success, error = atomic_write(archive_path, content)
|
|
89
|
-
if not success:
|
|
90
|
-
log_error("plan_manager", f"Failed to write archive: {error}")
|
|
91
|
-
return None, None, None
|
|
92
|
-
|
|
93
|
-
log_info("plan_manager", f"Archived plan to: {archive_path}")
|
|
94
|
-
return str(archive_path), plan_hash, plan_signature
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
# ---------------------------------------------------------------------------
|
|
98
|
-
# Plan lookup
|
|
99
|
-
# ---------------------------------------------------------------------------
|
|
100
|
-
|
|
101
|
-
def find_latest_plan(
|
|
102
|
-
context_id: str,
|
|
103
|
-
project_root: Path = None,
|
|
104
|
-
) -> Optional[str]:
|
|
105
|
-
"""Find the most relevant plan file for a context.
|
|
106
|
-
|
|
107
|
-
Priority:
|
|
108
|
-
1. state.json plan_path — if the file still exists on disk.
|
|
109
|
-
2. Most recent .md in plans/ directory by modification time.
|
|
110
|
-
3. None if no plans found.
|
|
111
|
-
|
|
112
|
-
Args:
|
|
113
|
-
context_id: Context identifier.
|
|
114
|
-
project_root: Project root directory (default: from env / cwd).
|
|
115
|
-
|
|
116
|
-
Returns:
|
|
117
|
-
Absolute path string to the plan file, or None.
|
|
118
|
-
"""
|
|
119
|
-
# 1. Check state.json plan_path first
|
|
120
|
-
try:
|
|
121
|
-
from .context_store import load_state
|
|
122
|
-
state = load_state(context_id, project_root)
|
|
123
|
-
if state and state.plan_path:
|
|
124
|
-
plan_path = Path(state.plan_path)
|
|
125
|
-
if plan_path.exists():
|
|
126
|
-
return str(plan_path)
|
|
127
|
-
except Exception as e:
|
|
128
|
-
log_warn("plan_manager", f"Failed to check state.json plan_path: {e}")
|
|
129
|
-
|
|
130
|
-
# 2. Fall back to most recent .md in plans/ dir by mtime
|
|
131
|
-
plans_dir = get_context_plans_dir(context_id, project_root)
|
|
132
|
-
if plans_dir.exists():
|
|
133
|
-
plans = sorted(
|
|
134
|
-
plans_dir.glob("*.md"),
|
|
135
|
-
key=lambda p: p.stat().st_mtime,
|
|
136
|
-
reverse=True,
|
|
137
|
-
)
|
|
138
|
-
if plans:
|
|
139
|
-
return str(plans[0])
|
|
140
|
-
|
|
141
|
-
# 3. No plan found
|
|
142
|
-
return None
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
# ---------------------------------------------------------------------------
|
|
146
|
-
# Plan identification and normalization
|
|
147
|
-
# ---------------------------------------------------------------------------
|
|
148
|
-
|
|
149
|
-
def generate_plan_id() -> str:
|
|
150
|
-
"""Generate a short unique plan identifier (8 hex chars)."""
|
|
151
|
-
return uuid.uuid4().hex[:8]
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def normalize_plan_content(text: str) -> str:
|
|
155
|
-
"""Aggressively normalize plan content for hashing.
|
|
156
|
-
|
|
157
|
-
Strips all XML/HTML tags and collapses whitespace so that
|
|
158
|
-
wrapper variations (e.g. <system-reminder>) don't affect the hash.
|
|
159
|
-
"""
|
|
160
|
-
text = re.sub(r'<[^>]+>', '', text)
|
|
161
|
-
text = re.sub(r'\s+', ' ', text).strip()
|
|
162
|
-
return text
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def extract_plan_anchors(content: str, max_anchors: int = 5) -> List[str]:
|
|
166
|
-
"""Extract structural anchors from plan content.
|
|
167
|
-
|
|
168
|
-
Returns markdown headings + first substantial paragraph as short strings.
|
|
169
|
-
Used for fuzzy matching when hash-based matching fails.
|
|
170
|
-
"""
|
|
171
|
-
anchors = []
|
|
172
|
-
for line in content.splitlines():
|
|
173
|
-
line = line.strip()
|
|
174
|
-
if line.startswith('#') and len(line) > 3:
|
|
175
|
-
anchors.append(line[:80])
|
|
176
|
-
elif not anchors and len(line) > 20:
|
|
177
|
-
anchors.append(line[:80])
|
|
178
|
-
if len(anchors) >= max_anchors:
|
|
179
|
-
break
|
|
180
|
-
return anchors
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
# ---------------------------------------------------------------------------
|
|
184
|
-
# Path extraction from tool output
|
|
185
|
-
# ---------------------------------------------------------------------------
|
|
186
|
-
|
|
187
|
-
def extract_plan_path_from_result(tool_result: str) -> Optional[str]:
|
|
188
|
-
"""Extract plan file path from ExitPlanMode tool result.
|
|
189
|
-
|
|
190
|
-
Parses the pattern: "Your plan has been saved to: <path>"
|
|
191
|
-
from the tool_result string returned by ExitPlanMode.
|
|
192
|
-
|
|
193
|
-
Args:
|
|
194
|
-
tool_result: Raw text output from the ExitPlanMode tool.
|
|
195
|
-
|
|
196
|
-
Returns:
|
|
197
|
-
Plan file path string (stripped), or None if not found.
|
|
198
|
-
"""
|
|
199
|
-
if not tool_result:
|
|
200
|
-
return None
|
|
201
|
-
match = re.search(r"Your plan has been saved to:\s*(.+\.md)", tool_result)
|
|
202
|
-
if match:
|
|
203
|
-
return match.group(1).strip()
|
|
204
|
-
return None
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
"""Task tracker — direct state.json CRUD for tasks.
|
|
2
|
-
|
|
3
|
-
Writes tasks directly to the tasks[] array in state.json,
|
|
4
|
-
bypassing events.jsonl for faster, simpler task operations.
|
|
5
|
-
|
|
6
|
-
All functions do their own I/O to avoid circular imports with
|
|
7
|
-
context_store.py.
|
|
8
|
-
"""
|
|
9
|
-
import json
|
|
10
|
-
import re
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Dict, List, Optional
|
|
13
|
-
|
|
14
|
-
from ..base.atomic_write import atomic_write
|
|
15
|
-
from ..base.constants import get_context_dir
|
|
16
|
-
from ..base.logger import log_warn
|
|
17
|
-
from ..base.utils import now_iso
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
# ---------------------------------------------------------------------------
|
|
21
|
-
# Internal I/O (avoids circular import with context_store)
|
|
22
|
-
# ---------------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
def _state_path(context_id: str, project_root: Path = None) -> Path:
|
|
25
|
-
return get_context_dir(context_id, project_root) / "state.json"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _load_state(context_id: str, project_root: Path = None) -> Optional[dict]:
|
|
29
|
-
sp = _state_path(context_id, project_root)
|
|
30
|
-
if not sp.exists():
|
|
31
|
-
return None
|
|
32
|
-
try:
|
|
33
|
-
return json.loads(sp.read_text(encoding="utf-8"))
|
|
34
|
-
except Exception as e:
|
|
35
|
-
log_warn("task_tracker", f"Failed to read state.json: {e}")
|
|
36
|
-
return None
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def _save_state(context_id: str, state_data: dict, project_root: Path = None) -> bool:
|
|
40
|
-
sp = _state_path(context_id, project_root)
|
|
41
|
-
content = json.dumps(state_data, indent=2, ensure_ascii=False)
|
|
42
|
-
success, error = atomic_write(sp, content)
|
|
43
|
-
if not success:
|
|
44
|
-
log_warn("task_tracker", f"Failed to write state.json: {error}")
|
|
45
|
-
return success
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# ---------------------------------------------------------------------------
|
|
49
|
-
# Public API
|
|
50
|
-
# ---------------------------------------------------------------------------
|
|
51
|
-
|
|
52
|
-
def generate_next_task_id(context_id: str, project_root: Path = None) -> str:
|
|
53
|
-
"""Scan tasks[] for highest aiw-N, return aiw-(N+1)."""
|
|
54
|
-
state = _load_state(context_id, project_root)
|
|
55
|
-
tasks = state.get("tasks", []) if state else []
|
|
56
|
-
|
|
57
|
-
max_num = 0
|
|
58
|
-
for t in tasks:
|
|
59
|
-
tid = t.get("id", "")
|
|
60
|
-
m = re.match(r"^aiw-(\d+)$", tid)
|
|
61
|
-
if m:
|
|
62
|
-
max_num = max(max_num, int(m.group(1)))
|
|
63
|
-
|
|
64
|
-
return f"aiw-{max_num + 1}"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def add_task(
|
|
68
|
-
context_id: str,
|
|
69
|
-
subject: str,
|
|
70
|
-
description: str = "",
|
|
71
|
-
active_form: str = "",
|
|
72
|
-
session_id: str = "",
|
|
73
|
-
project_root: Path = None,
|
|
74
|
-
) -> Optional[dict]:
|
|
75
|
-
"""Add a new task to state.json tasks[] and return the task dict."""
|
|
76
|
-
state = _load_state(context_id, project_root)
|
|
77
|
-
if state is None:
|
|
78
|
-
return None
|
|
79
|
-
|
|
80
|
-
task_id = generate_next_task_id(context_id, project_root)
|
|
81
|
-
task = {
|
|
82
|
-
"id": task_id,
|
|
83
|
-
"subject": subject,
|
|
84
|
-
"description": description,
|
|
85
|
-
"active_form": active_form,
|
|
86
|
-
"status": "pending",
|
|
87
|
-
"created_at": now_iso(),
|
|
88
|
-
"completed_at": None,
|
|
89
|
-
"evidence": "",
|
|
90
|
-
"work_summary": "",
|
|
91
|
-
"files_changed": [],
|
|
92
|
-
"session_id": session_id,
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
state.setdefault("tasks", []).append(task)
|
|
96
|
-
state["last_active"] = now_iso()
|
|
97
|
-
|
|
98
|
-
if _save_state(context_id, state, project_root):
|
|
99
|
-
return task
|
|
100
|
-
return None
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def update_task(
|
|
104
|
-
context_id: str,
|
|
105
|
-
task_id: str,
|
|
106
|
-
status: str = None,
|
|
107
|
-
evidence: str = "",
|
|
108
|
-
work_summary: str = "",
|
|
109
|
-
files_changed: List[str] = None,
|
|
110
|
-
session_id: str = "",
|
|
111
|
-
project_root: Path = None,
|
|
112
|
-
) -> bool:
|
|
113
|
-
"""Find task by task_id in tasks[], update fields, return True on success."""
|
|
114
|
-
state = _load_state(context_id, project_root)
|
|
115
|
-
if state is None:
|
|
116
|
-
return False
|
|
117
|
-
|
|
118
|
-
for task in state.get("tasks", []):
|
|
119
|
-
if task.get("id") == task_id:
|
|
120
|
-
if status is not None:
|
|
121
|
-
task["status"] = status
|
|
122
|
-
if status == "completed":
|
|
123
|
-
task["completed_at"] = now_iso()
|
|
124
|
-
if evidence:
|
|
125
|
-
task["evidence"] = evidence
|
|
126
|
-
if work_summary:
|
|
127
|
-
task["work_summary"] = work_summary
|
|
128
|
-
if files_changed is not None:
|
|
129
|
-
task["files_changed"] = files_changed
|
|
130
|
-
if session_id:
|
|
131
|
-
task["session_id"] = session_id
|
|
132
|
-
state["last_active"] = now_iso()
|
|
133
|
-
return _save_state(context_id, state, project_root)
|
|
134
|
-
|
|
135
|
-
log_warn("task_tracker", f"Task '{task_id}' not found in context '{context_id}'")
|
|
136
|
-
return False
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def delete_task(context_id: str, task_id: str, project_root: Path = None) -> bool:
|
|
140
|
-
"""Remove task from tasks[] and return True on success."""
|
|
141
|
-
state = _load_state(context_id, project_root)
|
|
142
|
-
if state is None:
|
|
143
|
-
return False
|
|
144
|
-
|
|
145
|
-
tasks = state.get("tasks", [])
|
|
146
|
-
original_len = len(tasks)
|
|
147
|
-
state["tasks"] = [t for t in tasks if t.get("id") != task_id]
|
|
148
|
-
|
|
149
|
-
if len(state["tasks"]) == original_len:
|
|
150
|
-
log_warn("task_tracker", f"Task '{task_id}' not found in context '{context_id}'")
|
|
151
|
-
return False
|
|
152
|
-
|
|
153
|
-
state["last_active"] = now_iso()
|
|
154
|
-
return _save_state(context_id, state, project_root)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def get_tasks(context_id: str, project_root: Path = None) -> List[dict]:
|
|
158
|
-
"""Return tasks[] from state.json."""
|
|
159
|
-
state = _load_state(context_id, project_root)
|
|
160
|
-
if state is None:
|
|
161
|
-
return []
|
|
162
|
-
return state.get("tasks", [])
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def generate_task_summary(context_id: str, project_root: Path = None) -> str:
|
|
166
|
-
"""Partition tasks and format as markdown checklist."""
|
|
167
|
-
tasks = get_tasks(context_id, project_root)
|
|
168
|
-
if not tasks:
|
|
169
|
-
return "No tasks in this context."
|
|
170
|
-
|
|
171
|
-
completed = [t for t in tasks if t.get("status") == "completed"]
|
|
172
|
-
in_progress = [t for t in tasks if t.get("status") == "in_progress"]
|
|
173
|
-
pending = [t for t in tasks if t.get("status") == "pending"]
|
|
174
|
-
blocked = [t for t in tasks if t.get("status") == "blocked"]
|
|
175
|
-
|
|
176
|
-
lines = [f"### Tasks ({len(tasks)} total)", ""]
|
|
177
|
-
|
|
178
|
-
for t in completed:
|
|
179
|
-
ws = f"\n Work: {t['work_summary']}" if t.get("work_summary") else ""
|
|
180
|
-
lines.append(f"- [x] {t['id']}: {t['subject']}{ws}")
|
|
181
|
-
for t in in_progress:
|
|
182
|
-
lines.append(f"- [~] {t['id']}: {t['subject']}")
|
|
183
|
-
for t in pending:
|
|
184
|
-
lines.append(f"- [ ] {t['id']}: {t['subject']}")
|
|
185
|
-
for t in blocked:
|
|
186
|
-
lines.append(f"- [!] {t['id']}: {t['subject']}")
|
|
187
|
-
|
|
188
|
-
return "\n".join(lines)
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"""Handoff utilities for context-aware session management.
|
|
2
|
-
|
|
3
|
-
This module provides graceful context degradation when Claude's
|
|
4
|
-
context window fills up. Instead of rushing or losing work,
|
|
5
|
-
it creates a handoff document and facilitates clean session continuation.
|
|
6
|
-
|
|
7
|
-
Components:
|
|
8
|
-
- document_generator: Creates handoff documents with work state
|
|
9
|
-
- context_monitor hook: Monitors context during tool use and triggers warnings
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from .document_generator import (
|
|
13
|
-
generate_handoff_document,
|
|
14
|
-
get_handoff_continuation_prompt,
|
|
15
|
-
HandoffDocument,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
__all__ = [
|
|
19
|
-
"generate_handoff_document",
|
|
20
|
-
"get_handoff_continuation_prompt",
|
|
21
|
-
"HandoffDocument",
|
|
22
|
-
]
|
|
Binary file
|
|
Binary file
|
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
"""Handoff document generator for context-aware session management.
|
|
2
|
-
|
|
3
|
-
Creates structured handoff documents when a session needs to transfer
|
|
4
|
-
work to a new session (typically due to context window limits).
|
|
5
|
-
|
|
6
|
-
Handoff documents capture:
|
|
7
|
-
- Links to active plan and context folder
|
|
8
|
-
- Current task state from events.jsonl
|
|
9
|
-
- Work in progress summary
|
|
10
|
-
- Next steps for continuation
|
|
11
|
-
"""
|
|
12
|
-
import json
|
|
13
|
-
import uuid
|
|
14
|
-
from dataclasses import dataclass, field
|
|
15
|
-
from datetime import datetime
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
from typing import Any, Dict, List, Optional
|
|
18
|
-
|
|
19
|
-
from ..base.atomic_write import atomic_write
|
|
20
|
-
from ..base.constants import get_context_handoffs_dir, get_context_dir
|
|
21
|
-
from ..base.logger import log_info, log_error
|
|
22
|
-
from ..base.utils import now_iso
|
|
23
|
-
from ..context.context_store import get_context as _get_context_state, save_state as _save_state
|
|
24
|
-
from ..context.task_tracker import get_tasks
|
|
25
|
-
from ..templates.formatters import render_task_list, format_continuation_header, format_reason
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@dataclass
|
|
29
|
-
class HandoffDocument:
|
|
30
|
-
"""Structured handoff document content."""
|
|
31
|
-
context_id: str
|
|
32
|
-
context_summary: str
|
|
33
|
-
session_id: str
|
|
34
|
-
reason: str # e.g., "low_context", "user_requested", "error_recovery"
|
|
35
|
-
created_at: str
|
|
36
|
-
|
|
37
|
-
# Links
|
|
38
|
-
plan_path: Optional[str] = None
|
|
39
|
-
context_folder: str = ""
|
|
40
|
-
events_log_path: str = ""
|
|
41
|
-
|
|
42
|
-
# Task state
|
|
43
|
-
active_tasks: List[Dict[str, Any]] = field(default_factory=list)
|
|
44
|
-
completed_tasks_this_session: List[Dict[str, Any]] = field(default_factory=list)
|
|
45
|
-
|
|
46
|
-
# Context summary
|
|
47
|
-
work_summary: str = ""
|
|
48
|
-
next_steps: List[str] = field(default_factory=list)
|
|
49
|
-
important_notes: List[str] = field(default_factory=list)
|
|
50
|
-
|
|
51
|
-
# File path (set after saving)
|
|
52
|
-
file_path: Optional[str] = None
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def generate_handoff_document(
|
|
56
|
-
context_id: str,
|
|
57
|
-
reason: str = "low_context",
|
|
58
|
-
work_summary: str = "",
|
|
59
|
-
next_steps: Optional[List[str]] = None,
|
|
60
|
-
important_notes: Optional[List[str]] = None,
|
|
61
|
-
completed_this_session: Optional[List[str]] = None,
|
|
62
|
-
project_root: Path = None
|
|
63
|
-
) -> Optional[HandoffDocument]:
|
|
64
|
-
"""
|
|
65
|
-
Generate and save a handoff document for a context.
|
|
66
|
-
|
|
67
|
-
This creates a markdown document capturing current work state,
|
|
68
|
-
saves it to the context's handoffs folder, and records the event.
|
|
69
|
-
|
|
70
|
-
Args:
|
|
71
|
-
context_id: Context identifier
|
|
72
|
-
reason: Why handoff is happening (low_context, user_requested, etc.)
|
|
73
|
-
work_summary: Summary of current work in progress
|
|
74
|
-
next_steps: List of next steps for continuation
|
|
75
|
-
important_notes: Important decisions or context to preserve
|
|
76
|
-
completed_this_session: List of task subjects completed this session
|
|
77
|
-
project_root: Project root directory
|
|
78
|
-
|
|
79
|
-
Returns:
|
|
80
|
-
HandoffDocument with file_path set, or None on failure
|
|
81
|
-
"""
|
|
82
|
-
context = _get_context_state(context_id, project_root)
|
|
83
|
-
if not context:
|
|
84
|
-
log_error("handoff", f"Context '{context_id}' not found")
|
|
85
|
-
return None
|
|
86
|
-
|
|
87
|
-
# Generate session ID
|
|
88
|
-
session_id = str(uuid.uuid4())[:8]
|
|
89
|
-
|
|
90
|
-
# Get pending tasks from state.json
|
|
91
|
-
all_tasks = get_tasks(context_id, project_root)
|
|
92
|
-
pending_tasks = [t for t in all_tasks if t.get("status") in ("pending", "in_progress", "blocked")]
|
|
93
|
-
|
|
94
|
-
# Build document
|
|
95
|
-
now = now_iso()
|
|
96
|
-
context_dir = get_context_dir(context_id, project_root)
|
|
97
|
-
|
|
98
|
-
doc = HandoffDocument(
|
|
99
|
-
context_id=context_id,
|
|
100
|
-
context_summary=context.summary,
|
|
101
|
-
session_id=session_id,
|
|
102
|
-
reason=reason,
|
|
103
|
-
created_at=now,
|
|
104
|
-
plan_path=context.plan_path,
|
|
105
|
-
context_folder=str(context_dir),
|
|
106
|
-
events_log_path=str(context_dir / "state.json"),
|
|
107
|
-
active_tasks=pending_tasks,
|
|
108
|
-
completed_tasks_this_session=[
|
|
109
|
-
{"subject": s} for s in (completed_this_session or [])
|
|
110
|
-
],
|
|
111
|
-
work_summary=work_summary,
|
|
112
|
-
next_steps=next_steps or [],
|
|
113
|
-
important_notes=important_notes or [],
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
# Compute file path BEFORE rendering markdown
|
|
117
|
-
handoffs_dir = get_context_handoffs_dir(context_id, project_root)
|
|
118
|
-
handoffs_dir.mkdir(parents=True, exist_ok=True)
|
|
119
|
-
|
|
120
|
-
# Filename: YYYY-MM-DD-session-{session_id}.md
|
|
121
|
-
date_str = datetime.now().strftime("%Y-%m-%d")
|
|
122
|
-
filename = f"{date_str}-session-{session_id}.md"
|
|
123
|
-
file_path = handoffs_dir / filename
|
|
124
|
-
|
|
125
|
-
# Set file_path on doc BEFORE rendering markdown
|
|
126
|
-
doc.file_path = str(file_path)
|
|
127
|
-
|
|
128
|
-
# Generate markdown content
|
|
129
|
-
markdown = _render_handoff_markdown(doc)
|
|
130
|
-
|
|
131
|
-
# Save to handoffs folder
|
|
132
|
-
|
|
133
|
-
success, error = atomic_write(file_path, markdown)
|
|
134
|
-
if not success:
|
|
135
|
-
log_error("handoff", f"Failed to write handoff document: {error}")
|
|
136
|
-
return None
|
|
137
|
-
|
|
138
|
-
log_info("handoff", f"Created handoff document: {file_path}")
|
|
139
|
-
return doc
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def _render_handoff_markdown(doc: HandoffDocument) -> str:
|
|
143
|
-
"""Render handoff document as markdown."""
|
|
144
|
-
lines = [
|
|
145
|
-
format_continuation_header("handoff", doc.context_id),
|
|
146
|
-
"",
|
|
147
|
-
f"**Created**: {doc.created_at}",
|
|
148
|
-
f"**Context ID**: {doc.context_id}",
|
|
149
|
-
f"**Session ID**: {doc.session_id}",
|
|
150
|
-
f"**Reason**: {format_reason(doc.reason)}",
|
|
151
|
-
"",
|
|
152
|
-
"## Links",
|
|
153
|
-
"",
|
|
154
|
-
]
|
|
155
|
-
|
|
156
|
-
# Plan link
|
|
157
|
-
if doc.plan_path:
|
|
158
|
-
lines.append(f"- **Plan**: [{Path(doc.plan_path).name}]({doc.plan_path})")
|
|
159
|
-
|
|
160
|
-
lines.extend([
|
|
161
|
-
f"- **Context Folder**: `{doc.context_folder}`",
|
|
162
|
-
f"- **Events Log**: `{doc.events_log_path}`",
|
|
163
|
-
"",
|
|
164
|
-
"## Current State",
|
|
165
|
-
"",
|
|
166
|
-
])
|
|
167
|
-
|
|
168
|
-
# Active tasks
|
|
169
|
-
lines.append(render_task_list(doc.active_tasks, header="Active Tasks", show_description=True).rstrip())
|
|
170
|
-
lines.append("")
|
|
171
|
-
|
|
172
|
-
# Completed this session
|
|
173
|
-
if doc.completed_tasks_this_session:
|
|
174
|
-
lines.append(render_task_list(
|
|
175
|
-
doc.completed_tasks_this_session,
|
|
176
|
-
header="Completed This Session",
|
|
177
|
-
show_description=False
|
|
178
|
-
).rstrip())
|
|
179
|
-
lines.append("")
|
|
180
|
-
|
|
181
|
-
# Work summary
|
|
182
|
-
if doc.work_summary:
|
|
183
|
-
lines.extend([
|
|
184
|
-
"## Context Summary",
|
|
185
|
-
"",
|
|
186
|
-
doc.work_summary,
|
|
187
|
-
"",
|
|
188
|
-
])
|
|
189
|
-
|
|
190
|
-
# Next steps
|
|
191
|
-
if doc.next_steps:
|
|
192
|
-
lines.extend([
|
|
193
|
-
"## Next Steps",
|
|
194
|
-
"",
|
|
195
|
-
])
|
|
196
|
-
for i, step in enumerate(doc.next_steps, 1):
|
|
197
|
-
lines.append(f"{i}. {step}")
|
|
198
|
-
lines.append("")
|
|
199
|
-
|
|
200
|
-
# Important notes
|
|
201
|
-
if doc.important_notes:
|
|
202
|
-
lines.extend([
|
|
203
|
-
"## Important Notes",
|
|
204
|
-
"",
|
|
205
|
-
])
|
|
206
|
-
for note in doc.important_notes:
|
|
207
|
-
lines.append(f"- {note}")
|
|
208
|
-
lines.append("")
|
|
209
|
-
|
|
210
|
-
# Continuation prompt
|
|
211
|
-
lines.extend([
|
|
212
|
-
"---",
|
|
213
|
-
"",
|
|
214
|
-
"**Continuation Prompt**:",
|
|
215
|
-
"```",
|
|
216
|
-
f'Continue working on context "{doc.context_id}".',
|
|
217
|
-
"",
|
|
218
|
-
f"Handoff document: {doc.file_path or 'See above'}",
|
|
219
|
-
"",
|
|
220
|
-
"Read the handoff document, restore tasks with TaskCreate, and continue implementation.",
|
|
221
|
-
"```",
|
|
222
|
-
])
|
|
223
|
-
|
|
224
|
-
return "\n".join(lines)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
def get_handoff_continuation_prompt(doc: HandoffDocument) -> str:
|
|
228
|
-
"""
|
|
229
|
-
Generate the prompt to paste into new session for continuation.
|
|
230
|
-
|
|
231
|
-
Args:
|
|
232
|
-
doc: HandoffDocument with file_path set
|
|
233
|
-
|
|
234
|
-
Returns:
|
|
235
|
-
Prompt string for continuing work
|
|
236
|
-
"""
|
|
237
|
-
return f"""Continue working on context "{doc.context_id}".
|
|
238
|
-
|
|
239
|
-
Handoff document: {doc.file_path}
|
|
240
|
-
|
|
241
|
-
Read the handoff document, restore tasks with TaskCreate, and continue implementation."""
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
def get_low_context_warning(context_remaining_percent: int, context_id: str) -> str:
|
|
245
|
-
"""
|
|
246
|
-
Generate system reminder for low context warning.
|
|
247
|
-
|
|
248
|
-
This is injected by the UserPromptSubmit hook when context is low.
|
|
249
|
-
|
|
250
|
-
Args:
|
|
251
|
-
context_remaining_percent: Percentage of context remaining
|
|
252
|
-
context_id: Current context identifier
|
|
253
|
-
|
|
254
|
-
Returns:
|
|
255
|
-
System reminder markdown
|
|
256
|
-
"""
|
|
257
|
-
return f"""<system-reminder>
|
|
258
|
-
## LOW CONTEXT WARNING ({context_remaining_percent}% remaining)
|
|
259
|
-
|
|
260
|
-
Your context window is running low. Please:
|
|
261
|
-
|
|
262
|
-
1. **Finish current task** if 1-2 steps away, OR save current progress
|
|
263
|
-
2. **Create handoff document** by calling:
|
|
264
|
-
```python
|
|
265
|
-
from _shared.lib.handoff import generate_handoff_document
|
|
266
|
-
doc = generate_handoff_document(
|
|
267
|
-
context_id="{context_id}",
|
|
268
|
-
reason="low_context",
|
|
269
|
-
work_summary="<describe current work>",
|
|
270
|
-
next_steps=["<step 1>", "<step 2>"],
|
|
271
|
-
important_notes=["<key decision 1>"]
|
|
272
|
-
)
|
|
273
|
-
```
|
|
274
|
-
3. **Ask permission** to clear and paste continuation prompt
|
|
275
|
-
|
|
276
|
-
After creating handoff, ask the user:
|
|
277
|
-
"Context is low. I've created a handoff document. May I clear and continue in a new session?"
|
|
278
|
-
</system-reminder>"""
|