@fgv/ts-agent-memory 5.1.0-41 → 5.1.0-43

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 (82) hide show
  1. package/.rush/temp/{cbbdbe09515171b4eba8f2592be2dace1e4e8142.tar.log → 285f03271c27ef724d49e730c0db58d9e4ac1a44.tar.log} +38 -2
  2. package/.rush/temp/chunked-rush-logs/ts-agent-memory.build.chunks.jsonl +2 -2
  3. package/.rush/temp/operation/build/all.log +2 -2
  4. package/.rush/temp/operation/build/log-chunks.jsonl +2 -2
  5. package/.rush/temp/operation/build/state.json +1 -1
  6. package/dist/packlets/retrieve/fragmentSemanticRetriever.js +78 -0
  7. package/dist/packlets/retrieve/fragmentSemanticRetriever.js.map +1 -0
  8. package/dist/packlets/retrieve/index.js +1 -0
  9. package/dist/packlets/retrieve/index.js.map +1 -1
  10. package/dist/packlets/store/fileTreeMemoryStore.js +105 -14
  11. package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
  12. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js +200 -0
  13. package/dist/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -0
  14. package/dist/packlets/vector/index.js +1 -0
  15. package/dist/packlets/vector/index.js.map +1 -1
  16. package/dist/packlets/vector/vectorIndex.js.map +1 -1
  17. package/dist/test/unit/retrieve/fragmentSemanticRetriever.test.js +116 -0
  18. package/dist/test/unit/retrieve/fragmentSemanticRetriever.test.js.map +1 -0
  19. package/dist/test/unit/store/fragmentEmbedOnWrite.test.js +255 -0
  20. package/dist/test/unit/store/fragmentEmbedOnWrite.test.js.map +1 -0
  21. package/dist/test/unit/store/lenientOpen.test.js +248 -0
  22. package/dist/test/unit/store/lenientOpen.test.js.map +1 -0
  23. package/dist/test/unit/vector/inMemoryFragmentCosineIndex.test.js +297 -0
  24. package/dist/test/unit/vector/inMemoryFragmentCosineIndex.test.js.map +1 -0
  25. package/dist/ts-agent-memory.d.ts +367 -4
  26. package/etc/ts-agent-memory.api.md +78 -0
  27. package/lib/packlets/retrieve/fragmentSemanticRetriever.d.ts +90 -0
  28. package/lib/packlets/retrieve/fragmentSemanticRetriever.d.ts.map +1 -0
  29. package/lib/packlets/retrieve/fragmentSemanticRetriever.js +82 -0
  30. package/lib/packlets/retrieve/fragmentSemanticRetriever.js.map +1 -0
  31. package/lib/packlets/retrieve/index.d.ts +1 -0
  32. package/lib/packlets/retrieve/index.d.ts.map +1 -1
  33. package/lib/packlets/retrieve/index.js +1 -0
  34. package/lib/packlets/retrieve/index.js.map +1 -1
  35. package/lib/packlets/store/fileTreeMemoryStore.d.ts +115 -1
  36. package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
  37. package/lib/packlets/store/fileTreeMemoryStore.js +104 -13
  38. package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
  39. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts +74 -0
  40. package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map +1 -0
  41. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js +204 -0
  42. package/lib/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -0
  43. package/lib/packlets/vector/index.d.ts +1 -0
  44. package/lib/packlets/vector/index.d.ts.map +1 -1
  45. package/lib/packlets/vector/index.js +1 -0
  46. package/lib/packlets/vector/index.js.map +1 -1
  47. package/lib/packlets/vector/vectorIndex.d.ts +85 -4
  48. package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
  49. package/lib/packlets/vector/vectorIndex.js.map +1 -1
  50. package/lib/test/unit/retrieve/fragmentSemanticRetriever.test.d.ts +2 -0
  51. package/lib/test/unit/retrieve/fragmentSemanticRetriever.test.d.ts.map +1 -0
  52. package/lib/test/unit/retrieve/fragmentSemanticRetriever.test.js +118 -0
  53. package/lib/test/unit/retrieve/fragmentSemanticRetriever.test.js.map +1 -0
  54. package/lib/test/unit/store/fragmentEmbedOnWrite.test.d.ts +2 -0
  55. package/lib/test/unit/store/fragmentEmbedOnWrite.test.d.ts.map +1 -0
  56. package/lib/test/unit/store/fragmentEmbedOnWrite.test.js +257 -0
  57. package/lib/test/unit/store/fragmentEmbedOnWrite.test.js.map +1 -0
  58. package/lib/test/unit/store/lenientOpen.test.d.ts +2 -0
  59. package/lib/test/unit/store/lenientOpen.test.d.ts.map +1 -0
  60. package/lib/test/unit/store/lenientOpen.test.js +250 -0
  61. package/lib/test/unit/store/lenientOpen.test.js.map +1 -0
  62. package/lib/test/unit/vector/inMemoryFragmentCosineIndex.test.d.ts +2 -0
  63. package/lib/test/unit/vector/inMemoryFragmentCosineIndex.test.d.ts.map +1 -0
  64. package/lib/test/unit/vector/inMemoryFragmentCosineIndex.test.js +299 -0
  65. package/lib/test/unit/vector/inMemoryFragmentCosineIndex.test.js.map +1 -0
  66. package/package.json +7 -7
  67. package/rush-logs/ts-agent-memory.build.cache.log +1 -1
  68. package/rush-logs/ts-agent-memory.build.log +2 -2
  69. package/src/packlets/retrieve/fragmentSemanticRetriever.ts +135 -0
  70. package/src/packlets/retrieve/index.ts +1 -0
  71. package/src/packlets/store/fileTreeMemoryStore.ts +208 -16
  72. package/src/packlets/vector/inMemoryFragmentCosineIndex.ts +262 -0
  73. package/src/packlets/vector/index.ts +1 -0
  74. package/src/packlets/vector/vectorIndex.ts +97 -4
  75. package/src/test/unit/retrieve/fragmentSemanticRetriever.test.ts +163 -0
  76. package/src/test/unit/store/fragmentEmbedOnWrite.test.ts +349 -0
  77. package/src/test/unit/store/lenientOpen.test.ts +292 -0
  78. package/src/test/unit/vector/inMemoryFragmentCosineIndex.test.ts +389 -0
  79. package/temp/build/lint/_eslint-5eVG3S6w.json +29 -5
  80. package/temp/build/typescript/ts_8nwakTlr.json +1 -1
  81. package/temp/ts-agent-memory.api.json +4584 -2859
  82. package/temp/ts-agent-memory.api.md +78 -0
@@ -1,5 +1,5 @@
1
- Start time: Tue Jul 14 2026 06:13:34 GMT+0000 (Coordinated Universal Time)
2
- Invoking "/usr/bin/tar -c -f /home/runner/work/fgv/fgv/common/temp/build-cache/cbbdbe09515171b4eba8f2592be2dace1e4e8142-c6a08a434367e77c.temp -z --files-from=-"
1
+ Start time: Mon Jul 20 2026 00:41:10 GMT+0000 (Coordinated Universal Time)
2
+ Invoking "/usr/bin/tar -c -f /home/runner/work/fgv/fgv/common/temp/build-cache/285f03271c27ef724d49e730c0db58d9e4ac1a44-b5b9975b621910d8.temp -z --files-from=-"
3
3
 
4
4
  ======= BEGIN PROCESS INPUT ======
5
5
  .rush/temp/operation/build/all.log
@@ -33,6 +33,8 @@ dist/packlets/observe/memoryObservationStore.js
33
33
  dist/packlets/observe/memoryObservationStore.js.map
34
34
  dist/packlets/observe/observer.js
35
35
  dist/packlets/observe/observer.js.map
36
+ dist/packlets/retrieve/fragmentSemanticRetriever.js
37
+ dist/packlets/retrieve/fragmentSemanticRetriever.js.map
36
38
  dist/packlets/retrieve/hybridRetriever.js
37
39
  dist/packlets/retrieve/hybridRetriever.js.map
38
40
  dist/packlets/retrieve/index.js
@@ -77,6 +79,8 @@ dist/packlets/types/writePolicy.js
77
79
  dist/packlets/types/writePolicy.js.map
78
80
  dist/packlets/vector/inMemoryCosineIndex.js
79
81
  dist/packlets/vector/inMemoryCosineIndex.js.map
82
+ dist/packlets/vector/inMemoryFragmentCosineIndex.js
83
+ dist/packlets/vector/inMemoryFragmentCosineIndex.js.map
80
84
  dist/packlets/vector/index.js
81
85
  dist/packlets/vector/index.js.map
82
86
  dist/packlets/vector/vectorIndex.js
@@ -97,6 +101,8 @@ dist/test/unit/ingest/orchestrator.test.js
97
101
  dist/test/unit/ingest/orchestrator.test.js.map
98
102
  dist/test/unit/observe/memoryObservationStore.test.js
99
103
  dist/test/unit/observe/memoryObservationStore.test.js.map
104
+ dist/test/unit/retrieve/fragmentSemanticRetriever.test.js
105
+ dist/test/unit/retrieve/fragmentSemanticRetriever.test.js.map
100
106
  dist/test/unit/retrieve/linkTraversalRetriever.test.js
101
107
  dist/test/unit/retrieve/linkTraversalRetriever.test.js.map
102
108
  dist/test/unit/retrieve/retrievers.test.js
@@ -109,6 +115,10 @@ dist/test/unit/store/embedOnWrite.test.js
109
115
  dist/test/unit/store/embedOnWrite.test.js.map
110
116
  dist/test/unit/store/fileTreeMemoryStore.test.js
111
117
  dist/test/unit/store/fileTreeMemoryStore.test.js.map
118
+ dist/test/unit/store/fragmentEmbedOnWrite.test.js
119
+ dist/test/unit/store/fragmentEmbedOnWrite.test.js.map
120
+ dist/test/unit/store/lenientOpen.test.js
121
+ dist/test/unit/store/lenientOpen.test.js.map
112
122
  dist/test/unit/store/listScoped.test.js
113
123
  dist/test/unit/store/listScoped.test.js.map
114
124
  dist/test/unit/store/observations.test.js
@@ -133,6 +143,8 @@ dist/test/unit/types/writePolicy.test.js
133
143
  dist/test/unit/types/writePolicy.test.js.map
134
144
  dist/test/unit/vector/inMemoryCosineIndex.test.js
135
145
  dist/test/unit/vector/inMemoryCosineIndex.test.js.map
146
+ dist/test/unit/vector/inMemoryFragmentCosineIndex.test.js
147
+ dist/test/unit/vector/inMemoryFragmentCosineIndex.test.js.map
136
148
  dist/test/unit/vector/vectorIndex.test.js
137
149
  dist/test/unit/vector/vectorIndex.test.js.map
138
150
  dist/ts-agent-memory.d.ts
@@ -193,6 +205,10 @@ lib/packlets/observe/observer.d.ts
193
205
  lib/packlets/observe/observer.d.ts.map
194
206
  lib/packlets/observe/observer.js
195
207
  lib/packlets/observe/observer.js.map
208
+ lib/packlets/retrieve/fragmentSemanticRetriever.d.ts
209
+ lib/packlets/retrieve/fragmentSemanticRetriever.d.ts.map
210
+ lib/packlets/retrieve/fragmentSemanticRetriever.js
211
+ lib/packlets/retrieve/fragmentSemanticRetriever.js.map
196
212
  lib/packlets/retrieve/hybridRetriever.d.ts
197
213
  lib/packlets/retrieve/hybridRetriever.d.ts.map
198
214
  lib/packlets/retrieve/hybridRetriever.js
@@ -281,6 +297,10 @@ lib/packlets/vector/inMemoryCosineIndex.d.ts
281
297
  lib/packlets/vector/inMemoryCosineIndex.d.ts.map
282
298
  lib/packlets/vector/inMemoryCosineIndex.js
283
299
  lib/packlets/vector/inMemoryCosineIndex.js.map
300
+ lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts
301
+ lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map
302
+ lib/packlets/vector/inMemoryFragmentCosineIndex.js
303
+ lib/packlets/vector/inMemoryFragmentCosineIndex.js.map
284
304
  lib/packlets/vector/index.d.ts
285
305
  lib/packlets/vector/index.d.ts.map
286
306
  lib/packlets/vector/index.js
@@ -321,6 +341,10 @@ lib/test/unit/observe/memoryObservationStore.test.d.ts
321
341
  lib/test/unit/observe/memoryObservationStore.test.d.ts.map
322
342
  lib/test/unit/observe/memoryObservationStore.test.js
323
343
  lib/test/unit/observe/memoryObservationStore.test.js.map
344
+ lib/test/unit/retrieve/fragmentSemanticRetriever.test.d.ts
345
+ lib/test/unit/retrieve/fragmentSemanticRetriever.test.d.ts.map
346
+ lib/test/unit/retrieve/fragmentSemanticRetriever.test.js
347
+ lib/test/unit/retrieve/fragmentSemanticRetriever.test.js.map
324
348
  lib/test/unit/retrieve/linkTraversalRetriever.test.d.ts
325
349
  lib/test/unit/retrieve/linkTraversalRetriever.test.d.ts.map
326
350
  lib/test/unit/retrieve/linkTraversalRetriever.test.js
@@ -345,6 +369,14 @@ lib/test/unit/store/fileTreeMemoryStore.test.d.ts
345
369
  lib/test/unit/store/fileTreeMemoryStore.test.d.ts.map
346
370
  lib/test/unit/store/fileTreeMemoryStore.test.js
347
371
  lib/test/unit/store/fileTreeMemoryStore.test.js.map
372
+ lib/test/unit/store/fragmentEmbedOnWrite.test.d.ts
373
+ lib/test/unit/store/fragmentEmbedOnWrite.test.d.ts.map
374
+ lib/test/unit/store/fragmentEmbedOnWrite.test.js
375
+ lib/test/unit/store/fragmentEmbedOnWrite.test.js.map
376
+ lib/test/unit/store/lenientOpen.test.d.ts
377
+ lib/test/unit/store/lenientOpen.test.d.ts.map
378
+ lib/test/unit/store/lenientOpen.test.js
379
+ lib/test/unit/store/lenientOpen.test.js.map
348
380
  lib/test/unit/store/listScoped.test.d.ts
349
381
  lib/test/unit/store/listScoped.test.d.ts.map
350
382
  lib/test/unit/store/listScoped.test.js
@@ -393,6 +425,10 @@ lib/test/unit/vector/inMemoryCosineIndex.test.d.ts
393
425
  lib/test/unit/vector/inMemoryCosineIndex.test.d.ts.map
394
426
  lib/test/unit/vector/inMemoryCosineIndex.test.js
395
427
  lib/test/unit/vector/inMemoryCosineIndex.test.js.map
428
+ lib/test/unit/vector/inMemoryFragmentCosineIndex.test.d.ts
429
+ lib/test/unit/vector/inMemoryFragmentCosineIndex.test.d.ts.map
430
+ lib/test/unit/vector/inMemoryFragmentCosineIndex.test.js
431
+ lib/test/unit/vector/inMemoryFragmentCosineIndex.test.js.map
396
432
  lib/test/unit/vector/vectorIndex.test.d.ts
397
433
  lib/test/unit/vector/vectorIndex.test.d.ts.map
398
434
  lib/test/unit/vector/vectorIndex.test.js
@@ -5,5 +5,5 @@
5
5
  {"kind":"O","text":"[build:lint] Using ESLint version 9.39.5\n"}
6
6
  {"kind":"O","text":"[build:api-extractor] Using API Extractor version 7.58.9\n"}
7
7
  {"kind":"O","text":"[build:api-extractor] Analysis will use the bundled TypeScript version 5.9.3\n"}
8
- {"kind":"O","text":" ---- build finished (26.015s) ---- \n"}
9
- {"kind":"O","text":"-------------------- Finished (26.02s) --------------------\n"}
8
+ {"kind":"O","text":" ---- build finished (26.514s) ---- \n"}
9
+ {"kind":"O","text":"-------------------- Finished (26.523s) --------------------\n"}
@@ -5,5 +5,5 @@ Invoking: heft build --clean
5
5
  [build:lint] Using ESLint version 9.39.5
6
6
  [build:api-extractor] Using API Extractor version 7.58.9
7
7
  [build:api-extractor] Analysis will use the bundled TypeScript version 5.9.3
8
- ---- build finished (26.015s) ----
9
- -------------------- Finished (26.02s) --------------------
8
+ ---- build finished (26.514s) ----
9
+ -------------------- Finished (26.523s) --------------------
@@ -5,5 +5,5 @@
5
5
  {"kind":"O","text":"[build:lint] Using ESLint version 9.39.5\n"}
6
6
  {"kind":"O","text":"[build:api-extractor] Using API Extractor version 7.58.9\n"}
7
7
  {"kind":"O","text":"[build:api-extractor] Analysis will use the bundled TypeScript version 5.9.3\n"}
8
- {"kind":"O","text":" ---- build finished (26.015s) ---- \n"}
9
- {"kind":"O","text":"-------------------- Finished (26.02s) --------------------\n"}
8
+ {"kind":"O","text":" ---- build finished (26.514s) ---- \n"}
9
+ {"kind":"O","text":"-------------------- Finished (26.523s) --------------------\n"}
@@ -1,3 +1,3 @@
1
1
  {
2
- "nonCachedDurationMs": 26865.358066
2
+ "nonCachedDurationMs": 27347.06894099999
3
3
  }
@@ -0,0 +1,78 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ import { fail, succeed } from '@fgv/ts-utils';
6
+ /**
7
+ * The loud-degradation message a {@link FragmentSemanticRetriever} returns when a
8
+ * fragment query is issued but no {@link IFragmentSemanticBackend | backend} is
9
+ * wired — the discovery surface NEVER answers a fragment query with a silent empty.
10
+ * @public
11
+ */
12
+ export const FRAGMENT_SEMANTIC_UNWIRED_MESSAGE = 'fragment recall: no fragment index is wired; wire an IFragmentSemanticBackend to enable sub-document search';
13
+ /**
14
+ * The sub-document semantic-search retriever — the "discovery" half of a
15
+ * search-then-read contract. It embeds a fragment query, queries the
16
+ * {@link IFragmentVectorIndex}, and returns the raw per-fragment
17
+ * {@link IVectorQueryHit | hits} (each carrying a record `target` AND the matched
18
+ * `locator`), NOT resolved records: the consumer re-reads each record and slices it
19
+ * by the locator on its own read side.
20
+ *
21
+ * @remarks
22
+ * Deliberately NOT an {@link IMemoryRetriever}: memory recall is record-granular and
23
+ * returns records; fragment discovery is span-granular and returns locators. Keeping
24
+ * it a distinct surface matches the consumer contract (memory stays record-granular;
25
+ * sub-document knowledge uses a separate fragment index) and avoids overloading the
26
+ * record retriever's return type with a locator that only makes sense here.
27
+ *
28
+ * When no backend is wired, `supportsFragmentRecall` is `false` and any fragment
29
+ * query degrades loudly ({@link FRAGMENT_SEMANTIC_UNWIRED_MESSAGE}) — it NEVER
30
+ * returns a silent empty. A consumer-supplied backend that rejects (throws) is
31
+ * normalized into a `Failure`.
32
+ * @public
33
+ */
34
+ export class FragmentSemanticRetriever {
35
+ constructor(backend) {
36
+ this._backend = backend;
37
+ }
38
+ /** What this retriever can do given its wiring. */
39
+ get capabilities() {
40
+ return { supportsFragmentRecall: this._backend !== undefined };
41
+ }
42
+ /** Family-convention factory. */
43
+ static create(params) {
44
+ return succeed(new FragmentSemanticRetriever(params.backend));
45
+ }
46
+ /**
47
+ * Embed `query.semantic`, query the fragment index, and return the per-fragment
48
+ * hits in descending score order. Fails loudly when no backend is wired.
49
+ */
50
+ async retrieve(query) {
51
+ if (this._backend === undefined) {
52
+ return fail(FRAGMENT_SEMANTIC_UNWIRED_MESSAGE);
53
+ }
54
+ const backend = this._backend;
55
+ // Consumer-supplied hooks may throw; normalize both a returned `fail` and a
56
+ // rejection into a single `fragment recall: <label> failed` Failure so
57
+ // `retrieve` always honors its `Promise<Result<...>>` contract.
58
+ const embedded = await FragmentSemanticRetriever._callBackend('query embedding', () => backend.embedQuery(query.semantic));
59
+ if (embedded.isFailure()) {
60
+ return fail(embedded.message);
61
+ }
62
+ return FragmentSemanticRetriever._callBackend('fragment query', () => { var _a; return backend.fragmentIndex.query(embedded.value, (_a = query.topK) !== null && _a !== void 0 ? _a : 10, query.maxPerRecord); });
63
+ }
64
+ /**
65
+ * Invoke a consumer-supplied backend hook, normalizing both a returned `fail`
66
+ * and a thrown/rejected promise into a single `fragment recall: <label> failed`
67
+ * `Failure`.
68
+ */
69
+ static async _callBackend(label, op) {
70
+ try {
71
+ return (await op()).withErrorFormat((msg) => `fragment recall: ${label} failed: ${msg}`);
72
+ }
73
+ catch (err) {
74
+ return fail(`fragment recall: ${label} failed: ${String(err)}`);
75
+ }
76
+ }
77
+ }
78
+ //# sourceMappingURL=fragmentSemanticRetriever.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fragmentSemanticRetriever.js","sourceRoot":"","sources":["../../../src/packlets/retrieve/fragmentSemanticRetriever.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAU,IAAI,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAItD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAC5C,6GAA6G,CAAC;AA0ChH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,OAAO,yBAAyB;IAGpC,YAAoB,OAA6C;QAC/D,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC1B,CAAC;IAED,mDAAmD;IACnD,IAAW,YAAY;QACrB,OAAO,EAAE,sBAAsB,EAAE,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;IACjE,CAAC;IAED,iCAAiC;IAC1B,MAAM,CAAC,MAAM,CAAC,MAEpB;QACC,OAAO,OAAO,CAAC,IAAI,yBAAyB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAChE,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,QAAQ,CAAC,KAAqB;QACzC,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC,iCAAiC,CAAC,CAAC;QACjD,CAAC;QACD,MAAM,OAAO,GAA6B,IAAI,CAAC,QAAQ,CAAC;QACxD,4EAA4E;QAC5E,uEAAuE;QACvE,gEAAgE;QAChE,MAAM,QAAQ,GAAyB,MAAM,yBAAyB,CAAC,YAAY,CACjF,iBAAiB,EACjB,GAAG,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CACzC,CAAC;QACF,IAAI,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,yBAAyB,CAAC,YAAY,CAAC,gBAAgB,EAAE,GAAG,EAAE,WACnE,OAAA,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAA,KAAK,CAAC,IAAI,mCAAI,EAAE,EAAE,KAAK,CAAC,YAAY,CAAC,CAAA,EAAA,CAClF,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,KAAK,CAAC,YAAY,CAAI,KAAa,EAAE,EAA4B;QAC9E,IAAI,CAAC;YACH,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,oBAAoB,KAAK,YAAY,GAAG,EAAE,CAAC,CAAC;QAC3F,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,oBAAoB,KAAK,YAAY,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;CACF","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result, fail, succeed } from '@fgv/ts-utils';\nimport { IFragmentVectorIndex, IVectorQueryHit } from '../vector';\nimport { QueryEmbedder } from './semanticRetriever';\n\n/**\n * The loud-degradation message a {@link FragmentSemanticRetriever} returns when a\n * fragment query is issued but no {@link IFragmentSemanticBackend | backend} is\n * wired — the discovery surface NEVER answers a fragment query with a silent empty.\n * @public\n */\nexport const FRAGMENT_SEMANTIC_UNWIRED_MESSAGE: string =\n 'fragment recall: no fragment index is wired; wire an IFragmentSemanticBackend to enable sub-document search';\n\n/**\n * The fragment backend wired into a {@link FragmentSemanticRetriever}: the fragment\n * index to query and the embedder that turns the query text into a vector. Both are\n * required together — a fragment index is useless without a way to embed the query.\n * @public\n */\nexport interface IFragmentSemanticBackend {\n /** The fragment-granular vector index to query. */\n readonly fragmentIndex: IFragmentVectorIndex;\n /** Turns the query text into a vector. */\n readonly embedQuery: QueryEmbedder;\n}\n\n/**\n * A sub-document semantic-search request: the natural-language `semantic` text to\n * match, an optional `topK` result cap (default 10), and an optional\n * `maxPerRecord` cap that keeps one long document from monopolizing the result.\n * @public\n */\nexport interface IFragmentQuery {\n /** The natural-language text to embed and match against stored fragments. */\n readonly semantic: string;\n /** Maximum number of fragment hits to return. Defaults to 10. */\n readonly topK?: number;\n /**\n * Maximum number of fragments any single record may contribute to the result.\n * Applied during selection (before the `topK` cut). Omit for uncapped.\n */\n readonly maxPerRecord?: number;\n}\n\n/**\n * What a {@link FragmentSemanticRetriever} can do given its wiring.\n * @public\n */\nexport interface IFragmentRetrieverCapabilities {\n /** `true` when a fragment backend is wired and fragment recall is operational. */\n readonly supportsFragmentRecall: boolean;\n}\n\n/**\n * The sub-document semantic-search retriever — the \"discovery\" half of a\n * search-then-read contract. It embeds a fragment query, queries the\n * {@link IFragmentVectorIndex}, and returns the raw per-fragment\n * {@link IVectorQueryHit | hits} (each carrying a record `target` AND the matched\n * `locator`), NOT resolved records: the consumer re-reads each record and slices it\n * by the locator on its own read side.\n *\n * @remarks\n * Deliberately NOT an {@link IMemoryRetriever}: memory recall is record-granular and\n * returns records; fragment discovery is span-granular and returns locators. Keeping\n * it a distinct surface matches the consumer contract (memory stays record-granular;\n * sub-document knowledge uses a separate fragment index) and avoids overloading the\n * record retriever's return type with a locator that only makes sense here.\n *\n * When no backend is wired, `supportsFragmentRecall` is `false` and any fragment\n * query degrades loudly ({@link FRAGMENT_SEMANTIC_UNWIRED_MESSAGE}) — it NEVER\n * returns a silent empty. A consumer-supplied backend that rejects (throws) is\n * normalized into a `Failure`.\n * @public\n */\nexport class FragmentSemanticRetriever {\n private readonly _backend: IFragmentSemanticBackend | undefined;\n\n private constructor(backend: IFragmentSemanticBackend | undefined) {\n this._backend = backend;\n }\n\n /** What this retriever can do given its wiring. */\n public get capabilities(): IFragmentRetrieverCapabilities {\n return { supportsFragmentRecall: this._backend !== undefined };\n }\n\n /** Family-convention factory. */\n public static create(params: {\n readonly backend?: IFragmentSemanticBackend;\n }): Result<FragmentSemanticRetriever> {\n return succeed(new FragmentSemanticRetriever(params.backend));\n }\n\n /**\n * Embed `query.semantic`, query the fragment index, and return the per-fragment\n * hits in descending score order. Fails loudly when no backend is wired.\n */\n public async retrieve(query: IFragmentQuery): Promise<Result<ReadonlyArray<IVectorQueryHit>>> {\n if (this._backend === undefined) {\n return fail(FRAGMENT_SEMANTIC_UNWIRED_MESSAGE);\n }\n const backend: IFragmentSemanticBackend = this._backend;\n // Consumer-supplied hooks may throw; normalize both a returned `fail` and a\n // rejection into a single `fragment recall: <label> failed` Failure so\n // `retrieve` always honors its `Promise<Result<...>>` contract.\n const embedded: Result<Float32Array> = await FragmentSemanticRetriever._callBackend(\n 'query embedding',\n () => backend.embedQuery(query.semantic)\n );\n if (embedded.isFailure()) {\n return fail(embedded.message);\n }\n return FragmentSemanticRetriever._callBackend('fragment query', () =>\n backend.fragmentIndex.query(embedded.value, query.topK ?? 10, query.maxPerRecord)\n );\n }\n\n /**\n * Invoke a consumer-supplied backend hook, normalizing both a returned `fail`\n * and a thrown/rejected promise into a single `fragment recall: <label> failed`\n * `Failure`.\n */\n private static async _callBackend<T>(label: string, op: () => Promise<Result<T>>): Promise<Result<T>> {\n try {\n return (await op()).withErrorFormat((msg) => `fragment recall: ${label} failed: ${msg}`);\n } catch (err) {\n return fail(`fragment recall: ${label} failed: ${String(err)}`);\n }\n }\n}\n"]}
@@ -8,6 +8,7 @@ export * from './linkTraversalRetriever';
8
8
  export * from './tagRetriever';
9
9
  export * from './structuredFilterRetriever';
10
10
  export * from './semanticRetriever';
11
+ export * from './fragmentSemanticRetriever';
11
12
  export * from './temporalRetrievers';
12
13
  export * from './hybridRetriever';
13
14
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/retrieve/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nexport * from './retriever';\nexport * from './recencyRetriever';\nexport * from './linkTraversalRetriever';\nexport * from './tagRetriever';\nexport * from './structuredFilterRetriever';\nexport * from './semanticRetriever';\nexport * from './temporalRetrievers';\nexport * from './hybridRetriever';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/retrieve/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nexport * from './retriever';\nexport * from './recencyRetriever';\nexport * from './linkTraversalRetriever';\nexport * from './tagRetriever';\nexport * from './structuredFilterRetriever';\nexport * from './semanticRetriever';\nexport * from './fragmentSemanticRetriever';\nexport * from './temporalRetrievers';\nexport * from './hybridRetriever';\n"]}
@@ -2,7 +2,7 @@
2
2
  * Copyright (c) 2026 Erik Fortune
3
3
  * SPDX-License-Identifier: MIT
4
4
  */
5
- import { Hash, Logging, fail, mapResults, succeed } from '@fgv/ts-utils';
5
+ import { Hash, Logging, fail, mapResults, mapSuccess, succeed } from '@fgv/ts-utils';
6
6
  import { FileTree } from '@fgv/ts-json-base';
7
7
  import { DEFAULT_DEDUP_SCOPE, KnowledgeLwwPolicy, isTemporalIdentityCodec, isTemporalRecord, isVersionCurrent, selectCurrentVersion, selectVersionAsOf } from '../types';
8
8
  import { parseMemoryFile, serializeMemoryFile } from '../converters';
@@ -41,10 +41,24 @@ export class FileTreeMemoryStore {
41
41
  this._logger = params.logger;
42
42
  this._vectorIndex = params.vectorIndex;
43
43
  this._embed = params.embed;
44
+ this._fragmentIndex = params.fragmentIndex;
45
+ this._fragmentEmbedder = params.fragmentEmbedder;
46
+ this._skippedRecords = [];
44
47
  this._seq = 0;
45
48
  this._observationSeq = 0;
46
49
  this._writeTail = Promise.resolve();
47
50
  }
51
+ /**
52
+ * Records the initial vault walk could not parse or validate and quarantined
53
+ * (not indexed). Non-empty only when the store was opened with
54
+ * {@link MemoryRecordErrorMode | `onRecordError: 'skip'`} AND at least one
55
+ * record failed to load. Each entry identifies the offending file so a host
56
+ * can repair it; the file itself is never deleted or mutated, so a later open
57
+ * (after the body converter is fixed) re-indexes it.
58
+ */
59
+ get skippedRecords() {
60
+ return this._skippedRecords;
61
+ }
48
62
  /**
49
63
  * Family-convention factory. Builds the derived index and a default LWW
50
64
  * policy, then performs an initial FileTree walk so an existing vault is
@@ -52,7 +66,7 @@ export class FileTreeMemoryStore {
52
66
  */
53
67
  static create(params) {
54
68
  return KnowledgeLwwPolicy.create().onSuccess((defaultPolicy) => MemoryIndex.create().onSuccess((index) => {
55
- var _a, _b, _c, _d, _e, _f, _g;
69
+ var _a, _b, _c, _d, _e, _f, _g, _h;
56
70
  const store = new FileTreeMemoryStore({
57
71
  root: params.root,
58
72
  registry: params.registry,
@@ -67,9 +81,11 @@ export class FileTreeMemoryStore {
67
81
  observers: (_f = params.observers) !== null && _f !== void 0 ? _f : [],
68
82
  logger: (_g = params.logger) !== null && _g !== void 0 ? _g : new Logging.NoOpLogger(),
69
83
  vectorIndex: params.vectorIndex,
70
- embed: params.embed
84
+ embed: params.embed,
85
+ fragmentIndex: params.fragmentIndex,
86
+ fragmentEmbedder: params.fragmentEmbedder
71
87
  });
72
- return store._initialIndex().onSuccess(() => succeed(store));
88
+ return store._initialIndex((_h = params.onRecordError) !== null && _h !== void 0 ? _h : 'fail').onSuccess(() => succeed(store));
73
89
  }));
74
90
  }
75
91
  /** {@inheritDoc IMemoryStore.get} */
@@ -386,6 +402,7 @@ export class FileTreeMemoryStore {
386
402
  return this._buildRecord(record, body, existing, policy, hash)
387
403
  .onSuccess((built) => succeed(this._stampRank(built)))
388
404
  .thenOnSuccess((built) => this._embedOnWrite(built, scope))
405
+ .thenOnSuccess((built) => this._embedFragmentsOnWrite(built, scope))
389
406
  .onSuccess((embeddedBuilt) => this._persist(embeddedBuilt, scope, idStem))
390
407
  .thenOnSuccess(async (persisted) => {
391
408
  // Everything after the authoritative `_persist` commit is best-effort and
@@ -428,6 +445,45 @@ export class FileTreeMemoryStore {
428
445
  }
429
446
  return succeed({ envelope: Object.assign(Object.assign({}, built.envelope), { embeddingRef: added.value }), body: built.body });
430
447
  }
448
+ /**
449
+ * Best-effort fragment-embed-on-write. When a fragment index AND a fragment
450
+ * embedder are wired, chunks + embeds the built record and replaces its
451
+ * fragments in the index (`addFragments` is whole-record-replace, so a re-authored
452
+ * document never leaves stale fragments behind — no explicit remove needed). A
453
+ * failure (returned `fail` OR a thrown/rejected hook) is logged and the record is
454
+ * returned unchanged — the put still persists, and the fragment index is a derived
455
+ * view a later `rebuild` reconciles. Unlike {@link FileTreeMemoryStore._embedOnWrite}
456
+ * it stamps nothing on the record (fragments have no per-record `embeddingRef`
457
+ * analog). A pass-through no-op when unwired (byte-identical record).
458
+ */
459
+ async _embedFragmentsOnWrite(built, scope) {
460
+ if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {
461
+ return succeed(built);
462
+ }
463
+ const fragmentIndex = this._fragmentIndex;
464
+ const fragmentEmbedder = this._fragmentEmbedder;
465
+ const target = { scope, id: built.envelope.id };
466
+ const embedded = await this._tryVectorOp(() => fragmentEmbedder(built), `fragment embedding '${built.envelope.id}'`);
467
+ if (embedded.isFailure()) {
468
+ return succeed(built);
469
+ }
470
+ await this._tryVectorOp(() => fragmentIndex.addFragments(target, embedded.value), `fragment add for '${built.envelope.id}'`);
471
+ return succeed(built);
472
+ }
473
+ /**
474
+ * Best-effort fragment removal. A no-op unless the full fragment lifecycle is
475
+ * wired (both an index AND an embedder), so an unwired store does no fragment
476
+ * work and behaves byte-identically. Failures are logged, never surfaced — a
477
+ * committed delete/eviction must not fail because a derived fragment index could
478
+ * not be pruned.
479
+ */
480
+ async _removeFragmentsBestEffort(target) {
481
+ if (this._fragmentIndex === undefined || this._fragmentEmbedder === undefined) {
482
+ return;
483
+ }
484
+ const fragmentIndex = this._fragmentIndex;
485
+ await this._tryVectorOp(() => fragmentIndex.remove(target), `fragment removal for '${target.id}'`);
486
+ }
431
487
  /**
432
488
  * Evict the records named by a `cull-oldest` decision, best-effort. Runs only
433
489
  * after the authoritative `_persist`, so a failed eviction is logged (never
@@ -460,6 +516,7 @@ export class FileTreeMemoryStore {
460
516
  async _removeEvictedVectors(evicted, scope) {
461
517
  for (const id of evicted) {
462
518
  await this._removeVectorBestEffort({ scope, id });
519
+ await this._removeFragmentsBestEffort({ scope, id });
463
520
  }
464
521
  }
465
522
  /**
@@ -476,7 +533,7 @@ export class FileTreeMemoryStore {
476
533
  result = fail(`${label} threw: ${String(err)}`);
477
534
  }
478
535
  if (result.isFailure()) {
479
- this._warnSwallowed(`memory: ${label} failed (best-effort; vector index left for rebuild): ${result.message}`);
536
+ this._warnSwallowed(`memory: ${label} failed (best-effort; derived index left for rebuild): ${result.message}`);
480
537
  }
481
538
  return result;
482
539
  }
@@ -563,6 +620,7 @@ export class FileTreeMemoryStore {
563
620
  .onSuccess(() => this._index.patch('delete', { scope, record: existing }))
564
621
  .thenOnSuccess(async () => {
565
622
  await this._removeVectorBestEffort({ scope, id: existing.envelope.id });
623
+ await this._removeFragmentsBestEffort({ scope, id: existing.envelope.id });
566
624
  return succeed(existing.envelope.id);
567
625
  });
568
626
  });
@@ -649,6 +707,7 @@ export class FileTreeMemoryStore {
649
707
  .thenOnSuccess((versionStem) => this._buildVersionedRecord(record, body, current, policy, hash, versionStem, validAt, now, seq)
650
708
  .onSuccess((built) => succeed(this._stampRank(built)))
651
709
  .thenOnSuccess((built) => this._embedOnWrite(built, scope))
710
+ .thenOnSuccess((built) => this._embedFragmentsOnWrite(built, scope))
652
711
  .onSuccess((embeddedBuilt) => this._persist(embeddedBuilt, scope, versionStem))
653
712
  .onSuccess((persisted) => this._invalidateCurrents(scope, priorCurrents, validAt, now).onSuccess(() => succeed(persisted)))
654
713
  .onSuccess((persisted) => succeed({ record: persisted, evicted: [] })));
@@ -985,9 +1044,13 @@ export class FileTreeMemoryStore {
985
1044
  /**
986
1045
  * Walk the FileTree once and rebuild the index. Also resumes the `seq`
987
1046
  * counter past the highest persisted `seq` so new writes stay monotonic.
1047
+ *
1048
+ * In `'skip'` mode each per-record failure is captured structurally on
1049
+ * `this._skippedRecords` (path + scope + path-tagged error) at its failure
1050
+ * site and logged at `warn`; the walk keeps every record that loaded.
988
1051
  */
989
- _initialIndex() {
990
- return this._collectEntries(this._root, []).onSuccess((entries) => this._index.rebuild(entries).onSuccess(() => {
1052
+ _initialIndex(onRecordError) {
1053
+ return this._collectEntries(this._root, [], onRecordError).onSuccess((entries) => this._index.rebuild(entries).onSuccess(() => {
991
1054
  for (const entry of entries) {
992
1055
  if (entry.record.envelope.seq > this._seq) {
993
1056
  this._seq = entry.record.envelope.seq;
@@ -997,11 +1060,11 @@ export class FileTreeMemoryStore {
997
1060
  }));
998
1061
  }
999
1062
  /** Recursively collect every `.md` record under `dir` (scope = path segments). */
1000
- _collectEntries(dir, scopeSegments) {
1063
+ _collectEntries(dir, scopeSegments, onRecordError) {
1001
1064
  return dir.getChildren().onSuccess((children) => {
1002
1065
  const results = children.map((child) => {
1003
1066
  if (child.type === 'directory') {
1004
- return this._collectEntries(child, [...scopeSegments, child.name]);
1067
+ return this._collectEntries(child, [...scopeSegments, child.name], onRecordError);
1005
1068
  }
1006
1069
  if (!child.name.endsWith(MEMORY_FILE_EXTENSION) || scopeSegments.length === 0) {
1007
1070
  // Skip non-record files and any record-shaped file sitting at the root
@@ -1009,15 +1072,43 @@ export class FileTreeMemoryStore {
1009
1072
  return succeed([]);
1010
1073
  }
1011
1074
  const scope = scopeSegments.join('/');
1012
- return child
1013
- .getRawContents()
1014
- .onSuccess((raw) => parseMemoryFile(raw, this._registry))
1015
- .onSuccess((parsedRecord) => this._verifyLoaded(scope, child, parsedRecord))
1016
- .onSuccess((verified) => succeed([{ scope, record: verified }]));
1075
+ return this._loadRecordFile(scope, child, onRecordError);
1017
1076
  });
1077
+ // `'skip'` mode: keep every record that parsed, drop the ones that failed
1078
+ // in a single pass (each failure is captured on `this._skippedRecords` and
1079
+ // warn-logged at its site in `_loadRecordFile`). `.orDefault([])` covers
1080
+ // the all-invalid-subtree edge where `mapSuccess` returns Failure because
1081
+ // no element succeeded. `'fail'` mode: `mapResults` fails the whole open on
1082
+ // any bad record — byte-identical to the historical load path.
1083
+ if (onRecordError === 'skip') {
1084
+ return succeed(mapSuccess(results).orDefault([]).flat());
1085
+ }
1018
1086
  return mapResults(results).onSuccess((perChild) => succeed(perChild.flat()));
1019
1087
  });
1020
1088
  }
1089
+ /**
1090
+ * Load and verify one record file. On failure in `'skip'` mode, records the
1091
+ * structured {@link ISkippedRecord} identity (path + scope + path-tagged
1092
+ * error) and logs the skip at `warn`; the failure is still returned so the
1093
+ * caller's `mapSuccess` drops it from the loaded set. In `'fail'` mode the
1094
+ * failure passes through untouched so the historical error is byte-identical.
1095
+ */
1096
+ _loadRecordFile(scope, child, onRecordError) {
1097
+ return child
1098
+ .getRawContents()
1099
+ .onSuccess((raw) => parseMemoryFile(raw, this._registry))
1100
+ .onSuccess((parsedRecord) => this._verifyLoaded(scope, child, parsedRecord))
1101
+ .onSuccess((verified) => succeed([{ scope, record: verified }]))
1102
+ .onFailure((message) => {
1103
+ if (onRecordError === 'skip') {
1104
+ const path = `${scope}/${child.name}`;
1105
+ const error = `memory record '${path}': ${message}`;
1106
+ this._skippedRecords.push({ path, scope, error });
1107
+ this._warnSwallowed(error);
1108
+ }
1109
+ return fail(message);
1110
+ });
1111
+ }
1021
1112
  }
1022
1113
  /**
1023
1114
  * The record-level mutable-field vocabulary: maps a declared mutable field