ADR-061: Generate AGENTS-VIBEWARDEN.md instead of .claude/agents/
Date: 2026-04-03 Issue: #632 Status: Accepted
Context
The current vibew init scaffolding generates agent instruction files inside
.claude/agents/ (architect.md, dev.md, reviewer.md). This approach has two problems:
-
Conflicts with user customizations: When users customize their agent instructions and then run
vibew init --forceor update their scaffolding, their customizations are overwritten. -
Incompatible with AGENTS.md convention: The emerging convention for AI coding assistants is to place agent instructions in an
AGENTS.mdfile at the project root. Claude, Cursor, Windsurf, and other tools look for this file. Having instructions scattered in.claude/agents/reduces discoverability.
The goal is to adopt the AGENTS.md convention while allowing vibew to safely regenerate
its instructions without destroying user content.
Decision
Replace the .claude/agents/ generation with a two-file approach:
AGENTS-VIBEWARDEN.md— fully owned by vibew, regenerated freely on updatesAGENTS.md— user-owned, with a reference line to AGENTS-VIBEWARDEN.md
AGENTS-VIBEWARDEN.md structure
This file consolidates all vibew-specific agent instructions into a single file:
# VibeWarden Sidecar — Agent Instructions
> This file is auto-generated by vibew. Do not edit — changes will be overwritten.
> For custom instructions, edit AGENTS.md and reference this file.
## Security boundary rule
**VibeWarden sidecar handles all security — do NOT implement TLS, auth,
rate-limiting, WAF, or security headers in app code.**
The sidecar handles:
- TLS termination (self-signed in dev, Let's Encrypt in prod)
- Authentication (Ory Kratos) — identity injected via headers
- Rate limiting — configured in vibewarden.yaml
- Security headers (HSTS, CSP, X-Frame-Options, etc.)
- WAF and egress security
App code focuses on **business logic only**.
## Sidecar-injected headers
When auth is enabled, read user identity from these headers:
- `X-User-Id` — user identifier
- `X-User-Email` — user email
- `X-User-Verified` — email verification status ("true"/"false")
## Architecture
This project follows hexagonal architecture (ports and adapters):
- `internal/domain/` (or `src/domain/`) — Domain logic, zero external dependencies
- `internal/ports/` (or `src/ports/`) — Interfaces (inbound and outbound)
- `internal/adapters/` (or `src/adapters/`) — Implementations of ports
- `internal/app/` (or `src/app/`) — Application services (use cases)
## Running the project — CRITICAL
**NEVER start the app directly.** Always use vibew:
```bash
vibew dev # Start dev environment (full stack with sidecar)
vibew dev --watch # Start with hot reload
vibew restart # Restart without full rebuild
vibew status # Check component health
vibew doctor # Diagnose issues
Access the app at https://localhost:8443 (through the sidecar). Never expose the app port directly — it has no security.
vibew CLI Reference
| Command | Description |
|---|---|
vibew dev | Start dev environment |
vibew dev --watch | Start with file watching |
vibew build | Build Docker image |
vibew restart | Restart containers |
vibew status | Show component health |
vibew logs | Tail logs |
vibew doctor | Diagnose issues |
vibew token | Generate dev JWT |
vibew secret get/set/list | Manage secrets |
vibew cert export | Export TLS certificate |
vibew validate | Validate vibewarden.yaml |
vibew add auth | Enable authentication |
vibew add postgres | Add PostgreSQL |
vibew eject | Eject to raw Docker Compose |
Security features to NEVER implement
Reject code that implements:
- Custom auth middleware (sidecar handles it)
- Custom rate limiter (sidecar handles it)
- TLS configuration (sidecar handles it)
- Security header middleware (sidecar handles it)
- Hardcoded secrets (use
vibew secret set)
[Language] Code conventions
<language-specific section appended here> ```The [Language] Code conventions section is rendered from the language-specific
template (go/claude.md.tmpl, kotlin/claude.md.tmpl, typescript/claude.md.tmpl).
AGENTS.md handling
-
If AGENTS.md does not exist: Create it with a single reference line:
# Agent Instructions See [AGENTS-VIBEWARDEN.md](./AGENTS-VIBEWARDEN.md) for VibeWarden sidecar instructions. -
If AGENTS.md exists: Check if it already contains a reference to AGENTS-VIBEWARDEN.md. If not, append the reference line at the end. If it does, leave it unchanged.
The reference detection uses a simple substring match for AGENTS-VIBEWARDEN.md.
Domain model changes
No new entities, value objects, or domain events. This is a scaffolding-only change.
Ports (interfaces)
No new ports. The existing ports.TemplateRenderer interface is sufficient.
Adapters
No new adapters. Template rendering uses the existing templateadapter.Renderer.
Application service
Modify internal/app/scaffold/init_project.go:
-
Remove the
sharedAgentTemplateFilesslice that generates.claude/agents/architect.mdand.claude/agents/reviewer.md. -
Remove the language-specific
dev.md.tmplentries fromgoTemplateFiles,kotlinTemplateFiles, andtypescriptTemplateFiles. -
Add a new method
renderAgentsVibewardenMD:// renderAgentsVibewardenMD renders AGENTS-VIBEWARDEN.md by combining the shared // agents/agents-vibewarden.md.tmpl template with language-specific code conventions. func (s *InitProjectService) renderAgentsVibewardenMD( projectDir string, lang domainscaffold.Language, data any, overwrite bool, ) error -
Add a new method
ensureAgentsMD:// ensureAgentsMD ensures AGENTS.md exists and contains a reference to AGENTS-VIBEWARDEN.md. // If AGENTS.md does not exist, creates it with a minimal reference. // If AGENTS.md exists but lacks the reference, appends it. // If AGENTS.md exists and has the reference, does nothing. func (s *InitProjectService) ensureAgentsMD(projectDir string) error -
Update
InitProjectto call these new methods instead of the old agent template rendering loop.
File layout
Templates to create:
| Path | Description |
|---|---|
internal/cli/templates/agents/agents-vibewarden.md.tmpl | Shared base for AGENTS-VIBEWARDEN.md |
internal/cli/templates/agents/agents.md.tmpl | Template for new AGENTS.md (minimal reference) |
Templates to delete:
| Path | Reason |
|---|---|
internal/cli/templates/agents/architect.md.tmpl | Consolidated into agents-vibewarden.md.tmpl |
internal/cli/templates/agents/reviewer.md.tmpl | Consolidated into agents-vibewarden.md.tmpl |
internal/cli/templates/go/dev.md.tmpl | Consolidated into agents-vibewarden.md.tmpl |
internal/cli/templates/kotlin/dev.md.tmpl | Consolidated into agents-vibewarden.md.tmpl |
internal/cli/templates/typescript/dev.md.tmpl | Consolidated into agents-vibewarden.md.tmpl |
Templates to modify:
| Path | Change |
|---|---|
internal/cli/templates/agents/claude.md.tmpl | Keep as-is (still generates CLAUDE.md) |
internal/cli/templates/go/claude.md.tmpl | Keep as-is (language-specific conventions for CLAUDE.md and AGENTS-VIBEWARDEN.md) |
internal/cli/templates/kotlin/claude.md.tmpl | Keep as-is |
internal/cli/templates/typescript/claude.md.tmpl | Keep as-is |
Code files to modify:
| Path | Change |
|---|---|
internal/app/scaffold/init_project.go | Remove old agent rendering, add new methods |
internal/app/scaffold/init_project_test.go | Update tests for new file structure |
internal/app/scaffold/init_project_shared_templates_test.go | Update tests for new file structure |
Generated output structure change:
Before:
<project>/
├── .claude/
│ └── agents/
│ ├── architect.md
│ ├── dev.md
│ └── reviewer.md
├── CLAUDE.md
└── ...
After:
<project>/
├── AGENTS.md # User-owned, references AGENTS-VIBEWARDEN.md
├── AGENTS-VIBEWARDEN.md # vibew-owned, regenerated on updates
├── CLAUDE.md # Still generated (project instructions)
└── ...
Sequence
- User runs
vibew init --lang go myproject - CLI creates project directory
- Service renders language-specific files (go.mod, main.go, Dockerfile, etc.)
- Service calls
renderAgentsVibewardenMD:- Renders
agents/agents-vibewarden.md.tmpl(shared base) - Renders
go/claude.md.tmpl(language-specific conventions) - Concatenates them into
AGENTS-VIBEWARDEN.md
- Renders
- Service calls
ensureAgentsMD:- Checks if
AGENTS.mdexists - If not, creates from
agents/agents.md.tmpl - If exists, checks for
AGENTS-VIBEWARDEN.mdreference - If reference missing, appends it
- Checks if
- Service calls
renderCombinedCLAUDEmd(unchanged — still generates CLAUDE.md) - Git init and initial commit
Error cases
| Error | Handling |
|---|---|
AGENTS-VIBEWARDEN.md exists without --force | Overwrite anyway (file is vibew-owned) |
| AGENTS.md exists but is read-only | Return wrapped OS error |
| Template rendering failure | Return wrapped error with template name |
| File append failure | Return wrapped OS error |
Note: AGENTS-VIBEWARDEN.md is always overwritten because it is explicitly vibew-owned. The warning header in the file makes this clear to users.
Test strategy
Unit tests to update (internal/app/scaffold/init_project_test.go):
| Test | Change |
|---|---|
TestInitProject_CreatesStructure | Assert AGENTS-VIBEWARDEN.md and AGENTS.md exist, remove .claude/agents/ assertions |
TestInitProject_Kotlin_CreatesStructure | Same updates |
TestInitProject_TypeScript_CreatesStructure | Same updates |
Unit tests to add:
| Test | Verification |
|---|---|
TestInitProject_CreatesAgentsVibewardenMD | AGENTS-VIBEWARDEN.md exists with expected content |
TestInitProject_CreatesAgentsMD_WhenMissing | AGENTS.md created with reference when absent |
TestInitProject_AppendsToAgentsMD_WhenMissingRef | Reference appended when AGENTS.md exists but lacks it |
TestInitProject_PreservesAgentsMD_WhenHasRef | AGENTS.md unchanged when reference already present |
TestInitProject_OverwritesAgentsVibewardenMD | AGENTS-VIBEWARDEN.md overwritten even without --force |
TestInitProject_NoClaudeAgentsDir | .claude/agents/ directory is NOT created |
Tests to delete:
| Test | Reason |
|---|---|
All tests asserting .claude/agents/*.md files | Directory no longer generated |
TestInitProject_UsesSharedArchitectTemplate | architect.md.tmpl deleted |
TestInitProject_UsesSharedReviewerTemplate | reviewer.md.tmpl deleted |
TestInitProject_UsesGoDevTemplate | dev.md.tmpl deleted |
TestInitProject_Kotlin_UsesKotlinDevTemplate | dev.md.tmpl deleted |
TestInitProject_TypeScript_UsesTypeScriptDevTemplate | dev.md.tmpl deleted |
Integration tests:
The existing real-FS tests (TestInitProject_WithRealFS_*) should be updated to verify:
- AGENTS-VIBEWARDEN.md contains vibew CLI reference and security rules
- AGENTS-VIBEWARDEN.md contains language-specific conventions
- AGENTS.md contains reference to AGENTS-VIBEWARDEN.md
- No .claude/agents/ directory exists
New dependencies
None. This implementation uses only existing dependencies.
Consequences
Positive:
- Compatible with AGENTS.md convention used by Claude, Cursor, Windsurf
- User customizations in AGENTS.md are never overwritten
- vibew can regenerate AGENTS-VIBEWARDEN.md safely on updates
- Single file is easier to discover than .claude/agents/ directory
- Consolidated instructions reduce duplication across architect/dev/reviewer roles
Negative:
- Breaking change for existing projects using .claude/agents/
- Users must manually migrate existing customizations to AGENTS.md
- Single AGENTS-VIBEWARDEN.md file is larger than individual role files
Migration path for existing projects:
- Run
vibew init --forceto regenerate scaffolding - Move any customizations from
.claude/agents/*.mdtoAGENTS.md - Delete
.claude/agents/directory - Commit changes
The .claude/ directory itself remains (it may contain other user files like
settings.json), only the agents/ subdirectory is deprecated.