Files
boshu2__agentops/cli/cmd/ao/quickstart.go
T
2026-04-02 15:11:54 -04:00

373 lines
10 KiB
Go

package main
import (
"bufio"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/spf13/cobra"
)
var quickstartCmd = &cobra.Command{
Use: "quick-start",
Short: "Set up AgentOps in your project (5 minutes)",
Long: `Initialize AgentOps in your current project.
This command:
1. Creates .agents/ directory structure
2. Optionally initializes beads (git-native issues)
3. Creates starter knowledge pack
4. Shows the software-factory operator lane
Examples:
ao quick-start # Full setup with beads
ao quick-start --no-beads # Skip beads initialization
ao quick-start --minimal # Just .agents/ structure`,
RunE: runQuickstart,
}
var (
noBeads bool
minimal bool
)
func init() {
quickstartCmd.GroupID = "start"
rootCmd.AddCommand(quickstartCmd)
quickstartCmd.Flags().BoolVar(&noBeads, "no-beads", false, "Skip beads initialization")
quickstartCmd.Flags().BoolVar(&minimal, "minimal", false, "Minimal setup (just directories)")
}
// quickstartBeadsStep handles step 3: beads initialization or skip.
func quickstartBeadsStep(cwd string) {
if !noBeads {
fmt.Println("\n━━━ STEP 3: Beads initialization ━━━")
if err := initBeads(cwd); err != nil {
fmt.Printf(" ⚠ Beads init skipped: %v\n", err)
fmt.Println(" → You can run 'bd init' later to enable git-native issues")
}
} else {
fmt.Println("\n━━━ STEP 3: Skipping beads (--no-beads) ━━━")
fmt.Println(" → Issues will be tracked in .agents/tasks.json instead")
createTasksFile(cwd)
}
}
// quickstartClaudeMdStep handles step 4: create CLAUDE.md if missing.
func quickstartClaudeMdStep(cwd string) {
fmt.Println("\n━━━ STEP 4: Project configuration ━━━")
claudeMdPath := filepath.Join(cwd, "CLAUDE.md")
if _, err := os.Stat(claudeMdPath); os.IsNotExist(err) {
if err := createProjectClaudeMd(cwd); err != nil {
fmt.Printf(" ⚠ Warning: %v\n", err)
} else {
fmt.Println(" ✓ Created CLAUDE.md (project instructions)")
}
} else {
fmt.Println(" ✓ CLAUDE.md already exists")
}
}
func runQuickstart(cmd *cobra.Command, args []string) error {
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to get current directory: %w", err)
}
fmt.Println(`
╔══════════════════════════════════════════════════════════════════╗
║ AGENTOPS QUICK START ║
║ Setting up your project for knowledge compounding ║
╚══════════════════════════════════════════════════════════════════╝`)
fmt.Printf("Project: %s\n\n", cwd)
fmt.Println("━━━ STEP 1: Creating .agents/ structure ━━━")
dirs := []string{
".agents/briefings",
".agents/knowledge",
".agents/playbooks",
".agents/research",
".agents/synthesis",
".agents/specs",
".agents/learnings",
".agents/patterns",
".agents/retros",
".agents/handoff",
}
for _, dir := range dirs {
path := filepath.Join(cwd, dir)
if err := os.MkdirAll(path, 0750); err != nil {
return fmt.Errorf("failed to create %s: %w", dir, err)
}
fmt.Printf(" ✓ %s/\n", dir)
}
if minimal {
fmt.Println("\n✓ Minimal setup complete!")
showNextSteps(false)
return nil
}
fmt.Println("\n━━━ STEP 2: Creating starter knowledge pack ━━━")
if err := createStarterPack(cwd); err != nil {
fmt.Printf(" ⚠ Warning: %v\n", err)
}
quickstartBeadsStep(cwd)
quickstartClaudeMdStep(cwd)
fmt.Println("\n━━━ SETUP COMPLETE ━━━")
showNextSteps(!noBeads)
return nil
}
func createStarterPack(cwd string) error {
// Create a few starter patterns that are universally useful
patterns := map[string]string{
".agents/patterns/context-boundaries.md": `# Pattern: Fresh Context Per Phase
**Tier:** 2 (Pattern)
**Source:** AgentOps multi-epic post-mortem
## Problem
Long sessions accumulate errors. Context pollution causes drift.
## Solution
Fresh Claude session for each RPI phase:
- /research → new session
- /plan → new session
- /implement → new session
- /post-mortem → new session
## The 40% Rule
| Context % | Success Rate |
|-----------|--------------|
| <40% | 98% |
| 40-60% | ~50% |
| >60% | ~1% |
At 35% context, checkpoint and consider new session.
`,
".agents/patterns/pre-mortem-first.md": `# Pattern: Pre-Mortem Before Implementation
**Tier:** 2 (Pattern)
**Source:** Knowledge Flywheel post-mortem (2026-01-22)
## Problem
Implementation failures are expensive. Debugging takes longer than preventing.
## Solution
Run /pre-mortem on P0/P1 work BEFORE /crank:
` + "```bash" + `
/pre-mortem .agents/specs/my-feature.md
# Review findings
# Then implement
/crank
` + "```" + `
## Evidence
Pre-mortem caught 6 critical issues before implementation:
- API group mismatches
- Path resolution errors
- Migration assumptions
- Schema drift
## When to Skip
- Bug fixes (already understood)
- Single-file changes (<50 lines)
- P2/P3 priority work
`,
".agents/learnings/session-hygiene.md": `# Learning: Session Hygiene
**Date:** Starter Pack
**Tier:** 1 (Learning)
## Key Practices
1. **Always push before saying done**
- Work that isn't pushed didn't happen
- ` + "`git push`" + ` is the final step
2. **Run /post-mortem after epics**
- Captures learnings for the flywheel
- Creates patterns from experience
3. **Check Smart Connections before starting**
- Search for prior art: ` + "`mcp__smart-connections-work__lookup`" + `
- Don't reinvent what exists
4. **Use beads for state**
- ` + "`bd ready`" + ` shows unblocked work
- beads auto-sync issue writes; use ` + "`bd export -o backup.jsonl`" + ` for a manual snapshot
`,
}
for path, content := range patterns {
fullPath := filepath.Join(cwd, path)
if err := os.WriteFile(fullPath, []byte(content), 0600); err != nil {
return err
}
fmt.Printf(" ✓ %s\n", path)
}
return nil
}
func initBeads(cwd string) error {
// Check if beads is available
if _, err := exec.LookPath("bd"); err != nil {
return fmt.Errorf("bd command not found (install: brew install beads)")
}
// Check if already initialized
beadsDir := filepath.Join(cwd, ".beads")
if _, err := os.Stat(beadsDir); err == nil {
fmt.Println(" ✓ Beads already initialized")
return nil
}
// Determine prefix from directory name
dirName := filepath.Base(cwd)
prefix := strings.ToLower(dirName)
if len(prefix) > 4 {
prefix = prefix[:4]
}
fmt.Printf(" Initializing beads with prefix '%s'...\n", prefix)
// Ask for confirmation
reader := bufio.NewReader(os.Stdin)
fmt.Printf(" Use prefix '%s'? [Y/n]: ", prefix)
response, _ := reader.ReadString('\n')
response = strings.TrimSpace(strings.ToLower(response))
if response == "n" || response == "no" {
fmt.Print(" Enter prefix: ")
prefix, _ = reader.ReadString('\n')
prefix = strings.TrimSpace(prefix)
}
// Run bd init
cmd := exec.Command("bd", "init", "--prefix", prefix)
cmd.Dir = cwd
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("bd init failed: %s", string(output))
}
fmt.Printf(" ✓ Beads initialized with prefix '%s'\n", prefix)
return nil
}
func createTasksFile(cwd string) {
tasksPath := filepath.Join(cwd, ".agents/tasks.json")
content := `{
"tasks": [],
"note": "Beads-optional mode. Use 'bd init' to enable full git-native issues."
}
`
//nolint:errcheck // quickstart setup, errors shown implicitly by missing output
os.WriteFile(tasksPath, []byte(content), 0600) // #nosec G104
fmt.Println(" ✓ Created .agents/tasks.json (beads-optional mode)")
}
func createProjectClaudeMd(cwd string) error {
dirName := filepath.Base(cwd)
content := fmt.Sprintf(`# %s
## Quick Start
`+"```bash"+`
bd ready # See unblocked issues
/implement <issue> # Work on an issue
/crank # Autonomous epic execution
`+"```"+`
## Session Protocol
`+"```bash"+`
# Start
gt hook # Check for hooked work
bd ready # Find available work
# End
git add .
git commit -m "..."
git push # NEVER stop before pushing
`+"```"+`
## JIT Loading
| Working On | Load |
|------------|------|
| Research | .agents/research/ |
| Implementation | Check existing patterns first |
| Debugging | .agents/learnings/ |
## Beads Prefix
Issue prefix: (set during ol quick-start)
`, dirName)
return os.WriteFile(filepath.Join(cwd, "CLAUDE.md"), []byte(content), 0600)
}
func showNextSteps(hasBeads bool) {
fmt.Print(`
═══════════════════════════════════════════════════════════════════
NEXT STEPS
═══════════════════════════════════════════════════════════════════
`)
if hasBeads {
fmt.Println(` 1. Start the software-factory lane for a concrete goal:
$ ao factory start --goal "your first objective"
2. Create your first issue:
$ bd create "My first task"
3. Start working:
$ claude
> /rpi "your first objective"
4. When done, close the loop explicitly:
$ ao codex stop`)
} else {
fmt.Println(` 1. Start the software-factory lane for a concrete goal:
$ ao factory start --goal "your first objective"
2. Start Claude in your project:
$ claude
3. Work normally:
> /rpi "your first objective"
4. When ready for tracked execution:
$ bd init
$ bd create "My first task"`)
}
fmt.Print(`
5. Your knowledge compounds through briefings, RPI, and flywheel closeout
═══════════════════════════════════════════════════════════════════
"Stateful environment. Stateless agents. One explicit operator lane."
═══════════════════════════════════════════════════════════════════
`)
}