aiwcli 0.9.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 (204) hide show
  1. package/README.md +1248 -0
  2. package/bin/dev.cmd +3 -0
  3. package/bin/dev.js +16 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +19 -0
  6. package/dist/commands/branch.d.ts +45 -0
  7. package/dist/commands/branch.js +488 -0
  8. package/dist/commands/clean.d.ts +34 -0
  9. package/dist/commands/clean.js +186 -0
  10. package/dist/commands/clear.d.ts +51 -0
  11. package/dist/commands/clear.js +835 -0
  12. package/dist/commands/init/index.d.ts +107 -0
  13. package/dist/commands/init/index.js +565 -0
  14. package/dist/commands/launch.d.ts +21 -0
  15. package/dist/commands/launch.js +108 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/lib/base-command.d.ts +114 -0
  19. package/dist/lib/base-command.js +153 -0
  20. package/dist/lib/bmad-installer.d.ts +38 -0
  21. package/dist/lib/bmad-installer.js +145 -0
  22. package/dist/lib/claude-settings-types.d.ts +102 -0
  23. package/dist/lib/claude-settings-types.js +5 -0
  24. package/dist/lib/config.d.ts +25 -0
  25. package/dist/lib/config.js +46 -0
  26. package/dist/lib/debug.d.ts +39 -0
  27. package/dist/lib/debug.js +74 -0
  28. package/dist/lib/env-compat.d.ts +26 -0
  29. package/dist/lib/env-compat.js +35 -0
  30. package/dist/lib/errors.d.ts +126 -0
  31. package/dist/lib/errors.js +145 -0
  32. package/dist/lib/generic-merge.d.ts +74 -0
  33. package/dist/lib/generic-merge.js +105 -0
  34. package/dist/lib/git/branch.d.ts +67 -0
  35. package/dist/lib/git/branch.js +155 -0
  36. package/dist/lib/git/index.d.ts +11 -0
  37. package/dist/lib/git/index.js +13 -0
  38. package/dist/lib/git/safety-checks.d.ts +44 -0
  39. package/dist/lib/git/safety-checks.js +102 -0
  40. package/dist/lib/git/types.d.ts +31 -0
  41. package/dist/lib/git/types.js +6 -0
  42. package/dist/lib/git/worktree.d.ts +67 -0
  43. package/dist/lib/git/worktree.js +220 -0
  44. package/dist/lib/gitignore-manager.d.ts +10 -0
  45. package/dist/lib/gitignore-manager.js +60 -0
  46. package/dist/lib/hooks-merger.d.ts +28 -0
  47. package/dist/lib/hooks-merger.js +94 -0
  48. package/dist/lib/ide-path-resolver.d.ts +102 -0
  49. package/dist/lib/ide-path-resolver.js +129 -0
  50. package/dist/lib/index.d.ts +13 -0
  51. package/dist/lib/index.js +22 -0
  52. package/dist/lib/output.d.ts +51 -0
  53. package/dist/lib/output.js +76 -0
  54. package/dist/lib/paths.d.ts +66 -0
  55. package/dist/lib/paths.js +136 -0
  56. package/dist/lib/quiet.d.ts +12 -0
  57. package/dist/lib/quiet.js +17 -0
  58. package/dist/lib/settings-hierarchy.d.ts +42 -0
  59. package/dist/lib/settings-hierarchy.js +105 -0
  60. package/dist/lib/spawn.d.ts +105 -0
  61. package/dist/lib/spawn.js +157 -0
  62. package/dist/lib/spinner.d.ts +19 -0
  63. package/dist/lib/spinner.js +34 -0
  64. package/dist/lib/stdin.d.ts +48 -0
  65. package/dist/lib/stdin.js +60 -0
  66. package/dist/lib/template-installer.d.ts +92 -0
  67. package/dist/lib/template-installer.js +375 -0
  68. package/dist/lib/template-linter.d.ts +49 -0
  69. package/dist/lib/template-linter.js +173 -0
  70. package/dist/lib/template-merger.d.ts +47 -0
  71. package/dist/lib/template-merger.js +173 -0
  72. package/dist/lib/template-resolver.d.ts +20 -0
  73. package/dist/lib/template-resolver.js +60 -0
  74. package/dist/lib/terminal.d.ts +102 -0
  75. package/dist/lib/terminal.js +245 -0
  76. package/dist/lib/tty-detection.d.ts +62 -0
  77. package/dist/lib/tty-detection.js +83 -0
  78. package/dist/lib/user-utils.d.ts +5 -0
  79. package/dist/lib/user-utils.js +23 -0
  80. package/dist/lib/version.d.ts +99 -0
  81. package/dist/lib/version.js +144 -0
  82. package/dist/lib/watch-templates.d.ts +6 -0
  83. package/dist/lib/watch-templates.js +73 -0
  84. package/dist/lib/windsurf-hooks-hierarchy.d.ts +30 -0
  85. package/dist/lib/windsurf-hooks-hierarchy.js +66 -0
  86. package/dist/lib/windsurf-hooks-merger.d.ts +26 -0
  87. package/dist/lib/windsurf-hooks-merger.js +53 -0
  88. package/dist/lib/windsurf-hooks-types.d.ts +33 -0
  89. package/dist/lib/windsurf-hooks-types.js +5 -0
  90. package/dist/templates/CLAUDE.md +174 -0
  91. package/dist/templates/_shared/.claude/commands/handoff.md +14 -0
  92. package/dist/templates/_shared/.claude/settings.json +61 -0
  93. package/dist/templates/_shared/.codex/workflows/handoff.md +14 -0
  94. package/dist/templates/_shared/.windsurf/workflows/handoff.md +14 -0
  95. package/dist/templates/_shared/hooks/__init__.py +16 -0
  96. package/dist/templates/_shared/hooks/archive_plan.py +270 -0
  97. package/dist/templates/_shared/hooks/context_enforcer.py +621 -0
  98. package/dist/templates/_shared/hooks/context_monitor.py +322 -0
  99. package/dist/templates/_shared/hooks/file-suggestion.py +188 -0
  100. package/dist/templates/_shared/hooks/task_create_capture.py +194 -0
  101. package/dist/templates/_shared/hooks/task_update_capture.py +254 -0
  102. package/dist/templates/_shared/hooks/user_prompt_submit.py +157 -0
  103. package/dist/templates/_shared/lib/__init__.py +1 -0
  104. package/dist/templates/_shared/lib/base/__init__.py +49 -0
  105. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  106. package/dist/templates/_shared/lib/base/atomic_write.py +180 -0
  107. package/dist/templates/_shared/lib/base/constants.py +299 -0
  108. package/dist/templates/_shared/lib/base/inference.py +189 -0
  109. package/dist/templates/_shared/lib/base/utils.py +216 -0
  110. package/dist/templates/_shared/lib/context/__init__.py +119 -0
  111. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  112. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  113. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  114. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  115. package/dist/templates/_shared/lib/context/cache.py +446 -0
  116. package/dist/templates/_shared/lib/context/context_manager.py +1171 -0
  117. package/dist/templates/_shared/lib/context/discovery.py +486 -0
  118. package/dist/templates/_shared/lib/context/event_log.py +308 -0
  119. package/dist/templates/_shared/lib/context/plan_archive.py +247 -0
  120. package/dist/templates/_shared/lib/context/task_sync.py +367 -0
  121. package/dist/templates/_shared/lib/handoff/__init__.py +22 -0
  122. package/dist/templates/_shared/lib/handoff/document_generator.py +307 -0
  123. package/dist/templates/_shared/lib/templates/README.md +215 -0
  124. package/dist/templates/_shared/lib/templates/__init__.py +40 -0
  125. package/dist/templates/_shared/lib/templates/formatters.py +147 -0
  126. package/dist/templates/_shared/lib/templates/plan_context.py +119 -0
  127. package/dist/templates/_shared/scripts/save_handoff.py +99 -0
  128. package/dist/templates/_shared/workflows/handoff.md +212 -0
  129. package/dist/templates/cc-native/.claude/agents/cc-native/ACCESSIBILITY-TESTER.md +80 -0
  130. package/dist/templates/cc-native/.claude/agents/cc-native/ARCHITECT-REVIEWER.md +75 -0
  131. package/dist/templates/cc-native/.claude/agents/cc-native/ASSUMPTION-CHAIN-TRACER.md +239 -0
  132. package/dist/templates/cc-native/.claude/agents/cc-native/CLARITY-AUDITOR.md +109 -0
  133. package/dist/templates/cc-native/.claude/agents/cc-native/CODE-REVIEWER.md +71 -0
  134. package/dist/templates/cc-native/.claude/agents/cc-native/COMPLETENESS-CHECKER.md +104 -0
  135. package/dist/templates/cc-native/.claude/agents/cc-native/CONTEXT-EXTRACTOR.md +93 -0
  136. package/dist/templates/cc-native/.claude/agents/cc-native/DEVILS-ADVOCATE.md +223 -0
  137. package/dist/templates/cc-native/.claude/agents/cc-native/DOCUMENTATION-REVIEWER.md +73 -0
  138. package/dist/templates/cc-native/.claude/agents/cc-native/FEASIBILITY-ANALYST.md +93 -0
  139. package/dist/templates/cc-native/.claude/agents/cc-native/FRESH-PERSPECTIVE.md +103 -0
  140. package/dist/templates/cc-native/.claude/agents/cc-native/HANDOFF-READINESS.md +145 -0
  141. package/dist/templates/cc-native/.claude/agents/cc-native/HIDDEN-COMPLEXITY-DETECTOR.md +248 -0
  142. package/dist/templates/cc-native/.claude/agents/cc-native/INCENTIVE-MAPPER.md +235 -0
  143. package/dist/templates/cc-native/.claude/agents/cc-native/PENETRATION-TESTER.md +80 -0
  144. package/dist/templates/cc-native/.claude/agents/cc-native/PERFORMANCE-ENGINEER.md +76 -0
  145. package/dist/templates/cc-native/.claude/agents/cc-native/PLAN-ORCHESTRATOR.md +141 -0
  146. package/dist/templates/cc-native/.claude/agents/cc-native/PRECEDENT-FINDER.md +240 -0
  147. package/dist/templates/cc-native/.claude/agents/cc-native/REVERSIBILITY-ANALYST.md +211 -0
  148. package/dist/templates/cc-native/.claude/agents/cc-native/RISK-ASSESSOR.md +101 -0
  149. package/dist/templates/cc-native/.claude/agents/cc-native/SECOND-ORDER-ANALYST.md +197 -0
  150. package/dist/templates/cc-native/.claude/agents/cc-native/SIMPLICITY-GUARDIAN.md +97 -0
  151. package/dist/templates/cc-native/.claude/agents/cc-native/SKEPTIC.md +349 -0
  152. package/dist/templates/cc-native/.claude/agents/cc-native/STAKEHOLDER-ADVOCATE.md +106 -0
  153. package/dist/templates/cc-native/.claude/agents/cc-native/TRADE-OFF-ILLUMINATOR.md +205 -0
  154. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +8 -0
  155. package/dist/templates/cc-native/.claude/commands/cc-native/specdev.md +10 -0
  156. package/dist/templates/cc-native/.claude/settings.json +119 -0
  157. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fix.md +8 -0
  158. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +8 -0
  159. package/dist/templates/cc-native/.windsurf/workflows/cc-native/implement.md +8 -0
  160. package/dist/templates/cc-native/.windsurf/workflows/cc-native/research.md +8 -0
  161. package/dist/templates/cc-native/CC-NATIVE-README.md +192 -0
  162. package/dist/templates/cc-native/MIGRATION.md +86 -0
  163. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +331 -0
  164. package/dist/templates/cc-native/_cc-native/docs/PERMISSION_REQUEST_VERIFICATION.md +147 -0
  165. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  166. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  167. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-agent-review.cpython-313.pyc +0 -0
  168. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  169. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/test_permission_request.cpython-313.pyc +0 -0
  170. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +150 -0
  171. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +746 -0
  172. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +339 -0
  173. package/dist/templates/cc-native/_cc-native/lib/__init__.py +57 -0
  174. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  175. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  176. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  177. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  178. package/dist/templates/cc-native/_cc-native/lib/async_archive.py +68 -0
  179. package/dist/templates/cc-native/_cc-native/lib/atomic_write.py +98 -0
  180. package/dist/templates/cc-native/_cc-native/lib/constants.py +45 -0
  181. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +273 -0
  182. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +28 -0
  183. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  184. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  185. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  186. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  187. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  188. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +164 -0
  189. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +89 -0
  190. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +119 -0
  191. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +103 -0
  192. package/dist/templates/cc-native/_cc-native/lib/state.py +251 -0
  193. package/dist/templates/cc-native/_cc-native/lib/utils.py +830 -0
  194. package/dist/templates/cc-native/_cc-native/plan-review.config.json +76 -0
  195. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  196. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +151 -0
  197. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +134 -0
  198. package/dist/templates/cc-native/_cc-native/workflows/specdev.md +9 -0
  199. package/dist/types/exit-codes.d.ts +11 -0
  200. package/dist/types/exit-codes.js +10 -0
  201. package/dist/types/index.d.ts +5 -0
  202. package/dist/types/index.js +7 -0
  203. package/oclif.manifest.json +405 -0
  204. package/package.json +109 -0
@@ -0,0 +1,119 @@
1
+ """Context management for AIW CLI templates."""
2
+ from .context_manager import (
3
+ Context,
4
+ InFlightState,
5
+ get_all_contexts,
6
+ get_context,
7
+ create_context,
8
+ update_context,
9
+ complete_context,
10
+ reopen_context,
11
+ archive_context,
12
+ update_plan_status,
13
+ get_context_with_pending_plan,
14
+ get_context_with_in_flight_work,
15
+ update_handoff_status,
16
+ clear_handoff_status,
17
+ get_context_with_handoff_pending,
18
+ )
19
+ from .event_log import (
20
+ Task,
21
+ ContextState,
22
+ append_event,
23
+ read_events,
24
+ get_current_state,
25
+ are_all_tasks_completed,
26
+ get_pending_tasks,
27
+ )
28
+ from .cache import (
29
+ rebuild_index_from_folders,
30
+ rebuild_archive_index,
31
+ rebuild_context_from_events,
32
+ rebuild_all_caches,
33
+ verify_cache_integrity,
34
+ )
35
+ from .discovery import (
36
+ discover_contexts_for_session,
37
+ get_in_flight_context,
38
+ format_context_list,
39
+ format_pending_plan_continuation,
40
+ format_handoff_continuation,
41
+ format_implementation_continuation,
42
+ format_context_picker_prompt,
43
+ format_ready_for_new_work,
44
+ )
45
+ from .task_sync import (
46
+ generate_hydration_instructions,
47
+ generate_task_summary,
48
+ record_session_start,
49
+ record_task_created,
50
+ record_task_started,
51
+ record_task_completed,
52
+ record_task_blocked,
53
+ generate_next_task_id,
54
+ )
55
+ from .plan_archive import (
56
+ archive_plan_to_context,
57
+ get_active_context_for_plan,
58
+ create_context_from_plan,
59
+ mark_plan_implementation_started,
60
+ mark_plan_completed,
61
+ )
62
+
63
+ __all__ = [
64
+ # Data Classes
65
+ "Context",
66
+ "InFlightState",
67
+ "Task",
68
+ "ContextState",
69
+ # Context Manager
70
+ "get_all_contexts",
71
+ "get_context",
72
+ "create_context",
73
+ "update_context",
74
+ "complete_context",
75
+ "reopen_context",
76
+ "archive_context",
77
+ "update_plan_status",
78
+ "get_context_with_pending_plan",
79
+ "get_context_with_in_flight_work",
80
+ "update_handoff_status",
81
+ "clear_handoff_status",
82
+ "get_context_with_handoff_pending",
83
+ # Event Log
84
+ "append_event",
85
+ "read_events",
86
+ "get_current_state",
87
+ "are_all_tasks_completed",
88
+ "get_pending_tasks",
89
+ # Cache
90
+ "rebuild_index_from_folders",
91
+ "rebuild_archive_index",
92
+ "rebuild_context_from_events",
93
+ "rebuild_all_caches",
94
+ "verify_cache_integrity",
95
+ # Discovery
96
+ "discover_contexts_for_session",
97
+ "get_in_flight_context",
98
+ "format_context_list",
99
+ "format_pending_plan_continuation",
100
+ "format_handoff_continuation",
101
+ "format_implementation_continuation",
102
+ "format_context_picker_prompt",
103
+ "format_ready_for_new_work",
104
+ # Task Sync
105
+ "generate_hydration_instructions",
106
+ "generate_task_summary",
107
+ "record_session_start",
108
+ "record_task_created",
109
+ "record_task_started",
110
+ "record_task_completed",
111
+ "record_task_blocked",
112
+ "generate_next_task_id",
113
+ # Plan Archive
114
+ "archive_plan_to_context",
115
+ "get_active_context_for_plan",
116
+ "create_context_from_plan",
117
+ "mark_plan_implementation_started",
118
+ "mark_plan_completed",
119
+ ]
@@ -0,0 +1,446 @@
1
+ """Cache rebuild utilities for context management.
2
+
3
+ These functions allow recovery from corrupted cache files
4
+ by rebuilding from the source of truth (events.jsonl).
5
+
6
+ Data hierarchy:
7
+ events.jsonl (source of truth)
8
+ → context.json (L1 cache) - can be rebuilt
9
+ → index.json (L2 cache) - can be rebuilt
10
+ """
11
+ import json
12
+ from pathlib import Path
13
+ from typing import Any, Dict, Optional
14
+
15
+ from ..base.atomic_write import atomic_write
16
+ from ..base.constants import (
17
+ get_contexts_dir,
18
+ get_context_file_path,
19
+ get_events_file_path,
20
+ get_index_path,
21
+ get_archive_dir,
22
+ get_archive_index_path,
23
+ ARCHIVE_DIR,
24
+ )
25
+ from ..base.utils import eprint, now_iso
26
+ from .event_log import read_events
27
+
28
+
29
+ def rebuild_context_from_events(context_dir: Path, project_root: Path = None) -> Optional['Context']:
30
+ """
31
+ Rebuild context.json by replaying events.jsonl.
32
+
33
+ This is the recovery mechanism when context.json is
34
+ corrupted or out of sync.
35
+
36
+ Args:
37
+ context_dir: Path to context directory
38
+ project_root: Project root directory (if known, avoids fragile path calculation)
39
+
40
+ Returns:
41
+ Rebuilt Context object, or None if events file doesn't exist
42
+ """
43
+ # Import here to avoid circular dependency
44
+ from .context_manager import Context, InFlightState
45
+
46
+ events_path = context_dir / "events.jsonl"
47
+ if not events_path.exists():
48
+ return None
49
+
50
+ context_id = context_dir.name
51
+
52
+ # Calculate project_root if not provided
53
+ # Structure: project_root/_output/contexts/{id}
54
+ if project_root is None:
55
+ # Traverse up: context_dir -> contexts -> _output -> project_root
56
+ project_root = context_dir.parent.parent.parent
57
+
58
+ events = read_events(context_id, project_root)
59
+
60
+ if not events:
61
+ return None
62
+
63
+ # Initialize context with defaults
64
+ context = Context(
65
+ id=context_id,
66
+ status="active",
67
+ folder=str(context_dir),
68
+ in_flight=InFlightState()
69
+ )
70
+
71
+ # Replay events to derive current state
72
+ for event in events:
73
+ event_type = event.get("event")
74
+ timestamp = event.get("timestamp")
75
+
76
+ # Update last_active for any event
77
+ context.last_active = timestamp
78
+
79
+ if event_type == "context_created":
80
+ context.summary = event.get("summary", "")
81
+ context.method = event.get("method")
82
+ context.tags = event.get("tags", [])
83
+ context.created_at = timestamp
84
+
85
+ elif event_type == "context_completed":
86
+ context.status = "completed"
87
+
88
+ elif event_type == "context_reopened":
89
+ context.status = "active"
90
+
91
+ elif event_type == "metadata_updated":
92
+ if "summary" in event:
93
+ context.summary = event["summary"]
94
+ if "tags" in event:
95
+ context.tags = event["tags"]
96
+ if "method" in event:
97
+ context.method = event["method"]
98
+
99
+ elif event_type == "planning_started":
100
+ context.in_flight.mode = "planning"
101
+
102
+ elif event_type == "plan_created":
103
+ context.in_flight.mode = "pending_implementation"
104
+ context.in_flight.artifact_path = event.get("path")
105
+ context.in_flight.artifact_hash = event.get("hash")
106
+ context.in_flight.started_at = timestamp
107
+
108
+ elif event_type == "plan_implementation_started":
109
+ context.in_flight.mode = "implementing"
110
+
111
+ elif event_type == "plan_completed":
112
+ context.in_flight.mode = "none"
113
+ context.in_flight.artifact_path = None
114
+ context.in_flight.artifact_hash = None
115
+ context.in_flight.started_at = None
116
+
117
+ elif event_type == "handoff_created":
118
+ context.in_flight.mode = "handoff_pending"
119
+ context.in_flight.handoff_path = event.get("path")
120
+
121
+ elif event_type == "handoff_cleared":
122
+ # Restore to "implementing" if artifact exists, otherwise "none"
123
+ restored_mode = event.get("restored_mode", "none")
124
+ context.in_flight.mode = restored_mode
125
+ context.in_flight.handoff_path = None
126
+
127
+ return context
128
+
129
+
130
+ def rebuild_index_from_folders(project_root: Path = None) -> Dict[str, Any]:
131
+ """
132
+ Rebuild index.json by scanning context folders.
133
+
134
+ This is the recovery mechanism when index.json is
135
+ corrupted or out of sync.
136
+
137
+ Note: Skips the archive/ folder - archived contexts are not included
138
+ in the main index.
139
+
140
+ Args:
141
+ project_root: Project root directory
142
+
143
+ Returns:
144
+ Rebuilt index dictionary
145
+ """
146
+ index = {
147
+ "version": "2.0",
148
+ "updated_at": now_iso(),
149
+ "contexts": {}
150
+ }
151
+
152
+ contexts_dir = get_contexts_dir(project_root)
153
+ if not contexts_dir.exists():
154
+ return index
155
+
156
+ for ctx_dir in contexts_dir.iterdir():
157
+ if not ctx_dir.is_dir():
158
+ continue
159
+
160
+ # Skip archive folder - archived contexts have their own index
161
+ if ctx_dir.name == ARCHIVE_DIR:
162
+ continue
163
+
164
+ # Try to read context.json first
165
+ context_file = ctx_dir / "context.json"
166
+ if context_file.exists():
167
+ try:
168
+ ctx_data = json.loads(context_file.read_text(encoding='utf-8'))
169
+ in_flight = ctx_data.get("in_flight", {})
170
+ index["contexts"][ctx_data["id"]] = {
171
+ "id": ctx_data["id"],
172
+ "status": ctx_data.get("status", "active"),
173
+ "method": ctx_data.get("method"),
174
+ "summary": ctx_data.get("summary", ""),
175
+ "created_at": ctx_data.get("created_at"),
176
+ "last_active": ctx_data.get("last_active"),
177
+ "folder": str(ctx_dir),
178
+ "in_flight_mode": in_flight.get("mode", "none")
179
+ }
180
+ continue
181
+ except Exception as e:
182
+ eprint(f"[cache] Failed to read {context_file}, rebuilding from events: {e}")
183
+
184
+ # Fallback: rebuild from events
185
+ context = rebuild_context_from_events(ctx_dir)
186
+ if context:
187
+ index["contexts"][context.id] = context.to_index_entry()
188
+
189
+ return index
190
+
191
+
192
+ def rebuild_archive_index(project_root: Path = None) -> Dict[str, Any]:
193
+ """
194
+ Rebuild archive/index.json by scanning archive folder.
195
+
196
+ This is the recovery mechanism when archive index is
197
+ corrupted or out of sync.
198
+
199
+ Args:
200
+ project_root: Project root directory
201
+
202
+ Returns:
203
+ Rebuilt archive index dictionary
204
+ """
205
+ archive_index = {
206
+ "version": "2.0",
207
+ "updated_at": now_iso(),
208
+ "contexts": {}
209
+ }
210
+
211
+ archive_dir = get_archive_dir(project_root)
212
+ if not archive_dir.exists():
213
+ return archive_index
214
+
215
+ for ctx_dir in archive_dir.iterdir():
216
+ if not ctx_dir.is_dir():
217
+ continue
218
+
219
+ # Skip index.json file itself
220
+ if ctx_dir.name == "index.json":
221
+ continue
222
+
223
+ # Try to read context.json first
224
+ context_file = ctx_dir / "context.json"
225
+ if context_file.exists():
226
+ try:
227
+ ctx_data = json.loads(context_file.read_text(encoding='utf-8'))
228
+ in_flight = ctx_data.get("in_flight", {})
229
+ archive_index["contexts"][ctx_data["id"]] = {
230
+ "id": ctx_data["id"],
231
+ "status": ctx_data.get("status", "completed"),
232
+ "method": ctx_data.get("method"),
233
+ "summary": ctx_data.get("summary", ""),
234
+ "created_at": ctx_data.get("created_at"),
235
+ "last_active": ctx_data.get("last_active"),
236
+ "folder": str(ctx_dir),
237
+ "in_flight_mode": in_flight.get("mode", "none")
238
+ }
239
+ continue
240
+ except Exception as e:
241
+ eprint(f"[cache] Failed to read {context_file}, rebuilding from events: {e}")
242
+
243
+ # Fallback: rebuild from events
244
+ context = rebuild_context_from_events(ctx_dir)
245
+ if context:
246
+ archive_index["contexts"][context.id] = context.to_index_entry()
247
+
248
+ return archive_index
249
+
250
+
251
+ def rebuild_all_caches(project_root: Path = None) -> bool:
252
+ """
253
+ Rebuild all cache files from events.jsonl files.
254
+
255
+ Useful for recovery after corruption or version migration.
256
+ Rebuilds both active context caches and archive index.
257
+
258
+ Args:
259
+ project_root: Project root directory
260
+
261
+ Returns:
262
+ True if all caches were rebuilt successfully
263
+ """
264
+ success = True
265
+ contexts_dir = get_contexts_dir(project_root)
266
+
267
+ if not contexts_dir.exists():
268
+ eprint("[cache] No contexts directory found, nothing to rebuild")
269
+ return True
270
+
271
+ # Rebuild each active context's cache (skip archive folder)
272
+ for ctx_dir in contexts_dir.iterdir():
273
+ if not ctx_dir.is_dir():
274
+ continue
275
+
276
+ # Skip archive folder - handled separately
277
+ if ctx_dir.name == ARCHIVE_DIR:
278
+ continue
279
+
280
+ events_path = ctx_dir / "events.jsonl"
281
+ if not events_path.exists():
282
+ continue
283
+
284
+ eprint(f"[cache] Rebuilding context: {ctx_dir.name}")
285
+ context = rebuild_context_from_events(ctx_dir)
286
+
287
+ if context:
288
+ context_file = ctx_dir / "context.json"
289
+ content = json.dumps(context.to_dict(), indent=2, ensure_ascii=False)
290
+ ok, error = atomic_write(context_file, content)
291
+ if not ok:
292
+ eprint(f"[cache] Failed to write {context_file}: {error}")
293
+ success = False
294
+ else:
295
+ eprint(f"[cache] Failed to rebuild context: {ctx_dir.name}")
296
+ success = False
297
+
298
+ # Rebuild archived context caches
299
+ archive_dir = get_archive_dir(project_root)
300
+ if archive_dir.exists():
301
+ for ctx_dir in archive_dir.iterdir():
302
+ if not ctx_dir.is_dir():
303
+ continue
304
+
305
+ events_path = ctx_dir / "events.jsonl"
306
+ if not events_path.exists():
307
+ continue
308
+
309
+ eprint(f"[cache] Rebuilding archived context: {ctx_dir.name}")
310
+ context = rebuild_context_from_events(ctx_dir)
311
+
312
+ if context:
313
+ context_file = ctx_dir / "context.json"
314
+ content = json.dumps(context.to_dict(), indent=2, ensure_ascii=False)
315
+ ok, error = atomic_write(context_file, content)
316
+ if not ok:
317
+ eprint(f"[cache] Failed to write {context_file}: {error}")
318
+ success = False
319
+ else:
320
+ eprint(f"[cache] Failed to rebuild archived context: {ctx_dir.name}")
321
+ success = False
322
+
323
+ # Rebuild global index
324
+ eprint("[cache] Rebuilding global index")
325
+ index = rebuild_index_from_folders(project_root)
326
+ index_path = get_index_path(project_root)
327
+
328
+ content = json.dumps(index, indent=2, ensure_ascii=False)
329
+ ok, error = atomic_write(index_path, content)
330
+ if not ok:
331
+ eprint(f"[cache] Failed to write index: {error}")
332
+ success = False
333
+
334
+ # Rebuild archive index
335
+ eprint("[cache] Rebuilding archive index")
336
+ archive_index = rebuild_archive_index(project_root)
337
+ archive_index_path = get_archive_index_path(project_root)
338
+
339
+ if archive_index["contexts"]: # Only write if there are archived contexts
340
+ content = json.dumps(archive_index, indent=2, ensure_ascii=False)
341
+ ok, error = atomic_write(archive_index_path, content)
342
+ if not ok:
343
+ eprint(f"[cache] Failed to write archive index: {error}")
344
+ success = False
345
+
346
+ total_contexts = len(index['contexts']) + len(archive_index['contexts'])
347
+ eprint(f"[cache] Rebuild complete. {len(index['contexts'])} active, {len(archive_index['contexts'])} archived contexts indexed.")
348
+ return success
349
+
350
+
351
+ def verify_cache_integrity(project_root: Path = None) -> Dict[str, Any]:
352
+ """
353
+ Verify integrity of cache files against events.
354
+
355
+ Returns a report of any discrepancies found.
356
+
357
+ Args:
358
+ project_root: Project root directory
359
+
360
+ Returns:
361
+ Dictionary with verification results
362
+ """
363
+ report = {
364
+ "ok": True,
365
+ "issues": [],
366
+ "contexts_checked": 0,
367
+ "contexts_with_issues": 0
368
+ }
369
+
370
+ contexts_dir = get_contexts_dir(project_root)
371
+ if not contexts_dir.exists():
372
+ return report
373
+
374
+ for ctx_dir in contexts_dir.iterdir():
375
+ if not ctx_dir.is_dir():
376
+ continue
377
+
378
+ # Skip archive folder - archived contexts verified separately
379
+ if ctx_dir.name == ARCHIVE_DIR:
380
+ continue
381
+
382
+ report["contexts_checked"] += 1
383
+ context_id = ctx_dir.name
384
+
385
+ # Check events.jsonl exists
386
+ events_path = ctx_dir / "events.jsonl"
387
+ if not events_path.exists():
388
+ report["issues"].append({
389
+ "context": context_id,
390
+ "issue": "Missing events.jsonl (source of truth)",
391
+ "severity": "critical"
392
+ })
393
+ report["contexts_with_issues"] += 1
394
+ report["ok"] = False
395
+ continue
396
+
397
+ # Check context.json exists
398
+ context_file = ctx_dir / "context.json"
399
+ if not context_file.exists():
400
+ report["issues"].append({
401
+ "context": context_id,
402
+ "issue": "Missing context.json (cache)",
403
+ "severity": "warning",
404
+ "action": "Run rebuild_all_caches()"
405
+ })
406
+ report["contexts_with_issues"] += 1
407
+ continue
408
+
409
+ # Compare cache with events-derived state
410
+ try:
411
+ cached = json.loads(context_file.read_text(encoding='utf-8'))
412
+ derived = rebuild_context_from_events(ctx_dir)
413
+
414
+ if derived:
415
+ has_issue = False
416
+ if cached.get("status") != derived.status:
417
+ report["issues"].append({
418
+ "context": context_id,
419
+ "issue": f"Status mismatch: cache={cached.get('status')}, events={derived.status}",
420
+ "severity": "warning"
421
+ })
422
+ has_issue = True
423
+
424
+ cached_mode = cached.get("in_flight", {}).get("mode", "none")
425
+ if cached_mode != derived.in_flight.mode:
426
+ report["issues"].append({
427
+ "context": context_id,
428
+ "issue": f"in_flight.mode mismatch: cache={cached_mode}, events={derived.in_flight.mode}",
429
+ "severity": "warning"
430
+ })
431
+ has_issue = True
432
+
433
+ if has_issue:
434
+ report["contexts_with_issues"] += 1
435
+ report["ok"] = False
436
+
437
+ except Exception as e:
438
+ report["issues"].append({
439
+ "context": context_id,
440
+ "issue": f"Verification error: {e}",
441
+ "severity": "error"
442
+ })
443
+ report["contexts_with_issues"] += 1
444
+ report["ok"] = False
445
+
446
+ return report