code-context-control 2.28.0__py3-none-any.whl
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.
- cli/__init__.py +1 -0
- cli/_hook_utils.py +99 -0
- cli/c3.py +6152 -0
- cli/commands/__init__.py +1 -0
- cli/commands/common.py +312 -0
- cli/commands/parser.py +286 -0
- cli/docs.html +3178 -0
- cli/edits.html +878 -0
- cli/hook_auto_snapshot.py +142 -0
- cli/hook_c3_signal.py +61 -0
- cli/hook_c3read.py +116 -0
- cli/hook_edit_ledger.py +213 -0
- cli/hook_edit_unlock.py +170 -0
- cli/hook_filter.py +130 -0
- cli/hook_ghost_files.py +238 -0
- cli/hook_pretool_enforce.py +334 -0
- cli/hook_read.py +200 -0
- cli/hook_session_stats.py +62 -0
- cli/hook_terse_advisor.py +190 -0
- cli/hub.html +3764 -0
- cli/hub_server.py +1619 -0
- cli/mcp_proxy.py +428 -0
- cli/mcp_server.py +660 -0
- cli/server.py +2985 -0
- cli/tools/__init__.py +4 -0
- cli/tools/_helpers.py +65 -0
- cli/tools/agent.py +1165 -0
- cli/tools/compress.py +215 -0
- cli/tools/delegate.py +1184 -0
- cli/tools/edit.py +313 -0
- cli/tools/edits.py +118 -0
- cli/tools/filter.py +285 -0
- cli/tools/impact.py +163 -0
- cli/tools/memory.py +469 -0
- cli/tools/read.py +224 -0
- cli/tools/search.py +337 -0
- cli/tools/session.py +95 -0
- cli/tools/shell.py +193 -0
- cli/tools/status.py +306 -0
- cli/tools/validate.py +310 -0
- cli/ui/api.js +36 -0
- cli/ui/app.js +207 -0
- cli/ui/components/chat.js +758 -0
- cli/ui/components/dashboard.js +689 -0
- cli/ui/components/edits.js +220 -0
- cli/ui/components/instructions.js +481 -0
- cli/ui/components/memory.js +626 -0
- cli/ui/components/sessions.js +606 -0
- cli/ui/components/settings.js +1404 -0
- cli/ui/components/sidebar.js +156 -0
- cli/ui/icons.js +51 -0
- cli/ui/shared.js +119 -0
- cli/ui/theme.js +22 -0
- cli/ui.html +168 -0
- cli/ui_legacy.html +6797 -0
- cli/ui_nano.html +503 -0
- code_context_control-2.28.0.dist-info/METADATA +248 -0
- code_context_control-2.28.0.dist-info/RECORD +150 -0
- code_context_control-2.28.0.dist-info/WHEEL +5 -0
- code_context_control-2.28.0.dist-info/entry_points.txt +4 -0
- code_context_control-2.28.0.dist-info/licenses/LICENSE +201 -0
- code_context_control-2.28.0.dist-info/top_level.txt +5 -0
- core/__init__.py +75 -0
- core/config.py +269 -0
- core/ide.py +188 -0
- oracle/__init__.py +1 -0
- oracle/config.py +75 -0
- oracle/oracle.html +3900 -0
- oracle/oracle_server.py +663 -0
- oracle/services/__init__.py +1 -0
- oracle/services/c3_bridge.py +210 -0
- oracle/services/chat_engine.py +1103 -0
- oracle/services/chat_store.py +155 -0
- oracle/services/cross_memory.py +154 -0
- oracle/services/federated_graph.py +463 -0
- oracle/services/health_checker.py +117 -0
- oracle/services/insight_engine.py +307 -0
- oracle/services/memory_reader.py +106 -0
- oracle/services/memory_writer.py +182 -0
- oracle/services/ollama_bridge.py +332 -0
- oracle/services/project_scanner.py +87 -0
- oracle/services/review_agent.py +206 -0
- services/__init__.py +1 -0
- services/activity_log.py +93 -0
- services/agent_base.py +124 -0
- services/agents.py +1529 -0
- services/auto_memory.py +407 -0
- services/bench/__init__.py +6 -0
- services/bench/external/__init__.py +29 -0
- services/bench/external/aider_polyglot.py +405 -0
- services/bench/external/swe_bench.py +485 -0
- services/benchmark_dashboard.py +596 -0
- services/claude_md.py +785 -0
- services/compressor.py +592 -0
- services/context_snapshot.py +356 -0
- services/conversation_store.py +870 -0
- services/doc_index.py +537 -0
- services/e2e_benchmark.py +2884 -0
- services/e2e_evaluator.py +396 -0
- services/e2e_tasks.py +743 -0
- services/edit_ledger.py +459 -0
- services/embedding_index.py +341 -0
- services/error_reporting.py +123 -0
- services/file_memory.py +734 -0
- services/hub_service.py +585 -0
- services/indexer.py +712 -0
- services/memory.py +318 -0
- services/memory_consolidator.py +538 -0
- services/memory_graph.py +382 -0
- services/memory_grounder.py +304 -0
- services/memory_scorer.py +246 -0
- services/metrics.py +86 -0
- services/notifications.py +209 -0
- services/ollama_client.py +201 -0
- services/output_filter.py +488 -0
- services/parser.py +1238 -0
- services/project_manager.py +579 -0
- services/protocol.py +306 -0
- services/proxy_state.py +152 -0
- services/retrieval_broker.py +129 -0
- services/router.py +414 -0
- services/runtime.py +326 -0
- services/session_benchmark.py +1945 -0
- services/session_manager.py +1026 -0
- services/session_preloader.py +251 -0
- services/text_index.py +90 -0
- services/tool_classifier.py +176 -0
- services/transcript_index.py +340 -0
- services/validation_cache.py +155 -0
- services/vector_store.py +299 -0
- services/version_tracker.py +271 -0
- services/watcher.py +192 -0
- tui/__init__.py +0 -0
- tui/backend.py +59 -0
- tui/main.py +145 -0
- tui/screens/__init__.py +1 -0
- tui/screens/benchmark_view.py +109 -0
- tui/screens/claudemd_view.py +46 -0
- tui/screens/compress_view.py +52 -0
- tui/screens/index_view.py +74 -0
- tui/screens/init_view.py +82 -0
- tui/screens/mcp_view.py +73 -0
- tui/screens/optimize_view.py +41 -0
- tui/screens/pipe_view.py +46 -0
- tui/screens/projects_view.py +355 -0
- tui/screens/search_view.py +55 -0
- tui/screens/session_view.py +143 -0
- tui/screens/stats.py +158 -0
- tui/screens/ui_view.py +54 -0
- tui/theme.tcss +335 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context Snapshot — Capture/restore working context across /clear boundaries.
|
|
3
|
+
|
|
4
|
+
Saves session state (decisions, files, notes, facts) before /clear,
|
|
5
|
+
provides compact briefings to reinstate context after /clear.
|
|
6
|
+
"""
|
|
7
|
+
import json
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from core import count_tokens
|
|
12
|
+
|
|
13
|
+
# Max characters per file structural map stored in snapshot
|
|
14
|
+
_FILE_MAP_MAX_CHARS = 600
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ContextSnapshot:
|
|
18
|
+
"""Snapshot/restore for clear-and-recall workflow."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, project_path: str, data_dir: str = ".c3/snapshots"):
|
|
21
|
+
self.project_path = Path(project_path)
|
|
22
|
+
self.data_dir = self.project_path / data_dir
|
|
23
|
+
self.data_dir.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
|
|
25
|
+
def capture(self, session_mgr, memory_store,
|
|
26
|
+
task_description: str = "",
|
|
27
|
+
working_files: list = None,
|
|
28
|
+
custom_notes: str = "",
|
|
29
|
+
compressor=None) -> dict:
|
|
30
|
+
"""Capture current working context to a snapshot file.
|
|
31
|
+
|
|
32
|
+
Returns {snapshot_id, path, token_count}.
|
|
33
|
+
"""
|
|
34
|
+
session = session_mgr.current_session or {}
|
|
35
|
+
session_id = session.get("id", "")
|
|
36
|
+
|
|
37
|
+
# Collect decisions from current session
|
|
38
|
+
decisions = session.get("decisions", [])
|
|
39
|
+
|
|
40
|
+
# Collect files touched
|
|
41
|
+
files_touched = session.get("files_touched", [])
|
|
42
|
+
|
|
43
|
+
# Collect context notes
|
|
44
|
+
context_notes = session.get("context_notes", [])
|
|
45
|
+
|
|
46
|
+
# Session-scoped facts (added this session)
|
|
47
|
+
session_facts = [
|
|
48
|
+
f for f in memory_store.facts
|
|
49
|
+
if f.get("source_session") == session_id and session_id
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
# Also recall top relevant facts from full memory store (cross-session)
|
|
53
|
+
relevant_facts = []
|
|
54
|
+
if task_description:
|
|
55
|
+
try:
|
|
56
|
+
recalled = memory_store.recall(task_description, top_k=8)
|
|
57
|
+
session_fact_texts = {f["fact"] for f in session_facts}
|
|
58
|
+
relevant_facts = [
|
|
59
|
+
{"fact": r["fact"], "category": r.get("category", "general")}
|
|
60
|
+
for r in recalled
|
|
61
|
+
if r["fact"] not in session_fact_texts
|
|
62
|
+
][:6]
|
|
63
|
+
except Exception:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
# Auto-populate working_files from files_touched when not explicitly provided
|
|
67
|
+
if not working_files and files_touched:
|
|
68
|
+
working_files = [ft["file"] for ft in files_touched[:8]]
|
|
69
|
+
|
|
70
|
+
# Capture structural maps of working files for immediate context on restore
|
|
71
|
+
file_maps = {}
|
|
72
|
+
if compressor and working_files:
|
|
73
|
+
for fp in working_files[:5]:
|
|
74
|
+
try:
|
|
75
|
+
abs_fp = str(self.project_path / fp) if not Path(fp).is_absolute() else fp
|
|
76
|
+
result = compressor.compress_file(abs_fp, mode="structure")
|
|
77
|
+
if result and not result.get("error"):
|
|
78
|
+
file_maps[fp] = result.get("compressed", "")[:_FILE_MAP_MAX_CHARS]
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
# Extract plan decisions separately so they are surfaced prominently on restore
|
|
83
|
+
plans = [
|
|
84
|
+
d for d in decisions
|
|
85
|
+
if d.get("decision", "").startswith("PLAN:")
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
# Context budget snapshot
|
|
89
|
+
budget = session.get("context_budget", {})
|
|
90
|
+
|
|
91
|
+
snapshot = {
|
|
92
|
+
"schema_version": 3,
|
|
93
|
+
"snapshot_id": datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S"),
|
|
94
|
+
"created": datetime.now(timezone.utc).isoformat(),
|
|
95
|
+
"session_id": session_id,
|
|
96
|
+
"task_description": task_description,
|
|
97
|
+
"working_files": working_files or [],
|
|
98
|
+
"custom_notes": custom_notes,
|
|
99
|
+
"decisions": decisions,
|
|
100
|
+
"plans": plans,
|
|
101
|
+
"files_touched": files_touched,
|
|
102
|
+
"context_notes": context_notes,
|
|
103
|
+
"session_facts": [
|
|
104
|
+
{"fact": f["fact"], "category": f["category"]}
|
|
105
|
+
for f in session_facts
|
|
106
|
+
],
|
|
107
|
+
"relevant_facts": relevant_facts,
|
|
108
|
+
"file_maps": file_maps,
|
|
109
|
+
"context_budget": {
|
|
110
|
+
"response_tokens": budget.get("response_tokens", 0),
|
|
111
|
+
"call_count": budget.get("call_count", 0),
|
|
112
|
+
},
|
|
113
|
+
"state": {
|
|
114
|
+
"task_description": task_description,
|
|
115
|
+
"working_files": working_files or [],
|
|
116
|
+
"decisions": decisions,
|
|
117
|
+
"files_touched": files_touched,
|
|
118
|
+
"context_notes": context_notes,
|
|
119
|
+
"session_facts": [
|
|
120
|
+
{"fact": f["fact"], "category": f["category"], "id": f.get("id", "")}
|
|
121
|
+
for f in session_facts
|
|
122
|
+
],
|
|
123
|
+
"context_budget": {
|
|
124
|
+
"response_tokens": budget.get("response_tokens", 0),
|
|
125
|
+
"call_count": budget.get("call_count", 0),
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
path = self.data_dir / f"snap_{snapshot['snapshot_id']}.json"
|
|
131
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
132
|
+
json.dump(snapshot, f, indent=2)
|
|
133
|
+
|
|
134
|
+
token_count = count_tokens(json.dumps(snapshot))
|
|
135
|
+
return {
|
|
136
|
+
"snapshot_id": snapshot["snapshot_id"],
|
|
137
|
+
"path": str(path),
|
|
138
|
+
"token_count": token_count,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
def restore(self, snapshot_id: str = "latest", memory_store=None, level: int = 0) -> dict:
|
|
142
|
+
"""Restore a snapshot as a full markdown briefing.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
snapshot_id: Snapshot ID or 'latest'.
|
|
146
|
+
memory_store: Optional MemoryStore for live recall enrichment.
|
|
147
|
+
level: 0=full briefing, 1=compact briefing (for auto-restore notifications).
|
|
148
|
+
|
|
149
|
+
Returns {snapshot_id, briefing, tokens}.
|
|
150
|
+
"""
|
|
151
|
+
snap = self._load_snapshot(snapshot_id)
|
|
152
|
+
if "error" in snap:
|
|
153
|
+
return snap
|
|
154
|
+
|
|
155
|
+
# Enrich with live memory recall so cross-session facts are surfaced immediately
|
|
156
|
+
if memory_store and snap.get("task_description"):
|
|
157
|
+
try:
|
|
158
|
+
recalled = memory_store.recall(snap["task_description"], top_k=6)
|
|
159
|
+
existing_texts = (
|
|
160
|
+
{f["fact"] for f in snap.get("session_facts", [])}
|
|
161
|
+
| {f["fact"] for f in snap.get("relevant_facts", [])}
|
|
162
|
+
)
|
|
163
|
+
snap["_live_recall"] = [
|
|
164
|
+
{"fact": r["fact"], "category": r.get("category", "general")}
|
|
165
|
+
for r in recalled
|
|
166
|
+
if r["fact"] not in existing_texts
|
|
167
|
+
][:5]
|
|
168
|
+
except Exception:
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
sid = snap["snapshot_id"]
|
|
172
|
+
briefing = self._compact_briefing(snap) if level > 0 else self._full_briefing(snap)
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
"snapshot_id": sid,
|
|
176
|
+
"briefing": briefing,
|
|
177
|
+
"tokens": count_tokens(briefing),
|
|
178
|
+
"state": snap.get("state", {}),
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
def list_snapshots(self, n: int = 10) -> list:
|
|
182
|
+
"""List recent snapshots."""
|
|
183
|
+
files = sorted(self.data_dir.glob("snap_*.json"), reverse=True)[:n]
|
|
184
|
+
results = []
|
|
185
|
+
for sf in files:
|
|
186
|
+
try:
|
|
187
|
+
with open(sf, encoding='utf-8') as f:
|
|
188
|
+
snap = json.load(f)
|
|
189
|
+
results.append({
|
|
190
|
+
"id": snap["snapshot_id"],
|
|
191
|
+
"created": snap.get("created", ""),
|
|
192
|
+
"task_description": snap.get("task_description", "")[:80],
|
|
193
|
+
"decisions_count": len(snap.get("decisions", [])),
|
|
194
|
+
"files_count": len(snap.get("files_touched", [])),
|
|
195
|
+
})
|
|
196
|
+
except Exception:
|
|
197
|
+
continue
|
|
198
|
+
return results
|
|
199
|
+
|
|
200
|
+
def restore_state(self, snapshot_id: str = "latest") -> dict:
|
|
201
|
+
snap = self._load_snapshot(snapshot_id)
|
|
202
|
+
if "error" in snap:
|
|
203
|
+
return snap
|
|
204
|
+
return {
|
|
205
|
+
"snapshot_id": snap["snapshot_id"],
|
|
206
|
+
"state": snap.get("state", {}),
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
def search(self, query: str, top_k: int = 5) -> list:
|
|
210
|
+
query_l = (query or "").lower().strip()
|
|
211
|
+
if not query_l:
|
|
212
|
+
return []
|
|
213
|
+
results = []
|
|
214
|
+
for item in self.list_snapshots(50):
|
|
215
|
+
snap = self._load_snapshot(item["id"])
|
|
216
|
+
haystack = " ".join([
|
|
217
|
+
snap.get("task_description", ""),
|
|
218
|
+
snap.get("custom_notes", ""),
|
|
219
|
+
" ".join(note for note in snap.get("context_notes", [])),
|
|
220
|
+
" ".join(d.get("decision", "") for d in snap.get("decisions", [])),
|
|
221
|
+
" ".join(f.get("fact", "") for f in snap.get("session_facts", [])),
|
|
222
|
+
]).lower()
|
|
223
|
+
if query_l in haystack:
|
|
224
|
+
results.append({
|
|
225
|
+
"snapshot_id": snap["snapshot_id"],
|
|
226
|
+
"task_description": snap.get("task_description", ""),
|
|
227
|
+
"created": snap.get("created", ""),
|
|
228
|
+
"score": 1.0 if query_l in snap.get("task_description", "").lower() else 0.6,
|
|
229
|
+
})
|
|
230
|
+
if len(results) >= top_k:
|
|
231
|
+
break
|
|
232
|
+
return results
|
|
233
|
+
|
|
234
|
+
def _load_snapshot(self, snapshot_id: str) -> dict:
|
|
235
|
+
"""Load a snapshot by ID or 'latest'."""
|
|
236
|
+
if snapshot_id == "latest":
|
|
237
|
+
files = sorted(self.data_dir.glob("snap_*.json"), reverse=True)
|
|
238
|
+
if not files:
|
|
239
|
+
return {"error": "No snapshots found"}
|
|
240
|
+
path = files[0]
|
|
241
|
+
else:
|
|
242
|
+
path = self.data_dir / f"snap_{snapshot_id}.json"
|
|
243
|
+
|
|
244
|
+
if not path.exists():
|
|
245
|
+
return {"error": f"Snapshot not found: {snapshot_id}"}
|
|
246
|
+
|
|
247
|
+
with open(path, encoding='utf-8') as f:
|
|
248
|
+
return json.load(f)
|
|
249
|
+
|
|
250
|
+
def _full_briefing(self, snap: dict) -> str:
|
|
251
|
+
"""Level 0: Full briefing with all details."""
|
|
252
|
+
parts = [f"# Context Restore: {snap.get('task_description', 'N/A')}"]
|
|
253
|
+
parts.append(f"Snapshot: {snap['snapshot_id']} | Session: {snap.get('session_id', '?')}")
|
|
254
|
+
|
|
255
|
+
if snap.get("custom_notes"):
|
|
256
|
+
parts.append(f"\n## Notes\n{snap['custom_notes']}")
|
|
257
|
+
|
|
258
|
+
# Plans first — highest priority context
|
|
259
|
+
plans = snap.get("plans", [])
|
|
260
|
+
if plans:
|
|
261
|
+
parts.append("\n## Plans")
|
|
262
|
+
for d in plans:
|
|
263
|
+
text = d["decision"].removeprefix("PLAN:").strip()
|
|
264
|
+
if d.get("reasoning"):
|
|
265
|
+
text += f" — {d['reasoning']}"
|
|
266
|
+
parts.append(f"- {text}")
|
|
267
|
+
|
|
268
|
+
# Live-recalled facts (cross-session, surfaced at restore time)
|
|
269
|
+
live_recall = snap.get("_live_recall", [])
|
|
270
|
+
if live_recall:
|
|
271
|
+
parts.append("\n## Relevant Memory (live recall)")
|
|
272
|
+
for fact in live_recall:
|
|
273
|
+
parts.append(f"- [{fact['category']}] {fact['fact']}")
|
|
274
|
+
|
|
275
|
+
# Relevant facts captured at snapshot time (cross-session)
|
|
276
|
+
relevant_facts = snap.get("relevant_facts", [])
|
|
277
|
+
if relevant_facts:
|
|
278
|
+
parts.append("\n## Relevant Memory (at snapshot)")
|
|
279
|
+
for fact in relevant_facts:
|
|
280
|
+
parts.append(f"- [{fact['category']}] {fact['fact']}")
|
|
281
|
+
|
|
282
|
+
decisions = snap.get("decisions", [])
|
|
283
|
+
non_plan_decisions = [d for d in decisions if not d.get("decision", "").startswith("PLAN:")]
|
|
284
|
+
if non_plan_decisions:
|
|
285
|
+
parts.append("\n## Decisions")
|
|
286
|
+
for d in non_plan_decisions:
|
|
287
|
+
line = f"- {d['decision']}"
|
|
288
|
+
if d.get("reasoning"):
|
|
289
|
+
line += f" — {d['reasoning']}"
|
|
290
|
+
parts.append(line)
|
|
291
|
+
|
|
292
|
+
files = snap.get("files_touched", [])
|
|
293
|
+
if files:
|
|
294
|
+
parts.append("\n## Files Touched")
|
|
295
|
+
for ft in files:
|
|
296
|
+
summary = f" — {ft['summary']}" if ft.get("summary") else ""
|
|
297
|
+
parts.append(f"- {ft.get('type', '?')}: {ft['file']}{summary}")
|
|
298
|
+
|
|
299
|
+
# File structural maps — skip re-reading after restore
|
|
300
|
+
file_maps = snap.get("file_maps", {})
|
|
301
|
+
if file_maps:
|
|
302
|
+
parts.append("\n## File Structures (skip re-reading)")
|
|
303
|
+
for fp, fmap in file_maps.items():
|
|
304
|
+
parts.append(f"\n### {fp}\n```\n{fmap}\n```")
|
|
305
|
+
|
|
306
|
+
ctx_notes = snap.get("context_notes", [])
|
|
307
|
+
if ctx_notes:
|
|
308
|
+
parts.append("\n## Context Notes")
|
|
309
|
+
for note in ctx_notes:
|
|
310
|
+
parts.append(f"- {note}")
|
|
311
|
+
|
|
312
|
+
facts = snap.get("session_facts", [])
|
|
313
|
+
if facts:
|
|
314
|
+
parts.append("\n## Session Facts")
|
|
315
|
+
for fact in facts:
|
|
316
|
+
parts.append(f"- [{fact['category']}] {fact['fact']}")
|
|
317
|
+
|
|
318
|
+
working = snap.get("working_files", [])
|
|
319
|
+
if working:
|
|
320
|
+
parts.append(f"\n## Working Files\n{', '.join(working)}")
|
|
321
|
+
|
|
322
|
+
budget = snap.get("context_budget", {})
|
|
323
|
+
if budget.get("response_tokens"):
|
|
324
|
+
parts.append(f"\n## Budget\n{budget['response_tokens']}tok / {budget['call_count']} calls")
|
|
325
|
+
|
|
326
|
+
return "\n".join(parts)
|
|
327
|
+
|
|
328
|
+
def _compact_briefing(self, snap: dict) -> str:
|
|
329
|
+
"""Level 1: Compact briefing — top decisions + file list."""
|
|
330
|
+
parts = [f"[restore:{snap['snapshot_id']}] {snap.get('task_description', '')}"]
|
|
331
|
+
|
|
332
|
+
plans = snap.get("plans", [])
|
|
333
|
+
if plans:
|
|
334
|
+
for d in plans:
|
|
335
|
+
text = d["decision"].removeprefix("PLAN:").strip()
|
|
336
|
+
parts.append(f"[plan] {text[:80]}")
|
|
337
|
+
|
|
338
|
+
decisions = snap.get("decisions", [])
|
|
339
|
+
non_plan = [d for d in decisions if not d.get("decision", "").startswith("PLAN:")]
|
|
340
|
+
if non_plan:
|
|
341
|
+
top = non_plan[-3:] # Most recent 3
|
|
342
|
+
for d in top:
|
|
343
|
+
parts.append(f"- {d['decision'][:80]}")
|
|
344
|
+
|
|
345
|
+
files = snap.get("files_touched", [])
|
|
346
|
+
if files:
|
|
347
|
+
file_list = ", ".join(ft["file"] for ft in files[:10])
|
|
348
|
+
parts.append(f"files: {file_list}")
|
|
349
|
+
|
|
350
|
+
facts = snap.get("session_facts", [])
|
|
351
|
+
relevant = snap.get("relevant_facts", [])
|
|
352
|
+
total_facts = len(facts) + len(relevant)
|
|
353
|
+
if total_facts:
|
|
354
|
+
parts.append(f"facts: {total_facts} available")
|
|
355
|
+
|
|
356
|
+
return "\n".join(parts)
|