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.
@@ -12,8 +12,8 @@ import { getCookie, setCookie, deleteCookie } from "hono/cookie";
12
12
  import { logger } from "hono/logger";
13
13
  import { serve } from "@hono/node-server";
14
14
  import { serveStatic } from "@hono/node-server/serve-static";
15
- import * as path14 from "path";
16
- import * as fs12 from "fs";
15
+ import * as path15 from "path";
16
+ import * as fs13 from "fs";
17
17
  import { createHmac, timingSafeEqual } from "crypto";
18
18
  import { fileURLToPath as fileUrlToPath } from "url";
19
19
 
@@ -2255,12 +2255,12 @@ function resolveProjectStoragePath(projectOrHash) {
2255
2255
  import Database from "better-sqlite3";
2256
2256
  import * as fs4 from "fs";
2257
2257
  import * as nodePath from "path";
2258
- function createSQLiteDatabase(path15, options) {
2259
- const dir = nodePath.dirname(path15);
2258
+ function createSQLiteDatabase(path16, options) {
2259
+ const dir = nodePath.dirname(path16);
2260
2260
  if (!fs4.existsSync(dir)) {
2261
2261
  fs4.mkdirSync(dir, { recursive: true });
2262
2262
  }
2263
- const db = new Database(path15, {
2263
+ const db = new Database(path16, {
2264
2264
  readonly: options?.readonly ?? false
2265
2265
  });
2266
2266
  if (!options?.readonly && (options?.walMode ?? true)) {
@@ -2723,17 +2723,17 @@ function emptyOutboxRecoveryResult() {
2723
2723
  function isRecord(value) {
2724
2724
  return typeof value === "object" && value !== null && !Array.isArray(value);
2725
2725
  }
2726
- function getNestedRecord(root, path15) {
2726
+ function getNestedRecord(root, path16) {
2727
2727
  let cursor = root;
2728
- for (const key of path15) {
2728
+ for (const key of path16) {
2729
2729
  if (!isRecord(cursor)) return void 0;
2730
2730
  cursor = cursor[key];
2731
2731
  }
2732
2732
  return isRecord(cursor) ? cursor : void 0;
2733
2733
  }
2734
- function getNestedString(root, path15) {
2734
+ function getNestedString(root, path16) {
2735
2735
  let cursor = root;
2736
- for (const key of path15) {
2736
+ for (const key of path16) {
2737
2737
  if (!isRecord(cursor)) return void 0;
2738
2738
  cursor = cursor[key];
2739
2739
  }
@@ -6696,12 +6696,12 @@ var GraphPathService = class {
6696
6696
  }
6697
6697
  }
6698
6698
  }
6699
- const paths = Array.from(bestByTarget.entries()).map(([key, path15]) => ({
6699
+ const paths = Array.from(bestByTarget.entries()).map(([key, path16]) => ({
6700
6700
  target: graph.node(nodeFromKey(key)),
6701
- hops: path15.hops,
6702
- totalCost: path15.totalCost,
6703
- scoreContribution: path15.totalCost > 0 ? 1 / path15.totalCost : 0,
6704
- steps: path15.steps
6701
+ hops: path16.hops,
6702
+ totalCost: path16.totalCost,
6703
+ scoreContribution: path16.totalCost > 0 ? 1 / path16.totalCost : 0,
6704
+ steps: path16.steps
6705
6705
  })).sort((a, b) => b.scoreContribution - a.scoreContribution || a.hops - b.hops || a.target.name.localeCompare(b.target.name)).slice(0, maxResults);
6706
6706
  return { startNodes, effectiveMaxHops, paths };
6707
6707
  }
@@ -9474,12 +9474,12 @@ var Retriever = class {
9474
9474
  direction: "both"
9475
9475
  });
9476
9476
  const titleByEntityId = new Map(startNodes.map((node) => [node.entityId, node.title]));
9477
- for (const path15 of expansion.paths) {
9478
- if (path15.target.type !== "event") continue;
9479
- const target = await this.eventStore.getEvent(path15.target.id);
9477
+ for (const path16 of expansion.paths) {
9478
+ if (path16.target.type !== "event") continue;
9479
+ const target = await this.eventStore.getEvent(path16.target.id);
9480
9480
  if (!target) continue;
9481
- const graphPath = toRetrievalGraphPathDebug(path15, titleByEntityId);
9482
- const score = graphPathScore(path15, opts.hopPenalty);
9481
+ const graphPath = toRetrievalGraphPathDebug(path16, titleByEntityId);
9482
+ const score = graphPathScore(path16, opts.hopPenalty);
9483
9483
  const existing = byId.get(target.id);
9484
9484
  const graphPaths = mergeGraphPaths(existing?.graphPaths ?? [], [graphPath]);
9485
9485
  const graphLane = {
@@ -9488,7 +9488,7 @@ var Retriever = class {
9488
9488
  score
9489
9489
  };
9490
9490
  const row = {
9491
- id: existing?.id ?? `graph-path-${path15.hops}-${target.id}`,
9491
+ id: existing?.id ?? `graph-path-${path16.hops}-${target.id}`,
9492
9492
  eventId: target.id,
9493
9493
  content: target.content,
9494
9494
  score: Math.max(existing?.score ?? 0, score),
@@ -9828,8 +9828,8 @@ _Context:_ ${sessionContext}`;
9828
9828
  }
9829
9829
  matchesMetadataScope(metadata, expected) {
9830
9830
  if (!metadata) return false;
9831
- return Object.entries(expected).every(([path15, value]) => {
9832
- const actual = path15.split(".").reduce((acc, key) => {
9831
+ return Object.entries(expected).every(([path16, value]) => {
9832
+ const actual = path16.split(".").reduce((acc, key) => {
9833
9833
  if (typeof acc !== "object" || acc === null) return void 0;
9834
9834
  return acc[key];
9835
9835
  }, metadata);
@@ -9880,22 +9880,22 @@ function uniqueEntityStartNodes(candidates) {
9880
9880
  }
9881
9881
  return nodes;
9882
9882
  }
9883
- function toRetrievalGraphPathDebug(path15, titleByEntityId) {
9884
- const firstStep = path15.steps[0];
9883
+ function toRetrievalGraphPathDebug(path16, titleByEntityId) {
9884
+ const firstStep = path16.steps[0];
9885
9885
  const startNode = firstStep?.direction === "incoming" ? firstStep.to : firstStep?.from;
9886
9886
  const startEntityId = startNode?.type === "entity" ? startNode.id : "";
9887
9887
  return {
9888
9888
  startEntityId,
9889
9889
  startEntityTitle: titleByEntityId.get(startEntityId) ?? startNode?.name,
9890
- targetId: path15.target.id,
9891
- targetType: path15.target.type,
9892
- hops: path15.hops,
9893
- relationPath: path15.steps.map((step) => step.relationType)
9890
+ targetId: path16.target.id,
9891
+ targetType: path16.target.type,
9892
+ hops: path16.hops,
9893
+ relationPath: path16.steps.map((step) => step.relationType)
9894
9894
  };
9895
9895
  }
9896
- function graphPathScore(path15, hopPenalty) {
9897
- const base = Math.min(0.95, Math.max(0, path15.scoreContribution));
9898
- return Math.max(0.05, base - hopPenalty * Math.max(0, path15.hops - 1));
9896
+ function graphPathScore(path16, hopPenalty) {
9897
+ const base = Math.min(0.95, Math.max(0, path16.scoreContribution));
9898
+ return Math.max(0.05, base - hopPenalty * Math.max(0, path16.hops - 1));
9899
9899
  }
9900
9900
  function clampGraphHops(maxHops) {
9901
9901
  if (!Number.isFinite(maxHops)) return 2;
@@ -9903,14 +9903,14 @@ function clampGraphHops(maxHops) {
9903
9903
  }
9904
9904
  function mergeGraphPaths(existing, incoming) {
9905
9905
  const byKey = /* @__PURE__ */ new Map();
9906
- for (const path15 of [...existing, ...incoming]) {
9907
- const key = [path15.startEntityId, path15.targetType, path15.targetId, path15.hops, ...path15.relationPath].join("\0");
9908
- if (!byKey.has(key)) byKey.set(key, path15);
9906
+ for (const path16 of [...existing, ...incoming]) {
9907
+ const key = [path16.startEntityId, path16.targetType, path16.targetId, path16.hops, ...path16.relationPath].join("\0");
9908
+ if (!byKey.has(key)) byKey.set(key, path16);
9909
9909
  }
9910
9910
  return [...byKey.values()].sort((a, b) => a.hops - b.hops || compareStable(graphPathSignature(a), graphPathSignature(b))).slice(0, 3);
9911
9911
  }
9912
- function graphPathSignature(path15) {
9913
- return [path15.startEntityId, path15.targetType, path15.targetId, path15.hops, ...path15.relationPath].join("|");
9912
+ function graphPathSignature(path16) {
9913
+ return [path16.startEntityId, path16.targetType, path16.targetId, path16.hops, ...path16.relationPath].join("|");
9914
9914
  }
9915
9915
  function compareStable(a, b) {
9916
9916
  if (a < b) return -1;
@@ -15573,6 +15573,150 @@ function streamClaudeResponse(prompt, stream) {
15573
15573
  // src/apps/server/api/health.ts
15574
15574
  import { Hono as Hono9 } from "hono";
15575
15575
  import { spawnSync } from "child_process";
15576
+ import * as fs12 from "fs";
15577
+ import * as path14 from "path";
15578
+
15579
+ // src/core/productivity-health-report.ts
15580
+ var PRODUCTIVITY_HEALTH_SCHEMA_VERSION = "agent-productivity-health-v1";
15581
+ var PRODUCTIVITY_HEALTH_PROFILES = ["coder", "reviewer", "pm", "support", "researcher", "team"];
15582
+ var PRODUCTIVITY_HEALTH_MODES = ["observe", "preview", "enforce"];
15583
+ function parseProductivityHealthProfile(value) {
15584
+ const normalized = typeof value === "string" && value.trim().length > 0 ? value.trim() : "coder";
15585
+ if (PRODUCTIVITY_HEALTH_PROFILES.includes(normalized)) {
15586
+ return normalized;
15587
+ }
15588
+ throw new Error(`Invalid --profile: expected one of ${PRODUCTIVITY_HEALTH_PROFILES.join(", ")}`);
15589
+ }
15590
+ function parseProductivityHealthMode(value) {
15591
+ const normalized = typeof value === "string" && value.trim().length > 0 ? value.trim() : "preview";
15592
+ if (PRODUCTIVITY_HEALTH_MODES.includes(normalized)) {
15593
+ return normalized;
15594
+ }
15595
+ throw new Error(`Invalid --mode: expected one of ${PRODUCTIVITY_HEALTH_MODES.join(", ")}`);
15596
+ }
15597
+ function buildProductivityHealthReport(input) {
15598
+ const profile = input.profile ?? "coder";
15599
+ const mode = input.mode ?? "preview";
15600
+ const embedding = normalizeQueue(input.outbox.embedding);
15601
+ const vector = normalizeQueue(input.outbox.vector);
15602
+ const totals = sumQueues(embedding, vector);
15603
+ const storage = {
15604
+ totalEvents: numberOrZero(input.stats.totalEvents),
15605
+ vectorCount: numberOrZero(input.stats.vectorCount),
15606
+ levelStats: normalizeLevelStats(input.stats.levelStats)
15607
+ };
15608
+ const warningReasons = [];
15609
+ const projectGate = buildProjectScopeGate(input.project);
15610
+ const outboxGate = buildOutboxGate(totals);
15611
+ const densityGate = buildMemoryDensityGate(storage.totalEvents);
15612
+ const riskGates = [projectGate, outboxGate, densityGate];
15613
+ if (projectGate.status === "warn") warningReasons.push("project_scope_unknown");
15614
+ if (outboxGate.status === "warn") warningReasons.push("outbox_requires_attention");
15615
+ if (densityGate.status === "warn") warningReasons.push("memory_density_low");
15616
+ const status = riskGates.some((gate) => gate.status === "warn") ? "needs-attention" : "ok";
15617
+ return {
15618
+ schemaVersion: PRODUCTIVITY_HEALTH_SCHEMA_VERSION,
15619
+ generatedAt: (input.generatedAt ?? /* @__PURE__ */ new Date()).toISOString(),
15620
+ status,
15621
+ profile,
15622
+ mode,
15623
+ project: input.project,
15624
+ summary: { warningReasons },
15625
+ signals: {
15626
+ storage,
15627
+ outbox: { embedding, vector, totals }
15628
+ },
15629
+ riskGates,
15630
+ nextBestAction: selectNextBestAction({ projectGate, outboxGate, densityGate })
15631
+ };
15632
+ }
15633
+ function buildProjectScopeGate(project) {
15634
+ if (project.scope === "project") {
15635
+ return { id: "project-scope-known", severity: "blocker", status: "pass" };
15636
+ }
15637
+ return {
15638
+ id: "project-scope-known",
15639
+ severity: "blocker",
15640
+ status: "warn",
15641
+ message: "No project parameter supplied; report uses global memory scope."
15642
+ };
15643
+ }
15644
+ function buildOutboxGate(totals) {
15645
+ if (totals.failed > 0 || totals.stuckProcessing > 0) {
15646
+ return {
15647
+ id: "outbox-healthy",
15648
+ severity: "warning",
15649
+ status: "warn",
15650
+ message: "Outbox has failed or stuck processing items; run recovery before trusting retrieval freshness."
15651
+ };
15652
+ }
15653
+ return { id: "outbox-healthy", severity: "warning", status: "pass" };
15654
+ }
15655
+ function buildMemoryDensityGate(totalEvents) {
15656
+ if (totalEvents <= 0) {
15657
+ return {
15658
+ id: "memory-density",
15659
+ severity: "warning",
15660
+ status: "warn",
15661
+ message: "No memories are available for this scope yet."
15662
+ };
15663
+ }
15664
+ return { id: "memory-density", severity: "warning", status: "pass" };
15665
+ }
15666
+ function selectNextBestAction(input) {
15667
+ if (input.outboxGate.status === "warn") {
15668
+ return "Run claude-memory-layer process --dry-run-recovery, then process pending embeddings.";
15669
+ }
15670
+ if (input.densityGate.status === "warn") {
15671
+ return "Import or capture project context before relying on productivity memory guidance.";
15672
+ }
15673
+ if (input.projectGate.status === "warn") {
15674
+ return "Provide a project path or project hash before relying on project-scoped productivity guidance.";
15675
+ }
15676
+ return "No immediate maintenance action required.";
15677
+ }
15678
+ function normalizeLevelStats(levelStats) {
15679
+ if (!Array.isArray(levelStats)) return [];
15680
+ 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) }));
15681
+ }
15682
+ function normalizeQueue(queue) {
15683
+ return {
15684
+ pending: numberOrZero(queue.pending),
15685
+ processing: numberOrZero(queue.processing),
15686
+ failed: numberOrZero(queue.failed),
15687
+ retryableFailed: numberOrZero(queue.retryableFailed),
15688
+ quarantinedFailed: numberOrZero(queue.quarantinedFailed),
15689
+ total: numberOrZero(queue.total),
15690
+ stuckProcessing: numberOrZero(queue.stuckProcessing),
15691
+ oldestProcessingAgeMs: normalizeNullableAge(queue.oldestProcessingAgeMs)
15692
+ };
15693
+ }
15694
+ function sumQueues(a, b) {
15695
+ return {
15696
+ pending: a.pending + b.pending,
15697
+ processing: a.processing + b.processing,
15698
+ failed: a.failed + b.failed,
15699
+ retryableFailed: (a.retryableFailed ?? 0) + (b.retryableFailed ?? 0),
15700
+ quarantinedFailed: (a.quarantinedFailed ?? 0) + (b.quarantinedFailed ?? 0),
15701
+ total: a.total + b.total,
15702
+ stuckProcessing: a.stuckProcessing + b.stuckProcessing,
15703
+ oldestProcessingAgeMs: maxNullable(a.oldestProcessingAgeMs, b.oldestProcessingAgeMs)
15704
+ };
15705
+ }
15706
+ function normalizeNullableAge(value) {
15707
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : null;
15708
+ }
15709
+ function maxNullable(a, b) {
15710
+ if (a === null) return b;
15711
+ if (b === null) return a;
15712
+ return Math.max(a, b);
15713
+ }
15714
+ function numberOrZero(value) {
15715
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
15716
+ return Math.floor(value);
15717
+ }
15718
+
15719
+ // src/apps/server/api/health.ts
15576
15720
  var healthRouter = new Hono9();
15577
15721
  function hasEnvSignal(name) {
15578
15722
  const value = process.env[name];
@@ -15613,6 +15757,33 @@ function aggregateOutbox(outbox) {
15613
15757
  const stuckProcessing = (outbox.embedding?.stuckProcessing || 0) + (outbox.vector?.stuckProcessing || 0);
15614
15758
  return { pending, processing, failed, retryableFailed, quarantinedFailed, stuckProcessing };
15615
15759
  }
15760
+ function resolveProductivityProjectIdentity(project) {
15761
+ if (!project || project.trim().length === 0) return { scope: "global", id: "global" };
15762
+ const normalized = project.trim();
15763
+ return { scope: "project", id: /^[a-f0-9]{8}$/.test(normalized) ? normalized : hashProjectPath(normalized) };
15764
+ }
15765
+ function explicitProjectStoreExists(project) {
15766
+ if (!project || project.trim().length === 0) return true;
15767
+ return fs12.existsSync(path14.join(resolveProjectStoragePath(project.trim()), "events.sqlite"));
15768
+ }
15769
+ function emptyProductivityStats() {
15770
+ return { totalEvents: 0, vectorCount: 0, levelStats: [] };
15771
+ }
15772
+ function emptyProductivityQueue() {
15773
+ return {
15774
+ pending: 0,
15775
+ processing: 0,
15776
+ failed: 0,
15777
+ retryableFailed: 0,
15778
+ quarantinedFailed: 0,
15779
+ total: 0,
15780
+ stuckProcessing: 0,
15781
+ oldestProcessingAgeMs: null
15782
+ };
15783
+ }
15784
+ function emptyProductivityOutbox() {
15785
+ return { embedding: emptyProductivityQueue(), vector: emptyProductivityQueue() };
15786
+ }
15616
15787
  healthRouter.get("/setup", async (c) => {
15617
15788
  const memoryService = getLightweightServiceFromQuery(c);
15618
15789
  try {
@@ -15669,6 +15840,40 @@ healthRouter.get("/setup", async (c) => {
15669
15840
  await memoryService.shutdown();
15670
15841
  }
15671
15842
  });
15843
+ healthRouter.get("/productivity", async (c) => {
15844
+ let memoryService;
15845
+ try {
15846
+ const profile = parseProductivityHealthProfile(c.req.query("profile"));
15847
+ const mode = parseProductivityHealthMode(c.req.query("mode"));
15848
+ const projectQuery = c.req.query("project") || c.req.query("projectId");
15849
+ const project = resolveProductivityProjectIdentity(projectQuery);
15850
+ if (!explicitProjectStoreExists(projectQuery)) {
15851
+ return c.json(buildProductivityHealthReport({
15852
+ stats: emptyProductivityStats(),
15853
+ outbox: emptyProductivityOutbox(),
15854
+ project,
15855
+ profile,
15856
+ mode
15857
+ }));
15858
+ }
15859
+ memoryService = getLightweightServiceFromQuery(c);
15860
+ await memoryService.initialize();
15861
+ const [stats, outbox] = await Promise.all([
15862
+ memoryService.getStats(),
15863
+ memoryService.getOutboxStats()
15864
+ ]);
15865
+ return c.json(buildProductivityHealthReport({ stats, outbox, project, profile, mode }));
15866
+ } catch (error) {
15867
+ const status = error instanceof Error && error.message.startsWith("Invalid --") ? 400 : 500;
15868
+ return c.json({
15869
+ status: "error",
15870
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
15871
+ error: status === 400 ? "Invalid productivity health option" : "Productivity health check failed"
15872
+ }, status);
15873
+ } finally {
15874
+ if (memoryService) await memoryService.shutdown();
15875
+ }
15876
+ });
15672
15877
  healthRouter.get("/", async (c) => {
15673
15878
  const memoryService = getLightweightServiceFromQuery(c);
15674
15879
  try {
@@ -15990,20 +16195,20 @@ playgroundRouter.post("/dry-run", async (c) => {
15990
16195
  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);
15991
16196
 
15992
16197
  // src/apps/server/index.ts
15993
- var moduleDir = path14.dirname(fileUrlToPath(import.meta.url));
16198
+ var moduleDir = path15.dirname(fileUrlToPath(import.meta.url));
15994
16199
  var DASHBOARD_SESSION_COOKIE = "cml_dashboard_session";
15995
16200
  var DEFAULT_SESSION_MAX_AGE_SECONDS = 60 * 60 * 24 * 30;
15996
16201
  function resolveUiPath() {
15997
16202
  const candidates = [
15998
16203
  // Built server: dist/server/index.js -> dist/ui
15999
- path14.resolve(moduleDir, "../ui"),
16204
+ path15.resolve(moduleDir, "../ui"),
16000
16205
  // Source/dev server: src/apps/server/index.ts -> src/apps/dashboard
16001
- path14.resolve(moduleDir, "../dashboard"),
16206
+ path15.resolve(moduleDir, "../dashboard"),
16002
16207
  // Fallback when running from a repository root.
16003
- path14.resolve(process.cwd(), "dist/ui"),
16004
- path14.resolve(process.cwd(), "src/apps/dashboard")
16208
+ path15.resolve(process.cwd(), "dist/ui"),
16209
+ path15.resolve(process.cwd(), "src/apps/dashboard")
16005
16210
  ];
16006
- return candidates.find((candidate) => fs12.existsSync(path14.join(candidate, "index.html"))) ?? candidates[0];
16211
+ return candidates.find((candidate) => fs13.existsSync(path15.join(candidate, "index.html"))) ?? candidates[0];
16007
16212
  }
16008
16213
  function normalizeDashboardHost(host = "localhost") {
16009
16214
  const normalized = host.trim().toLowerCase();
@@ -16231,13 +16436,13 @@ function createDashboardApp(options = {}) {
16231
16436
  }
16232
16437
  app2.route("/api", apiRouter);
16233
16438
  const uiPath = resolveUiPath();
16234
- if (fs12.existsSync(uiPath)) {
16439
+ if (fs13.existsSync(uiPath)) {
16235
16440
  app2.use("/*", serveStatic({ root: uiPath }));
16236
16441
  }
16237
16442
  app2.get("*", (c) => {
16238
- const indexPath = path14.join(uiPath, "index.html");
16239
- if (fs12.existsSync(indexPath)) {
16240
- return c.html(fs12.readFileSync(indexPath, "utf-8"));
16443
+ const indexPath = path15.join(uiPath, "index.html");
16444
+ if (fs13.existsSync(indexPath)) {
16445
+ return c.html(fs13.readFileSync(indexPath, "utf-8"));
16241
16446
  }
16242
16447
  return c.text('UI not built. Run "npm run build:ui" first.', 404);
16243
16448
  });