npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kai-0131/projiq

v0.1.0

Published

ProjIQ — project intelligence MCP server for AI-assisted dev. Retrieves only task-relevant files, audits stale / duplicate / broken refs, runs Markdown checklists. Cuts token cost, surfaces dead docs / configs.

Downloads

105

Readme

ProjIQ

An MCP server that delivers only task-relevant files as context to AI coding tools — across code, docs, config, and markup, at any scale from a handful of files to 10,000+. Cut token cost, latency, and noise-induced accuracy loss.

Status: Phase 3 landed — vector search, dependency-graph expansion, and both re-ranker backends (Anthropic Claude and local BGE cross-encoder) are implemented and wired into search_context. An in-process file watcher keeps the index fresh between sync_index calls. Phase 4a foundations landed — the Observer records every search_context response and tails .projiq/observations.jsonl for Read events so retrieval quality can be evaluated later. Phase 5 landed — HTML / CSS / JSON chunkers and dependency extraction, four new audit tools (run_review, audit_stale, detect_duplicates, check_broken_refs), a pure-TypeScript BM25 embedder for small / offline projects (PROJIQ_EMBEDDER=bm25), and an automatic small-project BM25 advice log on sync_index. Phase 5+1 landed — claude-projects mode with JSONL chunker for Claude Code session logs and a five-field session filter (sessionId / timeFrom / timeTo / messageType / workspace) on search_context, plus extraRoots[] in config to index ~/.claude/projects/ alongside your main repo.

Why

When you ask an AI coding assistant for help, loading an entire repository wastes tokens, slows responses, and dilutes accuracy through the "lost in the middle" effect. ProjIQ selects only the files that actually matter for the current task.

How it works

task description
   ↓
[A] Vector search         → top-20 candidates                    ← Phase 1 ✅
[B] Dependency graph walk → follow imports / md links / wikilinks ← Phase 2 ✅
[C] LLM re-ranking        → score 0-10 + threshold combo          ← Phase 3 ✅
   ↓
final shortlist
   ↓
[D] Observer              → learns from what you actually used    ← Phase 4

Use cases

ProjIQ is not just about shrinking prompts — the same index + dependency graph powers three everyday workflows that span any repo size, from a handful of files to 10,000+:

  1. Day-to-day housekeeping. audit_stale surfaces files nobody has touched in N days, detect_duplicates flags byte-for-byte copies of the same file drifting across folders, and check_broken_refs catches dangling imports, dead Markdown links, missing <script src> / @import / tsconfig.extends before they break the build or publish. All three run without sync_index and without downloading bge-m3, so they work on a fresh clone.
  2. Improvement proposals for existing files. search_context pulls the handful of files actually relevant to a task (vector + dependency graph + optional LLM rerank), then run_review applies any Markdown checklist — skills/lp-seo.md, a coding-style rubric, an accessibility audit — and returns per-item pass / fail / unclear verdicts with evidence snippets. Pair it with any AI coding assistant to turn "please improve this" into a grounded, item-by-item diff.
  3. Adding what's missing. The same retrieval pipeline lets you ask "what should this repo have that it doesn't?" — feed the shortlist + a gap-analysis checklist to run_review, or use search_context to find where a new feature / config fits naturally. Good for "what settings are we missing?", "where should this new module live?", and "is there a pattern to follow?"
  4. Searching past Claude Code conversations. Point ProjIQ at ~/.claude/projects/ (see extraRoots in config) and search_context becomes a semantic search over your own past sessions — recall what you decided, what bug you hit, what alternative you ruled out. Combine with sessionId / timeFrom / timeTo / messageType / workspace filters to pinpoint a single decision moment. Unique to ProjIQ.

Claude Code session search (claude-projects mode)

Claude Code stores every conversation as a JSONL file under ~/.claude/projects/<workspace>/<uuid>.jsonl. ProjIQ can index these session logs alongside your code repo and let search_context retrieve past conversation chunks by semantic similarity + a five-field filter.

Enable it by adding the directory as an extraRoots[] entry in your .projiq.json:

{
  "include": ["**/*.md", "**/*.ts", "**/*.jsonl"],
  "extraRoots": [
    {
      "path": "~/.claude/projects",
      "name": "claude-projects"
    }
  ]
}

Then run sync_index once. The JSONL chunker emits one chunk per content-bearing message (user / assistant / tool_use / tool_result / queue-operation enqueue) and skips dequeue events and hook errors. Each chunk carries metadata in the chunk_metadata sqlite table:

| Field | Source | Used by | |-------|--------|---------| | sessionId | obj.sessionId (or filename UUID) | sessionId filter | | timestamp | obj.timestamp ISO 8601 | timeFrom / timeTo filter | | messageType | obj.type or obj.message.role | messageType filter | | parentUuid | obj.parentUuid | conversation tree reconstruction | | workspace | derived from parent directory name | workspace filter |

Example search: find the moment you decided on the API design for a feature called "billing", restricted to assistant messages last week:

{
  "task": "billing API design",
  "messageType": "assistant",
  "timeFrom": "2026-05-12T00:00:00Z",
  "timeTo":   "2026-05-19T00:00:00Z"
}

Filters combine with AND semantics; an empty intersection short-circuits to zero hits without invoking the embedder. The metadata table is dropped via sqlite ON DELETE CASCADE when a session log file is removed, so the index stays consistent with the filesystem.

See docs/COMPATIBILITY.md for platform notes on the ~/.claude/projects/ path layout.

Stack

  • Runtime: Node.js (≥ 20), TypeScript, ESM
  • Protocol: Model Context Protocol via @modelcontextprotocol/sdk
  • Vector store: LanceDB (@lancedb/lancedb)
  • Metadata store: SQLite (better-sqlite3)
  • Embeddings: Local bge-m3 (1024-dim) via @huggingface/transformers — offline, no data leaves the machine. Also a pure-TypeScript BM25 embedder (PROJIQ_EMBEDDER=bm25) — no model download, sub-second startup, well-suited for small projects (< 500 files) and air-gapped environments.
  • Code chunker: tree-sitter (TypeScript / JavaScript / Python / HTML / CSS / JSON)
  • Dependency extraction: tree-sitter for JS/TS (import / require() / dynamic import(), plus extends / implements type-refs), Python (import / from … import / relative, plus class base-class type-refs), HTML (<script src> / <link href> / <img src> / <a href>, internal refs only), and CSS (@import, url()); regex-based for Markdown [text](path) + [[wikilink]]; fast-path JSON parse for known dependency keys (extends in tsconfig, main / module in package.json, etc.). External URLs (http(s)://…) are intentionally skipped to avoid graph noise.
  • Re-ranker: swappable Reranker interface — Anthropic Claude Haiku 4.5 (claude, requires ANTHROPIC_API_KEY), BGE-reranker-base cross-encoder via @huggingface/transformers (local-bge, fully local), or none (default; pass-through using vector similarity / graph distance). Task analysis (intent / category / keywords) is fused into the Claude re-rank prompt as a single LLM call; local-bge returns only scores.

Roadmap

  • [x] Phase 0 — scaffolding, minimal MCP server
  • [x] Phase 1 — vector-search MVP: sync_index + search_context tools
  • [x] Phase 2 — dependency graph expansion (graphExpansion field in search_context response)
  • [x] Phase 3 — swappable re-ranker (ClaudeReranker, LocalBgeReranker, NullReranker fallback) wired into search_context, Task Analyzer fused into the rerank prompt, Python chunker + dependency extractor, type-ref dep kind, imported_names persisted, NullReranker fallback on rerank failure.
  • [x] Phase 4a — auto-update watcher + Observer foundation: every search_context response is recorded in query_sessions / query_session_hits, and file Read events (via PostToolUse hook → .projiq/observations.jsonl tail, or the record_read MCP tool) are persisted in reads with TTL-based session matching.
  • [x] Phase 5 — multi-language generalization + audit tools: HTML / CSS / JSON chunkers and dependency extractors, four new audit MCP tools (run_review, audit_stale, detect_duplicates, check_broken_refs), a pure-TypeScript BM25 embedder (PROJIQ_EMBEDDER=bm25) for small / offline projects, and an automatic small-project BM25 advice log on sync_index (< 500 files).
  • [ ] Phase 4 — observer / feedback loop (learning / rerank feedback)

Install

npm install @kai-0131/projiq

Or run from source:

git clone https://github.com/Kai-0131/projiq
cd projiq
npm install
npm run build

Configure

Drop a .projiq.json at your repo root to override defaults. A minimal example:

{
  "include": [
    "**/*.md", "**/*.mdx",
    "**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.mjs", "**/*.cjs",
    "**/*.py", "**/*.pyi"
  ],
  "chunking": { "maxTokens": 512, "overlapTokens": 100 },
  "embedding": { "model": "Xenova/bge-m3", "dimensions": 1024 },
  "search": {
    "defaultLimit": 20,
    "maxLimit": 100,
    "graphDepth": 2,
    "graphDirection": "forward"
  },
  "maxFileSizeBytes": 1000000,
  "respectGitignore": true
}

See src/config/schema.ts for the full schema.

Run as an MCP server

With Claude Code

Add to your .claude/mcp_servers.json (or equivalent for your client):

{
  "mcpServers": {
    "projiq": {
      "command": "node",
      "args": ["/absolute/path/to/projiq/dist/index.js"],
      "env": {
        "PROJIQ_REPO_ROOT": "/absolute/path/to/the/repo/you/want/indexed"
      }
    }
  }
}

Environment variables

| Variable | Values | Purpose | |---|---|---| | PROJIQ_REPO_ROOT | absolute path | Repo to index (default: process.cwd()). | | PROJIQ_EMBEDDER | bge (default) / bm25 / mock | bge: real bge-m3 model (accurate, requires ~500 MB–1 GB download on first run). bm25: pure-TypeScript BM25 embedder — no model download, sub-second startup, recommended for small projects (< 500 files) and offline / air-gapped dev. mock: deterministic mock for tests / offline dev. | | PROJIQ_TOKENIZER | bge (default) / whitespace / charcount | Tokenizer used for chunk-token counting. | | PROJIQ_MODEL_CACHE | absolute path | Where @huggingface/transformers caches the downloaded model. | | PROJIQ_RERANKER | claude / local-bge / none (default) | Re-ranker backend. none returns vector + graph results without re-ranking. claude additionally requires ANTHROPIC_API_KEY. | | PROJIQ_RERANK_MODEL | model id | Overrides the default for the chosen backend: claude-haiku-4-5-20251001 for claude, Xenova/bge-reranker-base for local-bge. | | ANTHROPIC_API_KEY | API key | Required when PROJIQ_RERANKER=claude. | | PROJIQ_WATCH | on (default) / off | Enable the in-process file watcher that keeps the index fresh between sync_index calls. off is useful for CI, one-shot indexing, or debugging. | | PROJIQ_WATCH_DEBOUNCE_MS | positive integer | Coalesces rapid edits to the same file before re-indexing (default 500). | | PROJIQ_OBSERVE | on (default) / off | Enable the Observer. When on, every search_context response is persisted to query_sessions / query_session_hits, and the server tails .projiq/observations.jsonl for Read events. off disables both sides (the record_read tool becomes a no-op). | | PROJIQ_OBSERVE_QUERIES | plain (default) / hash / off | How task queries are stored in query_sessions. plain keeps the text and its SHA-256, hash keeps only the hash, off stops session recording entirely (reads are still written without a matched session). | | PROJIQ_OBSERVE_MATCH_TTL_MINUTES | positive integer | How long a recorded session remains eligible to be matched to a subsequent Read (default 30). Reads that arrive after the TTL expires are stored with matched_session_id = NULL. | | PROJIQ_QUIET | 1 / true / yes | Suppress ProjIQ's informational notices (currently: the small-project BM25 advice emitted after sync_index when the indexed file count is below 500). Unset / 0 / empty string / other values leave notices enabled. Case-insensitive. |

Project size recommendations

There is no single best setting for every repo. The embedder / reranker combo matters most; the table below is the default ProjIQ suggests. sync_index will print a one-time advice line after the scan when the indexed file count is below 500 and the embedder is bge — suppress it with PROJIQ_QUIET=1.

| Project size | Recommended embedder | Recommended reranker | Rationale | |---|---|---|---| | < 500 files (personal sites, prototypes, single-topic notebooks, docs-only repos) | PROJIQ_EMBEDDER=bm25 | PROJIQ_RERANKER=claude (or none offline) | No 500 MB–1 GB bge-m3 download, sub-second startup, identifier / keyword matches carry most of the retrieval signal at this scale. Claude reranker closes the semantic gap without changing embedder. | | 500 – 10,000 files (typical application repo, small monorepo) | PROJIQ_EMBEDDER=bge (default) | PROJIQ_RERANKER=claude or local-bge | Semantic recall from bge-m3 starts to pay back the model download. claude is strongest for natural-language tasks; local-bge is fully offline. | | 10,000+ files (large monorepo) | PROJIQ_EMBEDDER=bge | PROJIQ_RERANKER=claude or local-bge, larger graphDepth | Rerank quality becomes the main bottleneck; budget for the extra LLM cost and tune search.graphDepth / search.defaultLimit to keep shortlists crisp. | | Any size, air-gapped / no API key | PROJIQ_EMBEDDER=bm25 | PROJIQ_RERANKER=none or local-bge | Fully offline retrieval path. audit_stale / detect_duplicates / check_broken_refs do not need the index at all. |

Tools

sync_index

Scan the repository, detect added/changed/deleted files by content hash, and rebuild the vector index for all non-unchanged files.

  • Input: none
  • Output: IndexStats — scanned / kept / added / changed / unchanged / deleted counts, total chunks, total tokens, duration, error list
  • Notes: First run on a new repo has to download bge-m3 (~500 MB–1 GB). Subsequent runs are fast; unchanged files are skipped via SHA-256. With PROJIQ_WATCH=on (the default) the watcher keeps the index fresh between calls; sync_index is still the authoritative full scan for bootstrapping, drift repair, and CI.

search_context

Retrieve chunks most similar to a task description, plus files reachable through the dependency graph from the top hits.

  • Input:
    • task (string, required) — natural-language query
    • limit (number, optional) — max vector hits (default 20, max 100)
    • includeContent (boolean, optional) — include raw chunk text (default false)
    • paths (string[], optional) — restrict search to these repo-relative paths
    • graphDepth (number, optional) — BFS depth over the dependency graph (default 2, max 5)
    • graphDirection (optional) — forward (default) follows imports out of seed files; reverse finds files that import seeds; both walks bidirectionally
  • Output: query, rerankerMode, taskAnalysis ({intent, category, keywords} when the Claude reranker ran, otherwise null), limit, includeContent, graphDepth, graphDirection, totalHits, and ranked hits[]. Each hit includes path, source (vector / graph / both), rerankScore (0-10), optional rerankReason, inclusion (above-threshold / min-fill), startLine/endLine, startByte/endByte, symbol, semanticType, tokenCount, distance, similarity = 1 − distance, graphDistance, viaKinds (import / md-link / wikilink / type-ref), and (when includeContent=true) content.
  • Notes: Run sync_index first. Query is embedded with the same model used for chunks. The graph walker uses file_deps rows populated during indexing (JS/TS import / require() / dynamic import() and extends / implements type-refs; Python import / from … import / relative and class base-class type-refs; Markdown [text](path); Obsidian [[wikilink]]). The vector top-N and graph expansion are deduplicated into a file-level candidate list, sent to the configured reranker (PROJIQ_RERANKER=none|claude|local-bge), and filtered by the threshold combo (min = min(3, max), max = max(1, min(limit, 10)), threshold = 6). When the claude or local-bge reranker throws, the server logs the failure and falls back to NullReranker so the tool still returns results.

Semantic types

Each hit carries a semanticType label from the chunker:

| Type | Source | |---|---| | function | Top-level function, arrow-function, or method declaration | | class | class declaration (including interfaces/types) | | variable | Top-level const / let / var / type / interface / enum declarations | | text-block | Markdown content, or fallback for unparsed regions | | split | Overflow fragment when a single unit exceeds chunking.maxTokens and is token-split |

record_read

Record that a specific file was read, so it can be linked to the most recent search_context session within the configured TTL. Most Claude Code users will prefer the PostToolUse hook path (see Observation); record_read covers programmatic clients (Cursor, scripts) that can't easily emit a hook.

  • Input:
    • file_path (string, required) — repo-relative or absolute path of the file that was read
    • read_at (string, optional) — ISO 8601 timestamp; defaults to the server's current time
  • Output: { recorded, readId, matchedSessionId, filePath, readAt, source }. recorded=false with all other fields null when PROJIQ_OBSERVE=off; otherwise recorded=true, source="tool", and matchedSessionId is the id of the matching query_sessions row when one exists within PROJIQ_OBSERVE_MATCH_TTL_MINUTES, null otherwise.
  • Notes: The tool writes to the same reads table as the hook-backed tail, so you should configure one path or the other per client (enabling both will double-record). Errors in session matching never block the insert — an unmatched Read is still stored with matched_session_id = NULL.

run_review

Apply a Markdown checklist (e.g. a lint / SEO / QA skill file) to a target file or directory. For each bullet / numbered / checkbox item, ProjIQ searches the index inside target_path for relevant evidence, then asks Claude to judge pass / fail / unclear with a short reason and an optional recommended action. Returns per-item verdicts plus a ready-to-paste Markdown report.

  • Input:
    • checklist_path (string, required) — repo-relative or absolute path to the Markdown checklist. Every - / * / + / 1. bullet (with or without [ ] / [x]) across all headings becomes one review item.
    • target_path (string, required) — repo-relative or absolute path (file or directory). Searches are restricted to files at-or-under this path.
    • reranker_mode (optional) — none / local-bge / claude. Overrides the search-side reranker for this call; independent of the LLM judge (which is always Claude). Defaults to the env-resolved mode (PROJIQ_RERANKER).
    • max_items (number, optional) — cap on checklist items judged (default 50). Items past the cap are dropped and truncated=true is returned.
  • Output: items[] (each with id, text, section, level, verdict, reason, recommendedAction?, evidence[], candidates, durationMs), summary (pass / fail / unclear / total), truncated, mdReport (Markdown rendering of the verdicts, also surfaced as the tool's text content), and stats (itemsScanned, durationMs).
  • Notes: Requires ANTHROPIC_API_KEY — the per-item judge always runs on Claude. Works alongside the small-project BM25 embedder: PROJIQ_EMBEDDER=bm25 + PROJIQ_RERANKER=claude lets run_review operate without downloading bge-m3. Every checklist item is a separate LLM call, so max_items is the primary cost knob.

audit_stale

List files whose modification time is at least threshold_days days old. Useful for spotting abandoned docs, dead configs, and out-of-date assets during routine housekeeping.

  • Input:
    • threshold_days (number, required, ≥ 0) — minimum file age in whole days. A file is reported when floor((now − mtime) / 1 day) >= threshold_days. 0 reports every scanned file.
    • path (string, optional) — repo-relative or absolute scope. When provided, only files at-or-under this path are reported.
  • Output: stale[] (filePath, lastModifiedIso, daysOld), stats (filesScanned, staleCount, durationMs).
  • Notes: Language-agnostic and index-independent — runs without sync_index and without bge-m3. Honours exclude.directories / exclude.secrets / .gitignore, but intentionally ignores include and exclude.files/exclude.binaries so stale lockfiles, images, and binary assets are still surfaced.

detect_duplicates

Find files with byte-for-byte identical content (SHA-256). Groups of 2+ matching files are returned, sorted by size descending.

  • Input:
    • path (string, optional) — repo-relative or absolute scope.
    • min_size_bytes (number, optional, ≥ 0) — files strictly smaller than this are skipped entirely. Defaults to 0 (include all). Useful for ignoring tiny boilerplate files whose duplication is noise.
  • Output: groups[] (sizeBytes, sha256, files[]), stats (filesScanned, filesHashed, duplicateGroups, duplicateFiles, durationMs).
  • Notes: Language-agnostic and index-independent. Files are first bucketed by size so only size-collision candidates are hashed. Like audit_stale, honours exclude.directories / exclude.secrets / .gitignore but ignores include / exclude.files / exclude.binaries so duplicate images, PDFs, lockfiles etc. are surfaced.

check_broken_refs

Walk the repository, extract every dependency reference, and report those whose target file does not exist. Catches dangling import paths, missing Markdown / wikilink targets, broken <script src> / <link href> / @import / url(), and unresolved type-refs — all the kinds of breakage that only fail at runtime or on publish.

  • Input:
    • path (string, optional) — repo-relative or absolute path. When provided, only broken refs whose source file is inside this path are reported; target resolution still considers the whole project.
  • Output: broken[] (srcPath, target, kindimport / md-link / wikilink / type-ref, startLine), stats (filesScanned, filesWithDeps, totalDeps, resolvedDeps, brokenDeps, durationMs).
  • Notes: Index-independent — runs without sync_index. Uses the same dependency extractors as sync_index (JS/TS / Python / Markdown / HTML / CSS / JSON), so adding new language support in Phase 5 means check_broken_refs catches strictly more breakage: dangling HTML <script src="./missing.js">, CSS @import './gone.css', tsconfig.json extends "./removed.json", etc.

ping / status

Health and diagnostics. status reports resolved paths, config source, embedder / tokenizer / reranker modes, applied migrations, a watcher block (mode, active, queueDepth, eventsProcessed, lastEventAt) for the auto-update watcher, and an observer block (mode, queriesMode, matchTtlMinutes, tailActive, tailStats, session / read counters) for the Phase 4 foundation.

Automatic incremental updates

When PROJIQ_WATCH=on (the default), the MCP server spawns an in-process chokidar watcher rooted at PROJIQ_REPO_ROOT. File creates, edits, and deletes are debounced per-path (default 500 ms; see PROJIQ_WATCH_DEBOUNCE_MS) and routed through the same single-file upsert / delete path used by sync_index, so the vector index, dependency graph, and SQLite metadata stay consistent while the server is running.

Heavy directories (node_modules/, .git/, .projiq/, dist/, build artefacts) are filtered at the chokidar layer; the remaining events are re-checked against the configured include / exclude patterns and the project's .gitignore chain, so files the full scan would skip are skipped here too. Events are processed serially to avoid SQLite / LanceDB write contention — typical editor saves produce one event and complete in tens of milliseconds.

Set PROJIQ_WATCH=off to disable the watcher (e.g. during one-shot CI indexing); sync_index continues to work either way.

Observation (Phase 4 foundation)

When PROJIQ_OBSERVE=on (the default), ProjIQ captures the raw data needed to evaluate rerank quality later. Nothing is sent off the machine and no feedback loop runs yet — this block only builds up the SQLite tables that Phase 4 proper will learn from.

What gets recorded:

  1. query_sessions / query_session_hits — every response from search_context (task, rerank mode, graph depth, the ranked hits with rerankScore / source / inclusion). Controlled by PROJIQ_OBSERVE_QUERIES: plain stores the raw task text plus its SHA-256 (default), hash keeps only the hash, off skips session recording entirely.
  2. reads — each time a file is read during the session. Reads arriving within PROJIQ_OBSERVE_MATCH_TTL_MINUTES of a session row are linked via matched_session_id; later or ProjIQ-external reads are stored unmatched (still useful for analysis).

Two write paths into reads — use whichever matches your client, not both:

  • PostToolUse hook (Claude Code) — add scripts/record-read.mjs to .claude/settings.json. The script filters for tool_name === "Read", resolves the file against CLAUDE_PROJECT_DIR, and appends a line to .projiq/observations.jsonl. The MCP server tails the file (chokidar + .projiq/observations.cursor byte offset, truncation-safe) and commits each line to reads through the same code path as the tool. The script always exits 0 so it can never block another hook.

    // .claude/settings.json
    {
      "hooks": {
        "PostToolUse": [
          {
            "matcher": "Read",
            "hooks": [
              {
                "type": "command",
                "command": "node /absolute/path/to/projiq/scripts/record-read.mjs"
              }
            ]
          }
        ]
      }
    }
  • record_read MCP tool — for programmatic clients that can't easily register a hook (Cursor, automation scripts). See the tool docs above.

Set PROJIQ_OBSERVE=off to disable the Observer end-to-end: search_context stops writing sessions, the tail is not started, and record_read becomes a recorded=false no-op. query_sessions / query_session_hits / reads remain in the SQLite schema either way (they are part of migration v5).

Workspace layout

Runtime data lives in .projiq/ under the repo root:

.projiq/
├── meta.sqlite            # files metadata, query_sessions / query_session_hits / reads
├── index.lance/           # LanceDB vector index
├── observations.jsonl     # append-only Read log written by the hook / `record_read`
├── observations.cursor    # byte offset the MCP server has tailed up to
└── cache/

Add .projiq/ to your .gitignore.

Development

npm run typecheck   # tsc --noEmit on sources + tests
npm test            # vitest run (unit + e2e via in-memory MCP transport)
npx tsx scripts/dogfood-sync.ts                   # self-index with MockEmbedder
npx tsx scripts/dogfood-sync.ts --bge             # self-index with real bge-m3 (slow first run)

Hosted version (interest signup)

ProjIQ is a local CLI / MCP server today. A hosted (browser-based, no install) version is being evaluated — if you'd use it, please open a Discussion in the Ideas category and say "interested in hosted version":

Start a Discussion

Zero responses means we ship the local-only version forever, which is also a perfectly good outcome.

License

MIT