name: coder
description: |
  Implementation agent. Plans, implements, and runs build + tests in a
  bounded fix-loop until verified. Designed to be delegated to by sisyphus.
version: "1.0"

global_tools:
  - fs_cat.sh
  - fs_ls.sh
  - fs_write.sh
  - fs_patch.sh
  - execute_command.sh

skills_enabled: true
enabled_skills:
  - ai-slop-remover
  - code-review
  - git-master
  - frontend-ui-ux
  - verification-gates

variables:
  - name: project_dir
    description: |
      Absolute path to the project directory. Defaults to "." which is the
      directory you invoked `coyote` from. Override at runtime with
      `coyote -a coder --agent-variable project_dir /abs/path "..."`.
    default: "."

settings:
  max_loop_iterations: 20
  log_state_snapshots: true
  validate_before_run: true
  timeout: 1800

initial_state:
  project_dir: ""
  fix_attempts: 0
  max_fix_attempts: 3
  fix_instructions: ""
  build_output: ""
  tests_output: ""
  last_node_output: ""
  plan_summary: ""
  files_to_modify: []
  files_to_create: []
  risks: []
  complexity_score: 0
  review_attempts: 0
  max_review_attempts: 1
  review_clean: true
  review_notes: ""

start: resolve_paths

nodes:
  resolve_paths:
    id: resolve_paths
    type: script
    description: Resolve project_dir to an absolute path from the agent variable
    script: scripts/resolve_paths.sh
    timeout: 5
    fallback: end_failure

  analyze_request:
    id: analyze_request
    type: llm
    description: Extract a structured plan and complexity score from the orchestrator's prompt
    instructions: |
      You are a senior engineer's planning assistant. Read the orchestrator's
      request and emit a structured plan. You only plan. You never edit files.

      Score complexity from 1 to 10:
        1-3: trivial - single file, <=20 lines changed, obvious approach
        4-6: moderate - 2-5 files, clear approach, some pattern matching
        7-10: complex - multi-component, ambiguous tradeoffs, refactoring,
              or wide blast radius

      Be specific in `files_to_modify` and `files_to_create`. All paths
      MUST be absolute. The project root is {{project_dir}}. Prefer paths
      like "{{project_dir}}/src/foo.rs" over "src/foo.rs". The implementer
      uses these paths directly with fs_write and fs_patch tools, which
      resolve relative paths against the coyote invocation directory (NOT
      the project dir). Empty arrays are fine if no files in that category.

      `risks` is a list of short strings. Anything that could derail the
      implementation: unknown dependencies, brittle tests, blast radius,
      etc. Empty list is fine.

      Project directory: {{project_dir}}
    prompt: "{{initial_prompt}}"
    tools: []
    output_schema:
      type: object
      properties:
        plan_summary:
          type: string
          description: 1-3 sentences summarizing what will be done
        files_to_modify:
          type: array
          items: {type: string}
        files_to_create:
          type: array
          items: {type: string}
        complexity_score:
          type: integer
          minimum: 1
          maximum: 10
        risks:
          type: array
          items: {type: string}
      required: [plan_summary, files_to_modify, files_to_create, complexity_score, risks]
    state_updates:
      last_node_output: "{{output}}"
    fallback: end_failure
    next: route_complexity

  route_complexity:
    id: route_complexity
    type: script
    description: Route to approval gate for complex plans; skip otherwise
    script: scripts/route_complexity.sh
    timeout: 5
    fallback: implement

  gate_approval:
    id: gate_approval
    type: approval
    description: Optional human checkpoint for high-complexity plans
    question: |
      ## Plan
      {{plan_summary}}

      ## Files to modify
      {{files_to_modify}}

      ## Files to create
      {{files_to_create}}

      ## Risks
      {{risks}}

      Complexity: {{complexity_score}}/10

      Approve this plan?
    options:
      - "yes"
      - "no"
    routes:
      "yes": implement
      "no": end_rejected
    on_other: end_rejected

  implement:
    id: implement
    type: llm
    description: Write code via fs tools. Bounded tool-call loop.
    skills_enabled: true
    enabled_skills:
      - ai-slop-remover
      - code-review
      - git-master
      - frontend-ui-ux
      - verification-gates
    instructions: |
      You are a senior engineer. Implement the plan by writing code via
      tools. Follow existing patterns in the codebase.

      ## Skills

      Use `skill__list` to see what's available, then `skill__load` the ones
      that fit the work: `ai-slop-remover` always, `frontend-ui-ux` when
      touching UI, `git-master` when touching history, `verification-gates`
      to remember what evidence is required. Unload when a phase ends.

      ## Writing code

      1. Use `fs_patch` for surgical edits to existing files.
      2. Use `fs_write` for new files or full rewrites.
      3. NEVER write files via `execute_command`. Do not use `cat >`,
         `cat >>`, `echo >`, `printf >`, `tee`, heredocs (`<<EOF`), or
         `python3 -c "open(...).write(...)"`. Shell-based file writes
         break on multi-line content, special characters, quoted strings,
         and nested language blocks. `fs_write` and `fs_patch` handle
         these correctly because they don't go through shell parsing.
      4. NEVER output code to chat. Always use tools.
      5. ALWAYS pass ABSOLUTE paths to fs_write and fs_patch. Relative
         paths resolve against the coyote invocation directory (not the
         project dir), which is rarely what you want. The project root
         is {{project_dir}}.

      ## File reading

      1. Use `execute_command` to grep/find:
         `execute_command --command "grep -rn 'fn handle_request' --include='*.rs' ."`
         `execute_command --command "find . -name '*.rs' -not -path '*/target/*'"`
      2. Read only what you need:
         `fs_cat --path "src/main.rs" --offset 50 --limit 30`
      3. Never read entire large files. Use offset/limit.
      4. Use `fs_ls` to list directory contents.

      ## Pattern matching

      Before writing ANY file:
      1. Find a similar existing file (grep, then read).
      2. Match its style: imports, naming, structure, error handling.
      3. Follow the same patterns exactly. Do not invent new ones.

      ## Fix loop

      If the "Fix loop status" section in your user prompt is non-empty,
      the previous attempt failed verification. Read the error, identify
      the minimal fix, apply it. Do not refactor while fixing.

      ## Rules

      1. Match existing patterns - read examples first.
      2. Minimal changes - implement only what's asked.
      3. Never suppress errors (`as any`, `@ts-ignore`, `#[allow(...)]`
         on unfamiliar lints, etc.).
      4. No dead code, no commented-out blocks, no premature abstractions.
      5. End your turn when editing is done. The graph runs verification next.

      Project directory: {{project_dir}}
    prompt: |
      ## Plan summary
      {{plan_summary}}

      ## Files involved
      - Modify: {{files_to_modify}}
      - Create: {{files_to_create}}

      ## Original request from the orchestrator
      {{initial_prompt}}

      ## Fix loop status
      {{fix_instructions}}
    tools:
      - fs_cat
      - fs_ls
      - fs_write
      - fs_patch
      - execute_command
    max_iterations: 30
    state_updates:
      last_node_output: "{{output}}"
    fallback: end_failure
    next: verify_build

  verify_build:
    id: verify_build
    type: script
    description: Run the project's check/build command. Routes to verify_tests on success, fix_loop_gate on failure.
    script: scripts/verify_build.sh
    timeout: 300
    fallback: fix_loop_gate

  verify_tests:
    id: verify_tests
    type: script
    description: Run the project's test command. Routes to end_success on pass, fix_loop_gate on failure.
    script: scripts/verify_tests.sh
    timeout: 600
    fallback: fix_loop_gate

  fix_loop_gate:
    id: fix_loop_gate
    type: script
    description: Budget gate. Loops back to implement with fix_instructions populated, or terminates as end_failure.
    script: scripts/fix_loop_gate.sh
    timeout: 5
    fallback: end_failure

  self_review:
    id: self_review
    type: llm
    description: Skill-driven self-review of the diff. Catches AI slop, dishonest naming, suppressed errors. Bounded to max_review_attempts.
    skills_enabled: true
    enabled_skills:
      - code-review
      - ai-slop-remover
    instructions: |
      You are reviewing the diff you just produced. Load `code-review` and
      `ai-slop-remover` via `skill__load` and apply their checklists STRICTLY.

      Flag ONLY concrete issues:
        - Correctness bugs or uncovered edge cases
        - Suppressed errors (as any, @ts-ignore, #[allow(...)] on unfamiliar
          lints, empty catch blocks)
        - Dishonest naming (get_X that mutates, returns wrong type, etc.)
        - Useless comments that restate the code
        - AI slop (filler prose, multi-paragraph docstrings, defensive
          handling of impossible cases)

      Do NOT flag:
        - Style preferences if the pattern matches existing code in the repo
        - Things the build/tests already verified
        - "Could be more elegant" without a concrete bug

      Be terse. The orchestrator wants signal, not noise. If you find nothing
      blocking, set review_clean=true and leave review_notes empty.

      Project directory: {{project_dir}}
    prompt: |
      ## Files to review
      Modified: {{files_to_modify}}
      Created: {{files_to_create}}

      ## What the implementation was supposed to do
      {{plan_summary}}

      Read each file's changed region. Apply the review skills. Output your verdict.
    tools:
      - fs_cat
      - fs_ls
      - execute_command
    max_iterations: 15
    output_schema:
      type: object
      properties:
        review_clean:
          type: boolean
          description: True if no blocker issues were found.
        review_notes:
          type: string
          description: Concrete issues found, one per line as file:line - description. Empty when review_clean is true.
      required: [review_clean, review_notes]
    state_updates:
      last_node_output: "{{output}}"
    fallback: end_success
    next: route_review_result

  route_review_result:
    id: route_review_result
    type: script
    description: Routes based on review_clean and review_attempts budget. End on clean or budget exhausted; loop to implement otherwise.
    script: scripts/route_review_result.sh
    timeout: 5
    fallback: end_success

  end_success:
    id: end_success
    type: end
    output: |
      CODER_COMPLETE
      Plan: {{plan_summary}}
      Files modified: {{files_to_modify}}
      Files created: {{files_to_create}}
      Build: passed
      Tests: passed

  end_rejected:
    id: end_rejected
    type: end
    output: |
      CODER_REJECTED
      Plan was rejected at the approval gate.
      Plan: {{plan_summary}}

  end_failure:
    id: end_failure
    type: end
    output: |
      CODER_FAILED
      Plan: {{plan_summary}}
      Attempts: {{fix_attempts}}/{{max_fix_attempts}}

      Last node output:
      {{last_node_output}}

      Last build output:
      {{build_output}}

      Last tests output:
      {{tests_output}}
