Architecture¶
Package graph¶
SkillKeeper is a monorepo with two workspaces over one domain core: a Cargo
(Rust) workspace for the domain crates, the CLI, and the desktop backend, plus a
pnpm workspace for the desktop renderer and the one remaining TypeScript package
(i18n). The core is framework-agnostic and performs no direct console or
platform I/O.
Rust crates (Cargo workspace):
skillkeeper-core (no internal deps) domain model, resolver, SHA-256
hashing, install/uninstall/verify/
repair engine, hooks, git port,
update scheduler, adapter interface,
MCP model/install
skillkeeper-config -> core per-section config loading + serde
validation with defaults
skillkeeper-agents -> core adapter registry + Claude (reference)
and Codex/Copilot/Cursor/OpenCode adapters
skillkeeper-cli -> core, config, clap-based CLI (binary `skillkeeper`)
agents
apps/desktop/src-tauri -> core, config, Tauri v2 backend (Rust): owns the
agents filesystem, Git, config, application
state, scheduler, and the PTY
TypeScript (pnpm workspace):
packages/i18n (no internal deps) typed catalogs (en source + de/ru) + lookup
apps/desktop renderer React 19 + Zustand renderer that talks
to the Tauri backend
The dependency graph is acyclic. skillkeeper-core defines the agent adapter
interface; skillkeeper-agents implements it (dependency inversion), so the
install engine never depends on a concrete agent adapter directly.
Domain model¶
All types are defined in the skillkeeper-core crate.
SkillId- stable identity:{ group?: string, name: string }.SkillManifest- parsedSKILL.mdfrontmatter: name, optional version, optional description, optional license, optional declared executables, and optional hook references.HookManifest- parsedHOOK.mdfrontmatter: name, the target it edits (agent + file pattern or config key path), and the apply strategy (delimited-text,json-merge, orfile).ResolvedSkill- a skill discovered in a working tree: itsSkillId, source paths,SkillManifest, file list, and resolved hooks.Repository-{ id, name, url, kind, transport, lfs, localPath, lastFetched? }.AgentTarget-{ agent: AgentKind, scope: 'project' | 'global', projectId? }.InstallManifest- one installed skill at one target. Records source, resolvedSkillId, version, install time,ManagedFileentries, andManagedHookEditentries.ManagedFile-{ relPath, sha256, executable: boolean }.ManagedHookEdit- a tagged union:{ kind: 'delimited', file, delimiterId, sha256 }for comment-delimited regions in text files.{ kind: 'json', file, keyPath, markerId, sha256 }for entries merged into a JSON config (such as Claudesettings.json).{ kind: 'file', ...ManagedFile }for hook-owned standalone files.Project- a tracked directory:{ id, path, name, addedAt }.
State storage¶
Two stores under the OS application-data directory, kept separate because they have different lifecycles:
config.yaml- user settings. User-editable. See Configuration.- Application state store (JSON) - repositories, tracked projects, and install manifests with hashes. Written only by SkillKeeper. Each write is atomic (write to a temp file, then rename). A schema version field allows forward migration.
Desktop process boundaries¶
The desktop app splits into a Rust backend and a web renderer with strict boundaries:
- Tauri backend (Rust) -
apps/desktop/src-tauri. Owns the filesystem, Git, config, application state, the update scheduler, and the PTY. The only place the domain core runs in the GUI. It is the authority for all privileged data and re-validates every request. - Renderer - React 19 + Zustand UI in
apps/desktop/src/renderer. It holds UI state plus a mirror of backend data in a Zustand store and never touches the host directly. It reaches the backend only throughservices/bridge/client.ts, which calls Rust#[tauri::command]functions via Tauriinvoke()and subscribes to backend events vialisten(). This is a Tauri command/event bridge, not an OS-level notification hook. - Generated types - renderer request/response shapes are generated from the
Rust structs and enums via
ts-rs(#[ts(export)]), emitted underapps/desktop/src/renderer/services/bridge/generated/{core,config}/when the crate tests run (cargo test). Rust is the single source of truth for those shapes.
Technology choices¶
| Concern | Choice | Rationale |
|---|---|---|
| Core language | Rust | Domain crates, CLI, and desktop backend. |
| Renderer language | TypeScript (strict) | React renderer and the i18n package. |
| Cargo workspace | Rust crates | Core, config, agents, CLI, desktop backend. |
| pnpm workspace | renderer + i18n |
Strict, fast monorepo isolation for the TS side. |
| UI framework | React 19 | Required. |
| State store | Zustand | Small, modern, minimal boilerplate. |
| Desktop runtime | Tauri v2 | Rust backend + system webview; small bundles. |
| Renderer build | Vite | Bundles the web renderer Tauri serves. |
| Packaging | Tauri bundler | app, dmg, deb, appimage, nsis, msi. |
| Renderer <-> backend | Tauri invoke/listen |
Typed commands and events; Rust re-validates. |
| Generated types | ts-rs | Renderer types derived from Rust structs. |
| Tests | cargo test; Vitest | Rust unit tests; Vitest for the TS packages. |
| Lint / format | rustfmt + clippy; ESLint + Prettier | Enforced in CI on both sides. |
| Config parsing | serde (YAML) | Rust serde over the YAML config file. |
| CLI parsing | clap | Standard, well documented Rust CLI framework. |
| Git, SSH, LFS | system git subprocess | Honors user ssh-agent, LFS, and git config. |
| Hashing | SHA-256 (sha2) |
Recorded per managed file and hook edit. |
| i18n | custom catalogs | Three languages; avoids a heavy dependency. |
| PTY terminal | portable-pty (Rust) |
Cross-platform terminal in the backend. |
Runtime dependencies are intentionally few on both sides. Every new direct
dependency must pass the license policy in CONTRIBUTING.md and be justified.
Git is invoked as a subprocess with argument arrays only (no shell string interpolation). This reuses the user's existing SSH, ssh-agent, and Git LFS setup, keeps credential handling out of the application, and reduces the dependency and attack surface.
Repository layout¶
skillkeeper/
apps/
desktop/ Tauri v2 desktop app
src/renderer/ React 19 + Zustand renderer
src-tauri/ Tauri backend (Rust): filesystem, Git, config, state,
scheduler, PTY, Tauri commands/events
(icons/ is generated from assets/icons/, git-ignored)
assets/
icons/ app icon sources (icon-default.png / icon-dark.png)
crates/
skillkeeper-core/ domain model, resolver, hashing, install engine, hooks,
verify/repair, git port, scheduler, adapter interface, MCP
skillkeeper-config/ per-section serde config loading + validation
skillkeeper-agents/ adapter registry + Claude/Codex/Copilot/Cursor/OpenCode
skillkeeper-cli/ clap CLI (binary `skillkeeper`)
packages/
i18n/ en/de/ru typed catalogs + lookup (TypeScript)
docs/ mkdocs site (this site)
.github/workflows/ CI: lint, typecheck, test+coverage, build, release
AGENTS.md CLAUDE.md CONTRIBUTING.md README.md CHANGES.md LICENSE
Cargo.toml rust-toolchain.toml
package.json pnpm-workspace.yaml tsconfig.base.json