Skip to content

Commit f654757

Browse files
committed
feat: v0.4.0 — exec, daemon, mission, agents, snapshots, Waza workflows
New CLI commands: exec (non-interactive with JSON/worktree/agent support), daemon (background HTTP server with SSE), mission (multi-agent parallel orchestration), search (full-text session search), agent (persona management), snapshot (granular file undo). New slash commands: /think (planning), /hunt (debugging), /check (pre-ship review), /design (UI iteration), /snapshot (list/restore/diff). Engine enhancements: structured compaction template, doom loop escalation (threshold 3), auto-snapshot on file writes, session persistence for exec. New packages: mission/, daemon/, agents/, snapshot/. Inspired by Factory Droid, OpenCode, and Waza.
1 parent b4ce723 commit f654757

30 files changed

Lines changed: 3354 additions & 26 deletions

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,30 @@
11
# Changelog
22

3+
## [0.4.0] — 2026-05-05
4+
5+
### Added
6+
- **Exec Subcommand**: `hawk exec "prompt"` — full engine non-interactive mode with `--output-format json`, `--auto` autonomy levels, `--worktree` isolation, `--agent` personas, `--session-id` resume, stdin piping
7+
- **Daemon Server**: `hawk daemon start/stop/status` — background HTTP server with JSON + SSE streaming on `/v1/chat`, `/v1/health`, `/v1/sessions`
8+
- **Mission Mode**: `hawk mission "prompt"` — multi-agent orchestration decomposing work into parallel features executed in isolated git worktrees. `--dry-run` for planning only
9+
- **Session Search**: `hawk search "query"` — full-text search across all saved sessions with `--json` output
10+
- **Custom Agents**: `hawk agent list/create/show/remove` — markdown persona definitions in `~/.hawk/agents/` with YAML frontmatter (name, description, model)
11+
- **Snapshot System**: Shadow git tracking of every file change. `hawk snapshot list/restore/diff` + `/snapshot` slash command. Auto-snapshots on every Write/Edit tool call
12+
- **Waza Workflows**: `/think` (plan before code), `/hunt` (root-cause diagnosis), `/check` (pre-ship review with auto-fix), `/design` (screenshot-driven UI iteration)
13+
- **Structured Compaction**: Summary template with Goal/Constraints/Progress/Files/Decisions/Errors/Instructions/Next sections for better intent preservation
14+
- **Doom Loop Detection**: Lowered threshold to 3 (from 4). Two-tier escalation: first detection injects redirect prompt, doom loop hard-stops with "ask user for help"
15+
- **Session Persistence for exec**: All exec runs saved to `~/.hawk/sessions/` and searchable via `hawk search`
16+
17+
### Packages Added
18+
- `mission/` — Multi-agent orchestration with worktree-based parallel workers
19+
- `daemon/` — HTTP server with session factory, SSE streaming, PID file management
20+
- `agents/` — Markdown persona loader with frontmatter parsing
21+
- `snapshot/` — Shadow git repository for granular file-level undo
22+
23+
### Inspired By
24+
- Factory Droid v0.117.0 (exec mode, daemon, mission orchestration, custom agents)
25+
- OpenCode (structured compaction, snapshot system, doom loop escalation)
26+
- Waza by tw93 (engineering-habit workflows: think, hunt, check, design)
27+
328
## [0.3.0] — 2026-05-03
429

530
### Added

README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,17 @@ hawk --fork-session -r abc123 # Resume as new session
5959
hawk --mcp "npx @mcp/server" # Connect MCP server
6060
hawk -p "fix tests" --allowed-tools "Bash(go test:*) Edit Read"
6161
hawk -p "plan only" --permission-mode plan --tools "Read,Grep,Glob"
62+
hawk exec "fix all tests" # Non-interactive with full engine
63+
hawk exec --auto full -o json "add error handling"
64+
hawk exec --worktree "refactor auth module"
65+
hawk exec --agent reviewer "review last commit"
66+
hawk daemon start # Background HTTP server
67+
hawk daemon status # Check if daemon running
68+
hawk mission "add auth, rate limiting, and tests"
69+
hawk mission --dry-run "decompose this into features"
70+
hawk search "authentication" # Search across all sessions
71+
hawk agent list # List custom personas
72+
hawk agent create debugger # Create new persona
6273
hawk doctor # Run diagnostics
6374
hawk config # Show effective settings
6475
hawk sessions # List saved sessions
@@ -164,6 +175,10 @@ Core commands:
164175
| `/review` | Ask hawk to review changes |
165176
| `/test` | Run project tests |
166177
| `/lint` | Run linter |
178+
| `/think <topic>` | Plan before coding (Waza method) |
179+
| `/hunt <symptom>` | Root-cause diagnosis before fixing (Waza method) |
180+
| `/check` | Pre-ship review with auto-fix (Waza method) |
181+
| `/design <goal>` | Screenshot-driven UI iteration (Waza method) |
167182
| `/bughunter` | Hunt for bugs |
168183
| `/security-review` | Review security risks |
169184
| `/power <level>` | Set power level (1-10) |
@@ -237,13 +252,83 @@ hooks/ Event hook system with decision hooks
237252
analytics/ Session traces, activity tracking, cost optimization, model cascade
238253
trace/ Built-in tracer + optional OTel SDK (build tag)
239254
sandbox/ Command isolation (namespace, docker, chroot, seatbelt, landlock, seccomp)
255+
mission/ Multi-agent orchestration (parallel feature execution in worktrees)
256+
daemon/ Background HTTP server (JSON + SSE streaming)
257+
agents/ Custom agent persona loader (markdown + frontmatter)
258+
parallel/ Worktree-based parallel task execution
240259
retry/ Exponential backoff with jitter
241260
circuit/ Circuit breaker (closed/open/half-open)
242261
ratelimit/ Token bucket rate limiting
243262
```
244263

245264
Zero CGO. Single static binary. Cross-compiled for linux/darwin/windows amd64/arm64.
246265

266+
## Exec Mode
267+
268+
Run hawk non-interactively for scripting and CI:
269+
270+
```bash
271+
hawk exec "fix all failing tests"
272+
hawk exec --auto full "refactor to use context"
273+
hawk exec --output-format json "list all TODO comments"
274+
hawk exec --worktree "add rate limiting" # isolated branch
275+
hawk exec --agent reviewer "review the last 3 commits"
276+
hawk exec -s exec-123456 "continue from last time" # resume session
277+
echo "explain main.go" | hawk exec - # stdin
278+
```
279+
280+
## Mission Mode
281+
282+
Decompose work into parallel features:
283+
284+
```bash
285+
hawk mission "Add auth, rate limiting, and logging"
286+
hawk mission --workers 6 --model claude-sonnet-4-6 "Refactor into microservices"
287+
hawk mission --dry-run "What would this decompose into?"
288+
```
289+
290+
Each feature runs in its own git worktree with full autonomy. Results are committed on separate branches.
291+
292+
## Daemon
293+
294+
Background HTTP server for programmatic access:
295+
296+
```bash
297+
hawk daemon start # Start on port 4590
298+
hawk daemon start --port 8080 # Custom port
299+
hawk daemon status # Check if running
300+
hawk daemon stop # Graceful shutdown
301+
```
302+
303+
Endpoints:
304+
- `GET /v1/health` — server status
305+
- `POST /v1/chat` — send prompt, get response (JSON or SSE streaming)
306+
- `GET /v1/sessions` — list active sessions
307+
308+
SSE streaming: set `Accept: text/event-stream` header.
309+
310+
## Custom Agents
311+
312+
Define reusable personas in `~/.hawk/agents/`:
313+
314+
```bash
315+
hawk agent create reviewer -d "Security-focused code reviewer"
316+
hawk agent list
317+
hawk agent show reviewer
318+
hawk exec --agent reviewer "check the auth module"
319+
```
320+
321+
Agent files are markdown with YAML frontmatter:
322+
323+
```markdown
324+
---
325+
name: reviewer
326+
description: Security-focused code reviewer
327+
model: claude-sonnet-4-6
328+
---
329+
You are a security expert. Focus on injection, auth bypass, and secrets.
330+
```
331+
247332
## AGENTS.md
248333

249334
Create an `AGENTS.md` in your project root for project-specific instructions (max 10KB). Any AI coding agent can read this — hawk, Claude Code, Cursor, Codex:

agents/agents.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package agents
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
)
9+
10+
// Agent is a user-defined persona with a custom system prompt.
11+
// Stored as markdown files with YAML frontmatter in ~/.hawk/agents/.
12+
type Agent struct {
13+
Name string `json:"name"`
14+
Description string `json:"description"`
15+
Model string `json:"model,omitempty"`
16+
Prompt string `json:"prompt"`
17+
FilePath string `json:"file_path"`
18+
}
19+
20+
// Load reads an agent definition from a markdown file.
21+
// Format:
22+
//
23+
// ---
24+
// name: reviewer
25+
// description: Code review specialist
26+
// model: inherit
27+
// ---
28+
// You are a code reviewer...
29+
func Load(path string) (*Agent, error) {
30+
data, err := os.ReadFile(path)
31+
if err != nil {
32+
return nil, err
33+
}
34+
return Parse(string(data), path)
35+
}
36+
37+
// Parse extracts agent metadata and prompt from markdown content.
38+
func Parse(content, filePath string) (*Agent, error) {
39+
content = strings.TrimSpace(content)
40+
if !strings.HasPrefix(content, "---") {
41+
return nil, fmt.Errorf("agent file must start with --- frontmatter")
42+
}
43+
44+
// Find closing ---
45+
rest := content[3:]
46+
endIdx := strings.Index(rest, "\n---")
47+
if endIdx < 0 {
48+
return nil, fmt.Errorf("agent file missing closing --- for frontmatter")
49+
}
50+
51+
frontmatter := rest[:endIdx]
52+
body := strings.TrimSpace(rest[endIdx+4:])
53+
54+
agent := &Agent{
55+
Prompt: body,
56+
FilePath: filePath,
57+
}
58+
59+
for _, line := range strings.Split(frontmatter, "\n") {
60+
line = strings.TrimSpace(line)
61+
if line == "" {
62+
continue
63+
}
64+
key, val, ok := parseYAMLLine(line)
65+
if !ok {
66+
continue
67+
}
68+
switch key {
69+
case "name":
70+
agent.Name = val
71+
case "description":
72+
agent.Description = val
73+
case "model":
74+
if val != "inherit" {
75+
agent.Model = val
76+
}
77+
}
78+
}
79+
80+
if agent.Name == "" {
81+
base := filepath.Base(filePath)
82+
agent.Name = strings.TrimSuffix(base, filepath.Ext(base))
83+
}
84+
85+
return agent, nil
86+
}
87+
88+
// ListAll discovers all agent definitions from the standard directories.
89+
// Search order: ~/.hawk/agents/, .hawk/agents/ (project-local).
90+
func ListAll() ([]*Agent, error) {
91+
var agents []*Agent
92+
93+
dirs := agentDirs()
94+
for _, dir := range dirs {
95+
entries, err := os.ReadDir(dir)
96+
if err != nil {
97+
continue
98+
}
99+
for _, e := range entries {
100+
if e.IsDir() || !strings.HasSuffix(e.Name(), ".md") {
101+
continue
102+
}
103+
a, err := Load(filepath.Join(dir, e.Name()))
104+
if err != nil {
105+
continue
106+
}
107+
agents = append(agents, a)
108+
}
109+
}
110+
return agents, nil
111+
}
112+
113+
// Get finds an agent by name from all known directories.
114+
func Get(name string) (*Agent, error) {
115+
agents, err := ListAll()
116+
if err != nil {
117+
return nil, err
118+
}
119+
for _, a := range agents {
120+
if a.Name == name {
121+
return a, nil
122+
}
123+
}
124+
return nil, fmt.Errorf("agent %q not found", name)
125+
}
126+
127+
// DefaultDir returns the user's agent directory (~/.hawk/agents/).
128+
func DefaultDir() string {
129+
home, _ := os.UserHomeDir()
130+
return filepath.Join(home, ".hawk", "agents")
131+
}
132+
133+
// agentDirs returns the list of directories to search for agents.
134+
func agentDirs() []string {
135+
dirs := []string{DefaultDir()}
136+
137+
// Project-local agents
138+
if cwd, err := os.Getwd(); err == nil {
139+
localDir := filepath.Join(cwd, ".hawk", "agents")
140+
if info, err := os.Stat(localDir); err == nil && info.IsDir() {
141+
dirs = append(dirs, localDir)
142+
}
143+
}
144+
145+
return dirs
146+
}
147+
148+
func parseYAMLLine(line string) (key, val string, ok bool) {
149+
idx := strings.Index(line, ":")
150+
if idx < 0 {
151+
return "", "", false
152+
}
153+
key = strings.TrimSpace(line[:idx])
154+
val = strings.TrimSpace(line[idx+1:])
155+
// Strip surrounding quotes
156+
if len(val) >= 2 {
157+
if (val[0] == '"' && val[len(val)-1] == '"') ||
158+
(val[0] == '\'' && val[len(val)-1] == '\'') {
159+
val = val[1 : len(val)-1]
160+
}
161+
}
162+
// Handle multi-line YAML >- by collapsing (simplified)
163+
val = strings.TrimPrefix(val, ">-")
164+
val = strings.TrimSpace(val)
165+
return key, val, true
166+
}

0 commit comments

Comments
 (0)