@cortexkit/opencode-magic-context 0.21.2 → 0.21.5

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 (50) hide show
  1. package/README.md +1 -1
  2. package/dist/config/index.d.ts.map +1 -1
  3. package/dist/config/schema/magic-context.d.ts +0 -3
  4. package/dist/config/schema/magic-context.d.ts.map +1 -1
  5. package/dist/config/variable.d.ts +9 -7
  6. package/dist/config/variable.d.ts.map +1 -1
  7. package/dist/features/magic-context/compaction-marker.d.ts +3 -1
  8. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  9. package/dist/features/magic-context/dreamer/runner.d.ts.map +1 -1
  10. package/dist/features/magic-context/key-files/identify-key-files.d.ts.map +1 -1
  11. package/dist/features/magic-context/key-files/project-key-files.d.ts.map +1 -1
  12. package/dist/features/magic-context/key-files/read-history.d.ts.map +1 -1
  13. package/dist/features/magic-context/migrations.d.ts +18 -0
  14. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  15. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  16. package/dist/features/magic-context/storage-meta-persisted.d.ts +17 -0
  17. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  18. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  19. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  20. package/dist/features/magic-context/storage-notes.d.ts +6 -2
  21. package/dist/features/magic-context/storage-notes.d.ts.map +1 -1
  22. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  23. package/dist/features/magic-context/storage.d.ts +1 -1
  24. package/dist/features/magic-context/storage.d.ts.map +1 -1
  25. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
  26. package/dist/hooks/magic-context/compaction-marker-manager.d.ts +4 -4
  27. package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
  28. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  29. package/dist/hooks/magic-context/compartment-runner-types.d.ts +0 -2
  30. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  31. package/dist/hooks/magic-context/compartment-trigger.d.ts +2 -2
  32. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  33. package/dist/hooks/magic-context/hook.d.ts +0 -1
  34. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  35. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  36. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +0 -2
  37. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/transform.d.ts +0 -1
  39. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  40. package/dist/index.js +214 -75
  41. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  42. package/dist/plugin/sidebar-snapshot-cache.d.ts.map +1 -1
  43. package/dist/shared/jsonc-parser.d.ts +1 -0
  44. package/dist/shared/jsonc-parser.d.ts.map +1 -1
  45. package/dist/tools/ctx-note/constants.d.ts +1 -1
  46. package/dist/tools/ctx-note/constants.d.ts.map +1 -1
  47. package/dist/tools/ctx-note/tools.d.ts.map +1 -1
  48. package/package.json +1 -1
  49. package/src/shared/jsonc-parser.ts +1 -1
  50. package/src/tui/slots/sidebar-content.tsx +27 -14
package/dist/index.js CHANGED
@@ -14950,7 +14950,6 @@ var init_magic_context = __esm(() => {
14950
14950
  enabled: exports_external.boolean().default(true),
14951
14951
  min_clusters: exports_external.number().min(1).default(3)
14952
14952
  }).default({ enabled: true, min_clusters: 3 }),
14953
- compaction_markers: exports_external.boolean().default(true),
14954
14953
  system_prompt_injection: exports_external.object({
14955
14954
  enabled: exports_external.boolean().default(true),
14956
14955
  skip_signatures: exports_external.array(exports_external.string()).default(["<!-- magic-context: skip -->"])
@@ -156281,7 +156280,7 @@ function promoteRecompStaging(db, sessionId) {
156281
156280
  insertFactRows(db, sessionId, staging.facts, now);
156282
156281
  db.prepare("DELETE FROM recomp_compartments WHERE session_id = ?").run(sessionId);
156283
156282
  db.prepare("DELETE FROM recomp_facts WHERE session_id = ?").run(sessionId);
156284
- db.prepare("UPDATE session_meta SET memory_block_cache = '' WHERE session_id = ?").run(sessionId);
156283
+ db.prepare("UPDATE session_meta SET memory_block_cache = '', memory_block_ids = '' WHERE session_id = ?").run(sessionId);
156285
156284
  return { compartments: staging.compartments, facts: staging.facts };
156286
156285
  })();
156287
156286
  }
@@ -157706,8 +157705,10 @@ function deleteOrphanProjectKeyFiles(db, now = Date.now()) {
157706
157705
  if (existsSync10(row.projectPath))
157707
157706
  continue;
157708
157707
  try {
157709
- db.prepare("DELETE FROM project_key_files WHERE project_path = ?").run(row.projectPath);
157710
- db.prepare("DELETE FROM project_key_files_version WHERE project_path = ?").run(row.projectPath);
157708
+ db.transaction(() => {
157709
+ db.prepare("DELETE FROM project_key_files WHERE project_path = ?").run(row.projectPath);
157710
+ db.prepare("DELETE FROM project_key_files_version WHERE project_path = ?").run(row.projectPath);
157711
+ })();
157711
157712
  deletedProjects++;
157712
157713
  } catch (error51) {
157713
157714
  log(`[key-files] orphan GC failed for ${row.projectPath}:`, error51);
@@ -158255,6 +158256,12 @@ function getNoteById(db, noteId) {
158255
158256
  const row = db.prepare("SELECT * FROM notes WHERE id = ?").get(noteId);
158256
158257
  return isNoteRow(row) ? toNote(row) : null;
158257
158258
  }
158259
+ function noteBelongsToScope(note, scope) {
158260
+ if (note.type === "session") {
158261
+ return note.sessionId === scope.sessionId;
158262
+ }
158263
+ return note.projectPath === scope.projectPath;
158264
+ }
158258
158265
  function buildStatusClause(status) {
158259
158266
  if (status === undefined) {
158260
158267
  return null;
@@ -158316,9 +158323,9 @@ function getPendingSmartNotes(db, projectPath) {
158316
158323
  function getReadySmartNotes(db, projectPath) {
158317
158324
  return getSmartNotes(db, projectPath, "ready");
158318
158325
  }
158319
- function updateNote(db, noteId, updates) {
158326
+ function updateNote(db, noteId, updates, scope) {
158320
158327
  const existing = getNoteById(db, noteId);
158321
- if (!existing) {
158328
+ if (!existing || !noteBelongsToScope(existing, scope)) {
158322
158329
  return null;
158323
158330
  }
158324
158331
  const now = Date.now();
@@ -158365,7 +158372,11 @@ function updateNote(db, noteId, updates) {
158365
158372
  const result = db.prepare(`UPDATE notes SET ${sets.join(", ")} WHERE id = ? RETURNING *`).get(...params);
158366
158373
  return isNoteRow(result) ? toNote(result) : null;
158367
158374
  }
158368
- function dismissNote(db, noteId) {
158375
+ function dismissNote(db, noteId, scope) {
158376
+ const existing = getNoteById(db, noteId);
158377
+ if (!existing || !noteBelongsToScope(existing, scope)) {
158378
+ return false;
158379
+ }
158369
158380
  const result = db.prepare("UPDATE notes SET status = 'dismissed', updated_at = ? WHERE id = ? AND status != 'dismissed'").run(Date.now(), noteId);
158370
158381
  return result.changes > 0;
158371
158382
  }
@@ -160473,7 +160484,7 @@ function getCurrentVersion(db) {
160473
160484
  const row = db.prepare("SELECT MAX(version) as version FROM schema_migrations").get();
160474
160485
  return row?.version ?? 0;
160475
160486
  }
160476
- function isSiblingMigrationConflict(error51, version2) {
160487
+ function isSiblingMigrationConflict(db, error51, version2) {
160477
160488
  if (!(error51 instanceof Error))
160478
160489
  return false;
160479
160490
  const code = error51.code;
@@ -160485,7 +160496,8 @@ function isSiblingMigrationConflict(error51, version2) {
160485
160496
  return false;
160486
160497
  if (!msg.toLowerCase().includes("version"))
160487
160498
  return false;
160488
- return true;
160499
+ const confirmed = db.prepare("SELECT 1 FROM schema_migrations WHERE version = ?").get(version2);
160500
+ return confirmed != null;
160489
160501
  }
160490
160502
  function runMigrations(db) {
160491
160503
  ensureMigrationsTable(db);
@@ -160506,7 +160518,7 @@ function runMigrations(db) {
160506
160518
  log(`[migrations] applied v${migration.version}: ${migration.description}`);
160507
160519
  migrationIndex += 1;
160508
160520
  } catch (error51) {
160509
- if (isSiblingMigrationConflict(error51, migration.version)) {
160521
+ if (isSiblingMigrationConflict(db, error51, migration.version)) {
160510
160522
  log(`[migrations] v${migration.version} already applied by sibling instance — resuming with re-read version`);
160511
160523
  const reReadVersion = getCurrentVersion(db);
160512
160524
  if (reReadVersion <= currentVersion) {
@@ -160912,6 +160924,16 @@ var init_migrations = __esm(async () => {
160912
160924
  WHERE auto_search_hint_decisions IS NULL
160913
160925
  `);
160914
160926
  }
160927
+ },
160928
+ {
160929
+ version: 18,
160930
+ description: "Add pending_pi_compaction_marker_state column for Pi deferred marker drain",
160931
+ up: (db) => {
160932
+ const cols = db.prepare("PRAGMA table_info(session_meta)").all();
160933
+ if (!cols.some((c) => c.name === "pending_pi_compaction_marker_state")) {
160934
+ db.exec("ALTER TABLE session_meta ADD COLUMN pending_pi_compaction_marker_state TEXT");
160935
+ }
160936
+ }
160915
160937
  }
160916
160938
  ];
160917
160939
  });
@@ -161443,6 +161465,11 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
161443
161465
  -- Excluded from healNullTextColumns. Readers filter IS NOT NULL AND
161444
161466
  -- != empty-string defensively. Plan v6 section 3.
161445
161467
  pending_compaction_marker_state TEXT,
161468
+ -- pending_pi_compaction_marker_state: intentionally NULLABLE without a
161469
+ -- default. Absence of a deferred Pi-native marker is SQL NULL; presence
161470
+ -- is a valid JSON blob written via setPendingPiCompactionMarkerState.
161471
+ -- Excluded from healNullTextColumns.
161472
+ pending_pi_compaction_marker_state TEXT,
161446
161473
  -- deferred_execute_state: intentionally NULLABLE without a default.
161447
161474
  -- Absence is SQL NULL; presence is a JSON blob written via
161448
161475
  -- setDeferredExecutePendingIfAbsent. Excluded from healNullTextColumns.
@@ -161539,6 +161566,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
161539
161566
  ensureColumn(db, "session_meta", "detected_context_limit", "INTEGER DEFAULT 0");
161540
161567
  ensureColumn(db, "session_meta", "needs_emergency_recovery", "INTEGER DEFAULT 0");
161541
161568
  ensureColumn(db, "session_meta", "pending_compaction_marker_state", "TEXT");
161569
+ ensureColumn(db, "session_meta", "pending_pi_compaction_marker_state", "TEXT");
161542
161570
  ensureColumn(db, "session_meta", "deferred_execute_state", "TEXT");
161543
161571
  ensureColumn(db, "tags", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
161544
161572
  ensureColumn(db, "pending_ops", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
@@ -162614,22 +162642,24 @@ function updateTagMessageId(db, sessionId, tagId, messageId) {
162614
162642
  getUpdateTagMessageIdStatement(db).run(messageId, sessionId, tagId);
162615
162643
  }
162616
162644
  function deleteTagsByMessageId(db, sessionId, messageId) {
162617
- const escapedMessageId = escapeLikePattern(messageId);
162618
- const textPartPattern = `${escapedMessageId}:p%`;
162619
- const filePartPattern = `${escapedMessageId}:file%`;
162620
- const messageScopedTags = getTagNumbersByMessageIdStatement(db).all(sessionId, messageId, textPartPattern, filePartPattern).filter(isTagNumberRow).map((row) => row.tag_number);
162621
- const ownerScopedTagNumbers = getOwnerScopedToolTagNumbers(db, sessionId, messageId);
162622
- if (messageScopedTags.length === 0 && ownerScopedTagNumbers.length === 0) {
162623
- return [];
162624
- }
162625
- if (messageScopedTags.length > 0) {
162626
- getDeleteTagsByMessageIdStatement(db).run(sessionId, messageId, textPartPattern, filePartPattern);
162627
- }
162628
- if (ownerScopedTagNumbers.length > 0) {
162629
- deleteToolTagsByOwner(db, sessionId, messageId);
162630
- }
162631
- const merged = new Set([...messageScopedTags, ...ownerScopedTagNumbers]);
162632
- return Array.from(merged).sort((a, b) => a - b);
162645
+ return db.transaction(() => {
162646
+ const escapedMessageId = escapeLikePattern(messageId);
162647
+ const textPartPattern = `${escapedMessageId}:p%`;
162648
+ const filePartPattern = `${escapedMessageId}:file%`;
162649
+ const messageScopedTags = getTagNumbersByMessageIdStatement(db).all(sessionId, messageId, textPartPattern, filePartPattern).filter(isTagNumberRow).map((row) => row.tag_number);
162650
+ const ownerScopedTagNumbers = getOwnerScopedToolTagNumbers(db, sessionId, messageId);
162651
+ if (messageScopedTags.length === 0 && ownerScopedTagNumbers.length === 0) {
162652
+ return [];
162653
+ }
162654
+ if (messageScopedTags.length > 0) {
162655
+ getDeleteTagsByMessageIdStatement(db).run(sessionId, messageId, textPartPattern, filePartPattern);
162656
+ }
162657
+ if (ownerScopedTagNumbers.length > 0) {
162658
+ deleteToolTagsByOwner(db, sessionId, messageId);
162659
+ }
162660
+ const merged = new Set([...messageScopedTags, ...ownerScopedTagNumbers]);
162661
+ return Array.from(merged).sort((a, b) => a - b);
162662
+ })();
162633
162663
  }
162634
162664
  function getOwnerScopedToolTagNumbers(db, sessionId, ownerMsgId) {
162635
162665
  let stmt = getOwnerScopedToolTagNumbersStatements.get(db);
@@ -164561,7 +164591,7 @@ async function executePartialRecompInternal(deps, range) {
164561
164591
  }
164562
164592
  deps.onCompartmentStatePublished?.(sessionId);
164563
164593
  const lastEnd = merged[merged.length - 1]?.endMessage ?? snapEnd;
164564
- if (deps.experimentalCompactionMarkers && lastEnd > 0) {
164594
+ if (lastEnd > 0) {
164565
164595
  updateCompactionMarkerAfterPublication(db, sessionId, lastEnd, deps.directory);
164566
164596
  const stalePending = getPendingCompactionMarkerState(db, sessionId);
164567
164597
  if (stalePending) {
@@ -165778,6 +165808,7 @@ async function runCompartmentAgent(deps) {
165778
165808
  const existingValidationError = validateStoredCompartments(priorCompartments);
165779
165809
  if (existingValidationError) {
165780
165810
  sessionLog(sessionId, `historian failure: source=existing-validation reason="${existingValidationError}"`);
165811
+ incrementHistorianFailure(db, sessionId, existingValidationError);
165781
165812
  await notifyHistorianIssue(`## Historian alert
165782
165813
 
165783
165814
  Historian skipped this session because existing stored compartments are invalid: ${existingValidationError}
@@ -165788,15 +165819,20 @@ No new compartments or facts were written. Rebuild or clear the broken compartme
165788
165819
  const offset = priorCompartments.length > 0 ? priorCompartments[priorCompartments.length - 1].endMessage + 1 : 1;
165789
165820
  const protectedTailStart = getProtectedTailStartOrdinal(sessionId);
165790
165821
  if (protectedTailStart <= offset) {
165822
+ sessionLog(sessionId, `historian no-op: protectedTailStart=${protectedTailStart} <= offset=${offset} — nothing to compact`);
165823
+ clearEmergencyRecovery(db, sessionId);
165791
165824
  return;
165792
165825
  }
165793
165826
  const chunk = readSessionChunk(sessionId, historianChunkTokens, offset, protectedTailStart);
165794
165827
  if (!chunk.text || chunk.messageCount === 0) {
165828
+ sessionLog(sessionId, `historian no-op: chunk empty after filtering (messageCount=${chunk.messageCount}, textLen=${chunk.text?.length ?? 0}) range=${offset}-${protectedTailStart - 1}`);
165829
+ clearEmergencyRecovery(db, sessionId);
165795
165830
  return;
165796
165831
  }
165797
165832
  const chunkCoverageError = validateChunkCoverage(chunk);
165798
165833
  if (chunkCoverageError) {
165799
165834
  sessionLog(sessionId, `historian failure: source=chunk-coverage reason="${chunkCoverageError}" chunkRange=${chunk.startIndex}-${chunk.endIndex}`);
165835
+ incrementHistorianFailure(db, sessionId, chunkCoverageError);
165800
165836
  await notifyHistorianIssue(`## Historian alert
165801
165837
 
165802
165838
  Historian skipped this session because the raw chunk could not be represented safely: ${chunkCoverageError}
@@ -165858,7 +165894,7 @@ Historian returned compartments that made no forward progress beyond raw message
165858
165894
  No new compartments or facts were written. Check the historian model/output and try again.`);
165859
165895
  return;
165860
165896
  }
165861
- const deferMarkerApplication = deps.preserveInjectionCacheUntilConsumed === true && deps.experimentalCompactionMarkers === true;
165897
+ const deferMarkerApplication = deps.preserveInjectionCacheUntilConsumed === true;
165862
165898
  const lastCompartmentEnd = lastNewEnd;
165863
165899
  const lastNewEndMessageId = newCompartments[newCompartments.length - 1]?.endMessageId;
165864
165900
  db.transaction(() => {
@@ -165883,12 +165919,10 @@ No new compartments or facts were written. Check the historian model/output and
165883
165919
  promoteSessionFactsToMemory(db, sessionId, resolveProjectIdentity(deps.directory), validatedPass.facts ?? []);
165884
165920
  }
165885
165921
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd);
165886
- if (deps.experimentalCompactionMarkers) {
165887
- if (deferMarkerApplication) {
165888
- deps.onDeferredMarkerPending?.(sessionId);
165889
- } else {
165890
- updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, sessionDirectory);
165891
- }
165922
+ if (deferMarkerApplication) {
165923
+ deps.onDeferredMarkerPending?.(sessionId);
165924
+ } else {
165925
+ updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd, sessionDirectory);
165892
165926
  }
165893
165927
  if (deps.historyBudgetTokens && deps.historyBudgetTokens > 0) {
165894
165928
  await runCompressionPassIfNeeded({
@@ -166031,7 +166065,7 @@ Found ${existingStaging.compartments.length} staged compartment(s) from ${existi
166031
166065
  if (lastCompartmentEnd2 > 0) {
166032
166066
  queueDropsForCompartmentalizedMessages(db, sessionId, lastCompartmentEnd2);
166033
166067
  }
166034
- if (deps.experimentalCompactionMarkers && lastCompartmentEnd2 > 0) {
166068
+ if (lastCompartmentEnd2 > 0) {
166035
166069
  updateCompactionMarkerAfterPublication(db, sessionId, lastCompartmentEnd2, deps.directory);
166036
166070
  const stalePending = getPendingCompactionMarkerState(db, sessionId);
166037
166071
  if (stalePending) {
@@ -166414,6 +166448,7 @@ import { homedir as homedir2 } from "node:os";
166414
166448
  import { join } from "node:path";
166415
166449
 
166416
166450
  // src/config/variable.ts
166451
+ init_jsonc_parser();
166417
166452
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
166418
166453
  import { homedir } from "node:os";
166419
166454
  import { dirname, isAbsolute, resolve } from "node:path";
@@ -166422,6 +166457,21 @@ var FILE_PATTERN = /\{file:([^}]+)\}/g;
166422
166457
  function substituteConfigVariables(input) {
166423
166458
  const warnings = [];
166424
166459
  let text = input.text;
166460
+ if (input.isProjectConfig) {
166461
+ const hasEnvTokens = ENV_PATTERN.test(text);
166462
+ const hasFileTokens = FILE_PATTERN.test(text);
166463
+ ENV_PATTERN.lastIndex = 0;
166464
+ FILE_PATTERN.lastIndex = 0;
166465
+ if (hasEnvTokens || hasFileTokens) {
166466
+ const tokenTypes = [
166467
+ hasEnvTokens ? "{env:}" : undefined,
166468
+ hasFileTokens ? "{file:}" : undefined
166469
+ ].filter(Boolean).join(" and ");
166470
+ warnings.push(`Project-level config no longer supports ${tokenTypes} tokens for security reasons; leaving tokens literal. Move secret expansion to user-level config.`);
166471
+ }
166472
+ return { text, warnings };
166473
+ }
166474
+ text = stripJsonComments(text);
166425
166475
  text = text.replace(ENV_PATTERN, (_, rawName) => {
166426
166476
  const varName = rawName.trim();
166427
166477
  const value = varName ? process.env[varName] : undefined;
@@ -166429,7 +166479,7 @@ function substituteConfigVariables(input) {
166429
166479
  warnings.push(`Environment variable ${varName} is not set (referenced via {env:${varName}}); using empty string`);
166430
166480
  return "";
166431
166481
  }
166432
- return value;
166482
+ return JSON.stringify(value).slice(1, -1);
166433
166483
  });
166434
166484
  const fileMatches = Array.from(text.matchAll(FILE_PATTERN));
166435
166485
  if (fileMatches.length === 0) {
@@ -166484,13 +166534,17 @@ function getUserConfigBasePath() {
166484
166534
  function getProjectConfigBasePath(directory) {
166485
166535
  return join(directory, ".opencode", CONFIG_FILE_BASENAME);
166486
166536
  }
166487
- function loadConfigFile(configPath) {
166537
+ function loadConfigFile(configPath, isProjectConfig = false) {
166488
166538
  try {
166489
166539
  if (!existsSync3(configPath)) {
166490
166540
  return null;
166491
166541
  }
166492
166542
  const rawText = readFileSync3(configPath, "utf-8");
166493
- const substituted = substituteConfigVariables({ text: rawText, configPath });
166543
+ const substituted = substituteConfigVariables({
166544
+ text: rawText,
166545
+ configPath,
166546
+ isProjectConfig
166547
+ });
166494
166548
  return {
166495
166549
  config: parseJsonc(substituted.text),
166496
166550
  warnings: substituted.warnings.map((w) => `${configPath}: ${w}`)
@@ -166518,7 +166572,11 @@ function loadConfigFileDetailed(configPath, source) {
166518
166572
  };
166519
166573
  }
166520
166574
  try {
166521
- const substituted = substituteConfigVariables({ text: rawText, configPath });
166575
+ const substituted = substituteConfigVariables({
166576
+ text: rawText,
166577
+ configPath,
166578
+ isProjectConfig: source === "project"
166579
+ });
166522
166580
  return {
166523
166581
  config: parseJsonc(substituted.text),
166524
166582
  warnings: substituted.warnings.map((w) => `${configPath}: ${w}`),
@@ -166690,7 +166748,7 @@ function loadPluginConfig(directory) {
166690
166748
  const dotOpenCodeDetected = detectConfigFile(getProjectConfigBasePath(directory));
166691
166749
  const projectDetected = rootDetected.format !== "none" ? rootDetected : dotOpenCodeDetected;
166692
166750
  const userLoaded = userDetected.format === "none" ? null : loadConfigFile(userDetected.path);
166693
- const projectLoaded = projectDetected.format === "none" ? null : loadConfigFile(projectDetected.path);
166751
+ const projectLoaded = projectDetected.format === "none" ? null : loadConfigFile(projectDetected.path, true);
166694
166752
  const allWarnings = [];
166695
166753
  let mergedRaw = {};
166696
166754
  if (userLoaded) {
@@ -168219,6 +168277,43 @@ function normalizeProjectRelativePath(projectPath, filePath) {
168219
168277
  return null;
168220
168278
  return rel;
168221
168279
  }
168280
+ function isDocumentationOrMetaFile(rel) {
168281
+ const lower = rel.toLowerCase();
168282
+ if (lower.endsWith(".md") || lower.endsWith(".mdx") || lower.endsWith(".rst"))
168283
+ return true;
168284
+ if (lower.endsWith(".txt"))
168285
+ return true;
168286
+ const base = lower.split("/").pop() ?? "";
168287
+ const baseNoExt = base.replace(/\.[^.]+$/, "");
168288
+ const META_BASENAMES = new Set([
168289
+ "license",
168290
+ "licence",
168291
+ "notice",
168292
+ "copying",
168293
+ "authors",
168294
+ "contributors",
168295
+ "changelog",
168296
+ "changes",
168297
+ "history",
168298
+ "readme",
168299
+ "contributing",
168300
+ "code_of_conduct",
168301
+ "security",
168302
+ "support",
168303
+ "maintainers",
168304
+ "governance",
168305
+ "package-lock",
168306
+ "bun.lock",
168307
+ "bun.lockb",
168308
+ "yarn.lock",
168309
+ "pnpm-lock",
168310
+ "cargo.lock",
168311
+ "uv.lock",
168312
+ "poetry.lock",
168313
+ "gemfile.lock"
168314
+ ]);
168315
+ return META_BASENAMES.has(base) || META_BASENAMES.has(baseNoExt);
168316
+ }
168222
168317
  function primarySessionIds(db) {
168223
168318
  try {
168224
168319
  const rows = db.prepare("SELECT session_id AS sessionId FROM session_meta WHERE is_subagent = 0").all();
@@ -168250,6 +168345,8 @@ function collectKeyFileCandidates(args) {
168250
168345
  const rel = normalizeProjectRelativePath(args.projectPath, row.file_path);
168251
168346
  if (!rel)
168252
168347
  continue;
168348
+ if (isDocumentationOrMetaFile(rel))
168349
+ continue;
168253
168350
  const timestamp = toMs(row.time_created);
168254
168351
  const candidate = byPath.get(rel) ?? {
168255
168352
  path: rel,
@@ -168386,7 +168483,8 @@ Rules:
168386
168483
  - Total approx_token_estimate must be < ${args.config.token_budget}.
168387
168484
  - path must be relative, no .., no absolute paths, no symlinks escaping project root.
168388
168485
  - path must be unique case-sensitively and case-insensitively.
168389
- - content must be plain text; no XML tags inside.`;
168486
+ - content must be plain text; no XML tags inside.
168487
+ - DO NOT pick prose documentation (README.md, CONTRIBUTING.md, CHANGELOG, LICENSE, *.md/*.mdx/*.rst/*.txt) or lockfiles. Key files are project SOURCE the agent needs repeated orientation context on, not reference docs. Project docs are surfaced through a separate injection path.`;
168390
168488
  }
168391
168489
  function validateLlmOutput(raw, config2, projectPath) {
168392
168490
  let obj;
@@ -169278,6 +169376,13 @@ async function evaluateSmartNotes(args) {
169278
169376
  For each note below, determine whether its surface condition has been met.
169279
169377
  You have access to tools like GitHub CLI (gh), web search, and the local codebase to verify conditions.
169280
169378
 
169379
+ You DO NOT have access to:
169380
+ - Any conversation between the user and the original agent that wrote the note
169381
+ - The state of any active session, including whether messages have been sent
169382
+ - The current task, mood, or intent of the human user
169383
+
169384
+ If a condition references conversation context the user is having ("When the user mentions X", "When they ask to do Y", "When we revisit Z", "When relevant to current discussion", etc.), it is UNEVALUATABLE — skip it (do not include in results) so the note stays pending. These are misuse cases that should never have been written as smart notes; leaving them pending is the correct outcome, the dreamer's archive-stale task will eventually retire them.
169385
+
169281
169386
  ## Pending Smart Notes
169282
169387
 
169283
169388
  ${noteDescriptions}
@@ -169286,7 +169391,8 @@ ${noteDescriptions}
169286
169391
 
169287
169392
  1. Check each condition using the tools available to you.
169288
169393
  2. Be conservative — only mark a condition as met when you have clear evidence.
169289
- 3. Respond with a JSON array of results:
169394
+ 3. Skip conditions that depend on session/conversation context you cannot observe — do not invent a "false" verdict for them, just omit them from your response.
169395
+ 4. Respond with a JSON array of results:
169290
169396
 
169291
169397
  \`\`\`json
169292
169398
  [
@@ -169294,7 +169400,7 @@ ${noteDescriptions}
169294
169400
  ]
169295
169401
  \`\`\`
169296
169402
 
169297
- Only include notes whose conditions you could definitively evaluate. Skip notes where you cannot determine the status (they will be re-evaluated next run).`;
169403
+ Only include notes whose conditions you could definitively evaluate against external signals. Skip notes where you cannot determine the status (they will be re-evaluated next run, or eventually archived as stale).`;
169298
169404
  const taskStartedAt = Date.now();
169299
169405
  let agentSessionId = null;
169300
169406
  const abortController = new AbortController;
@@ -170761,8 +170867,7 @@ var FORCE_MATERIALIZE_PERCENTAGE = 85;
170761
170867
  function getProactiveCompartmentTriggerPercentage(executeThresholdPercentage) {
170762
170868
  return Math.max(0, executeThresholdPercentage - PROACTIVE_TRIGGER_OFFSET_PERCENTAGE);
170763
170869
  }
170764
- function estimateProjectedPostDropPercentage(db, sessionId, usage, autoDropToolAge, protectedTags, clearReasoningAge, clearedReasoningThroughTag, dropToolStructure = true) {
170765
- const activeTags = getTagsBySession(db, sessionId).filter((tag) => tag.status === "active");
170870
+ function estimateProjectedPostDropPercentage(db, sessionId, usage, activeTags, autoDropToolAge, protectedTags, clearReasoningAge, clearedReasoningThroughTag, dropToolStructure = true) {
170766
170871
  const totalActiveBytes = activeTags.reduce((sum, tag) => sum + tag.byteSize + tag.reasoningByteSize, 0);
170767
170872
  if (totalActiveBytes === 0)
170768
170873
  return null;
@@ -170849,15 +170954,24 @@ function getUnsummarizedTailInfo(db, sessionId, triggerBudget) {
170849
170954
  }
170850
170955
  });
170851
170956
  }
170852
- function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPercentage, executeThresholdPercentage, triggerBudget, autoDropToolAge, protectedTagCount, clearReasoningAge, dropToolStructure = true, commitClusterTrigger) {
170957
+ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPercentage, executeThresholdPercentage, triggerBudget, autoDropToolAge, protectedTagCount, clearReasoningAge, dropToolStructure = true, commitClusterTrigger, preloadedActiveTags) {
170853
170958
  if (sessionMeta.compartmentInProgress) {
170959
+ sessionLog(sessionId, `compartment trigger: skipped — historian already in progress (usage=${usage.percentage.toFixed(1)}%)`);
170854
170960
  return { shouldFire: false };
170855
170961
  }
170856
170962
  const tailInfo = getUnsummarizedTailInfo(db, sessionId, triggerBudget);
170857
170963
  if (!tailInfo.hasNewRawHistory) {
170964
+ try {
170965
+ const lastCompartmentEnd = getLastCompartmentEndMessage(db, sessionId);
170966
+ const rawMessageCount = getRawSessionMessageCount(sessionId);
170967
+ const protectedTailStart = getProtectedTailStartOrdinal(sessionId);
170968
+ sessionLog(sessionId, `compartment trigger: skipped — no new raw history (usage=${usage.percentage.toFixed(1)}% nextStartOrdinal=${tailInfo.nextStartOrdinal} lastCompartmentEnd=${lastCompartmentEnd} rawMessageCount=${rawMessageCount} protectedTailStart=${protectedTailStart})`);
170969
+ } catch (error51) {
170970
+ sessionLog(sessionId, `compartment trigger: skipped — no new raw history (usage=${usage.percentage.toFixed(1)}% nextStartOrdinal=${tailInfo.nextStartOrdinal} diagnostic-collection-failed: ${error51 instanceof Error ? error51.message : String(error51)})`);
170971
+ }
170858
170972
  return { shouldFire: false };
170859
170973
  }
170860
- const projectedPostDropPercentage = estimateProjectedPostDropPercentage(db, sessionId, usage, autoDropToolAge, protectedTagCount, clearReasoningAge, sessionMeta.clearedReasoningThroughTag, dropToolStructure);
170974
+ const projectedPostDropPercentage = estimateProjectedPostDropPercentage(db, sessionId, usage, preloadedActiveTags ?? getActiveTagsBySession(db, sessionId), autoDropToolAge, protectedTagCount, clearReasoningAge, sessionMeta.clearedReasoningThroughTag, dropToolStructure);
170861
170975
  const relativePostDropTarget = executeThresholdPercentage * POST_DROP_TARGET_RATIO;
170862
170976
  if (usage.percentage >= FORCE_COMPARTMENT_PERCENTAGE) {
170863
170977
  if (projectedPostDropPercentage !== null && projectedPostDropPercentage <= relativePostDropTarget) {
@@ -170879,6 +170993,7 @@ function checkCompartmentTrigger(db, sessionId, sessionMeta, usage, _previousPer
170879
170993
  }
170880
170994
  const proactiveTriggerPercentage = getProactiveCompartmentTriggerPercentage(executeThresholdPercentage);
170881
170995
  if (usage.percentage < proactiveTriggerPercentage) {
170996
+ sessionLog(sessionId, `compartment trigger: not firing at ${usage.percentage.toFixed(1)}% — below proactive floor (${proactiveTriggerPercentage}%)`);
170882
170997
  return { shouldFire: false };
170883
170998
  }
170884
170999
  if (projectedPostDropPercentage !== null && projectedPostDropPercentage <= relativePostDropTarget) {
@@ -171978,6 +172093,8 @@ function stripSystemInjectedMessages(messages, protectedTailStart, providerID) {
171978
172093
  const msg = messages[i];
171979
172094
  if (msg.parts.length === 0)
171980
172095
  continue;
172096
+ if (msg.info.role === "user")
172097
+ continue;
171981
172098
  if (msg.parts.length === 1 && isSentinel(msg.parts[0]))
171982
172099
  continue;
171983
172100
  let hasContentPart = false;
@@ -172435,7 +172552,6 @@ async function runCompartmentPhase(args) {
172435
172552
  fallbackModelId: args.fallbackModelId,
172436
172553
  ensureProjectRegistered: args.ensureProjectRegistered,
172437
172554
  getNotificationParams: args.getNotificationParams,
172438
- experimentalCompactionMarkers: args.experimentalCompactionMarkers,
172439
172555
  experimentalUserMemories: args.experimentalUserMemories,
172440
172556
  historianTwoPass: args.historianTwoPass,
172441
172557
  compressorMinCompartmentRatio: args.compressorMinCompartmentRatio,
@@ -172465,7 +172581,6 @@ async function runCompartmentPhase(args) {
172465
172581
  fallbackModelId: args.fallbackModelId,
172466
172582
  ensureProjectRegistered: args.ensureProjectRegistered,
172467
172583
  getNotificationParams: args.getNotificationParams,
172468
- experimentalCompactionMarkers: args.experimentalCompactionMarkers,
172469
172584
  experimentalUserMemories: args.experimentalUserMemories,
172470
172585
  historianTwoPass: args.historianTwoPass,
172471
172586
  compressorMinCompartmentRatio: args.compressorMinCompartmentRatio,
@@ -173818,7 +173933,7 @@ function stripNestedSystemReminders(text) {
173818
173933
  return result;
173819
173934
  }
173820
173935
  function extractUserPromptText(message) {
173821
- return stripNestedSystemReminders(collectUserPromptParts(message)).replace(/§\d+§\s*/g, "").replace(/<!--\s*\+[\d\s.hmdw]+\s*-->/g, "").replace(/<!--\s*OMO_INTERNAL_INITIATOR[\s\S]*?-->/g, "").replace(/<!--\s*ALFONSO_INTERNAL_INITIATOR[\s\S]*?-->/g, "").replace(/<ctx-search-hint>[\s\S]*?<\/ctx-search-hint>/g, "").replace(/<ctx-search-auto>[\s\S]*?<\/ctx-search-auto>/g, "").replace(/<instruction[^>]*>[\s\S]*?<\/instruction>/g, "").replace(/<sidekick-augmentation>[\s\S]*?<\/sidekick-augmentation>/g, "").replace(/[ \t]+\n/g, `
173936
+ return stripNestedSystemReminders(collectUserPromptParts(message)).replace(/<!--[\s\S]*?-->/g, "").replace(/<\/?[a-zA-Z][^<>]*>/g, "").replace(/§\d+§\s*/g, "").replace(/[ \t]+\n/g, `
173822
173937
  `).replace(/\n{3,}/g, `
173823
173938
 
173824
173939
  `).trim();
@@ -174378,12 +174493,6 @@ async function runPostTransformPhase(args) {
174378
174493
  didMutateFromPendingOperations = true;
174379
174494
  }
174380
174495
  logTransformTiming(args.sessionId, "applyHeuristicCleanup", t5, `droppedTools=${cleanup.droppedTools} deduplicatedTools=${cleanup.deduplicatedTools} droppedInjections=${cleanup.droppedInjections} compressedTextTags=${cleanup.compressedTextTags}`);
174381
- if (args.watermark > 0) {
174382
- const t6 = performance.now();
174383
- truncateErroredTools(args.messages, args.watermark, args.messageTagNumbers);
174384
- stripProcessedImages(args.messages, args.watermark, args.messageTagNumbers);
174385
- logTransformTiming(args.sessionId, "watermarkCleanup", t6);
174386
- }
174387
174496
  const t7 = performance.now();
174388
174497
  const clearedReasoning = clearOldReasoning(args.messages, args.reasoningByMessage, args.messageTagNumbers, args.clearReasoningAge);
174389
174498
  stripClearedReasoning(args.messages);
@@ -174427,6 +174536,12 @@ async function runPostTransformPhase(args) {
174427
174536
  deferredMaterializedSuccessfully = true;
174428
174537
  heuristicsRanSuccessfully = true;
174429
174538
  }
174539
+ if (args.watermark > 0) {
174540
+ const tWatermarkCleanup = performance.now();
174541
+ truncateErroredTools(args.messages, args.watermark, args.messageTagNumbers);
174542
+ stripProcessedImages(args.messages, args.watermark, args.messageTagNumbers);
174543
+ logTransformTiming(args.sessionId, "watermarkCleanup", tWatermarkCleanup);
174544
+ }
174430
174545
  if (shouldApplyPendingOps) {
174431
174546
  pendingOpsRanSuccessfully = true;
174432
174547
  }
@@ -174927,7 +175042,6 @@ function createTransform(deps) {
174927
175042
  directory: compartmentDirectory,
174928
175043
  fallbackModelId,
174929
175044
  getNotificationParams: () => notificationParams,
174930
- experimentalCompactionMarkers: deps.experimentalCompactionMarkers,
174931
175045
  experimentalUserMemories: deps.experimentalUserMemories,
174932
175046
  experimentalTemporalAwareness: deps.experimentalTemporalAwareness,
174933
175047
  historianTwoPass: deps.historianTwoPass,
@@ -175111,7 +175225,6 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
175111
175225
  suppressBackgroundCompressionThisPass: historyBustThisPass,
175112
175226
  deferredHistoryRefreshSessions,
175113
175227
  skipAwaitForThisPass: skipCompartmentAwaitForThisPass,
175114
- experimentalCompactionMarkers: deps.experimentalCompactionMarkers,
175115
175228
  experimentalUserMemories: deps.experimentalUserMemories,
175116
175229
  experimentalTemporalAwareness: deps.experimentalTemporalAwareness,
175117
175230
  historianTwoPass: deps.historianTwoPass,
@@ -175852,7 +175965,8 @@ function applyStickySnapshotCache(sessionId, fresh) {
175852
175965
  cache.delete(sessionId);
175853
175966
  return fresh;
175854
175967
  }
175855
- if (!hasInFlightEvidence(fresh)) {
175968
+ const stateSurvived = fresh.compartmentCount >= cached2.snapshot.compartmentCount && fresh.memoryCount >= cached2.snapshot.memoryCount;
175969
+ if (!hasInFlightEvidence(fresh) && !stateSurvived) {
175856
175970
  cache.delete(sessionId);
175857
175971
  return fresh;
175858
175972
  }
@@ -176493,12 +176607,10 @@ function createMagicContextHook(deps) {
176493
176607
  }
176494
176608
  const projectPath = resolveProjectIdentity(deps.directory);
176495
176609
  registerDreamProjectDirectory(projectPath, deps.directory);
176496
- if (deps.config.compaction_markers !== false) {
176497
- try {
176498
- checkCompactionMarkerConsistency(db);
176499
- } catch (error51) {
176500
- log("[magic-context] startup compaction-marker consistency check failed:", error51);
176501
- }
176610
+ try {
176611
+ checkCompactionMarkerConsistency(db);
176612
+ } catch (error51) {
176613
+ log("[magic-context] startup compaction-marker consistency check failed:", error51);
176502
176614
  }
176503
176615
  let lastScheduleCheckMs = 0;
176504
176616
  const getHistorianChunkTokens = () => deriveHistorianChunkTokens(resolveHistorianContextLimit(deps.config.historian?.model));
@@ -176589,7 +176701,6 @@ function createMagicContextHook(deps) {
176589
176701
  return model ? `${model.providerID}/${model.modelID}` : undefined;
176590
176702
  },
176591
176703
  projectPath,
176592
- experimentalCompactionMarkers: deps.config.compaction_markers,
176593
176704
  experimentalUserMemories: deps.config.dreamer?.user_memories?.enabled,
176594
176705
  experimentalTemporalAwareness: deps.config.experimental?.temporal_awareness === true,
176595
176706
  historianTwoPass: deps.config.historian?.two_pass === true,
@@ -176854,7 +176965,6 @@ function createSessionHooks(args) {
176854
176965
  sidekick: pluginConfig.sidekick,
176855
176966
  dreamer: pluginConfig.dreamer,
176856
176967
  commit_cluster_trigger: pluginConfig.commit_cluster_trigger,
176857
- compaction_markers: pluginConfig.compaction_markers,
176858
176968
  system_prompt_injection: pluginConfig.system_prompt_injection,
176859
176969
  compressor: pluginConfig.compressor,
176860
176970
  experimental: pluginConfig.experimental
@@ -177885,9 +177995,26 @@ Actions:
177885
177995
  - \`dismiss\`: Dismiss a note by \`note_id\`.
177886
177996
  - \`update\`: Update a note by \`note_id\`.
177887
177997
 
177888
- **Smart Notes**: When \`surface_condition\` is provided with \`write\`, the note becomes a project-scoped smart note.
177889
- The dreamer evaluates smart note conditions during nightly runs and surfaces them when conditions are met.
177890
- Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 is merged in this repo")\`
177998
+ **Smart Notes**: When \`surface_condition\` is provided with \`write\`, the note becomes a project-scoped smart note. A separate background process (the dreamer) periodically checks the condition using ONLY external, verifiable signals: GitHub state via \`gh\` CLI, web pages, files on disk, git history, etc. The dreamer cannot read your current conversation, cannot detect when the user says something, and has no memory of context that lives only in this session.
177999
+
178000
+ Write a smart note ONLY when the surface_condition is something an external agent with read-only tools can definitively check:
178001
+
178002
+ ✓ GOOD conditions (externally verifiable):
178003
+ - "When PR #42 in cortexkit/magic-context is merged"
178004
+ - "When the file packages/plugin/src/foo.ts contains a function named bar"
178005
+ - "When the latest release tag is >= v0.22.0"
178006
+ - "When the GitHub Actions workflow runs/123 succeeds"
178007
+
178008
+ ✗ BAD conditions (require knowing this session's context):
178009
+ - "When the user mentions the worktree system has landed" → dreamer cannot see user messages
178010
+ - "When they ask to re-run the audit fixes" → dreamer cannot see future requests
178011
+ - "When we revisit this code path" → no observable signal
178012
+ - "When relevant to the current discussion" → no observable signal
178013
+ - "After we finish the current refactor" → no externally checkable boundary
178014
+
178015
+ If you want context that surfaces based on what's happening in your session, use a regular note (omit surface_condition) — those show up on natural work boundaries within this session. If you want a reminder tied to your future work without a clean external trigger, just write a regular note describing what to do; you'll see it when you read notes later.
178016
+
178017
+ Example: \`ctx_note(action="write", content="Implement X because Y", surface_condition="When PR #42 in cortexkit/magic-context is merged")\`
177891
178018
 
177892
178019
  Historian reads these notes, deduplicates them, and rewrites the remaining useful notes over time.`;
177893
178020
  // src/tools/ctx-note/tools.ts
@@ -177965,7 +178092,7 @@ function createCtxNoteTool(deps) {
177965
178092
  args: {
177966
178093
  action: tool3.schema.enum(["write", "read", "dismiss", "update"]).optional().describe("Operation to perform. Defaults to 'write' when content is provided, otherwise 'read'."),
177967
178094
  content: tool3.schema.string().optional().describe("Note text to store when action is 'write'."),
177968
- surface_condition: tool3.schema.string().optional().describe("Open-ended condition for smart notes. When provided, creates a project-scoped smart note that the dreamer evaluates nightly. The note surfaces when the condition is met."),
178095
+ surface_condition: tool3.schema.string().optional().describe("Externally verifiable condition for smart notes. A separate background agent (dreamer) checks this using gh CLI, web fetches, file reads, git, etc. NOT your conversation history. Use only for things like GitHub PR/issue state, release tags, file contents, or workflow runs. DO NOT use for 'when the user mentions X' / 'when we revisit Y' / 'when relevant to current task' — dreamer has no access to session context. For session-relative reminders, omit this and write a regular note."),
177969
178096
  filter: tool3.schema.enum(["all", "active", "pending", "ready", "dismissed"]).optional().describe("Optional read filter. Defaults to active session notes + ready smart notes. Use 'all' to inspect every status or 'pending' to inspect unsurfaced smart notes."),
177970
178097
  note_id: tool3.schema.number().optional().describe("Note ID (required for 'dismiss' and 'update' actions).")
177971
178098
  },
@@ -178003,8 +178130,14 @@ function createCtxNoteTool(deps) {
178003
178130
  if (typeof noteId !== "number") {
178004
178131
  return "Error: 'note_id' is required when action is 'dismiss'.";
178005
178132
  }
178006
- const dismissed = dismissNote(deps.db, noteId);
178007
- return dismissed ? `Note #${noteId} dismissed.` : `Note #${noteId} not found or already dismissed.`;
178133
+ if (!projectIdentity) {
178134
+ return "Error: Could not resolve project identity for note dismiss.";
178135
+ }
178136
+ const dismissed = dismissNote(deps.db, noteId, {
178137
+ projectPath: projectIdentity,
178138
+ sessionId
178139
+ });
178140
+ return dismissed ? `Note #${noteId} dismissed.` : `Error: Note #${noteId} not found in your session/project or already dismissed.`;
178008
178141
  }
178009
178142
  if (action === "update") {
178010
178143
  const noteId = args.note_id;
@@ -178019,9 +178152,15 @@ function createCtxNoteTool(deps) {
178019
178152
  if (!updates.content && !updates.surfaceCondition) {
178020
178153
  return "Error: Provide 'content' and/or 'surface_condition' to update.";
178021
178154
  }
178022
- const updated = updateNote(deps.db, noteId, updates);
178155
+ if (!projectIdentity) {
178156
+ return "Error: Could not resolve project identity for note update.";
178157
+ }
178158
+ const updated = updateNote(deps.db, noteId, updates, {
178159
+ projectPath: projectIdentity,
178160
+ sessionId
178161
+ });
178023
178162
  if (!updated) {
178024
- return `Note #${noteId} not found or has no compatible fields to update.`;
178163
+ return `Error: Note #${noteId} not found in your session/project or has no compatible fields to update.`;
178025
178164
  }
178026
178165
  const parts = [];
178027
178166
  if (updates.content)