fix(multi-get): support brace expansion patterns in glob matching (#424)
Brace expansion patterns like `{doc1,doc2}.md` or `collection/{a,b}.md`
were incorrectly parsed as comma-separated file lists instead of being
passed to the glob matcher (picomatch). This happened because the
comma-detection heuristic only checked for `*` and `?` but not `{`.
Also adds `collection/path` matching in `matchFilesByGlob` so patterns
like `my-collection/{file1,file2}.md` work — previously the glob only
matched against `qmd://collection/path` (virtual) and `path` (relative
to collection root), missing the `collection/path` form.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
09a4d19b31
commit
ef062e1b54
@ -995,7 +995,7 @@ function multiGet(pattern: string, maxLines?: number, maxBytes: number = DEFAULT
|
||||
const db = getDb();
|
||||
|
||||
// Check if it's a comma-separated list or a glob pattern
|
||||
const isCommaSeparated = pattern.includes(',') && !pattern.includes('*') && !pattern.includes('?');
|
||||
const isCommaSeparated = pattern.includes(',') && !pattern.includes('*') && !pattern.includes('?') && !pattern.includes('{');
|
||||
|
||||
let files: { filepath: string; displayPath: string; bodyLength: number; collection?: string; path?: string }[];
|
||||
|
||||
|
||||
@ -2362,7 +2362,7 @@ export function matchFilesByGlob(db: Database, pattern: string): { filepath: str
|
||||
|
||||
const isMatch = picomatch(pattern);
|
||||
return allFiles
|
||||
.filter(f => isMatch(f.virtual_path) || isMatch(f.path))
|
||||
.filter(f => isMatch(f.virtual_path) || isMatch(f.path) || isMatch(f.collection + '/' + f.path))
|
||||
.map(f => ({
|
||||
filepath: f.virtual_path, // Virtual path for precise lookup
|
||||
displayPath: f.path, // Relative path for display
|
||||
@ -3540,7 +3540,7 @@ export function findDocuments(
|
||||
pattern: string,
|
||||
options: { includeBody?: boolean; maxBytes?: number } = {}
|
||||
): { docs: MultiGetResult[]; errors: string[] } {
|
||||
const isCommaSeparated = pattern.includes(',') && !pattern.includes('*') && !pattern.includes('?');
|
||||
const isCommaSeparated = pattern.includes(',') && !pattern.includes('*') && !pattern.includes('?') && !pattern.includes('{');
|
||||
const errors: string[] = [];
|
||||
const maxBytes = options.maxBytes ?? DEFAULT_MULTI_GET_MAX_BYTES;
|
||||
|
||||
|
||||
@ -1909,6 +1909,55 @@ describe("Document Retrieval", () => {
|
||||
|
||||
await cleanupTestDb(store);
|
||||
});
|
||||
|
||||
test("findDocuments supports brace expansion patterns", async () => {
|
||||
const store = await createTestStore();
|
||||
const collectionName = await createTestCollection();
|
||||
|
||||
await insertTestDocument(store.db, collectionName, {
|
||||
name: "doc1",
|
||||
filepath: "/path/doc1.md",
|
||||
displayPath: "doc1.md",
|
||||
});
|
||||
await insertTestDocument(store.db, collectionName, {
|
||||
name: "doc2",
|
||||
filepath: "/path/doc2.md",
|
||||
displayPath: "doc2.md",
|
||||
});
|
||||
await insertTestDocument(store.db, collectionName, {
|
||||
name: "doc3",
|
||||
filepath: "/path/doc3.md",
|
||||
displayPath: "doc3.md",
|
||||
});
|
||||
|
||||
const { docs, errors } = store.findDocuments("{doc1,doc2}.md");
|
||||
expect(errors).toHaveLength(0);
|
||||
expect(docs).toHaveLength(2);
|
||||
|
||||
await cleanupTestDb(store);
|
||||
});
|
||||
|
||||
test("findDocuments supports brace expansion with collection prefix", async () => {
|
||||
const store = await createTestStore();
|
||||
const collectionName = await createTestCollection();
|
||||
|
||||
await insertTestDocument(store.db, collectionName, {
|
||||
name: "readme",
|
||||
filepath: "/path/readme.md",
|
||||
displayPath: "readme.md",
|
||||
});
|
||||
await insertTestDocument(store.db, collectionName, {
|
||||
name: "changelog",
|
||||
filepath: "/path/changelog.md",
|
||||
displayPath: "changelog.md",
|
||||
});
|
||||
|
||||
const { docs, errors } = store.findDocuments(`${collectionName}/{readme,changelog}.md`);
|
||||
expect(errors).toHaveLength(0);
|
||||
expect(docs).toHaveLength(2);
|
||||
|
||||
await cleanupTestDb(store);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@ -2267,6 +2316,48 @@ describe("Fuzzy Matching", () => {
|
||||
|
||||
await cleanupTestDb(store);
|
||||
});
|
||||
|
||||
test("matchFilesByGlob matches collection/path patterns", async () => {
|
||||
const store = await createTestStore();
|
||||
const collectionName = await createTestCollection();
|
||||
|
||||
await insertTestDocument(store.db, collectionName, {
|
||||
filepath: "/p/readme.md",
|
||||
displayPath: "readme.md",
|
||||
});
|
||||
await insertTestDocument(store.db, collectionName, {
|
||||
filepath: "/p/changelog.md",
|
||||
displayPath: "changelog.md",
|
||||
});
|
||||
|
||||
const matches = store.matchFilesByGlob(`${collectionName}/*.md`);
|
||||
expect(matches).toHaveLength(2);
|
||||
|
||||
await cleanupTestDb(store);
|
||||
});
|
||||
|
||||
test("matchFilesByGlob matches brace expansion", async () => {
|
||||
const store = await createTestStore();
|
||||
const collectionName = await createTestCollection();
|
||||
|
||||
await insertTestDocument(store.db, collectionName, {
|
||||
filepath: "/p/readme.md",
|
||||
displayPath: "readme.md",
|
||||
});
|
||||
await insertTestDocument(store.db, collectionName, {
|
||||
filepath: "/p/changelog.md",
|
||||
displayPath: "changelog.md",
|
||||
});
|
||||
await insertTestDocument(store.db, collectionName, {
|
||||
filepath: "/p/license.md",
|
||||
displayPath: "license.md",
|
||||
});
|
||||
|
||||
const matches = store.matchFilesByGlob(`${collectionName}/{readme,changelog}.md`);
|
||||
expect(matches).toHaveLength(2);
|
||||
|
||||
await cleanupTestDb(store);
|
||||
});
|
||||
});
|
||||
|
||||
// =============================================================================
|
||||
|
||||
Loading…
Reference in New Issue
Block a user