feat: sync beads tasks back into plan file#246
Merged
Conversation
Adds a BeadsPlanSyncer that reads .beads/issues.jsonl directly and rewrites each phase's Tasks section with live checkbox-formatted task lines (task ID as backtick link, checked if closed). Two trigger mechanisms: - File watcher in BeadsPlugin: syncs immediately on any bd command (~300ms debounce on issues.jsonl changes) - Safety-net sync in BeadsInstructionGenerator: ensures fresh state on every whats_next call even if the watcher missed an event https://claude.ai/code/session_01WXzZCnA8xDv6ghtSZ9iPrp
- Watcher now starts in BeadsPlugin constructor (not afterStartDevelopment) - sync() derives the plan file path from the current git branch, same logic as ConversationManager — no plan file passed around - Removed activePlanFilePath field - Removed safety-net sync from BeadsInstructionGenerator - syncAll() replaced by sync(projectPath); no directory globbing https://claude.ai/code/session_01WXzZCnA8xDv6ghtSZ9iPrp
BeadsPlugin captures planFilePath from PluginHookContext in every hook that fires (afterStartDevelopment, beforePhaseTransition) and stores it as activePlanFilePath. The watcher uses that value — skipping silently if no conversation has started yet. BeadsPlanSyncer.sync() takes planFilePath explicitly again, removing the GitManager / branch-name derivation entirely. https://claude.ai/code/session_01WXzZCnA8xDv6ghtSZ9iPrp
- Watch .beads/ directory instead of issues.jsonl file so the watcher
works on fresh projects where the file doesn't exist yet
- Move process.once('exit') handler to constructor so it's registered
exactly once regardless of watcher start outcome
- Replace TOCTOU access()+readFile() pattern with direct readFile()
that rethrows non-ENOENT errors to the outer catch for logging
- Apply same ENOENT-specific handling to plan file read
- Inline isCompleted() one-liner into its single call site
https://claude.ai/code/session_01WXzZCnA8xDv6ghtSZ9iPrp
…ries Covers: task checkboxes (open/closed/in_progress), per-phase child isolation, placeholder when no tasks, idempotency, re-sync after status change, and graceful no-ops for missing JSONL, missing plan file, TBD phase IDs, no phase markers, and malformed JSONL lines. https://claude.ai/code/session_01WXzZCnA8xDv6ghtSZ9iPrp
…ections Both the with-tasks and no-tasks cases now include a consistent note making it clear the section is auto-synced and must be managed via the bd CLI, not edited directly in the plan file. https://claude.ai/code/session_01WXzZCnA8xDv6ghtSZ9iPrp
fe56083 to
88aa6b6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
When the beads plugin is active, the plan file becomes unreadable as documentation: every phase's Tasks section is just
*Tasks managed via \bd` CLI*` and the actual tasks live exclusively in beads. This change syncs beads tasks back into the plan file so it remains a useful, up-to-date record of what was done and what is left.Major changes
BeadsPlanSyncer(new)Reads
.beads/issues.jsonldirectly (nobdCLI spawn, no-db mode) and rewrites each phase's### Taskssection with checkbox-formatted lines:Only phases with a resolved (non-TBD)
<!-- beads-phase-id: ... -->marker are touched. Each phase only receives its own direct children (parent-child dependency).BeadsPlugin— file watcherA
fs.watchon the.beads/directory (not the file) is started on plugin construction with a 300 ms debounce. Watching the directory means the watcher works even on fresh projects whereissues.jsonldoesn't exist yet. On any change toissues.jsonl, the current plan file is re-synced.The plan file path is captured from
PluginHookContext.planFilePathinafterStartDevelopmentandbeforePhaseTransition— no GitManager involved. The watcher skips silently if no conversation has started yet.Integration tests
Real-filesystem tests (temp dirs, no mocks) covering: checkbox state mapping, per-phase child isolation, idempotency, status update on re-sync, and graceful no-ops for missing JSONL, missing plan file, TBD phase IDs, no markers, and malformed JSONL lines.
Key decisions
bdCLI.beads/directory, notissues.jsonlfs.watchon a non-existent file throws; directory watch works from project init and filters by filename in the callbackPluginHookContext, notGitManagerprocess.once('exit')in constructor, not in watcher startup