Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func NewRootCommand() *cobra.Command {
root.PersistentFlags().BoolVar(&flags.NoPager, "no-pager", false, "disable pager")
root.AddCommand(
newBootstrapCmd(),
newVocabCmd(),
newA2ACmd(),
newPlaceholderCmd("ask", "Agent assist: explain or inspect without mutating state"),
newPlaceholderCmd("plan", "Agent assist: generate a plan over deterministic tools"),
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ func TestRootCommandHasExpectedTopLevelCommands(t *testing.T) {
root := NewRootCommand()
want := map[string]bool{
"bootstrap": false,
"vocab": false,
"a2a": false,
"ask": false,
"plan": false,
Expand Down
51 changes: 51 additions & 0 deletions internal/cmd/vocab.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package cmd

import (
"github.com/socioprophet/prophet-cli/internal/vocab"
"github.com/spf13/cobra"
)

func newVocabCmd() *cobra.Command {
cmd := &cobra.Command{Use: "vocab", Short: "Ontogenesis vocabulary and policy-pack façade"}
cmd.AddCommand(
&cobra.Command{Use: "fetch", Short: "fetch or refresh Ontogenesis semantic assets", RunE: func(cmd *cobra.Command, args []string) error {
resp, err := vocab.Fetch(cmd.Context())
if err != nil { return err }
resp["command"] = "prophet vocab fetch"
return emit(resp)
}},
&cobra.Command{Use: "validate [graph-path ...]", Short: "Run Ontogenesis semantic-core validation", Args: cobra.ArbitraryArgs, RunE: func(cmd *cobra.Command, args []string) error {
resp, err := vocab.Validate(cmd.Context(), args)
if err != nil { return err }
resp["command"] = "prophet vocab validate"
return emit(resp)
}},
&cobra.Command{Use: "promote", Short: "Promote the current validated context set", RunE: func(cmd *cobra.Command, args []string) error {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject unexpected positional args for promote

The promote subcommand sets RunE without an Args validator, so Cobra will accept extra positional arguments and still return success (for example, prophet vocab promote typo). That makes automation and operator mistakes hard to detect because invalid invocations look successful. Adding Args: cobra.NoArgs here (and similarly on other no-arg verbs like sr gate) would make this command fail fast on malformed input.

Useful? React with 👍 / 👎.

resp, err := vocab.Promote(cmd.Context())
if err != nil { return err }
resp["command"] = "prophet vocab promote"
return emit(resp)
}},
newVocabSRCmd(),
)
return cmd
}

func newVocabSRCmd() *cobra.Command {
cmd := &cobra.Command{Use: "sr", Short: "Symbolic regression façade over Ontogenesis runners"}
cmd.AddCommand(
&cobra.Command{Use: "run <module>", Short: "Extract, train, and register SR for a module", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error {
resp, err := vocab.SRRun(cmd.Context(), args[0])
if err != nil { return err }
resp["command"] = "prophet vocab sr run"
return emit(resp)
}},
&cobra.Command{Use: "gate", Short: "Evaluate SR promotion thresholds", RunE: func(cmd *cobra.Command, args []string) error {
resp, err := vocab.SRGate(cmd.Context())
if err != nil { return err }
resp["command"] = "prophet vocab sr gate"
return emit(resp)
}},
)
return cmd
}
23 changes: 23 additions & 0 deletions internal/cmd/vocab_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package cmd

import "testing"

func TestVocabCommandHasExpectedSubcommands(t *testing.T) {
cmd := newVocabCmd()
want := map[string]bool{
"fetch": false,
"validate": false,
"promote": false,
"sr": false,
}
for _, c := range cmd.Commands() {
if _, ok := want[c.Name()]; ok {
want[c.Name()] = true
}
}
for name, seen := range want {
if !seen {
t.Fatalf("missing vocab subcommand: %s", name)
}
}
}
36 changes: 36 additions & 0 deletions internal/exec/runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package executil

import (
"bytes"
"context"
"os/exec"
"time"
)

type Result struct {
Command []string `json:"command"`
Stdout string `json:"stdout,omitempty"`
Stderr string `json:"stderr,omitempty"`
ExitCode int `json:"exit_code"`
Duration time.Duration `json:"duration_ns"`
}

func Run(ctx context.Context, argv []string) (Result, error) {
res := Result{Command: append([]string(nil), argv...)}
if len(argv) == 0 {
return res, nil
}
start := time.Now()
cmd := exec.CommandContext(ctx, argv[0], argv[1:]...)
var outb, errb bytes.Buffer
cmd.Stdout = &outb
cmd.Stderr = &errb
err := cmd.Run()
res.Stdout = outb.String()
res.Stderr = errb.String()
res.Duration = time.Since(start)
if cmd.ProcessState != nil {
res.ExitCode = cmd.ProcessState.ExitCode()
}
return res, err
}
58 changes: 58 additions & 0 deletions internal/vocab/vocab.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package vocab

import (
"context"
"path/filepath"

executil "github.com/socioprophet/prophet-cli/internal/exec"
)

type Response map[string]any

func Fetch(_ context.Context) (Response, error) {
return Response{
"delegated_to": "socioprophet-standards-knowledge",
"status": "scaffold",
"operation": "fetch",
}, nil
}

func Validate(ctx context.Context, graphPaths []string) (Response, error) {
cmd := []string{"python3", filepath.ToSlash("policy/tools/validate_all.py"), "--data"}
cmd = append(cmd, graphPaths...)
res, _ := executil.Run(ctx, cmd)
return Response{
"delegated_to": "socioprophet-standards-knowledge",
"status": "scaffold",
"operation": "validate",
"graph_paths": graphPaths,
"validator": "policy/tools/validate_all.py",
"probe": res,
}, nil
}

func Promote(_ context.Context) (Response, error) {
return Response{
"delegated_to": "socioprophet-standards-knowledge",
"status": "scaffold",
"operation": "promote",
"record_target": "/ontology/promotions/<ts>.jsonld",
}, nil
}

func SRRun(_ context.Context, module string) (Response, error) {
return Response{
"delegated_to": "socioprophet-standards-knowledge",
"status": "scaffold",
"operation": "sr_run",
"module": module,
}, nil
}

func SRGate(_ context.Context) (Response, error) {
return Response{
"delegated_to": "socioprophet-standards-knowledge",
"status": "scaffold",
"operation": "sr_gate",
}, nil
}
19 changes: 19 additions & 0 deletions internal/vocab/vocab_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package vocab

import (
"context"
"testing"
)

func TestValidateReportsValidatorPath(t *testing.T) {
resp, err := Validate(context.Background(), []string{"graphs/demo.ttl"})
if err != nil {
t.Fatalf("Validate returned error: %v", err)
}
if got := resp["validator"]; got != "policy/tools/validate_all.py" {
t.Fatalf("unexpected validator path: %v", got)
}
if got := resp["operation"]; got != "validate" {
t.Fatalf("unexpected operation: %v", got)
}
}
Loading