How to Use Docids to Reference Specific Documents in QMD Scripts

QMD assigns every indexed markdown file a short, stable docid derived from the first six characters of its content hash, enabling path-independent document retrieval in scripts and CLI workflows.

When building automation around the tobi/qmd repository, you need a reliable way to use docids to reference specific documents in QMD scripts without hardcoding fragile file paths. Docids provide a content-addressable layer that remains stable even when files move within your collection.

What Is a Docid in QMD?

A docid is a compact, hexadecimal identifier that represents a unique markdown document in the QMD index. Unlike file paths, which change when you reorganize directories, a docid remains constant as long as the file’s content does not change.

QMD generates this identifier by computing a SHA-256 hash of the file’s raw content and extracting the first six characters. This produces a short string like #dc5590 that is easy to copy, paste, and embed in scripts.

How Docids Are Generated and Stored

The indexing logic lives in src/store.ts. When a file is processed, the indexFile() function computes the content hash and derives the docid:

// src/store.ts (lines 1497-1503)
const hash = crypto.createHash('sha256').update(content).digest('hex');
const docid = hash.substring(0, 6);  // First six characters

This mapping is stored in two places:

  • SQLite FTS5 table: The docid column links the short hash to the full-text index.
  • In-memory Map: An internal docidMap provides O(1) lookups during CLI operations.

Normalizing Docid Input

Users may reference a docid in various formats: #abc123, "abc123", or simply abc123. To handle this, src/store.ts exports normalizeDocid():

// src/store.ts (lines 1501-1507)
export function normalizeDocid(input: string): string {
  let cleaned = input.trim();
  if (cleaned.startsWith('#')) cleaned = cleaned.slice(1);
  if ((cleaned.startsWith('"') && cleaned.endsWith('"')) ||
      (cleaned.startsWith("'") && cleaned.endsWith("'"))) {
    cleaned = cleaned.slice(1, -1);
  }
  return cleaned.toLowerCase();
}

This ensures that all variants are treated uniformly before lookup.

Retrieving Documents by Docid

The core lookup routine findDocumentByDocid() (also in src/store.ts) receives a normalized short hash, searches the index for a matching row, and returns the original file path and full hash:

// src/store.ts (lines 1530-1540)
export function findDocumentByDocid(
  db: Database,
  shortHash: string
): { filepath: string; hash: string } | null {
  const stmt = db.prepare(
    "SELECT filepath, hash FROM documents WHERE docid = ?"
  );
  return stmt.get(shortHash) || null;
}

All higher-level operations—search, CLI, and MCP—call this helper when the user supplies a docid.

Using Docids in CLI Workflows

Search Output

Every search format (--json, --csv, --md, --files, default) appends the docid to each result. In src/qmd.ts, the formatter prepends # to the short hash:


# Search and see docids in JSON output

qmd search "quarterly review" --json

# Output includes: {"docid":"#dc5590", "title":"Q3 Review", ...}

Retrieving Documents

The qmd get command accepts either a file path or a docid. Internally, it runs a fuzzy-lookup flow that first tries a direct path, then falls back to findDocumentByDocid:


# Retrieve by docid

qmd get "#dc5590"

# Or without the hash symbol

qmd get "dc5590"

This is implemented in src/qmd.ts (lines 748-754).

Programmatic Access in Scripts

When writing QMD scripts in TypeScript or JavaScript, import the store utilities to resolve docids manually:

import { findDocumentByDocid, normalizeDocid } from "./src/store.js";

// Resolve a docid to a file path
const { filepath, hash } = findDocumentByDocid(db, "dc5590");
console.log(`Document ${hash.slice(0,6)} located at: ${filepath}`);

Use normalizeDocid() to sanitize user input before lookup:

function resolveUserInput(input: string) {
  const docid = normalizeDocid(input);          // Handles "#dc5590" or '"dc5590"'
  const match = findDocumentByDocid(db, docid);
  return match?.filepath ?? null;
}

MCP and Chat-Based Interfaces

For AI assistants and chat-based tools, the MCP (Model Context Protocol) server exposes docids in document listings. The "get" action accepts a docid prefixed with #:

// src/mcp.ts (lines 361-368)
// MCP tools can call:
// { action: "get", docid: "#dc5590" }

This allows LLM-based scripts to reference documents without needing filesystem context.

Why Use Docids?

  • Path-independent references: Store #abc123 instead of fragile relative paths like ../notes/2024/q3.md. Moving files within the collection does not break scripts.
  • Stability across clones: As long as content remains identical, the docid is consistent across different machines and git clones.
  • Compact identifiers: Six-character strings are easier to embed in URLs, markdown links, and chat messages than full SHA-256 hashes or long file paths.

Summary

  • A docid is the first six characters of a file’s SHA-256 content hash, providing a stable, path-independent identifier.
  • Docids are generated in src/store.ts and stored in the SQLite FTS5 index alongside file paths.
  • Use normalizeDocid() to sanitize user input and findDocumentByDocid() to resolve short hashes to full paths programmatically.
  • The CLI commands qmd search and qmd get both support docid references, outputting and accepting the #abc123 format.
  • MCP integrations allow AI assistants to reference documents by docid in chat-based workflows.

Frequently Asked Questions

What happens if two files have the same first six hash characters?

While statistically unlikely due to the uniform distribution of SHA-256 hashes, findDocumentByDocid() in src/store.ts returns the first match found in the database. In practice, collisions are rare enough that six characters provide sufficient uniqueness for most document collections. If absolute uniqueness is required, use the full 64-character hash stored in the hash column.

Yes. Because docids are stable content identifiers, you can embed them in markdown links or documentation. When processed by QMD-aware tools, #abc123 can be resolved to the current file path via findDocumentByDocid(). For external tools, you may need to implement a lookup layer that queries the QMD SQLite database directly.

How do I find the docid for a specific file?

Run qmd search with a query that matches the file’s content or title, and include the --json or --md flag. The output will include the docid field (e.g., "#dc5590"). Alternatively, if you know part of the file name, search for it and extract the docid from the results. There is currently no direct "show docid for path" command, but you can script this using the findDocumentByDocid logic in reverse by querying the database for the path and reading the docid column.

Are docids stable when I edit a file?

No. Because docids are derived from the SHA-256 hash of file content, any modification—even a single character change—will generate a new hash and therefore a new docid. This is by design: docids identify specific content versions, not logical documents over time. If you need stable references across edits, you must maintain a separate mapping layer or use the file path directly.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →