@remnic/core 9.3.688 → 9.3.690

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 (163) hide show
  1. package/dist/access-boundary.d.ts +5 -4
  2. package/dist/access-boundary.js +12 -11
  3. package/dist/access-cli.js +90 -26
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +4 -3
  6. package/dist/access-http.js +15 -14
  7. package/dist/access-mcp.d.ts +11 -3
  8. package/dist/access-mcp.js +14 -13
  9. package/dist/access-operations.d.ts +11 -5
  10. package/dist/access-operations.js +15 -12
  11. package/dist/{access-service-DmCHJ4cH.d.ts → access-service-Dujr3MTm.d.ts} +62 -2
  12. package/dist/access-service.d.ts +4 -3
  13. package/dist/access-service.js +11 -10
  14. package/dist/access-surface-catalog.d.ts +4 -3
  15. package/dist/access-surface-catalog.js +2 -0
  16. package/dist/access-surface-catalog.js.map +1 -1
  17. package/dist/bootstrap.d.ts +3 -2
  18. package/dist/briefing.d.ts +1 -0
  19. package/dist/briefing.js +3 -2
  20. package/dist/buffer.d.ts +1 -0
  21. package/dist/{catalog-COqWZlZ6.d.ts → catalog-CKPtJ114.d.ts} +1 -1
  22. package/dist/causal-consolidation.js +4 -3
  23. package/dist/causal-consolidation.js.map +1 -1
  24. package/dist/{chunk-B4XVLHJA.js → chunk-2N6UNJSX.js} +2 -2
  25. package/dist/{chunk-NN7QYW5W.js → chunk-46URPRE6.js} +2 -2
  26. package/dist/chunk-46URPRE6.js.map +1 -0
  27. package/dist/{chunk-4N3TFFPH.js → chunk-54PVJDO5.js} +2 -2
  28. package/dist/{chunk-PH3HOKYW.js → chunk-67MMWC74.js} +2 -2
  29. package/dist/{chunk-STOEE37X.js → chunk-BKAMHZYR.js} +2 -2
  30. package/dist/{chunk-B43NZNMG.js → chunk-CE6CBRCV.js} +432 -2240
  31. package/dist/chunk-CE6CBRCV.js.map +1 -0
  32. package/dist/{chunk-RVYD6LR3.js → chunk-CP2NZQLT.js} +15 -4
  33. package/dist/chunk-CP2NZQLT.js.map +1 -0
  34. package/dist/{chunk-BLIWOONZ.js → chunk-CUNTLEJP.js} +4 -12
  35. package/dist/chunk-CUNTLEJP.js.map +1 -0
  36. package/dist/{chunk-2SJCWLQD.js → chunk-CWE74HRG.js} +3 -3
  37. package/dist/{chunk-VX6OBUDW.js → chunk-GR77Z2BM.js} +2 -2
  38. package/dist/{chunk-CTOQEZSN.js → chunk-LXIEXSHU.js} +2 -2
  39. package/dist/{chunk-KFBOZYME.js → chunk-NSVXK7M5.js} +33 -4
  40. package/dist/chunk-NSVXK7M5.js.map +1 -0
  41. package/dist/{chunk-473JIN2U.js → chunk-OMKUJTVJ.js} +37 -5
  42. package/dist/chunk-OMKUJTVJ.js.map +1 -0
  43. package/dist/{chunk-GS55WYRL.js → chunk-PHZKALOE.js} +2 -2
  44. package/dist/{chunk-6O6A6YUO.js → chunk-RGNEARXW.js} +3 -3
  45. package/dist/{chunk-4FE2K57M.js → chunk-RTFAZOIR.js} +2 -2
  46. package/dist/{chunk-UTYBJR7M.js → chunk-SB6CQTKP.js} +2 -2
  47. package/dist/chunk-SVOZFLIQ.js +16 -0
  48. package/dist/chunk-SVOZFLIQ.js.map +1 -0
  49. package/dist/{chunk-2KAYTPPT.js → chunk-TYF3D4MS.js} +5 -5
  50. package/dist/{chunk-WIHPNY65.js → chunk-UD5OKH4J.js} +2 -2
  51. package/dist/{chunk-QANVLERJ.js → chunk-UPIBE2DK.js} +2 -2
  52. package/dist/{chunk-CCWHPGT4.js → chunk-WROKD3XC.js} +45 -30
  53. package/dist/chunk-WROKD3XC.js.map +1 -0
  54. package/dist/{chunk-ZPTISBQU.js → chunk-X5J3JZR3.js} +7 -7
  55. package/dist/{chunk-OV4D5T7V.js → chunk-X7RLU5CR.js} +2 -2
  56. package/dist/{chunk-S2OU5DZY.js → chunk-XKUKJIOY.js} +30 -7
  57. package/dist/chunk-XKUKJIOY.js.map +1 -0
  58. package/dist/{chunk-FUCUR2OZ.js → chunk-Z7KILAOU.js} +603 -9
  59. package/dist/chunk-Z7KILAOU.js.map +1 -0
  60. package/dist/chunk-ZU7N3S6V.js +2190 -0
  61. package/dist/chunk-ZU7N3S6V.js.map +1 -0
  62. package/dist/{chunk-DCWIQFNA.js → chunk-ZUDM75KG.js} +4 -4
  63. package/dist/{cli-D8nZ2MPH.d.ts → cli-BkDp6WNi.d.ts} +2 -2
  64. package/dist/cli.d.ts +5 -4
  65. package/dist/cli.js +32 -30
  66. package/dist/compounding/engine.d.ts +1 -0
  67. package/dist/compounding/engine.js +3 -2
  68. package/dist/connectors/codex-materialize-runner.js +3 -2
  69. package/dist/connectors/index.js +3 -2
  70. package/dist/consolidation-provenance-check.d.ts +1 -0
  71. package/dist/consolidation-undo.d.ts +1 -0
  72. package/dist/contradiction/index.d.ts +1 -0
  73. package/dist/conversation-index/backend.js +2 -2
  74. package/dist/entity-retrieval.d.ts +1 -0
  75. package/dist/entity-retrieval.js +3 -2
  76. package/dist/explicit-capture.d.ts +3 -2
  77. package/dist/index.d.ts +6 -5
  78. package/dist/index.js +47 -44
  79. package/dist/index.js.map +1 -1
  80. package/dist/lcm/engine.js +3 -3
  81. package/dist/lcm/index.js +5 -5
  82. package/dist/maintenance/memory-governance.js +3 -2
  83. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -2
  84. package/dist/maintenance/rebuild-memory-projection.js +4 -3
  85. package/dist/mcp-memory-inspector-app.d.ts +4 -3
  86. package/dist/memory-worth-outcomes.d.ts +1 -0
  87. package/dist/namespaces/migrate.d.ts +2 -1
  88. package/dist/namespaces/migrate.js +10 -9
  89. package/dist/namespaces/search.js +6 -6
  90. package/dist/namespaces/storage.d.ts +2 -1
  91. package/dist/namespaces/storage.js +3 -2
  92. package/dist/operator-toolkit.d.ts +1 -0
  93. package/dist/operator-toolkit.js +13 -11
  94. package/dist/{orchestrator-CA6ouzBn.d.ts → orchestrator-B7ixmUkP.d.ts} +145 -1
  95. package/dist/orchestrator.d.ts +3 -2
  96. package/dist/orchestrator.js +22 -20
  97. package/dist/recall-pipeline-stages.d.ts +167 -0
  98. package/dist/recall-pipeline-stages.js +61 -0
  99. package/dist/recall-pipeline-stages.js.map +1 -0
  100. package/dist/schemas.d.ts +10 -10
  101. package/dist/search/factory.js +5 -5
  102. package/dist/search/index.js +9 -9
  103. package/dist/semantic-consolidation.js +4 -3
  104. package/dist/semantic-rule-promotion.js +3 -2
  105. package/dist/semantic-rule-verifier.js +3 -2
  106. package/dist/storage.d.ts +3 -27
  107. package/dist/storage.js +5 -3
  108. package/dist/structured-attributes.d.ts +29 -0
  109. package/dist/structured-attributes.js +8 -0
  110. package/dist/structured-attributes.js.map +1 -0
  111. package/dist/temporal-supersession.d.ts +1 -0
  112. package/dist/tier-migration.d.ts +1 -0
  113. package/dist/verified-recall.js +3 -2
  114. package/package.json +2 -2
  115. package/src/access-boundary.ts +2 -1
  116. package/src/access-cli.test.ts +40 -0
  117. package/src/access-cli.ts +90 -2
  118. package/src/access-http.ts +35 -6
  119. package/src/access-mcp.ts +34 -0
  120. package/src/access-operations.ts +45 -0
  121. package/src/access-service.ts +60 -0
  122. package/src/access-surface-catalog.test.ts +1 -1
  123. package/src/access-surface-catalog.ts +2 -0
  124. package/src/cli.ts +18 -0
  125. package/src/coding/architecture-card.test.ts +544 -0
  126. package/src/coding/architecture-card.ts +687 -0
  127. package/src/coding/architecture-surfaces.test.ts +579 -0
  128. package/src/coding/architecture-surfaces.ts +457 -0
  129. package/src/maintenance/namespace-maintenance-fanout.test.ts +595 -0
  130. package/src/maintenance/namespace-maintenance-fanout.ts +318 -0
  131. package/src/maintenance/namespace-planner.ts +74 -16
  132. package/src/operator-toolkit.ts +25 -0
  133. package/src/orchestrator.ts +144 -0
  134. package/src/recall-pipeline-stages.test.ts +269 -0
  135. package/src/recall-pipeline-stages.ts +294 -0
  136. package/src/storage.ts +6 -20
  137. package/src/structured-attributes.ts +39 -0
  138. package/dist/chunk-473JIN2U.js.map +0 -1
  139. package/dist/chunk-B43NZNMG.js.map +0 -1
  140. package/dist/chunk-BLIWOONZ.js.map +0 -1
  141. package/dist/chunk-CCWHPGT4.js.map +0 -1
  142. package/dist/chunk-FUCUR2OZ.js.map +0 -1
  143. package/dist/chunk-KFBOZYME.js.map +0 -1
  144. package/dist/chunk-NN7QYW5W.js.map +0 -1
  145. package/dist/chunk-RVYD6LR3.js.map +0 -1
  146. package/dist/chunk-S2OU5DZY.js.map +0 -1
  147. /package/dist/{chunk-B4XVLHJA.js.map → chunk-2N6UNJSX.js.map} +0 -0
  148. /package/dist/{chunk-4N3TFFPH.js.map → chunk-54PVJDO5.js.map} +0 -0
  149. /package/dist/{chunk-PH3HOKYW.js.map → chunk-67MMWC74.js.map} +0 -0
  150. /package/dist/{chunk-STOEE37X.js.map → chunk-BKAMHZYR.js.map} +0 -0
  151. /package/dist/{chunk-2SJCWLQD.js.map → chunk-CWE74HRG.js.map} +0 -0
  152. /package/dist/{chunk-VX6OBUDW.js.map → chunk-GR77Z2BM.js.map} +0 -0
  153. /package/dist/{chunk-CTOQEZSN.js.map → chunk-LXIEXSHU.js.map} +0 -0
  154. /package/dist/{chunk-GS55WYRL.js.map → chunk-PHZKALOE.js.map} +0 -0
  155. /package/dist/{chunk-6O6A6YUO.js.map → chunk-RGNEARXW.js.map} +0 -0
  156. /package/dist/{chunk-4FE2K57M.js.map → chunk-RTFAZOIR.js.map} +0 -0
  157. /package/dist/{chunk-UTYBJR7M.js.map → chunk-SB6CQTKP.js.map} +0 -0
  158. /package/dist/{chunk-2KAYTPPT.js.map → chunk-TYF3D4MS.js.map} +0 -0
  159. /package/dist/{chunk-WIHPNY65.js.map → chunk-UD5OKH4J.js.map} +0 -0
  160. /package/dist/{chunk-QANVLERJ.js.map → chunk-UPIBE2DK.js.map} +0 -0
  161. /package/dist/{chunk-ZPTISBQU.js.map → chunk-X5J3JZR3.js.map} +0 -0
  162. /package/dist/{chunk-OV4D5T7V.js.map → chunk-X7RLU5CR.js.map} +0 -0
  163. /package/dist/{chunk-DCWIQFNA.js.map → chunk-ZUDM75KG.js.map} +0 -0
@@ -0,0 +1,167 @@
1
+ import { EvidencePackItem } from './evidence-pack.js';
2
+
3
+ /**
4
+ * Issue #1539 PR2 — the staged recall pipeline spine.
5
+ *
6
+ * The four recall pipelines (`targeted-fact-recall.ts`, `response-guidance-
7
+ * recall.ts`, `explicit-cue-recall.ts`, `event-order-recall.ts`) implement
8
+ * the same stage sequence — intent → candidate collection → dedup → rank →
9
+ * filter → slice → metadata insertion → token budgeting — with ~70%
10
+ * near-duplicate code. The most dangerous divergence is the sort
11
+ * comparator: guidance/targeted-fact sort `turnIndex` DESC (recency) while
12
+ * event-order sorts `turnIndex` ASC (chronological). The comparators are
13
+ * byte-identical except for this direction — copy-pasting into a new
14
+ * chronological pipeline silently inverts the ordering.
15
+ *
16
+ * This module centralizes the **dedup + score + threshold-filter + sort**
17
+ * stages behind one declared config object. Per-tier divergence (threshold
18
+ * value, sort direction, content transformation) becomes a declared config
19
+ * field instead of embedded code.
20
+ *
21
+ * **PR 2 scope:** extract the module with NO pipeline changes. PRs 3–6
22
+ * migrate each pipeline to call `unifiedDedupeAndRank` one at a time,
23
+ * re-running the characterization snapshots
24
+ * (`tests/recall-pipeline-unified.test.ts`) to verify byte-for-byte parity.
25
+ *
26
+ * Future PRs will add the remaining stages from the issue's Solution
27
+ * (`mergeAcrossLcmKeys`, `insertMetadata`) once the per-tier hooks are
28
+ * factored out of each pipeline.
29
+ *
30
+ * @see https://github.com/joshuaswarren/remnic/issues/1539
31
+ */
32
+
33
+ /** A ranked evidence item: an `EvidencePackItem` with a computed `rank`. */
34
+ interface RankedEvidenceItem extends EvidencePackItem {
35
+ rank: number;
36
+ }
37
+ /**
38
+ * Sort direction for the turn-index secondary key. This is the divergence
39
+ * issue #1539 identifies: relevance-ranked pipelines (targeted-fact,
40
+ * response-guidance) sort `turnIndex` DESC (recency — latest first); the
41
+ * chronological pipeline (event-order) sorts `turnIndex` ASC (earliest
42
+ * first). The rank primary key is ALWAYS DESC (higher rank first); only
43
+ * the `turnIndex` tiebreaker flips direction.
44
+ */
45
+ type TurnIndexSortDirection = "desc" | "asc";
46
+ /**
47
+ * Configuration for the unified dedup + score + threshold-filter + sort
48
+ * pass. Each pipeline declares its divergences as fields here instead of
49
+ * embedding them in pipeline-specific code.
50
+ *
51
+ * The issue's Solution defines the intended full config object
52
+ * (`RecallPipelineConfig`); this interface is extracted incrementally:
53
+ * - PR 2 (this PR): dedup/score/threshold/sort only
54
+ * - PRs 3–6: migrate each pipeline to consume the spine
55
+ * - future: add `mergeAcrossLcmKeys`, `insertMetadata` once the per-tier
56
+ * hooks are factored out of each pipeline
57
+ */
58
+ interface UnifiedRankConfig<TIntent> {
59
+ /** The user query — passed to the scorer. */
60
+ query: string;
61
+ /**
62
+ * Per-tier intent classification result (already computed by the caller;
63
+ * intent classification stays per-tier because it is genuinely
64
+ * per-tier per issue #1539 Pitfall 1).
65
+ */
66
+ intents: TIntent[];
67
+ /**
68
+ * Score an item using its ORIGINAL (pre-transform) content. Higher = more
69
+ * relevant. The return value becomes the item's `rank`. For pipelines
70
+ * that don't score (explicit-cue), pass a constant scorer — the config
71
+ * makes the no-scoring policy explicit.
72
+ */
73
+ scoreEvidence: (item: EvidencePackItem, query: string, intents: TIntent[]) => number;
74
+ /**
75
+ * Optional content transformation applied to each surviving item's
76
+ * OUTPUT content (NOT to the content the scorer sees). Per-tier
77
+ * cue-appenders go here:
78
+ * - targeted-fact: `appendNormalizedNumericCues`
79
+ * - response-guidance: `appendGuidanceCues`
80
+ * - event-order: `appendChronologicalCues`
81
+ *
82
+ * The dedup key uses the TRANSFORMED content. This is equivalent to
83
+ * deduping on original content for all existing pipelines because every
84
+ * transform is a deterministic append (same original → same transformed →
85
+ * same key; different originals → different transformed → different key).
86
+ */
87
+ transformContent?: (content: string, intents: TIntent[]) => string;
88
+ /**
89
+ * Whether to deduplicate by normalized (transformed) content in addition to
90
+ * id. Default `true` — matches targeted-fact, response-guidance, and
91
+ * explicit-cue, which all collapse later items sharing a normalized content
92
+ * key. Event-order sets this to `false`: its rank pass
93
+ * (`rankAndSelectEventOrderItems`) deduplicates by turn id only and keeps
94
+ * distinct turns even when two turns share the same cue-appended body
95
+ * (legitimate repeated turns in a chronological transcript). Making this a
96
+ * declared field prevents PR 6's migration from silently dropping valid
97
+ * turns (cursor bugbot a4299851).
98
+ */
99
+ dedupByContent?: boolean;
100
+ /**
101
+ * Items with `rank` below this threshold are dropped. `undefined` = no
102
+ * filter. Event-order declares `rankThreshold: 6` here instead of
103
+ * inlining an undocumented `.filter((item) => item.rank >= 6)` (the
104
+ * "hardcoded rank threshold that exists in no config and no other
105
+ * pipeline" from issue #1539's audit).
106
+ */
107
+ rankThreshold?: number;
108
+ /**
109
+ * Sort direction for the `turnIndex` secondary key.
110
+ *
111
+ * - `"desc"` (default): relevance-ranked pipelines (targeted-fact,
112
+ * response-guidance) sort `turnIndex` DESC. Undefined `turnIndex`
113
+ * falls to `-1` (bottom of a DESC list). Tertiary tiebreaker:
114
+ * `score DESC`.
115
+ * - `"asc"`: the chronological pipeline (event-order) sorts `turnIndex`
116
+ * ASC. Undefined `turnIndex` falls to `Number.MAX_SAFE_INTEGER`
117
+ * (bottom of an ASC list). Tertiary tiebreaker: `content.localeCompare`.
118
+ *
119
+ * The rank primary key is ALWAYS DESC regardless of this setting.
120
+ */
121
+ turnIndexSortDirection?: TurnIndexSortDirection;
122
+ }
123
+ /**
124
+ * Deduplicate, score, threshold-filter, and sort evidence items under one
125
+ * unified policy.
126
+ *
127
+ * **Stage order** (matches every existing pipeline's rank/dedupe function):
128
+ * 1. **dedup** by `id` + normalized content (first-seen wins). The dedup
129
+ * key uses transformed content if `transformContent` is declared.
130
+ * 2. **score** each surviving item on its ORIGINAL content (the transform
131
+ * does not influence the score).
132
+ * 3. **threshold-filter**: drop items with `rank < rankThreshold` (if
133
+ * declared).
134
+ * 4. **sort**: `rank DESC` → `turnIndex` (direction-configurable) →
135
+ * tertiary tiebreaker (`score DESC` for relevance pipelines,
136
+ * `content.localeCompare` for chronological pipelines).
137
+ *
138
+ * This function does NOT slice (`maxResults`), budget, or format — those
139
+ * stages remain per-tier because they interact with per-tier metadata
140
+ * insertion. The issue's Solution describes a future `insertMetadata` hook
141
+ * that will make budget-adjustment uniform; that lands in the per-tier
142
+ * migration PRs.
143
+ *
144
+ * @example
145
+ * // Relevance-ranked pipeline (targeted-fact shape):
146
+ * unifiedDedupeAndRank(items, {
147
+ * query,
148
+ * intents: [],
149
+ * scoreEvidence: (item, q) => scoreTargetedFact(item, q),
150
+ * transformContent: (content) => appendNormalizedNumericCues(content),
151
+ * // turnIndexSortDirection defaults to "desc"
152
+ * });
153
+ *
154
+ * @example
155
+ * // Chronological pipeline (event-order shape):
156
+ * unifiedDedupeAndRank(items, {
157
+ * query,
158
+ * intents: [],
159
+ * scoreEvidence: (item, q) => scoreEventOrder(item, q),
160
+ * transformContent: (content) => appendChronologicalCues(content, query),
161
+ * rankThreshold: 6, // declared, not inlined
162
+ * turnIndexSortDirection: "asc",
163
+ * });
164
+ */
165
+ declare function unifiedDedupeAndRank<TIntent>(items: readonly EvidencePackItem[], config: UnifiedRankConfig<TIntent>): RankedEvidenceItem[];
166
+
167
+ export { type RankedEvidenceItem, type TurnIndexSortDirection, type UnifiedRankConfig, unifiedDedupeAndRank };
@@ -0,0 +1,61 @@
1
+ import "./chunk-PZ5AY32C.js";
2
+
3
+ // src/recall-pipeline-stages.ts
4
+ var UNDEFINED_TURN_INDEX_DESC_SENTINEL = -1;
5
+ var UNDEFINED_TURN_INDEX_ASC_SENTINEL = Number.MAX_SAFE_INTEGER;
6
+ function unifiedDedupeAndRank(items, config) {
7
+ const transformContent = config.transformContent ?? ((content) => content);
8
+ const direction = config.turnIndexSortDirection ?? "desc";
9
+ const dedupByContent = config.dedupByContent !== false;
10
+ const seenIds = /* @__PURE__ */ new Set();
11
+ const seenContent = /* @__PURE__ */ new Set();
12
+ const survivors = [];
13
+ for (const item of items) {
14
+ const id = resolveItemId(item);
15
+ if (id && seenIds.has(id)) continue;
16
+ const transformedContent = transformContent(item.content, config.intents);
17
+ if (dedupByContent) {
18
+ const contentKey = transformedContent.toLowerCase().replace(/\s+/g, " ").trim();
19
+ if (seenContent.has(contentKey)) continue;
20
+ seenContent.add(contentKey);
21
+ }
22
+ if (id) seenIds.add(id);
23
+ survivors.push({ original: item, transformedContent });
24
+ }
25
+ const scored = survivors.map(({ original, transformedContent }) => ({
26
+ ...original,
27
+ content: transformedContent,
28
+ rank: config.scoreEvidence(original, config.query, config.intents)
29
+ }));
30
+ const filtered = typeof config.rankThreshold === "number" ? scored.filter((item) => item.rank >= config.rankThreshold) : scored;
31
+ return filtered.sort(makeComparator(direction));
32
+ }
33
+ function makeComparator(direction) {
34
+ if (direction === "asc") {
35
+ return (left, right) => {
36
+ if (right.rank !== left.rank) return right.rank - left.rank;
37
+ const leftTurn = typeof left.turnIndex === "number" ? left.turnIndex : UNDEFINED_TURN_INDEX_ASC_SENTINEL;
38
+ const rightTurn = typeof right.turnIndex === "number" ? right.turnIndex : UNDEFINED_TURN_INDEX_ASC_SENTINEL;
39
+ if (leftTurn !== rightTurn) return leftTurn - rightTurn;
40
+ return left.content.localeCompare(right.content);
41
+ };
42
+ }
43
+ return (left, right) => {
44
+ if (right.rank !== left.rank) return right.rank - left.rank;
45
+ const leftTurn = typeof left.turnIndex === "number" ? left.turnIndex : UNDEFINED_TURN_INDEX_DESC_SENTINEL;
46
+ const rightTurn = typeof right.turnIndex === "number" ? right.turnIndex : UNDEFINED_TURN_INDEX_DESC_SENTINEL;
47
+ if (rightTurn !== leftTurn) return rightTurn - leftTurn;
48
+ return (right.score ?? 0) - (left.score ?? 0);
49
+ };
50
+ }
51
+ function resolveItemId(item) {
52
+ if (item.id) return item.id;
53
+ if (item.sessionId && typeof item.turnIndex === "number") {
54
+ return `${item.sessionId}:${item.turnIndex}`;
55
+ }
56
+ return void 0;
57
+ }
58
+ export {
59
+ unifiedDedupeAndRank
60
+ };
61
+ //# sourceMappingURL=recall-pipeline-stages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/recall-pipeline-stages.ts"],"sourcesContent":["/**\n * Issue #1539 PR2 — the staged recall pipeline spine.\n *\n * The four recall pipelines (`targeted-fact-recall.ts`, `response-guidance-\n * recall.ts`, `explicit-cue-recall.ts`, `event-order-recall.ts`) implement\n * the same stage sequence — intent → candidate collection → dedup → rank →\n * filter → slice → metadata insertion → token budgeting — with ~70%\n * near-duplicate code. The most dangerous divergence is the sort\n * comparator: guidance/targeted-fact sort `turnIndex` DESC (recency) while\n * event-order sorts `turnIndex` ASC (chronological). The comparators are\n * byte-identical except for this direction — copy-pasting into a new\n * chronological pipeline silently inverts the ordering.\n *\n * This module centralizes the **dedup + score + threshold-filter + sort**\n * stages behind one declared config object. Per-tier divergence (threshold\n * value, sort direction, content transformation) becomes a declared config\n * field instead of embedded code.\n *\n * **PR 2 scope:** extract the module with NO pipeline changes. PRs 3–6\n * migrate each pipeline to call `unifiedDedupeAndRank` one at a time,\n * re-running the characterization snapshots\n * (`tests/recall-pipeline-unified.test.ts`) to verify byte-for-byte parity.\n *\n * Future PRs will add the remaining stages from the issue's Solution\n * (`mergeAcrossLcmKeys`, `insertMetadata`) once the per-tier hooks are\n * factored out of each pipeline.\n *\n * @see https://github.com/joshuaswarren/remnic/issues/1539\n */\n\nimport type { EvidencePackItem } from \"./evidence-pack.js\";\n\n/** A ranked evidence item: an `EvidencePackItem` with a computed `rank`. */\nexport interface RankedEvidenceItem extends EvidencePackItem {\n rank: number;\n}\n\n/**\n * Sort direction for the turn-index secondary key. This is the divergence\n * issue #1539 identifies: relevance-ranked pipelines (targeted-fact,\n * response-guidance) sort `turnIndex` DESC (recency — latest first); the\n * chronological pipeline (event-order) sorts `turnIndex` ASC (earliest\n * first). The rank primary key is ALWAYS DESC (higher rank first); only\n * the `turnIndex` tiebreaker flips direction.\n */\nexport type TurnIndexSortDirection = \"desc\" | \"asc\";\n\n/**\n * Configuration for the unified dedup + score + threshold-filter + sort\n * pass. Each pipeline declares its divergences as fields here instead of\n * embedding them in pipeline-specific code.\n *\n * The issue's Solution defines the intended full config object\n * (`RecallPipelineConfig`); this interface is extracted incrementally:\n * - PR 2 (this PR): dedup/score/threshold/sort only\n * - PRs 3–6: migrate each pipeline to consume the spine\n * - future: add `mergeAcrossLcmKeys`, `insertMetadata` once the per-tier\n * hooks are factored out of each pipeline\n */\nexport interface UnifiedRankConfig<TIntent> {\n /** The user query — passed to the scorer. */\n query: string;\n /**\n * Per-tier intent classification result (already computed by the caller;\n * intent classification stays per-tier because it is genuinely\n * per-tier per issue #1539 Pitfall 1).\n */\n intents: TIntent[];\n /**\n * Score an item using its ORIGINAL (pre-transform) content. Higher = more\n * relevant. The return value becomes the item's `rank`. For pipelines\n * that don't score (explicit-cue), pass a constant scorer — the config\n * makes the no-scoring policy explicit.\n */\n scoreEvidence: (\n item: EvidencePackItem,\n query: string,\n intents: TIntent[],\n ) => number;\n /**\n * Optional content transformation applied to each surviving item's\n * OUTPUT content (NOT to the content the scorer sees). Per-tier\n * cue-appenders go here:\n * - targeted-fact: `appendNormalizedNumericCues`\n * - response-guidance: `appendGuidanceCues`\n * - event-order: `appendChronologicalCues`\n *\n * The dedup key uses the TRANSFORMED content. This is equivalent to\n * deduping on original content for all existing pipelines because every\n * transform is a deterministic append (same original → same transformed →\n * same key; different originals → different transformed → different key).\n */\n transformContent?: (content: string, intents: TIntent[]) => string;\n /**\n * Whether to deduplicate by normalized (transformed) content in addition to\n * id. Default `true` — matches targeted-fact, response-guidance, and\n * explicit-cue, which all collapse later items sharing a normalized content\n * key. Event-order sets this to `false`: its rank pass\n * (`rankAndSelectEventOrderItems`) deduplicates by turn id only and keeps\n * distinct turns even when two turns share the same cue-appended body\n * (legitimate repeated turns in a chronological transcript). Making this a\n * declared field prevents PR 6's migration from silently dropping valid\n * turns (cursor bugbot a4299851).\n */\n dedupByContent?: boolean;\n /**\n * Items with `rank` below this threshold are dropped. `undefined` = no\n * filter. Event-order declares `rankThreshold: 6` here instead of\n * inlining an undocumented `.filter((item) => item.rank >= 6)` (the\n * \"hardcoded rank threshold that exists in no config and no other\n * pipeline\" from issue #1539's audit).\n */\n rankThreshold?: number;\n /**\n * Sort direction for the `turnIndex` secondary key.\n *\n * - `\"desc\"` (default): relevance-ranked pipelines (targeted-fact,\n * response-guidance) sort `turnIndex` DESC. Undefined `turnIndex`\n * falls to `-1` (bottom of a DESC list). Tertiary tiebreaker:\n * `score DESC`.\n * - `\"asc\"`: the chronological pipeline (event-order) sorts `turnIndex`\n * ASC. Undefined `turnIndex` falls to `Number.MAX_SAFE_INTEGER`\n * (bottom of an ASC list). Tertiary tiebreaker: `content.localeCompare`.\n *\n * The rank primary key is ALWAYS DESC regardless of this setting.\n */\n turnIndexSortDirection?: TurnIndexSortDirection;\n}\n\n/**\n * Sentinel for undefined `turnIndex` when sorting DESC. `-1` sorts below\n * every real turn index (which are `>= 0`), so missing `turn_index` always\n * lands at the bottom of a DESC-ordered list — never wins ordering over a\n * real turn.\n */\nconst UNDEFINED_TURN_INDEX_DESC_SENTINEL = -1;\n\n/**\n * Sentinel for undefined `turnIndex` when sorting ASC.\n * `Number.MAX_SAFE_INTEGER` sorts above every real turn index, so missing\n * `turn_index` always lands at the bottom of an ASC-ordered list.\n */\nconst UNDEFINED_TURN_INDEX_ASC_SENTINEL = Number.MAX_SAFE_INTEGER;\n\n/**\n * Deduplicate, score, threshold-filter, and sort evidence items under one\n * unified policy.\n *\n * **Stage order** (matches every existing pipeline's rank/dedupe function):\n * 1. **dedup** by `id` + normalized content (first-seen wins). The dedup\n * key uses transformed content if `transformContent` is declared.\n * 2. **score** each surviving item on its ORIGINAL content (the transform\n * does not influence the score).\n * 3. **threshold-filter**: drop items with `rank < rankThreshold` (if\n * declared).\n * 4. **sort**: `rank DESC` → `turnIndex` (direction-configurable) →\n * tertiary tiebreaker (`score DESC` for relevance pipelines,\n * `content.localeCompare` for chronological pipelines).\n *\n * This function does NOT slice (`maxResults`), budget, or format — those\n * stages remain per-tier because they interact with per-tier metadata\n * insertion. The issue's Solution describes a future `insertMetadata` hook\n * that will make budget-adjustment uniform; that lands in the per-tier\n * migration PRs.\n *\n * @example\n * // Relevance-ranked pipeline (targeted-fact shape):\n * unifiedDedupeAndRank(items, {\n * query,\n * intents: [],\n * scoreEvidence: (item, q) => scoreTargetedFact(item, q),\n * transformContent: (content) => appendNormalizedNumericCues(content),\n * // turnIndexSortDirection defaults to \"desc\"\n * });\n *\n * @example\n * // Chronological pipeline (event-order shape):\n * unifiedDedupeAndRank(items, {\n * query,\n * intents: [],\n * scoreEvidence: (item, q) => scoreEventOrder(item, q),\n * transformContent: (content) => appendChronologicalCues(content, query),\n * rankThreshold: 6, // declared, not inlined\n * turnIndexSortDirection: \"asc\",\n * });\n */\nexport function unifiedDedupeAndRank<TIntent>(\n items: readonly EvidencePackItem[],\n config: UnifiedRankConfig<TIntent>,\n): RankedEvidenceItem[] {\n const transformContent = config.transformContent ?? ((content: string) => content);\n const direction: TurnIndexSortDirection = config.turnIndexSortDirection ?? \"desc\";\n const dedupByContent = config.dedupByContent !== false;\n\n // Stage 1: dedup by id (+ normalized transformed content when enabled).\n // First-seen wins, matching every existing pipeline. Event-order opts out\n // of content dedup (dedupByContent: false) because it keeps distinct turns\n // even when two turns share the same cue-appended body.\n const seenIds = new Set<string>();\n const seenContent = new Set<string>();\n const survivors: Array<{ original: EvidencePackItem; transformedContent: string }> = [];\n\n for (const item of items) {\n const id = resolveItemId(item);\n if (id && seenIds.has(id)) continue;\n\n const transformedContent = transformContent(item.content, config.intents);\n if (dedupByContent) {\n const contentKey = transformedContent.toLowerCase().replace(/\\s+/g, \" \").trim();\n if (seenContent.has(contentKey)) continue;\n seenContent.add(contentKey);\n }\n if (id) seenIds.add(id);\n survivors.push({ original: item, transformedContent });\n }\n\n // Stage 2: score on ORIGINAL content (transforms don't influence the score).\n const scored: RankedEvidenceItem[] = survivors.map(({ original, transformedContent }) => ({\n ...original,\n content: transformedContent,\n rank: config.scoreEvidence(original, config.query, config.intents),\n }));\n\n // Stage 3: threshold-filter (declared, not inlined).\n const filtered =\n typeof config.rankThreshold === \"number\"\n ? scored.filter((item) => item.rank >= (config.rankThreshold as number))\n : scored;\n\n // Stage 4: sort.\n // rank is ALWAYS DESC (higher rank first).\n // turnIndex direction is configurable: DESC (relevance) or ASC (chronology).\n // The tertiary tiebreaker follows the direction:\n // DESC → score DESC; ASC → content.localeCompare.\n return filtered.sort(makeComparator(direction));\n}\n\n/**\n * Build the sort comparator for the configured direction. Extracted so the\n * comparator's two shapes (DESC / ASC) can be tested independently and so\n * the direction divergence is visible in ONE place.\n */\nfunction makeComparator(\n direction: TurnIndexSortDirection,\n): (left: RankedEvidenceItem, right: RankedEvidenceItem) => number {\n if (direction === \"asc\") {\n // Chronological (event-order): rank DESC → turnIndex ASC → content localeCompare.\n // Matches event-order-recall.ts:159-164 (rankedByScore sort) byte-for-byte.\n return (left, right) => {\n if (right.rank !== left.rank) return right.rank - left.rank;\n const leftTurn =\n typeof left.turnIndex === \"number\"\n ? left.turnIndex\n : UNDEFINED_TURN_INDEX_ASC_SENTINEL;\n const rightTurn =\n typeof right.turnIndex === \"number\"\n ? right.turnIndex\n : UNDEFINED_TURN_INDEX_ASC_SENTINEL;\n if (leftTurn !== rightTurn) return leftTurn - rightTurn;\n return left.content.localeCompare(right.content);\n };\n }\n // Relevance-ranked (targeted-fact, response-guidance):\n // rank DESC → turnIndex DESC → score DESC.\n // Matches targeted-fact-recall.ts:239-245 and response-guidance-recall.ts:374-380\n // byte-for-byte.\n return (left, right) => {\n if (right.rank !== left.rank) return right.rank - left.rank;\n const leftTurn =\n typeof left.turnIndex === \"number\"\n ? left.turnIndex\n : UNDEFINED_TURN_INDEX_DESC_SENTINEL;\n const rightTurn =\n typeof right.turnIndex === \"number\"\n ? right.turnIndex\n : UNDEFINED_TURN_INDEX_DESC_SENTINEL;\n if (rightTurn !== leftTurn) return rightTurn - leftTurn;\n return (right.score ?? 0) - (left.score ?? 0);\n };\n}\n\n\n/**\n * Resolve an evidence item's id. Falls back to `sessionId:turnIndex` when\n * `id` is absent — mirrors every existing pipeline's fallback and\n * `evidence-pack.ts`'s `evidenceItemFallbackId`.\n */\nfunction resolveItemId(item: EvidencePackItem): string | undefined {\n if (item.id) return item.id;\n if (item.sessionId && typeof item.turnIndex === \"number\") {\n return `${item.sessionId}:${item.turnIndex}`;\n }\n return undefined;\n}\n"],"mappings":";;;AAuIA,IAAM,qCAAqC;AAO3C,IAAM,oCAAoC,OAAO;AA4C1C,SAAS,qBACd,OACA,QACsB;AACtB,QAAM,mBAAmB,OAAO,qBAAqB,CAAC,YAAoB;AAC1E,QAAM,YAAoC,OAAO,0BAA0B;AAC3E,QAAM,iBAAiB,OAAO,mBAAmB;AAMjD,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,cAAc,oBAAI,IAAY;AACpC,QAAM,YAA+E,CAAC;AAEtF,aAAW,QAAQ,OAAO;AACxB,UAAM,KAAK,cAAc,IAAI;AAC7B,QAAI,MAAM,QAAQ,IAAI,EAAE,EAAG;AAE3B,UAAM,qBAAqB,iBAAiB,KAAK,SAAS,OAAO,OAAO;AACxE,QAAI,gBAAgB;AAClB,YAAM,aAAa,mBAAmB,YAAY,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAC9E,UAAI,YAAY,IAAI,UAAU,EAAG;AACjC,kBAAY,IAAI,UAAU;AAAA,IAC5B;AACA,QAAI,GAAI,SAAQ,IAAI,EAAE;AACtB,cAAU,KAAK,EAAE,UAAU,MAAM,mBAAmB,CAAC;AAAA,EACvD;AAGA,QAAM,SAA+B,UAAU,IAAI,CAAC,EAAE,UAAU,mBAAmB,OAAO;AAAA,IACxF,GAAG;AAAA,IACH,SAAS;AAAA,IACT,MAAM,OAAO,cAAc,UAAU,OAAO,OAAO,OAAO,OAAO;AAAA,EACnE,EAAE;AAGF,QAAM,WACJ,OAAO,OAAO,kBAAkB,WAC5B,OAAO,OAAO,CAAC,SAAS,KAAK,QAAS,OAAO,aAAwB,IACrE;AAON,SAAO,SAAS,KAAK,eAAe,SAAS,CAAC;AAChD;AAOA,SAAS,eACP,WACiE;AACjE,MAAI,cAAc,OAAO;AAGvB,WAAO,CAAC,MAAM,UAAU;AACtB,UAAI,MAAM,SAAS,KAAK,KAAM,QAAO,MAAM,OAAO,KAAK;AACvD,YAAM,WACJ,OAAO,KAAK,cAAc,WACtB,KAAK,YACL;AACN,YAAM,YACJ,OAAO,MAAM,cAAc,WACvB,MAAM,YACN;AACN,UAAI,aAAa,UAAW,QAAO,WAAW;AAC9C,aAAO,KAAK,QAAQ,cAAc,MAAM,OAAO;AAAA,IACjD;AAAA,EACF;AAKA,SAAO,CAAC,MAAM,UAAU;AACtB,QAAI,MAAM,SAAS,KAAK,KAAM,QAAO,MAAM,OAAO,KAAK;AACvD,UAAM,WACJ,OAAO,KAAK,cAAc,WACtB,KAAK,YACL;AACN,UAAM,YACJ,OAAO,MAAM,cAAc,WACvB,MAAM,YACN;AACN,QAAI,cAAc,SAAU,QAAO,YAAY;AAC/C,YAAQ,MAAM,SAAS,MAAM,KAAK,SAAS;AAAA,EAC7C;AACF;AAQA,SAAS,cAAc,MAA4C;AACjE,MAAI,KAAK,GAAI,QAAO,KAAK;AACzB,MAAI,KAAK,aAAa,OAAO,KAAK,cAAc,UAAU;AACxD,WAAO,GAAG,KAAK,SAAS,IAAI,KAAK,SAAS;AAAA,EAC5C;AACA,SAAO;AACT;","names":[]}
package/dist/schemas.d.ts CHANGED
@@ -351,13 +351,13 @@ declare const ExtractedRelationshipSchema: z.ZodObject<{
351
351
  promptedByQuestion: z.ZodNullable<z.ZodOptional<z.ZodString>>;
352
352
  }, "strip", z.ZodTypeAny, {
353
353
  source: string;
354
- label: string;
355
354
  target: string;
355
+ label: string;
356
356
  promptedByQuestion?: string | null | undefined;
357
357
  }, {
358
358
  source: string;
359
- label: string;
360
359
  target: string;
360
+ label: string;
361
361
  promptedByQuestion?: string | null | undefined;
362
362
  }>;
363
363
  declare const ProactiveExtractionResultSchema: z.ZodObject<{
@@ -620,13 +620,13 @@ declare const ProactiveExtractionResultSchema: z.ZodObject<{
620
620
  promptedByQuestion: z.ZodNullable<z.ZodOptional<z.ZodString>>;
621
621
  }, "strip", z.ZodTypeAny, {
622
622
  source: string;
623
- label: string;
624
623
  target: string;
624
+ label: string;
625
625
  promptedByQuestion?: string | null | undefined;
626
626
  }, {
627
627
  source: string;
628
- label: string;
629
628
  target: string;
629
+ label: string;
630
630
  promptedByQuestion?: string | null | undefined;
631
631
  }>, "many">>>;
632
632
  }, "strip", z.ZodTypeAny, {
@@ -674,8 +674,8 @@ declare const ProactiveExtractionResultSchema: z.ZodObject<{
674
674
  profileUpdates: string[];
675
675
  relationships?: {
676
676
  source: string;
677
- label: string;
678
677
  target: string;
678
+ label: string;
679
679
  promptedByQuestion?: string | null | undefined;
680
680
  }[] | null | undefined;
681
681
  }, {
@@ -723,8 +723,8 @@ declare const ProactiveExtractionResultSchema: z.ZodObject<{
723
723
  profileUpdates: string[];
724
724
  relationships?: {
725
725
  source: string;
726
- label: string;
727
726
  target: string;
727
+ label: string;
728
728
  promptedByQuestion?: string | null | undefined;
729
729
  }[] | null | undefined;
730
730
  }>;
@@ -1002,13 +1002,13 @@ declare const ExtractionResultSchema: z.ZodObject<{
1002
1002
  promptedByQuestion: z.ZodNullable<z.ZodOptional<z.ZodString>>;
1003
1003
  }, "strip", z.ZodTypeAny, {
1004
1004
  source: string;
1005
- label: string;
1006
1005
  target: string;
1006
+ label: string;
1007
1007
  promptedByQuestion?: string | null | undefined;
1008
1008
  }, {
1009
1009
  source: string;
1010
- label: string;
1011
1010
  target: string;
1011
+ label: string;
1012
1012
  promptedByQuestion?: string | null | undefined;
1013
1013
  }>, "many">>>;
1014
1014
  }, "strip", z.ZodTypeAny, {
@@ -1061,8 +1061,8 @@ declare const ExtractionResultSchema: z.ZodObject<{
1061
1061
  profileUpdates: string[];
1062
1062
  relationships?: {
1063
1063
  source: string;
1064
- label: string;
1065
1064
  target: string;
1065
+ label: string;
1066
1066
  promptedByQuestion?: string | null | undefined;
1067
1067
  }[] | null | undefined;
1068
1068
  identityReflection?: string | null | undefined;
@@ -1116,8 +1116,8 @@ declare const ExtractionResultSchema: z.ZodObject<{
1116
1116
  profileUpdates: string[];
1117
1117
  relationships?: {
1118
1118
  source: string;
1119
- label: string;
1120
1119
  target: string;
1120
+ label: string;
1121
1121
  promptedByQuestion?: string | null | undefined;
1122
1122
  }[] | null | undefined;
1123
1123
  identityReflection?: string | null | undefined;
@@ -2,19 +2,19 @@ import {
2
2
  createConversationIndexRuntime,
3
3
  createConversationSearchBackend,
4
4
  createSearchBackend
5
- } from "../chunk-2KAYTPPT.js";
5
+ } from "../chunk-TYF3D4MS.js";
6
+ import "../chunk-5CEJH5ZN.js";
6
7
  import "../chunk-WRFKZEO6.js";
7
8
  import "../chunk-CYEPCZN5.js";
8
9
  import "../chunk-LQ6JI4VH.js";
9
10
  import "../chunk-SANZHXY2.js";
10
- import "../chunk-EHISUJFN.js";
11
- import "../chunk-5CEJH5ZN.js";
12
11
  import "../chunk-AER6MT24.js";
12
+ import "../chunk-EHISUJFN.js";
13
13
  import "../chunk-CINZGPSJ.js";
14
- import "../chunk-DCWIQFNA.js";
15
- import "../chunk-XBZQRZ6G.js";
14
+ import "../chunk-ZUDM75KG.js";
16
15
  import "../chunk-KCQA46NR.js";
17
16
  import "../chunk-SJHM6I4J.js";
17
+ import "../chunk-XBZQRZ6G.js";
18
18
  import "../chunk-K43PI6DQ.js";
19
19
  import "../chunk-YNQ6DFSV.js";
20
20
  import "../chunk-O75CRYGF.js";
@@ -2,7 +2,10 @@ import {
2
2
  createConversationIndexRuntime,
3
3
  createConversationSearchBackend,
4
4
  createSearchBackend
5
- } from "../chunk-2KAYTPPT.js";
5
+ } from "../chunk-TYF3D4MS.js";
6
+ import {
7
+ LanceDbBackend
8
+ } from "../chunk-5CEJH5ZN.js";
6
9
  import {
7
10
  MeilisearchBackend
8
11
  } from "../chunk-WRFKZEO6.js";
@@ -15,20 +18,17 @@ import {
15
18
  import {
16
19
  RemoteSearchBackend
17
20
  } from "../chunk-SANZHXY2.js";
18
- import {
19
- EmbedHelper
20
- } from "../chunk-EHISUJFN.js";
21
- import {
22
- LanceDbBackend
23
- } from "../chunk-5CEJH5ZN.js";
24
21
  import {
25
22
  scanMemoryDir
26
23
  } from "../chunk-AER6MT24.js";
24
+ import {
25
+ EmbedHelper
26
+ } from "../chunk-EHISUJFN.js";
27
27
  import "../chunk-CINZGPSJ.js";
28
- import "../chunk-DCWIQFNA.js";
29
- import "../chunk-XBZQRZ6G.js";
28
+ import "../chunk-ZUDM75KG.js";
30
29
  import "../chunk-KCQA46NR.js";
31
30
  import "../chunk-SJHM6I4J.js";
31
+ import "../chunk-XBZQRZ6G.js";
32
32
  import "../chunk-K43PI6DQ.js";
33
33
  import "../chunk-YNQ6DFSV.js";
34
34
  import "../chunk-O75CRYGF.js";
@@ -7,17 +7,18 @@ import {
7
7
  materializeAfterSemanticConsolidation,
8
8
  parseConsolidationResponse,
9
9
  parseOperatorAwareConsolidationResponse
10
- } from "./chunk-GS55WYRL.js";
11
- import "./chunk-QANVLERJ.js";
10
+ } from "./chunk-PHZKALOE.js";
11
+ import "./chunk-UPIBE2DK.js";
12
12
  import "./chunk-UUH4YQOF.js";
13
13
  import {
14
14
  resolveExtensionsRoot
15
15
  } from "./chunk-JLNBQWZ2.js";
16
16
  import "./chunk-3UXOZBHV.js";
17
- import "./chunk-BLIWOONZ.js";
17
+ import "./chunk-CUNTLEJP.js";
18
18
  import "./chunk-M7XQSUBB.js";
19
19
  import "./chunk-5UZXUTVO.js";
20
20
  import "./chunk-5GPPACXK.js";
21
+ import "./chunk-SVOZFLIQ.js";
21
22
  import "./chunk-J6A3CX5N.js";
22
23
  import "./chunk-NGFEWFNK.js";
23
24
  import "./chunk-MB5RSUW6.js";
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  promoteSemanticRuleFromMemory,
3
3
  setSemanticRulePromotionTestHooks
4
- } from "./chunk-UTYBJR7M.js";
5
- import "./chunk-BLIWOONZ.js";
4
+ } from "./chunk-SB6CQTKP.js";
5
+ import "./chunk-CUNTLEJP.js";
6
6
  import "./chunk-M7XQSUBB.js";
7
7
  import "./chunk-5UZXUTVO.js";
8
8
  import "./chunk-5GPPACXK.js";
9
+ import "./chunk-SVOZFLIQ.js";
9
10
  import "./chunk-J6A3CX5N.js";
10
11
  import "./chunk-NGFEWFNK.js";
11
12
  import "./chunk-MB5RSUW6.js";
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  compareVerifiedSemanticRuleResults,
3
3
  searchVerifiedSemanticRules
4
- } from "./chunk-PH3HOKYW.js";
5
- import "./chunk-BLIWOONZ.js";
4
+ } from "./chunk-67MMWC74.js";
5
+ import "./chunk-CUNTLEJP.js";
6
6
  import "./chunk-M7XQSUBB.js";
7
7
  import "./chunk-5UZXUTVO.js";
8
8
  import "./chunk-5GPPACXK.js";
9
+ import "./chunk-SVOZFLIQ.js";
9
10
  import "./chunk-J6A3CX5N.js";
10
11
  import "./chunk-NGFEWFNK.js";
11
12
  import "./chunk-MB5RSUW6.js";
package/dist/storage.d.ts CHANGED
@@ -2,6 +2,7 @@ import { VersioningConfig } from './page-versioning.js';
2
2
  import { ConsolidationOperator } from './consolidation-operator.js';
3
3
  import { P as PluginConfig, h as MemoryStatus, M as MemoryCategory, I as ImportanceScore, i as MemoryLink, j as MemoryFrontmatter, g as MemoryFile, E as EntityStructuredSection, k as BufferState, l as MetaState, m as MemoryActionEvent, n as MemoryLifecycleEvent, o as BufferSurpriseEvent, B as BehaviorSignalEvent, C as CompressionGuidelineOptimizerState, p as ContinuityIncidentOpenInput, q as ContinuityIncidentRecord, r as ContinuityIncidentCloseInput, s as ContinuityImprovementLoop, t as ContinuityLoopUpsertInput, u as ContinuityLoopReviewInput, F as FileHygieneConfig, v as EntityRelationship, w as EntityActivityEntry, x as EntityFile, y as AccessTrackingEntry, z as MemoryProjectionCurrentState, D as MemorySummary, T as TopicScore } from './types-Dm5xxVrr.js';
4
4
  import { ProjectedMemoryBrowseOptions, ProjectedMemoryBrowsePage, readProjectedGovernanceRecord } from './memory-projection-store.js';
5
+ export { stripAttributesSuffix } from './structured-attributes.js';
5
6
  import './types-ByK7T3L6.js';
6
7
  import './index-DJ9QWMw-.js';
7
8
  import './maintenance/memory-governance.js';
@@ -84,32 +85,7 @@ declare class ContentHashIndex {
84
85
  /** Normalize content and compute SHA-256 hash. */
85
86
  static computeHash(content: string): string;
86
87
  }
87
- /**
88
- * Render a structured-attributes map into a stable, canonical string fragment
89
- * suitable for appending to enriched memory content before hashing.
90
- *
91
- * Normalization rules:
92
- * - Keys are trimmed and lowercased (values are trimmed but preserve case)
93
- * - Key-value pairs are sorted alphabetically by normalized key
94
- * - Pairs are joined with "; " and rendered as "key: value"
95
- *
96
- * Using this helper at BOTH the write path (enrichedContent) and the
97
- * dedup-lookup path (dedupContent) guarantees identical output regardless of
98
- * the insertion order or casing used by the caller.
99
- *
100
- * @example
101
- * normalizeAttributePairs({ foo: "bar", BAZ: "qux" })
102
- * // → "baz: qux; foo: bar"
103
- */
104
- /**
105
- * Remove the "[Attributes: ...]" suffix `writeMemory` appends to the
106
- * stored body when structuredAttributes are present, yielding the raw
107
- * fact text for content comparison. Inverse companion of
108
- * `normalizeAttributePairs` enrichment. String operations, not regex —
109
- * CodeQL js/polynomial-redos on a suffix-anchored pattern over
110
- * library-supplied content.
111
- */
112
- declare function stripAttributesSuffix(content: string): string;
88
+
113
89
  declare function normalizeAttributePairs(pairs: Record<string, string>): string;
114
90
  declare function compareEntityTimestamps(left?: string, right?: string): number;
115
91
  declare function fingerprintEntityStructuredFacts(entity: Pick<EntityFile, "structuredSections">): string | undefined;
@@ -893,4 +869,4 @@ declare class StorageManager {
893
869
  private appendGeneratedMemoryLifecycleEventFailOpen;
894
870
  }
895
871
 
896
- export { ContentHashIndex, type MemoryLifecycleEventWriteOptions, type ReextractJobRequest, StorageManager, compareEntityTimestamps, fingerprintEntityStructuredFacts, isEntitySynthesisStale, normalizeAttributePairs, normalizeEntityName, parseEntityFile, serializeEntityFile, stripAttributesSuffix };
872
+ export { ContentHashIndex, type MemoryLifecycleEventWriteOptions, type ReextractJobRequest, StorageManager, compareEntityTimestamps, fingerprintEntityStructuredFacts, isEntitySynthesisStale, normalizeAttributePairs, normalizeEntityName, parseEntityFile, serializeEntityFile };
package/dist/storage.js CHANGED
@@ -7,12 +7,14 @@ import {
7
7
  normalizeAttributePairs,
8
8
  normalizeEntityName,
9
9
  parseEntityFile,
10
- serializeEntityFile,
11
- stripAttributesSuffix
12
- } from "./chunk-BLIWOONZ.js";
10
+ serializeEntityFile
11
+ } from "./chunk-CUNTLEJP.js";
13
12
  import "./chunk-M7XQSUBB.js";
14
13
  import "./chunk-5UZXUTVO.js";
15
14
  import "./chunk-5GPPACXK.js";
15
+ import {
16
+ stripAttributesSuffix
17
+ } from "./chunk-SVOZFLIQ.js";
16
18
  import "./chunk-J6A3CX5N.js";
17
19
  import "./chunk-NGFEWFNK.js";
18
20
  import "./chunk-MB5RSUW6.js";
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared helpers for the `\n[Attributes: …]` enrichment suffix that
3
+ * `writeMemory` appends to a stored body when `structuredAttributes` are
4
+ * present.
5
+ *
6
+ * Extracted here (out of `storage.ts`) so every reader — the storage
7
+ * manager, the wearable service, and the coding surfaces — strips the suffix
8
+ * with ONE definition. Previously `coding/architecture-surfaces.ts` carried a
9
+ * weaker local copy that only checked the trailing `]`, which could truncate
10
+ * card markdown that legitimately ends in a `[Attributes: …]` line (cursor
11
+ * review: duplicate attribute suffix stripper).
12
+ *
13
+ * String operations, not regex — CodeQL `js/polynomial-redos` flags
14
+ * suffix-anchored patterns over library-supplied content.
15
+ */
16
+ /**
17
+ * Remove the `[Attributes: …]` suffix `writeMemory` appends to the stored
18
+ * body when structuredAttributes are present, yielding the raw fact/card text
19
+ * for content comparison or client display. Inverse companion of
20
+ * `normalizeAttributePairs` enrichment in `storage.ts`.
21
+ *
22
+ * Returns the stripped (trimmed) body when the marker opens the final
23
+ * non-empty line and its inner payload contains no premature `]` or newline
24
+ * (mirrors the exact shape `normalizeAttributePairs` emits). Otherwise
25
+ * returns the trimmed input unchanged — never truncates legitimate content.
26
+ */
27
+ declare function stripAttributesSuffix(content: string): string;
28
+
29
+ export { stripAttributesSuffix };
@@ -0,0 +1,8 @@
1
+ import {
2
+ stripAttributesSuffix
3
+ } from "./chunk-SVOZFLIQ.js";
4
+ import "./chunk-PZ5AY32C.js";
5
+ export {
6
+ stripAttributesSuffix
7
+ };
8
+ //# sourceMappingURL=structured-attributes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -8,6 +8,7 @@ import './memory-projection-store.js';
8
8
  import './maintenance/memory-governance.js';
9
9
  import './runtime/better-sqlite.js';
10
10
  import 'better-sqlite3';
11
+ import './structured-attributes.js';
11
12
 
12
13
  /**
13
14
  * Temporal Supersession (issue #375)
@@ -10,6 +10,7 @@ import './memory-projection-store.js';
10
10
  import './maintenance/memory-governance.js';
11
11
  import './runtime/better-sqlite.js';
12
12
  import 'better-sqlite3';
13
+ import './structured-attributes.js';
13
14
  import './lifecycle.js';
14
15
 
15
16
  interface TierMigrationRequest {
@@ -1,12 +1,13 @@
1
1
  import {
2
2
  compareVerifiedEpisodeResults,
3
3
  searchVerifiedEpisodes
4
- } from "./chunk-4FE2K57M.js";
4
+ } from "./chunk-RTFAZOIR.js";
5
5
  import "./chunk-7FL4CNPV.js";
6
- import "./chunk-BLIWOONZ.js";
6
+ import "./chunk-CUNTLEJP.js";
7
7
  import "./chunk-M7XQSUBB.js";
8
8
  import "./chunk-5UZXUTVO.js";
9
9
  import "./chunk-5GPPACXK.js";
10
+ import "./chunk-SVOZFLIQ.js";
10
11
  import "./chunk-J6A3CX5N.js";
11
12
  import "./chunk-NGFEWFNK.js";
12
13
  import "./chunk-MB5RSUW6.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.3.688",
3
+ "version": "9.3.690",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -2931,7 +2931,7 @@
2931
2931
  "core"
2932
2932
  ],
2933
2933
  "peerDependencies": {
2934
- "@remnic/coding-graph": "^9.3.688"
2934
+ "@remnic/coding-graph": "^9.3.690"
2935
2935
  },
2936
2936
  "peerDependenciesMeta": {
2937
2937
  "@remnic/coding-graph": {
@@ -34,7 +34,8 @@ export type OperationName =
34
34
  | "memory_get"
35
35
  | "memory_search"
36
36
  | "memory_store"
37
- | "coding_decision";
37
+ | "coding_decision"
38
+ | "coding_architecture";
38
39
 
39
40
  // ---------------------------------------------------------------------------
40
41
  // Operation context — what every handler receives