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>
116 lines
3.4 KiB
Go
116 lines
3.4 KiB
Go
package sdlc
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"slices"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// Config represents the project SDLC configuration in .sdlc/config.yaml.
|
|
type Config struct {
|
|
Version int `yaml:"version" json:"version"`
|
|
Project ProjectConfig `yaml:"project" json:"project"`
|
|
Branches BranchConfig `yaml:"branches" json:"branches"`
|
|
Phases PhaseConfig `yaml:"phases" json:"phases"`
|
|
Compliance ComplianceConfig `yaml:"compliance" json:"compliance"`
|
|
Patterns PatternsConfig `yaml:"patterns,omitempty" json:"patterns,omitempty"` //nolint:omitzero
|
|
}
|
|
|
|
// ProjectConfig holds project-level settings.
|
|
type ProjectConfig struct {
|
|
Name string `yaml:"name" json:"name"`
|
|
Type string `yaml:"type,omitempty" json:"type,omitempty"`
|
|
}
|
|
|
|
// BranchConfig defines branch naming conventions.
|
|
type BranchConfig struct {
|
|
Main string `yaml:"main" json:"main"`
|
|
FeaturePrefix string `yaml:"feature_prefix" json:"feature_prefix"`
|
|
}
|
|
|
|
// PhaseConfig defines which phases are enabled and what artifacts are required.
|
|
type PhaseConfig struct {
|
|
Enabled []FeaturePhase `yaml:"enabled" json:"enabled"`
|
|
RequiredArtifacts map[FeaturePhase][]ArtifactType `yaml:"required_artifacts" json:"required_artifacts"`
|
|
}
|
|
|
|
// ComplianceConfig defines approval and gate requirements.
|
|
type ComplianceConfig struct {
|
|
RequireApprovals bool `yaml:"require_approvals" json:"require_approvals"`
|
|
RequireBranch bool `yaml:"require_branch" json:"require_branch"`
|
|
RequireQA bool `yaml:"require_qa" json:"require_qa"`
|
|
}
|
|
|
|
// PatternsConfig defines pattern enforcement.
|
|
type PatternsConfig struct {
|
|
AutoEnforce []string `yaml:"auto_enforce,omitempty" json:"auto_enforce,omitempty"`
|
|
}
|
|
|
|
// LoadConfig reads and parses .sdlc/config.yaml from the given project root.
|
|
func LoadConfig(root string) (*Config, error) {
|
|
path := ConfigPath(root)
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, ErrNotInitialized
|
|
}
|
|
return nil, fmt.Errorf("read config file: %w", err)
|
|
}
|
|
|
|
var c Config
|
|
if err := yaml.Unmarshal(data, &c); err != nil {
|
|
return nil, fmt.Errorf("parse config file: %w", err)
|
|
}
|
|
return &c, nil
|
|
}
|
|
|
|
// Save writes the config to .sdlc/config.yaml.
|
|
func (c *Config) Save(root string) error {
|
|
data, err := yaml.Marshal(c)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal config: %w", err)
|
|
}
|
|
|
|
path := ConfigPath(root)
|
|
if err := os.WriteFile(path, data, 0o644); err != nil {
|
|
return fmt.Errorf("write config file: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DefaultConfig returns a config with all phases enabled and standard requirements.
|
|
func DefaultConfig(projectName string) *Config {
|
|
return &Config{
|
|
Version: 1,
|
|
Project: ProjectConfig{
|
|
Name: projectName,
|
|
},
|
|
Branches: BranchConfig{
|
|
Main: "main",
|
|
FeaturePrefix: "feature/",
|
|
},
|
|
Phases: PhaseConfig{
|
|
Enabled: ValidPhases,
|
|
RequiredArtifacts: map[FeaturePhase][]ArtifactType{
|
|
PhaseSpecified: {ArtifactSpec},
|
|
PhasePlanned: {ArtifactSpec, ArtifactDesign, ArtifactTasks, ArtifactQAPlan},
|
|
PhaseReview: {ArtifactReview},
|
|
PhaseAudit: {ArtifactAudit},
|
|
PhaseQA: {ArtifactQAResults},
|
|
},
|
|
},
|
|
Compliance: ComplianceConfig{
|
|
RequireApprovals: true,
|
|
RequireBranch: true,
|
|
RequireQA: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
// IsPhaseEnabled returns true if the phase is in the enabled list.
|
|
func (c *Config) IsPhaseEnabled(phase FeaturePhase) bool {
|
|
return slices.Contains(c.Phases.Enabled, phase)
|
|
}
|