@pleaseai/ast-grep-config is a set of ast-grep rules that keep a codebase navigable for coding agents: public symbols you can find by name, error types in a designated file, and no tests inside source files. It enforces layer 2 (structural rules) of ADR-0022 for TypeScript, Dart, Kotlin, Java, and Rust.
Every rule ships as a warning. Nothing here fails a build unless you raise a rule’s severity in your own config.
Installation
bun add -D @pleaseai/ast-grep-config @ast-grep/clipnpm add -D @pleaseai/ast-grep-config @ast-grep/clinpm install -D @pleaseai/ast-grep-config @ast-grep/cli@ast-grep/cli is a peer dependency that provides the ast-grep binary. Install it next to this package so ast-grep is on your project’s bin path.
With bun, add @ast-grep/cli to trustedDependencies so its postinstall step runs. Otherwise ast-grep prints a fallback warning on every call (please-style check is unaffected).
{
"trustedDependencies": ["@ast-grep/cli"]
}Usage
Point your project’s sgconfig.yml at the rules:
ruleDirs:
- node_modules/@pleaseai/ast-grep-config/rulesbunx ast-grep scanOr scan with the package’s own config without adding one:
bunx ast-grep scan -c node_modules/@pleaseai/ast-grep-config/sgconfig.ymlThe Five Principles
| Slug | Principle |
|---|---|
code-greppable-public-symbols |
Public symbols are named declarations, so one name search finds the definition and every use. |
code-filename-matches-primary-symbol |
A file with one public symbol is named after it (normalized to the language’s file-name convention). |
code-error-types-in-dedicated-module |
A module’s error types live in its designated error file or package. |
test-path-derivable-from-source |
Tests live under the toolchain’s test root, at a path mirrored from the source they test. |
test-helpers-in-dedicated-location |
Helpers shared by several tests live in the designated helper location. |
The checks run in two layers:
- Layer 2 — the ast-grep rules in this package, run with
ast-grep scan. - Layer 3 —
please-style check, a path checker for what ast-grep cannot see (file names, test paths, and which helpers are shared). It runs the extraction rules in this package’sextract/directory.extract/is not listed inruleDirs, so its matches never appear inast-grep scanoutput.
Rules
| Rule | Slug | Language |
|---|---|---|
ts-no-default-export |
code-greppable-public-symbols |
TypeScript |
tsx-no-default-export |
code-greppable-public-symbols |
TSX |
rust-no-glob-reexport |
code-greppable-public-symbols |
Rust |
ts-error-outside-errors-file |
code-error-types-in-dedicated-module |
TypeScript |
tsx-error-outside-errors-file |
code-error-types-in-dedicated-module |
TSX |
dart-error-outside-errors-file |
code-error-types-in-dedicated-module |
Dart |
kotlin-error-outside-errors-file |
code-error-types-in-dedicated-module |
Kotlin |
java-error-outside-error-package |
code-error-types-in-dedicated-module |
Java |
rust-error-outside-error-file |
code-error-types-in-dedicated-module |
Rust |
ts-no-in-source-test |
test-path-derivable-from-source |
TypeScript |
tsx-no-in-source-test |
test-path-derivable-from-source |
TSX |
Each rule has a README next to it with the reason, a wrong and a right example, and the steps to fix a violation. The warning message names the slug and the README path inside node_modules, so an agent can fix the violation from the message alone:
[code-greppable-public-symbols] Default export hides the symbol's name from
grep. Export a named declaration instead. Fix guide:
node_modules/@pleaseai/ast-grep-config/rules/typescript/ts-no-default-export.mdNo default exports
ts-no-default-export and tsx-no-default-export flag export default …, export { foo as default }, and export { default } from './x'. A default import lets every importer pick its own local name, so a search for the declaration’s name misses those import sites. A named import must mention the exported name.
// Wrong
export default class UserService {}
// Right
export class UserService {}No glob re-exports
rust-no-glob-reexport flags any use with a visibility modifier (pub, pub(crate), pub(super)) that contains a *. Private glob imports such as use super::*; are not matched. Replace the glob with an explicit list of the items it exports today:
// Wrong
pub use crate::model::*;
// Right
pub use crate::model::{Account, User};Error types in a dedicated module
ast-grep has no type information, so the error rules match by name. For the class-based languages, a class is flagged when its supertype ends in Error or Exception, or when its own name ends in Error or Exception and it also has a supertype:
- TypeScript / TSX: the
extendssuperclass only.implementsdoes not match, and a class named*Erroror*Exceptionis flagged only when it has anextendsclause. - Java: the superclass only (
Throwablealso counts).implementsdoes not match. - Dart: the superclass or any implemented type. A class named
*Erroror*Exceptionneeds a superclass orimplementsclause. - Kotlin: any supertype (
Throwablealso counts), onclassandobjectdeclarations.
Rust flags a struct or enum whose name ends in Error (not Exception) or that has #[derive(…Error)], plus any impl Error for T (including std::error::Error and core::error::Error). Each language has its own designated location:
| Language | Error types live in | Also exempt |
|---|---|---|
| TypeScript / TSX | errors.ts in the module directory |
*.d.ts, *.test.*, *.spec.*, test/, tests/, __tests__/ |
| Dart / Flutter | errors.dart in the library directory |
*_test.dart, test/, integration_test/, test_driver/ |
| Kotlin | Errors.kt in the package directory |
src/test/, src/testFixtures/, source sets ending in Test |
| Java | an error/ subpackage of the module |
src/test/, src/testFixtures/, src/androidTest/ |
| Rust | error.rs next to the module root (or an error/ module) |
integration tests under tests/ |
Only the location is checked — the error file may contain helpers and imports.
No in-source tests
ts-no-in-source-test and tsx-no-in-source-test flag Vitest in-source tests (if (import.meta.vitest) { … }). A test/** glob cannot find them, and production builds need a bundler define to strip them. Move the block into a test file under the package’s test root, at a path mirrored from the source: src/math/add.ts becomes test/math/add.test.ts.
Framework Files
Files whose shape a framework dictates are excluded from ts-no-default-export and tsx-no-default-export through their ignores lists:
- Type declarations:
*.d.ts,*.d.mts,*.d.cts - Config files:
*.config.tsand*.config.*.ts(for examplevite.config.prod.ts), in their.mts,.cts, and.tsxforms too - Storybook stories (
*.stories.ts,*.stories.tsx, …),.vitepress/, and.storybook/ - Nuxt 4:
app/pages/,app/layouts/,app/middleware/,app/plugins/, plusapp/app.config.tsandapp/router.options.ts - Nitro:
server/api/,server/routes/,server/middleware/,server/plugins/,server/tasks/
The full lists are in ts-no-default-export.yml and tsx-no-default-export.yml. No other rule exempts these paths, except type declarations, which ts-error-outside-errors-file and tsx-error-outside-errors-file also skip.
Nuxt 3 root directories (pages/, layouts/, middleware/, and plugins/ without app/) are not excluded: a glob such as **/plugins/** would also exempt every unrelated directory named plugins. ast-grep cannot override a shipped rule’s ignores, so scope the rules from the command line instead — turn them off in the main scan and run them once more with your framework directories excluded:
ast-grep scan --off=ts-no-default-export --off=tsx-no-default-export
ast-grep scan --filter '^(ts|tsx)-no-default-export$' \
--globs '!pages/**' --globs '!layouts/**' \
--globs '!middleware/**' --globs '!plugins/**' --globs '!app.config.ts'Put both commands in one package script (for example lint:structure) so CI and agents run the same thing. After migrating to the Nuxt 4 app/ layout, replace the two-step workaround with a single plain ast-grep scan. Keeping only the first command would leave both rules turned off for every file.
Compatibility
@ast-grep/cli^0.45.3- Node.js
>=22.0.0