From all-skills
Universal code formatter using Tree-sitter queries for languages lacking dedicated formatters like JSON, TOML, Bash, Nickel, CSS. Aids writing .scm query files and configuring via languages.ncl.
npx claudepluginhub vinnie357/claude-skills --plugin alliumThis skill uses the workspace's default tool permissions.
Topiary is a universal code formatter that uses Tree-sitter grammars and queries to define formatting rules. Rather than implementing language-specific formatting logic, Topiary uses declarative Tree-sitter query files (.scm) to specify how code should be formatted.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Topiary is a universal code formatter that uses Tree-sitter grammars and queries to define formatting rules. Rather than implementing language-specific formatting logic, Topiary uses declarative Tree-sitter query files (.scm) to specify how code should be formatted.
Activate this skill when:
Tree-sitter is a parser generator that creates language parsers from grammar definitions. Topiary uses Tree-sitter queries to analyze code structures and apply formatting rules. Queries match patterns in the syntax tree using S-expression syntax and decorate matched nodes with capture names (prefixed with @) that describe formatting actions.
Language formatting is defined in .scm (Scheme) query files. Each capture name instructs Topiary how to format the matched node:
@prepend_hardline, @append_hardline (always insert newline), @prepend_empty_softline, @append_empty_softline (newline or nothing), @prepend_spaced_softline, @append_spaced_softline (newline or space)@prepend_indent_start, @append_indent_start, @prepend_indent_end, @append_indent_end (mark indentation scopes)@prepend_space, @append_space (add single space)@delete (remove matched node), @do_nothing (suppress default behavior), @leaf (prevent formatting within node)@prepend_input_softline, @append_input_softline (preserve original line break presence)Topiary formatting is idempotent—running the formatter repeatedly on already-formatted code produces identical output. This guarantee allows safe integration into version control hooks and CI pipelines.
Topiary respects the structural intent of code by preserving language-specific semantics. It focuses on whitespace, indentation, and line breaks rather than restructuring code. This approach works especially well with languages whose semantics are simple and whitespace-independent.
Add Topiary to your mise.toml:
[tools]
topiary = "latest"
[tasks]
fmt = "topiary fmt"
fmt:check = "topiary fmt --check"
Install directly from crates.io:
cargo install topiary-cli
Check the installed version and available languages:
topiary --version
topiary config show-sources
Format files in place by detected file extension:
topiary fmt src/main.ml
topiary fmt config.json
topiary fmt *.toml
Add --check to verify formatting without modifications:
topiary fmt --check src/
Format piped input by specifying language and optional query file:
echo '{"name":"example"}' | topiary format --language json
cat script.sh | topiary format --language bash
Use visualise to debug queries by inspecting the syntax tree:
topiary visualise script.json
topiary visualise --language bash script.sh
This outputs the Tree-sitter parse tree, useful for understanding node structure when writing .scm queries.
Override the default query file for a language:
topiary fmt --language nickel --query custom.scm script.ncl
Display the active configuration including registered languages and indentation settings:
topiary config show-sources
Create a .topiary/languages.ncl file in your project root to customize language registration, indentation, and grammar sources. This file uses Nickel syntax:
{
languages = {
json = {
extensions = ["json"],
indent = " ",
},
bash = {
extensions = ["sh", "bash"],
indent = " ",
}
}
}
Topiary searches for configuration in this order (first found wins):
--configuration CLI argument.topiary/languages.ncl in current directory or parent directories~/.config/topiary/languages.ncl (Linux), ~/Library/Application Support/topiary/languages.ncl (macOS), %APPDATA%\topiary\languages.ncl (Windows)Register custom Tree-sitter grammars by specifying Git source or local path:
{
languages = {
my-lang = {
extensions = ["ml"],
grammar.source.git = {
git = "https://github.com/example/tree-sitter-my-lang",
rev = "abc123def456"
}
}
}
}
Or reference a pre-compiled grammar on disk:
{
languages = {
my-lang = {
extensions = ["ml"],
grammar.source.path = "./grammars/my-lang.so"
}
}
}
Add formatting support for new languages by following these steps:
Create .topiary/languages.ncl in your project with the grammar source:
{
languages = {
my-lang = {
extensions = ["ml"],
indent = " ",
grammar.source.git = {
git = "https://github.com/my-org/tree-sitter-my-lang",
rev = "main"
}
}
}
}
Verify the grammar source URL points to a valid Tree-sitter grammar repository.
Create .topiary/queries/my-lang.scm with formatting rules. Start minimal:
; Format binary operators with surrounding spaces
((binary_op) @op
(#match? @op "^(+|-|=)$"))
@prepend_space
@append_space
; Hard line after statements
((statement) @stmt)
@append_hardline
Use topiary visualise to inspect the parse tree and understand node names and structure.
Test the formatter on sample files:
topiary fmt --language my-lang test-file.ml
topiary visualise --language my-lang test-file.ml
Iterate on the query file until formatting behaves as expected. Run topiary fmt on actual code to verify formatting is idempotent (run twice, should produce identical output).
Tree-sitter queries use S-expression syntax. A basic query matches nodes and captures them:
; Match a function definition and capture its name
(function_def
name: (identifier) @func-name)
The @func-name is a capture; function_def, name, and identifier are node types. Field names like name: link to specific children.
Format operators with spaces on both sides:
((binary_op operator: _ @op))
@prepend_space
@append_space
Indent function bodies:
(function_def
body: (block) @body)
@append_indent_start
@prepend_indent_end
Insert line breaks between statements:
((statement) @stmt)
@append_hardline
Preserve input line breaks (single line or multi-line lists):
(list
"[" @open
_ @item
"]" @close)
@append_spaced_softline
Use predicates to refine matches:
; Match only specific operators
((binary_op operator: _ @op)
(#match? @op "^(=|==|!=)$"))
@prepend_space
@append_space
; Match nodes that are NOT in comments
((statement) @stmt
(#not-eq? @stmt comment))
Available predicates: #match? (regex), #eq? (equality), #not-eq? (inequality).
Topiary actively maintains formatting for:
External maintainers provide formatters for:
Topiary works best with languages where formatting is independent of semantics. Whitespace-sensitive languages (Python, Haskell) present challenges and are not officially supported, as formatting changes could alter code meaning.
Define formatting tasks in mise.toml:
[tasks.fmt]
description = "Format all code"
run = """
topiary fmt src/
"""
[tasks.fmt:check]
description = "Check formatting without modifying"
run = """
topiary fmt --check src/
"""
[tasks.fmt:debug]
description = "Visualize parse tree for debugging"
run = """
topiary visualise {file}
"""
Run tasks with:
mise run fmt
mise run fmt:check
mise run fmt:debug -- script.ncl
Before claiming that Topiary formats a language or query correctly, verify behavior by running topiary fmt on actual code files and inspecting the output. Do not assume query behavior without testing; use topiary visualise to inspect the parse tree and confirm node names and structure match the query. Test idempotency by running the formatter twice and confirming output is identical.