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 +47 -2
- package/dist/cli/chunk-DDIKQZVD.js +445 -0
- package/dist/cli/chunk-DDIKQZVD.js.map +1 -0
- package/dist/cli/index.js +300 -51
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/{prompt-JNNNJLYF.js → prompt-YEJEJ3IZ.js} +2 -2
- package/dist/index.d.ts +139 -4
- package/dist/index.js +477 -58
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-HNEWBEWZ.js +0 -152
- package/dist/cli/chunk-HNEWBEWZ.js.map +0 -1
- /package/dist/cli/{prompt-JNNNJLYF.js.map → prompt-YEJEJ3IZ.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -47,8 +47,8 @@ function computeWait(attempt, initial, cap, retryAfter) {
|
|
|
47
47
|
}
|
|
48
48
|
function sleep(ms, signal) {
|
|
49
49
|
if (ms <= 0) return Promise.resolve();
|
|
50
|
-
return new Promise((
|
|
51
|
-
const timer = setTimeout(
|
|
50
|
+
return new Promise((resolve6, reject) => {
|
|
51
|
+
const timer = setTimeout(resolve6, ms);
|
|
52
52
|
if (signal) {
|
|
53
53
|
const onAbort = () => {
|
|
54
54
|
clearTimeout(timer);
|
|
@@ -1537,8 +1537,8 @@ var CacheFirstLoop = class {
|
|
|
1537
1537
|
}
|
|
1538
1538
|
);
|
|
1539
1539
|
for (let k = 0; k < budget; k++) {
|
|
1540
|
-
const sample = queue.shift() ?? await new Promise((
|
|
1541
|
-
waiter =
|
|
1540
|
+
const sample = queue.shift() ?? await new Promise((resolve6) => {
|
|
1541
|
+
waiter = resolve6;
|
|
1542
1542
|
});
|
|
1543
1543
|
yield {
|
|
1544
1544
|
turn: this._turn,
|
|
@@ -1950,6 +1950,291 @@ ${mem.content}
|
|
|
1950
1950
|
`;
|
|
1951
1951
|
}
|
|
1952
1952
|
|
|
1953
|
+
// src/user-memory.ts
|
|
1954
|
+
import { createHash as createHash2 } from "crypto";
|
|
1955
|
+
import {
|
|
1956
|
+
existsSync as existsSync3,
|
|
1957
|
+
mkdirSync as mkdirSync2,
|
|
1958
|
+
readFileSync as readFileSync3,
|
|
1959
|
+
readdirSync as readdirSync2,
|
|
1960
|
+
unlinkSync as unlinkSync2,
|
|
1961
|
+
writeFileSync as writeFileSync2
|
|
1962
|
+
} from "fs";
|
|
1963
|
+
import { homedir as homedir2 } from "os";
|
|
1964
|
+
import { join as join3, resolve } from "path";
|
|
1965
|
+
var USER_MEMORY_DIR = "memory";
|
|
1966
|
+
var MEMORY_INDEX_FILE = "MEMORY.md";
|
|
1967
|
+
var MEMORY_INDEX_MAX_CHARS = 4e3;
|
|
1968
|
+
var VALID_NAME = /^[a-zA-Z0-9_-][a-zA-Z0-9_.-]{1,38}[a-zA-Z0-9]$/;
|
|
1969
|
+
function sanitizeMemoryName(raw) {
|
|
1970
|
+
const trimmed = String(raw ?? "").trim();
|
|
1971
|
+
if (!VALID_NAME.test(trimmed)) {
|
|
1972
|
+
throw new Error(
|
|
1973
|
+
`invalid memory name: ${JSON.stringify(raw)} \u2014 must be 3-40 chars, alnum/_/-, no path separators`
|
|
1974
|
+
);
|
|
1975
|
+
}
|
|
1976
|
+
return trimmed;
|
|
1977
|
+
}
|
|
1978
|
+
function projectHash(rootDir) {
|
|
1979
|
+
const abs = resolve(rootDir);
|
|
1980
|
+
return createHash2("sha1").update(abs).digest("hex").slice(0, 16);
|
|
1981
|
+
}
|
|
1982
|
+
function scopeDir(opts) {
|
|
1983
|
+
if (opts.scope === "global") {
|
|
1984
|
+
return join3(opts.homeDir, USER_MEMORY_DIR, "global");
|
|
1985
|
+
}
|
|
1986
|
+
if (!opts.projectRoot) {
|
|
1987
|
+
throw new Error("scope=project requires a projectRoot on MemoryStore");
|
|
1988
|
+
}
|
|
1989
|
+
return join3(opts.homeDir, USER_MEMORY_DIR, projectHash(opts.projectRoot));
|
|
1990
|
+
}
|
|
1991
|
+
function ensureDir(p) {
|
|
1992
|
+
if (!existsSync3(p)) mkdirSync2(p, { recursive: true });
|
|
1993
|
+
}
|
|
1994
|
+
function parseFrontmatter(raw) {
|
|
1995
|
+
const lines = raw.split(/\r?\n/);
|
|
1996
|
+
if (lines[0] !== "---") return { data: {}, body: raw };
|
|
1997
|
+
const end = lines.indexOf("---", 1);
|
|
1998
|
+
if (end < 0) return { data: {}, body: raw };
|
|
1999
|
+
const data = {};
|
|
2000
|
+
for (let i = 1; i < end; i++) {
|
|
2001
|
+
const line = lines[i];
|
|
2002
|
+
if (!line) continue;
|
|
2003
|
+
const m = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*)$/);
|
|
2004
|
+
if (m?.[1]) data[m[1]] = (m[2] ?? "").trim();
|
|
2005
|
+
}
|
|
2006
|
+
return {
|
|
2007
|
+
data,
|
|
2008
|
+
body: lines.slice(end + 1).join("\n").replace(/^\n+/, "")
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
function formatFrontmatter(e) {
|
|
2012
|
+
return [
|
|
2013
|
+
"---",
|
|
2014
|
+
`name: ${e.name}`,
|
|
2015
|
+
`description: ${e.description.replace(/\n/g, " ")}`,
|
|
2016
|
+
`type: ${e.type}`,
|
|
2017
|
+
`scope: ${e.scope}`,
|
|
2018
|
+
`created: ${e.createdAt}`,
|
|
2019
|
+
"---",
|
|
2020
|
+
""
|
|
2021
|
+
].join("\n");
|
|
2022
|
+
}
|
|
2023
|
+
function todayIso() {
|
|
2024
|
+
const d = /* @__PURE__ */ new Date();
|
|
2025
|
+
return d.toISOString().slice(0, 10);
|
|
2026
|
+
}
|
|
2027
|
+
function indexLine(e) {
|
|
2028
|
+
const safeDesc = e.description.replace(/\n/g, " ").trim();
|
|
2029
|
+
const max = 130 - e.name.length;
|
|
2030
|
+
const clipped = safeDesc.length > max ? `${safeDesc.slice(0, Math.max(1, max - 1))}\u2026` : safeDesc;
|
|
2031
|
+
return `- [${e.name}](${e.name}.md) \u2014 ${clipped}`;
|
|
2032
|
+
}
|
|
2033
|
+
var MemoryStore = class {
|
|
2034
|
+
homeDir;
|
|
2035
|
+
projectRoot;
|
|
2036
|
+
constructor(opts = {}) {
|
|
2037
|
+
this.homeDir = opts.homeDir ?? join3(homedir2(), ".reasonix");
|
|
2038
|
+
this.projectRoot = opts.projectRoot ? resolve(opts.projectRoot) : void 0;
|
|
2039
|
+
}
|
|
2040
|
+
/** Directory this store writes `scope` files into, creating it if needed. */
|
|
2041
|
+
dir(scope) {
|
|
2042
|
+
const d = scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot });
|
|
2043
|
+
ensureDir(d);
|
|
2044
|
+
return d;
|
|
2045
|
+
}
|
|
2046
|
+
/** Absolute path to a memory file (no existence check). */
|
|
2047
|
+
pathFor(scope, name) {
|
|
2048
|
+
return join3(this.dir(scope), `${sanitizeMemoryName(name)}.md`);
|
|
2049
|
+
}
|
|
2050
|
+
/** True iff this store is configured with a project scope available. */
|
|
2051
|
+
hasProjectScope() {
|
|
2052
|
+
return this.projectRoot !== void 0;
|
|
2053
|
+
}
|
|
2054
|
+
/**
|
|
2055
|
+
* Read the `MEMORY.md` index for a scope. Returns post-cap content
|
|
2056
|
+
* (with a truncation marker if clipped), or `null` when absent / empty.
|
|
2057
|
+
*/
|
|
2058
|
+
loadIndex(scope) {
|
|
2059
|
+
if (scope === "project" && !this.projectRoot) return null;
|
|
2060
|
+
const file = join3(
|
|
2061
|
+
scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot }),
|
|
2062
|
+
MEMORY_INDEX_FILE
|
|
2063
|
+
);
|
|
2064
|
+
if (!existsSync3(file)) return null;
|
|
2065
|
+
let raw;
|
|
2066
|
+
try {
|
|
2067
|
+
raw = readFileSync3(file, "utf8");
|
|
2068
|
+
} catch {
|
|
2069
|
+
return null;
|
|
2070
|
+
}
|
|
2071
|
+
const trimmed = raw.trim();
|
|
2072
|
+
if (!trimmed) return null;
|
|
2073
|
+
const originalChars = trimmed.length;
|
|
2074
|
+
const truncated = originalChars > MEMORY_INDEX_MAX_CHARS;
|
|
2075
|
+
const content = truncated ? `${trimmed.slice(0, MEMORY_INDEX_MAX_CHARS)}
|
|
2076
|
+
\u2026 (truncated ${originalChars - MEMORY_INDEX_MAX_CHARS} chars)` : trimmed;
|
|
2077
|
+
return { content, originalChars, truncated };
|
|
2078
|
+
}
|
|
2079
|
+
/** Read one memory file's body (frontmatter stripped). Throws if missing. */
|
|
2080
|
+
read(scope, name) {
|
|
2081
|
+
const file = this.pathFor(scope, name);
|
|
2082
|
+
if (!existsSync3(file)) {
|
|
2083
|
+
throw new Error(`memory not found: scope=${scope} name=${name}`);
|
|
2084
|
+
}
|
|
2085
|
+
const raw = readFileSync3(file, "utf8");
|
|
2086
|
+
const { data, body } = parseFrontmatter(raw);
|
|
2087
|
+
return {
|
|
2088
|
+
name: data.name ?? name,
|
|
2089
|
+
type: data.type ?? "project",
|
|
2090
|
+
scope: data.scope ?? scope,
|
|
2091
|
+
description: data.description ?? "",
|
|
2092
|
+
body: body.trim(),
|
|
2093
|
+
createdAt: data.created ?? ""
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* List every memory in this store. Scans both scopes (skips project
|
|
2098
|
+
* scope if unconfigured). Silently skips malformed files; the index
|
|
2099
|
+
* must stay queryable even if one file is hand-edited into nonsense.
|
|
2100
|
+
*/
|
|
2101
|
+
list() {
|
|
2102
|
+
const out = [];
|
|
2103
|
+
const scopes = this.projectRoot ? ["global", "project"] : ["global"];
|
|
2104
|
+
for (const scope of scopes) {
|
|
2105
|
+
const dir = scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot });
|
|
2106
|
+
if (!existsSync3(dir)) continue;
|
|
2107
|
+
let entries;
|
|
2108
|
+
try {
|
|
2109
|
+
entries = readdirSync2(dir);
|
|
2110
|
+
} catch {
|
|
2111
|
+
continue;
|
|
2112
|
+
}
|
|
2113
|
+
for (const entry of entries) {
|
|
2114
|
+
if (entry === MEMORY_INDEX_FILE) continue;
|
|
2115
|
+
if (!entry.endsWith(".md")) continue;
|
|
2116
|
+
const name = entry.slice(0, -3);
|
|
2117
|
+
try {
|
|
2118
|
+
out.push(this.read(scope, name));
|
|
2119
|
+
} catch {
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
return out;
|
|
2124
|
+
}
|
|
2125
|
+
/**
|
|
2126
|
+
* Write a new memory (or overwrite existing). Creates the scope dir,
|
|
2127
|
+
* writes the `.md` file, and regenerates `MEMORY.md`. Returns the
|
|
2128
|
+
* absolute path written to.
|
|
2129
|
+
*/
|
|
2130
|
+
write(input) {
|
|
2131
|
+
if (input.scope === "project" && !this.projectRoot) {
|
|
2132
|
+
throw new Error("cannot write project-scoped memory: no projectRoot configured");
|
|
2133
|
+
}
|
|
2134
|
+
const name = sanitizeMemoryName(input.name);
|
|
2135
|
+
const desc = String(input.description ?? "").trim();
|
|
2136
|
+
if (!desc) throw new Error("memory description cannot be empty");
|
|
2137
|
+
const body = String(input.body ?? "").trim();
|
|
2138
|
+
if (!body) throw new Error("memory body cannot be empty");
|
|
2139
|
+
const entry = {
|
|
2140
|
+
...input,
|
|
2141
|
+
name,
|
|
2142
|
+
description: desc,
|
|
2143
|
+
body,
|
|
2144
|
+
createdAt: todayIso()
|
|
2145
|
+
};
|
|
2146
|
+
const dir = this.dir(input.scope);
|
|
2147
|
+
const file = join3(dir, `${name}.md`);
|
|
2148
|
+
const content = `${formatFrontmatter(entry)}${body}
|
|
2149
|
+
`;
|
|
2150
|
+
writeFileSync2(file, content, "utf8");
|
|
2151
|
+
this.regenerateIndex(input.scope);
|
|
2152
|
+
return file;
|
|
2153
|
+
}
|
|
2154
|
+
/** Delete one memory + its index line. No-op if the file is already gone. */
|
|
2155
|
+
delete(scope, rawName) {
|
|
2156
|
+
if (scope === "project" && !this.projectRoot) {
|
|
2157
|
+
throw new Error("cannot delete project-scoped memory: no projectRoot configured");
|
|
2158
|
+
}
|
|
2159
|
+
const file = this.pathFor(scope, rawName);
|
|
2160
|
+
if (!existsSync3(file)) return false;
|
|
2161
|
+
unlinkSync2(file);
|
|
2162
|
+
this.regenerateIndex(scope);
|
|
2163
|
+
return true;
|
|
2164
|
+
}
|
|
2165
|
+
/**
|
|
2166
|
+
* Rebuild `MEMORY.md` from the `.md` files currently in the scope dir.
|
|
2167
|
+
* Called after every write/delete. Sorted by name for stable prefix
|
|
2168
|
+
* hashing — two stores with the same set of files produce byte-identical
|
|
2169
|
+
* MEMORY.md content, keeping the cache prefix reproducible.
|
|
2170
|
+
*/
|
|
2171
|
+
regenerateIndex(scope) {
|
|
2172
|
+
const dir = scopeDir({ homeDir: this.homeDir, scope, projectRoot: this.projectRoot });
|
|
2173
|
+
if (!existsSync3(dir)) return;
|
|
2174
|
+
let files;
|
|
2175
|
+
try {
|
|
2176
|
+
files = readdirSync2(dir);
|
|
2177
|
+
} catch {
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
const mdFiles = files.filter((f) => f !== MEMORY_INDEX_FILE && f.endsWith(".md")).sort((a, b) => a.localeCompare(b));
|
|
2181
|
+
const indexPath = join3(dir, MEMORY_INDEX_FILE);
|
|
2182
|
+
if (mdFiles.length === 0) {
|
|
2183
|
+
if (existsSync3(indexPath)) unlinkSync2(indexPath);
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
const lines = [];
|
|
2187
|
+
for (const f of mdFiles) {
|
|
2188
|
+
const name = f.slice(0, -3);
|
|
2189
|
+
try {
|
|
2190
|
+
const entry = this.read(scope, name);
|
|
2191
|
+
lines.push(indexLine({ name: entry.name || name, description: entry.description }));
|
|
2192
|
+
} catch {
|
|
2193
|
+
lines.push(`- [${name}](${name}.md) \u2014 (malformed, check frontmatter)`);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
writeFileSync2(indexPath, `${lines.join("\n")}
|
|
2197
|
+
`, "utf8");
|
|
2198
|
+
}
|
|
2199
|
+
};
|
|
2200
|
+
function applyUserMemory(basePrompt, opts = {}) {
|
|
2201
|
+
if (!memoryEnabled()) return basePrompt;
|
|
2202
|
+
const store = new MemoryStore(opts);
|
|
2203
|
+
const global = store.loadIndex("global");
|
|
2204
|
+
const project = store.hasProjectScope() ? store.loadIndex("project") : null;
|
|
2205
|
+
if (!global && !project) return basePrompt;
|
|
2206
|
+
const parts = [basePrompt];
|
|
2207
|
+
if (global) {
|
|
2208
|
+
parts.push(
|
|
2209
|
+
"",
|
|
2210
|
+
"# User memory \u2014 global (~/.reasonix/memory/global/MEMORY.md)",
|
|
2211
|
+
"",
|
|
2212
|
+
"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.",
|
|
2213
|
+
"",
|
|
2214
|
+
"```",
|
|
2215
|
+
global.content,
|
|
2216
|
+
"```"
|
|
2217
|
+
);
|
|
2218
|
+
}
|
|
2219
|
+
if (project) {
|
|
2220
|
+
parts.push(
|
|
2221
|
+
"",
|
|
2222
|
+
"# User memory \u2014 this project",
|
|
2223
|
+
"",
|
|
2224
|
+
"Per-project facts the user established in prior sessions (not committed to the repo). TREAT AS AUTHORITATIVE. Same recall pattern as global memory.",
|
|
2225
|
+
"",
|
|
2226
|
+
"```",
|
|
2227
|
+
project.content,
|
|
2228
|
+
"```"
|
|
2229
|
+
);
|
|
2230
|
+
}
|
|
2231
|
+
return parts.join("\n");
|
|
2232
|
+
}
|
|
2233
|
+
function applyMemoryStack(basePrompt, rootDir) {
|
|
2234
|
+
const withProject = applyProjectMemory(basePrompt, rootDir);
|
|
2235
|
+
return applyUserMemory(withProject, { projectRoot: rootDir });
|
|
2236
|
+
}
|
|
2237
|
+
|
|
1953
2238
|
// src/tools/filesystem.ts
|
|
1954
2239
|
import { promises as fs } from "fs";
|
|
1955
2240
|
import * as pathMod from "path";
|
|
@@ -2173,7 +2458,7 @@ function registerFilesystemTools(registry, opts) {
|
|
|
2173
2458
|
});
|
|
2174
2459
|
registry.register({
|
|
2175
2460
|
name: "edit_file",
|
|
2176
|
-
description: "Apply a SEARCH/REPLACE edit to an existing file. `search` must match exactly (whitespace sensitive) \u2014 no regex. The match must be unique in the file; otherwise the edit is refused to avoid surprise rewrites.
|
|
2461
|
+
description: "Apply a SEARCH/REPLACE edit to an existing file. `search` must match exactly (whitespace sensitive) \u2014 no regex. The match must be unique in the file; otherwise the edit is refused to avoid surprise rewrites.",
|
|
2177
2462
|
parameters: {
|
|
2178
2463
|
type: "object",
|
|
2179
2464
|
properties: {
|
|
@@ -2290,6 +2575,127 @@ function lineDiff(a, b) {
|
|
|
2290
2575
|
return out;
|
|
2291
2576
|
}
|
|
2292
2577
|
|
|
2578
|
+
// src/tools/memory.ts
|
|
2579
|
+
function registerMemoryTools(registry, opts = {}) {
|
|
2580
|
+
const store = new MemoryStore({ homeDir: opts.homeDir, projectRoot: opts.projectRoot });
|
|
2581
|
+
const hasProject = store.hasProjectScope();
|
|
2582
|
+
registry.register({
|
|
2583
|
+
name: "remember",
|
|
2584
|
+
description: "Save a memory for future sessions. Use when the user states a preference, corrects your approach, shares a non-obvious fact about this project, or explicitly asks you to remember something. Don't remember transient task state \u2014 only things worth recalling next session. The memory is written now but won't re-load into the system prompt until the next `/new` or launch.",
|
|
2585
|
+
parameters: {
|
|
2586
|
+
type: "object",
|
|
2587
|
+
properties: {
|
|
2588
|
+
type: {
|
|
2589
|
+
type: "string",
|
|
2590
|
+
enum: ["user", "feedback", "project", "reference"],
|
|
2591
|
+
description: "'user' = role/skills/prefs; 'feedback' = corrections or confirmed approaches; 'project' = facts/decisions about the current work; 'reference' = pointers to external systems the user uses."
|
|
2592
|
+
},
|
|
2593
|
+
scope: {
|
|
2594
|
+
type: "string",
|
|
2595
|
+
enum: ["global", "project"],
|
|
2596
|
+
description: "'global' = applies across every project (preferences, tooling); 'project' = scoped to the current sandbox (decisions, local facts). Only available in `reasonix code`."
|
|
2597
|
+
},
|
|
2598
|
+
name: {
|
|
2599
|
+
type: "string",
|
|
2600
|
+
description: "filename-safe identifier, 3-40 chars, alnum + _ - . (no path separators, no leading dot)."
|
|
2601
|
+
},
|
|
2602
|
+
description: {
|
|
2603
|
+
type: "string",
|
|
2604
|
+
description: "One-line summary shown in MEMORY.md (under ~150 chars)."
|
|
2605
|
+
},
|
|
2606
|
+
content: {
|
|
2607
|
+
type: "string",
|
|
2608
|
+
description: "Full memory body in markdown. For feedback/project types, structure as: rule/fact, then **Why:** line, then **How to apply:** line."
|
|
2609
|
+
}
|
|
2610
|
+
},
|
|
2611
|
+
required: ["type", "scope", "name", "description", "content"]
|
|
2612
|
+
},
|
|
2613
|
+
fn: async (args) => {
|
|
2614
|
+
if (args.scope === "project" && !hasProject) {
|
|
2615
|
+
return JSON.stringify({
|
|
2616
|
+
error: "scope='project' is unavailable in this session (no sandbox root). Retry with scope='global', or ask the user to switch to `reasonix code` for project-scoped memory."
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
try {
|
|
2620
|
+
const path = store.write({
|
|
2621
|
+
name: args.name,
|
|
2622
|
+
type: args.type,
|
|
2623
|
+
scope: args.scope,
|
|
2624
|
+
description: args.description,
|
|
2625
|
+
body: args.content
|
|
2626
|
+
});
|
|
2627
|
+
const key = sanitizeMemoryName(args.name);
|
|
2628
|
+
return [
|
|
2629
|
+
`\u2713 REMEMBERED (${args.scope}/${key}): ${args.description}`,
|
|
2630
|
+
"",
|
|
2631
|
+
"TREAT THIS AS ESTABLISHED FACT for the rest of this session.",
|
|
2632
|
+
"The user just told you \u2014 don't re-explore the filesystem to re-derive it.",
|
|
2633
|
+
`(Saved to ${path}; pins into the system prompt on next /new or launch.)`
|
|
2634
|
+
].join("\n");
|
|
2635
|
+
} catch (err) {
|
|
2636
|
+
return JSON.stringify({ error: `remember failed: ${err.message}` });
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
});
|
|
2640
|
+
registry.register({
|
|
2641
|
+
name: "forget",
|
|
2642
|
+
description: "Delete a memory file and remove it from MEMORY.md. Use when the user explicitly asks to forget something, or when a previously-remembered fact has become wrong. Irreversible \u2014 no tombstone.",
|
|
2643
|
+
parameters: {
|
|
2644
|
+
type: "object",
|
|
2645
|
+
properties: {
|
|
2646
|
+
name: { type: "string", description: "Memory name (the identifier used in `remember`)." },
|
|
2647
|
+
scope: { type: "string", enum: ["global", "project"] }
|
|
2648
|
+
},
|
|
2649
|
+
required: ["name", "scope"]
|
|
2650
|
+
},
|
|
2651
|
+
fn: async (args) => {
|
|
2652
|
+
if (args.scope === "project" && !hasProject) {
|
|
2653
|
+
return JSON.stringify({
|
|
2654
|
+
error: "scope='project' is unavailable in this session (no sandbox root)."
|
|
2655
|
+
});
|
|
2656
|
+
}
|
|
2657
|
+
try {
|
|
2658
|
+
const existed = store.delete(args.scope, args.name);
|
|
2659
|
+
return existed ? `forgot (${args.scope}/${sanitizeMemoryName(args.name)}). Re-load on next /new or launch.` : `no such memory: ${args.scope}/${args.name} (nothing to forget).`;
|
|
2660
|
+
} catch (err) {
|
|
2661
|
+
return JSON.stringify({ error: `forget failed: ${err.message}` });
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
});
|
|
2665
|
+
registry.register({
|
|
2666
|
+
name: "recall_memory",
|
|
2667
|
+
description: "Read the full body of a memory file when its MEMORY.md one-liner (already in the system prompt) isn't enough detail. Most of the time the index suffices \u2014 only call this when the user's question genuinely requires the full context.",
|
|
2668
|
+
readOnly: true,
|
|
2669
|
+
parameters: {
|
|
2670
|
+
type: "object",
|
|
2671
|
+
properties: {
|
|
2672
|
+
name: { type: "string" },
|
|
2673
|
+
scope: { type: "string", enum: ["global", "project"] }
|
|
2674
|
+
},
|
|
2675
|
+
required: ["name", "scope"]
|
|
2676
|
+
},
|
|
2677
|
+
fn: async (args) => {
|
|
2678
|
+
if (args.scope === "project" && !hasProject) {
|
|
2679
|
+
return JSON.stringify({
|
|
2680
|
+
error: "scope='project' is unavailable in this session (no sandbox root)."
|
|
2681
|
+
});
|
|
2682
|
+
}
|
|
2683
|
+
try {
|
|
2684
|
+
const entry = store.read(args.scope, args.name);
|
|
2685
|
+
return [
|
|
2686
|
+
`# ${entry.name} (${entry.scope}/${entry.type}, created ${entry.createdAt || "?"})`,
|
|
2687
|
+
entry.description ? `> ${entry.description}` : "",
|
|
2688
|
+
"",
|
|
2689
|
+
entry.body
|
|
2690
|
+
].filter(Boolean).join("\n");
|
|
2691
|
+
} catch (err) {
|
|
2692
|
+
return JSON.stringify({ error: `recall failed: ${err.message}` });
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
});
|
|
2696
|
+
return registry;
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2293
2699
|
// src/tools/plan.ts
|
|
2294
2700
|
var PlanProposedError = class extends Error {
|
|
2295
2701
|
plan;
|
|
@@ -2337,7 +2743,7 @@ function registerPlanTool(registry, opts = {}) {
|
|
|
2337
2743
|
|
|
2338
2744
|
// src/tools/shell.ts
|
|
2339
2745
|
import { spawn } from "child_process";
|
|
2340
|
-
import { existsSync as
|
|
2746
|
+
import { existsSync as existsSync4, statSync as statSync2 } from "fs";
|
|
2341
2747
|
import * as pathMod2 from "path";
|
|
2342
2748
|
var DEFAULT_TIMEOUT_SEC = 60;
|
|
2343
2749
|
var DEFAULT_MAX_OUTPUT_CHARS = 32e3;
|
|
@@ -2457,7 +2863,7 @@ async function runCommand(cmd, opts) {
|
|
|
2457
2863
|
};
|
|
2458
2864
|
const { bin, args, spawnOverrides } = prepareSpawn(argv);
|
|
2459
2865
|
const effectiveSpawnOpts = { ...spawnOpts, ...spawnOverrides };
|
|
2460
|
-
return await new Promise((
|
|
2866
|
+
return await new Promise((resolve6, reject) => {
|
|
2461
2867
|
let child;
|
|
2462
2868
|
try {
|
|
2463
2869
|
child = spawn(bin, args, effectiveSpawnOpts);
|
|
@@ -2490,7 +2896,7 @@ async function runCommand(cmd, opts) {
|
|
|
2490
2896
|
const output = buf.length > maxChars ? `${buf.slice(0, maxChars)}
|
|
2491
2897
|
|
|
2492
2898
|
[\u2026 truncated ${buf.length - maxChars} chars \u2026]` : buf;
|
|
2493
|
-
|
|
2899
|
+
resolve6({ exitCode: code, output, timedOut });
|
|
2494
2900
|
});
|
|
2495
2901
|
});
|
|
2496
2902
|
}
|
|
@@ -2507,7 +2913,7 @@ function resolveExecutable(cmd, opts = {}) {
|
|
|
2507
2913
|
const isFile = opts.isFile ?? defaultIsFile;
|
|
2508
2914
|
for (const dir of pathDirs) {
|
|
2509
2915
|
for (const ext of pathExt) {
|
|
2510
|
-
const full = pathMod2.join(dir, cmd + ext);
|
|
2916
|
+
const full = pathMod2.win32.join(dir, cmd + ext);
|
|
2511
2917
|
if (isFile(full)) return full;
|
|
2512
2918
|
}
|
|
2513
2919
|
}
|
|
@@ -2515,7 +2921,7 @@ function resolveExecutable(cmd, opts = {}) {
|
|
|
2515
2921
|
}
|
|
2516
2922
|
function defaultIsFile(full) {
|
|
2517
2923
|
try {
|
|
2518
|
-
return
|
|
2924
|
+
return existsSync4(full) && statSync2(full).isFile();
|
|
2519
2925
|
} catch {
|
|
2520
2926
|
return false;
|
|
2521
2927
|
}
|
|
@@ -2565,7 +2971,7 @@ function registerShellTools(registry, opts) {
|
|
|
2565
2971
|
const allowAll = opts.allowAll ?? false;
|
|
2566
2972
|
registry.register({
|
|
2567
2973
|
name: "run_command",
|
|
2568
|
-
description: "Run a shell command in the project root and return its combined stdout+stderr.
|
|
2974
|
+
description: "Run a shell command in the project root and return its combined stdout+stderr. Common read-only inspection and test/lint/typecheck commands run immediately; anything that could mutate state, install dependencies, or touch the network is refused until the user confirms it in the TUI. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.",
|
|
2569
2975
|
// Plan-mode gate: allow allowlisted commands through (git status,
|
|
2570
2976
|
// cargo check, ls, grep …) so the model can actually investigate
|
|
2571
2977
|
// during planning. Anything that would otherwise trigger a
|
|
@@ -2581,7 +2987,7 @@ function registerShellTools(registry, opts) {
|
|
|
2581
2987
|
properties: {
|
|
2582
2988
|
command: {
|
|
2583
2989
|
type: "string",
|
|
2584
|
-
description: "Full command line
|
|
2990
|
+
description: "Full command line. Tokenized with POSIX-ish quoting; no shell expansion, no pipes, no redirects."
|
|
2585
2991
|
},
|
|
2586
2992
|
timeoutSec: {
|
|
2587
2993
|
type: "integer",
|
|
@@ -2800,12 +3206,12 @@ ${i + 1}. ${r.title}`);
|
|
|
2800
3206
|
}
|
|
2801
3207
|
|
|
2802
3208
|
// src/env.ts
|
|
2803
|
-
import { readFileSync as
|
|
2804
|
-
import { resolve as
|
|
3209
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
3210
|
+
import { resolve as resolve4 } from "path";
|
|
2805
3211
|
function loadDotenv(path = ".env") {
|
|
2806
3212
|
let raw;
|
|
2807
3213
|
try {
|
|
2808
|
-
raw =
|
|
3214
|
+
raw = readFileSync4(resolve4(process.cwd(), path), "utf8");
|
|
2809
3215
|
} catch {
|
|
2810
3216
|
return;
|
|
2811
3217
|
}
|
|
@@ -2824,7 +3230,7 @@ function loadDotenv(path = ".env") {
|
|
|
2824
3230
|
}
|
|
2825
3231
|
|
|
2826
3232
|
// src/transcript.ts
|
|
2827
|
-
import { createWriteStream, readFileSync as
|
|
3233
|
+
import { createWriteStream, readFileSync as readFileSync5 } from "fs";
|
|
2828
3234
|
function recordFromLoopEvent(ev, extra) {
|
|
2829
3235
|
const rec = {
|
|
2830
3236
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2875,7 +3281,7 @@ function openTranscriptFile(path, meta) {
|
|
|
2875
3281
|
return stream;
|
|
2876
3282
|
}
|
|
2877
3283
|
function readTranscript(path) {
|
|
2878
|
-
const raw =
|
|
3284
|
+
const raw = readFileSync5(path, "utf8");
|
|
2879
3285
|
return parseTranscript(raw);
|
|
2880
3286
|
}
|
|
2881
3287
|
function isPlanStateEmptyShape(s) {
|
|
@@ -3487,7 +3893,7 @@ var McpClient = class {
|
|
|
3487
3893
|
const id = this.nextId++;
|
|
3488
3894
|
const frame = { jsonrpc: "2.0", id, method, params };
|
|
3489
3895
|
let abortHandler = null;
|
|
3490
|
-
const promise = new Promise((
|
|
3896
|
+
const promise = new Promise((resolve6, reject) => {
|
|
3491
3897
|
const timeout = setTimeout(() => {
|
|
3492
3898
|
this.pending.delete(id);
|
|
3493
3899
|
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
@@ -3496,7 +3902,7 @@ var McpClient = class {
|
|
|
3496
3902
|
);
|
|
3497
3903
|
}, this.requestTimeoutMs);
|
|
3498
3904
|
this.pending.set(id, {
|
|
3499
|
-
resolve:
|
|
3905
|
+
resolve: resolve6,
|
|
3500
3906
|
reject,
|
|
3501
3907
|
timeout
|
|
3502
3908
|
});
|
|
@@ -3619,12 +4025,12 @@ var StdioTransport = class {
|
|
|
3619
4025
|
}
|
|
3620
4026
|
async send(message) {
|
|
3621
4027
|
if (this.closed) throw new Error("MCP transport is closed");
|
|
3622
|
-
return new Promise((
|
|
4028
|
+
return new Promise((resolve6, reject) => {
|
|
3623
4029
|
const line = `${JSON.stringify(message)}
|
|
3624
4030
|
`;
|
|
3625
4031
|
this.child.stdin.write(line, "utf8", (err) => {
|
|
3626
4032
|
if (err) reject(err);
|
|
3627
|
-
else
|
|
4033
|
+
else resolve6();
|
|
3628
4034
|
});
|
|
3629
4035
|
});
|
|
3630
4036
|
}
|
|
@@ -3635,8 +4041,8 @@ var StdioTransport = class {
|
|
|
3635
4041
|
continue;
|
|
3636
4042
|
}
|
|
3637
4043
|
if (this.closed) return;
|
|
3638
|
-
const next = await new Promise((
|
|
3639
|
-
this.waiters.push(
|
|
4044
|
+
const next = await new Promise((resolve6) => {
|
|
4045
|
+
this.waiters.push(resolve6);
|
|
3640
4046
|
});
|
|
3641
4047
|
if (next === null) return;
|
|
3642
4048
|
yield next;
|
|
@@ -3702,8 +4108,8 @@ var SseTransport = class {
|
|
|
3702
4108
|
constructor(opts) {
|
|
3703
4109
|
this.url = opts.url;
|
|
3704
4110
|
this.headers = opts.headers ?? {};
|
|
3705
|
-
this.endpointReady = new Promise((
|
|
3706
|
-
this.resolveEndpoint =
|
|
4111
|
+
this.endpointReady = new Promise((resolve6, reject) => {
|
|
4112
|
+
this.resolveEndpoint = resolve6;
|
|
3707
4113
|
this.rejectEndpoint = reject;
|
|
3708
4114
|
});
|
|
3709
4115
|
this.endpointReady.catch(() => void 0);
|
|
@@ -3730,8 +4136,8 @@ var SseTransport = class {
|
|
|
3730
4136
|
continue;
|
|
3731
4137
|
}
|
|
3732
4138
|
if (this.closed) return;
|
|
3733
|
-
const next = await new Promise((
|
|
3734
|
-
this.waiters.push(
|
|
4139
|
+
const next = await new Promise((resolve6) => {
|
|
4140
|
+
this.waiters.push(resolve6);
|
|
3735
4141
|
});
|
|
3736
4142
|
if (next === null) return;
|
|
3737
4143
|
yield next;
|
|
@@ -3930,8 +4336,8 @@ async function trySection(load) {
|
|
|
3930
4336
|
}
|
|
3931
4337
|
|
|
3932
4338
|
// src/code/edit-blocks.ts
|
|
3933
|
-
import { existsSync as
|
|
3934
|
-
import { dirname as dirname3, resolve as
|
|
4339
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync6, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
4340
|
+
import { dirname as dirname3, resolve as resolve5 } from "path";
|
|
3935
4341
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
3936
4342
|
function parseEditBlocks(text) {
|
|
3937
4343
|
const out = [];
|
|
@@ -3949,8 +4355,8 @@ function parseEditBlocks(text) {
|
|
|
3949
4355
|
return out;
|
|
3950
4356
|
}
|
|
3951
4357
|
function applyEditBlock(block, rootDir) {
|
|
3952
|
-
const absRoot =
|
|
3953
|
-
const absTarget =
|
|
4358
|
+
const absRoot = resolve5(rootDir);
|
|
4359
|
+
const absTarget = resolve5(absRoot, block.path);
|
|
3954
4360
|
if (absTarget !== absRoot && !absTarget.startsWith(`${absRoot}${sep()}`)) {
|
|
3955
4361
|
return {
|
|
3956
4362
|
path: block.path,
|
|
@@ -3959,7 +4365,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
3959
4365
|
};
|
|
3960
4366
|
}
|
|
3961
4367
|
const searchEmpty = block.search.length === 0;
|
|
3962
|
-
const exists =
|
|
4368
|
+
const exists = existsSync5(absTarget);
|
|
3963
4369
|
try {
|
|
3964
4370
|
if (!exists) {
|
|
3965
4371
|
if (!searchEmpty) {
|
|
@@ -3969,11 +4375,11 @@ function applyEditBlock(block, rootDir) {
|
|
|
3969
4375
|
message: "file does not exist; to create it, use an empty SEARCH block"
|
|
3970
4376
|
};
|
|
3971
4377
|
}
|
|
3972
|
-
|
|
3973
|
-
|
|
4378
|
+
mkdirSync3(dirname3(absTarget), { recursive: true });
|
|
4379
|
+
writeFileSync3(absTarget, block.replace, "utf8");
|
|
3974
4380
|
return { path: block.path, status: "created" };
|
|
3975
4381
|
}
|
|
3976
|
-
const content =
|
|
4382
|
+
const content = readFileSync6(absTarget, "utf8");
|
|
3977
4383
|
if (searchEmpty) {
|
|
3978
4384
|
return {
|
|
3979
4385
|
path: block.path,
|
|
@@ -3990,7 +4396,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
3990
4396
|
};
|
|
3991
4397
|
}
|
|
3992
4398
|
const replaced = `${content.slice(0, idx)}${block.replace}${content.slice(idx + block.search.length)}`;
|
|
3993
|
-
|
|
4399
|
+
writeFileSync3(absTarget, replaced, "utf8");
|
|
3994
4400
|
return { path: block.path, status: "applied" };
|
|
3995
4401
|
} catch (err) {
|
|
3996
4402
|
return { path: block.path, status: "error", message: err.message };
|
|
@@ -4000,19 +4406,19 @@ function applyEditBlocks(blocks, rootDir) {
|
|
|
4000
4406
|
return blocks.map((b) => applyEditBlock(b, rootDir));
|
|
4001
4407
|
}
|
|
4002
4408
|
function snapshotBeforeEdits(blocks, rootDir) {
|
|
4003
|
-
const absRoot =
|
|
4409
|
+
const absRoot = resolve5(rootDir);
|
|
4004
4410
|
const seen = /* @__PURE__ */ new Set();
|
|
4005
4411
|
const snapshots = [];
|
|
4006
4412
|
for (const b of blocks) {
|
|
4007
4413
|
if (seen.has(b.path)) continue;
|
|
4008
4414
|
seen.add(b.path);
|
|
4009
|
-
const abs =
|
|
4010
|
-
if (!
|
|
4415
|
+
const abs = resolve5(absRoot, b.path);
|
|
4416
|
+
if (!existsSync5(abs)) {
|
|
4011
4417
|
snapshots.push({ path: b.path, prevContent: null });
|
|
4012
4418
|
continue;
|
|
4013
4419
|
}
|
|
4014
4420
|
try {
|
|
4015
|
-
snapshots.push({ path: b.path, prevContent:
|
|
4421
|
+
snapshots.push({ path: b.path, prevContent: readFileSync6(abs, "utf8") });
|
|
4016
4422
|
} catch {
|
|
4017
4423
|
snapshots.push({ path: b.path, prevContent: null });
|
|
4018
4424
|
}
|
|
@@ -4020,9 +4426,9 @@ function snapshotBeforeEdits(blocks, rootDir) {
|
|
|
4020
4426
|
return snapshots;
|
|
4021
4427
|
}
|
|
4022
4428
|
function restoreSnapshots(snapshots, rootDir) {
|
|
4023
|
-
const absRoot =
|
|
4429
|
+
const absRoot = resolve5(rootDir);
|
|
4024
4430
|
return snapshots.map((snap) => {
|
|
4025
|
-
const abs =
|
|
4431
|
+
const abs = resolve5(absRoot, snap.path);
|
|
4026
4432
|
if (abs !== absRoot && !abs.startsWith(`${absRoot}${sep()}`)) {
|
|
4027
4433
|
return {
|
|
4028
4434
|
path: snap.path,
|
|
@@ -4032,14 +4438,14 @@ function restoreSnapshots(snapshots, rootDir) {
|
|
|
4032
4438
|
}
|
|
4033
4439
|
try {
|
|
4034
4440
|
if (snap.prevContent === null) {
|
|
4035
|
-
if (
|
|
4441
|
+
if (existsSync5(abs)) unlinkSync3(abs);
|
|
4036
4442
|
return {
|
|
4037
4443
|
path: snap.path,
|
|
4038
4444
|
status: "applied",
|
|
4039
4445
|
message: "removed (the edit had created it)"
|
|
4040
4446
|
};
|
|
4041
4447
|
}
|
|
4042
|
-
|
|
4448
|
+
writeFileSync3(abs, snap.prevContent, "utf8");
|
|
4043
4449
|
return {
|
|
4044
4450
|
path: snap.path,
|
|
4045
4451
|
status: "applied",
|
|
@@ -4055,7 +4461,7 @@ function sep() {
|
|
|
4055
4461
|
}
|
|
4056
4462
|
|
|
4057
4463
|
// src/code/prompt.ts
|
|
4058
|
-
import { existsSync as
|
|
4464
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
4059
4465
|
import { join as join5 } from "path";
|
|
4060
4466
|
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.
|
|
4061
4467
|
|
|
@@ -4070,13 +4476,13 @@ You have a \`submit_plan\` tool that shows the user a markdown plan and lets the
|
|
|
4070
4476
|
|
|
4071
4477
|
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.
|
|
4072
4478
|
|
|
4073
|
-
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"
|
|
4479
|
+
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.
|
|
4074
4480
|
|
|
4075
4481
|
# Plan mode (/plan)
|
|
4076
4482
|
|
|
4077
4483
|
The user can ALSO enter "plan mode" via /plan, which is a stronger, explicit constraint:
|
|
4078
4484
|
- 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.
|
|
4079
|
-
- Read tools (read_file, list_directory, search_files, directory_tree, get_file_info) and allowlisted
|
|
4485
|
+
- 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.
|
|
4080
4486
|
- You MUST call submit_plan before anything will execute. Approve exits plan mode; Refine stays in; Cancel exits without implementing.
|
|
4081
4487
|
|
|
4082
4488
|
|
|
@@ -4114,9 +4520,13 @@ Rules:
|
|
|
4114
4520
|
- 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).
|
|
4115
4521
|
- Paths are relative to the working directory. Don't use absolute paths.
|
|
4116
4522
|
|
|
4523
|
+
# Trust what you already know
|
|
4524
|
+
|
|
4525
|
+
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.
|
|
4526
|
+
|
|
4117
4527
|
# Exploration
|
|
4118
4528
|
|
|
4119
|
-
-
|
|
4529
|
+
- Skip dependency, build, and VCS directories unless the user explicitly asks. The pinned .gitignore block (if any, below) is your authoritative denylist.
|
|
4120
4530
|
- 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.
|
|
4121
4531
|
|
|
4122
4532
|
# Style
|
|
@@ -4126,12 +4536,12 @@ Rules:
|
|
|
4126
4536
|
- If you need to explore first (list / grep / read), do it with tool calls before writing any prose \u2014 silence while exploring is fine.
|
|
4127
4537
|
`;
|
|
4128
4538
|
function codeSystemPrompt(rootDir) {
|
|
4129
|
-
const withMemory =
|
|
4539
|
+
const withMemory = applyMemoryStack(CODE_SYSTEM_PROMPT, rootDir);
|
|
4130
4540
|
const gitignorePath = join5(rootDir, ".gitignore");
|
|
4131
|
-
if (!
|
|
4541
|
+
if (!existsSync6(gitignorePath)) return withMemory;
|
|
4132
4542
|
let content;
|
|
4133
4543
|
try {
|
|
4134
|
-
content =
|
|
4544
|
+
content = readFileSync7(gitignorePath, "utf8");
|
|
4135
4545
|
} catch {
|
|
4136
4546
|
return withMemory;
|
|
4137
4547
|
}
|
|
@@ -4151,15 +4561,15 @@ ${truncated}
|
|
|
4151
4561
|
}
|
|
4152
4562
|
|
|
4153
4563
|
// src/config.ts
|
|
4154
|
-
import { chmodSync as chmodSync2, mkdirSync as
|
|
4155
|
-
import { homedir as
|
|
4564
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
4565
|
+
import { homedir as homedir3 } from "os";
|
|
4156
4566
|
import { dirname as dirname4, join as join6 } from "path";
|
|
4157
4567
|
function defaultConfigPath() {
|
|
4158
|
-
return join6(
|
|
4568
|
+
return join6(homedir3(), ".reasonix", "config.json");
|
|
4159
4569
|
}
|
|
4160
4570
|
function readConfig(path = defaultConfigPath()) {
|
|
4161
4571
|
try {
|
|
4162
|
-
const raw =
|
|
4572
|
+
const raw = readFileSync8(path, "utf8");
|
|
4163
4573
|
const parsed = JSON.parse(raw);
|
|
4164
4574
|
if (parsed && typeof parsed === "object") return parsed;
|
|
4165
4575
|
} catch {
|
|
@@ -4167,8 +4577,8 @@ function readConfig(path = defaultConfigPath()) {
|
|
|
4167
4577
|
return {};
|
|
4168
4578
|
}
|
|
4169
4579
|
function writeConfig(cfg, path = defaultConfigPath()) {
|
|
4170
|
-
|
|
4171
|
-
|
|
4580
|
+
mkdirSync4(dirname4(path), { recursive: true });
|
|
4581
|
+
writeFileSync4(path, JSON.stringify(cfg, null, 2), "utf8");
|
|
4172
4582
|
try {
|
|
4173
4583
|
chmodSync2(path, 384);
|
|
4174
4584
|
} catch {
|
|
@@ -4194,7 +4604,7 @@ function redactKey(key) {
|
|
|
4194
4604
|
}
|
|
4195
4605
|
|
|
4196
4606
|
// src/index.ts
|
|
4197
|
-
var VERSION = "0.4.
|
|
4607
|
+
var VERSION = "0.4.20";
|
|
4198
4608
|
export {
|
|
4199
4609
|
AppendOnlyLog,
|
|
4200
4610
|
CODE_SYSTEM_PROMPT,
|
|
@@ -4203,7 +4613,10 @@ export {
|
|
|
4203
4613
|
DeepSeekClient,
|
|
4204
4614
|
ImmutablePrefix,
|
|
4205
4615
|
MCP_PROTOCOL_VERSION,
|
|
4616
|
+
MEMORY_INDEX_FILE,
|
|
4617
|
+
MEMORY_INDEX_MAX_CHARS,
|
|
4206
4618
|
McpClient,
|
|
4619
|
+
MemoryStore,
|
|
4207
4620
|
NeedsConfirmationError,
|
|
4208
4621
|
PROJECT_MEMORY_FILE,
|
|
4209
4622
|
PROJECT_MEMORY_MAX_CHARS,
|
|
@@ -4214,6 +4627,7 @@ export {
|
|
|
4214
4627
|
StormBreaker,
|
|
4215
4628
|
ToolCallRepair,
|
|
4216
4629
|
ToolRegistry,
|
|
4630
|
+
USER_MEMORY_DIR,
|
|
4217
4631
|
Usage,
|
|
4218
4632
|
VERSION,
|
|
4219
4633
|
VolatileScratch,
|
|
@@ -4222,7 +4636,9 @@ export {
|
|
|
4222
4636
|
appendSessionMessage,
|
|
4223
4637
|
applyEditBlock,
|
|
4224
4638
|
applyEditBlocks,
|
|
4639
|
+
applyMemoryStack,
|
|
4225
4640
|
applyProjectMemory,
|
|
4641
|
+
applyUserMemory,
|
|
4226
4642
|
bridgeMcpTools,
|
|
4227
4643
|
claudeEquivalentCost,
|
|
4228
4644
|
codeSystemPrompt,
|
|
@@ -4261,6 +4677,7 @@ export {
|
|
|
4261
4677
|
parseMojeekResults,
|
|
4262
4678
|
parseTranscript,
|
|
4263
4679
|
prepareSpawn,
|
|
4680
|
+
projectHash,
|
|
4264
4681
|
quoteForCmdExe,
|
|
4265
4682
|
readConfig,
|
|
4266
4683
|
readProjectMemory,
|
|
@@ -4268,6 +4685,7 @@ export {
|
|
|
4268
4685
|
recordFromLoopEvent,
|
|
4269
4686
|
redactKey,
|
|
4270
4687
|
registerFilesystemTools,
|
|
4688
|
+
registerMemoryTools,
|
|
4271
4689
|
registerPlanTool,
|
|
4272
4690
|
registerShellTools,
|
|
4273
4691
|
registerWebTools,
|
|
@@ -4279,6 +4697,7 @@ export {
|
|
|
4279
4697
|
restoreSnapshots,
|
|
4280
4698
|
runBranches,
|
|
4281
4699
|
runCommand,
|
|
4700
|
+
sanitizeMemoryName,
|
|
4282
4701
|
sanitizeName as sanitizeSessionName,
|
|
4283
4702
|
saveApiKey,
|
|
4284
4703
|
scavengeToolCalls,
|