feat(helm): route Helm sync to per-project GitHub boards - #28
Merged
Merged
Conversation
added 23 commits
September 16, 2026 02:09
…ping the card A registered-but-unmapped project's card landed with its own name in the default board's Project bucket, but the sync only checked that the option already existed there - it never created it, so the card silently never landed. Linking a second project onto an already-linked board hit the same gap: ensure_schema refused instead of adding the option (spec decision 6 makes sharing a board across projects normal, not an error). Both paths now call a shared fm_helm_ensure_field_options helper that reads a GitHub Project field's current options and adds whatever is missing, carrying every existing option's id/color/description forward so cards already set to it are undisturbed. The prior ensure_schema mutation was also broken against the real GitHub API (wrong GraphQL variable type, an invalid union selection) and never actually provisioned anything; the new helper is dry-validated against GitHub's live schema.
…and cache merge fixes
…if in fm-helm-sync test
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
Build per-project GitHub Project routing for Helm sync, from the approved design: let a local project optionally route to its own dedicated GitHub Project board instead of always landing on the one shared default board. Captain approved the design in spirit on 2026-09-15 and held the build only until Helm sync itself stabilized; that stabilization landed. Captain also confirmed board routing only needs project identity from data/projects.md, not a project's disk location - drop that concern if it comes up.
Correction 2026-09-15: multiple local projects sharing one board is intentional and explicit (spec decision 6) - a project with no mapping entry stays on the shared default board by default, and only an explicitly-linked project gets a dedicated board. Do not build a forced one-project-per-board constraint; grouping several mapping entries under the same (owner, number) is the normal, expected case.
What Changed
bin/fm-helm-project-map.sh, a CLI (list,link,move,unlink,sync) that managesdata/helm-project-map.json, the durable routing document letting a local project opt into a dedicated GitHub Project board instead of the shared default; moves are recorded instate/helm-moves.tsvso an interrupted add/create/delete sequence resumes safely.bin/fm-helm-sync.shto parse the fleet backlog once, group desired cards by(owner, number), and run the existing sync planner independently per routed board (multiple projects can share one board); updatedbin/fm-helm-poll.shto store and compare a signature per board instead of a single global one, and extendedbin/fm-helm-lib.shwith shared helpers includingfm_helm_ensure_field_optionsfor backfilling a missing Project field option in place.bin/fm-helm-reconcile.sh, a self-throttled watcher that checks mapped-board existence and title drift, resumes confirmed moves, and routes broken mappings through the existing captain-hold mechanism; updatedbin/fm-bootstrap.shand added/updated docs (docs/1.architecture/helm-project-routing.md,docs/2.api/helm-project-map.md,docs/2.api/guides/helm-project-routing.md,docs/4.configuration.md, plus edits todocs/architecture.md,docs/configuration.md,docs/scripts.md,docs/examples/helm.json.example) and tests (tests/fm-helm-project-map.test.shnew,tests/fm-helm-poll.test.shandtests/fm-helm-sync.test.shextended) to cover the new routing behavior.Risk Assessment
✅ Low: The only change since the last checkpoint is a one-line jq type-cast fix matching an established pattern elsewhere in the same file, plus a regression test that genuinely exercises the fixed code path via run_sync and checks real side effects (gh.log), not source text.
Testing
Completed 1 recorded test check.
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-helm-lib.sh:609- Same bug class as the one just fixed at line 788 (this run's own test-round-2 fix):$old.numbercomes from a cached TSV field, so it is a JSON string, while$board_numberis bound via--argjsonas a JSON number. jq never treats a string and a number as equal, so$old.number != $board_numberis true even when the values match numerically. That makes this branch fire for every task with a non-empty cached item, not just ones truly cached on a different board, so the 'retained on another board' path (action: none) always wins over the 'gone from this board' path at line 611 (action: skip) for any post-migration cache row. In the common single-board deletion case this is masked becausedeleted_entries(already fixed at line 788) runs afterward and overwrites the cache correctly, but for a card whose content still exists on the board with the task marker removed (item id still in item_set, sodeleted_entriesskips it at line 789), this branch wrongly re-publishes the stale cache identity instead of clearing it via the skip/tombstone path. Cast both sides with| tostringlike line 788 already does.🔧 Fix: fix: cast cached board number to string before jq comparison
1 warning still open:
bin/fm-helm-lib.sh:609- This round's fix (casting $old.number and $board_number to string before comparing) is correct and mirrors the existing pattern at line 788 and old_cache (line 554), fixing a real jq string-vs-number inequality bug. However, no regression test was added for it, unlike prior fix rounds on this same file (e.g. the move-source-resolution and cacheless-merge fixes both got matching tests in tests/fm-helm-project-map.test.sh). Without a test seeding a cached row where $old.number is a numeric-looking string equal to $board_number, a future refactor could silently reintroduce the type-mismatch false-positive on the 'retained on another board' branch.🔧 Fix: test: add regression test for board-number type-mismatch fix
✅ Re-checked - no issues remain.
⏭️ **Test** - skipped
bin/fm-test-run.sh --changed --exclude-family real-herdr-gated🔧 Fix: Skip stale post-move sync cache eviction
1 error still open:
bin/fm-test-run.sh --changed --exclude-family real-herdr-gateddocs/4.configuration.md:1- docs/4.configuration.md, docs/1.architecture/helm-project-routing.md, docs/2.api/helm-project-map.md, and docs/2.api/guides/helm-project-routing.md are a second, parallel doc surface (numbered Docus-style tree) that fully restates facts already owned by docs/configuration.md's 'Helm board sync' section, docs/architecture.md's 'Helm board synchronization' section, and docs/scripts.md's script table, instead of pointing to them. This was introduced by this branch's own feature commit and already accepted/classified in earlier review rounds (documentation-audiences.json entries were added specifically to satisfy the audience checker, not to question the duplication). I left it as-is: consolidating or removing it now would be a broad documentation-architecture migration outside this narrow staleness pass. Recommend a follow-up to fold the numbered-tree pages into short pointers at the existing owner docs so config fields, board routing state, and CLI usage each stay a single authoritative copy.🔧 **Lint** - 1 issue found → auto-fixed ✅
🔧 Fix: test: replace ambiguous && || chain with explicit if in fm-helm-sync test
✅ Re-checked - no issues remain.
✅ **Push** - passed
✅ No issues found.