function generateCommand(context) {
  const { command } = context;
  if (!command) {
    throw new Error("Command is required in template context");
  }
  const name = command.name;
  const description = command.description || "";
  const args = command.arguments || {};
  const flags = command.flags || {};
  const exitCodes = command.exitCodes || {};
  const subcommands = command.subcommands || {};
  const serviceRef = command.serviceRef;
  const positionalArgs = Object.entries(args).filter(([_, arg]) => arg.positional).sort((a, b) => (a[1].position || 0) - (b[1].position || 0)).map(([argName, arg]) => {
    const required = arg.required;
    return required ? `<${argName}>` : `[${argName}]`;
  }).join(" ");
  const commandStr = positionalArgs ? `${name} ${positionalArgs}` : name;
  const optionDefs = Object.entries(flags).map(([flagName, flag]) => {
    const alias = flag.alias ? `${flag.alias}, ` : "";
    const flagType = flag.type?.toLowerCase();
    const isDefaultTrueBoolean = flagType === "boolean" && flag.default === true;
    const emitName = isDefaultTrueBoolean ? flagName.replace(/^--/, "--no-") : flagName;
    const valuePart = flagType === "boolean" ? "" : ` <${flagName.replace(/^--/, "")}>`;
    const defaultVal = flag.default !== void 0 && !isDefaultTrueBoolean ? `, ${commanderDefault(flag.default, flagType)}` : "";
    const desc = escapeSingleQuotes(flag.description || `${flagName} option`);
    return `    .option('${alias}${emitName}${valuePart}', '${desc}'${defaultVal})`;
  });
  const optionTypes = Object.entries(flags).map(([flagName, flag]) => {
    const tsType = mapFlagTypeToTS(flag.type);
    const key = flagName.replace(/^--/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
    return `  ${key}${flag.required ? "" : "?"}: ${tsType};`;
  });
  const argTypes = Object.entries(args).filter(([_, arg]) => arg.positional).map(([argName, arg]) => {
    const tsType = mapArgTypeToTS(arg.type);
    return `${argName}: ${tsType}`;
  });
  const actionParams = argTypes.length > 0 ? argTypes.join(", ") + ", options: CommandOptions" : "options: CommandOptions";
  const exitCodeComments = Object.entries(exitCodes).length > 0 ? Object.entries(exitCodes).map(
    ([code, meaning]) => `  //   ${code}: ${meaning}`
  ).join("\n") : "";
  const hasSubcommands = Object.keys(subcommands).length > 0;
  const subcommandRegistrations = hasSubcommands ? generateSubcommandRegistrations(name, subcommands) : "";
  const serviceImport = serviceRef ? `import { ${serviceRef} } from '../services/${serviceRef}.js';` : "";
  const allImportSets = [];
  if (ENGINE_HANDLERS[name]?.imports) allImportSets.push(ENGINE_HANDLERS[name].imports);
  if (hasSubcommands) {
    for (const subName of Object.keys(subcommands)) {
      const key = `${name}.${subName}`;
      if (ENGINE_HANDLERS[key]?.imports) allImportSets.push(ENGINE_HANDLERS[key].imports);
    }
  }
  const registerBody = hasSubcommands ? generateCommandWithSubcommands(name, description, subcommands, optionDefs) : generateLeafCommand(name, description, commandStr, optionDefs, actionParams, serviceRef, exitCodes);
  const bodyForUsage = `${serviceImport}
${registerBody}
${subcommandRegistrations}`;
  const commandOptionsUsed = new RegExp("\\bCommandOptions\\b").test(bodyForUsage);
  const engineImports = deduplicateImports(allImportSets, bodyForUsage);
  const commandOptionsDecl = commandOptionsUsed ? `interface CommandOptions {
${optionTypes.length > 0 ? optionTypes.join("\n") : "  [key: string]: any;"}
}
` : "";
  return `/**
 * ${name} command
 * ${description}
 * Generated from SpecVerse specification
 */

import { Command } from 'commander';
${serviceImport}
${engineImports}

${commandOptionsDecl}
${exitCodeComments ? `/**
 * Exit codes:
${exitCodeComments}
 */` : ""}

/**
 * Register the ${name} command on the program.
 */
export function register${capitalize(name)}Command(program: Command): void {
  ${registerBody}
}
${subcommandRegistrations}
`;
}
const ENGINE_HANDLERS = {
  validate: {
    imports: `import { readFileSync, existsSync } from 'fs';
import { EngineRegistry } from '@specverse/entities';
import { summarizeSpecForCli } from '@specverse/engines/parser';
import type { ParserEngine } from '@specverse/types';`,
    handler: `if (!existsSync(file)) {
          console.error('File not found:', file);
          process.exit(1);
        }

        // Discover and initialize parser engine
        const registry = new EngineRegistry();
        await registry.discover();
        const parser = registry.getEngineForCapability('parse') as ParserEngine;
        if (!parser) {
          console.error('No parser engine found. Install @specverse/engines.');
          process.exit(1);
        }
        await parser.initialize();

        const content = readFileSync(file, 'utf8');
        const result = parser.parseContent(content, file);

        if (result.errors.length > 0) {
          console.error('Validation failed');
          result.errors.forEach((e: string) => console.error(' ', e));
          if (result.warnings && result.warnings.length > 0) {
            console.warn('Warnings:');
            result.warnings.forEach((w: string) => console.warn(' ', w));
          }
          process.exit(1);
        }

        // Lifecycle + cross-model soundness check (TODO #31). Runs as
        // part of normal validate \u2014 every spec gets these deterministic
        // gates regardless of flags. Catches lifecycle typos
        // (transition references unknown state), behavior-requires /
        // ensures referencing unknown states, and relationship targets
        // pointing at undeclared models. Violations are validation
        // errors and abort with exit 1, just like schema errors.
        try {
          const { generateQuintFromSpec } = await import('@specverse/engines/inference/quint-gen');
          const quintGen = generateQuintFromSpec(result.ast);
          if (quintGen.violations.length > 0) {
            if (options.json) {
              console.log(JSON.stringify({ valid: false, violations: quintGen.violations }, null, 2));
            } else {
              console.error('Validation failed');
              for (const v of quintGen.violations) {
                console.error('  [' + v.kind + '] ' + v.message);
              }
            }
            process.exit(1);
          }
        } catch (qe: any) {
          // Don't let quint-gen module-loading issues block normal
          // validate (e.g. running against a much older engines version).
          if (process.env.SPECVERSE_VERBOSE === '2') {
            console.warn('quint-gen check skipped:', qe?.message ?? String(qe));
          }
        }

        if (options.json) {
          // 'useful' level: curated parse facts (name/version/counts +
          // imports/exports for scope + deployment/manifest names). '--full'
          // additionally attaches the complete parsed AST.
          const spec = summarizeSpecForCli(result.ast, { specVersion: result.specVersion, specName: result.specName });
          const jsonOut = options.full
            ? { valid: true, warnings: result.warnings, spec, ast: result.ast }
            : { valid: true, warnings: result.warnings, spec };
          console.log(JSON.stringify(jsonOut, null, 2));
        } else {
          console.log('Validation successful');
          if (result.warnings && result.warnings.length > 0) {
            console.warn('Warnings:');
            result.warnings.forEach((w: string) => console.warn(' ', w));
          }
        }

        // L3 verification \u2014 opt-in via --verify flag. Runs the
        // transpiled Quint guards against the parsed spec. Guards
        // whose state-var dependencies aren't present in this spec
        // are reported as skipped (not failed).
        if (options.verify) {
          try {
            const { verifySpec, formatVerificationResult } = await import('@specverse/engines/inference');
            const verification = await verifySpec(result.ast);
            if (options.json) {
              console.log(JSON.stringify({ valid: true, verification }, null, 2));
            } else {
              console.log('');
              console.log(formatVerificationResult(verification, options.verbose));
            }
            if (verification.failed.length > 0) process.exit(1);
          } catch (ve: any) {
            console.error('Verification error:', ve.message);
            process.exit(1);
          }
        }`
  },
  "validate-bundle": {
    imports: `import { existsSync, statSync } from 'fs';
import { resolve } from 'path';`,
    handler: `const target = resolve(path);
        if (!existsSync(target)) {
          console.error('Path not found:', target);
          process.exit(1);
        }
        if (!statSync(target).isDirectory()) {
          console.error('Expected a bundle directory, got a file:', target);
          process.exit(1);
        }

        const { validateBundle } = await import('@specverse/engines/bundles');
        const report = await validateBundle(target);

        if (options.json) {
          console.log(JSON.stringify(report, null, 2));
          if (report.totals.fail > 0) process.exit(1);
          return;
        }

        const statusSymbol = (s: 'pass' | 'fail' | 'skip'): string => {
          if (s === 'pass') return '\u2713';
          if (s === 'fail') return '\u2717';
          return '\u2013';
        };

        console.log('Bundle: ' + report.bundle + '  (' + report.path + ')');
        console.log('');
        for (const facet of report.facets) {
          const facetLine = '  ' + statusSymbol(facet.status) + ' ' + facet.facet.padEnd(10) + facet.status + '  (' + facet.duration + 'ms)';
          console.log(facetLine);
          for (const issue of facet.issues) {
            const loc = issue.location ? ' [' + issue.location + ']' : '';
            console.log('      - ' + issue.severity + ': ' + issue.message + loc);
          }
        }
        console.log('');
        console.log('Totals: ' + report.totals.pass + ' pass, ' + report.totals.fail + ' fail, ' + report.totals.skip + ' skip  (' + report.duration + 'ms)');

        if (report.totals.fail > 0) process.exit(1);`
  },
  infer: {
    imports: `import { readFileSync, writeFileSync, existsSync } from 'fs';
import { EngineRegistry } from '@specverse/entities';
import type { ParserEngine, InferenceEngine } from '@specverse/types';`,
    handler: `if (!existsSync(file)) {
          console.error('File not found:', file);
          process.exit(1);
        }

        console.log('Running inference on', file, '...');

        // Discover engines
        const registry = new EngineRegistry();
        await registry.discover();

        const parser = registry.getEngineForCapability('parse') as ParserEngine;
        if (!parser) { console.error('No parser engine found.'); process.exit(1); }
        await parser.initialize();

        const inferEngine = registry.getEngineForCapability('infer') as InferenceEngine;
        if (!inferEngine) { console.error('No inference engine found.'); process.exit(1); }
        await inferEngine.initialize({ options: { verbose: options.verbose } });

        const content = readFileSync(file, 'utf8');
        const parseResult = parser.parseContent(content, file);

        if (parseResult.errors.length > 0) {
          console.error('Cannot infer from invalid specification:');
          parseResult.errors.forEach((e: string) => console.error(' ', e));
          process.exit(1);
        }

        const ast = parseResult.ast!;
        const inferResult = await inferEngine.infer(ast, {
          generateControllers: true,
          generateServices: true,
          generateEvents: true,
          generateViews: true,
          generateDeployment: options.deployment || false,
          verbose: options.verbose || false,
        });

        const outputFile = options.output || file.replace(/\\.specly$/, '-inferred.specly');
        writeFileSync(outputFile, inferResult.yaml, 'utf8');
        console.log('Inferred specification written to:', outputFile);`
  },
  realize: {
    imports: `import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { resolve, join, dirname } from 'path';
import { EngineRegistry } from '@specverse/entities';
import type { ParserEngine, InferenceEngine, RealizeEngine } from '@specverse/types';`,
    handler: `if (!existsSync(file)) {
          console.error('File not found:', file);
          process.exit(1);
        }

        // Discover engines
        const registry = new EngineRegistry();
        await registry.discover();

        // Parse \u2014 let the parser engine handle its own schema
        const parser = registry.getEngineForCapability('parse') as ParserEngine;
        if (!parser) { console.error('No parser engine found.'); process.exit(1); }
        await parser.initialize();

        const content = readFileSync(file, 'utf8');
        const parseResult = parser.parseContent(content, file);
        if (parseResult.errors.length > 0) {
          console.error('Invalid spec:');
          parseResult.errors.forEach((e: string) => console.error(' ', e));
          process.exit(1);
        }

        // Lifecycle + cross-model soundness check (TODO #31). Same gate
        // that runs in spv validate \u2014 abort realize before code generation
        // if the spec has lifecycle inconsistencies or dangling
        // relationship targets. R8 spirit: don't generate code from
        // unsound input.
        try {
          const { generateQuintFromSpec } = await import('@specverse/engines/inference/quint-gen');
          const quintGen = generateQuintFromSpec(parseResult.ast!);
          if (quintGen.violations.length > 0) {
            console.error('Cannot realize \u2014 spec has soundness violations:');
            for (const v of quintGen.violations) {
              console.error('  [' + v.kind + '] ' + v.message);
            }
            console.error('Run \`spv validate ' + file + '\` to inspect; fix the spec before realize.');
            process.exit(1);
          }
        } catch (qe: any) {
          if (process.env.SPECVERSE_VERBOSE === '2') {
            console.warn('quint-gen check skipped:', qe?.message ?? String(qe));
          }
        }

        // Infer \u2014 let the inference engine handle its own rules
        const inferEngine = registry.getEngineForCapability('infer') as InferenceEngine;
        if (!inferEngine) { console.error('No inference engine found.'); process.exit(1); }
        await inferEngine.initialize();
        const inferResult = await inferEngine.infer(parseResult.ast!, {
          generateControllers: true, generateServices: true,
          generateEvents: true, generateViews: true,
        });

        // Load inferred YAML and flatten component data for realize
        const yaml = await import('js-yaml');
        const inferredYaml = yaml.load(inferResult.yaml) as any;
        // Inference output: { components: { Name: { models, controllers, ... } } }
        // realizeAll expects: { models: {...}, controllers: {...}, ... } (flat).
        //
        // Multi-component fan-out (TODO #49): for specs with multiple components
        // (e.g. idle-meta with 8 domain components, cal-com with O(10)), MERGE all
        // components into a single flat componentData rather than picking only the
        // first. Map sections (models/controllers/services/events/views/primitives/
        // enums) merge by entity name \u2014 name collisions across components log a
        // warning and the LAST occurrence wins (matches Object.assign / spread
        // semantics; expected to be rare since the analyse prompt encourages
        // distinct names per component).
        const allComponents = inferredYaml?.components || {};
        const componentNames = Object.keys(allComponents);
        const MAP_SECTIONS = ['models', 'controllers', 'services', 'events', 'views', 'primitives', 'enums', 'lifecycles'];
        const componentData: Record<string, any> = {};
        // Carry forward non-map metadata (version / description / name / componentName)
        // from the first component \u2014 realize uses these to label the generated bundle.
        if (componentNames.length > 0) {
          const first = allComponents[componentNames[0]];
          for (const k of ['version', 'description', 'name', 'componentName', 'commonDefinitions']) {
            if (first[k] != null) componentData[k] = first[k];
          }
        }
        for (const compName of componentNames) {
          const comp = allComponents[compName];
          for (const section of MAP_SECTIONS) {
            const sectionData = comp[section];
            if (!sectionData || typeof sectionData !== 'object') continue;
            componentData[section] = componentData[section] || {};
            for (const [entryKey, entryVal] of Object.entries(sectionData)) {
              if (componentData[section][entryKey] != null) {
                console.warn('[realize] name collision: ' + section + '.' + entryKey + ' declared in multiple components; last occurrence (' + compName + ') wins');
              }
              componentData[section][entryKey] = entryVal;
            }
          }
        }

        // Merge original spec's services/events/views that inference didn't generate
        // (inference generates from models; explicit services in the spec are preserved here).
        // Iterate ALL original components, not just the first, to mirror the
        // multi-component fan-out above.
        const origComponentsRaw = parseResult.ast!.components || [];
        const origComponents = Array.isArray(origComponentsRaw)
          ? origComponentsRaw
          : Object.values(origComponentsRaw);
        // Iterate ALL original components (TODO #49 \u2014 multi-component fan-out).
        // For each, merge any user-declared services/events/views that inference
        // didn't generate, plus per-entry-merge user-declared controller actions
        // into the inferred ones (TODO #48 \u2014 preserve inferred steps that orig
        // didn't override). Earlier behaviour processed only the first component;
        // for multi-component specs (idle-meta, cal-com) the rest were dropped.
        const inferredControllers = componentData.controllers || {};
        for (const origComp of origComponents) {
          for (const section of ['services', 'events', 'views']) {
            const origData = (origComp as any)[section];
            if (!origData) continue;
            // Only merge if the section is empty in componentData \u2014 preserves the
            // original "inference-generated wins on collision" semantic for the
            // sections inference produces (events/views).
            const existing = componentData[section];
            const isEmpty = !existing || (typeof existing === 'object' && Object.keys(existing).length === 0);
            if (!isEmpty) continue;
            if (Array.isArray(origData)) {
              componentData[section] = {};
              for (const item of origData) componentData[section][item.name] = item;
            } else {
              componentData[section] = origData;
            }
          }

          const origControllers = Array.isArray((origComp as any).controllers)
            ? (origComp as any).controllers
            : Object.values((origComp as any).controllers || {});
          for (const origCtrl of origControllers) {
            const ctrlName = (origCtrl as any).name;
            if (!ctrlName || !(origCtrl as any).actions) continue;
            const inferredCtrl = inferredControllers[ctrlName];
            if (!inferredCtrl) continue;
            const merged: Record<string, any> = { ...(inferredCtrl.actions || {}) };
            for (const [actName, origAct] of Object.entries((origCtrl as any).actions) as [string, any][]) {
              const inferredAct = merged[actName] || {};
              const finalAct: Record<string, any> = { ...inferredAct };
              for (const [fieldKey, fieldVal] of Object.entries(origAct)) {
                const isEmptyArr = Array.isArray(fieldVal) && fieldVal.length === 0;
                const isEmptyObj = fieldVal && typeof fieldVal === 'object'
                  && !Array.isArray(fieldVal) && Object.keys(fieldVal).length === 0;
                if (fieldVal == null || isEmptyArr || isEmptyObj) continue;
                finalAct[fieldKey] = fieldVal;
              }
              merged[actName] = finalAct;
            }
            inferredCtrl.actions = merged;
          }
        }

        // Merge service operation steps from original spec into inferred services.
        // Inference generates service shells with preconditions/returns but drops
        // the declarative steps arrays \u2014 we restore them from the parsed AST.
        // Iterates ALL original components (TODO #49) so multi-component specs
        // don't lose service.operations.steps from the non-first components.
        const inferredServices = componentData.services || {};
        for (const origComp of origComponents) {
          const origServices = Array.isArray((origComp as any).services)
            ? (origComp as any).services
            : Object.values((origComp as any).services || {});
          for (const origSvc of origServices) {
            const svcName = (origSvc as any).name;
            if (!svcName) continue;
            const inferredSvc = inferredServices[svcName];
            if (!inferredSvc) continue;

            const origOps = (origSvc as any).operations || {};
            const inferredOps = inferredSvc.operations || {};
            for (const [opName, origOp] of Object.entries(origOps) as [string, any][]) {
              if (inferredOps[opName] && origOp.steps) {
                inferredOps[opName].steps = origOp.steps;
              }
            }
          }
        }

        // Inject key as name into entity maps, and expand convention-format attributes
        // Iterate all object-valued sections (not hardcoded \u2014 covers any entity type)
        const entitySections = Object.keys(componentData).filter(k =>
          componentData[k] && typeof componentData[k] === 'object' && !Array.isArray(componentData[k])
          && !['version', 'description', 'name', 'componentName', 'commonDefinitions'].includes(k)
        );
        for (const section of entitySections) {
          if (componentData[section] && typeof componentData[section] === 'object' && !Array.isArray(componentData[section])) {
            for (const [key, value] of Object.entries(componentData[section])) {
              if (value && typeof value === 'object') {
                (value as any).name = key;
                // Expand convention-format attributes: { attrName: "Type modifiers" } \u2192 [{ name, type, ... }]
                if ((value as any).attributes && typeof (value as any).attributes === 'object' && !Array.isArray((value as any).attributes)) {
                  (value as any).attributes = Object.entries((value as any).attributes).map(([attrName, attrDef]: [string, any]) => {
                    if (typeof attrDef === 'string') {
                      const parts = attrDef.split(' ');
                      const kv = (key: string) => parts.find((p: string) => p.startsWith(key + '='))?.split('=')[1];
                      const auto = kv('auto');
                      const explicitCategory = kv('category');
                      const category = explicitCategory || (auto || attrName === 'id' ? 'metadata' : 'business');
                      return { name: attrName, type: parts[0], required: parts.includes('required'), unique: parts.includes('unique'), auto, category };
                    }
                    return { name: attrName, ...(typeof attrDef === 'object' ? attrDef : {}) };
                  });
                }
                // Expand convention-format relationships: { relName: "type Target modifiers" } \u2192 array
                if ((value as any).relationships && typeof (value as any).relationships === 'object' && !Array.isArray((value as any).relationships)) {
                  (value as any).relationships = Object.entries((value as any).relationships).map(([relName, relDef]: [string, any]) => {
                    if (typeof relDef === 'object') return { name: relName, ...relDef };
                    if (typeof relDef === 'string') {
                      const parts = relDef.split(' ');
                      return { name: relName, type: parts[0], target: parts[1], cascade: parts.includes('cascade') };
                    }
                    return { name: relName };
                  });
                }
              }
            }
          }
        }
        // First component's name is used to label the realized bundle (matches
        // the prior single-component behaviour). For multi-component specs the
        // remaining components are still merged into componentData (TODO #49).
        const componentName = componentNames[0];
        const inferredSpec = { ...componentData, componentName, components: inferredYaml?.components || {} };

        // --estimate: report what realize WOULD do, broken down by layer.
        // Skip the actual realizeAll call (no manifest needed, no LLM cost).
        // The three layers:
        //   L1 \u2014 Instance factory (templates, no LLM)
        //   L2 \u2014 Convention pattern matching (CURVED ops + default events, no LLM)
        //   L3 \u2014 AI from steps (one LLM call per declared step)
        if (options.estimate) {
          const components = inferredSpec.components || {};
          const compNames = Object.keys(components);
          let entityCount = 0, controllerCount = 0, serviceCount = 0, eventCount = 0, viewCount = 0;
          let modelBehaviorSteps = 0, controllerActionSteps = 0, serviceOpSteps = 0;
          let modelBehaviorsWithSteps = 0, controllerActionsWithSteps = 0, serviceOpsWithSteps = 0;
          let curvedOpCount = 0;
          const sumSteps = (block: any) => {
            if (!block || typeof block !== 'object') return { ops: 0, opsWithSteps: 0, totalSteps: 0 };
            let ops = 0, opsWithSteps = 0, totalSteps = 0;
            for (const def of Object.values(block)) {
              ops++;
              const steps = (def as any)?.steps;
              if (Array.isArray(steps) && steps.length > 0) {
                opsWithSteps++;
                totalSteps += steps.length;
              }
            }
            return { ops, opsWithSteps, totalSteps };
          };
          for (const compName of compNames) {
            const comp = components[compName] || {};
            const models = comp.models || {};
            const controllers = comp.controllers || {};
            const services = comp.services || {};
            const events = comp.events || {};
            const views = comp.views || {};
            entityCount += Object.keys(models).length;
            controllerCount += Object.keys(controllers).length;
            serviceCount += Object.keys(services).length;
            eventCount += Object.keys(events).length;
            viewCount += Object.keys(views).length;
            for (const m of Object.values(models)) {
              const r = sumSteps((m as any)?.behaviors);
              modelBehaviorsWithSteps += r.opsWithSteps;
              modelBehaviorSteps += r.totalSteps;
            }
            for (const c of Object.values(controllers)) {
              const r = sumSteps((c as any)?.actions);
              controllerActionsWithSteps += r.opsWithSteps;
              controllerActionSteps += r.totalSteps;
              const cured = (c as any)?.cured || (c as any)?.curved;
              if (cured && typeof cured === 'object') curvedOpCount += Object.keys(cured).length;
            }
            for (const s of Object.values(services)) {
              const r = sumSteps((s as any)?.operations);
              serviceOpsWithSteps += r.opsWithSteps;
              serviceOpSteps += r.totalSteps;
            }
          }
          const totalLlmCalls = modelBehaviorSteps + controllerActionSteps + serviceOpSteps;
          const fileEstimate = entityCount * 7 + controllerCount + serviceCount * 2 + viewCount + 15;
          console.log('');
          console.log('\u2554\u2550\u2550 spv realize --estimate \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');
          console.log('\u2551');
          console.log('\u2551  Type: ' + type);
          console.log('\u2551  Components: ' + compNames.length + (compNames.length > 0 ? ' (' + compNames.join(', ') + ')' : ''));
          console.log('\u2551');
          console.log('\u2551  \u2500\u2500 Spec inventory (post-inference) \u2500\u2500');
          console.log('\u2551    Entities (models):    ' + entityCount);
          console.log('\u2551    Controllers:          ' + controllerCount);
          console.log('\u2551    Services:             ' + serviceCount);
          console.log('\u2551    Events:               ' + eventCount);
          console.log('\u2551    Views:                ' + viewCount);
          console.log('\u2551');
          console.log('\u2551  \u2500\u2500 Output by realize layer \u2500\u2500');
          console.log('\u2551');
          console.log('\u2551  L1 \u2014 Instance factory (file scaffolding, no LLM):');
          console.log('\u2551    File scaffolding for each entity / controller / service / view');
          console.log('\u2551    + framework boilerplate (Fastify routes, Prisma schema, React shell)');
          console.log('\u2551    Estimate: ~' + fileEstimate + ' files');
          console.log('\u2551');
          console.log('\u2551  L2 \u2014 Convention pattern matching (bodies, no LLM):');
          console.log('\u2551    CURVED ops auto-implemented:    ' + curvedOpCount);
          console.log('\u2551    Default events on lifecycles:   ' + eventCount + ' (declared) + auto-derived');
          console.log('\u2551    Default validation patterns:    ~' + (entityCount * 2));
          console.log('\u2551');
          console.log('\u2551  L3 \u2014 AI from steps (LLM, 1 call per step):');
          console.log('\u2551    Model behaviors with steps:    ' + modelBehaviorsWithSteps + ' behaviors \u2192 ' + modelBehaviorSteps + ' steps');
          console.log('\u2551    Controller actions with steps: ' + controllerActionsWithSteps + ' actions \u2192 ' + controllerActionSteps + ' steps');
          console.log('\u2551    Service ops with steps:        ' + serviceOpsWithSteps + ' operations \u2192 ' + serviceOpSteps + ' steps');
          console.log('\u2551    \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500');
          console.log('\u2551    Total LLM calls:               ' + totalLlmCalls);
          console.log('\u2551');
          if (totalLlmCalls > 0) {
            console.log('\u2551  \u2500\u2500 Estimated wall time + cost (L3 only) \u2500\u2500');
            console.log('\u2551    On Opus 4.7 / Max:    ~' + Math.ceil(totalLlmCalls * 0.2) + ' min wall (free, claude-cli session-cached)');
            console.log('\u2551    On Sonnet 4.6:        ~' + Math.ceil(totalLlmCalls * 0.1) + ' min wall (free)');
            console.log('\u2551    On Anthropic API:     ~' + Math.ceil(totalLlmCalls * 0.1) + ' min wall, ~$' + (totalLlmCalls * 0.015).toFixed(2) + ' (Sonnet rates)');
            console.log('\u2551    On DeepSeek/Together: ~' + Math.ceil(totalLlmCalls * 0.08) + ' min wall, ~$' + (totalLlmCalls * 0.0008).toFixed(3) + ' (10-30\xD7 cheaper)');
          } else {
            console.log('\u2551  No LLM cost \u2014 all output is L1 + L2 (templates + conventions only).');
          }
          console.log('\u2551');
          console.log('\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');
          return;
        }

        // Realize \u2014 let the realize engine handle its own library.
        // Locate the nearest implementation manifest by walking up from the
        // spec file, then falling back to the user's cwd. This lets the same
        // invocation work from any subdirectory (CLI, VSCode right-click, CI).
        const cwd = process.env.SPECVERSE_USER_CWD || process.cwd();
        let manifestPath: string | null = null;
        if (options.manifest) {
          manifestPath = resolve(cwd, options.manifest);
        } else {
          let searchDir = dirname(resolve(file));
          while (true) {
            const inManifests = join(searchDir, 'manifests', 'implementation.yaml');
            if (existsSync(inManifests)) { manifestPath = inManifests; break; }
            const flat = join(searchDir, 'implementation.yaml');
            if (existsSync(flat)) { manifestPath = flat; break; }
            const parent = dirname(searchDir);
            if (parent === searchDir) break;
            searchDir = parent;
          }
          if (!manifestPath) {
            const cwdBased = resolve(cwd, 'manifests/implementation.yaml');
            if (existsSync(cwdBased)) manifestPath = cwdBased;
          }
        }
        if (!manifestPath || !existsSync(manifestPath)) {
          console.error('Manifest not found. Looked for manifests/implementation.yaml walking up from the spec file, then in ' + cwd);
          process.exit(1);
        }

        // --static ejects to a standalone starter kit (ReactAppStarter).
        // Default: whatever the manifest declares (ReactAppRuntime in the
        // shipped templates). The swap is a convenience only \u2014 users can
        // achieve the same by editing their manifest directly.
        let effectiveManifestPath = manifestPath;
        if (options.static) {
          const manifestContent = readFileSync(manifestPath, 'utf8');
          if (/instanceFactory:\\s*["']?ReactAppRuntime["']?\\s*$/m.test(manifestContent)) {
            const { tmpdir } = await import('os');
            const staticManifest = manifestContent.replace(
              /instanceFactory:\\s*["']?ReactAppRuntime["']?\\s*$/gm,
              'instanceFactory: "ReactAppStarter"'
            );
            effectiveManifestPath = join(tmpdir(), 'specverse-static-manifest.yaml');
            writeFileSync(effectiveManifestPath, staticManifest, 'utf8');
            console.log('  Using static mode (standalone starter kit \u2014 ReactAppStarter)');
          } else {
            console.log('  Using static mode (manifest factory unchanged)');
          }
        }

        const realizeEngine = registry.getEngineForCapability('realize') as RealizeEngine;
        if (!realizeEngine) { console.error('No realize engine found.'); process.exit(1); }
        await realizeEngine.initialize({ manifestPath: effectiveManifestPath, workingDir: (process.env.SPECVERSE_USER_CWD || process.cwd()) });

        // #15 \u2014 subtype dispatch. \`type\` is the positional arg that was
        // previously captured-but-ignored. \`all\` is the default and runs
        // the full pipeline. Other values name a single entity section
        // (models / controllers / services / views / events / deployments
        // / commands / measures / promotions / conventions / distributions)
        // and we filter the inferred spec to that section per component
        // before handing it to realize. The realize engine iterates each
        // section independently; an empty section is a no-op for that
        // section's generators.
        const ENTITY_SECTIONS = new Set([
          'models', 'controllers', 'services', 'views', 'events',
          'deployments', 'commands', 'measures', 'promotions',
          'conventions', 'distributions',
        ]);
        let realizeSpec: any = inferredSpec;
        const realizeType = String(type || 'all').toLowerCase();
        if (realizeType !== 'all') {
          if (!ENTITY_SECTIONS.has(realizeType)) {
            console.error(\`Unknown realize subtype: '\${type}'. Expected 'all' or one of: \` + Array.from(ENTITY_SECTIONS).sort().join(', '));
            process.exit(1);
          }
          realizeSpec = JSON.parse(JSON.stringify(inferredSpec));
          const components: any[] = Array.isArray(realizeSpec?.components)
            ? realizeSpec.components
            : Object.values(realizeSpec?.components || {});
          for (const comp of components) {
            if (!comp || typeof comp !== 'object') continue;
            for (const section of ENTITY_SECTIONS) {
              if (section !== realizeType && comp[section] !== undefined) {
                comp[section] = Array.isArray(comp[section]) ? [] : {};
              }
            }
          }
        }

        const outputDir = resolve((process.env.SPECVERSE_USER_CWD || process.cwd()), options.output || 'generated/code');
        console.log('Realizing ' + type + ' from ' + file + '...');
        await (realizeEngine as any).realizeAll(realizeSpec, outputDir);

        // Write dev.specly for runtime mode \u2014 only if the manifest
        // actually declares a frontend (app.frontend capability). For
        // backend-only templates there's no frontend, so we'd otherwise
        // create a dead frontend/src/dev.specly file alongside a project
        // that has no frontend at all.
        if (!options.static && inferResult.devYaml) {
          const manifestText = readFileSync(effectiveManifestPath, 'utf8');
          const hasFrontend = /capability:\\s*["']?app\\.frontend["']?/.test(manifestText);
          if (hasFrontend) {
            // Detect frontend layout from the manifest. If the resolved
            // app.frontend factory uses outputStructure: standalone with
            // frontendDir: "." then dev.specly belongs at the output root.
            // Otherwise it goes under frontend/src/.
            const standaloneFrontend = /app\\.frontend[\\s\\S]*?outputStructure:\\s*["']?standalone["']?/.test(manifestText);
            const frontendDir = standaloneFrontend
              ? join(outputDir, 'src')
              : join(outputDir, 'frontend', 'src');
            mkdirSync(frontendDir, { recursive: true });
            const devSpeclyPath = join(frontendDir, 'dev.specly');
            writeFileSync(devSpeclyPath, inferResult.devYaml, 'utf8');
            console.log('  Wrote dev.specly \u2192', devSpeclyPath);
          }
        }`
  },
  init: {
    imports: `import { existsSync, mkdirSync, readdirSync, statSync, readFileSync, writeFileSync, copyFileSync } from 'fs';
import { resolve, join, dirname } from 'path';
import { fileURLToPath } from 'url';`,
    handler: `const __fn = fileURLToPath(import.meta.url);
        const __dn = dirname(__fn);

        // Resolve templates from package.json "specverse.templateDir" config
        const templatesDir = await (async () => {
          const { createRequire } = await import('module');
          const require = createRequire(import.meta.url);

          // Find our own package.json by walking up
          let dir = __dn;
          while (dir !== dirname(dir)) {
            const pkgPath = join(dir, 'package.json');
            if (existsSync(pkgPath)) {
              const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
              if (pkg.specverse?.templateDir) {
                const candidate = resolve(dir, pkg.specverse.templateDir);
                if (existsSync(candidate)) return candidate;
              }
            }
            dir = dirname(dir);
          }

          // Fallback: engine-realize assets
          try {
            const realizePkg = dirname(require.resolve('@specverse/engine-realize/package.json'));
            const candidate = join(realizePkg, 'assets', 'templates');
            if (existsSync(candidate)) return candidate;
          } catch { /* not installed */ }

          return null;
        })();

        if (!templatesDir) {
          console.error('Templates not found. Set "specverse.templateDir" in package.json or install @specverse/engine-realize.');
          process.exit(1);
        }

        if (options.list) {
          const templates = readdirSync(templatesDir).filter(
            d => statSync(join(templatesDir!, d)).isDirectory()
          );
          console.log('Available templates:');
          templates.forEach(t => console.log('  ' + t));
          return;
        }

        const projectName = name || 'my-project';
        // Commander applies the --template default ("full-stack"); the
        // fallback here only fires if a caller invoked the action handler
        // directly without going through commander.
        const templateName = options.template || 'full-stack';
        const templateDir = join(templatesDir, templateName);

        if (!existsSync(templateDir)) {
          console.error('Template not found: ' + templateName);
          console.error('Available: ' + readdirSync(templatesDir).filter(
            d => statSync(join(templatesDir!, d)).isDirectory()
          ).join(', '));
          process.exit(1);
        }

        const destDir = resolve((process.env.SPECVERSE_USER_CWD || process.cwd()), projectName);
        if (existsSync(destDir)) {
          console.error('Directory already exists: ' + destDir);
          process.exit(1);
        }

        // Copy template with variable substitution
        const kebab = projectName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
        // PascalCase: split on hyphens / underscores / whitespace, capitalize each segment, join.
        // 'audit-backend-only' -> 'AuditBackendOnly', 'my_app' -> 'MyApp', 'foo' -> 'Foo'.
        // NOTE: regex char class is double-escaped because this whole handler
        // is embedded as a string in the generator template \u2014 \\s here ends
        // up as s in the emitted source.
        const component = projectName
          .split(/[-_\\s]+/)
          .filter(Boolean)
          .map((s: string) => s.charAt(0).toUpperCase() + s.slice(1))
          .join('');
        const vars: Record<string, string> = {
          '{{PROJECT_NAME}}': projectName,
          '{{projectName}}': projectName,
          '{{projectNameKebab}}': kebab,
          '{{componentName}}': component,
          '{{DB_USER}}': 'postgres',
          '{{DB_PASSWORD}}': 'postgres',
        };

        function copyDir(src: string, dest: string) {
          mkdirSync(dest, { recursive: true });
          for (const item of readdirSync(src)) {
            const srcPath = join(src, item);
            let destName = item;
            if (item === 'gitignore') destName = '.gitignore';
            if (item === 'dot.env.example') destName = '.env.example';
            const destPath = join(dest, destName);

            if (statSync(srcPath).isDirectory()) {
              copyDir(srcPath, destPath);
            } else {
              let content = readFileSync(srcPath, 'utf8');
              for (const [key, val] of Object.entries(vars)) {
                content = content.split(key).join(val);
              }
              writeFileSync(destPath, content);
            }
          }
        }

        copyDir(templateDir, destDir);

        // If the copied template didn't ship a specs/main.specly, load
        // the canonical default spec from @specverse/engines/assets \u2014
        // same file app-demo's Server Manager "new spec" action uses,
        // so both entry points start a user on the same footing. The
        // template can opt out of this by shipping its own spec.
        const specDestPath = join(destDir, 'specs', 'main.specly');
        if (!existsSync(specDestPath)) {
          try {
            const { createRequire } = await import('module');
            const require = createRequire(import.meta.url);
            // Try @specverse/assets first (canonical home post 2026-04-25),
            // fall back to @specverse/engines for older installs.
            const candidates = [];
            try {
              const assetsPkg = require.resolve('@specverse/assets/package.json');
              candidates.push(
                join(dirname(assetsPkg), 'templates', 'default', 'specs', 'main.specly')
              );
            } catch {}
            try {
              const enginesPkg = require.resolve('@specverse/engines/package.json');
              candidates.push(
                join(dirname(enginesPkg), 'assets', 'templates', 'default', 'specs', 'main.specly')
              );
            } catch {}
            const canonicalSpec = candidates.find((p) => existsSync(p));
            if (canonicalSpec) {
              let content = readFileSync(canonicalSpec, 'utf8');
              for (const [key, val] of Object.entries(vars)) {
                content = content.split(key).join(val);
              }
              mkdirSync(join(destDir, 'specs'), { recursive: true });
              writeFileSync(specDestPath, content);
            }
          } catch { /* engines not installed or template missing \u2014 proceed */ }
        }

        // --static: flip any ReactAppRuntime mappings in the copied
        // manifests to ReactAppStarter so the user gets standalone-starter
        // output on first \`spv realize\` without having to re-pass the
        // flag. Same semantic as \`spv realize --static\` \u2014 just applied
        // at init time. Idempotent; no-op for templates without a frontend.
        if (options.static) {
          const manifestsDir = join(destDir, 'manifests');
          if (existsSync(manifestsDir)) {
            for (const f of readdirSync(manifestsDir)) {
              if (!f.endsWith('.yaml') && !f.endsWith('.yml')) continue;
              const p = join(manifestsDir, f);
              const before = readFileSync(p, 'utf8');
              const after = before.replace(
                /instanceFactory:\\s*["']?ReactAppRuntime["']?/g,
                'instanceFactory: "ReactAppStarter"'
              );
              if (after !== before) writeFileSync(p, after, 'utf8');
            }
          }
        }

        console.log('Project created: ' + destDir);
        console.log('Template: ' + templateName + (options.static ? ' (static / ReactAppStarter)' : ''));
        console.log('');

        // Build the "Next steps" hint from the actual scripts the
        // chosen template ships, so we never tell the user to run a
        // command that doesn't exist in their package.json.
        const destPkgPath = join(destDir, 'package.json');
        const destPkg = existsSync(destPkgPath)
          ? JSON.parse(readFileSync(destPkgPath, 'utf8'))
          : { scripts: {} };
        const scripts = destPkg.scripts || {};
        console.log('Next steps:');
        console.log('  cd ' + projectName);
        if (scripts.setup) {
          console.log('  npm run setup          # validate, generate code, install deps, setup db');
        }
        if (scripts['dev:backend'] && scripts['dev:frontend']) {
          console.log('  npm run dev:backend    # start backend (terminal 1)');
          console.log('  npm run dev:frontend   # start frontend (terminal 2)');
        } else if (scripts.dev) {
          console.log('  npm run dev            # start dev server');
        }
        if (scripts['test:e2e']) {
          console.log('  npm run test:e2e       # run end-to-end tests (after starting dev servers)');
        }`
  },
  // === gen subcommands ===
  "gen.diagrams": {
    imports: `import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { resolve, dirname, basename, join } from 'path';
import { fileURLToPath } from 'url';
import { EngineRegistry } from '@specverse/entities';
import type { ParserEngine } from '@specverse/types';`,
    handler: `const registry = new EngineRegistry();
        await registry.discover();
        const parser = registry.getEngineForCapability('parse') as ParserEngine;
        if (!parser) { console.error('No parser engine found.'); process.exit(1); }
        await parser.initialize();

        const content = readFileSync(file, 'utf8');
        const parseResult = parser.parseContent(content, file);
        if (parseResult.errors.length > 0) {
          console.error('Invalid spec:');
          parseResult.errors.forEach((e: string) => console.error(' ', e));
          process.exit(1);
        }

        const gen = registry.getEngineForCapability('generate-diagrams') as any;
        if (!gen) { console.error('No generators engine found.'); process.exit(1); }
        await gen.initialize();

        const diagrams = await gen.generateDiagrams(parseResult.ast!, { type: options.type || 'all' });
        const outputDir = options.output || basename(file, '.specly') + '-diagrams';
        if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true });
        for (const [diagramType, diagramContent] of diagrams.entries()) {
          writeFileSync(join(outputDir, diagramType + '.mmd'), diagramContent);
          console.log('  ' + diagramType);
        }
        console.log('Generated ' + diagrams.size + ' diagrams in: ' + outputDir);`
  },
  "gen.docs": {
    imports: `import { readFileSync, writeFileSync, existsSync } from 'fs';
import { resolve, dirname, basename } from 'path';
import { fileURLToPath } from 'url';
import { EngineRegistry } from '@specverse/entities';
import type { ParserEngine } from '@specverse/types';`,
    handler: `const registry = new EngineRegistry();
        await registry.discover();
        const parser = registry.getEngineForCapability('parse') as ParserEngine;
        if (!parser) { console.error('No parser engine found.'); process.exit(1); }
        await parser.initialize();

        const content = readFileSync(file, 'utf8');
        const parseResult = parser.parseContent(content, file);
        if (parseResult.errors.length > 0) {
          console.error('Invalid spec:');
          parseResult.errors.forEach((e: string) => console.error(' ', e));
          process.exit(1);
        }

        const gen = registry.getEngineForCapability('generate-docs') as any;
        if (!gen) { console.error('No generators engine found.'); process.exit(1); }
        await gen.initialize();

        const docs = await gen.generateDocs(parseResult.ast!, { format: options.format || 'markdown' });
        const ext = options.format === 'html' ? '.html' : '.md';
        const outputFile = options.output || basename(file, '.specly') + '-docs' + ext;
        writeFileSync(outputFile, docs);
        console.log('Documentation generated: ' + outputFile);`
  },
  "gen.uml": {
    imports: `import { readFileSync, writeFileSync, existsSync } from 'fs';
import { resolve, dirname, basename } from 'path';
import { fileURLToPath } from 'url';
import { EngineRegistry } from '@specverse/entities';
import type { ParserEngine } from '@specverse/types';`,
    handler: `const registry = new EngineRegistry();
        await registry.discover();
        const parser = registry.getEngineForCapability('parse') as ParserEngine;
        if (!parser) { console.error('No parser engine found.'); process.exit(1); }
        await parser.initialize();

        const content = readFileSync(file, 'utf8');
        const parseResult = parser.parseContent(content, file);
        if (parseResult.errors.length > 0) {
          console.error('Invalid spec:');
          parseResult.errors.forEach((e: string) => console.error(' ', e));
          process.exit(1);
        }

        const gen = registry.getEngineForCapability('generate-uml') as any;
        if (!gen) { console.error('No generators engine found.'); process.exit(1); }
        await gen.initialize();

        const uml = await gen.generateUML(parseResult.ast!, { type: options.type || 'all' });
        const outputFile = basename(file, '.specly') + '-uml.puml';
        writeFileSync(outputFile, uml);
        console.log('UML generated: ' + outputFile);`
  },
  // === dev subcommands ===
  "dev.format": {
    imports: `import { readFileSync, writeFileSync, existsSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { EngineRegistry } from '@specverse/entities';
import type { ParserEngine } from '@specverse/types';`,
    handler: `const registry = new EngineRegistry();
        await registry.discover();
        const parser = registry.getEngineForCapability('parse') as ParserEngine;
        if (!parser) { console.error('No parser engine found.'); process.exit(1); }
        await parser.initialize();

        const content = readFileSync(file, 'utf8');
        const result = parser.parseContent(content, file);
        if (result.errors.length > 0) {
          console.error('Cannot format invalid spec:');
          result.errors.forEach((e: string) => console.error(' ', e));
          process.exit(1);
        }
        const yaml = await import('js-yaml');
        const formatted = yaml.dump(yaml.load(content), { lineWidth: 120, noRefs: true });
        if (options.write) {
          writeFileSync(file, formatted);
          console.log('Formatted and saved: ' + file);
        } else {
          console.log(formatted);
        }`
  },
  "dev.watch": {
    imports: `import { readFileSync, existsSync, watch } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { EngineRegistry } from '@specverse/entities';
import type { ParserEngine } from '@specverse/types';`,
    handler: `const registry = new EngineRegistry();
        await registry.discover();
        const parser = registry.getEngineForCapability('parse') as ParserEngine;
        if (!parser) { console.error('No parser engine found.'); process.exit(1); }
        await parser.initialize();

        console.log('Watching ' + file + ' for changes...');
        const doValidate = () => {
          try {
            const c = readFileSync(file, 'utf8');
            const r = parser.parseContent(c, file);
            if (r.errors.length > 0) {
              console.log('[' + new Date().toLocaleTimeString() + '] FAILED');
              r.errors.forEach((e: string) => console.error(' ', e));
            } else {
              console.log('[' + new Date().toLocaleTimeString() + '] Valid');
            }
          } catch (e: any) { console.error('Watch error:', e.message); }
        };
        doValidate();
        watch(file, doValidate);
        await new Promise(() => {});`
  },
  "dev.quick": {
    imports: `import { readFileSync, existsSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { EngineRegistry } from '@specverse/entities';
import type { ParserEngine } from '@specverse/types';`,
    handler: `const registry = new EngineRegistry();
        await registry.discover();
        const parser = registry.getEngineForCapability('parse') as ParserEngine;
        if (!parser) { console.error('No parser engine found.'); process.exit(1); }
        await parser.initialize();

        const content = readFileSync(file, 'utf8');
        const result = parser.parseContent(content, file);
        if (result.errors.length > 0) {
          console.error('Quick check: FAILED');
          result.errors.forEach((e: string) => console.error(' ', e));
          process.exit(1);
        }
        console.log('Quick check: OK');`
  },
  // === cache command (leaf, not subcommand) ===
  cache: {
    imports: `import { EngineRegistry } from '@specverse/entities';
import type { ParserEngine } from '@specverse/types';`,
    handler: `const registry = new EngineRegistry();
        await registry.discover();
        const parser = registry.getEngineForCapability('parse') as ParserEngine;
        if (!parser) { console.error('No parser engine found.'); process.exit(1); }
        await parser.initialize();

        // Access ImportResolver cache via parser
        const resolverModule = await import('@specverse/engines/parser');
        const resolver = new resolverModule.ImportResolver({ basePath: (process.env.SPECVERSE_USER_CWD || process.cwd()) });

        const cacheDir = (resolver as any).getCacheDir ? (resolver as any).getCacheDir() : null;
        if (options.stats) {
          console.log('Cache directory:', cacheDir || 'default');
        } else if (options.list) {
          if (cacheDir) {
            const fs = await import('fs');
            if (fs.existsSync(cacheDir)) {
              const items = fs.readdirSync(cacheDir);
              if (items.length === 0) { console.log('Cache is empty'); }
              else { items.forEach((item: string) => console.log(' ', item)); }
            } else { console.log('Cache directory does not exist'); }
          } else { console.log('No cache directory configured'); }
        } else if (options.clear) {
          if (resolver.clearCache) { resolver.clearCache(); }
          console.log('Cache cleared');
        } else {
          console.log('Use --stats, --list, or --clear');
        }`
  },
  // === ai subcommands ===
  "ai.docs": {
    imports: `import { readFileSync, writeFileSync, existsSync } from 'fs';
import { resolve, dirname, basename } from 'path';
import { fileURLToPath } from 'url';
import { EngineRegistry } from '@specverse/entities';
import type { ParserEngine } from '@specverse/types';`,
    handler: `const registry = new EngineRegistry();
        await registry.discover();
        const parser = registry.getEngineForCapability('parse') as ParserEngine;
        if (!parser) { console.error('No parser engine found.'); process.exit(1); }
        await parser.initialize();

        const content = readFileSync(file, 'utf8');
        const parseResult = parser.parseContent(content, file);
        if (parseResult.errors.length > 0) {
          console.error('Invalid spec:');
          parseResult.errors.forEach((e: string) => console.error(' ', e));
          process.exit(1);
        }

        const aiEngine = registry.getEngineForCapability('ai-prompts') as any;
        if (!aiEngine) {
          console.error('AI engine not available. Install @specverse/engines.');
          process.exit(1);
        }
        await aiEngine.initialize({ provider: options.provider });
        const prompt = await aiEngine.generatePrompt(parseResult.ast!, { type: 'docs' });
        const outputFile = options.output || basename(file, '.specly') + '-ai-docs.md';
        writeFileSync(outputFile, prompt);
        console.log('AI documentation prompt generated: ' + outputFile);`
  },
  "ai.suggest": {
    imports: `import { readFileSync, existsSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { EngineRegistry } from '@specverse/entities';
import type { ParserEngine } from '@specverse/types';`,
    handler: `const registry = new EngineRegistry();
        await registry.discover();
        const parser = registry.getEngineForCapability('parse') as ParserEngine;
        if (!parser) { console.error('No parser engine found.'); process.exit(1); }
        await parser.initialize();

        const content = readFileSync(file, 'utf8');
        const parseResult = parser.parseContent(content, file);
        if (parseResult.errors.length > 0) {
          console.error('Invalid spec:');
          parseResult.errors.forEach((e: string) => console.error(' ', e));
          process.exit(1);
        }

        const aiEngine = registry.getEngineForCapability('ai-suggestions') as any;
        if (!aiEngine) {
          console.error('AI engine not available. Install @specverse/engines.');
          process.exit(1);
        }
        await aiEngine.initialize();
        const suggestions = await aiEngine.suggest(parseResult.ast!);
        if (suggestions.length === 0) {
          console.log('No suggestions \u2014 spec looks good!');
        } else {
          const warnings = suggestions.filter((s: any) => s.severity === 'warning');
          const improvements = suggestions.filter((s: any) => s.severity === 'improvement');
          const info = suggestions.filter((s: any) => s.severity === 'info');
          if (warnings.length > 0) {
            console.log('\\nWarnings:');
            warnings.forEach((s: any) => console.log('  [' + s.target + '] ' + s.description));
          }
          if (improvements.length > 0) {
            console.log('\\nSuggested improvements:');
            improvements.forEach((s: any) => console.log('  [' + s.target + '] ' + s.description));
          }
          if (info.length > 0) {
            console.log('\\nInfo:');
            info.forEach((s: any) => console.log('  [' + s.target + '] ' + s.description));
          }
          console.log('\\n' + suggestions.length + ' suggestion(s): ' + warnings.length + ' warning, ' + improvements.length + ' improvement, ' + info.length + ' info');
        }`
  },
  "ai.template": {
    imports: `import { writeFileSync } from 'fs';
import { EngineRegistry } from '@specverse/entities';`,
    handler: `const registry = new EngineRegistry();
        await registry.discover();
        const aiEngine = registry.getEngineForCapability('ai-templates') as any;
        if (!aiEngine) {
          console.error('AI engine not available. Install @specverse/engines.');
          process.exit(1);
        }
        await aiEngine.initialize();
        const template = await aiEngine.template(operation, { config: options.config });
        if (options.output) {
          writeFileSync(options.output, template);
          console.log('Template written to: ' + options.output);
        } else {
          console.log(template);
        }`
  },
  "ai.regenerate": {
    imports: `import { readFileSync, existsSync } from 'fs';
import { resolve } from 'path';
import { EngineRegistry } from '@specverse/entities';
import type { ParserEngine } from '@specverse/types';`,
    handler: `// Parse Owner.functionName \u2014 or just Owner with --all
        const target = fn;
        const dotIdx = target.indexOf('.');
        const ownerName = dotIdx >= 0 ? target.slice(0, dotIdx) : target;
        const functionName = dotIdx >= 0 ? target.slice(dotIdx + 1) : null;
        if (!functionName && !options.all) {
          console.error('Function must be specified as Owner.functionName, or use --all to regenerate every AI function in the owner.');
          process.exit(1);
        }

        // Resolve spec file \u2014 default to specs/main-inferred.specly next to cwd
        const userCwd = process.env.SPECVERSE_USER_CWD || process.cwd();
        const specCandidates = options.spec
          ? [resolve(userCwd, options.spec)]
          : [resolve(userCwd, 'specs/main-inferred.specly'), resolve(userCwd, 'specs/main.specly')];
        const specPath = specCandidates.find(p => existsSync(p));
        if (!specPath) {
          console.error('Spec not found. Tried: ' + specCandidates.join(', '));
          console.error('Pass --spec <file> or run from a project root with specs/main.specly.');
          process.exit(1);
        }
        console.log('Using spec: ' + specPath);

        const registry = new EngineRegistry();
        await registry.discover();
        const parser = registry.getEngineForCapability('parse') as ParserEngine;
        if (!parser) { console.error('No parser engine found.'); process.exit(1); }
        await parser.initialize();

        const specContent = readFileSync(specPath, 'utf8');
        const parseResult = parser.parseContent(specContent, specPath);
        if (parseResult.errors.length > 0) {
          console.error('Invalid spec:');
          parseResult.errors.forEach((e: string) => console.error('  ' + e));
          process.exit(1);
        }

        // Flatten components down to a realize-shaped spec
        // (controllers, services, models at top level) \u2014 matches what
        // regenerateBehavior expects. Collections may be arrays (parser
        // output) or objects (inferred spec); preserve the incoming shape.
        const ast: any = parseResult.ast;
        const flat: any = { ...ast };
        const components = ast?.components || {};
        for (const comp of Object.values(components) as any[]) {
          for (const key of ['models', 'controllers', 'services']) {
            const collection = comp?.[key];
            if (!collection) continue;
            if (Array.isArray(collection)) {
              flat[key] = [...(Array.isArray(flat[key]) ? flat[key] : []), ...collection];
            } else {
              flat[key] = { ...(flat[key] && !Array.isArray(flat[key]) ? flat[key] : {}), ...collection };
            }
          }
        }

        const { regenerateBehavior } = await import('@specverse/engines/ai');
        const outputDir = resolve(userCwd, options.output || 'generated/code');
        try {
          const result = await regenerateBehavior({
            spec: flat,
            ownerName,
            targetFunction: options.all ? null : functionName,
            outputDir,
            allFunctions: !!options.all,
          });
          console.log(\`\u2705 Regenerated \${result.filePath}\`);
          console.log(\`   Owner: \${ownerName}\`);
          console.log(\`   Unmatched functions: \${result.unmatchedCount}\`);
          console.log(\`   Target: \${options.all ? 'all' : functionName}\`);
          console.log(\`   Cache entries cleared: \${result.cacheCleared}\`);
        } catch (err: any) {
          console.error('Regeneration failed: ' + (err?.message || err));
          process.exit(1);
        }`
  },
  "ai.analyse": {
    imports: `import { resolve, basename } from 'path';
import { existsSync, mkdirSync, cpSync, rmSync, statSync, readdirSync } from 'fs';`,
    handler: `// Resolve the absolute source dir.
        const userCwd = process.env.SPECVERSE_USER_CWD || process.cwd();
        const sourceAbs = resolve(userCwd, source);
        if (!existsSync(sourceAbs)) {
          console.error('Source dir not found: ' + sourceAbs);
          process.exit(1);
        }

        // Default output: ./runs/analyse/<basename>/<date>_<label>/
        const { runAnalyse, buildRunFolder, caseRunPath, ensureSubdirs, inputDir, resolveProviderId } = await import('@specverse/engines/ai');
        const providerId = resolveProviderId();
        const modelId = process.env.SPECVERSE_AI_MODEL || 'default';
        const runFolder = buildRunFolder({
          prompt: 'analyse-v9',
          providerId,
          modelId,
          label: options.label,
        });
        const outputDir = options.output
          ? resolve(userCwd, options.output)
          : caseRunPath(userCwd, 'analyse', basename(sourceAbs), runFolder);
        ensureSubdirs(outputDir);

        // Copy source into outputDir/input/ (excluding noisy indices) so the
        // run dir is self-contained.
        // Skip the copy when sourceAbs IS already the input dir \u2014 orchestrators
        // (e.g. the eval harness) may pre-materialize source and pass it in.
        const inDir = inputDir(outputDir);
        const stat = statSync(sourceAbs);
        const sourceIsInput = sourceAbs === inDir;
        if (stat.isDirectory() && !sourceIsInput) {
          if (existsSync(inDir)) {
            // Replace the empty stub from ensureSubdirs with the real copy.
            try { cpSync(sourceAbs, inDir, { recursive: true, force: true }); }
            catch (err: any) {
              console.error('Failed to copy source into input/: ' + err.message);
              process.exit(1);
            }
          }
        } else if (!stat.isDirectory()) {
          // Single-file source \u2014 copy it into input/ as-is, UNLESS it already
          // lives there. Orchestrators (e.g. the eval harness) pre-stage a
          // single file into input/ and pass input/<file> as the source; the
          // directory branch above guards that case via sourceIsInput, but a
          // file path is never === inDir, so without this guard cpSync(X, X)
          // throws "src and dest cannot be the same".
          const fileTarget = resolve(inDir, basename(sourceAbs));
          if (resolve(sourceAbs) !== fileTarget) {
            mkdirSync(inDir, { recursive: true });
            cpSync(sourceAbs, fileTarget);
          }
        }
        // Always strip noise (handles both fresh-copy and pre-populated cases).
        // Engines 6.31.4+: also strip .git/. When source is a real git repo,
        // gitnexus reads the inherited .git/config upstream-URL and binds
        // the analyze run to the source-repo identity, ignoring our explicit
        // --name flag, so cypher --repo run-input-XXX 404s. Stripping .git/
        // forces gitnexus to register under our chosen name.
        // (Engines 6.31.4+: use the rmSync ES import \u2014 esbuild's ESM bundle
        // doesn't support require('fs').rmSync, which was silently failing.)
        for (const noise of ['.gitnexus', '.git', 'node_modules', '.DS_Store']) {
          const p = resolve(inDir, noise);
          try { if (existsSync(p)) rmSync(p, { recursive: true, force: true }); } catch {}
        }

        console.log('  source:  ' + sourceAbs);
        console.log('  output:  ' + outputDir);
        console.log('  provider: ' + providerId);
        if (options.facts) console.log('  facts:    ' + options.facts);
        if (options.autoFacts) console.log('  facts:    auto (runPrepass)');
        if (options.verify) console.log('  verify:   enabled');
        if (options.realize) console.log('  realize:  enabled');

        try {
          const result = await runAnalyse({
            sourceDir: inDir,
            outputDir,
            factsPath: options.facts ? resolve(userCwd, options.facts) : undefined,
            autoFacts: !!options.autoFacts,
            verify: !!options.verify,
            realize: !!options.realize,
            sessionId: options.sessionId,
            // useMicrocalls deliberately omitted: engines defaults to the
            // skeleton + per-action micro-calls path when prepassFacts are
            // available (autoFacts: true). The legacy --microcalls /
            // --no-microcalls CLI flag was retired in self 5.18.1.
            withBehaviours: options.behaviours !== false,
            prompt: 'analyse-v9',
            providerId,
            modelId,
            caseName: basename(sourceAbs),
          });
          console.log('');
          console.log('\u2705 Analyse complete');
          console.log('   Final spec: ' + result.finalSpecPath);
          console.log('   Validate:   ' + (result.validateFinal.ok ? '\u2713 pass' : '\u2717 ' + result.validateFinal.errors.slice(0, 100)));
          if (result.realize) {
            console.log('   Realize:    ' + (result.realize.ok ? '\u2713 project/ materialized' : '\u2717 ' + result.realize.error));
          }
          console.log('   README:     ' + outputDir + '/README.md');
        } catch (err: any) {
          console.error('Analyse failed: ' + (err?.message || err));
          process.exit(1);
        }`
  },
  "ai.behaviours": {
    imports: `import { resolve } from 'path';
import { existsSync, statSync } from 'fs';`,
    handler: `// Resolve the analysed-spec dir.
        const userCwd = process.env.SPECVERSE_USER_CWD || process.cwd();
        const dirAbs = resolve(userCwd, analysedSpecDir);
        if (!existsSync(dirAbs)) {
          console.error('Analysed spec dir not found: ' + dirAbs);
          process.exit(1);
        }
        if (!statSync(dirAbs).isDirectory()) {
          console.error('analysedSpecDir must be a directory (output of \\\`spv ai analyse --no-behaviours\\\`)');
          process.exit(1);
        }

        const { runBehaviours, resolveProviderId } = await import('@specverse/engines/ai');
        const providerId = resolveProviderId();
        const modelId = process.env.SPECVERSE_AI_MODEL || 'default';
        console.log('  analysed-spec-dir: ' + dirAbs);
        console.log('  output-dir:        ' + (options.output ? resolve(userCwd, options.output) : dirAbs));
        console.log('  provider:          ' + providerId + (modelId !== 'default' ? ' (' + modelId + ')' : ''));

        try {
          const result = await runBehaviours({
            analysedSpecDir: dirAbs,
            outputDir: options.output ? resolve(userCwd, options.output) : dirAbs,
            sessionId: options.sessionId,
            microcallConcurrency: options.concurrency ? parseInt(options.concurrency, 10) : undefined,
          });
          console.log('');
          console.log('\u2705 Behaviours filled');
          console.log('   Filled spec:     ' + result.filledSpecPath);
          console.log('   Microcalls:      ' + result.microcallStats.succeeded + '/' + result.microcallStats.attempted + ' (' + (result.microcallStats.wallTimeMs / 1000).toFixed(1) + 's)');
          if (result.surgicalRetryPasses > 0) {
            console.log('   Surgical retry:  ' + result.surgicalRetryPasses + ' pass(es)');
          }
          console.log('   Validate:        ' + (result.validateFinal.ok ? '\u2713 pass' : '\u2717 ' + result.validateFinal.errors.slice(0, 100)));
        } catch (err: any) {
          console.error('Behaviours fill failed: ' + (err?.message || err));
          process.exit(1);
        }`
  },
  "ai.create": {
    imports: `import { resolve, basename } from 'path';
import { existsSync, readFileSync } from 'fs';`,
    handler: `const userCwd = process.env.SPECVERSE_USER_CWD || process.cwd();
        // Resolve requirements: positional arg, or read from --file.
        let reqText = requirements;
        if (options.file) {
          const fp = resolve(userCwd, options.file);
          if (!existsSync(fp)) {
            console.error('Requirements file not found: ' + fp);
            process.exit(1);
          }
          reqText = readFileSync(fp, 'utf8');
        }
        if (!reqText || !reqText.trim()) {
          console.error('No requirements provided. Pass them as a positional argument or via --file.');
          process.exit(1);
        }

        const { runCreate, buildRunFolder, caseRunPath, ensureSubdirs, resolveProviderId } = await import('@specverse/engines/ai');
        const providerId = resolveProviderId();
        const modelId = process.env.SPECVERSE_AI_MODEL || 'default';
        const runFolder = buildRunFolder({
          prompt: 'create-v9',
          providerId,
          modelId,
          label: options.label,
        });
        // Default output: ./runs/create/<short-slug>/<date>_<label>/
        const slug = (options.name || 'spec').toLowerCase().replace(/[^a-z0-9-]+/g, '-').slice(0, 40) || 'spec';
        const outputDir = options.output
          ? resolve(userCwd, options.output)
          : caseRunPath(userCwd, 'create', slug, runFolder);
        ensureSubdirs(outputDir);

        console.log('  output:   ' + outputDir);
        console.log('  provider: ' + providerId);
        if (options.scale) console.log('  scale:    ' + options.scale);
        if (options.preferredTech) console.log('  tech:     ' + options.preferredTech);
        if (options.verify) console.log('  verify:   enabled');
        if (options.realize) console.log('  realize:  enabled');

        try {
          const result = await runCreate({
            requirements: reqText,
            scale: options.scale,
            preferredTech: options.preferredTech,
            outputDir,
            verify: !!options.verify,
            realize: !!options.realize,
            sessionId: options.sessionId,
            prompt: 'create-v9',
            providerId,
            modelId,
            caseName: slug,
          });
          console.log('');
          console.log('\u2705 Create complete');
          console.log('   Final spec: ' + result.finalSpecPath);
          console.log('   Validate:   ' + (result.validateFinal.ok ? '\u2713 pass' : '\u2717 ' + result.validateFinal.errors.slice(0, 100)));
          if (result.realize) {
            console.log('   Realize:    ' + (result.realize.ok ? '\u2713 project/ materialized' : '\u2717 ' + result.realize.error));
          }
          console.log('   README:     ' + outputDir + '/README.md');
        } catch (err: any) {
          console.error('Create failed: ' + (err?.message || err));
          process.exit(1);
        }`
  },
  // === session subcommands ===
  "session.create": {
    imports: `import { EngineRegistry } from '@specverse/entities';`,
    handler: `const registry = new EngineRegistry();
        await registry.discover();
        const aiEngine = registry.getEngineForCapability('ai-prompts') as any;
        if (!aiEngine) { console.error('AI engine not available.'); process.exit(1); }
        await aiEngine.initialize();

        const { SessionManager } = await import('@specverse/engines/ai');
        const manager = new SessionManager();
        const session = await manager.create({ name: options.name, pver: options.pver });

        if (options.json) {
          console.log(JSON.stringify(session, null, 2));
        } else {
          console.log('Session created: ' + session.sessionId);
          console.log('Status: ' + session.status);
          if (session.name) console.log('Name: ' + session.name);
          console.log('Prompt version: ' + session.pver);
        }`
  },
  "session.list": {
    imports: ``,
    handler: `const { SessionManager } = await import('@specverse/engines/ai');
        const manager = new SessionManager();
        const sessions = await manager.list({ all: options.all });

        if (options.json) {
          console.log(JSON.stringify(sessions, null, 2));
        } else if (sessions.length === 0) {
          console.log('No sessions found. Create one with: specverse session create');
        } else {
          console.log('Sessions (' + sessions.length + '):');
          for (const s of sessions) {
            console.log('  ' + (s.status === 'active' ? '*' : ' ') + ' ' + s.sessionId + (s.name ? ' (' + s.name + ')' : '') + ' \u2014 ' + s.status + ', ' + s.jobsProcessed + ' jobs');
          }
        }`
  },
  "session.delete": {
    imports: ``,
    handler: `const { SessionManager } = await import('@specverse/engines/ai');
        const manager = new SessionManager();
        await manager.delete(sessionId, { force: options.force });
        console.log('Session deleted: ' + sessionId);`
  },
  "session.submit": {
    imports: ``,
    handler: `const { SessionManager } = await import('@specverse/engines/ai');
        const manager = new SessionManager();
        const job = await manager.submit({
          jobId: options.jobId || ('job-' + Date.now()),
          sessionId,
          operation: (options.operation || 'create') as 'create' | 'analyse' | 'materialise' | 'realize',
          requirements,
          output: options.output,
        });
        console.log('Job submitted: ' + job.jobId);
        console.log('Status: ' + job.status);
        if (options.output) console.log('Output: ' + options.output);`
  },
  "session.status": {
    imports: ``,
    handler: `const { SessionManager } = await import('@specverse/engines/ai');
        const manager = new SessionManager();
        const status = await manager.status(id);
        if (options.json) {
          console.log(JSON.stringify(status, null, 2));
        } else {
          console.log('ID: ' + (status.sessionId || status.jobId));
          console.log('Status: ' + status.status);
          if (status.submitted) console.log('Submitted: ' + new Date(status.submitted).toLocaleString());
          if (status.started) console.log('Started: ' + new Date(status.started).toLocaleString());
          if (status.completed) console.log('Completed: ' + new Date(status.completed).toLocaleString());
          if (status.duration) console.log('Duration: ' + status.duration);
        }`
  },
  "session.process": {
    imports: ``,
    handler: `const { SessionManager } = await import('@specverse/engines/ai');
        const manager = new SessionManager();
        console.log('Processing job: ' + jobId);
        await manager.processJob(jobId);
        console.log('Job processed successfully');`
  },
  // === skill subcommands ===
  "skill.install": {
    imports: `import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, cpSync } from 'fs';
import { resolve, dirname, join } from 'path';
import { homedir } from 'os';
import { fileURLToPath } from 'url';
import { createRequire } from 'module';`,
    handler: `const requireResolve = createRequire(import.meta.url);

        // 1. Resolve target directory.
        let targetDir: string;
        if (options.target) {
          targetDir = resolve(options.target);
        } else if (options.global) {
          targetDir = join(homedir(), '.claude', 'skills', 'specverse');
        } else {
          // Default: project-local (current working directory).
          targetDir = join(process.env.SPECVERSE_USER_CWD || process.cwd(), '.claude', 'skills', 'specverse');
        }
        mkdirSync(targetDir, { recursive: true });
        mkdirSync(join(targetDir, 'reference'), { recursive: true });
        mkdirSync(join(targetDir, 'workflows'), { recursive: true });

        // 2. Helpers to resolve into installed packages.
        //    For @specverse/self specifically, we fall back to walking up from
        //    import.meta.url \u2014 handy when running the in-repo bootstrap CLI where
        //    self can't resolve its own package.json via require.
        const selfRoot = (() => {
          try {
            const pkgJson = requireResolve.resolve('@specverse/self/package.json');
            return dirname(pkgJson);
          } catch {
            let dir = dirname(fileURLToPath(import.meta.url));
            for (let i = 0; i < 8; i++) {
              const candidate = join(dir, 'package.json');
              if (existsSync(candidate)) {
                try {
                  const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
                  if (pkg?.name === '@specverse/self') return dir;
                } catch { /* continue */ }
              }
              const parent = dirname(dir);
              if (parent === dir) break;
              dir = parent;
            }
            return null;
          }
        })();

        const resolvePkg = (pkg: string, rel: string): string | null => {
          if (pkg === '@specverse/self') {
            return selfRoot ? join(selfRoot, rel) : null;
          }
          try {
            const pkgJson = requireResolve.resolve(pkg + '/package.json');
            return join(dirname(pkgJson), rel);
          } catch { return null; }
        };

        // 3. Copy SKILL.md from @specverse/self.
        const skillMdSrc = resolvePkg('@specverse/self', 'skills/specverse/SKILL.md');
        if (!skillMdSrc || !existsSync(skillMdSrc)) {
          console.error('Cannot locate SKILL.md in @specverse/self \u2014 install failed.');
          process.exit(1);
        }
        writeFileSync(join(targetDir, 'SKILL.md'), readFileSync(skillMdSrc, 'utf8'));

        // 4. Copy reference files (schema + ai-guidance + minimal-example + guide).
        const refCopies: [string, string, string][] = [
          ['@specverse/entities', 'schema/SPECVERSE-SCHEMA.json', 'reference/schema.json'],
          ['@specverse/entities', 'schema/SPECVERSE-SCHEMA-AI.yaml', 'reference/ai-guidance.yaml'],
          ['@specverse/entities', 'schema/MINIMAL-SYNTAX-REFERENCE.specly', 'reference/minimal-example.specly'],
          ['@specverse/self', 'docs/guides/SPECVERSE-COMPLETE-GUIDE.md', 'reference/guide.md'],
        ];
        let refCopied = 0;
        for (const [pkg, rel, dest] of refCopies) {
          const src = resolvePkg(pkg, rel);
          if (src && existsSync(src)) {
            writeFileSync(join(targetDir, dest), readFileSync(src, 'utf8'));
            refCopied++;
          } else {
            console.warn('  (skipped ' + dest + ' \u2014 ' + pkg + '/' + rel + ' not found)');
          }
        }

        // 5. Build cli-reference.md from the running CLI's own \`--help\`.
        // Minimal content \u2014 a pointer to the spv help and a static summary.
        const cliRef = [
          '# SpecVerse CLI Reference',
          '',
          'Run \`spv <command> --help\` for flag details on any command. Common subcommands:',
          '',
          '| Command | Purpose |',
          '|---|---|',
          '| \`spv init <name>\` | Scaffold a new project (templates: default / full-stack / backend-only / frontend-only) |',
          '| \`spv validate <spec>\` | Parse + schema-validate a .specly file |',
          '| \`spv validate-bundle <dir>\` | Validate an entity bundle (for engine extenders) |',
          '| \`spv infer <spec>\` | Expand a minimal spec to full architecture (controllers / services / events / views) |',
          '| \`spv realize all <spec>\` | Generate production code from inferred spec + manifest |',
          '| \`spv gen diagrams <spec>\` | Emit mermaid diagrams (ER, lifecycle, architecture) |',
          '| \`spv ai template <op> <spec>\` | Dump a canonical workflow prompt filled in for a spec |',
          '| \`spv smoke\` | Verify the \`spv\` install end-to-end |',
          '| \`spv skill install [--global\\\\|--project]\` | Reinstall / refresh this skill |',
          '',
          'For CI invocations, see the \`SPECVERSE-TOOLING.md\` guide in \`@specverse/self/docs/guides/\`.',
          '',
        ].join('\\n');
        writeFileSync(join(targetDir, 'reference', 'cli-reference.md'), cliRef);

        // 6. Emit one workflow markdown per prompt YAML in @specverse/engines/assets/prompts/core/standard/default.
        const yamlLib = await import('js-yaml').catch(() => null) as any;
        const workflowsDir = resolvePkg('@specverse/engines', 'assets/prompts/core/standard/default');
        let workflowCount = 0;
        if (yamlLib && workflowsDir && existsSync(workflowsDir)) {
          const entries = readdirSync(workflowsDir).filter((f: string) => f.endsWith('.prompt.yaml'));
          for (const entry of entries) {
            try {
              const parsed = yamlLib.load(readFileSync(join(workflowsDir, entry), 'utf8')) as any;
              if (!parsed?.name) continue;
              const md = buildWorkflowMarkdown(parsed);
              writeFileSync(join(targetDir, 'workflows', parsed.name + '.md'), md);
              workflowCount++;
            } catch (err: any) {
              console.warn('  (skipped workflow ' + entry + ' \u2014 ' + err.message + ')');
            }
          }
        } else {
          console.warn('  (no workflows emitted \u2014 js-yaml or @specverse/engines prompts unavailable)');
        }

        // 7. Copy the build-generated family skills (analyse / create / verify /
        //    behavior / manifest) from @specverse/assets. These carry the AI-op
        //    grounding that the claude-cli delivery path auto-loads (Phase 2b of
        //    the prompt-partial-composition proposal): with the family skill
        //    installed, \`spv ai <op>\` on claude-cli sends a thin prompt + lets the
        //    CLI load the grounding from the skill (cached). They install as
        //    SIBLINGS of the specverse skill at the skills root, using the same
        //    tag the runtime loads (\`SPECVERSE_PROMPT_TAG\`, default \`current\`).
        const skillsRoot = dirname(targetDir);
        const promptTag = process.env.SPECVERSE_PROMPT_TAG || 'current';
        const familySkillsDir = resolvePkg('@specverse/assets', 'prompts/_composed/' + promptTag + '/skills');
        let familySkillCount = 0;
        const familyNames: string[] = [];
        if (familySkillsDir && existsSync(familySkillsDir)) {
          for (const family of readdirSync(familySkillsDir)) {
            const srcFamily = join(familySkillsDir, family);
            if (!existsSync(join(srcFamily, 'SKILL.md'))) continue; // only real skill dirs
            cpSync(srcFamily, join(skillsRoot, family), { recursive: true });
            familySkillCount++;
            familyNames.push(family);
          }
        } else {
          console.warn('  (no family skills \u2014 @specverse/assets has no _composed/' + promptTag + '/skills; pre-Phase-2b assets)');
        }

        // 8. Report.
        console.log('Installed SpecVerse skill \u2192 ' + targetDir);
        console.log('  SKILL.md + ' + refCopied + ' reference file(s) + ' + workflowCount + ' workflow(s)');
        if (familySkillCount > 0) {
          console.log('  + ' + familySkillCount + ' family skill(s) [' + familyNames.join(', ') + '] \u2192 ' + skillsRoot);
        }
        if (!options.global && !options.target) {
          console.log('');
          console.log('Tip: use --global to install to ~/.claude/skills/ for cross-project use.');
        }

        function buildWorkflowMarkdown(parsed: any): string {
          const parts: string[] = [];
          parts.push('# SpecVerse workflow: ' + parsed.name);
          parts.push('');
          if (parsed.description) {
            parts.push('> ' + parsed.description);
            parts.push('');
          }
          const vars = Array.isArray(parsed?.user?.variables) ? parsed.user.variables : [];
          if (vars.length > 0) {
            parts.push('## Inputs');
            parts.push('');
            parts.push('| Variable | Required | Description |');
            parts.push('|---|---|---|');
            for (const v of vars) {
              const req = v.required === false ? 'no' : 'yes';
              const desc = (v.description || '').replace(/\\|/g, '\\\\|');
              parts.push('| \`' + v.name + '\` | ' + req + ' | ' + desc + ' |');
            }
            parts.push('');
          }
          if (parsed?.system?.role) {
            parts.push('## Role');
            parts.push('');
            parts.push(String(parsed.system.role).trim());
            parts.push('');
          }
          if (parsed?.system?.context) {
            parts.push('## Instructions');
            parts.push('');
            parts.push(String(parsed.system.context).trim());
            parts.push('');
          }
          if (parsed?.user?.template) {
            parts.push('## Task template');
            parts.push('');
            parts.push('When invoking this workflow, fill the variables above into the template below:');
            parts.push('');
            parts.push('\`\`\`');
            parts.push(String(parsed.user.template).trim());
            parts.push('\`\`\`');
            parts.push('');
          }
          return parts.join('\\n');
        }`
  }
};
function generateLeafCommand(name, description, commandStr, optionDefs, actionParams, serviceRef, exitCodes) {
  const engineHandler = ENGINE_HANDLERS[name];
  const handler = engineHandler ? engineHandler.handler : serviceRef ? `const service = new ${serviceRef}();
      const result = await service.execute(${actionParams.includes(":") ? "{ " + actionParams.split(",").map((p) => p.trim().split(":")[0].trim()).join(", ") + ", ...options }" : "options"});
      console.log(result);` : `console.log('Executing ${name}...');
      // TODO: Wire to service`;
  const finalActionParams = renameUnusedActionParams(actionParams, handler);
  return `program
    .command('${commandStr}')
    .description('${escapeSingleQuotes(description)}')
${optionDefs.join("\n")}
    .action(async (${finalActionParams}) => {
      try {
        ${handler}
      } catch (error: any) {
        console.error('Error:', error.message);
        process.exit(${Object.keys(exitCodes).find((k) => k !== "0") || "1"});
      }
    });`;
}
function escapeSingleQuotes(s) {
  return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
}
function commanderDefault(value, flagType) {
  if (flagType === "boolean") return JSON.stringify(Boolean(value));
  if (typeof value === "number") return JSON.stringify(String(value));
  return JSON.stringify(value);
}
function renameUnusedActionParams(actionParams, handler) {
  if (!actionParams.trim()) return actionParams;
  return actionParams.split(",").map((param) => {
    const trimmed = param.trim();
    const nameMatch = trimmed.match(/^(\w+)/);
    if (!nameMatch) return param;
    const name = nameMatch[1];
    if (name.startsWith("_")) return param;
    const re = new RegExp(`\\b${name}\\b`);
    if (re.test(handler)) return param;
    return param.replace(new RegExp(`\\b${name}\\b`), `_${name}`);
  }).join(", ");
}
function generateCommandWithSubcommands(name, description, subcommands, optionDefs) {
  const subcmdRegistrations = Object.entries(subcommands).map(([subName, subDef]) => {
    const subDesc = subDef.description || "";
    const subArgs = subDef.arguments || {};
    const subFlags = subDef.flags || {};
    const positionalStr = Object.entries(subArgs).filter(([_, a]) => a.positional).map(([n, a]) => a.required ? `<${n}>` : `[${n}]`).join(" ");
    const subCmdStr = positionalStr ? `${subName} ${positionalStr}` : subName;
    const subOptionDefs = Object.entries(subFlags).map(([flagName, flag]) => {
      const alias = flag.alias ? `${flag.alias}, ` : "";
      const flagType = flag.type?.toLowerCase();
      const isDefaultTrueBoolean = flagType === "boolean" && flag.default === true;
      const emitName = isDefaultTrueBoolean ? flagName.replace(/^--/, "--no-") : flagName;
      const valuePart = flagType === "boolean" ? "" : ` <${flagName.replace(/^--/, "")}>`;
      const defaultVal = flag.default !== void 0 && !isDefaultTrueBoolean ? `, ${commanderDefault(flag.default, flagType)}` : "";
      const desc = escapeSingleQuotes(flag.description || flagName);
      return `    .option('${alias}${emitName}${valuePart}', '${desc}'${defaultVal})`;
    });
    const handlerKey = `${name}.${subName}`;
    const engineHandler = ENGINE_HANDLERS[handlerKey];
    const subArgTypes = Object.entries(subArgs).filter(([_, a]) => a.positional).map(([n, a]) => `${n}: ${mapArgTypeToTS(a.type)}`);
    const subActionParams = subArgTypes.length > 0 ? subArgTypes.join(", ") + ", options: any" : "options: any";
    const handler = engineHandler ? engineHandler.handler : `console.log('${name} ${subName}: not yet implemented via engine');`;
    const finalSubActionParams = renameUnusedActionParams(subActionParams, handler);
    return `
  cmd
    .command('${subCmdStr}')
    .description('${escapeSingleQuotes(subDesc)}')
${subOptionDefs.join("\n")}
    .action(async (${finalSubActionParams}) => {
      try {
        ${handler}
      } catch (error: any) {
        console.error('Error:', error.message);
        process.exit(1);
      }
    });`;
  });
  return `const cmd = program
    .command('${name}')
    .description('${escapeSingleQuotes(description)}');
${subcmdRegistrations.join("\n")}`;
}
function generateSubcommandRegistrations(_parentName, _subcommands) {
  return "";
}
function mapFlagTypeToTS(type) {
  if (!type) return "string";
  const lower = type.toLowerCase();
  if (lower === "boolean") return "boolean";
  if (lower === "number" || lower === "integer") return "number";
  return "string";
}
function mapArgTypeToTS(type) {
  if (!type) return "string";
  const lower = type.toLowerCase();
  if (lower === "filepath" || lower === "string") return "string";
  if (lower === "number" || lower === "integer") return "number";
  if (lower === "boolean") return "boolean";
  return "string";
}
function capitalize(str) {
  return str.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
}
function deduplicateImports(importBlocks, usedIn) {
  const seen = /* @__PURE__ */ new Map();
  const typeImports = /* @__PURE__ */ new Map();
  const rawLines = /* @__PURE__ */ new Set();
  const isUsed = (name) => {
    if (!usedIn) return true;
    return new RegExp(`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(usedIn);
  };
  for (const block of importBlocks) {
    for (const line of block.split("\n").map((l) => l.trim()).filter((l) => l)) {
      const namedMatch = line.match(/^import\s+\{\s*(.+?)\s*\}\s+from\s+'(.+?)';?$/);
      if (namedMatch) {
        const names = namedMatch[1].split(",").map((n) => n.trim());
        const mod = namedMatch[2];
        if (!seen.has(mod)) seen.set(mod, /* @__PURE__ */ new Set());
        names.forEach((n) => seen.get(mod).add(n));
        continue;
      }
      const typeMatch = line.match(/^import\s+type\s+\{\s*(.+?)\s*\}\s+from\s+'(.+?)';?$/);
      if (typeMatch) {
        const names = typeMatch[1].split(",").map((n) => n.trim());
        const mod = typeMatch[2];
        if (!typeImports.has(mod)) typeImports.set(mod, /* @__PURE__ */ new Set());
        names.forEach((n) => typeImports.get(mod).add(n));
        continue;
      }
      rawLines.add(line);
    }
  }
  const result = [];
  for (const [mod, names] of seen.entries()) {
    const used = [...names].filter(isUsed);
    if (used.length > 0) {
      result.push(`import { ${used.join(", ")} } from '${mod}';`);
    }
  }
  for (const [mod, names] of typeImports.entries()) {
    const regularNames = seen.get(mod) || /* @__PURE__ */ new Set();
    const typeOnly = [...names].filter((n) => !regularNames.has(n)).filter(isUsed);
    if (typeOnly.length > 0) {
      result.push(`import type { ${typeOnly.join(", ")} } from '${mod}';`);
    }
  }
  for (const line of rawLines) {
    result.push(line);
  }
  return result.join("\n");
}
export {
  generateCommand as default
};
