Automattic Harper Project Structure: Monorepo Layout and Component Architecture

Harper is organized as a Rust-centric monorepo with a core grammar engine at its center, surrounded by language-specific front-ends including an LSP server, CLI tool, WebAssembly bindings, and multiple editor plugins.

The Automattic Harper repository is an open-source grammar and spell checking engine distributed as a monorepo. It houses the Rust-based core engine, language-specific front-ends, a documentation website, and multiple editor integrations all managed through unified Rust and JavaScript workspace configurations.

Core Rust Workspace Components

The heart of the project resides in several interconnected Rust crates defined in the root Cargo.toml workspace.

harper-core: The Grammar Engine

harper-core contains the grammar-checking engine written in Rust. It parses text, runs spelling and style rules, and exposes a library used by all other components. The public API surface including Document, LintGroup, and rule registration is defined in harper-core/src/lib.rs.

harper-ls: Language Server Protocol Implementation

harper-ls wraps the core engine in an LSP implementation, enabling real-time diagnostics in editors. The entry point is located at harper-ls/src/main.rs, which bridges editor buffers to the core linting functionality.

harper-cli: Command-Line Interface

harper-cli provides a debugging and linting tool for scripts or CI pipelines. Running harper-cli lint path/to/file.txt executes the driver in harper-cli/src/main.rs, which constructs a Document using the merged dictionary (user dictionary + curated wordlist) and outputs lint results as JSON. The linting logic implementation resides in harper-cli/src/lint.rs.

Supporting Crates: Comments and Brill Tagging

harper-comments provides language-specific parsers for linting comment blocks in source files, with its library root at harper-comments/src/lib.rs. harper-brill implements a statistical POS-tagger used by the core for advanced linguistic analysis, located at harper-brill/src/lib.rs.

JavaScript and WebAssembly Layer

The repository includes a JavaScript ecosystem coordinated through pnpm-workspace.yaml, allowing packages to depend on the compiled WebAssembly artifact (harper-wasm) produced by the core.

harper.js: WebAssembly Bridge

harper.js compiles the core to WebAssembly and provides a thin API for browser or Node.js usage. The JavaScript entry point at packages/harper.js/src/index.ts loads the compiled WebAssembly module:

import { lint } from 'harper.js';

(async () => {
  const text = "She *realy* enjoys writting.";
  const result = await lint(text);
  console.log(result.lints);
})();

Web Documentation and Demo

packages/web hosts the SvelteKit website (writewithharper.com) that serves documentation, live demos, and generated API references. The routing and sidebar layout are configured in packages/web/vite.config.ts.

Editor Integrations and Desktop Application

Beyond the core libraries, the monorepo contains multiple front-end implementations.

Editor Plugins

Individual extensions plug the LSP or WebAssembly client into host applications:

  • VS Code: packages/vscode-plugin/ with extension logic in packages/vscode-plugin/src/extension.ts
  • Chrome: packages/chrome-plugin/
  • Firefox: packages/firefox-plugin/
  • Obsidian: packages/obsidian-plugin/
  • WordPress: packages/wordpress-plugin/

The VS Code extension ships harper-ls as a binary and spawns the server (harper-ls/src/main.rs) when documents open, displaying diagnostics via the LSP client.

harper-desktop: Tauri Application

harper-desktop bundles a desktop application using Tauri, combining a Svelte-based web UI with a native overlay highlighter. The application entry point is harper-desktop/src-tauri/src/main.rs.

Repository Layout

The project organizes code into distinct directories under the repository root:


/ (repo root)
├─ Cargo.toml                 # workspace definition for all Rust crates

├─ pnpm-workspace.yaml       # workspace definition for all JS packages

├─ harper-core/              # core engine (Rust)

├─ harper-ls/                # LSP server (Rust)

├─ harper-cli/               # CLI front‑end (Rust)

├─ harper-comments/          # comment parsers (Rust)

├─ harper-brill/             # statistical tagger (Rust)

├─ harper-desktop/           # Tauri desktop app (Rust + Svelte)

├─ packages/
│   ├─ harper.js/            # WebAssembly + JS wrapper

│   ├─ harper-editor/       # Shared Svelte editor components

│   ├─ components/          # UI building blocks

│   ├─ web/                 # Documentation site (SvelteKit)

│   ├─ chrome-plugin/       # Chrome extension source

│   ├─ firefox-plugin/      # Firefox extension source

│   ├─ obsidian-plugin/     # Obsidian plugin source

│   ├─ wordpress-plugin/    # WordPress plugin source

│   └─ vscode-plugin/       # VS Code extension source

└─ ... (CI config, Dockerfiles, etc.)

Architecture and Data Flow

The Harper project structure supports a consistent diagnostic pipeline across all front-ends. According to the source code, the architecture follows this flow:

  1. Input (plain text, file, or editor buffer) enters the system
  2. Document construction creates a harper-core::Document using the merged dictionary (user dictionary + curated wordlist)
  3. LintGroup runs rule pipelines including Weir rules, Brill-tagging, and spelling checks
  4. Results emit as Lint objects, serialized to JSON for consumption by the LSP, CLI, or browser UI

All front-ends (CLI, LSP, Web, Desktop) ultimately call the same core library, guaranteeing consistent diagnostics across platforms.

Usage Examples

Linting via CLI

To lint a file from the command line:

harper-cli lint path/to/file.txt

This command runs harper-cli/src/main.rs, building a Document with the current dictionary and printing lint JSON to stdout.

Integrating the LSP in VS Code

The VS Code extension in packages/vscode-plugin/src/extension.ts connects to the language server spawned from harper-ls/src/main.rs, synchronizing documents and displaying diagnostics according to the LSP specification.

Using the JavaScript API

For browser or Node.js applications, the wrapper loads the Wasm module:

import { lint } from 'harper.js';

(async () => {
  const text = "She *realy* enjoys writting.";
  const result = await lint(text);
  console.log(result.lints);
})();

Summary

  • Harper uses a monorepo structure combining Rust workspaces (Cargo.toml) and JavaScript packages (pnpm-workspace.yaml)
  • The core engine (harper-core) provides the grammar and spell checking logic exposed through harper-core/src/lib.rs
  • Front-end diversity includes an LSP server (harper-ls), CLI tool (harper-cli), WebAssembly bindings (harper.js), and multiple editor plugins
  • Consistent architecture ensures all interfaces use the same Document construction and LintGroup pipelines, with results serialized from Lint objects
  • Key entry points include harper-ls/src/main.rs for editor integration, harper-cli/src/main.rs for scripting, and packages/harper.js/src/index.ts for web usage

Frequently Asked Questions

What is the relationship between harper-core and harper-ls?

Harper-ls is a Language Server Protocol implementation that wraps harper-core. While harper-core/src/lib.rs defines the public API for document parsing and lint generation, harper-ls/src/main.rs adapts these capabilities to the LSP specification, enabling real-time diagnostics in editors like VS Code and Neovim.

How does the JavaScript package access the Rust core functionality?

The harper.js package compiles the Rust core to WebAssembly (Wasm) and provides a JavaScript/TypeScript wrapper. The entry point at packages/harper.js/src/index.ts loads the compiled harper-wasm artifact and exposes functions like lint(), allowing browser and Node.js applications to use the engine without native binaries.

Where is the logic for linting comments in source code?

Comment parsing logic resides in harper-comments, located at harper-comments/src/lib.rs. This crate provides language-specific parsers that extract comment blocks from source files before passing them to the core linting engine, enabling spell checking within code comments.

What file controls the workspace configuration for all Rust components?

The Cargo.toml file in the repository root declares the Rust workspace. Adding a new crate to this workspace automatically integrates it into the build pipeline and makes it available to other Rust components like harper-cli and harper-ls.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →