{"version":3,"file":"doctor-Cbn2k8Hl.mjs","names":["colors","colors"],"sources":["../src/utils/naming.ts","../src/utils/regex.ts","../src/generators/templates/module-index.ts","../src/generators/templates/controller.ts","../src/generators/templates/dtos.ts","../src/generators/templates/repository.ts","../src/generators/templates/tests.ts","../src/generators/templates/rest-service.ts","../src/generators/patterns/minimal.ts","../src/generators/patterns/rest.ts","../src/generators/module.ts","../src/generators/adapter.ts","../src/utils/resolve-out-dir.ts","../src/generators/middleware.ts","../src/generators/guard.ts","../src/generators/service.ts","../src/generators/controller.ts","../src/generators/dto.ts","../src/typegen/plugin.ts","../src/commands/typegen-error-reporter.ts","../src/typegen/dev-watcher.ts","../src/generator-extension/context.ts","../src/generator-extension/discover.ts","../src/generator-extension/dispatch.ts","../src/commands/doctor.ts"],"sourcesContent":["import pkg from 'pluralize'\n\n/** Convert a name to PascalCase */\nexport function toPascalCase(name: string): string {\n  return name\n    .replace(/[-_\\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))\n    .replace(/^(.)/, (c) => c.toUpperCase())\n}\n\n/** Convert a name to camelCase */\nexport function toCamelCase(name: string): string {\n  const pascal = toPascalCase(name)\n  return pascal.charAt(0).toLowerCase() + pascal.slice(1)\n}\n\n/** Convert a name to kebab-case */\nexport function toKebabCase(name: string): string {\n  return name\n    .replace(/([a-z])([A-Z])/g, '$1-$2')\n    .replace(/[\\s_]+/g, '-')\n    .toLowerCase()\n}\n\n/**\n * Pluralize a kebab-case name for directory/file names.\n * Uses the `pluralize` npm package for correct English pluralization\n * including irregulars (person → people, status → statuses, child → children).\n */\nexport function pluralize(name: string): string {\n  return pkg.plural(name)\n}\n\n/**\n * Pluralize a PascalCase name for class identifiers.\n * Used for `List${pluralPascal}UseCase` to avoid `ListUserssUseCase`.\n */\nexport function pluralizePascal(name: string): string {\n  return pkg.plural(name)\n}\n","/**\n * Escape a string for safe interpolation into a `new RegExp(...)`\n * source. Each character that has special meaning inside a regex\n * pattern (`.`, `*`, `+`, `?`, `^`, `$`, `{`, `}`, `(`, `)`, `|`,\n * `[`, `]`, `\\`) is prefixed with a backslash so it's matched\n * literally instead of treated as a metacharacter.\n *\n * Used everywhere the codegen / removal flow builds a regex from\n * adopter-provided identifiers (`pascal`, `plural`, etc). Names\n * coming through `toPascalCase` / `toKebabCase` are already\n * alphanumeric in practice, but escaping defensively keeps the\n * code correct if upstream sanitization ever loosens.\n */\nexport function escapeRegex(input: string): string {\n  return input.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n","import type { RepoType } from '../module'\nimport type { ModuleStyle, TemplateContext } from './types'\n\nconst repoLabelMap: Record<string, string> = {\n  inmemory: 'in-memory',\n  drizzle: 'Drizzle',\n  prisma: 'Prisma',\n}\n\nfunction toPascalRepoType(repo: string): string {\n  return (\n    repo.charAt(0).toUpperCase() +\n    repo.slice(1).replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())\n  )\n}\n\nfunction toKebabRepoType(repo: string): string {\n  return repo.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()\n}\n\nfunction repoLabel(repo: RepoType): string {\n  return repoLabelMap[repo] ?? toPascalRepoType(repo)\n}\n\nfunction repoMaps(pascal: string, kebab: string, repo: RepoType) {\n  const repoClassMap: Record<string, string> = {\n    inmemory: `InMemory${pascal}Repository`,\n    drizzle: `Drizzle${pascal}Repository`,\n    prisma: `Prisma${pascal}Repository`,\n  }\n  const repoFileMap: Record<string, string> = {\n    inmemory: `in-memory-${kebab}`,\n    drizzle: `drizzle-${kebab}`,\n    prisma: `prisma-${kebab}`,\n  }\n  return {\n    repoClass: repoClassMap[repo] ?? `${toPascalRepoType(repo)}${pascal}Repository`,\n    repoFile: repoFileMap[repo] ?? `${toKebabRepoType(repo)}-${kebab}`,\n  }\n}\n\n/** Resolve the style flag, defaulting to 'define' for new code. */\nfunction resolveStyle(style?: ModuleStyle): ModuleStyle {\n  return style ?? 'define'\n}\n\n/** DDD module index — nested folders, use-cases, domain services */\nexport function generateModuleIndex(ctx: TemplateContext & { repo: RepoType }): string {\n  const { pascal, kebab, plural = '', repo, style } = ctx\n  const { repoClass, repoFile } = repoMaps(pascal, kebab, repo)\n  const resolvedStyle = resolveStyle(style)\n\n  const header = `/**\n * ${pascal} Module\n *\n * Self-contained feature module following Domain-Driven Design (DDD).\n * Registers dependencies in the DI container and declares HTTP routes.\n *\n * Structure:\n *   presentation/    — HTTP controllers (entry points)\n *   application/     — Use cases (orchestration) and DTOs (validation)\n *   domain/          — Entities, value objects, repository interfaces, domain services\n *   infrastructure/  — Repository implementations (currently ${repoLabel(repo)})\n */`\n\n  const repoImports = `import { ${pascal.toUpperCase()}_REPOSITORY } from './domain/repositories/${kebab}.repository'\nimport { ${repoClass} } from './infrastructure/repositories/${repoFile}.repository'\nimport { ${pascal}Controller } from './presentation/${kebab}.controller'\n\n// Eagerly load decorated classes so @Controller()/@Service()/@Repository() decorators\n// register in the DI container. Recursive globs (./**/) so the module keeps working\n// however you nest files (e.g. moving controllers into a controllers/ sub-folder).\nimport.meta.glob(\n  [\n    './**/*.controller.ts',\n    './**/*.service.ts',\n    './**/*.repository.ts',\n    './application/use-cases/**/*.ts',\n    '!./**/*.test.ts',\n  ],\n  { eager: true },\n)`\n\n  const routesDoc = `    /**\n     * Declare HTTP routes for this module. Return value shape:\n     *\n     *   - \\`path\\`        — URL prefix for this route set, mounted under\n     *                     \\`/{apiPrefix}/v{version}{path}\\`.\n     *   - \\`controller\\`  — Controller class. Used both for the route\n     *                     handler bindings and OpenAPI spec generation.\n     *   - \\`version\\`     — Optional. Overrides the app-wide API version\n     *                     for this route set only.\n     *\n     * Return an **array** to mount multiple route sets under the\n     * same module (e.g. side-by-side v1 + v2 controllers):\n     *\n     *   return [\n     *     { path: '/${plural}', version: 1, controller: ${pascal}V1Controller },\n     *     { path: '/${plural}', version: 2, controller: ${pascal}V2Controller },\n     *   ]\n     */`\n\n  if (resolvedStyle === 'class') {\n    return `${header}\nimport { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs'\n${repoImports}\n\nexport class ${pascal}Module implements AppModule {\n  /**\n   * Register module dependencies in the DI container.\n   * Bind repository interface tokens to their implementations here.\n   * Currently wired to ${repoLabel(repo)}. To swap implementations, change the factory target.\n   */\n  register(container: Container): void {\n    container.registerFactory(${pascal.toUpperCase()}_REPOSITORY, () =>\n      container.resolve(${repoClass}),\n    )\n  }\n\n${routesDoc.replace(/^ {4}/gm, '  ').replace(/^ {6}/gm, '    ')}\n  routes(): ModuleRoutes {\n    return {\n      path: '/${plural}',\n      controller: ${pascal}Controller,\n    }\n  }\n}\n`\n  }\n\n  return `${header}\nimport { defineModule } from '@forinda/kickjs'\n${repoImports}\n\nexport const ${pascal}Module = defineModule({\n  name: '${pascal}Module',\n  build: () => ({\n    /**\n     * Register module dependencies in the DI container.\n     * Bind repository interface tokens to their implementations here.\n     * Currently wired to ${repoLabel(repo)}. To swap implementations, change the factory target.\n     */\n    register(container) {\n      container.registerFactory(${pascal.toUpperCase()}_REPOSITORY, () =>\n        container.resolve(${repoClass}),\n      )\n    },\n\n${routesDoc}\n    routes() {\n      return {\n        path: '/${plural}',\n        controller: ${pascal}Controller,\n      }\n    },\n  }),\n})\n`\n}\n\n/** REST module index — flat folder, service + controller, no use-cases */\nexport function generateRestModuleIndex(ctx: TemplateContext & { repo: RepoType }): string {\n  const { pascal, kebab, plural = '', repo, style } = ctx\n  const { repoClass, repoFile } = repoMaps(pascal, kebab, repo)\n  const resolvedStyle = resolveStyle(style)\n\n  const header = `/**\n * ${pascal} Module\n *\n * REST module with a flat folder structure.\n * Controller delegates to service, service wraps the repository.\n *\n * Structure:\n *   ${kebab}.controller.ts  — HTTP routes (CRUD)\n *   ${kebab}.service.ts     — Business logic\n *   ${kebab}.repository.ts  — Repository interface\n *   ${repoFile}.repository.ts — Repository implementation\n *   dtos/                   — Request/response schemas\n */`\n\n  const repoImports = `import { ${pascal.toUpperCase()}_REPOSITORY } from './${kebab}.repository'\nimport { ${repoClass} } from './${repoFile}.repository'\nimport { ${pascal}Controller } from './${kebab}.controller'\n\n// Eagerly load decorated classes so @Controller()/@Service()/@Repository() decorators\n// register in the DI container. Recursive globs (./**/) so the module keeps working\n// however you nest files (e.g. moving controllers into a controllers/ sub-folder).\nimport.meta.glob(\n  ['./**/*.controller.ts', './**/*.service.ts', './**/*.repository.ts', '!./**/*.test.ts'],\n  { eager: true },\n)`\n\n  const routesDoc = `    /**\n     * Declare HTTP routes for this module. Return value shape:\n     *\n     *   - \\`path\\`        — URL prefix for this route set.\n     *   - \\`controller\\`  — Controller class (also drives OpenAPI).\n     *   - \\`version\\`     — Optional. Overrides the app-wide API version.\n     *\n     * Return an **array** to mount multiple route sets — admin\n     * surfaces, side-by-side v1 + v2 controllers, etc:\n     *\n     *   return [\n     *     { path: '/${plural}', version: 1, controller: ${pascal}V1Controller },\n     *     { path: '/${plural}', version: 2, controller: ${pascal}V2Controller },\n     *   ]\n     */`\n\n  if (resolvedStyle === 'class') {\n    return `${header}\nimport { Container, type AppModule, type ModuleRoutes } from '@forinda/kickjs'\n${repoImports}\n\nexport class ${pascal}Module implements AppModule {\n  register(container: Container): void {\n    container.registerFactory(${pascal.toUpperCase()}_REPOSITORY, () =>\n      container.resolve(${repoClass}),\n    )\n  }\n\n${routesDoc.replace(/^ {4}/gm, '  ').replace(/^ {6}/gm, '    ')}\n  routes(): ModuleRoutes {\n    return {\n      path: '/${plural}',\n      controller: ${pascal}Controller,\n    }\n  }\n}\n`\n  }\n\n  return `${header}\nimport { defineModule } from '@forinda/kickjs'\n${repoImports}\n\nexport const ${pascal}Module = defineModule({\n  name: '${pascal}Module',\n  build: () => ({\n    register(container) {\n      container.registerFactory(${pascal.toUpperCase()}_REPOSITORY, () =>\n        container.resolve(${repoClass}),\n      )\n    },\n\n${routesDoc}\n    routes() {\n      return {\n        path: '/${plural}',\n        controller: ${pascal}Controller,\n      }\n    },\n  }),\n})\n`\n}\n\n/** Minimal module index — just controller, no service/repo */\nexport function generateMinimalModuleIndex(ctx: TemplateContext): string {\n  const { pascal, kebab, plural = '', style } = ctx\n  const resolvedStyle = resolveStyle(style)\n\n  const routesDoc = `    /**\n     * Declare HTTP routes. Return value shape:\n     *\n     *   - \\`path\\`        — URL prefix for this route set.\n     *   - \\`controller\\`  — Controller class (also drives OpenAPI).\n     *   - \\`version\\`     — Optional. Overrides the app-wide API version.\n     *\n     * Return an array to mount multiple route sets:\n     *\n     *   return [\n     *     { path: '/${plural}', version: 1, controller: ${pascal}V1Controller },\n     *     { path: '/${plural}', version: 2, controller: ${pascal}V2Controller },\n     *   ]\n     */`\n\n  if (resolvedStyle === 'class') {\n    return `import { type AppModule, type ModuleRoutes } from '@forinda/kickjs'\nimport { ${pascal}Controller } from './${kebab}.controller'\n\nexport class ${pascal}Module implements AppModule {\n${routesDoc.replace(/^ {4}/gm, '  ').replace(/^ {6}/gm, '    ')}\n  routes(): ModuleRoutes {\n    return {\n      path: '/${plural}',\n      controller: ${pascal}Controller,\n    }\n  }\n}\n`\n  }\n\n  return `import { defineModule } from '@forinda/kickjs'\nimport { ${pascal}Controller } from './${kebab}.controller'\n\nexport const ${pascal}Module = defineModule({\n  name: '${pascal}Module',\n  build: () => ({\n${routesDoc}\n    routes() {\n      return {\n        path: '/${plural}',\n        controller: ${pascal}Controller,\n      }\n    },\n  }),\n})\n`\n}\n","import type { TemplateContext } from './types'\n\n/** DDD controller — injects use-cases, nested import paths */\nexport function generateController(ctx: TemplateContext): string {\n  const { pascal, kebab, plural = '', pluralPascal = '' } = ctx\n  // Wrapped exactly as Prettier would at the scaffold's printWidth of 100 —\n  // a single-line import is 112 chars, so `pnpm format` on a fresh project\n  // would otherwise rewrite this file on the adopter's first run.\n  return `import {\n  Controller,\n  Get,\n  Post,\n  Put,\n  Delete,\n  Autowired,\n  ApiQueryParams,\n  reply,\n  type Ctx,\n} from '@forinda/kickjs'\nimport { ApiTags } from '@forinda/kickjs-swagger'\nimport { Create${pascal}UseCase } from '../application/use-cases/create-${kebab}.use-case'\nimport { Get${pascal}UseCase } from '../application/use-cases/get-${kebab}.use-case'\nimport { List${pluralPascal}UseCase } from '../application/use-cases/list-${plural}.use-case'\nimport { Update${pascal}UseCase } from '../application/use-cases/update-${kebab}.use-case'\nimport { Delete${pascal}UseCase } from '../application/use-cases/delete-${kebab}.use-case'\nimport { create${pascal}Schema } from '../application/dtos/create-${kebab}.dto'\nimport { update${pascal}Schema } from '../application/dtos/update-${kebab}.dto'\nimport { ${pascal.toUpperCase()}_QUERY_CONFIG } from '../constants'\n\n// Each handler annotates its \\`ctx\\` with \\`Ctx<KickRoutes.${pascal}Controller['<method>']>\\`\n// so \\`ctx.params\\`, \\`ctx.body\\`, and \\`ctx.query\\` are typed end-to-end.\n// The \\`KickRoutes\\` namespace is generated by \\`kick typegen\\` (auto-run on\n// \\`kick dev\\`) — see https://kickjs.app/guide/typegen.\n//\n// Handlers RETURN their payload rather than calling \\`ctx.json()\\`: the runtime\n// auto-sends it, and \\`kick typegen\\` reads the return type to fill in\n// \\`KickRoutes[...].response\\` — which is what makes the typed client know what\n// each route gives back. A handler that writes via \\`ctx.json()\\` and returns\n// nothing infers as \\`unknown\\`. Use \\`reply(status, body)\\` (or the\n// \\`reply.created\\` / \\`reply.noContent\\` sugars) when the status isn't 200, and\n// \\`ctx.problem.*\\` for error branches — those send immediately and return\n// nothing, so they stay out of the success type.\n\n@Controller()\nexport class ${pascal}Controller {\n  @Autowired() private readonly create${pascal}UseCase!: Create${pascal}UseCase\n  @Autowired() private readonly get${pascal}UseCase!: Get${pascal}UseCase\n  @Autowired() private readonly list${pluralPascal}UseCase!: List${pluralPascal}UseCase\n  @Autowired() private readonly update${pascal}UseCase!: Update${pascal}UseCase\n  @Autowired() private readonly delete${pascal}UseCase!: Delete${pascal}UseCase\n\n  @Get('/')\n  @ApiTags('${pascal}')\n  @ApiQueryParams(${pascal.toUpperCase()}_QUERY_CONFIG)\n  async list(ctx: Ctx<KickRoutes.${pascal}Controller['list']>) {\n    return ctx.paginate(\n      (parsed) => this.list${pluralPascal}UseCase.execute(parsed),\n      ${pascal.toUpperCase()}_QUERY_CONFIG,\n    )\n  }\n\n  @Get('/:id')\n  @ApiTags('${pascal}')\n  async getById(ctx: Ctx<KickRoutes.${pascal}Controller['getById']>) {\n    const result = await this.get${pascal}UseCase.execute(ctx.params.id)\n    if (!result) {\n      // Sends RFC 9457 problem+json now; returning nothing keeps the 404 out\n      // of this route's inferred response type.\n      ctx.problem.notFound({ detail: \\`${pascal} \\${ctx.params.id} not found\\` })\n      return\n    }\n    return result\n  }\n\n  @Post('/', { body: create${pascal}Schema, name: 'Create${pascal}' })\n  @ApiTags('${pascal}')\n  async create(ctx: Ctx<KickRoutes.${pascal}Controller['create']>) {\n    return reply.created(await this.create${pascal}UseCase.execute(ctx.body))\n  }\n\n  @Put('/:id', { body: update${pascal}Schema, name: 'Update${pascal}' })\n  @ApiTags('${pascal}')\n  async update(ctx: Ctx<KickRoutes.${pascal}Controller['update']>) {\n    return this.update${pascal}UseCase.execute(ctx.params.id, ctx.body)\n  }\n\n  @Delete('/:id')\n  @ApiTags('${pascal}')\n  async remove(ctx: Ctx<KickRoutes.${pascal}Controller['remove']>) {\n    await this.delete${pascal}UseCase.execute(ctx.params.id)\n    return reply.noContent()\n  }\n}\n`\n}\n\n/** REST controller — injects service directly, flat import paths */\nexport function generateRestController(ctx: TemplateContext): string {\n  const { pascal, kebab } = ctx\n  const camel = pascal.charAt(0).toLowerCase() + pascal.slice(1)\n  // Wrapped exactly as Prettier would at the scaffold's printWidth of 100 —\n  // a single-line import is 112 chars, so `pnpm format` on a fresh project\n  // would otherwise rewrite this file on the adopter's first run.\n  return `import {\n  Controller,\n  Get,\n  Post,\n  Put,\n  Delete,\n  Autowired,\n  ApiQueryParams,\n  reply,\n  type Ctx,\n} from '@forinda/kickjs'\nimport { ApiTags } from '@forinda/kickjs-swagger'\nimport { ${pascal}Service } from './${kebab}.service'\nimport { create${pascal}Schema } from './dtos/create-${kebab}.dto'\nimport { update${pascal}Schema } from './dtos/update-${kebab}.dto'\nimport { ${pascal.toUpperCase()}_QUERY_CONFIG } from './${kebab}.constants'\n\n// Each handler annotates its \\`ctx\\` with \\`Ctx<KickRoutes.${pascal}Controller['<method>']>\\`\n// so \\`ctx.params\\`, \\`ctx.body\\`, and \\`ctx.query\\` are typed end-to-end.\n// The \\`KickRoutes\\` namespace is generated by \\`kick typegen\\` (auto-run on\n// \\`kick dev\\`) — see https://kickjs.app/guide/typegen.\n//\n// Handlers RETURN their payload rather than calling \\`ctx.json()\\`: the runtime\n// auto-sends it, and \\`kick typegen\\` reads the return type to fill in\n// \\`KickRoutes[...].response\\` — which is what makes the typed client know what\n// each route gives back. A handler that writes via \\`ctx.json()\\` and returns\n// nothing infers as \\`unknown\\`. Use \\`reply(status, body)\\` (or the\n// \\`reply.created\\` / \\`reply.noContent\\` sugars) when the status isn't 200, and\n// \\`ctx.problem.*\\` for error branches — those send immediately and return\n// nothing, so they stay out of the success type.\n\n@Controller()\nexport class ${pascal}Controller {\n  @Autowired() private readonly ${camel}Service!: ${pascal}Service\n\n  @Get('/')\n  @ApiTags('${pascal}')\n  @ApiQueryParams(${pascal.toUpperCase()}_QUERY_CONFIG)\n  async list(ctx: Ctx<KickRoutes.${pascal}Controller['list']>) {\n    return ctx.paginate(\n      (parsed) => this.${camel}Service.findPaginated(parsed),\n      ${pascal.toUpperCase()}_QUERY_CONFIG,\n    )\n  }\n\n  @Get('/:id')\n  @ApiTags('${pascal}')\n  async getById(ctx: Ctx<KickRoutes.${pascal}Controller['getById']>) {\n    const result = await this.${camel}Service.findById(ctx.params.id)\n    if (!result) {\n      // Sends RFC 9457 problem+json now; returning nothing keeps the 404 out\n      // of this route's inferred response type.\n      ctx.problem.notFound({ detail: \\`${pascal} \\${ctx.params.id} not found\\` })\n      return\n    }\n    return result\n  }\n\n  @Post('/', { body: create${pascal}Schema, name: 'Create${pascal}' })\n  @ApiTags('${pascal}')\n  async create(ctx: Ctx<KickRoutes.${pascal}Controller['create']>) {\n    return reply.created(await this.${camel}Service.create(ctx.body))\n  }\n\n  @Put('/:id', { body: update${pascal}Schema, name: 'Update${pascal}' })\n  @ApiTags('${pascal}')\n  async update(ctx: Ctx<KickRoutes.${pascal}Controller['update']>) {\n    return this.${camel}Service.update(ctx.params.id, ctx.body)\n  }\n\n  @Delete('/:id')\n  @ApiTags('${pascal}')\n  async remove(ctx: Ctx<KickRoutes.${pascal}Controller['remove']>) {\n    await this.${camel}Service.delete(ctx.params.id)\n    return reply.noContent()\n  }\n}\n`\n}\n","import type { TemplateContext } from './types'\n\nexport function generateCreateDTO(ctx: TemplateContext): string {\n  const { pascal } = ctx\n  return `import { z } from 'zod'\n\n/**\n * Create ${pascal} DTO — Zod schema for validating POST request bodies.\n * This schema is passed to @Post('/', { body: create${pascal}Schema }) for automatic validation.\n * It also generates OpenAPI request body docs when SwaggerAdapter is used.\n *\n * Add more fields as needed. Supported Zod types:\n *   z.string(), z.number(), z.boolean(), z.enum([...]),\n *   z.array(), z.object(), .optional(), .default(), .transform()\n */\nexport const create${pascal}Schema = z.object({\n  name: z.string().min(1, 'Name is required').max(200),\n})\n\nexport type Create${pascal}DTO = z.infer<typeof create${pascal}Schema>\n`\n}\n\nexport function generateUpdateDTO(ctx: TemplateContext): string {\n  const { pascal } = ctx\n  return `import { z } from 'zod'\n\nexport const update${pascal}Schema = z.object({\n  name: z.string().min(1).max(200).optional(),\n})\n\nexport type Update${pascal}DTO = z.infer<typeof update${pascal}Schema>\n`\n}\n\nexport function generateResponseDTO(ctx: TemplateContext): string {\n  const { pascal } = ctx\n  return `export interface ${pascal}ResponseDTO {\n  id: string\n  name: string\n  createdAt: string\n  updatedAt: string\n}\n`\n}\n","import type { TemplateContext } from './types'\n\nexport function generateRepositoryInterface(ctx: TemplateContext): string {\n  const { pascal, kebab, dtoPrefix = '../../application/dtos', tokenScope = 'app' } = ctx\n  return `/**\n * ${pascal} Repository Interface\n *\n * Defines the contract for data access.\n * The interface declares what operations are available;\n * implementations (in-memory, Drizzle, Prisma) fulfill the contract.\n *\n * To swap implementations, change the factory in the module's register() method.\n */\nimport { createToken } from '@forinda/kickjs'\nimport type { ${pascal}ResponseDTO } from '${dtoPrefix}/${kebab}-response.dto'\nimport type { Create${pascal}DTO } from '${dtoPrefix}/create-${kebab}.dto'\nimport type { Update${pascal}DTO } from '${dtoPrefix}/update-${kebab}.dto'\nimport type { ParsedQuery } from '@forinda/kickjs'\n\nexport interface I${pascal}Repository {\n  findById(id: string): Promise<${pascal}ResponseDTO | null>\n  findAll(): Promise<${pascal}ResponseDTO[]>\n  findPaginated(parsed: ParsedQuery): Promise<{ data: ${pascal}ResponseDTO[]; total: number }>\n  create(dto: Create${pascal}DTO): Promise<${pascal}ResponseDTO>\n  update(id: string, dto: Update${pascal}DTO): Promise<${pascal}ResponseDTO>\n  delete(id: string): Promise<void>\n}\n\n/**\n * Collision-safe DI token bound to \\`I${pascal}Repository\\`.\n * \\`container.resolve(${pascal.toUpperCase()}_REPOSITORY)\\` and\n * \\`@Inject(${pascal.toUpperCase()}_REPOSITORY)\\` both return the typed\n * interface — no manual generic, no \\`any\\` cast.\n *\n * The \\`'${tokenScope}/'\\` prefix matches the project scope so\n * \\`kick-lint\\`'s \\`token-reserved-prefix\\` rule never fires —\n * adopters must NOT use the reserved \\`'kick/'\\` namespace.\n */\nexport const ${pascal.toUpperCase()}_REPOSITORY = createToken<I${pascal}Repository>('${tokenScope}/${pascal}/repository')\n`\n}\n\nexport function generateInMemoryRepository(ctx: TemplateContext): string {\n  const {\n    pascal,\n    kebab,\n    repoPrefix = '../../domain/repositories',\n    dtoPrefix = '../../application/dtos',\n  } = ctx\n  return `/**\n * In-Memory ${pascal} Repository\n *\n * Implements the repository interface using a Map.\n * Useful for prototyping and testing. Replace with a database implementation\n * (Drizzle, Prisma, etc.) for production use.\n *\n * @Repository() registers this class in the DI container as a singleton.\n */\nimport { randomUUID } from 'node:crypto'\nimport { Repository, HttpException } from '@forinda/kickjs'\nimport type { ParsedQuery } from '@forinda/kickjs'\nimport type { I${pascal}Repository } from '${repoPrefix}/${kebab}.repository'\nimport type { ${pascal}ResponseDTO } from '${dtoPrefix}/${kebab}-response.dto'\nimport type { Create${pascal}DTO } from '${dtoPrefix}/create-${kebab}.dto'\nimport type { Update${pascal}DTO } from '${dtoPrefix}/update-${kebab}.dto'\n\n@Repository()\nexport class InMemory${pascal}Repository implements I${pascal}Repository {\n  private store = new Map<string, ${pascal}ResponseDTO>()\n\n  async findById(id: string): Promise<${pascal}ResponseDTO | null> {\n    return this.store.get(id) ?? null\n  }\n\n  async findAll(): Promise<${pascal}ResponseDTO[]> {\n    return Array.from(this.store.values())\n  }\n\n  async findPaginated(parsed: ParsedQuery): Promise<{ data: ${pascal}ResponseDTO[]; total: number }> {\n    const all = Array.from(this.store.values())\n    const data = all.slice(parsed.pagination.offset, parsed.pagination.offset + parsed.pagination.limit)\n    return { data, total: all.length }\n  }\n\n  async create(dto: Create${pascal}DTO): Promise<${pascal}ResponseDTO> {\n    const now = new Date().toISOString()\n    const entity: ${pascal}ResponseDTO = {\n      id: randomUUID(),\n      ...dto,\n      createdAt: now,\n      updatedAt: now,\n    }\n    this.store.set(entity.id, entity)\n    return entity\n  }\n\n  async update(id: string, dto: Update${pascal}DTO): Promise<${pascal}ResponseDTO> {\n    const existing = this.store.get(id)\n    if (!existing) throw HttpException.notFound('${pascal} not found')\n    const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }\n    this.store.set(id, updated)\n    return updated\n  }\n\n  async delete(id: string): Promise<void> {\n    if (!this.store.has(id)) throw HttpException.notFound('${pascal} not found')\n    this.store.delete(id)\n  }\n}\n`\n}\n\nexport function generateCustomRepository(ctx: TemplateContext): string {\n  const {\n    pascal,\n    kebab,\n    repoType = '',\n    repoPrefix = '../../domain/repositories',\n    dtoPrefix = '../../application/dtos',\n  } = ctx\n  const repoTypePascal =\n    repoType.charAt(0).toUpperCase() +\n    repoType.slice(1).replace(/-([a-z])/g, (_, c: string) => c.toUpperCase())\n  return `/**\n * ${repoTypePascal} ${pascal} Repository\n *\n * Stub implementation for a custom '${repoType}' repository.\n * Implements the repository interface using an in-memory Map as a placeholder.\n *\n * TODO: Replace the in-memory Map with your ${repoType} data-access logic.\n * See I${pascal}Repository for the interface contract.\n *\n * @Repository() registers this class in the DI container as a singleton.\n */\nimport { randomUUID } from 'node:crypto'\nimport { Repository, HttpException } from '@forinda/kickjs'\nimport type { ParsedQuery } from '@forinda/kickjs'\nimport type { I${pascal}Repository } from '${repoPrefix}/${kebab}.repository'\nimport type { ${pascal}ResponseDTO } from '${dtoPrefix}/${kebab}-response.dto'\nimport type { Create${pascal}DTO } from '${dtoPrefix}/create-${kebab}.dto'\nimport type { Update${pascal}DTO } from '${dtoPrefix}/update-${kebab}.dto'\n\n@Repository()\nexport class ${repoTypePascal}${pascal}Repository implements I${pascal}Repository {\n  // TODO: Replace with your ${repoType} client/connection\n  private store = new Map<string, ${pascal}ResponseDTO>()\n\n  async findById(id: string): Promise<${pascal}ResponseDTO | null> {\n    // TODO: Implement with ${repoType}\n    return this.store.get(id) ?? null\n  }\n\n  async findAll(): Promise<${pascal}ResponseDTO[]> {\n    // TODO: Implement with ${repoType}\n    return Array.from(this.store.values())\n  }\n\n  async findPaginated(parsed: ParsedQuery): Promise<{ data: ${pascal}ResponseDTO[]; total: number }> {\n    // TODO: Implement with ${repoType}\n    const all = Array.from(this.store.values())\n    const data = all.slice(parsed.pagination.offset, parsed.pagination.offset + parsed.pagination.limit)\n    return { data, total: all.length }\n  }\n\n  async create(dto: Create${pascal}DTO): Promise<${pascal}ResponseDTO> {\n    // TODO: Implement with ${repoType}\n    const now = new Date().toISOString()\n    const entity: ${pascal}ResponseDTO = {\n      id: randomUUID(),\n      ...dto,\n      createdAt: now,\n      updatedAt: now,\n    }\n    this.store.set(entity.id, entity)\n    return entity\n  }\n\n  async update(id: string, dto: Update${pascal}DTO): Promise<${pascal}ResponseDTO> {\n    // TODO: Implement with ${repoType}\n    const existing = this.store.get(id)\n    if (!existing) throw HttpException.notFound('${pascal} not found')\n    const updated = { ...existing, ...dto, updatedAt: new Date().toISOString() }\n    this.store.set(id, updated)\n    return updated\n  }\n\n  async delete(id: string): Promise<void> {\n    // TODO: Implement with ${repoType}\n    if (!this.store.has(id)) throw HttpException.notFound('${pascal} not found')\n    this.store.delete(id)\n  }\n}\n`\n}\n","import type { TemplateContext } from './types'\n\nexport function generateControllerTest(ctx: TemplateContext): string {\n  const { pascal, kebab, plural = '' } = ctx\n  return `import { describe, it, expect, beforeEach } from 'vitest'\nimport { Container } from '@forinda/kickjs'\n\ndescribe('${pascal}Controller', () => {\n  beforeEach(() => {\n    Container.reset()\n  })\n\n  it('should be defined', () => {\n    expect(true).toBe(true)\n  })\n\n  describe('POST /${plural}', () => {\n    it('should create a new ${kebab}', async () => {\n      // TODO: Set up test module, call create endpoint, assert 201\n      expect(true).toBe(true)\n    })\n  })\n\n  describe('GET /${plural}', () => {\n    it('should return paginated ${plural}', async () => {\n      // TODO: Set up test module, call list endpoint, assert { data, meta }\n      expect(true).toBe(true)\n    })\n  })\n\n  describe('GET /${plural}/:id', () => {\n    it('should return a ${kebab} by id', async () => {\n      // TODO: Create a ${kebab}, then fetch by id, assert match\n      expect(true).toBe(true)\n    })\n\n    it('should return 404 for non-existent ${kebab}', async () => {\n      // TODO: Fetch non-existent id, assert 404\n      expect(true).toBe(true)\n    })\n  })\n\n  describe('PUT /${plural}/:id', () => {\n    it('should update an existing ${kebab}', async () => {\n      // TODO: Create, update, assert changes\n      expect(true).toBe(true)\n    })\n  })\n\n  describe('DELETE /${plural}/:id', () => {\n    it('should delete a ${kebab}', async () => {\n      // TODO: Create, delete, assert gone\n      expect(true).toBe(true)\n    })\n  })\n})\n`\n}\n\nexport function generateRepositoryTest(ctx: TemplateContext): string {\n  const {\n    pascal,\n    kebab,\n    plural = '',\n    repoPrefix = `../infrastructure/repositories/in-memory-${kebab}.repository`,\n  } = ctx\n  return `import { describe, it, expect, beforeEach } from 'vitest'\nimport { InMemory${pascal}Repository } from '${repoPrefix}'\n\ndescribe('InMemory${pascal}Repository', () => {\n  let repo: InMemory${pascal}Repository\n\n  beforeEach(() => {\n    repo = new InMemory${pascal}Repository()\n  })\n\n  it('should create and retrieve a ${kebab}', async () => {\n    const created = await repo.create({ name: 'Test ${pascal}' })\n    expect(created).toBeDefined()\n    expect(created.name).toBe('Test ${pascal}')\n    expect(created.id).toBeDefined()\n\n    const found = await repo.findById(created.id)\n    expect(found).toEqual(created)\n  })\n\n  it('should return null for non-existent id', async () => {\n    const found = await repo.findById('non-existent')\n    expect(found).toBeNull()\n  })\n\n  it('should list all ${plural}', async () => {\n    await repo.create({ name: '${pascal} 1' })\n    await repo.create({ name: '${pascal} 2' })\n\n    const all = await repo.findAll()\n    expect(all).toHaveLength(2)\n  })\n\n  it('should return paginated results', async () => {\n    await repo.create({ name: '${pascal} 1' })\n    await repo.create({ name: '${pascal} 2' })\n    await repo.create({ name: '${pascal} 3' })\n\n    const result = await repo.findPaginated({\n      filters: [],\n      sort: [],\n      search: '',\n      pagination: { page: 1, limit: 2, offset: 0 },\n    })\n\n    expect(result.data).toHaveLength(2)\n    expect(result.total).toBe(3)\n  })\n\n  it('should update a ${kebab}', async () => {\n    const created = await repo.create({ name: 'Original' })\n    const updated = await repo.update(created.id, { name: 'Updated' })\n    expect(updated.name).toBe('Updated')\n  })\n\n  it('should delete a ${kebab}', async () => {\n    const created = await repo.create({ name: 'To Delete' })\n    await repo.delete(created.id)\n    const found = await repo.findById(created.id)\n    expect(found).toBeNull()\n  })\n})\n`\n}\n","import type { TemplateContext } from './types'\n\n/** REST service — wraps repository with CRUD methods, replaces use-cases for flat pattern */\nexport function generateRestService(ctx: TemplateContext): string {\n  const { pascal, kebab } = ctx\n  return `import { Service, Inject, HttpException } from '@forinda/kickjs'\nimport type { ParsedQuery } from '@forinda/kickjs'\nimport { ${pascal.toUpperCase()}_REPOSITORY, type I${pascal}Repository } from './${kebab}.repository'\nimport type { ${pascal}ResponseDTO } from './dtos/${kebab}-response.dto'\nimport type { Create${pascal}DTO } from './dtos/create-${kebab}.dto'\nimport type { Update${pascal}DTO } from './dtos/update-${kebab}.dto'\n\n@Service()\nexport class ${pascal}Service {\n  constructor(\n    @Inject(${pascal.toUpperCase()}_REPOSITORY) private readonly repo: I${pascal}Repository,\n  ) {}\n\n  async findById(id: string): Promise<${pascal}ResponseDTO | null> {\n    return this.repo.findById(id)\n  }\n\n  async findAll(): Promise<${pascal}ResponseDTO[]> {\n    return this.repo.findAll()\n  }\n\n  async findPaginated(parsed: ParsedQuery) {\n    return this.repo.findPaginated(parsed)\n  }\n\n  async create(dto: Create${pascal}DTO): Promise<${pascal}ResponseDTO> {\n    return this.repo.create(dto)\n  }\n\n  async update(id: string, dto: Update${pascal}DTO): Promise<${pascal}ResponseDTO> {\n    return this.repo.update(id, dto)\n  }\n\n  async delete(id: string): Promise<void> {\n    await this.repo.delete(id)\n  }\n}\n`\n}\n\n/** REST constants — query config for flat pattern */\nexport function generateRestConstants(ctx: TemplateContext): string {\n  const { pascal } = ctx\n  return `import type { QueryFieldConfig } from '@forinda/kickjs'\n\nexport const ${pascal.toUpperCase()}_QUERY_CONFIG: QueryFieldConfig = {\n  filterable: ['name'],\n  sortable: ['name', 'createdAt'],\n  searchable: ['name'],\n}\n`\n}\n","import type { ModuleContext } from './types'\nimport { generateMinimalModuleIndex } from '../templates'\n\nexport async function generateMinimalFiles(ctx: ModuleContext): Promise<void> {\n  const { pascal, kebab, plural, style, write } = ctx\n\n  // Module file (named `<kebab>.module.ts` so Vite's module-discovery plugin picks it up)\n  await write(`${kebab}.module.ts`, generateMinimalModuleIndex({ pascal, kebab, plural, style }))\n\n  await write(\n    `${kebab}.controller.ts`,\n    `import { Controller, Get, Post, Put, Delete, reply, type Ctx } from '@forinda/kickjs'\n\n// \\`Ctx<KickRoutes.${pascal}Controller['<method>']>\\` is generated by\n// \\`kick typegen\\` (auto-run on \\`kick dev\\`).\n//\n// Return the payload instead of calling \\`ctx.json()\\` — the runtime sends it,\n// and typegen reads the return type to fill in \\`KickRoutes[...].response\\`.\n// \\`reply(status, body)\\` carries a non-200 status without losing that type.\n//\n// The bodies below are placeholders: wire them to a service or repository.\n// \"Minimal\" is about the file count, not the route surface — you get the whole\n// CRUD shape here in one file.\n\n@Controller()\nexport class ${pascal}Controller {\n  @Get('/')\n  async list(ctx: Ctx<KickRoutes.${pascal}Controller['list']>) {\n    return { items: [], total: 0 }\n  }\n\n  @Get('/:id')\n  async getById(ctx: Ctx<KickRoutes.${pascal}Controller['getById']>) {\n    return { id: ctx.params.id }\n  }\n\n  @Post('/')\n  async create(ctx: Ctx<KickRoutes.${pascal}Controller['create']>) {\n    return reply.created({ data: ctx.body })\n  }\n\n  @Put('/:id')\n  async update(ctx: Ctx<KickRoutes.${pascal}Controller['update']>) {\n    return { id: ctx.params.id, data: ctx.body }\n  }\n\n  @Delete('/:id')\n  async remove(ctx: Ctx<KickRoutes.${pascal}Controller['remove']>) {\n    return reply.noContent()\n  }\n}\n`,\n  )\n}\n","import type { ModuleContext } from './types'\nimport { toKebabCase } from '../../utils/naming'\nimport {\n  generateRestModuleIndex,\n  generateRestController,\n  generateRestConstants,\n  generateRestService,\n  generateCreateDTO,\n  generateUpdateDTO,\n  generateResponseDTO,\n  generateRepositoryInterface,\n  generateInMemoryRepository,\n  generateCustomRepository,\n  generateControllerTest,\n  generateRepositoryTest,\n} from '../templates'\n\nexport async function generateRestFiles(ctx: ModuleContext): Promise<void> {\n  const { pascal, kebab, plural, pluralPascal, repo, noTests, tokenScope, style, write } = ctx\n\n  // Module file (named `<kebab>.module.ts` so Vite's module-discovery plugin picks it up)\n  await write(`${kebab}.module.ts`, generateRestModuleIndex({ pascal, kebab, plural, repo, style }))\n\n  // Constants\n  await write(`${kebab}.constants.ts`, generateRestConstants({ pascal, kebab }))\n\n  // Controller (injects service)\n  await write(\n    `${kebab}.controller.ts`,\n    generateRestController({ pascal, kebab, plural, pluralPascal }),\n  )\n\n  // Service (wraps repository)\n  await write(`${kebab}.service.ts`, generateRestService({ pascal, kebab }))\n\n  // DTOs\n  await write(`dtos/create-${kebab}.dto.ts`, generateCreateDTO({ pascal, kebab }))\n  await write(`dtos/update-${kebab}.dto.ts`, generateUpdateDTO({ pascal, kebab }))\n  await write(`dtos/${kebab}-response.dto.ts`, generateResponseDTO({ pascal, kebab }))\n\n  // Repository interface (flat imports)\n  await write(\n    `${kebab}.repository.ts`,\n    generateRepositoryInterface({ pascal, kebab, dtoPrefix: './dtos', tokenScope }),\n  )\n\n  // Repository implementation (flat imports). `inmemory` is the only\n  // built-in (zero-dep working impl); every other name scaffolds a\n  // generic custom stub — prisma/drizzle no longer have dedicated\n  // generators (see `warnIfDeprecatedRepo`).\n  const isInMemory = repo === 'inmemory'\n  const repoFile = isInMemory ? `in-memory-${kebab}` : `${toKebabCase(repo)}-${kebab}`\n  const repoContent = isInMemory\n    ? generateInMemoryRepository({ pascal, kebab, repoPrefix: '.', dtoPrefix: './dtos' })\n    : generateCustomRepository({\n        pascal,\n        kebab,\n        repoType: repo,\n        repoPrefix: '.',\n        dtoPrefix: './dtos',\n      })\n  await write(`${repoFile}.repository.ts`, repoContent)\n\n  // Tests\n  if (!noTests) {\n    // Always generate an in-memory repo for testing — even when using drizzle/prisma\n    if (repo !== 'inmemory') {\n      await write(\n        `in-memory-${kebab}.repository.ts`,\n        generateInMemoryRepository({ pascal, kebab, repoPrefix: '.', dtoPrefix: './dtos' }),\n      )\n    }\n    await write(\n      `__tests__/${kebab}.controller.test.ts`,\n      generateControllerTest({ pascal, kebab, plural }),\n    )\n    await write(\n      `__tests__/${kebab}.repository.test.ts`,\n      generateRepositoryTest({\n        pascal,\n        kebab,\n        plural,\n        repoPrefix: `../in-memory-${kebab}.repository`,\n      }),\n    )\n  }\n}\n","import { join } from 'node:path'\nimport { writeFileSafe, fileExists } from '../utils/fs'\nimport { confirm, log } from '../utils/prompts'\nimport { colors } from '../utils/colors'\nimport { toPascalCase, toKebabCase, pluralize, pluralizePascal } from '../utils/naming'\nimport { escapeRegex } from '../utils/regex'\nimport { readFile, writeFile } from 'node:fs/promises'\nimport type { ProjectPattern, RepoTypeConfig } from '../config'\nimport type { ModuleStyle } from './templates/types'\nimport { generateMinimalFiles, generateRestFiles } from './patterns'\nimport type { ModuleContext } from './patterns'\n\nexport type BuiltinRepoType = 'inmemory'\nexport type RepoType = BuiltinRepoType | (string & {})\n\n/** Resolve a RepoTypeConfig (from kick.config.ts) into a flat repo type string */\nexport function resolveRepoType(config?: RepoTypeConfig): RepoType {\n  if (!config) return 'inmemory'\n  if (typeof config === 'string') return config\n  return config.name\n}\n\ninterface GenerateModuleOptions {\n  name: string\n  modulesDir: string\n  noEntity?: boolean\n  noTests?: boolean\n  repo?: RepoType\n  minimal?: boolean\n  force?: boolean\n  pattern?: ProjectPattern\n  dryRun?: boolean\n  /** When false, skip pluralization — use singular names for folders, routes, and classes */\n  pluralize?: boolean\n  /** Prisma client import path (default: '@prisma/client', Prisma 7+: '@/generated/prisma/client') */\n  prismaClientPath?: string\n  /**\n   * DI-token scope prefix substituted into emitted `createToken<T>()`\n   * literals. Resolved by the orchestrating command from\n   * `kick.config.ts > tokenScope` or the project's package.json.\n   * Falls back to `'app'` when not set so the generator can be called\n   * without a config in tests/fixtures.\n   */\n  tokenScope?: string\n  /**\n   * Module declaration style — `'define'` (factory, default) or\n   * `'class'` (legacy). Resolved by the orchestrating command from\n   * `kick.config.ts > modules.style`.\n   */\n  style?: ModuleStyle\n}\n\n/**\n * Generate a module — structure depends on the project pattern.\n *\n * Patterns:\n *   rest         — flat folder: controller + service + DTOs + repo (default)\n *   minimal      — just controller + module index\n */\nexport async function generateModule(options: GenerateModuleOptions): Promise<string[]> {\n  const { name, modulesDir, noEntity, noTests, repo = 'inmemory', force, dryRun } = options\n  const shouldPluralize = options.pluralize !== false\n\n  let pattern = options.pattern ?? 'rest'\n  if (options.minimal) pattern = 'minimal'\n\n  const kebab = toKebabCase(name)\n  const pascal = toPascalCase(name)\n  const plural = shouldPluralize ? pluralize(kebab) : kebab\n  const pluralPascal = shouldPluralize ? pluralizePascal(pascal) : pascal\n  const moduleDir = join(modulesDir, plural)\n\n  const files: string[] = []\n  let overwriteAll = force ?? false\n\n  const write = async (relativePath: string, content: string) => {\n    const fullPath = join(moduleDir, relativePath)\n    if (dryRun) {\n      files.push(fullPath)\n      return\n    }\n    if (!overwriteAll && (await fileExists(fullPath))) {\n      const shouldOverwrite = await confirm({\n        message: `File exists: ${colors.dim(relativePath)}. Overwrite?`,\n        initialValue: false,\n      })\n      if (!shouldOverwrite) {\n        log.warn(`Skipped: ${relativePath}`)\n        return\n      }\n    }\n    await writeFileSafe(fullPath, content)\n    files.push(fullPath)\n  }\n\n  const ctx: ModuleContext = {\n    kebab,\n    pascal,\n    plural,\n    pluralPascal,\n    moduleDir,\n    repo,\n    noEntity: noEntity ?? false,\n    noTests: noTests ?? false,\n    prismaClientPath: options.prismaClientPath ?? '@prisma/client',\n    tokenScope: options.tokenScope ?? 'app',\n    style: options.style ?? 'define',\n    write,\n    files,\n  }\n\n  switch (pattern) {\n    case 'minimal':\n      await generateMinimalFiles(ctx)\n      break\n    case 'rest':\n    default:\n      await generateRestFiles(ctx)\n      break\n  }\n\n  // Auto-register in modules index (all patterns need this)\n  if (!dryRun) {\n    await autoRegisterModule(modulesDir, pascal, plural, kebab, ctx.style)\n  }\n\n  return files\n}\n\n// ── Auto-register in modules index ──────────────────────────────────────\n\n/**\n * Add a new module to `src/modules/index.ts`. Handles both the\n * fresh-project initial-write path (creates the file with the right\n * shape for the resolved style) and the established-project append\n * path (inserts the import + extends the flat array OR fluent chain\n * via {@link appendModuleEntry}).\n *\n * Exported so `scaffold.ts` can reuse it — both call sites had\n * line-for-line identical bodies before consolidation, including\n * the inline-regex chain-append bug CodeRabbit caught on PR #196.\n * Single source of truth here keeps the orchestrator and scaffold\n * paths from drifting again.\n */\nexport async function autoRegisterModule(\n  modulesDir: string,\n  pascal: string,\n  plural: string,\n  kebab: string,\n  style: ModuleStyle = 'define',\n): Promise<void> {\n  const indexPath = join(modulesDir, 'index.ts')\n  const exists = await fileExists(indexPath)\n  const importPath = `./${plural}/${kebab}.module`\n  // `defineModule` factories are called at the registration site\n  // (`${pascal}Module()`); legacy class modules are passed by reference\n  // (`${pascal}Module`). Application's loader discriminates class vs\n  // instance at boot, so both forms work — `style` only controls what\n  // the orchestrator emits.\n  const entryToken = style === 'class' ? `${pascal}Module` : `${pascal}Module()`\n\n  if (!exists) {\n    // For 'class' style we emit the legacy flat-array form because\n    // `defineModules` is the factory-form companion. For 'define' (default)\n    // we emit the fluent `defineModules().mount(...)` chain so subsequent\n    // `kick g module` invocations append cleanly.\n    const initialBody =\n      style === 'class'\n        ? `import type { AppModuleEntry } from '@forinda/kickjs'\nimport { ${pascal}Module } from '${importPath}'\n\nexport const modules: AppModuleEntry[] = [${entryToken}]\n`\n        : `import { defineModules } from '@forinda/kickjs'\nimport { ${pascal}Module } from '${importPath}'\n\nexport const modules = defineModules().mount(${entryToken})\n`\n    await writeFileSafe(indexPath, initialBody)\n    return\n  }\n\n  let content = await readFile(indexPath, 'utf-8')\n\n  // Two independent checks — a stale comment, a doc snippet, or a\n  // partially-edited file shouldn't make the gate skip both halves.\n  //\n  //   1. Import-line check: look for an exact `import { XModule }\n  //      from '<importPath>'` statement, not just any `XModule`\n  //      mention. A comment that names XModule doesn't satisfy this.\n  //   2. Registry-entry check: look for `XModule` (word-bounded)\n  //      inside the actual `export const modules` initializer rhs,\n  //      not anywhere in the file. Recovers a half-edited registry\n  //      where the import survived but the .mount entry was deleted.\n  const importLine = `import { ${pascal}Module } from '${importPath}'`\n  const escapedImportPath = escapeRegex(importPath)\n  const importPresentRe = new RegExp(\n    `^import\\\\s*\\\\{[^}]*\\\\b${escapeRegex(pascal)}Module\\\\b[^}]*\\\\}\\\\s*from\\\\s*['\"]${escapedImportPath}['\"]`,\n    'm',\n  )\n  if (!importPresentRe.test(content)) {\n    // Insert import after last existing import\n    const lastImportIdx = content.lastIndexOf('import ')\n    if (lastImportIdx !== -1) {\n      const lineEnd = content.indexOf('\\n', lastImportIdx)\n      content = content.slice(0, lineEnd + 1) + importLine + '\\n' + content.slice(lineEnd + 1)\n    } else {\n      content = importLine + '\\n' + content\n    }\n  }\n\n  // Independently confirm the registry rhs already names this entry\n  // before skipping the append. Scoped to the `export const modules`\n  // slice via `findModulesRhsSpan` so an `XModule` mention in an\n  // unrelated builder above the registry doesn't mask a missing\n  // `.mount(XModule())` further down.\n  const span = findModulesRhsSpan(content)\n  if (span) {\n    const rhsSlice = content.slice(span.rhsStart, span.rhsEnd + 1)\n    const entryPresentRe = new RegExp(`\\\\b${escapeRegex(pascal)}Module\\\\b`)\n    if (!entryPresentRe.test(rhsSlice)) {\n      content = appendModuleEntry(content, entryToken)\n    }\n  } else {\n    // No recognizable registry shape — fall back to the previous\n    // best-effort behaviour: try appending; appendModuleEntry returns\n    // content unchanged when it can't find a target.\n    content = appendModuleEntry(content, entryToken)\n  }\n\n  await writeFile(indexPath, content, 'utf-8')\n}\n\n/**\n * Append `entryToken` (e.g. `LionModule()` or bare `LionModule`) to\n * the project's modules registry. Handles two shapes:\n *\n *   1. Flat array literal:\n *      `export const modules: AppModuleEntry[] = [HelloModule()]`\n *   2. Fluent factory chain:\n *      `export const modules = defineModules().mount(HelloModule())`\n *\n * If neither shape is detected, content is returned unchanged — the\n * adopter's registration site is non-standard and they mount the\n * new module themselves.\n *\n * Exported so `scaffold.ts`'s auto-register can reuse the same\n * balanced-paren scanner — duplicating the regex caused\n * `mount(UserModule())` to corrupt on the scaffold path before this\n * was hoisted out.\n */\nexport function appendModuleEntry(content: string, entryToken: string): string {\n  const span = findModulesRhsSpan(content)\n  if (!span) return content\n\n  // Shape 1 — flat array literal at the rhs.\n  if (span.shape === 'array') {\n    const inside = content.slice(span.rhsStart + 1, span.rhsEnd)\n    const trimmed = inside.trim()\n    let rewritten: string\n    if (!trimmed) {\n      rewritten = `[${entryToken}]`\n    } else {\n      const needsComma = trimmed.endsWith(',') ? '' : ','\n      rewritten = `[${inside.trimEnd()}${needsComma} ${entryToken}]`\n    }\n    return content.slice(0, span.rhsStart) + rewritten + content.slice(span.rhsEnd + 1)\n  }\n\n  // Shape 2 — `defineModules()` fluent chain at the rhs. Insert\n  // `.mount(...)` right at `chainEnd` (the offset just past the\n  // last `.mount(...)`'s closing `)` or just past `defineModules()`'s\n  // closing `)` when the chain is empty).\n  return `${content.slice(0, span.chainEnd)}\\n  .mount(${entryToken})${content.slice(span.chainEnd)}`\n}\n\n/**\n * Span returned by {@link findModulesRhsSpan}. Both shapes carry the\n * `rhsStart` (the offset of `[` for arrays or `d` for the chain) and\n * an `end` offset describing where the rhs concludes:\n *\n *   - `array`: `rhsEnd` points at the matching `]` (inclusive).\n *   - `chain`: `chainEnd` points just past the last `.mount(...)`\n *      call (or just past `defineModules()`'s closing `)` when the\n *      chain is empty). For consistency with arrays, `rhsEnd =\n *      chainEnd - 1` so a `[rhsStart, rhsEnd + 1)` slice covers the\n *      whole initializer.\n */\ntype ModulesRhsSpan =\n  | { shape: 'array'; rhsStart: number; rhsEnd: number }\n  | { shape: 'chain'; rhsStart: number; rhsEnd: number; chainEnd: number }\n\n/**\n * Locate the `export const modules = <rhs>` initializer in `content`\n * and return the rhs's start/end offsets along with which shape it\n * is (flat array vs `defineModules()` chain). Used by both the\n * append path (`appendModuleEntry`) AND the remove path\n * (`stripChainMount` / array-entry rm regex) so neither mutates\n * unrelated text elsewhere in the file.\n *\n * Returns `null` when no `export const modules` declaration is\n * found, or when its rhs doesn't match either supported shape.\n */\nexport function findModulesRhsSpan(content: string): ModulesRhsSpan | null {\n  const declMatch = /export\\s+const\\s+modules\\b[^=]*=/.exec(content)\n  if (!declMatch) return null\n  const eqEnd = declMatch.index + declMatch[0].length\n  let rhsStart = eqEnd\n  while (rhsStart < content.length && /\\s/.test(content[rhsStart] ?? '')) rhsStart++\n\n  if (content[rhsStart] === '[') {\n    const close = balancedBracketClose(content, rhsStart)\n    if (close === -1) return null\n    return { shape: 'array', rhsStart, rhsEnd: close }\n  }\n\n  if (content.slice(rhsStart, rhsStart + 'defineModules'.length) === 'defineModules') {\n    const chainEnd = findChainEnd(content, rhsStart)\n    if (chainEnd === -1) return null\n    return { shape: 'chain', rhsStart, rhsEnd: chainEnd - 1, chainEnd }\n  }\n\n  return null\n}\n\n/**\n * Locate the insertion point after the last `.mount(...)` call in a\n * `defineModules()...` chain, or after `defineModules()` itself when\n * the chain is empty. Returns the source-string offset to insert at,\n * or -1 when the chain isn't found.\n *\n * The scanner walks balanced parens so nested factory calls inside\n * `.mount(X())` don't break boundary detection.\n */\nfunction findChainEnd(src: string, fromIdx = 0): number {\n  // Match the CALL site, not the import statement. We require an\n  // immediately-following `(` (allowing whitespace) to anchor on\n  // `defineModules(...)` and skip past `import { defineModules }`.\n  // `fromIdx` lets the caller scope the search to the rhs of an\n  // `export const modules =` declaration so unrelated calls\n  // elsewhere in the file aren't matched.\n  const callRegex = /defineModules\\s*\\(/g\n  callRegex.lastIndex = fromIdx\n  const match = callRegex.exec(src)\n  if (!match) return -1\n  // `match.index` points at `d`; `(` is at `match.index + match[0].length - 1`.\n  let i = match.index + match[0].length - 1\n  if (src[i] !== '(') return -1\n  // Balance the args of `defineModules(...)`.\n  i = balancedClose(src, i)\n  if (i === -1) return -1\n  i++\n  // Now consume zero or more `.mount(...)` calls. After each, `i`\n  // points just past its closing `)` so the next iteration sees a\n  // potential `.mount(...)` ahead.\n  for (;;) {\n    let j = i\n    while (j < src.length && /\\s/.test(src[j] ?? '')) j++\n    if (src[j] !== '.') break\n    if (src.slice(j, j + 6) !== '.mount') break\n    j += 6\n    while (j < src.length && /\\s/.test(src[j] ?? '')) j++\n    if (src[j] !== '(') break\n    const close = balancedClose(src, j)\n    if (close === -1) break\n    i = close + 1\n  }\n  return i\n}\n\n/**\n * Skip past a `//` line comment or `/* … *\\/` block comment that\n * starts at offset `i`. Returns the offset of the first character\n * after the comment, or `i` unchanged when there's no comment at\n * that position. Used by the balanced-paren / bracket scanners so\n * a `]` or `)` inside a comment doesn't terminate the scan early.\n */\nfunction skipComment(src: string, i: number): number {\n  const two = src.slice(i, i + 2)\n  if (two === '//') {\n    i += 2\n    while (i < src.length && src[i] !== '\\n') i++\n    return i\n  }\n  if (two === '/*') {\n    i += 2\n    while (i + 1 < src.length && !(src[i] === '*' && src[i + 1] === '/')) i++\n    return i + 2\n  }\n  return i\n}\n\n/**\n * Given an offset pointing at `[`, return the offset of its matching\n * `]`. Skips brackets inside string literals so `['weird]name']`\n * doesn't break, and inside `//` / `/* *\\/` comments. Returns -1 on\n * imbalance.\n */\nfunction balancedBracketClose(src: string, openIdx: number): number {\n  if (src[openIdx] !== '[') return -1\n  let depth = 1\n  let i = openIdx + 1\n  while (i < src.length) {\n    const next = src.slice(i, i + 2)\n    if (next === '//' || next === '/*') {\n      i = skipComment(src, i)\n      continue\n    }\n    const ch = src[i] ?? ''\n    if (ch === \"'\" || ch === '\"' || ch === '`') {\n      const quote = ch\n      i++\n      while (i < src.length && src[i] !== quote) {\n        if (src[i] === '\\\\') i++\n        i++\n      }\n      if (i < src.length) i++\n      continue\n    }\n    if (ch === '[') depth++\n    else if (ch === ']') {\n      depth--\n      if (depth === 0) return i\n    }\n    i++\n  }\n  return -1\n}\n\n/**\n * Given an offset pointing at `(`, return the offset of its matching\n * `)`, or -1 on imbalance. Skips parens inside string literals\n * (single, double, backtick) and inside `//` / `/* *\\/` comments —\n * a `)` in either would terminate the scan early and corrupt the\n * insertion offset.\n */\nfunction balancedClose(src: string, openIdx: number): number {\n  if (src[openIdx] !== '(') return -1\n  let depth = 1\n  let i = openIdx + 1\n  while (i < src.length) {\n    const next = src.slice(i, i + 2)\n    if (next === '//' || next === '/*') {\n      i = skipComment(src, i)\n      continue\n    }\n    const ch = src[i] ?? ''\n    if (ch === \"'\" || ch === '\"' || ch === '`') {\n      // Skip string literal — find matching unescaped quote.\n      const quote = ch\n      i++\n      while (i < src.length && src[i] !== quote) {\n        if (src[i] === '\\\\') i++\n        i++\n      }\n      if (i < src.length) i++\n      continue\n    }\n    if (ch === '(') depth++\n    else if (ch === ')') {\n      depth--\n      if (depth === 0) return i\n    }\n    i++\n  }\n  return -1\n}\n","import { join } from 'node:path'\nimport { writeFileSafe } from '../utils/fs'\nimport { toPascalCase, toKebabCase } from '../utils/naming'\n\ninterface GenerateAdapterOptions {\n  name: string\n  outDir: string\n}\n\n/**\n * Scaffold a `defineAdapter()` factory under `src/adapters/<name>.adapter.ts`.\n *\n * v4 dropped the `class implements AppAdapter` pattern in favour of the\n * `defineAdapter()` factory (architecture.md §21.3.4). The generated\n * template uses the new factory shape so adopters get a working\n * adapter with all four lifecycle hooks (beforeMount, beforeStart,\n * afterStart, shutdown), a typed config object with defaults, and the\n * factory's call / `.scoped()` / `.async()` surfaces — without\n * writing a single class.\n */\nexport async function generateAdapter(options: GenerateAdapterOptions): Promise<string[]> {\n  const { name, outDir } = options\n  const kebab = toKebabCase(name)\n  const pascal = toPascalCase(name)\n  const files: string[] = []\n\n  const filePath = join(outDir, `${kebab}.adapter.ts`)\n  await writeFileSafe(\n    filePath,\n    `import {\n  defineAdapter,\n  type AdapterContext,\n  type AdapterMiddleware,\n  type ContributorRegistrations,\n  type Constructor,\n} from '@forinda/kickjs'\n\n/**\n * Configuration for the ${pascal} adapter.\n *\n * Adapters typically take a small config object so callers can tune\n * behaviour at bootstrap time. Keep the shape narrow — anything\n * derived from the environment should be read inside the build\n * function via getEnv(), not forced onto the caller.\n */\nexport interface ${pascal}AdapterConfig {\n  // Add your adapter configuration here, e.g.:\n  // enabled?: boolean\n  // apiKey?: string\n}\n\n/**\n * ${pascal} adapter — built via \\`defineAdapter()\\` so callers get the\n * factory's call / \\`.scoped()\\` / \\`.async()\\` surfaces for free.\n *\n * Hooks into the Application lifecycle to add middleware, routes,\n * Context Contributors, or external service connections.\n *\n * Every lifecycle hook below is OPTIONAL. The scaffold emits all of\n * them so adopters can browse what's available and delete what they\n * don't need — \\`build()\\` returning \\`{}\\` is also valid for an adapter\n * that only contributes config defaults.\n *\n * @example\n * \\`\\`\\`ts\n * import { bootstrap } from '@forinda/kickjs'\n * import { ${pascal}Adapter } from './adapters/${kebab}.adapter'\n *\n * bootstrap({\n *   modules,\n *   adapters: [${pascal}Adapter({ /* config overrides *\\\\/ })],\n * })\n * \\`\\`\\`\n */\nexport const ${pascal}Adapter = defineAdapter<${pascal}AdapterConfig>({\n  name: '${pascal}Adapter',\n  defaults: {\n    // Default config values go here. The adopter's overrides shallow-merge\n    // on top of these before \\`build()\\` runs.\n  },\n  build: (_config, { name: _name }) => {\n    // Closures inside \\`build()\\` are how each adapter instance owns its\n    // own state (database client, Map, timer handle, …). The same\n    // \\`_config\\` is visible to every hook below.\n\n    return {\n      /**\n       * Express middleware entries the Application mounts at named phases.\n       *\n       * \\`phase\\` controls where each handler sits in the pipeline:\n       *   'beforeGlobal' | 'afterGlobal' | 'beforeRoutes' | 'afterRoutes'.\n       *\n       * \\`path\\` (optional) scopes the entry to a path prefix.\n       *\n       * Delete this hook entirely if you don't add middleware.\n       */\n      middleware(): AdapterMiddleware[] {\n        return [\n          // Example: add a custom header to all responses\n          // {\n          //   phase: 'beforeGlobal',\n          //   handler: (_req, res, next) => {\n          //     res.setHeader('X-${pascal}', 'true')\n          //     next()\n          //   },\n          // },\n          // Example: scope a rate limiter to one path prefix\n          // {\n          //   phase: 'beforeRoutes',\n          //   path: '/api/v1/auth',\n          //   handler: rateLimit({ max: 10 }),\n          // },\n        ]\n      },\n\n      /**\n       * Runs BEFORE global middleware. Mount routes that should bypass the\n       * middleware stack — health checks, docs UI, static assets, OAuth\n       * callbacks. Anything you want reachable even if a global middleware\n       * later in the chain rejects requests.\n       *\n       * Delete this hook if you have no early routes.\n       */\n      beforeMount(_ctx: AdapterContext): void {\n        // Example:\n        // _ctx.app.get('/${kebab}/status', (_req, res) => res.json({ status: 'ok' }))\n      },\n\n      /**\n       * Fires once per controller class as the router mounts. Use this to\n       * collect route metadata for OpenAPI specs, dependency graphs, route\n       * inventories, devtools dashboards.\n       *\n       * Delete this hook unless your adapter introspects the route registry.\n       */\n      onRouteMount(_controllerClass: Constructor, _mountPath: string): void {\n        // Example (Swagger-style): collect routes for the spec.\n        // openApiSpec.addController(_controllerClass, _mountPath)\n      },\n\n      /**\n       * Runs AFTER modules + routes are wired, BEFORE the server starts.\n       * Right place for late-stage DI registrations or final config validation.\n       *\n       * Delete this hook if there's nothing to wire post-modules.\n       */\n      beforeStart(_ctx: AdapterContext): void {\n        // Example: _ctx.container.registerInstance(MY_TOKEN, new MyService(_config))\n      },\n\n      /**\n       * Runs AFTER the HTTP server is listening. The raw \\`http.Server\\` is\n       * available on \\`ctx.server\\` — attach upgrade handlers (Socket.IO,\n       * gRPC, GraphQL subscriptions), warm caches, log a banner.\n       *\n       * Delete this hook if you don't need the running server reference.\n       */\n      afterStart(_ctx: AdapterContext): void {\n        // Example: const io = new Server(_ctx.server)\n      },\n\n      /**\n       * Returns Context Contributors to merge into every route's pipeline\n       * at the \\`'adapter'\\` precedence level. Per-route handlers can\n       * override the value at the method / class / module level.\n       *\n       * Delete this hook unless your adapter ships typed per-request values\n       * (auth user, tenant, locale, feature flags, geo, etc).\n       */\n      contributors(): ContributorRegistrations {\n        return [\n          // Example:\n          // import { defineHttpContextDecorator } from '@forinda/kickjs'\n          // declare module '@forinda/kickjs' { interface ContextMeta { ${kebab}: { id: string } } }\n          // const Load${pascal} = defineHttpContextDecorator({\n          //   key: '${kebab}',\n          //   resolve: (ctx) => ({ id: ctx.req.headers['x-${kebab}-id'] as string }),\n          // })\n          // return [Load${pascal}.registration]\n        ]\n      },\n\n      /**\n       * Runs on graceful shutdown AND on every HMR reload. Clean up\n       * long-lived resources the adapter OWNS: close connections, flush\n       * buffers, cancel timers.\n       *\n       * Close only what you own — \\`ctx.server\\` is shared (in dev it is\n       * Vite's listener), and closing it kills the dev server with no\n       * rebind. socket.io's \\`io.close()\\` does this; use\n       * \\`io.engine.close()\\` to detach instead.\n       *\n       * The framework runs every adapter's \\`shutdown\\` concurrently via\n       * \\`Promise.allSettled\\`, each time-boxed — one failure or hang won't\n       * block sibling adapters.\n       *\n       * Delete this hook if your adapter holds no resources.\n       */\n      async shutdown(): Promise<void> {\n        // Example: await this.pool.end()\n        // Example: clearInterval(this.heartbeatTimer)\n      },\n    }\n  },\n})\n`,\n  )\n  files.push(filePath)\n\n  return files\n}\n","import { resolve, join } from 'node:path'\nimport { pluralize, toKebabCase } from './naming'\nimport type { ProjectPattern } from '../config'\n\n/**\n * Flat folder mapping — the layout for both `rest` and `minimal`.\n * Files live at the module root or in shallow subdirectories.\n */\nconst FLAT_FOLDER_MAP: Record<string, string> = {\n  controller: '',\n  service: '',\n  dto: 'dtos',\n  guard: 'guards',\n  middleware: 'middleware',\n  contributor: 'contributors',\n}\n\nexport interface ResolveOutDirOptions {\n  /** The artifact type (controller, service, dto, guard, middleware) */\n  type: string\n  /** Explicit -o / --out dir from CLI flag (takes highest priority) */\n  outDir?: string\n  /** Module name from --module flag */\n  moduleName?: string\n  /** Modules directory (from config or default) */\n  modulesDir?: string\n  /** Standalone default directory when no --module is used (e.g. 'src/controllers') */\n  defaultDir: string\n  /** Project pattern — determines folder structure inside modules */\n  pattern?: ProjectPattern\n  /** Whether to pluralize the module folder name (default: true) */\n  shouldPluralize?: boolean\n}\n\n/**\n * Resolve the output directory for a generator artifact.\n *\n * Priority:\n *   1. Explicit --out flag (always wins)\n *   2. --module flag → maps into module's folder (DDD or flat based on pattern)\n *   3. Standalone default directory\n */\nexport function resolveOutDir(options: ResolveOutDirOptions): string {\n  const {\n    type,\n    outDir,\n    moduleName,\n    modulesDir = 'src/modules',\n    defaultDir,\n    shouldPluralize = true,\n  } = options\n\n  // Explicit --out always wins\n  if (outDir) return resolve(outDir)\n\n  // Module-scoped: place inside the module's folder. All patterns\n  // (rest/minimal) share the flat folder layout.\n  if (moduleName) {\n    const folderMap = FLAT_FOLDER_MAP\n    const kebab = toKebabCase(moduleName)\n    const folder = shouldPluralize ? pluralize(kebab) : kebab\n    const subfolder = folderMap[type] ?? ''\n    const base = join(modulesDir, folder)\n    return resolve(subfolder ? join(base, subfolder) : base)\n  }\n\n  // Standalone default\n  return resolve(defaultDir)\n}\n","import { join } from 'node:path'\nimport { writeFileSafe } from '../utils/fs'\nimport { toPascalCase, toKebabCase, toCamelCase } from '../utils/naming'\nimport { resolveOutDir } from '../utils/resolve-out-dir'\nimport type { KickConfig, ProjectPattern } from '../config'\n\ninterface GenerateMiddlewareOptions {\n  name: string\n  outDir?: string\n  moduleName?: string\n  modulesDir?: string\n  pattern?: ProjectPattern\n  pluralize?: boolean\n  /**\n   * The engine from `kick.config.ts`. Global middleware is connect-style on\n   * every runtime, but only Express hands the handler an `express.Request` —\n   * Fastify passes `request.raw` and h3 the node objects, so `node:http` is\n   * the honest type there (and those projects have no `express` dependency to\n   * import types from).\n   */\n  runtime?: KickConfig['runtime']\n}\n\nexport async function generateMiddleware(options: GenerateMiddlewareOptions): Promise<string[]> {\n  const { name, moduleName, modulesDir, pattern } = options\n  // Explicit opt-in, not a fallback: an unset `runtime` means a hand-written or\n  // pre-`--runtime` kick.config, which says nothing about the engine. Emitting\n  // express types there is how the original bug shipped — `express` isn't a\n  // dependency on a Fastify / h3 scaffold, so the import fails to compile.\n  // `kick new` always writes the field, so real Express projects still opt in.\n  const isExpress = options.runtime === 'express'\n  // An unset `runtime` is a hand-written or pre-`--runtime` kick.config, so the\n  // comment must not claim the project runs on one.\n  const engineClause = options.runtime\n    ? `this project runs on ${options.runtime} and has no\n  // \\`express\\``\n    : `this project declares no \\`runtime\\` in kick.config.ts, so it\n  // may have no \\`express\\``\n  const outDir = resolveOutDir({\n    type: 'middleware',\n    outDir: options.outDir,\n    moduleName,\n    modulesDir,\n    defaultDir: 'src/middleware',\n    pattern,\n    shouldPluralize: options.pluralize ?? true,\n  })\n  const kebab = toKebabCase(name)\n  const camel = toCamelCase(name)\n  const files: string[] = []\n\n  const filePath = join(outDir, `${kebab}.middleware.ts`)\n  await writeFileSafe(\n    filePath,\n    `${\n      isExpress\n        ? `import type { Request, Response, NextFunction } from 'express'`\n        : `import type { IncomingMessage, ServerResponse } from 'node:http'`\n    }\n\nexport interface ${toPascalCase(name)}Options {\n  // Add configuration options here. The factory below closes over the\n  // resolved options object; pass them at the call site —\n  // \\`${camel}({ foo: 'bar' })\\` — and the closure preserves them across\n  // every request.\n}\n\n/**\n * ${toPascalCase(name)} middleware.\n *\n * Usage in bootstrap (fires on every request):\n *   middleware: [${camel}()]\n *\n * Usage with adapter — phase controls *when* the handler runs:\n *\n *   middleware() {\n *     return [{ handler: ${camel}(), phase: 'afterGlobal' }]\n *   }\n *\n * Phase semantics (see \\`MiddlewarePhase\\` JSDoc for the full contract):\n *   - 'beforeGlobal' / 'afterGlobal' / 'beforeRoutes' — fire on every\n *     request, before module routes run.\n *   - 'afterRoutes' — fires ONLY when no route matched (404 fall-through)\n *     OR a route handler called \\`next()\\` without ending the response.\n *     Controllers that call \\`ctx.json(…)\\` end the chain and skip this\n *     phase. For per-response work (logging, metrics) attach to\n *     \\`res.on('finish', …)\\` from an earlier-phase middleware instead.\n *\n * Optional path scope — string, RegExp, or array of either:\n *   middleware() {\n *     return [{\n *       handler: ${camel}({ region: 'eu' }),\n *       phase: 'afterGlobal',\n *       path: ['/api', /^\\\\/admin/],\n *     }]\n *   }\n *\n * Usage with @Middleware decorator:\n *   @Middleware(${camel}())\n */\nexport function ${camel}(options: ${toPascalCase(name)}Options = {}) {\n${\n  isExpress\n    ? `  // Typed from \\`express\\` because that is this project's runtime\n  // (\\`runtime: 'express'\\` in kick.config.ts). Global middleware is\n  // connect-style on every engine, but only Express hands the handler its own\n  // request / response objects — so \\`req.originalUrl\\`, \\`req.query\\` and\n  // \\`res.json()\\` are all available here. Switching \\`runtime\\` later means\n  // re-typing this signature from \\`node:http\\`.\n  return (req: Request, res: Response, next: NextFunction) => {`\n    : `  // Typed from \\`node:http\\`, not \\`express\\`. Global middleware is connect-style\n  // on every engine, but ${engineClause} dependency to import types from. Under Fastify the handler\n  // receives \\`request.raw\\` / a reply driver, and under h3 the node objects, so\n  // anything Express-only (\\`req.originalUrl\\`, \\`res.json\\`) is absent. Reach\n  // for \\`ctx.*\\` helpers when you need a typed response.\n  return (req: IncomingMessage, res: ServerResponse, next: () => void) => {`\n}\n    // Implement your middleware logic here. \\`options\\` is captured by\n    // closure — log or read it anywhere in this handler body.\n    void options\n    next()\n  }\n}\n`,\n  )\n  files.push(filePath)\n\n  return files\n}\n","import { join } from 'node:path'\nimport { writeFileSafe } from '../utils/fs'\nimport { toPascalCase, toKebabCase, toCamelCase } from '../utils/naming'\nimport { resolveOutDir } from '../utils/resolve-out-dir'\nimport type { ProjectPattern } from '../config'\n\ninterface GenerateGuardOptions {\n  name: string\n  outDir?: string\n  moduleName?: string\n  modulesDir?: string\n  pattern?: ProjectPattern\n  pluralize?: boolean\n}\n\nexport async function generateGuard(options: GenerateGuardOptions): Promise<string[]> {\n  const { name, moduleName, modulesDir, pattern } = options\n  const outDir = resolveOutDir({\n    type: 'guard',\n    outDir: options.outDir,\n    moduleName,\n    modulesDir,\n    defaultDir: 'src/guards',\n    pattern,\n    shouldPluralize: options.pluralize ?? true,\n  })\n  const kebab = toKebabCase(name)\n  const camel = toCamelCase(name)\n  const pascal = toPascalCase(name)\n  const files: string[] = []\n\n  const filePath = join(outDir, `${kebab}.guard.ts`)\n  await writeFileSafe(\n    filePath,\n    `import { Container, HttpException } from '@forinda/kickjs'\nimport type { RequestContext } from '@forinda/kickjs'\n\n/**\n * ${pascal} guard.\n *\n * Guards protect routes by checking conditions before the handler runs.\n * Return early with an error response to block access.\n *\n * Usage:\n *   @Middleware(${camel}Guard)\n *   @Get('/protected')\n *   async handler(ctx: RequestContext) { ... }\n */\nexport async function ${camel}Guard(ctx: RequestContext, next: () => void): Promise<void> {\n  // Example: check for an authorization header\n  const header = ctx.headers.authorization\n  if (!header?.startsWith('Bearer ')) {\n    // \\`ctx.problem.*\\` (RFC 9457) rather than \\`ctx.res\\`: \\`ctx.res\\` is the\n    // ENGINE-NATIVE response, so \\`ctx.res.status(401).json(...)\\` only works on\n    // Express — \\`FastifyReply\\` has no \\`.json()\\`, and h3's event has no\n    // \\`.status()\\`. The \\`ctx.*\\` helpers work on every runtime.\n    ctx.problem.unauthorized({ detail: 'Missing or invalid authorization header' })\n    return\n  }\n\n  const token = header.slice(7)\n\n  try {\n    // Verify the token using a service from the DI container\n    // const container = Container.getInstance()\n    // const authService = container.resolve(AuthService)\n    // const payload = authService.verifyToken(token)\n    // ctx.set('auth', payload)\n\n    next()\n  } catch {\n    ctx.problem.unauthorized({ detail: 'Invalid or expired token' })\n  }\n}\n`,\n  )\n  files.push(filePath)\n\n  return files\n}\n","import { join } from 'node:path'\nimport { writeFileSafe } from '../utils/fs'\nimport { toPascalCase, toKebabCase } from '../utils/naming'\nimport { resolveOutDir } from '../utils/resolve-out-dir'\nimport type { ProjectPattern } from '../config'\n\ninterface GenerateServiceOptions {\n  name: string\n  outDir?: string\n  moduleName?: string\n  modulesDir?: string\n  pattern?: ProjectPattern\n  pluralize?: boolean\n}\n\nexport async function generateService(options: GenerateServiceOptions): Promise<string[]> {\n  const { name, moduleName, modulesDir, pattern } = options\n  const outDir = resolveOutDir({\n    type: 'service',\n    outDir: options.outDir,\n    moduleName,\n    modulesDir,\n    defaultDir: 'src/services',\n    pattern,\n    shouldPluralize: options.pluralize ?? true,\n  })\n  const kebab = toKebabCase(name)\n  const pascal = toPascalCase(name)\n  const files: string[] = []\n\n  const filePath = join(outDir, `${kebab}.service.ts`)\n  await writeFileSafe(\n    filePath,\n    `import { Service } from '@forinda/kickjs'\n\n@Service()\nexport class ${pascal}Service {\n  // Inject dependencies via constructor\n  // constructor(\n  //   @Inject(MY_REPO) private readonly repo: IMyRepository,\n  // ) {}\n}\n`,\n  )\n  files.push(filePath)\n\n  return files\n}\n","import { join } from 'node:path'\nimport { writeFileSafe } from '../utils/fs'\nimport { toPascalCase, toKebabCase } from '../utils/naming'\nimport { resolveOutDir } from '../utils/resolve-out-dir'\nimport type { ProjectPattern } from '../config'\n\ninterface GenerateControllerOptions {\n  name: string\n  outDir?: string\n  moduleName?: string\n  modulesDir?: string\n  pattern?: ProjectPattern\n  pluralize?: boolean\n}\n\nexport async function generateController(options: GenerateControllerOptions): Promise<string[]> {\n  const { name, moduleName, modulesDir, pattern } = options\n  const outDir = resolveOutDir({\n    type: 'controller',\n    outDir: options.outDir,\n    moduleName,\n    modulesDir,\n    defaultDir: 'src/controllers',\n    pattern,\n    shouldPluralize: options.pluralize ?? true,\n  })\n  const kebab = toKebabCase(name)\n  const pascal = toPascalCase(name)\n  const files: string[] = []\n\n  const filePath = join(outDir, `${kebab}.controller.ts`)\n  await writeFileSafe(\n    filePath,\n    `import { Controller, Get, Post, Put, Delete, reply, type Ctx } from '@forinda/kickjs'\n\n// \\`Ctx<KickRoutes.${pascal}Controller['<method>']>\\` is generated by\n// \\`kick typegen\\` (auto-run on \\`kick dev\\`). After the first run, your IDE\n// will autocomplete \\`ctx.params\\`, \\`ctx.body\\`, and \\`ctx.query\\`.\n// See https://kickjs.app/guide/typegen for details.\n//\n// Return the payload instead of calling \\`ctx.json()\\` — the runtime sends it,\n// and typegen reads the return type to fill in \\`KickRoutes[...].response\\`.\n// \\`reply(status, body)\\` carries a non-200 status without losing that type.\n//\n// The bodies below are placeholders: wire them to a service or repository.\n\n@Controller()\nexport class ${pascal}Controller {\n  // @Autowired() private readonly myService!: MyService\n\n  @Get('/')\n  async list(ctx: Ctx<KickRoutes.${pascal}Controller['list']>) {\n    return { items: [], total: 0 }\n  }\n\n  @Get('/:id')\n  async getById(ctx: Ctx<KickRoutes.${pascal}Controller['getById']>) {\n    return { id: ctx.params.id }\n  }\n\n  @Post('/')\n  async create(ctx: Ctx<KickRoutes.${pascal}Controller['create']>) {\n    return reply.created({ data: ctx.body })\n  }\n\n  @Put('/:id')\n  async update(ctx: Ctx<KickRoutes.${pascal}Controller['update']>) {\n    return { id: ctx.params.id, data: ctx.body }\n  }\n\n  @Delete('/:id')\n  async remove(ctx: Ctx<KickRoutes.${pascal}Controller['remove']>) {\n    return reply.noContent()\n  }\n}\n`,\n  )\n  files.push(filePath)\n\n  return files\n}\n","import { join } from 'node:path'\nimport { writeFileSafe } from '../utils/fs'\nimport { toPascalCase, toKebabCase, toCamelCase } from '../utils/naming'\nimport { resolveOutDir } from '../utils/resolve-out-dir'\nimport type { ProjectPattern } from '../config'\n\ninterface GenerateDtoOptions {\n  name: string\n  outDir?: string\n  moduleName?: string\n  modulesDir?: string\n  pattern?: ProjectPattern\n  pluralize?: boolean\n}\n\nexport async function generateDto(options: GenerateDtoOptions): Promise<string[]> {\n  const { name, moduleName, modulesDir, pattern } = options\n  const outDir = resolveOutDir({\n    type: 'dto',\n    outDir: options.outDir,\n    moduleName,\n    modulesDir,\n    defaultDir: 'src/dtos',\n    pattern,\n    shouldPluralize: options.pluralize ?? true,\n  })\n  const kebab = toKebabCase(name)\n  const pascal = toPascalCase(name)\n  const camel = toCamelCase(name)\n  const files: string[] = []\n\n  const filePath = join(outDir, `${kebab}.dto.ts`)\n  await writeFileSafe(\n    filePath,\n    `import { z } from 'zod'\n\nexport const ${camel}Schema = z.object({\n  // Define your schema fields here\n  name: z.string().min(1).max(200),\n})\n\nexport type ${pascal}DTO = z.infer<typeof ${camel}Schema>\n`,\n  )\n  files.push(filePath)\n\n  return files\n}\n","// TypegenPlugin contract — M2.B-T7.\n//\n// Each plugin owns a single `.kickjs/types/<id>.d.ts` file. The runner\n// invokes `generate(ctx)` per plugin, prepends a banner, writes only on\n// content change, and surfaces drift in --check mode (CI gate).\n//\n// Built-in plugins (kick/routes, kick/env, kick/assets, kick/db) live under\n// `./builtin`. Adopters register additional plugins via kick.config.ts.\n\nimport type { KickConfig } from '../config'\nimport type { ScanOptions, ScanResult } from './scanner'\n\nexport interface TypegenLogger {\n  info(msg: string): void\n  warn(msg: string): void\n  error(msg: string): void\n}\n\nexport interface TypegenContext {\n  cwd: string\n  config: KickConfig\n  /** Dynamic-import a TS module (Node loader). Used by plugins that need to\n   * read the adopter's schema / route map / asset registry at generate time. */\n  importTs<T = unknown>(absPath: string): Promise<T>\n  /** Write under `cwd`. Caller passes a relPath (e.g. `.kickjs/types/foo.d.ts`). */\n  writeFile(relPath: string, contents: string): Promise<void>\n  /**\n   * Run `scanProject` once per typegen pass, memoizing the result so\n   * multiple plugins (`kick/routes`, `kick/env`, future adopter plugins)\n   * share a single fs walk + AST extraction.\n   *\n   * The runner uses an order-independent cache key derived from the\n   * resolved options (`root`, `cwd`, `extensions`, `exclude`, `envFile`)\n   * — semantically equal options hit the cache regardless of how the\n   * caller built the literal. (We deliberately don't `JSON.stringify`\n   * the options for caching since that would be sensitive to property\n   * insertion order.) Plugins that don't need scanner data can ignore\n   * this method entirely.\n   *\n   * Implementation lives in the runner so test harnesses can inject\n   * a stub scanner; plugins only see the function.\n   */\n  getScanResult(opts: ScanOptions): Promise<ScanResult>\n  log: TypegenLogger\n}\n\nexport interface TypegenPlugin {\n  /** Stable id — used as filename: `.kickjs/types/${id}<outExtension>` (slashes → `__`). */\n  id: string\n  /** Glob patterns the Vite watcher subscribes to; change → re-run this plugin. */\n  inputs: string[]\n  /**\n   * Output filename extension. Default `.d.ts` — the right choice for\n   * pure module-augmentation plugins (kick/db, kick/assets) since\n   * declaration files don't need the runtime-import dance.\n   *\n   * `kick/routes` overrides to `.ts` because it emits hoisted\n   * `import type {...} from '...'` lines at the top of the file. Inline\n   * `import('...').X` references inside `.d.ts` silently degrade to\n   * `unknown` under `moduleResolution: 'bundler'`; emitting `.ts`\n   * sidesteps that quirk and gets full type resolution.\n   *\n   * Adopter-supplied plugins should leave this unset unless they hit\n   * the same hoisted-import constraint.\n   */\n  outExtension?: string\n  /**\n   * Return the augmentation source (without banner — runner prepends).\n   * Return null to skip emission (e.g. no schema file present).\n   */\n  generate(ctx: TypegenContext): Promise<string | null>\n}\n\nexport interface TypegenPluginResult {\n  id: string\n  status: 'written' | 'unchanged' | 'skipped' | 'error' | 'drifted'\n  outFile?: string\n}\n\n/**\n * Thrown by the runner under `--check` when a plugin's generated output\n * differs from what's on disk. Carries every drifted plugin, not just\n * the first, so one CI run reports the full list.\n *\n * This has its own class for a load-bearing reason: `runAllPluginTypegens`\n * wraps the pass in a catch that downgrades plugin failures to a warning\n * so a transiently-broken plugin can't crash the `kick dev` loop. That\n * catch used to swallow this error too, which made `--check` exit 0 on\n * *every* drift — the gate silently passed while printing \"skipped\".\n * The catch now rethrows this type. Do not make it a plain `Error`.\n */\nexport class TypegenDriftError extends Error {\n  /** Plugin ids whose output drifted, in run order. */\n  readonly drifted: readonly { id: string; outFile: string }[]\n\n  constructor(drifted: readonly { id: string; outFile: string }[]) {\n    const list = drifted.map((d) => `    ${d.id} → ${d.outFile}`).join('\\n')\n    super(\n      `kick typegen --check: ${drifted.length} generated file(s) are out of date:\\n${list}\\n` +\n        `  Run \\`kick typegen\\` and commit the result.`,\n    )\n    this.name = 'TypegenDriftError'\n    this.drifted = drifted\n  }\n}\n\n/**\n * Identity factory for {@link TypegenPlugin}. Returns the spec verbatim.\n * Exists for type inference and forward-compatibility — future\n * fields can be added with defaults without breaking adopters.\n *\n * Mirrors {@link defineGenerator} ergonomics. Use at the call site so\n * the plugin's `generate(ctx)` body gets a fully-typed `ctx` without\n * an explicit annotation:\n *\n * @example\n * ```ts\n * import { defineTypegen } from '@forinda/kickjs-cli'\n *\n * export const drizzleTypegen = defineTypegen({\n *   id: 'drizzle',\n *   inputs: ['src/db/schema.ts'],\n *   async generate(ctx) {\n *     const schema = await ctx.importTs(`${ctx.cwd}/src/db/schema.ts`)\n *     return `// declare module …`\n *   },\n * })\n * ```\n */\nexport function defineTypegen(spec: TypegenPlugin): TypegenPlugin {\n  return spec\n}\n","/**\n * Deduplicating reporter for typegen failures in `kick dev` watch mode.\n *\n * The watch pipeline used to swallow errors entirely (`.catch(() => {})`),\n * which left adopters debugging stale `.kickjs/types` with no signal.\n * This reporter warns on every NEW failure message per source, stays\n * quiet for repeats of the same message (each save in a broken state\n * would otherwise re-print it), and re-arms once the source succeeds\n * again so a recurring failure after a fix re-warns.\n */\nexport interface TypegenErrorReporter {\n  /** Report a failure for `source`; emits only if the message changed. */\n  report(source: string, err: unknown): void\n  /** Mark `source` healthy — the next failure emits even if identical. */\n  clear(source: string): void\n}\n\nexport function createTypegenErrorReporter(emit: (message: string) => void): TypegenErrorReporter {\n  const lastMessage = new Map<string, string>()\n  return {\n    report(source, err) {\n      const msg = err instanceof Error ? err.message : String(err)\n      if (lastMessage.get(source) === msg) return\n      lastMessage.set(source, msg)\n      emit(`  kick typegen: ${source} pass failed (${msg}) — types in .kickjs/types may be stale`)\n    },\n    clear(source) {\n      lastMessage.delete(source)\n    },\n  }\n}\n","/**\n * Debounced typegen-on-save watcher — the engine behind `kick dev`'s\n * file-change handling, extracted so `@forinda/kickjs-vite` can run the\n * SAME pipeline when the adopter boots plain `vite` (or any other\n * Vite-embedding tool) instead of `kick dev`.\n *\n * Behaviour (verbatim from the original inline implementation):\n *  - Watcher events batch into one debounced pass (default 100ms).\n *  - `.ts/.tsx/.mts/.cts` changes feed a precise `{changed, removed}`\n *    delta to the incremental scanner; `unlinkDir` forces a full walk\n *    (a dir removal can't be expressed as a per-file delta).\n *  - Files under an `assetMap.<ns>.src` dir mark the pass asset-dirty,\n *    driving an incremental `buildAssets` sweep.\n *  - `.kickjs/**` and `.d.ts` events are ignored (typegen's own output).\n *  - Failures surface through a deduplicating reporter (one warning per\n *    NEW failure message per source; quiet on repeats; re-arms after a\n *    successful pass).\n *\n * Exactly one watcher should own typegen per dev process. `kick dev`\n * claims ownership via {@link TYPEGEN_OWNER_KEY} on `globalThis` before\n * booting Vite; the vite plugin checks the marker and stands down.\n */\nimport path from 'node:path'\n\nimport type { KickConfig } from '../config'\nimport type { runTypegen, writeTypegenArtifacts } from './index'\nimport type { runAllPluginTypegens } from './run-plugins'\nimport type { buildAssets } from '../asset-manager/build'\nimport { createTypegenErrorReporter } from '../commands/typegen-error-reporter'\nimport type { ScanDelta } from './scanner'\n\n/**\n * `globalThis` key claiming typegen ownership for the current process.\n * Set by `kick dev` (which boots Vite in-process) so the vite plugin's\n * own watcher never double-runs the pipeline.\n */\nexport const TYPEGEN_OWNER_KEY = '__kickjs_typegen_owner'\n\nexport type TypegenWatchEvent = 'add' | 'change' | 'unlink' | 'unlinkDir'\n\n/** Injectable pipeline — production uses the real functions. */\nexport interface TypegenDevPipeline {\n  runTypegen: typeof runTypegen\n  runAllPluginTypegens: typeof runAllPluginTypegens\n  writeTypegenArtifacts: typeof writeTypegenArtifacts\n  buildAssets: typeof buildAssets\n}\n\nexport interface TypegenDevWatcherOptions {\n  cwd: string\n  /** Pre-loaded kick.config.ts (null when the project has none). */\n  config: KickConfig | null\n  /**\n   * Warning sink — wired by the caller to `console.warn` plus whatever\n   * HMR broadcast it has (e.g. the `kickjs:typegen-error` custom event).\n   */\n  emitWarning: (message: string) => void\n  /**\n   * Invoked after each pass's plugin chain settles (success or failure).\n   * `kick dev` uses it to schedule the `--typecheck` worker against\n   * fresh `.kickjs/types`.\n   */\n  onPassComplete?: () => void\n  /** Debounce window in ms. @default 100 */\n  debounceMs?: number\n  /** Test seam — defaults to the real typegen pipeline. */\n  pipeline?: TypegenDevPipeline\n}\n\nexport interface TypegenDevWatcher {\n  /** Feed a chokidar-style watcher event into the debounce window. */\n  handleWatchEvent(event: TypegenWatchEvent, file: string): void\n  /**\n   * Run one full (non-incremental) pass immediately — startup catch-up\n   * for callers that didn't run typegen before the server booted.\n   */\n  runOnce(): void\n  /**\n   * Absolute `assetMap.<ns>.src` roots. Vite's default watcher ignores\n   * extensions it doesn't compile, so callers should `watcher.add(...)`\n   * these to receive template-file events at all.\n   */\n  assetSrcRoots: readonly string[]\n  /** Cancel any pending debounced pass. */\n  dispose(): void\n}\n\nexport function createTypegenDevWatcher(opts: TypegenDevWatcherOptions): TypegenDevWatcher {\n  const { cwd, config } = opts\n  const debounceMs = opts.debounceMs ?? 100\n  // Default pipeline resolves lazily — a static import would drag the\n  // whole generator/typegen graph (including dist-layout-only module\n  // initializers) into every consumer of this module.\n  const pipeline: TypegenDevPipeline = opts.pipeline ?? {\n    runTypegen: async (o) => (await import('./index')).runTypegen(o),\n    runAllPluginTypegens: async (o) => (await import('./run-plugins')).runAllPluginTypegens(o),\n    writeTypegenArtifacts: async (dir, results, silent) =>\n      (await import('./index')).writeTypegenArtifacts(dir, results, silent),\n    buildAssets: async (cfg, o) => (await import('../asset-manager/build')).buildAssets(cfg, o),\n  }\n\n  const schemaValidator = config?.typegen?.schemaValidator ?? 'zod'\n  const envFile = config?.typegen?.envFile\n  const typesOutDir = path.resolve(cwd, config?.typegen?.outDir ?? '.kickjs/types')\n\n  const assetSrcRoots: readonly string[] = config?.assetMap\n    ? Object.values(config.assetMap)\n        .map((entry) => entry?.src)\n        .filter((src): src is string => typeof src === 'string' && src.length > 0)\n        .map((src) => path.resolve(cwd, src))\n    : []\n  const hasAssetMap = !!config?.assetMap && Object.keys(config.assetMap).length > 0\n  // Normalize BOTH sides to forward slashes before comparing — chokidar\n  // emits native separators on Windows while `path.resolve` produces\n  // backslash roots, so a raw startsWith would miss either direction.\n  const toPosix = (p: string): string => p.replaceAll('\\\\', '/')\n  const assetRootsPosix = assetSrcRoots.map(toPosix)\n  const isAssetFile = (file: string): boolean => {\n    const posix = toPosix(file)\n    return assetRootsPosix.some((root) => posix === root || posix.startsWith(`${root}/`))\n  }\n\n  const reporter = createTypegenErrorReporter(opts.emitWarning)\n\n  let timer: ReturnType<typeof setTimeout> | null = null\n  let disposed = false\n  const pendingChanged = new Set<string>()\n  const pendingRemoved = new Set<string>()\n  let forceFullScan = false\n  let assetDirty = false\n\n  function firePass(delta: ScanDelta | undefined, rebuildAssets: boolean): void {\n    pipeline\n      .runTypegen({\n        cwd,\n        silent: true,\n        allowDuplicates: true,\n        schemaValidator,\n        envFile,\n        srcDir: config?.typegen?.srcDir,\n        outDir: config?.typegen?.outDir,\n        assetMap: config?.assetMap,\n        changedFiles: delta,\n        // Plugin pipeline runs separately just below; opting out here\n        // avoids double-running it on every debounced trigger.\n        runPlugins: false,\n      })\n      .then(() => reporter.clear('scan'))\n      .catch((err) => reporter.report('scan', err))\n    pipeline\n      .runAllPluginTypegens({ cwd, config, silent: true, changedFiles: delta })\n      .then((r) => pipeline.writeTypegenArtifacts(typesOutDir, r, true))\n      .then(() => reporter.clear('plugins'))\n      .catch((err) => reporter.report('plugins', err))\n      // Post-pass hook AFTER the plugin chain settles so consumers (the\n      // --typecheck worker) see the freshest .kickjs/types this window\n      // produced.\n      .finally(() => opts.onPassComplete?.())\n    if (rebuildAssets && config) {\n      pipeline.buildAssets(config, { cwd, silent: true }).catch(() => {})\n    }\n  }\n\n  function flush(): void {\n    // `undefined` delta → full scan (the `unlinkDir` correctness path).\n    const delta = forceFullScan\n      ? undefined\n      : { changed: [...pendingChanged], removed: [...pendingRemoved] }\n    const rebuildAssets = assetDirty\n    pendingChanged.clear()\n    pendingRemoved.clear()\n    forceFullScan = false\n    assetDirty = false\n    firePass(delta, rebuildAssets)\n  }\n\n  return {\n    assetSrcRoots,\n\n    handleWatchEvent(event, file) {\n      if (disposed) return\n      // Typegen's own output directory — separator-aware so a file\n      // merely NAMED like `my.kickjs.backup.ts` isn't filtered.\n      if (toPosix(file).includes('/.kickjs/')) return\n      if (event === 'unlinkDir') {\n        // Only meaningful if the removed dir could have held scanned\n        // sources or watched assets; cheap to just force a full scan.\n        forceFullScan = true\n        if (hasAssetMap) assetDirty = true\n      } else {\n        if (file.endsWith('.d.ts')) return\n        const isTs = /\\.(ts|tsx|mts|cts)$/.test(file)\n        const isAsset = isAssetFile(file)\n        if (!isTs && !isAsset) return\n        if (isAsset && hasAssetMap) assetDirty = true\n        // Only `.ts` files participate in the source scan delta. Asset-\n        // only changes still trigger the pass (so the asset plugin\n        // re-emits) but contribute nothing to the scan — an empty `.ts`\n        // delta makes the incremental scan a near-instant cache replay.\n        if (isTs) {\n          if (event === 'unlink') {\n            pendingRemoved.add(file)\n            pendingChanged.delete(file)\n          } else {\n            pendingChanged.add(file)\n            pendingRemoved.delete(file)\n          }\n        }\n      }\n      if (timer) clearTimeout(timer)\n      timer = setTimeout(flush, debounceMs)\n    },\n\n    runOnce() {\n      if (disposed) return\n      firePass(undefined, hasAssetMap)\n    },\n\n    dispose() {\n      disposed = true\n      if (timer) clearTimeout(timer)\n      timer = null\n    },\n  }\n}\n","import { pathToFileURL } from 'node:url'\nimport { resolve } from 'node:path'\nimport { toPascalCase, toCamelCase, toKebabCase, pluralize } from '../utils/naming'\nimport { findProjectRoot } from '../utils/project-root'\nimport type { GeneratorContext } from './define'\n\n/** Convert any string to snake_case (`UserPost` / `user-post` → `user_post`). */\nfunction toSnakeCase(name: string): string {\n  return toKebabCase(name).replace(/-/g, '_')\n}\n\n/**\n * Build a {@link GeneratorContext} from the raw name + invocation\n * arguments. Centralises the case-transformation logic so every plugin\n * generator sees the same shape regardless of how the name was typed\n * on the command line (`Post` vs `post` vs `user_post`).\n */\nexport function buildGeneratorContext(input: {\n  name: string\n  args?: string[]\n  flags?: Record<string, string | boolean>\n  modulesDir?: string\n  cwd?: string\n  /**\n   * Resolved project root. When omitted, derived from {@link cwd} via\n   * `findProjectRoot()` so adopter-facing call sites stay zero-config.\n   * Callers that already know the root (e.g. `cli.ts` after one upfront\n   * resolution) should pass it through to avoid redundant fs walks.\n   */\n  projectRoot?: string\n  pluralize?: boolean\n}): GeneratorContext {\n  const cwd = input.cwd ?? process.cwd()\n  const projectRoot = input.projectRoot ?? findProjectRoot(cwd)\n  const usePlural = input.pluralize ?? true\n\n  const pascal = toPascalCase(input.name)\n  const camel = toCamelCase(input.name)\n  const kebab = toKebabCase(input.name)\n  const snake = toSnakeCase(input.name)\n\n  const ctx: GeneratorContext = {\n    name: input.name,\n    pascal,\n    camel,\n    kebab,\n    snake,\n    modulesDir: input.modulesDir ?? 'src/modules',\n    cwd,\n    projectRoot,\n    args: input.args ?? [],\n    flags: input.flags ?? {},\n  }\n\n  if (usePlural) {\n    const pluralKebab = pluralize(kebab)\n    ctx.pluralKebab = pluralKebab\n    ctx.pluralPascal = toPascalCase(pluralKebab)\n    ctx.pluralCamel = toCamelCase(pluralKebab)\n  }\n\n  return ctx\n}\n\n/** Resolve a generator output path against the context's cwd. */\nexport function resolveGeneratorPath(ctx: GeneratorContext, path: string): string {\n  return resolve(ctx.cwd, path)\n}\n\n/**\n * Dynamic-import a generator manifest file. Wraps `pathToFileURL` so\n * callers don't have to think about Windows/Unix path quirks.\n */\nexport async function importManifest(absPath: string): Promise<unknown> {\n  return import(pathToFileURL(absPath).href)\n}\n","import { readFile } from 'node:fs/promises'\nimport { resolve, dirname } from 'node:path'\nimport { existsSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport type { GeneratorSpec } from './define'\nimport { importManifest } from './context'\n\n/**\n * One row in the discovered registry. `source` is the npm package name\n * the generator came from — surfaced in error messages so adopters can\n * see which plugin owns a given generator.\n */\nexport interface DiscoveredGenerator {\n  source: string\n  spec: GeneratorSpec\n}\n\n/**\n * Plugin discovery result, kept around even when no generators were\n * registered so callers can distinguish \"no plugins installed\" from\n * \"no plugins matched the requested name.\"\n */\nexport interface DiscoveryResult {\n  generators: DiscoveredGenerator[]\n  /** Packages whose `kickjs.generators` was loaded successfully. */\n  loaded: string[]\n  /**\n   * Packages we tried to load but failed — typically a missing entry\n   * file or a default export that wasn't an array of GeneratorSpec.\n   */\n  failed: Array<{ source: string; reason: string }>\n}\n\n/** Shape of `package.json` we care about during discovery. */\ninterface PluginPackage {\n  name?: string\n  dependencies?: Record<string, string>\n  devDependencies?: Record<string, string>\n  peerDependencies?: Record<string, string>\n  kickjs?: {\n    generators?: string\n  }\n  keywords?: string[]\n}\n\n/**\n * Discover generator manifests shipped by every kickjs plugin in the\n * project's direct deps. Spec rationale: walking the\n * `node_modules/@scope/kickjs-name/` tree is one option, but reading\n * the project's own `package.json` and resolving each dep through\n * Node's module resolver gives:\n *\n *   1. Predictable scoping — only deps the project actually declared\n *      get scanned, no surprises from transitive packages\n *   2. pnpm `.pnpm` store compatibility — `createRequire().resolve()`\n *      handles the symlinked layout correctly\n *   3. Clear error attribution — the source package name is always\n *      known before the import happens\n *\n * The walk is shallow (direct deps only). Transitive plugins that want\n * to expose generators must be re-exported by a direct dep.\n *\n * Caches per-cwd inside one CLI invocation so a single `kick g` call\n * does the disk + import work exactly once even when multiple\n * generators dispatch through the same registry.\n */\nconst cache = new Map<string, Promise<DiscoveryResult>>()\n\nexport async function discoverPluginGenerators(cwd: string): Promise<DiscoveryResult> {\n  const cached = cache.get(cwd)\n  if (cached) return cached\n  const promise = doDiscover(cwd)\n  cache.set(cwd, promise)\n  return promise\n}\n\n/** Reset the cache — used by tests so repeated runs see fresh fixtures. */\nexport function resetGeneratorDiscoveryCache(): void {\n  cache.clear()\n}\n\nasync function doDiscover(cwd: string): Promise<DiscoveryResult> {\n  const projectPkgPath = resolve(cwd, 'package.json')\n  if (!existsSync(projectPkgPath)) {\n    return { generators: [], loaded: [], failed: [] }\n  }\n\n  const projectPkg = JSON.parse(await readFile(projectPkgPath, 'utf-8')) as PluginPackage\n  const depNames = collectDepNames(projectPkg)\n\n  const require = createRequire(resolve(cwd, 'package.json'))\n\n  const generators: DiscoveredGenerator[] = []\n  const loaded: string[] = []\n  const failed: Array<{ source: string; reason: string }> = []\n\n  for (const depName of depNames) {\n    let depPkgPath: string\n    try {\n      depPkgPath = require.resolve(`${depName}/package.json`)\n    } catch {\n      // Dep declared but not installed (or doesn't expose package.json\n      // via its export map). Skip silently — this is `kick g`, not\n      // `pnpm install`.\n      continue\n    }\n\n    let depPkg: PluginPackage\n    try {\n      depPkg = JSON.parse(await readFile(depPkgPath, 'utf-8')) as PluginPackage\n    } catch (err) {\n      failed.push({ source: depName, reason: `failed to parse package.json: ${err}` })\n      continue\n    }\n\n    if (!depPkg.kickjs?.generators) continue\n\n    const entryRel = depPkg.kickjs.generators\n    const entryAbs = resolve(dirname(depPkgPath), entryRel)\n    if (!existsSync(entryAbs)) {\n      failed.push({\n        source: depName,\n        reason: `kickjs.generators points to missing file: ${entryRel}`,\n      })\n      continue\n    }\n\n    let mod: unknown\n    try {\n      mod = await importManifest(entryAbs)\n    } catch (err) {\n      failed.push({ source: depName, reason: `failed to import manifest: ${err}` })\n      continue\n    }\n\n    const manifest = (mod as { default?: unknown }).default\n    if (!Array.isArray(manifest)) {\n      failed.push({\n        source: depName,\n        reason: `manifest's default export is not an array of GeneratorSpec`,\n      })\n      continue\n    }\n\n    for (const entry of manifest) {\n      if (!isGeneratorSpec(entry)) {\n        failed.push({\n          source: depName,\n          reason: `manifest entry is not a valid GeneratorSpec (missing name/files)`,\n        })\n        continue\n      }\n      generators.push({ source: depName, spec: entry })\n    }\n    loaded.push(depName)\n  }\n\n  return { generators, loaded, failed }\n}\n\nfunction collectDepNames(pkg: PluginPackage): string[] {\n  const set = new Set<string>()\n  for (const block of [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies]) {\n    if (!block) continue\n    for (const name of Object.keys(block)) set.add(name)\n  }\n  return Array.from(set)\n}\n\nfunction isGeneratorSpec(entry: unknown): entry is GeneratorSpec {\n  if (!entry || typeof entry !== 'object') return false\n  const e = entry as Record<string, unknown>\n  return typeof e.name === 'string' && typeof e.files === 'function'\n}\n","import { writeFileSafe } from '../utils/fs'\nimport { buildGeneratorContext, resolveGeneratorPath } from './context'\nimport {\n  discoverPluginGenerators,\n  type DiscoveredGenerator,\n  type DiscoveryResult,\n} from './discover'\nimport type { GeneratorSpec } from './define'\n\nexport interface DispatchInput {\n  /** The generator name typed on the CLI (`kick g command Order` → `'command'`). */\n  generatorName: string\n  /** The first positional argument after the generator name. */\n  itemName: string\n  /** Remaining positional arguments. */\n  args?: string[]\n  /** Parsed flag map (booleans + values). */\n  flags?: Record<string, string | boolean>\n  /** Project context — usually `process.cwd()`. */\n  cwd?: string\n  /**\n   * Resolved project root. When omitted, `buildGeneratorContext` derives\n   * it from `cwd` via `findProjectRoot()`. Pass through when the caller\n   * has already resolved it (the CLI entry point does this once at\n   * startup) to avoid re-walking the filesystem on every generator call.\n   */\n  projectRoot?: string\n  /** Modules dir from `kick.config.ts`. */\n  modulesDir?: string\n  /** Whether the project enables auto-pluralization. */\n  pluralize?: boolean\n}\n\nexport interface DispatchResult {\n  /** Absolute paths of the files that were written. */\n  files: string[]\n  /** Which plugin owns the generator we ran. */\n  source: string\n}\n\n/**\n * Look up a plugin generator by name and run it. Returns `null` when\n * no plugin generator matches — callers can then fall through to the\n * built-in dispatch (module / scaffold / etc.).\n *\n * Resolution order:\n *   1. `additional` — generators sourced from `KickCliPlugin.generators`\n *      via `kick.config.ts > plugins[]`. Authoritative; the canonical\n *      path going forward.\n *   2. `package.json > kickjs.generators` discovery — first-match-wins\n *      in dependency declaration order. Deprecated path retained for\n *      packages that haven't migrated yet.\n *\n * Adopters with conflicts should rename the generator on their side or\n * pin one of the plugins to a different version.\n */\nexport async function tryDispatchPluginGenerator(\n  input: DispatchInput,\n  additional: readonly DiscoveredGenerator[] = [],\n): Promise<DispatchResult | null> {\n  const cwd = input.cwd ?? process.cwd()\n\n  const fromConfig = additional.find((g) => g.spec.name === input.generatorName)\n  if (fromConfig) {\n    return runGenerator(fromConfig.spec, fromConfig.source, input, cwd)\n  }\n\n  const discovery = await discoverPluginGenerators(cwd)\n  const match = findGenerator(discovery, input.generatorName)\n  if (!match) return null\n\n  return runGenerator(match.spec, match.source, input, cwd)\n}\n\n/**\n * Public helper for `kick g --list` — returns every plugin generator\n * the CLI knows about, merging config-supplied entries on top of the\n * package.json discovery result. Config entries always come first.\n */\nexport async function listPluginGenerators(\n  cwd: string,\n  additional: readonly DiscoveredGenerator[] = [],\n): Promise<DiscoveryResult> {\n  const discovered = await discoverPluginGenerators(cwd)\n  const configNames = new Set(additional.map((g) => g.spec.name))\n  const filteredDiscovered = discovered.generators.filter((g) => !configNames.has(g.spec.name))\n  return {\n    generators: [...additional, ...filteredDiscovered],\n    loaded: discovered.loaded,\n    failed: discovered.failed,\n  }\n}\n\nfunction findGenerator(discovery: DiscoveryResult, name: string): DiscoveredGenerator | undefined {\n  return discovery.generators.find((g) => g.spec.name === name)\n}\n\n/**\n * Invoke a {@link GeneratorSpec.files} factory with a fully-populated\n * {@link GeneratorContext}, write every returned file under the context's\n * cwd, and return the absolute paths along with the source plugin name.\n *\n * Threads `input.projectRoot` through to `buildGeneratorContext` so\n * callers that already resolved the project root (the CLI entry does\n * this once at startup) avoid a redundant filesystem walk. When omitted,\n * `buildGeneratorContext` derives `projectRoot` from `cwd` via\n * `findProjectRoot()` — keeping ad-hoc callers zero-config.\n */\nasync function runGenerator(\n  spec: GeneratorSpec,\n  source: string,\n  input: DispatchInput,\n  cwd: string,\n): Promise<DispatchResult> {\n  const ctx = buildGeneratorContext({\n    name: input.itemName,\n    args: input.args,\n    flags: input.flags,\n    modulesDir: input.modulesDir,\n    pluralize: input.pluralize,\n    cwd,\n    projectRoot: input.projectRoot,\n  })\n\n  const files = await spec.files(ctx)\n  const written: string[] = []\n\n  for (const file of files) {\n    const absPath = resolveGeneratorPath(ctx, file.path)\n    await writeFileSafe(absPath, file.content)\n    written.push(absPath)\n  }\n\n  return { files: written, source }\n}\n","import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'\nimport { dirname, join, relative, resolve } from 'node:path'\nimport type { Command } from 'commander'\nimport { loadKickConfig, type KickConfig } from '../config'\nimport { resolveAppRuntime, UPLOAD_DRIVERS, type AppRuntime } from './add'\nimport { colors } from '../utils/colors'\nimport { intro, outro, log } from '../utils/prompts'\n\n/**\n * `kick doctor` — pre-flight checks for a KickJS project's dev\n * environment. Detects common misconfigs before they bite, with an\n * actionable fix hint for each problem.\n *\n * Sibling command to `kick check --deploy`, which scans for production\n * readiness (JWT, CORS, helmet, etc.). Doctor is the dev-setup\n * counterpart — \"is my environment correctly wired?\"\n *\n * Extending: adopters can ship their own checks by exporting a\n * `doctor.checks` array from `kick.config.ts`. Each {@link DoctorCheck}\n * receives the same {@link DoctorContext} the built-ins use and\n * returns one or more {@link DoctorResult}s. The framework stays\n * ORM-agnostic — Prisma / Drizzle / Mongoose-specific checks belong\n * in their respective adapters or in adopter config, not in core.\n */\n\n// ── Public types ──────────────────────────────────────────────────────\n\nexport interface DoctorContext {\n  cwd: string\n  pkg: any | null\n  /**\n   * The project's tsconfig with its full `extends` chain already merged\n   * into `compilerOptions`. `undefined` = no tsconfig.json; `null` = one\n   * exists but couldn't be parsed. Checks should distinguish the two —\n   * they call for different advice.\n   */\n  tsconfig: any | null | undefined\n  /**\n   * The project's HTTP runtime — from kick.config `runtime`, else sniffed from\n   * deps, else `express`. Lets engine-aware checks (engine peers, the upload\n   * multipart driver) validate against the right backend.\n   */\n  runtime: AppRuntime\n}\n\nexport interface DoctorResult {\n  /** Short label for the check (printed first). */\n  name: string\n  status: 'pass' | 'warn' | 'fail'\n  /** Optional extra context after the label (e.g. resolved version). */\n  message?: string\n  /** Multi-line actionable fix shown when status is `warn` or `fail`. */\n  fix?: string\n}\n\nexport type DoctorCheck = (\n  ctx: DoctorContext,\n) => DoctorResult | DoctorResult[] | null | Promise<DoctorResult | DoctorResult[] | null>\n\n/**\n * Shape of a doctor extension — the `doctor` block on `KickConfig`,\n * also the publishable unit that plugins and shared modules use to\n * ship a bundle of related checks.\n */\nexport interface DoctorExtension {\n  /** Extra checks merged after the built-ins. */\n  checks?: DoctorCheck[]\n}\n\n/**\n * Identity helper for adopters / plugins authoring a doctor extension.\n *\n * Provides type inference + autocomplete on the `checks` array without\n * requiring an explicit `: DoctorExtension` annotation. Mirrors the\n * `defineConfig` pattern.\n *\n * @example\n * ```ts\n * // doctor-checks/prisma.ts (shared across projects, or shipped as a\n * // standalone package)\n * import { defineDoctorExtension } from '@forinda/kickjs-cli'\n * import { existsSync } from 'node:fs'\n * import { join } from 'node:path'\n *\n * export const prismaDoctor = defineDoctorExtension({\n *   checks: [\n *     (ctx) => {\n *       if (!existsSync(join(ctx.cwd, 'prisma/schema.prisma'))) return null\n *       const generated = join(ctx.cwd, 'node_modules/@prisma/client/default.js')\n *       return existsSync(generated)\n *         ? { name: 'Prisma client generated', status: 'pass' }\n *         : { name: 'Prisma client generated', status: 'fail', fix: 'pnpm exec prisma generate' }\n *     },\n *   ],\n * })\n *\n * // kick.config.ts\n * import { defineConfig } from '@forinda/kickjs-cli'\n * import { prismaDoctor } from './doctor-checks/prisma'\n *\n * export default defineConfig({ doctor: prismaDoctor })\n * ```\n */\nexport function defineDoctorExtension(ext: DoctorExtension): DoctorExtension {\n  return ext\n}\n\n/**\n * Identity helper for a single doctor check. Pairs with\n * `defineDoctorExtension` when assembling an extension from separate\n * per-check files, and gives the same type-inference win for one-offs.\n *\n * @example\n * ```ts\n * import { defineDoctorCheck } from '@forinda/kickjs-cli'\n *\n * export const checkJwtSecretLength = defineDoctorCheck((ctx) => {\n *   const v = process.env.JWT_SECRET\n *   if (!v || v.length < 32) {\n *     return {\n *       name: 'JWT_SECRET ≥ 32 chars',\n *       status: 'warn',\n *       fix: 'Generate a strong secret: openssl rand -hex 32',\n *     }\n *   }\n *   return { name: 'JWT_SECRET ≥ 32 chars', status: 'pass' }\n * })\n * ```\n */\nexport function defineDoctorCheck(check: DoctorCheck): DoctorCheck {\n  return check\n}\n\n// ── File helpers ──────────────────────────────────────────────────────\n\nfunction safeReadJson(filepath: string): any | null {\n  try {\n    return JSON.parse(readFileSync(filepath, 'utf-8'))\n  } catch {\n    return null\n  }\n}\n\nfunction safeRead(filepath: string): string | null {\n  try {\n    return readFileSync(filepath, 'utf-8')\n  } catch {\n    return null\n  }\n}\n\n/** Depth cap for `extends` chains — well past any real-world config. */\nconst MAX_EXTENDS_DEPTH = 16\n\n/**\n * Parse a tsconfig's JSONC: block comments, line comments, and trailing\n * commas are all legal in tsconfig.json and all illegal in `JSON.parse`.\n *\n * Trailing commas matter more than they look: a plain `JSON.parse` throw\n * used to make `loadTsConfig` return null, which the decorator check\n * reports as \"tsconfig.json present → fail\" for a file that is sitting\n * right there. A formatting nicety became a phantom missing file.\n */\nfunction parseJsonc(raw: string): any | null {\n  const stripped = raw\n    // Block comments, but not inside strings — approximated by requiring\n    // the `/*` to not be preceded by a `:` + quote start. Good enough for\n    // config files; a full JSONC parser isn't worth the dependency here.\n    .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n    .replace(/(^|[^:\"'\\\\])\\/\\/.*$/gm, '$1')\n    // Trailing commas before } or ]\n    .replace(/,(\\s*[}\\]])/g, '$1')\n  try {\n    return JSON.parse(stripped)\n  } catch {\n    return null\n  }\n}\n\n/**\n * Read a tsconfig and resolve its full `extends` chain.\n *\n * Previously this followed exactly one level, only when `extends` was a\n * string, resolved relative paths against the project root rather than\n * the containing file, and parsed parents with a bare `JSON.parse`. Any\n * of those produced the same symptom: a project whose decorator options\n * live in a shared base config got told it was missing\n * `experimentalDecorators` / `emitDecoratorMetadata` that it plainly\n * sets. Lean per-package configs in a monorepo hit every one of them at\n * once.\n *\n * Now: chains of any depth, array `extends` (TS 5.0+), node_modules\n * lookup walking up from the config's own directory (pnpm/monorepo\n * hoisting), directory specifiers resolving to `tsconfig.json`, and\n * JSONC parsing for every file in the chain.\n *\n * Returns `undefined` when no tsconfig.json exists, and `null` when one\n * exists but couldn't be parsed — callers report those differently.\n *\n * Relative paths *inside* inherited options (`baseUrl`, `paths`,\n * `outDir`) are NOT rebased onto the child's directory the way `tsc`\n * does it. Every check that reads this cares only about booleans; revisit\n * if a path-sensitive check is ever added.\n */\nfunction loadTsConfig(cwd: string): any | null | undefined {\n  const tsconfigPath = join(cwd, 'tsconfig.json')\n  if (!existsSync(tsconfigPath)) return undefined\n  return loadTsConfigFrom(tsconfigPath, new Set())\n}\n\nfunction loadTsConfigFrom(filePath: string, seen: Set<string>): any | null {\n  if (seen.has(filePath) || seen.size >= MAX_EXTENDS_DEPTH) return null\n  seen.add(filePath)\n\n  const raw = safeRead(filePath)\n  if (raw === null) return null\n  const cfg = parseJsonc(raw)\n  if (cfg === null || typeof cfg !== 'object') return null\n\n  // `extends` is a string or, since TS 5.0, an array applied left to\n  // right with later entries overriding earlier ones.\n  const parents = Array.isArray(cfg.extends)\n    ? cfg.extends\n    : typeof cfg.extends === 'string'\n      ? [cfg.extends]\n      : []\n\n  // Mutating accumulator rather than re-spreading per iteration — later\n  // entries override earlier ones, which is the same result.\n  const inherited: Record<string, any> = {}\n  for (const ext of parents) {\n    if (typeof ext !== 'string') continue\n    const parentPath = resolveExtends(dirname(filePath), ext)\n    if (!parentPath) continue\n    const parent = loadTsConfigFrom(parentPath, seen)\n    if (!parent) continue\n    Object.assign(inherited, parent.compilerOptions)\n  }\n\n  cfg.compilerOptions = { ...inherited, ...cfg.compilerOptions }\n  return cfg\n}\n\n/**\n * Resolve one `extends` entry to an absolute file path, mirroring how\n * `tsc` resolves them.\n *\n * `fromDir` is the directory of the config doing the extending — NOT the\n * project root. A chain of relative extends only resolves correctly when\n * each hop is relative to its own file.\n *\n * Handles: relative paths with an implied `.json`, bare package\n * specifiers (`@tsconfig/node20`, `@acme/tsconfig/base.json`), directory\n * specifiers that mean `<dir>/tsconfig.json`, and node_modules\n * directories anywhere up the tree — the last of which is what pnpm\n * workspaces need, since the preset is usually hoisted to the repo root\n * rather than sitting in the package's own node_modules.\n */\nfunction resolveExtends(fromDir: string, ext: string): string | null {\n  if (ext.startsWith('.') || ext.startsWith('/')) {\n    return resolveConfigTarget(resolve(fromDir, ext))\n  }\n\n  // Bare specifier — walk up looking for node_modules/<specifier>.\n  let dir = fromDir\n  for (;;) {\n    const candidate = resolveConfigTarget(join(dir, 'node_modules', ext))\n    if (candidate) return candidate\n    const parent = dirname(dir)\n    if (parent === dir) break\n    dir = parent\n  }\n  return null\n}\n\n/**\n * Turn a resolved specifier into an actual config file: the path itself,\n * the path with `.json` appended, or `<path>/tsconfig.json` when it names\n * a directory. `@tsconfig/node20` resolves to a directory, and the old\n * code handed that straight to `JSON.parse` — an EISDIR that silently\n * became \"no inherited options\".\n */\nfunction resolveConfigTarget(target: string): string | null {\n  if (existsSync(target)) {\n    try {\n      if (statSync(target).isDirectory()) {\n        const nested = join(target, 'tsconfig.json')\n        return existsSync(nested) ? nested : null\n      }\n    } catch {\n      return null\n    }\n    return target\n  }\n  const withJson = `${target}.json`\n  return existsSync(withJson) ? withJson : null\n}\n\nfunction escapeRegExp(s: string): string {\n  return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n// ── Individual checks ─────────────────────────────────────────────────\n\nconst MIN_NODE_MAJOR = 20\n\nexport function checkNodeVersion(): DoctorResult {\n  const v = process.version\n  const major = Number.parseInt(v.replace(/^v/, '').split('.')[0]!, 10)\n  if (Number.isNaN(major) || major < MIN_NODE_MAJOR) {\n    return {\n      name: 'Node version',\n      status: 'fail',\n      message: v,\n      fix: `KickJS requires Node ${MIN_NODE_MAJOR} or newer.\\nInstall a supported version via nvm / fnm / volta.`,\n    }\n  }\n  return { name: 'Node version', status: 'pass', message: v }\n}\n\nexport function checkKickJsInstalled(ctx: DoctorContext): DoctorResult {\n  if (!ctx.pkg) {\n    return { name: '@forinda/kickjs installed', status: 'warn', message: 'no package.json' }\n  }\n  const deps = {\n    ...ctx.pkg.dependencies,\n    ...ctx.pkg.peerDependencies,\n  }\n  if (!deps['@forinda/kickjs']) {\n    return {\n      name: '@forinda/kickjs installed',\n      status: 'fail',\n      fix: 'This directory does not look like a KickJS project — `@forinda/kickjs` is not in your package.json. Run `kick doctor` from the project root, or scaffold a fresh project with `kick new <name>`.',\n    }\n  }\n  return { name: '@forinda/kickjs installed', status: 'pass', message: deps['@forinda/kickjs'] }\n}\n\nexport function checkExpressInstalled(ctx: DoctorContext): DoctorResult | null {\n  if (!ctx.pkg) return null\n  const deps = {\n    ...ctx.pkg.dependencies,\n    ...ctx.pkg.peerDependencies,\n  }\n  if (deps['@forinda/kickjs'] && !deps.express) {\n    return {\n      name: 'express installed',\n      status: 'fail',\n      fix: '`@forinda/kickjs` declares `express` as a required peer dependency, but your package.json does not include it. Install: pnpm add express',\n    }\n  }\n  return deps.express ? { name: 'express installed', status: 'pass', message: deps.express } : null\n}\n\n/** Engine peers required by the configured non-Express runtime. */\nconst RUNTIME_PEERS: Record<AppRuntime, string[]> = {\n  express: [], // covered by checkExpressInstalled\n  fastify: ['fastify', '@fastify/middie'],\n  h3: ['h3'],\n}\n\n/**\n * The configured runtime's engine peers are installed. Express is checked by\n * {@link checkExpressInstalled}; this covers the Fastify / h3 runtimes, whose\n * peers are optional on `@forinda/kickjs` and so only required when chosen.\n */\nexport function checkRuntimeEngine(ctx: DoctorContext): DoctorResult | null {\n  if (!ctx.pkg || ctx.runtime === 'express') return null\n  const deps = {\n    ...ctx.pkg.dependencies,\n    ...ctx.pkg.peerDependencies,\n    ...ctx.pkg.devDependencies,\n  }\n  const missing = RUNTIME_PEERS[ctx.runtime].filter((p) => !deps[p])\n  const name = `runtime engine (${ctx.runtime})`\n  if (missing.length > 0) {\n    return {\n      name,\n      status: 'fail',\n      fix: `Resolved runtime '${ctx.runtime}' is missing engine peer(s): ${missing.join(', ')}.\\nInstall: pnpm add ${missing.join(' ')}`,\n    }\n  }\n  return { name, status: 'pass' }\n}\n\n/**\n * When the project actually uses file uploads (`@FileUpload` / the `upload`\n * middleware), the multipart driver for its runtime must be installed —\n * express → multer, fastify → @fastify/multipart, h3 → built-in (nothing).\n * Skips silently when no upload usage is detected, so non-upload apps aren't\n * nagged about a driver they don't need.\n */\nexport function checkUploadDriver(ctx: DoctorContext): DoctorResult | null {\n  if (!ctx.pkg || !srcUsesUpload(ctx.cwd)) return null\n  const driver = UPLOAD_DRIVERS[ctx.runtime]\n  const name = `upload driver (${ctx.runtime})`\n  // h3 parses multipart natively — there is no driver to require.\n  if (!driver.prod) return { name, status: 'pass', message: 'native multipart' }\n  const deps = {\n    ...ctx.pkg.dependencies,\n    ...ctx.pkg.peerDependencies,\n    ...ctx.pkg.devDependencies,\n  }\n  if (!deps[driver.prod]) {\n    return {\n      name,\n      status: 'fail',\n      fix: `This project uses file uploads on the '${ctx.runtime}' runtime, which needs '${driver.prod}'.\\nInstall it: kick add upload (or pnpm add ${driver.prod})`,\n    }\n  }\n  return { name, status: 'pass', message: driver.prod }\n}\n\nconst MAX_UPLOAD_SCAN_FILES = 2000\n\n/** Bounded walk of src/ looking for @FileUpload / upload.single|array|none usage. */\nfunction srcUsesUpload(cwd: string): boolean {\n  const srcDir = join(cwd, 'src')\n  if (!existsSync(srcDir)) return false\n  const pattern = /@FileUpload\\b|\\bupload\\.(single|array|none)\\s*\\(/\n  const stack = [srcDir]\n  let visited = 0\n  while (stack.length > 0 && visited < MAX_UPLOAD_SCAN_FILES) {\n    const current = stack.pop()!\n    let entries\n    try {\n      entries = readdirSync(current, { withFileTypes: true })\n    } catch {\n      continue\n    }\n    for (const entry of entries) {\n      if (visited >= MAX_UPLOAD_SCAN_FILES) break\n      const full = join(current, entry.name)\n      if (entry.isDirectory()) {\n        if (entry.name !== 'node_modules') stack.push(full)\n        continue\n      }\n      if (!/\\.(ts|tsx|mts|cts)$/.test(entry.name)) continue\n      visited++\n      if (pattern.test(safeRead(full) ?? '')) return true\n    }\n  }\n  return false\n}\n\nexport function checkReflectMetadata(ctx: DoctorContext): DoctorResult {\n  if (!ctx.pkg)\n    return { name: 'reflect-metadata installed', status: 'warn', message: 'no package.json' }\n  const deps = {\n    ...ctx.pkg.dependencies,\n    ...ctx.pkg.peerDependencies,\n    ...ctx.pkg.devDependencies,\n  }\n  if (!deps['reflect-metadata']) {\n    return {\n      name: 'reflect-metadata installed',\n      status: 'fail',\n      fix: `KickJS decorators require the reflect-metadata polyfill.\\nInstall it: pnpm add reflect-metadata\\nThen import it at the top of src/index.ts:\\n\\n  import 'reflect-metadata'\\n  // ... rest of bootstrap`,\n    }\n  }\n  return { name: 'reflect-metadata installed', status: 'pass', message: deps['reflect-metadata'] }\n}\n\nexport function checkDecoratorTsConfig(ctx: DoctorContext): DoctorResult[] {\n  if (ctx.tsconfig === undefined) {\n    return [\n      {\n        name: 'tsconfig.json present',\n        status: 'fail',\n        fix: 'Create a tsconfig.json with `experimentalDecorators: true` and `emitDecoratorMetadata: true`. `kick new` scaffolds one automatically.',\n      },\n    ]\n  }\n  if (ctx.tsconfig === null) {\n    // The file exists but we couldn't read it (or something in its\n    // `extends` chain). Reporting the decorator options as missing here\n    // would be a guess, and a wrong one more often than not — say what\n    // actually happened instead.\n    return [\n      {\n        name: 'tsconfig.json readable',\n        status: 'warn',\n        message: 'could not parse tsconfig.json (or a config it extends)',\n        fix: 'Check tsconfig.json for a syntax error, and that every path in its `extends` chain resolves. Decorator options could not be verified.',\n      },\n    ]\n  }\n  const co = ctx.tsconfig.compilerOptions ?? {}\n  const results: DoctorResult[] = []\n  results.push(\n    co.experimentalDecorators === true\n      ? { name: 'tsconfig: experimentalDecorators', status: 'pass' }\n      : {\n          name: 'tsconfig: experimentalDecorators',\n          status: 'fail',\n          fix: 'Add `\"experimentalDecorators\": true` to compilerOptions in tsconfig.json. Without it, @Service / @Controller / @Get etc. don\\'t register any metadata at compile time.',\n        },\n  )\n  results.push(\n    co.emitDecoratorMetadata === true\n      ? { name: 'tsconfig: emitDecoratorMetadata', status: 'pass' }\n      : {\n          name: 'tsconfig: emitDecoratorMetadata',\n          status: 'fail',\n          fix: 'Add `\"emitDecoratorMetadata\": true` to compilerOptions in tsconfig.json. The DI container uses this metadata for constructor-parameter injection.',\n        },\n  )\n  return results\n}\n\n/**\n * The canonical \"you forgot to wire env\" footgun: an env-init file\n * (e.g. `src/env.ts`, `src/config/env.ts`, `src/config/index.ts`)\n * calls `loadEnv(envSchema)` as a side effect, but the app entry\n * (`src/index.ts` / `src/main.ts`) doesn't import it. Result:\n * `ConfigService.get('X')` returns undefined while `@Value('X')`\n * works via process.env fallback, so adopters get half-broken config\n * without seeing an error.\n *\n * Detection walks the common env-file locations, identifies the ones\n * that actually call `loadEnv(`, then verifies the app entry imports\n * one of them (via relative path or `@/` alias) BEFORE `bootstrap(`.\n */\nexport function checkEnvWiring(ctx: DoctorContext): DoctorResult | null {\n  const envCandidates = [\n    'src/env.ts',\n    'src/env/index.ts',\n    'src/config/env.ts',\n    'src/config/index.ts',\n  ]\n  const envFiles = envCandidates\n    .map((p) => join(ctx.cwd, p))\n    .filter((p) => existsSync(p))\n    .filter((p) => /\\bloadEnv\\s*\\(/.test(safeRead(p) ?? ''))\n\n  if (envFiles.length === 0) return null\n\n  const indexPath = ['src/index.ts', 'src/main.ts']\n    .map((p) => join(ctx.cwd, p))\n    .find((p) => existsSync(p))\n  if (!indexPath) {\n    return {\n      name: 'env wiring',\n      status: 'warn',\n      message: 'env-init file exists but no src/index.ts or src/main.ts found',\n    }\n  }\n  const indexContent = safeRead(indexPath) ?? ''\n  const indexDir = dirname(indexPath)\n\n  // Build the set of import specifiers that would wire any of these\n  // env files — relative paths (`./env`, `./config/env`, …) and the\n  // `@/` alias variant (`@/env`, `@/config/env`, …). For each file we\n  // accept both the with-`/index` suffix and the bare-directory form,\n  // since both resolve to the same module under TypeScript.\n  const importSpecs: string[] = []\n  for (const envFile of envFiles) {\n    const relPath = relative(indexDir, envFile).replace(/\\\\/g, '/').replace(/\\.ts$/, '')\n    const relImport = relPath.startsWith('.') ? relPath : './' + relPath\n    const relImportNoIndex = relImport.replace(/\\/index$/, '')\n    importSpecs.push(relImport, relImportNoIndex)\n\n    // `@/` is the standard alias the scaffold and most adopters use;\n    // it points at `src/`. Derive the @/-prefixed path from the file's\n    // location under src/.\n    const srcRel = envFile.replace(/\\\\/g, '/').match(/\\/src\\/(.+?)(?:\\.ts)?$/)\n    if (srcRel) {\n      const aliasImport = '@/' + srcRel[1]\n      const aliasNoIndex = aliasImport.replace(/\\/index$/, '')\n      importSpecs.push(aliasImport, aliasNoIndex)\n    }\n  }\n\n  let earliestImportIdx = -1\n  for (const spec of new Set(importSpecs)) {\n    const re = new RegExp(`^import\\\\s+(?:.*?from\\\\s+)?['\"]${escapeRegExp(spec)}['\"]`, 'm')\n    const m = indexContent.match(re)\n    if (m && m.index !== undefined) {\n      if (earliestImportIdx === -1 || m.index < earliestImportIdx) {\n        earliestImportIdx = m.index\n      }\n    }\n  }\n\n  const bootstrapIdx = indexContent.search(/\\bbootstrap\\s*\\(/)\n  const sample = envFiles.map((p) => relative(ctx.cwd, p).replace(/\\\\/g, '/')).join(', ')\n\n  if (earliestImportIdx === -1) {\n    return {\n      name: 'env wiring',\n      status: 'fail',\n      message: sample,\n      fix: `An env-init file (${sample}) calls \\`loadEnv(...)\\` but \\`${relative(ctx.cwd, indexPath).replace(/\\\\/g, '/')}\\` doesn't import it.\\nWithout this, ConfigService.get('X') returns undefined while @Value('X') works via process.env fallback — a half-broken config you won't notice until something is missing.\\n\\nFix: add a side-effect import at the top of ${relative(ctx.cwd, indexPath).replace(/\\\\/g, '/')} (above bootstrap()), pointing at one of the detected files. For example:\\n\\n  import './env'\\n  // or\\n  import './config'\\n  // or, with the @/ alias:\\n  import '@/config/env'`,\n    }\n  }\n  if (bootstrapIdx !== -1 && earliestImportIdx > bootstrapIdx) {\n    return {\n      name: 'env wiring',\n      status: 'warn',\n      message: 'env-init imported AFTER bootstrap() — should be before',\n      fix: `Move the env import above the bootstrap() call so the schema runs before any service reads from ConfigService.`,\n    }\n  }\n  return { name: 'env wiring', status: 'pass' }\n}\n\n/**\n * Find the newest file mtime under a directory tree, recursively.\n * Returns `0` when the tree is empty or unreadable. Walks at most\n * {@link MAX_TYPEGEN_FILES} entries to bound the cost on large\n * generated trees.\n *\n * Necessary because the directory's own `mtimeMs` does not update\n * when existing files inside are rewritten — `kick typegen` can run\n * successfully and the directory stat still looks stale. The newest\n * file mtime is the honest measure.\n */\nfunction newestFileMtime(dir: string, budget = MAX_TYPEGEN_FILES): number {\n  let newest = 0\n  let visited = 0\n  const stack = [dir]\n  while (stack.length > 0 && visited < budget) {\n    const current = stack.pop()!\n    let entries\n    try {\n      entries = readdirSync(current, { withFileTypes: true })\n    } catch {\n      continue\n    }\n    for (const entry of entries) {\n      if (visited >= budget) break\n      visited++\n      const full = join(current, entry.name)\n      if (entry.isDirectory()) {\n        stack.push(full)\n        continue\n      }\n      try {\n        const m = statSync(full).mtimeMs\n        if (m > newest) newest = m\n      } catch {\n        // skip unreadable files\n      }\n    }\n  }\n  return newest\n}\n\nconst MAX_TYPEGEN_FILES = 2000\n\nexport function checkTypegenFreshness(ctx: DoctorContext): DoctorResult | null {\n  const typegenDir = join(ctx.cwd, '.kickjs', 'types')\n  if (!existsSync(typegenDir)) return null\n  const newest = newestFileMtime(typegenDir)\n  if (newest === 0) return null\n  const ageMs = Date.now() - newest\n  const ageMin = Math.floor(ageMs / 60_000)\n  if (ageMin > 60) {\n    return {\n      name: 'typegen freshness',\n      status: 'warn',\n      message: `last updated ${ageMin} minutes ago`,\n      fix: 'Re-run `kick typegen` (or `kick dev`, which runs it on every reload) so generated types match the current code.',\n    }\n  }\n  return {\n    name: 'typegen freshness',\n    status: 'pass',\n    message: ageMin === 0 ? 'just now' : `${ageMin}m ago`,\n  }\n}\n\n/** Test-runner configs that mean \"this project has a suite\". */\nconst TEST_RUNNER_CONFIGS = [\n  'vitest.config.ts',\n  'vitest.config.js',\n  'vitest.config.mts',\n  'vite.config.ts',\n  'jest.config.ts',\n  'jest.config.js',\n]\n\n/**\n * Warn when a project has a `.env` and a test suite but no `.env.test`.\n *\n * KickJS loads dotenv at import time with `override: false`, so vars the\n * test runner pins are safe but everything it *doesn't* pin is backfilled\n * from the developer's `.env`. That makes the runner's `env` block a\n * hand-maintained allow-list, and the failure is silent: a suite can pin\n * its database URL and still reach live development services through the\n * vars it forgot.\n *\n * Under a test run, a `.env.test` / `.env.test.local` is read INSTEAD of the\n * generic `.env` and `.env.local` — no layering, no fallback — which closes\n * that hole, but only if one of those files exists. It is opt-in, and an\n * opt-in nobody discovers is worth nothing, hence this check.\n */\nconst TEST_ENV_FILES = ['.env.test', '.env.test.local'] as const\nconst GENERIC_ENV_FILES = ['.env', '.env.local'] as const\n\n/**\n * The value a `KICKJS_ENV_FILE` override would carry into a test run, or\n * `null` if nothing sets it. Checks the package's test scripts first (an\n * override pinned there applies to everyone), then doctor's own env.\n *\n * Deliberately a shallow scan of `scripts.test*`: a var exported from a\n * developer's shell profile or a CI job definition is not visible from\n * here, so this catches the common committed case rather than every case.\n */\nfunction findTestEnvFileOverride(ctx: DoctorContext): string | null {\n  const scripts = ctx.pkg?.scripts\n  if (scripts && typeof scripts === 'object') {\n    for (const [name, value] of Object.entries(scripts)) {\n      if (!name.startsWith('test') || typeof value !== 'string') continue\n      const hit = /KICKJS_ENV_FILE=(\\S+)/.exec(value)\n      if (hit?.[1]) return hit[1]\n    }\n  }\n  return process.env['KICKJS_ENV_FILE'] ?? null\n}\n\nexport function checkTestEnvIsolation(ctx: DoctorContext): DoctorResult | null {\n  const hasTestRunner = TEST_RUNNER_CONFIGS.some((f) => existsSync(join(ctx.cwd, f)))\n  if (!hasTestRunner) return null\n\n  const generic = GENERIC_ENV_FILES.filter((f) => existsSync(join(ctx.cwd, f)))\n  if (generic.length === 0) return null\n\n  const isolating = TEST_ENV_FILES.filter((f) => existsSync(join(ctx.cwd, f)))\n  if (isolating.length > 0) {\n    // `KICKJS_ENV_FILE` is consulted BEFORE the test-mode short-circuit, so\n    // it wins outright: a script running `KICKJS_ENV_FILE=.env vitest` reads\n    // `.env` no matter how complete the `.env.test` sitting next to it is.\n    // Reporting a plain pass there would certify isolation that is not in\n    // effect. Look for the override in the test scripts (where it would be\n    // pinned for everyone) and in doctor's own environment.\n    const override = findTestEnvFileOverride(ctx)\n    if (override && override !== 'off' && override !== 'none') {\n      return {\n        name: 'test env isolation',\n        status: 'warn',\n        message: `${isolating.join(', ')} present, but KICKJS_ENV_FILE=${override} overrides it`,\n        fix:\n          `KICKJS_ENV_FILE is checked before test-mode isolation, so ${isolating[0]} is\\n` +\n          `ignored and \"${override}\" is read instead. Drop the override to use the\\n` +\n          'test files, or set KICKJS_ENV_FILE=off if env comes from the runner.',\n      }\n    }\n    return {\n      name: 'test env isolation',\n      status: 'pass',\n      message: `${isolating.join(', ')} present`,\n    }\n  }\n\n  return {\n    name: 'test env isolation',\n    status: 'warn',\n    message: `${generic.join(', ')} present, no .env.test`,\n    fix:\n      \"Under NODE_ENV=test (or vitest's VITEST) KickJS reads .env.test / .env.test.local\\n\" +\n      `INSTEAD of ${generic.join(' and ')} — no layering, no fallback. Without one of\\n` +\n      'those files, every var your test config does not pin resolves from your\\n' +\n      'development env — including DATABASE_URL and any third-party credentials.\\n' +\n      '\\n' +\n      'Create .env.test holding the whole environment the suite should run against\\n' +\n      '(a var you leave out is then missing, not inherited), or set KICKJS_ENV_FILE=off\\n' +\n      'and supply env from the test runner.',\n  }\n}\n\n// ── Runner ────────────────────────────────────────────────────────────\n\nconst BUILT_IN_CHECKS: DoctorCheck[] = [\n  () => checkNodeVersion(),\n  checkKickJsInstalled,\n  checkExpressInstalled,\n  checkRuntimeEngine,\n  checkUploadDriver,\n  checkReflectMetadata,\n  checkDecoratorTsConfig,\n  checkEnvWiring,\n  checkTestEnvIsolation,\n  checkTypegenFreshness,\n]\n\nexport async function runChecks(\n  cwd: string,\n  options: { extraChecks?: DoctorCheck[]; runtime?: AppRuntime } = {},\n): Promise<DoctorResult[]> {\n  const ctx: DoctorContext = {\n    cwd,\n    pkg: safeReadJson(join(cwd, 'package.json')),\n    tsconfig: loadTsConfig(cwd),\n    runtime: options.runtime ?? 'express',\n  }\n  const checks = [...BUILT_IN_CHECKS, ...(options.extraChecks ?? [])]\n  const out: DoctorResult[] = []\n  for (const check of checks) {\n    // Per-check try/catch so one buggy extension can't abort the whole\n    // report. A throwing check produces a synthetic `fail` result with\n    // the error message; the loop continues to the next check.\n    let r: DoctorResult | DoctorResult[] | null\n    try {\n      r = await check(ctx)\n    } catch (err) {\n      out.push({\n        name: check.name || 'doctor check',\n        status: 'fail',\n        message: err instanceof Error ? err.message : String(err),\n      })\n      continue\n    }\n    if (r == null) continue\n    if (Array.isArray(r)) out.push(...r)\n    else out.push(r)\n  }\n  return out\n}\n\n// ── Output ────────────────────────────────────────────────────────────\n\nfunction statusTag(status: DoctorResult['status']): string {\n  switch (status) {\n    case 'pass':\n      return colors.green('✔')\n    case 'warn':\n      return colors.yellow('⚠')\n    case 'fail':\n      return colors.red('✖')\n  }\n}\n\nfunction formatResult(r: DoctorResult): string {\n  const tag = statusTag(r.status)\n  const tail = r.message ? `  ${colors.dim(`(${r.message})`)}` : ''\n  return `${tag}  ${r.name}${tail}`\n}\n\nfunction formatFix(fix: string): string {\n  return fix\n    .split('\\n')\n    .map((line) => `   ${colors.dim('→')} ${line}`)\n    .join('\\n')\n}\n\n// ── Command registration ──────────────────────────────────────────────\n\nfunction extractDoctorChecks(config: KickConfig | null): DoctorCheck[] {\n  return config?.doctor?.checks ?? []\n}\n\nexport function registerDoctorCommand(program: Command): void {\n  program\n    .command('doctor')\n    .description('Pre-flight checks for your KickJS project (dev environment health)')\n    .action(async () => {\n      const cwd = process.cwd()\n      const config = await loadKickConfig(cwd)\n      const extraChecks = extractDoctorChecks(config)\n      const runtime = await resolveAppRuntime(cwd)\n\n      intro('KickJS Doctor')\n\n      const results = await runChecks(cwd, { extraChecks, runtime })\n\n      for (const r of results) {\n        log.message(formatResult(r))\n        if (r.fix && r.status !== 'pass') {\n          log.message(formatFix(r.fix))\n        }\n      }\n\n      const passed = results.filter((r) => r.status === 'pass').length\n      const warns = results.filter((r) => r.status === 'warn').length\n      const fails = results.filter((r) => r.status === 'fail').length\n\n      const passText = colors.green(`${passed} passed`)\n      const warnText =\n        warns > 0 ? colors.yellow(`${warns} warning${warns === 1 ? '' : 's'}`) : `${warns} warnings`\n      const failText =\n        fails > 0 ? colors.red(`${fails} error${fails === 1 ? '' : 's'}`) : `${fails} errors`\n      const summary = [passText, warnText, failText].join(', ')\n\n      if (fails > 0) {\n        outro(`${summary} — fix the errors above before running the app`)\n        process.exit(1)\n      } else if (warns > 0) {\n        outro(`${summary} — review the warnings`)\n      } else {\n        outro(colors.green(`${summary} — your environment looks good`))\n      }\n    })\n}\n"],"mappings":"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"}