recallnest 1.4.1 → 2.2.1
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/.env +4 -0
- package/CLAUDE.md +7 -3
- package/README.md +202 -130
- package/README_CN.md +263 -190
- package/bin/recallnest.mjs +62 -0
- package/config.json +55 -0
- package/daily-recall.sh +23 -0
- package/integrations/setup-all.sh +31 -0
- package/logs/daily-recall.error.log +0 -0
- package/logs/daily-recall.log +30 -0
- package/logs/ingest-2026-04-11.log +110 -0
- package/logs/ingest-2026-04-12.log +90 -0
- package/logs/launchagent-stderr.log +0 -0
- package/logs/launchagent-stdout.log +0 -0
- package/package.json +10 -3
- package/src/access-tracker.ts +3 -2
- package/src/activity-counter.ts +109 -0
- package/src/adaptive-retrieval.ts +6 -5
- package/src/admission-control.ts +163 -0
- package/src/anchor-generator.ts +80 -0
- package/src/api-server.ts +10 -0
- package/src/audit-log.ts +122 -0
- package/src/auto-gc.ts +98 -29
- package/src/auto-recall.ts +33 -4
- package/src/capture-engine.ts +372 -23
- package/src/cascade-forget.ts +3 -2
- package/src/cli.ts +107 -5
- package/src/compat.ts +22 -0
- package/src/confidence-tracker.ts +210 -47
- package/src/conflict-store.ts +12 -3
- package/src/consolidation-engine.ts +296 -8
- package/src/context-collapse-renderer.ts +222 -0
- package/src/context-composer-stable-selection.ts +5 -1
- package/src/context-composer.ts +239 -4
- package/src/context-reconstructor.ts +404 -0
- package/src/conversation-importer.ts +441 -0
- package/src/data-checkup.ts +232 -0
- package/src/decay-engine.ts +84 -0
- package/src/distill-lock.ts +122 -0
- package/src/doctor.ts +8 -10
- package/src/dream-pipeline.ts +227 -0
- package/src/emotion-detector.ts +90 -0
- package/src/eval.ts +3 -2
- package/src/forget-engine.ts +269 -0
- package/src/frequency-tracker.ts +175 -0
- package/src/graph-export.ts +644 -0
- package/src/ingest.ts +36 -6
- package/src/interference-detector.ts +187 -0
- package/src/kg-extractor.ts +15 -24
- package/src/kg-store.ts +2 -1
- package/src/language-hook.ts +67 -0
- package/src/llm-client.ts +289 -6
- package/src/mcp-server.ts +707 -80
- package/src/memory-assets.ts +22 -5
- package/src/memory-evolution.ts +365 -0
- package/src/memory-lint.ts +429 -0
- package/src/memory-output.ts +69 -0
- package/src/memory-schema.ts +74 -2
- package/src/narrative-schema.ts +79 -0
- package/src/narrative-tagger.ts +164 -0
- package/src/pii-detector.ts +98 -0
- package/src/post-retrieval-filter.ts +76 -0
- package/src/prediction-engine.ts +253 -0
- package/src/preference-slots.ts +65 -2
- package/src/prospective-memory.ts +215 -0
- package/src/query-expander.ts +67 -0
- package/src/recall-governor.ts +183 -0
- package/src/retention-policy.ts +90 -0
- package/src/retriever.ts +828 -55
- package/src/rif.ts +34 -8
- package/src/runtime-config.ts +23 -8
- package/src/scope-inventory.ts +4 -3
- package/src/scope-policy.ts +4 -0
- package/src/session-distiller.ts +307 -0
- package/src/session-output.ts +40 -0
- package/src/session-schema.ts +37 -0
- package/src/session-store.ts +7 -2
- package/src/skill-engine.ts +208 -0
- package/src/skill-promotion.ts +293 -0
- package/src/skill-schema.ts +39 -0
- package/src/store.ts +96 -10
- package/src/topic-tag.ts +66 -0
- package/src/tracker.ts +3 -1
- package/src/ui-server.ts +80 -8
- package/src/workflow-observation-scope-review.ts +2 -1
- package/src/workflow-observation-store.ts +2 -1
- package/src/write-verifier.ts +126 -0
- package/.claude-plugin/CLAUDE.md +0 -29
- package/.claude-plugin/marketplace.json +0 -18
- package/.github/workflows/ci.yml +0 -28
- package/.mcp.json +0 -9
- package/CHANGELOG.md +0 -51
- package/DEMO.md +0 -94
- package/EVAL.md +0 -89
- package/FAILURES.md +0 -75
- package/OPERATIONS.md +0 -59
- package/ROADMAP.md +0 -198
- package/assets/recallnest-workbench.png +0 -0
- package/assets/ui/app.js +0 -807
- package/assets/ui/index.html +0 -154
- package/assets/ui/styles.css +0 -605
- package/assets/wechat-qr.jpg +0 -0
- package/daily-reflections/2026-03-05.md +0 -15
- package/daily-reflections/2026-03-06.md +0 -15
- package/daily-reflections/2026-03-07.md +0 -7
- package/daily-reflections/2026-03-08.md +0 -34
- package/daily-reflections/2026-03-09.md +0 -35
- package/daily-reflections/2026-03-10.md +0 -36
- package/daily-reflections/2026-03-11.md +0 -35
- package/daily-reflections/2026-03-12.md +0 -36
- package/daily-reflections/2026-03-13.md +0 -35
- package/daily-reflections/2026-03-14.md +0 -35
- package/daily-reflections/2026-03-15.md +0 -35
- package/daily-reflections/2026-03-16.md +0 -41
- package/daily-reflections/2026-03-17.md +0 -10
- package/daily-reflections/2026-03-18.md +0 -10
- package/daily-reflections/2026-03-19.md +0 -10
- package/daily-reflections/2026-03-20.md +0 -10
- package/daily-reflections/2026-03-21.md +0 -10
- package/daily-reflections/2026-03-22.md +0 -10
- package/daily-reflections/2026-03-23.md +0 -10
- package/daily-reflections/2026-03-24.md +0 -10
- package/daily-reflections/2026-03-25.md +0 -32
- package/daily-reflections/2026-03-26.md +0 -37
- package/daily-reflections/2026-03-27.md +0 -35
- package/daily-reflections/2026-03-28.md +0 -35
- package/daily-reflections/2026-03-29.md +0 -35
- package/daily-reflections/2026-03-30.md +0 -36
- package/daily-reflections/2026-03-31.md +0 -35
- package/daily-reflections/2026-04-01.md +0 -35
- package/docs/api-reference.md +0 -1291
- package/docs/architecture.md +0 -168
- package/docs/memory-boundary-contract.md +0 -226
- package/docs/memory-categories.md +0 -119
- package/docs/pipeline-audit-ultramemory.md +0 -177
- package/docs/plans/2026-03-08-query-expansion.md +0 -270
- package/docs/plans/2026-03-16-continuity-layer.md +0 -196
- package/docs/plans/mcp-tiering-plan.md +0 -126
- package/docs/research/recall-quality-audit-2026-03-22.md +0 -153
- package/docs/self-evolution.md +0 -198
- package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +0 -782
- package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +0 -77
- package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +0 -95
- package/eval/continuity/results-2026-03-16.md +0 -117
- package/eval/continuity/results-2026-03-19.md +0 -453
- package/eval/continuity/runbook.md +0 -110
- package/eval/continuity/seed-cases.md +0 -54
- package/eval/reports/2026-03-06-abstract-query-baseline.md +0 -96
- package/eval/reports/2026-03-06-aws-ssh-baseline.md +0 -82
- package/eval/reports/2026-03-06-baseline.md +0 -82
- package/eval/reports/query-expansion.md +0 -138
- package/lm +0 -19
- package/plan-v1.2-distributable.md +0 -105
- package/scripts/archive-distilled-junk.ts +0 -116
- package/scripts/backfill-l0l1l2.ts +0 -98
- package/scripts/blind-scan.ts +0 -588
- package/scripts/cleanup-step1-exact-dups.ts +0 -117
- package/scripts/cleanup-step2-template-dups.ts +0 -113
- package/scripts/cleanup-step3-stale-content.ts +0 -149
- package/scripts/cleanup-step4-orphan-scopes.ts +0 -131
- package/scripts/delete-distilled-junk.ts +0 -88
- package/scripts/distill-facts.ts +0 -305
- package/scripts/health-check.ts +0 -343
- package/scripts/incremental-ingest.sh +0 -71
- package/scripts/mlp-health-check.ts +0 -382
- package/scripts/pull-claude-desktop.ts +0 -515
- package/scripts/re-ingest-cc.sh +0 -66
- package/scripts/rebalance-memory-health.ts +0 -181
- package/scripts/run-desktop-ingest.sh +0 -11
- package/scripts/start-server.sh +0 -12
- package/scripts/viz-dashboard.html +0 -423
- package/scripts/viz-export.ts +0 -703
- package/src/__tests__/access-tracker-novelty.test.ts +0 -228
- package/src/__tests__/auto-consolidation.test.ts +0 -168
- package/src/__tests__/auto-recall.test.ts +0 -131
- package/src/__tests__/batch-dedup.test.ts +0 -65
- package/src/__tests__/capture-engine.test.ts +0 -1142
- package/src/__tests__/capture-heuristic.test.ts +0 -171
- package/src/__tests__/cascade-forget.test.ts +0 -210
- package/src/__tests__/category-min-scores.test.ts +0 -33
- package/src/__tests__/confidence-tracker.test.ts +0 -196
- package/src/__tests__/conflict-advisor.test.ts +0 -206
- package/src/__tests__/conflict-escalation.test.ts +0 -149
- package/src/__tests__/conflict-lifecycle.test.ts +0 -83
- package/src/__tests__/conflict-output.test.ts +0 -85
- package/src/__tests__/conflict-store.test.ts +0 -374
- package/src/__tests__/consolidation-engine.test.ts +0 -181
- package/src/__tests__/context-composer-pins.test.ts +0 -64
- package/src/__tests__/context-composer-task-selection.test.ts +0 -24
- package/src/__tests__/context-renderer.test.ts +0 -91
- package/src/__tests__/continuity-eval.test.ts +0 -275
- package/src/__tests__/continuity-schema.test.ts +0 -97
- package/src/__tests__/core-recall-eval.test.ts +0 -235
- package/src/__tests__/core-summary.test.ts +0 -109
- package/src/__tests__/dedup-actions.test.ts +0 -58
- package/src/__tests__/deterministic-id.test.ts +0 -39
- package/src/__tests__/doctor.test.ts +0 -303
- package/src/__tests__/drill-down.test.ts +0 -108
- package/src/__tests__/embedder.test.ts +0 -122
- package/src/__tests__/entity-resolver.test.ts +0 -58
- package/src/__tests__/event-segmenter.test.ts +0 -153
- package/src/__tests__/hotness-blend.test.ts +0 -76
- package/src/__tests__/ingest.test.ts +0 -360
- package/src/__tests__/kg-extractor.test.ts +0 -184
- package/src/__tests__/kg-store.test.ts +0 -198
- package/src/__tests__/llm-consolidation.test.ts +0 -226
- package/src/__tests__/memory-boundaries.test.ts +0 -237
- package/src/__tests__/memory-health-rebalance.test.ts +0 -144
- package/src/__tests__/memory-output.test.ts +0 -215
- package/src/__tests__/multi-vector.test.ts +0 -242
- package/src/__tests__/ppr-traversal.test.ts +0 -256
- package/src/__tests__/preference-guard-smoke.test.ts +0 -211
- package/src/__tests__/preference-matcher.test.ts +0 -230
- package/src/__tests__/preference-slots.test.ts +0 -106
- package/src/__tests__/prospective-memory.test.ts +0 -283
- package/src/__tests__/query-entity-detector.test.ts +0 -171
- package/src/__tests__/query-expander.test.ts +0 -34
- package/src/__tests__/recall-mode.test.ts +0 -64
- package/src/__tests__/result-synthesizer.test.ts +0 -198
- package/src/__tests__/resume-context.test.ts +0 -4743
- package/src/__tests__/retrieval-trace.test.ts +0 -213
- package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
- package/src/__tests__/retroactive-boost.test.ts +0 -223
- package/src/__tests__/scope-inventory.test.ts +0 -200
- package/src/__tests__/scope-policy.test.ts +0 -69
- package/src/__tests__/session-checkpoint.test.ts +0 -259
- package/src/__tests__/session-gc.test.ts +0 -215
- package/src/__tests__/store-scope-audit.test.ts +0 -81
- package/src/__tests__/temporal-parser.test.ts +0 -241
- package/src/__tests__/term-registry.test.ts +0 -121
- package/src/__tests__/tool-output-compressor.test.ts +0 -133
- package/src/__tests__/version-manager.test.ts +0 -213
- package/src/__tests__/workflow-observation.test.ts +0 -400
package/src/store.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { dirname } from "node:path";
|
|
|
9
9
|
import { logWarn } from "./stderr-log.js";
|
|
10
10
|
import type { DurableMemoryCategory } from "./memory-schema.js";
|
|
11
11
|
import { matchesScopeFilter } from "./scope-policy.js";
|
|
12
|
+
import { detectEmotionIfEnabled } from "./emotion-detector.js";
|
|
12
13
|
|
|
13
14
|
// ============================================================================
|
|
14
15
|
// Types
|
|
@@ -42,6 +43,8 @@ export interface MemoryEntry {
|
|
|
42
43
|
importance: number;
|
|
43
44
|
timestamp: number;
|
|
44
45
|
metadata?: string; // JSON string for extensible metadata — includes l0_abstract/l1_overview/l2_content/tier
|
|
46
|
+
language?: string; // ISO 639-1: "zh"|"ja"|"ko"|"en"
|
|
47
|
+
fts_text?: string; // Pre-tokenized text for FTS indexing
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
export interface MemorySearchResult {
|
|
@@ -258,14 +261,24 @@ export class MemoryStore {
|
|
|
258
261
|
try {
|
|
259
262
|
table = await db.openTable(TABLE_NAME);
|
|
260
263
|
|
|
261
|
-
//
|
|
264
|
+
// Backward compatibility: add missing columns to existing tables
|
|
262
265
|
try {
|
|
263
266
|
const sample = await table.query().limit(1).toArray();
|
|
264
|
-
if (sample.length > 0
|
|
265
|
-
|
|
267
|
+
if (sample.length > 0) {
|
|
268
|
+
if (!("scope" in sample[0])) {
|
|
269
|
+
logWarn("Adding scope column for backward compatibility with existing data");
|
|
270
|
+
}
|
|
271
|
+
if (!("language" in sample[0])) {
|
|
272
|
+
logWarn("Adding language column for backward compatibility");
|
|
273
|
+
await table.addColumns([{ name: "language", valueSql: "'en'" }]);
|
|
274
|
+
}
|
|
275
|
+
if (!("fts_text" in sample[0])) {
|
|
276
|
+
logWarn("Adding fts_text column for backward compatibility");
|
|
277
|
+
await table.addColumns([{ name: "fts_text", valueSql: "text" }]);
|
|
278
|
+
}
|
|
266
279
|
}
|
|
267
280
|
} catch (err) {
|
|
268
|
-
logWarn("Could not check table schema:", err);
|
|
281
|
+
logWarn("Could not check/migrate table schema:", err);
|
|
269
282
|
}
|
|
270
283
|
} catch (_openErr) {
|
|
271
284
|
// Table doesn't exist yet — create it
|
|
@@ -278,6 +291,8 @@ export class MemoryStore {
|
|
|
278
291
|
importance: 0,
|
|
279
292
|
timestamp: 0,
|
|
280
293
|
metadata: "{}",
|
|
294
|
+
language: "en",
|
|
295
|
+
fts_text: "__schema__",
|
|
281
296
|
};
|
|
282
297
|
|
|
283
298
|
try {
|
|
@@ -325,13 +340,13 @@ export class MemoryStore {
|
|
|
325
340
|
// Check if FTS index already exists
|
|
326
341
|
const indices = await table.listIndices();
|
|
327
342
|
const hasFtsIndex = indices?.some((idx: any) =>
|
|
328
|
-
idx.indexType === "FTS" || idx.columns?.includes("
|
|
343
|
+
idx.indexType === "FTS" || idx.columns?.includes("fts_text")
|
|
329
344
|
);
|
|
330
345
|
|
|
331
346
|
if (!hasFtsIndex) {
|
|
332
347
|
// LanceDB @lancedb/lancedb >=0.26: use Index.fts() config
|
|
333
348
|
const lancedb = await loadLanceDB();
|
|
334
|
-
await table.createIndex("
|
|
349
|
+
await table.createIndex("fts_text", {
|
|
335
350
|
config: (lancedb as any).Index.fts(),
|
|
336
351
|
});
|
|
337
352
|
}
|
|
@@ -348,8 +363,17 @@ export class MemoryStore {
|
|
|
348
363
|
id: entry.id || deterministicId(entry.scope, entry.text),
|
|
349
364
|
timestamp: Date.now(),
|
|
350
365
|
metadata: entry.metadata || "{}",
|
|
366
|
+
language: entry.language || "en",
|
|
367
|
+
fts_text: entry.fts_text || entry.text,
|
|
351
368
|
};
|
|
352
369
|
|
|
370
|
+
const emotionResult = detectEmotionIfEnabled(fullEntry.text);
|
|
371
|
+
if (emotionResult) {
|
|
372
|
+
const existingMeta = JSON.parse(fullEntry.metadata || "{}");
|
|
373
|
+
existingMeta.emotion = emotionResult;
|
|
374
|
+
fullEntry.metadata = JSON.stringify(existingMeta);
|
|
375
|
+
}
|
|
376
|
+
|
|
353
377
|
try {
|
|
354
378
|
await this.table!.add([fullEntry]);
|
|
355
379
|
} catch (err: any) {
|
|
@@ -375,10 +399,22 @@ export class MemoryStore {
|
|
|
375
399
|
id: deterministicId(entry.scope, entry.text),
|
|
376
400
|
timestamp: Date.now(),
|
|
377
401
|
metadata: entry.metadata || "{}",
|
|
402
|
+
language: entry.language || "en",
|
|
403
|
+
fts_text: entry.fts_text || entry.text,
|
|
378
404
|
}));
|
|
379
405
|
|
|
406
|
+
const enrichedEntries = fullEntries.map(e => {
|
|
407
|
+
const emotionResult = detectEmotionIfEnabled(e.text);
|
|
408
|
+
if (emotionResult) {
|
|
409
|
+
const meta = JSON.parse(e.metadata || "{}");
|
|
410
|
+
meta.emotion = emotionResult;
|
|
411
|
+
return { ...e, metadata: JSON.stringify(meta) };
|
|
412
|
+
}
|
|
413
|
+
return e;
|
|
414
|
+
});
|
|
415
|
+
|
|
380
416
|
try {
|
|
381
|
-
await this.table!.add(
|
|
417
|
+
await this.table!.add(enrichedEntries);
|
|
382
418
|
} catch (err: any) {
|
|
383
419
|
const code = err.code || "";
|
|
384
420
|
const message = err.message || String(err);
|
|
@@ -414,8 +450,22 @@ export class MemoryStore {
|
|
|
414
450
|
importance: Number.isFinite(entry.importance) ? entry.importance : 0.5,
|
|
415
451
|
timestamp: Number.isFinite(entry.timestamp) ? entry.timestamp : Date.now(),
|
|
416
452
|
metadata: entry.metadata || "{}",
|
|
453
|
+
language: entry.language || "en",
|
|
454
|
+
fts_text: entry.fts_text || entry.text,
|
|
417
455
|
};
|
|
418
456
|
|
|
457
|
+
// HP-emo: Backfill emotion if absent during import
|
|
458
|
+
try {
|
|
459
|
+
const metaParsed = JSON.parse(full.metadata || "{}");
|
|
460
|
+
if (!metaParsed.emotion) {
|
|
461
|
+
const emotionResult = detectEmotionIfEnabled(full.text);
|
|
462
|
+
if (emotionResult) {
|
|
463
|
+
metaParsed.emotion = emotionResult;
|
|
464
|
+
full.metadata = JSON.stringify(metaParsed);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
} catch { /* malformed metadata — skip backfill */ }
|
|
468
|
+
|
|
419
469
|
await this.table!.add([full]);
|
|
420
470
|
return full;
|
|
421
471
|
}
|
|
@@ -716,6 +766,30 @@ export class MemoryStore {
|
|
|
716
766
|
};
|
|
717
767
|
}
|
|
718
768
|
|
|
769
|
+
/**
|
|
770
|
+
* Batch-fetch vectors for a list of entry IDs.
|
|
771
|
+
* Used by graph-export for cross-scope semantic bridge computation.
|
|
772
|
+
* Single LanceDB query — much faster than N individual getById calls.
|
|
773
|
+
*/
|
|
774
|
+
async getVectors(ids: string[]): Promise<Map<string, number[]>> {
|
|
775
|
+
await this.ensureInitialized();
|
|
776
|
+
if (ids.length === 0) return new Map();
|
|
777
|
+
|
|
778
|
+
const conditions = ids.map(id => `id = '${escapeSqlLiteral(id)}'`).join(" OR ");
|
|
779
|
+
const rows = await this.table!.query()
|
|
780
|
+
.select(["id", "vector"])
|
|
781
|
+
.where(conditions)
|
|
782
|
+
.limit(ids.length)
|
|
783
|
+
.toArray();
|
|
784
|
+
|
|
785
|
+
const result = new Map<string, number[]>();
|
|
786
|
+
for (const row of rows) {
|
|
787
|
+
const vec = Array.from(row.vector as Iterable<number>);
|
|
788
|
+
if (vec.length > 0) result.set(row.id as string, vec);
|
|
789
|
+
}
|
|
790
|
+
return result;
|
|
791
|
+
}
|
|
792
|
+
|
|
719
793
|
async stats(scopeFilter?: string[]): Promise<{
|
|
720
794
|
totalCount: number;
|
|
721
795
|
scopeCounts: Record<string, number>;
|
|
@@ -800,7 +874,7 @@ export class MemoryStore {
|
|
|
800
874
|
|
|
801
875
|
async update(
|
|
802
876
|
id: string,
|
|
803
|
-
updates: { text?: string; vector?: number[]; importance?: number; category?: MemoryEntry["category"]; metadata?: string; timestamp?: number },
|
|
877
|
+
updates: { text?: string; vector?: number[]; importance?: number; category?: MemoryEntry["category"]; metadata?: string; timestamp?: number; language?: string; fts_text?: string },
|
|
804
878
|
scopeFilter?: string[]
|
|
805
879
|
): Promise<MemoryEntry | null> {
|
|
806
880
|
await this.ensureInitialized();
|
|
@@ -821,7 +895,7 @@ export class MemoryStore {
|
|
|
821
895
|
rows = await this.table!.query().where(`id = '${safeId}'`).limit(1).toArray();
|
|
822
896
|
} else {
|
|
823
897
|
// Prefix match
|
|
824
|
-
const all = await this.table!.query().select(["id", "text", "vector", "category", "scope", "importance", "timestamp", "metadata"]).toArray();
|
|
898
|
+
const all = await this.table!.query().select(["id", "text", "vector", "category", "scope", "importance", "timestamp", "metadata", "language", "fts_text"]).toArray();
|
|
825
899
|
rows = all.filter((r: any) => (r.id as string).startsWith(id));
|
|
826
900
|
if (rows.length > 1) {
|
|
827
901
|
throw new Error(`Ambiguous prefix "${id}" matches ${rows.length} memories. Use a longer prefix or full ID.`);
|
|
@@ -838,7 +912,7 @@ export class MemoryStore {
|
|
|
838
912
|
throw new Error(`Memory ${id} is outside accessible scopes`);
|
|
839
913
|
}
|
|
840
914
|
|
|
841
|
-
// Build updated entry, preserving original timestamp
|
|
915
|
+
// Build updated entry, preserving original timestamp and language fields
|
|
842
916
|
const updated: MemoryEntry = {
|
|
843
917
|
id: row.id as string,
|
|
844
918
|
text: updates.text ?? (row.text as string),
|
|
@@ -848,8 +922,20 @@ export class MemoryStore {
|
|
|
848
922
|
importance: updates.importance ?? Number(row.importance),
|
|
849
923
|
timestamp: updates.timestamp ?? Number(row.timestamp),
|
|
850
924
|
metadata: updates.metadata ?? ((row.metadata as string) || "{}"),
|
|
925
|
+
language: updates.language ?? ((row.language as string) || "en"),
|
|
926
|
+
fts_text: updates.fts_text ?? ((row.fts_text as string) || (updates.text ?? (row.text as string))),
|
|
851
927
|
};
|
|
852
928
|
|
|
929
|
+
// HP-emo: Re-detect emotion when text changes
|
|
930
|
+
if (updates.text) {
|
|
931
|
+
const emotionResult = detectEmotionIfEnabled(updates.text);
|
|
932
|
+
if (emotionResult) {
|
|
933
|
+
const meta = JSON.parse(updated.metadata || "{}");
|
|
934
|
+
meta.emotion = emotionResult;
|
|
935
|
+
updated.metadata = JSON.stringify(meta);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
853
939
|
// LanceDB doesn't support in-place update; delete + re-add
|
|
854
940
|
const resolvedId = escapeSqlLiteral(row.id as string);
|
|
855
941
|
await this.table!.delete(`id = '${resolvedId}'`);
|
package/src/topic-tag.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MP-1: Topic Tag — scope 内主题分区
|
|
3
|
+
*
|
|
4
|
+
* Keyword-based topic detection for memory entries.
|
|
5
|
+
* Tags are stored in metadata and used as a pre-filter during retrieval.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const TOPIC_PATTERNS: Array<{ tag: string; patterns: RegExp[] }> = [
|
|
9
|
+
{ tag: "auth", patterns: [/\b(auth|login|oauth|jwt|session.?token|credentials?|password|sso|saml)\b/i] },
|
|
10
|
+
{ tag: "deploy", patterns: [/\b(deploy|ci[\s/]?cd|pipeline|release|rollout|staging|production|rollback)\b/i] },
|
|
11
|
+
{ tag: "infra", patterns: [/\b(docker|kubernetes|k8s|terraform|nginx|server|cluster|load.?balanc|cdn|dns)\b/i] },
|
|
12
|
+
{ tag: "testing", patterns: [/\b(test|spec|jest|vitest|bun.?test|coverage|assertion|mock|fixture|e2e)\b/i] },
|
|
13
|
+
{ tag: "database", patterns: [/\b(database|sql|postgres|mysql|sqlite|lancedb|mongodb|migration|schema|index)\b/i] },
|
|
14
|
+
{ tag: "api", patterns: [/\b(api|endpoint|rest|graphql|grpc|webhook|http|request|response|route)\b/i] },
|
|
15
|
+
{ tag: "ui", patterns: [/\b(ui|frontend|react|vue|svelte|css|tailwind|component|layout|responsive)\b/i] },
|
|
16
|
+
{ tag: "perf", patterns: [/\b(performance|latency|throughput|cache|optimization|bottleneck|profil|benchmark)\b/i] },
|
|
17
|
+
{ tag: "security", patterns: [/\b(security|vulnerability|xss|csrf|injection|encrypt|certificate|tls|ssl)\b/i] },
|
|
18
|
+
{ tag: "docs", patterns: [/\b(documentation|readme|changelog|tutorial|guide|specification|api.?doc)\b/i] },
|
|
19
|
+
{ tag: "memory", patterns: [/\b(memory|recall|retriev|embed|vector|checkpoint|distill|ingest|lancedb)\b/i] },
|
|
20
|
+
{ tag: "mcp", patterns: [/\b(mcp|model.?context.?protocol|tool.?registr|mcp.?server)\b/i] },
|
|
21
|
+
{ tag: "llm", patterns: [/\b(llm|language.?model|prompt|token|context.?window|embedding|openai|anthropic|claude)\b/i] },
|
|
22
|
+
{ tag: "config", patterns: [/\b(config|settings?|environment|env.?var|dotenv|feature.?flag)\b/i] },
|
|
23
|
+
{ tag: "data", patterns: [/\b(data|dataset|csv|json|parsing|transform|etl|pipeline|ingest)\b/i] },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const MAX_SCAN_LENGTH = 2000;
|
|
27
|
+
|
|
28
|
+
export function detectTopicTag(text: string): string | undefined {
|
|
29
|
+
const sample = text.slice(0, MAX_SCAN_LENGTH).toLowerCase();
|
|
30
|
+
let bestTag: string | undefined;
|
|
31
|
+
let bestCount = 0;
|
|
32
|
+
|
|
33
|
+
for (const { tag, patterns } of TOPIC_PATTERNS) {
|
|
34
|
+
let count = 0;
|
|
35
|
+
for (const pattern of patterns) {
|
|
36
|
+
const matches = sample.match(new RegExp(pattern.source, "gi"));
|
|
37
|
+
if (matches) count += matches.length;
|
|
38
|
+
}
|
|
39
|
+
if (count > bestCount) {
|
|
40
|
+
bestCount = count;
|
|
41
|
+
bestTag = tag;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return bestCount >= 1 ? bestTag : undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function extractTopicTag(metadata?: string): string | undefined {
|
|
49
|
+
if (!metadata) return undefined;
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(metadata);
|
|
52
|
+
return typeof parsed.topicTag === "string" ? parsed.topicTag : undefined;
|
|
53
|
+
} catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function injectTopicTag(metadata: string, topicTag: string): string {
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(metadata);
|
|
61
|
+
parsed.topicTag = topicTag;
|
|
62
|
+
return JSON.stringify(parsed);
|
|
63
|
+
} catch {
|
|
64
|
+
return metadata;
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/tracker.ts
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import { metaDir } from "./compat.js";
|
|
9
|
+
|
|
10
|
+
const TRACKER_PATH = resolve(metaDir(import.meta), "../data/ingested-files.json");
|
|
9
11
|
|
|
10
12
|
interface TrackerData {
|
|
11
13
|
files: Record<string, { ingestedAt: string; size: number; chunks: number; mtimeMs?: number }>;
|
package/src/ui-server.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { existsSync, realpathSync } from "node:fs";
|
|
4
4
|
import { join, resolve, sep } from "node:path";
|
|
5
5
|
|
|
6
|
+
import { metaDir } from "./compat.js";
|
|
6
7
|
import type { RetrievalResult } from "./retriever.js";
|
|
7
8
|
import type { MemoryStore } from "./store.js";
|
|
8
9
|
import { distillResults, formatExplainResults, formatSearchResults, selectBriefSeedResults, summarizeResults } from "./memory-output.js";
|
|
@@ -83,8 +84,8 @@ function errorResponse(error: unknown): Response {
|
|
|
83
84
|
function isAllowedArtifactPath(targetPath: string): boolean {
|
|
84
85
|
try {
|
|
85
86
|
const resolved = realpathSync(targetPath);
|
|
86
|
-
const exportsDir = resolve(import.meta
|
|
87
|
-
const pinsDir = resolve(import.meta
|
|
87
|
+
const exportsDir = resolve(metaDir(import.meta), "../data/exports");
|
|
88
|
+
const pinsDir = resolve(metaDir(import.meta), "../data/pins");
|
|
88
89
|
return (
|
|
89
90
|
resolved === exportsDir ||
|
|
90
91
|
resolved.startsWith(`${exportsDir}${sep}`) ||
|
|
@@ -141,7 +142,7 @@ function serializeResults(results: RetrievalResult[]) {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
function serveStatic(pathname: string): Response | null {
|
|
144
|
-
const uiDir = resolve(import.meta
|
|
145
|
+
const uiDir = resolve(metaDir(import.meta), "../assets/ui");
|
|
145
146
|
const relativePath = pathname === "/"
|
|
146
147
|
? join(uiDir, "index.html")
|
|
147
148
|
: join(uiDir, pathname.replace(/^\/ui\//, ""));
|
|
@@ -157,14 +158,17 @@ function serveStatic(pathname: string): Response | null {
|
|
|
157
158
|
async function handleSearch(mode: "search" | "explain" | "distill", body: Record<string, any>) {
|
|
158
159
|
const query = requireString(body.query, "query");
|
|
159
160
|
const { retriever, profile } = getComponents(readOptionalString(body.profile));
|
|
161
|
+
const explicitScope = readOptionalString(body.scope);
|
|
160
162
|
const results = await retriever.retrieve(buildRetrievalContext({
|
|
161
163
|
query,
|
|
162
164
|
limit: readLimit(body.limit, 5, 1, 20),
|
|
163
|
-
scope:
|
|
165
|
+
scope: explicitScope,
|
|
164
166
|
sessionId: readOptionalString(body.sessionId),
|
|
165
|
-
allScopes:
|
|
167
|
+
allScopes: explicitScope ? false : true,
|
|
168
|
+
topicTag: readOptionalString(body.topicTag),
|
|
166
169
|
}, {
|
|
167
170
|
operation: `ui:${mode}`,
|
|
171
|
+
allowUnscoped: true,
|
|
168
172
|
}));
|
|
169
173
|
const context = { query, profile: profile.name };
|
|
170
174
|
const output = mode === "explain"
|
|
@@ -260,6 +264,28 @@ const server = Bun.serve({
|
|
|
260
264
|
});
|
|
261
265
|
}
|
|
262
266
|
|
|
267
|
+
if (request.method === "GET" && url.pathname === "/api/skills") {
|
|
268
|
+
const { store, embedder } = getComponents();
|
|
269
|
+
const { retrieveSkills } = await import("./skill-engine.js");
|
|
270
|
+
const scope = url.searchParams.get("scope") || undefined;
|
|
271
|
+
const skills = await retrieveSkills(store, embedder, "all skills", scope, 20);
|
|
272
|
+
return Response.json({
|
|
273
|
+
items: skills.map(({ skill, score }) => ({
|
|
274
|
+
shortId: skill.id.slice(0, 8),
|
|
275
|
+
name: skill.name,
|
|
276
|
+
description: skill.description,
|
|
277
|
+
trigger: skill.triggerPattern,
|
|
278
|
+
type: skill.implementationType,
|
|
279
|
+
implementation: skill.implementation,
|
|
280
|
+
verification: skill.verification,
|
|
281
|
+
scope: skill.scope,
|
|
282
|
+
tags: skill.tags,
|
|
283
|
+
score: Math.round(score * 100),
|
|
284
|
+
storedAt: skill.storedAt,
|
|
285
|
+
})),
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
263
289
|
if (request.method === "GET" && url.pathname === "/api/stats") {
|
|
264
290
|
const { store } = getComponents();
|
|
265
291
|
const stats = await store.stats();
|
|
@@ -296,7 +322,7 @@ const server = Bun.serve({
|
|
|
296
322
|
const scopeSelection = resolveScopeSelection({
|
|
297
323
|
scope: readOptionalString(body.scope),
|
|
298
324
|
sessionId: readOptionalString(body.sessionId),
|
|
299
|
-
allScopes: body.
|
|
325
|
+
allScopes: readOptionalString(body.scope) ? false : true,
|
|
300
326
|
operation: "ui:pin",
|
|
301
327
|
});
|
|
302
328
|
const entry = await store.get(memoryId, scopeSelection.scopeFilter);
|
|
@@ -327,7 +353,7 @@ const server = Bun.serve({
|
|
|
327
353
|
limit: readLimit(body.limit, 8, 1, 20),
|
|
328
354
|
scope: readOptionalString(body.scope),
|
|
329
355
|
sessionId: readOptionalString(body.sessionId),
|
|
330
|
-
allScopes: body.
|
|
356
|
+
allScopes: readOptionalString(body.scope) ? false : true,
|
|
331
357
|
}, {
|
|
332
358
|
operation: "ui:brief",
|
|
333
359
|
}));
|
|
@@ -377,7 +403,7 @@ const server = Bun.serve({
|
|
|
377
403
|
limit: readLimit(body.limit, 8, 1, 20),
|
|
378
404
|
scope: readOptionalString(body.scope),
|
|
379
405
|
sessionId: readOptionalString(body.sessionId),
|
|
380
|
-
allScopes: body.
|
|
406
|
+
allScopes: readOptionalString(body.scope) ? false : true,
|
|
381
407
|
}, {
|
|
382
408
|
operation: "ui:export",
|
|
383
409
|
}));
|
|
@@ -420,6 +446,52 @@ const server = Bun.serve({
|
|
|
420
446
|
});
|
|
421
447
|
}
|
|
422
448
|
|
|
449
|
+
// ----- Dashboard API -----
|
|
450
|
+
if (request.method === "GET" && url.pathname === "/api/dashboard-stats") {
|
|
451
|
+
const { store } = getComponents();
|
|
452
|
+
const stats = await store.stats();
|
|
453
|
+
const allEntries = await store.list(undefined, undefined, 10000, 0);
|
|
454
|
+
|
|
455
|
+
const now = Date.now();
|
|
456
|
+
const weekAgo = now - 7 * 24 * 60 * 60 * 1000;
|
|
457
|
+
const monthAgo = now - 30 * 24 * 60 * 60 * 1000;
|
|
458
|
+
const thisWeek = allEntries.filter(e => e.timestamp > weekAgo).length;
|
|
459
|
+
const thisMonth = allEntries.filter(e => e.timestamp > monthAgo).length;
|
|
460
|
+
|
|
461
|
+
return Response.json({
|
|
462
|
+
totalCount: stats.totalCount,
|
|
463
|
+
categoryCounts: stats.categoryCounts,
|
|
464
|
+
scopeCounts: stats.scopeCounts,
|
|
465
|
+
growth: { thisWeek, thisMonth },
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (request.method === "GET" && url.pathname === "/api/stale-memories") {
|
|
470
|
+
const { store } = getComponents();
|
|
471
|
+
const { runMemoryLint } = await import("./memory-lint.js");
|
|
472
|
+
const report = await runMemoryLint({ store });
|
|
473
|
+
const staleFindings = report.findings.filter(f => f.check === "stale");
|
|
474
|
+
return Response.json({
|
|
475
|
+
count: staleFindings.length,
|
|
476
|
+
items: staleFindings.slice(0, 20).map(f => ({
|
|
477
|
+
detail: f.detail,
|
|
478
|
+
memoryIds: f.memoryIds,
|
|
479
|
+
})),
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (request.method === "GET" && url.pathname === "/api/lint-summary") {
|
|
484
|
+
const { store } = getComponents();
|
|
485
|
+
const { runMemoryLint } = await import("./memory-lint.js");
|
|
486
|
+
const report = await runMemoryLint({ store });
|
|
487
|
+
return Response.json({
|
|
488
|
+
healthScore: report.healthScore,
|
|
489
|
+
totalScanned: report.totalScanned,
|
|
490
|
+
summary: report.summary,
|
|
491
|
+
timestamp: report.timestamp,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
423
495
|
return new Response("Not Found", { status: 404 });
|
|
424
496
|
} catch (error) {
|
|
425
497
|
return errorResponse(error);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, join, resolve } from "node:path";
|
|
3
3
|
|
|
4
|
+
import { metaDir } from "./compat.js";
|
|
4
5
|
import { buildLegacyScopeKeepReview, suppressesLegacyScopeIssue } from "./legacy-scope-review.js";
|
|
5
6
|
import { classifyLegacyScope } from "./store.js";
|
|
6
7
|
|
|
@@ -66,7 +67,7 @@ export interface WorkflowObservationScopeReviewOptions {
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
function defaultDir(): string {
|
|
69
|
-
return resolve(import.meta
|
|
70
|
+
return resolve(metaDir(import.meta), "../data/workflow-observations");
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
function clip(text: string, maxLen = 72): string {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
|
|
4
|
+
import { metaDir } from "./compat.js";
|
|
4
5
|
import type { WorkflowObservationOutcome, WorkflowObservationRecord } from "./workflow-observation-schema.js";
|
|
5
6
|
import { WorkflowObservationRecordSchema } from "./workflow-observation-schema.js";
|
|
6
7
|
|
|
@@ -26,7 +27,7 @@ function sortNewestFirst(records: WorkflowObservationRecord[]): WorkflowObservat
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export class WorkflowObservationStore {
|
|
29
|
-
constructor(private readonly dir = resolve(import.meta
|
|
30
|
+
constructor(private readonly dir = resolve(metaDir(import.meta), "../data/workflow-observations")) {}
|
|
30
31
|
|
|
31
32
|
get dataDir(): string {
|
|
32
33
|
return ensureDir(this.dir);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HP-2: Formation Review Pass — post-write verification.
|
|
3
|
+
* After storing a memory, verify: embedding exists, metadata complete, scope set.
|
|
4
|
+
* Non-blocking: failures are logged but never block the write path.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { MemoryStore, MemoryEntry } from "./store.js";
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Config
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
export interface WriteVerifierConfig {
|
|
14
|
+
/** Enable/disable verification (default: true) */
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
/** Maximum time to wait for verification in ms (default: 2000) */
|
|
17
|
+
timeoutMs: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const DEFAULT_VERIFIER_CONFIG: WriteVerifierConfig = {
|
|
21
|
+
enabled: true,
|
|
22
|
+
timeoutMs: 2000,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Verification result
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
export type VerificationIssue =
|
|
30
|
+
| "missing_entry"
|
|
31
|
+
| "missing_vector"
|
|
32
|
+
| "missing_scope"
|
|
33
|
+
| "missing_importance"
|
|
34
|
+
| "empty_text";
|
|
35
|
+
|
|
36
|
+
export interface VerificationResult {
|
|
37
|
+
ok: boolean;
|
|
38
|
+
entryId: string;
|
|
39
|
+
issues: VerificationIssue[];
|
|
40
|
+
durationMs: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Public API
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Verify a just-written memory entry by reading it back.
|
|
49
|
+
* Returns issues found (empty = all good).
|
|
50
|
+
*/
|
|
51
|
+
export async function verifyWrite(
|
|
52
|
+
store: Pick<MemoryStore, "get">,
|
|
53
|
+
entryId: string,
|
|
54
|
+
cfg?: Partial<WriteVerifierConfig>,
|
|
55
|
+
): Promise<VerificationResult> {
|
|
56
|
+
const config = { ...DEFAULT_VERIFIER_CONFIG, ...cfg };
|
|
57
|
+
const start = Date.now();
|
|
58
|
+
|
|
59
|
+
if (!config.enabled) {
|
|
60
|
+
return { ok: true, entryId, issues: [], durationMs: 0 };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const entry = await Promise.race([
|
|
65
|
+
store.get(entryId),
|
|
66
|
+
new Promise<null>((_, reject) =>
|
|
67
|
+
setTimeout(() => reject(new Error("timeout")), config.timeoutMs),
|
|
68
|
+
),
|
|
69
|
+
]) as MemoryEntry | null;
|
|
70
|
+
|
|
71
|
+
const issues = checkEntry(entry, entryId);
|
|
72
|
+
return {
|
|
73
|
+
ok: issues.length === 0,
|
|
74
|
+
entryId,
|
|
75
|
+
issues,
|
|
76
|
+
durationMs: Date.now() - start,
|
|
77
|
+
};
|
|
78
|
+
} catch {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
entryId,
|
|
82
|
+
issues: ["missing_entry"],
|
|
83
|
+
durationMs: Date.now() - start,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Internal checks
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
function checkEntry(entry: MemoryEntry | null, id: string): VerificationIssue[] {
|
|
93
|
+
const issues: VerificationIssue[] = [];
|
|
94
|
+
|
|
95
|
+
if (!entry) {
|
|
96
|
+
issues.push("missing_entry");
|
|
97
|
+
return issues;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Vector should exist (embedding succeeded)
|
|
101
|
+
if (!entry.vector || (Array.isArray(entry.vector) && entry.vector.length === 0)) {
|
|
102
|
+
issues.push("missing_vector");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Text should not be empty
|
|
106
|
+
if (!entry.text || entry.text.trim().length === 0) {
|
|
107
|
+
issues.push("empty_text");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Metadata should contain scope
|
|
111
|
+
if (entry.metadata) {
|
|
112
|
+
try {
|
|
113
|
+
const meta = JSON.parse(entry.metadata);
|
|
114
|
+
if (!meta.scope) issues.push("missing_scope");
|
|
115
|
+
if (typeof meta.importance !== "number") issues.push("missing_importance");
|
|
116
|
+
} catch {
|
|
117
|
+
issues.push("missing_scope");
|
|
118
|
+
issues.push("missing_importance");
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
issues.push("missing_scope");
|
|
122
|
+
issues.push("missing_importance");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return issues;
|
|
126
|
+
}
|
package/.claude-plugin/CLAUDE.md
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# RecallNest Memory Plugin
|
|
2
|
-
|
|
3
|
-
RecallNest is a local-first shared memory layer backed by LanceDB. It stores and recalls context across sessions, terminals, and agents (Claude Code, Codex, Gemini CLI).
|
|
4
|
-
|
|
5
|
-
**Prerequisite**: Requires [Bun](https://bun.sh) runtime. On first start, dependencies install automatically.
|
|
6
|
-
|
|
7
|
-
## Tool Quick Reference
|
|
8
|
-
|
|
9
|
-
| When | Tool | Purpose |
|
|
10
|
-
|------|------|---------|
|
|
11
|
-
| Session opens, user says continue/接着/上个窗口 | `resume_context` | Load stable context, latest checkpoint, recent cases |
|
|
12
|
-
| Before answering about prior work / known facts | `search_memory` | Targeted fact/pattern recall (2-3 key nouns) |
|
|
13
|
-
| After solving something significant | `store_memory` or `store_case` | Persist durable knowledge |
|
|
14
|
-
| Before closing a window with unfinished work | `checkpoint_session` | Save current state for next session |
|
|
15
|
-
| After discovering a reusable workflow | `store_workflow_pattern` | Persist as durable pattern |
|
|
16
|
-
| Memory feels stale or conflicted | `consolidate_memories` | Merge near-duplicates (dry-run by default) |
|
|
17
|
-
|
|
18
|
-
## Continuity Rules
|
|
19
|
-
|
|
20
|
-
- If the user says `continue`, `继续`, `接着`, `刚才`, `上个窗口`, `不要让我重复前情`, or asks where you stopped / what to do next — call `resume_context` before any Bash, Read, or repo exploration.
|
|
21
|
-
- If the user starts a concrete task in an active project or asks for prior implementation detail — do lightweight RecallNest recovery before repo exploration even without an explicit `continue`.
|
|
22
|
-
- Do not substitute `git status`, `git log`, or reading local docs for `resume_context`. Local inspection can validate current code state only **after** continuity has been recovered.
|
|
23
|
-
- When `scope` or `sessionId` is known, pass it into recall tools instead of relying on global recall.
|
|
24
|
-
- If `resume_context` returns a resolved scope or checkpoint scope, reuse that same scope in follow-up `search_memory`, `brief_memory`, `pin_memory` calls.
|
|
25
|
-
- If recalled context mentions repo state (modified files, pending push), treat it as **handoff context only** — do not restate as current state without verifying in this window.
|
|
26
|
-
- Before leaving a window with resumable work, call `checkpoint_session`. Do not include repo state claims unless you actually inspected the repo this window.
|
|
27
|
-
- Use `store_memory` for durable profile, preference, entity, or case knowledge.
|
|
28
|
-
- Use `store_workflow_pattern` for reusable multi-step workflows.
|
|
29
|
-
- If the user explicitly corrects a continuity miss, fix the workflow and call `workflow_observe` with `outcome: corrected`.
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "AliceLJY",
|
|
3
|
-
"owner": {
|
|
4
|
-
"name": "Alice Jin Ye"
|
|
5
|
-
},
|
|
6
|
-
"metadata": {
|
|
7
|
-
"description": "RecallNest — shared memory layer for Claude Code, Codex, and Gemini CLI",
|
|
8
|
-
"homepage": "https://github.com/AliceLJY/recallnest"
|
|
9
|
-
},
|
|
10
|
-
"plugins": [
|
|
11
|
-
{
|
|
12
|
-
"name": "recallnest",
|
|
13
|
-
"version": "1.4.1",
|
|
14
|
-
"source": "./",
|
|
15
|
-
"description": "Local-first memory system: store, search, and recall context across sessions and terminals. Powered by LanceDB with hybrid retrieval."
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
}
|