// hosts/claude-code/ux/mcp-resources.ts — SPEC-588
// Registers Planu UX resources on the MCP server at startup.
// Resources are materialized on-demand (no streaming, no TTL cache — data is small JSON).
import { readFile } from 'node:fs/promises';
import { glob } from 'glob';
/** Planu-namespaced resource URIs exposed by SPEC-588. */
export const PLANU_UX_RESOURCE_URIS = {
    specList: 'planu://ux/specs',
    progress: 'planu://ux/progress',
    constitution: 'planu://ux/constitution',
    recentActivity: 'planu://ux/recent-activity',
};
// ---------------------------------------------------------------------------
// Resource metadata (exported so the sync script can document them)
// ---------------------------------------------------------------------------
export const specListResourceMeta = {
    name: 'Planu UX — Spec List',
    uri: PLANU_UX_RESOURCE_URIS.specList,
    description: 'Summary of all Planu specs across all registered projects — id, title, status, type.',
    mimeType: 'application/json',
};
export const progressResourceMeta = {
    name: 'Planu UX — Progress',
    uri: PLANU_UX_RESOURCE_URIS.progress,
    description: 'Progress snapshot: spec counts by status, active work, recent completions.',
    mimeType: 'application/json',
};
export const constitutionResourceMeta = {
    name: 'Planu UX — Constitution',
    uri: PLANU_UX_RESOURCE_URIS.constitution,
    description: 'The project constitution — guiding principles and architectural decisions.',
    mimeType: 'text/markdown',
};
export const recentActivityResourceMeta = {
    name: 'Planu UX — Recent Activity',
    uri: PLANU_UX_RESOURCE_URIS.recentActivity,
    description: 'Last 20 spec status changes — what changed, when, and by whom.',
    mimeType: 'application/json',
};
// ---------------------------------------------------------------------------
// Resource content generators
// ---------------------------------------------------------------------------
function toSpecDataEntry(entry) {
    return {
        id: entry.id,
        title: entry.title,
        status: entry.status,
        type: entry.type,
        updatedAt: entry.updatedAt,
        createdAt: entry.createdAt,
    };
}
function safeStr(value) {
    return typeof value === 'string' ? value : '';
}
async function loadAllSpecEntries() {
    const specs = [];
    try {
        const files = await glob('data/projects/*/specs.json');
        for (const file of files) {
            try {
                const raw = await readFile(file, 'utf-8');
                const parsed = JSON.parse(raw);
                if (Array.isArray(parsed)) {
                    for (const entry of parsed) {
                        if (entry !== null && typeof entry === 'object' && 'id' in entry) {
                            specs.push(toSpecDataEntry(entry));
                        }
                    }
                }
            }
            catch {
                // skip malformed files
            }
        }
    }
    catch {
        // data/ not present yet
    }
    return specs;
}
async function buildSpecListContent() {
    const all = await loadAllSpecEntries();
    const specs = all.map((s) => ({ id: s.id, title: s.title, status: s.status, type: s.type }));
    return JSON.stringify({ count: specs.length, specs }, null, 2);
}
async function buildProgressContent() {
    const all = await loadAllSpecEntries();
    const byStatus = {};
    for (const s of all) {
        const status = safeStr(s.status) || 'unknown';
        byStatus[status] = (byStatus[status] ?? 0) + 1;
    }
    const recentDone = all
        .filter((s) => safeStr(s.status) === 'done')
        .map((s) => ({ id: s.id, title: s.title, updatedAt: s.updatedAt }))
        .sort((a, b) => safeStr(b.updatedAt).localeCompare(safeStr(a.updatedAt)))
        .slice(0, 5);
    const total = all.length;
    const implementing = byStatus.implementing ?? 0;
    const review = byStatus.review ?? 0;
    return JSON.stringify({ total, byStatus, activeWork: implementing + review, recentCompletions: recentDone }, null, 2);
}
async function buildConstitutionContent() {
    try {
        const files = await glob('data/projects/*/constitution.json');
        if (files.length > 0 && files[0]) {
            const raw = await readFile(files[0], 'utf-8');
            const parsed = JSON.parse(raw);
            return typeof parsed.content === 'string' ? parsed.content : JSON.stringify(parsed, null, 2);
        }
    }
    catch {
        // no constitution yet
    }
    return '# Constitution\n\nNo constitution found. Run `init_constitution` to create one.\n';
}
async function buildRecentActivityContent() {
    const all = await loadAllSpecEntries();
    const activity = all
        .map((s) => ({
        specId: s.id,
        title: s.title,
        status: s.status,
        changedAt: s.updatedAt ?? s.createdAt,
    }))
        .sort((a, b) => safeStr(b.changedAt).localeCompare(safeStr(a.changedAt)))
        .slice(0, 20);
    return JSON.stringify({ count: all.length, recent: activity }, null, 2);
}
// ---------------------------------------------------------------------------
// Registration
// ---------------------------------------------------------------------------
const RESOURCE_DEFS = [
    { meta: specListResourceMeta, handler: buildSpecListContent },
    { meta: progressResourceMeta, handler: buildProgressContent },
    { meta: constitutionResourceMeta, handler: buildConstitutionContent },
    { meta: recentActivityResourceMeta, handler: buildRecentActivityContent },
];
/**
 * Register Planu UX read-only resources on the MCP server.
 * Called once at startup — resources are materialized on each read request (no TTL cache).
 * Data is small JSON (< 50KB) so per-request materialization is fine.
 */
export function registerPlanuMcpResources(server) {
    for (const { meta, handler } of RESOURCE_DEFS) {
        server.registerResource(meta.name, meta.uri, { description: meta.description, mimeType: meta.mimeType }, async (uri) => ({
            contents: [{ uri: uri.href, text: await handler(), mimeType: meta.mimeType }],
        }));
    }
}
//# sourceMappingURL=mcp-resources.js.map