Conductor: write proposed TODOs to TODOS.md instead of prose decision brief; block /ship on unresolved proposals
- Dominant language
- TypeScript
- Stars
- 133k
- Forks
- 19.9k
- Avg merge
- 18h 46m
- Merged PRs (30d)
- 26
Description
## Problem
When a TODO decision arises inside a Conductor session (e.g. during `/plan-ceo-review`), gstack currently:
1. Tries to call `AskUserQuestion` — immediately blocked by the `question-preference-hook`'s `isConductor()` guard.
2. Falls back to a **prose decision brief** in the chat: D label, ELI10, Recommendation, one paragraph per option, \"reply with a letter, then STOP.\"
The prose fallback is still **synchronous**. The user must be present and reply in-chat before the skill continues. It also means multiple pending TODO decisions arrive as separate, sequential prose blocks — no way to batch-review them.
This is a subset of the broader AUQ-in-Conductor problem (#2035, #2006), but it's worth treating separately because the *goal* here is different: TODO proposals don't need an immediate answer. They can be deferred without blocking the session.
## Proposed alternative: write proposals to TODOS.md
Instead of rendering a prose brief and stopping, when gstack is in a Conductor session and needs a TODO decision, it should:
1. **Write the proposed item directly to TODOS.md** using a new `proposed` status (or `**Status:** pending-decision`).
2. Include the full decision context inline — What, Why, Pros, Cons, Options (A/Add · B/Skip · C/Build now), Effort, Priority — so the user has everything they need to decide without digging into chat history.
3. **Continue the session** rather than blocking. The user reviews TODOS.md at their own pace.
Conductor has an inline diff comment feature: the user can leave a comment on any line of a modified file directly in the Conductor UI. This makes TODOS.md a natural async communication channel — the user comments on a proposed item with their decision (A/B/C), and the agent picks it up next session.
## The comment-reading gap
Conductor's current MCP surface exposes `DiffComment` for **writing** inline comments, but no tool for **reading** them back. gstack cannot programmatically check whether the user has already responded to a proposed TODO via a Conductor diff comment.
Two paths forward:
**Option A — Conductor adds a read tool.**
A `GetDiffComments` MCP tool (or similar) that returns comments on a given file would let gstack read responses and resolve proposals automatically. This would be a Conductor-side feature request.
**Option B — User updates the TODOS.md status field directly.**
Require the user to change `**Status:** proposed` to `accepted` / `skipped` / `build-now` in TODOS.md after reviewing. This keeps everything in the file, no MCP round-trip needed. gstack can read the file to check resolution.
Option B requires less infrastructure and keeps TODOS.md as the single source of truth. Option A is more ergonomic for users who prefer the Conductor comment UI.
## `/ship` guard
Regardless of which option is chosen, `/ship` Step 14 should block (or warn loudly) if TODOS.md contains any items in `proposed` / `pending-decision` status. The intent is to prevent shipping past unreviewed TODO proposals.
If Option A lands, the guard could additionally check whether any Conductor diff comments on TODOS.md are unresolved.
## Opt-in / user preference
This could be gated behind a preference (`gstack-config set todos-decision-mode file-async`) so users who prefer the current prose-brief flow aren't affected. Conductor-session detection could auto-enable it, with an explicit `always-prose` escape hatch.
## Related issues
- **#2035** — root cause: `question-preference-hook` emits `permissionDecision: "defer"` on no-op paths, breaking AUQ in recent Claude Code builds (same reporter, macOS/VSCode). Proposed fix: change `defer()` to emit no `permissionDecision`.
- **#2006** — same root cause, independent report. Includes the isolation table and minimal fix.
- **#1208** — AUQ panel layout breaks with long decision-brief bodies and multi-tab batches. PR #1241 proposes moving decision briefs outside the tool call into prose. The file-write approach here is a further step in the same direction: move the decision context into a persistent artifact rather than ephemeral chat.
- **#1124** — design question: why does TODOS.md live in git while `~/.gstack/projects/` docs stay local? No maintainer response yet. The async-proposal flow here reinforces that TODOS.md is the right place for shared, persistent decisions.
- **#2015** — Hermes host: use native `clarify` tool instead of AUQ. Demonstrates gstack already routes question UI per host. Conductor could be treated similarly: same host-specific routing, different target (file write instead of tool call).
## Acceptance criteria
- [ ] When `isConductor()` and a TODO decision is triggered, gstack writes a `proposed` item to TODOS.md instead of rendering a prose brief
- [ ] The written item includes all decision context (What/Why/Pros/Cons/Options/Effort/Priority) following `TODOS-format.md`
- [ ] `/ship` Step 14 detects `proposed`-status items and blocks or warns before pushing
- [ ] Behavior is opt-in or Conductor-auto with a prose escape hatch
- [ ] Existing prose-fallback behavior is preserved for non-Conductor sessions
Contributor guide
Research direction
Start by tracing the TODO decision flow around isConductor() and the question-preference-hook, then inspect TODOS.md, TODOS-format.md, and /ship Step 14. Confirm how proposed status and decision context should be represented, how Conductor responses are handled, and how existing prose fallback works outside Conductor. Done means the acceptance criteria pass without changing non-Conductor behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli, tooling
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100