{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://docs.torcato.dev/repo-ai/repo-ai.json",
  "title": ".repo-ai.json",
  "description": "Config for the @rtorcato/repo-ai loop, read by readConfig() in src/base/config.ts.",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "$schema": {
      "type": "string",
      "description": "URL of this schema, for editor completion and validation."
    },
    "agentUser": {
      "type": "string",
      "description": "GitHub login the loop must run as. `loop guard` halts a tick authenticated as anyone else; unset, the loop runs as whoever `gh` is signed in as."
    },
    "humanUser": {
      "type": "string",
      "description": "GitHub login to assign merge-ready PRs, ai-blocked issues and declined issues to. Defaults to the repo owner when it is a user; an organisation-owned repo has no such owner, so set this or those handoffs get no assignee."
    },
    "requiredSkills": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Skills `doctor` checks are installed, when agentUser is set. Empty or unset means no check."
    },
    "pollSeconds": {
      "type": "integer",
      "minimum": 60,
      "default": 180,
      "description": "Seconds between `loop watch` polls. Each poll costs several GitHub API calls against the 5,000/h limit; values below 60 are raised to 60."
    },
    "budgetTokens": {
      "type": "integer",
      "minimum": 1000,
      "default": 400000,
      "description": "Output-token cap the ai-loop-pickup and ai-loop-recover Workflow scripts enforce per tick, on top of any interactive '+Nk' budget. Once spent, no further agent in that tick is queued — it is skipped, logged, and left for the next tick. Counts output tokens only (the Workflow runtime's budget.spent()); the harness's total, input and cache reads included, runs several times higher."
    },
    "quietStopMinutes": {
      "type": "integer",
      "minimum": 0,
      "default": 120,
      "description": "Minutes the loop's status summary may stay unchanged before a tick stops the loop: it deletes its /ai-loop job and stops any `loop watch` Monitor. Typing /ai-loop restarts it. 0 disables the cutoff."
    },
    "autoMerge": {
      "type": "boolean",
      "default": false,
      "description": "Let the loop merge a fully-passed issue PR unattended. Takes effect only when the repo is also release-gated (its publishing job runs behind an environment with required_reviewers). Unset or false, every issue PR stops for a human."
    },
    "maxInFlight": {
      "type": "integer",
      "minimum": 1,
      "default": 6,
      "description": "Issues the loop keeps in flight at once (open issues labelled ai-wip); `loop tick` sizes its pickup slots from it. Values below 1 are ignored."
    },
    "maxFixRounds": {
      "type": "integer",
      "minimum": 0,
      "default": 2,
      "description": "Fix rounds per PR. ai-loop-pickup runs at most this many, and the round-cap block (ai-blocked) fires on ai-changes application number maxFixRounds + 1. Values below 0 are ignored."
    },
    "maxTasksPerTick": {
      "type": "integer",
      "minimum": 1,
      "default": 8,
      "description": "Review and fix tasks the ai-loop-recover Workflow runs per tick; the rest wait for the next tick. Values below 1 are ignored."
    },
    "staleMinutes": {
      "type": "integer",
      "minimum": 1,
      "default": 45,
      "description": "Minutes a claim label (ai-wip, ai-reviewing-*, ai-fixing) may sit before `loop reap` treats its agent as dead. Values below 1 are ignored."
    },
    "busyMinutes": {
      "type": "integer",
      "minimum": 1,
      "default": 10,
      "description": "Minutes between /ai-loop cron ticks while work is in flight and no `loop watch` Monitor runs. Values below 1 are ignored."
    },
    "idleMinutes": {
      "type": "integer",
      "minimum": 1,
      "default": 30,
      "description": "Minutes between /ai-loop cron ticks when idle, and the fallback cadence under a `loop watch` Monitor. Values below 1 are ignored."
    },
    "dependabotStallMinutes": {
      "type": "integer",
      "minimum": 1,
      "default": 30,
      "description": "Minutes a `@dependabot recreate` may go without a new head before the PR is handed to the human with a decision comment. Values below 1 are ignored."
    },
    "maxAgents": {
      "type": "integer",
      "minimum": 1,
      "description": "Agents running at once across every Workflow, counted from claim labels: an open ai-wip issue with no PR yet, each ai-reviewing-* claim, each ai-fixing claim. `loop tick` trims fix rounds, then reviews, then pickup slots so live plus new stays at or under it. Unset means no cap. Values below 1 are ignored."
    },
    "ciWorkflow": {
      "type": "string",
      "default": "ci.yml",
      "description": "File name of the CI workflow under .github/workflows that runs on pushes to the default branch. `loop tick` and `doctor` watch its runs for cancelled-with-no-jobs, stuck-approval and failed-release symptoms; `doctor` warns when the file does not exist. PR checks need no setting — they cover every workflow."
    }
  }
}
