nexrall-code 0.5.92 → 0.5.94

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.
Files changed (2) hide show
  1. package/dist/index.js +157 -67
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -11149,19 +11149,19 @@ var require_memory = __commonJS({
11149
11149
  };
11150
11150
  }();
11151
11151
  Object.defineProperty(exports2, "__esModule", { value: true });
11152
- exports2.AGENT_MEMORY_MAX_BYTES = exports2.AGENT_MEMORY_INJECT_MAX = exports2.MEMORY_HARD_CAP_BYTES = exports2.MEMORY_COMPACT_TRIGGER_BYTES = exports2.MEMORY_MAX_BYTES = exports2.MEMORY_ENTRY_MAX_CHARS = void 0;
11152
+ exports2.AGENT_MEMORY_MAX_BYTES = exports2.AGENT_MEMORY_INJECT_MAX = exports2.MEMORY_ARCHIVE_MAX_BYTES = exports2.MEMORY_HARD_CAP_BYTES = exports2.MEMORY_COMPACT_TRIGGER_BYTES = exports2.MEMORY_MAX_BYTES = exports2.MEMORY_ENTRY_MAX_CHARS = void 0;
11153
11153
  exports2.memoryFilePath = memoryFilePath;
11154
11154
  exports2.writeMemory = writeMemory;
11155
11155
  exports2.readMemory = readMemory2;
11156
11156
  exports2.readAllMemory = readAllMemory2;
11157
11157
  exports2.clearMemory = clearMemory2;
11158
11158
  exports2.memoryStats = memoryStats2;
11159
+ exports2.compactMemoryIfNeeded = compactMemoryIfNeeded2;
11159
11160
  exports2.isSafeAgentName = isSafeAgentName;
11160
11161
  exports2.agentMemoryPath = agentMemoryPath;
11161
11162
  exports2.readAgentMemory = readAgentMemory;
11162
11163
  exports2.writeAgentMemory = writeAgentMemory;
11163
11164
  exports2.agentMemoryPreamble = agentMemoryPreamble;
11164
- exports2.compactMemoryIfNeeded = compactMemoryIfNeeded2;
11165
11165
  var fs9 = __importStar(__require("fs"));
11166
11166
  var os6 = __importStar(__require("os"));
11167
11167
  var path7 = __importStar(__require("path"));
@@ -11178,7 +11178,10 @@ var require_memory = __commonJS({
11178
11178
  exports2.MEMORY_MAX_BYTES = 12e3;
11179
11179
  exports2.MEMORY_COMPACT_TRIGGER_BYTES = 16e3;
11180
11180
  exports2.MEMORY_HARD_CAP_BYTES = 4 * exports2.MEMORY_COMPACT_TRIGGER_BYTES;
11181
+ exports2.MEMORY_ARCHIVE_MAX_BYTES = Math.floor(exports2.MEMORY_MAX_BYTES / 2);
11181
11182
  var FINGERPRINT_LEN = 60;
11183
+ var SOURCE_TAG_MAX_CHARS = 60;
11184
+ var SUPERSEDED_HEADER = "## Superseded";
11182
11185
  var _locks = /* @__PURE__ */ new Map();
11183
11186
  async function withLock(key, fn) {
11184
11187
  const prev = _locks.get(key) ?? Promise.resolve();
@@ -11207,49 +11210,107 @@ var require_memory = __commonJS({
11207
11210
  fs9.mkdirSync(path7.dirname(file), { recursive: true });
11208
11211
  fs9.writeFileSync(file, content, "utf-8");
11209
11212
  }
11210
- function evictOldest(content, maxBytes) {
11211
- if (Buffer.byteLength(content, "utf-8") <= maxBytes)
11212
- return content;
11213
- const lines = content.split("\n").filter((l2) => l2.trim().length > 0);
11214
- while (lines.length > 1 && Buffer.byteLength(lines.join("\n"), "utf-8") > maxBytes)
11215
- lines.shift();
11216
- return "\n" + lines.join("\n");
11213
+ function parseSections(content) {
11214
+ const lines = content.split("\n");
11215
+ const headerIdx = lines.findIndex((l2) => l2.trim() === SUPERSEDED_HEADER);
11216
+ if (headerIdx === -1) {
11217
+ return { active: lines.filter((l2) => l2.trim().length > 0), archived: [] };
11218
+ }
11219
+ return {
11220
+ active: lines.slice(0, headerIdx).filter((l2) => l2.trim().length > 0),
11221
+ archived: lines.slice(headerIdx + 1).filter((l2) => l2.trim().length > 0)
11222
+ };
11223
+ }
11224
+ function serializeSections(active, archived) {
11225
+ let out = "\n" + active.join("\n");
11226
+ if (archived.length > 0)
11227
+ out += `
11228
+
11229
+ ${SUPERSEDED_HEADER}
11230
+ ` + archived.join("\n");
11231
+ return out;
11217
11232
  }
11218
- async function writeMemory(content, scope, workDir) {
11233
+ function evictToFit(active, archived, maxBytes) {
11234
+ const a = [...active];
11235
+ const arch = [...archived];
11236
+ let content = serializeSections(a, arch);
11237
+ while (Buffer.byteLength(content, "utf-8") > maxBytes && (arch.length > 0 || a.length > 1)) {
11238
+ if (arch.length > 0)
11239
+ arch.shift();
11240
+ else
11241
+ a.shift();
11242
+ content = serializeSections(a, arch);
11243
+ }
11244
+ return content;
11245
+ }
11246
+ function sanitizeSourceTag(source2) {
11247
+ if (!source2)
11248
+ return "";
11249
+ return source2.trim().replace(/[[\]\n\r]/g, "").slice(0, SOURCE_TAG_MAX_CHARS);
11250
+ }
11251
+ async function writeMemory(content, scope, workDir, opts = {}) {
11219
11252
  let trimmed = content.trim();
11220
11253
  if (!trimmed)
11221
11254
  return { ok: false, already: false, scope, file: "" };
11222
- if (trimmed.length > exports2.MEMORY_ENTRY_MAX_CHARS)
11223
- trimmed = trimmed.slice(0, exports2.MEMORY_ENTRY_MAX_CHARS - 1).trimEnd() + "\u2026";
11255
+ const sourceTag = sanitizeSourceTag(opts.source);
11256
+ const prefix = sourceTag ? `[src: ${sourceTag}] ` : "";
11257
+ const budget = exports2.MEMORY_ENTRY_MAX_CHARS - prefix.length;
11258
+ if (budget <= 0) {
11259
+ if (trimmed.length > exports2.MEMORY_ENTRY_MAX_CHARS)
11260
+ trimmed = trimmed.slice(0, exports2.MEMORY_ENTRY_MAX_CHARS - 1).trimEnd() + "\u2026";
11261
+ } else if (trimmed.length > budget) {
11262
+ trimmed = trimmed.slice(0, budget - 1).trimEnd() + "\u2026";
11263
+ }
11264
+ const body = prefix && budget > 0 ? prefix + trimmed : trimmed;
11224
11265
  const file = memoryFilePath(scope, workDir);
11225
11266
  return withLock(file, async () => {
11226
11267
  const existing = readMemoryFile(file);
11227
- const fingerprint = trimmed.toLowerCase().slice(0, FINGERPRINT_LEN);
11228
- if (existing.toLowerCase().includes(fingerprint)) {
11229
- return { ok: true, already: true, scope, file };
11230
- }
11268
+ let { active, archived } = parseSections(existing);
11231
11269
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
11232
- let next = existing + `
11233
- - [${today}] ${trimmed}`;
11270
+ let superseded = false;
11271
+ if (opts.supersedes && opts.supersedes.trim()) {
11272
+ const supFingerprint = opts.supersedes.trim().toLowerCase().slice(0, FINGERPRINT_LEN);
11273
+ const stillActive = [];
11274
+ for (const line of active) {
11275
+ if (!superseded && line.toLowerCase().includes(supFingerprint)) {
11276
+ archived = [...archived, `${line} \u2014 superseded ${today}`];
11277
+ superseded = true;
11278
+ } else {
11279
+ stillActive.push(line);
11280
+ }
11281
+ }
11282
+ active = stillActive;
11283
+ }
11284
+ const fingerprint = body.toLowerCase().slice(0, FINGERPRINT_LEN);
11285
+ if (active.some((l2) => l2.toLowerCase().includes(fingerprint))) {
11286
+ if (superseded)
11287
+ writeMemoryFile(file, serializeSections(active, archived));
11288
+ return { ok: true, already: true, scope, file, superseded };
11289
+ }
11290
+ active = [...active, `- [${today}] ${body}`];
11291
+ let next = serializeSections(active, archived);
11234
11292
  if (Buffer.byteLength(next, "utf-8") > exports2.MEMORY_HARD_CAP_BYTES) {
11235
- next = evictOldest(next, exports2.MEMORY_MAX_BYTES);
11293
+ next = evictToFit(active, archived, exports2.MEMORY_MAX_BYTES);
11236
11294
  }
11237
11295
  writeMemoryFile(file, next);
11238
- return { ok: true, already: false, scope, file };
11296
+ return { ok: true, already: false, scope, file, superseded };
11239
11297
  });
11240
11298
  }
11241
- function readMemory2(scope, workDir) {
11299
+ function readMemory2(scope, workDir, opts = {}) {
11242
11300
  const file = memoryFilePath(scope, workDir);
11243
- return readMemoryFile(file).trim();
11301
+ const { active, archived } = parseSections(readMemoryFile(file));
11302
+ if (!opts.includeArchived)
11303
+ return active.length ? serializeSections(active, []).trim() : "";
11304
+ return serializeSections(active, archived).trim();
11244
11305
  }
11245
- function readAllMemory2(workDir) {
11306
+ function readAllMemory2(workDir, opts = {}) {
11246
11307
  const parts = [];
11247
- const globalMem = readMemory2("global");
11308
+ const globalMem = readMemory2("global", void 0, opts);
11248
11309
  if (globalMem)
11249
11310
  parts.push(`[Global memories \u2014 apply to every project]
11250
11311
  ${globalMem}`);
11251
11312
  if (workDir) {
11252
- const projMem = readMemory2("project", workDir);
11313
+ const projMem = readMemory2("project", workDir, opts);
11253
11314
  if (projMem)
11254
11315
  parts.push(`[Project memories \u2014 ${path7.basename(path7.resolve(workDir))}]
11255
11316
  ${projMem}`);
@@ -11266,8 +11327,52 @@ ${projMem}`);
11266
11327
  function memoryStats2(scope, workDir) {
11267
11328
  const file = memoryFilePath(scope, workDir);
11268
11329
  const content = readMemoryFile(file);
11269
- const entries = content.split("\n").filter((l2) => /^-\s\[\d{4}-\d{2}-\d{2}\]/.test(l2)).length;
11270
- return { file, bytes: Buffer.byteLength(content, "utf-8"), entries };
11330
+ const { active, archived } = parseSections(content);
11331
+ return {
11332
+ file,
11333
+ bytes: Buffer.byteLength(content, "utf-8"),
11334
+ entries: active.filter((l2) => /^-\s\[\d{4}-\d{2}-\d{2}\]/.test(l2)).length,
11335
+ archived: archived.filter((l2) => /^-\s\[\d{4}-\d{2}-\d{2}\]/.test(l2)).length
11336
+ };
11337
+ }
11338
+ async function compactMemoryIfNeeded2(scope, workDir, summarize) {
11339
+ const file = memoryFilePath(scope, workDir);
11340
+ return withLock(file, async () => {
11341
+ const content = readMemoryFile(file);
11342
+ if (Buffer.byteLength(content, "utf-8") <= exports2.MEMORY_COMPACT_TRIGGER_BYTES)
11343
+ return false;
11344
+ let { active, archived } = parseSections(content);
11345
+ let changed = false;
11346
+ if (Buffer.byteLength(archived.join("\n"), "utf-8") > exports2.MEMORY_ARCHIVE_MAX_BYTES) {
11347
+ while (archived.length > 1 && Buffer.byteLength(archived.join("\n"), "utf-8") > exports2.MEMORY_ARCHIVE_MAX_BYTES) {
11348
+ archived = archived.slice(1);
11349
+ }
11350
+ changed = true;
11351
+ }
11352
+ if (Buffer.byteLength(serializeSections(active, archived), "utf-8") > exports2.MEMORY_COMPACT_TRIGGER_BYTES && active.length >= 8) {
11353
+ const cut = Math.floor(active.length / 2);
11354
+ const older = active.slice(0, cut);
11355
+ const recent = active.slice(cut);
11356
+ const prompt2 = `Consolidate these persistent-memory bullet entries into a shorter set of bullets, merging duplicates/near-duplicates that describe the same fact in different words. Do NOT invent a resolution for facts that seem to conflict \u2014 contradictions are handled structurally elsewhere; if two entries genuinely disagree, keep both rather than guessing which is current. Keep each resulting bullet under ${exports2.MEMORY_ENTRY_MAX_CHARS} characters, one fact per line, prefixed "- [YYYY-MM-DD] " using the LATEST date among the entries it draws from, and preserve any leading "[src: ...]" provenance tag verbatim when present. Output ONLY the bullet lines, nothing else.
11357
+
11358
+ ${older.join("\n")}`;
11359
+ try {
11360
+ const summarized = (await summarize(prompt2)).trim();
11361
+ if (summarized) {
11362
+ active = [...summarized.split("\n").filter((l2) => l2.trim().length > 0), ...recent];
11363
+ changed = true;
11364
+ }
11365
+ } catch {
11366
+ }
11367
+ }
11368
+ if (!changed)
11369
+ return false;
11370
+ let next = serializeSections(active, archived);
11371
+ if (Buffer.byteLength(next, "utf-8") > exports2.MEMORY_MAX_BYTES)
11372
+ next = evictToFit(active, archived, exports2.MEMORY_MAX_BYTES);
11373
+ writeMemoryFile(file, next);
11374
+ return true;
11375
+ });
11271
11376
  }
11272
11377
  exports2.AGENT_MEMORY_INJECT_MAX = 8e3;
11273
11378
  exports2.AGENT_MEMORY_MAX_BYTES = 24e3;
@@ -11318,7 +11423,10 @@ ${projMem}`);
11318
11423
  let next = existing + `
11319
11424
  - [${today}] ${trimmed}`;
11320
11425
  if (Buffer.byteLength(next, "utf-8") > exports2.AGENT_MEMORY_MAX_BYTES) {
11321
- next = evictOldest(next, exports2.AGENT_MEMORY_MAX_BYTES);
11426
+ const lines = next.split("\n").filter((l2) => l2.trim().length > 0);
11427
+ while (lines.length > 1 && Buffer.byteLength(lines.join("\n"), "utf-8") > exports2.AGENT_MEMORY_MAX_BYTES)
11428
+ lines.shift();
11429
+ next = "\n" + lines.join("\n");
11322
11430
  }
11323
11431
  writeMemoryFile(file, next);
11324
11432
  return { ok: true, already: false, file };
@@ -11334,34 +11442,6 @@ These are notes YOU saved on previous runs. Treat them as your accumulated exper
11334
11442
 
11335
11443
  ${kept}`;
11336
11444
  }
11337
- async function compactMemoryIfNeeded2(scope, workDir, summarize) {
11338
- const file = memoryFilePath(scope, workDir);
11339
- return withLock(file, async () => {
11340
- const content = readMemoryFile(file);
11341
- if (Buffer.byteLength(content, "utf-8") <= exports2.MEMORY_COMPACT_TRIGGER_BYTES)
11342
- return false;
11343
- const lines = content.split("\n").filter((l2) => l2.trim().length > 0);
11344
- if (lines.length < 8)
11345
- return false;
11346
- const cut = Math.floor(lines.length / 2);
11347
- const older = lines.slice(0, cut);
11348
- const recent = lines.slice(cut);
11349
- const prompt2 = `Consolidate these persistent-memory bullet entries into a shorter set of bullets, merging duplicates/near-duplicates and dropping anything clearly stale or superseded by a later entry. Keep each resulting bullet under ${exports2.MEMORY_ENTRY_MAX_CHARS} characters, one fact per line, prefixed "- [YYYY-MM-DD] " using the LATEST date among the entries it draws from. Output ONLY the bullet lines, nothing else.
11350
-
11351
- ${older.join("\n")}`;
11352
- let summarized;
11353
- try {
11354
- summarized = (await summarize(prompt2)).trim();
11355
- } catch {
11356
- return false;
11357
- }
11358
- if (!summarized)
11359
- return false;
11360
- const next = summarized + "\n" + recent.join("\n");
11361
- writeMemoryFile(file, Buffer.byteLength(next, "utf-8") > exports2.MEMORY_MAX_BYTES ? evictOldest(next, exports2.MEMORY_MAX_BYTES) : next);
11362
- return true;
11363
- });
11364
- }
11365
11445
  }
11366
11446
  });
11367
11447
 
@@ -101327,18 +101407,24 @@ ${lines.join("\n")}` };
101327
101407
  if (!content)
101328
101408
  return { error: "content is required" };
101329
101409
  const scope = memoryScopeOf(input);
101330
- const r2 = await (0, memory_1.writeMemory)(content, scope, workDir);
101410
+ const supersedes = typeof input.supersedes === "string" ? input.supersedes.trim() : void 0;
101411
+ const source2 = typeof input.source === "string" ? input.source.trim() : void 0;
101412
+ const r2 = await (0, memory_1.writeMemory)(content, scope, workDir, { supersedes, source: source2 });
101331
101413
  if (!r2.ok)
101332
101414
  return { error: "failed to save memory" };
101333
- return { output: r2.already ? `Memory already recorded (skipped duplicate): ${content}` : `Memory saved (${scope}): ${content}` };
101415
+ if (r2.already)
101416
+ return { output: `Memory already recorded (skipped duplicate): ${content}` };
101417
+ const supersedeNote = r2.superseded ? " (older fact moved to Superseded)" : "";
101418
+ return { output: `Memory saved (${scope}): ${content}${supersedeNote}` };
101334
101419
  }
101335
101420
  async function memoryRead(input, workDir) {
101336
101421
  try {
101422
+ const includeArchived = input?.include_archived === true;
101337
101423
  if (input && (input.scope === "project" || input.scope === "global")) {
101338
- const mem2 = (0, memory_1.readMemory)(input.scope, workDir);
101424
+ const mem2 = (0, memory_1.readMemory)(input.scope, workDir, { includeArchived });
101339
101425
  return { output: mem2 || "No memories saved yet." };
101340
101426
  }
101341
- const mem = (0, memory_1.readAllMemory)(workDir);
101427
+ const mem = (0, memory_1.readAllMemory)(workDir, { includeArchived });
101342
101428
  return { output: mem || "No memories saved yet." };
101343
101429
  } catch (err) {
101344
101430
  return { error: err.message };
@@ -152454,7 +152540,7 @@ var App2 = ({ onReady }) => {
152454
152540
  }, [forceFullRedraw]);
152455
152541
  const collapseStartupPadding = (0, import_react35.useCallback)(async () => {
152456
152542
  await inkInstance?.waitUntilRenderFlush();
152457
- process.stdout.write("\x1B[2J\x1B[H");
152543
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
152458
152544
  repadBottomChrome();
152459
152545
  setStaticKey((k) => k + 1);
152460
152546
  }, [repadBottomChrome]);
@@ -153271,7 +153357,7 @@ function printHelp() {
153271
153357
  ["/rewind [id]", "List file checkpoints, or roll back to one"],
153272
153358
  ["/compact", "Summarize conversation to save tokens"],
153273
153359
  ["/init", "Generate nexrall.md for this project"],
153274
- ["/memory [global] [clear]", "View persistent memory (project or global); /memory clear to wipe"],
153360
+ ["/memory [global] [archived] [clear]", "View persistent memory (project or global); archived also shows superseded facts; clear wipes it"],
153275
153361
  ["/help", "Show this help"],
153276
153362
  ["/model [name]", "Switch model (e.g. claude-opus-5, gpt-5.4)"],
153277
153363
  ["/mode [ask|edit|plan|auto]", "Set agent mode"],
@@ -153818,6 +153904,7 @@ ${dirList}`;
153818
153904
  const memArgs = arg.toLowerCase().split(/\s+/).filter(Boolean);
153819
153905
  const wantsGlobalOnly = memArgs.includes("global");
153820
153906
  const wantsClear = memArgs.includes("clear");
153907
+ const wantsArchived = memArgs.includes("archived");
153821
153908
  const memScope = wantsGlobalOnly ? "global" : "project";
153822
153909
  if (wantsClear) {
153823
153910
  const label = memScope === "global" ? "GLOBAL" : "this PROJECT's";
@@ -153835,20 +153922,23 @@ ${dirList}`;
153835
153922
  console.log();
153836
153923
  if (wantsGlobalOnly) {
153837
153924
  const stats = (0, import_code_core3.memoryStats)("global");
153838
- const content = (0, import_code_core3.readMemory)("global");
153839
- console.log(source_default.bold(" Global memory ") + source_default.dim(`(${stats.entries} entries, ${(stats.bytes / 1024).toFixed(1)}KB) \u2014 ${stats.file}`));
153925
+ const content = (0, import_code_core3.readMemory)("global", void 0, { includeArchived: wantsArchived });
153926
+ const archivedNote = stats.archived > 0 ? `, ${stats.archived} archived` : "";
153927
+ console.log(source_default.bold(" Global memory ") + source_default.dim(`(${stats.entries} entries${archivedNote}, ${(stats.bytes / 1024).toFixed(1)}KB) \u2014 ${stats.file}`));
153840
153928
  console.log();
153841
153929
  console.log(content || source_default.dim(" No global memories saved yet."));
153842
153930
  } else {
153843
153931
  const projStats = (0, import_code_core3.memoryStats)("project", workDir);
153844
153932
  const globalStats = (0, import_code_core3.memoryStats)("global");
153845
- console.log(source_default.bold(" Memory") + source_default.dim(` \u2014 project: ${projStats.entries} entries (${(projStats.bytes / 1024).toFixed(1)}KB) \xB7 global: ${globalStats.entries} entries (${(globalStats.bytes / 1024).toFixed(1)}KB)`));
153933
+ const projArchived = projStats.archived > 0 ? `+${projStats.archived} archived ` : "";
153934
+ const globalArchived = globalStats.archived > 0 ? `+${globalStats.archived} archived ` : "";
153935
+ console.log(source_default.bold(" Memory") + source_default.dim(` \u2014 project: ${projStats.entries} ${projArchived}entries (${(projStats.bytes / 1024).toFixed(1)}KB) \xB7 global: ${globalStats.entries} ${globalArchived}entries (${(globalStats.bytes / 1024).toFixed(1)}KB)`));
153846
153936
  console.log();
153847
- const merged = (0, import_code_core3.readAllMemory)(workDir);
153937
+ const merged = (0, import_code_core3.readAllMemory)(workDir, { includeArchived: wantsArchived });
153848
153938
  console.log(merged || source_default.dim(" No memories saved yet."));
153849
153939
  }
153850
153940
  console.log();
153851
- console.log(source_default.dim(" Tip: /memory global \xB7 /memory clear \xB7 /memory global clear"));
153941
+ console.log(source_default.dim(" Tip: /memory global \xB7 /memory archived \xB7 /memory clear \xB7 /memory global clear"));
153852
153942
  rl.prompt();
153853
153943
  return;
153854
153944
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexrall-code",
3
- "version": "0.5.92",
3
+ "version": "0.5.94",
4
4
  "description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
5
5
  "keywords": [
6
6
  "ai",
@@ -41,7 +41,7 @@
41
41
  "release": "node build.js && node scripts/upload-release.cjs"
42
42
  },
43
43
  "dependencies": {
44
- "@nexrall/code-core": "^1.4.46",
44
+ "@nexrall/code-core": "^1.4.47",
45
45
  "chalk": "^5.3.0",
46
46
  "commander": "^12.0.0",
47
47
  "diff": "^5.2.0",