"use strict";
/**
 * KiroGraph Installer for Kiro
 *
 * Wires up:
 *  1. .kiro/settings/mcp.json  — registers the MCP server
 *  2. .kiro/hooks/*.json       — auto-sync on file save/create/delete (via mark-dirty + sync-if-dirty)
 *  3. .kiro/steering/kirograph.md — teaches Kiro to use the graph tools
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.runInstaller = runInstaller;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const readline = __importStar(require("readline"));
const child_process_1 = require("child_process");
const errors_1 = require("../errors");
const banner_1 = require("../bin/banner");
const progress_1 = require("../bin/progress");
// ── Helpers ───────────────────────────────────────────────────────────────────
const violet = '\x1b[38;5;99m';
const reset = '\x1b[0m';
const dim = '\x1b[2m';
function ask(rl, question) {
    return new Promise(resolve => rl.question(question, resolve));
}
/**
 * Interactive arrow-key selection menu.
 * Temporarily pauses the readline interface to take over raw stdin,
 * then resumes it when done so subsequent prompts work normally.
 */
async function arrowSelect(rl, label, options, defaultIndex = 0) {
    const bold = '\x1b[1m';
    const green = '\x1b[32m';
    const CURSOR_UP = '\x1b[A';
    const CURSOR_DOWN = '\x1b[B';
    const CLEAR_LINE = '\x1b[2K\x1b[G';
    let selected = defaultIndex;
    function render(first) {
        if (!first) {
            // Move cursor up by options.length + 1 (description line)
            process.stdout.write(`\x1b[${options.length + 1}A`);
        }
        for (let i = 0; i < options.length; i++) {
            const active = i === selected;
            const cursor = active ? `${green}${bold}❯${reset}` : ' ';
            const text = active ? `${bold}${options[i].label}${reset}` : `${dim}${options[i].label}${reset}`;
            process.stdout.write(`${CLEAR_LINE}  ${cursor} ${text}\n`);
        }
        const desc = options[selected].description;
        process.stdout.write(`${CLEAR_LINE}  ${dim}${desc}${reset}\n`);
    }
    return new Promise(resolve => {
        console.log(`\n  ${violet}${label}${reset}`);
        render(true);
        rl.pause();
        const stdin = process.stdin;
        const wasTTY = stdin.isTTY;
        if (wasTTY)
            stdin.setRawMode(true);
        stdin.resume();
        stdin.setEncoding('utf8');
        function onData(key) {
            if (key === CURSOR_UP || key === '\x1b[A') {
                selected = (selected - 1 + options.length) % options.length;
                render(false);
            }
            else if (key === CURSOR_DOWN || key === '\x1b[B') {
                selected = (selected + 1) % options.length;
                render(false);
            }
            else if (key === '\r' || key === '\n' || key === ' ') {
                stdin.removeListener('data', onData);
                if (wasTTY)
                    stdin.setRawMode(false);
                stdin.pause();
                rl.resume();
                // Print final selection
                process.stdout.write(`\x1b[${options.length + 1}A`);
                for (let i = 0; i < options.length; i++) {
                    const active = i === selected;
                    const cursor = active ? `${green}${bold}❯${reset}` : ' ';
                    const text = active ? `${green}${bold}${options[i].label}${reset}` : `${dim}${options[i].label}${reset}`;
                    process.stdout.write(`${CLEAR_LINE}  ${cursor} ${text}\n`);
                }
                process.stdout.write(`${CLEAR_LINE}\n`);
                resolve(options[selected].value);
            }
            else if (key === '\x03') {
                // Ctrl-C
                stdin.removeListener('data', onData);
                if (wasTTY)
                    stdin.setRawMode(false);
                process.exit(1);
            }
        }
        stdin.on('data', onData);
    });
}
function ensureDir(p) {
    fs.mkdirSync(p, { recursive: true });
}
function readJson(p) {
    try {
        return JSON.parse(fs.readFileSync(p, 'utf8'));
    }
    catch {
        return {};
    }
}
function writeJson(p, data) {
    fs.writeFileSync(p, JSON.stringify(data, null, 2) + '\n');
}
/**
 * Prompt a yes/no question, re-prompting on invalid input.
 * Accepts: "" (use default), "y", "Y", "n", "N".
 */
async function askBool(rl, question, description, defaultYes = true) {
    const hint = defaultYes ? 'Y/n' : 'y/N';
    console.log(`\n  ${dim}${description}${reset}`);
    while (true) {
        const raw = await ask(rl, `  ${violet}${question}${reset} ${dim}(${hint})${reset} `);
        if (raw === '')
            return defaultYes;
        if (raw === 'y' || raw === 'Y')
            return true;
        if (raw === 'n' || raw === 'N')
            return false;
        console.log(`  Please enter y or n.`);
    }
}
/**
 * Prompt for a string value, returning the default on empty input.
 */
async function askString(rl, question, description, defaultValue) {
    console.log(`\n  ${dim}${description}${reset}`);
    const raw = await ask(rl, `  ${violet}${question}${reset} ${dim}(${defaultValue})${reset} `);
    return raw === '' ? defaultValue : raw;
}
// ── Config Options ────────────────────────────────────────────────────────────
const config_1 = require("../config");
const DEFAULT_EMBEDDING_MODEL = 'nomic-ai/nomic-embed-text-v1.5';
async function promptConfigOptions(rl) {
    const enableEmbeddings = await askBool(rl, 'Enable semantic embeddings for similarity search? (requires a local embedding model)', 'Enables semantic/similarity-based code search. Increases indexing time and requires a compatible local embedding model (e.g. via Ollama).');
    const patch = { enableEmbeddings, useVecIndex: false, semanticEngine: 'cosine', extractDocstrings: true, trackCallSites: true };
    if (enableEmbeddings) {
        console.log(`\n  ${dim}HuggingFace model identifier for generating embeddings (e.g. org/model-name).${reset}`);
        console.log(`  ${dim}Press Enter to use the default: ${DEFAULT_EMBEDDING_MODEL}${reset}`);
        let embeddingModel = DEFAULT_EMBEDDING_MODEL;
        while (true) {
            const raw = (await ask(rl, `  ${violet}Embedding model identifier:${reset} `)).trim();
            if (raw === '') {
                embeddingModel = DEFAULT_EMBEDDING_MODEL;
                break;
            }
            if (raw.includes('/')) {
                embeddingModel = raw;
                break;
            }
            console.log(`  Expected a HuggingFace model ID in the format org/model-name (e.g. nomic-ai/nomic-embed-text-v1.5).`);
        }
        patch.embeddingModel = embeddingModel;
        if (embeddingModel !== DEFAULT_EMBEDDING_MODEL) {
            console.log(`\n  ℹ  To use this model locally, run: ollama pull ${embeddingModel}`);
        }
        // Engine selection
        const semanticEngine = await arrowSelect(rl, 'Choose the semantic search engine:', [
            { value: 'cosine', label: 'cosine', description: 'In-process cosine similarity. No extra deps. Best for small/medium projects.' },
            { value: 'sqlite-vec', label: 'sqlite-vec', description: 'ANN index. Sub-linear search. Best for large codebases. Needs: better-sqlite3, sqlite-vec (native).' },
            { value: 'orama', label: 'orama', description: 'Hybrid search (full-text + vector). Pure JS. Needs: @orama/orama, @orama/plugin-data-persistence.' },
            { value: 'pglite', label: 'pglite', description: 'Hybrid search via PostgreSQL + pgvector. Exact results. Pure WASM. Needs: @electric-sql/pglite.' },
        ]);
        patch.semanticEngine = semanticEngine;
        patch.useVecIndex = semanticEngine === 'sqlite-vec'; // keep legacy field in sync
    }
    patch.extractDocstrings = await askBool(rl, 'Extract docstrings from source files?', 'Enriches symbol metadata and improves context quality. Slightly increases indexing time.');
    patch.trackCallSites = await askBool(rl, 'Track call sites to enable caller/callee graph traversal?', 'Enables the kirograph_callers and kirograph_callees MCP tools for graph traversal. Increases index size.');
    return patch;
}
// ── MCP Config ────────────────────────────────────────────────────────────────
function writeMcpConfig(kiroDir) {
    const mcpPath = path.join(kiroDir, 'settings', 'mcp.json');
    ensureDir(path.dirname(mcpPath));
    const existing = readJson(mcpPath);
    existing.mcpServers = existing.mcpServers ?? {};
    existing.mcpServers.kirograph = {
        command: 'kirograph',
        args: ['serve', '--mcp'],
        disabled: false,
        autoApprove: [
            'kirograph_search',
            'kirograph_context',
            'kirograph_callers',
            'kirograph_callees',
            'kirograph_impact',
            'kirograph_node',
            'kirograph_status',
            'kirograph_files',
            'kirograph_dead_code',
            'kirograph_circular_deps',
            'kirograph_path',
            'kirograph_type_hierarchy',
        ],
    };
    writeJson(mcpPath, existing);
    console.log(`  ✓ MCP server registered in ${mcpPath}`);
}
// ── Kiro Hooks ────────────────────────────────────────────────────────────────
const FILE_PATTERNS = [
    '**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx',
    '**/*.py', '**/*.go', '**/*.rs', '**/*.java',
    '**/*.cs', '**/*.rb', '**/*.php', '**/*.swift',
    '**/*.kt', '**/*.dart',
];
const HOOKS = [
    {
        filename: 'kirograph-mark-dirty-on-save.json',
        hook: {
            name: 'KiroGraph Mark Dirty on Save',
            version: '1.0.0',
            description: 'Mark the KiroGraph index as dirty when source files are saved. Sync is deferred to agent idle.',
            when: {
                type: 'fileEdited',
                patterns: FILE_PATTERNS,
            },
            then: {
                type: 'runCommand',
                command: 'kirograph mark-dirty 2>/dev/null || true',
            },
        },
    },
    {
        filename: 'kirograph-mark-dirty-on-create.json',
        hook: {
            name: 'KiroGraph Mark Dirty on Create',
            version: '1.0.0',
            description: 'Mark the KiroGraph index as dirty when source files are created.',
            when: {
                type: 'fileCreated',
                patterns: FILE_PATTERNS,
            },
            then: {
                type: 'runCommand',
                command: 'kirograph mark-dirty 2>/dev/null || true',
            },
        },
    },
    {
        filename: 'kirograph-sync-on-delete.json',
        hook: {
            name: 'KiroGraph Sync on Delete',
            version: '1.0.0',
            description: 'Remove deleted files from the KiroGraph index immediately.',
            when: {
                type: 'fileDeleted',
                patterns: FILE_PATTERNS,
            },
            then: {
                type: 'runCommand',
                command: 'kirograph sync-if-dirty 2>/dev/null || true',
            },
        },
    },
    {
        filename: 'kirograph-sync-if-dirty.json',
        hook: {
            name: 'KiroGraph Deferred Sync',
            version: '1.0.0',
            description: 'Sync the KiroGraph index when the agent is idle and a dirty marker is present. Batches multiple rapid saves into one sync.',
            when: {
                type: 'agentStop',
            },
            then: {
                type: 'runCommand',
                command: 'kirograph sync-if-dirty --quiet 2>/dev/null || true',
            },
        },
    },
];
function migrateOnIdleHooks(hooksDir) {
    if (!fs.existsSync(hooksDir))
        return;
    let files;
    try {
        files = fs.readdirSync(hooksDir).filter(f => f.endsWith('.json'));
    }
    catch {
        return;
    }
    for (const file of files) {
        const filePath = path.join(hooksDir, file);
        let raw;
        try {
            raw = fs.readFileSync(filePath, 'utf8');
        }
        catch {
            (0, errors_1.logWarn)(`KiroGraph installer: could not read hook file ${filePath}`);
            continue;
        }
        let obj;
        try {
            obj = JSON.parse(raw);
        }
        catch {
            (0, errors_1.logWarn)(`KiroGraph installer: could not parse hook file ${filePath}`);
            continue;
        }
        if (obj?.when?.type === 'onIdle') {
            obj.when.type = 'agentStop';
            try {
                fs.writeFileSync(filePath, JSON.stringify(obj, null, 2) + '\n');
            }
            catch {
                (0, errors_1.logWarn)(`KiroGraph installer: could not write migrated hook file ${filePath}`);
            }
        }
    }
}
function writeHooks(kiroDir) {
    const hooksDir = path.join(kiroDir, 'hooks');
    ensureDir(hooksDir);
    // Migrate existing hooks from onIdle → agentStop
    migrateOnIdleHooks(hooksDir);
    // Remove old sync hooks that are now replaced
    const oldHooks = [
        'kirograph-sync-on-save.json',
        'kirograph-sync-on-create.json',
    ];
    for (const old of oldHooks) {
        const p = path.join(hooksDir, old);
        if (fs.existsSync(p))
            fs.unlinkSync(p);
    }
    for (const { filename, hook } of HOOKS) {
        writeJson(path.join(hooksDir, filename), hook);
    }
    console.log(`  ✓ Auto-sync hooks written to ${hooksDir}`);
}
// ── Steering File ─────────────────────────────────────────────────────────────
const STEERING_CONTENT = `---
inclusion: always
---

# KiroGraph

KiroGraph builds a semantic knowledge graph of your codebase for faster, smarter code exploration.

## When \`.kirograph/\` exists in the project

Use KiroGraph MCP tools for exploration instead of grep/glob/file reads:

| Tool | Use For |
|------|---------|
| \`kirograph_search\` | Find symbols by name (functions, classes, types) |
| \`kirograph_context\` | Get relevant code context for a task — **start here** |
| \`kirograph_callers\` | Find what calls a function |
| \`kirograph_callees\` | Find what a function calls |
| \`kirograph_impact\` | See what's affected by changing a symbol |
| \`kirograph_node\` | Get details + source code for a symbol |
| \`kirograph_status\` | Check index health and statistics |
| \`kirograph_files\` | List the indexed file structure |
| \`kirograph_dead_code\` | Find symbols with no incoming references |
| \`kirograph_circular_deps\` | Detect circular import dependencies |
| \`kirograph_path\` | Find the shortest path between two symbols |
| \`kirograph_type_hierarchy\` | Traverse class/interface inheritance |

### Workflow

1. Start with \`kirograph_context\` for any task — it returns entry points and related code in one call.
2. Use \`kirograph_search\` instead of grep for finding symbols.
3. Use \`kirograph_callers\`/\`kirograph_callees\` to trace code flow.
4. Use \`kirograph_impact\` before making changes to understand blast radius.
5. Use \`kirograph_files\` to explore the project structure.
6. Use \`kirograph_dead_code\` to identify unused code before refactoring.

### If \`.kirograph/\` does NOT exist

Ask the user: "This project doesn't have KiroGraph initialized. Run \`kirograph init -i\` to build a code knowledge graph for faster exploration?"
`;
function writeSteering(kiroDir) {
    const steeringDir = path.join(kiroDir, 'steering');
    ensureDir(steeringDir);
    const steeringPath = path.join(steeringDir, 'kirograph.md');
    fs.writeFileSync(steeringPath, STEERING_CONTENT);
    console.log(`  ✓ Steering file written to ${steeringPath}`);
}
// ── Main Installer ────────────────────────────────────────────────────────────
async function runInstaller() {
    (0, banner_1.printBanner)();
    const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
    try {
        // Determine workspace root
        const cwd = process.cwd();
        const kiroDir = path.join(cwd, '.kiro');
        console.log(`  Workspace: ${cwd}\n`);
        const proceed = await ask(rl, '  Install KiroGraph for this Kiro workspace? (Y/n) ');
        if (proceed.toLowerCase() === 'n') {
            console.log('  Cancelled.');
            rl.close();
            return;
        }
        console.log();
        // 1. MCP config
        writeMcpConfig(kiroDir);
        // 2. Hooks
        writeHooks(kiroDir);
        // 3. Steering
        writeSteering(kiroDir);
        // 4. Prompt for config options and persist
        const patch = await promptConfigOptions(rl);
        try {
            await (0, config_1.updateConfig)(cwd, patch);
            console.log(`\n  Configuration saved to ${cwd}/.kirograph/config.json`);
            console.log(`  • enableEmbeddings: ${patch.enableEmbeddings}`);
            if ('embeddingModel' in patch) {
                console.log(`  • embeddingModel: ${patch.embeddingModel}`);
            }
            if (patch.enableEmbeddings) {
                console.log(`  • semanticEngine: ${patch.semanticEngine}`);
                if (patch.semanticEngine === 'sqlite-vec') {
                    console.log(`\n  Installing sqlite-vec dependencies...`);
                    const result = (0, child_process_1.spawnSync)('npm', ['install', 'better-sqlite3', 'sqlite-vec'], {
                        stdio: 'inherit',
                        shell: true,
                    });
                    if (result.status === 0) {
                        console.log(`  ✓ better-sqlite3 and sqlite-vec installed`);
                    }
                    else {
                        console.warn(`  ✗ npm install failed (exit ${result.status}). Run manually:`);
                        console.warn(`    npm install better-sqlite3 sqlite-vec`);
                    }
                }
                else if (patch.semanticEngine === 'orama') {
                    console.log(`\n  Installing Orama dependencies...`);
                    const result = (0, child_process_1.spawnSync)('npm', ['install', '@orama/orama', '@orama/plugin-data-persistence'], {
                        stdio: 'inherit',
                        shell: true,
                    });
                    if (result.status === 0) {
                        console.log(`  ✓ @orama/orama and @orama/plugin-data-persistence installed`);
                    }
                    else {
                        console.warn(`  ✗ npm install failed (exit ${result.status}). Run manually:`);
                        console.warn(`    npm install @orama/orama @orama/plugin-data-persistence`);
                    }
                }
                else if (patch.semanticEngine === 'pglite') {
                    console.log(`\n  Installing PGlite dependencies...`);
                    const result = (0, child_process_1.spawnSync)('npm', ['install', '@electric-sql/pglite'], {
                        stdio: 'inherit',
                        shell: true,
                    });
                    if (result.status === 0) {
                        console.log(`  ✓ @electric-sql/pglite installed`);
                    }
                    else {
                        console.warn(`  ✗ npm install failed (exit ${result.status}). Run manually:`);
                        console.warn(`    npm install @electric-sql/pglite`);
                    }
                }
            }
            console.log(`  • extractDocstrings: ${patch.extractDocstrings}`);
            console.log(`  • trackCallSites: ${patch.trackCallSites}`);
        }
        catch (err) {
            const reason = err instanceof Error ? err.message : String(err);
            console.error(`\n  ✗ Failed to write configuration: ${reason}`);
            process.exit(1);
        }
        // 5. Optionally init + index
        const doIndex = await ask(rl, '\n  Initialize and index this project now? (Y/n) ');
        if (doIndex.toLowerCase() !== 'n') {
            const KiroGraph = (await Promise.resolve().then(() => __importStar(require('../index')))).default;
            if (!KiroGraph.isInitialized(cwd)) {
                await KiroGraph.init(cwd);
                console.log('  ✓ Created .kirograph/');
            }
            const cg = await KiroGraph.open(cwd);
            console.log('  Indexing...');
            const result = await cg.indexAll({
                onProgress: progress_1.renderIndexProgress,
            });
            process.stdout.write('\n');
            console.log(`  ✓ Indexed ${result.filesIndexed} files, ${result.nodesCreated} symbols, ${result.edgesCreated} edges`);
            cg.close();
        }
        console.log('\n  Done! Restart Kiro for the MCP server to load.\n');
    }
    finally {
        rl.close();
    }
}
//# sourceMappingURL=index.js.map