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.
@@ -2243,12 +2243,12 @@ function resolveProjectStoragePath(projectOrHash) {
2243
2243
  import Database from "better-sqlite3";
2244
2244
  import * as fs4 from "fs";
2245
2245
  import * as nodePath from "path";
2246
- function createSQLiteDatabase(path14, options) {
2247
- const dir = nodePath.dirname(path14);
2246
+ function createSQLiteDatabase(path15, options) {
2247
+ const dir = nodePath.dirname(path15);
2248
2248
  if (!fs4.existsSync(dir)) {
2249
2249
  fs4.mkdirSync(dir, { recursive: true });
2250
2250
  }
2251
- const db = new Database(path14, {
2251
+ const db = new Database(path15, {
2252
2252
  readonly: options?.readonly ?? false
2253
2253
  });
2254
2254
  if (!options?.readonly && (options?.walMode ?? true)) {
@@ -2711,17 +2711,17 @@ function emptyOutboxRecoveryResult() {
2711
2711
  function isRecord(value) {
2712
2712
  return typeof value === "object" && value !== null && !Array.isArray(value);
2713
2713
  }
2714
- function getNestedRecord(root, path14) {
2714
+ function getNestedRecord(root, path15) {
2715
2715
  let cursor = root;
2716
- for (const key of path14) {
2716
+ for (const key of path15) {
2717
2717
  if (!isRecord(cursor)) return void 0;
2718
2718
  cursor = cursor[key];
2719
2719
  }
2720
2720
  return isRecord(cursor) ? cursor : void 0;
2721
2721
  }
2722
- function getNestedString(root, path14) {
2722
+ function getNestedString(root, path15) {
2723
2723
  let cursor = root;
2724
- for (const key of path14) {
2724
+ for (const key of path15) {
2725
2725
  if (!isRecord(cursor)) return void 0;
2726
2726
  cursor = cursor[key];
2727
2727
  }
@@ -6684,12 +6684,12 @@ var GraphPathService = class {
6684
6684
  }
6685
6685
  }
6686
6686
  }
6687
- const paths = Array.from(bestByTarget.entries()).map(([key, path14]) => ({
6687
+ const paths = Array.from(bestByTarget.entries()).map(([key, path15]) => ({
6688
6688
  target: graph.node(nodeFromKey(key)),
6689
- hops: path14.hops,
6690
- totalCost: path14.totalCost,
6691
- scoreContribution: path14.totalCost > 0 ? 1 / path14.totalCost : 0,
6692
- steps: path14.steps
6689
+ hops: path15.hops,
6690
+ totalCost: path15.totalCost,
6691
+ scoreContribution: path15.totalCost > 0 ? 1 / path15.totalCost : 0,
6692
+ steps: path15.steps
6693
6693
  })).sort((a, b) => b.scoreContribution - a.scoreContribution || a.hops - b.hops || a.target.name.localeCompare(b.target.name)).slice(0, maxResults);
6694
6694
  return { startNodes, effectiveMaxHops, paths };
6695
6695
  }
@@ -9462,12 +9462,12 @@ var Retriever = class {
9462
9462
  direction: "both"
9463
9463
  });
9464
9464
  const titleByEntityId = new Map(startNodes.map((node) => [node.entityId, node.title]));
9465
- for (const path14 of expansion.paths) {
9466
- if (path14.target.type !== "event") continue;
9467
- const target = await this.eventStore.getEvent(path14.target.id);
9465
+ for (const path15 of expansion.paths) {
9466
+ if (path15.target.type !== "event") continue;
9467
+ const target = await this.eventStore.getEvent(path15.target.id);
9468
9468
  if (!target) continue;
9469
- const graphPath = toRetrievalGraphPathDebug(path14, titleByEntityId);
9470
- const score = graphPathScore(path14, opts.hopPenalty);
9469
+ const graphPath = toRetrievalGraphPathDebug(path15, titleByEntityId);
9470
+ const score = graphPathScore(path15, opts.hopPenalty);
9471
9471
  const existing = byId.get(target.id);
9472
9472
  const graphPaths = mergeGraphPaths(existing?.graphPaths ?? [], [graphPath]);
9473
9473
  const graphLane = {
@@ -9476,7 +9476,7 @@ var Retriever = class {
9476
9476
  score
9477
9477
  };
9478
9478
  const row = {
9479
- id: existing?.id ?? `graph-path-${path14.hops}-${target.id}`,
9479
+ id: existing?.id ?? `graph-path-${path15.hops}-${target.id}`,
9480
9480
  eventId: target.id,
9481
9481
  content: target.content,
9482
9482
  score: Math.max(existing?.score ?? 0, score),
@@ -9816,8 +9816,8 @@ _Context:_ ${sessionContext}`;
9816
9816
  }
9817
9817
  matchesMetadataScope(metadata, expected) {
9818
9818
  if (!metadata) return false;
9819
- return Object.entries(expected).every(([path14, value]) => {
9820
- const actual = path14.split(".").reduce((acc, key) => {
9819
+ return Object.entries(expected).every(([path15, value]) => {
9820
+ const actual = path15.split(".").reduce((acc, key) => {
9821
9821
  if (typeof acc !== "object" || acc === null) return void 0;
9822
9822
  return acc[key];
9823
9823
  }, metadata);
@@ -9868,22 +9868,22 @@ function uniqueEntityStartNodes(candidates) {
9868
9868
  }
9869
9869
  return nodes;
9870
9870
  }
9871
- function toRetrievalGraphPathDebug(path14, titleByEntityId) {
9872
- const firstStep = path14.steps[0];
9871
+ function toRetrievalGraphPathDebug(path15, titleByEntityId) {
9872
+ const firstStep = path15.steps[0];
9873
9873
  const startNode = firstStep?.direction === "incoming" ? firstStep.to : firstStep?.from;
9874
9874
  const startEntityId = startNode?.type === "entity" ? startNode.id : "";
9875
9875
  return {
9876
9876
  startEntityId,
9877
9877
  startEntityTitle: titleByEntityId.get(startEntityId) ?? startNode?.name,
9878
- targetId: path14.target.id,
9879
- targetType: path14.target.type,
9880
- hops: path14.hops,
9881
- relationPath: path14.steps.map((step) => step.relationType)
9878
+ targetId: path15.target.id,
9879
+ targetType: path15.target.type,
9880
+ hops: path15.hops,
9881
+ relationPath: path15.steps.map((step) => step.relationType)
9882
9882
  };
9883
9883
  }
9884
- function graphPathScore(path14, hopPenalty) {
9885
- const base = Math.min(0.95, Math.max(0, path14.scoreContribution));
9886
- return Math.max(0.05, base - hopPenalty * Math.max(0, path14.hops - 1));
9884
+ function graphPathScore(path15, hopPenalty) {
9885
+ const base = Math.min(0.95, Math.max(0, path15.scoreContribution));
9886
+ return Math.max(0.05, base - hopPenalty * Math.max(0, path15.hops - 1));
9887
9887
  }
9888
9888
  function clampGraphHops(maxHops) {
9889
9889
  if (!Number.isFinite(maxHops)) return 2;
@@ -9891,14 +9891,14 @@ function clampGraphHops(maxHops) {
9891
9891
  }
9892
9892
  function mergeGraphPaths(existing, incoming) {
9893
9893
  const byKey = /* @__PURE__ */ new Map();
9894
- for (const path14 of [...existing, ...incoming]) {
9895
- const key = [path14.startEntityId, path14.targetType, path14.targetId, path14.hops, ...path14.relationPath].join("\0");
9896
- if (!byKey.has(key)) byKey.set(key, path14);
9894
+ for (const path15 of [...existing, ...incoming]) {
9895
+ const key = [path15.startEntityId, path15.targetType, path15.targetId, path15.hops, ...path15.relationPath].join("\0");
9896
+ if (!byKey.has(key)) byKey.set(key, path15);
9897
9897
  }
9898
9898
  return [...byKey.values()].sort((a, b) => a.hops - b.hops || compareStable(graphPathSignature(a), graphPathSignature(b))).slice(0, 3);
9899
9899
  }
9900
- function graphPathSignature(path14) {
9901
- return [path14.startEntityId, path14.targetType, path14.targetId, path14.hops, ...path14.relationPath].join("|");
9900
+ function graphPathSignature(path15) {
9901
+ return [path15.startEntityId, path15.targetType, path15.targetId, path15.hops, ...path15.relationPath].join("|");
9902
9902
  }
9903
9903
  function compareStable(a, b) {
9904
9904
  if (a < b) return -1;
@@ -15561,6 +15561,150 @@ function streamClaudeResponse(prompt, stream) {
15561
15561
  // src/apps/server/api/health.ts
15562
15562
  import { Hono as Hono9 } from "hono";
15563
15563
  import { spawnSync } from "child_process";
15564
+ import * as fs12 from "fs";
15565
+ import * as path14 from "path";
15566
+
15567
+ // src/core/productivity-health-report.ts
15568
+ var PRODUCTIVITY_HEALTH_SCHEMA_VERSION = "agent-productivity-health-v1";
15569
+ var PRODUCTIVITY_HEALTH_PROFILES = ["coder", "reviewer", "pm", "support", "researcher", "team"];
15570
+ var PRODUCTIVITY_HEALTH_MODES = ["observe", "preview", "enforce"];
15571
+ function parseProductivityHealthProfile(value) {
15572
+ const normalized = typeof value === "string" && value.trim().length > 0 ? value.trim() : "coder";
15573
+ if (PRODUCTIVITY_HEALTH_PROFILES.includes(normalized)) {
15574
+ return normalized;
15575
+ }
15576
+ throw new Error(`Invalid --profile: expected one of ${PRODUCTIVITY_HEALTH_PROFILES.join(", ")}`);
15577
+ }
15578
+ function parseProductivityHealthMode(value) {
15579
+ const normalized = typeof value === "string" && value.trim().length > 0 ? value.trim() : "preview";
15580
+ if (PRODUCTIVITY_HEALTH_MODES.includes(normalized)) {
15581
+ return normalized;
15582
+ }
15583
+ throw new Error(`Invalid --mode: expected one of ${PRODUCTIVITY_HEALTH_MODES.join(", ")}`);
15584
+ }
15585
+ function buildProductivityHealthReport(input) {
15586
+ const profile = input.profile ?? "coder";
15587
+ const mode = input.mode ?? "preview";
15588
+ const embedding = normalizeQueue(input.outbox.embedding);
15589
+ const vector = normalizeQueue(input.outbox.vector);
15590
+ const totals = sumQueues(embedding, vector);
15591
+ const storage = {
15592
+ totalEvents: numberOrZero(input.stats.totalEvents),
15593
+ vectorCount: numberOrZero(input.stats.vectorCount),
15594
+ levelStats: normalizeLevelStats(input.stats.levelStats)
15595
+ };
15596
+ const warningReasons = [];
15597
+ const projectGate = buildProjectScopeGate(input.project);
15598
+ const outboxGate = buildOutboxGate(totals);
15599
+ const densityGate = buildMemoryDensityGate(storage.totalEvents);
15600
+ const riskGates = [projectGate, outboxGate, densityGate];
15601
+ if (projectGate.status === "warn") warningReasons.push("project_scope_unknown");
15602
+ if (outboxGate.status === "warn") warningReasons.push("outbox_requires_attention");
15603
+ if (densityGate.status === "warn") warningReasons.push("memory_density_low");
15604
+ const status = riskGates.some((gate) => gate.status === "warn") ? "needs-attention" : "ok";
15605
+ return {
15606
+ schemaVersion: PRODUCTIVITY_HEALTH_SCHEMA_VERSION,
15607
+ generatedAt: (input.generatedAt ?? /* @__PURE__ */ new Date()).toISOString(),
15608
+ status,
15609
+ profile,
15610
+ mode,
15611
+ project: input.project,
15612
+ summary: { warningReasons },
15613
+ signals: {
15614
+ storage,
15615
+ outbox: { embedding, vector, totals }
15616
+ },
15617
+ riskGates,
15618
+ nextBestAction: selectNextBestAction({ projectGate, outboxGate, densityGate })
15619
+ };
15620
+ }
15621
+ function buildProjectScopeGate(project) {
15622
+ if (project.scope === "project") {
15623
+ return { id: "project-scope-known", severity: "blocker", status: "pass" };
15624
+ }
15625
+ return {
15626
+ id: "project-scope-known",
15627
+ severity: "blocker",
15628
+ status: "warn",
15629
+ message: "No project parameter supplied; report uses global memory scope."
15630
+ };
15631
+ }
15632
+ function buildOutboxGate(totals) {
15633
+ if (totals.failed > 0 || totals.stuckProcessing > 0) {
15634
+ return {
15635
+ id: "outbox-healthy",
15636
+ severity: "warning",
15637
+ status: "warn",
15638
+ message: "Outbox has failed or stuck processing items; run recovery before trusting retrieval freshness."
15639
+ };
15640
+ }
15641
+ return { id: "outbox-healthy", severity: "warning", status: "pass" };
15642
+ }
15643
+ function buildMemoryDensityGate(totalEvents) {
15644
+ if (totalEvents <= 0) {
15645
+ return {
15646
+ id: "memory-density",
15647
+ severity: "warning",
15648
+ status: "warn",
15649
+ message: "No memories are available for this scope yet."
15650
+ };
15651
+ }
15652
+ return { id: "memory-density", severity: "warning", status: "pass" };
15653
+ }
15654
+ function selectNextBestAction(input) {
15655
+ if (input.outboxGate.status === "warn") {
15656
+ return "Run claude-memory-layer process --dry-run-recovery, then process pending embeddings.";
15657
+ }
15658
+ if (input.densityGate.status === "warn") {
15659
+ return "Import or capture project context before relying on productivity memory guidance.";
15660
+ }
15661
+ if (input.projectGate.status === "warn") {
15662
+ return "Provide a project path or project hash before relying on project-scoped productivity guidance.";
15663
+ }
15664
+ return "No immediate maintenance action required.";
15665
+ }
15666
+ function normalizeLevelStats(levelStats) {
15667
+ if (!Array.isArray(levelStats)) return [];
15668
+ 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) }));
15669
+ }
15670
+ function normalizeQueue(queue) {
15671
+ return {
15672
+ pending: numberOrZero(queue.pending),
15673
+ processing: numberOrZero(queue.processing),
15674
+ failed: numberOrZero(queue.failed),
15675
+ retryableFailed: numberOrZero(queue.retryableFailed),
15676
+ quarantinedFailed: numberOrZero(queue.quarantinedFailed),
15677
+ total: numberOrZero(queue.total),
15678
+ stuckProcessing: numberOrZero(queue.stuckProcessing),
15679
+ oldestProcessingAgeMs: normalizeNullableAge(queue.oldestProcessingAgeMs)
15680
+ };
15681
+ }
15682
+ function sumQueues(a, b) {
15683
+ return {
15684
+ pending: a.pending + b.pending,
15685
+ processing: a.processing + b.processing,
15686
+ failed: a.failed + b.failed,
15687
+ retryableFailed: (a.retryableFailed ?? 0) + (b.retryableFailed ?? 0),
15688
+ quarantinedFailed: (a.quarantinedFailed ?? 0) + (b.quarantinedFailed ?? 0),
15689
+ total: a.total + b.total,
15690
+ stuckProcessing: a.stuckProcessing + b.stuckProcessing,
15691
+ oldestProcessingAgeMs: maxNullable(a.oldestProcessingAgeMs, b.oldestProcessingAgeMs)
15692
+ };
15693
+ }
15694
+ function normalizeNullableAge(value) {
15695
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : null;
15696
+ }
15697
+ function maxNullable(a, b) {
15698
+ if (a === null) return b;
15699
+ if (b === null) return a;
15700
+ return Math.max(a, b);
15701
+ }
15702
+ function numberOrZero(value) {
15703
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
15704
+ return Math.floor(value);
15705
+ }
15706
+
15707
+ // src/apps/server/api/health.ts
15564
15708
  var healthRouter = new Hono9();
15565
15709
  function hasEnvSignal(name) {
15566
15710
  const value = process.env[name];
@@ -15601,6 +15745,33 @@ function aggregateOutbox(outbox) {
15601
15745
  const stuckProcessing = (outbox.embedding?.stuckProcessing || 0) + (outbox.vector?.stuckProcessing || 0);
15602
15746
  return { pending, processing, failed, retryableFailed, quarantinedFailed, stuckProcessing };
15603
15747
  }
15748
+ function resolveProductivityProjectIdentity(project) {
15749
+ if (!project || project.trim().length === 0) return { scope: "global", id: "global" };
15750
+ const normalized = project.trim();
15751
+ return { scope: "project", id: /^[a-f0-9]{8}$/.test(normalized) ? normalized : hashProjectPath(normalized) };
15752
+ }
15753
+ function explicitProjectStoreExists(project) {
15754
+ if (!project || project.trim().length === 0) return true;
15755
+ return fs12.existsSync(path14.join(resolveProjectStoragePath(project.trim()), "events.sqlite"));
15756
+ }
15757
+ function emptyProductivityStats() {
15758
+ return { totalEvents: 0, vectorCount: 0, levelStats: [] };
15759
+ }
15760
+ function emptyProductivityQueue() {
15761
+ return {
15762
+ pending: 0,
15763
+ processing: 0,
15764
+ failed: 0,
15765
+ retryableFailed: 0,
15766
+ quarantinedFailed: 0,
15767
+ total: 0,
15768
+ stuckProcessing: 0,
15769
+ oldestProcessingAgeMs: null
15770
+ };
15771
+ }
15772
+ function emptyProductivityOutbox() {
15773
+ return { embedding: emptyProductivityQueue(), vector: emptyProductivityQueue() };
15774
+ }
15604
15775
  healthRouter.get("/setup", async (c) => {
15605
15776
  const memoryService = getLightweightServiceFromQuery(c);
15606
15777
  try {
@@ -15657,6 +15828,40 @@ healthRouter.get("/setup", async (c) => {
15657
15828
  await memoryService.shutdown();
15658
15829
  }
15659
15830
  });
15831
+ healthRouter.get("/productivity", async (c) => {
15832
+ let memoryService;
15833
+ try {
15834
+ const profile = parseProductivityHealthProfile(c.req.query("profile"));
15835
+ const mode = parseProductivityHealthMode(c.req.query("mode"));
15836
+ const projectQuery = c.req.query("project") || c.req.query("projectId");
15837
+ const project = resolveProductivityProjectIdentity(projectQuery);
15838
+ if (!explicitProjectStoreExists(projectQuery)) {
15839
+ return c.json(buildProductivityHealthReport({
15840
+ stats: emptyProductivityStats(),
15841
+ outbox: emptyProductivityOutbox(),
15842
+ project,
15843
+ profile,
15844
+ mode
15845
+ }));
15846
+ }
15847
+ memoryService = getLightweightServiceFromQuery(c);
15848
+ await memoryService.initialize();
15849
+ const [stats, outbox] = await Promise.all([
15850
+ memoryService.getStats(),
15851
+ memoryService.getOutboxStats()
15852
+ ]);
15853
+ return c.json(buildProductivityHealthReport({ stats, outbox, project, profile, mode }));
15854
+ } catch (error) {
15855
+ const status = error instanceof Error && error.message.startsWith("Invalid --") ? 400 : 500;
15856
+ return c.json({
15857
+ status: "error",
15858
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
15859
+ error: status === 400 ? "Invalid productivity health option" : "Productivity health check failed"
15860
+ }, status);
15861
+ } finally {
15862
+ if (memoryService) await memoryService.shutdown();
15863
+ }
15864
+ });
15660
15865
  healthRouter.get("/", async (c) => {
15661
15866
  const memoryService = getLightweightServiceFromQuery(c);
15662
15867
  try {