Skip to content
Draft
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
5 changes: 5 additions & 0 deletions agents/refactoring-agent/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
GITHUB_TOKEN=your_github_personal_access_token_here
REPO_OWNER=github_username_or_org
REPO_NAME=repository_name
GEMINI_API_KEY=your_gemini_api_key_here
BASE_BRANCH=main
8 changes: 8 additions & 0 deletions agents/refactoring-agent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
dist/
node_modules/
state.json
.env
.DS_Store
.pnpm-store/
.npm-cache/
.yarn-cache/
56 changes: 56 additions & 0 deletions agents/refactoring-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Refactoring Agent

An autonomous Level-1 agent that reviews merged GitHub PRs since its last run, synthesises the diffs with local skill documentation, and opens a draft PR with prioritised refactoring suggestions.

## Architecture

```
src/
├── index.ts # Agent orchestrator (entry point)
├── config.ts # Environment variable loading
├── state.ts # Reads/writes state.json (lastRunAt)
├── skills.ts # Loads markdown files from skills/ into system prompt
└── tools/
├── list-prs.ts # makeListPrsTool — lists merged PRs since last run
├── get-pr-diff.ts # makeGetPrDiffTool — fetches unified diff
└── create-draft-pr.ts # makeCreateDraftPrTool — creates the output draft PR
prompts/system.md # System prompt template ({SKILLS} and {REPO} are replaced at runtime)
skills/ # Drop .md files here to give the agent domain context
state.json # Gitignored — persists lastRunAt timestamp
```

**Model:** `gemini-2.5-flash` via `@mariozechner/pi-ai` + `@mariozechner/pi-agent-core`

## Setup

```bash
cp .env.example .env
# Fill in GITHUB_TOKEN, REPO_OWNER, REPO_NAME, GEMINI_API_KEY
npm install
```

## Running

```bash
npm run dev # Quick run via tsx (no build needed)
npm run build # Compile to dist/
npm start # Run compiled output
```

## Adding Skills

Drop any `.md` file into the `skills/` directory. These are read at startup and injected into the system prompt, giving the agent project-specific domain context (coding conventions, architectural patterns, etc.).

## How It Works

1. Loads `state.json` to get `lastRunAt` (defaults to 7 days ago on first run)
2. Loads skill `.md` files and injects them into the system prompt
3. Starts the agent loop with three tools: `list_prs`, `get_pr_diff`, `create_draft_pr`
4. The LLM lists PRs → fetches diffs → synthesises → creates a draft PR
5. Saves updated `lastRunAt` to `state.json`

## Tests

```bash
npm test
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
name: Refactoring Agent — Implementation Plan
goal: Build a Level-1 autonomous agent using `@mariozechner/pi-agent-core` + `@mariozechner/pi-ai` that fetches GitHub PR diffs since its last run, synthesizes them with local skill docs, and opens a draft PR with prioritised refactoring suggestions.
created: 2026-03-06T16:35:45+01:00
progress: 0/0
---

# Refactoring Agent — Implementation Plan

**Goal:** Build a Level-1 autonomous agent using `@mariozechner/pi-agent-core` + `@mariozechner/pi-ai` that fetches GitHub PR diffs since its last run, synthesizes them with local skill docs, and opens a draft PR with prioritised refactoring suggestions.

**Architecture:** A standalone TypeScript project under `agent-hours/agents/refactoring-agent/`. The `Agent` class from `@mariozechner/pi-agent-core` is instantiated in `src/index.ts` with three `AgentTool` objects wrapping `@octokit/rest` (`list_prs`, `get_pr_diff`, `create_draft_pr`). A `state.json` file (gitignored) tracks `lastRunAt`. The system prompt (from `prompts/system.md`) is dynamically prepended with skill markdown files loaded via `src/skills.ts` — the domain context informs the LLM without modifying pi-mono internals.

---

## 8 Tasks at a Glance

### Task 1: Project scaffolding
**Key Output:** `package.json`, `tsconfig.json`, `.gitignore`

### Task 2: Config & state management
**Key Output:** `src/config.ts`, `src/state.ts`

### Task 3: GitHub API tools
**Key Output:** `src/tools/{list-prs,get-pr-diff,create-draft-pr}.ts`

### Task 4: Skills context loader
**Key Output:** `src/skills.ts`

### Task 5: System prompt
**Key Output:** `prompts/system.md`

### Task 6: Agent orchestrator
**Key Output:** `src/index.ts` — wires all pieces together

### Task 7: README & submission
**Key Output:** `README.md`, `SUBMISSION.md`

### Task 8: Unit tests
**Key Output:** `src/tools/list-prs.test.ts`, `vitest.config.ts`

---

## Key Design Decisions

- **`AgentTool` factory functions** (e.g. `makeListPrsTool(octokit, owner, repo, since)`) close over the Octokit client and `since` timestamp — the `Agent` sees clean, parameterless tools while the software layer handles state.
- **Skill loading at startup** — `loadSkills(dir)` reads all `.md` files from the configured skills directory and prepends them to the system prompt. This is the "knowledge injection" step that distinguishes this agent from a generic LLM call.
- **`state.json`** holds only `{ lastRunAt: string | null }` — minimal, easily inspectable, gitignored.
- **Error contract**: tools `throw` on GitHub API failures; pi-mono catches and reports these to the LLM as `isError: true` tool results, which naturally triggers graceful stopping per the operational boundaries in `plan.md`.
- **`google/gemini-3-flash`** is used as the model via `getModel("google", "gemini-3-flash")` — fast and cheap for a text-only synthesis task.
<!-- dispatch-status: task-1 = running -->
<!-- dispatch-status: task-2 = running -->
<!-- dispatch-status: task-3 = running -->
<!-- dispatch-status: task-4 = running -->
53 changes: 53 additions & 0 deletions agents/refactoring-agent/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// @ts-check
import tseslint from "typescript-eslint";

export default tseslint.config(
// Base: recommended + strict + stylistic type-checked rules
...tseslint.configs.strictTypeChecked,
...tseslint.configs.stylisticTypeChecked,

{
languageOptions: {
parserOptions: {
project: "tsconfig.eslint.json",
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
// ── Correctness ───────────────────────────────────────────────
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unsafe-assignment": "error",
"@typescript-eslint/no-unsafe-call": "error",
"@typescript-eslint/no-unsafe-member-access": "error",
"@typescript-eslint/no-unsafe-return": "error",
"@typescript-eslint/no-floating-promises": "error",

// Allow _-prefixed params (required by AgentTool interface signature)
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],

// ── Clarity ───────────────────────────────────────────────────
"@typescript-eslint/explicit-function-return-type": ["error", { allowExpressions: true }],
"@typescript-eslint/consistent-type-imports": ["error", { prefer: "type-imports" }],
"@typescript-eslint/no-import-type-side-effects": "error",

// ── Safety ────────────────────────────────────────────────────
"@typescript-eslint/no-non-null-assertion": "error",
"no-console": "off", // console logging is intentional in CLI tools
"eqeqeq": ["error", "always"],
},
},

// Relax rules for test files
{
files: ["**/*.test.ts"],
rules: {
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-explicit-any": "off",
},
},

// Ignore compiled output and the eslint config itself
{ ignores: ["dist/**", "eslint.config.js"] },
);
Loading