Skip to content

Search improvements, export/import, MCP server, and Smithery submission#9

Merged
miyaontherelay merged 8 commits into
mainfrom
claude/session-search-comparison-TMM5i
Jun 4, 2026
Merged

Search improvements, export/import, MCP server, and Smithery submission#9
miyaontherelay merged 8 commits into
mainfrom
claude/session-search-comparison-TMM5i

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

Summary

  • --json output on all commands (search, recent, show, session, stats, pack, resume) for scripting and piping
  • FTS5 boolean operators in search: AND, OR, NOT (uppercase), leading - to exclude, trailing * for prefix wildcards
  • prompt_hash column (SHA-256[:16]) added to the schema for dedup and fast lookup; existing DBs migrate automatically
  • pack command — assembles a token-budget-aware evidence bundle from search results, ready to paste into a new LLM context
  • resume command — finds the best matching session and prints the exact CLI command to continue it (claude --resume, codex resume, etc.)
  • Semantic exit codes — exit 1 on no results / not found, 0 on success
  • export command — dump history to JSONL (stdout, file, or .gz) or SQLite; supports --source, --project, --since filters
  • import command — ingest an exported JSONL/.gz/.db with dedup and --dry-run preview
  • TypeScript MCP server (sdk-ts/src/mcp-server.ts) — 6 tools (search_history, get_session, get_context, recent_history, pack_evidence, history_stats) with a system prompt, tool annotations, and Smithery-ready descriptions
  • smithery.yaml added to sdk-ts/ for Smithery submission
  • Removed Python MCP servercmd_mcp dropped (~430 lines); MCP users use npx ai-hist-mcp instead (tracks Migrate core logic to Rust crate to eliminate Python/TypeScript duplication #8)
  • TypeScript SDK gains getEntry() and getInTimeWindow() methods powering get_context

Test plan

  • python3 -m pytest test_ai_hist.py — 141 tests, all passing
  • cd sdk-ts && npm run build — clean TypeScript compile
  • Smoke-test ai-hist search, pack, resume, export, import against a real history DB
  • Verify npx ai-hist-mcp connects as an MCP server in Claude Code

Generated by Claude Code

claude added 5 commits May 31, 2026 04:43
- Add --json flag to search, recent, show, session, stats for machine-readable
  output; structured exit codes (1=no results, used in search/show/context/session)
- FTS5 operator auto-detection in search/pack/resume: AND/OR/NOT tokens and -/
  * prefixes/suffixes pass through raw; plain terms still get quoted for safety.
  Add --fts flag to force raw passthrough explicitly.
- Add prompt_hash column (SHA-256[:16], indexed) on history table; stored on all
  new inserts across claude/codex/cursor/relay parsers; existing DBs migrated
  via ALTER TABLE in init_db.
- New `pack` command: token-budget-aware evidence bundle with numbered entries +
  resume commands, designed for pasting into an LLM context window.
- New `resume` command: FTS search → best matching session → print exact resume
  command (claude --resume / codex resume / cursor-agent --resume=); ideal for
  shell aliases.
- Centralise _resume_cmd / _shell_quote / _build_fts_query / _row_to_dict as
  shared helpers; cmd_show reuses _resume_cmd instead of duplicating logic.
- Update tests: parser assertions include prompt_hash; not-found/no-results
  cases now assert SystemExit(1).

https://claude.ai/code/session_01P9bLkkL9takMHdm2i3uq27
Exposes five MCP tools over stdio JSON-RPC 2.0, backed by the existing
SQLite FTS5 database — no extra deps, no daemon, no embeddings service:

  search_history   — FTS5 search across Claude/Codex/Cursor/Relay history
  get_session      — full prompt list for a session, with resume command
  get_context      — same-session + ±N-minute time-window view around an entry
  recent_history   — N most recent entries with source/project filters
  pack_evidence    — token-budget evidence bundle designed for LLM handoff

All tool errors surface as MCP isError responses rather than crashes.
Wire into Claude Code or Cursor via MCP config:

  { "mcpServers": { "ai-hist": { "command": "ai-hist", "args": ["mcp"] } } }

This is the only MCP history server covering Claude Code + Codex + Cursor
in a single index; ai-agent-history-rag-mcp (the closest prior art)
omits Cursor and requires LanceDB + an Ollama embeddings service.

https://claude.ai/code/session_01P9bLkkL9takMHdm2i3uq27
Export history to JSONL (default) or SQLite for backup or sharing with
colleagues. Import deduplicates against the existing DB so it is safe to
run multiple times or merge overlapping exports.

  ai-hist export                           # JSONL to stdout (pipeable)
  ai-hist export history.jsonl.gz          # gzip-compressed JSONL
  ai-hist export backup.db --format sqlite # full SQLite file
  ai-hist export --source claude --since 2025-01-01 recent.jsonl
  ai-hist import colleague.jsonl           # JSONL or .db, with dedup
  ai-hist import colleague.jsonl --dry-run # preview without writing

Key behaviours:
- JSONL rows carry source/session_id/project/prompt/prompt_hash/timestamp_ms
- SQLite export uses init_db so the file is immediately usable as $AI_HIST_DB
- Import tolerates older exports that lack prompt_hash (backfills via SHA-256)
- Filtering: --source, --project, --since on export
- 15 new tests covering stdout/file/gz/sqlite/dedup/dry-run/roundtrip

https://claude.ai/code/session_01P9bLkkL9takMHdm2i3uq27
- New sdk-ts/src/mcp-server.ts: stdio MCP server via @modelcontextprotocol/sdk
  with 6 tools (search_history, get_session, get_context, recent_history,
  pack_evidence, history_stats) and a system prompt guide
- sdk-ts/smithery.yaml: runtime: typescript for Smithery submission
- sdk-ts/package.json: ai-hist-mcp bin entry, mcp-server export, keywords
- sdk-ts/src/index.ts: getEntry() and getInTimeWindow() methods for get_context

https://claude.ai/code/session_01P9bLkkL9takMHdm2i3uq27
The zero-dependency Python MCP implementation duplicated ~430 lines of logic
already covered by sdk-ts/src/mcp-server.ts. MCP users should use the
TypeScript package (npx ai-hist-mcp). Tracks issue #8.

https://claude.ai/code/session_01P9bLkkL9takMHdm2i3uq27
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

Warning

Review limit reached

@miyaontherelay, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 47 minutes and 33 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fb129941-9c68-4fc3-be21-92808b32f20d

📥 Commits

Reviewing files that changed from the base of the PR and between 4657b65 and e57bd0a.

📒 Files selected for processing (1)
  • sdk-ts/README.md
📝 Walkthrough

Walkthrough

This PR adds prompt deduplication via SHA-256 hashing and complete import/export capability to the AI history tool. The Python CLI gains new pack and resume commands, unified JSON output, stricter error handling, and an MCP server for tool exposure. The TypeScript SDK adds query methods and wraps the CLI via Model Context Protocol.

Changes

Prompt Hash & History Portability

Layer / File(s) Summary
Prompt hash storage and migration
ai-hist
New prompt_hash TEXT column added to SQLite history table; init_db migrates existing databases with column addition and index creation; imports gzip and hashlib.
Hash computation in all parsers and sync paths
ai-hist
Claude and Codex parsers compute prompt_hash; Codex sync, Cursor transcript, Relaycast channel/DM, and generic sync inserts persist prompt_hash via INSERT OR IGNORE; Relaycast message-to-row always includes it.
FTS, resume, and output formatting helpers
ai-hist
Helpers introduced: _prompt_hash, _shell_quote, _resume_cmd, _build_fts_query, and row→dict/JSON helpers.
Enhanced search, recent, show, context, session, and stats commands
ai-hist
Commands gain --json outputs, consistent exit status 1 on empty/not-found cases, FTS query building via _build_fts_query, and standardized resume_cmd/session_count fields in show.
Evidence bundling and session selection commands
ai-hist
New cmd_pack creates token-budgeted numbered evidence bundles; new cmd_resume selects best resumable session via FTS and outputs a resume command or JSON; both return exit code 1 on no matches.
Export and import with JSONL and SQLite support
ai-hist
cmd_export writes JSONL (optionally gzipped) or a self-contained SQLite backup including prompt_hash with source/--since filters; cmd_import reads JSONL/gz or SQLite, backfills missing prompt_hash, supports dry-run, and reports counts.
CLI argument parsing and command dispatch
ai-hist
Updated arguments add --json/--fts flags and new subcommands pack, resume, export, and import; dispatch map updated.
TypeScript SDK method additions and package updates
sdk-ts/src/index.ts, sdk-ts/package.json, sdk-ts/smithery.yaml
AiHist gains getEntry(id) and getInTimeWindow(timestampMs, windowMs); package version bumped to 0.3.0; added @modelcontextprotocol/sdk and zod; new ./mcp-server export and ai-hist-mcp bin; Smithery runtime set to TypeScript.
MCP server exposing history as tools
sdk-ts/src/mcp-server.ts
New Node.js MCP server (ai-hist) lazily initializes AiHist and exposes tools: search_history, get_session, get_context, recent_history, pack_evidence, and history_stats; includes system prompt and stdio transport connect.
Test updates for parsers, exit codes, and export/import
test_ai_hist.py
Parser tests now expect prompt_hash; CLI tests assert exit code 1 for empty/not-found cases; new export/import tests validate JSONL/gz/SQLite formats, dedup, backfill, dry-run, and roundtrip behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • AgentWorkforce/ai-hist#7: This PR incorporates build_codex_session_map and _backfill_codex_projects logic alongside the new prompt_hash column migration and Codex inserts.

Poem

🐰 A hash for every whisper, a backup in hand,
Export and import across the land,
MCP tools expose what history holds,
Search, pack, resume—the future unfolds!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the major changes: search improvements (FTS5 operators, --json), export/import commands, MCP server implementation, and Smithery submission. All four main components are represented concisely.
Description check ✅ Passed The description is highly detailed and directly related to the changeset, covering all major features (--json output, FTS5 operators, prompt_hash, pack/resume commands, exit codes, export/import, MCP server, Smithery, and SDK methods) with clear explanations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/session-search-comparison-TMM5i

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a major update to the ai-hist tool, adding database migration support for a new prompt_hash column, new CLI commands for exporting/importing history (supporting JSONL and SQLite formats), and a new pack command for generating token-budget-aware evidence bundles. Crucially, it also implements a TypeScript SDK extension and a new Model Context Protocol (MCP) server that exposes session history as tools for AI coding agents. Feedback on these changes highlights several robustness issues, including a potential crash in FTS5 query building when search terms contain double quotes, a filesystem pollution issue where sqlite3.connect creates empty files for non-existent paths, a potential TypeError during dry-run imports when prompts are null, and logic errors in the MCP server's get_context tool when handling entries without a session ID. Additionally, it is recommended to replace the custom shell quoting helper with the standard shlex.quote module.

Comment thread ai-hist Outdated
Comment on lines +103 to +117
def _build_fts_query(terms, raw=False):
"""Build an FTS5 query string from a list of terms.

Auto-detects FTS5 operators (uppercase AND/OR/NOT, leading -, trailing *,
pre-quoted phrases) and passes through raw. Otherwise quotes each term for
literal matching, which is the safe default for most user input.
Use raw=True or --fts to force raw FTS5 expression passthrough.
"""
if raw:
return " ".join(terms)
fts_ops = {"AND", "OR", "NOT"}
for t in terms:
if t in fts_ops or t.startswith("-") or t.endswith("*") or (t.startswith('"') and t.endswith('"')):
return " ".join(terms)
return " ".join(f'"{t}"' for t in terms)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If any search term contains a double quote, the query builder will produce invalid FTS5 syntax (e.g., "he"llo"), causing a sqlite3.OperationalError crash. Double quotes inside FTS5 quoted strings must be escaped by doubling them (e.g., "he""llo").

def _build_fts_query(terms, raw=False):
    """Build an FTS5 query string from a list of terms.

    Auto-detects FTS5 operators (uppercase AND/OR/NOT, leading -, trailing *,
    pre-quoted phrases) and passes through raw. Otherwise quotes each term for
    literal matching, which is the safe default for most user input.
    Use raw=True or --fts to force raw FTS5 expression passthrough.
    """
    if raw:
        return " ".join(terms)
    fts_ops = {"AND", "OR", "NOT"}
    for t in terms:
        if t in fts_ops or t.startswith("-") or t.endswith("*") or (t.startswith('"') and t.endswith('"')):
            return " ".join(terms)
    return " ".join(f'" {t.replace(\'"\', \'""\')}"' for t in terms)

Comment thread ai-hist
Comment on lines +78 to +82
def _shell_quote(value):
import re
if re.match(r'^[A-Za-z0-9_./-]+$', value):
return value
return "'" + value.replace("'", "'\\''") + "'"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of implementing a custom regex-based shell quoting helper, use Python's standard library shlex.quote which is robust and handles all shell quoting edge cases securely.

def _shell_quote(value):
    import shlex
    return shlex.quote(value)

Comment thread ai-hist
Comment on lines +1170 to +1174
def _load_rows_from_sqlite(path):
"""Load rows from an exported SQLite DB, tolerating missing prompt_hash."""
try:
src = sqlite3.connect(path)
cols = {r[1] for r in src.execute("PRAGMA table_info(history)").fetchall()}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the specified SQLite file does not exist, sqlite3.connect(path) will automatically create an empty database file before failing with a 'no such table' error. This leaves stray empty files on the filesystem. Check if the file exists before attempting to connect.

def _load_rows_from_sqlite(path):
    """Load rows from an exported SQLite DB, tolerating missing prompt_hash."""
    if not os.path.exists(path):
        print(f"Error: File '{path}' does not exist.", file=sys.stderr)
        sys.exit(1)
    try:
        src = sqlite3.connect(path)
        cols = {r[1] for r in src.execute("PRAGMA table_info(history)").fetchall()}

Comment thread ai-hist
Comment on lines +1238 to +1245
for row in rows[:5]:
source = row.get("source", "?")
project = row.get("project") or ""
prompt = row.get("prompt", "")[:80].replace("\n", " ")
ts_ms = row.get("timestamp_ms", 0)
dt = time.strftime("%Y-%m-%d %H:%M", time.localtime(ts_ms / 1000)) if ts_ms else "?"
proj = f" [{project}]" if project else ""
print(f" {dt} ({source}){proj} {prompt}")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If row.get("prompt") is None in the imported data, row.get("prompt", "") will return None (since the key exists but has a null value). This causes None[:80] to raise a TypeError during dry-run. Coerce the prompt to a string safely.

Suggested change
for row in rows[:5]:
source = row.get("source", "?")
project = row.get("project") or ""
prompt = row.get("prompt", "")[:80].replace("\n", " ")
ts_ms = row.get("timestamp_ms", 0)
dt = time.strftime("%Y-%m-%d %H:%M", time.localtime(ts_ms / 1000)) if ts_ms else "?"
proj = f" [{project}]" if project else ""
print(f" {dt} ({source}){proj} {prompt}")
for row in rows[:5]:
source = row.get("source", "?")
project = row.get("project") or ""
prompt = (row.get("prompt") or "")[:80].replace("\n", " ")
ts_ms = row.get("timestamp_ms", 0)
dt = time.strftime("%Y-%m-%d %H:%M", time.localtime(ts_ms / 1000)) if ts_ms else "?"
proj = f" [{project}]" if project else ""
print(f" {dt} ({source}){proj} {prompt}")

Comment thread sdk-ts/src/mcp-server.ts
Comment on lines +232 to +245
const lines: string[] = [];
if (entry.sessionId) {
const sessionEntries = hist.getSession(entry.sessionId);
lines.push(`=== Session ${entry.sessionId} (${sessionEntries.length} entries) ===`);
for (const e of sessionEntries) {
const dt = new Date(e.timestampMs).toISOString().slice(0, 16).replace("T", " ");
const marker = e.id === id ? ">>>" : " ";
lines.push(`${marker} [${dt}] #${e.id} (${e.source}) ${e.prompt.slice(0, 150).replace(/\n/g, " ")}`);
}
}
const windowMs = window_minutes * 60 * 1000;
const nearby = hist
.getInTimeWindow(entry.timestampMs, windowMs)
.filter((e) => e.sessionId !== entry.sessionId && e.id !== id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the target entry has no sessionId (e.g., null), it is never printed in the output. Additionally, the filter e.sessionId !== entry.sessionId becomes e.sessionId !== null, which incorrectly filters out any other nearby entries that also lack a session ID. Handle the null session ID case gracefully to display the target entry and include other sessionless entries in the nearby context.

      const lines: string[] = [];
      if (entry.sessionId) {
        const sessionEntries = hist.getSession(entry.sessionId);
        lines.push('=== Session ' + entry.sessionId + ' (' + sessionEntries.length + ' entries) ===');
        for (const e of sessionEntries) {
          const dt = new Date(e.timestampMs).toISOString().slice(0, 16).replace('T', ' ');
          const marker = e.id === id ? '>>>' : '   ';
          lines.push(marker + ' [' + dt + '] #' + e.id + ' (' + e.source + ')  ' + e.prompt.slice(0, 150).replace(/\n/g, ' '));
        }
      } else {
        const dt = new Date(entry.timestampMs).toISOString().slice(0, 16).replace('T', ' ');
        lines.push('=== Target Entry #' + entry.id + ' (No Session) ===');
        lines.push('>>> [' + dt + '] (' + entry.source + ')  ' + entry.prompt.slice(0, 150).replace(/\n/g, ' '));
      }
      const windowMs = window_minutes * 60 * 1000;
      const nearby = hist
        .getInTimeWindow(entry.timestampMs, windowMs)
        .filter((e) => e.id !== id && (entry.sessionId === null || e.sessionId !== entry.sessionId));

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
test_ai_hist.py (1)

1628-1628: ⚡ Quick win

Use descriptive variable name instead of l.

The static analysis tool flags l as ambiguous (Ruff E741). Consider using line for clarity.

Example fix for line 1628
-        lines = [l for l in captured.out.strip().splitlines() if l]
+        lines = [line for line in captured.out.strip().splitlines() if line]

Apply the same pattern to the other flagged lines.

Also applies to: 1645-1645, 1659-1659, 1688-1688, 1701-1701

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test_ai_hist.py` at line 1628, Replace the ambiguous single-letter loop
variable `l` in the list comprehension that builds `lines` (currently written as
`[l for l in captured.out.strip().splitlines() if l]`) with a descriptive name
like `line`; update the comprehension to `[line for line in
captured.out.strip().splitlines() if line]`, and apply the same rename for the
other occurrences flagged (the comprehensions at the other locations referenced
in the comment: lines using `l` at the other occurrences).
ai-hist (1)

1291-1293: 💤 Low value

Duplicate section comment.

Lines 1291 and 1293 both contain the same # --- CLI --- comment header.

🧹 Remove duplicate comment
-# --- CLI ---------------------------------------------------------------------
-
 # --- CLI ---------------------------------------------------------------------
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ai-hist` around lines 1291 - 1293, Remove the duplicated section header by
deleting the redundant "# --- CLI
---------------------------------------------------------------------" line so
only one "# --- CLI
---------------------------------------------------------------------" remains;
search for the exact string "# --- CLI ---" (full dashed variant shown in the
diff) and remove the second occurrence to avoid duplicate section comments.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ai-hist`:
- Around line 1198-1201: The import loop silently drops malformed JSON lines
because the except json.JSONDecodeError block just passes; add a parse_errors
counter (e.g., parse_errors = 0 before the loop) and increment it inside the
except block instead of passing, keep collecting valid rows via
rows.append(json.loads(line)), and then propagate/return the parse_errors count
to the caller (update cmd_import to receive and include parse_errors in the
final import summary or log). Ensure the variables referenced are rows,
json.loads, the except json.JSONDecodeError handler, and cmd_import so the
summary reports how many lines were skipped due to parse errors.
- Around line 103-117: The _build_fts_query function can produce malformed FTS5
syntax when a term contains embedded double quotes; fix it by sanitizing terms
before wrapping them in quotes: inside _build_fts_query (when raw is False and
the function falls through to the quoted branch), escape embedded double quotes
(and backslashes) in each term (e.g., replace backslash with double-backslash
then replace " with \") before constructing the f'"{t}"' string so queries like
foo"bar become valid FTS5 phrases; keep the existing early passthrough logic for
explicit FTS operators and raw=True unchanged.

In `@sdk-ts/src/mcp-server.ts`:
- Around line 114-162: The tool description for search_history falsely claims
FTS5 boolean operators and prefix matching while hist.search() (implemented in
index.ts) uses plain LIKE substring matching; fix by updating the search_history
tool text and the query .describe(...) to accurately state it performs
case-insensitive substring (LIKE-style) matching with literal interpretation of
characters (no AND/OR/NOT, no leading - exclusion, no trailing * prefix), and
update the identical claim in the system prompt so messaging matches actual
behavior of hist.search(); reference the search_history handler and
hist.search() when making edits.

---

Nitpick comments:
In `@ai-hist`:
- Around line 1291-1293: Remove the duplicated section header by deleting the
redundant "# --- CLI
---------------------------------------------------------------------" line so
only one "# --- CLI
---------------------------------------------------------------------" remains;
search for the exact string "# --- CLI ---" (full dashed variant shown in the
diff) and remove the second occurrence to avoid duplicate section comments.

In `@test_ai_hist.py`:
- Line 1628: Replace the ambiguous single-letter loop variable `l` in the list
comprehension that builds `lines` (currently written as `[l for l in
captured.out.strip().splitlines() if l]`) with a descriptive name like `line`;
update the comprehension to `[line for line in captured.out.strip().splitlines()
if line]`, and apply the same rename for the other occurrences flagged (the
comprehensions at the other locations referenced in the comment: lines using `l`
at the other occurrences).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c4c9d7f9-4028-42a2-bd49-d1cb58f26266

📥 Commits

Reviewing files that changed from the base of the PR and between eedb5b2 and c0b94f8.

⛔ Files ignored due to path filters (1)
  • sdk-ts/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • ai-hist
  • sdk-ts/package.json
  • sdk-ts/smithery.yaml
  • sdk-ts/src/index.ts
  • sdk-ts/src/mcp-server.ts
  • test_ai_hist.py

Comment thread ai-hist Outdated
Comment thread ai-hist
Comment on lines +1198 to +1201
try:
rows.append(json.loads(line))
except json.JSONDecodeError:
pass
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Silent data loss on malformed JSONL lines during import.

Invalid JSON lines in the import file are silently skipped. The user has no visibility into how many entries were lost. Consider tracking a parse error count and including it in the final import summary.

🔧 Proposed fix to track and report parse errors
 def _load_rows_from_jsonl(path):
     """Load rows from a JSONL (optionally gzip-compressed) export file."""
     rows = []
+    parse_errors = 0
     try:
         with _open_file(path) as f:
             for line in f:
                 line = line.strip()
                 if not line:
                     continue
                 try:
                     rows.append(json.loads(line))
                 except json.JSONDecodeError:
-                    pass
+                    parse_errors += 1
     except (OSError, EOFError) as e:
         print(f"Error reading {path}: {e}", file=sys.stderr)
         sys.exit(1)
-    return rows
+    return rows, parse_errors

Then update cmd_import to handle and report the parse_errors count.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ai-hist` around lines 1198 - 1201, The import loop silently drops malformed
JSON lines because the except json.JSONDecodeError block just passes; add a
parse_errors counter (e.g., parse_errors = 0 before the loop) and increment it
inside the except block instead of passing, keep collecting valid rows via
rows.append(json.loads(line)), and then propagate/return the parse_errors count
to the caller (update cmd_import to receive and include parse_errors in the
final import summary or log). Ensure the variables referenced are rows,
json.loads, the except json.JSONDecodeError handler, and cmd_import so the
summary reports how many lines were skipped due to parse errors.

Comment thread sdk-ts/src/mcp-server.ts
Comment on lines +114 to +162
server.tool(
"search_history",
"Search your AI coding agent history using full-text search across all prompts " +
"from Claude Code, Codex, Cursor, and Agent Relay. Returns matching entries with " +
"source, project path, session ID, and timestamp ordered by most recent first. " +
"Supports FTS5 boolean operators (AND, OR, NOT), leading - to exclude a term, and " +
"trailing * for prefix matching. Use get_session with a returned session_id to read " +
"the full conversation.",
{
query: z
.string()
.describe(
"Search query. Plain terms are matched literally. Use AND/OR/NOT (uppercase) for " +
'boolean, leading - to exclude, trailing * for prefix. ' +
'Examples: "authentication", "auth AND login", "deploy*", "refactor -test"',
),
source: z
.enum(["claude", "codex", "cursor", "relay"])
.optional()
.describe("Filter to a single agent source. Omit to search all sources."),
project: z
.string()
.optional()
.describe(
'Filter by project directory path. Substring match — use a partial path like "/myproject" or "src/api".',
),
limit: z
.number()
.int()
.min(1)
.max(100)
.optional()
.default(20)
.describe("Maximum number of results to return. Default: 20."),
},
READ_ONLY,
async ({ query, source, project, limit }) => {
try {
const hist = await getHist();
const results = hist.search(query, { source, project, limit });
if (results.length === 0) return { content: [{ type: "text", text: "No results found." }] };
const lines = [`Found ${results.length} result(s):\n`];
for (const e of results) lines.push(fmtEntry(e));
return { content: [{ type: "text", text: lines.join("\n\n") }] };
} catch (err) {
return { content: [{ type: "text", text: `Error: ${String(err)}` }], isError: true };
}
},
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Tool description overpromises FTS5 behavior the SDK's search() doesn't implement.

This tool (and the query description) advertise FTS5 boolean operators (AND, OR, NOT), leading - to exclude, and trailing * for prefix matching. But hist.search() in index.ts does plain LIKE substring matching and explicitly escapes %/_; it has no FTS5 module. As a result, a query like auth AND login or deploy* is matched literally as a substring, so the model will be told operators work and silently get wrong/empty results.

Either align the descriptions with the actual LIKE behavior, or route this tool through the Python CLI's FTS5 search. Note the same misleading claim appears in the system prompt (lines 76‑78).

📝 Suggested description alignment
   "Search your AI coding agent history using full-text search across all prompts " +
     "from Claude Code, Codex, Cursor, and Agent Relay. Returns matching entries with " +
-    "source, project path, session ID, and timestamp ordered by most recent first. " +
-    "Supports FTS5 boolean operators (AND, OR, NOT), leading - to exclude a term, and " +
-    "trailing * for prefix matching. Use get_session with a returned session_id to read " +
-    "the full conversation.",
+    "source, project path, session ID, and timestamp ordered by most recent first. " +
+    "Matches the query as a case-insensitive substring across prompt and project. " +
+    "Use get_session with a returned session_id to read the full conversation.",

Also update the query .describe(...) text (lines 126‑128) and the system prompt bullet (lines 76‑78) accordingly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk-ts/src/mcp-server.ts` around lines 114 - 162, The tool description for
search_history falsely claims FTS5 boolean operators and prefix matching while
hist.search() (implemented in index.ts) uses plain LIKE substring matching; fix
by updating the search_history tool text and the query .describe(...) to
accurately state it performs case-insensitive substring (LIKE-style) matching
with literal interpretation of characters (no AND/OR/NOT, no leading -
exclusion, no trailing * prefix), and update the identical claim in the system
prompt so messaging matches actual behavior of hist.search(); reference the
search_history handler and hist.search() when making edits.

@agent-relay-code
Copy link
Copy Markdown
Contributor

pr-reviewer applied fixes — committed and pushed 2a17f99 to this PR. The notes below describe what changed.

Reviewed the PR diff and traced the changed CLI/SDK surfaces.

I fixed one related issue: sdk-ts/README.md was stale and documented the old native/synchronous SDK API. It now matches this PR’s sql.js implementation, openAiHist(), JSONL fallback, MCP binary, and current schema.

Local checks run:

  • pytest -q -> 147 passed
  • npm test in sdk-ts -> TypeScript build passed, node test runner completed
  • Targeted smoke check for new CLI JSON/search/recent/show/session/stats/pack/resume/export/import paths
  • npm pack --dry-run verified package contents include the updated README and MCP dist files

Copy link
Copy Markdown
Contributor

@agent-relay-code agent-relay-code Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pr-reviewer applied fixes — committed and pushed 2a17f99 to this PR. The notes below describe what changed.

Reviewed the PR diff and traced the changed CLI/SDK surfaces.

I fixed one related issue: sdk-ts/README.md was stale and documented the old native/synchronous SDK API. It now matches this PR’s sql.js implementation, openAiHist(), JSONL fallback, MCP binary, and current schema.

Local checks run:

  • pytest -q -> 147 passed
  • npm test in sdk-ts -> TypeScript build passed, node test runner completed
  • Targeted smoke check for new CLI JSON/search/recent/show/session/stats/pack/resume/export/import paths
  • npm pack --dry-run verified package contents include the updated README and MCP dist files

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="ai-hist">

<violation number="1" location="ai-hist:1028">
P1: `resume` can choose non-resumable sources (e.g., relay) and fail despite matching resumable sessions.</violation>

<violation number="2" location="ai-hist:1201">
P2: Import silently ignores malformed JSONL lines, which can cause unnoticed partial imports/data loss.</violation>
</file>

<file name="sdk-ts/src/mcp-server.ts">

<violation number="1" location="sdk-ts/src/mcp-server.ts:153">
P2: `search_history` claims/accepts boolean and wildcard query syntax, but it routes to `AiHist.search()` which only does literal `LIKE` matching. MCP users will get incorrect search behavior for `AND/OR/NOT`, `-term`, and `*` queries.</violation>

<violation number="2" location="sdk-ts/src/mcp-server.ts:290">
P2: `project` filter behavior is documented as substring match, but implementation uses exact `project = ?` matching, so partial-path filters silently miss expected results.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread sdk-ts/smithery.yaml Outdated
Comment thread ai-hist
sql = (
"SELECT h.id, h.source, h.session_id, h.project, h.prompt, h.timestamp_ms "
"FROM history_fts f JOIN history h ON f.rowid = h.id "
"WHERE history_fts MATCH ? AND h.session_id IS NOT NULL AND h.session_id != '' "
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: resume can choose non-resumable sources (e.g., relay) and fail despite matching resumable sessions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ai-hist, line 1028:

<comment>`resume` can choose non-resumable sources (e.g., relay) and fail despite matching resumable sessions.</comment>

<file context>
@@ -769,47 +863,196 @@ def cmd_session(args):
+    sql = (
+        "SELECT h.id, h.source, h.session_id, h.project, h.prompt, h.timestamp_ms "
+        "FROM history_fts f JOIN history h ON f.rowid = h.id "
+        "WHERE history_fts MATCH ? AND h.session_id IS NOT NULL AND h.session_id != '' "
+        "ORDER BY h.timestamp_ms DESC LIMIT 1"
+    )
</file context>
Suggested change
"WHERE history_fts MATCH ? AND h.session_id IS NOT NULL AND h.session_id != '' "
"WHERE history_fts MATCH ? AND h.session_id IS NOT NULL AND h.session_id != '' AND h.source IN ('claude', 'codex', 'cursor') "

Comment thread ai-hist
try:
rows.append(json.loads(line))
except json.JSONDecodeError:
pass
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Import silently ignores malformed JSONL lines, which can cause unnoticed partial imports/data loss.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ai-hist, line 1201:

<comment>Import silently ignores malformed JSONL lines, which can cause unnoticed partial imports/data loss.</comment>

<file context>
@@ -823,6 +1066,230 @@ def cmd_watch(args):
+                try:
+                    rows.append(json.loads(line))
+                except json.JSONDecodeError:
+                    pass
+    except (OSError, EOFError) as e:
+        print(f"Error reading {path}: {e}", file=sys.stderr)
</file context>
Suggested change
pass
print(f"Invalid JSONL line in {path}: {line[:120]}", file=sys.stderr)
sys.exit(1)

Comment thread sdk-ts/src/mcp-server.ts
async ({ query, source, project, limit }) => {
try {
const hist = await getHist();
const results = hist.search(query, { source, project, limit });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: search_history claims/accepts boolean and wildcard query syntax, but it routes to AiHist.search() which only does literal LIKE matching. MCP users will get incorrect search behavior for AND/OR/NOT, -term, and * queries.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At sdk-ts/src/mcp-server.ts, line 153:

<comment>`search_history` claims/accepts boolean and wildcard query syntax, but it routes to `AiHist.search()` which only does literal `LIKE` matching. MCP users will get incorrect search behavior for `AND/OR/NOT`, `-term`, and `*` queries.</comment>

<file context>
@@ -0,0 +1,397 @@
+  async ({ query, source, project, limit }) => {
+    try {
+      const hist = await getHist();
+      const results = hist.search(query, { source, project, limit });
+      if (results.length === 0) return { content: [{ type: "text", text: "No results found." }] };
+      const lines = [`Found ${results.length} result(s):\n`];
</file context>

Comment thread sdk-ts/src/mcp-server.ts
async ({ n, source, project }) => {
try {
const hist = await getHist();
const entries = hist.recent({ limit: n, source, project });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: project filter behavior is documented as substring match, but implementation uses exact project = ? matching, so partial-path filters silently miss expected results.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At sdk-ts/src/mcp-server.ts, line 290:

<comment>`project` filter behavior is documented as substring match, but implementation uses exact `project = ?` matching, so partial-path filters silently miss expected results.</comment>

<file context>
@@ -0,0 +1,397 @@
+  async ({ n, source, project }) => {
+    try {
+      const hist = await getHist();
+      const entries = hist.recent({ limit: n, source, project });
+      if (entries.length === 0) return { content: [{ type: "text", text: "No history entries found." }] };
+      const lines = [`Most recent ${entries.length} entries:\n`];
</file context>

@agent-relay-code
Copy link
Copy Markdown
Contributor

Reviewed and fixed PR #9 locally.

Changes made:

  • Count and report malformed JSONL import parse errors.
  • Added regression coverage for JSONL parse error reporting.
  • Updated MCP search descriptions to match actual LIKE-based substring search.
  • Aligned TypeScript SDK project filters with documented substring behavior.
  • Removed duplicate CLI section header and cleaned ambiguous test variables.
  • Updated SDK/root docs for sql.js, async openAiHist, MCP binary, prompt_hash, and codex resume.
  • Added 0.3.0 changelog notes.

Local checks passed:

  • pytest -q: 148 passed
  • npm test in sdk-ts: TypeScript compile passed; Node test runner completed

Copy link
Copy Markdown
Contributor

@agent-relay-code agent-relay-code Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ pr-reviewer did not push — the PR branch advanced during the review, so fixes were withheld to avoid overwriting newer commits. Re-trigger the review once the branch settles. The notes below are advisory and were not pushed.

Reviewed and fixed PR #9 locally.

Changes made:

  • Count and report malformed JSONL import parse errors.
  • Added regression coverage for JSONL parse error reporting.
  • Updated MCP search descriptions to match actual LIKE-based substring search.
  • Aligned TypeScript SDK project filters with documented substring behavior.
  • Removed duplicate CLI section header and cleaned ambiguous test variables.
  • Updated SDK/root docs for sql.js, async openAiHist, MCP binary, prompt_hash, and codex resume.
  • Added 0.3.0 changelog notes.

Local checks passed:

  • pytest -q: 148 passed
  • npm test in sdk-ts: TypeScript compile passed; Node test runner completed

@agent-relay-code
Copy link
Copy Markdown
Contributor

⚠️ pr-reviewer did not push — the PR branch advanced during the review, so fixes were withheld to avoid overwriting newer commits. Re-trigger the review once the branch settles. The notes below are advisory and were not pushed.

Reviewed and fixed PR #9 locally.

Changes made:

  • ai-hist resume now skips non-resumable sources like relay.
  • JSONL import now fails loudly on malformed lines before writing partial data.
  • SDK project filters now use documented substring matching with escaped SQL wildcards.
  • MCP search descriptions now match the SDK’s literal substring behavior.
  • smithery.yaml now uses a stdio startCommand compatible with the current self-bootstrapping MCP server.
  • Added regression tests for the Python fixes and SDK project filtering.

Local checks passed:

  • python -m pytest -q: 149 passed
  • npm test in sdk-ts: 3 passed

Smithery config was checked against Smithery’s stdio/startCommand documentation: https://smithery.mintlify.dev/docs/migrations/typescript-with-smithery-cli

Copy link
Copy Markdown
Contributor

@agent-relay-code agent-relay-code Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ pr-reviewer did not push — the PR branch advanced during the review, so fixes were withheld to avoid overwriting newer commits. Re-trigger the review once the branch settles. The notes below are advisory and were not pushed.

Reviewed and fixed PR #9 locally.

Changes made:

  • ai-hist resume now skips non-resumable sources like relay.
  • JSONL import now fails loudly on malformed lines before writing partial data.
  • SDK project filters now use documented substring matching with escaped SQL wildcards.
  • MCP search descriptions now match the SDK’s literal substring behavior.
  • smithery.yaml now uses a stdio startCommand compatible with the current self-bootstrapping MCP server.
  • Added regression tests for the Python fixes and SDK project filtering.

Local checks passed:

  • python -m pytest -q: 149 passed
  • npm test in sdk-ts: 3 passed

Smithery config was checked against Smithery’s stdio/startCommand documentation: https://smithery.mintlify.dev/docs/migrations/typescript-with-smithery-cli

@agent-relay-code
Copy link
Copy Markdown
Contributor

⚠️ pr-reviewer did not push — the PR branch advanced during the review, so fixes were withheld to avoid overwriting newer commits. Re-trigger the review once the branch settles. The notes below are advisory and were not pushed.

Reviewed and fixed PR #9 locally.

Key fixes:

  • ai-hist resume now skips non-resumable sources like relay.
  • JSONL import now fails before writing when malformed/non-object lines are present.
  • SDK project filters now use escaped substring matching consistently.
  • MCP search descriptions now match actual literal substring behavior.
  • smithery.yaml now uses a stdio startCommand for the current self-bootstrapping MCP server.
  • Added Python and TypeScript regression tests.

Local checks passed:

  • python -m pytest -q: 149 passed
  • npm test in sdk-ts: 4 passed
  • python -m py_compile ai-hist

Copy link
Copy Markdown
Contributor

@agent-relay-code agent-relay-code Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ pr-reviewer did not push — the PR branch advanced during the review, so fixes were withheld to avoid overwriting newer commits. Re-trigger the review once the branch settles. The notes below are advisory and were not pushed.

Reviewed and fixed PR #9 locally.

Key fixes:

  • ai-hist resume now skips non-resumable sources like relay.
  • JSONL import now fails before writing when malformed/non-object lines are present.
  • SDK project filters now use escaped substring matching consistently.
  • MCP search descriptions now match actual literal substring behavior.
  • smithery.yaml now uses a stdio startCommand for the current self-bootstrapping MCP server.
  • Added Python and TypeScript regression tests.

Local checks passed:

  • python -m pytest -q: 149 passed
  • npm test in sdk-ts: 4 passed
  • python -m py_compile ai-hist

@agent-relay-code
Copy link
Copy Markdown
Contributor

⚠️ pr-reviewer did not push — the PR branch advanced during the review, so fixes were withheld to avoid overwriting newer commits. Re-trigger the review once the branch settles. The notes below are advisory and were not pushed.

Reviewed PR #9 locally and made fixes.

Changed:

  • Fixed FTS query construction in ai-hist so boolean searches still quote literal operands like agent-relay, and foo AND -bar emits valid FTS.
  • Added regression coverage in test_ai_hist.py.
  • Made SDK project filters use substring matching, matching CLI/MCP docs, in sdk-ts/src/index.ts.
  • Corrected MCP search wording to match actual SDK substring search in sdk-ts/src/mcp-server.ts.
  • Fixed the Codex resume example in README.md.

Local checks run:

  • python -m pytest -q -> 149 passed
  • python -m compileall -q ai-hist test_ai_hist.py
  • npm run build
  • SDK SQLite smoke test for partial project filters

Copy link
Copy Markdown
Contributor

@agent-relay-code agent-relay-code Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ pr-reviewer did not push — the PR branch advanced during the review, so fixes were withheld to avoid overwriting newer commits. Re-trigger the review once the branch settles. The notes below are advisory and were not pushed.

Reviewed PR #9 locally and made fixes.

Changed:

  • Fixed FTS query construction in ai-hist so boolean searches still quote literal operands like agent-relay, and foo AND -bar emits valid FTS.
  • Added regression coverage in test_ai_hist.py.
  • Made SDK project filters use substring matching, matching CLI/MCP docs, in sdk-ts/src/index.ts.
  • Corrected MCP search wording to match actual SDK substring search in sdk-ts/src/mcp-server.ts.
  • Fixed the Codex resume example in README.md.

Local checks run:

  • python -m pytest -q -> 149 passed
  • python -m compileall -q ai-hist test_ai_hist.py
  • npm run build
  • SDK SQLite smoke test for partial project filters

@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Jun 4, 2026

CodeAnt AI is reviewing your PR.

@miyaontherelay miyaontherelay merged commit 6f19b8e into main Jun 4, 2026
2 checks passed
@khaliqgant khaliqgant deleted the claude/session-search-comparison-TMM5i branch June 4, 2026 11:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants