/**
 * founderLocalPipeline.ts — Local intelligence pipeline that executes the
 * founder_deep_context_gather protocol end-to-end WITHOUT Convex.
 *
 * This is the P0 bridge: takes protocol → produces finished packet using
 * only local sources (git log, filesystem, SQLite session memory).
 *
 * Three tools:
 *   1. founder_local_gather   — Reads all local sources, returns structured context
 *   2. founder_local_synthesize — Takes gathered context, produces a FounderPacket
 *   3. founder_local_weekly_reset — Convenience: gather + synthesize in one call
 */
import { execSync } from "child_process";
import { readFileSync, existsSync } from "fs";
import { join, resolve, dirname } from "path";
import { fileURLToPath } from "url";
import { getDb, genId } from "../db.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/* ─── Helpers ────────────────────────────────────────────────────────────── */
function safeRead(filePath) {
    try {
        if (!existsSync(filePath))
            return null;
        return readFileSync(filePath, "utf-8");
    }
    catch {
        return null;
    }
}
function safeExec(cmd, cwd) {
    try {
        return execSync(cmd, { cwd, encoding: "utf-8", timeout: 10_000, stdio: ["pipe", "pipe", "pipe"] }).trim();
    }
    catch {
        return "";
    }
}
function findProjectRoot() {
    // Walk up from packages/mcp-local to find the monorepo root (has CLAUDE.md)
    let dir = resolve(__dirname, "..", "..");
    for (let i = 0; i < 5; i++) {
        if (existsSync(join(dir, "CLAUDE.md")))
            return dir;
        dir = resolve(dir, "..");
    }
    return process.cwd();
}
/* ─── Tool 1: founder_local_gather ───────────────────────────────────────── */
function gatherLocalContext(daysBack = 7) {
    const root = findProjectRoot();
    // ── Identity ──────────────────────────────────────────────────
    const claudeMd = safeRead(join(root, "CLAUDE.md"));
    const claudeMdSnippet = claudeMd
        ? claudeMd.split("\n").slice(0, 8).join("\n")
        : null;
    const pkgJson = safeRead(join(root, "packages", "mcp-local", "package.json"));
    let packageName = null;
    let packageVersion = null;
    if (pkgJson) {
        try {
            const pkg = JSON.parse(pkgJson);
            packageName = pkg.name ?? null;
            packageVersion = pkg.version ?? null;
        }
        catch { /* ignore */ }
    }
    // ── Recent changes (git) ──────────────────────────────────────
    const gitLog = safeExec(`git log --oneline --since="${daysBack} days ago" -30`, root);
    const gitDiffStat = safeExec(`git diff --stat HEAD~10 HEAD 2>/dev/null || echo "no diff"`, root);
    const modifiedFiles = safeExec(`git diff --name-only HEAD~10 HEAD 2>/dev/null || echo ""`, root)
        .split("\n")
        .filter(Boolean)
        .slice(0, 30);
    // ── Public surfaces ───────────────────────────────────────────
    const indexHtml = safeRead(join(root, "index.html"));
    let indexHtmlTitle = null;
    let indexHtmlOgDescription = null;
    if (indexHtml) {
        const titleMatch = indexHtml.match(/<title>([^<]+)<\/title>/);
        indexHtmlTitle = titleMatch?.[1] ?? null;
        const ogDescMatch = indexHtml.match(/og:description[^>]+content="([^"]+)"/);
        indexHtmlOgDescription = ogDescMatch?.[1] ?? null;
    }
    const serverJson = safeRead(join(root, "packages", "mcp-local", "server.json"));
    let serverJsonDescription = null;
    if (serverJson) {
        try {
            serverJsonDescription = JSON.parse(serverJson).description ?? null;
        }
        catch { /* ignore */ }
    }
    const readme = safeRead(join(root, "packages", "mcp-local", "README.md"));
    const readmeTagline = readme
        ? (readme.match(/\*\*([^*]+)\*\*/)?.[1] ?? null)
        : null;
    // ── Session memory (SQLite) ───────────────────────────────────
    let recentActions = [];
    let recentMilestones = [];
    let totalActions7d = 0;
    let totalMilestones7d = 0;
    try {
        const db = getDb();
        const sevenDaysAgo = new Date(Date.now() - daysBack * 86_400_000).toISOString().slice(0, 10);
        const actions = db.prepare(`SELECT action, category, impactLevel, timestamp FROM tracking_actions
       WHERE date(timestamp) >= ? ORDER BY timestamp DESC LIMIT 20`).all(sevenDaysAgo);
        recentActions = actions.map((a) => ({
            action: a.action, category: a.category, impact: a.impactLevel, timestamp: a.timestamp,
        }));
        const milestones = db.prepare(`SELECT title, category, timestamp FROM tracking_milestones
       WHERE date(timestamp) >= ? ORDER BY timestamp DESC LIMIT 10`).all(sevenDaysAgo);
        recentMilestones = milestones.map((m) => ({
            title: m.title, category: m.category, timestamp: m.timestamp,
        }));
        totalActions7d = db.prepare(`SELECT COUNT(*) as c FROM tracking_actions WHERE date(timestamp) >= ?`).get(sevenDaysAgo)?.c ?? 0;
        totalMilestones7d = db.prepare(`SELECT COUNT(*) as c FROM tracking_milestones WHERE date(timestamp) >= ?`).get(sevenDaysAgo)?.c ?? 0;
    }
    catch {
        // SQLite tables may not exist yet — that's fine
    }
    // ── Dogfood findings ──────────────────────────────────────────
    const docsDir = join(root, "docs");
    let latestDogfoodFile = null;
    let dogfoodVerdict = null;
    let p0Count = 0;
    let p1Count = 0;
    const dogfoodFindings = [];
    try {
        const files = safeExec(`ls -1t "${docsDir}"/dogfood-findings-*.json 2>/dev/null || echo ""`, root)
            .split("\n")
            .filter(Boolean);
        if (files.length > 0) {
            latestDogfoodFile = files[0];
            const content = safeRead(files[0]);
            if (content) {
                const parsed = JSON.parse(content);
                dogfoodVerdict = parsed.verdict ?? null;
                const allFindings = parsed.runs?.flatMap((r) => r.findings ?? []) ?? parsed.global_findings ?? [];
                for (const f of allFindings) {
                    if (f.severity === "P0")
                        p0Count++;
                    if (f.severity === "P1")
                        p1Count++;
                    dogfoodFindings.push(`[${f.severity}] ${f.description?.slice(0, 120) ?? f.title?.slice(0, 120) ?? "unknown"}`);
                }
            }
        }
    }
    catch { /* ignore */ }
    // ── Architecture docs ─────────────────────────────────────────
    const archDir = join(root, "docs", "architecture");
    const architectureDocs = [];
    try {
        const archFiles = safeExec(`ls -1t "${archDir}"/*.md 2>/dev/null | head -10`, root)
            .split("\n")
            .filter(Boolean);
        for (const f of archFiles) {
            const name = f.split("/").pop() ?? f;
            architectureDocs.push(name);
        }
    }
    catch { /* ignore */ }
    const prd = safeRead(join(archDir, "NODEBENCH_AI_APP_PRD_V1.md"));
    const prdSnippet = prd ? prd.split("\n").slice(0, 12).join("\n") : null;
    const runbook = safeRead(join(archDir, "DOGFOOD_RUNBOOK_V1.md"));
    const dogfoodRunbookSnippet = runbook ? runbook.split("\n").slice(0, 12).join("\n") : null;
    return {
        identity: { projectRoot: root, claudeMdSnippet, packageName, packageVersion },
        recentChanges: { gitLogOneline: gitLog.split("\n").filter(Boolean), gitDiffStat, modifiedFiles, daysBack },
        publicSurfaces: { indexHtmlTitle, indexHtmlOgDescription, serverJsonDescription, readmeTagline },
        sessionMemory: { recentActions, recentMilestones, totalActions7d, totalMilestones7d },
        dogfoodFindings: { latestFile: latestDogfoodFile, verdict: dogfoodVerdict, p0Count, p1Count, findings: dogfoodFindings },
        docs: { prdSnippet, dogfoodRunbookSnippet, architectureDocs },
    };
}
/* ─── Tool 2: founder_local_synthesize ───────────────────────────────────── */
function synthesizePacket(ctx, packetType) {
    const packetId = genId("pkt");
    // ── Extract identity from CLAUDE.md ───────────────────────────
    const claudeMd = ctx.identity.claudeMdSnippet ?? "";
    let canonicalMission = "Unknown";
    let wedge = "Unknown";
    // Parse the overview line from CLAUDE.md
    const overviewMatch = claudeMd.match(/NodeBench\s*[—–-]\s*(.+?)(?:\.\s|$)/m);
    if (overviewMatch) {
        canonicalMission = overviewMatch[1].trim();
    }
    const toolCountMatch = claudeMd.match(/(\d+)-tool/);
    if (toolCountMatch) {
        wedge = `${toolCountMatch[1]}-tool MCP server with entity intelligence`;
    }
    // ── Detect what changed from git ──────────────────────────────
    const whatChanged = ctx.recentChanges.gitLogOneline.slice(0, 8).map((line) => {
        const hash = line.slice(0, 8);
        const msg = line.slice(9);
        return { description: msg, date: "this week", source: `git:${hash}` };
    });
    // ── Detect contradictions ─────────────────────────────────────
    const contradictions = [];
    // Check: CLAUDE.md mission vs index.html title
    if (ctx.publicSurfaces.indexHtmlTitle && canonicalMission) {
        const titleLower = ctx.publicSurfaces.indexHtmlTitle.toLowerCase();
        const missionLower = canonicalMission.toLowerCase();
        // Simple word overlap check
        const missionWords = new Set(missionLower.split(/\s+/).filter(w => w.length > 3));
        const titleWords = new Set(titleLower.split(/\s+/).filter(w => w.length > 3));
        const overlap = [...missionWords].filter(w => titleWords.has(w)).length;
        if (overlap < 2) {
            contradictions.push({
                claim: `CLAUDE.md says: "${canonicalMission}"`,
                evidence: `index.html title says: "${ctx.publicSurfaces.indexHtmlTitle}"`,
                severity: "medium",
            });
        }
    }
    // Check: server.json vs README tagline alignment
    if (ctx.publicSurfaces.serverJsonDescription && ctx.publicSurfaces.readmeTagline) {
        const sjLower = ctx.publicSurfaces.serverJsonDescription.toLowerCase();
        const rdLower = ctx.publicSurfaces.readmeTagline.toLowerCase();
        if (!sjLower.includes("entity") && rdLower.includes("entity")) {
            contradictions.push({
                claim: "README positions as entity intelligence",
                evidence: `server.json description doesn't mention 'entity': "${ctx.publicSurfaces.serverJsonDescription.slice(0, 80)}..."`,
                severity: "low",
            });
        }
    }
    // Check: dogfood findings indicate unresolved P0s
    if (ctx.dogfoodFindings.p0Count > 0) {
        contradictions.push({
            claim: `${ctx.dogfoodFindings.p0Count} P0 dogfood findings unresolved`,
            evidence: ctx.dogfoodFindings.findings.filter(f => f.startsWith("[P0]")).join("; "),
            severity: "high",
        });
    }
    // Check: many surfaces but few tracked actions (building without proving)
    if (ctx.recentChanges.modifiedFiles.length > 20 && ctx.sessionMemory.totalActions7d < 5) {
        contradictions.push({
            claim: `${ctx.recentChanges.modifiedFiles.length} files modified this week but only ${ctx.sessionMemory.totalActions7d} tracked actions`,
            evidence: "Building is outpacing tracking — habits may not be proven yet",
            severity: "medium",
        });
    }
    // ── Rank next actions from dogfood + changes ──────────────────
    const nextActions = [];
    let priority = 1;
    // P0 dogfood findings become top actions
    for (const finding of ctx.dogfoodFindings.findings.filter(f => f.startsWith("[P0]"))) {
        nextActions.push({
            action: `Fix: ${finding.replace("[P0] ", "").slice(0, 100)}`,
            priority: priority++,
            reasoning: "P0 dogfood finding — blocks core habit loop",
        });
    }
    // P1 dogfood findings next
    for (const finding of ctx.dogfoodFindings.findings.filter(f => f.startsWith("[P1]")).slice(0, 3)) {
        nextActions.push({
            action: `Fix: ${finding.replace("[P1] ", "").slice(0, 100)}`,
            priority: priority++,
            reasoning: "P1 dogfood finding — degrades tool reliability",
        });
    }
    // If few actions, add defaults
    if (nextActions.length < 3) {
        if (contradictions.length > 0) {
            nextActions.push({
                action: "Resolve top contradiction: " + contradictions[0].claim.slice(0, 80),
                priority: priority++,
                reasoning: "Unresolved contradiction weakens canonical truth",
            });
        }
        nextActions.push({
            action: "Run full dogfood cycle (13 scenarios) and log pass/fail",
            priority: priority++,
            reasoning: "Proves the three core habits work end-to-end",
        });
        nextActions.push({
            action: "Publish updated package with fixed tracking tools and local pipeline",
            priority: priority++,
            reasoning: "Public package should match internal capabilities",
        });
    }
    // ── Signals from git activity ─────────────────────────────────
    const signals = [];
    const commitCount = ctx.recentChanges.gitLogOneline.length;
    signals.push({
        name: `${commitCount} commits in last ${ctx.recentChanges.daysBack} days`,
        direction: commitCount > 5 ? "up" : commitCount > 0 ? "neutral" : "down",
        impact: "medium",
    });
    signals.push({
        name: `${ctx.recentChanges.modifiedFiles.length} files modified`,
        direction: ctx.recentChanges.modifiedFiles.length > 10 ? "up" : "neutral",
        impact: "medium",
    });
    signals.push({
        name: `${ctx.sessionMemory.totalActions7d} tracked actions / ${ctx.sessionMemory.totalMilestones7d} milestones`,
        direction: ctx.sessionMemory.totalActions7d > 5 ? "up" : "neutral",
        impact: "high",
    });
    if (ctx.dogfoodFindings.verdict) {
        signals.push({
            name: `Dogfood verdict: ${ctx.dogfoodFindings.verdict.slice(0, 60)}`,
            direction: ctx.dogfoodFindings.verdict.toLowerCase().includes("pass") ? "up" : "down",
            impact: "high",
        });
    }
    // ── Public narrative check ────────────────────────────────────
    const publicMismatches = [];
    if (ctx.publicSurfaces.indexHtmlTitle && !ctx.publicSurfaces.indexHtmlTitle.toLowerCase().includes("entity")) {
        publicMismatches.push(`index.html title doesn't mention 'entity intelligence': "${ctx.publicSurfaces.indexHtmlTitle}"`);
    }
    if (ctx.publicSurfaces.serverJsonDescription && ctx.publicSurfaces.serverJsonDescription.includes("304")) {
        publicMismatches.push("server.json still references '304' tools");
    }
    if (ctx.publicSurfaces.readmeTagline && ctx.publicSurfaces.readmeTagline.includes("Operating intelligence")) {
        publicMismatches.push("README still uses 'Operating intelligence' instead of 'Entity intelligence'");
    }
    // ── Generate memo ─────────────────────────────────────────────
    const memoLines = [
        `# NodeBench Founder Weekly Reset`,
        `**Generated:** ${new Date().toISOString().slice(0, 10)} by founder_local_pipeline`,
        `**Package:** ${ctx.identity.packageName}@${ctx.identity.packageVersion}`,
        ``,
        `## What We Are Building`,
        canonicalMission,
        ``,
        `## What Changed This Week`,
        ...whatChanged.slice(0, 5).map((c, i) => `${i + 1}. ${c.description}`),
        ``,
        `## Contradictions (${contradictions.length})`,
        ...contradictions.map((c) => `- **[${c.severity}]** ${c.claim}\n  Evidence: ${c.evidence}`),
        ``,
        `## Next 3 Moves`,
        ...nextActions.slice(0, 3).map((a) => `${a.priority}. **${a.action}**\n   ${a.reasoning}`),
        ``,
        `## Signals`,
        ...signals.map((s) => `- ${s.direction === "up" ? "+" : s.direction === "down" ? "-" : "="} ${s.name} (${s.impact})`),
        ``,
        `## Public Narrative`,
        publicMismatches.length === 0
            ? "All public surfaces aligned with internal thesis."
            : publicMismatches.map((m) => `- MISMATCH: ${m}`).join("\n"),
        ``,
        `## Session Memory`,
        `- ${ctx.sessionMemory.totalActions7d} actions tracked / ${ctx.sessionMemory.totalMilestones7d} milestones in last 7 days`,
        `- Dogfood: ${ctx.dogfoodFindings.verdict ?? "no runs yet"}`,
    ];
    return {
        packetId,
        packetType,
        generatedAt: new Date().toISOString(),
        generatedBy: "founder_local_pipeline",
        canonicalEntity: {
            name: "NodeBench",
            canonicalMission,
            wedge,
            companyState: "building",
            identityConfidence: contradictions.length === 0 ? 85 : Math.max(50, 85 - contradictions.length * 10),
        },
        whatChanged,
        contradictions,
        nextActions,
        signals,
        publicNarrativeCheck: {
            aligned: publicMismatches.length === 0,
            mismatches: publicMismatches,
        },
        sessionStats: {
            actionsTracked7d: ctx.sessionMemory.totalActions7d,
            milestonesTracked7d: ctx.sessionMemory.totalMilestones7d,
            dogfoodVerdict: ctx.dogfoodFindings.verdict,
        },
        memo: memoLines.join("\n"),
    };
}
/* ─── Exported Tools ─────────────────────────────────────────────────────── */
export const founderLocalPipelineTools = [
    {
        name: "founder_local_gather",
        description: "Gathers all locally-available context for a founder packet: git log, " +
            "CLAUDE.md identity, public surface state (index.html, server.json, README), " +
            "SQLite session memory (tracked actions + milestones), dogfood findings, and " +
            "architecture docs. Returns structured GatheredContext. No Convex or external " +
            "APIs required. Use this as the first step of a local intelligence pipeline.",
        inputSchema: {
            type: "object",
            properties: {
                daysBack: {
                    type: "number",
                    description: "How many days of history to gather (default: 7)",
                },
            },
        },
        annotations: { readOnlyHint: true },
        handler: async (args) => {
            const ctx = gatherLocalContext(args.daysBack ?? 7);
            return {
                gathered: true,
                identity: ctx.identity,
                recentChanges: {
                    commitCount: ctx.recentChanges.gitLogOneline.length,
                    modifiedFileCount: ctx.recentChanges.modifiedFiles.length,
                    daysBack: ctx.recentChanges.daysBack,
                    topCommits: ctx.recentChanges.gitLogOneline.slice(0, 5),
                },
                publicSurfaces: ctx.publicSurfaces,
                sessionMemory: {
                    actions7d: ctx.sessionMemory.totalActions7d,
                    milestones7d: ctx.sessionMemory.totalMilestones7d,
                    recentActions: ctx.sessionMemory.recentActions.slice(0, 5),
                },
                dogfoodFindings: {
                    verdict: ctx.dogfoodFindings.verdict,
                    p0: ctx.dogfoodFindings.p0Count,
                    p1: ctx.dogfoodFindings.p1Count,
                    topFindings: ctx.dogfoodFindings.findings.slice(0, 5),
                },
                architectureDocs: ctx.docs.architectureDocs.slice(0, 8),
            };
        },
    },
    {
        name: "founder_local_synthesize",
        description: "Takes gathered local context and synthesizes a complete Founder Artifact Packet. " +
            "Detects contradictions between CLAUDE.md identity, public surfaces, and dogfood " +
            "findings. Ranks next actions by dogfood severity. Generates a readable memo. " +
            "No Convex or external APIs required. Call founder_local_gather first, or use " +
            "founder_local_weekly_reset for the full pipeline in one call.",
        inputSchema: {
            type: "object",
            properties: {
                packetType: {
                    type: "string",
                    enum: ["weekly_reset", "pre_delegation", "important_change"],
                    description: "Type of artifact packet to produce",
                },
                daysBack: {
                    type: "number",
                    description: "How many days of history to include (default: 7)",
                },
            },
            required: ["packetType"],
        },
        handler: async (args) => {
            const packetType = args.packetType;
            const ctx = gatherLocalContext(args.daysBack ?? 7);
            const packet = synthesizePacket(ctx, packetType);
            return packet;
        },
    },
    {
        name: "founder_local_weekly_reset",
        description: "One-call convenience: gathers all local context and produces a complete " +
            "weekly founder reset packet with: canonical truth, what changed (from git), " +
            "contradictions (comparing CLAUDE.md vs public surfaces vs dogfood findings), " +
            "ranked next actions, signals, public narrative alignment check, and a " +
            "human-readable memo. No Convex, no external APIs, no agent loop needed. " +
            "This is the first habit: messy context → canonical truth → change/contradiction " +
            "→ ranked interpretation → Artifact Packet.",
        inputSchema: {
            type: "object",
            properties: {
                daysBack: {
                    type: "number",
                    description: "How many days of history to include (default: 7)",
                },
            },
        },
        handler: async (args) => {
            const ctx = gatherLocalContext(args.daysBack ?? 7);
            const packet = synthesizePacket(ctx, "weekly_reset");
            // Also track this as a milestone
            try {
                const db = getDb();
                const now = new Date();
                const m = now.getMonth() + 1;
                const y = now.getFullYear();
                const dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
                db.prepare(`INSERT OR IGNORE INTO tracking_milestones
            (milestoneId, sessionId, timestamp, title, description, category, evidence, metrics, dayOfWeek, weekNumber, month, quarter, year)
           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(packet.packetId, `pipeline_${Date.now()}`, now.toISOString(), "Weekly founder reset generated", `Contradictions: ${packet.contradictions.length}, Next actions: ${packet.nextActions.length}, Signals: ${packet.signals.length}`, "dogfood", null, JSON.stringify({ contradictions: packet.contradictions.length, nextActions: packet.nextActions.length }), dayNames[now.getDay()], Math.ceil((now.getTime() - new Date(y, 0, 1).getTime()) / 604800000), `${y}-${String(m).padStart(2, "0")}`, `${y}-Q${Math.ceil(m / 3)}`, y);
            }
            catch {
                // Non-fatal — packet is the primary output
            }
            return packet;
        },
    },
];
//# sourceMappingURL=founderLocalPipeline.js.map