kotadb 2.2.0-next.20260204230500 → 2.2.0-next.20260204235102
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/package.json +1 -1
- package/src/api/queries.ts +72 -0
- package/src/mcp/server.ts +2 -0
- package/src/mcp/tools.ts +156 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kotadb",
|
|
3
|
-
"version": "2.2.0-next.
|
|
3
|
+
"version": "2.2.0-next.20260204235102",
|
|
4
4
|
"description": "Local-only code intelligence tool for CLI agents. SQLite-backed repository indexing and code search via MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "src/index.ts",
|
package/src/api/queries.ts
CHANGED
|
@@ -1409,3 +1409,75 @@ export async function createDefaultOrganization(
|
|
|
1409
1409
|
): Promise<string> {
|
|
1410
1410
|
throw new Error('createDefaultOrganization() is not available in local-only mode - organizations are a cloud-only feature');
|
|
1411
1411
|
}
|
|
1412
|
+
|
|
1413
|
+
/**
|
|
1414
|
+
* Get index statistics for startup context display.
|
|
1415
|
+
* Queries counts of indexed files, symbols, references, and memory entries.
|
|
1416
|
+
*
|
|
1417
|
+
* @param db - Database instance (for testability)
|
|
1418
|
+
* @returns Statistics object with counts by type
|
|
1419
|
+
*/
|
|
1420
|
+
function getIndexStatisticsInternal(db: KotaDatabase): {
|
|
1421
|
+
files: number;
|
|
1422
|
+
symbols: number;
|
|
1423
|
+
references: number;
|
|
1424
|
+
decisions: number;
|
|
1425
|
+
patterns: number;
|
|
1426
|
+
failures: number;
|
|
1427
|
+
repositories: number;
|
|
1428
|
+
} {
|
|
1429
|
+
const stats = {
|
|
1430
|
+
files: 0,
|
|
1431
|
+
symbols: 0,
|
|
1432
|
+
references: 0,
|
|
1433
|
+
decisions: 0,
|
|
1434
|
+
patterns: 0,
|
|
1435
|
+
failures: 0,
|
|
1436
|
+
repositories: 0,
|
|
1437
|
+
};
|
|
1438
|
+
|
|
1439
|
+
// Helper function to safely query count with fallback for missing tables
|
|
1440
|
+
const safeCount = (tableName: string): number => {
|
|
1441
|
+
try {
|
|
1442
|
+
const result = db.queryOne<{ count: number }>(
|
|
1443
|
+
`SELECT COUNT(*) as count FROM ${tableName}`
|
|
1444
|
+
);
|
|
1445
|
+
return result?.count || 0;
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
// Table doesn't exist yet (e.g., memory layer tables)
|
|
1448
|
+
return 0;
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1451
|
+
|
|
1452
|
+
// Count indexed files
|
|
1453
|
+
stats.files = safeCount('indexed_files');
|
|
1454
|
+
|
|
1455
|
+
// Count indexed symbols
|
|
1456
|
+
stats.symbols = safeCount('indexed_symbols');
|
|
1457
|
+
|
|
1458
|
+
// Count references
|
|
1459
|
+
stats.references = safeCount('indexed_references');
|
|
1460
|
+
|
|
1461
|
+
// Count decisions (may not exist in all installations)
|
|
1462
|
+
stats.decisions = safeCount('kota_decisions');
|
|
1463
|
+
|
|
1464
|
+
// Count patterns (may not exist in all installations)
|
|
1465
|
+
stats.patterns = safeCount('kota_patterns');
|
|
1466
|
+
|
|
1467
|
+
// Count failures (may not exist in all installations)
|
|
1468
|
+
stats.failures = safeCount('kota_failures');
|
|
1469
|
+
|
|
1470
|
+
// Count repositories
|
|
1471
|
+
stats.repositories = safeCount('repositories');
|
|
1472
|
+
|
|
1473
|
+
return stats;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
/**
|
|
1477
|
+
* Get index statistics for startup context display (public API).
|
|
1478
|
+
*
|
|
1479
|
+
* @returns Statistics object with counts by type
|
|
1480
|
+
*/
|
|
1481
|
+
export function getIndexStatistics(): ReturnType<typeof getIndexStatisticsInternal> {
|
|
1482
|
+
return getIndexStatisticsInternal(getGlobalDatabase());
|
|
1483
|
+
}
|
package/src/mcp/server.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { Sentry } from "../instrument.js";
|
|
|
16
16
|
import {
|
|
17
17
|
ANALYZE_CHANGE_IMPACT_TOOL,
|
|
18
18
|
GENERATE_TASK_CONTEXT_TOOL,
|
|
19
|
+
GET_INDEX_STATISTICS_TOOL,
|
|
19
20
|
INDEX_REPOSITORY_TOOL,
|
|
20
21
|
LIST_RECENT_FILES_TOOL,
|
|
21
22
|
SEARCH_TOOL,
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
// Execute functions
|
|
36
37
|
executeAnalyzeChangeImpact,
|
|
37
38
|
executeGenerateTaskContext,
|
|
39
|
+
executeGetIndexStatistics,
|
|
38
40
|
executeIndexRepository,
|
|
39
41
|
executeListRecentFiles,
|
|
40
42
|
executeSearch,
|
package/src/mcp/tools.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
+
getIndexStatistics,
|
|
9
10
|
listRecentFiles,
|
|
10
11
|
queryDependencies,
|
|
11
12
|
queryDependents,
|
|
@@ -353,6 +354,21 @@ export const ANALYZE_CHANGE_IMPACT_TOOL: ToolDefinition = {
|
|
|
353
354
|
},
|
|
354
355
|
};
|
|
355
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Tool: get_index_statistics
|
|
359
|
+
*/
|
|
360
|
+
export const GET_INDEX_STATISTICS_TOOL: ToolDefinition = {
|
|
361
|
+
tier: "core",
|
|
362
|
+
name: "get_index_statistics",
|
|
363
|
+
description:
|
|
364
|
+
"Get statistics about indexed data (files, symbols, references, decisions, patterns, failures). Useful for understanding what data is available for search.",
|
|
365
|
+
inputSchema: {
|
|
366
|
+
type: "object",
|
|
367
|
+
properties: {},
|
|
368
|
+
required: [],
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
|
|
356
372
|
/**
|
|
357
373
|
* Tool: validate_implementation_spec
|
|
358
374
|
*/
|
|
@@ -770,6 +786,7 @@ export function getToolDefinitions(): ToolDefinition[] {
|
|
|
770
786
|
LIST_RECENT_FILES_TOOL,
|
|
771
787
|
SEARCH_DEPENDENCIES_TOOL,
|
|
772
788
|
ANALYZE_CHANGE_IMPACT_TOOL,
|
|
789
|
+
GET_INDEX_STATISTICS_TOOL,
|
|
773
790
|
VALIDATE_IMPLEMENTATION_SPEC_TOOL,
|
|
774
791
|
SYNC_EXPORT_TOOL,
|
|
775
792
|
SYNC_IMPORT_TOOL,
|
|
@@ -958,11 +975,121 @@ async function searchSymbols(
|
|
|
958
975
|
}));
|
|
959
976
|
}
|
|
960
977
|
|
|
978
|
+
/**
|
|
979
|
+
* Generate contextual tips based on search query and parameters.
|
|
980
|
+
* Uses static pattern matching (no NLP) to detect suboptimal usage patterns.
|
|
981
|
+
*
|
|
982
|
+
* Tip frequency: MODERATE - show tips frequently including "nice to know" suggestions.
|
|
983
|
+
*
|
|
984
|
+
* @param query - Search query string
|
|
985
|
+
* @param scopes - Search scopes used
|
|
986
|
+
* @param filters - Normalized filters applied
|
|
987
|
+
* @param scopeResults - Results by scope
|
|
988
|
+
* @returns Array of tip strings (empty if search is optimal)
|
|
989
|
+
*/
|
|
990
|
+
function generateSearchTips(
|
|
991
|
+
query: string,
|
|
992
|
+
scopes: string[],
|
|
993
|
+
filters: NormalizedFilters,
|
|
994
|
+
scopeResults: Record<string, unknown[]>
|
|
995
|
+
): string[] {
|
|
996
|
+
const tips: string[] = [];
|
|
997
|
+
const queryLower = query.toLowerCase();
|
|
998
|
+
|
|
999
|
+
// Pattern 1: Query contains structural keywords but not using symbols scope
|
|
1000
|
+
const structuralKeywords = ['function', 'class', 'interface', 'type', 'method', 'component'];
|
|
1001
|
+
const hasStructuralKeyword = structuralKeywords.some(kw => queryLower.includes(kw));
|
|
1002
|
+
|
|
1003
|
+
if (hasStructuralKeyword && !scopes.includes('symbols')) {
|
|
1004
|
+
const matchedKeyword = structuralKeywords.find(kw => queryLower.includes(kw)) || 'function';
|
|
1005
|
+
tips.push(
|
|
1006
|
+
`You searched for "${query}" in code. Try scope: ['symbols'] with filters: {symbol_kind: ['${matchedKeyword}']} for precise structural discovery.`
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// Pattern 2: Query looks like a file path but using code search
|
|
1011
|
+
const looksLikeFilePath = /^[\w\-./]+\.(ts|tsx|js|jsx|py|rs|go|java)$/i.test(query);
|
|
1012
|
+
if (looksLikeFilePath && scopes.includes('code')) {
|
|
1013
|
+
tips.push(
|
|
1014
|
+
`Query "${query}" looks like a file path. Consider using search_dependencies tool to find files that depend on this file or its dependencies.`
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// Pattern 3: Symbol search without exported_only filter
|
|
1019
|
+
if (scopes.includes('symbols') && filters.exported_only === undefined) {
|
|
1020
|
+
const symbolCount = scopeResults['symbols']?.length || 0;
|
|
1021
|
+
if (symbolCount > 10) {
|
|
1022
|
+
tips.push(
|
|
1023
|
+
`Found ${symbolCount} symbols. Add filters: {exported_only: true} to narrow to public API only.`
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Pattern 4: No repository filter with large result set
|
|
1029
|
+
if (!filters.repositoryId) {
|
|
1030
|
+
const totalResults = Object.values(scopeResults).reduce((sum, arr) => sum + arr.length, 0);
|
|
1031
|
+
if (totalResults > 20) {
|
|
1032
|
+
tips.push(
|
|
1033
|
+
`Found ${totalResults} results across all repositories. Add filters: {repository: "owner/repo"} to narrow to a specific repository.`
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// Pattern 5: Code search without glob/language filters
|
|
1039
|
+
if (scopes.includes('code') && !filters.glob && !filters.language) {
|
|
1040
|
+
const codeCount = scopeResults['code']?.length || 0;
|
|
1041
|
+
if (codeCount > 15) {
|
|
1042
|
+
tips.push(
|
|
1043
|
+
`Found ${codeCount} code results. Try filters: {glob: "**/*.ts"} or {language: "typescript"} to narrow file types.`
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Pattern 6: Suggest decisions scope for "why" questions
|
|
1049
|
+
if (/\b(why|reason|decision|chose|choice)\b/i.test(query) && !scopes.includes('decisions')) {
|
|
1050
|
+
tips.push(
|
|
1051
|
+
`Query contains "why/reason/decision". Try scope: ['decisions'] to search architectural decisions and rationale.`
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// Pattern 7: Suggest patterns scope for "how" questions
|
|
1056
|
+
if (/\b(how|pattern|best practice|convention)\b/i.test(query) && !scopes.includes('patterns')) {
|
|
1057
|
+
tips.push(
|
|
1058
|
+
`Query asks "how to". Try scope: ['patterns'] to search coding patterns and conventions from this codebase.`
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// Pattern 8: Suggest failures scope for error-related queries
|
|
1063
|
+
if (/\b(error|bug|fail|issue|problem|fix)\b/i.test(query) && !scopes.includes('failures')) {
|
|
1064
|
+
tips.push(
|
|
1065
|
+
`Query mentions errors/issues. Try scope: ['failures'] to learn from past mistakes and avoid repeated failures.`
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// Pattern 9: Single scope when multi-scope could be useful
|
|
1070
|
+
if (scopes.length === 1 && scopes[0] === 'code') {
|
|
1071
|
+
tips.push(
|
|
1072
|
+
`Tip: You can search multiple scopes simultaneously. Try scope: ['code', 'symbols'] for broader discovery.`
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// Pattern 10: Suggest compact format for large result sets
|
|
1077
|
+
const totalResults = Object.values(scopeResults).reduce((sum, arr) => sum + arr.length, 0);
|
|
1078
|
+
if (totalResults > 30 && !tips.some(t => t.includes('output: "compact"'))) {
|
|
1079
|
+
tips.push(
|
|
1080
|
+
`Returning ${totalResults} full results. Use output: "compact" for summary view or output: "paths" for file paths only.`
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
return tips;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
961
1087
|
function formatSearchResults(
|
|
962
1088
|
query: string,
|
|
963
1089
|
scopes: string[],
|
|
964
1090
|
scopeResults: Record<string, unknown[]>,
|
|
965
|
-
format: string
|
|
1091
|
+
format: string,
|
|
1092
|
+
filters: NormalizedFilters
|
|
966
1093
|
): Record<string, unknown> {
|
|
967
1094
|
const response: Record<string, unknown> = {
|
|
968
1095
|
query,
|
|
@@ -1007,6 +1134,13 @@ function formatSearchResults(
|
|
|
1007
1134
|
(response.counts as Record<string, unknown>).total = ((response.counts as Record<string, unknown>).total as number) + items.length;
|
|
1008
1135
|
}
|
|
1009
1136
|
|
|
1137
|
+
|
|
1138
|
+
// Generate and add tips if applicable
|
|
1139
|
+
const tips = generateSearchTips(query, scopes, filters, scopeResults);
|
|
1140
|
+
if (tips.length > 0) {
|
|
1141
|
+
response.tips = tips;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1010
1144
|
return response;
|
|
1011
1145
|
}
|
|
1012
1146
|
|
|
@@ -1142,7 +1276,7 @@ export async function executeSearch(
|
|
|
1142
1276
|
await Promise.all(searchPromises);
|
|
1143
1277
|
|
|
1144
1278
|
// Format output
|
|
1145
|
-
const response = formatSearchResults(p.query as string, scopes, results, output);
|
|
1279
|
+
const response = formatSearchResults(p.query as string, scopes, results, output, filters);
|
|
1146
1280
|
|
|
1147
1281
|
logger.info("Unified search completed", {
|
|
1148
1282
|
query: p.query,
|
|
@@ -1634,6 +1768,26 @@ export async function executeAnalyzeChangeImpact(
|
|
|
1634
1768
|
return result;
|
|
1635
1769
|
}
|
|
1636
1770
|
|
|
1771
|
+
/**
|
|
1772
|
+
* Execute get_index_statistics tool
|
|
1773
|
+
*/
|
|
1774
|
+
export async function executeGetIndexStatistics(
|
|
1775
|
+
params: unknown,
|
|
1776
|
+
requestId: string | number,
|
|
1777
|
+
userId: string,
|
|
1778
|
+
): Promise<unknown> {
|
|
1779
|
+
// No parameters to validate
|
|
1780
|
+
|
|
1781
|
+
logger.info("Getting index statistics", { request_id: String(requestId), user_id: userId });
|
|
1782
|
+
|
|
1783
|
+
const stats = getIndexStatistics();
|
|
1784
|
+
|
|
1785
|
+
return {
|
|
1786
|
+
...stats,
|
|
1787
|
+
summary: `${stats.symbols.toLocaleString()} symbols, ${stats.files.toLocaleString()} files, ${stats.repositories} repositories indexed`,
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1637
1791
|
/**
|
|
1638
1792
|
|
|
1639
1793
|
/**
|