feat: add hashline editing and TypeScript LSP

This commit is contained in:
云服务部-叶林立
2026-08-19 10:24:03 +08:00
parent 3d431a353d
commit bf8bb42a09
14 changed files with 438 additions and 36 deletions
+8 -6
View File
@@ -14,14 +14,15 @@
- 该目录从上游源码仓库导入并由本仓库直接维护,不使用 npm 预编译产物,不是 submodule,也不保留嵌套 `.git` - 该目录从上游源码仓库导入并由本仓库直接维护,不使用 npm 预编译产物,不是 submodule,也不保留嵌套 `.git`
- `extensions/fff-override.ts`:强制使用 FFF 官方 `override` 模式,统一接管 `find``grep``multi_grep` 和 FFF 的 `@` 补全;显式 CLI flag 仍遵循 FFF 官方优先级。 - `extensions/fff-override.ts`:强制使用 FFF 官方 `override` 模式,统一接管 `find``grep``multi_grep` 和 FFF 的 `@` 补全;显式 CLI flag 仍遵循 FFF 官方优先级。
- `context-mode`:加载上游 Pi adapter 与 skills,提供 `ctx_*` 工具、隔离式大输出处理和会话连续性。 - `context-mode`:加载上游 Pi adapter 与 skills,提供 `ctx_*` 工具、隔离式大输出处理和会话连续性。
- `extensions/hashline.ts`:部署组合包权威 `config/pi-hashline-edit.json` 后加载 `pi-hashline-edit`,以行哈希锚点覆盖内置 `read` / `edit`Hashline 自带 `grep` 强制关闭。
- `extensions/codegraph.ts`:通过隔离的 `pi-mcp-adapter` 配置连接机器现有的 `codegraph serve --mcp`,只暴露 `codegraph_explore` - `extensions/codegraph.ts`:通过隔离的 `pi-mcp-adapter` 配置连接机器现有的 `codegraph serve --mcp`,只暴露 `codegraph_explore`
- `extensions/permission-system.ts`:在权限扩展注册前,将 `config/pi-permission-system.json` 同步为全局权威配置。 - `extensions/permission-system.ts`:在权限扩展注册前,将 `config/pi-permission-system.json` 同步为全局权威配置。
- `extensions/lsp.ts`部署 Kotlin/JDT LS 全局配置并加载 `pi-lsp` - `extensions/lsp.ts`把组合包内 TypeScript Language Server CLI 物化为绝对命令路径,部署 TypeScript/Kotlin/JDT LS 全局配置并加载 `pi-lsp`
- `extensions/tool-routing.ts`:保留 Pi 默认系统提示词,在每轮开始前按激活工具追加简短路由规则,并提供 `/dump-system-prompt` 将扩展所见的有效提示词写入 `.pi-debug/effective-system-prompt.md` - `extensions/tool-routing.ts`:保留 Pi 默认系统提示词,在每轮开始前按激活工具追加简短路由规则,并提供 `/dump-system-prompt` 将扩展所见的有效提示词写入 `.pi-debug/effective-system-prompt.md`
- `pi-lsp@0.1.7`:提供声明式 LSP 接入;组合包配置 `kotlin-lsp --stdio``jdtls` - `pi-lsp@0.1.7`:提供声明式 LSP 接入;组合包内置 `typescript-language-server@5.3.0` + `typescript@6.0.3`,并配置机器级 `kotlin-lsp --stdio``jdtls`
- `pi-hermes-memory@0.9.6`:提供持久记忆、会话搜索、后台学习和 secret scanning,默认使用 policy-only 模式。 - `pi-hermes-memory@0.9.6`:提供持久记忆、会话搜索、后台学习和 secret scanning,默认使用 policy-only 模式。
- `@ogulcancelik/pi-codex-compaction@0.1.3`:为 `openai-codex` 提供原生远程 compaction,默认阈值为 90%。 - `@ogulcancelik/pi-codex-compaction@0.1.3`:为 `openai-codex` 提供原生远程 compaction,默认阈值为 90%。
- 根包还固定安装 `pi-context-view``@firstpick/pi-extension-codex-fast-mode``@gotgenes/pi-permission-system` - 根包还固定安装 `pi-hashline-edit@0.8.3``typescript-language-server@5.3.0``typescript@6.0.3``pi-context-view``@firstpick/pi-extension-codex-fast-mode``@gotgenes/pi-permission-system`
- `install.sh`:先安装根组合包,再交互检查 Kitty/Solarized Dark、Oh My Zsh/Powerlevel10k/Zsh 插件、CodeGraph、Kotlin LSP、Java 21+ 和 JDT LS;缺失项目只在用户明确选择 `Y` 后安装或配置。Powerlevel10k 默认配置来自仓库内置的 `config/p10k.zsh`(当前 Rainbow/ASCII 单行紧凑主题)。 - `install.sh`:先安装根组合包,再交互检查 Kitty/Solarized Dark、Oh My Zsh/Powerlevel10k/Zsh 插件、CodeGraph、Kotlin LSP、Java 21+ 和 JDT LS;缺失项目只在用户明确选择 `Y` 后安装或配置。Powerlevel10k 默认配置来自仓库内置的 `config/p10k.zsh`(当前 Rainbow/ASCII 单行紧凑主题)。
- `update.sh`:先通过 `pi update` 升级根组合包,再只升级当前已安装的终端环境和机器级依赖;未安装项直接跳过。升级前先查询并比较本地与远端版本,只有版本不同时才下载或替换;Powerlevel10k 配置优先从 `pi update` 后的已安装组合包读取,并与 `.zshrc` 受管块一起按内容比较后增量同步。 - `update.sh`:先通过 `pi update` 升级根组合包,再只升级当前已安装的终端环境和机器级依赖;未安装项直接跳过。升级前先查询并比较本地与远端版本,只有版本不同时才下载或替换;Powerlevel10k 配置优先从 `pi update` 后的已安装组合包读取,并与 `.zshrc` 受管块一起按内容比较后增量同步。
- `uninstall.sh`:只移除根组合包,不卸载或还原可能被其他项目共享的终端环境、CodeGraph、Kotlin LSP、JDT LS 或 Java。 - `uninstall.sh`:只移除根组合包,不卸载或还原可能被其他项目共享的终端环境、CodeGraph、Kotlin LSP、JDT LS 或 Java。
@@ -34,7 +35,8 @@
- `readCompaction.enabled``sourceCodeFilteringEnabled``smartTruncate.enabled` 当前均默认 `false`,源码读取保持原样。未经用户明确决定,不因节省上下文而改变这些默认值。 - `readCompaction.enabled``sourceCodeFilteringEnabled``smartTruncate.enabled` 当前均默认 `false`,源码读取保持原样。未经用户明确决定,不因节省上下文而改变这些默认值。
- 若以后开启 read 压缩,优先考虑 `readCompaction + smartTruncate`,源码过滤仍独立评估;必须保留精确 `offset/limit` 读取、短文件和行锚点的完整性。 - 若以后开启 read 压缩,优先考虑 `readCompaction + smartTruncate`,源码过滤仍独立评估;必须保留精确 `offset/limit` 读取、短文件和行锚点的完整性。
- Context Mode 负责避免批量读取、命令研究和网页原始内容直接撑大上下文;FFF 仍负责精确字面搜索,RTK 仍处理未走 Context Mode 的普通输出。 - Context Mode 负责避免批量读取、命令研究和网页原始内容直接撑大上下文;FFF 仍负责精确字面搜索,RTK 仍处理未走 Context Mode 的普通输出。
- 工具路由规则不得替换 Pi 默认系统提示词:代码结构与符号关系优先 CodeGraph;字面搜索先用 FFF `find` 收敛文件位置,再在收敛路径内用 `grep`/`multi_grep` 获取行号,最后才按需精确 `read`。遇到大量结果、截断或上限时继续缩小 path/glob/pattern,不得靠提高 limit 或倾倒全部结果解决 - Hashline 默认以 2 字符行哈希覆盖内置 `read` / `edit``grep` `replaceText` 均关闭;它保证锚定编辑而不负责压缩,大文件上下文节省必须来自先定位/隔离分析、再做最小范围 `offset/limit` 读取
- 工具路由规则不得替换 Pi 默认系统提示词:代码结构、调用关系和待修改 symbol 优先 CodeGraph,定义/引用/类型/诊断优先 LSP;工作区内大文件探索、分析和总结优先 `ctx_execute_file`,日志、构建与不可预测命令输出优先 Context Mode;字面搜索先用 FFF `find` 收敛文件位置,再在收敛路径内用 `grep`/`multi_grep` 获取行号;真正修改前才用小范围 Hashline `read` 获取新鲜锚点并用锚定 `edit`。遇到大量结果、截断或上限时继续缩小 path/glob/pattern,不得靠提高 limit 或倾倒全部结果解决。
- `.pi-debug/``/dump-system-prompt` 生成的本地诊断目录,不提交到仓库,也不作为组合包运行时配置源。 - `.pi-debug/``/dump-system-prompt` 生成的本地诊断目录,不提交到仓库,也不作为组合包运行时配置源。
- CodeGraph 扩展只配置 Pi 到外部 `codegraph` 命令的 MCP 连接。根包安装本身不安装 CodeGraph;便捷脚本仅在组合包安装完成且用户明确选择 `Y` 后调用官方安装器。仓库不执行 `codegraph init`,不创建或管理 `.codegraph/`,也不改动索引、更新或遥测设置。 - CodeGraph 扩展只配置 Pi 到外部 `codegraph` 命令的 MCP 连接。根包安装本身不安装 CodeGraph;便捷脚本仅在组合包安装完成且用户明确选择 `Y` 后调用官方安装器。仓库不执行 `codegraph init`,不创建或管理 `.codegraph/`,也不改动索引、更新或遥测设置。
- CodeGraph MCP 使用 `keep-alive` 并只直接暴露 `codegraph_explore`;命令缺失、项目未初始化或连接失败时不得阻止其他扩展加载。 - CodeGraph MCP 使用 `keep-alive` 并只直接暴露 `codegraph_explore`;命令缺失、项目未初始化或连接失败时不得阻止其他扩展加载。
@@ -45,7 +47,7 @@
- reviewer 返回 `allow` 时自动批准、返回 `deny` 时直接拒绝,配置、模型、认证、超时或响应异常时必须 `defer` 到正常人工提示。`pi-permission-system` 的 delegation envelope 继续禁止 authorizer 自动批准 `path``external_directory` 请求。 - reviewer 返回 `allow` 时自动批准、返回 `deny` 时直接拒绝,配置、模型、认证、超时或响应异常时必须 `defer` 到正常人工提示。`pi-permission-system` 的 delegation envelope 继续禁止 authorizer 自动批准 `path``external_directory` 请求。
- 默认 reviewer 为 `openai-codex/codex-auto-review`、low reasoning、90 秒总重试预算和内置 Codex Guardian 风格策略;只把 active branch 中的直接用户消息与已识别结构化问答作为授权证据,assistant/tool/compaction 内容不能自行授权。 - 默认 reviewer 为 `openai-codex/codex-auto-review`、low reasoning、90 秒总重试预算和内置 Codex Guardian 风格策略;只把 active branch 中的直接用户消息与已识别结构化问答作为授权证据,assistant/tool/compaction 内容不能自行授权。
- `config/pi-permission-system.json` 必须显式配置 `authorizerChain: ["auto-review"]`,并把需要自动复核的 Git 非只读操作、包管理及其他类别声明为 `ask`;硬 `deny` 不得改成可由模型覆盖的 `ask` - `config/pi-permission-system.json` 必须显式配置 `authorizerChain: ["auto-review"]`,并把需要自动复核的 Git 非只读操作、包管理及其他类别声明为 `ask`;硬 `deny` 不得改成可由模型覆盖的 `ask`
- `pi-lsp` 使用组合包部署的 `kotlin-lsp --stdio``jdtls` 配置,根包安装本身不安装这两个系统可执行文件便捷脚本可在用户逐项明确确认后通过 Homebrew 安装 Kotlin LSP、Java 21 和 JDT LS。 - `pi-lsp` 的 TypeScript/JavaScript 后端由根包固定依赖提供,`extensions/lsp.ts` 使用当前 Node 可执行文件直接启动包内 `typescript-language-server` CLI,不得依赖或调用 VS Code GUIKotlin/JDT LS 仍使用组合包部署的 `kotlin-lsp --stdio``jdtls` 配置,根包不安装这两个系统可执行文件便捷脚本可在用户逐项明确确认后通过 Homebrew 安装 Kotlin LSP、Java 21 和 JDT LS。
- 终端环境配置只属于便捷脚本:Kitty 可选安装后可通过官方 kitten 启用 Solarized DarkOh My Zsh 使用不切换 shell 的 unattended 安装;Powerlevel10k 将仓库内置的 `config/p10k.zsh`Rainbow/ASCII 单行紧凑主题)部署到 `~/.config/my-pi/p10k.zsh`(遵循 `XDG_CONFIG_HOME`),不得覆盖用户自己的 `~/.p10k.zsh``.zshrc` 中脚本拥有的内容必须使用 `# >>> my-pi:<id> >>>` / `# <<< my-pi:<id> <<<` 受管块,更新时只替换块内内容;标记不完整或重复时拒绝修改。实际修改前必须创建带时间戳的备份。`git``zsh-autosuggestions``zsh-syntax-highlighting` 在安装流程中逐项询问后才安装或启用。 - 终端环境配置只属于便捷脚本:Kitty 可选安装后可通过官方 kitten 启用 Solarized DarkOh My Zsh 使用不切换 shell 的 unattended 安装;Powerlevel10k 将仓库内置的 `config/p10k.zsh`Rainbow/ASCII 单行紧凑主题)部署到 `~/.config/my-pi/p10k.zsh`(遵循 `XDG_CONFIG_HOME`),不得覆盖用户自己的 `~/.p10k.zsh``.zshrc` 中脚本拥有的内容必须使用 `# >>> my-pi:<id> >>>` / `# <<< my-pi:<id> <<<` 受管块,更新时只替换块内内容;标记不完整或重复时拒绝修改。实际修改前必须创建带时间戳的备份。`git``zsh-autosuggestions``zsh-syntax-highlighting` 在安装流程中逐项询问后才安装或启用。
- Codex 远程压缩只对 `openai-codex` 生效,默认在 turn boundary 达到 90% 时触发;Hermes Memory 默认使用 policy-only 模式。 - Codex 远程压缩只对 `openai-codex` 生效,默认在 turn boundary 达到 90% 时触发;Hermes Memory 默认使用 policy-only 模式。
- `install.sh` 默认从 `git:git@bitbucket.org:siakitem/my-pi.git` 安装,并允许用 `PI_PACKAGE_SOURCE` 覆盖来源;组合包安装失败时立即停止,机器级依赖安装失败时继续检查其余依赖并最终返回非零状态。 - `install.sh` 默认从 `git:git@bitbucket.org:siakitem/my-pi.git` 安装,并允许用 `PI_PACKAGE_SOURCE` 覆盖来源;组合包安装失败时立即停止,机器级依赖安装失败时继续检查其余依赖并最终返回非零状态。
@@ -56,7 +58,7 @@
- 优先在目标扩展目录内完成改动;不要让一个扩展依赖另一个扩展的未公开内部实现。 - 优先在目标扩展目录内完成改动;不要让一个扩展依赖另一个扩展的未公开内部实现。
- 保留原项目的 `LICENSE`、版权信息和必要的来源说明。 - 保留原项目的 `LICENSE`、版权信息和必要的来源说明。
- 扩展运行目录中的 `config.json`、日志、构建产物、覆盖率目录和依赖目录属于本地状态,不应提交;`config/pi-permission-system.json` 是权限基线与 authorizer chain 的组合包权威源配置,必须提交并维护。 - 扩展运行目录中的 `config.json`、日志、构建产物、覆盖率目录和依赖目录属于本地状态,不应提交;`config/pi-permission-system.json``config/pi-hashline-edit.json``config/lsp.json` 分别是权限链、Hashline 默认行为和 LSP 后端的组合包权威源配置,必须提交并维护。
- 外部 Pi 扩展依赖必须在根 `package.json` 中使用精确版本,并更新根 `package-lock.json`;不要用仓库级 `.pi/settings.json` 代替组合包依赖。 - 外部 Pi 扩展依赖必须在根 `package.json` 中使用精确版本,并更新根 `package-lock.json`;不要用仓库级 `.pi/settings.json` 代替组合包依赖。
- 需要原生构建的依赖只按锁定版本加入根 `allowScripts`;当前仅允许 Hermes Memory 所需的 `better-sqlite3`,不得批量批准其他 install scripts。 - 需要原生构建的依赖只按锁定版本加入根 `allowScripts`;当前仅允许 Hermes Memory 所需的 `better-sqlite3`,不得批量批准其他 install scripts。
- Pi 核心包只作为宿主 peer dependencies,不得在组合包内再安装或打包一套 Pi runtime;保留根 `.npmrc` 的 peer 安装策略。 - Pi 核心包只作为宿主 peer dependencies,不得在组合包内再安装或打包一套 Pi runtime;保留根 `.npmrc` 的 peer 安装策略。
+15 -6
View File
@@ -8,7 +8,7 @@
- `pi-mcp-adapter@2.26.0`:只用于把机器现有的 CodeGraph MCP Server 接入为 `codegraph_explore` - `pi-mcp-adapter@2.26.0`:只用于把机器现有的 CodeGraph MCP Server 接入为 `codegraph_explore`
- `pi-context-view@0.4.2`:查看上下文占用。 - `pi-context-view@0.4.2`:查看上下文占用。
- `@firstpick/pi-extension-codex-fast-mode@0.1.1`:为 Codex provider 提供会话级 `/fast-mode` - `@firstpick/pi-extension-codex-fast-mode@0.1.1`:为 Codex provider 提供会话级 `/fast-mode`
- `pi-lsp@0.1.7`:为 Kotlin 和 Java 提供 LSP 诊断、跳转与符号工具。 - `pi-lsp@0.1.7`:为 TypeScript/JavaScript、Kotlin 和 Java 提供 LSP 诊断、跳转与符号工具TypeScript 后端由组合包内置
- `pi-hermes-memory@0.9.6`:提供持久记忆、会话搜索和 secret scanning。 - `pi-hermes-memory@0.9.6`:提供持久记忆、会话搜索和 secret scanning。
- `@ogulcancelik/pi-codex-compaction@0.1.3`:为 `openai-codex` 提供原生远程压缩。 - `@ogulcancelik/pi-codex-compaction@0.1.3`:为 `openai-codex` 提供原生远程压缩。
- 本仓库维护的 `pi-permission-auto-review`:作为 `pi-permission-system` authorizer,使用 Codex Guardian 风格策略自动复核 `ask` 请求。 - 本仓库维护的 `pi-permission-auto-review`:作为 `pi-permission-system` authorizer,使用 Codex Guardian 风格策略自动复核 `ask` 请求。
@@ -115,12 +115,18 @@ CodeGraph 本体不由组合包安装。需要使用 CodeGraph 的机器应自
### 工具与搜索路由 ### 工具与搜索路由
`extensions/tool-routing.ts` 不替换 Pi 默认系统提示词,而是在每轮开始前根据当前激活工具追加简短规则:代码结构和符号关系优先使用 CodeGraph;字面搜索先用 FFF `find` 缩小文件或目录范围,再在已收敛的路径中用 `grep`/`multi_grep` 获取行号,最后才用带 `offset/limit``read` 读取精确区域。宽泛搜索命中上百或上千结果、发生截断或达到上限时,应继续缩小路径、glob 或 pattern,而不是提高 limit 或输出全部结果。 `extensions/tool-routing.ts` 不替换 Pi 默认系统提示词,而是在每轮开始前根据当前激活工具追加简短规则:代码结构、调用关系和待修改 symbol 优先使用 CodeGraph,定义、引用、类型和修改后诊断优先使用 LSP;字面搜索先用 FFF `find` 缩小文件或目录范围,再在已收敛的路径中用 `grep`/`multi_grep` 获取行号。宽泛搜索命中上百或上千结果、发生截断或达到上限时,应继续缩小路径、glob 或 pattern,而不是提高 limit 或输出全部结果。
日志、测试/构建输出、大文件分析和不可预测的大输出优先交给 Context Mode;需要编辑所依赖的精确原文或短文件才直接使用 `read`。已有工具结果足够时停止检索,避免对同一问题依次重复调用 CodeGraph、FFF 和 `read` 日志、测试/构建输出、工作区内大文件的探索/分析/总结以及不可预测的大输出优先交给 Context Mode。只有确实需要精确源码或准备修改时,才用带 `offset/limit` 的 Hashline `read` 读取最小区域并取得 `LINE#HASH` 锚点,随后用锚定 `edit` 修改;成功编辑返回的新锚点可继续复用,只有锚点过期或下一目标区域尚未展示时才重新读取。已有工具结果足够时停止检索,避免对同一问题依次重复调用 CodeGraph、FFF 和 `read`
使用 `/dump-system-prompt` 可将当前扩展所见的有效提示词写入当前项目的 `.pi-debug/effective-system-prompt.md``.pi-debug/` 属于本地诊断输出,默认不提交。 使用 `/dump-system-prompt` 可将当前扩展所见的有效提示词写入当前项目的 `.pi-debug/effective-system-prompt.md``.pi-debug/` 属于本地诊断输出,默认不提交。
### 精确读写归 Hashline
组合包固定加载 `pi-hashline-edit@0.8.3`,以带 `LINE#HASH` 行锚点的实现覆盖 Pi 内置 `read``edit``extensions/hashline.ts` 在加载扩展前把 `config/pi-hashline-edit.json` 同步到 `~/.pi/agent/hashline.json``hashLength` 保持最小的 `2``grep` 强制关闭以避免与 FFF 冲突,`replaceText` 关闭以要求修改使用可验证锚点。
Hashline 不负责压缩大文件;每行锚点本身还会增加少量 token。上下文节省来自先用 CodeGraph/LSP 定位、再用 Context Mode 隔离分析,最后只对真正准备修改的区域执行小范围 Hashline `read`。因此不应为了探索、计数、比较或总结而直接读取完整大文件。
### 搜索归 FFF ### 搜索归 FFF
`extensions/fff-override.ts` 强制设置 FFF 官方的 `PI_FFF_MODE=override`(显式传入 `extensions/fff-override.ts` 强制设置 FFF 官方的 `PI_FFF_MODE=override`(显式传入
@@ -191,15 +197,18 @@ codegraph status
reviewer 默认使用 `openai-codex/codex-auto-review`、low reasoning 和内置 Codex Guardian 风格策略,并读取当前 session active branch 中的可信用户证据。可通过 `/permission-auto-review` 查看或调整全局/项目配置;无配置时使用源码内置默认值。 reviewer 默认使用 `openai-codex/codex-auto-review`、low reasoning 和内置 Codex Guardian 风格策略,并读取当前 session active branch 中的可信用户证据。可通过 `/permission-auto-review` 查看或调整全局/项目配置;无配置时使用源码内置默认值。
### Kotlin 与 Java LSP ### TypeScript、Kotlin 与 Java LSP
`extensions/lsp.ts` 会把 `config/lsp.json` 部署到全局 `~/.pi/agent/lsp.json`,其中启用: `extensions/lsp.ts` 会把 `config/lsp.json` 部署到全局 `~/.pi/agent/lsp.json`,其中启用:
- `typescript-language-server@5.3.0 --stdio`:匹配 TypeScript/JavaScript 及 JSX/TSX、MTS/CTS、MJS/CJS;组合包同时固定 `typescript@6.0.3` 作为 `tsserver` 后端。
- `kotlin-lsp --stdio`:匹配 `.kt``.kts` - `kotlin-lsp --stdio`:匹配 `.kt``.kts`
- `jdtls`:匹配 `.java`;官方 wrapper 会按当前项目工作目录选择 cache data 目录。 - `jdtls`:匹配 `.java`;官方 wrapper 会按当前项目工作目录选择 cache data 目录。
两者按 Gradle、Maven 或 Git marker 定位项目根,忽略常见构建输出,并为大型 Android/Gradle TypeScript LSP 通过组合包内 Node 和 `typescript-language-server` CLI 直接启动,不依赖、调用或拉起 VS Code GUI;部署配置时会把包内 CLI 的绝对路径写入全局配置。它优先使用项目自身可用的 TypeScript,否则回退到组合包固定的 TypeScript 6 后端。
项目预留 120 秒启动时间。`pi-lsp` 只负责接入,不安装 language server 可执行文件;使用前需
Kotlin 与 Java 按 Gradle、Maven 或 Git marker 定位项目根,忽略常见构建输出,并为大型 Android/Gradle
项目预留 120 秒启动时间。Kotlin 与 Java 的 language server 可执行文件不由根包安装;使用前需
确保 `kotlin-lsp``jdtls` 位于启动 Pi 的 `PATH`。Kotlin 官方 Homebrew 安装方式为: 确保 `kotlin-lsp``jdtls` 位于启动 Pi 的 `PATH`。Kotlin 官方 Homebrew 安装方式为:
```bash ```bash
+29
View File
@@ -1,6 +1,35 @@
{ {
"version": 1, "version": 1,
"servers": [ "servers": [
{
"id": "typescript-language-server",
"enabled": true,
"include": ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts", "**/*.js", "**/*.jsx", "**/*.mjs", "**/*.cjs"],
"exclude": ["**/node_modules/**", "**/dist/**", "**/build/**", "**/coverage/**"],
"rootMarkers": [
"tsconfig.json",
"jsconfig.json",
"package.json",
".git"
],
"bin": "typescript-language-server",
"args": ["--stdio"],
"cwd": "{root}",
"languageIdByExtension": {
".ts": "typescript",
".tsx": "typescriptreact",
".mts": "typescript",
".cts": "typescript",
".js": "javascript",
".jsx": "javascriptreact",
".mjs": "javascript",
".cjs": "javascript"
},
"startupTimeoutMs": 45000,
"diagnosticsWaitMs": 2000,
"initializationOptions": {},
"settings": {}
},
{ {
"id": "kotlin-lsp", "id": "kotlin-lsp",
"enabled": true, "enabled": true,
+5
View File
@@ -0,0 +1,5 @@
{
"hashLength": 2,
"grep": false,
"replaceText": false
}
+23
View File
@@ -0,0 +1,23 @@
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const BUNDLE_CONFIG_PATH = fileURLToPath(new URL("../config/pi-hashline-edit.json", import.meta.url));
export function deployBundleHashlineConfig(agentDir: string): void {
const targetPath = join(agentDir, "hashline.json");
const bundledConfig = readFileSync(BUNDLE_CONFIG_PATH, "utf8");
try {
if (readFileSync(targetPath, "utf8") === bundledConfig) {
return;
}
} catch {
// Missing or unreadable target: replace it with the bundle-owned defaults.
}
mkdirSync(dirname(targetPath), { recursive: true });
const temporaryPath = `${targetPath}.my-pi.tmp`;
writeFileSync(temporaryPath, bundledConfig, "utf8");
renameSync(temporaryPath, targetPath);
}
+9
View File
@@ -0,0 +1,9 @@
import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { deployBundleHashlineConfig } from "./hashline-config.ts";
/** Deploy bundle defaults before pi-hashline-edit reads its module-level config. */
export default async function bundledHashlineExtension(pi: ExtensionAPI): Promise<void> {
deployBundleHashlineConfig(getAgentDir());
const { default: hashlineExtension } = await import("../node_modules/pi-hashline-edit/index.ts");
hashlineExtension(pi);
}
+49
View File
@@ -0,0 +1,49 @@
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const BUNDLE_CONFIG_PATH = fileURLToPath(new URL("../config/lsp.json", import.meta.url));
const TYPESCRIPT_SERVER_ID = "typescript-language-server";
interface LspConfigFile {
version?: number;
servers?: Array<{
id?: string;
bin?: string;
args?: string[];
[key: string]: unknown;
}>;
}
export interface TypeScriptLspRuntime {
nodeBinary: string;
serverCliPath: string;
}
export function materializeBundleLspConfig(source: string, runtime: TypeScriptLspRuntime): string {
const config = JSON.parse(source) as LspConfigFile;
const server = config.servers?.find((entry) => entry.id === TYPESCRIPT_SERVER_ID);
if (server === undefined) {
throw new Error(`Missing ${TYPESCRIPT_SERVER_ID} entry in bundled LSP config`);
}
server.bin = runtime.nodeBinary;
server.args = [runtime.serverCliPath, "--stdio"];
return `${JSON.stringify(config, null, 2)}\n`;
}
export function deployBundleLspConfig(agentDir: string, runtime: TypeScriptLspRuntime): void {
const targetPath = join(agentDir, "lsp.json");
const bundledConfig = materializeBundleLspConfig(readFileSync(BUNDLE_CONFIG_PATH, "utf8"), runtime);
try {
if (readFileSync(targetPath, "utf8") === bundledConfig) return;
} catch {
// Missing or unreadable target: replace it with the bundle-owned baseline.
}
mkdirSync(dirname(targetPath), { recursive: true });
const temporaryPath = `${targetPath}.my-pi.tmp`;
writeFileSync(temporaryPath, bundledConfig, "utf8");
renameSync(temporaryPath, targetPath);
}
+8 -19
View File
@@ -1,28 +1,17 @@
import lspExtension from "../node_modules/pi-lsp/extensions/pi-lsp/index.ts"; import lspExtension from "../node_modules/pi-lsp/extensions/pi-lsp/index.ts";
import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { readFileSync, renameSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { deployBundleLspConfig } from "./lsp-config.ts";
const BUNDLE_CONFIG_PATH = fileURLToPath(new URL("../config/lsp.json", import.meta.url)); const TYPESCRIPT_LANGUAGE_SERVER_CLI = fileURLToPath(
new URL("../node_modules/typescript-language-server/lib/cli.mjs", import.meta.url),
function deployBundleLspConfig(): void { );
const targetPath = join(getAgentDir(), "lsp.json");
const bundledConfig = readFileSync(BUNDLE_CONFIG_PATH, "utf8");
try {
if (readFileSync(targetPath, "utf8") === bundledConfig) return;
} catch {
// Missing or unreadable target: replace it with the bundle-owned baseline.
}
const temporaryPath = `${targetPath}.my-pi.tmp`;
writeFileSync(temporaryPath, bundledConfig, "utf8");
renameSync(temporaryPath, targetPath);
}
/** Deploy the bundle-owned LSP config before registering pi-lsp. */ /** Deploy the bundle-owned LSP config before registering pi-lsp. */
export default function bundledLspExtension(pi: ExtensionAPI): void { export default function bundledLspExtension(pi: ExtensionAPI): void {
deployBundleLspConfig(); deployBundleLspConfig(getAgentDir(), {
nodeBinary: process.execPath,
serverCliPath: TYPESCRIPT_LANGUAGE_SERVER_CLI,
});
lspExtension(pi); lspExtension(pi);
} }
+18 -2
View File
@@ -20,6 +20,16 @@ export function buildToolRoutingSection(selectedTools: SelectedTools): string {
); );
} }
if (
hasTool(selectedTools, "lsp_definition") ||
hasTool(selectedTools, "lsp_references") ||
hasTool(selectedTools, "lsp_diagnostics")
) {
rules.push(
"- Use LSP for symbol definitions, references, hover/type information, and post-edit diagnostics when the language server supports the file.",
);
}
if (hasTool(selectedTools, "find") || hasTool(selectedTools, "grep") || hasTool(selectedTools, "multi_grep")) { if (hasTool(selectedTools, "find") || hasTool(selectedTools, "grep") || hasTool(selectedTools, "multi_grep")) {
rules.push( rules.push(
"- For literal search, narrow in stages instead of requesting a repository-wide dump:", "- For literal search, narrow in stages instead of requesting a repository-wide dump:",
@@ -32,7 +42,7 @@ export function buildToolRoutingSection(selectedTools: SelectedTools): string {
if (hasTool(selectedTools, "ctx_execute") || hasTool(selectedTools, "ctx_execute_file")) { if (hasTool(selectedTools, "ctx_execute") || hasTool(selectedTools, "ctx_execute_file")) {
rules.push( rules.push(
"- Use Context Mode for logs, test/build output, generated data, large-file analysis, and any command whose output may be large or unpredictable. Print only the derived answer needed for the task.", "- Use Context Mode for logs, test/build output, generated data, large-file exploration/analysis/summarization, and any command whose output may be large or unpredictable. Prefer ctx_execute_file over read when deriving an answer from a large file inside the workspace, and print only the derived answer needed for the task.",
); );
} }
@@ -44,7 +54,13 @@ export function buildToolRoutingSection(selectedTools: SelectedTools): string {
if (hasTool(selectedTools, "read")) { if (hasTool(selectedTools, "read")) {
rules.push( rules.push(
"- Use read directly when exact text is needed for editing or when a file is small. Do not read a complete large file merely to copy, compare, hash, count, or summarize it.", "- Use read only when exact source is needed or the file is small. Before editing, request the smallest useful offset/limit range; do not read a complete large file merely to copy, compare, hash, count, explore, or summarize it.",
);
}
if (hasTool(selectedTools, "read") && hasTool(selectedTools, "edit")) {
rules.push(
"- For existing-file changes, obtain fresh LINE#HASH anchors with a small read, then use anchored edit operations. Reuse fresh anchors returned by successful edits for chained changes; re-read only when anchors are stale or the next target region was not shown.",
); );
} }
+166
View File
@@ -14,9 +14,12 @@
"@ogulcancelik/pi-codex-compaction": "0.1.3", "@ogulcancelik/pi-codex-compaction": "0.1.3",
"context-mode": "1.0.169", "context-mode": "1.0.169",
"pi-context-view": "0.4.2", "pi-context-view": "0.4.2",
"pi-hashline-edit": "0.8.3",
"pi-hermes-memory": "0.9.6", "pi-hermes-memory": "0.9.6",
"pi-lsp": "0.1.7", "pi-lsp": "0.1.7",
"pi-mcp-adapter": "2.26.0", "pi-mcp-adapter": "2.26.0",
"typescript": "6.0.3",
"typescript-language-server": "5.3.0",
"zod": "4.4.3" "zod": "4.4.3"
}, },
"engines": { "engines": {
@@ -43,6 +46,16 @@
} }
} }
}, },
"node_modules/@borewit/text-codec": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz",
"integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@clack/core": { "node_modules/@clack/core": {
"version": "1.4.3", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/@clack/core/-/core-1.4.3.tgz", "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.4.3.tgz",
@@ -677,6 +690,29 @@
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@tokenizer/inflate": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz",
"integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.3",
"token-types": "^6.1.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/@tokenizer/token": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
"license": "MIT"
},
"node_modules/@yuuang/ffi-rs-android-arm64": { "node_modules/@yuuang/ffi-rs-android-arm64": {
"version": "1.3.7", "version": "1.3.7",
"resolved": "https://registry.npmjs.org/@yuuang/ffi-rs-android-arm64/-/ffi-rs-android-arm64-1.3.7.tgz", "resolved": "https://registry.npmjs.org/@yuuang/ffi-rs-android-arm64/-/ffi-rs-android-arm64-1.3.7.tgz",
@@ -1189,6 +1225,12 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/cuint": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
"integrity": "sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==",
"license": "MIT"
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -1288,6 +1330,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/diff": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz",
"integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/dunder-proto": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -1528,6 +1579,24 @@
"@yuuang/ffi-rs-win32-x64-msvc": "1.3.7" "@yuuang/ffi-rs-win32-x64-msvc": "1.3.7"
} }
}, },
"node_modules/file-type": {
"version": "21.3.4",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz",
"integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==",
"license": "MIT",
"dependencies": {
"@tokenizer/inflate": "^0.4.1",
"strtok3": "^10.3.4",
"token-types": "^6.1.1",
"uint8array-extras": "^1.4.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
}
},
"node_modules/file-uri-to-path": { "node_modules/file-uri-to-path": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -2107,6 +2176,23 @@
"@earendil-works/pi-tui": "*" "@earendil-works/pi-tui": "*"
} }
}, },
"node_modules/pi-hashline-edit": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/pi-hashline-edit/-/pi-hashline-edit-0.8.3.tgz",
"integrity": "sha512-eQWvwvR8aS7e/8CLTt8sdGhFXJguPTyFK+m9IB2muQQHsPnyMlF/cXKzEo5mQ898LxAM1jl7UVhPsmNXR8y/Mw==",
"license": "MIT",
"dependencies": {
"diff": "^8.0.2",
"file-type": "^21.3.0",
"xxhashjs": "^0.2.2"
},
"peerDependencies": {
"@earendil-works/pi-ai": ">=0.74.0",
"@earendil-works/pi-coding-agent": ">=0.74.0",
"@earendil-works/pi-tui": "*",
"@sinclair/typebox": "*"
}
},
"node_modules/pi-hermes-memory": { "node_modules/pi-hermes-memory": {
"version": "0.9.6", "version": "0.9.6",
"resolved": "https://registry.npmjs.org/pi-hermes-memory/-/pi-hermes-memory-0.9.6.tgz", "resolved": "https://registry.npmjs.org/pi-hermes-memory/-/pi-hermes-memory-0.9.6.tgz",
@@ -2735,6 +2821,22 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/strtok3": {
"version": "10.3.5",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz",
"integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==",
"license": "MIT",
"dependencies": {
"@tokenizer/token": "^0.3.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/synckit": { "node_modules/synckit": {
"version": "0.9.2", "version": "0.9.2",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
@@ -2788,6 +2890,24 @@
"node": ">=0.6" "node": ">=0.6"
} }
}, },
"node_modules/token-types": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz",
"integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==",
"license": "MIT",
"dependencies": {
"@borewit/text-codec": "^0.2.1",
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/tree-sitter-bash": { "node_modules/tree-sitter-bash": {
"version": "0.25.1", "version": "0.25.1",
"resolved": "https://registry.npmjs.org/tree-sitter-bash/-/tree-sitter-bash-0.25.1.tgz", "resolved": "https://registry.npmjs.org/tree-sitter-bash/-/tree-sitter-bash-0.25.1.tgz",
@@ -2875,6 +2995,43 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/typescript": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/typescript-language-server": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/typescript-language-server/-/typescript-language-server-5.3.0.tgz",
"integrity": "sha512-5puofxZHgFdAYtfNpmwCAvgtaYgg8wrUnH30m7Ze3QuguId5RNRadKASpOpyDxTyUdAF51FjhTdjntLw/EuWcQ==",
"license": "Apache-2.0",
"bin": {
"typescript-language-server": "lib/cli.mjs"
},
"engines": {
"node": ">=20"
}
},
"node_modules/uint8array-extras": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz",
"integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/unpipe": { "node_modules/unpipe": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -2975,6 +3132,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/xxhashjs": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz",
"integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==",
"license": "MIT",
"dependencies": {
"cuint": "^0.2.2"
}
},
"node_modules/zod": { "node_modules/zod": {
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
+4
View File
@@ -24,6 +24,7 @@
"extensions": [ "extensions": [
"./extensions/fff-override.ts", "./extensions/fff-override.ts",
"./pi-rtk-optimizer/index.ts", "./pi-rtk-optimizer/index.ts",
"./extensions/hashline.ts",
"./node_modules/context-mode/build/adapters/pi/extension.js", "./node_modules/context-mode/build/adapters/pi/extension.js",
"./extensions/codegraph.ts", "./extensions/codegraph.ts",
"./extensions/lsp.ts", "./extensions/lsp.ts",
@@ -46,9 +47,12 @@
"@ogulcancelik/pi-codex-compaction": "0.1.3", "@ogulcancelik/pi-codex-compaction": "0.1.3",
"context-mode": "1.0.169", "context-mode": "1.0.169",
"pi-context-view": "0.4.2", "pi-context-view": "0.4.2",
"pi-hashline-edit": "0.8.3",
"pi-hermes-memory": "0.9.6", "pi-hermes-memory": "0.9.6",
"pi-lsp": "0.1.7", "pi-lsp": "0.1.7",
"pi-mcp-adapter": "2.26.0", "pi-mcp-adapter": "2.26.0",
"typescript": "6.0.3",
"typescript-language-server": "5.3.0",
"zod": "4.4.3" "zod": "4.4.3"
}, },
"peerDependencies": { "peerDependencies": {
+30
View File
@@ -0,0 +1,30 @@
import assert from "node:assert/strict";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { deployBundleHashlineConfig } from "../extensions/hashline-config.ts";
const EXPECTED_CONFIG = {
hashLength: 2,
grep: false,
replaceText: false,
};
test("hashline config deployment enforces bundle defaults", async () => {
const agentDir = await mkdtemp(join(tmpdir(), "my-pi-hashline-"));
const targetPath = join(agentDir, "hashline.json");
try {
await writeFile(targetPath, '{"grep":true}\n', "utf8");
deployBundleHashlineConfig(agentDir);
assert.deepEqual(JSON.parse(await readFile(targetPath, "utf8")), EXPECTED_CONFIG);
const first = await readFile(targetPath, "utf8");
deployBundleHashlineConfig(agentDir);
assert.equal(await readFile(targetPath, "utf8"), first);
} finally {
await rm(agentDir, { recursive: true, force: true });
}
});
+54
View File
@@ -0,0 +1,54 @@
import assert from "node:assert/strict";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { deployBundleLspConfig, materializeBundleLspConfig } from "../extensions/lsp-config.ts";
const RUNTIME = {
nodeBinary: "/runtime/node",
serverCliPath: "/bundle/typescript-language-server/lib/cli.mjs",
};
test("LSP config materializes the bundled TypeScript server command", () => {
const source = JSON.stringify({
version: 1,
servers: [
{ id: "typescript-language-server", bin: "typescript-language-server", args: ["--stdio"] },
{ id: "jdtls", bin: "jdtls", args: [] },
],
});
const config = JSON.parse(materializeBundleLspConfig(source, RUNTIME));
assert.deepEqual(config.servers[0], {
id: "typescript-language-server",
bin: RUNTIME.nodeBinary,
args: [RUNTIME.serverCliPath, "--stdio"],
});
assert.deepEqual(config.servers[1], { id: "jdtls", bin: "jdtls", args: [] });
});
test("LSP config deployment writes the materialized bundle config", async () => {
const agentDir = await mkdtemp(join(tmpdir(), "my-pi-lsp-"));
try {
deployBundleLspConfig(agentDir, RUNTIME);
const config = JSON.parse(await readFile(join(agentDir, "lsp.json"), "utf8"));
const server = config.servers.find((entry: { id?: string }) => entry.id === "typescript-language-server");
assert.equal(server.bin, RUNTIME.nodeBinary);
assert.deepEqual(server.args, [RUNTIME.serverCliPath, "--stdio"]);
assert.deepEqual(server.include, [
"**/*.ts",
"**/*.tsx",
"**/*.mts",
"**/*.cts",
"**/*.js",
"**/*.jsx",
"**/*.mjs",
"**/*.cjs",
]);
} finally {
await rm(agentDir, { recursive: true, force: true });
}
});
+20 -3
View File
@@ -21,13 +21,29 @@ test("search routing narrows files before requesting matching line numbers", ()
assert.match(section, /Use read with offset\/limit only for the exact matching region/); assert.match(section, /Use read with offset\/limit only for the exact matching region/);
}); });
test("ctx_execute_file routing warns about its project-root boundary", () => { test("context routing analyzes large files without a full read", () => {
const section = buildToolRoutingSection(["ctx_execute_file"]); const section = buildToolRoutingSection(["ctx_execute", "ctx_execute_file"]);
assert.match(section, /Prefer ctx_execute_file over read when deriving an answer from a large file/);
assert.match(section, /ctx_execute_file is confined to the current project root/); assert.match(section, /ctx_execute_file is confined to the current project root/);
assert.match(section, /host permission approval does not bypass this Context Mode boundary/); assert.match(section, /host permission approval does not bypass this Context Mode boundary/);
}); });
test("hashline routing uses small reads and fresh anchors for edits", () => {
const section = buildToolRoutingSection(["read", "edit"]);
assert.match(section, /smallest useful offset\/limit range/);
assert.match(section, /obtain fresh LINE#HASH anchors with a small read/);
assert.match(section, /Reuse fresh anchors returned by successful edits/);
});
test("LSP routing covers navigation and post-edit diagnostics", () => {
const section = buildToolRoutingSection(["lsp_definition", "lsp_references", "lsp_diagnostics"]);
assert.match(section, /Use LSP for symbol definitions, references/);
assert.match(section, /post-edit diagnostics/);
});
test("routing includes only guidance for active optional tools", () => { test("routing includes only guidance for active optional tools", () => {
const section = buildToolRoutingSection(["read"]); const section = buildToolRoutingSection(["read"]);
@@ -35,7 +51,8 @@ test("routing includes only guidance for active optional tools", () => {
assert.doesNotMatch(section, /For literal search/); assert.doesNotMatch(section, /For literal search/);
assert.doesNotMatch(section, /Use Context Mode/); assert.doesNotMatch(section, /Use Context Mode/);
assert.doesNotMatch(section, /ctx_execute_file is confined/); assert.doesNotMatch(section, /ctx_execute_file is confined/);
assert.match(section, /Use read directly/); assert.doesNotMatch(section, /fresh LINE#HASH anchors/);
assert.match(section, /Use read only when exact source is needed/);
}); });
test("routing is appended once", () => { test("routing is appended once", () => {