Repository navigation
fix(mcp): db-backed branch resolution + reopen db created mid-session (2 PR-review P2s) #2512
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 | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -611,6 +611,24 @@ export function getWishGroups(db: Database, wish: string): WishGroupRow[] { | |||||||||||||||||||||||||||||||||||||||||||||||
| return rows.map(mapWishGroup); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||
| * Distinct known wish slugs (from tasks + wish_groups), longest first. Used to | ||||||||||||||||||||||||||||||||||||||||||||||||
| * disambiguate a `wish/<slug>-<group>` branch when the slug itself contains | ||||||||||||||||||||||||||||||||||||||||||||||||
| * hyphens (`genie-mcp` vs a `genie` wish with an `mcp` group). | ||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||
| export function listWishSlugs(db: Database): string[] { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const rows = db | ||||||||||||||||||||||||||||||||||||||||||||||||
| .query( | ||||||||||||||||||||||||||||||||||||||||||||||||
| // UNION already de-duplicates; order longest-first for prefix disambiguation. | ||||||||||||||||||||||||||||||||||||||||||||||||
| `SELECT wish FROM ( | ||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT wish FROM tasks WHERE wish IS NOT NULL | ||||||||||||||||||||||||||||||||||||||||||||||||
| UNION SELECT wish FROM wish_groups WHERE wish IS NOT NULL | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) ORDER BY LENGTH(wish) DESC`, | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||
| .all() as Array<{ wish: string }>; | ||||||||||||||||||||||||||||||||||||||||||||||||
| return rows.map((r) => r.wish); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+619
to
+630
Contributor
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. The Additionally, since this is a read-only MCP tool designed to degrade gracefully and never throw, wrapping the query in a
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| function getWishGroup(db: Database, wish: string, name: string): WishGroupRow | null { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const row = db.query('SELECT * FROM wish_groups WHERE wish = ? AND name = ?').get(wish, name) as | ||||||||||||||||||||||||||||||||||||||||||||||||
| | Parameters<typeof mapWishGroup>[0] | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -74,8 +74,10 @@ export async function runMcpServer(): Promise<void> { | |
| // Lazy: the read-only bun:sqlite open + tools load here, not at genie startup. | ||
| const { MCP_TOOLS, openReadonlyDb } = await import('../lib/v5/mcp-tools.js'); | ||
| const cwd = process.cwd(); | ||
| const db = openReadonlyDb(cwd); | ||
| const ctx: ToolContext = { db, cwd }; | ||
| // Single source of truth for the handle: the per-call reopen below writes back | ||
| // to ctx.db, and close() reads ctx.db — so a mid-session reopen is always the | ||
| // one that gets closed (no stale/leaked handle). | ||
| const ctx: ToolContext = { db: openReadonlyDb(cwd), cwd }; | ||
|
|
||
| const toolByName = new Map(MCP_TOOLS.map((t) => [t.name, t] as const)); | ||
|
|
||
|
|
@@ -91,6 +93,10 @@ export async function runMcpServer(): Promise<void> { | |
| }); | ||
| return; | ||
| } | ||
| // The db may have been absent when the server started (null handle → empty | ||
| // board). Re-attempt the read-only open per call so a db created mid-session | ||
| // (e.g. a fresh `genie init`) is picked up instead of serving empty forever. | ||
| if (!ctx.db) ctx.db = openReadonlyDb(cwd); | ||
|
Contributor
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 To fix this, please update the close handler to close rl.on('close', () => {
ctx.db?.close();
...
}); |
||
| const payload = tool.handler(ctx, args); | ||
| ok(id, { | ||
| content: [{ type: 'text', text: JSON.stringify(payload) }], | ||
|
|
@@ -162,7 +168,7 @@ export async function runMcpServer(): Promise<void> { | |
|
|
||
| await new Promise<void>((resolve) => { | ||
| rl.on('close', () => { | ||
| db?.close(); | ||
| ctx.db?.close(); | ||
| // Flush stdout BEFORE exiting: the empty-write callback fires after the | ||
| // stream's buffer (including all prior response writes) drains to the OS, | ||
| // so the final response is never truncated. See SPIKE.md flush note. | ||
|
|
||
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 a launched group branch's
<slug>-<group>string is also a known wish slug, this early exact match resolves the branch as a top-level wish instead of the group worktree.genie launchcreates branches aswish/${slug}-${name}and both slugs and groups allow hyphens, so with wishfoo-bargroupbazplus a separate wishfoo-bar-baz, thefoo-bar/bazworktree now receives all tasks forfoo-bar-bazrather than its own group context. Please validate known wish/group (or task group) candidates before treating the entire suffix as a top-level wish.Useful? React with 👍 / 👎.