#!/usr/bin/env node
// scripts/generate-slash-commands.js
//
// Host-Native generator (v1.10.0+).
// Produces files under dist-templates/<host>/commands/openlife/{,agents/,flow/}
// for hosts: claude-code, gemini-cli, codex.
//
// Architecture (v1.10.0):
// LLM-reasoning commands respond DIRECTLY using the host's LLM (Claude in
// Claude Code, Gemini in Gemini CLI, etc.) by reading the OpenLife method
// catalog at `.openlife/method/` for persona/workflow context. They do NOT
// shell out to `openlife <verb>` (which would route to an external Brain
// chain and require API keys).
//
// Runtime-state commands (doctor, status, project-mode get) still shell
// out — they query real local state, not LLM reasoning.
//
// External Brain mode is opt-in via the `--external` flag on the command
// or `OPENLIFE_USE_EXTERNAL_BRAIN=true` env var — useful for headless / CI
// contexts or when the user explicitly wants the configured model chain.
//
// Idempotent: re-running overwrites with the canonical content.

const fs = require('fs');
const path = require('path');

const HOSTS = ['claude-code', 'gemini-cli', 'codex'];
const REPO_ROOT = path.resolve(__dirname, '..');

// ─── Command catalog ────────────────────────────────────────────────────

const TOP_LEVEL = [
  // ── LLM-reasoning: Claude responds directly, no shellout ──
  {
    name: 'ask',
    mode: 'host-native',
    persona: 'lyra',
    description: 'Ask OpenLife — the host LLM answers directly using the OpenLife method context (no external API key required)',
    argumentHint: '<question>',
    instruction: `Answer the user's question directly using your own reasoning. You ARE the OpenLife Brain when invoked through a host CLI.

Optional context (read only when relevant to the question):
- \`.openlife/method/agents/lyra.md\` — research / synthesis persona to adopt when the question is research-shaped
- \`.openlife/project.json\` — current project mode (greenfield/brownfield)
- \`.catalog/\` — user's agents, squads, skills, capabilities
- \`dist-templates/workflows/\` — bundled method workflows

If the question is open-ended or asks for OpenLife status, prefer answering from this conversation's existing context rather than reading files speculatively. If the user explicitly wants the external model chain (e.g., for batch / cron / cost-tracking reasons), they can invoke \`openlife ask "..."\` directly in a terminal or pass \`--external\` to this command.`,
  },
  {
    name: 'doctor',
    mode: 'runtime',
    description: 'Run OpenLife health checks (API keys, model chain, runtime catalogs, daemon state)',
    instruction: 'Run `openlife system doctor` and walk the user through results.\n\nFor each failed check: quote the line, explain in 1 sentence what it verifies, suggest the concrete fix.',
  },
  {
    name: 'status',
    mode: 'runtime',
    description: 'Show OpenLife system status — installed profile, host, catalog counts, daemon state',
    instruction: 'Run `openlife status --json` and present a tight summary of: active profile, host CLIs installed, catalog counts (agents/squads/skills), daemon heartbeat freshness, telegram allowed user.',
  },
  {
    name: 'dream',
    mode: 'host-native',
    persona: 'lyra',
    description: 'Dream Organizer — surface ideas, pending stories, and prioritization from accumulated context (host LLM answers directly)',
    instruction: `Run the OpenLife Dream Organizer using your own reasoning, not an external model.

1. Read recent context from this conversation + any planning files the user has open
2. Optionally read \`.catalog/\` for project-specific ideas already captured
3. Surface unprioritized ideas, pending stories, and recommendations grouped by theme
4. Be opinionated about priority order — don't return a flat list

If the user wants the external model chain for richer ideation: \`openlife dream "..."\` in a terminal.`,
  },
  {
    name: 'start',
    mode: 'host-native',
    description: 'Bootstrap or resume work — reads .openlife/project.json mode and routes to greenfield or brownfield flow',
    instruction: `Detect the project mode and route to the right workflow.

1. Read \`.openlife/project.json\` if it exists. Use its \`mode\` field (\`greenfield\` / \`brownfield\` / \`not-applicable\`).
2. If missing, ask the user via AskUserQuestion: "Is this a brand new project (greenfield) or existing codebase (brownfield)?"
3. Based on the answer, decide and announce the recommended workflow:
   - greenfield + fullstack → run \`/openlife:flow:greenfield-fullstack\`
   - greenfield + service → run \`/openlife:flow:greenfield-service\`
   - greenfield + ui → run \`/openlife:flow:greenfield-ui\`
   - brownfield + first time → run \`/openlife:flow:brownfield-blueprint\`
   - brownfield + already discovered → ask sub-mode (fullstack/service/ui)
4. If mode was just set, persist via \`openlife project-mode set <mode>\`.`,
  },
  {
    name: 'plan',
    mode: 'host-native',
    persona: 'genesis',
    description: 'Run the spec pipeline (gather → assess → research → write → critique → plan) — host LLM orchestrates the 6 phases',
    instruction: `Orchestrate the spec-pipeline workflow YOURSELF, phase by phase. You play multiple personas inline.

1. Read \`dist-templates/workflows/spec-pipeline.yaml\` to understand the phases
2. Phase 1 — Gather: become @openlife-genesis (read \`.openlife/method/agents/genesis.md\`) and elicit requirements from the user via AskUserQuestion
3. Phase 2 — Assess: become @openlife-atlas, score the 5 complexity dimensions
4. Phase 3 — Research (skip if SIMPLE complexity): become @openlife-lyra, surface unknowns
5. Phase 4 — Write: become @openlife-genesis again, draft spec.md
6. Phase 5 — Critique: become @openlife-sentinel, return verdict (APPROVED / NEEDS_REVISION / BLOCKED)
7. Phase 6 — Plan: become @openlife-atlas, generate implementation.yaml

Each phase ends with the user explicitly OK'ing before the next.`,
  },
  {
    name: 'story',
    mode: 'host-native',
    description: 'Create the next story (Conductor) or validate a draft (Steward) — host LLM acts as the right persona',
    instruction: `Determine intent from \`$ARGUMENTS\`:

- If the user wants to CREATE a story → read \`.openlife/method/agents/conductor.md\`, become Conductor, run \`*create-next-story\`. Apply the story template (frontmatter, title, description, AC, scope, deps, complexity, business value, risks, DoD).
- If the user wants to VALIDATE a draft → read \`.openlife/method/agents/steward.md\`, become Steward, run \`*validate-story\` with the 10-point checklist. Verdict: GO (≥7/10) or NO-GO (with required fixes).
- If unclear, ask via AskUserQuestion which action to take.

You play the persona directly — no shellout.`,
  },
  {
    name: 'review',
    mode: 'host-native',
    persona: 'sentinel',
    description: 'Run the QA gate on a story — host LLM acts as Sentinel and applies 7 quality checks',
    instruction: `Read \`.openlife/method/agents/sentinel.md\`, become Sentinel, and run \`*qa-gate\` on the target (story id or file path from \`$ARGUMENTS\`).

Apply the 7 checks: code review, unit tests, AC coverage, no regressions, performance, security (OWASP-grade), documentation.

Return verdict: PASS / CONCERNS / FAIL / WAIVED with structured findings (severity + category + file:line + recommendation per finding).

If verdict is FAIL with HIGH/CRITICAL issues, suggest \`/openlife:agents:builder\` for the fix-loop iteration.`,
  },
  {
    name: 'ship',
    mode: 'host-native',
    persona: 'vortex',
    description: 'Run the continuous-deployment pipeline — host LLM orchestrates Vortex through PR → merge → tag → publish',
    instruction: `Orchestrate the continuous-deployment workflow yourself.

1. Read \`dist-templates/workflows/continuous-deployment.yaml\` to understand the phases
2. Read \`.openlife/method/agents/vortex.md\` and become Vortex for steps that require the EXCLUSIVE git/PR/release authority
3. Walk through: pre-release QA → open PR → wait CI → merge → version bump → tag → publish → release notes → log event
4. EVERY destructive step (push, tag, publish) requires explicit user OK before proceeding — never auto-merge

Vortex is the only persona authorized to push, open/merge PRs, and run releases.`,
  },
  {
    name: 'explore',
    mode: 'host-native',
    persona: 'lyra',
    description: 'Exploratory ideation — host LLM acts as Lyra for divergent brainstorming then convergent narrative',
    instruction: `Read \`.openlife/method/agents/lyra.md\`, become Lyra, and run \`*brainstorm "$ARGUMENTS"\`.

Surface options + tradeoffs. Mark confidence per claim (high / medium / low). Cite sources when claims are non-obvious.

Do NOT commit to a plan in this command — that's \`/openlife:plan\`. Explore is divergent; Plan is convergent.`,
  },
  {
    name: 'audit',
    mode: 'host-native',
    persona: 'genesis',
    description: 'Run brownfield-blueprint — host LLM orchestrates the 10-phase audit of an existing codebase',
    instruction: `Orchestrate the brownfield-blueprint workflow yourself, playing multiple personas inline.

1. Read \`dist-templates/workflows/brownfield-blueprint.yaml\` to understand the 10 phases
2. Walk through:
   - Phase 1 (Atlas): system-architecture.md
   - Phase 2 (Mesh, conditional): SCHEMA.md + DB-AUDIT.md
   - Phase 3 (Prism, conditional): frontend-spec.md
   - Phase 4 (Atlas): technical-debt-DRAFT.md
   - Phase 5 (Mesh): db-specialist-review.md
   - Phase 6 (Prism): ux-specialist-review.md
   - Phase 7 (Sentinel): qa-review.md (gate: APPROVED / NEEDS WORK)
   - Phase 8 (Atlas): technical-debt-assessment.md (final)
   - Phase 9 (Lyra): TECHNICAL-DEBT-REPORT.md (executive)
   - Phase 10 (Genesis): Epic + stories ready for backlog

Each phase ends with the user OK'ing before the next.`,
  },
  {
    name: 'health',
    mode: 'host-native',
    description: 'Extended health check — combines `doctor` runtime check with Maestro\'s framework integrity review',
    instruction: `Two-step health check:

1. Run \`openlife system doctor\` (real shell command) to capture API keys, model chain, catalog state, daemon state
2. Read \`.openlife/method/agents/maestro.md\`, become Maestro, and check cross-agent integrity:
   - Catalog completeness (314 agents / 46 squads / 55 skills present?)
   - Missing handoff artifacts in \`.openlife/handoffs/\`
   - Draft components pending promotion in \`.catalog/{agents,squads,skills}/<id>/\` with \`status: draft\`
   - Any contradictions between \`.openlife/project.json\` mode and active workflow

Present both diagnostics in one consolidated report.`,
  },
];

const AGENTS = [
  { id: 'maestro', desc: 'Master Orchestrator — routes work, manages handoffs, arbitrates cross-agent decisions' },
  { id: 'builder', desc: 'Full-Stack Developer — implements validated stories with self-review' },
  { id: 'sentinel', desc: 'QA / Test Architect — 7-check gate + iterative QA Loop (max 5)' },
  { id: 'steward', desc: 'Product Owner / Story Validator — owns the Draft → Ready gate' },
  { id: 'conductor', desc: 'Scrum Master — drafts stories, manages sprint cadence, runs retros' },
  { id: 'vortex', desc: 'DevOps — EXCLUSIVE authority over git push, gh PR, MCP, releases' },
  { id: 'mesh', desc: 'Data Engineer — DDL, query optimization, RLS policies, migrations' },
  { id: 'prism', desc: 'UX / UI Designer — wireframes, user research, design-system audits' },
  { id: 'atlas', desc: 'System Architect — ADRs, technology selection, impact analysis' },
  { id: 'lyra', desc: 'Analyst — deep research, synthesis, PRDs, ADRs, release notes' },
  { id: 'forge', desc: 'Component Creator — agents, squads, skills, workflows, MCP configs, slash commands' },
  { id: 'genesis', desc: 'Product Manager / Bootstrap — gather requirements, create epics, set project mode' },
];

const FLOWS = [
  { id: 'greenfield-fullstack', desc: 'Greenfield Full-Stack: end-to-end new app from concept to shippable stories' },
  { id: 'greenfield-service', desc: 'Greenfield Service: backend-only / API-only / CLI / worker / MCP server' },
  { id: 'greenfield-ui', desc: 'Greenfield UI: frontend-only / SPA / landing / admin panel / docs site' },
  { id: 'brownfield-blueprint', desc: 'Brownfield Blueprint: 10-phase audit of an existing codebase' },
  { id: 'brownfield-fullstack', desc: 'Brownfield Full-Stack: enhance an existing full-stack app' },
  { id: 'brownfield-service', desc: 'Brownfield Service: modify an existing service / API' },
  { id: 'brownfield-ui', desc: 'Brownfield UI: refactor or enhance an existing UI' },
  { id: 'story-cycle', wfId: 'story-development-cycle', desc: 'Story Development Cycle: 4-phase Create → Validate → Implement → QA' },
  { id: 'qa-loop', desc: 'QA Loop: iterative review-fix cycle (max 5 iterations) between Sentinel and Builder' },
  { id: 'spec-pipeline', desc: 'Spec Pipeline: 6-phase gather → assess → research → write → critique → plan' },
  { id: 'epic', wfId: 'epic-orchestration', desc: 'Epic Orchestration: coordinate multiple dependent stories within an epic' },
  { id: 'release', wfId: 'continuous-deployment', desc: 'Continuous Deployment: release pipeline driven by Vortex (PR → merge → tag → publish)' },
];

// ─── CLI passthrough catalog (v1.19.0+) ──────────────────────────────────
//
// Thin wrappers over the real `openlife <verb>` CLI for operational commands
// that query/mutate live state (not LLM reasoning). They shell out, unlike the
// host-native commands above. The wrapper lets the host LLM consult
// `openlife <verb> --help` at runtime to discover subcommands, so we never
// hardcode the subcommand tree here — descriptions only.
//
// Excluded on purpose: commands already covered by TOP_LEVEL
// (ask, doctor, dream, start, status, story, ship) and meta verbs (help, index).
//
// Flags:
//   destructive: true → wrapper requires explicit user confirmation first
//   interactive: true → REPL/TTY command; wrapper tells the user to run it in a
//                       terminal instead of shelling out (would hang on input)

const CLI_PASSTHROUGH = [
  { name: 'goal', description: 'Set or query the current OpenLife mission (/goal)', argumentHint: '[text]' },
  { name: 'install', description: 'Unified onboarding — install the CLI or the Autonomous Agent', destructive: true },
  { name: 'init', description: 'Interactive install wizard (phase-based, sequential, multi-host)', interactive: true },
  { name: 'logs', description: 'Readable tail of .openlife/*.jsonl event streams', argumentHint: '[options]' },
  { name: 'auth', description: 'Manage native authentication (no API keys)' },
  { name: 'models', description: 'Intelligent model lifecycle & routing' },
  { name: 'phase1-check', description: 'Run the minimal Phase 1 checks for the main runtime' },
  { name: 'system', description: 'OpenLife install, bootstrap & status' },
  { name: 'memory', description: 'OpenLife memory operations' },
  { name: 'swarm', description: 'Swarm & consensus operations' },
  { name: 'governance', description: 'Governance & risk operations' },
  { name: 'benchmark', description: 'OpenLife benchmark operations' },
  { name: 'agents', description: 'Agent operations (list/create/update/validate/run/check)' },
  { name: 'squads', description: 'Squad operations (list/create/validate/run)' },
  { name: 'skills', description: 'Skill operations (list/create/validate/run/learn)' },
  { name: 'route', description: 'Intent routing to squads, agents and skills', argumentHint: '[intent]' },
  { name: 'mcp', description: 'MCP operations (list/search/install/remove)' },
  { name: 'catalog', description: 'Runtime catalog diagnostics' },
  { name: 'teams', description: 'Domain agent teams administration' },
  { name: 'networks', description: 'Domain skill networks administration' },
  { name: 'media', description: 'Media routing & telemetry (vision/stt/tts)' },
  { name: 'orchestration', description: 'Orchestration contract & snapshot' },
  { name: 'restart', description: 'Controlled restart of the OpenLife daemon', destructive: true },
  { name: 'update', description: 'Update deps, rebuild the core & validate status (dev mode)', destructive: true },
  { name: 'up', description: 'One-shot boot — quick doctor + start the agent daemon', destructive: true },
  { name: 'agent', description: 'Autonomous agent lifecycle (install/start/status)', argumentHint: '<verb>', destructive: true },
  { name: 'smoke', description: 'Formal production/local smoke checks' },
  { name: 'service', description: 'Service operations (mode service)' },
  { name: 'task', description: 'Task operations (mode task)' },
  { name: 'job', description: 'Autonomous job lifecycle' },
  { name: 'runtime', description: 'Unified runtime registry' },
  { name: 'reversa', description: 'Reversa-lite reverse-spec pipeline' },
  { name: 'sources', description: 'External sources catalog (skills/squads/agents/mcps)' },
  { name: 'teammate', description: 'Agents-as-teammates board' },
  { name: 'learning', description: 'Governed skill learning loop' },
  { name: 'context', description: 'OpenLife institutional context' },
  { name: 'plugin', description: 'Internal governed plugin/skill marketplace' },
  { name: 'pilot', description: 'Measurable pilots for OpenLife services/capabilities' },
  { name: 'eval', description: 'Internal LLM-as-judge evaluation for missions' },
  { name: 'learn', description: 'Feedback loop turning human review into evolution' },
  { name: 'enterprise', description: 'Aggregated enterprise-agentic core diagnostics' },
  { name: 'license', description: 'Activate & check OpenLife Business license (Pro/Team/Enterprise)', argumentHint: '[activate|status|deactivate] [args]' },
  { name: 'designmd', description: 'DESIGN.md mode manager' },
  { name: 'workflow', description: 'Workflow engine (list/validate/run/status/resume/abort)' },
  { name: 'mission', description: 'Inspect mission state & checkpoints' },
  { name: 'aiobuilder', description: 'AIOBUILDER operations' },
  { name: 'create', description: 'Guided creator surface (forwards to aiobuilder)' },
  { name: 'cron', description: 'CronManager — schedule recurring missions' },
  { name: 'profile', description: 'ProfileManager — catalog scope, memory namespace, toolsets' },
  { name: 'telegram', description: 'Manage Telegram delivery mode (polling vs webhook)' },
  { name: 'config', description: 'Interactive configuration (provider, API keys, Telegram, voice, daemon)', interactive: true },
  { name: 'chat', description: 'Start the interactive terminal chat (Matrix-themed REPL)', interactive: true },
  { name: 'flow', description: 'OpenLife method workflows (list/run/dry-run/resume)' },
  { name: 'project-mode', description: 'Greenfield / brownfield mode for .openlife/project.json' },
  { name: 'bridge', description: 'Core ↔ Agent bridge (discover daemon, push/pull, tail events, search memory)' },
  { name: 'graph', description: 'Codebase graph — symbols, dependencies, callers' },
  { name: 'provider', description: 'Per-CLI tier router, zero-config first-run wizard' },
  { name: 'design', description: 'Design.md Hub — brand design profiles (pull/list/show/extract/search/diff/drift)' },
  { name: 'scaffold', description: 'Scaffold a SaaS project from a bundled or user-defined template' },
  { name: 'deploy', description: 'Deploy to a provider (Vercel, Railway) — auto-detected', destructive: true },
];

// ─── File renderers ─────────────────────────────────────────────────────

function renderTopLevel(cmd) {
  const hintLine = cmd.argumentHint ? `argument-hint: "${cmd.argumentHint}"\n` : '';

  // Runtime-state commands shell out (real diagnostics, not LLM reasoning).
  if (cmd.mode === 'runtime') {
    return `---
description: ${cmd.description}
${hintLine}allowed-tools:
  - Read
  - Bash(openlife:*)
  - Grep
  - Glob
  - Agent
  - AskUserQuestion
---

${cmd.instruction}
`;
  }

  // Host-native LLM-reasoning commands respond directly using the host LLM.
  return `---
description: ${cmd.description}
${hintLine}allowed-tools:
  - Read
  - Write
  - Edit
  - Bash(openlife:*)
  - Grep
  - Glob
  - Agent
  - AskUserQuestion
---

**Mode:** host-native (the host LLM running this conversation answers directly — no external API key required).

${cmd.instruction}

---

### Want the external model chain instead?

If you specifically need the configured \`models.json\` chain (for cost-tracking, batch, or non-host contexts), run the command in a terminal:

\`\`\`bash
openlife ${cmd.name} "$ARGUMENTS"
\`\`\`

That path uses the Brain dispatcher and requires the appropriate API key in \`.env\`. The slash command (this one) intentionally bypasses external APIs to keep host-CLI usage zero-config.
`;
}

function renderCliPassthrough(cmd) {
  const hint = cmd.argumentHint || '[subcommand] [args]';

  // Interactive REPL/TTY commands can't be shelled out from a slash command —
  // it would hang waiting for stdin. Point the user at the terminal instead.
  if (cmd.interactive) {
    return `---
description: ${cmd.description}
argument-hint: "${hint}"
allowed-tools:
  - Read
  - Bash(openlife:*)
  - Grep
  - Glob
  - Agent
  - AskUserQuestion
---

**Interactive command.** \`openlife ${cmd.name}\` opens an interactive terminal session (REPL/wizard), so it can't run inside this slash command — it would hang waiting for input.

Tell the user to run it directly in their terminal:

\`\`\`bash
openlife ${cmd.name}
\`\`\`

If they pass non-interactive flags in \`$ARGUMENTS\` (e.g. \`--help\` or a one-shot flag), you MAY run \`openlife ${cmd.name} $ARGUMENTS\` and present the output — but never start the bare interactive session from here.
`;
  }

  const confirmLine = cmd.destructive
    ? '\n> **Destructive command.** Confirm the exact action with the user before running it — this can install, restart, redeploy, or rebuild the system.\n'
    : '';

  return `---
description: ${cmd.description}
argument-hint: "${hint}"
allowed-tools:
  - Read
  - Bash(openlife:*)
  - Grep
  - Glob
  - Agent
  - AskUserQuestion
---

Run the OpenLife \`${cmd.name}\` command via the CLI.
${confirmLine}
1. If \`$ARGUMENTS\` is empty and this command has subcommands, run \`openlife ${cmd.name} --help\` first, present the available subcommands to the user, and ask which to run (AskUserQuestion).
2. Otherwise run \`openlife ${cmd.name} $ARGUMENTS\` and present the result clearly (summarize JSON output; quote key lines).
3. On error, quote the message and suggest \`openlife doctor\` to diagnose.
`;
}

function renderAgent(agent) {
  return `---
description: Activate ${capitalize(agent.id)} — ${agent.desc}
argument-hint: "[optional context]"
allowed-tools:
  - Read
  - Write
  - Edit
  - Bash(openlife:*)
  - Grep
  - Glob
  - Agent
  - AskUserQuestion
---

**Mode:** host-native — you (the host LLM) become **${capitalize(agent.id)}** for this conversation.

Activation:

1. Read \`.openlife/method/agents/${agent.id}.md\` in full.
2. Adopt the persona and execute the activation flow defined there:
   - Display the persona greeting (icon + 1-line role)
   - Show the top 5 commands
   - HALT and await \`*command\` from the user
3. If \`$ARGUMENTS\` is non-empty, treat it as an initial task or context for the persona.

After the user's first \`*command\`, follow the persona's hand-off rules to suggest the next persona when appropriate.

This activation does NOT use any external model — you ARE the agent. The OpenLife Brain (external model chain) is reserved for headless / CI usage via \`openlife ask\` in a terminal.
`;
}

function renderFlow(flow) {
  const wfId = flow.wfId || flow.id;
  return `---
description: ${flow.desc}
argument-hint: "[--dry-run] [--vars key=value]"
allowed-tools:
  - Read
  - Bash(openlife:*)
  - Grep
  - Glob
  - Agent
  - AskUserQuestion
---

**Mode:** host-native — you (the host LLM) orchestrate the \`${wfId}\` workflow yourself, playing the personas as needed.

### Step 1 — Load the workflow definition

Read \`dist-templates/workflows/${wfId}.yaml\` (project local override at \`.openlife/method/workflows/${wfId}.yaml\` or \`.catalog/workflows/${wfId}.yaml\` takes precedence if present).

If \`$ARGUMENTS\` contains \`--dry-run\`, ALSO shell out to \`openlife flow run ${wfId} --dry-run\` to print the phase plan, then STOP.

### Step 2 — Walk the phases

For each phase in the workflow's \`sequence\`:

1. Announce which phase you're entering and which persona owns this phase (the \`agent:\` field on the step)
2. Read \`.openlife/method/agents/<persona-id>.md\` for the active persona — adopt that persona for this phase
3. Execute the step's \`action\` using the persona's discipline (commands, hand-off rules, anti-patterns)
4. Produce the artifacts listed in \`creates:\` — write them to the paths declared in the YAML
5. If \`elicit: true\`, ask the user via AskUserQuestion before proceeding
6. Hand off to the next phase's persona; announce the handoff

### Step 3 — Surface failures explicitly

On any step failure: identify the failing step, the active persona, and offer to escalate to \`@openlife-maestro\` (read \`.openlife/method/agents/maestro.md\`).

### Why host-native, not shell?

Running the workflow this way uses your reasoning (the host LLM) instead of the external Brain chain. Zero API key needed for orchestration. If the user wants the external Brain to drive (headless / batch / cron), they can run \`openlife flow run ${wfId} "$ARGUMENTS"\` in a terminal.
`;
}

function capitalize(s) {
  return s[0].toUpperCase() + s.slice(1);
}

// ─── Emit ───────────────────────────────────────────────────────────────

let total = 0;
let hostNative = 0;
let runtime = 0;
let passthrough = 0;

for (const host of HOSTS) {
  const baseDir = path.join(REPO_ROOT, 'dist-templates', host, 'commands', 'openlife');
  fs.mkdirSync(path.join(baseDir, 'agents'), { recursive: true });
  fs.mkdirSync(path.join(baseDir, 'flow'), { recursive: true });

  for (const cmd of TOP_LEVEL) {
    const fp = path.join(baseDir, `${cmd.name}.md`);
    fs.writeFileSync(fp, renderTopLevel(cmd));
    total++;
    if (cmd.mode === 'host-native') hostNative++;
    else if (cmd.mode === 'runtime') runtime++;
  }
  for (const cmd of CLI_PASSTHROUGH) {
    const fp = path.join(baseDir, `${cmd.name}.md`);
    fs.writeFileSync(fp, renderCliPassthrough(cmd));
    total++;
    passthrough++;
  }
  for (const agent of AGENTS) {
    const fp = path.join(baseDir, 'agents', `${agent.id}.md`);
    fs.writeFileSync(fp, renderAgent(agent));
    total++;
    hostNative++;
  }
  for (const flow of FLOWS) {
    const fp = path.join(baseDir, 'flow', `${flow.id}.md`);
    fs.writeFileSync(fp, renderFlow(flow));
    total++;
    hostNative++;
  }
}

const perHost = TOP_LEVEL.length + CLI_PASSTHROUGH.length + AGENTS.length + FLOWS.length;
console.log(`[generate-slash-commands] wrote ${total} files across ${HOSTS.length} hosts.`);
console.log(`Breakdown per host: ${TOP_LEVEL.length} top-level + ${CLI_PASSTHROUGH.length} cli-passthrough + ${AGENTS.length} agents + ${FLOWS.length} flows = ${perHost}`);
console.log(`Mode split: ${hostNative} host-native + ${runtime} runtime-state + ${passthrough} cli-passthrough (× ${HOSTS.length} hosts)`);
