fix(#4256): resolve todos from the root via todosDir everywhere (#4479)

* test(#4256): pin todos as root-scoped under workstreams (RED)

* fix(#4256): resolve todos from the root via todosDir everywhere

* chore(#4256): changeset fragment (pr number to backfill)

* chore(#4256): backfill PR number in changeset

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-09-07 08:24:14 -04:00
committed by GitHub
parent 6ebe6372ce
commit 0a0905705a
6 changed files with 410 additions and 17 deletions

View File

@@ -104,6 +104,7 @@ const {
planningPaths,
planningDir,
planningRoot,
todosDir,
listAvailableWorkstreams,
peekActiveWorkstream,
diagnoseUnresolvedActiveWorkstream,
@@ -2356,7 +2357,13 @@ function renderPendingTodoBullet(todo: Record<string, unknown>, projectRoot?: st
function cmdInitTodos(cwd: string, area: string | undefined, raw: boolean): void {
const config = loadConfig(cwd);
const pendingDir = path.join(planningDir(cwd), 'todos', 'pending');
// #4256: todos are root-scoped shared state (migrateToWorkstreams keeps
// them at .planning/todos/ and every workflow writer writes that literal
// path), so this read resolves via todosDir(cwd) — NOT planningDir(cwd),
// which would look in .planning/workstreams/<ws>/todos/ under a workstream
// (a directory nothing creates) and report existing todos as absent.
const todosRoot = todosDir(cwd);
const pendingDir = path.join(todosRoot, 'pending');
let count = 0;
const todos: Record<string, unknown>[] = [];
// #2618: distinct from "genuinely zero pending todos" — false only when
@@ -2412,7 +2419,7 @@ function cmdInitTodos(cwd: string, area: string | undefined, raw: boolean): void
title: titleMatch ? titleMatch[1].trim() : 'Untitled',
area: todoArea,
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
path: toPosixPath(path.join(planningDir(cwd), 'todos', 'pending', file)),
path: toPosixPath(path.join(pendingDir, file)),
...(severityMatch ? { severity: severityMatch[1].trim() } : {}),
...(needs ? { needs } : {}),
});
@@ -2438,12 +2445,15 @@ function cmdInitTodos(cwd: string, area: string | undefined, raw: boolean): void
area_filter: area || null,
// #2376: absolute — see comment on phase_dir in cmdInitExecutePhase.
pending_dir: toPosixPath(path.join(planningDir(cwd), 'todos', 'pending')),
completed_dir: toPosixPath(path.join(planningDir(cwd), 'todos', 'completed')),
// #4256: both dir fields probe the ROOT todos tree via todosDir(cwd).
pending_dir: toPosixPath(pendingDir),
completed_dir: toPosixPath(path.join(todosRoot, 'completed')),
// planning_exists intentionally stays workstream/project-scoped — it
// answers "does the ACTIVE planning dir exist", not a todos question.
planning_exists: fs.existsSync(planningDir(cwd)),
todos_dir_exists: fs.existsSync(path.join(planningDir(cwd), 'todos')),
pending_dir_exists: fs.existsSync(path.join(planningDir(cwd), 'todos', 'pending')),
todos_dir_exists: fs.existsSync(todosRoot),
pending_dir_exists: fs.existsSync(pendingDir),
// #2618: see PENDING_TODO_BULLET_MAX_CHARS comment / design doc. Consumed
// by add-todo.md / check-todos.md's update_state step; omitted entirely