import { z } from 'zod'; import type { Tool } from './types'; /** * debug — diagnose common AI/UI (kai-*) integration failures. * * Given a `symptom` (prose) and/or a `snippet` (code), returns the likely * cause + fix for the classic `kai-*` failure modes so a harness can * self-correct without a human in the loop. * * Rule set sourced from: * - apps/docs/src/content/docs/guides/for-ai-agents.mdx §"What agents most commonly get wrong" * - context7.json `rules` array (5 contract rules) */ interface Rule { id: string; test: (text: string) => boolean; title: string; cause: string; fix: string; } const RULES: Rule[] = [ { // Rule 1 — array/object data set as an HTML attribute // Source: for-ai-agents.mdx §1; context7.json rule 2 id: 'array-as-attribute', test: (t) => /\b(messages|models|context|suggestions|triggers)\s*=\s*["']/.test(t), title: 'Array/object prop set as an HTML attribute (silent failure)', cause: 'An HTML attribute is always a string. Passing `messages`, `models`, `context`, ' + '`suggestions`, or `triggers` as an HTML attribute silently fails — ' + 'the element receives a stringified value it cannot parse.', fix: 'Set the property in JavaScript, not as an HTML attribute. ' + 'Only scalar props (`placeholder`, `loading`, `theme`) work as attributes.\n\n' + '```js\n' + "// ✅ Works — set messages in JavaScript as a property\n" + "const chat = document.querySelector('kai-chat');\n" + "chat.messages = [{ id: '1', role: 'assistant', content: 'Hello!' }];\n" + '```\n\n' + '```html\n' + '\n' + '\n' + '```', }, { // Rule 2 — in-place mutation → no re-render // Source: for-ai-agents.mdx §3; context7.json rule 4 id: 'in-place-mutation', test: (t) => /don'?t\s+update|doesn'?t\s+re.?render|no\s+re.?render|\.push\(|\bpush(?:es|ing)?\s+(?:to|into|onto)\b|mutate|in.place/.test(t), title: 'In-place mutation does not trigger a re-render', cause: 'Mutating an existing message object or array in place (e.g. `chat.messages.push(…)` ' + 'or `chat.messages[i].content = …`) does not trigger a re-render. ' + 'The element only reacts when it detects a new array/object reference.', fix: 'Assign a NEW array (and a new object) on every change — never mutate in place.\n\n' + '```js\n' + '// ✅ Triggers re-render — new array + new object reference\n' + 'chat.messages = [\n' + ' ...chat.messages,\n' + " { id: crypto.randomUUID(), role: 'user', content: userText },\n" + '];\n\n' + '// ✅ During streaming — replace with a new array + new object on every chunk\n' + 'chat.messages = chat.messages.map((m) =>\n' + ' m.id === assistantId ? { ...m, content: accumulated } : m\n' + ');\n\n' + '// ❌ Does NOT trigger re-render\n' + 'chat.messages.push(newMsg);\n' + 'chat.messages[i].content = accumulated;\n' + '```\n\n' + 'The ergonomic path: helpers in `@kitn.ai/ui/state` (`appendMessage`, `updateMessage`, `appendContent`) ' + 'and `createAssistantStream` handle the new-reference contract for you. ' + '`useKaiChat` (React) and `createKaiChat` (Solid) own state entirely so mutation is never an option.\n\n' + '// setMessages((m) => appendMessage(m, msg)) // new array, no footgun', }, { // Rule 3 — listening for events on a parent / wrong element // Source: for-ai-agents.mdx §2; context7.json rule 3 id: 'event-bubbling', test: (t) => /event.*not\s+fir|not\s+fir.*event|listen.*document|document.*listen|listen.*parent|parent.*listen|event.*bubbl/i.test( t, ), title: 'Listening for events on the wrong element (events are non-bubbling)', cause: '`kai-*` events are non-bubbling CustomEvents. Adding a listener to `document`, ' + '`window`, or a parent container will never fire because the event does not bubble up.', fix: 'Listen directly on the `kai-*` element. ' + 'The submit event is `kai-submit` with `event.detail.value`.\n\n' + '```js\n' + "const chat = document.querySelector('kai-chat');\n\n" + "// ✅ Listen directly on the element\n" + "chat.addEventListener('kai-submit', (e) => {\n" + ' console.log(e.detail.value); // the text the user typed\n' + '});\n\n' + "// ❌ Never fires — kai-submit does not bubble\n" + "document.addEventListener('kai-submit', handler);\n" + '```\n\n' + 'Common events: `kai-submit`, `kai-feedback`, `kai-model-change`, `kai-new-chat`, `kai-select`.', }, { // Rule 4 — wrong element prefix kitn- // Source: context7.json rule 1 id: 'wrong-prefix', test: (t) => /\bkitn-/.test(t), title: 'Wrong element prefix `kitn-` (should be `kai-`)', cause: 'The custom element prefix is `kai-` (e.g. ``, ``). ' + '`kitn-` is a legacy name (the register-all bundle is `kai.es.js`) — it is not a ' + 'registered element name. Using `` results in an unknown element that renders nothing.', fix: 'Replace the `kitn-` prefix with `kai-` everywhere.\n\n' + '```html\n' + '\n' + '\n' + '\n\n' + '\n' + '\n' + '```', }, { // Rule 6 — custom elements not registered / renders nothing (React #1 failure) // Source: field-test reports; for-ai-agents.mdx §"Import order matters" id: 'elements-not-registered', test: (t) => { // Core render-nothing / unregistered-element signals if ( /renders?\s+nothing|nothing\s+renders?|not\s+registered|unregistered|not\s+upgraded|unknown\s+element|customElements\.get|undefined\s+element|no\s+shadow\s+root/.test(t) ) return true; // "empty" / "blank" / "doesn't render" / "won't render" only fire when // a render/element/component context is also present to avoid false positives if ( /\b(empty|blank)\b/.test(t) && /render|element|component|kai-|<[a-z]+-|shadow/.test(t) ) return true; if ( /doesn'?t\s+render|won'?t\s+render/.test(t) && /kai-|element|component|custom.?element/.test(t) ) return true; return false; }, title: 'Custom elements not registered — renders nothing / empty box', cause: 'The `@kitn.ai/ui/react` wrappers (and bare `` tags) do NOT register the ' + 'custom elements by themselves. Without the element-registration side-effect import, ' + '`` / `` is an un-upgraded unknown element — an empty box. ' + '`customElements.get(\'kai-chat\') === undefined`.', fix: 'Import the elements bundle for its side effect BEFORE your first render — ' + 'it must run before the component mounts.\n\n' + '```tsx\n' + "import '@kitn.ai/ui/elements' // registers — REQUIRED, must come first\n" + "import { Chat } from '@kitn.ai/ui/react'\n" + "import '@kitn.ai/ui/theme.css'\n" + '```\n\n' + 'In plain HTML: `import \'@kitn.ai/ui/elements\'` in your module script. ' + 'The import is a side effect — keep it even if your linter flags it as "unused".', }, { // Rule 7 — tsc errors inside node_modules/@kitn.ai/ui/src (SolidJS source pulled in) // Source: field-test reports; packaging gap (tracked upstream) id: 'tsc-source-pull', test: (t) => /node_modules\/@kitn\.ai\/ui/.test(t) && /tsc|TS2786|cannot\s+be\s+used\s+as\s+a\s+jsx\s+component|Show\b|Portal\b|Dynamic\b|error\s+TS|type\s+error/.test( t, ), title: 'tsc errors inside node_modules/@kitn.ai/ui/src (SolidJS source compiled under React)', cause: 'The package currently ships TypeScript/TSX source, and a type entry value-re-exports ' + 'from it, so the consumer\'s `tsc` resolves and compiles the library\'s SolidJS internals ' + '(`src/ui/*.tsx`) under the app\'s React JSX config — `Show`/`Portal`/`Dynamic` aren\'t ' + 'React components, causing TS2786 / "cannot be used as a JSX component" errors. ' + '`vite`/esbuild build fine (they strip types); only `tsc` breaks. ' + '`skipLibCheck` does not help (these are `.tsx` source, not `.d.ts`).', fix: 'Redirect the type resolution for that subpath in your tsconfig ' + '(Vite ignores tsconfig `paths`, so runtime is unaffected):\n\n' + '```jsonc\n' + '// tsconfig (app)\n' + '"baseUrl": ".",\n' + '"paths": { "@kitn.ai/ui/elements": ["./src/stubs/kitn-elements.d.ts"] }\n' + '```\n\n' + '```ts\n' + '// src/stubs/kitn-elements.d.ts\n' + 'export {}\n' + '```\n\n' + '(This is a known packaging gap being tracked upstream.)', }, { // Rule 8 — fetch('/api/chat') 404 in a Vite SPA (no server-side routes) // Source: field-test reports; common scaffold confusion id: 'vite-api-404', test: (t) => { // /api/chat 404 if (/\/api\/chat/.test(t) && /\b404\b|not\s+found/i.test(t)) return true; // Vite + missing API route / route handler / POST if (/\bvite\b/.test(t) && /api\s+route|route\s+handler|\bPOST\b.*not\s+work/.test(t)) return true; // Next.js route handler used in a Vite app if (/next\.?js.*route|route.*next\.?js/.test(t) && /\bvite\b/.test(t)) return true; return false; }, title: 'fetch(\'/api/chat\') 404 — Vite SPA has no server-side API routes', cause: 'A plain Vite/CRA React SPA has no server — there are no `/api` routes. ' + 'A scaffolded Next.js route handler (`export async function POST`) does not run there, ' + 'so `fetch(\'/api/chat\')` 404s.', fix: 'Either run the backend somewhere real, or skip it entirely for local dev:\n\n' + '```ts\n' + '// Option A — use Next.js where route handlers are supported\n' + "// app/api/chat/route.ts: export async function POST(req) { ... }\n\n" + '// Option B — add a Vite dev-server middleware/proxy\n' + "// vite.config.ts: server: { proxy: { '/api': 'http://localhost:3001' } }\n\n" + '// Option C — run a separate Express/Hono server\n' + "// framework: 'express' in your harness config\n\n" + '// Option D — zero-config local dev with mock integration (no backend needed)\n' + "// Use `integration: 'mock'` in the scaffold tool\n" + '```', }, { // Rule 9 — reduce bundle size / footprint / "how much does @kitn.ai/ui add" // Source: dist/elements/.js per-element exports; dist/autoloader.js id: 'bundle-footprint', test: (t) => /bundle\s*size|footprint|tree.?shak|how\s+much.*does.*@kitn|reduce.*import|import.*only.*element|per.?element\s+import|autoload|cdn.*no.?build|no.?build.*cdn/i.test( t, ), title: 'Reducing bundle footprint — three load modes', cause: 'The default `import \'@kitn.ai/ui/elements\'` registers every `kai-*` element. ' + 'If your page uses only one or two elements, that pulls in the full ~119 KB gz bundle. ' + 'Two opt-in modes let you load only what you need.', fix: '**Mode 1 — register-all (default, SSR-safe):**\n' + 'Best for multi-element apps or any SSR/meta-framework. ' + 'Load once and every `kai-*` element is available.\n\n' + '```js\n' + "import '@kitn.ai/ui/elements'; // ~119 KB gz — registers everything\n" + '```\n\n' + '**Mode 2 — per-element import (tree-shaking, bundler apps):**\n' + 'Use `import \'@kitn.ai/ui/elements/\'` to register only one element. ' + 'A bundler (Vite, webpack, Rollup) will tree-shake to just its chunks (~73 KB gz for `kai-chat` alone). ' + 'Client-only — do not use in SSR entry points.\n\n' + '```js\n' + "// Registers only (~73 KB gz vs ~119 KB gz register-all)\n" + "import '@kitn.ai/ui/elements/chat';\n\n" + "// Other examples:\n" + "import '@kitn.ai/ui/elements/code-block'; // \n" + "import '@kitn.ai/ui/elements/confirm-card'; // \n" + '```\n\n' + 'The file name is the element\'s source basename from `element-manifest.json` ' + '(e.g. `kai-chat` → `chat`, `kai-confirm` → `confirm-card`).\n\n' + '**Mode 3 — autoloader (no-build / CDN pages only):**\n' + 'Watches the DOM and dynamically imports each `kai-*` element\'s module on demand. ' + 'A page that uses only `` never downloads the other elements. ' + 'It is a CDN / static-file tool — load it from a `\n' + '```\n\n' + 'In a BUNDLED app (Vite/webpack/Next) use Mode 1 or Mode 2 instead — not the autoloader.\n\n' + '**SSR note:** use Mode 1 (register-all) in SSR apps — ' + 'per-element imports and the autoloader are client-only (they call DOM APIs at module eval). ' + 'Modes 1 & 2 are side-effect imports; keep them even if your linter flags them as "unused".', }, { // Rule 5 — SSR / server component / document is not defined // Source: for-ai-agents.mdx (client-only import); context7.json rule 2 (property rule requires DOM) id: 'ssr-server-component', test: (t) => { // Core SSR signals — always trigger if (/\bssr\b|server\s+component|document\s+is\s+not\s+defined|window\s+is\s+not\s+defined|next\.?js.*server|server.*next\.?js/.test(t)) return true; // "hydration" only triggers when a web-component / kai context is also present if (/hydration/.test(t) && /kai|web.?component|custom.?element|<[a-z]+-/.test(t)) return true; return false; }, title: 'SSR / server-side rendering — element requires the browser DOM', cause: '`kai-*` elements are client-side web components. They require `document` and ' + '`customElements` to register and render. Importing them in a server component ' + '(Next.js App Router server component, Nuxt SSR, etc.) throws ' + '"document is not defined" or silently produces no output.', fix: 'Register the element on the client only. ' + 'Use your framework\'s "client-only" / island / dynamic-import pattern.\n\n' + '```js\n' + "// ✅ Plain HTML / vanilla — import in a