# 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: 1. **Conflicts with user customizations**: When users customize their agent instructions and then run `vibew init --force` or update their scaffolding, their customizations are overwritten. 2. **Incompatible with AGENTS.md convention**: The emerging convention for AI coding assistants is to place agent instructions in an `AGENTS.md` file 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: 1. **`AGENTS-VIBEWARDEN.md`** — fully owned by vibew, regenerated freely on updates 2. **`AGENTS.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: ```markdown # 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 ``` 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: ```markdown # 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`: 1. **Remove** the `sharedAgentTemplateFiles` slice that generates `.claude/agents/architect.md` and `.claude/agents/reviewer.md`. 2. **Remove** the language-specific `dev.md.tmpl` entries from `goTemplateFiles`, `kotlinTemplateFiles`, and `typescriptTemplateFiles`. 3. **Add** a new method `renderAgentsVibewardenMD`: ```go // 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 ``` 4. **Add** a new method `ensureAgentsMD`: ```go // 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 ``` 5. **Update** `InitProject` to 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: ``` / ├── .claude/ │ └── agents/ │ ├── architect.md │ ├── dev.md │ └── reviewer.md ├── CLAUDE.md └── ... ``` After: ``` / ├── AGENTS.md # User-owned, references AGENTS-VIBEWARDEN.md ├── AGENTS-VIBEWARDEN.md # vibew-owned, regenerated on updates ├── CLAUDE.md # Still generated (project instructions) └── ... ``` #### Sequence 1. User runs `vibew init --lang go myproject` 2. CLI creates project directory 3. Service renders language-specific files (go.mod, main.go, Dockerfile, etc.) 4. 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` 5. Service calls `ensureAgentsMD`: - Checks if `AGENTS.md` exists - If not, creates from `agents/agents.md.tmpl` - If exists, checks for `AGENTS-VIBEWARDEN.md` reference - If reference missing, appends it 6. Service calls `renderCombinedCLAUDEmd` (unchanged — still generates CLAUDE.md) 7. 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:** 1. Run `vibew init --force` to regenerate scaffolding 2. Move any customizations from `.claude/agents/*.md` to `AGENTS.md` 3. Delete `.claude/agents/` directory 4. Commit changes The `.claude/` directory itself remains (it may contain other user files like settings.json), only the `agents/` subdirectory is deprecated. ---