recallnest 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/.claude-plugin/CLAUDE.md +29 -0
  2. package/.claude-plugin/marketplace.json +18 -0
  3. package/.env.example +3 -0
  4. package/.github/workflows/ci.yml +28 -0
  5. package/.mcp.json +9 -0
  6. package/CHANGELOG.md +51 -0
  7. package/CLAUDE.md +42 -0
  8. package/DEMO.md +94 -0
  9. package/EVAL.md +89 -0
  10. package/FAILURES.md +75 -0
  11. package/LICENSE +21 -0
  12. package/OPERATIONS.md +59 -0
  13. package/README.md +427 -0
  14. package/README_CN.md +425 -0
  15. package/ROADMAP.md +198 -0
  16. package/assets/recallnest-workbench.png +0 -0
  17. package/assets/ui/app.js +807 -0
  18. package/assets/ui/index.html +154 -0
  19. package/assets/ui/styles.css +605 -0
  20. package/assets/wechat-qr.jpg +0 -0
  21. package/config.json.example +38 -0
  22. package/daily-recall.sh +87 -0
  23. package/daily-reflections/2026-03-05.md +15 -0
  24. package/daily-reflections/2026-03-06.md +15 -0
  25. package/daily-reflections/2026-03-07.md +7 -0
  26. package/daily-reflections/2026-03-08.md +34 -0
  27. package/daily-reflections/2026-03-09.md +35 -0
  28. package/daily-reflections/2026-03-10.md +36 -0
  29. package/daily-reflections/2026-03-11.md +35 -0
  30. package/daily-reflections/2026-03-12.md +36 -0
  31. package/daily-reflections/2026-03-13.md +35 -0
  32. package/daily-reflections/2026-03-14.md +35 -0
  33. package/daily-reflections/2026-03-15.md +35 -0
  34. package/daily-reflections/2026-03-16.md +41 -0
  35. package/daily-reflections/2026-03-17.md +10 -0
  36. package/daily-reflections/2026-03-18.md +10 -0
  37. package/daily-reflections/2026-03-19.md +10 -0
  38. package/daily-reflections/2026-03-20.md +10 -0
  39. package/daily-reflections/2026-03-21.md +10 -0
  40. package/daily-reflections/2026-03-22.md +10 -0
  41. package/daily-reflections/2026-03-23.md +10 -0
  42. package/daily-reflections/2026-03-24.md +10 -0
  43. package/daily-reflections/2026-03-25.md +32 -0
  44. package/daily-reflections/2026-03-26.md +37 -0
  45. package/daily-reflections/2026-03-27.md +35 -0
  46. package/daily-reflections/2026-03-28.md +35 -0
  47. package/daily-reflections/2026-03-29.md +35 -0
  48. package/daily-reflections/2026-03-30.md +36 -0
  49. package/daily-reflections/2026-03-31.md +35 -0
  50. package/daily-reflections/2026-04-01.md +35 -0
  51. package/docs/api-reference.md +1291 -0
  52. package/docs/architecture.md +168 -0
  53. package/docs/memory-boundary-contract.md +226 -0
  54. package/docs/memory-categories.md +119 -0
  55. package/docs/pipeline-audit-ultramemory.md +177 -0
  56. package/docs/plans/2026-03-08-query-expansion.md +270 -0
  57. package/docs/plans/2026-03-16-continuity-layer.md +196 -0
  58. package/docs/plans/mcp-tiering-plan.md +126 -0
  59. package/docs/research/recall-quality-audit-2026-03-22.md +153 -0
  60. package/docs/self-evolution.md +198 -0
  61. package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +782 -0
  62. package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +77 -0
  63. package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +95 -0
  64. package/eval/cases.json +85 -0
  65. package/eval/continuity/case-seeds.json +90 -0
  66. package/eval/continuity/cases.json +302 -0
  67. package/eval/continuity/memory-seeds.json +26 -0
  68. package/eval/continuity/pattern-seeds.json +100 -0
  69. package/eval/continuity/results-2026-03-16.md +117 -0
  70. package/eval/continuity/results-2026-03-19.md +453 -0
  71. package/eval/continuity/runbook.md +110 -0
  72. package/eval/continuity/seed-cases.md +54 -0
  73. package/eval/reports/2026-03-06-abstract-query-baseline.md +96 -0
  74. package/eval/reports/2026-03-06-aws-ssh-baseline.md +82 -0
  75. package/eval/reports/2026-03-06-baseline.md +82 -0
  76. package/eval/reports/query-expansion.md +138 -0
  77. package/integrations/README.md +57 -0
  78. package/integrations/claude-code/README.md +133 -0
  79. package/integrations/claude-code/claude-md-snippet.md +31 -0
  80. package/integrations/claude-code/setup.sh +66 -0
  81. package/integrations/claude-code/smoke-continuity.sh +452 -0
  82. package/integrations/codex/README.md +49 -0
  83. package/integrations/codex/agents-md-snippet.md +31 -0
  84. package/integrations/codex/setup.sh +58 -0
  85. package/integrations/examples/claude-agent-sdk/memory-agent.ts +182 -0
  86. package/integrations/examples/langchain/memory-chain.py +111 -0
  87. package/integrations/examples/openai-agents-sdk/memory-agent.py +109 -0
  88. package/integrations/gemini-cli/README.md +57 -0
  89. package/integrations/gemini-cli/gemini-md-snippet.md +31 -0
  90. package/integrations/gemini-cli/setup.sh +64 -0
  91. package/integrations/shared/install-managed-block.sh +51 -0
  92. package/lm +19 -0
  93. package/package.json +50 -0
  94. package/plan-v1.2-distributable.md +105 -0
  95. package/scripts/archive-distilled-junk.ts +116 -0
  96. package/scripts/backfill-l0l1l2.ts +98 -0
  97. package/scripts/blind-scan.ts +588 -0
  98. package/scripts/cleanup-step1-exact-dups.ts +117 -0
  99. package/scripts/cleanup-step2-template-dups.ts +113 -0
  100. package/scripts/cleanup-step3-stale-content.ts +149 -0
  101. package/scripts/cleanup-step4-orphan-scopes.ts +131 -0
  102. package/scripts/delete-distilled-junk.ts +88 -0
  103. package/scripts/distill-facts.ts +305 -0
  104. package/scripts/health-check.ts +343 -0
  105. package/scripts/incremental-ingest.sh +71 -0
  106. package/scripts/mlp-health-check.ts +382 -0
  107. package/scripts/pull-claude-desktop.ts +515 -0
  108. package/scripts/re-ingest-cc.sh +66 -0
  109. package/scripts/rebalance-memory-health.ts +181 -0
  110. package/scripts/run-desktop-ingest.sh +11 -0
  111. package/scripts/start-server.sh +12 -0
  112. package/scripts/viz-dashboard.html +423 -0
  113. package/scripts/viz-export.ts +703 -0
  114. package/src/__tests__/access-tracker-novelty.test.ts +228 -0
  115. package/src/__tests__/auto-consolidation.test.ts +168 -0
  116. package/src/__tests__/auto-recall.test.ts +131 -0
  117. package/src/__tests__/batch-dedup.test.ts +65 -0
  118. package/src/__tests__/capture-engine.test.ts +1142 -0
  119. package/src/__tests__/capture-heuristic.test.ts +171 -0
  120. package/src/__tests__/cascade-forget.test.ts +210 -0
  121. package/src/__tests__/category-min-scores.test.ts +33 -0
  122. package/src/__tests__/confidence-tracker.test.ts +196 -0
  123. package/src/__tests__/conflict-advisor.test.ts +206 -0
  124. package/src/__tests__/conflict-escalation.test.ts +149 -0
  125. package/src/__tests__/conflict-lifecycle.test.ts +83 -0
  126. package/src/__tests__/conflict-output.test.ts +85 -0
  127. package/src/__tests__/conflict-store.test.ts +374 -0
  128. package/src/__tests__/consolidation-engine.test.ts +181 -0
  129. package/src/__tests__/context-composer-pins.test.ts +64 -0
  130. package/src/__tests__/context-composer-task-selection.test.ts +24 -0
  131. package/src/__tests__/context-renderer.test.ts +91 -0
  132. package/src/__tests__/continuity-eval.test.ts +275 -0
  133. package/src/__tests__/continuity-schema.test.ts +97 -0
  134. package/src/__tests__/core-recall-eval.test.ts +235 -0
  135. package/src/__tests__/core-summary.test.ts +109 -0
  136. package/src/__tests__/dedup-actions.test.ts +58 -0
  137. package/src/__tests__/deterministic-id.test.ts +39 -0
  138. package/src/__tests__/doctor.test.ts +303 -0
  139. package/src/__tests__/drill-down.test.ts +108 -0
  140. package/src/__tests__/embedder.test.ts +122 -0
  141. package/src/__tests__/entity-resolver.test.ts +58 -0
  142. package/src/__tests__/event-segmenter.test.ts +153 -0
  143. package/src/__tests__/hotness-blend.test.ts +76 -0
  144. package/src/__tests__/ingest.test.ts +360 -0
  145. package/src/__tests__/kg-extractor.test.ts +184 -0
  146. package/src/__tests__/kg-store.test.ts +198 -0
  147. package/src/__tests__/llm-consolidation.test.ts +226 -0
  148. package/src/__tests__/memory-boundaries.test.ts +237 -0
  149. package/src/__tests__/memory-health-rebalance.test.ts +144 -0
  150. package/src/__tests__/memory-output.test.ts +215 -0
  151. package/src/__tests__/multi-vector.test.ts +242 -0
  152. package/src/__tests__/ppr-traversal.test.ts +256 -0
  153. package/src/__tests__/preference-guard-smoke.test.ts +211 -0
  154. package/src/__tests__/preference-matcher.test.ts +230 -0
  155. package/src/__tests__/preference-slots.test.ts +106 -0
  156. package/src/__tests__/prospective-memory.test.ts +283 -0
  157. package/src/__tests__/query-entity-detector.test.ts +171 -0
  158. package/src/__tests__/query-expander.test.ts +34 -0
  159. package/src/__tests__/recall-mode.test.ts +64 -0
  160. package/src/__tests__/result-synthesizer.test.ts +198 -0
  161. package/src/__tests__/resume-context.test.ts +4743 -0
  162. package/src/__tests__/retrieval-trace.test.ts +213 -0
  163. package/src/__tests__/retriever-boundary-weight.test.ts +136 -0
  164. package/src/__tests__/retroactive-boost.test.ts +223 -0
  165. package/src/__tests__/scope-inventory.test.ts +200 -0
  166. package/src/__tests__/scope-policy.test.ts +69 -0
  167. package/src/__tests__/session-checkpoint.test.ts +259 -0
  168. package/src/__tests__/session-gc.test.ts +215 -0
  169. package/src/__tests__/store-scope-audit.test.ts +81 -0
  170. package/src/__tests__/temporal-parser.test.ts +241 -0
  171. package/src/__tests__/term-registry.test.ts +121 -0
  172. package/src/__tests__/tool-output-compressor.test.ts +133 -0
  173. package/src/__tests__/version-manager.test.ts +213 -0
  174. package/src/__tests__/workflow-observation.test.ts +400 -0
  175. package/src/access-tracker.ts +316 -0
  176. package/src/adaptive-retrieval.ts +64 -0
  177. package/src/api-server.ts +667 -0
  178. package/src/asset-sync.ts +79 -0
  179. package/src/auto-consolidation.ts +133 -0
  180. package/src/auto-gc.ts +124 -0
  181. package/src/auto-recall.ts +95 -0
  182. package/src/capture-engine.ts +931 -0
  183. package/src/capture-heuristic.ts +177 -0
  184. package/src/cascade-forget.ts +135 -0
  185. package/src/chunker.ts +253 -0
  186. package/src/cli.ts +1917 -0
  187. package/src/confidence-tracker.ts +166 -0
  188. package/src/conflict-advisor.ts +433 -0
  189. package/src/conflict-engine.ts +181 -0
  190. package/src/conflict-escalation.ts +138 -0
  191. package/src/conflict-lifecycle.ts +67 -0
  192. package/src/conflict-output.ts +218 -0
  193. package/src/conflict-schema.ts +130 -0
  194. package/src/conflict-store.ts +127 -0
  195. package/src/consolidation-engine.ts +256 -0
  196. package/src/context-composer-pins.ts +183 -0
  197. package/src/context-composer-queries.ts +121 -0
  198. package/src/context-composer-scope.ts +56 -0
  199. package/src/context-composer-stable-selection.ts +200 -0
  200. package/src/context-composer-stable.ts +217 -0
  201. package/src/context-composer-task-fallbacks.ts +36 -0
  202. package/src/context-composer-task-ranking.ts +197 -0
  203. package/src/context-composer-task-results.ts +137 -0
  204. package/src/context-composer-task-selection.ts +342 -0
  205. package/src/context-composer-text.ts +57 -0
  206. package/src/context-composer.ts +300 -0
  207. package/src/context-renderer.ts +179 -0
  208. package/src/decay-engine.ts +189 -0
  209. package/src/doctor.ts +543 -0
  210. package/src/embedder.ts +557 -0
  211. package/src/entity-resolver.ts +131 -0
  212. package/src/eval.ts +632 -0
  213. package/src/event-segmenter.ts +216 -0
  214. package/src/ingest.ts +1822 -0
  215. package/src/kg-extractor.ts +217 -0
  216. package/src/kg-store.ts +353 -0
  217. package/src/legacy-scope-review.ts +53 -0
  218. package/src/llm-client.ts +629 -0
  219. package/src/llm-consolidation.ts +168 -0
  220. package/src/mcp-server.ts +1337 -0
  221. package/src/memory-assets.ts +475 -0
  222. package/src/memory-boundaries.ts +354 -0
  223. package/src/memory-health-rebalance.ts +220 -0
  224. package/src/memory-output.ts +474 -0
  225. package/src/memory-schema.ts +184 -0
  226. package/src/multi-vector.ts +234 -0
  227. package/src/noise-filter.ts +155 -0
  228. package/src/ppr-traversal.ts +279 -0
  229. package/src/preference-matcher.ts +174 -0
  230. package/src/preference-slots.ts +259 -0
  231. package/src/prospective-memory.ts +198 -0
  232. package/src/query-entity-detector.ts +207 -0
  233. package/src/query-expander.ts +98 -0
  234. package/src/result-synthesizer.ts +114 -0
  235. package/src/retrieval-profiles.ts +101 -0
  236. package/src/retrieval-trace.ts +94 -0
  237. package/src/retriever.ts +1338 -0
  238. package/src/retroactive-boost.ts +154 -0
  239. package/src/rif.ts +73 -0
  240. package/src/runtime-config.ts +172 -0
  241. package/src/schema-utils.ts +56 -0
  242. package/src/scope-inventory.ts +310 -0
  243. package/src/scope-policy.ts +142 -0
  244. package/src/session-engine.ts +168 -0
  245. package/src/session-output.ts +96 -0
  246. package/src/session-schema.ts +60 -0
  247. package/src/session-store.ts +293 -0
  248. package/src/stderr-log.ts +16 -0
  249. package/src/store.ts +903 -0
  250. package/src/temporal-parser.ts +318 -0
  251. package/src/term-registry.ts +791 -0
  252. package/src/tool-output-compressor.ts +115 -0
  253. package/src/tracker.ts +57 -0
  254. package/src/ui-server.ts +430 -0
  255. package/src/version-manager.ts +172 -0
  256. package/src/workflow-observation-engine.ts +316 -0
  257. package/src/workflow-observation-managed.ts +83 -0
  258. package/src/workflow-observation-output.ts +86 -0
  259. package/src/workflow-observation-schema.ts +87 -0
  260. package/src/workflow-observation-scope-review.ts +409 -0
  261. package/src/workflow-observation-store.ts +69 -0
@@ -0,0 +1,121 @@
1
+ import type { StableCategory } from "./context-composer-stable.js";
2
+ import { cleanText, dedupeText } from "./context-composer-text.js";
3
+ import type { TaskCategory } from "./context-composer-task-selection.js";
4
+ import {
5
+ ASSOCIATIVE_RECALL_CUE_TERMS,
6
+ containsAnyTerm,
7
+ extractTerms,
8
+ looksLikeContinuityTask,
9
+ looksLikeStyleTask,
10
+ } from "./term-registry.js";
11
+
12
+ export function buildStableQuery(category: StableCategory, taskSeed?: string): string {
13
+ if (taskSeed) {
14
+ switch (category) {
15
+ case "profile":
16
+ return `${taskSeed} user background identity role`;
17
+ case "preferences":
18
+ if (looksLikeStyleTask(taskSeed)) {
19
+ return `${taskSeed} user preferences writing tone voice style habits 口语化 不端着 自嘲 鸡血 浮夸`;
20
+ }
21
+ return `${taskSeed} user preferences workflow style`;
22
+ case "entities":
23
+ return `${taskSeed} project tools repository entities`;
24
+ }
25
+ }
26
+
27
+ switch (category) {
28
+ case "profile":
29
+ return "user background identity role";
30
+ case "preferences":
31
+ return "user preferences workflow style habits";
32
+ case "entities":
33
+ return "active project tools repository entities";
34
+ }
35
+ }
36
+
37
+ export function buildStylePreferenceFallbackQuery(taskSeed?: string): string {
38
+ const extracted = extractTerms(taskSeed).filter((term) =>
39
+ containsAnyTerm(term, ["写作", "风格", "语气", "偏好", "表达", "style", "tone", "voice", "preference"])
40
+ );
41
+ const lead = dedupeText([
42
+ "写作风格",
43
+ "语气",
44
+ "偏好",
45
+ "避免表达",
46
+ "口语化",
47
+ "不端着",
48
+ ...extracted,
49
+ ], 6).join(" ");
50
+
51
+ return lead || "写作风格 语气 偏好 避免表达 口语化 不端着";
52
+ }
53
+
54
+ export function buildTaskQuery(category: TaskCategory, taskSeed?: string): string {
55
+ if (taskSeed) {
56
+ return category === "patterns"
57
+ ? `${taskSeed} reusable workflow pattern steps`
58
+ : `${taskSeed} similar solved case previous fix`;
59
+ }
60
+ return category === "patterns"
61
+ ? "reusable workflow pattern steps"
62
+ : "similar solved case previous fix";
63
+ }
64
+
65
+ export function buildScopedEntityFallbackQuery(scope?: string, taskSeed?: string): string {
66
+ const scopeTerms = extractTerms(scope).slice(0, 4);
67
+ const taskTerms = extractTerms(taskSeed).slice(0, 4);
68
+ const query = dedupeText([
69
+ ...scopeTerms,
70
+ ...taskTerms,
71
+ "active project",
72
+ "shared memory layer",
73
+ "continuity",
74
+ "checkpoint_session",
75
+ "resume_context",
76
+ "project entity",
77
+ "tools",
78
+ "repository",
79
+ ], 10).join(" ");
80
+
81
+ return query || "active project continuity checkpoint_session resume_context project entity tools repository";
82
+ }
83
+
84
+ export function buildAssociativeNestEntityFallbackQuery(taskSeed?: string): string {
85
+ const associativeRecallCue = Boolean(
86
+ taskSeed &&
87
+ (
88
+ containsAnyTerm(taskSeed, ["RecallNest", "recallnest", "Nest", "nest"]) ||
89
+ (
90
+ looksLikeContinuityTask(taskSeed) &&
91
+ containsAnyTerm(taskSeed, ASSOCIATIVE_RECALL_CUE_TERMS)
92
+ )
93
+ )
94
+ );
95
+ if (!taskSeed || !associativeRecallCue) {
96
+ return "";
97
+ }
98
+
99
+ const taskTerms = extractTerms(taskSeed).slice(0, 4);
100
+ const query = dedupeText([
101
+ ...taskTerms,
102
+ "RecallNest",
103
+ "checkpoint_session",
104
+ "resume_context",
105
+ "store_memory",
106
+ "memory system",
107
+ "记忆系统",
108
+ "continuity",
109
+ "project entity",
110
+ ], 10).join(" ");
111
+
112
+ return query || "RecallNest checkpoint_session resume_context store_memory memory system continuity project entity";
113
+ }
114
+
115
+ export function formatLatestCheckpointHeadline(
116
+ sessionId: string,
117
+ updatedAt: string,
118
+ summary: string,
119
+ ): string {
120
+ return `Latest checkpoint from ${sessionId} on ${updatedAt.slice(0, 10)}: ${cleanText(summary, 220)}`;
121
+ }
@@ -0,0 +1,56 @@
1
+ import { dedupeText } from "./context-composer-text.js";
2
+ import {
3
+ GENERIC_SCOPE_TERMS,
4
+ buildTaskHintTerms,
5
+ extractTerms,
6
+ normalizeText,
7
+ } from "./term-registry.js";
8
+
9
+ export function normalizeScopedValue(scope: string): string {
10
+ const normalized = normalizeText(scope);
11
+ if (normalized.startsWith("memory:")) return normalized.slice("memory:".length);
12
+ if (normalized.startsWith("asset:")) return normalized.slice("asset:".length);
13
+ return normalized;
14
+ }
15
+
16
+ function buildScopeIdentityTerms(scope?: string): string[] {
17
+ if (!scope) return [];
18
+
19
+ const normalizedScope = normalizeScopedValue(scope);
20
+ const identity = normalizedScope.includes(":")
21
+ ? normalizedScope.slice(normalizedScope.indexOf(":") + 1)
22
+ : normalizedScope;
23
+ if (!identity) return [];
24
+
25
+ const spaced = identity.replace(/[-_/.:]+/g, " ");
26
+ return dedupeText([
27
+ identity,
28
+ spaced,
29
+ ...extractTerms(identity),
30
+ ...extractTerms(spaced),
31
+ ], 12)
32
+ .map((term) => normalizeText(term))
33
+ .filter((term) =>
34
+ term.length >= 2 &&
35
+ !GENERIC_SCOPE_TERMS.has(term),
36
+ );
37
+ }
38
+
39
+ export function buildProjectScopeCueTerms(scope?: string): string[] {
40
+ if (!scope) return [];
41
+ return dedupeText(extractTerms(scope), 8)
42
+ .map((term) => normalizeText(term))
43
+ .filter((term) =>
44
+ term.length >= 2 &&
45
+ !GENERIC_SCOPE_TERMS.has(term),
46
+ );
47
+ }
48
+
49
+ export function taskMentionsScopeIdentity(taskSeed: string | undefined, scope?: string): boolean {
50
+ if (!taskSeed) return false;
51
+ const identityTerms = buildScopeIdentityTerms(scope);
52
+ if (identityTerms.length === 0) return false;
53
+
54
+ const haystack = normalizeText(`${taskSeed} ${buildTaskHintTerms(taskSeed).join(" ")}`);
55
+ return identityTerms.some((term) => haystack.includes(term));
56
+ }
@@ -0,0 +1,200 @@
1
+ import { extractBoundaryMetadata, extractCanonicalKey, shouldUseStableMemoryResult } from "./memory-boundaries.js";
2
+ import { buildProjectScopeCueTerms, normalizeScopedValue } from "./context-composer-scope.js";
3
+ import { bestSummaryText, cleanText, dedupeText, stripConversationMarkers } from "./context-composer-text.js";
4
+ import type { RetrievalResult } from "./retriever.js";
5
+ import {
6
+ GENERIC_ENTITY_TASK_TERMS,
7
+ GENERIC_SCOPE_TERMS,
8
+ PREFERENCE_SPECIFICITY_GROUPS,
9
+ TASK_CUE_EXTRACTION_LIMIT,
10
+ buildTaskHintTerms,
11
+ containsAnyTerm,
12
+ containsLowSignalStableTerm,
13
+ extractTerms,
14
+ looksLikeStableInstruction,
15
+ normalizeText,
16
+ } from "./term-registry.js";
17
+
18
+ export type StableCategory = "profile" | "preferences" | "entities";
19
+
20
+ const STABLE_CATEGORY_LABELS: Record<StableCategory, string> = {
21
+ profile: "Profile",
22
+ preferences: "Preference",
23
+ entities: "Entity",
24
+ };
25
+
26
+ function isDurableStableScope(scope: string): boolean {
27
+ return scope.startsWith("memory:") || scope.startsWith("asset:");
28
+ }
29
+
30
+ function isStableCandidateUseful(category: StableCategory, result: RetrievalResult): boolean {
31
+ if (!shouldUseStableMemoryResult({
32
+ category,
33
+ scope: result.entry.scope,
34
+ metadata: result.entry.metadata,
35
+ })) {
36
+ return false;
37
+ }
38
+
39
+ const stripped = stripConversationMarkers(result.entry.text);
40
+ const normalized = normalizeText(stripped);
41
+ if (!normalized || normalized.length < 8) return false;
42
+ if (looksLikeStableInstruction(normalized)) return false;
43
+ if (containsLowSignalStableTerm(normalized)) return false;
44
+ if (!isDurableStableScope(result.entry.scope) && stripped.length > 180) return false;
45
+ if (category === "entities" && /^(用户|助手|pinned asset|memory brief)/i.test(stripped)) return false;
46
+ return true;
47
+ }
48
+
49
+ function buildStableScopeCueTerms(scope?: string, taskSeed?: string): string[] {
50
+ return dedupeText([
51
+ ...extractTerms(scope, TASK_CUE_EXTRACTION_LIMIT),
52
+ ...extractTerms(taskSeed, TASK_CUE_EXTRACTION_LIMIT),
53
+ ...buildTaskHintTerms(taskSeed),
54
+ ], TASK_CUE_EXTRACTION_LIMIT)
55
+ .map((term) => normalizeText(term))
56
+ .filter((term) =>
57
+ term.length >= 2 &&
58
+ !GENERIC_SCOPE_TERMS.has(term),
59
+ );
60
+ }
61
+
62
+ function buildTaskEntityCueTerms(taskSeed?: string): string[] {
63
+ return dedupeText([
64
+ ...extractTerms(taskSeed, TASK_CUE_EXTRACTION_LIMIT),
65
+ ...buildTaskHintTerms(taskSeed),
66
+ ], TASK_CUE_EXTRACTION_LIMIT)
67
+ .map((term) => normalizeText(term))
68
+ .filter((term) =>
69
+ term.length >= 2 &&
70
+ !GENERIC_ENTITY_TASK_TERMS.has(term),
71
+ );
72
+ }
73
+
74
+ function countTaskEntityCueMatches(result: RetrievalResult, taskSeed?: string): number {
75
+ const cueTerms = buildTaskEntityCueTerms(taskSeed);
76
+ if (cueTerms.length === 0) return 0;
77
+
78
+ const haystack = normalizeText(stripConversationMarkers(result.entry.text));
79
+ return cueTerms.filter((term) => haystack.includes(term)).length;
80
+ }
81
+
82
+ function isRelevantToScopedStableRecall(
83
+ result: RetrievalResult,
84
+ params: { scope?: string; taskSeed?: string },
85
+ ): boolean {
86
+ if (!params.scope) {
87
+ return true;
88
+ }
89
+
90
+ const requestScope = normalizeScopedValue(params.scope);
91
+ const resultScope = normalizeScopedValue(result.entry.scope);
92
+ if (resultScope === requestScope) {
93
+ return true;
94
+ }
95
+
96
+ const haystack = normalizeText(stripConversationMarkers(result.entry.text));
97
+ const requestIsProject = requestScope.startsWith("project:");
98
+ const resultIsProject = resultScope.startsWith("project:");
99
+ if (requestIsProject && resultIsProject) {
100
+ const projectCueTerms = buildProjectScopeCueTerms(params.scope);
101
+ if (projectCueTerms.length === 0) {
102
+ return false;
103
+ }
104
+ return projectCueTerms.some((term) => haystack.includes(term));
105
+ }
106
+
107
+ const cueTerms = buildStableScopeCueTerms(params.scope, params.taskSeed);
108
+ if (cueTerms.length === 0) {
109
+ return true;
110
+ }
111
+
112
+ return cueTerms.some((term) => haystack.includes(term));
113
+ }
114
+
115
+ function stableResultKey(result: RetrievalResult): string {
116
+ return extractCanonicalKey(result.entry.metadata) || normalizeText(stripConversationMarkers(result.entry.text));
117
+ }
118
+
119
+ function scoreStableResult(
120
+ category: StableCategory,
121
+ result: RetrievalResult,
122
+ params: { taskSeed?: string; styleFocused?: boolean },
123
+ ): number {
124
+ let score = result.score;
125
+ const stripped = stripConversationMarkers(result.entry.text);
126
+ const boundary = extractBoundaryMetadata(result.entry.metadata);
127
+
128
+ if (isDurableStableScope(result.entry.scope)) score += 2;
129
+ if (boundary?.layer === "durable") score += 2;
130
+ if (extractCanonicalKey(result.entry.metadata)) score += 1;
131
+
132
+ if (
133
+ params.styleFocused &&
134
+ category === "preferences" &&
135
+ containsAnyTerm(
136
+ stripped,
137
+ [...extractTerms(params.taskSeed, TASK_CUE_EXTRACTION_LIMIT), ...buildTaskHintTerms(params.taskSeed)],
138
+ )
139
+ ) {
140
+ score += 2;
141
+ }
142
+
143
+ return score;
144
+ }
145
+
146
+ function hasUnsupportedPreferenceSpecificity(result: RetrievalResult, taskSeed?: string, styleFocused?: boolean): boolean {
147
+ if (styleFocused || !taskSeed) return false;
148
+
149
+ const taskHaystack = normalizeText(`${taskSeed} ${buildTaskHintTerms(taskSeed).join(" ")}`);
150
+ if (!taskHaystack) return false;
151
+
152
+ const resultHaystack = normalizeText(stripConversationMarkers(result.entry.text));
153
+ return PREFERENCE_SPECIFICITY_GROUPS.some((group) =>
154
+ group.resultTerms.some((term) => resultHaystack.includes(term)) &&
155
+ !group.taskTerms.some((term) => taskHaystack.includes(term))
156
+ );
157
+ }
158
+
159
+ function formatStableResult(category: StableCategory, result: RetrievalResult): string {
160
+ const text = bestSummaryText(result.entry.text, result.entry.metadata);
161
+ return cleanText(`${STABLE_CATEGORY_LABELS[category]}: ${text}`, 220);
162
+ }
163
+
164
+ export function selectStableResults(
165
+ category: StableCategory,
166
+ results: RetrievalResult[],
167
+ limit: number,
168
+ params: { taskSeed?: string; styleFocused?: boolean; scope?: string } = {},
169
+ ): string[] {
170
+ const ranked = results
171
+ .filter((result) =>
172
+ isStableCandidateUseful(category, result) &&
173
+ (category !== "preferences" || !hasUnsupportedPreferenceSpecificity(result, params.taskSeed, params.styleFocused)) &&
174
+ (category !== "entities" || isRelevantToScopedStableRecall(result, params))
175
+ )
176
+ .map((result) => ({
177
+ result,
178
+ key: stableResultKey(result),
179
+ score: scoreStableResult(category, result, params),
180
+ taskCueMatches: category === "entities" ? countTaskEntityCueMatches(result, params.taskSeed) : 0,
181
+ }))
182
+ .sort((a, b) => b.score - a.score);
183
+
184
+ const maxTaskCueMatches = category === "entities" && !params.scope
185
+ ? Math.max(0, ...ranked.map((item) => item.taskCueMatches))
186
+ : 0;
187
+ const filteredRanked = category === "entities" && !params.scope && maxTaskCueMatches > 0
188
+ ? ranked.filter((item) => item.taskCueMatches === maxTaskCueMatches)
189
+ : ranked;
190
+
191
+ const seen = new Set<string>();
192
+ const output: string[] = [];
193
+ for (const item of filteredRanked) {
194
+ if (!item.key || seen.has(item.key)) continue;
195
+ seen.add(item.key);
196
+ output.push(formatStableResult(category, item.result));
197
+ if (output.length >= limit) break;
198
+ }
199
+ return output;
200
+ }
@@ -0,0 +1,217 @@
1
+ import type { PinAsset } from "./memory-assets.js";
2
+ import type { RetrievalResult } from "./retriever.js";
3
+ import type { SessionCheckpointRecord } from "./session-schema.js";
4
+ import { selectPinnedContext } from "./context-composer-pins.js";
5
+ import { cleanText, dedupeText } from "./context-composer-text.js";
6
+ import { selectStableResults } from "./context-composer-stable-selection.js";
7
+ import {
8
+ TASK_CUE_EXTRACTION_LIMIT,
9
+ buildTaskHintTerms,
10
+ containsLowSignalStableTerm,
11
+ extractTerms,
12
+ looksLikeContinuityTask,
13
+ looksLikeStableInstruction,
14
+ normalizeText,
15
+ } from "./term-registry.js";
16
+
17
+ export type { StableCategory } from "./context-composer-stable-selection.js";
18
+
19
+ const TASK_FOCUS_LOW_SIGNAL_TERMS = new Set([
20
+ "context",
21
+ "composer",
22
+ "helper",
23
+ "boundary",
24
+ "刚才",
25
+ "那个",
26
+ "audit",
27
+ "ranking",
28
+ "scoring",
29
+ "selection",
30
+ "orchestration",
31
+ "stable",
32
+ "fallback",
33
+ "cleanup",
34
+ "regression",
35
+ "hardening",
36
+ "issue",
37
+ "error",
38
+ "problem",
39
+ "calling",
40
+ "窗口",
41
+ "window",
42
+ "系统",
43
+ "system",
44
+ ]);
45
+
46
+ function interleaveUnique(buckets: string[][], limit: number): string[] {
47
+ const seen = new Set<string>();
48
+ const output: string[] = [];
49
+ let cursor = 0;
50
+
51
+ while (output.length < limit) {
52
+ let progressed = false;
53
+ for (const bucket of buckets) {
54
+ const value = bucket[cursor];
55
+ if (!value) continue;
56
+ const key = normalizeText(value);
57
+ if (seen.has(key)) continue;
58
+ seen.add(key);
59
+ output.push(value);
60
+ progressed = true;
61
+ if (output.length >= limit) break;
62
+ }
63
+ if (!progressed) break;
64
+ cursor += 1;
65
+ }
66
+
67
+ return output;
68
+ }
69
+
70
+ function buildCheckpointStableContext(
71
+ checkpoint: SessionCheckpointRecord | null,
72
+ limit: number,
73
+ ): string[] {
74
+ if (!checkpoint) return [];
75
+
76
+ const items = [
77
+ checkpoint.summary ? `Checkpoint summary: ${checkpoint.summary}` : "",
78
+ checkpoint.task ? `Checkpoint focus: ${checkpoint.task}` : "",
79
+ checkpoint.decisions[0] ? `Checkpoint decision: ${checkpoint.decisions[0]}` : "",
80
+ checkpoint.nextActions[0] ? `Checkpoint next: ${checkpoint.nextActions[0]}` : "",
81
+ checkpoint.entities[0] ? `Checkpoint entity: ${checkpoint.entities[0]}` : "",
82
+ ].filter(Boolean);
83
+
84
+ return dedupeText(items.map((item) => cleanText(item, 220)), limit);
85
+ }
86
+
87
+ function buildTaskSeedStableContext(taskSeed?: string, limit = 1): string[] {
88
+ if (!taskSeed) return [];
89
+
90
+ const candidates = dedupeText([
91
+ ...buildTaskHintTerms(taskSeed),
92
+ ...extractTerms(taskSeed, TASK_CUE_EXTRACTION_LIMIT),
93
+ ], TASK_CUE_EXTRACTION_LIMIT)
94
+ .filter((term) => {
95
+ const normalized = normalizeText(term);
96
+ if (!normalized || normalized.length < 2) return false;
97
+ if (looksLikeStableInstruction(normalized)) return false;
98
+ if (containsLowSignalStableTerm(normalized)) return false;
99
+
100
+ if (/^[a-z0-9._/-]+$/i.test(term)) {
101
+ return term.length >= 3;
102
+ }
103
+
104
+ return (
105
+ term.includes("记忆") ||
106
+ term.includes("项目") ||
107
+ term.includes("连续") ||
108
+ term.includes("文章") ||
109
+ term.includes("写作") ||
110
+ term.includes("配图") ||
111
+ term.includes("封面") ||
112
+ term.includes("终端") ||
113
+ term.includes("窗口") ||
114
+ normalized.includes("memory") ||
115
+ normalized.includes("layer") ||
116
+ normalized.includes("nest")
117
+ );
118
+ });
119
+ const preferredCandidates = candidates.filter((term) => !TASK_FOCUS_LOW_SIGNAL_TERMS.has(normalizeText(term)));
120
+ const selectedCandidates = (preferredCandidates.length > 0 ? preferredCandidates : candidates)
121
+ .slice(0, limit);
122
+
123
+ return dedupeText(selectedCandidates.map((term) => `Task focus: ${term}`), limit);
124
+ }
125
+
126
+ export function buildStableContextSections(params: {
127
+ profileResults: RetrievalResult[];
128
+ preferenceResults: RetrievalResult[];
129
+ entityResults: RetrievalResult[];
130
+ pinAssets: Array<PinAsset & { path: string }>;
131
+ latestCheckpoint: SessionCheckpointRecord | null;
132
+ taskSeed?: string;
133
+ scope?: string;
134
+ stableLimit: number;
135
+ styleFocusedTask?: boolean;
136
+ }): {
137
+ profileContext: string[];
138
+ preferenceContext: string[];
139
+ entityContext: string[];
140
+ checkpointContext: string[];
141
+ pinnedContext: string[];
142
+ taskFocusContext: string[];
143
+ stableContext: string[];
144
+ } {
145
+ const {
146
+ profileResults,
147
+ preferenceResults,
148
+ entityResults,
149
+ pinAssets,
150
+ latestCheckpoint,
151
+ taskSeed,
152
+ scope,
153
+ stableLimit,
154
+ styleFocusedTask,
155
+ } = params;
156
+
157
+ const profileContext = selectStableResults("profile", profileResults, Math.max(2, stableLimit), {
158
+ taskSeed,
159
+ styleFocused: styleFocusedTask,
160
+ scope,
161
+ });
162
+ const preferenceContext = selectStableResults("preferences", preferenceResults, Math.max(2, stableLimit), {
163
+ taskSeed,
164
+ styleFocused: styleFocusedTask,
165
+ scope,
166
+ });
167
+ const entityContext = selectStableResults("entities", entityResults, Math.max(2, stableLimit), {
168
+ taskSeed,
169
+ styleFocused: styleFocusedTask,
170
+ scope,
171
+ });
172
+ const checkpointContext = buildCheckpointStableContext(latestCheckpoint, Math.min(3, stableLimit));
173
+ const taskHints = buildTaskHintTerms(taskSeed);
174
+ const suppressPinnedContextForGenericContinuity = Boolean(
175
+ !styleFocusedTask &&
176
+ taskHints.length === 0 &&
177
+ looksLikeContinuityTask(taskSeed) &&
178
+ (entityContext.length > 0 || checkpointContext.length > 0)
179
+ );
180
+ const pinnedContext = suppressPinnedContextForGenericContinuity
181
+ ? []
182
+ : selectPinnedContext(pinAssets, {
183
+ taskSeed,
184
+ scope,
185
+ limit: Math.min(2, stableLimit),
186
+ styleFocused: styleFocusedTask,
187
+ skipForStyleTask: Boolean(styleFocusedTask && preferenceContext.length > 0),
188
+ });
189
+ const taskFocusContext = (
190
+ checkpointContext.length === 0 &&
191
+ profileContext.length === 0 &&
192
+ preferenceContext.length === 0 &&
193
+ entityContext.length === 0 &&
194
+ pinnedContext.length === 0
195
+ )
196
+ ? buildTaskSeedStableContext(taskSeed, 1)
197
+ : [];
198
+
199
+ const stableContext = interleaveUnique([
200
+ profileContext,
201
+ preferenceContext,
202
+ entityContext,
203
+ checkpointContext,
204
+ taskFocusContext,
205
+ pinnedContext,
206
+ ], stableLimit);
207
+
208
+ return {
209
+ profileContext,
210
+ preferenceContext,
211
+ entityContext,
212
+ checkpointContext,
213
+ pinnedContext,
214
+ taskFocusContext,
215
+ stableContext,
216
+ };
217
+ }
@@ -0,0 +1,36 @@
1
+ import { containsAnyTerm, taskCueCoverage } from "./term-registry.js";
2
+
3
+ export function buildWorkflowFallbackQuery(taskSeed?: string): string {
4
+ if (taskSeed) {
5
+ return `${taskSeed} search_memory resume_context checkpoint_session checkpoint autoRecall sessionStrategy workflow pattern steps`;
6
+ }
7
+ return "search_memory resume_context checkpoint_session checkpoint autoRecall sessionStrategy workflow pattern steps";
8
+ }
9
+
10
+ export function buildCaseFallbackQuery(taskSeed?: string): string {
11
+ if (taskSeed) {
12
+ if (containsAnyTerm(taskSeed, ["RecallNest", "recallnest"])) {
13
+ return `${taskSeed} scope fallback project scope handoff stable context cleanup continuity durable case 项目范围 交接 稳定上下文 问题 解决 方案 排查`;
14
+ }
15
+ return `${taskSeed} case solution fix root cause workaround cleanup continuity 问题 解决 方案 排查`;
16
+ }
17
+ return "case solution fix root cause workaround cleanup continuity 问题 解决 方案 排查";
18
+ }
19
+
20
+ export function buildContinuityFallbackPatterns(limit: number, existingItems: string[] = []): string[] {
21
+ const patterns = [
22
+ "Start fresh windows with resume_context before coding so stable context is restored early.",
23
+ "If resume_context still leaves gaps, run search_memory with the project name and task nouns before repo exploration drifts.",
24
+ "Before leaving a window, save checkpoint_session so the next session can recover decisions and next actions.",
25
+ ];
26
+ if (existingItems.length === 0) {
27
+ return patterns.slice(0, limit);
28
+ }
29
+
30
+ const coveredCues = new Set(existingItems.flatMap((item) => taskCueCoverage("patterns", item)));
31
+ return patterns
32
+ .filter((pattern) =>
33
+ taskCueCoverage("patterns", pattern).some((term) => !coveredCues.has(term))
34
+ )
35
+ .slice(0, limit);
36
+ }