cxs is a local-first CLI for searching Codex session logs. It is designed for progressive retrieval: find the right session first, then read
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: update cxs architecture and quality review

cat ca4091f2 72b777f9

+272 -43
+3 -1
.agents/skills/cxs/SKILL.md
··· 85 85 先看这些字段: 86 86 87 87 - `results[].sessionUuid` 88 + - `results[].matchSource` 88 89 - `results[].matchSeq` 89 90 - `results[].cwd` 90 91 - `results[].startedAt` ··· 113 114 工作流心法: 114 115 115 116 - 永远先 `find`,不要直接 `read-page` 117 + - `matchSource=session` 表示命中来自 title / summary / compact / reasoning summary 等 session-level 字段,且 `matchSeq=null`;这种结果先 `read-page`,不要伪造 `read-range --seq` 116 118 - 升级到 `read-page` 之前,先尝试放大 `--before/--after` 117 119 - 用户给出 `cwd` 或时间窗口时,优先 `list` 缩范围再 `read-range --query` 118 120 - `cwd` 只是候选过滤,不是主题真相;还要再看 `title`、`summaryText`、开头几条 message ··· 120 122 121 123 ## JSON 模式速查 122 124 123 - - `find --json`:顶层是 `{ query, results }` 125 + - `find --json`:顶层是 `{ query, results }`;重点看 `sessionUuid`、`matchSource`、`matchSeq`、`summaryText`、`snippet` 124 126 - `read-range --json`:重点看 `anchorSeq`、`rangeStartSeq`、`rangeEndSeq`、`messages[]` 125 127 - `read-page --json`:重点看 `offset`、`limit`、`totalCount`、`hasMore` 126 128 - `list --json`:重点看 `results[].cwd`、`startedAt`、`endedAt`、`messageCount`
+6 -3
.agents/skills/cxs/references/json-schema.md
··· 23 23 startedAt: string; 24 24 endedAt: string; 25 25 matchCount: number; 26 - matchSeq: number; 27 - matchRole: "user" | "assistant"; 28 - matchTimestamp: string; 26 + matchSource: "message" | "session"; 27 + matchSeq: number | null; 28 + matchRole: "user" | "assistant" | "session"; 29 + matchTimestamp: string | null; 29 30 score: number; 30 31 snippet: string; 31 32 } 32 33 ``` 34 + 35 + `matchSource = "session"` means the hit came from session-level fields such as title, derived summary, compact handoff, or reasoning summary rather than a concrete message. In that case `matchSeq` is `null`; use `read-page` first instead of fabricating a `read-range --seq` anchor. 33 36 34 37 ## read-range 35 38
+7 -7
AGENTS.md
··· 6 6 7 7 当前接受的产品边界: 8 8 9 - - 命令面固定为:`sync`、`find`、`read-range`、`read-page`、`list`、`stats` 9 + - 命令面固定为:`current`、`sync`、`find`、`read-range`、`read-page`、`list`、`stats` 10 10 - 主工作流固定为:`sync -> find -> read-range/read-page` 11 11 - `sync` 是唯一会修改索引的命令;其余命令只读 SQLite 12 12 - 默认接受手动增量同步,不做 watcher / daemon / realtime sync ··· 14 14 15 15 ## 当前实现真相 16 16 17 - - 检索主链是 `message recall -> session heuristic rerank -> progressive read` 18 - - 候选召回来自 `messages_fts`,极少数零 token CJK query 会回退到 LIKE 19 - - `summary_text` 已持久化,也会参与结果展示和 rerank,但还没有进入 recall 面 17 + - 检索主链是 `message/session recall -> session heuristic rerank -> progressive read` 18 + - 候选召回来自 `messages_fts` 与 `sessions_fts(title + summary_text + compact_text + reasoning_summary_text)`;极少数零 token CJK query 在 message 侧回退到 LIKE 19 + - `summary_text`、`compact_text`、`reasoning_summary_text` 已持久化,也会通过 `sessions_fts` 参与 session-level recall 20 + - session-level FTS 使用显式字段权重:title 8.0、compact 4.0、summary 3.0、reasoning summary 1.2 20 21 - `classifyQueryProfile()` 仍存在,但当前评分没有按 `broad/exact` 做显式分权 21 - - parser 只入库 `event_msg` 里的 user / assistant message;其他 event 不形成 projection 22 + - parser 只把 `event_msg` 里的 user / assistant 写入 `messages`;`type=compacted` 与 `response_item.reasoning.summary` 只进入 session-level 索引字段,不形成可回读 message projection 22 23 23 24 不要把下面这些说成已完成: 24 25 25 26 - 真正独立的 stage-2 / resource-level reranker 26 - - `summary_text` 或 session/resource 级独立 FTS recall 27 27 - richer projection / range cache / event-level replay 28 28 - duplicate family collapse / diversity control 29 29 - 强约束的 gold set / rubric / error taxonomy ··· 74 74 ## 当前近端优先级 75 75 76 76 1. 先把 eval 从弱提示升级成更可信的 acceptance gate 77 - 2. 再决定是否推进 `summary/projection` 独立 recall 面 77 + 2. 继续观察 session-level 字段召回是否引入排序噪音,并补 eval 覆盖 78 78 3. 更重的 reranker / projection / diversity 控制放后面 79 79 80 80 具体 roadmap 见 [docs/ROADMAP.md](/Users/envvar/work/repos/cxs/docs/ROADMAP.md)。
+6 -4
README.md
··· 28 28 - `cxs read-page <sessionUuid>` 29 29 - `cxs list` 30 30 - `cxs stats` 31 + - `cxs current` 31 32 32 33 ## 安装 33 34 ··· 72 73 ./bin/cxs find "health check" 73 74 ``` 74 75 75 - `find` 会返回标题、派生的 session summary,以及当前锚点 snippet,方便先做轻量筛选再决定是否 `read-range`。 76 + `find` 会返回标题、派生的 session summary,以及当前锚点 snippet,方便先做轻量筛选再决定是否 `read-range`。如果命中只来自 session-level title/summary/compact/reasoning summary,结果会标为 `matchSource = "session"`,这时先用 `read-page` 浏览整场会话。 76 77 77 78 围绕命中点读取局部上下文: 78 79 ··· 121 122 122 123 当前 retrieval 主链是: 123 124 124 - `message recall -> session heuristic rerank -> read-range/read-page` 125 + `message/session recall -> session heuristic rerank -> read-range/read-page` 125 126 126 127 已经落地的能力: 127 128 128 129 - `messages_fts` 驱动的候选召回 130 + - `sessions_fts(title + summary_text + compact_text + reasoning_summary_text)` 驱动的 session-level 召回 129 131 - `summary_text` 派生摘要 132 + - JSONL `type=compacted` handoff 与 `response_item.reasoning.summary` 低成本接入 133 + - session-level FTS 字段权重:title 8.0、compact 4.0、summary 3.0、reasoning summary 1.2 130 134 - session 级 heuristic rerank 131 135 - manual eval 导出与 batch compare 132 136 133 137 还没落地的能力: 134 138 135 - - `summary_text` 参与 recall 136 - - 独立的 session/resource 级搜索面 137 139 - 真正的 resource-level reranker 138 140 - duplicate collapse / diversity control 139 141
+23 -8
docs/ARCHITECTURE.md
··· 4 4 5 5 `cxs` 是一个面向本机 Codex session 日志的渐进式检索 CLI,当前架构是: 6 6 7 - `sync -> message recall -> session heuristic rerank -> read-range/read-page` 7 + `sync -> message/session recall -> session heuristic rerank -> read-range/read-page` 8 8 9 9 它已经可用,但仍是轻量 retrieval 后端,不是完整的 resource-level retrieval 系统。 10 10 ··· 16 16 - `cxs read-page <sessionUuid>` 17 17 - `cxs list` 18 18 - `cxs stats` 19 + - `cxs current` 19 20 20 21 这套命令面已经定型,不再保留 `window/session` 旧别名语义。 21 22 ··· 39 40 - `sessions` 40 41 - `messages` 41 42 42 - 以及一个全文索引: 43 + 以及两个全文索引: 43 44 44 45 - `messages_fts` 46 + - `sessions_fts` 45 47 46 - 当前 `sessions` 已包含 `summary_text` 字段,但没有单独的 `sessions_fts`。 48 + `messages_fts` 只索引真实消息,`sessions_fts` 索引 `title + summary_text + compact_text + reasoning_summary_text`。这样可以让生成标题、派生摘要、compact handoff、reasoning summary 参与召回,同时不把这些 session-level 信号伪装成 `seq = -1` 的消息。 49 + 50 + SQLite 访问层当前已经按 reader / writer 分流: 51 + 52 + - `sync` 走 writer 连接,负责 schema ensure、WAL 初始化与写入事务 53 + - `find` / `read-range` / `read-page` / `list` / `stats` 走只读连接 54 + - 读路径默认设置 `busy_timeout`,避免并发 agent 多查时把瞬时锁竞争直接暴露成 `SQLITE_BUSY` 55 + - `sync` 额外有文件级 single-writer lock;遇到活跃 writer 会等待,遇到 dead pid 残留锁会自动清理 47 56 48 57 ### 3. 查询 49 58 ··· 55 64 56 65 `findSessions()` 当前流程是: 57 66 58 - 1. 先从 `messages_fts` 做候选召回 59 - 2. 极少数零 token CJK query 回退到 LIKE 60 - 3. 把 raw hits 交给 [ranking.ts](/Users/envvar/work/repos/cxs/ranking.ts) 做 session 级排序 67 + 1. 从 `messages_fts` 做原文证据召回 68 + 2. 从 `sessions_fts` 做 session-level 字段召回 69 + 3. 极少数零 token CJK query 在 message 侧回退到 LIKE 70 + 4. 把 raw hits 合并后交给 [ranking.ts](/Users/envvar/work/repos/cxs/ranking.ts) 做 session 级排序 71 + 72 + `messages` 仍然只代表可回读的真实 transcript。session-level 命中会以 `matchSource = "session"` 返回;如果没有真实 message anchor,`matchSeq` 为 `null`,CLI 会建议先 `read-page`。 61 73 62 74 ### 4. 排序 63 75 ··· 66 78 主要信号包括: 67 79 68 80 - row 级 bm25 翻转分数 81 + - session-level FTS 字段权重:title 8.0、compact 4.0、summary 3.0、reasoning summary 1.2 69 82 - content phrase / term coverage 70 83 - user message bump 71 84 - title phrase / term hits 72 85 - cwd term hits 73 86 - user hit count 87 + - session-level hit count 74 88 - hit count 75 89 - recency 76 90 ··· 79 93 - 渐进式命令面 80 94 - CJK 兼容的 tokenized FTS 81 95 - `summary_text` 派生摘要 96 + - `compact_text` 解析 JSONL `type=compacted` handoff 97 + - `reasoning_summary_text` 解析 `response_item.reasoning.summary` 98 + - `sessions_fts(title + summary_text + compact_text + reasoning_summary_text)` session-level recall 82 99 - strict / best-effort 两种 sync 语义 83 100 - manual eval 导出 84 101 - eval batch compare ··· 87 104 88 105 下面这些不要误写成现状: 89 106 90 - - `summary_text` 参与 recall 91 - - `sessions_fts` 或 session/resource 独立搜索面 92 107 - 真正按 broad/exact query profile 分权的 scoring 93 108 - richer projection / event replay / range cache 94 109 - duplicate collapse / diversity control
+200
docs/CODE_QUALITY_REVIEW_2026-04-27.md
··· 1 + # cxs 代码质量审查报告(2026-04-27) 2 + 3 + ## 结论 4 + 5 + 当前 `cxs` 代码质量中上,已经是可接手的小型 CLI 项目;主要风险不在代码结构本身,而在工程化、异常路径和少数边界防御还需要补强。 6 + 7 + 综合评分:**7.5 / 10**。 8 + 9 + ## 审查范围 10 + 11 + 本次审查基于当前工作区状态,而不是 clean `main`。审查覆盖: 12 + 13 + - `cli.ts` 14 + - `parser.ts` 15 + - `db.ts` 16 + - `query.ts` 17 + - `ranking.ts` 18 + - `indexer.ts` 19 + - `sync-lock.ts` 20 + - `types.ts` 21 + - 测试文件与项目文档 22 + 23 + 验证命令: 24 + 25 + ```bash 26 + bun test 27 + ``` 28 + 29 + 结果: 30 + 31 + ```text 32 + 30 pass 33 + 0 fail 34 + 109 expect() calls 35 + ``` 36 + 37 + ## 优点 38 + 39 + ### 1. 项目边界清楚 40 + 41 + `cxs` 明确定位为本机 Codex session 日志的渐进式检索 CLI,不把 GUI、watcher、daemon、realtime sync 混进来。 42 + 43 + 当前主工作流保持为: 44 + 45 + ```text 46 + sync -> find -> read-range/read-page 47 + ``` 48 + 49 + ### 2. 模块拆分健康 50 + 51 + 当前代码地图比较清晰: 52 + 53 + - `cli.ts`:CLI 命令面 54 + - `indexer.ts`:sync 与索引更新 55 + - `parser.ts`:Codex JSONL 解析与 session summary 生成 56 + - `db.ts`:SQLite schema、session/message 存取、FTS 表维护 57 + - `query.ts`:find/list/read-range/read-page/current 查询编排 58 + - `ranking.ts`:session 级 heuristic rerank 59 + - `eval/`:manual eval 与 batch compare 60 + 61 + 这对一个本地 CLI 来说是健康结构。 62 + 63 + ### 3. 当前改动方向合理 64 + 65 + 本轮实现方向整体正确: 66 + 67 + - 增加 `current` 命令,从 Codex state DB 按 cwd 找候选 session。 68 + - 增加 session-level recall,把 `title + summary_text + compact_text + reasoning_summary_text` 纳入 `sessions_fts`。 69 + - 引入 `matchSource = "session"` 与 `matchSeq = null` 语义,避免把 summary/compact 伪造成可回读 message。 70 + - 保持 `messages` 只表示真实可回读 transcript。 71 + - 增加 sync single-writer lock 与 read busy timeout。 72 + - 同步更新 README、AGENTS、architecture、roadmap、skill JSON schema 等文档。 73 + 74 + 尤其值得保留的是:**不把 compact/summary 写成 `seq = -1` 的虚拟 message**。这个设计比把 session-level signal 混入 message stream 更干净。 75 + 76 + ### 4. 测试覆盖不差 77 + 78 + 现有测试已经覆盖: 79 + 80 + - CLI help/current/find/list/stats/read-page/sync fail 81 + - parser 对 compacted handoff 与 reasoning summary 的抽取 82 + - query 的 title-only recall、field weights、compact recall、snippet density、parallel read lock 83 + - indexer 的 strict/best-effort、writer lock、stale lock 84 + - eval predicate 语义 85 + 86 + ## 主要问题与风险 87 + 88 + ### P0-1:缺少真正的 TypeScript 类型检查 89 + 90 + `package.json` 中 `check` 当前只是 `bun test`,没有 `tsc --noEmit`。Bun 可以执行 TypeScript,但不等于有完整类型检查。 91 + 92 + 当前类型已经开始变复杂,例如: 93 + 94 + - `matchSeq: number | null` 95 + - `matchSource: "message" | "session"` 96 + - `FindMatchRole` 97 + 98 + 建议补: 99 + 100 + - `tsconfig.json` 101 + - `bunx tsc --noEmit` 102 + - 把 `check` 改成 `tsc --noEmit && bun test` 103 + 104 + ### P0-2:DB 连接在异常路径上可能泄漏 105 + 106 + 部分 query 函数当前是: 107 + 108 + ```ts 109 + const db = openReadDb(dbPath); 110 + const session = getSessionRecord(db, sessionUuid); 111 + if (!session) throw new Error(...); 112 + db.close(); 113 + ``` 114 + 115 + 如果中间 throw,`db.close()` 不会执行。 116 + 117 + 建议把 query 层所有 open/close 统一改成: 118 + 119 + ```ts 120 + const db = openReadDb(dbPath); 121 + try { 122 + // work 123 + } finally { 124 + db.close(); 125 + } 126 + ``` 127 + 128 + `getCurrentSessions()` 已经采用这种结构,其他函数应对齐。 129 + 130 + ### P0-3:sync lock stale 清理存在竞争窗口 131 + 132 + 当前逻辑遇到已有 lock 后会读 lock info,判断 pid 不存在就删除 lock 文件。这里有一个竞态:读到 stale lock 之后、删除之前,另一个进程可能已经创建了新 lock;当前进程可能误删别人的新 lock。 133 + 134 + 建议删除前重新读取并比对 `pid + createdAt`,只删除自己刚判断过的那份 lock。 135 + 136 + 另外,损坏的 lock file 当前会被解析为 `null`,然后等到 timeout 报 unknown owner。可以增加基于 mtime 的过期清理或更明确的错误信息。 137 + 138 + ### P0-4:`current` 命令对 Codex state DB schema 假设偏硬 139 + 140 + `current` 直接依赖 `threads` 表和字段: 141 + 142 + - `id` 143 + - `rollout_path` 144 + - `cwd` 145 + - `title` 146 + - `updated_at_ms` 147 + 148 + 这对当前本机可用,但作为开源 CLI,Codex state DB schema 可能变化。建议: 149 + 150 + - 先检查 table/columns 是否存在。 151 + - schema 不匹配时输出友好错误。 152 + - `--json` 模式返回结构化 error,而不是 SQLite 原始异常。 153 + 154 + ## 次级问题 155 + 156 + ### P1-1:ranking 逻辑开始堆 magic constants 157 + 158 + 当前 ranking 使用多组启发式权重,例如 title phrase、title term、cwd term、message bump、user bump、session hit、hit count、recency 等。 159 + 160 + 这对轻量 retrieval CLI 是合理的,但继续堆会变难调。下一步不建议继续盲目加权重,应优先补 manual eval acceptance gate。 161 + 162 + ### P1-2:mixed match 展示策略需要专门测试 163 + 164 + 当前展示行优先选择 message hit,避免用户拿到 `matchSeq = null` 后无法 `read-range`。这是合理产品取舍,但可能掩盖真正强的 session-level 命中。 165 + 166 + 建议增加 mixed case 测试:同一个 session 里同时有强 session-level hit 和弱 message hit 时,确认展示策略符合预期。 167 + 168 + ### P1-3:缺少真实大库性能基准 169 + 170 + 现有单元测试较好,但还缺: 171 + 172 + - 几千 session 下的 sync/find p95 173 + - 大型 `sessions_fts` 的 query 噪音评估 174 + - real-world manual eval regression gate 175 + 176 + ## 建议优先级 177 + 178 + ### P0:发前必须做 179 + 180 + 1. 补 `tsc --noEmit`。 181 + 2. query 层所有 DB open 改成 `try/finally close`。 182 + 3. 修 sync lock 删除 stale lock 的 race。 183 + 4. 对 `current` 做 state DB schema 检查和友好错误。 184 + 185 + ### P1:增强可信度 186 + 187 + 1. 把 session-level recall 的 mixed cases 加进 eval。 188 + 2. 给 ranking magic constants 写一页短说明,避免后续 agent 乱调。 189 + 3. 跑一次真实库 smoke:`sync -> stats -> find -> read-range/read-page`。 190 + 4. 把当前大工作区改动拆成 focused commits。 191 + 192 + ### P2:长期演进 193 + 194 + 1. eval 从人工报告升级为 regression gate。 195 + 2. 再考虑 duplicate collapse / diversity control。 196 + 3. 再考虑真正 resource-level reranker。 197 + 198 + ## 结语 199 + 200 + `cxs` 当前已经不是玩具脚本,结构、测试和文档同步意识都比较像一个正经小型 CLI。下一阶段最值得投入的是类型检查、异常路径资源释放、lock race 和 state DB schema 防御,而不是继续堆检索权重。
+14 -10
docs/ROADMAP.md
··· 2 2 3 3 ## 当前判断 4 4 5 - `cxs` 现在已经有一条可用的 retrieval 主链,但下一步不该盲目继续堆排序逻辑。当前最缺的是一个更可信的 acceptance gate。 5 + `cxs` 现在已经有一条可用的 retrieval 主链。`title + summary_text + compact_text + reasoning_summary_text` 已作为 session-level recall 面接入,并通过 FTS5 column weights 显式分权;下一步仍不该盲目继续堆排序逻辑,当前最缺的是更可信的 acceptance gate。 6 6 7 7 ## 优先级 8 8 ··· 29 29 - `bun run eval:manual` 30 30 - `bun run ./eval/compare-eval-batches.ts <before> <after>` 31 31 32 - ### P1: 再决定是否补 summary recall 32 + ### P1: 已补 session-level 字段召回 33 33 34 - 目标:解决“正文不命中、只有 summary 命中”的 recall 漏洞。 34 + 目标:解决“正文不命中、只有 title / summary / compact handoff 命中”的 recall 漏洞。 35 35 36 36 当前现状: 37 37 38 - - `summary_text` 已生成、已持久化、已参与 rerank 39 - - 但 `find` 的候选仍只来自 `messages_fts` / LIKE 38 + - `messages_fts` 负责真实 message evidence recall 39 + - `sessions_fts(title + summary_text + compact_text + reasoning_summary_text)` 负责 session-level recall 40 + - 字段权重固定为:title 8.0、compact 4.0、summary 3.0、reasoning summary 1.2 41 + - session-only 命中返回 `matchSource = "session"`,且 `matchSeq = null` 42 + - CLI 对 session-only 命中建议 `read-page`,不伪造 `read-range --seq` 40 43 41 - 候选实现方向: 44 + 已经排除: 42 45 43 - - 方案 A:把 summary 作为虚拟 message 写入 `messages` + `messages_fts` 44 - - 方案 B:新增 `sessions_fts(title + summary_text)`,`find` 时 UNION 两路候选 46 + - 不把 summary 写成 `seq = -1` 的虚拟 message 47 + - 不新增 `session_projections` 业务表 45 48 46 - 当前不把它排到 P0 的原因: 49 + 后续需要补: 47 50 48 - - [docs/TODO.md](/Users/envvar/work/repos/cxs/docs/TODO.md) 记录的现有 eval 里,还没观察到明显 recall 漏洞 51 + - 把 title/summary/compact-only query 加进 manual eval 52 + - 对 session-only 命中补专门断言 49 53 50 54 ### P2: 真正的 query profile 分流 51 55
+13 -10
docs/TODO.md
··· 16 16 - 对 `read-range` 可用性的断言 17 17 - 更清晰的 failure taxonomy 18 18 19 - ## P1: summary 参与 recall 19 + ## P1: session-level recall 后续补强 20 20 21 - 当前 `sessions.summary_text` 已经生成、存库、参与 rerank,但没有进 recall 面。 21 + 当前 `sessions.summary_text`、`sessions.compact_text`、`sessions.reasoning_summary_text` 已经生成、存库,并通过 `sessions_fts(title + summary_text + compact_text + reasoning_summary_text)` 进入 recall 面。 22 22 23 - 这意味着:如果一个 session 的正文和 query 不重合,只有 summary 命中,它当前不会被 `find` 召回。 23 + 已定边界: 24 24 25 - 候选方案: 25 + - `messages` 仍只保存真实可回读消息 26 + - compact handoff 与 reasoning summary 只作为 session-level 检索信号,不写成可回读 message 27 + - session-level FTS 字段权重固定为:title 8.0、compact 4.0、summary 3.0、reasoning summary 1.2 28 + - session-only 命中返回 `matchSource = "session"` 和 `matchSeq = null` 29 + - 不插入 `seq = -1` 虚拟 message 30 + - 不新增 `session_projections` 业务表 26 31 27 - - 插入一条 `seq = -1`、`role = "summary"` 的虚拟 message 进 `messages` + `messages_fts` 28 - - 或者新建 `sessions_fts(title + summary_text)`,`find` 时 UNION 两路候选 32 + 下一步应该补: 29 33 30 - 当前仍不优先的原因: 31 - 32 - - 现有 eval 下还没观察到明显 recall 漏洞 33 - - 在更强 eval 就位前,先改 recall 面容易变成“改了很多,但证据不够硬” 34 + - 把 title/summary/compact-only query 写进 manual eval 35 + - 增加对 `matchSource` / `matchSeq = null` 的断言 36 + - 继续观察 session-level recall 是否引入排序噪音 34 37 35 38 ## P2: 真正接通 broad / exact query 分流 36 39