Skip to content

feat(config): rename agent.yaml to moat.yaml#204

Merged
dpup merged 1 commit into
mainfrom
feat/moat-yaml
Mar 4, 2026
Merged

feat(config): rename agent.yaml to moat.yaml#204
dpup merged 1 commit into
mainfrom
feat/moat-yaml

Conversation

@dpup
Copy link
Copy Markdown
Collaborator

@dpup dpup commented Mar 4, 2026

Summary

  • Renames the config file from agent.yaml to moat.yaml to align with the CLI name
  • agent.yaml is still supported as a silent fallback for backwards compatibility
  • Precedence: moat.yaml > agent.yaml (if both exist, moat.yaml wins)

Changes

Core logic (internal/config/config.go):

  • Load() tries moat.yaml first, falls back to agent.yaml
  • Exports ConfigFilename ("moat.yaml") and LegacyConfigFilename ("agent.yaml") constants

Settings (internal/providers/claude/settings.go):

  • Renames SourceAgentYAMLSourceMoatYAML

CLI (8 files in cmd/moat/cli/):

  • Updated all help text, error messages, and examples
  • Doctor command dynamically checks for both filenames
  • Fixed pre-existing bug: agent depsmoat deps in help text

Examples: All 19 agent.yaml files renamed to moat.yaml

Documentation (50+ files):

  • Reference page renamed 02-agent-yaml.md02-moat-yaml.md
  • All guides, concepts, reference pages, and planning docs updated
  • Backwards-compatibility note added to reference page

Tests:

  • All test helpers write moat.yaml
  • Added TestLoadLegacyAgentYaml (verifies fallback works)
  • Added TestLoadMoatYamlPreferred (verifies precedence when both exist)

Test plan

  • go build ./... compiles
  • make lint passes with 0 issues
  • go test -race ./internal/config/ ./internal/providers/claude/ ./internal/run/ all pass
  • New tests cover: legacy-only, both-present (moat.yaml wins), neither-present (nil, nil)
  • Verify moat run works with moat.yaml
  • Verify moat run works with only agent.yaml (fallback)

🤖 Generated with Claude Code

@claude
Copy link
Copy Markdown

claude Bot commented Mar 4, 2026

📦 Previous Review (superseded by newer review)

Claude finished @dpup's task in 2m 15s —— View job


Code Review

The implementation is clean and straightforward. The core logic is correct and the test coverage is good. A few minor issues to flag:


1. Doctor label shows "moat.yaml overrides" when displaying agent.yaml

cmd/moat/cli/doctor.go:438-441

configPath := filepath.Join(cwd, config.ConfigFilename)
if _, err := os.Stat(configPath); os.IsNotExist(err) {
    configPath = filepath.Join(cwd, config.LegacyConfigFilename)
}
if data, err := os.ReadFile(configPath); err == nil {
    ...
    fmt.Fprintf(tw, "  5. moat.yaml overrides:\t%s %s\n", configPath, ui.OKTag())

When agent.yaml is the fallback, the row label still reads "moat.yaml overrides:" while the path value shows …/agent.yaml. A user with only agent.yaml who runs moat doctor will see a confusing mismatch. Easy fix — use filepath.Base(configPath) in the label:

fmt.Fprintf(tw, "  5. %s overrides:\t%s %s\n", filepath.Base(configPath), configPath, ui.OKTag())

Fix this →


2. No migration nudge for agent.yaml users

The fallback is silent by design, which is fine for compatibility. Consider whether a one-time ui.Warn (or even just a note in the doctor output) would help users discover that renaming to moat.yaml is recommended. This is a UX suggestion, not a bug.


3. Minor: os.Stat + os.ReadFile in doctor is a TOCTOU, and other errors on moat.yaml are silently swallowed

cmd/moat/cli/doctor.go:430-432

if _, err := os.Stat(configPath); os.IsNotExist(err) {
    configPath = filepath.Join(cwd, config.LegacyConfigFilename)
}

If moat.yaml exists but has a permission error, os.IsNotExist(err) is false, so configPath stays as moat.yaml. The subsequent os.ReadFile will also fail (permission denied), and the else branch falls through silently to just print the path without the content. This is arguably fine for a diagnostic tool, but it differs from config.Load()'s behaviour which surfaces that error. Not a blocker.


Everything else looks good

  • internal/config/config.go:361-378 — The Load() fallback logic is correct. !os.IsNotExist(err) on moat.yaml propagates real errors immediately; the agent.yaml branch handles both not-found (nil, nil) and other errors properly.
  • SourceMoatYAML rename — Clean rename with no missed callsites.
  • Test coverageTestLoadLegacyAgentYaml and TestLoadMoatYamlPreferred cover the two new code paths. The "neither present → nil, nil" case is covered by the existing TestLoadConfigNotFound.
  • LegacyConfigFilename / ConfigFilename constants — Good: exports the names so the doctor command and tests reference them symbolically rather than hardcoding strings.
  • Documentation — The backwards-compat note in 02-moat-yaml.md is accurate and clear.
  • Examples — All 19 renamed; READMEs updated.

Overall this is a solid, low-risk rename. The one concrete fix worth making before merge is the label inconsistency in doctor.go (#1 above).

Comment thread cmd/moat/cli/doctor.go
…e fallback

Rename the config file from agent.yaml to moat.yaml to align with the
CLI name. The config loader now checks moat.yaml first and silently
falls back to agent.yaml if not found, so existing projects continue
to work without changes.

Changes:
- config.Load() tries moat.yaml first, falls back to agent.yaml
- Export ConfigFilename and LegacyConfigFilename constants
- Rename SourceAgentYAML to SourceMoatYAML
- Update all CLI help text, error messages, and comments
- Rename all 19 example agent.yaml files to moat.yaml
- Rename docs reference page to 02-moat-yaml.md
- Update all documentation (guides, concepts, reference, plans)
- Add tests for legacy fallback and moat.yaml precedence
- Fix pre-existing "agent deps" command name in help text
@dpup dpup force-pushed the feat/moat-yaml branch from 9c3dd26 to 35425e3 Compare March 4, 2026 18:57
@claude
Copy link
Copy Markdown

claude Bot commented Mar 4, 2026

Claude finished @dpup's task in 1m 27s —— View job


Code Review

  • Read changed files
  • Review core logic and tests
  • Post review summary

The implementation is clean and well-structured. The backwards-compatibility fallback in config.Load() is correct, tests cover the key scenarios, and the SourceMoatYAML rename is complete with no missed callsites.

One issue from the previous review remains unfixed:

doctor.go label hardcoded as "moat.yaml overrides" when agent.yaml is shown

cmd/moat/cli/doctor.go:438–443

if hasClaudeConfig {
    fmt.Fprintf(tw, "  5. moat.yaml overrides:\t%s %s (has claude config)\n", configPath, ui.OKTag())
} else {
    fmt.Fprintf(tw, "  5. moat.yaml overrides:\t%s %s\n", configPath, ui.OKTag())
}
} else {
    fmt.Fprintf(tw, "  5. moat.yaml overrides:\t%s\n", filepath.Join(cwd, config.ConfigFilename))

The code correctly falls back to agent.yaml on lines 431–432 and updates configPath, but the label is hardcoded in all three Fprintf calls. A user with only agent.yaml sees:

  5. moat.yaml overrides:  /project/agent.yaml ✓

The label and path disagree. Easy fix — use filepath.Base(configPath) in the label:

label := filepath.Base(configPath) + " overrides"
if hasClaudeConfig {
    fmt.Fprintf(tw, "  5. %s:\t%s %s (has claude config)\n", label, configPath, ui.OKTag())
} else {
    fmt.Fprintf(tw, "  5. %s:\t%s %s\n", label, configPath, ui.OKTag())
}
} else {
    // Neither file exists — show moat.yaml as the expected path
    fmt.Fprintf(tw, "  5. %s:\t%s\n", config.ConfigFilename+" overrides", filepath.Join(cwd, config.ConfigFilename))

Fix this →


Everything else looks good — the fallback logic, test coverage, constant exports, and docs update are all solid. Just needs the label fix before merge.

Comment thread cmd/moat/cli/doctor.go
Comment on lines 437 to 441
if hasClaudeConfig {
fmt.Fprintf(tw, " 5. agent.yaml overrides:\t%s %s (has claude config)\n", agentYamlPath, ui.OKTag())
fmt.Fprintf(tw, " 5. moat.yaml overrides:\t%s %s (has claude config)\n", configPath, ui.OKTag())
} else {
fmt.Fprintf(tw, " 5. agent.yaml overrides:\t%s %s\n", agentYamlPath, ui.OKTag())
fmt.Fprintf(tw, " 5. moat.yaml overrides:\t%s %s\n", configPath, ui.OKTag())
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The label is still hardcoded as "moat.yaml overrides" even when configPath was updated to agent.yaml above. All three Fprintf calls need to use the dynamic label:

Suggested change
if hasClaudeConfig {
fmt.Fprintf(tw, " 5. agent.yaml overrides:\t%s %s (has claude config)\n", agentYamlPath, ui.OKTag())
fmt.Fprintf(tw, " 5. moat.yaml overrides:\t%s %s (has claude config)\n", configPath, ui.OKTag())
} else {
fmt.Fprintf(tw, " 5. agent.yaml overrides:\t%s %s\n", agentYamlPath, ui.OKTag())
fmt.Fprintf(tw, " 5. moat.yaml overrides:\t%s %s\n", configPath, ui.OKTag())
}
label := filepath.Base(configPath) + " overrides"
if hasClaudeConfig {
fmt.Fprintf(tw, " 5. %s:\t%s %s (has claude config)\n", label, configPath, ui.OKTag())
} else {
fmt.Fprintf(tw, " 5. %s:\t%s %s\n", label, configPath, ui.OKTag())
}

@dpup dpup merged commit a9977db into main Mar 4, 2026
6 checks passed
@dpup dpup deleted the feat/moat-yaml branch March 4, 2026 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant