fix: surface phantom backlog blockers as integrity warnings - #60
Merged
Conversation
added 5 commits
August 3, 2026 14:35
…warning, not a silent gate A backlog dependency is recorded two ways: the structured blocked_by edge tasks-axi reads, and the free-text "blocked-by:<id>" token the snapshots parse into unresolved_blocker_ids. When the target exists nowhere - never created, renamed, or mistyped - tasks-axi drops the edge (so `tasks-axi ready` lists the item) while the parsed token dangles forever and fm-bearings-snapshot, fm-fleet-snapshot, and fm-sea-chart render the item as permanently gated, hiding ready work with no warning. fm-backlog-lint already classifies this exact case as a dangling BACKLOG_STALE finding, because it reads the snapshot's free-text-derived blocked_by_ids, not tasks-axi's structured edge - so detection was never the gap; the snapshots were. Add bin/fm-blocker-class-lib.sh as the one owner of "is a blocked-by target real" (present as a structured record in the live backlog or the done archive), spliced into the fleet snapshot, sea chart, and backlog lint so they agree on what dangling means: - fm-fleet-snapshot --json now reads the archive and, per record, records dangling_blocker_ids and drops those ids from unresolved_blocker_ids, so a phantom no longer gates. A target still present as a not-Done record (live or archived) keeps gating exactly as before, preserving the reader-disagreement and stale-edge paths. The per-file --backlog-json reader is unchanged, so the callers that combine live and archive themselves stay correct. - fm-bearings-snapshot adds a loud integrity surface; a phantom-blocked item now appears under Charted Next as ready with no blocker, plus a named integrity row. - fm-sea-chart keeps a phantom-blocked leg takeable and names the stale edge, and recovers a phantom-blocked captain decision as answerable now. - fm-backlog-lint routes its dangling decision through the shared predicate. The /bearings skill renders the integrity array as ready-with-caution under Charted Next. Colocated tests cover the dangling, real, and archived cases in all three renderers; the existing silent-gate assertion is updated to the new contract. No change to data/backlog.md.
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
Fix a status-surface bug where finished/ready backlog work masqueraded as permanently blocked. A backlog dependency is recorded two ways: the structured blocked_by edge tasks-axi reads, and a free-text 'blocked-by:' token the snapshots parse into unresolved_blocker_ids. When the target id exists nowhere (never created, renamed, or mistyped), tasks-axi drops the edge (so 'tasks-axi ready' correctly lists the item) but the parsed token dangles forever and bin/fm-bearings-snapshot.sh, bin/fm-fleet-snapshot.sh, and bin/fm-sea-chart.sh render the item as permanently gated, silently hiding ready work.
Key decision: fm-backlog-lint.sh already detects this exact case as a dangling BACKLOG_STALE finding (it reads the snapshot's free-text-derived blocked_by_ids, not tasks-axi's structured edge), so detection was never the gap - the status views were. I deliberately did NOT write a second detector. Instead I added bin/fm-blocker-class-lib.sh as the single owner of 'is a blocked-by target real' (present as a structured record in the live backlog or the done archive), spliced into the fleet snapshot, sea chart, and lint so they agree on what dangling means (the brief's one-owner requirement).
Deliberate scoping choices a diff reader would not know: (1) I made only fm-fleet-snapshot's --json path archive-aware and left the per-file --backlog-json output UNCHANGED on purpose, because lint and sea-chart combine live+archive themselves and depend on the raw token still appearing in unresolved_blocker_ids - changing per-file output would have broken lint's reader-disagreement class. (2) Only a truly dangling target (absent from BOTH live and archive) is pulled out of gating; a target still present as a not-Done record, live or long-archived, keeps gating exactly as before, preserving the existing reader-disagreement and sea-chart stale-edge paths. (3) captain_actionable is intentionally left computed pre-narrow, so a captain decision blocked only by a phantom stays off the decision board (captain_actionable=false) and is surfaced loudly via the new integrity surface and sea-chart's dangling-edge withheld cause instead - this avoids duplicating the captain-actionability predicate (one-owner rule) and treats a broken dependency edge as something to clear before the decision is offered. I updated an existing fleet-snapshot test that had encoded the old silent-gate behavior (it asserted a missing blocker stays in unresolved_blocker_ids) to the new contract. Also added a coverage-map arm in bin/fm-test-run.sh for the new lib, and extended the /bearings SKILL.md so the human report renders the integrity array as ready-with-caution under Charted Next (keeping the strict four-section contract). No change to data/backlog.md - the live data was already reconciled by hand. Colocated tests added in all three snapshot test suites; 59 changed-selection suites and shellcheck are green.
What Changed
blocked-by:<id>tokens only gate work when the target exists in the live backlog or done archive.Risk Assessment
✅ Low: Captain, the change is narrowly scoped to blocker classification/projection and the follow-up fixes align the main and secondmate current surfaces without introducing a substantiated merge-blocking risk.
Testing
Startup completed with one unrelated watcher-unit advisory left untouched; the changed fleet, bearings, sea-chart, and backlog-lint test suites all passed, and an independent CLI fixture demonstrated the intended end-to-end behavior: phantom blockers no longer silently gate ready work, real blockers still gate, sea-chart/bearings surface the integrity warning, and lint still reports the dangling edge with a fix.
Evidence: tasks-axi ready output
tasks-axi readylistedvoy-phantomas ready while omittingvoy-real-blocked, matching the end-user readiness surface.Evidence: Fleet phantom blocker excerpt
Fleet snapshot excerpt showsvoy-phantomhasunresolved_blocker_ids: []anddangling_blocker_ids: ["ghost-x"];voy-real-blockedstill hasunresolved_blocker_ids: ["real-target"].Evidence: Bearings text output
Bearings text output shows phantom-blocked items withblocked_byas-plusintegrityrows namingghost-x.Evidence: Sea chart summary
Sea chart summary rendersvoy-phantomunderTAKEABLE NOWwith a stale-edge caution, while the phantom-blocked captain decision is shown underWITHHELDwith an explicit dangling-edge cause.Evidence: Backlog lint output
Backlog lint reports the danglingghost-xedges asBACKLOG_STALEwith concrete unblock fixes, preserving detection in the lint surface.Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed (2) ✅
bin/fm-fleet-snapshot.sh:1363- The new archive-aware dangling-blocker normalization only runs for the full--jsonpath;--secondmate-home-summaryreturns before this block, and registered secondmate homes are read through that mode. A phantom blocker in a secondmate backlog will still be emitted asunresolved_blocker_idswith nodangling_blocker_ids, so/bearingscan continue rendering secondmate work as blocked instead of ready-with-integrity-warning. Apply the same live+archive narrowing before building the secondmate summary, or share the normalization helper between both output paths.🔧 Fix: Surface secondmate dangling blockers
1 warning still open:
bin/fm-bearings-snapshot.sh:441- The new integrity projection scans every structured main-backlog record withdangling_blocker_ids, regardless of lifecycle state. Because the snapshot normalizes Done and in-flight records too, a completed or actively worked item that still contains an old typo token will be reported in/bearingsas Charted Next ready work. Restrict the main integrity rows to the same current queued/held backlog surface used for gates, or at least excludestate == "done"and actively working in-flight rows.🔧 Fix: Restrict integrity to current gates
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
bin/fm-session-start.shgit status --shortgit diff --stat c8798d32fca67d07031478f29514f777499035b5..3d1a266d8a87cbc1ea244ddb0f8576a72dd8fb3bgit diff --name-only c8798d32fca67d07031478f29514f777499035b5..3d1a266d8a87cbc1ea244ddb0f8576a72dd8fb3brg -n "blocker|blocked_by|fm-blocker|fleet-snapshot|sea-chart|bearings|BACKLOG_STALE|ready-with-caution|integrity" test tests bin .agents -g '!node_modules'bash tests/fm-fleet-snapshot-view.test.shbash tests/fm-bearings-snapshot.test.shbash tests/fm-sea-chart.test.shbash tests/fm-backlog-lint.test.shCreated an independent fixture under/tmp/no-mistakes-evidence/01KZ40SHJH51TME5R36S25S1NY/phantom-blocker-1785769897/homeand rantasks-axi ready,bin/fm-fleet-snapshot.sh --json,bin/fm-bearings-snapshot.sh --json,bin/fm-bearings-snapshot.sh,bin/fm-sea-chart.sh voy --summary,bin/fm-sea-chart.sh voy --json, andbin/fm-backlog-lint.shagainst it.jq -eassertions over the captured fleet, bearings, and sea-chart JSON evidence.Finalgit status --shortcheck.✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.