@cleocode/core 2026.4.37 → 2026.4.39

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 (66) 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 +1048 -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/brain-lifecycle.d.ts +2 -0
  11. package/dist/memory/brain-lifecycle.d.ts.map +1 -1
  12. package/dist/memory/decisions.d.ts.map +1 -1
  13. package/dist/memory/decisions.js +18 -0
  14. package/dist/memory/decisions.js.map +1 -1
  15. package/dist/memory/engine-compat.d.ts +17 -0
  16. package/dist/memory/engine-compat.d.ts.map +1 -1
  17. package/dist/memory/engine-compat.js +36 -0
  18. package/dist/memory/engine-compat.js.map +1 -1
  19. package/dist/memory/graph-memory-bridge.d.ts +158 -0
  20. package/dist/memory/graph-memory-bridge.d.ts.map +1 -0
  21. package/dist/memory/graph-memory-bridge.js +519 -0
  22. package/dist/memory/graph-memory-bridge.js.map +1 -0
  23. package/dist/memory/index.d.ts +1 -0
  24. package/dist/memory/index.d.ts.map +1 -1
  25. package/dist/memory/index.js +2 -0
  26. package/dist/memory/index.js.map +1 -1
  27. package/dist/memory/learnings.d.ts.map +1 -1
  28. package/dist/memory/learnings.js +18 -0
  29. package/dist/memory/learnings.js.map +1 -1
  30. package/dist/memory/llm-extraction.js.map +1 -1
  31. package/dist/memory/patterns.d.ts.map +1 -1
  32. package/dist/memory/patterns.js +18 -0
  33. package/dist/memory/patterns.js.map +1 -1
  34. package/dist/memory/quality-feedback.d.ts +129 -0
  35. package/dist/memory/quality-feedback.d.ts.map +1 -0
  36. package/dist/memory/quality-feedback.js +449 -0
  37. package/dist/memory/quality-feedback.js.map +1 -0
  38. package/dist/memory/sleep-consolidation.d.ts +98 -0
  39. package/dist/memory/sleep-consolidation.d.ts.map +1 -0
  40. package/dist/memory/sleep-consolidation.js +706 -0
  41. package/dist/memory/sleep-consolidation.js.map +1 -0
  42. package/dist/memory/temporal-supersession.d.ts +155 -0
  43. package/dist/memory/temporal-supersession.d.ts.map +1 -0
  44. package/dist/memory/temporal-supersession.js +406 -0
  45. package/dist/memory/temporal-supersession.js.map +1 -0
  46. package/dist/tasks/complete.d.ts.map +1 -1
  47. package/package.json +8 -8
  48. package/src/hooks/handlers/task-hooks.ts +11 -0
  49. package/src/internal.ts +12 -0
  50. package/src/memory/__tests__/graph-memory-bridge.test.ts +357 -0
  51. package/src/memory/__tests__/llm-extraction.test.ts +17 -0
  52. package/src/memory/__tests__/quality-feedback.test.ts +418 -0
  53. package/src/memory/__tests__/sleep-consolidation.test.ts +790 -0
  54. package/src/memory/__tests__/temporal-supersession.test.ts +534 -0
  55. package/src/memory/brain-lifecycle.ts +13 -0
  56. package/src/memory/decisions.ts +24 -0
  57. package/src/memory/engine-compat.ts +37 -0
  58. package/src/memory/graph-memory-bridge.ts +751 -0
  59. package/src/memory/index.ts +2 -0
  60. package/src/memory/learnings.ts +24 -0
  61. package/src/memory/patterns.ts +24 -0
  62. package/src/memory/quality-feedback.ts +640 -0
  63. package/src/memory/sleep-consolidation.ts +932 -0
  64. package/src/memory/temporal-supersession.ts +568 -0
  65. package/src/store/__tests__/performance-safety.test.ts +4 -4
  66. package/src/tasks/complete.ts +20 -0
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",
@@ -42115,6 +42360,390 @@ var init_brain_consolidator = __esm({
42115
42360
  }
42116
42361
  });
42117
42362
 
42363
+ // packages/core/src/memory/graph-memory-bridge.ts
42364
+ var graph_memory_bridge_exports = {};
42365
+ __export(graph_memory_bridge_exports, {
42366
+ autoLinkMemories: () => autoLinkMemories,
42367
+ linkMemoryToCode: () => linkMemoryToCode,
42368
+ listCodeLinks: () => listCodeLinks,
42369
+ queryCodeForMemory: () => queryCodeForMemory,
42370
+ queryMemoriesForCode: () => queryMemoriesForCode
42371
+ });
42372
+ function extractFilePaths(text3) {
42373
+ const paths = /* @__PURE__ */ new Set();
42374
+ for (const m of text3.matchAll(FILE_PATH_PATTERN)) {
42375
+ const p = m[1];
42376
+ if (p) paths.add(p);
42377
+ }
42378
+ return Array.from(paths);
42379
+ }
42380
+ function extractSymbolCandidates(text3) {
42381
+ const syms = /* @__PURE__ */ new Set();
42382
+ for (const m of text3.matchAll(SYMBOL_PATTERN)) {
42383
+ const s = m[1];
42384
+ if (s && s.length >= 4 && !SYMBOL_STOP_WORDS.has(s.toLowerCase())) {
42385
+ syms.add(s);
42386
+ }
42387
+ }
42388
+ return Array.from(syms);
42389
+ }
42390
+ function metadataText(metaJson) {
42391
+ if (!metaJson) return "";
42392
+ try {
42393
+ const obj = JSON.parse(metaJson);
42394
+ return Object.values(obj).filter((v) => typeof v === "string").join(" ");
42395
+ } catch {
42396
+ return "";
42397
+ }
42398
+ }
42399
+ async function linkMemoryToCode(projectRoot, memoryId, codeSymbol) {
42400
+ try {
42401
+ const brainDb = await getBrainDb(projectRoot);
42402
+ await getNexusDb();
42403
+ const nexusNative = getNexusNativeDb();
42404
+ if (!nexusNative) return false;
42405
+ const nexusNode = nexusNative.prepare("SELECT id, label, file_path, kind FROM nexus_nodes WHERE id = ? LIMIT 1").get(codeSymbol);
42406
+ if (!nexusNode) return false;
42407
+ const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
42408
+ const idParts = memoryId.split(":");
42409
+ const nodeType = idParts[0] ?? "observation";
42410
+ await brainDb.insert(brainPageNodes).values({
42411
+ id: memoryId,
42412
+ nodeType,
42413
+ label: memoryId,
42414
+ qualityScore: 0.5,
42415
+ contentHash: null,
42416
+ lastActivityAt: now,
42417
+ createdAt: now,
42418
+ updatedAt: now
42419
+ }).onConflictDoUpdate({
42420
+ target: brainPageNodes.id,
42421
+ set: { lastActivityAt: now, updatedAt: now }
42422
+ });
42423
+ await brainDb.insert(brainPageEdges).values({
42424
+ fromId: memoryId,
42425
+ toId: codeSymbol,
42426
+ edgeType: "code_reference",
42427
+ weight: 1,
42428
+ provenance: "manual",
42429
+ createdAt: now
42430
+ }).onConflictDoNothing();
42431
+ return true;
42432
+ } catch (err) {
42433
+ console.warn("[graph-memory-bridge] linkMemoryToCode failed:", err);
42434
+ return false;
42435
+ }
42436
+ }
42437
+ async function autoLinkMemories(projectRoot) {
42438
+ const result = { scanned: 0, linked: 0, alreadyLinked: 0, links: [] };
42439
+ try {
42440
+ await getBrainDb(projectRoot);
42441
+ const brainNative = getBrainNativeDb();
42442
+ await getNexusDb();
42443
+ const nexusNative = getNexusNativeDb();
42444
+ if (!brainNative || !nexusNative) return result;
42445
+ const brainNodes = typedAll(
42446
+ brainNative.prepare(`
42447
+ SELECT id, node_type, label, quality_score, metadata_json
42448
+ FROM brain_page_nodes
42449
+ WHERE node_type IN ('observation', 'decision', 'pattern', 'learning')
42450
+ AND quality_score >= 0.3
42451
+ ORDER BY quality_score DESC
42452
+ LIMIT 500
42453
+ `)
42454
+ );
42455
+ result.scanned = brainNodes.length;
42456
+ if (brainNodes.length === 0) return result;
42457
+ const nexusNodes2 = typedAll(
42458
+ nexusNative.prepare(`
42459
+ SELECT id, label, name, file_path, kind
42460
+ FROM nexus_nodes
42461
+ WHERE kind NOT IN ('community', 'process', 'folder')
42462
+ LIMIT 20000
42463
+ `)
42464
+ );
42465
+ if (nexusNodes2.length === 0) return result;
42466
+ const byFilePath = /* @__PURE__ */ new Map();
42467
+ const byNameExact = /* @__PURE__ */ new Map();
42468
+ const byNameLower = /* @__PURE__ */ new Map();
42469
+ for (const node of nexusNodes2) {
42470
+ if (node.file_path) {
42471
+ const fp = node.file_path.toLowerCase();
42472
+ const existing = byFilePath.get(fp) ?? [];
42473
+ existing.push(node);
42474
+ byFilePath.set(fp, existing);
42475
+ }
42476
+ if (node.name) {
42477
+ const exact = byNameExact.get(node.name) ?? [];
42478
+ exact.push(node);
42479
+ byNameExact.set(node.name, exact);
42480
+ const lower = node.name.toLowerCase();
42481
+ const fuzzy = byNameLower.get(lower) ?? [];
42482
+ fuzzy.push(node);
42483
+ byNameLower.set(lower, fuzzy);
42484
+ }
42485
+ }
42486
+ const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
42487
+ const existingEdges = /* @__PURE__ */ new Set();
42488
+ const rawEdges = typedAll(
42489
+ brainNative.prepare(`
42490
+ SELECT from_id, to_id FROM brain_page_edges WHERE edge_type = 'code_reference'
42491
+ `)
42492
+ );
42493
+ for (const e of rawEdges) {
42494
+ existingEdges.add(`${e.from_id}|${e.to_id}`);
42495
+ }
42496
+ for (const brainNode of brainNodes) {
42497
+ const corpus = `${brainNode.label} ${metadataText(brainNode.metadata_json)}`;
42498
+ const filePaths = extractFilePaths(corpus);
42499
+ const symbolCandidates = extractSymbolCandidates(corpus);
42500
+ const candidates = [];
42501
+ for (const fp of filePaths) {
42502
+ const matches = byFilePath.get(fp.toLowerCase());
42503
+ if (matches) {
42504
+ for (const n of matches) {
42505
+ candidates.push({ nexusNode: n, strategy: "exact-file", weight: 1 });
42506
+ }
42507
+ }
42508
+ }
42509
+ for (const sym of symbolCandidates) {
42510
+ const exactMatches = byNameExact.get(sym);
42511
+ if (exactMatches) {
42512
+ for (const n of exactMatches) {
42513
+ candidates.push({ nexusNode: n, strategy: "exact-symbol", weight: 1 });
42514
+ }
42515
+ }
42516
+ }
42517
+ const exactSymSet = new Set(
42518
+ symbolCandidates.flatMap((s) => byNameExact.get(s) ?? []).map((n) => n.id)
42519
+ );
42520
+ for (const sym of symbolCandidates) {
42521
+ if (sym.length < 5) continue;
42522
+ const lower = sym.toLowerCase();
42523
+ const fuzzyMatches = byNameLower.get(lower);
42524
+ if (fuzzyMatches) {
42525
+ for (const n of fuzzyMatches) {
42526
+ if (!exactSymSet.has(n.id)) {
42527
+ candidates.push({ nexusNode: n, strategy: "fuzzy-symbol", weight: 0.6 });
42528
+ }
42529
+ }
42530
+ }
42531
+ }
42532
+ const bestByNexusId = /* @__PURE__ */ new Map();
42533
+ for (const c of candidates) {
42534
+ const existing = bestByNexusId.get(c.nexusNode.id);
42535
+ if (!existing || c.weight > existing.weight) {
42536
+ bestByNexusId.set(c.nexusNode.id, c);
42537
+ }
42538
+ }
42539
+ const sortedCandidates = Array.from(bestByNexusId.values()).sort((a, b) => b.weight - a.weight).slice(0, 10);
42540
+ for (const { nexusNode, strategy, weight } of sortedCandidates) {
42541
+ const edgeKey = `${brainNode.id}|${nexusNode.id}`;
42542
+ if (existingEdges.has(edgeKey)) {
42543
+ result.alreadyLinked++;
42544
+ continue;
42545
+ }
42546
+ brainNative.prepare(`
42547
+ INSERT OR IGNORE INTO brain_page_nodes
42548
+ (id, node_type, label, quality_score, content_hash, metadata_json, last_activity_at, created_at, updated_at)
42549
+ VALUES (?, ?, ?, ?, NULL, NULL, ?, ?, ?)
42550
+ `).run(
42551
+ brainNode.id,
42552
+ brainNode.node_type,
42553
+ brainNode.label,
42554
+ brainNode.quality_score,
42555
+ now,
42556
+ now,
42557
+ now
42558
+ );
42559
+ try {
42560
+ brainNative.prepare(`
42561
+ INSERT OR IGNORE INTO brain_page_edges
42562
+ (from_id, to_id, edge_type, weight, provenance, created_at)
42563
+ VALUES (?, ?, 'code_reference', ?, ?, ?)
42564
+ `).run(brainNode.id, nexusNode.id, weight, `auto:${strategy}`, now);
42565
+ existingEdges.add(edgeKey);
42566
+ result.linked++;
42567
+ result.links.push({
42568
+ brainNodeId: brainNode.id,
42569
+ nexusNodeId: nexusNode.id,
42570
+ nexusLabel: nexusNode.label,
42571
+ matchStrategy: strategy,
42572
+ weight
42573
+ });
42574
+ } catch (edgeErr) {
42575
+ console.warn("[graph-memory-bridge] edge insert failed:", edgeErr);
42576
+ }
42577
+ }
42578
+ }
42579
+ } catch (err) {
42580
+ console.warn("[graph-memory-bridge] autoLinkMemories failed:", err);
42581
+ }
42582
+ return result;
42583
+ }
42584
+ async function queryMemoriesForCode(projectRoot, symbol2) {
42585
+ const result = { nexusNodeId: symbol2, memories: [] };
42586
+ try {
42587
+ await getBrainDb(projectRoot);
42588
+ const brainNative = getBrainNativeDb();
42589
+ if (!brainNative) return result;
42590
+ const rows = typedAll(
42591
+ brainNative.prepare(`
42592
+ SELECT n.id, n.node_type, n.label, n.quality_score,
42593
+ e.weight, e.provenance
42594
+ FROM brain_page_edges e
42595
+ JOIN brain_page_nodes n ON n.id = e.from_id
42596
+ WHERE e.to_id = ?
42597
+ AND e.edge_type = 'code_reference'
42598
+ ORDER BY e.weight DESC, n.quality_score DESC
42599
+ LIMIT 50
42600
+ `),
42601
+ symbol2
42602
+ );
42603
+ result.memories = rows.map((r) => ({
42604
+ nodeId: r.id,
42605
+ nodeType: r.node_type,
42606
+ label: r.label,
42607
+ qualityScore: r.quality_score,
42608
+ edgeWeight: r.weight,
42609
+ matchStrategy: r.provenance?.replace("auto:", "") ?? "manual"
42610
+ }));
42611
+ } catch (err) {
42612
+ console.warn("[graph-memory-bridge] queryMemoriesForCode failed:", err);
42613
+ }
42614
+ return result;
42615
+ }
42616
+ async function queryCodeForMemory(projectRoot, memoryId) {
42617
+ const result = { brainNodeId: memoryId, codeNodes: [] };
42618
+ try {
42619
+ await getBrainDb(projectRoot);
42620
+ const brainNative = getBrainNativeDb();
42621
+ await getNexusDb();
42622
+ const nexusNative = getNexusNativeDb();
42623
+ if (!brainNative || !nexusNative) return result;
42624
+ const brainEdges = typedAll(
42625
+ brainNative.prepare(`
42626
+ SELECT to_id, weight, provenance
42627
+ FROM brain_page_edges
42628
+ WHERE from_id = ?
42629
+ AND edge_type = 'code_reference'
42630
+ ORDER BY weight DESC
42631
+ LIMIT 50
42632
+ `),
42633
+ memoryId
42634
+ );
42635
+ if (brainEdges.length === 0) return result;
42636
+ for (const edge of brainEdges) {
42637
+ const nexusNode = nexusNative.prepare("SELECT id, label, file_path, kind FROM nexus_nodes WHERE id = ? LIMIT 1").get(edge.to_id);
42638
+ if (nexusNode) {
42639
+ result.codeNodes.push({
42640
+ nexusNodeId: nexusNode.id,
42641
+ label: nexusNode.label,
42642
+ filePath: nexusNode.file_path,
42643
+ kind: nexusNode.kind,
42644
+ edgeWeight: edge.weight,
42645
+ matchStrategy: edge.provenance?.replace("auto:", "") ?? "manual"
42646
+ });
42647
+ }
42648
+ }
42649
+ } catch (err) {
42650
+ console.warn("[graph-memory-bridge] queryCodeForMemory failed:", err);
42651
+ }
42652
+ return result;
42653
+ }
42654
+ async function listCodeLinks(projectRoot, limit = 100) {
42655
+ const entries = [];
42656
+ try {
42657
+ await getBrainDb(projectRoot);
42658
+ const brainNative = getBrainNativeDb();
42659
+ await getNexusDb();
42660
+ const nexusNative = getNexusNativeDb();
42661
+ if (!brainNative || !nexusNative) return entries;
42662
+ const rows = typedAll(
42663
+ brainNative.prepare(`
42664
+ SELECT e.from_id, e.to_id, e.weight, e.created_at,
42665
+ n.node_type, n.label
42666
+ FROM brain_page_edges e
42667
+ JOIN brain_page_nodes n ON n.id = e.from_id
42668
+ WHERE e.edge_type = 'code_reference'
42669
+ ORDER BY e.weight DESC, e.created_at DESC
42670
+ LIMIT ?
42671
+ `),
42672
+ limit
42673
+ );
42674
+ for (const row of rows) {
42675
+ const nexusNode = nexusNative.prepare("SELECT id, label, file_path, kind FROM nexus_nodes WHERE id = ? LIMIT 1").get(row.to_id);
42676
+ entries.push({
42677
+ brainNodeId: row.from_id,
42678
+ brainNodeType: row.node_type,
42679
+ brainNodeLabel: row.label,
42680
+ nexusNodeId: row.to_id,
42681
+ nexusNodeLabel: nexusNode?.label ?? row.to_id,
42682
+ filePath: nexusNode?.file_path ?? null,
42683
+ kind: nexusNode?.kind ?? "unknown",
42684
+ weight: row.weight,
42685
+ createdAt: row.created_at
42686
+ });
42687
+ }
42688
+ } catch (err) {
42689
+ console.warn("[graph-memory-bridge] listCodeLinks failed:", err);
42690
+ }
42691
+ return entries;
42692
+ }
42693
+ var FILE_PATH_PATTERN, SYMBOL_PATTERN, SYMBOL_STOP_WORDS;
42694
+ var init_graph_memory_bridge = __esm({
42695
+ "packages/core/src/memory/graph-memory-bridge.ts"() {
42696
+ "use strict";
42697
+ init_brain_schema();
42698
+ init_brain_sqlite();
42699
+ init_nexus_sqlite();
42700
+ init_typed_query();
42701
+ FILE_PATH_PATTERN = /(?:^|\s|['"`(])([a-zA-Z0-9_\-./]+\.(?:ts|tsx|js|jsx|rs|go|py|mjs|cjs))(?:$|\s|['"`)])/g;
42702
+ SYMBOL_PATTERN = /\b([a-zA-Z_][a-zA-Z0-9_]*(?:[A-Z][a-zA-Z0-9_]*)+|[a-zA-Z_]{4,}[a-zA-Z0-9_]*)\b/g;
42703
+ SYMBOL_STOP_WORDS = /* @__PURE__ */ new Set([
42704
+ "true",
42705
+ "false",
42706
+ "null",
42707
+ "undefined",
42708
+ "const",
42709
+ "async",
42710
+ "await",
42711
+ "return",
42712
+ "export",
42713
+ "import",
42714
+ "from",
42715
+ "type",
42716
+ "interface",
42717
+ "function",
42718
+ "class",
42719
+ "this",
42720
+ "super",
42721
+ "extends",
42722
+ "implements",
42723
+ "with",
42724
+ "that",
42725
+ "then",
42726
+ "when",
42727
+ "have",
42728
+ "been",
42729
+ "will",
42730
+ "should",
42731
+ "could",
42732
+ "would",
42733
+ "error",
42734
+ "result",
42735
+ "value",
42736
+ "data",
42737
+ "info",
42738
+ "note",
42739
+ "todo",
42740
+ "done",
42741
+ "fail",
42742
+ "pass"
42743
+ ]);
42744
+ }
42745
+ });
42746
+
42118
42747
  // packages/core/src/memory/brain-lifecycle.ts
42119
42748
  var brain_lifecycle_exports = {};
42120
42749
  __export(brain_lifecycle_exports, {
@@ -42148,11 +42777,11 @@ async function applyTemporalDecay(projectRoot, options) {
42148
42777
  tablesProcessed: ["brain_learnings"]
42149
42778
  };
42150
42779
  }
42151
- function extractKeywords2(text3) {
42780
+ function extractKeywords3(text3) {
42152
42781
  const words = text3.toLowerCase().replace(/[^a-z0-9\s-]/g, "").split(/\s+/);
42153
42782
  const keywords = /* @__PURE__ */ new Set();
42154
42783
  for (const w of words) {
42155
- if (w.length >= 4 && !STOP_WORDS2.has(w)) {
42784
+ if (w.length >= 4 && !STOP_WORDS3.has(w)) {
42156
42785
  keywords.add(w);
42157
42786
  }
42158
42787
  }
@@ -42190,7 +42819,7 @@ async function consolidateMemories(projectRoot, options) {
42190
42819
  }
42191
42820
  const entries = oldObservations.map((obs) => ({
42192
42821
  ...obs,
42193
- keywords: extractKeywords2(`${obs.title} ${obs.narrative ?? ""}`),
42822
+ keywords: extractKeywords3(`${obs.title} ${obs.narrative ?? ""}`),
42194
42823
  clustered: false
42195
42824
  }));
42196
42825
  const clusters = [];
@@ -42414,6 +43043,13 @@ async function runConsolidation(projectRoot) {
42414
43043
  } catch (err) {
42415
43044
  console.warn("[consolidation] Step 7 summary generation failed:", err);
42416
43045
  }
43046
+ try {
43047
+ const { autoLinkMemories: autoLinkMemories2 } = await Promise.resolve().then(() => (init_graph_memory_bridge(), graph_memory_bridge_exports));
43048
+ const bridgeResult = await autoLinkMemories2(projectRoot);
43049
+ result.graphLinksCreated = bridgeResult.linked;
43050
+ } catch (err) {
43051
+ console.warn("[consolidation] Step 8 graph memory bridge failed:", err);
43052
+ }
42417
43053
  return result;
42418
43054
  }
42419
43055
  async function deduplicateByEmbedding(projectRoot) {
@@ -42592,13 +43228,13 @@ async function strengthenCoRetrievedEdges(projectRoot) {
42592
43228
  }
42593
43229
  return strengthened;
42594
43230
  }
42595
- var STOP_WORDS2;
43231
+ var STOP_WORDS3;
42596
43232
  var init_brain_lifecycle = __esm({
42597
43233
  "packages/core/src/memory/brain-lifecycle.ts"() {
42598
43234
  "use strict";
42599
43235
  init_brain_accessor();
42600
43236
  init_typed_query();
42601
- STOP_WORDS2 = /* @__PURE__ */ new Set([
43237
+ STOP_WORDS3 = /* @__PURE__ */ new Set([
42602
43238
  "the",
42603
43239
  "a",
42604
43240
  "an",
@@ -43203,6 +43839,358 @@ var init_session_hooks = __esm({
43203
43839
  }
43204
43840
  });
43205
43841
 
43842
+ // packages/core/src/memory/quality-feedback.ts
43843
+ var quality_feedback_exports = {};
43844
+ __export(quality_feedback_exports, {
43845
+ correlateOutcomes: () => correlateOutcomes,
43846
+ getMemoryQualityReport: () => getMemoryQualityReport,
43847
+ trackMemoryUsage: () => trackMemoryUsage
43848
+ });
43849
+ async function ensureUsageLogTable(projectRoot) {
43850
+ const { getBrainDb: getBrainDb2, getBrainNativeDb: getBrainNativeDb2 } = await Promise.resolve().then(() => (init_brain_sqlite(), brain_sqlite_exports));
43851
+ await getBrainDb2(projectRoot);
43852
+ const nativeDb = getBrainNativeDb2();
43853
+ if (!nativeDb) return;
43854
+ try {
43855
+ nativeDb.prepare(
43856
+ `CREATE TABLE IF NOT EXISTS brain_usage_log (
43857
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
43858
+ entry_id TEXT NOT NULL,
43859
+ task_id TEXT,
43860
+ used INTEGER NOT NULL DEFAULT 0,
43861
+ outcome TEXT NOT NULL DEFAULT 'unknown',
43862
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
43863
+ )`
43864
+ ).run();
43865
+ nativeDb.prepare(
43866
+ `CREATE INDEX IF NOT EXISTS idx_brain_usage_log_entry_id
43867
+ ON brain_usage_log(entry_id)`
43868
+ ).run();
43869
+ nativeDb.prepare(
43870
+ `CREATE INDEX IF NOT EXISTS idx_brain_usage_log_task_id
43871
+ ON brain_usage_log(task_id)`
43872
+ ).run();
43873
+ } catch {
43874
+ }
43875
+ }
43876
+ async function ensurePruneCandidateColumn(projectRoot) {
43877
+ const { getBrainDb: getBrainDb2, getBrainNativeDb: getBrainNativeDb2 } = await Promise.resolve().then(() => (init_brain_sqlite(), brain_sqlite_exports));
43878
+ await getBrainDb2(projectRoot);
43879
+ const nativeDb = getBrainNativeDb2();
43880
+ if (!nativeDb) return;
43881
+ const tables = [
43882
+ "brain_decisions",
43883
+ "brain_patterns",
43884
+ "brain_learnings",
43885
+ "brain_observations"
43886
+ ];
43887
+ for (const tbl of tables) {
43888
+ try {
43889
+ nativeDb.prepare(`ALTER TABLE ${tbl} ADD COLUMN prune_candidate INTEGER DEFAULT 0`).run();
43890
+ } catch {
43891
+ }
43892
+ }
43893
+ }
43894
+ async function trackMemoryUsage(projectRoot, memoryId, used, taskId, outcome = "unknown") {
43895
+ if (!memoryId?.trim()) return;
43896
+ await ensureUsageLogTable(projectRoot);
43897
+ const { getBrainNativeDb: getBrainNativeDb2 } = await Promise.resolve().then(() => (init_brain_sqlite(), brain_sqlite_exports));
43898
+ const nativeDb = getBrainNativeDb2();
43899
+ if (!nativeDb) return;
43900
+ const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
43901
+ try {
43902
+ nativeDb.prepare(
43903
+ `INSERT INTO brain_usage_log (entry_id, task_id, used, outcome, created_at)
43904
+ VALUES (?, ?, ?, ?, ?)`
43905
+ ).run(memoryId, taskId ?? null, used ? 1 : 0, outcome, now);
43906
+ } catch {
43907
+ }
43908
+ }
43909
+ function tableForId(id) {
43910
+ if (id.startsWith("D-") || /^D\d/.test(id)) return "brain_decisions";
43911
+ if (id.startsWith("P-") || /^P\d/.test(id)) return "brain_patterns";
43912
+ if (id.startsWith("L-") || /^L\d/.test(id)) return "brain_learnings";
43913
+ if (id.startsWith("O-") || id.startsWith("CM-") || /^O/.test(id)) return "brain_observations";
43914
+ return null;
43915
+ }
43916
+ function applyQualityDelta(nativeDb, table, id, delta, now) {
43917
+ if (!nativeDb) return;
43918
+ try {
43919
+ nativeDb.prepare(
43920
+ `UPDATE ${table}
43921
+ SET quality_score = MAX(0.0, MIN(1.0, COALESCE(quality_score, 0.5) + ?)),
43922
+ updated_at = ?
43923
+ WHERE id = ?`
43924
+ ).run(delta, now, id);
43925
+ } catch {
43926
+ }
43927
+ }
43928
+ async function correlateOutcomes(projectRoot) {
43929
+ await ensureUsageLogTable(projectRoot);
43930
+ await ensurePruneCandidateColumn(projectRoot);
43931
+ const { getBrainDb: getBrainDb2, getBrainNativeDb: getBrainNativeDb2 } = await Promise.resolve().then(() => (init_brain_sqlite(), brain_sqlite_exports));
43932
+ await getBrainDb2(projectRoot);
43933
+ const nativeDb = getBrainNativeDb2();
43934
+ const ranAt = (/* @__PURE__ */ new Date()).toISOString();
43935
+ if (!nativeDb) {
43936
+ return { boosted: 0, penalized: 0, flaggedForPruning: 0, ranAt };
43937
+ }
43938
+ const now = ranAt.replace("T", " ").slice(0, 19);
43939
+ let boosted = 0;
43940
+ let penalized = 0;
43941
+ let usageRows = [];
43942
+ try {
43943
+ usageRows = typedAll(
43944
+ nativeDb.prepare(
43945
+ `SELECT entry_id, outcome, SUM(used) AS used_count
43946
+ FROM brain_usage_log
43947
+ WHERE outcome IN ('success', 'failure')
43948
+ GROUP BY entry_id, outcome`
43949
+ )
43950
+ );
43951
+ } catch {
43952
+ usageRows = [];
43953
+ }
43954
+ for (const row of usageRows) {
43955
+ const table = tableForId(row.entry_id);
43956
+ if (!table) continue;
43957
+ if (row.outcome === "success" && row.used_count > 0) {
43958
+ applyQualityDelta(nativeDb, table, row.entry_id, 0.05, now);
43959
+ boosted++;
43960
+ } else if (row.outcome === "failure" && row.used_count > 0) {
43961
+ applyQualityDelta(nativeDb, table, row.entry_id, -0.05, now);
43962
+ penalized++;
43963
+ }
43964
+ }
43965
+ const cutoffDate = new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString().replace("T", " ").slice(0, 19);
43966
+ let flaggedForPruning = 0;
43967
+ const pruneTargetTables = [
43968
+ { table: "brain_decisions", dateCol: "created_at" },
43969
+ { table: "brain_patterns", dateCol: "extracted_at" },
43970
+ { table: "brain_learnings", dateCol: "created_at" },
43971
+ { table: "brain_observations", dateCol: "created_at" }
43972
+ ];
43973
+ for (const { table, dateCol } of pruneTargetTables) {
43974
+ try {
43975
+ const result = nativeDb.prepare(
43976
+ `UPDATE ${table}
43977
+ SET prune_candidate = 1
43978
+ WHERE COALESCE(citation_count, 0) = 0
43979
+ AND ${dateCol} < ?`
43980
+ ).run(cutoffDate);
43981
+ flaggedForPruning += result.changes ?? 0;
43982
+ } catch {
43983
+ }
43984
+ }
43985
+ return { boosted, penalized, flaggedForPruning, ranAt };
43986
+ }
43987
+ async function getMemoryQualityReport(projectRoot) {
43988
+ await ensureUsageLogTable(projectRoot);
43989
+ const { getBrainDb: getBrainDb2, getBrainNativeDb: getBrainNativeDb2 } = await Promise.resolve().then(() => (init_brain_sqlite(), brain_sqlite_exports));
43990
+ await getBrainDb2(projectRoot);
43991
+ const nativeDb = getBrainNativeDb2();
43992
+ const emptyReport = {
43993
+ totalRetrievals: 0,
43994
+ uniqueEntriesRetrieved: 0,
43995
+ usageRate: 0,
43996
+ topRetrieved: [],
43997
+ neverRetrieved: [],
43998
+ qualityDistribution: { low: 0, medium: 0, high: 0 },
43999
+ tierDistribution: { short: 0, medium: 0, long: 0, unknown: 0 },
44000
+ noiseRatio: 0
44001
+ };
44002
+ if (!nativeDb) return emptyReport;
44003
+ let totalRetrievals = 0;
44004
+ let uniqueEntriesRetrieved = 0;
44005
+ try {
44006
+ const logCount = typedAll(
44007
+ nativeDb.prepare("SELECT COUNT(*) AS cnt FROM brain_retrieval_log")
44008
+ );
44009
+ totalRetrievals = logCount[0]?.cnt ?? 0;
44010
+ const uniqueCount = typedAll(
44011
+ nativeDb.prepare(
44012
+ `SELECT COUNT(DISTINCT value) AS cnt
44013
+ FROM brain_retrieval_log,
44014
+ json_each('["' || replace(entry_ids, ',', '","') || '"]')`
44015
+ )
44016
+ );
44017
+ uniqueEntriesRetrieved = uniqueCount[0]?.cnt ?? 0;
44018
+ } catch {
44019
+ }
44020
+ let usageRate = 0;
44021
+ try {
44022
+ const totalUsage = typedAll(
44023
+ nativeDb.prepare("SELECT COUNT(*) AS cnt FROM brain_usage_log")
44024
+ );
44025
+ const usedCount = typedAll(
44026
+ nativeDb.prepare("SELECT COUNT(*) AS cnt FROM brain_usage_log WHERE used = 1")
44027
+ );
44028
+ const total = totalUsage[0]?.cnt ?? 0;
44029
+ const used = usedCount[0]?.cnt ?? 0;
44030
+ usageRate = total > 0 ? used / total : 0;
44031
+ } catch {
44032
+ }
44033
+ const topRetrieved = [];
44034
+ try {
44035
+ const rows = typedAll(
44036
+ nativeDb.prepare(
44037
+ `SELECT id,
44038
+ 'decision' AS type,
44039
+ decision AS title,
44040
+ COALESCE(citation_count, 0) AS citation_count
44041
+ FROM brain_decisions
44042
+ UNION ALL
44043
+ SELECT id,
44044
+ 'pattern' AS type,
44045
+ pattern AS title,
44046
+ COALESCE(citation_count, 0) AS citation_count
44047
+ FROM brain_patterns
44048
+ UNION ALL
44049
+ SELECT id,
44050
+ 'learning' AS type,
44051
+ insight AS title,
44052
+ COALESCE(citation_count, 0) AS citation_count
44053
+ FROM brain_learnings
44054
+ UNION ALL
44055
+ SELECT id,
44056
+ 'observation' AS type,
44057
+ title AS title,
44058
+ COALESCE(citation_count, 0) AS citation_count
44059
+ FROM brain_observations
44060
+ ORDER BY citation_count DESC
44061
+ LIMIT 10`
44062
+ )
44063
+ );
44064
+ for (const r of rows) {
44065
+ topRetrieved.push({
44066
+ id: r.id,
44067
+ type: r.type,
44068
+ title: String(r.title ?? "").slice(0, 120),
44069
+ citationCount: r.citation_count
44070
+ });
44071
+ }
44072
+ } catch {
44073
+ }
44074
+ const neverRetrieved = [];
44075
+ try {
44076
+ const rows = typedAll(
44077
+ nativeDb.prepare(
44078
+ `SELECT id,
44079
+ 'decision' AS type,
44080
+ decision AS title,
44081
+ COALESCE(quality_score, 0.5) AS quality_score
44082
+ FROM brain_decisions
44083
+ WHERE COALESCE(citation_count, 0) = 0
44084
+ UNION ALL
44085
+ SELECT id,
44086
+ 'pattern' AS type,
44087
+ pattern AS title,
44088
+ COALESCE(quality_score, 0.5) AS quality_score
44089
+ FROM brain_patterns
44090
+ WHERE COALESCE(citation_count, 0) = 0
44091
+ UNION ALL
44092
+ SELECT id,
44093
+ 'learning' AS type,
44094
+ insight AS title,
44095
+ COALESCE(quality_score, 0.5) AS quality_score
44096
+ FROM brain_learnings
44097
+ WHERE COALESCE(citation_count, 0) = 0
44098
+ UNION ALL
44099
+ SELECT id,
44100
+ 'observation' AS type,
44101
+ title AS title,
44102
+ COALESCE(quality_score, 0.5) AS quality_score
44103
+ FROM brain_observations
44104
+ WHERE COALESCE(citation_count, 0) = 0
44105
+ ORDER BY quality_score ASC
44106
+ LIMIT 10`
44107
+ )
44108
+ );
44109
+ for (const r of rows) {
44110
+ neverRetrieved.push({
44111
+ id: r.id,
44112
+ type: r.type,
44113
+ title: String(r.title ?? "").slice(0, 120),
44114
+ qualityScore: r.quality_score
44115
+ });
44116
+ }
44117
+ } catch {
44118
+ }
44119
+ let qualityDistribution = { low: 0, medium: 0, high: 0 };
44120
+ try {
44121
+ const rows = typedAll(
44122
+ nativeDb.prepare(
44123
+ `SELECT
44124
+ SUM(CASE WHEN qs < 0.3 THEN 1 ELSE 0 END) AS low,
44125
+ SUM(CASE WHEN qs >= 0.3 AND qs <= 0.6 THEN 1 ELSE 0 END) AS medium,
44126
+ SUM(CASE WHEN qs > 0.6 THEN 1 ELSE 0 END) AS high
44127
+ FROM (
44128
+ SELECT COALESCE(quality_score, 0.5) AS qs FROM brain_decisions
44129
+ UNION ALL
44130
+ SELECT COALESCE(quality_score, 0.5) AS qs FROM brain_patterns
44131
+ UNION ALL
44132
+ SELECT COALESCE(quality_score, 0.5) AS qs FROM brain_learnings
44133
+ UNION ALL
44134
+ SELECT COALESCE(quality_score, 0.5) AS qs FROM brain_observations
44135
+ )`
44136
+ )
44137
+ );
44138
+ if (rows[0]) {
44139
+ qualityDistribution = {
44140
+ low: rows[0].low ?? 0,
44141
+ medium: rows[0].medium ?? 0,
44142
+ high: rows[0].high ?? 0
44143
+ };
44144
+ }
44145
+ } catch {
44146
+ }
44147
+ const tierDistribution = { short: 0, medium: 0, long: 0, unknown: 0 };
44148
+ try {
44149
+ const rows = typedAll(
44150
+ nativeDb.prepare(
44151
+ `SELECT memory_tier AS tier, COUNT(*) AS cnt
44152
+ FROM (
44153
+ SELECT memory_tier FROM brain_decisions
44154
+ UNION ALL
44155
+ SELECT memory_tier FROM brain_patterns
44156
+ UNION ALL
44157
+ SELECT memory_tier FROM brain_learnings
44158
+ UNION ALL
44159
+ SELECT memory_tier FROM brain_observations
44160
+ )
44161
+ GROUP BY memory_tier`
44162
+ )
44163
+ );
44164
+ for (const r of rows) {
44165
+ const tier = r.tier?.toLowerCase() ?? "unknown";
44166
+ if (tier === "short" || tier === "medium" || tier === "long") {
44167
+ tierDistribution[tier] += r.cnt;
44168
+ } else {
44169
+ tierDistribution.unknown += r.cnt;
44170
+ }
44171
+ }
44172
+ } catch {
44173
+ }
44174
+ const totalEntries = qualityDistribution.low + qualityDistribution.medium + qualityDistribution.high;
44175
+ const noiseRatio = totalEntries > 0 ? qualityDistribution.low / totalEntries : 0;
44176
+ return {
44177
+ totalRetrievals,
44178
+ uniqueEntriesRetrieved,
44179
+ usageRate,
44180
+ topRetrieved,
44181
+ neverRetrieved,
44182
+ qualityDistribution,
44183
+ tierDistribution,
44184
+ noiseRatio
44185
+ };
44186
+ }
44187
+ var init_quality_feedback = __esm({
44188
+ "packages/core/src/memory/quality-feedback.ts"() {
44189
+ "use strict";
44190
+ init_typed_query();
44191
+ }
44192
+ });
44193
+
43206
44194
  // packages/core/src/hooks/handlers/task-hooks.ts
43207
44195
  async function handleToolStart(projectRoot, payload) {
43208
44196
  const { observeBrain: observeBrain2 } = await Promise.resolve().then(() => (init_brain_retrieval(), brain_retrieval_exports));
@@ -43236,6 +44224,13 @@ async function handleToolComplete(projectRoot, payload) {
43236
44224
  } catch {
43237
44225
  }
43238
44226
  });
44227
+ setImmediate(async () => {
44228
+ try {
44229
+ const { correlateOutcomes: correlateOutcomes2 } = await Promise.resolve().then(() => (init_quality_feedback(), quality_feedback_exports));
44230
+ await correlateOutcomes2(projectRoot);
44231
+ } catch {
44232
+ }
44233
+ });
43239
44234
  await maybeRefreshMemoryBridge(projectRoot);
43240
44235
  }
43241
44236
  var init_task_hooks = __esm({
@@ -51567,6 +52562,19 @@ async function completeTask(options, cwd, accessor) {
51567
52562
  })()
51568
52563
  ).catch(() => {
51569
52564
  });
52565
+ try {
52566
+ const { hooks: hooks2 } = await Promise.resolve().then(() => (init_registry(), registry_exports));
52567
+ await hooks2.dispatch("PostToolUse", cwd ?? process.cwd(), {
52568
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
52569
+ taskId: options.taskId,
52570
+ taskTitle: task.title,
52571
+ previousStatus: before.status,
52572
+ newStatus: "done",
52573
+ unblockedCount: unblockedTasks.length
52574
+ }).catch(() => {
52575
+ });
52576
+ } catch {
52577
+ }
51570
52578
  return {
51571
52579
  task,
51572
52580
  ...autoCompleted.length > 0 && { autoCompleted },
@@ -57502,7 +58510,7 @@ function generateRecommendation2(changeType, affectedCount, cascadeDepth, taskId
57502
58510
  }
57503
58511
  }
57504
58512
  function scoreTaskMatch(change, task) {
57505
- const STOP_WORDS4 = /* @__PURE__ */ new Set([
58513
+ const STOP_WORDS5 = /* @__PURE__ */ new Set([
57506
58514
  "a",
57507
58515
  "an",
57508
58516
  "the",
@@ -57524,7 +58532,7 @@ function scoreTaskMatch(change, task) {
57524
58532
  "do",
57525
58533
  "not"
57526
58534
  ]);
57527
- const tokenise = (text3) => text3.toLowerCase().split(/\W+/).filter((t) => t.length > 2 && !STOP_WORDS4.has(t));
58535
+ const tokenise = (text3) => text3.toLowerCase().split(/\W+/).filter((t) => t.length > 2 && !STOP_WORDS5.has(t));
57528
58536
  const changeTokens = new Set(tokenise(change));
57529
58537
  if (changeTokens.size === 0) return 0;
57530
58538
  const taskText = `${task.title ?? ""} ${task.description ?? ""}`;
@@ -60490,6 +61498,7 @@ __export(memory_exports, {
60490
61498
  bulkLink: () => bulkLink,
60491
61499
  compactManifest: () => compactManifest,
60492
61500
  consolidateMemories: () => consolidateMemories,
61501
+ correlateOutcomes: () => correlateOutcomes,
60493
61502
  detectContradictions: () => detectContradictions,
60494
61503
  ensureFts5Tables: () => ensureFts5Tables,
60495
61504
  fetchBrainEntries: () => fetchBrainEntries,
@@ -60500,6 +61509,7 @@ __export(memory_exports, {
60500
61509
  getLinkedLearnings: () => getLinkedLearnings,
60501
61510
  getLinkedPatterns: () => getLinkedPatterns,
60502
61511
  getMemoryLinks: () => getMemoryLinks,
61512
+ getMemoryQualityReport: () => getMemoryQualityReport,
60503
61513
  getTaskLinks: () => getTaskLinks,
60504
61514
  hybridSearch: () => hybridSearch,
60505
61515
  learningStats: () => learningStats,
@@ -60541,6 +61551,7 @@ __export(memory_exports, {
60541
61551
  storePattern: () => storePattern,
60542
61552
  storeVerifiedCandidate: () => storeVerifiedCandidate,
60543
61553
  timelineBrain: () => timelineBrain,
61554
+ trackMemoryUsage: () => trackMemoryUsage,
60544
61555
  unlinkMemoryFromTask: () => unlinkMemoryFromTask,
60545
61556
  updateDecisionOutcome: () => updateDecisionOutcome,
60546
61557
  updateResearch: () => updateResearch,
@@ -60932,6 +61943,7 @@ async function verifyAndStoreBatch(projectRoot, candidates) {
60932
61943
  // packages/core/src/memory/index.ts
60933
61944
  init_learnings();
60934
61945
  init_patterns();
61946
+ init_quality_feedback();
60935
61947
  function getResearchPath(cwd) {
60936
61948
  return join49(getCleoDirAbsolute(cwd), "research.json");
60937
61949
  }
@@ -63848,7 +64860,7 @@ __export(nexus_exports, {
63848
64860
  criticalPath: () => criticalPath,
63849
64861
  discoverRelated: () => discoverRelated,
63850
64862
  executeTransfer: () => executeTransfer,
63851
- extractKeywords: () => extractKeywords3,
64863
+ extractKeywords: () => extractKeywords4,
63852
64864
  generateProjectHash: () => generateProjectHash,
63853
64865
  getCurrentProject: () => getCurrentProject,
63854
64866
  getNexusCacheDir: () => getNexusCacheDir,
@@ -64391,7 +65403,7 @@ async function orphanDetection() {
64391
65403
  // packages/core/src/nexus/discover.ts
64392
65404
  init_data_accessor();
64393
65405
  init_registry3();
64394
- var STOP_WORDS3 = /* @__PURE__ */ new Set([
65406
+ var STOP_WORDS4 = /* @__PURE__ */ new Set([
64395
65407
  "the",
64396
65408
  "a",
64397
65409
  "an",
@@ -64473,8 +65485,8 @@ var STOP_WORDS3 = /* @__PURE__ */ new Set([
64473
65485
  "it",
64474
65486
  "its"
64475
65487
  ]);
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));
65488
+ function extractKeywords4(text3) {
65489
+ return text3.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOP_WORDS4.has(w));
64478
65490
  }
64479
65491
  async function discoverRelated(taskQuery, method = "auto", limit = 10) {
64480
65492
  if (!validateSyntax(taskQuery)) {
@@ -64497,7 +65509,7 @@ async function discoverRelated(taskQuery, method = "auto", limit = 10) {
64497
65509
  const sourceLabels = new Set(sourceTask.labels ?? []);
64498
65510
  const sourceDesc = (sourceTask.description ?? "").toLowerCase();
64499
65511
  const sourceTitle = (sourceTask.title ?? "").toLowerCase();
64500
- const sourceWords = extractKeywords3(sourceTitle + " " + sourceDesc);
65512
+ const sourceWords = extractKeywords4(sourceTitle + " " + sourceDesc);
64501
65513
  const parsed = parseQuery(taskQuery);
64502
65514
  const registry2 = await readRegistry();
64503
65515
  if (!registry2) {
@@ -64532,7 +65544,7 @@ async function discoverRelated(taskQuery, method = "auto", limit = 10) {
64532
65544
  }
64533
65545
  if (method === "description" || method === "auto") {
64534
65546
  const taskDesc = ((task.description ?? "") + " " + (task.title ?? "")).toLowerCase();
64535
- const taskWords = extractKeywords3(taskDesc);
65547
+ const taskWords = extractKeywords4(taskDesc);
64536
65548
  const commonWords = sourceWords.filter((w) => taskWords.includes(w));
64537
65549
  if (commonWords.length > 0) {
64538
65550
  const descScore = commonWords.length / Math.max(sourceWords.length, taskWords.length, 1);
@@ -69574,6 +70586,7 @@ __export(research_exports, {
69574
70586
  bulkLink: () => bulkLink,
69575
70587
  compactManifest: () => compactManifest,
69576
70588
  consolidateMemories: () => consolidateMemories,
70589
+ correlateOutcomes: () => correlateOutcomes,
69577
70590
  detectContradictions: () => detectContradictions,
69578
70591
  ensureFts5Tables: () => ensureFts5Tables,
69579
70592
  fetchBrainEntries: () => fetchBrainEntries,
@@ -69584,6 +70597,7 @@ __export(research_exports, {
69584
70597
  getLinkedLearnings: () => getLinkedLearnings,
69585
70598
  getLinkedPatterns: () => getLinkedPatterns,
69586
70599
  getMemoryLinks: () => getMemoryLinks,
70600
+ getMemoryQualityReport: () => getMemoryQualityReport,
69587
70601
  getTaskLinks: () => getTaskLinks,
69588
70602
  hybridSearch: () => hybridSearch,
69589
70603
  learningStats: () => learningStats,
@@ -69625,6 +70639,7 @@ __export(research_exports, {
69625
70639
  storePattern: () => storePattern,
69626
70640
  storeVerifiedCandidate: () => storeVerifiedCandidate,
69627
70641
  timelineBrain: () => timelineBrain,
70642
+ trackMemoryUsage: () => trackMemoryUsage,
69628
70643
  unlinkMemoryFromTask: () => unlinkMemoryFromTask,
69629
70644
  updateDecisionOutcome: () => updateDecisionOutcome,
69630
70645
  updateResearch: () => updateResearch,