Repository navigation
feat(indexer): search output overhaul + Qdrant Docker #136
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
e085ae6
cbd149a
c51b18c
115379b
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 |
|---|---|---|
| @@ -1,9 +1,12 @@ | ||
| import { isAbsolute, relative } from "node:path"; | ||
| import { toToon } from "@app/json/lib/toon"; | ||
| import { isInteractive } from "@app/utils/cli"; | ||
| import { SafeJSON } from "@app/utils/json"; | ||
| import type { SearchResult } from "@app/utils/search/types"; | ||
| import { truncateText } from "@app/utils/string"; | ||
| import * as p from "@clack/prompts"; | ||
| import type { Command } from "commander"; | ||
| import pc from "picocolors"; | ||
| import { normalizeConfidence } from "../lib/confidence"; | ||
| import { formatChunkDisplayName } from "../lib/display-name"; | ||
| import { parseQueryWords } from "../lib/highlight"; | ||
|
|
@@ -56,7 +59,7 @@ export function registerSearchCommand(program: Command): void { | |
| .command("search") | ||
| .description("Search across indexes") | ||
| .argument("<query>", "Search query") | ||
| .option("-i, --index <name>", "Search specific index (default: all)") | ||
| .option("-i, --index <name>", "Index to search (auto-detect from cwd; 'all' for all)") | ||
| .option("-m, --mode <mode>", "Search mode: fulltext, vector, hybrid (default: auto-detect)") | ||
| .option("-l, --limit <n>", "Max results", parseInt, 20) | ||
| .option("-f, --file <filter>", "Filter results to files matching this substring") | ||
|
|
@@ -82,14 +85,67 @@ export function registerSearchCommand(program: Command): void { | |
| return; | ||
| } | ||
|
|
||
| const names = opts.index ? [opts.index] : manager.getIndexNames(); | ||
| let names: string[]; | ||
|
|
||
| if (opts.index === "all") { | ||
| names = manager.getIndexNames(); | ||
| } else if (opts.index) { | ||
| names = [opts.index]; | ||
| } else { | ||
|
Comment on lines
+90
to
+94
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.
Index names are unconstrained strings, so a real index named 🤖 Prompt for AI Agents |
||
| const allMeta = manager.listIndexes(); | ||
|
|
||
| if (allMeta.length === 0) { | ||
| p.log.info("No indexes configured. Run: tools indexer add <path>"); | ||
| return; | ||
| } | ||
|
|
||
| if (allMeta.length === 1) { | ||
| names = [allMeta[0].name]; | ||
| } else { | ||
| const cwd = process.cwd(); | ||
| const cwdMatches = allMeta | ||
| .filter((m) => { | ||
| const rel = relative(m.config.baseDir, cwd); | ||
| return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel)); | ||
| }) | ||
| .sort((a, b) => b.config.baseDir.length - a.config.baseDir.length); | ||
|
|
||
| const cwdMatch = cwdMatches[0]; | ||
| const isUniqueDeepest = | ||
| cwdMatches.length < 2 || | ||
| cwdMatches[0].config.baseDir.length > cwdMatches[1].config.baseDir.length; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| if (cwdMatch && isUniqueDeepest) { | ||
| names = [cwdMatch.name]; | ||
| p.log.info(pc.dim(`(using index "${cwdMatch.name}" from cwd)`)); | ||
| } else if (isInteractive()) { | ||
|
Comment on lines
+105
to
+121
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. Use the deepest path match for cwd auto-detection. This 🛠️ Suggested fix+import { isAbsolute, relative, sep } from "node:path";
@@
- const cwdMatch = allMeta.find((m) => {
- const baseDir = m.config.baseDir;
- return cwd === baseDir || cwd.startsWith(`${baseDir}/`);
- });
+ const cwdMatches = allMeta
+ .filter((m) => {
+ const rel = relative(m.config.baseDir, cwd);
+ return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
+ })
+ .sort((a, b) => b.config.baseDir.length - a.config.baseDir.length);
+
+ const cwdMatch = cwdMatches[0];
+ const isUniqueDeepest =
+ cwdMatches.length < 2 || cwdMatches[0].config.baseDir.length > cwdMatches[1].config.baseDir.length;
- if (cwdMatch) {
+ if (cwdMatch && isUniqueDeepest) {
names = [cwdMatch.name];
p.log.info(pc.dim(`(using index "${cwdMatch.name}" from cwd)`));
} else if (isInteractive()) {As per coding guidelines, use Node.js 🤖 Prompt for AI Agents |
||
| const allNames = allMeta.map((m) => m.name); | ||
| const selected = await p.select({ | ||
| message: "Select index to search", | ||
| options: [ | ||
| { value: "__all__", label: "All indexes" }, | ||
| ...allNames.map((n) => ({ value: n, label: n })), | ||
| ], | ||
| }); | ||
|
|
||
| if (p.isCancel(selected)) { | ||
| p.log.info("Cancelled"); | ||
| return; | ||
| } | ||
|
|
||
| names = selected === "__all__" ? allNames : [selected]; | ||
| } else { | ||
| p.log.error("Multiple indexes found. Use -i <name> or -i all."); | ||
| return; | ||
|
Comment on lines
+95
to
+139
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. 🛠️ Refactor suggestion | 🟠 Major Extract this selection flow into This branch now owns cwd matching, deepest-match disambiguation, prompt fallback, and selection policy. Moving it out of the Commander action will keep the command wrapper thin and make the behavior easier to unit-test. As per coding guidelines, "Treat 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| } | ||
|
|
||
| if (names.length === 0) { | ||
| p.log.info("No indexes configured. Run: tools indexer add <path>"); | ||
| return; | ||
| } | ||
|
|
||
| // Load all indexers upfront for mode detection across all indexes | ||
| const indexers = await Promise.all(names.map((n) => manager.getIndex(n))); | ||
|
|
||
| let mode: SearchMode; | ||
|
|
@@ -171,13 +227,22 @@ export function registerSearchCommand(program: Command): void { | |
| return; | ||
| } | ||
|
|
||
| const baseDirs = new Map<string, string>(); | ||
|
|
||
| for (const indexer of indexers) { | ||
| const cfg = indexer.getConfig(); | ||
| baseDirs.set(cfg.name, cfg.baseDir); | ||
| } | ||
|
|
||
| const words = parseQueryWords(query); | ||
| const output = formatSearchResults({ | ||
| results: filtered, | ||
| format: format as OutputFormat, | ||
| query, | ||
| mode: effectiveMode, | ||
| highlightWords: words, | ||
| baseDirs, | ||
| multiIndex: names.length > 1, | ||
| }); | ||
| console.log(output); | ||
| } finally { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.