@inetafrica/open-claudia 2.6.8 → 2.6.9

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/core/entities.js CHANGED
@@ -287,7 +287,9 @@ function queryTerms(text) {
287
287
  const terms = [];
288
288
  for (const raw of String(text || "").toLowerCase().split(/[^a-z0-9_.-]+/)) {
289
289
  const t = raw.replace(/^[.-]+|[.-]+$/g, "");
290
- if (t.length < 3 || STOPWORDS.has(t) || seen.has(t)) continue;
290
+ // 2-char floor (not 3): keep technical acronyms like ip/ci/ha/ws/db/s3;
291
+ // common 2-letter words (is/it/in/of/on...) are caught by STOPWORDS.
292
+ if (t.length < 2 || STOPWORDS.has(t) || seen.has(t)) continue;
291
293
  seen.add(t);
292
294
  terms.push(t);
293
295
  if (terms.length >= 40) break;
@@ -90,6 +90,7 @@ ${clip(assistantText)}
90
90
  Decide. Rules:
91
91
  - Bias toward action: if the turn did real work on an identifiable topic (a named system, project, server, app, person, or domain), record it — at minimum a journal line. Most working turns deserve one. Reserve empty actions for small talk, pure status checks, and turns that contain nothing new.
92
92
  - UPDATE an existing pack when the turn touched that topic: append a journal line, and rewrite State if where-things-stand changed. Update Stance when the user expressed a lasting preference or rule; Procedure when a verified working method emerged.
93
+ - PROMOTE durable conclusions out of the Journal. A confirmed root-cause diagnosis, an established fact, or a settled design decision must go into Stance/Procedure/State (the parts always injected in full), NOT only a Journal line — Journal is truncated to the last few lines on injection, so a conclusion left only there will silently fall out of recall over time.
93
94
  - CREATE a pack when the turn worked on a durable topic no existing pack covers. Topics recur more than you expect — a named system or project is durable by default. Do not create packs for one-off trivia, and prefer update when an existing pack fits.
94
95
  - Never store secrets, tokens, passwords, or credentials.
95
96
  - State should be concise (under 150 words). Journal entries one sentence. Never start a journal or log sentence with a date — dates are prepended automatically.
package/core/packs.js CHANGED
@@ -287,7 +287,9 @@ function queryTerms(text) {
287
287
  const terms = [];
288
288
  for (const raw of String(text || "").toLowerCase().split(/[^a-z0-9_.-]+/)) {
289
289
  const t = raw.replace(/^[.-]+|[.-]+$/g, "");
290
- if (t.length < 3 || STOPWORDS.has(t) || seen.has(t)) continue;
290
+ // 2-char floor (not 3): keep technical acronyms like ip/ci/ha/ws/db/s3;
291
+ // common 2-letter words (is/it/in/of/on...) are caught by STOPWORDS.
292
+ if (t.length < 2 || STOPWORDS.has(t) || seen.has(t)) continue;
291
293
  seen.add(t);
292
294
  terms.push(t);
293
295
  if (terms.length >= 40) break;
@@ -5,6 +5,7 @@
5
5
  const fs = require("fs");
6
6
  const path = require("path");
7
7
  const { SOUL_FILE, CRONS_FILE, VAULT_FILE, FILES_DIR, BOT_DIR, WHISPER_CLI, FFMPEG } = require("./config");
8
+ const CONFIG_DIR = require("../config-dir");
8
9
  const { currentState } = require("./state");
9
10
  const { currentAdapter, currentChannelId } = require("./context");
10
11
  const { vault } = require("./vault-store");
@@ -311,11 +312,27 @@ function formatPackForContext(pack, packsLib) {
311
312
  const body = (pack.sections[section] || "").trim();
312
313
  if (body) parts.push(`#### ${section}\n${body}`);
313
314
  }
314
- const journal = (pack.sections.Journal || "").trim().split("\n").filter(Boolean).slice(-6).join("\n");
315
+ const journal = (pack.sections.Journal || "").trim().split("\n").filter(Boolean).slice(-10).join("\n");
315
316
  if (journal) parts.push(`#### Journal (recent)\n${journal}`);
316
317
  return clip(parts.join("\n\n"), PACK_INJECT_MAX_CHARS);
317
318
  }
318
319
 
320
+ // Compact anchor re-injected on later turns of the same session: the full
321
+ // pack was already shown once, but compaction can silently drop it from
322
+ // the rolling context, leaving a recall hole. A short Stance+State+latest
323
+ // reminder keeps the pack "present" without re-paying the full token cost.
324
+ function formatPackCompact(pack) {
325
+ const clip = (s, n) => (s.length > n ? s.slice(0, n) + "\n…[truncated]" : s);
326
+ const parts = [`### Pack: ${pack.name} (${pack.dir}) — recalled (full version shown earlier this session; read PACK.md for detail)`];
327
+ const stance = (pack.sections.Stance || "").trim();
328
+ if (stance) parts.push(`#### Stance\n${stance}`);
329
+ const state = (pack.sections.State || "").trim();
330
+ if (state) parts.push(`#### State\n${state}`);
331
+ const journal = (pack.sections.Journal || "").trim().split("\n").filter(Boolean).slice(-1).join("\n");
332
+ if (journal) parts.push(`#### Journal (latest)\n${journal}`);
333
+ return clip(parts.join("\n\n"), 1200);
334
+ }
335
+
319
336
  function buildPackBlock(matches) {
320
337
  try {
321
338
  const packsLib = require("./packs");
@@ -332,9 +349,14 @@ function buildPackBlock(matches) {
332
349
  used.push(m.dir);
333
350
  const key = `${adapter?.id || "?"}:${channelId || "?"}:${m.dir}`;
334
351
  const stamp = `${sess}:${pack.updated}`;
335
- if (packsInjectedFor.get(key) === stamp) continue;
336
- packsInjectedFor.set(key, stamp);
337
352
  lastInjected.packs.push(pack.name || m.dir);
353
+ if (packsInjectedFor.get(key) === stamp) {
354
+ // Already injected the full pack this session-version; re-inject a
355
+ // compact anchor so compaction can't silently drop it.
356
+ blocks.push(formatPackCompact(pack));
357
+ continue;
358
+ }
359
+ packsInjectedFor.set(key, stamp);
338
360
  blocks.push(formatPackForContext(pack, packsLib));
339
361
  }
340
362
  if (used.length > 0) setImmediate(() => { try { packsLib.touchUsed(used); } catch (e) {} });
@@ -458,6 +480,24 @@ function mergeMatches(primary, secondary, keyOf) {
458
480
  return out;
459
481
  }
460
482
 
483
+ // Append one JSONL line per turn recording what recall matched vs. what
484
+ // survived the relevance gate. This is the data we tune the match
485
+ // threshold with — disable via RECALL_DEBUG=off.
486
+ function logRecall(msg, candPacks, candEntities, keptPacks, keptEntities) {
487
+ if (String(process.env.RECALL_DEBUG || "on").toLowerCase() === "off") return;
488
+ try {
489
+ const rec = {
490
+ ts: new Date().toISOString(),
491
+ msg: String(msg || "").slice(0, 200),
492
+ candidatePacks: candPacks.map((m) => ({ dir: m.dir, score: m.score, origin: m.origin })),
493
+ keptPacks: keptPacks.map((m) => m.dir),
494
+ candidateEntities: candEntities.map((m) => ({ slug: m.slug, score: m.score, origin: m.origin })),
495
+ keptEntities: keptEntities.map((m) => m.slug),
496
+ };
497
+ fs.appendFileSync(path.join(CONFIG_DIR, "recall-debug.jsonl"), JSON.stringify(rec) + "\n");
498
+ } catch (e) {}
499
+ }
500
+
461
501
  async function promptWithDynamicContext(prompt) {
462
502
  lastInjected = { packs: [], entities: [] };
463
503
  try {
@@ -485,9 +525,12 @@ async function promptWithDynamicContext(prompt) {
485
525
  (m) => m.slug,
486
526
  );
487
527
  } catch (e) {}
528
+ const candPacks = packMatches;
529
+ const candEntities = entityMatches;
488
530
  ({ packMatches, entityMatches } = await filterMatches(userText, fullContext, packMatches, entityMatches));
489
531
  packMatches = packMatches.slice(0, 3);
490
532
  entityMatches = entityMatches.slice(0, 4);
533
+ logRecall(userText, candPacks, candEntities, packMatches, entityMatches);
491
534
  return `${buildDynamicContextBlock()}${buildPackBlock(packMatches)}${buildEntityBlock(entityMatches)}\n\nCurrent user request:\n${prompt}`;
492
535
  } catch (e) {
493
536
  return prompt;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "2.6.8",
3
+ "version": "2.6.9",
4
4
  "description": "Your always-on AI coding assistant — Claude Code, Cursor Agent, and OpenAI Codex via Telegram or Kazee Chat",
5
5
  "main": "bot.js",
6
6
  "bin": {