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
62 changes: 23 additions & 39 deletions cmd/chief/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func findAvailablePRD() string {

for _, entry := range entries {
if entry.IsDir() {
prdPath := filepath.Join(prdsDir, entry.Name(), "prd.json")
prdPath := filepath.Join(prdsDir, entry.Name(), "prd.md")
if _, err := os.Stat(prdPath); err == nil {
return prdPath
}
Expand All @@ -113,7 +113,7 @@ func listAvailablePRDs() []string {
var names []string
for _, entry := range entries {
if entry.IsDir() {
prdPath := filepath.Join(prdsDir, entry.Name(), "prd.json")
prdPath := filepath.Join(prdsDir, entry.Name(), "prd.md")
if _, err := os.Stat(prdPath); err == nil {
names = append(names, entry.Name())
}
Expand Down Expand Up @@ -241,11 +241,11 @@ func parseTUIFlags() *TUIOptions {
os.Exit(1)
default:
// Positional argument: PRD name or path
if strings.HasSuffix(arg, ".json") || strings.HasSuffix(arg, "/") {
if strings.HasSuffix(arg, ".md") || strings.HasSuffix(arg, ".json") || strings.HasSuffix(arg, "/") {
opts.PRDPath = arg
} else {
// Treat as PRD name
opts.PRDPath = fmt.Sprintf(".chief/prds/%s/prd.json", arg)
opts.PRDPath = fmt.Sprintf(".chief/prds/%s/prd.md", arg)
}
}
}
Expand Down Expand Up @@ -282,18 +282,11 @@ func runNew() {
func runEdit() {
opts := cmd.EditOptions{}

// Parse arguments: chief edit [name] [--merge] [--force] [--agent X] [--agent-path X]
// Parse arguments: chief edit [name] [--agent X] [--agent-path X]
flagAgent, flagPath, remaining := parseAgentFlags(os.Args, 2)
for _, arg := range remaining {
switch {
case arg == "--merge":
opts.Merge = true
case arg == "--force":
opts.Force = true
default:
if opts.Name == "" && !strings.HasPrefix(arg, "-") {
opts.Name = arg
}
if opts.Name == "" && !strings.HasPrefix(arg, "-") {
opts.Name = arg
}
}

Expand Down Expand Up @@ -368,7 +361,7 @@ func runTUIWithOptions(opts *TUIOptions) {
// If no PRD specified, try to find one
if prdPath == "" {
// Try "main" first
mainPath := ".chief/prds/main/prd.json"
mainPath := ".chief/prds/main/prd.md"
if _, err := os.Stat(mainPath); err == nil {
prdPath = mainPath
} else {
Expand Down Expand Up @@ -411,30 +404,23 @@ func runTUIWithOptions(opts *TUIOptions) {
}

// Restart TUI with the new PRD
opts.PRDPath = fmt.Sprintf(".chief/prds/%s/prd.json", result.PRDName)
opts.PRDPath = fmt.Sprintf(".chief/prds/%s/prd.md", result.PRDName)
runTUIWithOptions(opts)
return
}
}

prdDir := filepath.Dir(prdPath)

// Check if prd.md is newer than prd.json and run conversion if needed
needsConvert, err := prd.NeedsConversion(prdDir)
if err != nil {
fmt.Printf("Warning: failed to check conversion status: %v\n", err)
} else if needsConvert {
fmt.Println("prd.md is newer than prd.json, running conversion...")
if err := cmd.RunConvertWithOptions(cmd.ConvertOptions{
PRDDir: prdDir,
Merge: opts.Merge,
Force: opts.Force,
Provider: provider,
}); err != nil {
fmt.Printf("Error converting PRD: %v\n", err)
os.Exit(1)
// Auto-migrate: if prd.json exists alongside prd.md, migrate status
jsonPath := filepath.Join(prdDir, "prd.json")
if _, err := os.Stat(jsonPath); err == nil {
fmt.Println("Migrating status from prd.json to prd.md...")
if err := prd.MigrateFromJSON(prdDir); err != nil {
fmt.Printf("Warning: migration failed: %v\n", err)
} else {
fmt.Println("Migration complete (prd.json renamed to prd.json.bak).")
}
fmt.Println("Conversion complete.")
}

app, err := tui.NewAppWithOptions(prdPath, opts.MaxIterations, provider)
Expand Down Expand Up @@ -492,23 +478,21 @@ func runTUIWithOptions(opts *TUIOptions) {
os.Exit(1)
}
// Restart TUI with the new PRD
opts.PRDPath = fmt.Sprintf(".chief/prds/%s/prd.json", finalApp.PostExitPRD)
opts.PRDPath = fmt.Sprintf(".chief/prds/%s/prd.md", finalApp.PostExitPRD)
runTUIWithOptions(opts)

case tui.PostExitEdit:
// Run edit command then restart TUI
editOpts := cmd.EditOptions{
Name: finalApp.PostExitPRD,
Merge: opts.Merge,
Force: opts.Force,
Provider: provider,
}
if err := cmd.RunEdit(editOpts); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
// Restart TUI with the edited PRD
opts.PRDPath = fmt.Sprintf(".chief/prds/%s/prd.json", finalApp.PostExitPRD)
opts.PRDPath = fmt.Sprintf(".chief/prds/%s/prd.md", finalApp.PostExitPRD)
runTUIWithOptions(opts)
}
}
Expand All @@ -518,7 +502,7 @@ func printHelp() {
fmt.Println(`Chief - Autonomous PRD Agent

Usage:
chief [options] [<name>|<path/to/prd.json>]
chief [options] [<name>|<path/to/prd.md>]
chief <command> [arguments]

Commands:
Expand All @@ -545,13 +529,13 @@ Edit Options:
--force Auto-overwrite on conversion conflicts

Positional Arguments:
<name> PRD name (loads .chief/prds/<name>/prd.json)
<path/to/prd.json> Direct path to a prd.json file
<name> PRD name (loads .chief/prds/<name>/prd.md)
<path/to/prd.md> Direct path to a prd.md file

Examples:
chief Launch TUI with default PRD (.chief/prds/main/)
chief auth Launch TUI with named PRD (.chief/prds/auth/)
chief ./my-prd.json Launch TUI with specific PRD file
chief ./my-prd.md Launch TUI with specific PRD file
chief -n 20 Launch with 20 max iterations
chief --max-iterations=5 auth
Launch auth PRD with 5 max iterations
Expand Down
52 changes: 13 additions & 39 deletions docs/concepts/chief-directory.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ your-project/
├── config.yaml # Project settings (worktree, auto-push, PR)
├── prds/
│ └── my-feature/
│ ├── prd.md # Human-readable PRD (you write this)
│ ├── prd.json # Machine-readable PRD (Chief reads/writes)
│ ├── prd.md # Structured PRD (you write, Chief reads/updates)
│ ├── progress.md # Progress log (Chief appends after each story)
│ └── claude.log # Raw agent output (for debugging)
└── worktrees/ # Isolated checkouts for parallel PRDs
Expand All @@ -45,42 +44,21 @@ Chief uses this folder as the working context for the entire run. All reads and

### `prd.md`

The human-readable product requirements document. You write this file (or generate it with `chief new`). It contains context, background, technical notes, and anything else that helps the agent understand what to build.
The structured product requirements document. You write this file (or generate it with `chief new`). It contains freeform context at the top (background, technical notes, design guidance) and structured user stories that Chief parses and updates.

This file is included in the prompt sent to the agent at the start of each iteration. Write it as if you're briefing a senior developer who's new to the project — the more context you provide, the better the output.
Chief reads this file at the start of each iteration to determine which story to work on, and updates status fields after completing a story. The agent also reads the freeform context to understand what you're building and how.

```markdown
# My Feature

## Background
We need to add user authentication to our API...

## Technical Notes
- We use Express.js with TypeScript
- Database is PostgreSQL with Prisma ORM
- Follow existing middleware patterns in `src/middleware/`
```

### `prd.json`

The structured, machine-readable PRD. This is where user stories, their priorities, and their completion status live. Chief reads this file at the start of each iteration to determine which story to work on, and writes to it after completing a story.

Key fields:
Key story fields (parsed from markdown):

| Field | Type | Description |
|-------|------|-------------|
| `project` | string | Project name |
| `description` | string | Brief project description |
| `userStories` | array | List of user stories |
| `userStories[].id` | string | Story identifier (e.g., `US-001`) |
| `userStories[].title` | string | Short story title |
| `userStories[].description` | string | User story in "As a... I want... so that..." format |
| `userStories[].acceptanceCriteria` | array | List of criteria that must be met |
| `userStories[].priority` | number | Execution order (lower = higher priority) |
| `userStories[].passes` | boolean | Whether the story is complete |
| `userStories[].inProgress` | boolean | Whether Chief is currently working on this story |
| Field | Format | Description |
|-------|--------|-------------|
| ID + Title | `### US-001: Story Title` | Story heading parsed by Chief |
| Status | `**Status:** done\|in-progress\|todo` | Completion state, updated by Chief |
| Priority | `**Priority:** N` | Execution order (lower = higher priority) |
| Description | `**Description:** ...` | Story description |
| Acceptance Criteria | `- [ ]` / `- [x]` | Checkbox items tracked by Chief |

Chief selects the next story by finding the highest-priority story (lowest `priority` number) where `passes` is `false`. See the [PRD Format](/concepts/prd-format) reference for full details.
Chief selects the next story by finding the highest-priority story (lowest `**Priority:**` number) without `**Status:** done`. See the [PRD Format](/concepts/prd-format) reference for full details.

### `progress.md`

Expand Down Expand Up @@ -184,15 +162,12 @@ A single project can have multiple PRDs, each tracking a separate feature or ini
├── prds/
│ ├── auth-system/
│ │ ├── prd.md
│ │ ├── prd.json
│ │ └── progress.md
│ ├── payment-integration/
│ │ ├── prd.md
│ │ ├── prd.json
│ │ └── progress.md
│ └── admin-dashboard/
│ ├── prd.md
│ ├── prd.json
│ └── progress.md
└── worktrees/
├── auth-system/
Expand Down Expand Up @@ -244,8 +219,7 @@ If you want collaborators to see progress and continue where you left off, commi
```

This shares:
- `prd.md`: Your requirements, the source of truth for what to build
- `prd.json`: Story state and progress, so collaborators see what's done
- `prd.md`: Your requirements and story state — the source of truth for what to build and what's done
- `progress.md`: Implementation history and learnings, valuable project context

The `claude.log` files are large, regenerated each run, and only useful for debugging.
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Here's what happens in each step:
2. **Invoke Agent**: Constructs a prompt with the story details and project context, then spawns the agent
3. **Agent Codes**: The agent reads files, writes code, runs tests, and fixes issues until the story is complete
4. **Commit**: The agent commits the changes with a message like `feat: [US-001] - Feature Title`
5. **Mark Complete**: Chief updates the project state and records progress
5. **Mark Complete**: Chief updates the story status in `prd.md` and records progress
6. **Repeat**: If more stories remain, the loop continues

This isolation is intentional. If something breaks, you know exactly which story caused it. Each commit represents one complete feature.
Expand Down
Loading