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;
- });
-});