Skip to content

Commit 1dcbcf4

Browse files
author
Test
committed
feat(fkg): onto now documents mxcli itself — command tree auto-discovery
- CmdAdapter reads mxcli command tree via sync.Once lazy init (no init ordering issues) - Recursive walker extracts name/flags from cobra command tree - Panic recovery for pflag -v/volumes collision in LocalFlags() - All 80+ mxcli commands discoverable via 'onto explore cmd:<name>' - Concept count: 20→118 (all command nodes at all depths) - Every command carries its Long help text and flags as SyntaxFeatures Next step toward 'onto replaces --help': commands can be browsed, explored, and reasoned about via the same FKG query interface.
1 parent 18264fa commit 1dcbcf4

5 files changed

Lines changed: 135 additions & 7 deletions

File tree

cmd/mxcli/cmd_onto.go

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import (
55
"encoding/json"
66
"fmt"
77
"strings"
8+
"sync"
89

910
"github.com/mendixlabs/mxcli/internal/fkg"
10-
_ "github.com/mendixlabs/mxcli/internal/fkg/concepts" // register all adapters
11+
"github.com/mendixlabs/mxcli/internal/fkg/concepts"
1112
"github.com/spf13/cobra"
13+
"github.com/spf13/pflag"
1214
)
1315

1416
func init() {
@@ -18,7 +20,54 @@ func init() {
1820
ontoCmd.AddCommand(ontoPathCmd)
1921
ontoCmd.AddCommand(ontoGuideCmd)
2022
ontoCmd.AddCommand(ontoPlanCmd)
23+
ontoCmd.AddCommand(ontoOrchestrateCmd)
2124
ontoExploreCmd.Flags().Int("depth", 2, "Traversal depth (default 2)")
25+
26+
// Note: command tree registration happens lazily via initMXCLICmds.
27+
// This ensures all init() functions across the package have completed
28+
// before we read the command tree.
29+
}
30+
31+
var mxcliCmdsOnce sync.Once
32+
33+
// initMXCLICmds registers the mxcli command tree into FKG.
34+
// Called lazily before the first onto query so all init() functions have run.
35+
func initMXCLICmds() {
36+
mxcliCmdsOnce.Do(func() {
37+
registerMXCLICommands(rootCmd, "")
38+
})
39+
}
40+
41+
// registerMXCLICommands recursively walks the cobra command tree and registers
42+
// each command with FKG so that "onto explore cmd:exec" etc. can discover them.
43+
func registerMXCLICommands(cmd *cobra.Command, parent string) {
44+
use := cmd.Use
45+
// Use the first word of Use as the canonical command name
46+
// (e.g., "exec <file>" → "exec", "show <type> [name]" → "show")
47+
name := use
48+
for i, r := range use {
49+
if r == ' ' || r == '\t' {
50+
name = use[:i]
51+
break
52+
}
53+
}
54+
if name == "" {
55+
return
56+
}
57+
58+
flags := []string{}
59+
func() {
60+
defer func() { recover() }() // ignore flagset collisions in cobra internals
61+
cmd.LocalFlags().VisitAll(func(f *pflag.Flag) {
62+
flags = append(flags, "--"+f.Name)
63+
})
64+
}()
65+
66+
concepts.RegisterMXCLICmd(name, cmd.Short, cmd.Long, parent, flags)
67+
68+
for _, sub := range cmd.Commands() {
69+
registerMXCLICommands(sub, name)
70+
}
2271
}
2372

2473
var ontoCmd = &cobra.Command{
@@ -80,11 +129,17 @@ Examples:
80129
},
81130
}
82131

132+
// ontoNew builds the FKG, ensuring the mxcli command tree is registered first.
133+
func ontoNew() (fkg.Querier, error) {
134+
initMXCLICmds()
135+
return fkg.New()
136+
}
137+
83138
var ontoSchemaCmd = &cobra.Command{
84139
Use: "schema",
85140
Short: "Show the full ontology skeleton (node types, edge types, root concepts)",
86141
RunE: func(cmd *cobra.Command, args []string) error {
87-
q, err := fkg.New()
142+
q, err := ontoNew()
88143
if err != nil {
89144
return err
90145
}
@@ -115,7 +170,7 @@ var ontoExploreCmd = &cobra.Command{
115170
Args: cobra.ExactArgs(1),
116171
RunE: func(cmd *cobra.Command, args []string) error {
117172
depth, _ := cmd.Flags().GetInt("depth")
118-
q, err := fkg.New()
173+
q, err := ontoNew()
119174
if err != nil {
120175
return err
121176
}
@@ -153,7 +208,7 @@ var ontoPathCmd = &cobra.Command{
153208
Short: "Discover structural path schemas between two nodes",
154209
Args: cobra.ExactArgs(2),
155210
RunE: func(cmd *cobra.Command, args []string) error {
156-
q, err := fkg.New()
211+
q, err := ontoNew()
157212
if err != nil {
158213
return err
159214
}

cmd/mxcli/cmd_onto_guide.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ var ontoGuideCmd = &cobra.Command{
1919
Short: "Show implementation guidance for a concept",
2020
Args: cobra.ExactArgs(1),
2121
RunE: func(cmd *cobra.Command, args []string) error {
22-
q, err := fkg.New()
22+
q, err := ontoNew()
2323
if err != nil {
2424
return err
2525
}

cmd/mxcli/cmd_onto_orchestrate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ var ontoOrchestrateCmd = &cobra.Command{
2020
Short: "Ordered implementation plan across multiple concepts",
2121
Args: cobra.MinimumNArgs(1),
2222
RunE: func(cmd *cobra.Command, args []string) error {
23-
q, err := fkg.New()
23+
q, err := ontoNew()
2424
if err != nil {
2525
return err
2626
}

cmd/mxcli/cmd_onto_plan.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ var ontoPlanCmd = &cobra.Command{
1919
Short: "Show curriculum plan for a learning module",
2020
Args: cobra.ExactArgs(1),
2121
RunE: func(cmd *cobra.Command, args []string) error {
22-
q, err := fkg.New()
22+
q, err := ontoNew()
2323
if err != nil {
2424
return err
2525
}

internal/fkg/concepts/cmd_mxcli.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// internal/fkg/concepts/cmd_mxcli.go
2+
package concepts
3+
4+
import (
5+
"context"
6+
"fmt"
7+
8+
"github.com/mendixlabs/mxcli/internal/mxgraph"
9+
)
10+
11+
// mxcli command descriptors are registered via RegisterMXCLICmd() before Build().
12+
// The adapter reads them in Build() to create FKG command nodes.
13+
14+
type cmdDescriptor struct {
15+
Name string
16+
Short string
17+
Long string
18+
Parent string
19+
Flags []string
20+
}
21+
22+
var mxcliCmds []cmdDescriptor
23+
24+
// RegisterMXCLICmd adds an mxcli command to the FKG command registry.
25+
// Called during init() by the CLI layer.
26+
func RegisterMXCLICmd(name, short, long, parent string, flags []string) {
27+
mxcliCmds = append(mxcliCmds, cmdDescriptor{
28+
Name: name, Short: short, Long: long, Parent: parent, Flags: flags,
29+
})
30+
}
31+
32+
func init() { Register(&CmdAdapter{}) }
33+
34+
type CmdAdapter struct{}
35+
36+
func (a *CmdAdapter) Name() string { return "fkg:cmd-mxcli" }
37+
func (a *CmdAdapter) Schema() *mxgraph.GraphSchema {
38+
return &mxgraph.GraphSchema{
39+
NodeLabels: []mxgraph.Label{LabelConcept, LabelSyntaxFeature},
40+
EdgeTypes: []struct {
41+
Type mxgraph.RelType
42+
From mxgraph.Label
43+
To mxgraph.Label
44+
}{
45+
{Specializes, LabelConcept, LabelConcept},
46+
{HasSyntax, LabelConcept, LabelSyntaxFeature},
47+
},
48+
}
49+
}
50+
func (a *CmdAdapter) Watch(_ context.Context, _ mxgraph.EventSink) (func(), error) {
51+
return func() {}, nil
52+
}
53+
func (a *CmdAdapter) Build(_ context.Context, sink mxgraph.EventSink) error {
54+
var events []mxgraph.Event
55+
for _, c := range mxcliCmds {
56+
cmdID := "cmd:" + c.Name
57+
events = append(events, conceptNode(cmdID, c.Name, c.Short))
58+
59+
if c.Long != "" {
60+
events = append(events, syntaxNode("cmd."+c.Name+".long", c.Long))
61+
events = append(events, edge(cmdID, "syntax:cmd."+c.Name+".long", HasSyntax))
62+
}
63+
for _, f := range c.Flags {
64+
fid := fmt.Sprintf("cmd.%s.%s", c.Name, f)
65+
events = append(events, syntaxNode(fid, fmt.Sprintf("Flag %s of %s", f, c.Name)))
66+
events = append(events, edge(cmdID, "syntax:"+fid, HasSyntax))
67+
}
68+
if c.Parent != "" {
69+
events = append(events, edge(cmdID, "cmd:"+c.Parent, Specializes))
70+
}
71+
}
72+
return sink.Emit(events)
73+
}

0 commit comments

Comments
 (0)