From fb7edccc8565b941424eb4a4b4fa6eb811c48569 Mon Sep 17 00:00:00 2001 From: vinsew Date: Tue, 12 May 2026 18:41:28 +0800 Subject: [PATCH] fix(cli): register 'reindex' in CLI_ONLY so v0.32.7 sweep is reachable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `case 'reindex'` lives inside `handleCliOnly` (src/cli.ts:1075), which the dispatcher only enters when `CLI_ONLY.has(command)` is true at src/cli.ts:90. The set lists `'reindex-code'` and `'reindex-frontmatter'` but not bare `'reindex'`, so the markdown re-chunk sweep introduced in v0.32.7 is dead code: every invocation falls through to `cliOps.get('reindex')` (undefined, no Operation registered) and exits 1 with "Unknown command: reindex". Reproduce on unmodified origin/master: $ gbrain reindex --markdown --dry-run --json Unknown command: reindex Run gbrain --help for available commands. EXIT=1 Fix: add `'reindex',` next to `'reindex-code'` / `'reindex-frontmatter'`. Test coverage added in `test/reindex.test.ts`: - Structural: parse the CLI_ONLY line and require it match `/'reindex',/`. - Runtime: spawn `bun run src/cli.ts reindex --markdown --help` and assert stderr+stdout does NOT contain "Unknown command: reindex". The pre-existing `test/reindex.test.ts` cases only call `runReindex(engine, ...)` directly, bypassing the dispatcher — which is how this regression slipped past CI in #898. Verified locally on a production brain: with the fix in place, `gbrain reindex --markdown` swept 279 pre-bump pages to chunker_version=2 in 3:28, 0 failures, ~$0.08 in OpenAI embeddings. Context: PR #898 (v0.32.7) body marked Codex finding #3 (reindex not in CLI_ONLY) as a false positive on the grounds that "CLI_ONLY is the set that doesn't need an engine; reindex correctly belongs to the engine-backed dispatch." That premise is incorrect for this codebase — `handleCliOnly` contains many engine-backed commands (orchestrating engine connect inside the per-case branch). `case 'reindex'` at src/cli.ts:1075 follows the same pattern as `case 'orphans'` (1069) and `case 'salience'` (1081), both of which are in CLI_ONLY. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/cli.ts | 2 +- test/reindex.test.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/cli.ts b/src/cli.ts index ab9afb783..f669d703c 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -27,7 +27,7 @@ for (const op of operations) { } // CLI-only commands that bypass the operation layer -const CLI_ONLY = new Set(['init', 'upgrade', 'post-upgrade', 'check-update', 'integrations', 'publish', 'check-backlinks', 'lint', 'report', 'import', 'export', 'files', 'embed', 'serve', 'call', 'config', 'doctor', 'migrate', 'eval', 'sync', 'extract', 'features', 'autopilot', 'graph-query', 'jobs', 'agent', 'apply-migrations', 'skillpack-check', 'skillpack', 'resolvers', 'integrity', 'repair-jsonb', 'orphans', 'sources', 'mounts', 'dream', 'check-resolvable', 'routing-eval', 'skillify', 'smoke-test', 'providers', 'storage', 'repos', 'code-def', 'code-refs', 'reindex-code', 'reindex-frontmatter', 'code-callers', 'code-callees', 'frontmatter', 'auth', 'friction', 'claw-test', 'book-mirror', 'takes', 'think', 'salience', 'anomalies', 'transcripts', 'models', 'remote', 'recall', 'forget']); +const CLI_ONLY = new Set(['init', 'upgrade', 'post-upgrade', 'check-update', 'integrations', 'publish', 'check-backlinks', 'lint', 'report', 'import', 'export', 'files', 'embed', 'serve', 'call', 'config', 'doctor', 'migrate', 'eval', 'sync', 'extract', 'features', 'autopilot', 'graph-query', 'jobs', 'agent', 'apply-migrations', 'skillpack-check', 'skillpack', 'resolvers', 'integrity', 'repair-jsonb', 'orphans', 'sources', 'mounts', 'dream', 'check-resolvable', 'routing-eval', 'skillify', 'smoke-test', 'providers', 'storage', 'repos', 'code-def', 'code-refs', 'reindex', 'reindex-code', 'reindex-frontmatter', 'code-callers', 'code-callees', 'frontmatter', 'auth', 'friction', 'claw-test', 'book-mirror', 'takes', 'think', 'salience', 'anomalies', 'transcripts', 'models', 'remote', 'recall', 'forget']); // CLI-only commands whose handlers print their own --help text. These are // excluded from the generic short-circuit so detailed per-command and // per-subcommand usage stays reachable. diff --git a/test/reindex.test.ts b/test/reindex.test.ts index 9901f2044..1f6af1b9e 100644 --- a/test/reindex.test.ts +++ b/test/reindex.test.ts @@ -7,6 +7,7 @@ */ import { describe, test, expect, beforeAll, afterAll, beforeEach } from 'bun:test'; +import { readFileSync } from 'fs'; import { PGLiteEngine } from '../src/core/pglite-engine.ts'; import { runReindex } from '../src/commands/reindex.ts'; import { MARKDOWN_CHUNKER_VERSION } from '../src/core/chunkers/recursive.ts'; @@ -136,3 +137,37 @@ describe('gbrain reindex --markdown (v0.32.7)', () => { expect(result.reindexed).toBe(1); }); }); + +describe('gbrain reindex — CLI registration regression', () => { + const REPO_ROOT = new URL('..', import.meta.url).pathname; + const cliSource = readFileSync(new URL('../src/cli.ts', import.meta.url), 'utf-8'); + + test("'reindex' appears in CLI_ONLY set (not just 'reindex-code' / 'reindex-frontmatter')", () => { + // Regression guard: case 'reindex' lives in handleCliOnly's switch, so + // dispatch can only reach it when 'reindex' is in CLI_ONLY. Without + // this token, `gbrain reindex` exits with "Unknown command: reindex" + // before ever entering handleCliOnly. + const cliOnlyLine = cliSource.split('\n').find(l => l.includes('const CLI_ONLY = new Set(')); + expect(cliOnlyLine).toBeDefined(); + expect(cliOnlyLine!).toMatch(/'reindex',/); + }); + + test("subprocess `gbrain reindex --markdown --help` does not report 'Unknown command'", async () => { + const proc = Bun.spawn( + ['bun', 'run', 'src/cli.ts', 'reindex', '--markdown', '--help'], + { + cwd: REPO_ROOT, + stdout: 'pipe', + stderr: 'pipe', + env: { ...process.env, DATABASE_URL: '' }, + }, + ); + const stderr = await new Response(proc.stderr).text(); + const stdout = await new Response(proc.stdout).text(); + await proc.exited; + // Either prints help OR fails on something else (e.g. connect / args). + // The one outcome that MUST NOT happen is the dispatch-layer + // "Unknown command: reindex" exit-1 path. That's the bug this PR fixes. + expect(stderr + stdout).not.toContain('Unknown command: reindex'); + }); +});