Repository navigation
fix(v5): harden roadmap sync per PR #2729 bot-review findings #2734
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # Reconcile the local genie.db with the canonical board snapshot | ||
| # (.genie/roadmap.json) after a branch checkout swapped the snapshot — | ||
| # post-merge/post-rewrite do not fire here, so without this hook the shared db | ||
| # keeps exposing the previous branch's board until the next sync. Same | ||
| # three-way `task sync` contract: fail-open, refuses on divergence, skipped in | ||
| # linked worktrees and for file (non-branch) checkouts ($3 = 0). | ||
| [ "$3" = "1" ] || exit 0 | ||
| git_dir=$(git rev-parse --path-format=absolute --git-dir 2>/dev/null) | ||
| git_common=$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null) | ||
| if [ "$git_dir" = "$git_common" ] && [ -f src/genie.ts ] && [ -f .genie/roadmap.json ]; then | ||
| bun src/genie.ts task sync || true | ||
| fi |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ | |
|
|
||
| import type { Database } from 'bun:sqlite'; | ||
| import { existsSync, readFileSync, writeFileSync } from 'node:fs'; | ||
| import { resolve } from 'node:path'; | ||
| import type { Command } from 'commander'; | ||
| import { color, formatTimestamp, padRight, truncate } from '../lib/term-format.js'; | ||
| import { livenessBadge } from '../lib/v5/card-render.js'; | ||
|
|
@@ -428,16 +429,18 @@ function handleExport(opts: ExportOptions): void { | |
| run(() => { | ||
| const db = openDb(); | ||
| try { | ||
| // --write publishes the roadmap slice (no hire_roster — machine-local | ||
| // worktree paths); the plain stdout dump stays the complete database. | ||
| const state = opts.write ? roadmapSnapshot(db) : exportState(db); | ||
| // Only the canonical roadmap.json gets the roadmap slice (no hire_roster — | ||
| // machine-local worktree paths). Custom-file writes and the plain stdout | ||
| // dump stay the complete database, so a backup file round-trips lossless. | ||
| const target = opts.write ? (typeof opts.write === 'string' ? resolve(opts.write) : resolveRoadmapPath()) : null; | ||
| const canonical = target === resolveRoadmapPath(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When this command runs inside a linked worktree and the user explicitly passes AGENTS.md reference: AGENTS.md:L35-L35 Useful? React with 👍 / 👎. |
||
| const state = canonical ? roadmapSnapshot(db) : exportState(db); | ||
| const json = `${JSON.stringify(state, null, 2)}\n`; | ||
| if (opts.write) { | ||
| const target = typeof opts.write === 'string' ? opts.write : resolveRoadmapPath(); | ||
| if (target) { | ||
| writeFileSync(target, json); | ||
| // Writing the canonical snapshot declares "this pair is intentional" — | ||
| // it is the keep-the-local-board resolution for a diverged sync. | ||
| if (target === resolveRoadmapPath()) recordSyncBaseline(db); | ||
| if (canonical) recordSyncBaseline(db); | ||
| out(`Wrote board snapshot to ${target}.`); | ||
| return; | ||
| } | ||
|
|
@@ -467,7 +470,9 @@ function handleSync(): void { | |
|
|
||
| function handleImport(file: string | undefined, opts: ImportOptions): void { | ||
| run(() => { | ||
| const source = file ?? resolveRoadmapPath(); | ||
| // Normalize before the canonical comparison: an explicit relative spelling | ||
| // of the roadmap path must behave exactly like omitting it. | ||
| const source = file ? resolve(file) : resolveRoadmapPath(); | ||
| if (!existsSync(source)) { | ||
| fail(`Snapshot not found: ${source}. Generate one with \`genie task export --write\` and commit it.`); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When another process holds the SQLite write lock longer than
busy_timeout,apply()throws a retryableSQLITE_BUSY, but this blanket conversion reportsSnapshotFormatErrorand instructs the user to repair or re-export a snapshot that is not malformed. Preserve transient and operational SQLite errors here, and only translate bind or constraint failures that can actually be attributed to malformed snapshot rows.Useful? React with 👍 / 👎.