This document is the master build plan. Follow these steps in order. Don't skip ahead. Each step should produce working, testable output before moving on.
Goal: get a working monorepo that builds and runs.
- Initialize pnpm workspace
- Create
package.jsonat root with"private": trueand"workspaces" - Create
pnpm-workspace.yamlpointing atapps/*andpackages/*
- Create
- Initialize
apps/web/as a Next.js 14+ app with App Router, TypeScript, Tailwind - Create stub packages:
packages/core,packages/ingestion,packages/llm- Each has its own
package.json,tsconfig.json,src/index.ts
- Each has its own
- Set up TypeScript project references between them
- Install shadcn/ui in the web app and verify a button renders
- Add ESLint, Prettier, Vitest at the root level
- Add
.gitignorecoveringnode_modules,.next,*.tsbuildinfo,dist - Add
LICENSE(MIT),README.md, copyCLAUDE.mdanddocs/from setup
Done when: pnpm dev runs the web app at localhost:3000 with a hello-world page that imports something from packages/core.
Goal: read and write the wiki folder structure.
- In
packages/core/src/wiki.ts, implement:initWikiFolder(path): create directories and stub filesreadPage(wikiPath, slug): parse frontmatter and contentwritePage(wikiPath, slug, page): write with frontmatterlistPages(wikiPath): scanwiki/directoryreadIndex(wikiPath): readindex.mdwriteIndex(wikiPath, indexContent): writeindex.mdappendLog(wikiPath, entry): append tolog.mdreadSchema(wikiPath): readCLAUDE.md
- Use
gray-matterfor frontmatter parsing - Write unit tests in
packages/core/src/wiki.test.ts
Done when: tests pass and I can manually verify by initializing a folder and listing pages.
Goal: persistent metadata store.
- In
packages/core/src/db.ts, implement:openDb(wikiPath): returns abetter-sqlite3Database instancerunMigrations(db): creates tables perdocs/03-data-model.md- Repository functions:
getPage,insertPage,updatePage,deletePage, similar for sources, chats, usage
- Add startup logic that opens the DB and runs migrations
- Tests: create in-memory DB, run migrations, assert tables exist
Done when: DB is created on first wiki access and tables are present.
Goal: keep SQLite in sync with the wiki folder files.
- In
packages/core/src/sync.ts, implement:syncWikiToDb(wikiPath, db): full scan, update DB rows where mtime differswatchWiki(wikiPath, db, onChange): live watch viachokidar
- Wire this into app startup
Done when: I can edit a .md file in wiki/ with my editor and the app picks up the change.
Goal: working LLM call.
- In
packages/llm/src/client.ts, implement:createClient(apiKey): factory for OpenAI SDK pointed at OpenRoutercallLLM<T>(opts): wrapschat.completions.create, parses JSON, validates with zod- Retry logic for transient errors
- In
packages/llm/src/models.ts, defineDEFAULT_MODELSand agetPricing(model)function - Add a smoke test that requires an API key in env: makes one call to a cheap model and verifies response
Done when: I can call callLLM with a test schema and get a parsed response back.
Goal: store API key safely, load it on startup.
- In
packages/core/src/config.ts, implement:loadGlobalConfig(): reads~/.llm-wiki/config.json, returns parsedsaveGlobalConfig(config): writes it backloadWikiSettings(wikiPath): reads.llm-wiki/settings.jsonsaveWikiSettings(wikiPath, settings)
- Use
keytarfor API key, fall back to plain config file with a permissions warning - Wire into Next.js API routes via a shared
getConfig()helper
Done when: API key can be set via UI or CLI and persists.
Goal: ingest a plain-text or markdown source end-to-end.
- In
packages/ingestion/src/, build onlymarkdown.ts,plain.ts, anddetect.tsfor now - In
packages/core/src/ingest.ts, implementingestSource:- Build prompt from schema, index, relevant pages, source
- Call LLM with
IngestResponseSchema - Apply the response: write new pages, update existing, rebuild index, append log
- Add API route
POST /api/ingestinapps/web/src/app/api/ingest/route.ts - Build a basic Sources page in the UI with a textarea + "Ingest" button
Done when: I can paste text, click ingest, and see new wiki pages appear in the folder.
Goal: browse the wiki in the browser.
- Build the app shell with the header nav
- Wiki sidebar component with page list and search filter
- Markdown renderer that handles
[[wikilinks]] - Page view with backlinks
- Inline edit mode with markdown textarea + save
Done when: I can browse pages, click cross-links, and edit a page.
Goal: support all V1 source types.
Build in this order:
html.tsandurl.ts(Readability + Turndown)docx.ts(mammoth)pptx.ts,xlsx.ts(officeparser)pdf.ts(pass-through to vision model)image.ts(pass-through to vision model)
For each: add file upload to UI, route through the right parser, then ingest.
Done when: dragging a PDF, DOCX, or image into the Sources view results in a successful ingest.
Goal: one-off questions with citations.
- In
packages/core/src/query.ts, implementqueryWiki - API route
POST /api/query - Query view in UI with streaming text and citation pills
- "Save as wiki page" action
Done when: I can ask a question, see citations, and promote the answer to a wiki page.
Goal: persistent conversations as .md files.
- In
packages/core/src/chat.ts, implement:createChat(wikiPath, folder): returns new chat id and filenameappendMessage(wikiPath, chatId, role, content): writes to file, updates DBlistChats(wikiPath, folder?): from DBmoveChat,renameChat,deleteChat,pinChat
- API routes for each
- Chat view UI: folder list + thread list + active chat
- Promote-message-to-wiki-page flow
Done when: I can have a multi-turn chat, switch between chats, organize them into folders.
Goal: health checks.
- In
packages/core/src/lint.ts, implementlintWiki - API route
POST /api/lint - Lint view in UI with results table and quick-fix buttons
Done when: lint runs, returns issues, and I can apply a fix.
Goal: edit CLAUDE.md and configure models.
- Schema editor page with monaco editor + preview
- Settings page with all tabs from
docs/08-ui-design.md - Cost tracking page showing token usage from SQLite
Done when: All settings persist and changes take effect immediately.
Goal: shippable as an npm package.
- Create
apps/web/bin/llm-wiki.mjsperdocs/09-cli-distribution.md - Implement commands:
start,init,config,doctor,version - Add port detection, browser auto-open
- Wire
package.jsonbinfield
Done when: pnpm pack produces a tarball I can install globally and run from any folder.
Final pass before publish:
- Error states: every API route returns proper errors, UI shows them as toasts
- Empty states: every view has a designed empty state
- Loading states: all async ops show progress
- Cost previews: shown before any expensive operation
- Backups: page edits create entries in
.llm-wiki/page-history/ - Trash: deleted chats go to
.llm-wiki/trash/chats/for 30 days - Keyboard shortcuts wired up
- Command palette (Cmd+K) working
- Attribution in UI footer
- Write
INSTALL.md,OPENROUTER_SETUP.md,MODELS.md,TROUBLESHOOTING.mdindocs/ - Polish
README.mdwith screenshots and quickstart - Verify install on Mac, Windows (WSL), Linux
- Tag
v0.1.0 - Publish to npm as
@syasas/llm-wiki(or your actual scope) - Announce wherever you announce things
Do NOT build these even if tempted; they're V2+:
- Graph view (force-directed page link visualization)
- Embeddings-based search (FTS5 is enough at V1 scale)
- Multi-wiki management UI
- Mobile-responsive design
- Tauri desktop wrapper
- MCP server endpoints
- Ollama support
- Scheduled lint
- Plugin system
For one person working evenings with Claude Code:
- Steps 0-5: weekend (foundation)
- Steps 6-10: 1-2 weeks (core features)
- Steps 11-13: 1 week (operations and CLI)
- Steps 14-15: 1 week (polish and launch)
Total: about 4-5 weeks to a shippable V1.