aiwcli 0.9.8 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/bin/run.js +5 -2
  2. package/dist/lib/claude-settings-types.d.ts +2 -0
  3. package/dist/templates/CLAUDE.md +3 -3
  4. package/dist/templates/_shared/.claude/settings.json +4 -0
  5. package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  6. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  7. package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
  8. package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
  9. package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
  10. package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
  11. package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
  12. package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
  13. package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
  14. package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
  15. package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  16. package/dist/templates/_shared/hooks/archive_plan.py +87 -178
  17. package/dist/templates/_shared/hooks/context_monitor.py +104 -247
  18. package/dist/templates/_shared/hooks/file-suggestion.py +26 -23
  19. package/dist/templates/_shared/hooks/pre_compact.py +47 -32
  20. package/dist/templates/_shared/hooks/session_end.py +103 -60
  21. package/dist/templates/_shared/hooks/session_start.py +110 -81
  22. package/dist/templates/_shared/hooks/task_create_capture.py +26 -50
  23. package/dist/templates/_shared/hooks/task_update_capture.py +42 -115
  24. package/dist/templates/_shared/hooks/user_prompt_submit.py +61 -61
  25. package/dist/templates/_shared/lib/base/__init__.py +16 -0
  26. package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
  27. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  28. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  29. package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
  30. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  31. package/dist/templates/_shared/lib/base/hook_utils.py +199 -11
  32. package/dist/templates/_shared/lib/base/inference.py +121 -0
  33. package/dist/templates/_shared/lib/base/logger.py +291 -0
  34. package/dist/templates/_shared/lib/base/utils.py +42 -9
  35. package/dist/templates/_shared/lib/context/__init__.py +72 -80
  36. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  37. package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
  38. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  39. package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
  40. package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
  41. package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
  42. package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
  43. package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
  44. package/dist/templates/_shared/lib/context/context_formatter.py +316 -0
  45. package/dist/templates/_shared/lib/context/context_selector.py +491 -0
  46. package/dist/templates/_shared/lib/context/context_store.py +636 -0
  47. package/dist/templates/_shared/lib/context/plan_manager.py +204 -0
  48. package/dist/templates/_shared/lib/context/task_tracker.py +188 -0
  49. package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
  50. package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
  51. package/dist/templates/_shared/lib/handoff/document_generator.py +14 -40
  52. package/dist/templates/_shared/lib/templates/README.md +5 -13
  53. package/dist/templates/_shared/lib/templates/__init__.py +2 -6
  54. package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
  55. package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
  56. package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
  57. package/dist/templates/_shared/lib/templates/plan_context.py +1 -38
  58. package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
  59. package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
  60. package/dist/templates/_shared/scripts/save_handoff.py +39 -19
  61. package/dist/templates/_shared/scripts/status_line.py +701 -0
  62. package/dist/templates/_shared/workflows/handoff.md +9 -3
  63. package/dist/templates/cc-native/.claude/settings.json +41 -8
  64. package/dist/templates/cc-native/CC-NATIVE-README.md +25 -28
  65. package/dist/templates/cc-native/MIGRATION.md +1 -1
  66. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +14 -39
  67. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +49 -21
  68. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  69. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  70. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
  71. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
  72. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
  73. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
  74. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +57 -55
  75. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +163 -131
  76. package/dist/templates/cc-native/_cc-native/hooks/plan_accepted.py +127 -0
  77. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +81 -0
  78. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +26 -25
  79. package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +6 -4
  80. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  81. package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
  82. package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
  83. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  84. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  85. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  86. package/dist/templates/cc-native/_cc-native/lib/debug.py +37 -22
  87. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +34 -29
  88. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  89. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  90. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  91. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  92. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  93. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +26 -21
  94. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +12 -7
  95. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +12 -7
  96. package/dist/templates/cc-native/_cc-native/lib/state.py +31 -16
  97. package/dist/templates/cc-native/_cc-native/lib/utils.py +207 -40
  98. package/dist/templates/cc-native/_cc-native/plan-review.config.json +1 -2
  99. package/oclif.manifest.json +1 -1
  100. package/package.json +1 -1
  101. package/dist/templates/_shared/hooks/context_enforcer.py +0 -625
  102. package/dist/templates/_shared/hooks/task_create_atomicity.py +0 -177
  103. package/dist/templates/_shared/lib/context/auto_state.py +0 -167
  104. package/dist/templates/_shared/lib/context/cache.py +0 -444
  105. package/dist/templates/_shared/lib/context/context_extractor.py +0 -115
  106. package/dist/templates/_shared/lib/context/context_manager.py +0 -1057
  107. package/dist/templates/_shared/lib/context/discovery.py +0 -554
  108. package/dist/templates/_shared/lib/context/event_log.py +0 -316
  109. package/dist/templates/_shared/lib/context/plan_archive.py +0 -101
  110. package/dist/templates/_shared/lib/context/task_sync.py +0 -407
  111. package/dist/templates/_shared/lib/templates/persona_questions.py +0 -113
  112. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  113. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
  114. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
  115. package/dist/templates/cc-native/_cc-native/lib/async_archive.py +0 -68
  116. package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +0 -98
@@ -1,316 +0,0 @@
1
- """Event log utilities for context management.
2
-
3
- events.jsonl is the SOURCE OF TRUTH for each context.
4
- All state is derived by replaying these events.
5
-
6
- Event format (one JSON object per line):
7
- {"event": "event_type", "timestamp": "ISO8601", ...event-specific fields}
8
-
9
- Crash safety:
10
- - Append-only file
11
- - Each line is independent JSON
12
- - Corrupted lines are skipped with warning
13
- - Valid events before corruption are preserved
14
- """
15
- import json
16
- from dataclasses import dataclass, field
17
- from datetime import datetime
18
- from pathlib import Path
19
- from typing import Any, Dict, List, Optional
20
-
21
- from ..base.atomic_write import atomic_append
22
- from ..base.constants import get_events_file_path
23
- from ..base.utils import eprint, now_iso
24
-
25
-
26
- # Event type constants
27
- EVENT_CONTEXT_CREATED = "context_created"
28
- EVENT_CONTEXT_COMPLETED = "context_completed"
29
- EVENT_CONTEXT_REOPENED = "context_reopened"
30
- EVENT_CONTEXT_ARCHIVED = "context_archived"
31
- EVENT_METADATA_UPDATED = "metadata_updated"
32
- EVENT_TASK_ADDED = "task_added"
33
- EVENT_TASK_STARTED = "task_started"
34
- EVENT_TASK_COMPLETED = "task_completed"
35
- EVENT_TASK_BLOCKED = "task_blocked"
36
- EVENT_NOTE_ADDED = "note_added"
37
- EVENT_SESSION_STARTED = "session_started"
38
- EVENT_PLANNING_STARTED = "planning_started"
39
- EVENT_PLAN_CREATED = "plan_created"
40
- EVENT_PLAN_IMPLEMENTATION_STARTED = "plan_implementation_started"
41
- EVENT_PLAN_COMPLETED = "plan_completed"
42
- EVENT_HANDOFF_CREATED = "handoff_created"
43
- EVENT_HANDOFF_CLEARED = "handoff_cleared"
44
- EVENT_SESSION_ENDED = "session_ended"
45
- EVENT_AUTO_STATE_SAVED = "auto_state_saved"
46
- EVENT_TASK_DELETED = "task_deleted"
47
-
48
-
49
- @dataclass
50
- class Task:
51
- """Task state derived from events."""
52
- id: str
53
- subject: str
54
- description: str = ""
55
- active_form: str = ""
56
- status: str = "pending" # pending, in_progress, completed, blocked
57
- evidence: str = ""
58
- work_summary: str = ""
59
- files_changed: List[str] = field(default_factory=list)
60
- blocked_reason: str = ""
61
-
62
-
63
- @dataclass
64
- class ContextState:
65
- """Current state of a context, derived from events."""
66
- id: str
67
- status: str = "active" # active, completed
68
- summary: str = ""
69
- method: Optional[str] = None
70
- tags: List[str] = field(default_factory=list)
71
- created_at: Optional[str] = None
72
- last_active: Optional[str] = None
73
- tasks: List[Task] = field(default_factory=list)
74
- notes: List[str] = field(default_factory=list)
75
- plan_status: str = "none" # none, planning, pending_implementation, implementing
76
- plan_path: Optional[str] = None
77
- plan_hash: Optional[str] = None
78
-
79
-
80
- def read_events(context_id: str, project_root: Path = None) -> List[Dict[str, Any]]:
81
- """
82
- Read all events from a context's events.jsonl file.
83
-
84
- Handles corrupted lines gracefully by skipping them with a warning.
85
-
86
- Args:
87
- context_id: Context identifier
88
- project_root: Project root directory (default: cwd)
89
-
90
- Returns:
91
- List of event dictionaries, in chronological order
92
- """
93
- events_path = get_events_file_path(context_id, project_root)
94
-
95
- if not events_path.exists():
96
- return []
97
-
98
- events = []
99
- try:
100
- content = events_path.read_text(encoding='utf-8')
101
- for line_num, line in enumerate(content.splitlines(), 1):
102
- line = line.strip()
103
- if not line:
104
- continue
105
-
106
- try:
107
- event = json.loads(line)
108
- events.append(event)
109
- except json.JSONDecodeError:
110
- eprint(f"[event_log] WARNING: Skipping corrupted line {line_num} in {events_path}")
111
-
112
- except UnicodeDecodeError as e:
113
- eprint(f"[event_log] WARNING: Invalid UTF-8 in events file {events_path}, attempting fallback read")
114
- # Try reading with error handling to salvage what we can
115
- try:
116
- content = events_path.read_text(encoding='utf-8', errors='replace')
117
- for line_num, line in enumerate(content.splitlines(), 1):
118
- line = line.strip()
119
- if not line:
120
- continue
121
-
122
- try:
123
- event = json.loads(line)
124
- events.append(event)
125
- except json.JSONDecodeError:
126
- eprint(f"[event_log] WARNING: Skipping corrupted line {line_num} in {events_path}")
127
- except Exception as fallback_error:
128
- eprint(f"[event_log] ERROR: Fallback read failed: {fallback_error}")
129
-
130
- except Exception as e:
131
- eprint(f"[event_log] ERROR reading events file: {e}")
132
-
133
- return events
134
-
135
-
136
- def append_event(
137
- context_id: str,
138
- event_type: str,
139
- project_root: Path = None,
140
- **event_data
141
- ) -> bool:
142
- """
143
- Append an event to a context's events.jsonl file.
144
-
145
- Args:
146
- context_id: Context identifier
147
- event_type: Type of event (e.g., "task_added", "context_completed")
148
- project_root: Project root directory (default: cwd)
149
- **event_data: Additional event-specific data
150
-
151
- Returns:
152
- True if event was successfully appended
153
- """
154
- events_path = get_events_file_path(context_id, project_root)
155
-
156
- event = {
157
- "event": event_type,
158
- "timestamp": now_iso(),
159
- **event_data
160
- }
161
-
162
- try:
163
- event_json = json.dumps(event, ensure_ascii=False)
164
- success, error = atomic_append(events_path, event_json + "\n")
165
-
166
- if not success:
167
- eprint(f"[event_log] ERROR appending event: {error}")
168
- return False
169
-
170
- return True
171
-
172
- except Exception as e:
173
- eprint(f"[event_log] ERROR serializing event: {e}")
174
- return False
175
-
176
-
177
- def get_current_state(context_id: str, project_root: Path = None) -> ContextState:
178
- """
179
- Compute current context state by replaying events.
180
-
181
- This is the canonical way to determine current state -
182
- everything is derived from events.jsonl.
183
-
184
- Args:
185
- context_id: Context identifier
186
- project_root: Project root directory (default: cwd)
187
-
188
- Returns:
189
- ContextState representing current state
190
- """
191
- events = read_events(context_id, project_root)
192
-
193
- state = ContextState(id=context_id)
194
- tasks_map: Dict[str, Task] = {}
195
-
196
- for event in events:
197
- event_type = event.get("event")
198
- timestamp = event.get("timestamp")
199
-
200
- # Update last_active for any event
201
- state.last_active = timestamp
202
-
203
- if event_type == EVENT_CONTEXT_CREATED:
204
- state.summary = event.get("summary", "")
205
- state.method = event.get("method")
206
- state.tags = event.get("tags", [])
207
- state.created_at = timestamp
208
-
209
- elif event_type == EVENT_CONTEXT_COMPLETED:
210
- state.status = "completed"
211
-
212
- elif event_type == EVENT_CONTEXT_REOPENED:
213
- state.status = "active"
214
-
215
- elif event_type == EVENT_METADATA_UPDATED:
216
- if "summary" in event:
217
- state.summary = event["summary"]
218
- if "tags" in event:
219
- state.tags = event["tags"]
220
- if "method" in event:
221
- state.method = event["method"]
222
-
223
- elif event_type == EVENT_TASK_ADDED:
224
- task_id = event.get("task_id")
225
- if task_id:
226
- tasks_map[task_id] = Task(
227
- id=task_id,
228
- subject=event.get("subject", ""),
229
- description=event.get("description", ""),
230
- active_form=event.get("activeForm", ""),
231
- status="pending"
232
- )
233
-
234
- elif event_type == EVENT_TASK_STARTED:
235
- task_id = event.get("task_id")
236
- if task_id and task_id in tasks_map:
237
- tasks_map[task_id].status = "in_progress"
238
-
239
- elif event_type == EVENT_TASK_COMPLETED:
240
- task_id = event.get("task_id")
241
- if task_id and task_id in tasks_map:
242
- task = tasks_map[task_id]
243
- task.status = "completed"
244
- task.evidence = event.get("evidence", "")
245
- task.work_summary = event.get("work_summary", "")
246
- task.files_changed = event.get("files_changed", [])
247
-
248
- elif event_type == EVENT_TASK_BLOCKED:
249
- task_id = event.get("task_id")
250
- if task_id and task_id in tasks_map:
251
- tasks_map[task_id].status = "blocked"
252
- tasks_map[task_id].blocked_reason = event.get("reason", "")
253
-
254
- elif event_type == EVENT_TASK_DELETED:
255
- task_id = event.get("task_id")
256
- if task_id and task_id in tasks_map:
257
- del tasks_map[task_id]
258
-
259
- elif event_type == EVENT_NOTE_ADDED:
260
- note = event.get("content", "")
261
- if note:
262
- state.notes.append(note)
263
-
264
- elif event_type == EVENT_PLAN_CREATED:
265
- state.plan_status = "pending_implementation"
266
- state.plan_path = event.get("path")
267
- state.plan_hash = event.get("hash")
268
-
269
- elif event_type == EVENT_PLAN_IMPLEMENTATION_STARTED:
270
- state.plan_status = "implementing"
271
-
272
- elif event_type == EVENT_PLAN_COMPLETED:
273
- state.plan_status = "none"
274
- state.plan_path = None
275
- state.plan_hash = None
276
-
277
- # Convert tasks map to list
278
- state.tasks = list(tasks_map.values())
279
-
280
- return state
281
-
282
-
283
- def are_all_tasks_completed(context_id: str, project_root: Path = None) -> bool:
284
- """
285
- Check if all tasks in a context are completed.
286
-
287
- Useful for suggesting context completion to user.
288
-
289
- Args:
290
- context_id: Context identifier
291
- project_root: Project root directory (default: cwd)
292
-
293
- Returns:
294
- True if all tasks are completed (or no tasks exist)
295
- """
296
- state = get_current_state(context_id, project_root)
297
-
298
- if not state.tasks:
299
- return True # No tasks = trivially complete
300
-
301
- return all(task.status == "completed" for task in state.tasks)
302
-
303
-
304
- def get_pending_tasks(context_id: str, project_root: Path = None) -> List[Task]:
305
- """
306
- Get all non-completed tasks from a context.
307
-
308
- Args:
309
- context_id: Context identifier
310
- project_root: Project root directory (default: cwd)
311
-
312
- Returns:
313
- List of tasks that are not completed
314
- """
315
- state = get_current_state(context_id, project_root)
316
- return [t for t in state.tasks if t.status != "completed"]
@@ -1,101 +0,0 @@
1
- """Plan archive utilities for context management.
2
-
3
- Provides functions for archiving plans to context folders and
4
- managing plan lifecycle.
5
-
6
- Used by:
7
- - ExitPlanMode hook to archive approved plans
8
- - SessionStart to detect pending implementations
9
- """
10
- import hashlib
11
- import shutil
12
- from datetime import datetime
13
- from pathlib import Path
14
- from typing import Optional, Tuple
15
-
16
- from .context_manager import (
17
- Context,
18
- create_context,
19
- get_context,
20
- get_all_contexts,
21
- update_plan_status,
22
- )
23
- from .event_log import append_event, EVENT_PLAN_CREATED
24
- from ..base.atomic_write import atomic_write
25
- from ..base.constants import get_context_plans_dir
26
- from ..base.utils import eprint, now_iso, sanitize_title
27
-
28
-
29
- def archive_plan_to_context(
30
- plan_path: str,
31
- context_id: str,
32
- project_root: Path = None
33
- ) -> Tuple[Optional[str], Optional[str]]:
34
- """
35
- Archive plan to context's plans folder.
36
-
37
- Actions:
38
- 1. Copy plan to _output/contexts/<context_id>/plans/<date>-<slug>.md
39
- 2. Compute plan hash for change detection
40
- 3. Update context.json: in_flight.mode = "pending_implementation"
41
- 4. Update context.json: in_flight.artifact_path = archived path
42
-
43
- Args:
44
- plan_path: Path to the plan file to archive
45
- context_id: Target context ID
46
- project_root: Project root directory
47
-
48
- Returns:
49
- Tuple of (archived_path, plan_hash) or (None, None) on error
50
- """
51
- plan_file = Path(plan_path)
52
- if not plan_file.exists():
53
- eprint(f"[plan_archive] Plan file not found: {plan_path}")
54
- return None, None
55
-
56
- # Read plan content
57
- try:
58
- plan_content = plan_file.read_text(encoding='utf-8')
59
- except Exception as e:
60
- eprint(f"[plan_archive] Failed to read plan: {e}")
61
- return None, None
62
-
63
- # Compute hash for change detection
64
- plan_hash = hashlib.sha256(plan_content.encode('utf-8')).hexdigest()[:12]
65
-
66
- # Create plans directory
67
- plans_dir = get_context_plans_dir(context_id, project_root)
68
- plans_dir.mkdir(parents=True, exist_ok=True)
69
-
70
- # Generate archive filename: YYYY-MM-DD-<slug>.md
71
- date_str = datetime.now().strftime("%Y-%m-%d")
72
- slug = sanitize_title(plan_file.stem, max_len=30)
73
- archive_name = f"{date_str}-{slug}.md"
74
- archive_path = plans_dir / archive_name
75
-
76
- # Handle name collision
77
- counter = 2
78
- while archive_path.exists():
79
- archive_name = f"{date_str}-{slug}-{counter}.md"
80
- archive_path = plans_dir / archive_name
81
- counter += 1
82
-
83
- # Write archived plan
84
- success, error = atomic_write(archive_path, plan_content)
85
- if not success:
86
- eprint(f"[plan_archive] Failed to write archive: {error}")
87
- return None, None
88
-
89
- # Update context plan status
90
- update_plan_status(
91
- context_id,
92
- status="pending_implementation",
93
- path=str(archive_path),
94
- hash=plan_hash,
95
- project_root=project_root
96
- )
97
-
98
- eprint(f"[plan_archive] Archived plan to: {archive_path}")
99
- return str(archive_path), plan_hash
100
-
101
-