reasonix 0.15.0 → 0.16.1

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
@@ -72,7 +72,26 @@ command list.
72
72
 
73
73
  ---
74
74
 
75
- ## Web dashboard *(new in 0.12)*
75
+ ## At a glance
76
+
77
+ | | Reasonix | Claude Code | Cursor | Aider |
78
+ |------------------------------------|----------------|----------------|----------------|----------------|
79
+ | Backend | DeepSeek V4 | Anthropic | OpenAI / Anthropic | any |
80
+ | Cost / typical task | **~$0.001–0.005** | ~$0.05–0.50 | $20/mo + usage | varies |
81
+ | Where it runs | terminal | terminal + IDE | IDE (Electron) | terminal |
82
+ | License | **MIT** | closed | closed | Apache 2 |
83
+ | DeepSeek prefix-cache hit rate | **90.2%** | n/a | n/a | ~33% |
84
+ | Reviewable edits (no auto-write) | **yes** (`/apply`) | yes | partial | yes |
85
+ | MCP servers | **first-class**| first-class | — | — |
86
+
87
+ Numbers from `benchmarks/tau-bench-lite` (8 multi-turn coding tasks ×
88
+ 3 repeats, live `deepseek-chat`). Same workload, sole variable is
89
+ prefix stability — committed transcripts in [`benchmarks/`](./benchmarks/).
90
+ The full feature comparison [is below](#why-reasonix-vs-cursor--claude-code--cline--aider).
91
+
92
+ ---
93
+
94
+ ## Web dashboard
76
95
 
77
96
  Type `/dashboard` inside any session and Reasonix prints a localhost
78
97
  URL with a one-time token. Open it for a 13-tab control surface that
@@ -98,15 +117,14 @@ keep alive.
98
117
 
99
118
  Three things you'd come to Reasonix for, that nothing else combines:
100
119
 
101
- - **The cost economics actually land in your bill.** DeepSeek V4 is
102
- ~30× cheaper than Claude Sonnet per token. Cheaper tokens alone
103
- isn't the win — *cheap tokens with a 90%+ prefix-cache hit* is.
104
- Reasonix's loop is engineered around append-only prompt growth so
105
- the cache-stable prefix survives every tool call, which the
106
- benchmarks section below verifies end-to-end (94.4% live, vs 46.6%
107
- for a generic harness against the same workload). The `/stats`
108
- panel tracks "vs Claude Sonnet 4.6" savings every turn so you can
109
- watch your bill not happen.
120
+ - **Cost economics that land in your bill.** DeepSeek V4 is ~30×
121
+ cheaper than Claude Sonnet per token. Cheap tokens alone isn't the
122
+ win — *cheap tokens with a 90%+ prefix-cache hit* is. Reasonix's
123
+ loop is engineered around append-only prompt growth so the
124
+ cache-stable prefix survives every tool call. The benchmarks
125
+ section verifies this end-to-end: 90.2% live cache hit, versus
126
+ 32.8% for a generic harness on the same workload. The `/stats`
127
+ panel surfaces "vs Claude Sonnet 4.6" savings on every turn.
110
128
 
111
129
  - **It lives in your terminal.** Pure CLI — no Electron, no VS Code
112
130
  extension, no IDE plugin to wedge into your editor. Sits next to
@@ -136,6 +154,9 @@ Three things you'd come to Reasonix for, that nothing else combines:
136
154
  | Cross-session cost dashboard | **yes** (`/stats`) | — | — | — | — |
137
155
  | Sandbox boundary enforcement | **strict** (refuses `..` escape) | yes | partial | yes | partial |
138
156
 
157
+ <details>
158
+ <summary><strong>When reasonix is the wrong choice · DeepSeek/Anthropic-compat caveats · vs Aider/Cline/Continue</strong></summary>
159
+
139
160
  ### Pick something else when
140
161
 
141
162
  - **You want multi-provider flexibility** (mix Claude / GPT / Gemini /
@@ -194,8 +215,8 @@ to.
194
215
 
195
216
  Reasonix's loop was designed around byte-stable prefix from line one.
196
217
  No markers, no breakpoints — append-only is the invariant. That's why
197
- the same τ-bench workload lands at **94.4% cache hit** on Reasonix
198
- and **46.6%** on a cache-hostile baseline (committed transcripts;
218
+ the same τ-bench workload lands at **90.2% cache hit** on Reasonix
219
+ and **32.8%** on a cache-hostile baseline (committed transcripts;
199
220
  benchmarks section below). At DeepSeek's pricing — $0.07/Mtok
200
221
  uncached, ~$0.014/Mtok cached — the difference between 50% and 94%
201
222
  hit is **roughly 2.5× on input cost alone**.
@@ -223,6 +244,8 @@ handful of DeepSeek-specific quirks generic loops don't handle:
223
244
  > CLI — it's an agent CLI built around DeepSeek's specific cache
224
245
  > mechanic and pricing model.
225
246
 
247
+ </details>
248
+
226
249
  ---
227
250
 
228
251
  ## `reasonix code` — pair programmer in your terminal
@@ -311,7 +334,7 @@ assistant
311
334
  ```
312
335
 
313
336
  No allowlist gate — user-typed shell = explicit consent. 60s timeout,
314
- 32k char cap, survives session resume since 0.5.14.
337
+ 32k char cap, survives session resume.
315
338
 
316
339
  **`@path/to/file` — inline a file under "Referenced files."** Start
317
340
  typing `@` and a picker appears (↑/↓ navigate, Tab/Enter to insert).
@@ -578,6 +601,9 @@ rendering, retries.
578
601
 
579
602
  ## Commands inside the session
580
603
 
604
+ <details>
605
+ <summary><strong>Slash command reference</strong> (click to expand)</summary>
606
+
581
607
  **Core**
582
608
 
583
609
  | command | what it does |
@@ -654,6 +680,8 @@ rendering, retries.
654
680
  in-flight tool, rejects pending MCP requests)
655
681
  - `y` / `n` on confirm prompts — hotkey accept / reject
656
682
 
683
+ </details>
684
+
657
685
  ---
658
686
 
659
687
  ## Sessions and safety nets
@@ -755,6 +783,9 @@ Supported transports: **stdio** (local command) and **HTTP+SSE**
755
783
 
756
784
  ## CLI reference
757
785
 
786
+ <details>
787
+ <summary><strong>Commands, flags, env vars</strong> (click to expand)</summary>
788
+
758
789
  ```bash
759
790
  npx reasonix code [path] # coding mode scoped to path (default: cwd)
760
791
  npx reasonix # chat (uses saved config)
@@ -794,10 +825,16 @@ export REASONIX_SEARCH=off # disable web_search / web_fetch
794
825
  export REASONIX_UI=plain # disable live rows (ghosting workaround)
795
826
  ```
796
827
 
828
+ </details>
829
+
797
830
  ---
798
831
 
799
832
  ## Library usage
800
833
 
834
+ <details>
835
+ <summary><strong>Programmatic API — embed reasonix in your own Node project</strong> (click to expand)</summary>
836
+
837
+
801
838
  ```ts
802
839
  import {
803
840
  CacheFirstLoop,
@@ -842,6 +879,8 @@ callers who want the `reasonix code` loop wiring without the CLI
842
879
  wrapper. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for
843
880
  internals.
844
881
 
882
+ </details>
883
+
845
884
  ---
846
885
 
847
886
  ## Benchmarks — verify the cache-hit claim yourself
@@ -865,9 +904,9 @@ variable prefix stability:
865
904
 
866
905
  | metric | baseline (cache-hostile) | Reasonix | delta |
867
906
  |---|---:|---:|---:|
868
- | cache hit | 46.6% | **94.4%** | +47.7 pp |
869
- | cost / task | $0.002599 | $0.001579 | **−39%** |
870
- | pass rate | 96% (23/24) | **100% (24/24)** | — |
907
+ | cache hit | 32.8% | **90.2%** | +57.4 pp |
908
+ | cost / task | $0.000992 | $0.000593 | **−40%** |
909
+ | pass rate | 100% (24/24) | **100% (24/24)** | — |
871
910
 
872
911
  **Reproduce without spending an API credit:**
873
912
 
package/README.zh-CN.md CHANGED
@@ -69,7 +69,26 @@ Windows Terminal)。任何时候按 `Esc` 中断;`/help` 查看完整命令
69
69
 
70
70
  ---
71
71
 
72
- ## Web 控制台 *(0.12 新功能)*
72
+ ## 一览
73
+
74
+ | | Reasonix | Claude Code | Cursor | Aider |
75
+ |----------------------------------|----------------|----------------|--------------------|-----------|
76
+ | 后端 | DeepSeek V4 | Anthropic | OpenAI / Anthropic | 任意 |
77
+ | 单任务成本 | **~$0.001–0.005** | ~$0.05–0.50 | $20/月 + 用量 | 不一 |
78
+ | 运行环境 | 终端 | 终端 + IDE | IDE (Electron) | 终端 |
79
+ | 协议 | **MIT** | 闭源 | 闭源 | Apache 2 |
80
+ | DeepSeek 前缀缓存命中率 | **90.2%** | n/a | n/a | ~33% |
81
+ | 编辑可审 (不自动落盘) | **支持** (`/apply`) | 支持 | 部分 | 支持 |
82
+ | MCP servers | **一等公民** | 一等公民 | — | — |
83
+
84
+ 数据来自 `benchmarks/tau-bench-lite`(8 个多轮工具任务 × 3 次重放,
85
+ 真实 `deepseek-chat`)。同一负载、唯一变量是前缀稳定性 ——
86
+ 完整 transcript 提交在 [`benchmarks/`](./benchmarks/) 目录里。详细对比
87
+ 见[下方](#为什么选-reasonixvs-cursor--claude-code--cline--aider)。
88
+
89
+ ---
90
+
91
+ ## Web 控制台
73
92
 
74
93
  会话内输入 `/dashboard`,Reasonix 会打印一个带一次性 token 的本地 URL。
75
94
  浏览器打开后是一个 13 个面板的控制面 —— 与正在运行的 TUI 实时双向同步:
@@ -95,9 +114,8 @@ TUI 继续可用 —— 弹窗(shell 确认、plan 审阅、edit 闸门)会
95
114
  - **成本节省落到账单上。** DeepSeek V4 的 token 单价大约是 Claude Sonnet
96
115
  的 1/30。光便宜还不够 —— *便宜的 token 配上 90%+ 的前缀缓存命中*才是关键。
97
116
  Reasonix 的循环按 append-only 增长设计,缓存稳定的前缀在每次工具调用之间
98
- 都活着,下面的 benchmark 章节端到端验证过:实测 94.4% 缓存命中,对照组通用
99
- 框架只有 46.6%。`/stats` 面板每轮都跟踪 "vs Claude Sonnet 4.6" 的节省额,
100
- 你可以亲眼看着账单不涨。
117
+ 都活着,下面的 benchmark 章节端到端验证过:实测 90.2% 缓存命中,对照组通用
118
+ 框架只有 32.8%。`/stats` 面板每轮都跟踪 "vs Claude Sonnet 4.6" 的节省额。
101
119
 
102
120
  - **它住在终端里。** 纯 CLI —— 没有 Electron,没有 VS Code 插件,没有要
103
121
  塞进编辑器的 IDE 插件。和 git、tmux、shell 历史并排。macOS / Linux /
@@ -123,6 +141,9 @@ TUI 继续可用 —— 弹窗(shell 确认、plan 审阅、edit 闸门)会
123
141
  | 跨会话成本面板 | **支持**(`/stats`)| — | — | — | — |
124
142
  | 沙箱边界强制 | **严格**(拒绝 `..` 逃逸)| 支持 | 部分 | 支持 | 部分 |
125
143
 
144
+ <details>
145
+ <summary><strong>什么时候 reasonix 不适合 · DeepSeek/Anthropic-compat 细节 · vs Aider/Cline/Continue</strong></summary>
146
+
126
147
  ### 这些情况下应该选别的
127
148
 
128
149
  - **你想要多模型混用**(在一个工具里同时切 Claude / GPT / Gemini / 本地 Llama)。
@@ -175,9 +196,9 @@ loop 的 invariant 跟它现在对话的缓存机制不匹配。
175
196
 
176
197
  Reasonix 的 loop 从第一行起就是按 byte-stable prefix 的不变量设计的。没有
177
198
  marker、没有断点 —— append-only 就是 invariant。这就是为什么同一份 τ-bench
178
- 负载在 Reasonix 上是 **94.4% 缓存命中**、在 cache-hostile baseline 上是
179
- **46.6%**(已 commit 的 transcript,见下面 benchmark 段)。按 DeepSeek 的
180
- 单价 —— $0.07/Mtok 非缓存、约 $0.014/Mtok 缓存命中 —— 50% 和 94% 命中之间
199
+ 负载在 Reasonix 上是 **90.2% 缓存命中**、在 cache-hostile baseline 上是
200
+ **32.8%**(已 commit 的 transcript,见下面 benchmark 段)。按 DeepSeek 的
201
+ 单价 —— $0.07/Mtok 非缓存、约 $0.014/Mtok 缓存命中 —— 33% 和 90% 命中之间
181
202
  **仅 input 这一侧就大约是 2.5× 的差距**。
182
203
 
183
204
  ### "那 Aider / Cline / Continue 呢?"
@@ -200,6 +221,8 @@ byte-stability 的通用压缩 / 摘要模式。命中率落在和 baseline 同
200
221
  > 不是"又一个 agent CLI",是**围绕 DeepSeek 具体的缓存机制和定价模型
201
222
  > 设计的 agent CLI**。
202
223
 
224
+ </details>
225
+
203
226
  ---
204
227
 
205
228
  ## `reasonix code` — 终端里的结对编程
@@ -281,7 +304,7 @@ assistant
281
304
  ```
282
305
 
283
306
  无白名单门 —— 用户主动输入的 shell 命令就是显式同意。60 秒超时、32k 字符
284
- 上限,0.5.14 起会话恢复后依然保留。
307
+ 上限,会话恢复后依然保留。
285
308
 
286
309
  **`@path/to/file` —— 内联一个文件作为 "Referenced files"。** 输入 `@`
287
310
  弹出选择器(↑/↓ 切换、Tab/Enter 插入)。比让模型 `read_file` 后再问更省事:
@@ -517,6 +540,9 @@ MCP 工具走和原生工具一样的 Cache-First + 修复 + 上下文安全管
517
540
 
518
541
  ## 会话内命令
519
542
 
543
+ <details>
544
+ <summary><strong>斜杠命令清单</strong>(点击展开)</summary>
545
+
520
546
  **核心**
521
547
 
522
548
  | 命令 | 作用 |
@@ -592,6 +618,8 @@ MCP 工具走和原生工具一样的 Cache-First + 修复 + 上下文安全管
592
618
  - `Esc` —— 中断当前轮(停 API 调用、取消进行中的工具、拒绝待响应的 MCP 请求)
593
619
  - 确认弹窗里的 `y` / `n` —— 快捷接受 / 拒绝
594
620
 
621
+ </details>
622
+
595
623
  ---
596
624
 
597
625
  ## 会话与安全网
@@ -680,6 +708,9 @@ spec)。
680
708
 
681
709
  ## CLI 参考
682
710
 
711
+ <details>
712
+ <summary><strong>命令、flags、环境变量</strong>(点击展开)</summary>
713
+
683
714
  ```bash
684
715
  npx reasonix code [path] # 编程模式,作用域 path(默认 cwd)
685
716
  npx reasonix # 聊天(用已保存配置)
@@ -719,10 +750,16 @@ export REASONIX_SEARCH=off # 关闭 web_search / web_fetch
719
750
  export REASONIX_UI=plain # 关闭实时行(鬼影绕开)
720
751
  ```
721
752
 
753
+ </details>
754
+
722
755
  ---
723
756
 
724
757
  ## 库用法
725
758
 
759
+ <details>
760
+ <summary><strong>程序化 API —— 在自己的 Node 项目里嵌入 reasonix</strong>(点击展开)</summary>
761
+
762
+
726
763
  ```ts
727
764
  import {
728
765
  CacheFirstLoop,
@@ -766,6 +803,8 @@ console.log(loop.stats.summary());
766
803
  `reasonix code` 的 loop 接线、不要 CLI 包装。详见
767
804
  [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)。
768
805
 
806
+ </details>
807
+
769
808
  ---
770
809
 
771
810
  ## 性能对比 —— 缓存命中率自己也能验证
@@ -787,9 +826,9 @@ console.log(loop.stats.summary());
787
826
 
788
827
  | 指标 | 基线(缓存敌对) | Reasonix | 差值 |
789
828
  |---|---:|---:|---:|
790
- | 缓存命中 | 46.6% | **94.4%** | +47.7 pp |
791
- | 单任务成本 | $0.002599 | $0.001579 | **−39%** |
792
- | 通过率 | 96% (23/24) | **100% (24/24)** | — |
829
+ | 缓存命中 | 32.8% | **90.2%** | +57.4 pp |
830
+ | 单任务成本 | $0.000992 | $0.000593 | **−40%** |
831
+ | 通过率 | 100% (24/24) | **100% (24/24)** | — |
793
832
 
794
833
  **无需消耗 API 额度即可复现:**
795
834
 
@@ -4,6 +4,24 @@
4
4
  import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
5
5
  import { join as join4 } from "path";
6
6
 
7
+ // src/memory/user.ts
8
+ import { createHash } from "crypto";
9
+ import {
10
+ existsSync as existsSync3,
11
+ mkdirSync,
12
+ readFileSync as readFileSync3,
13
+ readdirSync as readdirSync2,
14
+ unlinkSync,
15
+ writeFileSync
16
+ } from "fs";
17
+ import { homedir as homedir2 } from "os";
18
+ import { join as join3, resolve as resolve2 } from "path";
19
+
20
+ // src/skills.ts
21
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
22
+ import { homedir } from "os";
23
+ import { join, resolve } from "path";
24
+
7
25
  // src/prompt-fragments.ts
8
26
  var TUI_FORMATTING_RULES = `Formatting (rendered in a TUI with a real markdown renderer):
9
27
  - Tabular data \u2192 GitHub-Flavored Markdown tables with ASCII pipes (\`| col | col |\` header + \`| --- | --- |\` separator). Never use Unicode box-drawing characters (\u2502 \u2500 \u253C \u250C \u2510 \u2514 \u2518 \u251C \u2524) \u2014 they look intentional but break terminal word-wrap and render as garbled columns at narrow widths.
@@ -28,66 +46,7 @@ If you have a search tool (\`search_content\`, \`grep\`, web search), call it FI
28
46
 
29
47
  If you have no search tool, qualify hard: "I haven't verified \u2014 this is a guess." Never assert absence with fake authority.`;
30
48
 
31
- // src/user-memory.ts
32
- import { createHash } from "crypto";
33
- import {
34
- existsSync as existsSync3,
35
- mkdirSync,
36
- readFileSync as readFileSync3,
37
- readdirSync as readdirSync2,
38
- unlinkSync,
39
- writeFileSync
40
- } from "fs";
41
- import { homedir as homedir2 } from "os";
42
- import { join as join3, resolve as resolve2 } from "path";
43
-
44
- // src/project-memory.ts
45
- import { existsSync, readFileSync } from "fs";
46
- import { join } from "path";
47
- var PROJECT_MEMORY_FILE = "REASONIX.md";
48
- var PROJECT_MEMORY_MAX_CHARS = 8e3;
49
- function readProjectMemory(rootDir) {
50
- const path = join(rootDir, PROJECT_MEMORY_FILE);
51
- if (!existsSync(path)) return null;
52
- let raw;
53
- try {
54
- raw = readFileSync(path, "utf8");
55
- } catch {
56
- return null;
57
- }
58
- const trimmed = raw.trim();
59
- if (!trimmed) return null;
60
- const originalChars = trimmed.length;
61
- const truncated = originalChars > PROJECT_MEMORY_MAX_CHARS;
62
- const content = truncated ? `${trimmed.slice(0, PROJECT_MEMORY_MAX_CHARS)}
63
- \u2026 (truncated ${originalChars - PROJECT_MEMORY_MAX_CHARS} chars)` : trimmed;
64
- return { path, content, originalChars, truncated };
65
- }
66
- function memoryEnabled() {
67
- const env = process.env.REASONIX_MEMORY;
68
- if (env === "off" || env === "false" || env === "0") return false;
69
- return true;
70
- }
71
- function applyProjectMemory(basePrompt, rootDir) {
72
- if (!memoryEnabled()) return basePrompt;
73
- const mem = readProjectMemory(rootDir);
74
- if (!mem) return basePrompt;
75
- return `${basePrompt}
76
-
77
- # Project memory (REASONIX.md)
78
-
79
- The user pinned these notes about this project \u2014 treat them as authoritative context for every turn:
80
-
81
- \`\`\`
82
- ${mem.content}
83
- \`\`\`
84
- `;
85
- }
86
-
87
49
  // src/skills.ts
88
- import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
89
- import { homedir } from "os";
90
- import { join as join2, resolve } from "path";
91
50
  var SKILLS_DIRNAME = "skills";
92
51
  var SKILL_FILE = "SKILL.md";
93
52
  var SKILLS_INDEX_MAX_CHARS = 4e3;
@@ -130,18 +89,18 @@ var SkillStore = class {
130
89
  const out = [];
131
90
  if (this.projectRoot) {
132
91
  out.push({
133
- dir: join2(this.projectRoot, ".reasonix", SKILLS_DIRNAME),
92
+ dir: join(this.projectRoot, ".reasonix", SKILLS_DIRNAME),
134
93
  scope: "project"
135
94
  });
136
95
  }
137
- out.push({ dir: join2(this.homeDir, ".reasonix", SKILLS_DIRNAME), scope: "global" });
96
+ out.push({ dir: join(this.homeDir, ".reasonix", SKILLS_DIRNAME), scope: "global" });
138
97
  return out;
139
98
  }
140
99
  /** Higher-priority root wins on collision (project > global > builtin); sorted for stable prefix hash. */
141
100
  list() {
142
101
  const byName = /* @__PURE__ */ new Map();
143
102
  for (const { dir, scope } of this.roots()) {
144
- if (!existsSync2(dir)) continue;
103
+ if (!existsSync(dir)) continue;
145
104
  let entries;
146
105
  try {
147
106
  entries = readdirSync(dir, { withFileTypes: true });
@@ -165,13 +124,13 @@ var SkillStore = class {
165
124
  read(name) {
166
125
  if (!isValidSkillName(name)) return null;
167
126
  for (const { dir, scope } of this.roots()) {
168
- if (!existsSync2(dir)) continue;
169
- const dirCandidate = join2(dir, name, SKILL_FILE);
170
- if (existsSync2(dirCandidate) && statSync(dirCandidate).isFile()) {
127
+ if (!existsSync(dir)) continue;
128
+ const dirCandidate = join(dir, name, SKILL_FILE);
129
+ if (existsSync(dirCandidate) && statSync(dirCandidate).isFile()) {
171
130
  return this.parse(dirCandidate, name, scope);
172
131
  }
173
- const flatCandidate = join2(dir, `${name}.md`);
174
- if (existsSync2(flatCandidate) && statSync(flatCandidate).isFile()) {
132
+ const flatCandidate = join(dir, `${name}.md`);
133
+ if (existsSync(flatCandidate) && statSync(flatCandidate).isFile()) {
175
134
  return this.parse(flatCandidate, name, scope);
176
135
  }
177
136
  }
@@ -185,21 +144,21 @@ var SkillStore = class {
185
144
  readEntry(dir, scope, entry) {
186
145
  if (entry.isDirectory()) {
187
146
  if (!isValidSkillName(entry.name)) return null;
188
- const file = join2(dir, entry.name, SKILL_FILE);
189
- if (!existsSync2(file)) return null;
147
+ const file = join(dir, entry.name, SKILL_FILE);
148
+ if (!existsSync(file)) return null;
190
149
  return this.parse(file, entry.name, scope);
191
150
  }
192
151
  if (entry.isFile() && entry.name.endsWith(".md")) {
193
152
  const stem = entry.name.slice(0, -3);
194
153
  if (!isValidSkillName(stem)) return null;
195
- return this.parse(join2(dir, entry.name), stem, scope);
154
+ return this.parse(join(dir, entry.name), stem, scope);
196
155
  }
197
156
  return null;
198
157
  }
199
158
  parse(path, stem, scope) {
200
159
  let raw;
201
160
  try {
202
- raw = readFileSync2(path, "utf8");
161
+ raw = readFileSync(path, "utf8");
203
162
  } catch {
204
163
  return null;
205
164
  }
@@ -434,7 +393,50 @@ var BUILTIN_SKILLS = Object.freeze([
434
393
  })
435
394
  ]);
436
395
 
437
- // src/user-memory.ts
396
+ // src/memory/project.ts
397
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
398
+ import { join as join2 } from "path";
399
+ var PROJECT_MEMORY_FILE = "REASONIX.md";
400
+ var PROJECT_MEMORY_MAX_CHARS = 8e3;
401
+ function readProjectMemory(rootDir) {
402
+ const path = join2(rootDir, PROJECT_MEMORY_FILE);
403
+ if (!existsSync2(path)) return null;
404
+ let raw;
405
+ try {
406
+ raw = readFileSync2(path, "utf8");
407
+ } catch {
408
+ return null;
409
+ }
410
+ const trimmed = raw.trim();
411
+ if (!trimmed) return null;
412
+ const originalChars = trimmed.length;
413
+ const truncated = originalChars > PROJECT_MEMORY_MAX_CHARS;
414
+ const content = truncated ? `${trimmed.slice(0, PROJECT_MEMORY_MAX_CHARS)}
415
+ \u2026 (truncated ${originalChars - PROJECT_MEMORY_MAX_CHARS} chars)` : trimmed;
416
+ return { path, content, originalChars, truncated };
417
+ }
418
+ function memoryEnabled() {
419
+ const env = process.env.REASONIX_MEMORY;
420
+ if (env === "off" || env === "false" || env === "0") return false;
421
+ return true;
422
+ }
423
+ function applyProjectMemory(basePrompt, rootDir) {
424
+ if (!memoryEnabled()) return basePrompt;
425
+ const mem = readProjectMemory(rootDir);
426
+ if (!mem) return basePrompt;
427
+ return `${basePrompt}
428
+
429
+ # Project memory (REASONIX.md)
430
+
431
+ The user pinned these notes about this project \u2014 treat them as authoritative context for every turn:
432
+
433
+ \`\`\`
434
+ ${mem.content}
435
+ \`\`\`
436
+ `;
437
+ }
438
+
439
+ // src/memory/user.ts
438
440
  var USER_MEMORY_DIR = "memory";
439
441
  var MEMORY_INDEX_FILE = "MEMORY.md";
440
442
  var MEMORY_INDEX_MAX_CHARS = 4e3;
@@ -971,4 +973,4 @@ export {
971
973
  CODE_SYSTEM_PROMPT,
972
974
  codeSystemPrompt
973
975
  };
974
- //# sourceMappingURL=chunk-7546PPEL.js.map
976
+ //# sourceMappingURL=chunk-3ALFOYE6.js.map