@cleocode/core 2026.4.37 → 2026.4.38

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 (61) hide show
  1. package/dist/hooks/handlers/task-hooks.d.ts.map +1 -1
  2. package/dist/hooks/handlers/task-hooks.js +11 -0
  3. package/dist/hooks/handlers/task-hooks.js.map +1 -1
  4. package/dist/index.js +644 -33
  5. package/dist/index.js.map +4 -4
  6. package/dist/internal.d.ts +3 -1
  7. package/dist/internal.d.ts.map +1 -1
  8. package/dist/internal.js +3 -1
  9. package/dist/internal.js.map +1 -1
  10. package/dist/memory/decisions.d.ts.map +1 -1
  11. package/dist/memory/decisions.js +18 -0
  12. package/dist/memory/decisions.js.map +1 -1
  13. package/dist/memory/engine-compat.d.ts +17 -0
  14. package/dist/memory/engine-compat.d.ts.map +1 -1
  15. package/dist/memory/engine-compat.js +36 -0
  16. package/dist/memory/engine-compat.js.map +1 -1
  17. package/dist/memory/graph-memory-bridge.d.ts +158 -0
  18. package/dist/memory/graph-memory-bridge.d.ts.map +1 -0
  19. package/dist/memory/graph-memory-bridge.js +519 -0
  20. package/dist/memory/graph-memory-bridge.js.map +1 -0
  21. package/dist/memory/index.d.ts +1 -0
  22. package/dist/memory/index.d.ts.map +1 -1
  23. package/dist/memory/index.js +2 -0
  24. package/dist/memory/index.js.map +1 -1
  25. package/dist/memory/learnings.d.ts.map +1 -1
  26. package/dist/memory/learnings.js +18 -0
  27. package/dist/memory/learnings.js.map +1 -1
  28. package/dist/memory/llm-extraction.js.map +1 -1
  29. package/dist/memory/patterns.d.ts.map +1 -1
  30. package/dist/memory/patterns.js +18 -0
  31. package/dist/memory/patterns.js.map +1 -1
  32. package/dist/memory/quality-feedback.d.ts +129 -0
  33. package/dist/memory/quality-feedback.d.ts.map +1 -0
  34. package/dist/memory/quality-feedback.js +449 -0
  35. package/dist/memory/quality-feedback.js.map +1 -0
  36. package/dist/memory/sleep-consolidation.d.ts +98 -0
  37. package/dist/memory/sleep-consolidation.d.ts.map +1 -0
  38. package/dist/memory/sleep-consolidation.js +706 -0
  39. package/dist/memory/sleep-consolidation.js.map +1 -0
  40. package/dist/memory/temporal-supersession.d.ts +155 -0
  41. package/dist/memory/temporal-supersession.d.ts.map +1 -0
  42. package/dist/memory/temporal-supersession.js +406 -0
  43. package/dist/memory/temporal-supersession.js.map +1 -0
  44. package/package.json +6 -6
  45. package/src/hooks/handlers/task-hooks.ts +11 -0
  46. package/src/internal.ts +12 -0
  47. package/src/memory/__tests__/graph-memory-bridge.test.ts +357 -0
  48. package/src/memory/__tests__/llm-extraction.test.ts +17 -0
  49. package/src/memory/__tests__/quality-feedback.test.ts +418 -0
  50. package/src/memory/__tests__/sleep-consolidation.test.ts +790 -0
  51. package/src/memory/__tests__/temporal-supersession.test.ts +534 -0
  52. package/src/memory/decisions.ts +24 -0
  53. package/src/memory/engine-compat.ts +37 -0
  54. package/src/memory/graph-memory-bridge.ts +751 -0
  55. package/src/memory/index.ts +2 -0
  56. package/src/memory/learnings.ts +24 -0
  57. package/src/memory/patterns.ts +24 -0
  58. package/src/memory/quality-feedback.ts +640 -0
  59. package/src/memory/sleep-consolidation.ts +932 -0
  60. package/src/memory/temporal-supersession.ts +568 -0
  61. package/src/store/__tests__/performance-safety.test.ts +4 -4
package/dist/index.js CHANGED
@@ -18648,6 +18648,210 @@ var init_quality_scoring = __esm({
18648
18648
  }
18649
18649
  });
18650
18650
 
18651
+ // packages/core/src/store/typed-query.ts
18652
+ function typedAll(stmt, ...params) {
18653
+ return stmt.all(...params);
18654
+ }
18655
+ function typedGet(stmt, ...params) {
18656
+ return stmt.get(...params);
18657
+ }
18658
+ var init_typed_query = __esm({
18659
+ "packages/core/src/store/typed-query.ts"() {
18660
+ "use strict";
18661
+ }
18662
+ });
18663
+
18664
+ // packages/core/src/memory/temporal-supersession.ts
18665
+ function extractKeywords(text3) {
18666
+ const words = text3.toLowerCase().replace(/[^a-z0-9\s\-_]/g, " ").split(/\s+/);
18667
+ const keywords = /* @__PURE__ */ new Set();
18668
+ for (const w of words) {
18669
+ if (w.length >= 4 && !STOP_WORDS.has(w)) {
18670
+ keywords.add(w);
18671
+ }
18672
+ }
18673
+ return keywords;
18674
+ }
18675
+ function keywordSimilarity(textA, textB) {
18676
+ const kwA = extractKeywords(textA);
18677
+ const kwB = extractKeywords(textB);
18678
+ if (kwA.size === 0 || kwB.size === 0) return { similarity: 0, shared: [] };
18679
+ const shared = [];
18680
+ for (const w of kwA) {
18681
+ if (kwB.has(w)) shared.push(w);
18682
+ }
18683
+ if (shared.length < MIN_SHARED_KEYWORDS) return { similarity: 0, shared: [] };
18684
+ const union3 = kwA.size + kwB.size - shared.length;
18685
+ const similarity = union3 > 0 ? shared.length / union3 : 0;
18686
+ return { similarity, shared };
18687
+ }
18688
+ function buildNodeId(type, entryId) {
18689
+ return `${type}:${entryId}`;
18690
+ }
18691
+ async function locateEntry(projectRoot, entryId) {
18692
+ await getBrainDb(projectRoot);
18693
+ const nativeDb = getBrainNativeDb();
18694
+ if (!nativeDb) return null;
18695
+ for (const tc of SUPERSEDABLE_TABLES) {
18696
+ const row = typedGet(
18697
+ nativeDb.prepare(`SELECT id FROM ${tc.table} WHERE id = ? LIMIT 1`),
18698
+ entryId
18699
+ );
18700
+ if (row) {
18701
+ return { tableConfig: tc, nodeId: buildNodeId(tc.type, entryId) };
18702
+ }
18703
+ }
18704
+ return null;
18705
+ }
18706
+ async function supersedeMemory(projectRoot, oldId, newId, reason) {
18707
+ if (!oldId?.trim()) throw new Error("oldId is required");
18708
+ if (!newId?.trim()) throw new Error("newId is required");
18709
+ if (!reason?.trim()) throw new Error("reason is required");
18710
+ if (oldId === newId) throw new Error("oldId and newId must be different entries");
18711
+ await getBrainDb(projectRoot);
18712
+ const nativeDb = getBrainNativeDb();
18713
+ if (!nativeDb) throw new Error("brain.db is unavailable");
18714
+ const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
18715
+ const oldLocation = await locateEntry(projectRoot, oldId);
18716
+ if (!oldLocation) throw new Error(`Entry not found: ${oldId}`);
18717
+ const newLocation = await locateEntry(projectRoot, newId);
18718
+ if (!newLocation) throw new Error(`Entry not found: ${newId}`);
18719
+ const { tableConfig: oldTc, nodeId: oldNodeId } = oldLocation;
18720
+ const { nodeId: newNodeId } = newLocation;
18721
+ try {
18722
+ nativeDb.prepare(
18723
+ `UPDATE ${oldTc.table} SET invalid_at = ?, updated_at = ? WHERE id = ? AND invalid_at IS NULL`
18724
+ ).run(now, now, oldId);
18725
+ } catch {
18726
+ }
18727
+ const provenanceText = reason.substring(0, 500);
18728
+ try {
18729
+ nativeDb.prepare(
18730
+ `INSERT OR IGNORE INTO brain_page_edges
18731
+ (from_id, to_id, edge_type, weight, provenance, created_at)
18732
+ VALUES (?, ?, 'supersedes', 1.0, ?, ?)`
18733
+ ).run(newNodeId, oldNodeId, provenanceText, now);
18734
+ } catch (err) {
18735
+ const message = err instanceof Error ? err.message : String(err);
18736
+ throw new Error(`Failed to create supersedes edge: ${message}`);
18737
+ }
18738
+ return {
18739
+ success: true,
18740
+ oldId,
18741
+ newId,
18742
+ edgeType: "supersedes"
18743
+ };
18744
+ }
18745
+ async function detectSupersession(projectRoot, newEntry) {
18746
+ try {
18747
+ await getBrainDb(projectRoot);
18748
+ const nativeDb = getBrainNativeDb();
18749
+ if (!nativeDb) return [];
18750
+ const newLocation = await locateEntry(projectRoot, newEntry.id);
18751
+ if (!newLocation) return [];
18752
+ const { tableConfig } = newLocation;
18753
+ const existing = typedAll(
18754
+ nativeDb.prepare(`
18755
+ SELECT id, COALESCE(${tableConfig.textCol}, '') AS text,
18756
+ created_at, quality_score
18757
+ FROM ${tableConfig.table}
18758
+ WHERE invalid_at IS NULL
18759
+ AND id != ?
18760
+ ORDER BY created_at DESC
18761
+ LIMIT 200
18762
+ `),
18763
+ newEntry.id
18764
+ );
18765
+ if (existing.length === 0) return [];
18766
+ const candidates = [];
18767
+ for (const row of existing) {
18768
+ if (row.created_at >= newEntry.createdAt) continue;
18769
+ const { similarity, shared } = keywordSimilarity(newEntry.text, row.text);
18770
+ if (similarity >= KEYWORD_OVERLAP_THRESHOLD) {
18771
+ candidates.push({
18772
+ existingId: row.id,
18773
+ similarity,
18774
+ table: tableConfig.table,
18775
+ sharedKeywords: shared.slice(0, 10)
18776
+ });
18777
+ }
18778
+ }
18779
+ candidates.sort((a, b) => b.similarity - a.similarity);
18780
+ return candidates;
18781
+ } catch (err) {
18782
+ console.warn("[temporal-supersession] detectSupersession failed:", err);
18783
+ return [];
18784
+ }
18785
+ }
18786
+ var KEYWORD_OVERLAP_THRESHOLD, MIN_SHARED_KEYWORDS, STOP_WORDS, SUPERSEDABLE_TABLES;
18787
+ var init_temporal_supersession = __esm({
18788
+ "packages/core/src/memory/temporal-supersession.ts"() {
18789
+ "use strict";
18790
+ init_brain_sqlite();
18791
+ init_typed_query();
18792
+ KEYWORD_OVERLAP_THRESHOLD = 0.8;
18793
+ MIN_SHARED_KEYWORDS = 3;
18794
+ STOP_WORDS = /* @__PURE__ */ new Set([
18795
+ "the",
18796
+ "a",
18797
+ "an",
18798
+ "is",
18799
+ "are",
18800
+ "was",
18801
+ "were",
18802
+ "be",
18803
+ "been",
18804
+ "being",
18805
+ "have",
18806
+ "has",
18807
+ "had",
18808
+ "do",
18809
+ "does",
18810
+ "did",
18811
+ "will",
18812
+ "would",
18813
+ "could",
18814
+ "should",
18815
+ "may",
18816
+ "might",
18817
+ "shall",
18818
+ "can",
18819
+ "to",
18820
+ "of",
18821
+ "in",
18822
+ "for",
18823
+ "on",
18824
+ "with",
18825
+ "at",
18826
+ "by",
18827
+ "from",
18828
+ "as",
18829
+ "into",
18830
+ "through",
18831
+ "and",
18832
+ "but",
18833
+ "or",
18834
+ "nor",
18835
+ "so",
18836
+ "yet",
18837
+ "this",
18838
+ "that",
18839
+ "these",
18840
+ "those",
18841
+ "it",
18842
+ "its",
18843
+ "not",
18844
+ "no"
18845
+ ]);
18846
+ SUPERSEDABLE_TABLES = [
18847
+ { table: "brain_decisions", textCol: "decision", type: "decision" },
18848
+ { table: "brain_learnings", textCol: "insight", type: "learning" },
18849
+ { table: "brain_patterns", textCol: "pattern", type: "pattern" },
18850
+ { table: "brain_observations", textCol: "narrative", type: "observation" }
18851
+ ];
18852
+ }
18853
+ });
18854
+
18651
18855
  // packages/core/src/memory/patterns.ts
18652
18856
  var patterns_exports = {};
18653
18857
  __export(patterns_exports, {
@@ -18745,6 +18949,22 @@ async function storePattern(projectRoot, params) {
18745
18949
  { type: saved.type, impact: saved.impact ?? void 0 }
18746
18950
  ).catch(() => {
18747
18951
  });
18952
+ detectSupersession(projectRoot, {
18953
+ id: saved.id,
18954
+ text: saved.pattern + " " + saved.context,
18955
+ createdAt: saved.extractedAt ?? (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)
18956
+ }).then((candidates) => {
18957
+ for (const candidate of candidates) {
18958
+ supersedeMemory(
18959
+ projectRoot,
18960
+ candidate.existingId,
18961
+ saved.id,
18962
+ "auto:pattern-supersedes \u2014 high overlap detected at store time"
18963
+ ).catch(() => {
18964
+ });
18965
+ }
18966
+ }).catch(() => {
18967
+ });
18748
18968
  return {
18749
18969
  ...saved,
18750
18970
  examples: JSON.parse(saved.examplesJson || "[]")
@@ -18801,6 +19021,7 @@ var init_patterns = __esm({
18801
19021
  init_brain_accessor();
18802
19022
  init_graph_auto_populate();
18803
19023
  init_quality_scoring();
19024
+ init_temporal_supersession();
18804
19025
  }
18805
19026
  });
18806
19027
 
@@ -18892,6 +19113,22 @@ async function storeLearning(projectRoot, params) {
18892
19113
  { source: saved.source, confidence: saved.confidence, actionable: saved.actionable }
18893
19114
  ).catch(() => {
18894
19115
  });
19116
+ detectSupersession(projectRoot, {
19117
+ id: saved.id,
19118
+ text: saved.insight,
19119
+ createdAt: saved.createdAt ?? (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)
19120
+ }).then((candidates) => {
19121
+ for (const candidate of candidates) {
19122
+ supersedeMemory(
19123
+ projectRoot,
19124
+ candidate.existingId,
19125
+ saved.id,
19126
+ "auto:learning-supersedes \u2014 high overlap detected at store time"
19127
+ ).catch(() => {
19128
+ });
19129
+ }
19130
+ }).catch(() => {
19131
+ });
18895
19132
  return {
18896
19133
  ...saved,
18897
19134
  applicableTypes: JSON.parse(saved.applicableTypesJson || "[]")
@@ -18952,6 +19189,7 @@ var init_learnings = __esm({
18952
19189
  init_brain_accessor();
18953
19190
  init_graph_auto_populate();
18954
19191
  init_quality_scoring();
19192
+ init_temporal_supersession();
18955
19193
  }
18956
19194
  });
18957
19195
 
@@ -18990,16 +19228,6 @@ var init_mvi_helpers = __esm({
18990
19228
  }
18991
19229
  });
18992
19230
 
18993
- // packages/core/src/store/typed-query.ts
18994
- function typedAll(stmt, ...params) {
18995
- return stmt.all(...params);
18996
- }
18997
- var init_typed_query = __esm({
18998
- "packages/core/src/store/typed-query.ts"() {
18999
- "use strict";
19000
- }
19001
- });
19002
-
19003
19231
  // packages/core/src/memory/brain-similarity.ts
19004
19232
  function parseIdPrefix(id) {
19005
19233
  if (id.startsWith("D-") || /^D\d/.test(id)) return "decision";
@@ -20784,6 +21012,22 @@ async function storeDecision(projectRoot, params) {
20784
21012
  }
20785
21013
  } catch {
20786
21014
  }
21015
+ detectSupersession(projectRoot, {
21016
+ id: saved.id,
21017
+ text: saved.decision + " " + saved.rationale,
21018
+ createdAt: saved.createdAt ?? (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19)
21019
+ }).then((candidates) => {
21020
+ for (const candidate of candidates) {
21021
+ supersedeMemory(
21022
+ projectRoot,
21023
+ candidate.existingId,
21024
+ saved.id,
21025
+ "auto:decision-supersedes \u2014 high overlap detected at store time"
21026
+ ).catch(() => {
21027
+ });
21028
+ }
21029
+ }).catch(() => {
21030
+ });
20787
21031
  return saved;
20788
21032
  }
20789
21033
  async function recallDecision(projectRoot, id) {
@@ -20836,6 +21080,7 @@ var init_decisions2 = __esm({
20836
21080
  init_sqlite2();
20837
21081
  init_graph_auto_populate();
20838
21082
  init_quality_scoring();
21083
+ init_temporal_supersession();
20839
21084
  }
20840
21085
  });
20841
21086
 
@@ -41908,11 +42153,11 @@ var brain_consolidator_exports = {};
41908
42153
  __export(brain_consolidator_exports, {
41909
42154
  detectContradictions: () => detectContradictions
41910
42155
  });
41911
- function extractKeywords(text3) {
42156
+ function extractKeywords2(text3) {
41912
42157
  const words = text3.toLowerCase().replace(/[^a-z0-9\s\-_]/g, " ").split(/\s+/);
41913
42158
  const keywords = /* @__PURE__ */ new Set();
41914
42159
  for (const w of words) {
41915
- if (w.length >= 4 && !STOP_WORDS.has(w)) {
42160
+ if (w.length >= 4 && !STOP_WORDS2.has(w)) {
41916
42161
  keywords.add(w);
41917
42162
  }
41918
42163
  }
@@ -41961,7 +42206,7 @@ async function detectContradictions(projectRoot) {
41961
42206
  const keywordMap = /* @__PURE__ */ new Map();
41962
42207
  const negationMap = /* @__PURE__ */ new Map();
41963
42208
  for (const entry of entries) {
41964
- keywordMap.set(entry.id, extractKeywords(entry.text));
42209
+ keywordMap.set(entry.id, extractKeywords2(entry.text));
41965
42210
  negationMap.set(entry.id, findNegationMarkers(entry.text));
41966
42211
  }
41967
42212
  for (let i = 0; i < entries.length; i++) {
@@ -41975,7 +42220,7 @@ async function detectContradictions(projectRoot) {
41975
42220
  const keywordsB = keywordMap.get(entryB.id);
41976
42221
  const negationsB = negationMap.get(entryB.id);
41977
42222
  const shared = keywordIntersection(keywordsA, keywordsB);
41978
- if (shared.length < MIN_SHARED_KEYWORDS) continue;
42223
+ if (shared.length < MIN_SHARED_KEYWORDS2) continue;
41979
42224
  const negationsOnlyInA = negationsA.filter((m) => !negationsB.includes(m));
41980
42225
  const negationsOnlyInB = negationsB.filter((m) => !negationsA.includes(m));
41981
42226
  const negationFlip = negationsOnlyInA.length > 0 || negationsOnlyInB.length > 0;
@@ -41993,8 +42238,8 @@ async function detectContradictions(projectRoot) {
41993
42238
  sharedKeywords: shared.slice(0, 10),
41994
42239
  negationMarkers: negMarkers.slice(0, 5)
41995
42240
  });
41996
- const nodeA = buildNodeId(table, entryA.id);
41997
- const nodeB = buildNodeId(table, entryB.id);
42241
+ const nodeA = buildNodeId2(table, entryA.id);
42242
+ const nodeB = buildNodeId2(table, entryB.id);
41998
42243
  try {
41999
42244
  nativeDb.prepare(`
42000
42245
  INSERT OR IGNORE INTO brain_page_edges
@@ -42023,7 +42268,7 @@ async function detectContradictions(projectRoot) {
42023
42268
  }
42024
42269
  return results;
42025
42270
  }
42026
- function buildNodeId(table, entryId) {
42271
+ function buildNodeId2(table, entryId) {
42027
42272
  const typeMap = {
42028
42273
  brain_observations: "observation",
42029
42274
  brain_learnings: "learning",
@@ -42033,13 +42278,13 @@ function buildNodeId(table, entryId) {
42033
42278
  const type = typeMap[table] ?? "entry";
42034
42279
  return `${type}:${entryId}`;
42035
42280
  }
42036
- var MIN_SHARED_KEYWORDS, NEGATION_MARKERS, STOP_WORDS;
42281
+ var MIN_SHARED_KEYWORDS2, NEGATION_MARKERS, STOP_WORDS2;
42037
42282
  var init_brain_consolidator = __esm({
42038
42283
  "packages/core/src/memory/brain-consolidator.ts"() {
42039
42284
  "use strict";
42040
42285
  init_brain_sqlite();
42041
42286
  init_typed_query();
42042
- MIN_SHARED_KEYWORDS = 3;
42287
+ MIN_SHARED_KEYWORDS2 = 3;
42043
42288
  NEGATION_MARKERS = [
42044
42289
  "not",
42045
42290
  "never",
@@ -42060,7 +42305,7 @@ var init_brain_consolidator = __esm({
42060
42305
  "undone",
42061
42306
  "superseded"
42062
42307
  ];
42063
- STOP_WORDS = /* @__PURE__ */ new Set([
42308
+ STOP_WORDS2 = /* @__PURE__ */ new Set([
42064
42309
  "the",
42065
42310
  "a",
42066
42311
  "an",
@@ -42148,11 +42393,11 @@ async function applyTemporalDecay(projectRoot, options) {
42148
42393
  tablesProcessed: ["brain_learnings"]
42149
42394
  };
42150
42395
  }
42151
- function extractKeywords2(text3) {
42396
+ function extractKeywords3(text3) {
42152
42397
  const words = text3.toLowerCase().replace(/[^a-z0-9\s-]/g, "").split(/\s+/);
42153
42398
  const keywords = /* @__PURE__ */ new Set();
42154
42399
  for (const w of words) {
42155
- if (w.length >= 4 && !STOP_WORDS2.has(w)) {
42400
+ if (w.length >= 4 && !STOP_WORDS3.has(w)) {
42156
42401
  keywords.add(w);
42157
42402
  }
42158
42403
  }
@@ -42190,7 +42435,7 @@ async function consolidateMemories(projectRoot, options) {
42190
42435
  }
42191
42436
  const entries = oldObservations.map((obs) => ({
42192
42437
  ...obs,
42193
- keywords: extractKeywords2(`${obs.title} ${obs.narrative ?? ""}`),
42438
+ keywords: extractKeywords3(`${obs.title} ${obs.narrative ?? ""}`),
42194
42439
  clustered: false
42195
42440
  }));
42196
42441
  const clusters = [];
@@ -42592,13 +42837,13 @@ async function strengthenCoRetrievedEdges(projectRoot) {
42592
42837
  }
42593
42838
  return strengthened;
42594
42839
  }
42595
- var STOP_WORDS2;
42840
+ var STOP_WORDS3;
42596
42841
  var init_brain_lifecycle = __esm({
42597
42842
  "packages/core/src/memory/brain-lifecycle.ts"() {
42598
42843
  "use strict";
42599
42844
  init_brain_accessor();
42600
42845
  init_typed_query();
42601
- STOP_WORDS2 = /* @__PURE__ */ new Set([
42846
+ STOP_WORDS3 = /* @__PURE__ */ new Set([
42602
42847
  "the",
42603
42848
  "a",
42604
42849
  "an",
@@ -43203,6 +43448,358 @@ var init_session_hooks = __esm({
43203
43448
  }
43204
43449
  });
43205
43450
 
43451
+ // packages/core/src/memory/quality-feedback.ts
43452
+ var quality_feedback_exports = {};
43453
+ __export(quality_feedback_exports, {
43454
+ correlateOutcomes: () => correlateOutcomes,
43455
+ getMemoryQualityReport: () => getMemoryQualityReport,
43456
+ trackMemoryUsage: () => trackMemoryUsage
43457
+ });
43458
+ async function ensureUsageLogTable(projectRoot) {
43459
+ const { getBrainDb: getBrainDb2, getBrainNativeDb: getBrainNativeDb2 } = await Promise.resolve().then(() => (init_brain_sqlite(), brain_sqlite_exports));
43460
+ await getBrainDb2(projectRoot);
43461
+ const nativeDb = getBrainNativeDb2();
43462
+ if (!nativeDb) return;
43463
+ try {
43464
+ nativeDb.prepare(
43465
+ `CREATE TABLE IF NOT EXISTS brain_usage_log (
43466
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
43467
+ entry_id TEXT NOT NULL,
43468
+ task_id TEXT,
43469
+ used INTEGER NOT NULL DEFAULT 0,
43470
+ outcome TEXT NOT NULL DEFAULT 'unknown',
43471
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
43472
+ )`
43473
+ ).run();
43474
+ nativeDb.prepare(
43475
+ `CREATE INDEX IF NOT EXISTS idx_brain_usage_log_entry_id
43476
+ ON brain_usage_log(entry_id)`
43477
+ ).run();
43478
+ nativeDb.prepare(
43479
+ `CREATE INDEX IF NOT EXISTS idx_brain_usage_log_task_id
43480
+ ON brain_usage_log(task_id)`
43481
+ ).run();
43482
+ } catch {
43483
+ }
43484
+ }
43485
+ async function ensurePruneCandidateColumn(projectRoot) {
43486
+ const { getBrainDb: getBrainDb2, getBrainNativeDb: getBrainNativeDb2 } = await Promise.resolve().then(() => (init_brain_sqlite(), brain_sqlite_exports));
43487
+ await getBrainDb2(projectRoot);
43488
+ const nativeDb = getBrainNativeDb2();
43489
+ if (!nativeDb) return;
43490
+ const tables = [
43491
+ "brain_decisions",
43492
+ "brain_patterns",
43493
+ "brain_learnings",
43494
+ "brain_observations"
43495
+ ];
43496
+ for (const tbl of tables) {
43497
+ try {
43498
+ nativeDb.prepare(`ALTER TABLE ${tbl} ADD COLUMN prune_candidate INTEGER DEFAULT 0`).run();
43499
+ } catch {
43500
+ }
43501
+ }
43502
+ }
43503
+ async function trackMemoryUsage(projectRoot, memoryId, used, taskId, outcome = "unknown") {
43504
+ if (!memoryId?.trim()) return;
43505
+ await ensureUsageLogTable(projectRoot);
43506
+ const { getBrainNativeDb: getBrainNativeDb2 } = await Promise.resolve().then(() => (init_brain_sqlite(), brain_sqlite_exports));
43507
+ const nativeDb = getBrainNativeDb2();
43508
+ if (!nativeDb) return;
43509
+ const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
43510
+ try {
43511
+ nativeDb.prepare(
43512
+ `INSERT INTO brain_usage_log (entry_id, task_id, used, outcome, created_at)
43513
+ VALUES (?, ?, ?, ?, ?)`
43514
+ ).run(memoryId, taskId ?? null, used ? 1 : 0, outcome, now);
43515
+ } catch {
43516
+ }
43517
+ }
43518
+ function tableForId(id) {
43519
+ if (id.startsWith("D-") || /^D\d/.test(id)) return "brain_decisions";
43520
+ if (id.startsWith("P-") || /^P\d/.test(id)) return "brain_patterns";
43521
+ if (id.startsWith("L-") || /^L\d/.test(id)) return "brain_learnings";
43522
+ if (id.startsWith("O-") || id.startsWith("CM-") || /^O/.test(id)) return "brain_observations";
43523
+ return null;
43524
+ }
43525
+ function applyQualityDelta(nativeDb, table, id, delta, now) {
43526
+ if (!nativeDb) return;
43527
+ try {
43528
+ nativeDb.prepare(
43529
+ `UPDATE ${table}
43530
+ SET quality_score = MAX(0.0, MIN(1.0, COALESCE(quality_score, 0.5) + ?)),
43531
+ updated_at = ?
43532
+ WHERE id = ?`
43533
+ ).run(delta, now, id);
43534
+ } catch {
43535
+ }
43536
+ }
43537
+ async function correlateOutcomes(projectRoot) {
43538
+ await ensureUsageLogTable(projectRoot);
43539
+ await ensurePruneCandidateColumn(projectRoot);
43540
+ const { getBrainDb: getBrainDb2, getBrainNativeDb: getBrainNativeDb2 } = await Promise.resolve().then(() => (init_brain_sqlite(), brain_sqlite_exports));
43541
+ await getBrainDb2(projectRoot);
43542
+ const nativeDb = getBrainNativeDb2();
43543
+ const ranAt = (/* @__PURE__ */ new Date()).toISOString();
43544
+ if (!nativeDb) {
43545
+ return { boosted: 0, penalized: 0, flaggedForPruning: 0, ranAt };
43546
+ }
43547
+ const now = ranAt.replace("T", " ").slice(0, 19);
43548
+ let boosted = 0;
43549
+ let penalized = 0;
43550
+ let usageRows = [];
43551
+ try {
43552
+ usageRows = typedAll(
43553
+ nativeDb.prepare(
43554
+ `SELECT entry_id, outcome, SUM(used) AS used_count
43555
+ FROM brain_usage_log
43556
+ WHERE outcome IN ('success', 'failure')
43557
+ GROUP BY entry_id, outcome`
43558
+ )
43559
+ );
43560
+ } catch {
43561
+ usageRows = [];
43562
+ }
43563
+ for (const row of usageRows) {
43564
+ const table = tableForId(row.entry_id);
43565
+ if (!table) continue;
43566
+ if (row.outcome === "success" && row.used_count > 0) {
43567
+ applyQualityDelta(nativeDb, table, row.entry_id, 0.05, now);
43568
+ boosted++;
43569
+ } else if (row.outcome === "failure" && row.used_count > 0) {
43570
+ applyQualityDelta(nativeDb, table, row.entry_id, -0.05, now);
43571
+ penalized++;
43572
+ }
43573
+ }
43574
+ const cutoffDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString().replace("T", " ").slice(0, 19);
43575
+ let flaggedForPruning = 0;
43576
+ const pruneTargetTables = [
43577
+ { table: "brain_decisions", dateCol: "created_at" },
43578
+ { table: "brain_patterns", dateCol: "extracted_at" },
43579
+ { table: "brain_learnings", dateCol: "created_at" },
43580
+ { table: "brain_observations", dateCol: "created_at" }
43581
+ ];
43582
+ for (const { table, dateCol } of pruneTargetTables) {
43583
+ try {
43584
+ const result = nativeDb.prepare(
43585
+ `UPDATE ${table}
43586
+ SET prune_candidate = 1
43587
+ WHERE COALESCE(citation_count, 0) = 0
43588
+ AND ${dateCol} < ?`
43589
+ ).run(cutoffDate);
43590
+ flaggedForPruning += result.changes ?? 0;
43591
+ } catch {
43592
+ }
43593
+ }
43594
+ return { boosted, penalized, flaggedForPruning, ranAt };
43595
+ }
43596
+ async function getMemoryQualityReport(projectRoot) {
43597
+ await ensureUsageLogTable(projectRoot);
43598
+ const { getBrainDb: getBrainDb2, getBrainNativeDb: getBrainNativeDb2 } = await Promise.resolve().then(() => (init_brain_sqlite(), brain_sqlite_exports));
43599
+ await getBrainDb2(projectRoot);
43600
+ const nativeDb = getBrainNativeDb2();
43601
+ const emptyReport = {
43602
+ totalRetrievals: 0,
43603
+ uniqueEntriesRetrieved: 0,
43604
+ usageRate: 0,
43605
+ topRetrieved: [],
43606
+ neverRetrieved: [],
43607
+ qualityDistribution: { low: 0, medium: 0, high: 0 },
43608
+ tierDistribution: { short: 0, medium: 0, long: 0, unknown: 0 },
43609
+ noiseRatio: 0
43610
+ };
43611
+ if (!nativeDb) return emptyReport;
43612
+ let totalRetrievals = 0;
43613
+ let uniqueEntriesRetrieved = 0;
43614
+ try {
43615
+ const logCount = typedAll(
43616
+ nativeDb.prepare("SELECT COUNT(*) AS cnt FROM brain_retrieval_log")
43617
+ );
43618
+ totalRetrievals = logCount[0]?.cnt ?? 0;
43619
+ const uniqueCount = typedAll(
43620
+ nativeDb.prepare(
43621
+ `SELECT COUNT(DISTINCT value) AS cnt
43622
+ FROM brain_retrieval_log,
43623
+ json_each('["' || replace(entry_ids, ',', '","') || '"]')`
43624
+ )
43625
+ );
43626
+ uniqueEntriesRetrieved = uniqueCount[0]?.cnt ?? 0;
43627
+ } catch {
43628
+ }
43629
+ let usageRate = 0;
43630
+ try {
43631
+ const totalUsage = typedAll(
43632
+ nativeDb.prepare("SELECT COUNT(*) AS cnt FROM brain_usage_log")
43633
+ );
43634
+ const usedCount = typedAll(
43635
+ nativeDb.prepare("SELECT COUNT(*) AS cnt FROM brain_usage_log WHERE used = 1")
43636
+ );
43637
+ const total = totalUsage[0]?.cnt ?? 0;
43638
+ const used = usedCount[0]?.cnt ?? 0;
43639
+ usageRate = total > 0 ? used / total : 0;
43640
+ } catch {
43641
+ }
43642
+ const topRetrieved = [];
43643
+ try {
43644
+ const rows = typedAll(
43645
+ nativeDb.prepare(
43646
+ `SELECT id,
43647
+ 'decision' AS type,
43648
+ decision AS title,
43649
+ COALESCE(citation_count, 0) AS citation_count
43650
+ FROM brain_decisions
43651
+ UNION ALL
43652
+ SELECT id,
43653
+ 'pattern' AS type,
43654
+ pattern AS title,
43655
+ COALESCE(citation_count, 0) AS citation_count
43656
+ FROM brain_patterns
43657
+ UNION ALL
43658
+ SELECT id,
43659
+ 'learning' AS type,
43660
+ insight AS title,
43661
+ COALESCE(citation_count, 0) AS citation_count
43662
+ FROM brain_learnings
43663
+ UNION ALL
43664
+ SELECT id,
43665
+ 'observation' AS type,
43666
+ title AS title,
43667
+ COALESCE(citation_count, 0) AS citation_count
43668
+ FROM brain_observations
43669
+ ORDER BY citation_count DESC
43670
+ LIMIT 10`
43671
+ )
43672
+ );
43673
+ for (const r of rows) {
43674
+ topRetrieved.push({
43675
+ id: r.id,
43676
+ type: r.type,
43677
+ title: String(r.title ?? "").slice(0, 120),
43678
+ citationCount: r.citation_count
43679
+ });
43680
+ }
43681
+ } catch {
43682
+ }
43683
+ const neverRetrieved = [];
43684
+ try {
43685
+ const rows = typedAll(
43686
+ nativeDb.prepare(
43687
+ `SELECT id,
43688
+ 'decision' AS type,
43689
+ decision AS title,
43690
+ COALESCE(quality_score, 0.5) AS quality_score
43691
+ FROM brain_decisions
43692
+ WHERE COALESCE(citation_count, 0) = 0
43693
+ UNION ALL
43694
+ SELECT id,
43695
+ 'pattern' AS type,
43696
+ pattern AS title,
43697
+ COALESCE(quality_score, 0.5) AS quality_score
43698
+ FROM brain_patterns
43699
+ WHERE COALESCE(citation_count, 0) = 0
43700
+ UNION ALL
43701
+ SELECT id,
43702
+ 'learning' AS type,
43703
+ insight AS title,
43704
+ COALESCE(quality_score, 0.5) AS quality_score
43705
+ FROM brain_learnings
43706
+ WHERE COALESCE(citation_count, 0) = 0
43707
+ UNION ALL
43708
+ SELECT id,
43709
+ 'observation' AS type,
43710
+ title AS title,
43711
+ COALESCE(quality_score, 0.5) AS quality_score
43712
+ FROM brain_observations
43713
+ WHERE COALESCE(citation_count, 0) = 0
43714
+ ORDER BY quality_score ASC
43715
+ LIMIT 10`
43716
+ )
43717
+ );
43718
+ for (const r of rows) {
43719
+ neverRetrieved.push({
43720
+ id: r.id,
43721
+ type: r.type,
43722
+ title: String(r.title ?? "").slice(0, 120),
43723
+ qualityScore: r.quality_score
43724
+ });
43725
+ }
43726
+ } catch {
43727
+ }
43728
+ let qualityDistribution = { low: 0, medium: 0, high: 0 };
43729
+ try {
43730
+ const rows = typedAll(
43731
+ nativeDb.prepare(
43732
+ `SELECT
43733
+ SUM(CASE WHEN qs < 0.3 THEN 1 ELSE 0 END) AS low,
43734
+ SUM(CASE WHEN qs >= 0.3 AND qs <= 0.6 THEN 1 ELSE 0 END) AS medium,
43735
+ SUM(CASE WHEN qs > 0.6 THEN 1 ELSE 0 END) AS high
43736
+ FROM (
43737
+ SELECT COALESCE(quality_score, 0.5) AS qs FROM brain_decisions
43738
+ UNION ALL
43739
+ SELECT COALESCE(quality_score, 0.5) AS qs FROM brain_patterns
43740
+ UNION ALL
43741
+ SELECT COALESCE(quality_score, 0.5) AS qs FROM brain_learnings
43742
+ UNION ALL
43743
+ SELECT COALESCE(quality_score, 0.5) AS qs FROM brain_observations
43744
+ )`
43745
+ )
43746
+ );
43747
+ if (rows[0]) {
43748
+ qualityDistribution = {
43749
+ low: rows[0].low ?? 0,
43750
+ medium: rows[0].medium ?? 0,
43751
+ high: rows[0].high ?? 0
43752
+ };
43753
+ }
43754
+ } catch {
43755
+ }
43756
+ const tierDistribution = { short: 0, medium: 0, long: 0, unknown: 0 };
43757
+ try {
43758
+ const rows = typedAll(
43759
+ nativeDb.prepare(
43760
+ `SELECT memory_tier AS tier, COUNT(*) AS cnt
43761
+ FROM (
43762
+ SELECT memory_tier FROM brain_decisions
43763
+ UNION ALL
43764
+ SELECT memory_tier FROM brain_patterns
43765
+ UNION ALL
43766
+ SELECT memory_tier FROM brain_learnings
43767
+ UNION ALL
43768
+ SELECT memory_tier FROM brain_observations
43769
+ )
43770
+ GROUP BY memory_tier`
43771
+ )
43772
+ );
43773
+ for (const r of rows) {
43774
+ const tier = r.tier?.toLowerCase() ?? "unknown";
43775
+ if (tier === "short" || tier === "medium" || tier === "long") {
43776
+ tierDistribution[tier] += r.cnt;
43777
+ } else {
43778
+ tierDistribution.unknown += r.cnt;
43779
+ }
43780
+ }
43781
+ } catch {
43782
+ }
43783
+ const totalEntries = qualityDistribution.low + qualityDistribution.medium + qualityDistribution.high;
43784
+ const noiseRatio = totalEntries > 0 ? qualityDistribution.low / totalEntries : 0;
43785
+ return {
43786
+ totalRetrievals,
43787
+ uniqueEntriesRetrieved,
43788
+ usageRate,
43789
+ topRetrieved,
43790
+ neverRetrieved,
43791
+ qualityDistribution,
43792
+ tierDistribution,
43793
+ noiseRatio
43794
+ };
43795
+ }
43796
+ var init_quality_feedback = __esm({
43797
+ "packages/core/src/memory/quality-feedback.ts"() {
43798
+ "use strict";
43799
+ init_typed_query();
43800
+ }
43801
+ });
43802
+
43206
43803
  // packages/core/src/hooks/handlers/task-hooks.ts
43207
43804
  async function handleToolStart(projectRoot, payload) {
43208
43805
  const { observeBrain: observeBrain2 } = await Promise.resolve().then(() => (init_brain_retrieval(), brain_retrieval_exports));
@@ -43236,6 +43833,13 @@ async function handleToolComplete(projectRoot, payload) {
43236
43833
  } catch {
43237
43834
  }
43238
43835
  });
43836
+ setImmediate(async () => {
43837
+ try {
43838
+ const { correlateOutcomes: correlateOutcomes2 } = await Promise.resolve().then(() => (init_quality_feedback(), quality_feedback_exports));
43839
+ await correlateOutcomes2(projectRoot);
43840
+ } catch {
43841
+ }
43842
+ });
43239
43843
  await maybeRefreshMemoryBridge(projectRoot);
43240
43844
  }
43241
43845
  var init_task_hooks = __esm({
@@ -57502,7 +58106,7 @@ function generateRecommendation2(changeType, affectedCount, cascadeDepth, taskId
57502
58106
  }
57503
58107
  }
57504
58108
  function scoreTaskMatch(change, task) {
57505
- const STOP_WORDS4 = /* @__PURE__ */ new Set([
58109
+ const STOP_WORDS5 = /* @__PURE__ */ new Set([
57506
58110
  "a",
57507
58111
  "an",
57508
58112
  "the",
@@ -57524,7 +58128,7 @@ function scoreTaskMatch(change, task) {
57524
58128
  "do",
57525
58129
  "not"
57526
58130
  ]);
57527
- const tokenise = (text3) => text3.toLowerCase().split(/\W+/).filter((t) => t.length > 2 && !STOP_WORDS4.has(t));
58131
+ const tokenise = (text3) => text3.toLowerCase().split(/\W+/).filter((t) => t.length > 2 && !STOP_WORDS5.has(t));
57528
58132
  const changeTokens = new Set(tokenise(change));
57529
58133
  if (changeTokens.size === 0) return 0;
57530
58134
  const taskText = `${task.title ?? ""} ${task.description ?? ""}`;
@@ -60490,6 +61094,7 @@ __export(memory_exports, {
60490
61094
  bulkLink: () => bulkLink,
60491
61095
  compactManifest: () => compactManifest,
60492
61096
  consolidateMemories: () => consolidateMemories,
61097
+ correlateOutcomes: () => correlateOutcomes,
60493
61098
  detectContradictions: () => detectContradictions,
60494
61099
  ensureFts5Tables: () => ensureFts5Tables,
60495
61100
  fetchBrainEntries: () => fetchBrainEntries,
@@ -60500,6 +61105,7 @@ __export(memory_exports, {
60500
61105
  getLinkedLearnings: () => getLinkedLearnings,
60501
61106
  getLinkedPatterns: () => getLinkedPatterns,
60502
61107
  getMemoryLinks: () => getMemoryLinks,
61108
+ getMemoryQualityReport: () => getMemoryQualityReport,
60503
61109
  getTaskLinks: () => getTaskLinks,
60504
61110
  hybridSearch: () => hybridSearch,
60505
61111
  learningStats: () => learningStats,
@@ -60541,6 +61147,7 @@ __export(memory_exports, {
60541
61147
  storePattern: () => storePattern,
60542
61148
  storeVerifiedCandidate: () => storeVerifiedCandidate,
60543
61149
  timelineBrain: () => timelineBrain,
61150
+ trackMemoryUsage: () => trackMemoryUsage,
60544
61151
  unlinkMemoryFromTask: () => unlinkMemoryFromTask,
60545
61152
  updateDecisionOutcome: () => updateDecisionOutcome,
60546
61153
  updateResearch: () => updateResearch,
@@ -60932,6 +61539,7 @@ async function verifyAndStoreBatch(projectRoot, candidates) {
60932
61539
  // packages/core/src/memory/index.ts
60933
61540
  init_learnings();
60934
61541
  init_patterns();
61542
+ init_quality_feedback();
60935
61543
  function getResearchPath(cwd) {
60936
61544
  return join49(getCleoDirAbsolute(cwd), "research.json");
60937
61545
  }
@@ -63848,7 +64456,7 @@ __export(nexus_exports, {
63848
64456
  criticalPath: () => criticalPath,
63849
64457
  discoverRelated: () => discoverRelated,
63850
64458
  executeTransfer: () => executeTransfer,
63851
- extractKeywords: () => extractKeywords3,
64459
+ extractKeywords: () => extractKeywords4,
63852
64460
  generateProjectHash: () => generateProjectHash,
63853
64461
  getCurrentProject: () => getCurrentProject,
63854
64462
  getNexusCacheDir: () => getNexusCacheDir,
@@ -64391,7 +64999,7 @@ async function orphanDetection() {
64391
64999
  // packages/core/src/nexus/discover.ts
64392
65000
  init_data_accessor();
64393
65001
  init_registry3();
64394
- var STOP_WORDS3 = /* @__PURE__ */ new Set([
65002
+ var STOP_WORDS4 = /* @__PURE__ */ new Set([
64395
65003
  "the",
64396
65004
  "a",
64397
65005
  "an",
@@ -64473,8 +65081,8 @@ var STOP_WORDS3 = /* @__PURE__ */ new Set([
64473
65081
  "it",
64474
65082
  "its"
64475
65083
  ]);
64476
- function extractKeywords3(text3) {
64477
- return text3.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOP_WORDS3.has(w));
65084
+ function extractKeywords4(text3) {
65085
+ return text3.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOP_WORDS4.has(w));
64478
65086
  }
64479
65087
  async function discoverRelated(taskQuery, method = "auto", limit = 10) {
64480
65088
  if (!validateSyntax(taskQuery)) {
@@ -64497,7 +65105,7 @@ async function discoverRelated(taskQuery, method = "auto", limit = 10) {
64497
65105
  const sourceLabels = new Set(sourceTask.labels ?? []);
64498
65106
  const sourceDesc = (sourceTask.description ?? "").toLowerCase();
64499
65107
  const sourceTitle = (sourceTask.title ?? "").toLowerCase();
64500
- const sourceWords = extractKeywords3(sourceTitle + " " + sourceDesc);
65108
+ const sourceWords = extractKeywords4(sourceTitle + " " + sourceDesc);
64501
65109
  const parsed = parseQuery(taskQuery);
64502
65110
  const registry2 = await readRegistry();
64503
65111
  if (!registry2) {
@@ -64532,7 +65140,7 @@ async function discoverRelated(taskQuery, method = "auto", limit = 10) {
64532
65140
  }
64533
65141
  if (method === "description" || method === "auto") {
64534
65142
  const taskDesc = ((task.description ?? "") + " " + (task.title ?? "")).toLowerCase();
64535
- const taskWords = extractKeywords3(taskDesc);
65143
+ const taskWords = extractKeywords4(taskDesc);
64536
65144
  const commonWords = sourceWords.filter((w) => taskWords.includes(w));
64537
65145
  if (commonWords.length > 0) {
64538
65146
  const descScore = commonWords.length / Math.max(sourceWords.length, taskWords.length, 1);
@@ -69574,6 +70182,7 @@ __export(research_exports, {
69574
70182
  bulkLink: () => bulkLink,
69575
70183
  compactManifest: () => compactManifest,
69576
70184
  consolidateMemories: () => consolidateMemories,
70185
+ correlateOutcomes: () => correlateOutcomes,
69577
70186
  detectContradictions: () => detectContradictions,
69578
70187
  ensureFts5Tables: () => ensureFts5Tables,
69579
70188
  fetchBrainEntries: () => fetchBrainEntries,
@@ -69584,6 +70193,7 @@ __export(research_exports, {
69584
70193
  getLinkedLearnings: () => getLinkedLearnings,
69585
70194
  getLinkedPatterns: () => getLinkedPatterns,
69586
70195
  getMemoryLinks: () => getMemoryLinks,
70196
+ getMemoryQualityReport: () => getMemoryQualityReport,
69587
70197
  getTaskLinks: () => getTaskLinks,
69588
70198
  hybridSearch: () => hybridSearch,
69589
70199
  learningStats: () => learningStats,
@@ -69625,6 +70235,7 @@ __export(research_exports, {
69625
70235
  storePattern: () => storePattern,
69626
70236
  storeVerifiedCandidate: () => storeVerifiedCandidate,
69627
70237
  timelineBrain: () => timelineBrain,
70238
+ trackMemoryUsage: () => trackMemoryUsage,
69628
70239
  unlinkMemoryFromTask: () => unlinkMemoryFromTask,
69629
70240
  updateDecisionOutcome: () => updateDecisionOutcome,
69630
70241
  updateResearch: () => updateResearch,