Implements deterministic feature lifecycle management for agent-driven
development. Agents use the CLI in pods; operators control via REST API.
Library (internal/sdlc/):
- Feature lifecycle with 10 phases (draft → released)
- Classifier engine with priority-ordered rules
- Artifact tracking with approval workflow
- Task management within features
- YAML-based state persistence
CLI (cmd/sdlc/):
- init, state, next, feature, artifact, task, query commands
- --json flag for machine-readable output
- Runs inside project pods
API (21 endpoints under /projects/{id}/sdlc/):
- State: GET /state, GET /next
- Features: CRUD + transition/block/unblock
- Artifacts: approve/reject per type
- Tasks: add/start/complete/block
- Queries: blocked/ready/needs-approval
Architecture:
- Port: SDLCExecutor interface (internal/port/)
- Adapter: kubectl exec into pods (internal/adapter/kubernetes/)
- Service: pod resolution + logging (internal/service/)
- Handlers: 5 files under 500-line limit (internal/handlers/)
Also includes template upgrades (chassis framework, UI components,
OpenAPI helpers, backend/frontend guides) and component improvements.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
82 lines
1.7 KiB
Go
82 lines
1.7 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/orchard9/rdev/internal/sdlc"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
rootDir string
|
|
jsonOutput bool
|
|
)
|
|
|
|
var rootCmd = &cobra.Command{
|
|
Use: "sdlc",
|
|
Short: "Deterministic SDLC orchestration tool",
|
|
Long: "Manage the software development lifecycle with deterministic state, artifacts, and classification.",
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.PersistentFlags().StringVar(&rootDir, "root", "", "project root (default: auto-detect)")
|
|
rootCmd.PersistentFlags().BoolVar(&jsonOutput, "json", false, "output as JSON")
|
|
}
|
|
|
|
// resolveRoot finds the project root by looking for .sdlc/ or .git/ walking up.
|
|
func resolveRoot() (string, error) {
|
|
if rootDir != "" {
|
|
abs, err := filepath.Abs(rootDir)
|
|
if err != nil {
|
|
return "", fmt.Errorf("resolve root: %w", err)
|
|
}
|
|
return abs, nil
|
|
}
|
|
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
return "", fmt.Errorf("get working directory: %w", err)
|
|
}
|
|
|
|
for {
|
|
if sdlc.IsInitialized(dir) {
|
|
return dir, nil
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dir, ".git")); err == nil {
|
|
return dir, nil
|
|
}
|
|
parent := filepath.Dir(dir)
|
|
if parent == dir {
|
|
break
|
|
}
|
|
dir = parent
|
|
}
|
|
|
|
// Fall back to cwd
|
|
cwd, _ := os.Getwd()
|
|
return cwd, nil
|
|
}
|
|
|
|
// mustResolveRoot resolves root or exits.
|
|
func mustResolveRoot() string {
|
|
root, err := resolveRoot()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
return root
|
|
}
|
|
|
|
// printJSON marshals v as indented JSON and prints to stdout.
|
|
func printJSON(v any) {
|
|
data, err := json.MarshalIndent(v, "", " ")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error marshaling JSON: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Println(string(data))
|
|
}
|