# SKILL: Analyzer — 63 reglas, troubleshooting, extender

Cargar cuando: necesitás conocer las reglas del analyzer, agregar regla nueva, o resolver un error específico.

## 63 REGLAS

| # | Regla | ❌ | ✅ | Code | Sev |
|---|---|---|---|---|---|
| 1 | Props con type nativo | `type: 'string'` | `type: String` | PROP_MISSING_TYPE | error |
| 2 | Emits kebab-case | `'userSaved'` | `'user-saved'` | EMIT_CAMELCASE | error |
| 3 | Return 3 llaves | `{...,methods}` | `{state,computed,actions}` | SETUP_UNKNOWN_RETURN_KEY | error |
| 4 | Handler con actions. | `@click="fn"` | `@click="actions.fn"` | HANDLER_NOT_IN_ACTIONS | error |
| 5 | v-for namespaced | `v-for="x in items"` | `v-for="x in state.items"` | VFOR_NOT_NAMESPACED | error |
| 6 | v-if namespaced | `v-if="visible"` | `v-if="state.visible"` | VIF_NOT_NAMESPACED | warn |
| 7 | No v-model | `v-model="x"` | `:value + @input` | V_MODEL_UNSUPPORTED | error |
| 8 | No ref/reactive | `ref(0)` | `signal(0)` | REF_REACTIVE_USAGE | error |
| 9 | No provide/inject | `provide()` | `defineStore()` | PROVIDE_INJECT_USAGE | error |
| 10 | No fetch en componente | `fetch()` en `.ark` | `createService()` | DIRECT_FETCH_IN_COMPONENT | error |
| 11 | No lógica en template | `@click="x++"` | computed + action | LOGIC_IN_TEMPLATE | error |
| 12 | Actions function decl | `const f = () =>` | `function f() {}` | ARROW_FUNCTION_ACTION | warn |
| 13 | v-for con :key | sin `:key` | `:key="x.id"` | VFOR_MISSING_KEY | error |
| 14 | `<style>` scoped | `<style>` | `<style scoped>` | STYLE_BLOCK_IGNORED | error |
| 14b | NO `props.X.value` en template | `{{ props.x.value }}` | `{{ props.x }}` | PROP_VALUE_IN_TEMPLATE | error |
| 14c | NO action curried en `@event` nativo | `function fn(id){return()=>{...}}` + `@click="actions.fn(x)"` | `function fn(id){signal.value=id}` | CURRIED_ACTION_IN_TEMPLATE | error |
| 15 | Teleport requiere import | `<Teleport>` sin import | `import { Teleport }` | MISSING_TELEPORT_IMPORT | error |
| 16 | Usar primitivos | `<button class="bg-primary">` | `<Button variant="primary">` | PRIMITIVE_INLINE | error |
| 17 | No override primitivo | `<Button class="extra">` | agregar variant | OVERRIDE_PRIMITIVE_STYLE | error |
| 18 | img alt (a11y) | `<img :src/>` | `<img :src alt="..."/>` | IMG_MISSING_ALT | error |
| 19 | No @click en div/span (a11y) | `<div @click>` | `<Button variant="ghost">` | CLICK_ON_NON_INTERACTIVE | error |
| 20 | Button async + :loading | sin loading | `:loading="state.saving"` | BUTTON_ASYNC_NO_LOADING | error |
| 21 | Async + try/catch | `await x()` sin try | try/catch + toast.error | ASYNC_ACTION_NO_ERROR_HANDLING | error |
| 22 | Comp <250 líneas | 300 | dividir | COMPONENT_TOO_LONG | warn |
| 23 | <10 props | 12 | dividir | TOO_MANY_PROPS | warn |
| 24 | Template depth ≤8 | 9 | extraer hijo | TEMPLATE_DEEP_NESTING | warn |
| 25 | Cyclomatic ≤10 | 12 ramas | dividir | HIGH_CYCLOMATIC_COMPLEXITY | warn |
| 26 | Store <15 actions / <10 state | god store | dividir | GOD_STORE | warn |
| 27 | Test paralelo | `.ark` sin `.test.ts` | crear test | COMPONENT_MISSING_TEST | warn |
| 28 | No hash links | `href="#/..."` | `navigateTo()` + action | HASH_LINK_IN_HISTORY_ROUTER | error |
| 29 | Layout store-dependent primitives | `<ToastContainer/>` sin import/store | crear store + import o sacar del layout | STORELESS_PRIMITIVE_IN_LAYOUT | error |
| 30 | No texto "Cargando..." | `<div>Cargando...</div>` | `<Skeleton />` o `<Spinner />` | LOADING_TEXT_IN_TEMPLATE | error |
| 31 | No texto "No hay..." | `<div>No hay resultados</div>` | `<EmptyState title="..." />` | EMPTY_STATE_TEXT | error |
| 32 | No href interno | `<a href="/page">` | `@click="actions.go"` + `navigateTo()` | HREF_NAVIGATION | error |
| 33 | No `<table>` nativo | `<table><tr><td>` | `<DataTable :rows :columns />` | NATIVE_TABLE | error |
| 34 | Page con SEO | page sin `useSeoMeta()` | `useSeoMeta({ title, description, ... })` | MISSING_SEO_META | error |
| 35 | No `<textarea>` nativo | `<textarea>` | `<Textarea :value :onChange />` | NATIVE_TEXTAREA | error |
| 36 | No checkbox nativo | `<input type="checkbox">` | `<Checkbox :checked :onChange />` | NATIVE_CHECKBOX | error |
| 37 | No radio nativo | `<input type="radio">` | `<RadioGroup :value :options :onChange />` | NATIVE_RADIO | error |
| 38 | No file input nativo | `<input type="file">` | `<FileUpload :onUpload />` | NATIVE_FILE_INPUT | error |
| 39 | No range nativo | `<input type="range">` | `<Slider :value :min :max :onChange />` | NATIVE_RANGE | error |
| 40 | Form manual → defineForm | 3+ signals + `@submit` | `defineForm({ schema, submit })` | MANUAL_FORM | error |
| 41 | Tabs manual → `<Tabs>` | `v-if="state.tab === 'x'"` x2+ | `<Tabs :items :activeKey :onChange />` | MANUAL_TABS | error |
| 42 | Toast manual → useToast | `signal()` para toast + `v-if` | `useToast()` + `<ToastContainer/>` | MANUAL_TOAST | error |
| 43 | confirm() → useConfirm | `confirm('Sure?')` nativo | `useConfirm()` + `<ConfirmDialog/>` | NATIVE_CONFIRM | error |
| 44 | Paginacion manual | `totalPages` + `currentPage` + `v-for pages` | `<Pagination :currentPage :totalPages />` | MANUAL_PAGINATION | error |
| 45 | No number nativo | `<input type="number">` | `<NumberInput :value :min :max :step />` | NATIVE_NUMBER_INPUT | error |
| 46 | No date nativo | `<input type="date">` | `<DatePicker :value />` | NATIVE_DATE_INPUT | error |
| 47 | No time nativo | `<input type="time">` | `<TimePicker :value />` | NATIVE_TIME_INPUT | error |
| 48 | No email nativo | `<input type="email">` | `<Input type="email" />` | NATIVE_EMAIL_INPUT | error |
| 49 | No password nativo | `<input type="password">` | `<Input type="password" />` | NATIVE_PASSWORD_INPUT | error |
| 50 | No tel nativo | `<input type="tel">` | `<Input type="tel" />` | NATIVE_TEL_INPUT | error |
| 51 | No `<hr>` | `<hr />` | `<Separator />` | NATIVE_HR | error |
| 52 | No `<progress>` | `<progress>` | `<ProgressBar :value :max />` | NATIVE_PROGRESS | error |
| 53 | No `<details>` | `<details>` | `<Accordion :items />` | NATIVE_DETAILS | error |
| 54 | No `<dialog>` | `<dialog>` | `<Dialog :open :onClose />` | NATIVE_DIALOG_ELEMENT | error |
| 55 | No `<select multiple>` | `<select multiple>` | `<MultiSelect :value :options />` | NATIVE_SELECT_MULTIPLE | error |
| 56 | Tooltip manual | `@mouseenter` | `<Tooltip content><Child /></Tooltip>` | MANUAL_TOOLTIP | error |
| 57 | Modal manual | `fixed inset-0 z-50` | `<Dialog>` o `<Drawer>` | MANUAL_MODAL | error |
| 58 | Dropdown manual | toggle + `v-if state.menu` | `<DropdownMenu :items />` | MANUAL_DROPDOWN | error |
| 59 | Stepper manual | `currentStep` + `nextStep()` | `<Stepper :steps />` | MANUAL_STEPPER | error |
| 60 | Card manual | `div.border.rounded.shadow` | `<Card>` | MANUAL_CARD | error |
| 61 | Avatar manual | `img.rounded-full` | `<Avatar :src :alt />` | MANUAL_AVATAR | error |
| 62 | Alert manual | `div.bg-red/green.rounded` | `<Alert variant />` | MANUAL_ALERT | error |
| 63 | Badge manual | `span.rounded-full.text-xs.px` | `<Badge variant size />` | MANUAL_BADGE | error |

**Reglas adicionales (estructura + cross-file) que también puede emitir el analyzer**:

| Code | Cuándo | Severity |
|---|---|---|
| MISSING_TEMPLATE / MISSING_SCRIPT / MISSING_LANG_TS / WRONG_TEMPLATE_ORDER | `.ark` mal estructurado | error |
| MISSING_COMPONENT_NAME | `defineComponent({...})` sin `name` | error |
| HTML_COMMENT_IN_TEMPLATE | `<!-- -->` en template | error (con fixOp) |
| TEMPLATE_UNDEFINED_REF | `state.x` en template sin estar en return | error (fixOp) |
| UNUSED_RETURN_KEY | key en return no usada en template ni script | warn |
| SIGNAL_IN_COMPUTED | `signal()` exportado en `return.computed` | error (fixOp) |
| LAYER_IMPORT_VIOLATION | import que viola las capas (ui→features, services→stores) | error |
| STATIC_INLINE_STYLE | `style="..."` estático (usar utilities Tailwind) | warn (fixOp) |
| SCOPED_STYLE_TAILWIND_DUPLICATE | `<style scoped>` con props que Tailwind cubre (padding, flex, gap…) | warn |
| FILE_NAMING_CONVENTION | naming canónico por capa | warn |
| TOO_MANY_STORES | componente con > 3 stores | warn |
| FUNCTION_TOO_LONG | función con > 40 líneas | warn |
| DUPLICATE_INTERFACE | misma `interface X` en 2+ `.ark` | warn (cross-file) |
| CSS_DUPLICATES_TAILWIND | regla CSS con solo props que Tailwind cubre | error (cross-file) |
| EFFECT_NO_CLEANUP | `effect()` sin `onUnmount()` para detenerlo | error |
| LISTENER_NO_CLEANUP | `addEventListener` sin `removeEventListener` en onUnmount | error |

**Total real: ~49 codes**. La lista canónica vive en el código (`src/compiler/analyzer.ts`). Para verla completa con sus mensajes y fixOps, correr `npx ark analyze --json` sobre un proyecto.

Suprimir por archivo: `<!-- arckode-disable CODE -->` o `// arckode-disable CODE` en primeras 5 líneas.

## TROUBLESHOOTING

| Error | Causa | Fix |
|---|---|---|
| `Failed to load transformWithEsbuild` | Vite 6+ | `bun add -d vite@5.4` |
| `Unknown file extension ".ark"` | plugin faltante | `plugins: [arkcodeUi(), tailwindcss()]` |
| `arkcodeUi is not a function` | typo (K minúscula) | `import { arkcodeUi } from 'arckode-ui/vite'` |
| Tailwind no aplica | sintaxis v3 | `@import "tailwindcss"` + `@tailwindcss/vite` |
| Pantalla en blanco | id de mount inexistente | verificar id en index.html |
| `process is not defined` | `/server` en cliente | `arckode-ui` (sin `/server`) en `.ark` |
| `<Teleport>` no renderiza | target no existe al mount | `<div id="modals"></div>` en index.html |
| `Cannot read properties of undefined (reading 'value')` | `props.X.value` en template | quitar `.value`; default en setup |
| `[Component] X es requerido` | falta prop obligatorio | leer mensaje, pasar prop |
| Componente queda vacío sin error | `state.X.value` o `props.X.value` en template | quitar `.value` |

No listados: `npx ark analyze` → leer `violation.fix`.

---

## Extender — agregar regla nueva

### Interfaces

```typescript
interface Violation {
  code: string                                  // UPPER_SNAKE
  severity: 'error' | 'warning'                 // error bloquea build prod
  line: number                                  // 1-based
  message: string                               // qué + valor entre comillas
  fix?: string                                  // qué cambiar EXACTO — obligatorio si determinístico
  fixOp?: FixOp                                 // para `ark fix` automático
}
interface FixOp {
  type: 'replace' | 'insert_after' | 'delete_line'
  line: number
  search?: string; replace?: string; insert?: string
}
```

### Pasos

```typescript
function checkMyRule(source: string): Violation[] {
  const violations: Violation[] = []
  const tpl = extractTemplateContent(source); if (!tpl) return violations
  // detectar, push violations con fix
  return violations
}
// en analyze():
if (!missing.template) violations.push(...checkMyRule(source))
// test:
test('MY_RULE', () => {
  const v = findViolation(analyze(bad, 'X.ark'), 'MY_RULE')
  expect(v?.fix).toContain('esperado')
})
```
Documentar en la tabla de arriba + README.

### Helpers

```typescript
extractTemplateContent(source)            // { content, lineOffset } | null
extractBalancedBlock(source, startIdx)    // { content, start, end } | null
getTopLevelObjectKeys(content)            // string[]
getLineNumber(source, index)              // 1-based
getLines(source)                          // string[]
```

### Retrocompat (REGLA #2 del repo)

- NUNCA cambiar `code` existente (CI parsers dependen)
- NUNCA eliminar regla (marcar deprecated en comment, seguir emitiendo)
- NUNCA quitar `fix` ya existente
- OK: mejorar mensaje/fix, agregar reglas nuevas

## Política red

`fetch()` directo: SOLO en `src/services/`. En `.ark` y composables `.ts` dispara `DIRECT_FETCH_IN_COMPONENT`. Composables envuelven services (loading/error/data reactivos), no llaman `fetch()` directo.

## `formatViolation` output

```
[arckode-ui] UserCard.ark:5
  CODE: mensaje con "valor" entre comillas.

  > 5 | <button @click="handleClick">
  Fix: instrucción concreta.
```
`Fix:` solo si `violation.fix !== undefined`.

## Cross-file (sobre proyecto completo)

- `DUPLICATE_INTERFACE` (warn) — misma `interface X` en 2+ `.ark`
- `CSS_DUPLICATES_TAILWIND` (error) — regla `.css` con solo props que Tailwind cubre
- `COMPONENT_MISSING_TEST` (warn) — `.ark` fuera de `ui/` sin `.test.ts` paralelo

`.ts` en `composables/`: solo lógica (`REF_REACTIVE_USAGE`, `PROVIDE_INJECT_USAGE`, `DIRECT_FETCH_IN_COMPONENT`, `FUNCTION_TOO_LONG`, `LAYER_IMPORT_VIOLATION`).
