musememory 2.2.0 β†’ 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  # πŸ“ Current Shipped State & Active Invariants
2
2
 
3
- > **Version**: `v2.1.0`
4
- > **Status**: Production Release (P2P Sync, Studio, and Monorepo Mesh)
3
+ > **Version**: `v2.2.1`
4
+ > **Status**: Production Release (Hardened v2.2 with Linus-Style Code Review Bug Fixes)
5
5
 
6
6
  ---
7
7
 
@@ -34,6 +34,11 @@
34
34
  25. **Cross-Agent Knowledge Sync & P2P Gossip Protocol (R14)**: Portable sealed `SyncPacket` with SHA-256 integrity, vector clock causal ordering, bidirectional shared pool drop-directory gossip exchange, deduplication, Vibeguard secret scrubbing, and semantic contradiction tagging with mutual `conflict_ids`.
35
35
  26. **Full Web Observability Studio & Interactive Visual Dashboard (R15)**: Integrated web studio served over local HTTP (`memory studio`), featuring a 5-pillar health scorecard, overall letter grade, prioritized remediation action checklist, ADR registry with code-to-doc drift inspector, autonomous "Why" reasoner, bug friction clusters heatmap, debt hotspots, and live P2P mesh topology visualizer.
36
36
  27. **Multi-Repo & Monorepo Cross-Project Mesh (R16)**: Automatic workspace detection across pnpm, npm, bun, lerna, and multi-repo sibling groups; cross-project memory querying with origin package provenance; cross-package contract and entrypoint export auditing; explicit mesh linking (`mesh_links.json`); and shared invariant propagation across the entire mesh.
37
+ 28. **Unified Code & Memory Impact Analysis**: Pre-flight blast radius analysis evaluating AST callers, test suites, governing ADRs, negative warnings, and risk levels (`memory code-impact` / `muse_code_impact`).
38
+ 29. **PR & Change Context Generator**: Automatically turns git diffs into rich, memory-grounded GitHub PR descriptions linking touched anchors and active constraints (`memory pr-context` / `muse_pr_context`).
39
+ 30. **Interactive Code Anchor Reconciler**: Audits and repairs code anchors across memories, pruning dead references and updating structural hashes (`memory reconcile` / `muse_reconcile_anchors`).
40
+ 31. **Latency & Quality Telemetry Suite**: Benchmarks microsecond hot caches, FTS5 BM25 search, knapsack token packing, and ROI in a clean ASCII scoreboard (`memory benchmark`).
41
+ 32. **Linus-Style Architectural Hardening (v2.2.1)**: Elimination of shell command injection via `execFileSync`, concurrency-safe `PRAGMA busy_timeout = 5000` on SQLite WAL mode, O(N) stat storm elimination in `list(store)`, structured warnings on YAML dual-write failures, and tightened word-boundary/code-anchor ADR matching.
37
42
 
38
43
  ---
39
44
 
package/README.md CHANGED
@@ -24,60 +24,34 @@
24
24
 
25
25
  ---
26
26
 
27
- ## πŸš€ What's New in v2.2.0, v2.1.0 & v2.0.0 (Impact Analysis, PR Context, Reconciler & Telemetry)
28
-
29
- ### πŸ’₯ v2.2.0 Flagship Additions: Code Impact, PR Context & Cognitive Governance
30
- - **πŸ’₯ Unified Code & Memory Impact Analysis (`memory code-impact`)**:
31
- - Synthesizes AST code callers, affected test suites, linked memories, living ADRs, negative anti-patterns, and active constraints.
32
- - Computes a composite Risk Score (0–100) and Risk Level (`LOW`, `MEDIUM`, `HIGH`, `CRITICAL`) with pre-flight actionable recommendations before modifying code.
33
- - Available via CLI `memory code-impact <file> [--symbol <name>]` and MCP tool `muse_code_impact`.
34
- - **πŸ“‹ PR & Change Context Generator (`memory pr-context`)**:
35
- - Automatically analyzes git diff against base branch, linking touched code anchors, respected ADRs, active project constraints, and blast radius.
36
- - Generates rich GitHub-ready Markdown pull request descriptions with verification checklists and merge promotion proposals.
37
- - Available via CLI `memory pr-context [--base main] [--out <file>]` and MCP tool `muse_pr_context`.
38
- - **βš“ Interactive Code Anchor Reconciler (`memory reconcile`)**:
39
- - Audits code anchors across memories, pruning dead anchors, refreshing structural hashes for drifted code, and marking abandoned memories stale.
40
- - Supports `--dry-run`, `--prune`, `--mark-stale`, `--update-hashes`, and MCP tool `muse_reconcile_anchors`.
41
- - **⚑ Repeatable Speed, Quality & ROI Benchmark Suite (`memory benchmark`)**:
42
- - Measures microsecond in-process cache reads (P50 < 4Β΅s), SQLite FTS5 BM25 search, knapsack token packing, contradiction rates, and compression ratios in a clean ASCII scoreboard.
43
- - **🧠 Complete Agent Skills Roster for v2.0–v2.2**:
44
- - Added `.agents/skills/muse-health`, `muse-drift`, `muse-why`, `muse-sync`, and `muse-mesh`.
45
-
46
- ### 🌟 v2.1.0 Flagship Additions: Multi-Agent & Mesh Orchestration
47
- - **🀝 Cross-Agent Knowledge Sync & P2P Gossip Protocol (R14)**:
48
- - Cryptographically sealed, portable `SyncPacket` engine with whole-packet SHA-256 integrity verification and vector clock causal ordering.
49
- - Zero-daemon shared filesystem gossip pool (`.memory/sync_pool/`) allowing autonomous subagent teams (frontend, backend, test) to exchange insights asynchronously.
50
- - Ingestion deduplication, inline Vibeguard secret scrubbing, and semantic contradiction tagging with mutual `conflict_ids`.
51
- - CLI `memory sync` command suite & 4 MCP tools (`muse_sync_broadcast`, `muse_sync_ingest`, `muse_sync_status`, `muse_sync_pool`).
52
- - **🌐 Full Web Observability Studio (`memory studio`) (R15)**:
53
- - Embedded zero-dependency visual web dashboard (`memory studio` or `memory ui`) with interactive control plane.
54
- - Live 5-pillar health scorecard (A–F letter grade, PASS/WARN/FAIL gate status, and prioritized remediation checklist).
55
- - Interactive ADR registry and bidirectional documentation $\longleftrightarrow$ code drift inspector.
56
- - Historical "Why" reasoner query interface, recurring bug friction heatmap, and debt scanner.
57
- - Real-time P2P gossip mesh status and interactive broadcast/shared pool sync controls.
58
- - **πŸ•ΈοΈ Multi-Repo & Monorepo Cross-Project Mesh (R16)**:
59
- - Passive automatic monorepo discovery across `pnpm`, `npm`, `bun`, `lerna`, and multi-repo sibling directories.
60
- - Cross-project memory query resolver (`resolveMeshMemories`) with origin package provenance tags (`[mesh:@scope/package]`).
61
- - Shared invariant and working constraint propagation (`propagateConstraintToMesh`) broadcasting critical security rules across all packages.
62
- - Cross-package dependency contract and entrypoint export auditor (`auditMeshContracts`) validating cross-package imports and cross-repo code anchors.
63
- - CLI `memory mesh` suite & 4 MCP tools (`muse_mesh_status`, `muse_mesh_query`, `muse_mesh_audit`, `muse_mesh_link`).
64
-
65
- ### 🧠 v2.0.0 Major Cognitive Overhaul:
66
- - **πŸ₯ Unified 5-Pillar Project Health Gate (`memory health` / `muse_health`)**: Single-call architectural audit across Store Integrity, Code Anchors, Doc-Code Alignment, Negative Sentry, and Technical Debt.
67
- - **πŸ€” Autonomous Engineering Cognition & "Why" Reasoner (`muse_why`)**: Reconstructs historical code evolution through past bug workarounds, accepted trade-offs, and timeless rules before refactoring.
68
- - **πŸ›οΈ First-Class Architecture Decision Records (`memory adr` / `memory_adr_record`)**: Living, queryable ADR entities with options considered, consequences, native code anchors, and supersession lineage.
69
- - **πŸ”„ Bidirectional Documentation $\longleftrightarrow$ Code Drift Engine (`memory_drift_audit`)**: Continuously verifies alignment across 6 states (`DOCUMENTED`, `IMPLEMENTED`, `PARTIAL`, `CONFLICTING`, `STALE`, `MISSING`).
70
- - **🎯 Flagship Unified Context Orchestrator (`muse_context`)**: Single-call fused context injection with knapsack token budget packing.
71
- - **βš™οΈ Task-Focused MCP Profiles**: Dynamic role-based tool filtering (`core`, `coding`, `debugging`, `review`, `architecture`, `maintenance`).
72
- - **βš“ Native Structural Code Anchors (`src/anchors/`)**: Line-independent structural SHA-256 hashing insensitive to spacing/comments.
73
- - **πŸͺœ Scoped Promotion & Extended Archival Lifecycle (`src/promotion/`)**: 3-tier promotion ladder (`local` $\rightarrow$ `project` $\rightarrow$ `global`), 5Γ— repeated success policy, universal generalization, and `active` $\rightarrow$ `cold` $\rightarrow$ `dormant` $\rightarrow$ `archived` lifecycle with query rehydration.
74
- - **⚑ Hot Query Cache & SQLite WAL Optimization**: Microsecond memory lookups and high-throughput concurrent I/O.
27
+ ## πŸš€ What's New
28
+
29
+ ### v2.2.0
30
+ - **Unified Code & Memory Impact Analysis**: Pre-flight blast radius analysis evaluating AST callers, test suites, governing ADRs, negative warnings, and risk levels (`memory code-impact` / `muse_code_impact`).
31
+ - **PR & Change Context Generator**: Automatically turns git diffs into rich, memory-grounded GitHub PR descriptions linking touched anchors and active constraints (`memory pr-context` / `muse_pr_context`).
32
+ - **Interactive Code Anchor Reconciler**: Audits and repairs code anchors across memories, pruning dead references and updating structural hashes (`memory reconcile` / `muse_reconcile_anchors`).
33
+ - **Latency & Quality Telemetry Suite**: Benchmarks microsecond hot caches, FTS5 BM25 search, knapsack token packing, and ROI in a clean ASCII scoreboard (`memory benchmark`).
34
+ - **Expanded Agent Skills**: Shipped turnkey skills for `muse-health`, `muse-drift`, `muse-why`, `muse-sync`, and `muse-mesh`.
35
+
36
+ ### v2.1.0
37
+ - **Cross-Agent Knowledge Sync & P2P Gossip Protocol**: Sealed portable `SyncPacket` engine with SHA-256 integrity and zero-daemon shared filesystem gossip pool (`memory sync` / `muse_sync_*`).
38
+ - **Web Observability Studio**: Embedded visual dashboard (`memory studio` / `memory ui`) with interactive 5-pillar health, ADR registry, code drift inspector, and sync controls.
39
+ - **Monorepo & Multi-Repo Mesh**: Automatic workspace discovery (pnpm, npm, bun, lerna) with cross-project memory queries and shared constraint propagation (`memory mesh` / `muse_mesh_*`).
40
+
41
+ ### v2.0.0
42
+ - **5-Pillar Project Health Gate**: Architectural health gate evaluating store integrity, code anchors, doc-code alignment, negative sentry, and technical debt (`memory health` / `muse_health`).
43
+ - **Cognitive "Why" Reasoner**: Reconstructs historical code evolution, bug workarounds, and trade-offs before refactoring (`muse_why`).
44
+ - **Living Architecture Decision Records (ADRs)**: Queryable ADRs with options, consequences, native code anchors, and supersession lineage (`memory adr` / `memory_adr_record`).
45
+ - **Doc ⟷ Code Drift Engine**: Continuously audits documentation against AST code implementations across 6 drift states (`memory drift` / `memory_drift_audit`).
46
+ - **Unified Context Orchestrator & MCP Profiles**: Single-call fused context injection with knapsack token packing (`muse_context`) and task-specific tool profiles.
47
+ - **Native Structural Code Anchors**: Line-independent structural SHA-256 hashing for files, symbols, and routes.
48
+ - **Scoped Promotion & Archival Lifecycle**: 3-tier promotion ladder (`local` β†’ `project` β†’ `global`) and cold/dormant/archived storage tiers.
75
49
 
76
50
  ---
77
51
 
78
52
  ## πŸ“‘ Table of Contents
79
53
 
80
- - [πŸš€ What's New in v2.1.0 & v2.0.0](#-whats-new-in-v210--v200-autonomous-cognitive-engine-p2p-sync--monorepo-mesh)
54
+ - [πŸš€ What's New](#-whats-new)
81
55
  - [πŸ’‘ What is Muse Memory? (TL;DR)](#-what-is-muse-memory-tldr)
82
56
  - [✨ Key Feature Highlights](#-key-feature-highlights)
83
57
  - [⚑ Quick Start & Installation](#-quick-start--installation)
package/dist/index.js CHANGED
@@ -5720,6 +5720,8 @@ function openDatabase(dbPath) {
5720
5720
  rawDb = new Database(dbPath, { create: true });
5721
5721
  rawDb.run("PRAGMA journal_mode = WAL;");
5722
5722
  rawDb.run("PRAGMA synchronous = NORMAL;");
5723
+ rawDb.run("PRAGMA busy_timeout = 5000;");
5724
+ rawDb.run("PRAGMA foreign_keys = ON;");
5723
5725
  rawDb.run("PRAGMA cache_size = -64000;");
5724
5726
  rawDb.run("PRAGMA mmap_size = 268435456;");
5725
5727
  rawDb.run("PRAGMA temp_store = MEMORY;");
@@ -5758,6 +5760,8 @@ function openDatabase(dbPath) {
5758
5760
  rawDb = new DatabaseSync(dbPath);
5759
5761
  rawDb.exec("PRAGMA journal_mode = WAL;");
5760
5762
  rawDb.exec("PRAGMA synchronous = NORMAL;");
5763
+ rawDb.exec("PRAGMA busy_timeout = 5000;");
5764
+ rawDb.exec("PRAGMA foreign_keys = ON;");
5761
5765
  rawDb.exec("PRAGMA cache_size = -64000;");
5762
5766
  rawDb.exec("PRAGMA mmap_size = 268435456;");
5763
5767
  rawDb.exec("PRAGMA temp_store = MEMORY;");
@@ -6709,19 +6713,52 @@ function list(store, filters) {
6709
6713
  if (cached)
6710
6714
  return cached;
6711
6715
  }
6712
- const ids = listIds(store);
6713
- const entries = ids.map((id) => get(store, id)).filter((e) => e !== null);
6714
- let filtered = entries;
6715
- if (filters?.project)
6716
- filtered = filtered.filter((e) => e.project === filters.project);
6717
- if (filters?.type)
6718
- filtered = filtered.filter((e) => e.type === filters.type);
6719
- if (filters?.status)
6720
- filtered = filtered.filter((e) => e.status === filters.status);
6716
+ let entries = [];
6717
+ if (store.db) {
6718
+ entries = listMemories(store.db, filters);
6719
+ if (existsSync10(store.dir)) {
6720
+ const dbIds = new Set(entries.map((e) => e.id));
6721
+ const diskFiles = readdirSync2(store.dir);
6722
+ for (const f of diskFiles) {
6723
+ if (f.endsWith(".yaml")) {
6724
+ const diskId = f.slice(0, -5);
6725
+ if (!dbIds.has(diskId)) {
6726
+ const externalEntry = get(store, diskId);
6727
+ if (externalEntry) {
6728
+ try {
6729
+ insertOrReplaceMemory(store.db, externalEntry);
6730
+ } catch {}
6731
+ let matches = true;
6732
+ if (filters?.project && externalEntry.project !== filters.project)
6733
+ matches = false;
6734
+ if (filters?.type && externalEntry.type !== filters.type)
6735
+ matches = false;
6736
+ if (filters?.status && externalEntry.status !== filters.status)
6737
+ matches = false;
6738
+ if (matches)
6739
+ entries.push(externalEntry);
6740
+ }
6741
+ }
6742
+ }
6743
+ }
6744
+ }
6745
+ } else {
6746
+ const ids = listIds(store);
6747
+ entries = ids.map((id) => get(store, id)).filter((e) => e !== null);
6748
+ if (filters?.project)
6749
+ entries = entries.filter((e) => e.project === filters.project);
6750
+ if (filters?.type)
6751
+ entries = entries.filter((e) => e.type === filters.type);
6752
+ if (filters?.status)
6753
+ entries = entries.filter((e) => e.status === filters.status);
6754
+ }
6721
6755
  if (store.cache) {
6722
- store.cache.setQuery(cacheKey, filtered, undefined, dirMtime);
6756
+ store.cache.setQuery(cacheKey, entries, undefined, dirMtime);
6757
+ for (const e of entries) {
6758
+ store.cache.setEntry(e);
6759
+ }
6723
6760
  }
6724
- return filtered;
6761
+ return entries;
6725
6762
  }
6726
6763
  function extractEntryText(entry) {
6727
6764
  const parts = [
@@ -6756,7 +6793,9 @@ function save(store, entry, options = {}) {
6756
6793
  try {
6757
6794
  mtimeMs = statSync(file).mtimeMs;
6758
6795
  } catch {}
6759
- } catch {}
6796
+ } catch (err) {
6797
+ console.warn(`[WARN] Dual-write YAML failed for memory '${entry.id}':`, err instanceof Error ? err.message : String(err));
6798
+ }
6760
6799
  if (store.cache) {
6761
6800
  store.cache.setEntry(entry, undefined, mtimeMs);
6762
6801
  }
@@ -20574,77 +20613,97 @@ async function reconcileCodeAnchors(store, options = {}) {
20574
20613
  let repairedEntriesCount = 0;
20575
20614
  let staleEntriesCount = 0;
20576
20615
  const details = [];
20577
- for (const entry of entries) {
20578
- if (!entry.anchors || entry.anchors.length === 0)
20579
- continue;
20580
- totalAuditedEntries++;
20581
- const survivingAnchors = [];
20582
- const entryOrphaned = [];
20583
- const entryDrifted = [];
20584
- const actionsTaken = [];
20585
- let entryModified = false;
20586
- for (const anchor of entry.anchors) {
20587
- totalAnchors++;
20588
- const verification = verifyCodeAnchor(root, anchor);
20589
- if (verification.status === "valid") {
20590
- validCount++;
20591
- survivingAnchors.push(anchor);
20592
- } else if (verification.status === "drifted") {
20593
- driftedCount++;
20594
- entryDrifted.push(`${anchor.file_path}${anchor.symbol_name ? `::${anchor.symbol_name}` : ""}`);
20595
- if (updateHashes && verification.current_hash) {
20596
- if (!dryRun) {
20597
- anchor.structural_hash = verification.current_hash;
20598
- anchor.verified_at = new Date().toISOString();
20616
+ const inTx = !dryRun && !!store.db;
20617
+ if (inTx) {
20618
+ try {
20619
+ store.db.exec("BEGIN TRANSACTION;");
20620
+ } catch {}
20621
+ }
20622
+ try {
20623
+ for (const entry of entries) {
20624
+ if (!entry.anchors || entry.anchors.length === 0)
20625
+ continue;
20626
+ totalAuditedEntries++;
20627
+ const survivingAnchors = [];
20628
+ const entryOrphaned = [];
20629
+ const entryDrifted = [];
20630
+ const actionsTaken = [];
20631
+ let entryModified = false;
20632
+ for (const anchor of entry.anchors) {
20633
+ totalAnchors++;
20634
+ const verification = verifyCodeAnchor(root, anchor);
20635
+ if (verification.status === "valid") {
20636
+ validCount++;
20637
+ survivingAnchors.push(anchor);
20638
+ } else if (verification.status === "drifted") {
20639
+ driftedCount++;
20640
+ entryDrifted.push(`${anchor.file_path}${anchor.symbol_name ? `::${anchor.symbol_name}` : ""}`);
20641
+ if (updateHashes && verification.current_hash) {
20642
+ if (!dryRun) {
20643
+ anchor.structural_hash = verification.current_hash;
20644
+ anchor.verified_at = new Date().toISOString();
20645
+ }
20646
+ entryModified = true;
20647
+ actionsTaken.push(`Updated structural hash for drifted anchor '${anchor.id}'`);
20599
20648
  }
20600
- entryModified = true;
20601
- actionsTaken.push(`Updated structural hash for drifted anchor '${anchor.id}'`);
20602
- }
20603
- survivingAnchors.push(anchor);
20604
- } else {
20605
- orphanedCount++;
20606
- entryOrphaned.push(`${anchor.file_path}${anchor.symbol_name ? `::${anchor.symbol_name}` : ""}`);
20607
- if (prune) {
20608
- entryModified = true;
20609
- prunedAnchorsCount++;
20610
- actionsTaken.push(`Pruned orphaned anchor '${anchor.id}' (${verification.drift_details || "code deleted"})`);
20611
- } else {
20612
20649
  survivingAnchors.push(anchor);
20650
+ } else {
20651
+ orphanedCount++;
20652
+ entryOrphaned.push(`${anchor.file_path}${anchor.symbol_name ? `::${anchor.symbol_name}` : ""}`);
20653
+ if (prune) {
20654
+ entryModified = true;
20655
+ prunedAnchorsCount++;
20656
+ actionsTaken.push(`Pruned orphaned anchor '${anchor.id}' (${verification.drift_details || "code deleted"})`);
20657
+ } else {
20658
+ survivingAnchors.push(anchor);
20659
+ }
20613
20660
  }
20614
20661
  }
20615
- }
20616
- if (prune && entryModified) {
20617
- if ((!survivingAnchors || survivingAnchors.length === 0) && markStale2 && entry.status === "confirmed") {
20618
- staleEntriesCount++;
20619
- actionsTaken.push(`Marked memory '${entry.id}' as stale (all code anchors orphaned)`);
20620
- }
20621
- if (!dryRun) {
20622
- entry.anchors = survivingAnchors.length > 0 ? survivingAnchors : undefined;
20623
- if ((!entry.anchors || entry.anchors.length === 0) && markStale2 && entry.status === "confirmed") {
20624
- entry.status = "stale";
20625
- entry.stale_reason = "All associated code anchors were orphaned and pruned";
20662
+ if (prune && entryModified) {
20663
+ if ((!survivingAnchors || survivingAnchors.length === 0) && markStale2 && entry.status === "confirmed") {
20664
+ staleEntriesCount++;
20665
+ actionsTaken.push(`Marked memory '${entry.id}' as stale (all code anchors orphaned)`);
20626
20666
  }
20627
- entry.updated_at = new Date().toISOString();
20628
- save(store, entry);
20667
+ if (!dryRun) {
20668
+ entry.anchors = survivingAnchors.length > 0 ? survivingAnchors : undefined;
20669
+ if ((!entry.anchors || entry.anchors.length === 0) && markStale2 && entry.status === "confirmed") {
20670
+ entry.status = "stale";
20671
+ entry.stale_reason = "All associated code anchors were orphaned and pruned";
20672
+ }
20673
+ entry.updated_at = new Date().toISOString();
20674
+ save(store, entry);
20675
+ }
20676
+ repairedEntriesCount++;
20677
+ } else if (updateHashes && entryModified) {
20678
+ if (!dryRun) {
20679
+ entry.anchors = survivingAnchors.length > 0 ? survivingAnchors : undefined;
20680
+ entry.updated_at = new Date().toISOString();
20681
+ save(store, entry);
20682
+ }
20683
+ repairedEntriesCount++;
20629
20684
  }
20630
- repairedEntriesCount++;
20631
- } else if (updateHashes && entryModified) {
20632
- if (!dryRun) {
20633
- entry.anchors = survivingAnchors.length > 0 ? survivingAnchors : undefined;
20634
- entry.updated_at = new Date().toISOString();
20635
- save(store, entry);
20685
+ if (entryOrphaned.length > 0 || entryDrifted.length > 0 || actionsTaken.length > 0) {
20686
+ details.push({
20687
+ entryId: entry.id,
20688
+ title: entry.title,
20689
+ actionsTaken,
20690
+ orphanedAnchors: entryOrphaned,
20691
+ driftedAnchors: entryDrifted
20692
+ });
20636
20693
  }
20637
- repairedEntriesCount++;
20638
20694
  }
20639
- if (entryOrphaned.length > 0 || entryDrifted.length > 0 || actionsTaken.length > 0) {
20640
- details.push({
20641
- entryId: entry.id,
20642
- title: entry.title,
20643
- actionsTaken,
20644
- orphanedAnchors: entryOrphaned,
20645
- driftedAnchors: entryDrifted
20646
- });
20695
+ if (inTx) {
20696
+ try {
20697
+ store.db.exec("COMMIT;");
20698
+ } catch {}
20647
20699
  }
20700
+ } catch (err) {
20701
+ if (inTx) {
20702
+ try {
20703
+ store.db.exec("ROLLBACK;");
20704
+ } catch {}
20705
+ }
20706
+ throw err;
20648
20707
  }
20649
20708
  if (!dryRun && store.memoryDir && (prunedAnchorsCount > 0 || staleEntriesCount > 0)) {
20650
20709
  recordAuditEvent(store.memoryDir, {
@@ -33838,11 +33897,19 @@ async function analyzeMemoryCodeImpact(store, options) {
33838
33897
  }));
33839
33898
  const allAdrs = listAdrs(store);
33840
33899
  const matchedAdrs = [];
33900
+ const normTarget = targetFile.toLowerCase().replace(/^[./\\]+/, "");
33841
33901
  const fileBase = basename2(targetFile).toLowerCase();
33902
+ const symbolRegex = targetSymbol && targetSymbol.length > 2 ? new RegExp(`\\b${targetSymbol.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&")}\\b`, "i") : null;
33903
+ const pathRegex = fileBase.length > 3 && fileBase !== "index.ts" && fileBase !== "index.js" ? new RegExp(`\\b${fileBase.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&")}\\b`, "i") : null;
33842
33904
  for (const adr of allAdrs) {
33843
- const text = `${adr.title} ${adr.content}`.toLowerCase();
33844
- const hasAnchor = adr.anchors?.some((a) => a.file_path.toLowerCase().includes(fileBase) || targetSymbol && a.symbol_name?.toLowerCase() === targetSymbol.toLowerCase());
33845
- if (hasAnchor || text.includes(fileBase) || targetSymbol && text.includes(targetSymbol.toLowerCase())) {
33905
+ const text = `${adr.title} ${adr.content}`;
33906
+ const hasAnchor = adr.anchors?.some((a) => {
33907
+ const aPath = a.file_path.toLowerCase().replace(/^[./\\]+/, "");
33908
+ return aPath === normTarget || aPath.endsWith("/" + fileBase) || targetSymbol && a.symbol_name?.toLowerCase() === targetSymbol.toLowerCase();
33909
+ });
33910
+ const matchesSymbol = symbolRegex ? symbolRegex.test(text) : false;
33911
+ const matchesPath = pathRegex ? pathRegex.test(text) : false;
33912
+ if (hasAnchor || matchesSymbol || matchesPath) {
33846
33913
  matchedAdrs.push({
33847
33914
  id: adr.id,
33848
33915
  title: adr.title,
@@ -34295,28 +34362,38 @@ var init_harvester2 = __esm(() => {
34295
34362
  });
34296
34363
 
34297
34364
  // src/compaction/pr-context.ts
34298
- import { execSync as execSync3 } from "node:child_process";
34365
+ import { execFileSync } from "node:child_process";
34299
34366
  import { existsSync as existsSync46, readFileSync as readFileSync38 } from "node:fs";
34300
34367
  import { join as join43, basename as basename3 } from "node:path";
34301
34368
  async function generatePrContext(store, options = {}) {
34302
34369
  const root = options.workspaceRoot || store.dir || process.cwd();
34303
- const base = options.baseBranch || "main";
34370
+ const rawBase = options.baseBranch || "main";
34371
+ const safeBase = /^[a-zA-Z0-9._\/-]+$/.test(rawBase) ? rawBase : "main";
34304
34372
  let changedFiles = [];
34305
34373
  try {
34306
- const diffCmd = `git diff --name-only ${base}...HEAD`;
34307
- const stdout = execSync3(diffCmd, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
34374
+ const stdout = execFileSync("git", ["diff", "--name-only", `${safeBase}...HEAD`], {
34375
+ cwd: root,
34376
+ encoding: "utf8",
34377
+ stdio: ["ignore", "pipe", "ignore"]
34378
+ });
34308
34379
  changedFiles = stdout.split(`
34309
34380
  `).map((f) => f.trim()).filter(Boolean);
34310
34381
  } catch {
34311
34382
  try {
34312
- const diffCmd = `git diff --name-only HEAD~1`;
34313
- const stdout = execSync3(diffCmd, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
34383
+ const stdout = execFileSync("git", ["diff", "--name-only", "HEAD~1"], {
34384
+ cwd: root,
34385
+ encoding: "utf8",
34386
+ stdio: ["ignore", "pipe", "ignore"]
34387
+ });
34314
34388
  changedFiles = stdout.split(`
34315
34389
  `).map((f) => f.trim()).filter(Boolean);
34316
34390
  } catch {
34317
34391
  try {
34318
- const diffCmd = `git status --porcelain`;
34319
- const stdout = execSync3(diffCmd, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
34392
+ const stdout = execFileSync("git", ["status", "--porcelain"], {
34393
+ cwd: root,
34394
+ encoding: "utf8",
34395
+ stdio: ["ignore", "pipe", "ignore"]
34396
+ });
34320
34397
  changedFiles = stdout.split(`
34321
34398
  `).map((l) => l.slice(3).trim()).filter(Boolean);
34322
34399
  } catch {
@@ -34472,7 +34549,7 @@ async function generatePrContext(store, options = {}) {
34472
34549
  return {
34473
34550
  title: prTitle,
34474
34551
  bodyMarkdown: md,
34475
- baseBranch: base,
34552
+ baseBranch: safeBase,
34476
34553
  changedFiles,
34477
34554
  anchorsTouched: totalAnchorsTouched,
34478
34555
  adrsInvolved,
@@ -35349,7 +35426,7 @@ __export(exports_drift, {
35349
35426
  scanCodeDrift: () => scanCodeDrift,
35350
35427
  scanCodeDriftFromDiff: () => scanCodeDriftFromDiff
35351
35428
  });
35352
- import { execSync as execSync4 } from "node:child_process";
35429
+ import { execSync as execSync3 } from "node:child_process";
35353
35430
  import { existsSync as existsSync52 } from "node:fs";
35354
35431
  import { join as join49, isAbsolute as isAbsolute2 } from "node:path";
35355
35432
  function scanCodeDriftFromDiff(entries, changedFiles, workspaceRoot) {
@@ -35390,7 +35467,7 @@ function scanCodeDriftFromDiff(entries, changedFiles, workspaceRoot) {
35390
35467
  function getGitChangedFiles(workspaceRoot) {
35391
35468
  const root = workspaceRoot || process.cwd();
35392
35469
  try {
35393
- const stdout = execSync4("git status --porcelain", {
35470
+ const stdout = execSync3("git status --porcelain", {
35394
35471
  cwd: root,
35395
35472
  encoding: "utf8",
35396
35473
  stdio: ["ignore", "pipe", "ignore"]
@@ -35407,7 +35484,7 @@ function getGitChangedFiles(workspaceRoot) {
35407
35484
  files.push(filePath);
35408
35485
  }
35409
35486
  try {
35410
- const diffOut = execSync4("git diff --name-only HEAD", {
35487
+ const diffOut = execSync3("git diff --name-only HEAD", {
35411
35488
  cwd: root,
35412
35489
  encoding: "utf8",
35413
35490
  stdio: ["ignore", "pipe", "ignore"]
@@ -40249,8 +40326,9 @@ async function runMemoryBenchmark(store, options = {}) {
40249
40326
  const iterations = options.iterations || 30;
40250
40327
  const testQuery = options.query || "authentication architecture security";
40251
40328
  const tokenBudget = options.tokenBudget || 1500;
40252
- const allEntries = list(store);
40253
40329
  const coldStartT0 = performance.now();
40330
+ const coldStore = openStore(store.memoryDir || store.dir);
40331
+ const allEntries = list(coldStore);
40254
40332
  const entriesCount = allEntries.length;
40255
40333
  const cold_startup_ms = Math.round((performance.now() - coldStartT0) * 100) / 100;
40256
40334
  const hotSamplesUs = [];
@@ -40473,10 +40551,11 @@ async function handleReconcileCommand(parsed) {
40473
40551
  return 1;
40474
40552
  }
40475
40553
  const { root, store } = ctx;
40476
- const prune = parsed.flags["prune"] === "true" || parsed.flags["p"] === "true";
40477
- const markStale2 = parsed.flags["mark-stale"] === "true" || parsed.flags["stale"] === "true";
40478
- const updateHashes = parsed.flags["update-hashes"] === "true";
40479
- const dryRun = parsed.flags["dry-run"] === "true" || !prune && !markStale2 && !updateHashes;
40554
+ const isTrue = (val) => val === true || val === "true" || val === "";
40555
+ const prune = isTrue(parsed.flags["prune"]) || isTrue(parsed.flags["p"]);
40556
+ const markStale2 = isTrue(parsed.flags["mark-stale"]) || isTrue(parsed.flags["stale"]);
40557
+ const updateHashes = isTrue(parsed.flags["update-hashes"]);
40558
+ const dryRun = isTrue(parsed.flags["dry-run"]) || !prune && !markStale2 && !updateHashes;
40480
40559
  const { reconcileCodeAnchors: reconcileCodeAnchors2, formatReconcileReport: formatReconcileReport2 } = await Promise.resolve().then(() => (init_health(), exports_health));
40481
40560
  const report = await reconcileCodeAnchors2(store, {
40482
40561
  prune,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musememory",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "Autonomous, self-organizing cognitive memory system for AI agents and agency networks",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",