Skip to content

Commit f30de9d

Browse files
Kasper Jungeclaude
authored andcommitted
chore: streamline ops β€” add ruff, ty, align CI/CD and docs with agr
Add ruff and ty to dev dependencies and fix all lint/format/type issues. Rewrite CI: test.yml now runs quality checks (ruff, ty) alongside pytest matrix and docs build. publish.yml switches from release-trigger to tag-push trigger with auto GitHub Release from CHANGELOG.md. Move changelog to root CHANGELOG.md with snippet include for MkDocs. Add AGENTS.md. Update CLAUDE.md with new commands and Boundaries section. Update release skill to match new flow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c32ed02 commit f30de9d

33 files changed

+1719
-755
lines changed

β€Ž.github/workflows/publish.ymlβ€Ž

Lines changed: 102 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,51 @@
11
name: Publish to PyPI
22

33
on:
4-
release:
5-
types: [published]
4+
push:
5+
tags:
6+
- 'v*'
7+
workflow_dispatch:
8+
inputs:
9+
dry_run:
10+
description: 'Dry run (build but do not publish)'
11+
required: false
12+
default: false
13+
type: boolean
14+
15+
permissions:
16+
contents: read
17+
id-token: write
618

719
jobs:
8-
test:
9-
name: Run tests
20+
quality:
21+
name: Quality Checks
1022
runs-on: ubuntu-latest
1123
steps:
1224
- uses: actions/checkout@v4
25+
with:
26+
ref: ${{ github.ref_name }}
1327
- uses: astral-sh/setup-uv@v5
1428
- uses: actions/setup-python@v5
1529
with:
16-
python-version: "3.11"
30+
python-version: "3.13"
1731
- run: uv sync --group dev
32+
- run: uv run ruff check .
33+
- run: uv run ruff format --check .
34+
- run: uv run ty check
1835
- run: uv run pytest
1936

2037
build:
21-
name: Build package
22-
needs: test
38+
name: Build Package
39+
needs: quality
2340
runs-on: ubuntu-latest
2441
steps:
2542
- uses: actions/checkout@v4
43+
with:
44+
ref: ${{ github.ref_name }}
2645
- uses: astral-sh/setup-uv@v5
2746
- uses: actions/setup-python@v5
2847
with:
29-
python-version: "3.11"
48+
python-version: "3.13"
3049
- run: uv build
3150
- name: Verify package version matches tag
3251
run: |
@@ -49,7 +68,10 @@ jobs:
4968
name: Publish to PyPI
5069
needs: build
5170
runs-on: ubuntu-latest
52-
environment: pypi
71+
if: github.event_name == 'push' || !inputs.dry_run
72+
environment:
73+
name: pypi
74+
url: https://pypi.org/project/ralphify/
5375
permissions:
5476
id-token: write
5577
steps:
@@ -58,3 +80,74 @@ jobs:
5880
name: dist
5981
path: dist/
6082
- uses: pypa/gh-action-pypi-publish@release/v1
83+
84+
release:
85+
name: Create GitHub Release
86+
needs: publish
87+
runs-on: ubuntu-latest
88+
if: github.event_name == 'push' || !inputs.dry_run
89+
permissions:
90+
contents: write
91+
env:
92+
TAG_NAME: ${{ github.ref_name }}
93+
steps:
94+
- uses: actions/checkout@v4
95+
with:
96+
ref: ${{ github.ref_name }}
97+
98+
- name: Validate tag format
99+
run: |
100+
if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
101+
echo "Error: Invalid tag format '$TAG_NAME'"
102+
echo "Expected format: vX.Y.Z (e.g., v0.3.0)"
103+
exit 1
104+
fi
105+
106+
- name: Extract release notes from CHANGELOG
107+
run: |
108+
if [ ! -f CHANGELOG.md ]; then
109+
echo "Error: CHANGELOG.md not found"
110+
exit 1
111+
fi
112+
113+
VERSION_NUM="${TAG_NAME#v}"
114+
115+
NOTES=$(awk -v ver="$VERSION_NUM" '
116+
/^## / {
117+
if (found) exit
118+
if ($0 ~ ver) found=1
119+
next
120+
}
121+
found { print }
122+
' CHANGELOG.md)
123+
124+
if [ -z "$NOTES" ]; then
125+
echo "Error: Version $VERSION_NUM not found in CHANGELOG.md"
126+
echo "Make sure CHANGELOG.md has a section like: ## $VERSION_NUM β€” YYYY-MM-DD"
127+
exit 1
128+
fi
129+
130+
{
131+
echo "## What's New in $TAG_NAME"
132+
echo ""
133+
echo "$NOTES"
134+
echo ""
135+
echo "---"
136+
echo ""
137+
echo "**Full changelog**: ${{ github.server_url }}/${{ github.repository }}/blob/main/CHANGELOG.md"
138+
} > "${RUNNER_TEMP}/release_notes.md"
139+
140+
echo "Release notes extracted successfully"
141+
cat "${RUNNER_TEMP}/release_notes.md"
142+
143+
- name: Create GitHub Release
144+
env:
145+
GH_TOKEN: ${{ github.token }}
146+
run: |
147+
if gh release view "$TAG_NAME" > /dev/null 2>&1; then
148+
echo "::warning::Release $TAG_NAME already exists, skipping creation"
149+
else
150+
gh release create "$TAG_NAME" \
151+
--title "$TAG_NAME" \
152+
--notes-file "${RUNNER_TEMP}/release_notes.md"
153+
fi

β€Ž.github/workflows/test.ymlβ€Ž

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ on:
77
branches: [main]
88

99
jobs:
10+
quality:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: astral-sh/setup-uv@v5
15+
- uses: actions/setup-python@v5
16+
with:
17+
python-version: "3.13"
18+
- run: uv sync --group dev
19+
- run: uv run ruff check .
20+
- run: uv run ruff format --check .
21+
- run: uv run ty check
22+
1023
test:
1124
runs-on: ubuntu-latest
1225
strategy:
@@ -25,9 +38,11 @@ jobs:
2538
runs-on: ubuntu-latest
2639
steps:
2740
- uses: actions/checkout@v4
41+
with:
42+
fetch-depth: 0
2843
- uses: astral-sh/setup-uv@v5
2944
- uses: actions/setup-python@v5
3045
with:
31-
python-version: "3.11"
46+
python-version: "3.13"
3247
- run: uv sync --group dev
3348
- run: uv run mkdocs build --strict

β€ŽAGENTS.mdβ€Ž

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# CLAUDE.md β€” ralphify
2+
3+
Ralphify is the open-source framework for ralph loop harness engineering, published on [PyPI](https://pypi.org/project/ralphify/). It's a CLI tool (`ralph`) that runs AI coding agents in autonomous loops β€” piping prompts to agents, running commands to capture dynamic data, and repeating with fresh context each iteration.
4+
5+
For full architecture details, see `docs/contributing/codebase-map.md`.
6+
7+
## Quick commands
8+
9+
```bash
10+
uv run pytest # Run tests (required before any commit)
11+
uv run pytest -x # Stop on first failure
12+
uv run ruff check . # Lint
13+
uv run ruff format --check . # Check formatting
14+
uv run ty check # Type check
15+
uv run mkdocs build --strict # Build docs (must pass with zero warnings)
16+
uv run mkdocs serve # Preview docs at http://127.0.0.1:8000
17+
```
18+
19+
## Project layout
20+
21+
All source code is in `src/ralphify/`. The main file is `cli.py` β€” it contains the CLI commands and delegates to the engine for the core loop.
22+
23+
Key modules:
24+
- `cli.py` β€” CLI commands (`run`, `new`, `init`); delegates to `_console_emitter.py` for terminal event rendering
25+
- `engine.py` β€” Core run loop orchestration with structured event emission
26+
- `manager.py` β€” Multi-run orchestration (concurrent runs via threads)
27+
- `_frontmatter.py` β€” YAML frontmatter parsing (uses PyYAML) and the `RALPH_MARKER` constant
28+
- `_run_types.py` β€” `RunConfig`, `RunState`, `RunStatus`, `Command` dataclasses
29+
- `_resolver.py` β€” Template placeholder resolution (`{{ commands.name }}`, `{{ args.name }}`)
30+
- `_agent.py` β€” Run agent subprocesses (streaming + blocking modes, log writing)
31+
- `_runner.py` β€” Generic command execution with timeout
32+
- `_events.py` β€” Event types, emitter protocol (NullEmitter, QueueEmitter, FanoutEmitter), and BoundEmitter convenience wrapper
33+
- `_console_emitter.py` β€” Rich terminal rendering of events
34+
- `_output.py` β€” `ProcessResult` base class, combine stdout+stderr, format durations
35+
- `_brand.py` β€” Brand color constants shared across CLI and console rendering
36+
- `_source.py` β€” GitHub source parsing and git-based ralph fetching for `ralph add`
37+
- `_skills.py` β€” Skill installation, agent detection, and command building for `ralph new`
38+
- `skills/new-ralph/SKILL.md` β€” AI-guided ralph creation skill (bundled, installed into agent skill dir)
39+
40+
Tests are in `tests/` with one file per module. Docs are in `docs/` using MkDocs with Material theme.
41+
42+
## Core concepts
43+
44+
A **ralph** is a directory containing a `RALPH.md` file. That's it. No project-level config, no `.ralphify/` directory, no `ralph init`.
45+
46+
**RALPH.md** has YAML frontmatter + a prompt body:
47+
- `agent` (required) β€” the agent command to run
48+
- `commands` (optional) β€” list of `{name, run}` commands whose output fills `{{ commands.<name> }}` placeholders
49+
- `args` (optional) β€” declared argument names for `{{ args.<name> }}` placeholders
50+
51+
**The loop**: run commands β†’ assemble prompt (resolve placeholders) β†’ pipe to agent β†’ repeat.
52+
53+
## Conventions
54+
55+
- **Commit messages**: `docs: explain X for users who want to Y`, `feat: add X so users can Y`, `fix: resolve X that caused Y`
56+
- **Dependencies**: Minimal by design. Runtime deps are `typer`, `rich`, and `pyyaml`. Prefer stdlib over new deps.
57+
- **Tests**: No external services, no API keys. All tests use temporary directories.
58+
- **Docs**: Every user-facing feature needs a docs page. Run `mkdocs build --strict` before committing doc changes.
59+
- **Keeping docs surfaces in sync**: When making user-facing changes (new features, changed behavior, new CLI flags), update all relevant surfaces:
60+
- `docs/` (MkDocs) β€” user-facing docs: CLI reference, quick reference, writing prompts guide, cookbook. Only include what's relevant for users.
61+
- `docs/contributing/` β€” contributor/agent docs: codebase map, architecture. Only include what's relevant for contributors and coding agents.
62+
- `README.md` β€” keep short and high-level. Update only when the change affects the quickstart, install, or core concepts.
63+
- `src/ralphify/skills/new-ralph/SKILL.md` β€” the skill that powers `ralph new`. Update when new frontmatter fields or features are added.
64+
- `CHANGELOG.md` β€” add an entry for every release.
65+
66+
## Boundaries
67+
68+
### Always Do
69+
- Run `uv run pytest` and `uv run ruff check .` before committing
70+
- Save all skills in `skills/` directory (not `.claude/skills/` which is gitignored)
71+
72+
## Traps
73+
74+
- The ralph marker filename (`RALPH.md`) is defined as `RALPH_MARKER` in `_frontmatter.py`. All modules import from there.
75+
- Frontmatter parsing uses PyYAML (`yaml.safe_load`). The `commands` field is a list of dicts, `args` is a list of strings.
76+
- Commands use `_runner.py:run_command()` with `shlex.split()` β€” no shell features (pipes, redirections, `&&`). Scripts are the escape hatch.
77+
- Commands starting with `./` run relative to the ralph directory. Other commands run from the project root.
78+
- The `agent` field in frontmatter is split with `shlex.split()` to build the command list.
79+
- Placeholder resolution uses `_resolver.py` β€” `{{ commands.<name> }}` and `{{ args.<name> }}` are the two supported kinds.
80+
81+
The project has a website called ralphify.co and the docs live a ralphify.co/docs/

0 commit comments

Comments
Β (0)