The World's First Dedicated File Structure Linter
Enforce a clean repository layout with an expressive .chous rules file.
Created by Cheez Lin
chous is the world's first dedicated linter for file structures. While other linters focus on what's inside your files, chous focuses on where those files live and how they are named.
chous /tʃoʊs/ - From Chinese "抽丝" (chōu sī), meaning "to draw out silk threads, to untangle". Just as this tool helps you untangle complex file structure issues.
It ensures your project architecture remains consistent, whether you're working in a massive monorepo or a small agile project.
Are you a Vibe Coder using AI editors? Use this 5-step workflow to let the AI handle the architecture for you.
Note
Cursor users can automate Step 5 with hooks. For Windsurf or other editors, simply rely on Step 4 for verification.
"I want to use
chousto enforce my project's file structure. Please follow these steps:
- Run
npx chous initto generate a configuration template.- Run
npx chousto identify current file structure inconsistencies.- Based on the lint results and my project goals, customize the
.chousfile (or help me move files) to achieve a clean architecture.- Keep running
npx chousand fixing issues until all checks pass.- (Optional - Cursor Only) Once verified, run
npx chous cursor installto enable real-time protection."
- Expressive DSL: A human-readable language designed specifically for file systems.
- Built-in Presets: Instant support for Next.js, Nuxt 4, Go, Python, and more.
- Nested Blocks: Group rules naturally with
in <dir>: ...syntax. - AI Editor Integration: Native hooks for Cursor, providing real-time feedback as you code.
- Elegant Reporting: Beautiful, tree-style terminal reports that pinpoint exact inconsistencies.
# Run without installation
npx chous
# Or install globally
npm install -g chous-
Initialize:
chous init
This will auto-detect your project type and create a
.chousfile with sensible defaults based on your stack. -
Lint:
chous
chous # Lint the current project
chous init # Create a .chous rules file from the detected stack
chous cursor install # Install Cursor hooks for automatic linting
chous --help # Show all commands and options| Option | Description |
|---|---|
-c, --config <path> |
Specify rules file (default: ./.chous) |
-v, --verbose |
Show full filesystem tree (mode: verbose) |
-l, --lang <code> |
Set output language — de / en / es / fr / ja / ko / pt-BR / zh, or auto to follow the system language |
-s, --strict |
Enable strict mode for all directory-only rules |
--stats-output <path> |
Output rule performance statistics to a JSON file |
--no-color |
Disable colored output |
-h, --help |
Show help |
Tip
chous --verbose is exactly what the Cursor hook asks your AI agent to run, and it is also the fastest way to see every issue together with its fix suggestion.
If you are using Cursor, you can install an automated hook that runs chous after every AI-powered edit:
chous cursor installImportant
It is recommended to run this command only after you have successfully configured your .chous and all manual lint checks pass. This ensures the AI has a clear "source of truth" to follow.
The .chous file uses a simple, powerful syntax. Here are some real-world examples derived from the Nuxt 4 preset:
# Ensure specific files exist
must have [nuxt.config.ts, app]
# Global naming conventions
use kebab-case for files **/*.ts
Logical grouping prevents path repetition and makes rules readable:
in app:
# Allow standard Nuxt directories
allow [assets, components, composables, pages]
# Deeply nested rules
in components:
# All components must be PascalCase
use PascalCase for files **/*.vue
# Except when they are inside a PascalCase directory
use kebab-case for files **/*.vue if-parent-matches PascalCase
strict
Clean up your assets folder automatically:
in assets:
move *.{css,scss} to css
move *.{png,jpg,svg} to images
| Keyword | Purpose |
|---|---|
import <preset> |
Import a built-in preset. |
must have <paths> / has <paths> |
Require that the given files or directories exist. |
optional <paths> |
Cancel a must have requirement inherited from a preset. |
no <globs> |
Forbid matching files or directories (deny / reject are aliases). |
allow [...] |
Whitelist entries of a directory (meaningful together with strict). |
use <style> for files|dirs <glob> |
Enforce a naming style: PascalCase, camelCase, kebab-case, snake_case, SCREAMING_SNAKE_CASE, flatcase. |
strict |
Only whitelisted entries may live at that level. |
strict files in <dir> |
Apply the whitelist to a directory's files only. |
in <dir>: ... |
Nested block — every rule inside is scoped to <dir>. |
move <glob> to <dir> |
Suggest moving matching files into <dir>. |
rename <glob> to <newglob> |
Suggest renaming matching files. |
[where: <glob>] |
Scope a whole config (or group) to specific paths. |
--- |
Separate multiple config groups inside one file. |
Modifiers (append to use rules):
| Modifier | Effect |
|---|---|
except <glob> |
Skip matching entries. |
prefix: /re/ |
Ignore a leading pattern before checking the name. |
suffix: /re/ |
Ignore a trailing pattern before checking the name. |
if-contains <name> |
Only apply when the directory contains <name>. |
if-parent-matches <style> |
Only apply when the parent directory matches <style>. |
if empty / if exists <path> / if file-size > <n> |
Apply the rule conditionally. |
Paths can be excluded globally with a .chousignore file (gitignore syntax).
basic: Standard ignores and root files.js: Common JavaScript/TypeScript patterns.nextjs: App router and Page router conventions.nuxt3: Nuxt 3 directory structures and dynamic routes.nuxt4: Nuxt 4 directory structures and dynamic routes.go: Standard Go workspace layouts.python: PEP 8 and common Python project structures.
This project is licensed under the MIT License.
