VibeTags is a compile-time Java annotation processor that generates AI platform-specific guardrail files from source annotations — zero runtime overhead, all from a single mvn compile.
At a glance: 44 annotations → guardrails for 37 AI platforms, written as 49 config files and 13 scoped-rule directories. These numbers are the single source of truth for the project's scope; other docs link back here rather than restating them. A platform is a tool, not a file — Cursor is one platform with both
.cursorrulesand.cursorignore. (All four counts verified byProjectFactsConsistencyTest.)
.cursorrules, CLAUDE.md, and similar files are hand-edited by each developer, grow inconsistent across the team, and go stale the moment the code changes. VibeTags makes your AI configuration source-controlled and compile-enforced:
- Annotate once, all platforms updated — add
@AILockedtoPaymentProcessorand every AI tool's guardrail file is regenerated on the next compile. No more per-developer copy-pasting across 49 config files. - Derived from the code, not separate from it — guardrails live next to the code they protect. When the code moves, the rules move with it.
- Granular rules keep the always-loaded context slim — opt a platform's scoped-rules directory in (
.claude/rules/,.cursor/rules/,.windsurf/rules/,.github/instructions/,.gemini/rules/) and its aggregate file collapses to an index: only the safety buckets (@AILocked,@AICore,@AIPrivacy,@AIIgnore,@AIAudit,@AISecure) stay inline, and the per-element detail loads on demand when the matching source file is opened. This repository dogfoods it — the generated block in its ownCLAUDE.mdis 45 lines, with 115 lines of per-element detail sitting in.claude/rules/until they are relevant. Without it, that file grows linearly with every annotated element. See USAGE.md. - Zero runtime cost —
RetentionPolicy.SOURCEannotations are erased at compile time; nothing reaches the JVM. - CI-enforceable — opt-in check mode (
-Avibetags.check=true) fails the build when guardrail files have drifted from the annotations, and the locked-files GitHub Action fails any PR whose diff touches@AILockedcode. See USAGE.md.
Maven
1. Add to pom.xml:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>se.deversity.vibetags</groupId>
<artifactId>vibetags-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>se.deversity.vibetags</groupId>
<artifactId>vibetags-annotations</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>se.deversity.vibetags</groupId>
<artifactId>vibetags-processor</artifactId>
<version>1.0.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>2. Opt in to the AI platforms you use (file presence = opt-in; VibeTags never creates files on its own):
touch CLAUDE.md .cursorrules AGENTS.md # Claude, Cursor, Codex CLI — add whichever you use3. Annotate your first class:
@AILocked(reason = "Legacy payment integration — changes will break production.")
public interface PaymentProcessor {
String processPayment(double amount, String currency, String merchantId);
}4. Compile:
mvn compileCLAUDE.md and .cursorrules now contain generated guardrail rules. Open either file and look for the VIBETAGS-START / VIBETAGS-END marker block — that section is managed by VibeTags and regenerated on every compile.
Gradle
1. Add to build.gradle:
dependencies {
implementation platform('se.deversity.vibetags:vibetags-bom:1.0.0')
annotationProcessor platform('se.deversity.vibetags:vibetags-bom:1.0.0')
compileOnly 'se.deversity.vibetags:vibetags-annotations'
annotationProcessor 'se.deversity.vibetags:vibetags-processor'
}2. Opt in to the AI platforms you use:
touch CLAUDE.md .cursorrules AGENTS.md3. Annotate your first class:
@AILocked(reason = "Legacy payment integration — changes will break production.")
public interface PaymentProcessor {
String processPayment(double amount, String currency, String merchantId);
}4. Compile:
gradle compileJava- What is VibeTags?
- Project Structure
- Installation
- How It Works
- Organizing Context Files
- Documentation
- Building from Source
- Performance & Load Tests
- When to Use VibeTags
- Advanced Features & Annotation Reference → full guide in USAGE.md
- Contributing
- Project Components
- License
VibeTags provides Java annotations that serve as instructions for AI code generation tools. When your project is compiled, the VibeTags annotation processor automatically generates platform-specific configuration files that enforce your rules across different AI platforms.
VibeTags AI Guardrails for Java — what the annotations do, what they generate, and how the generated files reach each AI tool.
Two facts you need before writing an annotation, neither of which the categorised list below carries: what it can annotate, and which tier its guardrail lands in.
- Can annotate is the annotation's
@Target. Most are type-and-method; a few are narrower, and two (@AIInputSanitized,@AISecureLogging) can only go on a parameter or field — the finest addressing VibeTags produces. - Tier is where the rule shows up once a platform's scoped-rules directory is opted in.
safety guardrails stay inline in the always-loaded aggregate, because a rule that only loads
after the agent opens the file it protects has already failed. Everything else moves to the
scoped file and is replaced by a one-line pointer. See
example-all-tiers/, which demonstrates all three. - Required attributes have no default; leave one out and the code does not compile.
Every annotation below is used in example/ — that README maps each one to the
file demonstrating it — and all four example projects exercise the full set.
| Annotation | Can annotate | Required attributes | Tier |
|---|---|---|---|
@AIArchitecture |
type | — | scoped |
@AIAudit |
type, method | — | safety |
@AIBannedApi |
type, method | forbidden |
scoped |
@AICallersOnly |
type, method | value |
scoped |
@AIContext |
type, method | — | scoped |
@AIContract |
type, method | — | scoped |
@AICore |
type, method, field | — | safety |
@AIDeprecated |
type, method, field | — | scoped |
@AIDomainModel |
type | — | scoped |
@AIDraft |
type, method | — | scoped |
@AIExplain |
type, method | — | scoped |
@AIExtensible |
type | — | scoped |
@AIFeatureFlag |
type, method, field | — | scoped |
@AIGenerated |
type, method, field | from |
scoped |
@AIIdempotent |
type, method | — | scoped |
@AIIgnore |
type, method, field | — | safety |
@AIImmutable |
type | — | scoped |
@AIInputSanitized |
parameter, field | value |
scoped |
@AIInternationalized |
type, method | — | scoped |
@AIKeepInSync |
type, method, field | mirrors |
scoped |
@AILegacyBridge |
type, method | — | scoped |
@AILoadBearing |
type, method, field, parameter | invariant |
scoped |
@AILocked |
type, method, field | — | safety |
@AIMemoryBudget |
type, method | — | scoped |
@AIObservability |
type, method | — | scoped |
@AIParallelTests |
type, method | — | scoped |
@AIPerformance |
type, method, field | — | scoped |
@AIPrivacy |
type, method, field | — | safety |
@AIPrototype |
type | — | scoped |
@AIPublicAPI |
type, method | — | scoped |
@AIPure |
method | — | scoped |
@AIRegulation |
type, method, field | standard |
scoped |
@AISandboxOnly |
type, method | — | scoped |
@AISchemaSafe |
type, field | — | scoped |
@AISecure |
type, method | — | safety |
@AISecureLogging |
field, parameter | — | scoped |
@AIStrictClasspath |
type, method | — | scoped |
@AIStrictExceptions |
type, method | — | scoped |
@AIStrictTypes |
type, method, field | — | scoped |
@AISunset |
type, method, field | jira |
scoped |
@AITemporary |
type, method | expiresOn, reason |
scoped |
@AITestDriven |
type, method | — | scoped |
@AIThreadAffinity |
type, method | value |
scoped |
@AIThreadSafe |
type, method | — | scoped |
AnnotationReferenceTest regenerates this table from the annotation sources and the renderer and
fails if it drifts, so the targets and tiers here are the ones the compiler and processor actually
use rather than a description of them.
The 44 annotations group into six categories by intent. Within each category they are listed alphabetically.
- 🔒 @AICallersOnly - Restrict method or class access to authorized packages/classes only (prevents architectural bypasses)
- 🚫 @AIIgnore - Exclude classes, methods, or fields from AI context entirely (auto-generated code, deprecated scaffolding)
- 🌉 @AILegacyBridge - Mark compatibility bridges working around upstream dependencies/bugs (AI must not refactor or modernize structure; modify internal business logic only)
- 🔒 @AILocked - Protect critical code from AI modifications (legacy systems, compliance code, security-critical logic)
- 🧪 @AISandboxOnly - Restrict elements strictly to mock/sandbox environments (prevents leakage into production pathways)
- 🧱 @AIArchitecture - Enforce layering boundaries (declares
belongsTolayer and forbiddencannotReferencelayers) - ⛔ @AIBannedApi - Forbid named symbols at this element even though they compile — hosted on the consumer so it reaches stdlib and third-party APIs you cannot annotate, and names the sanctioned replacement
- 📜 @AIContract - Freeze the public signature of an interface or method — AI may change internal logic but must not alter method names, parameter types, parameter order, return types, or checked exceptions
- 💾 @AIMemoryBudget - Enforce zero-allocation, no-new-objects, or no-autoboxing policies on high-performance paths
- ⚡ @AIPerformance - Enforce strict time/space complexity constraints for performance-critical hot-paths
- 🧼 @AIPure - Mark deterministic, side-effect-free pure mathematical functions
- 📦 @AIStrictClasspath - Prevent dependency bloat (restricts imports and implementation to JDK and existing classpath only)
- 🧠 @AICore - Mark well-tested core logic that is sensitive to changes (modifications require extreme caution)
- 🌾 @AIDomainModel - Enforce Domain-Driven Design (DDD) boundaries by preventing external framework imports (JPA, Jackson, Spring, etc.)
- 🔌 @AIExtensible - Mark classes open for capability extensions using Strategy, Visitor, or Factory patterns instead of if-else spikes
- ❄️ @AIImmutable - Declare a class immutable; the processor warns if any non-static instance field is non-final
- 🗣️ @AIInternationalized - Prohibit hardcoded user-facing strings (all visible text must be extracted to i18n bundle message keys)
- 🔗 @AIKeepInSync - Name the sites this element is duplicated at; it may change freely, but a partial change silently desyncs a mirror no compiler checks
- 🧩 @AILoadBearing - Mark code that looks redundant or over-defensive and is deliberate — records the invariant and the concrete failure that "cleaning it up" reintroduces
- 📡 @AIObservability - Name the metrics, traces, and log statements downstream dashboards depend on — AI must not silently remove or rename them
- 🌐 @AIPublicAPI - Protect public APIs (all modifications must be additive; forbidden to rename or change serialization formats)
- 🗄️ @AISchemaSafe - Protect persistent database entities (forbids destructive changes, column drops, table drops)
- 🛡️ @AIStrictExceptions - Enforce strict error handling (forbids swallowing exceptions or throwing generic RuntimeExceptions)
- 📐 @AIStrictTypes - Require high-precision or timezone-sensitive data structures (e.g.
BigDecimalfor currency andInstant/ZonedDateTimefor time) - 🧵 @AIThreadAffinity - Declare that an element is safe on exactly one thread — the inverse of
@AIThreadSafe, so an AI asked to "make it thread-safe" marshals the call instead of adding a lock - 🧵 @AIThreadSafe - Declare a thread-safety strategy (
SYNCHRONIZED,LOCK_FREE,IMMUTABLE,THREAD_LOCAL,OTHER) that AI must preserve on every change
- 🛡️ @AIAudit - Tag critical infrastructure for continuous AI security auditing (SQL injection, thread safety, etc.)
- 🧼 @AIInputSanitized - Require input parameters or fields to be run through SQL injection, XSS, LDAP, or path traversal sanitizers
- 🔐 @AIPrivacy - Mark fields and methods that handle PII — AI must never include their values in logs, suggestions, test fixtures, or external API calls
- 📜 @AIRegulation - Tie code to a specific regulatory clause (GDPR, PCI-DSS, HIPAA, SOX) — AI must document compliance impact and never weaken the requirement
- 🔒 @AISecureLogging - Enforce masking (omit, hash, credit-card, email) on log statement variables to avoid runtime leakage
- 📋 @AIContext - Guide AI on how to work with specific classes (performance optimizations, design patterns, frameworks)
- ✏️ @AIDraft - Mark methods or classes that need AI implementation with detailed instructions
- 💬 @AIExplain - Demand Chain-of-Thought (CoT) sequence/class diagrams or mathematical justifications before applying code changes
- 🚩 @AIFeatureFlag - Mark code gated behind a feature flag; AI must preserve the flag check and never assume the flag is always active
- 🤖 @AIGenerated - Mark machine-generated code whose hand edits are overwritten, and redirect the change to the true source — unlike
@AILocked, which can only say "stop" - ♻️ @AIIdempotent - Declare that an operation must remain idempotent; AI must never introduce side effects that cause repeated calls to produce different results
- 🧪 @AIParallelTests - Enforce strict test isolation for concurrent execution (forbids shared mutable state or resource conflicts)
- 🧪 @AIPrototype - Relax standard strict quality rules (e.g. coverage, i18n) for rapid spikes while preventing leaks into production code
- 🔐 @AISecure - Mark security-critical code (authentication, encryption, authorization) — AI must not weaken security properties and must flag any change for security review
- 🧪 @AITestDriven - Enforce Red-Green-Refactor discipline — AI must provide matching test updates alongside any logic changes (configurable coverage goal, framework, and mock policy)
⚠️ @AIDeprecated - Actively route callers away from a deprecated element — declares the replacement, migration guide, and removal deadline- 🌅 @AISunset - Block any new references to an API scheduled for decommissioning (specifies JIRA ticket and target replacement class)
- ⏳ @AITemporary - Tag a hotfix, mock stub, or dirty hack with an expiration date (YYYY-MM-DD) which warns at compile-time if exceeded
Generated configuration files work out-of-the-box with the 37 AI platforms below (Cursor and Windsurf each appear under two formats):
- Aider (
CONVENTIONS.md,.aiderignore) - Antigravity AI (
.antigravityignore) - Claude (
CLAUDE.md,CLAUDE.local.md,.claude/skills/vibetags-guardrails/SKILL.md,.claudeignore) - Cline (
.clinerules) - Codex CLI (
AGENTS.md†,.codex/config.toml,.codex/rules/*.rules) - Codeium (
.codeiumignore) - Cursor (
.cursorrulesor Granular.cursor/rules/*.mdc) - Double.bot (
.doubleignore) - Firebase AI (
.idx/airules.md) - Gemini (
gemini_instructions.md,GEMINI.md,.aiexclude) - GitHub Copilot (
.github/copilot-instructions.md,.copilotignore) - JetBrains Junie (
.junie/guidelines.md) - Mentat (
.mentatconfig.json) - Open Interpreter (
.interpreter/profiles/vibetags.yaml) - Plandex (
.plandex.yaml) - Qwen (
QWEN.md,.qwen/settings.json,.qwen/commands/refactor.md,.qwenignore) - Sourcegraph Cody (
.cody/config.json,.codyignore) - Supermaven (
.supermavenignore) - Sweep (
sweep.yaml) — AI code review rules for the Sweep GitHub App - Void Editor (
.void/rules.md) - Windsurf IDE (
.windsurfrules)
- CodeRabbit (
.coderabbit.yaml) —reviews.path_instructionsthat flag PRs violating guardrails - Qodo / Codium PR-Agent (
.pr_agent.toml) —extra_instructionsfor the reviewer and code-suggestion tools - Ellipsis (
ellipsis.yaml) — onepr_review.rulesentry per guardrail
- Repomix (
.repomixignore) - Gitingest (
.gitingestignore) - GPT context packer (
.gptignore) - Ghostcoder (
.ghostcoderignore) - Pieces for Developers (
.piecesignore)
- Amazon Q (
.amazonq/rules/*.md) - Claude (
.claude/rules/*.md— YAML front-matter (paths:) + Markdown) - Continue (
.continue/rules/*.md— YAML front-matter + Markdown) - Cursor (
.cursor/rules/*.mdc— YAML front-matter + Markdown) - GitHub Copilot (
.github/instructions/*.instructions.md— YAML front-matter (applyTo:) + Markdown) - PearAI (
.pearai/rules/*.md— YAML front-matter + Markdown) - Amazon Kiro (
.kiro/steering/*.md) - Roo Code (formerly Roo Cline) (
.roo/rules/*.md, plus a.roomodes"VibeTags Architect" custom mode) - Tabnine (
.tabnine/guidelines/*.md) - Trae (
.trae/rules/*.md) - Universal AI (
.ai/rules/*.md— open standard for multi-tool projects) - Windsurf (
.windsurf/rules/*.md— YAML front-matter + Markdown)
†
AGENTS.mdis only generated when it is the sole AI config file in the project. BecauseAGENTS.mdis a near-universal agent file that teams often keep as a thin pointer to another tool's file (e.g.CLAUDE.md), VibeTags leaves it untouched whenever any other AI config file is present (this also disables the.codex/sidecar). Opt in to onlyAGENTS.mdto have it managed.
vibetags/
├── vibetags/ # Core annotation processor library
│ ├── pom.xml # Maven build configuration
│ ├── build.gradle # Gradle build configuration
│ └── src/ # Library source code
├── example/ # Example e-commerce application
│ ├── pom.xml # Maven build configuration
│ ├── build.gradle # Gradle build configuration
│ ├── README.md # Detailed usage guide and best practices
│ └── src/ # Example source code with annotations
├── vibetags-annotations/ # The 24 @interface classes (zero deps, RetentionPolicy.SOURCE)
│ ├── pom.xml
│ ├── build.gradle
│ └── src/main/java/ # AIArchitecture, AIAudit, AIContract, AIContext, AICore, AIDeprecated, AIDraft, AIIdempotent, AIIgnore, AIImmutable, AIInternationalized, AILegacyBridge, AILocked, AIObservability, AIParallelTests, AIPerformance, AIPrivacy, AIPublicAPI, AIRegulation, AISchemaSafe, AIStrictClasspath, AIStrictExceptions, AIStrictTypes, AITestDriven, AIThreadSafe
├── vibetags-bom/ # Bill of Materials (versions only, no source)
│ └── pom.xml # Imported by consumers to manage vibetags-* versions in one place
├── load-tests/ # Performance & safety test harness (standalone)
│ ├── README.md # How to run, what to measure, baseline comparison guide
│ ├── pom.xml # Maven configuration (JMH + JUnit 5)
│ ├── src/
│ │ ├── main/java/ # JMH benchmark classes + helpers
│ │ └── test/java/ # Stress test + concurrent build test
│ └── results/ # Frozen per-release baselines (env, stress, concurrent, jmh.json) + _plots/
├── tools/
│ └── plot-results.py # Renders comparison PNGs from load-tests/results/
├── docs/ # Architecture documentation and diagrams
│ ├── ARCHITECTURE.md # Technical deep-dive into the processor internals
│ └── diagrams/ # PlantUML source files and rendered PNGs
├── .claude/
│ └── skills/
│ └── vibetags-usage/ # Claude Code skill — annotation reference and usage guide
└── README.md # This file
- Java 21 or higher (tested on 21, 25, 26)
- Maven 3.6+ or Gradle 7.0+
VibeTags ships as two artifacts:
vibetags-annotations— 24@interfaceclasses (zero dependencies). Goes on the consumer's compile classpath.vibetags-processor— thejavacannotation processor (depends on slf4j/logback forvibetags.log). Goes on the annotation-processor path only — keeping it offcompileClasspathis what stops slf4j/logback from leaking into consumer code.
The recommended setup uses the BOM (vibetags-bom) to manage both versions in one place; pinning each version explicitly is also supported.
Maven:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>se.deversity.vibetags</groupId>
<artifactId>vibetags-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>se.deversity.vibetags</groupId>
<artifactId>vibetags-annotations</artifactId>
</dependency>
</dependencies>
<!-- Processor only on the AP path, not as a regular dependency -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>se.deversity.vibetags</groupId>
<artifactId>vibetags-processor</artifactId>
<version>1.0.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>Note:
maven-compiler-plugin's<annotationProcessorPaths>does not honour<dependencyManagement>(see MCOMPILER-391). Reuse the BOM version property there — seeexample/pom.xml.
Note (JDK 23+):
javaconly runs annotation processors that are explicitly configured;<annotationProcessorPaths>and Gradle'sannotationProcessorqualify. A pom that instead listsvibetags-processoras an ordinary dependency silently generates nothing on JDK 23+ unless<proc>full</proc>is added tomaven-compiler-plugin. More silent-failure cases: Troubleshooting: Nothing Was Generated.
Gradle:
dependencies {
implementation platform('se.deversity.vibetags:vibetags-bom:1.0.0')
annotationProcessor platform('se.deversity.vibetags:vibetags-bom:1.0.0')
compileOnly 'se.deversity.vibetags:vibetags-annotations'
annotationProcessor 'se.deversity.vibetags:vibetags-processor'
}Maven:
<dependency>
<groupId>se.deversity.vibetags</groupId>
<artifactId>vibetags-annotations</artifactId>
<version>1.0.0</version>
</dependency>
<!-- vibetags-processor goes in <annotationProcessorPaths> as shown above -->Gradle:
compileOnly 'se.deversity.vibetags:vibetags-annotations:1.0.0'
annotationProcessor 'se.deversity.vibetags:vibetags-processor:1.0.0'Backwards compatibility: Existing 0.5.x setups that depended on
vibetags-processor:<version>directly continue to work — the processor pullsvibetags-annotationstransitively. New projects should prefer the split pattern above.
- Add Annotations - Place VibeTags annotations on your Java classes and methods
- Compile - Run your normal build process (Maven/Gradle)
- Generate - VibeTags automatically creates AI configuration files
- Use - AI tools read these files and follow your guardrails
// Protect critical legacy code
@AILocked(reason = "Tied to legacy database schema. Changes will break production.")
public interface PaymentProcessor {
String processPayment(double amount, String currency, String merchantId);
}
// Guide AI behavior for performance-critical code
@AIContext(
focus = "Optimize for memory usage over CPU speed",
avoids = "java.util.regex, String.split(), StringBuilder in loops"
)
public class StringParser {
// AI will follow these guidelines
}
// Request AI implementation
@AIDraft(instructions = "Implement email sending with HTML template support and retry logic")
public boolean sendEmail(String to, String subject, String body) {
// @DIDraft: AI should implement this
}
// Tag critical infrastructure for continuous security auditing
@AIAudit(checkFor = {"SQL Injection", "Thread Safety issues"})
public class DatabaseConnector {
// AI must audit any modifications for SQL injection and thread safety
}
// Exclude auto-generated code from AI context entirely
@AIIgnore(reason = "Auto-generated at build time. Manual edits are overwritten on every build.")
public class GeneratedMetadata {
// AI tools will not reference or suggest changes to this class
}
// Mark PII fields — AI must never log or expose these values
public class DatabaseConnector {
@AIPrivacy(reason = "Database credential - never log or include in error messages")
private final String username;
@AIPrivacy(reason = "Database credential - never log or include in error messages")
private final String password;
}
// Mark sensitive core logic
@AICore(sensitivity = "Critical", note = "Core transaction routing logic. Do NOT refactor without user approval.")
public class TransactionRouter {
// AI will treat this as highly sensitive
}
// Enforce performance constraints
@AIPerformance(constraint = "Must maintain O(log n) time complexity for search operations")
public class BinarySearchTree {
// AI will avoid suboptimal complexity implementations
}
// Freeze the public API signature — internal logic may change freely
public class PricingService {
@AIContract(reason = "Signature locked by OpenAPI v2 contract shared with checkout-service and mobile-app")
public double calculatePrice(String productId, int quantity, String customerId) {
// AI can replace this entire implementation — just never touch the signature
}
}AI tools work best when guardrails are scoped to where you're working, not dumped into one ever-growing file. VibeTags lets you opt into three tiers (Tier 1–3) that compose and de-duplicate automatically — here using Claude Code as the example:
| Tier | Layer | Opt in by creating | When Claude loads it |
|---|---|---|---|
| Tier 1 | Project | CLAUDE.md (repo root); add .vibetags-root-index for the lean indexed root |
Always in context |
| Tier 2 | Module | module-a/CLAUDE.md |
When working inside that module |
| Tier 3 | Element / topic | .claude/rules/ (directory; group with .vibetags-roles) |
When it opens a matching source file |
The layers never duplicate content:
- Root + granular together → the root
CLAUDE.mdkeeps only the always-on safety guardrails inline (@AILocked,@AICore,@AIPrivacy,@AIIgnore,@AIAudit,@AISecure) and replaces the rest with a one-line index pointing at the scoped.claude/rules/*.mdfiles. Verbose per-element detail (context, contracts, performance, …) is pulled in only when Claude opens that file — keeping your always-loaded context lean and the high-value rules undiluted. - Per-module
CLAUDE.md→ holds only that module's guardrails, so Claude gets focused rules while working in the module, while the repo-rootCLAUDE.mdstill carries the whole picture. - Indexed reactor root (
.vibetags-root-index+ per-module.claude/rules/) → the same split, one level up: each module's region in the rootCLAUDE.mdkeeps that module's safety guardrails inline and points atmodule-a/.claude/rules/for everything else. A module with nothing in the safety tier contributes only the pointer.
By default each annotated class gets its own scoped file (com-example-PaymentProcessor.md). For the more idiomatic layout — a few human-named topic files, the way Claude's docs recommend — drop a .vibetags-roles file in the repo (or a module) root:
# .vibetags-roles — name = comma-separated globs and/or fully-qualified names
api-endpoints = **/*Controller.java
database-models = **/*Entity.java
external-webhooks = **/webhooks/**, com.example.legacy.WeirdEndpoint
On the next compile, .claude/rules/ (and .cursor/rules/, …) contains api-endpoints.md, database-models.md, external-webhooks.md — each with a paths: glob list and the grouped guardrails, loaded on-demand when Claude opens a matching file. Three tiers, simplest first:
- Package scope (zero config) — annotate a
package-info.java; that package gets one directory-scoped rule file. Nothing else needed. - Role globs (the power feature) — the
.vibetags-rolesglobs above group elements by naming pattern or directory. An element goes to the first matching role (config order); anything matching no role keeps its own per-class file, so nothing is ever lost. - Per-element override — for the odd class that doesn't fit its glob, add its fully-qualified name to a role line (see
com.example.legacy.WeirdEndpointabove). No annotation required — all routing lives in one file.
.vibetags-roles works per module too (drop one in a module root) and composes with the index and per-module layers above.
Guardrails are scoped to the module that owns the annotated source. If your reactor keeps every test
in one module, the code that actually exercises your @AILocked bridges and @AIPrivacy key
material sits in a sibling of the rules protecting it — so a tool that discovers rule directories
by walking up from the edited file finds nothing, silently
(#312).
The consuming module opts in, by dropping a .vibetags-mirror next to its own .claude/rules/:
# payments-tests/.vibetags-mirror
# Source modules, relative to this file. Omit them all to mirror every module.
../payments-core
../payments-api
# Globs appended to each mirrored file, so the rules match this module's sources.
# Defaults to **/payments-tests/**/*.java
glob = **/payments-tests/src/test/java/**/*.java
The next compile writes payments-tests/.claude/rules/mirrored-payments-core-*.md — the source
module's rule verbatim, with your test globs added. The target needs no @AI* annotations of its
own, mirrored files are namespaced per source module (so modules compiling independently never
clobber each other), and they are cleaned up like any other generated file when the annotations go
away. Worked example: example-multimodule/tests/.
my-app/
├── CLAUDE.md # always loaded: safety guardrails + index to the rest
├── .claude/rules/ # root-level detail only; per-module detail lives in each module below
│ ├── com-example-PaymentProcessor.md
│ └── …
├── payments/
│ ├── CLAUDE.md # loaded when working in payments/
│ └── .claude/rules/ # payments-scoped per-file detail
└── billing/
└── CLAUDE.md # loaded when working in billing/
Activate any layer by creating the file or directory and compiling — VibeTags never creates opt-in files for you, and deleting one turns that layer off. The same pattern works for Cursor (.cursorrules + .cursor/rules/), Windsurf (.windsurfrules + .windsurf/rules/), and Copilot (.github/copilot-instructions.md + .github/instructions/).
.claude/rules/ at the repo root is a single-module (or non-reactor) mechanism — there the
root is the project, so its scoped rules are simply the project's Tier-3 detail. In a multi-module
reactor a root .claude/rules/ cannot aggregate across modules (each module overwrites it — see
#295), so put Tier-3 rules in each module
(module-a/.claude/rules/) and let the indexed Tier-1 root (.vibetags-root-index) point at them.
Reserve a root .claude/rules/ in a reactor for genuine root-level sources.
| You have | Tier-1 root | Tier-3 detail |
|---|---|---|
| Single module | CLAUDE.md |
root .claude/rules/ ✅ |
| Reactor, lean | CLAUDE.md + .vibetags-root-index (indexed) |
per-module .claude/rules/ |
| Reactor, always-on | CLAUDE.md (merged) |
per-module CLAUDE.md (Tier 2) |
Worked examples: example/ (single-module, root granular — Tier 3 at the root),
example-multimodule/ (reactor, merged root + per-module Tier 2),
example-multimodule-indexed/ (reactor, indexed root + per-module Tier 3),
and example-all-tiers/ (all three tiers at once, with a class annotated at
every level — type, instance field, method, and method parameter — so you can see which tier each
one lands in).
| Resource | What it covers |
|---|---|
| Usage & Annotation Reference | The full configuration guide: logging, the file-existence opt-in model, granular rules, the llms.txt standard, and a worked example for every annotation (@AIAudit, @AIDraft, @AIContract, @AITestDriven, and the v0.9.8 design-intent and platform-guardrail annotations). Read this after the quickstart to get the most out of VibeTags. |
| Example Project | A runnable e-commerce demo that exercises all 44 annotations in realistic, real-world scenarios. Includes the exact output generated for every supported platform (Cursor, Claude, Gemini, Codex CLI, Qwen, Copilot, llms.txt, …), best practices for writing effective annotations, advanced configuration (custom log path, output root, Gradle setup), and a troubleshooting guide. Start here if you want to see VibeTags in action before adding it to your own project. |
| Architecture | A technical deep-dive into how VibeTags works internally. Covers the multi-round annotation accumulation model, the file-existence opt-in mechanism, marker-based partial updates, multi-module build safety, granular rule generation and orphan cleanup, and all 22+ output file formats. Includes class, component, build-sequence, and data-flow diagrams. Essential reading before contributing or debugging unexpected processor behaviour. |
| Load Tests | The performance harness — what each test category measures (annotation-volume sweep, JMH hot-path, concurrent build), which dimensions matter for a compile-time annotation processor, how to capture release-tagged baselines under load-tests/results/<version>/, and how to diff two baselines. Read before adding a new benchmark or treating a stress-test number as a regression. |
| Claude Code Skill | A Claude Code /skill that teaches your AI assistant how to use VibeTags alongside you. Covers the full annotation reference, valid and invalid annotation combinations, how to set up granular rules for Cursor/Trae/Roo Code, all processor options (Maven & Gradle), and a troubleshooting table for common issues. Install it in Claude Code and invoke it with /vibetags-usage so Claude knows the library as well as you do. |
# Build library
cd vibetags && mvn clean install
# Build example
cd ../example && mvn clean compile# Build library
cd vibetags && gradle clean build publishToMavenLocal
# Build example
cd ../example && gradle clean buildThe load-tests/ subproject is a standalone Maven module that stress-tests and benchmarks AIGuardrailProcessor. It must be run after the processor is installed locally (cd vibetags && mvn install -DskipTests).
| Test class | What it measures |
|---|---|
AnnotationVolumeStressTest |
Compiles N synthetic annotated classes (N = 10 → 10 000) in-process via javax.tools.JavaCompiler and reports wall-clock processor overhead vs. a -proc:none baseline, plus total output-file size. |
ConcurrentBuildTest |
Runs N threads simultaneously against a shared project root to surface file-corruption risks from the lack of write locking in writeFileIfChanged. |
ProcessorHotPathBenchmark |
JMH microbenchmarks for writeFileIfChanged (1 KB / 64 KB) and buildServiceFileMap / resolveActiveServices. |
# Install the processor first
cd vibetags && mvn install -DskipTests
# Stress + concurrent tests (full sweep: N = 10, 100, 500, 1000, 5000, 10 000)
cd load-tests && mvn test
# CI-sized run — skip N > 500 to keep it fast
cd load-tests && mvn test -Dstress.max.classes=500
# Increase concurrent threads (default: 4)
cd load-tests && mvn test -Dtest=ConcurrentBuildTest -Dload.test.threads=8
# JMH microbenchmarks (~2 min, produces a fat-jar)
cd load-tests && mvn package exec:java -Dexec.mainClass=org.openjdk.jmh.Main
# Run a specific JMH benchmark
cd load-tests && mvn package exec:java -Dexec.mainClass=org.openjdk.jmh.Main \
-Dexec.args="writeFileIfChanged -f 1 -wi 3 -i 5 -tu ms"Results are written to load-tests/target/stress-results.txt and printed to stdout.
Note
The stress test passes -Avibetags.root=<tempDir> to the compiler so each run writes into an isolated temporary directory, not the project root. This is the same compiler option used in production when a consumer project needs to override the output directory.
The load-tests workflow job (see .github/workflows/build.yml) runs automatically on every push and PR using JDK 21. It caps the sweep at N = 500 (-Dstress.max.classes=500) so the job finishes in under a minute. The stress-results.txt artefact is uploaded for inspection even if a step fails.
Per-release baselines (env metadata, stress-sweep table, concurrent-build report, and JMH JSON) are committed under load-tests/results/<version>/. Run python tools/plot-results.py to regenerate the comparison PNGs in load-tests/results/_plots/. See load-tests/README.md for the full capture procedure, what each metric means, and which dimensions are worth tracking for a compile-time annotation processor — actual numbers live with the baselines, not here.
| Scenario | Use Case |
|---|---|
| Legacy systems | Protect integrations that work and can't be changed |
| Core Logic | Protect stable, well-tested core functionality from regressions |
| Performance-critical | Guide AI toward specific optimization strategies and complexity constraints |
| Compliance code | PCI-DSS, HIPAA, and other regulated code |
| Boilerplate code | Let AI implement standard patterns safely |
| Team projects | Enforce consistent AI behavior across your team |
| Complex algorithms | Protect code that took months to stabilize |
| PII handling | Prevent AI from leaking personal data in logs or suggestions |
VibeTags ships far more than the basics shown above:
- Selective service generation — opt out of specific AI platforms with no config required
- Configurable logging — full control over log file path and level, including turning it off
- Granular rules — automatic
.mdc/.mdfiles with YAML front-matter for precise AI scoping - Compile-time validation — proactive warnings for contradictory or empty annotations
- Per-annotation deep dives —
@AIAudit,@AIDraft,@AIContract,@AITestDriven, plus the v0.9.8 design-intent and platform-guardrail annotations
📖 The full configuration guide and every annotation example now live in USAGE.md.
VibeTags is designed to evolve based on community needs. The two annotations this section
used to propose have both shipped — @AIExtensible takes a design pattern to extend through, and
@AITestDriven enforces Red-Green-Refactor — so the open ground is elsewhere:
- A new platform — one renderer plus two registry entries; see the
add-platformskill - A new annotation — see the
add-annotationskill, which lists every place one has to be wired - Organization-specific processors that consume the same annotations for in-house tooling
Every annotation must be demonstrated in all four example projects and appear in the
annotation reference; ExampleCoverageTest and AnnotationReferenceTest
fail the build otherwise.
The core annotation processor library. Contains the annotation processor that generates AI
configuration files at compile time; the annotations themselves live in vibetags-annotations/
(see project facts for the count).
A practical e-commerce application demonstrating every VibeTags annotation
(see project facts for the count), held to that by ExampleCoverageTest. Shows how to protect legacy payment processors, guide AI on security configurations, request AI implementations for notification services, enforce continuous security auditing for database infrastructure, mark PII fields, identify core business logic, and enforce hot-path performance constraints.
Technical reference for the annotation processor internals. Read this before contributing or if you need to understand why a particular file is (or is not) being generated.
A Claude Code skill that gives your AI assistant a full working knowledge of VibeTags — annotation semantics, valid combinations, processor configuration, and troubleshooting. Activate it in Claude Code with /vibetags-usage.
VibeTags is built and maintained by a single developer in his spare time. If it saves you time, a small donation helps keep the project alive:
- Donate via PayPal — every contribution, however small, is appreciated.
- Don't have PayPal yet? Sign up with this invite link — free for you, and PayPal gives the project a referral bonus.
This project is licensed under the MIT License.
Built with ❤️ for safer AI-assisted development

