/**
 * standalone/index.ts — Zero-orchestration standards discovery (C20).
 *
 * Public entry point for consumers running their own orchestrator (Continue,
 * Aider, a custom agent) who only need read-only access to Aperant's
 * standards artifacts: AGENTS.md principles, resolved profile chain,
 * feature registry, design tokens, and design taste.
 *
 * Pure function contract:
 *   - No `process.exit`, no writes, no mutation of `.aperant/state.json`.
 *   - Errors are returned as `{ok: false, error, project_dir}`, never thrown.
 *   - Missing sections degrade to `null` per-section (not whole-bundle fail)
 *     EXCEPT an explicit `opts.profile` that fails to resolve, which is
 *     treated as a caller contract violation and returned as whole-bundle
 *     error.
 *
 * Reuses (no re-implementation):
 *   - parseAgentsMd / deriveKeywords / normalizeTitle from cli/constitution/parse.mjs
 *   - resolveProfile from cli/profile/resolve.mjs
 *   - createCoordinationStore for shared-root lookup (worktree-aware)
 *   - FeatureRegistrySchema for per-area validation
 *
 * No imports from orchestration modules (task/*, gate/*, route/*, wfrun/*,
 * team/*, roadmap/*).
 */
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { deriveKeywords, normalizeTitle, parseAgentsMd } from '../cli/constitution/parse.mjs';
import { createCoordinationStore } from '../cli/coordination/store.mjs';
import { resolveProfile } from '../cli/profile/resolve.mjs';
import { FeatureRegistrySchema } from '../schemas/feature-registry';
// Mirrors src/constitution.ts:39 — AGENTS.md preferred, CLAUDE.md fallback.
const AGENTS_SEARCH = ['AGENTS.md', 'agents.md', 'CLAUDE.md', 'claude.md'];
/**
 * Discover the project's standards artifacts as a single read-only bundle.
 *
 * @param projectDir directory containing (directly or via worktree shared root)
 *   AGENTS.md + `.aperant/` artifacts.
 * @param opts optional knobs. `opts.profile` resolves the cascade and merges
 *   the profile's obligations into `constitution.principles` (AGENTS.md wins
 *   on title collision).
 * @returns StandardsBundle — `{ok: true, ...}` on success; `{ok: false, error}`
 *   if `projectDir` does not exist or an explicit `opts.profile` fails to
 *   resolve.
 */
export function discoverStandards(projectDir, opts) {
    const absProjectDir = resolve(projectDir || '.');
    // Whole-bundle fail: non-existent project dir. We surface the absolute
    // resolution so consumers can log a single path.
    if (!existsSync(absProjectDir)) {
        return {
            ok: false,
            error: `project directory not found: ${absProjectDir}`,
            project_dir: absProjectDir,
        };
    }
    // Worktree-aware shared root. In a git worktree, the real `.aperant/` lives
    // at the main repo root; the standalone module matches the behavior of
    // every other apt-tools command by looking up through the coordination
    // store. AGENTS.md lives at the shared root too (not under `.aperant/`).
    let sharedRoot;
    try {
        sharedRoot = createCoordinationStore(absProjectDir).resolveSharedRoot();
    }
    catch {
        // Defensive: if the shared root lookup throws we still want to return a
        // bundle — the consumer asked for standards, not orchestration state.
        sharedRoot = absProjectDir;
    }
    // --- Constitution ---------------------------------------------------------
    const constitution = readConstitution(sharedRoot);
    // --- Profile --------------------------------------------------------------
    const requestedProfile = typeof opts?.profile === 'string' ? opts.profile : null;
    let profile = null;
    let profilePrinciples = [];
    if (requestedProfile && requestedProfile.length > 0) {
        // resolveProfile takes the *project root* (not shared root) because it
        // reuses createCoordinationStore internally — mirror commands/profile.mjs.
        const result = resolveProfile(absProjectDir, requestedProfile);
        if (!result || result.ok !== true) {
            // Whole-bundle fail — caller passed an explicit profile and it is
            // invalid. Spec: this is the one case we do NOT degrade per-section.
            const reason = (result && result.reason) || 'profile resolution failed';
            return {
                ok: false,
                error: reason,
                project_dir: absProjectDir,
            };
        }
        const resolved = result.resolved;
        const resolvedName = typeof resolved.name === 'string' ? resolved.name : requestedProfile;
        // Normalize obligations into Principle shape — mirrors
        // commands/constitution.mjs:92-111 so the two surfaces stay consistent.
        const obligations = Array.isArray(resolved.obligations)
            ? resolved.obligations
            : [];
        profilePrinciples = obligations.map((o) => {
            const title = typeof o.title === 'string' ? o.title : '';
            const text = typeof o.text === 'string' ? o.text : '';
            const severity = typeof o.severity === 'string' ? o.severity : 'obligation';
            return {
                id: typeof o.id === 'string' ? o.id : '',
                title,
                text,
                source: { section: `profile:${resolvedName}`, line: -1 },
                keywords: deriveKeywords(`${title} ${text}`),
                severity,
            };
        });
        profile = {
            name: resolvedName,
            chain: Array.isArray(result.chain) ? result.chain : [],
            resolved,
        };
    }
    // Merge profile obligations into constitution.principles — AGENTS.md wins
    // on normalized-title collision so profile additions never silently shadow
    // a hard-coded project principle.
    let principles = constitution.principles;
    if (profilePrinciples.length > 0) {
        const seen = new Set(principles.map((p) => normalizeTitle(p.title)));
        const merged = [...principles];
        for (const p of profilePrinciples) {
            const key = normalizeTitle(p.title);
            if (!key || seen.has(key))
                continue;
            seen.add(key);
            merged.push(p);
        }
        principles = merged;
    }
    // --- Feature registry -----------------------------------------------------
    const feature_registry = readFeatureRegistry(sharedRoot);
    // --- Design tokens --------------------------------------------------------
    const design_tokens = readDesignTokens(sharedRoot);
    // --- Design taste ---------------------------------------------------------
    const design_taste = readDesignTaste(sharedRoot);
    return {
        ok: true,
        project_dir: absProjectDir,
        constitution: {
            source: constitution.source,
            count: principles.length,
            principles,
        },
        profile,
        feature_registry,
        design_tokens,
        design_taste,
    };
}
// ---------------------------------------------------------------------------
// Section readers
// ---------------------------------------------------------------------------
function readConstitution(sharedRoot) {
    for (const filename of AGENTS_SEARCH) {
        const path = join(sharedRoot, filename);
        if (!existsSync(path))
            continue;
        try {
            const md = readFileSync(path, 'utf-8');
            const principles = parseAgentsMd(md);
            return { source: filename, principles };
        }
        catch {
            // Unreadable file — degrade to empty per the R1 contract ("per-section
            // null"). Constitution is an object so we return empty principles, not
            // null, to keep the primary-product shape predictable.
            return { source: filename, principles: [] };
        }
    }
    return { source: null, principles: [] };
}
function readFeatureRegistry(sharedRoot) {
    const dir = join(sharedRoot, '.aperant', 'features');
    if (!existsSync(dir))
        return null;
    let entries;
    try {
        entries = readdirSync(dir);
    }
    catch {
        return null;
    }
    const areas = [];
    const invalid = [];
    for (const name of entries) {
        if (!name.endsWith('.json'))
            continue;
        const path = join(dir, name);
        let isFile = false;
        try {
            isFile = statSync(path).isFile();
        }
        catch {
            continue;
        }
        if (!isFile)
            continue;
        let raw;
        try {
            raw = readFileSync(path, 'utf-8');
        }
        catch (err) {
            invalid.push({
                path,
                errors: [{ path: '<file>', message: `unreadable: ${err.message}` }],
            });
            continue;
        }
        let parsed;
        try {
            parsed = JSON.parse(raw);
        }
        catch (err) {
            invalid.push({
                path,
                errors: [{ path: '<root>', message: `invalid JSON: ${err.message}` }],
            });
            continue;
        }
        const result = FeatureRegistrySchema.safeParse(parsed);
        if (!result.success) {
            invalid.push({
                path,
                errors: result.error.issues.map((issue) => ({
                    path: issue.path.length === 0 ? '<root>' : issue.path.join('.'),
                    message: issue.message,
                })),
            });
            continue;
        }
        areas.push(result.data);
    }
    return { count: areas.length, areas, invalid };
}
function readDesignTokens(sharedRoot) {
    const path = join(sharedRoot, '.aperant', 'design', 'tokens.json');
    if (!existsSync(path))
        return null;
    try {
        const raw = readFileSync(path, 'utf-8');
        const parsed = JSON.parse(raw);
        if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
            return parsed;
        }
        return null;
    }
    catch {
        // Malformed JSON / read error → null. The write-side validator (C27)
        // already guards tokens.json; a corrupted file is not a standalone-read
        // concern.
        return null;
    }
}
function readDesignTaste(sharedRoot) {
    const path = join(sharedRoot, '.aperant', 'design', 'taste.yaml');
    if (!existsSync(path))
        return null;
    try {
        const raw = readFileSync(path, 'utf-8');
        return { raw };
    }
    catch {
        return null;
    }
}
//# sourceMappingURL=index.js.map