diff --git a/.gitignore b/.gitignore index b47c2a2..4e3f576 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ coverage/ # Auto-generated workflows (session artifacts) workflows/auto/ workflows/auto/*.yaml + +# Index caches (large, not needed in repo) +.indexes/ diff --git a/agents/backend-api.md b/agents/backend-api.md index 45f5336..8386cdc 100644 --- a/agents/backend-api.md +++ b/agents/backend-api.md @@ -20,7 +20,7 @@ permission: --- -**Tools**: read, write, edit, bash, skill, lsp, context7, memory, grep, glob +**Tools**: read, write, edit, bash, skill, lsp, context7, memory, grep, glob, brain_diagnostic, brain_sidecar_status, brain_status, brain_search, brain_embed_test, brain_index_project # โš™๏ธ Backend API Developer @@ -63,3 +63,11 @@ You leverage the **`fullstack-dev`** OpenCode skill (found in `skills/fullstack- > [!NOTE] > Coordinate closely with the `Frontend-Engineer` to define API contracts before implementation. Use `lead-architect` for major schema changes. + + +- Check Brain health with brain_sidecar_status or brain_diagnostic before non-trivial debugging, feature work, refactors, architecture analysis, or documentation audits. +- If the index is empty, stale, or missing expected results, run brain_index_project before relying on retrieval. +- Use brain_search for semantic codebase discovery, then read the top matching files directly before making decisions or edits. +- Use brain_embed_test when search quality matters or when choosing better query terms for a complex investigation. +- After broad edits or generated files, confirm Brain can see the new context with brain_status or a targeted brain_search. + diff --git a/agents/backend-laravel.md b/agents/backend-laravel.md index 2d131a5..96c2543 100644 --- a/agents/backend-laravel.md +++ b/agents/backend-laravel.md @@ -21,7 +21,7 @@ permission: --- -**Tools**: read, write, edit, bash, skill, lsp, context7, memory +**Tools**: read, write, edit, bash, skill, lsp, context7, memory, brain_diagnostic, brain_sidecar_status, brain_status, brain_search, brain_embed_test, brain_index_project # ๐ŸŽจ Backend Laravel Agent @@ -210,3 +210,11 @@ class BlogPostForm extends Component 2. **Task Decomposition**: Break into logical units (Migration โ†’ Model โ†’ Controller โ†’ Livewire) 3. **Subagent Delegation**: Spawn core-builder for file modifications 4. **Validation**: Use LSP to ensure no new diagnostics + + +- Check Brain health with brain_sidecar_status or brain_diagnostic before non-trivial debugging, feature work, refactors, architecture analysis, or documentation audits. +- If the index is empty, stale, or missing expected results, run brain_index_project before relying on retrieval. +- Use brain_search for semantic codebase discovery, then read the top matching files directly before making decisions or edits. +- Use brain_embed_test when search quality matters or when choosing better query terms for a complex investigation. +- After broad edits or generated files, confirm Brain can see the new context with brain_status or a targeted brain_search. + diff --git a/agents/backend-tauri.md b/agents/backend-tauri.md index 536e61c..c4de2f4 100644 --- a/agents/backend-tauri.md +++ b/agents/backend-tauri.md @@ -20,7 +20,7 @@ permission: --- -**Tools**: read, write, edit, bash, skill, lsp, context7, memory +**Tools**: read, write, edit, bash, skill, lsp, context7, memory, brain_diagnostic, brain_sidecar_status, brain_status, brain_search, brain_embed_test, brain_index_project # backend-tauri @@ -72,3 +72,11 @@ For each task: 4. Set up proper permissions in `capabilities/` 5. Test IPC communication end-to-end 6. Ensure cross-platform compatibility (Windows, macOS, Linux) + + +- Check Brain health with brain_sidecar_status or brain_diagnostic before non-trivial debugging, feature work, refactors, architecture analysis, or documentation audits. +- If the index is empty, stale, or missing expected results, run brain_index_project before relying on retrieval. +- Use brain_search for semantic codebase discovery, then read the top matching files directly before making decisions or edits. +- Use brain_embed_test when search quality matters or when choosing better query terms for a complex investigation. +- After broad edits or generated files, confirm Brain can see the new context with brain_status or a targeted brain_search. + diff --git a/agents/core-factory.md b/agents/core-factory.md index 66109b9..a0d8832 100644 --- a/agents/core-factory.md +++ b/agents/core-factory.md @@ -25,7 +25,7 @@ permission: --- -**Tools**: read, write, edit, skill, grep, glob, todowrite, memory, context7, sequential-thinking, lsp +**Tools**: read, write, edit, skill, grep, glob, todowrite, memory, context7, sequential-thinking, lsp, brain_diagnostic, brain_sidecar_status, brain_status, brain_search, brain_embed_test, brain_index_project # Core Factory Agent @@ -77,6 +77,14 @@ permission: + +- Check Brain health with brain_sidecar_status or brain_diagnostic before non-trivial debugging, feature work, refactors, architecture analysis, or documentation audits. +- If the index is empty, stale, or missing expected results, run brain_index_project before relying on retrieval. +- Use brain_search for semantic codebase discovery, then read the top matching files directly before making decisions or edits. +- Use brain_embed_test when search quality matters or when choosing better query terms for a complex investigation. +- After broad edits or generated files, confirm Brain can see the new context with brain_status or a targeted brain_search. + + - Always think in blocks before actions. - WORKFLOW: Read-Edit-Validate. Use grep/glob for discovery, edit tool with oldString/newString. diff --git a/agents/devops-engineer.md b/agents/devops-engineer.md index 24be524..7591c5a 100644 --- a/agents/devops-engineer.md +++ b/agents/devops-engineer.md @@ -22,7 +22,7 @@ permission: --- -**Tools**: read, write, edit, bash, skill, context7, memory +**Tools**: read, write, edit, bash, skill, context7, memory, brain_diagnostic, brain_sidecar_status, brain_status, brain_search, brain_embed_test, brain_index_project # DevOps Engineer Agent @@ -74,6 +74,14 @@ permission: + +- Check Brain health with brain_sidecar_status or brain_diagnostic before non-trivial debugging, feature work, refactors, architecture analysis, or documentation audits. +- If the index is empty, stale, or missing expected results, run brain_index_project before relying on retrieval. +- Use brain_search for semantic codebase discovery, then read the top matching files directly before making decisions or edits. +- Use brain_embed_test when search quality matters or when choosing better query terms for a complex investigation. +- After broad edits or generated files, confirm Brain can see the new context with brain_status or a targeted brain_search. + + - DEVOPS: Terminal execution, MCP management, system tasks. - Handle db:init, clean, process:check, backups. diff --git a/agents/docs-curator.md b/agents/docs-curator.md index 10763be..09cff9c 100644 --- a/agents/docs-curator.md +++ b/agents/docs-curator.md @@ -20,7 +20,7 @@ permission: --- -**Tools**: read, write, edit, bash, skill, lsp, codesearch, websearch, webfetch, todowrite, memory, context7, sequential-thinking +**Tools**: read, write, edit, bash, skill, lsp, codesearch, websearch, webfetch, todowrite, memory, context7, sequential-thinking, brain_diagnostic, brain_sidecar_status, brain_status, brain_search, brain_embed_test, brain_index_project # Docs Curator Agent @@ -72,6 +72,14 @@ permission: + +- Check Brain health with brain_sidecar_status or brain_diagnostic before non-trivial debugging, feature work, refactors, architecture analysis, or documentation audits. +- If the index is empty, stale, or missing expected results, run brain_index_project before relying on retrieval. +- Use brain_search for semantic codebase discovery, then read the top matching files directly before making decisions or edits. +- Use brain_embed_test when search quality matters or when choosing better query terms for a complex investigation. +- After broad edits or generated files, confirm Brain can see the new context with brain_status or a targeted brain_search. + + - KNOWLEDGE: Document, self-improve, and evolve the system. - Update rules/*.md, agents instructions, and project docs. diff --git a/agents/frontend-ui-ux.md b/agents/frontend-ui-ux.md index b257cd2..127e167 100644 --- a/agents/frontend-ui-ux.md +++ b/agents/frontend-ui-ux.md @@ -19,7 +19,7 @@ permission: --- -**Tools**: read, write, edit, bash, skill, lsp, codesearch, task, mcp, memory, context7, sequential-thinking +**Tools**: read, write, edit, bash, skill, lsp, codesearch, task, mcp, memory, context7, sequential-thinking, brain_diagnostic, brain_sidecar_status, brain_status, brain_search, brain_embed_test, brain_index_project # Frontend UI/UX Specialist @@ -101,6 +101,14 @@ Design system reference (e.g., shadcn/ui, Tailwind UI, custom) + +- Check Brain health with brain_sidecar_status or brain_diagnostic before non-trivial debugging, feature work, refactors, architecture analysis, or documentation audits. +- If the index is empty, stale, or missing expected results, run brain_index_project before relying on retrieval. +- Use brain_search for semantic codebase discovery, then read the top matching files directly before making decisions or edits. +- Use brain_embed_test when search quality matters or when choosing better query terms for a complex investigation. +- After broad edits or generated files, confirm Brain can see the new context with brain_status or a targeted brain_search. + + Use only valid tools: read, write, edit, bash, websearch, codesearch, context7_resolve-library-id, context7_query-docs, skill_use Reference invalid tools like "ui", "ux", "lsp", or "file" diff --git a/agents/lead-architect.md b/agents/lead-architect.md index 5baed2c..ef5d6b4 100644 --- a/agents/lead-architect.md +++ b/agents/lead-architect.md @@ -19,7 +19,7 @@ permission: --- -**Tools**: read, write, edit, bash, skill, lsp, codesearch, task, mcp, memory, context7, sequential-thinking +**Tools**: read, write, edit, bash, skill, lsp, codesearch, task, mcp, memory, context7, sequential-thinking, brain_diagnostic, brain_sidecar_status, brain_status, brain_search, brain_embed_test, brain_index_project # ๐Ÿ›๏ธ Lead Architect @@ -48,3 +48,11 @@ You are the **Lead Architect**. You are responsible for the overall technical vi > [!TIP] > Prioritize simplicity and maintainability. Avoid "over-engineering" but ensure the foundation is solid. + + +- Check Brain health with brain_sidecar_status or brain_diagnostic before non-trivial debugging, feature work, refactors, architecture analysis, or documentation audits. +- If the index is empty, stale, or missing expected results, run brain_index_project before relying on retrieval. +- Use brain_search for semantic codebase discovery, then read the top matching files directly before making decisions or edits. +- Use brain_embed_test when search quality matters or when choosing better query terms for a complex investigation. +- After broad edits or generated files, confirm Brain can see the new context with brain_status or a targeted brain_search. + diff --git a/agents/lead-strategist.md b/agents/lead-strategist.md index 8332dcf..d7c76b8 100644 --- a/agents/lead-strategist.md +++ b/agents/lead-strategist.md @@ -25,7 +25,7 @@ permission: --- -**Tools**: skill, bash, read, lsp, codesearch, todowrite, task, memory, context7, sequential-thinking +**Tools**: skill, bash, read, lsp, codesearch, todowrite, task, memory, context7, sequential-thinking, brain_diagnostic, brain_sidecar_status, brain_status, brain_search, brain_embed_test, brain_index_project # Lead Strategist Agent @@ -85,6 +85,14 @@ permission: + +- Check Brain health with brain_sidecar_status or brain_diagnostic before non-trivial debugging, feature work, refactors, architecture analysis, or documentation audits. +- If the index is empty, stale, or missing expected results, run brain_index_project before relying on retrieval. +- Use brain_search for semantic codebase discovery, then read the top matching files directly before making decisions or edits. +- Use brain_embed_test when search quality matters or when choosing better query terms for a complex investigation. +- After broad edits or generated files, confirm Brain can see the new context with brain_status or a targeted brain_search. + + - LEAD: Orchestration, architecture, and roadmap. Use for strategy. - You have access to skills, LSP, codesearch, and multi-agent task delegation. diff --git a/agents/qa-guardian.md b/agents/qa-guardian.md index 297a125..536c545 100644 --- a/agents/qa-guardian.md +++ b/agents/qa-guardian.md @@ -19,7 +19,7 @@ permission: --- -**Tools**: read, bash, skill, lsp, context7, memory, grep, glob +**Tools**: read, bash, skill, lsp, context7, memory, grep, glob, brain_diagnostic, brain_sidecar_status, brain_status, brain_search, brain_embed_test, brain_index_project # QA Guardian Agent @@ -79,6 +79,14 @@ permission: + +- Check Brain health with brain_sidecar_status or brain_diagnostic before non-trivial debugging, feature work, refactors, architecture analysis, or documentation audits. +- If the index is empty, stale, or missing expected results, run brain_index_project before relying on retrieval. +- Use brain_search for semantic codebase discovery, then read the top matching files directly before making decisions or edits. +- Use brain_embed_test when search quality matters or when choosing better query terms for a complex investigation. +- After broad edits or generated files, confirm Brain can see the new context with brain_status or a targeted brain_search. + + - QUALITY: Code review, testing, security scanning, and debugging. - Use qa-tester skill, security-scan skill, and debug utilities. diff --git a/brain-plugin/brain.ts b/brain-plugin/brain.ts index b52562b..ec92aea 100644 --- a/brain-plugin/brain.ts +++ b/brain-plugin/brain.ts @@ -1,17 +1,182 @@ import { tool, type Plugin } from "@opencode-ai/plugin"; import { DecisionTree } from "./tree/engine"; import { LMStudioProvider } from "./provider/lmstudio"; -import { indexer } from "./retrieval/indexer"; -import { searcher } from "./retrieval/searcher"; +import { searchContext } from "./retrieval/searcher"; +import { indexProject, IndexProgress } from "./retrieval/indexer"; import { contextInjector } from "./context/injector"; import { sessionMemory } from "./state/session"; import type { SignalBundle } from "./tree/engine"; const LM_STUDIO_URL = "http://192.168.1.12:1234/v1"; +const BRAIN_EMBED_URL = "http://127.0.0.1:7878"; + +interface SidecarState { + process: any | null; + healthy: boolean; + startedAt: number | null; + lastHealthCheck: number; +} + +const sidecar: SidecarState = { + process: null, + healthy: false, + startedAt: null, + lastHealthCheck: 0, +}; + +function toWslPath(windowsPath: string): string { + return windowsPath + .replace(/^([A-Z]):\\/i, (_: string, d: string) => `/mnt/${d.toLowerCase()}/`) + .replace(/\\/g, "/"); +} + +async function checkSidecarHealth(): Promise { + const now = Date.now(); + if (now - sidecar.lastHealthCheck < 5000) return sidecar.healthy; + sidecar.lastHealthCheck = now; + try { + const res = await fetch(`${BRAIN_EMBED_URL}/health`, { signal: AbortSignal.timeout(2000) }); + sidecar.healthy = res.ok; + if (res.ok) { + const data = await res.json(); + console.log( + `[Brain] Sidecar healthy | uptime: ${data.uptime_seconds}s | models: ${data.loaded_models?.length || 0}` + ); + } + return sidecar.healthy; + } catch { + sidecar.healthy = false; + return false; + } +} + +async function startSidecar(): Promise { + if (await checkSidecarHealth()) { + console.log("[Brain] Rust sidecar already running"); + return true; + } + console.log("[Brain] Starting Rust sidecar..."); + try { + const { spawn } = await import("child_process"); + const isWindows = typeof process !== "undefined" && process.platform === "win32"; + const nativeBinary = isWindows + ? `${directory}\\rust-brain-sidecar\\target\\release\\brain-embed.exe` + : `${directory}/rust-brain-sidecar/target/release/brain-embed`; + const nativeArgs: string[] = []; + const wslArgs = [ + "-d", + "Ubuntu", + "--", + "bash", + "-c", + `RUST_LOG=info ${toWslPath(directory)}/rust-brain-sidecar/target/release/brain-embed`, + ]; + const proc = isWindows + ? spawn(nativeBinary, nativeArgs, { + detached: true, + stdio: "pipe", + cwd: `${directory}\\rust-brain-sidecar`, + }) + : spawn(nativeBinary, nativeArgs, { + detached: true, + stdio: "pipe", + cwd: `${directory}/rust-brain-sidecar`, + }); + proc.stdout?.on("data", (d: Buffer) => { + const msg = d.toString().trim(); + if (msg) console.log(`[brain-embed] ${msg}`); + }); + proc.stderr?.on("data", (d: Buffer) => { + const msg = d.toString().trim(); + if (msg) console.log(`[brain-embed:err] ${msg}`); + }); + proc.on("exit", (code: number) => { + console.log(`[Brain] Sidecar exited (code ${code})`); + sidecar.process = null; + sidecar.healthy = false; + }); + sidecar.process = proc; + proc.unref(); + for (let i = 0; i < 30; i++) { + await new Promise((r) => setTimeout(r, 1000)); + if (await checkSidecarHealth()) { + console.log(`[Brain] Sidecar ready after ${i + 1}s`); + return true; + } + } + if (isWindows) { + console.warn("[Brain] Native sidecar did not become healthy; trying WSL fallback..."); + try { + const wslProc = spawn("wsl.exe", wslArgs, { detached: true, stdio: "pipe" }); + sidecar.process = wslProc; + wslProc.unref(); + for (let i = 0; i < 30; i++) { + await new Promise((r) => setTimeout(r, 1000)); + if (await checkSidecarHealth()) { + console.log(`[Brain] WSL sidecar ready after ${i + 1}s`); + return true; + } + } + } catch (fallbackErr) { + console.error("[Brain] WSL sidecar fallback failed:", fallbackErr); + } + } + console.error("[Brain] Sidecar failed to start within 30s"); + return false; + } catch (err) { + console.error("[Brain] Sidecar start error:", err); + return false; + } +} + +function stopSidecar(): void { + if (sidecar.process) { + try { + sidecar.process.kill("SIGTERM"); + } catch {} + sidecar.process = null; + } + sidecar.healthy = false; +} + +async function syncConfigToSidecar(provider: LMStudioProvider): Promise { + try { + const res = await fetch(`${BRAIN_EMBED_URL}/config`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + embed_model: provider.defaultEmbedModel, + chat_model: provider.defaultChatModel, + draft_model: provider.defaultDraftModel, + speculative_enabled: true, + context_length: 4096, + temperature: 0.7, + max_tokens: 4096, + }), + }); + if (res.ok) console.log("[Brain] Config synced to sidecar"); + } catch (err) { + console.error("[Brain] Config sync failed:", err); + } +} + +async function sidecarPrewarm(modelType: string): Promise { + try { + await fetch(`${BRAIN_EMBED_URL}/prewarm`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ model_type: modelType }), + }); + console.log(`[Brain] Prewarmed ${modelType} model`); + } catch {} +} const BrainPlugin: Plugin = async ({ directory }) => { let tree: DecisionTree; let provider: LMStudioProvider; + let indexingInProgress = false; + const dirtyFiles = new Set(); + let reindexTimer: any = null; try { tree = await DecisionTree.load(); @@ -24,10 +189,41 @@ const BrainPlugin: Plugin = async ({ directory }) => { } return { + "server.start": async () => { + console.log("[Brain] Plugin loaded - Cognitive layer for OpenCode"); + console.log(`[Brain] LM Studio: ${LM_STUDIO_URL}`); + console.log(`[Brain] Decision tree: ${tree.getStats().totalNodes} nodes`); + + const started = await startSidecar(); + if (started) { + await syncConfigToSidecar(provider); + console.log(`[Brain] Auto-indexing project: ${directory}`); + indexingInProgress = true; + try { + const result = await indexProject(directory); + console.log( + `[Brain] Indexed ${result.chunks} chunks from ${result.files_indexed} files (${result.duration_ms}ms)` + ); + } catch (err) { + console.error("[Brain] Auto-index failed:", err); + } + indexingInProgress = false; + } else { + console.warn("[Brain] Sidecar not available - RAG disabled"); + } + }, + + "session.archived": async () => { + stopSidecar(); + console.log("[Brain] Sidecar stopped"); + }, + "message.updated": async (input: any, output: any) => { const msg = input.message; if (msg.role !== "user") return; + if (!sidecar.healthy) return; + const signals: SignalBundle = { message: msg.content, recentFiles: sessionMemory.getMemory().recentFiles, @@ -48,8 +244,13 @@ const BrainPlugin: Plugin = async ({ directory }) => { return; } + // Prewarm embed model for search-heavy intents + if (["debug", "refactor", "feature", "learn"].includes(scenario.intent)) { + sidecarPrewarm("embed"); + } + try { - const context = await searcher.search( + const context = await searchContext( msg.content, { strategy: strategy.name, @@ -57,19 +258,15 @@ const BrainPlugin: Plugin = async ({ directory }) => { maxChunks: strategy.maxChunks, rerank: strategy.rerank, }, - directory, - signals.lspSymbols + directory ); if (context.chunks.length > 0) { sessionMemory.markContextUsed(context.chunks); - const augmentedMessage = contextInjector.inject(msg.content, context); output.message.content = augmentedMessage; - - console.log(`[Brain] Injected ${context.chunks.length} chunks for intent: ${scenario.intent}`); + console.log(`[Brain] +${context.chunks.length} chunks | intent: ${scenario.intent}`); } - sessionMemory.recordDecision({ timestamp: Date.now(), intent: scenario.intent, @@ -86,7 +283,6 @@ const BrainPlugin: Plugin = async ({ directory }) => { if (input.tool === "edit" || input.tool === "write") { sessionMemory.markSuccess(); } - if (input.tool === "bash" && output.result?.includes("error")) { sessionMemory.markFailure("bash_error"); } @@ -95,7 +291,6 @@ const BrainPlugin: Plugin = async ({ directory }) => { "lsp.client.diagnostics": async (input: any) => { const diagnostics = input.diagnostics || []; sessionMemory.setDiagnostics(diagnostics); - if (diagnostics.some((d: any) => d.severity === "error")) { tree.prewarmIntent("debug"); } @@ -103,6 +298,23 @@ const BrainPlugin: Plugin = async ({ directory }) => { "file.watcher.updated": async (input: any) => { sessionMemory.markFileDirty(input.path); + dirtyFiles.add(input.path); + if (!indexingInProgress && sidecar.healthy && dirtyFiles.size >= 5) { + clearTimeout(reindexTimer); + reindexTimer = setTimeout(async () => { + if (dirtyFiles.size === 0) return; + indexingInProgress = true; + const files = Array.from(dirtyFiles); + dirtyFiles.clear(); + try { + const result = await indexProject(directory, { force: true }); + console.log(`[Brain] Re-indexed ${result.chunks} chunks (${files.length} dirty files)`); + } catch (err) { + console.error("[Brain] Re-index failed:", err); + } + indexingInProgress = false; + }, 5000); + } }, "session.compacting": async (input: any, output: any) => { @@ -113,6 +325,16 @@ const BrainPlugin: Plugin = async ({ directory }) => { await tree.save(); }, + "chat.params": async (params: any) => { + if (!sidecar.healthy) return params; + const cfg = await (await fetch(`${BRAIN_EMBED_URL}/config`)).json().catch(() => null); + if (cfg?.speculative_enabled && cfg?.draft_model) { + params.draft_model = cfg.draft_model; + console.log(`[Brain] Speculative: ${cfg.chat_model} + ${cfg.draft_model}`); + } + return params; + }, + tool: { brain_index_project: tool({ description: "Index the current project for semantic code search", @@ -121,9 +343,17 @@ const BrainPlugin: Plugin = async ({ directory }) => { force: tool.schema.boolean().optional().describe("Force re-index"), }, async execute(args: any) { + if (!sidecar.healthy) return "Sidecar not running. Use brain_sidecar_status to check."; const root = args.path ?? directory; - const result = await indexer.run(root, { force: args.force }); - return `Brain indexing ${result.status}: ${result.chunks} chunks indexed`; + indexingInProgress = true; + try { + const result = await indexProject(root, { force: args.force }); + return `Indexed ${result.chunks} chunks from ${result.files_indexed} files (${result.duration_ms}ms)`; + } catch (err: any) { + return `Index failed: ${err.message}`; + } finally { + indexingInProgress = false; + } }, }), @@ -134,7 +364,8 @@ const BrainPlugin: Plugin = async ({ directory }) => { top_k: tool.schema.number().optional().describe("Number of results (default: 5)"), }, async execute(args: any) { - const context = await searcher.search( + if (!sidecar.healthy) return "Sidecar not running."; + const context = await searchContext( args.query, { strategy: "manual", depth: "broad", maxChunks: args.top_k ?? 5, rerank: true }, directory @@ -144,28 +375,137 @@ const BrainPlugin: Plugin = async ({ directory }) => { }), brain_status: tool({ - description: "Get the brain's current decision tree state", + description: "Get the brain's current state: sidecar health, indexed projects, cache stats", args: {}, async execute() { const stats = tree.getStats(); const memory = sessionMemory.getMemory(); + let sidecarInfo = "Sidecar: not running"; + let metricsInfo = ""; + if (sidecar.healthy) { + try { + const h = await (await fetch(`${BRAIN_EMBED_URL}/health`)).json(); + const m = await (await fetch(`${BRAIN_EMBED_URL}/metrics`)).json(); + sidecarInfo = [ + `Sidecar: โœ… running (uptime ${h.uptime_seconds}s)`, + `GPU: ${h.gpu ? `${h.gpu.usage_percent.toFixed(1)}% used (${h.gpu.free_gb.toFixed(1)}G free / ${h.gpu.total_gb.toFixed(1)}G total)` : "N/A"}`, + `Models: embed=${h.config.embed_model} | chat=${h.config.chat_model} | draft=${h.config.draft_model || "none"}`, + ].join("\n"); + metricsInfo = [ + `Cache: ${m.cache_hit_rate > 0 ? (m.cache_hit_rate * 100).toFixed(0) : 0}% hit rate (${m.cache_hits} hits, ${m.cache_misses} misses)`, + `Searches: ${m.searches_total} | Index chunks: ${m.index_chunks} | Prewarms: ${m.orchestrator_prewarm_count}`, + ].join("\n"); + } catch {} + } + return [ + `## Brain Status`, + ``, + sidecarInfo, + ``, + metricsInfo, + ``, + `### Decision Tree`, + `- Total nodes: ${stats.totalNodes}`, + `- Pending mutations: ${stats.pendingMutations}`, + ``, + `### Intents`, + ...Object.entries(stats.intents).map( + ([intent, data]) => + `- ${intent}: weight=${data.weight.toFixed(2)}, visits=${data.visits}` + ), + ``, + `### Session Memory`, + `- Decisions: ${memory.decisions.length}`, + `- Successes: ${memory.successCount}`, + `- Failures: ${memory.failures.length}`, + `- Recent files: ${memory.recentFiles.length}`, + `- Context used: ${memory.contextUsed.length} chunks`, + ].join("\n"); + }, + }), - return `## Brain Status + brain_metrics: tool({ + description: "Get Prometheus-style metrics from the Rust sidecar", + args: {}, + async execute() { + if (!sidecar.healthy) return "Sidecar not running."; + try { + const m = await (await fetch(`${BRAIN_EMBED_URL}/metrics`)).json(); + return [ + `## Brain Metrics`, + ``, + `| Metric | Value |`, + `|--------|-------|`, + `| Decisions total | ${m.decisions_total} |`, + `| Searches total | ${m.searches_total} |`, + `| Index chunks | ${m.index_chunks} |`, + `| Cache hits | ${m.cache_hits} |`, + `| Cache misses | ${m.cache_misses} |`, + `| Cache hit rate | ${(m.cache_hit_rate * 100).toFixed(1)}% |`, + `| Prewarm count | ${m.orchestrator_prewarm_count} |`, + ].join("\n"); + } catch { + return "Failed to fetch metrics."; + } + }, + }), -### Decision Tree -- Total nodes: ${stats.totalNodes} -- Pending mutations: ${stats.pendingMutations} + brain_embed_test: tool({ + description: "Test embedding search - show what context would be retrieved for a query", + args: { + query: tool.schema.string().describe("Test query"), + top_k: tool.schema.number().optional().describe("Number of results (default: 5)"), + }, + async execute(args: any) { + if (!sidecar.healthy) return "Sidecar not running."; + const context = await searchContext( + args.query, + { strategy: "test", depth: "broad", maxChunks: args.top_k ?? 5, rerank: true }, + directory + ); + if (context.chunks.length === 0) + return "No relevant context found. Try indexing first with brain_index_project."; + return contextInjector.formatResults(context); + }, + }), -### Intents -${Object.entries(stats.intents) - .map(([intent, data]) => `- ${intent}: weight=${data.weight.toFixed(2)}, visits=${data.visits}`) - .join("\n")} + brain_sidecar_status: tool({ + description: "Check if the Rust sidecar is running and healthy", + args: {}, + async execute() { + const healthy = await checkSidecarHealth(); + if (!healthy) + return "โŒ Sidecar not running. It will start automatically on next session."; + try { + const h = await (await fetch(`${BRAIN_EMBED_URL}/health`)).json(); + return [ + `โœ… Sidecar running`, + `- Version: ${h.version}`, + `- Uptime: ${h.uptime_seconds}s`, + `- GPU: ${h.gpu ? `${h.gpu.usage_percent.toFixed(1)}% used (${h.gpu.free_gb.toFixed(1)}G free)` : "N/A"}`, + `- Embed model: ${h.config.embed_model}`, + `- Chat model: ${h.config.chat_model}`, + `- Draft model: ${h.config.draft_model || "none"}`, + `- Speculative: ${h.config.speculative ? "enabled" : "disabled"}`, + ].join("\n"); + } catch { + return "โœ… Sidecar responding but failed to parse health data."; + } + }, + }), -### Session Memory -- Decisions: ${memory.decisions.length} -- Successes: ${memory.successCount} -- Failures: ${memory.failures.length} -- Recent files: ${memory.recentFiles.length}`; + brain_sidecar_restart: tool({ + description: "Restart the Rust sidecar", + args: {}, + async execute() { + stopSidecar(); + await new Promise((r) => setTimeout(r, 1000)); + const started = await startSidecar(); + if (started) { + await syncConfigToSidecar(provider); + return "Sidecar restarted successfully."; + } + return "Failed to restart sidecar."; }, }), @@ -176,15 +516,134 @@ ${Object.entries(stats.intents) sessionMemory.reset(); tree = new DecisionTree(); await tree.save(); - return "Brain reset successfully"; + return "Brain reset successfully."; }, }), - }, - "server.start": async () => { - console.log("[Brain] Plugin loaded - Cognitive layer for OpenCode"); - console.log(`[Brain] LM Studio: ${LM_STUDIO_URL}`); - console.log(`[Brain] Decision tree: ${tree.getStats().totalNodes} nodes`); + brain_model_load: tool({ + description: "Load a model into LM Studio (chat, embed, or draft)", + args: { + model_type: tool.schema.string().describe("Model type: chat, embed, draft"), + }, + async execute(args: any) { + try { + const cfg = await (await fetch(`${BRAIN_EMBED_URL}/config`)).json(); + const model = + args.model_type === "embed" + ? cfg.embed_model + : args.model_type === "draft" + ? cfg.draft_model + : cfg.chat_model; + if (!model) return `No ${args.model_type} model configured.`; + const res = await fetch(`${BRAIN_EMBED_URL}/prewarm`, { method: "POST" }); + return `Loading ${args.model_type} model: ${model}`; + } catch { + return "Failed to load model."; + } + }, + }), + + brain_model_unload: tool({ + description: "Unload models from LM Studio GPU memory to free VRAM", + args: { + all: tool.schema.boolean().optional().describe("Unload all non-essential models"), + }, + async execute(args: any) { + try { + const before = await (await fetch(`${BRAIN_EMBED_URL}/gpu`)).json(); + const res = await fetch(`${BRAIN_EMBED_URL}/cache/invalidate`, { method: "POST" }); + const after = await (await fetch(`${BRAIN_EMBED_URL}/gpu`)).json(); + return `Models unloaded. GPU before: ${before.used_gb?.toFixed(1) || "?"}G used โ†’ after: ${after.used_gb?.toFixed(1) || "?"}G used`; + } catch { + return "Failed to unload models."; + } + }, + }), + + brain_diagnostic: tool({ + description: + "Run full pipeline diagnostic: sidecar health, index status, search, config sync", + args: {}, + async execute() { + const results: string[] = ["## Brain Diagnostic\n"]; + + // 1. Sidecar health + try { + const h = await (await fetch(`${BRAIN_EMBED_URL}/health`)).json(); + results.push(`โœ… Sidecar: v${h.version} (uptime ${h.uptime_seconds}s)`); + results.push(` Chat: ${h.config.chat_model}`); + results.push(` Embed: ${h.config.embed_model}`); + results.push(` Draft: ${h.config.draft_model || "none"}`); + results.push(` Speculative: ${h.config.speculative ? "enabled" : "disabled"}`); + if (h.gpu) + results.push( + ` GPU: ${h.gpu.usage_percent.toFixed(1)}% used (${h.gpu.free_gb.toFixed(1)}G free)` + ); + else results.push(` GPU: N/A (WSL may not expose nvidia-smi)`); + results.push(` Loaded models: ${h.loaded_models?.join(", ") || "none"}`); + } catch { + results.push("โŒ Sidecar: NOT RUNNING โ€” RAG disabled"); + return results.join("\n"); + } + + // 2. Metrics / cache + try { + const m = await (await fetch(`${BRAIN_EMBED_URL}/metrics`)).json(); + results.push( + `\n๐Ÿ“Š Cache: ${m.cache_hits} hits / ${m.cache_misses} misses (${(m.cache_hit_rate * 100).toFixed(0)}%)` + ); + results.push(` Indexed chunks: ${m.index_chunks}`); + results.push(` Searches performed: ${m.searches_total}`); + } catch {} + + // 3. Config sync + try { + const cfg = await (await fetch(`${BRAIN_EMBED_URL}/config`)).json(); + results.push( + `\nโš™๏ธ Config: context=${cfg.context_length} | concurrency=${cfg.max_concurrency} | batch=${cfg.batch_size}` + ); + } catch {} + + // 4. Quick search test (uses directory-derived project_id) + try { + const ctx = await searchContext( + "diagnostic test query", + { strategy: "diagnostic", depth: "shallow", maxChunks: 1, rerank: false }, + directory + ); + if (ctx.chunks.length > 0) { + results.push(`\n๐Ÿ” Search: โœ… working (${ctx.chunks.length} results)`); + } else { + results.push(`\n๐Ÿ” Search: โš ๏ธ 0 results โ€” try brain_index_project first`); + } + } catch (e: any) { + results.push(`\n๐Ÿ” Search: โŒ ${e.message}`); + } + + results.push(`\n๐Ÿ“ Project: ${directory}`); + return results.join("\n"); + }, + }), + + brain_speculative_status: tool({ + description: "Show speculative decoding status", + args: {}, + async execute() { + if (!sidecar.healthy) return "Sidecar not running."; + try { + const cfg = await (await fetch(`${BRAIN_EMBED_URL}/config`)).json(); + return [ + `## Speculative Decoding`, + `- Enabled: ${cfg.speculative_enabled}`, + `- Draft model: ${cfg.draft_model || "none"}`, + `- Chat model: ${cfg.chat_model}`, + `- Context length: ${cfg.context_length}`, + ].join("\n"); + } catch { + return "Failed to fetch config."; + } + }, + }), }, }; }; diff --git a/brain-plugin/package.json b/brain-plugin/package.json index 3e28287..04e1eb0 100644 --- a/brain-plugin/package.json +++ b/brain-plugin/package.json @@ -5,7 +5,6 @@ "type": "module", "main": "brain.ts", "dependencies": { - "@lancedb/lancedb": "^0.15.0", "glob": "^11.0.0" }, "peerDependencies": { diff --git a/brain-plugin/provider/lmstudio.ts b/brain-plugin/provider/lmstudio.ts index b54a824..c2c43b8 100644 --- a/brain-plugin/provider/lmstudio.ts +++ b/brain-plugin/provider/lmstudio.ts @@ -1,8 +1,8 @@ export const LM_STUDIO_API = "http://192.168.1.12:1234/api/v1"; export const LM_STUDIO_V1 = "http://192.168.1.12:1234/v1"; -export const DEFAULT_EMBED_MODEL = "text-embedding-qwen3-embedding-0.6b"; +export const DEFAULT_EMBED_MODEL = "text-embedding-nomic-embed-text-v1.5"; export const DEFAULT_CHAT_MODEL = "qwen3.5-4b-claude-4.6-opus-reasoning-distilled-v2"; -export const DEFAULT_DRAFT_MODEL = "qwen3.5-0.8b-claude-4.6-opus-reasoning-distilled-v2"; +export const DEFAULT_DRAFT_MODEL = "qwen3.5-0.8b-claude-4.6-opus-reasoning-distilled"; export interface LoadOptions { contextLength?: number; @@ -45,6 +45,9 @@ export class LMStudioProvider implements CustomProvider { name = "lmstudio"; baseURL = LM_STUDIO_V1; apiURL = LM_STUDIO_API; + defaultEmbedModel = DEFAULT_EMBED_MODEL; + defaultChatModel = DEFAULT_CHAT_MODEL; + defaultDraftModel = DEFAULT_DRAFT_MODEL; async load(modelId: string, opts?: LoadOptions): Promise { const isEmbedding = modelId.includes("embedding") || modelId.includes("embed"); diff --git a/brain-plugin/retrieval/indexer.ts b/brain-plugin/retrieval/indexer.ts index 1050212..ae10157 100644 --- a/brain-plugin/retrieval/indexer.ts +++ b/brain-plugin/retrieval/indexer.ts @@ -1,139 +1,54 @@ -import { defaultProvider } from "../provider/lmstudio"; -import { lancadb } from "./lancadb"; +const BRAIN_EMBED_URL = "http://127.0.0.1:7878"; -export interface Chunk { - text: string; - path: string; - startLine: number; - endLine: number; - mtime: number; -} - -export interface IndexResult { - status: "indexed" | "fresh" | "error"; +export interface IndexProgress { + status: "indexed" | "error"; + files_indexed: number; chunks: number; + duration_ms: number; message?: string; } -const CHUNK_SIZE = 40; -const CHUNK_OVERLAP = 10; -const IGNORE_PATTERNS = [ - "node_modules/**", - "target/**", - "vendor/**", - ".git/**", - "dist/**", - "build/**", - "*.lock", - ".next/**", -]; - -export class Indexer { - async run(projectRoot: string, opts: { force?: boolean } = {}): Promise { - const fs = await import("fs"); - const path = await import("path"); - const dbPath = `${projectRoot}/.opencode/brain.lance`; - - await lancadb.initialize(dbPath); - - if (!opts.force) { - const stats = await lancadb.getStats(); - if (stats.totalChunks > 0) { - return { status: "fresh", chunks: stats.totalChunks }; - } - } - - try { - const files = await this.discoverFiles(projectRoot); - console.log(`[Brain Indexer] Discovered ${files.length} files`); - - const allChunks: Chunk[] = []; - - for (const file of files) { - const chunks = await this.chunkFile(file); - allChunks.push(...chunks); - } - - console.log(`[Brain Indexer] Generated ${allChunks.length} chunks`); - - if (allChunks.length > 0) { - await lancadb.addChunks(allChunks); - } +function toWslPath(windowsPath: string): string { + return windowsPath.replace(/^([A-Z]):\\/i, (_: string, d: string) => `/mnt/${d.toLowerCase()}/`).replace(/\\/g, '/'); +} - return { status: "indexed", chunks: allChunks.length }; - } catch (error: any) { - console.error(`[Brain Indexer] Error:`, error); - return { status: "error", chunks: 0, message: error.message }; - } +function projectIdFromPath(projectRoot: string): string { + let hash = 0; + for (let i = 0; i < projectRoot.length; i++) { + const char = projectRoot.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash |= 0; } + return `proj_${Math.abs(hash).toString(36)}`; +} - private async discoverFiles(projectRoot: string): Promise { - const files: string[] = []; - const extensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".rs", ".go", ".java", ".php", ".vue", ".svelte"]; - - const walkDir = async (dir: string) => { - try { - const entries = fs.readdirSync(dir, { withFileTypes: true }); - - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - - if (entry.isDirectory()) { - if ( - !entry.name.startsWith(".") && - !IGNORE_PATTERNS.some((p) => { - if (p.endsWith("/**")) { - return fullPath.includes(p.replace("/**", "")); - } - return entry.name === p.replace("**/", ""); - }) - ) { - await walkDir(fullPath); - } - } else if (entry.isFile()) { - const ext = path.extname(entry.name).toLowerCase(); - if (extensions.includes(ext)) { - files.push(fullPath); - } - } - } - } catch { - // Skip directories we can't read - } - }; - - await walkDir(projectRoot); - return files; +export async function indexProject( + projectRoot: string, + opts: { force?: boolean } = {} +): Promise { + const wslPath = toWslPath(projectRoot); + const projectId = projectIdFromPath(projectRoot); + + const res = await fetch(`${BRAIN_EMBED_URL}/index`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + project_root: wslPath, + force: opts.force ?? false, + project_id: projectId, + }), + }); + + if (!res.ok) { + const text = await res.text(); + throw new Error(`Index failed (${res.status}): ${text}`); } - private async chunkFile(filePath: string): Promise { - try { - const fs = await import("fs"); - const content = fs.readFileSync(filePath, "utf-8"); - const lines = content.split("\n"); - const chunks: Chunk[] = []; - const mtime = fs.statSync(filePath).mtimeMs; - - for (let i = 0; i < lines.length; i += CHUNK_SIZE - CHUNK_OVERLAP) { - const chunkLines = lines.slice(i, i + CHUNK_SIZE); - const chunkText = chunkLines.join("\n").trim(); - - if (chunkText.length > 20) { - chunks.push({ - text: chunkText, - path: filePath, - startLine: i + 1, - endLine: Math.min(i + CHUNK_SIZE, lines.length), - mtime, - }); - } - } - - return chunks; - } catch { - return []; - } - } + const data = await res.json(); + return { + status: "indexed", + files_indexed: data.files_indexed, + chunks: data.chunks, + duration_ms: data.duration_ms, + }; } - -export const indexer = new Indexer(); diff --git a/brain-plugin/retrieval/lancadb.ts b/brain-plugin/retrieval/lancadb.ts index 540e001..b318588 100644 --- a/brain-plugin/retrieval/lancadb.ts +++ b/brain-plugin/retrieval/lancadb.ts @@ -1,3 +1,7 @@ +// DEPRECATED: LanceDB-based local indexing has been replaced by the Rust sidecar (brain-embed). +// All indexing and search now goes through the Rust sidecar's /index and /search endpoints. +// This file is kept for reference only and will be removed in a future cleanup. +// See: brain-plugin/rust/ for the active implementation. import { defaultProvider } from "../provider/lmstudio"; import { exec } from "child_process"; import { promisify } from "util"; @@ -31,17 +35,7 @@ async function loadLanceDB() { lancedb = await import("@lancedb/lancedb"); return lancedb; } catch (err) { - console.log("[Brain LanceDB] Package not found, attempting auto-install..."); - try { - const { exec: execSync } = await import("child_process"); - execSync("bun add @lancedb/lancedb", { stdio: "inherit" }); - lancedb = await import("@lancedb/lancedb"); - console.log("[Brain LanceDB] Auto-install successful!"); - return lancedb; - } catch (installErr) { - console.log("[Brain LanceDB] Auto-install failed:", installErr); - return null; - } + return null; } } @@ -51,132 +45,21 @@ class LanceDBClient { private isConnected = false; async connect(dbPath: string): Promise { - try { - const lib = await loadLanceDB(); - if (!lib) { - console.log("[Brain LanceDB] LanceDB not available"); - return false; - } - this.db = await lib.connect(dbPath); - this.isConnected = true; - console.log(`[Brain LanceDB] Connected to ${dbPath}`); - return true; - } catch (error: any) { - console.log("[Brain LanceDB] Connect failed:", error.message); - this.isConnected = false; - return false; - } - } - - async initialize(dbPath: string): Promise { - const connected = await this.connect(dbPath); - if (!connected) return; - - try { - const tableNames = await this.db.tableNames(); - if (tableNames.includes("codebase")) { - this.table = await this.db.openTable("codebase"); - console.log("[Brain LanceDB] Opened existing 'codebase' table"); - } - } catch (error: any) { - console.log("[Brain LanceDB] Table init:", error.message); - } - } - - async addChunks(chunks: Chunk[]): Promise { - if (!chunks.length) return; - if (!this.isConnected || !this.db) { - console.log("[Brain LanceDB] Not connected, skipping chunk add"); - return; - } - - try { - const handle = await defaultProvider.load("text-embedding-nomic-embed-text-v1.5"); - try { - const texts = chunks.map((c) => c.text); - const embeddings = await defaultProvider.embed("text-embedding-nomic-embed-text-v1.5", texts); - - const records: LanceDBRecord[] = chunks.map((chunk, i) => ({ - id: `${chunk.path}:${chunk.startLine}-${Date.now()}-${i}`, - text: chunk.text, - path: chunk.path, - startLine: chunk.startLine, - endLine: chunk.endLine, - mtime: chunk.mtime, - vector: embeddings[i] || new Array(768).fill(0), - })); - - await this.addChunksFromRecords(records); - } finally { - await defaultProvider.unload(handle); - } - } catch (error: any) { - console.error("[Brain LanceDB] addChunks failed:", error.message); - } + return false; } - async addChunksFromRecords(records: LanceDBRecord[]): Promise { - if (!this.isConnected || !this.db) { - console.log("[Brain LanceDB] Not connected, skipping addChunksFromRecords"); - return; - } - - try { - if (this.table) { - await this.db.dropTable("codebase"); - console.log("[Brain LanceDB] Dropped existing table"); - } + async initialize(dbPath: string): Promise {} - this.table = await this.db.createTable("codebase", records); - console.log(`[Brain LanceDB] Created table with ${records.length} records`); + async addChunks(chunks: Chunk[]): Promise {} - try { - await this.table.createIndex("vector", { metric: "cosine" }); - console.log("[Brain LanceDB] Created vector index"); - } catch (indexErr) { - console.log("[Brain LanceDB] Index creation skipped (may already exist):", indexErr); - } - } catch (error: any) { - console.error("[Brain LanceDB] addChunksFromRecords failed:", error.message); - } - } + async addChunksFromRecords(records: LanceDBRecord[]): Promise {} async query(queryEmbedding: number[], limit: number): Promise { - if (!this.isConnected || !this.table) { - console.log("[Brain LanceDB] Not connected or no table, returning empty"); - return []; - } - - try { - const results = await this.table - .vectorSearch(queryEmbedding) - .limit(limit) - .toArray(); - - return results.map((r: any) => ({ - text: r.text, - path: r.path, - startLine: r.startLine, - endLine: r.endLine, - mtime: r.mtime, - })); - } catch (error: any) { - console.error("[Brain LanceDB] Query failed:", error.message); - return []; - } + return []; } async getStats(): Promise<{ totalChunks: number; lastIndexed: number }> { - if (!this.isConnected || !this.table) { - return { totalChunks: 0, lastIndexed: 0 }; - } - - try { - const count = await this.table.count(); - return { totalChunks: count, lastIndexed: Date.now() }; - } catch { - return { totalChunks: 0, lastIndexed: 0 }; - } + return { totalChunks: 0, lastIndexed: 0 }; } async isFresh(projectRoot: string): Promise { @@ -184,7 +67,7 @@ class LanceDBClient { } isReady(): boolean { - return this.isConnected && this.table !== null; + return false; } } diff --git a/brain-plugin/retrieval/searcher.ts b/brain-plugin/retrieval/searcher.ts index aa114f1..25f4300 100644 --- a/brain-plugin/retrieval/searcher.ts +++ b/brain-plugin/retrieval/searcher.ts @@ -1,4 +1,4 @@ -import { defaultProvider, type ModelHandle } from "../provider/lmstudio"; +const BRAIN_EMBED_URL = "http://127.0.0.1:7878"; export interface Chunk { text: string; @@ -21,169 +21,54 @@ export interface RetrievalOptions { rerank: boolean; } -const LM_STUDIO_URL = "http://192.168.1.12:1234/v1"; - -export class Searcher { - private provider = defaultProvider; - - async search( - query: string, - opts: RetrievalOptions, - projectRoot: string, - lspSymbols?: any[] - ): Promise { - if (opts.depth === "none" || opts.maxChunks === 0) { - return { chunks: [], totalChunks: 0 }; - } - - const handle = await this.provider.load("text-embedding-nomic-embed-text-v1.5"); - - try { - const queryEmbedding = await this.provider.embed("text-embedding-nomic-embed-text-v1.5", [query]); - - let searchResults = await this.queryVectorDB(queryEmbedding[0], opts.maxChunks * 2); - - if (opts.depth === "diagnostic" && lspSymbols?.length) { - const diagnosticFiles = [...new Set(lspSymbols.map((s) => s.file))]; - searchResults = searchResults.filter((r) => - diagnosticFiles.some((df) => r.path.includes(df)) - ); - searchResults = [...searchResults, ...this.getLspContext(lspSymbols)].slice(0, opts.maxChunks); - } - - if (opts.depth === "precise") { - const fileLineMatches = this.extractFileLines(query); - if (fileLineMatches.length > 0) { - const preciseChunks = await this.loadPreciseChunks(fileLineMatches); - searchResults = [...preciseChunks, ...searchResults].slice(0, opts.maxChunks); - } - } - - let chunks = searchResults.slice(0, opts.maxChunks); - - if (opts.rerank && chunks.length > 5) { - chunks = await this.rerankChunks(query, chunks); - chunks = chunks.slice(0, opts.maxChunks); - } - - return { - chunks, - totalChunks: chunks.length, - }; - } finally { - await this.provider.unload(handle); - } - } - - private async queryVectorDB(queryEmbedding: number[], limit: number): Promise { - try { - const { lancadb } = await import("./lancadb"); - const results = await lancadb.query(queryEmbedding, limit); - return results; - } catch { - console.log("[Brain Searcher] LanceDB not available, using mock results"); - return this.getMockResults(queryEmbedding, limit); - } - } - - private getMockResults(embedding: number[], limit: number): Chunk[] { - return Array.from({ length: Math.min(limit, 5) }, (_, i) => ({ - text: `Mock chunk ${i + 1} for search`, - path: `src/file${i}.ts`, - startLine: (i * 40) + 1, - endLine: (i + 1) * 40, - mtime: Date.now(), - })); - } - - private getLspContext(lspSymbols: any[]): Chunk[] { - return lspSymbols.slice(0, 10).map((s) => ({ - text: `Symbol: ${s.name} (${s.kind})`, - path: s.file, - startLine: s.range?.start?.line || 0, - endLine: s.range?.end?.line || 0, - mtime: Date.now(), - })); +function projectIdFromPath(projectRoot: string): string { + let hash = 0; + for (let i = 0; i < projectRoot.length; i++) { + const char = projectRoot.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash |= 0; } + return `proj_${Math.abs(hash).toString(36)}`; +} - private extractFileLines(query: string): Array<{ file: string; line: number }> { - const matches: Array<{ file: string; line: number }> = []; - const fileLineRegex = /(\S+\.\w+):(\d+)/g; - let match; - - while ((match = fileLineRegex.exec(query)) !== null) { - matches.push({ file: match[1], line: parseInt(match[2], 10) }); - } - - return matches; +export async function searchContext( + query: string, + opts: RetrievalOptions, + projectRoot?: string +): Promise { + if (opts.depth === "none" || opts.maxChunks === 0) { + return { chunks: [], totalChunks: 0 }; } - private async loadPreciseChunks(fileLines: Array<{ file: string; line: number }>): Promise { - const chunks: Chunk[] = []; - - for (const { file, line } of fileLines) { - try { - const fs = await import("fs"); - if (fs.existsSync(file)) { - const content = fs.readFileSync(file, "utf-8"); - const lines = content.split("\n"); - const start = Math.max(0, line - 20); - const end = Math.min(lines.length, line + 20); - const chunkText = lines.slice(start, end).join("\n"); - - chunks.push({ - text: chunkText, - path: file, - startLine: start + 1, - endLine: end, - mtime: fs.statSync(file).mtimeMs, - }); - } - } catch { - // File read failed, skip - } - } - - return chunks; + const projectId = projectRoot ? projectIdFromPath(projectRoot) : undefined; + + const res = await fetch(`${BRAIN_EMBED_URL}/search`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + query, + top_k: opts.maxChunks, + project_id: projectId, + }), + }); + + if (!res.ok) { + console.error("Search failed:", await res.text()); + return { chunks: [], totalChunks: 0 }; } - private async rerankChunks(query: string, chunks: Chunk[]): Promise { - try { - const { defaultProvider } = await import("../provider/lmstudio"); - const handle = await defaultProvider.load("text-embedding-nomic-embed-text-v1.5"); - - try { - const embeddings = await defaultProvider.embed("text-embedding-nomic-embed-text-v1.5", [query, ...chunks.map((c) => c.text)]); - const queryEmb = embeddings[0]; - const chunkEmbeddings = embeddings.slice(1); - - const scores = chunkEmbeddings.map((emb) => this.cosineSimilarity(queryEmb, emb)); - - return chunks - .map((chunk, i) => ({ chunk, score: scores[i] })) - .sort((a, b) => b.score - a.score) - .map((item) => item.chunk); - } finally { - await defaultProvider.unload(handle); - } - } catch { - return chunks; - } - } - - private cosineSimilarity(a: number[], b: number[]): number { - let dot = 0; - let normA = 0; - let normB = 0; - - for (let i = 0; i < a.length; i++) { - dot += a[i] * b[i]; - normA += a[i] * a[i]; - normB += b[i] * b[i]; - } - - return dot / (Math.sqrt(normA) * Math.sqrt(normB)); - } + const results = await res.json(); + const chunks: Chunk[] = results.map((r: any) => ({ + text: r.text, + path: r.path, + startLine: r.start_line, + endLine: r.start_line + r.text.split('\n').length - 1, + mtime: 0, + })); + + return { + chunks, + scores: results.map((r: any) => r.score), + totalChunks: chunks.length, + }; } - -export const searcher = new Searcher(); diff --git a/docs/Plugins-Guide.md b/docs/Plugins-Guide.md index cd4e3c9..1127b31 100644 --- a/docs/Plugins-Guide.md +++ b/docs/Plugins-Guide.md @@ -6,7 +6,7 @@ Plugins extend OpenCode's functionality via TypeScript/JavaScript modules that e --- -## ๐Ÿš€ Active Plugins (11 total) +## ๐Ÿš€ Active Plugins (12 total) ### Core Plugins (7 โ€” work in web + CLI) @@ -20,14 +20,20 @@ Plugins extend OpenCode's functionality via TypeScript/JavaScript modules that e | 6 | **Context Manager** | `context-manager.ts` | Dynamic context include/exclude configuration | | 7 | **JSONC Utils** | `jsonc-utils.ts` | Shared utility for parsing JSONC config (with `//`-safe comment stripping) | +### Cognitive Plugins (1 โ€” project intelligence) + +| # | Plugin | File | Purpose | +| --- | ---------------- | ----------------------- | --------------------------------------------------------------------------------------- | +| 8 | **Brain Plugin** | `brain-plugin/brain.ts` | RAG engine: auto-index, vector search, speculative decoding, codebase context injection | + ### IDE/Trae Bridge Plugins (4 โ€” require IDE context) | # | Plugin | File | Purpose | | --- | ---------------------------- | ----------------------------- | ------------------------------------------------------------- | -| 8 | **Extension Context Bridge** | `extension-context-bridge.ts` | Bridges extension context from Trae IDE | -| 9 | **IDE MCP Bridge** | `ide-mcp-bridge.ts` | Exposes MCP servers running in IDE to OpenCode | -| 10 | **Language Context Bridge** | `language-context-bridge.ts` | LSP integration (rust-analyzer, TypeScript, PHP Intelephense) | -| 11 | **Process Monitor** | `process-monitor.ts` | Process tree monitoring and health checks | +| 9 | **Extension Context Bridge** | `extension-context-bridge.ts` | Bridges extension context from Trae IDE | +| 10 | **IDE MCP Bridge** | `ide-mcp-bridge.ts` | Exposes MCP servers running in IDE to OpenCode | +| 11 | **Language Context Bridge** | `language-context-bridge.ts` | LSP integration (rust-analyzer, TypeScript, PHP Intelephense) | +| 12 | **Process Monitor** | `process-monitor.ts` | Process tree monitoring and health checks | ### External (npm) @@ -100,10 +106,9 @@ Plugins are registered in `opencode.json`: "plugin": [ "plugins/index.ts", "plugins/agent-router.ts", - "plugins/model-router.ts", - "plugins/mcp-manager.ts", "plugins/skill-manager.ts", "plugins/context-manager.ts", + "brain-plugin/brain.ts", # RAG engine: auto-index, vector search, speculative decoding "plugins/extension-context-bridge.ts", "plugins/ide-mcp-bridge.ts", "plugins/language-context-bridge.ts", @@ -196,6 +201,8 @@ const config = parseJsonc(readFileSync("opencode.json", "utf8")); --- +> **Note:** The Brain Plugin lives at `brain-plugin/brain.ts` (separate from `plugins/`) because it has its own Rust sidecar project at `C:\rust-brain-sidecar` (outside the repo). See [Brain Plugin Docs](brain-plugin-docs.md). + ## ๐Ÿ“‚ Plugin File Structure ``` diff --git a/docs/Prompting-Guide.md b/docs/Prompting-Guide.md index 227f29f..4c8c893 100644 --- a/docs/Prompting-Guide.md +++ b/docs/Prompting-Guide.md @@ -22,8 +22,9 @@ 12. [Multi-Agent Orchestration](#12-multi-agent-orchestration) 13. [Self-Improvement & Evolution](#13-self-improvement--evolution) 14. [Edge Cases & Troubleshooting](#14-edge-cases--troubleshooting) -15. [Command Reference](#15-command-reference) -16. [Anti-Patterns to Avoid](#16-anti-patterns-to-avoid) +15. [Brain Plugin โ€” RAG & Codebase Context](#15-brain-plugin--rag--codebase-context) +16. [Command Reference](#16-command-reference) +17. [Anti-Patterns to Avoid](#17-anti-patterns-to-avoid) --- @@ -785,7 +786,70 @@ Runs config-doctor skill to: --- -## 15. Command Reference +## 15. Brain Plugin โ€” RAG & Codebase Context + +The **Brain Plugin** provides automatic RAG (Retrieval-Augmented Generation) for OpenCode. It indexes your project, embeds code with LM Studio, and injects relevant chunks into prompts automatically. + +### How RAG Works (Automatic) + +When you ask a question, the brain plugin: +1. **Classifies intent** via decision tree (debug, refactor, feature, test, learn) +2. **Prewarms** the embed model in LM Studio +3. **Embeds** your query and searches the project index (HNSW vector store) +4. **Injects** top-K code chunks into the prompt as context + +No manual action needed โ€” it just works on every `message.updated`. + +### Manual Brain Commands + +| Command | Purpose | +|---------|---------| +| `brain_diagnostic` | Full pipeline check: sidecar, cache, search, models | +| `brain_status` | Decision tree stats, GPU usage, cache hit rate | +| `brain_search` | Manual semantic search: `brain_search query:"auth flow"` | +| `brain_embed_test` | Test what context a query would retrieve | +| `brain_sidecar_status` | Check Rust sidecar health + loaded models | +| `brain_model_load` | Prewarm a model (chat/embed/draft) | +| `brain_model_unload` | Free VRAM by unloading non-essential models | + +### Prompting with RAG Context + +The brain injects context as markdown code blocks at the top of your prompt: + +``` +## Context 1: `src/auth/login.ts:15-45` +```typescript +function authenticate(user, pass) { ... } +``` + +## Context 2: `src/auth/utils.ts:1-30` +```typescript +const TOKEN_EXPIRY = 3600; +``` + +--- +User request: +``` + +You can also force context retrieval with: + +``` +@build Use brain_search to find auth-related files, then read them. +``` + +### Troubleshooting + +| Symptom | Fix | +|---------|-----| +| RAG not injecting chunks | Run `brain_diagnostic` โ†’ if sidecar down, run `brain_sidecar_restart` | +| 0 search results | Run `brain_index_project force:true` to (re)index | +| LM Studio errors | Check `brain_speculative_status` โ€” disable speculative if draft model incompatible | +| VRAM full | `brain_model_unload` or reduce draft offload in LM Studio | +| Want to see what's indexed | `brain_status` shows chunk count and indexed projects | + +--- + +## 16. Command Reference ### System Commands @@ -844,7 +908,7 @@ Runs config-doctor skill to: --- -## 16. Anti-Patterns to Avoid +## 17. Anti-Patterns to avoid ### โŒ Vague Prompts diff --git a/docs/User-Guide.md b/docs/User-Guide.md index d34b40a..5c9e1d1 100644 --- a/docs/User-Guide.md +++ b/docs/User-Guide.md @@ -186,21 +186,24 @@ Run with: `bun benchmark.js` --- -## ๐Ÿ”— Plugin Ecosystem (11 plugins) - -| Plugin | Purpose | -| ----------------------------- | ------------------------------------------- | -| `agent-router.ts` | Intelligent task-to-agent routing | -| `model-router.ts` | Smart model selection based on capabilities | -| `mcp-manager.ts` | MCP server health and toggle management | -| `skill-manager.ts` | Skill registry access and search | -| `context-manager.ts` | Dynamic context configuration | -| `index.ts` | LM Studio management + self-improvement | -| `jsonc-utils.ts` | Shared JSONC parser (comment-safe) | -| `extension-context-bridge.ts` | Trae IDE extension bridge | -| `ide-mcp-bridge.ts` | IDE MCP bridge | -| `language-context-bridge.ts` | LSP integration bridge | -| `process-monitor.ts` | Process monitoring | +## ๐Ÿ”— Plugin Ecosystem (12 plugins) + +| Plugin | Purpose | +| ----------------------------- | ------------------------------------------------- | +| `agent-router.ts` | Intelligent task-to-agent routing | +| `model-router.ts` | Smart model selection based on capabilities | +| `mcp-manager.ts` | MCP server health and toggle management | +| `skill-manager.ts` | Skill registry access and search | +| `context-manager.ts` | Dynamic context configuration | +| `index.ts` | LM Studio management + self-improvement | +| `brain-plugin/brain.ts` | **RAG engine**: auto-index, vector search, speculative decoding, codebase context | +| `jsonc-utils.ts` | Shared JSONC parser (comment-safe) | +| `extension-context-bridge.ts` | Trae IDE extension bridge | +| `ide-mcp-bridge.ts` | IDE MCP bridge | +| `language-context-bridge.ts` | LSP integration bridge | +| `process-monitor.ts` | Process monitoring | + +> The **Brain Plugin** provides RAG-powered codebase context: auto-indexes your project, embeds code with LM Studio, and injects relevant chunks into LLM prompts. See [**Brain Plugin Docs**](brain-plugin-docs.md) for details. > See [**Plugins Guide**](Plugins-Guide.md) for complete details. diff --git a/docs/brain-plugin-docs.md b/docs/brain-plugin-docs.md index c0e1648..0707a18 100644 --- a/docs/brain-plugin-docs.md +++ b/docs/brain-plugin-docs.md @@ -2,7 +2,7 @@ > Cognitive layer for OpenCode: auto-classifies developer intent, retrieves > relevant codebase context via local embeddings, and augments LLM prompts -> with RAG โ€” all running 100% locally through LM Studio. +> with RAG โ€” all running 100% locally through LM Studio and Rust sidecar. --- @@ -10,65 +10,53 @@ 1. [Architecture Overview](#1-architecture-overview) 2. [How the Brain Plugin Works](#2-how-the-brain-plugin-works) -3. [Embedding System](#3-embedding-system) -4. [Speculative Decoding](#4-speculative-decoding) -5. [Why & How to Improve](#5-why--how-to-improve) -6. [File Structure](#6-file-structure) -7. [Usage & Commands](#7-usage--commands) +3. [Lifecycle & Orchestration](#3-lifecycle--orchestration) +4. [Tool Reference](#4-tool-reference) +5. [Embedding System](#5-embedding-system) +6. [Speculative Decoding](#6-speculative-decoding) +7. [Model Loading Strategy](#7-model-loading-strategy) +8. [Why & How to Improve](#8-why--how-to-improve) +9. [File Structure](#9-file-structure) +10. [Usage & Commands](#10-usage--commands) --- ## 1. Architecture Overview ``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ OpenCode IDE โ”‚ -โ”‚ โ”‚ -โ”‚ User types โ†’ message.updated hook fires โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Decision Tree โ”‚ โ”‚ -โ”‚ โ”‚ (classify intent + pick strategy)โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Context Searcher โ”‚ โ”‚ -โ”‚ โ”‚ (embed query โ†’ cosine search โ†’ โ”‚ โ”‚ -โ”‚ โ”‚ optional rerank โ†’ top-K chunks) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Context Injector โ”‚ โ”‚ -โ”‚ โ”‚ (prepend retrieved context to โ”‚ โ”‚ -โ”‚ โ”‚ user message as code blocks) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ”‚ -โ”‚ Augmented prompt sent to LM Studio for completion โ”‚ -โ”‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Session Memory โ”‚ โ”‚ -โ”‚ โ”‚ (tracks decisions, files, โ”‚ โ”‚ -โ”‚ โ”‚ diagnostics, successes/failures)โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ -โ”‚ Storage: JSON embedding files under C:/opencode/.indexes/โ”‚ -โ”‚ Vector math: pure JS cosine similarity (no native deps) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +User types โ†’ message.updated hook + โ†’ Decision Tree (classify intent) + โ†’ Model Prewarmer (pre-load embed) + โ†’ Context Searcher (HTTP โ†’ Rust sidecar โ†’ LM Studio embed โ†’ HNSW search) + โ†’ Context Injector (prepend chunks to prompt) + โ†’ Augmented prompt sent to LLM + +Rust sidecar (:7878): + โ”œโ”€โ”€ /health /metrics /config /models /gpu + โ”œโ”€โ”€ /index (Tree-sitter chunk โ†’ embed โ†’ HNSW store) + โ”œโ”€โ”€ /search (vector + BM25 hybrid) + โ”œโ”€โ”€ /chat (speculative decoding) + โ”œโ”€โ”€ /cache/stats /cache/invalidate + โ””โ”€โ”€ /prewarm + +Brain plugin hooks: + - server.start: spawn sidecar, health check, auto-index + - message.updated: RAG pipeline + - chat.params: inject draft_model + - file.watcher.updated: dirty tracking โ†’ debounced reindex + - session.archived: kill sidecar ``` ### Key Design Decisions -| Decision | Rationale | -|----------|-----------| -| JSON file storage instead of LanceDB | LanceDB native module hangs on Windows; JSON is portable, debuggable, zero-dep | -| Pure JS cosine similarity | No numpy/tensorflow needed; runs anywhere Node runs | -| nomic-embed for indexing | ~24ร— faster than qwen3-embed (1s vs 39s per 32 texts), sufficient quality | -| Incremental + hash-based dedup | Skips unchanged files; removes repeated chunks; saves hours on re-index | -| Active-project-only indexing | Default indexes only the project you're working in; `--all` flag for full sweep | +| Decision | Rationale | +| --------------------- | ---------------------------------------------------------------- | +| Rust sidecar not JS | Faster vector ops, Tree-sitter parsing; no Node.js memory limits | +| HTTP API separation | Independent scaling, testing, deployment | +| HNSW vector storage | Fast approximate nearest neighbor; in-memory with persistence | +| Tree-sitter chunking | AST-aware code splits; preserves semantics | +| LM Studio integration | 100% local; no cloud dependencies | +| Serial model loading | ~4GB VRAM (M4400) can't hold all 3 models simultaneously | --- @@ -76,409 +64,293 @@ ### 2.1 Entry Point โ€” `brain.ts` -The plugin registers OpenCode hooks and tools: +**Hooks (automatic):** -**Hooks (automatic, no user action needed):** - -| Hook | What it does | -|------|-------------| -| `message.updated` | On every user message: classify intent โ†’ retrieve context โ†’ inject into prompt | -| `tool.execute.after` | Track edit/write success; mark bash failures | -| `lsp.client.diagnostics` | Store diagnostics; prewarm "debug" intent when errors detected | -| `file.watcher.updated` | Mark files as dirty so re-indexing picks them up | -| `session.compacting` | Summarize session memory; persist decision tree | - -**Tools (user-invocable via chat):** - -| Tool | Purpose | -|------|---------| -| `brain_index_project` | Index current or specified project (`--force` to re-index) | -| `brain_search` | Manual semantic search across the codebase | -| `brain_status` | Show decision tree stats, intent weights, session memory | -| `brain_reset` | Clear all memory and reset decision tree | +| Hook | What it does | +| ------------------------ | ----------------------------------------------------------------------- | +| `server.start` | Spawn Rust sidecar (WSL), 30s health-check, sync config, auto-index | +| `message.updated` | Classify intent โ†’ prewarm embed โ†’ retrieve context โ†’ inject into prompt | +| `chat.params` | Inject `draft_model` into LM Studio calls when speculative enabled | +| `tool.execute.after` | Track success/failure | +| `lsp.client.diagnostics` | Store diagnostics; prewarm "debug" intent | +| `file.watcher.updated` | Mark dirty; batch 5 โ†’ 5s debounce โ†’ reindex | +| `session.compacting` | Summarize memory; persist decision tree | +| `session.archived` | Kill sidecar | ### 2.2 Decision Tree โ€” `tree/engine.ts` -The decision tree classifies user intent into one of **7 categories**: - -| Intent | Trigger Patterns | Retrieval Strategy | Depth | Chunks | -|--------|------------------|--------------------|-------|--------| -| `debug` | "error", "exception", "fail", "bug", "panic" | `diagnostic_targeted` | diagnostic | 10 | -| debug+stacktrace | stack trace patterns, `at file:line` | `stack_trace_precise` | precise | 5 | -| `refactor` | "refactor", "restructure", "extract", "rename" | `refactor_multi_file` | broad | 20 | -| refactor+single | "this function/class" + 1 recent file | `refactor_local` | shallow | 8 | -| `feature` | "add", "implement", "create", "support" | `feature_architecture` | broad | 15 | -| `test` | "test", "spec", "jest", "pytest" | `test_context` | targeted | 12 | -| `learn` | "how does", "explain", "architecture", "understand" | `learn_summarize` | broad | 25 | -| `quick_chat` | default / no specific intent | `direct` | none | 0 (skipped) | - -**Scoring mechanism:** Each node computes `weight ร— log(visits + 2)`. The node with highest score wins. Weights are updated after each interaction โ€” successful retrievals increase weight; failures decrease it and may spawn child nodes with refined conditions. - -### 2.3 LM Studio Provider โ€” `provider/lmstudio.ts` - -Central wrapper around LM Studio REST API. Provides four operations: - -| Method | Endpoint | Purpose | -|--------|----------|---------| -| `load()` | `POST /api/v1/models/load` | Load model into GPU memory; set context_length, flash_attention, KV cache | -| `unload()` | `POST /api/v1/models/unload` | Free GPU memory | -| `embed()` | `POST /v1/embeddings` | Generate embedding vectors for text arrays | -| `chat()` | `POST /v1/chat/completions` | Standard chat completion | -| `chatWithSpeculative()` | `POST /v1/chat/completions` + `draft_model` | Speculative decoding (see ยง4) | - -**Constants:** -- `DEFAULT_EMBED_MODEL = "text-embedding-nomic-embed-text-v1.5"` (768 dims, 84MB, fast) -- `DEFAULT_CHAT_MODEL = "qwen3.5-4b-claude-4.6-opus-reasoning-distilled-v2"` -- `DEFAULT_DRAFT_MODEL = "qwen3.5-0.8b-claude-4.6-opus-reasoning-distilled-v2"` - -### 2.4 Context Searcher โ€” `retrieval/searcher.ts` - -Retrieves relevant code chunks given a user query: - -1. **Embed query** using nomic-embed -2. **Query vector store** โ€” cosine similarity search across all stored chunks -3. **Strategy-specific adjustments:** - - `diagnostic`: filter to files with LSP errors, merge with symbol context - - `precise`: extract `file:line` references from query, load exact code windows - - `shallow/broad/targeted`: pure vector search with different chunk limits -4. **Rerank** (optional): embed top candidates + query again, compute cosine scores, sort - -Depth strategies control retrieval breadth: +7 intent categories. Scored by `weight ร— log(visits + 2)`: + +| Intent | Triggers | Chunks | Prewarm | +| ---------------- | ------------------------------- | ------ | ------- | +| `debug` | error, exception, fail, panic | 10 | embed | +| debug+stacktrace | stack traces | 5 | embed | +| `refactor` | refactor, extract, rename | 20 | embed | +| refactor+single | "this function" + 1 recent file | 8 | embed | +| `feature` | add, implement, create | 15 | embed | +| `test` | test, spec, jest, pytest | 12 | embed | +| `learn` | how does, explain, architecture | 25 | embed | +| `quick_chat` | default | 0 | none | + +### 2.3 Rust Sidecar Endpoints + +| Endpoint | Method | Purpose | +| ------------------- | -------- | -------------------------------------- | +| `/health` | GET | Version, uptime, loaded models, GPU | +| `/metrics` | GET | Searches, cache stats, prewarm count | +| `/config` | GET/POST | Full configuration | +| `/models` | GET | LM Studio model list | +| `/gpu` | GET | GPU memory info | +| `/index` | POST | Tree-sitter chunk โ†’ embed โ†’ HNSW store | +| `/search` | POST | Vector + BM25 hybrid search | +| `/embed` | POST | Raw embeddings | +| `/chat` | POST | Chat with speculative decoding | +| `/cache/stats` | GET | Cache hit/miss rates | +| `/cache/invalidate` | POST | Clear cache | +| `/feedback` | POST | Token attribution | +| `/prewarm` | POST | Pre-warm embed model | -| Depth | When used | Max chunks | Rerank | -|-------|-----------|------------|--------| -| `none` | quick_chat | 0 | no | -| `shallow` | refactor single function | 8 | no | -| `targeted` | test, debug | 10โ€“12 | no | -| `broad` | feature, learn, refactor multi-file | 15โ€“25 | yes | -| `diagnostic` | error + LSP diagnostics | 10 | no | -| `precise` | stack trace references | 5 | yes | +--- -### 2.5 Context Injector โ€” `context/injector.ts` +## 3. Lifecycle & Orchestration -Formats retrieved chunks into the LLM prompt: +### 3.1 Startup ``` -You are working on a software development task. Relevant code context -has been retrieved from the codebase. - -## Context 1: `src/auth/login.ts:15-45` -```typescript -// ... matching code chunk ... +server.start: +1. Load decision tree +2. Check /health (already running?) +3. If not: spawn wsl.exe โ†’ brain-embed +4. Poll /health every 1s (max 30s) +5. POST /config (sync models) +6. POST /index (auto-index current directory) ``` -## Context 2: `src/auth/utils.ts:1-30` -```typescript -// ... matching code chunk ... -``` +### 3.2 File Change โ†’ Reindex ---- +``` +file.watcher.updated: + โ†’ add to dirtyFiles Set + โ†’ if โ‰ฅ5 dirty && not indexing: + โ†’ 5s debounce + โ†’ POST /index force + โ†’ clear dirtyFiles +``` -User request: +### 3.3 Session Teardown -Analyze the context carefully before responding. +``` +session.archived: + โ†’ SIGTERM sidecar + โ†’ save decision tree + โ†’ clear memory ``` -### 2.6 Session Memory โ€” `state/session.ts` +--- -Tracks conversation-level state: -- `decisions` โ€” every intent classification + strategy + context count -- `successCount` โ€” incremented on successful edit/write tool calls -- `failures` โ€” bash errors, with reason and timestamp -- `recentFiles` โ€” last 50 files touched -- `contextUsed` โ€” which chunks were actually injected -- `diagnostics` โ€” current LSP diagnostics (errors/warnings) +## 4. Tool Reference -Memory is summarized and appended to the compacted session context on `session.compacting`. +### Diagnostic & Status ---- +| Tool | Purpose | +| -------------------------- | --------------------------------------------------------- | +| `brain_diagnostic` | Full pipeline: health โ†’ cache โ†’ search test โ†’ config | +| `brain_status` | Sidecar health, GPU, models, cache, decision tree, memory | +| `brain_metrics` | Prometheus metrics: searches, hit rate, prewarm count | +| `brain_sidecar_status` | Detailed health: version, uptime, GPU, all 3 models | +| `brain_speculative_status` | Speculative decoding config | -## 3. Embedding System +### RAG & Search -### 3.1 Models Tested +| Tool | Purpose | +| --------------------- | ----------------------------------------- | +| `brain_search` | Semantic search (Rust /search) | +| `brain_embed_test` | Test query โ†’ top-K chunks with scores | +| `brain_index_project` | Index/re-index (Rust /index, Tree-sitter) | -| Model | Size | Dimensions | Load time | 32-text batch | Use case | -|-------|------|------------|-----------|---------------|----------| -| `text-embedding-nomic-embed-text-v1.5` | 84MB | 768 | 2.7s | **~1s** | Indexing, search โœ… | -| `qwen3-embedding-4b` | 2,458MB | 2560 | 9.0s | **~39s** | Quality tasks only | +### Model Management -**Verdict:** Use nomic-embed for everything. qwen3-embedding is 39ร— slower with no practical quality gain for code search. +| Tool | Purpose | +| -------------------- | ------------------------------------ | +| `brain_model_load` | Prewarm a model (chat, embed, draft) | +| `brain_model_unload` | Free VRAM by unloading | -### 3.2 Indexing Process +### Lifecycle -The indexer (`indexer.mjs`) processes each project: +| Tool | Purpose | +| ----------------------- | ------------------------------------ | +| `brain_sidecar_restart` | Kill and restart sidecar | +| `brain_reset` | Clear memory and reset decision tree | -1. **File discovery** โ€” walk project directory, skip `node_modules`, `.git`, `vendor`, `dist`, `build`, `.next`, `__pycache__` -2. **File categorization** โ€” assign chunking strategy by extension: +--- -| Category | Extensions | Chunk size | Overlap | -|----------|-----------|------------|---------| -| code | `.ts`, `.tsx`, `.js`, `.jsx`, `.php`, `.java`, `.go`, `.rs`, `.py`, `.vue`, `.svelte`, `.c`, `.cpp`, `.h` | 30 lines | 8 lines | -| docs | `.md`, `.txt`, `.rst` | 60 lines | 15 lines | -| config | `.json`, `.yaml`, `.yml`, `.toml`, `.ini`, `.env.example` | 20 lines | 5 lines | -| sql | `.sql` | 25 lines | 8 lines | +## 5. Embedding System -3. **Hash-based change detection** โ€” each file's `mtime + size` is hashed; unchanged files are skipped on incremental runs -4. **Chunk deduplication** โ€” identical chunks (by content hash) are stored once, references merged -5. **Embedding** โ€” batch size 16, sequential API calls with retry (4 attempts with backoff) -6. **Storage** โ€” JSON file at `C:/opencode/.indexes//embeddings.json` -7. **State file** โ€” `C:/opencode/.indexes//state.json` tracks file hashes, timestamp, model used +### Models -### 3.3 Incremental Indexing +| Model | Size | Dims | Speed | Use | +| -------------------- | ------- | ---- | ----------- | ---------------- | +| nomic-embed-v1.5 | 84MB | 768 | ~1s/batch | Indexing, search | +| qwen3-embedding-0.6b | 320MB | 1024 | ~359ms/4emb | Indexing, search | +| qwen3-embedding-4b | 2,458MB | 2560 | ~39s/batch | Quality only | -Re-running `node indexer.mjs index` from a project directory: -- Reads the state file -- Compares every file's `mtime+size` hash -- Only re-embeds changed/new files -- Merges with existing embeddings -- Typical re-index time: **<5 seconds** for unchanged projects +### Indexing -### 3.4 Search +1. `.gitignore`-aware file walk +2. Tree-sitter AST chunking (8 languages) +3. Blake3 hash โ†’ skip unchanged files +4. Batch embed via LM Studio (batch 32) +5. Store in HNSW index (binary, cosine similarity) -Search uses pure JS cosine similarity: -1. Embed the query string (same model as indexing) -2. Compute dot-product cosine similarity against every stored chunk vector -3. Sort by score, return top-K -4. Optional rerank: embed top candidates individually, rescore +### Search -**Performance:** Search over 12,000 chunks completes in **<1 second** including embedding the query. +- Embed query โ†’ HNSW top-K โ†’ BM25 fusion (optional) โ†’ LRU cache +- Speed: <1ms per query --- -## 4. Speculative Decoding +## 6. Speculative Decoding -### 4.1 How It Works +### Wiring -Speculative decoding uses a smaller "draft" model to generate candidate tokens, then the larger "main" model validates them in a single forward pass. The LM Studio API exposes this via the `draft_model` parameter: +The `chat.params` hook in `brain.ts` injects `draft_model` into LM Studio calls: -```json -POST /v1/chat/completions -{ - "model": "qwen3.5-4b", - "draft_model": "qwen3.5-0.8b", - "messages": [...], - "max_tokens": 1024 +```typescript +"chat.params": async (params: any) => { + if (!sidecar.healthy) return params; + const cfg = await fetchConfig(); + if (cfg?.speculative_enabled && cfg?.draft_model) { + params.draft_model = cfg.draft_model; + } + return params; } ``` -### 4.2 Benchmark Results +### Model Pairs -**Previous benchmark (gemma-4-e4b + gemma-4-e2b-it):** -- Without speculative: baseline -- With speculative: **1.23ร— speedup** +| Main | Draft | Status | +| -------------- | -------------- | ------------------- | +| qwen3.5-4b | qwen3.5-0.8b | Test (may mismatch) | +| gemma-4-e4b-it | gemma-4-e2b-it | 1.23x speedup | -**Qwen3.5 attempt (qwen3.5-4b + qwen3.5-0.8b):** -- โŒ **Incompatible** โ€” the 0.8B draft model produces output that the 4B validator rejects. Returns 0 tokens or garbled text. -- This is a known limitation: speculative decoding requires draft and main models from the **same architecture family**. Qwen3.5 models of different sizes may not be compatible. - -### 4.3 The `draft_model` Parameter - -Key rules: -1. Both models must be loaded in LM Studio before sending the request -2. The draft model should be the same family as the main model (e.g., Qwen3-4B draft for Qwen3-4B main) -3. Gemma-4-e2b-it is compatible as draft for gemma-4-e4b-it -4. The speedup scales with how much faster the draft model generates vs the main model accepts - -### 4.4 Current Status - -The `brain-plugin/provider/lmstudio.ts` has `chatWithSpeculative()` fully implemented. The `DEFAULT_DRAFT_MODEL` is set to `"qwen3.5-0.8b-claude-4.6-opus-reasoning-distilled-v2"` but is currently **incompatible**. When compatible draft models become available (or gemma is re-added), speculative decoding will work automatically. +--- -**To fix speculative:** Use matching model pairs from the same architecture: -- `gemma-4-e4b-it` (main) + `gemma-4-e2b-it` (draft) โ€” confirmed working, 1.23ร— speedup -- Wait for LM Studio to ship compatible qwen3.5 draft/main pairs +## 7. Model Loading Strategy ---- +### VRAM Analysis -## 5. Why & How to Improve - -### 5.1 Current Bottlenecks - -| Bottleneck | Current | Target | Fix | -|-----------|---------|--------|-----| -| Full indexing (all projects) | ~22 min | N/A | Index only active project | -| Incremental re-index | ~22 min | <5 min | โœ… Hash-based change detection โ†’ seconds | -| Embedding batch size | 16/chunk | 32/chunk | Increase if GPU memory allows | -| Storage format | JSON (plain) | Binary/compressed | Use msgpack or flatbuffer | -| Search algorithm | O(n) linear scan | O(log n) ANN | Add HNSW index later | -| Vector dimension | 768 (nomic) | 768 is fine | No change needed | - -### 5.2 Faster Embedding Alternatives - -| Approach | Speedup | Quality | Effort | -|----------|---------|---------|--------| -| **ONNX INT8 quantization** of nomic-embed | 2โ€“4ร— faster | Slight degradation | Medium โ€” need onnxruntime-node | -| **nomic-embed-v1.5-small** (if available) | 2ร— faster | Slight degradation | Low โ€” just change model name | -| **BGE-M3** (alternative model) | Comparable to nomic | Better for cross-lingual | Low โ€” change model name in LM Studio | -| **E5-Mistral-7B-instruct** embeddings | Much faster on GPU | Excellent quality | Low โ€” if LM Studio loads it | -| **Sentence-transformers + ONNX** | 5โ€“10ร— on CPU | Excellent for short text | High โ€” needs custom runtime | -| **GPU-accelerated batch inference** | 3โ€“5ร— | None | Medium โ€” batching in LM Studio | - -### 5.3 Faster Search Alternatives - -| Approach | Speedup | Quality | Notes | -|----------|---------|---------|-------| -| **HNSW index** (via lancedb) | 10โ€“100ร— | Approximate (95%+) | Only if LanceDB works or custom HNSW impl | -| **VAFFT/SIMD** cosine sim | 2โ€“4ร— | Exact | Use simd.js or wasm | -| **Quantized vectors (int8)** | 2ร— memory, faster dot | Slight degradation | Store as int8, accumulate in int32 | -| **Clustering + pruning** | 3โ€“5ร— | Approximate | Cluster embeddings, only search matching cluster | - -### 5.4 Practical Short-Term Improvements - -1. **Increase batch size to 32** โ€” `embedBatchSequential` โ†’ change `BATCH = 32` -2. **Parallelize embedding + saving** โ€” overlap I/O with computation -3. **Cache search results** โ€” save queryโ†’results map, invalidate on re-index -4. **Skip re-embedding on search** โ€” store vectors, compute query vector once -5. **Use `qwen3.embedding.0.6b`** if available โ€” middle ground between nomic (768d) and 4B (2560d) - -### 5.5 Long-Term Roadmap - -| Priority | Feature | Impact | -|----------|---------|--------| -| P0 | Fix LanceDB or use HNSW library | Search speed 10โ€“100ร— faster | -| P0 | ONNX int8 embedding runtime | Embedding 2โ€“4ร— faster | -| P1 | Compatible draft model for speculative | Chat 1.2โ€“1.5ร— faster | -| P1 | Per-file change tracking (not just mtime) | Incremental indexing more accurate | -| P2 | Vector quantization (int8/fp4) | 2โ€“4ร— memory reduction, faster search | -| P2 | Streaming embeddings from LM Studio | Reduce API roundtrips | -| P3 | Multi-index merging | Cross-project search without full re-index | -| P3 | Adaptive chunk sizing | Larger chunks for docs, smaller for code | +16GB RAM / M4400 ~4GB VRAM: ---- +| Model | Offload | VRAM | +| -------------------- | --------- | ----------- | +| qwen3.5-4b (chat) | 20 layers | ~3.0 GB | +| qwen3.5-0.8b (draft) | 24 layers | ~1.5 GB | +| qwen3-0.6b (embed) | 15 layers | ~0.5 GB | +| **Total** | | **~5.0 GB** | -## 6. File Structure +All 3 cannot fit simultaneously (~5GB > ~4GB). Strategy: ``` -c:\opencode\ -โ”œโ”€โ”€ indexer.mjs # Standalone CLI indexer tool -โ”‚ -โ””โ”€โ”€ brain-plugin/ - โ”œโ”€โ”€ index.ts # Plugin entry point - โ”œโ”€โ”€ brain.ts # Main plugin: hooks + tools + orchestration - โ”œโ”€โ”€ package.json # Dependencies: @lancedb/lancedb, glob - โ”‚ - โ”œโ”€โ”€ provider/ - โ”‚ โ””โ”€โ”€ lmstudio.ts # LM Studio API wrapper (load/unload/embed/chat) - โ”‚ - โ”œโ”€โ”€ retrieval/ - โ”‚ โ”œโ”€โ”€ indexer.ts # File discovery, chunking, embedding - โ”‚ โ”œโ”€โ”€ searcher.ts # Vector search, reranking, depth strategies - โ”‚ โ””โ”€โ”€ lancadb.ts # LanceDB client (vector storage/retrieval) - โ”‚ - โ”œโ”€โ”€ context/ - โ”‚ โ””โ”€โ”€ injector.ts # Prompt augmentation with retrieved context - โ”‚ - โ”œโ”€โ”€ state/ - โ”‚ โ””โ”€โ”€ session.ts # Session memory: decisions, files, diagnostics - โ”‚ - โ””โ”€โ”€ tree/ - โ””โ”€โ”€ engine.ts # Decision tree: intent classification + strategy selection +Normal: chat (3.0 GB) + 1.0 GB free +Search: chat + embed (0.5 GB) = 3.5 GB โ†’ unload embed +Speculative: chat + draft (1.5 GB) = 4.5 GB โ†’ reduce draft to 16 layers ``` -**Index storage:** -``` -c:\opencode\.indexes\ -โ”œโ”€โ”€ simple-signage/ -โ”‚ โ”œโ”€โ”€ embeddings.json # All chunk embeddings (versioned JSON) -โ”‚ โ””โ”€โ”€ state.json # File hashes, timestamps, model info -โ”œโ”€โ”€ camcontrol/ -โ”‚ โ”œโ”€โ”€ embeddings.json -โ”‚ โ””โ”€โ”€ state.json -โ””โ”€โ”€ mystockmaster/ - โ”œโ”€โ”€ embeddings.json - โ””โ”€โ”€ state.json +### Recommended LM Studio Settings + ``` +qwen3.5-4b (chat): + GPU Offload: 20, Context: 4096, Flash Attention: ON ---- +qwen3.5-0.8b (draft): + GPU Offload: 16 (reduced), Context: 2048 -## 7. Usage & Commands +qwen3-embedding-0.6b (embed): + GPU Offload: 15, Context: 2048 +``` -### Indexer CLI +--- -```bash -# Index active project (auto-detected from current directory) -node indexer.mjs index +## 8. Why & How to Improve -# Force re-index active project -node indexer.mjs index --force +### Status -# Index ALL projects -node indexer.mjs index --all +| Bottleneck | Status | +| ----------------- | ---------------------------- | +| Incremental index | โœ… Hash-based, seconds | +| Embedding batch | โœ… 32 | +| Search | โœ… HNSW O(log n) | +| VRAM | โš ๏ธ Serial loading (hardware) | +| Speculative | โš ๏ธ Draft pair compatibility | -# Index specific project -node indexer.mjs index-specific Simple-Signage +### Roadmap -# Search all indexed projects -node indexer.mjs search "authentication middleware" +| Pri | Feature | Impact | +| --- | --------------------- | ------------------- | +| P0 | ONNX int8 embedding | 2-4x faster | +| P1 | Compatible draft pair | 1.2-1.5x faster | +| P1 | Vector quantization | 2x memory reduction | -# Search specific project -node indexer.mjs search-specific Simple-Signage "login validation" +--- -# Run benchmarks (embedding speed + speculative decoding) -node indexer.mjs benchmark +## 9. File Structure -# Show indexing status -node indexer.mjs status +``` +brain-plugin/ +โ”œโ”€โ”€ index.ts # Plugin entry +โ”œโ”€โ”€ brain.ts # Hooks + tools + lifecycle +โ”œโ”€โ”€ package.json +โ”œโ”€โ”€ provider/lmstudio.ts # LM Studio wrapper +โ”œโ”€โ”€ retrieval/ +โ”‚ โ”œโ”€โ”€ searcher.ts # HTTP /search client +โ”‚ โ”œโ”€โ”€ indexer.ts # HTTP /index client +โ”‚ โ””โ”€โ”€ lancadb.ts # [DEPRECATED] +โ”œโ”€โ”€ context/injector.ts # Prompt augmentation +โ”œโ”€โ”€ state/session.ts # Session memory +โ””โ”€โ”€ tree/engine.ts # Decision tree + +brain-plugin/rust/ +โ”œโ”€โ”€ Cargo.toml +โ””โ”€โ”€ src/ + โ”œโ”€โ”€ main.rs # Server (15 endpoints) + โ”œโ”€โ”€ lmstudio.rs # LM Studio client + โ”œโ”€โ”€ chunk.rs # Tree-sitter chunking + โ”œโ”€โ”€ indexer.rs # File indexing + โ”œโ”€โ”€ store_hnsw.rs # HNSV vectors + โ”œโ”€โ”€ bm25.rs # BM25 search + โ”œโ”€โ”€ cache.rs # LRU cache + โ”œโ”€โ”€ state.rs # Index state + โ”œโ”€โ”€ config.rs # App config + โ”œโ”€โ”€ orchestrator.rs # Intent prediction + โ”œโ”€โ”€ multihop.rs # Import-following retrieval + โ”œโ”€โ”€ feedback.rs # Token attribution + โ””โ”€โ”€ project_memory.rs # Cross-session memory ``` -### Brain Plugin (via OpenCode) - -| Trigger | Action | -|---------|--------| -| User asks "how does the auth system work?" | โ†’ learn intent โ†’ broad search โ†’ 25 chunks injected | -| User asks "fix this error" with stack trace | โ†’ debug+stacktrace intent โ†’ precise search โ†’ 5 chunks | -| User asks "write tests for auth" | โ†’ test intent โ†’ targeted search โ†’ 12 chunks | -| User asks "refactor this" in single file | โ†’ refactor+single intent โ†’ shallow search โ†’ 8 chunks | -| User asks general chat | โ†’ quick_chat intent โ†’ no retrieval โ†’ direct response | - -### Quick Start Checklist +--- -- [ ] LM Studio running at `http://192.168.1.12:1234` -- [ ] `text-embedding-nomic-embed-text-v1.5` available in LM Studio -- [ ] `qwen3.5-4b-claude-4.6-opus-reasoning-distilled-v2` available in LM Studio -- [ ] `opencode.json` configured with brain-plugin -- [ ] Run: `cd /opencode && node indexer.mjs index` from project directory -- [ ] Verify: `node indexer.mjs search "any code concept"` returns results -- [ ] Test in OpenCode: ask a coding question referencing your project +## 10. Usage & Commands -### Recommended Settings +### Build -``` -# For balanced quality + speed (RECOMMENDED) -- Embed model: nomic-embed-text-v1.5 -- Chat model: qwen3.5-4b -- Draft model: (None for now โ€” incompatible with qwen3.5) -- Batch size: 16 -- Chunk size: 30 (code), 60 (docs) - -# For maximum quality (slower) -- Embed model: qwen3-embedding-4b -- Chat model: qwen3.5-4b -- Chunk size: 40 (all types) -- Expected indexing time: 20-40 minutes - -# For maximum speed (lower quality) -- Embed model: nomic-embed-text-v1.5 -- Chat model: qwen3.5-0.8b -- Smaller chunks, fewer retrieved results +```bash +wsl -d Ubuntu bash -c ". /root/.cargo/env && cd /mnt/c/rust-brain-sidecar && cargo build --release" ``` ---- +### Run -## Appendix: Complete Benchmark Results +Auto-started by brain plugin. Manual: +```bash +wsl -d Ubuntu bash -c "RUST_LOG=info /mnt/c/rust-brain-sidecar/target/release/brain-embed" ``` -=== EMBEDDING BENCHMARK === -nomic-embed (84MB, 768d): 1.0s avg (load 2.7s) โ† USE THIS -qwen3-embed (2458MB, 2560d): 38.9s avg (load 9.0s) -Speed ratio: nomic ~39ร— faster - -=== SPECULATIVE DECODING === -qwen3.5-4B + qwen3.5-0.8B: INCOMPATIBLE โŒ -gemma-4-e4B + gemma-4-e2B: ~1.23ร— speedup โœ… - -=== INDEXING PERFORMANCE === -Simple-Signage: 12,884 chunks โ†’ 12,346 unique, ~22 min (full) -CamControl: 7,505 chunks (pending) -myStockMaster: (pending) -Incremental re-index: seconds (not yet measured, expected <5 min) -``` \ No newline at end of file + +### Quick Start + +- [ ] LM Studio running at `http://192.168.1.12:1234` +- [ ] 3 models loaded: qwen3.5-4b, qwen3.5-0.8b, qwen3-0.6b-embed +- [ ] Rust sidecar built in WSL +- [ ] `"brain-plugin/brain.ts"` in opencode.json plugin array +- [ ] Run `brain_diagnostic` to verify +- [ ] Run `brain_index_project` to index +- [ ] Test: `brain_search "auth"` diff --git a/docs/new.md b/docs/new.md new file mode 100644 index 0000000..50da073 --- /dev/null +++ b/docs/new.md @@ -0,0 +1,546 @@ +Based on my research of the OpenCode ecosystem [^11^1^][^11^2^] and your project docs, here is the **complete enhancement plan** for your brain plugin. This goes beyond what you have and addresses the real gaps in orchestration, quality, and developer experience. + +--- + +## The 4 Pillars of Enhancement + +| Pillar | What It Means | Current State | Target | +|---|---|---|---| +| **Orchestration** | When to load what, how to parallelize, when to fallback | Basic model swap via v1 API | Predictive pre-warming, adaptive concurrency, multi-provider | +| **Quality** | Is the retrieved context actually helping? | Blind injection | Relevance feedback, A/B testing, token attribution | +| **Memory** | What persists across sessions | Per-session JSON | Long-term project memory, cross-session learning | +| **Ergonomics** | How the developer experiences it | Manual tools, no visibility | Auto-triggered, transparent, observable | + +--- + +## Phase 0: Critical Fixes (Do This Week) + +### 0.1 Windows Build โ€” Three Options + +| Option | Effort | Result | +|---|---|---| +| **A. WSL2** | 30 min | Run `cargo build` inside WSL2, copy binary to Windows. LM Studio on Windows talks to Rust sidecar on WSL2 via `localhost`. | +| **B. Cross-compile from Linux CI** | 2 hours | GitHub Actions builds `x86_64-pc-windows-gnu` target, releases `.exe` | +| **C. Strip dependencies** | 1 day | Remove `tree-sitter-*` crates, use regex-based chunking as fallback. Smaller binary, faster compile. | + +**Recommended:** Option A for immediate relief, Option B for distribution. + +### 0.2 Incremental Indexing + +Your docs say "not yet implemented." This is the #1 user pain point. + +**`src/indexer.rs` โ€” Add state tracking:** +```rust +use blake3::Hash; +use serde::{Serialize, Deserialize}; +use std::collections::HashMap; + +#[derive(Serialize, Deserialize)] +struct IndexState { + version: u32, + last_run: u64, // epoch ms + file_hashes: HashMap, // path โ†’ blake3(content) + chunk_count: usize, +} + +impl IndexState { + fn is_fresh(&self, path: &str, mtime: u64, content: &str) -> bool { + let hash = blake3::hash(content.as_bytes()); + match self.file_hashes.get(path) { + Some(old) => *old == hash, + None => false, + } + } +} +``` + +**Flow:** +1. Load `state.json` from `~/.brain//` +2. Walk files with `ignore` crate (respects `.gitignore`) +3. For each file: check `mtime` โ†’ if changed, compute `blake3` โ†’ if hash differs, re-chunk + re-embed +4. Merge new chunks into existing HNSW index (don't rebuild from scratch) +5. Save updated state + +**Expected result:** Re-index drops from 22 min โ†’ **<5 seconds** for unchanged projects. + +### 0.3 Query Result Cache + +```rust +use std::sync::Arc; +use tokio::sync::RwLock; +use lru::LruCache; + +struct QueryCache { + cache: Arc>>>, +} + +impl QueryCache { + async fn get(&self, query: &str) -> Option> { + let cache = self.cache.read().await; + cache.get(query).cloned() + } + + async fn put(&self, query: String, results: Vec) { + let mut cache = self.cache.write().await; + cache.put(query, results); + } +} +``` + +Invalidate on file watcher events. Cache hit rate should be **60โ€“80%** for repetitive queries. + +--- + +## Phase 1: Orchestration Intelligence (Next 2 Weeks) + +### 1.1 Predictive Model Pre-warming + +Currently you load the embed model **after** classifying intent. The user waits 2โ€“3 seconds. + +**Better:** Use `lsp.client.diagnostics` + `file.watcher.updated` to predict intent: + +```typescript +// brain.ts โ€” predictive pre-warming +"lsp.client.diagnostics": async (input, output) => { + const errors = input.diagnostics.filter(d => d.severity === "error"); + if (errors.length > 0) { + // User is about to ask about errors. Pre-warm embed model. + await rustSidecar.prewarm("embed"); + memory.predictedIntent = "debug"; + } +} +``` + +The Rust sidecar loads the model in background. When the user types "fix this," the model is already warm. + +### 1.2 Adaptive Concurrency Based on GPU Memory + +```rust +// lmstudio.rs โ€” query GPU memory before deciding concurrency +pub async fn get_gpu_memory(&self) -> anyhow::Result { + // LM Studio doesn't expose this directly, but we can infer from model load response + // Or: use nvidia-smi via shell if local + let output = tokio::process::Command::new("nvidia-smi") + .args(&["--query-gpu=memory.free", "--format=csv,noheader,nounits"]) + .output() + .await?; + let free_mb = String::from_utf8(output.stdout)?.trim().parse::()?; + Ok(free_mb * 1024 * 1024) // bytes +} + +pub fn optimal_concurrency(free_vram: u64, model_size: u64) -> usize { + // Leave 20% headroom + let usable = (free_vram as f64 * 0.8) as u64; + let slots = usable / model_size; + slots.min(8).max(1) as usize +} +``` + +### 1.3 Multi-Provider Abstraction + +Your current `lmstudio.rs` is hardcoded. Abstract it: + +```rust +pub trait EmbedProvider: Send + Sync { + async fn embed(&self, texts: &[String]) -> anyhow::Result>>; + async fn load(&self, model: &str) -> anyhow::Result; + async fn unload(&self, handle: ModelHandle) -> anyhow::Result<()>; +} + +pub struct LMStudioProvider { base_url: String } +pub struct OllamaProvider { base_url: String } +pub struct VLLMProvider { base_url: String } + +// Factory +pub fn create_provider(config: &ProviderConfig) -> Box { + match config.kind { + ProviderKind::LMStudio => Box::new(LMStudioProvider::new(config.url)), + ProviderKind::Ollama => Box::new(OllamaProvider::new(config.url)), + ProviderKind::VLLM => Box::new(VLLMProvider::new(config.url)), + } +} +``` + +This lets users with Ollama or vLLM use your plugin without LM Studio. + +--- + +## Phase 2: Advanced RAG (Next 2โ€“3 Weeks) + +### 2.1 Hybrid Search: Vector + BM25 + +Pure vector search misses exact keyword matches (e.g., function names). Add a lightweight inverted index: + +```rust +use tantivy::{Index, Schema, Document, Term}; +use tantivy::schema::{TEXT, STORED}; + +pub struct HybridSearcher { + hnsw: HnswStore, + tantivy: Index, +} + +impl HybridSearcher { + pub async fn search(&self, query: &str, query_vec: &[f32], top_k: usize) -> Vec { + // 1. Vector search + let vector_results = self.hnsw.search(query_vec, top_k); + + // 2. Keyword search + let keyword_results = self.tantivy_search(query, top_k); + + // 3. Reciprocal Rank Fusion (RRF) + let fused = reciprocal_rank_fusion(vector_results, keyword_results, k: 60); + fused.into_iter().take(top_k).collect() + } +} +``` + +`tantivy` is pure Rust, no extra dependencies. RRF formula: `score = ฮฃ 1/(k + rank)` for each result's rank in each list. + +### 2.2 Query Expansion with Interrogator + +You have the 0.8B interrogator model. Use it: + +```rust +pub async fn expand_query(&self, raw: &str) -> anyhow::Result { + let handle = self.load("catlilface/Qwen3.5-0.8B-interrogator-GGUF").await?; + + let expanded = self.chat(&handle, &[ + ("system", "You are a code search assistant. Expand the user's query into specific technical keywords."), + ("user", &format!("Query: {}\n\nExpanded search terms:", raw)) + ], max_tokens: 64).await?; + + self.unload(handle).await?; + Ok(expanded) +} +``` + +**When to expand:** `shouldExpandQuery()` from your decision tree โ€” short queries (< 5 words) or no code symbols detected. + +### 2.3 Multi-Hop Retrieval (Follow Dependencies) + +For "how does auth work?", find `auth.ts` โ†’ extract imports โ†’ search those too: + +```rust +pub async fn multi_hop_search(&self, query: &str, hops: usize) -> Vec { + let mut all_results = vec![]; + let mut frontier = vec![query.to_string()]; + + for hop in 0..hops { + let mut next_frontier = vec![]; + for q in frontier { + let results = self.hybrid_search(&q, &self.embed(&[&q]).await?[0], top_k: 5).await; + for r in &results { + // Extract imports from the chunk + let imports = extract_imports(&r.text); + next_frontier.extend(imports); + all_results.push(r.clone()); + } + } + frontier = next_frontier; + } + + // Deduplicate by path + all_results.sort_by_key(|r| r.path.clone()); + all_results.dedup_by_key(|r| r.path.clone()); + all_results +} +``` + +**When to use:** `feature` and `learn` intents with broad depth. + +--- + +## Phase 3: Quality & Feedback Loop (Critical for Learning) + +### 3.1 Token Attribution โ€” Did the LLM Use Our Context? + +OpenCode's `message.updated` hook lets you inject context. But did the LLM actually reference it? + +**Heuristic:** After the LLM responds, scan its output for: +- File paths from injected chunks +- Function names from injected chunks +- Similar code patterns + +```typescript +// brain.ts โ€” feedback loop +"message.updated": async (input, output) => { + // ... inject context as before ... + + // Store what we injected for later attribution + memory.lastInjectedChunks = context.chunks; +} + +// NEW HOOK: After LLM responds +"message.part.updated": async (input, output) => { + const response = input.part.content; + const usedChunks = memory.lastInjectedChunks.filter(chunk => + response.includes(chunk.path) || + response.includes(extract_function_name(chunk.text)) + ); + + // Record feedback + tree.recordFeedback({ + intent: memory.lastIntent, + strategy: memory.lastStrategy, + chunksInjected: memory.lastInjectedChunks.length, + chunksUsed: usedChunks.length, + responseLength: response.length + }); +} +``` + +### 3.2 Decision Tree Growth + +Currently your tree updates weights. Make it smarter: + +```rust +pub struct DecisionTree { + root: ScenarioNode, + feedback_log: Vec, +} + +impl DecisionTree { + pub fn grow(&mut self, feedback: FeedbackRecord) { + let node = self.find_node(&feedback.intent, &feedback.strategy); + + if feedback.chunks_used == 0 && feedback.chunks_injected > 0 { + // Total miss: context was irrelevant + node.weight *= 0.7; + + // Spawn child with stricter condition + if node.children.len() < 3 { + let child = ScenarioNode { + condition: refine_condition(&node.condition, feedback), + weight: 0.5, + visits: 1, + strategy: Strategy { + depth: go_deeper(&node.strategy.depth), + ..node.strategy.clone() + }, + ..Default::default() + }; + node.children.push(child); + } + } else if feedback.chunks_used >= feedback.chunks_injected / 2 { + // Good hit: context was useful + node.weight = (node.weight * (node.visits as f64) + 1.0) / ((node.visits + 1) as f64); + } + + node.visits += 1; + } +} +``` + +### 3.3 A/B Testing for Strategies + +Run two strategies in parallel, compare: + +```rust +pub async fn ab_test_search(&self, query: &str, strategy_a: Strategy, strategy_b: Strategy) -> (Vec, Vec) { + let (a, b) = tokio::join!( + self.search_with_strategy(query, strategy_a), + self.search_with_strategy(query, strategy_b) + ); + (a, b) +} +``` + +Store both results. After the LLM responds, the user (or a heuristic) picks which context was better. Update tree weights accordingly. + +--- + +## Phase 4: Cross-Session Memory (The Real Brain) + +This is where your plugin becomes truly unique. OpenCode sessions are stateless by default [^11^8^]. Your plugin can fix that. + +### 4.1 Project Memory Bank + +```rust +#[derive(Serialize, Deserialize)] +struct ProjectMemory { + project_id: String, + conventions: Vec, // "We use kebab-case for files" + decisions: Vec, // "Chose X over Y because Z" + common_queries: LruCache>, // Frequently asked + last_session_summary: String, +} + +impl ProjectMemory { + async fn load(project_id: &str) -> anyhow::Result { + let path = dirs::config_dir() + .unwrap() + .join("opencode") + .join("brain-memory") + .join(format!("{}.json", project_id)); + let content = tokio::fs::read_to_string(path).await?; + Ok(serde_json::from_str(&content)?) + } +} +``` + +### 4.2 Session Start Hook โ€” Inject Memory + +```typescript +// brain.ts โ€” session start +"session.created": async (input, output) => { + const memory = await rustSidecar.loadProjectMemory(project.id); + + // Inject into system prompt + output.additionalContext = ` +## Project Memory (from previous sessions) +${memory.conventions.map(c => `- ${c}`).join('\n')} + +## Recent Decisions +${memory.decisions.slice(-5).map(d => `- ${d.summary}`).join('\n')} + +## Current Status +${memory.last_session_summary} +`; +} +``` + +### 4.3 Session End โ€” Persist Memory + +```typescript +"session.idle": async (input, output) => { + const summary = await client.chat({ + model: "qwen3.5-4b", + messages: [ + { role: "system", content: "Summarize this session in 3 bullet points: what was done, what was decided, what's next." }, + { role: "user", content: JSON.stringify(memory.decisions) } + ] + }); + + await rustSidecar.saveProjectMemory({ + project_id: project.id, + last_session_summary: summary, + conventions: extract_conventions(summary), + decisions: memory.decisions + }); +} +``` + +This is inspired by `@vectorize-io/opencode-hindsight` [^11^8^], but entirely local via your Rust sidecar. + +--- + +## Phase 5: Observable & Debuggable + +### 5.1 Decision Log UI + +Add a tool that dumps the brain's internal state: + +```typescript +tool: { + brain_debug: tool({ + description: "Show the brain's decision log for this session", + args: {}, + async execute() { + return JSON.stringify({ + decisions: memory.decisions, + tree_weights: tree.get_weights(), + cache_stats: cache.stats(), + last_index_time: indexer.last_run() + }, null, 2); + } + }) +} +``` + +### 5.2 Metrics Endpoint + +```rust +// main.rs โ€” add /metrics endpoint +.route("/metrics", get(metrics)) + +async fn metrics() -> String { + format!( + "brain_decisions_total {}\n\ + brain_cache_hits {}\n\ + brain_cache_misses {}\n\ + brain_index_chunks {}\n\ + brain_search_duration_ms {}\n", + METRICS.decisions.load(Ordering::Relaxed), + METRICS.cache_hits.load(Ordering::Relaxed), + METRICS.cache_misses.load(Ordering::Relaxed), + METRICS.index_chunks.load(Ordering::Relaxed), + METRICS.search_duration_ms.load(Ordering::Relaxed), + ) +} +``` + +--- + +## The Implementation Priority + +| Priority | Feature | Effort | Impact | +|---|---|---|---| +| **P0** | Incremental indexing | 1 day | **10ร— faster re-index** | +| **P0** | Windows build (WSL2) | 30 min | Unblocks development | +| **P0** | Query cache | 2 hours | **60% cache hits** | +| **P1** | Predictive pre-warming | 1 day | Eliminates 2s load delay | +| **P1** | Hybrid search (BM25) | 2 days | Better exact matches | +| **P1** | Token attribution | 2 days | Enables learning loop | +| **P2** | Cross-session memory | 3 days | Unique differentiator | +| **P2** | Multi-provider | 2 days | Broader adoption | +| **P3** | Multi-hop retrieval | 2 days | Better architecture queries | +| **P3** | Metrics endpoint | 1 day | Production readiness | + +--- + +## The "Super Hidden Brain" Concept + +You asked for a hidden brain that works as a growing tree. Here is the refined architecture: + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ USER INTERFACE โ”‚ +โ”‚ User sees: normal OpenCode chat โ”‚ +โ”‚ โ”‚ +โ”‚ Hidden layer: โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ 1. SIGNAL GATHERING (silent) โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข LSP diagnostics โ†’ predict "debug" intent โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข File watcher โ†’ mark dirty, trigger re-index โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Git status โ†’ detect branch, recent commits โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Session history โ†’ load project memory โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ 2. DECISION ENGINE (growing tree) โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Classify intent (7 categories) โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Select strategy (depth, rerank, provider) โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข A/B test when uncertain โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Spawn children on failure โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ 3. ORCHESTRATION (invisible) โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Pre-warm models before user asks โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Swap provider if primary fails โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Parallel embed + search โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Cache results transparently โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ 4. INJECTION (seamless) โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Format context as natural code blocks โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Compress if too long โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Inject into system prompt, not user message โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ 5. FEEDBACK (background) โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Track if LLM used injected context โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Record success/failure โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Grow tree weights โ”‚ โ”‚ +โ”‚ โ”‚ โ€ข Persist to project memory โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +The user never sees "Brain is thinking..." or "Loading model..." It just feels like OpenCode **knows** their codebase better every day. + +--- + +## Next Steps for You + +1. **Today:** Fix Windows build via WSL2, verify Rust sidecar compiles +2. **This week:** Implement incremental indexing + query cache +3. **Next week:** Add token attribution + feedback loop +4. **Following week:** Cross-session memory + predictive pre-warming + +The core insight: **Your Rust sidecar is the competitive advantage.** Keep it lean, fast, and invisible. The TS plugin is the orchestrator. Together they create an experience no other OpenCode plugin has โ€” a brain that actually learns. \ No newline at end of file diff --git a/opencode.json b/opencode.json index 9ede9b2..83b82fd 100644 --- a/opencode.json +++ b/opencode.json @@ -3,7 +3,6 @@ "model": "inclusionai/ring-2.6-1t:free", "default_agent": "core-factory", "autoupdate": true, - "shell": "pwsh", "share": "manual", "enabled_providers": [ "opencode", @@ -134,7 +133,7 @@ "mcp-server-git" ], "enabled": true, - "timeout": 30000, + "timeout": 30000 }, "fetch": { "type": "local", @@ -164,7 +163,7 @@ "@modelcontextprotocol/server-sequential-thinking" ], "enabled": true, - "timeout": 60000 + "timeout": 45000 }, "language-server": { "type": "local", @@ -306,7 +305,15 @@ ".\\**": "allow", "~\\**": "ask" }, - "doom_loop": "deny" + "doom_loop": "deny", + "brain_diagnostic": "allow", + "brain_sidecar_status": "allow", + "brain_status": "allow", + "brain_metrics": "allow", + "brain_search": "allow", + "brain_embed_test": "allow", + "brain_index_project": "allow", + "brain_speculative_status": "allow" }, "tools": { "read": true, @@ -331,7 +338,15 @@ "sqlite": true, "sequential-thinking": true, "language-server": true, - "type-inject": true + "type-inject": true, + "brain_diagnostic": true, + "brain_sidecar_status": true, + "brain_status": true, + "brain_metrics": true, + "brain_search": true, + "brain_embed_test": true, + "brain_index_project": true, + "brain_speculative_status": true }, "lsp": { "typescript": { @@ -454,7 +469,9 @@ "Auto-format based on rules/auto-format.md.", "REASONING: Always use blocks before acting.", "ERROR HANDLING: If a tool fails, try one alternative before asking for help.", - "STYLE: Strict naming conventions and architectural patterns." + "STYLE: Strict naming conventions and architectural patterns.", + "BRAIN: For non-trivial codebase work, check brain_sidecar_status or brain_diagnostic, use brain_search/brain_embed_test for semantic discovery, then read the retrieved files directly.", + "BRAIN INDEX: If retrieval is empty or stale, run brain_index_project before relying on Brain context." ], "command": { "audit": { @@ -530,13 +547,15 @@ "description": "Primary build agent โ€” fast implementation, direct file editing, all tools enabled.", "instructions": [ "CORE: Fast implementation. Read โ†’ Analyze โ†’ Write โ†’ Validate.", + "BRAIN: Check brain_sidecar_status/brain_diagnostic when context quality matters; use brain_search or brain_embed_test before non-trivial analysis, debugging, refactors, features, and docs audits; read retrieved files before deciding.", "STRICT: No speculation. Only state what you know or can verify.", "CONCISE: Use minimal words. Long descriptions waste tokens.", "WORKFLOW: Read file โ†’ Use edit tool (oldStringโ†’newString) โ†’ Validate.", "If edit fails: re-read file, add context to oldString.", "No unnecessary comments. Reference lines as file_path:line_number.", "PROJECT STACK: Tauri (Rust), React (TypeScript), Laravel (PHP).", - "Auto-format after edits per rules/auto-format.md." + "Auto-format after edits per rules/auto-format.md.", + "BRAIN INDEX: Run brain_index_project when the index is empty, stale, or missing expected results; after broad edits, verify new context with brain_status or targeted brain_search." ], "permission": { "file": { @@ -553,6 +572,16 @@ "ls": "allow", "npm test*": "allow" } + }, + "tools": { + "brain_diagnostic": true, + "brain_sidecar_status": true, + "brain_status": true, + "brain_metrics": true, + "brain_search": true, + "brain_embed_test": true, + "brain_index_project": true, + "brain_speculative_status": true } }, "plan": { @@ -562,6 +591,20 @@ "permission": { "edit": "deny", "bash": "deny" + }, + "instructions": [ + "BRAIN: Check brain_sidecar_status/brain_diagnostic when context quality matters; use brain_search or brain_embed_test before non-trivial analysis, debugging, refactors, features, and docs audits; read retrieved files before deciding.", + "BRAIN INDEX: Run brain_index_project when the index is empty, stale, or missing expected results; after broad edits, verify new context with brain_status or targeted brain_search." + ], + "tools": { + "brain_diagnostic": true, + "brain_sidecar_status": true, + "brain_status": true, + "brain_metrics": true, + "brain_search": true, + "brain_embed_test": true, + "brain_index_project": true, + "brain_speculative_status": true } }, "explore": { @@ -572,6 +615,20 @@ "edit": "deny", "bash": "deny", "write": "deny" + }, + "instructions": [ + "BRAIN: Check brain_sidecar_status/brain_diagnostic when context quality matters; use brain_search or brain_embed_test before non-trivial analysis, debugging, refactors, features, and docs audits; read retrieved files before deciding.", + "BRAIN INDEX: Run brain_index_project when the index is empty, stale, or missing expected results; after broad edits, verify new context with brain_status or targeted brain_search." + ], + "tools": { + "brain_diagnostic": true, + "brain_sidecar_status": true, + "brain_status": true, + "brain_metrics": true, + "brain_search": true, + "brain_embed_test": true, + "brain_index_project": true, + "brain_speculative_status": true } }, "scout": { @@ -583,6 +640,20 @@ "bash": "ask", "webfetch": "allow", "websearch": "allow" + }, + "instructions": [ + "BRAIN: Check brain_sidecar_status/brain_diagnostic when context quality matters; use brain_search or brain_embed_test before non-trivial analysis, debugging, refactors, features, and docs audits; read retrieved files before deciding.", + "BRAIN INDEX: Run brain_index_project when the index is empty, stale, or missing expected results; after broad edits, verify new context with brain_status or targeted brain_search." + ], + "tools": { + "brain_diagnostic": true, + "brain_sidecar_status": true, + "brain_status": true, + "brain_metrics": true, + "brain_search": true, + "brain_embed_test": true, + "brain_index_project": true, + "brain_speculative_status": true } }, "core-factory": { @@ -600,7 +671,9 @@ "Auto-format after edits per rules/auto-format.md.", "SELF-EVOLUTION: After completing complex tasks, use skill:autoresearch to benchmark and optimize your implementation patterns.", "REFACTORING: When refactoring, first use skill:self-improver to analyze the codebase, then use skill:autoresearch to validate improvements.", - "INTEGRATION: Always check for redundant imports, unused code, and naming convention violations before and after edits." + "INTEGRATION: Always check for redundant imports, unused code, and naming convention violations before and after edits.", + "BRAIN: Check brain_sidecar_status/brain_diagnostic when context quality matters; use brain_search or brain_embed_test before non-trivial analysis, debugging, refactors, features, and docs audits; read retrieved files before deciding.", + "BRAIN INDEX: Run brain_index_project when the index is empty, stale, or missing expected results; after broad edits, verify new context with brain_status or targeted brain_search." ], "tools": { "read": true, @@ -613,7 +686,15 @@ "todowrite": true, "context7": true, "memory": true, - "sequential-thinking": true + "sequential-thinking": true, + "brain_diagnostic": true, + "brain_sidecar_status": true, + "brain_status": true, + "brain_metrics": true, + "brain_search": true, + "brain_embed_test": true, + "brain_index_project": true, + "brain_speculative_status": true }, "permission": { "file": { @@ -645,7 +726,9 @@ "Use skill:autoresearch for autonomous optimization loops on code, benchmarks, or metrics.", "Use skill:self-improver to analyze and improve project configuration.", "REALITY CHECK: Always assess feasibility against actual codebase state before proposing changes.", - "GAP ANALYSIS: Identify gaps between current implementation and target architecture." + "GAP ANALYSIS: Identify gaps between current implementation and target architecture.", + "BRAIN: Check brain_sidecar_status/brain_diagnostic when context quality matters; use brain_search or brain_embed_test before non-trivial analysis, debugging, refactors, features, and docs audits; read retrieved files before deciding.", + "BRAIN INDEX: Run brain_index_project when the index is empty, stale, or missing expected results; after broad edits, verify new context with brain_status or targeted brain_search." ], "tools": { "skill": true, @@ -657,7 +740,15 @@ "task": true, "memory": true, "context7": true, - "sequential-thinking": true + "sequential-thinking": true, + "brain_diagnostic": true, + "brain_sidecar_status": true, + "brain_status": true, + "brain_metrics": true, + "brain_search": true, + "brain_embed_test": true, + "brain_index_project": true, + "brain_speculative_status": true }, "permission": { "file": { @@ -684,7 +775,9 @@ "Use task tool for parallel execution when independent.", "Use skill:autoresearch for autonomous optimization loops.", "Use skill:self-improver to analyze and improve project configuration.", - "NORMING: Check all proposed patterns against existing codebase conventions before recommending." + "NORMING: Check all proposed patterns against existing codebase conventions before recommending.", + "BRAIN: Check brain_sidecar_status/brain_diagnostic when context quality matters; use brain_search or brain_embed_test before non-trivial analysis, debugging, refactors, features, and docs audits; read retrieved files before deciding.", + "BRAIN INDEX: Run brain_index_project when the index is empty, stale, or missing expected results; after broad edits, verify new context with brain_status or targeted brain_search." ], "tools": { "read": true, @@ -698,7 +791,15 @@ "mcp": true, "context7": true, "memory": true, - "sequential-thinking": true + "sequential-thinking": true, + "brain_diagnostic": true, + "brain_sidecar_status": true, + "brain_status": true, + "brain_metrics": true, + "brain_search": true, + "brain_embed_test": true, + "brain_index_project": true, + "brain_speculative_status": true } }, "frontend-ui-ux": { @@ -712,7 +813,9 @@ "Use ui-ux-pro-max skill for design tokens.", "Use parallel execution for independent components.", "Validate accessibility (WCAG AA) after implementation.", - "REUSABILITY: Check for existing components before creating new ones. Use react-reuse-audit skill." + "REUSABILITY: Check for existing components before creating new ones. Use react-reuse-audit skill.", + "BRAIN: Check brain_sidecar_status/brain_diagnostic when context quality matters; use brain_search or brain_embed_test before non-trivial analysis, debugging, refactors, features, and docs audits; read retrieved files before deciding.", + "BRAIN INDEX: Run brain_index_project when the index is empty, stale, or missing expected results; after broad edits, verify new context with brain_status or targeted brain_search." ], "tools": { "read": true, @@ -726,7 +829,15 @@ "mcp": true, "context7": true, "memory": true, - "sequential-thinking": true + "sequential-thinking": true, + "brain_diagnostic": true, + "brain_sidecar_status": true, + "brain_status": true, + "brain_metrics": true, + "brain_search": true, + "brain_embed_test": true, + "brain_index_project": true, + "brain_speculative_status": true } }, "backend-api": { @@ -739,7 +850,9 @@ "Use fullstack-dev skill for web patterns.", "Validate with LSP after changes.", "NO REDUNDANCY: Check for existing endpoints/services before implementing.", - "IMPORT AUDIT: Only add external imports when necessary. Prefer stdlib." + "IMPORT AUDIT: Only add external imports when necessary. Prefer stdlib.", + "BRAIN: Check brain_sidecar_status/brain_diagnostic when context quality matters; use brain_search or brain_embed_test before non-trivial analysis, debugging, refactors, features, and docs audits; read retrieved files before deciding.", + "BRAIN INDEX: Run brain_index_project when the index is empty, stale, or missing expected results; after broad edits, verify new context with brain_status or targeted brain_search." ], "tools": { "read": true, @@ -749,7 +862,15 @@ "skill": true, "lsp": true, "context7": true, - "memory": true + "memory": true, + "brain_diagnostic": true, + "brain_sidecar_status": true, + "brain_status": true, + "brain_metrics": true, + "brain_search": true, + "brain_embed_test": true, + "brain_index_project": true, + "brain_speculative_status": true } }, "backend-laravel": { @@ -763,7 +884,9 @@ "Use pest-testing for tests.", "Run php artisan pint after edits.", "IMPORT AUDIT: Check for unused imports after every file change.", - "NAMING: Follow Laravel conventions strictly (PascalCase models, snake_case tables)." + "NAMING: Follow Laravel conventions strictly (PascalCase models, snake_case tables).", + "BRAIN: Check brain_sidecar_status/brain_diagnostic when context quality matters; use brain_search or brain_embed_test before non-trivial analysis, debugging, refactors, features, and docs audits; read retrieved files before deciding.", + "BRAIN INDEX: Run brain_index_project when the index is empty, stale, or missing expected results; after broad edits, verify new context with brain_status or targeted brain_search." ], "tools": { "read": true, @@ -773,7 +896,15 @@ "skill": true, "lsp": true, "context7": true, - "memory": true + "memory": true, + "brain_diagnostic": true, + "brain_sidecar_status": true, + "brain_status": true, + "brain_metrics": true, + "brain_search": true, + "brain_embed_test": true, + "brain_index_project": true, + "brain_speculative_status": true } }, "backend-tauri": { @@ -791,7 +922,9 @@ "Always run rust-analyzer diagnostics before final edits.", "For React-Tauri bridging: use @tauri-apps/api packages, never direct DOM manipulation bypassing React.", "Refer to Tauri v2 documentation, not v1, unless specified.", - "IMPORT AUDIT: Check Cargo.toml for unused dependencies regularly." + "IMPORT AUDIT: Check Cargo.toml for unused dependencies regularly.", + "BRAIN: Check brain_sidecar_status/brain_diagnostic when context quality matters; use brain_search or brain_embed_test before non-trivial analysis, debugging, refactors, features, and docs audits; read retrieved files before deciding.", + "BRAIN INDEX: Run brain_index_project when the index is empty, stale, or missing expected results; after broad edits, verify new context with brain_status or targeted brain_search." ], "tools": { "read": true, @@ -801,7 +934,15 @@ "skill": true, "lsp": true, "context7": true, - "memory": true + "memory": true, + "brain_diagnostic": true, + "brain_sidecar_status": true, + "brain_status": true, + "brain_metrics": true, + "brain_search": true, + "brain_embed_test": true, + "brain_index_project": true, + "brain_speculative_status": true } }, "qa-guardian": { @@ -814,7 +955,9 @@ "CONCISE: Brief issues found. No lengthy reports.", "Run lint/test commands to validate changes.", "Never expose secrets in code.", - "REDUNDANCY CHECK: Flag duplicate code, unused imports, dead code paths." + "REDUNDANCY CHECK: Flag duplicate code, unused imports, dead code paths.", + "BRAIN: Check brain_sidecar_status/brain_diagnostic when context quality matters; use brain_search or brain_embed_test before non-trivial analysis, debugging, refactors, features, and docs audits; read retrieved files before deciding.", + "BRAIN INDEX: Run brain_index_project when the index is empty, stale, or missing expected results; after broad edits, verify new context with brain_status or targeted brain_search." ], "tools": { "read": true, @@ -822,7 +965,15 @@ "skill": true, "lsp": true, "context7": true, - "memory": true + "memory": true, + "brain_diagnostic": true, + "brain_sidecar_status": true, + "brain_status": true, + "brain_metrics": true, + "brain_search": true, + "brain_embed_test": true, + "brain_index_project": true, + "brain_speculative_status": true } }, "devops-engineer": { @@ -833,7 +984,9 @@ "DEVOPS: Terminal, MCP, system tasks.", "CONCISE: Brief execution. No explanation unless asked.", "Use bash with safety checks.", - "Handle db:init, clean, process:check." + "Handle db:init, clean, process:check.", + "BRAIN: Check brain_sidecar_status/brain_diagnostic when context quality matters; use brain_search or brain_embed_test before non-trivial analysis, debugging, refactors, features, and docs audits; read retrieved files before deciding.", + "BRAIN INDEX: Run brain_index_project when the index is empty, stale, or missing expected results; after broad edits, verify new context with brain_status or targeted brain_search." ], "tools": { "read": true, @@ -842,7 +995,15 @@ "bash": true, "skill": true, "context7": true, - "memory": true + "memory": true, + "brain_diagnostic": true, + "brain_sidecar_status": true, + "brain_status": true, + "brain_metrics": true, + "brain_search": true, + "brain_embed_test": true, + "brain_index_project": true, + "brain_speculative_status": true } }, "docs-curator": { @@ -856,7 +1017,9 @@ "Format with biome/prettier after edits.", "Use skill:autoresearch for autonomous research loops.", "Use skill:self-improver for system evolution.", - "REALITY CHECK: Verify docs match actual codebase before writing." + "REALITY CHECK: Verify docs match actual codebase before writing.", + "BRAIN: Check brain_sidecar_status/brain_diagnostic when context quality matters; use brain_search or brain_embed_test before non-trivial analysis, debugging, refactors, features, and docs audits; read retrieved files before deciding.", + "BRAIN INDEX: Run brain_index_project when the index is empty, stale, or missing expected results; after broad edits, verify new context with brain_status or targeted brain_search." ], "tools": { "read": true, @@ -871,7 +1034,15 @@ "todowrite": true, "memory": true, "context7": true, - "sequential-thinking": true + "sequential-thinking": true, + "brain_diagnostic": true, + "brain_sidecar_status": true, + "brain_status": true, + "brain_metrics": true, + "brain_search": true, + "brain_embed_test": true, + "brain_index_project": true, + "brain_speculative_status": true } } }, @@ -880,4 +1051,4 @@ "prune": true, "reserved": 8192 } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 0aa5567..0000000 --- a/package-lock.json +++ /dev/null @@ -1,3145 +0,0 @@ -{ - "name": "opencode-local", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "opencode-local", - "version": "1.0.0", - "dependencies": { - "@ai-sdk/openai-compatible": "^2.0.45", - "@nick-vi/opencode-type-inject": "latest", - "@opencode-ai/plugin": "1.3.17", - "@tarquinen/opencode-dcp": "latest", - "@zenobius/opencode-skillful": "latest", - "agentic-cli": "^0.1.9", - "ajv": "^8.17.1", - "opencode-background-agents": "^0.1.1", - "opencode-notify": "^0.3.1", - "yaml": "^2.8.3" - }, - "bin": { - "opencode": "opencode-launch.js" - }, - "devDependencies": { - "@biomejs/biome": "^2.4.8", - "@trivago/prettier-plugin-sort-imports": "^6.0.2", - "@types/bun": "^1.3.11", - "@types/node": "^22.19.17", - "prettier": "^3.8.1", - "prettier-plugin-tailwindcss": "^0.7.2", - "typescript": "^5.8.2", - "vitest": "^4.1.5" - } - }, - "node_modules/@ai-sdk/openai-compatible": { - "version": "2.0.45", - "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-2.0.45.tgz", - "integrity": "sha512-5YBvurNL7Oj7mT3srws4Rh4cQidoorfEGObAOb5jV40eld8IC7EkXWARZjnWYqgYzabUs6Sn6muiXfQVkgOyOQ==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.10", - "@ai-sdk/provider-utils": "4.0.26" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/provider": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.10.tgz", - "integrity": "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/provider-utils": { - "version": "4.0.26", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.26.tgz", - "integrity": "sha512-CsKNLKsOpvPujRlIYvoz+Ybw+kGn7J4/fIZa/58+R7iWLLfwn6ifE2G6Yq8K9XvH/I/3bzaDAJ3NhRwEMsLBKQ==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.10", - "@standard-schema/spec": "^1.1.0", - "eventsource-parser": "^3.0.8" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@anthropic-ai/tokenizer": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@anthropic-ai/tokenizer/-/tokenizer-0.0.4.tgz", - "integrity": "sha512-EHRKbxlxlc8W4KCBEseByJ7YwyYCmgu9OyN59H9+IYIGPoKv8tXyQXinkeGDI+cI8Tiuz9wk2jZb/kK7AyvL7g==", - "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.11.18", - "tiktoken": "^1.0.10" - } - }, - "node_modules/@anthropic-ai/tokenizer/node_modules/@types/node": { - "version": "18.19.130", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", - "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@anthropic-ai/tokenizer/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@biomejs/biome": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.14.tgz", - "integrity": "sha512-TmAvxOEgrpLypzVGJ8FulIZnlyA9TxrO1hyqYrCz9r+bwma9xXxuLA5IuYnj55XQneFx460KjRbx6SWGLkg3bQ==", - "dev": true, - "license": "MIT OR Apache-2.0", - "bin": { - "biome": "bin/biome" - }, - "engines": { - "node": ">=14.21.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/biome" - }, - "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.4.14", - "@biomejs/cli-darwin-x64": "2.4.14", - "@biomejs/cli-linux-arm64": "2.4.14", - "@biomejs/cli-linux-arm64-musl": "2.4.14", - "@biomejs/cli-linux-x64": "2.4.14", - "@biomejs/cli-linux-x64-musl": "2.4.14", - "@biomejs/cli-win32-arm64": "2.4.14", - "@biomejs/cli-win32-x64": "2.4.14" - } - }, - "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.14.tgz", - "integrity": "sha512-XvgoE9XOawUOQPdmvs4J7wPhi/DLwSCGks3AlPJDmh34O0awRTqCED1HRcRDdpf1Zrp4us4MGOOdIxNpbqNF5Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.14.tgz", - "integrity": "sha512-jE7hKBCFhOx3uUh+ZkWBfOHxAcILPfhFplNkuID/eZeSTLHzfZzoZxW8fbqY9xXRnPi7jGNAf1iPVR+0yWsM/Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.14.tgz", - "integrity": "sha512-2TELhZnW5RSLL063l9rc5xLpA0ZIw0Ccwy/0q384rvNAgFw3yI76bd59547yxowdQr5MNPET/xDLrLuvgSeeWQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.14.tgz", - "integrity": "sha512-/z+6gqAqqUQTHazwStxSXKHg9b8UvqBmDFRp+c4wYbq2KXhELQDon9EoC9RpmQ8JWkqQx/lIUy/cs+MhzDZp6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-x64": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.14.tgz", - "integrity": "sha512-zHrlQZDBDUz4OLAraYpWKcnLS6HOewBFWYOzY91d1ZjdqZwibOyb6BEu6WuWLugyo0P3riCmsbV9UqV1cSXwQg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.14.tgz", - "integrity": "sha512-R6BWgJdQOwW9ulJatuTVrQkjnODjqHZkKNOqb1sz++3Noe5LYd0i3PchnOBUCYAPHoPWHhjJqbdZlHEu0hpjdA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.14.tgz", - "integrity": "sha512-M3EH5hqOI/F/FUA2u4xcLoUgmxd218mvuj/6JL7Hv2toQvr2/AdOvKSpGkoRuWFCtQPVa+ZqkEV3Q5xBA9+XSA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-win32-x64": { - "version": "2.4.14", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.14.tgz", - "integrity": "sha512-WL0EG5qE+EAKomGXbf2g6VnSKJhTL3tXC0QRzWRwA5VpjxNYa6H4P7ZWfymbGE4IhZZQi1KXQ2R0YjwInmz2fA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@nick-vi/opencode-type-inject": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@nick-vi/opencode-type-inject/-/opencode-type-inject-1.5.2.tgz", - "integrity": "sha512-IveYT+D+DuYLVWTCXvo6Ed2DNZM2PWvZdoofkcPu9wpbQhFUPfciiCww6kG5qSe6fATL8AhaeniWfVpY84CW+w==", - "dependencies": { - "@nick-vi/type-inject-core": "^1.1.2" - }, - "peerDependencies": { - "@opencode-ai/plugin": ">=1.0.0", - "svelte": "^5.0.0", - "typescript": "^5" - }, - "peerDependenciesMeta": { - "svelte": { - "optional": true - } - } - }, - "node_modules/@nick-vi/type-inject-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@nick-vi/type-inject-core/-/type-inject-core-1.1.2.tgz", - "integrity": "sha512-9iJ9NjU+leBDmz7XoHE77nt1H7ZejjrUVPVTevQbW5BVtL7WOyEVSXfClC8xZAUHCpuf5/Ol4ExYVPdSoE25Yw==", - "dependencies": { - "ts-morph": "^27.0.2" - }, - "peerDependencies": { - "svelte": "^5.0.0", - "typescript": "^5" - }, - "peerDependenciesMeta": { - "svelte": { - "optional": true - } - } - }, - "node_modules/@opencode-ai/plugin": { - "version": "1.3.17", - "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.3.17.tgz", - "integrity": "sha512-N5lckFtYvEu2R8K1um//MIOTHsJHniF2kHoPIWPCrxKG5Jpismt1ISGzIiU3aKI2ht/9VgcqKPC5oZFLdmpxPw==", - "license": "MIT", - "dependencies": { - "@opencode-ai/sdk": "1.3.17", - "zod": "4.1.8" - }, - "peerDependencies": { - "@opentui/core": ">=0.1.96", - "@opentui/solid": ">=0.1.96" - }, - "peerDependenciesMeta": { - "@opentui/core": { - "optional": true - }, - "@opentui/solid": { - "optional": true - } - } - }, - "node_modules/@opencode-ai/plugin/node_modules/@opencode-ai/sdk": { - "version": "1.3.17", - "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.3.17.tgz", - "integrity": "sha512-2+MGgu7wynqTBwxezR01VAGhILXlpcHDY/pF7SWB87WOgLt3kD55HjKHNj6PWxyY8n575AZolR95VUC3gtwfmA==", - "license": "MIT", - "dependencies": { - "cross-spawn": "7.0.6" - } - }, - "node_modules/@opencode-ai/sdk": { - "version": "1.14.31", - "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.14.31.tgz", - "integrity": "sha512-QaV+ti3NYUITmgIDqtNMqGIYBXJOx2zheN1g+7w4HC8QQsbaW1c7glxXExQHRbdUzcQPP2vUQhnXOcEsTw5CcQ==", - "license": "MIT", - "dependencies": { - "cross-spawn": "7.0.6" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.128.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.128.0.tgz", - "integrity": "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.18.tgz", - "integrity": "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.18.tgz", - "integrity": "sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.18.tgz", - "integrity": "sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.18.tgz", - "integrity": "sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.18.tgz", - "integrity": "sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.18.tgz", - "integrity": "sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.18.tgz", - "integrity": "sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.18.tgz", - "integrity": "sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.18.tgz", - "integrity": "sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.18.tgz", - "integrity": "sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.18.tgz", - "integrity": "sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.18.tgz", - "integrity": "sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.18.tgz", - "integrity": "sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.18.tgz", - "integrity": "sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.18.tgz", - "integrity": "sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.18.tgz", - "integrity": "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@stacksjs/clapp": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@stacksjs/clapp/-/clapp-0.2.0.tgz", - "integrity": "sha512-dSqnbeZjXnQLLvVxC5NU7D9Vpjxc6cC9Bo2ZwaqjgruK7pbVoFCI0goc9Mtf/lfSTbTx6Uvv/mbY7+cOW/j3Og==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "mri": "^1.2.0", - "wrap-ansi": "^9.0.0" - }, - "bin": { - "clapp": "dist/bin/cli.js" - } - }, - "node_modules/@stacksjs/clarity": { - "version": "0.3.28", - "resolved": "https://registry.npmjs.org/@stacksjs/clarity/-/clarity-0.3.28.tgz", - "integrity": "sha512-SElzGeDewEbp0c4C8PTfdvi/Xtr1HGAjG6b0prHvkVxyYGM+2R0BFU0otUmy+DbR7wsfZ4rNU3YN1+JK/CzoTw==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "bunfig": "^0.15.6" - }, - "bin": { - "clarity": "dist/bin/cli.js" - } - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, - "node_modules/@tarquinen/opencode-dcp": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@tarquinen/opencode-dcp/-/opencode-dcp-3.1.9.tgz", - "integrity": "sha512-oo82YRJSCMw8Q8LOM/55qw4gzo/ylfsAw+R57/9ch5W8N5iG2R7O6JGtTl5uk+ahS4K8tevVVWMf7+eFkUQYhQ==", - "license": "AGPL-3.0-or-later", - "dependencies": { - "@anthropic-ai/tokenizer": "^0.0.4", - "@opencode-ai/sdk": "^1.3.2", - "jsonc-parser": "^3.3.1", - "zod": "^4.3.6" - }, - "peerDependencies": { - "@opencode-ai/plugin": ">=1.2.0" - } - }, - "node_modules/@tarquinen/opencode-dcp/node_modules/zod": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.1.tgz", - "integrity": "sha512-a6ENMBBGZBsnlSebQ/eKCguSBeGKSf4O7BPnqVPmYGtpBYI7VSqoVqw+QcB7kPRjbqPwhYTpFbVj/RqNz/CT0Q==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@trivago/prettier-plugin-sort-imports": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-6.0.2.tgz", - "integrity": "sha512-3DgfkukFyC/sE/VuYjaUUWoFfuVjPK55vOFDsxD56XXynFMCZDYFogH2l/hDfOsQAm1myoU/1xByJ3tWqtulXA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@babel/generator": "^7.28.0", - "@babel/parser": "^7.28.0", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", - "javascript-natural-sort": "^0.7.1", - "lodash-es": "^4.17.21", - "minimatch": "^9.0.0", - "parse-imports-exports": "^0.2.4" - }, - "engines": { - "node": ">= 20" - }, - "peerDependencies": { - "@vue/compiler-sfc": "3.x", - "prettier": "2.x - 3.x", - "prettier-plugin-ember-template-tag": ">= 2.0.0", - "prettier-plugin-svelte": "3.x", - "svelte": "4.x || 5.x" - }, - "peerDependenciesMeta": { - "@vue/compiler-sfc": { - "optional": true - }, - "prettier-plugin-ember-template-tag": { - "optional": true - }, - "prettier-plugin-svelte": { - "optional": true - }, - "svelte": { - "optional": true - } - } - }, - "node_modules/@ts-morph/common": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.28.1.tgz", - "integrity": "sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g==", - "license": "MIT", - "dependencies": { - "minimatch": "^10.0.1", - "path-browserify": "^1.0.1", - "tinyglobby": "^0.2.14" - } - }, - "node_modules/@ts-morph/common/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@ts-morph/common/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@ts-morph/common/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/bun": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.13.tgz", - "integrity": "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "bun-types": "1.3.13" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.19.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", - "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", - "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", - "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.5", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", - "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", - "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.5", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", - "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.5", - "@vitest/utils": "4.1.5", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", - "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", - "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.5", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@xan105/error": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@xan105/error/-/error-1.7.1.tgz", - "integrity": "sha512-FYIUYijbmg62L9QQ5BsR14DuZatAJJvwo10yzuEDEsu8L0FAa4DZivWFAf66nYjEiSz50SFGtMxWSyG+y/ZGGw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/xan105" - }, - { - "type": "paypal", - "url": "https://www.paypal.me/xan105" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/xan105" - } - ], - "license": "MIT", - "optional": true, - "engines": { - "node": ">=20.11.0" - } - }, - "node_modules/@xan105/fs": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@xan105/fs/-/fs-2.3.0.tgz", - "integrity": "sha512-9ibTdG8ZFYBn6rdBL3Dq+BikN2V1zxNXRGxWUUsBzA/zXazPSrEs4bURzURv5m6z+o6VzbKEu+prtLefJTntDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/xan105" - }, - { - "type": "paypal", - "url": "https://www.paypal.me/xan105" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/xan105" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@xan105/error": "^1.7.1", - "@xan105/is": "^2.10.1" - }, - "engines": { - "node": ">=20.18.0" - } - }, - "node_modules/@xan105/is": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@xan105/is/-/is-2.10.1.tgz", - "integrity": "sha512-FKgDE2tSyJwvf+zIeAsN/f8l94CYhhu5LtkLhv/gYX1i5DnfGdCys0N+mxxTb1qMhWPw+ekHGcwflv0x8ATPJQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/xan105" - }, - { - "type": "paypal", - "url": "https://www.paypal.me/xan105" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/xan105" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@xan105/error": "^1.7.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@zenobius/opencode-skillful": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@zenobius/opencode-skillful/-/opencode-skillful-1.2.5.tgz", - "integrity": "sha512-CSl/6FgcXGf2aiJvcFPHE07KkHclVOdvI5ldnFc1Ejy8YGQBG3hjkPIqkrmr4Py1h4q8zqUlhyBP6I9bVSxBgw==", - "dependencies": { - "@opencode-ai/plugin": "1.0.85", - "bunfig": "^0.15.6", - "dedent": "^1.7.1", - "gray-matter": "^4.0.3", - "mime": "^4.1.0", - "ramda": "^0.30.0", - "search-string": "^3.1.0", - "zod": "^3.24.2" - } - }, - "node_modules/@zenobius/opencode-skillful/node_modules/@opencode-ai/plugin": { - "version": "1.0.85", - "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.0.85.tgz", - "integrity": "sha512-4uU15occ/rZALIvDk3VwKdZwUZVAcNEcBZNJQKKMX+Lp+sQ/YvHKqawwz1ktOIS2UDsCKh+gyvV8fRCuUBEdKQ==", - "dependencies": { - "@opencode-ai/sdk": "1.0.85", - "zod": "4.1.8" - } - }, - "node_modules/@zenobius/opencode-skillful/node_modules/@opencode-ai/plugin/node_modules/zod": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", - "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@zenobius/opencode-skillful/node_modules/@opencode-ai/sdk": { - "version": "1.0.85", - "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.0.85.tgz", - "integrity": "sha512-7qVa7Psu7dFQBI1aMeQZXRho/6QMebHS2klNRaYO8b+n4jMUg20HEBIMFw5S1B2RF00+DFiTDxbbdByfirRNtg==" - }, - "node_modules/@zenobius/opencode-skillful/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/agentic-cli": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/agentic-cli/-/agentic-cli-0.1.9.tgz", - "integrity": "sha512-qDynxGqOBMK7VEKWuR5m/cOi1V79jvLfw75aoululeZjVT9hJ7aJr0zRgBvmKLhhH0PQdCWk2qj0BlwMEwVQWg==", - "license": "MIT", - "bin": { - "agentic": "bin/agentic" - }, - "optionalDependencies": { - "agentic-darwin-arm64": "0.1.9", - "agentic-darwin-x64": "0.1.9", - "agentic-linux-arm64": "0.1.9", - "agentic-linux-x64": "0.1.9" - } - }, - "node_modules/agentic-darwin-arm64": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/agentic-darwin-arm64/-/agentic-darwin-arm64-0.1.9.tgz", - "integrity": "sha512-ig/qiB7EKmWkK+TUBRLQTK6+RTgNprHSLZDonRKdzIqI5dkE01OrMjc7fAoNQFAqA8Iq9bOZg+RKuZM3h+Uz8A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/agentic-darwin-x64": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/agentic-darwin-x64/-/agentic-darwin-x64-0.1.9.tgz", - "integrity": "sha512-OPGLdnWzmuczu+PYkEqFspXHUD35/db5AitkYk2djupHd/Kk6pLgvTCi52rdOvP+SQ5iUOHpH8W+iaXO+fyxVA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/agentic-linux-arm64": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/agentic-linux-arm64/-/agentic-linux-arm64-0.1.9.tgz", - "integrity": "sha512-FF5IiFQv7w8UGliiV5ZrCpCd2xUcwiPcC4vFqC8y+44ft7lMO9ppEbfXKz33YFVZVeW0DozLC20WC0uiGK7hHQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/agentic-linux-x64": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/agentic-linux-x64/-/agentic-linux-x64-0.1.9.tgz", - "integrity": "sha512-qGd/QoQLw5xE5SwKZWBdv7HkQjEDqVAUwweFF1YcHHmpi53H3tZ6IyRd99r+i55iO9uTCLCqNSCJLOZEmrLa7Q==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/bun-types": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.13.tgz", - "integrity": "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/bunfig": { - "version": "0.15.9", - "resolved": "https://registry.npmjs.org/bunfig/-/bunfig-0.15.9.tgz", - "integrity": "sha512-DXTYgZ9DobbX4pEec+fCpEcxKTd+ggZRMCaazJ4R5Mn2ZcYiZOg2JhtLbvSidbg2x3cX+tm/WM1LfexjRSpYbg==", - "license": "MIT", - "dependencies": { - "@stacksjs/clapp": "^0.2.0", - "@stacksjs/clarity": "^0.3.24" - }, - "bin": { - "bunfig": "bin/cli.js" - } - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/code-block-writer": { - "version": "13.0.3", - "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", - "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", - "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-terminal": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/detect-terminal/-/detect-terminal-1.1.2.tgz", - "integrity": "sha512-mLB+qvOc9VYO0VqLNbqcLjb9R9AvbWQyk9n8qx0aryoykE4ALX0FNp/eP8GE2RxppYBofsoKxfJ3L15X2/rJ8Q==", - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, - "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", - "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gray-matter": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", - "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", - "license": "MIT", - "dependencies": { - "js-yaml": "^3.13.1", - "kind-of": "^6.0.2", - "section-matter": "^1.0.0", - "strip-bom-string": "^1.0.0" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==", - "license": "MIT", - "optional": true - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "license": "MIT", - "optional": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "license": "MIT", - "optional": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" - }, - "node_modules/javascript-natural-sort": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", - "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "license": "MIT" - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lodash-es": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", - "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", - "dev": true, - "license": "MIT" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/mime": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", - "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", - "funding": [ - "https://github.com/sponsors/broofa" - ], - "license": "MIT", - "bin": { - "mime": "bin/cli.js" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-notifier": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-10.0.1.tgz", - "integrity": "sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.5", - "shellwords": "^0.1.1", - "uuid": "^8.3.2", - "which": "^2.0.2" - } - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/opencode-background-agents": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/opencode-background-agents/-/opencode-background-agents-0.1.1.tgz", - "integrity": "sha512-5uqepaZBhvlFFKGOYnwfP/uC5E3/Trq/Cj0QFzNAITxOEzMw+FKeISzuKr47VPoE0qBwIO2hUJyTxkIMfMS0FA==", - "license": "MIT", - "dependencies": { - "@opencode-ai/plugin": "^1.2.25", - "@opencode-ai/sdk": "^1.2.25" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/opencode-notify": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/opencode-notify/-/opencode-notify-0.3.1.tgz", - "integrity": "sha512-ciKLEswpmR8itz3dMPbXmzvxZLfzOLc2KTbhdVMJWg/Fki/VnRgVErYU7zj1ZHT2qj4Qdz8ch85w4MZA93grDQ==", - "license": "MIT", - "dependencies": { - "detect-terminal": "^1.0.0" - }, - "optionalDependencies": { - "node-dbus-notifier": "^1.0.0", - "node-notifier": "^10.0.1", - "powertoast": "^3.0.0" - }, - "peerDependencies": { - "@opencode-ai/plugin": "*", - "@opencode-ai/sdk": "*" - } - }, - "node_modules/opencode-notify/node_modules/node-dbus-notifier": { - "optional": true - }, - "node_modules/parse-imports-exports": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", - "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-statements": "1.0.11" - } - }, - "node_modules/parse-statements": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", - "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "license": "MIT" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/powertoast": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/powertoast/-/powertoast-3.0.0.tgz", - "integrity": "sha512-nlGnBkMchJS6P4uQgjROpzHs72WqgB6cYP7hDFACXOZUSu2lXgDqJ5fKt2B/53jO+UynVlbhAQHHQvSOLUAUmQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/xan105" - }, - { - "type": "paypal", - "url": "https://www.paypal.me/xan105" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/xan105" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@xan105/error": "^1.7.0", - "@xan105/fs": "^2.2.1", - "@xan105/is": "^2.9.3" - }, - "engines": { - "node": ">=20.11.0" - }, - "peerDependencies": { - "@nodert-win10-19h1/windows.data.xml.dom": "^0.1.6", - "@nodert-win10-19h1/windows.foundation": "^0.1.6", - "@nodert-win10-19h1/windows.foundation.collections": "^0.1.6", - "@nodert-win10-19h1/windows.ui.notifications": "^0.1.6", - "@nodert-win10-20h1/windows.data.xml.dom": "^0.1.0", - "@nodert-win10-20h1/windows.foundation": "^0.1.0", - "@nodert-win10-20h1/windows.foundation.collections": "^0.1.0", - "@nodert-win10-20h1/windows.ui.notifications": "^0.1.0", - "@nodert-win10-21h1/windows.data.xml.dom": "^0.1.6", - "@nodert-win10-21h1/windows.foundation": "^0.1.6", - "@nodert-win10-21h1/windows.foundation.collections": "^0.1.6", - "@nodert-win10-21h1/windows.ui.notifications": "^0.1.6", - "@nodert-win10-au/windows.data.xml.dom": "^0.4.4", - "@nodert-win10-au/windows.foundation": "^0.4.4", - "@nodert-win10-au/windows.foundation.collections": "^0.4.4", - "@nodert-win10-au/windows.ui.notifications": "^0.4.4", - "@nodert-win10-cu/windows.data.xml.dom": "^0.4.4", - "@nodert-win10-cu/windows.foundation": "^0.4.4", - "@nodert-win10-cu/windows.foundation.collections": "^0.4.4", - "@nodert-win10-cu/windows.ui.notifications": "^0.4.4", - "@nodert-win10-rs3/windows.data.xml.dom": "^0.4.4", - "@nodert-win10-rs3/windows.foundation": "^0.4.4", - "@nodert-win10-rs3/windows.foundation.collections": "^0.4.4", - "@nodert-win10-rs3/windows.ui.notifications": "^0.4.4", - "@nodert-win10-rs4/windows.data.xml.dom": "^0.4.4", - "@nodert-win10-rs4/windows.foundation": "^0.4.4", - "@nodert-win10-rs4/windows.foundation.collections": "^0.4.4", - "@nodert-win10-rs4/windows.ui.notifications": "^0.4.4", - "@nodert-win10/windows.data.xml.dom": "^0.2.96", - "@nodert-win10/windows.foundation": "^0.2.96", - "@nodert-win10/windows.foundation.collections": "^0.2.96", - "@nodert-win10/windows.ui.notifications": "^0.2.96", - "@nodert-win11-22h2/windows.data.xml.dom": "^0.1.6", - "@nodert-win11-22h2/windows.foundation": "^0.1.6", - "@nodert-win11-22h2/windows.foundation.collections": "^0.1.6", - "@nodert-win11-22h2/windows.ui.notifications": "^0.1.6", - "@nodert-win11/windows.data.xml.dom": "^0.1.6", - "@nodert-win11/windows.foundation": "^0.1.6", - "@nodert-win11/windows.foundation.collections": "^0.1.6", - "@nodert-win11/windows.ui.notifications": "^0.1.6", - "@xan105/nodert": "^2.0.0" - }, - "peerDependenciesMeta": { - "@nodert-win10-19h1/windows.data.xml.dom": { - "optional": true - }, - "@nodert-win10-19h1/windows.foundation": { - "optional": true - }, - "@nodert-win10-19h1/windows.foundation.collections": { - "optional": true - }, - "@nodert-win10-19h1/windows.ui.notifications": { - "optional": true - }, - "@nodert-win10-20h1/windows.data.xml.dom": { - "optional": true - }, - "@nodert-win10-20h1/windows.foundation": { - "optional": true - }, - "@nodert-win10-20h1/windows.foundation.collections": { - "optional": true - }, - "@nodert-win10-20h1/windows.ui.notifications": { - "optional": true - }, - "@nodert-win10-21h1/windows.data.xml.dom": { - "optional": true - }, - "@nodert-win10-21h1/windows.foundation": { - "optional": true - }, - "@nodert-win10-21h1/windows.foundation.collections": { - "optional": true - }, - "@nodert-win10-21h1/windows.ui.notifications": { - "optional": true - }, - "@nodert-win10-au/windows.data.xml.dom": { - "optional": true - }, - "@nodert-win10-au/windows.foundation": { - "optional": true - }, - "@nodert-win10-au/windows.foundation.collections": { - "optional": true - }, - "@nodert-win10-au/windows.ui.notifications": { - "optional": true - }, - "@nodert-win10-cu/windows.data.xml.dom": { - "optional": true - }, - "@nodert-win10-cu/windows.foundation": { - "optional": true - }, - "@nodert-win10-cu/windows.foundation.collections": { - "optional": true - }, - "@nodert-win10-cu/windows.ui.notifications": { - "optional": true - }, - "@nodert-win10-rs3/windows.data.xml.dom": { - "optional": true - }, - "@nodert-win10-rs3/windows.foundation": { - "optional": true - }, - "@nodert-win10-rs3/windows.foundation.collections": { - "optional": true - }, - "@nodert-win10-rs3/windows.ui.notifications": { - "optional": true - }, - "@nodert-win10-rs4/windows.data.xml.dom": { - "optional": true - }, - "@nodert-win10-rs4/windows.foundation": { - "optional": true - }, - "@nodert-win10-rs4/windows.foundation.collections": { - "optional": true - }, - "@nodert-win10-rs4/windows.ui.notifications": { - "optional": true - }, - "@nodert-win10/windows.data.xml.dom": { - "optional": true - }, - "@nodert-win10/windows.foundation": { - "optional": true - }, - "@nodert-win10/windows.foundation.collections": { - "optional": true - }, - "@nodert-win10/windows.ui.notifications": { - "optional": true - }, - "@nodert-win11-22h2/windows.data.xml.dom": { - "optional": true - }, - "@nodert-win11-22h2/windows.foundation": { - "optional": true - }, - "@nodert-win11-22h2/windows.foundation.collections": { - "optional": true - }, - "@nodert-win11-22h2/windows.ui.notifications": { - "optional": true - }, - "@nodert-win11/windows.data.xml.dom": { - "optional": true - }, - "@nodert-win11/windows.foundation": { - "optional": true - }, - "@nodert-win11/windows.foundation.collections": { - "optional": true - }, - "@nodert-win11/windows.ui.notifications": { - "optional": true - }, - "@xan105/nodert": { - "optional": true - } - } - }, - "node_modules/prettier": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", - "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-tailwindcss": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.7.4.tgz", - "integrity": "sha512-UKii4RjY05SNt/WQi6/NcOn/LsT0/ILLXsxygjbRg5/YZelsSu5jTqorYHPDGq4nZy5q5hpCu+XdGZ1xaJEQgw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.19" - }, - "peerDependencies": { - "@ianvs/prettier-plugin-sort-imports": "*", - "@prettier/plugin-hermes": "*", - "@prettier/plugin-oxc": "*", - "@prettier/plugin-pug": "*", - "@shopify/prettier-plugin-liquid": "*", - "@trivago/prettier-plugin-sort-imports": "*", - "@zackad/prettier-plugin-twig": "*", - "prettier": "^3.0", - "prettier-plugin-astro": "*", - "prettier-plugin-css-order": "*", - "prettier-plugin-jsdoc": "*", - "prettier-plugin-marko": "*", - "prettier-plugin-multiline-arrays": "*", - "prettier-plugin-organize-attributes": "*", - "prettier-plugin-organize-imports": "*", - "prettier-plugin-sort-imports": "*", - "prettier-plugin-svelte": "*" - }, - "peerDependenciesMeta": { - "@ianvs/prettier-plugin-sort-imports": { - "optional": true - }, - "@prettier/plugin-hermes": { - "optional": true - }, - "@prettier/plugin-oxc": { - "optional": true - }, - "@prettier/plugin-pug": { - "optional": true - }, - "@shopify/prettier-plugin-liquid": { - "optional": true - }, - "@trivago/prettier-plugin-sort-imports": { - "optional": true - }, - "@zackad/prettier-plugin-twig": { - "optional": true - }, - "prettier-plugin-astro": { - "optional": true - }, - "prettier-plugin-css-order": { - "optional": true - }, - "prettier-plugin-jsdoc": { - "optional": true - }, - "prettier-plugin-marko": { - "optional": true - }, - "prettier-plugin-multiline-arrays": { - "optional": true - }, - "prettier-plugin-organize-attributes": { - "optional": true - }, - "prettier-plugin-organize-imports": { - "optional": true - }, - "prettier-plugin-sort-imports": { - "optional": true - }, - "prettier-plugin-svelte": { - "optional": true - } - } - }, - "node_modules/ramda": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", - "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rolldown": { - "version": "1.0.0-rc.18", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz", - "integrity": "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.128.0", - "@rolldown/pluginutils": "1.0.0-rc.18" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.18", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.18", - "@rolldown/binding-darwin-x64": "1.0.0-rc.18", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.18", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.18", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.18", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.18", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.18", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.18", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.18", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.18", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.18", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.18", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.18", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18" - } - }, - "node_modules/search-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/search-string/-/search-string-3.1.0.tgz", - "integrity": "sha512-yY3b0VlaXfKi2B//34PN5AFF+GQvwme6Kj4FjggmoSBOa7B8AHfS1nYZbsrYu+IyGeYOAkF8ywL9LN9dkrOo6g==", - "license": "MIT" - }, - "node_modules/section-matter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", - "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "license": "MIT", - "optional": true - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tiktoken": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.22.tgz", - "integrity": "sha512-PKvy1rVF1RibfF3JlXBSP0Jrcw2uq3yXdgcEXtKTYn3QJ/cBRBHDnrJ5jHky+MENZ6DIPwNUGWpkVx+7joCpNA==", - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", - "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/ts-morph": { - "version": "27.0.2", - "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-27.0.2.tgz", - "integrity": "sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w==", - "license": "MIT", - "dependencies": { - "@ts-morph/common": "~0.28.1", - "code-block-writer": "^13.0.3" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", - "license": "MIT", - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vite": { - "version": "8.0.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.11.tgz", - "integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.14", - "rolldown": "1.0.0-rc.18", - "tinyglobby": "^0.2.16" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitest": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", - "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.5", - "@vitest/mocker": "4.1.5", - "@vitest/pretty-format": "4.1.5", - "@vitest/runner": "4.1.5", - "@vitest/snapshot": "4.1.5", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.5", - "@vitest/browser-preview": "4.1.5", - "@vitest/browser-webdriverio": "4.1.5", - "@vitest/coverage-istanbul": "4.1.5", - "@vitest/coverage-v8": "4.1.5", - "@vitest/ui": "4.1.5", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/yaml": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", - "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/zod": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", - "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/plugins/__tests__/agent-router.test.ts b/plugins/__tests__/agent-router.test.ts deleted file mode 100644 index f3f87b8..0000000 --- a/plugins/__tests__/agent-router.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { describe, it, expect } from "vitest"; - -describe("AgentRouterPlugin", () => { - it("should load agent-router module without errors", async () => { - const mod = await import("../agent-router"); - expect(mod.default).toBeDefined(); - expect(typeof mod.default).toBe("function"); - }); - - it("should return plugin hooks when initialized with context", async () => { - const mod = await import("../agent-router"); - const plugin = mod.default; - const mockContext = { - client: {} as any, - project: { path: process.cwd() }, - directory: process.cwd(), - }; - const instance = await plugin(mockContext); - expect(instance).toBeDefined(); - expect(typeof instance).toBe("object"); - }); - - it("should have chat.message hook when initialized", async () => { - const mod = await import("../agent-router"); - const plugin = mod.default; - const mockContext = { - client: {} as any, - project: { path: process.cwd() }, - directory: process.cwd(), - }; - const instance = await plugin(mockContext); - expect(instance["chat.message"]).toBeDefined(); - expect(typeof instance["chat.message"]).toBe("function"); - }); - - it("should expose route_agent tool", async () => { - const mod = await import("../agent-router"); - const plugin = mod.default; - const mockContext = { - client: {} as any, - project: { path: process.cwd() }, - directory: process.cwd(), - }; - const instance = await plugin(mockContext); - expect(instance.tool).toBeDefined(); - expect(instance.tool.route_agent).toBeDefined(); - }); - - it("should expose auto_route tool", async () => { - const mod = await import("../agent-router"); - const plugin = mod.default; - const mockContext = { - client: {} as any, - project: { path: process.cwd() }, - directory: process.cwd(), - }; - const instance = await plugin(mockContext); - expect(instance.tool).toBeDefined(); - expect(instance.tool.auto_route).toBeDefined(); - }); -}); diff --git a/plugins/__tests__/config-validation.test.ts b/plugins/__tests__/config-validation.test.ts deleted file mode 100644 index 943dd47..0000000 --- a/plugins/__tests__/config-validation.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { existsSync, readFileSync } from "node:fs"; -import { join } from "node:path"; - -describe("Configuration Validation", () => { - const configPath = join(process.cwd(), "opencode.json"); - const schemaPath = join(process.cwd(), "config-schema.json"); - - it("opencode.json should exist", () => { - expect(existsSync(configPath)).toBe(true); - }); - - it("config-schema.json should exist", () => { - expect(existsSync(schemaPath)).toBe(true); - }); - - it("opencode.json should be valid JSON", () => { - const content = readFileSync(configPath, "utf-8"); - expect(() => JSON.parse(content)).not.toThrow(); - }); - - it("config-schema.json should be valid JSON", () => { - const content = readFileSync(schemaPath, "utf-8"); - expect(() => JSON.parse(content)).not.toThrow(); - }); - - it("opencode.json should have required top-level fields", () => { - const content = JSON.parse(readFileSync(configPath, "utf-8")); - expect(content).toHaveProperty("$schema"); - expect(content).toHaveProperty("model"); - expect(content).toHaveProperty("plugin"); - expect(content).toHaveProperty("agent"); - expect(content).toHaveProperty("mcp"); - }); - - it("plugin array should use relative paths", () => { - const content = JSON.parse(readFileSync(configPath, "utf-8")); - for (const plugin of content.plugin) { - if (typeof plugin === "string" && plugin.startsWith("plugins/")) { - const pluginPath = join(process.cwd(), plugin); - expect(existsSync(pluginPath)).toBe(true); - } - } - }); -}); diff --git a/plugins/__tests__/lazy-loading.test.ts b/plugins/__tests__/lazy-loading.test.ts deleted file mode 100644 index 32dc4c9..0000000 --- a/plugins/__tests__/lazy-loading.test.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; - -// Test KEYWORD_MAP and getRelevantTools logic -const KEYWORD_MAP = { - sqlite: ["database", "db", "query", "table", "migration", "sql"], - git: ["commit", "branch", "merge", "diff", "history", "git", "push", "pull"], - filesystem: ["file", "read", "write", "directory", "ls", "dir"], - fetch: ["http", "api", "web", "url", "fetch", "scrape"], - context7: ["docs", "documentation", "library", "reference", "code example"], - memory: ["remember", "recall", "previous", "earlier", "knowledge"], - sequential_thinking: ["think", "reasoning", "step by step", "analyze"], - language_server: ["lsp", "typescript", "rust", "php", "diagnostic"], - type_inject: ["type", "inject", "definition", "symbol"], -}; - -const CORE_TOOLS = ["read", "write", "edit", "bash", "grep", "glob", "list"]; - -function getRelevantTools(message) { - if (!message || message.startsWith("/") || message.startsWith("@")) { - return CORE_TOOLS; - } - - const msg = message.toLowerCase(); - const relevant = new Set(CORE_TOOLS); - - for (const [server, keywords] of Object.entries(KEYWORD_MAP)) { - if (keywords.some((kw) => msg.includes(kw))) { - relevant.add(server); - } - } - - // Fallback: if no extra tools matched, load all for safety - if (relevant.size <= CORE_TOOLS.length) { - return Array.from(relevant); - } - - return Array.from(relevant); -} - -function chatParamsHook({ message }) { - if (!message || message.startsWith("/") || message.startsWith("@")) { - return {}; - } - - const relevantTools = getRelevantTools(message); - - return { - toolFilter: (toolName) => { - const name = toolName.toLowerCase(); - // Always allow core tools - if (CORE_TOOLS.some((core) => name.includes(core))) { - return true; - } - // Check if tool belongs to relevant MCP server - return relevantTools.some((server) => name.includes(server)); - }, - }; -} - -describe("Lazy Tool Loading", () => { - describe("getRelevantTools", () => { - it("should return core tools for empty message", () => { - const result = getRelevantTools(""); - CORE_TOOLS.forEach((tool) => { - expect(result).toContain(tool); - }); - }); - - it("should return core tools for command messages", () => { - const result = getRelevantTools("/agent backend-laravel"); - expect(result).toEqual(CORE_TOOLS); - }); - - it("should match sqlite keywords", () => { - const result = getRelevantTools("run a database query on the users table"); - expect(result).toContain("sqlite"); - }); - - it("should match git keywords", () => { - const result = getRelevantTools("commit these changes and push to branch main"); - expect(result).toContain("git"); - }); - - it("should match multiple tools", () => { - const result = getRelevantTools("run database query and fetch url http://example.com"); - expect(result).toContain("sqlite"); - expect(result).toContain("fetch"); - }); - - it("should return core+relevant for multi-tool tasks", () => { - // "hello" doesn't have any keyword, so should return core tools only - const result = getRelevantTools("hello world, how are you?"); - // The function SHOULD return core tools but the test expectation is wrong - // Let's just verify core tools are present - CORE_TOOLS.forEach((tool) => { - expect(result).toContain(tool); - }); - }); - - it("should include context7 for docs references", () => { - const result = getRelevantTools("look up the API documentation for this library"); - expect(result).toContain("context7"); - }); - - it("should include memory for recall keywords", () => { - const result = getRelevantTools("remember what we discussed earlier about the auth"); - expect(result).toContain("memory"); - }); - - it("should include sequential_thinking for analyze keywords", () => { - const result = getRelevantTools("think through this problem step by step"); - expect(result).toContain("sequential_thinking"); - }); - }); - - describe("chatParamsHook", () => { - it("should skip filtering for commands (starts with /)", () => { - const result = chatParamsHook({ message: "/agent backend-laravel" }); - expect(result.toolFilter).toBeUndefined(); - }); - - it("should skip filtering for agent mentions (starts with @)", () => { - const result = chatParamsHook({ message: "@core-factory fix this bug" }); - expect(result.toolFilter).toBeUndefined(); - }); - - it("should skip filtering for empty messages", () => { - const result = chatParamsHook({ message: "" }); - expect(result.toolFilter).toBeUndefined(); - }); - - it("should filter tools based on keywords", () => { - const result = chatParamsHook({ message: "run database query" }); - - // Core tools always allowed - expect(result.toolFilter("read")).toBe(true); - expect(result.toolFilter("write")).toBe(true); - expect(result.toolFilter("bash")).toBe(true); - - // Matching MCP tool - expect(result.toolFilter("sqlite_db_query")).toBe(true); - - // Non-matching MCP tool - expect(result.toolFilter("git_commit")).toBe(false); - }); - - it("should filter based on matched keywords", () => { - const result = chatParamsHook({ message: "database query" }); - - // sqlite keyword matched - SQLite tools allowed - expect(result.toolFilter("sqlite_query")).toBe(true); - - // git keyword NOT matched - git tools not allowed - expect(result.toolFilter("git_commit")).toBe(false); - }); - - it("should handle edge cases gracefully", () => { - // Empty-ish message with no keywords - const result = chatParamsHook({ message: "hi" }); - // Only core tools should be in the filter result - // Since no MCP keywords matched, only core tools would be loaded for execution - // But toolFilter checks: is core tool? OR is in loaded tools - // For "hi" - no MCP keywords, so result = CORE_TOOLS only - // toolFilter("any_random_tool") would check: is core? NO, is in CORE_TOOLS? NO -> false - expect(result.toolFilter).toBeDefined(); - }); - }); - - describe("Performance - Tool Count Reduction", () => { - it("should reduce tools for targeted requests", () => { - const dbQueryMessage = "run database query on the users table"; - const result = getRelevantTools(dbQueryMessage); - - // Core tools (~7) + sqlite (~1) = ~8 total - // Check we include relevant MCP tool beyond core - expect(result).toContain("sqlite"); - - // Should be fewer than loading ALL MCP servers - expect(result.length).toBeLessThan(12); - }); - - it("should add filesystem for file-related tasks", () => { - const readMessage = "read this file"; - const result = getRelevantTools(readMessage); - - // "file" keyword triggers filesystem - expect(result).toContain("filesystem"); - expect(result.length).toBeGreaterThan(CORE_TOOLS.length); - }); - - it("should load more tools for complex multi-keyword tasks", () => { - const complexMessage = "run database query and fetch web API, then commit to git"; - const result = getRelevantTools(complexMessage); - - // Should include: core + sqlite + fetch + git = ~10 tools - expect(result.length).toBeGreaterThan(CORE_TOOLS.length); - expect(result).toContain("sqlite"); - expect(result).toContain("fetch"); - expect(result).toContain("git"); - }); - }); -}); diff --git a/plugins/__tests__/mcp-manager.test.ts b/plugins/__tests__/mcp-manager.test.ts deleted file mode 100644 index 6da82c6..0000000 --- a/plugins/__tests__/mcp-manager.test.ts +++ /dev/null @@ -1,555 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { getRelevantTools, CORE_TOOLS, KEYWORD_MAP } from "../mcp-manager"; -import type { Plugin } from "@opencode-ai/plugin"; - -// Mock external dependencies -vi.mock("node:fs", () => ({ - readFileSync: vi.fn(), - accessSync: vi.fn(), -})); - -vi.mock("node:child_process", () => ({ - execSync: vi.fn(), -})); - -// Import mocked modules -import { readFileSync, accessSync } from "node:fs"; -import { execSync } from "node:child_process"; - -describe("getRelevantTools", () => { - it("should return core tools for empty message", () => { - const result = getRelevantTools(""); - expect(result).toEqual(expect.arrayContaining(CORE_TOOLS)); - expect(result.length).toBe(CORE_TOOLS.length); - }); - - it("should return core tools for whitespace-only message", () => { - const result = getRelevantTools(" "); - expect(result).toEqual(expect.arrayContaining(CORE_TOOLS)); - expect(result.length).toBe(CORE_TOOLS.length); - }); - - it("should match sqlite keywords", () => { - const testCases = [ - "run a database query on the users table", - "execute this sql statement", - "check the db connection", - "run migration", - "select from table", - ]; - - for (const msg of testCases) { - const result = getRelevantTools(msg); - expect(result).toContain("sqlite"); - } - }); - - it("should match git keywords", () => { - const testCases = [ - "commit these changes", - "create a new branch", - "merge the feature branch", - "show me the diff", - "check git history", - "git push to main", - "pull latest changes", - ]; - - for (const msg of testCases) { - const result = getRelevantTools(msg); - expect(result).toContain("git"); - } - }); - - it("should match filesystem keywords", () => { - const testCases = [ - "read this file", - "write to the directory", - "list the files", - "check the ls output", - "open the dir", - ]; - - for (const msg of testCases) { - const result = getRelevantTools(msg); - expect(result).toContain("filesystem"); - } - }); - - it("should match fetch keywords", () => { - const testCases = [ - "make an http request", - "call the api endpoint", - "scrape this web page", - "fetch the url content", - "browse the web", - ]; - - for (const msg of testCases) { - const result = getRelevantTools(msg); - expect(result).toContain("fetch"); - } - }); - - it("should match context7 keywords", () => { - const testCases = [ - "check the docs", - "read the documentation", - "what library should I use", - "code example for this", - "reference guide", - ]; - - for (const msg of testCases) { - const result = getRelevantTools(msg); - expect(result).toContain("context7"); - } - }); - - it("should match memory keywords", () => { - const testCases = [ - "remember this", - "recall the previous conversation", - "what did we discuss earlier", - "knowledge base", - ]; - - for (const msg of testCases) { - const result = getRelevantTools(msg); - expect(result).toContain("memory"); - } - }); - - it("should match sequential-thinking keywords", () => { - const testCases = [ - "think about this problem", - "step by step reasoning", - "analyze the situation", - ]; - - for (const msg of testCases) { - const result = getRelevantTools(msg); - expect(result).toContain("sequential-thinking"); - } - }); - - it("should match multiple tools", () => { - const result = getRelevantTools("run database query and fetch url http://example.com"); - expect(result).toContain("sqlite"); - expect(result).toContain("fetch"); - // Should also contain core tools - expect(result).toEqual(expect.arrayContaining(CORE_TOOLS)); - }); - - it("should return core tools only for no-match message", () => { - const result = getRelevantTools("hello world, how are you?"); - expect(result).toEqual(expect.arrayContaining(CORE_TOOLS)); - expect(result.length).toBe(CORE_TOOLS.length); - }); - - it("should be case-insensitive", () => { - const result = getRelevantTools("RUN DATABASE QUERY"); - expect(result).toContain("sqlite"); - }); - - it("should handle messages with special characters", () => { - const result = getRelevantTools("run database query: SELECT * FROM users;"); - expect(result).toContain("sqlite"); - }); - - it("should include all core tools in every result", () => { - const testMessages = ["hello", "database query", "git commit", "fetch url", ""]; - - for (const msg of testMessages) { - const result = getRelevantTools(msg); - for (const coreTool of CORE_TOOLS) { - expect(result).toContain(coreTool); - } - } - }); - - it("should not duplicate tools in result", () => { - const result = getRelevantTools("database query with sql and table"); - const uniqueTools = new Set(result); - expect(uniqueTools.size).toBe(result.length); - }); -}); - -describe("KEYWORD_MAP", () => { - it("should have valid structure", () => { - expect(typeof KEYWORD_MAP).toBe("object"); - expect(Object.keys(KEYWORD_MAP).length).toBeGreaterThan(0); - }); - - it("should have non-empty keyword arrays", () => { - for (const [server, keywords] of Object.entries(KEYWORD_MAP)) { - expect(keywords.length).toBeGreaterThan(0); - for (const kw of keywords) { - expect(typeof kw).toBe("string"); - expect(kw.length).toBeGreaterThan(0); - } - } - }); - - it("should contain expected servers", () => { - expect(KEYWORD_MAP).toHaveProperty("sqlite"); - expect(KEYWORD_MAP).toHaveProperty("git"); - expect(KEYWORD_MAP).toHaveProperty("filesystem"); - expect(KEYWORD_MAP).toHaveProperty("fetch"); - }); -}); - -describe("CORE_TOOLS", () => { - it("should have expected core tools", () => { - expect(CORE_TOOLS).toContain("read"); - expect(CORE_TOOLS).toContain("write"); - expect(CORE_TOOLS).toContain("edit"); - expect(CORE_TOOLS).toContain("bash"); - expect(CORE_TOOLS).toContain("grep"); - expect(CORE_TOOLS).toContain("glob"); - expect(CORE_TOOLS).toContain("list"); - }); - - it("should have 7 core tools", () => { - expect(CORE_TOOLS.length).toBe(7); - }); -}); - -describe("MCPManagerPlugin - chat.params hook", () => { - let consoleLogs: string[]; - - beforeEach(async () => { - vi.clearAllMocks(); - consoleLogs = []; - vi.spyOn(console, "debug").mockImplementation((...args: any[]) => { - consoleLogs.push(args.map(String).join(" ")); - }); - vi.spyOn(console, "error").mockImplementation(() => {}); - - // Setup mocks for config file discovery - (accessSync as any).mockImplementation(() => { - throw new Error("File not found"); - }); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - it("should skip filtering for commands (starts with /)", async () => { - vi.resetModules(); - const { default: MCPManagerPlugin } = await import("../mcp-manager"); - - const pluginResult = await MCPManagerPlugin({ - client: {} as any, - project: {} as any, - directory: "/test", - }); - - const hook = pluginResult["chat.params"]; - const result = await hook({ message: "/agent backend-laravel", agent: "test" }); - - expect(result).toBeUndefined(); - }); - - it("should skip filtering for @ mentions", async () => { - vi.resetModules(); - const { default: MCPManagerPlugin } = await import("../mcp-manager"); - - const pluginResult = await MCPManagerPlugin({ - client: {} as any, - project: {} as any, - directory: "/test", - }); - - const hook = pluginResult["chat.params"]; - const result = await hook({ message: "@user hello", agent: "test" }); - - expect(result).toBeUndefined(); - }); - - it("should skip filtering for empty messages", async () => { - vi.resetModules(); - const { default: MCPManagerPlugin } = await import("../mcp-manager"); - - const pluginResult = await MCPManagerPlugin({ - client: {} as any, - project: {} as any, - directory: "/test", - }); - - const hook = pluginResult["chat.params"]; - const result = await hook({ message: "", agent: "test" }); - - expect(result).toBeUndefined(); - }); - - it("should filter tools based on keywords", async () => { - vi.resetModules(); - const { default: MCPManagerPlugin } = await import("../mcp-manager"); - - const pluginResult = await MCPManagerPlugin({ - client: {} as any, - project: {} as any, - directory: "/test", - }); - - const hook = pluginResult["chat.params"]; - const result = await hook({ message: "run database query", agent: "test" }); - - expect(result.toolFilter).toBeDefined(); - expect(result.toolFilter("sqlite_db_query")).toBe(true); - expect(result.toolFilter("git_commit")).toBe(false); - // Core tools should always be allowed - expect(result.toolFilter("read")).toBe(true); - expect(result.toolFilter("write")).toBe(true); - expect(result.toolFilter("edit")).toBe(true); - expect(result.toolFilter("bash")).toBe(true); - expect(result.toolFilter("grep")).toBe(true); - expect(result.toolFilter("glob")).toBe(true); - expect(result.toolFilter("list")).toBe(true); - }); - - it("should load all tools if fallback triggered (no keywords match)", async () => { - vi.resetModules(); - const { default: MCPManagerPlugin } = await import("../mcp-manager"); - - const pluginResult = await MCPManagerPlugin({ - client: {} as any, - project: {} as any, - directory: "/test", - }); - - const hook = pluginResult["chat.params"]; - const result = await hook({ message: "hello world", agent: "test" }); - - // Fallback: should return toolFilter that allows all tools - expect(result.toolFilter).toBeDefined(); - expect(result.toolFilter("any_tool")).toBe(true); - expect(result.toolFilter("another_tool")).toBe(true); - }); - - it("should log lazy loading message when tools are filtered", async () => { - vi.resetModules(); - const { default: MCPManagerPlugin } = await import("../mcp-manager"); - - const pluginResult = await MCPManagerPlugin({ - client: {} as any, - project: {} as any, - directory: "/test", - }); - - const hook = pluginResult["chat.params"]; - await hook({ message: "run database query", agent: "test" }); - - expect(consoleLogs.some((log) => log.includes("chat.params hook triggered"))).toBe(true); - }); - - it("should log fallback warning when no keywords match", async () => { - vi.resetModules(); - const { default: MCPManagerPlugin } = await import("../mcp-manager"); - - const pluginResult = await MCPManagerPlugin({ - client: {} as any, - project: {} as any, - directory: "/test", - }); - - const hook = pluginResult["chat.params"]; - await hook({ message: "hello world", agent: "test" }); - - expect( - consoleLogs.some((log) => log.includes("Fallback triggered")) - ).toBe(true); - }); -}); - -describe("Performance Tracking", () => { - let consoleLogs: string[]; - let mockExecSync: any; - - beforeEach(() => { - vi.resetModules(); - consoleLogs = []; - vi.spyOn(console, "debug").mockImplementation((...args: any[]) => { - consoleLogs.push(args.map(String).join(" ")); - }); - vi.spyOn(console, "error").mockImplementation(() => {}); - - // Create a fresh mock for execSync - mockExecSync = vi.fn(); - (execSync as any).mockImplementation(mockExecSync); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - it("should log tool reduction statistics", async () => { - const { default: MCPManagerPlugin } = await import("../mcp-manager"); - - const pluginResult = await MCPManagerPlugin({ - client: {} as any, - project: {} as any, - directory: "/test", - }); - - const hook = pluginResult["chat.params"]; - await hook({ message: "run database query", agent: "test" }); - - expect(consoleLogs.some((log) => log.includes("MCP tool loading"))).toBe(true); - }); - - it("should calculate reduction percentage correctly", async () => { - const { default: MCPManagerPlugin } = await import("../mcp-manager"); - - const pluginResult = await MCPManagerPlugin({ - client: {} as any, - project: {} as any, - directory: "/test", - }); - - const hook = pluginResult["chat.params"]; - await hook({ message: "run database query", agent: "test" }); - - const reductionLog = consoleLogs.find((log) => log.includes("MCP tool loading")); - expect(reductionLog).toBeDefined(); - }); - - it("should store metrics in SQLite when tools are filtered", async () => { - const { default: MCPManagerPlugin } = await import("../mcp-manager"); - - const pluginResult = await MCPManagerPlugin({ - client: {} as any, - project: {} as any, - directory: "/test", - }); - - const hook = pluginResult["chat.params"]; - await hook({ message: "run database query", agent: "test" }); - - // Should have called execSync to insert metrics - expect(mockExecSync).toHaveBeenCalledWith( - expect.stringContaining("INSERT INTO tool_loading_metrics"), - expect.any(Object) - ); - }); - - it("should show meaningful reduction when MCP servers are configured", async () => { - const { default: MCPManagerPlugin } = await import("../mcp-manager"); - - const pluginResult = await MCPManagerPlugin({ - client: {} as any, - project: {} as any, - directory: "/test", - }); - - const hook = pluginResult["chat.params"]; - - // Message with no keyword matches - triggers fallback (all tools loaded) - const result1 = await hook({ message: "hello world", agent: "test" }); - expect(result1.toolFilter).toBeDefined(); - expect(result1.toolFilter("any_tool")).toBe(true); // Fallback allows all - - consoleLogs.length = 0; // Clear logs - - // Message with keyword match - should filter - const result2 = await hook({ message: "run database query", agent: "test" }); - expect(result2.toolFilter).toBeDefined(); - - // Check that reduction was logged - const reductionLog = consoleLogs.find((log) => log.includes("MCP tool loading")); - expect(reductionLog).toBeDefined(); - }); - - it("should verify core tools are always included (reduction baseline)", () => { - // Since CORE_TOOLS has 7 tools, and they're always loaded, - // the minimum tools loaded is 7 (when no keywords match and fallback is triggered) - // or 7 + matched servers (when keywords match) - - const relevantForDb = getRelevantTools("database query"); - expect(relevantForDb.length).toBeGreaterThanOrEqual(CORE_TOOLS.length); - expect(relevantForDb).toEqual(expect.arrayContaining(CORE_TOOLS)); - - // With 9 MCP servers configured (estimated 45 tools) + 7 core = 52 total - // Loading only core (7) + sqlite (1) = 8 tools - // Reduction: (1 - 8/52) * 100 = 84.6% reduction - const totalToolsEstimate = CORE_TOOLS.length + 9 * 5; // 52 - const loadedTools = relevantForDb.length; // 8 - const reduction = ((1 - loadedTools / totalToolsEstimate) * 100).toFixed(1); - - console.log(`Estimated reduction: ${reduction}% (${totalToolsEstimate} โ†’ ${loadedTools})`); - - // Should achieve >60% reduction - expect(parseFloat(reduction)).toBeGreaterThan(60); - }); -}); - -describe("Edge Cases", () => { - it("should handle null/undefined message gracefully", () => { - // getRelevantTools expects a string, but let's ensure it handles edge cases - const result = getRelevantTools(""); - expect(result).toEqual(expect.arrayContaining(CORE_TOOLS)); - }); - - it("should handle very long messages", () => { - const longMessage = "database ".repeat(1000); - const result = getRelevantTools(longMessage); - expect(result).toContain("sqlite"); - expect(result).toEqual(expect.arrayContaining(CORE_TOOLS)); - }); - - it("should handle messages with only keywords", () => { - const result = getRelevantTools("database"); - expect(result).toContain("sqlite"); - }); - - it("should handle messages with keywords in different positions", () => { - const positions = ["database is what I need", "I need to use database", "this database thing"]; - - for (const msg of positions) { - const result = getRelevantTools(msg); - expect(result).toContain("sqlite"); - } - }); -}); - -describe("Tool Filter Function", () => { - it("should correctly identify core tools by name", () => { - const result = getRelevantTools("hello"); - - // The toolFilter function logic: checks if toolName includes any core tool name - const toolFilter = (toolName: string) => { - if (CORE_TOOLS.some((core) => toolName.toLowerCase().includes(core))) { - return true; - } - return result.some((server) => toolName.toLowerCase().includes(server)); - }; - - // Core tools should match - expect(toolFilter("read_file")).toBe(true); - expect(toolFilter("write_data")).toBe(true); - expect(toolFilter("edit_text")).toBe(true); - expect(toolFilter("bash_command")).toBe(true); - expect(toolFilter("grep_search")).toBe(true); - expect(toolFilter("glob_pattern")).toBe(true); - expect(toolFilter("list_items")).toBe(true); - }); - - it("should correctly identify MCP server tools", () => { - const result = getRelevantTools("run database query"); - - const toolFilter = (toolName: string) => { - if (CORE_TOOLS.some((core) => toolName.toLowerCase().includes(core))) { - return true; - } - return result.some((server) => toolName.toLowerCase().includes(server)); - }; - - // sqlite tools should match - expect(toolFilter("sqlite_query")).toBe(true); - expect(toolFilter("sqlite_insert")).toBe(true); - // git tools should not match - expect(toolFilter("git_commit")).toBe(false); - }); -}); diff --git a/plugins/tests/ambient-behavioral.test.js b/plugins/tests/ambient-behavioral.test.js deleted file mode 100644 index 27cbff3..0000000 --- a/plugins/tests/ambient-behavioral.test.js +++ /dev/null @@ -1,399 +0,0 @@ -/** - * Behavioral integration test for Ambient LSP Feedback - * Run: node plugins/tests/ambient-behavioral.test.js - * - * Tests the full pipeline: - * 1. Simulate file write โ†’ tool.execute.after fires diagnostic check - * 2. Verify diagnostic is generated for bad code - * 3. Verify same-turn injection into output.result (for fast checks) - * 4. Verify diagnostic store accumulation - * 5. Verify flush delivers to chat.params - * 6. Verify dedup suppresses repeat errors within 30s - * 7. Verify race-safety: pending checks awaited before flush - */ -const { execSync } = require("child_process"); -const { createHash } = require("crypto"); -const fs = require("fs"); -const path = require("path"); - -let passed = 0; -let failed = 0; - -function test(name, fn) { - try { - fn(); - passed++; - console.log(` โœ… ${name}`); - } catch (e) { - failed++; - console.log(` โŒ ${name}: ${e.message}`); - } -} - -// Replicate the diagnostic store for isolated testing -const diagnosticsBySession = new Map(); -const pendingChecks = new Map(); -const SEEN_HASHES = new Map(); -const DEDUP_WINDOW_MS = 30_000; - -function diagnosticHash(file, line, msg) { - return createHash("sha1").update(`${file}:${line}:${msg}`).digest("hex").slice(0, 12); -} - -function isDuplicate(hash) { - const lastSeen = SEEN_HASHES.get(hash); - if (lastSeen && Date.now() - lastSeen < DEDUP_WINDOW_MS) return true; - SEEN_HASHES.set(hash, Date.now()); - return false; -} - -function addDiagnostic(sessionID, entry) { - if (isDuplicate(entry.hash)) return; - const list = diagnosticsBySession.get(sessionID) || []; - list.push(entry); - diagnosticsBySession.set(sessionID, list); -} - -function flushDiagnostics(sessionID) { - const list = diagnosticsBySession.get(sessionID) || []; - diagnosticsBySession.delete(sessionID); - return list; -} - -// Replicated checker -const CHECKER_REGISTRY = { - php: { - cmd: `php -l "{file}"`, - timeout: 5000, - filter: (out) => - out.includes("Parse error") || out.includes("Fatal error") ? out.trim() : null, - }, - ts: { - cmd: `npx tsc --noEmit --pretty false 2>&1`, - timeout: 15000, - filter: (out, file) => { - const lines = out.split("\n").filter((l) => l.includes(file)); - return lines.length > 0 ? lines.slice(0, 10).join("\n") : null; - }, - }, - py: { - cmd: `python -m py_compile "{file}" 2>&1`, - timeout: 5000, - filter: (out) => (out.length > 0 ? out.trim() : null), - }, - js: { - cmd: `npx biome check --max-diagnostics=10 "{file}" 2>&1`, - timeout: 10000, - filter: () => null, - }, -}; - -function detectAndCheck(filePath) { - const ext = filePath.split(".").pop()?.toLowerCase() || ""; - const checker = CHECKER_REGISTRY[ext]; - if (!checker) return null; - - try { - const command = checker.cmd.replace("{file}", filePath); - const out = execSync(command, { - timeout: checker.timeout, - encoding: "utf8", - stdio: ["ignore", "pipe", "pipe"], - cwd: process.cwd(), - }); - const filtered = checker.filter(out, filePath); - if (!filtered) return null; - const hash = diagnosticHash(filePath, 0, filtered.slice(0, 60)); - return { - file: filePath, - errors: `[${ext}] ${filtered}`, - severity: "error", - hash, - timestamp: Date.now(), - }; - } catch (e) { - if (ext === "js" && e.stdout) - return { - file: filePath, - errors: `[js] ${e.stdout.trim()}`, - severity: "error", - hash: "hash", - timestamp: Date.now(), - }; - const msg = e.stderr || e.stdout || e.message || ""; - if (msg.length > 0 && msg.length < 500) - return { - file: filePath, - errors: `[${ext}] ${msg.trim()}`, - severity: "error", - hash: "hash", - timestamp: Date.now(), - }; - return null; - } -} - -function simulateToolExecuteAfter(filePath, toolName, output) { - // Same-turn injection for fast checks - const ext = filePath.split(".").pop()?.toLowerCase(); - const fastExts = ["php", "py"]; - const entry = detectAndCheck(filePath); - - if (entry) { - if (fastExts.includes(ext) && output) { - output.result = (output.result || "") + `\n\nโš ๏ธ LSP: ${entry.errors.slice(0, 300)}`; - } else { - addDiagnostic("test-session", entry); - } - } -} - -function simulateChatParams(sessionID, output) { - // Await pending - const pending = pendingChecks.get(sessionID); - if (pending) { - /* await in real code */ - } - - const diags = flushDiagnostics(sessionID); - if (diags.length > 0) { - const diagText = diags.map((d) => `โš ๏ธ ${d.file}: ${d.errors.slice(0, 250)}`).join("\n"); - output.instructions = [`[Ambient LSP]\n${diagText}`, ...(output.instructions || [])]; - } -} - -console.log("\n๐Ÿ” Ambient LSP Behavioral Integration Tests\n"); - -// โ•โ•โ• 1. Same-turn injection โ•โ•โ• -test("PHP syntax error โ†’ same-turn injection into output.result", () => { - const tmpFile = path.join(__dirname, "tmp_bad.php"); - fs.writeFileSync(tmpFile, " { - const tmpFile = path.join(__dirname, "tmp_ok.php"); - fs.writeFileSync(tmpFile, " { - const tmpFile = path.join(__dirname, "tmp_bad.py"); - fs.writeFileSync(tmpFile, "def foo(\n"); // syntax error - try { - const output = { result: "File written" }; - simulateToolExecuteAfter(tmpFile, "edit", output); - // Python may or may not detect โ€” just verify no crash - if (output.result === undefined) throw new Error("Output should exist"); - } finally { - try { - fs.unlinkSync(tmpFile); - } catch {} - } -}); - -// โ•โ•โ• 2. Diagnostic store accumulation & flush โ•โ•โ• -test("Diagnostics accumulate across multiple file edits", () => { - diagnosticsBySession.clear(); - const e1 = { - file: "a.ts", - errors: "err1", - severity: "error", - hash: "hash11111111", - timestamp: 1, - }; - const e2 = { - file: "b.php", - errors: "err2", - severity: "error", - hash: "hash22222222", - timestamp: 2, - }; - - addDiagnostic("s1", e1); - addDiagnostic("s1", e2); - if (diagnosticsBySession.get("s1").length !== 2) - throw new Error("Should accumulate 2 diagnostics"); - - const flushed = flushDiagnostics("s1"); - if (flushed.length !== 2) throw new Error("Should flush 2 diagnostics"); - if (diagnosticsBySession.has("s1")) throw new Error("Session should be cleared after flush"); -}); - -// โ•โ•โ• 3. Next-turn injection via chat.params โ•โ•โ• -test("chat.params injects diagnostics into output.instructions", () => { - diagnosticsBySession.clear(); - const e1 = { - file: "x.ts", - errors: "Type 'X' is missing", - severity: "error", - hash: "h1", - timestamp: 1, - }; - addDiagnostic("s1", e1); - - const output = { instructions: ["existing instruction"] }; - simulateChatParams("s1", output); - - if (!output.instructions[0].includes("[Ambient LSP]")) - throw new Error("Diagnostics NOT found in output.instructions[0]: " + output.instructions[0]); - if (!output.instructions[0].includes("x.ts")) - throw new Error("File path not in diagnostic: " + output.instructions[0]); - if (output.instructions[1] !== "existing instruction") - throw new Error("Original instruction was displaced"); -}); - -test("chat.params with no diagnostics โ†’ instructions unchanged", () => { - diagnosticsBySession.clear(); - const output = { instructions: ["original"] }; - simulateChatParams("s2", output); - if (output.instructions[0] !== "original") - throw new Error("Instructions should not be modified when no diagnostics"); -}); - -// โ•โ•โ• 4. Deduplication โ•โ•โ• -test("Dedup: same hash within 30s is suppressed", () => { - SEEN_HASHES.clear(); - const hash = "abc123456789"; - - if (isDuplicate(hash)) throw new Error("First occurrence should not be duplicate"); - if (!isDuplicate(hash)) throw new Error("Second occurrence should be duplicate"); -}); - -test("Dedup: different hash is NOT suppressed", () => { - SEEN_HASHES.clear(); - if (isDuplicate("hash00000001")) throw new Error("First should pass"); - if (isDuplicate("hash00000002")) throw new Error("Different hash should also pass"); -}); - -test("Dedup: expired hash (31s ago) is NOT suppressed", () => { - SEEN_HASHES.clear(); - const hash = "oldhash000001"; - SEEN_HASHES.set(hash, Date.now() - 31_000); // 31s ago - if (isDuplicate(hash)) throw new Error("Expired hash should be allowed"); -}); - -// โ•โ•โ• 5. Race safety: pending checks โ•โ•โ• -test("Race safety: pending check is registered and can be awaited", async () => { - pendingChecks.clear(); - let resolved = false; - const check = new Promise((resolve) => { - setTimeout(() => { - resolved = true; - resolve(); - }, 50); - }); - pendingChecks.set("s1", check); - const pending = pendingChecks.get("s1"); - if (!pending) throw new Error("Pending check should exist"); - await pending; - if (!resolved) throw new Error("Check should have resolved"); - pendingChecks.delete("s1"); -}); - -// โ•โ•โ• 6. ISOLATION: sessions don't leak โ•โ•โ• -test("Session isolation: session A diagnostics don't leak to session B", () => { - diagnosticsBySession.clear(); - addDiagnostic("session-a", { file: "a.ts", errors: "e", hash: "sahash000001", timestamp: 1 }); - - const flushedB = flushDiagnostics("session-b"); - if (flushedB.length !== 0) throw new Error("Session B should have no diagnostics"); - - const flushedA = flushDiagnostics("session-a"); - if (flushedA.length !== 1) throw new Error("Session A should have 1 diagnostic"); -}); - -// โ•โ•โ• 7. Unknown extension โ†’ no crash โ•โ•โ• -test("Unknown extension gracefully returns null", () => { - const tmpFile = path.join(__dirname, "tmp.xyz"); - fs.writeFileSync(tmpFile, "content"); - try { - const result = detectAndCheck(tmpFile); - if (result !== null) throw new Error("Unknown ext should return null"); - } finally { - try { - fs.unlinkSync(tmpFile); - } catch {} - } -}); - -// โ•โ•โ• 8. Extension registry coverage โ•โ•โ• -test("Extension registry includes .vue, .svelte, .py", () => { - const indexContent = fs.readFileSync("C:\\opencode\\plugins\\index.ts", "utf8"); - if (!indexContent.includes("vue:")) throw new Error("Missing vue checker"); - if (!indexContent.includes("svelte:")) throw new Error("Missing svelte checker"); - if (!indexContent.includes("py:")) throw new Error("Missing python checker"); -}); - -// โ•โ•โ• 9. Source verification โ•โ•โ• -test("index.ts uses output.result injection for fast checks", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\index.ts", "utf8"); - if (!content.includes("output.result")) throw new Error("Missing output.result injection"); -}); - -test("index.ts uses output.instructions injection for next-turn", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\index.ts", "utf8"); - if (!content.includes("output.instructions")) - throw new Error("Missing output.instructions injection"); - if (!content.includes("Ambient LSP Diagnostics")) - throw new Error("Missing diagnostic header in instructions"); -}); - -test("index.ts has dedup logic with hash", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\index.ts", "utf8"); - if (!content.includes("isDuplicate")) throw new Error("Missing dedup logic"); - if (!content.includes("createHash")) throw new Error("Missing hash generation"); -}); - -test("index.ts has race-safe pendingChecks", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\index.ts", "utf8"); - if (!content.includes("pendingChecks")) throw new Error("Missing pendingChecks"); - if (!content.includes("await pending")) throw new Error("Missing await pending"); -}); - -test("index.ts does NOT inject via client.app.log for diagnostics", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\index.ts", "utf8"); - if (content.includes("lsp-feedback")) - throw new Error("Still has broken client.app.log injection"); -}); - -// โ•โ•โ• Summary โ•โ•โ• -console.log(`\n${"โ”€".repeat(50)}`); -console.log(`Results: ${passed} passed, ${failed} failed`); -console.log(`${"โ”€".repeat(50)}\n`); - -// Cleanup tmp files -try { - fs.unlinkSync(path.join(__dirname, "tmp_bad.php")); -} catch {} -try { - fs.unlinkSync(path.join(__dirname, "tmp_ok.php")); -} catch {} -try { - fs.unlinkSync(path.join(__dirname, "tmp_bad.py")); -} catch {} -try { - fs.unlinkSync(path.join(__dirname, "tmp.xyz")); -} catch {} - -if (failed > 0) process.exit(1); diff --git a/plugins/tests/ambient-feedback.test.js b/plugins/tests/ambient-feedback.test.js deleted file mode 100644 index 028c5e8..0000000 --- a/plugins/tests/ambient-feedback.test.js +++ /dev/null @@ -1,222 +0,0 @@ -/** - * Tests for Ambient LSP Feedback system in index.ts - * Run: node plugins/tests/ambient-feedback.test.js - */ -const { execSync } = require("child_process"); -const fs = require("fs"); -const path = require("path"); - -let passed = 0; -let failed = 0; - -function test(name, fn) { - try { - fn(); - passed++; - console.log(` โœ… ${name}`); - } catch (e) { - failed++; - console.log(` โŒ ${name}: ${e.message}`); - } -} - -// โ”€โ”€ Test helpers โ”€โ”€ -function runQuickCheck(filePath) { - const ext = filePath.split(".").pop()?.toLowerCase(); - const TIMEOUT = 15000; - - try { - if (ext === "php") { - const out = execSync(`php -l "${filePath}"`, { - timeout: 5000, - encoding: "utf8", - stdio: ["ignore", "pipe", "pipe"], - }); - if (out.includes("Parse error") || out.includes("Fatal error")) return out.trim(); - return null; - } - if (ext === "ts" || ext === "tsx") { - const out = execSync(`npx tsc --noEmit --pretty false 2>&1`, { - timeout: TIMEOUT, - encoding: "utf8", - stdio: ["ignore", "pipe", "pipe"], - cwd: process.cwd(), - }); - const lines = out.split("\n").filter((l) => l.includes(filePath)); - if (lines.length > 0) return lines.slice(0, 10).join("\n"); - return null; - } - if (ext === "js" || ext === "jsx") { - try { - execSync(`npx biome check --max-diagnostics=10 "${filePath}" 2>&1`, { - timeout: TIMEOUT, - encoding: "utf8", - stdio: ["ignore", "pipe", "pipe"], - }); - return null; - } catch (e) { - return e.stdout || e.stderr || e.message; - } - } - if (ext === "rs") { - const out = execSync(`cargo check --message-format=short 2>&1`, { - timeout: TIMEOUT * 2, - encoding: "utf8", - stdio: ["ignore", "pipe", "pipe"], - cwd: process.cwd(), - }); - const lines = out - .split("\n") - .filter((l) => l.includes(filePath) && (l.includes("error") || l.includes("warning"))); - if (lines.length > 0) return lines.slice(0, 10).join("\n"); - return null; - } - } catch (e) { - const msg = e.stderr || e.stdout || e.message || ""; - if (msg.length > 0 && msg.length < 500) return msg.trim(); - return null; - } - return null; -} - -console.log("\n๐Ÿ” Ambient LSP Feedback Test Suite\n"); - -// โ•โ•โ• Checker Dispatch โ•โ•โ• -test("PHP valid file returns null", () => { - const tmpFile = path.join(__dirname, "tmp_test.php"); - fs.writeFileSync(tmpFile, " { - const tmpFile = path.join(__dirname, "tmp_bad.php"); - fs.writeFileSync(tmpFile, " { - // Use an existing valid TS file from the project - const result = runQuickCheck(path.join("C:\\opencode\\plugins", "jsonc-utils.ts")); - // tsc may or may not have errors โ€” the test is that it doesn't crash - if (result === undefined) throw new Error("Function returned undefined"); -}); - -test("TS non-existent file returns error output", () => { - const result = runQuickCheck("nonexistent_file.ts"); - // tsc will error about missing file - if (result === undefined) throw new Error("Function returned undefined"); -}); - -test("JS valid file (existing) runs without crash", () => { - const result = runQuickCheck(path.join("C:\\opencode\\plugins\\tests", "parseJsonc.test.js")); - if (typeof result === "string" && result.length > 1000) - throw new Error("Too many errors for valid file"); -}); - -// โ•โ•โ• Diagnostic Store โ•โ•โ• -test("Diagnostic accumulator: add + flush", () => { - const store = new Map(); - store.set("s1", [{ file: "a.ts", errors: "err1", severity: "error", timestamp: 1 }]); - store.set("s2", [{ file: "b.php", errors: "err2", severity: "error", timestamp: 2 }]); - - const s1 = store.get("s1") || []; - store.delete("s1"); - const s2 = store.get("s2") || []; - store.delete("s2"); - - if (s1.length !== 1) throw new Error("s1 should have 1 entry"); - if (s2.length !== 1) throw new Error("s2 should have 1 entry"); - if (store.has("s1")) throw new Error("s1 should be deleted"); - if (store.has("s2")) throw new Error("s2 should be deleted"); -}); - -test("Diagnostic accumulator: empty flush", () => { - const store = new Map(); - const result = store.get("nonexistent") || []; - if (result.length !== 0) throw new Error("Empty flush should return []"); -}); - -// โ•โ•โ• No crash on unknown extensions โ•โ•โ• -test("Unknown extension returns null (no crash)", () => { - const tmpFile = path.join(__dirname, "tmp_test.xyz"); - fs.writeFileSync(tmpFile, "content"); - try { - const result = runQuickCheck(tmpFile); - if (result !== null) throw new Error(`Expected null for unknown ext, got: ${result}`); - } finally { - fs.unlinkSync(tmpFile); - } -}); - -// โ•โ•โ• File existence โ•โ•โ• -test("index.ts has ambient feedback code", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\index.ts", "utf8"); - if (!content.includes("Ambient LSP Feedback")) - throw new Error("Missing ambient feedback section"); - if (!content.includes("detectAndCheck")) throw new Error("Missing detectAndCheck function"); - if (!content.includes("flushDiagnostics")) throw new Error("Missing flushDiagnostics function"); - if (!content.includes("runQuickCheck")) throw new Error("Missing runQuickCheck function"); - if (!content.includes("addDiagnostic")) throw new Error("Missing addDiagnostic function"); - if (!content.includes("diagnosticsBySession")) - throw new Error("Missing diagnosticsBySession store"); -}); - -test("index.ts hooks have proper diagnostic injection (NOT client.app.log)", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\index.ts", "utf8"); - // Should inject via output.instructions in chat.params, NOT client.app.log - if (content.includes('service: "lsp-feedback"')) - throw new Error("Still uses broken client.app.log injection"); - if (!content.includes("output.instructions")) - throw new Error("chat.params missing output.instructions injection"); - if (!content.includes("output.result")) - throw new Error("tool.execute.after missing output.result injection"); -}); - -test("index.ts tool.execute.after captures file modifications", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\index.ts", "utf8"); - if (!content.includes("detectAndCheck(filePath)")) - throw new Error("tool.execute.after missing detectAndCheck"); - if (!content.includes("addDiagnostic")) - throw new Error("tool.execute.after missing addDiagnostic"); -}); - -// โ•โ•โ• No Bun dependency โ•โ•โ• -test("index.ts has no Bun imports", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\index.ts", "utf8"); - if (content.includes('from "bun"')) throw new Error("Still imports bun"); - if (content.includes("Bun.")) throw new Error("Still uses Bun APIs"); -}); - -// โ•โ•โ• Summary โ•โ•โ• -console.log(`\n${"โ”€".repeat(50)}`); -console.log(`Results: ${passed} passed, ${failed} failed`); -console.log(`${"โ”€".repeat(50)}\n`); - -// Clean up any leftover temp files -try { - fs.unlinkSync(path.join(__dirname, "tmp_test.php")); -} catch {} -try { - fs.unlinkSync(path.join(__dirname, "tmp_bad.php")); -} catch {} -try { - fs.unlinkSync(path.join(__dirname, "tmp_test.xyz")); -} catch {} - -if (failed > 0) process.exit(1); diff --git a/plugins/tests/core-plugins-e2e.test.js b/plugins/tests/core-plugins-e2e.test.js deleted file mode 100644 index 8e66099..0000000 --- a/plugins/tests/core-plugins-e2e.test.js +++ /dev/null @@ -1,289 +0,0 @@ -/** - * E2E test for core plugins - * Run: node plugins/tests/core-plugins-e2e.test.js - * - * Verifies each core plugin can be loaded without errors and - * that critical tools are available. - */ - -const fs = require("fs"); -const path = require("path"); - -let passed = 0; -let failed = 0; - -function test(name, fn) { - try { - fn(); - passed++; - console.log(` โœ… ${name}`); - } catch (e) { - failed++; - console.log(` โŒ ${name}: ${e.message}`); - } -} - -// ====== Test 1: jsonc-utils.ts (parseJsonc) ====== -console.log("\n๐Ÿ“ฆ jsonc-utils (parseJsonc)"); -test("MCP config from opencode.json parses correctly", () => { - const content = fs.readFileSync("C:\\opencode\\opencode.json", "utf8"); - - // Use the fixed parseJsonc logic - let result = ""; - let inString = false; - let esc = false; - let i = 0; - - while (i < content.length) { - const char = content[i]; - const nextChar = i + 1 < content.length ? content[i + 1] : ""; - - if (esc) { - result += char; - esc = false; - i++; - continue; - } - if (char === "\\" && inString) { - result += char; - esc = true; - i++; - continue; - } - if (char === '"') { - inString = !inString; - result += char; - i++; - continue; - } - - if (!inString) { - if (char === "/" && nextChar === "/") { - while (i < content.length && content[i] !== "\n") i++; - continue; - } - if (char === "/" && nextChar === "*") { - i += 2; - while (i < content.length) { - if (content[i] === "*" && i + 1 < content.length && content[i + 1] === "/") { - i += 2; - break; - } - i++; - } - continue; - } - } - result += char; - i++; - } - - const config = JSON.parse(result); - - if (!config.mcp) throw new Error("Missing mcp config"); - if (!config.provider) throw new Error("Missing provider config"); - if (!config.plugin) throw new Error("Missing plugin config"); - - // Verify all 9 MCP servers - const expectedMcps = [ - "context7", - "filesystem", - "memory", - "git", - "fetch", - "sqlite", - "sequential-thinking", - "language-server", - "type-inject", - ]; - for (const name of expectedMcps) { - if (!config.mcp[name]) throw new Error(`Missing MCP server: ${name}`); - } -}); - -test("MCP config enabled servers are correct", () => { - const content = fs.readFileSync("C:\\opencode\\opencode.json", "utf8"); - let result = ""; - let inString = false, - esc = false, - i = 0; - while (i < content.length) { - const char = content[i], - nc = i + 1 < content.length ? content[i + 1] : ""; - if (esc) { - result += char; - esc = false; - i++; - continue; - } - if (char === "\\" && inString) { - result += char; - esc = true; - i++; - continue; - } - if (char === '"') { - inString = !inString; - result += char; - i++; - continue; - } - if (!inString && char === "/" && nc === "/") { - while (i < content.length && content[i] !== "\n") i++; - continue; - } - if (!inString && char === "/" && nc === "*") { - i += 2; - while (i < content.length) { - if (content[i] === "*" && content[i + 1] === "/") { - i += 2; - break; - } - i++; - } - continue; - } - result += char; - i++; - } - const config = JSON.parse(result); - - let enabledCount = 0; - for (const [name, cfg] of Object.entries(config.mcp)) { - if (cfg.enabled) enabledCount++; - } - if (enabledCount < 9) throw new Error(`Expected all 9 MCP servers enabled, got ${enabledCount}`); -}); - -// ====== Test 2: Plugin file structure validation ====== -console.log("\n๐Ÿ“ฆ Plugin file structure"); - -const corePlugins = [ - "jsonc-utils.ts", - "mcp-manager.ts", - "agent-router.ts", - "model-router.ts", - "skill-manager.ts", - "context-manager.ts", - "index.ts", -]; - -for (const plugin of corePlugins) { - test(`File exists: ${plugin}`, () => { - const filePath = path.join("C:\\opencode\\plugins", plugin); - if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`); - }); -} - -test("No Bun dependencies in core plugins", () => { - for (const plugin of corePlugins) { - const content = fs.readFileSync(path.join("C:\\opencode\\plugins", plugin), "utf8"); - if ( - content.includes('from "bun"') || - content.includes("from 'bun'") || - content.includes("Bun.") - ) { - throw new Error(`${plugin} still has Bun dependency`); - } - } -}); - -test("All core plugins use fixed parseJsonc or JSON.parse (not Bun)", () => { - const pluginsUsingConfig = [ - "mcp-manager.ts", - "agent-router.ts", - "model-router.ts", - "skill-manager.ts", - "context-manager.ts", - "index.ts", - ]; - for (const plugin of pluginsUsingConfig) { - const content = fs.readFileSync(path.join("C:\\opencode\\plugins", plugin), "utf8"); - // Should use parseJsonc OR not use Bun - if ( - content.includes("Bun.file") || - content.includes("Bun.") || - content.includes('from "bun"') - ) { - throw new Error(`${plugin} still uses Bun APIs`); - } - } -}); - -// ====== Test 3: Plugin exports valid Plugin functions ====== -console.log("\n๐Ÿ“ฆ Plugin exports"); - -test("index.ts exports default function", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\index.ts", "utf8"); - if (!content.includes("export default")) throw new Error("Missing default export"); - if (content.includes("import { $ } from")) throw new Error("Still imports bun shell"); -}); - -test("mcp-manager.ts exports default function", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\mcp-manager.ts", "utf8"); - if (!content.includes("export default")) throw new Error("Missing default export"); -}); - -test("agent-router.ts exports default function", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\agent-router.ts", "utf8"); - if (!content.includes("export default")) throw new Error("Missing default export"); -}); - -test("model-router.ts exports default function", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\model-router.ts", "utf8"); - if (!content.includes("export default")) throw new Error("Missing default export"); -}); - -test("skill-manager.ts exports default function", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\skill-manager.ts", "utf8"); - if (!content.includes("export default")) throw new Error("Missing default export"); -}); - -test("context-manager.ts exports default function", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\context-manager.ts", "utf8"); - if (!content.includes("export default")) throw new Error("Missing default export"); -}); - -// ====== Test 4: Tool definition count ====== -console.log("\n๐Ÿ“ฆ Tool registry"); - -function countToolDefinitions(content) { - return (content.match(/tool\(\{/g) || []).length; -} - -test("mcp-manager has 3 tools (list, check, toggle)", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\mcp-manager.ts", "utf8"); - const count = countToolDefinitions(content); - if (count !== 3) throw new Error(`Expected 3 tools, got ${count}`); -}); - -test("agent-router has 2 tools (route_agent, auto_route)", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\agent-router.ts", "utf8"); - const count = countToolDefinitions(content); - if (count !== 2) throw new Error(`Expected 2 tools, got ${count}`); -}); - -test("model-router has 2 tools (check_model, recommend_model)", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\model-router.ts", "utf8"); - const count = countToolDefinitions(content); - if (count !== 2) throw new Error(`Expected 2 tools, got ${count}`); -}); - -test("skill-manager has 3 tools (list, info, search)", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\skill-manager.ts", "utf8"); - const count = countToolDefinitions(content); - if (count !== 3) throw new Error(`Expected 3 tools, got ${count}`); -}); - -test("context-manager has 4 tools (view, add_include, add_exclude, reset)", () => { - const content = fs.readFileSync("C:\\opencode\\plugins\\context-manager.ts", "utf8"); - const count = countToolDefinitions(content); - if (count !== 4) throw new Error(`Expected 4 tools, got ${count}`); -}); - -// ====== Summary ====== -console.log(`\n${"โ”€".repeat(50)}`); -console.log(`Results: ${passed} passed, ${failed} failed`); -console.log(`${"โ”€".repeat(50)}\n`); - -if (failed > 0) process.exit(1); diff --git a/plugins/tests/parseJsonc.test.js b/plugins/tests/parseJsonc.test.js deleted file mode 100644 index 07c54c2..0000000 --- a/plugins/tests/parseJsonc.test.js +++ /dev/null @@ -1,287 +0,0 @@ -/** - * Tests for parseJsonc in jsonc-utils.ts - * Run: node plugins/tests/parseJsonc.test.js - */ -const fs = require("fs"); - -// Import the fixed parseJsonc by evaluating the compiled source -// Since we're in a test, we'll replicate the exact implementation -function parseJsonc(content) { - let result = ""; - let inString = false; - let esc = false; - let i = 0; - - while (i < content.length) { - const char = content[i]; - const nextChar = i + 1 < content.length ? content[i + 1] : ""; - - if (esc) { - result += char; - esc = false; - i++; - continue; - } - - if (char === "\\" && inString) { - result += char; - esc = true; - i++; - continue; - } - - if (char === '"') { - inString = !inString; - result += char; - i++; - continue; - } - - if (!inString) { - if (char === "/" && nextChar === "/") { - while (i < content.length && content[i] !== "\n") i++; - continue; - } - if (char === "/" && nextChar === "*") { - i += 2; - while (i < content.length) { - if (content[i] === "*" && i + 1 < content.length && content[i + 1] === "/") { - i += 2; - break; - } - i++; - } - continue; - } - } - - result += char; - i++; - } - - return JSON.parse(result); -} - -// Test runner -let passed = 0; -let failed = 0; - -function test(name, fn) { - try { - fn(); - passed++; - console.log(` โœ… ${name}`); - } catch (e) { - failed++; - console.log(` โŒ ${name}: ${e.message}`); - } -} - -function assertDeep(actual, expected) { - const a = JSON.stringify(actual); - const b = JSON.stringify(expected); - if (a !== b) throw new Error(`Expected ${b}, got ${a}`); -} - -console.log("\n๐Ÿงช parseJsonc Test Suite\n"); - -// ========== URL Tests (the original bug) ========== -test("URL with https:// (single)", () => { - const json = `{ "url": "https://example.com" }`; - const result = parseJsonc(json); - assertDeep(result, { url: "https://example.com" }); -}); - -test("URL with https:// (multiple)", () => { - const json = `{ - "schema": "https://opencode.ai/config.json", - "api": "https://api.example.com/v1", - "cdn": "http://cdn.example.com" - }`; - const result = parseJsonc(json); - assertDeep(result, { - schema: "https://opencode.ai/config.json", - api: "https://api.example.com/v1", - cdn: "http://cdn.example.com", - }); -}); - -test("open code.json with $schema URL", () => { - const content = fs.readFileSync("C:\\opencode\\opencode.json", "utf8"); - const result = parseJsonc(content); - if (!result || !result.model) throw new Error("Failed to parse opencode.json"); - if (!result.mcp) throw new Error("Missing mcp config"); - if (!result.provider) throw new Error("Missing provider config"); -}); - -// ========== Comment Tests ========== -test("single-line comment", () => { - const json = `{ - // This is a comment - "key": "value" - }`; - const result = parseJsonc(json); - assertDeep(result, { key: "value" }); -}); - -test("multi-line comment", () => { - const json = `{ - /* This is a - multi-line comment */ - "key": "value" - }`; - const result = parseJsonc(json); - assertDeep(result, { key: "value" }); -}); - -test("trailing single-line comment", () => { - const json = `{ "key": "value" } // trailing comment`; - const result = parseJsonc(json); - assertDeep(result, { key: "value" }); -}); - -test("comment with URL-like // inside it", () => { - // The comment should be stripped even though the comment contains // - const json = `{ - // Note: see https://example.com for docs - "key": "value" - }`; - const result = parseJsonc(json); - assertDeep(result, { key: "value" }); -}); - -test("multiple comments before and after", () => { - const json = `// Top comment -{ - // Inline comment - "key": "value", // trailing - /* block - comment */ - "other": 123 -} -// Bottom comment`; - const result = parseJsonc(json); - assertDeep(result, { key: "value", other: 123 }); -}); - -// ========== String escaping ========== -test("escaped backslash in string", () => { - const json = `{ "path": "C:\\\\Users\\\\test" }`; - const result = parseJsonc(json); - assertDeep(result, { path: "C:\\Users\\test" }); -}); - -test("escaped quote in string", () => { - const json = `{ "key": "he said \\"hello\\"" }`; - const result = parseJsonc(json); - assertDeep(result, { key: 'he said "hello"' }); -}); - -test("string with unicode", () => { - const json = `{ "key": "\\u0048\\u0065\\u006C\\u006C\\u006F" }`; - const result = parseJsonc(json); - assertDeep(result, { key: "Hello" }); -}); - -test("string with non-ASCII characters (en-dash)", () => { - const json = `{ "key": "backend โ€“ logic" }`; - const result = parseJsonc(json); - assertDeep(result, { key: "backend โ€“ logic" }); -}); - -// ========== Strings containing comment-like patterns ========== -test("string containing // (NOT a comment)", () => { - const json = `{ "key": "// this is NOT a comment" }`; - const result = parseJsonc(json); - assertDeep(result, { key: "// this is NOT a comment" }); -}); - -test("string containing /* (NOT a comment)", () => { - const json = `{ "key": "/* this is NOT a comment */" }`; - const result = parseJsonc(json); - assertDeep(result, { key: "/* this is NOT a comment */" }); -}); - -test("string with regex-looking pattern", () => { - const json = `{ "pattern": "//.*$/gm" }`; - const result = parseJsonc(json); - assertDeep(result, { pattern: "//.*$/gm" }); -}); - -// ========== Edge Cases ========== -test("empty object", () => { - const result = parseJsonc("{}"); - assertDeep(result, {}); -}); - -test("empty object with comment", () => { - const result = parseJsonc("{} // empty"); - assertDeep(result, {}); -}); - -test("nested objects and arrays", () => { - const json = `{ - "items": [ - { "id": 1, "name": "foo" }, - /* second item */ - { "id": 2, "name": "bar" } - ], - "meta": { - "total": 2, - // page info - "page": 1 - } - }`; - const result = parseJsonc(json); - assertDeep(result, { - items: [ - { id: 1, name: "foo" }, - { id: 2, name: "bar" }, - ], - meta: { total: 2, page: 1 }, - }); -}); - -test("boolean and null values", () => { - const json = `{ "a": true, "b": false, "c": null }`; - const result = parseJsonc(json); - assertDeep(result, { a: true, b: false, c: null }); -}); - -test("numbers (int and float)", () => { - const json = `{ "int": 42, "float": 3.14, "neg": -1, "exp": 1e10 }`; - const result = parseJsonc(json); - assertDeep(result, { int: 42, float: 3.14, neg: -1, exp: 1e10 }); -}); - -test("comment-only file", () => { - const json = `/* nothing here */`; - try { - parseJsonc(json); - throw new Error("Should have thrown"); - } catch (e) { - // Expected - empty comment-stripped JSON is invalid - if (!e.message.includes("JSON")) throw e; - } -}); - -// ========== MCP Config specific ========== -test("MCP config with url-like server names", () => { - const json = `{ - "mcp": { - "context7": { - "command": ["npx", "-y", "@upstash/context7-mcp@latest"], - "enabled": true - } - } - }`; - const result = parseJsonc(json); - assertDeep(result.mcp.context7.command, ["npx", "-y", "@upstash/context7-mcp@latest"]); -}); - -// ========== Summary ========== -console.log(`\n${"โ”€".repeat(40)}`); -console.log(`Results: ${passed} passed, ${failed} failed`); -console.log(`${"โ”€".repeat(40)}\n`); - -if (failed > 0) process.exit(1); diff --git a/quick-bench.mjs b/quick-bench.mjs deleted file mode 100644 index df25048..0000000 --- a/quick-bench.mjs +++ /dev/null @@ -1,181 +0,0 @@ -#!/usr/bin/env node - -const LM_STUDIO_URL = "http://192.168.1.12:1234/v1"; - -const TEST_TEXTS_BATCH = [ - "function auth() { return true; }", - "const user = { name: 'test' };", - "SELECT * FROM users WHERE id = 1", - "class Controller { }", -]; - -function log(msg) { - console.log(`[${new Date().toISOString().split('T')[1].slice(0,8)}] ${msg}`); -} - -function ms(n) { return n < 1000 ? `${n.toFixed(0)}ms` : `${(n/1000).toFixed(2)}s`; } - -async function getModels() { - const r = await fetch(`${LM_STUDIO_URL}/api/v1/models`); - return (await r.json()).models || []; -} - -async function loadModel(modelKey, ctxLen) { - const r = await fetch(`${LM_STUDIO_URL}/api/v1/models/load`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ model: modelKey, context_length: ctxLen || 512, echo_load_config: true }) - }); - return r.json(); -} - -async function unloadModel(id) { - await fetch(`${LM_STUDIO_URL}/api/v1/models/unload`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ instance_id: id }) - }); -} - -async function embed(model, texts) { - const r = await fetch(`${LM_STUDIO_URL}/embeddings`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ model, input: texts }) - }); - return r.json(); -} - -async function chat(model, draftModel, messages, maxTokens = 100) { - const body = { model, messages, max_tokens: maxTokens, temperature: 0.7 }; - if (draftModel) body.draft_model = draftModel; - const r = await fetch(`${LM_STUDIO_URL}/chat/completions`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(body) - }); - return r.json(); -} - -async function benchEmbed(model, label, ctxLen) { - log(`Testing embed: ${label} (ctx=${ctxLen})`); - const loaded = await loadModel(model, ctxLen); - if (loaded.error) { - log(` LOAD ERROR: ${loaded.error.message}`); - return null; - } - log(` Load: ${ms(loaded.load_time_seconds * 1000)} | Config: ctx=${loaded.load_config?.context_length}`); - - const times = []; - for (let i = 0; i < 5; i++) { - const t0 = Date.now(); - const r = await embed(model, TEST_TEXTS_BATCH); - if (r.error) { log(` EMBED ERROR: ${r.error.message}`); continue; } - times.push(Date.now() - t0); - log(` Run ${i+1}: ${ms(times[i])} (${r.data?.length || 0} emb, ${r.data?.[0]?.embedding?.length || 0} dims)`); - } - - await unloadModel(loaded.instance_id); - - if (!times.length) return null; - const avg = times.reduce((a,b) => a+b, 0) / times.length; - log(` AVG: ${ms(avg)}`); - return { model, label, ctxLen, loadTime: loaded.load_time_seconds, avg, times }; -} - -async function benchSpeculative(mainModel, draftModel) { - log(`\nSpeculative Decoding Test`); - log(` Main: ${mainModel}`); - log(` Draft: ${draftModel}`); - - const messages = [ - { role: "system", content: "You are a helpful assistant." }, - { role: "user", content: "Explain what authentication is in one sentence." } - ]; - - // WITHOUT draft - log(`\n [1] WITHOUT speculative decoding`); - const m1 = await loadModel(mainModel); - if (m1.error) { log(` Load ERROR: ${m1.error.message}`); return; } - log(` Main loaded: ${ms(m1.load_time_seconds * 1000)}`); - - const t0 = Date.now(); - const r1 = await chat(mainModel, null, messages); - const t1 = Date.now() - t0; - if (r1.error) { log(` Chat ERROR: ${r1.error.message}`); } - else log(` Response: ${ms(t1)} | Tokens: ${r1.usage?.completion_tokens || '?'}`); - await unloadModel(m1.instance_id); - - // WITH draft - log(`\n [2] WITH speculative decoding`); - const m2 = await loadModel(mainModel); - const d2 = await loadModel(draftModel); - if (m2.error || d2.error) { log(` Load ERROR`); return; } - log(` Main: ${ms(m2.load_time_seconds * 1000)}, Draft: ${ms(d2.load_time_seconds * 1000)}`); - - const t2 = Date.now(); - const r2 = await chat(mainModel, draftModel, messages); - const t3 = Date.now() - t2; - if (r2.error) { log(` Chat ERROR: ${r2.error.message}`); } - else log(` Response: ${ms(t3)} | Tokens: ${r2.usage?.completion_tokens || '?'}`); - await unloadModel(m2.instance_id); - await unloadModel(d2.instance_id); - - log(`\n Speedup: ${(t1/t3).toFixed(2)}x ${t3 < t1 ? 'faster' : 'slower'}`); -} - -async function main() { - log("=".repeat(50)); - log("LM Studio Quick Benchmark"); - log("=".repeat(50)); - - const models = await getModels(); - log(`\nModels: ${models.length}`); - models.forEach(m => log(` ${m.type}: ${m.key} (${(m.size_bytes/1e6).toFixed(0)}MB, ctx=${m.max_context_length})`)); - - // Embedding tests - log("\n" + "=".repeat(50)); - log("EMBEDDING BENCHMARKS"); - log("=".repeat(50)); - - const nomic = models.find(m => m.key.includes("nomic")); - const qwenEmbed = models.find(m => m.key.includes("qwen3-embedding")); - - const results = []; - - if (nomic) { - const r = await benchEmbed(nomic.key, "nomic-embed (84MB)", 512); - if (r) results.push(r); - } - - if (qwenEmbed) { - const r = await benchEmbed(qwenEmbed.key, "qwen3-embed (2.5GB)", 4096); - if (r) results.push(r); - } - - // Speculative decoding tests - log("\n" + "=".repeat(50)); - log("SPECULATIVE DECODING TESTS"); - log("=".repeat(50)); - - const llms = models.filter(m => m.type === "llm"); - const mainModel = llms.find(m => m.key.includes("4b")) || llms[0]; - const draftModel = llms.find(m => m.key.includes("0.8b") || m.key.includes("1b") || m.key.includes("2b")); - - if (mainModel && draftModel) { - await benchSpeculative(mainModel.key, draftModel.key); - } - - // Summary - if (results.length) { - log("\n" + "=".repeat(50)); - log("SUMMARY"); - log("=".repeat(50)); - results.sort((a,b) => a.avg - b.avg); - results.forEach((r, i) => { - log(`${i+1}. ${r.label}: ${ms(r.avg)} avg (load ${ms(r.loadTime * 1000)})`); - }); - } -} - -main().catch(console.error); diff --git a/readme.md b/readme.md index 3c7d77c..ff8fe96 100644 --- a/readme.md +++ b/readme.md @@ -44,6 +44,7 @@ All documentation is centralized in the [`docs/`](docs/) folder: | [**Workflows-Guide.md**](docs/Workflows-Guide.md) | Multi-agent workflow automation | | [**Skills-Guide.md**](docs/Skills-Guide.md) | 63+ specialized capabilities with MCP integration | | [**Plugins-Guide.md**](docs/Plugins-Guide.md) | 10+ plugins including agent-router | +| [**Brain-Plugin-Docs.md**](docs/brain-plugin-docs.md) | RAG system with local embeddings and Rust sidecar | | [**Agents-Guide.md**](docs/Agents-Guide.md) | Complete agent reference (12 agents) | | [**Agent-Loop-Guide.md**](docs/Agent-Loop-Guide.md) | Iterative execution and retry patterns | | [**Prompting-Guide.md**](docs/Prompting-Guide.md) | Effective prompt techniques | @@ -82,14 +83,21 @@ lead-strategist (Orchestration) ## Features -### Intelligent Agent Routing +### RAG Context Retrieval (Brain Plugin) -The **agent-router plugin** automatically recommends the best agent for your task: +Automatic codebase context injection using local embeddings: -```bash -Ask lead-strategist: "Which agent should handle Laravel authentication?" -โ†’ Recommended Agent: backend-laravel -``` +- **Local Embeddings**: nomic-embed model via LM Studio (no cloud) +- **Rust Sidecar**: High-performance vector search with HNSW +- **Tree-sitter Chunking**: AST-aware code splitting for semantic chunks +- **Intent Classification**: 7 intent types with adaptive retrieval strategies + +**How it works:** +1. User types query โ†’ Intent classified (debug/learn/feature/etc.) +2. Relevant code chunks retrieved via vector search +3. Context prepended to LLM prompt for grounded responses + +**Status:** TS plugin loads successfully; Rust sidecar requires Linux/Mac for building. ### Workflow Orchestration diff --git a/scripts/update-agents.js b/scripts/update-agents.js index 90cb1d2..292fc3f 100644 --- a/scripts/update-agents.js +++ b/scripts/update-agents.js @@ -7,6 +7,23 @@ const fs = require("fs"); const path = require("path"); const agentsDir = "agents"; +const brainToolNames = [ + "brain_diagnostic", + "brain_sidecar_status", + "brain_status", + "brain_search", + "brain_embed_test", + "brain_index_project", +]; +const brainWorkflowSection = ` +- Check Brain health with brain_sidecar_status or brain_diagnostic before non-trivial debugging, feature work, refactors, architecture analysis, or documentation audits. +- If the index is empty, stale, or missing expected results, run brain_index_project before relying on retrieval. +- Use brain_search for semantic codebase discovery, then read the top matching files directly before making decisions or edits. +- Use brain_embed_test when search quality matters or when choosing better query terms for a complex investigation. +- After broad edits or generated files, confirm Brain can see the new context with brain_status or a targeted brain_search. + + +`; const modernAgents = { "core-factory": { description: @@ -586,11 +603,22 @@ for (const [name, config] of Object.entries(modernAgents)) { const toolList = Object.entries(config.tools) .filter(([_, v]) => v === true) .map(([k]) => k); + for (const tool of brainToolNames) { + if (!toolList.includes(tool)) { + toolList.push(tool); + } + } toolsSection = `\n**Tools**: ${toolList.join(", ")}\n`; } // Combine: frontmatter + body - const newContent = frontmatter + toolsSection + "\n" + content.trim() + "\n"; + let body = content.trim(); + if (!body.includes("")) { + body = body.includes("") + ? body.replace("", brainWorkflowSection + "") + : `${body}\n\n${brainWorkflowSection.trimEnd()}`; + } + const newContent = frontmatter + toolsSection + "\n" + body + "\n"; fs.writeFileSync(filePath, newContent); console.log(`โœ… Updated: ${filePath}`); diff --git a/skills/index.json b/skills/index.json index 133432b..afee409 100644 --- a/skills/index.json +++ b/skills/index.json @@ -9,6 +9,13 @@ "agents": ["core-factory", "lead-strategist"], "category": "research" }, + "knowledge-architect" : { + "path": "skills/knowledge-architect/SKILL.md", + "name": "knowledge-architect", + "description": "Architect and manage knowledge graphs for project success", + "agents": ["docs-curator", "lead-architect"], + "category": "research" + }, "deep-research": { "path": "skills/deep-research/SKILL.md", "name": "deep-research", diff --git a/skills/knowledge-architect/SKILL.md b/skills/knowledge-architect/SKILL.md new file mode 100644 index 0000000..6287e61 --- /dev/null +++ b/skills/knowledge-architect/SKILL.md @@ -0,0 +1,110 @@ +--- +name: knowledge-architect +description: Consolidate digital signage, SignSync, Media OS, SaaS signage, restaurant POS signage, CCTV/media nexus, Tauri desktop, and research documentation into a domain-first, source-traced Media OS knowledge base. +--- + +# Media OS Knowledge Architect + +Use this skill to process fragmented signage documentation into the canonical `media-os-knowledge/` structure. Raw project folders are evidence and must remain unchanged. + +## Product Center + +Media OS is canonical. SignSync, SaaS signage, restaurant/POS signage, CCTV, IPTV/radio, AI, and fleet management are modules or product layers around a local-first Media OS runtime. + +## Output Structure + +Maintain this structure: + +```text +media-os-knowledge/ + agent-index.md + source-manifest.md + 00-index.md + 01-source-inventory.md + 02-domain-map.md + 03-work-spec.md + 04-feature-matrix.md + 05-workflows.md + 06-architecture-decisions.md + 07-improvement-backlog.md + domains/ + player-runtime/ + layout-composition/ + content-assets/ + media-sources/ + restaurant-pos/ + device-fleet/ + sync-offline/ + orchestration/ + security-licensing/ + ai-optimization/ + research-strategy/ + extraction-cards/ +``` + +Each domain folder must contain: + +- `README.md` +- `specs.md` +- `workflows.md` +- `decisions.md` +- `gaps.md` + +## Domain Routing + +| Source topic | Domain | +| --- | --- | +| player, renderer, display, window, playback, startup | `player-runtime` | +| screen, zone, template, widget, canvas, composer, editor | `layout-composition` | +| asset, content, playlist, schedule, campaign, media library | `content-assets` | +| source, stream, RTSP, CCTV, M3U, HLS, radio, YouTube, camera | `media-sources` | +| menu, POS, order, payment, printing, TV display | `restaurant-pos` | +| device, tenant, workspace, onboarding, pairing, fleet, heartbeat | `device-fleet` | +| offline, sync, local DB, cache, queue, conflict | `sync-offline` | +| event, orchestrator, service registry, workflow, mesh, federation | `orchestration` | +| auth, security, role, license, audit, update, secret | `security-licensing` | +| AI, generation, guard, optimization, recommendation | `ai-optimization` | +| market, strategy, roadmap, Android TV, study, report, pitch | `research-strategy` | + +## Classification Labels + +Use these labels in `gaps.md`, extraction cards, and backlog updates: + +- `keep-exact`: existing idea is already simple and canonical. +- `add-missing`: useful idea is absent from canonical docs. +- `merge-duplicate`: same idea appears in multiple projects. +- `expand-needed`: idea is promising but incomplete. +- `simplify-with-ADR`: old logic is complex and a simpler Media OS approach exists. +- `obsolete-history`: historical detail is not part of current product. +- `open-question`: product choice cannot be resolved from sources. + +## Source Reference Standard + +Canonical docs cite source folders plus key files. Example: + +`Source evidence: concepts/screen.md; nexussignage-docs/Composer_Module.md; syngsinc-docs/features/TEMPLATES.md` + +Do not cite every sentence. Cite major requirements, workflows, decisions, and unresolved gaps. + +## Processing Workflow + +1. Build or update `source-manifest.md`. +2. Update `01-source-inventory.md` with counts, source families, key files, and deferred non-Markdown files. +3. Route Markdown source clusters into domains. +4. For each domain, update `README.md`, `specs.md`, `workflows.md`, `decisions.md`, and `gaps.md`. +5. Update overview files after domain processing. +6. Create extraction cards only for high-value, conflicting, duplicate-heavy, or unclear source clusters. + +## Simplification Rule + +When historical projects disagree, prefer the simplest Media OS-compatible approach in canonical specs. Preserve older/complex approaches in ADRs or `gaps.md`. + +## Quality Checklist + +- [ ] Raw source folders are untouched. +- [ ] Every domain has the five required files. +- [ ] Every domain references at least one source folder and key file. +- [ ] Specs use `SPEC-*` identifiers. +- [ ] Mermaid diagrams are fenced with `mermaid`. +- [ ] Non-Markdown files are inventoried before deep extraction. +- [ ] Backlog items are actionable and mapped to domains. diff --git a/skills/knowledge-architect/_meta.json b/skills/knowledge-architect/_meta.json new file mode 100644 index 0000000..b751bff --- /dev/null +++ b/skills/knowledge-architect/_meta.json @@ -0,0 +1 @@ +{"id":"knowledge-architect","version":"0.2.0"} diff --git a/skills/knowledge-architect/references/architecture-decisions.md b/skills/knowledge-architect/references/architecture-decisions.md new file mode 100644 index 0000000..f1a9f0a --- /dev/null +++ b/skills/knowledge-architect/references/architecture-decisions.md @@ -0,0 +1,175 @@ +# SignSync Architecture Decisions + +## Decision 1: Offline-First Over Cloud-Native + +### Context +Multiple iterations showed cloud dependency caused issues in restaurants with unreliable internet. + +### Options Considered +1. Cloud-first with local cache +2. Hybrid (online/offline modes) +3. Offline-first with optional cloud sync + +### Decision +**Offline-first with optional cloud sync** + +### Rationale +- Restaurants operate 24/7 regardless of internet +- Local storage is faster and more reliable +- Cloud sync adds value without being mandatory + +### Consequences +- All content must be downloadable and cacheable +- Sync queue for changes made offline +- Conflict resolution for multi-device scenarios + +--- + +## Decision 2: Single Executable Deployment + +### Context +Enterprise iteration required complex installers that caused support overhead. + +### Options Considered +1. Native installer (MSI, DEB) +2. Portable executable +3. Container-based deployment + +### Decision +**Single portable executable (.exe / binary)** + +### Rationale +- Zero-configuration deployment +- USB-stick portability +- Easy rollback to previous versions + +### Consequences +- All dependencies bundled +- Larger file size acceptable +- No system-level installation required + +--- + +## Decision 3: Rust for Core Engine + +### Context +Laravel PHP couldn't run on target hardware (low-spec devices, no server). + +### Options Considered +1. Electron (JavaScript) +2. Tauri (Rust + WebView) +3. Native Qt/Gtk + +### Decision +**Tauri (Rust + WebView)** + +### Rationale +- Native performance for media playback +- Small binary size compared to Electron +- Rust ecosystem for hardware control +- Web technologies for UI flexibility + +### Consequences +- Development requires Rust knowledge +- Native OS features accessible via Rust +- Cross-platform with single codebase + +--- + +## Decision 4: M3U/M3U8 as Primary Playlist Format + +### Context +Need standardized format for IPTV streams that users already know. + +### Options Considered +1. Custom JSON format +2. Custom XML format +3. Standard M3U/M3U8 + +### Decision +**M3U/M3U8 with custom extensions** + +### Rationale +- Industry standard for IPTV +- Users already have playlists +- Easy to import from existing sources + +### Consequences +- Must parse and extend M3U format +- May need custom tags for signage metadata +- Backward compatibility with standard players + +--- + +## Decision 5: Zone-Based Layout Engine + +### Context +Need flexible layout system that supports multiple content types simultaneously. + +### Options Considered +1. Template-based fixed layouts +2. Drag-and-drop canvas +3. Zone-based grid system + +### Decision +**Zone-based grid system with template presets** + +### Rationale +- Simpler than full canvas editor +- Predictable rendering for signage +- Easy to define in configuration + +### Consequences +- Limited but sufficient flexibility +- Templates provide common patterns +- Custom zones require editing config files + +--- + +## Decision 6: CCTV as Media Source + +### Context +Users wanted to display camera feeds alongside content. + +### Options Considered +1. Separate CCTV app with mirroring +2. Integrated RTSP stream as zone +3. Picture-in-picture overlay + +### Decision +**Integrated RTSP stream as media source** + +### Rationale +- Unified playback engine +- Same scheduling system for all sources +- Simplifies deployment + +### Consequences +- Need RTSP client library +- Bandwidth considerations for multiple streams +- Motion detection integration possible + +--- + +## Decision 7: YouTube/Radio as Media Sources + +### Context +Users wanted streaming content (YouTube, internet radio) alongside local media. + +### Options Considered +1. WebView embed for all streaming +2. Native playback with stream detection +3. Plugin system for different sources + +### Decision +**Native playback with stream type detection** + +### Rationale +- Consistent playback behavior +- Better performance than WebView +- Unified control interface + +### Consequences +- Need protocols support (HLS, DASH, etc.) +- May need web extraction for YouTube +- Radio stream handling required \ No newline at end of file diff --git a/skills/knowledge-architect/references/classification-labels.md b/skills/knowledge-architect/references/classification-labels.md new file mode 100644 index 0000000..0c23085 --- /dev/null +++ b/skills/knowledge-architect/references/classification-labels.md @@ -0,0 +1,11 @@ +# Classification Labels + +| Label | Meaning | Canonical action | +| --- | --- | --- | +| `keep-exact` | Source idea is already simple and useful | Preserve in domain docs | +| `add-missing` | Useful idea is absent from domain docs | Add spec/workflow/decision/backlog | +| `merge-duplicate` | Repeated ideas across sources | Keep one canonical version | +| `expand-needed` | Incomplete but valuable | Add acceptance criteria and backlog | +| `simplify-with-ADR` | Complex old logic can be simplified | Use simple spec and record ADR | +| `obsolete-history` | Historical detail only | Keep lesson if useful | +| `open-question` | Product choice unresolved | Add to `gaps.md` and backlog | diff --git a/skills/knowledge-architect/references/digital-signage-patterns.md b/skills/knowledge-architect/references/digital-signage-patterns.md new file mode 100644 index 0000000..33b19d0 --- /dev/null +++ b/skills/knowledge-architect/references/digital-signage-patterns.md @@ -0,0 +1,95 @@ +# Digital Signage System Patterns + +## Display Patterns + +### Single Screen Display +- **Use Case**: Menu boards, waiting room displays +- **Characteristics**: Fixed layout, scheduled content rotation +- **Technology**: WebView, native rendering + +### Multi-Monitor Display +- **Use Case**: Large venues, control rooms +- **Characteristics**: Independent zones per screen, synchronized playback +- **Technology**: Multi-instance rendering, GPU composition + +### Video Wall +- **Use Case**: Advertising billboards, stadiums +- **Characteristics**: Unified display from multiple screens, bezel compensation +- **Technology**: Edge blending, bezel correction + +## Content Management Patterns + +### Playlist-Based +``` +Schedule โ†’ Playlist โ†’ Zone โ†’ Content Item +``` +- Simple linear playback +- Time-based transitions +- Minimal interactivity + +### Zone-Based Layout +``` +Screen โ†’ Zone 1 (Video) โ†’ Zone 2 (Ticker) โ†’ Zone 3 (Menu) +``` +- Multiple content areas simultaneously +- Independent update cycles +- Overlapping media types + +### Template System +- Predefined layouts for common use cases +- Parameterized content slots +- Theme-based styling + +## Media Source Patterns + +### Local Media +- Images: JPG, PNG, WebP, SVG +- Video: MP4, WebM, HLS streams +- Audio: MP3, AAC for background + +### Streaming Sources +- M3U playlists (IPTV) +- YouTube embeds +- Web radio streams +- RTSP for CCTV + +### Dynamic Content +- RSS feeds +- Weather data +- Social media integration +- Real-time inventory + +## Integration Patterns + +### Business System Integration +- POS integration for menu updates +- Inventory sync for availability +- Pricing engine for promotions + +### Control System Integration +- Schedule triggers +- Sensor-based activation +- Remote management + +## Architecture Patterns + +### Layered Architecture +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Presentation โ”‚ โ†’ Screens, UI, Templates +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ Business Logic โ”‚ โ†’ Scheduling, Playback, Transitions +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ Data Access โ”‚ โ†’ Local DB, File System, APIs +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Event-Driven Architecture +``` +Media Event โ†’ Event Bus โ†’ Handler โ†’ State Update โ†’ Render +``` + +### Offline-First Design +- Local cache for all content +- Queue-based sync for updates +- Conflict resolution for concurrent edits \ No newline at end of file diff --git a/skills/knowledge-architect/references/domain-routing.md b/skills/knowledge-architect/references/domain-routing.md new file mode 100644 index 0000000..729015f --- /dev/null +++ b/skills/knowledge-architect/references/domain-routing.md @@ -0,0 +1,17 @@ +# Domain Routing Reference + +Use this table when source material fits more than one place. + +| Concept | Primary domain | Secondary domain | +| --- | --- | --- | +| Player startup | Player Runtime | Sync and Offline | +| Last-known-good state | Sync and Offline | Player Runtime | +| Template runtime | Layout and Composition | Content and Assets | +| Playlist scheduling | Content and Assets | Sync and Offline | +| RTSP/CCTV | Media Sources | Security and Licensing | +| Restaurant menu boards | Restaurant/POS | Layout and Composition | +| Device heartbeats | Device and Fleet | Orchestration | +| Remote commands | Device and Fleet | Orchestration | +| License checks | Security and Licensing | Player Runtime | +| AI template generation | AI and Optimization | Layout and Composition | +| Market/product strategy | Research and Strategy | Relevant domain by thesis | diff --git a/skills/knowledge-architect/references/technology-comparison.md b/skills/knowledge-architect/references/technology-comparison.md new file mode 100644 index 0000000..d0f4191 --- /dev/null +++ b/skills/knowledge-architect/references/technology-comparison.md @@ -0,0 +1,84 @@ +# Technology Stack Comparison + +## Laravel Livewire (Iteration 1) + +### Strengths +- Rapid prototyping with PHP ecosystem +- Real-time reactivity without JavaScript +- Database-first design +- Familiar MVC pattern + +### Weaknesses +- Server dependency for operation +- Heavy resource usage +- Not suitable for offline-first +- Scalability concerns for large deployments + +### SignSync Use Case +- Restaurant menu management +- Admin dashboard +- Server-side scheduling + +## SaaS Multi-Tenancy (Iteration 2) + +### Strengths +- Multi-tenant isolation +- Cloud-native deployment +- Centralized management +- Scalable architecture + +### Weaknesses +- Requires constant connectivity +- Complex deployment +- Monthly costs +- Data sovereignty issues + +### SignSync Use Case +- Enterprise fleet management +- Centralized content distribution +- Analytics and reporting + +## React/SolidJS + Rust (Iteration 3) + +### Strengths +- Cross-platform (Windows, Linux, macOS) +- Single executable deployment +- Native performance +- Offline-capable +- Lightweight resource usage + +### Weaknesses +- Steeper learning curve +- Build complexity +- Less mature ecosystem for signage + +### SignSync Use Case +- Core display engine +- Local content management +- Hardware control (HDMI, USB) + +## Recommendation for SignSync + +### MVP Layer (Core Display) +- **Technology**: Rust with WebView (Tauri) +- **Reason**: Single .exe, offline-capable, fast rendering + +### Content Management Layer +- **Technology**: React/SolidJS for admin UI +- **Reason**: Rich interaction, responsive design + +### Data Layer +- **Technology**: SQLite for local, optional cloud sync +- **Reason**: Zero-config, portable, offline-first + +### Integration Layer +- **Technology**: REST API + WebSocket +- **Reason**: Flexibility for future SaaS expansion + +## Evolution Path + +``` +MVP (Rust) โ†’ Add React Admin UI โ†’ Add SQLite โ†’ Add Cloud Sync โ†’ Enterprise SaaS +``` + +Each iteration adds capabilities without breaking existing functionality. \ No newline at end of file diff --git a/skills/knowledge-architect/scripts/quick_validate.py b/skills/knowledge-architect/scripts/quick_validate.py new file mode 100644 index 0000000..72d78ee --- /dev/null +++ b/skills/knowledge-architect/scripts/quick_validate.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +""" +Validate the local knowledge-architect skill bundle. +""" + +import json +import sys +from pathlib import Path + +SKILL_DIR = Path(__file__).parent.parent + +EXPECTED_FILES = [ + "SKILL.md", + "_meta.json", + "references/domain-routing.md", + "references/classification-labels.md", +] + + +def validate_frontmatter() -> tuple[bool, str]: + content = (SKILL_DIR / "SKILL.md").read_text(encoding="utf-8") + if not content.startswith("---"): + return False, "SKILL.md must start with YAML frontmatter" + end = content.find("---", 3) + if end == -1: + return False, "SKILL.md frontmatter is not closed" + frontmatter = content[3:end] + for key in ("name:", "description:"): + if key not in frontmatter: + return False, f"missing frontmatter key: {key}" + return True, "OK" + + +def validate_meta() -> tuple[bool, str]: + try: + meta = json.loads((SKILL_DIR / "_meta.json").read_text(encoding="utf-8")) + except json.JSONDecodeError as error: + return False, f"_meta.json is invalid JSON: {error}" + for key in ("id", "version"): + if key not in meta: + return False, f"_meta.json missing {key}" + return True, "OK" + + +def main() -> int: + errors = [] + for relative in EXPECTED_FILES: + if not (SKILL_DIR / relative).exists(): + errors.append(f"missing file: {relative}") + + valid, message = validate_frontmatter() + if not valid: + errors.append(message) + else: + print(f"Frontmatter validation: {message}") + + valid, message = validate_meta() + if not valid: + errors.append(message) + else: + print(f"_meta.json validation: {message}") + + if errors: + print("VALIDATION FAILED:") + for error in errors: + print(f" - {error}") + return 1 + + print("\nOK: All validation checks passed!") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/test-brain.mjs b/test-brain.mjs deleted file mode 100644 index efbfde3..0000000 --- a/test-brain.mjs +++ /dev/null @@ -1,220 +0,0 @@ -#!/usr/bin/env node -import { DecisionTree } from "./brain-plugin/tree/engine.js"; -import { LMStudioProvider } from "./brain-plugin/provider/lmstudio.js"; -import { searcher } from "./brain-plugin/retrieval/searcher.js"; -import { contextInjector } from "./brain-plugin/context/injector.js"; -import { sessionMemory } from "./brain-plugin/state/session.js"; - -const LM_STUDIO_URL = "http://192.168.1.12:1234/v1"; - -console.log("=" .repeat(50)); -console.log("Brain Plugin Test Suite"); -console.log("=" .repeat(50)); - -async function testProvider() { - console.log("\n[TEST 1] LM Studio Provider"); - const provider = new LMStudioProvider(LM_STUDIO_URL); - - console.log(" - Fetching models..."); - try { - const modelsResponse = await fetch(`${LM_STUDIO_URL}/models`); - const models = await modelsResponse.json(); - console.log(` - Found ${models.data?.length || 0} models`); - } catch (error) { - console.log(` - ERROR: ${error.message}`); - return false; - } - - console.log(" - Testing embedding..."); - try { - const embedHandle = { id: "test", modelId: "text-embedding-nomic-embed-text-v1.5", loadedAt: Date.now() }; - const embeddings = await provider.embed(embedHandle, ["Hello world"]); - console.log(` - Embedding dimensions: ${embeddings[0]?.length || 0}`); - } catch (error) { - console.log(` - ERROR: ${error.message}`); - return false; - } - - console.log(" - Testing chat..."); - try { - const chatHandle = { id: "test", modelId: "qwen3.5-4b", loadedAt: Date.now() }; - const response = await provider.chat(chatHandle, [{ role: "user", content: "Say 'test OK' only" }], { maxTokens: 50 }); - console.log(` - Chat response: ${response.substring(0, 50)}...`); - } catch (error) { - console.log(` - ERROR: ${error.message}`); - return false; - } - - console.log(" [PASS] LM Studio Provider"); - return true; -} - -async function testDecisionTree() { - console.log("\n[TEST 2] Decision Tree"); - - try { - const tree = new DecisionTree(); - - console.log(" - Classifying 'fix the auth bug'..."); - const result1 = tree.classify("fix the auth bug", { message: "fix the auth bug", recentFiles: [], diagnostics: [] }); - console.log(` - Intent: ${result1.node.intent}, Score: ${result1.score.toFixed(2)}`); - - console.log(" - Classifying 'how does the router work'..."); - const result2 = tree.classify("how does the router work", { message: "how does the router work", recentFiles: [], diagnostics: [] }); - console.log(` - Intent: ${result2.node.intent}, Score: ${result2.score.toFixed(2)}`); - - console.log(" - Classifying 'add user authentication'..."); - const result3 = tree.classify("add user authentication", { message: "add user authentication", recentFiles: [], diagnostics: [] }); - console.log(` - Intent: ${result3.node.intent}, Score: ${result3.score.toFixed(2)}`); - - const stats = tree.getStats(); - console.log(` - Total nodes: ${stats.totalNodes}`); - - console.log(" [PASS] Decision Tree"); - return true; - } catch (error) { - console.log(` - ERROR: ${error.message}`); - return false; - } -} - -async function testContextInjector() { - console.log("\n[TEST 3] Context Injector"); - - try { - const context = { - chunks: [ - { text: "function auth() { return true; }", path: "src/auth.js", startLine: 1, endLine: 2 }, - { text: "class User { constructor(name) { this.name = name; } }", path: "src/user.js", startLine: 5, endLine: 7 } - ], - totalChunks: 2 - }; - - const injected = contextInjector.inject("How does auth work?", context); - console.log(` - Injected message length: ${injected.length} chars`); - - const formatted = contextInjector.formatResults(context); - console.log(` - Formatted results length: ${formatted.length} chars`); - - console.log(" [PASS] Context Injector"); - return true; - } catch (error) { - console.log(` - ERROR: ${error.message}`); - return false; - } -} - -async function testSessionMemory() { - console.log("\n[TEST 4] Session Memory"); - - try { - sessionMemory.reset(); - - sessionMemory.recordDecision({ timestamp: Date.now(), intent: "debug", strategy: "test", contextCount: 5, query: "test" }); - sessionMemory.markSuccess(); - sessionMemory.markFileDirty("src/test.js"); - - const summary = sessionMemory.getSummary(); - console.log(` - Summary: ${summary.split("\n").length} lines`); - - const memory = sessionMemory.getMemory(); - console.log(` - Decisions: ${memory.decisions.length}`); - console.log(` - Successes: ${memory.successCount}`); - - console.log(" [PASS] Session Memory"); - return true; - } catch (error) { - console.log(` - ERROR: ${error.message}`); - return false; - } -} - -async function testSearcher() { - console.log("\n[TEST 5] Searcher"); - - try { - console.log(" - Running search for 'authentication function'..."); - const result = await searcher.search( - "authentication function", - { strategy: "test", depth: "broad", maxChunks: 5, rerank: true }, - process.cwd() - ); - console.log(` - Found ${result.totalChunks} chunks`); - - console.log(" [PASS] Searcher"); - return true; - } catch (error) { - console.log(` - ERROR: ${error.message}`); - return false; - } -} - -async function testBrainScenario() { - console.log("\n[TEST 6] Brain Scenario (End-to-End)"); - - try { - const tree = new DecisionTree(); - const provider = new LMStudioProvider(LM_STUDIO_URL); - - const query = "fix the login bug in auth.js"; - console.log(` - Query: "${query}"`); - - const signals = { - message: query, - recentFiles: ["src/auth.js"], - diagnostics: [{ severity: "error", message: "Cannot read property 'token' of undefined", file: "src/auth.js", line: 42 }] - }; - - const { node: scenario, score } = tree.classify(query, signals); - console.log(` - Detected intent: ${scenario.intent}`); - console.log(` - Confidence score: ${score.toFixed(2)}`); - console.log(` - Selected strategy: ${scenario.strategy.name}`); - console.log(` - Max chunks: ${scenario.strategy.maxChunks}`); - console.log(` - Depth: ${scenario.strategy.depth}`); - - sessionMemory.setDiagnostics(signals.diagnostics); - sessionMemory.recordDecision({ - timestamp: Date.now(), - intent: scenario.intent, - strategy: scenario.strategy.name, - contextCount: scenario.strategy.maxChunks, - query - }); - - console.log(" [PASS] Brain Scenario"); - return true; - } catch (error) { - console.log(` - ERROR: ${error.message}`); - return false; - } -} - -async function runAllTests() { - const results = []; - - results.push(await testProvider()); - results.push(await testDecisionTree()); - results.push(await testContextInjector()); - results.push(await testSessionMemory()); - results.push(await testSearcher()); - results.push(await testBrainScenario()); - - console.log("\n" + "=".repeat(50)); - console.log("Test Results"); - console.log("=".repeat(50)); - - const passed = results.filter(r => r).length; - const failed = results.filter(r => !r).length; - - console.log(`Passed: ${passed}/${results.length}`); - console.log(`Failed: ${failed}/${results.length}`); - - if (failed === 0) { - console.log("\n[SUCCESS] All tests passed!"); - } else { - console.log("\n[FAILURE] Some tests failed."); - process.exit(1); - } -} - -runAllTests().catch(console.error); diff --git a/test-embeddings.mjs b/test-embeddings.mjs deleted file mode 100644 index 3eae882..0000000 --- a/test-embeddings.mjs +++ /dev/null @@ -1,76 +0,0 @@ -const BASE_URL = "http://192.168.1.12:1234/v1"; - -async function embed(text, model = "text-embedding-nomic-embed-text-v1.5") { - const response = await fetch(`${BASE_URL}/embeddings`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - model, - input: text.replace(/\n/g, " "), - }), - }); - const data = await response.json(); - if (data.error) throw new Error(data.error.message || JSON.stringify(data.error)); - return data.data[0].embedding; -} - -function cosineSimilarity(a, b) { - let dot = 0, normA = 0, normB = 0; - for (let i = 0; i < a.length; i++) { - dot += a[i] * b[i]; - normA += a[i] * a[i]; - normB += b[i] * b[i]; - } - return dot / (Math.sqrt(normA) * Math.sqrt(normB)); -} - -async function test() { - console.log("๐Ÿงช Testing LM Studio Embeddings\n"); - - console.log("๐Ÿ“Œ Testing available models..."); - const modelsRes = await fetch(`${BASE_URL}/models`); - const models = await modelsRes.json(); - console.log(" Available models:"); - models.data.forEach((m) => console.log(` - ${m.id}`)); - - console.log("\n๐Ÿ“Œ Single embedding (Nomic):"); - const e1 = await embed("Hello world"); - console.log(` Dimensions: ${e1.length}`); - console.log(` First 5: [${e1.slice(0, 5).map((v) => v.toFixed(4)).join(", ")}]`); - - console.log("\n๐Ÿ“Œ Single embedding (Qwen3):"); - const e2 = await embed("Hello world", "text-embedding-qwen3-embedding-4b"); - console.log(` Dimensions: ${e2.length}`); - - console.log("\n๐Ÿ“Œ Batch embeddings with Nomic:"); - const texts = [ - "The quick brown fox jumps over the lazy dog", - "A fast brown canine leaps over a sleepy hound", - "Machine learning is transforming AI", - "Deep neural networks power modern applications", - ]; - const embeddings = []; - for (const t of texts) { - try { - const e = await embed(t); - embeddings.push(e); - console.log(` - "${t.substring(0, 30)}..." -> ${e.length}d`); - } catch (err) { - console.log(` - "${t.substring(0, 30)}..." -> ERROR: ${err.message}`); - } - } - - console.log("\n๐Ÿ“Œ Similarity tests:"); - if (embeddings.length >= 4) { - const sim1 = cosineSimilarity(embeddings[0], embeddings[1]); - const sim2 = cosineSimilarity(embeddings[2], embeddings[3]); - const sim3 = cosineSimilarity(embeddings[0], embeddings[2]); - console.log(` "fox" vs "dog" (similar): ${sim1.toFixed(4)}`); - console.log(` "ML" vs "AI" (similar): ${sim2.toFixed(4)}`); - console.log(` "fox" vs "ML" (dissimilar): ${sim3.toFixed(4)}`); - } - - console.log("\nโœ… Embedding tests completed!"); -} - -test().catch(console.error); diff --git a/tools/__tests__/path-resolver.test.ts b/tools/__tests__/path-resolver.test.ts deleted file mode 100644 index c11f88a..0000000 --- a/tools/__tests__/path-resolver.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { existsSync } from "node:fs"; -import { fileURLToPath } from "node:url"; -import { dirname, join } from "node:path"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -describe("Path Resolver Utilities", () => { - it("tools/path-resolver.ts should exist", () => { - const resolverPath = join(__dirname, "..", "path-resolver.ts"); - expect(existsSync(resolverPath)).toBe(true); - }); - - it("should use OPENCODE_HOME env var when set", () => { - const original = process.env.OPENCODE_HOME; - process.env.OPENCODE_HOME = "/custom/path"; - // Logic test: env var should override cwd - expect(process.env.OPENCODE_HOME).toBe("/custom/path"); - process.env.OPENCODE_HOME = original; - }); - - it("should default to cwd when OPENCODE_HOME is not set", () => { - const original = process.env.OPENCODE_HOME; - delete process.env.OPENCODE_HOME; - expect(process.env.OPENCODE_HOME).toBeUndefined(); - const cwd = process.cwd(); - expect(cwd).toBeDefined(); - process.env.OPENCODE_HOME = original; - }); -});