5 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
npm run dev— esbuild watch mode; bundlessrc/main.tstomain.js(CJS, Obsidian plugin format) and copiesmain.css→styles.cssafter each rebuild.npm run build—tsc --noEmittype-check, then a production esbuild bundle.npm test— runs Vitest once (vitest run --passWithNoTests).npm run test:watch— Vitest in watch mode.- Single test:
npx vitest run tests/path/to/file.test.ts(optionally-t "test name"). - No lint script is wired up, though
eslint+eslint-plugin-svelteare installed; invoke vianpx eslint <path>if needed.
There is no install-into-vault step in scripts — main.js, manifest.json, and styles.css at the repo root are the plugin artifacts. Symlink or copy the repo into <vault>/.obsidian/plugins/smart-note-agent/ for local testing.
Architecture
This is an Obsidian plugin that adds an agentic chat pane capable of reading and (with approval) modifying vault notes. It is LLM-provider-agnostic.
Layered structure (all under src/)
main.ts—ObsidianNoteAgentPlugin(extendsPlugin). Wires everything together, owns the single activeConversation, exposessendMessage()as an async generator that streamsLoopEvents to the UI. Also contains thecommitWrite/computeDiffbridge that turns pending tool calls into real vault mutations after UI approval, and runsSchedulerServicejobs.agent/— provider-agnostic orchestration.agent-loop.ts— the core tool-calling loop. For each iteration it assembles[system, ...conversation.messages], passes throughtrimHistory(budget), streams the provider response (text deltas + tool calls), then dispatches each tool call. Uses a per-iterationAbortControllerlinked to both outer cancel andturnTimeoutMs—AbortErroris translated into a user-facing timeoutLoopEvent.approval-queue.ts— gate write tools behind UI approval.mode-gate.ts— picks the system prompt key based onMode.conversation.ts,history-trimmer.ts— message store and token-budget trimming.
providers/— one file per LLM vendor, all implementingLLMProvider.chat()as an async iterable ofDeltas.registry.ts::createProvider(id, cfg)is the only entry point;http.tsandopenai-compat.tscontain shared streaming/SSE plumbing used by the OpenAI-compatible backends (DeepSeek, Qwen, Kimi, Zhipu, MiniMax, OpenRouter). Anthropic and Ollama have their own implementations.tools/— tool schemas and handlers.registry.ts::buildToolRegistry(ctx, mode)returns the mode-appropriate set:ask→ read-only;scheduled→ read +create_noteonly;edit→ read + all write tools. Write tools do not mutate the vault directly — they return aPENDING_PREFIX-tagged marker string. The agent loop surfaces this as apendingevent;main.tsthen callscomputeDiff, waits for approval (viaDiffReviewBlock.svelte), and only then invokescommitWritewhich executesVaultServiceops and records them inlastTurnSummary.services/— side-effectful helpers:VaultService(Obsidian file ops),ConversationStore(persists conversations as notes undersettings.chatsFolder),SchedulerService(daily/weekly cron-like runs callingrunScheduledinmain.ts),I18n(en / zh-CN, used for system prompts too — seemode-gate.systemPromptKey).ui/— Svelte 4 + ObsidianItemView.chat-view.tsmountsChatView.svelte;SettingsTab.tsis the settings pane (no Svelte). Svelte is compiled in-bundle byesbuild-svelte.
Key cross-cutting details
- Streaming model: everything from the provider → agent loop → plugin → UI is an async iterable of events, not a Promise. Don't wrap in
.then;for awaitthrough it, and remember that cancellation flows viaAgentLoop.cancel()→AbortController. - Pending-write protocol: if you add a new mutating tool, follow the existing pattern — handler returns
PENDING_PREFIX + JSON.stringify({ tool, args }), add acaseinmain.ts::commitWrite, and (usually) a branch incomputeDiffso the UI can show a preview. - Settings migration:
migrateSettings()insettings.tsis the single place new settings fields get defaulted; call it on load so existing users don't break. - Provider list of record:
types.ts::ProviderIdis the canonical union. If you add a provider, update bothProviderIdandproviders/registry.ts. - Build externals:
obsidian,electron, and@codemirror/*are marked external inesbuild.config.mjs— never import them into code paths that might be unit-tested under Node without mocking.
Tests
Vitest, Node environment. Test tree mirrors src/ (tests/agent, tests/providers, tests/tools, tests/services, plus tests/fixtures). Obsidian APIs are not available in tests — tests either mock VaultService or exercise pure modules (agent-loop, history-trimmer, providers against stubbed fetch, patch utils).