Skip to content

Commit b2b522f

Browse files
authored
Merge pull request #59 from LAA-Software-Engineering/issue/25-validate-cmd
feat(cli): agentctl validate command (closes #25)
2 parents 4d3dc3e + 075740a commit b2b522f

11 files changed

Lines changed: 290 additions & 0 deletions

File tree

internal/cli/doc.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
// Package cli defines agentctl commands, global flags (design doc section 11.1), and exit-code
22
// mapping (section 11.2). Output formatting lives in [render].
3+
//
4+
// The validate command (section 10.2) loads the project, applies defaults and optional environment
5+
// overlays, then runs [spec.ValidateProjectGraph].
36
package cli

internal/cli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func NewRootCmd() *cobra.Command {
3333
}
3434
BindPersistentFlags(root)
3535
root.AddCommand(newVersionCmd())
36+
root.AddCommand(newValidateCmd())
3637
return root
3738
}
3839

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
apiVersion: agentic.dev/v0
2+
kind: Tool
3+
metadata:
4+
name: broken
5+
spec: {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
apiVersion: agentic.dev/v0
2+
kind: Project
3+
metadata:
4+
name: validate-bad
5+
spec:
6+
imports:
7+
- ./broken-tool.yaml
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: agentic.dev/v0
2+
kind: Environment
3+
metadata:
4+
name: staging
5+
spec:
6+
overrides:
7+
policies:
8+
default:
9+
execution:
10+
maxWallClockSeconds: 600
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: agentic.dev/v0
2+
kind: Policy
3+
metadata:
4+
name: default
5+
spec:
6+
execution:
7+
maxWallClockSeconds: 300
8+
maxTotalCostUsd: 5
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apiVersion: agentic.dev/v0
2+
kind: Project
3+
metadata:
4+
name: validate-ok
5+
spec:
6+
imports:
7+
- ./tool.yaml
8+
- ./policy.yaml
9+
- ./workflow.yaml
10+
- ./env.yaml
11+
defaults:
12+
policy: default
13+
model: mock/gpt-4
14+
providers:
15+
models:
16+
mock:
17+
type: mock
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: agentic.dev/v0
2+
kind: Tool
3+
metadata:
4+
name: helper
5+
spec:
6+
type: native
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: agentic.dev/v0
2+
kind: Workflow
3+
metadata:
4+
name: demo
5+
spec:
6+
policy: default
7+
steps:
8+
- id: ping
9+
uses: tool.helper.echo
10+
with:
11+
msg: "hi"

internal/cli/validate.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
7+
"github.com/LAA-Software-Engineering/agentic-control-plane/internal/project"
8+
"github.com/LAA-Software-Engineering/agentic-control-plane/internal/render"
9+
"github.com/LAA-Software-Engineering/agentic-control-plane/internal/runtime/local"
10+
"github.com/LAA-Software-Engineering/agentic-control-plane/internal/spec"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func newValidateCmd() *cobra.Command {
15+
return &cobra.Command{
16+
Use: "validate",
17+
Short: "Validate project YAML, references, and schema files",
18+
SilenceUsage: true,
19+
Long: `Load the project from --project, apply Project defaults, optionally apply
20+
the selected Environment (-e / --env), then run spec validation.
21+
22+
Exit code 2 indicates validation failure (design doc sections 10.2, 11.2).`,
23+
RunE: runValidate,
24+
}
25+
}
26+
27+
func runValidate(cmd *cobra.Command, args []string) error {
28+
_ = args
29+
g := Globals()
30+
root, err := filepath.Abs(filepath.Clean(g.ProjectRoot))
31+
if err != nil {
32+
return NewExitErrorf(ExitValidationError, "project root: %w", err)
33+
}
34+
35+
graph, err := project.LoadProject(root)
36+
if err != nil {
37+
return NewExitErrorf(ExitValidationError, "%w", err)
38+
}
39+
40+
spec.NormalizeProjectGraph(graph)
41+
42+
graph, err = local.ApplyEnvironment(graph, g.Env)
43+
if err != nil {
44+
return NewExitErrorf(ExitValidationError, "%w", err)
45+
}
46+
47+
if err := spec.ValidateProjectGraph(graph, root); err != nil {
48+
return NewExitError(ExitValidationError, err)
49+
}
50+
51+
return writeValidateSuccess(cmd, graph, g)
52+
}
53+
54+
func writeValidateSuccess(cmd *cobra.Command, graph *spec.ProjectGraph, g *Global) error {
55+
out := cmd.OutOrStdout()
56+
envLabel := g.Env
57+
if envLabel == "" {
58+
envLabel = "(none)"
59+
}
60+
projName := graph.Meta.Name
61+
if projName == "" {
62+
projName = "(unnamed)"
63+
}
64+
n := resourceCount(graph)
65+
66+
switch g.Output {
67+
case render.FormatJSON:
68+
payload := struct {
69+
Project string `json:"project"`
70+
Environment string `json:"environment"`
71+
ResourceCount int `json:"resourceCount"`
72+
Valid bool `json:"valid"`
73+
Message string `json:"message"`
74+
}{
75+
Project: projName,
76+
Environment: envLabel,
77+
ResourceCount: n,
78+
Valid: true,
79+
Message: "Validation successful",
80+
}
81+
return render.WriteJSON(out, payload)
82+
case render.FormatYAML:
83+
return render.WriteYAML(out, map[string]any{
84+
"project": projName,
85+
"environment": envLabel,
86+
"resourceCount": n,
87+
"valid": true,
88+
"message": "Validation successful",
89+
})
90+
default:
91+
p := passPrefix(g)
92+
if _, err := fmt.Fprintf(out, "Project: %s\nEnvironment: %s\n\n", projName, envLabel); err != nil {
93+
return err
94+
}
95+
if _, err := fmt.Fprintf(out, "%s Loaded %d resources\n", p, n); err != nil {
96+
return err
97+
}
98+
if _, err := fmt.Fprintf(out, "%s References resolved\n", p); err != nil {
99+
return err
100+
}
101+
if _, err := fmt.Fprintf(out, "%s Schemas valid\n", p); err != nil {
102+
return err
103+
}
104+
if _, err := fmt.Fprintf(out, "%s All workflows valid\n", p); err != nil {
105+
return err
106+
}
107+
_, err := fmt.Fprintf(out, "\nValidation successful\n")
108+
return err
109+
}
110+
}
111+
112+
func resourceCount(g *spec.ProjectGraph) int {
113+
if g == nil {
114+
return 0
115+
}
116+
return len(g.Agents) + len(g.Tools) + len(g.Workflows) + len(g.Policies) + len(g.Environments)
117+
}
118+
119+
func passPrefix(g *Global) string {
120+
if g != nil && g.NoColor {
121+
return "*"
122+
}
123+
return "✓"
124+
}

0 commit comments

Comments
 (0)