/**
 * commands/modes.mjs — `apt-tools modes list`.
 *
 * Power-user discoverability tool. Returns a JSON envelope with one
 * record per discovered skill, exposing modes / flags / content-formats /
 * config-driven behavior so a power user can build a mental model of
 * the framework without reading 30 SKILL.md files.
 *
 * Adding a new skill auto-appears in `modes list` output without any
 * code changes — the discovery is filesystem-scan-based (mirrors the
 * router's eager skill-discover module).
 *
 * CLI:
 *   apt-tools modes list [project-dir]
 *
 * Output envelope:
 *   { status: 'ok', command: 'modes-list',
 *     skills: [{ name, modes, flags, content_formats, config_keys, file_path }] }
 *
 * Field semantics:
 *   - name             — frontmatter `name` (e.g. "apt:plan")
 *   - modes            — frontmatter `execution_modes` (array)
 *   - flags            — parsed from frontmatter `argument-hint`
 *   - content_formats  — frontmatter `produces` (optional array; [] when absent)
 *   - config_keys      — hardcoded skill→config-paths map maintained inline
 */
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { parse as parseYaml } from 'yaml';
import { sanitizeAptVersionForYamlParse } from '../install/version-header.mjs';
import { ok } from '../util/result.mjs';
const FRONTMATTER_RE = /^---\s*([\s\S]*?)\s*---/;
/**
 * Hardcoded skill→config-paths map. Maintained inline so adding a new
 * config-dependent skill is a one-line change.
 *
 * Per AC12, this is the mapping `modes list` consumes to enumerate
 * which config keys a skill cares about. Skills that don't touch
 * config are omitted (their config_keys is `[]`).
 *
 * AUDIT-001 (0.7.2): all dotted paths below resolve against the MERGED
 * config (`loadMergedProjectConfig()` — shared `config.json` + local
 * `config.local.json` overlay). The five per-device fields enumerated by
 * `AUDIT_001_LOCAL_FIELDS` in `cli/config/load.mjs` (currently
 * `verification.open_report`, `multi_model.cross_verification.tools`,
 * `preferences.{date_format,time_format,quick_task_post_verify}`)
 * physically live in `config.local.json`; the wildcard `'*'` surface for
 * `apt:setup` covers both files. Consumers MUST go through the merged
 * loader — never raw `JSON.parse(readFileSync('.aperant/config.json'))` —
 * to honor per-device overrides correctly.
 *
 * @type {Record<string, string[]>}
 */
export const SKILL_CONFIG_KEYS = {
    'apt:execute': ['coding.tdd_default', 'tdd.iron_law'],
    'apt:triage': [
        'task_tracking.backend',
        'task_tracking.publish_to_tracker',
        'task_tracking.tracker_url',
        'task_tracking.tracker_label_vocabulary',
    ],
    'apt:prototype': ['coding.prototype_discipline'],
    'apt:verify': ['verification.evidence_capture', 'verification.max_iterations'],
    'apt:verify-proof': ['verification.open_report', 'verification.report_formats'],
    'apt:review': ['pipeline.skip_review'],
    'apt:ship': ['pipeline.auto_branch', 'pipeline.branch_prefix'],
    'apt:setup': ['*'], // setup edits config — wildcard surface
    'apt:run': ['parallelization.enabled', 'parallelization.max_agents'],
    // FRAMEWORK-BUG-033 capability gap (AC10) — fan-out concurrency cap.
    // Consumed by the /apt:fan-out SKILL body via cli/config/parallelization.mjs.
    'apt:fan-out': ['parallelization.max_tasks', 'parallelization.max_agents'],
};
/**
 * Parse the `argument-hint` frontmatter field for `--flag` tokens.
 * Returns a deduplicated array.
 *
 * @param {string|undefined} hint
 * @returns {string[]}
 */
function parseFlags(hint) {
    if (typeof hint !== 'string')
        return [];
    const flags = new Set();
    for (const m of hint.matchAll(/--[a-z][a-z0-9-]*/gi)) {
        flags.add(m[0]);
    }
    return [...flags];
}
/**
 * Walk a skills root and return every apt-<slug>/SKILL.md it contains.
 *
 * @param {string} root
 */
function walkSkillRoot(root) {
    if (!existsSync(root))
        return [];
    let stat;
    try {
        stat = statSync(root);
    }
    catch {
        return [];
    }
    if (!stat.isDirectory())
        return [];
    let entries;
    try {
        entries = readdirSync(root, { withFileTypes: true });
    }
    catch {
        return [];
    }
    const out = [];
    for (const entry of entries) {
        if (!entry.name.startsWith('apt'))
            continue;
        if (!entry.isDirectory())
            continue;
        const skillFile = join(root, entry.name, 'SKILL.md');
        if (existsSync(skillFile))
            out.push(skillFile);
    }
    return out;
}
/**
 * Parse a single SKILL.md into a modes-list record. Returns null if
 * the frontmatter is unparseable.
 *
 * @param {string} file
 */
function readSkillForModes(file) {
    let text;
    try {
        text = readFileSync(file, 'utf-8');
    }
    catch {
        return null;
    }
    const m = text.match(FRONTMATTER_RE);
    if (!m)
        return null;
    let parsed;
    try {
        parsed = parseYaml(sanitizeAptVersionForYamlParse(m[1]));
    }
    catch {
        return null;
    }
    if (parsed === null || typeof parsed !== 'object')
        return null;
    const name = typeof parsed.name === 'string' ? parsed.name : null;
    if (name === null)
        return null;
    const modes = Array.isArray(parsed.execution_modes)
        ? parsed.execution_modes.filter((m) => typeof m === 'string')
        : [];
    const flags = parseFlags(parsed['argument-hint']);
    const content_formats = Array.isArray(parsed.produces)
        ? parsed.produces.filter((p) => typeof p === 'string')
        : [];
    // config_keys: prefer frontmatter `config_keys:` field when present; fall back
    // to the hardcoded SKILL_CONFIG_KEYS map so skills not yet migrated still work.
    const config_keys = Array.isArray(parsed.config_keys)
        ? parsed.config_keys.filter((k) => typeof k === 'string')
        : SKILL_CONFIG_KEYS[name] || [];
    return {
        name,
        modes,
        flags,
        content_formats,
        config_keys,
        file_path: file,
    };
}
/**
 * CLI entry: `apt-tools modes <subcommand> [project-dir]`.
 *
 * Subcommands: `list` (the only v1 subcommand).
 *
 * The trusted-skill-roots discovery follows the same pattern as the
 * router's skill-discover module, but at minimum scans
 * `packages/framework/skills/` relative to the framework checkout. For
 * project-installed skills, the project's `.aperant/deps/.../skills`
 * tree is also scanned when present.
 */
export function cmdModes(subcommand, projectDir, _extraArgs) {
    const cmd = subcommand || 'list';
    if (cmd !== 'list') {
        return ok({
            status: 'error',
            command: 'modes',
            error: `Unknown subcommand: ${cmd}. Use 'list'.`,
        });
    }
    // Project-dir is optional for modes list — defaults to cwd.
    const absProjectDir = projectDir ? resolve(projectDir) : process.cwd();
    // Compute trusted skill roots. The framework's own skills root
    // (relative to this file) is always included; project-installed
    // roots (.aperant/deps/...) are added when present.
    const roots = new Set();
    // Framework checkout root: this file is at
    // .../packages/framework/src/cli/commands/modes.mjs → 5 levels up
    // to the framework root, then skills/.
    const frameworkRoot = resolve(new URL('../../../skills', import.meta.url).pathname);
    if (existsSync(frameworkRoot))
        roots.add(frameworkRoot);
    const projectSkillRoot = join(absProjectDir, 'packages/framework/skills');
    if (existsSync(projectSkillRoot))
        roots.add(projectSkillRoot);
    const installedRoot = join(absProjectDir, '.aperant/deps/node_modules/@aperant/framework/skills');
    if (existsSync(installedRoot))
        roots.add(installedRoot);
    const seen = new Set();
    const skills = [];
    for (const root of roots) {
        for (const file of walkSkillRoot(root)) {
            const record = readSkillForModes(file);
            if (!record)
                continue;
            if (seen.has(record.name))
                continue; // first-writer-wins
            seen.add(record.name);
            skills.push(record);
        }
    }
    skills.sort((a, b) => a.name.localeCompare(b.name));
    return ok({
        status: 'ok',
        command: 'modes-list',
        project_dir: absProjectDir,
        skills,
        scanned_roots: [...roots],
    });
}
//# sourceMappingURL=modes.mjs.map