claude-memory-layer 1.0.54 → 1.0.55
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/dist/cli/index.js +417 -139
- package/dist/cli/index.js.map +4 -4
- package/dist/server/api/index.js +239 -34
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +251 -46
- package/dist/server/index.js.map +4 -4
- package/package.json +4 -2
package/dist/cli/index.js
CHANGED
|
@@ -9,8 +9,8 @@ const __dirname = dirname(__filename);
|
|
|
9
9
|
// src/apps/cli/index.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
11
|
import { exec } from "child_process";
|
|
12
|
-
import * as
|
|
13
|
-
import * as
|
|
12
|
+
import * as fs20 from "fs";
|
|
13
|
+
import * as path26 from "path";
|
|
14
14
|
import * as os13 from "os";
|
|
15
15
|
|
|
16
16
|
// src/services/memory-service.ts
|
|
@@ -2245,12 +2245,12 @@ function resolveProjectStoragePath(projectOrHash) {
|
|
|
2245
2245
|
import Database from "better-sqlite3";
|
|
2246
2246
|
import * as fs4 from "fs";
|
|
2247
2247
|
import * as nodePath from "path";
|
|
2248
|
-
function createSQLiteDatabase(
|
|
2249
|
-
const dir = nodePath.dirname(
|
|
2248
|
+
function createSQLiteDatabase(path27, options) {
|
|
2249
|
+
const dir = nodePath.dirname(path27);
|
|
2250
2250
|
if (!fs4.existsSync(dir)) {
|
|
2251
2251
|
fs4.mkdirSync(dir, { recursive: true });
|
|
2252
2252
|
}
|
|
2253
|
-
const db = new Database(
|
|
2253
|
+
const db = new Database(path27, {
|
|
2254
2254
|
readonly: options?.readonly ?? false
|
|
2255
2255
|
});
|
|
2256
2256
|
if (!options?.readonly && (options?.walMode ?? true)) {
|
|
@@ -2713,17 +2713,17 @@ function emptyOutboxRecoveryResult() {
|
|
|
2713
2713
|
function isRecord(value) {
|
|
2714
2714
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2715
2715
|
}
|
|
2716
|
-
function getNestedRecord(root,
|
|
2716
|
+
function getNestedRecord(root, path27) {
|
|
2717
2717
|
let cursor = root;
|
|
2718
|
-
for (const key of
|
|
2718
|
+
for (const key of path27) {
|
|
2719
2719
|
if (!isRecord(cursor)) return void 0;
|
|
2720
2720
|
cursor = cursor[key];
|
|
2721
2721
|
}
|
|
2722
2722
|
return isRecord(cursor) ? cursor : void 0;
|
|
2723
2723
|
}
|
|
2724
|
-
function getNestedString(root,
|
|
2724
|
+
function getNestedString(root, path27) {
|
|
2725
2725
|
let cursor = root;
|
|
2726
|
-
for (const key of
|
|
2726
|
+
for (const key of path27) {
|
|
2727
2727
|
if (!isRecord(cursor)) return void 0;
|
|
2728
2728
|
cursor = cursor[key];
|
|
2729
2729
|
}
|
|
@@ -7895,9 +7895,9 @@ function safeParseObject(value) {
|
|
|
7895
7895
|
return void 0;
|
|
7896
7896
|
}
|
|
7897
7897
|
}
|
|
7898
|
-
function nestedString(root,
|
|
7898
|
+
function nestedString(root, path27) {
|
|
7899
7899
|
let cursor = root;
|
|
7900
|
-
for (const key of
|
|
7900
|
+
for (const key of path27) {
|
|
7901
7901
|
if (!isRecord2(cursor)) return void 0;
|
|
7902
7902
|
cursor = cursor[key];
|
|
7903
7903
|
}
|
|
@@ -7972,12 +7972,12 @@ var GraphPathService = class {
|
|
|
7972
7972
|
}
|
|
7973
7973
|
}
|
|
7974
7974
|
}
|
|
7975
|
-
const paths = Array.from(bestByTarget.entries()).map(([key,
|
|
7975
|
+
const paths = Array.from(bestByTarget.entries()).map(([key, path27]) => ({
|
|
7976
7976
|
target: graph.node(nodeFromKey(key)),
|
|
7977
|
-
hops:
|
|
7978
|
-
totalCost:
|
|
7979
|
-
scoreContribution:
|
|
7980
|
-
steps:
|
|
7977
|
+
hops: path27.hops,
|
|
7978
|
+
totalCost: path27.totalCost,
|
|
7979
|
+
scoreContribution: path27.totalCost > 0 ? 1 / path27.totalCost : 0,
|
|
7980
|
+
steps: path27.steps
|
|
7981
7981
|
})).sort((a, b) => b.scoreContribution - a.scoreContribution || a.hops - b.hops || a.target.name.localeCompare(b.target.name)).slice(0, maxResults);
|
|
7982
7982
|
return { startNodes, effectiveMaxHops, paths };
|
|
7983
7983
|
}
|
|
@@ -10940,12 +10940,12 @@ var Retriever = class {
|
|
|
10940
10940
|
direction: "both"
|
|
10941
10941
|
});
|
|
10942
10942
|
const titleByEntityId = new Map(startNodes.map((node) => [node.entityId, node.title]));
|
|
10943
|
-
for (const
|
|
10944
|
-
if (
|
|
10945
|
-
const target = await this.eventStore.getEvent(
|
|
10943
|
+
for (const path27 of expansion.paths) {
|
|
10944
|
+
if (path27.target.type !== "event") continue;
|
|
10945
|
+
const target = await this.eventStore.getEvent(path27.target.id);
|
|
10946
10946
|
if (!target) continue;
|
|
10947
|
-
const graphPath = toRetrievalGraphPathDebug(
|
|
10948
|
-
const score = graphPathScore(
|
|
10947
|
+
const graphPath = toRetrievalGraphPathDebug(path27, titleByEntityId);
|
|
10948
|
+
const score = graphPathScore(path27, opts.hopPenalty);
|
|
10949
10949
|
const existing = byId.get(target.id);
|
|
10950
10950
|
const graphPaths = mergeGraphPaths(existing?.graphPaths ?? [], [graphPath]);
|
|
10951
10951
|
const graphLane = {
|
|
@@ -10954,7 +10954,7 @@ var Retriever = class {
|
|
|
10954
10954
|
score
|
|
10955
10955
|
};
|
|
10956
10956
|
const row = {
|
|
10957
|
-
id: existing?.id ?? `graph-path-${
|
|
10957
|
+
id: existing?.id ?? `graph-path-${path27.hops}-${target.id}`,
|
|
10958
10958
|
eventId: target.id,
|
|
10959
10959
|
content: target.content,
|
|
10960
10960
|
score: Math.max(existing?.score ?? 0, score),
|
|
@@ -11294,8 +11294,8 @@ _Context:_ ${sessionContext}`;
|
|
|
11294
11294
|
}
|
|
11295
11295
|
matchesMetadataScope(metadata, expected) {
|
|
11296
11296
|
if (!metadata) return false;
|
|
11297
|
-
return Object.entries(expected).every(([
|
|
11298
|
-
const actual =
|
|
11297
|
+
return Object.entries(expected).every(([path27, value]) => {
|
|
11298
|
+
const actual = path27.split(".").reduce((acc, key) => {
|
|
11299
11299
|
if (typeof acc !== "object" || acc === null) return void 0;
|
|
11300
11300
|
return acc[key];
|
|
11301
11301
|
}, metadata);
|
|
@@ -11346,22 +11346,22 @@ function uniqueEntityStartNodes(candidates) {
|
|
|
11346
11346
|
}
|
|
11347
11347
|
return nodes;
|
|
11348
11348
|
}
|
|
11349
|
-
function toRetrievalGraphPathDebug(
|
|
11350
|
-
const firstStep =
|
|
11349
|
+
function toRetrievalGraphPathDebug(path27, titleByEntityId) {
|
|
11350
|
+
const firstStep = path27.steps[0];
|
|
11351
11351
|
const startNode = firstStep?.direction === "incoming" ? firstStep.to : firstStep?.from;
|
|
11352
11352
|
const startEntityId = startNode?.type === "entity" ? startNode.id : "";
|
|
11353
11353
|
return {
|
|
11354
11354
|
startEntityId,
|
|
11355
11355
|
startEntityTitle: titleByEntityId.get(startEntityId) ?? startNode?.name,
|
|
11356
|
-
targetId:
|
|
11357
|
-
targetType:
|
|
11358
|
-
hops:
|
|
11359
|
-
relationPath:
|
|
11356
|
+
targetId: path27.target.id,
|
|
11357
|
+
targetType: path27.target.type,
|
|
11358
|
+
hops: path27.hops,
|
|
11359
|
+
relationPath: path27.steps.map((step) => step.relationType)
|
|
11360
11360
|
};
|
|
11361
11361
|
}
|
|
11362
|
-
function graphPathScore(
|
|
11363
|
-
const base = Math.min(0.95, Math.max(0,
|
|
11364
|
-
return Math.max(0.05, base - hopPenalty * Math.max(0,
|
|
11362
|
+
function graphPathScore(path27, hopPenalty) {
|
|
11363
|
+
const base = Math.min(0.95, Math.max(0, path27.scoreContribution));
|
|
11364
|
+
return Math.max(0.05, base - hopPenalty * Math.max(0, path27.hops - 1));
|
|
11365
11365
|
}
|
|
11366
11366
|
function clampGraphHops(maxHops) {
|
|
11367
11367
|
if (!Number.isFinite(maxHops)) return 2;
|
|
@@ -11369,14 +11369,14 @@ function clampGraphHops(maxHops) {
|
|
|
11369
11369
|
}
|
|
11370
11370
|
function mergeGraphPaths(existing, incoming) {
|
|
11371
11371
|
const byKey = /* @__PURE__ */ new Map();
|
|
11372
|
-
for (const
|
|
11373
|
-
const key = [
|
|
11374
|
-
if (!byKey.has(key)) byKey.set(key,
|
|
11372
|
+
for (const path27 of [...existing, ...incoming]) {
|
|
11373
|
+
const key = [path27.startEntityId, path27.targetType, path27.targetId, path27.hops, ...path27.relationPath].join("\0");
|
|
11374
|
+
if (!byKey.has(key)) byKey.set(key, path27);
|
|
11375
11375
|
}
|
|
11376
11376
|
return [...byKey.values()].sort((a, b) => a.hops - b.hops || compareStable(graphPathSignature(a), graphPathSignature(b))).slice(0, 3);
|
|
11377
11377
|
}
|
|
11378
|
-
function graphPathSignature(
|
|
11379
|
-
return [
|
|
11378
|
+
function graphPathSignature(path27) {
|
|
11379
|
+
return [path27.startEntityId, path27.targetType, path27.targetId, path27.hops, ...path27.relationPath].join("|");
|
|
11380
11380
|
}
|
|
11381
11381
|
function compareStable(a, b) {
|
|
11382
11382
|
if (a < b) return -1;
|
|
@@ -16350,8 +16350,8 @@ import { getCookie, setCookie, deleteCookie } from "hono/cookie";
|
|
|
16350
16350
|
import { logger } from "hono/logger";
|
|
16351
16351
|
import { serve } from "@hono/node-server";
|
|
16352
16352
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
16353
|
-
import * as
|
|
16354
|
-
import * as
|
|
16353
|
+
import * as path19 from "path";
|
|
16354
|
+
import * as fs17 from "fs";
|
|
16355
16355
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
16356
16356
|
import { fileURLToPath as fileUrlToPath } from "url";
|
|
16357
16357
|
|
|
@@ -19100,6 +19100,150 @@ function streamClaudeResponse(prompt, stream) {
|
|
|
19100
19100
|
// src/apps/server/api/health.ts
|
|
19101
19101
|
import { Hono as Hono9 } from "hono";
|
|
19102
19102
|
import { spawnSync } from "child_process";
|
|
19103
|
+
import * as fs16 from "fs";
|
|
19104
|
+
import * as path18 from "path";
|
|
19105
|
+
|
|
19106
|
+
// src/core/productivity-health-report.ts
|
|
19107
|
+
var PRODUCTIVITY_HEALTH_SCHEMA_VERSION = "agent-productivity-health-v1";
|
|
19108
|
+
var PRODUCTIVITY_HEALTH_PROFILES = ["coder", "reviewer", "pm", "support", "researcher", "team"];
|
|
19109
|
+
var PRODUCTIVITY_HEALTH_MODES = ["observe", "preview", "enforce"];
|
|
19110
|
+
function parseProductivityHealthProfile(value) {
|
|
19111
|
+
const normalized = typeof value === "string" && value.trim().length > 0 ? value.trim() : "coder";
|
|
19112
|
+
if (PRODUCTIVITY_HEALTH_PROFILES.includes(normalized)) {
|
|
19113
|
+
return normalized;
|
|
19114
|
+
}
|
|
19115
|
+
throw new Error(`Invalid --profile: expected one of ${PRODUCTIVITY_HEALTH_PROFILES.join(", ")}`);
|
|
19116
|
+
}
|
|
19117
|
+
function parseProductivityHealthMode(value) {
|
|
19118
|
+
const normalized = typeof value === "string" && value.trim().length > 0 ? value.trim() : "preview";
|
|
19119
|
+
if (PRODUCTIVITY_HEALTH_MODES.includes(normalized)) {
|
|
19120
|
+
return normalized;
|
|
19121
|
+
}
|
|
19122
|
+
throw new Error(`Invalid --mode: expected one of ${PRODUCTIVITY_HEALTH_MODES.join(", ")}`);
|
|
19123
|
+
}
|
|
19124
|
+
function buildProductivityHealthReport(input) {
|
|
19125
|
+
const profile = input.profile ?? "coder";
|
|
19126
|
+
const mode = input.mode ?? "preview";
|
|
19127
|
+
const embedding = normalizeQueue(input.outbox.embedding);
|
|
19128
|
+
const vector = normalizeQueue(input.outbox.vector);
|
|
19129
|
+
const totals = sumQueues(embedding, vector);
|
|
19130
|
+
const storage = {
|
|
19131
|
+
totalEvents: numberOrZero(input.stats.totalEvents),
|
|
19132
|
+
vectorCount: numberOrZero(input.stats.vectorCount),
|
|
19133
|
+
levelStats: normalizeLevelStats(input.stats.levelStats)
|
|
19134
|
+
};
|
|
19135
|
+
const warningReasons = [];
|
|
19136
|
+
const projectGate = buildProjectScopeGate(input.project);
|
|
19137
|
+
const outboxGate = buildOutboxGate(totals);
|
|
19138
|
+
const densityGate = buildMemoryDensityGate(storage.totalEvents);
|
|
19139
|
+
const riskGates = [projectGate, outboxGate, densityGate];
|
|
19140
|
+
if (projectGate.status === "warn") warningReasons.push("project_scope_unknown");
|
|
19141
|
+
if (outboxGate.status === "warn") warningReasons.push("outbox_requires_attention");
|
|
19142
|
+
if (densityGate.status === "warn") warningReasons.push("memory_density_low");
|
|
19143
|
+
const status = riskGates.some((gate) => gate.status === "warn") ? "needs-attention" : "ok";
|
|
19144
|
+
return {
|
|
19145
|
+
schemaVersion: PRODUCTIVITY_HEALTH_SCHEMA_VERSION,
|
|
19146
|
+
generatedAt: (input.generatedAt ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
19147
|
+
status,
|
|
19148
|
+
profile,
|
|
19149
|
+
mode,
|
|
19150
|
+
project: input.project,
|
|
19151
|
+
summary: { warningReasons },
|
|
19152
|
+
signals: {
|
|
19153
|
+
storage,
|
|
19154
|
+
outbox: { embedding, vector, totals }
|
|
19155
|
+
},
|
|
19156
|
+
riskGates,
|
|
19157
|
+
nextBestAction: selectNextBestAction({ projectGate, outboxGate, densityGate })
|
|
19158
|
+
};
|
|
19159
|
+
}
|
|
19160
|
+
function buildProjectScopeGate(project) {
|
|
19161
|
+
if (project.scope === "project") {
|
|
19162
|
+
return { id: "project-scope-known", severity: "blocker", status: "pass" };
|
|
19163
|
+
}
|
|
19164
|
+
return {
|
|
19165
|
+
id: "project-scope-known",
|
|
19166
|
+
severity: "blocker",
|
|
19167
|
+
status: "warn",
|
|
19168
|
+
message: "No project parameter supplied; report uses global memory scope."
|
|
19169
|
+
};
|
|
19170
|
+
}
|
|
19171
|
+
function buildOutboxGate(totals) {
|
|
19172
|
+
if (totals.failed > 0 || totals.stuckProcessing > 0) {
|
|
19173
|
+
return {
|
|
19174
|
+
id: "outbox-healthy",
|
|
19175
|
+
severity: "warning",
|
|
19176
|
+
status: "warn",
|
|
19177
|
+
message: "Outbox has failed or stuck processing items; run recovery before trusting retrieval freshness."
|
|
19178
|
+
};
|
|
19179
|
+
}
|
|
19180
|
+
return { id: "outbox-healthy", severity: "warning", status: "pass" };
|
|
19181
|
+
}
|
|
19182
|
+
function buildMemoryDensityGate(totalEvents) {
|
|
19183
|
+
if (totalEvents <= 0) {
|
|
19184
|
+
return {
|
|
19185
|
+
id: "memory-density",
|
|
19186
|
+
severity: "warning",
|
|
19187
|
+
status: "warn",
|
|
19188
|
+
message: "No memories are available for this scope yet."
|
|
19189
|
+
};
|
|
19190
|
+
}
|
|
19191
|
+
return { id: "memory-density", severity: "warning", status: "pass" };
|
|
19192
|
+
}
|
|
19193
|
+
function selectNextBestAction(input) {
|
|
19194
|
+
if (input.outboxGate.status === "warn") {
|
|
19195
|
+
return "Run claude-memory-layer process --dry-run-recovery, then process pending embeddings.";
|
|
19196
|
+
}
|
|
19197
|
+
if (input.densityGate.status === "warn") {
|
|
19198
|
+
return "Import or capture project context before relying on productivity memory guidance.";
|
|
19199
|
+
}
|
|
19200
|
+
if (input.projectGate.status === "warn") {
|
|
19201
|
+
return "Provide a project path or project hash before relying on project-scoped productivity guidance.";
|
|
19202
|
+
}
|
|
19203
|
+
return "No immediate maintenance action required.";
|
|
19204
|
+
}
|
|
19205
|
+
function normalizeLevelStats(levelStats) {
|
|
19206
|
+
if (!Array.isArray(levelStats)) return [];
|
|
19207
|
+
return levelStats.filter((entry) => typeof entry.level === "string" && entry.level.trim().length > 0).map((entry) => ({ level: entry.level.trim().slice(0, 20), count: numberOrZero(entry.count) }));
|
|
19208
|
+
}
|
|
19209
|
+
function normalizeQueue(queue) {
|
|
19210
|
+
return {
|
|
19211
|
+
pending: numberOrZero(queue.pending),
|
|
19212
|
+
processing: numberOrZero(queue.processing),
|
|
19213
|
+
failed: numberOrZero(queue.failed),
|
|
19214
|
+
retryableFailed: numberOrZero(queue.retryableFailed),
|
|
19215
|
+
quarantinedFailed: numberOrZero(queue.quarantinedFailed),
|
|
19216
|
+
total: numberOrZero(queue.total),
|
|
19217
|
+
stuckProcessing: numberOrZero(queue.stuckProcessing),
|
|
19218
|
+
oldestProcessingAgeMs: normalizeNullableAge(queue.oldestProcessingAgeMs)
|
|
19219
|
+
};
|
|
19220
|
+
}
|
|
19221
|
+
function sumQueues(a, b) {
|
|
19222
|
+
return {
|
|
19223
|
+
pending: a.pending + b.pending,
|
|
19224
|
+
processing: a.processing + b.processing,
|
|
19225
|
+
failed: a.failed + b.failed,
|
|
19226
|
+
retryableFailed: (a.retryableFailed ?? 0) + (b.retryableFailed ?? 0),
|
|
19227
|
+
quarantinedFailed: (a.quarantinedFailed ?? 0) + (b.quarantinedFailed ?? 0),
|
|
19228
|
+
total: a.total + b.total,
|
|
19229
|
+
stuckProcessing: a.stuckProcessing + b.stuckProcessing,
|
|
19230
|
+
oldestProcessingAgeMs: maxNullable(a.oldestProcessingAgeMs, b.oldestProcessingAgeMs)
|
|
19231
|
+
};
|
|
19232
|
+
}
|
|
19233
|
+
function normalizeNullableAge(value) {
|
|
19234
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : null;
|
|
19235
|
+
}
|
|
19236
|
+
function maxNullable(a, b) {
|
|
19237
|
+
if (a === null) return b;
|
|
19238
|
+
if (b === null) return a;
|
|
19239
|
+
return Math.max(a, b);
|
|
19240
|
+
}
|
|
19241
|
+
function numberOrZero(value) {
|
|
19242
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
|
|
19243
|
+
return Math.floor(value);
|
|
19244
|
+
}
|
|
19245
|
+
|
|
19246
|
+
// src/apps/server/api/health.ts
|
|
19103
19247
|
var healthRouter = new Hono9();
|
|
19104
19248
|
function hasEnvSignal(name) {
|
|
19105
19249
|
const value = process.env[name];
|
|
@@ -19140,6 +19284,33 @@ function aggregateOutbox(outbox) {
|
|
|
19140
19284
|
const stuckProcessing = (outbox.embedding?.stuckProcessing || 0) + (outbox.vector?.stuckProcessing || 0);
|
|
19141
19285
|
return { pending, processing, failed, retryableFailed, quarantinedFailed, stuckProcessing };
|
|
19142
19286
|
}
|
|
19287
|
+
function resolveProductivityProjectIdentity(project) {
|
|
19288
|
+
if (!project || project.trim().length === 0) return { scope: "global", id: "global" };
|
|
19289
|
+
const normalized = project.trim();
|
|
19290
|
+
return { scope: "project", id: /^[a-f0-9]{8}$/.test(normalized) ? normalized : hashProjectPath(normalized) };
|
|
19291
|
+
}
|
|
19292
|
+
function explicitProjectStoreExists(project) {
|
|
19293
|
+
if (!project || project.trim().length === 0) return true;
|
|
19294
|
+
return fs16.existsSync(path18.join(resolveProjectStoragePath(project.trim()), "events.sqlite"));
|
|
19295
|
+
}
|
|
19296
|
+
function emptyProductivityStats() {
|
|
19297
|
+
return { totalEvents: 0, vectorCount: 0, levelStats: [] };
|
|
19298
|
+
}
|
|
19299
|
+
function emptyProductivityQueue() {
|
|
19300
|
+
return {
|
|
19301
|
+
pending: 0,
|
|
19302
|
+
processing: 0,
|
|
19303
|
+
failed: 0,
|
|
19304
|
+
retryableFailed: 0,
|
|
19305
|
+
quarantinedFailed: 0,
|
|
19306
|
+
total: 0,
|
|
19307
|
+
stuckProcessing: 0,
|
|
19308
|
+
oldestProcessingAgeMs: null
|
|
19309
|
+
};
|
|
19310
|
+
}
|
|
19311
|
+
function emptyProductivityOutbox() {
|
|
19312
|
+
return { embedding: emptyProductivityQueue(), vector: emptyProductivityQueue() };
|
|
19313
|
+
}
|
|
19143
19314
|
healthRouter.get("/setup", async (c) => {
|
|
19144
19315
|
const memoryService = getLightweightServiceFromQuery(c);
|
|
19145
19316
|
try {
|
|
@@ -19196,6 +19367,40 @@ healthRouter.get("/setup", async (c) => {
|
|
|
19196
19367
|
await memoryService.shutdown();
|
|
19197
19368
|
}
|
|
19198
19369
|
});
|
|
19370
|
+
healthRouter.get("/productivity", async (c) => {
|
|
19371
|
+
let memoryService;
|
|
19372
|
+
try {
|
|
19373
|
+
const profile = parseProductivityHealthProfile(c.req.query("profile"));
|
|
19374
|
+
const mode = parseProductivityHealthMode(c.req.query("mode"));
|
|
19375
|
+
const projectQuery = c.req.query("project") || c.req.query("projectId");
|
|
19376
|
+
const project = resolveProductivityProjectIdentity(projectQuery);
|
|
19377
|
+
if (!explicitProjectStoreExists(projectQuery)) {
|
|
19378
|
+
return c.json(buildProductivityHealthReport({
|
|
19379
|
+
stats: emptyProductivityStats(),
|
|
19380
|
+
outbox: emptyProductivityOutbox(),
|
|
19381
|
+
project,
|
|
19382
|
+
profile,
|
|
19383
|
+
mode
|
|
19384
|
+
}));
|
|
19385
|
+
}
|
|
19386
|
+
memoryService = getLightweightServiceFromQuery(c);
|
|
19387
|
+
await memoryService.initialize();
|
|
19388
|
+
const [stats, outbox] = await Promise.all([
|
|
19389
|
+
memoryService.getStats(),
|
|
19390
|
+
memoryService.getOutboxStats()
|
|
19391
|
+
]);
|
|
19392
|
+
return c.json(buildProductivityHealthReport({ stats, outbox, project, profile, mode }));
|
|
19393
|
+
} catch (error) {
|
|
19394
|
+
const status = error instanceof Error && error.message.startsWith("Invalid --") ? 400 : 500;
|
|
19395
|
+
return c.json({
|
|
19396
|
+
status: "error",
|
|
19397
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19398
|
+
error: status === 400 ? "Invalid productivity health option" : "Productivity health check failed"
|
|
19399
|
+
}, status);
|
|
19400
|
+
} finally {
|
|
19401
|
+
if (memoryService) await memoryService.shutdown();
|
|
19402
|
+
}
|
|
19403
|
+
});
|
|
19199
19404
|
healthRouter.get("/", async (c) => {
|
|
19200
19405
|
const memoryService = getLightweightServiceFromQuery(c);
|
|
19201
19406
|
try {
|
|
@@ -19517,20 +19722,20 @@ playgroundRouter.post("/dry-run", async (c) => {
|
|
|
19517
19722
|
var apiRouter = new Hono11().route("/sessions", sessionsRouter).route("/events", eventsRouter).route("/search", searchRouter).route("/stats", statsRouter).route("/citations", citationsRouter).route("/turns", turnsRouter).route("/projects", projectsRouter).route("/chat", chatRouter).route("/health", healthRouter).route("/playground", playgroundRouter);
|
|
19518
19723
|
|
|
19519
19724
|
// src/apps/server/index.ts
|
|
19520
|
-
var moduleDir =
|
|
19725
|
+
var moduleDir = path19.dirname(fileUrlToPath(import.meta.url));
|
|
19521
19726
|
var DASHBOARD_SESSION_COOKIE = "cml_dashboard_session";
|
|
19522
19727
|
var DEFAULT_SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;
|
|
19523
19728
|
function resolveUiPath() {
|
|
19524
19729
|
const candidates = [
|
|
19525
19730
|
// Built server: dist/server/index.js -> dist/ui
|
|
19526
|
-
|
|
19731
|
+
path19.resolve(moduleDir, "../ui"),
|
|
19527
19732
|
// Source/dev server: src/apps/server/index.ts -> src/apps/dashboard
|
|
19528
|
-
|
|
19733
|
+
path19.resolve(moduleDir, "../dashboard"),
|
|
19529
19734
|
// Fallback when running from a repository root.
|
|
19530
|
-
|
|
19531
|
-
|
|
19735
|
+
path19.resolve(process.cwd(), "dist/ui"),
|
|
19736
|
+
path19.resolve(process.cwd(), "src/apps/dashboard")
|
|
19532
19737
|
];
|
|
19533
|
-
return candidates.find((candidate) =>
|
|
19738
|
+
return candidates.find((candidate) => fs17.existsSync(path19.join(candidate, "index.html"))) ?? candidates[0];
|
|
19534
19739
|
}
|
|
19535
19740
|
function normalizeDashboardHost(host = "localhost") {
|
|
19536
19741
|
const normalized = host.trim().toLowerCase();
|
|
@@ -19758,13 +19963,13 @@ function createDashboardApp(options = {}) {
|
|
|
19758
19963
|
}
|
|
19759
19964
|
app2.route("/api", apiRouter);
|
|
19760
19965
|
const uiPath = resolveUiPath();
|
|
19761
|
-
if (
|
|
19966
|
+
if (fs17.existsSync(uiPath)) {
|
|
19762
19967
|
app2.use("/*", serveStatic({ root: uiPath }));
|
|
19763
19968
|
}
|
|
19764
19969
|
app2.get("*", (c) => {
|
|
19765
|
-
const indexPath =
|
|
19766
|
-
if (
|
|
19767
|
-
return c.html(
|
|
19970
|
+
const indexPath = path19.join(uiPath, "index.html");
|
|
19971
|
+
if (fs17.existsSync(indexPath)) {
|
|
19972
|
+
return c.html(fs17.readFileSync(indexPath, "utf-8"));
|
|
19768
19973
|
}
|
|
19769
19974
|
return c.text('UI not built. Run "npm run build:ui" first.', 404);
|
|
19770
19975
|
});
|
|
@@ -20222,20 +20427,20 @@ function formatMetadataValue(value) {
|
|
|
20222
20427
|
}
|
|
20223
20428
|
|
|
20224
20429
|
// src/apps/cli/mcp-install.ts
|
|
20225
|
-
import { existsSync as
|
|
20226
|
-
import { dirname as dirname7, join as
|
|
20430
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync6, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
20431
|
+
import { dirname as dirname7, join as join19 } from "node:path";
|
|
20227
20432
|
import { homedir as homedir11 } from "node:os";
|
|
20228
20433
|
function getDefaultClaudeDesktopConfigPath(platform = process.platform, env = process.env, home = homedir11()) {
|
|
20229
20434
|
if (platform === "darwin") {
|
|
20230
|
-
return
|
|
20435
|
+
return join19(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
20231
20436
|
}
|
|
20232
20437
|
if (platform === "win32") {
|
|
20233
|
-
return
|
|
20438
|
+
return join19(env.APPDATA ?? join19(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
20234
20439
|
}
|
|
20235
|
-
return
|
|
20440
|
+
return join19(env.XDG_CONFIG_HOME ?? join19(home, ".config"), "claude", "claude_desktop_config.json");
|
|
20236
20441
|
}
|
|
20237
20442
|
function readJsonConfig(configPath) {
|
|
20238
|
-
if (!
|
|
20443
|
+
if (!existsSync15(configPath)) {
|
|
20239
20444
|
return {};
|
|
20240
20445
|
}
|
|
20241
20446
|
const content = readFileSync6(configPath, "utf8").trim();
|
|
@@ -20284,7 +20489,7 @@ function installMcpServer(options = {}) {
|
|
|
20284
20489
|
}
|
|
20285
20490
|
|
|
20286
20491
|
// src/apps/cli/claude-settings-hooks.ts
|
|
20287
|
-
import * as
|
|
20492
|
+
import * as path20 from "path";
|
|
20288
20493
|
var REQUIRED_HOOK_FILES = [
|
|
20289
20494
|
"user-prompt-submit.js",
|
|
20290
20495
|
"post-tool-use.js",
|
|
@@ -20303,7 +20508,7 @@ function shellQuotePathForNode(filePath) {
|
|
|
20303
20508
|
return `'${filePath.replace(/'/g, `'\\''`)}'`;
|
|
20304
20509
|
}
|
|
20305
20510
|
function buildHookCommand(pluginPath, fileName) {
|
|
20306
|
-
return `node ${shellQuotePathForNode(
|
|
20511
|
+
return `node ${shellQuotePathForNode(path20.join(pluginPath, "hooks", fileName))}`;
|
|
20307
20512
|
}
|
|
20308
20513
|
function getHooksConfig(pluginPath) {
|
|
20309
20514
|
const makeHook = (fileName) => [
|
|
@@ -20374,8 +20579,8 @@ function mergePluginHooksIntoSettings(settings, pluginPath) {
|
|
|
20374
20579
|
}
|
|
20375
20580
|
|
|
20376
20581
|
// src/apps/cli/codex-validation-output.ts
|
|
20377
|
-
import * as
|
|
20378
|
-
import * as
|
|
20582
|
+
import * as fs18 from "fs";
|
|
20583
|
+
import * as path21 from "path";
|
|
20379
20584
|
function formatNumber(value) {
|
|
20380
20585
|
return Number.isFinite(value) ? value.toLocaleString("en-US") : String(value);
|
|
20381
20586
|
}
|
|
@@ -20449,14 +20654,14 @@ function formatCodexValidationReport(report, format = "markdown") {
|
|
|
20449
20654
|
return lines.join("\n");
|
|
20450
20655
|
}
|
|
20451
20656
|
function writeCodexValidationReport(outputPath, report, format = "markdown") {
|
|
20452
|
-
const dir =
|
|
20453
|
-
|
|
20454
|
-
|
|
20657
|
+
const dir = path21.dirname(outputPath);
|
|
20658
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
20659
|
+
fs18.writeFileSync(outputPath, formatCodexValidationReport(report, format), "utf8");
|
|
20455
20660
|
}
|
|
20456
20661
|
|
|
20457
20662
|
// src/apps/cli/hermes-validation-output.ts
|
|
20458
|
-
import * as
|
|
20459
|
-
import * as
|
|
20663
|
+
import * as fs19 from "fs";
|
|
20664
|
+
import * as path22 from "path";
|
|
20460
20665
|
function formatNumber2(value) {
|
|
20461
20666
|
return Number.isFinite(value) ? value.toLocaleString("en-US") : String(value);
|
|
20462
20667
|
}
|
|
@@ -20526,9 +20731,9 @@ function formatHermesValidationReport(report, format = "markdown") {
|
|
|
20526
20731
|
return lines.join("\n");
|
|
20527
20732
|
}
|
|
20528
20733
|
function writeHermesValidationReport(outputPath, report, format = "markdown") {
|
|
20529
|
-
const dir =
|
|
20530
|
-
|
|
20531
|
-
|
|
20734
|
+
const dir = path22.dirname(outputPath);
|
|
20735
|
+
fs19.mkdirSync(dir, { recursive: true });
|
|
20736
|
+
fs19.writeFileSync(outputPath, formatHermesValidationReport(report, format), "utf8");
|
|
20532
20737
|
}
|
|
20533
20738
|
|
|
20534
20739
|
// src/apps/cli/codex-import-runner.ts
|
|
@@ -21215,7 +21420,7 @@ function redactSecrets(input) {
|
|
|
21215
21420
|
}
|
|
21216
21421
|
|
|
21217
21422
|
// src/apps/cli/process-command.ts
|
|
21218
|
-
import * as
|
|
21423
|
+
import * as path23 from "node:path";
|
|
21219
21424
|
function resolveProcessCommandOptions(options, cwd = process.cwd(), deps = {}) {
|
|
21220
21425
|
const explicitProject = options.project;
|
|
21221
21426
|
if (explicitProject !== void 0 && explicitProject.trim().length === 0) {
|
|
@@ -21236,7 +21441,7 @@ function resolveProcessCommandOptions(options, cwd = process.cwd(), deps = {}) {
|
|
|
21236
21441
|
projectPath,
|
|
21237
21442
|
recoverStuck,
|
|
21238
21443
|
dryRunRecovery,
|
|
21239
|
-
lockPath: explicitLockPath ??
|
|
21444
|
+
lockPath: explicitLockPath ?? path23.join(getProjectStoragePath2(projectPath), "vector-worker.lock")
|
|
21240
21445
|
};
|
|
21241
21446
|
}
|
|
21242
21447
|
function formatProcessLockBusy(input) {
|
|
@@ -21256,7 +21461,7 @@ function formatProcessRecoveryPreview(input) {
|
|
|
21256
21461
|
const embeddingRetried = input.recovery.embedding.retriedFailed;
|
|
21257
21462
|
const vectorRetried = input.recovery.vector.retriedFailed;
|
|
21258
21463
|
const retriedTotal = embeddingRetried + vectorRetried;
|
|
21259
|
-
const oldestProcessingAge =
|
|
21464
|
+
const oldestProcessingAge = maxNullable2(
|
|
21260
21465
|
input.stats.embedding.oldestProcessingAgeMs,
|
|
21261
21466
|
input.stats.vector.oldestProcessingAgeMs
|
|
21262
21467
|
);
|
|
@@ -21271,7 +21476,7 @@ function formatProcessRecoveryPreview(input) {
|
|
|
21271
21476
|
`Next command: claude-memory-layer process --project ${quoteCliArg(input.projectPath)}`
|
|
21272
21477
|
].join("\n");
|
|
21273
21478
|
}
|
|
21274
|
-
function
|
|
21479
|
+
function maxNullable2(a, b) {
|
|
21275
21480
|
if (a === null) return b;
|
|
21276
21481
|
if (b === null) return a;
|
|
21277
21482
|
return Math.max(a, b);
|
|
@@ -21331,15 +21536,15 @@ function formatVectorStatusJsonReport(input) {
|
|
|
21331
21536
|
return JSON.stringify(buildVectorStatusReport(input), null, 2);
|
|
21332
21537
|
}
|
|
21333
21538
|
function buildVectorStatusReport(input) {
|
|
21334
|
-
const embedding =
|
|
21335
|
-
const vector =
|
|
21336
|
-
const totals =
|
|
21539
|
+
const embedding = normalizeQueue2(input.outbox.embedding);
|
|
21540
|
+
const vector = normalizeQueue2(input.outbox.vector);
|
|
21541
|
+
const totals = sumQueues2(embedding, vector);
|
|
21337
21542
|
const status = totals.failed > 0 || totals.stuckProcessing > 0 ? "needs-attention" : "ok";
|
|
21338
|
-
const oldestProcessingAgeMs =
|
|
21543
|
+
const oldestProcessingAgeMs = maxNullable3(embedding.oldestProcessingAgeMs, vector.oldestProcessingAgeMs);
|
|
21339
21544
|
return {
|
|
21340
21545
|
storage: {
|
|
21341
|
-
totalEvents:
|
|
21342
|
-
vectorCount:
|
|
21546
|
+
totalEvents: numberOrZero2(input.stats.totalEvents),
|
|
21547
|
+
vectorCount: numberOrZero2(input.stats.vectorCount)
|
|
21343
21548
|
},
|
|
21344
21549
|
outbox: { embedding, vector, totals },
|
|
21345
21550
|
status,
|
|
@@ -21352,19 +21557,19 @@ function selectRecommendedAction(totals) {
|
|
|
21352
21557
|
if ((totals.quarantinedFailed ?? 0) > 0) return "inspect-quarantined";
|
|
21353
21558
|
return "none";
|
|
21354
21559
|
}
|
|
21355
|
-
function
|
|
21560
|
+
function normalizeQueue2(queue) {
|
|
21356
21561
|
return {
|
|
21357
|
-
pending:
|
|
21358
|
-
processing:
|
|
21359
|
-
failed:
|
|
21360
|
-
retryableFailed:
|
|
21361
|
-
quarantinedFailed:
|
|
21362
|
-
total:
|
|
21363
|
-
stuckProcessing:
|
|
21562
|
+
pending: numberOrZero2(queue.pending),
|
|
21563
|
+
processing: numberOrZero2(queue.processing),
|
|
21564
|
+
failed: numberOrZero2(queue.failed),
|
|
21565
|
+
retryableFailed: numberOrZero2(queue.retryableFailed),
|
|
21566
|
+
quarantinedFailed: numberOrZero2(queue.quarantinedFailed),
|
|
21567
|
+
total: numberOrZero2(queue.total),
|
|
21568
|
+
stuckProcessing: numberOrZero2(queue.stuckProcessing),
|
|
21364
21569
|
oldestProcessingAgeMs: Number.isFinite(queue.oldestProcessingAgeMs ?? Number.NaN) ? queue.oldestProcessingAgeMs : null
|
|
21365
21570
|
};
|
|
21366
21571
|
}
|
|
21367
|
-
function
|
|
21572
|
+
function sumQueues2(a, b) {
|
|
21368
21573
|
return {
|
|
21369
21574
|
pending: a.pending + b.pending,
|
|
21370
21575
|
processing: a.processing + b.processing,
|
|
@@ -21373,7 +21578,7 @@ function sumQueues(a, b) {
|
|
|
21373
21578
|
quarantinedFailed: (a.quarantinedFailed ?? 0) + (b.quarantinedFailed ?? 0),
|
|
21374
21579
|
stuckProcessing: a.stuckProcessing + b.stuckProcessing,
|
|
21375
21580
|
total: a.total + b.total,
|
|
21376
|
-
oldestProcessingAgeMs:
|
|
21581
|
+
oldestProcessingAgeMs: maxNullable3(a.oldestProcessingAgeMs, b.oldestProcessingAgeMs)
|
|
21377
21582
|
};
|
|
21378
21583
|
}
|
|
21379
21584
|
function formatQueueRow(label, queue) {
|
|
@@ -21389,12 +21594,12 @@ function formatQueueRow(label, queue) {
|
|
|
21389
21594
|
formatDuration2(queue.oldestProcessingAgeMs).padStart(7)
|
|
21390
21595
|
].join(" ");
|
|
21391
21596
|
}
|
|
21392
|
-
function
|
|
21597
|
+
function maxNullable3(a, b) {
|
|
21393
21598
|
if (a === null) return b;
|
|
21394
21599
|
if (b === null) return a;
|
|
21395
21600
|
return Math.max(a, b);
|
|
21396
21601
|
}
|
|
21397
|
-
function
|
|
21602
|
+
function numberOrZero2(value) {
|
|
21398
21603
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
|
|
21399
21604
|
return Math.floor(value);
|
|
21400
21605
|
}
|
|
@@ -21414,12 +21619,12 @@ function formatDuration2(ms) {
|
|
|
21414
21619
|
}
|
|
21415
21620
|
|
|
21416
21621
|
// src/apps/cli/mongo-sync-command.ts
|
|
21417
|
-
import * as
|
|
21622
|
+
import * as path25 from "node:path";
|
|
21418
21623
|
|
|
21419
21624
|
// src/core/worker-lock.ts
|
|
21420
|
-
import { closeSync, existsSync as
|
|
21625
|
+
import { closeSync, existsSync as existsSync16, mkdirSync as mkdirSync10, openSync, readFileSync as readFileSync7, unlinkSync, writeFileSync as writeFileSync7 } from "node:fs";
|
|
21421
21626
|
import * as os12 from "node:os";
|
|
21422
|
-
import * as
|
|
21627
|
+
import * as path24 from "node:path";
|
|
21423
21628
|
import { randomUUID as randomUUID18 } from "node:crypto";
|
|
21424
21629
|
var WorkerLock = class {
|
|
21425
21630
|
lockPath;
|
|
@@ -21428,7 +21633,7 @@ var WorkerLock = class {
|
|
|
21428
21633
|
now;
|
|
21429
21634
|
isProcessRunning;
|
|
21430
21635
|
acquired = false;
|
|
21431
|
-
constructor(lockPath =
|
|
21636
|
+
constructor(lockPath = path24.join(os12.tmpdir(), "claude-memory-layer", "vector-worker.lock"), options = {}) {
|
|
21432
21637
|
this.lockPath = lockPath;
|
|
21433
21638
|
this.pid = options.pid ?? process.pid;
|
|
21434
21639
|
this.ownerId = options.ownerId ?? randomUUID18();
|
|
@@ -21441,7 +21646,7 @@ var WorkerLock = class {
|
|
|
21441
21646
|
acquire() {
|
|
21442
21647
|
let staleRecovered = false;
|
|
21443
21648
|
for (; ; ) {
|
|
21444
|
-
mkdirSync10(
|
|
21649
|
+
mkdirSync10(path24.dirname(this.lockPath), { recursive: true });
|
|
21445
21650
|
const createResult = this.tryCreateLockFile();
|
|
21446
21651
|
if (createResult === "created") {
|
|
21447
21652
|
this.acquired = true;
|
|
@@ -21475,7 +21680,7 @@ var WorkerLock = class {
|
|
|
21475
21680
|
}
|
|
21476
21681
|
}
|
|
21477
21682
|
release() {
|
|
21478
|
-
if (!this.acquired || !
|
|
21683
|
+
if (!this.acquired || !existsSync16(this.lockPath)) return false;
|
|
21479
21684
|
const existing = this.readPayload();
|
|
21480
21685
|
if (!this.isOwnedPayload(existing)) return false;
|
|
21481
21686
|
try {
|
|
@@ -21573,7 +21778,7 @@ function resolveMongoSyncProcessOptions(options, cwd = process.cwd(), deps = {})
|
|
|
21573
21778
|
projectPath,
|
|
21574
21779
|
processAfterSync: options.processAfterSync === true,
|
|
21575
21780
|
processIntervalMs,
|
|
21576
|
-
lockPath: explicitLockPath ??
|
|
21781
|
+
lockPath: explicitLockPath ?? path25.join(getProjectStoragePath2(projectPath), "vector-worker.lock")
|
|
21577
21782
|
};
|
|
21578
21783
|
}
|
|
21579
21784
|
function createMongoSyncPostProcessor(options, deps = {}) {
|
|
@@ -21656,28 +21861,28 @@ function parsePositiveIntegerOption2(raw, flagName) {
|
|
|
21656
21861
|
}
|
|
21657
21862
|
|
|
21658
21863
|
// src/apps/cli/index.ts
|
|
21659
|
-
var CLAUDE_SETTINGS_PATH =
|
|
21864
|
+
var CLAUDE_SETTINGS_PATH = path26.join(os13.homedir(), ".claude", "settings.json");
|
|
21660
21865
|
function getPluginPath() {
|
|
21661
21866
|
const possiblePaths = [
|
|
21662
|
-
|
|
21867
|
+
path26.join(__dirname, ".."),
|
|
21663
21868
|
// When running from dist/cli
|
|
21664
|
-
|
|
21869
|
+
path26.join(__dirname, "../..", "dist"),
|
|
21665
21870
|
// When running from src
|
|
21666
|
-
|
|
21871
|
+
path26.join(process.cwd(), "dist")
|
|
21667
21872
|
// Current working directory
|
|
21668
21873
|
];
|
|
21669
21874
|
for (const p of possiblePaths) {
|
|
21670
|
-
const hooksPath =
|
|
21671
|
-
if (
|
|
21875
|
+
const hooksPath = path26.join(p, "hooks", "user-prompt-submit.js");
|
|
21876
|
+
if (fs20.existsSync(hooksPath)) {
|
|
21672
21877
|
return p;
|
|
21673
21878
|
}
|
|
21674
21879
|
}
|
|
21675
|
-
return
|
|
21880
|
+
return path26.join(os13.homedir(), ".npm-global", "lib", "node_modules", "claude-memory-layer", "dist");
|
|
21676
21881
|
}
|
|
21677
21882
|
function loadClaudeSettings() {
|
|
21678
21883
|
try {
|
|
21679
|
-
if (
|
|
21680
|
-
const content =
|
|
21884
|
+
if (fs20.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
21885
|
+
const content = fs20.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8");
|
|
21681
21886
|
return JSON.parse(content);
|
|
21682
21887
|
}
|
|
21683
21888
|
} catch (error) {
|
|
@@ -21686,13 +21891,52 @@ function loadClaudeSettings() {
|
|
|
21686
21891
|
return {};
|
|
21687
21892
|
}
|
|
21688
21893
|
function saveClaudeSettings(settings) {
|
|
21689
|
-
const dir =
|
|
21690
|
-
if (!
|
|
21691
|
-
|
|
21894
|
+
const dir = path26.dirname(CLAUDE_SETTINGS_PATH);
|
|
21895
|
+
if (!fs20.existsSync(dir)) {
|
|
21896
|
+
fs20.mkdirSync(dir, { recursive: true });
|
|
21692
21897
|
}
|
|
21693
21898
|
const tempPath = CLAUDE_SETTINGS_PATH + ".tmp";
|
|
21694
|
-
|
|
21695
|
-
|
|
21899
|
+
fs20.writeFileSync(tempPath, JSON.stringify(settings, null, 2));
|
|
21900
|
+
fs20.renameSync(tempPath, CLAUDE_SETTINGS_PATH);
|
|
21901
|
+
}
|
|
21902
|
+
function resolveHealthProjectIdentity(projectPath) {
|
|
21903
|
+
if (!projectPath || projectPath.trim().length === 0) return { scope: "global", id: "global" };
|
|
21904
|
+
const normalized = projectPath.trim();
|
|
21905
|
+
return { scope: "project", id: /^[a-f0-9]{8}$/.test(normalized) ? normalized : hashProjectPath(normalized) };
|
|
21906
|
+
}
|
|
21907
|
+
function resolveHealthStoragePath(projectPathOrHash) {
|
|
21908
|
+
const normalized = projectPathOrHash?.trim();
|
|
21909
|
+
return normalized && normalized.length > 0 ? resolveProjectStoragePath(normalized) : path26.join(os13.homedir(), ".claude-code", "memory");
|
|
21910
|
+
}
|
|
21911
|
+
function createHealthMemoryService(projectPathOrHash) {
|
|
21912
|
+
return new MemoryService({
|
|
21913
|
+
storagePath: resolveHealthStoragePath(projectPathOrHash),
|
|
21914
|
+
readOnly: true,
|
|
21915
|
+
lightweightMode: true,
|
|
21916
|
+
analyticsEnabled: false,
|
|
21917
|
+
sharedStoreConfig: DISABLED_SHARED_STORE_CONFIG
|
|
21918
|
+
});
|
|
21919
|
+
}
|
|
21920
|
+
function healthStoreExists(projectPathOrHash) {
|
|
21921
|
+
return fs20.existsSync(path26.join(resolveHealthStoragePath(projectPathOrHash), "events.sqlite"));
|
|
21922
|
+
}
|
|
21923
|
+
function emptyHealthStats() {
|
|
21924
|
+
return { totalEvents: 0, vectorCount: 0, levelStats: [] };
|
|
21925
|
+
}
|
|
21926
|
+
function emptyHealthQueue() {
|
|
21927
|
+
return {
|
|
21928
|
+
pending: 0,
|
|
21929
|
+
processing: 0,
|
|
21930
|
+
failed: 0,
|
|
21931
|
+
retryableFailed: 0,
|
|
21932
|
+
quarantinedFailed: 0,
|
|
21933
|
+
total: 0,
|
|
21934
|
+
stuckProcessing: 0,
|
|
21935
|
+
oldestProcessingAgeMs: null
|
|
21936
|
+
};
|
|
21937
|
+
}
|
|
21938
|
+
function emptyHealthOutbox() {
|
|
21939
|
+
return { embedding: emptyHealthQueue(), vector: emptyHealthQueue() };
|
|
21696
21940
|
}
|
|
21697
21941
|
function parseCommaList(value) {
|
|
21698
21942
|
if (!value) return void 0;
|
|
@@ -21752,7 +21996,7 @@ async function runCodexValidationCommand(options) {
|
|
|
21752
21996
|
process.stdout.write(rendered.endsWith("\n") ? rendered : `${rendered}
|
|
21753
21997
|
`);
|
|
21754
21998
|
if (options.output) {
|
|
21755
|
-
const outputPath =
|
|
21999
|
+
const outputPath = path26.resolve(options.output);
|
|
21756
22000
|
writeCodexValidationReport(outputPath, report, format);
|
|
21757
22001
|
console.log(`
|
|
21758
22002
|
Report written to ${outputPath}`);
|
|
@@ -21772,7 +22016,7 @@ async function runHermesValidationCommand(options) {
|
|
|
21772
22016
|
process.stdout.write(rendered.endsWith("\n") ? rendered : `${rendered}
|
|
21773
22017
|
`);
|
|
21774
22018
|
if (options.output) {
|
|
21775
|
-
const outputPath =
|
|
22019
|
+
const outputPath = path26.resolve(options.output);
|
|
21776
22020
|
writeHermesValidationReport(outputPath, report, format);
|
|
21777
22021
|
console.log(`
|
|
21778
22022
|
Report written to ${outputPath}`);
|
|
@@ -21806,18 +22050,18 @@ var OPERATION_PRIVACY_CONFIG = {
|
|
|
21806
22050
|
}
|
|
21807
22051
|
};
|
|
21808
22052
|
function resolveOperationProject(project) {
|
|
21809
|
-
const projectPath =
|
|
22053
|
+
const projectPath = path26.resolve(project ?? process.cwd());
|
|
21810
22054
|
const projectHash = hashProjectPath(projectPath);
|
|
21811
22055
|
const storagePath = getProjectStoragePath(projectPath);
|
|
21812
22056
|
return {
|
|
21813
22057
|
projectPath,
|
|
21814
22058
|
projectHash,
|
|
21815
22059
|
storagePath,
|
|
21816
|
-
dbPath:
|
|
22060
|
+
dbPath: path26.join(storagePath, "events.sqlite")
|
|
21817
22061
|
};
|
|
21818
22062
|
}
|
|
21819
22063
|
function openOperationReadDatabase(context) {
|
|
21820
|
-
if (!
|
|
22064
|
+
if (!fs20.existsSync(context.dbPath)) return null;
|
|
21821
22065
|
return createSQLiteDatabase(context.dbPath, { readonly: true, walMode: false });
|
|
21822
22066
|
}
|
|
21823
22067
|
async function withOperationWriteDatabase(context, callback) {
|
|
@@ -22027,7 +22271,7 @@ async function runOperationCli(action, label) {
|
|
|
22027
22271
|
}
|
|
22028
22272
|
}
|
|
22029
22273
|
var program = new Command();
|
|
22030
|
-
program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.
|
|
22274
|
+
program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.55");
|
|
22031
22275
|
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) => {
|
|
22032
22276
|
try {
|
|
22033
22277
|
await runMarketContextCommand(options);
|
|
@@ -22040,7 +22284,7 @@ program.command("install").description("Install hooks into Claude Code settings"
|
|
|
22040
22284
|
try {
|
|
22041
22285
|
const pluginPath = options.path || getPluginPath();
|
|
22042
22286
|
const missingHooks = REQUIRED_HOOK_FILES.filter(
|
|
22043
|
-
(file) => !
|
|
22287
|
+
(file) => !fs20.existsSync(path26.join(pluginPath, "hooks", file))
|
|
22044
22288
|
);
|
|
22045
22289
|
if (missingHooks.length > 0) {
|
|
22046
22290
|
console.error(`
|
|
@@ -22106,7 +22350,7 @@ program.command("status").description("Check plugin installation status").action
|
|
|
22106
22350
|
console.log(` PostToolUse: ${hasPostToolHook ? "\u2705 Installed" : "\u274C Not installed"}`);
|
|
22107
22351
|
console.log(` Stop: ${hasStopHook ? "\u2705 Installed" : "\u274C Not installed"}`);
|
|
22108
22352
|
console.log(` SessionEnd: ${hasSessionEndHook ? "\u2705 Installed" : "\u274C Not installed"}`);
|
|
22109
|
-
const hooksExist = REQUIRED_HOOK_FILES.every((file) =>
|
|
22353
|
+
const hooksExist = REQUIRED_HOOK_FILES.every((file) => fs20.existsSync(path26.join(pluginPath, "hooks", file)));
|
|
22110
22354
|
console.log(`
|
|
22111
22355
|
Plugin files: ${hooksExist ? "\u2705 Found" : "\u274C Not found"}`);
|
|
22112
22356
|
console.log(` Path: ${pluginPath}`);
|
|
@@ -22241,6 +22485,40 @@ program.command("stats").description("View memory statistics").option("-p, --pro
|
|
|
22241
22485
|
process.exit(1);
|
|
22242
22486
|
}
|
|
22243
22487
|
});
|
|
22488
|
+
program.command("health").description("Show aggregate health reports for agent productivity workflows").option("--productivity", "Print Project Health Report for agent productivity workflows").option("--json", "Print health report as JSON").option("-p, --project <path>", "Project path or 8-character project hash").option("--profile <profile>", "Agent profile: coder, reviewer, pm, support, researcher, team", "coder").option("--mode <mode>", "Report mode: observe, preview, enforce", "preview").action(async (options) => {
|
|
22489
|
+
let service;
|
|
22490
|
+
try {
|
|
22491
|
+
if (options.productivity !== true) {
|
|
22492
|
+
throw new Error("Use --productivity to select the Project Health Report");
|
|
22493
|
+
}
|
|
22494
|
+
if (options.json !== true) {
|
|
22495
|
+
throw new Error("Only --json output is currently supported for productivity health");
|
|
22496
|
+
}
|
|
22497
|
+
const profile = parseProductivityHealthProfile(options.profile);
|
|
22498
|
+
const mode = parseProductivityHealthMode(options.mode);
|
|
22499
|
+
const projectPath = options.project ?? process.cwd();
|
|
22500
|
+
const project = resolveHealthProjectIdentity(projectPath);
|
|
22501
|
+
let stats;
|
|
22502
|
+
let outbox;
|
|
22503
|
+
if (healthStoreExists(projectPath)) {
|
|
22504
|
+
service = createHealthMemoryService(projectPath);
|
|
22505
|
+
[stats, outbox] = await Promise.all([
|
|
22506
|
+
service.getStats(),
|
|
22507
|
+
service.getOutboxStats()
|
|
22508
|
+
]);
|
|
22509
|
+
} else {
|
|
22510
|
+
stats = emptyHealthStats();
|
|
22511
|
+
outbox = emptyHealthOutbox();
|
|
22512
|
+
}
|
|
22513
|
+
console.log(JSON.stringify(buildProductivityHealthReport({ stats, outbox, project, profile, mode }), null, 2));
|
|
22514
|
+
} catch (error) {
|
|
22515
|
+
const message = error instanceof Error ? error.message : "Health report failed";
|
|
22516
|
+
console.error(message.startsWith("Invalid --") || message.startsWith("Use --") || message.startsWith("Only --") ? message : "Health report failed");
|
|
22517
|
+
process.exit(1);
|
|
22518
|
+
} finally {
|
|
22519
|
+
await service?.shutdown().catch(() => void 0);
|
|
22520
|
+
}
|
|
22521
|
+
});
|
|
22244
22522
|
program.command("vector-status").description("Show aggregate vector outbox status").option("-p, --project <path>", "Project path (defaults to cwd)").option("--json", "Print aggregate vector outbox status as JSON for automation").action(async (options) => {
|
|
22245
22523
|
let service;
|
|
22246
22524
|
try {
|
|
@@ -22341,8 +22619,8 @@ repairCommand.command("legacy-project-scope").description("Dry-run or apply proj
|
|
|
22341
22619
|
apply: options.apply
|
|
22342
22620
|
});
|
|
22343
22621
|
const storagePath = projectPath ? getProjectStoragePath(projectPath) : resolveProjectStoragePath(repairOptions.projectHash);
|
|
22344
|
-
const dbPath =
|
|
22345
|
-
if (repairOptions.dryRun && !
|
|
22622
|
+
const dbPath = path26.join(storagePath, "events.sqlite");
|
|
22623
|
+
if (repairOptions.dryRun && !fs20.existsSync(dbPath)) {
|
|
22346
22624
|
const projectHash = repairOptions.projectHash || hashProjectPath(repairOptions.projectPath);
|
|
22347
22625
|
console.log(formatLegacyProjectScopeRepairResult({
|
|
22348
22626
|
dryRun: true,
|
|
@@ -22589,8 +22867,8 @@ retentionCommand.command("audit").description("Run a dry-run retention audit for
|
|
|
22589
22867
|
});
|
|
22590
22868
|
const projectHash = auditOptions.projectHash ?? hashProjectPath(auditOptions.projectPath);
|
|
22591
22869
|
const storagePath = auditOptions.projectPath ? getProjectStoragePath(auditOptions.projectPath) : resolveProjectStoragePath(projectHash);
|
|
22592
|
-
const dbPath =
|
|
22593
|
-
if (!
|
|
22870
|
+
const dbPath = path26.join(storagePath, "events.sqlite");
|
|
22871
|
+
if (!fs20.existsSync(dbPath)) {
|
|
22594
22872
|
const emptyReport = emptyRetentionAuditReport(projectHash, auditOptions.limit);
|
|
22595
22873
|
console.log(formatRetentionAuditReport(emptyReport, { json: auditOptions.json }));
|
|
22596
22874
|
return;
|
|
@@ -22617,7 +22895,7 @@ program.command("mongo-sync").description("Sync events with MongoDB for multi-se
|
|
|
22617
22895
|
const projectPath = options.project || process.cwd();
|
|
22618
22896
|
const mongoUri = options.mongoUri || process.env.CLAUDE_MEMORY_MONGO_URI;
|
|
22619
22897
|
const mongoDb = options.mongoDb || process.env.CLAUDE_MEMORY_MONGO_DB;
|
|
22620
|
-
const projectKey = options.mongoProject || process.env.CLAUDE_MEMORY_MONGO_PROJECT ||
|
|
22898
|
+
const projectKey = options.mongoProject || process.env.CLAUDE_MEMORY_MONGO_PROJECT || path26.basename(projectPath);
|
|
22621
22899
|
const direction = String(options.direction || "both").toLowerCase();
|
|
22622
22900
|
if (!mongoUri || !mongoDb) {
|
|
22623
22901
|
console.error("\n\u274C MongoDB sync is not configured.");
|
|
@@ -22647,14 +22925,14 @@ program.command("mongo-sync").description("Sync events with MongoDB for multi-se
|
|
|
22647
22925
|
}
|
|
22648
22926
|
})();
|
|
22649
22927
|
const storagePath = getProjectStoragePath(projectPath);
|
|
22650
|
-
if (!
|
|
22651
|
-
|
|
22928
|
+
if (!fs20.existsSync(storagePath)) {
|
|
22929
|
+
fs20.mkdirSync(storagePath, { recursive: true });
|
|
22652
22930
|
}
|
|
22653
22931
|
const postProcessor = createMongoSyncPostProcessor(processOptions, {
|
|
22654
22932
|
log: (message) => process.stdout.write(`${message}
|
|
22655
22933
|
`)
|
|
22656
22934
|
});
|
|
22657
|
-
const sqliteStore = new SQLiteEventStore(
|
|
22935
|
+
const sqliteStore = new SQLiteEventStore(path26.join(storagePath, "events.sqlite"));
|
|
22658
22936
|
const worker = new MongoSyncWorker(sqliteStore, {
|
|
22659
22937
|
uri: mongoUri,
|
|
22660
22938
|
dbName: mongoDb,
|
|
@@ -22714,7 +22992,7 @@ function renderProgress(event) {
|
|
|
22714
22992
|
break;
|
|
22715
22993
|
case "session-start": {
|
|
22716
22994
|
const pct = Math.round(event.sessionIndex / event.totalSessions * 100);
|
|
22717
|
-
const sessionName =
|
|
22995
|
+
const sessionName = path26.basename(event.filePath, ".jsonl").slice(0, 8);
|
|
22718
22996
|
process.stdout.write(
|
|
22719
22997
|
`\r \u{1F4C4} [${event.sessionIndex + 1}/${event.totalSessions}] ${pct}% | Session ${sessionName}... `
|
|
22720
22998
|
);
|
|
@@ -22789,9 +23067,9 @@ async function listMarkdownFiles(root) {
|
|
|
22789
23067
|
const stack = [root];
|
|
22790
23068
|
while (stack.length > 0) {
|
|
22791
23069
|
const dir = stack.pop();
|
|
22792
|
-
const entries = await
|
|
23070
|
+
const entries = await fs20.promises.readdir(dir, { withFileTypes: true });
|
|
22793
23071
|
for (const e of entries) {
|
|
22794
|
-
const full =
|
|
23072
|
+
const full = path26.join(dir, e.name);
|
|
22795
23073
|
if (e.isDirectory()) stack.push(full);
|
|
22796
23074
|
else if (e.isFile() && e.name.endsWith(".md") && e.name !== "_index.md") out.push(full);
|
|
22797
23075
|
}
|
|
@@ -22799,8 +23077,8 @@ async function listMarkdownFiles(root) {
|
|
|
22799
23077
|
return out.sort();
|
|
22800
23078
|
}
|
|
22801
23079
|
function deriveNamespaceCategory(sourceRoot, filePath) {
|
|
22802
|
-
const rel =
|
|
22803
|
-
const dirSeg =
|
|
23080
|
+
const rel = path26.relative(sourceRoot, filePath);
|
|
23081
|
+
const dirSeg = path26.dirname(rel).split(path26.sep).filter(Boolean);
|
|
22804
23082
|
if (dirSeg.length >= 2) {
|
|
22805
23083
|
const namespace = sanitizeSegment3(dirSeg[0], "default");
|
|
22806
23084
|
const categoryPath = dirSeg.slice(1).map((s) => sanitizeSegment3(s, "uncategorized"));
|
|
@@ -22819,10 +23097,10 @@ function extractImportEvidence(markdown) {
|
|
|
22819
23097
|
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) => {
|
|
22820
23098
|
const projectPath = options.project || process.cwd();
|
|
22821
23099
|
const sessionId = options.session || "import:organized";
|
|
22822
|
-
const sourceRoot =
|
|
22823
|
-
const repoPath =
|
|
22824
|
-
if (!
|
|
22825
|
-
|
|
23100
|
+
const sourceRoot = path26.resolve(sourceDir || options.out || projectPath);
|
|
23101
|
+
const repoPath = path26.resolve(options.repo || projectPath);
|
|
23102
|
+
if (!fs20.existsSync(sourceRoot)) {
|
|
23103
|
+
fs20.mkdirSync(sourceRoot, { recursive: true });
|
|
22826
23104
|
}
|
|
22827
23105
|
const service = getMemoryServiceForProject(projectPath);
|
|
22828
23106
|
try {
|
|
@@ -22832,7 +23110,7 @@ program.command("organize-import [sourceDir]").description("Import existing mark
|
|
|
22832
23110
|
const hasMarkdown = files.length > 0;
|
|
22833
23111
|
const shouldBootstrap = Boolean(options.forceBootstrap || options.bootstrap || !hasMarkdown && options.bootstrapIfEmpty);
|
|
22834
23112
|
if (shouldBootstrap) {
|
|
22835
|
-
const outDir =
|
|
23113
|
+
const outDir = path26.resolve(options.out || path26.join(sourceRoot, "bootstrap-kb"));
|
|
22836
23114
|
const since = options.since || "180 days ago";
|
|
22837
23115
|
const maxCommits = options.maxCommits ? Math.max(1, parseInt(options.maxCommits, 10)) : 1e3;
|
|
22838
23116
|
console.log("\n\u{1F9E0} Bootstrapping markdown knowledge base...");
|
|
@@ -22871,13 +23149,13 @@ program.command("organize-import [sourceDir]").description("Import existing mark
|
|
|
22871
23149
|
let imported = 0;
|
|
22872
23150
|
let skipped = 0;
|
|
22873
23151
|
for (const file of targets) {
|
|
22874
|
-
const text = await
|
|
23152
|
+
const text = await fs20.promises.readFile(file, "utf8");
|
|
22875
23153
|
if (!text.trim()) {
|
|
22876
23154
|
skipped += 1;
|
|
22877
23155
|
continue;
|
|
22878
23156
|
}
|
|
22879
23157
|
const { namespace, categoryPath } = deriveNamespaceCategory(activeSourceRoot, file);
|
|
22880
|
-
const rel =
|
|
23158
|
+
const rel = path26.relative(activeSourceRoot, file);
|
|
22881
23159
|
const evidence = extractImportEvidence(text);
|
|
22882
23160
|
if (options.dryRun) {
|
|
22883
23161
|
console.log(`- ${rel} -> namespace=${namespace} category=${categoryPath.join("/")} confidence=${evidence.confidence || "n/a"} sources=${evidence.sources.length}`);
|