recallnest 2.3.0 → 2.6.0
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/CHANGELOG.md +187 -0
- package/README.md +49 -38
- package/README_CN.md +46 -31
- package/assets/ui/app.js +1031 -0
- package/assets/ui/index.html +224 -0
- package/assets/ui/styles.css +781 -0
- package/config.json.example +4 -1
- package/docs/api-reference.md +1291 -0
- package/docs/architecture.md +168 -0
- package/docs/connector-spec.md +126 -0
- package/eval/cases-canary.json +58 -0
- package/eval/cases-scope-robustness.json +278 -0
- package/eval/cases.json +117 -20
- package/package.json +25 -4
- package/src/__tests__/access-tracker-novelty.test.ts +304 -0
- package/src/__tests__/activity-counter.test.ts +125 -0
- package/src/__tests__/adaptive-fidelity.test.ts +102 -0
- package/src/__tests__/admission-control.test.ts +206 -0
- package/src/__tests__/anchor-generator.test.ts +67 -0
- package/src/__tests__/api-server-security.test.ts +102 -0
- package/src/__tests__/auto-consolidation.test.ts +168 -0
- package/src/__tests__/auto-recall.test.ts +131 -0
- package/src/__tests__/babel-integration.test.ts +144 -0
- package/src/__tests__/batch-dedup.test.ts +65 -0
- package/src/__tests__/belief-history.test.ts +220 -0
- package/src/__tests__/canary-eval.test.ts +114 -0
- package/src/__tests__/capture-engine.test.ts +1328 -0
- package/src/__tests__/capture-heuristic.test.ts +171 -0
- package/src/__tests__/cascade-forget.test.ts +210 -0
- package/src/__tests__/category-min-scores.test.ts +116 -0
- package/src/__tests__/cc8-cc1-essential-context.test.ts +298 -0
- package/src/__tests__/circuit-breaker.test.ts +99 -0
- package/src/__tests__/cluster-consolidation.test.ts +945 -0
- package/src/__tests__/confidence-metadata.test.ts +125 -0
- package/src/__tests__/confidence-tracker.test.ts +261 -0
- package/src/__tests__/conflict-advisor.test.ts +206 -0
- package/src/__tests__/conflict-escalation.test.ts +149 -0
- package/src/__tests__/conflict-lifecycle.test.ts +83 -0
- package/src/__tests__/conflict-output.test.ts +85 -0
- package/src/__tests__/conflict-store.test.ts +418 -0
- package/src/__tests__/connector-v1.test.ts +181 -0
- package/src/__tests__/consolidation-engine.test.ts +430 -0
- package/src/__tests__/constructive-retrieval.test.ts +509 -0
- package/src/__tests__/context-collapse-renderer.test.ts +214 -0
- package/src/__tests__/context-composer-pins.test.ts +64 -0
- package/src/__tests__/context-composer-task-selection.test.ts +24 -0
- package/src/__tests__/context-reconstructor.test.ts +65 -0
- package/src/__tests__/context-renderer.test.ts +91 -0
- package/src/__tests__/continuity-eval.test.ts +275 -0
- package/src/__tests__/continuity-schema.test.ts +97 -0
- package/src/__tests__/contracts.test.ts +262 -0
- package/src/__tests__/conversation-importer.test.ts +692 -0
- package/src/__tests__/core-recall-eval.test.ts +235 -0
- package/src/__tests__/core-summary.test.ts +109 -0
- package/src/__tests__/data-checkup.test.ts +276 -0
- package/src/__tests__/decay-exemption.test.ts +129 -0
- package/src/__tests__/dedup-actions.test.ts +58 -0
- package/src/__tests__/dedup-guard.test.ts +39 -0
- package/src/__tests__/deterministic-id.test.ts +39 -0
- package/src/__tests__/distill-lock.test.ts +305 -0
- package/src/__tests__/distill-session-argshape.test.ts +79 -0
- package/src/__tests__/doctor.test.ts +319 -0
- package/src/__tests__/dream-pipeline.test.ts +780 -0
- package/src/__tests__/drill-down.test.ts +108 -0
- package/src/__tests__/embedder.test.ts +122 -0
- package/src/__tests__/emotion-decay.test.ts +46 -0
- package/src/__tests__/emotion-detector.test.ts +56 -0
- package/src/__tests__/emotion-phase1-integration.test.ts +153 -0
- package/src/__tests__/emotion-retrieval.test.ts +56 -0
- package/src/__tests__/entity-resolver.test.ts +58 -0
- package/src/__tests__/env-config.test.ts +151 -0
- package/src/__tests__/error-signature-boost.test.ts +87 -0
- package/src/__tests__/error-signature.test.ts +95 -0
- package/src/__tests__/error-taxonomy.test.ts +129 -0
- package/src/__tests__/ethics-layer.test.ts +476 -0
- package/src/__tests__/event-segmenter.test.ts +153 -0
- package/src/__tests__/feature-flags-integration.test.ts +54 -0
- package/src/__tests__/frequency-tracker.test.ts +113 -0
- package/src/__tests__/freshness.test.ts +186 -0
- package/src/__tests__/graph-export.test.ts +489 -0
- package/src/__tests__/hotness-blend.test.ts +283 -0
- package/src/__tests__/ingest-chunking.test.ts +44 -0
- package/src/__tests__/ingest.test.ts +360 -0
- package/src/__tests__/interference-detector.test.ts +203 -0
- package/src/__tests__/kg-extractor.test.ts +192 -0
- package/src/__tests__/kg-store.test.ts +300 -0
- package/src/__tests__/llm-client-pivot.test.ts +628 -0
- package/src/__tests__/llm-consolidation.test.ts +226 -0
- package/src/__tests__/maintenance-locks.test.ts +189 -0
- package/src/__tests__/mcp-efficiency.test.ts +288 -0
- package/src/__tests__/mcp-related-scopes.test.ts +151 -0
- package/src/__tests__/memory-boundaries.test.ts +282 -0
- package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +345 -0
- package/src/__tests__/memory-evolution.test.ts +271 -0
- package/src/__tests__/memory-health-rebalance.test.ts +144 -0
- package/src/__tests__/memory-lint.test.ts +421 -0
- package/src/__tests__/memory-output.test.ts +215 -0
- package/src/__tests__/memory-promotion.test.ts +315 -0
- package/src/__tests__/migrate-dedup-ids.test.ts +65 -0
- package/src/__tests__/multi-vector.test.ts +242 -0
- package/src/__tests__/narrative-architecture.test.ts +456 -0
- package/src/__tests__/noise-filter.test.ts +205 -0
- package/src/__tests__/noise-prototype-bank.test.ts +280 -0
- package/src/__tests__/obsidian-connector.test.ts +363 -0
- package/src/__tests__/omitted-reporting.test.ts +98 -0
- package/src/__tests__/pending-review.test.ts +77 -0
- package/src/__tests__/pii-redact.test.ts +333 -0
- package/src/__tests__/pivot-apply.test.ts +373 -0
- package/src/__tests__/pivot-distill-supervisor.test.ts +457 -0
- package/src/__tests__/pivot-distill.test.ts +1178 -0
- package/src/__tests__/post-retrieval-filter.test.ts +178 -0
- package/src/__tests__/ppr-traversal.test.ts +256 -0
- package/src/__tests__/prediction-engine.test.ts +349 -0
- package/src/__tests__/predictive-prospective-memory.test.ts +416 -0
- package/src/__tests__/preference-guard-smoke.test.ts +211 -0
- package/src/__tests__/preference-matcher.test.ts +282 -0
- package/src/__tests__/preference-slots.test.ts +205 -0
- package/src/__tests__/prospective-memory.test.ts +283 -0
- package/src/__tests__/pull-from-macbook-sync-only.test.ts +679 -0
- package/src/__tests__/query-entity-detector.test.ts +171 -0
- package/src/__tests__/query-expander-alias.test.ts +146 -0
- package/src/__tests__/query-expander.test.ts +34 -0
- package/src/__tests__/recall-gate.test.ts +266 -0
- package/src/__tests__/recall-governor.test.ts +227 -0
- package/src/__tests__/recall-mode.test.ts +64 -0
- package/src/__tests__/resolve-db-path.test.ts +130 -0
- package/src/__tests__/resolve-tier.test.ts +64 -0
- package/src/__tests__/result-synthesizer.test.ts +198 -0
- package/src/__tests__/resume-context.test.ts +4898 -0
- package/src/__tests__/retrieval-eval.test.ts +163 -0
- package/src/__tests__/retrieval-trace.test.ts +213 -0
- package/src/__tests__/retriever-boundary-weight.test.ts +136 -0
- package/src/__tests__/retriever-lazy-init-dispatch.test.ts +123 -0
- package/src/__tests__/retroactive-boost.test.ts +223 -0
- package/src/__tests__/salience-scoring.test.ts +106 -0
- package/src/__tests__/scope-inventory.test.ts +200 -0
- package/src/__tests__/scope-policy.test.ts +69 -0
- package/src/__tests__/scope-suggester.test.ts +80 -0
- package/src/__tests__/session-checkpoint.test.ts +291 -0
- package/src/__tests__/session-distiller.test.ts +640 -0
- package/src/__tests__/session-gc.test.ts +215 -0
- package/src/__tests__/skill-engine.test.ts +552 -0
- package/src/__tests__/skill-promotion.test.ts +654 -0
- package/src/__tests__/skill-verifier.test.ts +122 -0
- package/src/__tests__/source-heartbeat.test.ts +250 -0
- package/src/__tests__/stable-context-length-contract.test.ts +62 -0
- package/src/__tests__/store-patch-metadata.test.ts +205 -0
- package/src/__tests__/store-read-consistency.test.ts +122 -0
- package/src/__tests__/store-schema-migration.test.ts +87 -0
- package/src/__tests__/store-scope-audit.test.ts +81 -0
- package/src/__tests__/store-upsert.test.ts +201 -0
- package/src/__tests__/supersede-chain.test.ts +154 -0
- package/src/__tests__/temporal-parser.test.ts +241 -0
- package/src/__tests__/temporal-validity.test.ts +160 -0
- package/src/__tests__/term-registry-terminal-roster.test.ts +127 -0
- package/src/__tests__/term-registry.test.ts +121 -0
- package/src/__tests__/tool-error-surfacing.test.ts +98 -0
- package/src/__tests__/tool-output-compressor.test.ts +160 -0
- package/src/__tests__/topic-tag.test.ts +113 -0
- package/src/__tests__/trusted-memory.test.ts +839 -0
- package/src/__tests__/ui-server-security.test.ts +93 -0
- package/src/__tests__/usage-tracker.test.ts +287 -0
- package/src/__tests__/version-manager.test.ts +213 -0
- package/src/__tests__/workflow-observation-skill-outcome.test.ts +352 -0
- package/src/__tests__/workflow-observation.test.ts +433 -0
- package/src/__tests__/write-verifier.test.ts +122 -0
- package/src/access-tracker.ts +90 -34
- package/src/activity-counter.ts +46 -18
- package/src/adaptive-retrieval.ts +11 -0
- package/src/aliases.ts +133 -0
- package/src/api-server.ts +36 -9
- package/src/audit-log.ts +4 -2
- package/src/auto-consolidation.ts +8 -3
- package/src/auto-gc.ts +85 -21
- package/src/auto-recall.ts +61 -1
- package/src/belief-history.ts +164 -0
- package/src/capture-engine.ts +142 -32
- package/src/cli.ts +268 -41
- package/src/confidence-tracker.ts +36 -18
- package/src/conflict-engine.ts +24 -3
- package/src/consolidation-engine.ts +255 -23
- package/src/context-composer-stable-selection.ts +7 -1
- package/src/context-composer.ts +8 -1
- package/src/data-checkup.ts +96 -7
- package/src/decay-engine.ts +59 -7
- package/src/distill-lock.ts +194 -15
- package/src/doctor.ts +13 -4
- package/src/dream-pipeline.ts +473 -41
- package/src/env-config.ts +118 -0
- package/src/error-signature.ts +153 -0
- package/src/error-taxonomy.ts +115 -0
- package/src/eval.ts +402 -14
- package/src/freshness.ts +192 -0
- package/src/http-request-policy.ts +148 -0
- package/src/idempotency.ts +8 -0
- package/src/ingest.ts +308 -12
- package/src/kg-extractor.ts +13 -3
- package/src/kg-store.ts +236 -44
- package/src/llm-client.ts +106 -11
- package/src/llm-consolidation.ts +2 -1
- package/src/mcp-server.ts +87 -1800
- package/src/mcp-tool-deps.ts +48 -0
- package/src/mcp-tools-advanced.ts +954 -0
- package/src/mcp-tools-core.ts +637 -0
- package/src/mcp-tools-governance.ts +475 -0
- package/src/memory-boundaries.ts +26 -3
- package/src/memory-evolution.ts +73 -1
- package/src/memory-health-rebalance.ts +4 -0
- package/src/memory-lint.ts +183 -10
- package/src/memory-output.ts +129 -4
- package/src/memory-promotion.ts +246 -0
- package/src/memory-schema.ts +25 -2
- package/src/memory-store-port.ts +46 -0
- package/src/multi-vector.ts +2 -1
- package/src/narrative-schema.ts +2 -1
- package/src/noise-filter.ts +57 -6
- package/src/noise-prototype-bank.ts +408 -0
- package/src/pii-detector.ts +207 -5
- package/src/pivot-apply.ts +399 -0
- package/src/preference-matcher.ts +37 -3
- package/src/query-expander.ts +90 -9
- package/src/recall-gate.ts +305 -0
- package/src/result-synthesizer.ts +2 -1
- package/src/retention-policy.ts +2 -1
- package/src/retrieval-profiles.ts +46 -4
- package/src/retriever.ts +354 -157
- package/src/retroactive-boost.ts +6 -2
- package/src/runtime-config.ts +78 -6
- package/src/scope-suggester.ts +113 -0
- package/src/session-distiller.ts +23 -2
- package/src/session-engine.ts +2 -1
- package/src/session-schema.ts +1 -0
- package/src/session-store.ts +5 -0
- package/src/skill-engine.ts +78 -0
- package/src/skill-promotion.ts +304 -48
- package/src/skill-schema.ts +16 -3
- package/src/skill-verifier.ts +152 -0
- package/src/store.ts +321 -57
- package/src/term-registry.ts +35 -0
- package/src/tool-output-compressor.ts +3 -1
- package/src/ui-server.ts +9 -1
- package/src/usage-tracker.ts +242 -0
- package/src/version-manager.ts +3 -2
- package/src/version.ts +19 -0
- package/src/workflow-observation-engine.ts +2 -1
- package/src/workflow-observation-schema.ts +2 -1
- package/src/workflow-observation-store.ts +6 -2
- package/.env +0 -4
- package/CLAUDE.md +0 -46
- package/config.json +0 -55
- package/daily-recall.sh +0 -110
- package/logs/daily-recall.error.log +0 -0
- package/logs/daily-recall.log +0 -60
- package/logs/ingest-2026-04-11.log +0 -110
- package/logs/ingest-2026-04-12.log +0 -90
- package/logs/ingest-2026-04-13.log +0 -87
- package/logs/launchagent-stderr.log +0 -0
- package/logs/launchagent-stdout.log +0 -0
|
@@ -0,0 +1,4898 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { composeLightResumeContext, composeResumeContext } from "../context-composer.js";
|
|
4
|
+
import type { RetrievalContext, RetrievalResult } from "../retriever.js";
|
|
5
|
+
import type { SessionCheckpointRecord } from "../session-schema.js";
|
|
6
|
+
|
|
7
|
+
function buildResult(id: string, category: "profile" | "preferences" | "entities" | "patterns" | "cases" | "fact", text: string): RetrievalResult {
|
|
8
|
+
return {
|
|
9
|
+
entry: {
|
|
10
|
+
id,
|
|
11
|
+
text,
|
|
12
|
+
vector: [],
|
|
13
|
+
category,
|
|
14
|
+
scope: "memory:agent",
|
|
15
|
+
importance: 0.8,
|
|
16
|
+
timestamp: Date.parse("2026-03-16T00:00:00.000Z"),
|
|
17
|
+
metadata: "{}",
|
|
18
|
+
},
|
|
19
|
+
score: 0.9,
|
|
20
|
+
sources: {
|
|
21
|
+
fused: { score: 0.9 },
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function withScope(result: RetrievalResult, scope: string): RetrievalResult {
|
|
27
|
+
return {
|
|
28
|
+
...result,
|
|
29
|
+
entry: {
|
|
30
|
+
...result.entry,
|
|
31
|
+
scope,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function withMetadata(result: RetrievalResult, metadata: Record<string, unknown>): RetrievalResult {
|
|
37
|
+
return {
|
|
38
|
+
...result,
|
|
39
|
+
entry: {
|
|
40
|
+
...result.entry,
|
|
41
|
+
metadata: JSON.stringify(metadata),
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("composeResumeContext", () => {
|
|
47
|
+
it("uses the latest checkpoint to recover task bias and shared scope", async () => {
|
|
48
|
+
const calls: RetrievalContext[] = [];
|
|
49
|
+
const checkpoint: SessionCheckpointRecord = {
|
|
50
|
+
checkpointId: "checkpoint-001",
|
|
51
|
+
sessionId: "session-1",
|
|
52
|
+
resolvedScope: "agent:codex",
|
|
53
|
+
summary: "Implement startup continuity for fresh windows",
|
|
54
|
+
task: "Implement resume_context",
|
|
55
|
+
decisions: ["Keep checkpoints outside LanceDB"],
|
|
56
|
+
openLoops: ["Need startup composition"],
|
|
57
|
+
nextActions: ["Wire API and MCP endpoints"],
|
|
58
|
+
entities: ["RecallNest", "Codex"],
|
|
59
|
+
files: ["src/context-composer.ts"],
|
|
60
|
+
updatedAt: "2026-03-16T05:00:00.000Z",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const retriever = {
|
|
64
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
65
|
+
calls.push(context);
|
|
66
|
+
switch (context.category) {
|
|
67
|
+
case "profile":
|
|
68
|
+
return [buildResult("profile-1", "profile", "User builds local-first memory systems.")];
|
|
69
|
+
case "preferences":
|
|
70
|
+
return [buildResult("pref-1", "preferences", "User prefers concise technical replies.")];
|
|
71
|
+
case "entities":
|
|
72
|
+
return [buildResult("entity-1", "entities", "RecallNest is shared across Claude Code, Codex, and Gemini CLI.")];
|
|
73
|
+
case "patterns":
|
|
74
|
+
return [buildResult("pattern-1", "patterns", "At task start, run search_memory before coding.")];
|
|
75
|
+
case "cases":
|
|
76
|
+
return [buildResult("case-1", "cases", "Keep session state in a checkpoint store instead of the durable index.")];
|
|
77
|
+
default:
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const checkpointStore = {
|
|
84
|
+
async getLatest(query?: { sessionId?: string; scope?: string }) {
|
|
85
|
+
if (query?.sessionId === "session-1" || query?.scope === "agent:codex") {
|
|
86
|
+
return checkpoint;
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const response = await composeResumeContext({
|
|
93
|
+
retriever,
|
|
94
|
+
checkpointStore,
|
|
95
|
+
listPins: () => [],
|
|
96
|
+
}, {
|
|
97
|
+
sessionId: "session-1",
|
|
98
|
+
limitPerSection: 3,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(response.resolvedScope).toBe("agent:codex");
|
|
102
|
+
expect(response.latestCheckpoint?.sessionId).toBe("session-1");
|
|
103
|
+
expect(response.latestCheckpoint?.resolvedScope).toBe("agent:codex");
|
|
104
|
+
expect(response.stableContext).toContain("Profile: User builds local-first memory systems.");
|
|
105
|
+
expect(response.relevantPatterns).toEqual(["At task start, run search_memory before coding."]);
|
|
106
|
+
expect(response.recentCases).toEqual(["Keep session state in a checkpoint store instead of the durable index."]);
|
|
107
|
+
expect(response.summary).toContain("Latest checkpoint from session-1");
|
|
108
|
+
|
|
109
|
+
const patternCall = calls.find((call) => call.category === "patterns");
|
|
110
|
+
expect(patternCall?.scopeFilter).toEqual(["agent:codex"]);
|
|
111
|
+
expect(patternCall?.query).toContain("Implement resume_context");
|
|
112
|
+
expect(patternCall?.source).toBe("auto-recall");
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("fills sparse stable context with pinned memory and skips checkpoint lookup when disabled", async () => {
|
|
116
|
+
let checkpointLookups = 0;
|
|
117
|
+
const retriever = {
|
|
118
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
119
|
+
if (context.category === "patterns") {
|
|
120
|
+
return [buildResult("pattern-2", "patterns", "Use resume_context before starting a new terminal task.")];
|
|
121
|
+
}
|
|
122
|
+
return [];
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const checkpointStore = {
|
|
127
|
+
async getLatest() {
|
|
128
|
+
checkpointLookups += 1;
|
|
129
|
+
return null;
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const response = await composeResumeContext({
|
|
134
|
+
retriever,
|
|
135
|
+
checkpointStore,
|
|
136
|
+
listPins: () => [{
|
|
137
|
+
id: "pin-1",
|
|
138
|
+
type: "pinned-memory",
|
|
139
|
+
createdAt: "2026-03-16T04:00:00.000Z",
|
|
140
|
+
updatedAt: "2026-03-16T04:00:00.000Z",
|
|
141
|
+
title: "Continuity rule",
|
|
142
|
+
summary: "Pinned reminder: keep stable context visible across fresh windows.",
|
|
143
|
+
tags: ["continuity", "resume_context"],
|
|
144
|
+
source: {
|
|
145
|
+
memoryId: "memory-1",
|
|
146
|
+
scope: "agent:codex",
|
|
147
|
+
timestamp: Date.parse("2026-03-16T04:00:00.000Z"),
|
|
148
|
+
metadata: {},
|
|
149
|
+
},
|
|
150
|
+
snippet: "keep stable context visible across fresh windows",
|
|
151
|
+
path: "/tmp/pin-1.json",
|
|
152
|
+
}],
|
|
153
|
+
}, {
|
|
154
|
+
task: "cross window continuity",
|
|
155
|
+
includeLatestCheckpoint: false,
|
|
156
|
+
limitPerSection: 2,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(checkpointLookups).toBe(0);
|
|
160
|
+
expect(response.latestCheckpoint).toBeUndefined();
|
|
161
|
+
expect(response.stableContext).toContain("Pinned: Continuity rule: Pinned reminder: keep stable context visible across fresh windows.");
|
|
162
|
+
expect(response.relevantPatterns).toEqual(["Use resume_context before starting a new terminal task."]);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("ignores evidence-only stable recall from transcripts", async () => {
|
|
166
|
+
const retriever = {
|
|
167
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
168
|
+
if (context.category === "profile") {
|
|
169
|
+
return [
|
|
170
|
+
withMetadata(
|
|
171
|
+
withScope(buildResult("profile-evidence", "profile", "User is a product builder who likes local tools."), "cc:session"),
|
|
172
|
+
{
|
|
173
|
+
boundary: {
|
|
174
|
+
layer: "evidence",
|
|
175
|
+
authority: "transcript-ingest",
|
|
176
|
+
conflictPolicy: "append-only",
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
),
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (context.category === "preferences") {
|
|
184
|
+
return [
|
|
185
|
+
withMetadata(
|
|
186
|
+
buildResult("pref-durable", "preferences", "User prefers concise technical replies."),
|
|
187
|
+
{
|
|
188
|
+
boundary: {
|
|
189
|
+
layer: "durable",
|
|
190
|
+
authority: "structured-memory",
|
|
191
|
+
conflictPolicy: "latest-wins",
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
),
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return [];
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const response = await composeResumeContext({
|
|
203
|
+
retriever,
|
|
204
|
+
checkpointStore: {
|
|
205
|
+
async getLatest() {
|
|
206
|
+
return null;
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
listPins: () => [],
|
|
210
|
+
}, {
|
|
211
|
+
includeLatestCheckpoint: false,
|
|
212
|
+
limitPerSection: 2,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(response.stableContext).toContain("Preference: User prefers concise technical replies.");
|
|
216
|
+
expect(response.stableContext.some((item) => item.includes("product builder"))).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("prefers durable style preferences over older pins for style-focused writing tasks", async () => {
|
|
220
|
+
const retriever = {
|
|
221
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
222
|
+
if (context.category !== "preferences") return [];
|
|
223
|
+
return [
|
|
224
|
+
withMetadata(
|
|
225
|
+
buildResult(
|
|
226
|
+
"pref-writing-tone",
|
|
227
|
+
"preferences",
|
|
228
|
+
"用户不接受浮夸/亢奋/营销腔,正确语气是口语化、不端着、可自嘲,但不鸡血不吆喝。",
|
|
229
|
+
),
|
|
230
|
+
{
|
|
231
|
+
boundary: {
|
|
232
|
+
layer: "durable",
|
|
233
|
+
authority: "structured-memory",
|
|
234
|
+
conflictPolicy: "latest-wins",
|
|
235
|
+
},
|
|
236
|
+
canonicalKey: "pref-writing-tone-no-hype",
|
|
237
|
+
},
|
|
238
|
+
),
|
|
239
|
+
];
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const response = await composeResumeContext({
|
|
244
|
+
retriever,
|
|
245
|
+
checkpointStore: {
|
|
246
|
+
async getLatest() {
|
|
247
|
+
return null;
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
listPins: () => [{
|
|
251
|
+
id: "pin-writing-style",
|
|
252
|
+
type: "pinned-memory",
|
|
253
|
+
createdAt: "2026-03-16T04:00:00.000Z",
|
|
254
|
+
updatedAt: "2026-03-16T04:00:00.000Z",
|
|
255
|
+
title: "旧写作风格包",
|
|
256
|
+
summary: "80%严肃分析 + 20%口语调剂,banned_fillers 规则已生效。",
|
|
257
|
+
tags: ["写作", "风格"],
|
|
258
|
+
source: {
|
|
259
|
+
memoryId: "memory-old-pin",
|
|
260
|
+
scope: "cc:old-pin",
|
|
261
|
+
timestamp: Date.parse("2026-03-16T04:00:00.000Z"),
|
|
262
|
+
metadata: {},
|
|
263
|
+
},
|
|
264
|
+
snippet: "banned_fillers 规则已生效",
|
|
265
|
+
path: "/tmp/pin-writing-style.json",
|
|
266
|
+
}],
|
|
267
|
+
}, {
|
|
268
|
+
task: "继续公众号「我的AI小木屋」文章写作,回忆写作风格偏好",
|
|
269
|
+
profile: "writing",
|
|
270
|
+
includeLatestCheckpoint: false,
|
|
271
|
+
limitPerSection: 3,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
expect(response.stableContext).toContain(
|
|
275
|
+
"Preference: 用户不接受浮夸/亢奋/营销腔,正确语气是口语化、不端着、可自嘲,但不鸡血不吆喝。",
|
|
276
|
+
);
|
|
277
|
+
expect(response.responseMode).toBe("recall-only");
|
|
278
|
+
expect(response.responseGuidance).toContain("answer from the recalled stable context");
|
|
279
|
+
expect(response.stableContext.some((item) => item.includes("80%严肃分析"))).toBe(false);
|
|
280
|
+
expect(response.stableContext.some((item) => item.startsWith("Pinned:"))).toBe(false);
|
|
281
|
+
expect(response.stableContext.some((item) => item.startsWith("Task focus:"))).toBe(false);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("uses a narrow style fallback query before falling back to task focus", async () => {
|
|
285
|
+
const calls: RetrievalContext[] = [];
|
|
286
|
+
const retriever = {
|
|
287
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
288
|
+
calls.push(context);
|
|
289
|
+
|
|
290
|
+
if (context.category !== "preferences") return [];
|
|
291
|
+
if (context.query.includes("user preferences writing tone voice style habits")) {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
if (context.query.includes("写作风格") && context.query.includes("避免表达")) {
|
|
295
|
+
return [
|
|
296
|
+
withMetadata(
|
|
297
|
+
buildResult(
|
|
298
|
+
"pref-writing-fallback",
|
|
299
|
+
"preferences",
|
|
300
|
+
"用户不喜欢AI味过重的文案语气,偏好口语化、不端着、可自嘲但不浮夸。",
|
|
301
|
+
),
|
|
302
|
+
{
|
|
303
|
+
boundary: {
|
|
304
|
+
layer: "durable",
|
|
305
|
+
authority: "structured-memory",
|
|
306
|
+
conflictPolicy: "latest-wins",
|
|
307
|
+
},
|
|
308
|
+
canonicalKey: "pref-writing-tone-no-hype",
|
|
309
|
+
},
|
|
310
|
+
),
|
|
311
|
+
];
|
|
312
|
+
}
|
|
313
|
+
return [];
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const response = await composeResumeContext({
|
|
318
|
+
retriever,
|
|
319
|
+
checkpointStore: {
|
|
320
|
+
async getLatest() {
|
|
321
|
+
return null;
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
listPins: () => [],
|
|
325
|
+
}, {
|
|
326
|
+
task: "回忆写作风格偏好:语气注意事项、要避免的表达、默认风格",
|
|
327
|
+
profile: "writing",
|
|
328
|
+
includeLatestCheckpoint: false,
|
|
329
|
+
limitPerSection: 3,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
expect(calls.some((call) =>
|
|
333
|
+
call.category === "preferences" &&
|
|
334
|
+
call.query.includes("user preferences writing tone voice style habits")
|
|
335
|
+
)).toBe(true);
|
|
336
|
+
expect(calls.some((call) =>
|
|
337
|
+
call.category === "preferences" &&
|
|
338
|
+
call.query.includes("写作风格") &&
|
|
339
|
+
call.query.includes("避免表达")
|
|
340
|
+
)).toBe(true);
|
|
341
|
+
expect(response.stableContext).toContain(
|
|
342
|
+
"Preference: 用户不喜欢AI味过重的文案语气,偏好口语化、不端着、可自嘲但不浮夸。",
|
|
343
|
+
);
|
|
344
|
+
expect(response.responseMode).toBe("recall-only");
|
|
345
|
+
expect(response.stableContext.some((item) => item.startsWith("Task focus:"))).toBe(false);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it("falls back to broad workflow recall when direct pattern retrieval is empty", async () => {
|
|
349
|
+
const calls: RetrievalContext[] = [];
|
|
350
|
+
const retriever = {
|
|
351
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
352
|
+
calls.push(context);
|
|
353
|
+
if (context.category === "patterns") return [];
|
|
354
|
+
if (!context.category) {
|
|
355
|
+
return [
|
|
356
|
+
buildResult(
|
|
357
|
+
"fact-1",
|
|
358
|
+
"fact",
|
|
359
|
+
"[助手] autoRecall 和 sessionStrategy 是两个独立配置项,开新窗口前先确认自动召回是否开启。",
|
|
360
|
+
),
|
|
361
|
+
];
|
|
362
|
+
}
|
|
363
|
+
return [];
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const response = await composeResumeContext({
|
|
368
|
+
retriever,
|
|
369
|
+
checkpointStore: {
|
|
370
|
+
async getLatest() {
|
|
371
|
+
return null;
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
listPins: () => [],
|
|
375
|
+
}, {
|
|
376
|
+
task: "开个新窗口继续做同一个终端项目",
|
|
377
|
+
includeLatestCheckpoint: false,
|
|
378
|
+
limitPerSection: 2,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
expect(response.relevantPatterns).toEqual([
|
|
382
|
+
"autoRecall 和 sessionStrategy 是两个独立配置项,开新窗口前先确认自动召回是否开启。",
|
|
383
|
+
]);
|
|
384
|
+
expect(calls.some((call) => !call.category && call.query.includes("resume_context"))).toBe(true);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("adds built-in continuity patterns when no workflow memories are available", async () => {
|
|
388
|
+
const retriever = {
|
|
389
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
390
|
+
return [];
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const response = await composeResumeContext({
|
|
395
|
+
retriever,
|
|
396
|
+
checkpointStore: {
|
|
397
|
+
async getLatest() {
|
|
398
|
+
return null;
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
listPins: () => [],
|
|
402
|
+
}, {
|
|
403
|
+
task: "open a fresh window to continue the same terminal project",
|
|
404
|
+
includeLatestCheckpoint: false,
|
|
405
|
+
limitPerSection: 3,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
expect(response.relevantPatterns).toHaveLength(3);
|
|
409
|
+
expect(response.relevantPatterns).toContain(
|
|
410
|
+
"Start fresh windows with resume_context before coding so stable context is restored early.",
|
|
411
|
+
);
|
|
412
|
+
expect(response.relevantPatterns).toContain(
|
|
413
|
+
"If resume_context still leaves gaps, run search_memory with the project name and task nouns before repo exploration drifts.",
|
|
414
|
+
);
|
|
415
|
+
expect(response.relevantPatterns).toContain(
|
|
416
|
+
"Before leaving a window, save checkpoint_session so the next session can recover decisions and next actions.",
|
|
417
|
+
);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it("keeps structured workflow tools visible in pattern summaries", async () => {
|
|
421
|
+
const retriever = {
|
|
422
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
423
|
+
if (context.category !== "patterns") return [];
|
|
424
|
+
return [
|
|
425
|
+
withMetadata(
|
|
426
|
+
buildResult(
|
|
427
|
+
"pattern-structured",
|
|
428
|
+
"patterns",
|
|
429
|
+
"Workflow pattern: Recall before repo exploration",
|
|
430
|
+
),
|
|
431
|
+
{
|
|
432
|
+
workflowPattern: {
|
|
433
|
+
title: "Recall before repo exploration",
|
|
434
|
+
trigger: "When a fresh window continues an existing project and startup context still looks sparse",
|
|
435
|
+
steps: [
|
|
436
|
+
"Call resume_context before reading local files or docs.",
|
|
437
|
+
"If stable context is still thin, run search_memory with the project name and task nouns.",
|
|
438
|
+
"Only after recall is established, inspect the repo and continue implementation.",
|
|
439
|
+
],
|
|
440
|
+
tools: ["resume_context", "search_memory"],
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
),
|
|
444
|
+
];
|
|
445
|
+
},
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const response = await composeResumeContext({
|
|
449
|
+
retriever,
|
|
450
|
+
checkpointStore: {
|
|
451
|
+
async getLatest() {
|
|
452
|
+
return null;
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
listPins: () => [],
|
|
456
|
+
}, {
|
|
457
|
+
task: "开个新窗口继续做同一个终端项目",
|
|
458
|
+
includeLatestCheckpoint: false,
|
|
459
|
+
limitPerSection: 2,
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
expect(response.relevantPatterns).toHaveLength(2);
|
|
463
|
+
expect(response.relevantPatterns[0]).toContain("resume_context");
|
|
464
|
+
expect(response.relevantPatterns[0]).toContain("search_memory");
|
|
465
|
+
expect(response.relevantPatterns[0]).toContain("Recall before repo exploration");
|
|
466
|
+
expect(response.relevantPatterns.join("\n")).toContain("checkpoint_session");
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it("diversifies workflow patterns so strong cue coverage includes search_memory", async () => {
|
|
470
|
+
const pattern = (
|
|
471
|
+
id: string,
|
|
472
|
+
title: string,
|
|
473
|
+
tools: string[],
|
|
474
|
+
steps: string[],
|
|
475
|
+
score: number,
|
|
476
|
+
): RetrievalResult => withMetadata({
|
|
477
|
+
...buildResult(id, "patterns", `Workflow pattern: ${title}`),
|
|
478
|
+
score,
|
|
479
|
+
sources: {
|
|
480
|
+
fused: { score },
|
|
481
|
+
},
|
|
482
|
+
}, {
|
|
483
|
+
workflowPattern: {
|
|
484
|
+
title,
|
|
485
|
+
trigger: "When continuing the same project in a fresh terminal window",
|
|
486
|
+
steps,
|
|
487
|
+
tools,
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
const retriever = {
|
|
492
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
493
|
+
if (context.category !== "patterns") return [];
|
|
494
|
+
return [
|
|
495
|
+
pattern(
|
|
496
|
+
"pattern-checkpoint",
|
|
497
|
+
"Checkpoint before switching windows",
|
|
498
|
+
["checkpoint_session"],
|
|
499
|
+
["Write checkpoint_session before leaving the current window."],
|
|
500
|
+
0.97,
|
|
501
|
+
),
|
|
502
|
+
pattern(
|
|
503
|
+
"pattern-handoff",
|
|
504
|
+
"Cross-window continuity handoff",
|
|
505
|
+
["resume_context", "latest_checkpoint"],
|
|
506
|
+
["Call resume_context before planning work in the fresh window."],
|
|
507
|
+
0.96,
|
|
508
|
+
),
|
|
509
|
+
pattern(
|
|
510
|
+
"pattern-promote",
|
|
511
|
+
"Promote recurring continuity workflow",
|
|
512
|
+
["store_workflow_pattern", "/v1/pattern"],
|
|
513
|
+
["Store recurring continuity workflows as durable patterns."],
|
|
514
|
+
0.95,
|
|
515
|
+
),
|
|
516
|
+
pattern(
|
|
517
|
+
"pattern-search",
|
|
518
|
+
"Recall before repo exploration",
|
|
519
|
+
["resume_context", "search_memory"],
|
|
520
|
+
["Run search_memory with the project name before inspecting local files."],
|
|
521
|
+
0.9,
|
|
522
|
+
),
|
|
523
|
+
];
|
|
524
|
+
},
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
const response = await composeResumeContext({
|
|
528
|
+
retriever,
|
|
529
|
+
checkpointStore: {
|
|
530
|
+
async getLatest() {
|
|
531
|
+
return null;
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
listPins: () => [],
|
|
535
|
+
}, {
|
|
536
|
+
task: "开个新窗口继续做同一个终端项目",
|
|
537
|
+
includeLatestCheckpoint: false,
|
|
538
|
+
limitPerSection: 3,
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
expect(response.relevantPatterns).toHaveLength(3);
|
|
542
|
+
expect(response.relevantPatterns.join("\n")).toContain("search_memory");
|
|
543
|
+
expect(response.relevantPatterns.join("\n")).toContain("Recall before repo exploration");
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it("uses broader workflow fallback when direct continuity patterns miss search_memory coverage", async () => {
|
|
547
|
+
const calls: RetrievalContext[] = [];
|
|
548
|
+
const pattern = (
|
|
549
|
+
id: string,
|
|
550
|
+
title: string,
|
|
551
|
+
tools: string[],
|
|
552
|
+
steps: string[],
|
|
553
|
+
): RetrievalResult => withMetadata(
|
|
554
|
+
buildResult(id, "patterns", `Workflow pattern: ${title}`),
|
|
555
|
+
{
|
|
556
|
+
workflowPattern: {
|
|
557
|
+
title,
|
|
558
|
+
trigger: "When continuing the same project in a fresh terminal window",
|
|
559
|
+
steps,
|
|
560
|
+
tools,
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
const retriever = {
|
|
566
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
567
|
+
calls.push(context);
|
|
568
|
+
if (context.category === "patterns") {
|
|
569
|
+
return [
|
|
570
|
+
pattern(
|
|
571
|
+
"pattern-checkpoint",
|
|
572
|
+
"Checkpoint before switching windows",
|
|
573
|
+
["checkpoint_session"],
|
|
574
|
+
["Write checkpoint_session before leaving the current window."],
|
|
575
|
+
),
|
|
576
|
+
pattern(
|
|
577
|
+
"pattern-handoff",
|
|
578
|
+
"Cross-window continuity handoff",
|
|
579
|
+
["resume_context", "latest_checkpoint"],
|
|
580
|
+
["Call resume_context before planning work in the fresh window."],
|
|
581
|
+
),
|
|
582
|
+
pattern(
|
|
583
|
+
"pattern-promote",
|
|
584
|
+
"Promote recurring continuity workflow",
|
|
585
|
+
["store_workflow_pattern", "/v1/pattern"],
|
|
586
|
+
["Store recurring continuity workflows as durable patterns."],
|
|
587
|
+
),
|
|
588
|
+
];
|
|
589
|
+
}
|
|
590
|
+
if (!context.category) {
|
|
591
|
+
return [
|
|
592
|
+
withMetadata(
|
|
593
|
+
buildResult(
|
|
594
|
+
"pattern-search",
|
|
595
|
+
"patterns",
|
|
596
|
+
"Workflow pattern: Recall before repo exploration\nUse when: When continuing the same project in a fresh terminal window\nSteps:\n1. Run search_memory with the project name before inspecting local files.\nTools: resume_context, search_memory\nOutcome: Fresh windows recover task detail before repo exploration drifts.",
|
|
597
|
+
),
|
|
598
|
+
{
|
|
599
|
+
workflowPattern: {
|
|
600
|
+
title: "Recall before repo exploration",
|
|
601
|
+
trigger: "When continuing the same project in a fresh terminal window",
|
|
602
|
+
steps: ["Run search_memory with the project name before inspecting local files."],
|
|
603
|
+
tools: ["resume_context", "search_memory"],
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
),
|
|
607
|
+
];
|
|
608
|
+
}
|
|
609
|
+
return [];
|
|
610
|
+
},
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
const response = await composeResumeContext({
|
|
614
|
+
retriever,
|
|
615
|
+
checkpointStore: {
|
|
616
|
+
async getLatest() {
|
|
617
|
+
return null;
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
listPins: () => [],
|
|
621
|
+
}, {
|
|
622
|
+
task: "开个新窗口继续做同一个终端项目",
|
|
623
|
+
includeLatestCheckpoint: false,
|
|
624
|
+
limitPerSection: 3,
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
expect(response.relevantPatterns).toHaveLength(3);
|
|
628
|
+
expect(response.relevantPatterns.join("\n")).toContain("search_memory");
|
|
629
|
+
expect(calls.some((call) => !call.category && call.query.includes("search_memory"))).toBe(true);
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
it("supplements continuity workflow coverage with built-in search_memory fallback when retrieval stays sparse", async () => {
|
|
633
|
+
const pattern = (
|
|
634
|
+
id: string,
|
|
635
|
+
title: string,
|
|
636
|
+
tools: string[],
|
|
637
|
+
steps: string[],
|
|
638
|
+
): RetrievalResult => withMetadata(
|
|
639
|
+
buildResult(id, "patterns", `Workflow pattern: ${title}`),
|
|
640
|
+
{
|
|
641
|
+
workflowPattern: {
|
|
642
|
+
title,
|
|
643
|
+
trigger: "When continuing the same project in a fresh terminal window",
|
|
644
|
+
steps,
|
|
645
|
+
tools,
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
);
|
|
649
|
+
|
|
650
|
+
const retriever = {
|
|
651
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
652
|
+
if (context.category === "patterns") {
|
|
653
|
+
return [
|
|
654
|
+
pattern(
|
|
655
|
+
"pattern-handoff",
|
|
656
|
+
"Cross-window continuity handoff",
|
|
657
|
+
["resume_context", "latest_checkpoint"],
|
|
658
|
+
["Call resume_context before planning work in the fresh window."],
|
|
659
|
+
),
|
|
660
|
+
pattern(
|
|
661
|
+
"pattern-promote",
|
|
662
|
+
"Promote recurring continuity workflow",
|
|
663
|
+
["store_workflow_pattern", "/v1/pattern"],
|
|
664
|
+
["Store recurring continuity workflows as durable patterns."],
|
|
665
|
+
),
|
|
666
|
+
];
|
|
667
|
+
}
|
|
668
|
+
return [];
|
|
669
|
+
},
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
const response = await composeResumeContext({
|
|
673
|
+
retriever,
|
|
674
|
+
checkpointStore: {
|
|
675
|
+
async getLatest() {
|
|
676
|
+
return null;
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
listPins: () => [],
|
|
680
|
+
}, {
|
|
681
|
+
task: "开个新窗口继续做同一个终端项目",
|
|
682
|
+
includeLatestCheckpoint: false,
|
|
683
|
+
limitPerSection: 3,
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
expect(response.relevantPatterns).toHaveLength(2);
|
|
687
|
+
expect(response.relevantPatterns.join("\n")).toContain("search_memory");
|
|
688
|
+
expect(response.relevantPatterns.join("\n")).toContain("resume_context");
|
|
689
|
+
expect(response.relevantPatterns.join("\n")).not.toContain("Promote recurring continuity workflow");
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
it("supplements a single handoff pattern with built-in search_memory guidance when cue coverage is still incomplete", async () => {
|
|
693
|
+
const retriever = {
|
|
694
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
695
|
+
if (context.category === "patterns") {
|
|
696
|
+
return [
|
|
697
|
+
withMetadata(
|
|
698
|
+
buildResult(
|
|
699
|
+
"pattern-handoff-only",
|
|
700
|
+
"patterns",
|
|
701
|
+
"Workflow pattern: Cross-window continuity handoff",
|
|
702
|
+
),
|
|
703
|
+
{
|
|
704
|
+
workflowPattern: {
|
|
705
|
+
title: "Cross-window continuity handoff",
|
|
706
|
+
trigger: "When opening a fresh terminal window for the same project",
|
|
707
|
+
tools: ["resume_context", "latest_checkpoint"],
|
|
708
|
+
steps: ["Call resume_context before planning work in the fresh window."],
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
),
|
|
712
|
+
];
|
|
713
|
+
}
|
|
714
|
+
return [];
|
|
715
|
+
},
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
const response = await composeResumeContext({
|
|
719
|
+
retriever,
|
|
720
|
+
checkpointStore: {
|
|
721
|
+
async getLatest() {
|
|
722
|
+
return null;
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
listPins: () => [],
|
|
726
|
+
}, {
|
|
727
|
+
task: "开个新窗口继续做同一个终端项目",
|
|
728
|
+
includeLatestCheckpoint: false,
|
|
729
|
+
limitPerSection: 3,
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
expect(response.relevantPatterns.join("\n")).toContain("Cross-window continuity handoff");
|
|
733
|
+
expect(response.relevantPatterns.join("\n")).toContain("search_memory");
|
|
734
|
+
expect(response.relevantPatterns.join("\n")).toContain("resume_context");
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
it("supplements a sparse checkpoint-backed project handoff with built-in search_memory guidance", async () => {
|
|
738
|
+
const retriever = {
|
|
739
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
740
|
+
if (context.category === "patterns") {
|
|
741
|
+
return [
|
|
742
|
+
withMetadata(
|
|
743
|
+
buildResult(
|
|
744
|
+
"pattern-promote-only",
|
|
745
|
+
"patterns",
|
|
746
|
+
"Workflow pattern: Promote recurring continuity workflow",
|
|
747
|
+
),
|
|
748
|
+
{
|
|
749
|
+
workflowPattern: {
|
|
750
|
+
title: "Promote recurring continuity workflow",
|
|
751
|
+
trigger: "When continuity workflows repeat across sessions",
|
|
752
|
+
tools: ["store_workflow_pattern", "/v1/pattern"],
|
|
753
|
+
steps: ["Store recurring continuity workflows as durable patterns."],
|
|
754
|
+
},
|
|
755
|
+
},
|
|
756
|
+
),
|
|
757
|
+
];
|
|
758
|
+
}
|
|
759
|
+
return [];
|
|
760
|
+
},
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
const response = await composeResumeContext({
|
|
764
|
+
retriever,
|
|
765
|
+
checkpointStore: {
|
|
766
|
+
async getLatest() {
|
|
767
|
+
return {
|
|
768
|
+
checkpointId: "checkpoint-task-pivot",
|
|
769
|
+
sessionId: "task-pivot-session",
|
|
770
|
+
resolvedScope: "project:recallnest",
|
|
771
|
+
summary: "RecallNest 已补上 HTTP auto-recall,下一步把 task-pivot 的主动 recall 变成 smoke 和 eval 的稳定回归信号。",
|
|
772
|
+
task: "RecallNest task-pivot recall regression",
|
|
773
|
+
decisions: ["任务切换时也要先回忆,不能只在 continue 场景触发"],
|
|
774
|
+
openLoops: [],
|
|
775
|
+
nextActions: ["补 task-pivot continuity eval case"],
|
|
776
|
+
entities: ["RecallNest", "auto-recall", "search_memory"],
|
|
777
|
+
files: ["src/context-composer.ts"],
|
|
778
|
+
updatedAt: "2026-03-17T14:40:00.000Z",
|
|
779
|
+
} satisfies SessionCheckpointRecord;
|
|
780
|
+
},
|
|
781
|
+
},
|
|
782
|
+
listPins: () => [],
|
|
783
|
+
}, {
|
|
784
|
+
task: "回到 RecallNest 项目,处理主动 recall regression",
|
|
785
|
+
scope: "project:recallnest",
|
|
786
|
+
includeLatestCheckpoint: true,
|
|
787
|
+
limitPerSection: 3,
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
expect(response.relevantPatterns.join("\n")).toContain("search_memory");
|
|
791
|
+
expect(response.relevantPatterns.join("\n")).toContain("resume_context");
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
it("supplements a single low-coverage continuity pattern without needing a checkpoint", async () => {
|
|
795
|
+
const retriever = {
|
|
796
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
797
|
+
if (context.category === "patterns") {
|
|
798
|
+
return [
|
|
799
|
+
withMetadata(
|
|
800
|
+
buildResult(
|
|
801
|
+
"pattern-promote-only-no-checkpoint",
|
|
802
|
+
"patterns",
|
|
803
|
+
"Workflow pattern: Promote recurring continuity workflow",
|
|
804
|
+
),
|
|
805
|
+
{
|
|
806
|
+
workflowPattern: {
|
|
807
|
+
title: "Promote recurring continuity workflow",
|
|
808
|
+
trigger: "When continuity workflows repeat across sessions",
|
|
809
|
+
tools: ["store_workflow_pattern", "/v1/pattern"],
|
|
810
|
+
steps: ["Store recurring continuity workflows as durable patterns."],
|
|
811
|
+
},
|
|
812
|
+
},
|
|
813
|
+
),
|
|
814
|
+
];
|
|
815
|
+
}
|
|
816
|
+
return [];
|
|
817
|
+
},
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
const response = await composeResumeContext({
|
|
821
|
+
retriever,
|
|
822
|
+
checkpointStore: {
|
|
823
|
+
async getLatest() {
|
|
824
|
+
return null;
|
|
825
|
+
},
|
|
826
|
+
},
|
|
827
|
+
listPins: () => [],
|
|
828
|
+
}, {
|
|
829
|
+
task: "开个新窗口继续做同一个终端项目",
|
|
830
|
+
includeLatestCheckpoint: false,
|
|
831
|
+
limitPerSection: 3,
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
expect(response.relevantPatterns).toHaveLength(3);
|
|
835
|
+
expect(response.relevantPatterns.join("\n")).toContain("search_memory");
|
|
836
|
+
expect(response.relevantPatterns.join("\n")).toContain("resume_context");
|
|
837
|
+
expect(response.relevantPatterns.join("\n")).toContain("checkpoint");
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
it("filters plan-like non-durable pattern notes so workflow fallback can recover durable patterns", async () => {
|
|
841
|
+
const calls: RetrievalContext[] = [];
|
|
842
|
+
const retriever = {
|
|
843
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
844
|
+
calls.push(context);
|
|
845
|
+
if (context.category === "patterns") {
|
|
846
|
+
return [
|
|
847
|
+
withScope(
|
|
848
|
+
buildResult(
|
|
849
|
+
"pattern-note",
|
|
850
|
+
"patterns",
|
|
851
|
+
"我先用 resume_context 恢复上下文,再跑 search_memory 看看还有哪些线索。",
|
|
852
|
+
),
|
|
853
|
+
"cc:working-note",
|
|
854
|
+
),
|
|
855
|
+
];
|
|
856
|
+
}
|
|
857
|
+
if (!context.category) {
|
|
858
|
+
return [
|
|
859
|
+
buildResult(
|
|
860
|
+
"pattern-fallback",
|
|
861
|
+
"fact",
|
|
862
|
+
"Workflow pattern: Cross-window continuity handoff Use when: When opening a fresh terminal window for the same project Steps: 1. Call resume_context before coding. 2. Review stable context before reading local files. 3. Save checkpoint_session before leaving the window.",
|
|
863
|
+
),
|
|
864
|
+
];
|
|
865
|
+
}
|
|
866
|
+
return [];
|
|
867
|
+
},
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
const response = await composeResumeContext({
|
|
871
|
+
retriever,
|
|
872
|
+
checkpointStore: {
|
|
873
|
+
async getLatest() {
|
|
874
|
+
return null;
|
|
875
|
+
},
|
|
876
|
+
},
|
|
877
|
+
listPins: () => [],
|
|
878
|
+
}, {
|
|
879
|
+
task: "开个新窗口继续做同一个终端项目",
|
|
880
|
+
includeLatestCheckpoint: false,
|
|
881
|
+
limitPerSection: 2,
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
expect(response.relevantPatterns).toHaveLength(1);
|
|
885
|
+
expect(response.relevantPatterns[0]).toContain("Workflow pattern: Cross-window continuity handoff");
|
|
886
|
+
expect(response.relevantPatterns[0]).toContain("Call resume_context before coding");
|
|
887
|
+
expect(response.relevantPatterns.join(" ")).not.toContain("我先用 resume_context");
|
|
888
|
+
expect(calls.some((call) => !call.category && call.query.includes("checkpoint_session"))).toBe(true);
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
it("filters transcript-style non-durable pattern fragments for generic RecallNest continues", async () => {
|
|
892
|
+
const calls: RetrievalContext[] = [];
|
|
893
|
+
const retriever = {
|
|
894
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
895
|
+
calls.push(context);
|
|
896
|
+
if (context.category === "patterns") {
|
|
897
|
+
return [
|
|
898
|
+
withScope(
|
|
899
|
+
buildResult(
|
|
900
|
+
"pattern-transcript-fragment",
|
|
901
|
+
"patterns",
|
|
902
|
+
"[助手] 对,这次快很多,而且原因很明确。从你这张图看,路径已经变成了:- `resume_context` - 直接回答。不再有这几层绕路:- 本地文件读取 - `search_memory` 二次补查。",
|
|
903
|
+
),
|
|
904
|
+
"codex:session-fragment",
|
|
905
|
+
),
|
|
906
|
+
];
|
|
907
|
+
}
|
|
908
|
+
if (!context.category) {
|
|
909
|
+
return [
|
|
910
|
+
buildResult(
|
|
911
|
+
"pattern-fallback-clean",
|
|
912
|
+
"fact",
|
|
913
|
+
"Workflow pattern: Recall before repo exploration Use when: When a fresh window continues the same project but task details are still sparse Steps: 1. Run search_memory before reading local files. Tools: resume_context, search_memory. Outcome: Fresh windows recover task detail before repo exploration drifts.",
|
|
914
|
+
),
|
|
915
|
+
];
|
|
916
|
+
}
|
|
917
|
+
return [];
|
|
918
|
+
},
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
const response = await composeResumeContext({
|
|
922
|
+
retriever,
|
|
923
|
+
checkpointStore: {
|
|
924
|
+
async getLatest() {
|
|
925
|
+
return null;
|
|
926
|
+
},
|
|
927
|
+
},
|
|
928
|
+
listPins: () => [],
|
|
929
|
+
}, {
|
|
930
|
+
task: "继续整理 RecallNest 实施清单,不要让我重复前情",
|
|
931
|
+
includeLatestCheckpoint: false,
|
|
932
|
+
limitPerSection: 2,
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
expect(response.relevantPatterns).toHaveLength(1);
|
|
936
|
+
expect(response.relevantPatterns[0]).toContain("Workflow pattern: Recall before repo exploration");
|
|
937
|
+
expect(response.relevantPatterns.join(" ")).not.toContain("这次快很多");
|
|
938
|
+
expect(response.relevantPatterns.join(" ")).not.toContain("直接回答");
|
|
939
|
+
expect(calls.some((call) => !call.category && call.query.includes("checkpoint_session"))).toBe(true);
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
it("filters low-signal stable recall and backfills with checkpoint context", async () => {
|
|
943
|
+
const checkpoint: SessionCheckpointRecord = {
|
|
944
|
+
checkpointId: "checkpoint-002",
|
|
945
|
+
sessionId: "recallnest-session",
|
|
946
|
+
resolvedScope: "recallnest",
|
|
947
|
+
summary: "Phase 3 continuity work is active and resume_context compose quality is the current bottleneck.",
|
|
948
|
+
task: "RecallNest continuity layer 开发状态梳理",
|
|
949
|
+
decisions: ["resume_context compose 质量是最高优先级短板"],
|
|
950
|
+
openLoops: ["Need broader continuity eval coverage"],
|
|
951
|
+
nextActions: ["改进 compose 质量:增加 pattern/case 召回率,优化 stable context 筛选"],
|
|
952
|
+
entities: ["recallnest (~/recallnest/)"],
|
|
953
|
+
files: ["src/context-composer.ts"],
|
|
954
|
+
updatedAt: "2026-03-16T08:00:00.000Z",
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
const retriever = {
|
|
958
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
959
|
+
if (context.category === "entities") {
|
|
960
|
+
return [
|
|
961
|
+
withScope(
|
|
962
|
+
buildResult("entity-noise-1", "entities", "[助手] 再看看 RecallNest 现有的 setup 脚本和项目结构。"),
|
|
963
|
+
"cc:session",
|
|
964
|
+
),
|
|
965
|
+
withScope(
|
|
966
|
+
buildResult("entity-noise-2", "entities", "[助手] recallnest 在 GitHub 上有但本地没 clone。"),
|
|
967
|
+
"cc:session",
|
|
968
|
+
),
|
|
969
|
+
];
|
|
970
|
+
}
|
|
971
|
+
return [];
|
|
972
|
+
},
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
const response = await composeResumeContext({
|
|
976
|
+
retriever,
|
|
977
|
+
checkpointStore: {
|
|
978
|
+
async getLatest() {
|
|
979
|
+
return checkpoint;
|
|
980
|
+
},
|
|
981
|
+
},
|
|
982
|
+
listPins: () => [{
|
|
983
|
+
id: "pin-visual",
|
|
984
|
+
type: "pinned-memory",
|
|
985
|
+
createdAt: "2026-03-16T04:00:00.000Z",
|
|
986
|
+
updatedAt: "2026-03-16T04:00:00.000Z",
|
|
987
|
+
title: "Visual style",
|
|
988
|
+
summary: "Pinned reminder: 用户常用视觉风格是手绘涂鸦风加高对比撞色。",
|
|
989
|
+
tags: ["visual-style"],
|
|
990
|
+
source: {
|
|
991
|
+
memoryId: "memory-visual",
|
|
992
|
+
scope: "memory:agent",
|
|
993
|
+
timestamp: Date.parse("2026-03-16T04:00:00.000Z"),
|
|
994
|
+
metadata: {},
|
|
995
|
+
},
|
|
996
|
+
snippet: "手绘涂鸦风加高对比撞色",
|
|
997
|
+
path: "/tmp/pin-visual.json",
|
|
998
|
+
}],
|
|
999
|
+
}, {
|
|
1000
|
+
scope: "recallnest",
|
|
1001
|
+
task: "RecallNest项目当前状态、最近进展、下一步计划",
|
|
1002
|
+
limitPerSection: 3,
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
expect(response.stableContext).toContain("Checkpoint focus: RecallNest continuity layer 开发状态梳理");
|
|
1006
|
+
expect(response.stableContext).toContain("Checkpoint decision: resume_context compose 质量是最高优先级短板");
|
|
1007
|
+
expect(response.stableContext.some((item) => item.includes("手绘涂鸦"))).toBe(false);
|
|
1008
|
+
expect(response.stableContext.some((item) => item.includes("本地没 clone"))).toBe(false);
|
|
1009
|
+
expect(response.stableContext.some((item) => item.includes("再看看"))).toBe(false);
|
|
1010
|
+
expect(response.summary).toContain("Stable context:");
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
it("prioritizes checkpoint summary over checkpoint focus when stable slots are tight", async () => {
|
|
1014
|
+
const checkpoint: SessionCheckpointRecord = {
|
|
1015
|
+
checkpointId: "checkpoint-task-pivot",
|
|
1016
|
+
sessionId: "task-pivot-session",
|
|
1017
|
+
resolvedScope: "project:recallnest",
|
|
1018
|
+
summary: "RecallNest 已补上 HTTP auto-recall,下一步把 task-pivot 的主动 recall 变成 smoke 和 eval 的稳定回归信号。",
|
|
1019
|
+
task: "RecallNest task-pivot recall regression",
|
|
1020
|
+
decisions: ["任务切换时也要先回忆,不能只在 continue 场景触发"],
|
|
1021
|
+
openLoops: [],
|
|
1022
|
+
nextActions: ["补 task-pivot continuity eval case"],
|
|
1023
|
+
entities: ["RecallNest", "auto-recall"],
|
|
1024
|
+
files: ["src/context-composer.ts"],
|
|
1025
|
+
updatedAt: "2026-03-17T14:40:00.000Z",
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
const retriever = {
|
|
1029
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
1030
|
+
if (context.category !== "entities") return [];
|
|
1031
|
+
return [
|
|
1032
|
+
buildResult(
|
|
1033
|
+
"entity-primitives",
|
|
1034
|
+
"entities",
|
|
1035
|
+
"RecallNest continuity revolves around three primitives: store_memory, checkpoint_session, and resume_context.",
|
|
1036
|
+
),
|
|
1037
|
+
buildResult(
|
|
1038
|
+
"entity-transport",
|
|
1039
|
+
"entities",
|
|
1040
|
+
"RecallNest exposes continuity through both HTTP API and MCP.",
|
|
1041
|
+
),
|
|
1042
|
+
];
|
|
1043
|
+
},
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
const response = await composeResumeContext({
|
|
1047
|
+
retriever,
|
|
1048
|
+
checkpointStore: {
|
|
1049
|
+
async getLatest() {
|
|
1050
|
+
return checkpoint;
|
|
1051
|
+
},
|
|
1052
|
+
},
|
|
1053
|
+
listPins: () => [],
|
|
1054
|
+
}, {
|
|
1055
|
+
task: "回到 RecallNest 项目,处理主动 recall regression",
|
|
1056
|
+
scope: "project:recallnest",
|
|
1057
|
+
includeLatestCheckpoint: true,
|
|
1058
|
+
limitPerSection: 3,
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
expect(response.stableContext).toHaveLength(3);
|
|
1062
|
+
expect(response.stableContext.some((item) => item.includes("auto-recall"))).toBe(true);
|
|
1063
|
+
expect(response.stableContext.some((item) => item.includes("Checkpoint summary:"))).toBe(true);
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
it("enriches latest checkpoint summary with missing checkpoint entity hints", async () => {
|
|
1067
|
+
const checkpoint: SessionCheckpointRecord = {
|
|
1068
|
+
checkpointId: "checkpoint-project-scope",
|
|
1069
|
+
sessionId: "eval-project-recallnest-checkpoint",
|
|
1070
|
+
resolvedScope: "project:recallnest",
|
|
1071
|
+
summary: "RecallNest 项目范围下已完成 continuity baseline setup、doctor baseline 和 headless Claude Code smoke,下一步继续扩 continuity eval case。",
|
|
1072
|
+
task: "RecallNest continuity acceptance — project scope handoff",
|
|
1073
|
+
decisions: ["继续把 project-scope continuity 做成稳定回归信号"],
|
|
1074
|
+
openLoops: [],
|
|
1075
|
+
nextActions: ["补更多 scoped project continuity cases"],
|
|
1076
|
+
entities: ["RecallNest", "seed:continuity", "doctor", "smoke:claude-continuity"],
|
|
1077
|
+
files: ["src/context-composer.ts"],
|
|
1078
|
+
updatedAt: "2026-03-17T08:40:00.000Z",
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
const response = await composeResumeContext({
|
|
1082
|
+
retriever: {
|
|
1083
|
+
async retrieve() {
|
|
1084
|
+
return [];
|
|
1085
|
+
},
|
|
1086
|
+
},
|
|
1087
|
+
checkpointStore: {
|
|
1088
|
+
async getLatest() {
|
|
1089
|
+
return checkpoint;
|
|
1090
|
+
},
|
|
1091
|
+
},
|
|
1092
|
+
listPins: () => [],
|
|
1093
|
+
}, {
|
|
1094
|
+
task: "继续这个项目,不要让我重复前情",
|
|
1095
|
+
scope: "project:recallnest",
|
|
1096
|
+
includeLatestCheckpoint: true,
|
|
1097
|
+
limitPerSection: 3,
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
expect(response.latestCheckpoint?.summary).toContain("smoke:claude-continuity");
|
|
1101
|
+
expect(response.summary).toContain("smoke:claude-continuity");
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
it("uses task hints to keep relevant writing or visual pins in sparse contexts", async () => {
|
|
1105
|
+
const retriever = {
|
|
1106
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
1107
|
+
return [];
|
|
1108
|
+
},
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
const response = await composeResumeContext({
|
|
1112
|
+
retriever,
|
|
1113
|
+
checkpointStore: {
|
|
1114
|
+
async getLatest() {
|
|
1115
|
+
return null;
|
|
1116
|
+
},
|
|
1117
|
+
},
|
|
1118
|
+
listPins: () => [{
|
|
1119
|
+
id: "pin-visual",
|
|
1120
|
+
type: "pinned-memory",
|
|
1121
|
+
createdAt: "2026-03-16T04:00:00.000Z",
|
|
1122
|
+
updatedAt: "2026-03-16T04:00:00.000Z",
|
|
1123
|
+
title: "用户视觉审美偏好",
|
|
1124
|
+
summary: "用户常用视觉风格是手绘涂鸦风加高对比撞色;在内容包装和配图生成时,应优先沿用这一审美方向。",
|
|
1125
|
+
tags: ["审美偏好", "手绘涂鸦", "高对比撞色", "配图"],
|
|
1126
|
+
source: {
|
|
1127
|
+
memoryId: "memory-visual",
|
|
1128
|
+
scope: "cc:397f4d4d",
|
|
1129
|
+
timestamp: Date.parse("2026-03-16T04:00:00.000Z"),
|
|
1130
|
+
metadata: {},
|
|
1131
|
+
},
|
|
1132
|
+
snippet: "给刚才写的文章生成配图,风格:手绘涂鸦风+高对比撞色。",
|
|
1133
|
+
path: "/tmp/pin-visual.json",
|
|
1134
|
+
}],
|
|
1135
|
+
}, {
|
|
1136
|
+
task: "给文章做封面和配图",
|
|
1137
|
+
includeLatestCheckpoint: false,
|
|
1138
|
+
limitPerSection: 2,
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
expect(response.stableContext.join(" ")).toContain(
|
|
1142
|
+
"Pinned: 用户视觉审美偏好: 用户常用视觉风格是手绘涂鸦风加高对比撞色;在内容包装和配图生成时,应优先沿用这一审美方向。",
|
|
1143
|
+
);
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
it("keeps writing-style pins visible for sparse writing prompts", async () => {
|
|
1147
|
+
const retriever = {
|
|
1148
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
1149
|
+
return [];
|
|
1150
|
+
},
|
|
1151
|
+
};
|
|
1152
|
+
|
|
1153
|
+
const response = await composeResumeContext({
|
|
1154
|
+
retriever,
|
|
1155
|
+
checkpointStore: {
|
|
1156
|
+
async getLatest() {
|
|
1157
|
+
return null;
|
|
1158
|
+
},
|
|
1159
|
+
},
|
|
1160
|
+
listPins: () => [{
|
|
1161
|
+
id: "pin-writing-style",
|
|
1162
|
+
type: "pinned-memory",
|
|
1163
|
+
createdAt: "2026-03-16T04:00:00.000Z",
|
|
1164
|
+
updatedAt: "2026-03-16T04:00:00.000Z",
|
|
1165
|
+
title: "用户写作语气偏好",
|
|
1166
|
+
summary: "用户稳定偏好口语化、不端着、可以自嘲但不说教。",
|
|
1167
|
+
tags: ["写作", "语气", "风格"],
|
|
1168
|
+
source: {
|
|
1169
|
+
memoryId: "memory-writing",
|
|
1170
|
+
scope: "cc:writing-pin",
|
|
1171
|
+
timestamp: Date.parse("2026-03-16T04:00:00.000Z"),
|
|
1172
|
+
metadata: {},
|
|
1173
|
+
},
|
|
1174
|
+
snippet: "口语化、不端着、可以自嘲",
|
|
1175
|
+
path: "/tmp/pin-writing-style.json",
|
|
1176
|
+
}],
|
|
1177
|
+
}, {
|
|
1178
|
+
task: "不要让我重复前情,接着写",
|
|
1179
|
+
profile: "writing",
|
|
1180
|
+
includeLatestCheckpoint: false,
|
|
1181
|
+
limitPerSection: 2,
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
expect(response.stableContext).toContain(
|
|
1185
|
+
"Pinned: 用户写作语气偏好: 用户稳定偏好口语化、不端着、可以自嘲但不说教。",
|
|
1186
|
+
);
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
it("keeps Chinese writing pins visible for English writing-project prompts", async () => {
|
|
1190
|
+
const retriever = {
|
|
1191
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
1192
|
+
return [];
|
|
1193
|
+
},
|
|
1194
|
+
};
|
|
1195
|
+
|
|
1196
|
+
const response = await composeResumeContext({
|
|
1197
|
+
retriever,
|
|
1198
|
+
checkpointStore: {
|
|
1199
|
+
async getLatest() {
|
|
1200
|
+
return null;
|
|
1201
|
+
},
|
|
1202
|
+
},
|
|
1203
|
+
listPins: () => [{
|
|
1204
|
+
id: "pin-writing-cross-language",
|
|
1205
|
+
type: "pinned-memory",
|
|
1206
|
+
createdAt: "2026-03-16T04:00:00.000Z",
|
|
1207
|
+
updatedAt: "2026-03-16T04:00:00.000Z",
|
|
1208
|
+
title: "用户写作语气偏好",
|
|
1209
|
+
summary: "用户稳定偏好口语化、不端着、可以自嘲但不说教。",
|
|
1210
|
+
tags: ["写作风格", "口语化", "不端着"],
|
|
1211
|
+
source: {
|
|
1212
|
+
memoryId: "memory-writing-cross-language",
|
|
1213
|
+
scope: "cc:writing-pin",
|
|
1214
|
+
timestamp: Date.parse("2026-03-16T04:00:00.000Z"),
|
|
1215
|
+
metadata: {},
|
|
1216
|
+
},
|
|
1217
|
+
snippet: "[助手] 根据你的档案:AI 野路子,不是程序员。公众号「我的AI小木屋」运营者。写作风格:口语化、不端着、可以自嘲但不说教。",
|
|
1218
|
+
path: "/tmp/pin-writing-cross-language.json",
|
|
1219
|
+
}],
|
|
1220
|
+
}, {
|
|
1221
|
+
task: "continue my AI writing project",
|
|
1222
|
+
scope: "project:recallnest",
|
|
1223
|
+
includeLatestCheckpoint: false,
|
|
1224
|
+
limitPerSection: 3,
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
const stableJoined = response.stableContext.join(" ");
|
|
1228
|
+
expect(stableJoined).toContain("写作");
|
|
1229
|
+
expect(stableJoined).toContain("AI");
|
|
1230
|
+
expect(stableJoined).toContain("公众号");
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
it("skips unrelated pins for continuity status prompts when project entity context is already present", async () => {
|
|
1234
|
+
const retriever = {
|
|
1235
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
1236
|
+
if (context.category === "entities") {
|
|
1237
|
+
return [
|
|
1238
|
+
withScope(
|
|
1239
|
+
buildResult(
|
|
1240
|
+
"entity-recallnest-status",
|
|
1241
|
+
"entities",
|
|
1242
|
+
"RecallNest continuity revolves around three primitives: store_memory, checkpoint_session, and resume_context.",
|
|
1243
|
+
),
|
|
1244
|
+
"project:recallnest",
|
|
1245
|
+
),
|
|
1246
|
+
];
|
|
1247
|
+
}
|
|
1248
|
+
return [];
|
|
1249
|
+
},
|
|
1250
|
+
};
|
|
1251
|
+
|
|
1252
|
+
const response = await composeResumeContext({
|
|
1253
|
+
retriever,
|
|
1254
|
+
checkpointStore: {
|
|
1255
|
+
async getLatest() {
|
|
1256
|
+
return null;
|
|
1257
|
+
},
|
|
1258
|
+
},
|
|
1259
|
+
listPins: () => [{
|
|
1260
|
+
id: "pin-visual-status",
|
|
1261
|
+
type: "pinned-memory",
|
|
1262
|
+
createdAt: "2026-03-16T04:00:00.000Z",
|
|
1263
|
+
updatedAt: "2026-03-16T04:00:00.000Z",
|
|
1264
|
+
title: "用户视觉审美偏好",
|
|
1265
|
+
summary: "用户常用视觉风格是手绘涂鸦风加高对比撞色;在内容包装和配图生成时,应优先沿用这一审美方向。",
|
|
1266
|
+
tags: ["审美偏好", "手绘涂鸦", "高对比撞色", "配图"],
|
|
1267
|
+
source: {
|
|
1268
|
+
memoryId: "memory-visual-status",
|
|
1269
|
+
scope: "cc:visual-pin",
|
|
1270
|
+
timestamp: Date.parse("2026-03-16T04:00:00.000Z"),
|
|
1271
|
+
metadata: {},
|
|
1272
|
+
},
|
|
1273
|
+
snippet: "给刚才写的文章生成配图,风格:手绘涂鸦风+高对比撞色。",
|
|
1274
|
+
path: "/tmp/pin-visual-status.json",
|
|
1275
|
+
}],
|
|
1276
|
+
}, {
|
|
1277
|
+
task: "RecallNest 刚才做到哪了",
|
|
1278
|
+
includeLatestCheckpoint: false,
|
|
1279
|
+
limitPerSection: 3,
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
expect(response.stableContext.join(" ")).toContain("RecallNest continuity revolves around three primitives");
|
|
1283
|
+
expect(response.stableContext.join(" ")).not.toContain("手绘涂鸦");
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
it("suppresses project-scoped smoke-validation preferences for generic shared-memory continues", async () => {
|
|
1287
|
+
const retriever = {
|
|
1288
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
1289
|
+
if (context.category === "preferences") {
|
|
1290
|
+
return [
|
|
1291
|
+
withScope(
|
|
1292
|
+
buildResult(
|
|
1293
|
+
"preference-recallnest-smoke-validation",
|
|
1294
|
+
"preferences",
|
|
1295
|
+
"在 RecallNest 这条线里,如果需要独立的 Claude Code 验证视角,可以按需直接让 CC 介入做 smoke 或 integration 验收,不必事先再次征求。",
|
|
1296
|
+
),
|
|
1297
|
+
"project:recallnest",
|
|
1298
|
+
),
|
|
1299
|
+
];
|
|
1300
|
+
}
|
|
1301
|
+
if (context.category === "entities") {
|
|
1302
|
+
return [
|
|
1303
|
+
withScope(
|
|
1304
|
+
buildResult(
|
|
1305
|
+
"entity-recallnest-shared-memory",
|
|
1306
|
+
"entities",
|
|
1307
|
+
"RecallNest exposes continuity through both HTTP API and MCP, so different agents can share one memory layer and the same handoff model.",
|
|
1308
|
+
),
|
|
1309
|
+
"project:recallnest",
|
|
1310
|
+
),
|
|
1311
|
+
];
|
|
1312
|
+
}
|
|
1313
|
+
return [];
|
|
1314
|
+
},
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
const response = await composeResumeContext({
|
|
1318
|
+
retriever,
|
|
1319
|
+
checkpointStore: {
|
|
1320
|
+
async getLatest() {
|
|
1321
|
+
return null;
|
|
1322
|
+
},
|
|
1323
|
+
},
|
|
1324
|
+
listPins: () => [],
|
|
1325
|
+
}, {
|
|
1326
|
+
task: "把共享记忆这条线接着做",
|
|
1327
|
+
scope: "project:recallnest",
|
|
1328
|
+
includeLatestCheckpoint: false,
|
|
1329
|
+
limitPerSection: 3,
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
const stableJoined = response.stableContext.join(" ");
|
|
1333
|
+
expect(stableJoined).toContain("share one memory layer");
|
|
1334
|
+
expect(stableJoined).not.toContain("Claude Code 验证视角");
|
|
1335
|
+
expect(stableJoined).not.toContain("smoke");
|
|
1336
|
+
expect(stableJoined).not.toContain("integration 验收");
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
it("keeps project-scoped smoke-validation preferences when the task explicitly asks for them", async () => {
|
|
1340
|
+
const retriever = {
|
|
1341
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
1342
|
+
if (context.category === "preferences") {
|
|
1343
|
+
return [
|
|
1344
|
+
withScope(
|
|
1345
|
+
buildResult(
|
|
1346
|
+
"preference-recallnest-smoke-validation-explicit",
|
|
1347
|
+
"preferences",
|
|
1348
|
+
"在 RecallNest 这条线里,如果需要独立的 Claude Code 验证视角,可以按需直接让 CC 介入做 smoke 或 integration 验收,不必事先再次征求。",
|
|
1349
|
+
),
|
|
1350
|
+
"project:recallnest",
|
|
1351
|
+
),
|
|
1352
|
+
];
|
|
1353
|
+
}
|
|
1354
|
+
if (context.category === "entities") {
|
|
1355
|
+
return [
|
|
1356
|
+
withScope(
|
|
1357
|
+
buildResult(
|
|
1358
|
+
"entity-recallnest-shared-memory-explicit",
|
|
1359
|
+
"entities",
|
|
1360
|
+
"RecallNest exposes continuity through both HTTP API and MCP, so different agents can share one memory layer and the same handoff model.",
|
|
1361
|
+
),
|
|
1362
|
+
"project:recallnest",
|
|
1363
|
+
),
|
|
1364
|
+
];
|
|
1365
|
+
}
|
|
1366
|
+
return [];
|
|
1367
|
+
},
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
const response = await composeResumeContext({
|
|
1371
|
+
retriever,
|
|
1372
|
+
checkpointStore: {
|
|
1373
|
+
async getLatest() {
|
|
1374
|
+
return null;
|
|
1375
|
+
},
|
|
1376
|
+
},
|
|
1377
|
+
listPins: () => [],
|
|
1378
|
+
}, {
|
|
1379
|
+
task: "继续做 RecallNest 的 Claude Code smoke integration 验收",
|
|
1380
|
+
scope: "project:recallnest",
|
|
1381
|
+
includeLatestCheckpoint: false,
|
|
1382
|
+
limitPerSection: 3,
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
const stableJoined = response.stableContext.join(" ");
|
|
1386
|
+
expect(stableJoined).toContain("Claude Code 验证视角");
|
|
1387
|
+
expect(stableJoined).toContain("smoke");
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
it("uses a scope-aware entity fallback query for sparse project prompts", async () => {
|
|
1391
|
+
const calls: RetrievalContext[] = [];
|
|
1392
|
+
const retriever = {
|
|
1393
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
1394
|
+
calls.push(context);
|
|
1395
|
+
if (
|
|
1396
|
+
context.category === "entities" &&
|
|
1397
|
+
context.query.includes("recallnest") &&
|
|
1398
|
+
context.query.includes("checkpoint_session")
|
|
1399
|
+
) {
|
|
1400
|
+
return [
|
|
1401
|
+
withMetadata(
|
|
1402
|
+
withScope(
|
|
1403
|
+
buildResult(
|
|
1404
|
+
"entity-recallnest",
|
|
1405
|
+
"entities",
|
|
1406
|
+
"RecallNest is the shared memory layer for Claude Code, Codex, and Gemini CLI.",
|
|
1407
|
+
),
|
|
1408
|
+
"memory:project:recallnest",
|
|
1409
|
+
),
|
|
1410
|
+
{
|
|
1411
|
+
boundary: {
|
|
1412
|
+
layer: "durable",
|
|
1413
|
+
authority: "structured-memory",
|
|
1414
|
+
conflictPolicy: "latest-wins",
|
|
1415
|
+
},
|
|
1416
|
+
canonicalKey: "entities:recallnest:shared-memory-layer",
|
|
1417
|
+
},
|
|
1418
|
+
),
|
|
1419
|
+
];
|
|
1420
|
+
}
|
|
1421
|
+
return [];
|
|
1422
|
+
},
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
const response = await composeResumeContext({
|
|
1426
|
+
retriever,
|
|
1427
|
+
checkpointStore: {
|
|
1428
|
+
async getLatest() {
|
|
1429
|
+
return null;
|
|
1430
|
+
},
|
|
1431
|
+
},
|
|
1432
|
+
listPins: () => [],
|
|
1433
|
+
}, {
|
|
1434
|
+
task: "继续 RecallNest MCP transport 适配",
|
|
1435
|
+
scope: "project:recallnest",
|
|
1436
|
+
includeLatestCheckpoint: false,
|
|
1437
|
+
limitPerSection: 3,
|
|
1438
|
+
});
|
|
1439
|
+
|
|
1440
|
+
expect(calls.some((call) =>
|
|
1441
|
+
call.category === "entities" &&
|
|
1442
|
+
call.query.includes("recallnest") &&
|
|
1443
|
+
call.query.includes("checkpoint_session")
|
|
1444
|
+
)).toBe(true);
|
|
1445
|
+
expect(response.stableContext).toContain(
|
|
1446
|
+
"Entity: RecallNest is the shared memory layer for Claude Code, Codex, and Gemini CLI.",
|
|
1447
|
+
);
|
|
1448
|
+
expect(response.stableContext.some((item) => item.startsWith("Task focus:"))).toBe(false);
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
it("filters unrelated global entity results from scoped stable recall", async () => {
|
|
1452
|
+
const retriever = {
|
|
1453
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
1454
|
+
if (context.category !== "entities") {
|
|
1455
|
+
return [];
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
const scopedResult = withScope(
|
|
1459
|
+
buildResult(
|
|
1460
|
+
"entity-recallnest-scoped",
|
|
1461
|
+
"entities",
|
|
1462
|
+
"RecallNest is the shared memory layer for Claude Code, Codex, and Gemini CLI.",
|
|
1463
|
+
),
|
|
1464
|
+
"project:recallnest",
|
|
1465
|
+
);
|
|
1466
|
+
|
|
1467
|
+
if (context.scopeFilter?.includes("project:recallnest")) {
|
|
1468
|
+
return [scopedResult];
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
return [
|
|
1472
|
+
scopedResult,
|
|
1473
|
+
withScope(
|
|
1474
|
+
buildResult(
|
|
1475
|
+
"entity-other-project",
|
|
1476
|
+
"entities",
|
|
1477
|
+
"[project_cmp_status] claude-memory-pro is the current active maintenance target for a different repository.",
|
|
1478
|
+
),
|
|
1479
|
+
"project:other",
|
|
1480
|
+
),
|
|
1481
|
+
];
|
|
1482
|
+
},
|
|
1483
|
+
};
|
|
1484
|
+
|
|
1485
|
+
const response = await composeResumeContext({
|
|
1486
|
+
retriever,
|
|
1487
|
+
checkpointStore: {
|
|
1488
|
+
async getLatest() {
|
|
1489
|
+
return null;
|
|
1490
|
+
},
|
|
1491
|
+
},
|
|
1492
|
+
listPins: () => [],
|
|
1493
|
+
}, {
|
|
1494
|
+
task: "继续我的项目",
|
|
1495
|
+
scope: "project:recallnest",
|
|
1496
|
+
includeLatestCheckpoint: false,
|
|
1497
|
+
limitPerSection: 3,
|
|
1498
|
+
});
|
|
1499
|
+
|
|
1500
|
+
const stableJoined = response.stableContext.join(" ");
|
|
1501
|
+
expect(stableJoined).toContain("RecallNest");
|
|
1502
|
+
expect(stableJoined).not.toContain("claude-memory-pro");
|
|
1503
|
+
});
|
|
1504
|
+
|
|
1505
|
+
it("filters foreign project entities from scoped stable recall when overlap is only shared tool nouns", async () => {
|
|
1506
|
+
const retriever = {
|
|
1507
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
1508
|
+
if (context.category !== "entities") {
|
|
1509
|
+
return [];
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
return [
|
|
1513
|
+
withScope(
|
|
1514
|
+
buildResult(
|
|
1515
|
+
"entity-recallnest-mcp",
|
|
1516
|
+
"entities",
|
|
1517
|
+
"RecallNest MCP transport and memory routing stay shared across Claude Code, Codex, and Gemini CLI.",
|
|
1518
|
+
),
|
|
1519
|
+
"project:recallnest",
|
|
1520
|
+
),
|
|
1521
|
+
withScope(
|
|
1522
|
+
buildResult(
|
|
1523
|
+
"entity-foreign-mcp",
|
|
1524
|
+
"entities",
|
|
1525
|
+
"Telegram bridge MCP transport sync handles message relay and adapter wiring.",
|
|
1526
|
+
),
|
|
1527
|
+
"project:telegram-bridge",
|
|
1528
|
+
),
|
|
1529
|
+
];
|
|
1530
|
+
},
|
|
1531
|
+
};
|
|
1532
|
+
|
|
1533
|
+
const response = await composeResumeContext({
|
|
1534
|
+
retriever,
|
|
1535
|
+
checkpointStore: {
|
|
1536
|
+
async getLatest() {
|
|
1537
|
+
return null;
|
|
1538
|
+
},
|
|
1539
|
+
},
|
|
1540
|
+
listPins: () => [],
|
|
1541
|
+
}, {
|
|
1542
|
+
task: "继续 RecallNest MCP transport 适配",
|
|
1543
|
+
scope: "project:recallnest",
|
|
1544
|
+
includeLatestCheckpoint: false,
|
|
1545
|
+
limitPerSection: 3,
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
const stableJoined = response.stableContext.join(" ");
|
|
1549
|
+
expect(stableJoined).toContain("RecallNest MCP transport");
|
|
1550
|
+
expect(stableJoined).not.toContain("Telegram bridge MCP transport");
|
|
1551
|
+
});
|
|
1552
|
+
|
|
1553
|
+
it("filters foreign project patterns and cases from scoped continuity recall when overlap is only shared tool nouns", async () => {
|
|
1554
|
+
const retriever = {
|
|
1555
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
1556
|
+
if (context.category === "entities") {
|
|
1557
|
+
return [
|
|
1558
|
+
withScope(
|
|
1559
|
+
buildResult(
|
|
1560
|
+
"entity-recallnest-continuity",
|
|
1561
|
+
"entities",
|
|
1562
|
+
"RecallNest continuity revolves around scoped memory and MCP tooling.",
|
|
1563
|
+
),
|
|
1564
|
+
"project:recallnest",
|
|
1565
|
+
),
|
|
1566
|
+
];
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
if (context.category === "patterns") {
|
|
1570
|
+
return [
|
|
1571
|
+
withScope(
|
|
1572
|
+
buildResult(
|
|
1573
|
+
"pattern-recallnest-transport",
|
|
1574
|
+
"patterns",
|
|
1575
|
+
"Workflow pattern: RecallNest MCP transport rollout Tools: resume_context, search_memory Use when: When RecallNest transport wiring changes Steps: 1. Check scoped memory continuity before transport changes.",
|
|
1576
|
+
),
|
|
1577
|
+
"project:recallnest",
|
|
1578
|
+
),
|
|
1579
|
+
withScope(
|
|
1580
|
+
buildResult(
|
|
1581
|
+
"pattern-foreign-transport",
|
|
1582
|
+
"patterns",
|
|
1583
|
+
"Workflow pattern: Telegram bridge MCP transport rollout Tools: resume_context, search_memory Use when: When bridge transport wiring changes Steps: 1. Check bridge relay continuity before transport changes.",
|
|
1584
|
+
),
|
|
1585
|
+
"project:telegram-bridge",
|
|
1586
|
+
),
|
|
1587
|
+
];
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
if (context.category === "cases") {
|
|
1591
|
+
return [
|
|
1592
|
+
withScope(
|
|
1593
|
+
buildResult(
|
|
1594
|
+
"case-recallnest-transport",
|
|
1595
|
+
"cases",
|
|
1596
|
+
"Case: RecallNest MCP transport regression Problem: RecallNest transport recall drifted under scoped MCP changes. Solution: tighten scoped recall before transport rollout.",
|
|
1597
|
+
),
|
|
1598
|
+
"project:recallnest",
|
|
1599
|
+
),
|
|
1600
|
+
withScope(
|
|
1601
|
+
buildResult(
|
|
1602
|
+
"case-foreign-transport",
|
|
1603
|
+
"cases",
|
|
1604
|
+
"Case: Telegram bridge MCP transport regression Problem: bridge relay drifted during MCP transport rollout. Solution: inspect bridge transport sync.",
|
|
1605
|
+
),
|
|
1606
|
+
"project:telegram-bridge",
|
|
1607
|
+
),
|
|
1608
|
+
];
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
return [];
|
|
1612
|
+
},
|
|
1613
|
+
};
|
|
1614
|
+
|
|
1615
|
+
const response = await composeResumeContext({
|
|
1616
|
+
retriever,
|
|
1617
|
+
checkpointStore: {
|
|
1618
|
+
async getLatest() {
|
|
1619
|
+
return null;
|
|
1620
|
+
},
|
|
1621
|
+
},
|
|
1622
|
+
listPins: () => [],
|
|
1623
|
+
}, {
|
|
1624
|
+
task: "继续 RecallNest continuity MCP transport rollout",
|
|
1625
|
+
scope: "project:recallnest",
|
|
1626
|
+
includeLatestCheckpoint: false,
|
|
1627
|
+
limitPerSection: 3,
|
|
1628
|
+
});
|
|
1629
|
+
|
|
1630
|
+
const patternJoined = response.relevantPatterns.join(" ");
|
|
1631
|
+
const caseJoined = response.recentCases.join(" ");
|
|
1632
|
+
expect(patternJoined).toContain("RecallNest MCP transport rollout");
|
|
1633
|
+
expect(patternJoined).not.toContain("Telegram bridge MCP transport rollout");
|
|
1634
|
+
expect(caseJoined).toContain("RecallNest MCP transport regression");
|
|
1635
|
+
expect(caseJoined).not.toContain("Telegram bridge MCP transport regression");
|
|
1636
|
+
});
|
|
1637
|
+
|
|
1638
|
+
it("filters foreign project workflow fallback patterns under scoped continuity recall", async () => {
|
|
1639
|
+
const retriever = {
|
|
1640
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
1641
|
+
if (context.category === "entities") {
|
|
1642
|
+
return [
|
|
1643
|
+
withScope(
|
|
1644
|
+
buildResult(
|
|
1645
|
+
"entity-recallnest-fallback",
|
|
1646
|
+
"entities",
|
|
1647
|
+
"RecallNest continuity revolves around scoped memory and MCP tooling.",
|
|
1648
|
+
),
|
|
1649
|
+
"project:recallnest",
|
|
1650
|
+
),
|
|
1651
|
+
];
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
if (context.category === "patterns") {
|
|
1655
|
+
return [];
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
if (!context.category) {
|
|
1659
|
+
return [
|
|
1660
|
+
withScope(
|
|
1661
|
+
buildResult(
|
|
1662
|
+
"pattern-fallback-recallnest",
|
|
1663
|
+
"fact",
|
|
1664
|
+
"Workflow pattern: RecallNest MCP transport rollout Tools: resume_context, search_memory Use when: When RecallNest transport wiring changes Steps: 1. Run search_memory before transport rollout.",
|
|
1665
|
+
),
|
|
1666
|
+
"memory:project:recallnest",
|
|
1667
|
+
),
|
|
1668
|
+
withScope(
|
|
1669
|
+
buildResult(
|
|
1670
|
+
"pattern-fallback-foreign",
|
|
1671
|
+
"fact",
|
|
1672
|
+
"Workflow pattern: Telegram bridge MCP transport rollout Tools: resume_context, search_memory Use when: When bridge transport wiring changes Steps: 1. Run search_memory before bridge relay rollout.",
|
|
1673
|
+
),
|
|
1674
|
+
"memory:project:telegram-bridge",
|
|
1675
|
+
),
|
|
1676
|
+
];
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
return [];
|
|
1680
|
+
},
|
|
1681
|
+
};
|
|
1682
|
+
|
|
1683
|
+
const response = await composeResumeContext({
|
|
1684
|
+
retriever,
|
|
1685
|
+
checkpointStore: {
|
|
1686
|
+
async getLatest() {
|
|
1687
|
+
return null;
|
|
1688
|
+
},
|
|
1689
|
+
},
|
|
1690
|
+
listPins: () => [],
|
|
1691
|
+
}, {
|
|
1692
|
+
task: "继续 RecallNest continuity MCP transport rollout",
|
|
1693
|
+
scope: "project:recallnest",
|
|
1694
|
+
includeLatestCheckpoint: false,
|
|
1695
|
+
limitPerSection: 3,
|
|
1696
|
+
});
|
|
1697
|
+
|
|
1698
|
+
const patternJoined = response.relevantPatterns.join(" ");
|
|
1699
|
+
expect(patternJoined).toContain("RecallNest MCP transport rollout");
|
|
1700
|
+
expect(patternJoined).not.toContain("Telegram bridge MCP transport rollout");
|
|
1701
|
+
});
|
|
1702
|
+
|
|
1703
|
+
it("filters foreign project cases from scoped continuity recall even when the foreign case mentions RecallNest", async () => {
|
|
1704
|
+
const retriever = {
|
|
1705
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
1706
|
+
if (context.category === "entities") {
|
|
1707
|
+
return [
|
|
1708
|
+
withScope(
|
|
1709
|
+
buildResult(
|
|
1710
|
+
"entity-recallnest-cases",
|
|
1711
|
+
"entities",
|
|
1712
|
+
"RecallNest continuity revolves around scoped memory and MCP tooling.",
|
|
1713
|
+
),
|
|
1714
|
+
"project:recallnest",
|
|
1715
|
+
),
|
|
1716
|
+
];
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
if (context.category === "cases") {
|
|
1720
|
+
return [
|
|
1721
|
+
withScope(
|
|
1722
|
+
buildResult(
|
|
1723
|
+
"case-recallnest-transport-scoped",
|
|
1724
|
+
"cases",
|
|
1725
|
+
"Case: RecallNest MCP transport regression Problem: RecallNest transport recall drifted under scoped MCP changes. Solution: tighten scoped recall before transport rollout.",
|
|
1726
|
+
),
|
|
1727
|
+
"project:recallnest",
|
|
1728
|
+
),
|
|
1729
|
+
withScope(
|
|
1730
|
+
buildResult(
|
|
1731
|
+
"case-foreign-transport-mentions-recallnest",
|
|
1732
|
+
"cases",
|
|
1733
|
+
"Case: Telegram bridge MCP transport regression Problem: bridge transport fixes should stay recoverable inside the bridge project without leaking into RecallNest continuity.",
|
|
1734
|
+
),
|
|
1735
|
+
"project:telegram-bridge",
|
|
1736
|
+
),
|
|
1737
|
+
];
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
return [];
|
|
1741
|
+
},
|
|
1742
|
+
};
|
|
1743
|
+
|
|
1744
|
+
const response = await composeResumeContext({
|
|
1745
|
+
retriever,
|
|
1746
|
+
checkpointStore: {
|
|
1747
|
+
async getLatest() {
|
|
1748
|
+
return null;
|
|
1749
|
+
},
|
|
1750
|
+
},
|
|
1751
|
+
listPins: () => [],
|
|
1752
|
+
}, {
|
|
1753
|
+
task: "继续 RecallNest MCP transport 适配",
|
|
1754
|
+
scope: "project:recallnest",
|
|
1755
|
+
includeLatestCheckpoint: false,
|
|
1756
|
+
limitPerSection: 3,
|
|
1757
|
+
});
|
|
1758
|
+
|
|
1759
|
+
const caseJoined = response.recentCases.join(" ");
|
|
1760
|
+
expect(caseJoined).toContain("RecallNest MCP transport regression");
|
|
1761
|
+
expect(caseJoined).not.toContain("Telegram bridge MCP transport regression");
|
|
1762
|
+
});
|
|
1763
|
+
|
|
1764
|
+
it("suppresses project-scoped transport task results for writing-focused tasks with unrelated hints", async () => {
|
|
1765
|
+
const retriever = {
|
|
1766
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
1767
|
+
if (context.category === "patterns") {
|
|
1768
|
+
return [
|
|
1769
|
+
withScope(
|
|
1770
|
+
buildResult(
|
|
1771
|
+
"pattern-recallnest-transport-writing",
|
|
1772
|
+
"patterns",
|
|
1773
|
+
"Workflow pattern: RecallNest MCP transport rollout Tools: resume_context, search_memory, eval:continuity Use when: When RecallNest continuity work touches MCP transport wiring under project scope Steps: 1. Call resume_context with project:recallnest before transport changes.",
|
|
1774
|
+
),
|
|
1775
|
+
"project:recallnest",
|
|
1776
|
+
),
|
|
1777
|
+
];
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
if (context.category === "cases") {
|
|
1781
|
+
return [
|
|
1782
|
+
withScope(
|
|
1783
|
+
buildResult(
|
|
1784
|
+
"case-bridge-transport-writing",
|
|
1785
|
+
"cases",
|
|
1786
|
+
"Case: Telegram bridge MCP transport regression Problem: bridge transport fixes should stay recoverable inside the bridge project without leaking into RecallNest continuity.",
|
|
1787
|
+
),
|
|
1788
|
+
"project:telegram-bridge",
|
|
1789
|
+
),
|
|
1790
|
+
];
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
return [];
|
|
1794
|
+
},
|
|
1795
|
+
};
|
|
1796
|
+
|
|
1797
|
+
const response = await composeResumeContext({
|
|
1798
|
+
retriever,
|
|
1799
|
+
checkpointStore: {
|
|
1800
|
+
async getLatest() {
|
|
1801
|
+
return null;
|
|
1802
|
+
},
|
|
1803
|
+
},
|
|
1804
|
+
listPins: () => [],
|
|
1805
|
+
}, {
|
|
1806
|
+
task: "不要让我重复前情,接着写",
|
|
1807
|
+
profile: "writing",
|
|
1808
|
+
includeLatestCheckpoint: false,
|
|
1809
|
+
limitPerSection: 3,
|
|
1810
|
+
});
|
|
1811
|
+
|
|
1812
|
+
// Project-scoped transport patterns should not leak, but the task IS a
|
|
1813
|
+
// continuity task ("不要让我重复前情") so built-in continuity guidance is expected.
|
|
1814
|
+
const patternJoined = response.relevantPatterns.join("\n");
|
|
1815
|
+
expect(patternJoined).not.toContain("RecallNest MCP transport");
|
|
1816
|
+
expect(response.recentCases).toEqual([]);
|
|
1817
|
+
});
|
|
1818
|
+
|
|
1819
|
+
it("suppresses same-project transport and smoke task results for generic scoped prompts", async () => {
|
|
1820
|
+
const retriever = {
|
|
1821
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
1822
|
+
if (context.category === "entities") {
|
|
1823
|
+
return [
|
|
1824
|
+
withScope(
|
|
1825
|
+
buildResult(
|
|
1826
|
+
"entity-recallnest-generic",
|
|
1827
|
+
"entities",
|
|
1828
|
+
"RecallNest continuity revolves around scoped memory and startup recovery.",
|
|
1829
|
+
),
|
|
1830
|
+
"project:recallnest",
|
|
1831
|
+
),
|
|
1832
|
+
];
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
if (context.category === "patterns") {
|
|
1836
|
+
return [
|
|
1837
|
+
withScope(
|
|
1838
|
+
buildResult(
|
|
1839
|
+
"pattern-recallnest-transport-generic",
|
|
1840
|
+
"patterns",
|
|
1841
|
+
"Workflow pattern: RecallNest MCP transport rollout Tools: resume_context, search_memory, eval:continuity Use when: When RecallNest continuity work touches MCP transport wiring under project scope Steps: 1. Call resume_context with project:recallnest before transport changes.",
|
|
1842
|
+
),
|
|
1843
|
+
"project:recallnest",
|
|
1844
|
+
),
|
|
1845
|
+
withScope(
|
|
1846
|
+
buildResult(
|
|
1847
|
+
"pattern-recallnest-smoke-generic",
|
|
1848
|
+
"patterns",
|
|
1849
|
+
"Workflow pattern: Headless Claude Code continuity smoke Tools: claude, bun run smoke:claude-continuity, resume_context, checkpoint_session Use when: When RecallNest needs a real continuity acceptance check Steps: 1. Run smoke:claude-continuity before shipping continuity changes.",
|
|
1850
|
+
),
|
|
1851
|
+
"project:recallnest",
|
|
1852
|
+
),
|
|
1853
|
+
withScope(
|
|
1854
|
+
buildResult(
|
|
1855
|
+
"pattern-recallnest-handoff-generic",
|
|
1856
|
+
"patterns",
|
|
1857
|
+
"Workflow pattern: Cross-window continuity handoff Tools: resume_context, latest_checkpoint Use when: When opening a fresh terminal window for the same project Steps: 1. Call resume_context before coding.",
|
|
1858
|
+
),
|
|
1859
|
+
"project:recallnest",
|
|
1860
|
+
),
|
|
1861
|
+
];
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
if (context.category === "cases") {
|
|
1865
|
+
return [
|
|
1866
|
+
withScope(
|
|
1867
|
+
buildResult(
|
|
1868
|
+
"case-recallnest-transport-generic",
|
|
1869
|
+
"cases",
|
|
1870
|
+
"Case: RecallNest MCP transport regression Problem: Scoped RecallNest continuity work around MCP transport could still drift unless the transport fixes were easy to recover.",
|
|
1871
|
+
),
|
|
1872
|
+
"project:recallnest",
|
|
1873
|
+
),
|
|
1874
|
+
withScope(
|
|
1875
|
+
buildResult(
|
|
1876
|
+
"case-recallnest-scope-fallback-generic",
|
|
1877
|
+
"cases",
|
|
1878
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: prefer durable cases and patterns.",
|
|
1879
|
+
),
|
|
1880
|
+
"project:recallnest",
|
|
1881
|
+
),
|
|
1882
|
+
];
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
return [];
|
|
1886
|
+
},
|
|
1887
|
+
};
|
|
1888
|
+
|
|
1889
|
+
const response = await composeResumeContext({
|
|
1890
|
+
retriever,
|
|
1891
|
+
checkpointStore: {
|
|
1892
|
+
async getLatest() {
|
|
1893
|
+
return null;
|
|
1894
|
+
},
|
|
1895
|
+
},
|
|
1896
|
+
listPins: () => [],
|
|
1897
|
+
}, {
|
|
1898
|
+
task: "继续这个项目,不要让我重复前情",
|
|
1899
|
+
scope: "project:recallnest",
|
|
1900
|
+
includeLatestCheckpoint: false,
|
|
1901
|
+
limitPerSection: 3,
|
|
1902
|
+
});
|
|
1903
|
+
|
|
1904
|
+
const patternJoined = response.relevantPatterns.join(" ");
|
|
1905
|
+
const caseJoined = response.recentCases.join(" ");
|
|
1906
|
+
expect(patternJoined).toContain("Cross-window continuity handoff");
|
|
1907
|
+
expect(patternJoined).not.toContain("RecallNest MCP transport rollout");
|
|
1908
|
+
expect(patternJoined).not.toContain("Headless Claude Code continuity smoke");
|
|
1909
|
+
expect(caseJoined).toContain("RecallNest scope fallback cleanup");
|
|
1910
|
+
expect(caseJoined).not.toContain("RecallNest MCP transport regression");
|
|
1911
|
+
});
|
|
1912
|
+
|
|
1913
|
+
it("suppresses same-project transport and smoke task results for generic named RecallNest tasks", async () => {
|
|
1914
|
+
const retriever = {
|
|
1915
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
1916
|
+
if (context.category === "entities") {
|
|
1917
|
+
return [
|
|
1918
|
+
withScope(
|
|
1919
|
+
buildResult(
|
|
1920
|
+
"entity-recallnest-named-generic",
|
|
1921
|
+
"entities",
|
|
1922
|
+
"RecallNest is the shared memory continuity layer across Claude Code, Codex, and Gemini CLI.",
|
|
1923
|
+
),
|
|
1924
|
+
"project:recallnest",
|
|
1925
|
+
),
|
|
1926
|
+
];
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
if (context.category === "patterns") {
|
|
1930
|
+
return [
|
|
1931
|
+
withScope(
|
|
1932
|
+
buildResult(
|
|
1933
|
+
"pattern-recallnest-transport-named-generic",
|
|
1934
|
+
"patterns",
|
|
1935
|
+
"Workflow pattern: RecallNest MCP transport rollout Tools: resume_context, search_memory, eval:continuity Use when: When RecallNest continuity work touches MCP transport wiring under project scope Steps: 1. Call resume_context with project:recallnest before transport changes.",
|
|
1936
|
+
),
|
|
1937
|
+
"project:recallnest",
|
|
1938
|
+
),
|
|
1939
|
+
withScope(
|
|
1940
|
+
buildResult(
|
|
1941
|
+
"pattern-recallnest-smoke-named-generic",
|
|
1942
|
+
"patterns",
|
|
1943
|
+
"Workflow pattern: Headless Claude Code continuity smoke Tools: claude, bun run smoke:claude-continuity, resume_context, checkpoint_session Use when: When RecallNest needs a real continuity acceptance check Steps: 1. Run smoke:claude-continuity before shipping continuity changes.",
|
|
1944
|
+
),
|
|
1945
|
+
"project:recallnest",
|
|
1946
|
+
),
|
|
1947
|
+
withScope(
|
|
1948
|
+
buildResult(
|
|
1949
|
+
"pattern-recallnest-handoff-named-generic",
|
|
1950
|
+
"patterns",
|
|
1951
|
+
"Workflow pattern: Cross-window continuity handoff Tools: resume_context, latest_checkpoint Use when: When opening a fresh terminal window for the same project Steps: 1. Call resume_context before coding.",
|
|
1952
|
+
),
|
|
1953
|
+
"project:recallnest",
|
|
1954
|
+
),
|
|
1955
|
+
];
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
if (context.category === "cases") {
|
|
1959
|
+
return [
|
|
1960
|
+
withScope(
|
|
1961
|
+
buildResult(
|
|
1962
|
+
"case-recallnest-transport-named-generic",
|
|
1963
|
+
"cases",
|
|
1964
|
+
"Case: RecallNest MCP transport regression Problem: Scoped RecallNest continuity work around MCP transport could still drift unless the transport fixes were easy to recover.",
|
|
1965
|
+
),
|
|
1966
|
+
"project:recallnest",
|
|
1967
|
+
),
|
|
1968
|
+
withScope(
|
|
1969
|
+
buildResult(
|
|
1970
|
+
"case-recallnest-sparse-startup-named-generic",
|
|
1971
|
+
"cases",
|
|
1972
|
+
"Case: RecallNest sparse startup context cleanup Problem: resume_context returned noisy transcript fragments and unrelated memories instead of a clean project handoff. Solution: filter low-signal transcripts and backfill stable context from checkpoint decisions.",
|
|
1973
|
+
),
|
|
1974
|
+
"project:recallnest",
|
|
1975
|
+
),
|
|
1976
|
+
];
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
return [];
|
|
1980
|
+
},
|
|
1981
|
+
};
|
|
1982
|
+
|
|
1983
|
+
const response = await composeResumeContext({
|
|
1984
|
+
retriever,
|
|
1985
|
+
checkpointStore: {
|
|
1986
|
+
async getLatest() {
|
|
1987
|
+
return null;
|
|
1988
|
+
},
|
|
1989
|
+
},
|
|
1990
|
+
listPins: () => [],
|
|
1991
|
+
}, {
|
|
1992
|
+
task: "继续整理 RecallNest 实施清单,不要让我重复前情",
|
|
1993
|
+
includeLatestCheckpoint: false,
|
|
1994
|
+
limitPerSection: 3,
|
|
1995
|
+
});
|
|
1996
|
+
|
|
1997
|
+
const stableJoined = response.stableContext.join(" ");
|
|
1998
|
+
const patternJoined = response.relevantPatterns.join(" ");
|
|
1999
|
+
const caseJoined = response.recentCases.join(" ");
|
|
2000
|
+
expect(stableJoined).toContain("RecallNest");
|
|
2001
|
+
expect(patternJoined).toContain("Cross-window continuity handoff");
|
|
2002
|
+
expect(patternJoined).not.toContain("RecallNest MCP transport rollout");
|
|
2003
|
+
expect(patternJoined).not.toContain("Headless Claude Code continuity smoke");
|
|
2004
|
+
expect(caseJoined).toContain("RecallNest sparse startup context cleanup");
|
|
2005
|
+
expect(caseJoined).not.toContain("RecallNest MCP transport regression");
|
|
2006
|
+
});
|
|
2007
|
+
|
|
2008
|
+
it("suppresses same-project doctor and seed task results for generic named RecallNest tasks", async () => {
|
|
2009
|
+
const retriever = {
|
|
2010
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
2011
|
+
if (context.category === "entities") {
|
|
2012
|
+
return [
|
|
2013
|
+
withScope(
|
|
2014
|
+
buildResult(
|
|
2015
|
+
"entity-recallnest-doctor-generic",
|
|
2016
|
+
"entities",
|
|
2017
|
+
"RecallNest is the shared memory continuity layer across Claude Code, Codex, and Gemini CLI.",
|
|
2018
|
+
),
|
|
2019
|
+
"project:recallnest",
|
|
2020
|
+
),
|
|
2021
|
+
];
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
if (context.category === "patterns") {
|
|
2025
|
+
return [
|
|
2026
|
+
withScope(
|
|
2027
|
+
buildResult(
|
|
2028
|
+
"pattern-recallnest-doctor-generic",
|
|
2029
|
+
"patterns",
|
|
2030
|
+
"Workflow pattern: RecallNest doctor baseline check Tools: doctor, seed:continuity, eval:continuity Use when: When refreshing RecallNest continuity baseline coverage before release Steps: 1. Run doctor --ci before reseeding continuity material.",
|
|
2031
|
+
),
|
|
2032
|
+
"project:recallnest",
|
|
2033
|
+
),
|
|
2034
|
+
withScope(
|
|
2035
|
+
buildResult(
|
|
2036
|
+
"pattern-recallnest-seed-generic",
|
|
2037
|
+
"patterns",
|
|
2038
|
+
"Workflow pattern: RecallNest seed continuity refresh Tools: seed:continuity, seed:patterns, seed:cases Use when: When continuity seeds or baseline fixtures drift from live durable memory Steps: 1. Reseed continuity material before rerunning eval.",
|
|
2039
|
+
),
|
|
2040
|
+
"project:recallnest",
|
|
2041
|
+
),
|
|
2042
|
+
withScope(
|
|
2043
|
+
buildResult(
|
|
2044
|
+
"pattern-recallnest-handoff-doctor-generic",
|
|
2045
|
+
"patterns",
|
|
2046
|
+
"Workflow pattern: Cross-window continuity handoff Tools: resume_context, latest_checkpoint Use when: When opening a fresh terminal window for the same project Steps: 1. Call resume_context before coding.",
|
|
2047
|
+
),
|
|
2048
|
+
"project:recallnest",
|
|
2049
|
+
),
|
|
2050
|
+
];
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
if (context.category === "cases") {
|
|
2054
|
+
return [
|
|
2055
|
+
withScope(
|
|
2056
|
+
buildResult(
|
|
2057
|
+
"case-recallnest-doctor-generic",
|
|
2058
|
+
"cases",
|
|
2059
|
+
"Case: RecallNest doctor baseline hardening Problem: doctor baseline coverage drifted after continuity helper splits. Solution: tighten doctor checks and reseed continuity material before release.",
|
|
2060
|
+
),
|
|
2061
|
+
"project:recallnest",
|
|
2062
|
+
),
|
|
2063
|
+
withScope(
|
|
2064
|
+
buildResult(
|
|
2065
|
+
"case-recallnest-seed-generic",
|
|
2066
|
+
"cases",
|
|
2067
|
+
"Case: RecallNest seed continuity refresh Problem: continuity seeds fell behind the live durable project material after baseline changes. Solution: rerun seed:continuity before eval.",
|
|
2068
|
+
),
|
|
2069
|
+
"project:recallnest",
|
|
2070
|
+
),
|
|
2071
|
+
withScope(
|
|
2072
|
+
buildResult(
|
|
2073
|
+
"case-recallnest-scope-fallback-doctor-generic",
|
|
2074
|
+
"cases",
|
|
2075
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: prefer durable cases and patterns.",
|
|
2076
|
+
),
|
|
2077
|
+
"project:recallnest",
|
|
2078
|
+
),
|
|
2079
|
+
];
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
return [];
|
|
2083
|
+
},
|
|
2084
|
+
};
|
|
2085
|
+
|
|
2086
|
+
const response = await composeResumeContext({
|
|
2087
|
+
retriever,
|
|
2088
|
+
checkpointStore: {
|
|
2089
|
+
async getLatest() {
|
|
2090
|
+
return null;
|
|
2091
|
+
},
|
|
2092
|
+
},
|
|
2093
|
+
listPins: () => [],
|
|
2094
|
+
}, {
|
|
2095
|
+
task: "继续整理 RecallNest 实施清单,不要让我重复前情",
|
|
2096
|
+
includeLatestCheckpoint: false,
|
|
2097
|
+
limitPerSection: 3,
|
|
2098
|
+
});
|
|
2099
|
+
|
|
2100
|
+
const patternJoined = response.relevantPatterns.join(" ");
|
|
2101
|
+
const caseJoined = response.recentCases.join(" ");
|
|
2102
|
+
expect(patternJoined).toContain("Cross-window continuity handoff");
|
|
2103
|
+
expect(patternJoined).not.toContain("RecallNest doctor baseline check");
|
|
2104
|
+
expect(patternJoined).not.toContain("RecallNest seed continuity refresh");
|
|
2105
|
+
expect(caseJoined).toContain("RecallNest scope fallback cleanup");
|
|
2106
|
+
expect(caseJoined).not.toContain("RecallNest doctor baseline hardening");
|
|
2107
|
+
expect(caseJoined).not.toContain("RecallNest seed continuity refresh");
|
|
2108
|
+
});
|
|
2109
|
+
|
|
2110
|
+
it("suppresses same-project eval task results for generic checkpoint-led RecallNest continue tasks", async () => {
|
|
2111
|
+
const retriever = {
|
|
2112
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
2113
|
+
if (context.category === "entities") {
|
|
2114
|
+
return [
|
|
2115
|
+
withScope(
|
|
2116
|
+
buildResult(
|
|
2117
|
+
"entity-recallnest-checkpoint-generic",
|
|
2118
|
+
"entities",
|
|
2119
|
+
"RecallNest continuity revolves around three primitives: store_memory, checkpoint_session, and resume_context.",
|
|
2120
|
+
),
|
|
2121
|
+
"recallnest",
|
|
2122
|
+
),
|
|
2123
|
+
];
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
if (context.category === "patterns") {
|
|
2127
|
+
return [
|
|
2128
|
+
withScope(
|
|
2129
|
+
buildResult(
|
|
2130
|
+
"pattern-recallnest-eval-generic",
|
|
2131
|
+
"patterns",
|
|
2132
|
+
"Workflow pattern: Continuity eval regression sweep Tools: eval:continuity, checkpoint_session Use when: When refreshing RecallNest eval fixtures before shipping continuity changes Steps: 1. Re-run eval:continuity with checkpoint fixtures before checking the report.",
|
|
2133
|
+
),
|
|
2134
|
+
"recallnest",
|
|
2135
|
+
),
|
|
2136
|
+
withScope(
|
|
2137
|
+
buildResult(
|
|
2138
|
+
"pattern-recallnest-handoff-eval-generic",
|
|
2139
|
+
"patterns",
|
|
2140
|
+
"Workflow pattern: Cross-window continuity handoff Tools: resume_context, latest_checkpoint Use when: When opening a fresh terminal window for the same project Steps: 1. Call resume_context before coding.",
|
|
2141
|
+
),
|
|
2142
|
+
"recallnest",
|
|
2143
|
+
),
|
|
2144
|
+
];
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
if (context.category === "cases") {
|
|
2148
|
+
return [
|
|
2149
|
+
withScope(
|
|
2150
|
+
buildResult(
|
|
2151
|
+
"case-recallnest-eval-generic",
|
|
2152
|
+
"cases",
|
|
2153
|
+
"Case: Continuity eval checkpoint isolation Problem: continuity eval results drifted because they were reading whichever live latest checkpoint happened to exist locally. Solution: build an in-memory checkpoint store from fixture checkpoints before rerunning eval.",
|
|
2154
|
+
),
|
|
2155
|
+
"recallnest",
|
|
2156
|
+
),
|
|
2157
|
+
withScope(
|
|
2158
|
+
buildResult(
|
|
2159
|
+
"case-recallnest-trigger-generic",
|
|
2160
|
+
"cases",
|
|
2161
|
+
"Case: Three-terminal continuity trigger validation Problem: Claude Code, Codex, and Gemini CLI were configured with MCP but only continue-style prompts triggered recall reliably.",
|
|
2162
|
+
),
|
|
2163
|
+
"recallnest",
|
|
2164
|
+
),
|
|
2165
|
+
withScope(
|
|
2166
|
+
buildResult(
|
|
2167
|
+
"case-recallnest-startup-generic",
|
|
2168
|
+
"cases",
|
|
2169
|
+
"Case: RecallNest sparse startup context cleanup Problem: resume_context returned noisy transcript fragments instead of a clean project handoff. Solution: filter low-signal transcripts and backfill stable context from checkpoint decisions.",
|
|
2170
|
+
),
|
|
2171
|
+
"recallnest",
|
|
2172
|
+
),
|
|
2173
|
+
];
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
return [];
|
|
2177
|
+
},
|
|
2178
|
+
};
|
|
2179
|
+
|
|
2180
|
+
const response = await composeResumeContext({
|
|
2181
|
+
retriever,
|
|
2182
|
+
checkpointStore: {
|
|
2183
|
+
async getLatest() {
|
|
2184
|
+
return {
|
|
2185
|
+
checkpointId: "checkpoint-recallnest-generic-eval",
|
|
2186
|
+
sessionId: "eval-recallnest-checkpoint",
|
|
2187
|
+
resolvedScope: "recallnest",
|
|
2188
|
+
summary: "RecallNest Phase 3/4 并行推进中。核心三件套 store_memory、checkpoint_session、resume_context 已上线,当前继续收 resume_context compose。",
|
|
2189
|
+
task: "RecallNest continuity layer — 状态交接给下个窗口",
|
|
2190
|
+
decisions: ["Phase 3/4 并行推进,continuity layer 是当前核心方向"],
|
|
2191
|
+
openLoops: [],
|
|
2192
|
+
nextActions: ["继续优化 resume_context compose 质量"],
|
|
2193
|
+
entities: ["RecallNest"],
|
|
2194
|
+
files: [],
|
|
2195
|
+
updatedAt: "2026-03-16T03:30:00.000Z",
|
|
2196
|
+
};
|
|
2197
|
+
},
|
|
2198
|
+
},
|
|
2199
|
+
listPins: () => [],
|
|
2200
|
+
}, {
|
|
2201
|
+
task: "继续 recallnest",
|
|
2202
|
+
scope: "recallnest",
|
|
2203
|
+
includeLatestCheckpoint: true,
|
|
2204
|
+
limitPerSection: 3,
|
|
2205
|
+
});
|
|
2206
|
+
|
|
2207
|
+
const patternJoined = response.relevantPatterns.join(" ");
|
|
2208
|
+
const caseJoined = response.recentCases.join(" ");
|
|
2209
|
+
expect(patternJoined).toContain("Cross-window continuity handoff");
|
|
2210
|
+
expect(patternJoined).not.toContain("Continuity eval regression sweep");
|
|
2211
|
+
expect(caseJoined).toContain("RecallNest sparse startup context cleanup");
|
|
2212
|
+
expect(caseJoined).not.toContain("Continuity eval checkpoint isolation");
|
|
2213
|
+
expect(caseJoined).not.toContain("Three-terminal continuity trigger validation");
|
|
2214
|
+
});
|
|
2215
|
+
|
|
2216
|
+
it("suppresses same-project workflow observation task results for generic named RecallNest tasks", async () => {
|
|
2217
|
+
const retriever = {
|
|
2218
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
2219
|
+
if (context.category === "entities") {
|
|
2220
|
+
return [
|
|
2221
|
+
withScope(
|
|
2222
|
+
buildResult(
|
|
2223
|
+
"entity-recallnest-workflow-observation-generic",
|
|
2224
|
+
"entities",
|
|
2225
|
+
"RecallNest is the shared memory continuity layer across Claude Code, Codex, and Gemini CLI.",
|
|
2226
|
+
),
|
|
2227
|
+
"project:recallnest",
|
|
2228
|
+
),
|
|
2229
|
+
];
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
if (context.category === "patterns") {
|
|
2233
|
+
return [
|
|
2234
|
+
withMetadata(
|
|
2235
|
+
withScope(
|
|
2236
|
+
buildResult(
|
|
2237
|
+
"pattern-recallnest-workflow-observation-generic",
|
|
2238
|
+
"patterns",
|
|
2239
|
+
"continuity、是否被用户纠正、是否重复开 conflict、checkpoint 是否缺失、resume_context 是否被跳过。 如果只选一个最该立刻借的方向,我会选这个:1. 新增 workflow_observe 这一层,append-only。2. 观测对象先只做 workflow_health / workflow_evidence。",
|
|
2240
|
+
),
|
|
2241
|
+
"codex:019cfa20",
|
|
2242
|
+
),
|
|
2243
|
+
{
|
|
2244
|
+
boundary: {
|
|
2245
|
+
layer: "evidence",
|
|
2246
|
+
authority: "transcript-ingest",
|
|
2247
|
+
conflictPolicy: "latest-wins",
|
|
2248
|
+
originalCategory: "patterns",
|
|
2249
|
+
},
|
|
2250
|
+
},
|
|
2251
|
+
),
|
|
2252
|
+
];
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
if (context.category === "cases") {
|
|
2256
|
+
return [
|
|
2257
|
+
withScope(
|
|
2258
|
+
buildResult(
|
|
2259
|
+
"case-recallnest-workflow-observation-generic",
|
|
2260
|
+
"cases",
|
|
2261
|
+
"Case: RecallNest workflow observation rollout Problem: workflow_observe and workflow_health were leaking into generic continuity previews. Solution: keep workflow observation surfaces behind explicit task cues.",
|
|
2262
|
+
),
|
|
2263
|
+
"project:recallnest",
|
|
2264
|
+
),
|
|
2265
|
+
withScope(
|
|
2266
|
+
buildResult(
|
|
2267
|
+
"case-recallnest-scope-fallback-workflow-observation-generic",
|
|
2268
|
+
"cases",
|
|
2269
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: prefer durable cases and patterns.",
|
|
2270
|
+
),
|
|
2271
|
+
"project:recallnest",
|
|
2272
|
+
),
|
|
2273
|
+
];
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
return [];
|
|
2277
|
+
},
|
|
2278
|
+
};
|
|
2279
|
+
|
|
2280
|
+
const response = await composeResumeContext({
|
|
2281
|
+
retriever,
|
|
2282
|
+
checkpointStore: {
|
|
2283
|
+
async getLatest() {
|
|
2284
|
+
return null;
|
|
2285
|
+
},
|
|
2286
|
+
},
|
|
2287
|
+
listPins: () => [],
|
|
2288
|
+
}, {
|
|
2289
|
+
task: "继续整理 RecallNest 实施清单,不要让我重复前情",
|
|
2290
|
+
includeLatestCheckpoint: false,
|
|
2291
|
+
limitPerSection: 3,
|
|
2292
|
+
});
|
|
2293
|
+
|
|
2294
|
+
const patternJoined = response.relevantPatterns.join(" ");
|
|
2295
|
+
const caseJoined = response.recentCases.join(" ");
|
|
2296
|
+
expect(patternJoined).toContain("resume_context");
|
|
2297
|
+
expect(patternJoined).toContain("checkpoint_session");
|
|
2298
|
+
expect(patternJoined).not.toContain("workflow_observe");
|
|
2299
|
+
expect(patternJoined).not.toContain("workflow_health");
|
|
2300
|
+
expect(caseJoined).toContain("RecallNest scope fallback cleanup");
|
|
2301
|
+
expect(caseJoined).not.toContain("workflow observation rollout");
|
|
2302
|
+
});
|
|
2303
|
+
|
|
2304
|
+
it("suppresses same-project trigger validation task results for generic named RecallNest tasks", async () => {
|
|
2305
|
+
const retriever = {
|
|
2306
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
2307
|
+
if (context.category === "entities") {
|
|
2308
|
+
return [
|
|
2309
|
+
withScope(
|
|
2310
|
+
buildResult(
|
|
2311
|
+
"entity-recallnest-trigger-generic",
|
|
2312
|
+
"entities",
|
|
2313
|
+
"RecallNest is the shared memory layer for Claude Code, Codex, and Gemini CLI.",
|
|
2314
|
+
),
|
|
2315
|
+
"recallnest",
|
|
2316
|
+
),
|
|
2317
|
+
];
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
if (context.category === "patterns") {
|
|
2321
|
+
return [
|
|
2322
|
+
withScope(
|
|
2323
|
+
buildResult(
|
|
2324
|
+
"pattern-recallnest-handoff-trigger-generic",
|
|
2325
|
+
"patterns",
|
|
2326
|
+
"Workflow pattern: Cross-window continuity handoff Tools: resume_context, latest_checkpoint Use when: When opening a fresh terminal window for the same project Steps: 1. Call resume_context before coding.",
|
|
2327
|
+
),
|
|
2328
|
+
"recallnest",
|
|
2329
|
+
),
|
|
2330
|
+
];
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
if (context.category === "cases") {
|
|
2334
|
+
return [
|
|
2335
|
+
withScope(
|
|
2336
|
+
buildResult(
|
|
2337
|
+
"case-recallnest-trigger-validation-generic",
|
|
2338
|
+
"cases",
|
|
2339
|
+
"Case: Three-terminal continuity trigger validation Problem: Claude Code, Codex, and Gemini CLI were configured with MCP but only continue-style prompts triggered recall reliably.",
|
|
2340
|
+
),
|
|
2341
|
+
"recallnest",
|
|
2342
|
+
),
|
|
2343
|
+
withScope(
|
|
2344
|
+
buildResult(
|
|
2345
|
+
"case-recallnest-scope-fallback-trigger-generic",
|
|
2346
|
+
"cases",
|
|
2347
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: prefer durable cases and patterns.",
|
|
2348
|
+
),
|
|
2349
|
+
"recallnest",
|
|
2350
|
+
),
|
|
2351
|
+
];
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
return [];
|
|
2355
|
+
},
|
|
2356
|
+
};
|
|
2357
|
+
|
|
2358
|
+
const response = await composeResumeContext({
|
|
2359
|
+
retriever,
|
|
2360
|
+
checkpointStore: {
|
|
2361
|
+
async getLatest() {
|
|
2362
|
+
return null;
|
|
2363
|
+
},
|
|
2364
|
+
},
|
|
2365
|
+
listPins: () => [],
|
|
2366
|
+
}, {
|
|
2367
|
+
task: "继续整理 RecallNest 实施清单,不要让我重复前情",
|
|
2368
|
+
includeLatestCheckpoint: false,
|
|
2369
|
+
limitPerSection: 3,
|
|
2370
|
+
});
|
|
2371
|
+
|
|
2372
|
+
const caseJoined = response.recentCases.join(" ");
|
|
2373
|
+
expect(caseJoined).toContain("RecallNest scope fallback cleanup");
|
|
2374
|
+
expect(caseJoined).not.toContain("Three-terminal continuity trigger validation");
|
|
2375
|
+
expect(caseJoined).not.toContain("continue-style prompts triggered recall reliably");
|
|
2376
|
+
});
|
|
2377
|
+
|
|
2378
|
+
it("suppresses same-project pin maintenance task results for generic RecallNest project-context tasks", async () => {
|
|
2379
|
+
const retriever = {
|
|
2380
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
2381
|
+
if (context.category === "entities") {
|
|
2382
|
+
return [
|
|
2383
|
+
withScope(
|
|
2384
|
+
buildResult(
|
|
2385
|
+
"entity-recallnest-project-context-pins",
|
|
2386
|
+
"entities",
|
|
2387
|
+
"RecallNest continuity revolves around three primitives: store_memory, checkpoint_session, and resume_context.",
|
|
2388
|
+
),
|
|
2389
|
+
"project:recallnest",
|
|
2390
|
+
),
|
|
2391
|
+
];
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
if (context.category === "patterns") {
|
|
2395
|
+
return [
|
|
2396
|
+
withScope(
|
|
2397
|
+
buildResult(
|
|
2398
|
+
"pattern-recallnest-project-context-handoff",
|
|
2399
|
+
"patterns",
|
|
2400
|
+
"Workflow pattern: Cross-window continuity handoff Tools: resume_context, latest_checkpoint Use when: When opening a fresh terminal window for the same project Steps: 1. Call resume_context before coding.",
|
|
2401
|
+
),
|
|
2402
|
+
"project:recallnest",
|
|
2403
|
+
),
|
|
2404
|
+
];
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
if (context.category === "cases") {
|
|
2408
|
+
return [
|
|
2409
|
+
withScope(
|
|
2410
|
+
buildResult(
|
|
2411
|
+
"case-recallnest-project-context-startup",
|
|
2412
|
+
"cases",
|
|
2413
|
+
"Case: RecallNest sparse startup context cleanup Problem: resume_context returned noisy transcript fragments instead of a clean project handoff. Solution: filter low-signal transcripts and backfill stable context from checkpoint decisions.",
|
|
2414
|
+
),
|
|
2415
|
+
"project:recallnest",
|
|
2416
|
+
),
|
|
2417
|
+
withScope(
|
|
2418
|
+
buildResult(
|
|
2419
|
+
"case-recallnest-project-context-conversational-pins",
|
|
2420
|
+
"cases",
|
|
2421
|
+
"Case: Scoped recall leaked conversational durable pins Problem: Scoped resume_context could still surface raw `[助手]/[用户]` bridge transcript pins after they had been pinned into durable memory, polluting `project:recallnest` requests with external `telegram-cli-bridge` content.",
|
|
2422
|
+
),
|
|
2423
|
+
"project:recallnest",
|
|
2424
|
+
),
|
|
2425
|
+
withScope(
|
|
2426
|
+
buildResult(
|
|
2427
|
+
"case-recallnest-project-context-pin-collision",
|
|
2428
|
+
"cases",
|
|
2429
|
+
"Case: Scoped mixed-project pin collision leaked foreign project summaries Problem: When `resume_context` had an explicit `project:*` scope, pinned summaries from another `project:*` scope could still enter stable context if they matched overlapping task nouns such as `bridge` or `README`.",
|
|
2430
|
+
),
|
|
2431
|
+
"project:recallnest",
|
|
2432
|
+
),
|
|
2433
|
+
];
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
return [];
|
|
2437
|
+
},
|
|
2438
|
+
};
|
|
2439
|
+
|
|
2440
|
+
const response = await composeResumeContext({
|
|
2441
|
+
retriever,
|
|
2442
|
+
checkpointStore: {
|
|
2443
|
+
async getLatest() {
|
|
2444
|
+
return null;
|
|
2445
|
+
},
|
|
2446
|
+
},
|
|
2447
|
+
listPins: () => [],
|
|
2448
|
+
}, {
|
|
2449
|
+
task: "一起继续排查 RecallNest 的连续性问题",
|
|
2450
|
+
includeLatestCheckpoint: false,
|
|
2451
|
+
limitPerSection: 3,
|
|
2452
|
+
});
|
|
2453
|
+
|
|
2454
|
+
const caseJoined = response.recentCases.join(" ");
|
|
2455
|
+
expect(caseJoined).toContain("RecallNest sparse startup context cleanup");
|
|
2456
|
+
expect(caseJoined).not.toContain("Scoped recall leaked conversational durable pins");
|
|
2457
|
+
expect(caseJoined).not.toContain("Scoped mixed-project pin collision leaked foreign project summaries");
|
|
2458
|
+
expect(caseJoined).not.toContain("telegram-cli-bridge");
|
|
2459
|
+
});
|
|
2460
|
+
|
|
2461
|
+
it("suppresses same-project scoped collision maintenance cases for generic RecallNest tasks", async () => {
|
|
2462
|
+
const retriever = {
|
|
2463
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
2464
|
+
if (context.category === "entities") {
|
|
2465
|
+
return [
|
|
2466
|
+
withScope(
|
|
2467
|
+
buildResult(
|
|
2468
|
+
"entity-recallnest-generic-scoped-collision",
|
|
2469
|
+
"entities",
|
|
2470
|
+
"RecallNest continuity revolves around three primitives: store_memory, checkpoint_session, and resume_context.",
|
|
2471
|
+
),
|
|
2472
|
+
"project:recallnest",
|
|
2473
|
+
),
|
|
2474
|
+
];
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
if (context.category === "patterns") {
|
|
2478
|
+
return [
|
|
2479
|
+
withScope(
|
|
2480
|
+
buildResult(
|
|
2481
|
+
"pattern-recallnest-generic-scoped-collision",
|
|
2482
|
+
"patterns",
|
|
2483
|
+
"Workflow pattern: Cross-window continuity handoff Tools: resume_context, latest_checkpoint Use when: When opening a fresh terminal window for the same project Steps: 1. Call resume_context before coding.",
|
|
2484
|
+
),
|
|
2485
|
+
"project:recallnest",
|
|
2486
|
+
),
|
|
2487
|
+
];
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
if (context.category === "cases") {
|
|
2491
|
+
return [
|
|
2492
|
+
withScope(
|
|
2493
|
+
buildResult(
|
|
2494
|
+
"case-recallnest-generic-startup-scoped-collision",
|
|
2495
|
+
"cases",
|
|
2496
|
+
"Case: RecallNest sparse startup context cleanup Problem: resume_context returned noisy transcript fragments instead of a clean project handoff. Solution: filter low-signal transcripts and backfill stable context from checkpoint decisions.",
|
|
2497
|
+
),
|
|
2498
|
+
"project:recallnest",
|
|
2499
|
+
),
|
|
2500
|
+
withScope(
|
|
2501
|
+
buildResult(
|
|
2502
|
+
"case-recallnest-generic-entity-collision",
|
|
2503
|
+
"cases",
|
|
2504
|
+
"Case: Scoped entity recall leaked foreign project entities via shared tool nouns Problem: With an explicit `project:*` scope, scoped entity recall could still include foreign-project entities if they matched shared tool nouns such as `MCP` or `transport`.",
|
|
2505
|
+
),
|
|
2506
|
+
"project:recallnest",
|
|
2507
|
+
),
|
|
2508
|
+
withScope(
|
|
2509
|
+
buildResult(
|
|
2510
|
+
"case-recallnest-generic-task-result-collision",
|
|
2511
|
+
"cases",
|
|
2512
|
+
"Case: Scoped task results leaked foreign project patterns and cases Problem: With an explicit `project:*` scope, `relevantPatterns` and `recentCases` could still include foreign-project results when they matched shared tool nouns such as `MCP` or `transport`.",
|
|
2513
|
+
),
|
|
2514
|
+
"project:recallnest",
|
|
2515
|
+
),
|
|
2516
|
+
];
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
return [];
|
|
2520
|
+
},
|
|
2521
|
+
};
|
|
2522
|
+
|
|
2523
|
+
const response = await composeResumeContext({
|
|
2524
|
+
retriever,
|
|
2525
|
+
checkpointStore: {
|
|
2526
|
+
async getLatest() {
|
|
2527
|
+
return null;
|
|
2528
|
+
},
|
|
2529
|
+
},
|
|
2530
|
+
listPins: () => [],
|
|
2531
|
+
}, {
|
|
2532
|
+
task: "继续整理 RecallNest 实施清单,不要让我重复前情",
|
|
2533
|
+
includeLatestCheckpoint: false,
|
|
2534
|
+
limitPerSection: 3,
|
|
2535
|
+
});
|
|
2536
|
+
|
|
2537
|
+
const caseJoined = response.recentCases.join(" ");
|
|
2538
|
+
expect(caseJoined).toContain("RecallNest sparse startup context cleanup");
|
|
2539
|
+
expect(caseJoined).not.toContain("Scoped entity recall leaked foreign project entities via shared tool nouns");
|
|
2540
|
+
expect(caseJoined).not.toContain("Scoped task results leaked foreign project patterns and cases");
|
|
2541
|
+
expect(caseJoined).not.toContain("shared tool nouns");
|
|
2542
|
+
});
|
|
2543
|
+
|
|
2544
|
+
it("suppresses workflow promotion patterns for generic named RecallNest tasks and backfills continuity guidance", async () => {
|
|
2545
|
+
const retriever = {
|
|
2546
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
2547
|
+
if (context.category === "entities") {
|
|
2548
|
+
return [
|
|
2549
|
+
withScope(
|
|
2550
|
+
buildResult(
|
|
2551
|
+
"entity-recallnest-generic-workflow-promotion",
|
|
2552
|
+
"entities",
|
|
2553
|
+
"RecallNest is the shared memory continuity layer across Claude Code, Codex, and Gemini CLI.",
|
|
2554
|
+
),
|
|
2555
|
+
"recallnest",
|
|
2556
|
+
),
|
|
2557
|
+
];
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
if (context.category === "patterns") {
|
|
2561
|
+
return [
|
|
2562
|
+
withMetadata(
|
|
2563
|
+
withScope(
|
|
2564
|
+
buildResult(
|
|
2565
|
+
"pattern-recallnest-generic-workflow-promotion",
|
|
2566
|
+
"patterns",
|
|
2567
|
+
"Workflow pattern: Promote recurring continuity workflow Tools: store_workflow_pattern, /v1/pattern Use when: When the same startup or handoff workflow repeats across multiple windows Steps: 1. Rewrite the workflow as a reusable pattern.",
|
|
2568
|
+
),
|
|
2569
|
+
"recallnest",
|
|
2570
|
+
),
|
|
2571
|
+
{
|
|
2572
|
+
boundary: {
|
|
2573
|
+
layer: "durable",
|
|
2574
|
+
authority: "structured-memory",
|
|
2575
|
+
conflictPolicy: "append-only",
|
|
2576
|
+
originalCategory: "patterns",
|
|
2577
|
+
},
|
|
2578
|
+
canonicalKey: "patterns:promote-recurring-continuity-workflow",
|
|
2579
|
+
},
|
|
2580
|
+
),
|
|
2581
|
+
];
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
if (context.category === "cases") {
|
|
2585
|
+
return [
|
|
2586
|
+
withMetadata(
|
|
2587
|
+
withScope(
|
|
2588
|
+
buildResult(
|
|
2589
|
+
"case-recallnest-generic-workflow-promotion",
|
|
2590
|
+
"cases",
|
|
2591
|
+
"Case: RecallNest sparse startup context cleanup Problem: resume_context returned noisy transcript fragments instead of a clean project handoff. Solution: filter low-signal transcripts and backfill stable context from checkpoint decisions.",
|
|
2592
|
+
),
|
|
2593
|
+
"recallnest",
|
|
2594
|
+
),
|
|
2595
|
+
{
|
|
2596
|
+
boundary: {
|
|
2597
|
+
layer: "durable",
|
|
2598
|
+
authority: "structured-memory",
|
|
2599
|
+
conflictPolicy: "append-only",
|
|
2600
|
+
originalCategory: "cases",
|
|
2601
|
+
},
|
|
2602
|
+
canonicalKey: "cases:recallnest-sparse-startup-context-cleanup",
|
|
2603
|
+
},
|
|
2604
|
+
),
|
|
2605
|
+
];
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
return [];
|
|
2609
|
+
},
|
|
2610
|
+
};
|
|
2611
|
+
|
|
2612
|
+
const response = await composeResumeContext({
|
|
2613
|
+
retriever,
|
|
2614
|
+
checkpointStore: {
|
|
2615
|
+
async getLatest() {
|
|
2616
|
+
return null;
|
|
2617
|
+
},
|
|
2618
|
+
},
|
|
2619
|
+
listPins: () => [],
|
|
2620
|
+
}, {
|
|
2621
|
+
task: "继续整理 RecallNest 实施清单,不要让我重复前情",
|
|
2622
|
+
includeLatestCheckpoint: false,
|
|
2623
|
+
limitPerSection: 3,
|
|
2624
|
+
});
|
|
2625
|
+
|
|
2626
|
+
const patternJoined = response.relevantPatterns.join(" ");
|
|
2627
|
+
expect(patternJoined).not.toContain("Promote recurring continuity workflow");
|
|
2628
|
+
expect(patternJoined).not.toContain("store_workflow_pattern");
|
|
2629
|
+
expect(patternJoined).toContain("resume_context");
|
|
2630
|
+
expect(patternJoined).toContain("search_memory");
|
|
2631
|
+
expect(patternJoined).toContain("checkpoint_session");
|
|
2632
|
+
});
|
|
2633
|
+
|
|
2634
|
+
it("keeps bridge continuity task results for generic named bridge tasks without leaking RecallNest transport results", async () => {
|
|
2635
|
+
const retriever = {
|
|
2636
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
2637
|
+
if (context.category === "entities") {
|
|
2638
|
+
return [
|
|
2639
|
+
withScope(
|
|
2640
|
+
buildResult(
|
|
2641
|
+
"entity-bridge-named-generic",
|
|
2642
|
+
"entities",
|
|
2643
|
+
"Telegram bridge keeps relay continuity and adapter wiring stable across fresh windows.",
|
|
2644
|
+
),
|
|
2645
|
+
"project:telegram-bridge",
|
|
2646
|
+
),
|
|
2647
|
+
withScope(
|
|
2648
|
+
buildResult(
|
|
2649
|
+
"entity-recallnest-foreign-named-generic",
|
|
2650
|
+
"entities",
|
|
2651
|
+
"RecallNest is the shared memory layer for Claude Code, Codex, and Gemini CLI.",
|
|
2652
|
+
),
|
|
2653
|
+
"project:recallnest",
|
|
2654
|
+
),
|
|
2655
|
+
];
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
if (context.category === "patterns") {
|
|
2659
|
+
return [
|
|
2660
|
+
withScope(
|
|
2661
|
+
buildResult(
|
|
2662
|
+
"pattern-bridge-handoff-named-generic",
|
|
2663
|
+
"patterns",
|
|
2664
|
+
"Workflow pattern: Telegram bridge continuity handoff Tools: resume_context, latest_checkpoint Use when: When continuing bridge work from a fresh window Steps: 1. Recover bridge continuity before editing relay code.",
|
|
2665
|
+
),
|
|
2666
|
+
"project:telegram-bridge",
|
|
2667
|
+
),
|
|
2668
|
+
withScope(
|
|
2669
|
+
buildResult(
|
|
2670
|
+
"pattern-recallnest-transport-foreign-named-generic",
|
|
2671
|
+
"patterns",
|
|
2672
|
+
"Workflow pattern: RecallNest MCP transport rollout Tools: resume_context, search_memory, eval:continuity Use when: When RecallNest continuity work touches MCP transport wiring under project scope Steps: 1. Call resume_context with project:recallnest before transport changes.",
|
|
2673
|
+
),
|
|
2674
|
+
"project:recallnest",
|
|
2675
|
+
),
|
|
2676
|
+
];
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
if (context.category === "cases") {
|
|
2680
|
+
return [
|
|
2681
|
+
withScope(
|
|
2682
|
+
buildResult(
|
|
2683
|
+
"case-bridge-cleanup-named-generic",
|
|
2684
|
+
"cases",
|
|
2685
|
+
"Case: Telegram bridge continuity cleanup Problem: bridge handoff notes were too sparse after window switches. Solution: recover bridge context from checkpoint focus and latest relay decisions.",
|
|
2686
|
+
),
|
|
2687
|
+
"project:telegram-bridge",
|
|
2688
|
+
),
|
|
2689
|
+
withScope(
|
|
2690
|
+
buildResult(
|
|
2691
|
+
"case-recallnest-transport-foreign-named-generic",
|
|
2692
|
+
"cases",
|
|
2693
|
+
"Case: RecallNest MCP transport regression Problem: Scoped RecallNest continuity work around MCP transport could still drift unless the transport fixes were easy to recover.",
|
|
2694
|
+
),
|
|
2695
|
+
"project:recallnest",
|
|
2696
|
+
),
|
|
2697
|
+
];
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
return [];
|
|
2701
|
+
},
|
|
2702
|
+
};
|
|
2703
|
+
|
|
2704
|
+
const response = await composeResumeContext({
|
|
2705
|
+
retriever,
|
|
2706
|
+
checkpointStore: {
|
|
2707
|
+
async getLatest() {
|
|
2708
|
+
return null;
|
|
2709
|
+
},
|
|
2710
|
+
},
|
|
2711
|
+
listPins: () => [],
|
|
2712
|
+
}, {
|
|
2713
|
+
task: "继续看 telegram bridge 项目最近进展,不要让我重复前情",
|
|
2714
|
+
includeLatestCheckpoint: false,
|
|
2715
|
+
limitPerSection: 3,
|
|
2716
|
+
});
|
|
2717
|
+
|
|
2718
|
+
const stableJoined = response.stableContext.join(" ");
|
|
2719
|
+
const patternJoined = response.relevantPatterns.join(" ");
|
|
2720
|
+
const caseJoined = response.recentCases.join(" ");
|
|
2721
|
+
expect(stableJoined).toContain("Telegram bridge");
|
|
2722
|
+
expect(stableJoined).not.toContain("RecallNest is the shared memory layer");
|
|
2723
|
+
expect(patternJoined).toContain("Telegram bridge continuity handoff");
|
|
2724
|
+
expect(patternJoined).not.toContain("RecallNest MCP transport rollout");
|
|
2725
|
+
expect(caseJoined).toContain("Telegram bridge continuity cleanup");
|
|
2726
|
+
expect(caseJoined).not.toContain("RecallNest MCP transport regression");
|
|
2727
|
+
});
|
|
2728
|
+
|
|
2729
|
+
it("filters bare recallnest continuity seeds from unscoped external bridge tasks while keeping generic handoff guidance", async () => {
|
|
2730
|
+
const retriever = {
|
|
2731
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
2732
|
+
if (context.category === "patterns") {
|
|
2733
|
+
return [
|
|
2734
|
+
withScope(
|
|
2735
|
+
buildResult(
|
|
2736
|
+
"pattern-seeded-recallnest-handoff",
|
|
2737
|
+
"patterns",
|
|
2738
|
+
"Workflow pattern: Scoped project continuity recall Tools: resume_context, latest_checkpoint, search_memory Use when: When continuing a named project that already has a shared scope or project key Steps: 1. Call resume_context before repo exploration.",
|
|
2739
|
+
),
|
|
2740
|
+
"recallnest",
|
|
2741
|
+
),
|
|
2742
|
+
];
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
if (context.category === "cases") {
|
|
2746
|
+
return [
|
|
2747
|
+
withScope(
|
|
2748
|
+
buildResult(
|
|
2749
|
+
"case-seeded-recallnest-startup",
|
|
2750
|
+
"cases",
|
|
2751
|
+
"Case: RecallNest sparse startup context cleanup Problem: resume_context returned noisy transcript fragments and unrelated memories instead of a clean project handoff.",
|
|
2752
|
+
),
|
|
2753
|
+
"recallnest",
|
|
2754
|
+
),
|
|
2755
|
+
];
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
return [];
|
|
2759
|
+
},
|
|
2760
|
+
};
|
|
2761
|
+
|
|
2762
|
+
const response = await composeResumeContext({
|
|
2763
|
+
retriever,
|
|
2764
|
+
checkpointStore: {
|
|
2765
|
+
async getLatest() {
|
|
2766
|
+
return null;
|
|
2767
|
+
},
|
|
2768
|
+
},
|
|
2769
|
+
listPins: () => [],
|
|
2770
|
+
}, {
|
|
2771
|
+
task: "继续 telegram ai bridge 项目",
|
|
2772
|
+
includeLatestCheckpoint: false,
|
|
2773
|
+
limitPerSection: 3,
|
|
2774
|
+
});
|
|
2775
|
+
|
|
2776
|
+
expect(response.stableContext.join(" ")).toContain("telegram");
|
|
2777
|
+
expect(response.relevantPatterns.join(" ")).toContain("resume_context");
|
|
2778
|
+
expect(response.relevantPatterns.join(" ")).toContain("search_memory");
|
|
2779
|
+
expect(response.relevantPatterns.join(" ")).toContain("checkpoint_session");
|
|
2780
|
+
expect(response.relevantPatterns.join(" ")).not.toContain("RecallNest");
|
|
2781
|
+
expect(response.recentCases).toEqual([]);
|
|
2782
|
+
});
|
|
2783
|
+
|
|
2784
|
+
it("filters foreign durable case memories from generic scopes for unscoped external bridge tasks", async () => {
|
|
2785
|
+
const retriever = {
|
|
2786
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
2787
|
+
if (context.category !== "cases") {
|
|
2788
|
+
return [];
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
return [
|
|
2792
|
+
buildResult(
|
|
2793
|
+
"case-memory-agent-recallnest-foreign",
|
|
2794
|
+
"cases",
|
|
2795
|
+
"Case: RecallNest sparse startup context cleanup Problem: resume_context returned noisy transcript fragments and unrelated memories instead of a clean project handoff.",
|
|
2796
|
+
),
|
|
2797
|
+
];
|
|
2798
|
+
},
|
|
2799
|
+
};
|
|
2800
|
+
|
|
2801
|
+
const response = await composeResumeContext({
|
|
2802
|
+
retriever,
|
|
2803
|
+
checkpointStore: {
|
|
2804
|
+
async getLatest() {
|
|
2805
|
+
return null;
|
|
2806
|
+
},
|
|
2807
|
+
},
|
|
2808
|
+
listPins: () => [],
|
|
2809
|
+
}, {
|
|
2810
|
+
task: "继续 telegram ai bridge 项目",
|
|
2811
|
+
includeLatestCheckpoint: false,
|
|
2812
|
+
limitPerSection: 3,
|
|
2813
|
+
});
|
|
2814
|
+
|
|
2815
|
+
expect(response.stableContext.join(" ")).toContain("telegram");
|
|
2816
|
+
expect(response.recentCases).toEqual([]);
|
|
2817
|
+
});
|
|
2818
|
+
|
|
2819
|
+
it("prefers transport-specific results over generic bare recallnest continuity seeds", async () => {
|
|
2820
|
+
const retriever = {
|
|
2821
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
2822
|
+
if (context.category === "patterns") {
|
|
2823
|
+
return [
|
|
2824
|
+
withScope(
|
|
2825
|
+
buildResult(
|
|
2826
|
+
"pattern-seeded-recallnest-handoff-transport",
|
|
2827
|
+
"patterns",
|
|
2828
|
+
"Workflow pattern: Scoped project continuity recall Tools: resume_context, latest_checkpoint, search_memory Use when: When continuing a named project that already has a shared scope or project key Steps: 1. Call resume_context before repo exploration.",
|
|
2829
|
+
),
|
|
2830
|
+
"recallnest",
|
|
2831
|
+
),
|
|
2832
|
+
withScope(
|
|
2833
|
+
buildResult(
|
|
2834
|
+
"pattern-recallnest-transport-seeded-specific",
|
|
2835
|
+
"patterns",
|
|
2836
|
+
"Workflow pattern: RecallNest MCP transport rollout Tools: resume_context, search_memory, eval:continuity Use when: When RecallNest continuity work touches MCP transport wiring under project scope Steps: 1. Call resume_context with project:recallnest before transport changes.",
|
|
2837
|
+
),
|
|
2838
|
+
"project:recallnest",
|
|
2839
|
+
),
|
|
2840
|
+
];
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2843
|
+
if (context.category === "cases") {
|
|
2844
|
+
return [
|
|
2845
|
+
withScope(
|
|
2846
|
+
buildResult(
|
|
2847
|
+
"case-seeded-recallnest-validation",
|
|
2848
|
+
"cases",
|
|
2849
|
+
"Case: Three-terminal continuity trigger validation Problem: Claude Code, Codex, and Gemini CLI were configured with MCP but only continue-style prompts triggered recall reliably.",
|
|
2850
|
+
),
|
|
2851
|
+
"recallnest",
|
|
2852
|
+
),
|
|
2853
|
+
withScope(
|
|
2854
|
+
buildResult(
|
|
2855
|
+
"case-recallnest-transport-seeded-specific",
|
|
2856
|
+
"cases",
|
|
2857
|
+
"Case: RecallNest MCP transport regression Problem: Scoped RecallNest continuity work around MCP transport could still drift unless the transport fixes were easy to recover.",
|
|
2858
|
+
),
|
|
2859
|
+
"project:recallnest",
|
|
2860
|
+
),
|
|
2861
|
+
];
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
return [];
|
|
2865
|
+
},
|
|
2866
|
+
};
|
|
2867
|
+
|
|
2868
|
+
const response = await composeResumeContext({
|
|
2869
|
+
retriever,
|
|
2870
|
+
checkpointStore: {
|
|
2871
|
+
async getLatest() {
|
|
2872
|
+
return null;
|
|
2873
|
+
},
|
|
2874
|
+
},
|
|
2875
|
+
listPins: () => [],
|
|
2876
|
+
}, {
|
|
2877
|
+
task: "继续 RecallNest continuity MCP transport rollout",
|
|
2878
|
+
scope: "project:recallnest",
|
|
2879
|
+
includeLatestCheckpoint: false,
|
|
2880
|
+
limitPerSection: 3,
|
|
2881
|
+
});
|
|
2882
|
+
|
|
2883
|
+
expect(response.relevantPatterns.join(" ")).toContain("RecallNest MCP transport rollout");
|
|
2884
|
+
expect(response.recentCases.join(" ")).toContain("RecallNest MCP transport regression");
|
|
2885
|
+
});
|
|
2886
|
+
|
|
2887
|
+
it("keeps transport-specific patterns ahead of generic continuity diversity fillers", async () => {
|
|
2888
|
+
const retriever = {
|
|
2889
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
2890
|
+
if (context.category === "patterns") {
|
|
2891
|
+
return [
|
|
2892
|
+
withScope(
|
|
2893
|
+
buildResult(
|
|
2894
|
+
"pattern-recallnest-generic-scoped-project",
|
|
2895
|
+
"patterns",
|
|
2896
|
+
"Workflow pattern: Scoped project continuity recall Tools: resume_context, latest_checkpoint, search_memory Use when: When continuing a named project that already has a shared scope or project key Steps: 1. Call resume_context before repo exploration.",
|
|
2897
|
+
),
|
|
2898
|
+
"project:recallnest",
|
|
2899
|
+
),
|
|
2900
|
+
withScope(
|
|
2901
|
+
buildResult(
|
|
2902
|
+
"pattern-recallnest-generic-handoff",
|
|
2903
|
+
"patterns",
|
|
2904
|
+
"Workflow pattern: Cross-window continuity handoff Tools: resume_context, latest_checkpoint Use when: When opening a fresh terminal window for the same project Steps: 1. Call resume_context before coding.",
|
|
2905
|
+
),
|
|
2906
|
+
"project:recallnest",
|
|
2907
|
+
),
|
|
2908
|
+
withScope(
|
|
2909
|
+
buildResult(
|
|
2910
|
+
"pattern-recallnest-generic-search",
|
|
2911
|
+
"patterns",
|
|
2912
|
+
"Workflow pattern: Recall before repo exploration Tools: resume_context, search_memory Use when: When a fresh window continues the same project but task details are still sparse Steps: 1. Run search_memory before reading local files.",
|
|
2913
|
+
),
|
|
2914
|
+
"project:recallnest",
|
|
2915
|
+
),
|
|
2916
|
+
withScope(
|
|
2917
|
+
buildResult(
|
|
2918
|
+
"pattern-recallnest-transport-priority",
|
|
2919
|
+
"patterns",
|
|
2920
|
+
"Workflow pattern: RecallNest MCP transport rollout Tools: resume_context, search_memory, eval:continuity Use when: When RecallNest continuity work touches MCP transport wiring under project scope Steps: 1. Call resume_context with project:recallnest before transport changes.",
|
|
2921
|
+
),
|
|
2922
|
+
"project:recallnest",
|
|
2923
|
+
),
|
|
2924
|
+
];
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
if (context.category === "cases") {
|
|
2928
|
+
return [
|
|
2929
|
+
withScope(
|
|
2930
|
+
buildResult(
|
|
2931
|
+
"case-recallnest-transport-priority",
|
|
2932
|
+
"cases",
|
|
2933
|
+
"Case: RecallNest MCP transport regression Problem: Scoped RecallNest continuity work around MCP transport could still drift unless the transport fixes were easy to recover.",
|
|
2934
|
+
),
|
|
2935
|
+
"project:recallnest",
|
|
2936
|
+
),
|
|
2937
|
+
];
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
return [];
|
|
2941
|
+
},
|
|
2942
|
+
};
|
|
2943
|
+
|
|
2944
|
+
const response = await composeResumeContext({
|
|
2945
|
+
retriever,
|
|
2946
|
+
checkpointStore: {
|
|
2947
|
+
async getLatest() {
|
|
2948
|
+
return null;
|
|
2949
|
+
},
|
|
2950
|
+
},
|
|
2951
|
+
listPins: () => [],
|
|
2952
|
+
}, {
|
|
2953
|
+
task: "继续 RecallNest continuity MCP transport rollout",
|
|
2954
|
+
scope: "project:recallnest",
|
|
2955
|
+
includeLatestCheckpoint: false,
|
|
2956
|
+
limitPerSection: 3,
|
|
2957
|
+
});
|
|
2958
|
+
|
|
2959
|
+
expect(response.relevantPatterns.join(" ")).toContain("RecallNest MCP transport rollout");
|
|
2960
|
+
expect(response.recentCases.join(" ")).toContain("RecallNest MCP transport regression");
|
|
2961
|
+
});
|
|
2962
|
+
|
|
2963
|
+
it("prefers named non-RecallNest entities over unrelated project entities for unscoped tasks", async () => {
|
|
2964
|
+
const retriever = {
|
|
2965
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
2966
|
+
if (context.category !== "entities") {
|
|
2967
|
+
return [];
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
return [
|
|
2971
|
+
buildResult(
|
|
2972
|
+
"entity-recallnest",
|
|
2973
|
+
"entities",
|
|
2974
|
+
"RecallNest is the shared memory layer for Claude Code, Codex, and Gemini CLI.",
|
|
2975
|
+
),
|
|
2976
|
+
buildResult(
|
|
2977
|
+
"entity-telegram-bridge",
|
|
2978
|
+
"entities",
|
|
2979
|
+
"Telegram AI bridge handles A2A Claude Agent SDK query debugging and group-chat transport.",
|
|
2980
|
+
),
|
|
2981
|
+
];
|
|
2982
|
+
},
|
|
2983
|
+
};
|
|
2984
|
+
|
|
2985
|
+
const response = await composeResumeContext({
|
|
2986
|
+
retriever,
|
|
2987
|
+
checkpointStore: {
|
|
2988
|
+
async getLatest() {
|
|
2989
|
+
return null;
|
|
2990
|
+
},
|
|
2991
|
+
},
|
|
2992
|
+
listPins: () => [{
|
|
2993
|
+
id: "pin-writing-style",
|
|
2994
|
+
type: "pinned-memory",
|
|
2995
|
+
createdAt: "2026-03-16T04:00:00.000Z",
|
|
2996
|
+
updatedAt: "2026-03-16T04:00:00.000Z",
|
|
2997
|
+
title: "用户写作语气偏好",
|
|
2998
|
+
summary: "用户稳定偏好口语化、不端着、可以自嘲但不说教。",
|
|
2999
|
+
tags: ["写作", "风格"],
|
|
3000
|
+
source: {
|
|
3001
|
+
memoryId: "memory-old-pin",
|
|
3002
|
+
scope: "memory:agent",
|
|
3003
|
+
timestamp: Date.parse("2026-03-16T04:00:00.000Z"),
|
|
3004
|
+
metadata: {},
|
|
3005
|
+
},
|
|
3006
|
+
snippet: "根据你的档案:GitHub, Claude Code CLI, 本地 Docker。",
|
|
3007
|
+
path: "/tmp/pin-writing-style.json",
|
|
3008
|
+
}],
|
|
3009
|
+
}, {
|
|
3010
|
+
task: "A2A code Claude SDK calling error",
|
|
3011
|
+
includeLatestCheckpoint: false,
|
|
3012
|
+
limitPerSection: 3,
|
|
3013
|
+
});
|
|
3014
|
+
|
|
3015
|
+
const stableJoined = response.stableContext.join(" ");
|
|
3016
|
+
expect(stableJoined).toContain("Telegram AI bridge");
|
|
3017
|
+
expect(stableJoined).not.toContain("RecallNest is the shared memory layer");
|
|
3018
|
+
expect(stableJoined).not.toContain("口语化");
|
|
3019
|
+
});
|
|
3020
|
+
|
|
3021
|
+
it("keeps later entity cues from long unscoped task prompts", async () => {
|
|
3022
|
+
const retriever = {
|
|
3023
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
3024
|
+
if (context.category !== "entities") {
|
|
3025
|
+
return [];
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
return [
|
|
3029
|
+
buildResult(
|
|
3030
|
+
"entity-recallnest",
|
|
3031
|
+
"entities",
|
|
3032
|
+
"RecallNest is the shared memory layer for Claude Code, Codex, and Gemini CLI.",
|
|
3033
|
+
),
|
|
3034
|
+
buildResult(
|
|
3035
|
+
"entity-telegram-bridge",
|
|
3036
|
+
"entities",
|
|
3037
|
+
"Telegram AI bridge handles A2A Claude Agent SDK query debugging and group-chat transport.",
|
|
3038
|
+
),
|
|
3039
|
+
];
|
|
3040
|
+
},
|
|
3041
|
+
};
|
|
3042
|
+
|
|
3043
|
+
const response = await composeResumeContext({
|
|
3044
|
+
retriever,
|
|
3045
|
+
checkpointStore: {
|
|
3046
|
+
async getLatest() {
|
|
3047
|
+
return null;
|
|
3048
|
+
},
|
|
3049
|
+
},
|
|
3050
|
+
listPins: () => [],
|
|
3051
|
+
}, {
|
|
3052
|
+
task: "继续 context composer helper boundary audit ranking scoring selection orchestration stable fallback cleanup regression hardening A2A Claude SDK calling error",
|
|
3053
|
+
includeLatestCheckpoint: false,
|
|
3054
|
+
limitPerSection: 2,
|
|
3055
|
+
});
|
|
3056
|
+
|
|
3057
|
+
const stableJoined = response.stableContext.join(" ");
|
|
3058
|
+
expect(stableJoined).toContain("Telegram AI bridge");
|
|
3059
|
+
expect(stableJoined).not.toContain("RecallNest is the shared memory layer");
|
|
3060
|
+
});
|
|
3061
|
+
|
|
3062
|
+
it("keeps vague associative Nest tasks pointed at RecallNest instead of unrelated entities", async () => {
|
|
3063
|
+
const retriever = {
|
|
3064
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
3065
|
+
if (context.category !== "entities") {
|
|
3066
|
+
return [];
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
return [
|
|
3070
|
+
buildResult(
|
|
3071
|
+
"entity-recallnest",
|
|
3072
|
+
"entities",
|
|
3073
|
+
"RecallNest is the shared memory layer for Claude Code, Codex, and Gemini CLI.",
|
|
3074
|
+
),
|
|
3075
|
+
buildResult(
|
|
3076
|
+
"entity-telegram-bridge",
|
|
3077
|
+
"entities",
|
|
3078
|
+
"Telegram AI bridge handles A2A Claude Agent SDK query debugging and group-chat transport.",
|
|
3079
|
+
),
|
|
3080
|
+
];
|
|
3081
|
+
},
|
|
3082
|
+
};
|
|
3083
|
+
|
|
3084
|
+
const response = await composeResumeContext({
|
|
3085
|
+
retriever,
|
|
3086
|
+
checkpointStore: {
|
|
3087
|
+
async getLatest() {
|
|
3088
|
+
return null;
|
|
3089
|
+
},
|
|
3090
|
+
},
|
|
3091
|
+
listPins: () => [{
|
|
3092
|
+
id: "pin-writing-style",
|
|
3093
|
+
type: "pinned-memory",
|
|
3094
|
+
createdAt: "2026-03-16T04:00:00.000Z",
|
|
3095
|
+
updatedAt: "2026-03-16T04:00:00.000Z",
|
|
3096
|
+
title: "用户写作语气偏好",
|
|
3097
|
+
summary: "用户稳定偏好口语化、不端着、可以自嘲但不说教。",
|
|
3098
|
+
tags: ["写作", "风格"],
|
|
3099
|
+
source: {
|
|
3100
|
+
memoryId: "memory-old-pin",
|
|
3101
|
+
scope: "memory:agent",
|
|
3102
|
+
timestamp: Date.parse("2026-03-16T04:00:00.000Z"),
|
|
3103
|
+
metadata: {},
|
|
3104
|
+
},
|
|
3105
|
+
snippet: "根据你的档案:GitHub, Claude Code CLI, 本地 Docker。",
|
|
3106
|
+
path: "/tmp/pin-writing-style.json",
|
|
3107
|
+
}],
|
|
3108
|
+
}, {
|
|
3109
|
+
task: "之前弄过那个什么 Nest 的记忆系统",
|
|
3110
|
+
includeLatestCheckpoint: false,
|
|
3111
|
+
limitPerSection: 3,
|
|
3112
|
+
});
|
|
3113
|
+
|
|
3114
|
+
const stableJoined = response.stableContext.join(" ");
|
|
3115
|
+
expect(stableJoined).toContain("RecallNest");
|
|
3116
|
+
expect(stableJoined).not.toContain("Telegram AI bridge");
|
|
3117
|
+
expect(stableJoined).not.toContain("口语化");
|
|
3118
|
+
});
|
|
3119
|
+
|
|
3120
|
+
it("uses an associative Nest fallback query to recover continuity primitives for vague RecallNest tasks", async () => {
|
|
3121
|
+
const calls: RetrievalContext[] = [];
|
|
3122
|
+
const retriever = {
|
|
3123
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
3124
|
+
calls.push(context);
|
|
3125
|
+
if (context.category !== "entities") {
|
|
3126
|
+
return [];
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
if (context.query.includes("checkpoint_session")) {
|
|
3130
|
+
return [
|
|
3131
|
+
buildResult(
|
|
3132
|
+
"entity-primitives",
|
|
3133
|
+
"entities",
|
|
3134
|
+
"RecallNest continuity revolves around three primitives: store_memory, checkpoint_session, and resume_context.",
|
|
3135
|
+
),
|
|
3136
|
+
];
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
return [
|
|
3140
|
+
buildResult(
|
|
3141
|
+
"entity-recallnest",
|
|
3142
|
+
"entities",
|
|
3143
|
+
"RecallNest is the shared memory layer for Claude Code, Codex, and Gemini CLI.",
|
|
3144
|
+
),
|
|
3145
|
+
];
|
|
3146
|
+
},
|
|
3147
|
+
};
|
|
3148
|
+
|
|
3149
|
+
const response = await composeResumeContext({
|
|
3150
|
+
retriever,
|
|
3151
|
+
checkpointStore: {
|
|
3152
|
+
async getLatest() {
|
|
3153
|
+
return null;
|
|
3154
|
+
},
|
|
3155
|
+
},
|
|
3156
|
+
listPins: () => [],
|
|
3157
|
+
}, {
|
|
3158
|
+
task: "之前弄过那个什么 Nest 的记忆系统",
|
|
3159
|
+
includeLatestCheckpoint: false,
|
|
3160
|
+
limitPerSection: 3,
|
|
3161
|
+
});
|
|
3162
|
+
|
|
3163
|
+
const stableJoined = response.stableContext.join(" ");
|
|
3164
|
+
expect(stableJoined).toContain("RecallNest");
|
|
3165
|
+
expect(stableJoined).toContain("checkpoint_session");
|
|
3166
|
+
expect(calls.filter((call) => call.category === "entities")).toHaveLength(2);
|
|
3167
|
+
expect(calls.some((call) => call.category === "entities" && call.query.includes("checkpoint_session"))).toBe(true);
|
|
3168
|
+
});
|
|
3169
|
+
|
|
3170
|
+
it("filters conversational transcript pins out of stable context for external bridge tasks", async () => {
|
|
3171
|
+
const retriever = {
|
|
3172
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
3173
|
+
return [];
|
|
3174
|
+
},
|
|
3175
|
+
};
|
|
3176
|
+
|
|
3177
|
+
const response = await composeResumeContext({
|
|
3178
|
+
retriever,
|
|
3179
|
+
checkpointStore: {
|
|
3180
|
+
async getLatest() {
|
|
3181
|
+
return null;
|
|
3182
|
+
},
|
|
3183
|
+
},
|
|
3184
|
+
listPins: () => [{
|
|
3185
|
+
id: "pin-bridge-raw",
|
|
3186
|
+
type: "pinned-memory",
|
|
3187
|
+
createdAt: "2026-03-16T04:00:00.000Z",
|
|
3188
|
+
updatedAt: "2026-03-16T04:00:00.000Z",
|
|
3189
|
+
title: "[助手] 现在更新 telegram-cli-bridge 自己的 README(已经有旧引用)。",
|
|
3190
|
+
summary: "[助手] 现在更新 telegram-cli-bridge 自己的 README(已经有旧引用)。",
|
|
3191
|
+
tags: ["telegram", "bridge"],
|
|
3192
|
+
source: {
|
|
3193
|
+
memoryId: "memory-bridge-raw",
|
|
3194
|
+
scope: "cc:bridge-session",
|
|
3195
|
+
timestamp: Date.parse("2026-03-16T04:00:00.000Z"),
|
|
3196
|
+
metadata: {},
|
|
3197
|
+
},
|
|
3198
|
+
snippet: "[助手] 现在更新 telegram-cli-bridge 自己的 README(已经有旧引用)。",
|
|
3199
|
+
path: "/tmp/pin-bridge-raw.json",
|
|
3200
|
+
}],
|
|
3201
|
+
}, {
|
|
3202
|
+
task: "继续 telegram ai bridge 项目",
|
|
3203
|
+
includeLatestCheckpoint: false,
|
|
3204
|
+
limitPerSection: 3,
|
|
3205
|
+
});
|
|
3206
|
+
|
|
3207
|
+
const stableJoined = response.stableContext.join(" ");
|
|
3208
|
+
expect(stableJoined).toContain("Task focus:");
|
|
3209
|
+
expect(stableJoined).toContain("telegram");
|
|
3210
|
+
expect(stableJoined).not.toContain("[助手]");
|
|
3211
|
+
expect(stableJoined).not.toContain("README");
|
|
3212
|
+
});
|
|
3213
|
+
|
|
3214
|
+
it("filters conversational durable pins out of scoped stable context", async () => {
|
|
3215
|
+
const retriever = {
|
|
3216
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
3217
|
+
return [];
|
|
3218
|
+
},
|
|
3219
|
+
};
|
|
3220
|
+
|
|
3221
|
+
const response = await composeResumeContext({
|
|
3222
|
+
retriever,
|
|
3223
|
+
checkpointStore: {
|
|
3224
|
+
async getLatest() {
|
|
3225
|
+
return null;
|
|
3226
|
+
},
|
|
3227
|
+
},
|
|
3228
|
+
listPins: () => [{
|
|
3229
|
+
id: "pin-bridge-durable",
|
|
3230
|
+
type: "pinned-memory",
|
|
3231
|
+
createdAt: "2026-03-16T04:00:00.000Z",
|
|
3232
|
+
updatedAt: "2026-03-16T04:00:00.000Z",
|
|
3233
|
+
title: "[助手] 现在更新 telegram-cli-bridge 自己的 README(已经有旧引用)。",
|
|
3234
|
+
summary: "[助手] 现在更新 telegram-cli-bridge 自己的 README(已经有旧引用)。",
|
|
3235
|
+
tags: ["telegram", "bridge"],
|
|
3236
|
+
source: {
|
|
3237
|
+
memoryId: "memory-bridge-durable",
|
|
3238
|
+
scope: "memory:project:telegram-bridge",
|
|
3239
|
+
timestamp: Date.parse("2026-03-16T04:00:00.000Z"),
|
|
3240
|
+
metadata: {},
|
|
3241
|
+
},
|
|
3242
|
+
snippet: "[助手] 现在更新 telegram-cli-bridge 自己的 README(已经有旧引用)。",
|
|
3243
|
+
path: "/tmp/pin-bridge-durable.json",
|
|
3244
|
+
}],
|
|
3245
|
+
}, {
|
|
3246
|
+
task: "继续 RecallNest continuity bridge 适配",
|
|
3247
|
+
scope: "project:recallnest",
|
|
3248
|
+
includeLatestCheckpoint: false,
|
|
3249
|
+
limitPerSection: 3,
|
|
3250
|
+
});
|
|
3251
|
+
|
|
3252
|
+
const stableJoined = response.stableContext.join(" ");
|
|
3253
|
+
expect(stableJoined).toContain("Task focus: recallnest");
|
|
3254
|
+
expect(stableJoined).not.toContain("[助手]");
|
|
3255
|
+
expect(stableJoined).not.toContain("README");
|
|
3256
|
+
expect(stableJoined).not.toContain("telegram-cli-bridge");
|
|
3257
|
+
});
|
|
3258
|
+
|
|
3259
|
+
it("filters foreign project pins from scoped stable context when overlap is only task terms", async () => {
|
|
3260
|
+
const retriever = {
|
|
3261
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
3262
|
+
return [];
|
|
3263
|
+
},
|
|
3264
|
+
};
|
|
3265
|
+
|
|
3266
|
+
const response = await composeResumeContext({
|
|
3267
|
+
retriever,
|
|
3268
|
+
checkpointStore: {
|
|
3269
|
+
async getLatest() {
|
|
3270
|
+
return null;
|
|
3271
|
+
},
|
|
3272
|
+
},
|
|
3273
|
+
listPins: () => [{
|
|
3274
|
+
id: "pin-foreign-project",
|
|
3275
|
+
type: "pinned-memory",
|
|
3276
|
+
createdAt: "2026-03-16T04:00:00.000Z",
|
|
3277
|
+
updatedAt: "2026-03-16T04:00:00.000Z",
|
|
3278
|
+
title: "Telegram bridge README adaptation plan",
|
|
3279
|
+
summary: "Bridge adapter migration notes for telegram-cli-bridge transport and README sync.",
|
|
3280
|
+
tags: ["bridge", "readme", "telegram"],
|
|
3281
|
+
source: {
|
|
3282
|
+
memoryId: "memory-bridge-foreign",
|
|
3283
|
+
scope: "memory:project:telegram-bridge",
|
|
3284
|
+
timestamp: Date.parse("2026-03-16T04:00:00.000Z"),
|
|
3285
|
+
metadata: {},
|
|
3286
|
+
},
|
|
3287
|
+
snippet: "adapter migration README sync",
|
|
3288
|
+
path: "/tmp/pin-foreign-project.json",
|
|
3289
|
+
}],
|
|
3290
|
+
}, {
|
|
3291
|
+
task: "继续 RecallNest bridge README 适配",
|
|
3292
|
+
scope: "project:recallnest",
|
|
3293
|
+
includeLatestCheckpoint: false,
|
|
3294
|
+
limitPerSection: 3,
|
|
3295
|
+
});
|
|
3296
|
+
|
|
3297
|
+
const stableJoined = response.stableContext.join(" ");
|
|
3298
|
+
expect(stableJoined).toContain("Task focus: recallnest");
|
|
3299
|
+
expect(stableJoined).not.toContain("Telegram bridge README adaptation plan");
|
|
3300
|
+
expect(stableJoined).not.toContain("telegram-cli-bridge");
|
|
3301
|
+
});
|
|
3302
|
+
|
|
3303
|
+
it("adds a task focus fallback when stable recall is otherwise empty", async () => {
|
|
3304
|
+
const retriever = {
|
|
3305
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
3306
|
+
return [];
|
|
3307
|
+
},
|
|
3308
|
+
};
|
|
3309
|
+
|
|
3310
|
+
const response = await composeResumeContext({
|
|
3311
|
+
retriever,
|
|
3312
|
+
checkpointStore: {
|
|
3313
|
+
async getLatest() {
|
|
3314
|
+
return null;
|
|
3315
|
+
},
|
|
3316
|
+
},
|
|
3317
|
+
listPins: () => [],
|
|
3318
|
+
}, {
|
|
3319
|
+
task: "一起继续排查 RecallNest 的连续性问题",
|
|
3320
|
+
includeLatestCheckpoint: false,
|
|
3321
|
+
limitPerSection: 2,
|
|
3322
|
+
});
|
|
3323
|
+
|
|
3324
|
+
expect(response.stableContext).toContain("Task focus: recallnest");
|
|
3325
|
+
});
|
|
3326
|
+
|
|
3327
|
+
it("uses RecallNest task focus for vague memory-layer continuity prompts instead of unrelated pins", async () => {
|
|
3328
|
+
const retriever = {
|
|
3329
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
3330
|
+
return [];
|
|
3331
|
+
},
|
|
3332
|
+
};
|
|
3333
|
+
|
|
3334
|
+
const response = await composeResumeContext({
|
|
3335
|
+
retriever,
|
|
3336
|
+
checkpointStore: {
|
|
3337
|
+
async getLatest() {
|
|
3338
|
+
return null;
|
|
3339
|
+
},
|
|
3340
|
+
},
|
|
3341
|
+
listPins: () => [{
|
|
3342
|
+
id: "pin-visual-memory-layer",
|
|
3343
|
+
type: "pinned-memory",
|
|
3344
|
+
createdAt: "2026-03-16T04:00:00.000Z",
|
|
3345
|
+
updatedAt: "2026-03-16T04:00:00.000Z",
|
|
3346
|
+
title: "用户视觉审美偏好",
|
|
3347
|
+
summary: "用户常用视觉风格是手绘涂鸦风加高对比撞色;在内容包装和配图生成时,应优先沿用这一审美方向。",
|
|
3348
|
+
tags: ["审美偏好", "手绘涂鸦", "高对比撞色", "给刚才写的文章生成配图"],
|
|
3349
|
+
source: {
|
|
3350
|
+
memoryId: "memory-visual-memory-layer",
|
|
3351
|
+
scope: "cc:visual-pin",
|
|
3352
|
+
timestamp: Date.parse("2026-03-16T04:00:00.000Z"),
|
|
3353
|
+
metadata: {},
|
|
3354
|
+
},
|
|
3355
|
+
snippet: "给刚才写的文章生成配图,风格:手绘涂鸦风+高对比撞色。",
|
|
3356
|
+
path: "/tmp/pin-visual-memory-layer.json",
|
|
3357
|
+
}],
|
|
3358
|
+
}, {
|
|
3359
|
+
task: "把刚才那个 memory layer 接回去",
|
|
3360
|
+
includeLatestCheckpoint: false,
|
|
3361
|
+
limitPerSection: 2,
|
|
3362
|
+
});
|
|
3363
|
+
|
|
3364
|
+
const stableJoined = response.stableContext.join(" ");
|
|
3365
|
+
expect(stableJoined).toContain("Task focus: recallnest");
|
|
3366
|
+
expect(stableJoined).not.toContain("手绘涂鸦");
|
|
3367
|
+
});
|
|
3368
|
+
|
|
3369
|
+
it("uses RecallNest task focus for vague cross-window memory-system prompts", async () => {
|
|
3370
|
+
const retriever = {
|
|
3371
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
3372
|
+
return [];
|
|
3373
|
+
},
|
|
3374
|
+
};
|
|
3375
|
+
|
|
3376
|
+
const response = await composeResumeContext({
|
|
3377
|
+
retriever,
|
|
3378
|
+
checkpointStore: {
|
|
3379
|
+
async getLatest() {
|
|
3380
|
+
return null;
|
|
3381
|
+
},
|
|
3382
|
+
},
|
|
3383
|
+
listPins: () => [],
|
|
3384
|
+
}, {
|
|
3385
|
+
task: "继续那个跨窗口记忆系统",
|
|
3386
|
+
includeLatestCheckpoint: false,
|
|
3387
|
+
limitPerSection: 2,
|
|
3388
|
+
});
|
|
3389
|
+
|
|
3390
|
+
const stableJoined = response.stableContext.join(" ");
|
|
3391
|
+
expect(stableJoined).toContain("Task focus: recallnest");
|
|
3392
|
+
expect(stableJoined).not.toContain("Task focus: 窗口");
|
|
3393
|
+
});
|
|
3394
|
+
|
|
3395
|
+
it("uses an associative RecallNest fallback query for colloquial cross-window memory shorthand", async () => {
|
|
3396
|
+
const calls: RetrievalContext[] = [];
|
|
3397
|
+
const retriever = {
|
|
3398
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
3399
|
+
calls.push(context);
|
|
3400
|
+
if (context.category !== "entities") {
|
|
3401
|
+
return [];
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
if (context.query.includes("checkpoint_session")) {
|
|
3405
|
+
return [
|
|
3406
|
+
buildResult(
|
|
3407
|
+
"entity-cross-window-memory-primitives",
|
|
3408
|
+
"entities",
|
|
3409
|
+
"RecallNest continuity revolves around three primitives: store_memory, checkpoint_session, and resume_context.",
|
|
3410
|
+
),
|
|
3411
|
+
];
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
return [];
|
|
3415
|
+
},
|
|
3416
|
+
};
|
|
3417
|
+
|
|
3418
|
+
const response = await composeResumeContext({
|
|
3419
|
+
retriever,
|
|
3420
|
+
checkpointStore: {
|
|
3421
|
+
async getLatest() {
|
|
3422
|
+
return null;
|
|
3423
|
+
},
|
|
3424
|
+
},
|
|
3425
|
+
listPins: () => [],
|
|
3426
|
+
}, {
|
|
3427
|
+
task: "把那条跨窗口记忆的活接着弄完",
|
|
3428
|
+
includeLatestCheckpoint: false,
|
|
3429
|
+
limitPerSection: 2,
|
|
3430
|
+
});
|
|
3431
|
+
|
|
3432
|
+
const stableJoined = response.stableContext.join(" ");
|
|
3433
|
+
expect(stableJoined).toContain("RecallNest");
|
|
3434
|
+
expect(stableJoined).toContain("checkpoint_session");
|
|
3435
|
+
expect(calls.filter((call) => call.category === "entities")).toHaveLength(2);
|
|
3436
|
+
expect(calls.some((call) =>
|
|
3437
|
+
call.category === "entities" &&
|
|
3438
|
+
call.query.includes("RecallNest") &&
|
|
3439
|
+
call.query.includes("checkpoint_session")
|
|
3440
|
+
)).toBe(true);
|
|
3441
|
+
});
|
|
3442
|
+
|
|
3443
|
+
it("uses RecallNest task focus for vague multi-window memory shorthand", async () => {
|
|
3444
|
+
const retriever = {
|
|
3445
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
3446
|
+
return [];
|
|
3447
|
+
},
|
|
3448
|
+
};
|
|
3449
|
+
|
|
3450
|
+
const response = await composeResumeContext({
|
|
3451
|
+
retriever,
|
|
3452
|
+
checkpointStore: {
|
|
3453
|
+
async getLatest() {
|
|
3454
|
+
return null;
|
|
3455
|
+
},
|
|
3456
|
+
},
|
|
3457
|
+
listPins: () => [],
|
|
3458
|
+
}, {
|
|
3459
|
+
task: "继续弄那个多窗口记忆",
|
|
3460
|
+
includeLatestCheckpoint: false,
|
|
3461
|
+
limitPerSection: 2,
|
|
3462
|
+
});
|
|
3463
|
+
|
|
3464
|
+
const stableJoined = response.stableContext.join(" ");
|
|
3465
|
+
expect(stableJoined).toContain("Task focus: recallnest");
|
|
3466
|
+
expect(stableJoined).not.toContain("Task focus: 记忆");
|
|
3467
|
+
});
|
|
3468
|
+
|
|
3469
|
+
it("treats recall-pipeline shorthand as a continuity prompt", async () => {
|
|
3470
|
+
const retriever = {
|
|
3471
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
3472
|
+
return [];
|
|
3473
|
+
},
|
|
3474
|
+
};
|
|
3475
|
+
|
|
3476
|
+
const response = await composeResumeContext({
|
|
3477
|
+
retriever,
|
|
3478
|
+
checkpointStore: {
|
|
3479
|
+
async getLatest() {
|
|
3480
|
+
return null;
|
|
3481
|
+
},
|
|
3482
|
+
},
|
|
3483
|
+
listPins: () => [],
|
|
3484
|
+
}, {
|
|
3485
|
+
task: "把之前那套 recall 管线先捡起来",
|
|
3486
|
+
includeLatestCheckpoint: false,
|
|
3487
|
+
limitPerSection: 2,
|
|
3488
|
+
});
|
|
3489
|
+
|
|
3490
|
+
expect(response.stableContext).toContain("Task focus: recallnest");
|
|
3491
|
+
expect(response.relevantPatterns.join(" ")).toContain("resume_context");
|
|
3492
|
+
});
|
|
3493
|
+
|
|
3494
|
+
it("keeps later task focus terms from long sparse prompts", async () => {
|
|
3495
|
+
const retriever = {
|
|
3496
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
3497
|
+
return [];
|
|
3498
|
+
},
|
|
3499
|
+
};
|
|
3500
|
+
|
|
3501
|
+
const response = await composeResumeContext({
|
|
3502
|
+
retriever,
|
|
3503
|
+
checkpointStore: {
|
|
3504
|
+
async getLatest() {
|
|
3505
|
+
return null;
|
|
3506
|
+
},
|
|
3507
|
+
},
|
|
3508
|
+
listPins: () => [],
|
|
3509
|
+
}, {
|
|
3510
|
+
task: "继续 context composer helper boundary audit ranking scoring selection orchestration stable fallback cleanup regression hardening recallnest",
|
|
3511
|
+
includeLatestCheckpoint: false,
|
|
3512
|
+
limitPerSection: 2,
|
|
3513
|
+
});
|
|
3514
|
+
|
|
3515
|
+
expect(response.stableContext).toContain("Task focus: recallnest");
|
|
3516
|
+
});
|
|
3517
|
+
|
|
3518
|
+
it("treats vague memory-layer shorthand as a continuity prompt", async () => {
|
|
3519
|
+
const retriever = {
|
|
3520
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
3521
|
+
return [];
|
|
3522
|
+
},
|
|
3523
|
+
};
|
|
3524
|
+
|
|
3525
|
+
const response = await composeResumeContext({
|
|
3526
|
+
retriever,
|
|
3527
|
+
checkpointStore: {
|
|
3528
|
+
async getLatest() {
|
|
3529
|
+
return null;
|
|
3530
|
+
},
|
|
3531
|
+
},
|
|
3532
|
+
listPins: () => [],
|
|
3533
|
+
}, {
|
|
3534
|
+
task: "把记忆层这条线先续上",
|
|
3535
|
+
includeLatestCheckpoint: false,
|
|
3536
|
+
limitPerSection: 2,
|
|
3537
|
+
});
|
|
3538
|
+
|
|
3539
|
+
expect(response.stableContext).toContain("Task focus: recallnest");
|
|
3540
|
+
expect(response.relevantPatterns.join(" ")).toContain("resume_context");
|
|
3541
|
+
});
|
|
3542
|
+
|
|
3543
|
+
it("filters noisy non-durable cases and keeps durable case memories", async () => {
|
|
3544
|
+
const retriever = {
|
|
3545
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
3546
|
+
if (context.category === "cases") {
|
|
3547
|
+
return [
|
|
3548
|
+
withScope(
|
|
3549
|
+
buildResult(
|
|
3550
|
+
"case-noise",
|
|
3551
|
+
"cases",
|
|
3552
|
+
"[用户] 笑不活了,怎么https://github.com/AliceLJY/recallnest/issues 我的还在,解决了帮我关闭啊。。。 [助手] 三个 open issues,让我看看内容。",
|
|
3553
|
+
),
|
|
3554
|
+
"cc:14c6e6d9",
|
|
3555
|
+
),
|
|
3556
|
+
withScope(
|
|
3557
|
+
buildResult(
|
|
3558
|
+
"case-durable",
|
|
3559
|
+
"cases",
|
|
3560
|
+
"RecallNest continuity case: resume_context returned sparse startup context, so we filtered noisy transcript fragments and backfilled stable context from checkpoint focus, summary, and decisions.",
|
|
3561
|
+
),
|
|
3562
|
+
"memory:agent",
|
|
3563
|
+
),
|
|
3564
|
+
];
|
|
3565
|
+
}
|
|
3566
|
+
return [];
|
|
3567
|
+
},
|
|
3568
|
+
};
|
|
3569
|
+
|
|
3570
|
+
const response = await composeResumeContext({
|
|
3571
|
+
retriever,
|
|
3572
|
+
checkpointStore: {
|
|
3573
|
+
async getLatest() {
|
|
3574
|
+
return null;
|
|
3575
|
+
},
|
|
3576
|
+
},
|
|
3577
|
+
listPins: () => [],
|
|
3578
|
+
}, {
|
|
3579
|
+
task: "一起继续排查 RecallNest 的连续性问题",
|
|
3580
|
+
includeLatestCheckpoint: false,
|
|
3581
|
+
limitPerSection: 3,
|
|
3582
|
+
});
|
|
3583
|
+
|
|
3584
|
+
expect(response.recentCases).toEqual([
|
|
3585
|
+
"RecallNest continuity case: resume_context returned sparse startup context, so we filtered noisy transcript fragments and backfilled stable context from checkpoint focus, summary, and decisions.",
|
|
3586
|
+
]);
|
|
3587
|
+
});
|
|
3588
|
+
|
|
3589
|
+
it("filters non-durable query-analysis case notes from broad fallback results", async () => {
|
|
3590
|
+
const retriever = {
|
|
3591
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
3592
|
+
if (context.category !== "cases") return [];
|
|
3593
|
+
if (context.query.includes("similar solved case previous fix")) {
|
|
3594
|
+
return [];
|
|
3595
|
+
}
|
|
3596
|
+
return [
|
|
3597
|
+
withScope(
|
|
3598
|
+
buildResult(
|
|
3599
|
+
"case-query-note",
|
|
3600
|
+
"cases",
|
|
3601
|
+
"[助手] fallback query 本身也值得看一眼,`case solution fix root cause workaround cleanup continuity` 这串很可能把 A2A repair case 一起拉进来了。",
|
|
3602
|
+
),
|
|
3603
|
+
"codex:query-note",
|
|
3604
|
+
),
|
|
3605
|
+
withScope(
|
|
3606
|
+
buildResult(
|
|
3607
|
+
"case-fallback-cleanup-structured",
|
|
3608
|
+
"cases",
|
|
3609
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: reject noisy transcript fragments and prefer durable continuity material.",
|
|
3610
|
+
),
|
|
3611
|
+
"project:recallnest",
|
|
3612
|
+
),
|
|
3613
|
+
];
|
|
3614
|
+
},
|
|
3615
|
+
};
|
|
3616
|
+
|
|
3617
|
+
const response = await composeResumeContext({
|
|
3618
|
+
retriever,
|
|
3619
|
+
checkpointStore: {
|
|
3620
|
+
async getLatest() {
|
|
3621
|
+
return null;
|
|
3622
|
+
},
|
|
3623
|
+
},
|
|
3624
|
+
listPins: () => [],
|
|
3625
|
+
}, {
|
|
3626
|
+
task: "继续我的项目",
|
|
3627
|
+
scope: "project:recallnest",
|
|
3628
|
+
includeLatestCheckpoint: false,
|
|
3629
|
+
limitPerSection: 3,
|
|
3630
|
+
});
|
|
3631
|
+
|
|
3632
|
+
expect(response.recentCases.join(" ")).toContain("RecallNest scope fallback cleanup");
|
|
3633
|
+
expect(response.recentCases.join(" ")).not.toContain("fallback query 本身也值得看一眼");
|
|
3634
|
+
expect(response.recentCases.join(" ")).not.toContain("A2A repair case");
|
|
3635
|
+
});
|
|
3636
|
+
|
|
3637
|
+
it("suppresses eval-profile maintenance cases for sparse project-scoped continues", async () => {
|
|
3638
|
+
const retriever = {
|
|
3639
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
3640
|
+
if (context.category !== "cases") return [];
|
|
3641
|
+
return [
|
|
3642
|
+
withScope(
|
|
3643
|
+
buildResult(
|
|
3644
|
+
"case-profile-forwarding-gap",
|
|
3645
|
+
"cases",
|
|
3646
|
+
"Case: Continuity eval profile forwarding gap Problem: A continuity eval case that used profile: writing still failed on sparse writing prompts even after composeResumeContext had the right sparse-style fallback behavior. Solution: forward the eval profile into resume_context requests.",
|
|
3647
|
+
),
|
|
3648
|
+
"project:recallnest",
|
|
3649
|
+
),
|
|
3650
|
+
withScope(
|
|
3651
|
+
buildResult(
|
|
3652
|
+
"case-scope-fallback-cleanup",
|
|
3653
|
+
"cases",
|
|
3654
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: prefer durable cases and patterns.",
|
|
3655
|
+
),
|
|
3656
|
+
"project:recallnest",
|
|
3657
|
+
),
|
|
3658
|
+
];
|
|
3659
|
+
},
|
|
3660
|
+
};
|
|
3661
|
+
|
|
3662
|
+
const response = await composeResumeContext({
|
|
3663
|
+
retriever,
|
|
3664
|
+
checkpointStore: {
|
|
3665
|
+
async getLatest() {
|
|
3666
|
+
return null;
|
|
3667
|
+
},
|
|
3668
|
+
},
|
|
3669
|
+
listPins: () => [],
|
|
3670
|
+
}, {
|
|
3671
|
+
task: "继续这个项目,不要让我重复前情",
|
|
3672
|
+
scope: "project:recallnest",
|
|
3673
|
+
includeLatestCheckpoint: false,
|
|
3674
|
+
limitPerSection: 3,
|
|
3675
|
+
});
|
|
3676
|
+
|
|
3677
|
+
expect(response.recentCases.join(" ")).toContain("RecallNest scope fallback cleanup");
|
|
3678
|
+
expect(response.recentCases.join(" ")).not.toContain("profile forwarding gap");
|
|
3679
|
+
expect(response.recentCases.join(" ")).not.toContain("sparse writing prompts");
|
|
3680
|
+
expect(response.recentCases.join(" ")).not.toContain("profile: writing");
|
|
3681
|
+
});
|
|
3682
|
+
|
|
3683
|
+
it("allows eval-profile maintenance cases for explicit eval-profile debugging tasks", async () => {
|
|
3684
|
+
const retriever = {
|
|
3685
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
3686
|
+
if (context.category !== "cases") return [];
|
|
3687
|
+
return [
|
|
3688
|
+
withScope(
|
|
3689
|
+
buildResult(
|
|
3690
|
+
"case-profile-forwarding-gap",
|
|
3691
|
+
"cases",
|
|
3692
|
+
"Case: Continuity eval profile forwarding gap Problem: A continuity eval case that used profile: writing still failed on sparse writing prompts even after composeResumeContext had the right sparse-style fallback behavior. Solution: forward the eval profile into resume_context requests.",
|
|
3693
|
+
),
|
|
3694
|
+
"project:recallnest",
|
|
3695
|
+
),
|
|
3696
|
+
];
|
|
3697
|
+
},
|
|
3698
|
+
};
|
|
3699
|
+
|
|
3700
|
+
const response = await composeResumeContext({
|
|
3701
|
+
retriever,
|
|
3702
|
+
checkpointStore: {
|
|
3703
|
+
async getLatest() {
|
|
3704
|
+
return null;
|
|
3705
|
+
},
|
|
3706
|
+
},
|
|
3707
|
+
listPins: () => [],
|
|
3708
|
+
}, {
|
|
3709
|
+
task: "继续排查 continuity eval profile forwarding gap 的 sparse writing prompts 回归",
|
|
3710
|
+
scope: "project:recallnest",
|
|
3711
|
+
includeLatestCheckpoint: false,
|
|
3712
|
+
limitPerSection: 3,
|
|
3713
|
+
});
|
|
3714
|
+
|
|
3715
|
+
expect(response.recentCases.join(" ")).toContain("profile forwarding gap");
|
|
3716
|
+
expect(response.recentCases.join(" ")).toContain("profile: writing");
|
|
3717
|
+
expect(response.recentCases.join(" ")).toContain("sparse writing prompts");
|
|
3718
|
+
});
|
|
3719
|
+
|
|
3720
|
+
it("suppresses eval-profile maintenance cases for adjacent writing-slot engineering tasks", async () => {
|
|
3721
|
+
const retriever = {
|
|
3722
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
3723
|
+
if (context.category !== "cases") return [];
|
|
3724
|
+
return [
|
|
3725
|
+
withScope(
|
|
3726
|
+
buildResult(
|
|
3727
|
+
"case-profile-forwarding-gap",
|
|
3728
|
+
"cases",
|
|
3729
|
+
"Case: Continuity eval profile forwarding gap Problem: A continuity eval case that used profile: writing still failed on sparse writing prompts even after composeResumeContext had the right sparse-style fallback behavior. Solution: forward the eval profile into resume_context requests.",
|
|
3730
|
+
),
|
|
3731
|
+
"project:recallnest",
|
|
3732
|
+
),
|
|
3733
|
+
];
|
|
3734
|
+
},
|
|
3735
|
+
};
|
|
3736
|
+
|
|
3737
|
+
const response = await composeResumeContext({
|
|
3738
|
+
retriever,
|
|
3739
|
+
checkpointStore: {
|
|
3740
|
+
async getLatest() {
|
|
3741
|
+
return null;
|
|
3742
|
+
},
|
|
3743
|
+
},
|
|
3744
|
+
listPins: () => [],
|
|
3745
|
+
}, {
|
|
3746
|
+
task: "继续收 RecallNest 写作风格偏好存储",
|
|
3747
|
+
scope: "project:recallnest",
|
|
3748
|
+
includeLatestCheckpoint: false,
|
|
3749
|
+
limitPerSection: 3,
|
|
3750
|
+
});
|
|
3751
|
+
|
|
3752
|
+
expect(response.recentCases).toHaveLength(0);
|
|
3753
|
+
});
|
|
3754
|
+
|
|
3755
|
+
it("suppresses eval-runner maintenance cases for sparse scoped project prompts", async () => {
|
|
3756
|
+
const retriever = {
|
|
3757
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
3758
|
+
if (context.category !== "cases") return [];
|
|
3759
|
+
return [
|
|
3760
|
+
withScope(
|
|
3761
|
+
buildResult(
|
|
3762
|
+
"case-eval-runner-shared-components",
|
|
3763
|
+
"cases",
|
|
3764
|
+
"Case: Eval runner shared components skewed later continuity previews Problem: Continuity eval reports could diverge from single-case replay because shared components let earlier retrieval state bleed into later preview composition. Solution: move continuity eval to per-case fresh components and verify against fresh-window replay.",
|
|
3765
|
+
),
|
|
3766
|
+
"project:recallnest",
|
|
3767
|
+
),
|
|
3768
|
+
withScope(
|
|
3769
|
+
buildResult(
|
|
3770
|
+
"case-scope-fallback-cleanup",
|
|
3771
|
+
"cases",
|
|
3772
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: prefer durable cases and patterns.",
|
|
3773
|
+
),
|
|
3774
|
+
"project:recallnest",
|
|
3775
|
+
),
|
|
3776
|
+
];
|
|
3777
|
+
},
|
|
3778
|
+
};
|
|
3779
|
+
|
|
3780
|
+
const response = await composeResumeContext({
|
|
3781
|
+
retriever,
|
|
3782
|
+
checkpointStore: {
|
|
3783
|
+
async getLatest() {
|
|
3784
|
+
return null;
|
|
3785
|
+
},
|
|
3786
|
+
},
|
|
3787
|
+
listPins: () => [],
|
|
3788
|
+
}, {
|
|
3789
|
+
task: "继续我的项目",
|
|
3790
|
+
scope: "project:recallnest",
|
|
3791
|
+
includeLatestCheckpoint: false,
|
|
3792
|
+
limitPerSection: 3,
|
|
3793
|
+
});
|
|
3794
|
+
|
|
3795
|
+
expect(response.recentCases.join(" ")).toContain("RecallNest scope fallback cleanup");
|
|
3796
|
+
expect(response.recentCases.join(" ")).not.toContain("shared components skewed later continuity previews");
|
|
3797
|
+
expect(response.recentCases.join(" ")).not.toContain("single-case replay");
|
|
3798
|
+
expect(response.recentCases.join(" ")).not.toContain("per-case fresh components");
|
|
3799
|
+
});
|
|
3800
|
+
|
|
3801
|
+
it("allows eval-runner maintenance cases for explicit eval-runner debugging tasks", async () => {
|
|
3802
|
+
const retriever = {
|
|
3803
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
3804
|
+
if (context.category !== "cases") return [];
|
|
3805
|
+
return [
|
|
3806
|
+
withScope(
|
|
3807
|
+
buildResult(
|
|
3808
|
+
"case-eval-runner-shared-components",
|
|
3809
|
+
"cases",
|
|
3810
|
+
"Case: Eval runner shared components skewed later continuity previews Problem: Continuity eval reports could diverge from single-case replay because shared components let earlier retrieval state bleed into later preview composition. Solution: move continuity eval to per-case fresh components and verify against fresh-window replay.",
|
|
3811
|
+
),
|
|
3812
|
+
"project:recallnest",
|
|
3813
|
+
),
|
|
3814
|
+
];
|
|
3815
|
+
},
|
|
3816
|
+
};
|
|
3817
|
+
|
|
3818
|
+
const response = await composeResumeContext({
|
|
3819
|
+
retriever,
|
|
3820
|
+
checkpointStore: {
|
|
3821
|
+
async getLatest() {
|
|
3822
|
+
return null;
|
|
3823
|
+
},
|
|
3824
|
+
},
|
|
3825
|
+
listPins: () => [],
|
|
3826
|
+
}, {
|
|
3827
|
+
task: "继续排查 eval runner isolation 的 single-case replay 差异",
|
|
3828
|
+
scope: "project:recallnest",
|
|
3829
|
+
includeLatestCheckpoint: false,
|
|
3830
|
+
limitPerSection: 3,
|
|
3831
|
+
});
|
|
3832
|
+
|
|
3833
|
+
expect(response.recentCases.join(" ")).toContain("shared components skewed later continuity previews");
|
|
3834
|
+
expect(response.recentCases.join(" ")).toContain("single-case replay");
|
|
3835
|
+
expect(response.recentCases).toHaveLength(1);
|
|
3836
|
+
});
|
|
3837
|
+
|
|
3838
|
+
it("suppresses eval-runner maintenance cases for adjacent component refactor tasks", async () => {
|
|
3839
|
+
const retriever = {
|
|
3840
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
3841
|
+
if (context.category !== "cases") return [];
|
|
3842
|
+
return [
|
|
3843
|
+
withScope(
|
|
3844
|
+
buildResult(
|
|
3845
|
+
"case-eval-runner-shared-components",
|
|
3846
|
+
"cases",
|
|
3847
|
+
"Case: Eval runner shared components skewed later continuity previews Problem: Continuity eval reports could diverge from single-case replay because shared components let earlier retrieval state bleed into later preview composition. Solution: move continuity eval to per-case fresh components and verify against fresh-window replay.",
|
|
3848
|
+
),
|
|
3849
|
+
"project:recallnest",
|
|
3850
|
+
),
|
|
3851
|
+
];
|
|
3852
|
+
},
|
|
3853
|
+
};
|
|
3854
|
+
|
|
3855
|
+
const response = await composeResumeContext({
|
|
3856
|
+
retriever,
|
|
3857
|
+
checkpointStore: {
|
|
3858
|
+
async getLatest() {
|
|
3859
|
+
return null;
|
|
3860
|
+
},
|
|
3861
|
+
},
|
|
3862
|
+
listPins: () => [],
|
|
3863
|
+
}, {
|
|
3864
|
+
task: "继续拆 context-composer 组件边界",
|
|
3865
|
+
scope: "project:recallnest",
|
|
3866
|
+
includeLatestCheckpoint: false,
|
|
3867
|
+
limitPerSection: 3,
|
|
3868
|
+
});
|
|
3869
|
+
|
|
3870
|
+
expect(response.recentCases).toHaveLength(0);
|
|
3871
|
+
});
|
|
3872
|
+
|
|
3873
|
+
it("falls back to a broader case query when direct case recall is empty or noisy", async () => {
|
|
3874
|
+
const calls: RetrievalContext[] = [];
|
|
3875
|
+
const retriever = {
|
|
3876
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
3877
|
+
calls.push(context);
|
|
3878
|
+
if (context.category !== "cases") return [];
|
|
3879
|
+
if (context.query.includes("similar solved case previous fix")) {
|
|
3880
|
+
return [
|
|
3881
|
+
withScope(
|
|
3882
|
+
buildResult(
|
|
3883
|
+
"case-noise",
|
|
3884
|
+
"cases",
|
|
3885
|
+
"[用户] 笑不活了,怎么https://github.com/AliceLJY/recallnest/issues 我的还在,解决了帮我关闭啊。。。",
|
|
3886
|
+
),
|
|
3887
|
+
"cc:14c6e6d9",
|
|
3888
|
+
),
|
|
3889
|
+
];
|
|
3890
|
+
}
|
|
3891
|
+
if (
|
|
3892
|
+
context.query.includes("scope fallback") &&
|
|
3893
|
+
context.query.includes("project scope") &&
|
|
3894
|
+
context.query.includes("handoff")
|
|
3895
|
+
) {
|
|
3896
|
+
return [
|
|
3897
|
+
withScope(
|
|
3898
|
+
buildResult(
|
|
3899
|
+
"case-durable",
|
|
3900
|
+
"cases",
|
|
3901
|
+
"Case: RecallNest sparse startup context cleanup Problem: resume_context returned noisy transcript fragments and unrelated memories instead of a clean project handoff. Solution: Filter low-signal transcripts and backfill stable context from checkpoint decisions.",
|
|
3902
|
+
),
|
|
3903
|
+
"memory:agent",
|
|
3904
|
+
),
|
|
3905
|
+
];
|
|
3906
|
+
}
|
|
3907
|
+
return [];
|
|
3908
|
+
},
|
|
3909
|
+
};
|
|
3910
|
+
|
|
3911
|
+
const response = await composeResumeContext({
|
|
3912
|
+
retriever,
|
|
3913
|
+
checkpointStore: {
|
|
3914
|
+
async getLatest() {
|
|
3915
|
+
return null;
|
|
3916
|
+
},
|
|
3917
|
+
},
|
|
3918
|
+
listPins: () => [],
|
|
3919
|
+
}, {
|
|
3920
|
+
task: "一起继续排查 RecallNest 的连续性问题",
|
|
3921
|
+
includeLatestCheckpoint: false,
|
|
3922
|
+
limitPerSection: 2,
|
|
3923
|
+
});
|
|
3924
|
+
|
|
3925
|
+
expect(response.recentCases).toHaveLength(1);
|
|
3926
|
+
expect(response.recentCases[0]).toContain("Case: RecallNest sparse startup context cleanup");
|
|
3927
|
+
expect(response.recentCases[0]).toContain("resume_context returned noisy transcript fragments");
|
|
3928
|
+
expect(calls.some((call) =>
|
|
3929
|
+
call.category === "cases" &&
|
|
3930
|
+
call.query.includes("scope fallback") &&
|
|
3931
|
+
call.query.includes("project scope") &&
|
|
3932
|
+
call.query.includes("handoff")
|
|
3933
|
+
)).toBe(true);
|
|
3934
|
+
});
|
|
3935
|
+
|
|
3936
|
+
it("uses a scope-handoff flavored fallback query for generic named RecallNest continue tasks", async () => {
|
|
3937
|
+
const calls: RetrievalContext[] = [];
|
|
3938
|
+
const retriever = {
|
|
3939
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
3940
|
+
calls.push(context);
|
|
3941
|
+
if (context.category !== "cases") return [];
|
|
3942
|
+
if (context.query.includes("similar solved case previous fix")) {
|
|
3943
|
+
return [
|
|
3944
|
+
withScope(
|
|
3945
|
+
buildResult(
|
|
3946
|
+
"case-sparse-validation",
|
|
3947
|
+
"cases",
|
|
3948
|
+
"Case: Three-terminal continuity trigger validation Problem: Claude Code, Codex, and Gemini CLI were configured with MCP but only continue-style prompts triggered recall reliably.",
|
|
3949
|
+
),
|
|
3950
|
+
"recallnest",
|
|
3951
|
+
),
|
|
3952
|
+
];
|
|
3953
|
+
}
|
|
3954
|
+
if (
|
|
3955
|
+
context.query.includes("scope fallback") &&
|
|
3956
|
+
context.query.includes("project scope") &&
|
|
3957
|
+
context.query.includes("handoff")
|
|
3958
|
+
) {
|
|
3959
|
+
return [
|
|
3960
|
+
withScope(
|
|
3961
|
+
buildResult(
|
|
3962
|
+
"case-fallback-cleanup",
|
|
3963
|
+
"cases",
|
|
3964
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: prefer durable cases and patterns.",
|
|
3965
|
+
),
|
|
3966
|
+
"recallnest",
|
|
3967
|
+
),
|
|
3968
|
+
];
|
|
3969
|
+
}
|
|
3970
|
+
if (context.query.includes("root cause workaround cleanup")) {
|
|
3971
|
+
return [
|
|
3972
|
+
withScope(
|
|
3973
|
+
buildResult(
|
|
3974
|
+
"case-a2a-noise",
|
|
3975
|
+
"cases",
|
|
3976
|
+
"Case: Borderline transcript dedup swallowed incremental A2A details Problem: Transcript ingest was dropping same-topic A2A upgrade chunks that added new implementation detail.",
|
|
3977
|
+
),
|
|
3978
|
+
"project:recallnest",
|
|
3979
|
+
),
|
|
3980
|
+
];
|
|
3981
|
+
}
|
|
3982
|
+
return [];
|
|
3983
|
+
},
|
|
3984
|
+
};
|
|
3985
|
+
|
|
3986
|
+
const response = await composeResumeContext({
|
|
3987
|
+
retriever,
|
|
3988
|
+
checkpointStore: {
|
|
3989
|
+
async getLatest() {
|
|
3990
|
+
return null;
|
|
3991
|
+
},
|
|
3992
|
+
},
|
|
3993
|
+
listPins: () => [],
|
|
3994
|
+
}, {
|
|
3995
|
+
task: "继续整理 RecallNest 实施清单,不要让我重复前情",
|
|
3996
|
+
includeLatestCheckpoint: false,
|
|
3997
|
+
limitPerSection: 3,
|
|
3998
|
+
});
|
|
3999
|
+
|
|
4000
|
+
expect(response.recentCases.join(" ")).toContain("RecallNest scope fallback cleanup");
|
|
4001
|
+
expect(response.recentCases.join(" ")).not.toContain("incremental A2A details");
|
|
4002
|
+
expect(calls.some((call) =>
|
|
4003
|
+
call.category === "cases" &&
|
|
4004
|
+
call.query.includes("scope fallback") &&
|
|
4005
|
+
call.query.includes("project scope") &&
|
|
4006
|
+
call.query.includes("handoff")
|
|
4007
|
+
)).toBe(true);
|
|
4008
|
+
});
|
|
4009
|
+
|
|
4010
|
+
it("widens the case fallback candidate pool so generic RecallNest continues can still recover cleanup cases", async () => {
|
|
4011
|
+
const calls: RetrievalContext[] = [];
|
|
4012
|
+
const retriever = {
|
|
4013
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4014
|
+
calls.push(context);
|
|
4015
|
+
if (context.category !== "cases") return [];
|
|
4016
|
+
if (context.query.includes("similar solved case previous fix")) {
|
|
4017
|
+
return [
|
|
4018
|
+
withScope(
|
|
4019
|
+
buildResult(
|
|
4020
|
+
"case-direct-note",
|
|
4021
|
+
"cases",
|
|
4022
|
+
"[助手] 我先看 case fallback 能不能把 RecallNest continuity 的 recentCases 补起来。",
|
|
4023
|
+
),
|
|
4024
|
+
"codex:working-note",
|
|
4025
|
+
),
|
|
4026
|
+
];
|
|
4027
|
+
}
|
|
4028
|
+
if (
|
|
4029
|
+
context.query.includes("scope fallback") &&
|
|
4030
|
+
context.query.includes("project scope") &&
|
|
4031
|
+
context.query.includes("handoff")
|
|
4032
|
+
) {
|
|
4033
|
+
const noisyResults = [
|
|
4034
|
+
withScope(
|
|
4035
|
+
buildResult(
|
|
4036
|
+
"case-noisy-meta",
|
|
4037
|
+
"cases",
|
|
4038
|
+
"Case: Fallback-query meta case leaked into generic RecallNest previews Problem: Generic status prompts surfaced fallback-query cleanup notes instead of cleanup cases. Solution: keep query-analysis notes behind explicit ranking cues.",
|
|
4039
|
+
),
|
|
4040
|
+
"project:recallnest",
|
|
4041
|
+
),
|
|
4042
|
+
withScope(
|
|
4043
|
+
buildResult(
|
|
4044
|
+
"case-noisy-pins",
|
|
4045
|
+
"cases",
|
|
4046
|
+
"Case: Scoped recall leaked conversational durable pins Problem: Scoped resume_context could still surface raw bridge transcript pins after they had been pinned into durable memory.",
|
|
4047
|
+
),
|
|
4048
|
+
"project:recallnest",
|
|
4049
|
+
),
|
|
4050
|
+
];
|
|
4051
|
+
if (context.limit < 8) return noisyResults;
|
|
4052
|
+
return [
|
|
4053
|
+
...noisyResults,
|
|
4054
|
+
withScope(
|
|
4055
|
+
buildResult(
|
|
4056
|
+
"case-fallback-cleanup-wide-pool",
|
|
4057
|
+
"cases",
|
|
4058
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: prefer durable cases and patterns.",
|
|
4059
|
+
),
|
|
4060
|
+
"recallnest",
|
|
4061
|
+
),
|
|
4062
|
+
];
|
|
4063
|
+
}
|
|
4064
|
+
return [];
|
|
4065
|
+
},
|
|
4066
|
+
};
|
|
4067
|
+
|
|
4068
|
+
const response = await composeResumeContext({
|
|
4069
|
+
retriever,
|
|
4070
|
+
checkpointStore: {
|
|
4071
|
+
async getLatest() {
|
|
4072
|
+
return null;
|
|
4073
|
+
},
|
|
4074
|
+
},
|
|
4075
|
+
listPins: () => [],
|
|
4076
|
+
}, {
|
|
4077
|
+
task: "回到 RecallNest 继续做",
|
|
4078
|
+
includeLatestCheckpoint: false,
|
|
4079
|
+
limitPerSection: 3,
|
|
4080
|
+
});
|
|
4081
|
+
|
|
4082
|
+
expect(response.recentCases.join(" ")).toContain("RecallNest scope fallback cleanup");
|
|
4083
|
+
expect(calls.some((call) =>
|
|
4084
|
+
call.category === "cases" &&
|
|
4085
|
+
call.query.includes("scope fallback") &&
|
|
4086
|
+
call.limit >= 8
|
|
4087
|
+
)).toBe(true);
|
|
4088
|
+
});
|
|
4089
|
+
|
|
4090
|
+
it("backfills continuity guidance for conversational named RecallNest project continues", async () => {
|
|
4091
|
+
const retriever = {
|
|
4092
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4093
|
+
if (context.category === "entities") {
|
|
4094
|
+
return [
|
|
4095
|
+
withScope(
|
|
4096
|
+
buildResult(
|
|
4097
|
+
"entity-recallnest-conversational",
|
|
4098
|
+
"entities",
|
|
4099
|
+
"RecallNest continuity revolves around three primitives: store_memory, checkpoint_session, and resume_context.",
|
|
4100
|
+
),
|
|
4101
|
+
"project:recallnest",
|
|
4102
|
+
),
|
|
4103
|
+
];
|
|
4104
|
+
}
|
|
4105
|
+
if (
|
|
4106
|
+
context.category === "cases" &&
|
|
4107
|
+
context.query.includes("scope fallback") &&
|
|
4108
|
+
context.query.includes("project scope") &&
|
|
4109
|
+
context.query.includes("handoff")
|
|
4110
|
+
) {
|
|
4111
|
+
return [
|
|
4112
|
+
withScope(
|
|
4113
|
+
buildResult(
|
|
4114
|
+
"case-fallback-conversational",
|
|
4115
|
+
"cases",
|
|
4116
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: prefer durable cases and patterns.",
|
|
4117
|
+
),
|
|
4118
|
+
"project:recallnest",
|
|
4119
|
+
),
|
|
4120
|
+
];
|
|
4121
|
+
}
|
|
4122
|
+
return [];
|
|
4123
|
+
},
|
|
4124
|
+
};
|
|
4125
|
+
|
|
4126
|
+
const response = await composeResumeContext({
|
|
4127
|
+
retriever,
|
|
4128
|
+
checkpointStore: {
|
|
4129
|
+
async getLatest() {
|
|
4130
|
+
return null;
|
|
4131
|
+
},
|
|
4132
|
+
},
|
|
4133
|
+
listPins: () => [],
|
|
4134
|
+
}, {
|
|
4135
|
+
task: "接着做 RecallNest 这个项目",
|
|
4136
|
+
includeLatestCheckpoint: false,
|
|
4137
|
+
limitPerSection: 3,
|
|
4138
|
+
});
|
|
4139
|
+
|
|
4140
|
+
expect(response.relevantPatterns.join(" ")).toContain("resume_context");
|
|
4141
|
+
expect(response.relevantPatterns.join(" ")).toContain("search_memory");
|
|
4142
|
+
expect(response.relevantPatterns.join(" ")).toContain("checkpoint_session");
|
|
4143
|
+
expect(response.recentCases.join(" ")).toContain("RecallNest scope fallback cleanup");
|
|
4144
|
+
});
|
|
4145
|
+
|
|
4146
|
+
it("backfills continuity guidance for named RecallNest continues without project nouns", async () => {
|
|
4147
|
+
const retriever = {
|
|
4148
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4149
|
+
if (context.category === "entities") {
|
|
4150
|
+
return [
|
|
4151
|
+
withScope(
|
|
4152
|
+
buildResult(
|
|
4153
|
+
"entity-recallnest-named-no-project-noun",
|
|
4154
|
+
"entities",
|
|
4155
|
+
"RecallNest continuity revolves around three primitives: store_memory, checkpoint_session, and resume_context.",
|
|
4156
|
+
),
|
|
4157
|
+
"project:recallnest",
|
|
4158
|
+
),
|
|
4159
|
+
];
|
|
4160
|
+
}
|
|
4161
|
+
if (
|
|
4162
|
+
context.category === "cases" &&
|
|
4163
|
+
context.query.includes("scope fallback") &&
|
|
4164
|
+
context.query.includes("project scope") &&
|
|
4165
|
+
context.query.includes("handoff")
|
|
4166
|
+
) {
|
|
4167
|
+
return [
|
|
4168
|
+
withScope(
|
|
4169
|
+
buildResult(
|
|
4170
|
+
"case-fallback-named-no-project-noun",
|
|
4171
|
+
"cases",
|
|
4172
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: prefer durable cases and patterns.",
|
|
4173
|
+
),
|
|
4174
|
+
"project:recallnest",
|
|
4175
|
+
),
|
|
4176
|
+
];
|
|
4177
|
+
}
|
|
4178
|
+
return [];
|
|
4179
|
+
},
|
|
4180
|
+
};
|
|
4181
|
+
|
|
4182
|
+
const response = await composeResumeContext({
|
|
4183
|
+
retriever,
|
|
4184
|
+
checkpointStore: {
|
|
4185
|
+
async getLatest() {
|
|
4186
|
+
return null;
|
|
4187
|
+
},
|
|
4188
|
+
},
|
|
4189
|
+
listPins: () => [],
|
|
4190
|
+
}, {
|
|
4191
|
+
task: "回到 RecallNest 继续做",
|
|
4192
|
+
includeLatestCheckpoint: false,
|
|
4193
|
+
limitPerSection: 3,
|
|
4194
|
+
});
|
|
4195
|
+
|
|
4196
|
+
expect(response.relevantPatterns.join(" ")).toContain("resume_context");
|
|
4197
|
+
expect(response.relevantPatterns.join(" ")).toContain("search_memory");
|
|
4198
|
+
expect(response.relevantPatterns.join(" ")).toContain("checkpoint_session");
|
|
4199
|
+
expect(response.recentCases.join(" ")).toContain("RecallNest scope fallback cleanup");
|
|
4200
|
+
});
|
|
4201
|
+
|
|
4202
|
+
it("suppresses same-project cue-coverage maintenance cases for generic RecallNest continues", async () => {
|
|
4203
|
+
const retriever = {
|
|
4204
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4205
|
+
if (context.category === "entities") {
|
|
4206
|
+
return [
|
|
4207
|
+
withScope(
|
|
4208
|
+
buildResult(
|
|
4209
|
+
"entity-recallnest-generic-cue-coverage",
|
|
4210
|
+
"entities",
|
|
4211
|
+
"RecallNest continuity revolves around three primitives: store_memory, checkpoint_session, and resume_context.",
|
|
4212
|
+
),
|
|
4213
|
+
"project:recallnest",
|
|
4214
|
+
),
|
|
4215
|
+
];
|
|
4216
|
+
}
|
|
4217
|
+
if (context.category !== "cases") return [];
|
|
4218
|
+
return [
|
|
4219
|
+
withScope(
|
|
4220
|
+
buildResult(
|
|
4221
|
+
"case-recallnest-cue-coverage",
|
|
4222
|
+
"cases",
|
|
4223
|
+
"Case: Conversational named RecallNest continue prompts missed continuity guidance Problem: Conversational named RecallNest continue prompts like `接着做 RecallNest 这个项目` recovered entity/case context but missed built-in guidance. Solution: broaden cue coverage and add a regression.",
|
|
4224
|
+
),
|
|
4225
|
+
"project:recallnest",
|
|
4226
|
+
),
|
|
4227
|
+
withScope(
|
|
4228
|
+
buildResult(
|
|
4229
|
+
"case-recallnest-scope-fallback-generic-cue-coverage",
|
|
4230
|
+
"cases",
|
|
4231
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: prefer durable cases and patterns.",
|
|
4232
|
+
),
|
|
4233
|
+
"project:recallnest",
|
|
4234
|
+
),
|
|
4235
|
+
];
|
|
4236
|
+
},
|
|
4237
|
+
};
|
|
4238
|
+
|
|
4239
|
+
const response = await composeResumeContext({
|
|
4240
|
+
retriever,
|
|
4241
|
+
checkpointStore: {
|
|
4242
|
+
async getLatest() {
|
|
4243
|
+
return null;
|
|
4244
|
+
},
|
|
4245
|
+
},
|
|
4246
|
+
listPins: () => [],
|
|
4247
|
+
}, {
|
|
4248
|
+
task: "回到 RecallNest 继续做",
|
|
4249
|
+
includeLatestCheckpoint: false,
|
|
4250
|
+
limitPerSection: 3,
|
|
4251
|
+
});
|
|
4252
|
+
|
|
4253
|
+
expect(response.recentCases.join(" ")).toContain("RecallNest scope fallback cleanup");
|
|
4254
|
+
expect(response.recentCases.join(" ")).not.toContain("missed continuity guidance");
|
|
4255
|
+
});
|
|
4256
|
+
|
|
4257
|
+
it("suppresses fallback-query meta maintenance cases for generic RecallNest status prompts", async () => {
|
|
4258
|
+
const retriever = {
|
|
4259
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4260
|
+
if (context.category !== "cases") return [];
|
|
4261
|
+
return [
|
|
4262
|
+
withScope(
|
|
4263
|
+
buildResult(
|
|
4264
|
+
"case-fallback-query-meta",
|
|
4265
|
+
"cases",
|
|
4266
|
+
"Case: Broad case fallback query surfaced query-analysis assistant notes Problem: A broad case fallback query started surfacing query-analysis assistant notes as recentCases. Solution: tighten structured case detection and keep non-durable assistant notes out.",
|
|
4267
|
+
),
|
|
4268
|
+
"project:recallnest",
|
|
4269
|
+
),
|
|
4270
|
+
withScope(
|
|
4271
|
+
buildResult(
|
|
4272
|
+
"case-recallnest-scope-fallback-status",
|
|
4273
|
+
"cases",
|
|
4274
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: prefer durable cases and patterns.",
|
|
4275
|
+
),
|
|
4276
|
+
"project:recallnest",
|
|
4277
|
+
),
|
|
4278
|
+
];
|
|
4279
|
+
},
|
|
4280
|
+
};
|
|
4281
|
+
|
|
4282
|
+
const response = await composeResumeContext({
|
|
4283
|
+
retriever,
|
|
4284
|
+
checkpointStore: {
|
|
4285
|
+
async getLatest() {
|
|
4286
|
+
return null;
|
|
4287
|
+
},
|
|
4288
|
+
},
|
|
4289
|
+
listPins: () => [],
|
|
4290
|
+
}, {
|
|
4291
|
+
task: "RecallNest 刚才做到哪了",
|
|
4292
|
+
includeLatestCheckpoint: false,
|
|
4293
|
+
limitPerSection: 3,
|
|
4294
|
+
});
|
|
4295
|
+
|
|
4296
|
+
expect(response.recentCases.join(" ")).toContain("RecallNest scope fallback cleanup");
|
|
4297
|
+
expect(response.recentCases.join(" ")).not.toContain("fallback query");
|
|
4298
|
+
expect(response.recentCases.join(" ")).not.toContain("query-analysis assistant notes");
|
|
4299
|
+
});
|
|
4300
|
+
|
|
4301
|
+
it("suppresses same-project A2A dedup maintenance cases for generic RecallNest project continues", async () => {
|
|
4302
|
+
const retriever = {
|
|
4303
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4304
|
+
if (context.category !== "cases") return [];
|
|
4305
|
+
return [
|
|
4306
|
+
withScope(
|
|
4307
|
+
buildResult(
|
|
4308
|
+
"case-a2a-noise",
|
|
4309
|
+
"cases",
|
|
4310
|
+
"Case: Borderline transcript dedup swallowed incremental A2A details Problem: Transcript ingest was dropping same-topic A2A upgrade chunks that added new implementation detail.",
|
|
4311
|
+
),
|
|
4312
|
+
"project:recallnest",
|
|
4313
|
+
),
|
|
4314
|
+
withScope(
|
|
4315
|
+
buildResult(
|
|
4316
|
+
"case-fallback-cleanup",
|
|
4317
|
+
"cases",
|
|
4318
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: prefer durable cases and patterns.",
|
|
4319
|
+
),
|
|
4320
|
+
"project:recallnest",
|
|
4321
|
+
),
|
|
4322
|
+
];
|
|
4323
|
+
},
|
|
4324
|
+
};
|
|
4325
|
+
|
|
4326
|
+
const response = await composeResumeContext({
|
|
4327
|
+
retriever,
|
|
4328
|
+
checkpointStore: {
|
|
4329
|
+
async getLatest() {
|
|
4330
|
+
return null;
|
|
4331
|
+
},
|
|
4332
|
+
},
|
|
4333
|
+
listPins: () => [],
|
|
4334
|
+
}, {
|
|
4335
|
+
scope: "project:recallnest",
|
|
4336
|
+
task: "继续 RecallNest 项目",
|
|
4337
|
+
includeLatestCheckpoint: false,
|
|
4338
|
+
limitPerSection: 3,
|
|
4339
|
+
});
|
|
4340
|
+
|
|
4341
|
+
expect(response.recentCases.join(" ")).toContain("RecallNest scope fallback cleanup");
|
|
4342
|
+
expect(response.recentCases.join(" ")).not.toContain("incremental A2A details");
|
|
4343
|
+
});
|
|
4344
|
+
|
|
4345
|
+
it("filters plan-like non-durable case notes so broader case fallback can recover durable cases", async () => {
|
|
4346
|
+
const calls: RetrievalContext[] = [];
|
|
4347
|
+
const retriever = {
|
|
4348
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4349
|
+
calls.push(context);
|
|
4350
|
+
if (context.category !== "cases") return [];
|
|
4351
|
+
if (context.query.includes("similar solved case previous fix")) {
|
|
4352
|
+
return [
|
|
4353
|
+
withScope(
|
|
4354
|
+
buildResult(
|
|
4355
|
+
"case-note",
|
|
4356
|
+
"cases",
|
|
4357
|
+
"我先看真实召回密度,确认问题是不是出在 scope 太窄,再决定怎么修复。",
|
|
4358
|
+
),
|
|
4359
|
+
"cc:working-note",
|
|
4360
|
+
),
|
|
4361
|
+
];
|
|
4362
|
+
}
|
|
4363
|
+
if (
|
|
4364
|
+
context.query.includes("scope fallback") &&
|
|
4365
|
+
context.query.includes("project scope") &&
|
|
4366
|
+
context.query.includes("handoff")
|
|
4367
|
+
) {
|
|
4368
|
+
return [
|
|
4369
|
+
buildResult(
|
|
4370
|
+
"case-fallback",
|
|
4371
|
+
"cases",
|
|
4372
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: Reject plan-like transcript snippets and fall back to durable case memories.",
|
|
4373
|
+
),
|
|
4374
|
+
];
|
|
4375
|
+
}
|
|
4376
|
+
return [];
|
|
4377
|
+
},
|
|
4378
|
+
};
|
|
4379
|
+
|
|
4380
|
+
const response = await composeResumeContext({
|
|
4381
|
+
retriever,
|
|
4382
|
+
checkpointStore: {
|
|
4383
|
+
async getLatest() {
|
|
4384
|
+
return null;
|
|
4385
|
+
},
|
|
4386
|
+
},
|
|
4387
|
+
listPins: () => [],
|
|
4388
|
+
}, {
|
|
4389
|
+
task: "一起继续排查 RecallNest 的连续性问题",
|
|
4390
|
+
includeLatestCheckpoint: false,
|
|
4391
|
+
limitPerSection: 2,
|
|
4392
|
+
});
|
|
4393
|
+
|
|
4394
|
+
expect(response.recentCases).toHaveLength(1);
|
|
4395
|
+
expect(response.recentCases[0]).toContain("Case: RecallNest scope fallback cleanup");
|
|
4396
|
+
expect(response.recentCases[0]).toContain("Reject plan-like transcript snippets");
|
|
4397
|
+
expect(response.recentCases.join(" ")).not.toContain("我先看真实召回密度");
|
|
4398
|
+
expect(calls.some((call) =>
|
|
4399
|
+
call.category === "cases" &&
|
|
4400
|
+
call.query.includes("scope fallback") &&
|
|
4401
|
+
call.query.includes("project scope") &&
|
|
4402
|
+
call.query.includes("handoff")
|
|
4403
|
+
)).toBe(true);
|
|
4404
|
+
});
|
|
4405
|
+
|
|
4406
|
+
it("suppresses transcript-fragment meta cases so generic RecallNest continues keep cleanup cases", async () => {
|
|
4407
|
+
const retriever = {
|
|
4408
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4409
|
+
if (context.category !== "cases") return [];
|
|
4410
|
+
return [
|
|
4411
|
+
withScope(
|
|
4412
|
+
buildResult(
|
|
4413
|
+
"case-transcript-fragment-meta",
|
|
4414
|
+
"cases",
|
|
4415
|
+
"Case: Transcript-style pattern fragment leaked into generic continuity preview Problem: Generic RecallNest continue prompts could surface non-durable transcript fragments in relevantPatterns when the fragment mentioned workflow cues like resume_context and search_memory.",
|
|
4416
|
+
),
|
|
4417
|
+
"project:recallnest",
|
|
4418
|
+
),
|
|
4419
|
+
withScope(
|
|
4420
|
+
buildResult(
|
|
4421
|
+
"case-scope-fallback-cleanup",
|
|
4422
|
+
"cases",
|
|
4423
|
+
"Case: RecallNest scope fallback cleanup Problem: resume_context stayed too narrow on project scope and surfaced ongoing notes instead of stable handoff context. Solution: prefer durable cases and patterns.",
|
|
4424
|
+
),
|
|
4425
|
+
"recallnest",
|
|
4426
|
+
),
|
|
4427
|
+
withScope(
|
|
4428
|
+
buildResult(
|
|
4429
|
+
"case-sparse-startup-cleanup",
|
|
4430
|
+
"cases",
|
|
4431
|
+
"Case: RecallNest sparse startup context cleanup Problem: resume_context was returning noisy transcript fragments and unrelated memories instead of a clean RecallNest continuity handoff. Solution: filter low-signal transcripts and backfill stable context from checkpoint decisions.",
|
|
4432
|
+
),
|
|
4433
|
+
"recallnest",
|
|
4434
|
+
),
|
|
4435
|
+
];
|
|
4436
|
+
},
|
|
4437
|
+
};
|
|
4438
|
+
|
|
4439
|
+
const response = await composeResumeContext({
|
|
4440
|
+
retriever,
|
|
4441
|
+
checkpointStore: {
|
|
4442
|
+
async getLatest() {
|
|
4443
|
+
return null;
|
|
4444
|
+
},
|
|
4445
|
+
},
|
|
4446
|
+
listPins: () => [],
|
|
4447
|
+
}, {
|
|
4448
|
+
scope: "project:recallnest",
|
|
4449
|
+
task: "回到 RecallNest 继续做",
|
|
4450
|
+
includeLatestCheckpoint: false,
|
|
4451
|
+
limitPerSection: 3,
|
|
4452
|
+
});
|
|
4453
|
+
|
|
4454
|
+
expect(response.recentCases.join(" ")).toContain("RecallNest scope fallback cleanup");
|
|
4455
|
+
expect(response.recentCases.join(" ")).toContain("RecallNest sparse startup context cleanup");
|
|
4456
|
+
expect(response.recentCases.join(" ")).not.toContain("Transcript-style pattern fragment leaked");
|
|
4457
|
+
expect(response.recentCases.join(" ")).not.toContain("relevantPatterns");
|
|
4458
|
+
});
|
|
4459
|
+
|
|
4460
|
+
it("suppresses distillation workflow patterns for generic RecallNest continues", async () => {
|
|
4461
|
+
const retriever = {
|
|
4462
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4463
|
+
if (context.category === "patterns") {
|
|
4464
|
+
return [
|
|
4465
|
+
buildResult(
|
|
4466
|
+
"pattern-scoped-continuity-minimal",
|
|
4467
|
+
"patterns",
|
|
4468
|
+
"Workflow pattern: Scoped project continuity recall Tools: resume_context Use when: When continuing a named project that already has a shared scope or project key Steps: 1. Call resume_context with the project scope when it is known.",
|
|
4469
|
+
),
|
|
4470
|
+
];
|
|
4471
|
+
}
|
|
4472
|
+
if (
|
|
4473
|
+
!context.category &&
|
|
4474
|
+
context.query.includes("search_memory") &&
|
|
4475
|
+
context.query.includes("checkpoint_session") &&
|
|
4476
|
+
context.query.includes("workflow pattern steps")
|
|
4477
|
+
) {
|
|
4478
|
+
return [
|
|
4479
|
+
buildResult(
|
|
4480
|
+
"pattern-distillation-workflow",
|
|
4481
|
+
"patterns",
|
|
4482
|
+
"Workflow pattern: Bulk Fact Distillation With Checkpointing Tools: bun, scripts/distill-facts.ts, health-check.ts Use when: When running or resuming large fact distillation jobs across many scopes. Steps: 1. Use scripts/distill-facts.ts with smartExtractBatch on qwen-turbo for batch extraction.",
|
|
4483
|
+
),
|
|
4484
|
+
buildResult(
|
|
4485
|
+
"pattern-cross-window-handoff",
|
|
4486
|
+
"patterns",
|
|
4487
|
+
"Workflow pattern: Cross-window continuity handoff Tools: resume_context, latest_checkpoint Use when: When opening a fresh terminal window for the same project or after context loss Steps: 1. Call resume_context before coding.",
|
|
4488
|
+
),
|
|
4489
|
+
buildResult(
|
|
4490
|
+
"pattern-recall-before-repo",
|
|
4491
|
+
"patterns",
|
|
4492
|
+
"Workflow pattern: Recall before repo exploration Tools: resume_context, search_memory Use when: When a fresh window continues an existing project and startup context still looks sparse Steps: 1. Call resume_context before repo exploration. 2. Run search_memory with the project name and task nouns.",
|
|
4493
|
+
),
|
|
4494
|
+
];
|
|
4495
|
+
}
|
|
4496
|
+
return [];
|
|
4497
|
+
},
|
|
4498
|
+
};
|
|
4499
|
+
|
|
4500
|
+
const response = await composeResumeContext({
|
|
4501
|
+
retriever,
|
|
4502
|
+
checkpointStore: {
|
|
4503
|
+
async getLatest() {
|
|
4504
|
+
return null;
|
|
4505
|
+
},
|
|
4506
|
+
},
|
|
4507
|
+
listPins: () => [],
|
|
4508
|
+
}, {
|
|
4509
|
+
scope: "project:recallnest",
|
|
4510
|
+
task: "继续 RecallNest",
|
|
4511
|
+
includeLatestCheckpoint: false,
|
|
4512
|
+
limitPerSection: 3,
|
|
4513
|
+
});
|
|
4514
|
+
|
|
4515
|
+
expect(response.relevantPatterns.join(" ")).toContain("Scoped project continuity recall");
|
|
4516
|
+
expect(response.relevantPatterns.join(" ")).toContain("Cross-window continuity handoff");
|
|
4517
|
+
expect(response.relevantPatterns.join(" ")).toContain("Recall before repo exploration");
|
|
4518
|
+
expect(response.relevantPatterns.join(" ")).not.toContain("Bulk Fact Distillation With Checkpointing");
|
|
4519
|
+
expect(response.relevantPatterns.join(" ")).not.toContain("qwen-turbo");
|
|
4520
|
+
});
|
|
4521
|
+
|
|
4522
|
+
it("filters non-canonical durable case distillations from session scopes for vague unscoped tasks", async () => {
|
|
4523
|
+
const retriever = {
|
|
4524
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4525
|
+
if (context.category !== "cases") return [];
|
|
4526
|
+
return [
|
|
4527
|
+
withMetadata(
|
|
4528
|
+
withScope(
|
|
4529
|
+
buildResult(
|
|
4530
|
+
"case-distilled-summary",
|
|
4531
|
+
"cases",
|
|
4532
|
+
"记录了多个技术问题的修复方案,包括环境变量、SSE 事件缓冲、Session ID 解析等",
|
|
4533
|
+
),
|
|
4534
|
+
"cc:7acc774e",
|
|
4535
|
+
),
|
|
4536
|
+
{
|
|
4537
|
+
boundary: {
|
|
4538
|
+
layer: "durable",
|
|
4539
|
+
authority: "distillation",
|
|
4540
|
+
conflictPolicy: "append-only",
|
|
4541
|
+
originalCategory: "cases",
|
|
4542
|
+
},
|
|
4543
|
+
},
|
|
4544
|
+
),
|
|
4545
|
+
];
|
|
4546
|
+
},
|
|
4547
|
+
};
|
|
4548
|
+
|
|
4549
|
+
const response = await composeResumeContext({
|
|
4550
|
+
retriever,
|
|
4551
|
+
checkpointStore: {
|
|
4552
|
+
async getLatest() {
|
|
4553
|
+
return null;
|
|
4554
|
+
},
|
|
4555
|
+
},
|
|
4556
|
+
listPins: () => [],
|
|
4557
|
+
}, {
|
|
4558
|
+
task: "之前我记得有个关于",
|
|
4559
|
+
includeLatestCheckpoint: false,
|
|
4560
|
+
limitPerSection: 3,
|
|
4561
|
+
});
|
|
4562
|
+
|
|
4563
|
+
expect(response.recentCases).toEqual([]);
|
|
4564
|
+
});
|
|
4565
|
+
|
|
4566
|
+
it.each([
|
|
4567
|
+
"接着上个窗口的写作风格",
|
|
4568
|
+
"继续上个窗口的写作偏好",
|
|
4569
|
+
"resume writing style from last window",
|
|
4570
|
+
"回到上次写作项目 继续调整 tone",
|
|
4571
|
+
])("mixed style+continuity prompt still gets continuity guidance: %s", async (task) => {
|
|
4572
|
+
const retriever = {
|
|
4573
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4574
|
+
if (context.category === "patterns") {
|
|
4575
|
+
return [
|
|
4576
|
+
withMetadata(
|
|
4577
|
+
buildResult(
|
|
4578
|
+
"pattern-recall",
|
|
4579
|
+
"patterns",
|
|
4580
|
+
"Workflow pattern: Recall before repo exploration",
|
|
4581
|
+
),
|
|
4582
|
+
{
|
|
4583
|
+
workflowPattern: {
|
|
4584
|
+
title: "Recall before repo exploration",
|
|
4585
|
+
trigger: "When a fresh window continues an existing project",
|
|
4586
|
+
steps: [
|
|
4587
|
+
"Call resume_context before reading local files.",
|
|
4588
|
+
"Run search_memory with project name and task nouns.",
|
|
4589
|
+
],
|
|
4590
|
+
tools: ["resume_context", "search_memory"],
|
|
4591
|
+
},
|
|
4592
|
+
},
|
|
4593
|
+
),
|
|
4594
|
+
];
|
|
4595
|
+
}
|
|
4596
|
+
return [];
|
|
4597
|
+
},
|
|
4598
|
+
};
|
|
4599
|
+
|
|
4600
|
+
const response = await composeResumeContext({
|
|
4601
|
+
retriever,
|
|
4602
|
+
checkpointStore: { async getLatest() { return null; } },
|
|
4603
|
+
listPins: () => [],
|
|
4604
|
+
}, {
|
|
4605
|
+
task,
|
|
4606
|
+
includeLatestCheckpoint: false,
|
|
4607
|
+
limitPerSection: 3,
|
|
4608
|
+
});
|
|
4609
|
+
|
|
4610
|
+
expect(response.relevantPatterns.length).toBeGreaterThan(0);
|
|
4611
|
+
expect(response.relevantPatterns.join("\n")).toContain("resume_context");
|
|
4612
|
+
});
|
|
4613
|
+
|
|
4614
|
+
it("treats 跨终端记忆/记忆功能 shorthand as associative RecallNest cue", async () => {
|
|
4615
|
+
const calls: RetrievalContext[] = [];
|
|
4616
|
+
const retriever = {
|
|
4617
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4618
|
+
calls.push(context);
|
|
4619
|
+
if (context.category === "entities" && context.query.includes("checkpoint_session")) {
|
|
4620
|
+
return [
|
|
4621
|
+
buildResult(
|
|
4622
|
+
"entity-cross-terminal-primitives",
|
|
4623
|
+
"entities",
|
|
4624
|
+
"RecallNest continuity revolves around three primitives: store_memory, checkpoint_session, and resume_context.",
|
|
4625
|
+
),
|
|
4626
|
+
];
|
|
4627
|
+
}
|
|
4628
|
+
return [];
|
|
4629
|
+
},
|
|
4630
|
+
};
|
|
4631
|
+
|
|
4632
|
+
const response = await composeResumeContext({
|
|
4633
|
+
retriever,
|
|
4634
|
+
checkpointStore: { async getLatest() { return null; } },
|
|
4635
|
+
listPins: () => [],
|
|
4636
|
+
}, {
|
|
4637
|
+
task: "继续那个跨终端记忆",
|
|
4638
|
+
includeLatestCheckpoint: false,
|
|
4639
|
+
limitPerSection: 2,
|
|
4640
|
+
});
|
|
4641
|
+
|
|
4642
|
+
const stableJoined = response.stableContext.join(" ");
|
|
4643
|
+
expect(stableJoined).toContain("RecallNest");
|
|
4644
|
+
expect(response.relevantPatterns.join(" ")).toContain("resume_context");
|
|
4645
|
+
});
|
|
4646
|
+
|
|
4647
|
+
it("treats 记忆功能 with 上次 as continuity prompt", async () => {
|
|
4648
|
+
const retriever = {
|
|
4649
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4650
|
+
if (context.category === "entities" && context.query.includes("checkpoint_session")) {
|
|
4651
|
+
return [
|
|
4652
|
+
buildResult(
|
|
4653
|
+
"entity-memory-function-primitives",
|
|
4654
|
+
"entities",
|
|
4655
|
+
"RecallNest continuity revolves around three primitives: store_memory, checkpoint_session, and resume_context.",
|
|
4656
|
+
),
|
|
4657
|
+
];
|
|
4658
|
+
}
|
|
4659
|
+
return [];
|
|
4660
|
+
},
|
|
4661
|
+
};
|
|
4662
|
+
|
|
4663
|
+
const response = await composeResumeContext({
|
|
4664
|
+
retriever,
|
|
4665
|
+
checkpointStore: { async getLatest() { return null; } },
|
|
4666
|
+
listPins: () => [],
|
|
4667
|
+
}, {
|
|
4668
|
+
task: "上次做的那个记忆功能",
|
|
4669
|
+
includeLatestCheckpoint: false,
|
|
4670
|
+
limitPerSection: 2,
|
|
4671
|
+
});
|
|
4672
|
+
|
|
4673
|
+
const stableJoined = response.stableContext.join(" ");
|
|
4674
|
+
expect(stableJoined).toContain("RecallNest");
|
|
4675
|
+
});
|
|
4676
|
+
|
|
4677
|
+
it("supplements continuity guidance when only noise patterns are retrieved", async () => {
|
|
4678
|
+
const retriever = {
|
|
4679
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4680
|
+
if (context.category === "entities" && context.query.includes("checkpoint_session")) {
|
|
4681
|
+
return [
|
|
4682
|
+
buildResult("entity-rn", "entities", "RecallNest continuity revolves around three primitives."),
|
|
4683
|
+
];
|
|
4684
|
+
}
|
|
4685
|
+
if (context.category === "patterns") {
|
|
4686
|
+
return [
|
|
4687
|
+
buildResult("noise-pattern", "patterns", "从想法到验证再到记忆的创造性工作流程", {
|
|
4688
|
+
scope: "memory:structured-memory",
|
|
4689
|
+
}),
|
|
4690
|
+
];
|
|
4691
|
+
}
|
|
4692
|
+
return [];
|
|
4693
|
+
},
|
|
4694
|
+
};
|
|
4695
|
+
|
|
4696
|
+
const response = await composeResumeContext({
|
|
4697
|
+
retriever,
|
|
4698
|
+
checkpointStore: { async getLatest() { return null; } },
|
|
4699
|
+
listPins: () => [],
|
|
4700
|
+
}, {
|
|
4701
|
+
task: "上次做的那个记忆功能",
|
|
4702
|
+
includeLatestCheckpoint: false,
|
|
4703
|
+
limitPerSection: 3,
|
|
4704
|
+
});
|
|
4705
|
+
|
|
4706
|
+
const patternJoined = response.relevantPatterns.join(" ");
|
|
4707
|
+
// Should have continuity guidance even though a noise pattern was retrieved
|
|
4708
|
+
expect(patternJoined).toContain("resume_context");
|
|
4709
|
+
});
|
|
4710
|
+
|
|
4711
|
+
it("treats 记忆项目 shorthand as a continuity prompt with RecallNest hints", async () => {
|
|
4712
|
+
const calls: RetrievalContext[] = [];
|
|
4713
|
+
const retriever = {
|
|
4714
|
+
async retrieve(context: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4715
|
+
calls.push(context);
|
|
4716
|
+
if (context.category === "entities" && context.query.includes("checkpoint_session")) {
|
|
4717
|
+
return [
|
|
4718
|
+
buildResult(
|
|
4719
|
+
"entity-memory-project-primitives",
|
|
4720
|
+
"entities",
|
|
4721
|
+
"RecallNest continuity revolves around three primitives: store_memory, checkpoint_session, and resume_context.",
|
|
4722
|
+
),
|
|
4723
|
+
];
|
|
4724
|
+
}
|
|
4725
|
+
return [];
|
|
4726
|
+
},
|
|
4727
|
+
};
|
|
4728
|
+
|
|
4729
|
+
const response = await composeResumeContext({
|
|
4730
|
+
retriever,
|
|
4731
|
+
checkpointStore: { async getLatest() { return null; } },
|
|
4732
|
+
listPins: () => [],
|
|
4733
|
+
}, {
|
|
4734
|
+
task: "那个记忆项目,之前做到哪了",
|
|
4735
|
+
includeLatestCheckpoint: false,
|
|
4736
|
+
limitPerSection: 2,
|
|
4737
|
+
});
|
|
4738
|
+
|
|
4739
|
+
const stableJoined = response.stableContext.join(" ");
|
|
4740
|
+
expect(stableJoined).toContain("RecallNest");
|
|
4741
|
+
expect(response.relevantPatterns.join(" ")).toContain("resume_context");
|
|
4742
|
+
});
|
|
4743
|
+
});
|
|
4744
|
+
|
|
4745
|
+
// ---------------------------------------------------------------------------
|
|
4746
|
+
// MP-3: composeLightResumeContext — Ultra-Light Wake-up Mode
|
|
4747
|
+
// ---------------------------------------------------------------------------
|
|
4748
|
+
|
|
4749
|
+
describe("composeLightResumeContext", () => {
|
|
4750
|
+
const checkpoint: SessionCheckpointRecord = {
|
|
4751
|
+
checkpointId: "cp-light-1",
|
|
4752
|
+
sessionId: "session-light",
|
|
4753
|
+
resolvedScope: "project:recallnest",
|
|
4754
|
+
summary: "Implemented LME-9 circuit breaker for LLM degradation",
|
|
4755
|
+
task: "LME-9 circuit breaker",
|
|
4756
|
+
decisions: ["Three-layer degradation"],
|
|
4757
|
+
openLoops: ["Soak test pending"],
|
|
4758
|
+
nextActions: ["Run full eval"],
|
|
4759
|
+
entities: ["RecallNest"],
|
|
4760
|
+
files: ["src/llm-client.ts"],
|
|
4761
|
+
updatedAt: "2026-04-06T10:00:00.000Z",
|
|
4762
|
+
};
|
|
4763
|
+
|
|
4764
|
+
it("returns checkpoint summary and stable memories under token budget", async () => {
|
|
4765
|
+
const retriever = {
|
|
4766
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
4767
|
+
return [
|
|
4768
|
+
buildResult("p1", "profile", "User builds local-first memory systems with RecallNest."),
|
|
4769
|
+
buildResult("pref1", "preferences", "User prefers concise technical replies without fluff."),
|
|
4770
|
+
buildResult("e1", "entities", "RecallNest is a cross-terminal shared memory layer."),
|
|
4771
|
+
];
|
|
4772
|
+
},
|
|
4773
|
+
};
|
|
4774
|
+
|
|
4775
|
+
const result = await composeLightResumeContext({
|
|
4776
|
+
retriever,
|
|
4777
|
+
checkpointStore: { async getLatest() { return checkpoint; } },
|
|
4778
|
+
listPins: () => [],
|
|
4779
|
+
}, {
|
|
4780
|
+
task: "继续 RecallNest 项目",
|
|
4781
|
+
scope: "project:recallnest",
|
|
4782
|
+
includeLatestCheckpoint: true,
|
|
4783
|
+
limitPerSection: 3,
|
|
4784
|
+
});
|
|
4785
|
+
|
|
4786
|
+
expect(result.text).toContain("session-light");
|
|
4787
|
+
expect(result.text).toContain("LME-9");
|
|
4788
|
+
expect(result.text).toContain("resume_context(mode='full')");
|
|
4789
|
+
expect(result.resolvedScope).toBe("project:recallnest");
|
|
4790
|
+
expect(result.generatedAt).toBeTruthy();
|
|
4791
|
+
// Budget check: <300 tokens ≈ <1200 chars
|
|
4792
|
+
expect(result.text.length).toBeLessThan(1200);
|
|
4793
|
+
});
|
|
4794
|
+
|
|
4795
|
+
it("works without checkpoint", async () => {
|
|
4796
|
+
const retriever = {
|
|
4797
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
4798
|
+
return [
|
|
4799
|
+
buildResult("p1", "profile", "Data scientist working on observability."),
|
|
4800
|
+
];
|
|
4801
|
+
},
|
|
4802
|
+
};
|
|
4803
|
+
|
|
4804
|
+
const result = await composeLightResumeContext({
|
|
4805
|
+
retriever,
|
|
4806
|
+
checkpointStore: { async getLatest() { return null; } },
|
|
4807
|
+
listPins: () => [],
|
|
4808
|
+
}, {
|
|
4809
|
+
includeLatestCheckpoint: true,
|
|
4810
|
+
limitPerSection: 3,
|
|
4811
|
+
});
|
|
4812
|
+
|
|
4813
|
+
expect(result.text).not.toContain("Last session");
|
|
4814
|
+
expect(result.text).toContain("observability");
|
|
4815
|
+
expect(result.text).toContain("resume_context(mode='full')");
|
|
4816
|
+
});
|
|
4817
|
+
|
|
4818
|
+
it("includes pin assets as stable context", async () => {
|
|
4819
|
+
const retriever = {
|
|
4820
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
4821
|
+
return [];
|
|
4822
|
+
},
|
|
4823
|
+
};
|
|
4824
|
+
|
|
4825
|
+
const result = await composeLightResumeContext({
|
|
4826
|
+
retriever,
|
|
4827
|
+
checkpointStore: { async getLatest() { return checkpoint; } },
|
|
4828
|
+
listPins: () => [{
|
|
4829
|
+
path: "/tmp/pin1.md",
|
|
4830
|
+
title: "Auth migration decision",
|
|
4831
|
+
summary: "Session tokens must comply with new legal requirements",
|
|
4832
|
+
pinId: "pin-1",
|
|
4833
|
+
memoryId: "mem-1",
|
|
4834
|
+
createdAt: "2026-04-01T00:00:00.000Z",
|
|
4835
|
+
}],
|
|
4836
|
+
}, {
|
|
4837
|
+
scope: "project:recallnest",
|
|
4838
|
+
includeLatestCheckpoint: true,
|
|
4839
|
+
limitPerSection: 3,
|
|
4840
|
+
});
|
|
4841
|
+
|
|
4842
|
+
expect(result.text).toContain("legal requirements");
|
|
4843
|
+
});
|
|
4844
|
+
|
|
4845
|
+
it("deduplicates pin content from stable memories", async () => {
|
|
4846
|
+
const retriever = {
|
|
4847
|
+
async retrieve(): Promise<RetrievalResult[]> {
|
|
4848
|
+
return [
|
|
4849
|
+
buildResult("dup1", "profile", "Session tokens must comply with new legal requirements"),
|
|
4850
|
+
];
|
|
4851
|
+
},
|
|
4852
|
+
};
|
|
4853
|
+
|
|
4854
|
+
const result = await composeLightResumeContext({
|
|
4855
|
+
retriever,
|
|
4856
|
+
checkpointStore: { async getLatest() { return null; } },
|
|
4857
|
+
listPins: () => [{
|
|
4858
|
+
path: "/tmp/pin1.md",
|
|
4859
|
+
title: "Auth migration",
|
|
4860
|
+
summary: "Session tokens must comply with new legal requirements",
|
|
4861
|
+
pinId: "pin-1",
|
|
4862
|
+
memoryId: "mem-1",
|
|
4863
|
+
createdAt: "2026-04-01T00:00:00.000Z",
|
|
4864
|
+
}],
|
|
4865
|
+
}, {
|
|
4866
|
+
includeLatestCheckpoint: true,
|
|
4867
|
+
limitPerSection: 3,
|
|
4868
|
+
});
|
|
4869
|
+
|
|
4870
|
+
// Should not have duplicate entries
|
|
4871
|
+
const matches = result.text.match(/legal requirements/g);
|
|
4872
|
+
expect(matches?.length).toBeLessThanOrEqual(1);
|
|
4873
|
+
});
|
|
4874
|
+
|
|
4875
|
+
it("respects scope from input over checkpoint", async () => {
|
|
4876
|
+
const calls: RetrievalContext[] = [];
|
|
4877
|
+
const retriever = {
|
|
4878
|
+
async retrieve(ctx: RetrievalContext): Promise<RetrievalResult[]> {
|
|
4879
|
+
calls.push(ctx);
|
|
4880
|
+
return [];
|
|
4881
|
+
},
|
|
4882
|
+
};
|
|
4883
|
+
|
|
4884
|
+
const result = await composeLightResumeContext({
|
|
4885
|
+
retriever,
|
|
4886
|
+
checkpointStore: { async getLatest() { return checkpoint; } },
|
|
4887
|
+
listPins: () => [],
|
|
4888
|
+
}, {
|
|
4889
|
+
scope: "project:custom",
|
|
4890
|
+
includeLatestCheckpoint: true,
|
|
4891
|
+
limitPerSection: 3,
|
|
4892
|
+
});
|
|
4893
|
+
|
|
4894
|
+
expect(result.resolvedScope).toBe("project:custom");
|
|
4895
|
+
// retrieveCandidates fires scoped + global in parallel; at least one must have the scope filter
|
|
4896
|
+
expect(calls.some(c => c.scopeFilter?.includes("project:custom"))).toBe(true);
|
|
4897
|
+
});
|
|
4898
|
+
});
|