Skip to content
Merged
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: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

## Project Structure & Module Organization

This is a Bash CLI project. The active runtime is the root `devloop` executable. `install.sh` links it into a local bin directory and installs bundled skills into `~/.agents/skills`, `tests/devloop_test.sh` covers the shell runtime, `skills/devloop-spec/SKILL.md` is the spec-generation skill, `skills/devloop-review/SKILL.md` is the review skill, and `skills/devloop-spec/references/spec-template.md` is the starter spec. Generated runtime output belongs under `.codex/` in target repositories and should not be committed here.
This is a Bash CLI project. The active runtime is the root `devloop` executable. `VERSION` is the single version source, `release.sh` cuts local release commits and annotated tags, `install.sh` links the CLI into a local bin directory and installs bundled skills into `~/.agents/skills` and `~/.claude/skills`, `tests/devloop_test.sh` covers the shell runtime, `skills/devloop-spec/SKILL.md` is the spec-generation skill, `skills/devloop-review/SKILL.md` is the review skill, and `skills/devloop-spec/references/spec-template.md` is the starter spec. Generated runtime output belongs under `.codex/` in target repositories and should not be committed here.

## Build, Test, and Development Commands

- `bash tests/devloop_test.sh`: run the shell test suite.
- `./install.sh`: link `devloop` into `~/.local/bin` or `DEVLOOP_BIN_DIR`.
- `./devloop --plain .specs/change.md`: example local CLI invocation from a target git worktree.
- `./release.sh 0.1.0 --dry-run`: validate the release path without changing files.

## Coding Style & Naming Conventions

Expand All @@ -24,4 +25,4 @@ Git history follows Conventional Commits, for example `fix: surface devloop comm

## Agent-Specific Instructions

Keep changes narrow and regression-first. Do not commit `.codex/`, coverage artifacts, local specs, or dependency caches unless explicitly requested. When modifying acceptance or reporting behavior, update both tests and `README.md` examples if user-facing output changes.
Keep changes narrow and regression-first. Do not commit `.codex/`, coverage artifacts, local specs, or dependency caches unless explicitly requested. When modifying acceptance, reporting, versioning, or release behavior, update tests and `README.md` examples if user-facing output changes.
56 changes: 56 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Changelog

All notable changes to this project will be documented in this file.

## [0.1.0](https://github.com/satyaborg/devloop/releases/tag/v0.1.0) - 2026-05-30

### Added

- initial implementation ([96901ee](https://github.com/satyaborg/devloop/commit/96901ee67cbe8e01e4fe25cea9807d100dc94f59))
- reuse agent sessions in devloop ([6cf72c5](https://github.com/satyaborg/devloop/commit/6cf72c59df7acf8ae95e66e882e7e05898365abd))
- port devloop to bun opentui ([38cf9bd](https://github.com/satyaborg/devloop/commit/38cf9bde799ec7e8c5b47b261e0ff986a14dd67f))
- show default cli welcome ([4e55181](https://github.com/satyaborg/devloop/commit/4e551816a2446ac041100b22c988eaa0e3e5a398))
- run devloop in isolated worktrees ([17b7fd5](https://github.com/satyaborg/devloop/commit/17b7fd5ac76ee90feb601efd11ac4b902bd73c99))
- derive semantic work names ([0bb4bf0](https://github.com/satyaborg/devloop/commit/0bb4bf0411c854f872038c0ca22826582dd65b40))
- show tui step progress ([fcfe52a](https://github.com/satyaborg/devloop/commit/fcfe52af09db5c6b6d3dbecb3e170a9849a860ac))
- bundle spec generation skill ([a4ae43d](https://github.com/satyaborg/devloop/commit/a4ae43d45cb9e8e3cbfb01ebde03e94bf67fd3a2))
- configure coder and reviewer agents ([f00c2ae](https://github.com/satyaborg/devloop/commit/f00c2aee76b55f15a98c806e20a527c0062420f5))
- require evidence-rich review matrices ([1f31f70](https://github.com/satyaborg/devloop/commit/1f31f7077da79fcca2ec1219252c3d0e7ce37373))
- automate pass commits and prs ([4c222a7](https://github.com/satyaborg/devloop/commit/4c222a72eaa73535777ed0dfbe789902f704589b))
- npm-release-readiness ([057aef0](https://github.com/satyaborg/devloop/commit/057aef0fc8e715952c394af4be8ba3fc6ba59b1d))
- force maximum agent effort ([51af634](https://github.com/satyaborg/devloop/commit/51af634d573774b6a88d722927f3460c4081d854))
- add bash devloop runtime ([f6611e0](https://github.com/satyaborg/devloop/commit/f6611e00c480058014b73708ab7bfdae6eefc5cc))
- add adversarial review gates ([5143efd](https://github.com/satyaborg/devloop/commit/5143efd46713e0b95b6e550d9e702fb2a814fba5))
- overhaul interactive cli ui ([3d0f033](https://github.com/satyaborg/devloop/commit/3d0f0332592b1a68a4e3606249ca82013b7ba506))
- configure spec directories ([c8a7cbd](https://github.com/satyaborg/devloop/commit/c8a7cbdc1ee272f8baae9e146a180d271d55de94))
- add scoped devloop config ([00868e6](https://github.com/satyaborg/devloop/commit/00868e6532e87e1663340c187bee34872a2db38e))
- simplify spec path settings ([1e1213c](https://github.com/satyaborg/devloop/commit/1e1213cafabe8f984e45ee8212e3585bd768d15f))
- polish interactive tui ([a4485ae](https://github.com/satyaborg/devloop/commit/a4485ae3022a827275bed4870128a6ebeda2ff2c))


### Breaking changes

- install skills for codex and claude ([eb2cb6d](https://github.com/satyaborg/devloop/commit/eb2cb6de9f988ea2acac988f7f1fb782a25a39ba))


### Documentation

- add repository guidelines ([447dd0c](https://github.com/satyaborg/devloop/commit/447dd0ce63264b1fc9cc124eaa975fa179ee3e76))
- link claude guide to agents ([70be06e](https://github.com/satyaborg/devloop/commit/70be06eebb326baaa54ff5eb4d8c6199f074d029))
- clarify worktree cleanup ([3dc7b1b](https://github.com/satyaborg/devloop/commit/3dc7b1b239684e17683c04f8e0f3ba15c2d16b0e))
- add license section ([ac82872](https://github.com/satyaborg/devloop/commit/ac8287290a2f7dae956248fc46411e9ac9cacf8b))


### Fixed

- surface devloop commit failures ([493031a](https://github.com/satyaborg/devloop/commit/493031ab90ba593ad2bc904ec3d8c0ba1fd92b96))
- clarify worktree outputs ([6e1067a](https://github.com/satyaborg/devloop/commit/6e1067acf5cf0298a3e3c825176f190cbb346bbb))
- clean up naming temp logs ([424a502](https://github.com/satyaborg/devloop/commit/424a502fe78441e6b9e9877f719bc876acbd2210))
- align tui navigation labels ([3f74b90](https://github.com/satyaborg/devloop/commit/3f74b90b0d87294c5224f33888f6c05346ba1397))
- derive report subtitles from specs ([1984ca6](https://github.com/satyaborg/devloop/commit/1984ca6b8e82dfdd3689df864fc1108d38ec2517))
- add topical report haiku ([2cd843d](https://github.com/satyaborg/devloop/commit/2cd843df292217f676e59b3631d7f6d922d3a1db))
- key sessions by configured agent ([16ef419](https://github.com/satyaborg/devloop/commit/16ef419de2429bc4569fc7789fb4fb1998aefc0f))
- align release readiness tests ([8d9f0bf](https://github.com/satyaborg/devloop/commit/8d9f0bf14d5ca4be0096e01cca8ca19d140f270b))
- harden bash runtime helpers ([7fb28cc](https://github.com/satyaborg/devloop/commit/7fb28cc7f7da3788d7ef3a233242465ad4a8e2c9))
- preserve interactive run status ([48cadbb](https://github.com/satyaborg/devloop/commit/48cadbb140662919f6ae8ce6597274cf755d13a1))

29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ cd devloop
./install.sh
```

Check the installed version:

```sh
devloop --version
```

Run without installing:

```sh
Expand Down Expand Up @@ -104,6 +110,7 @@ devloop [options] <spec.md> [max=5]
| `--no-strict` | Weaken strict review gates |
| `--no-shell`, `--stay` | Do not open a shell in the generated worktree after completion |
| `--shell`, `--enter-worktree` | Open a shell in the generated worktree after completion |
| `-V`, `--version` | Show version |

## Interactive UI

Expand Down Expand Up @@ -137,15 +144,35 @@ Nested menu screens keep `Back` as the final option so you can return to the pre
## Development

```sh
bash -n devloop install.sh
bash -n devloop install.sh release.sh skill_helpers.sh
./devloop --help
./devloop --version
tmp="$(mktemp -d)"
DEVLOOP_BIN_DIR="$tmp/bin" HOME="$tmp/home" ./install.sh
PATH="$tmp/bin:$PATH" HOME="$tmp/home" devloop doctor
```

The supported runtime is the root [`devloop`](devloop) Bash script.

## Versioning and Release

`devloop` uses [Semantic Versioning](https://semver.org/) and stores the current version in [`VERSION`](VERSION). `0.x` releases are initial public API releases, so breaking changes can happen between minor versions.

Release notes in [`CHANGELOG.md`](CHANGELOG.md) are generated from Conventional Commit history with [`git-cliff`](https://git-cliff.org/). Install it before cutting a real release:

```sh
brew install git-cliff
```

Cut a release from a clean tree:

```sh
./release.sh 0.1.0 --dry-run
./release.sh 0.1.0
```

That updates `VERSION` and [`CHANGELOG.md`](CHANGELOG.md), runs `bash tests/devloop_test.sh`, commits `chore: release 0.1.0`, and creates an annotated `v0.1.0` tag. Add `--push` to push the release branch and tag. By default, pushed releases must run from `main`.

## License

MIT. See [LICENSE](LICENSE).
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1.0
36 changes: 36 additions & 0 deletions cliff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[changelog]
header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n"
body = """
{% if version -%}
## [{{ version | trim_start_matches(pat="v") }}](https://github.com/satyaborg/devloop/releases/tag/{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
{% else -%}
## [Unreleased]
{% endif -%}
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {{ commit.message | split(pat="\n") | first | trim }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/satyaborg/devloop/commit/{{ commit.id }}))
{%- endfor %}

{% endfor %}
"""
trim = true

[git]
conventional_commits = true
filter_unconventional = false
split_commits = false
commit_parsers = [
{ message = "^chore: release", skip = true },
{ message = "^Merge pull request", skip = true },
{ message = "^.+!:", group = "Breaking changes" },
{ message = "^feat", group = "Added" },
{ message = "^fix", group = "Fixed" },
{ message = "^docs", group = "Documentation" },
{ message = "^test", skip = true },
{ message = "^chore", skip = true },
{ message = "^refactor", skip = true },
{ message = "^.*", skip = true },
]
tag_pattern = "v[0-9].*"
sort_commits = "oldest"
25 changes: 24 additions & 1 deletion devloop
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ while [ -L "$SCRIPT_PATH" ]; do
done
ROOT_DIR="$(cd -P "$(dirname "$SCRIPT_PATH")" >/dev/null 2>&1 && pwd)"
source "$ROOT_DIR/skill_helpers.sh"
DEVLOOP_VERSION="$(sed -n '1p' "$ROOT_DIR/VERSION" 2>/dev/null || true)"
if [ -z "$DEVLOOP_VERSION" ]; then DEVLOOP_VERSION="0.0.0-dev"; fi

USE_TUI=false
if [ -t 1 ]; then USE_TUI=true; fi
Expand Down Expand Up @@ -67,6 +69,11 @@ main() {
if has_arg "--plain" "$@"; then USE_TUI=false; fi
if has_arg "--tui" "$@"; then USE_TUI=true; fi

if version_args_only "$@"; then
printf 'devloop %s\n' "$DEVLOOP_VERSION"
return 0
fi

if [ "${1:-}" = "spec" ]; then
shift
spec_command "$@"
Expand Down Expand Up @@ -127,6 +134,7 @@ devloop_logo() {
░█░█░█▀▀░▀▄▀░█░░░█░█░█░█░█▀▀
░▀▀░░▀▀▀░░▀░░▀▀▀░▀▀▀░▀▀▀░▀░░
EOF
printf 'v%s\n' "$DEVLOOP_VERSION"
}

welcome_plain() {
Expand Down Expand Up @@ -162,6 +170,7 @@ Options:
--create-pr, --pr push accepted branch and open a PR
--no-shell, --stay do not open a shell in the run worktree after completion
--shell, --enter-worktree open a shell in the run worktree after completion
-V, --version show version
-h, --help show this screen
EOF
}
Expand Down Expand Up @@ -192,11 +201,12 @@ welcome_tui() {
printf ' %-30s %s\n' "--create-pr, --pr" "push accepted branch and open a PR"
printf ' %-30s %s\n' "--no-shell, --stay" "do not open a shell after completion"
printf ' %-30s %s\n' "--shell, --enter-worktree" "open a shell after completion"
printf ' %-30s %s\n' "-V, --version" "show version"
printf ' %-30s %s\n' "-h, --help" "show this screen"
}

usage() {
printf '%s\n' "usage: devloop [--plain|--tui] [--in-place] [--no-strict] [--create-pr|--pr] [--no-shell|--stay|--shell|--enter-worktree] [--coder codex|claude] [--reviewer codex|claude] [--report-format html|markdown] <spec.md> [max=5]"
printf '%s\n' "usage: devloop [--version] [--plain|--tui] [--in-place] [--no-strict] [--create-pr|--pr] [--no-shell|--stay|--shell|--enter-worktree] [--coder codex|claude] [--reviewer codex|claude] [--report-format html|markdown] <spec.md> [max=5]"
printf '%s\n' "agents: Codex or Claude Code"
}

Expand All @@ -222,6 +232,19 @@ has_arg() {
return 1
}

version_args_only() {
local item saw_version=false
[ "$#" -gt 0 ] || return 1
for item in "$@"; do
case "$item" in
--version|-V) saw_version=true ;;
--plain|--tui) ;;
*) return 1 ;;
esac
done
[ "$saw_version" = true ]
}

trim_string() {
printf '%s\n' "$1" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//'
}
Expand Down
151 changes: 151 additions & 0 deletions release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_PATH="${BASH_SOURCE[0]}"
while [ -L "$SCRIPT_PATH" ]; do
SCRIPT_DIR="$(cd -P "$(dirname "$SCRIPT_PATH")" >/dev/null 2>&1 && pwd)"
SCRIPT_TARGET="$(readlink "$SCRIPT_PATH")"
case "$SCRIPT_TARGET" in
/*) SCRIPT_PATH="$SCRIPT_TARGET" ;;
*) SCRIPT_PATH="$SCRIPT_DIR/$SCRIPT_TARGET" ;;
esac
done

ROOT="$(cd -P "$(dirname "$SCRIPT_PATH")" >/dev/null 2>&1 && pwd)"

release_usage() {
cat <<'EOF'
usage: ./release.sh <version> [--dry-run] [--push]

Creates a release commit and annotated tag from a SemVer version.

Examples:
./release.sh 0.1.0 --dry-run
./release.sh 0.1.0
./release.sh 0.1.0 --push
EOF
}

release_version_valid() {
local version="$1"
printf '%s\n' "$version" | grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$'
}

release_tag_for_version() {
printf 'v%s\n' "$1"
}

release_require_command() {
local command="$1"
if command -v "$command" >/dev/null 2>&1; then return 0; fi
printf 'missing required command: %s\n' "$command" >&2
return 1
}

release_assert_clean_tree() {
if [ -z "$(git -C "$ROOT" status --porcelain)" ]; then return 0; fi
printf '%s\n' "working tree must be clean before release" >&2
return 1
}

release_assert_tag_available() {
local tag="$1"
if git -C "$ROOT" rev-parse -q --verify "refs/tags/$tag" >/dev/null; then
printf 'tag already exists: %s\n' "$tag" >&2
return 1
fi
}

release_current_branch() {
git -C "$ROOT" branch --show-current
}

release_assert_push_branch() {
local branch
branch="$(release_current_branch)"
if [ -z "$branch" ]; then
printf '%s\n' "refusing to push release from detached HEAD" >&2
return 1
fi
if [ "$branch" = "main" ] || [ "${DEVLOOP_RELEASE_ALLOW_BRANCH:-0}" = "1" ]; then return 0; fi
printf 'refusing to push release from branch: %s\n' "$branch" >&2
printf '%s\n' "checkout main, or set DEVLOOP_RELEASE_ALLOW_BRANCH=1" >&2
return 1
}

release_main() {
local version=""
local dry_run=false
local push=false
local tag branch

while [ "$#" -gt 0 ]; do
case "$1" in
--dry-run) dry_run=true ;;
--push) push=true ;;
-h|--help) release_usage; return 0 ;;
--*)
printf 'unknown option: %s\n' "$1" >&2
release_usage >&2
return 2
;;
*)
if [ -n "$version" ]; then
release_usage >&2
return 2
fi
version="$1"
;;
esac
shift
done

if [ -z "$version" ]; then
release_usage >&2
return 2
fi
if ! release_version_valid "$version"; then
printf 'invalid SemVer version: %s\n' "$version" >&2
return 2
fi

tag="$(release_tag_for_version "$version")"
release_require_command git
if [ "$dry_run" = true ]; then
release_assert_tag_available "$tag"
printf 'release: %s\n' "$tag"
if [ -n "$(git -C "$ROOT" status --porcelain)" ]; then
printf '%s\n' "note: actual release requires a clean working tree"
fi
printf '%s\n' "would run bash tests/devloop_test.sh"
printf '%s\n' "would update VERSION and CHANGELOG.md"
printf 'would commit: chore: release %s\n' "$version"
printf 'would tag: %s\n' "$tag"
if [ "$push" = true ]; then printf '%s\n' "would push branch and tag"; fi
return 0
fi

release_require_command git-cliff
release_assert_tag_available "$tag"
release_assert_clean_tree
if [ "$push" = true ]; then release_assert_push_branch; fi

bash "$ROOT/tests/devloop_test.sh"
printf '%s\n' "$version" > "$ROOT/VERSION"
git-cliff --config "$ROOT/cliff.toml" --workdir "$ROOT" --tag "$tag" --output "$ROOT/CHANGELOG.md"
git -C "$ROOT" add VERSION CHANGELOG.md
git -C "$ROOT" commit -m "chore: release $version"
git -C "$ROOT" tag -a "$tag" -m "devloop $version"

if [ "$push" = true ]; then
branch="$(release_current_branch)"
git -C "$ROOT" push origin "$branch"
git -C "$ROOT" push origin "$tag"
fi

printf 'released %s\n' "$tag"
}

if [ "${DEVLOOP_RELEASE_LIB:-}" != "1" ]; then
release_main "$@"
fi
Loading