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): add Subcommands affordance to Runner for cobra-based CLIs
The original Runner shape assumed --since/--until lived on the root
binary. crono is cobra-based — date flags live on `biometrics`,
`exercises`, `nutrition`, `servings`, `notes` — so the dates suite
red-x'd HelpDocumentsDateFlags against it. Liftoff and Withings will
hit the same pattern.
Changes:
- Runner gains a Subcommands []string field declaring the subcommands
under which the contract surface lives, plus a WithSubcommand(name)
composition helper that prepends the subcommand to argv on Run.
- dates.RunContract dispatches per-subcommand when Subcommands is
non-empty, scoping each pass under `subcommand=NAME/...` so any
single-subcommand regression surfaces as a named failure rather
than masking the rest. Flat CLIs leave Subcommands empty and the
bundle runs against the root binary unchanged.
- stubcli grows a second mode (STUBCLI_MODE=cobra) that mirrors a
cobra-based CLI: root --help lists subcommands without mentioning
the date flags, and only `biometrics` carries --since/--until. The
cobra-mode self-test fails fast if the Runner ever stops prepending
the subcommand.
- New compat/internal/argecho helper and compat_test.go give the
WithSubcommand prepend a focused unit test against a binary that
just echoes os.Args.
- README + CONTRIBUTING document the affordance and the cobra-style
integration shape.
Addresses LeadGoEngineer review on PR #6 — the "first machine-attested
contract test" should not ship with a known gap that prevents its only
consumer from going green.
Refs CONTRACT.md §3.
Co-Authored-By: Paperclip <[email protected]>
Copy file name to clipboardExpand all lines: CONTRIBUTING.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -76,6 +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 (`compat/dates`, future `compat/formats`, `compat/auth`, `compat/prime`). Each 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
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.
80
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.
81
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.
60
+
61
+
### CI workflow
41
62
42
63
```yaml
43
64
- name: build
@@ -59,6 +80,8 @@ The exporter does not need a separate `go.mod` for compat tests — the standard
59
80
| `HelpIsHermetic` | §5 | `--help` succeeds with all HTTP proxies pointed at an unreachable address. |
60
81
| `FlagValidationIsHermetic` | §5 | A parse failure also produces no successful outbound request. |
61
82
83
+
When `compat.Runner.Subcommands` is set, every row above runs once per declared subcommand under `subcommand=NAME/...`.
84
+
62
85
## What it does NOT cover yet
63
86
64
87
The actual local-midnight semantics of `--since 2026-04-15` (the harmonization that just landed across crono/liftoff/withings) is still **human-attested** in the status table. Asserting it black-box requires either:
@@ -78,3 +101,5 @@ When that affordance lands, the test belongs here as `dates.LocalMidnightSemanti
78
101
## Self-test
79
102
80
103
This module has its own test that runs the suite against a stub CLI in `internal/stubcli/`. The stub is intentionally narrow — it exists so `go test ./...` from this module's root proves the library compiles and the assertions fire correctly, without depending on any of the real export-CLIs. Failures in the self-test mean the library has a bug; failures in an exporter's compat test mean the exporter drifted from the contract.
104
+
105
+
The stub has two modes (`STUBCLI_MODE=flat` and `STUBCLI_MODE=cobra`). The flat-mode self-test exercises the original Runner shape; the cobra-mode self-test exercises `Subcommands`-based dispatch. In cobra mode, the stub's root `--help` deliberately omits `--since/--until`, so the cobra-mode self-test fails fast if `compat.Runner` ever stops prepending the subcommand. There is also a focused unit test for `Runner.WithSubcommand` using an `argecho` helper that just prints `os.Args`.
0 commit comments