claude-memory-layer 1.0.43 → 1.0.44
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 +4 -1
- package/dist/cli/index.js +200 -73
- package/dist/cli/index.js.map +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -489,7 +489,7 @@ claude-memory-layer dashboard --bind localhost --port 37777 --password "<local-p
|
|
|
489
489
|
claude-memory-layer market-context --company 삼성전자 --dart-corp-code 00126380 --symbol 005930.KS --json
|
|
490
490
|
```
|
|
491
491
|
|
|
492
|
-
MongoDB 동기화는 선택 기능입니다. 여러 서버에서 같은 프로젝트를 개발할 때, 각 서버의 로컬 SQLite 이벤트를 하나의 MongoDB로 모아 push/pull할 수 있습니다.
|
|
492
|
+
MongoDB 동기화는 선택 기능입니다. 여러 서버에서 같은 프로젝트를 개발할 때, 각 서버의 로컬 SQLite 이벤트를 하나의 MongoDB로 모아 push/pull할 수 있습니다. Pull된 이벤트를 바로 semantic search/context-pack에서 쓰고 싶다면 `--process-after-sync`를 함께 켜서 새 이벤트가 내려온 뒤 pending embedding/vector outbox를 처리하세요.
|
|
493
493
|
|
|
494
494
|
```bash
|
|
495
495
|
export CLAUDE_MEMORY_MONGO_URI="mongodb://USER:***@HOST:PORT/"
|
|
@@ -497,8 +497,11 @@ export CLAUDE_MEMORY_MONGO_DB="claude_memory_layer"
|
|
|
497
497
|
export CLAUDE_MEMORY_MONGO_PROJECT="my-project"
|
|
498
498
|
claude-memory-layer mongo-sync
|
|
499
499
|
claude-memory-layer mongo-sync --watch --interval 30000
|
|
500
|
+
claude-memory-layer mongo-sync --watch --interval 30000 --process-after-sync --process-interval 120000
|
|
500
501
|
```
|
|
501
502
|
|
|
503
|
+
`--process-after-sync`는 pull된 이벤트가 있을 때만 실행되며, `--process-interval` 동안 debounce되어 매 sync tick마다 불필요하게 임베딩을 재처리하지 않습니다. 내부적으로 project-scoped `vector-worker.lock`을 사용하므로 별도 `process` worker가 이미 실행 중이면 skip합니다.
|
|
504
|
+
|
|
502
505
|
### memU-inspired Retrieval 사용 예시
|
|
503
506
|
|
|
504
507
|
아래 예시는 SDK/서비스 레벨에서 `retrieveMemories()` 호출 시 적용되는 옵션입니다.
|
package/dist/cli/index.js
CHANGED
|
@@ -10,7 +10,7 @@ const __dirname = dirname(__filename);
|
|
|
10
10
|
import { Command } from "commander";
|
|
11
11
|
import { exec } from "child_process";
|
|
12
12
|
import * as fs18 from "fs";
|
|
13
|
-
import * as
|
|
13
|
+
import * as path24 from "path";
|
|
14
14
|
import * as os13 from "os";
|
|
15
15
|
|
|
16
16
|
// src/services/memory-service.ts
|
|
@@ -2241,12 +2241,12 @@ function resolveProjectStoragePath(projectOrHash) {
|
|
|
2241
2241
|
import Database from "better-sqlite3";
|
|
2242
2242
|
import * as fs4 from "fs";
|
|
2243
2243
|
import * as nodePath from "path";
|
|
2244
|
-
function createSQLiteDatabase(
|
|
2245
|
-
const dir = nodePath.dirname(
|
|
2244
|
+
function createSQLiteDatabase(path25, options) {
|
|
2245
|
+
const dir = nodePath.dirname(path25);
|
|
2246
2246
|
if (!fs4.existsSync(dir)) {
|
|
2247
2247
|
fs4.mkdirSync(dir, { recursive: true });
|
|
2248
2248
|
}
|
|
2249
|
-
const db = new Database(
|
|
2249
|
+
const db = new Database(path25, {
|
|
2250
2250
|
readonly: options?.readonly ?? false
|
|
2251
2251
|
});
|
|
2252
2252
|
if (!options?.readonly && (options?.walMode ?? true)) {
|
|
@@ -2697,18 +2697,18 @@ function emptyOutboxRecoveryResult() {
|
|
|
2697
2697
|
function isRecord(value) {
|
|
2698
2698
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2699
2699
|
}
|
|
2700
|
-
function getNestedRecord(root,
|
|
2700
|
+
function getNestedRecord(root, path25) {
|
|
2701
2701
|
let cursor = root;
|
|
2702
|
-
for (const key of
|
|
2702
|
+
for (const key of path25) {
|
|
2703
2703
|
if (!isRecord(cursor))
|
|
2704
2704
|
return void 0;
|
|
2705
2705
|
cursor = cursor[key];
|
|
2706
2706
|
}
|
|
2707
2707
|
return isRecord(cursor) ? cursor : void 0;
|
|
2708
2708
|
}
|
|
2709
|
-
function getNestedString(root,
|
|
2709
|
+
function getNestedString(root, path25) {
|
|
2710
2710
|
let cursor = root;
|
|
2711
|
-
for (const key of
|
|
2711
|
+
for (const key of path25) {
|
|
2712
2712
|
if (!isRecord(cursor))
|
|
2713
2713
|
return void 0;
|
|
2714
2714
|
cursor = cursor[key];
|
|
@@ -7777,9 +7777,9 @@ function safeParseObject(value) {
|
|
|
7777
7777
|
return void 0;
|
|
7778
7778
|
}
|
|
7779
7779
|
}
|
|
7780
|
-
function nestedString(root,
|
|
7780
|
+
function nestedString(root, path25) {
|
|
7781
7781
|
let cursor = root;
|
|
7782
|
-
for (const key of
|
|
7782
|
+
for (const key of path25) {
|
|
7783
7783
|
if (!isRecord2(cursor))
|
|
7784
7784
|
return void 0;
|
|
7785
7785
|
cursor = cursor[key];
|
|
@@ -7856,12 +7856,12 @@ var GraphPathService = class {
|
|
|
7856
7856
|
}
|
|
7857
7857
|
}
|
|
7858
7858
|
}
|
|
7859
|
-
const paths = Array.from(bestByTarget.entries()).map(([key,
|
|
7859
|
+
const paths = Array.from(bestByTarget.entries()).map(([key, path25]) => ({
|
|
7860
7860
|
target: graph.node(nodeFromKey(key)),
|
|
7861
|
-
hops:
|
|
7862
|
-
totalCost:
|
|
7863
|
-
scoreContribution:
|
|
7864
|
-
steps:
|
|
7861
|
+
hops: path25.hops,
|
|
7862
|
+
totalCost: path25.totalCost,
|
|
7863
|
+
scoreContribution: path25.totalCost > 0 ? 1 / path25.totalCost : 0,
|
|
7864
|
+
steps: path25.steps
|
|
7865
7865
|
})).sort((a, b) => b.scoreContribution - a.scoreContribution || a.hops - b.hops || a.target.name.localeCompare(b.target.name)).slice(0, maxResults);
|
|
7866
7866
|
return { startNodes, effectiveMaxHops, paths };
|
|
7867
7867
|
}
|
|
@@ -10843,14 +10843,14 @@ var Retriever = class {
|
|
|
10843
10843
|
direction: "both"
|
|
10844
10844
|
});
|
|
10845
10845
|
const titleByEntityId = new Map(startNodes.map((node) => [node.entityId, node.title]));
|
|
10846
|
-
for (const
|
|
10847
|
-
if (
|
|
10846
|
+
for (const path25 of expansion.paths) {
|
|
10847
|
+
if (path25.target.type !== "event")
|
|
10848
10848
|
continue;
|
|
10849
|
-
const target = await this.eventStore.getEvent(
|
|
10849
|
+
const target = await this.eventStore.getEvent(path25.target.id);
|
|
10850
10850
|
if (!target)
|
|
10851
10851
|
continue;
|
|
10852
|
-
const graphPath = toRetrievalGraphPathDebug(
|
|
10853
|
-
const score = graphPathScore(
|
|
10852
|
+
const graphPath = toRetrievalGraphPathDebug(path25, titleByEntityId);
|
|
10853
|
+
const score = graphPathScore(path25, opts.hopPenalty);
|
|
10854
10854
|
const existing = byId.get(target.id);
|
|
10855
10855
|
const graphPaths = mergeGraphPaths(existing?.graphPaths ?? [], [graphPath]);
|
|
10856
10856
|
const graphLane = {
|
|
@@ -10859,7 +10859,7 @@ var Retriever = class {
|
|
|
10859
10859
|
score
|
|
10860
10860
|
};
|
|
10861
10861
|
const row = {
|
|
10862
|
-
id: existing?.id ?? `graph-path-${
|
|
10862
|
+
id: existing?.id ?? `graph-path-${path25.hops}-${target.id}`,
|
|
10863
10863
|
eventId: target.id,
|
|
10864
10864
|
content: target.content,
|
|
10865
10865
|
score: Math.max(existing?.score ?? 0, score),
|
|
@@ -11206,8 +11206,8 @@ _Context:_ ${sessionContext}`;
|
|
|
11206
11206
|
matchesMetadataScope(metadata, expected) {
|
|
11207
11207
|
if (!metadata)
|
|
11208
11208
|
return false;
|
|
11209
|
-
return Object.entries(expected).every(([
|
|
11210
|
-
const actual =
|
|
11209
|
+
return Object.entries(expected).every(([path25, value]) => {
|
|
11210
|
+
const actual = path25.split(".").reduce((acc, key) => {
|
|
11211
11211
|
if (typeof acc !== "object" || acc === null)
|
|
11212
11212
|
return void 0;
|
|
11213
11213
|
return acc[key];
|
|
@@ -11267,22 +11267,22 @@ function uniqueEntityStartNodes(candidates) {
|
|
|
11267
11267
|
}
|
|
11268
11268
|
return nodes;
|
|
11269
11269
|
}
|
|
11270
|
-
function toRetrievalGraphPathDebug(
|
|
11271
|
-
const firstStep =
|
|
11270
|
+
function toRetrievalGraphPathDebug(path25, titleByEntityId) {
|
|
11271
|
+
const firstStep = path25.steps[0];
|
|
11272
11272
|
const startNode = firstStep?.direction === "incoming" ? firstStep.to : firstStep?.from;
|
|
11273
11273
|
const startEntityId = startNode?.type === "entity" ? startNode.id : "";
|
|
11274
11274
|
return {
|
|
11275
11275
|
startEntityId,
|
|
11276
11276
|
startEntityTitle: titleByEntityId.get(startEntityId) ?? startNode?.name,
|
|
11277
|
-
targetId:
|
|
11278
|
-
targetType:
|
|
11279
|
-
hops:
|
|
11280
|
-
relationPath:
|
|
11277
|
+
targetId: path25.target.id,
|
|
11278
|
+
targetType: path25.target.type,
|
|
11279
|
+
hops: path25.hops,
|
|
11280
|
+
relationPath: path25.steps.map((step) => step.relationType)
|
|
11281
11281
|
};
|
|
11282
11282
|
}
|
|
11283
|
-
function graphPathScore(
|
|
11284
|
-
const base = Math.min(0.95, Math.max(0,
|
|
11285
|
-
return Math.max(0.05, base - hopPenalty * Math.max(0,
|
|
11283
|
+
function graphPathScore(path25, hopPenalty) {
|
|
11284
|
+
const base = Math.min(0.95, Math.max(0, path25.scoreContribution));
|
|
11285
|
+
return Math.max(0.05, base - hopPenalty * Math.max(0, path25.hops - 1));
|
|
11286
11286
|
}
|
|
11287
11287
|
function clampGraphHops(maxHops) {
|
|
11288
11288
|
if (!Number.isFinite(maxHops))
|
|
@@ -11291,15 +11291,15 @@ function clampGraphHops(maxHops) {
|
|
|
11291
11291
|
}
|
|
11292
11292
|
function mergeGraphPaths(existing, incoming) {
|
|
11293
11293
|
const byKey = /* @__PURE__ */ new Map();
|
|
11294
|
-
for (const
|
|
11295
|
-
const key = [
|
|
11294
|
+
for (const path25 of [...existing, ...incoming]) {
|
|
11295
|
+
const key = [path25.startEntityId, path25.targetType, path25.targetId, path25.hops, ...path25.relationPath].join("\0");
|
|
11296
11296
|
if (!byKey.has(key))
|
|
11297
|
-
byKey.set(key,
|
|
11297
|
+
byKey.set(key, path25);
|
|
11298
11298
|
}
|
|
11299
11299
|
return [...byKey.values()].sort((a, b) => a.hops - b.hops || compareStable(graphPathSignature(a), graphPathSignature(b))).slice(0, 3);
|
|
11300
11300
|
}
|
|
11301
|
-
function graphPathSignature(
|
|
11302
|
-
return [
|
|
11301
|
+
function graphPathSignature(path25) {
|
|
11302
|
+
return [path25.startEntityId, path25.targetType, path25.targetId, path25.hops, ...path25.relationPath].join("|");
|
|
11303
11303
|
}
|
|
11304
11304
|
function compareStable(a, b) {
|
|
11305
11305
|
if (a < b)
|
|
@@ -21007,6 +21007,9 @@ function formatDuration2(ms) {
|
|
|
21007
21007
|
return remainingHours === 0 ? `${days}d` : `${days}d ${remainingHours}h`;
|
|
21008
21008
|
}
|
|
21009
21009
|
|
|
21010
|
+
// src/apps/cli/mongo-sync-command.ts
|
|
21011
|
+
import * as path23 from "node:path";
|
|
21012
|
+
|
|
21010
21013
|
// src/core/worker-lock.ts
|
|
21011
21014
|
import { closeSync, existsSync as existsSync14, mkdirSync as mkdirSync10, openSync, readFileSync as readFileSync7, unlinkSync, writeFileSync as writeFileSync7 } from "node:fs";
|
|
21012
21015
|
import * as os12 from "node:os";
|
|
@@ -21151,24 +21154,129 @@ function defaultIsProcessRunning(pid) {
|
|
|
21151
21154
|
}
|
|
21152
21155
|
}
|
|
21153
21156
|
|
|
21157
|
+
// src/apps/cli/mongo-sync-command.ts
|
|
21158
|
+
var DEFAULT_PROCESS_INTERVAL_MS = 12e4;
|
|
21159
|
+
function resolveMongoSyncProcessOptions(options, cwd = process.cwd(), deps = {}) {
|
|
21160
|
+
const explicitProject = options.project;
|
|
21161
|
+
if (explicitProject !== void 0 && explicitProject.trim().length === 0) {
|
|
21162
|
+
throw new Error("--project must not be empty");
|
|
21163
|
+
}
|
|
21164
|
+
const explicitLockPath = options.processLockPath;
|
|
21165
|
+
if (explicitLockPath !== void 0 && explicitLockPath.trim().length === 0) {
|
|
21166
|
+
throw new Error("--process-lock-path must not be empty");
|
|
21167
|
+
}
|
|
21168
|
+
const projectPath = explicitProject ?? cwd;
|
|
21169
|
+
const processIntervalMs = parsePositiveIntegerOption2(
|
|
21170
|
+
options.processInterval ?? String(DEFAULT_PROCESS_INTERVAL_MS),
|
|
21171
|
+
"--process-interval"
|
|
21172
|
+
);
|
|
21173
|
+
const getProjectStoragePath2 = deps.getProjectStoragePath ?? getProjectStoragePath;
|
|
21174
|
+
return {
|
|
21175
|
+
projectPath,
|
|
21176
|
+
processAfterSync: options.processAfterSync === true,
|
|
21177
|
+
processIntervalMs,
|
|
21178
|
+
lockPath: explicitLockPath ?? path23.join(getProjectStoragePath2(projectPath), "vector-worker.lock")
|
|
21179
|
+
};
|
|
21180
|
+
}
|
|
21181
|
+
function createMongoSyncPostProcessor(options, deps = {}) {
|
|
21182
|
+
const now = deps.now ?? (() => Date.now());
|
|
21183
|
+
const processOnce = deps.processOnce ?? processProjectEmbeddingsOnce;
|
|
21184
|
+
const log = deps.log ?? (() => void 0);
|
|
21185
|
+
let lastProcessAtMs = null;
|
|
21186
|
+
return {
|
|
21187
|
+
async afterSync(result) {
|
|
21188
|
+
if (!options.processAfterSync)
|
|
21189
|
+
return;
|
|
21190
|
+
if (result.pulled <= 0)
|
|
21191
|
+
return;
|
|
21192
|
+
const currentTimeMs = now();
|
|
21193
|
+
if (lastProcessAtMs !== null && currentTimeMs - lastProcessAtMs < options.processIntervalMs) {
|
|
21194
|
+
return;
|
|
21195
|
+
}
|
|
21196
|
+
lastProcessAtMs = currentTimeMs;
|
|
21197
|
+
log(`[mongo-sync] Processing pending embeddings after pulling ${result.pulled} events...`);
|
|
21198
|
+
let outcome;
|
|
21199
|
+
try {
|
|
21200
|
+
outcome = await processOnce({
|
|
21201
|
+
projectPath: options.projectPath,
|
|
21202
|
+
lockPath: options.lockPath
|
|
21203
|
+
});
|
|
21204
|
+
} catch (error) {
|
|
21205
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
21206
|
+
log(`[mongo-sync] Process-after-sync failed: ${message}`);
|
|
21207
|
+
return;
|
|
21208
|
+
}
|
|
21209
|
+
if (outcome.skipped) {
|
|
21210
|
+
const holder = outcome.holderPid === void 0 || outcome.holderPid === null ? "unknown" : String(outcome.holderPid);
|
|
21211
|
+
log(`[mongo-sync] Skipped embedding processing because another vector worker is running (holderPid=${holder})`);
|
|
21212
|
+
return;
|
|
21213
|
+
}
|
|
21214
|
+
log(`[mongo-sync] Processed ${outcome.processed} embeddings after sync`);
|
|
21215
|
+
}
|
|
21216
|
+
};
|
|
21217
|
+
}
|
|
21218
|
+
async function processProjectEmbeddingsOnce(input, deps = {}) {
|
|
21219
|
+
const createWorkerLock = deps.createWorkerLock ?? ((lockPath) => new WorkerLock(lockPath));
|
|
21220
|
+
const createService = deps.createMemoryService ?? createMemoryService;
|
|
21221
|
+
const getProjectStoragePath2 = deps.getProjectStoragePath ?? getProjectStoragePath;
|
|
21222
|
+
const hashProjectPath2 = deps.hashProjectPath ?? hashProjectPath;
|
|
21223
|
+
const workerLock = createWorkerLock(input.lockPath);
|
|
21224
|
+
const lockResult = workerLock.acquire();
|
|
21225
|
+
if (!lockResult.acquired) {
|
|
21226
|
+
return {
|
|
21227
|
+
skipped: true,
|
|
21228
|
+
processed: 0,
|
|
21229
|
+
holderPid: "holderPid" in lockResult ? lockResult.holderPid : null
|
|
21230
|
+
};
|
|
21231
|
+
}
|
|
21232
|
+
let service;
|
|
21233
|
+
try {
|
|
21234
|
+
service = createService({
|
|
21235
|
+
storagePath: getProjectStoragePath2(input.projectPath),
|
|
21236
|
+
projectHash: hashProjectPath2(input.projectPath),
|
|
21237
|
+
projectPath: input.projectPath,
|
|
21238
|
+
sharedStoreConfig: DISABLED_SHARED_STORE_CONFIG,
|
|
21239
|
+
analyticsEnabled: false
|
|
21240
|
+
});
|
|
21241
|
+
await service.initialize();
|
|
21242
|
+
await service.recoverStuckOutboxItems();
|
|
21243
|
+
const processed = await service.processPendingEmbeddings();
|
|
21244
|
+
return { skipped: false, processed };
|
|
21245
|
+
} finally {
|
|
21246
|
+
workerLock.release();
|
|
21247
|
+
await service?.shutdown().catch(() => void 0);
|
|
21248
|
+
}
|
|
21249
|
+
}
|
|
21250
|
+
function parsePositiveIntegerOption2(raw, flagName) {
|
|
21251
|
+
const value = raw.trim();
|
|
21252
|
+
if (!/^\d+$/.test(value)) {
|
|
21253
|
+
throw new Error(`${flagName} must be a positive integer number of milliseconds`);
|
|
21254
|
+
}
|
|
21255
|
+
const parsed = Number(value);
|
|
21256
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
21257
|
+
throw new Error(`${flagName} must be a positive integer number of milliseconds`);
|
|
21258
|
+
}
|
|
21259
|
+
return parsed;
|
|
21260
|
+
}
|
|
21261
|
+
|
|
21154
21262
|
// src/apps/cli/index.ts
|
|
21155
|
-
var CLAUDE_SETTINGS_PATH =
|
|
21263
|
+
var CLAUDE_SETTINGS_PATH = path24.join(os13.homedir(), ".claude", "settings.json");
|
|
21156
21264
|
function getPluginPath() {
|
|
21157
21265
|
const possiblePaths = [
|
|
21158
|
-
|
|
21266
|
+
path24.join(__dirname, ".."),
|
|
21159
21267
|
// When running from dist/cli
|
|
21160
|
-
|
|
21268
|
+
path24.join(__dirname, "../..", "dist"),
|
|
21161
21269
|
// When running from src
|
|
21162
|
-
|
|
21270
|
+
path24.join(process.cwd(), "dist")
|
|
21163
21271
|
// Current working directory
|
|
21164
21272
|
];
|
|
21165
21273
|
for (const p of possiblePaths) {
|
|
21166
|
-
const hooksPath =
|
|
21274
|
+
const hooksPath = path24.join(p, "hooks", "user-prompt-submit.js");
|
|
21167
21275
|
if (fs18.existsSync(hooksPath)) {
|
|
21168
21276
|
return p;
|
|
21169
21277
|
}
|
|
21170
21278
|
}
|
|
21171
|
-
return
|
|
21279
|
+
return path24.join(os13.homedir(), ".npm-global", "lib", "node_modules", "claude-memory-layer", "dist");
|
|
21172
21280
|
}
|
|
21173
21281
|
function loadClaudeSettings() {
|
|
21174
21282
|
try {
|
|
@@ -21182,7 +21290,7 @@ function loadClaudeSettings() {
|
|
|
21182
21290
|
return {};
|
|
21183
21291
|
}
|
|
21184
21292
|
function saveClaudeSettings(settings) {
|
|
21185
|
-
const dir =
|
|
21293
|
+
const dir = path24.dirname(CLAUDE_SETTINGS_PATH);
|
|
21186
21294
|
if (!fs18.existsSync(dir)) {
|
|
21187
21295
|
fs18.mkdirSync(dir, { recursive: true });
|
|
21188
21296
|
}
|
|
@@ -21211,7 +21319,7 @@ function parseMarketProviders(value) {
|
|
|
21211
21319
|
}
|
|
21212
21320
|
return selected;
|
|
21213
21321
|
}
|
|
21214
|
-
function
|
|
21322
|
+
function parsePositiveIntegerOption3(value, optionName) {
|
|
21215
21323
|
if (value === void 0)
|
|
21216
21324
|
return void 0;
|
|
21217
21325
|
const normalized = value.trim();
|
|
@@ -21246,14 +21354,14 @@ async function runCodexValidationCommand(options) {
|
|
|
21246
21354
|
const report = await validateCodexSessions({
|
|
21247
21355
|
sessionsDir: options.sessionsDir,
|
|
21248
21356
|
projectPath: options.project,
|
|
21249
|
-
limit:
|
|
21357
|
+
limit: parsePositiveIntegerOption3(options.limit, "limit"),
|
|
21250
21358
|
anonymizeProjects: options.anonymizeProjects === true
|
|
21251
21359
|
});
|
|
21252
21360
|
const rendered = formatCodexValidationReport(report, format);
|
|
21253
21361
|
process.stdout.write(rendered.endsWith("\n") ? rendered : `${rendered}
|
|
21254
21362
|
`);
|
|
21255
21363
|
if (options.output) {
|
|
21256
|
-
const outputPath =
|
|
21364
|
+
const outputPath = path24.resolve(options.output);
|
|
21257
21365
|
writeCodexValidationReport(outputPath, report, format);
|
|
21258
21366
|
console.log(`
|
|
21259
21367
|
Report written to ${outputPath}`);
|
|
@@ -21267,13 +21375,13 @@ async function runHermesValidationCommand(options) {
|
|
|
21267
21375
|
const report = await validateHermesSessions({
|
|
21268
21376
|
stateDbPath: options.stateDb,
|
|
21269
21377
|
projectPath: options.project,
|
|
21270
|
-
limit:
|
|
21378
|
+
limit: parsePositiveIntegerOption3(options.limit, "limit")
|
|
21271
21379
|
});
|
|
21272
21380
|
const rendered = formatHermesValidationReport(report, format);
|
|
21273
21381
|
process.stdout.write(rendered.endsWith("\n") ? rendered : `${rendered}
|
|
21274
21382
|
`);
|
|
21275
21383
|
if (options.output) {
|
|
21276
|
-
const outputPath =
|
|
21384
|
+
const outputPath = path24.resolve(options.output);
|
|
21277
21385
|
writeHermesValidationReport(outputPath, report, format);
|
|
21278
21386
|
console.log(`
|
|
21279
21387
|
Report written to ${outputPath}`);
|
|
@@ -21307,14 +21415,14 @@ var OPERATION_PRIVACY_CONFIG = {
|
|
|
21307
21415
|
}
|
|
21308
21416
|
};
|
|
21309
21417
|
function resolveOperationProject(project) {
|
|
21310
|
-
const projectPath =
|
|
21418
|
+
const projectPath = path24.resolve(project ?? process.cwd());
|
|
21311
21419
|
const projectHash = hashProjectPath(projectPath);
|
|
21312
21420
|
const storagePath = getProjectStoragePath(projectPath);
|
|
21313
21421
|
return {
|
|
21314
21422
|
projectPath,
|
|
21315
21423
|
projectHash,
|
|
21316
21424
|
storagePath,
|
|
21317
|
-
dbPath:
|
|
21425
|
+
dbPath: path24.join(storagePath, "events.sqlite")
|
|
21318
21426
|
};
|
|
21319
21427
|
}
|
|
21320
21428
|
function openOperationReadDatabase(context) {
|
|
@@ -21544,7 +21652,7 @@ async function runOperationCli(action, label) {
|
|
|
21544
21652
|
}
|
|
21545
21653
|
}
|
|
21546
21654
|
var program = new Command();
|
|
21547
|
-
program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.
|
|
21655
|
+
program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.44");
|
|
21548
21656
|
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) => {
|
|
21549
21657
|
try {
|
|
21550
21658
|
await runMarketContextCommand(options);
|
|
@@ -21557,7 +21665,7 @@ program.command("install").description("Install hooks into Claude Code settings"
|
|
|
21557
21665
|
try {
|
|
21558
21666
|
const pluginPath = options.path || getPluginPath();
|
|
21559
21667
|
const missingHooks = REQUIRED_HOOK_FILES.filter(
|
|
21560
|
-
(file) => !fs18.existsSync(
|
|
21668
|
+
(file) => !fs18.existsSync(path24.join(pluginPath, "hooks", file))
|
|
21561
21669
|
);
|
|
21562
21670
|
if (missingHooks.length > 0) {
|
|
21563
21671
|
console.error(`
|
|
@@ -21623,7 +21731,7 @@ program.command("status").description("Check plugin installation status").action
|
|
|
21623
21731
|
console.log(` PostToolUse: ${hasPostToolHook ? "\u2705 Installed" : "\u274C Not installed"}`);
|
|
21624
21732
|
console.log(` Stop: ${hasStopHook ? "\u2705 Installed" : "\u274C Not installed"}`);
|
|
21625
21733
|
console.log(` SessionEnd: ${hasSessionEndHook ? "\u2705 Installed" : "\u274C Not installed"}`);
|
|
21626
|
-
const hooksExist = REQUIRED_HOOK_FILES.every((file) => fs18.existsSync(
|
|
21734
|
+
const hooksExist = REQUIRED_HOOK_FILES.every((file) => fs18.existsSync(path24.join(pluginPath, "hooks", file)));
|
|
21627
21735
|
console.log(`
|
|
21628
21736
|
Plugin files: ${hooksExist ? "\u2705 Found" : "\u274C Not found"}`);
|
|
21629
21737
|
console.log(` Path: ${pluginPath}`);
|
|
@@ -21857,7 +21965,7 @@ repairCommand.command("legacy-project-scope").description("Dry-run or apply proj
|
|
|
21857
21965
|
apply: options.apply
|
|
21858
21966
|
});
|
|
21859
21967
|
const storagePath = projectPath ? getProjectStoragePath(projectPath) : resolveProjectStoragePath(repairOptions.projectHash);
|
|
21860
|
-
const dbPath =
|
|
21968
|
+
const dbPath = path24.join(storagePath, "events.sqlite");
|
|
21861
21969
|
if (repairOptions.dryRun && !fs18.existsSync(dbPath)) {
|
|
21862
21970
|
const projectHash = repairOptions.projectHash || hashProjectPath(repairOptions.projectPath);
|
|
21863
21971
|
console.log(formatLegacyProjectScopeRepairResult({
|
|
@@ -22108,7 +22216,7 @@ retentionCommand.command("audit").description("Run a dry-run retention audit for
|
|
|
22108
22216
|
});
|
|
22109
22217
|
const projectHash = auditOptions.projectHash ?? hashProjectPath(auditOptions.projectPath);
|
|
22110
22218
|
const storagePath = auditOptions.projectPath ? getProjectStoragePath(auditOptions.projectPath) : resolveProjectStoragePath(projectHash);
|
|
22111
|
-
const dbPath =
|
|
22219
|
+
const dbPath = path24.join(storagePath, "events.sqlite");
|
|
22112
22220
|
if (!fs18.existsSync(dbPath)) {
|
|
22113
22221
|
const emptyReport = emptyRetentionAuditReport(projectHash, auditOptions.limit);
|
|
22114
22222
|
console.log(formatRetentionAuditReport(emptyReport, { json: auditOptions.json }));
|
|
@@ -22132,11 +22240,11 @@ retentionCommand.command("audit").description("Run a dry-run retention audit for
|
|
|
22132
22240
|
process.exit(1);
|
|
22133
22241
|
}
|
|
22134
22242
|
});
|
|
22135
|
-
program.command("mongo-sync").description("Sync events with MongoDB for multi-server collaboration (optional)").option("-p, --project <path>", "Project path (defaults to cwd)").option("--mongo-uri <uri>", "MongoDB connection URI (env: CLAUDE_MEMORY_MONGO_URI)").option("--mongo-db <name>", "MongoDB database name (env: CLAUDE_MEMORY_MONGO_DB)").option("--mongo-project <key>", "Remote project key (env: CLAUDE_MEMORY_MONGO_PROJECT, default: basename(projectPath))").option("--direction <dir>", "push|pull|both", "both").option("--batch-size <n>", "Batch size", "500").option("--interval <ms>", "Watch interval ms", "30000").option("--watch", "Run continuously").action(async (options) => {
|
|
22243
|
+
program.command("mongo-sync").description("Sync events with MongoDB for multi-server collaboration (optional)").option("-p, --project <path>", "Project path (defaults to cwd)").option("--mongo-uri <uri>", "MongoDB connection URI (env: CLAUDE_MEMORY_MONGO_URI)").option("--mongo-db <name>", "MongoDB database name (env: CLAUDE_MEMORY_MONGO_DB)").option("--mongo-project <key>", "Remote project key (env: CLAUDE_MEMORY_MONGO_PROJECT, default: basename(projectPath))").option("--direction <dir>", "push|pull|both", "both").option("--batch-size <n>", "Batch size", "500").option("--interval <ms>", "Watch interval ms", "30000").option("--watch", "Run continuously").option("--process-after-sync", "Process pending embeddings after pull activity").option("--process-interval <ms>", "Minimum ms between process runs when --process-after-sync is enabled", "120000").option("--process-lock-path <path>", "Override process-after-sync lock path (advanced)").action(async (options) => {
|
|
22136
22244
|
const projectPath = options.project || process.cwd();
|
|
22137
22245
|
const mongoUri = options.mongoUri || process.env.CLAUDE_MEMORY_MONGO_URI;
|
|
22138
22246
|
const mongoDb = options.mongoDb || process.env.CLAUDE_MEMORY_MONGO_DB;
|
|
22139
|
-
const projectKey = options.mongoProject || process.env.CLAUDE_MEMORY_MONGO_PROJECT ||
|
|
22247
|
+
const projectKey = options.mongoProject || process.env.CLAUDE_MEMORY_MONGO_PROJECT || path24.basename(projectPath);
|
|
22140
22248
|
const direction = String(options.direction || "both").toLowerCase();
|
|
22141
22249
|
if (!mongoUri || !mongoDb) {
|
|
22142
22250
|
console.error("\n\u274C MongoDB sync is not configured.");
|
|
@@ -22147,15 +22255,33 @@ program.command("mongo-sync").description("Sync events with MongoDB for multi-se
|
|
|
22147
22255
|
console.error("\n\u274C Invalid --direction. Use: push | pull | both\n");
|
|
22148
22256
|
process.exit(1);
|
|
22149
22257
|
}
|
|
22150
|
-
const storagePath = getProjectStoragePath(projectPath);
|
|
22151
|
-
if (!fs18.existsSync(storagePath)) {
|
|
22152
|
-
fs18.mkdirSync(storagePath, { recursive: true });
|
|
22153
|
-
}
|
|
22154
22258
|
const batchSizeParsed = parseInt(options.batchSize, 10);
|
|
22155
22259
|
const intervalParsed = parseInt(options.interval, 10);
|
|
22156
22260
|
const batchSize = Number.isFinite(batchSizeParsed) && batchSizeParsed > 0 ? batchSizeParsed : 500;
|
|
22157
22261
|
const intervalMs = Number.isFinite(intervalParsed) && intervalParsed > 0 ? intervalParsed : 3e4;
|
|
22158
|
-
const
|
|
22262
|
+
const processOptions = (() => {
|
|
22263
|
+
try {
|
|
22264
|
+
return resolveMongoSyncProcessOptions({
|
|
22265
|
+
project: projectPath,
|
|
22266
|
+
processAfterSync: options.processAfterSync,
|
|
22267
|
+
processInterval: options.processInterval,
|
|
22268
|
+
processLockPath: options.processLockPath
|
|
22269
|
+
});
|
|
22270
|
+
} catch (error) {
|
|
22271
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
22272
|
+
console.error(`[mongo-sync] Failed: ${message}`);
|
|
22273
|
+
process.exit(1);
|
|
22274
|
+
}
|
|
22275
|
+
})();
|
|
22276
|
+
const storagePath = getProjectStoragePath(projectPath);
|
|
22277
|
+
if (!fs18.existsSync(storagePath)) {
|
|
22278
|
+
fs18.mkdirSync(storagePath, { recursive: true });
|
|
22279
|
+
}
|
|
22280
|
+
const postProcessor = createMongoSyncPostProcessor(processOptions, {
|
|
22281
|
+
log: (message) => process.stdout.write(`${message}
|
|
22282
|
+
`)
|
|
22283
|
+
});
|
|
22284
|
+
const sqliteStore = new SQLiteEventStore(path24.join(storagePath, "events.sqlite"));
|
|
22159
22285
|
const worker = new MongoSyncWorker(sqliteStore, {
|
|
22160
22286
|
uri: mongoUri,
|
|
22161
22287
|
dbName: mongoDb,
|
|
@@ -22169,6 +22295,7 @@ program.command("mongo-sync").description("Sync events with MongoDB for multi-se
|
|
|
22169
22295
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
22170
22296
|
process.stdout.write(`[mongo-sync] ${ts} project=${projectKey} pushed=${pushed} pulled=${pulled}
|
|
22171
22297
|
`);
|
|
22298
|
+
await postProcessor.afterSync({ pushed, pulled });
|
|
22172
22299
|
};
|
|
22173
22300
|
try {
|
|
22174
22301
|
if (!options.watch) {
|
|
@@ -22214,7 +22341,7 @@ function renderProgress(event) {
|
|
|
22214
22341
|
break;
|
|
22215
22342
|
case "session-start": {
|
|
22216
22343
|
const pct = Math.round(event.sessionIndex / event.totalSessions * 100);
|
|
22217
|
-
const sessionName =
|
|
22344
|
+
const sessionName = path24.basename(event.filePath, ".jsonl").slice(0, 8);
|
|
22218
22345
|
process.stdout.write(
|
|
22219
22346
|
`\r \u{1F4C4} [${event.sessionIndex + 1}/${event.totalSessions}] ${pct}% | Session ${sessionName}... `
|
|
22220
22347
|
);
|
|
@@ -22291,7 +22418,7 @@ async function listMarkdownFiles(root) {
|
|
|
22291
22418
|
const dir = stack.pop();
|
|
22292
22419
|
const entries = await fs18.promises.readdir(dir, { withFileTypes: true });
|
|
22293
22420
|
for (const e of entries) {
|
|
22294
|
-
const full =
|
|
22421
|
+
const full = path24.join(dir, e.name);
|
|
22295
22422
|
if (e.isDirectory())
|
|
22296
22423
|
stack.push(full);
|
|
22297
22424
|
else if (e.isFile() && e.name.endsWith(".md") && e.name !== "_index.md")
|
|
@@ -22301,8 +22428,8 @@ async function listMarkdownFiles(root) {
|
|
|
22301
22428
|
return out.sort();
|
|
22302
22429
|
}
|
|
22303
22430
|
function deriveNamespaceCategory(sourceRoot, filePath) {
|
|
22304
|
-
const rel =
|
|
22305
|
-
const dirSeg =
|
|
22431
|
+
const rel = path24.relative(sourceRoot, filePath);
|
|
22432
|
+
const dirSeg = path24.dirname(rel).split(path24.sep).filter(Boolean);
|
|
22306
22433
|
if (dirSeg.length >= 2) {
|
|
22307
22434
|
const namespace = sanitizeSegment3(dirSeg[0], "default");
|
|
22308
22435
|
const categoryPath = dirSeg.slice(1).map((s) => sanitizeSegment3(s, "uncategorized"));
|
|
@@ -22321,8 +22448,8 @@ function extractImportEvidence(markdown) {
|
|
|
22321
22448
|
program.command("organize-import [sourceDir]").description("Import existing markdown memory files, or bootstrap knowledge docs from codebase/git when markdown is missing").option("-p, --project <path>", "Project path (defaults to cwd)").option("--session <id>", "Session id for imported events (default: import:organized)").option("--limit <n>", "Limit number of files to import").option("--dry-run", "Preview mapping without writing").option("--bootstrap", "Force-generate structured markdown from codebase + git history before import").option("--bootstrap-if-empty", "Auto-bootstrap when source has no markdown files (default: true)", true).option("--no-bootstrap-if-empty", "Disable auto-bootstrap when source has no markdown files").option("--force-bootstrap", "Run bootstrap even when markdown files exist").option("--repo <path>", "Repository root for bootstrap analysis (default: project path)").option("--out <path>", "Output directory for generated bootstrap markdown (default: <sourceDir>/bootstrap-kb)").option("--since <range>", 'Git history range for bootstrap (default: "180 days ago")').option("--max-commits <n>", "Max commits to analyze for bootstrap (default: 1000)").option("--incremental", "Use previous bootstrap manifest as baseline for incremental updates (default: true)", true).option("--no-incremental", "Disable incremental bootstrap; regenerate full snapshot").action(async (sourceDir, options) => {
|
|
22322
22449
|
const projectPath = options.project || process.cwd();
|
|
22323
22450
|
const sessionId = options.session || "import:organized";
|
|
22324
|
-
const sourceRoot =
|
|
22325
|
-
const repoPath =
|
|
22451
|
+
const sourceRoot = path24.resolve(sourceDir || options.out || projectPath);
|
|
22452
|
+
const repoPath = path24.resolve(options.repo || projectPath);
|
|
22326
22453
|
if (!fs18.existsSync(sourceRoot)) {
|
|
22327
22454
|
fs18.mkdirSync(sourceRoot, { recursive: true });
|
|
22328
22455
|
}
|
|
@@ -22334,7 +22461,7 @@ program.command("organize-import [sourceDir]").description("Import existing mark
|
|
|
22334
22461
|
const hasMarkdown = files.length > 0;
|
|
22335
22462
|
const shouldBootstrap = Boolean(options.forceBootstrap || options.bootstrap || !hasMarkdown && options.bootstrapIfEmpty);
|
|
22336
22463
|
if (shouldBootstrap) {
|
|
22337
|
-
const outDir =
|
|
22464
|
+
const outDir = path24.resolve(options.out || path24.join(sourceRoot, "bootstrap-kb"));
|
|
22338
22465
|
const since = options.since || "180 days ago";
|
|
22339
22466
|
const maxCommits = options.maxCommits ? Math.max(1, parseInt(options.maxCommits, 10)) : 1e3;
|
|
22340
22467
|
console.log("\n\u{1F9E0} Bootstrapping markdown knowledge base...");
|
|
@@ -22379,7 +22506,7 @@ program.command("organize-import [sourceDir]").description("Import existing mark
|
|
|
22379
22506
|
continue;
|
|
22380
22507
|
}
|
|
22381
22508
|
const { namespace, categoryPath } = deriveNamespaceCategory(activeSourceRoot, file);
|
|
22382
|
-
const rel =
|
|
22509
|
+
const rel = path24.relative(activeSourceRoot, file);
|
|
22383
22510
|
const evidence = extractImportEvidence(text);
|
|
22384
22511
|
if (options.dryRun) {
|
|
22385
22512
|
console.log(`- ${rel} -> namespace=${namespace} category=${categoryPath.join("/")} confidence=${evidence.confidence || "n/a"} sources=${evidence.sources.length}`);
|
|
@@ -22423,8 +22550,8 @@ program.command("import").description("Import existing Claude Code conversation
|
|
|
22423
22550
|
const service = getMemoryServiceForProject(targetProjectPath);
|
|
22424
22551
|
const importer = createSessionHistoryImporter(service);
|
|
22425
22552
|
const importOpts = {
|
|
22426
|
-
limit:
|
|
22427
|
-
sessionLimit:
|
|
22553
|
+
limit: parsePositiveIntegerOption3(options.limit, "limit"),
|
|
22554
|
+
sessionLimit: parsePositiveIntegerOption3(options.sessionLimit, "session-limit"),
|
|
22428
22555
|
force: options.force,
|
|
22429
22556
|
verbose: options.verbose,
|
|
22430
22557
|
onProgress: renderProgress
|