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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## 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, `tests/devloop_test.sh` covers the shell runtime, `skills/devloop-spec/SKILL.md` is the spec-generation prompt asset, `skills/devloop-review/SKILL.md` is the review prompt asset, and `templates/spec.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. `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.

## Build, Test, and Development Commands

Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ Run without installing:
./devloop --help
```

`install.sh` also installs the bundled Agent Skills globally under `~/.agents/skills`.
After install or update, verify the local setup:

```sh
devloop doctor
```

## Quick Start

Create a spec:
Expand All @@ -44,7 +51,7 @@ devloop --create-pr .specs/change.md

## Specs

A good spec is short, concrete, and verifiable. Start from [`templates/spec.md`](templates/spec.md), or generate one:
A good spec is short, concrete, and verifiable. Start from [`skills/devloop-spec/references/spec-template.md`](skills/devloop-spec/references/spec-template.md), or generate one:

```sh
devloop spec
Expand Down Expand Up @@ -94,7 +101,9 @@ devloop [options] <spec.md> [max=5]
```sh
bash -n devloop install.sh
./devloop --help
DEVLOOP_BIN_DIR="$(mktemp -d)/bin" ./install.sh
tmp="$(mktemp -d)"
DEVLOOP_BIN_DIR="$tmp/bin" DEVLOOP_SKILLS_DIR="$tmp/skills" ./install.sh
PATH="$tmp/bin:$PATH" DEVLOOP_SKILLS_DIR="$tmp/skills" devloop doctor
```

The supported runtime is the root [`devloop`](devloop) Bash script.
Expand Down
25 changes: 18 additions & 7 deletions devloop
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ while [ -L "$SCRIPT_PATH" ]; do
esac
done
ROOT_DIR="$(cd -P "$(dirname "$SCRIPT_PATH")" >/dev/null 2>&1 && pwd)"
source "$ROOT_DIR/skill_helpers.sh"

USE_TUI=false
if [ -t 1 ]; then USE_TUI=true; fi
Expand Down Expand Up @@ -52,6 +53,16 @@ main() {
return $?
fi

if [ "${1:-}" = "doctor" ]; then
shift
if [ "$#" -gt 0 ]; then
printf '%s\n' "usage: devloop doctor" >&2
return 2
fi
devloop_doctor "$ROOT_DIR"
return $?
fi

if [ "$#" -eq 0 ] || has_arg "-h" "$@" || has_arg "--help" "$@"; then
welcome
return 0
Expand All @@ -75,6 +86,7 @@ Usage:
devloop [options] <spec.md> [max=5]

Common commands:
devloop doctor
devloop spec "add retry behavior to the chat sender"
devloop .specs/change.md
devloop --tui .specs/change.md
Expand Down Expand Up @@ -1081,7 +1093,7 @@ review_prompt() {
local criteria priors review_skill strict_rule
criteria="$(criteria_block "$criteria_file")"
priors="$(list_reviews "$slug" "$pass" "$max")"
review_skill="$(cat "$ROOT_DIR/skills/devloop-review/SKILL.md")"
review_skill="$ROOT_DIR/skills/devloop-review/SKILL.md"
strict_rule=""
if [ "$strict" = true ]; then
strict_rule="
Expand All @@ -1099,8 +1111,8 @@ $priors
Acceptance criteria:
$criteria
Output path: $output
Bundled review skill:
$review_skill
Skill: use the installed devloop-review skill.
Bundled skill path, for fallback only: $review_skill

Steps:
1. Read the spec and track.
Expand Down Expand Up @@ -1644,7 +1656,7 @@ spec_command() {
local context today prompt cmd_args=()
context="$(resolve_spec_context "${context_items[@]}")"
today="$(date +%F)"
prompt="$(spec_prompt "$context" "$output" "$(cat "$skill")" "$today")"
prompt="$(spec_prompt "$context" "$output" "$skill" "$today")"
if [ "$agent" = "codex" ]; then
run_with_prompt "$PWD" "" "" "$prompt" codex exec "${CODEX_REASONING_ARGS[@]}" -s read-only -C "$PWD" -
elif [ "$agent" = "claude" ]; then
Expand Down Expand Up @@ -1705,15 +1717,14 @@ spec_prompt() {
output_line="Output path: choose a .specs/YYYY-MM-DD-<slug>.md path if you write a file; otherwise return markdown on stdout."
fi
cat <<EOF
Use this bundled devloop skill to produce one implementation spec.
Use the installed devloop-spec skill to produce one implementation spec.

Current date: $today
$output_line

If the source context is missing or too thin, follow the skill's interview path before drafting. Return only the final markdown spec. Do not wrap it in a code fence.

Bundled skill:
$skill
Bundled skill path, for fallback only: $skill

Context:
$context
Expand Down
7 changes: 6 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ while [ -L "$SCRIPT_PATH" ]; do
done

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

BIN_DIR="${DEVLOOP_BIN_DIR:-$HOME/.local/bin}"
TARGET="$BIN_DIR/devloop"
SOURCE="$ROOT/devloop"
SKILL_STATUS=0

if [ ! -f "$SOURCE" ]; then
echo "missing devloop executable: $SOURCE" >&2
Expand All @@ -26,6 +29,7 @@ chmod +x "$SOURCE"
ln -sfn "$SOURCE" "$TARGET"

echo "installed devloop -> $SOURCE"
devloop_install_skills "$ROOT" || SKILL_STATUS=$?

case ":${PATH:-}:" in
*":$BIN_DIR:"*) ;;
Expand All @@ -37,4 +41,5 @@ case ":${PATH:-}:" in
esac

echo
echo "try: devloop --help"
echo "try: devloop doctor"
exit "$SKILL_STATUS"
212 changes: 212 additions & 0 deletions skill_helpers.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#!/usr/bin/env bash

devloop_skills_dir() {
printf '%s\n' "${DEVLOOP_SKILLS_DIR:-$HOME/.agents/skills}"
}

devloop_checksum_file() {
local file="$1"
if command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$file" | awk '{print $1}'
elif command -v sha256sum >/dev/null 2>&1; then
sha256sum "$file" | awk '{print $1}'
else
cksum "$file" | awk '{print $1}'
fi
}

devloop_skill_tree_checksum() {
local root="$1"
local manifest checksum
if [ ! -d "$root" ]; then return 1; fi
manifest="$(mktemp "${TMPDIR:-/tmp}/devloop-skill-manifest.XXXXXX")"
(
cd "$root" || exit 1
find . -type f ! -name ".devloop-checksum" | LC_ALL=C sort | while IFS= read -r file; do
printf '%s %s\n' "$(devloop_checksum_file "$file")" "${file#./}"
done
) > "$manifest" || {
rm -f "$manifest"
return 1
}
checksum="$(devloop_checksum_file "$manifest")"
rm -f "$manifest"
printf '%s\n' "$checksum"
}

devloop_skill_name() {
sed -n 's/^name: *//p' "$1" | head -n 1
}

devloop_valid_skill_name() {
printf '%s\n' "$1" | grep -Eq '^[a-z0-9]+(-[a-z0-9]+)*$'
}

devloop_can_replace_skill() {
local dest="$1"
local force="${DEVLOOP_FORCE:-0}"
local marker recorded current
if [ "$force" = "1" ]; then return 0; fi
if [ ! -e "$dest" ] && [ ! -L "$dest" ]; then return 0; fi
if [ -L "$dest" ]; then return 1; fi

marker="$dest/.devloop-checksum"
if [ ! -f "$marker" ]; then return 1; fi
recorded="$(cat "$marker")"
current="$(devloop_skill_tree_checksum "$dest")" || return 1
[ "$recorded" = "$current" ]
}

devloop_install_skills() {
local root="$1"
local skills_dir mode source name dest checksum status
skills_dir="$(devloop_skills_dir)"
mode="${DEVLOOP_SKILL_INSTALL:-copy}"
status=0

case "$mode" in
copy|link) ;;
*)
printf 'unknown DEVLOOP_SKILL_INSTALL: %s\n' "$mode" >&2
return 2
;;
esac

if ! mkdir -p "$skills_dir"; then
printf 'failed to create skills directory: %s\n' "$skills_dir" >&2
return 1
fi
for source in "$root"/skills/*; do
if [ ! -d "$source" ]; then continue; fi
name="$(basename "$source")"
dest="$skills_dir/$name"
checksum="$(devloop_skill_tree_checksum "$source")" || {
printf 'failed to checksum bundled skill: %s\n' "$source" >&2
status=1
continue
}

if ! devloop_can_replace_skill "$dest"; then
printf 'skipping modified skill: %s (set DEVLOOP_FORCE=1 to overwrite)\n' "$dest" >&2
continue
fi

if ! rm -rf "$dest"; then
printf 'failed to replace skill: %s\n' "$dest" >&2
status=1
continue
fi

if [ "$mode" = "link" ]; then
if ! ln -s "$source" "$dest"; then
printf 'failed to link skill: %s\n' "$dest" >&2
status=1
continue
fi
printf 'installed skill %s -> %s\n' "$name" "$source"
else
if ! mkdir -p "$dest"; then
printf 'failed to create skill directory: %s\n' "$dest" >&2
status=1
continue
fi
if ! cp -R "$source/." "$dest/"; then
printf 'failed to copy skill: %s\n' "$dest" >&2
status=1
continue
fi
if ! printf '%s\n' "$checksum" > "$dest/.devloop-checksum"; then
printf 'failed to write skill checksum: %s\n' "$dest/.devloop-checksum" >&2
status=1
continue
fi
printf 'installed skill %s -> %s\n' "$name" "$dest"
fi
done

return "$status"
}

devloop_doctor_command() {
local command="$1"
local resolved
resolved="$(command -v "$command" 2>/dev/null || true)"
if [ -n "$resolved" ]; then
printf '[ok] %s: %s\n' "$command" "$resolved"
return 0
fi
printf '[fail] missing command: %s\n' "$command" >&2
return 1
}

devloop_doctor_skills() {
local root="$1"
local skills_dir source name dest declared bundled installed status
skills_dir="$(devloop_skills_dir)"
status=0

for source in "$root"/skills/*; do
if [ ! -d "$source" ]; then continue; fi
name="$(basename "$source")"
dest="$skills_dir/$name"

if [ ! -f "$dest/SKILL.md" ]; then
printf '[fail] missing skill: %s\n' "$dest/SKILL.md" >&2
status=1
continue
fi

declared="$(devloop_skill_name "$dest/SKILL.md")"
if [ "$declared" != "$name" ]; then
printf '[fail] skill name mismatch: %s declares %s\n' "$dest/SKILL.md" "$declared" >&2
status=1
continue
fi

if ! devloop_valid_skill_name "$declared"; then
printf '[fail] invalid skill name: %s\n' "$declared" >&2
status=1
continue
fi

bundled="$(devloop_skill_tree_checksum "$source")" || {
printf '[fail] failed to checksum bundled skill: %s\n' "$source" >&2
status=1
continue
}
installed="$(devloop_skill_tree_checksum "$dest")" || {
printf '[fail] failed to checksum installed skill: %s\n' "$dest" >&2
status=1
continue
}

if [ "$bundled" != "$installed" ]; then
printf '[fail] stale skill: %s (run ./install.sh)\n' "$dest" >&2
status=1
continue
fi

printf '[ok] skill %s: %s\n' "$name" "$dest"
done

return "$status"
}

devloop_doctor() {
local root="$1"
local status=0

printf 'devloop doctor\n'
devloop_doctor_command devloop || status=1
devloop_doctor_command git || status=1
devloop_doctor_command codex || status=1
devloop_doctor_command claude || status=1
devloop_doctor_skills "$root" || status=1

if [ "$status" -eq 0 ]; then
printf 'devloop doctor: ready\n'
else
printf 'devloop doctor: not ready\n' >&2
fi
return "$status"
}
2 changes: 2 additions & 0 deletions skills/devloop-review/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
name: devloop-review
description: Use this skill when reviewing a devloop implementation pass against a spec, track, diff, prior reviews, acceptance criteria, or engineering quality gates. Decide ACCEPT, REJECT, or UNCLEAR with concrete evidence and fix instructions.
metadata:
devloop-managed: "true"
---

# Devloop Review
Expand Down
4 changes: 4 additions & 0 deletions skills/devloop-spec/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
name: devloop-spec
description: Use this skill when the user wants a devloop-ready implementation spec from a rough idea, notes, file, URL, issue, research, or conversation context. Interview from a cold start when source material is too thin; otherwise distill the provided material into one concrete spec.
metadata:
devloop-managed: "true"
---

# Devloop Spec
Expand All @@ -12,6 +14,8 @@ Produce one implementation spec that conforms to the devloop standard. This skil

The output is the spec that `devloop` will use as its implementation input.

When a starter document is needed, read `references/spec-template.md`.

## Scope Guard

Write exactly one spec sized for one worktree and one PR. Push back before drafting when the request mixes multiple logical changes, depends on an unresolved preparatory refactor, or would plausibly exceed about 300 meaningful changed lines.
Expand Down
File renamed without changes.
Loading