commits
- Optimized `Date.parse()` out of multiple sort loops. ISO 8601 strings
sort lexicographically exactly the same as they do chronologically, making
direct string comparison mathematically safe and significantly faster.
- Converted `uniqueNonEmpty` and other mapped array `.map().filter()`
calls to single loops adding directly to `Set`s, avoiding multiple
intermediate array allocations.
- Replaced the O(N log N) `dateRange` computation (which previously used
`.sort()`) with a single O(N) pass to find min/max dates.
Co-authored-by: catoncat <204556023+catoncat@users.noreply.github.com>
Replaced the double array clone and reverse search pattern `[...messages].reverse().find(...)` in `buildSessionSummary` with a single reverse loop. This avoids multiple O(N) array memory allocations and traverses the array backwards with early returns, achieving the same result faster and more efficiently.
Co-authored-by: catoncat <204556023+catoncat@users.noreply.github.com>
- 明确退出 Windows 支持,binary 4 平台(macOS + Linux)
- 默认数据目录从 ~/.cache/cxs 迁到 ~/.local/state/cxs,首次跑自动 mv
- npm publish 通过 GH Actions NPM_TOKEN secret 自动触发
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: caf0df6cba91
之前用 ~/.cache/cxs/index.sqlite 不准确 — 索引是「可重建但重建有
成本」的状态(2867 session 全 sync 75s),不是 throwaway cache。XDG
state 是这种数据的标准位置:
- $XDG_STATE_HOME/cxs (尊重)
- ~/.local/state/cxs (fallback)
CXS_DATA_DIR 仍优先级最高。
迁移逻辑 (migrateLegacyCacheDir):
- legacy 不存在 → no-op
- dest 已有数据 → no-op (不 clobber,旧 cache 留原地)
- legacy === dest → no-op
- 其余 → renameSync(rename atomic);失败 swallow,重 sync 即可恢复
trigger 点 = cli.ts 入口,而非 env.ts 模块顶层 — 避免 vitest import
env.ts 时把用户 home 数据搬走 (test isolation)。
实测当前机器:240 MB 索引从 ~/.cache/cxs/ rename 到
~/.local/state/cxs/,lastSyncAt 与 sessionCount 保留,零丢失。
测试: env.test.ts 4 个 case 覆盖 mv / clean install / dest 已有 /
self === self 四种迁移分支。
Windows 注:这个改动跟前一个 platform reduction commit 一起,正式
把 cxs 限定到 macOS+Linux,Windows 用户走 WSL。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 8f6309354104
之前 release matrix 出 cxs-windows-x64.exe,但实际从未在原生 Windows
验证过完整流程 (install.sh 是 bash only;~/.codex/* hardcode 假设;
ci.yml 只跑 ubuntu-latest)。坦白做减法,不冒充支持。
- release.yml 删 windows-x64 build (4 平台 binary)
- install.sh 删 msys/cygwin/mingw case,unsupported OS 直接退
- package.json 加 os: ["darwin", "linux"] —— Windows 用户跑
npm i -g @act0r/cxs 会得到 EBADPLATFORM,不是更深的错误
- README CLI Install Guide 头部明示 "macOS / Linux only,Windows 走 WSL"
历史 release v0.1.0 / v0.2.0 上的 cxs-windows-x64.exe 不删 (作为
best-effort artifact 留着,Windows 用户自取自担风险)。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 68a4b5fdbe46
加 publish-npm job,跟 binary build/release 解耦并行。条件同前
(tag push v* 或 workflow_dispatch),用 NPM_TOKEN repo secret 授权。
publish 经由 prepublishOnly hook 自动跑 npm run build (esbuild bundle),
不复用 binary build 的产物。
setup-node 必须在 setup-bun 之前 — 让 node binary 与 .npmrc auth
token 在 PATH 优先,避免 bun 自带 node shim 抢走 npm publish。
前置:repo Settings → Secrets and variables → Actions 加 NPM_TOKEN,
值是 granular access token,scope 限 @act0r/cxs (或 @act0r/*),
permission read+write,且勾选 "Allow publishing without 2FA"。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 724c115e6d42
之前临时选 @catoncat 是按 GitHub username 推断;实际发布用 npm org
@act0r。GitHub repo 仍在 catoncat/cxs,跟 npm org 名解耦。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: f9024017389c
- 实测 npm publish 发现 cxs 包名已被 css-in-js 库占用(发到 6.2.0),
改成 scoped @catoncat/cxs(npm 标准做法,GitHub username 作 scope)。
全局命令名仍是 cxs(由 bin map key 决定,跟包名解耦)
- bin 值去掉 "./" 前缀: { "cxs": "dist/cli.js" } —— 此前 npm publish
warning "bin[cxs] script name dist/cli.js was invalid and removed"
- 加 publishConfig.access=public —— scoped 包默认 private,显式声明 public
避免每次 npm publish 都要带 --access public flag
- README CLI Install Guide 同步改 npm i -g @catoncat/cxs,加一句解释
scoped 原因
GH Release v0.2.0 binary 不受影响(binary 路径独立于 npm 包名)。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 12b7e8c0e860
包含本轮全部 fix 与双发布:
- P0 修复:DB 资源泄漏 / sync lock TOCTOU mitigation / current schema
防御(threads 表 + 必需列检查 + structured CLI error)
- 性能 / 可调性:ranking 权重一页说明文档、mixed match displayRow 测试
锁定、真实大库 perf-bench 脚本
- skill 文档:补 current 工作流、减薄 SKILL.md、加 state_db_unavailable
与 --json error shape 速查
- 架构修复:DATA_DIR 默认 ~/.cache/cxs(允许 CXS_DATA_DIR 覆盖,修了
npm 全局 / standalone binary 写仓库根的 P0)、cli.ts version 用
build-time JSON import 修了 standalone binary ENOENT
- Track A 上线:GH Actions matrix 5 平台 standalone binary +
scripts/install.sh 一行 curl 装 + Quick Install README
- Track B 上线:Node 化(better-sqlite3 + vitest + esbuild bundle),
npm i -g cxs ~13 MB 零 Bun 依赖
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 7f27a1cf3a54
让 cxs 不依赖 Bun runtime 也能跑,完成"双发布"双轨的另一条腿。
用户 npm i -g cxs 装 ~13 MB(better-sqlite3 prebuild + chalk + commander),
对比 standalone binary 60-117 MB 体积优势明显。Bun 仍是主开发 runtime
(bun:sqlite 路径已弃用,因为 Bun 不支持 better-sqlite3 N-API,见
oven-sh/bun#4290)。
DB 层:bun:sqlite → better-sqlite3
- db.ts / query.ts:Database 改 better-sqlite3 default import,
Database.setCustomSQLite 删除(不需要 homebrew dylib 优化),
db.query<RowType, ParamType> 改 db.prepare<ParamType, RowType>(类型参
数顺序倒置),db.run 带参数版本改 db.prepare(sql).run(...args),
db.run 不带参的 DDL 改 db.exec
- 测试文件同步改 import + db.exec/db.prepare
- query.test.ts 子进程内嵌 eval script 也从 bun:sqlite 改 better-sqlite3
测试框架:bun:test → vitest
- 7 个 .test.ts import 切到 vitest(API 完全兼容 BDD 风格)
- @types/bun + bun-types 删除
- import.meta.dir → import.meta.dirname(Node 22+ 内置标准 API)
子进程 spawn:Bun.spawn → child_process.spawn
- cli.test.ts runCli + runExecutable 重写为 promise 风格
- cli.test.ts 子进程跑 cli.ts 改用 node --import tsx 路径
- query.test.ts runReadChild 内嵌 script 也加 --import tsx
- eval/perf-bench.ts + eval/run-manual-eval.ts 同样
- eval 内 ./bin/cxs 改成 process.execPath + tsx + cli.ts
- 删除 cli.test.ts "bin/cxs symlink" 测试(bin/cxs 不存在了)
Build pipeline:
- bin/cxs bash shim 删除;package.json bin 字段直指 dist/cli.js
- esbuild 把 cli.ts bundle 成 dist/cli.js 单文件 57 KB
(better-sqlite3 / chalk / commander external 走 npm 解析)
- scripts/post-build.mjs 给 dist/cli.js 加 #!/usr/bin/env node shebang
+ chmod 0755
- npm run build 跑完整 pipeline,prepublishOnly 自动触发
- package.json files 字段改 ["dist/", "README.md", "LICENSE"];
engines 从 bun>=1.3 改 node>=22(JSON import + with type 需要)
- tsconfig.json 加 resolveJsonModule + dist/ 到 exclude
Dev runtime:
- "cxs" / "eval:manual" / "eval:perf" 全改 tsx 跑(放弃 bun run cli.ts)
- README "CLI Install Guide" 重组为三档:standalone binary / npm i -g
/ 源码开发,各档体积与依赖说明清晰
实测全过:
- bun run check:tsc + 42 vitest tests 全绿
- npm run build:dist/cli.js 57 KB single file
- node dist/cli.js 全套 (--version / current bad / sync empty / stats /
find) 完整跑通
- npm pack 19.4 KB,4 文件 tarball;npm i -g 装上 12.8 MB,跑通
- bun build --compile darwin-arm64 仍 work(无回归)
- npm run eval:perf 子进程链路通
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 160b6a90ffb6
提供给最终用户的零依赖安装路径:
- .github/workflows/release.yml: tag push v* 或手动 workflow_dispatch
触发,matrix 5 平台 (darwin-arm64/x64 / linux-x64/arm64 / windows-x64),
全在 ubuntu-latest 上用 bun build --compile 跨平台编译,upload 到
release 资产
- scripts/install.sh: 一行 curl ... | bash,自动 detect uname,从
release 拉对应 binary 到 ~/.local/bin/cxs (CXS_INSTALL_DIR 覆盖),
chmod +x 并跑一次 --version 自检;如果 install dir 不在 PATH 主动
提示
- README "CLI Install Guide" 重写:把 binary 一行装放第一位,源码
模式作为 dev/PR 路径放第二;补 CXS_DATA_DIR 数据目录覆盖说明
binary 模式无 Bun/Node 运行时依赖。本地实测 bun build --compile
--target=bun-darwin-arm64 出 58 MB binary,--version / current
schema-error / sync empty / stats 全部跑通。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: b17128559666
之前 cli.ts 的 readPackageVersion() 用 readFileSync(new URL("./package.json",
import.meta.url)),源码 / npm 装都能 work,但 bun build --compile 出来
的 standalone binary 在虚拟 /$bunfs/root/ 下找不到 package.json
(JSON 没被 bundle),启动直接 ENOENT。
改成 import packageJson from "./package.json" with { type: "json" } —
bundler 把 JSON 内联进 binary,运行时不再读 fs。源码模式 / npm 模式 /
binary 模式三者都 OK。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 0a7fade52c1f
之前 DATA_DIR = resolve(import.meta.dir, "data") 是 *相对仓库根* 的路径,
对源码 checkout 模式 OK,但对 npm i -g 与 bun build --compile 都炸:
- npm i -g 后 import.meta.dir 指向全局 node_modules/cxs,默认 db 写到
那里,要么权限不够要么污染全局安装目录
- bun build --compile 出来的 binary fs 是只读虚拟 /$bunfs/,
ensureDataDir() 直接 EROFS
改成默认 ~/.cache/cxs (XDG cache 约定),并支持 CXS_DATA_DIR 环境变量
覆盖。源码 dev 模式行为也跟着变 — 现有 ./data/index.sqlite 不再被
默认读到,需要重 sync 一次到新位置 (~75s,幂等)。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 8f465c5aec2f
cli.ts 直接 import { SyncLockTimeoutError } from "./sync-lock",但
package.json 的 files 字段一直漏了 sync-lock.ts,导致 npm 装的全局
binary 一启动就报 "Cannot find module './sync-lock'",任何子命令都
无法运行。
实测路径: npm pack && npm i -g --prefix /tmp/test ./cxs-0.1.0.tgz
后跑 cxs --version 直接 exit 1。补 sync-lock.ts 后重测:
- --version / --help OK
- current --state-db <bad> --json 输出结构化 state_db_unavailable
- sync 空目录返回 SyncSummary 全 0
- stats / find 在空索引上正常返回
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 7ed6acd590f0
review 后的最佳实践 follow-up:
- 加 cxs current 子命令工作流 (cli-surface.md / progressive-workflow.md
Scenario 4 / SKILL.md "什么时候用 cxs" 表)。current 直读 Codex state DB,
零索引依赖,适合 sync 没跑过或刚换机器的场景,之前 SKILL.md 完全没教
- failure-cookbook 加 ## state_db_unavailable 段 (列 missing-file /
missing-table / missing-column 三类 message 与处置),并加一张 --json
error shape 速查表 (sync SyncSummary / sync lock-timeout string /
current structured payload 三种形状)
- SKILL.md 减薄至 76 行 (原 166):删与 references 重复的"三步渐进式
检索"命令 / "JSON 模式速查"字段 / "常用排障"完整表;主体只留触发
场景 + 心法 + 链 references
- frontmatter description 合并本机已装版的英文触发词
(last time I / earlier session / previous codex chat / ...) 避免重
install 后丢失英文召回
- install 段重排成"两步":1) CLI 二进制(指 README CLI Install Guide)
2) skill (npx skills add ...);并说明 -a codex 是 Codex agent
runtime,Claude Code/Anthropic 用户应换 -a 取值
skill-sync 时间戳 bump 到 2026-04-27。
同步 ~/.claude/skills/cxs/ 已与 repo 一致 (rsync + diff 确认)。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 0e41f6684198
- 所有 .ts 链接从绝对路径 /Users/envvar/work/repos/cxs/... 改为
相对路径 ../...,在 GitHub 与他人 clone 下都可点。
- 修正不存在的 cxs CLI 选项 "read-range <uuid> --start 0":read-range
实际只支持 --seq/--query 锚定,matchSeq=null 时直接断了重锚链路 ——
按当前 CLI 真实语义重写说明。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 059017285930
之前的 commit 187c5d9 在 tryRemoveStaleLock 加了二次比对,但这并不是
原子 TOCTOU 修复:在二次读取与 path-based rmSync 之间仍有残余 race
窗口,另一个进程可能在该窗口内删并替换 lock,导致当前进程仍可能
rmSync 别人的新 lock。要做真正原子需 OS-level flock 或 inode-pinned
unlink,代价不划算 — 接受 best-effort 表述。
- sync-lock.ts: 改写 tryRemoveStaleLock 注释,明确说明这是 mitigation
而非原子修复,以及为什么对 cxs 低并发场景可接受
- docs/CODE_QUALITY_REVIEW_2026-04-27.md: P0-3 标记为 ⚠️ best-effort
mitigation 并解释工程决策,避免后人以为已"彻底修复"
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 6ed014a6a4bc
之前仅检查 threads 表存在,如果表存在但列(rollout_path / updated_at_ms 等)
被上游改名,SELECT 会冒泡 SQLiteError 堆栈而不是走 CurrentStateDbError +
CLI 结构化错误分支。
补 PRAGMA table_info(threads) 列检查:用 THREADS_REQUIRED_COLUMNS 常量
列出 SELECT 引用的所有列(id, rollout_path, cwd, title, updated_at_ms),
缺任意一列即抛 CurrentStateDbError 指明缺哪些列。
测试: query.test.ts +1, cli.test.ts +1, 后者额外断言 stdout 不含
"SQLiteError" 锁定结构化 payload 契约。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: b9987d117979
eval/perf-bench.ts 复用本机 ~/.codex/sessions 真实数据,跑 sync 全量 +
7 个代表性 query (单 token / 多 token / CJK / 中英混合) 各 5 次取后 4
样本 p50/p95,产出 data/cxs-perf/<ts>/report.{json,md}。stdout 打 JSON
summary 对齐 eval/run-manual-eval 风格。
实跑参考: 2867 session 全量 sync ~75s,db 240MB,单 token query p95
~86-90ms,双 token "edge tts" p95 190ms (FTS AND-match 成本)。
仅 advisory,不做 CI gate;P2 阶段如果上 reranker 可用作回归对照。
对应 review 报告 P1-3。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 5506719bb0ff
加 3 条测试覆盖 ranking.shouldUseDisplayRow 的展示行选择规则:
- mixed: 同 session 同时含 strong session hit 与 weak message hit 时,
displayRow 选 message (matchSeq 是 number,read-range 可重锚),
但 sessionScore 仍由 titlePhrase/titleTermHits 顶上去压过 message-only
对照 session
- session-only: 仅 session-level 命中 → matchSource = "session", matchSeq = null
- message-only: 仅 message body 命中 → matchSource = "message", matchSeq 为
number,作为对照基线
锁定"session signal 提排名 + message signal 给定位"双轨契约。对应
review 报告 P1-2。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: de3ec2613fde
为 ranking.ts 中的 magic constants 写一页说明,五层组织:SQL 列权重 /
row signal / session signal / recency / display rule。每个常量都给出
值 + 为什么是这个量级 + 改动它会影响什么三段;末尾附调权 checklist
拦住盲调。对应 review 报告 P1-1。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: c25efef3693f
- query: 引入 withReadDb 统一收口读连接生命周期,findSessions /
getMessageRange / getMessagePage / listSessionSummaries / collectStats
在异常路径下不再泄漏连接 (P0-2)
- sync-lock: 抽出 tryRemoveStaleLock,删除 stale lock 前二次比对
pid+createdAt,避免 TOCTOU 误删另一进程刚抢到的新 lock (P0-3)
- current: codex state db 缺 threads 表时抛 CurrentStateDbError,CLI
在 --json 下输出 {error:{code,message}} 并以非零退出 (P0-4)
测试: query.test.ts +1, cli.test.ts +2, 新建 sync-lock.test.ts (4 cases),
bun run check 通过 (37 pass)。
文档: 标记 P0-1 已在 be75d87 修复。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: dd6f619c0078
- Optimized `Date.parse()` out of multiple sort loops. ISO 8601 strings
sort lexicographically exactly the same as they do chronologically, making
direct string comparison mathematically safe and significantly faster.
- Converted `uniqueNonEmpty` and other mapped array `.map().filter()`
calls to single loops adding directly to `Set`s, avoiding multiple
intermediate array allocations.
- Replaced the O(N log N) `dateRange` computation (which previously used
`.sort()`) with a single O(N) pass to find min/max dates.
Co-authored-by: catoncat <204556023+catoncat@users.noreply.github.com>
Replaced the double array clone and reverse search pattern `[...messages].reverse().find(...)` in `buildSessionSummary` with a single reverse loop. This avoids multiple O(N) array memory allocations and traverses the array backwards with early returns, achieving the same result faster and more efficiently.
Co-authored-by: catoncat <204556023+catoncat@users.noreply.github.com>
- 明确退出 Windows 支持,binary 4 平台(macOS + Linux)
- 默认数据目录从 ~/.cache/cxs 迁到 ~/.local/state/cxs,首次跑自动 mv
- npm publish 通过 GH Actions NPM_TOKEN secret 自动触发
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: caf0df6cba91
之前用 ~/.cache/cxs/index.sqlite 不准确 — 索引是「可重建但重建有
成本」的状态(2867 session 全 sync 75s),不是 throwaway cache。XDG
state 是这种数据的标准位置:
- $XDG_STATE_HOME/cxs (尊重)
- ~/.local/state/cxs (fallback)
CXS_DATA_DIR 仍优先级最高。
迁移逻辑 (migrateLegacyCacheDir):
- legacy 不存在 → no-op
- dest 已有数据 → no-op (不 clobber,旧 cache 留原地)
- legacy === dest → no-op
- 其余 → renameSync(rename atomic);失败 swallow,重 sync 即可恢复
trigger 点 = cli.ts 入口,而非 env.ts 模块顶层 — 避免 vitest import
env.ts 时把用户 home 数据搬走 (test isolation)。
实测当前机器:240 MB 索引从 ~/.cache/cxs/ rename 到
~/.local/state/cxs/,lastSyncAt 与 sessionCount 保留,零丢失。
测试: env.test.ts 4 个 case 覆盖 mv / clean install / dest 已有 /
self === self 四种迁移分支。
Windows 注:这个改动跟前一个 platform reduction commit 一起,正式
把 cxs 限定到 macOS+Linux,Windows 用户走 WSL。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 8f6309354104
之前 release matrix 出 cxs-windows-x64.exe,但实际从未在原生 Windows
验证过完整流程 (install.sh 是 bash only;~/.codex/* hardcode 假设;
ci.yml 只跑 ubuntu-latest)。坦白做减法,不冒充支持。
- release.yml 删 windows-x64 build (4 平台 binary)
- install.sh 删 msys/cygwin/mingw case,unsupported OS 直接退
- package.json 加 os: ["darwin", "linux"] —— Windows 用户跑
npm i -g @act0r/cxs 会得到 EBADPLATFORM,不是更深的错误
- README CLI Install Guide 头部明示 "macOS / Linux only,Windows 走 WSL"
历史 release v0.1.0 / v0.2.0 上的 cxs-windows-x64.exe 不删 (作为
best-effort artifact 留着,Windows 用户自取自担风险)。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 68a4b5fdbe46
加 publish-npm job,跟 binary build/release 解耦并行。条件同前
(tag push v* 或 workflow_dispatch),用 NPM_TOKEN repo secret 授权。
publish 经由 prepublishOnly hook 自动跑 npm run build (esbuild bundle),
不复用 binary build 的产物。
setup-node 必须在 setup-bun 之前 — 让 node binary 与 .npmrc auth
token 在 PATH 优先,避免 bun 自带 node shim 抢走 npm publish。
前置:repo Settings → Secrets and variables → Actions 加 NPM_TOKEN,
值是 granular access token,scope 限 @act0r/cxs (或 @act0r/*),
permission read+write,且勾选 "Allow publishing without 2FA"。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 724c115e6d42
- 实测 npm publish 发现 cxs 包名已被 css-in-js 库占用(发到 6.2.0),
改成 scoped @catoncat/cxs(npm 标准做法,GitHub username 作 scope)。
全局命令名仍是 cxs(由 bin map key 决定,跟包名解耦)
- bin 值去掉 "./" 前缀: { "cxs": "dist/cli.js" } —— 此前 npm publish
warning "bin[cxs] script name dist/cli.js was invalid and removed"
- 加 publishConfig.access=public —— scoped 包默认 private,显式声明 public
避免每次 npm publish 都要带 --access public flag
- README CLI Install Guide 同步改 npm i -g @catoncat/cxs,加一句解释
scoped 原因
GH Release v0.2.0 binary 不受影响(binary 路径独立于 npm 包名)。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 12b7e8c0e860
包含本轮全部 fix 与双发布:
- P0 修复:DB 资源泄漏 / sync lock TOCTOU mitigation / current schema
防御(threads 表 + 必需列检查 + structured CLI error)
- 性能 / 可调性:ranking 权重一页说明文档、mixed match displayRow 测试
锁定、真实大库 perf-bench 脚本
- skill 文档:补 current 工作流、减薄 SKILL.md、加 state_db_unavailable
与 --json error shape 速查
- 架构修复:DATA_DIR 默认 ~/.cache/cxs(允许 CXS_DATA_DIR 覆盖,修了
npm 全局 / standalone binary 写仓库根的 P0)、cli.ts version 用
build-time JSON import 修了 standalone binary ENOENT
- Track A 上线:GH Actions matrix 5 平台 standalone binary +
scripts/install.sh 一行 curl 装 + Quick Install README
- Track B 上线:Node 化(better-sqlite3 + vitest + esbuild bundle),
npm i -g cxs ~13 MB 零 Bun 依赖
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 7f27a1cf3a54
让 cxs 不依赖 Bun runtime 也能跑,完成"双发布"双轨的另一条腿。
用户 npm i -g cxs 装 ~13 MB(better-sqlite3 prebuild + chalk + commander),
对比 standalone binary 60-117 MB 体积优势明显。Bun 仍是主开发 runtime
(bun:sqlite 路径已弃用,因为 Bun 不支持 better-sqlite3 N-API,见
oven-sh/bun#4290)。
DB 层:bun:sqlite → better-sqlite3
- db.ts / query.ts:Database 改 better-sqlite3 default import,
Database.setCustomSQLite 删除(不需要 homebrew dylib 优化),
db.query<RowType, ParamType> 改 db.prepare<ParamType, RowType>(类型参
数顺序倒置),db.run 带参数版本改 db.prepare(sql).run(...args),
db.run 不带参的 DDL 改 db.exec
- 测试文件同步改 import + db.exec/db.prepare
- query.test.ts 子进程内嵌 eval script 也从 bun:sqlite 改 better-sqlite3
测试框架:bun:test → vitest
- 7 个 .test.ts import 切到 vitest(API 完全兼容 BDD 风格)
- @types/bun + bun-types 删除
- import.meta.dir → import.meta.dirname(Node 22+ 内置标准 API)
子进程 spawn:Bun.spawn → child_process.spawn
- cli.test.ts runCli + runExecutable 重写为 promise 风格
- cli.test.ts 子进程跑 cli.ts 改用 node --import tsx 路径
- query.test.ts runReadChild 内嵌 script 也加 --import tsx
- eval/perf-bench.ts + eval/run-manual-eval.ts 同样
- eval 内 ./bin/cxs 改成 process.execPath + tsx + cli.ts
- 删除 cli.test.ts "bin/cxs symlink" 测试(bin/cxs 不存在了)
Build pipeline:
- bin/cxs bash shim 删除;package.json bin 字段直指 dist/cli.js
- esbuild 把 cli.ts bundle 成 dist/cli.js 单文件 57 KB
(better-sqlite3 / chalk / commander external 走 npm 解析)
- scripts/post-build.mjs 给 dist/cli.js 加 #!/usr/bin/env node shebang
+ chmod 0755
- npm run build 跑完整 pipeline,prepublishOnly 自动触发
- package.json files 字段改 ["dist/", "README.md", "LICENSE"];
engines 从 bun>=1.3 改 node>=22(JSON import + with type 需要)
- tsconfig.json 加 resolveJsonModule + dist/ 到 exclude
Dev runtime:
- "cxs" / "eval:manual" / "eval:perf" 全改 tsx 跑(放弃 bun run cli.ts)
- README "CLI Install Guide" 重组为三档:standalone binary / npm i -g
/ 源码开发,各档体积与依赖说明清晰
实测全过:
- bun run check:tsc + 42 vitest tests 全绿
- npm run build:dist/cli.js 57 KB single file
- node dist/cli.js 全套 (--version / current bad / sync empty / stats /
find) 完整跑通
- npm pack 19.4 KB,4 文件 tarball;npm i -g 装上 12.8 MB,跑通
- bun build --compile darwin-arm64 仍 work(无回归)
- npm run eval:perf 子进程链路通
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 160b6a90ffb6
提供给最终用户的零依赖安装路径:
- .github/workflows/release.yml: tag push v* 或手动 workflow_dispatch
触发,matrix 5 平台 (darwin-arm64/x64 / linux-x64/arm64 / windows-x64),
全在 ubuntu-latest 上用 bun build --compile 跨平台编译,upload 到
release 资产
- scripts/install.sh: 一行 curl ... | bash,自动 detect uname,从
release 拉对应 binary 到 ~/.local/bin/cxs (CXS_INSTALL_DIR 覆盖),
chmod +x 并跑一次 --version 自检;如果 install dir 不在 PATH 主动
提示
- README "CLI Install Guide" 重写:把 binary 一行装放第一位,源码
模式作为 dev/PR 路径放第二;补 CXS_DATA_DIR 数据目录覆盖说明
binary 模式无 Bun/Node 运行时依赖。本地实测 bun build --compile
--target=bun-darwin-arm64 出 58 MB binary,--version / current
schema-error / sync empty / stats 全部跑通。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: b17128559666
之前 cli.ts 的 readPackageVersion() 用 readFileSync(new URL("./package.json",
import.meta.url)),源码 / npm 装都能 work,但 bun build --compile 出来
的 standalone binary 在虚拟 /$bunfs/root/ 下找不到 package.json
(JSON 没被 bundle),启动直接 ENOENT。
改成 import packageJson from "./package.json" with { type: "json" } —
bundler 把 JSON 内联进 binary,运行时不再读 fs。源码模式 / npm 模式 /
binary 模式三者都 OK。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 0a7fade52c1f
之前 DATA_DIR = resolve(import.meta.dir, "data") 是 *相对仓库根* 的路径,
对源码 checkout 模式 OK,但对 npm i -g 与 bun build --compile 都炸:
- npm i -g 后 import.meta.dir 指向全局 node_modules/cxs,默认 db 写到
那里,要么权限不够要么污染全局安装目录
- bun build --compile 出来的 binary fs 是只读虚拟 /$bunfs/,
ensureDataDir() 直接 EROFS
改成默认 ~/.cache/cxs (XDG cache 约定),并支持 CXS_DATA_DIR 环境变量
覆盖。源码 dev 模式行为也跟着变 — 现有 ./data/index.sqlite 不再被
默认读到,需要重 sync 一次到新位置 (~75s,幂等)。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 8f465c5aec2f
cli.ts 直接 import { SyncLockTimeoutError } from "./sync-lock",但
package.json 的 files 字段一直漏了 sync-lock.ts,导致 npm 装的全局
binary 一启动就报 "Cannot find module './sync-lock'",任何子命令都
无法运行。
实测路径: npm pack && npm i -g --prefix /tmp/test ./cxs-0.1.0.tgz
后跑 cxs --version 直接 exit 1。补 sync-lock.ts 后重测:
- --version / --help OK
- current --state-db <bad> --json 输出结构化 state_db_unavailable
- sync 空目录返回 SyncSummary 全 0
- stats / find 在空索引上正常返回
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 7ed6acd590f0
review 后的最佳实践 follow-up:
- 加 cxs current 子命令工作流 (cli-surface.md / progressive-workflow.md
Scenario 4 / SKILL.md "什么时候用 cxs" 表)。current 直读 Codex state DB,
零索引依赖,适合 sync 没跑过或刚换机器的场景,之前 SKILL.md 完全没教
- failure-cookbook 加 ## state_db_unavailable 段 (列 missing-file /
missing-table / missing-column 三类 message 与处置),并加一张 --json
error shape 速查表 (sync SyncSummary / sync lock-timeout string /
current structured payload 三种形状)
- SKILL.md 减薄至 76 行 (原 166):删与 references 重复的"三步渐进式
检索"命令 / "JSON 模式速查"字段 / "常用排障"完整表;主体只留触发
场景 + 心法 + 链 references
- frontmatter description 合并本机已装版的英文触发词
(last time I / earlier session / previous codex chat / ...) 避免重
install 后丢失英文召回
- install 段重排成"两步":1) CLI 二进制(指 README CLI Install Guide)
2) skill (npx skills add ...);并说明 -a codex 是 Codex agent
runtime,Claude Code/Anthropic 用户应换 -a 取值
skill-sync 时间戳 bump 到 2026-04-27。
同步 ~/.claude/skills/cxs/ 已与 repo 一致 (rsync + diff 确认)。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 0e41f6684198
- 所有 .ts 链接从绝对路径 /Users/envvar/work/repos/cxs/... 改为
相对路径 ../...,在 GitHub 与他人 clone 下都可点。
- 修正不存在的 cxs CLI 选项 "read-range <uuid> --start 0":read-range
实际只支持 --seq/--query 锚定,matchSeq=null 时直接断了重锚链路 ——
按当前 CLI 真实语义重写说明。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 059017285930
之前的 commit 187c5d9 在 tryRemoveStaleLock 加了二次比对,但这并不是
原子 TOCTOU 修复:在二次读取与 path-based rmSync 之间仍有残余 race
窗口,另一个进程可能在该窗口内删并替换 lock,导致当前进程仍可能
rmSync 别人的新 lock。要做真正原子需 OS-level flock 或 inode-pinned
unlink,代价不划算 — 接受 best-effort 表述。
- sync-lock.ts: 改写 tryRemoveStaleLock 注释,明确说明这是 mitigation
而非原子修复,以及为什么对 cxs 低并发场景可接受
- docs/CODE_QUALITY_REVIEW_2026-04-27.md: P0-3 标记为 ⚠️ best-effort
mitigation 并解释工程决策,避免后人以为已"彻底修复"
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 6ed014a6a4bc
之前仅检查 threads 表存在,如果表存在但列(rollout_path / updated_at_ms 等)
被上游改名,SELECT 会冒泡 SQLiteError 堆栈而不是走 CurrentStateDbError +
CLI 结构化错误分支。
补 PRAGMA table_info(threads) 列检查:用 THREADS_REQUIRED_COLUMNS 常量
列出 SELECT 引用的所有列(id, rollout_path, cwd, title, updated_at_ms),
缺任意一列即抛 CurrentStateDbError 指明缺哪些列。
测试: query.test.ts +1, cli.test.ts +1, 后者额外断言 stdout 不含
"SQLiteError" 锁定结构化 payload 契约。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: b9987d117979
eval/perf-bench.ts 复用本机 ~/.codex/sessions 真实数据,跑 sync 全量 +
7 个代表性 query (单 token / 多 token / CJK / 中英混合) 各 5 次取后 4
样本 p50/p95,产出 data/cxs-perf/<ts>/report.{json,md}。stdout 打 JSON
summary 对齐 eval/run-manual-eval 风格。
实跑参考: 2867 session 全量 sync ~75s,db 240MB,单 token query p95
~86-90ms,双 token "edge tts" p95 190ms (FTS AND-match 成本)。
仅 advisory,不做 CI gate;P2 阶段如果上 reranker 可用作回归对照。
对应 review 报告 P1-3。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: 5506719bb0ff
加 3 条测试覆盖 ranking.shouldUseDisplayRow 的展示行选择规则:
- mixed: 同 session 同时含 strong session hit 与 weak message hit 时,
displayRow 选 message (matchSeq 是 number,read-range 可重锚),
但 sessionScore 仍由 titlePhrase/titleTermHits 顶上去压过 message-only
对照 session
- session-only: 仅 session-level 命中 → matchSource = "session", matchSeq = null
- message-only: 仅 message body 命中 → matchSource = "message", matchSeq 为
number,作为对照基线
锁定"session signal 提排名 + message signal 给定位"双轨契约。对应
review 报告 P1-2。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: de3ec2613fde
- query: 引入 withReadDb 统一收口读连接生命周期,findSessions /
getMessageRange / getMessagePage / listSessionSummaries / collectStats
在异常路径下不再泄漏连接 (P0-2)
- sync-lock: 抽出 tryRemoveStaleLock,删除 stale lock 前二次比对
pid+createdAt,避免 TOCTOU 误删另一进程刚抢到的新 lock (P0-3)
- current: codex state db 缺 threads 表时抛 CurrentStateDbError,CLI
在 --json 下输出 {error:{code,message}} 并以非零退出 (P0-4)
测试: query.test.ts +1, cli.test.ts +2, 新建 sync-lock.test.ts (4 cases),
bun run check 通过 (37 pass)。
文档: 标记 P0-1 已在 be75d87 修复。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Entire-Checkpoint: dd6f619c0078