aiwcli 0.10.3 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/run.js +1 -1
- package/dist/commands/clear.js +28 -131
- package/dist/commands/init/index.js +3 -3
- package/dist/lib/gitignore-manager.d.ts +32 -0
- package/dist/lib/gitignore-manager.js +141 -2
- package/dist/templates/CLAUDE.md +8 -8
- package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
- package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
- package/dist/templates/_shared/.claude/settings.json +7 -7
- package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
- package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
- package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
- package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
- package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
- package/dist/templates/_shared/hooks-ts/session_end.ts +107 -0
- package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
- package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
- package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
- package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
- package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
- package/dist/templates/_shared/lib-ts/base/atomic-write.ts +12 -12
- package/dist/templates/_shared/lib-ts/base/constants.ts +22 -15
- package/dist/templates/_shared/lib-ts/base/git-state.ts +1 -1
- package/dist/templates/_shared/lib-ts/base/hook-utils.ts +129 -50
- package/dist/templates/_shared/lib-ts/base/inference.ts +28 -21
- package/dist/templates/_shared/lib-ts/base/logger.ts +15 -2
- package/dist/templates/_shared/lib-ts/base/state-io.ts +9 -7
- package/dist/templates/_shared/lib-ts/base/stop-words.ts +131 -131
- package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +142 -0
- package/dist/templates/_shared/lib-ts/base/utils.ts +69 -69
- package/dist/templates/_shared/lib-ts/context/context-formatter.ts +30 -24
- package/dist/templates/_shared/lib-ts/context/context-selector.ts +50 -32
- package/dist/templates/_shared/lib-ts/context/context-store.ts +76 -48
- package/dist/templates/_shared/lib-ts/context/plan-manager.ts +43 -23
- package/dist/templates/_shared/lib-ts/context/task-tracker.ts +10 -6
- package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +11 -10
- package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +158 -0
- package/dist/templates/_shared/lib-ts/templates/formatters.ts +6 -4
- package/dist/templates/_shared/lib-ts/types.ts +68 -55
- package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
- package/dist/templates/_shared/scripts/resume_handoff.ts +345 -0
- package/dist/templates/_shared/scripts/save_handoff.ts +3 -3
- package/dist/templates/_shared/scripts/status_line.ts +687 -0
- package/dist/templates/cc-native/.claude/settings.json +175 -185
- package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
- package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +0 -2
- package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +1027 -0
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +156 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +792 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +144 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/corroboration.ts +115 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +120 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +168 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +250 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +275 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +107 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +240 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +385 -0
- package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
- package/dist/templates/cc-native/_cc-native/plan-review.config.json +14 -1
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
- package/dist/templates/_shared/hooks/__init__.py +0 -16
- package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
- package/dist/templates/_shared/hooks/archive_plan.py +0 -177
- package/dist/templates/_shared/hooks/context_monitor.py +0 -270
- package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
- package/dist/templates/_shared/hooks/pre_compact.py +0 -104
- package/dist/templates/_shared/hooks/session_end.py +0 -173
- package/dist/templates/_shared/hooks/session_start.py +0 -206
- package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
- package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
- package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
- package/dist/templates/_shared/lib/__init__.py +0 -1
- package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__init__.py +0 -65
- package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
- package/dist/templates/_shared/lib/base/constants.py +0 -358
- package/dist/templates/_shared/lib/base/hook_utils.py +0 -339
- package/dist/templates/_shared/lib/base/inference.py +0 -307
- package/dist/templates/_shared/lib/base/logger.py +0 -305
- package/dist/templates/_shared/lib/base/stop_words.py +0 -221
- package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
- package/dist/templates/_shared/lib/base/utils.py +0 -263
- package/dist/templates/_shared/lib/context/__init__.py +0 -102
- package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
- package/dist/templates/_shared/lib/context/context_selector.py +0 -508
- package/dist/templates/_shared/lib/context/context_store.py +0 -653
- package/dist/templates/_shared/lib/context/plan_manager.py +0 -303
- package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
- package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
- package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
- package/dist/templates/_shared/lib/templates/README.md +0 -206
- package/dist/templates/_shared/lib/templates/__init__.py +0 -36
- package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
- package/dist/templates/_shared/lib/templates/formatters.py +0 -146
- package/dist/templates/_shared/lib/templates/plan_context.py +0 -73
- package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
- package/dist/templates/_shared/scripts/save_handoff.py +0 -357
- package/dist/templates/_shared/scripts/status_line.py +0 -716
- package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
- package/dist/templates/cc-native/MIGRATION.md +0 -86
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
- package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -954
- package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
- package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
- package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
- package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
- package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
- package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
- package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
- package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
- package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
- package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
- package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1071
- package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
- package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
- package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"""Plan context templates for add_plan_context hook.
|
|
2
|
-
|
|
3
|
-
Provides standardized templates for:
|
|
4
|
-
- Evaluation context reminder (injected on plan writes)
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def get_evaluation_context_reminder() -> str:
|
|
9
|
-
"""Get the plan evaluation context reminder template.
|
|
10
|
-
|
|
11
|
-
Returns:
|
|
12
|
-
Formatted markdown reminder about adding evaluation context
|
|
13
|
-
"""
|
|
14
|
-
return """
|
|
15
|
-
## CRITICAL: Write This Plan for a Different Agent
|
|
16
|
-
|
|
17
|
-
The agent executing this plan has ZERO context from this conversation — no chat history, no memory of files you explored or research you did.
|
|
18
|
-
|
|
19
|
-
**Write as if YOU are that agent. What would you need?**
|
|
20
|
-
|
|
21
|
-
### Required Structure
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
# Plan: <descriptive title>
|
|
25
|
-
|
|
26
|
-
## Background
|
|
27
|
-
Why this change is needed (2-3 sentences)
|
|
28
|
-
|
|
29
|
-
## Task
|
|
30
|
-
What exactly to build/change
|
|
31
|
-
|
|
32
|
-
## Files
|
|
33
|
-
**Modify:**
|
|
34
|
-
- `exact/path/to/file.py` - What changes (reference line numbers or patterns)
|
|
35
|
-
|
|
36
|
-
**Reference:**
|
|
37
|
-
- `exact/path/to/reference.py` - Why relevant (e.g., "pattern to follow at lines 12-30")
|
|
38
|
-
|
|
39
|
-
## Steps
|
|
40
|
-
1. [Specific steps with function names, patterns, or code snippets]
|
|
41
|
-
2. [Enough detail for someone who never saw this conversation]
|
|
42
|
-
|
|
43
|
-
## Constraints
|
|
44
|
-
- Technical requirements, preferences, or limitations
|
|
45
|
-
|
|
46
|
-
## Documentation
|
|
47
|
-
Decisions not written down are lost when this session ends. Update the nearest CLAUDE.md and MEMORY.md so the next session inherits what you learned.
|
|
48
|
-
|
|
49
|
-
**CLAUDE.md** (nearest to changed code — cascades to subdirectories):
|
|
50
|
-
- `exact/path/to/CLAUDE.md` — What to document
|
|
51
|
-
|
|
52
|
-
**What to write:**
|
|
53
|
-
- Architectural choices and why alternatives were rejected
|
|
54
|
-
- Non-obvious constraints (what breaks if this changes)
|
|
55
|
-
- Workarounds with context on the underlying issue
|
|
56
|
-
- Patterns that prevent future mistakes
|
|
57
|
-
|
|
58
|
-
**Format:** `## Topic` / `**Decision:** ...` / `**Rationale:** ...`
|
|
59
|
-
|
|
60
|
-
**MEMORY.md** (cross-session learning for the AI agent):
|
|
61
|
-
- Insight that would prevent a future mistake (e.g., "hook X silently drops field Y")
|
|
62
|
-
|
|
63
|
-
**Include when:** Architectural decisions, non-obvious constraints, workarounds, or patterns discovered during implementation.
|
|
64
|
-
**Omit entries for:** Routine changes with no decisions (rename, formatting, dependency bump).
|
|
65
|
-
When in doubt, write it — a lean entry is better than a lost decision.
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Self-Check
|
|
69
|
-
- [ ] Could I execute this if I forgot our entire conversation?
|
|
70
|
-
- [ ] Are file paths exact (not "the auth file")?
|
|
71
|
-
- [ ] Are implementation details specific (not "use the approach we discussed")?
|
|
72
|
-
- [ ] Do documentation entries capture decisions the next session would otherwise lose?
|
|
73
|
-
""".strip()
|
|
Binary file
|
|
Binary file
|
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Save a handoff document with folder-based sharding.
|
|
3
|
-
|
|
4
|
-
Usage:
|
|
5
|
-
python .aiwcli/_shared/scripts/save_handoff.py <context_id> <<'EOF'
|
|
6
|
-
# Your handoff markdown content here (with <!-- SECTION: name --> markers)
|
|
7
|
-
EOF
|
|
8
|
-
|
|
9
|
-
Or with a file:
|
|
10
|
-
python .aiwcli/_shared/scripts/save_handoff.py <context_id> < handoff.md
|
|
11
|
-
|
|
12
|
-
This script:
|
|
13
|
-
1. Parses sections from incoming markdown using <!-- SECTION: name --> markers
|
|
14
|
-
2. Creates a timestamped folder at _output/contexts/{context_id}/handoffs/{YYYY-MM-DD-HHMM}/
|
|
15
|
-
3. Writes sharded files:
|
|
16
|
-
- index.md (main entry point with navigation)
|
|
17
|
-
- completed-work.md, dead-ends.md, decisions.md, pending.md, context.md
|
|
18
|
-
- plan.md (copy of original plan if it exists)
|
|
19
|
-
4. Records the event in events.jsonl (informational only)
|
|
20
|
-
"""
|
|
21
|
-
import json
|
|
22
|
-
import re
|
|
23
|
-
import subprocess
|
|
24
|
-
import sys
|
|
25
|
-
from datetime import datetime
|
|
26
|
-
from pathlib import Path
|
|
27
|
-
from typing import Dict, Optional
|
|
28
|
-
|
|
29
|
-
# Add parent directories to path for imports
|
|
30
|
-
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
31
|
-
SHARED_ROOT = SCRIPT_DIR.parent
|
|
32
|
-
sys.path.insert(0, str(SHARED_ROOT))
|
|
33
|
-
|
|
34
|
-
from lib.context.context_store import get_context, save_state
|
|
35
|
-
from lib.base.logger import log_info, log_warn, log_error
|
|
36
|
-
from lib.base.atomic_write import atomic_write
|
|
37
|
-
from lib.base.constants import get_handoff_folder_path
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def parse_frontmatter(content: str) -> tuple[Dict[str, str], str]:
|
|
41
|
-
"""Parse YAML frontmatter from markdown content.
|
|
42
|
-
|
|
43
|
-
Returns:
|
|
44
|
-
Tuple of (frontmatter dict, remaining content)
|
|
45
|
-
"""
|
|
46
|
-
frontmatter = {}
|
|
47
|
-
remaining = content
|
|
48
|
-
|
|
49
|
-
if content.startswith('---'):
|
|
50
|
-
parts = content.split('---', 2)
|
|
51
|
-
if len(parts) >= 3:
|
|
52
|
-
fm_lines = parts[1].strip().split('\n')
|
|
53
|
-
for line in fm_lines:
|
|
54
|
-
if ':' in line:
|
|
55
|
-
key, value = line.split(':', 1)
|
|
56
|
-
frontmatter[key.strip()] = value.strip()
|
|
57
|
-
remaining = parts[2].strip()
|
|
58
|
-
|
|
59
|
-
return frontmatter, remaining
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def parse_handoff_sections(content: str) -> Dict[str, str]:
|
|
63
|
-
"""Parse markdown content by section markers.
|
|
64
|
-
|
|
65
|
-
Looks for <!-- SECTION: name --> markers and extracts content between them.
|
|
66
|
-
|
|
67
|
-
Returns:
|
|
68
|
-
Dict mapping section names to their content
|
|
69
|
-
"""
|
|
70
|
-
sections = {}
|
|
71
|
-
current_section = None
|
|
72
|
-
current_content = []
|
|
73
|
-
|
|
74
|
-
for line in content.split('\n'):
|
|
75
|
-
if line.strip().startswith('<!-- SECTION:'):
|
|
76
|
-
# Save previous section
|
|
77
|
-
if current_section:
|
|
78
|
-
sections[current_section] = '\n'.join(current_content).strip()
|
|
79
|
-
# Extract new section name
|
|
80
|
-
match = re.search(r'<!-- SECTION:\s*(\S+)\s*-->', line)
|
|
81
|
-
if match:
|
|
82
|
-
current_section = match.group(1)
|
|
83
|
-
current_content = []
|
|
84
|
-
elif current_section:
|
|
85
|
-
current_content.append(line)
|
|
86
|
-
|
|
87
|
-
# Save final section
|
|
88
|
-
if current_section:
|
|
89
|
-
sections[current_section] = '\n'.join(current_content).strip()
|
|
90
|
-
|
|
91
|
-
return sections
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def get_git_status() -> str:
|
|
95
|
-
"""Get current git status."""
|
|
96
|
-
try:
|
|
97
|
-
result = subprocess.run(
|
|
98
|
-
['git', 'status', '--short'],
|
|
99
|
-
capture_output=True,
|
|
100
|
-
text=True,
|
|
101
|
-
timeout=5
|
|
102
|
-
)
|
|
103
|
-
return result.stdout.strip() or "(no changes)"
|
|
104
|
-
except Exception:
|
|
105
|
-
return "(git status unavailable)"
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def get_plan_path_from_context(context_id: str, project_root: Path) -> Optional[Path]:
|
|
109
|
-
"""Get the plan path from state.json if available."""
|
|
110
|
-
context = get_context(context_id, project_root)
|
|
111
|
-
if not context or not context.plan_path:
|
|
112
|
-
return None
|
|
113
|
-
|
|
114
|
-
plan_path = Path(context.plan_path)
|
|
115
|
-
if plan_path.exists():
|
|
116
|
-
return plan_path
|
|
117
|
-
|
|
118
|
-
return None
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def generate_index(
|
|
122
|
-
frontmatter: Dict[str, str],
|
|
123
|
-
sections: Dict[str, str],
|
|
124
|
-
git_status: str,
|
|
125
|
-
has_plan: bool,
|
|
126
|
-
) -> str:
|
|
127
|
-
"""Generate the index.md file with summary and navigation."""
|
|
128
|
-
now = datetime.now()
|
|
129
|
-
|
|
130
|
-
lines = [
|
|
131
|
-
"---",
|
|
132
|
-
"type: handoff",
|
|
133
|
-
f"context_id: {frontmatter.get('context_id', 'unknown')}",
|
|
134
|
-
f"created_at: {now.isoformat()}",
|
|
135
|
-
f"session_id: {frontmatter.get('session_id', 'unknown')}",
|
|
136
|
-
f"project: {frontmatter.get('project', 'unknown')}",
|
|
137
|
-
f"plan_path: {frontmatter.get('plan_document', 'none')}",
|
|
138
|
-
"---",
|
|
139
|
-
"",
|
|
140
|
-
f"# Session Handoff - {now.strftime('%Y-%m-%d %H:%M')}",
|
|
141
|
-
"",
|
|
142
|
-
]
|
|
143
|
-
|
|
144
|
-
# Summary section
|
|
145
|
-
summary = sections.get('summary', '').strip()
|
|
146
|
-
if summary:
|
|
147
|
-
# Extract just the content (skip the ## Summary header if present)
|
|
148
|
-
summary_lines = summary.split('\n')
|
|
149
|
-
summary_text = '\n'.join(
|
|
150
|
-
line for line in summary_lines
|
|
151
|
-
if not line.strip().startswith('##')
|
|
152
|
-
).strip()
|
|
153
|
-
lines.extend([
|
|
154
|
-
"## Summary",
|
|
155
|
-
summary_text,
|
|
156
|
-
"",
|
|
157
|
-
])
|
|
158
|
-
|
|
159
|
-
# Navigation table
|
|
160
|
-
lines.extend([
|
|
161
|
-
"## Quick Navigation",
|
|
162
|
-
"",
|
|
163
|
-
"| Document | Purpose | Priority |",
|
|
164
|
-
"|----------|---------|----------|",
|
|
165
|
-
"| [Dead Ends](./dead-ends.md) | Failed approaches - DO NOT RETRY | Read First |",
|
|
166
|
-
"| [Pending](./pending.md) | Next steps and blockers | Action Items |",
|
|
167
|
-
"| [Completed Work](./completed-work.md) | Tasks finished this session | Reference |",
|
|
168
|
-
"| [Decisions](./decisions.md) | Technical choices and rationale | Reference |",
|
|
169
|
-
])
|
|
170
|
-
|
|
171
|
-
if has_plan:
|
|
172
|
-
lines.append("| [Plan](./plan.md) | Original plan being implemented | Reference |")
|
|
173
|
-
|
|
174
|
-
lines.extend([
|
|
175
|
-
"| [Context](./context.md) | External requirements and notes | Reference |",
|
|
176
|
-
"",
|
|
177
|
-
"## Continuation Instructions",
|
|
178
|
-
"",
|
|
179
|
-
"To continue this work in a new session:",
|
|
180
|
-
"1. This index document provides the overview",
|
|
181
|
-
"2. **Read [Dead Ends](./dead-ends.md) first** to avoid repeating failed approaches",
|
|
182
|
-
"3. Check [Pending](./pending.md) for immediate next steps",
|
|
183
|
-
"4. Reference other documents as needed",
|
|
184
|
-
"",
|
|
185
|
-
"## Git Status at Handoff",
|
|
186
|
-
"```",
|
|
187
|
-
git_status,
|
|
188
|
-
"```",
|
|
189
|
-
"",
|
|
190
|
-
])
|
|
191
|
-
|
|
192
|
-
return '\n'.join(lines)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def write_section_file(folder: Path, filename: str, title: str, content: str) -> bool:
|
|
196
|
-
"""Write a section file with header."""
|
|
197
|
-
lines = [
|
|
198
|
-
f"# {title}",
|
|
199
|
-
"",
|
|
200
|
-
content if content else "(No content for this section)",
|
|
201
|
-
"",
|
|
202
|
-
]
|
|
203
|
-
|
|
204
|
-
file_path = folder / filename
|
|
205
|
-
success, error = atomic_write(file_path, '\n'.join(lines))
|
|
206
|
-
if not success:
|
|
207
|
-
log_warn("save_handoff", f"Failed to write {filename}: {error}")
|
|
208
|
-
return False
|
|
209
|
-
return True
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
def main():
|
|
213
|
-
if len(sys.argv) < 2:
|
|
214
|
-
print("Usage: python save_handoff.py <context_id> < content.md", file=sys.stderr)
|
|
215
|
-
print(" python save_handoff.py <context_id> <<'EOF'", file=sys.stderr)
|
|
216
|
-
print(" ... markdown content with <!-- SECTION: name --> markers ...", file=sys.stderr)
|
|
217
|
-
print(" EOF", file=sys.stderr)
|
|
218
|
-
sys.exit(1)
|
|
219
|
-
|
|
220
|
-
context_id = sys.argv[1]
|
|
221
|
-
|
|
222
|
-
# Read content from stdin
|
|
223
|
-
content = sys.stdin.read()
|
|
224
|
-
if not content.strip():
|
|
225
|
-
log_error("save_handoff", "No content provided via stdin")
|
|
226
|
-
sys.exit(1)
|
|
227
|
-
|
|
228
|
-
# Project root is the parent of .aiwcli
|
|
229
|
-
project_root = SHARED_ROOT.parent.parent
|
|
230
|
-
|
|
231
|
-
# Verify context exists
|
|
232
|
-
context = get_context(context_id, project_root)
|
|
233
|
-
if not context:
|
|
234
|
-
log_error("save_handoff", f"Context not found: {context_id}")
|
|
235
|
-
sys.exit(1)
|
|
236
|
-
|
|
237
|
-
# Parse frontmatter and sections
|
|
238
|
-
frontmatter, body = parse_frontmatter(content)
|
|
239
|
-
sections = parse_handoff_sections(body)
|
|
240
|
-
|
|
241
|
-
log_info("save_handoff", f"Parsed {len(sections)} sections: {list(sections.keys())}")
|
|
242
|
-
|
|
243
|
-
# Create handoff folder
|
|
244
|
-
handoff_folder = get_handoff_folder_path(context_id, project_root)
|
|
245
|
-
handoff_folder.mkdir(parents=True, exist_ok=True)
|
|
246
|
-
log_info("save_handoff", f"Created folder: {handoff_folder}")
|
|
247
|
-
|
|
248
|
-
# Get git status
|
|
249
|
-
git_status = get_git_status()
|
|
250
|
-
|
|
251
|
-
# Check for plan
|
|
252
|
-
plan_path = get_plan_path_from_context(context_id, project_root)
|
|
253
|
-
has_plan = plan_path is not None
|
|
254
|
-
|
|
255
|
-
# Copy plan if exists
|
|
256
|
-
if plan_path:
|
|
257
|
-
try:
|
|
258
|
-
plan_content = plan_path.read_text(encoding='utf-8')
|
|
259
|
-
plan_dest = handoff_folder / "plan.md"
|
|
260
|
-
success, error = atomic_write(plan_dest, plan_content)
|
|
261
|
-
if success:
|
|
262
|
-
log_info("save_handoff", f"Copied plan from {plan_path}")
|
|
263
|
-
else:
|
|
264
|
-
log_warn("save_handoff", f"Failed to copy plan: {error}")
|
|
265
|
-
except Exception as e:
|
|
266
|
-
log_warn("save_handoff", f"Failed to read plan: {e}")
|
|
267
|
-
|
|
268
|
-
# Write index.md
|
|
269
|
-
index_content = generate_index(frontmatter, sections, git_status, has_plan)
|
|
270
|
-
index_path = handoff_folder / "index.md"
|
|
271
|
-
success, error = atomic_write(index_path, index_content)
|
|
272
|
-
if not success:
|
|
273
|
-
log_error("save_handoff", f"Failed to write index.md: {error}")
|
|
274
|
-
sys.exit(1)
|
|
275
|
-
|
|
276
|
-
# Write section files
|
|
277
|
-
section_mapping = {
|
|
278
|
-
'completed': ('completed-work.md', 'Work Completed'),
|
|
279
|
-
'dead-ends': ('dead-ends.md', 'Dead Ends - Do Not Retry'),
|
|
280
|
-
'decisions': ('decisions.md', 'Key Decisions'),
|
|
281
|
-
'pending': ('pending.md', 'Pending Issues'),
|
|
282
|
-
'next-steps': ('pending.md', None), # Append to pending.md
|
|
283
|
-
'files': ('completed-work.md', None), # Append to completed-work.md
|
|
284
|
-
'context': ('context.md', 'Context for Future Sessions'),
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
# Track which files we've written with their content
|
|
288
|
-
file_contents: Dict[str, list] = {}
|
|
289
|
-
|
|
290
|
-
for section_name, (filename, title) in section_mapping.items():
|
|
291
|
-
section_content = sections.get(section_name, '')
|
|
292
|
-
if not section_content:
|
|
293
|
-
continue
|
|
294
|
-
|
|
295
|
-
if title is None:
|
|
296
|
-
# Append mode - add to existing content
|
|
297
|
-
if filename not in file_contents:
|
|
298
|
-
file_contents[filename] = []
|
|
299
|
-
file_contents[filename].append(section_content)
|
|
300
|
-
else:
|
|
301
|
-
# Write mode - set as main content with title
|
|
302
|
-
if filename not in file_contents:
|
|
303
|
-
file_contents[filename] = [f"# {title}", "", section_content]
|
|
304
|
-
else:
|
|
305
|
-
# Insert title at beginning if not present
|
|
306
|
-
file_contents[filename] = [f"# {title}", ""] + file_contents[filename] + ["", section_content]
|
|
307
|
-
|
|
308
|
-
# Write all accumulated content
|
|
309
|
-
for filename, content_parts in file_contents.items():
|
|
310
|
-
file_path = handoff_folder / filename
|
|
311
|
-
full_content = '\n'.join(content_parts) + '\n'
|
|
312
|
-
success, error = atomic_write(file_path, full_content)
|
|
313
|
-
if not success:
|
|
314
|
-
log_warn("save_handoff", f"Failed to write {filename}: {error}")
|
|
315
|
-
|
|
316
|
-
# Ensure all expected files exist (even if empty)
|
|
317
|
-
expected_files = ['completed-work.md', 'dead-ends.md', 'decisions.md', 'pending.md', 'context.md']
|
|
318
|
-
titles = {
|
|
319
|
-
'completed-work.md': 'Work Completed',
|
|
320
|
-
'dead-ends.md': 'Dead Ends - Do Not Retry',
|
|
321
|
-
'decisions.md': 'Key Decisions',
|
|
322
|
-
'pending.md': 'Pending Issues & Next Steps',
|
|
323
|
-
'context.md': 'Context for Future Sessions',
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
for filename in expected_files:
|
|
327
|
-
file_path = handoff_folder / filename
|
|
328
|
-
if not file_path.exists():
|
|
329
|
-
write_section_file(handoff_folder, filename, titles[filename], "")
|
|
330
|
-
|
|
331
|
-
# Set handoff_path so next session auto-detects it
|
|
332
|
-
try:
|
|
333
|
-
index_path_str = str(handoff_folder / "index.md")
|
|
334
|
-
state = get_context(context_id, project_root)
|
|
335
|
-
if state:
|
|
336
|
-
state.handoff_path = index_path_str
|
|
337
|
-
state.handoff_consumed = False
|
|
338
|
-
save_state(state, project_root)
|
|
339
|
-
log_info("save_handoff", f"Set handoff_path: {index_path_str}")
|
|
340
|
-
else:
|
|
341
|
-
log_warn("save_handoff", f"Could not load context state for {context_id}")
|
|
342
|
-
except Exception as e:
|
|
343
|
-
log_warn("save_handoff", f"Handoff saved but auto-resume won't work (context update failed): {e}")
|
|
344
|
-
|
|
345
|
-
# Output success message (ASCII-safe for Windows)
|
|
346
|
-
print(f"[OK] Created handoff folder: {handoff_folder}")
|
|
347
|
-
print(f" - index.md (entry point with navigation)")
|
|
348
|
-
|
|
349
|
-
files_created = [f.name for f in handoff_folder.iterdir() if f.is_file() and f.name != 'index.md']
|
|
350
|
-
print(f" - {', '.join(sorted(files_created))}")
|
|
351
|
-
|
|
352
|
-
print()
|
|
353
|
-
print("Handoff document saved. Use this folder for context in the next session.")
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if __name__ == "__main__":
|
|
357
|
-
main()
|