kotadb 2.2.0-next.20260205005118 → 2.2.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.
- package/package.json +1 -1
- package/src/api/queries.ts +812 -989
- package/src/db/migrations/004_memory_layer.sql +144 -16
- package/src/db/sqlite/sqlite-client.ts +1 -26
- package/src/db/sqlite-schema.sql +0 -48
- package/src/indexer/constants.ts +0 -1
- package/src/indexer/parsers.ts +1 -3
- package/src/mcp/server.ts +0 -2
- package/src/mcp/tools.ts +9 -230
- package/src/db/migrations/005_workflow_contexts.sql +0 -41
- package/src/db/migrations/006_add_migration_checksums.sql +0 -15
- package/src/db/sqlite/migration-runner.ts +0 -335
|
@@ -3,53 +3,181 @@
|
|
|
3
3
|
-- Migration: 004_memory_layer
|
|
4
4
|
-- Issue: Memory Layer for Agent Intelligence
|
|
5
5
|
-- Author: Claude Code
|
|
6
|
-
-- Date: 2026-02-03
|
|
6
|
+
-- Date: 2026-02-03
|
|
7
7
|
--
|
|
8
|
-
-- This migration extends the memory layer with:
|
|
9
|
-
-- - decisions
|
|
10
|
-
-- -
|
|
8
|
+
-- This migration extends the memory layer tables with additional schema:
|
|
9
|
+
-- - decisions: Add status column (active, superseded, deprecated)
|
|
10
|
+
-- - failed_approaches: Alternative to failures with clearer naming
|
|
11
|
+
-- - pattern_annotations: Enhanced patterns with evidence/confidence scoring
|
|
12
|
+
-- - agent_sessions: Track agent work sessions
|
|
13
|
+
-- - session_insights: Insights linked to sessions with file references
|
|
11
14
|
--
|
|
12
15
|
-- Note: The base sqlite-schema.sql already has decisions, failures, patterns,
|
|
13
|
-
-- and insights tables. This migration
|
|
16
|
+
-- and insights tables. This migration adds enhanced versions and the missing
|
|
17
|
+
-- agent_sessions table for complete session tracking.
|
|
14
18
|
|
|
15
19
|
-- ============================================================================
|
|
16
20
|
-- 1. Extend Decisions Table - Add status column
|
|
17
21
|
-- ============================================================================
|
|
18
22
|
-- Add status column to track decision lifecycle
|
|
19
|
-
-- Note: SQLite ALTER TABLE ADD COLUMN will fail if column exists,
|
|
20
|
-
-- which is the expected behavior (migration already applied)
|
|
21
23
|
|
|
22
24
|
ALTER TABLE decisions ADD COLUMN status TEXT DEFAULT 'active';
|
|
23
25
|
|
|
26
|
+
-- Add index for active decisions (most common query)
|
|
24
27
|
CREATE INDEX IF NOT EXISTS idx_decisions_status ON decisions(status) WHERE status = 'active';
|
|
25
28
|
|
|
26
29
|
-- ============================================================================
|
|
27
|
-
-- 2.
|
|
30
|
+
-- 2. Failed Approaches Table (alternative to failures with clearer naming)
|
|
31
|
+
-- ============================================================================
|
|
32
|
+
-- Tracks what didn't work to prevent repeating mistakes
|
|
33
|
+
|
|
34
|
+
CREATE TABLE IF NOT EXISTS failed_approaches (
|
|
35
|
+
id TEXT PRIMARY KEY, -- uuid → TEXT
|
|
36
|
+
repository_id TEXT NOT NULL, -- Foreign key to repositories
|
|
37
|
+
title TEXT NOT NULL, -- Short description
|
|
38
|
+
problem TEXT NOT NULL, -- What problem was being solved
|
|
39
|
+
approach TEXT NOT NULL, -- What was tried
|
|
40
|
+
failure_reason TEXT NOT NULL, -- Why it failed
|
|
41
|
+
related_files TEXT, -- JSON array of related file paths
|
|
42
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
43
|
+
|
|
44
|
+
FOREIGN KEY (repository_id) REFERENCES repositories(id) ON DELETE CASCADE
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
-- Indexes for common queries
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_failed_approaches_repository_id ON failed_approaches(repository_id);
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_failed_approaches_created_at ON failed_approaches(created_at DESC);
|
|
50
|
+
|
|
51
|
+
-- ============================================================================
|
|
52
|
+
-- 3. Failed Approaches FTS5 Virtual Table
|
|
53
|
+
-- ============================================================================
|
|
54
|
+
-- External content FTS5 for searching failed approaches
|
|
55
|
+
|
|
56
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS failed_approaches_fts USING fts5(
|
|
57
|
+
title,
|
|
58
|
+
problem,
|
|
59
|
+
approach,
|
|
60
|
+
failure_reason,
|
|
61
|
+
content='failed_approaches',
|
|
62
|
+
content_rowid='rowid'
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
-- ============================================================================
|
|
66
|
+
-- 4. Failed Approaches FTS5 Sync Triggers
|
|
67
|
+
-- ============================================================================
|
|
68
|
+
|
|
69
|
+
-- After INSERT: Add new failed approach to FTS index
|
|
70
|
+
CREATE TRIGGER IF NOT EXISTS failed_approaches_fts_ai
|
|
71
|
+
AFTER INSERT ON failed_approaches
|
|
72
|
+
BEGIN
|
|
73
|
+
INSERT INTO failed_approaches_fts(rowid, title, problem, approach, failure_reason)
|
|
74
|
+
VALUES (new.rowid, new.title, new.problem, new.approach, new.failure_reason);
|
|
75
|
+
END;
|
|
76
|
+
|
|
77
|
+
-- After DELETE: Remove failed approach from FTS index
|
|
78
|
+
CREATE TRIGGER IF NOT EXISTS failed_approaches_fts_ad
|
|
79
|
+
AFTER DELETE ON failed_approaches
|
|
80
|
+
BEGIN
|
|
81
|
+
INSERT INTO failed_approaches_fts(failed_approaches_fts, rowid, title, problem, approach, failure_reason)
|
|
82
|
+
VALUES ('delete', old.rowid, old.title, old.problem, old.approach, old.failure_reason);
|
|
83
|
+
END;
|
|
84
|
+
|
|
85
|
+
-- After UPDATE: Update failed approach in FTS index (delete old, insert new)
|
|
86
|
+
CREATE TRIGGER IF NOT EXISTS failed_approaches_fts_au
|
|
87
|
+
AFTER UPDATE ON failed_approaches
|
|
88
|
+
BEGIN
|
|
89
|
+
INSERT INTO failed_approaches_fts(failed_approaches_fts, rowid, title, problem, approach, failure_reason)
|
|
90
|
+
VALUES ('delete', old.rowid, old.title, old.problem, old.approach, old.failure_reason);
|
|
91
|
+
INSERT INTO failed_approaches_fts(rowid, title, problem, approach, failure_reason)
|
|
92
|
+
VALUES (new.rowid, new.title, new.problem, new.approach, new.failure_reason);
|
|
93
|
+
END;
|
|
94
|
+
|
|
95
|
+
-- ============================================================================
|
|
96
|
+
-- 5. Pattern Annotations Table
|
|
97
|
+
-- ============================================================================
|
|
98
|
+
-- Enhanced pattern detection with evidence counting and confidence scoring
|
|
99
|
+
|
|
100
|
+
CREATE TABLE IF NOT EXISTS pattern_annotations (
|
|
101
|
+
id TEXT PRIMARY KEY, -- uuid → TEXT
|
|
102
|
+
repository_id TEXT NOT NULL, -- Foreign key to repositories
|
|
103
|
+
pattern_type TEXT NOT NULL, -- Pattern category (logging, error-handling, testing, etc.)
|
|
104
|
+
pattern_name TEXT NOT NULL, -- Pattern identifier
|
|
105
|
+
description TEXT NOT NULL, -- Human-readable description
|
|
106
|
+
example_code TEXT, -- Code example (optional)
|
|
107
|
+
evidence_count INTEGER NOT NULL DEFAULT 1, -- Number of occurrences found
|
|
108
|
+
confidence REAL NOT NULL DEFAULT 1.0, -- Confidence score (0.0-1.0)
|
|
109
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
110
|
+
|
|
111
|
+
FOREIGN KEY (repository_id) REFERENCES repositories(id) ON DELETE CASCADE,
|
|
112
|
+
|
|
113
|
+
CHECK (confidence >= 0.0 AND confidence <= 1.0),
|
|
114
|
+
CHECK (evidence_count >= 1)
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
-- Indexes for common queries
|
|
118
|
+
CREATE INDEX IF NOT EXISTS idx_pattern_annotations_repository_id ON pattern_annotations(repository_id);
|
|
119
|
+
CREATE INDEX IF NOT EXISTS idx_pattern_annotations_pattern_type ON pattern_annotations(pattern_type);
|
|
120
|
+
CREATE INDEX IF NOT EXISTS idx_pattern_annotations_confidence ON pattern_annotations(confidence DESC);
|
|
121
|
+
-- Composite index for high-confidence patterns by type
|
|
122
|
+
CREATE INDEX IF NOT EXISTS idx_pattern_annotations_type_confidence
|
|
123
|
+
ON pattern_annotations(repository_id, pattern_type, confidence DESC);
|
|
124
|
+
|
|
125
|
+
-- ============================================================================
|
|
126
|
+
-- 6. Agent Sessions Table
|
|
28
127
|
-- ============================================================================
|
|
29
128
|
-- Tracks agent work sessions for learning and analysis
|
|
30
129
|
|
|
31
130
|
CREATE TABLE IF NOT EXISTS agent_sessions (
|
|
32
|
-
id TEXT PRIMARY KEY,
|
|
33
|
-
repository_id TEXT NOT NULL,
|
|
34
|
-
agent_type TEXT,
|
|
35
|
-
task_summary TEXT,
|
|
36
|
-
outcome TEXT,
|
|
37
|
-
files_modified TEXT,
|
|
131
|
+
id TEXT PRIMARY KEY, -- uuid → TEXT
|
|
132
|
+
repository_id TEXT NOT NULL, -- Foreign key to repositories
|
|
133
|
+
agent_type TEXT, -- Type of agent (plan, build, improve, etc.)
|
|
134
|
+
task_summary TEXT, -- What the agent was working on
|
|
135
|
+
outcome TEXT, -- Session outcome
|
|
136
|
+
files_modified TEXT, -- JSON array of modified file paths
|
|
38
137
|
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
39
|
-
ended_at TEXT,
|
|
138
|
+
ended_at TEXT, -- NULL if session is ongoing
|
|
40
139
|
|
|
41
140
|
FOREIGN KEY (repository_id) REFERENCES repositories(id) ON DELETE CASCADE,
|
|
141
|
+
|
|
42
142
|
CHECK (outcome IS NULL OR outcome IN ('success', 'failure', 'partial'))
|
|
43
143
|
);
|
|
44
144
|
|
|
145
|
+
-- Indexes for common queries
|
|
45
146
|
CREATE INDEX IF NOT EXISTS idx_agent_sessions_repository_id ON agent_sessions(repository_id);
|
|
46
147
|
CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent_type ON agent_sessions(agent_type) WHERE agent_type IS NOT NULL;
|
|
47
148
|
CREATE INDEX IF NOT EXISTS idx_agent_sessions_outcome ON agent_sessions(outcome) WHERE outcome IS NOT NULL;
|
|
48
149
|
CREATE INDEX IF NOT EXISTS idx_agent_sessions_started_at ON agent_sessions(started_at DESC);
|
|
150
|
+
-- Partial index for ongoing sessions
|
|
49
151
|
CREATE INDEX IF NOT EXISTS idx_agent_sessions_ongoing ON agent_sessions(repository_id) WHERE ended_at IS NULL;
|
|
50
152
|
|
|
51
153
|
-- ============================================================================
|
|
52
|
-
--
|
|
154
|
+
-- 7. Session Insights Table
|
|
155
|
+
-- ============================================================================
|
|
156
|
+
-- Insights discovered during agent sessions with proper foreign keys
|
|
157
|
+
|
|
158
|
+
CREATE TABLE IF NOT EXISTS session_insights (
|
|
159
|
+
id TEXT PRIMARY KEY, -- uuid → TEXT
|
|
160
|
+
session_id TEXT NOT NULL, -- Foreign key to agent_sessions
|
|
161
|
+
insight_type TEXT NOT NULL, -- Type of insight
|
|
162
|
+
content TEXT NOT NULL, -- The insight content
|
|
163
|
+
related_file_id TEXT, -- Optional reference to indexed_files
|
|
164
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
165
|
+
|
|
166
|
+
FOREIGN KEY (session_id) REFERENCES agent_sessions(id) ON DELETE CASCADE,
|
|
167
|
+
FOREIGN KEY (related_file_id) REFERENCES indexed_files(id) ON DELETE SET NULL,
|
|
168
|
+
|
|
169
|
+
CHECK (insight_type IN ('discovery', 'failure', 'workaround'))
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
-- Indexes for common queries
|
|
173
|
+
CREATE INDEX IF NOT EXISTS idx_session_insights_session_id ON session_insights(session_id);
|
|
174
|
+
CREATE INDEX IF NOT EXISTS idx_session_insights_insight_type ON session_insights(insight_type);
|
|
175
|
+
CREATE INDEX IF NOT EXISTS idx_session_insights_related_file ON session_insights(related_file_id)
|
|
176
|
+
WHERE related_file_id IS NOT NULL;
|
|
177
|
+
CREATE INDEX IF NOT EXISTS idx_session_insights_created_at ON session_insights(created_at DESC);
|
|
178
|
+
|
|
179
|
+
-- ============================================================================
|
|
180
|
+
-- 8. Record Migration
|
|
53
181
|
-- ============================================================================
|
|
54
182
|
|
|
55
183
|
INSERT OR IGNORE INTO schema_migrations (name) VALUES ('004_memory_layer');
|
|
@@ -19,7 +19,6 @@ import { cpus } from "node:os";
|
|
|
19
19
|
import { createLogger } from "@logging/logger.js";
|
|
20
20
|
import { findProjectRoot } from "@config/project-root.js";
|
|
21
21
|
import { ensureKotadbIgnored } from "@config/gitignore.js";
|
|
22
|
-
import { runMigrations, updateExistingChecksums } from "./migration-runner.js";
|
|
23
22
|
|
|
24
23
|
const logger = createLogger({ module: "sqlite-client" });
|
|
25
24
|
|
|
@@ -138,11 +137,8 @@ export class KotaDatabase {
|
|
|
138
137
|
this.configurePragmas();
|
|
139
138
|
|
|
140
139
|
// Auto-initialize schema if not already present (writer only)
|
|
141
|
-
// - New database: Apply full base schema
|
|
142
|
-
// - Existing database: Run pending migrations
|
|
143
140
|
if (!this.config.readonly && !this.config.skipSchemaInit) {
|
|
144
141
|
if (!this.tableExists("indexed_files")) {
|
|
145
|
-
// NEW DATABASE: Apply base schema
|
|
146
142
|
const schemaPath = join(__dirname, "../sqlite-schema.sql");
|
|
147
143
|
const schema = readFileSync(schemaPath, "utf-8");
|
|
148
144
|
this.exec(schema);
|
|
@@ -150,28 +146,7 @@ export class KotaDatabase {
|
|
|
150
146
|
path: this.config.path,
|
|
151
147
|
});
|
|
152
148
|
} else {
|
|
153
|
-
|
|
154
|
-
const migrationsDir = join(__dirname, "../migrations");
|
|
155
|
-
try {
|
|
156
|
-
const result = runMigrations(this, migrationsDir);
|
|
157
|
-
if (result.appliedCount > 0) {
|
|
158
|
-
logger.info("Applied pending migrations", {
|
|
159
|
-
count: result.appliedCount,
|
|
160
|
-
migrations: result.appliedMigrations,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
// Update checksums for existing migrations (after checksum column added)
|
|
164
|
-
updateExistingChecksums(this, migrationsDir);
|
|
165
|
-
if (result.errors.length > 0) {
|
|
166
|
-
logger.error("Migration errors", { errors: result.errors });
|
|
167
|
-
}
|
|
168
|
-
} catch (error) {
|
|
169
|
-
logger.error("Migration runner failed", {
|
|
170
|
-
error: error instanceof Error ? error.message : String(error),
|
|
171
|
-
});
|
|
172
|
-
// Do not throw - allow database to continue operating with current schema
|
|
173
|
-
// This prevents startup failures due to migration issues
|
|
174
|
-
}
|
|
149
|
+
logger.debug("SQLite schema already initialized");
|
|
175
150
|
}
|
|
176
151
|
}
|
|
177
152
|
|
package/src/db/sqlite-schema.sql
CHANGED
|
@@ -462,54 +462,6 @@ BEGIN
|
|
|
462
462
|
VALUES (new.rowid, new.content);
|
|
463
463
|
END;
|
|
464
464
|
|
|
465
|
-
-- ============================================================================
|
|
466
|
-
-- 11. Workflow Contexts Table (Issue #144)
|
|
467
|
-
-- ============================================================================
|
|
468
|
-
-- Stores curated context data for each workflow phase
|
|
469
|
-
|
|
470
|
-
CREATE TABLE IF NOT EXISTS workflow_contexts (
|
|
471
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
472
|
-
workflow_id TEXT NOT NULL,
|
|
473
|
-
phase TEXT NOT NULL,
|
|
474
|
-
context_data TEXT NOT NULL,
|
|
475
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
476
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
477
|
-
|
|
478
|
-
UNIQUE(workflow_id, phase),
|
|
479
|
-
CHECK (phase IN ('analysis', 'plan', 'build', 'improve'))
|
|
480
|
-
);
|
|
481
|
-
|
|
482
|
-
CREATE INDEX IF NOT EXISTS idx_workflow_contexts_workflow_id
|
|
483
|
-
ON workflow_contexts(workflow_id);
|
|
484
|
-
|
|
485
|
-
CREATE INDEX IF NOT EXISTS idx_workflow_contexts_created_at
|
|
486
|
-
ON workflow_contexts(created_at DESC);
|
|
487
|
-
|
|
488
|
-
-- ============================================================================
|
|
489
|
-
-- 12. Agent Sessions Table
|
|
490
|
-
-- ============================================================================
|
|
491
|
-
-- Tracks agent work sessions for learning and analysis
|
|
492
|
-
|
|
493
|
-
CREATE TABLE IF NOT EXISTS agent_sessions (
|
|
494
|
-
id TEXT PRIMARY KEY,
|
|
495
|
-
repository_id TEXT NOT NULL,
|
|
496
|
-
agent_type TEXT,
|
|
497
|
-
task_summary TEXT,
|
|
498
|
-
outcome TEXT,
|
|
499
|
-
files_modified TEXT,
|
|
500
|
-
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
501
|
-
ended_at TEXT,
|
|
502
|
-
|
|
503
|
-
FOREIGN KEY (repository_id) REFERENCES repositories(id) ON DELETE CASCADE,
|
|
504
|
-
CHECK (outcome IS NULL OR outcome IN ('success', 'failure', 'partial'))
|
|
505
|
-
);
|
|
506
|
-
|
|
507
|
-
CREATE INDEX IF NOT EXISTS idx_agent_sessions_repository_id ON agent_sessions(repository_id);
|
|
508
|
-
CREATE INDEX IF NOT EXISTS idx_agent_sessions_agent_type ON agent_sessions(agent_type) WHERE agent_type IS NOT NULL;
|
|
509
|
-
CREATE INDEX IF NOT EXISTS idx_agent_sessions_outcome ON agent_sessions(outcome) WHERE outcome IS NOT NULL;
|
|
510
|
-
CREATE INDEX IF NOT EXISTS idx_agent_sessions_started_at ON agent_sessions(started_at DESC);
|
|
511
|
-
CREATE INDEX IF NOT EXISTS idx_agent_sessions_ongoing ON agent_sessions(repository_id) WHERE ended_at IS NULL;
|
|
512
|
-
|
|
513
465
|
-- Record memory layer migration
|
|
514
466
|
INSERT OR IGNORE INTO schema_migrations (name) VALUES ('002_memory_layer_tables');
|
|
515
467
|
|
package/src/indexer/constants.ts
CHANGED
package/src/indexer/parsers.ts
CHANGED
|
@@ -15,7 +15,6 @@ const SUPPORTED_EXTENSIONS = new Set<string>([
|
|
|
15
15
|
".cjs",
|
|
16
16
|
".mjs",
|
|
17
17
|
".json",
|
|
18
|
-
".sql",
|
|
19
18
|
]);
|
|
20
19
|
|
|
21
20
|
const IGNORED_DIRECTORIES = new Set<string>([
|
|
@@ -162,8 +161,7 @@ export async function parseSourceFile(
|
|
|
162
161
|
return null;
|
|
163
162
|
}
|
|
164
163
|
|
|
165
|
-
|
|
166
|
-
const dependencies = extname(path) === ".sql" ? [] : extractDependencies(content);
|
|
164
|
+
const dependencies = extractDependencies(content);
|
|
167
165
|
|
|
168
166
|
return {
|
|
169
167
|
projectRoot: resolve(projectRoot),
|
package/src/mcp/server.ts
CHANGED
|
@@ -16,7 +16,6 @@ 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,
|
|
20
19
|
INDEX_REPOSITORY_TOOL,
|
|
21
20
|
LIST_RECENT_FILES_TOOL,
|
|
22
21
|
SEARCH_TOOL,
|
|
@@ -36,7 +35,6 @@ import {
|
|
|
36
35
|
// Execute functions
|
|
37
36
|
executeAnalyzeChangeImpact,
|
|
38
37
|
executeGenerateTaskContext,
|
|
39
|
-
executeGetIndexStatistics,
|
|
40
38
|
executeIndexRepository,
|
|
41
39
|
executeListRecentFiles,
|
|
42
40
|
executeSearch,
|
package/src/mcp/tools.ts
CHANGED
|
@@ -6,14 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
getIndexStatistics,
|
|
10
9
|
listRecentFiles,
|
|
11
10
|
queryDependencies,
|
|
12
11
|
queryDependents,
|
|
13
12
|
resolveFilePath,
|
|
14
13
|
runIndexingWorkflow,
|
|
15
14
|
searchFiles,
|
|
16
|
-
extractLineSnippets,
|
|
17
15
|
} from "@api/queries";
|
|
18
16
|
import { getDomainKeyFiles } from "@api/expertise-queries.js";
|
|
19
17
|
import { getGlobalDatabase } from "@db/sqlite/index.js";
|
|
@@ -113,20 +111,8 @@ export function isValidToolset(value: string): value is ToolsetTier {
|
|
|
113
111
|
export const SEARCH_TOOL: ToolDefinition = {
|
|
114
112
|
tier: "core",
|
|
115
113
|
name: "search",
|
|
116
|
-
description:
|
|
117
|
-
|
|
118
|
-
OUTPUT MODES:
|
|
119
|
-
- 'paths': File paths only (~100 bytes/result)
|
|
120
|
-
- 'compact': Summary info (~200 bytes/result) - DEFAULT for code scope
|
|
121
|
-
- 'snippet': Matching lines with context (~2KB/result)
|
|
122
|
-
- 'full': Complete content (~100KB/result) - Use with caution for code scope
|
|
123
|
-
|
|
124
|
-
TIPS:
|
|
125
|
-
- Use 'snippet' for code exploration (shows matches in context)
|
|
126
|
-
- Use 'compact' for quick file discovery
|
|
127
|
-
- Use 'full' only for small result sets (symbols, decisions, etc.)
|
|
128
|
-
|
|
129
|
-
Supports multiple search scopes simultaneously with scope-specific filters.`,
|
|
114
|
+
description:
|
|
115
|
+
"Search indexed code, symbols, decisions, patterns, and failures. Supports multiple search scopes simultaneously with scope-specific filters and output formats.",
|
|
130
116
|
inputSchema: {
|
|
131
117
|
type: "object",
|
|
132
118
|
properties: {
|
|
@@ -210,14 +196,8 @@ Supports multiple search scopes simultaneously with scope-specific filters.`,
|
|
|
210
196
|
},
|
|
211
197
|
output: {
|
|
212
198
|
type: "string",
|
|
213
|
-
enum: ["full", "paths", "compact"
|
|
214
|
-
description: "Output format
|
|
215
|
-
},
|
|
216
|
-
context_lines: {
|
|
217
|
-
type: "number",
|
|
218
|
-
description: "Lines of context before/after matches (snippet mode only, default: 3, max: 10)",
|
|
219
|
-
minimum: 0,
|
|
220
|
-
maximum: 10,
|
|
199
|
+
enum: ["full", "paths", "compact"],
|
|
200
|
+
description: "Output format (default: 'full')",
|
|
221
201
|
},
|
|
222
202
|
},
|
|
223
203
|
required: ["query"],
|
|
@@ -373,21 +353,6 @@ export const ANALYZE_CHANGE_IMPACT_TOOL: ToolDefinition = {
|
|
|
373
353
|
},
|
|
374
354
|
};
|
|
375
355
|
|
|
376
|
-
/**
|
|
377
|
-
* Tool: get_index_statistics
|
|
378
|
-
*/
|
|
379
|
-
export const GET_INDEX_STATISTICS_TOOL: ToolDefinition = {
|
|
380
|
-
tier: "core",
|
|
381
|
-
name: "get_index_statistics",
|
|
382
|
-
description:
|
|
383
|
-
"Get statistics about indexed data (files, symbols, references, decisions, patterns, failures). Useful for understanding what data is available for search.",
|
|
384
|
-
inputSchema: {
|
|
385
|
-
type: "object",
|
|
386
|
-
properties: {},
|
|
387
|
-
required: [],
|
|
388
|
-
},
|
|
389
|
-
};
|
|
390
|
-
|
|
391
356
|
/**
|
|
392
357
|
* Tool: validate_implementation_spec
|
|
393
358
|
*/
|
|
@@ -805,7 +770,6 @@ export function getToolDefinitions(): ToolDefinition[] {
|
|
|
805
770
|
LIST_RECENT_FILES_TOOL,
|
|
806
771
|
SEARCH_DEPENDENCIES_TOOL,
|
|
807
772
|
ANALYZE_CHANGE_IMPACT_TOOL,
|
|
808
|
-
GET_INDEX_STATISTICS_TOOL,
|
|
809
773
|
VALIDATE_IMPLEMENTATION_SPEC_TOOL,
|
|
810
774
|
SYNC_EXPORT_TOOL,
|
|
811
775
|
SYNC_IMPORT_TOOL,
|
|
@@ -994,122 +958,11 @@ async function searchSymbols(
|
|
|
994
958
|
}));
|
|
995
959
|
}
|
|
996
960
|
|
|
997
|
-
/**
|
|
998
|
-
* Generate contextual tips based on search query and parameters.
|
|
999
|
-
* Uses static pattern matching (no NLP) to detect suboptimal usage patterns.
|
|
1000
|
-
*
|
|
1001
|
-
* Tip frequency: MODERATE - show tips frequently including "nice to know" suggestions.
|
|
1002
|
-
*
|
|
1003
|
-
* @param query - Search query string
|
|
1004
|
-
* @param scopes - Search scopes used
|
|
1005
|
-
* @param filters - Normalized filters applied
|
|
1006
|
-
* @param scopeResults - Results by scope
|
|
1007
|
-
* @returns Array of tip strings (empty if search is optimal)
|
|
1008
|
-
*/
|
|
1009
|
-
function generateSearchTips(
|
|
1010
|
-
query: string,
|
|
1011
|
-
scopes: string[],
|
|
1012
|
-
filters: NormalizedFilters,
|
|
1013
|
-
scopeResults: Record<string, unknown[]>
|
|
1014
|
-
): string[] {
|
|
1015
|
-
const tips: string[] = [];
|
|
1016
|
-
const queryLower = query.toLowerCase();
|
|
1017
|
-
|
|
1018
|
-
// Pattern 1: Query contains structural keywords but not using symbols scope
|
|
1019
|
-
const structuralKeywords = ['function', 'class', 'interface', 'type', 'method', 'component'];
|
|
1020
|
-
const hasStructuralKeyword = structuralKeywords.some(kw => queryLower.includes(kw));
|
|
1021
|
-
|
|
1022
|
-
if (hasStructuralKeyword && !scopes.includes('symbols')) {
|
|
1023
|
-
const matchedKeyword = structuralKeywords.find(kw => queryLower.includes(kw)) || 'function';
|
|
1024
|
-
tips.push(
|
|
1025
|
-
`You searched for "${query}" in code. Try scope: ['symbols'] with filters: {symbol_kind: ['${matchedKeyword}']} for precise structural discovery.`
|
|
1026
|
-
);
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
// Pattern 2: Query looks like a file path but using code search
|
|
1030
|
-
const looksLikeFilePath = /^[\w\-./]+\.(ts|tsx|js|jsx|py|rs|go|java)$/i.test(query);
|
|
1031
|
-
if (looksLikeFilePath && scopes.includes('code')) {
|
|
1032
|
-
tips.push(
|
|
1033
|
-
`Query "${query}" looks like a file path. Consider using search_dependencies tool to find files that depend on this file or its dependencies.`
|
|
1034
|
-
);
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
// Pattern 3: Symbol search without exported_only filter
|
|
1038
|
-
if (scopes.includes('symbols') && filters.exported_only === undefined) {
|
|
1039
|
-
const symbolCount = scopeResults['symbols']?.length || 0;
|
|
1040
|
-
if (symbolCount > 10) {
|
|
1041
|
-
tips.push(
|
|
1042
|
-
`Found ${symbolCount} symbols. Add filters: {exported_only: true} to narrow to public API only.`
|
|
1043
|
-
);
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
// Pattern 4: No repository filter with large result set
|
|
1048
|
-
if (!filters.repositoryId) {
|
|
1049
|
-
const totalResults = Object.values(scopeResults).reduce((sum, arr) => sum + arr.length, 0);
|
|
1050
|
-
if (totalResults > 20) {
|
|
1051
|
-
tips.push(
|
|
1052
|
-
`Found ${totalResults} results across all repositories. Add filters: {repository: "owner/repo"} to narrow to a specific repository.`
|
|
1053
|
-
);
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
// Pattern 5: Code search without glob/language filters
|
|
1058
|
-
if (scopes.includes('code') && !filters.glob && !filters.language) {
|
|
1059
|
-
const codeCount = scopeResults['code']?.length || 0;
|
|
1060
|
-
if (codeCount > 15) {
|
|
1061
|
-
tips.push(
|
|
1062
|
-
`Found ${codeCount} code results. Try filters: {glob: "**/*.ts"} or {language: "typescript"} to narrow file types.`
|
|
1063
|
-
);
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
// Pattern 6: Suggest decisions scope for "why" questions
|
|
1068
|
-
if (/\b(why|reason|decision|chose|choice)\b/i.test(query) && !scopes.includes('decisions')) {
|
|
1069
|
-
tips.push(
|
|
1070
|
-
`Query contains "why/reason/decision". Try scope: ['decisions'] to search architectural decisions and rationale.`
|
|
1071
|
-
);
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
// Pattern 7: Suggest patterns scope for "how" questions
|
|
1075
|
-
if (/\b(how|pattern|best practice|convention)\b/i.test(query) && !scopes.includes('patterns')) {
|
|
1076
|
-
tips.push(
|
|
1077
|
-
`Query asks "how to". Try scope: ['patterns'] to search coding patterns and conventions from this codebase.`
|
|
1078
|
-
);
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
// Pattern 8: Suggest failures scope for error-related queries
|
|
1082
|
-
if (/\b(error|bug|fail|issue|problem|fix)\b/i.test(query) && !scopes.includes('failures')) {
|
|
1083
|
-
tips.push(
|
|
1084
|
-
`Query mentions errors/issues. Try scope: ['failures'] to learn from past mistakes and avoid repeated failures.`
|
|
1085
|
-
);
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
// Pattern 9: Single scope when multi-scope could be useful
|
|
1089
|
-
if (scopes.length === 1 && scopes[0] === 'code') {
|
|
1090
|
-
tips.push(
|
|
1091
|
-
`Tip: You can search multiple scopes simultaneously. Try scope: ['code', 'symbols'] for broader discovery.`
|
|
1092
|
-
);
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
// Pattern 10: Suggest compact format for large result sets
|
|
1096
|
-
const totalResults = Object.values(scopeResults).reduce((sum, arr) => sum + arr.length, 0);
|
|
1097
|
-
if (totalResults > 30 && !tips.some(t => t.includes('output: "compact"'))) {
|
|
1098
|
-
tips.push(
|
|
1099
|
-
`Returning ${totalResults} full results. Use output: "compact" for summary view or output: "paths" for file paths only.`
|
|
1100
|
-
);
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
return tips;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
961
|
function formatSearchResults(
|
|
1107
962
|
query: string,
|
|
1108
963
|
scopes: string[],
|
|
1109
964
|
scopeResults: Record<string, unknown[]>,
|
|
1110
|
-
format: string
|
|
1111
|
-
filters: NormalizedFilters,
|
|
1112
|
-
contextLines?: number
|
|
965
|
+
format: string
|
|
1113
966
|
): Record<string, unknown> {
|
|
1114
967
|
const response: Record<string, unknown> = {
|
|
1115
968
|
query,
|
|
@@ -1145,36 +998,6 @@ function formatSearchResults(
|
|
|
1145
998
|
}
|
|
1146
999
|
return item;
|
|
1147
1000
|
});
|
|
1148
|
-
} else if (format === "snippet") {
|
|
1149
|
-
// Snippet extraction with context
|
|
1150
|
-
if (scope === "code") {
|
|
1151
|
-
(response.results as Record<string, unknown>)[scope] = items.map((item: any) => {
|
|
1152
|
-
const matches = extractLineSnippets(
|
|
1153
|
-
item.content || "",
|
|
1154
|
-
query,
|
|
1155
|
-
contextLines || 3
|
|
1156
|
-
);
|
|
1157
|
-
return {
|
|
1158
|
-
path: item.path,
|
|
1159
|
-
matches: matches
|
|
1160
|
-
};
|
|
1161
|
-
});
|
|
1162
|
-
} else {
|
|
1163
|
-
// For non-code scopes, fall back to compact format
|
|
1164
|
-
// (snippets only meaningful for code files)
|
|
1165
|
-
(response.results as Record<string, unknown>)[scope] = items.map((item: any) => {
|
|
1166
|
-
if (scope === "symbols") {
|
|
1167
|
-
return { name: item.name, kind: item.kind, file: item.location.file };
|
|
1168
|
-
} else if (scope === "decisions") {
|
|
1169
|
-
return { title: item.title, scope: item.scope };
|
|
1170
|
-
} else if (scope === "patterns") {
|
|
1171
|
-
return { pattern_type: item.pattern_type, file_path: item.file_path };
|
|
1172
|
-
} else if (scope === "failures") {
|
|
1173
|
-
return { title: item.title, problem: item.problem };
|
|
1174
|
-
}
|
|
1175
|
-
return item;
|
|
1176
|
-
});
|
|
1177
|
-
}
|
|
1178
1001
|
} else {
|
|
1179
1002
|
// Full details
|
|
1180
1003
|
(response.results as Record<string, unknown>)[scope] = items;
|
|
@@ -1184,13 +1007,6 @@ function formatSearchResults(
|
|
|
1184
1007
|
(response.counts as Record<string, unknown>).total = ((response.counts as Record<string, unknown>).total as number) + items.length;
|
|
1185
1008
|
}
|
|
1186
1009
|
|
|
1187
|
-
|
|
1188
|
-
// Generate and add tips if applicable
|
|
1189
|
-
const tips = generateSearchTips(query, scopes, filters, scopeResults);
|
|
1190
|
-
if (tips.length > 0) {
|
|
1191
|
-
response.tips = tips;
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
1010
|
return response;
|
|
1195
1011
|
}
|
|
1196
1012
|
|
|
@@ -1241,30 +1057,13 @@ export async function executeSearch(
|
|
|
1241
1057
|
}
|
|
1242
1058
|
|
|
1243
1059
|
if (p.output !== undefined) {
|
|
1244
|
-
if (typeof p.output !== "string" || !["full", "paths", "compact"
|
|
1245
|
-
throw new Error("Parameter 'output' must be one of: full, paths, compact
|
|
1060
|
+
if (typeof p.output !== "string" || !["full", "paths", "compact"].includes(p.output)) {
|
|
1061
|
+
throw new Error("Parameter 'output' must be one of: full, paths, compact");
|
|
1246
1062
|
}
|
|
1247
1063
|
}
|
|
1248
1064
|
|
|
1249
|
-
if (p.context_lines !== undefined && typeof p.context_lines !== "number") {
|
|
1250
|
-
throw new Error("Parameter 'context_lines' must be a number");
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
if (p.context_lines !== undefined && (p.context_lines < 0 || p.context_lines > 10)) {
|
|
1254
|
-
throw new Error("Parameter 'context_lines' must be between 0 and 10");
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
1065
|
const limit = Math.min(Math.max((p.limit as number) || 20, 1), 100);
|
|
1258
|
-
|
|
1259
|
-
let defaultOutput = "full";
|
|
1260
|
-
if (scopes.length === 1 && scopes[0] === "code") {
|
|
1261
|
-
defaultOutput = "compact"; // Code-only searches default to compact
|
|
1262
|
-
} else if (scopes.includes("code") && scopes.length > 1) {
|
|
1263
|
-
defaultOutput = "compact"; // Multi-scope including code defaults to compact
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
const output = (p.output as string) || defaultOutput;
|
|
1267
|
-
const contextLines = Math.min(Math.max((p.context_lines as number) || 3, 0), 10);
|
|
1066
|
+
const output = (p.output as string) || "full";
|
|
1268
1067
|
const filters = normalizeFilters(p.filters);
|
|
1269
1068
|
|
|
1270
1069
|
// Route to scope handlers in parallel
|
|
@@ -1343,7 +1142,7 @@ export async function executeSearch(
|
|
|
1343
1142
|
await Promise.all(searchPromises);
|
|
1344
1143
|
|
|
1345
1144
|
// Format output
|
|
1346
|
-
const response = formatSearchResults(p.query as string, scopes, results, output
|
|
1145
|
+
const response = formatSearchResults(p.query as string, scopes, results, output);
|
|
1347
1146
|
|
|
1348
1147
|
logger.info("Unified search completed", {
|
|
1349
1148
|
query: p.query,
|
|
@@ -1835,26 +1634,6 @@ export async function executeAnalyzeChangeImpact(
|
|
|
1835
1634
|
return result;
|
|
1836
1635
|
}
|
|
1837
1636
|
|
|
1838
|
-
/**
|
|
1839
|
-
* Execute get_index_statistics tool
|
|
1840
|
-
*/
|
|
1841
|
-
export async function executeGetIndexStatistics(
|
|
1842
|
-
params: unknown,
|
|
1843
|
-
requestId: string | number,
|
|
1844
|
-
userId: string,
|
|
1845
|
-
): Promise<unknown> {
|
|
1846
|
-
// No parameters to validate
|
|
1847
|
-
|
|
1848
|
-
logger.info("Getting index statistics", { request_id: String(requestId), user_id: userId });
|
|
1849
|
-
|
|
1850
|
-
const stats = getIndexStatistics();
|
|
1851
|
-
|
|
1852
|
-
return {
|
|
1853
|
-
...stats,
|
|
1854
|
-
summary: `${stats.symbols.toLocaleString()} symbols, ${stats.files.toLocaleString()} files, ${stats.repositories} repositories indexed`,
|
|
1855
|
-
};
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
1637
|
/**
|
|
1859
1638
|
|
|
1860
1639
|
/**
|