diff --git a/README.md b/README.md index 26c159d2..9f9a8eac 100644 --- a/README.md +++ b/README.md @@ -353,7 +353,7 @@ adl generate --file agent.yaml --output ./test-my-agent --deployment cloudrun -- > > - see the matrix below. > `adl init` writes all toggles as `false` by default - they're opt-in. Generated files -> (`CLAUDE.md`, `GEMINI.md`, `AGENTS.md`, `.github/workflows/ci.yml`, +> (`AGENTS.md`, `.github/workflows/ci.yml`, > `.github/workflows/cd.yml`, `.github/workflows/claude.yml`, > `.github/workflows/codex.yml`, `.github/workflows/gemini.yml`, > `.github/workflows/infer.yml`, @@ -391,11 +391,11 @@ spec: ai: orchestrators: claudecode: - enabled: true # generates CLAUDE.md + .github/workflows/claude.yml + enabled: true # generates AGENTS.md + .github/workflows/claude.yml codex: enabled: false # would generate AGENTS.md + .github/workflows/codex.yml gemini: - enabled: false # would generate GEMINI.md + .github/workflows/gemini.yml + enabled: false # would generate AGENTS.md + .gemini/settings.json + .github/workflows/gemini.yml opencode: enabled: false # would generate AGENTS.md (no upstream action yet) infer: @@ -406,17 +406,17 @@ spec: | Agent toggle | Docs file the agent reads | GitHub Actions workflow generated? | | ------------ | ------------------------- | --------------------------------------------------------------------------------- | -| `claudecode` | `CLAUDE.md` | yes (`.github/workflows/claude.yml`, uses `anthropics/claude-code-action`) | +| `claudecode` | `AGENTS.md` (shared) | yes (`.github/workflows/claude.yml`, uses `anthropics/claude-code-action`) | | `codex` | `AGENTS.md` (shared) | yes (`.github/workflows/codex.yml`, uses `openai/codex-action`) | -| `gemini` | `GEMINI.md` | yes (`.github/workflows/gemini.yml`, uses `google-github-actions/run-gemini-cli`) | +| `gemini` | `AGENTS.md` (shared) | yes (`.github/workflows/gemini.yml`, uses `google-github-actions/run-gemini-cli`) | | `opencode` | `AGENTS.md` (shared) | no upstream action yet - docs only | | `infer` | `AGENTS.md` (shared) | yes (`.github/workflows/infer.yml`, uses `inference-gateway/infer-action`) | -- `AGENTS.md` is generated **once** and is shared by every enabled agent that - reads from it (`codex`, `opencode`, `infer`); the file's contents are - agent-agnostic. -- `CLAUDE.md` and `GEMINI.md` are agent-specific and only appear when the - matching toggle is on. +- `AGENTS.md` is generated **once** and is shared by every enabled agent; the + file's contents are agent-agnostic. `CLAUDE.md` and `GEMINI.md` are no longer + generated. +- Gemini CLI defaults to `GEMINI.md`, so enabling `gemini` also generates + `.gemini/settings.json` pointing its context file name at `AGENTS.md`. - If no toggles are enabled, no AI docs or workflows are emitted. - Pre-v0.8.0 manifests using `spec.development.ai.enabled: true`, and the flat per-agent shape `spec.development.ai.` (pre-orchestrators), are no @@ -1379,7 +1379,7 @@ my-go-agent/ ├── .gitignore # Standard Git ignore patterns ├── .gitattributes # Git attributes configuration ├── .editorconfig # Editor configuration -├── CLAUDE.md # AI assistant instructions (spec.development.ai.orchestrators.claudecode.enabled: true) +├── AGENTS.md # AI assistant instructions (claudecode, codex, opencode or infer enabled) └── README.md # Project documentation with setup instructions ``` @@ -1412,7 +1412,7 @@ my-rust-agent/ ├── .releaserc.yaml # Semantic-release configuration (with --cd flag) ├── k8s/ │ └── deployment.yaml # Kubernetes deployment (with --deployment kubernetes) -├── CLAUDE.md # AI assistant instructions (spec.development.ai.orchestrators.claudecode.enabled: true) +├── AGENTS.md # AI assistant instructions (claudecode, codex, opencode or infer enabled) └── README.md # Documentation ``` @@ -1450,7 +1450,7 @@ my-typescript-agent/ ├── .releaserc.yaml # Semantic-release configuration (with --cd flag) ├── k8s/ │ └── deployment.yaml # Kubernetes deployment (with --deployment kubernetes) -├── CLAUDE.md # AI assistant instructions (spec.development.ai.orchestrators.claudecode.enabled: true) +├── AGENTS.md # AI assistant instructions (claudecode, codex, opencode or infer enabled) └── README.md # Documentation ``` @@ -1475,9 +1475,8 @@ All projects include these essential files regardless of language: - **DevContainer**: `.devcontainer/devcontainer.json` when `sandbox.devcontainer.enabled: true` - **AI Assistant Instructions** - Per-agent toggles under `spec.development.ai.orchestrators` (see [Per-agent AI assistants](#per-agent-ai-assistants)): - - **CLAUDE.md** when `spec.development.ai.orchestrators.claudecode.enabled: true` - - **GEMINI.md** when `spec.development.ai.orchestrators.gemini.enabled: true` - - **AGENTS.md** (shared) when any of `codex`, `opencode`, or `infer` is enabled + - **.gemini/settings.json** when `spec.development.ai.orchestrators.gemini.enabled: true` + - **AGENTS.md** (shared) when any of `claudecode`, `codex`, `opencode`, or `infer` is enabled ### CI Integration @@ -2090,7 +2089,7 @@ Each language has its own file mapping that determines what gets generated: - `docker-compose.yaml`, `.env.example` → Docker Compose sandbox (conditional on sandbox config) - `.flox/` → Flox reproducible environment (conditional) - `.devcontainer/` → Devcontainer configuration (conditional) -- `CLAUDE.md`, `GEMINI.md`, `AGENTS.md` → AI assistant documentation (conditional on AI toggles) +- `AGENTS.md` → AI assistant documentation (conditional on AI toggles) - `.github/ISSUE_TEMPLATE/*.md` → GitHub issue templates (conditional on SCM config) - `.github/dependabot.yml` → Dependabot configuration (conditional on SCM config) - CI workflow (`.github/workflows/ci.yml`) → Conditional on SCM CI toggle @@ -2116,7 +2115,7 @@ Each language has its own file mapping that determines what gets generated: - `docker-compose.yaml`, `.env.example` → Docker Compose sandbox (conditional on sandbox config) - `.flox/` → Flox reproducible environment (conditional) - `.devcontainer/` → Devcontainer configuration (conditional) -- `CLAUDE.md`, `GEMINI.md`, `AGENTS.md` → AI assistant documentation (conditional on AI toggles) +- `AGENTS.md` → AI assistant documentation (conditional on AI toggles) - `.github/ISSUE_TEMPLATE/*.md` → GitHub issue templates (conditional on SCM config) - `.github/dependabot.yml` → Dependabot configuration (conditional on SCM config) - CI workflow (`.github/workflows/ci.yml`) → Conditional on SCM CI toggle @@ -2148,7 +2147,7 @@ Each language has its own file mapping that determines what gets generated: - `docker-compose.yaml`, `.env.example` → Docker Compose sandbox (conditional on sandbox config) - `.flox/` → Flox reproducible environment (conditional) - `.devcontainer/` → Devcontainer configuration (conditional) -- `CLAUDE.md`, `GEMINI.md`, `AGENTS.md` → AI assistant documentation (conditional on AI toggles) +- `AGENTS.md` → AI assistant documentation (conditional on AI toggles) - `.github/ISSUE_TEMPLATE/*.md` → GitHub issue templates (conditional on SCM config) - `.github/dependabot.yml` → Dependabot configuration (conditional on SCM config) - CI workflow (`.github/workflows/ci.yml`) → Conditional on SCM CI toggle diff --git a/cmd/generate_test.go b/cmd/generate_test.go index 5fc6e57d..b18243c4 100644 --- a/cmd/generate_test.go +++ b/cmd/generate_test.go @@ -378,20 +378,20 @@ spec: t.Fatalf("unexpected error: %v", err) } - claudeMdPath := filepath.Join(outputPath, "CLAUDE.md") - if _, err := os.Stat(claudeMdPath); os.IsNotExist(err) { - t.Errorf("expected CLAUDE.md to be generated when spec.development.ai.orchestrators.claudecode.enabled is true") + agentsMdPath := filepath.Join(outputPath, "AGENTS.md") + if _, err := os.Stat(agentsMdPath); os.IsNotExist(err) { + t.Errorf("expected AGENTS.md to be generated when spec.development.ai.orchestrators.claudecode.enabled is true") } - claudeMdContent, err := os.ReadFile(claudeMdPath) + agentsMdContent, err := os.ReadFile(agentsMdPath) if err != nil { - t.Fatalf("failed to read CLAUDE.md: %v", err) + t.Fatalf("failed to read AGENTS.md: %v", err) } - if !containsString(string(claudeMdContent), "test-ai-agent") { - t.Errorf("expected CLAUDE.md to contain agent name") + if !containsString(string(agentsMdContent), "test-ai-agent") { + t.Errorf("expected AGENTS.md to contain agent name") } - if !containsString(string(claudeMdContent), "Test AI agent with sandbox environments") { - t.Errorf("expected CLAUDE.md to contain agent description") + if !containsString(string(agentsMdContent), "Test AI agent with sandbox environments") { + t.Errorf("expected AGENTS.md to contain agent description") } devcontainerPath := filepath.Join(outputPath, ".devcontainer/devcontainer.json") @@ -426,9 +426,6 @@ spec: t.Fatalf("failed to read .gitattributes: %v", err) } gitattributesContent := string(gitattributesBytes) - if !containsString(gitattributesContent, "CLAUDE.md linguist-generated=true") { - t.Errorf("expected .gitattributes to mark CLAUDE.md as linguist-generated, got:\n%s", gitattributesContent) - } if !containsString(gitattributesContent, "AGENTS.md linguist-generated=true") { t.Errorf("expected .gitattributes to mark AGENTS.md as linguist-generated, got:\n%s", gitattributesContent) } @@ -679,8 +676,8 @@ func containsString(content, substr string) bool { return false } -// TestGenerateAIDocsRespectSandboxEnabledFlags verifies that AGENTS.md and -// CLAUDE.md only mention sandbox environments that are actually enabled. This +// TestGenerateAIDocsRespectSandboxEnabledFlags verifies that AGENTS.md +// only mentions sandbox environments that are actually enabled. This // guards against the regression in issue #129 where `init` populates all three // sandbox pointers (flox/devcontainer/dockerCompose), causing the templates to // document every environment regardless of its `enabled` flag. @@ -779,7 +776,7 @@ spec: t.Fatalf("unexpected error: %v", err) } - for _, doc := range []string{"AGENTS.md", "CLAUDE.md"} { + for _, doc := range []string{"AGENTS.md"} { path := filepath.Join(outputPath, doc) bytes, err := os.ReadFile(path) if err != nil { diff --git a/cmd/init.go b/cmd/init.go index c3655f4b..9c141f61 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -80,7 +80,7 @@ func init() { initCmd.Flags().Bool("flox", false, "Enable Flox environment") initCmd.Flags().Bool("devcontainer", false, "Enable DevContainer environment") initCmd.Flags().Bool("docker-compose", false, "Enable Docker Compose environment") - initCmd.Flags().Bool("ai", false, "Enable AI assistant docs (CLAUDE.md/AGENTS.md) and claude-code in sandboxes") + initCmd.Flags().Bool("ai", false, "Enable AI assistant docs (AGENTS.md) and claude-code in sandboxes") initCmd.Flags().Bool("ci", false, "Enable CI workflow generation") initCmd.Flags().Bool("cd", false, "Enable CD pipeline generation") initCmd.Flags().String("deployment", "", "Deployment type (kubernetes, defaults to empty for no deployment)") @@ -1218,7 +1218,7 @@ func collectAnswersNonInteractive(projectName string, useDefaults bool) answers } tui.Println(tui.Header("AI Assistant Documentation")) - ans.Claudecode = promptBoolWithConfig("ai", useDefaults, "Enable Claude Code (CLAUDE.md + claude-code in sandboxes)", false) + ans.Claudecode = promptBoolWithConfig("ai", useDefaults, "Enable Claude Code (AGENTS.md + claude-code in sandboxes)", false) ans.ClaudecodeAppIDSecret = "CLAUDE_APP_ID" ans.ClaudecodeAppPrivateKeySecret = "CLAUDE_APP_PRIVATE_KEY" if ans.Claudecode { diff --git a/internal/generator/ai_test.go b/internal/generator/ai_test.go index c5f340c4..abd66266 100644 --- a/internal/generator/ai_test.go +++ b/internal/generator/ai_test.go @@ -66,9 +66,8 @@ func TestGenerator_AI_NoTogglesNoFiles(t *testing.T) { mustGenerate(t, manifest, out, Config{Overwrite: true, Version: "test"}) - assertFile(t, out, "CLAUDE.md", false) assertFile(t, out, "AGENTS.md", false) - assertFile(t, out, "GEMINI.md", false) + assertFile(t, out, ".gemini/settings.json", false) assertFile(t, out, ".github/workflows/claude.yml", false) assertFile(t, out, ".github/workflows/codex.yml", false) assertFile(t, out, ".github/workflows/gemini.yml", false) @@ -87,11 +86,11 @@ func TestGenerator_AI_ClaudeCodeOnly(t *testing.T) { mustGenerate(t, manifest, out, Config{Overwrite: true, Version: "test"}) - assertFile(t, out, "CLAUDE.md", true) + assertFile(t, out, "AGENTS.md", true) assertFile(t, out, ".github/workflows/claude.yml", true) - assertFile(t, out, "AGENTS.md", false) - assertFile(t, out, "GEMINI.md", false) + assertFile(t, out, "CLAUDE.md", false) + assertFile(t, out, ".gemini/settings.json", false) assertFile(t, out, ".github/workflows/codex.yml", false) assertFile(t, out, ".github/workflows/gemini.yml", false) } @@ -108,13 +107,15 @@ func TestGenerator_AI_GeminiOnly(t *testing.T) { mustGenerate(t, manifest, out, Config{Overwrite: true, Version: "test"}) - assertFile(t, out, "GEMINI.md", true) + assertFile(t, out, "AGENTS.md", true) assertFile(t, out, ".github/workflows/gemini.yml", true) - assertFile(t, out, "CLAUDE.md", false) - assertFile(t, out, "AGENTS.md", false) + assertFile(t, out, "GEMINI.md", false) assertFile(t, out, ".github/workflows/claude.yml", false) assertFile(t, out, ".github/workflows/codex.yml", false) + + settings := readGenerated(t, out, ".gemini/settings.json") + assertContains(t, settings, `"fileName": "AGENTS.md"`, "gemini settings") } func TestGenerator_AI_AgentsMDSharedAcrossCodexOpencodeInfer(t *testing.T) { @@ -179,8 +180,7 @@ func TestGenerator_AI_AgentsMDSharedAcrossCodexOpencodeInfer(t *testing.T) { mustGenerate(t, manifest, out, Config{Overwrite: true, Version: "test"}) assertFile(t, out, "AGENTS.md", true) - assertFile(t, out, "CLAUDE.md", false) - assertFile(t, out, "GEMINI.md", false) + assertFile(t, out, ".gemini/settings.json", false) assertFile(t, out, ".github/workflows/codex.yml", tt.wantCodexWorkflow) assertFile(t, out, ".github/workflows/infer.yml", tt.wantInferWorkflow) assertFile(t, out, ".github/workflows/claude.yml", false) @@ -209,9 +209,12 @@ func TestGenerator_AI_AllAgentsEnabled(t *testing.T) { mustGenerate(t, manifest, out, Config{Overwrite: true, Version: "test"}) - assertFile(t, out, "CLAUDE.md", true) - assertFile(t, out, "GEMINI.md", true) assertFile(t, out, "AGENTS.md", true) + assertFile(t, out, "GEMINI.md", false) + assertFile(t, out, ".gemini/settings.json", true) + + gitattributes := readGenerated(t, out, ".gitattributes") + assertContains(t, gitattributes, ".gemini/settings.json linguist-generated=true", "linguist tag for gemini settings") assertFile(t, out, ".github/workflows/claude.yml", true) assertFile(t, out, ".github/workflows/codex.yml", true) @@ -233,7 +236,7 @@ func TestGenerator_AI_NoWorkflowsWhenSCMNotGithub(t *testing.T) { mustGenerate(t, manifest, out, Config{Overwrite: true, Version: "test"}) - assertFile(t, out, "CLAUDE.md", true) + assertFile(t, out, "AGENTS.md", true) assertFile(t, out, ".github/workflows/claude.yml", false) } diff --git a/internal/schema/ai.go b/internal/schema/ai.go index e0738021..d84748d5 100644 --- a/internal/schema/ai.go +++ b/internal/schema/ai.go @@ -13,18 +13,13 @@ type AIAgentToggles struct { Infer bool } -// Any reports whether at least one agent toggle is enabled. +// Any reports whether at least one agent toggle is enabled. Every +// orchestrator reads the shared AGENTS.md, so this also decides whether +// AGENTS.md is generated. func (t AIAgentToggles) Any() bool { return t.ClaudeCode || t.Codex || t.Gemini || t.OpenCode || t.Infer } -// AnyAgentsMD reports whether AGENTS.md should be generated. AGENTS.md -// is shared by codex, opencode, and infer - generate it once if any of -// those three are enabled. -func (t AIAgentToggles) AnyAgentsMD() bool { - return t.Codex || t.OpenCode || t.Infer -} - // ResolveAIAgentToggles inspects spec.development.ai.orchestrators and // returns the effective set of per-agent toggles. The legacy flat shapes // (`spec.development.ai.enabled` and `spec.development.ai.`) are diff --git a/internal/schema/ai_test.go b/internal/schema/ai_test.go index 2e4c7977..bcbeeae4 100644 --- a/internal/schema/ai_test.go +++ b/internal/schema/ai_test.go @@ -72,30 +72,16 @@ func TestAIAgentToggles_Any(t *testing.T) { if (AIAgentToggles{}).Any() { t.Fatal("empty toggles should report Any()=false") } - if !(AIAgentToggles{OpenCode: true}).Any() { - t.Fatal("opencode toggle should report Any()=true") - } -} - -func TestAIAgentToggles_AnyAgentsMD(t *testing.T) { - tests := map[string]struct { - toggles AIAgentToggles - want bool - }{ - "claudecode alone is not enough": {AIAgentToggles{ClaudeCode: true}, false}, - "gemini alone is not enough": {AIAgentToggles{Gemini: true}, false}, - "codex flips it on": {AIAgentToggles{Codex: true}, true}, - "opencode flips it on": {AIAgentToggles{OpenCode: true}, true}, - "infer flips it on": {AIAgentToggles{Infer: true}, true}, - "claudecode + infer combo flips it on": {AIAgentToggles{ClaudeCode: true, Infer: true}, true}, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - if got := tc.toggles.AnyAgentsMD(); got != tc.want { - t.Fatalf("AnyAgentsMD() = %v, want %v", got, tc.want) - } - }) + for name, toggles := range map[string]AIAgentToggles{ + "claudecode": {ClaudeCode: true}, + "codex": {Codex: true}, + "gemini": {Gemini: true}, + "opencode": {OpenCode: true}, + "infer": {Infer: true}, + } { + if !toggles.Any() { + t.Fatalf("%s toggle should report Any()=true", name) + } } } diff --git a/internal/schema/builtin_config.go b/internal/schema/builtin_config.go index b0df164f..2df610ca 100644 --- a/internal/schema/builtin_config.go +++ b/internal/schema/builtin_config.go @@ -37,7 +37,7 @@ func IsReservedToolID(id string) bool { // BuiltinToolMeta is the documentation-facing view of a reserved built-in // tool - the same information the generator bakes into the per-language -// runtime descriptor, surfaced here so templates (README, CLAUDE.md) can +// runtime descriptor, surfaced here so templates (README, AGENTS.md) can // render a uniform table without each template re-stating it. type BuiltinToolMeta struct { ID string diff --git a/internal/schema/validator.go b/internal/schema/validator.go index dcd4a581..df4a7e81 100644 --- a/internal/schema/validator.go +++ b/internal/schema/validator.go @@ -202,7 +202,7 @@ func checkLegacySpecFields(yamlData any) error { if dev, ok := spec["development"].(map[string]any); ok { if ai, ok := dev["ai"].(map[string]any); ok { if _, exists := ai["enabled"]; exists { - return fmt.Errorf("manifest uses the pre-v0.8.0 single-flag AI shape `spec.development.ai.enabled`; this field was removed in ADL v0.8.0. Move it to a per-agent toggle under spec.development.ai.orchestrators, e.g.:\n\n spec:\n development:\n ai:\n orchestrators:\n claudecode:\n enabled: true # generates CLAUDE.md + .github/workflows/claude.yml\n # codex / gemini / opencode / infer are independent toggles\n\nSee https://github.com/inference-gateway/adl/releases/tag/v0.8.0 for the full per-agent matrix") + return fmt.Errorf("manifest uses the pre-v0.8.0 single-flag AI shape `spec.development.ai.enabled`; this field was removed in ADL v0.8.0. Move it to a per-agent toggle under spec.development.ai.orchestrators, e.g.:\n\n spec:\n development:\n ai:\n orchestrators:\n claudecode:\n enabled: true # generates AGENTS.md + .github/workflows/claude.yml\n # codex / gemini / opencode / infer are independent toggles\n\nSee https://github.com/inference-gateway/adl/releases/tag/v0.8.0 for the full per-agent matrix") } var flatAgents []string diff --git a/internal/templates/common/ai/claude.md.tmpl b/internal/templates/common/ai/claude.md.tmpl deleted file mode 100644 index 88ecabb3..00000000 --- a/internal/templates/common/ai/claude.md.tmpl +++ /dev/null @@ -1,210 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -{{ .ADL.Metadata.Name }} is an A2A (Agent-to-Agent) server implementing the [A2A Protocol](https://github.com/inference-gateway/adk) for agent-to-agent communication. {{ .ADL.Metadata.Description }}. The project is automatically generated from ADL (Agent Definition Language) specifications defined in `agent.yaml`. - -## Core Architecture - -### ADL-Generated Structure - -The codebase is generated using ADL CLI {{ .Metadata.CLIVersion }} and follows a strict generation pattern: -- **Generated Files**: Marked with `DO NOT EDIT` headers - manual changes will be overwritten -- **Configuration Source**: `agent.yaml` - defines agent capabilities, skills, and metadata -- **Server Implementation**: Built on the ADK (Agent Development Kit) framework from `github.com/inference-gateway/adk` - -### Key Components - -- **Main Entry Point**: `main.go` - A cobra-based CLI. The root command exposes - `--version` and `--help`; the `start` subcommand boots the A2A server with: - - OpenAI-compatible LLM client configuration - - Agent builder with system prompt from `agent.yaml` - - A2A server with streaming and background task handlers - - Graceful shutdown handling - -- **Agent Configuration**: `.well-known/agent-card.json` - Serves agent metadata at runtime -- **Environment Configuration**: Extensive env vars with `A2A_` prefix (see CONFIGURATIONS.md for full list) - -## Development Commands - -```bash -# Generate/regenerate code from ADL specification -task generate - -# Run the agent in development mode (debug enabled, port 8080) -task run - -# Run tests (note: no tests currently exist) -task test -task test:cover # with coverage - -# Code quality -task lint # Run golangci-lint -task fmt # Format code with go fmt - -# Build -task build # Creates bin/{{ .ADL.Metadata.Name }} -task docker:build # Build Docker image - -# Clean build artifacts -task clean -``` - -## Testing Individual Components - -```bash -# Run specific test file (when tests are added) -go test -v ./path/to/package -run TestFunctionName - -# Debug with A2A Debugger -docker run --rm -it --network host ghcr.io/inference-gateway/a2a-debugger:latest \ - --server-url http://localhost:8080 tasks submit "Your query" -``` - -## LLM Provider Configuration - -The agent uses OpenAI-compatible LLM client. Configure with: -- `A2A_AGENT_CLIENT_PROVIDER`: {{- if and .ADL.Spec.Agent .ADL.Spec.Agent.Provider }} `{{ .ADL.Spec.Agent.Provider }}`{{ else }} `openai`, `anthropic`, `azure`, `ollama`, `deepseek`{{ end }} -- `A2A_AGENT_CLIENT_MODEL`: {{- if and .ADL.Spec.Agent .ADL.Spec.Agent.Model }} `{{ .ADL.Spec.Agent.Model }}`{{ else }} Model identifier{{ end }} -- `A2A_AGENT_CLIENT_API_KEY`: Provider API key -- `A2A_AGENT_CLIENT_BASE_URL`: Custom endpoint (optional) - -## Adding New Functionality - -### Tools (function-call) - -{{- if .ADL.Spec.Tools }} -The following tools are currently defined: -{{- range .ADL.Spec.Tools }} -{{- if isBuiltinToolID .ID }}{{- $m := builtinToolMeta .ID }} -- **{{ $m.Name }}** (built-in): {{ $m.Description }} -{{- else }} -- **{{ .Name }}**: {{ .Description }} -{{- end }} -{{- end }} - -To modify tools: -1. Update `agent.yaml` `spec.tools` with tool definitions -2. Run `task generate` to regenerate the codebase -3. Implement tool logic in the generated `tools/` files (look for TODO placeholders) -4. Write tests for each tool -{{- else }} -Currently no tools are defined. To add tools: -1. Add entries under `spec.tools` in `agent.yaml` -2. Run `task generate` to regenerate the codebase -3. Implement tool logic in the generated `tools/` files (look for TODO placeholders) -4. Write tests for each tool -{{- end }} - -### Skills (markdown system-prompt playbooks) - -{{- if .ADL.Spec.Skills }} -The following skills are currently shipped with the agent: -{{- range .Skills }} -- **{{ .Name }}** ({{ if .Bare }}bare scaffold{{ else }}registry{{ end }}): {{ .Description }} -{{- end }} - -Each skill lives in its own directory at `.agents/skills//SKILL.md` -and is loaded into the system prompt at startup. A generated `.claude/skills` --> `../.agents/skills` symlink makes the same tree readable as `.claude/skills//SKILL.md`, -so Claude Code picks these skills up directly. Bare skills can ship arbitrary -bundled assets (scripts, templates, resources) alongside `SKILL.md` - -the whole `.agents/skills//` directory is protected by `.adl-ignore` against -regeneration overwrites. To modify skills: -1. Update `agent.yaml` `spec.skills` with skill definitions -2. Run `task generate` (registry skills are re-fetched; bare skill - directories are preserved when listed in `.adl-ignore`) -3. For bare skills, edit `.agents/skills//SKILL.md` directly - frontmatter - (`name`/`description`/`tags`) shows up on the agent card. Drop helper - scripts or templates next to it (e.g. `.agents/skills//scripts/foo.py`). -{{- else }} -No skills are currently defined. To add skills: -1. Add entries under `spec.skills` in `agent.yaml` - - Set `bare: true` and provide `name`/`description`/`tags` to scaffold - a blank skill you author by hand - - Or omit `bare` to pull from the registry - (`registry.inference-gateway.com/skills/.md`) -2. Run `task generate` -{{- end }} - -### Modifying Agent Behavior - -- **System Prompt**: Edit in `agent.yaml`, then regenerate -- **Capabilities**: Modify in `agent.yaml` (streaming, pushNotifications, extendedAgentCard) -- **Server Configuration**: Update environment variables or `agent.yaml` server section - -## Testing Strategy - -When implementing tests: -- Create `*_test.go` files alongside implementation files -- Use table-driven tests for comprehensive coverage -- Mock external dependencies (LLM client, Redis if used) -- Test A2A protocol compliance with integration tests - -## Environment Management - -### Development Environment -{{- $sandbox := false }} -{{- if and .ADL.Spec.Development .ADL.Spec.Development.Sandbox }} -{{- if and .ADL.Spec.Development.Sandbox.Flox .ADL.Spec.Development.Sandbox.Flox.Enabled }} -{{- $sandbox = true }} -- **Flox Environment**: ✅ Configured via `.flox/env/manifest.toml` providing -{{- if .ADL.Spec.Language.Go }} Go {{ .ADL.Spec.Language.Go.Version | default "latest" }}, -{{- else if .ADL.Spec.Language.Rust }} Rust {{ .ADL.Spec.Language.Rust.Version | default "latest" }}, -{{- else if .ADL.Spec.Language.TypeScript }} Node.js {{ .ADL.Spec.Language.TypeScript.NodeVersion | default "latest" }}, -{{- else }} the development runtime, -{{- end }} linter, `go-task`, Docker, and the Claude Code CLI. Activate with `flox activate`. -{{- end }} -{{- if and .ADL.Spec.Development.Sandbox.DevContainer .ADL.Spec.Development.Sandbox.DevContainer.Enabled }} -{{- $sandbox = true }} -- **DevContainer**: ✅ VS Code DevContainer config at `.devcontainer/devcontainer.json`. Open the project in a DevContainer-compatible editor (VS Code, GitHub Codespaces) for automatic setup. -{{- end }} -{{- if and .ADL.Spec.Development.Sandbox.DockerCompose .ADL.Spec.Development.Sandbox.DockerCompose.Enabled }} -{{- $sandbox = true }} -- **Docker Compose**: ✅ Local service stack defined in `docker-compose.yaml`. Bring up the Inference Gateway and the agent (built from the local `Dockerfile`) with `docker compose up --build`. Opt-in profiles add the `infer` CLI (`docker compose --profile cli run --rm cli`) and the `a2a-debugger` (`docker compose --profile debugger run --rm debugger --server-url http://{{ .ADL.Metadata.Name }}:{{ .ADL.Spec.Server.Port | default 8080 }} tasks list`). -{{- end }} -{{- end }} -{{- if not $sandbox }} -No sandboxed environments are configured. Install the toolchain manually: -{{- if .ADL.Spec.Language.Go }} -- Go {{ .ADL.Spec.Language.Go.Version | default "1.26.8+" }} -{{- else if .ADL.Spec.Language.Rust }} -- Rust {{ .ADL.Spec.Language.Rust.Version | default "1.94+" }} -{{- else if .ADL.Spec.Language.TypeScript }} -- Node.js {{ .ADL.Spec.Language.TypeScript.NodeVersion | default "24+" }} -{{- else }} -- Development runtime as needed -{{- end }} -- Task runner (`go install github.com/go-task/task/v3/cmd/task@latest`) -{{- end }} - -## Important Constraints - -- **Generated Files**: Never manually edit files with "DO NOT EDIT" headers -- **Configuration Changes**: Always modify `agent.yaml` and regenerate -- **ADL Version**: Ensure ADL CLI {{ .Metadata.CLIVersion }} or compatible version for regeneration -- **Port Configuration**: Default {{ .ADL.Spec.Server.Port | default "8080" }}, configurable via `A2A_SERVER_PORT` - -## Code Readability - -- Write self-explanatory code: clear names and small, single-purpose functions carry the intent. - If a block needs a comment to be understood, extract it into a well-named function or variable. -- No inline comments inside function bodies. -- Doc comments on functions and types are at most 5 lines: what it does and why, not how. -- No comments above modules, packages, or files. -- Tool directives are not comments and stay where the tool needs them (lint suppressions, build - tags, compiler pragmas, code generation markers). -- No semicolons in documentation prose (Markdown files, doc comments): split the sentence or use - a dash instead. - -## Debugging Tips - -- Enable debug mode: `A2A_DEBUG=true`{{ if .ADL.Spec.Server.Debug }} (currently enabled in agent.yaml){{ end }} -- Check health: `GET /health` -- View agent metadata: `GET /.well-known/agent-card.json` -{{- if .ADL.Spec.Capabilities.Streaming }} -- Monitor streaming updates: Set `A2A_STREAMING_STATUS_UPDATE_INTERVAL` -{{- end }} -- Use A2A Debugger container for interactive testing diff --git a/internal/templates/common/ai/gemini-settings.json.tmpl b/internal/templates/common/ai/gemini-settings.json.tmpl new file mode 100644 index 00000000..05d52d75 --- /dev/null +++ b/internal/templates/common/ai/gemini-settings.json.tmpl @@ -0,0 +1,5 @@ +{ + "context": { + "fileName": "AGENTS.md" + } +} diff --git a/internal/templates/common/ai/gemini.md.tmpl b/internal/templates/common/ai/gemini.md.tmpl deleted file mode 100644 index 699cab8e..00000000 --- a/internal/templates/common/ai/gemini.md.tmpl +++ /dev/null @@ -1,148 +0,0 @@ -# GEMINI.md - -This file provides guidance to Google's Gemini coding agent when working with code in this repository. - -## Project Overview - -{{ .ADL.Metadata.Name }} is an A2A (Agent-to-Agent) server implementing the [A2A Protocol](https://github.com/inference-gateway/adk) for agent-to-agent communication. {{ .ADL.Metadata.Description }}. The project is automatically generated from ADL (Agent Definition Language) specifications defined in `agent.yaml`. - -## Core Architecture - -### ADL-Generated Structure - -The codebase is generated using ADL CLI {{ .Metadata.CLIVersion }} and follows a strict generation pattern: -- **Generated Files**: Marked with `DO NOT EDIT` headers - manual changes will be overwritten -- **Configuration Source**: `agent.yaml` - defines agent capabilities, skills, and metadata -- **Server Implementation**: Built on the ADK (Agent Development Kit) framework from `github.com/inference-gateway/adk` - -### Key Components - -- **Main Entry Point**: -{{- if eq .Language "go" }} `main.go` - A cobra-based CLI. The root command exposes - `--version` and `--help`; the `start` subcommand boots the A2A server with: -{{- else if eq .Language "rust" }} `src/main.rs` - boots the A2A server with: -{{- else if eq .Language "typescript" }} `src/index.ts` - boots the A2A server with: -{{- end }} - - OpenAI-compatible LLM client configuration - - Agent builder with system prompt from `agent.yaml` - - A2A server with streaming and background task handlers - - Graceful shutdown handling - -- **Agent Configuration**: `.well-known/agent-card.json` - Serves agent metadata at runtime -- **Environment Configuration**: Extensive env vars with `A2A_` prefix (see CONFIGURATIONS.md for full list) - -## Development Commands - -```bash -# Generate/regenerate code from ADL specification -task generate - -# Run the agent in development mode (debug enabled, port {{ .ADL.Spec.Server.Port | default "8080" }}) -task run - -# Run tests -task test -task test:cover # with coverage - -# Code quality -task lint # Run linters -task fmt # Format code - -# Build -task build # Build the agent -task docker:build # Build Docker image - -# Clean build artifacts -task clean -``` - -## LLM Provider Configuration - -The agent uses OpenAI-compatible LLM client. Configure with: -- `A2A_AGENT_CLIENT_PROVIDER`: {{- if and .ADL.Spec.Agent .ADL.Spec.Agent.Provider }} `{{ .ADL.Spec.Agent.Provider }}`{{ else }} `openai`, `anthropic`, `azure`, `ollama`, `deepseek`, `google`{{ end }} -- `A2A_AGENT_CLIENT_MODEL`: {{- if and .ADL.Spec.Agent .ADL.Spec.Agent.Model }} `{{ .ADL.Spec.Agent.Model }}`{{ else }} Model identifier{{ end }} -- `A2A_AGENT_CLIENT_API_KEY`: Provider API key -- `A2A_AGENT_CLIENT_BASE_URL`: Custom endpoint (optional) - -## Adding New Functionality - -### Tools (function-call) - -{{- if .ADL.Spec.Tools }} -The following tools are currently defined: -{{- range .ADL.Spec.Tools }} -{{- if isBuiltinToolID .ID }}{{- $m := builtinToolMeta .ID }} -- **{{ $m.Name }}** (built-in): {{ $m.Description }} -{{- else }} -- **{{ .Name }}**: {{ .Description }} -{{- end }} -{{- end }} - -To modify tools: -1. Update `agent.yaml` `spec.tools` with tool definitions -2. Run `task generate` to regenerate the codebase -3. Implement tool logic in the generated `tools/` files (look for TODO placeholders) -4. Write tests for each tool -{{- else }} -Currently no tools are defined. To add tools: -1. Add entries under `spec.tools` in `agent.yaml` -2. Run `task generate` to regenerate the codebase -3. Implement tool logic in the generated `tools/` files (look for TODO placeholders) -4. Write tests for each tool -{{- end }} - -### Skills (markdown system-prompt playbooks) - -{{- if .ADL.Spec.Skills }} -The following skills are currently shipped with the agent: -{{- range .Skills }} -- **{{ .Name }}** ({{ if .Bare }}bare scaffold{{ else }}registry{{ end }}): {{ .Description }} -{{- end }} - -Each skill lives in its own directory at `.agents/skills//SKILL.md` -(a `.claude/skills` -> `../.agents/skills` symlink makes the same tree readable as -`.claude/skills//SKILL.md` for Claude Code) and is loaded into the -system prompt at startup. Bare skills can ship arbitrary bundled assets -(scripts, templates, resources) alongside `SKILL.md` - the whole -`.agents/skills//` directory is protected by `.adl-ignore` against -regeneration overwrites. -{{- else }} -No skills are currently defined. To add skills: -1. Add entries under `spec.skills` in `agent.yaml` - - Set `bare: true` and provide `name`/`description`/`tags` to scaffold - a blank skill you author by hand - - Or omit `bare` to pull from the registry - (`registry.inference-gateway.com/skills/.md`) -2. Run `task generate` -{{- end }} - -### Modifying Agent Behavior - -- **System Prompt**: Edit in `agent.yaml`, then regenerate -- **Capabilities**: Modify in `agent.yaml` (streaming, pushNotifications, extendedAgentCard) -- **Server Configuration**: Update environment variables or `agent.yaml` server section - -## Testing Strategy - -When implementing tests: -- Create test files alongside implementation files -- Use table-driven tests for comprehensive coverage -- Mock external dependencies (LLM client, Redis if used) -- Test A2A protocol compliance with integration tests - -## Important Constraints - -- **Generated Files**: Never manually edit files with "DO NOT EDIT" headers -- **Configuration Changes**: Always modify `agent.yaml` and regenerate -- **ADL Version**: Ensure ADL CLI {{ .Metadata.CLIVersion }} or compatible version for regeneration -- **Port Configuration**: Default {{ .ADL.Spec.Server.Port | default "8080" }}, configurable via `A2A_SERVER_PORT` - -## Debugging Tips - -- Enable debug mode: `A2A_DEBUG=true`{{ if .ADL.Spec.Server.Debug }} (currently enabled in agent.yaml){{ end }} -- Check health: `GET /health` -- View agent metadata: `GET /.well-known/agent-card.json` -{{- if .ADL.Spec.Capabilities.Streaming }} -- Monitor streaming updates: Set `A2A_STREAMING_STATUS_UPDATE_INTERVAL` -{{- end }} -- Use A2A Debugger container for interactive testing diff --git a/internal/templates/common/config/dockerignore.tmpl b/internal/templates/common/config/dockerignore.tmpl index 35818a79..51f62b0b 100644 --- a/internal/templates/common/config/dockerignore.tmpl +++ b/internal/templates/common/config/dockerignore.tmpl @@ -33,6 +33,4 @@ Dockerfile # Docs README.md CONFIGURATIONS.md -CLAUDE.md AGENTS.md -GEMINI.md diff --git a/internal/templates/common/config/gitattributes.tmpl b/internal/templates/common/config/gitattributes.tmpl index 2204dce6..3417cb60 100644 --- a/internal/templates/common/config/gitattributes.tmpl +++ b/internal/templates/common/config/gitattributes.tmpl @@ -45,23 +45,9 @@ docker-compose.yaml linguist-generated=true .github/dependabot.yml linguist-generated=true {{- end }} -{{- if .AIToggles.ClaudeCode }} - -# AI assistant documentation (generated) -CLAUDE.md linguist-generated=true -{{- end }} -{{- if .AIToggles.Gemini }} -{{- if not .AIToggles.ClaudeCode }} +{{- if .AIToggles.Any }} # AI assistant documentation (generated) -{{- end }} -GEMINI.md linguist-generated=true -{{- end }} -{{- if .AIToggles.AnyAgentsMD }} -{{- if not (or .AIToggles.ClaudeCode .AIToggles.Gemini) }} - -# AI assistant documentation (generated) -{{- end }} AGENTS.md linguist-generated=true {{- end }} {{- if .AIToggles.ClaudeCode }} @@ -71,6 +57,7 @@ AGENTS.md linguist-generated=true .github/workflows/codex.yml linguist-generated=true {{- end }} {{- if .AIToggles.Gemini }} +.gemini/settings.json linguist-generated=true .github/workflows/gemini.yml linguist-generated=true {{- end }} {{- if .AIToggles.Infer }} diff --git a/internal/templates/common/github/workflows/ai-gemini.yaml.tmpl b/internal/templates/common/github/workflows/ai-gemini.yaml.tmpl index b541b63b..914157fd 100644 --- a/internal/templates/common/github/workflows/ai-gemini.yaml.tmpl +++ b/internal/templates/common/github/workflows/ai-gemini.yaml.tmpl @@ -2,8 +2,8 @@ name: Gemini # Runs Google's Gemini coding agent on issues and pull requests that -# mention @gemini. The agent picks up GEMINI.md from the repo root for -# project-specific instructions. +# mention @gemini. The agent picks up AGENTS.md from the repo root for +# project-specific instructions (see .gemini/settings.json). on: issue_comment: diff --git a/internal/templates/registry.go b/internal/templates/registry.go index a2c49109..9744a74a 100644 --- a/internal/templates/registry.go +++ b/internal/templates/registry.go @@ -357,26 +357,20 @@ func (r *Registry) getTypeScriptFiles(adl *schema.ADL) map[string]string { return files } -// addAIFiles maps per-agent AI assistant documentation onto the -// generated project layout. Each agent's toggle in -// spec.development.ai.orchestrators is consulted independently: -// -// - CLAUDE.md ← claudecode.enabled -// - GEMINI.md ← gemini.enabled -// - AGENTS.md ← any of codex / opencode / infer (single shared file) -// -// AGENTS.md is shared by the three "AGENTS.md"-flavoured agents and -// is emitted once even when more than one of them is enabled. +// addAIFiles maps AI assistant documentation onto the generated project +// layout. Every orchestrator reads the shared AGENTS.md, so it is emitted +// once when any toggle in spec.development.ai.orchestrators is enabled. +// Gemini CLI defaults to GEMINI.md, so it also gets a settings file that +// points its context file name at AGENTS.md. func (r *Registry) addAIFiles(files map[string]string) { - t := r.aiToggles - if t.ClaudeCode { - files["CLAUDE.md"] = "ai/claude.md" - } - if t.Gemini { - files["GEMINI.md"] = "ai/gemini.md" + if !r.aiToggles.Any() { + return } - if t.AnyAgentsMD() { - files["AGENTS.md"] = "ai/agents.md" + + files["AGENTS.md"] = "ai/agents.md" + + if r.aiToggles.Gemini { + files[".gemini/settings.json"] = "ai/gemini-settings.json" } } diff --git a/internal/templates/registry_dockerignore_test.go b/internal/templates/registry_dockerignore_test.go index 48c3c06f..1e29fe9d 100644 --- a/internal/templates/registry_dockerignore_test.go +++ b/internal/templates/registry_dockerignore_test.go @@ -75,6 +75,11 @@ func TestDockerignoreTemplate_ContainsExpectedExclusions(t *testing.T) { t.Errorf("dockerignore missing %q\n---\n%s", line, out) } } + for _, retired := range []string{"CLAUDE.md", "GEMINI.md"} { + if strings.Contains(out, retired) { + t.Errorf("dockerignore lists %q, which is no longer generated\n---\n%s", retired, out) + } + } } // TestRegistry_EnvExample_GatedOnDockerCompose confirms the .env.example