recallnest 1.4.1 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/.env +4 -0
  2. package/CLAUDE.md +7 -3
  3. package/README.md +202 -130
  4. package/README_CN.md +263 -190
  5. package/bin/recallnest.mjs +62 -0
  6. package/config.json +55 -0
  7. package/daily-recall.sh +23 -0
  8. package/integrations/setup-all.sh +31 -0
  9. package/logs/daily-recall.error.log +0 -0
  10. package/logs/daily-recall.log +30 -0
  11. package/logs/ingest-2026-04-11.log +110 -0
  12. package/logs/ingest-2026-04-12.log +90 -0
  13. package/logs/launchagent-stderr.log +0 -0
  14. package/logs/launchagent-stdout.log +0 -0
  15. package/package.json +10 -3
  16. package/src/access-tracker.ts +3 -2
  17. package/src/activity-counter.ts +109 -0
  18. package/src/adaptive-retrieval.ts +6 -5
  19. package/src/admission-control.ts +163 -0
  20. package/src/anchor-generator.ts +80 -0
  21. package/src/api-server.ts +10 -0
  22. package/src/audit-log.ts +122 -0
  23. package/src/auto-gc.ts +98 -29
  24. package/src/auto-recall.ts +33 -4
  25. package/src/capture-engine.ts +372 -23
  26. package/src/cascade-forget.ts +3 -2
  27. package/src/cli.ts +107 -5
  28. package/src/compat.ts +22 -0
  29. package/src/confidence-tracker.ts +210 -47
  30. package/src/conflict-store.ts +12 -3
  31. package/src/consolidation-engine.ts +296 -8
  32. package/src/context-collapse-renderer.ts +222 -0
  33. package/src/context-composer-stable-selection.ts +5 -1
  34. package/src/context-composer.ts +239 -4
  35. package/src/context-reconstructor.ts +404 -0
  36. package/src/conversation-importer.ts +441 -0
  37. package/src/data-checkup.ts +232 -0
  38. package/src/decay-engine.ts +84 -0
  39. package/src/distill-lock.ts +122 -0
  40. package/src/doctor.ts +8 -10
  41. package/src/dream-pipeline.ts +227 -0
  42. package/src/emotion-detector.ts +90 -0
  43. package/src/eval.ts +3 -2
  44. package/src/forget-engine.ts +269 -0
  45. package/src/frequency-tracker.ts +175 -0
  46. package/src/graph-export.ts +644 -0
  47. package/src/ingest.ts +36 -6
  48. package/src/interference-detector.ts +187 -0
  49. package/src/kg-extractor.ts +15 -24
  50. package/src/kg-store.ts +2 -1
  51. package/src/language-hook.ts +67 -0
  52. package/src/llm-client.ts +289 -6
  53. package/src/mcp-server.ts +707 -80
  54. package/src/memory-assets.ts +22 -5
  55. package/src/memory-evolution.ts +365 -0
  56. package/src/memory-lint.ts +429 -0
  57. package/src/memory-output.ts +69 -0
  58. package/src/memory-schema.ts +74 -2
  59. package/src/narrative-schema.ts +79 -0
  60. package/src/narrative-tagger.ts +164 -0
  61. package/src/pii-detector.ts +98 -0
  62. package/src/post-retrieval-filter.ts +76 -0
  63. package/src/prediction-engine.ts +253 -0
  64. package/src/preference-slots.ts +65 -2
  65. package/src/prospective-memory.ts +215 -0
  66. package/src/query-expander.ts +67 -0
  67. package/src/recall-governor.ts +183 -0
  68. package/src/retention-policy.ts +90 -0
  69. package/src/retriever.ts +828 -55
  70. package/src/rif.ts +34 -8
  71. package/src/runtime-config.ts +23 -8
  72. package/src/scope-inventory.ts +4 -3
  73. package/src/scope-policy.ts +4 -0
  74. package/src/session-distiller.ts +307 -0
  75. package/src/session-output.ts +40 -0
  76. package/src/session-schema.ts +37 -0
  77. package/src/session-store.ts +7 -2
  78. package/src/skill-engine.ts +208 -0
  79. package/src/skill-promotion.ts +293 -0
  80. package/src/skill-schema.ts +39 -0
  81. package/src/store.ts +96 -10
  82. package/src/topic-tag.ts +66 -0
  83. package/src/tracker.ts +3 -1
  84. package/src/ui-server.ts +80 -8
  85. package/src/workflow-observation-scope-review.ts +2 -1
  86. package/src/workflow-observation-store.ts +2 -1
  87. package/src/write-verifier.ts +126 -0
  88. package/.claude-plugin/CLAUDE.md +0 -29
  89. package/.claude-plugin/marketplace.json +0 -18
  90. package/.github/workflows/ci.yml +0 -28
  91. package/.mcp.json +0 -9
  92. package/CHANGELOG.md +0 -51
  93. package/DEMO.md +0 -94
  94. package/EVAL.md +0 -89
  95. package/FAILURES.md +0 -75
  96. package/OPERATIONS.md +0 -59
  97. package/ROADMAP.md +0 -198
  98. package/assets/recallnest-workbench.png +0 -0
  99. package/assets/ui/app.js +0 -807
  100. package/assets/ui/index.html +0 -154
  101. package/assets/ui/styles.css +0 -605
  102. package/assets/wechat-qr.jpg +0 -0
  103. package/daily-reflections/2026-03-05.md +0 -15
  104. package/daily-reflections/2026-03-06.md +0 -15
  105. package/daily-reflections/2026-03-07.md +0 -7
  106. package/daily-reflections/2026-03-08.md +0 -34
  107. package/daily-reflections/2026-03-09.md +0 -35
  108. package/daily-reflections/2026-03-10.md +0 -36
  109. package/daily-reflections/2026-03-11.md +0 -35
  110. package/daily-reflections/2026-03-12.md +0 -36
  111. package/daily-reflections/2026-03-13.md +0 -35
  112. package/daily-reflections/2026-03-14.md +0 -35
  113. package/daily-reflections/2026-03-15.md +0 -35
  114. package/daily-reflections/2026-03-16.md +0 -41
  115. package/daily-reflections/2026-03-17.md +0 -10
  116. package/daily-reflections/2026-03-18.md +0 -10
  117. package/daily-reflections/2026-03-19.md +0 -10
  118. package/daily-reflections/2026-03-20.md +0 -10
  119. package/daily-reflections/2026-03-21.md +0 -10
  120. package/daily-reflections/2026-03-22.md +0 -10
  121. package/daily-reflections/2026-03-23.md +0 -10
  122. package/daily-reflections/2026-03-24.md +0 -10
  123. package/daily-reflections/2026-03-25.md +0 -32
  124. package/daily-reflections/2026-03-26.md +0 -37
  125. package/daily-reflections/2026-03-27.md +0 -35
  126. package/daily-reflections/2026-03-28.md +0 -35
  127. package/daily-reflections/2026-03-29.md +0 -35
  128. package/daily-reflections/2026-03-30.md +0 -36
  129. package/daily-reflections/2026-03-31.md +0 -35
  130. package/daily-reflections/2026-04-01.md +0 -35
  131. package/docs/api-reference.md +0 -1291
  132. package/docs/architecture.md +0 -168
  133. package/docs/memory-boundary-contract.md +0 -226
  134. package/docs/memory-categories.md +0 -119
  135. package/docs/pipeline-audit-ultramemory.md +0 -177
  136. package/docs/plans/2026-03-08-query-expansion.md +0 -270
  137. package/docs/plans/2026-03-16-continuity-layer.md +0 -196
  138. package/docs/plans/mcp-tiering-plan.md +0 -126
  139. package/docs/research/recall-quality-audit-2026-03-22.md +0 -153
  140. package/docs/self-evolution.md +0 -198
  141. package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +0 -782
  142. package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +0 -77
  143. package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +0 -95
  144. package/eval/continuity/results-2026-03-16.md +0 -117
  145. package/eval/continuity/results-2026-03-19.md +0 -453
  146. package/eval/continuity/runbook.md +0 -110
  147. package/eval/continuity/seed-cases.md +0 -54
  148. package/eval/reports/2026-03-06-abstract-query-baseline.md +0 -96
  149. package/eval/reports/2026-03-06-aws-ssh-baseline.md +0 -82
  150. package/eval/reports/2026-03-06-baseline.md +0 -82
  151. package/eval/reports/query-expansion.md +0 -138
  152. package/lm +0 -19
  153. package/plan-v1.2-distributable.md +0 -105
  154. package/scripts/archive-distilled-junk.ts +0 -116
  155. package/scripts/backfill-l0l1l2.ts +0 -98
  156. package/scripts/blind-scan.ts +0 -588
  157. package/scripts/cleanup-step1-exact-dups.ts +0 -117
  158. package/scripts/cleanup-step2-template-dups.ts +0 -113
  159. package/scripts/cleanup-step3-stale-content.ts +0 -149
  160. package/scripts/cleanup-step4-orphan-scopes.ts +0 -131
  161. package/scripts/delete-distilled-junk.ts +0 -88
  162. package/scripts/distill-facts.ts +0 -305
  163. package/scripts/health-check.ts +0 -343
  164. package/scripts/incremental-ingest.sh +0 -71
  165. package/scripts/mlp-health-check.ts +0 -382
  166. package/scripts/pull-claude-desktop.ts +0 -515
  167. package/scripts/re-ingest-cc.sh +0 -66
  168. package/scripts/rebalance-memory-health.ts +0 -181
  169. package/scripts/run-desktop-ingest.sh +0 -11
  170. package/scripts/start-server.sh +0 -12
  171. package/scripts/viz-dashboard.html +0 -423
  172. package/scripts/viz-export.ts +0 -703
  173. package/src/__tests__/access-tracker-novelty.test.ts +0 -228
  174. package/src/__tests__/auto-consolidation.test.ts +0 -168
  175. package/src/__tests__/auto-recall.test.ts +0 -131
  176. package/src/__tests__/batch-dedup.test.ts +0 -65
  177. package/src/__tests__/capture-engine.test.ts +0 -1142
  178. package/src/__tests__/capture-heuristic.test.ts +0 -171
  179. package/src/__tests__/cascade-forget.test.ts +0 -210
  180. package/src/__tests__/category-min-scores.test.ts +0 -33
  181. package/src/__tests__/confidence-tracker.test.ts +0 -196
  182. package/src/__tests__/conflict-advisor.test.ts +0 -206
  183. package/src/__tests__/conflict-escalation.test.ts +0 -149
  184. package/src/__tests__/conflict-lifecycle.test.ts +0 -83
  185. package/src/__tests__/conflict-output.test.ts +0 -85
  186. package/src/__tests__/conflict-store.test.ts +0 -374
  187. package/src/__tests__/consolidation-engine.test.ts +0 -181
  188. package/src/__tests__/context-composer-pins.test.ts +0 -64
  189. package/src/__tests__/context-composer-task-selection.test.ts +0 -24
  190. package/src/__tests__/context-renderer.test.ts +0 -91
  191. package/src/__tests__/continuity-eval.test.ts +0 -275
  192. package/src/__tests__/continuity-schema.test.ts +0 -97
  193. package/src/__tests__/core-recall-eval.test.ts +0 -235
  194. package/src/__tests__/core-summary.test.ts +0 -109
  195. package/src/__tests__/dedup-actions.test.ts +0 -58
  196. package/src/__tests__/deterministic-id.test.ts +0 -39
  197. package/src/__tests__/doctor.test.ts +0 -303
  198. package/src/__tests__/drill-down.test.ts +0 -108
  199. package/src/__tests__/embedder.test.ts +0 -122
  200. package/src/__tests__/entity-resolver.test.ts +0 -58
  201. package/src/__tests__/event-segmenter.test.ts +0 -153
  202. package/src/__tests__/hotness-blend.test.ts +0 -76
  203. package/src/__tests__/ingest.test.ts +0 -360
  204. package/src/__tests__/kg-extractor.test.ts +0 -184
  205. package/src/__tests__/kg-store.test.ts +0 -198
  206. package/src/__tests__/llm-consolidation.test.ts +0 -226
  207. package/src/__tests__/memory-boundaries.test.ts +0 -237
  208. package/src/__tests__/memory-health-rebalance.test.ts +0 -144
  209. package/src/__tests__/memory-output.test.ts +0 -215
  210. package/src/__tests__/multi-vector.test.ts +0 -242
  211. package/src/__tests__/ppr-traversal.test.ts +0 -256
  212. package/src/__tests__/preference-guard-smoke.test.ts +0 -211
  213. package/src/__tests__/preference-matcher.test.ts +0 -230
  214. package/src/__tests__/preference-slots.test.ts +0 -106
  215. package/src/__tests__/prospective-memory.test.ts +0 -283
  216. package/src/__tests__/query-entity-detector.test.ts +0 -171
  217. package/src/__tests__/query-expander.test.ts +0 -34
  218. package/src/__tests__/recall-mode.test.ts +0 -64
  219. package/src/__tests__/result-synthesizer.test.ts +0 -198
  220. package/src/__tests__/resume-context.test.ts +0 -4743
  221. package/src/__tests__/retrieval-trace.test.ts +0 -213
  222. package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
  223. package/src/__tests__/retroactive-boost.test.ts +0 -223
  224. package/src/__tests__/scope-inventory.test.ts +0 -200
  225. package/src/__tests__/scope-policy.test.ts +0 -69
  226. package/src/__tests__/session-checkpoint.test.ts +0 -259
  227. package/src/__tests__/session-gc.test.ts +0 -215
  228. package/src/__tests__/store-scope-audit.test.ts +0 -81
  229. package/src/__tests__/temporal-parser.test.ts +0 -241
  230. package/src/__tests__/term-registry.test.ts +0 -121
  231. package/src/__tests__/tool-output-compressor.test.ts +0 -133
  232. package/src/__tests__/version-manager.test.ts +0 -213
  233. package/src/__tests__/workflow-observation.test.ts +0 -400
@@ -1,400 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
-
6
- import {
7
- applyWorkflowObservationScopeSuggestions,
8
- formatWorkflowObservationScopeReview,
9
- keepWorkflowObservationScopesGlobal,
10
- parseWorkflowObservationScopeCue,
11
- reviewWorkflowObservationScopes,
12
- } from "../workflow-observation-scope-review.js";
13
- import {
14
- buildWorkflowEvidence,
15
- buildWorkflowHealthDashboard,
16
- buildWorkflowObservationRecord,
17
- inspectWorkflowHealth,
18
- resolveWorkflowObservationScope,
19
- } from "../workflow-observation-engine.js";
20
- import { collectScopeInventory } from "../scope-inventory.js";
21
- import { buildSessionCheckpointResult } from "../session-engine.js";
22
- import { MemoryStore } from "../store.js";
23
- import { buildManagedCheckpointObservation, buildManagedResumeObservation } from "../workflow-observation-managed.js";
24
- import { WorkflowObservationStore } from "../workflow-observation-store.js";
25
-
26
- describe("workflow observation engine", () => {
27
- it("defaults observation scope to global", () => {
28
- const record = buildWorkflowObservationRecord({
29
- workflowId: "resume_context",
30
- outcome: "missed",
31
- summary: "Fresh window skipped resume_context before repo exploration.",
32
- });
33
-
34
- expect(resolveWorkflowObservationScope(record)).toBe("global");
35
- expect(record.resolvedScope).toBe("global");
36
- });
37
-
38
- it("aggregates workflow health and evidence from append-only observations", async () => {
39
- const dir = mkdtempSync(join(tmpdir(), "recallnest-workflow-observations-"));
40
- try {
41
- const store = new WorkflowObservationStore(dir);
42
- const records = [
43
- {
44
- workflowId: "resume_context",
45
- scope: "project:recallnest",
46
- outcome: "missed",
47
- summary: "Fresh window explored the repo before continuity recovery.",
48
- signal: "missed-startup-trigger",
49
- source: "smoke",
50
- recordedAt: "2026-03-15T03:00:00.000Z",
51
- },
52
- {
53
- workflowId: "resume_context",
54
- scope: "project:recallnest",
55
- outcome: "corrected",
56
- summary: "User had to remind the agent to recover continuity first.",
57
- signal: "user-correction",
58
- source: "agent",
59
- recordedAt: "2026-03-16T03:00:00.000Z",
60
- },
61
- {
62
- workflowId: "resume_context",
63
- scope: "project:recallnest",
64
- outcome: "success",
65
- summary: "Fresh window recovered RecallNest continuity before coding.",
66
- signal: "startup-recovered",
67
- source: "smoke",
68
- recordedAt: "2026-03-17T03:00:00.000Z",
69
- },
70
- {
71
- workflowId: "checkpoint_session",
72
- scope: "project:recallnest",
73
- outcome: "failure",
74
- summary: "Checkpoint still carried repo-state text before the product-side guard landed.",
75
- signal: "repo-state-contamination",
76
- source: "smoke",
77
- recordedAt: "2026-03-17T04:00:00.000Z",
78
- },
79
- ];
80
-
81
- for (const record of records) {
82
- await store.save(buildWorkflowObservationRecord(record));
83
- }
84
-
85
- const health = await inspectWorkflowHealth(store, {
86
- workflowId: "resume_context",
87
- scope: "project:recallnest",
88
- now: new Date("2026-03-17T12:00:00.000Z"),
89
- });
90
- expect(health.status).toBe("watch");
91
- expect(health.windows[1]?.total).toBe(3);
92
- expect(health.windows[1]?.missed).toBe(1);
93
- expect(health.windows[1]?.corrected).toBe(1);
94
- expect(health.windows[1]?.successRate).toBeCloseTo(1 / 3, 5);
95
-
96
- const dashboard = buildWorkflowHealthDashboard(
97
- await store.listRecent({ scope: "project:recallnest", limit: 50 }),
98
- { scope: "project:recallnest" },
99
- );
100
- expect(dashboard[0]?.workflowId).toBe("checkpoint_session");
101
- expect(dashboard[0]?.status).toBe("critical");
102
-
103
- const evidence = await buildWorkflowEvidence(store, {
104
- workflowId: "checkpoint_session",
105
- scope: "project:recallnest",
106
- now: new Date("2026-03-17T12:00:00.000Z"),
107
- });
108
- expect(evidence.topSignals[0]?.signal).toBe("repo-state-contamination");
109
- expect(evidence.suggestions).toContain(
110
- "Keep volatile repo-state text out of saved checkpoints and handoff summaries unless this window verified it.",
111
- );
112
- expect(evidence.suggestions).toContain(
113
- "Add end-of-window guards so checkpoint content is sanitized before it becomes the next handoff.",
114
- );
115
- } finally {
116
- rmSync(dir, { recursive: true, force: true });
117
- }
118
- });
119
-
120
- it("builds managed continuity observations without routing them into regular memory", () => {
121
- const resumeObservation = buildManagedResumeObservation({
122
- sessionId: "session-123",
123
- task: "Continue RecallNest continuity work",
124
- }, {
125
- stableContext: ["Entity: RecallNest continuity revolves around three primitives."],
126
- relevantPatterns: ["Call resume_context before coding."],
127
- recentCases: ["Case: RecallNest sparse startup context cleanup"],
128
- latestCheckpoint: {
129
- sessionId: "session-123",
130
- summary: "Checkpoint summary",
131
- updatedAt: "2026-03-17T11:00:00.000Z",
132
- },
133
- responseMode: "default",
134
- });
135
-
136
- expect(resumeObservation.workflowId).toBe("resume_context");
137
- expect(resumeObservation.outcome).toBe("success");
138
- expect(resumeObservation.scope).toBe("session:session-123");
139
- expect(resumeObservation.source).toBe("managed");
140
- expect(resumeObservation.signal).toBe("managed-resume-resolved");
141
- expect(resumeObservation.tags).toContain("managed");
142
- expect(resumeObservation.tools).toEqual(["resume_context"]);
143
-
144
- const checkpointObservation = buildManagedCheckpointObservation(buildSessionCheckpointResult({
145
- sessionId: "session-123",
146
- scope: "project:recallnest",
147
- summary: "Only resumed context here. git status shows modified files.",
148
- openLoops: ["git status still needs review"],
149
- }));
150
-
151
- expect(checkpointObservation.workflowId).toBe("checkpoint_session");
152
- expect(checkpointObservation.outcome).toBe("corrected");
153
- expect(checkpointObservation.scope).toBe("project:recallnest");
154
- expect(checkpointObservation.source).toBe("managed");
155
- expect(checkpointObservation.signal).toBe("repo-state-sanitized");
156
- expect(checkpointObservation.summary).toContain("summary and openLoops");
157
- expect(checkpointObservation.tools).toEqual(["checkpoint_session"]);
158
- });
159
-
160
- it("uses resolved scope fallback for managed resume observations when request scope is absent", () => {
161
- const observation = buildManagedResumeObservation({
162
- task: "Continue RecallNest scope cleanup",
163
- }, {
164
- resolvedScope: "project:recallnest",
165
- stableContext: ["Entity: RecallNest continuity revolves around three primitives."],
166
- relevantPatterns: [],
167
- recentCases: [],
168
- latestCheckpoint: {
169
- sessionId: "session-xyz",
170
- resolvedScope: "project:recallnest",
171
- summary: "Checkpoint summary",
172
- updatedAt: "2026-03-18T00:00:00.000Z",
173
- },
174
- responseMode: "default",
175
- });
176
-
177
- expect(observation.scope).toBe("project:recallnest");
178
- });
179
-
180
- it("reviews and applies cue-based scope suggestions for legacy global workflow observations", () => {
181
- const dir = mkdtempSync(join(tmpdir(), "recallnest-workflow-scope-review-"));
182
- try {
183
- const recallnestPath = join(dir, "recallnest.json");
184
- const externalPath = join(dir, "external.json");
185
- writeFileSync(recallnestPath, JSON.stringify({
186
- workflowId: "resume_context",
187
- summary: "Managed resume_context recovered 1 stable item(s), 1 pattern(s), and 1 case(s).",
188
- source: "managed",
189
- task: "Continue RecallNest scope cleanup",
190
- recordedAt: "2026-03-18T00:00:00.000Z",
191
- observationId: "obs-recallnest",
192
- resolvedScope: "global",
193
- }, null, 2) + "\n");
194
- writeFileSync(externalPath, JSON.stringify({
195
- workflowId: "resume_context",
196
- summary: "Managed resume_context recovered 1 stable item(s), 0 pattern(s), and 0 case(s).",
197
- source: "managed",
198
- task: "A2A code Claude SDK calling error",
199
- recordedAt: "2026-03-18T00:01:00.000Z",
200
- observationId: "obs-external",
201
- resolvedScope: "global",
202
- }, null, 2) + "\n");
203
-
204
- const review = reviewWorkflowObservationScopes({
205
- dir,
206
- currentScope: "global",
207
- source: "managed",
208
- workflowId: "resume_context",
209
- cues: [parseWorkflowObservationScopeCue("recallnest=project:recallnest")],
210
- });
211
-
212
- expect(review.totalCount).toBe(2);
213
- expect(review.suggestedCount).toBe(1);
214
- expect(review.manualReviewCount).toBe(1);
215
- expect(review.entries[0]?.suggestedScope).toBeUndefined();
216
- expect(review.entries[1]?.suggestedScope).toBe("project:recallnest");
217
- expect(formatWorkflowObservationScopeReview(review)).toContain("project:recallnest");
218
-
219
- const result = applyWorkflowObservationScopeSuggestions(review);
220
- expect(result.updatedCount).toBe(1);
221
- expect(result.skippedCount).toBe(1);
222
-
223
- const rewritten = JSON.parse(readFileSync(recallnestPath, "utf-8"));
224
- const untouched = JSON.parse(readFileSync(externalPath, "utf-8"));
225
- expect(rewritten.scope).toBe("project:recallnest");
226
- expect(rewritten.resolvedScope).toBe("project:recallnest");
227
- expect(untouched.resolvedScope).toBe("global");
228
- } finally {
229
- rmSync(dir, { recursive: true, force: true });
230
- }
231
- });
232
-
233
- it("can infer a scope from a nearby managed observation when cues are absent", () => {
234
- const dir = mkdtempSync(join(tmpdir(), "recallnest-workflow-scope-neighbor-"));
235
- try {
236
- const globalPath = join(dir, "global.json");
237
- const neighborPath = join(dir, "neighbor.json");
238
- writeFileSync(globalPath, JSON.stringify({
239
- workflowId: "resume_context",
240
- summary: "Managed resume_context recovered 2 stable item(s), 1 pattern(s), and 0 case(s).",
241
- source: "managed",
242
- recordedAt: "2026-03-18T00:00:00.000Z",
243
- observationId: "obs-global",
244
- resolvedScope: "global",
245
- }, null, 2) + "\n");
246
- writeFileSync(neighborPath, JSON.stringify({
247
- workflowId: "checkpoint_session",
248
- summary: "Managed checkpoint_session sanitized repo-state text out of summary before saving the handoff.",
249
- source: "managed",
250
- recordedAt: "2026-03-18T00:00:40.000Z",
251
- observationId: "obs-neighbor",
252
- resolvedScope: "session:recallnest-2026-03-18",
253
- }, null, 2) + "\n");
254
-
255
- const review = reviewWorkflowObservationScopes({
256
- dir,
257
- currentScope: "global",
258
- source: "managed",
259
- workflowId: "resume_context",
260
- neighborWindowSeconds: 90,
261
- });
262
-
263
- expect(review.totalCount).toBe(1);
264
- expect(review.suggestedCount).toBe(1);
265
- expect(review.entries[0]?.suggestedScope).toBe("session:recallnest-2026-03-18");
266
- expect(review.entries[0]?.reason).toContain("matched nearby checkpoint_session");
267
- } finally {
268
- rmSync(dir, { recursive: true, force: true });
269
- }
270
- });
271
-
272
- it("can mark manual-review legacy global observations as reviewed keep-global", () => {
273
- const dir = mkdtempSync(join(tmpdir(), "recallnest-workflow-scope-keep-global-"));
274
- try {
275
- const externalPath = join(dir, "external.json");
276
- writeFileSync(externalPath, JSON.stringify({
277
- workflowId: "resume_context",
278
- summary: "Managed resume_context recovered 1 stable item(s), 0 pattern(s), and 0 case(s).",
279
- source: "managed",
280
- task: "A2A code Claude SDK calling error",
281
- recordedAt: "2026-03-18T00:01:00.000Z",
282
- observationId: "obs-external",
283
- resolvedScope: "global",
284
- }, null, 2) + "\n");
285
-
286
- const review = reviewWorkflowObservationScopes({
287
- dir,
288
- currentScope: "global",
289
- source: "managed",
290
- workflowId: "resume_context",
291
- });
292
- expect(review.totalCount).toBe(1);
293
- expect(review.manualReviewCount).toBe(1);
294
-
295
- const result = keepWorkflowObservationScopesGlobal(review, {
296
- ids: ["obs-external"],
297
- reason: "external-task",
298
- reviewedAt: "2026-03-18T00:05:00.000Z",
299
- });
300
- expect(result.reviewedCount).toBe(1);
301
- expect(result.skippedCount).toBe(0);
302
- expect(result.unmatchedIds).toEqual([]);
303
- expect(result.ambiguousIds).toEqual([]);
304
-
305
- const rewritten = JSON.parse(readFileSync(externalPath, "utf-8"));
306
- expect(rewritten.legacyScopeReview).toEqual({
307
- decision: "keep",
308
- kind: "global",
309
- reason: "external-task",
310
- reviewedAt: "2026-03-18T00:05:00.000Z",
311
- });
312
-
313
- const nextReview = reviewWorkflowObservationScopes({
314
- dir,
315
- currentScope: "global",
316
- source: "managed",
317
- workflowId: "resume_context",
318
- });
319
- expect(nextReview.totalCount).toBe(0);
320
- } finally {
321
- rmSync(dir, { recursive: true, force: true });
322
- }
323
- });
324
-
325
- it("keeps mixed rewrite and keep-global review flows converged on a clean second pass", async () => {
326
- const root = mkdtempSync(join(tmpdir(), "recallnest-workflow-scope-mixed-"));
327
- try {
328
- const workflowDir = join(root, "workflow-observations");
329
- mkdirSync(workflowDir, { recursive: true });
330
- const recallnestPath = join(workflowDir, "recallnest.json");
331
- const externalPath = join(workflowDir, "external.json");
332
- writeFileSync(recallnestPath, JSON.stringify({
333
- workflowId: "resume_context",
334
- summary: "Managed resume_context recovered 1 stable item(s), 1 pattern(s), and 1 case(s).",
335
- source: "managed",
336
- task: "Continue RecallNest scope cleanup",
337
- recordedAt: "2026-03-18T00:00:00.000Z",
338
- observationId: "obs-recallnest",
339
- resolvedScope: "global",
340
- }, null, 2) + "\n");
341
- writeFileSync(externalPath, JSON.stringify({
342
- workflowId: "resume_context",
343
- summary: "Managed resume_context recovered 1 stable item(s), 0 pattern(s), and 0 case(s).",
344
- source: "managed",
345
- task: "A2A code Claude SDK calling error",
346
- recordedAt: "2026-03-18T00:01:00.000Z",
347
- observationId: "obs-external",
348
- resolvedScope: "global",
349
- }, null, 2) + "\n");
350
-
351
- const review = reviewWorkflowObservationScopes({
352
- dir: workflowDir,
353
- currentScope: "global",
354
- source: "managed",
355
- workflowId: "resume_context",
356
- cues: [parseWorkflowObservationScopeCue("recallnest=project:recallnest")],
357
- });
358
- expect(review.totalCount).toBe(2);
359
- expect(review.suggestedCount).toBe(1);
360
- expect(review.manualReviewCount).toBe(1);
361
-
362
- const keepResult = keepWorkflowObservationScopesGlobal(review, {
363
- ids: ["obs-external"],
364
- reason: "external-task",
365
- reviewedAt: "2026-03-18T00:05:00.000Z",
366
- });
367
- const rewriteResult = applyWorkflowObservationScopeSuggestions(review);
368
- expect(keepResult.reviewedCount).toBe(1);
369
- expect(rewriteResult.updatedCount).toBe(1);
370
-
371
- const nextReview = reviewWorkflowObservationScopes({
372
- dir: workflowDir,
373
- currentScope: "global",
374
- source: "managed",
375
- workflowId: "resume_context",
376
- cues: [parseWorkflowObservationScopeCue("recallnest=project:recallnest")],
377
- });
378
- expect(nextReview.totalCount).toBe(0);
379
-
380
- const store = new MemoryStore({
381
- dbPath: join(root, "db"),
382
- vectorDim: 3,
383
- });
384
- const inventory = await collectScopeInventory({
385
- store,
386
- sampleLimit: 5,
387
- workflowObservationsDir: workflowDir,
388
- pinsDir: join(root, "pins"),
389
- checkpointsDir: join(root, "checkpoints"),
390
- });
391
- const workflowLayer = inventory.layers.find((layer) => layer.layer === "workflow-observations");
392
- expect(inventory.totalAnomalyCount).toBe(0);
393
- expect(inventory.totalReviewedCount).toBe(1);
394
- expect(workflowLayer?.anomalyCount).toBe(0);
395
- expect(workflowLayer?.reviewedCount).toBe(1);
396
- } finally {
397
- rmSync(root, { recursive: true, force: true });
398
- }
399
- });
400
- });