// Package command owns the narrow Cobra surface that exposes the agent runtime // to a headless operator. It declares request/response DTOs and a service port // so the root command can dispatch without depending on concrete task manager, // config, or provider implementations. // // Every command builds a request DTO, hands it to a CommandService, and prints // the response DTO. The service port is the only mutation boundary; preview // deliberately cannot invoke mutation methods. package command import ( "context" "encoding/json" "fmt" "io" "strings" ) // Service is the narrow application-facing port for the CLI command surface. // Implementations supply configuration loading, provider discovery, project // workflow observation, and task lifecycle control. Construction never starts // or stops anything. type Service interface { // Validate returns a non-nil error when the supplied configuration paths // cannot be loaded into a usable snapshot. It is side-effect free. Validate(ctx context.Context, req ValidateRequest) (ValidateResponse, error) // ProviderList returns the catalog entries registered under the supplied // provider catalog path. It is side-effect free. ProviderList(ctx context.Context, req ProviderListRequest) (ProviderListResponse, error) // ProjectList returns registered projects and their Milestone metadata // from the loaded runtime snapshot. It is side-effect free. ProjectList(ctx context.Context, req ProjectListRequest) (ProjectListResponse, error) // MilestoneList returns the work units declared by the active PLAN for // the supplied project. It is side-effect free. MilestoneList(ctx context.Context, req MilestoneListRequest) (MilestoneListResponse, error) // MilestoneSelect records the user's explicit Milestone choice for the // supplied project. Start rejects a project whose selected milestone is // empty. MilestoneSelect(ctx context.Context, req MilestoneSelectRequest) (MilestoneSelectResponse, error) // Preview runs the same selection and dependency logic as start but // returns a PreviewResponse without mutating durable state, starting // processes, or acquiring leases. Preview(ctx context.Context, req PreviewRequest) (PreviewResponse, error) // Serve blocks until ctx is cancelled and drives the runtime lifecycle. // It is the only command that performs sustained mutation. Serve(ctx context.Context, req ServeRequest) error // Start records a manual start intent for the supplied project. It // rejects projects whose selected milestone is empty. Start(ctx context.Context, req StartRequest) (StartResponse, error) // Stop records a manual stop intent for the supplied project. Stop(ctx context.Context, req StopRequest) (StopResponse, error) // Resume records a manual resume intent for the supplied project. Resume(ctx context.Context, req ResumeRequest) (ResumeResponse, error) // Status returns the live project status including overlay state, // integration queue position, and any active blockers. Status(ctx context.Context, req StatusRequest) (StatusResponse, error) // TaskLoop performs one bounded operator pass over the authoritative // standalone runtime. Dry-run only reads projections; live operation may // resume an explicitly requested blocked project and reconcile it once. TaskLoop(ctx context.Context, req TaskLoopRequest) (TaskLoopResponse, error) // TaskLoopParity validates and renders the S13 disposal manifest. TaskLoopParity(ctx context.Context, output io.Writer) error // ValidatePlan validates one candidate PLAN write set against a workspace. // It is read-only and never constructs a runtime or provider. ValidatePlan(ctx context.Context, req PlanValidationRequest) error } // RuntimeConfigPaths carries the repo-global and user-local configuration paths. type RuntimeConfigPaths struct { RepoGlobalPath string UserLocalPath string ProviderCatalog string } // ValidateRequest carries the configuration paths that the operator has // supplied on the command line. type ValidateRequest struct { RepoGlobalPath string UserLocalPath string ProviderCatalog string } // ValidateResponse reports whether the supplied configuration loads into a // usable snapshot. type ValidateResponse struct { Valid bool Revision string Projects int Providers int Profiles int Errors []string } // ProviderListRequest carries the catalog path used to enumerate providers. type ProviderListRequest struct { CatalogPath string } // ProviderListResponse reports every provider declared in the catalog. type ProviderListResponse struct { Providers []ProviderEntry } // ProviderEntry is one catalog-declared provider identity. type ProviderEntry struct { ID string Command string Capabilities []string } // ProjectListRequest carries the runtime configuration paths used to enumerate projects. type ProjectListRequest struct { Config RuntimeConfigPaths } // ProjectListResponse reports every registered project and its Milestone // selection state. type ProjectListResponse struct { Projects []ProjectEntry } // ProjectEntry is one registered project identity with its Milestone state. type ProjectEntry struct { ID string Workspace string Enabled bool SelectedMilestone string StartedMilestone string AutoResumeInterrupt bool } // MilestoneListRequest carries the project identity used to list its work // units. type MilestoneListRequest struct { Config RuntimeConfigPaths Project string } // MilestoneListResponse reports every work unit declared by the active PLAN // for the supplied project. type MilestoneListResponse struct { Milestones []MilestoneEntry } // MilestoneEntry is one work unit identity with its completion state. type MilestoneEntry struct { ID string Aliases []string Completed bool WriteSet string Isolation string } // MilestoneSelectRequest records the operator's explicit Milestone choice. type MilestoneSelectRequest struct { Config RuntimeConfigPaths Project string MilestoneID string } // MilestoneSelectResponse reports the recorded selection. type MilestoneSelectResponse struct { Project string MilestoneID string } // PreviewRequest carries the project identity used to run selection and // dependency logic without mutation. type PreviewRequest struct { Config RuntimeConfigPaths Project string } // PreviewResponse reports the selection and dependency verdict that start // would apply. It must not call any mutation method on the backing service. type PreviewResponse struct { Project string Selected bool MilestoneID string NextWork string Blockers []BlockerEntry } // BlockerEntry is one active blocker code and human-readable message. type BlockerEntry struct { Code string Message string Retryable bool } // ServeRequest carries the configuration paths and runtime hooks used to // drive the full lifecycle. type ServeRequest struct { RepoGlobalPath string UserLocalPath string ProviderCatalog string } // StartRequest carries the project identity used to record a manual start // intent. type StartRequest struct { Config RuntimeConfigPaths Project string } // StartResponse reports the recorded start intent. type StartResponse struct { Project string Status string } // StopRequest carries the project identity used to record a manual stop // intent. type StopRequest struct { Config RuntimeConfigPaths Project string } // StopResponse reports the recorded stop intent. type StopResponse struct { Project string Status string } // ResumeRequest carries the project identity used to record a manual resume // intent. type ResumeRequest struct { Config RuntimeConfigPaths Project string } // ResumeResponse reports the recorded resume intent. type ResumeResponse struct { Project string Status string } // StatusRequest carries the project identity used to query live state. type StatusRequest struct { Config RuntimeConfigPaths Project string } // StatusResponse reports the live project status including overlay, // integration, and blockers. type StatusResponse struct { Project string Status string Overlay string Integration string IntegrationPos int Blockers []BlockerEntry SelectedMilestone string StartedMilestone string } // TaskLoopRequest is the bounded operator request used by the production // orchestration skill. TaskGroup, when supplied, must be an exact m- prefixed // selected-Milestone group; it never infers a dependency from numeric order. type TaskLoopRequest struct { Config RuntimeConfigPaths TaskGroup string DryRun bool RetryBlocked bool } // PlanValidationRequest carries a candidate PLAN path and the workspace that // bounds every declared write target. The candidate itself may be outside the // workspace so plan/review rendering can use a temporary file. type PlanValidationRequest struct { Workspace string PlanPath string } // TaskLoopResponse reports a single runtime pass. ExitCode is zero for a // successful or dry-run pass and two only when the final reconciled selected // scope is blocked with no started or running project. type TaskLoopResponse struct { DryRun bool Projects []TaskLoopProject ExitCode int } type TaskLoopProject struct { Project string Group string Status string NextWork string Blockers []BlockerEntry } // FormatText renders a ValidateResponse as stable plain text. func (r ValidateResponse) FormatText() string { var b strings.Builder if r.Valid { b.WriteString("validate: ok\n") } else { b.WriteString("validate: FAIL\n") } b.WriteString(fmt.Sprintf(" revision: %s\n", r.Revision)) b.WriteString(fmt.Sprintf(" projects: %d\n", r.Projects)) b.WriteString(fmt.Sprintf(" providers: %d\n", r.Providers)) b.WriteString(fmt.Sprintf(" profiles: %d\n", r.Profiles)) for _, err := range r.Errors { b.WriteString(fmt.Sprintf(" error: %s\n", err)) } return b.String() } // FormatText renders a ProviderListResponse as stable plain text. func (r ProviderListResponse) FormatText() string { var b strings.Builder b.WriteString(fmt.Sprintf("providers: %d\n", len(r.Providers))) for _, p := range r.Providers { b.WriteString(fmt.Sprintf(" - %s command=%s caps=%s\n", p.ID, p.Command, strings.Join(p.Capabilities, ","))) } return b.String() } // FormatText renders a ProjectListResponse as stable plain text. func (r ProjectListResponse) FormatText() string { var b strings.Builder b.WriteString(fmt.Sprintf("projects: %d\n", len(r.Projects))) for _, p := range r.Projects { flag := "enabled" if !p.Enabled { flag = "disabled" } autoResume := "no" if p.AutoResumeInterrupt { autoResume = "yes" } b.WriteString(fmt.Sprintf(" - %s ws=%s %s selected=%q started=%q auto_resume=%s\n", p.ID, p.Workspace, flag, p.SelectedMilestone, p.StartedMilestone, autoResume)) } return b.String() } // FormatText renders a MilestoneListResponse as stable plain text. func (r MilestoneListResponse) FormatText() string { var b strings.Builder b.WriteString(fmt.Sprintf("milestones: %d\n", len(r.Milestones))) for _, m := range r.Milestones { done := "pending" if m.Completed { done = "completed" } b.WriteString(fmt.Sprintf(" - %s aliases=%s state=%s write_set=%q isolation=%s\n", m.ID, joinStrings(m.Aliases), done, m.WriteSet, m.Isolation)) } return b.String() } // FormatText renders a MilestoneSelectResponse as stable plain text. func (r MilestoneSelectResponse) FormatText() string { return fmt.Sprintf("milestone select project=%s milestone=%s\n", r.Project, r.MilestoneID) } // FormatText renders a PreviewResponse as stable plain text. func (r PreviewResponse) FormatText() string { var b strings.Builder b.WriteString(fmt.Sprintf("preview project=%s\n", r.Project)) if r.Selected { b.WriteString(fmt.Sprintf(" selected: %s\n", r.MilestoneID)) b.WriteString(fmt.Sprintf(" next: %s\n", r.NextWork)) } else { b.WriteString(" selected: (none)\n") } for _, bl := range r.Blockers { retry := "no" if bl.Retryable { retry = "yes" } b.WriteString(fmt.Sprintf(" blocker: %s %s retryable=%s\n", bl.Code, bl.Message, retry)) } return b.String() } // FormatText renders a StartResponse as stable plain text. func (r StartResponse) FormatText() string { return fmt.Sprintf("start project=%s status=%s\n", r.Project, r.Status) } // FormatText renders a StopResponse as stable plain text. func (r StopResponse) FormatText() string { return fmt.Sprintf("stop project=%s status=%s\n", r.Project, r.Status) } // FormatText renders a ResumeResponse as stable plain text. func (r ResumeResponse) FormatText() string { return fmt.Sprintf("resume project=%s status=%s\n", r.Project, r.Status) } // FormatText renders a StatusResponse as stable plain text. func (r StatusResponse) FormatText() string { var b strings.Builder b.WriteString(fmt.Sprintf("status project=%s\n", r.Project)) b.WriteString(fmt.Sprintf(" state: %s\n", r.Status)) b.WriteString(fmt.Sprintf(" overlay: %s\n", r.Overlay)) b.WriteString(fmt.Sprintf(" integration: %s (pos %d)\n", r.Integration, r.IntegrationPos)) b.WriteString(fmt.Sprintf(" selected_milestone: %s\n", r.SelectedMilestone)) b.WriteString(fmt.Sprintf(" started_milestone: %s\n", r.StartedMilestone)) for _, bl := range r.Blockers { retry := "no" if bl.Retryable { retry = "yes" } b.WriteString(fmt.Sprintf(" blocker: %s %s retryable=%s\n", bl.Code, bl.Message, retry)) } return b.String() } func (r TaskLoopResponse) FormatText() string { var b strings.Builder b.WriteString(fmt.Sprintf("task-loop dry_run=%t projects=%d\n", r.DryRun, len(r.Projects))) for _, project := range r.Projects { b.WriteString(fmt.Sprintf(" - project=%s group=%s status=%s next=%s\n", project.Project, project.Group, project.Status, project.NextWork)) for _, blocker := range project.Blockers { b.WriteString(fmt.Sprintf(" blocker=%s retryable=%t %s\n", blocker.Code, blocker.Retryable, blocker.Message)) } } b.WriteString(fmt.Sprintf(" exit_code: %d\n", r.ExitCode)) return b.String() } // FormatJSON renders a ValidateResponse as stable JSON. func (r ValidateResponse) FormatJSON() string { type dto struct { Valid bool `json:"valid"` Revision string `json:"revision"` Projects int `json:"projects"` Providers int `json:"providers"` Profiles int `json:"profiles"` Errors []string `json:"errors"` } errs := r.Errors if errs == nil { errs = []string{} } data, _ := json.MarshalIndent(dto{ Valid: r.Valid, Revision: r.Revision, Projects: r.Projects, Providers: r.Providers, Profiles: r.Profiles, Errors: errs, }, "", " ") return string(data) } // FormatJSON renders a ProviderListResponse as stable JSON. func (r ProviderListResponse) FormatJSON() string { type providerDTO struct { ID string `json:"id"` Command string `json:"command"` Capabilities []string `json:"capabilities"` } type dto struct { Providers []providerDTO `json:"providers"` } list := make([]providerDTO, 0, len(r.Providers)) for _, p := range r.Providers { caps := p.Capabilities if caps == nil { caps = []string{} } list = append(list, providerDTO{ ID: p.ID, Command: p.Command, Capabilities: caps, }) } data, _ := json.MarshalIndent(dto{Providers: list}, "", " ") return string(data) } // FormatJSON renders a ProjectListResponse as stable JSON. func (r ProjectListResponse) FormatJSON() string { type projectDTO struct { ID string `json:"id"` Workspace string `json:"workspace"` Enabled bool `json:"enabled"` SelectedMilestone string `json:"selected_milestone"` StartedMilestone string `json:"started_milestone"` AutoResumeInterrupt bool `json:"auto_resume_interrupted"` } type dto struct { Projects []projectDTO `json:"projects"` } list := make([]projectDTO, 0, len(r.Projects)) for _, p := range r.Projects { list = append(list, projectDTO{ ID: p.ID, Workspace: p.Workspace, Enabled: p.Enabled, SelectedMilestone: p.SelectedMilestone, StartedMilestone: p.StartedMilestone, AutoResumeInterrupt: p.AutoResumeInterrupt, }) } data, _ := json.MarshalIndent(dto{Projects: list}, "", " ") return string(data) } // FormatJSON renders a MilestoneListResponse as stable JSON. func (r MilestoneListResponse) FormatJSON() string { type milestoneDTO struct { ID string `json:"id"` Aliases []string `json:"aliases"` Completed bool `json:"completed"` WriteSet string `json:"write_set"` Isolation string `json:"isolation"` } type dto struct { Milestones []milestoneDTO `json:"milestones"` } list := make([]milestoneDTO, 0, len(r.Milestones)) for _, m := range r.Milestones { aliases := m.Aliases if aliases == nil { aliases = []string{} } list = append(list, milestoneDTO{ ID: m.ID, Aliases: aliases, Completed: m.Completed, WriteSet: m.WriteSet, Isolation: m.Isolation, }) } data, _ := json.MarshalIndent(dto{Milestones: list}, "", " ") return string(data) } // FormatJSON renders a MilestoneSelectResponse as stable JSON. func (r MilestoneSelectResponse) FormatJSON() string { type dto struct { Project string `json:"project"` MilestoneID string `json:"milestone_id"` } data, _ := json.Marshal(dto{ Project: r.Project, MilestoneID: r.MilestoneID, }) return string(data) } // FormatJSON renders a PreviewResponse as stable JSON. func (r PreviewResponse) FormatJSON() string { type blockerDTO struct { Code string `json:"code"` Message string `json:"message"` Retryable bool `json:"retryable"` } type dto struct { Project string `json:"project"` Selected bool `json:"selected"` MilestoneID string `json:"milestone_id"` NextWork string `json:"next_work"` Blockers []blockerDTO `json:"blockers"` } blockers := make([]blockerDTO, 0, len(r.Blockers)) for _, bl := range r.Blockers { blockers = append(blockers, blockerDTO{ Code: bl.Code, Message: bl.Message, Retryable: bl.Retryable, }) } data, _ := json.MarshalIndent(dto{ Project: r.Project, Selected: r.Selected, MilestoneID: r.MilestoneID, NextWork: r.NextWork, Blockers: blockers, }, "", " ") return string(data) } // FormatJSON renders a StartResponse as stable JSON. func (r StartResponse) FormatJSON() string { type dto struct { Project string `json:"project"` Status string `json:"status"` } data, _ := json.Marshal(dto{ Project: r.Project, Status: r.Status, }) return string(data) } // FormatJSON renders a StopResponse as stable JSON. func (r StopResponse) FormatJSON() string { type dto struct { Project string `json:"project"` Status string `json:"status"` } data, _ := json.Marshal(dto{ Project: r.Project, Status: r.Status, }) return string(data) } // FormatJSON renders a ResumeResponse as stable JSON. func (r ResumeResponse) FormatJSON() string { type dto struct { Project string `json:"project"` Status string `json:"status"` } data, _ := json.Marshal(dto{ Project: r.Project, Status: r.Status, }) return string(data) } // FormatJSON renders a StatusResponse as stable JSON. func (r StatusResponse) FormatJSON() string { type blockerDTO struct { Code string `json:"code"` Message string `json:"message"` Retryable bool `json:"retryable"` } type dto struct { Project string `json:"project"` Status string `json:"status"` Overlay string `json:"overlay"` Integration string `json:"integration"` IntegrationPos int `json:"integration_position"` SelectedMilestone string `json:"selected_milestone"` StartedMilestone string `json:"started_milestone"` Blockers []blockerDTO `json:"blockers"` } blockers := make([]blockerDTO, 0, len(r.Blockers)) for _, bl := range r.Blockers { blockers = append(blockers, blockerDTO{ Code: bl.Code, Message: bl.Message, Retryable: bl.Retryable, }) } data, _ := json.MarshalIndent(dto{ Project: r.Project, Status: r.Status, Overlay: r.Overlay, Integration: r.Integration, IntegrationPos: r.IntegrationPos, SelectedMilestone: r.SelectedMilestone, StartedMilestone: r.StartedMilestone, Blockers: blockers, }, "", " ") return string(data) } func (r TaskLoopResponse) FormatJSON() string { type blockerDTO struct { Code string `json:"code"` Message string `json:"message"` Retryable bool `json:"retryable"` } type projectDTO struct { Project string `json:"project"` Group string `json:"group"` Status string `json:"status"` NextWork string `json:"next_work"` Blockers []blockerDTO `json:"blockers"` } projects := make([]projectDTO, 0, len(r.Projects)) for _, project := range r.Projects { blockers := make([]blockerDTO, 0, len(project.Blockers)) for _, blocker := range project.Blockers { blockers = append(blockers, blockerDTO{Code: blocker.Code, Message: blocker.Message, Retryable: blocker.Retryable}) } projects = append(projects, projectDTO{Project: project.Project, Group: project.Group, Status: project.Status, NextWork: project.NextWork, Blockers: blockers}) } data, _ := json.MarshalIndent(struct { DryRun bool `json:"dry_run"` Projects []projectDTO `json:"projects"` ExitCode int `json:"exit_code"` }{DryRun: r.DryRun, Projects: projects, ExitCode: r.ExitCode}, "", " ") return string(data) } func joinStrings(items []string) string { return strings.Join(items, ", ") }