kotadb 2.0.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 (52) hide show
  1. package/README.md +79 -0
  2. package/package.json +75 -0
  3. package/src/api/auto-reindex.ts +55 -0
  4. package/src/api/openapi/builder.ts +209 -0
  5. package/src/api/openapi/paths.ts +354 -0
  6. package/src/api/openapi/schemas.ts +608 -0
  7. package/src/api/queries.ts +1168 -0
  8. package/src/api/routes.ts +339 -0
  9. package/src/auth/middleware.ts +83 -0
  10. package/src/cli.ts +221 -0
  11. package/src/config/constants.ts +96 -0
  12. package/src/config/environment.ts +96 -0
  13. package/src/config/gitignore.ts +68 -0
  14. package/src/config/index.ts +20 -0
  15. package/src/config/project-root.ts +52 -0
  16. package/src/db/client.ts +72 -0
  17. package/src/db/sqlite/index.ts +35 -0
  18. package/src/db/sqlite/jsonl-exporter.ts +416 -0
  19. package/src/db/sqlite/jsonl-importer.ts +361 -0
  20. package/src/db/sqlite/sqlite-client.ts +536 -0
  21. package/src/index.ts +66 -0
  22. package/src/indexer/ast-parser.ts +146 -0
  23. package/src/indexer/ast-types.ts +54 -0
  24. package/src/indexer/circular-detector.ts +262 -0
  25. package/src/indexer/dependency-extractor.ts +352 -0
  26. package/src/indexer/extractors.ts +54 -0
  27. package/src/indexer/import-resolver.ts +167 -0
  28. package/src/indexer/parsers.ts +177 -0
  29. package/src/indexer/reference-extractor.ts +488 -0
  30. package/src/indexer/repos.ts +245 -0
  31. package/src/indexer/storage.ts +277 -0
  32. package/src/indexer/symbol-extractor.ts +660 -0
  33. package/src/instrument.ts +88 -0
  34. package/src/logging/context.ts +46 -0
  35. package/src/logging/logger.ts +193 -0
  36. package/src/logging/middleware.ts +107 -0
  37. package/src/mcp/github-integration.ts +293 -0
  38. package/src/mcp/headers.ts +101 -0
  39. package/src/mcp/impact-analysis.ts +495 -0
  40. package/src/mcp/jsonrpc.ts +141 -0
  41. package/src/mcp/lifecycle.ts +73 -0
  42. package/src/mcp/server.ts +202 -0
  43. package/src/mcp/session.ts +44 -0
  44. package/src/mcp/spec-validation.ts +491 -0
  45. package/src/mcp/tools.ts +889 -0
  46. package/src/sync/deletion-manifest.ts +210 -0
  47. package/src/sync/index.ts +16 -0
  48. package/src/sync/merge-driver.ts +172 -0
  49. package/src/sync/watcher.ts +221 -0
  50. package/src/types/rate-limit.ts +88 -0
  51. package/src/validation/common-schemas.ts +96 -0
  52. package/src/validation/schemas.ts +187 -0
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Sentry SDK Instrumentation
3
+ * IMPORTANT: This file must be imported first in index.ts before all other imports
4
+ * to ensure proper tracing and error capture.
5
+ */
6
+
7
+ import * as Sentry from "@sentry/node";
8
+ import { expressErrorHandler } from "@sentry/node";
9
+
10
+ // Test environment guard: disable Sentry in tests to prevent test errors from polluting dashboard
11
+ if (process.env.NODE_ENV !== "test") {
12
+ // Determine environment from Vercel or Node environment
13
+ const environment = process.env.VERCEL_ENV || process.env.NODE_ENV || "development";
14
+
15
+ // Environment-specific sampling rates
16
+ const isDevelopment = environment === "development";
17
+ const tracesSampleRate = isDevelopment ? 1.0 : 0.1;
18
+
19
+ // Initialize Sentry SDK
20
+ try {
21
+ Sentry.init({
22
+ dsn: process.env.SENTRY_DSN,
23
+ environment,
24
+ tracesSampleRate,
25
+
26
+ // Privacy compliance: don't send IP addresses or user agents automatically
27
+ sendDefaultPii: false,
28
+
29
+ // Enable debug mode in development
30
+ debug: isDevelopment,
31
+
32
+ // Scrub sensitive headers before sending to Sentry
33
+ beforeSend(event, hint) {
34
+ // Remove sensitive headers
35
+ if (event.request?.headers) {
36
+ const headers = event.request.headers;
37
+ if (headers.authorization) headers.authorization = "[REDACTED]";
38
+ if (headers["x-api-key"]) headers["x-api-key"] = "[REDACTED]";
39
+ }
40
+
41
+ // Add request_id from Express request for correlation with structured logs
42
+ const originalException = hint.originalException as Error & { req?: { requestId?: string } };
43
+ if (originalException?.req?.requestId) {
44
+ event.tags = {
45
+ ...event.tags,
46
+ request_id: originalException.req.requestId,
47
+ };
48
+ }
49
+
50
+ return event;
51
+ },
52
+
53
+ // Filter out health check endpoints from transaction tracking
54
+ beforeSendTransaction(event) {
55
+ // Exclude /health endpoint from performance tracking
56
+ if (event.transaction === "GET /health") {
57
+ return null;
58
+ }
59
+ return event;
60
+ },
61
+ });
62
+
63
+ if (isDevelopment && process.env.SENTRY_DSN) {
64
+ process.stdout.write(
65
+ JSON.stringify({
66
+ timestamp: new Date().toISOString(),
67
+ level: "info",
68
+ message: "Sentry SDK initialized",
69
+ environment,
70
+ tracesSampleRate,
71
+ }) + "\n",
72
+ );
73
+ }
74
+ } catch (error) {
75
+ // Log warning but don't crash - observability failures should not cause outages
76
+ process.stderr.write(
77
+ JSON.stringify({
78
+ timestamp: new Date().toISOString(),
79
+ level: "warn",
80
+ message: "Failed to initialize Sentry SDK",
81
+ error: error instanceof Error ? error.message : String(error),
82
+ }) + "\n",
83
+ );
84
+ }
85
+ }
86
+
87
+ // Export Sentry instance and Express error handler for use in other modules
88
+ export { Sentry, expressErrorHandler };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Correlation context utilities for tracking requests across async boundaries
3
+ */
4
+
5
+ import { randomUUID } from "node:crypto";
6
+ import type { LogContext } from "./logger";
7
+
8
+ /**
9
+ * Generate a unique request ID for correlation
10
+ */
11
+ export function generateRequestId(): string {
12
+ return randomUUID();
13
+ }
14
+
15
+ /**
16
+ * Generate a unique job ID for queue job correlation
17
+ */
18
+ export function generateJobId(): string {
19
+ return randomUUID();
20
+ }
21
+
22
+ /**
23
+ * Create a correlation context from request metadata
24
+ */
25
+ export function createRequestContext(requestId: string, userId?: string, keyId?: string): LogContext {
26
+ const context: LogContext = { request_id: requestId };
27
+ if (userId) context.user_id = userId;
28
+ if (keyId) context.key_id = keyId;
29
+ return context;
30
+ }
31
+
32
+ /**
33
+ * Create a correlation context for queue jobs
34
+ */
35
+ export function createJobContext(jobId: string, userId?: string): LogContext {
36
+ const context: LogContext = { job_id: jobId };
37
+ if (userId) context.user_id = userId;
38
+ return context;
39
+ }
40
+
41
+ /**
42
+ * Extend existing context with additional fields
43
+ */
44
+ export function extendContext(base: LogContext, additional: LogContext): LogContext {
45
+ return { ...base, ...additional };
46
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Structured logging with JSON format, correlation IDs, and sensitive data masking
3
+ */
4
+
5
+ export type LogLevel = "debug" | "info" | "warn" | "error";
6
+
7
+ export interface LogContext {
8
+ request_id?: string;
9
+ user_id?: string;
10
+ key_id?: string;
11
+ job_id?: string;
12
+ [key: string]: unknown;
13
+ }
14
+
15
+ export interface LogEntry {
16
+ timestamp: string;
17
+ level: LogLevel;
18
+ message: string;
19
+ context?: LogContext;
20
+ error?: {
21
+ message: string;
22
+ stack?: string;
23
+ code?: string;
24
+ };
25
+ }
26
+
27
+ export interface Logger {
28
+ debug(message: string, context?: LogContext): void;
29
+ info(message: string, context?: LogContext): void;
30
+ warn(message: string, context?: LogContext): void;
31
+ error(message: string, errorOrContext?: Error | LogContext, context?: LogContext): void;
32
+ child(childContext: LogContext): Logger;
33
+ }
34
+
35
+ // Sensitive keys that should be masked in logs
36
+ const SENSITIVE_KEYS = [
37
+ "apiKey",
38
+ "api_key",
39
+ "apikey",
40
+ "token",
41
+ "password",
42
+ "secret",
43
+ "authorization",
44
+ "bearer",
45
+ "key",
46
+ "private_key",
47
+ "privateKey",
48
+ "client_secret",
49
+ "clientSecret",
50
+ "access_token",
51
+ "accessToken",
52
+ "refresh_token",
53
+ "refreshToken",
54
+ "session",
55
+ "cookie",
56
+ ];
57
+
58
+ const LOG_LEVELS: Record<LogLevel, number> = {
59
+ debug: 0,
60
+ info: 1,
61
+ warn: 2,
62
+ error: 3,
63
+ };
64
+
65
+ /**
66
+ * Get configured log level from environment (default: info)
67
+ */
68
+ function getLogLevel(): LogLevel {
69
+ const level = process.env.LOG_LEVEL?.toLowerCase();
70
+ if (level === "debug" || level === "info" || level === "warn" || level === "error") {
71
+ return level;
72
+ }
73
+ return "info";
74
+ }
75
+
76
+ /**
77
+ * Check if a log level should be output based on configured level
78
+ */
79
+ function shouldLog(level: LogLevel): boolean {
80
+ const configuredLevel = getLogLevel();
81
+ return LOG_LEVELS[level] >= LOG_LEVELS[configuredLevel];
82
+ }
83
+
84
+ /**
85
+ * Mask sensitive data in context objects
86
+ */
87
+ function maskSensitiveData(context: LogContext): LogContext {
88
+ const masked: LogContext = {};
89
+ for (const [key, value] of Object.entries(context)) {
90
+ if (SENSITIVE_KEYS.some((sensitiveKey) => key.toLowerCase().includes(sensitiveKey))) {
91
+ masked[key] = "[REDACTED]";
92
+ } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
93
+ masked[key] = maskSensitiveData(value as LogContext);
94
+ } else {
95
+ masked[key] = value;
96
+ }
97
+ }
98
+ return masked;
99
+ }
100
+
101
+ /**
102
+ * Format and write log entry to stdout (info/debug/warn) or stderr (error)
103
+ */
104
+ function writeLog(entry: LogEntry): void {
105
+ const json = JSON.stringify(entry);
106
+ const output = `${json}\n`;
107
+
108
+ if (entry.level === "error") {
109
+ process.stderr.write(output);
110
+ } else {
111
+ process.stdout.write(output);
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Create a logger instance with optional correlation context
117
+ */
118
+ export function createLogger(baseContext?: LogContext): Logger {
119
+ const context = baseContext ? maskSensitiveData(baseContext) : {};
120
+
121
+ return {
122
+ debug(message: string, additionalContext?: LogContext): void {
123
+ if (!shouldLog("debug")) return;
124
+
125
+ const entry: LogEntry = {
126
+ timestamp: new Date().toISOString(),
127
+ level: "debug",
128
+ message,
129
+ context: additionalContext ? { ...context, ...maskSensitiveData(additionalContext) } : Object.keys(context).length > 0 ? context : undefined,
130
+ };
131
+ writeLog(entry);
132
+ },
133
+
134
+ info(message: string, additionalContext?: LogContext): void {
135
+ if (!shouldLog("info")) return;
136
+
137
+ const entry: LogEntry = {
138
+ timestamp: new Date().toISOString(),
139
+ level: "info",
140
+ message,
141
+ context: additionalContext ? { ...context, ...maskSensitiveData(additionalContext) } : Object.keys(context).length > 0 ? context : undefined,
142
+ };
143
+ writeLog(entry);
144
+ },
145
+
146
+ warn(message: string, additionalContext?: LogContext): void {
147
+ if (!shouldLog("warn")) return;
148
+
149
+ const entry: LogEntry = {
150
+ timestamp: new Date().toISOString(),
151
+ level: "warn",
152
+ message,
153
+ context: additionalContext ? { ...context, ...maskSensitiveData(additionalContext) } : Object.keys(context).length > 0 ? context : undefined,
154
+ };
155
+ writeLog(entry);
156
+ },
157
+
158
+ error(message: string, errorOrContext?: Error | LogContext, additionalContext?: LogContext): void {
159
+ if (!shouldLog("error")) return;
160
+
161
+ const entry: LogEntry = {
162
+ timestamp: new Date().toISOString(),
163
+ level: "error",
164
+ message,
165
+ };
166
+
167
+ // Handle error as second parameter
168
+ if (errorOrContext instanceof Error) {
169
+ entry.error = {
170
+ message: errorOrContext.message,
171
+ stack: errorOrContext.stack,
172
+ code: (errorOrContext as Error & { code?: string }).code,
173
+ };
174
+ if (additionalContext) {
175
+ entry.context = { ...context, ...maskSensitiveData(additionalContext) };
176
+ } else if (Object.keys(context).length > 0) {
177
+ entry.context = context;
178
+ }
179
+ } else if (errorOrContext) {
180
+ // Handle context as second parameter
181
+ entry.context = { ...context, ...maskSensitiveData(errorOrContext) };
182
+ } else if (Object.keys(context).length > 0) {
183
+ entry.context = context;
184
+ }
185
+
186
+ writeLog(entry);
187
+ },
188
+
189
+ child(childContext: LogContext): Logger {
190
+ return createLogger({ ...context, ...childContext });
191
+ },
192
+ };
193
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Express middleware for request/response logging with correlation IDs
3
+ */
4
+
5
+ import type { Request, Response, NextFunction } from "express";
6
+ import { createLogger } from "./logger";
7
+ import { generateRequestId, createRequestContext } from "./context";
8
+ import type { Logger } from "./logger";
9
+
10
+ // Extend Express Request to include logger
11
+ declare global {
12
+ namespace Express {
13
+ interface Request {
14
+ logger: Logger;
15
+ requestId: string;
16
+ }
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Express middleware that:
22
+ * - Generates unique request_id for each request
23
+ * - Attaches logger instance to req.logger with correlation context
24
+ * - Logs incoming requests and outgoing responses
25
+ * - Captures errors with stack traces
26
+ */
27
+ export function requestLoggingMiddleware(req: Request, res: Response, next: NextFunction): void {
28
+ const startTime = Date.now();
29
+ const requestId = generateRequestId();
30
+
31
+ // Attach request ID to request object
32
+ req.requestId = requestId;
33
+
34
+ // Create base logger with request_id
35
+ const baseContext = createRequestContext(requestId);
36
+ req.logger = createLogger(baseContext);
37
+
38
+ // Log incoming request
39
+ req.logger.info("Incoming request", {
40
+ method: req.method,
41
+ url: req.url,
42
+ path: req.path,
43
+ ip: req.ip,
44
+ userAgent: req.get("user-agent"),
45
+ });
46
+
47
+ // Capture response finish event for logging
48
+ const originalSend = res.send;
49
+ res.send = function (body) {
50
+ // Log response
51
+ const duration = Date.now() - startTime;
52
+ const level = res.statusCode >= 400 ? "warn" : "info";
53
+
54
+ if (level === "warn") {
55
+ req.logger.warn("Request completed with error", {
56
+ method: req.method,
57
+ url: req.url,
58
+ status: res.statusCode,
59
+ duration_ms: duration,
60
+ });
61
+ } else {
62
+ req.logger.info("Request completed", {
63
+ method: req.method,
64
+ url: req.url,
65
+ status: res.statusCode,
66
+ duration_ms: duration,
67
+ });
68
+ }
69
+
70
+ return originalSend.call(this, body);
71
+ };
72
+
73
+ // Capture errors
74
+ const originalNext = next;
75
+ next = function (error?: unknown) {
76
+ if (error instanceof Error) {
77
+ req.logger.error("Request error", error);
78
+ }
79
+ return originalNext(error);
80
+ };
81
+
82
+ next();
83
+ }
84
+
85
+ /**
86
+ * Error handling middleware to log unhandled errors
87
+ */
88
+ export function errorLoggingMiddleware(error: Error, req: Request, res: Response, next: NextFunction): void {
89
+ // Log error with full stack trace
90
+ if (req.logger) {
91
+ req.logger.error("Unhandled error", error, {
92
+ method: req.method,
93
+ url: req.url,
94
+ path: req.path,
95
+ });
96
+ } else {
97
+ // Fallback if logger not attached
98
+ const logger = createLogger({ request_id: req.requestId || "unknown" });
99
+ logger.error("Unhandled error (no logger attached)", error, {
100
+ method: req.method,
101
+ url: req.url,
102
+ path: req.path,
103
+ });
104
+ }
105
+
106
+ next(error);
107
+ }