diff --git a/AGENTS.md b/AGENTS.md index 481d82f..a412f1c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,118 +1,41 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +# Lorah ## Project Overview -Lorah is a simple infinite-loop harness for long-running autonomous coding agents. It runs Claude Code CLI in a continuous loop, parsing stream-JSON output and formatting it nicely. The agent manages its own workflow - Lorah just provides the loop, error recovery, and output formatting. Follows the [Ralph pattern](https://ghuntley.com/ralph/). Distributed as a single self-contained binary with no external runtime dependencies. +Lorah is a simple infinite-loop harness for long-running autonomous coding agents. It runs Claude Code CLI in a continuous loop, parsing stream-JSON output and formatting it nicely. Includes a task management system for structured agent workflow coordination. The agent manages its own workflow — Lorah just provides the loop, error recovery, output formatting, and task tracking. Follows the Ralph pattern. Distributed as a single self-contained binary with no external runtime dependencies. ## Commands ```bash -# Build binary -go build -o ./bin/lorah . - -# Install globally -go install . - -# Run Lorah -lorah PROMPT.md [claude-flags...] +make build # Build binary +go run . run PROMPT.md # Development run +lorah run PROMPT.md [flags...] # Run loop (all flags after prompt passed to claude CLI) +lorah task [args...] # Task management ``` -All arguments after `PROMPT.md` are passed directly to Claude CLI: - -```bash -# With settings file -lorah PROMPT.md --settings .lorah/settings.json - -# With specific model -lorah PROMPT.md --model claude-opus-4-6 - -# With multiple flags -lorah PROMPT.md --settings settings.json --max-turns 50 --verbose -``` - -For development: - -```bash -go run . PROMPT.md [claude-flags...] -``` - -No tests currently - the implementation is simple enough to verify manually. No linter or formatter beyond `gofmt`. +Use TDD — write tests before implementation. Use `make fmt` and `make lint`. ## Architecture -Lorah is a single-file Go program that implements a simple infinite loop: - ``` -1. Execute: claude -p --output-format=stream-json --verbose [user-flags...] PROMPT.md -2. Parse stream-JSON line-by-line from stdout -3. Format output with color-coded sections and tool activity -4. On error: print error, sleep 5s, retry -5. On success: continue immediately to next iteration -6. Repeat forever until Ctrl+C +main.go CLI router: subcommand dispatch, help text, version +internal/loop/ + loop.go Run() entry point, signal handling, infinite loop + claude.go Subprocess execution + output.go Stream-JSON parsing and formatted output + constants.go ANSI colors, buffer size, retry delay +internal/task/ + task.go Core types: Phase, Section, Task, TaskStatus, TaskList, Filter + storage.go Storage interface + json_storage.go JSONStorage implementation (tasks.json) + format.go Output formatters: json, markdown + cmd.go CLI subcommand handlers +docs/design/ Design specifications (authoritative reference) ``` -### File Structure - -``` -main.go Single file containing everything - - Constants ANSI color codes, retry delay - - Message Types stream-JSON message/content block structs - - Main Loop Infinite execution loop with signal handling - - Claude Exec Build command, pipe I/O, run subprocess - - Message Parsing Parse newline-delimited JSON into typed structs - - Output Format Color-coded section headers, tool activity display -go.mod Go module definition -go.sum Dependency checksums (none - stdlib only) -``` - -### Main Sections - -**Constants** — ANSI color codes for terminal output and retry delay configuration. - -**Helper Functions** — `printSection()` outputs labeled sections with color formatting. - -**Main Loop** — Entry point: - -- Parses CLI args: `PROMPT.md` and optional Claude CLI flags -- Handles `--version` and `--help` flags -- Sets up signal handler for graceful Ctrl+C shutdown -- Runs infinite loop calling `runClaude()` with error recovery - -**Claude Execution** — `runClaude()` function: - -- Opens prompt file and pipes it to stdin -- Builds `claude` command with `-p`, `--output-format=stream-json`, `--verbose`, and user flags -- Streams stdout to `printMessages()` for real-time output -- Returns error if command fails - -**Output Formatting** — `printMessages()` function: - -- Scans stdout line-by-line (newline-delimited JSON) -- Parses each line as `map[string]any` for flexibility -- Gracefully skips malformed/unknown message types for forward compatibility -- Displays color-coded section headers: `==> Claude`, `==> Lorah`, `==> Error` -- Shows tool activity: `==> Bash`, `==> Read`, etc. with relevant input displayed -- Extracts relevant input params (command, file_path, pattern, url, etc.) based on tool name -- Truncates long tool inputs to 3 lines for readability - -## Key Patterns - -- **Single file** — Entire program in one `main.go` file. No packages, no navigation overhead. Readable in one sitting. -- **No configuration** — Zero config files. Prompts are plain markdown. Claude CLI flags passed through directly. -- **Ralph pattern** — Simple infinite loop. Agent decides workflow. Harness provides loop + nice output. Inspired by [Geoffrey Huntley's Ralph](https://ghuntley.com/ralph/). -- **Agent-driven** — No phase orchestration. Agent reads codebase, decides what to do next, makes progress iteratively. -- **Filesystem as state** — No session files. Git commits show progress. Agent reads files to understand context. -- **Flag passthrough** — All args after `PROMPT.md` passed directly to `claude` CLI. User has full control. -- **CLI-native security** — Security enforced through Claude CLI `--settings` flag (sandbox, permissions). See [Claude Code Settings](https://code.claude.com/docs/en/settings). -- **Stream processing** — stdout parsed line-by-line as newline-delimited JSON; unknown types skipped gracefully for forward compatibility. -- **Simple error handling** — Fixed 5-second retry delay on errors. No exponential backoff complexity. -- **Color-coded output** — Section headers and tool activity formatted with ANSI colors for readability. - ## Design Principles -**Ralph Philosophy**: The agent is smart enough to manage its own workflow. Don't orchestrate - provide a simple loop and trust the model. +**Ralph Philosophy**: The agent is smart enough to manage its own workflow. Don't orchestrate — provide a simple loop and trust the model. **Radical Simplicity**: Every line of code is overhead. The simplest solution that works is the best solution. Prefer deleting code over adding it. @@ -120,13 +43,10 @@ go.sum Dependency checksums (none - stdlib only) **No Ceremony**: No config files, session state, lock files, or scaffolding commands. Just a prompt file and a loop. -Required reading: +**Filesystem as State**: No session files. Git commits show progress. Agent reads files to understand context. -- [Ralph by Geoffrey Huntley](https://ghuntley.com/ralph/) - The pattern this implementation follows -- [Building Effective Agents](https://www.anthropic.com/research/building-effective-agents) - Agent design principles from Anthropic -- [Agent SDK Overview](https://platform.claude.com/docs/en/agent-sdk/overview) - Understanding agent capabilities -- [Claude Code Sandboxing](https://www.anthropic.com/engineering/claude-code-sandboxing) - Security model +**Design Specifications**: Authoritative design docs live in `docs/design/`. When in doubt about intended behavior, consult the specs: `cli.md`, `run.md`, `output.md`, `task.md`. ## Dependencies -No external runtime dependencies. All functionality uses the Go standard library. The `claude` CLI (separate install) is the only runtime requirement for executing agent sessions. +No external runtime dependencies. All functionality uses the Go standard library. The `claude` CLI (separate install) is the only runtime requirement. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c72a4e..30ea1c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Subcommand-based CLI: `lorah [arguments]` with `run` and `task` commands +- Task management system (`lorah task`) with 6 subcommands: list, get, create, update, delete, export +- JSON-based task storage in `tasks.json` with Storage interface for future backend swaps +- Two task output formats: json, markdown (default); `--flat` flag on `list` for flat bullet output +- Loop status messages now include iteration number + +### Changed + +- **BREAKING**: CLI changed to `lorah run [claude-flags...]` (was `lorah [claude-flags...]`) +- Removed color from Claude text and thinking output sections + +### Fixed + +- Removed extra newline before loop error output +- Tool name matching now uses prefix match for Task tool names +- Scanner errors are now handled gracefully instead of being silently ignored + ## [0.4.0] - 2026-03-09 ### Changed @@ -25,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Infinite loop execution following [Ralph pattern](https://ghuntley.com/ralph/) -- Direct flag passthrough to Claude CLI +- Direct flag passthrough to Claude Code CLI ### Changed @@ -42,7 +61,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- **BREAKING**: Configuration format changed to a split structure with `harness` (lorah settings) and `claude` (passthrough to Claude CLI) sections. Existing configs require migration—run `lorah info template` to see the new format. +- **BREAKING**: Configuration format changed to a split structure with `harness` (lorah settings) and `claude` (passthrough to Claude Code CLI) sections. Existing configs require migration—run `lorah info template` to see the new format. - Updated documentation to document two-phase execution model with fixed file names (initialization and implementation phases) - Renamed `docs/setup-guide.md` to `docs/getting-started.md` - Renamed review workflow prompts from `inventory.md`/`fix.md` to `initialization.md`/`implementation.md` diff --git a/Makefile b/Makefile index 02c37ec..9bcdb58 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help build clean fmt lint install +.PHONY: help build clean fmt lint test # Default target - show help help: @@ -9,10 +9,10 @@ help: @echo "Targets:" @echo " help Show this help message" @echo " build Build the lorah binary" - @echo " install Install lorah to GOPATH/bin" @echo " clean Remove build artifacts" @echo " fmt Format all Go code" @echo " lint Run go vet for static analysis" + @echo " test Run all tests" # Build the lorah binary build: @@ -21,12 +21,6 @@ build: echo "Building lorah $$VERSION..."; \ go build -ldflags "-X 'main.Version=$$VERSION'" -o ./bin/lorah . -# Install lorah to GOPATH/bin -install: - @VERSION=$$(date -u '+dev+%Y%m%d%H%M%S'); \ - echo "Installing lorah $$VERSION..."; \ - go install -ldflags "-X 'main.Version=$$VERSION'" . - # Clean build artifacts clean: rm -rf ./bin @@ -45,3 +39,7 @@ fmt: lint: @echo "Running go vet..." go vet ./... + +# Run all tests +test: + go test ./... diff --git a/README.md b/README.md index 44ee07c..ff6a9e9 100644 --- a/README.md +++ b/README.md @@ -22,20 +22,22 @@ But you get raw `stream-json` output that's unreadable: **Lorah gives you clean, color-coded output:** ``` -==> Claude +⏺ Claude Let me read the file -==> Read + +⏺ Read /path/to/file ``` -Plus automatic error recovery, graceful shutdown, and full Claude CLI compatibility. +Plus automatic error recovery, graceful shutdown, and full Claude Code CLI compatibility. **Key Features:** - **Formatted output** - Color-coded sections and tool activity (the main reason Lorah exists) - **Simple infinite loop** - Runs continuously until you stop it - **Automatic error recovery** - Retries on failures with 5-second delay -- **Flag passthrough** - All Claude CLI flags work transparently +- **Flag passthrough** - All Claude Code CLI flags work transparently +- **Task management** - Structured task tracking for agent workflow coordination ## Prerequisites @@ -49,22 +51,33 @@ brew install cpplain/tap/lorah ## Usage -Lorah is an implementatioin of the Ralph loop. You must understand the Ralph technique to use Lorah effectively. +Lorah is an implementation of the Ralph loop. You must understand the Ralph technique to use Lorah effectively. Learn more about the Ralph technique: [Ralph Wiggum as a "software engineer"](https://ghuntley.com/ralph/) by Geoffrey Huntley **Syntax:** ```bash -lorah [claude-flags...] +lorah [arguments] +``` + +**Run loop:** + +```bash +lorah run PROMPT.md +lorah run PROMPT.md --settings .lorah/settings.json +lorah run PROMPT.md --model claude-opus-4-6 --max-turns 50 ``` -**Examples:** +**Task management:** ```bash -lorah PROMPT.md -lorah PROMPT.md --settings .lorah/settings.json -lorah PROMPT.md --model claude-opus-4-6 --max-turns 50 +lorah task list --status=pending +lorah task get +lorah task create --subject="Fix auth bug" +lorah task update --status=completed +lorah task delete +lorah task export ``` ## License diff --git a/docs/design/README.md b/docs/design/README.md new file mode 100644 index 0000000..a3d790f --- /dev/null +++ b/docs/design/README.md @@ -0,0 +1,30 @@ +# Lorah Specifications + +Design documentation for `lorah`, a simple infinite-loop harness for Claude Code. + +## Index + +| Spec | Description | +| ---------------------- | ------------------------------------------------------------------------------ | +| [cli.md](cli.md) | Command-line interface: subcommands, routing, flags, help, version, exit codes | +| [run.md](run.md) | `run` command: loop lifecycle, signal handling, retry, subprocess execution | +| [output.md](output.md) | Output system: stream-JSON parsing, color-coded formatting, tool display | +| [task.md](task.md) | `task` command: CRUD subcommands, JSON storage, agent integration | + +## Glossary + +| Term | Definition | +| ------------------ | ------------------------------------------------------------------------------------------ | +| **prompt file** | A markdown file containing instructions for the Claude Code agent, piped to `claude` stdin | +| **loop iteration** | One complete execution of the Claude Code CLI subprocess from start to finish | +| **stream-JSON** | Newline-delimited JSON output by Claude Code CLI's `--output-format=stream-json` flag | +| **claude flags** | Arguments passed through to the `claude` CLI unchanged after the prompt file | +| **task file** | JSON file (`tasks.json`) storing structured task data for agent workflow management | + +## Design Principles + +- **Ralph Philosophy**: the agent is smart enough to manage its own workflow; the harness provides the loop and nice output, nothing more +- **Radical Simplicity**: the simplest solution that works is the best solution; prefer deleting code over adding it +- **Agent in Control**: the harness provides the loop and output; the agent reads the codebase, decides what to do, and makes progress +- **No Ceremony**: no config files, session state, lock files, or scaffolding commands +- **No External Dependencies**: stdlib only; `claude` CLI is the only runtime requirement diff --git a/docs/design/cli.md b/docs/design/cli.md new file mode 100644 index 0000000..3771daf --- /dev/null +++ b/docs/design/cli.md @@ -0,0 +1,230 @@ +# CLI Specification + +--- + +## 1. Overview + +### Purpose + +`lorah` uses a subcommand-based CLI. The first argument selects the operation. +All routing is a stdlib `switch` statement with no external dependencies. +`main.go` contains only routing logic — no business logic lives there. + +### Goals + +- **Subcommand-based**: `lorah [args...]` with unambiguous routing +- **Thin router**: `main.go` is ~60 lines of routing only +- **Flag passthrough**: `run` passes all trailing args to `claude` CLI unchanged +- **Helpful on error**: unknown commands print error + usage; missing args explain correct usage + +### Non-Goals + +- External flag parsing library (no cobra, no pflag) +- Shell completion +- Typo suggestion for unknown commands + +--- + +## 2. Interface + +### Usage + +``` +lorah [arguments] +``` + +### Commands + +| Command | Arguments | Description | +| ------- | --------------------------------- | --------------------------------------- | +| `run` | ` [claude-flags...]` | Run Claude Code CLI in an infinite loop | +| `task` | ` [args...]` | Manage tasks | + +### Top-Level Flags + +| Flag | Short | Description | +| ----------- | ---------------- | ------------------------------- | +| `--version` | `-V`, `-version` | Print version and exit 0 | +| `--help` | `-h`, `-help` | Show top-level usage and exit 0 | + +Top-level flags are only recognized as `os.Args[1]`. They are not parsed anywhere else. + +--- + +## 3. Behavior + +### Routing Rules + +1. No arguments → print top-level usage, exit 1 +2. `--version`, `-version`, `-V` → print `lorah `, exit 0 +3. `--help`, `-help`, `-h` → print top-level usage, exit 0 +4. `run` → dispatch to `runCmd` with `os.Args[2:]` +5. `task` → dispatch to `taskCmd` with `os.Args[2:]` +6. Anything else → print `Unknown command: ` + top-level usage to stderr, exit 1 + +### Version Output + +``` +lorah +``` + +`Version` is `"dev"` by default; injected at build time via `-ldflags '-X main.Version=...'`. + +### Top-Level Usage (`lorah` or `lorah --help`) + +``` +Usage: lorah [arguments] + +Simple infinite-loop harness for Claude Code. + +Commands: + run Run Claude Code CLI in an infinite loop + task Manage tasks + +Flags: + -V, --version Print version and exit + -h, --help Show this help message + +Run 'lorah --help' for command-specific help. +``` + +### Run Command Usage (`lorah run --help`) + +``` +Usage: lorah run [claude-flags...] + +Run Claude Code CLI in a continuous loop with formatted output. +Retries automatically on error with a 5-second delay. + +Arguments: + Path to prompt file (required) + [claude-flags...] Flags passed directly to claude CLI + +Examples: + lorah run prompt.md + lorah run task.txt --settings .lorah/settings.json + lorah run instructions.md --model claude-opus-4-6 --max-turns 50 + +Flags: + -h, --help Show this help message +``` + +### Task Command Usage (`lorah task --help`) + +``` +Usage: lorah task [args...] [flags...] + +Manage tasks stored in tasks.json. + +Subcommands: + list List tasks + get Get task details + create Create a new task + update Update a task + delete Delete a task + export Export tasks to markdown + +Flags: + -h, --help Show this help message + +Run 'lorah task --help' for subcommand-specific help. +``` + +--- + +## 4. Router Implementation + +### `main.go` Structure + +```go +var Version = "dev" + +func main() // subcommand switch on os.Args[1] +func runCmd(args []string) // extract prompt file + claude flags, call loop.Run() +func taskCmd(args []string) // dispatch to task subcommand handler +func printUsage() // top-level help text +func printRunUsage() // run-specific help text +func printTaskUsage() // task-specific help text +``` + +### What Lives in `main.go` + +- `var Version` — ldflags target must be in package `main` +- `main()` — the subcommand switch only +- `runCmd()`, `taskCmd()` — argument validation and dispatch +- `printUsage()`, `printRunUsage()`, `printTaskUsage()` — help text + +Nothing else. No signal handling, no loop logic, no output formatting, no task logic. + +### Flag Parsing + +Top-level routing uses a stdlib `switch` on `os.Args[1]`. Subcommand flag parsing +(e.g., `--status=pending`, `--format=markdown`) uses `flag.NewFlagSet` — also stdlib. +No external flag parsing library is used anywhere in the codebase. + +### `runCmd` Behavior + +```go +func runCmd(args []string) +``` + +1. If `len(args) == 0` → print run usage, exit 1 +2. If `args[0]` is `--help`, `-help`, or `-h` → print run usage, exit 0 +3. `promptFile = args[0]`, `claudeFlags = args[1:]` +4. Call `loop.Run(promptFile, claudeFlags)` + +### `taskCmd` Behavior + +```go +func taskCmd(args []string) +``` + +1. If `len(args) == 0` → print task usage, exit 1 +2. If `args[0]` is `--help`, `-help`, or `-h` → print task usage, exit 0 +3. Dispatch on `args[0]` (subcommand name) to the appropriate handler in `internal/task` +4. Unknown subcommand → print `Unknown subcommand: ` + task usage to stderr, exit 1 + +--- + +## 5. Exit Codes + +| Code | Meaning | +| ---- | ----------------------------------------- | +| 0 | Success (including `--version`, `--help`) | +| 1 | Runtime error or usage error | + +--- + +## 6. Examples + +```sh +# Version and help +lorah --version +lorah --help + +# Run command +lorah run prompt.md +lorah run prompt.md --settings .lorah/settings.json +lorah run prompt.md --model claude-opus-4-6 --max-turns 50 +lorah run --help + +# Task command +lorah task list +lorah task list --status=pending +lorah task get 1 +lorah task update 1 --status=completed --notes="Done" +lorah task --help + +# Error cases +lorah unknown # Unknown command: unknown +lorah run # shows run usage, exits 1 +lorah task # shows task usage, exits 1 +``` + +--- + +## 7. Related Specifications + +- [run.md](run.md) — `run` command behavior and loop lifecycle +- [task.md](task.md) — `task` subcommand behavior and storage +- [output.md](output.md) — output formatting used by `run` diff --git a/docs/design/output.md b/docs/design/output.md new file mode 100644 index 0000000..5d9c60b --- /dev/null +++ b/docs/design/output.md @@ -0,0 +1,196 @@ +# Output Specification + +--- + +## 1. Overview + +### Purpose + +The output system parses Claude Code CLI's stream-JSON stdout and displays color-coded, +human-readable sections in real-time as each line arrives. + +### Goals + +- **Real-time**: output displayed as stream-JSON lines arrive, not buffered to end +- **Color-coded**: section headers colored by source for visual scanning +- **Forward-compatible**: unknown message types and block types silently skipped +- **Truncated**: multi-line tool inputs condensed to first line plus remaining count + +### Non-Goals + +- Machine-readable (JSON) output from Lorah itself +- Log files or output persistence +- Configurable color themes or color disabling + +--- + +## 2. Color Scheme + +| Constant | ANSI Code | Use | +| ------------ | ---------- | -------------------- | +| `colorReset` | `\033[0m` | Reset all formatting | +| `colorGreen` | `\033[32m` | Tool section icon | +| `colorBlue` | `\033[34m` | Lorah section icon | +| `colorBold` | `\033[1m` | Section label text | +| `colorRed` | `\033[31m` | Error section icon | + +--- + +## 3. Section Format + +### Signature + +```go +func printSection(label, color, content string) +``` + +### Output Template + +``` +