reasonix 0.4.19 → 0.4.20

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
@@ -209,8 +209,53 @@ EOF
209
209
 
210
210
  Re-launch (or `/new`) to pick it up; the prefix is hashed once per
211
211
  session to keep the DeepSeek cache warm. `/memory` prints what's
212
- currently pinned. `REASONIX_MEMORY=off` disables the lookup for CI /
213
- offline repro.
212
+ currently pinned. `REASONIX_MEMORY=off` disables every memory source
213
+ (REASONIX.md + `~/.reasonix/memory/`) for CI / offline repro.
214
+
215
+ ### User memory — `~/.reasonix/memory/`
216
+
217
+ A second, **private per-user** memory layer lives under your home
218
+ directory. Unlike `REASONIX.md` it's never committed, and the model
219
+ can write to it itself via the `remember` tool. Two scopes:
220
+
221
+ - `~/.reasonix/memory/global/` — cross-project (your preferences, tooling).
222
+ - `~/.reasonix/memory/<project-hash>/` — scoped to one sandbox root in
223
+ `reasonix code` (decisions, local facts, per-repo shortcuts).
224
+
225
+ Each scope keeps an always-loaded `MEMORY.md` index of one-liners plus
226
+ zero or more `<name>.md` detail files (loaded on demand via
227
+ `recall_memory`). Writes land immediately; pinning into the system
228
+ prompt takes effect on next `/new` or launch so the cache prefix stays
229
+ stable for the current session.
230
+
231
+ ```
232
+ reasonix code › 我用 bun 而不是 npm,请以后都用 bun 跑构建
233
+
234
+ assistant
235
+ ▸ tool<remember> → project/bun_build saved
236
+ "Build command on this machine is `bun run build`"
237
+
238
+ reasonix code › /memory list
239
+ project/project bun_build Build command on this machine is `bun run build`
240
+ ```
241
+
242
+ **Slash commands:**
243
+
244
+ - `/memory` — show everything pinned (REASONIX.md + both MEMORY.md scopes)
245
+ - `/memory list` — every memory file with scope + type + description
246
+ - `/memory show <name>` — dump one file's full body
247
+ - `/memory forget <name>` — delete one memory (no LLM turn)
248
+ - `/memory clear <global|project> confirm` — wipe a scope (typed literal)
249
+
250
+ **Model tools** (available in every session):
251
+
252
+ - `remember(type, scope, name, description, content)` — save a memory
253
+ - `forget(scope, name)` — delete it
254
+ - `recall_memory(scope, name)` — read the full body when the one-liner isn't enough
255
+
256
+ Project scope is only available inside `reasonix code` (there has to be
257
+ a real sandbox root to hash); plain `reasonix` / `reasonix chat` gets
258
+ the global scope only.
214
259
 
215
260
  ```bash
216
261
  npx reasonix code src/ # narrower sandbox (only src/ is writable)
@@ -0,0 +1,445 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/code/prompt.ts
4
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
5
+ import { join as join3 } from "path";
6
+
7
+ // src/user-memory.ts
8
+ import { createHash } from "crypto";
9
+ import {
10
+ existsSync as existsSync2,
11
+ mkdirSync,
12
+ readFileSync as readFileSync2,
13
+ readdirSync,
14
+ unlinkSync,
15
+ writeFileSync
16
+ } from "fs";
17
+ import { homedir } from "os";
18
+ import { join as join2, resolve } from "path";
19
+
20
+ // src/project-memory.ts
21
+ import { existsSync, readFileSync } from "fs";
22
+ import { join } from "path";
23
+ var PROJECT_MEMORY_FILE = "REASONIX.md";
24
+ var PROJECT_MEMORY_MAX_CHARS = 8e3;
25
+ function readProjectMemory(rootDir) {
26
+ const path = join(rootDir, PROJECT_MEMORY_FILE);
27
+ if (!existsSync(path)) return null;
28
+ let raw;
29
+ try {
30
+ raw = readFileSync(path, "utf8");
31
+ } catch {
32
+ return null;
33
+ }
34
+ const trimmed = raw.trim();
35
+ if (!trimmed) return null;
36
+ const originalChars = trimmed.length;
37
+ const truncated = originalChars > PROJECT_MEMORY_MAX_CHARS;
38
+ const content = truncated ? `${trimmed.slice(0, PROJECT_MEMORY_MAX_CHARS)}
39
+ \u2026 (truncated ${originalChars - PROJECT_MEMORY_MAX_CHARS} chars)` : trimmed;
40
+ return { path, content, originalChars, truncated };
41
+ }
42
+ function memoryEnabled() {
43
+ const env = process.env.REASONIX_MEMORY;
44
+ if (env === "off" || env === "false" || env === "0") return false;
45
+ return true;
46
+ }
47
+ function applyProjectMemory(basePrompt, rootDir) {
48
+ if (!memoryEnabled()) return basePrompt;
49
+ const mem = readProjectMemory(rootDir);
50
+ if (!mem) return basePrompt;
51
+ return `${basePrompt}
52
+
53
+ # Project memory (REASONIX.md)
54
+
55
+ The user pinned these notes about this project \u2014 treat them as authoritative context for every turn:
56
+
57
+ \`\`\`
58
+ ${mem.content}
59
+ \`\`\`
60
+ `;
61
+ }
62
+
63
+ // src/user-memory.ts
64
+ var USER_MEMORY_DIR = "memory";
65
+ var MEMORY_INDEX_FILE = "MEMORY.md";
66
+ var MEMORY_INDEX_MAX_CHARS = 4e3;
67
+ var VALID_NAME = /^[a-zA-Z0-9_-][a-zA-Z0-9_.-]{1,38}[a-zA-Z0-9]$/;
68
+ function sanitizeMemoryName(raw) {
69
+ const trimmed = String(raw ?? "").trim();
70
+ if (!VALID_NAME.test(trimmed)) {
71
+ throw new Error(
72
+ `invalid memory name: ${JSON.stringify(raw)} \u2014 must be 3-40 chars, alnum/_/-, no path separators`
73
+ );
74
+ }
75
+ return trimmed;
76
+ }
77
+ function projectHash(rootDir) {
78
+ const abs = resolve(rootDir);
79
+ return createHash("sha1").update(abs).digest("hex").slice(0, 16);
80
+ }
81
+ function scopeDir(opts) {
82
+ if (opts.scope === "global") {
83
+ return join2(opts.homeDir, USER_MEMORY_DIR, "global");
84
+ }
85
+ if (!opts.projectRoot) {
86
+ throw new Error("scope=project requires a projectRoot on MemoryStore");
87
+ }
88
+ return join2(opts.homeDir, USER_MEMORY_DIR, projectHash(opts.projectRoot));
89
+ }
90
+ function ensureDir(p) {
91
+ if (!existsSync2(p)) mkdirSync(p, { recursive: true });
92
+ }
93
+ function parseFrontmatter(raw) {
94
+ const lines = raw.split(/\r?\n/);
95
+ if (lines[0] !== "---") return { data: {}, body: raw };
96
+ const end = lines.indexOf("---", 1);
97
+ if (end < 0) return { data: {}, body: raw };
98
+ const data = {};
99
+ for (let i = 1; i < end; i++) {
100
+ const line = lines[i];
101
+ if (!line) continue;
102
+ const m = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*)$/);
103
+ if (m?.[1]) data[m[1]] = (m[2] ?? "").trim();
104
+ }
105
+ return {
106
+ data,
107
+ body: lines.slice(end + 1).join("\n").replace(/^\n+/, "")
108
+ };
109
+ }
110
+ function formatFrontmatter(e) {
111
+ return [
112
+ "---",
113
+ `name: ${e.name}`,
114
+ `description: ${e.description.replace(/\n/g, " ")}`,
115
+ `type: ${e.type}`,
116
+ `scope: ${e.scope}`,
117
+ `created: ${e.createdAt}`,
118
+ "---",
119
+ ""
120
+ ].join("\n");
121
+ }
122
+ function todayIso() {
123
+ const d = /* @__PURE__ */ new Date();
124
+ return d.toISOString().slice(0, 10);
125
+ }
126
+ function indexLine(e) {
127
+ const safeDesc = e.description.replace(/\n/g, " ").trim();
128
+ const max = 130 - e.name.length;
129
+ const clipped = safeDesc.length > max ? `${safeDesc.slice(0, Math.max(1, max - 1))}\u2026` : safeDesc;
130
+ return `- [${e.name}](${e.name}.md) \u2014 ${clipped}`;
131
+ }
132
+ var MemoryStore = class {
133
+ homeDir;
134
+ projectRoot;
135
+ constructor(opts = {}) {
136
+ this.homeDir = opts.homeDir ?? join2(homedir(), ".reasonix");
137
+ this.projectRoot = opts.projectRoot ? resolve(opts.projectRoot) : void 0;
138
+ }
139
+ /** Directory this store writes `scope` files into, creating it if needed. */
140
+ dir(scope) {
141
+ const d = scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot });
142
+ ensureDir(d);
143
+ return d;
144
+ }
145
+ /** Absolute path to a memory file (no existence check). */
146
+ pathFor(scope, name) {
147
+ return join2(this.dir(scope), `${sanitizeMemoryName(name)}.md`);
148
+ }
149
+ /** True iff this store is configured with a project scope available. */
150
+ hasProjectScope() {
151
+ return this.projectRoot !== void 0;
152
+ }
153
+ /**
154
+ * Read the `MEMORY.md` index for a scope. Returns post-cap content
155
+ * (with a truncation marker if clipped), or `null` when absent / empty.
156
+ */
157
+ loadIndex(scope) {
158
+ if (scope === "project" && !this.projectRoot) return null;
159
+ const file = join2(
160
+ scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot }),
161
+ MEMORY_INDEX_FILE
162
+ );
163
+ if (!existsSync2(file)) return null;
164
+ let raw;
165
+ try {
166
+ raw = readFileSync2(file, "utf8");
167
+ } catch {
168
+ return null;
169
+ }
170
+ const trimmed = raw.trim();
171
+ if (!trimmed) return null;
172
+ const originalChars = trimmed.length;
173
+ const truncated = originalChars > MEMORY_INDEX_MAX_CHARS;
174
+ const content = truncated ? `${trimmed.slice(0, MEMORY_INDEX_MAX_CHARS)}
175
+ \u2026 (truncated ${originalChars - MEMORY_INDEX_MAX_CHARS} chars)` : trimmed;
176
+ return { content, originalChars, truncated };
177
+ }
178
+ /** Read one memory file's body (frontmatter stripped). Throws if missing. */
179
+ read(scope, name) {
180
+ const file = this.pathFor(scope, name);
181
+ if (!existsSync2(file)) {
182
+ throw new Error(`memory not found: scope=${scope} name=${name}`);
183
+ }
184
+ const raw = readFileSync2(file, "utf8");
185
+ const { data, body } = parseFrontmatter(raw);
186
+ return {
187
+ name: data.name ?? name,
188
+ type: data.type ?? "project",
189
+ scope: data.scope ?? scope,
190
+ description: data.description ?? "",
191
+ body: body.trim(),
192
+ createdAt: data.created ?? ""
193
+ };
194
+ }
195
+ /**
196
+ * List every memory in this store. Scans both scopes (skips project
197
+ * scope if unconfigured). Silently skips malformed files; the index
198
+ * must stay queryable even if one file is hand-edited into nonsense.
199
+ */
200
+ list() {
201
+ const out = [];
202
+ const scopes = this.projectRoot ? ["global", "project"] : ["global"];
203
+ for (const scope of scopes) {
204
+ const dir = scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot });
205
+ if (!existsSync2(dir)) continue;
206
+ let entries;
207
+ try {
208
+ entries = readdirSync(dir);
209
+ } catch {
210
+ continue;
211
+ }
212
+ for (const entry of entries) {
213
+ if (entry === MEMORY_INDEX_FILE) continue;
214
+ if (!entry.endsWith(".md")) continue;
215
+ const name = entry.slice(0, -3);
216
+ try {
217
+ out.push(this.read(scope, name));
218
+ } catch {
219
+ }
220
+ }
221
+ }
222
+ return out;
223
+ }
224
+ /**
225
+ * Write a new memory (or overwrite existing). Creates the scope dir,
226
+ * writes the `.md` file, and regenerates `MEMORY.md`. Returns the
227
+ * absolute path written to.
228
+ */
229
+ write(input) {
230
+ if (input.scope === "project" && !this.projectRoot) {
231
+ throw new Error("cannot write project-scoped memory: no projectRoot configured");
232
+ }
233
+ const name = sanitizeMemoryName(input.name);
234
+ const desc = String(input.description ?? "").trim();
235
+ if (!desc) throw new Error("memory description cannot be empty");
236
+ const body = String(input.body ?? "").trim();
237
+ if (!body) throw new Error("memory body cannot be empty");
238
+ const entry = {
239
+ ...input,
240
+ name,
241
+ description: desc,
242
+ body,
243
+ createdAt: todayIso()
244
+ };
245
+ const dir = this.dir(input.scope);
246
+ const file = join2(dir, `${name}.md`);
247
+ const content = `${formatFrontmatter(entry)}${body}
248
+ `;
249
+ writeFileSync(file, content, "utf8");
250
+ this.regenerateIndex(input.scope);
251
+ return file;
252
+ }
253
+ /** Delete one memory + its index line. No-op if the file is already gone. */
254
+ delete(scope, rawName) {
255
+ if (scope === "project" && !this.projectRoot) {
256
+ throw new Error("cannot delete project-scoped memory: no projectRoot configured");
257
+ }
258
+ const file = this.pathFor(scope, rawName);
259
+ if (!existsSync2(file)) return false;
260
+ unlinkSync(file);
261
+ this.regenerateIndex(scope);
262
+ return true;
263
+ }
264
+ /**
265
+ * Rebuild `MEMORY.md` from the `.md` files currently in the scope dir.
266
+ * Called after every write/delete. Sorted by name for stable prefix
267
+ * hashing — two stores with the same set of files produce byte-identical
268
+ * MEMORY.md content, keeping the cache prefix reproducible.
269
+ */
270
+ regenerateIndex(scope) {
271
+ const dir = scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot });
272
+ if (!existsSync2(dir)) return;
273
+ let files;
274
+ try {
275
+ files = readdirSync(dir);
276
+ } catch {
277
+ return;
278
+ }
279
+ const mdFiles = files.filter((f) => f !== MEMORY_INDEX_FILE && f.endsWith(".md")).sort((a, b) => a.localeCompare(b));
280
+ const indexPath = join2(dir, MEMORY_INDEX_FILE);
281
+ if (mdFiles.length === 0) {
282
+ if (existsSync2(indexPath)) unlinkSync(indexPath);
283
+ return;
284
+ }
285
+ const lines = [];
286
+ for (const f of mdFiles) {
287
+ const name = f.slice(0, -3);
288
+ try {
289
+ const entry = this.read(scope, name);
290
+ lines.push(indexLine({ name: entry.name || name, description: entry.description }));
291
+ } catch {
292
+ lines.push(`- [${name}](${name}.md) \u2014 (malformed, check frontmatter)`);
293
+ }
294
+ }
295
+ writeFileSync(indexPath, `${lines.join("\n")}
296
+ `, "utf8");
297
+ }
298
+ };
299
+ function applyUserMemory(basePrompt, opts = {}) {
300
+ if (!memoryEnabled()) return basePrompt;
301
+ const store = new MemoryStore(opts);
302
+ const global = store.loadIndex("global");
303
+ const project = store.hasProjectScope() ? store.loadIndex("project") : null;
304
+ if (!global && !project) return basePrompt;
305
+ const parts = [basePrompt];
306
+ if (global) {
307
+ parts.push(
308
+ "",
309
+ "# User memory \u2014 global (~/.reasonix/memory/global/MEMORY.md)",
310
+ "",
311
+ "Cross-project facts and preferences the user has told you in prior sessions. TREAT AS AUTHORITATIVE \u2014 don't re-verify via filesystem or web. One-liners index detail files; call `recall_memory` for full bodies only when the one-liner isn't enough.",
312
+ "",
313
+ "```",
314
+ global.content,
315
+ "```"
316
+ );
317
+ }
318
+ if (project) {
319
+ parts.push(
320
+ "",
321
+ "# User memory \u2014 this project",
322
+ "",
323
+ "Per-project facts the user established in prior sessions (not committed to the repo). TREAT AS AUTHORITATIVE. Same recall pattern as global memory.",
324
+ "",
325
+ "```",
326
+ project.content,
327
+ "```"
328
+ );
329
+ }
330
+ return parts.join("\n");
331
+ }
332
+ function applyMemoryStack(basePrompt, rootDir) {
333
+ const withProject = applyProjectMemory(basePrompt, rootDir);
334
+ return applyUserMemory(withProject, { projectRoot: rootDir });
335
+ }
336
+
337
+ // src/code/prompt.ts
338
+ var CODE_SYSTEM_PROMPT = `You are Reasonix Code, a coding assistant. You have filesystem tools (read_file, write_file, list_directory, search_files, etc.) rooted at the user's working directory.
339
+
340
+ # When to propose a plan (submit_plan)
341
+
342
+ You have a \`submit_plan\` tool that shows the user a markdown plan and lets them Approve / Refine / Cancel before you execute. Use it proactively when the task is large enough to deserve a review gate:
343
+
344
+ - Multi-file refactors or renames.
345
+ - Architecture changes (moving modules, splitting / merging files, new abstractions).
346
+ - Anything where "undo" after the fact would be expensive \u2014 migrations, destructive cleanups, API shape changes.
347
+ - When the user's request is ambiguous and multiple reasonable interpretations exist \u2014 propose your reading as a plan and let them confirm.
348
+
349
+ Skip submit_plan for small, obvious changes: one-line typo, clear bug with a clear fix, adding a missing import, renaming a local variable. Just do those.
350
+
351
+ Plan body: one-sentence summary, then a file-by-file breakdown of what you'll change and why, and any risks or open questions. If some decisions are genuinely up to the user (naming, tradeoffs, out-of-scope possibilities), list them in an "Open questions" section \u2014 the user sees the plan in a picker and has a text input to answer your questions before approving. Don't pretend certainty you don't have; flagged questions are how the user tells you what they care about. After calling submit_plan, STOP \u2014 don't call any more tools, wait for the user's verdict.
352
+
353
+ # Plan mode (/plan)
354
+
355
+ The user can ALSO enter "plan mode" via /plan, which is a stronger, explicit constraint:
356
+ - Write tools (edit_file, write_file, create_directory, move_file) and non-allowlisted run_command calls are BOUNCED at dispatch \u2014 you'll get a tool result like "unavailable in plan mode". Don't retry them.
357
+ - Read tools (read_file, list_directory, search_files, directory_tree, get_file_info) and allowlisted read-only / test shell commands still work \u2014 use them to investigate.
358
+ - You MUST call submit_plan before anything will execute. Approve exits plan mode; Refine stays in; Cancel exits without implementing.
359
+
360
+
361
+ # When to edit vs. when to explore
362
+
363
+ Only propose edits when the user explicitly asks you to change, fix, add, remove, refactor, or write something. Do NOT propose edits when the user asks you to:
364
+ - analyze, read, explore, describe, or summarize a project
365
+ - explain how something works
366
+ - answer a question about the code
367
+
368
+ In those cases, use tools to gather what you need, then reply in prose. No SEARCH/REPLACE blocks, no file changes. If you're unsure what the user wants, ask.
369
+
370
+ When you do propose edits, the user will review them and decide whether to \`/apply\` or \`/discard\`. Don't assume they'll accept \u2014 write as if each edit will be audited, because it will.
371
+
372
+ # Editing files
373
+
374
+ When you've been asked to change a file, output one or more SEARCH/REPLACE blocks in this exact format:
375
+
376
+ path/to/file.ext
377
+ <<<<<<< SEARCH
378
+ exact existing lines from the file, including whitespace
379
+ =======
380
+ the new lines
381
+ >>>>>>> REPLACE
382
+
383
+ Rules:
384
+ - Always read_file first so your SEARCH matches byte-for-byte. If it doesn't match, the edit is rejected and you'll have to retry with the exact current content.
385
+ - One edit per block. Multiple blocks in one response are fine.
386
+ - To create a new file, leave SEARCH empty:
387
+ path/to/new.ts
388
+ <<<<<<< SEARCH
389
+ =======
390
+ (whole file content here)
391
+ >>>>>>> REPLACE
392
+ - Do NOT use write_file to change existing files \u2014 the user reviews your edits as SEARCH/REPLACE. write_file is only for files you explicitly want to overwrite wholesale (rare).
393
+ - Paths are relative to the working directory. Don't use absolute paths.
394
+
395
+ # Trust what you already know
396
+
397
+ Before exploring the filesystem to answer a factual question, check whether the answer is already in context: the user's current message, earlier turns in this conversation (including prior tool results from \`remember\`), and the pinned memory blocks at the top of this prompt. When the user has stated a fact or you have remembered one, it outranks what the files say \u2014 don't re-derive from code what the user already told you. Explore when you genuinely don't know.
398
+
399
+ # Exploration
400
+
401
+ - Skip dependency, build, and VCS directories unless the user explicitly asks. The pinned .gitignore block (if any, below) is your authoritative denylist.
402
+ - Prefer search_files / grep over list_directory when you know roughly what you're looking for \u2014 it saves context and avoids enumerating huge trees.
403
+
404
+ # Style
405
+
406
+ - Show edits; don't narrate them in prose. "Here's the fix:" is enough.
407
+ - One short paragraph explaining *why*, then the blocks.
408
+ - If you need to explore first (list / grep / read), do it with tool calls before writing any prose \u2014 silence while exploring is fine.
409
+ `;
410
+ function codeSystemPrompt(rootDir) {
411
+ const withMemory = applyMemoryStack(CODE_SYSTEM_PROMPT, rootDir);
412
+ const gitignorePath = join3(rootDir, ".gitignore");
413
+ if (!existsSync3(gitignorePath)) return withMemory;
414
+ let content;
415
+ try {
416
+ content = readFileSync3(gitignorePath, "utf8");
417
+ } catch {
418
+ return withMemory;
419
+ }
420
+ const MAX = 2e3;
421
+ const truncated = content.length > MAX ? `${content.slice(0, MAX)}
422
+ \u2026 (truncated ${content.length - MAX} chars)` : content;
423
+ return `${withMemory}
424
+
425
+ # Project .gitignore
426
+
427
+ The user's repo ships this .gitignore \u2014 treat every pattern as "don't traverse or edit inside these paths unless explicitly asked":
428
+
429
+ \`\`\`
430
+ ${truncated}
431
+ \`\`\`
432
+ `;
433
+ }
434
+
435
+ export {
436
+ PROJECT_MEMORY_FILE,
437
+ readProjectMemory,
438
+ memoryEnabled,
439
+ sanitizeMemoryName,
440
+ MemoryStore,
441
+ applyMemoryStack,
442
+ CODE_SYSTEM_PROMPT,
443
+ codeSystemPrompt
444
+ };
445
+ //# sourceMappingURL=chunk-DDIKQZVD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/code/prompt.ts","../../src/user-memory.ts","../../src/project-memory.ts"],"sourcesContent":["/**\n * System prompt used by `reasonix code`. Teaches the model:\n *\n * 1. It has a filesystem MCP bridge rooted at the user's CWD.\n * 2. To modify files it emits SEARCH/REPLACE blocks (not\n * `write_file` — that would whole-file rewrite and kill diff\n * reviewability).\n * 3. Read first, edit second — SEARCH must match byte-for-byte.\n * 4. Be concise. The user can read a diff faster than prose.\n *\n * Kept short on purpose. Long system prompts eat context budget that\n * the Cache-First Loop is trying to conserve. The SEARCH/REPLACE spec\n * is the one unavoidable bloat; we trim everything else.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { applyMemoryStack } from \"../user-memory.js\";\n\nexport const CODE_SYSTEM_PROMPT = `You are Reasonix Code, a coding assistant. You have filesystem tools (read_file, write_file, list_directory, search_files, etc.) rooted at the user's working directory.\n\n# When to propose a plan (submit_plan)\n\nYou have a \\`submit_plan\\` tool that shows the user a markdown plan and lets them Approve / Refine / Cancel before you execute. Use it proactively when the task is large enough to deserve a review gate:\n\n- Multi-file refactors or renames.\n- Architecture changes (moving modules, splitting / merging files, new abstractions).\n- Anything where \"undo\" after the fact would be expensive — migrations, destructive cleanups, API shape changes.\n- When the user's request is ambiguous and multiple reasonable interpretations exist — propose your reading as a plan and let them confirm.\n\nSkip submit_plan for small, obvious changes: one-line typo, clear bug with a clear fix, adding a missing import, renaming a local variable. Just do those.\n\nPlan body: one-sentence summary, then a file-by-file breakdown of what you'll change and why, and any risks or open questions. If some decisions are genuinely up to the user (naming, tradeoffs, out-of-scope possibilities), list them in an \"Open questions\" section — the user sees the plan in a picker and has a text input to answer your questions before approving. Don't pretend certainty you don't have; flagged questions are how the user tells you what they care about. After calling submit_plan, STOP — don't call any more tools, wait for the user's verdict.\n\n# Plan mode (/plan)\n\nThe user can ALSO enter \"plan mode\" via /plan, which is a stronger, explicit constraint:\n- Write tools (edit_file, write_file, create_directory, move_file) and non-allowlisted run_command calls are BOUNCED at dispatch — you'll get a tool result like \"unavailable in plan mode\". Don't retry them.\n- Read tools (read_file, list_directory, search_files, directory_tree, get_file_info) and allowlisted read-only / test shell commands still work — use them to investigate.\n- You MUST call submit_plan before anything will execute. Approve exits plan mode; Refine stays in; Cancel exits without implementing.\n\n\n# When to edit vs. when to explore\n\nOnly propose edits when the user explicitly asks you to change, fix, add, remove, refactor, or write something. Do NOT propose edits when the user asks you to:\n- analyze, read, explore, describe, or summarize a project\n- explain how something works\n- answer a question about the code\n\nIn those cases, use tools to gather what you need, then reply in prose. No SEARCH/REPLACE blocks, no file changes. If you're unsure what the user wants, ask.\n\nWhen you do propose edits, the user will review them and decide whether to \\`/apply\\` or \\`/discard\\`. Don't assume they'll accept — write as if each edit will be audited, because it will.\n\n# Editing files\n\nWhen you've been asked to change a file, output one or more SEARCH/REPLACE blocks in this exact format:\n\npath/to/file.ext\n<<<<<<< SEARCH\nexact existing lines from the file, including whitespace\n=======\nthe new lines\n>>>>>>> REPLACE\n\nRules:\n- Always read_file first so your SEARCH matches byte-for-byte. If it doesn't match, the edit is rejected and you'll have to retry with the exact current content.\n- One edit per block. Multiple blocks in one response are fine.\n- To create a new file, leave SEARCH empty:\n path/to/new.ts\n <<<<<<< SEARCH\n =======\n (whole file content here)\n >>>>>>> REPLACE\n- Do NOT use write_file to change existing files — the user reviews your edits as SEARCH/REPLACE. write_file is only for files you explicitly want to overwrite wholesale (rare).\n- Paths are relative to the working directory. Don't use absolute paths.\n\n# Trust what you already know\n\nBefore exploring the filesystem to answer a factual question, check whether the answer is already in context: the user's current message, earlier turns in this conversation (including prior tool results from \\`remember\\`), and the pinned memory blocks at the top of this prompt. When the user has stated a fact or you have remembered one, it outranks what the files say — don't re-derive from code what the user already told you. Explore when you genuinely don't know.\n\n# Exploration\n\n- Skip dependency, build, and VCS directories unless the user explicitly asks. The pinned .gitignore block (if any, below) is your authoritative denylist.\n- Prefer search_files / grep over list_directory when you know roughly what you're looking for — it saves context and avoids enumerating huge trees.\n\n# Style\n\n- Show edits; don't narrate them in prose. \"Here's the fix:\" is enough.\n- One short paragraph explaining *why*, then the blocks.\n- If you need to explore first (list / grep / read), do it with tool calls before writing any prose — silence while exploring is fine.\n`;\n\n/**\n * Inject the project's `.gitignore` content into the system prompt as a\n * \"respect this on top of the built-in denylist\" hint. We don't parse\n * the file — we hand it to the model as-is. Truncate long ones so we\n * don't eat context budget on huge generated ignore lists.\n *\n * Stacking order (stable for cache prefix):\n * base prompt → REASONIX.md → global MEMORY.md → project MEMORY.md → .gitignore\n */\nexport function codeSystemPrompt(rootDir: string): string {\n const withMemory = applyMemoryStack(CODE_SYSTEM_PROMPT, rootDir);\n const gitignorePath = join(rootDir, \".gitignore\");\n if (!existsSync(gitignorePath)) return withMemory;\n let content: string;\n try {\n content = readFileSync(gitignorePath, \"utf8\");\n } catch {\n return withMemory;\n }\n const MAX = 2000;\n const truncated =\n content.length > MAX\n ? `${content.slice(0, MAX)}\\n… (truncated ${content.length - MAX} chars)`\n : content;\n return `${withMemory}\n\n# Project .gitignore\n\nThe user's repo ships this .gitignore — treat every pattern as \"don't traverse or edit inside these paths unless explicitly asked\":\n\n\\`\\`\\`\n${truncated}\n\\`\\`\\`\n`;\n}\n","/**\n * User memory — `~/.reasonix/memory/` markdown notes pinned into the\n * immutable-prefix system prompt across sessions.\n *\n * Two scopes:\n * - `global` → `~/.reasonix/memory/global/` (cross-project)\n * - `project` → `~/.reasonix/memory/<hash>/` (per sandbox root)\n *\n * Each scope has an always-loaded `MEMORY.md` index plus zero-or-more\n * `<name>.md` detail files loaded on demand via `recall_memory`.\n *\n * Distinct from `src/project-memory.ts` (REASONIX.md) in purpose:\n * REASONIX.md is committable, team-shared project memory.\n * ~/.reasonix/memory is user-private memory, never committed.\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { applyProjectMemory, memoryEnabled } from \"./project-memory.js\";\n\nexport const USER_MEMORY_DIR = \"memory\";\nexport const MEMORY_INDEX_FILE = \"MEMORY.md\";\n/** Cap on the index file content loaded into the prefix, per scope. */\nexport const MEMORY_INDEX_MAX_CHARS = 4000;\n\nexport type MemoryType = \"user\" | \"feedback\" | \"project\" | \"reference\";\nexport type MemoryScope = \"global\" | \"project\";\n\nexport interface MemoryEntry {\n name: string;\n type: MemoryType;\n scope: MemoryScope;\n description: string;\n body: string;\n /** ISO date string (YYYY-MM-DD). */\n createdAt: string;\n}\n\nexport interface MemoryStoreOptions {\n /** Override `~/.reasonix` — tests set this to a tmpdir. */\n homeDir?: string;\n /** Absolute sandbox root. Required to use `scope: \"project\"`. */\n projectRoot?: string;\n}\n\nexport interface WriteInput {\n name: string;\n type: MemoryType;\n scope: MemoryScope;\n description: string;\n body: string;\n}\n\nconst VALID_NAME = /^[a-zA-Z0-9_-][a-zA-Z0-9_.-]{1,38}[a-zA-Z0-9]$/;\n\n/**\n * Throws on filename injection attempts (`../foo`, `foo/bar`, leading\n * dots, etc.). Allowed: 3-40 chars, alnum + `_` + `-` + interior `.`.\n */\nexport function sanitizeMemoryName(raw: string): string {\n const trimmed = String(raw ?? \"\").trim();\n if (!VALID_NAME.test(trimmed)) {\n throw new Error(\n `invalid memory name: ${JSON.stringify(raw)} — must be 3-40 chars, alnum/_/-, no path separators`,\n );\n }\n return trimmed;\n}\n\n/** Stable 16-hex-char hash of an absolute sandbox root path. */\nexport function projectHash(rootDir: string): string {\n const abs = resolve(rootDir);\n return createHash(\"sha1\").update(abs).digest(\"hex\").slice(0, 16);\n}\n\nfunction scopeDir(opts: { homeDir: string; scope: MemoryScope; projectRoot?: string }): string {\n if (opts.scope === \"global\") {\n return join(opts.homeDir, USER_MEMORY_DIR, \"global\");\n }\n if (!opts.projectRoot) {\n throw new Error(\"scope=project requires a projectRoot on MemoryStore\");\n }\n return join(opts.homeDir, USER_MEMORY_DIR, projectHash(opts.projectRoot));\n}\n\nfunction ensureDir(p: string): void {\n if (!existsSync(p)) mkdirSync(p, { recursive: true });\n}\n\n/**\n * Parse a `---` frontmatter block off the top of a markdown string.\n * Tolerates missing frontmatter, returning `{}` for data and the full\n * string as body. Only recognizes the simple `key: value` shape — no\n * quoting, no multi-line, no YAML features. Matches what we emit.\n */\nfunction parseFrontmatter(raw: string): { data: Record<string, string>; body: string } {\n const lines = raw.split(/\\r?\\n/);\n if (lines[0] !== \"---\") return { data: {}, body: raw };\n const end = lines.indexOf(\"---\", 1);\n if (end < 0) return { data: {}, body: raw };\n const data: Record<string, string> = {};\n for (let i = 1; i < end; i++) {\n const line = lines[i];\n if (!line) continue;\n const m = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\\s*(.*)$/);\n if (m?.[1]) data[m[1]] = (m[2] ?? \"\").trim();\n }\n return {\n data,\n body: lines\n .slice(end + 1)\n .join(\"\\n\")\n .replace(/^\\n+/, \"\"),\n };\n}\n\nfunction formatFrontmatter(e: WriteInput & { createdAt: string }): string {\n return [\n \"---\",\n `name: ${e.name}`,\n `description: ${e.description.replace(/\\n/g, \" \")}`,\n `type: ${e.type}`,\n `scope: ${e.scope}`,\n `created: ${e.createdAt}`,\n \"---\",\n \"\",\n ].join(\"\\n\");\n}\n\nfunction todayIso(): string {\n const d = new Date();\n return d.toISOString().slice(0, 10);\n}\n\n/**\n * A `MEMORY.md` index line for one entry. One-liner, under ~150 chars.\n * `description` is truncated if it would push past the soft limit.\n */\nfunction indexLine(e: Pick<MemoryEntry, \"name\" | \"description\">): string {\n const safeDesc = e.description.replace(/\\n/g, \" \").trim();\n const max = 130 - e.name.length;\n const clipped = safeDesc.length > max ? `${safeDesc.slice(0, Math.max(1, max - 1))}…` : safeDesc;\n return `- [${e.name}](${e.name}.md) — ${clipped}`;\n}\n\nexport class MemoryStore {\n private readonly homeDir: string;\n private readonly projectRoot: string | undefined;\n\n constructor(opts: MemoryStoreOptions = {}) {\n this.homeDir = opts.homeDir ?? join(homedir(), \".reasonix\");\n this.projectRoot = opts.projectRoot ? resolve(opts.projectRoot) : undefined;\n }\n\n /** Directory this store writes `scope` files into, creating it if needed. */\n dir(scope: MemoryScope): string {\n const d = scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot });\n ensureDir(d);\n return d;\n }\n\n /** Absolute path to a memory file (no existence check). */\n pathFor(scope: MemoryScope, name: string): string {\n return join(this.dir(scope), `${sanitizeMemoryName(name)}.md`);\n }\n\n /** True iff this store is configured with a project scope available. */\n hasProjectScope(): boolean {\n return this.projectRoot !== undefined;\n }\n\n /**\n * Read the `MEMORY.md` index for a scope. Returns post-cap content\n * (with a truncation marker if clipped), or `null` when absent / empty.\n */\n loadIndex(\n scope: MemoryScope,\n ): { content: string; originalChars: number; truncated: boolean } | null {\n if (scope === \"project\" && !this.projectRoot) return null;\n const file = join(\n scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot }),\n MEMORY_INDEX_FILE,\n );\n if (!existsSync(file)) return null;\n let raw: string;\n try {\n raw = readFileSync(file, \"utf8\");\n } catch {\n return null;\n }\n const trimmed = raw.trim();\n if (!trimmed) return null;\n const originalChars = trimmed.length;\n const truncated = originalChars > MEMORY_INDEX_MAX_CHARS;\n const content = truncated\n ? `${trimmed.slice(0, MEMORY_INDEX_MAX_CHARS)}\\n… (truncated ${originalChars - MEMORY_INDEX_MAX_CHARS} chars)`\n : trimmed;\n return { content, originalChars, truncated };\n }\n\n /** Read one memory file's body (frontmatter stripped). Throws if missing. */\n read(scope: MemoryScope, name: string): MemoryEntry {\n const file = this.pathFor(scope, name);\n if (!existsSync(file)) {\n throw new Error(`memory not found: scope=${scope} name=${name}`);\n }\n const raw = readFileSync(file, \"utf8\");\n const { data, body } = parseFrontmatter(raw);\n return {\n name: data.name ?? name,\n type: (data.type as MemoryType) ?? \"project\",\n scope: (data.scope as MemoryScope) ?? scope,\n description: data.description ?? \"\",\n body: body.trim(),\n createdAt: data.created ?? \"\",\n };\n }\n\n /**\n * List every memory in this store. Scans both scopes (skips project\n * scope if unconfigured). Silently skips malformed files; the index\n * must stay queryable even if one file is hand-edited into nonsense.\n */\n list(): MemoryEntry[] {\n const out: MemoryEntry[] = [];\n const scopes: MemoryScope[] = this.projectRoot ? [\"global\", \"project\"] : [\"global\"];\n for (const scope of scopes) {\n const dir = scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot });\n if (!existsSync(dir)) continue;\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n continue;\n }\n for (const entry of entries) {\n if (entry === MEMORY_INDEX_FILE) continue;\n if (!entry.endsWith(\".md\")) continue;\n const name = entry.slice(0, -3);\n try {\n out.push(this.read(scope, name));\n } catch {\n // malformed file — skip rather than fail the whole list\n }\n }\n }\n return out;\n }\n\n /**\n * Write a new memory (or overwrite existing). Creates the scope dir,\n * writes the `.md` file, and regenerates `MEMORY.md`. Returns the\n * absolute path written to.\n */\n write(input: WriteInput): string {\n if (input.scope === \"project\" && !this.projectRoot) {\n throw new Error(\"cannot write project-scoped memory: no projectRoot configured\");\n }\n const name = sanitizeMemoryName(input.name);\n const desc = String(input.description ?? \"\").trim();\n if (!desc) throw new Error(\"memory description cannot be empty\");\n const body = String(input.body ?? \"\").trim();\n if (!body) throw new Error(\"memory body cannot be empty\");\n const entry: WriteInput & { createdAt: string } = {\n ...input,\n name,\n description: desc,\n body,\n createdAt: todayIso(),\n };\n const dir = this.dir(input.scope);\n const file = join(dir, `${name}.md`);\n const content = `${formatFrontmatter(entry)}${body}\\n`;\n writeFileSync(file, content, \"utf8\");\n this.regenerateIndex(input.scope);\n return file;\n }\n\n /** Delete one memory + its index line. No-op if the file is already gone. */\n delete(scope: MemoryScope, rawName: string): boolean {\n if (scope === \"project\" && !this.projectRoot) {\n throw new Error(\"cannot delete project-scoped memory: no projectRoot configured\");\n }\n const file = this.pathFor(scope, rawName);\n if (!existsSync(file)) return false;\n unlinkSync(file);\n this.regenerateIndex(scope);\n return true;\n }\n\n /**\n * Rebuild `MEMORY.md` from the `.md` files currently in the scope dir.\n * Called after every write/delete. Sorted by name for stable prefix\n * hashing — two stores with the same set of files produce byte-identical\n * MEMORY.md content, keeping the cache prefix reproducible.\n */\n private regenerateIndex(scope: MemoryScope): void {\n const dir = scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot });\n if (!existsSync(dir)) return;\n let files: string[];\n try {\n files = readdirSync(dir);\n } catch {\n return;\n }\n const mdFiles = files\n .filter((f) => f !== MEMORY_INDEX_FILE && f.endsWith(\".md\"))\n .sort((a, b) => a.localeCompare(b));\n const indexPath = join(dir, MEMORY_INDEX_FILE);\n if (mdFiles.length === 0) {\n if (existsSync(indexPath)) unlinkSync(indexPath);\n return;\n }\n const lines: string[] = [];\n for (const f of mdFiles) {\n const name = f.slice(0, -3);\n try {\n const entry = this.read(scope, name);\n lines.push(indexLine({ name: entry.name || name, description: entry.description }));\n } catch {\n // Malformed: still surface it in the index so the user notices.\n lines.push(`- [${name}](${name}.md) — (malformed, check frontmatter)`);\n }\n }\n writeFileSync(indexPath, `${lines.join(\"\\n\")}\\n`, \"utf8\");\n }\n}\n\n/**\n * Append `MEMORY_GLOBAL` and (optionally) `MEMORY_PROJECT` blocks to\n * `basePrompt`. Omits a block entirely when its index is absent — an\n * empty tag would add bytes to the prefix hash without content.\n * Respects `REASONIX_MEMORY=off` via `memoryEnabled()` from\n * `project-memory.ts`.\n */\nexport function applyUserMemory(\n basePrompt: string,\n opts: { homeDir?: string; projectRoot?: string } = {},\n): string {\n if (!memoryEnabled()) return basePrompt;\n const store = new MemoryStore(opts);\n const global = store.loadIndex(\"global\");\n const project = store.hasProjectScope() ? store.loadIndex(\"project\") : null;\n if (!global && !project) return basePrompt;\n const parts: string[] = [basePrompt];\n if (global) {\n parts.push(\n \"\",\n \"# User memory — global (~/.reasonix/memory/global/MEMORY.md)\",\n \"\",\n \"Cross-project facts and preferences the user has told you in prior sessions. TREAT AS AUTHORITATIVE — don't re-verify via filesystem or web. One-liners index detail files; call `recall_memory` for full bodies only when the one-liner isn't enough.\",\n \"\",\n \"```\",\n global.content,\n \"```\",\n );\n }\n if (project) {\n parts.push(\n \"\",\n \"# User memory — this project\",\n \"\",\n \"Per-project facts the user established in prior sessions (not committed to the repo). TREAT AS AUTHORITATIVE. Same recall pattern as global memory.\",\n \"\",\n \"```\",\n project.content,\n \"```\",\n );\n }\n return parts.join(\"\\n\");\n}\n\n/**\n * Compose REASONIX.md + user memory in one call. Drop-in replacement\n * for `applyProjectMemory` at CLI entry points — preserves the existing\n * REASONIX_PROJECT block order, then appends the two user-memory blocks.\n */\nexport function applyMemoryStack(basePrompt: string, rootDir: string): string {\n const withProject = applyProjectMemory(basePrompt, rootDir);\n return applyUserMemory(withProject, { projectRoot: rootDir });\n}\n","/**\n * Project memory — a user-authored `REASONIX.md` in the project root\n * that gets pinned into the immutable-prefix system prompt.\n *\n * Design notes:\n *\n * - The file lands in `ImmutablePrefix.system`, so the whole memory\n * block is hashed into the cache prefix fingerprint. Editing the\n * file invalidates the prefix; unchanged memory across sessions\n * keeps the DeepSeek prefix cache warm. That matches Pillar 1 —\n * memory is a deliberate, stable prefix, not per-turn drift.\n * - Only one source: the working-root `REASONIX.md`. No parent walk,\n * no `~/.reasonix/REASONIX.md`, no CLAUDE.md fallback. User-global\n * memory can come later; for v1 one file == one mental model.\n * - Truncated at 8 000 chars (≈ 2k tokens). `.gitignore` gets 2 000\n * because it's a constraint dump; memory gets more headroom because\n * it's deliberate instructions.\n * - Opt-out via `REASONIX_MEMORY=off|false|0`. No CLI flag — memory\n * is a file, `rm REASONIX.md` is the other opt-out.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport const PROJECT_MEMORY_FILE = \"REASONIX.md\";\nexport const PROJECT_MEMORY_MAX_CHARS = 8000;\n\nexport interface ProjectMemory {\n /** Absolute path the memory was read from. */\n path: string;\n /** Post-truncation content (may include a \"… (truncated N chars)\" marker). */\n content: string;\n /** Original byte length before truncation. */\n originalChars: number;\n /** True iff `originalChars > PROJECT_MEMORY_MAX_CHARS`. */\n truncated: boolean;\n}\n\n/**\n * Read `REASONIX.md` from `rootDir`. Returns `null` when the file is\n * missing, unreadable, or empty (whitespace-only counts as empty — an\n * empty memory file shouldn't perturb the cache prefix).\n */\nexport function readProjectMemory(rootDir: string): ProjectMemory | null {\n const path = join(rootDir, PROJECT_MEMORY_FILE);\n if (!existsSync(path)) return null;\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n return null;\n }\n const trimmed = raw.trim();\n if (!trimmed) return null;\n const originalChars = trimmed.length;\n const truncated = originalChars > PROJECT_MEMORY_MAX_CHARS;\n const content = truncated\n ? `${trimmed.slice(0, PROJECT_MEMORY_MAX_CHARS)}\\n… (truncated ${\n originalChars - PROJECT_MEMORY_MAX_CHARS\n } chars)`\n : trimmed;\n return { path, content, originalChars, truncated };\n}\n\n/**\n * Resolve whether project memory should be read. Default: on.\n * `REASONIX_MEMORY=off|false|0` turns it off (CI, reproducing issues,\n * intentional offline runs).\n */\nexport function memoryEnabled(): boolean {\n const env = process.env.REASONIX_MEMORY;\n if (env === \"off\" || env === \"false\" || env === \"0\") return false;\n return true;\n}\n\n/**\n * Return `basePrompt` with the project's `REASONIX.md` appended as a\n * \"Project memory\" section. No-op when the file is absent, empty, or\n * memory is disabled via env.\n *\n * The appended block is deterministic — identical input ⇒ identical\n * output — so every session that opens against the same memory file\n * gets the same prefix hash.\n */\nexport function applyProjectMemory(basePrompt: string, rootDir: string): string {\n if (!memoryEnabled()) return basePrompt;\n const mem = readProjectMemory(rootDir);\n if (!mem) return basePrompt;\n return `${basePrompt}\n\n# Project memory (REASONIX.md)\n\nThe user pinned these notes about this project — treat them as authoritative context for every turn:\n\n\\`\\`\\`\n${mem.content}\n\\`\\`\\`\n`;\n}\n"],"mappings":"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","names":["existsSync","readFileSync","join","existsSync","readFileSync","join","join","existsSync","readFileSync","join","existsSync","readFileSync"]}