← back to Cli Printing Press
fix(cli): dogfood uses cobra Use: fields and recursive help walking
b80d31401f6c1c094786d24cab2617c4d76c798b · 2026-04-04 16:10:57 -0700 · Trevin Chow
Two fixes to the command tree wiring check:
1. Extract command names from cobra Use: fields instead of Go function
names. Function names encode file hierarchy (newBookingsPromotedCmd →
"bookings-promoted") while Use: fields are actual command names
("bookings"). This eliminates false positives from naming mismatches.
2. Recursive help walking instead of two-level. Many CLIs have 3+ levels
(bookings → attendees → add). Now walks up to 4 levels deep to
discover all registered subcommands.
Cal.com CLI: 119 false positives → 95 genuinely unreachable commands
(hidden sub-resource groups wired into promoted commands retain Hidden:
true, making their children unreachable — a real generator wiring issue
to fix separately).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Files touched
M internal/pipeline/dogfood.goM internal/pipeline/dogfood_test.go
Diff
commit b80d31401f6c1c094786d24cab2617c4d76c798b
Author: Trevin Chow <trevin@trevinchow.com>
Date: Sat Apr 4 16:10:57 2026 -0700
fix(cli): dogfood uses cobra Use: fields and recursive help walking
Two fixes to the command tree wiring check:
1. Extract command names from cobra Use: fields instead of Go function
names. Function names encode file hierarchy (newBookingsPromotedCmd →
"bookings-promoted") while Use: fields are actual command names
("bookings"). This eliminates false positives from naming mismatches.
2. Recursive help walking instead of two-level. Many CLIs have 3+ levels
(bookings → attendees → add). Now walks up to 4 levels deep to
discover all registered subcommands.
Cal.com CLI: 119 false positives → 95 genuinely unreachable commands
(hidden sub-resource groups wired into promoted commands retain Hidden:
true, making their children unreachable — a real generator wiring issue
to fix separately).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
internal/pipeline/dogfood.go | 44 ++++++++++++++++++++++++++-------------
internal/pipeline/dogfood_test.go | 20 +++++++++---------
2 files changed, 40 insertions(+), 24 deletions(-)
diff --git a/internal/pipeline/dogfood.go b/internal/pipeline/dogfood.go
index d6cc2586..1108bd77 100644
--- a/internal/pipeline/dogfood.go
+++ b/internal/pipeline/dogfood.go
@@ -963,21 +963,24 @@ func checkCommandTree(dir string) CommandTreeResult {
cliDir := filepath.Join(dir, "internal", "cli")
files := listGoFiles(cliDir)
- // Scan for func new*Cmd() patterns
- cmdFuncRe := regexp.MustCompile(`(?m)^func\s+new(\w+)Cmd\s*\(`)
+ // Scan for cobra Use: fields to get the actual command names users see.
+ // Previous approach derived names from Go function names (newXxxCmd → xxx),
+ // but function names encode file hierarchy (e.g., newBookingsPromotedCmd)
+ // while Use: fields are the actual cobra names (e.g., "bookings").
+ useFieldRe := regexp.MustCompile(`(?m)Use:\s*"([^"\s]+)`)
definedCmds := make(map[string]struct{})
for _, file := range files {
data, err := os.ReadFile(file)
if err != nil {
continue
}
- matches := cmdFuncRe.FindAllStringSubmatch(string(data), -1)
+ matches := useFieldRe.FindAllStringSubmatch(string(data), -1)
for _, match := range matches {
- // Convert CamelCase function name to kebab-case command name.
- // e.g., newApiGetCategoryCmd -> "api-get-category"
- // This matches cobra's command naming convention in help output.
- cmdName := camelToKebab(match[1])
- definedCmds[cmdName] = struct{}{}
+ // Extract just the command name (before any positional args)
+ cmdName := strings.Fields(match[1])[0]
+ if cmdName != "" {
+ definedCmds[cmdName] = struct{}{}
+ }
}
}
@@ -1007,14 +1010,27 @@ func checkCommandTree(dir string) CommandTreeResult {
return result
}
- // Also gather subcommand help by extracting top-level commands
+ // Recursively gather help output from all command levels.
+ // Many CLIs have 3+ levels (e.g., bookings → attendees → add).
+ // Only checking two levels misses nested subcommands.
helpLower := strings.ToLower(helpOut)
- topCmds := extractCommandNames(helpOut)
- for _, topCmd := range topCmds {
- subOut, err := runDogfoodCmd(binaryPath, 15*time.Second, topCmd, "--help")
- if err == nil {
- helpLower += "\n" + strings.ToLower(subOut)
+ var walkHelp func(parentArgs []string, depth int)
+ walkHelp = func(parentArgs []string, depth int) {
+ if depth > 4 {
+ return // safety cap
+ }
+ args := append(parentArgs, "--help")
+ out, err := runDogfoodCmd(binaryPath, 15*time.Second, args...)
+ if err != nil {
+ return
}
+ helpLower += "\n" + strings.ToLower(out)
+ for _, sub := range extractCommandNames(out) {
+ walkHelp(append(parentArgs, sub), depth+1)
+ }
+ }
+ for _, topCmd := range extractCommandNames(helpOut) {
+ walkHelp([]string{topCmd}, 1)
}
for cmdName := range definedCmds {
diff --git a/internal/pipeline/dogfood_test.go b/internal/pipeline/dogfood_test.go
index 1311f8a2..35deec66 100644
--- a/internal/pipeline/dogfood_test.go
+++ b/internal/pipeline/dogfood_test.go
@@ -330,12 +330,12 @@ func TestCheckCommandTree(t *testing.T) {
cliDir := filepath.Join(dir, "internal", "cli")
require.NoError(t, os.MkdirAll(cliDir, 0o755))
- // Define two commands via newXxxCmd() functions
+ // Define commands with cobra Use: fields (what users see in --help)
writeTestFile(t, filepath.Join(cliDir, "foo.go"), `package cli
-func newFooCmd() {}
+func newFooCmd() { cmd := &cobra.Command{Use: "foo"} }
`)
writeTestFile(t, filepath.Join(cliDir, "bar.go"), `package cli
-func newBarCmd() {}
+func newBarCmd() { cmd := &cobra.Command{Use: "bar"} }
`)
// Without a buildable binary, checkCommandTree can't verify registration,
@@ -347,25 +347,25 @@ func newBarCmd() {}
assert.Empty(t, result.Unregistered)
}
-func TestCheckCommandTree_KebabConversion(t *testing.T) {
+func TestCheckCommandTree_UseFieldExtraction(t *testing.T) {
dir := t.TempDir()
cliDir := filepath.Join(dir, "internal", "cli")
require.NoError(t, os.MkdirAll(cliDir, 0o755))
- // Define subcommands with CamelCase function names
+ // Use: fields may include positional args — we extract just the command name
writeTestFile(t, filepath.Join(cliDir, "api.go"), `package cli
-func newApiGetCategoryCmd() {}
-func newApiListTeamsCmd() {}
+cmd := &cobra.Command{Use: "get-category <id>"}
+cmd2 := &cobra.Command{Use: "list-teams"}
`)
writeTestFile(t, filepath.Join(cliDir, "auth.go"), `package cli
-func newAuthLoginCmd() {}
+cmd := &cobra.Command{Use: "login"}
`)
writeTestFile(t, filepath.Join(cliDir, "sync.go"), `package cli
-func newSyncCmd() {}
+cmd := &cobra.Command{Use: "sync"}
`)
result := checkCommandTree(dir)
- // Should find 4 defined commands
+ // Should find 4 defined commands: get-category, list-teams, login, sync
assert.Equal(t, 4, result.Defined)
// Without a buildable binary, all treated as registered
assert.Equal(t, 4, result.Registered)
← 6e2e1b3c fix(skills): comprehensive README requirements for polish ag
·
back to Cli Printing Press
·
fix(cli): unhide sub-resource groups when wired into promote 26fe5727 →