编码代理检测与会话解析工具

franken-agent-detection 是一个 Rust 库,用于检测本机已安装的编码代理(coding agent)并以只读方式将各代理的会话历史解析为统一的对话模型,适用于本地编码代理工具链、会话检索索引与开发效率分析等场景。

  • by Dicklesworthstone
  • Rust
  • 本地部署
  • 15 星标
  • 更新于
在 GitHub 查看 Dicklesworthstone/franken_agent_detection

使用说明

franken-agent-detection

A Rust crate with two layers for working with local coding-agent tools:

  1. Detection (always available, no features): which coding agents are installed on this machine, as one stable, JSON-serializable report.
  2. Connectors (connectors feature and friends): read each agent's own session history into one normalized conversation model, with discovery, provenance, streaming and resumable ingestion. This is the parsing layer cass builds its search index on.
cargo add franken-agent-detection                                # detection only
cargo add franken-agent-detection --features all-connectors      # every connector

Everything is synchronous, local and read-only: no async runtime, no network, and no connector ever writes to an agent's store.

Detection

use franken_agent_detection::{
    detect_installed_agents, AgentDetectOptions, AgentDetectRootOverride,
};
use std::path::PathBuf;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let report = detect_installed_agents(&AgentDetectOptions {
        only_connectors: Some(vec!["codex".into(), "gemini".into()]),
        include_undetected: true,
        root_overrides: vec![AgentDetectRootOverride {
            slug: "codex".into(),
            root: PathBuf::from("/tmp/mock-codex"),
        }],
    })?;

    println!(
        "Detected {} of {}",
        report.summary.detected_count, report.summary.total_count
    );
    Ok(())
}
Item Purpose
detect_installed_agents Run filesystem probes and produce the full report
AgentDetectOptions Connector filtering (only_connectors), include_undetected, root_overrides
InstalledAgentDetectionReport format_version, installed_agents (slug, evidence, root paths), summary
default_probe_paths_tilde The ~/… probe table, e.g. for remote probing over SSH
AgentDetectError UnknownConnectors (slug not recognized)

Slugs are normalized (claude-code → claude, oh-my-pi → omp, …); unknown slugs are an explicit error. Probe roots honor the agents' own environment overrides (CODEX_HOME, PI_CODING_AGENT_DIR, CLAUDE_CONFIG_DIR, …) and root_overrides makes tests deterministic.

PI_SESSIONS_DIR adds a sessions location alongside ~/.pi/agent/sessions. Setting PI_CODING_AGENT_DIR replaces that default agent home while keeping the separate sessions override. A missing additive sessions directory does not hide an existing default history.

Custom history stores use the same environment settings as their connectors:

Agent Environment settings Detection policy
Gemini GEMINI_HOME Replaces the default session root; accepts a directory or an admitted chats/session-*.json / .jsonl file
Qwen Code QWEN_RUNTIME_DIR, QWEN_HOME Runtime directory takes precedence over the home override; detects current projects and legacy tmp stores without falling back from a missing configured root
Cline SDK CLINE_SESSION_DATA_DIR, CLINE_DATA_DIR, CLINE_DIR Resolves the SDK session directory in that order while keeping legacy extension histories independently available
Continue CONTINUE_GLOBAL_DIR Replaces the default ~/.continue home; relative paths resolve against the process working directory, and ~ remains literal
Kiro CLI v2 KIRO_HOME Replaces ~/.kiro, including sessions/cli; a missing configured home does not select another profile
OpenCode OPENCODE_STORAGE_ROOT, OPENCODE_SQLITE_DB Keeps legacy storage and SQLite independent, with default database candidates still available
Hermes HERMES_SQLITE_DB, HERMES_HOME Prefers an existing explicit database, then $HERMES_HOME/state.db, then the default store
Crush CRUSH_SQLITE_DB Prefers an existing explicit global database, with the default as fallback
Goose GOOSE_SQLITE_DB, GOOSE_PATH_ROOT Resolves the database and JSONL sessions directory independently, including their default fallbacks
Cursor CASS_CURSOR_PROJECTS_ROOT Replaces the Agent transcript root while retaining Composer detection

These remain filesystem installation probes. A configured database path must be a file; detection does not open databases or validate their schemas. The probes work without optional connector features, while reading each store requires its connector's feature. Explicit detector root_overrides stay restricted to the supplied paths. Reported roots are candidate locations; passing a database file as a scan context deliberately scopes applicable connectors to that file.

Connectors

use franken_agent_detection::{
    Connector, PiAgentConnector, ScanContext, ScanRoot, get_connector_factories,
};
use std::path::PathBuf;

fn main() -> anyhow::Result<()> {
    // Default locations for one agent.
    let ctx = ScanContext::local_default(PathBuf::from("/tmp/cass-state"), None);
    for conversation in PiAgentConnector::new().scan(&ctx)? {
        println!("{:?}: {} messages", conversation.title, conversation.messages.len());
    }

    // Every compiled-in connector, over explicit roots, streaming.
    let ctx = ScanContext::with_roots(
        PathBuf::from("/tmp/cass-state"),
        vec![ScanRoot::local(PathBuf::from("/home/me/.codex"))],
        None, // or Some(since_ms) for incremental scans
    );
    for (slug, make) in get_connector_factories() {
        make().scan_with_callback(&ctx, &mut |conversation| {
            println!("{slug}: {:?}", conversation.external_id);
            Ok(())
        })?;
    }
    Ok(())
}

Every connector produces NormalizedConversations: agent_slug, external_id, title, workspace, source_path, timestamps, metadata, and NormalizedMessages (role, author, content, created_at, structured tool and skill invocations with skill/Skill/load_skill/use_skill wrappers normalized via unwrap_skill_invocations, and the raw provider record in extra). All 34 connector types (including MuseConnector) and scan helpers (canonical_connector_slug, ScanProgressTick, unwrap_skill_invocations, percent_decode_utf8) are re-exported at the crate root. Connector-derived annotations live under extra.cass (for example branch and context membership for Pi sessions, or cass.tool_call_count across tool-using turns). extract_tokens_for_agent turns a message's extra into exact API token usage where the agent records it (and an estimate otherwise) while preserving model, provider (anthropic, openai, google, mistral, xai, moonshot, deepseek, or explicit provider metadata), and tool_call_count / has_tool_calls across all 34 connectors (including qualified openclaw/<agent> multi-agent slugs and all canonical connector aliases).

Claude Code session files of 32 MiB or larger retain compact metadata in extra.cass instead of duplicating the full raw record. Reply identifiers are preserved as strings at these locations:

Identifier Raw extra Compact extra
Message ID message.id cass.message_id
Request ID requestId cass.request_id

Claude Code can repeat a reply's full token usage in multiple content-block records. Compaction does not merge or remove normalized messages, and token extraction is per record. Consumers aggregating usage should count each identified (message_id, request_id) pair once within a conversation. Compaction omits missing or non-string identifiers. Records without both nonblank identifiers must not be merged under a shared default key. Compaction retains the model, usage, tool-call count, and existing attachment metadata without retaining the raw message content a second time.

Beyond scan:

API Purpose
discover_source_files Pre-parse list of the files a scan will read (role, origin, size/mtime), so hosts can mirror sources before parsing
scan_with_callback / supports_streaming_scan Emit conversations incrementally instead of materializing the corpus (supported by all 34 connectors)
scan_with_source_boundaries / supports_source_boundaries Resumable ingestion: a pre-parse skip predicate and a per-source completion event (with required sidecars such as SQLite WALs), supported by 29 single-source connectors
ScanRoot::remote + Origin + with_mirrored_source_path Scan synced copies of remote machines across all 30 store-based connectors (ScanRoot::mirrored_store), keeping host provenance
ScanRoot::with_rewrite / with_agent_rewrite / with_mapping (apply_workspace_rewrite) Rewrite remote workspace prefixes (from -> to, optionally scoped to canonical agent slugs/aliases) and record metadata.cass.workspace_original across all 34 connectors
ScanContext::with_progress_tick (progress_tick) Host stall-watchdog liveness callback (cass#373 Variant A), invoked per scanned source/session across all 34 connectors
CASS_EXCLUDE_PATHS Comma/newline-separated files or directories that all 34 connectors skip before opening or parsing them. Entries match whole path components; relative, .. and symlinked spellings of an existing directory match too. Exact Codex JSONL exclusions cover both the plain and compressed (.jsonl.zst) representation

Aider accepts an exact .aider.chat.history.md as its scan context, including an intentionally selected archived copy. A missing exact file yields no history. Directory contexts keep the existing Cass state-directory fallback, but names such as cassie and cassette-player remain scoped to that directory. CASS_AIDER_DATA_ROOT takes precedence over the context directory; explicit ScanRoots take precedence over both.

The copilot / github-copilot connector reads VS Code chat stores and extension exports. Copilot CLI histories belong to copilot_cli, including when both connectors scan one supplied home directory. Detection and source discovery use the same ownership split. Supplied VS Code session files and native store containers remain scoped to that location, including empty or missing stores; they do not fall back to unrelated live histories.

Native Copilot responses retain whole-turn output usage after append-log replay, once per assistant response. Valid modelTotals take precedence over saved completionTokens, including explicit zero totals. Per-model input, cache and output counters are retained in extra.cass.model_totals; only output is normalized as API usage because the saved input/cache relationship differs between backends. Latest-call prompt counts and session-wide credits are not added. Mixed-model turns retain their individual model labels without being attributed to the selected model alone. Invalid or overflowing totals do not fall back to an older saved count.

Cline reads extension task stores at tasks/<taskId>/ui_messages.json, with api_conversation_history.json as the fallback when UI history is absent. Legacy direct <store>/<taskId> layouts remain supported. Exact transcript files, task directories, tasks directories and extension roots keep their scope, and overlapping roots share one task selection. Native ask / say records expose user, assistant and tool roles while preserving their raw subtypes.

Cline also reads version-1 SDK lead-session snapshots at ~/.cline/data/sessions/<id>/<id>.messages.json, together with their neighboring <id>.json manifests. CLINE_SESSION_DATA_DIR selects the sessions directory; otherwise CLINE_DATA_DIR/sessions, CLINE_DIR/data/sessions, and the default location apply in order. Overrides are trimmed literal paths without tilde expansion. Explicit roots stay scoped. Source discovery includes the required manifest, and a change to either file re-reads the complete session. Snapshot order, message IDs, model identity, thought text and readable tool activity are preserved in compact normalized messages without duplicating binary payloads.

When an SDK migration and its original Cline UI history are both in the supplied scope, the connector uses the original history followed by the resumed SDK turns. The copied cumulative usage before the native resume marker is counted once. Matching respects source origin; standalone SDK copies keep their saved history. SDK teammate/child artifacts and model-context compaction files are not imported as separate conversations.

Cline token extraction reads recorded tokensIn, tokensOut, cacheWrites and cacheReads from request, deleted-request and subagent usage rows. These are four independent buckets. Legacy request start/finish pairs retain all transcript rows, with final counters attached to the start row under extra.cass.token_usage; the finish row returns TokenDataSource::Suppressed and contributes neither API usage nor an estimate. Only the four counters are attached, without copying request payloads. Explicit zero and partial counts remain API data; invalid fields and ordinary transcript text retain the estimation fallback. The existing 100 MB task-file cap is unchanged.

SDK metrics use a different convention: inputTokens includes cache reads and writes. Extraction separates the valid cache buckets while preserving the full input total. Only assistant metrics count as API usage; SDK display-only messages contribute neither usage nor estimates. Manifest-wide counters are not added to per-message metrics.

Exclusion entries are literal paths, without shell or tilde expansion. An unset or empty CASS_EXCLUDE_PATHS leaves scans unrestricted. Invalid Unicode, unresolvable entries, or relative entries without a usable working directory return an error from all 34 connectors' scan and discovery methods before source hooks or parsing. All 34 connectors support streaming callback scans (supports_streaming_scan() == true), workspace path rewrites (ScanRoot::with_rewrite), and progress liveness ticks (ScanContext::with_progress_tick). Twenty-nine connectors expose per-source lifecycle boundary hooks (supports_source_boundaries() == true via scan_with_source_boundaries()); the remaining 5 cross-source connectors (cline, copilot, cursor, opencode, and qwen) correlate, splice, or deduplicate sessions across multiple files/DBs before emitting conversations and therefore report supports_source_boundaries() == false. All 30 store-based connectors resolve remote mirror containers via ScanRoot::mirrored_store (the remaining 4 project/explicit-root connectors — aider, chatgpt, grok_bot, and pi_durable — scan project directories, single files, or OS application caches directly). Absolute exclusion entries remain usable without a working directory. The existing infallible Pi discover_sources and durable store_diagnostics helpers log invalid-policy errors and return no sources; pi_wire::try_discover_sources exposes the discovery error to direct callers.

Claude Code, Factory, Kimi, Kiro, Clawdbot, Vibe, OpenClaw, Muse, Grok, Prime and Gemini stop reading a transcript after an underlying I/O error and discard its incomplete contents. Their JSONL readers still skip individual malformed JSON or invalid UTF-8 records and retain later valid records. The shared line reader buffers only the current line and adds no full-transcript copy.

Vibe retains native assistant turns containing only reasoning or tool calls, as well as ordinary text. Tool names and call IDs are exposed through invocations; JSON-encoded arguments up to 64 KiB become structured values. Larger arguments remain in the original extra record, and opaque reasoning and image payloads do not enter searchable content. Each source row remains one normalized message. Per-message token usage remains estimated because Vibe stores its API counters separately as cumulative session statistics. Similarly, Crush and Muse record API token counters at the session level in conversation.metadata (prompt_tokens/completion_tokens for Crush; usage.input_tokens/output_tokens for Muse), so per-message token extraction preserves model and provider metadata while falling back to content estimation rather than duplicating session totals across assistant turns. OpenCode 1.x (SQLite and flat-file JSON) and 2.x (session_v2/session_message) preserve per-message modelID, providerID, tokens (input, output, reasoning, cache.read, cache.write), and cass.tool_call_count on extra, surfacing positive API token counts via extract_tokens_for_agent("opencode", ...) and falling back to content estimation for zero-initialized Tokens::default() blocks. Aider and Grok Bot also extract structured NormalizedInvocation entries (such as Aider #### /run / #### /git / #### /test commands and Grok Bot tool_calls / tool_use / function_call blocks) alongside per-message model and cass.tool_call_count metadata.

Continue reads IDE and CLI snapshots from ~/.continue/sessions/<sessionId>.json, or CONTINUE_GLOBAL_DIR/sessions. Explicit home, global-home, session-directory and exact-file scopes stay isolated; overlapping and symlinked paths yield each source once. The native session ID must match the filename. The connector supports callback streaming, source discovery and per-source completion hooks. Invalid or interrupted snapshots never authorize a resume skip. The shared sessions.json list is not a transcript and is not required to reconstruct a session.

Continue preserves ordered text, reasoning, tool calls, tool results and message IDs. IDE tool-result rows take precedence over their duplicate copies in toolCallStates.output; CLI inline results are emitted as tool messages. Typed reading skips duplicated editor state, prompt/completion logs, retrieved context and image payloads. Tool arguments up to 64 KiB become structured values; larger arguments are omitted from normalized metadata. The original source file remains available through source discovery. Native snapshots do not record message timestamps, so those fields remain unset.

Continue usage is attached once to its assistant message, including replies with multiple text blocks or no text. Recorded completion tokens and input without cache activity are exposed as API counts. Cache-bearing prompt totals remain in compact metadata because the IDE's legacy Anthropic adapter and the CLI's provider adapters use different input/cache conventions. Display model aliases are kept separate from actual model IDs. Session-wide usage stays in conversation metadata and is never added to per-reply counts. These contracts follow the pinned Continue session writer and CLI history service.

Amp exposes its native per-assistant inputTokens, outputTokens, cacheCreationInputTokens and cacheReadInputTokens as four disjoint API buckets. Combined totalInputTokens, context limits, debug calls and thread ledgers are not additional usage. Native streaming updates replace the saved assistant snapshot, so multiple content blocks still contribute one observation. Usage-only replies and explicit zero counts survive ingestion, and native message IDs and timestamps remain available. The mapping is verified against the vendor's published CLI artifact and documented stream schema. Cross-conversation fork reconciliation remains the consumer's responsibility: Amp can retain a stale fork index after edits and reuse descendant message IDs, so those fields alone cannot safely suppress copied usage.

Kiro's supported v2 JSONL store follows KIRO_HOME/sessions/cli, including renamed profile copies, their session directories and exact transcript files. The native override was introduced in Kiro 2.3. JSON metadata sidecars, source provenance and existing message projection are preserved. This support covers the v2 JSONL/JSON layout; it does not infer a schema for Kiro v3 stores.

Gemini CLI JSONL sessions are replayed into their final message history. Repeated native message IDs replace earlier versions in place; $patch updates content and tool results, removes messages, or reorders surviving IDs. $rewindTo removes its target and later messages, while $set.messages replaces history with an authoritative checkpoint. Optional-kind metadata headers and legacy messages without IDs remain supported. Replay keeps one payload per surviving ID, and the existing 32 MiB metadata compaction threshold is unchanged.

Gemini messages also retain stored thought summaries and tool activity when ordinary text is empty. Each native message remains one normalized message; tool names, call IDs and arguments are exposed through invocations. Readable tool results are included in searchable content, while binary result parts are omitted. Compact metadata does not duplicate the raw thought, tool-call or result payloads.

Gemini token extraction reads the native per-message tokens fields in both JSON and JSONL histories. The same six numeric fields survive metadata compaction at 32 MiB. Cached tokens are separated from the prompt, tool-use prompt tokens count as input, and generated thoughts count as output. thinking_tokens describes the reasoning subset already included in output; the aggregate counts it once. Invalid token fields are ignored, and messages without usable API counts retain the existing estimation fallback.

Qwen Code reads current UUID-named JSONL sessions in projects/<project>/chats/, including chats/archive/, alongside its legacy tmp/<project>/chats/session-*.json histories. Native UUID fragments form one message with the latest recorded usage, and parent links select the active conversation branch. Thought text, tool calls and readable results, authored user display text, and custom titles are preserved. Exact discovered files and recognizable copied stores remain scoped to their supplied roots; explicit scan roots exclude unrelated local stores. Configured Qwen paths support the agent's tilde expansion and relative-path resolution.

Qwen also reads native subagent transcripts at projects/<project>/subagents/<parentSessionId>/agent-<agentId>.jsonl. Every record must agree on the parent session, agent and sidechain identity. The external ID is an escaped JSON tuple of the original parent and agent IDs, so filename sanitization cannot merge different agents. Completed rounds keep their own usage once; separate tool-call records, bootstrap copies, .jsonl.stream files and cumulative sidecar statistics add no API usage. Optional .meta.json sidecars supply bounded descriptions, lineage and status and participate in incremental selection. Reads are capped at 1 MiB, and a missing or malformed optional sidecar does not discard the transcript. The format follows the pinned Qwen subagent writer.

At 32 MiB, Qwen JSONL metadata retains reply identity, model, selected scalar usage fields and tool-call counts without duplicating the raw content or tool payloads. Its public token extractor keeps cached input separate and uses the recorded total to distinguish additional Google reasoning tokens from reasoning already included in OpenAI-compatible output. Ambiguous reasoning stays in metadata; total-only records retain estimation instead of receiving a guessed input/output split. Native transcript I/O failures discard the incomplete conversation. Qwen streams completed conversations; per-source completion hooks and the distinct managed-session format are not supported.

Claude Code streaming scans continue to deliver healthy sources after a source open/read failure, then return an error with the failure count and the first original I/O error. Failed sources emit no conversation or completion event. The collecting scan() API remains all-or-error, so its caller receives no partial vector. Conversation-callback and completion-callback errors stop traversal immediately, including when a source error was already deferred.

Feature flags

Feature Enables
connectors Connector framework and every connector that needs no extra dependencies
chatgpt ChatGPT desktop, including AES-GCM encrypted v2/v3 conversations
cursor, opencode, goose, hermes, crush, devin, shelley SQLite-backed stores for those agents
openclaw-sqlite OpenClaw 2 per-agent SQLite transcripts (zstd events included)
codex-zstd Codex rollouts compressed to rollout-*.jsonl.zst (Codex's local_thread_store_compression)
copilot-vscdb Legacy VS Code Copilot chat sessions in state.vscdb
pi-sqlite pi_agent_rust SQLite sessions for the Pi family
pi-durable SQLite stores of Pi's experimental durable harness
codebuff, grok-bot Codebuff / Freebuff shared history; Grok Bot desktop replicas
all-connectors All of the above

SQLite stores are read with FrankenSQLite opened read-only inside one read transaction, so a live WAL is seen as one coherent snapshot and nothing is recovered or checkpointed.

Coverage

Slug Agent Storage read
aider Aider .aider.chat.history.md
amp Amp thread JSON
antigravity Antigravity IDE and agy CLI transcripts and conversation stores
chatgpt ChatGPT desktop conversation JSON (encrypted generations with chatgpt)
claude Claude Code session JSONL (CLAUDE_CONFIG_DIR, XDG_CONFIG_HOME aware)
clawdbot Clawdbot session JSONL
cline Cline task JSON
codebuff Codebuff / Freebuff (one shared store) ~/.config/manicode chats
codex Codex CLI rollout JSONL (.jsonl.zst with codex-zstd)
continue Continue IDE / CLI native session JSON snapshots
copilot / github-copilot VS Code Copilot Chat chat-session JSON and append logs; legacy state.vscdb
copilot_cli Copilot CLI session event logs
crush Charm Crush SQLite
cursor Cursor state.vscdb and agent transcripts
devin Devin CLI sessions.db (detection-only without devin)
factory Factory Droid session JSONL
gemini Gemini CLI session JSON and event-sourced JSONL
goose Goose sessions.db and legacy JSONL
grok / grok_bot Grok Build CLI / Grok Bot desktop session logs / rolling replicas
hermes Hermes Agent state.db
kimi, kiro, qwen, vibe, muse Kimi Code, Kiro CLI, Qwen Code, Mistral Vibe, Muse Code their session logs
omp Oh My Pi session JSONL, profiles and XDG layouts
openclaw OpenClaw session JSONL; OpenClaw 2 SQLite
opencode OpenCode JSON files and opencode.db
openhands OpenHands conversation event JSON
pi_agent pi-mono and pi_agent_rust session JSONL; SQLite sessions with pi-sqlite
pi_durable Pi durable harness main.jsonl commit logs; session.sqlite with pi-durable
prime_agent Prime Agent session JSONL
shelley Shelley shelley.db
windsurf Windsurf detection only

Pi-family transcripts keep every branch of a session (abandoned branches stay searchable), and each message records whether it is on the active branch and in the active model context, with summaries, injected context and shell runs labeled by kind.

Design goals

  1. Keep output stable for downstream tooling and snapshot tests.
  2. Keep behavior explicit: connector normalization, unknown slugs, unsupported store versions and incomplete sources are reported, never guessed at.
  3. Stay local and read-only: filesystem and read-only SQLite access only.
  4. Stay runtime-neutral with a synchronous API.

Installation

For detection only:

[dependencies]
franken-agent-detection = "0.3"                                          # detection

For the full parsing layer:

[dependencies]
franken-agent-detection = { version = "0.3", features = ["all-connectors"] } # parsing

From source:

git clone https://github.com/Dicklesworthstone/franken_agent_detection
cd franken_agent_detection
cargo test --locked --all-features

Toolchain & MSRV

  • Default detection-only build (default = [], rust-version = "1.85"): uses standard Rust 2024 edition syntax with zero optional dependencies.
  • Non-SQLite connector features (connectors, codex-zstd, chatgpt): use Rust 1.88+ let-chains.
  • SQLite-backed connector features (cursor, opencode, goose, hermes, crush, devin, shelley, openclaw-sqlite, pi-sqlite, pi-durable, all-connectors): depend on frankensqlite and asupersync, which require the repository's pinned rust-toolchain.toml (nightly-2026-08-31 or newer).
  • Repository .cargo/config.toml: sets rustflags = ["-Z", "threads=4"] for the pinned nightly toolchain; pass RUSTFLAGS="" if building non-SQLite feature slices with a stable compiler inside a checkout of this repository.

Troubleshooting

Symptom Cause Fix
UnknownConnectors error Connector slug not recognized Use known slugs (codex, claude, omp, gemini, …)
Empty detection results No roots exist on this machine Set include_undetected = true to inspect evidence
A connector scans nothing Its storage needs a cargo feature (SQLite, crypto) Enable the feature from the table above, or all-connectors
Non-deterministic tests Real home-dir probing Use root_overrides / explicit ScanRoots over temp directories
Stack overflow in SQLite tests fsqlite's large debug-mode futures .cargo/config.toml sets RUST_MIN_STACK; keep it when vendoring
-Z threads=4 or asupersync MSRV error on stable Building repository checkout or SQLite features without rust-toolchain.toml Let rustup select rust-toolchain.toml, or pass RUSTFLAGS="" when building non-SQLite features on stable 1.88+

Limitations

  • Default probe roots are opinionated; custom layouts need overrides or explicit scan roots.
  • No background watching; scans run only when called (since_ts makes them incremental, source boundaries make them resumable).
  • Remote SQLite stores are not read: a live database and its WAL cannot be synced as one consistent snapshot. Index them on their own host.

About Contributions

About Contributions: Please don't take this the wrong way, but I do not accept outside contributions for any of my projects. I simply don't have the mental bandwidth to review anything, and it's my name on the thing, so I'm responsible for any problems it causes; thus, the risk-reward is highly asymmetric from my perspective. I'd also have to worry about other "stakeholders," which seems unwise for tools I mostly make for myself for free. Feel free to submit issues, and even PRs if you want to illustrate a proposed fix, but know I won't merge them directly. Instead, I'll have Claude or Codex review submissions via gh and independently decide whether and how to address them. Bug reports in particular are welcome. Sorry if this offends, but I want to avoid wasted time and hurt feelings. I understand this isn't in sync with the prevailing open-source ethos that seeks community contributions, but it's the only way I can move at this velocity and keep my sanity.

License

MIT