claude-memory-layer 2.4.16 → 2.4.17

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.
package/README.md CHANGED
@@ -639,6 +639,8 @@ claude-memory-layer mongo-sync --watch --interval 30000 --process-after-sync --p
639
639
 
640
640
  서버마다 프로젝트 경로가 달라도 됩니다. Pull된 이벤트는 이 서버의 프로젝트(`--project` 경로의 hash)로 다시 귀속되어 project-scoped 검색에 나오며, 원래 서버의 project scope와 hostname은 `metadata.mongoSync`에 남습니다. 같은 프로젝트를 모으려면 모든 서버에서 `--mongo-project`(또는 `CLAUDE_MEMORY_MONGO_PROJECT`)를 같은 값으로 지정하세요. 기본값은 경로의 마지막 폴더 이름입니다.
641
641
 
642
+ 여러 서버가 동시에 push하면 seq를 먼저 예약한 서버의 문서가 늦게 들어올 수 있어, pull은 직전 pull보다 30분 앞부터 들어온 문서를 커서 아래까지 다시 읽습니다(이미 가진 이벤트는 건너뜀). 서버 간 시계 차이도 이 범위 안이어야 합니다.
643
+
642
644
  ### memU-inspired Retrieval 사용 예시
643
645
 
644
646
  아래 예시는 SDK/서비스 레벨에서 `retrieveMemories()` 호출 시 적용되는 옵션입니다.
package/dist/cli/index.js CHANGED
@@ -38,7 +38,7 @@ function emptyLatencyAggregate() {
38
38
  return { count: 0, failedCount: 0, totalMs: 0, maxMs: 0 };
39
39
  }
40
40
  function runtimeVersion() {
41
- const value = "2.4.16"?.trim();
41
+ const value = "2.4.17"?.trim();
42
42
  return sanitizeRuntimeVersion(value || "development");
43
43
  }
44
44
  function sanitizeRuntimeVersion(value) {
@@ -32157,6 +32157,7 @@ function localizePulledMetadata(doc, localProject) {
32157
32157
  };
32158
32158
  return metadata;
32159
32159
  }
32160
+ var LATE_INSERT_WINDOW_MS = 30 * 60 * 1e3;
32160
32161
  function parseIntOrZero(value) {
32161
32162
  if (!value) return 0;
32162
32163
  const n = parseInt(value, 10);
@@ -32283,6 +32284,7 @@ var MongoSyncWorker = class {
32283
32284
  await this.events.createIndex({ projectKey: 1, seq: 1 }, { unique: true });
32284
32285
  await this.events.createIndex({ projectKey: 1, eventId: 1 }, { unique: true });
32285
32286
  await this.events.createIndex({ projectKey: 1, dedupeKey: 1 });
32287
+ await this.events.createIndex({ projectKey: 1, insertedAt: 1 });
32286
32288
  } catch (err) {
32287
32289
  console.warn("[MongoSyncWorker] Failed to ensure indexes (continuing):", err);
32288
32290
  }
@@ -32365,39 +32367,60 @@ var MongoSyncWorker = class {
32365
32367
  }
32366
32368
  return pushed;
32367
32369
  }
32370
+ /**
32371
+ * A pusher reserves its seq range before it inserts, so a document can appear below a cursor that another machine's
32372
+ * faster insert already moved past. Each pull therefore re-reads what was inserted shortly before the previous pull
32373
+ * started — import skips the events it already has — and then reads past the cursor as before. The window also
32374
+ * absorbs clock differences between machines (insertedAt is the pusher's clock).
32375
+ */
32368
32376
  async pullEvents() {
32369
32377
  if (!this.events) throw new Error("Mongo not connected");
32370
32378
  const position = await this.sqliteStore.getSyncPosition(this.pullTargetName());
32371
32379
  let lastSeq = parseIntOrZero(position.lastEventId);
32380
+ const startedAt = /* @__PURE__ */ new Date();
32381
+ const previousPullAt = position.lastTimestamp ? new Date(position.lastTimestamp) : null;
32372
32382
  let pulled = 0;
32383
+ if (lastSeq > 0 && previousPullAt && !Number.isNaN(previousPullAt.getTime())) {
32384
+ const since = new Date(previousPullAt.getTime() - LATE_INSERT_WINDOW_MS);
32385
+ let afterSeq = 0;
32386
+ while (true) {
32387
+ const docs = await this.events.find(
32388
+ { projectKey: this.config.projectKey, seq: { $gt: afterSeq, $lte: lastSeq }, insertedAt: { $gte: since } },
32389
+ { sort: { seq: 1 }, limit: this.config.batchSize }
32390
+ ).toArray();
32391
+ if (docs.length === 0) break;
32392
+ pulled += await this.importPulled(docs);
32393
+ afterSeq = docs[docs.length - 1].seq;
32394
+ if (docs.length < this.config.batchSize) break;
32395
+ }
32396
+ }
32373
32397
  while (true) {
32374
32398
  const docs = await this.events.find(
32375
32399
  { projectKey: this.config.projectKey, seq: { $gt: lastSeq } },
32376
32400
  { sort: { seq: 1 }, limit: this.config.batchSize }
32377
32401
  ).toArray();
32378
32402
  if (docs.length === 0) break;
32379
- const events = docs.map((d) => ({
32380
- id: d.eventId,
32381
- eventType: d.eventType,
32382
- sessionId: d.sessionId,
32383
- timestamp: d.timestamp instanceof Date ? d.timestamp : new Date(d.timestamp),
32384
- content: d.content,
32385
- canonicalKey: d.canonicalKey,
32386
- dedupeKey: d.dedupeKey,
32387
- metadata: this.config.localProject ? localizePulledMetadata(d, this.config.localProject) : d.metadata ?? void 0
32388
- }));
32389
- const result = await this.sqliteStore.importEvents(events);
32390
- pulled += result.inserted;
32403
+ pulled += await this.importPulled(docs);
32391
32404
  lastSeq = docs[docs.length - 1].seq;
32392
- await this.sqliteStore.updateSyncPosition(
32393
- this.pullTargetName(),
32394
- String(lastSeq),
32395
- (/* @__PURE__ */ new Date()).toISOString()
32396
- );
32405
+ await this.sqliteStore.updateSyncPosition(this.pullTargetName(), String(lastSeq), startedAt.toISOString());
32397
32406
  if (docs.length < this.config.batchSize) break;
32398
32407
  }
32408
+ await this.sqliteStore.updateSyncPosition(this.pullTargetName(), String(lastSeq), startedAt.toISOString());
32399
32409
  return pulled;
32400
32410
  }
32411
+ async importPulled(docs) {
32412
+ const events = docs.map((d) => ({
32413
+ id: d.eventId,
32414
+ eventType: d.eventType,
32415
+ sessionId: d.sessionId,
32416
+ timestamp: d.timestamp instanceof Date ? d.timestamp : new Date(d.timestamp),
32417
+ content: d.content,
32418
+ canonicalKey: d.canonicalKey,
32419
+ dedupeKey: d.dedupeKey,
32420
+ metadata: this.config.localProject ? localizePulledMetadata(d, this.config.localProject) : d.metadata ?? void 0
32421
+ }));
32422
+ return (await this.sqliteStore.importEvents(events)).inserted;
32423
+ }
32401
32424
  };
32402
32425
 
32403
32426
  // src/apps/cli/index.ts
@@ -37031,7 +37054,7 @@ async function runOperationCli(action, label) {
37031
37054
  }
37032
37055
  }
37033
37056
  var program = new Command();
37034
- program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("2.4.16");
37057
+ program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("2.4.17");
37035
37058
  program.command("market-context").description("Fetch read-only DART/FRED/Finnhub context with structured MarketContextSnapshot bull/bear/risk/catalyst analysis").option("--company <name>", "Company name for DART fallback search and report subject").option("--dart-corp-code <code>", "Exact DART corp_code for issuer-specific filings").option("--symbol <ticker>", "Listed ticker for Finnhub company profile").option("--providers <list>", "Comma-separated providers: dart,fred,finnhub").option("--fred-series <list>", "Comma-separated FRED series IDs").option("--json", "Print structured JSON including analysis.marketSnapshot").option("--no-snapshot", "Disable MarketContextSnapshot and DART company snapshot analysis").action(async (options) => {
37036
37059
  try {
37037
37060
  await runMarketContextCommand(options);