Skip to content

Commit 64bfdaa

Browse files
fix(spec): prefer linked agent todo on session sync pull.
Stale per-session todo.txt no longer overwrites merged tasks_md after workspace import-agent-plan. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 6301c98 commit 64bfdaa

2 files changed

Lines changed: 85 additions & 5 deletions

File tree

cecli/spec/agent_todos.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,29 @@ def session_agent_todo_relpath(session: AgentTodoSession) -> str:
479479
return session.coder.local_agent_folder("todo.txt")
480480

481481

482+
def _resolve_agent_todo_pull_relpath(
483+
api: WorkspaceTodos,
484+
store: TodoStore,
485+
session: AgentTodoSession,
486+
) -> str:
487+
"""Prefer the active task's linked agent todo over this session's stale copy."""
488+
session_relpath = session_agent_todo_relpath(session)
489+
active = api.find(store, store.active_id) if store.active_id else None
490+
if active:
491+
linked = parse_agent_todo_link(active.links)
492+
if linked:
493+
linked_path = api.root / linked
494+
if linked_path.is_file():
495+
return linked.replace("\\", "/")
496+
session_path = api.root / session_relpath
497+
if session_path.is_file():
498+
return session_relpath
499+
latest = find_latest_agent_todo_txt(api.root)
500+
if latest:
501+
return str(latest.relative_to(api.root)).replace("\\", "/")
502+
return session_relpath
503+
504+
482505
def try_import_agent_plan_for_workspace(
483506
workspace_dir: str | Path,
484507
*,
@@ -507,12 +530,13 @@ def sync_session_agent_todos(
507530
Returns ``(store, sanitize_warnings)``.
508531
"""
509532
api = WorkspaceTodos(session.coder.root)
510-
relpath = session_agent_todo_relpath(session)
533+
session_relpath = session_agent_todo_relpath(session)
511534
store = api.load()
512535
warnings: list[str] = []
513536

514537
if pull:
515-
path = api.root / relpath
538+
pull_relpath = _resolve_agent_todo_pull_relpath(api, store, session)
539+
path = api.root / pull_relpath
516540
if path.is_file():
517541
rows = parse_agent_todo_txt(path.read_text(encoding="utf-8"))
518542
if rows and sanitize is not None:
@@ -528,14 +552,14 @@ def sync_session_agent_todos(
528552
store,
529553
rows,
530554
target_todo_id=store.active_id,
531-
agent_todo_relpath=relpath,
555+
agent_todo_relpath=pull_relpath,
532556
)
533557

534558
if push_active and store.active_id:
535559
item = api.find(store, store.active_id)
536560
if item:
537-
export_todo_item_to_agent(api.root, relpath, item)
538-
_ensure_agent_link(item, relpath)
561+
export_todo_item_to_agent(api.root, session_relpath, item)
562+
_ensure_agent_link(item, session_relpath)
539563
item.updated_at = _now_iso()
540564

541565
api.save(store)

tests/spec/test_agent_todos.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
rows_from_todo_item,
2323
rows_to_tasks_md,
2424
sanitize_agent_todo_rows,
25+
sync_session_agent_todos,
2526
)
2627
from cecli.spec.todos import ChecklistItem, TodoItem, WorkspaceTodos, _now_iso
2728

@@ -267,3 +268,58 @@ def test_load_agent_todo_rows_from_latest(tmp_path: Path):
267268
assert len(rows) == 1
268269
assert rows[0].current
269270
assert "3.1" in rows[0].text
271+
272+
273+
def test_sync_session_pull_prefers_linked_agent_todo_over_stale_session_copy(tmp_path: Path):
274+
"""Pre-session push must not revert a later workspace import from the linked todo.txt."""
275+
spec_tasks = (
276+
"## Implementation tasks\n\n"
277+
"- [ ] 1. Wire generate-spec API for REQ-001 (depends: none)\n"
278+
"- [ ] 2. Add tests for REQ-002 (depends: 1)\n"
279+
)
280+
api = WorkspaceTodos(tmp_path)
281+
store = api.load()
282+
item = TodoItem(
283+
id="user1",
284+
title="My feature",
285+
tasks_md=spec_tasks,
286+
status="in_progress",
287+
links=[],
288+
checklist=[],
289+
created_at=_now_iso(),
290+
updated_at=_now_iso(),
291+
)
292+
store.todos.append(item)
293+
store.active_id = item.id
294+
api.save(store)
295+
296+
class FakeCoder:
297+
def __init__(self, root: Path):
298+
self.root = root
299+
300+
def local_agent_folder(self, name: str) -> str:
301+
return f".cecli/agents/2026-06-03/session-a/{name}"
302+
303+
class FakeSession:
304+
def __init__(self, root: Path):
305+
self.coder = FakeCoder(root)
306+
307+
session_rel = ".cecli/agents/2026-06-03/session-a/todo.txt"
308+
export_todo_item_to_agent(tmp_path, session_rel, item)
309+
310+
linked_rel = ".cecli/agents/2026-05-27/imported/todo.txt"
311+
linked = tmp_path / linked_rel
312+
linked.parent.mkdir(parents=True)
313+
linked.write_text(
314+
"Done:\n"
315+
"✓ 1. Wire generate-spec API for REQ-001 (depends: none)\n\n"
316+
"Remaining:\n"
317+
"→ 2. Add tests for REQ-002 (depends: 1)\n",
318+
encoding="utf-8",
319+
)
320+
import_agent_plan_for_workspace(tmp_path, agent_todo_relpath=linked_rel)
321+
322+
store2, _ = sync_session_agent_todos(FakeSession(tmp_path), pull=True, push_active=True)
323+
merged = store2.todos[0]
324+
assert "- [x] 1. Wire generate-spec" in merged.tasks_md
325+
assert merged.checklist[0].done is True

0 commit comments

Comments
 (0)