mgcheck 0.1.0 → 0.1.1
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/dist/{activity-log-ETNHCZ7B.js → activity-log-IH2PG5UL.js} +4 -2
- package/dist/{chunk-3G3R2NM3.js → chunk-KTVSSEHE.js} +9 -2
- package/dist/chunk-KTVSSEHE.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/{installer-KGWDJ6OR.js → installer-IYFL6ZGQ.js} +3 -3
- package/dist/{installer-KGWDJ6OR.js.map → installer-IYFL6ZGQ.js.map} +1 -1
- package/dist/mcp/server.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-3G3R2NM3.js.map +0 -1
- /package/dist/{activity-log-ETNHCZ7B.js.map → activity-log-IH2PG5UL.js.map} +0 -0
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LOG_FILE,
|
|
3
|
+
clearActivityLog,
|
|
3
4
|
formatActivityEntry,
|
|
4
5
|
logActivity,
|
|
5
6
|
watchActivityLog
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-KTVSSEHE.js";
|
|
7
8
|
export {
|
|
8
9
|
LOG_FILE,
|
|
10
|
+
clearActivityLog,
|
|
9
11
|
formatActivityEntry,
|
|
10
12
|
logActivity,
|
|
11
13
|
watchActivityLog
|
|
12
14
|
};
|
|
13
|
-
//# sourceMappingURL=activity-log-
|
|
15
|
+
//# sourceMappingURL=activity-log-IH2PG5UL.js.map
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
// src/core/activity-log.ts
|
|
2
|
-
import { existsSync, mkdirSync, appendFileSync, readFileSync, statSync } from "fs";
|
|
2
|
+
import { existsSync, mkdirSync, appendFileSync, writeFileSync, readFileSync, statSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import os from "os";
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
var LOG_DIR = join(os.homedir(), ".mgcheck");
|
|
7
7
|
var LOG_FILE = join(LOG_DIR, "activity.log");
|
|
8
|
+
function clearActivityLog() {
|
|
9
|
+
if (!existsSync(LOG_DIR)) {
|
|
10
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
writeFileSync(LOG_FILE, "", "utf-8");
|
|
13
|
+
}
|
|
8
14
|
function logActivity(entry) {
|
|
9
15
|
if (!existsSync(LOG_DIR)) {
|
|
10
16
|
mkdirSync(LOG_DIR, { recursive: true });
|
|
@@ -66,8 +72,9 @@ function formatActivityEntry(entry) {
|
|
|
66
72
|
|
|
67
73
|
export {
|
|
68
74
|
LOG_FILE,
|
|
75
|
+
clearActivityLog,
|
|
69
76
|
logActivity,
|
|
70
77
|
watchActivityLog,
|
|
71
78
|
formatActivityEntry
|
|
72
79
|
};
|
|
73
|
-
//# sourceMappingURL=chunk-
|
|
80
|
+
//# sourceMappingURL=chunk-KTVSSEHE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/activity-log.ts"],"sourcesContent":["import { existsSync, mkdirSync, appendFileSync, writeFileSync, readFileSync, statSync, watchFile, unwatchFile } from 'node:fs';\nimport { join } from 'node:path';\nimport os from 'node:os';\nimport chalk from 'chalk';\n\nconst LOG_DIR = join(os.homedir(), '.mgcheck');\nconst LOG_FILE = join(LOG_DIR, 'activity.log');\n\nexport interface ActivityEntry {\n timestamp: string;\n tool: string;\n summary: string;\n violations: number;\n errors: number;\n warnings: number;\n passed: boolean;\n statements: string[];\n}\n\n/**\n * Clear the activity log file, ensuring directory exists.\n */\nexport function clearActivityLog(): void {\n if (!existsSync(LOG_DIR)) {\n mkdirSync(LOG_DIR, { recursive: true });\n }\n writeFileSync(LOG_FILE, '', 'utf-8');\n}\n\n/**\n * Append an activity entry to the shared log file.\n * Called by the MCP server when a tool is invoked.\n */\nexport function logActivity(entry: ActivityEntry): void {\n if (!existsSync(LOG_DIR)) {\n mkdirSync(LOG_DIR, { recursive: true });\n }\n const line = JSON.stringify(entry) + '\\n';\n appendFileSync(LOG_FILE, line, 'utf-8');\n}\n\n/**\n * Watch the activity log and call the handler for each new entry.\n * Returns a cleanup function to stop watching.\n */\nexport function watchActivityLog(handler: (entry: ActivityEntry) => void): () => void {\n if (!existsSync(LOG_DIR)) {\n mkdirSync(LOG_DIR, { recursive: true });\n }\n\n // Create log file if it doesn't exist\n if (!existsSync(LOG_FILE)) {\n appendFileSync(LOG_FILE, '', 'utf-8');\n }\n\n // Track current file size to only read new content\n let lastSize = statSync(LOG_FILE).size;\n\n const interval = setInterval(() => {\n try {\n const currentSize = statSync(LOG_FILE).size;\n if (currentSize > lastSize) {\n // Read only the new bytes\n const fd = readFileSync(LOG_FILE, 'utf-8');\n const newContent = fd.substring(lastSize);\n lastSize = currentSize;\n\n const lines = newContent.split('\\n').filter((l) => l.trim());\n for (const line of lines) {\n try {\n const entry: ActivityEntry = JSON.parse(line);\n handler(entry);\n } catch {\n // Skip malformed lines\n }\n }\n }\n } catch {\n // File may be temporarily unavailable\n }\n }, 500);\n\n return () => {\n clearInterval(interval);\n };\n}\n\n/**\n * Format an activity entry for terminal display.\n */\nexport function formatActivityEntry(entry: ActivityEntry): string {\n const time = chalk.dim(`[${entry.timestamp}]`);\n const tool = chalk.cyan(entry.tool);\n\n const lines: string[] = [];\n lines.push(`${time} ${tool} called`);\n\n // Show each statement\n if (entry.statements.length > 0) {\n for (const stmt of entry.statements) {\n const truncated = stmt.length > 80 ? stmt.substring(0, 77) + '...' : stmt;\n lines.push(chalk.dim(' │ ') + chalk.white(truncated));\n }\n }\n\n // Show result\n if (entry.passed) {\n lines.push(chalk.dim(' └─ ') + chalk.green('✅ PASSED') + chalk.dim(` (${entry.violations} violations)`));\n } else {\n lines.push(\n chalk.dim(' └─ ') +\n chalk.red('❌ BLOCKED') +\n chalk.dim(` (${entry.errors} error${entry.errors !== 1 ? 's' : ''}, ${entry.warnings} warning${entry.warnings !== 1 ? 's' : ''})`)\n );\n }\n\n return lines.join('\\n');\n}\n\nexport { LOG_FILE };\n"],"mappings":";AAAA,SAAS,YAAY,WAAW,gBAAgB,eAAe,cAAc,gBAAwC;AACrH,SAAS,YAAY;AACrB,OAAO,QAAQ;AACf,OAAO,WAAW;AAElB,IAAM,UAAU,KAAK,GAAG,QAAQ,GAAG,UAAU;AAC7C,IAAM,WAAW,KAAK,SAAS,cAAc;AAgBtC,SAAS,mBAAyB;AACvC,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,cAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,EACxC;AACA,gBAAc,UAAU,IAAI,OAAO;AACrC;AAMO,SAAS,YAAY,OAA4B;AACtD,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,cAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,EACxC;AACA,QAAM,OAAO,KAAK,UAAU,KAAK,IAAI;AACrC,iBAAe,UAAU,MAAM,OAAO;AACxC;AAMO,SAAS,iBAAiB,SAAqD;AACpF,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,cAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,EACxC;AAGA,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,mBAAe,UAAU,IAAI,OAAO;AAAA,EACtC;AAGA,MAAI,WAAW,SAAS,QAAQ,EAAE;AAElC,QAAM,WAAW,YAAY,MAAM;AACjC,QAAI;AACF,YAAM,cAAc,SAAS,QAAQ,EAAE;AACvC,UAAI,cAAc,UAAU;AAE1B,cAAM,KAAK,aAAa,UAAU,OAAO;AACzC,cAAM,aAAa,GAAG,UAAU,QAAQ;AACxC,mBAAW;AAEX,cAAM,QAAQ,WAAW,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;AAC3D,mBAAW,QAAQ,OAAO;AACxB,cAAI;AACF,kBAAM,QAAuB,KAAK,MAAM,IAAI;AAC5C,oBAAQ,KAAK;AAAA,UACf,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF,GAAG,GAAG;AAEN,SAAO,MAAM;AACX,kBAAc,QAAQ;AAAA,EACxB;AACF;AAKO,SAAS,oBAAoB,OAA8B;AAChE,QAAM,OAAO,MAAM,IAAI,IAAI,MAAM,SAAS,GAAG;AAC7C,QAAM,OAAO,MAAM,KAAK,MAAM,IAAI;AAElC,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,GAAG,IAAI,IAAI,IAAI,SAAS;AAGnC,MAAI,MAAM,WAAW,SAAS,GAAG;AAC/B,eAAW,QAAQ,MAAM,YAAY;AACnC,YAAM,YAAY,KAAK,SAAS,KAAK,KAAK,UAAU,GAAG,EAAE,IAAI,QAAQ;AACrE,YAAM,KAAK,MAAM,IAAI,WAAM,IAAI,MAAM,MAAM,SAAS,CAAC;AAAA,IACvD;AAAA,EACF;AAGA,MAAI,MAAM,QAAQ;AAChB,UAAM,KAAK,MAAM,IAAI,iBAAO,IAAI,MAAM,MAAM,eAAU,IAAI,MAAM,IAAI,KAAK,MAAM,UAAU,cAAc,CAAC;AAAA,EAC1G,OAAO;AACL,UAAM;AAAA,MACJ,MAAM,IAAI,iBAAO,IACf,MAAM,IAAI,gBAAW,IACrB,MAAM,IAAI,KAAK,MAAM,MAAM,SAAS,MAAM,WAAW,IAAI,MAAM,EAAE,KAAK,MAAM,QAAQ,WAAW,MAAM,aAAa,IAAI,MAAM,EAAE,GAAG;AAAA,IACrI;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -71,7 +71,7 @@ program.command("mcp").description("Start the MCP server for AI agent integratio
|
|
|
71
71
|
await import("./mcp/server.js");
|
|
72
72
|
});
|
|
73
73
|
program.command("setup").alias("init").alias("install-mcp").description("Interactive wizard to connect Migration Guardian to Antigravity, Cursor, and Claude").option("-y, --yes", "Skip confirmation prompt and connect automatically", false).option("--client <client>", "Target client: antigravity | claude | cursor | all", "all").action(async (options) => {
|
|
74
|
-
const { runInteractiveWizard } = await import("./installer-
|
|
74
|
+
const { runInteractiveWizard } = await import("./installer-IYFL6ZGQ.js");
|
|
75
75
|
await runInteractiveWizard({ yes: options.yes, client: options.client });
|
|
76
76
|
});
|
|
77
77
|
function buildConfig(options) {
|
|
@@ -91,7 +91,7 @@ function buildConfig(options) {
|
|
|
91
91
|
return loadConfig(overrides, options.config);
|
|
92
92
|
}
|
|
93
93
|
if (process.argv.slice(2).length === 0) {
|
|
94
|
-
const { runInteractiveWizard } = await import("./installer-
|
|
94
|
+
const { runInteractiveWizard } = await import("./installer-IYFL6ZGQ.js");
|
|
95
95
|
await runInteractiveWizard();
|
|
96
96
|
} else {
|
|
97
97
|
program.parse();
|
|
@@ -298,8 +298,8 @@ async function runInteractiveWizard(options = {}) {
|
|
|
298
298
|
console.log(chalk.dim(" Press Ctrl+C to stop.\n"));
|
|
299
299
|
console.log(chalk.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
300
300
|
console.log("");
|
|
301
|
-
const { watchActivityLog, formatActivityEntry,
|
|
302
|
-
|
|
301
|
+
const { watchActivityLog, formatActivityEntry, clearActivityLog } = await import("./activity-log-IH2PG5UL.js");
|
|
302
|
+
clearActivityLog();
|
|
303
303
|
const stopWatching = watchActivityLog((entry) => {
|
|
304
304
|
console.log(formatActivityEntry(entry));
|
|
305
305
|
console.log("");
|
|
@@ -320,4 +320,4 @@ export {
|
|
|
320
320
|
setupMcp,
|
|
321
321
|
verifyConnections
|
|
322
322
|
};
|
|
323
|
-
//# sourceMappingURL=installer-
|
|
323
|
+
//# sourceMappingURL=installer-IYFL6ZGQ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/installer/index.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { resolve, join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport * as readline from 'node:readline/promises';\nimport { stdin as input, stdout as output } from 'node:process';\nimport os from 'node:os';\nimport chalk from 'chalk';\n\nexport interface SetupOptions {\n client?: 'antigravity' | 'claude' | 'cursor' | 'all';\n yes?: boolean;\n}\n\nexport interface DetectResult {\n antigravity: boolean;\n cursor: boolean;\n claude: boolean;\n hasAny: boolean;\n}\n\n/**\n * Detect which AI editors/clients are installed on the user's system or workspace.\n */\nexport function detectClients(): DetectResult {\n // 1. Antigravity check\n const geminiDir = join(os.homedir(), '.gemini');\n const antigravity = existsSync(geminiDir);\n\n // 2. Cursor check (local .cursor or system Cursor install)\n const localCursor = existsSync(join(process.cwd(), '.cursor'));\n let systemCursor = false;\n if (process.platform === 'win32') {\n systemCursor = existsSync(join(process.env.APPDATA || '', 'Cursor')) ||\n existsSync(join(process.env.LOCALAPPDATA || '', 'Programs', 'cursor'));\n } else if (process.platform === 'darwin') {\n systemCursor = existsSync('/Applications/Cursor.app') ||\n existsSync(join(os.homedir(), 'Applications', 'Cursor.app'));\n } else {\n systemCursor = existsSync(join(os.homedir(), '.config', 'Cursor'));\n }\n const cursor = localCursor || systemCursor;\n\n // 3. Claude Desktop check\n let claudeDir = '';\n if (process.platform === 'win32') {\n claudeDir = join(process.env.APPDATA || '', 'Claude');\n } else if (process.platform === 'darwin') {\n claudeDir = join(os.homedir(), 'Library', 'Application Support', 'Claude');\n } else {\n claudeDir = join(os.homedir(), '.config', 'Claude');\n }\n const claude = existsSync(claudeDir);\n\n return {\n antigravity,\n cursor,\n claude,\n hasAny: antigravity || cursor || claude,\n };\n}\n\n/**\n * Apply the MCP server configuration to the specified clients.\n */\nexport function setupMcp(options: SetupOptions = {}): string[] {\n const targetClient = options.client || 'all';\n const currentDir = dirname(fileURLToPath(import.meta.url));\n const serverPath = resolve(currentDir, './mcp/server.js');\n const fallbackPath = resolve(currentDir, '../dist/mcp/server.js');\n const finalServerPath = existsSync(serverPath) ? serverPath : fallbackPath;\n const results: string[] = [];\n\n // 1. Antigravity Configuration (~/.gemini/config/mcp_config.json)\n if (targetClient === 'all' || targetClient === 'antigravity') {\n try {\n const geminiConfigDir = join(os.homedir(), '.gemini', 'config');\n const mcpConfigFile = join(geminiConfigDir, 'mcp_config.json');\n\n if (!existsSync(geminiConfigDir)) {\n mkdirSync(geminiConfigDir, { recursive: true });\n }\n\n let config: any = { mcpServers: {} };\n if (existsSync(mcpConfigFile)) {\n try {\n config = JSON.parse(readFileSync(mcpConfigFile, 'utf-8'));\n if (!config.mcpServers) config.mcpServers = {};\n } catch {\n config = { mcpServers: {} };\n }\n }\n\n config.mcpServers['migration-guardian'] = {\n command: 'node',\n args: [finalServerPath],\n };\n\n writeFileSync(mcpConfigFile, JSON.stringify(config, null, 2), 'utf-8');\n results.push(chalk.green(' ✅ Google Antigravity') + chalk.dim(` (${mcpConfigFile})`));\n } catch (err) {\n results.push(chalk.yellow(' ⚠️ Google Antigravity: ') + (err as Error).message);\n }\n }\n\n // 2. Cursor Configuration (.cursor/mcp.json in workspace)\n if (targetClient === 'all' || targetClient === 'cursor') {\n try {\n const cursorDir = join(process.cwd(), '.cursor');\n const cursorFile = join(cursorDir, 'mcp.json');\n\n if (!existsSync(cursorDir)) {\n mkdirSync(cursorDir, { recursive: true });\n }\n\n let config: any = { mcpServers: {} };\n if (existsSync(cursorFile)) {\n try {\n config = JSON.parse(readFileSync(cursorFile, 'utf-8'));\n if (!config.mcpServers) config.mcpServers = {};\n } catch {\n config = { mcpServers: {} };\n }\n }\n\n config.mcpServers['migration-guardian'] = {\n command: 'node',\n args: [finalServerPath],\n };\n\n writeFileSync(cursorFile, JSON.stringify(config, null, 2), 'utf-8');\n results.push(chalk.green(' ✅ Cursor IDE') + chalk.dim(` (${cursorFile})`));\n } catch (err) {\n results.push(chalk.yellow(' ⚠️ Cursor IDE: ') + (err as Error).message);\n }\n }\n\n // 3. Claude Desktop Configuration\n if (targetClient === 'all' || targetClient === 'claude') {\n try {\n let claudeDir = '';\n if (process.platform === 'win32') {\n claudeDir = join(process.env.APPDATA || '', 'Claude');\n } else if (process.platform === 'darwin') {\n claudeDir = join(os.homedir(), 'Library', 'Application Support', 'Claude');\n } else {\n claudeDir = join(os.homedir(), '.config', 'Claude');\n }\n\n const claudeConfigFile = join(claudeDir, 'claude_desktop_config.json');\n\n if (existsSync(claudeDir)) {\n let config: any = { mcpServers: {} };\n if (existsSync(claudeConfigFile)) {\n try {\n config = JSON.parse(readFileSync(claudeConfigFile, 'utf-8'));\n if (!config.mcpServers) config.mcpServers = {};\n } catch {\n config = { mcpServers: {} };\n }\n }\n\n config.mcpServers['migration-guardian'] = {\n command: 'node',\n args: [finalServerPath],\n };\n\n writeFileSync(claudeConfigFile, JSON.stringify(config, null, 2), 'utf-8');\n } else if (targetClient === 'claude') {\n results.push(chalk.dim(' ℹ️ Claude Desktop not installed (skipped)'));\n }\n } catch (err) {\n results.push(chalk.yellow(' ⚠️ Claude Desktop: ') + (err as Error).message);\n }\n }\n\n // 4. Inject Agent Rule Files into current workspace\n const ruleResults = injectAgentRules(process.cwd());\n results.push(...ruleResults);\n\n return results;\n}\n\nconst GUARDIAN_RULE_BANNER = '<!-- MIGRATION GUARDIAN SAFETY RULES -->';\nconst GUARDIAN_RULE_CONTENT = `${GUARDIAN_RULE_BANNER}\n## Database Migration Safety (Enforced by Migration Guardian)\n\nWhen creating, modifying, or executing database migrations (SQL, Prisma, Drizzle, TypeORM, Knex, etc.):\n1. **NEVER execute raw DDL or database migrations directly** on local, staging, or production databases without prior safety checks.\n2. **ALWAYS use Migration Guardian (\\`mgcheck\\`)**:\n - If MCP tools are available, invoke \\\\\\`check_migration\\\\\\` (or \\\\\\`analyze_migration\\\\\\` for static analysis).\n - Alternatively, run \\\\\\`npx mgcheck analyze <path>\\\\\\` or \\\\\\`npx mgcheck run <path> --provider pglite\\\\\\` in terminal.\n3. **Follow Zero-Downtime Migration Standards**:\n - \\\\\\`MG001\\\\\\`: Always use \\\\\\`CREATE INDEX CONCURRENTLY\\\\\\` instead of blocking indexes.\n - \\\\\\`MG002\\\\\\`: Never use \\\\\\`ALTER COLUMN TYPE\\\\\\` directly (use expand/contract pattern).\n - \\\\\\`MG003\\\\\\`: Always provide a \\\\\\`DEFAULT\\\\\\` value when adding \\\\\\`NOT NULL\\\\\\` columns.\n - \\\\\\`MG004\\\\\\`: Add constraints with \\\\\\`NOT VALID\\\\\\`, then validate them in a subsequent step.\n - \\\\\\`MG005\\\\\\` / \\\\\\`MG006\\\\\\`: Never drop columns or tables without explicit user confirmation.\n - \\\\\\`MG009\\\\\\`: Always set \\\\\\`SET lock_timeout = '5s';\\\\\\` before risky DDL.\n - \\\\\\`MG010\\\\\\`: Provide a rollback / down migration file whenever applicable.\n`;\n\n/**\n * Append or create AI agent rule files (.cursorrules, CLAUDE.md, AGENTS.md, etc.)\n */\nfunction appendOrWriteRule(filePath: string, name: string): string | null {\n try {\n const parentDir = dirname(filePath);\n if (!existsSync(parentDir)) {\n mkdirSync(parentDir, { recursive: true });\n }\n\n if (existsSync(filePath)) {\n const existing = readFileSync(filePath, 'utf-8');\n if (existing.includes(GUARDIAN_RULE_BANNER) || existing.includes('Migration Guardian')) {\n return null; // Already present\n }\n writeFileSync(filePath, `${existing.trimEnd()}\\n\\n${GUARDIAN_RULE_CONTENT}`, 'utf-8');\n return chalk.green(` ✅ Updated ${name}`) + chalk.dim(` (${filePath})`);\n } else {\n writeFileSync(filePath, GUARDIAN_RULE_CONTENT, 'utf-8');\n return chalk.green(` ✅ Created ${name}`) + chalk.dim(` (${filePath})`);\n }\n } catch (err) {\n return chalk.yellow(` ⚠️ ${name}: `) + (err as Error).message;\n }\n}\n\n/**\n * Injects Migration Guardian safety rules into AI rule files across popular AI IDEs and agents.\n */\nexport function injectAgentRules(workspaceDir: string = process.cwd()): string[] {\n const results: string[] = [];\n\n // 1. Cursor IDE Rules (.cursor/rules/migration-guardian.mdc and .cursorrules)\n const cursorRuleFile = join(workspaceDir, '.cursor', 'rules', 'migration-guardian.mdc');\n const resCursorMdc = appendOrWriteRule(cursorRuleFile, 'Cursor Rule (.cursor/rules)');\n if (resCursorMdc) results.push(resCursorMdc);\n\n const cursorRulesLegacy = join(workspaceDir, '.cursorrules');\n const resCursorLegacy = appendOrWriteRule(cursorRulesLegacy, 'Cursor Rules (.cursorrules)');\n if (resCursorLegacy) results.push(resCursorLegacy);\n\n // 2. Claude Code / Claude Desktop Rules (CLAUDE.md)\n const claudeMd = join(workspaceDir, 'CLAUDE.md');\n const resClaude = appendOrWriteRule(claudeMd, 'Claude Rules (CLAUDE.md)');\n if (resClaude) results.push(resClaude);\n\n // 3. Antigravity & General Agent Rules (AGENTS.md)\n const agentsMd = join(workspaceDir, 'AGENTS.md');\n const resAgents = appendOrWriteRule(agentsMd, 'Agent Rules (AGENTS.md)');\n if (resAgents) results.push(resAgents);\n\n // 4. Windsurf Rules (.windsurfrules)\n const windsurfRules = join(workspaceDir, '.windsurfrules');\n const resWindsurf = appendOrWriteRule(windsurfRules, 'Windsurf Rules (.windsurfrules)');\n if (resWindsurf) results.push(resWindsurf);\n\n // 5. GitHub Copilot Instructions (.github/copilot-instructions.md)\n const copilotInstructions = join(workspaceDir, '.github', 'copilot-instructions.md');\n const resCopilot = appendOrWriteRule(copilotInstructions, 'GitHub Copilot Instructions');\n if (resCopilot) results.push(resCopilot);\n\n return results;\n}\n\n/**\n * Verify that migration-guardian is present in each AI client's MCP config.\n * Returns human-readable status lines.\n */\nexport function verifyConnections(): string[] {\n const lines: string[] = [];\n\n // 1. Antigravity\n const antigravityConfig = join(os.homedir(), '.gemini', 'config', 'mcp_config.json');\n if (existsSync(antigravityConfig)) {\n try {\n const cfg = JSON.parse(readFileSync(antigravityConfig, 'utf-8'));\n if (cfg?.mcpServers?.['migration-guardian']) {\n lines.push(chalk.green(' ✅ Connected to Google Antigravity'));\n }\n } catch { /* skip */ }\n }\n\n // 2. Cursor\n const cursorConfig = join(process.cwd(), '.cursor', 'mcp.json');\n if (existsSync(cursorConfig)) {\n try {\n const cfg = JSON.parse(readFileSync(cursorConfig, 'utf-8'));\n if (cfg?.mcpServers?.['migration-guardian']) {\n lines.push(chalk.green(' ✅ Connected to Cursor IDE'));\n }\n } catch { /* skip */ }\n }\n\n // 3. Claude Desktop\n let claudeConfig = '';\n if (process.platform === 'win32') {\n claudeConfig = join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json');\n } else if (process.platform === 'darwin') {\n claudeConfig = join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');\n } else {\n claudeConfig = join(os.homedir(), '.config', 'Claude', 'claude_desktop_config.json');\n }\n if (existsSync(claudeConfig)) {\n try {\n const cfg = JSON.parse(readFileSync(claudeConfig, 'utf-8'));\n if (cfg?.mcpServers?.['migration-guardian']) {\n lines.push(chalk.green(' ✅ Connected to Claude Desktop'));\n }\n } catch { /* skip */ }\n }\n\n return lines;\n}\n\n/**\n * Interactive Wizard: Auto-detects clients, prompts the user, and connects.\n */\nexport async function runInteractiveWizard(options: SetupOptions = {}) {\n console.log('');\n console.log(chalk.bold.cyan(' 🛡️ Migration Guardian') + chalk.dim(' (mgcheck) — Database Migration Safety'));\n console.log(chalk.dim(' Catch unsafe database migrations before they hit production.\\n'));\n\n const detected = detectClients();\n const detectedList: string[] = [];\n\n if (detected.antigravity) detectedList.push('Google Antigravity');\n if (detected.cursor) detectedList.push('Cursor IDE');\n if (detected.claude) detectedList.push('Claude Desktop');\n\n if (detectedList.length > 0) {\n console.log(chalk.bold(' Detected AI Clients on your system:'));\n for (const client of detectedList) {\n console.log(` ${chalk.cyan('•')} ${chalk.white(client)}`);\n }\n console.log('');\n } else {\n console.log(chalk.dim(' Configuring MCP integration for your AI editors (Antigravity, Cursor, Claude)...\\n'));\n }\n\n // Check if --yes was passed to bypass the prompt\n let shouldProceed = options.yes ?? false;\n\n if (!shouldProceed) {\n const promptText = detectedList.length > 0\n ? ` Connect Migration Guardian to ${detectedList.join(' & ')}? ${chalk.dim('[Y/n]')} `\n : ` Connect Migration Guardian to your AI agents? ${chalk.dim('[Y/n]')} `;\n\n const rl = readline.createInterface({ input, output });\n\n try {\n const answer = await rl.question(promptText);\n const trimmed = answer.trim().toLowerCase();\n shouldProceed = trimmed === '' || trimmed === 'y' || trimmed === 'yes';\n } finally {\n rl.close();\n }\n }\n\n if (!shouldProceed) {\n console.log(chalk.dim('\\n Setup skipped.'));\n console.log(chalk.white(' You can connect anytime by running: ') + chalk.cyan('npx mgcheck setup'));\n console.log(chalk.white(' Or run checks manually with: ') + chalk.cyan('npx mgcheck run <migration.sql>\\n'));\n return;\n }\n\n console.log(chalk.dim('\\n Configuring MCP integrations...\\n'));\n const results = setupMcp(options);\n\n for (const res of results) {\n console.log(res);\n }\n\n // ── Connection Verification ──────────────────────────────────────\n console.log('');\n console.log(chalk.bold(' 🔗 Connection Status'));\n\n const verified = verifyConnections();\n if (verified.length === 0) {\n console.log(chalk.yellow(' ⚠ No active connections found. Run ') + chalk.cyan('mgcheck setup') + chalk.yellow(' to retry.'));\n } else {\n for (const v of verified) {\n console.log(v);\n }\n }\n\n console.log(chalk.bold.green('\\n 🎉 Migration Guardian is ready!'));\n console.log(chalk.white(' Your AI assistant will now automatically check migrations before executing.\\n'));\n console.log(chalk.dim(' Tools available to your AI:'));\n console.log(chalk.dim(' • check_migration — shadow database execution + 10 AST safety rules'));\n console.log(chalk.dim(' • analyze_migration — fast static SQL linting\\n'));\n console.log(chalk.dim(' Manual CLI Commands:'));\n console.log(chalk.dim(' • mgcheck run <path> — analyze + shadow DB execution'));\n console.log(chalk.dim(' • mgcheck analyze <path> — static analysis only\\n'));\n\n // ── Enter Watch Mode ──────────────────────────────────────────────\n console.log(chalk.bold.cyan(' 👁️ Watch Mode — Listening for AI migration activity...'));\n console.log(chalk.dim(' Press Ctrl+C to stop.\\n'));\n console.log(chalk.dim(' ─────────────────────────────────────────────────────────'));\n console.log('');\n\n const { watchActivityLog, formatActivityEntry, LOG_FILE } = await import('../core/activity-log.js');\n\n // Clear old log entries so they don't replay on startup\n writeFileSync(LOG_FILE, '', 'utf-8');\n\n const stopWatching = watchActivityLog((entry) => {\n console.log(formatActivityEntry(entry));\n console.log('');\n });\n\n // Keep the process alive with an active interval timer\n const keepAlive = setInterval(() => {}, 1000 * 60 * 60);\n\n // Handle graceful shutdown on Ctrl+C\n process.on('SIGINT', () => {\n clearInterval(keepAlive);\n stopWatching();\n console.log(chalk.dim('\\n Migration Guardian stopped. Goodbye! 👋\\n'));\n process.exit(0);\n });\n}\n\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/installer/index.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { resolve, join, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport * as readline from 'node:readline/promises';\nimport { stdin as input, stdout as output } from 'node:process';\nimport os from 'node:os';\nimport chalk from 'chalk';\n\nexport interface SetupOptions {\n client?: 'antigravity' | 'claude' | 'cursor' | 'all';\n yes?: boolean;\n}\n\nexport interface DetectResult {\n antigravity: boolean;\n cursor: boolean;\n claude: boolean;\n hasAny: boolean;\n}\n\n/**\n * Detect which AI editors/clients are installed on the user's system or workspace.\n */\nexport function detectClients(): DetectResult {\n // 1. Antigravity check\n const geminiDir = join(os.homedir(), '.gemini');\n const antigravity = existsSync(geminiDir);\n\n // 2. Cursor check (local .cursor or system Cursor install)\n const localCursor = existsSync(join(process.cwd(), '.cursor'));\n let systemCursor = false;\n if (process.platform === 'win32') {\n systemCursor = existsSync(join(process.env.APPDATA || '', 'Cursor')) ||\n existsSync(join(process.env.LOCALAPPDATA || '', 'Programs', 'cursor'));\n } else if (process.platform === 'darwin') {\n systemCursor = existsSync('/Applications/Cursor.app') ||\n existsSync(join(os.homedir(), 'Applications', 'Cursor.app'));\n } else {\n systemCursor = existsSync(join(os.homedir(), '.config', 'Cursor'));\n }\n const cursor = localCursor || systemCursor;\n\n // 3. Claude Desktop check\n let claudeDir = '';\n if (process.platform === 'win32') {\n claudeDir = join(process.env.APPDATA || '', 'Claude');\n } else if (process.platform === 'darwin') {\n claudeDir = join(os.homedir(), 'Library', 'Application Support', 'Claude');\n } else {\n claudeDir = join(os.homedir(), '.config', 'Claude');\n }\n const claude = existsSync(claudeDir);\n\n return {\n antigravity,\n cursor,\n claude,\n hasAny: antigravity || cursor || claude,\n };\n}\n\n/**\n * Apply the MCP server configuration to the specified clients.\n */\nexport function setupMcp(options: SetupOptions = {}): string[] {\n const targetClient = options.client || 'all';\n const currentDir = dirname(fileURLToPath(import.meta.url));\n const serverPath = resolve(currentDir, './mcp/server.js');\n const fallbackPath = resolve(currentDir, '../dist/mcp/server.js');\n const finalServerPath = existsSync(serverPath) ? serverPath : fallbackPath;\n const results: string[] = [];\n\n // 1. Antigravity Configuration (~/.gemini/config/mcp_config.json)\n if (targetClient === 'all' || targetClient === 'antigravity') {\n try {\n const geminiConfigDir = join(os.homedir(), '.gemini', 'config');\n const mcpConfigFile = join(geminiConfigDir, 'mcp_config.json');\n\n if (!existsSync(geminiConfigDir)) {\n mkdirSync(geminiConfigDir, { recursive: true });\n }\n\n let config: any = { mcpServers: {} };\n if (existsSync(mcpConfigFile)) {\n try {\n config = JSON.parse(readFileSync(mcpConfigFile, 'utf-8'));\n if (!config.mcpServers) config.mcpServers = {};\n } catch {\n config = { mcpServers: {} };\n }\n }\n\n config.mcpServers['migration-guardian'] = {\n command: 'node',\n args: [finalServerPath],\n };\n\n writeFileSync(mcpConfigFile, JSON.stringify(config, null, 2), 'utf-8');\n results.push(chalk.green(' ✅ Google Antigravity') + chalk.dim(` (${mcpConfigFile})`));\n } catch (err) {\n results.push(chalk.yellow(' ⚠️ Google Antigravity: ') + (err as Error).message);\n }\n }\n\n // 2. Cursor Configuration (.cursor/mcp.json in workspace)\n if (targetClient === 'all' || targetClient === 'cursor') {\n try {\n const cursorDir = join(process.cwd(), '.cursor');\n const cursorFile = join(cursorDir, 'mcp.json');\n\n if (!existsSync(cursorDir)) {\n mkdirSync(cursorDir, { recursive: true });\n }\n\n let config: any = { mcpServers: {} };\n if (existsSync(cursorFile)) {\n try {\n config = JSON.parse(readFileSync(cursorFile, 'utf-8'));\n if (!config.mcpServers) config.mcpServers = {};\n } catch {\n config = { mcpServers: {} };\n }\n }\n\n config.mcpServers['migration-guardian'] = {\n command: 'node',\n args: [finalServerPath],\n };\n\n writeFileSync(cursorFile, JSON.stringify(config, null, 2), 'utf-8');\n results.push(chalk.green(' ✅ Cursor IDE') + chalk.dim(` (${cursorFile})`));\n } catch (err) {\n results.push(chalk.yellow(' ⚠️ Cursor IDE: ') + (err as Error).message);\n }\n }\n\n // 3. Claude Desktop Configuration\n if (targetClient === 'all' || targetClient === 'claude') {\n try {\n let claudeDir = '';\n if (process.platform === 'win32') {\n claudeDir = join(process.env.APPDATA || '', 'Claude');\n } else if (process.platform === 'darwin') {\n claudeDir = join(os.homedir(), 'Library', 'Application Support', 'Claude');\n } else {\n claudeDir = join(os.homedir(), '.config', 'Claude');\n }\n\n const claudeConfigFile = join(claudeDir, 'claude_desktop_config.json');\n\n if (existsSync(claudeDir)) {\n let config: any = { mcpServers: {} };\n if (existsSync(claudeConfigFile)) {\n try {\n config = JSON.parse(readFileSync(claudeConfigFile, 'utf-8'));\n if (!config.mcpServers) config.mcpServers = {};\n } catch {\n config = { mcpServers: {} };\n }\n }\n\n config.mcpServers['migration-guardian'] = {\n command: 'node',\n args: [finalServerPath],\n };\n\n writeFileSync(claudeConfigFile, JSON.stringify(config, null, 2), 'utf-8');\n } else if (targetClient === 'claude') {\n results.push(chalk.dim(' ℹ️ Claude Desktop not installed (skipped)'));\n }\n } catch (err) {\n results.push(chalk.yellow(' ⚠️ Claude Desktop: ') + (err as Error).message);\n }\n }\n\n // 4. Inject Agent Rule Files into current workspace\n const ruleResults = injectAgentRules(process.cwd());\n results.push(...ruleResults);\n\n return results;\n}\n\nconst GUARDIAN_RULE_BANNER = '<!-- MIGRATION GUARDIAN SAFETY RULES -->';\nconst GUARDIAN_RULE_CONTENT = `${GUARDIAN_RULE_BANNER}\n## Database Migration Safety (Enforced by Migration Guardian)\n\nWhen creating, modifying, or executing database migrations (SQL, Prisma, Drizzle, TypeORM, Knex, etc.):\n1. **NEVER execute raw DDL or database migrations directly** on local, staging, or production databases without prior safety checks.\n2. **ALWAYS use Migration Guardian (\\`mgcheck\\`)**:\n - If MCP tools are available, invoke \\\\\\`check_migration\\\\\\` (or \\\\\\`analyze_migration\\\\\\` for static analysis).\n - Alternatively, run \\\\\\`npx mgcheck analyze <path>\\\\\\` or \\\\\\`npx mgcheck run <path> --provider pglite\\\\\\` in terminal.\n3. **Follow Zero-Downtime Migration Standards**:\n - \\\\\\`MG001\\\\\\`: Always use \\\\\\`CREATE INDEX CONCURRENTLY\\\\\\` instead of blocking indexes.\n - \\\\\\`MG002\\\\\\`: Never use \\\\\\`ALTER COLUMN TYPE\\\\\\` directly (use expand/contract pattern).\n - \\\\\\`MG003\\\\\\`: Always provide a \\\\\\`DEFAULT\\\\\\` value when adding \\\\\\`NOT NULL\\\\\\` columns.\n - \\\\\\`MG004\\\\\\`: Add constraints with \\\\\\`NOT VALID\\\\\\`, then validate them in a subsequent step.\n - \\\\\\`MG005\\\\\\` / \\\\\\`MG006\\\\\\`: Never drop columns or tables without explicit user confirmation.\n - \\\\\\`MG009\\\\\\`: Always set \\\\\\`SET lock_timeout = '5s';\\\\\\` before risky DDL.\n - \\\\\\`MG010\\\\\\`: Provide a rollback / down migration file whenever applicable.\n`;\n\n/**\n * Append or create AI agent rule files (.cursorrules, CLAUDE.md, AGENTS.md, etc.)\n */\nfunction appendOrWriteRule(filePath: string, name: string): string | null {\n try {\n const parentDir = dirname(filePath);\n if (!existsSync(parentDir)) {\n mkdirSync(parentDir, { recursive: true });\n }\n\n if (existsSync(filePath)) {\n const existing = readFileSync(filePath, 'utf-8');\n if (existing.includes(GUARDIAN_RULE_BANNER) || existing.includes('Migration Guardian')) {\n return null; // Already present\n }\n writeFileSync(filePath, `${existing.trimEnd()}\\n\\n${GUARDIAN_RULE_CONTENT}`, 'utf-8');\n return chalk.green(` ✅ Updated ${name}`) + chalk.dim(` (${filePath})`);\n } else {\n writeFileSync(filePath, GUARDIAN_RULE_CONTENT, 'utf-8');\n return chalk.green(` ✅ Created ${name}`) + chalk.dim(` (${filePath})`);\n }\n } catch (err) {\n return chalk.yellow(` ⚠️ ${name}: `) + (err as Error).message;\n }\n}\n\n/**\n * Injects Migration Guardian safety rules into AI rule files across popular AI IDEs and agents.\n */\nexport function injectAgentRules(workspaceDir: string = process.cwd()): string[] {\n const results: string[] = [];\n\n // 1. Cursor IDE Rules (.cursor/rules/migration-guardian.mdc and .cursorrules)\n const cursorRuleFile = join(workspaceDir, '.cursor', 'rules', 'migration-guardian.mdc');\n const resCursorMdc = appendOrWriteRule(cursorRuleFile, 'Cursor Rule (.cursor/rules)');\n if (resCursorMdc) results.push(resCursorMdc);\n\n const cursorRulesLegacy = join(workspaceDir, '.cursorrules');\n const resCursorLegacy = appendOrWriteRule(cursorRulesLegacy, 'Cursor Rules (.cursorrules)');\n if (resCursorLegacy) results.push(resCursorLegacy);\n\n // 2. Claude Code / Claude Desktop Rules (CLAUDE.md)\n const claudeMd = join(workspaceDir, 'CLAUDE.md');\n const resClaude = appendOrWriteRule(claudeMd, 'Claude Rules (CLAUDE.md)');\n if (resClaude) results.push(resClaude);\n\n // 3. Antigravity & General Agent Rules (AGENTS.md)\n const agentsMd = join(workspaceDir, 'AGENTS.md');\n const resAgents = appendOrWriteRule(agentsMd, 'Agent Rules (AGENTS.md)');\n if (resAgents) results.push(resAgents);\n\n // 4. Windsurf Rules (.windsurfrules)\n const windsurfRules = join(workspaceDir, '.windsurfrules');\n const resWindsurf = appendOrWriteRule(windsurfRules, 'Windsurf Rules (.windsurfrules)');\n if (resWindsurf) results.push(resWindsurf);\n\n // 5. GitHub Copilot Instructions (.github/copilot-instructions.md)\n const copilotInstructions = join(workspaceDir, '.github', 'copilot-instructions.md');\n const resCopilot = appendOrWriteRule(copilotInstructions, 'GitHub Copilot Instructions');\n if (resCopilot) results.push(resCopilot);\n\n return results;\n}\n\n/**\n * Verify that migration-guardian is present in each AI client's MCP config.\n * Returns human-readable status lines.\n */\nexport function verifyConnections(): string[] {\n const lines: string[] = [];\n\n // 1. Antigravity\n const antigravityConfig = join(os.homedir(), '.gemini', 'config', 'mcp_config.json');\n if (existsSync(antigravityConfig)) {\n try {\n const cfg = JSON.parse(readFileSync(antigravityConfig, 'utf-8'));\n if (cfg?.mcpServers?.['migration-guardian']) {\n lines.push(chalk.green(' ✅ Connected to Google Antigravity'));\n }\n } catch { /* skip */ }\n }\n\n // 2. Cursor\n const cursorConfig = join(process.cwd(), '.cursor', 'mcp.json');\n if (existsSync(cursorConfig)) {\n try {\n const cfg = JSON.parse(readFileSync(cursorConfig, 'utf-8'));\n if (cfg?.mcpServers?.['migration-guardian']) {\n lines.push(chalk.green(' ✅ Connected to Cursor IDE'));\n }\n } catch { /* skip */ }\n }\n\n // 3. Claude Desktop\n let claudeConfig = '';\n if (process.platform === 'win32') {\n claudeConfig = join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json');\n } else if (process.platform === 'darwin') {\n claudeConfig = join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');\n } else {\n claudeConfig = join(os.homedir(), '.config', 'Claude', 'claude_desktop_config.json');\n }\n if (existsSync(claudeConfig)) {\n try {\n const cfg = JSON.parse(readFileSync(claudeConfig, 'utf-8'));\n if (cfg?.mcpServers?.['migration-guardian']) {\n lines.push(chalk.green(' ✅ Connected to Claude Desktop'));\n }\n } catch { /* skip */ }\n }\n\n return lines;\n}\n\n/**\n * Interactive Wizard: Auto-detects clients, prompts the user, and connects.\n */\nexport async function runInteractiveWizard(options: SetupOptions = {}) {\n console.log('');\n console.log(chalk.bold.cyan(' 🛡️ Migration Guardian') + chalk.dim(' (mgcheck) — Database Migration Safety'));\n console.log(chalk.dim(' Catch unsafe database migrations before they hit production.\\n'));\n\n const detected = detectClients();\n const detectedList: string[] = [];\n\n if (detected.antigravity) detectedList.push('Google Antigravity');\n if (detected.cursor) detectedList.push('Cursor IDE');\n if (detected.claude) detectedList.push('Claude Desktop');\n\n if (detectedList.length > 0) {\n console.log(chalk.bold(' Detected AI Clients on your system:'));\n for (const client of detectedList) {\n console.log(` ${chalk.cyan('•')} ${chalk.white(client)}`);\n }\n console.log('');\n } else {\n console.log(chalk.dim(' Configuring MCP integration for your AI editors (Antigravity, Cursor, Claude)...\\n'));\n }\n\n // Check if --yes was passed to bypass the prompt\n let shouldProceed = options.yes ?? false;\n\n if (!shouldProceed) {\n const promptText = detectedList.length > 0\n ? ` Connect Migration Guardian to ${detectedList.join(' & ')}? ${chalk.dim('[Y/n]')} `\n : ` Connect Migration Guardian to your AI agents? ${chalk.dim('[Y/n]')} `;\n\n const rl = readline.createInterface({ input, output });\n\n try {\n const answer = await rl.question(promptText);\n const trimmed = answer.trim().toLowerCase();\n shouldProceed = trimmed === '' || trimmed === 'y' || trimmed === 'yes';\n } finally {\n rl.close();\n }\n }\n\n if (!shouldProceed) {\n console.log(chalk.dim('\\n Setup skipped.'));\n console.log(chalk.white(' You can connect anytime by running: ') + chalk.cyan('npx mgcheck setup'));\n console.log(chalk.white(' Or run checks manually with: ') + chalk.cyan('npx mgcheck run <migration.sql>\\n'));\n return;\n }\n\n console.log(chalk.dim('\\n Configuring MCP integrations...\\n'));\n const results = setupMcp(options);\n\n for (const res of results) {\n console.log(res);\n }\n\n // ── Connection Verification ──────────────────────────────────────\n console.log('');\n console.log(chalk.bold(' 🔗 Connection Status'));\n\n const verified = verifyConnections();\n if (verified.length === 0) {\n console.log(chalk.yellow(' ⚠ No active connections found. Run ') + chalk.cyan('mgcheck setup') + chalk.yellow(' to retry.'));\n } else {\n for (const v of verified) {\n console.log(v);\n }\n }\n\n console.log(chalk.bold.green('\\n 🎉 Migration Guardian is ready!'));\n console.log(chalk.white(' Your AI assistant will now automatically check migrations before executing.\\n'));\n console.log(chalk.dim(' Tools available to your AI:'));\n console.log(chalk.dim(' • check_migration — shadow database execution + 10 AST safety rules'));\n console.log(chalk.dim(' • analyze_migration — fast static SQL linting\\n'));\n console.log(chalk.dim(' Manual CLI Commands:'));\n console.log(chalk.dim(' • mgcheck run <path> — analyze + shadow DB execution'));\n console.log(chalk.dim(' • mgcheck analyze <path> — static analysis only\\n'));\n\n // ── Enter Watch Mode ──────────────────────────────────────────────\n console.log(chalk.bold.cyan(' 👁️ Watch Mode — Listening for AI migration activity...'));\n console.log(chalk.dim(' Press Ctrl+C to stop.\\n'));\n console.log(chalk.dim(' ─────────────────────────────────────────────────────────'));\n console.log('');\n\n const { watchActivityLog, formatActivityEntry, clearActivityLog } = await import('../core/activity-log.js');\n\n // Clear old log entries so they don't replay on startup (ensures directory exists)\n clearActivityLog();\n\n const stopWatching = watchActivityLog((entry) => {\n console.log(formatActivityEntry(entry));\n console.log('');\n });\n\n // Keep the process alive with an active interval timer\n const keepAlive = setInterval(() => {}, 1000 * 60 * 60);\n\n // Handle graceful shutdown on Ctrl+C\n process.on('SIGINT', () => {\n clearInterval(keepAlive);\n stopWatching();\n console.log(chalk.dim('\\n Migration Guardian stopped. Goodbye! 👋\\n'));\n process.exit(0);\n });\n}\n\n"],"mappings":"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","names":[]}
|
package/dist/mcp/server.js
CHANGED
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/activity-log.ts"],"sourcesContent":["import { existsSync, mkdirSync, appendFileSync, readFileSync, statSync, watchFile, unwatchFile } from 'node:fs';\nimport { join } from 'node:path';\nimport os from 'node:os';\nimport chalk from 'chalk';\n\nconst LOG_DIR = join(os.homedir(), '.mgcheck');\nconst LOG_FILE = join(LOG_DIR, 'activity.log');\n\nexport interface ActivityEntry {\n timestamp: string;\n tool: string;\n summary: string;\n violations: number;\n errors: number;\n warnings: number;\n passed: boolean;\n statements: string[];\n}\n\n/**\n * Append an activity entry to the shared log file.\n * Called by the MCP server when a tool is invoked.\n */\nexport function logActivity(entry: ActivityEntry): void {\n if (!existsSync(LOG_DIR)) {\n mkdirSync(LOG_DIR, { recursive: true });\n }\n const line = JSON.stringify(entry) + '\\n';\n appendFileSync(LOG_FILE, line, 'utf-8');\n}\n\n/**\n * Watch the activity log and call the handler for each new entry.\n * Returns a cleanup function to stop watching.\n */\nexport function watchActivityLog(handler: (entry: ActivityEntry) => void): () => void {\n if (!existsSync(LOG_DIR)) {\n mkdirSync(LOG_DIR, { recursive: true });\n }\n\n // Create log file if it doesn't exist\n if (!existsSync(LOG_FILE)) {\n appendFileSync(LOG_FILE, '', 'utf-8');\n }\n\n // Track current file size to only read new content\n let lastSize = statSync(LOG_FILE).size;\n\n const interval = setInterval(() => {\n try {\n const currentSize = statSync(LOG_FILE).size;\n if (currentSize > lastSize) {\n // Read only the new bytes\n const fd = readFileSync(LOG_FILE, 'utf-8');\n const newContent = fd.substring(lastSize);\n lastSize = currentSize;\n\n const lines = newContent.split('\\n').filter((l) => l.trim());\n for (const line of lines) {\n try {\n const entry: ActivityEntry = JSON.parse(line);\n handler(entry);\n } catch {\n // Skip malformed lines\n }\n }\n }\n } catch {\n // File may be temporarily unavailable\n }\n }, 500);\n\n return () => {\n clearInterval(interval);\n };\n}\n\n/**\n * Format an activity entry for terminal display.\n */\nexport function formatActivityEntry(entry: ActivityEntry): string {\n const time = chalk.dim(`[${entry.timestamp}]`);\n const tool = chalk.cyan(entry.tool);\n\n const lines: string[] = [];\n lines.push(`${time} ${tool} called`);\n\n // Show each statement\n if (entry.statements.length > 0) {\n for (const stmt of entry.statements) {\n const truncated = stmt.length > 80 ? stmt.substring(0, 77) + '...' : stmt;\n lines.push(chalk.dim(' │ ') + chalk.white(truncated));\n }\n }\n\n // Show result\n if (entry.passed) {\n lines.push(chalk.dim(' └─ ') + chalk.green('✅ PASSED') + chalk.dim(` (${entry.violations} violations)`));\n } else {\n lines.push(\n chalk.dim(' └─ ') +\n chalk.red('❌ BLOCKED') +\n chalk.dim(` (${entry.errors} error${entry.errors !== 1 ? 's' : ''}, ${entry.warnings} warning${entry.warnings !== 1 ? 's' : ''})`)\n );\n }\n\n return lines.join('\\n');\n}\n\nexport { LOG_FILE };\n"],"mappings":";AAAA,SAAS,YAAY,WAAW,gBAAgB,cAAc,gBAAwC;AACtG,SAAS,YAAY;AACrB,OAAO,QAAQ;AACf,OAAO,WAAW;AAElB,IAAM,UAAU,KAAK,GAAG,QAAQ,GAAG,UAAU;AAC7C,IAAM,WAAW,KAAK,SAAS,cAAc;AAiBtC,SAAS,YAAY,OAA4B;AACtD,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,cAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,EACxC;AACA,QAAM,OAAO,KAAK,UAAU,KAAK,IAAI;AACrC,iBAAe,UAAU,MAAM,OAAO;AACxC;AAMO,SAAS,iBAAiB,SAAqD;AACpF,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,cAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,EACxC;AAGA,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,mBAAe,UAAU,IAAI,OAAO;AAAA,EACtC;AAGA,MAAI,WAAW,SAAS,QAAQ,EAAE;AAElC,QAAM,WAAW,YAAY,MAAM;AACjC,QAAI;AACF,YAAM,cAAc,SAAS,QAAQ,EAAE;AACvC,UAAI,cAAc,UAAU;AAE1B,cAAM,KAAK,aAAa,UAAU,OAAO;AACzC,cAAM,aAAa,GAAG,UAAU,QAAQ;AACxC,mBAAW;AAEX,cAAM,QAAQ,WAAW,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC;AAC3D,mBAAW,QAAQ,OAAO;AACxB,cAAI;AACF,kBAAM,QAAuB,KAAK,MAAM,IAAI;AAC5C,oBAAQ,KAAK;AAAA,UACf,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF,GAAG,GAAG;AAEN,SAAO,MAAM;AACX,kBAAc,QAAQ;AAAA,EACxB;AACF;AAKO,SAAS,oBAAoB,OAA8B;AAChE,QAAM,OAAO,MAAM,IAAI,IAAI,MAAM,SAAS,GAAG;AAC7C,QAAM,OAAO,MAAM,KAAK,MAAM,IAAI;AAElC,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,GAAG,IAAI,IAAI,IAAI,SAAS;AAGnC,MAAI,MAAM,WAAW,SAAS,GAAG;AAC/B,eAAW,QAAQ,MAAM,YAAY;AACnC,YAAM,YAAY,KAAK,SAAS,KAAK,KAAK,UAAU,GAAG,EAAE,IAAI,QAAQ;AACrE,YAAM,KAAK,MAAM,IAAI,WAAM,IAAI,MAAM,MAAM,SAAS,CAAC;AAAA,IACvD;AAAA,EACF;AAGA,MAAI,MAAM,QAAQ;AAChB,UAAM,KAAK,MAAM,IAAI,iBAAO,IAAI,MAAM,MAAM,eAAU,IAAI,MAAM,IAAI,KAAK,MAAM,UAAU,cAAc,CAAC;AAAA,EAC1G,OAAO;AACL,UAAM;AAAA,MACJ,MAAM,IAAI,iBAAO,IACf,MAAM,IAAI,gBAAW,IACrB,MAAM,IAAI,KAAK,MAAM,MAAM,SAAS,MAAM,WAAW,IAAI,MAAM,EAAE,KAAK,MAAM,QAAQ,WAAW,MAAM,aAAa,IAAI,MAAM,EAAE,GAAG;AAAA,IACrI;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;","names":[]}
|
|
File without changes
|