@cortexkit/pi-magic-context 0.30.7 → 0.31.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.
@@ -8017,7 +8017,7 @@ var require_src2 = __commonJS((exports, module) => {
8017
8017
  });
8018
8018
 
8019
8019
  // ../plugin/src/features/magic-context/storage-db.ts
8020
- import { chmodSync, copyFileSync, cpSync, existsSync as existsSync3, mkdirSync as mkdirSync3, mkdtempSync } from "node:fs";
8020
+ import { chmodSync, copyFileSync, cpSync, existsSync as existsSync4, mkdirSync as mkdirSync3, mkdtempSync } from "node:fs";
8021
8021
  init_data_path();
8022
8022
  import { dirname as dirname2, join as join3 } from "node:path";
8023
8023
 
@@ -8311,13 +8311,23 @@ var CATEGORY_DEFAULT_TTL = {
8311
8311
  };
8312
8312
 
8313
8313
  // ../plugin/src/features/magic-context/memory/project-identity.ts
8314
+ init_logger();
8314
8315
  import { execFileSync } from "node:child_process";
8315
8316
  import { createHash } from "node:crypto";
8316
- import { statSync } from "node:fs";
8317
+ import { existsSync as existsSync2, realpathSync, statSync } from "node:fs";
8317
8318
  import path3 from "node:path";
8318
8319
  var GIT_TIMEOUT_MS = 5000;
8320
+ var TRANSIENT_FAILURE_COOLDOWN_MS = 5 * 60 * 1000;
8319
8321
  var identityCache = new Map;
8322
+ var lastKnownGitIdentityCache = new Map;
8320
8323
  var directoryFallbackCache = new Map;
8324
+ var transientFailureCooldown = new Map;
8325
+ var dubiousOwnershipFallbackDirectories = new Set;
8326
+ var dubiousOwnershipLoggedDirectories = new Set;
8327
+ var dubiousOwnershipWarnedDirectories = new Set;
8328
+ var transientGitIdentityReuseLoggedDirectories = new Set;
8329
+ var execFileSyncForIdentity = execFileSync;
8330
+ var nowMs = () => Date.now();
8321
8331
 
8322
8332
  class ProjectIdentityError extends Error {
8323
8333
  errorClass;
@@ -8407,6 +8417,9 @@ function classifyGitError(error, rawDirectory) {
8407
8417
  return new ProjectIdentityError("permission_denied", rawDirectory, "Permission denied while spawning git", asError(error));
8408
8418
  }
8409
8419
  const stderr = getErrorStderr(error).toLowerCase();
8420
+ if (stderr.includes("detected dubious ownership")) {
8421
+ return new ProjectIdentityError("dubious_ownership", rawDirectory, "git refused to read the repository because it detected dubious ownership", asError(error));
8422
+ }
8410
8423
  if (stderr.includes("not a git repository") || stderr.includes("does not have any commits yet") || stderr.includes("ambiguous argument 'head'") || stderr.includes("unknown revision or path")) {
8411
8424
  return new ProjectIdentityError("not_git_repo", rawDirectory, "Directory has no git root commit; caller may use directory fallback", asError(error));
8412
8425
  }
@@ -8419,9 +8432,12 @@ function resolveProjectIdentityStrict(directory) {
8419
8432
  return cached;
8420
8433
  }
8421
8434
  assertDirectoryUsable(canonical, directory);
8435
+ if (!hasGitDir(canonical)) {
8436
+ throw new ProjectIdentityError("not_git_repo", directory, "Directory has no git metadata; caller may use directory fallback");
8437
+ }
8422
8438
  let output;
8423
8439
  try {
8424
- output = execFileSync("git", ["rev-list", "--max-parents=0", "HEAD"], {
8440
+ output = execFileSyncForIdentity("git", ["rev-list", "--max-parents=0", "HEAD"], {
8425
8441
  cwd: canonical,
8426
8442
  encoding: "utf8",
8427
8443
  env: { ...process.env, LC_ALL: "C", LANG: "C" },
@@ -8439,10 +8455,73 @@ function resolveProjectIdentityStrict(directory) {
8439
8455
  }
8440
8456
  const identity = `git:${rootCommit}`;
8441
8457
  identityCache.set(canonical, identity);
8458
+ lastKnownGitIdentityCache.set(canonical, identity);
8459
+ transientFailureCooldown.delete(canonical);
8460
+ dubiousOwnershipFallbackDirectories.delete(canonical);
8461
+ transientGitIdentityReuseLoggedDirectories.delete(canonical);
8442
8462
  return identity;
8443
8463
  }
8444
8464
  function shouldUseDirectoryFallback(error) {
8445
- return error.errorClass === "not_git_repo" || error.errorClass === "unknown" && error.message.startsWith("Unable to access project directory:");
8465
+ return error.errorClass !== "permission_denied";
8466
+ }
8467
+ function getActiveCooldown(canonical) {
8468
+ const until = transientFailureCooldown.get(canonical);
8469
+ if (until === undefined)
8470
+ return;
8471
+ if (nowMs() < until)
8472
+ return until;
8473
+ transientFailureCooldown.delete(canonical);
8474
+ return;
8475
+ }
8476
+ function lastKnownGitIdentity(canonical) {
8477
+ return lastKnownGitIdentityCache.get(canonical) ?? identityCache.get(canonical);
8478
+ }
8479
+ function nearestLastKnownGitIdentity(canonical) {
8480
+ const visited = new Set;
8481
+ const walk = (start) => {
8482
+ let current = start;
8483
+ while (!visited.has(current)) {
8484
+ visited.add(current);
8485
+ const cached = lastKnownGitIdentity(current);
8486
+ if (cached !== undefined)
8487
+ return { identity: cached, source: current };
8488
+ const parent = path3.dirname(current);
8489
+ if (parent === current)
8490
+ break;
8491
+ current = parent;
8492
+ }
8493
+ return;
8494
+ };
8495
+ const exactOrAncestor = walk(canonical);
8496
+ if (exactOrAncestor)
8497
+ return exactOrAncestor;
8498
+ try {
8499
+ const realCanonical = realpathSync.native(canonical);
8500
+ if (realCanonical !== canonical)
8501
+ return walk(realCanonical);
8502
+ } catch {}
8503
+ return;
8504
+ }
8505
+ function reuseLastKnownGitIdentity(canonical) {
8506
+ const cached = nearestLastKnownGitIdentity(canonical);
8507
+ if (cached === undefined)
8508
+ return;
8509
+ if (!transientGitIdentityReuseLoggedDirectories.has(canonical)) {
8510
+ transientGitIdentityReuseLoggedDirectories.add(canonical);
8511
+ const sourceNote = cached.source === canonical ? "" : ` from ancestor ${cached.source}`;
8512
+ log(`[magic-context] git identity resolution is temporarily unavailable for ${canonical}; reusing the last successful project identity${sourceNote} to avoid splitting project-scoped memory`);
8513
+ }
8514
+ return cached.identity;
8515
+ }
8516
+ function formatDubiousOwnershipWarning(canonical) {
8517
+ return `Magic Context: git refused to read ${canonical} (dubious ownership — the repo is owned by a different user). Using a directory-based project identity for now, which keeps memory separate from this repo's normal identity. Fix: git config --global --add safe.directory ${canonical}`;
8518
+ }
8519
+ function recordDubiousOwnershipFallback(canonical) {
8520
+ dubiousOwnershipFallbackDirectories.add(canonical);
8521
+ if (dubiousOwnershipLoggedDirectories.has(canonical))
8522
+ return;
8523
+ dubiousOwnershipLoggedDirectories.add(canonical);
8524
+ log(`[magic-context] ${formatDubiousOwnershipWarning(canonical)}`);
8446
8525
  }
8447
8526
  function resolveProjectIdentity(directory) {
8448
8527
  const canonical = path3.resolve(directory);
@@ -8453,27 +8532,74 @@ function resolveProjectIdentity(directory) {
8453
8532
  }
8454
8533
  directoryFallbackCache.delete(canonical);
8455
8534
  }
8535
+ if (getActiveCooldown(canonical) !== undefined) {
8536
+ if (hasGitDir(canonical)) {
8537
+ const cachedGitIdentity = reuseLastKnownGitIdentity(canonical);
8538
+ if (cachedGitIdentity !== undefined) {
8539
+ return cachedGitIdentity;
8540
+ }
8541
+ }
8542
+ return directoryFallback(canonical);
8543
+ }
8456
8544
  try {
8457
8545
  return resolveProjectIdentityStrict(directory);
8458
8546
  } catch (error) {
8459
8547
  if (error instanceof ProjectIdentityError && shouldUseDirectoryFallback(error)) {
8460
8548
  const fallback = directoryFallback(canonical);
8461
- if (!hasGitDir(canonical)) {
8549
+ const hasGitMetadata = hasGitDir(canonical);
8550
+ if (!hasGitMetadata) {
8462
8551
  directoryFallbackCache.set(canonical, fallback);
8552
+ transientFailureCooldown.delete(canonical);
8553
+ } else {
8554
+ transientFailureCooldown.set(canonical, nowMs() + TRANSIENT_FAILURE_COOLDOWN_MS);
8555
+ const cachedGitIdentity = reuseLastKnownGitIdentity(canonical);
8556
+ if (cachedGitIdentity !== undefined) {
8557
+ return cachedGitIdentity;
8558
+ }
8559
+ }
8560
+ if (error.errorClass === "dubious_ownership") {
8561
+ recordDubiousOwnershipFallback(canonical);
8463
8562
  }
8464
8563
  return fallback;
8465
8564
  }
8466
8565
  throw error;
8467
8566
  }
8468
8567
  }
8469
- function hasGitDir(canonical) {
8568
+ function resolveProjectIdentityOrFallback(directory) {
8470
8569
  try {
8471
- statSync(path3.join(canonical, ".git"));
8570
+ return resolveProjectIdentity(directory);
8571
+ } catch (error) {
8572
+ const canonical = path3.resolve(directory);
8573
+ const fallback = directoryFallback(canonical);
8574
+ const message = error instanceof Error ? error.message : String(error);
8575
+ log(`[magic-context] project identity resolution failed for ${canonical}; using directory fallback ${fallback}: ${message}`);
8576
+ return fallback;
8577
+ }
8578
+ }
8579
+ function hasGitDir(canonical) {
8580
+ if (hasGitDirInAncestorChain(canonical)) {
8472
8581
  return true;
8582
+ }
8583
+ try {
8584
+ const realCanonical = realpathSync.native(canonical);
8585
+ return realCanonical !== canonical && hasGitDirInAncestorChain(realCanonical);
8473
8586
  } catch {
8474
8587
  return false;
8475
8588
  }
8476
8589
  }
8590
+ function hasGitDirInAncestorChain(startDirectory) {
8591
+ let current = startDirectory;
8592
+ while (true) {
8593
+ if (existsSync2(path3.join(current, ".git"))) {
8594
+ return true;
8595
+ }
8596
+ const parent = path3.dirname(current);
8597
+ if (parent === current) {
8598
+ return false;
8599
+ }
8600
+ current = parent;
8601
+ }
8602
+ }
8477
8603
  function normalizeStoredProjectPath(rawOrStored) {
8478
8604
  if (rawOrStored.startsWith("git:") || rawOrStored.startsWith("dir:")) {
8479
8605
  return rawOrStored;
@@ -10038,6 +10164,15 @@ var MIGRATIONS = [
10038
10164
  );
10039
10165
  `);
10040
10166
  }
10167
+ },
10168
+ {
10169
+ version: 50,
10170
+ description: "add durable ctx-wrapup session marker",
10171
+ up(db) {
10172
+ if (tableExists2(db, "session_meta")) {
10173
+ ensureColumn(db, "session_meta", "wrapup_in_progress_state", "TEXT");
10174
+ }
10175
+ }
10041
10176
  }
10042
10177
  ];
10043
10178
  var LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max, m) => Math.max(max, m.version), 0);
@@ -142760,7 +142895,7 @@ function loadToolDefinitionMeasurements(db) {
142760
142895
  // ../plugin/src/features/magic-context/tool-owner-backfill.ts
142761
142896
  init_data_path();
142762
142897
  init_logger();
142763
- import { existsSync as existsSync2 } from "node:fs";
142898
+ import { existsSync as existsSync3 } from "node:fs";
142764
142899
  import { join as join2 } from "node:path";
142765
142900
  var LEASE_DURATION_MS = 5 * 60 * 1000;
142766
142901
  var LEASE_RENEWAL_MS = 60 * 1000;
@@ -142800,7 +142935,7 @@ function runToolOwnerBackfill(db) {
142800
142935
  return result;
142801
142936
  }
142802
142937
  const opencodeDbPath = resolveOpencodeDbPath();
142803
- if (!existsSync2(opencodeDbPath)) {
142938
+ if (!existsSync3(opencodeDbPath)) {
142804
142939
  log(`[backfill] OpenCode DB not found at ${opencodeDbPath} — marking all unbackfilled sessions as skipped. Lazy adoption (defense-in-depth) handles legacy rows at runtime.`);
142805
142940
  markAllUnbackfilledSessionsSkipped(db);
142806
142941
  result.sessionsSkippedNoOcDb = countSessionsByStatus(db, "skipped");
@@ -143022,7 +143157,7 @@ var persistenceByDatabase = new WeakMap;
143022
143157
  var persistenceErrorByDatabase = new WeakMap;
143023
143158
  var pathByDatabase = new WeakMap;
143024
143159
  var lastSchemaFenceRejection = null;
143025
- var LATEST_SUPPORTED_VERSION = 49;
143160
+ var LATEST_SUPPORTED_VERSION = 50;
143026
143161
  var PERMISSIONS_ENFORCEABLE = process.platform !== "win32";
143027
143162
  function ensureSecureStorageDir(dir) {
143028
143163
  mkdirSync3(dir, { recursive: true, mode: 448 });
@@ -143039,7 +143174,7 @@ function restrictDatabaseFilePermissions(dbPath) {
143039
143174
  return;
143040
143175
  for (const suffix of ["", "-wal", "-shm"]) {
143041
143176
  const file = `${dbPath}${suffix}`;
143042
- if (!existsSync3(file))
143177
+ if (!existsSync4(file))
143043
143178
  continue;
143044
143179
  try {
143045
143180
  chmodSync(file, 384);
@@ -143065,11 +143200,11 @@ function getDatabasePath(db) {
143065
143200
  return pathByDatabase.get(db) ?? null;
143066
143201
  }
143067
143202
  function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
143068
- if (existsSync3(targetDbPath))
143203
+ if (existsSync4(targetDbPath))
143069
143204
  return;
143070
143205
  const legacyDir = getLegacyOpenCodeMagicContextStorageDir();
143071
143206
  const legacyDbPath = join3(legacyDir, "context.db");
143072
- if (!existsSync3(legacyDbPath))
143207
+ if (!existsSync4(legacyDbPath))
143073
143208
  return;
143074
143209
  log(`[magic-context] migrating legacy plugin storage: ${legacyDir} -> ${targetDbDir} (legacy left in place as backup)`);
143075
143210
  ensureSecureStorageDir(targetDbDir);
@@ -143086,7 +143221,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
143086
143221
  for (const suffix of ["", "-wal", "-shm"]) {
143087
143222
  const src = `${legacyDbPath}${suffix}`;
143088
143223
  const dst = join3(targetDbDir, `context.db${suffix}`);
143089
- if (existsSync3(src)) {
143224
+ if (existsSync4(src)) {
143090
143225
  try {
143091
143226
  copyFileSync(src, dst);
143092
143227
  } catch (error) {
@@ -143096,7 +143231,7 @@ function migrateLegacyStorageIfNeeded(targetDbPath, targetDbDir) {
143096
143231
  }
143097
143232
  const legacyModelsDir = join3(legacyDir, "models");
143098
143233
  const targetModelsDir = join3(targetDbDir, "models");
143099
- if (existsSync3(legacyModelsDir) && !existsSync3(targetModelsDir)) {
143234
+ if (existsSync4(legacyModelsDir) && !existsSync4(targetModelsDir)) {
143100
143235
  try {
143101
143236
  cpSync(legacyModelsDir, targetModelsDir, { recursive: true });
143102
143237
  } catch (error) {
@@ -143700,6 +143835,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
143700
143835
  force_emergency_bypass_used INTEGER NOT NULL DEFAULT 0,
143701
143836
  emergency_drain_active INTEGER NOT NULL DEFAULT 0,
143702
143837
  historian_drain_failure_at INTEGER NOT NULL DEFAULT 0,
143838
+ wrapup_in_progress_state TEXT,
143703
143839
  cached_m0_materialized_at INTEGER,
143704
143840
  cached_m0_session_facts_version INTEGER,
143705
143841
  cached_m0_upgrade_state TEXT,
@@ -143988,6 +144124,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
143988
144124
  ensureColumn(db, "session_meta", "force_emergency_bypass_used", "INTEGER NOT NULL DEFAULT 0");
143989
144125
  ensureColumn(db, "session_meta", "emergency_drain_active", "INTEGER NOT NULL DEFAULT 0");
143990
144126
  ensureColumn(db, "session_meta", "historian_drain_failure_at", "INTEGER NOT NULL DEFAULT 0");
144127
+ ensureColumn(db, "session_meta", "wrapup_in_progress_state", "TEXT");
143991
144128
  ensureColumn(db, "session_meta", "cached_m0_materialized_at", "INTEGER");
143992
144129
  ensureColumn(db, "session_meta", "cached_m0_session_facts_version", "INTEGER");
143993
144130
  ensureColumn(db, "session_meta", "cached_m0_upgrade_state", "TEXT");
@@ -144164,7 +144301,7 @@ init_logger();
144164
144301
  // ../plugin/src/config/migrate-config-location.ts
144165
144302
  import {
144166
144303
  closeSync,
144167
- existsSync as existsSync4,
144304
+ existsSync as existsSync5,
144168
144305
  mkdirSync as mkdirSync4,
144169
144306
  openSync,
144170
144307
  readFileSync as readFileSync2,
@@ -144459,7 +144596,7 @@ function visibleConfigMigrationWarning(scope, targetPath, paths, reason) {
144459
144596
  }
144460
144597
  function migrateConfigFile(opts) {
144461
144598
  const warnings = [];
144462
- const existingSources = opts.legacySources.filter((source) => existsSync4(source.path));
144599
+ const existingSources = opts.legacySources.filter((source) => existsSync5(source.path));
144463
144600
  const info = opts.logger?.info ?? opts.logger?.log;
144464
144601
  if (existingSources.length === 0) {
144465
144602
  return { migrated: false, conflict: false, targetPath: opts.targetPath, warnings };
@@ -144475,7 +144612,7 @@ function migrateConfigFile(opts) {
144475
144612
  ...source,
144476
144613
  content: readFileSync2(source.path, "utf-8")
144477
144614
  }));
144478
- if (existsSync4(opts.targetPath)) {
144615
+ if (existsSync5(opts.targetPath)) {
144479
144616
  const targetContent = readFileSync2(opts.targetPath, "utf-8");
144480
144617
  const differing2 = sources.filter((source) => !fileSemanticsMatch(source.content, targetContent));
144481
144618
  if (differing2.length > 0) {
@@ -144572,7 +144709,7 @@ function pruneNestedConfigLeaf(block, relativePath) {
144572
144709
  }
144573
144710
 
144574
144711
  // src/config/index.ts
144575
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
144712
+ import { existsSync as existsSync7, readFileSync as readFileSync4 } from "node:fs";
144576
144713
 
144577
144714
  // ../plugin/src/config/agent-disable.ts
144578
144715
  function isDreamerRunnable(config) {
@@ -159724,7 +159861,6 @@ var MagicContextConfigSchema = exports_external.object({
159724
159861
  enabled: exports_external.boolean().default(true).describe("Enable magic context (default: true)"),
159725
159862
  auto_update: exports_external.boolean().optional().describe("Enable automatic npm self-update checks for the OpenCode plugin. Security: USER-only in config loader, so hostile project configs cannot suppress updates."),
159726
159863
  language: exports_external.string().trim().toLowerCase().refine((s) => isValidLanguageCode(s), 'language must be a 2-letter ISO 639-1 code (e.g. "tr", "es", "de")').optional().describe("Output language for Magic Context's generated content and guidance, as a " + '2-letter ISO 639-1 code (e.g. "tr", "es", "de", "ja", "pt"). When set, the ' + "historian, dreamer, sidekick, and the agent-guidance block instruct the model to " + "write its PROSE in this language while keeping all structural tokens (XML tags, " + "the five memory category names, code identifiers, file paths) in English. " + "USER-LEVEL ONLY (ignored in project config for security). Unset = today's " + "behavior (model mirrors the conversation; English scaffolding). Changing it " + "triggers one cache re-materialization; existing compartments/memories keep their " + "original language until naturally rewritten."),
159727
- ctx_reduce_enabled: exports_external.boolean().default(true).describe("When false, ctx_reduce tool is hidden, all nudges disabled, and prompt guidance about ctx_reduce stripped. Heuristic cleanup, compartments, memory, and other features still work. (default: true)"),
159728
159864
  historian: HistorianConfigSchema.describe("Historian agent configuration (model, fallback_models, variant, temperature, maxTokens, permission, two_pass, etc.)"),
159729
159865
  dreamer: DreamerConfigSchema.optional().describe("Dreamer agent + scheduling configuration (model, fallback_models, disable, schedule, tasks, etc.)"),
159730
159866
  cache_ttl: exports_external.union([exports_external.string(), exports_external.object({ default: exports_external.string() }).catchall(exports_external.string())]).default("5m").describe('Cache TTL: string (e.g. "5m") or per-model object ({ default: "5m", "model-id": "10m" })'),
@@ -159763,9 +159899,9 @@ var MagicContextConfigSchema = exports_external.object({
159763
159899
  keep_subagents: exports_external.boolean().default(false).describe("Debug: keep the child sessions Magic Context spawns for its own subagents (historian, dreamer, sidekick, memory-migration) instead of deleting them on success. Useful for short-term inspection/data collection — their full transcript (prompt, tool calls, token usage, output) stays in the host session store. Kept sessions accumulate until manually cleared; leave false for normal use. Requires a restart to take effect."),
159764
159900
  smart_drops: exports_external.boolean().default(false).describe("Content-aware reclaim of provably-superseded tool output, layered on the existing execute-pass auto-drop. When on: superseded todowrite (keep newest 1), spent ctx_reduce (keep newest 5), and zero-value meta (bash_status, bash_kill, ctx_note read/dismiss) outputs are dropped; older edits to a file are compressed to a filePath-preserving marker while the newest edit per file stays full. Only acts on passes already busting the cache, so it never originates a cache bust. Honors the protected-tag reserve. Experimental: opt-in, default off until cache stability is proven; when off the wire is byte-identical to the positional-only reclaim. Requires a restart."),
159765
159901
  caveman_text_compression: exports_external.object({
159766
- enabled: exports_external.boolean().default(false).describe("Apply deterministic caveman-style text compression to old conversation text. Only active when ctx_reduce_enabled=false. Compresses user/assistant text in oldest-first tiers: ultra (oldest 20%), full, lite, untouched (newest 40%)."),
159902
+ enabled: exports_external.boolean().default(false).describe("Apply deterministic caveman-style text compression to old conversation text. Active for primary sessions when enabled; never for subagents. Compresses user/assistant text in oldest-first tiers: ultra (oldest 20%), full, lite, untouched (newest 40%)."),
159767
159903
  min_chars: exports_external.number().min(100).max(1e4).default(500).describe("Text parts shorter than this (characters) stay untouched. Min 100, max 10000. Default: 500.")
159768
- }).default({ enabled: false, min_chars: 500 }).describe("Age-tier caveman compression for long user/assistant text parts. Only active when ctx_reduce_enabled is false. Oldest 20% of eligible tags (outside protected tail) go to ultra, next 20% to full, next 20% to lite, newest 40% untouched. Graduated from experimental.caveman_text_compression; opt-in, default off (lossy)."),
159904
+ }).default({ enabled: false, min_chars: 500 }).describe("Age-tier caveman compression for long user/assistant text parts. Active for primary sessions when enabled; never for subagents. Oldest 20% of eligible tags (outside protected tail) go to ultra, next 20% to full, next 20% to lite, newest 40% untouched. Graduated from experimental.caveman_text_compression; opt-in, default off (lossy)."),
159769
159905
  memory: exports_external.object({
159770
159906
  enabled: exports_external.boolean().default(true).describe("Enable cross-session memory (default: true)"),
159771
159907
  injection_budget_tokens: exports_external.number().min(500).max(20000).default(4000).describe("Token budget for memory injection on session start (min: 500, max: 20000, default: 4000)"),
@@ -159798,7 +159934,7 @@ var MagicContextConfigSchema = exports_external.object({
159798
159934
  });
159799
159935
 
159800
159936
  // ../plugin/src/config/variable.ts
159801
- import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
159937
+ import { existsSync as existsSync6, readFileSync as readFileSync3 } from "node:fs";
159802
159938
  import { homedir as homedir3 } from "node:os";
159803
159939
  import { dirname as dirname4, isAbsolute as isAbsolute2, resolve } from "node:path";
159804
159940
 
@@ -159933,7 +160069,7 @@ function substituteConfigVariables(input) {
159933
160069
  if (sensitiveReason) {
159934
160070
  warnings.push(`${token} resolves to a sensitive path (${sensitiveReason}: ${filePath}); ` + "inlining its contents into config — make sure this is intentional.");
159935
160071
  }
159936
- if (!existsSync5(filePath)) {
160072
+ if (!existsSync6(filePath)) {
159937
160073
  warnings.push(`File not found for ${token} (resolved to ${filePath}); using empty string`);
159938
160074
  continue;
159939
160075
  }
@@ -159962,10 +160098,10 @@ function getUserConfigPaths() {
159962
160098
  return [`${basePath}.jsonc`, `${basePath}.json`];
159963
160099
  }
159964
160100
  function resolveFirstExisting(paths) {
159965
- return paths.find((path4) => existsSync6(path4));
160101
+ return paths.find((path4) => existsSync7(path4));
159966
160102
  }
159967
160103
  function resolvePiLegacyFallback(sources) {
159968
- return sources.find((source) => existsSync6(source.path)) ?? null;
160104
+ return sources.find((source) => existsSync7(source.path)) ?? null;
159969
160105
  }
159970
160106
  function loadConfigFile(path4, scope) {
159971
160107
  try {
@@ -160102,7 +160238,7 @@ function loadPiConfig(opts = {}) {
160102
160238
  if (loaded)
160103
160239
  loadedFiles.push(loaded);
160104
160240
  }
160105
- const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync6(source.path));
160241
+ const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync7(source.path));
160106
160242
  const userPath = resolveFirstExisting(getUserConfigPaths());
160107
160243
  const userLegacyFallback = userPath ? null : resolvePiLegacyFallback(harnessLegacy.user);
160108
160244
  const userReadPath = userPath ?? userLegacyFallback?.path;
@@ -160111,7 +160247,7 @@ function loadPiConfig(opts = {}) {
160111
160247
  if (loaded)
160112
160248
  loadedFiles.push(loaded);
160113
160249
  }
160114
- const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync6(source.path));
160250
+ const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync7(source.path));
160115
160251
  if (userLegacyFallback) {
160116
160252
  warnings.push(`[user config] reading legacy config from ${userLegacyFallback.path} until migration completes; run \`npx @cortexkit/magic-context doctor\` to consolidate into the shared CortexKit location.`);
160117
160253
  } else if (legacyUserUnmigrated) {
@@ -160212,7 +160348,7 @@ function loadPiConfigDetailed(opts = {}) {
160212
160348
  if (loaded)
160213
160349
  loadedFiles.push(loaded);
160214
160350
  }
160215
- const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync6(source.path));
160351
+ const legacyProjectUnmigrated = !projectPath && !projectLegacyFallback && legacySources.project.some((source) => existsSync7(source.path));
160216
160352
  const userPath = resolveFirstExisting(getUserConfigPaths());
160217
160353
  const userLegacyFallback = userPath ? null : resolvePiLegacyFallback(harnessLegacy.user);
160218
160354
  const userReadPath = userPath ?? userLegacyFallback?.path;
@@ -160221,7 +160357,7 @@ function loadPiConfigDetailed(opts = {}) {
160221
160357
  if (loaded)
160222
160358
  loadedFiles.push(loaded);
160223
160359
  }
160224
- const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync6(source.path));
160360
+ const legacyUserUnmigrated = !userPath && !userLegacyFallback && legacySources.user.some((source) => existsSync7(source.path));
160225
160361
  if (userLegacyFallback) {
160226
160362
  warnings.push(`[user config] reading legacy config from ${userLegacyFallback.path} until migration completes; run \`npx @cortexkit/magic-context doctor\` to consolidate into the shared CortexKit location.`);
160227
160363
  } else if (legacyUserUnmigrated) {
@@ -161055,7 +161191,8 @@ function isNativeRuntimeMissingError(error51) {
161055
161191
  if (code === "ERR_DLOPEN_FAILED" && lower.includes("onnxruntime")) {
161056
161192
  return true;
161057
161193
  }
161058
- if (!lower.includes("onnxruntime-node"))
161194
+ const mentionsNativeRuntime = lower.includes("onnxruntime-node") || lower.includes("onnxruntime_binding");
161195
+ if (!mentionsNativeRuntime)
161059
161196
  return false;
161060
161197
  return code === "ERR_MODULE_NOT_FOUND" || name2 === "ResolveMessage" || lower.includes("cannot find package") || lower.includes("cannot find module") || lower.includes("err_module_not_found");
161061
161198
  }
@@ -161200,7 +161337,7 @@ class LocalEmbeddingProvider {
161200
161337
  } catch (error51) {
161201
161338
  if (isNativeRuntimeMissingError(error51)) {
161202
161339
  nativeRuntimeMissing = true;
161203
- log("[magic-context] local embedding runtime is not installed (onnxruntime-node missing from this install). Local embeddings are disabled. Fix: reinstall the plugin (run `npx @cortexkit/magic-context@latest doctor --force`), or configure an `openai-compatible`/`ollama` embedding endpoint instead. Existing memories are unaffected.");
161340
+ log("[magic-context] local embeddings are disabled because the onnxruntime-node native binding is missing or failed to load. Run `npx @cortexkit/magic-context@latest doctor` for repair guidance (use `doctor --force` to reinstall cached plugin packages), or configure an `openai-compatible` embedding HTTP endpoint. Existing memories are unaffected.");
161204
161341
  } else {
161205
161342
  log("[magic-context] embedding model failed to load:", error51);
161206
161343
  }
@@ -163623,13 +163760,13 @@ function escapeXmlContent(s) {
163623
163760
  // ../plugin/src/hooks/magic-context/read-session-db.ts
163624
163761
  init_data_path();
163625
163762
  init_logger();
163626
- import { existsSync as existsSync7 } from "node:fs";
163763
+ import { existsSync as existsSync8 } from "node:fs";
163627
163764
  import { join as join6 } from "node:path";
163628
163765
  function getOpenCodeDbPath() {
163629
163766
  return join6(getDataDir(), "opencode", "opencode.db");
163630
163767
  }
163631
163768
  function openCodeDbExists() {
163632
- return existsSync7(getOpenCodeDbPath());
163769
+ return existsSync8(getOpenCodeDbPath());
163633
163770
  }
163634
163771
  var cachedReadOnlyDb = null;
163635
163772
  function closeCachedReadOnlyDb() {
@@ -169963,7 +170100,7 @@ function getMemoryVerifications(db, memoryIds) {
169963
170100
  }
169964
170101
  // ../plugin/src/features/magic-context/memory/verification-paths.ts
169965
170102
  import { execFile } from "node:child_process";
169966
- import { existsSync as existsSync8, realpathSync, statSync as statSync3 } from "node:fs";
170103
+ import { existsSync as existsSync9, realpathSync as realpathSync2, statSync as statSync3 } from "node:fs";
169967
170104
  import path4 from "node:path";
169968
170105
  import { promisify } from "node:util";
169969
170106
  var execFileAsync = promisify(execFile);
@@ -169990,7 +170127,7 @@ function isWithin(root, candidate) {
169990
170127
  }
169991
170128
  function safeRealpath(value) {
169992
170129
  try {
169993
- return realpathSync.native(value);
170130
+ return realpathSync2.native(value);
169994
170131
  } catch {
169995
170132
  return null;
169996
170133
  }
@@ -170068,7 +170205,7 @@ function verificationFileExists(baseRoot, filePath) {
170068
170205
  return false;
170069
170206
  const root = path4.resolve(baseRoot);
170070
170207
  const candidate = path4.resolve(root, filePath);
170071
- return isWithin(root, candidate) && existsSync8(candidate);
170208
+ return isWithin(root, candidate) && existsSync9(candidate);
170072
170209
  }
170073
170210
  async function normalizeVerificationFiles(args) {
170074
170211
  const cwd = path4.resolve(args.cwd);
@@ -170101,7 +170238,7 @@ async function normalizeVerificationFiles(args) {
170101
170238
  warnings.push(`Skipped verification path "${value}" because it is the repo/project root.`);
170102
170239
  continue;
170103
170240
  }
170104
- if (existsSync8(candidate)) {
170241
+ if (existsSync9(candidate)) {
170105
170242
  try {
170106
170243
  if (statSync3(candidate).isDirectory()) {
170107
170244
  warnings.push(`Skipped verification path "${value}" because it is a directory.`);
@@ -170132,7 +170269,7 @@ async function normalizeVerificationFiles(args) {
170132
170269
  }
170133
170270
  normalized.push(tracked);
170134
170271
  } else {
170135
- if (!existsSync8(candidate)) {
170272
+ if (!existsSync9(candidate)) {
170136
170273
  warnings.push(`Skipped verification path "${value}" because it does not exist.`);
170137
170274
  continue;
170138
170275
  }
@@ -170734,6 +170871,148 @@ function recordProtectedTailNoEligibleHead(db, sessionId) {
170734
170871
  return loadProtectedTailMeta(db, sessionId).recoveryNoEligibleHeadCount;
170735
170872
  }
170736
170873
  var DRAIN_WINDOW_MS = 10 * 60 * 1000;
170874
+ var WRAPUP_IN_PROGRESS_TTL_MS = 5 * 60 * 1000;
170875
+ function parseWrapupState(value) {
170876
+ if (typeof value !== "string" || value.trim().length === 0)
170877
+ return null;
170878
+ try {
170879
+ const parsed = JSON.parse(value);
170880
+ if (!parsed || typeof parsed !== "object")
170881
+ return null;
170882
+ if (typeof parsed.holderId !== "string" || parsed.holderId.length === 0)
170883
+ return null;
170884
+ const numberFields = [
170885
+ "acquiredAt",
170886
+ "expiresAt",
170887
+ "messagesToKeep",
170888
+ "anchorRawMessageCount",
170889
+ "targetEligibleEndOrdinal",
170890
+ "lastCompartmentEnd",
170891
+ "chunkIndex",
170892
+ "expectedChunks",
170893
+ "updatedAt"
170894
+ ];
170895
+ for (const field of numberFields) {
170896
+ if (typeof parsed[field] !== "number" || !Number.isFinite(parsed[field]))
170897
+ return null;
170898
+ }
170899
+ return parsed;
170900
+ } catch {
170901
+ return null;
170902
+ }
170903
+ }
170904
+ function readRawWrapupState(db, sessionId) {
170905
+ const row = db.prepare("SELECT wrapup_in_progress_state FROM session_meta WHERE session_id = ?").get(sessionId);
170906
+ return parseWrapupState(row?.wrapup_in_progress_state);
170907
+ }
170908
+ function getWrapupInProgressState(db, sessionId, now = Date.now()) {
170909
+ const state = readRawWrapupState(db, sessionId);
170910
+ if (!state)
170911
+ return null;
170912
+ if (state.expiresAt > now)
170913
+ return state;
170914
+ try {
170915
+ db.exec("BEGIN IMMEDIATE");
170916
+ } catch {
170917
+ return null;
170918
+ }
170919
+ let finished = false;
170920
+ try {
170921
+ const current = readRawWrapupState(db, sessionId);
170922
+ if (current && current.expiresAt <= now) {
170923
+ db.prepare("UPDATE session_meta SET wrapup_in_progress_state = NULL WHERE session_id = ?").run(sessionId);
170924
+ }
170925
+ db.exec("COMMIT");
170926
+ finished = true;
170927
+ } finally {
170928
+ if (!finished) {
170929
+ try {
170930
+ db.exec("ROLLBACK");
170931
+ } catch {}
170932
+ }
170933
+ }
170934
+ return null;
170935
+ }
170936
+ function isWrapupInProgress(db, sessionId, now = Date.now()) {
170937
+ return getWrapupInProgressState(db, sessionId, now) !== null;
170938
+ }
170939
+ function acquireWrapupInProgress(db, sessionId, state, now = Date.now()) {
170940
+ const acquiredAt = now;
170941
+ const next = {
170942
+ ...state,
170943
+ acquiredAt,
170944
+ expiresAt: acquiredAt + WRAPUP_IN_PROGRESS_TTL_MS,
170945
+ updatedAt: acquiredAt
170946
+ };
170947
+ db.exec("BEGIN IMMEDIATE");
170948
+ let finished = false;
170949
+ try {
170950
+ ensureSessionMetaRow(db, sessionId);
170951
+ const current = readRawWrapupState(db, sessionId);
170952
+ if (current && current.expiresAt > now && current.holderId !== state.holderId) {
170953
+ db.exec("COMMIT");
170954
+ finished = true;
170955
+ return { ok: false, state: current };
170956
+ }
170957
+ db.prepare("UPDATE session_meta SET wrapup_in_progress_state = ? WHERE session_id = ?").run(stableStringify(next), sessionId);
170958
+ db.exec("COMMIT");
170959
+ finished = true;
170960
+ return { ok: true, state: next };
170961
+ } finally {
170962
+ if (!finished) {
170963
+ try {
170964
+ db.exec("ROLLBACK");
170965
+ } catch {}
170966
+ }
170967
+ }
170968
+ }
170969
+ function updateWrapupInProgress(db, sessionId, holderId, updates, now = Date.now()) {
170970
+ db.exec("BEGIN IMMEDIATE");
170971
+ let finished = false;
170972
+ try {
170973
+ const current = readRawWrapupState(db, sessionId);
170974
+ if (!current || current.holderId !== holderId || current.expiresAt <= now) {
170975
+ db.exec("ROLLBACK");
170976
+ finished = true;
170977
+ return null;
170978
+ }
170979
+ const next = {
170980
+ ...current,
170981
+ ...updates,
170982
+ holderId,
170983
+ expiresAt: now + WRAPUP_IN_PROGRESS_TTL_MS,
170984
+ updatedAt: now
170985
+ };
170986
+ db.prepare("UPDATE session_meta SET wrapup_in_progress_state = ? WHERE session_id = ?").run(stableStringify(next), sessionId);
170987
+ db.exec("COMMIT");
170988
+ finished = true;
170989
+ return next;
170990
+ } finally {
170991
+ if (!finished) {
170992
+ try {
170993
+ db.exec("ROLLBACK");
170994
+ } catch {}
170995
+ }
170996
+ }
170997
+ }
170998
+ function releaseWrapupInProgress(db, sessionId, holderId) {
170999
+ db.exec("BEGIN IMMEDIATE");
171000
+ let finished = false;
171001
+ try {
171002
+ const current = readRawWrapupState(db, sessionId);
171003
+ if (current?.holderId === holderId) {
171004
+ db.prepare("UPDATE session_meta SET wrapup_in_progress_state = NULL WHERE session_id = ?").run(sessionId);
171005
+ }
171006
+ db.exec("COMMIT");
171007
+ finished = true;
171008
+ } finally {
171009
+ if (!finished) {
171010
+ try {
171011
+ db.exec("ROLLBACK");
171012
+ } catch {}
171013
+ }
171014
+ }
171015
+ }
170737
171016
  function protectedTailWindowBudget(usagePercentage, usable, perRunCap) {
170738
171017
  if (usagePercentage >= 95)
170739
171018
  return Math.min(1e6, Math.max(4 * perRunCap, Math.round(0.5 * usable)));
@@ -174219,6 +174498,147 @@ function searchMessages(args) {
174219
174498
  role: entry.row.role
174220
174499
  }));
174221
174500
  }
174501
+ var NOTE_SEARCHABLE_STATUSES = ["active", "pending", "ready", "dismissed"];
174502
+ function noteSearchText(note) {
174503
+ const reason = note.readyReason?.trim();
174504
+ return reason ? `${note.content}
174505
+ Reason: ${reason}` : note.content;
174506
+ }
174507
+ function tokenizeKeywordNeedle(text) {
174508
+ const matches = text.toLowerCase().match(/[a-z0-9/._:-]+/g) ?? [];
174509
+ const seen = new Set;
174510
+ const tokens = [];
174511
+ for (const match of matches) {
174512
+ if (match.length <= 1 || !/[a-z0-9]/.test(match) || seen.has(match)) {
174513
+ continue;
174514
+ }
174515
+ seen.add(match);
174516
+ tokens.push(match);
174517
+ }
174518
+ return tokens;
174519
+ }
174520
+ function rankNotesForNeedle(notes, needle) {
174521
+ const normalizedNeedle = needle.trim().toLowerCase();
174522
+ if (normalizedNeedle.length === 0) {
174523
+ return [];
174524
+ }
174525
+ const needleTokens = tokenizeKeywordNeedle(normalizedNeedle);
174526
+ const ranked = [];
174527
+ for (const note of notes) {
174528
+ const text = noteSearchText(note);
174529
+ const normalizedText = text.toLowerCase();
174530
+ const noteTokens = new Set(tokenizeKeywordNeedle(normalizedText));
174531
+ const exact = normalizedText.includes(normalizedNeedle);
174532
+ const matchedTokens = needleTokens.filter((token) => noteTokens.has(token)).length;
174533
+ if (!exact && matchedTokens === 0) {
174534
+ continue;
174535
+ }
174536
+ const coverage = needleTokens.length > 0 ? matchedTokens / needleTokens.length : 0;
174537
+ const score = (exact ? 2 : 0) + coverage + (needleTokens.length > 1 && matchedTokens === needleTokens.length ? 0.5 : 0);
174538
+ ranked.push({ note, score, text });
174539
+ }
174540
+ return ranked.sort((left, right) => {
174541
+ if (right.score !== left.score) {
174542
+ return right.score - left.score;
174543
+ }
174544
+ if (right.note.createdAt !== left.note.createdAt) {
174545
+ return right.note.createdAt - left.note.createdAt;
174546
+ }
174547
+ return left.note.id - right.note.id;
174548
+ });
174549
+ }
174550
+ function searchNotes(args) {
174551
+ if (args.limit <= 0) {
174552
+ return [];
174553
+ }
174554
+ const notes = [
174555
+ ...getNotes(args.db, {
174556
+ sessionId: args.sessionId,
174557
+ type: "session",
174558
+ status: NOTE_SEARCHABLE_STATUSES
174559
+ }),
174560
+ ...getNotes(args.db, {
174561
+ projectPath: args.projectPath,
174562
+ type: "smart",
174563
+ status: NOTE_SEARCHABLE_STATUSES
174564
+ })
174565
+ ];
174566
+ if (notes.length === 0) {
174567
+ return [];
174568
+ }
174569
+ const baseList = rankNotesForNeedle(notes, args.query);
174570
+ const probes = args.probes ?? [];
174571
+ if (probes.length === 0) {
174572
+ const ranked2 = baseList.slice(0, args.limit);
174573
+ return ranked2.map((entry, rank) => ({
174574
+ source: "note",
174575
+ content: previewText(entry.text),
174576
+ score: linearDecayScore(rank, ranked2.length),
174577
+ noteId: entry.note.id,
174578
+ status: entry.note.status,
174579
+ createdAt: entry.note.createdAt,
174580
+ anchorOrdinal: entry.note.anchorOrdinal,
174581
+ sourceSessionId: entry.note.sessionId
174582
+ }));
174583
+ }
174584
+ const queryLists = [];
174585
+ if (baseList.length > 0) {
174586
+ queryLists.push({ rows: baseList, weight: 1 });
174587
+ }
174588
+ const probeWeights = new Map;
174589
+ for (const probe of probes) {
174590
+ const rows = rankNotesForNeedle(notes, probe);
174591
+ if (rows.length === 0) {
174592
+ continue;
174593
+ }
174594
+ const weight = probeDiscriminationWeight(rows.length, notes.length);
174595
+ probeWeights.set(probe, weight);
174596
+ queryLists.push({ rows, weight });
174597
+ }
174598
+ const fused = new Map;
174599
+ for (const list of queryLists) {
174600
+ list.rows.forEach((row, rank) => {
174601
+ const rrf = list.weight / (RRF_K + rank);
174602
+ const existing = fused.get(row.note.id);
174603
+ if (existing) {
174604
+ existing.score += rrf;
174605
+ } else {
174606
+ fused.set(row.note.id, { entry: row, score: rrf });
174607
+ }
174608
+ });
174609
+ }
174610
+ for (const match of fused.values()) {
174611
+ let best = 0;
174612
+ for (const probe of probes) {
174613
+ const weight = probeWeights.get(probe) ?? 0;
174614
+ if (weight > best && containsProbeVerbatim(match.entry.text, [probe])) {
174615
+ best = weight;
174616
+ }
174617
+ }
174618
+ if (best > 0) {
174619
+ match.score += best * VERBATIM_RANK_BONUS;
174620
+ }
174621
+ }
174622
+ const ranked = [...fused.values()].sort((left, right) => {
174623
+ if (right.score !== left.score) {
174624
+ return right.score - left.score;
174625
+ }
174626
+ if (right.entry.note.createdAt !== left.entry.note.createdAt) {
174627
+ return right.entry.note.createdAt - left.entry.note.createdAt;
174628
+ }
174629
+ return left.entry.note.id - right.entry.note.id;
174630
+ }).slice(0, args.limit);
174631
+ return ranked.map((entry, rank) => ({
174632
+ source: "note",
174633
+ content: previewText(entry.entry.text),
174634
+ score: linearDecayScore(rank, ranked.length),
174635
+ noteId: entry.entry.note.id,
174636
+ status: entry.entry.note.status,
174637
+ createdAt: entry.entry.note.createdAt,
174638
+ anchorOrdinal: entry.entry.note.anchorOrdinal,
174639
+ sourceSessionId: entry.entry.note.sessionId
174640
+ }));
174641
+ }
174222
174642
  function searchCompartmentChunks(args) {
174223
174643
  if (!args.queryEmbedding || args.limit <= 0 || !args.modelId || args.modelId === "off")
174224
174644
  return [];
@@ -174304,6 +174724,8 @@ function getSourceBoost(result) {
174304
174724
  return GIT_COMMIT_SOURCE_BOOST;
174305
174725
  case "primer":
174306
174726
  return PRIMER_SOURCE_BOOST;
174727
+ case "note":
174728
+ return 1;
174307
174729
  }
174308
174730
  }
174309
174731
  function compareUnifiedResults(left, right) {
@@ -174327,6 +174749,9 @@ function compareUnifiedResults(left, right) {
174327
174749
  if (left.source === "primer" && right.source === "primer") {
174328
174750
  return right.support - left.support || left.primerId - right.primerId;
174329
174751
  }
174752
+ if (left.source === "note" && right.source === "note") {
174753
+ return right.createdAt - left.createdAt || left.noteId - right.noteId;
174754
+ }
174330
174755
  return 0;
174331
174756
  }
174332
174757
  function toGitCommitResult(hit) {
@@ -174394,11 +174819,11 @@ function searchPrimers(args) {
174394
174819
  }
174395
174820
  function resolveSources(sources) {
174396
174821
  if (sources === undefined) {
174397
- return new Set(["memory", "message", "git_commit", "primer"]);
174822
+ return new Set(["memory", "message", "git_commit", "primer", "note"]);
174398
174823
  }
174399
174824
  const set2 = new Set;
174400
174825
  for (const source of sources) {
174401
- if (source === "memory" || source === "message" || source === "git_commit" || source === "primer") {
174826
+ if (source === "memory" || source === "message" || source === "git_commit" || source === "primer" || source === "note") {
174402
174827
  set2.add(source);
174403
174828
  }
174404
174829
  }
@@ -174421,6 +174846,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
174421
174846
  const runMessages = activeSources.has("message");
174422
174847
  const runGitCommits = activeSources.has("git_commit") && gitCommitsEnabled;
174423
174848
  const runPrimers = activeSources.has("primer") && memoryFeatureEnabled;
174849
+ const runNotes = activeSources.has("note");
174424
174850
  const runCompartmentChunks = runMessages && memoryFeatureEnabled && embeddingEnabled;
174425
174851
  const needsEmbedding = (runMemory || runGitCommits || runCompartmentChunks || runPrimers) && embeddingEnabled && isEmbeddingRuntimeEnabled();
174426
174852
  const queryEmbeddingPromise = needsEmbedding ? embedQuery(trimmedQuery, options.signal).catch((error51) => {
@@ -174456,7 +174882,7 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
174456
174882
  compartments: compartmentResults,
174457
174883
  limit: tierLimit
174458
174884
  });
174459
- const [memoryResults, gitCommitResults, primerResults] = await Promise.all([
174885
+ const [memoryResults, gitCommitResults, primerResults, noteResults] = await Promise.all([
174460
174886
  runMemory ? searchMemories({
174461
174887
  db,
174462
174888
  projectPath,
@@ -174483,9 +174909,23 @@ async function unifiedSearch(db, sessionId, projectPath, query, options = {}) {
174483
174909
  limit: tierLimit,
174484
174910
  queryEmbedding,
174485
174911
  queryModelId: embeddingModelId && embeddingModelId !== "off" ? embeddingModelId : null
174912
+ })) : Promise.resolve([]),
174913
+ runNotes ? Promise.resolve(searchNotes({
174914
+ db,
174915
+ sessionId,
174916
+ projectPath,
174917
+ query: trimmedQuery,
174918
+ limit: tierLimit,
174919
+ probes: messageProbes
174486
174920
  })) : Promise.resolve([])
174487
174921
  ]);
174488
- const results = [...memoryResults, ...primerResults, ...messageLikeResults, ...gitCommitResults].sort(compareUnifiedResults).slice(0, limit);
174922
+ const results = [
174923
+ ...memoryResults,
174924
+ ...primerResults,
174925
+ ...messageLikeResults,
174926
+ ...gitCommitResults,
174927
+ ...noteResults
174928
+ ].sort(compareUnifiedResults).slice(0, limit);
174489
174929
  const countRetrievals = options.countRetrievals ?? true;
174490
174930
  if (countRetrievals) {
174491
174931
  const memoryIds = results.filter((result) => result.source === "memory").map((result) => result.memoryId);
@@ -175018,14 +175458,17 @@ Sources (omit for a broad search across all):
175018
175458
  - memory: curated cross-session project knowledge — rules, constraints, conventions.
175019
175459
  - message: the raw conversation behind your compacted history. Hits include message ordinals — expand the surrounding exchange with ctx_expand(start=N-10, end=N+5).
175020
175460
  - git_commit: this repository's commit history.
175461
+ - note: parked decisions, follow-ups, and dismissed notes with their recorded text.
175021
175462
 
175022
175463
  Picking sources:
175023
175464
  - "when did this change / was this working before" → ["git_commit", "message"]
175024
175465
  - "did we discuss this earlier" → ["message"]
175466
+ - "did we decide something about this / leave a follow-up" → ["note"]
175025
175467
  - "what's our convention / rule for X" → ["memory"]`;
175026
175468
 
175027
175469
  // src/tools/ctx-search.ts
175028
175470
  var DEFAULT_LIMIT = 10;
175471
+ var NOTE_EXPAND_HINT = "Use ctx_expand(start=N-10, end=N) around any note @msg anchor above to read the surrounding conversation context.";
175029
175472
  var ParamsSchema5 = exports_typebox.Object({
175030
175473
  query: exports_typebox.String({
175031
175474
  description: "Search query. Matches against memory content, Primers, git commit messages, and raw user/assistant message text."
@@ -175037,9 +175480,10 @@ var ParamsSchema5 = exports_typebox.Object({
175037
175480
  exports_typebox.Literal("memory"),
175038
175481
  exports_typebox.Literal("message"),
175039
175482
  exports_typebox.Literal("git_commit"),
175040
- exports_typebox.Literal("primer")
175483
+ exports_typebox.Literal("primer"),
175484
+ exports_typebox.Literal("note")
175041
175485
  ]), {
175042
- description: 'Optional. Restrict to specific sources. Examples: ["primer"] for standing project explanations, ["git_commit"] for "when did we change X", ["memory"] for naming conventions, ["message"] for "did we discuss this earlier", ["git_commit","message"] for regression hunts. Omit for a broad search across all enabled sources.'
175486
+ description: 'Optional. Restrict to specific sources. Examples: ["primer"] for standing project explanations, ["git_commit"] for "when did we change X", ["memory"] for naming conventions, ["message"] for "did we discuss this earlier", ["note"] for parked decisions or follow-ups, ["git_commit","message"] for regression hunts. Omit for a broad search across all enabled sources.'
175043
175487
  }))
175044
175488
  });
175045
175489
  function normalizeLimit3(limit) {
@@ -175066,7 +175510,7 @@ function formatAge(committedAtMs) {
175066
175510
  const years = Math.floor(days / 365);
175067
175511
  return years === 1 ? "1y ago" : `${years}y ago`;
175068
175512
  }
175069
- function formatResult(result, index) {
175513
+ function formatResult(result, index, currentSessionId) {
175070
175514
  if (result.source === "memory") {
175071
175515
  const source = result.sourceName ? ` source=${result.sourceName}` : "";
175072
175516
  return [
@@ -175087,6 +175531,14 @@ function formatResult(result, index) {
175087
175531
  `[${index}] [primer] score=${result.score.toFixed(2)} id=${result.primerId} support=${result.support} match=${result.matchType}`,
175088
175532
  result.content
175089
175533
  ].join(`
175534
+ `);
175535
+ }
175536
+ if (result.source === "note") {
175537
+ const anchor = result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId ? ` @msg ${result.anchorOrdinal}` : "";
175538
+ return [
175539
+ `[${index}] [note] score=${result.score.toFixed(2)} id=#${result.noteId} status=${result.status} ${formatAge(result.createdAt)}${anchor}`,
175540
+ result.content
175541
+ ].join(`
175090
175542
  `);
175091
175543
  }
175092
175544
  if (result.source === "compartment") {
@@ -175104,14 +175556,17 @@ function formatResult(result, index) {
175104
175556
  ].join(`
175105
175557
  `);
175106
175558
  }
175107
- function formatSearchResults(query, results) {
175559
+ function formatSearchResults(query, results, currentSessionId) {
175108
175560
  if (results.length === 0) {
175109
- return `No results found for "${query}" across memories, primers, git commits, or message history.`;
175561
+ return `No results found for "${query}" across notes, memories, primers, git commits, or message history.`;
175110
175562
  }
175111
- const bodyParts = results.map((result, index) => formatResult(result, index + 1));
175563
+ const bodyParts = results.map((result, index) => formatResult(result, index + 1, currentSessionId));
175112
175564
  if (results.some((result) => result.source === "message" || result.source === "compartment")) {
175113
175565
  bodyParts.push("Use ctx_expand(start, end) with the range from any message result above to read the full conversation context.");
175114
175566
  }
175567
+ if (results.some((result) => result.source === "note" && result.anchorOrdinal !== null && result.sourceSessionId === currentSessionId)) {
175568
+ bodyParts.push(NOTE_EXPAND_HINT);
175569
+ }
175115
175570
  const body = bodyParts.join(`
175116
175571
 
175117
175572
  `);
@@ -175160,7 +175615,12 @@ function createCtxSearchTool(deps) {
175160
175615
  explicitSearch: true
175161
175616
  });
175162
175617
  return {
175163
- content: [{ type: "text", text: formatSearchResults(query, results) }],
175618
+ content: [
175619
+ {
175620
+ type: "text",
175621
+ text: formatSearchResults(query, results, sessionId)
175622
+ }
175623
+ ],
175164
175624
  details: undefined
175165
175625
  };
175166
175626
  }
@@ -175257,7 +175717,7 @@ function registerMagicContextTools(pi, opts) {
175257
175717
  pi.registerTool(createCtxExpandTool({ db: opts.db }));
175258
175718
  }
175259
175719
  pi.registerTool(createTodowriteTool());
175260
- if (opts.ctxReduceEnabled === true) {
175720
+ if (!opts.sessionScopedToolsDisabled) {
175261
175721
  pi.registerTool(createCtxReduceTool({
175262
175722
  db: opts.db,
175263
175723
  protectedTags: opts.protectedTags ?? 20