tobi/qmdPublic

mini cli search engine for your docs, knowledge bases, meeting notes, whatever. Tracking current sota approaches while being all local

AI summary: A local-first, minimal CLI search engine for querying documentation and personal knowledge bases.

Stars
28.6K
+42 today
Forks
1.8K
Watchers
99
Open issues
87
Open PRs
89
Contributors
~83
Commits
582
Branches
32

TypeScriptMITCreated Dec 8, 2025Last push 1mo agoLatest release v2.5.3+222 stars this week+222 this month

Star history

since Dec 7, 2025
010K20KDec 2025Feb 2026May 2026Aug 2026
28.6K stars as of Aug 6, 2026, tracked back to Dec 7, 2025. Historical curve reconstructed from public GitHub event archives, calibrated to the current total.

Contribution activity

commits per day, last 52 weeks
AugSepOctNovDecJanFebMarAprMayJunJulMonWedFri2025-08-02: 0 commits2025-08-03: 0 commits2025-08-04: 0 commits2025-08-05: 0 commits2025-08-06: 0 commits2025-08-07: 0 commits2025-08-08: 0 commits2025-08-09: 0 commits2025-08-10: 0 commits2025-08-11: 0 commits2025-08-12: 0 commits2025-08-13: 0 commits2025-08-14: 0 commits2025-08-15: 0 commits2025-08-16: 0 commits2025-08-17: 0 commits2025-08-18: 0 commits2025-08-19: 0 commits2025-08-20: 0 commits2025-08-21: 0 commits2025-08-22: 0 commits2025-08-23: 0 commits2025-08-24: 0 commits2025-08-25: 0 commits2025-08-26: 0 commits2025-08-27: 0 commits2025-08-28: 0 commits2025-08-29: 0 commits2025-08-30: 0 commits2025-08-31: 0 commits2025-09-01: 0 commits2025-09-02: 0 commits2025-09-03: 0 commits2025-09-04: 0 commits2025-09-05: 0 commits2025-09-06: 0 commits2025-09-07: 0 commits2025-09-08: 0 commits2025-09-09: 0 commits2025-09-10: 0 commits2025-09-11: 0 commits2025-09-12: 0 commits2025-09-13: 0 commits2025-09-14: 0 commits2025-09-15: 0 commits2025-09-16: 0 commits2025-09-17: 0 commits2025-09-18: 0 commits2025-09-19: 0 commits2025-09-20: 0 commits2025-09-21: 0 commits2025-09-22: 0 commits2025-09-23: 0 commits2025-09-24: 0 commits2025-09-25: 0 commits2025-09-26: 0 commits2025-09-27: 0 commits2025-09-28: 0 commits2025-09-29: 0 commits2025-09-30: 0 commits2025-10-01: 0 commits2025-10-02: 0 commits2025-10-03: 0 commits2025-10-04: 0 commits2025-10-05: 0 commits2025-10-06: 0 commits2025-10-07: 0 commits2025-10-08: 0 commits2025-10-09: 0 commits2025-10-10: 0 commits2025-10-11: 0 commits2025-10-12: 0 commits2025-10-13: 0 commits2025-10-14: 0 commits2025-10-15: 0 commits2025-10-16: 0 commits2025-10-17: 0 commits2025-10-18: 0 commits2025-10-19: 0 commits2025-10-20: 0 commits2025-10-21: 0 commits2025-10-22: 0 commits2025-10-23: 0 commits2025-10-24: 0 commits2025-10-25: 0 commits2025-10-26: 0 commits2025-10-27: 0 commits2025-10-28: 0 commits2025-10-29: 0 commits2025-10-30: 0 commits2025-10-31: 0 commits2025-11-01: 0 commits2025-11-02: 0 commits2025-11-03: 0 commits2025-11-04: 0 commits2025-11-05: 0 commits2025-11-06: 0 commits2025-11-07: 0 commits2025-11-09: 0 commits2025-11-10: 0 commits2025-11-11: 0 commits2025-11-12: 0 commits2025-11-13: 0 commits2025-11-14: 0 commits2025-11-15: 0 commits2025-11-16: 0 commits2025-11-17: 0 commits2025-11-18: 0 commits2025-11-19: 0 commits2025-11-20: 0 commits2025-11-21: 0 commits2025-11-22: 0 commits2025-11-23: 0 commits2025-11-24: 0 commits2025-11-25: 0 commits2025-11-26: 0 commits2025-11-27: 0 commits2025-11-28: 0 commits2025-11-29: 0 commits2025-11-30: 0 commits2025-12-01: 0 commits2025-12-02: 0 commits2025-12-03: 0 commits2025-12-04: 0 commits2025-12-05: 0 commits2025-12-06: 0 commits2025-12-07: 3 commits2025-12-08: 8 commits2025-12-09: 2 commits2025-12-10: 1 commit2025-12-11: 0 commits2025-12-12: 26 commits2025-12-13: 33 commits2025-12-14: 1 commit2025-12-15: 0 commits2025-12-16: 3 commits2025-12-17: 0 commits2025-12-18: 0 commits2025-12-19: 0 commits2025-12-20: 5 commits2025-12-21: 15 commits2025-12-22: 0 commits2025-12-23: 0 commits2025-12-24: 0 commits2025-12-25: 0 commits2025-12-26: 0 commits2025-12-27: 0 commits2025-12-28: 0 commits2025-12-29: 0 commits2025-12-30: 0 commits2025-12-31: 1 commit2026-01-01: 0 commits2026-01-02: 0 commits2026-01-03: 1 commit2026-01-04: 2 commits2026-01-05: 0 commits2026-01-06: 0 commits2026-01-07: 0 commits2026-01-08: 2 commits2026-01-09: 2 commits2026-01-10: 0 commits2026-01-11: 3 commits2026-01-12: 0 commits2026-01-13: 1 commit2026-01-14: 0 commits2026-01-15: 0 commits2026-01-16: 0 commits2026-01-17: 0 commits2026-01-18: 0 commits2026-01-19: 1 commit2026-01-20: 3 commits2026-01-21: 0 commits2026-01-22: 0 commits2026-01-23: 3 commits2026-01-24: 4 commits2026-01-25: 15 commits2026-01-26: 2 commits2026-01-27: 1 commit2026-01-28: 8 commits2026-01-29: 0 commits2026-01-30: 2 commits2026-01-31: 17 commits2026-02-01: 4 commits2026-02-02: 1 commit2026-02-03: 0 commits2026-02-04: 0 commits2026-02-05: 0 commits2026-02-06: 0 commits2026-02-07: 0 commits2026-02-08: 0 commits2026-02-09: 0 commits2026-02-10: 1 commit2026-02-11: 0 commits2026-02-12: 0 commits2026-02-13: 7 commits2026-02-14: 1 commit2026-02-15: 25 commits2026-02-16: 13 commits2026-02-17: 5 commits2026-02-18: 15 commits2026-02-19: 12 commits2026-02-20: 6 commits2026-02-21: 1 commit2026-02-22: 8 commits2026-02-23: 1 commit2026-02-24: 2 commits2026-02-25: 0 commits2026-02-26: 0 commits2026-02-27: 0 commits2026-02-28: 0 commits2026-03-01: 1 commit2026-03-02: 0 commits2026-03-03: 1 commit2026-03-04: 0 commits2026-03-05: 1 commit2026-03-06: 8 commits2026-03-07: 9 commits2026-03-08: 1 commit2026-03-09: 2 commits2026-03-10: 13 commits2026-03-11: 4 commits2026-03-12: 3 commits2026-03-13: 7 commits2026-03-14: 0 commits2026-03-15: 0 commits2026-03-16: 0 commits2026-03-17: 0 commits2026-03-18: 0 commits2026-03-19: 0 commits2026-03-20: 0 commits2026-03-21: 1 commit2026-03-22: 1 commit2026-03-23: 2 commits2026-03-24: 5 commits2026-03-25: 0 commits2026-03-26: 3 commits2026-03-27: 2 commits2026-03-28: 1 commit2026-03-29: 1 commit2026-03-30: 0 commits2026-03-31: 0 commits2026-04-01: 0 commits2026-04-02: 0 commits2026-04-03: 6 commits2026-04-04: 0 commits2026-04-05: 30 commits2026-04-06: 1 commit2026-04-07: 4 commits2026-04-08: 1 commit2026-04-09: 4 commits2026-04-10: 1 commit2026-04-11: 3 commits2026-04-12: 0 commits2026-04-13: 0 commits2026-04-14: 1 commit2026-04-15: 0 commits2026-04-16: 0 commits2026-04-17: 0 commits2026-04-18: 0 commits2026-04-19: 0 commits2026-04-20: 0 commits2026-04-21: 1 commit2026-04-22: 0 commits2026-04-23: 2 commits2026-04-24: 0 commits2026-04-25: 0 commits2026-04-26: 0 commits2026-04-27: 0 commits2026-04-28: 0 commits2026-04-29: 0 commits2026-04-30: 0 commits2026-05-01: 0 commits2026-05-02: 0 commits2026-05-03: 0 commits2026-05-04: 0 commits2026-05-05: 0 commits2026-05-06: 0 commits2026-05-07: 0 commits2026-05-08: 0 commits2026-05-09: 18 commits2026-05-10: 0 commits2026-05-11: 1 commit2026-05-12: 0 commits2026-05-13: 2 commits2026-05-14: 0 commits2026-05-15: 1 commit2026-05-16: 13 commits2026-05-17: 0 commits2026-05-18: 3 commits2026-05-19: 10 commits2026-05-20: 5 commits2026-05-21: 0 commits2026-05-22: 5 commits2026-05-23: 0 commits2026-05-24: 1 commit2026-05-25: 1 commit2026-05-26: 0 commits2026-05-27: 0 commits2026-05-28: 8 commits2026-05-29: 0 commits2026-05-30: 0 commits2026-05-31: 1 commit2026-06-01: 2 commits2026-06-02: 0 commits2026-06-03: 0 commits2026-06-04: 0 commits2026-06-05: 1 commit2026-06-06: 0 commits2026-06-07: 1 commit2026-06-08: 1 commit2026-06-09: 1 commit2026-06-10: 0 commits2026-06-11: 1 commit2026-06-12: 2 commits2026-06-13: 1 commit2026-06-14: 2 commits2026-06-15: 0 commits2026-06-16: 0 commits2026-06-17: 1 commit2026-06-18: 0 commits2026-06-19: 0 commits2026-06-20: 1 commit2026-06-21: 0 commits2026-06-22: 1 commit2026-06-23: 0 commits2026-06-24: 2 commits2026-06-25: 0 commits2026-06-26: 0 commits2026-06-27: 0 commits2026-06-28: 0 commits2026-06-29: 0 commits2026-06-30: 0 commits2026-07-01: 0 commits2026-07-02: 0 commits2026-07-03: 0 commits2026-07-04: 0 commits2026-07-05: 0 commits2026-07-06: 0 commits2026-07-07: 0 commits2026-07-08: 0 commits2026-07-09: 0 commits2026-07-10: 0 commits2026-07-11: 0 commits2026-07-12: 0 commits2026-07-13: 0 commits2026-07-14: 0 commits2026-07-15: 0 commits2026-07-16: 0 commits2026-07-17: 0 commits2026-07-18: 0 commits2026-07-19: 0 commits2026-07-20: 0 commits2026-07-21: 0 commits2026-07-22: 0 commits2026-07-23: 0 commits2026-07-24: 0 commits2026-07-25: 0 commits2026-07-26: 0 commits2026-07-27: 0 commits2026-07-28: 0 commits2026-07-29: 0 commits2026-07-30: 0 commits2026-07-31: 0 commits2026-08-01: 0 commits
473 commits in the last yearLessMore

Signals and awards

derived from tracked data
  • Widely adopted

    28,620 stars

  • Permissive license

    MIT

  • Repeat trending

    5 trending appearances

What qmd does

qmd is a lightweight command-line tool that indexes and searches local text files, documentation, and meeting notes without relying on cloud services. It brings state-of-the-art embedding and retrieval techniques directly to the developer's terminal, prioritizing speed and privacy. The technical approach involves running embedding models locally to generate vector representations of documents, allowing for semantic search rather than just keyword matching. What's distinctive is its uncompromising focus on being entirely local and minimal, rejecting the bloat of traditional enterprise search tools. It provides a fast, privacy-respecting way to query sprawling personal knowledge bases directly from the command line.

This tool is built for developers, researchers, and power users who maintain large local repositories of text and prefer terminal-based workflows. It requires basic familiarity with command-line interfaces and local environment setup.

  • Local Semantic Search: utilizes local embedding models to understand the context of queries rather than just matching exact keywords.
  • Zero Cloud Dependency: ensures complete privacy by performing all indexing and searching directly on the user's hardware.
  • Minimalist CLI Interface: integrates seamlessly into existing terminal workflows without requiring a heavy graphical interface.
  • State-of-the-Art Retrieval: implements modern vector search techniques to surface highly relevant documents quickly.
  • Broad Format Support: parses and indexes various text formats including Markdown, meeting notes, and raw code files.

Where teams use it

Personal Knowledge Management

Developers use it to instantly locate specific code snippets or architectural decisions hidden deep within their local notes.

Offline Documentation Search

Engineers working in air-gapped environments or while traveling use it to query large documentation sets without internet access.

Meeting Note Retrieval

Project managers quickly surface historical context by semantically searching years of plain-text meeting minutes.

Terminal-Integrated Workflows

Power users integrate the tool into shell scripts or aliases to automate information retrieval during active development.

Getting started: npm install -g qmd-cli && qmd index ./docs

README

main branch

QMD - Query Markup Documents

An on-device search engine for everything you need to remember. Index your markdown notes, meeting transcripts, documentation, and knowledge bases. Search with keywords or natural language. Ideal for your agentic flows.

QMD combines BM25 full-text search, vector semantic search, and LLM re-ranking—all running locally via node-llama-cpp with GGUF models.

QMD Architecture

You can read more about QMD's progress in the CHANGELOG.

Quick Start

# Install globally (Node or Bun)
npm install -g @tobilu/qmd
# or
bun install -g @tobilu/qmd

# Or run directly
npx @tobilu/qmd ...
bunx @tobilu/qmd ...

# Create collections for your notes, docs, and meeting transcripts
qmd collection add ~/notes --name notes
qmd collection add ~/Documents/meetings --name meetings
qmd collection add ~/work/docs --name docs

# Add context to help with search results, each piece of context will be returned when matching sub documents are returned. This works as a tree. This is the key feature of QMD as it allows LLMs to make much better contextual choices when selecting documents. Don't sleep on it!
qmd context add qmd://notes "Personal notes and ideas"
qmd context add qmd://meetings "Meeting transcripts and notes"
qmd context add qmd://docs "Work documentation"

# Generate embeddings for semantic search
qmd embed

# Search across everything
qmd search "project timeline"           # Fast keyword search
qmd vsearch "how to deploy"             # Semantic search
qmd query "quarterly planning process"  # Hybrid + reranking (best quality)

# Get a specific document
qmd get "meetings/2024-01-15.md"

# Get a document by docid (shown in search results)
qmd get "#abc123"

# Get multiple documents by glob pattern
qmd multi-get "journals/2025-05*.md"

# Search within a specific collection
qmd search "API" -c notes

# Export all matches for an agent
qmd search "API" --all --files --min-score 0.3

Using with AI Agents

QMD's --json and --files output formats are designed for agentic workflows:

# Get structured results for an LLM
qmd search "authentication" --json -n 10

# List all relevant files above a threshold
qmd query "error handling" --all --files --min-score 0.4

# Retrieve full document content
qmd get "docs/api-reference.md" --full

MCP Server

Although the tool works perfectly fine when you just tell your agent to use it on the command line, it also exposes an MCP (Model Context Protocol) server for tighter integration.

Tools exposed:

  • query — Search with typed sub-queries (lex/vec/hyde), combined via RRF + reranking
  • get — Retrieve a document by path or docid (with fuzzy matching suggestions)
  • multi_get — Batch retrieve by glob pattern, comma-separated list, or docids
  • status — Index health and collection info

Claude Desktop configuration (~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "qmd": {
      "command": "qmd",
      "args": ["mcp"]
    }
  }
}

Claude Code — Install the plugin (recommended):

claude plugin marketplace add tobi/qmd
claude plugin install qmd@qmd

Or configure MCP manually in ~/.claude/settings.json:

{
  "mcpServers": {
    "qmd": {
      "command": "qmd",
      "args": ["mcp"]
    }
  }
}

HTTP Transport

By default, QMD's MCP server uses stdio (launched as a subprocess by each client). For a shared, long-lived server that avoids repeated model loading, use the HTTP transport:

# Foreground (Ctrl-C to stop)
qmd mcp --http                    # localhost:8181
qmd mcp --http --port 8080        # custom port
qmd mcp --http --host 0.0.0.0     # bind all interfaces (e.g. container probes)

# Background daemon
qmd mcp --http --daemon           # start, writes PID to ~/.cache/qmd/mcp.pid
qmd mcp stop                      # stop via PID file
qmd status                        # shows "MCP: running (PID ...)" when active

The server binds to localhost by default. Pass --host (or set the QMD_HOST environment variable) to override — --host 0.0.0.0 is useful when the server runs in a container and a liveness probe connects from a non-loopback address.

The HTTP server exposes two endpoints:

  • POST /mcp — MCP Streamable HTTP (JSON responses, stateless)
  • GET /health — liveness check with uptime

LLM models stay loaded in VRAM across requests. Embedding/reranking contexts are disposed after 5 min idle and transparently recreated on the next request (~1s penalty, models remain loaded).

Point any MCP client at http://localhost:8181/mcp to connect.

MCP Tool Parameters

Tool Parameter Type Notes
query searches array Typed sub-queries (lex/vec/hyde), 1–10. Required. First gets 2x weight.
query collections string[] Filter by collection names (OR). Array only — singular collection is silently ignored.
query intent string Disambiguation context (does not search on its own)
query limit number Max results (default 10)
query minScore number Minimum relevance 0–1 (default 0)
query candidateLimit number Max candidates to rerank (default 40)
query rerank boolean Run LLM reranking (default true); set false for RRF-only
get file string Path, docid (#abc123), or path:from:count (e.g. #abc123:120:40)
get fromLine number Start line (1-indexed); overrides the :from suffix
get maxLines number Limit returned lines
get lineNumbers boolean Prefix lines with numbers (default true)
multi_get pattern string Glob pattern or comma-separated list
multi_get maxBytes number Skip files larger than N (default 10240)
multi_get maxLines number Limit lines per file
multi_get lineNumbers boolean Prefix lines with numbers (default true)

Unknown parameters are silently ignored (not rejected) — double-check names if results seem unscoped. The HTTP /query and /search endpoints return qmd://collection/path URIs in the file field, matching the CLI and MCP output.

SDK / Library Usage

Use QMD as a library in your own Node.js or Bun applications.

Installation

npm install @tobilu/qmd

Quick Start

import { createStore } from '@tobilu/qmd'

const store = await createStore({
  dbPath: './my-index.sqlite',
  config: {
    collections: {
      docs: { path: '/path/to/docs', pattern: '**/*.md' },
    },
  },
})

const results = await store.search({ query: "authentication flow" })
console.log(results.map(r => `${r.title} (${Math.round(r.score * 100)}%)`))

await store.close()

Store Creation

createStore() accepts three modes:

import { createStore } from '@tobilu/qmd'

// 1. Inline config — no files needed besides the DB
const store = await createStore({
  dbPath: './index.sqlite',
  config: {
    collections: {
      docs: { path: '/path/to/docs', pattern: '**/*.md' },
      notes: { path: '/path/to/notes' },
    },
  },
})

// 2. YAML config file — collections defined in a file
const store2 = await createStore({
  dbPath: './index.sqlite',
  configPath: './qmd.yml',
})

// 3. DB-only — reopen a previously configured store
const store3 = await createStore({ dbPath: './index.sqlite' })

Search

The unified search() method handles both simple queries and pre-expanded structured queries:

// Simple query — auto-expanded via LLM, then BM25 + vector + reranking
const results = await store.search({ query: "authentication flow" })

// With options
const results2 = await store.search({
  query: "rate limiting",
  intent: "API throttling and abuse prevention",
  collection: "docs",
  limit: 5,
  minScore: 0.3,
  explain: true,
})

// Pre-expanded queries — skip auto-expansion, control each sub-query
const results3 = await store.search({
  queries: [
    { type: 'lex', query: '"connection pool" timeout -redis' },
    { type: 'vec', query: 'why do database connections time out under load' },
  ],
  collections: ["docs", "notes"],
})

// Skip reranking for faster results
const fast = await store.search({ query: "auth", rerank: false })

For direct backend access:

// BM25 keyword search (fast, no LLM)
const lexResults = await store.searchLex("auth middleware", { limit: 10 })

// Vector similarity search (embedding model, no reranking)
const vecResults = await store.searchVector("how users log in", { limit: 10 })

// Manual query expansion for full control
const expanded = await store.expandQuery("auth flow", { intent: "user login" })
const results4 = await store.search({ queries: expanded })

Retrieval

// Get a document by path or docid
const doc = await store.get("docs/readme.md")
const byId = await store.get("#abc123")

if (!("error" in doc)) {
  console.log(doc.title, doc.displayPath, doc.context)
}

// Get document body with line range
const body = await store.getDocumentBody("docs/readme.md", {
  fromLine: 50,
  maxLines: 100,
})

// Batch retrieve by glob or comma-separated list
const { docs, errors } = await store.multiGet("docs/**/*.md", {
  maxBytes: 20480,
})

Collections

// Add a collection
await store.addCollection("myapp", {
  path: "/src/myapp",
  pattern: "**/*.ts",
  ignore: ["node_modules/**", "*.test.ts"],
})

// List collections with document stats
const collections = await store.listCollections()
// => [{ name, pwd, glob_pattern, doc_count, active_count, last_modified, includeByDefault }]

// Get names of collections included in queries by default
const defaults = await store.getDefaultCollectionNames()

// Remove / rename
await store.removeCollection("myapp")
await store.renameCollection("old-name", "new-name")

Context

Context adds descriptive metadata that improves search relevance and is returned alongside results:

// Add context for a path within a collection
await store.addContext("docs", "/api", "REST API reference documentation")

// Set global context (applies to all collections)
await store.setGlobalContext("Internal engineering documentation")

// List all contexts
const contexts = await store.listContexts()
// => [{ collection, path, context }]

// Remove context
await store.removeContext("docs", "/api")
await store.setGlobalContext(undefined)  // clear global

Indexing

// Re-index collections by scanning the filesystem
const result = await store.update({
  collections: ["docs"],  // optional — defaults to all
  onProgress: ({ collection, file, current, total }) => {
    console.log(`[${collection}] ${current}/${total} ${file}`)
  },
})
// => { collections, indexed, updated, unchanged, removed, needsEmbedding }

// Generate vector embeddings
const embedResult = await store.embed({
  force: false,           // true to re-embed everything
  chunkStrategy: "auto",  // "regex" (default) or "auto" (AST for code files)
  onProgress: ({ current, total, collection }) => {
    console.log(`Embedding ${current}/${total}`)
  },
})

Types

Key types exported for SDK consumers:

import type {
  QMDStore,            // The store interface
  SearchOptions,       // Options for search()
  LexSearchOptions,    // Options for searchLex()
  VectorSearchOptions, // Options for searchVector()
  HybridQueryResult,   // Search result with score, snippet, context
  SearchResult,        // Result from searchLex/searchVector
  ExpandedQuery,       // Typed sub-query { type: 'lex'|'vec'|'hyde', query }
  DocumentResult,      // Document metadata + body
  DocumentNotFound,    // Error with similarFiles suggestions
  MultiGetResult,      // Batch retrieval result
  UpdateProgress,      // Progress callback info for update()
  UpdateResult,        // Aggregated update result
  EmbedProgress,       // Progress callback info for embed()
  EmbedResult,         // Embedding result
  StoreOptions,        // createStore() options
  CollectionConfig,    // Inline config shape
  IndexStatus,         // From getStatus()
  IndexHealthInfo,     // From getIndexHealth()
} from '@tobilu/qmd'

Utility exports:

import {
  extractSnippet,              // Extract a relevant snippet from text
  addLineNumbers,              // Add line numbers to text
  DEFAULT_MULTI_GET_MAX_BYTES, // Default max file size for multiGet (64KB)
  Maintenance,                 // Database maintenance operations
} from '@tobilu/qmd'

Lifecycle

// Close the store — disposes LLM models and DB connection
await store.close()

The SDK requires explicit dbPath — no defaults are assumed. This makes it safe to embed in any application without side effects.

Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                         QMD Hybrid Search Pipeline                          │
└─────────────────────────────────────────────────────────────────────────────┘

                              ┌─────────────────┐
                              │   User Query    │
                              └────────┬────────┘
                                       │
                        ┌──────────────┴──────────────┐
                        ▼                             ▼
               ┌────────────────┐            ┌────────────────┐
               │ Query Expansion│            │  Original Query│
               │  (fine-tuned)  │            │   (×2 weight)  │
               └───────┬────────┘            └───────┬────────┘
                       │                             │
                       │ 2 alternative queries       │
                       └──────────────┬──────────────┘
                                      │
              ┌───────────────────────┼───────────────────────┐
              ▼                       ▼                       ▼
     ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
     │ Original Query  │     │ Expanded Query 1│     │ Expanded Query 2│
     └────────┬────────┘     └────────┬────────┘     └────────┬────────┘
              │                       │                       │
      ┌───────┴───────┐       ┌───────┴───────┐       ┌───────┴───────┐
      ▼               ▼       ▼               ▼       ▼               ▼
  ┌───────┐       ┌───────┐ ┌───────┐     ┌───────┐ ┌───────┐     ┌───────┐
  │ BM25  │       │Vector │ │ BM25  │     │Vector │ │ BM25  │     │Vector │
  │(FTS5) │       │Search │ │(FTS5) │     │Search │ │(FTS5) │     │Search │
  └───┬───┘       └───┬───┘ └───┬───┘     └───┬───┘ └───┬───┘     └───┬───┘
      │               │         │             │         │             │
      └───────┬───────┘         └──────┬──────┘         └──────┬──────┘
              │                        │                       │
              └────────────────────────┼───────────────────────┘
                                       │
                                       ▼
                          ┌───────────────────────┐
                          │   RRF Fusion + Bonus  │
                          │  Original query: ×2   │
                          │  Top-rank bonus: +0.05│
                          │     Top 30 Kept       │
                          └───────────┬───────────┘
                                      │
                                      ▼
                          ┌───────────────────────┐
                          │    LLM Re-ranking     │
                          │  (qwen3-reranker)     │
                          │  Yes/No + logprobs    │
                          └───────────┬───────────┘
                                      │
                                      ▼
                          ┌───────────────────────┐
                          │  Position-Aware Blend │
                          │  Top 1-3:  75% RRF    │
                          │  Top 4-10: 60% RRF    │
                          │  Top 11+:  40% RRF    │
                          └───────────────────────┘

Score Normalization & Fusion

Search Backends

Backend Raw Score Conversion Range
FTS (BM25) SQLite FTS5 BM25 Math.abs(score) 0 to ~25+
Vector Cosine distance 1 / (1 + distance) 0.0 to 1.0
Reranker LLM 0-10 rating score / 10 0.0 to 1.0

Fusion Strategy

The query command uses Reciprocal Rank Fusion (RRF) with position-aware blending:

  1. Query Expansion: Original query (×2 for weighting) + 1 LLM variation
  2. Parallel Retrieval: Each query searches both FTS and vector indexes
  3. RRF Fusion: Combine all result lists using score = Σ(1/(k+rank+1)) where k=60
  4. Top-Rank Bonus: Documents ranking #1 in any list get +0.05, #2-3 get +0.02
  5. Top-K Selection: Take top 30 candidates for reranking
  6. Re-ranking: LLM scores each document (yes/no with logprobs confidence)
  7. Position-Aware Blending:
    • RRF rank 1-3: 75% retrieval, 25% reranker (preserves exact matches)
    • RRF rank 4-10: 60% retrieval, 40% reranker
    • RRF rank 11+: 40% retrieval, 60% reranker (trust reranker more)

Why this approach: Pure RRF can dilute exact matches when expanded queries don't match. The top-rank bonus preserves documents that score #1 for the original query. Position-aware blending prevents the reranker from destroying high-confidence retrieval results.

Score Interpretation

Score Meaning
0.8 - 1.0 Highly relevant
0.5 - 0.8 Moderately relevant
0.2 - 0.5 Somewhat relevant
0.0 - 0.2 Low relevance

Requirements

System Requirements

  • Node.js >= 22
  • Bun >= 1.0.0
  • macOS: Homebrew SQLite (for extension support)
    brew install sqlite

GGUF Models (via node-llama-cpp)

QMD uses three local GGUF models (auto-downloaded on first use):

Model Purpose Size
embeddinggemma-300M-Q8_0 Vector embeddings (default) ~300MB
qwen3-reranker-0.6b-q8_0 Re-ranking ~640MB
qmd-query-expansion-1.7B-q4_k_m Query expansion (fine-tuned) ~1.1GB

Models are downloaded from HuggingFace and cached in ~/.cache/qmd/models/.

Custom Embedding Model

Override the default embedding model via the QMD_EMBED_MODEL environment variable. This is useful for multilingual corpora (e.g. Chinese, Japanese, Korean) where embeddinggemma-300M has limited coverage.

# Use Qwen3-Embedding-0.6B for better multilingual (CJK) support
export QMD_EMBED_MODEL="hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf"

# After changing the model, re-embed all collections:
qmd embed -f

Supported model families:

  • embeddinggemma (default) — English-optimized, small footprint
  • Qwen3-Embedding — Multilingual (119 languages including CJK), MTEB top-ranked

Note: When switching embedding models, you must re-index with qmd embed -f since vectors are not cross-compatible between models. The prompt format is automatically adjusted for each model family.

Installation

npm install -g @tobilu/qmd
# or
bun install -g @tobilu/qmd

Development

git clone https://github.com/tobi/qmd
cd qmd
npm install
npm link

Usage

Collection Management

# Create a collection from current directory
qmd collection add . --name myproject

# Create a collection with explicit path and custom glob mask
qmd collection add ~/Documents/notes --name notes --mask "**/*.md"

# List all collections
qmd collection list

# Remove a collection
qmd collection remove myproject

# Rename a collection
qmd collection rename myproject my-project

# List files in a collection
qmd ls notes
qmd ls notes/subfolder

# Show collection details (path, glob mask, include status, context count)
qmd collection show notes

# Include or exclude a collection from default (unscoped) queries
qmd collection include notes
qmd collection exclude notes

# Run a command before every `qmd update` (e.g. git pull); empty arg clears it
qmd collection update-cmd notes 'git pull --rebase'
qmd collection update-cmd notes

Generate Vector Embeddings

# Embed all indexed documents (900 tokens/chunk, 15% overlap)
qmd embed

# Force re-embed everything
qmd embed -f

# Enable AST-aware chunking for code files (TS, JS, Python, Go, Rust)
qmd embed --chunk-strategy auto

# Also works with query for consistent chunk selection
qmd query "auth flow" --chunk-strategy auto

# Memory control for large corpora / constrained systems
qmd embed --max-docs-per-batch 50   # cap docs per embedding batch
qmd embed --max-batch-mb 64         # cap batch size in MB

AST-aware chunking (--chunk-strategy auto) uses tree-sitter to chunk code files at function, class, and import boundaries instead of arbitrary text positions. This produces higher-quality chunks and better search results for codebases. Markdown and other file types always use regex-based chunking regardless of strategy.

The default is regex (existing behavior). Use --chunk-strategy auto to opt in. Run qmd status to verify which grammars are available.

Note: Tree-sitter grammars are optional dependencies. If they are not installed, --chunk-strategy auto falls back to regex-only chunking automatically. Tested on both Node.js and Bun.

Context Management

Context adds descriptive metadata to collections and paths, helping search understand your content.

# Add context to a collection (using qmd:// virtual paths)
qmd context add qmd://notes "Personal notes and ideas"
qmd context add qmd://docs/api "API documentation"

# Add context from within a collection directory
cd ~/notes && qmd context add "Personal notes and ideas"
cd ~/notes/work && qmd context add "Work-related notes"

# Add global context (applies to all collections)
qmd context add / "Knowledge base for my projects"

# List all contexts
qmd context list

# Remove context
qmd context rm qmd://notes/old

Configuring index.yml

The collection and context commands above all read and write a single YAML config file — you can also edit it directly. Everything QMD knows about your collections (paths, masks, exclusions, per-collection update hooks, contexts, and optional model overrides) lives here. A fully-commented starter template ships as example-index.yml in this repo.

Location: ~/.config/qmd/index.yml by default. The directory honors XDG_CONFIG_HOME (→ $XDG_CONFIG_HOME/qmd/index.yml) and QMD_CONFIG_DIR. A named index uses {name}.ymlqmd --index work … reads/writes work.yml. A project-local index created with qmd init lives at .qmd/index.yml (.qmd/index.yaml is also accepted) alongside a project-local index.sqlite, so config and index stay inside the project instead of ~/.config / ~/.cache.

# ~/.config/qmd/index.yml

# Context applied to every collection (system-message style). Optional.
global_context: "Knowledge base for my projects"

# Terminal hyperlink template for search results. Optional.
# Overridden by the QMD_EDITOR_URI env var. See "Editor Links" below.
editor_uri: "vscode://file{path}:{line}:{col}"

# Override the default GGUF models per role. Optional — omit to use the
# built-in defaults. `qmd init` writes this block pre-filled with the
# resolved defaults. See "Model Configuration" for the default URIs.
models:
  embed: "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf"
  rerank: "hf:ggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF/qwen3-reranker-0.6b-q8_0.gguf"
  generate: "hf:tobil/qmd-query-expansion-1.7B-gguf/qmd-query-expansion-1.7B-q4_k_m.gguf"

# One entry per collection. The key is the collection name.
collections:
  notes:
    path: /Users/me/notes        # absolute path to index (required)
    pattern: "**/*.md"           # glob mask (default: **/*.md)
    ignore:                      # glob patterns to exclude from indexing
      - "Archive/**"
      - "**/drafts/**"
    update: "git pull --rebase"  # bash command run before each `qmd update`
    includeByDefault: true       # include in unscoped queries (default: true)
    context:                     # path prefix → description; longest match wins
      "/": "Personal notes and ideas"
      "/work": "Work-related notes"
Key Scope Purpose
global_context top-level Context prepended for every collection. Set via qmd context add /.
editor_uri (alias editor_uri_template) top-level Hyperlink template for clickable result paths; QMD_EDITOR_URI overrides.
models.embed / .rerank / .generate top-level HuggingFace GGUF URIs (hf:<user>/<repo>/<file>) overriding the built-in defaults per role.
collections.<name>.path per-collection Absolute directory to index.
collections.<name>.pattern per-collection Glob mask. Set via qmd collection add --mask. Default **/*.md.
collections.<name>.ignore per-collection Glob patterns excluded from indexing — useful to stop nested collections double-indexing. YAML-only — no CLI command sets this. Additive with QMD's built-in exclusions (node_modules, .git, .cache, vendor, dist, build), which you cannot un-ignore.
collections.<name>.update per-collection Bash command run before qmd update re-indexes this collection. Set via qmd collection update-cmd.
collections.<name>.includeByDefault per-collection Whether unscoped queries search it. Toggle with qmd collection include/exclude. Default true.
collections.<name>.context per-collection Path-prefix → description map; the most specific (longest) matching prefix wins. Set via qmd context add.

Note: Editing index.yml changes which directories and models QMD uses, but does not re-index on its own. Run qmd update after changing path, pattern, or ignore, and qmd embed after changing models.embed.

Automatic update commands

A collection's update field is QMD's built-in refresh hook: when you run qmd update, each collection's update command runs first, then the collection is re-indexed. This keeps a collection in sync with an upstream source (a git remote, a sync script) without wrapping qmd yourself.

collections:
  wiki:
    path: ~/reference/wiki
    update: "git pull --ff-only"
$ qmd update
[1/3] wiki (**/*.md)
    Running update command: git pull --ff-only
    Already up to date.
Collection: ~/reference/wiki (**/*.md)
Indexed: 0 new, 2 updated, 340 unchanged, 0 removed

The command runs via bash -c in the collection's own directory (its path), not your current working directory. If it exits non-zero, qmd update prints the failure and aborts the entire run — collections after the failing one are not re-indexed. Set or clear it from the CLI instead of editing YAML by hand:

qmd collection update-cmd wiki 'git pull --ff-only'   # set
qmd collection update-cmd wiki                         # clear

Search Commands

┌──────────────────────────────────────────────────────────────────┐
│                        Search Modes                              │
├──────────┬───────────────────────────────────────────────────────┤
│ search   │ BM25 full-text search only                           │
│ vsearch  │ Vector semantic search only                          │
│ query    │ Hybrid: FTS + Vector + Query Expansion + Re-ranking  │
└──────────┴───────────────────────────────────────────────────────┘
# Full-text search (fast, keyword-based)
qmd search "authentication flow"

# Vector search (semantic similarity)
qmd vsearch "how to login"

# Hybrid search with re-ranking (best quality)
qmd query "user authentication"

Two aliases exist for the semantic/hybrid modes: vector-search (→ vsearch) and deep-search (→ query).

Options

# Search options
-n <num>           # Number of results (default: 5, or 20 for --files/--json)
-c, --collection   # Restrict search to a specific collection
--all              # Return all matches (use with --min-score to filter)
--min-score <num>  # Minimum score threshold (default: 0)
--full             # Show full document content
--line-numbers     # Add line numbers to output
--explain          # Include retrieval score traces (query, JSON/CLI output)
--index <name>     # Use named index
--intent "<text>"  # Disambiguation context (e.g. "web page load times")
--no-rerank        # Skip LLM reranking (RRF scores only; faster on CPU)
-C, --candidate-limit <n>  # Max candidates to rerank (default: 40)
--full-path        # Emit on-disk filesystem paths instead of qmd:// URIs

# Output formats (for search and multi-get)
--format <kind>    # cli (default) | json | csv | md | xml | files
                   # (--json, --csv, --md, --xml, --files are legacy aliases)

# Get options
qmd get <file>[:from[:count]]  # Get document; optional start line and count
-l <num>                       # Maximum lines to return
--from <num>                   # Start line (overrides the :from suffix)
--no-line-numbers              # Disable li

(README truncated)

View on GitHub

Recent activity

commits and pull requests

Releases and announcements

13 total
  1. v2.5.3v2.5.3May 29, 2026

    ## [2.5.3] - 2026-05-28 ### Features - `qmd get` now accepts a `:from:count` suffix on a path or docid (e.g. `qmd get "#abc123:120:40"` reads 40 lines starting at line 120). Explicit `--from`/`-l` flags still override the suffix. The MCP `get` tool accepts the same suffix. - `qmd get` and `qmd multi-get` are now **line-numbered by default** and print the document's `#docid` and `qmd://` path in the output header. Disable line numbers with `--no-line-numbers`. The MCP `get`/`multi_get` tools default `lineNumbers` to `true` to match. - `qmd multi-get` now includes the `#docid` in every output format (`--md`, `--json`, `--csv`, `--xml`, `--files`, and the default CLI view), consistent with `qmd search`. - `qmd get` and `qmd multi-get` accept `--full-path`, which replaces the `qmd://` path + `#docid` with the document's on-disk filesystem path (handy for piping into `Read`/`Edit`/an editor). Falls back to the canonical `qmd://` + docid header when the file no longer exists on disk. - `qmd search` / `qmd query` now show a clearer hit identifier: the default CLI view (and the new `**file:**` line in `--md` output) always prints the full `qmd://collection/path`

  2. v2.5.2v2.5.2May 22, 2026

    ## [2.5.2] - 2026-05-22 ### Fixes - Launcher: Rewrite `bin/qmd` as a Node-based shebang polyglot to fix global npm installation execution failures on Windows (#668 / #452), while supporting seamless fallback to Bun in Node-less environments. ## [2.5.1] - 2026-05-20 ### Changes - Release: publish from GitHub Actions via npm Trusted Publishing/OIDC instead of a long-lived `NPM_TOKEN` secret. ## [2.5.0] - 2026-05-19 ### Changes - Dependencies: update core SQLite/config/chunking packages (`better-sqlite3`, `yaml`, `web-tree-sitter`, `tree-sitter-go`, and `tree-sitter-python`) while keeping incompatible `zod`, `tsx`, and `vitest` majors pinned. - Agent skills: add `qmd skills list|get|path` to serve version-matched runtime skill instructions from the installed CLI, and make `qmd skill install` write a stable discovery stub so installed agent skills do not go stale after QMD upgrades. - CLI: add `qmd doctor` for index/runtime diagnostics, including SQLite/sqlite-vec versions, embedding fingerprint freshness, mixed-fingerprint detection, safe legacy fingerprint adoption, and content-hash sampling. ### Fixes - Launcher: prefer runnable TypeScript source in git checkouts even whe

  3. v2.5.1v2.5.1May 20, 2026

    ## [2.5.1] - 2026-05-20 ### Changes - Release: publish from GitHub Actions via npm Trusted Publishing/OIDC instead of a long-lived `NPM_TOKEN` secret. ## [2.5.0] - 2026-05-19 ### Changes - Dependencies: update core SQLite/config/chunking packages (`better-sqlite3`, `yaml`, `web-tree-sitter`, `tree-sitter-go`, and `tree-sitter-python`) while keeping incompatible `zod`, `tsx`, and `vitest` majors pinned. - Agent skills: add `qmd skills list|get|path` to serve version-matched runtime skill instructions from the installed CLI, and make `qmd skill install` write a stable discovery stub so installed agent skills do not go stale after QMD upgrades. - CLI: add `qmd doctor` for index/runtime diagnostics, including SQLite/sqlite-vec versions, embedding fingerprint freshness, mixed-fingerprint detection, safe legacy fingerprint adoption, and content-hash sampling. ### Fixes - Launcher: prefer runnable TypeScript source in git checkouts even when ignored `dist/` artifacts exist, while packaged installs continue to run `dist/`. - GPU: keep node-llama-cpp's documented `gpu: "auto"` initialization as the primary path, then perform no-build packaged CUDA/Vulkan/Metal probes only if auto fal

  4. v2.1.0v2.1.0Apr 5, 2026

    ## [2.1.0] - 2026-04-05 Code files now chunk at function and class boundaries via tree-sitter, clickable editor links land you at the right line from search results, and per-collection model configuration means you can point different collections at different embedding models. 25+ community PRs fix embedding stability, BM25 accuracy, and cross-platform launcher issues. ### Changes - AST-aware chunking for code files via `web-tree-sitter`. Supported languages: TypeScript/JavaScript, Python, Go, and Rust. Code files are chunked at function, class, and import boundaries instead of arbitrary text positions. Markdown and unknown file types are unchanged. `--chunk-strategy <auto|regex>` flag on `qmd embed` and `qmd query` (default `regex`). SDK: `chunkStrategy` option on `embed()` and `search()`. `qmd status` shows grammar availability. - `qmd bench <fixture.json>` command for search quality benchmarks. Measures precision@k, recall, MRR, and F1 across BM25, vector, hybrid, and full pipeline backends. Ships with an example fixture against the eval-docs test collection. #470 (thanks @jmilinovich) - `models:` section in `index.yml` lets you configure `embed`, `rerank`,

  5. v2.0.1v2.0.1Mar 11, 2026

    ## [2.0.1] - 2026-03-10 ### Changes - `qmd skill install` copies the packaged QMD skill into `~/.claude/commands/` for one-command setup. #355 (thanks @nibzard) ### Fixes - Fix Qwen3-Embedding GGUF filename case — HuggingFace filenames are case-sensitive, the lowercase variant returned 404. #349 (thanks @byheaven) - Resolve symlinked global launcher path so `qmd` works correctly when installed via `npm i -g`. #352 (thanks @nibzard) ## [2.0.0] - 2026-03-10 QMD 2.0 declares a stable library API. The SDK is now the primary interface — the MCP server is a clean consumer of it, and the source is organized into `src/cli/` and `src/mcp/`. Also: Node 25 support and a runtime-aware bin wrapper for bun installs. ### Changes - Stable SDK API with `QMDStore` interface — search, retrieval, collection/context management, indexing, lifecycle - Unified `search()`: pass `query` for auto-expansion or `queries` for pre-expanded lex/vec/hyde — replaces the old query/search/structuredSearch split - New `getDocumentBody()`, `getDefaultCollectionNames()`, `Maintenance` class - MCP server rewritten as a clean SDK consumer — zero internal store access - CLI and MCP organized into `src/cli

Code frequency

additions and deletions
+45.8K-45.8KWeek of 2025-12-07: +27,368 linesWeek of 2025-12-07: -14,559 linesWeek of 2025-12-14: +2,979 linesWeek of 2025-12-14: -1,961 linesWeek of 2025-12-21: +2,078 linesWeek of 2025-12-21: -1,110 linesWeek of 2025-12-28: +452 linesWeek of 2025-12-28: -393 linesWeek of 2026-01-04: +285 linesWeek of 2026-01-04: -232 linesWeek of 2026-01-11: +110 linesWeek of 2026-01-11: -52 linesWeek of 2026-01-18: +13,710 linesWeek of 2026-01-18: -1,810 linesWeek of 2026-01-25: +45,813 linesWeek of 2026-01-25: -32,872 linesWeek of 2026-02-01: +277 linesWeek of 2026-02-01: -177 linesWeek of 2026-02-08: +1,863 linesWeek of 2026-02-08: -711 linesWeek of 2026-02-15: +26,410 linesWeek of 2026-02-15: -16,513 linesWeek of 2026-02-22: +6,144 linesWeek of 2026-02-22: -15,930 linesWeek of 2026-03-01: +1,459 linesWeek of 2026-03-01: -335 linesWeek of 2026-03-08: +9,429 linesWeek of 2026-03-08: -5,185 linesWeek of 2026-03-15: +14 linesWeek of 2026-03-15: -5 linesWeek of 2026-03-22: +2,754 linesWeek of 2026-03-22: -104 linesWeek of 2026-03-29: +295 linesWeek of 2026-03-29: -839 linesWeek of 2026-04-05: +6,966 linesWeek of 2026-04-05: -2,845 linesWeek of 2026-04-12: +1 linesWeek of 2026-04-12: -0 linesWeek of 2026-04-19: +61 linesWeek of 2026-04-19: -31 linesWeek of 2026-04-26: +0 linesWeek of 2026-04-26: -0 linesWeek of 2026-05-03: +1,239 linesWeek of 2026-05-03: -140 linesWeek of 2026-05-10: +2,063 linesWeek of 2026-05-10: -313 linesWeek of 2026-05-17: +3,065 linesWeek of 2026-05-17: -998 linesWeek of 2026-05-24: +1,386 linesWeek of 2026-05-24: -285 linesWeek of 2026-05-31: +641 linesWeek of 2026-05-31: -8 linesWeek of 2026-06-07: +673 linesWeek of 2026-06-07: -73 linesWeek of 2026-06-14: +654 linesWeek of 2026-06-14: -78 linesWeek of 2026-06-21: +382 linesWeek of 2026-06-21: -146 linesWeek of 2026-06-28: +0 linesWeek of 2026-06-28: -0 linesWeek of 2026-07-05: +0 linesWeek of 2026-07-05: -0 linesWeek of 2026-07-12: +0 linesWeek of 2026-07-12: -0 linesWeek of 2026-07-19: +0 linesWeek of 2026-07-19: -0 linesWeek of 2026-07-26: +0 linesWeek of 2026-07-26: -0 linesDec 7, 2025Jul 26, 2026
+158.6K lines added, -97.7K removed over the last year.

Commits per week

last 52 weeks
770Week of 2025-08-02: 0 commitsWeek of 2025-08-09: 0 commitsWeek of 2025-08-16: 0 commitsWeek of 2025-08-23: 0 commitsWeek of 2025-08-30: 0 commitsWeek of 2025-09-06: 0 commitsWeek of 2025-09-13: 0 commitsWeek of 2025-09-20: 0 commitsWeek of 2025-09-27: 0 commitsWeek of 2025-10-04: 0 commitsWeek of 2025-10-11: 0 commitsWeek of 2025-10-18: 0 commitsWeek of 2025-10-25: 0 commitsWeek of 2025-11-01: 0 commitsWeek of 2025-11-09: 0 commitsWeek of 2025-11-16: 0 commitsWeek of 2025-11-23: 0 commitsWeek of 2025-11-30: 0 commitsWeek of 2025-12-07: 73 commitsWeek of 2025-12-14: 9 commitsWeek of 2025-12-21: 15 commitsWeek of 2025-12-28: 2 commitsWeek of 2026-01-04: 6 commitsWeek of 2026-01-11: 4 commitsWeek of 2026-01-18: 11 commitsWeek of 2026-01-25: 45 commitsWeek of 2026-02-01: 5 commitsWeek of 2026-02-08: 9 commitsWeek of 2026-02-15: 77 commitsWeek of 2026-02-22: 11 commitsWeek of 2026-03-01: 20 commitsWeek of 2026-03-08: 30 commitsWeek of 2026-03-15: 1 commitsWeek of 2026-03-22: 14 commitsWeek of 2026-03-29: 7 commitsWeek of 2026-04-05: 44 commitsWeek of 2026-04-12: 1 commitsWeek of 2026-04-19: 3 commitsWeek of 2026-04-26: 0 commitsWeek of 2026-05-03: 18 commitsWeek of 2026-05-10: 17 commitsWeek of 2026-05-17: 23 commitsWeek of 2026-05-24: 10 commitsWeek of 2026-05-31: 4 commitsWeek of 2026-06-07: 7 commitsWeek of 2026-06-14: 4 commitsWeek of 2026-06-21: 3 commitsWeek of 2026-06-28: 0 commitsWeek of 2026-07-05: 0 commitsWeek of 2026-07-12: 0 commitsWeek of 2026-07-19: 0 commitsWeek of 2026-07-26: 0 commitsAug 2, 2025Jul 26, 2026
473 commits in the last 52 weeks.

When work happens

weekday and hour
SunMonTueWedThuFriSat036912151821Sun 0:00 — 7 commitsSun 1:00 — 2 commitsSun 2:00 — 0 commitsSun 3:00 — 1 commitsSun 4:00 — 1 commitsSun 5:00 — 0 commitsSun 6:00 — 0 commitsSun 7:00 — 0 commitsSun 8:00 — 1 commitsSun 9:00 — 2 commitsSun 10:00 — 9 commitsSun 11:00 — 6 commitsSun 12:00 — 3 commitsSun 13:00 — 20 commitsSun 14:00 — 5 commitsSun 15:00 — 7 commitsSun 16:00 — 7 commitsSun 17:00 — 9 commitsSun 18:00 — 13 commitsSun 19:00 — 5 commitsSun 20:00 — 0 commitsSun 21:00 — 5 commitsSun 22:00 — 11 commitsSun 23:00 — 2 commitsMon 0:00 — 0 commitsMon 1:00 — 1 commitsMon 2:00 — 2 commitsMon 3:00 — 0 commitsMon 4:00 — 1 commitsMon 5:00 — 0 commitsMon 6:00 — 0 commitsMon 7:00 — 1 commitsMon 8:00 — 6 commitsMon 9:00 — 5 commitsMon 10:00 — 1 commitsMon 11:00 — 4 commitsMon 12:00 — 2 commitsMon 13:00 — 0 commitsMon 14:00 — 5 commitsMon 15:00 — 0 commitsMon 16:00 — 0 commitsMon 17:00 — 4 commitsMon 18:00 — 0 commitsMon 19:00 — 0 commitsMon 20:00 — 6 commitsMon 21:00 — 1 commitsMon 22:00 — 0 commitsMon 23:00 — 1 commitsTue 0:00 — 1 commitsTue 1:00 — 0 commitsTue 2:00 — 0 commitsTue 3:00 — 0 commitsTue 4:00 — 1 commitsTue 5:00 — 0 commitsTue 6:00 — 3 commitsTue 7:00 — 0 commitsTue 8:00 — 2 commitsTue 9:00 — 2 commitsTue 10:00 — 3 commitsTue 11:00 — 8 commitsTue 12:00 — 2 commitsTue 13:00 — 3 commitsTue 14:00 — 5 commitsTue 15:00 — 2 commitsTue 16:00 — 1 commitsTue 17:00 — 0 commitsTue 18:00 — 2 commitsTue 19:00 — 1 commitsTue 20:00 — 4 commitsTue 21:00 — 3 commitsTue 22:00 — 3 commitsTue 23:00 — 8 commitsWed 0:00 — 3 commitsWed 1:00 — 1 commitsWed 2:00 — 0 commitsWed 3:00 — 1 commitsWed 4:00 — 1 commitsWed 5:00 — 1 commitsWed 6:00 — 0 commitsWed 7:00 — 1 commitsWed 8:00 — 1 commitsWed 9:00 — 1 commitsWed 10:00 — 1 commitsWed 11:00 — 1 commitsWed 12:00 — 3 commitsWed 13:00 — 1 commitsWed 14:00 — 1 commitsWed 15:00 — 4 commitsWed 16:00 — 1 commitsWed 17:00 — 0 commitsWed 18:00 — 0 commitsWed 19:00 — 4 commitsWed 20:00 — 2 commitsWed 21:00 — 2 commitsWed 22:00 — 6 commitsWed 23:00 — 4 commitsThu 0:00 — 1 commitsThu 1:00 — 1 commitsThu 2:00 — 0 commitsThu 3:00 — 0 commitsThu 4:00 — 0 commitsThu 5:00 — 4 commitsThu 6:00 — 5 commitsThu 7:00 — 1 commitsThu 8:00 — 1 commitsThu 9:00 — 1 commitsThu 10:00 — 3 commitsThu 11:00 — 3 commitsThu 12:00 — 0 commitsThu 13:00 — 3 commitsThu 14:00 — 0 commitsThu 15:00 — 3 commitsThu 16:00 — 0 commitsThu 17:00 — 2 commitsThu 18:00 — 0 commitsThu 19:00 — 2 commitsThu 20:00 — 0 commitsThu 21:00 — 0 commitsThu 22:00 — 3 commitsThu 23:00 — 3 commitsFri 0:00 — 3 commitsFri 1:00 — 0 commitsFri 2:00 — 0 commitsFri 3:00 — 1 commitsFri 4:00 — 1 commitsFri 5:00 — 0 commitsFri 6:00 — 0 commitsFri 7:00 — 2 commitsFri 8:00 — 0 commitsFri 9:00 — 1 commitsFri 10:00 — 0 commitsFri 11:00 — 1 commitsFri 12:00 — 5 commitsFri 13:00 — 3 commitsFri 14:00 — 4 commitsFri 15:00 — 5 commitsFri 16:00 — 18 commitsFri 17:00 — 14 commitsFri 18:00 — 0 commitsFri 19:00 — 6 commitsFri 20:00 — 5 commitsFri 21:00 — 2 commitsFri 22:00 — 2 commitsFri 23:00 — 6 commitsSat 0:00 — 1 commitsSat 1:00 — 3 commitsSat 2:00 — 0 commitsSat 3:00 — 0 commitsSat 4:00 — 0 commitsSat 5:00 — 0 commitsSat 6:00 — 0 commitsSat 7:00 — 1 commitsSat 8:00 — 0 commitsSat 9:00 — 7 commitsSat 10:00 — 5 commitsSat 11:00 — 7 commitsSat 12:00 — 8 commitsSat 13:00 — 10 commitsSat 14:00 — 7 commitsSat 15:00 — 9 commitsSat 16:00 — 4 commitsSat 17:00 — 14 commitsSat 18:00 — 17 commitsSat 19:00 — 6 commitsSat 20:00 — 4 commitsSat 21:00 — 2 commitsSat 22:00 — 2 commitsSat 23:00 — 2 commits
Commit volume by weekday and hour (UTC). Larger dots mean more commits.

Who is committing

last 52 weeks
Maintainer commits443 (76%)
Community commits139 (24%)

582 commits in total over the last year.

DateListRankStars gained
Feb 2, 2026daily#18+137
Feb 1, 2026daily#15+155
Jan 31, 2026daily#21+136
Jan 27, 2026daily#11+227
Jan 11, 2026daily#18+202
  • freeCodeCamp/freeCodeCamp

    freeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.

    453.6K stars · TypeScript

  • openclaw/openclaw

    Your own personal AI assistant. Any OS. Any Platform. The lobster way. 🦞

    385.5K stars · TypeScript

  • openclaw/openclaw

    Your own personal AI assistant. Any OS. Any Platform. The lobster way. 🦞

    384.4K stars · TypeScript

  • openclaw/openclaw

    Your own personal AI assistant. Any OS. Any Platform. The lobster way. 🦞

    384.4K stars · TypeScript

  • openclaw/openclaw

    Your own personal AI assistant. Any OS. Any Platform. The lobster way. 🦞

    384.4K stars · TypeScript

  • anomalyco/opencode

    The open source coding agent.

    194.7K stars · TypeScript