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 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 fs19 from "fs";
13
- import * as path25 from "path";
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(path26, options) {
2249
- const dir = nodePath.dirname(path26);
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(path26, {
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, path26) {
2716
+ function getNestedRecord(root, path27) {
2717
2717
  let cursor = root;
2718
- for (const key of path26) {
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, path26) {
2724
+ function getNestedString(root, path27) {
2725
2725
  let cursor = root;
2726
- for (const key of path26) {
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, path26) {
7898
+ function nestedString(root, path27) {
7899
7899
  let cursor = root;
7900
- for (const key of path26) {
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, path26]) => ({
7975
+ const paths = Array.from(bestByTarget.entries()).map(([key, path27]) => ({
7976
7976
  target: graph.node(nodeFromKey(key)),
7977
- hops: path26.hops,
7978
- totalCost: path26.totalCost,
7979
- scoreContribution: path26.totalCost > 0 ? 1 / path26.totalCost : 0,
7980
- steps: path26.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 path26 of expansion.paths) {
10944
- if (path26.target.type !== "event") continue;
10945
- const target = await this.eventStore.getEvent(path26.target.id);
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(path26, titleByEntityId);
10948
- const score = graphPathScore(path26, opts.hopPenalty);
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-${path26.hops}-${target.id}`,
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(([path26, value]) => {
11298
- const actual = path26.split(".").reduce((acc, key) => {
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(path26, titleByEntityId) {
11350
- const firstStep = path26.steps[0];
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: path26.target.id,
11357
- targetType: path26.target.type,
11358
- hops: path26.hops,
11359
- relationPath: path26.steps.map((step) => step.relationType)
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(path26, hopPenalty) {
11363
- const base = Math.min(0.95, Math.max(0, path26.scoreContribution));
11364
- return Math.max(0.05, base - hopPenalty * Math.max(0, path26.hops - 1));
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 path26 of [...existing, ...incoming]) {
11373
- const key = [path26.startEntityId, path26.targetType, path26.targetId, path26.hops, ...path26.relationPath].join("\0");
11374
- if (!byKey.has(key)) byKey.set(key, path26);
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(path26) {
11379
- return [path26.startEntityId, path26.targetType, path26.targetId, path26.hops, ...path26.relationPath].join("|");
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 path18 from "path";
16354
- import * as fs16 from "fs";
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 = path18.dirname(fileUrlToPath(import.meta.url));
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
- path18.resolve(moduleDir, "../ui"),
19731
+ path19.resolve(moduleDir, "../ui"),
19527
19732
  // Source/dev server: src/apps/server/index.ts -> src/apps/dashboard
19528
- path18.resolve(moduleDir, "../dashboard"),
19733
+ path19.resolve(moduleDir, "../dashboard"),
19529
19734
  // Fallback when running from a repository root.
19530
- path18.resolve(process.cwd(), "dist/ui"),
19531
- path18.resolve(process.cwd(), "src/apps/dashboard")
19735
+ path19.resolve(process.cwd(), "dist/ui"),
19736
+ path19.resolve(process.cwd(), "src/apps/dashboard")
19532
19737
  ];
19533
- return candidates.find((candidate) => fs16.existsSync(path18.join(candidate, "index.html"))) ?? candidates[0];
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 (fs16.existsSync(uiPath)) {
19966
+ if (fs17.existsSync(uiPath)) {
19762
19967
  app2.use("/*", serveStatic({ root: uiPath }));
19763
19968
  }
19764
19969
  app2.get("*", (c) => {
19765
- const indexPath = path18.join(uiPath, "index.html");
19766
- if (fs16.existsSync(indexPath)) {
19767
- return c.html(fs16.readFileSync(indexPath, "utf-8"));
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 existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync6, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
20226
- import { dirname as dirname7, join as join18 } from "node:path";
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 join18(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
20435
+ return join19(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
20231
20436
  }
20232
20437
  if (platform === "win32") {
20233
- return join18(env.APPDATA ?? join18(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
20438
+ return join19(env.APPDATA ?? join19(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
20234
20439
  }
20235
- return join18(env.XDG_CONFIG_HOME ?? join18(home, ".config"), "claude", "claude_desktop_config.json");
20440
+ return join19(env.XDG_CONFIG_HOME ?? join19(home, ".config"), "claude", "claude_desktop_config.json");
20236
20441
  }
20237
20442
  function readJsonConfig(configPath) {
20238
- if (!existsSync14(configPath)) {
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 path19 from "path";
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(path19.join(pluginPath, "hooks", fileName))}`;
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 fs17 from "fs";
20378
- import * as path20 from "path";
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 = path20.dirname(outputPath);
20453
- fs17.mkdirSync(dir, { recursive: true });
20454
- fs17.writeFileSync(outputPath, formatCodexValidationReport(report, format), "utf8");
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 fs18 from "fs";
20459
- import * as path21 from "path";
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 = path21.dirname(outputPath);
20530
- fs18.mkdirSync(dir, { recursive: true });
20531
- fs18.writeFileSync(outputPath, formatHermesValidationReport(report, format), "utf8");
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 path22 from "node:path";
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 ?? path22.join(getProjectStoragePath2(projectPath), "vector-worker.lock")
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 = maxNullable(
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 maxNullable(a, b) {
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 = normalizeQueue(input.outbox.embedding);
21335
- const vector = normalizeQueue(input.outbox.vector);
21336
- const totals = sumQueues(embedding, vector);
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 = maxNullable2(embedding.oldestProcessingAgeMs, vector.oldestProcessingAgeMs);
21543
+ const oldestProcessingAgeMs = maxNullable3(embedding.oldestProcessingAgeMs, vector.oldestProcessingAgeMs);
21339
21544
  return {
21340
21545
  storage: {
21341
- totalEvents: numberOrZero(input.stats.totalEvents),
21342
- vectorCount: numberOrZero(input.stats.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 normalizeQueue(queue) {
21560
+ function normalizeQueue2(queue) {
21356
21561
  return {
21357
- pending: numberOrZero(queue.pending),
21358
- processing: numberOrZero(queue.processing),
21359
- failed: numberOrZero(queue.failed),
21360
- retryableFailed: numberOrZero(queue.retryableFailed),
21361
- quarantinedFailed: numberOrZero(queue.quarantinedFailed),
21362
- total: numberOrZero(queue.total),
21363
- stuckProcessing: numberOrZero(queue.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 sumQueues(a, b) {
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: maxNullable2(a.oldestProcessingAgeMs, b.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 maxNullable2(a, b) {
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 numberOrZero(value) {
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 path24 from "node:path";
21622
+ import * as path25 from "node:path";
21418
21623
 
21419
21624
  // src/core/worker-lock.ts
21420
- import { closeSync, existsSync as existsSync15, mkdirSync as mkdirSync10, openSync, readFileSync as readFileSync7, unlinkSync, writeFileSync as writeFileSync7 } from "node:fs";
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 path23 from "node:path";
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 = path23.join(os12.tmpdir(), "claude-memory-layer", "vector-worker.lock"), options = {}) {
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(path23.dirname(this.lockPath), { recursive: true });
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 || !existsSync15(this.lockPath)) return false;
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 ?? path24.join(getProjectStoragePath2(projectPath), "vector-worker.lock")
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 = path25.join(os13.homedir(), ".claude", "settings.json");
21864
+ var CLAUDE_SETTINGS_PATH = path26.join(os13.homedir(), ".claude", "settings.json");
21660
21865
  function getPluginPath() {
21661
21866
  const possiblePaths = [
21662
- path25.join(__dirname, ".."),
21867
+ path26.join(__dirname, ".."),
21663
21868
  // When running from dist/cli
21664
- path25.join(__dirname, "../..", "dist"),
21869
+ path26.join(__dirname, "../..", "dist"),
21665
21870
  // When running from src
21666
- path25.join(process.cwd(), "dist")
21871
+ path26.join(process.cwd(), "dist")
21667
21872
  // Current working directory
21668
21873
  ];
21669
21874
  for (const p of possiblePaths) {
21670
- const hooksPath = path25.join(p, "hooks", "user-prompt-submit.js");
21671
- if (fs19.existsSync(hooksPath)) {
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 path25.join(os13.homedir(), ".npm-global", "lib", "node_modules", "claude-memory-layer", "dist");
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 (fs19.existsSync(CLAUDE_SETTINGS_PATH)) {
21680
- const content = fs19.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8");
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 = path25.dirname(CLAUDE_SETTINGS_PATH);
21690
- if (!fs19.existsSync(dir)) {
21691
- fs19.mkdirSync(dir, { recursive: true });
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
- fs19.writeFileSync(tempPath, JSON.stringify(settings, null, 2));
21695
- fs19.renameSync(tempPath, CLAUDE_SETTINGS_PATH);
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 = path25.resolve(options.output);
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 = path25.resolve(options.output);
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 = path25.resolve(project ?? process.cwd());
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: path25.join(storagePath, "events.sqlite")
22060
+ dbPath: path26.join(storagePath, "events.sqlite")
21817
22061
  };
21818
22062
  }
21819
22063
  function openOperationReadDatabase(context) {
21820
- if (!fs19.existsSync(context.dbPath)) return null;
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.54");
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) => !fs19.existsSync(path25.join(pluginPath, "hooks", 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) => fs19.existsSync(path25.join(pluginPath, "hooks", 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 = path25.join(storagePath, "events.sqlite");
22345
- if (repairOptions.dryRun && !fs19.existsSync(dbPath)) {
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 = path25.join(storagePath, "events.sqlite");
22593
- if (!fs19.existsSync(dbPath)) {
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 || path25.basename(projectPath);
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 (!fs19.existsSync(storagePath)) {
22651
- fs19.mkdirSync(storagePath, { recursive: true });
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(path25.join(storagePath, "events.sqlite"));
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 = path25.basename(event.filePath, ".jsonl").slice(0, 8);
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 fs19.promises.readdir(dir, { withFileTypes: true });
23070
+ const entries = await fs20.promises.readdir(dir, { withFileTypes: true });
22793
23071
  for (const e of entries) {
22794
- const full = path25.join(dir, e.name);
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 = path25.relative(sourceRoot, filePath);
22803
- const dirSeg = path25.dirname(rel).split(path25.sep).filter(Boolean);
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 = path25.resolve(sourceDir || options.out || projectPath);
22823
- const repoPath = path25.resolve(options.repo || projectPath);
22824
- if (!fs19.existsSync(sourceRoot)) {
22825
- fs19.mkdirSync(sourceRoot, { recursive: true });
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 = path25.resolve(options.out || path25.join(sourceRoot, "bootstrap-kb"));
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 fs19.promises.readFile(file, "utf8");
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 = path25.relative(activeSourceRoot, file);
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}`);