Skip to content

feat(helm): route Helm sync to per-project GitHub boards - #28

Merged
geojitsu merged 23 commits into
mainfrom
fm/fm-helm-project-routing-build-001
Sep 16, 2026
Merged

geojitsu merged 23 commits into
mainfrom
fm/fm-helm-project-routing-build-001

Conversation

@geojitsu

@geojitsu geojitsu commented Sep 16, 2026 •

Copy link
Copy Markdown
Owner

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

  • Added bin/fm-helm-project-map.sh, a CLI (list, link, move, unlink, sync) that manages data/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 in state/helm-moves.tsv so an interrupted add/create/delete sequence resumes safely.
  • Reworked bin/fm-helm-sync.sh to 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); updated bin/fm-helm-poll.sh to store and compare a signature per board instead of a single global one, and extended bin/fm-helm-lib.sh with shared helpers including fm_helm_ensure_field_options for backfilling a missing Project field option in place.
  • Added 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; updated bin/fm-bootstrap.sh and 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 to docs/architecture.md, docs/configuration.md, docs/scripts.md, docs/examples/helm.json.example) and tests (tests/fm-helm-project-map.test.sh new, tests/fm-helm-poll.test.sh and tests/fm-helm-sync.test.sh extended) 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.

  • Outcome: ⏭️ skipped across 2 runs (1h15m33s)

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.number comes from a cached TSV field, so it is a JSON string, while $board_number is bound via --argjson as a JSON number. jq never treats a string and a number as equal, so $old.number != $board_number is 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 because deleted_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, so deleted_entries skips 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 | tostring like 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
  • 🚨 tests failed with exit code 1
  • bin/fm-test-run.sh --changed --exclude-family real-herdr-gated

🔧 Fix: Skip stale post-move sync cache eviction
1 error still open:

  • 🚨 tests failed with exit code 1
  • bin/fm-test-run.sh --changed --exclude-family real-herdr-gated
⚠️ **Document** - 1 info
  • ℹ️ docs/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 ✅
  • ⚠️ linter found issues (exit code 1)

🔧 Fix: test: replace ambiguous && || chain with explicit if in fm-helm-sync test
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

firstmate-crewmate 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.
@geojitsu geojitsu changed the title feat: route Helm projects to dedicated boards feat(helm): route Helm sync to per-project GitHub boards Sep 16, 2026
@geojitsu
geojitsu merged commit ac7ec8e into main Sep 16, 2026
13 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant