Guidance for LLM-based code agents
==================================
This file provides guidance to LLM-based code agents (e.g., Claude Code,
OpenCode) when working with code in this repository.
Project overview
----------------
Hongdown is a Markdown formatter that enforces Hong Minhee's Markdown style
conventions. The formatter is implemented in Rust (Edition 2024) using the
comrak library for parsing. It produces consistently formatted Markdown
output following a distinctive style used across multiple projects including
Fedify, LogTape, and Optique.
### Project architecture
~~~~ text
hongdown/
├── src/
│ ├── main.rs # CLI entry point (clap-based argument parsing)
│ ├── lib.rs # Library entry point (Options, format functions)
│ ├── config.rs # Configuration file handling (.hongdown.toml)
│ ├── wasm.rs # WASM bindings (wasm-bindgen)
│ └── serializer/ # Core formatting logic
│ ├── mod.rs # Main serializer module
│ ├── state.rs # Serializer state and types
│ ├── block.rs # Block quotes and GitHub alerts
│ ├── code.rs # Code blocks (fenced and indented)
│ ├── document.rs # Document-level handling
│ ├── escape.rs # Text escaping utilities
│ ├── inline.rs # Inline elements (emphasis, code spans)
│ ├── link.rs # Links and images
│ ├── list.rs # Ordered and unordered lists
│ ├── table.rs # Table formatting
│ ├── wrap.rs # Text wrapping utilities
│ └── tests.rs # Unit tests for serializer
├── tests/
│ └── integration.rs # Integration tests
├── packages/
│ ├── hongdown/ # CLI npm package (hongdown)
│ │ ├── package.json
│ │ └── bin/hongdown.js
│ └── wasm/ # WASM library package (@hongdown/wasm)
│ ├── src/ # TypeScript wrapper source
│ └── test/ # Tests for Node.js, Bun, Deno
├── demo/ # Web demo application (hongdown-demo)
│ ├── src/
│ │ ├── components/ # UI components (TabBar, Options, Inputs)
│ │ ├── App.tsx # Main application logic
│ │ ├── index.tsx # Entry point
│ │ ├── sample.ts # Sample Markdown content
│ │ └── styles.css # Custom styles (scrollbar, etc.)
│ ├── index.html # HTML template
│ ├── vite.config.ts # Vite configuration
│ └── uno.config.ts # UnoCSS configuration
├── pnpm-workspace.yaml # pnpm workspace configuration
└── package.json # Root workspace package.json
~~~~
Development commands
--------------------
This is a Rust project using Cargo as the build system. We use [mise] for
task management to streamline common development workflows.
[mise]: https://mise.jdx.dev/
### Initial setup
After cloning the repository, set up the Git pre-commit hook to automatically
run quality checks before each commit:
~~~~ bash
mise generate git-pre-commit --task=check --write
~~~~
### Building
~~~~ bash
cargo build # Debug build
cargo build --release # Release build
~~~~
### Testing
The recommended way to run all tests is using mise:
~~~~ bash
mise run test # Run all tests (Rust + WASM)
mise run test:rust # Run Rust tests only
mise run test:wasm # Run WASM package tests only
~~~~
You can also run Cargo tests directly:
~~~~ bash
cargo test # Run all Rust tests
cargo test <name> # Run tests matching a name pattern
cargo test serialize_table # e.g., runs all table serialization tests
cargo test -- --nocapture # Show println! output
cargo test -- --test-threads=1 # Run tests sequentially
~~~~
Tests are located in several places:
- *Rust unit tests*: *src/serializer/tests.rs* contains serializer tests
- *Rust integration tests*: *tests/integration.rs* contains CLI and full
document tests
- *WASM package tests*: *packages/wasm/test/* contains tests that run on
Node.js, Bun, and Deno
Test helper functions in *src/serializer/tests.rs*:
- `parse_and_serialize(input)` - Format with default options
- `parse_and_serialize_with_options(input, options)` - Format with custom
options
- `parse_and_serialize_with_source(input)` - Format with source context
for directive support
- `parse_and_serialize_with_warnings(input)` - Format and capture warnings
### Demo application
The *demo/* directory contains a web-based playground for Hongdown. To run
the demo locally:
~~~~ bash
pnpm --filter hongdown-demo dev # Start development server
pnpm --filter hongdown-demo build # Build for production
~~~~
The demo app uses:
- *Solid.js* for reactive UI
- *UnoCSS* for styling with atomic CSS
- *Vite* for bundling and development
- *@hongdown/wasm* for real-time Markdown formatting
Design principles:
- *Borderless design*: Visual hierarchy is established through background
color differences (e.g., `bg-neutral-50` vs `bg-white` in light mode)
rather than borders
- *Responsive layout*: Desktop shows split-view (editor/output side-by-side),
mobile shows tabbed interface (Editor/Output/Warnings tabs)
- *Dark mode*: Automatically respects system preferences via
`@media (prefers-color-scheme: dark)`
When modifying the demo UI, verify changes in both light and dark modes.
### Formatting
To format all files (Rust code and Markdown files):
~~~~ bash
mise run fmt # Format all files (recommended)
~~~~
This runs:
- `mise run fmt:rust` - Format Rust code with `cargo fmt`
- `mise run fmt:markdown` - Format Markdown files with Hongdown
### Quality checks
The recommended way to run quality checks is using mise tasks:
~~~~ bash
mise run check # Run all quality checks (recommended)
~~~~
This runs all checks in parallel:
- `mise run check:clippy` - Run clippy linter with warnings as errors
- `mise run check:fmt` - Check code formatting
- `mise run check:type` - Run Rust type checking
- `mise run check:markdown` - Check Markdown formatting
You can also run individual Cargo commands directly:
~~~~ bash
cargo fmt # Format code
cargo fmt --check # Check formatting without modifying
cargo clippy # Run linter
~~~~
Development practices
---------------------
### Test-driven development
This project strictly follows test-driven development (TDD) practices.
All new code must be developed using the TDD cycle:
1. *Red*: Write a failing test that describes the expected behavior.
Run the test to confirm it fails.
2. *Green*: Write the minimum code necessary to make the test pass.
Run the test to confirm it passes.
3. *Refactor*: Improve the code while keeping all tests passing.
Run tests after each refactoring step.
Additional TDD guidelines:
- *Write tests first*: Before implementing new functionality, write tests
that describe the expected behavior. Confirm that the tests fail before
proceeding with the implementation.
- *Regression tests for bugs*: When fixing bugs, first write a regression
test that reproduces the bug. Confirm that the test fails, then fix the
bug and verify the test passes.
- *Small increments*: Implement features in small, testable increments.
Each increment should have its own test.
- *Run tests frequently*: Run `cargo test` after every change to ensure
existing functionality is not broken.
### Commit messages
- Do not use Conventional Commits (no `fix:`, `feat:`, etc. prefixes).
Keep the first line under 50 characters when possible.
- Focus on *why* the change was made, not just *what* changed.
- When referencing issues or PRs, use permalink URLs instead of just
numbers (e.g., `#123`). This preserves context if the repository
is moved later.
- When listing items after a colon, add a blank line after the colon:
~~~~
This commit includes the following changes:
- Added foo
- Fixed bar
~~~~
- When using LLMs or coding agents, include credit via `Co-Authored-By:`.
Include a permalink to the agent session if available.
### Changelog (*CHANGES.md*)
This repository uses *CHANGES.md* as a human-readable changelog. Follow these
conventions:
- *Structure*: Keep entries in reverse chronological order (newest version at
the top).
- *Version sections*: Each release is a top-level section:
~~~~
Version 0.1.0
-------------
~~~~
- *Unreleased version*: The next version should start with:
~~~~
To be released.
~~~~
- *Released versions*: Use a release-date line right after the version header:
~~~~
Released on December 30, 2025.
~~~~
If you need to add brief context (e.g., initial release), keep it on the
same sentence:
~~~~
Released on August 21, 2025. Initial release.
~~~~
- *Bullets and wrapping*: Use ` - ` list items, wrap around ~80 columns, and
indent continuation lines by 4 spaces so they align with the bullet text.
- *Write useful change notes*: Prefer concrete, user-facing descriptions.
Include what changed, why it changed, and what users should do differently
(especially for breaking changes, deprecations, and security fixes).
- *Multi-paragraph items*: For longer explanations, keep paragraphs inside the
same bullet item by indenting them by 4 spaces and separating paragraphs
with a blank line (also indented).
- *Code blocks in bullets*: If a bullet includes code, indent the entire code
fence by 4 spaces so it remains part of that list item. Use `~~~~` fences
and specify a language (e.g., `~~~~ rust`).
- *Nested lists*: If you need sub-items (e.g., a list of added features), use
a nested list inside the parent bullet, indented by 4 spaces.
- *Issue and PR references*: Use `[[#123]]` markers in the text and add
reference links at the end of the version section.
When listing multiple issues/PRs, list them like `[[#123], [#124]]`.
When the reference is for a PR authored by an external contributor, append
`by <NAME>` after the last reference marker (e.g., `[[#123] by Hong Minhee]`
or `[[#123], [#124] by Hong Minhee]`).
~~~~
[#123]: https://github.com/dahlia/hongdown/issues/123
[#124]: https://github.com/dahlia/hongdown/pull/124
~~~~
Development tips
----------------
### Dependency management
- *Always use `cargo add`*: When adding dependencies, use `cargo add`
instead of manually editing *Cargo.toml*. This ensures you get the
latest compatible version:
~~~~ bash
cargo add serde --features derive
cargo add tokio --features full
~~~~
- *Check before adding*: Before adding a new dependency, consider whether
the functionality can be achieved with existing dependencies or the
standard library.
### Configuration changes
- *Options struct*: When adding new configuration options, update both
the `Config` struct in *src/config.rs* and the `Options` struct in
*src/lib.rs*. Also update *src/main.rs* to wire them together.
- *Update documentation*: When adding configuration options, update the
Configuration file section in *README.md*.
### Code organization
- *Serializer modules*: The serializer is split into multiple modules
under *src/serializer/*. Each module handles a specific type of
Markdown element (e.g., *list.rs*, *table.rs*, *code.rs*).
- *Test location*: Unit tests for the serializer are in
*src/serializer/tests.rs*. Integration tests are in *tests/*.
### Quality checks
- *Run checks frequently*: Run `cargo fmt --check` and `cargo clippy`
frequently during development, not just before committing. This
catches issues early and keeps the codebase clean.
- *Format files*: After editing code or Markdown files, format them:
~~~~ bash
mise run fmt ~~~~
- *Before committing*: Always run the full quality check suite. This is
automatically enforced by the Git pre-commit hook:
~~~~ bash
mise run check ~~~~
The pre-commit hook runs `mise run check` automatically before each
commit. If any check fails, the commit will be aborted.
### Performance considerations
- *Parallel processing*: The CLI uses `rayon` for parallel file
processing in `--write` and `--check` modes. Keep this in mind
when modifying file processing logic.
- *Avoid unnecessary allocations*: Prefer borrowing over cloning when
possible. Use `&str` instead of `String` for read-only string data.
Code style
----------
This project uses default `rustfmt` formatting (no *rustfmt.toml*) and default
clippy lints (no *clippy.toml*). Run `cargo fmt` to format code.
### Imports
Organize imports in this order, with blank lines between groups:
1. Standard library (`use std::...`)
2. External crates (`use clap::...`, `use comrak::...`)
3. Internal modules (`use super::...`, `use crate::...`)
~~~~ rust
use std::fs;
use std::path::PathBuf;
use clap::Parser;
use comrak::{Arena, Options as ComrakOptions};
use crate::config::Config;
~~~~
### Naming conventions
- *Modules and files*: `snake_case` (e.g., *code\_block.rs*)
- *Types and traits*: `PascalCase` (e.g., `Serializer`, `FormatError`)
- *Functions and methods*: `snake_case` (e.g., `serialize_node`)
- *Constants*: `SCREAMING_SNAKE_CASE` (e.g., `CONFIG_FILE_NAME`)
- *Method prefixes*: Use `serialize_*` for serialization, `is_*` for
boolean checks, `with_*` for builder patterns
### Type safety
- All code must be type-safe. Avoid using `unsafe` blocks unless
absolutely necessary.
- Prefer immutable data structures unless there is a specific reason to
use mutable ones.
### Error handling
- Define custom error types as `enum` with descriptive variants.
- Manually implement `std::fmt::Display` and `std::error::Error` traits.
- Include context in error messages (e.g., file paths, pattern names).
- Prefer `Result` over `panic!` for recoverable errors.
- End error messages with a period.
Example error type pattern:
~~~~ rust
#[derive(Debug)]
pub enum ConfigError {
Io(PathBuf, std::io::Error),
Parse(PathBuf, toml::de::Error),
}
impl std::fmt::Display for ConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConfigError::Io(path, err) => {
write!(f, "failed to read {}: {}", path.display(), err)
}
ConfigError::Parse(path, err) => {
write!(f, "failed to parse {}: {}", path.display(), err)
}
}
}
}
impl std::error::Error for ConfigError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ConfigError::Io(_, err) => Some(err),
ConfigError::Parse(_, err) => Some(err),
}
}
}
~~~~
### Struct patterns
- Use `#[derive(...)]` extensively for common traits.
- Common derives: `Debug`, `Clone`, `PartialEq`.
- For serde: `#[derive(Deserialize)]`, `#[serde(default)]`,
`#[serde(rename_all = "lowercase")]`.
- Implement `Default` for configuration structs.
### API documentation
- All public APIs must have doc comments describing their purpose,
parameters, and return values.
- Use `///` for item documentation and `//!` for module documentation.
Markdown style guide
--------------------
This project follows [Hong Minhee's Markdown style convention], documented
in *[STYLE.md]*. That document is the authoritative specification for all
formatting rules enforced by Hongdown.
When modifying Hongdown's formatting behavior, you must also update
*STYLE.md* to reflect the changes. The specification should always match
the actual behavior of the formatter.
[Hong Minhee's Markdown style convention]: ./STYLE.md
[STYLE.md]: ./STYLE.md