hamr0/litectx
GitHub: hamr0/litectx
一个基于 SQLite/FTS5 的本地优先代码上下文图库,为 AI agent 提供跨迭代的代码检索、变更影响分析和持久化记忆能力。
Stars: 0 | Forks: 0
```
.ts · .js · .py · .md ─┐
git blame · commit log ─┤ ╭───────────────╮ recall → ranked, activation-weighted hits
tree-sitter · ripgrep -w ─┼─▶ │ ▓▓ litectx ▓▓ │ ─▶ impact → called-by/calling blast radius + risk
ACT-R activation ─┘ ╰───────────────╯ one SQLite file · no service · embeddings opt-in
litectx
```
## 这是什么
litectx 是**一个基底,两个视图**。基底是一个代码+上下文**图 (graph)**:带类型的节点(函数、类、文件、文档块),带类型的边(调用、被调用、导入),以及每个节点的信号(近期性、频率、变动、复杂度)。两个视图读取同一个图——在查询时组合,永远不会被重新提取。
- **recall** —— 排序搜索。候选项由 FTS5/BM25 把关,然后通过**跨图边扩散激活**(被验证可泛化的 ACT-R 术语)重新加权。Git 活动(提交、近期性)作为每次命中的基础依据,而不是作为分数。结果是*当前相关的*内容,而不仅仅是词法匹配的内容。*(基础层级激活——近期性 × 频率衰减——是长期记忆层,一旦存在真实的访问日志就会引入;embeddings 增加了语义层。)*
- **impact** —— 给它一个符号;它会遍历调用 / 被调用边得出**爆炸半径**,并将变更风险划分为 **low / med / high**。在设计上,重复计数是可以接受的——其输出是一个风险*区间*,而不是精确的引用列表。
它**不是** LSP / 语言服务器,**不是**代码智能 SaaS,**也不是**基于 embeddings 优先的语义搜索(那是可选层),并且**不提供任何 UI 或服务器**——它只是一个基于 SQLite 文件的库。每个采用者唯一的定制是索引*什么*(按文件扩展名)以及选择*哪个*层(确定性还是 + embeddings);litectx 掌控着其结构(提取 → 图 → 排序)。
## litectx 的定位
litectx 是一个小家族的一部分,了解它们的分工会有所帮助:
- **litectx = agent *知道什么*,以及它是如何组织的** —— 上下文器官 / 记忆。一个你可以 `import` 的库;没有循环,没有 LLM,没有自己的进程。
- **baresuite (`bareagent` + `bareguard`) = agent 一步步*做什么*,且安全地执行** —— 运行时:agent 循环、人工审核环节(human-in-the-loop)、工具调度、预算、内容信任。
如果 litectx 是记忆,那么 baresuite 就是神经系统和肌肉。它们在一个接缝处汇合——一个 `{ store, search, get, delete }` 接口——因此,每当任务运行时间长到上下文管理开始变得重要时,baresuite 就会*调用* litectx。依赖是单向的:**baresuite 消费 litectx;绝不反过来。** litectx 是独立的。
| | baresuite | litectx |
|---|---|---|
| **是一个** | runtime / harness | 库 |
| **掌控** | 循环, 工具, 门控, 生成, 预算 | recall, impact, 图, 记忆, 上下文原语 |
| **适用于** | 轻量级**一次性**自动化 | **持久的、长期运行的** agent 循环 |
| **LLM / 循环** | 是 | 否 —— 确定性的 |
| **依赖于** | 导入 litectx | 无(独立) |
简短版本是:baresuite *一次性*运行任务;而 litectx 能将一个普通的循环中的 agent 转变为一个**聪明、灵活且拥有记忆的 agent**——它跨迭代携带上下文和决策,而不是每次都从零开始。
## 安装
```
npm install litectx
```
Node **>= 18**。**只有一个生产依赖** (`better-sqlite3`));`typescript` / `@types/node` 仅用于开发(JSDoc → 生成 `.d.ts`,因此您开箱即可获得自动补全)。Embeddings(如果启用)会引入它们各自的可选层。
## 快速开始
```
import { LiteCtx } from "litectx";
// one config — point it at a repo, choose what to index (by extension)
const ctx = new LiteCtx({
root: "/path/to/repo",
include: [".ts", ".js", ".py", ".md"], // routed by EXTENSION, never sniffed
});
await ctx.index(); // incremental: (mtime, size) fast-skip → content-hash
// recall — kind-scoped; kinds never share a ranking, so prose can't bury code (async)
const hits = await ctx.recall("where do we validate the auth token?", { kind: "code" });
// → [{ path, kind, format, score, git }, …] (omit kind → grouped { code, doc, fact, episode }, 5 each)
// impact — blast radius + risk bucket for a symbol (async; shells `rg -w`)
const blast = await ctx.impact("validateToken");
// → { symbol, risk: "high", refCount: 37, confirmed, mentions,
// callers: [...], callees: [...], complexity, defs, hedges } | null
// memory that isn't a file — facts/episodes/runtime docs; survives every index() pass
await ctx.remember("fact:auth-uses-jwt", "Auth is JWT, verified in middleware.", { kind: "fact", by: "human" });
const facts = await ctx.recall("jwt auth", { kind: "fact" });
ctx.get("fact:auth-uses-jwt")?.text; // the body behind any pointer (recall returns ranked pointers)
ctx.forget("fact:auth-uses-jwt"); // by key — or forget({ by: "agent" }) in bulk
```
**可选语义层:** `new LiteCtx({ root, embeddings: true })` 将 embedding 余弦相似度融合到 recall 中(这是从双≈85% 提升至三≈95% 的一步)——而对于书面记忆(`fact`/`episode`),它还会**提名**:最接近查询的存储向量加入候选池,因此即使是一个与事实没有共用任何单词的释义也能找到它(例如 "money back" → 找到 refunds 事实;基准测试的 para MRR 从 0.000 提升至 0.574,同时保留了精确/词形匹配)。默认关闭——它需要可选的同级依赖 (`npm i @huggingface/transformers`) 并在首次使用时加载一个小型本地模型;确定性的 BM25 + 扩散核心从不碰它。
**完全不需要集成代码——盒子内自带两个接口**,两者都是对同一公共 API 的轻量适配器(针对同一个索引,可以使用库、CLI、MCP server 或同时使用全部三者):
```
litectx index && litectx recall "auth token validation" --kind code
echo "Auth is JWT, verified in middleware." | litectx remember fact:auth-uses-jwt
litectx get fact:auth-uses-jwt # body → stdout, pipes clean
litectx help # all commands + the output-column legend
```
```
// MCP (Claude Code, Cursor, …): stdio, spawned by the client, not a daemon. Zero extra deps.
{ "mcpServers": { "litectx": { "command": "litectx-mcp", "args": ["--root", "/path/to/repo"] } } }
// → tools: index · recall · impact · get · recent · promotions · remember · forget
```
图基底是公共 API:**`getNode(id)`** 描述一个节点(其符号作为 `chunks` + 精确的导入边计数;与类型无关),而 **`related(id, {dir, hops})`** 会遍历其持久化的 `import` 边。较低级别的访问也可以通过 `Store`(`symbolDefs`、`nodesForPath`、`allSymbolNames`)导出。
**索引是通过文件扩展名路由的**,从不通过嗅探内容。v1 支持的语言:代码支持 **TypeScript、JavaScript、Python**,外加 **Markdown** 文档。文件列表来自 `git ls-files`(在 git 仓库之外则是文件系统遍历);重新索引是增量的——`(mtime, size)` 快速跳过后会进入内容哈希比对。**Git 活动**(提交次数 + 近期性,来自 `git log`)作为基础元数据附加到每次命中上——这样您就能看到哪些内容曾被处理过,而不会让它影响排名。
**`kind` 是一等公民——并且写入路径是实时的。** 文件通过 `index()` 进入 → `code` / `doc` (Markdown);不是文件的知识通过 `remember()` 进入 → `fact` / `episode` / `doc`(agent 学到的事实、会话事件、运行时常见问题解答)。书面记忆存在于同一个存储中,通过相同的以 kind 划分范围的排序进行召回,携带出处 (`by: "human" | "agent"`),并且在结构上能够在重新索引后存活。每次 recall 命中都会被记录——即 `reviewCandidates()`(由人工验证常用 agent **事实**)和 `promotionCandidates()`(agent 将常用的**片段(episodes)**提炼为事实;litectx 只负责标记,从不总结)背后的审计追踪。`recentActivity()` 根据观察到的编辑回答“我刚才在做什么”。片段(Episodes)是一个临时草稿本——一个 30 天的滚动窗口,在写入时自动清理。这些访问日志**读取视图**已发布;但作为*排名*信号的基础层级激活仍被排除在外(POC 发现其依赖于具体仓库)。其他文档格式(通过 `format` 的 pdf/docx/txt)仍保留 schema,**无需迁移**。除了记忆之外,**`stash(id, text)`** 还可以存放一个*不是*记忆的 payload——可恢复的压缩 (R-C4):丢弃一个大型工具结果或将其移出上下文,保留低成本的 `id`,然后使用 `get(id)` 恢复;它永远不会被索引、永远不会被召回、也永远不会被清理。它按设计是一个**库/编排动词**——*而不是* CLI 或 MCP 工具,因为存放 payload 是宿主循环执行的一种运行时机制,而不是推理模型发出的调用(MCP 接口保留模型的动词:recall/remember/impact)。它的读取一半是 **`peek(id)`**(R-I3,handle/懒加载):在不恢复的情况下,提供已停放 blob 的 `{ bytes, head, tail, truncated }` **头+尾**预览(结论——退出代码、失败帧——位于末尾,因此尾部很重要)。其优势在于**有界结果**——只返回约头+尾的字节,因此 payload 不会占用您的上下文/token 预算——在 handle 上进行推理,仅在您决定需要时才使用 `get(id)` 获取完整主体。还有 **`compress(node, { level })`**(R-C7,按层级排序的渲染)以三种保真度之一渲染代码符号——`verbatim`、`signature`(声明头 + 其文档,省略实现主体——在真实代码上**缩小了约 82%**;由 tree-sitter 提取,因此它保留 `export`/`async`/generics,适用于方法,并重新附加 JSDoc/docstring),或 `drop`(一个 `name …` 标记)——这样,在分配 token 预算时,调用者可以完整显示最匹配的命中项,并以签名形式显示长尾内容。就像 `stash`/`peek` 一样,它是一个**库/编排动词**(不是 CLI/MCP):选择渲染层级是宿主循环的工作,而不是模型动词。
## 图
一个 SQLite 文件保存着整个基底——节点、边、信号和 FTS5 索引——因此数据生命周期长于进程,并且这一个文件就是整个读取接口。
```
// node: a symbol chunk (function/class/method/doc-section), with line span
{ "symbol": "validateToken", "kind": "code", "format": "ts",
"node_type": "function_declaration", "start_line": 42, "end_line": 71 }
// edge: typed, directional. Today `import` edges are persisted (they drive recall
// spreading); the `call` graph that impact walks is resolved on demand, not stored.
{ "type": "import", "src_path": "src/routes.ts", "dst_path": "src/auth.ts" }
```
```
// recall result — BM25-gated, then 1-hop import-spreading re-ranked; git is grounding, not scored
{ path: "src/auth.ts", kind: "code", format: "ts", score: 0.91, git: { commits: 12, lastCommit: 1.7e9 } }
// impact result — blast radius bucketed, not a raw ref dump (refCount = max(confirmed, mentions))
{ symbol: "validateToken", risk: "high", refCount: 37, confirmed: 31, mentions: 37,
callers: [/* … */], callees: [/* … */], complexity: 7, defs: [/* … */], hedges: [/* … */] }
```
**complexity ≠ risk:** complexity 是局部的 AST 分支计数;risk/impact 是来自调用图(爆炸半径)的引用计数。在设计上,它们是独立的字段。
## 文档
| | |
|---|---|
| **集成指南** (`litectx.context.md`) | 完整的采用者契约——每个选项、完整的公共 API、图 schema、扩展契约以及拒绝事项。*把它交给您的 AI 助手。* 随包提供。 |
| **[PRD](docs/01-product/litectx-memory-prd.md)** | 锁定的决策 + *原因*、基底/视图模型、POC 验证门、构建顺序、拒绝事项。*(仅限仓库)* |
| **[更新日志](CHANGELOG.md)** | keep-a-changelog 格式;每次发布都有记录。 |
## 许可证
Apache 2.0。详见 [LICENSE](LICENSE)。
标签:MITM代理, SOC Prime, SQLite, tree-sitter, 代码图谱, 代码搜索, 开发工具, 影响分析, 自定义脚本