Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 18 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down Expand Up @@ -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:
Expand All @@ -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.<agent>` (pre-orchestrators), are no
Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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
```

Expand All @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
27 changes: 12 additions & 15 deletions cmd/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
Expand Down Expand Up @@ -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 {
Expand Down
29 changes: 16 additions & 13 deletions internal/generator/ai_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}

Expand Down
11 changes: 3 additions & 8 deletions internal/schema/ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.<agent>`) are
Expand Down
34 changes: 10 additions & 24 deletions internal/schema/ai_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion internal/schema/builtin_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion internal/schema/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading