@massa-ai/mcp-client 1.2.1 → 1.4.0

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.
Files changed (3) hide show
  1. package/dist/config-cli.js +257 -175
  2. package/dist/index.js +201 -122
  3. package/package.json +4 -4
@@ -557,7 +557,8 @@ __export(exports_config_loader, {
557
557
  getConfigPath: () => getConfigPath,
558
558
  getConfigForEnv: () => getConfigForEnv,
559
559
  getConfigDir: () => getConfigDir,
560
- configExists: () => configExists
560
+ configExists: () => configExists,
561
+ __resetMigrationForTests: () => __resetMigrationForTests
561
562
  });
562
563
  import fs from "fs";
563
564
  import path3 from "path";
@@ -627,6 +628,9 @@ function migrateDataDirOnce() {
627
628
  console.error(`[massa-ai] Data directory migration skipped:`, err);
628
629
  }
629
630
  }
631
+ function __resetMigrationForTests() {
632
+ migrationAttempted = false;
633
+ }
630
634
  function saveConfig(config) {
631
635
  if (!fs.existsSync(CONFIG_DIR)) {
632
636
  fs.mkdirSync(CONFIG_DIR, { recursive: true });
@@ -636,7 +640,7 @@ function saveConfig(config) {
636
640
  function initConfig() {
637
641
  if (!fs.existsSync(CONFIG_FILE)) {
638
642
  saveConfig(defaultMassaAiConfig);
639
- console.log(`Created default config at ${CONFIG_FILE}`);
643
+ console.error(`Created default config at ${CONFIG_FILE}`);
640
644
  }
641
645
  }
642
646
  function getConfigForEnv() {
@@ -688,7 +692,7 @@ var import_dotenv, envPath;
688
692
  var init_env = __esm(() => {
689
693
  import_dotenv = __toESM(require_main(), 1);
690
694
  envPath = findEnvFile();
691
- import_dotenv.config({ path: envPath });
695
+ import_dotenv.config({ path: envPath, quiet: true });
692
696
  try {
693
697
  const { loadConfigSafe: loadConfigSafe2, migrateDataDirOnce: migrateDataDirOnce2 } = (init_config_loader(), __toCommonJS(exports_config_loader));
694
698
  migrateDataDirOnce2();
@@ -6957,11 +6961,7 @@ class Logger {
6957
6961
  return `[${timestamp}] [${level}] ${message}${metaStr}`;
6958
6962
  }
6959
6963
  write(message, level) {
6960
- if (level >= LogLevel.WARN) {
6961
- console.error(message);
6962
- } else {
6963
- console.log(message);
6964
- }
6964
+ console.error(message);
6965
6965
  }
6966
6966
  debug(message, meta) {
6967
6967
  if (this.shouldLog(LogLevel.DEBUG)) {
@@ -7405,8 +7405,9 @@ class FileFilterCache {
7405
7405
  }
7406
7406
  invalidateProject(projectId) {
7407
7407
  let removed = 0;
7408
+ const prefix = `project:${projectId}`;
7408
7409
  for (const [key, _entry] of this.cache.entries()) {
7409
- if (key.startsWith(`project:${projectId}|`)) {
7410
+ if (key === prefix || key.startsWith(`${prefix}|`)) {
7410
7411
  this.cache.delete(key);
7411
7412
  removed++;
7412
7413
  }
@@ -109741,9 +109742,9 @@ class SearchCachePg {
109741
109742
  if (this.l1Cache.size <= this.L1_MAX_SIZE)
109742
109743
  return;
109743
109744
  let oldestKey = null;
109744
- let oldestTime = Date.now();
109745
+ let oldestTime = Infinity;
109745
109746
  for (const [key, entry2] of this.l1Cache.entries()) {
109746
- if (entry2.lastAccessed < oldestTime) {
109747
+ if (entry2.lastAccessed <= oldestTime) {
109747
109748
  oldestTime = entry2.lastAccessed;
109748
109749
  oldestKey = key;
109749
109750
  }
@@ -110352,7 +110353,8 @@ function generationDefinitionIdentityColumns(def) {
110352
110353
  }
110353
110354
  if (parsed.kind !== def.kind)
110354
110355
  throw new TypeError(`definition_fqn_kind_mismatch:${def.id}`);
110355
- if (parsed.qualifiedName.split(".").at(-1) !== def.name) {
110356
+ const terminalMatchesName = parsed.qualifiedName === def.name || parsed.qualifiedName.endsWith(`.${def.name}`);
110357
+ if (!terminalMatchesName) {
110356
110358
  throw new TypeError(`definition_fqn_name_mismatch:${def.id}`);
110357
110359
  }
110358
110360
  if (def.qualified_name !== undefined && def.qualified_name !== parsed.qualifiedName) {
@@ -110639,11 +110641,24 @@ async function writeFileGeneration(input) {
110639
110641
  last_known_good_generation_id = EXCLUDED.last_known_good_generation_id,
110640
110642
  last_successful_at = EXCLUDED.last_successful_at
110641
110643
  `;
110642
- for (const definition of definitions) {
110644
+ const seenDefinitionIds = new Set;
110645
+ const uniqueDefinitions = definitions.filter((definition) => {
110646
+ if (seenDefinitionIds.has(definition.id))
110647
+ return false;
110648
+ seenDefinitionIds.add(definition.id);
110649
+ return true;
110650
+ });
110651
+ for (const definition of uniqueDefinitions) {
110643
110652
  const identity = generationDefinitionIdentityColumns(definition);
110644
110653
  await tx.$executeRaw`
110645
110654
  INSERT INTO symbol_definitions (id, project_id, generation_id, file_path, name, kind, line_start, line_end, exported, doc_comment, indexed_at, qualified_name, canonical_signature, signature_hash, legacy_fqn, source_span)
110646
110655
  VALUES (${definition.id}, ${lease.projectId}, ${lease.generationId}, ${file2.relative_path}, ${definition.name}, ${definition.kind}, ${definition.line_start}, ${definition.line_end}, ${definition.exported}, ${definition.doc_comment ?? null}, ${new Date(definition.indexed_at)}, ${identity.qualifiedName}, ${identity.canonicalSignature}, ${identity.signatureHash}, ${identity.legacyFqn}, ${identity.sourceSpan}::jsonb)
110656
+ ON CONFLICT (project_id, generation_id, id) DO UPDATE SET
110657
+ name = EXCLUDED.name, kind = EXCLUDED.kind, line_start = EXCLUDED.line_start,
110658
+ line_end = EXCLUDED.line_end, exported = EXCLUDED.exported, doc_comment = EXCLUDED.doc_comment,
110659
+ indexed_at = EXCLUDED.indexed_at, qualified_name = EXCLUDED.qualified_name,
110660
+ canonical_signature = EXCLUDED.canonical_signature, signature_hash = EXCLUDED.signature_hash,
110661
+ legacy_fqn = EXCLUDED.legacy_fqn, source_span = EXCLUDED.source_span
110647
110662
  `;
110648
110663
  }
110649
110664
  for (const reference of references) {
@@ -110894,7 +110909,7 @@ async function upsertWorkspace(ws) {
110894
110909
  AND generation.id = 'legacy-' || md5(current_workspace.project_id)
110895
110910
  AND generation.project_id = current_workspace.project_id
110896
110911
  `;
110897
- });
110912
+ }, { timeout: 60000, maxWait: 1e4 });
110898
110913
  }
110899
110914
  async function updateWorkspaceStatus(projectId, status, opts) {
110900
110915
  const lastError = typeof opts === "string" ? opts : opts?.lastError ?? null;
@@ -110965,7 +110980,7 @@ async function updateWorkspaceStatus(projectId, status, opts) {
110965
110980
  FROM active_counts c
110966
110981
  WHERE w.project_id = c.project_id AND EXISTS (SELECT 1 FROM updated_generation)
110967
110982
  `;
110968
- });
110983
+ }, { timeout: 60000, maxWait: 1e4 });
110969
110984
  }
110970
110985
  var init_symbol_repo_workspace = __esm(() => {
110971
110986
  init_prisma_client();
@@ -112574,7 +112589,7 @@ class MemoryRepositoryPg {
112574
112589
  importance: m.importance,
112575
112590
  tags: JSON.stringify(tagsArr),
112576
112591
  embedding: m.embedding,
112577
- metadata: m.metadata ? JSON.stringify(m.metadata) : null,
112592
+ metadata: m.metadata == null ? null : typeof m.metadata === "string" ? m.metadata : JSON.stringify(m.metadata),
112578
112593
  created_at: m.created_at instanceof Date ? m.created_at.getTime() : Number(m.created_at),
112579
112594
  updated_at: m.updated_at instanceof Date ? m.updated_at.getTime() : Number(m.updated_at),
112580
112595
  access_count: m.access_count,
@@ -112768,7 +112783,7 @@ class MemoryRepositoryPg {
112768
112783
  pinned, deleted_at
112769
112784
  FROM memories
112770
112785
  WHERE deleted_at IS NULL
112771
- ORDER BY created_at DESC
112786
+ ORDER BY created_at DESC, id DESC
112772
112787
  LIMIT ${limit} OFFSET ${offset}
112773
112788
  `;
112774
112789
  return rows.map((r) => this.toMemoryRow(r));
@@ -112776,7 +112791,7 @@ class MemoryRepositoryPg {
112776
112791
  async findRecentByTag(tag, opts) {
112777
112792
  const since = new Date(opts.sinceMs);
112778
112793
  const conditions = [
112779
- import_prisma2.Prisma.sql`${tag} = ANY(tags)`,
112794
+ import_prisma2.Prisma.sql`${tag} = ANY(tags::text[])`,
112780
112795
  import_prisma2.Prisma.sql`created_at >= ${since}`
112781
112796
  ];
112782
112797
  if (opts.sessionId)
@@ -113319,7 +113334,8 @@ function restoreWorkingMemoryBuffer(snapshot) {
113319
113334
  for (const e of snapshot.entries) {
113320
113335
  if (!e || !e.result || typeof e.id !== "string")
113321
113336
  continue;
113322
- if (now2 - (e.lastAccessedAt ?? 0) >= snapshot.config.ttlMs)
113337
+ const lastAccessedAt = e.lastAccessedAt ?? now2;
113338
+ if (now2 - lastAccessedAt >= snapshot.config.ttlMs)
113323
113339
  continue;
113324
113340
  const result = e.result;
113325
113341
  buf.entries.set(e.id, {
@@ -113327,7 +113343,7 @@ function restoreWorkingMemoryBuffer(snapshot) {
113327
113343
  queryTokens: new Set,
113328
113344
  contentTokens: tokenize(result.content || ""),
113329
113345
  addedAt: e.addedAt ?? now2,
113330
- lastAccessedAt: e.lastAccessedAt ?? now2,
113346
+ lastAccessedAt,
113331
113347
  baselineScore: e.baselineScore ?? result.score
113332
113348
  });
113333
113349
  }
@@ -123720,10 +123736,10 @@ class GraphQueries {
123720
123736
  return new Map;
123721
123737
  }
123722
123738
  const rows = await getPrismaClient2().$queryRaw`
123723
- SELECT id, content, type, level, importance, array_to_json(tags)::text AS tags,
123739
+ SELECT id, content, type, level, importance, tags,
123724
123740
  created_at, updated_at, access_count, user_id, session_id, project_id, agent_id,
123725
123741
  NULL::bytea AS embedding, NULL::text AS metadata, NULL::timestamp AS last_accessed,
123726
- pinned::integer AS pinned, deleted_at
123742
+ CASE WHEN pinned THEN 1 ELSE 0 END AS pinned, deleted_at
123727
123743
  FROM memories WHERE id = ANY(${memoryIds}::text[])`;
123728
123744
  const result = new Map;
123729
123745
  for (const row of rows) {
@@ -128266,6 +128282,7 @@ function buildStructuralResolverDefinitions(documents) {
128266
128282
  return Object.freeze(documents.flatMap((document2) => document2.structure.symbols.map((symbol27) => {
128267
128283
  const key = `${document2.file}\x00${symbol27.qualifiedName}\x00${symbol27.kind}`;
128268
128284
  const visibleNested = document2.dialect === "java" ? symbol27.signatureMaterial.modifiers.includes("public") && (["class", "interface", "enum"].includes(symbol27.kind) || symbol27.signatureMaterial.modifiers.includes("static")) : !symbol27.signatureMaterial.modifiers.includes("private");
128285
+ const isExportMarker = symbol27.kind === "export";
128269
128286
  return Object.freeze({
128270
128287
  identity: Object.freeze({
128271
128288
  file: document2.file,
@@ -128278,7 +128295,7 @@ function buildStructuralResolverDefinitions(documents) {
128278
128295
  typeTokens: symbol27.signatureMaterial.typeTokens,
128279
128296
  modifiers: symbol27.signatureMaterial.modifiers,
128280
128297
  scope: symbol27.qualifiedName === symbol27.name ? "top_level" : "nested",
128281
- overload: (groups.get(key) ?? 0) > 1 ? "overloaded" : "unique"
128298
+ overload: (groups.get(key) ?? 0) > 1 || isExportMarker ? "overloaded" : "unique"
128282
128299
  }),
128283
128300
  exported: symbol27.exported || !symbol27.name.startsWith("#") && !symbol27.name.startsWith("%23") && visibleNested && exportedRoots.has(`${document2.file}\x00${symbol27.qualifiedName.split(".")[0]}`),
128284
128301
  defaultExport: symbol27.defaultExport
@@ -128947,6 +128964,7 @@ class ResolveStage {
128947
128964
  timestamp: Date.now()
128948
128965
  });
128949
128966
  }
128967
+ await new Promise((resolve6) => setTimeout(resolve6, 0));
128950
128968
  }
128951
128969
  const durationMs = Math.round(performance.now() - t0);
128952
128970
  ctx.emit({
@@ -129270,6 +129288,47 @@ var init_resolve = __esm(() => {
129270
129288
  STRUCTURAL_SEED_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".rb", ".php", ".lua", ".c", ".h", ".cpp", ".hpp", ".go", ".rs", ".zig", ".java", ".kt", ".kts", ".scala", ".cs", ".swift", ".dart", ".ex", ".exs", ".erl", ".clj", ".ml", ".hs", ".vue", ".md", ".json", ".yaml", ".yml"]);
129271
129289
  });
129272
129290
 
129291
+ // ../../packages/core/dist/data/with-deadlock-retry.js
129292
+ function isRetriableTransactionError(error51) {
129293
+ if (!error51 || typeof error51 !== "object")
129294
+ return false;
129295
+ const code = error51.code;
129296
+ if (typeof code === "string" && RETRIABLE_SQLSTATES.has(code))
129297
+ return true;
129298
+ const message = error51.message;
129299
+ if (typeof message === "string" && /Code:\s*`(?:40P01|40001|40P02)`/.test(message)) {
129300
+ return true;
129301
+ }
129302
+ return false;
129303
+ }
129304
+ async function withDeadlockRetry(operation, options = {}) {
129305
+ const maxAttempts = options.maxAttempts ?? 5;
129306
+ const baseDelayMs = options.baseDelayMs ?? 75;
129307
+ for (let attempt = 1;attempt <= maxAttempts; attempt++) {
129308
+ try {
129309
+ return await operation();
129310
+ } catch (error51) {
129311
+ if (!isRetriableTransactionError(error51) || attempt === maxAttempts)
129312
+ throw error51;
129313
+ const delayMs = baseDelayMs * 2 ** (attempt - 1);
129314
+ logger.warn("Retriable DB lock cycle; retrying operation", {
129315
+ operation: options.operation ?? "unknown",
129316
+ attempt,
129317
+ maxAttempts,
129318
+ delayMs,
129319
+ error: error51?.message?.slice(0, 120)
129320
+ });
129321
+ await new Promise((resolve6) => setTimeout(resolve6, delayMs));
129322
+ }
129323
+ }
129324
+ throw new Error("withDeadlockRetry exhausted retries");
129325
+ }
129326
+ var RETRIABLE_SQLSTATES;
129327
+ var init_with_deadlock_retry = __esm(() => {
129328
+ init_dist();
129329
+ RETRIABLE_SQLSTATES = new Set(["40P01", "40001", "40P02"]);
129330
+ });
129331
+
129273
129332
  // ../../packages/core/dist/services/etl/stages/load.js
129274
129333
  import path18 from "path";
129275
129334
  function formatDuration(ms) {
@@ -129386,8 +129445,12 @@ class LoadStage {
129386
129445
  return;
129387
129446
  try {
129388
129447
  const [chunkCount, symCount] = await Promise.all([
129389
- mode === "structural" ? Promise.resolve(0) : this.loadToSearchStores(ctx, file2),
129390
- mode === "semantic" ? Promise.resolve(file2.symbols.length) : this.loadToSymbolDb(ctx, file2)
129448
+ mode === "structural" ? Promise.resolve(0) : withDeadlockRetry(() => this.loadToSearchStores(ctx, file2), {
129449
+ operation: "etl.loadToSearchStores"
129450
+ }),
129451
+ mode === "semantic" ? Promise.resolve(file2.symbols.length) : withDeadlockRetry(() => this.loadToSymbolDb(ctx, file2), {
129452
+ operation: "etl.loadToSymbolDb"
129453
+ })
129391
129454
  ]);
129392
129455
  if (!ctx.graphGenerationLease && mode !== "structural") {
129393
129456
  await getSymbolRepository().upsertFile({
@@ -129579,6 +129642,7 @@ class LoadStage {
129579
129642
  }
129580
129643
  var init_load = __esm(() => {
129581
129644
  init_dist();
129645
+ init_with_deadlock_retry();
129582
129646
  init_vector_store_factory();
129583
129647
  init_keyword_search_factory();
129584
129648
  init_symbol_repository_factory();
@@ -129776,7 +129840,7 @@ class GraphGenerationRepositoryPg {
129776
129840
  AND graph_lease_token = ${lease.leaseToken}
129777
129841
  `;
129778
129842
  return { status: "renewed", leaseExpiresAt: renewed[0].lease_expires_at.getTime() };
129779
- });
129843
+ }, { timeout: 60000, maxWait: 1e4 });
129780
129844
  }
129781
129845
  async complete(lease) {
129782
129846
  return getPrismaClient2().$transaction(async (tx) => {
@@ -129950,7 +130014,7 @@ class GraphGenerationCoordinator {
129950
130014
  } while (true);
129951
130015
  }
129952
130016
  async heartbeat(lease) {
129953
- const outcome2 = await this.repository.heartbeat(lease, GRAPH_GENERATION_LEASE_TTL_MS);
130017
+ const outcome2 = await withDeadlockRetry(() => this.repository.heartbeat(lease, GRAPH_GENERATION_LEASE_TTL_MS), { operation: "graph_generation.heartbeat", maxAttempts: 3 });
129954
130018
  if (outcome2.status !== "renewed")
129955
130019
  throw new Error("graph_generation_lease_lost");
129956
130020
  }
@@ -129986,6 +130050,7 @@ class GraphGenerationCoordinator {
129986
130050
  var GRAPH_GENERATION_LEASE_TTL_MS = 300000;
129987
130051
  var init_graph_generation_coordinator = __esm(() => {
129988
130052
  init_graph_generation_repository_factory();
130053
+ init_with_deadlock_retry();
129989
130054
  });
129990
130055
 
129991
130056
  // ../../packages/core/dist/services/etl/pipeline.js
@@ -130063,6 +130128,7 @@ var init_pipeline = __esm(() => {
130063
130128
  init_symbol_repository_factory();
130064
130129
  init_index_job_tracker();
130065
130130
  init_cache_factory();
130131
+ init_index_manager();
130066
130132
  init_vector_store_factory();
130067
130133
  init_keyword_search_factory();
130068
130134
  init_alias_resolver();
@@ -130354,6 +130420,16 @@ var init_pipeline = __esm(() => {
130354
130420
  }
130355
130421
  };
130356
130422
  await getSearchCache().invalidateProject(projectId);
130423
+ try {
130424
+ const admissionMarker = new IndexManager(await getVectorStore());
130425
+ await admissionMarker.updateIndexMetadata(projectId, projectPath, discovered.map((file2) => file2.relativePath));
130426
+ } catch (markerError) {
130427
+ logger.warn("EtlPipeline: search-admission marker write failed", {
130428
+ projectId,
130429
+ jobId,
130430
+ error: markerError.message.slice(0, 160)
130431
+ });
130432
+ }
130357
130433
  indexJobTracker.updateProgress(jobId, result.filesIndexed, result.filesIndexed);
130358
130434
  await indexJobTracker.setResultAndFlush(jobId, {
130359
130435
  filesIndexed: result.filesIndexed,
@@ -131694,16 +131770,14 @@ var TOOL_NAME_NORMALIZE, classifyToolCall = (_source, payload) => {
131694
131770
  const cmdRaw = field2(payload, "command") ?? (toolInput && typeof toolInput === "object" ? toolInput.command : undefined);
131695
131771
  const cmd = lower(cmdRaw);
131696
131772
  if (cmd.startsWith("git ") || cmd.includes(" git ")) {
131697
- if (cmd.includes("commit") || cmd.includes("merge") || cmd.includes("rebase")) {
131698
- return "git-changes";
131699
- }
131700
131773
  return "git-changes";
131701
131774
  }
131702
131775
  return "tool-calls";
131703
131776
  }
131704
131777
  case "TodoWrite":
131705
- case "Task":
131706
131778
  return "tasks";
131779
+ case "Task":
131780
+ return "subagents-spawned";
131707
131781
  case "WebFetch":
131708
131782
  return "web-fetch";
131709
131783
  case "WebSearch":
@@ -131720,8 +131794,6 @@ var TOOL_NAME_NORMALIZE, classifyToolCall = (_source, payload) => {
131720
131794
  return "memories-stored";
131721
131795
  case "compact_snapshot":
131722
131796
  return "compaction-snapshots";
131723
- case "Task":
131724
- return "subagents-spawned";
131725
131797
  default:
131726
131798
  if (toolName.startsWith("mcp__"))
131727
131799
  return "mcp-calls";
@@ -150348,8 +150420,6 @@ var init_recover_project = __esm(() => {
150348
150420
 
150349
150421
  // src/config-cli.ts
150350
150422
  init_config();
150351
- var args = process.argv.slice(2);
150352
- var command = args[0];
150353
150423
  function help() {
150354
150424
  console.log(`
150355
150425
  massa-ai-config - Configuration manager for massa-ai
@@ -150384,13 +150454,13 @@ Examples:
150384
150454
  massa-ai-config recover my-project --path /home/user/renamed-dir
150385
150455
  `);
150386
150456
  }
150387
- function parseOptions(args2) {
150457
+ function parseOptions(args) {
150388
150458
  const options = {};
150389
- for (let i = 0;i < args2.length; i++) {
150390
- if (args2[i].startsWith("--")) {
150391
- const key = args2[i].slice(2);
150392
- if (i + 1 < args2.length && !args2[i + 1].startsWith("--")) {
150393
- options[key] = args2[i + 1];
150459
+ for (let i = 0;i < args.length; i++) {
150460
+ if (args[i].startsWith("--")) {
150461
+ const key = args[i].slice(2);
150462
+ if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
150463
+ options[key] = args[i + 1];
150394
150464
  i++;
150395
150465
  } else {
150396
150466
  options[key] = true;
@@ -150399,147 +150469,159 @@ function parseOptions(args2) {
150399
150469
  }
150400
150470
  return options;
150401
150471
  }
150402
- if (!command || command === "--help" || command === "-h") {
150403
- help();
150404
- process.exit(0);
150405
- }
150406
- var options = parseOptions(args.slice(1));
150407
- switch (command) {
150408
- case "init": {
150409
- initConfig();
150410
- if (options.mistral && typeof options.mistral === "string") {
150411
- const config3 = loadConfig();
150412
- config3.embedding = {
150413
- provider: "mistral",
150414
- model: "mistral-embed",
150415
- apiKey: options.mistral,
150416
- dimensions: 1024
150417
- };
150418
- saveConfig(config3);
150419
- console.log("\u2713 Configured for Mistral embeddings");
150420
- } else if (options.openai && typeof options.openai === "string") {
150421
- const config3 = loadConfig();
150422
- config3.embedding = {
150423
- provider: "openai",
150424
- model: "text-embedding-3-small",
150425
- apiKey: options.openai,
150426
- dimensions: 1536
150427
- };
150428
- saveConfig(config3);
150429
- console.log("\u2713 Configured for OpenAI embeddings");
150430
- } else {
150431
- console.log("\u2713 Configured for Ollama (local) embeddings");
150432
- }
150433
- console.log(`
150434
- Config file: ${getConfigPath()}`);
150435
- break;
150436
- }
150437
- case "path": {
150438
- console.log(getConfigPath());
150439
- break;
150472
+ async function runCli(argv) {
150473
+ const args = argv;
150474
+ const command = args[0];
150475
+ if (!command || command === "--help" || command === "-h") {
150476
+ help();
150477
+ return 0;
150440
150478
  }
150441
- case "show": {
150442
- if (!configExists()) {
150443
- console.log("No config file found. Run `massa-ai-config init` to create one.");
150479
+ const options = parseOptions(args.slice(1));
150480
+ switch (command) {
150481
+ case "init": {
150482
+ initConfig();
150483
+ if (options.mistral && typeof options.mistral === "string") {
150484
+ const config3 = loadConfig();
150485
+ config3.embedding = {
150486
+ provider: "mistral",
150487
+ model: "mistral-embed",
150488
+ apiKey: options.mistral,
150489
+ dimensions: 1024
150490
+ };
150491
+ saveConfig(config3);
150492
+ console.log("\u2713 Configured for Mistral embeddings");
150493
+ } else if (options.openai && typeof options.openai === "string") {
150494
+ const config3 = loadConfig();
150495
+ config3.embedding = {
150496
+ provider: "openai",
150497
+ model: "text-embedding-3-small",
150498
+ apiKey: options.openai,
150499
+ dimensions: 1536
150500
+ };
150501
+ saveConfig(config3);
150502
+ console.log("\u2713 Configured for OpenAI embeddings");
150503
+ } else {
150504
+ console.log("\u2713 Configured for Ollama (local) embeddings");
150505
+ }
150444
150506
  console.log(`
150507
+ Config file: ${getConfigPath()}`);
150508
+ break;
150509
+ }
150510
+ case "path": {
150511
+ console.log(getConfigPath());
150512
+ break;
150513
+ }
150514
+ case "show": {
150515
+ if (!configExists()) {
150516
+ console.log("No config file found. Run `massa-ai-config init` to create one.");
150517
+ console.log(`
150445
150518
  Using defaults:`);
150446
- console.log(JSON.stringify(defaultMassaAiConfig, null, 2));
150447
- process.exit(0);
150448
- }
150449
- const config3 = loadConfig();
150450
- console.log(JSON.stringify(config3, null, 2));
150451
- break;
150452
- }
150453
- case "set": {
150454
- const key = args[1];
150455
- const value = args[2];
150456
- if (!key || !value) {
150457
- console.error("Usage: massa-ai-config set <key> <value>");
150458
- process.exit(1);
150459
- }
150460
- const config3 = loadConfig();
150461
- const keys = key.split(".");
150462
- let obj = config3;
150463
- for (let i = 0;i < keys.length - 1; i++) {
150464
- obj = obj[keys[i]];
150465
- }
150466
- const parsedValue = isNaN(Number(value)) ? value : Number(value);
150467
- obj[keys[keys.length - 1]] = parsedValue;
150468
- saveConfig(config3);
150469
- console.log(`\u2713 Set ${key} = ${value}`);
150470
- break;
150471
- }
150472
- case "use": {
150473
- const provider = args[1];
150474
- if (!provider || !["ollama", "mistral", "openai"].includes(provider)) {
150475
- console.error("Provider must be: ollama, mistral, or openai");
150476
- process.exit(1);
150477
- }
150478
- const config3 = loadConfig();
150479
- if (provider === "ollama") {
150480
- config3.embedding = {
150481
- provider: "ollama",
150482
- model: options.model || "qwen3-embedding:8b",
150483
- baseURL: options["base-url"] || "http://localhost:11434",
150484
- dimensions: 768
150485
- };
150486
- } else if (provider === "mistral") {
150487
- if (!options["api-key"]) {
150488
- console.error("Error: --api-key required for Mistral");
150489
- process.exit(1);
150490
- }
150491
- config3.embedding = {
150492
- provider: "mistral",
150493
- model: options.model || "mistral-embed",
150494
- apiKey: options["api-key"],
150495
- dimensions: 1024
150496
- };
150497
- } else if (provider === "openai") {
150498
- if (!options["api-key"]) {
150499
- console.error("Error: --api-key required for OpenAI");
150500
- process.exit(1);
150519
+ console.log(JSON.stringify(defaultMassaAiConfig, null, 2));
150520
+ return 0;
150501
150521
  }
150502
- config3.embedding = {
150503
- provider: "openai",
150504
- model: options.model || "text-embedding-3-small",
150505
- apiKey: options["api-key"],
150506
- dimensions: 1536
150507
- };
150522
+ const config3 = loadConfig();
150523
+ console.log(JSON.stringify(config3, null, 2));
150524
+ break;
150508
150525
  }
150509
- saveConfig(config3);
150510
- console.log(`\u2713 Switched to ${provider} embeddings`);
150511
- console.log(` Model: ${config3.embedding.model}`);
150512
- break;
150513
- }
150514
- case "recover": {
150515
- const projectId = args[1];
150516
- const newPath = options.path;
150517
- if (!projectId) {
150518
- console.error("Error: projectId required. Usage: massa-ai-config recover <projectId> --path <newPath>");
150519
- process.exit(1);
150526
+ case "set": {
150527
+ const key = args[1];
150528
+ const value = args[2];
150529
+ if (!key || !value) {
150530
+ console.error("Usage: massa-ai-config set <key> <value>");
150531
+ return 1;
150532
+ }
150533
+ const config3 = loadConfig();
150534
+ const keys = key.split(".");
150535
+ let obj = config3;
150536
+ for (let i = 0;i < keys.length - 1; i++) {
150537
+ obj = obj[keys[i]];
150538
+ }
150539
+ const parsedValue = isNaN(Number(value)) ? value : Number(value);
150540
+ obj[keys[keys.length - 1]] = parsedValue;
150541
+ saveConfig(config3);
150542
+ console.log(`\u2713 Set ${key} = ${value}`);
150543
+ break;
150520
150544
  }
150521
- if (!newPath || typeof newPath !== "string") {
150522
- console.error("Error: --path required. Usage: massa-ai-config recover <projectId> --path <newPath>");
150523
- process.exit(1);
150545
+ case "use": {
150546
+ const provider = args[1];
150547
+ if (!provider || !["ollama", "mistral", "openai"].includes(provider)) {
150548
+ console.error("Provider must be: ollama, mistral, or openai");
150549
+ return 1;
150550
+ }
150551
+ const config3 = loadConfig();
150552
+ if (provider === "ollama") {
150553
+ config3.embedding = {
150554
+ provider: "ollama",
150555
+ model: options.model || "qwen3-embedding:8b",
150556
+ baseURL: options["base-url"] || "http://localhost:11434",
150557
+ dimensions: 768
150558
+ };
150559
+ } else if (provider === "mistral") {
150560
+ if (!options["api-key"]) {
150561
+ console.error("Error: --api-key required for Mistral");
150562
+ return 1;
150563
+ }
150564
+ config3.embedding = {
150565
+ provider: "mistral",
150566
+ model: options.model || "mistral-embed",
150567
+ apiKey: options["api-key"],
150568
+ dimensions: 1024
150569
+ };
150570
+ } else if (provider === "openai") {
150571
+ if (!options["api-key"]) {
150572
+ console.error("Error: --api-key required for OpenAI");
150573
+ return 1;
150574
+ }
150575
+ config3.embedding = {
150576
+ provider: "openai",
150577
+ model: options.model || "text-embedding-3-small",
150578
+ apiKey: options["api-key"],
150579
+ dimensions: 1536
150580
+ };
150581
+ }
150582
+ saveConfig(config3);
150583
+ console.log(`\u2713 Switched to ${provider} embeddings`);
150584
+ console.log(` Model: ${config3.embedding.model}`);
150585
+ break;
150524
150586
  }
150525
- try {
150526
- const { recoverProjectPath: recoverProjectPath2 } = await Promise.resolve().then(() => (init_recover_project(), exports_recover_project));
150527
- const result = await recoverProjectPath2(projectId, newPath);
150528
- if (!result.found) {
150529
- console.error(`Error: project "${projectId}" not found. Cannot recover \u2014 the project must be indexed first.`);
150530
- process.exit(1);
150531
- }
150532
- console.log(`\u2713 Recovered project "${projectId}" \u2014 path updated to ${newPath}`);
150533
- console.log(` Previous path: ${result.oldPath ?? "(none)"}`);
150534
- } catch (e) {
150535
- const err = e;
150536
- console.error(`Error: recovery failed \u2014 ${err.message}`);
150537
- process.exit(1);
150587
+ case "recover": {
150588
+ const projectId = args[1];
150589
+ const newPath = options.path;
150590
+ if (!projectId) {
150591
+ console.error("Error: projectId required. Usage: massa-ai-config recover <projectId> --path <newPath>");
150592
+ return 1;
150593
+ }
150594
+ if (!newPath || typeof newPath !== "string") {
150595
+ console.error("Error: --path required. Usage: massa-ai-config recover <projectId> --path <newPath>");
150596
+ return 1;
150597
+ }
150598
+ try {
150599
+ const { recoverProjectPath: recoverProjectPath2 } = await Promise.resolve().then(() => (init_recover_project(), exports_recover_project));
150600
+ const result = await recoverProjectPath2(projectId, newPath);
150601
+ if (!result.found) {
150602
+ console.error(`Error: project "${projectId}" not found. Cannot recover \u2014 the project must be indexed first.`);
150603
+ return 1;
150604
+ }
150605
+ console.log(`\u2713 Recovered project "${projectId}" \u2014 path updated to ${newPath}`);
150606
+ console.log(` Previous path: ${result.oldPath ?? "(none)"}`);
150607
+ } catch (e) {
150608
+ const err = e;
150609
+ console.error(`Error: recovery failed \u2014 ${err.message}`);
150610
+ return 1;
150611
+ }
150612
+ break;
150538
150613
  }
150539
- break;
150614
+ default:
150615
+ console.error(`Unknown command: ${command}`);
150616
+ help();
150617
+ return 1;
150540
150618
  }
150541
- default:
150542
- console.error(`Unknown command: ${command}`);
150543
- help();
150544
- process.exit(1);
150619
+ return 0;
150620
+ }
150621
+ if (import.meta.main) {
150622
+ runCli(process.argv.slice(2)).then((code) => process.exit(code));
150545
150623
  }
150624
+ export {
150625
+ runCli,
150626
+ parseOptions
150627
+ };
package/dist/index.js CHANGED
@@ -25554,7 +25554,8 @@ __export(exports_config_loader, {
25554
25554
  getConfigPath: () => getConfigPath,
25555
25555
  getConfigForEnv: () => getConfigForEnv,
25556
25556
  getConfigDir: () => getConfigDir,
25557
- configExists: () => configExists
25557
+ configExists: () => configExists,
25558
+ __resetMigrationForTests: () => __resetMigrationForTests
25558
25559
  });
25559
25560
  import fs from "fs";
25560
25561
  import path3 from "path";
@@ -25624,6 +25625,9 @@ function migrateDataDirOnce() {
25624
25625
  console.error(`[massa-ai] Data directory migration skipped:`, err);
25625
25626
  }
25626
25627
  }
25628
+ function __resetMigrationForTests() {
25629
+ migrationAttempted = false;
25630
+ }
25627
25631
  function saveConfig(config2) {
25628
25632
  if (!fs.existsSync(CONFIG_DIR)) {
25629
25633
  fs.mkdirSync(CONFIG_DIR, { recursive: true });
@@ -25633,7 +25637,7 @@ function saveConfig(config2) {
25633
25637
  function initConfig() {
25634
25638
  if (!fs.existsSync(CONFIG_FILE)) {
25635
25639
  saveConfig(defaultMassaAiConfig);
25636
- console.log(`Created default config at ${CONFIG_FILE}`);
25640
+ console.error(`Created default config at ${CONFIG_FILE}`);
25637
25641
  }
25638
25642
  }
25639
25643
  function getConfigForEnv() {
@@ -25685,7 +25689,7 @@ var import_dotenv, envPath;
25685
25689
  var init_env = __esm(() => {
25686
25690
  import_dotenv = __toESM(require_main(), 1);
25687
25691
  envPath = findEnvFile();
25688
- import_dotenv.config({ path: envPath });
25692
+ import_dotenv.config({ path: envPath, quiet: true });
25689
25693
  try {
25690
25694
  const { loadConfigSafe: loadConfigSafe2, migrateDataDirOnce: migrateDataDirOnce2 } = (init_config_loader(), __toCommonJS(exports_config_loader));
25691
25695
  migrateDataDirOnce2();
@@ -31954,11 +31958,7 @@ class Logger {
31954
31958
  return `[${timestamp}] [${level}] ${message}${metaStr}`;
31955
31959
  }
31956
31960
  write(message, level) {
31957
- if (level >= LogLevel.WARN) {
31958
- console.error(message);
31959
- } else {
31960
- console.log(message);
31961
- }
31961
+ console.error(message);
31962
31962
  }
31963
31963
  debug(message, meta3) {
31964
31964
  if (this.shouldLog(LogLevel.DEBUG)) {
@@ -32402,8 +32402,9 @@ class FileFilterCache {
32402
32402
  }
32403
32403
  invalidateProject(projectId) {
32404
32404
  let removed = 0;
32405
+ const prefix = `project:${projectId}`;
32405
32406
  for (const [key, _entry] of this.cache.entries()) {
32406
- if (key.startsWith(`project:${projectId}|`)) {
32407
+ if (key === prefix || key.startsWith(`${prefix}|`)) {
32407
32408
  this.cache.delete(key);
32408
32409
  removed++;
32409
32410
  }
@@ -116300,9 +116301,9 @@ class SearchCachePg {
116300
116301
  if (this.l1Cache.size <= this.L1_MAX_SIZE)
116301
116302
  return;
116302
116303
  let oldestKey = null;
116303
- let oldestTime = Date.now();
116304
+ let oldestTime = Infinity;
116304
116305
  for (const [key, entry2] of this.l1Cache.entries()) {
116305
- if (entry2.lastAccessed < oldestTime) {
116306
+ if (entry2.lastAccessed <= oldestTime) {
116306
116307
  oldestTime = entry2.lastAccessed;
116307
116308
  oldestKey = key;
116308
116309
  }
@@ -116911,7 +116912,8 @@ function generationDefinitionIdentityColumns(def) {
116911
116912
  }
116912
116913
  if (parsed.kind !== def.kind)
116913
116914
  throw new TypeError(`definition_fqn_kind_mismatch:${def.id}`);
116914
- if (parsed.qualifiedName.split(".").at(-1) !== def.name) {
116915
+ const terminalMatchesName = parsed.qualifiedName === def.name || parsed.qualifiedName.endsWith(`.${def.name}`);
116916
+ if (!terminalMatchesName) {
116915
116917
  throw new TypeError(`definition_fqn_name_mismatch:${def.id}`);
116916
116918
  }
116917
116919
  if (def.qualified_name !== undefined && def.qualified_name !== parsed.qualifiedName) {
@@ -117198,11 +117200,24 @@ async function writeFileGeneration(input) {
117198
117200
  last_known_good_generation_id = EXCLUDED.last_known_good_generation_id,
117199
117201
  last_successful_at = EXCLUDED.last_successful_at
117200
117202
  `;
117201
- for (const definition of definitions) {
117203
+ const seenDefinitionIds = new Set;
117204
+ const uniqueDefinitions = definitions.filter((definition) => {
117205
+ if (seenDefinitionIds.has(definition.id))
117206
+ return false;
117207
+ seenDefinitionIds.add(definition.id);
117208
+ return true;
117209
+ });
117210
+ for (const definition of uniqueDefinitions) {
117202
117211
  const identity = generationDefinitionIdentityColumns(definition);
117203
117212
  await tx.$executeRaw`
117204
117213
  INSERT INTO symbol_definitions (id, project_id, generation_id, file_path, name, kind, line_start, line_end, exported, doc_comment, indexed_at, qualified_name, canonical_signature, signature_hash, legacy_fqn, source_span)
117205
117214
  VALUES (${definition.id}, ${lease.projectId}, ${lease.generationId}, ${file2.relative_path}, ${definition.name}, ${definition.kind}, ${definition.line_start}, ${definition.line_end}, ${definition.exported}, ${definition.doc_comment ?? null}, ${new Date(definition.indexed_at)}, ${identity.qualifiedName}, ${identity.canonicalSignature}, ${identity.signatureHash}, ${identity.legacyFqn}, ${identity.sourceSpan}::jsonb)
117215
+ ON CONFLICT (project_id, generation_id, id) DO UPDATE SET
117216
+ name = EXCLUDED.name, kind = EXCLUDED.kind, line_start = EXCLUDED.line_start,
117217
+ line_end = EXCLUDED.line_end, exported = EXCLUDED.exported, doc_comment = EXCLUDED.doc_comment,
117218
+ indexed_at = EXCLUDED.indexed_at, qualified_name = EXCLUDED.qualified_name,
117219
+ canonical_signature = EXCLUDED.canonical_signature, signature_hash = EXCLUDED.signature_hash,
117220
+ legacy_fqn = EXCLUDED.legacy_fqn, source_span = EXCLUDED.source_span
117206
117221
  `;
117207
117222
  }
117208
117223
  for (const reference of references) {
@@ -117453,7 +117468,7 @@ async function upsertWorkspace(ws) {
117453
117468
  AND generation.id = 'legacy-' || md5(current_workspace.project_id)
117454
117469
  AND generation.project_id = current_workspace.project_id
117455
117470
  `;
117456
- });
117471
+ }, { timeout: 60000, maxWait: 1e4 });
117457
117472
  }
117458
117473
  async function updateWorkspaceStatus(projectId, status, opts) {
117459
117474
  const lastError = typeof opts === "string" ? opts : opts?.lastError ?? null;
@@ -117524,7 +117539,7 @@ async function updateWorkspaceStatus(projectId, status, opts) {
117524
117539
  FROM active_counts c
117525
117540
  WHERE w.project_id = c.project_id AND EXISTS (SELECT 1 FROM updated_generation)
117526
117541
  `;
117527
- });
117542
+ }, { timeout: 60000, maxWait: 1e4 });
117528
117543
  }
117529
117544
  var init_symbol_repo_workspace = __esm(() => {
117530
117545
  init_prisma_client();
@@ -119133,7 +119148,7 @@ class MemoryRepositoryPg {
119133
119148
  importance: m.importance,
119134
119149
  tags: JSON.stringify(tagsArr),
119135
119150
  embedding: m.embedding,
119136
- metadata: m.metadata ? JSON.stringify(m.metadata) : null,
119151
+ metadata: m.metadata == null ? null : typeof m.metadata === "string" ? m.metadata : JSON.stringify(m.metadata),
119137
119152
  created_at: m.created_at instanceof Date ? m.created_at.getTime() : Number(m.created_at),
119138
119153
  updated_at: m.updated_at instanceof Date ? m.updated_at.getTime() : Number(m.updated_at),
119139
119154
  access_count: m.access_count,
@@ -119327,7 +119342,7 @@ class MemoryRepositoryPg {
119327
119342
  pinned, deleted_at
119328
119343
  FROM memories
119329
119344
  WHERE deleted_at IS NULL
119330
- ORDER BY created_at DESC
119345
+ ORDER BY created_at DESC, id DESC
119331
119346
  LIMIT ${limit} OFFSET ${offset}
119332
119347
  `;
119333
119348
  return rows.map((r) => this.toMemoryRow(r));
@@ -119335,7 +119350,7 @@ class MemoryRepositoryPg {
119335
119350
  async findRecentByTag(tag, opts) {
119336
119351
  const since = new Date(opts.sinceMs);
119337
119352
  const conditions = [
119338
- import_prisma2.Prisma.sql`${tag} = ANY(tags)`,
119353
+ import_prisma2.Prisma.sql`${tag} = ANY(tags::text[])`,
119339
119354
  import_prisma2.Prisma.sql`created_at >= ${since}`
119340
119355
  ];
119341
119356
  if (opts.sessionId)
@@ -119878,7 +119893,8 @@ function restoreWorkingMemoryBuffer(snapshot) {
119878
119893
  for (const e of snapshot.entries) {
119879
119894
  if (!e || !e.result || typeof e.id !== "string")
119880
119895
  continue;
119881
- if (now2 - (e.lastAccessedAt ?? 0) >= snapshot.config.ttlMs)
119896
+ const lastAccessedAt = e.lastAccessedAt ?? now2;
119897
+ if (now2 - lastAccessedAt >= snapshot.config.ttlMs)
119882
119898
  continue;
119883
119899
  const result = e.result;
119884
119900
  buf.entries.set(e.id, {
@@ -119886,7 +119902,7 @@ function restoreWorkingMemoryBuffer(snapshot) {
119886
119902
  queryTokens: new Set,
119887
119903
  contentTokens: tokenize(result.content || ""),
119888
119904
  addedAt: e.addedAt ?? now2,
119889
- lastAccessedAt: e.lastAccessedAt ?? now2,
119905
+ lastAccessedAt,
119890
119906
  baselineScore: e.baselineScore ?? result.score
119891
119907
  });
119892
119908
  }
@@ -126449,6 +126465,7 @@ function buildStructuralResolverDefinitions(documents) {
126449
126465
  return Object.freeze(documents.flatMap((document2) => document2.structure.symbols.map((symbol27) => {
126450
126466
  const key = `${document2.file}\x00${symbol27.qualifiedName}\x00${symbol27.kind}`;
126451
126467
  const visibleNested = document2.dialect === "java" ? symbol27.signatureMaterial.modifiers.includes("public") && (["class", "interface", "enum"].includes(symbol27.kind) || symbol27.signatureMaterial.modifiers.includes("static")) : !symbol27.signatureMaterial.modifiers.includes("private");
126468
+ const isExportMarker = symbol27.kind === "export";
126452
126469
  return Object.freeze({
126453
126470
  identity: Object.freeze({
126454
126471
  file: document2.file,
@@ -126461,7 +126478,7 @@ function buildStructuralResolverDefinitions(documents) {
126461
126478
  typeTokens: symbol27.signatureMaterial.typeTokens,
126462
126479
  modifiers: symbol27.signatureMaterial.modifiers,
126463
126480
  scope: symbol27.qualifiedName === symbol27.name ? "top_level" : "nested",
126464
- overload: (groups.get(key) ?? 0) > 1 ? "overloaded" : "unique"
126481
+ overload: (groups.get(key) ?? 0) > 1 || isExportMarker ? "overloaded" : "unique"
126465
126482
  }),
126466
126483
  exported: symbol27.exported || !symbol27.name.startsWith("#") && !symbol27.name.startsWith("%23") && visibleNested && exportedRoots.has(`${document2.file}\x00${symbol27.qualifiedName.split(".")[0]}`),
126467
126484
  defaultExport: symbol27.defaultExport
@@ -127130,6 +127147,7 @@ class ResolveStage {
127130
127147
  timestamp: Date.now()
127131
127148
  });
127132
127149
  }
127150
+ await new Promise((resolve5) => setTimeout(resolve5, 0));
127133
127151
  }
127134
127152
  const durationMs = Math.round(performance.now() - t0);
127135
127153
  ctx.emit({
@@ -127453,6 +127471,47 @@ var init_resolve = __esm(() => {
127453
127471
  STRUCTURAL_SEED_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".rb", ".php", ".lua", ".c", ".h", ".cpp", ".hpp", ".go", ".rs", ".zig", ".java", ".kt", ".kts", ".scala", ".cs", ".swift", ".dart", ".ex", ".exs", ".erl", ".clj", ".ml", ".hs", ".vue", ".md", ".json", ".yaml", ".yml"]);
127454
127472
  });
127455
127473
 
127474
+ // ../../packages/core/dist/data/with-deadlock-retry.js
127475
+ function isRetriableTransactionError(error51) {
127476
+ if (!error51 || typeof error51 !== "object")
127477
+ return false;
127478
+ const code = error51.code;
127479
+ if (typeof code === "string" && RETRIABLE_SQLSTATES.has(code))
127480
+ return true;
127481
+ const message = error51.message;
127482
+ if (typeof message === "string" && /Code:\s*`(?:40P01|40001|40P02)`/.test(message)) {
127483
+ return true;
127484
+ }
127485
+ return false;
127486
+ }
127487
+ async function withDeadlockRetry(operation, options = {}) {
127488
+ const maxAttempts = options.maxAttempts ?? 5;
127489
+ const baseDelayMs = options.baseDelayMs ?? 75;
127490
+ for (let attempt = 1;attempt <= maxAttempts; attempt++) {
127491
+ try {
127492
+ return await operation();
127493
+ } catch (error51) {
127494
+ if (!isRetriableTransactionError(error51) || attempt === maxAttempts)
127495
+ throw error51;
127496
+ const delayMs = baseDelayMs * 2 ** (attempt - 1);
127497
+ logger.warn("Retriable DB lock cycle; retrying operation", {
127498
+ operation: options.operation ?? "unknown",
127499
+ attempt,
127500
+ maxAttempts,
127501
+ delayMs,
127502
+ error: error51?.message?.slice(0, 120)
127503
+ });
127504
+ await new Promise((resolve5) => setTimeout(resolve5, delayMs));
127505
+ }
127506
+ }
127507
+ throw new Error("withDeadlockRetry exhausted retries");
127508
+ }
127509
+ var RETRIABLE_SQLSTATES;
127510
+ var init_with_deadlock_retry = __esm(() => {
127511
+ init_dist();
127512
+ RETRIABLE_SQLSTATES = new Set(["40P01", "40001", "40P02"]);
127513
+ });
127514
+
127456
127515
  // ../../packages/core/dist/services/etl/stages/load.js
127457
127516
  import path15 from "path";
127458
127517
  function formatDuration(ms) {
@@ -127569,8 +127628,12 @@ class LoadStage {
127569
127628
  return;
127570
127629
  try {
127571
127630
  const [chunkCount, symCount] = await Promise.all([
127572
- mode === "structural" ? Promise.resolve(0) : this.loadToSearchStores(ctx, file2),
127573
- mode === "semantic" ? Promise.resolve(file2.symbols.length) : this.loadToSymbolDb(ctx, file2)
127631
+ mode === "structural" ? Promise.resolve(0) : withDeadlockRetry(() => this.loadToSearchStores(ctx, file2), {
127632
+ operation: "etl.loadToSearchStores"
127633
+ }),
127634
+ mode === "semantic" ? Promise.resolve(file2.symbols.length) : withDeadlockRetry(() => this.loadToSymbolDb(ctx, file2), {
127635
+ operation: "etl.loadToSymbolDb"
127636
+ })
127574
127637
  ]);
127575
127638
  if (!ctx.graphGenerationLease && mode !== "structural") {
127576
127639
  await getSymbolRepository().upsertFile({
@@ -127762,6 +127825,7 @@ class LoadStage {
127762
127825
  }
127763
127826
  var init_load = __esm(() => {
127764
127827
  init_dist();
127828
+ init_with_deadlock_retry();
127765
127829
  init_vector_store_factory();
127766
127830
  init_keyword_search_factory();
127767
127831
  init_symbol_repository_factory();
@@ -127959,7 +128023,7 @@ class GraphGenerationRepositoryPg {
127959
128023
  AND graph_lease_token = ${lease.leaseToken}
127960
128024
  `;
127961
128025
  return { status: "renewed", leaseExpiresAt: renewed[0].lease_expires_at.getTime() };
127962
- });
128026
+ }, { timeout: 60000, maxWait: 1e4 });
127963
128027
  }
127964
128028
  async complete(lease) {
127965
128029
  return getPrismaClient2().$transaction(async (tx) => {
@@ -128133,7 +128197,7 @@ class GraphGenerationCoordinator {
128133
128197
  } while (true);
128134
128198
  }
128135
128199
  async heartbeat(lease) {
128136
- const outcome2 = await this.repository.heartbeat(lease, GRAPH_GENERATION_LEASE_TTL_MS);
128200
+ const outcome2 = await withDeadlockRetry(() => this.repository.heartbeat(lease, GRAPH_GENERATION_LEASE_TTL_MS), { operation: "graph_generation.heartbeat", maxAttempts: 3 });
128137
128201
  if (outcome2.status !== "renewed")
128138
128202
  throw new Error("graph_generation_lease_lost");
128139
128203
  }
@@ -128169,6 +128233,7 @@ class GraphGenerationCoordinator {
128169
128233
  var GRAPH_GENERATION_LEASE_TTL_MS = 300000;
128170
128234
  var init_graph_generation_coordinator = __esm(() => {
128171
128235
  init_graph_generation_repository_factory();
128236
+ init_with_deadlock_retry();
128172
128237
  });
128173
128238
 
128174
128239
  // ../../packages/core/dist/services/etl/pipeline.js
@@ -128246,6 +128311,7 @@ var init_pipeline = __esm(() => {
128246
128311
  init_symbol_repository_factory();
128247
128312
  init_index_job_tracker();
128248
128313
  init_cache_factory();
128314
+ init_index_manager();
128249
128315
  init_vector_store_factory();
128250
128316
  init_keyword_search_factory();
128251
128317
  init_alias_resolver();
@@ -128537,6 +128603,16 @@ var init_pipeline = __esm(() => {
128537
128603
  }
128538
128604
  };
128539
128605
  await getSearchCache().invalidateProject(projectId);
128606
+ try {
128607
+ const admissionMarker = new IndexManager(await getVectorStore());
128608
+ await admissionMarker.updateIndexMetadata(projectId, projectPath, discovered.map((file2) => file2.relativePath));
128609
+ } catch (markerError) {
128610
+ logger.warn("EtlPipeline: search-admission marker write failed", {
128611
+ projectId,
128612
+ jobId,
128613
+ error: markerError.message.slice(0, 160)
128614
+ });
128615
+ }
128540
128616
  indexJobTracker.updateProgress(jobId, result.filesIndexed, result.filesIndexed);
128541
128617
  await indexJobTracker.setResultAndFlush(jobId, {
128542
128618
  filesIndexed: result.filesIndexed,
@@ -131951,10 +132027,10 @@ class GraphQueries {
131951
132027
  return new Map;
131952
132028
  }
131953
132029
  const rows = await getPrismaClient2().$queryRaw`
131954
- SELECT id, content, type, level, importance, array_to_json(tags)::text AS tags,
132030
+ SELECT id, content, type, level, importance, tags,
131955
132031
  created_at, updated_at, access_count, user_id, session_id, project_id, agent_id,
131956
132032
  NULL::bytea AS embedding, NULL::text AS metadata, NULL::timestamp AS last_accessed,
131957
- pinned::integer AS pinned, deleted_at
132033
+ CASE WHEN pinned THEN 1 ELSE 0 END AS pinned, deleted_at
131958
132034
  FROM memories WHERE id = ANY(${memoryIds}::text[])`;
131959
132035
  const result = new Map;
131960
132036
  for (const row of rows) {
@@ -133259,7 +133335,7 @@ class CompressContextTool {
133259
133335
  } catch (error51) {
133260
133336
  logger.error("Failed to compress context", error51, {
133261
133337
  strategy,
133262
- contentLength: content.length
133338
+ contentLength: typeof content === "string" ? content.length : 0
133263
133339
  });
133264
133340
  return {
133265
133341
  success: false,
@@ -135381,16 +135457,14 @@ var TOOL_NAME_NORMALIZE, classifyToolCall = (_source, payload) => {
135381
135457
  const cmdRaw = field2(payload, "command") ?? (toolInput && typeof toolInput === "object" ? toolInput.command : undefined);
135382
135458
  const cmd = lower(cmdRaw);
135383
135459
  if (cmd.startsWith("git ") || cmd.includes(" git ")) {
135384
- if (cmd.includes("commit") || cmd.includes("merge") || cmd.includes("rebase")) {
135385
- return "git-changes";
135386
- }
135387
135460
  return "git-changes";
135388
135461
  }
135389
135462
  return "tool-calls";
135390
135463
  }
135391
135464
  case "TodoWrite":
135392
- case "Task":
135393
135465
  return "tasks";
135466
+ case "Task":
135467
+ return "subagents-spawned";
135394
135468
  case "WebFetch":
135395
135469
  return "web-fetch";
135396
135470
  case "WebSearch":
@@ -135407,8 +135481,6 @@ var TOOL_NAME_NORMALIZE, classifyToolCall = (_source, payload) => {
135407
135481
  return "memories-stored";
135408
135482
  case "compact_snapshot":
135409
135483
  return "compaction-snapshots";
135410
- case "Task":
135411
- return "subagents-spawned";
135412
135484
  default:
135413
135485
  if (toolName.startsWith("mcp__"))
135414
135486
  return "mcp-calls";
@@ -168147,36 +168219,37 @@ function resolveFlavor(request) {
168147
168219
  // src/index.ts
168148
168220
  init_config();
168149
168221
  var args = process.argv.slice(2);
168150
- if (args.includes("--config-show")) {
168151
- try {
168152
- const config3 = loadConfig();
168153
- console.log(JSON.stringify(config3, null, 2));
168154
- process.exit(0);
168155
- } catch (error51) {
168156
- console.error("Error loading config:", error51 instanceof Error ? error51.message : String(error51));
168157
- process.exit(1);
168222
+ function processCliArgs(cliArgs) {
168223
+ if (cliArgs.includes("--config-show")) {
168224
+ try {
168225
+ const config3 = loadConfig();
168226
+ console.log(JSON.stringify(config3, null, 2));
168227
+ return 0;
168228
+ } catch (error51) {
168229
+ console.error("Error loading config:", error51 instanceof Error ? error51.message : String(error51));
168230
+ return 1;
168231
+ }
168158
168232
  }
168159
- }
168160
- if (args.includes("--config-path")) {
168161
- console.log(getConfigPath());
168162
- process.exit(0);
168163
- }
168164
- if (args.includes("--config-dir")) {
168165
- console.log(getConfigDir());
168166
- process.exit(0);
168167
- }
168168
- if (args.includes("--config-init")) {
168169
- try {
168170
- initConfig();
168171
- console.log(`Configuration initialized at: ${getConfigPath()}`);
168172
- process.exit(0);
168173
- } catch (error51) {
168174
- console.error("Error initializing config:", error51 instanceof Error ? error51.message : String(error51));
168175
- process.exit(1);
168233
+ if (cliArgs.includes("--config-path")) {
168234
+ console.log(getConfigPath());
168235
+ return 0;
168176
168236
  }
168177
- }
168178
- if (args.includes("--help") || args.includes("-h")) {
168179
- console.log(`
168237
+ if (cliArgs.includes("--config-dir")) {
168238
+ console.log(getConfigDir());
168239
+ return 0;
168240
+ }
168241
+ if (cliArgs.includes("--config-init")) {
168242
+ try {
168243
+ initConfig();
168244
+ console.log(`Configuration initialized at: ${getConfigPath()}`);
168245
+ return 0;
168246
+ } catch (error51) {
168247
+ console.error("Error initializing config:", error51 instanceof Error ? error51.message : String(error51));
168248
+ return 1;
168249
+ }
168250
+ }
168251
+ if (cliArgs.includes("--help") || cliArgs.includes("-h")) {
168252
+ console.log(`
168180
168253
  massa-ai MCP Client
168181
168254
 
168182
168255
  Usage:
@@ -168196,7 +168269,24 @@ Examples:
168196
168269
  npx @massa-ai/mcp-client --config-show
168197
168270
  npx @massa-ai/mcp-client --config-path
168198
168271
  `);
168199
- process.exit(0);
168272
+ return 0;
168273
+ }
168274
+ const KNOWN_FLAGS = ["--config-show", "--config-path", "--config-dir", "--config-init", "--help", "-h"];
168275
+ const unknown3 = cliArgs.filter((a) => a.startsWith("-") && !KNOWN_FLAGS.includes(a));
168276
+ if (unknown3.length) {
168277
+ console.error(`Unknown flag: ${unknown3[0]}
168278
+ Run 'massa-ai --help' for usage.`);
168279
+ return 2;
168280
+ }
168281
+ return;
168282
+ }
168283
+ if (import.meta.main) {
168284
+ const code = processCliArgs(args);
168285
+ if (code !== undefined)
168286
+ process.exit(code);
168287
+ if (!configExists()) {
168288
+ initConfig();
168289
+ }
168200
168290
  }
168201
168291
  function textContent(text3) {
168202
168292
  return { content: [{ type: "text", text: text3 }] };
@@ -168204,22 +168294,6 @@ function textContent(text3) {
168204
168294
  function isEmbeddedMode() {
168205
168295
  return process.env.MASSA_AI_EMBEDDED === "true";
168206
168296
  }
168207
- var KNOWN_FLAGS = ["--config-show", "--config-path", "--config-dir", "--config-init", "--help", "-h"];
168208
- var unknown3 = args.filter((a) => a.startsWith("-") && !KNOWN_FLAGS.includes(a));
168209
- if (unknown3.length) {
168210
- console.error(`Unknown flag: ${unknown3[0]}
168211
- Run 'massa-ai --help' for usage.`);
168212
- process.exit(2);
168213
- }
168214
- if (!configExists()) {
168215
- initConfig();
168216
- console.log(`
168217
- [massa-ai] Initialized with default configuration
168218
- [massa-ai] Config: ${getConfigPath()}
168219
- [massa-ai] Provider: Ollama (local, free)
168220
- [massa-ai] To change: npx @massa-ai/mcp-client massa-ai-config use mistral --api-key YOUR_KEY
168221
- `);
168222
- }
168223
168297
 
168224
168298
  class McpProxyServer {
168225
168299
  server;
@@ -168246,32 +168320,34 @@ class McpProxyServer {
168246
168320
  this.setupHandlers();
168247
168321
  }
168248
168322
  setupHandlers() {
168249
- this.server.setRequestHandler(ListToolsRequestSchema, async (request) => {
168250
- const result = pageToolDefinitions(TOOL_DEFINITIONS, request.params?.cursor);
168251
- const flavor = resolveFlavor(request);
168252
- return applyMoonshotFlavor(result, flavor);
168253
- });
168254
- this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
168255
- const { name: name26, arguments: args2 } = request.params;
168256
- try {
168257
- if (name26 === "index") {
168258
- return await this.handleIndexTool(args2 ?? {});
168259
- }
168260
- return await proxyCallTool(this.apiClient, name26, args2 ?? {});
168261
- } catch (error51) {
168262
- return {
168263
- content: [
168264
- {
168265
- type: "text",
168266
- text: JSON.stringify({
168267
- success: false,
168268
- error: error51 instanceof Error ? error51.message : String(error51)
168269
- })
168270
- }
168271
- ]
168272
- };
168323
+ this.server.setRequestHandler(ListToolsRequestSchema, async (request) => this.handleListTools(request));
168324
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => this.handleCallTool(request));
168325
+ }
168326
+ async handleListTools(request) {
168327
+ const result = pageToolDefinitions(TOOL_DEFINITIONS, request.params?.cursor);
168328
+ const flavor = resolveFlavor(request);
168329
+ return applyMoonshotFlavor(result, flavor);
168330
+ }
168331
+ async handleCallTool(request) {
168332
+ const { name: name26, arguments: args2 } = request.params;
168333
+ try {
168334
+ if (name26 === "index") {
168335
+ return await this.handleIndexTool(args2 ?? {});
168273
168336
  }
168274
- });
168337
+ return await proxyCallTool(this.apiClient, name26, args2 ?? {});
168338
+ } catch (error51) {
168339
+ return {
168340
+ content: [
168341
+ {
168342
+ type: "text",
168343
+ text: JSON.stringify({
168344
+ success: false,
168345
+ error: error51 instanceof Error ? error51.message : String(error51)
168346
+ })
168347
+ }
168348
+ ]
168349
+ };
168350
+ }
168275
168351
  }
168276
168352
  async handleIndexTool(args2) {
168277
168353
  const projectPath2 = args2.projectPath;
@@ -168304,27 +168380,30 @@ class McpProxyServer {
168304
168380
  }
168305
168381
  async start() {
168306
168382
  const healthy = await this.apiClient.healthCheck();
168307
- if (!healthy) {
168308
- console.log(`[massa-ai-mcp] Warning: Tools API is not reachable (mode: ${this.mode}). Requests will fail until API is available.`);
168309
- } else {
168310
- console.log(`[massa-ai-mcp] Connected (mode: ${this.mode})`);
168311
- }
168312
168383
  await this.server.connect(this.transport);
168313
168384
  }
168314
168385
  async close() {
168315
168386
  await this.server.close();
168316
168387
  }
168317
168388
  }
168318
- var client = new McpProxyServer;
168319
- client.start().catch((error51) => {
168320
- console.error("Failed to start MCP client:", error51);
168321
- process.exit(1);
168322
- });
168323
- process.on("SIGINT", async () => {
168324
- await client.close();
168325
- process.exit(0);
168326
- });
168327
- process.on("SIGTERM", async () => {
168328
- await client.close();
168329
- process.exit(0);
168330
- });
168389
+ if (import.meta.main) {
168390
+ const client = new McpProxyServer;
168391
+ client.start().catch((error51) => {
168392
+ console.error("Failed to start MCP client:", error51);
168393
+ process.exit(1);
168394
+ });
168395
+ process.on("SIGINT", async () => {
168396
+ await client.close();
168397
+ process.exit(0);
168398
+ });
168399
+ process.on("SIGTERM", async () => {
168400
+ await client.close();
168401
+ process.exit(0);
168402
+ });
168403
+ }
168404
+ export {
168405
+ textContent,
168406
+ processCliArgs,
168407
+ isEmbeddedMode,
168408
+ McpProxyServer
168409
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massa-ai/mcp-client",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "description": "massa-ai MCP server - Semantic code search, memory, and context compression via Model Context Protocol",
5
5
  "author": "luizgmassa",
6
6
  "type": "module",
@@ -16,12 +16,12 @@
16
16
  "dev": "bun run --watch src/index.ts",
17
17
  "start": "bun run src/index.ts",
18
18
  "build": "bun build src/index.ts src/config-cli.ts --outdir=dist --target=bun",
19
- "test": "bun test",
19
+ "test": "bun scripts/run-tests-isolated.ts",
20
20
  "type-check": "tsc --noEmit"
21
21
  },
22
22
  "dependencies": {
23
- "@massa-ai/core": "^1.2.1",
24
- "@massa-ai/shared": "^1.2.1",
23
+ "@massa-ai/core": "^1.4.0",
24
+ "@massa-ai/shared": "^1.4.0",
25
25
  "@modelcontextprotocol/sdk": "^1.0.0"
26
26
  },
27
27
  "devDependencies": {