KSym04/limpet

GitHub: KSym04/limpet

limpet 是一个基于 AST 锚定的 AI 编程 agent 持久记忆层,让跨会话知识在代码演进时自动检测陈旧并保持可信。

Stars: 2 | Forks: 0

# 🐚 limpet **小巧的 shell,持久的记忆。** [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/99/993938d8ce5e902ccfb9d6747725c320d855dea3235ed9a304cedf0d94c9321f.svg)](https://github.com/KSym04/limpet/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) [![Rust](https://img.shields.io/badge/rust-stable-orange.svg)](https://rustup.rs) [![Platforms](https://img.shields.io/badge/macOS%20%7C%20Linux%20%7C%20Windows-supported-lightgrey.svg)](https://github.com/KSym04/limpet/actions/workflows/ci.yml) [![100% local](https://img.shields.io/badge/network%20calls-zero-blue.svg)](SECURITY.md) **为 AI 编程 agent 提供持久的工程记忆。** AI 在不同会话之间会忘记一切。limpet 能记住你的 agent 所学到的关于代码库的知识,并且知道这些知识何时不再成立。

limpet visual memory graph: memories colored by health, clamped to code symbols
limpet ui: green memories are trustworthy, amber went stale when their code changed, squares are the symbols they clamp onto

每个编程 agent 都在运行相同的循环:阅读代码、推理、回答、遗忘。下一次会话中,它又要付出全部的代价。而且它之前记录的任何内容(会话笔记、markdown 记忆文件)都会悄无声息地失效,因为没有任何东西将这些笔记与它们所描述的代码联系起来。limpet 将这一循环替换为一个知识生命周期: ``` read -> reason -> remember -> verify -> flag stale when code changes -> re-verify -> reuse ``` 你的 agent 所学到的关于项目的所有内容(决策、已验证的事实、失败的尝试、陷阱、意图)都将作为持久记忆存储,锚定到它所描述的实际代码,并在该代码发生更改的那一刻被自动标记。它以 MCP 服务器的形式发布,因此任何支持 MCP 的 agent 都可以使用它:一个 Rust 二进制文件,一个 SQLite 文件,100% 本地化。 这里每一个设计决策背后的原则,以及为之付出的生产级代价,都记录在 [PHILOSOPHY.md](PHILOSOPHY.md) 中。 它的名字就是它的机制:帽贝(limpet)会牢牢固定在一个地方,并在每次潮水过后返回原处。这里的记忆也会牢牢锁定在经过 AST 哈希处理的符号上,跟随着它们经历重命名和文件移动,并在其下方的代码实际发生更改时变得明显陈旧。 ## 有何不同(不仅仅是一个记忆存储) 这里的每一项声明都可以在这个仓库中验证,绝非营销噱头: - **它知道自己何时是错的。** 通用的 AI 记忆(向量存储、RAG、笔记)会永远相信自己写下的一切。limpet 通过规范化的 AST 哈希将每一条记忆锚定到它所描述的代码上,因此一旦代码被编辑,记忆就会翻转为 `stale` 状态;它会跟随代码经历重命名和文件移动,并且在更改被还原时自我修复。自我失效是其核心所在,没有其他开源记忆层能做到这一点。→ [锚点生命周期](#-the-anchor-lifecycle) - **它向你展示了它保存了什么。** 每次召回都会根据其替代的文件读取量进行计价,并保存在一个你可读的滚动账本中:`limpet stats`。基准测试表明,在 agent 每次会话都要重新回答的问题上,token 消耗减少了 4.0 倍,而且这个数字还是刻意保守计算的。→ [收据](#-the-receipts-token-savings-measured) - **它索引整个仓库,而不仅仅是符号。** 六种 tree-sitter 语法(PHP, JS, TS, Python, Rust, C/C++)提供了函数和类级别的锚定;其他所有文件(模板、样式、配置、数据,甚至非 UTF-8 的遗留源代码)都可以在文件级别进行锚定。记忆可以依附于任何地方,并在其中任何内容发生改变时变得陈旧。→ [整个仓库被索引](#-whole-repo-indexed-thin-on-purpose) - **它绝不因遗漏而撒谎。** 每一个响应都带有一个诚实信封:匹配了什么,返回了什么,丢弃了什么以及为什么,索引有多新鲜,有多少内容陈旧或相互矛盾。没有任何代码路径会静默截断,当基准测试门槛发现 limpet 自身的功能越过这条红线时,这些功能已被扼杀。 它不是向量数据库,不是代码搜索引擎,也不是调用图预言机;它是那个记住*为什么*、并与代码绑定的层级,当这个“为什么”不再成立时,它会告诉你。请参阅 [limpet 不是什么](#-what-limpet-is-not)。 ## 🧠 为什么需要它 代码索引器回答“什么在哪里”,并且回答得很好。但那些昂贵的知识并不存在于任何文件中: - 为什么批处理大小(batch size)是 50 - 哪种重构尝试过并被回滚了,以及破坏了什么 - 哪些方法名因为客户会依赖(hook)它们而被冻结了 - 那个奇怪的 cron job 实际上是为了防范什么 agent 在每次会话中都会重新推导或重新询问这些问题,燃烧着 token,甚至更糟——它们会去猜。而真正扼杀 AI 编程助手的失败模式不是遗忘,而是记住了不再真实的信息。一个仍然相信上个月版本中某个函数行为的 agent 并不是“没有帮助”,而是“自信地犯错”,市面上的每一个通用记忆存储都会永远向它灌输这种谎言,因为在这些系统中,知识只被写入一次,然后被永远信任。 limpet 的前提是:一段关于代码的记忆只有在代码仍然与之匹配时才是可信的。因此,这里的知识是有生命周期的,就像工程师赋予它的生命周期一样: ``` valid -> code changed -> stale (reason attached, confidence drops) -> re-verified or superseded ``` 这需要具备三个目前没有其他工具能同时做到的特性: 1. **锚定。** 记忆通过规范化的 AST 主体哈希依附于符号,而不是行号或文件路径。重命名函数或移动文件,记忆都会紧随其后。编辑函数主体,记忆就会带着理由翻转为陈旧状态。 2. **诚实。** 每个响应都带有一个元数据信封:索引有多新鲜,匹配的结果数与返回的结果数各是多少,以及你得到的内容中有多少是陈旧的或相互矛盾的。没有任何代码路径会进行静默截断。 3. **有据可查。** 一个事实可以携带证明它的命令。当它的锚点失效时,limpet 会把重新验证它的精确命令交给 agent。 ## 👥 适用人群 **与 AI agent 协作的个人开发者。** 第一次会话会花费数万个 token 来了解你的代码库;在那之后的每一次会话,知识只需一次 `recall` 即可获取。能够熬过 `/clear`、上下文压缩和新机器迁移。无需重新教你自己的项目,agent 也不会自信地对你昨天重写的函数重弹上个月的老调。 **团队。** `admin export` 会写出供 git 使用的 `.limpet/memory.jsonl` 文件;团队成员拉取后即可导入。新人入职所需的知识(“为什么 batch size 是 50”、“客户依赖这些方法名,它们已被冻结”)会随仓库一起流转,而且与 wiki 不同,当代码演进时它会自动标记自身。`affected` 会告诉提交者,他们的 diff 刚刚危及了哪些文档化的决策。 **模板繁重和多语言的代码库。** 每个文件都是可锚定的,而不仅仅是包含符号的源代码,因此“由于设计系统,此布局被锁定为 480px”可以钉在实际的模板或样式表上,并在有人编辑它时变得陈旧。在逻辑存在于模板、样式、配置和数据文件(Rails, Laravel, Django, Vue,任何组件框架,任何 CMS 主题)的技术栈中,这恰恰是大多数值得保留的知识所在之处,而这正是只关注符号的索引器无法触及的。 **开源维护者。** `intent` 和 `decision` 记忆可以在那个试图“修复”它的 PR 合并之前,回答“为什么这里会有这么奇怪的代码”;`episode` 记忆可以阻止第三个贡献者再次尝试那种已经把事情搞砸两次的重构。 **Agent 构建者。** 一个位于标准 MCP 接口之后的、与模型无关的记忆后端:单一二进制文件,零网络调用,可审查的 SQLite。安全审查中无需解释任何复杂的东西。 **哪些人可以忽略它:** 一次性脚本、你只碰一次的仓库,以及不使用 AI agent 的团队。记忆只有在问题会重复出现时才有回报。 ## 🧰 六大工具 | 工具 | 功能 | |---|---| | `recall` | 输入任务描述,输出受 token 预算限制的排序记忆包。陈旧和相互矛盾的项目总是会被标记,绝不隐藏。 | | `remember` | 存储记忆:`fact`、`decision`、`episode`、`insight` 或 `intent`。将其锚定到代码。附加证据使其成为已验证状态。`private: true` 将记忆保留在本地(这里可被召回,但导出时保留不发布);`origin` 使写入操作对于初始化流程具有幂等性。 | | `map` | 文件或符号的结构大纲,以及依附其上的所有记忆。代码与知识在一个答案中呈现。 | | `affected` | 我未提交的 diff 触及了什么:符号、现在面临风险的记忆,以及限制正在编辑代码的决策。 | | `verify_queue` | 锚定代码已发生更改的已验证事实,每一个都附带最初证明它的确切命令。 | | `admin` | index, status, forget, export / import(受保护), ledger / ledger_reset(节省额度的收据)。导出会报告 `private_withheld`,以便调用者知道有多少记忆保留在了本地。 | 每个响应都包装在诚实信封中: ``` { "data": [ ... ], "meta": { "freshness": { "indexed_at": "2026-07-03T10:12:44Z", "dirty": 0 }, "completeness": { "matched": 7, "returned": 3, "omitted_reason": "budget" }, "staleness": { "stale": 1, "contradicted": 0 } } } ``` ## ⚓ 锚点生命周期 ``` code change anchor resolution memory becomes --------------------- ------------------------ ----------------------------- reformat / comments same normalized AST hash active (untouched) rename symbol body found under new FQN active, anchor follows move file body found in new file active, anchor follows edit function body hash differs at FQN stale (body_edited), conf drops delete symbol body found nowhere invalidated (kept as history) duplicate bodies multiple matches stale (ambiguous_anchor) edit anchored file file content hash differs stale (file_edited), conf drops delete anchored file file row gone invalidated (kept as history) lose SOME anchors others still resolve stale (anchor_lost), never killed ``` 一个多锚点记忆只有在**所有**锚点都失效时才会消亡。当一个锚点丢失而其他锚点仍能解析时,它会降级为 `stale:anchor_lost`,以便存留的知识仍然可用。并且 `remember` 会在写入时大声拒绝它无法解析的锚点:没有任何记忆是一出生就死掉的。 陈旧性也是对称的:还原代码(一个被回滚的实验,一次 `git checkout`),记忆就会在下一次调用时恢复为 active 状态,因为锚点哈希再次匹配了。对于未曾发生过的更改,无需进行重新验证仪式。这也适用于失效情况:分支切换、`git stash` 或让文件短暂消失的 rebase 过程,都不是死刑判决——当代码回来且锚点解析成功时,记忆就会恢复。唯一的最终状态是 `superseded`,它记录的是人类深思熟虑的决定,而不是文件系统的动荡。 矛盾是显式关联的:当新记忆与旧记忆矛盾时,两者都会保持可见并标出冲突,直到其中一个 `supersedes`(取代)另一个。历史绝不会被静默覆盖。 ## 🪙 收据:实测的 token 节省量 持久记忆恰好也比重新推导要便宜得多。这些节省来自哪里: 1. **答案以结论的形式传输,而不是源代码。** 一条记忆是经过提炼的 30 个 token 的答案;而它所学自的文件则是数千个 token。阅读代码以重新推导一个已知事实,每次会话都要付出全价。而召回每个问题只需支付一次代价。 2. **最糟糕的消耗是无法成功的探索。** 为什么 batch size 是 50,哪个重构被回滚了,哪个 API 被冻结了:这些都不在任何文件中。没有记忆,agent 只能 grep、阅读,然后依然一无所知,所以这些 token 等于白买。有了记忆,这些就成了所有问题中最廉价的。 3. **响应打包了预算并削减了噪音。** `recall` 接收一个 token 预算,按最佳优先原则打包,丢弃低相关性的尾部,并报告它忽略了什么。你只花费你允许的额度,绝不是碰巧匹配到的那些。 4. **上下文丢失后无需重新教导。** 压缩、`/clear`、新会话:知识在上下文窗口之外存活,并以召回价格返回,而不是重新推导价格。 使用可复现的基准测试进行的测量,该测试在一个包含 9 个文件的真实 fixture 服务上初始化了 12 条记忆,提出了 agent 通常在每次会话中都会重新回答的 10 个问题: ``` question files+grep recall ratio in code? ---------------------------------------------------------------------------------------------------- why is the batch size 50 and why is there a queue at all 1929 367 5.3x no (answer only in memory) why does the scanner skip draft products, is that a bug 1630 377 4.3x no (answer only in memory) how is the health score computed 1630 352 4.6x yes why semicolon delimiter and BOM in the csv export 1327 361 3.7x no (answer only in memory) where do report files get written and why 1327 371 3.6x no (answer only in memory) how long are download tokens valid 1023 167 6.1x yes has anyone tried streaming the csv export 1327 369 3.6x no (answer only in memory) can I rename check_product in the scanner 1630 377 4.3x no (answer only in memory) what does the nightly cron actually exist for 803 317 2.5x no (answer only in memory) how often does the dashboard poll progress and can I lower it 1072 340 3.2x no (answer only in memory) ---------------------------------------------------------------------------------------------------- TOTAL 13698 3398 4.0x ``` **在整个基准测试中,token 消耗减少了 4.0 倍(节省了 75%)。** 自己动手复现: ``` cargo build --release python3 bench/token_savings.py ``` 而且这个数字不仅仅存在于基准测试中:limpet 会保留**你自己的收据**。每一次召回都会使用相同的方法论,针对其文件读取的反事实情况进行计价,而 `limpet stats`(或 `admin {op:"ledger"}`,或 UI 标题)会显示会话和生命周期累计的节省量:节省的 token、避免的读取、总召回次数和独立召回次数。负节省也会如实显示,绝不设底线,无锚点记忆计为零基准,因此这个数字是一个保守的下限,而不是营销噱头。

terminal statusline segment showing 9 active memories and 134k tokens saved
the receipt, live in a Claude Code statusline: active memories and lifetime tokens saved, read straight from the store

方法论说明,这个数字是为了供你核查,而不是盲目相信: - “不使用 limpet”的成本是包含答案的**最小**文件集加上 300 个用于搜索往返的平摊 token。真实的 agent 会读取比最小集合更多的内容,因此实际的节省会更高。 - 两方的 token 估算方式完全相同,均为 ceil(bytes/4)。 - 10 个问题中有 8 个在上面被标记为“no”:它们的答案**不存在于任何文件中,无论以什么 token 价格**(、历史、隐性知识)。阅读文件只能让你得到代码,却得不到答案。我们仍然按照文件读取的成本向 limpet 收取全价,而不是声称节省了无限多。 - 该脚本是一个回归门槛:如果节省量降至 4 倍以下,它将以非零状态退出。 - Fixture 文件为 58 到 179 行。真实的源文件要大上数倍,而且“不使用 limpet”的一方会随着文件大小的增加而变大,而召回响应并不会。 ## 🗺️ 可视化记忆 ``` limpet ui --port 9748 ``` UI 是它自己的独立命令,与 MCP 服务器分离。`limpet serve` (stdio) 是 Claude Code 在你执行 `limpet install` 后自动为你启动的;在你自己启动 `limpet ui` 之前,不会有任何进程监听端口。如果 http://127.0.0.1:9748 拒绝连接,说明 MCP 服务器并没有损坏;只是 UI 没有运行。 打开 http://127.0.0.1:9748 即可看到知识图谱的实时力导向图:记忆节点的大小代表置信度,颜色代表健康状态(绿色 active,琥珀色 stale,红色 invalidated),它们锁定在所描述的文件和符号上,并绘制了矛盾和取代的边。“需要注意”过滤器会准确显示哪些内容变陈旧了以及原因,只需单击即可获取重新验证的命令。其他工具可视化的是代码结构,而 limpet 可视化的是你的 agent 知道什么,以及它是否依然成立。由同一个单一二进制文件提供服务,仅绑定到 127.0.0.1。 ## 📦 安装 **一行命令。** 安装程序会为你的平台下载最新的发布版二进制文件,验证其 sha256,安装它,并将其注册到 Claude Code: macOS / Linux: ``` curl -fsSL https://raw.githubusercontent.com/KSym04/limpet/main/install.sh | bash ``` Windows (PowerShell): ``` irm https://raw.githubusercontent.com/KSym04/limpet/main/install.ps1 | iex ``` 然后重启 Claude Code,在任何项目中输入 `/limpet`。设置到此结束。(`LIMPET_INSTALL_DIR` 可覆盖安装位置;`LIMPET_VERSION` 可指定固定的发布标签。) 不想将脚本直接通过管道传给你的 shell?下面的每一步都是手动等效操作。预构建的二进制文件适用于 Apple Silicon 架构的 macOS、x86_64 架构的 Linux 和 x86_64 架构的 Windows,可在 [最新发布版](https://github.com/KSym04/limpet/releases/latest) 中获取;每个资产都有已发布的 sha256 —— 在信任该二进制文件之前请先进行验证。 **macOS (Apple Silicon)** ``` curl -fsSLO https://github.com/KSym04/limpet/releases/latest/download/limpet-aarch64-apple-darwin.tar.gz curl -fsSLO https://github.com/KSym04/limpet/releases/latest/download/limpet-aarch64-apple-darwin.tar.gz.sha256 shasum -a 256 -c limpet-aarch64-apple-darwin.tar.gz.sha256 tar xzf limpet-aarch64-apple-darwin.tar.gz # extracts a single `limpet` binary sudo install -m 755 limpet /usr/local/bin/limpet limpet install ``` macOS 可能会隔离下载的二进制文件;如果它拒绝运行,请使用 `xattr -d com.apple.quarantine /usr/local/bin/limpet` 清除该标志。 **Linux (x86_64)** ``` curl -fsSLO https://github.com/KSym04/limpet/releases/latest/download/limpet-x86_64-unknown-linux-gnu.tar.gz curl -fsSLO https://github.com/KSym04/limpet/releases/latest/download/limpet-x86_64-unknown-linux-gnu.tar.gz.sha256 sha256sum -c limpet-x86_64-unknown-linux-gnu.tar.gz.sha256 tar xzf limpet-x86_64-unknown-linux-gnu.tar.gz # extracts a single `limpet` binary sudo install -m 755 limpet /usr/local/bin/limpet # or ~/.local/bin if it is on your PATH limpet install ``` **Windows (x86_64, PowerShell)** ``` Invoke-WebRequest https://github.com/KSym04/limpet/releases/latest/download/limpet-x86_64-pc-windows-msvc.zip -OutFile limpet.zip Invoke-WebRequest https://github.com/KSym04/limpet/releases/latest/download/limpet-x86_64-pc-windows-msvc.zip.sha256 -OutFile limpet.zip.sha256 # 比较这两个 hash — 它们必须匹配 (Get-FileHash limpet.zip -Algorithm SHA256).Hash Get-Content limpet.zip.sha256 Expand-Archive limpet.zip -DestinationPath "$env:LOCALAPPDATA\limpet" # extracts limpet.exe # 将该文件夹添加到 PATH 一次,然后重启终端 [Environment]::SetEnvironmentVariable('Path', $env:Path + ";$env:LOCALAPPDATA\limpet", 'User') limpet install ``` **使用 Rust** ([rustup.rs](https://rustup.rs)) —— 适用于 Intel Mac、ARM Linux 以及任何没有预构建二进制文件的平台: ``` cargo install limpet limpet install ``` 重启 Claude Code。完成。(`limpet install --dry-run` 可先预览确切的配置更改;`limpet uninstall` 可撤销这些更改。) 稍后使用 `limpet update` 进行更新:它会获取你平台的最新发布二进制文件,根据已发布的 sha256 对其进行验证,并原子性地替换正在运行的执行文件。`limpet update --check` 可报告是否存在较新版本而不进行安装。这是唯一会触及网络的命令。之后请重启 Claude Code,以便 MCP 服务器重新加载到新的二进制文件上。 ## 🚀 60 秒上手指南 **1. 在任何项目中输入 `/limpet`。** 它会索引代码,召回已知的所有内容(标记陈旧项),并将本次会话切换为记忆优先模式。 **2. 尽情工作即可。** agent 现在会在学习的同时存储所学内容: **3. 下一次会话,在全新的上下文中,询问它曾经学过的任何内容:** 无需费力翻找文件,无需重新解释你自己的代码库。 **4. 修改代码,记忆会做出反应。** 编辑该函数,记忆在出现的每个地方都会翻转为 `stale: body_edited`。重命名或移动该函数,记忆会默默跟随。当信息不再最新时,绝不会假装是最新的。 日常命令: | 命令 | 作用 | |---|---| | `/limpet` | index + recall + 本会话的记忆优先模式 | | `/limpet status` | 计数以及任何需要注意的内容 | | `/limpet review` | 使用存储的证明命令重新验证陈旧的事实 | | `/limpet export` | 写入 `.limpet/memory.jsonl` 以提交并与团队共享 | | `limpet stats` | token 节省收据:包含会话和生命周期统计及方法论 | | `limpet doctor` | 单屏安装诊断;也会在安装和更新后自动运行 | | `limpet ui` | 位于 http://127.0.0.1:9748 的知识图谱,在一个视图中查看所有项目 | | `limpet statusline` | 状态栏片段(记忆 + 节省的 token),只读且即时 | | `limpet hook` | 为 Claude Code hooks 提供单行 SessionStart 简报,只读 | | `limpet update` | 自我更新至最新发布版本,经过校验和验证(唯一联网的命令) | 数据存放在 `~/.local/share/limpet/` 下,每个仓库一个 SQLite 存储。团队成员在拉取 JSONL 后运行 `limpet import`。 **任何平台上的状态栏。** `limpet statusline --root ` 会打印 shell 片段(`| 🐚 13 · ↑32k tokens saved`,当 UI 运行时,计数会超链接到该项目的图谱),或者什么都不打印——它以严格的只读方式打开存储,从不写入,且总是以状态码 0 退出,因此可以安全地驻留在提示符中。因为渲染逻辑存在于二进制文件中,同一行代码既可以在 macOS/Linux 的 bash 状态栏中工作,也可以在 Windows 的 PowerShell 或 cmd 状态栏中工作——不需要 sqlite3 CLI,也不需要 bash: ``` # 在 Claude Code statusline.ps1 内部 $limpetSeg = & limpet statusline --root $projectDir ``` 使用 `/limpet statusline` 将其关闭(写入 `~/.claude/.limpet-statusline-off`;该命令会遵循此标志)。 **会话开始时自动召回。** 没有任何 hook 的情况下,记忆优先的行为取决于 agent 是否记得输入 `/limpet`。有了 SessionStart hook,在一个已经索引的项目中,每个新会话都会以注入上下文的单行简报开始——“本项目拥有 limpet 记忆:13 条活跃记忆,2 条陈旧……”——agent 会在读取之前进行召回。添加到 `~/.claude/settings.json`: ``` { "hooks": { "SessionStart": [ { "hooks": [ { "type": "command", "command": "PATH=\"$HOME/.local/bin:$HOME/.cargo/bin:$PATH\" limpet hook" } ] } ] } } ``` `limpet hook` 在项目没有存储时不打印任何内容(已索引仓库之外的会话保持干净),它以严格的只读方式打开存储,并且总是以状态码 0 退出。显式的 PATH 前缀很重要:hook 运行在非登录 shell 中,通常缺少 `~/.local/bin` 和 `~/.cargo/bin`,而静默丢失的二进制文件正是该命令被设计为永远绝不暴露的故障类型。在 Windows 上,请使用 `%USERPROFILE%\AppData\Local\Programs\limpet\limpet.exe hook`。 ### 初始化项目 `/limpet scan` 从已存在的内容冷启动一个仓库的记忆。`light` 模式(默认)会收集合并提交、标签和 README;`deep` 模式会增加所有文档、长篇幅提交记录以及助手的工程记忆目录。预检召回会捕捉到存储中已经回答了的候选项,因此重新运行只会补充空白。收集工作在子 agent 中运行,以将原始 git 输出和文档转储排除在主上下文之外;只返回经过筛选的候选表。在写入任何内容之前,你分两层批准候选项:高置信度项目作为一个整块批准(默认同意,异常拒绝),边缘项目逐一批准。每一个被批准的记忆都会盖上一个 `origin` 戳,以便在未来的扫描中捕获重复项;而对于私密来源的项目,会以 `private: true` 进行存储,因此它们永远不会被导出。 ## 🌳 整个仓库被索引,刻意保持精简 **仓库中的每一个文件都会被索引并支持锚定。** 带有已发布语法的文件(PHP, JavaScript, TypeScript, Python, Rust, C/C++)可获取完整的符号提取:函数、类、导入以及标记为 `syntactic` 的基于名称的调用引用。其他所有文件(`.twig`、`.scss`、`.vue`、`.blade.php`、`.erb`、`.md`、`.yml`、配置文件等)都会获得一个带有内容哈希的文件级节点,因此记忆可以锚定到它上面,并在它发生改变的那一刻变为 `stale:file_edited`。在模板繁重的技术栈中,这恰恰是值得记住的知识真正存在的地方。 遗留编码会优雅降级:一个匹配了语法但不是有效 UTF-8 的文件(比如旧 C++ 引擎中的 CP949 或 UTF-16 源码)会保留其文件级锚点,而不是从索引中消失。语法只能提升文件的处理级别,绝不会使其变得不可锚定。 扫描刻意跳过的内容:`.gitignore` 中的所有内容,可选的 `.limpetignore` 中的所有内容(使用 gitignore 语法,即使在没有 git 仓库的情况下也能工作)、`node_modules`/`vendor`/`target`/`dist`/`build`、隐藏的垃圾文件、`*.min.*` 资产以及超过 8MB 的文件。超过 512KB 的源文件会在文件级别被索引,但绝不会为了提取符号而解析:此上限是为了保护 tree-sitter 免受自动生成的捆绑包的影响,并且让一个巨大的手写翻译单元依然可锚定,而不是消失。正是这些边界条件,才使得一个没有 `.gitignore` 的庞大第三方依赖树不会占满你的 CPU;你可以使用 `.limpetignore` 排除其他任何内容。 这里没有 LSP,没有类型推断,也没有声称可以发布调用图:索引的存在是为了给记忆提供锚点、失效机制和召回局部性。每一个发布的语法在测试套件中都有 fixture 覆盖;语言的加入取决于它们是否能被测试,而不是为了凑数。 新鲜度模型:每一次工具调用都会运行一个有界的增量扫描(通过 tree-sitter,更改后的文件能在几毫秒内重新解析)。查询永远不会因为索引而阻塞;任何仍然处于脏状态的内容都会列在信封中。 ## 🔒 安全态势 - **默认本地化。** 索引、召回、记忆和 UI 绝不进行网络调用:没有遥测,没有 API 密钥,没有云。唯一的例外是你显式调用的 `limpet update`;它通过 HTTPS 获取一个经过校验和验证的发布二进制文件,并且除了一个 `limpet/` 的 User-Agent 之外不发送任何内容。UI 绑定到 127.0.0.1,并且只提供一个嵌入式页面,仅限 GET 请求。 - **秘密永不停留。** `remember` 会扫描每一个主体和证据输出,并拒绝存储任何看起来像凭据的东西(云访问密钥、提供商令牌、PEM 私钥块、JWT),因此机密信息无法到达本地存储或共享的 `.limpet/memory.jsonl` 中。 - **私有记忆保留在本地。** 以 `private: true` 存储的记忆在项目内可被正常召回,但绝不会包含在 `export` 输出中,因此敏感上下文无法到达共享的 `.limpet/memory.jsonl`。 - **没有 shell 插值。** 外部命令(仅限 git)使用参数数组运行;没有任何字符串会触及 shell。 - **路径验证。** 通过 MCP 到达的每个文件路径都会根据仓库根目录进行检查;绝对路径和路径遍历在同一个关键节点被拒绝。 - **仅限参数化 SQL。** 代码库中没有查询会拼接用户输入。 - **畸形输入可存活。** JSON-RPC 循环会针对解析错误和处理程序 panic 回应 JSON-RPC 错误,并继续提供服务。 - `install` 仅编辑其自身的 `mcpServers.limpet` 条目,拒绝触碰它不识别的配置;`uninstall` 会完全逆转该操作。 - **导入是受保护的路径。** 从团队成员处拉取的 `.limpet/memory.jsonl` 是不受信任的输入,因此 `import` 强制执行与 `remember` 相同的规则:拒绝机密(它们永远无法进入存储,即使是来自同伴的)、对主体进行大小限制、限制置信度、未来日期的条目无法毒害合并,并且导入的锚点哈希会根据你本地的代码重新解析,因此伪造的哈希无法伪造新鲜度。被拒绝的行会被计数,绝不会静默应用。 - **安装损坏了?运行 `limpet doctor`。** 它会检查二进制文件、Claude Code 注册情况(包括移动过的二进制文件不匹配的情况)、skill 文件、存储、其戳和索引,逐行打印 ok/FAIL。它会在 `install` 和 `update` 之后自动运行。 ## 🚫 limpet 不是什么 - 不是通用的 AI 记忆存储、向量数据库或 RAG 流水线。没有嵌入,没有相似度猜测:锚点是确定性的 AST 哈希,陈旧性是事实,而不是评分。不感知代码的记忆存储永远不会注意到代码何时向前演进;而察觉到这一点正是 limpet 的全部前提。 - 不是代码搜索引擎。你的 agent 已经有 grep 了。 - 不是调用图预言机。调用边是句法层面的,并已如此标记。 - 不是云记忆平台。没有账号,没有同步,没有服务器。 - 检索质量取决于写入的内容:简短、具体、有锚点的记忆召回效果更好,而工具模式会引导 agent 恰好做到这一点。 ## 🧭 路线图 完整计划请参见 [ROADMAP.md](ROADMAP.md) —— 基于符号的片段检索、避免返工指标、可移植的仓库身份、第二波语法以及 1.0 稳定性契约。有一条规则支配着这一切:一个功能只有在它能为收据(`limpet stats`、基准测试、避免返工)或诚实信封提供数据时才会发布。 ## ⚖️ 依赖声明与许可 limpet 是判断的辅助工具,而非替代品。它的新鲜度和 置信度信号是根据代码结构计算出的启发式指标,而非证明: 一条“active”的记忆是 limpet 提供的表明存储知识仍然成立的最佳证据, 绝不是它正确或完整的保证。在依赖它之前,请验证任何你不想弄错的事情, 尤其是在安全敏感或生产环境的更改中。整体设计理念是 揭示不确定性而非掩盖它([PHILOSOPHY.md](PHILOSOPHY.md)), 而只有当你阅读它给你的标志时,这才能保护你。 本软件按“原样”提供,不附带任何形式的保证,作者对其 使用不承担任何责任,如下面的许可协议所述。 欢迎做出贡献,并按照与项目相同的 MIT 许可协议接受(入站 = 出站): 通过提交 pull 请求,即表示你同意你的贡献可以按照这些条款进行分发。请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。 ## 📄 许可证 MIT。请参阅 [LICENSE](LICENSE)。MIT 许可声明包含明确的免责声明及 责任限制;这些条款是对用户和作者双方都具有法律效力的保护。
标签:AI编程助手, MCP, Rust, SOC Prime, SQLite, 上下文管理, 可视化界面, 开发工具, 网络流量审计, 记忆系统, 通知系统