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
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
-- SQLite Migration: Workflow Context Accumulation
|
|
2
|
-
--
|
|
3
|
-
-- Migration: 005_workflow_contexts
|
|
4
|
-
-- Issue: #144 - ADW context accumulation for inter-phase handoffs
|
|
5
|
-
-- Author: Claude Code
|
|
6
|
-
-- Date: 2026-02-04
|
|
7
|
-
--
|
|
8
|
-
-- This migration adds workflow context storage for ADW automation,
|
|
9
|
-
-- enabling context accumulation between workflow phases (analysis -> plan -> build -> improve).
|
|
10
|
-
-- Context is stored in the main KotaDB database for future MCP tool integration.
|
|
11
|
-
|
|
12
|
-
-- ============================================================================
|
|
13
|
-
-- 1. Workflow Contexts Table
|
|
14
|
-
-- ============================================================================
|
|
15
|
-
-- Stores curated context data for each workflow phase
|
|
16
|
-
|
|
17
|
-
CREATE TABLE IF NOT EXISTS workflow_contexts (
|
|
18
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
19
|
-
workflow_id TEXT NOT NULL, -- 'adw-123-20260204T120000'
|
|
20
|
-
phase TEXT NOT NULL, -- 'analysis' | 'plan' | 'build' | 'improve'
|
|
21
|
-
context_data TEXT NOT NULL, -- JSON blob
|
|
22
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
23
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
24
|
-
|
|
25
|
-
UNIQUE(workflow_id, phase),
|
|
26
|
-
CHECK (phase IN ('analysis', 'plan', 'build', 'improve'))
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
-- Index for workflow-scoped queries (primary access pattern)
|
|
30
|
-
CREATE INDEX IF NOT EXISTS idx_workflow_contexts_workflow_id
|
|
31
|
-
ON workflow_contexts(workflow_id);
|
|
32
|
-
|
|
33
|
-
-- Index for time-based queries (debugging/monitoring)
|
|
34
|
-
CREATE INDEX IF NOT EXISTS idx_workflow_contexts_created_at
|
|
35
|
-
ON workflow_contexts(created_at DESC);
|
|
36
|
-
|
|
37
|
-
-- ============================================================================
|
|
38
|
-
-- 2. Record Migration
|
|
39
|
-
-- ============================================================================
|
|
40
|
-
|
|
41
|
-
INSERT OR IGNORE INTO schema_migrations (name) VALUES ('005_workflow_contexts');
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
-- SQLite Migration: Add Checksum Tracking to Schema Migrations
|
|
2
|
-
--
|
|
3
|
-
-- Migration: 006_add_migration_checksums
|
|
4
|
-
-- Issue: #166 - Migration infrastructure
|
|
5
|
-
-- Author: Claude Code
|
|
6
|
-
-- Date: 2026-02-04
|
|
7
|
-
--
|
|
8
|
-
-- Adds checksum column to schema_migrations table for drift detection.
|
|
9
|
-
-- Existing migrations will have NULL checksums (validation skipped).
|
|
10
|
-
|
|
11
|
-
ALTER TABLE schema_migrations ADD COLUMN checksum TEXT;
|
|
12
|
-
|
|
13
|
-
CREATE INDEX IF NOT EXISTS idx_schema_migrations_name ON schema_migrations(name);
|
|
14
|
-
|
|
15
|
-
INSERT OR IGNORE INTO schema_migrations (name) VALUES ('006_add_migration_checksums');
|
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Migration runner for KotaDB SQLite schema management.
|
|
3
|
-
*
|
|
4
|
-
* Features:
|
|
5
|
-
* - Scans migrations directory for .sql files
|
|
6
|
-
* - Applies pending migrations in order
|
|
7
|
-
* - Validates checksums for drift detection
|
|
8
|
-
* - Transactional execution with automatic rollback
|
|
9
|
-
*
|
|
10
|
-
* @module @db/sqlite/migration-runner
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { readdirSync, readFileSync, existsSync } from "node:fs";
|
|
14
|
-
import { join } from "node:path";
|
|
15
|
-
import { createHash } from "node:crypto";
|
|
16
|
-
import type { KotaDatabase } from "./sqlite-client.js";
|
|
17
|
-
import { createLogger } from "@logging/logger.js";
|
|
18
|
-
|
|
19
|
-
const logger = createLogger({ module: "migration-runner" });
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Parsed migration file information
|
|
23
|
-
*/
|
|
24
|
-
export interface Migration {
|
|
25
|
-
filename: string;
|
|
26
|
-
name: string;
|
|
27
|
-
number: number;
|
|
28
|
-
path: string;
|
|
29
|
-
content: string;
|
|
30
|
-
checksum: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Migration record from the database
|
|
35
|
-
*/
|
|
36
|
-
export interface AppliedMigration {
|
|
37
|
-
name: string;
|
|
38
|
-
applied_at: string;
|
|
39
|
-
checksum?: string | null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Result of a migration run
|
|
44
|
-
*/
|
|
45
|
-
export interface MigrationResult {
|
|
46
|
-
appliedCount: number;
|
|
47
|
-
driftDetected: boolean;
|
|
48
|
-
appliedMigrations: string[];
|
|
49
|
-
errors: string[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Parse migration filename to extract number and name.
|
|
54
|
-
* Format: {number}_{name}.sql
|
|
55
|
-
*
|
|
56
|
-
* @param filename - Migration filename (e.g., "004_memory_layer.sql")
|
|
57
|
-
* @returns Parsed number and name, or null if invalid format
|
|
58
|
-
*/
|
|
59
|
-
export function parseMigrationFilename(
|
|
60
|
-
filename: string
|
|
61
|
-
): { number: number; name: string } | null {
|
|
62
|
-
const match = filename.match(/^(\d+)_(.+)\.sql$/);
|
|
63
|
-
if (!match) return null;
|
|
64
|
-
|
|
65
|
-
const numStr = match[1];
|
|
66
|
-
const name = match[2];
|
|
67
|
-
if (!numStr || !name) return null;
|
|
68
|
-
|
|
69
|
-
return { number: parseInt(numStr, 10), name: `${numStr}_${name}` };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Compute SHA-256 checksum of migration content.
|
|
74
|
-
*
|
|
75
|
-
* @param content - Migration SQL content
|
|
76
|
-
* @returns Hex-encoded SHA-256 hash
|
|
77
|
-
*/
|
|
78
|
-
export function computeChecksum(content: string): string {
|
|
79
|
-
return createHash("sha256").update(content, "utf-8").digest("hex");
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Scan migrations directory and return sorted list of migrations.
|
|
84
|
-
*
|
|
85
|
-
* @param migrationsDir - Path to migrations directory
|
|
86
|
-
* @returns Array of migrations sorted by number
|
|
87
|
-
*/
|
|
88
|
-
export function scanMigrations(migrationsDir: string): Migration[] {
|
|
89
|
-
if (!existsSync(migrationsDir)) {
|
|
90
|
-
logger.warn("Migrations directory does not exist", { path: migrationsDir });
|
|
91
|
-
return [];
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const files = readdirSync(migrationsDir)
|
|
95
|
-
.filter((f) => f.endsWith(".sql"))
|
|
96
|
-
.sort(); // Alphabetical sort ensures numeric order for 001, 002, etc.
|
|
97
|
-
|
|
98
|
-
const migrations: Migration[] = [];
|
|
99
|
-
|
|
100
|
-
for (const filename of files) {
|
|
101
|
-
const parsed = parseMigrationFilename(filename);
|
|
102
|
-
if (!parsed) {
|
|
103
|
-
logger.warn("Invalid migration filename (skipping)", { filename });
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const path = join(migrationsDir, filename);
|
|
108
|
-
const content = readFileSync(path, "utf-8");
|
|
109
|
-
const checksum = computeChecksum(content);
|
|
110
|
-
|
|
111
|
-
migrations.push({
|
|
112
|
-
filename,
|
|
113
|
-
name: parsed.name,
|
|
114
|
-
number: parsed.number,
|
|
115
|
-
path,
|
|
116
|
-
content,
|
|
117
|
-
checksum,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Sort by number (redundant if naming is correct, but ensures correctness)
|
|
122
|
-
migrations.sort((a, b) => a.number - b.number);
|
|
123
|
-
|
|
124
|
-
return migrations;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Get list of applied migrations from schema_migrations table.
|
|
129
|
-
*
|
|
130
|
-
* @param db - Database instance
|
|
131
|
-
* @returns Map of migration name to applied migration record
|
|
132
|
-
*/
|
|
133
|
-
export function getAppliedMigrations(
|
|
134
|
-
db: KotaDatabase
|
|
135
|
-
): Map<string, AppliedMigration> {
|
|
136
|
-
// Ensure schema_migrations table exists
|
|
137
|
-
if (!db.tableExists("schema_migrations")) {
|
|
138
|
-
logger.warn("schema_migrations table does not exist");
|
|
139
|
-
return new Map();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Check if checksum column exists
|
|
143
|
-
const columns = db.query<{ name: string }>(
|
|
144
|
-
"SELECT name FROM pragma_table_info('schema_migrations')"
|
|
145
|
-
);
|
|
146
|
-
const hasChecksum = columns.some((c) => c.name === "checksum");
|
|
147
|
-
|
|
148
|
-
// Query with or without checksum column
|
|
149
|
-
const query = hasChecksum
|
|
150
|
-
? "SELECT name, applied_at, checksum FROM schema_migrations ORDER BY id"
|
|
151
|
-
: "SELECT name, applied_at, NULL as checksum FROM schema_migrations ORDER BY id";
|
|
152
|
-
|
|
153
|
-
const rows = db.query<AppliedMigration>(query);
|
|
154
|
-
|
|
155
|
-
return new Map(rows.map((r) => [r.name, r]));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Apply a single migration within a transaction.
|
|
160
|
-
*
|
|
161
|
-
* @param db - Database instance
|
|
162
|
-
* @param migration - Migration to apply
|
|
163
|
-
*/
|
|
164
|
-
export function applyMigration(db: KotaDatabase, migration: Migration): void {
|
|
165
|
-
logger.info("Applying migration", { name: migration.name });
|
|
166
|
-
|
|
167
|
-
db.immediateTransaction(() => {
|
|
168
|
-
// Execute migration SQL
|
|
169
|
-
db.exec(migration.content);
|
|
170
|
-
|
|
171
|
-
// Check if checksum column exists for INSERT
|
|
172
|
-
const columns = db.query<{ name: string }>(
|
|
173
|
-
"SELECT name FROM pragma_table_info('schema_migrations')"
|
|
174
|
-
);
|
|
175
|
-
const hasChecksum = columns.some((c) => c.name === "checksum");
|
|
176
|
-
|
|
177
|
-
// Record migration (with checksum if column exists)
|
|
178
|
-
if (hasChecksum) {
|
|
179
|
-
db.run(
|
|
180
|
-
"INSERT OR REPLACE INTO schema_migrations (name, checksum, applied_at) VALUES (?, ?, datetime('now'))",
|
|
181
|
-
[migration.name, migration.checksum]
|
|
182
|
-
);
|
|
183
|
-
} else {
|
|
184
|
-
// Migration file already contains INSERT OR IGNORE for name
|
|
185
|
-
// Just ensure the record exists
|
|
186
|
-
db.run(
|
|
187
|
-
"INSERT OR IGNORE INTO schema_migrations (name, applied_at) VALUES (?, datetime('now'))",
|
|
188
|
-
[migration.name]
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
logger.info("Migration applied successfully", { name: migration.name });
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Validate checksum for already-applied migration (drift detection).
|
|
198
|
-
*
|
|
199
|
-
* @param migration - Migration file info
|
|
200
|
-
* @param applied - Applied migration record
|
|
201
|
-
* @returns True if checksum matches or no checksum stored
|
|
202
|
-
*/
|
|
203
|
-
export function validateChecksum(
|
|
204
|
-
migration: Migration,
|
|
205
|
-
applied: AppliedMigration
|
|
206
|
-
): boolean {
|
|
207
|
-
if (!applied.checksum) {
|
|
208
|
-
// Old migrations may not have checksum - skip validation
|
|
209
|
-
logger.debug("No checksum stored for migration (skipping validation)", {
|
|
210
|
-
name: migration.name,
|
|
211
|
-
});
|
|
212
|
-
return true;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (migration.checksum !== applied.checksum) {
|
|
216
|
-
logger.warn("Migration checksum mismatch (DRIFT DETECTED)", {
|
|
217
|
-
name: migration.name,
|
|
218
|
-
expected: applied.checksum,
|
|
219
|
-
actual: migration.checksum,
|
|
220
|
-
message:
|
|
221
|
-
"Migration file was modified after being applied. This may indicate schema drift.",
|
|
222
|
-
});
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return true;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Run all pending migrations.
|
|
231
|
-
*
|
|
232
|
-
* @param db - Database instance
|
|
233
|
-
* @param migrationsDir - Path to migrations directory
|
|
234
|
-
* @returns Migration result with count and status
|
|
235
|
-
*/
|
|
236
|
-
export function runMigrations(
|
|
237
|
-
db: KotaDatabase,
|
|
238
|
-
migrationsDir: string
|
|
239
|
-
): MigrationResult {
|
|
240
|
-
logger.info("Starting migration runner", { migrationsDir });
|
|
241
|
-
|
|
242
|
-
const result: MigrationResult = {
|
|
243
|
-
appliedCount: 0,
|
|
244
|
-
driftDetected: false,
|
|
245
|
-
appliedMigrations: [],
|
|
246
|
-
errors: [],
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
// Scan filesystem for migration files
|
|
250
|
-
const availableMigrations = scanMigrations(migrationsDir);
|
|
251
|
-
logger.debug("Found migration files", { count: availableMigrations.length });
|
|
252
|
-
|
|
253
|
-
// Get applied migrations from database
|
|
254
|
-
const appliedMigrations = getAppliedMigrations(db);
|
|
255
|
-
logger.debug("Found applied migrations", { count: appliedMigrations.size });
|
|
256
|
-
|
|
257
|
-
for (const migration of availableMigrations) {
|
|
258
|
-
const applied = appliedMigrations.get(migration.name);
|
|
259
|
-
|
|
260
|
-
if (applied) {
|
|
261
|
-
// Migration already applied - validate checksum
|
|
262
|
-
const valid = validateChecksum(migration, applied);
|
|
263
|
-
if (!valid) {
|
|
264
|
-
result.driftDetected = true;
|
|
265
|
-
}
|
|
266
|
-
} else {
|
|
267
|
-
// Migration not yet applied - apply it
|
|
268
|
-
try {
|
|
269
|
-
applyMigration(db, migration);
|
|
270
|
-
result.appliedCount++;
|
|
271
|
-
result.appliedMigrations.push(migration.name);
|
|
272
|
-
} catch (error) {
|
|
273
|
-
const errorMessage =
|
|
274
|
-
error instanceof Error ? error.message : String(error);
|
|
275
|
-
logger.error("Migration failed", {
|
|
276
|
-
name: migration.name,
|
|
277
|
-
error: errorMessage,
|
|
278
|
-
});
|
|
279
|
-
result.errors.push(`Migration ${migration.name} failed: ${errorMessage}`);
|
|
280
|
-
// Stop applying further migrations on failure
|
|
281
|
-
break;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (result.driftDetected) {
|
|
287
|
-
logger.warn(
|
|
288
|
-
"Schema drift detected. Some migration files have been modified after being applied."
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
logger.info("Migration runner completed", {
|
|
293
|
-
appliedCount: result.appliedCount,
|
|
294
|
-
driftDetected: result.driftDetected,
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
return result;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Update checksums for all existing migrations.
|
|
302
|
-
* Useful after adding checksum column to update records.
|
|
303
|
-
*
|
|
304
|
-
* @param db - Database instance
|
|
305
|
-
* @param migrationsDir - Path to migrations directory
|
|
306
|
-
* @returns Number of records updated
|
|
307
|
-
*/
|
|
308
|
-
export function updateExistingChecksums(
|
|
309
|
-
db: KotaDatabase,
|
|
310
|
-
migrationsDir: string
|
|
311
|
-
): number {
|
|
312
|
-
const migrations = scanMigrations(migrationsDir);
|
|
313
|
-
const applied = getAppliedMigrations(db);
|
|
314
|
-
let updatedCount = 0;
|
|
315
|
-
|
|
316
|
-
for (const migration of migrations) {
|
|
317
|
-
const record = applied.get(migration.name);
|
|
318
|
-
if (record && !record.checksum) {
|
|
319
|
-
db.run("UPDATE schema_migrations SET checksum = ? WHERE name = ?", [
|
|
320
|
-
migration.checksum,
|
|
321
|
-
migration.name,
|
|
322
|
-
]);
|
|
323
|
-
updatedCount++;
|
|
324
|
-
logger.debug("Updated checksum for migration", { name: migration.name });
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (updatedCount > 0) {
|
|
329
|
-
logger.info("Updated checksums for existing migrations", {
|
|
330
|
-
count: updatedCount,
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return updatedCount;
|
|
335
|
-
}
|