Procházet zdrojové kódy

Fix query hang, SQL errors, and missing docid in search results

- Fix SQL syntax error when collectionId is empty string (searchFTS, searchVec)
- Add 1-second timeout to llama.dispose() to prevent indefinite hang
- Add process.exit(0) after cleanup for clean CLI exit
- Include hash/docid in search results mapping
- Update query expansion to use structured Queryable types
- Switch to Qwen3-1.7B model for better query expansion
- Improve bun discovery in qmd wrapper script

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tobi Lutke před 4 měsíci
rodič
revize
0dfd7a4686
5 změnil soubory, kde provedl 174 přidání a 148 odebrání
  1. 31 31
      .beads/issues.jsonl
  2. 46 5
      qmd
  3. 82 102
      src/llm.ts
  4. 3 0
      src/qmd.ts
  5. 12 10
      src/store.ts

+ 31 - 31
.beads/issues.jsonl

@@ -1,48 +1,48 @@
-{"id":"qmd-0ic","title":"in qmd status, list all the additonal contexts under the collections that match","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:41:42.126194-05:00","updated_at":"2025-12-12T17:14:48.268119-05:00","closed_at":"2025-12-12T17:14:48.268119-05:00"}
-{"id":"qmd-18s","title":"Move cleanup/maintenance DB operations to store.ts","description":"Move cleanup operations from cleanup() command to store.ts. Create methods like deleteInactiveDocuments(), vacuumDatabase(), cleanupOrphanedContent(), etc.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:36:21.815781-05:00","updated_at":"2025-12-12T16:42:36.896806-05:00","closed_at":"2025-12-12T16:42:36.896806-05:00","dependencies":[{"issue_id":"qmd-18s","depends_on_id":"qmd-29c","type":"parent-child","created_at":"2025-12-12T16:37:03.014111-05:00","created_by":"daemon"}]}
-{"id":"qmd-1xd","title":"Update tests for YAML-based collections","description":"Update all tests to use YAML config instead of DB collections. Update test helpers to create temporary YAML configs.","notes":"Test suite has been updated for YAML-based collections. 92 tests passing, 4 skipped, 10 failing.\n\nThe 4 skipped tests call getStatus() which has a bug (queries non-existent collections table).\n\nThe 10 failing tests are due to bugs in store.ts functions (findDocument, getDocumentBody, getDocument, findSimilarFiles, matchFilesByGlob) that need to be updated to use YAML configuration. These are production code bugs, not test bugs.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:54:53.349545-05:00","updated_at":"2025-12-13T11:37:16.935866-05:00","closed_at":"2025-12-13T11:37:16.935866-05:00","dependencies":[{"issue_id":"qmd-1xd","depends_on_id":"qmd-thw","type":"blocks","created_at":"2025-12-13T09:55:08.14305-05:00","created_by":"daemon"}]}
+{"id":"qmd-0ic","title":"in qmd status, list all the additonal contexts under the collections that match","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:41:42.126194-05:00","updated_at":"2025-12-12T17:14:48.268119-05:00","closed_at":"2025-12-12T17:14:48.268119-05:00"}
+{"id":"qmd-18s","title":"Move cleanup/maintenance DB operations to store.ts","description":"Move cleanup operations from cleanup() command to store.ts. Create methods like deleteInactiveDocuments(), vacuumDatabase(), cleanupOrphanedContent(), etc.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:36:21.815781-05:00","updated_at":"2025-12-12T16:42:36.896806-05:00","closed_at":"2025-12-12T16:42:36.896806-05:00","dependencies":[{"issue_id":"qmd-18s","depends_on_id":"qmd-29c","type":"parent-child","created_at":"2025-12-12T16:37:03.014111-05:00","created_by":"daemon","metadata":"{}"}]}
+{"id":"qmd-1xd","title":"Update tests for YAML-based collections","description":"Update all tests to use YAML config instead of DB collections. Update test helpers to create temporary YAML configs.","notes":"Test suite has been updated for YAML-based collections. 92 tests passing, 4 skipped, 10 failing.\n\nThe 4 skipped tests call getStatus() which has a bug (queries non-existent collections table).\n\nThe 10 failing tests are due to bugs in store.ts functions (findDocument, getDocumentBody, getDocument, findSimilarFiles, matchFilesByGlob) that need to be updated to use YAML configuration. These are production code bugs, not test bugs.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:54:53.349545-05:00","updated_at":"2025-12-13T11:37:16.935866-05:00","closed_at":"2025-12-13T11:37:16.935866-05:00","dependencies":[{"issue_id":"qmd-1xd","depends_on_id":"qmd-thw","type":"blocks","created_at":"2025-12-13T09:55:08.14305-05:00","created_by":"daemon","metadata":"{}"}]}
 {"id":"qmd-29c","title":"Move all database operations from qmd.ts to store.ts","description":"Currently qmd.ts has ~70 direct database operations (db.prepare, db.exec). All database operations should be moved to store.ts to improve separation of concerns. qmd.ts should only use high-level methods from store.ts that don't require direct SQL knowledge.","notes":"Phase 1 complete: Moved collection operations (listCollections, removeCollection, renameCollection) to store.ts. Created 4 subtasks for remaining work: document indexing, context management, embeddings, and cleanup operations.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:32:13.722223-05:00","updated_at":"2025-12-12T16:49:53.829124-05:00","closed_at":"2025-12-12T16:49:53.829124-05:00"}
 {"id":"qmd-2gn","title":"Fix store.ts functions to use YAML collections","description":"Update findDocument(), getDocumentBody(), getDocument(), findSimilarFiles(), matchFilesByGlob(), and getStatus() to use YAML collection configuration instead of querying the collections table. These functions currently fail because they try to query the non-existent collections table.","notes":"Fixed:\n- FTS schema (filepath, title, body columns) \n- getStatus() to use YAML collections\n- searchFTS() to not query collections table\n- findDocument() absolute path matching\n\nTest results: 93 passing (up from 92), 4 skipped, 9 failing\n\nRemaining failures:\n- getDocumentBody (2 tests)\n- getDocument (1 test)  \n- findSimilarFiles (2 tests)\n- matchFilesByGlob (1 test)\n- Integration/context tests (3 tests)","status":"in_progress","priority":1,"issue_type":"bug","created_at":"2025-12-13T11:37:22.706882-05:00","updated_at":"2025-12-13T12:32:38.336752-05:00"}
-{"id":"qmd-3qi","title":"Document docid hash usage in CLAUDE.md","description":"Update CLAUDE.md to document that short hash IDs (#abc123) work with get and multi-get commands. Include examples.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-15T12:54:30.634116-05:00","updated_at":"2025-12-15T13:12:38.91973-05:00","closed_at":"2025-12-15T13:12:38.91973-05:00","dependencies":[{"issue_id":"qmd-3qi","depends_on_id":"qmd-lwo","type":"parent-child","created_at":"2025-12-15T12:54:52.002856-05:00","created_by":"daemon"},{"issue_id":"qmd-3qi","depends_on_id":"qmd-apl","type":"blocks","created_at":"2025-12-15T12:54:52.066036-05:00","created_by":"daemon"}]}
+{"id":"qmd-3qi","title":"Document docid hash usage in CLAUDE.md","description":"Update CLAUDE.md to document that short hash IDs (#abc123) work with get and multi-get commands. Include examples.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-15T12:54:30.634116-05:00","updated_at":"2025-12-15T13:12:38.91973-05:00","closed_at":"2025-12-15T13:12:38.91973-05:00","dependencies":[{"issue_id":"qmd-3qi","depends_on_id":"qmd-lwo","type":"parent-child","created_at":"2025-12-15T12:54:52.002856-05:00","created_by":"daemon","metadata":"{}"},{"issue_id":"qmd-3qi","depends_on_id":"qmd-apl","type":"blocks","created_at":"2025-12-15T12:54:52.066036-05:00","created_by":"daemon","metadata":"{}"}]}
 {"id":"qmd-3z9","title":"Design YAML schema and create collections.ts module","description":"Create collections.ts to manage YAML-based collection configuration at ~/.config/qmd/index.yml. Define TypeScript types for collections and contexts. Implement load/save functions with Bun's native YAML support.","design":"YAML structure:\n```yaml\n# Global context for all collections\nglobal_context: \"...\"\n\ncollections:\n  name:\n    path: /absolute/path\n    pattern: \"**/*.md\"\n    context:\n      \"/path/prefix\": \"Description\"\n      \"/\": \"Root context\"\n```\n\nTypeScript types:\n- Collection: { path, pattern, context }\n- CollectionConfig: { global_context?, collections }\n- Functions: loadConfig(), saveConfig(), getCollection(), listCollections()","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:54:52.586027-05:00","updated_at":"2025-12-13T09:56:57.309927-05:00","closed_at":"2025-12-13T09:56:57.309927-05:00"}
-{"id":"qmd-4ru","title":"Update document retrieval for new schema","description":"Functions like getDocument, findDocument, getMultipleDocuments need to work with new schema (path instead of filepath, content joins, virtual paths).","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-12T15:29:53.911881-05:00","updated_at":"2025-12-12T15:56:11.054888-05:00","closed_at":"2025-12-12T15:56:11.054888-05:00","dependencies":[{"issue_id":"qmd-4ru","depends_on_id":"qmd-ama","type":"discovered-from","created_at":"2025-12-12T15:29:53.912607-05:00","created_by":"daemon"}]}
-{"id":"qmd-4u4","title":"Move embedding/vector DB operations to store.ts","description":"Move vector indexing DB operations from vectorIndex() to store.ts. Create methods like getHashesForEmbedding(), insertEmbedding(), clearEmbeddings(), etc.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:36:21.683434-05:00","updated_at":"2025-12-12T16:42:40.42653-05:00","closed_at":"2025-12-12T16:42:40.42653-05:00","dependencies":[{"issue_id":"qmd-4u4","depends_on_id":"qmd-29c","type":"parent-child","created_at":"2025-12-12T16:37:02.944591-05:00","created_by":"daemon"}]}
-{"id":"qmd-6s5","title":"Export current database to index.yml","description":"Write a script to export current collections and path_contexts from SQLite to ~/.config/qmd/index.yml format. Include all collection metadata and contexts.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:54:52.707844-05:00","updated_at":"2025-12-13T09:57:36.650437-05:00","closed_at":"2025-12-13T09:57:36.650437-05:00","dependencies":[{"issue_id":"qmd-6s5","depends_on_id":"qmd-3z9","type":"blocks","created_at":"2025-12-13T09:55:07.606834-05:00","created_by":"daemon"}]}
-{"id":"qmd-7ss","title":"remove all the symlinks and stuff in the git repo, clean up the root directory","description":"","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-12T16:40:00.744982-05:00","updated_at":"2025-12-12T17:11:18.034215-05:00","closed_at":"2025-12-12T17:11:18.034215-05:00"}
-{"id":"qmd-8eu","title":"Update documents table schema for collection names","description":"Change documents.collection_id (integer FK) to documents.collection (text). Update all queries and indices. Keep backwards compatibility during transition.","design":"Schema change:\n- Add `collection TEXT` column\n- Migrate data: UPDATE documents SET collection = (SELECT name FROM collections WHERE id = collection_id)\n- Drop collection_id column\n- Update FTS5 trigger\n- Update all queries in store.ts","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:54:52.830305-05:00","updated_at":"2025-12-13T10:08:24.88716-05:00","closed_at":"2025-12-13T10:08:24.88716-05:00","dependencies":[{"issue_id":"qmd-8eu","depends_on_id":"qmd-6s5","type":"blocks","created_at":"2025-12-13T09:55:07.662048-05:00","created_by":"daemon"}]}
+{"id":"qmd-4ru","title":"Update document retrieval for new schema","description":"Functions like getDocument, findDocument, getMultipleDocuments need to work with new schema (path instead of filepath, content joins, virtual paths).","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-12T15:29:53.911881-05:00","updated_at":"2025-12-12T15:56:11.054888-05:00","closed_at":"2025-12-12T15:56:11.054888-05:00","dependencies":[{"issue_id":"qmd-4ru","depends_on_id":"qmd-ama","type":"discovered-from","created_at":"2025-12-12T15:29:53.912607-05:00","created_by":"daemon","metadata":"{}"}]}
+{"id":"qmd-4u4","title":"Move embedding/vector DB operations to store.ts","description":"Move vector indexing DB operations from vectorIndex() to store.ts. Create methods like getHashesForEmbedding(), insertEmbedding(), clearEmbeddings(), etc.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:36:21.683434-05:00","updated_at":"2025-12-12T16:42:40.42653-05:00","closed_at":"2025-12-12T16:42:40.42653-05:00","dependencies":[{"issue_id":"qmd-4u4","depends_on_id":"qmd-29c","type":"parent-child","created_at":"2025-12-12T16:37:02.944591-05:00","created_by":"daemon","metadata":"{}"}]}
+{"id":"qmd-6s5","title":"Export current database to index.yml","description":"Write a script to export current collections and path_contexts from SQLite to ~/.config/qmd/index.yml format. Include all collection metadata and contexts.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:54:52.707844-05:00","updated_at":"2025-12-13T09:57:36.650437-05:00","closed_at":"2025-12-13T09:57:36.650437-05:00","dependencies":[{"issue_id":"qmd-6s5","depends_on_id":"qmd-3z9","type":"blocks","created_at":"2025-12-13T09:55:07.606834-05:00","created_by":"daemon","metadata":"{}"}]}
+{"id":"qmd-7ss","title":"remove all the symlinks and stuff in the git repo, clean up the root directory","status":"closed","priority":4,"issue_type":"task","created_at":"2025-12-12T16:40:00.744982-05:00","updated_at":"2025-12-12T17:11:18.034215-05:00","closed_at":"2025-12-12T17:11:18.034215-05:00"}
+{"id":"qmd-8eu","title":"Update documents table schema for collection names","description":"Change documents.collection_id (integer FK) to documents.collection (text). Update all queries and indices. Keep backwards compatibility during transition.","design":"Schema change:\n- Add `collection TEXT` column\n- Migrate data: UPDATE documents SET collection = (SELECT name FROM collections WHERE id = collection_id)\n- Drop collection_id column\n- Update FTS5 trigger\n- Update all queries in store.ts","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:54:52.830305-05:00","updated_at":"2025-12-13T10:08:24.88716-05:00","closed_at":"2025-12-13T10:08:24.88716-05:00","dependencies":[{"issue_id":"qmd-8eu","depends_on_id":"qmd-6s5","type":"blocks","created_at":"2025-12-13T09:55:07.662048-05:00","created_by":"daemon","metadata":"{}"}]}
 {"id":"qmd-9ij","title":"Conditional query expansion based on BM25 signal strength","description":"Query expansion helps recall but injects false positives. Skip expansion if original BM25 top-5 has strong signals (exact term hits, high proximity). Only expand when recall is weak.","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-12-20T17:18:41.806447-05:00","updated_at":"2025-12-21T12:05:40.85997-05:00","closed_at":"2025-12-21T12:05:40.85997-05:00"}
-{"id":"qmd-9ua","title":"Update all qmd commands for YAML-based collections","description":"Update qmd.ts commands: collection add/list/remove/rename, status, update, ls. All should use collections.ts instead of store.ts collection functions.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:54:53.14644-05:00","updated_at":"2025-12-13T10:17:39.67707-05:00","closed_at":"2025-12-13T10:17:39.67707-05:00","dependencies":[{"issue_id":"qmd-9ua","depends_on_id":"qmd-u84","type":"blocks","created_at":"2025-12-13T09:55:07.893268-05:00","created_by":"daemon"},{"issue_id":"qmd-9ua","depends_on_id":"qmd-oxy","type":"blocks","created_at":"2025-12-13T09:55:07.942221-05:00","created_by":"daemon"}]}
-{"id":"qmd-afe","title":"implement qmd collection rename, which changes the global path prefix for the collection","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T15:55:54.779325-05:00","updated_at":"2025-12-12T16:29:24.153196-05:00","closed_at":"2025-12-12T16:29:24.153196-05:00"}
+{"id":"qmd-9ua","title":"Update all qmd commands for YAML-based collections","description":"Update qmd.ts commands: collection add/list/remove/rename, status, update, ls. All should use collections.ts instead of store.ts collection functions.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:54:53.14644-05:00","updated_at":"2025-12-13T10:17:39.67707-05:00","closed_at":"2025-12-13T10:17:39.67707-05:00","dependencies":[{"issue_id":"qmd-9ua","depends_on_id":"qmd-u84","type":"blocks","created_at":"2025-12-13T09:55:07.893268-05:00","created_by":"daemon","metadata":"{}"},{"issue_id":"qmd-9ua","depends_on_id":"qmd-oxy","type":"blocks","created_at":"2025-12-13T09:55:07.942221-05:00","created_by":"daemon","metadata":"{}"}]}
+{"id":"qmd-afe","title":"implement qmd collection rename, which changes the global path prefix for the collection","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T15:55:54.779325-05:00","updated_at":"2025-12-12T16:29:24.153196-05:00","closed_at":"2025-12-12T16:29:24.153196-05:00"}
 {"id":"qmd-ama","title":"Refactor database system","description":"All documents should be stored as content addressable hash, e.g. hash, doc, created_at,\n┃ updated_at. documents should be a file system layer on top e.g. collection, path, hash,\n┃ created_at, updated_at. (collection,path)\n┃\n┃\n\n┃ All documents should be stored as content addressable hash, e.g. hash, doc, created_at,\n┃ updated_at. documents should be a file system layer on top e.g. collection_id, path, hash,\n┃ created_at, updated_at. (collection,path) is unique. There is also collection which stores PWD\n┃ + glob pattern, name (\\w+). Every document is treated as path qmd://collection.name/","notes":"## Completed\n- ✅ Implemented content-addressable storage (content table with hash→doc mapping)\n- ✅ Refactored documents table as file system layer (collection_id, path, hash)\n- ✅ Added collection names (e.g., \"pages\", \"journals\", \"archive\")\n- ✅ Implemented virtual paths (qmd://collection-name/path/to/file.md)\n- ✅ Added hierarchical context support (collection-scoped)\n- ✅ Successfully migrated existing database\n- ✅ Updated search functions to work with new schema\n- ✅ Updated indexing logic to use content-addressable storage\n- ✅ Orphaned content hash cleanup\n\n## Still TODO\n- Fix migration SQL to properly extract basename (currently needs manual fix)\n- Implement `qmd collection add . --name \u003cname\u003e --mask '**/*.md'`\n- Implement `qmd ls [path]` for exploring virtual file tree","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-10T10:57:35.497489-05:00","updated_at":"2025-12-12T15:39:48.879143-05:00","closed_at":"2025-12-12T15:39:48.879143-05:00"}
-{"id":"qmd-apl","title":"Support docid hash lookup in get and multi-get commands","description":"Allow get and multi-get to accept #hash (6 char) as a file identifier. Add lookup function to resolve short hash to full document path. Handle collisions gracefully.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-15T12:54:30.448466-05:00","updated_at":"2025-12-15T13:12:38.741755-05:00","closed_at":"2025-12-15T13:12:38.741755-05:00","dependencies":[{"issue_id":"qmd-apl","depends_on_id":"qmd-lwo","type":"parent-child","created_at":"2025-12-15T12:54:51.903613-05:00","created_by":"daemon"},{"issue_id":"qmd-apl","depends_on_id":"qmd-gbt","type":"blocks","created_at":"2025-12-15T12:54:52.031069-05:00","created_by":"daemon"}]}
-{"id":"qmd-bs8","title":"Update documentation for YAML configuration","description":"Update CLAUDE.md, README.md with new YAML configuration approach. Document index.yml format and manual editing instructions.","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-13T09:54:53.449584-05:00","updated_at":"2025-12-13T09:54:53.449584-05:00","dependencies":[{"issue_id":"qmd-bs8","depends_on_id":"qmd-1xd","type":"blocks","created_at":"2025-12-13T09:55:08.264615-05:00","created_by":"daemon"}]}
-{"id":"qmd-bx1","title":"Fix migration SQL for proper basename extraction","description":"The migration currently generates collection names incorrectly (uses full path instead of basename). Need to fix the SQL in migrateToContentAddressable to properly extract the directory basename.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-12T15:29:53.757723-05:00","updated_at":"2025-12-12T15:50:29.349134-05:00","closed_at":"2025-12-12T15:50:29.349134-05:00","dependencies":[{"issue_id":"qmd-bx1","depends_on_id":"qmd-ama","type":"discovered-from","created_at":"2025-12-12T15:29:53.758524-05:00","created_by":"daemon"}]}
+{"id":"qmd-apl","title":"Support docid hash lookup in get and multi-get commands","description":"Allow get and multi-get to accept #hash (6 char) as a file identifier. Add lookup function to resolve short hash to full document path. Handle collisions gracefully.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-15T12:54:30.448466-05:00","updated_at":"2025-12-15T13:12:38.741755-05:00","closed_at":"2025-12-15T13:12:38.741755-05:00","dependencies":[{"issue_id":"qmd-apl","depends_on_id":"qmd-lwo","type":"parent-child","created_at":"2025-12-15T12:54:51.903613-05:00","created_by":"daemon","metadata":"{}"},{"issue_id":"qmd-apl","depends_on_id":"qmd-gbt","type":"blocks","created_at":"2025-12-15T12:54:52.031069-05:00","created_by":"daemon","metadata":"{}"}]}
+{"id":"qmd-bs8","title":"Update documentation for YAML configuration","description":"Update CLAUDE.md, README.md with new YAML configuration approach. Document index.yml format and manual editing instructions.","status":"open","priority":2,"issue_type":"task","created_at":"2025-12-13T09:54:53.449584-05:00","updated_at":"2025-12-13T09:54:53.449584-05:00","dependencies":[{"issue_id":"qmd-bs8","depends_on_id":"qmd-1xd","type":"blocks","created_at":"2025-12-13T09:55:08.264615-05:00","created_by":"daemon","metadata":"{}"}]}
+{"id":"qmd-bx1","title":"Fix migration SQL for proper basename extraction","description":"The migration currently generates collection names incorrectly (uses full path instead of basename). Need to fix the SQL in migrateToContentAddressable to properly extract the directory basename.","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-12T15:29:53.757723-05:00","updated_at":"2025-12-12T15:50:29.349134-05:00","closed_at":"2025-12-12T15:50:29.349134-05:00","dependencies":[{"issue_id":"qmd-bx1","depends_on_id":"qmd-ama","type":"discovered-from","created_at":"2025-12-12T15:29:53.758524-05:00","created_by":"daemon","metadata":"{}"}]}
 {"id":"qmd-c0m","title":"Comprehensive CLI review and consistency pass","description":"Review entire CLI command structure:\n- Consistent naming (add vs create, remove vs delete)\n- Consistent flag usage (--name, --mask, etc)\n- Update help text for all commands\n- Ensure virtual paths work everywhere\n- Test all commands end-to-end","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-12T15:29:38.083564-05:00","updated_at":"2025-12-12T16:06:51.544695-05:00","closed_at":"2025-12-12T16:06:51.544695-05:00"}
-{"id":"qmd-clr","title":"fix embed","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:14:55.292114-05:00","updated_at":"2025-12-12T16:31:27.661829-05:00","closed_at":"2025-12-12T16:31:27.661829-05:00"}
+{"id":"qmd-clr","title":"fix embed","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:14:55.292114-05:00","updated_at":"2025-12-12T16:31:27.661829-05:00","closed_at":"2025-12-12T16:31:27.661829-05:00"}
 {"id":"qmd-d00","title":"Add offline evaluation harness for tuning","description":"Create a small benchmark with ~100 labeled queries from real searches. Would enable tuning: expansion on/off threshold, candidate count (30 vs 100), blending weights, reranker threshold.","notes":"Test samples must be: 1) entirely synthetic, OR 2) public documents (e.g., public podcasts, public memos). No private/personal content in eval set.","status":"closed","priority":3,"issue_type":"feature","created_at":"2025-12-20T17:18:42.007265-05:00","updated_at":"2025-12-21T12:10:40.497797-05:00","closed_at":"2025-12-21T12:10:40.497797-05:00"}
-{"id":"qmd-deh","title":"Refactor database introduce qmd collection *","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-10T10:56:04.516137-05:00","updated_at":"2025-12-12T16:12:12.349428-05:00","closed_at":"2025-12-12T16:12:12.349428-05:00"}
+{"id":"qmd-deh","title":"Refactor database introduce qmd collection *","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-10T10:56:04.516137-05:00","updated_at":"2025-12-12T16:12:12.349428-05:00","closed_at":"2025-12-12T16:12:12.349428-05:00"}
 {"id":"qmd-df5","title":"Rerank multiple chunks per document with score aggregation","description":"Currently we only rerank 1 chunk per doc (selected by keyword heuristic). Should rerank top 2-3 chunks per document, then aggregate scores (max, softmax, or top-2 average). This improves ranking for long documents where the keyword-matched chunk isn't always the most relevant.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-20T17:18:41.592575-05:00","updated_at":"2025-12-21T12:04:11.777309-05:00","closed_at":"2025-12-21T12:04:11.777309-05:00"}
-{"id":"qmd-dmi","title":"Implement 'qmd collection' commands","description":"Add explicit collection management:\n- qmd collection add . --name \u003cname\u003e --mask '**/*.md'\n- qmd collection list\n- qmd collection remove \u003cname\u003e\n\nThis gives users control over collection names and patterns.","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-12-12T15:29:53.810666-05:00","updated_at":"2025-12-12T16:02:08.079158-05:00","closed_at":"2025-12-12T16:02:08.079158-05:00","dependencies":[{"issue_id":"qmd-dmi","depends_on_id":"qmd-ama","type":"discovered-from","created_at":"2025-12-12T15:29:53.811294-05:00","created_by":"daemon"}]}
+{"id":"qmd-dmi","title":"Implement 'qmd collection' commands","description":"Add explicit collection management:\n- qmd collection add . --name \u003cname\u003e --mask '**/*.md'\n- qmd collection list\n- qmd collection remove \u003cname\u003e\n\nThis gives users control over collection names and patterns.","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-12-12T15:29:53.810666-05:00","updated_at":"2025-12-12T16:02:08.079158-05:00","closed_at":"2025-12-12T16:02:08.079158-05:00","dependencies":[{"issue_id":"qmd-dmi","depends_on_id":"qmd-ama","type":"discovered-from","created_at":"2025-12-12T15:29:53.811294-05:00","created_by":"daemon","metadata":"{}"}]}
 {"id":"qmd-dt1","title":"Redesign context add command for better usability","description":"Current issues: \n1. Virtual path qmd://journals/ is rejected as invalid\n2. Syntax is confusing - sometimes path is first arg, sometimes second\n3. Need to support collection root context (qmd://name/)\n4. Should be intuitive: qmd context add \u003cwhere\u003e \u003cwhat\u003e\nDesign goals:\n- Support qmd://collection/ for collection root context\n- Support qmd://collection/path for path-specific context\n- Clear, consistent syntax\n- Good error messages","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:39:19.764114-05:00","updated_at":"2025-12-13T09:41:38.467861-05:00","closed_at":"2025-12-13T09:41:38.467861-05:00"}
-{"id":"qmd-e2c","title":"Implement 'qmd ls' command","description":"Add command to explore virtual file tree:\n- qmd ls → list all collections\n- qmd ls \u003ccollection\u003e → list files in collection\n- qmd ls \u003ccollection\u003e/\u003cpath\u003e → list files under path\nOutput: flat list of qmd:// paths","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-12-12T15:29:53.859804-05:00","updated_at":"2025-12-12T15:55:12.777701-05:00","closed_at":"2025-12-12T15:55:12.777701-05:00","dependencies":[{"issue_id":"qmd-e2c","depends_on_id":"qmd-ama","type":"discovered-from","created_at":"2025-12-12T15:29:53.860535-05:00","created_by":"daemon"}]}
-{"id":"qmd-gbt","title":"Add docid field (first 6 chars of hash) to search results","description":"Include docid formatted as #hash[0:5] in all search output formats (CLI, JSON, CSV, Markdown, XML, files). The docid should be the first 6 characters of the document's SHA256 hash.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-15T12:54:30.388094-05:00","updated_at":"2025-12-15T13:12:27.553492-05:00","closed_at":"2025-12-15T13:12:27.553492-05:00","dependencies":[{"issue_id":"qmd-gbt","depends_on_id":"qmd-lwo","type":"parent-child","created_at":"2025-12-15T12:54:51.846848-05:00","created_by":"daemon"}]}
-{"id":"qmd-ht6","title":"Add --line-numbers CLI option for line-numbered output","description":"Add --line-numbers flag to CLI that formats output with line numbers: each line becomes \"{lineNum}: {content}\". Apply to get, multi-get, and search snippet output.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-15T12:54:30.51773-05:00","updated_at":"2025-12-15T13:12:38.810032-05:00","closed_at":"2025-12-15T13:12:38.810032-05:00","dependencies":[{"issue_id":"qmd-ht6","depends_on_id":"qmd-lwo","type":"parent-child","created_at":"2025-12-15T12:54:51.941635-05:00","created_by":"daemon"}]}
+{"id":"qmd-e2c","title":"Implement 'qmd ls' command","description":"Add command to explore virtual file tree:\n- qmd ls → list all collections\n- qmd ls \u003ccollection\u003e → list files in collection\n- qmd ls \u003ccollection\u003e/\u003cpath\u003e → list files under path\nOutput: flat list of qmd:// paths","status":"closed","priority":1,"issue_type":"feature","created_at":"2025-12-12T15:29:53.859804-05:00","updated_at":"2025-12-12T15:55:12.777701-05:00","closed_at":"2025-12-12T15:55:12.777701-05:00","dependencies":[{"issue_id":"qmd-e2c","depends_on_id":"qmd-ama","type":"discovered-from","created_at":"2025-12-12T15:29:53.860535-05:00","created_by":"daemon","metadata":"{}"}]}
+{"id":"qmd-gbt","title":"Add docid field (first 6 chars of hash) to search results","description":"Include docid formatted as #hash[0:5] in all search output formats (CLI, JSON, CSV, Markdown, XML, files). The docid should be the first 6 characters of the document's SHA256 hash.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-15T12:54:30.388094-05:00","updated_at":"2025-12-15T13:12:27.553492-05:00","closed_at":"2025-12-15T13:12:27.553492-05:00","dependencies":[{"issue_id":"qmd-gbt","depends_on_id":"qmd-lwo","type":"parent-child","created_at":"2025-12-15T12:54:51.846848-05:00","created_by":"daemon","metadata":"{}"}]}
+{"id":"qmd-ht6","title":"Add --line-numbers CLI option for line-numbered output","description":"Add --line-numbers flag to CLI that formats output with line numbers: each line becomes \"{lineNum}: {content}\". Apply to get, multi-get, and search snippet output.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-15T12:54:30.51773-05:00","updated_at":"2025-12-15T13:12:38.810032-05:00","closed_at":"2025-12-15T13:12:38.810032-05:00","dependencies":[{"issue_id":"qmd-ht6","depends_on_id":"qmd-lwo","type":"parent-child","created_at":"2025-12-15T12:54:51.941635-05:00","created_by":"daemon","metadata":"{}"}]}
 {"id":"qmd-hw2","title":"Fix MCP resource listing and display paths","description":"Remove MCP resource listing, fix display paths to include collection name, handelize filenames during indexing, make line-numbers default for MCP snippets.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-16T12:00:02.815946-05:00","updated_at":"2025-12-16T12:26:17.690252-05:00","closed_at":"2025-12-16T12:26:17.690252-05:00"}
-{"id":"qmd-i3t","title":"Move context management DB operations to store.ts","description":"Move path_contexts INSERT/DELETE/SELECT operations from addContext(), listContexts(), removeContext() to store.ts. Create methods like insertContext(), deleteContext(), etc.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:36:21.561746-05:00","updated_at":"2025-12-12T16:48:57.271485-05:00","closed_at":"2025-12-12T16:48:57.271485-05:00","dependencies":[{"issue_id":"qmd-i3t","depends_on_id":"qmd-29c","type":"parent-child","created_at":"2025-12-12T16:37:02.866006-05:00","created_by":"daemon"}]}
+{"id":"qmd-i3t","title":"Move context management DB operations to store.ts","description":"Move path_contexts INSERT/DELETE/SELECT operations from addContext(), listContexts(), removeContext() to store.ts. Create methods like insertContext(), deleteContext(), etc.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:36:21.561746-05:00","updated_at":"2025-12-12T16:48:57.271485-05:00","closed_at":"2025-12-12T16:48:57.271485-05:00","dependencies":[{"issue_id":"qmd-i3t","depends_on_id":"qmd-29c","type":"parent-child","created_at":"2025-12-12T16:37:02.866006-05:00","created_by":"daemon","metadata":"{}"}]}
 {"id":"qmd-j9z","title":"Add unit tests for content addressable hashes","description":"add same file from multiple places and verify that they both point at same hash. drop one collection and the content stays.","status":"closed","priority":3,"issue_type":"task","created_at":"2025-12-12T15:39:15.459504-05:00","updated_at":"2025-12-12T16:21:35.473776-05:00","closed_at":"2025-12-12T16:21:35.473776-05:00"}
-{"id":"qmd-kf8","title":"Move document indexing DB operations to store.ts","description":"Move INSERT/UPDATE/DELETE operations for documents and content tables from indexFiles() to store.ts. Create methods like insertDocument(), updateDocument(), deactivateDocuments(), etc.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:36:14.558702-05:00","updated_at":"2025-12-12T16:45:38.830978-05:00","closed_at":"2025-12-12T16:45:38.830978-05:00","dependencies":[{"issue_id":"qmd-kf8","depends_on_id":"qmd-29c","type":"parent-child","created_at":"2025-12-12T16:37:02.770251-05:00","created_by":"daemon"}]}
+{"id":"qmd-kf8","title":"Move document indexing DB operations to store.ts","description":"Move INSERT/UPDATE/DELETE operations for documents and content tables from indexFiles() to store.ts. Create methods like insertDocument(), updateDocument(), deactivateDocuments(), etc.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:36:14.558702-05:00","updated_at":"2025-12-12T16:45:38.830978-05:00","closed_at":"2025-12-12T16:45:38.830978-05:00","dependencies":[{"issue_id":"qmd-kf8","depends_on_id":"qmd-29c","type":"parent-child","created_at":"2025-12-12T16:37:02.770251-05:00","created_by":"daemon","metadata":"{}"}]}
 {"id":"qmd-ltg","title":"look for missing context","description":"i ran qmd context list and thats only one bit of context, i had a lot more. i think the path matching isn't quite working right","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:42:57.324769-05:00","updated_at":"2025-12-12T17:16:27.835047-05:00","closed_at":"2025-12-12T17:16:27.835047-05:00"}
 {"id":"qmd-lwo","title":"Add short docid hash references and line numbers to output","description":"Add short 6-character hash IDs (docid #abc123) to search results and document retrieval, support these IDs in get/multi-get commands, and add --line-numbers option for line-numbered output.","status":"closed","priority":1,"issue_type":"epic","created_at":"2025-12-15T12:54:30.335556-05:00","updated_at":"2025-12-15T13:12:43.758557-05:00","closed_at":"2025-12-15T13:12:43.758557-05:00"}
-{"id":"qmd-mro","title":"Add lineNumbers boolean to MCP tools","description":"Add lineNumbers boolean parameter to MCP get, multi_get, and search tools. When true, return content with line numbers prefixed to each line.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-15T12:54:30.581671-05:00","updated_at":"2025-12-15T13:12:38.8682-05:00","closed_at":"2025-12-15T13:12:38.8682-05:00","dependencies":[{"issue_id":"qmd-mro","depends_on_id":"qmd-lwo","type":"parent-child","created_at":"2025-12-15T12:54:51.970363-05:00","created_by":"daemon"}]}
-{"id":"qmd-oxy","title":"Update context system to use YAML","description":"Remove path_contexts table. Implement context management in collections.ts. Update context add/list/rm commands to modify YAML file instead of database.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:54:53.042839-05:00","updated_at":"2025-12-13T10:16:07.680285-05:00","closed_at":"2025-12-13T10:16:07.680285-05:00","dependencies":[{"issue_id":"qmd-oxy","depends_on_id":"qmd-3z9","type":"blocks","created_at":"2025-12-13T09:55:07.842488-05:00","created_by":"daemon"}]}
-{"id":"qmd-p1h","title":"Create collection add|remove","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-10T10:57:00.717864-05:00","updated_at":"2025-12-12T16:12:00.557003-05:00","closed_at":"2025-12-12T16:12:00.557003-05:00"}
-{"id":"qmd-rck","title":"move the source files to src/*, clean up teh directory","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:40:19.198119-05:00","updated_at":"2025-12-12T17:12:22.502746-05:00","closed_at":"2025-12-12T17:12:22.502746-05:00"}
-{"id":"qmd-rhd","title":"Fix 'qmd status' output for new schema","description":"Update status to show collections by name, cleaner context display, virtual path examples.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T15:29:54.020596-05:00","updated_at":"2025-12-12T16:13:28.08389-05:00","closed_at":"2025-12-12T16:13:28.08389-05:00","dependencies":[{"issue_id":"qmd-rhd","depends_on_id":"qmd-ama","type":"discovered-from","created_at":"2025-12-12T15:29:54.021095-05:00","created_by":"daemon"}]}
+{"id":"qmd-mro","title":"Add lineNumbers boolean to MCP tools","description":"Add lineNumbers boolean parameter to MCP get, multi_get, and search tools. When true, return content with line numbers prefixed to each line.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-15T12:54:30.581671-05:00","updated_at":"2025-12-15T13:12:38.8682-05:00","closed_at":"2025-12-15T13:12:38.8682-05:00","dependencies":[{"issue_id":"qmd-mro","depends_on_id":"qmd-lwo","type":"parent-child","created_at":"2025-12-15T12:54:51.970363-05:00","created_by":"daemon","metadata":"{}"}]}
+{"id":"qmd-oxy","title":"Update context system to use YAML","description":"Remove path_contexts table. Implement context management in collections.ts. Update context add/list/rm commands to modify YAML file instead of database.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:54:53.042839-05:00","updated_at":"2025-12-13T10:16:07.680285-05:00","closed_at":"2025-12-13T10:16:07.680285-05:00","dependencies":[{"issue_id":"qmd-oxy","depends_on_id":"qmd-3z9","type":"blocks","created_at":"2025-12-13T09:55:07.842488-05:00","created_by":"daemon","metadata":"{}"}]}
+{"id":"qmd-p1h","title":"Create collection add|remove","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-10T10:57:00.717864-05:00","updated_at":"2025-12-12T16:12:00.557003-05:00","closed_at":"2025-12-12T16:12:00.557003-05:00"}
+{"id":"qmd-rck","title":"move the source files to src/*, clean up teh directory","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T16:40:19.198119-05:00","updated_at":"2025-12-12T17:12:22.502746-05:00","closed_at":"2025-12-12T17:12:22.502746-05:00"}
+{"id":"qmd-rhd","title":"Fix 'qmd status' output for new schema","description":"Update status to show collections by name, cleaner context display, virtual path examples.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T15:29:54.020596-05:00","updated_at":"2025-12-12T16:13:28.08389-05:00","closed_at":"2025-12-12T16:13:28.08389-05:00","dependencies":[{"issue_id":"qmd-rhd","depends_on_id":"qmd-ama","type":"discovered-from","created_at":"2025-12-12T15:29:54.021095-05:00","created_by":"daemon","metadata":"{}"}]}
 {"id":"qmd-s1y","title":"Update 'qmd add-context' for collection scoping","description":"Update add-context to work with collection-scoped contexts using new path_contexts schema.","notes":"Refactoring to:\n- qmd context add [path] \"text\" (defaults to current collection if in one)\n- qmd context list\n- qmd context rm \u003cpath\u003e\n- Support \"/\" for global/system context\n- Auto-detect collection from pwd","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T15:29:54.076582-05:00","updated_at":"2025-12-12T15:37:47.683263-05:00","closed_at":"2025-12-12T15:37:47.683263-05:00"}
-{"id":"qmd-thw","title":"Drop collections and path_contexts tables","description":"Remove collections and path_contexts tables from schema. Update initDb() to not create these tables. Only keep documents, content, and search indices.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:54:53.247136-05:00","updated_at":"2025-12-13T10:27:06.54374-05:00","closed_at":"2025-12-13T10:27:06.54374-05:00","dependencies":[{"issue_id":"qmd-thw","depends_on_id":"qmd-9ua","type":"blocks","created_at":"2025-12-13T09:55:08.027101-05:00","created_by":"daemon"}]}
-{"id":"qmd-u84","title":"Refactor store.ts to use collections.ts","description":"Replace all collection DB queries with collections.ts calls. Remove getCollectionById, getCollectionByName, listCollections DB functions. Use YAML config instead.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:54:52.936782-05:00","updated_at":"2025-12-13T10:16:07.681047-05:00","closed_at":"2025-12-13T10:16:07.681047-05:00","dependencies":[{"issue_id":"qmd-u84","depends_on_id":"qmd-3z9","type":"blocks","created_at":"2025-12-13T09:55:07.720439-05:00","created_by":"daemon"},{"issue_id":"qmd-u84","depends_on_id":"qmd-8eu","type":"blocks","created_at":"2025-12-13T09:55:07.782051-05:00","created_by":"daemon"}]}
-{"id":"qmd-vro","title":"Update 'qmd get' to support virtual paths","description":"Allow qmd get to accept both virtual paths (qmd://journals/...) and filesystem paths, plus fuzzy matching by filename.","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-12T15:29:53.963113-05:00","updated_at":"2025-12-12T15:47:29.178955-05:00","closed_at":"2025-12-12T15:47:29.178955-05:00","dependencies":[{"issue_id":"qmd-vro","depends_on_id":"qmd-ama","type":"discovered-from","created_at":"2025-12-12T15:29:53.963641-05:00","created_by":"daemon"}]}
+{"id":"qmd-thw","title":"Drop collections and path_contexts tables","description":"Remove collections and path_contexts tables from schema. Update initDb() to not create these tables. Only keep documents, content, and search indices.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:54:53.247136-05:00","updated_at":"2025-12-13T10:27:06.54374-05:00","closed_at":"2025-12-13T10:27:06.54374-05:00","dependencies":[{"issue_id":"qmd-thw","depends_on_id":"qmd-9ua","type":"blocks","created_at":"2025-12-13T09:55:08.027101-05:00","created_by":"daemon","metadata":"{}"}]}
+{"id":"qmd-u84","title":"Refactor store.ts to use collections.ts","description":"Replace all collection DB queries with collections.ts calls. Remove getCollectionById, getCollectionByName, listCollections DB functions. Use YAML config instead.","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:54:52.936782-05:00","updated_at":"2025-12-13T10:16:07.681047-05:00","closed_at":"2025-12-13T10:16:07.681047-05:00","dependencies":[{"issue_id":"qmd-u84","depends_on_id":"qmd-3z9","type":"blocks","created_at":"2025-12-13T09:55:07.720439-05:00","created_by":"daemon","metadata":"{}"},{"issue_id":"qmd-u84","depends_on_id":"qmd-8eu","type":"blocks","created_at":"2025-12-13T09:55:07.782051-05:00","created_by":"daemon","metadata":"{}"}]}
+{"id":"qmd-vro","title":"Update 'qmd get' to support virtual paths","description":"Allow qmd get to accept both virtual paths (qmd://journals/...) and filesystem paths, plus fuzzy matching by filename.","status":"closed","priority":0,"issue_type":"task","created_at":"2025-12-12T15:29:53.963113-05:00","updated_at":"2025-12-12T15:47:29.178955-05:00","closed_at":"2025-12-12T15:47:29.178955-05:00","dependencies":[{"issue_id":"qmd-vro","depends_on_id":"qmd-ama","type":"discovered-from","created_at":"2025-12-12T15:29:53.963641-05:00","created_by":"daemon","metadata":"{}"}]}
 {"id":"qmd-x19","title":"Update 'qmd add-context' for collection-scoped contexts","description":"Update add-context to work with collections:\n- qmd add-context \u003ccollection\u003e/\u003cpath\u003e \"context description\"\n- Support both virtual and filesystem paths\n- Update to use new path_contexts schema","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T15:29:38.142575-05:00","updated_at":"2025-12-12T15:53:00.525001-05:00","closed_at":"2025-12-12T15:53:00.525001-05:00"}
-{"id":"qmd-x64","title":"for each collection, on update, check if there is a .git directory, if so write out the git status, add --pull as a qmd update --pull parameter which also executes git pull before reindexing\n","description":"","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T17:04:15.994054-05:00","updated_at":"2025-12-12T17:14:40.107181-05:00","closed_at":"2025-12-12T17:14:40.107181-05:00"}
+{"id":"qmd-x64","title":"for each collection, on update, check if there is a .git directory, if so write out the git status, add --pull as a qmd update --pull parameter which also executes git pull before reindexing\n","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-12T17:04:15.994054-05:00","updated_at":"2025-12-12T17:14:40.107181-05:00","closed_at":"2025-12-12T17:14:40.107181-05:00"}
 {"id":"qmd-yzj","title":"Add optional update: command support to collections YAML","description":"Collections can now specify an optional 'update:' key with a bash command that will be executed during 'qmd update' before indexing files. Runs in cwd, prints output, stops on error.","status":"closed","priority":2,"issue_type":"feature","created_at":"2025-12-13T11:16:32.527608-05:00","updated_at":"2025-12-13T11:17:57.500434-05:00","closed_at":"2025-12-13T11:17:57.500434-05:00"}
 {"id":"qmd-zin","title":"Improve qmd ls command to be more like ls -l with colors","description":"Make qmd ls more Unix-like:\n1. Format like ls -l with columns (permissions, size, date, name)\n2. Add colors (directories, files, etc.)\n3. Dim the qmd:// prefix to show it's optional\n4. Show file sizes in human-readable format\n5. Show modification times\n6. Consider adding -l flag for long format","status":"closed","priority":1,"issue_type":"task","created_at":"2025-12-13T09:44:48.703843-05:00","updated_at":"2025-12-13T09:48:22.298822-05:00","closed_at":"2025-12-13T09:48:22.298822-05:00"}

+ 46 - 5
qmd

@@ -1,14 +1,55 @@
-#!/bin/bash
+#!/usr/bin/env bash
 # qmd - Quick Markdown Search
-# Run with: ./qmd or symlink to PATH
+set -euo pipefail
 
-# Resolve symlinks to find actual script location
+# Find bun - prefer PATH, fallback to known locations
+find_bun() {
+  # First: check if bun is in PATH and modern enough
+  if command -v bun &>/dev/null; then
+    local ver=$(bun --version 2>/dev/null || echo "0")
+    if [[ "$ver" =~ ^1\. ]]; then
+      command -v bun
+      return 0
+    fi
+  fi
+
+  # Fallback: derive paths (need HOME)
+  : "${HOME:=$(eval echo ~)}"
+
+  # If running from .bun tree, use that bun
+  if [[ "${BASH_SOURCE[0]}" == */.bun/* ]]; then
+    local bun_home="${BASH_SOURCE[0]%%/.bun/*}/.bun"
+    if [[ -x "$bun_home/bin/bun" ]]; then
+      echo "$bun_home/bin/bun"
+      return 0
+    fi
+  fi
+
+  # Check known locations
+  local candidates=(
+    "$HOME/.local/share/mise/installs/bun/latest/bin/bun"
+    "$HOME/.local/share/mise/shims/bun"
+    "$HOME/.asdf/shims/bun"
+    "/opt/homebrew/bin/bun"
+    "/usr/local/bin/bun"
+    "$HOME/.bun/bin/bun"
+  )
+  for c in "${candidates[@]}"; do
+    [[ -x "$c" ]] && { echo "$c"; return 0; }
+  done
+
+  return 1
+}
+
+BUN=$(find_bun) || { echo "Error: bun not found. Install from https://bun.sh" >&2; exit 1; }
+
+# Resolve symlinks to find script location
 SOURCE="${BASH_SOURCE[0]}"
-while [ -L "$SOURCE" ]; do
+while [[ -L "$SOURCE" ]]; do
   DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
   SOURCE="$(readlink "$SOURCE")"
   [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
 done
 SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
 
-exec bun "$SCRIPT_DIR/src/qmd.ts" "$@"
+exec "$BUN" "$SCRIPT_DIR/src/qmd.ts" "$@"

+ 82 - 102
src/llm.ts

@@ -120,12 +120,16 @@ export type RerankOptions = {
 };
 
 /**
- * Structured query expansion result
+ * Supported query types for different search backends
  */
-export type ExpandedQuery = {
-  lexicalQuery: string | null;  // Alternative query for BM25/keyword search
-  vectorQuery: string;          // Alternative query for semantic search
-  hyde: string;                 // Hypothetical document that would answer the query
+export type QueryType = 'lex' | 'vec' | 'hyde';
+
+/**
+ * A single query and its target backend type
+ */
+export type Queryable = {
+  type: QueryType;
+  text: string;
 };
 
 /**
@@ -145,7 +149,8 @@ export type RerankDocument = {
 // Format: hf:<user>/<repo>/<file>
 const DEFAULT_EMBED_MODEL = "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf";
 const DEFAULT_RERANK_MODEL = "hf:ggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF/qwen3-reranker-0.6b-q8_0.gguf";
-const DEFAULT_GENERATE_MODEL = "hf:ggml-org/Qwen3-0.6B-GGUF/Qwen3-0.6B-Q8_0.gguf";
+// const DEFAULT_GENERATE_MODEL = "hf:ggml-org/Qwen3-0.6B-GGUF/Qwen3-0.6B-Q8_0.gguf";
+const DEFAULT_GENERATE_MODEL = "hf:ggml-org/Qwen3-1.7B-GGUF/Qwen3-1.7b-q8_0.gguf";
 
 // Local model cache directory
 const MODEL_CACHE_DIR = join(homedir(), ".cache", "qmd", "models");
@@ -174,9 +179,10 @@ export interface LLM {
   modelExists(model: string): Promise<ModelInfo>;
 
   /**
-   * Expand a search query into multiple variations
+   * Expand a search query into multiple variations for different backends.
+   * Returns a list of Queryable objects.
    */
-  expandQuery(query: string, numVariations?: number): Promise<string[]>;
+  expandQuery(query: string, options?: { context?: string, includeLexical?: boolean }): Promise<Queryable[]>;
 
   /**
    * Rerank documents by relevance to a query
@@ -613,125 +619,96 @@ export class LlamaCpp implements LLM {
   // High-level abstractions
   // ==========================================================================
 
-  async expandQuery(query: string, numVariations: number = 2): Promise<string[]> {
-    const prompt = `You are a search query expander. Given a search query, generate ${numVariations} alternative queries that would help find relevant documents.
-
-Rules:
-- Use synonyms and related terminology
-- Rephrase to capture different angles
-- Keep proper nouns exactly as written
-- Each variation should be 3-8 words, natural search terms
-- Do NOT append words like "search" or "find"
-
-Query: "${query}"
+  async expandQuery(query: string, options: { context?: string, includeLexical?: boolean } = {}): Promise<Queryable[]> {
+    const llama = await this.ensureLlama();
+    await this.ensureGenerateModel();
 
-Output exactly ${numVariations} variations, one per line, no numbering or bullets:`;
+    const includeLexical = options.includeLexical ?? true;
+    const context = options.context;
 
-    const result = await this.generate(prompt, {
-      maxTokens: 150,
-      temperature: 0,
+    const grammar = await llama.createGrammar({
+      grammar: `
+        root ::= line+
+        line ::= type ": " content "\\n"
+        type ::= "lex" | "vec" | "hyde"
+        content ::= [^\\n]+
+      `
     });
 
-    if (!result) {
-      return [query];
-    }
+    const prompt = `You are a search query optimization expert. Your task is to improve retrieval by rewriting queries and generating hypothetical documents.
 
-    // Parse response - filter out thinking tags and clean up
-    const cleanText = result.text.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
-    const lines = cleanText
-      .split("\n")
-      .map((l) => l.trim())
-      .filter((l) => l.length > 2 && l.length < 100 && !l.startsWith("<"));
+Original Query: ${query}
 
-    return [query, ...lines.slice(0, numVariations)];
-  }
+${context ? `Additional Context, ONLY USE IF RELEVANT:\n\n<context>${context}</context>` : ""}
 
-  /**
-   * Expand query using structured output with JSON schema grammar.
-   * Returns different query types optimized for different retrieval methods.
-   *
-   * @param query - Original search query
-   * @param includeLexical - Whether to include lexical query (false for vector-only search)
-   */
-  async expandQueryStructured(query: string, includeLexical: boolean = true): Promise<ExpandedQuery> {
-    const llama = await this.ensureLlama();
-    await this.ensureGenerateModel();
+## Step 1: Query Analysis
+Identify entities, search intent, and missing context.
 
-    // Define JSON schema for structured output
-    const schema = {
-      type: "object" as const,
-      properties: {
-        lexicalQuery: {
-          type: "string" as const,
-          description: "Alternative keyword-based query using synonyms (3-6 words)"
-        },
-        vectorQuery: {
-          type: "string" as const,
-          description: "Semantically rephrased query capturing the intent (5-10 words)"
-        },
-        hyde: {
-          type: "string" as const,
-          description: "Write a short passage (50-100 words) that directly answers the query as if from a relevant document"
-        }
-      },
-      required: [] as const
-    };
+## Step 2: Generate Hypothetical Document
+Write a focused sentence passage that would answer the query. Include specific terminology and domain vocabulary.
 
-    const grammar = await llama.createGrammarForJsonSchema(schema);
+## Step 3: Query Rewrites
+Generate 2-3 alternative search queries that resolve ambiguities. Use terminology from the hypothetical document.
 
-    const systemPrompt = includeLexical
-      ? `You expand search queries into structured alternatives for a hybrid search system.
-Given a query, generate:
-1. lexicalQuery: Alternative keywords using synonyms (for BM25 keyword search)
-2. vectorQuery: Semantically rephrased query (for vector/embedding search)
-3. hyde: Write a brief example passage (50-100 words) that answers the query, as if excerpted from a relevant document
+## Step 4: Final Retrieval Text
+Output exactly 1-3 'lex' lines, 1-3 'vec' lines, and MAX ONE 'hyde' line.
 
-Keep proper nouns exactly as written. Be concise.`
-      : `You expand search queries for semantic search.
-Given a query, generate:
-1. vectorQuery: Semantically rephrased query capturing the full intent (must be different from the original query)
-2. HyDE: Write a brief example passage (50-100 words) that answers the query, as if excerpted from a relevant document
+<format>
+lex: {single search term}
+vec: {single vector query}
+hyde: {complete hypothetical document passage from Step 2 on a SINGLE LINE}
+</format>
 
-Keep proper nouns exactly as written. Be concise.`;
+<example>
+Example (FOR FORMAT ONLY - DO NOT COPY THIS CONTENT):
+lex: example keyword 1
+lex: example keyword 2
+vec: example semantic query
+hyde: This is an example of a hypothetical document passage that would answer the example query. It contains multiple sentences and relevant vocabulary.
+</example>
 
-    const prompt = `Query: "${query}"
+<rules>
+- DO NOT repeat the same line.
+- Each 'lex:' line MUST be a different keyword variation based on the ORIGINAL QUERY.
+- Each 'vec:' line MUST be a different semantic variation based on the ORIGINAL QUERY.
+- The 'hyde:' line MUST be the full sentence passage from Step 2, but all on one line.
+- DO NOT use the example content above.
+${!includeLexical ? "- Do NOT output any 'lex:' lines" : ""}
+</rules>
 
-Generate the structured expansion:`;
+Final Output:`;
 
     // Create fresh context for each call
-    const context = await this.generateModel!.createContext();
-    const sequence = context.getSequence();
-    const session = new LlamaChatSession({ contextSequence: sequence, systemPrompt });
+    const genContext = await this.generateModel!.createContext();
+    const sequence = genContext.getSequence();
+    const session = new LlamaChatSession({ contextSequence: sequence });
 
     try {
       const result = await session.prompt(prompt, {
         grammar,
-        maxTokens: 500,
-        temperature: 0,
+        maxTokens: 1000,
+        temperature: 1,
       });
 
-      const parsed = grammar.parse(result) as {
-        lexicalQuery?: string;
-        vectorQuery: string;
-        hyde: string;
-      };
-
-      return {
-        lexicalQuery: includeLexical && parsed.lexicalQuery ? parsed.lexicalQuery : null,
-        vectorQuery: parsed.vectorQuery || query,
-        hyde: parsed.hyde || "",
-      };
+      const lines = result.trim().split("\n");
+      const queryables: Queryable[] = lines.map(line => {
+        const colonIdx = line.indexOf(":");
+        if (colonIdx === -1) return null;
+        const type = line.slice(0, colonIdx).trim();
+        if (type !== 'lex' && type !== 'vec' && type !== 'hyde') return null;
+        const text = line.slice(colonIdx + 1).trim();
+        return { type: type as QueryType, text };
+      }).filter((q): q is Queryable => q !== null);
+
+      return queryables;
     } catch (error) {
       console.error("Structured query expansion failed:", error);
       // Fallback to original query
-      return {
-        lexicalQuery: includeLexical ? query : null,
-        vectorQuery: query,
-        hyde: "",
-      };
+      const fallback: Queryable[] = [{ type: 'vec', text: query }];
+      if (includeLexical) fallback.unshift({ type: 'lex', text: query });
+      return fallback;
     } finally {
-      // Dispose context (disposes session too per lifecycle rules)
-      await context.dispose();
+      await genContext.dispose();
     }
   }
 
@@ -785,8 +762,11 @@ Generate the structured expansion:`;
 
     // Disposing llama cascades to models and contexts automatically
     // See: https://node-llama-cpp.withcat.ai/guide/objects-lifecycle
+    // Note: llama.dispose() can hang indefinitely, so we use a timeout
     if (this.llama) {
-      await this.llama.dispose();
+      const disposePromise = this.llama.dispose();
+      const timeoutPromise = new Promise<void>((resolve) => setTimeout(resolve, 1000));
+      await Promise.race([disposePromise, timeoutPromise]);
     }
 
     // Clear references

+ 3 - 0
src/qmd.ts

@@ -1908,6 +1908,8 @@ function search(query: string, opts: OutputOptions): void {
     body: r.body || "",
     score: r.score,
     context: getContextForFile(db, r.filepath),
+    hash: r.hash,
+    docid: r.docid,
   }));
 
   closeDb();
@@ -2619,5 +2621,6 @@ if (import.meta.main) {
 
   // Cleanup LlamaCpp instance to prevent NAPI crash on exit
   await disposeDefaultLlamaCpp();
+  process.exit(0);
 
 } // end if (import.meta.main)

+ 12 - 10
src/store.ts

@@ -43,7 +43,7 @@ import {
 const HOME = Bun.env.HOME || "/tmp";
 export const DEFAULT_EMBED_MODEL = "embeddinggemma";
 export const DEFAULT_RERANK_MODEL = "ExpedientFalcon/qwen3-reranker:0.6b-q8_0";
-export const DEFAULT_QUERY_MODEL = "qwen3:0.6b";
+export const DEFAULT_QUERY_MODEL = "Qwen/Qwen3-1.7B";
 export const DEFAULT_GLOB = "**/*.md";
 export const DEFAULT_MULTI_GET_MAX_BYTES = 10 * 1024; // 10KB
 
@@ -106,7 +106,7 @@ export function getDefaultDbPath(indexName: string = "index"): string {
 
   const cacheDir = Bun.env.XDG_CACHE_HOME || resolve(homedir(), ".cache");
   const qmdCacheDir = resolve(cacheDir, "qmd");
-  try { Bun.spawnSync(["mkdir", "-p", qmdCacheDir]); } catch {}
+  try { Bun.spawnSync(["mkdir", "-p", qmdCacheDir]); } catch { }
   return resolve(qmdCacheDir, `${indexName}.sqlite`);
 }
 
@@ -120,7 +120,7 @@ export function getRealPath(path: string): string {
     if (result.success) {
       return result.stdout.toString().trim();
     }
-  } catch {}
+  } catch { }
   return resolve(path);
 }
 
@@ -270,7 +270,7 @@ if (process.platform === "darwin") {
     if (Bun.file(homebrewSqlitePath).size > 0) {
       Database.setCustomSQLite(homebrewSqlitePath);
     }
-  } catch {}
+  } catch { }
 }
 
 function initializeDatabase(db: Database): void {
@@ -1665,7 +1665,7 @@ export function searchFTS(db: Database, query: string, limit: number = 20, colle
   `;
   const params: (string | number)[] = [ftsQuery];
 
-  if (collectionId !== undefined) {
+  if (collectionId) {
     // Note: collectionId is a legacy parameter that should be phased out
     // Collections are now managed in YAML. For now, we interpret it as a collection name filter.
     // This code path is likely unused as collection filtering should be done at CLI level.
@@ -1729,7 +1729,7 @@ export async function searchVec(db: Database, query: string, model: string, limi
     WHERE v.embedding MATCH ? AND k = ?
   `;
 
-  if (collectionId !== undefined) {
+  if (collectionId) {
     // Note: collectionId is a legacy parameter that should be phased out
     // Collections are now managed in YAML. For now, we interpret it as a collection name filter.
     sql += ` AND d.collection = ?`;
@@ -1843,14 +1843,16 @@ export async function expandQuery(query: string, model: string = DEFAULT_QUERY_M
 
   const llm = getDefaultLlamaCpp();
   // Note: LlamaCpp uses hardcoded model, model parameter is ignored
-  const results = await llm.expandQuery(query, 2);
+  const results = await llm.expandQuery(query);
+  const queryTexts = results.map(r => r.text);
 
   // Cache the expanded queries (excluding original)
-  if (results.length > 1) {
-    setCachedResult(db, cacheKey, results.slice(1).join('\n'));
+  const expandedOnly = queryTexts.filter(t => t !== query);
+  if (expandedOnly.length > 0) {
+    setCachedResult(db, cacheKey, expandedOnly.join('\n'));
   }
 
-  return results;
+  return Array.from(new Set([query, ...queryTexts]));
 }
 
 // =============================================================================