rolobits/nestjs-doctor/nestjs-doctor-create-rule
Claude Code and agent skill by rolobits/nestjs-doctor
- community
Checking that the SKILL.md file is still in the repository.
Use when the user wants a convention of their own enforced, asks for a custom nestjs-doctor rule, or says the built-in rules do not cover something their team cares about. Walks through choosing the scope, matching the AST with ts-morph, registering the rule, and verifying it loads.
Install
npx vskill@latest install rolobits/nestjs-doctor/nestjs-doctor-create-ruleSKILL.md
Create a custom rule
v0.0.0
Create a custom rule for nestjs-doctor that detects a specific pattern or anti-pattern in the user's NestJS codebase.
Capabilities and Limitations
Before generating any rule, use this reference to determine what is possible.
What file rules can do
File rules receive a single ts-morph SourceFile at a time. Available APIs:
- Classes:
getClasses(),getName(),getDecorator("Name"),getDecorators(),getImplements(),getConstructors(),getProperties(),getMethods() - Constructor params:
ctor.getParameters()->param.getName(),param.getType().getText(),param.isReadonly(),param.getTypeNode()?.getText() - Methods:
getMethods()->method.getName(),method.getDecorators(),method.getReturnType(),method.getStartLineNumber() - Imports:
getImportDeclarations()->imp.getModuleSpecifierValue(),imp.getNamedImports() - AST traversal:
getDescendantsOfKind(SyntaxKind.X)— find any AST node type - Position:
node.getStartLineNumber(),node.getStart(),node.getStartLinePos(),getText() - External: Can
require()npm packages and Node.js builtins (e.g.,fs) - Source lines: The runner automatically attaches +-5 lines of context to each diagnostic
Type info caveat: param.getType().getText() often returns import("/path").ClassName because the project uses skipFileDependencyResolution. Use param.getTypeNode()?.getText() for the literal annotation text, or regex-extract the class name from the type string.
What project rules can do (everything above, plus)
Project rules receive the entire Project and cross-file analysis data:
context.project.getSourceFile(path)— access any file's ASTcontext.files— array of all file paths being analyzedcontext.moduleGraph.modules—Map<string, ModuleNode>where eachModuleNodehas:{ name, filePath, classDeclaration, imports[], exports[], providers[], controllers[] }context.moduleGraph.edges—Map<string, Set<string>>(module name -> set of imported module names)context.moduleGraph.providerToModule—Map<string, ModuleNode>(provider class name -> owning module)context.providers—Map<string, ProviderInfo>where eachProviderInfohas:{ name, filePath, classDeclaration, dependencies: string[], publicMethodCount }context.config— fullNestjsDoctorConfigobject
Caveats:
providersonly includes@Injectable()classes — value/factory/alias providers are not indexed- Project rule diagnostics do NOT get automatic
sourceLines— include them yourself if needed
Hard limits (CRITICAL)
- `check()` MUST be synchronous — the runner calls
rule.check(context)withoutawait. Anasync check()silently returns a Promise that is discarded. Zero diagnostics, zero errors. This is the #1 silent failure mode. - Cannot modify source files — mutations corrupt the shared AST used by all rules
- File rules cannot access other files, the module graph, or providers
- Only `.ts` rule files are loaded —
.js,.mjs,.cjsare ignored by the custom rule loader - Cross-file type resolution is often incomplete — prefer
param.getTypeNode()?.getText()overparam.getType().getText()for reliable results
Step 1: Assess Feasibility
Before asking for details, evaluate the user's request against the capabilities above.
- Determine detection scope: Single-file pattern -> file rule. Cross-file analysis -> project rule.
- Map the core operation to available APIs. Ask yourself: can ts-morph or the module graph answer this question?
| Request | Feasible? | Why | |
|---|---|---|---|
| "Check that every @Controller has @ApiTags()" | YES | cls.getDecorator("Controller") + cls.getDecorator("ApiTags") | |
| "Check service X is only used in module Y" | YES | moduleGraph.providerToModule.get("X") | |
| "Detect providers with too many dependencies" | YES | provider.dependencies.length threshold check | |
| "Ban a specific npm import" | YES | imp.getModuleSpecifierValue() === "banned-pkg" | |
| "Check runtime types match" | NO | Static analysis only — no runtime access | |
| "Check database query results" | NO | No runtime or I/O during analysis | |
| "Check code is formatted" | NO | Use a linter/formatter instead | |
| "Check that an async handler awaits a call" | PARTIAL | Can detect async keyword and check for AwaitExpression nodes, but cannot trace all control-flow paths |
- Check for the async pitfall — if the detection logic needs to read files from disk asynchronously, make HTTP calls, or do any I/O that requires
await, the rule CANNOT work becausecheck()must be synchronous. Synchronousfs.readFileSync()orfs.existsSync()is fine.
- Report assessment to the user:
- FEASIBLE — proceed to Step 2
- PARTIALLY FEASIBLE — explain what can be detected and what gap remains, let user decide
- NOT FEASIBLE — explain why, suggest an alternative tool or approach
Step 2: Understand the Request
Ask the user what they want to detect. Determine:
- Pattern to detect: What code pattern is bad (or required)?
- Scope: Does the rule check individual files (
file) or need cross-file analysis (project)? - Category:
security,correctness,architecture, orperformance - Severity:
error,warning, orinfo - Rule ID: A short kebab-case name (e.g.,
require-logger-in-services)
If the user is unsure, see the Suggestions section at the bottom for common rule ideas.
Step 3: Check Config
Find the existing nestjs-doctor configuration. Check these locations in order:
Note whether customRulesDir is already set. If it is, use that directory. If not, default to ./nestjs-doctor-rules.
Step 4: Create Rules Directory
Create the custom rules directory if it doesn't exist:
Step 5: Generate the Rule
Read the reference that matches what you are about to write:
| Reference | Read it for | |
|---|---|---|
| references/rule-api.md | The rule object's shape, every meta field, what validation rejects, and putting several rules in one file | |
| references/examples.md | A complete file-scoped rule and a complete project-scoped rule | |
| references/ts-morph.md | Finding decorators, classes, imports, and call expressions in the AST |
Write a .ts file to the custom rules directory. The filename should match the rule ID (e.g., require-logger-in-services.ts).
Step 6: Update Config
If customRulesDir is not already set in the project config, add it.
If `nestjs-doctor.config.json` or `.nestjs-doctor.json` exists, read it and add the customRulesDir field:
If config is in `package.json`, update the nestjs-doctor key.
If no config exists, create nestjs-doctor.config.json:
Step 7: Verify
Run nestjs-doctor and check that the custom rule loads without warnings:
Check the output for:
- The custom rule appearing in diagnostics with
custom/prefix - No validation warnings about the rule file
- The scan completing successfully
If there are warnings, fix the rule file and re-run.
Suggestions
If the user isn't sure what to check, suggest these common custom rules:
| Rule idea | Scope | Category | Detection logic | |
|---|---|---|---|---|
Require a Logger in every service | file | correctness | cls.getDecorator("Injectable") && !ctor.getParameters().some(p => p.getTypeNode()?.getText()?.includes("Logger")) | |
Ban specific npm imports (e.g., moment) | file | performance | imp.getModuleSpecifierValue() === "moment" | |
Require @ApiTags() on all controllers | file | architecture | cls.getDecorator("Controller") && !cls.getDecorator("ApiTags") | |
Require @ApiOperation() on HTTP handlers | file | architecture | method.getDecorators().some(d => httpDecorators.has(d.getName())) && !method.getDecorator("ApiOperation") | |
| Enforce max constructor dependencies | file | architecture | ctor.getParameters().length > MAX on @Injectable() classes | |
| Ban direct DB queries outside repositories | file | architecture | Check getDescendantsOfKind(SyntaxKind.CallExpression) for ORM calls in non-*Repository classes | |
| Enforce naming conventions | file | architecture | cls.getDecorator("Injectable") && !name.endsWith("Service") && !name.endsWith("Repository") | |
| Require services have test files | project | correctness | require("fs").existsSync(provider.filePath.replace(".ts", ".spec.ts")) | |
| Detect providers in multiple modules | project | architecture | Count each provider name across moduleGraph.modules.values() entries' providers[] — flag if > 1 | |
| Require DTO validation pipes on POST/PUT | file | correctness | Check @Post()/@Put() handler params for @Body() + @UsePipes(ValidationPipe) or global pipe |
Security
- Security scanPassed, 93 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingpackages/nestjs-doctor/skills/nestjs-doctor-create-rule/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/rolobits/nestjs-doctor/nestjs-doctor-create-rule)