You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(compat): support nested subcommand paths in Runner.WithSubcommand (#12)
Liftoff-export's data-producing leaves live two levels deep
(`liftoff-export workouts stats`, `liftoff-export bodyweights list`).
The pre-existing Runner.WithSubcommand passed the value as a single
argv entry, so a nested path arrived at cobra as one literal
"workouts stats" command name and was rejected as unknown.
Switch the internal representation to []string, split via
strings.Fields on WithSubcommand intake, and prepend each segment as
its own argv entry. Single-word callers (crono's `biometrics`,
`exercises`, ...) keep working unchanged because Fields("biometrics")
returns ["biometrics"]. The empty string still clears the path, so
the API stays reversible without an extra method.
Subcommand() returns the path joined with single spaces, which is
what callers passed in. Section bundles already use it for
t.Run("subcommand="+sub, ...) names, so nested-path failures surface
as `subcommand=workouts stats/...` subtests instead of masking.
Two new self-tests in compat_test.go lock in the split-on-whitespace
and empty-clear behaviors via the existing argecho helper:
- TestWithSubcommand_NestedPathSplitsOnWhitespace exercises the
liftoff shape end-to-end (two segments + flag args).
- TestWithSubcommand_EmptyStringClears asserts the reset path.
README + CONTRIBUTING gain a one-paragraph note pointing partial-
codec exporters at the nested-path usage, so future onboarding
heartbeats don't trip on the same surprise.
Unblocks QUA-16 (liftoff onboarding as second consumer of
compat/formats).
Co-authored-by: LeadGoEngineer <leadgoengineer@quantcli.local>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Copy file name to clipboardExpand all lines: CONTRIBUTING.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -76,7 +76,7 @@ Rules:
76
76
- Anyone changing `CONTRACT.md` is also expected to update or add tests under `compat/` that exercise the new behavior against every `*-export-cli`.
77
77
- The harness is deliberately black-box: it shells out to the binary and asserts on stdout, stderr, and exit code only. It must not import a CLI's internal packages.
78
78
- One subpackage per contract section. The naming convention is `compat/<section>` where `<section>` is the CONTRACT.md section being attested — currently `compat/dates` (§2–§3) and `compat/formats` (§4); `compat/auth` (§5) and `compat/prime` (§6) are expected to follow. Each subpackage exposes a single entry point — `RunContract(t, runner)` — that exporters call from one build-tagged `_test.go` file.
79
-
- Cobra-based exporters whose contract surface lives on subcommands set `compat.Runner.Subcommands`; section bundles dispatch per-subcommand under a `subcommand=NAME/...` subtree. Flat CLIs leave the field empty and the bundle runs against the root binary.
79
+
- Cobra-based exporters whose contract surface lives on subcommands set `compat.Runner.Subcommands`; section bundles dispatch per-subcommand under a `subcommand=NAME/...` subtree. Flat CLIs leave the field empty and the bundle runs against the root binary. Nested paths (e.g. `liftoff-export workouts stats`) are passed as a single space-separated entry — the dispatcher splits on whitespace so each segment is its own argv entry.
80
80
- A PR that changes the contract without touching `compat/` is incomplete. Either update the tests in the same PR or open a follow-up issue and link it from the PR body before merging — the Lead Go Engineer holds the line on this.
81
81
- Compat tests run in CI on every PR and on `main`. A failing compat test on `main` means at least one shipped CLI no longer matches the contract, and that's a release-blocker incident, not a flake.
82
82
- The Status table in `CONTRACT.md` distinguishes **machine-attested** rows (covered by `compat/`) from **human-attested** rows (still verified by reviewer judgment). Promoting a row from human to machine attestation is itself a worthwhile PR.
Each subcommand is verified under a `subcommand=NAME/...` subtree, so a regression in any single one fails as a named subtest instead of masking the rest.
69
69
70
+
Nested-path subcommands are supported by passing a space-separated string. The dispatcher splits on whitespace so each segment becomes its own argv entry, which is what cobra's command tree resolves against:
71
+
72
+
```go
73
+
dates.RunContract(t, compat.Runner{
74
+
Binary: bin,
75
+
Subcommands: []string{
76
+
"bodyweights list", "bodyweights stats",
77
+
"workouts list", "workouts stats",
78
+
},
79
+
})
80
+
```
81
+
82
+
This is the pattern liftoff-export uses, where `--since`/`--until` live on two-level leaves (`liftoff-export workouts stats`) rather than top-level subcommands.
0 commit comments