reasonix 0.11.1 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,9 +18,10 @@
18
18
  [![downloads](https://img.shields.io/npm/dm/reasonix.svg)](https://www.npmjs.com/package/reasonix)
19
19
  [![node](https://img.shields.io/node/v/reasonix.svg)](./package.json)
20
20
 
21
- **A DeepSeek-native AI coding agent in your terminal.** Edits files as
22
- reviewable SEARCH/REPLACE blocks. Ink TUI. MCP first-class. No
23
- LangChain.
21
+ **A DeepSeek-native AI coding agent in your terminal.** ~30× cheaper
22
+ per task than Claude Code, with a cache-first loop engineered for
23
+ DeepSeek's pricing model. Edits as reviewable SEARCH/REPLACE blocks.
24
+ MIT-licensed. No IDE lock-in. MCP first-class.
24
25
 
25
26
  ---
26
27
 
@@ -71,6 +72,137 @@ command list.
71
72
 
72
73
  ---
73
74
 
75
+ ## Why Reasonix? (vs Cursor / Claude Code / Cline / Aider)
76
+
77
+ Three things you'd come to Reasonix for, that nothing else combines:
78
+
79
+ - **The cost economics actually land in your bill.** DeepSeek V4 is
80
+ ~30× cheaper than Claude Sonnet per token. Cheaper tokens alone
81
+ isn't the win — *cheap tokens with a 90%+ prefix-cache hit* is.
82
+ Reasonix's loop is engineered around append-only prompt growth so
83
+ the cache-stable prefix survives every tool call, which the
84
+ benchmarks section below verifies end-to-end (94.4% live, vs 46.6%
85
+ for a generic harness against the same workload). The `/stats`
86
+ panel tracks "vs Claude Sonnet 4.6" savings every turn so you can
87
+ watch your bill not happen.
88
+
89
+ - **It lives in your terminal.** Pure CLI — no Electron, no VS Code
90
+ extension, no IDE plugin to wedge into your editor. Sits next to
91
+ git, tmux, and your shell history. macOS / Linux / Windows
92
+ (PowerShell, Git Bash, Windows Terminal all tested). The only
93
+ network call is to the DeepSeek API itself; no vendor server in
94
+ the middle.
95
+
96
+ - **Open source and hackable, end to end.** MIT-licensed TypeScript.
97
+ The entire loop, tool registry, cache-stable prefix, TUI, MCP
98
+ bridge — all in `src/` under 30k lines. Fork it, ship a private
99
+ build, drop it into CI. No SaaS layer, no enterprise tier, no
100
+ feature gates.
101
+
102
+ | | Reasonix | Claude Code | Cursor | Cline | Aider |
103
+ |---|---|---|---|---|---|
104
+ | Backend | DeepSeek V4 only | Anthropic only | OpenAI / Anthropic | any (OpenRouter) | any (OpenRouter) |
105
+ | Cost / typical task | **~$0.001–$0.005** | ~$0.05–$0.50 | $20/mo + usage | varies | varies |
106
+ | Where it runs | terminal | terminal + IDE | IDE (Electron) | VS Code only | terminal |
107
+ | License | **MIT** | closed | closed | Apache 2 | Apache 2 |
108
+ | Cache-first prefix loop | **engineered (94% hit)** | basic | n/a | n/a | basic |
109
+ | MCP servers | **first-class** | first-class | — | beta | — |
110
+ | Plan mode (read-only audit gate) | **yes** | yes | — | yes | — |
111
+ | User-authored skills | **yes** | yes | — | — | — |
112
+ | Edit review (no auto-write) | **yes** (`/apply`) | yes | partial | yes | yes |
113
+ | Workspace switch (`/cwd`, `change_workspace`) | **yes** | — | n/a (per-window) | — | — |
114
+ | Cross-session cost dashboard | **yes** (`/stats`) | — | — | — | — |
115
+ | Sandbox boundary enforcement | **strict** (refuses `..` escape) | yes | partial | yes | partial |
116
+
117
+ ### Pick something else when
118
+
119
+ - **You want multi-provider flexibility** (mix Claude / GPT / Gemini /
120
+ local Llama in one tool). Try [Aider](https://aider.chat) or
121
+ [Cline](https://cline.bot). Reasonix is DeepSeek-only on purpose —
122
+ every layer (cache-first loop, R1 harvesting, JSON-mode tool repair,
123
+ reasoning-effort cap) is tuned against DeepSeek-specific behavior
124
+ and economics. Coupling to one backend is the feature, not a
125
+ limitation we'll grow out of.
126
+ - **You want IDE integration** (inline diff in your gutter,
127
+ multi-cursor, ghost text, refactor previews). Try
128
+ [Cursor](https://cursor.com) or Claude Code's IDE mode. Reasonix
129
+ is terminal-first; the diff lives in `git diff`, the file tree
130
+ lives in `ls`, the chat lives in your shell.
131
+ - **You're chasing the hardest reasoning benchmarks.** Claude Opus
132
+ 4.6 still wins some leaderboards. DeepSeek V4-pro is competitive
133
+ on most coding tasks but doesn't lead every benchmark. If your
134
+ task is "solve this PhD-level proof" rather than "fix this auth
135
+ bug," start with Claude.
136
+ - **You need fully-local / fully-free**. DeepSeek's API has free
137
+ credit on signup, but isn't free forever. For air-gapped or
138
+ always-free, look at Aider + Ollama or [Continue](https://continue.dev).
139
+
140
+ ### "But DeepSeek now has an Anthropic-compatible API — can't I just point Claude Code at it?"
141
+
142
+ You can. DeepSeek ships an official Anthropic-compatible endpoint at
143
+ `https://api.deepseek.com/anthropic`, and Claude Code (or any Anthropic
144
+ SDK client) talks to it without modification. The protocol works. The
145
+ **caching economics** don't transfer, and that's the whole point.
146
+
147
+ Look at DeepSeek's [own compatibility table](https://api-docs.deepseek.com/guides/anthropic_api):
148
+
149
+ | Field | Status on DeepSeek's compat endpoint |
150
+ |---|---|
151
+ | `cache_control` markers | **Ignored** |
152
+ | `mcp_servers` (API-level) | Ignored |
153
+ | `thinking.budget_tokens` | Ignored |
154
+ | Images / documents / citations | Not supported |
155
+
156
+ `cache_control: Ignored` is the load-bearing line. Two completely
157
+ different cache mechanics are colliding here:
158
+
159
+ | | Anthropic native | DeepSeek auto-cache |
160
+ |---|---|---|
161
+ | Model | **Marker-based.** You put `cache_control` on a message; Anthropic caches "everything up to this marker" as a content-addressed unit. Multiple markers = multiple independent breakpoints. | **Byte-stable prefix.** The cache fingerprints the literal byte stream from byte 0. |
162
+ | Claude Code's design | Built around this. Markers on system prompt + tool defs let the loop reorder, compact, or insert metadata after the markers without losing the cache. | n/a — Claude Code wasn't designed for byte-stable prefixes. |
163
+ | What happens when Claude Code → DeepSeek compat | Markers stripped (ignored). Claude Code's main caching strategy disappears. | Falls back to auto-cache. But Claude Code's prefix isn't byte-stable (markers were the *substitute* for byte-stability), so auto-cache misses too. |
164
+
165
+ Net effect: **Claude Code's loop, redirected at DeepSeek, gets the
166
+ cheap tokens and loses the cache hit it depended on.** A loop running
167
+ at 80%+ cache hit on Anthropic's marker cache lands somewhere in the
168
+ 40-60% range on DeepSeek's auto-cache (matches the generic-harness
169
+ baseline in our benchmarks). Same model, same API, same workload —
170
+ the loop's invariants don't fit the cache mechanic it's now talking
171
+ to.
172
+
173
+ Reasonix's loop was designed around byte-stable prefix from line one.
174
+ No markers, no breakpoints — append-only is the invariant. That's why
175
+ the same τ-bench workload lands at **94.4% cache hit** on Reasonix
176
+ and **46.6%** on a cache-hostile baseline (committed transcripts;
177
+ benchmarks section below). At DeepSeek's pricing — $0.07/Mtok
178
+ uncached, ~$0.014/Mtok cached — the difference between 50% and 94%
179
+ hit is **roughly 2.5× on input cost alone**.
180
+
181
+ ### "What about Aider / Cline / Continue?"
182
+
183
+ They support DeepSeek natively (no compat layer needed) and you do
184
+ get the cheap token price. What you don't get is the DeepSeek-
185
+ specific loop work — those tools' loops support every backend
186
+ generically (OpenAI / Anthropic / local Llama / ...) and use
187
+ compaction + summarization patterns that destroy byte-stability. They
188
+ land in the same 40-60% cache-hit range as the baseline. Plus a
189
+ handful of DeepSeek-specific quirks generic loops don't handle:
190
+
191
+ | Generic loops assume | DeepSeek actually does | Reasonix's fix |
192
+ |---|---|---|
193
+ | Reasoning emitted as a structured `thinking` block | R1 sometimes leaks tool-call JSON inside `<think>` tags | a `scavenge` pass that pulls escaped tool calls back out, otherwise the model thinks it called and waits for output that never comes |
194
+ | Tool schemas validated strictly | DeepSeek silently drops deeply-nested object/array params | auto-flatten — nested params get rewritten to single-level prefixed names so the model sees them at all |
195
+ | Tool-call args are well-formed JSON | DeepSeek occasionally produces `string="false"` and other malformed fragments | dedicated `ToolCallRepair` heals the common shapes before they hit dispatch |
196
+ | Reasoning depth tuned via system-level switches | V4 exposes a `reasoning_effort` knob (`max` / `high`) | `/effort` slash + `--effort` flag, so users can step down for cheap turns |
197
+ | Old tool results kept in full forever | 1M context — don't compact pre-emptively, but most agents do | call-storm breaker + result token cap, but the prefix is *never* rewritten; compaction lands as new turns at the tail |
198
+
199
+ > Cache-stability isn't a feature you turn on; it's an invariant
200
+ > the loop is designed around. Reasonix isn't yet-another agent
201
+ > CLI — it's an agent CLI built around DeepSeek's specific cache
202
+ > mechanic and pricing model.
203
+
204
+ ---
205
+
74
206
  ## `reasonix code` — pair programmer in your terminal
75
207
 
76
208
  Scoped to the directory you launch from. The model has native
@@ -771,7 +903,7 @@ cd reasonix
771
903
  npm install
772
904
  npm run dev code # run CLI from source via tsx
773
905
  npm run build # tsup to dist/
774
- npm test # vitest (1007 tests)
906
+ npm test # vitest (1482 tests)
775
907
  npm run lint # biome
776
908
  npm run typecheck # tsc --noEmit
777
909
  ```
package/README.zh-CN.md CHANGED
@@ -18,8 +18,9 @@
18
18
  [![downloads](https://img.shields.io/npm/dm/reasonix.svg)](https://www.npmjs.com/package/reasonix)
19
19
  [![node](https://img.shields.io/node/v/reasonix.svg)](./package.json)
20
20
 
21
- **DeepSeek 原生的终端 AI 编程代理。** 编辑以可审查的 SEARCH/REPLACE 块呈现,
22
- 落盘前必须确认。Ink TUI、原生 MCP、不依赖 LangChain。
21
+ **DeepSeek 原生的终端 AI 编程代理。** 单次任务成本约为 Claude Code 的
22
+ 1/30,缓存优先的循环是为 DeepSeek 的定价模型量身打造的。编辑以可审查的
23
+ SEARCH/REPLACE 块呈现,落盘前必须确认。MIT 许可、不绑 IDE、原生 MCP。
23
24
 
24
25
  ---
25
26
 
@@ -68,6 +69,120 @@ Windows Terminal)。任何时候按 `Esc` 中断;`/help` 查看完整命令
68
69
 
69
70
  ---
70
71
 
72
+ ## 为什么选 Reasonix?(vs Cursor / Claude Code / Cline / Aider)
73
+
74
+ 三件事,别家不会同时都给你:
75
+
76
+ - **成本节省落到账单上。** DeepSeek V4 的 token 单价大约是 Claude Sonnet
77
+ 的 1/30。光便宜还不够 —— *便宜的 token 配上 90%+ 的前缀缓存命中*才是关键。
78
+ Reasonix 的循环按 append-only 增长设计,缓存稳定的前缀在每次工具调用之间
79
+ 都活着,下面的 benchmark 章节端到端验证过:实测 94.4% 缓存命中,对照组通用
80
+ 框架只有 46.6%。`/stats` 面板每轮都跟踪 "vs Claude Sonnet 4.6" 的节省额,
81
+ 你可以亲眼看着账单不涨。
82
+
83
+ - **它住在终端里。** 纯 CLI —— 没有 Electron,没有 VS Code 插件,没有要
84
+ 塞进编辑器的 IDE 插件。和 git、tmux、shell 历史并排。macOS / Linux /
85
+ Windows(PowerShell、Git Bash、Windows Terminal 都测过)。唯一的网络
86
+ 请求就是 DeepSeek API 本身,中间没有厂商服务器。
87
+
88
+ - **开源且彻底可改。** MIT 许可的 TypeScript。整个循环、工具注册表、
89
+ 缓存稳定前缀、TUI、MCP 桥接 —— 全部在 `src/` 下,不到 3 万行。Fork
90
+ 它、做私有构建、塞进 CI 都可以。没有 SaaS 层,没有企业版,没有功能闸门。
91
+
92
+ | | Reasonix | Claude Code | Cursor | Cline | Aider |
93
+ |---|---|---|---|---|---|
94
+ | 后端 | 仅 DeepSeek V4 | 仅 Anthropic | OpenAI / Anthropic | 任意(OpenRouter)| 任意(OpenRouter)|
95
+ | 单次任务成本 | **~$0.001–$0.005** | ~$0.05–$0.50 | $20/月 + 用量 | 视情况 | 视情况 |
96
+ | 运行环境 | 终端 | 终端 + IDE | IDE(Electron)| 仅 VS Code | 终端 |
97
+ | 开源协议 | **MIT** | 闭源 | 闭源 | Apache 2 | Apache 2 |
98
+ | 缓存优先前缀循环 | **工程化(94% 命中)** | 基础 | n/a | n/a | 基础 |
99
+ | MCP 服务器 | **原生支持** | 原生支持 | — | 测试中 | — |
100
+ | 计划模式(只读审计闸门)| **支持** | 支持 | — | 支持 | — |
101
+ | 用户编写的 skills | **支持** | 支持 | — | — | — |
102
+ | 编辑审阅(不自动落盘)| **支持**(`/apply`)| 支持 | 部分 | 支持 | 支持 |
103
+ | 工作区切换(`/cwd`、`change_workspace`)| **支持** | — | n/a(每窗一项目)| — | — |
104
+ | 跨会话成本面板 | **支持**(`/stats`)| — | — | — | — |
105
+ | 沙箱边界强制 | **严格**(拒绝 `..` 逃逸)| 支持 | 部分 | 支持 | 部分 |
106
+
107
+ ### 这些情况下应该选别的
108
+
109
+ - **你想要多模型混用**(在一个工具里同时切 Claude / GPT / Gemini / 本地 Llama)。
110
+ 试试 [Aider](https://aider.chat) 或 [Cline](https://cline.bot)。Reasonix
111
+ 故意只绑 DeepSeek —— 每一层(缓存优先循环、R1 harvest、JSON 模式的工具
112
+ 调用修复、reasoning_effort 上限)都是为 DeepSeek 的具体行为和经济模型
113
+ 调出来的。绑死后端是设计选择,不是早晚要解决的限制。
114
+ - **你想要 IDE 集成**(编辑器侧边栏 inline diff、多光标、ghost text、重构
115
+ 预览)。试试 [Cursor](https://cursor.com) 或 Claude Code 的 IDE 模式。
116
+ Reasonix 是终端优先的:diff 在 `git diff` 里、文件树在 `ls` 里、对话
117
+ 在 shell 里。
118
+ - **你在追最难的推理 benchmark**。Claude Opus 4.6 还是赢一些榜单的。
119
+ DeepSeek V4-pro 在大多数编程任务上都很有竞争力,但不是每个 benchmark
120
+ 都领先。如果你的任务是"证明这个 PhD 级别的数学命题"而不是"修这个
121
+ auth bug",从 Claude 起步更合适。
122
+ - **你需要完全本地 / 永远免费**。DeepSeek API 注册送额度,但不是永久
123
+ 免费。要真正离线/永久免费,看看 Aider + Ollama 或者
124
+ [Continue](https://continue.dev)。
125
+
126
+ ### "DeepSeek 现在有 Anthropic 兼容 API 了,我直接拿 Claude Code 接上不就行?"
127
+
128
+ 可以接。DeepSeek 官方提供了 Anthropic 兼容端点
129
+ `https://api.deepseek.com/anthropic`,Claude Code(或任何 Anthropic SDK
130
+ 客户端)不改一行代码就能连上去。**协议跑得通,缓存经济学跑不通** ——
131
+ 而后者才是关键。
132
+
133
+ 看 [DeepSeek 自己的兼容性表](https://api-docs.deepseek.com/guides/anthropic_api):
134
+
135
+ | 字段 | 在 DeepSeek 兼容端点上的状态 |
136
+ |---|---|
137
+ | `cache_control` 标记 | **Ignored(被忽略)** |
138
+ | `mcp_servers`(API 层)| Ignored |
139
+ | `thinking.budget_tokens` | Ignored |
140
+ | 图像 / 文档 / 引用 | 不支持 |
141
+
142
+ `cache_control: Ignored` 就是杀手级的那一行。这里有**两套完全不同的缓存
143
+ 机制在打架**:
144
+
145
+ | | Anthropic 原生 | DeepSeek 自动缓存 |
146
+ |---|---|---|
147
+ | 模型 | **Marker 驱动。** 你在某条消息上打 `cache_control`,Anthropic 把"到此 marker 为止"的内容做内容寻址缓存。多个 marker = 多个独立断点。 | **Byte-stable prefix。** 缓存对字面字节流从第 0 字节起做指纹。 |
148
+ | Claude Code 的设计 | 围绕这个设计的。在 system prompt + tool 定义上插 marker,让 loop 在 marker 之后做重排、压缩、插元数据都不丢缓存。 | n/a —— Claude Code 不是为 byte-stable prefix 设计的。 |
149
+ | Claude Code 接 DeepSeek 兼容端点之后 | Marker 被 strip(忽略)。Claude Code 的主缓存策略消失。 | Fallback 到 auto-cache。但 Claude Code 的 prefix 不是 byte-stable 的(marker 本来就是 byte-stability 的*替代*),auto-cache 也命中不了。 |
150
+
151
+ 净效果:**Claude Code 的 loop 重定向到 DeepSeek 之后,便宜 token 拿到了,
152
+ 原本依赖的缓存命中没了**。一个在 Anthropic marker cache 上 80%+ 命中的
153
+ loop,到 DeepSeek 的 auto-cache 上大概率掉到 40-60%(跟我们 benchmark 里
154
+ 通用 harness 的 baseline 同区间)。同一个模型、同一个 API、同一个负载 ——
155
+ loop 的 invariant 跟它现在对话的缓存机制不匹配。
156
+
157
+ Reasonix 的 loop 从第一行起就是按 byte-stable prefix 的不变量设计的。没有
158
+ marker、没有断点 —— append-only 就是 invariant。这就是为什么同一份 τ-bench
159
+ 负载在 Reasonix 上是 **94.4% 缓存命中**、在 cache-hostile baseline 上是
160
+ **46.6%**(已 commit 的 transcript,见下面 benchmark 段)。按 DeepSeek 的
161
+ 单价 —— $0.07/Mtok 非缓存、约 $0.014/Mtok 缓存命中 —— 50% 和 94% 命中之间
162
+ **仅 input 这一侧就大约是 2.5× 的差距**。
163
+
164
+ ### "那 Aider / Cline / Continue 呢?"
165
+
166
+ 它们原生支持 DeepSeek(不需要兼容层),便宜 token 单价你确实拿到了。
167
+ 但你拿不到 DeepSeek-specific 的循环工程 —— 这些工具的 loop 是为**通用支持**
168
+ 所有后端(OpenAI / Anthropic / 本地 Llama / ...)设计的,用的是那种会破坏
169
+ byte-stability 的通用压缩 / 摘要模式。命中率落在和 baseline 同样的 40-60%
170
+ 区间。再加上一堆 DeepSeek 怪癖通用 loop 不处理:
171
+
172
+ | 通用 loop 假定 | DeepSeek 实际行为 | Reasonix 怎么处理 |
173
+ |---|---|---|
174
+ | 推理通过结构化 `thinking` 块产出 | R1 偶尔把 tool-call JSON 漏到 `<think>` 标签里 | `scavenge` 扫描把漏出的 tool call 拣回,否则模型以为自己已经调用,等不到结果 |
175
+ | tool schema 严格校验 | DeepSeek 静默丢弃深嵌套 object/array 参数 | auto-flatten:嵌套参数被重写成单层 prefixed name,模型才看得见 |
176
+ | tool-call args 是良构 JSON | DeepSeek 偶发 `string="false"` 之类的破碎片段 | 专用 `ToolCallRepair` 在 dispatch 之前修好常见形状 |
177
+ | 推理深度靠系统级开关 | V4 直接暴露 `reasoning_effort` 旋钮(`max` / `high`) | `/effort` slash + `--effort` flag,简单任务可以降到 high 省钱 |
178
+ | 老 tool result 永久保留 | 1M context 不需要主动 compact,但通用工具都会做 | call-storm breaker + 结果 token cap,但前缀**永不重写** —— 压缩作为新 turn 追加在尾部 |
179
+
180
+ > 缓存稳定性不是一个开关,是 loop 设计之初就要建立的不变量。Reasonix
181
+ > 不是"又一个 agent CLI",是**围绕 DeepSeek 具体的缓存机制和定价模型
182
+ > 设计的 agent CLI**。
183
+
184
+ ---
185
+
71
186
  ## `reasonix code` — 终端里的结对编程
72
187
 
73
188
  作用域为启动目录。模型自带 `read_file` / `write_file` / `edit_file` /
@@ -709,7 +824,7 @@ cd reasonix
709
824
  npm install
710
825
  npm run dev code # 用 tsx 直接从源码跑 CLI
711
826
  npm run build # tsup 打包到 dist/
712
- npm test # vitest(1007 个测试)
827
+ npm test # vitest(1482 个测试)
713
828
  npm run lint # biome
714
829
  npm run typecheck # tsc --noEmit
715
830
  ```
package/dist/cli/index.js CHANGED
@@ -112,7 +112,7 @@ import { createParser } from "eventsource-parser";
112
112
 
113
113
  // src/retry.ts
114
114
  var DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504];
115
- async function fetchWithRetry(fetchFn, url, init, opts = {}) {
115
+ async function fetchWithRetry(fetchFn, url, init2, opts = {}) {
116
116
  const maxAttempts = opts.maxAttempts ?? 4;
117
117
  const initial = opts.initialBackoffMs ?? 500;
118
118
  const cap = opts.maxBackoffMs ?? 1e4;
@@ -121,7 +121,7 @@ async function fetchWithRetry(fetchFn, url, init, opts = {}) {
121
121
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
122
122
  if (opts.signal?.aborted) throw new Error("aborted");
123
123
  try {
124
- const resp = await fetchFn(url, init);
124
+ const resp = await fetchFn(url, init2);
125
125
  if (resp.ok || !retryable.has(resp.status)) return resp;
126
126
  if (attempt === maxAttempts - 1) return resp;
127
127
  await resp.text().catch(() => void 0);
@@ -7254,12 +7254,12 @@ function formatLogSize(path5 = defaultUsageLogPath()) {
7254
7254
  }
7255
7255
 
7256
7256
  // src/cli/commands/chat.tsx
7257
- import { existsSync as existsSync15, statSync as statSync9 } from "fs";
7257
+ import { existsSync as existsSync16, statSync as statSync9 } from "fs";
7258
7258
  import { render } from "ink";
7259
7259
  import React27, { useState as useState12 } from "react";
7260
7260
 
7261
7261
  // src/cli/ui/App.tsx
7262
- import * as pathMod6 from "path";
7262
+ import * as pathMod7 from "path";
7263
7263
  import { Box as Box22, Static, Text as Text20, useApp, useStdout as useStdout8 } from "ink";
7264
7264
  import React24, { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef6, useState as useState10 } from "react";
7265
7265
 
@@ -11626,6 +11626,13 @@ var SLASH_COMMANDS = [
11626
11626
  },
11627
11627
  { cmd: "exit", summary: "quit the TUI" },
11628
11628
  // Code-mode only
11629
+ {
11630
+ cmd: "init",
11631
+ argsHint: "[force]",
11632
+ summary: "scan the project and synthesize a baseline REASONIX.md (model writes; review with /apply). `force` overwrites an existing file.",
11633
+ contextual: "code",
11634
+ argCompleter: ["force"]
11635
+ },
11629
11636
  {
11630
11637
  cmd: "apply",
11631
11638
  argsHint: "[N|N,M|N-M]",
@@ -12460,6 +12467,103 @@ var handlers3 = {
12460
12467
  walk: walk2
12461
12468
  };
12462
12469
 
12470
+ // src/cli/ui/slash/handlers/init.ts
12471
+ import { existsSync as existsSync15 } from "fs";
12472
+ import * as pathMod6 from "path";
12473
+ var INIT_PROMPT = [
12474
+ "# Task: Initialize REASONIX.md",
12475
+ "",
12476
+ "I want you to generate a REASONIX.md at the project root that captures",
12477
+ "the working knowledge a future Reasonix session needs to be productive",
12478
+ "here. This file is auto-pinned into your system prompt every launch,",
12479
+ "so its size and accuracy matter.",
12480
+ "",
12481
+ "## Hard constraints (do NOT relax these)",
12482
+ "",
12483
+ "- **Length cap: \u2264 80 lines / 3KB total.** Be concise. If you can't fit a",
12484
+ " section, drop it.",
12485
+ "- **Only document things you can verify by reading files.** Do NOT",
12486
+ " speculate about architectural intent, future roadmap, or design",
12487
+ " rationale. If it isn't obvious from the code, leave it out.",
12488
+ "- **No placeholder text.** No 'TODO: describe X', no 'Add more here'.",
12489
+ " Either state a fact or omit the section.",
12490
+ "",
12491
+ "## Procedure",
12492
+ "",
12493
+ "1. Read the top of any existing README* file.",
12494
+ "2. Read the manifest (package.json / Cargo.toml / pyproject.toml /",
12495
+ " go.mod / etc.) \u2014 pick whichever exists.",
12496
+ "3. `directory_tree` 1-2 levels deep on the project root, skipping",
12497
+ " common build/dependency dirs (node_modules, dist, target, .git,",
12498
+ " venv, __pycache__).",
12499
+ "4. Identify: primary language + framework, top-level layout, test",
12500
+ " runner, lint/format setup, build/run/test scripts, any non-obvious",
12501
+ " convention with visible evidence (commit message format, import",
12502
+ " order, naming pattern).",
12503
+ "5. Write REASONIX.md with the sections below, skipping any you can't",
12504
+ " fill from evidence.",
12505
+ "",
12506
+ "## Sections to use (skip ones with no evidence)",
12507
+ "",
12508
+ "- **Stack** \u2014 language + framework + 3-5 key deps. One line each.",
12509
+ "- **Layout** \u2014 top-level dirs and what lives in each. One line each.",
12510
+ "- **Commands** \u2014 verbatim from `scripts` block (or equivalent):",
12511
+ " build / test / lint / typecheck / dev / format. Whatever exists.",
12512
+ "- **Conventions** \u2014 only things visible in the code. Examples:",
12513
+ " '*.test.ts colocated with source', 'named exports only',",
12514
+ " 'commits use Conventional Commits prefix'. If you can't find any",
12515
+ " CONVENTION evidence, omit the whole section.",
12516
+ "- **Watch out for** \u2014 gotchas a new contributor would benefit from",
12517
+ " knowing BEFORE editing. Examples: 'edit_file SEARCH must match",
12518
+ " byte-for-byte', 'this dir is generated, don't edit by hand'.",
12519
+ " Omit if you find nothing concrete.",
12520
+ "",
12521
+ "## Output",
12522
+ "",
12523
+ "Write the result to `REASONIX.md` in the project root using the",
12524
+ "filesystem tools (edit_file with empty SEARCH if creating new,",
12525
+ "write_file if overwriting). After writing, STOP \u2014 do not summarize",
12526
+ "what you did, do not propose follow-up tasks. The user will review",
12527
+ "the pending edit via /apply.",
12528
+ "",
12529
+ "Start now."
12530
+ ].join("\n");
12531
+ var init = (args, _loop, ctx) => {
12532
+ if (!ctx.codeRoot) {
12533
+ return {
12534
+ info: [
12535
+ "/init only works in code mode (it needs filesystem tools).",
12536
+ "Run `reasonix code [path]` to start a session rooted at the",
12537
+ "project you want to initialize, then run /init."
12538
+ ].join("\n")
12539
+ };
12540
+ }
12541
+ const force = (args[0] ?? "").toLowerCase() === "force";
12542
+ const target = pathMod6.join(ctx.codeRoot, "REASONIX.md");
12543
+ if (existsSync15(target) && !force) {
12544
+ return {
12545
+ info: [
12546
+ `\u25B8 REASONIX.md already exists at ${target}`,
12547
+ "",
12548
+ " /init force regenerate from scratch (overwrites)",
12549
+ "",
12550
+ " Or edit it by hand \u2014 it's just markdown. The current file is",
12551
+ " pinned into the system prompt every launch as-is."
12552
+ ].join("\n")
12553
+ };
12554
+ }
12555
+ return {
12556
+ info: [
12557
+ "\u25B8 /init \u2014 model will scan the project and synthesize REASONIX.md.",
12558
+ " The result lands as a pending edit; review with /apply or /walk."
12559
+ ].join("\n"),
12560
+ resubmit: INIT_PROMPT
12561
+ };
12562
+ };
12563
+ var handlers4 = {
12564
+ init
12565
+ };
12566
+
12463
12567
  // src/cli/ui/slash/handlers/jobs.ts
12464
12568
  var jobs = (_args, _loop, ctx) => {
12465
12569
  if (!ctx.jobs) {
@@ -12515,7 +12619,7 @@ $ ${out.command}`;
12515
12619
  return { info: out.output ? `${header2}
12516
12620
  ${out.output}` : header2 };
12517
12621
  };
12518
- var handlers4 = {
12622
+ var handlers5 = {
12519
12623
  jobs,
12520
12624
  kill,
12521
12625
  logs
@@ -12576,7 +12680,7 @@ var mcp = (_args, loop2, ctx) => {
12576
12680
  lines.push("To change this set, exit and run `reasonix setup`.");
12577
12681
  return { info: lines.join("\n") };
12578
12682
  };
12579
- var handlers5 = { mcp };
12683
+ var handlers6 = { mcp };
12580
12684
 
12581
12685
  // src/cli/ui/slash/handlers/memory.ts
12582
12686
  var memory = (args, _loop, ctx) => {
@@ -12711,7 +12815,7 @@ var memory = (args, _loop, ctx) => {
12711
12815
  );
12712
12816
  return { info: parts.join("\n") };
12713
12817
  };
12714
- var handlers6 = { memory };
12818
+ var handlers7 = { memory };
12715
12819
 
12716
12820
  // src/cli/ui/slash/handlers/model.ts
12717
12821
  var model = (args, loop2, ctx) => {
@@ -12864,7 +12968,7 @@ var pro = (args, loop2, ctx) => {
12864
12968
  };
12865
12969
  };
12866
12970
  var ESCALATION_MODEL_ID = "deepseek-v4-pro";
12867
- var handlers7 = {
12971
+ var handlers8 = {
12868
12972
  model,
12869
12973
  models,
12870
12974
  harvest: harvest2,
@@ -13017,7 +13121,7 @@ var compact = (args, loop2) => {
13017
13121
  info: `\u25B8 compacted ${healedCount} payload(s) to ${cap.toLocaleString()} tokens each (tool results + tool-call args), saved ${tokensSaved.toLocaleString()} tokens (${charsSaved.toLocaleString()} chars). Session file rewritten.`
13018
13122
  };
13019
13123
  };
13020
- var handlers8 = {
13124
+ var handlers9 = {
13021
13125
  think,
13022
13126
  reasoning: think,
13023
13127
  tool,
@@ -13105,7 +13209,7 @@ var replay = (args, loop2) => {
13105
13209
  }
13106
13210
  };
13107
13211
  };
13108
- var handlers9 = {
13212
+ var handlers10 = {
13109
13213
  plans,
13110
13214
  replay
13111
13215
  };
@@ -13478,7 +13582,7 @@ async function readIndexMeta(rootDir) {
13478
13582
  return null;
13479
13583
  }
13480
13584
  }
13481
- var handlers10 = {
13585
+ var handlers11 = {
13482
13586
  semantic
13483
13587
  };
13484
13588
 
@@ -13513,7 +13617,7 @@ var forget = (_args, loop2) => {
13513
13617
  info: ok ? `\u25B8 deleted session "${name}" \u2014 current screen still shows the conversation, but next launch starts fresh` : `could not delete session "${name}" (already gone?)`
13514
13618
  };
13515
13619
  };
13516
- var handlers11 = {
13620
+ var handlers12 = {
13517
13621
  sessions,
13518
13622
  forget
13519
13623
  };
@@ -13589,7 +13693,7 @@ ${found.body}${argsLine}`;
13589
13693
  resubmit: payload
13590
13694
  };
13591
13695
  };
13592
- var handlers12 = {
13696
+ var handlers13 = {
13593
13697
  skill,
13594
13698
  skills: skill
13595
13699
  };
@@ -13607,7 +13711,8 @@ var HANDLERS = {
13607
13711
  ...handlers9,
13608
13712
  ...handlers10,
13609
13713
  ...handlers11,
13610
- ...handlers12
13714
+ ...handlers12,
13715
+ ...handlers13
13611
13716
  };
13612
13717
  function handleSlash(cmd, args, loop2, ctx = {}) {
13613
13718
  const h = HANDLERS[cmd];
@@ -15403,8 +15508,8 @@ function App({
15403
15508
  const parsed = JSON.parse(ev.toolArgs);
15404
15509
  if (typeof parsed.path === "string" && parsed.path.trim()) {
15405
15510
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
15406
- const expanded = parsed.path.startsWith("~") && home ? pathMod6.join(home, parsed.path.slice(1)) : parsed.path;
15407
- const abs = pathMod6.resolve(expanded);
15511
+ const expanded = parsed.path.startsWith("~") && home ? pathMod7.join(home, parsed.path.slice(1)) : parsed.path;
15512
+ const abs = pathMod7.resolve(expanded);
15408
15513
  setPendingWorkspace({ path: abs });
15409
15514
  }
15410
15515
  } catch {
@@ -16473,7 +16578,7 @@ async function chatCommand(opts) {
16473
16578
  const prior = loadSessionMessages(opts.session);
16474
16579
  if (prior.length > 0) {
16475
16580
  const p = sessionPath(opts.session);
16476
- const mtime = existsSync15(p) ? statSync9(p).mtime : /* @__PURE__ */ new Date();
16581
+ const mtime = existsSync16(p) ? statSync9(p).mtime : /* @__PURE__ */ new Date();
16477
16582
  sessionPreview = { messageCount: prior.length, lastActive: mtime };
16478
16583
  }
16479
16584
  } else if (opts.session && opts.forceNew) {