reasonix 0.4.17 → 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.
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  CODE_SYSTEM_PROMPT,
4
4
  codeSystemPrompt
5
- } from "./chunk-3YQRWFES.js";
5
+ } from "./chunk-DDIKQZVD.js";
6
6
  export {
7
7
  CODE_SYSTEM_PROMPT,
8
8
  codeSystemPrompt
9
9
  };
10
- //# sourceMappingURL=prompt-HK5XLH55.js.map
10
+ //# sourceMappingURL=prompt-YEJEJ3IZ.js.map
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { SpawnOptions } from 'node:child_process';
1
2
  import { WriteStream } from 'node:fs';
2
3
 
3
4
  /**
@@ -392,6 +393,14 @@ declare class ToolCallRepair {
392
393
  private readonly storm;
393
394
  private readonly opts;
394
395
  constructor(opts: ToolCallRepairOptions);
396
+ /**
397
+ * Drop the StormBreaker's sliding window of recent (name, args)
398
+ * signatures. Called at the start of every user turn — a fresh user
399
+ * message is a new intent, so carrying old repetition state into it
400
+ * would turn a valid "try again with different input" flow into a
401
+ * false-positive block.
402
+ */
403
+ resetStorm(): void;
395
404
  process(declaredCalls: ToolCall[], reasoningContent: string | null, content?: string | null): {
396
405
  calls: ToolCall[];
397
406
  report: RepairReport;
@@ -462,6 +471,25 @@ interface ToolDefinition<A = any, R = any> {
462
471
  name: string;
463
472
  description?: string;
464
473
  parameters?: JSONSchema;
474
+ /**
475
+ * Marks a tool as read-only: safe to invoke during plan mode. `true`
476
+ * for tools that only observe (read_file, list_directory, search, web
477
+ * fetch/search). Leave undefined / `false` for anything that can write,
478
+ * execute, or mutate state.
479
+ *
480
+ * The registry enforces this at dispatch: non-readonly tools called
481
+ * while `planMode` is on return a refusal string the model can
482
+ * learn from, instead of actually running.
483
+ */
484
+ readOnly?: boolean;
485
+ /**
486
+ * Dynamic read-only check for tools whose safety depends on arguments
487
+ * — `run_command` with an allowlisted argv is safe, `run_command
488
+ * rm -rf` isn't. Called with the parsed arguments; `true` means "treat
489
+ * as read-only for plan mode". Takes precedence over `readOnly` when
490
+ * both are set.
491
+ */
492
+ readOnlyCheck?: (args: A) => boolean;
465
493
  fn: (args: A, ctx?: ToolCallContext) => R | Promise<R>;
466
494
  }
467
495
  interface ToolRegistryOptions {
@@ -475,7 +503,19 @@ interface ToolRegistryOptions {
475
503
  declare class ToolRegistry {
476
504
  private readonly _tools;
477
505
  private readonly _autoFlatten;
506
+ /**
507
+ * When true, `dispatch` refuses any tool whose `readOnly` flag isn't
508
+ * set (and whose `readOnlyCheck` doesn't pass on the specific args).
509
+ * Drives `reasonix code`'s Plan Mode — the model can still explore
510
+ * via read tools but its writes and non-allowlisted shell calls are
511
+ * bounced until the user approves a submitted plan.
512
+ */
513
+ private _planMode;
478
514
  constructor(opts?: ToolRegistryOptions);
515
+ /** Enable / disable plan-mode enforcement at dispatch. */
516
+ setPlanMode(on: boolean): void;
517
+ /** True when the registry is currently refusing non-readonly calls. */
518
+ get planMode(): boolean;
479
519
  register<A, R>(def: ToolDefinition<A, R>): this;
480
520
  has(name: string): boolean;
481
521
  get(name: string): ToolDefinition | undefined;
@@ -773,6 +813,117 @@ declare function memoryEnabled(): boolean;
773
813
  */
774
814
  declare function applyProjectMemory(basePrompt: string, rootDir: string): string;
775
815
 
816
+ /**
817
+ * User memory — `~/.reasonix/memory/` markdown notes pinned into the
818
+ * immutable-prefix system prompt across sessions.
819
+ *
820
+ * Two scopes:
821
+ * - `global` → `~/.reasonix/memory/global/` (cross-project)
822
+ * - `project` → `~/.reasonix/memory/<hash>/` (per sandbox root)
823
+ *
824
+ * Each scope has an always-loaded `MEMORY.md` index plus zero-or-more
825
+ * `<name>.md` detail files loaded on demand via `recall_memory`.
826
+ *
827
+ * Distinct from `src/project-memory.ts` (REASONIX.md) in purpose:
828
+ * REASONIX.md is committable, team-shared project memory.
829
+ * ~/.reasonix/memory is user-private memory, never committed.
830
+ */
831
+ declare const USER_MEMORY_DIR = "memory";
832
+ declare const MEMORY_INDEX_FILE = "MEMORY.md";
833
+ /** Cap on the index file content loaded into the prefix, per scope. */
834
+ declare const MEMORY_INDEX_MAX_CHARS = 4000;
835
+ type MemoryType = "user" | "feedback" | "project" | "reference";
836
+ type MemoryScope = "global" | "project";
837
+ interface MemoryEntry {
838
+ name: string;
839
+ type: MemoryType;
840
+ scope: MemoryScope;
841
+ description: string;
842
+ body: string;
843
+ /** ISO date string (YYYY-MM-DD). */
844
+ createdAt: string;
845
+ }
846
+ interface MemoryStoreOptions {
847
+ /** Override `~/.reasonix` — tests set this to a tmpdir. */
848
+ homeDir?: string;
849
+ /** Absolute sandbox root. Required to use `scope: "project"`. */
850
+ projectRoot?: string;
851
+ }
852
+ interface WriteInput {
853
+ name: string;
854
+ type: MemoryType;
855
+ scope: MemoryScope;
856
+ description: string;
857
+ body: string;
858
+ }
859
+ /**
860
+ * Throws on filename injection attempts (`../foo`, `foo/bar`, leading
861
+ * dots, etc.). Allowed: 3-40 chars, alnum + `_` + `-` + interior `.`.
862
+ */
863
+ declare function sanitizeMemoryName(raw: string): string;
864
+ /** Stable 16-hex-char hash of an absolute sandbox root path. */
865
+ declare function projectHash(rootDir: string): string;
866
+ declare class MemoryStore {
867
+ private readonly homeDir;
868
+ private readonly projectRoot;
869
+ constructor(opts?: MemoryStoreOptions);
870
+ /** Directory this store writes `scope` files into, creating it if needed. */
871
+ dir(scope: MemoryScope): string;
872
+ /** Absolute path to a memory file (no existence check). */
873
+ pathFor(scope: MemoryScope, name: string): string;
874
+ /** True iff this store is configured with a project scope available. */
875
+ hasProjectScope(): boolean;
876
+ /**
877
+ * Read the `MEMORY.md` index for a scope. Returns post-cap content
878
+ * (with a truncation marker if clipped), or `null` when absent / empty.
879
+ */
880
+ loadIndex(scope: MemoryScope): {
881
+ content: string;
882
+ originalChars: number;
883
+ truncated: boolean;
884
+ } | null;
885
+ /** Read one memory file's body (frontmatter stripped). Throws if missing. */
886
+ read(scope: MemoryScope, name: string): MemoryEntry;
887
+ /**
888
+ * List every memory in this store. Scans both scopes (skips project
889
+ * scope if unconfigured). Silently skips malformed files; the index
890
+ * must stay queryable even if one file is hand-edited into nonsense.
891
+ */
892
+ list(): MemoryEntry[];
893
+ /**
894
+ * Write a new memory (or overwrite existing). Creates the scope dir,
895
+ * writes the `.md` file, and regenerates `MEMORY.md`. Returns the
896
+ * absolute path written to.
897
+ */
898
+ write(input: WriteInput): string;
899
+ /** Delete one memory + its index line. No-op if the file is already gone. */
900
+ delete(scope: MemoryScope, rawName: string): boolean;
901
+ /**
902
+ * Rebuild `MEMORY.md` from the `.md` files currently in the scope dir.
903
+ * Called after every write/delete. Sorted by name for stable prefix
904
+ * hashing — two stores with the same set of files produce byte-identical
905
+ * MEMORY.md content, keeping the cache prefix reproducible.
906
+ */
907
+ private regenerateIndex;
908
+ }
909
+ /**
910
+ * Append `MEMORY_GLOBAL` and (optionally) `MEMORY_PROJECT` blocks to
911
+ * `basePrompt`. Omits a block entirely when its index is absent — an
912
+ * empty tag would add bytes to the prefix hash without content.
913
+ * Respects `REASONIX_MEMORY=off` via `memoryEnabled()` from
914
+ * `project-memory.ts`.
915
+ */
916
+ declare function applyUserMemory(basePrompt: string, opts?: {
917
+ homeDir?: string;
918
+ projectRoot?: string;
919
+ }): string;
920
+ /**
921
+ * Compose REASONIX.md + user memory in one call. Drop-in replacement
922
+ * for `applyProjectMemory` at CLI entry points — preserves the existing
923
+ * REASONIX_PROJECT block order, then appends the two user-memory blocks.
924
+ */
925
+ declare function applyMemoryStack(basePrompt: string, rootDir: string): string;
926
+
776
927
  /**
777
928
  * Built-in filesystem tools for `reasonix code`.
778
929
  *
@@ -817,6 +968,89 @@ interface FilesystemToolsOptions {
817
968
  }
818
969
  declare function registerFilesystemTools(registry: ToolRegistry, opts: FilesystemToolsOptions): ToolRegistry;
819
970
 
971
+ /**
972
+ * `remember` / `forget` / `recall_memory` — tools that let the model
973
+ * read and write the user-memory store across sessions.
974
+ *
975
+ * Scope rules:
976
+ * - `global` — always available (no sandbox needed).
977
+ * - `project` — requires a `projectRoot` on MemoryStore. In chat mode
978
+ * (no sandbox), the tools still register but a `scope=project` call
979
+ * returns a structured refusal so the model can try `global` instead.
980
+ *
981
+ * Memory changes are written eagerly but NOT re-loaded into the prefix
982
+ * mid-session (cache invariant). The user notices at `/new` or the next
983
+ * launch — or they can read fresh content via `recall_memory` which
984
+ * always hits disk.
985
+ */
986
+
987
+ interface MemoryToolsOptions {
988
+ /** Sandbox root for the `project` scope. Omit for chat mode. */
989
+ projectRoot?: string;
990
+ /** Override `~/.reasonix` (tests). */
991
+ homeDir?: string;
992
+ }
993
+ declare function registerMemoryTools(registry: ToolRegistry, opts?: MemoryToolsOptions): ToolRegistry;
994
+
995
+ /**
996
+ * Plan Mode — read-only exploration phase for `reasonix code`.
997
+ *
998
+ * Shape (mirrors claude-code's plan/act split, adapted for Reasonix):
999
+ *
1000
+ * 1. User types `/plan` → registry switches to plan-mode enforcement
1001
+ * (write tools refused at dispatch; reads + allowlisted shell
1002
+ * still work).
1003
+ * 2. Model explores, then calls `submit_plan` with a markdown plan.
1004
+ * 3. `submit_plan` throws `PlanProposedError`, which the TUI renders
1005
+ * as a picker: Approve / Refine / Cancel.
1006
+ * 4. Approve → registry leaves plan mode, a synthetic user message
1007
+ * "The plan has been approved. Implement it now." is pushed into
1008
+ * the loop so the next turn executes.
1009
+ *
1010
+ * The read-only enforcement lives in `ToolRegistry.dispatch` via
1011
+ * `readOnly` / `readOnlyCheck`; this file only ships the `submit_plan`
1012
+ * escape hatch and the error type that carries the plan out of the
1013
+ * registry without stuffing it into the message.
1014
+ *
1015
+ * We do not change `ImmutablePrefix.toolSpecs` when plan mode toggles —
1016
+ * that would break Pillar 1's prefix cache. Instead the same full spec
1017
+ * list stays pinned, and the registry enforces mode at dispatch time.
1018
+ * The refusal string teaches the model the rule; cache hits stay
1019
+ * intact.
1020
+ */
1021
+
1022
+ /**
1023
+ * Thrown by `submit_plan` when plan mode is active, carrying the plan
1024
+ * text the TUI will render for the user's approval.
1025
+ *
1026
+ * Implements the `toToolResult` protocol so `ToolRegistry.dispatch`
1027
+ * serializes the full plan into the tool-result JSON (not just the
1028
+ * error message). The TUI parses `{ error, plan }` from the tool event
1029
+ * and mounts the `PlanConfirm` picker.
1030
+ */
1031
+ declare class PlanProposedError extends Error {
1032
+ readonly plan: string;
1033
+ constructor(plan: string);
1034
+ /**
1035
+ * Structured tool-result shape. Consumed by the TUI to extract the
1036
+ * plan without regex-scraping the error message.
1037
+ */
1038
+ toToolResult(): {
1039
+ error: string;
1040
+ plan: string;
1041
+ };
1042
+ }
1043
+ interface PlanToolOptions {
1044
+ /**
1045
+ * Optional side-channel callback fired when the model submits a plan.
1046
+ * The TUI uses this to preview the plan in real time (the tool-result
1047
+ * event is also emitted; this is just earlier and friendlier to
1048
+ * test harnesses that don't want to parse JSON).
1049
+ */
1050
+ onPlanSubmitted?: (plan: string) => void;
1051
+ }
1052
+ declare function registerPlanTool(registry: ToolRegistry, opts?: PlanToolOptions): ToolRegistry;
1053
+
820
1054
  /**
821
1055
  * Native shell tool — lets the model run commands inside the sandbox
822
1056
  * root so it can actually verify its own work (run tests, check git
@@ -894,6 +1128,64 @@ declare function runCommand(cmd: string, opts: {
894
1128
  maxOutputChars?: number;
895
1129
  signal?: AbortSignal;
896
1130
  }): Promise<RunCommandResult>;
1131
+ /**
1132
+ * Test/override hooks for {@link resolveExecutable}. Omitting any field
1133
+ * falls through to the real process globals — the runtime call path
1134
+ * uses defaults; tests inject `platform` + `env` + `isFile` to exercise
1135
+ * Windows-specific lookup from a Linux CI runner without touching
1136
+ * actual fs.
1137
+ */
1138
+ interface ResolveExecutableOptions {
1139
+ platform?: NodeJS.Platform;
1140
+ env?: {
1141
+ PATH?: string;
1142
+ PATHEXT?: string;
1143
+ };
1144
+ /** Predicate swapped in by tests to avoid creating real files. */
1145
+ isFile?: (path: string) => boolean;
1146
+ /** Path.join used for the lookup. Defaults to Windows semantics on Windows. */
1147
+ pathDelimiter?: string;
1148
+ }
1149
+ /**
1150
+ * Resolve a bare command name (e.g. `npm`) to its on-disk path via
1151
+ * PATH × PATHEXT on Windows. Returns the input unchanged on non-Windows
1152
+ * platforms, when the input is already a path (contains `/`, `\`, or is
1153
+ * absolute), or when no match is found in PATH × PATHEXT (caller gets a
1154
+ * natural ENOENT from spawn, which surfaces cleanly).
1155
+ *
1156
+ * Why this exists: `child_process.spawn` with `shell: false` invokes
1157
+ * Windows `CreateProcess`, which does not honor `PATHEXT` and does not
1158
+ * search for `.cmd` / `.bat` wrappers. Node-ecosystem tools ship as
1159
+ * `npm.cmd`, `npx.cmd`, `yarn.cmd`, etc., so a bare `npm` fails with
1160
+ * ENOENT under `shell: false`. Flipping to `shell: true` would work
1161
+ * but reintroduces shell-expansion (pipes, redirects, chained cmds)
1162
+ * that the tool was explicitly designed to forbid. This resolver
1163
+ * threads the needle.
1164
+ */
1165
+ declare function resolveExecutable(cmd: string, opts?: ResolveExecutableOptions): string;
1166
+ /**
1167
+ * Prepare `(bin, args, spawnOpts)` for the runCommand spawn call,
1168
+ * applying Windows-specific workarounds for (a) PATHEXT lookup and
1169
+ * (b) the CVE-2024-27980 prohibition on direct `.cmd`/`.bat` spawns.
1170
+ *
1171
+ * Exported so tests can assert the transformation without booting an
1172
+ * actual child process.
1173
+ */
1174
+ declare function prepareSpawn(argv: readonly string[], opts?: ResolveExecutableOptions): {
1175
+ bin: string;
1176
+ args: string[];
1177
+ spawnOverrides: SpawnOptions;
1178
+ };
1179
+ /**
1180
+ * Quote an argument so cmd.exe parses it back as a single token. We
1181
+ * always wrap in double quotes when the arg contains whitespace or
1182
+ * any cmd.exe metacharacter, doubling embedded quotes per cmd.exe's
1183
+ * `""` escape rule. Bare alphanumeric args pass through unquoted for
1184
+ * readability in logs.
1185
+ *
1186
+ * Exported for test coverage of the quoting semantics.
1187
+ */
1188
+ declare function quoteForCmdExe(arg: string): string;
897
1189
  /** Error thrown by `run_command` when the command isn't allowlisted. */
898
1190
  declare class NeedsConfirmationError extends Error {
899
1191
  readonly command: string;
@@ -1927,7 +2219,7 @@ declare function restoreSnapshots(snapshots: EditSnapshot[], rootDir: string): A
1927
2219
  * the Cache-First Loop is trying to conserve. The SEARCH/REPLACE spec
1928
2220
  * is the one unavoidable bloat; we trim everything else.
1929
2221
  */
1930
- declare 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 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 \u2014 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 \u2014 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# Exploration\n\n- Avoid listing or reading inside these common dependency / build directories unless the user explicitly asks about them: node_modules, dist, build, out, .next, .nuxt, .svelte-kit, .git, .venv, venv, __pycache__, target, coverage, .turbo, .cache. They're expensive and usually irrelevant.\n- 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.\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 \u2014 silence while exploring is fine.\n";
2222
+ declare 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 \u2014 migrations, destructive cleanups, API shape changes.\n- When the user's request is ambiguous and multiple reasonable interpretations exist \u2014 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 \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.\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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 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 \u2014 silence while exploring is fine.\n";
1931
2223
  /**
1932
2224
  * Inject the project's `.gitignore` content into the system prompt as a
1933
2225
  * "respect this on top of the built-in denylist" hint. We don't parse
@@ -1935,7 +2227,7 @@ declare const CODE_SYSTEM_PROMPT = "You are Reasonix Code, a coding assistant. Y
1935
2227
  * don't eat context budget on huge generated ignore lists.
1936
2228
  *
1937
2229
  * Stacking order (stable for cache prefix):
1938
- * base prompt → project memory (REASONIX.md) → .gitignore block
2230
+ * base prompt → REASONIX.md global MEMORY.md → project MEMORY.md → .gitignore
1939
2231
  */
1940
2232
  declare function codeSystemPrompt(rootDir: string): string;
1941
2233
 
@@ -2009,6 +2301,6 @@ declare function redactKey(key: string): string;
2009
2301
 
2010
2302
  /** Reasonix — DeepSeek-native agent framework. Library entry point. */
2011
2303
 
2012
- declare const VERSION = "0.4.17";
2304
+ declare const VERSION = "0.4.20";
2013
2305
 
2014
- export { AppendOnlyLog, type ApplyResult, type ApplyStatus, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, type BridgeOptions, type BridgeResult, CODE_SYSTEM_PROMPT, CacheFirstLoop, type CacheFirstLoopOptions, type CallToolResult, type ChatMessage, type ChatResponse, DEFAULT_MAX_RESULT_CHARS, DeepSeekClient, type DeepSeekClientOptions, type RenderOptions as DiffRenderOptions, type DiffReport, type DiffSide, type EditBlock, type EditSnapshot, type EventRole, type FilesystemToolsOptions, type FlattenDecision, type FlattenOptions, type GetPromptResult, type HarvestOptions, ImmutablePrefix, type ImmutablePrefixOptions, type InitializeResult, type InspectionReport, type JSONSchema, type JsonRpcMessage, type JsonRpcRequest, type JsonRpcResponse, type ListPromptsResult, type ListResourcesResult, type ListToolsResult, type LoopEvent, MCP_PROTOCOL_VERSION, McpClient, type McpClientOptions, type McpContentBlock, type McpProgressHandler, type McpProgressInfo, type McpPrompt, type McpPromptArgument, type McpPromptMessage, type McpPromptResourceBlock, type McpResource, type McpResourceContents, type McpResourceContentsBlob, type McpResourceContentsText, type McpSpec, type McpTool, type McpToolSchema, type McpTransport, NeedsConfirmationError, PROJECT_MEMORY_FILE, PROJECT_MEMORY_MAX_CHARS, type PageContent, type ProgressNotificationParams, type ProjectMemory, type ReadResourceResult, type ReadTranscriptResult, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type ReplayStats, type RetryInfo, type RetryOptions, type Role, type RunCommandResult, type ScavengeOptions, type ScavengeResult, type SearchResult, type SectionResult, type SessionInfo, SessionStats, type SessionSummary, type ShellToolsOptions, type SseMcpSpec, SseTransport, type SseTransportOptions, type StdioMcpSpec, StdioTransport, type StdioTransportOptions, StormBreaker, type StreamChunk, type ToolCall, type ToolCallContext, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TranscriptMeta, type TranscriptRecord, type TruncationRepairResult, type TurnPair, type TurnStats, type TypedPlanState, Usage, VERSION, VolatileScratch, type WebFetchOptions, type WebSearchOptions, type WebToolsOptions, aggregateBranchUsage, analyzeSchema, appendSessionMessage, applyEditBlock, applyEditBlocks, applyProjectMemory, bridgeMcpTools, claudeEquivalentCost, codeSystemPrompt, computeReplayStats, costUsd, defaultConfigPath, defaultSelector, deleteSession, diffTranscripts, emptyPlanState, fetchWithRetry, flattenMcpResult, flattenSchema, formatCommandResult, formatLoopError, formatSearchResults, harvest, healLoadedMessages, htmlToText, inputCostUsd, inspectMcpServer, isAllowed, isJsonRpcError, isPlanStateEmpty, isPlausibleKey, listSessions, loadApiKey, loadDotenv, loadSessionMessages, memoryEnabled, nestArguments, openTranscriptFile, outputCostUsd, parseEditBlocks, parseMcpSpec, parseMojeekResults, parseTranscript, readConfig, readProjectMemory, readTranscript, recordFromLoopEvent, redactKey, registerFilesystemTools, registerShellTools, registerWebTools, renderMarkdown as renderDiffMarkdown, renderSummaryTable as renderDiffSummary, repairTruncatedJson, replayFromFile, restoreSnapshots, runBranches, runCommand, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, similarity, snapshotBeforeEdits, stripHallucinatedToolMarkup, tokenizeCommand, truncateForModel, webFetch, webSearch, writeConfig, writeMeta, writeRecord };
2306
+ export { AppendOnlyLog, type ApplyResult, type ApplyStatus, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, type BridgeOptions, type BridgeResult, CODE_SYSTEM_PROMPT, CacheFirstLoop, type CacheFirstLoopOptions, type CallToolResult, type ChatMessage, type ChatResponse, DEFAULT_MAX_RESULT_CHARS, DeepSeekClient, type DeepSeekClientOptions, type RenderOptions as DiffRenderOptions, type DiffReport, type DiffSide, type EditBlock, type EditSnapshot, type EventRole, type FilesystemToolsOptions, type FlattenDecision, type FlattenOptions, type GetPromptResult, type HarvestOptions, ImmutablePrefix, type ImmutablePrefixOptions, type InitializeResult, type InspectionReport, type JSONSchema, type JsonRpcMessage, type JsonRpcRequest, type JsonRpcResponse, type ListPromptsResult, type ListResourcesResult, type ListToolsResult, type LoopEvent, MCP_PROTOCOL_VERSION, MEMORY_INDEX_FILE, MEMORY_INDEX_MAX_CHARS, McpClient, type McpClientOptions, type McpContentBlock, type McpProgressHandler, type McpProgressInfo, type McpPrompt, type McpPromptArgument, type McpPromptMessage, type McpPromptResourceBlock, type McpResource, type McpResourceContents, type McpResourceContentsBlob, type McpResourceContentsText, type McpSpec, type McpTool, type McpToolSchema, type McpTransport, type MemoryEntry, type MemoryScope, MemoryStore, type MemoryStoreOptions, type MemoryToolsOptions, type MemoryType, type WriteInput as MemoryWriteInput, NeedsConfirmationError, PROJECT_MEMORY_FILE, PROJECT_MEMORY_MAX_CHARS, type PageContent, PlanProposedError, type PlanToolOptions, type ProgressNotificationParams, type ProjectMemory, type ReadResourceResult, type ReadTranscriptResult, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type ReplayStats, type RetryInfo, type RetryOptions, type Role, type RunCommandResult, type ScavengeOptions, type ScavengeResult, type SearchResult, type SectionResult, type SessionInfo, SessionStats, type SessionSummary, type ShellToolsOptions, type SseMcpSpec, SseTransport, type SseTransportOptions, type StdioMcpSpec, StdioTransport, type StdioTransportOptions, StormBreaker, type StreamChunk, type ToolCall, type ToolCallContext, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TranscriptMeta, type TranscriptRecord, type TruncationRepairResult, type TurnPair, type TurnStats, type TypedPlanState, USER_MEMORY_DIR, Usage, VERSION, VolatileScratch, type WebFetchOptions, type WebSearchOptions, type WebToolsOptions, aggregateBranchUsage, analyzeSchema, appendSessionMessage, applyEditBlock, applyEditBlocks, applyMemoryStack, applyProjectMemory, applyUserMemory, bridgeMcpTools, claudeEquivalentCost, codeSystemPrompt, computeReplayStats, costUsd, defaultConfigPath, defaultSelector, deleteSession, diffTranscripts, emptyPlanState, fetchWithRetry, flattenMcpResult, flattenSchema, formatCommandResult, formatLoopError, formatSearchResults, harvest, healLoadedMessages, htmlToText, inputCostUsd, inspectMcpServer, isAllowed, isJsonRpcError, isPlanStateEmpty, isPlausibleKey, listSessions, loadApiKey, loadDotenv, loadSessionMessages, memoryEnabled, nestArguments, openTranscriptFile, outputCostUsd, parseEditBlocks, parseMcpSpec, parseMojeekResults, parseTranscript, prepareSpawn, projectHash, quoteForCmdExe, readConfig, readProjectMemory, readTranscript, recordFromLoopEvent, redactKey, registerFilesystemTools, registerMemoryTools, registerPlanTool, registerShellTools, registerWebTools, renderMarkdown as renderDiffMarkdown, renderSummaryTable as renderDiffSummary, repairTruncatedJson, replayFromFile, resolveExecutable, restoreSnapshots, runBranches, runCommand, sanitizeMemoryName, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, similarity, snapshotBeforeEdits, stripHallucinatedToolMarkup, tokenizeCommand, truncateForModel, webFetch, webSearch, writeConfig, writeMeta, writeRecord };