/**
 * studiograph start command
 *
 * Launches the Studiograph agent chat interface with memory support
 */
import { Command } from 'commander';
import { Agent } from '@mariozechner/pi-agent-core';
import { getModel } from '@mariozechner/pi-ai';
import { Workspace } from '../../core/workspace.js';
import { WorkspaceManager } from '../../core/workspace-manager.js';
import { MemoryService } from '../../services/memory-service.js';
import { SchemaRegistry } from '../../core/schema-registry.js';
import { ENTITY_SCHEMAS } from '../../core/validation.js';
import { renderEntityTypesSection } from '../../agent/prompts/entity-types-section.js';
import { createGraphTools } from '../../agent/tools/graph-tools.js';
import { createFsTools } from '../../agent/tools/fs-tools.js';
import { createOpsTools } from '../../agent/tools/ops-tools.js';
import { loadSkillIndex, buildSkillIndexPrompt, loadEagerSkills } from '../../agent/skill-loader.js';
import { parseFollowups } from '../../agent/followups.js';
import { createLoadSkillTool } from '../../agent/tools/load-skill.js';
import { loadCustomTools } from '../../agent/tools/tool-loader.js';
import { ConnectorManager } from '../../mcp/connector-manager.js';
import { buildConnectorRegistry, createConnectorTools } from '../../agent/tools/connector-tools.js';
import { spinner } from '@clack/prompts';
import { runPreflight } from '../../utils/preflight.js';
import { promptLLMSetupIfNeeded } from '../setup-wizard.js';
import { loadUserConfig, resolveApiKey, resolveProvider, resolveModelId } from '../../core/user-config.js';
import { notifyUpdateAvailable } from '../../utils/version-checker.js';
import { join, dirname } from 'path';
import { homedir } from 'os';
import { fileURLToPath } from 'url';
import { readFileSync, existsSync } from 'fs';
import { execFileSync } from 'child_process';
import * as readline from 'readline';
import { Markdown } from '@mariozechner/pi-tui';
import { info, dimText, header, warn, toolRef, renderUserPrompt } from '../colors.js';
import { box } from 'consola/utils';
import ora from 'ora';
import { markdownTheme } from '../theme.js';
export const startCommand = new Command('start')
    .description('Start the Studiograph agent chat interface')
    .option('--verbose', 'Show detailed startup info (tools, etc.)')
    .action(async (opts) => {
    if (opts.verbose)
        process.env.STUDIOGRAPH_VERBOSE = '1';
    const workspace = new Workspace();
    // Check if workspace is initialized
    if (!workspace.isWorkspace()) {
        console.error('❌ Not a Studiograph workspace. Run `studiograph init` first.');
        process.exit(1);
    }
    const ready = runPreflight();
    if (!ready) {
        process.exit(1);
        return;
    }
    // Load workspace config
    const config = await workspace.loadConfig();
    const repos = await workspace.getRepos();
    // Model setup — shared wizard handles first-time prompts and "already configured" message
    await promptLLMSetupIfNeeded();
    // Re-read user config (wizard may have updated it)
    const userConfig = loadUserConfig();
    const modelProvider = resolveProvider(userConfig, config);
    const modelId = resolveModelId(userConfig, config);
    const apiKey = resolveApiKey(userConfig, config);
    if (!apiKey) {
        console.error('❌ No API key configured. Run `studiograph config` to set one up.');
        process.exit(1);
    }
    // Initialize memory service
    const memoryService = new MemoryService();
    const recentMemory = memoryService.loadRecentMemory();
    // Initialize workspace manager
    const gitUser = { id: 'cli-user', name: config.team_name, email: 'noreply@studiograph.local' };
    const workspaceManager = new WorkspaceManager(process.cwd(), config, gitUser);
    // Build skill index from user > workspace > bundled directories
    const startFileDir = dirname(fileURLToPath(import.meta.url));
    const bundledSkillsDir = join(startFileDir, '../../agent/skills');
    const userSkillsDir = join(homedir(), '.studiograph', 'skills');
    const workspaceSkillsDir = join(process.cwd(), '.studiograph', 'skills');
    const skillIndex = loadSkillIndex([userSkillsDir, workspaceSkillsDir, bundledSkillsDir]);
    // Load ALL tools: graph + sync + skill loader
    const registryRef = { registry: null };
    const tools = [
        ...createGraphTools(workspaceManager, gitUser, process.cwd()),
        ...createOpsTools(process.cwd(), config),
        ...createFsTools(),
        createLoadSkillTool(skillIndex),
    ];
    // Create system prompt with tool reference, skills, and memory
    const systemPrompt = createSystemPrompt(config.team_name, repos, recentMemory, config.schema_extensions, tools, skillIndex);
    // Initialize agent
    const model = getModel(modelProvider, modelId);
    const agent = new Agent({
        getApiKey: () => apiKey,
        initialState: { model, systemPrompt, tools },
    });
    // Load custom tools and connector tools before starting the chat loop
    const userToolsDir = join(homedir(), '.studiograph', 'tools');
    const workspaceToolsDir = join(process.cwd(), '.studiograph', 'tools');
    let connectorSummary = '';
    try {
        const [customTools, connectorTools] = await Promise.all([
            loadCustomTools([userToolsDir, workspaceToolsDir], { workspaceManager, gitUser }),
            ConnectorManager.buildTools(ConnectorManager.loadConfigs(process.cwd())),
        ]);
        const extra = [...customTools];
        // Connector tools go through a proxy registry (connector_list + connector_call)
        // instead of registering hundreds of individual tools
        if (connectorTools.length > 0) {
            const registry = buildConnectorRegistry(connectorTools);
            extra.push(...createConnectorTools(registry));
            registryRef.registry = registry;
            connectorSummary = [...registry.connectors.keys()].join(', ');
        }
        if (extra.length > 0) {
            const existing = agent.state.tools;
            agent.state.tools = [...existing, ...extra];
        }
    }
    catch (err) {
        // Non-fatal — agent starts with core tools only
    }
    // Search index: first-run setup and staleness check
    await initSearchIndex(workspaceManager, config.repos, process.cwd());
    const totalTools = agent.state.tools.length;
    const memoryDays = memoryService.listMemoryFiles().length;
    const hasSemanticSearch = !!loadUserConfig().voyage_api_key;
    const searchStatus = hasSemanticSearch
        ? 'semantic'
        : `keyword ${dimText('(run studiograph config to enable semantic search)')}`;
    console.log(box([
        `Model:   ${header(modelProvider + '/' + modelId)}`,
        `Tools:   ${totalTools} registered${connectorSummary ? ` (${connectorSummary})` : ''}`,
        `Memory:  ${memoryDays} day${memoryDays === 1 ? '' : 's'} loaded`,
        `Search:  ${searchStatus}`,
    ].join('\n'), {
        title: `Studiograph — ${config.team_name}`,
        style: { borderColor: 'cyan', padding: 1 },
    }));
    if (opts.verbose) {
        const toolNames = agent.state.tools.map((t) => t.name).sort();
        const skillNames = skillIndex.map((s) => s.name).sort();
        console.log(dimText(`\n  Tools (${toolNames.length}): ${toolNames.join(', ')}`));
        console.log(dimText(`  Skills (${skillNames.length}): ${skillNames.join(', ') || '(none)'}`));
        if (connectorSummary) {
            console.log(dimText(`  Connectors: ${connectorSummary}`));
        }
        console.log(dimText(`  Workspace: ${process.cwd()}`));
        console.log(dimText(`  Repos: ${repos.map((r) => r.name).join(', ')}\n`));
    }
    // Check for updates (non-blocking, cached)
    await notifyUpdateAvailable();
    console.log(dimText('Type your message and press Enter. Type "exit" to quit.\n'));
    // Track conversation for memory
    const conversationLog = [];
    // Track whether any non-whitespace content has been written this turn
    let hasWrittenOutput = false;
    // Track whether we're in a consecutive tool sequence (for spacing)
    let inToolSequence = false;
    // Markdown rendering state — pi-tui Markdown component for styled output
    let mdRenderer = new Markdown('', 1, 0, markdownTheme);
    let fullText = '';
    let displayedLineCount = 0;
    let lastRenderedText = '';
    // Collapse consecutive blank lines in rendered output
    let lastLineWasBlank = false;
    // Spinner state for tool execution
    let toolSpinner = null;
    // Disable ora animation in non-TTY sessions or when explicitly opted out
    // (pty-wrapped captures, CI logs, agent-driven sessions). Ora's own
    // isTTY check only sees the pty, not the capture behind it.
    const spinnersEnabled = !!process.stderr.isTTY
        && process.env.STUDIOGRAPH_NO_SPINNER !== '1'
        && process.env.CI !== 'true';
    // Subscribe to agent events
    agent.subscribe((event) => {
        if (event.type === 'message_start') {
            // Reset per-turn output tracker but preserve inToolSequence across
            // message boundaries so text following a tool turn gets a blank line.
            hasWrittenOutput = false;
            // Reset markdown rendering state for new message
            mdRenderer = new Markdown('', 1, 0, markdownTheme);
            fullText = '';
            displayedLineCount = 0;
            lastRenderedText = '';
            lastLineWasBlank = false;
        }
        else if (event.type === 'message_update') {
            const msgEvent = event.assistantMessageEvent;
            if (msgEvent && msgEvent.type === 'text_delta') {
                let output = msgEvent.delta;
                // Suppress whitespace-only deltas until real content appears.
                if (!hasWrittenOutput) {
                    output = output.replace(/^\s+/, '');
                    if (!output)
                        return;
                }
                // Add breathing room before text that follows a tool sequence
                if (inToolSequence) {
                    process.stdout.write('\n');
                    inToolSequence = false;
                    lastLineWasBlank = true; // prevent double blank after tool gap
                }
                hasWrittenOutput = true;
                // Accumulate text and render complete lines through Markdown.
                // Truncate at any '<!--' to keep the trailing followups marker —
                // which the agent appends to every response — out of rendered
                // output. We surface the parsed suggestions on message_end.
                fullText += output;
                const markerStart = fullText.indexOf('<!--');
                const renderable = markerStart >= 0 ? fullText.substring(0, markerStart) : fullText;
                const lastNewline = renderable.lastIndexOf('\n');
                if (lastNewline >= 0) {
                    const completeText = renderable.substring(0, lastNewline + 1);
                    if (completeText !== lastRenderedText) {
                        // Defer rendering while inside an unclosed code fence — pi-tui
                        // renders code blocks with borders, which shifts line indices
                        // and causes content to be skipped by the incremental display.
                        const fenceCount = (completeText.match(/^```/gm) || []).length;
                        if (fenceCount % 2 === 1)
                            return;
                        mdRenderer.setText(completeText);
                        const width = process.stdout.columns || 80;
                        const allLines = mdRenderer.render(width);
                        for (let i = displayedLineCount; i < allLines.length; i++) {
                            const isBlank = allLines[i].trim() === '';
                            if (isBlank && lastLineWasBlank)
                                continue;
                            lastLineWasBlank = isBlank;
                            process.stdout.write(allLines[i] + '\n');
                        }
                        displayedLineCount = allLines.length;
                        lastRenderedText = completeText;
                    }
                }
            }
        }
        else if (event.type === 'message_end') {
            // Extract full assistant text and split off the followups marker
            // before the final render so the CLI never shows the raw comment.
            const msg = event.message;
            const textContent = msg.role === 'assistant'
                ? msg.content.filter((c) => c.type === 'text').map((c) => c.text).join('')
                : '';
            const { content: finalContent, suggestions } = parseFollowups(textContent || fullText);
            // Render any remaining text (trailing incomplete line) from the
            // suggestion-free content.
            if (finalContent && finalContent !== lastRenderedText) {
                mdRenderer.setText(finalContent);
                const width = process.stdout.columns || 80;
                const allLines = mdRenderer.render(width);
                for (let i = displayedLineCount; i < allLines.length; i++) {
                    const isBlank = allLines[i].trim() === '';
                    if (isBlank && lastLineWasBlank)
                        continue;
                    lastLineWasBlank = isBlank;
                    process.stdout.write(allLines[i] + '\n');
                }
            }
            if (msg.role === 'assistant' && textContent) {
                conversationLog.push(`Assistant: ${textContent}`);
                // Render suggestions as a dim numbered block under the response.
                if (suggestions && suggestions.length > 0) {
                    process.stdout.write('\x1b[0m\n');
                    process.stdout.write(dimText('  Try:') + '\n');
                    suggestions.forEach((s, i) => {
                        process.stdout.write(dimText(`    ${i + 1}. ${s}`) + '\n');
                    });
                }
                // Reset all formatting, then one blank line before next prompt
                process.stdout.write('\x1b[0m\n');
                inToolSequence = false;
            }
        }
        else if (event.type === 'tool_execution_start') {
            if (inToolSequence) {
                process.stderr.write('\n');
            }
            else {
                inToolSequence = true;
            }
            const toolLabel = info(`Using tool: ${toolRef(event.toolName)}`);
            if (spinnersEnabled) {
                toolSpinner = ora({
                    text: toolLabel,
                    stream: process.stderr,
                    color: 'cyan',
                }).start();
            }
            else {
                // Non-animated: one line on start, one on end. Keeps captured logs
                // (CI, pty-wrapped sessions) from filling with cursor-control frames.
                process.stderr.write(`${info('⠋')} ${toolLabel}\n`);
            }
            hasWrittenOutput = true;
        }
        else if (event.type === 'agent_end') {
            // Agent completely done - fire terminal bell once
            process.stdout.write('\x07');
        }
        else if (event.type === 'tool_execution_end') {
            const toolLabel = info(`Using tool: ${toolRef(event.toolName)}`);
            const errText = event.isError
                ? warn(`${toolRef(event.toolName)}: Execution failed`)
                : event.result?.details?.success === false
                    ? warn(`${toolRef(event.toolName)}: ${event.result.details.error || 'Unknown error'}`)
                    : null;
            if (toolSpinner) {
                if (errText)
                    toolSpinner.fail(errText);
                else
                    toolSpinner.stopAndPersist({ symbol: info('🔧'), text: toolLabel });
                toolSpinner = null;
            }
            else if (!spinnersEnabled) {
                process.stderr.write(`${errText ? warn('✗') : info('🔧')} ${errText || toolLabel}\n`);
            }
        }
    });
    // Create readline interface
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
        prompt: '> ',
    });
    // ── Multiline paste handling ──────────────────────────────────────────────
    // When text is pasted, readline fires 'line' for each \n in rapid
    // succession. We collect lines that arrive within a short window and
    // join them into a single message (like Claude Code).
    const PASTE_DEBOUNCE_MS = 50;
    let pasteBuffer = [];
    let pasteTimer = null;
    // ── Prompt serialization ──────────────────────────────────────────────────
    // If the user pastes multiple chunks or types while the agent is busy,
    // we queue messages and process them one at a time.
    let agentBusy = false;
    const messageQueue = [];
    // Once the readline stream closes (exit command, Ctrl+D, EOF on piped
    // stdin) we drain any buffered work before calling process.exit so
    // session memory is saved in every exit path, not just the 'exit'
    // command path.
    let closing = false;
    function saveSessionAndExit(code = 0) {
        if (conversationLog.length > 0) {
            try {
                const summary = conversationLog.join('\n\n');
                memoryService.saveSessionSummary(summary, {
                    timestamp: new Date().toISOString(),
                    message_count: conversationLog.length,
                });
            }
            catch { /* non-fatal */ }
        }
        console.log('\n👋 Goodbye!');
        process.exit(code);
    }
    async function processMessage(message) {
        if (message.toLowerCase() === 'exit') {
            // Routing through rl.close() means every exit path (exit command,
            // Ctrl+D, piped-stdin EOF) funnels through a single save+exit fn.
            rl.close();
            return;
        }
        if (!message) {
            if (!closing) {
                process.stdout.write('\x1b[0m');
                rl.prompt();
            }
            return;
        }
        agentBusy = true;
        try {
            conversationLog.push(`User: ${message}`);
            // Re-render user input with dark gray background (overwrites readline echo)
            // Account for multi-line wrapping: calculate how many terminal rows
            // the readline echo occupied, then move up and clear each one.
            const cols = process.stdout.columns || 80;
            const echoLen = 2 + message.length; // "> " prompt + message
            const wrappedLines = Math.ceil(echoLen / cols) || 1;
            process.stdout.write(`\x1b[${wrappedLines}A`); // move up N lines
            for (let i = 0; i < wrappedLines; i++) {
                process.stdout.write('\x1b[2K'); // clear current line
                if (i < wrappedLines - 1)
                    process.stdout.write('\x1b[1B'); // move down
            }
            process.stdout.write('\x1b[G'); // move to column 1
            process.stdout.write(renderUserPrompt(message) + '\n\n');
            // Send message to agent and wait for completion
            await agent.prompt(message);
            await agent.waitForIdle();
        }
        catch (error) {
            console.error('\n❌ Error:', error instanceof Error ? error.message : 'Unknown error');
            if (error instanceof Error && error.stack) {
                console.error('Stack trace:', error.stack);
            }
        }
        agentBusy = false;
        // Drain the queue, then either re-prompt or exit if we were waiting
        // for pending work to finish before closing.
        if (messageQueue.length > 0) {
            const next = messageQueue.shift();
            if (!closing) {
                process.stdout.write('\x1b[0m');
                rl.prompt();
            }
            setImmediate(() => processMessage(next));
        }
        else if (closing) {
            saveSessionAndExit();
        }
        else {
            process.stdout.write('\x1b[0m');
            rl.prompt();
        }
    }
    function flushPasteBuffer() {
        pasteTimer = null;
        const message = pasteBuffer.join('\n').trim();
        pasteBuffer = [];
        if (!message) {
            if (!agentBusy && !closing) {
                process.stdout.write('\x1b[0m');
                rl.prompt();
            }
            return;
        }
        if (agentBusy) {
            messageQueue.push(message);
        }
        else {
            processMessage(message);
        }
    }
    rl.prompt();
    rl.on('line', (input) => {
        pasteBuffer.push(input);
        // Reset the debounce timer — if another line arrives within
        // PASTE_DEBOUNCE_MS it's part of the same paste operation
        if (pasteTimer)
            clearTimeout(pasteTimer);
        pasteTimer = setTimeout(flushPasteBuffer, PASTE_DEBOUNCE_MS);
    });
    rl.on('close', () => {
        if (closing)
            return; // re-entrant close (rare but safe)
        closing = true;
        // If the close was triggered while there's still buffered input,
        // queued messages, or in-flight agent work, drain it before exiting
        // so piped input (echo "..." | studiograph start) and Ctrl+D during
        // a response both persist the session.
        if (pasteTimer) {
            clearTimeout(pasteTimer);
            pasteTimer = null;
        }
        if (pasteBuffer.length > 0) {
            flushPasteBuffer(); // schedules processMessage async
        }
        if (agentBusy || messageQueue.length > 0) {
            return; // processMessage will call saveSessionAndExit when drained
        }
        saveSessionAndExit();
    });
});
/**
 * Initialize the search index on startup.
 *
 * First run: prompts for Voyage AI key (optional), then runs full index.
 * Subsequent runs: checks git log for commits since last index and re-indexes changed entities.
 */
async function initSearchIndex(workspaceManager, repos, workspacePath) {
    const userConfig = loadUserConfig();
    const dbPath = userConfig.vector_db_path || join(homedir(), '.studiograph', 'vector');
    const vectorService = workspaceManager.vectorService;
    // First-run: no repos have been indexed yet
    const isFirstRun = repos.every(r => !vectorService.getLastIndexedTime(r.name));
    if (isFirstRun) {
        // Full index on first run
        const s = spinner();
        s.start('Indexing workspace for search...');
        let total = 0;
        for (const repo of repos) {
            const repoPath = join(workspacePath, repo.path);
            try {
                const graph = workspaceManager['graphs'].get(repo.name);
                if (!graph)
                    continue;
                const entities = graph.list();
                if (entities.length > 0) {
                    await vectorService.upsertMany(repo.name, entities);
                    total += entities.length;
                }
                vectorService.setLastIndexedTime(repo.name);
            }
            catch { /* non-fatal */ }
        }
        s.stop(`Search index ready (${total} entities)`);
        return;
    }
    // Subsequent runs: check git log for changes since last index
    const staleEntities = [];
    for (const repo of repos) {
        const lastIndexed = vectorService.getLastIndexedTime(repo.name);
        if (!lastIndexed)
            continue;
        const repoPath = join(workspacePath, repo.path);
        try {
            const output = execFileSync('git', ['log', `--since=${lastIndexed}`, '--name-status', '--format=', '--', '*.md'], { cwd: repoPath, encoding: 'utf-8', stdio: 'pipe' }).trim();
            if (!output)
                continue;
            for (const line of output.split('\n')) {
                const match = line.match(/^([AMD])\s+(\w+)\/([^/]+)\.md$/);
                if (!match)
                    continue;
                const [, status, entityType, entityId] = match;
                staleEntities.push({ repoName: repo.name, entityType, entityId, deleted: status === 'D' });
            }
        }
        catch { /* non-fatal */ }
    }
    if (staleEntities.length === 0)
        return;
    const s = spinner();
    s.start(`Updating search index (${staleEntities.length} change${staleEntities.length === 1 ? '' : 's'})...`);
    for (const item of staleEntities) {
        try {
            if (item.deleted) {
                await vectorService.delete(item.repoName, item.entityType, item.entityId);
            }
            else {
                const graph = workspaceManager['graphs'].get(item.repoName);
                if (!graph)
                    continue;
                const entity = graph.get(item.entityType, item.entityId);
                await vectorService.upsert(item.repoName, entity);
            }
        }
        catch { /* entity may not exist — skip */ }
    }
    for (const repo of repos) {
        vectorService.setLastIndexedTime(repo.name);
    }
    s.stop('Search index updated');
}
/**
 * Create system prompt with workspace context, tool reference, skills, and memory.
 */
function createSystemPrompt(teamName, repos, recentMemory, schemaExtensions, tools, skillIndex) {
    // Load the authoritative system prompt from system.md
    const __filename = fileURLToPath(import.meta.url);
    const __dirname = dirname(__filename);
    const promptPath = join(__dirname, '../../agent/prompts/system.md');
    let base = existsSync(promptPath)
        ? readFileSync(promptPath, 'utf-8')
        : 'You are Studiograph, an AI agent that helps creative teams manage their knowledge graphs.';
    // Auto-generate tool reference from registered tools, grouped by category
    if (tools && tools.length > 0) {
        const categories = {
            'Entity Management': [],
            'Enrichment': [],
            'Workspace Git': [],
            'External Connectors': [],
            'Operations': [],
            'Skills': [],
            'Other': [],
        };
        for (const t of tools) {
            if (t.description?.startsWith('DEPRECATED'))
                continue;
            const name = t.name ?? '';
            if (name.startsWith('enrich_'))
                categories['Enrichment'].push(t);
            else if (name.startsWith('workspace_'))
                categories['Workspace Git'].push(t);
            else if (name.startsWith('connector_'))
                categories['External Connectors'].push(t);
            else if (name.startsWith('app_') || name.startsWith('deploy_'))
                categories['Operations'].push(t);
            else if (name === 'load_skill')
                categories['Skills'].push(t);
            else if (['create_entity', 'get_entity', 'update_entity', 'delete_entity', 'transfer_entity', 'search_entities', 'list_entities', 'get_related', 'get_backlinks', 'validate_wikilinks', 'get_workspace_summary', 'list_repos', 'get_entity_schema', 'lint_entities'].includes(name))
                categories['Entity Management'].push(t);
            else if (['list_file_history', 'list_folder_history', 'list_recent_deletions', 'read_file_at_commit', 'get_entity_diff', 'restore_file'].includes(name))
                (categories['Git History'] ??= []).push(t);
            else
                categories['Other'].push(t);
        }
        const toolRef = Object.entries(categories)
            .filter(([, items]) => items.length > 0)
            .map(([cat, items]) => `**${cat}:**\n${items.map((t) => `- \`${t.name}\` — ${t.description}`).join('\n')}`)
            .join('\n\n');
        base = base.replace('{{TOOL_REFERENCE}}', toolRef);
    }
    else {
        base = base.replace('{{TOOL_REFERENCE}}', '');
    }
    const registry = new SchemaRegistry(schemaExtensions);
    const schemaSection = registry.toPromptSection();
    // Render the agent-visible entity type list from the launch allowlist +
    // any workspace-defined custom types.
    const builtinTypeKeys = new Set(Object.keys(ENTITY_SCHEMAS));
    const customTypes = registry.getAllTypes().filter(t => !builtinTypeKeys.has(t));
    base = base.replace('{{ENTITY_TYPES}}', renderEntityTypesSection({ customTypes }));
    const workspaceSection = `## Workspace: ${teamName}

You are working in a Studiograph workspace with the following repositories:

${repos.map(repo => `- ${repo.name} (${repo.type}): ${repo.description || 'No description'}`).join('\n')}`;
    const memorySection = recentMemory
        ? `## Memory Context\n\n${recentMemory}`
        : '';
    const outputSection = `## Output Formatting

You are running in a CLI terminal. Keep responses **short and scannable**.

**Length rules:**
- Default to 2-4 sentences for simple answers
- Use bullet lists for 3+ items — never write items as prose paragraphs
- Limit tool result summaries to the most relevant 5-10 items
- Only use headings (##) when the response has 2+ distinct sections
- If the user asks a yes/no question, lead with the answer

**Formatting rules:**
- Use **bold** for emphasis and \`inline code\` for commands, entity types, and field names
- Use fenced code blocks for multi-line code, YAML, or file contents
- Use > blockquotes sparingly for callouts
- NEVER use markdown tables (| pipes) — use bullet lists instead
- Do NOT repeat information the user already knows

Start by greeting the user briefly and asking how you can help.`;
    const parts = [base.trim(), workspaceSection];
    if (memorySection)
        parts.push(memorySection);
    if (schemaSection)
        parts.push(schemaSection);
    // Inline eager skills directly into the system prompt
    if (skillIndex) {
        const eagerContent = loadEagerSkills(skillIndex);
        if (eagerContent)
            parts.push(eagerContent);
    }
    // Append skill index for on-demand skills
    if (skillIndex) {
        const index = buildSkillIndexPrompt(skillIndex);
        if (index)
            parts.push(index);
    }
    parts.push(outputSection);
    return parts.join('\n\n');
}
//# sourceMappingURL=start.js.map