v1.21.0 ·
The second write is the one that fails
.aidlc/roadmap/ is the right place for a roadmap and the wrong place for one to be seen. A team that already triages in GitHub Issues will not keep a second backlog in a directory. And anyone who has not cloned the repo, a stakeholder, a designer, a manager, has no view of what is planned or in flight.
The obvious fix is two-way sync. I refused it. Two writers over one fact means permanent reconciliation, maintained by one person.
What changed
aidlc roadmap sync renders the roadmap as GitHub issues, one way. aidlc roadmap import <issue-url> seeds a single inbox item from an existing issue. Both do nothing until you write a roadmap.sync section into your config, and neither installs a lifecycle action. A project that has not opted in behaves exactly as before. Disk stays the source of truth, the tracker is a rendering, and the rendering can be thrown away and rebuilt.
The decision that carries the design is that sync writes nothing to disk. There is no remote: field and no cache. Creating an issue and then writing a link back is two writes to two systems, and every failure of the second leaves an issue with no link, which the next run turns into a duplicate. On a projection meant to run often, that means duplicates within days. So the link lives on the issue itself, as an HTML-comment marker carrying the item id, found through one shared label. There is no second write to fail, no commit loop for a CI run to break, and anyone can run it any number of times.
I chose a command over a lifecycle action alone, because an action gives you no dry run and no way to debug a failed projection except by finishing another instance. Shipping it as a default action would also have fired it in every project that runs aidlc update. A workflow that syncs on every push to main stays possible later, in about ten lines you write yourself.
Three defaults protect what gets published. The inbox and on-hold stages are not projected, because an untriaged idea and a parked one are not decisions anyone announced. The done stage is never backfilled, so switching it on does not create 43 closed issues nobody will read. And a closed issue is never reopened. Closing is the main triage act of the person this is for, so the sync reports the divergence and leaves it.
Every write needs consent. --dry-run prints the plan and touches nothing. An interactive run prints it and asks. A non-interactive run without --yes prints it and exits non-zero, because a green run that wrote nothing is how an automation comes to believe it is syncing when it never was. The plan and the question both name the resolved repository.
What the reviews caught
Two adversarial reviews returned 53 findings, eight critical, and at least six would have shipped as real defects. gh issue list returns 30 results by default, so "one call" truncated the issue map and every item past the thirtieth became a permanent duplicate. gh issue list --json labels returns objects, not strings, so every label comparison failed and every item planned an update on every run. execFileSync has a one-mebibyte buffer, so fetching issue bodies would have failed in the low hundreds of issues. gh label create exits non-zero on an existing label, and --force alone is not the fix, since gh picks a random colour and would repaint it each time. I measured each against gh 2.98.0 and wrote a test that fails against the original design.
The reviews also made it smaller. A proposed aidlc doctor migration was withdrawn because it duplicated an existing one and would have made doctor depend on the network. --json was dropped. Item prose was removed from issue bodies, which closed marker injection, @mention side effects and GitHub's length limit with one decision.
What I verified, and what I did not
3558 tests pass across all three packages, 207 of them new. A read-only dry run against this repo's own 74 roadmap items plans exactly two creates and 43 not-backfilled skips. I have not applied anything to GitHub. That is an outward write on a public repository, and I left it for the maintainer to authorise.