docs: CLI migration design — core/go pkg/cli registry pattern
Replace manual switch/flag.FlagSet with cli.Main() + WithCommands(). 6 command groups, 28 commands, full framework lifecycle. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8532077e46
commit
37010f4b6b
1 changed files with 122 additions and 0 deletions
122
docs/plans/2026-02-22-cli-migration-design.md
Normal file
122
docs/plans/2026-02-22-cli-migration-design.md
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
# LEM CLI Migration Design
|
||||
|
||||
Date: 2026-02-22
|
||||
Status: Approved
|
||||
|
||||
## Problem
|
||||
|
||||
LEM's `main.go` is a 296-line manual `switch os.Args[1]` with `flag.FlagSet` per command. No signal handling, no shell completion, no grouped help, no framework lifecycle. The Core Go Framework provides `pkg/cli` — a full CLI SDK wrapping cobra, charmbracelet TUI, and the DI lifecycle. Every other domain repo in the fleet uses it.
|
||||
|
||||
## Solution
|
||||
|
||||
Replace `main.go` with `cli.Main()` + `cli.WithCommands()`. Commands register through the Core framework lifecycle. LEM gets signal handling, structured logging, shell completion, grouped help, TUI primitives (Spinner, ProgressBar, Viewport), and workspace support for free.
|
||||
|
||||
### Single import rule
|
||||
|
||||
LEM imports `forge.lthn.ai/core/go/pkg/cli` and **nothing else** for CLI concerns. No cobra, no lipgloss, no bubbletea. `pkg/cli` wraps everything.
|
||||
|
||||
### New main.go (~10 lines)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"forge.lthn.ai/core/go/pkg/cli"
|
||||
"forge.lthn.ai/lthn/lem/cmd/lemcmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cli.Main(
|
||||
cli.WithCommands("lem", lemcmd.AddLEMCommands),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Command Groups (6 groups, 28 commands)
|
||||
|
||||
```
|
||||
lem score [score|probe|compare|tier-score|agent] — Scoring
|
||||
lem gen [distill|expand|conv] — Generation
|
||||
lem data [import-all|consolidate|normalize|approve] — Data Management
|
||||
lem export [jsonl|parquet|publish|convert] — Export & Publish
|
||||
lem mon [status|expand-status|inventory|coverage|metrics] — Monitoring
|
||||
lem infra [ingest|seed-influx|query|worker] — Infrastructure
|
||||
```
|
||||
|
||||
### File Layout
|
||||
|
||||
```
|
||||
cmd/lemcmd/
|
||||
├── lem.go # AddLEMCommands — creates groups, registers all
|
||||
├── score.go # score, probe, compare, tier-score, agent
|
||||
├── gen.go # distill, expand, conv
|
||||
├── data.go # import-all, consolidate, normalize, approve
|
||||
├── export.go # export (renamed jsonl), parquet, publish, convert
|
||||
├── mon.go # status, expand-status, inventory, coverage, metrics
|
||||
└── infra.go # ingest, seed-influx, query, worker
|
||||
```
|
||||
|
||||
### Registration Pattern
|
||||
|
||||
Following the fleet pattern (go-ml, go-devops, cli/):
|
||||
|
||||
```go
|
||||
// cmd/lemcmd/lem.go
|
||||
package lemcmd
|
||||
|
||||
import "forge.lthn.ai/core/go/pkg/cli"
|
||||
|
||||
func AddLEMCommands(root *cli.Command) {
|
||||
addScoreCommands(root)
|
||||
addGenCommands(root)
|
||||
addDataCommands(root)
|
||||
addExportCommands(root)
|
||||
addMonCommands(root)
|
||||
addInfraCommands(root)
|
||||
}
|
||||
```
|
||||
|
||||
Each group file:
|
||||
|
||||
```go
|
||||
// cmd/lemcmd/gen.go
|
||||
package lemcmd
|
||||
|
||||
import "forge.lthn.ai/core/go/pkg/cli"
|
||||
|
||||
func addGenCommands(root *cli.Command) {
|
||||
genCmd := cli.NewGroup("gen", "Generation commands", "")
|
||||
|
||||
distillCmd := cli.NewCommand("distill", "Native Metal distillation", "", runDistill)
|
||||
// flags via cli.StringFlag, cli.IntFlag, etc.
|
||||
|
||||
genCmd.AddCommand(distillCmd)
|
||||
root.AddCommand(genCmd)
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 1: Pass-through to existing RunFoo functions
|
||||
|
||||
Each `RunE` handler builds an `[]string` args slice from cobra flags and calls the existing `lem.RunFoo(args)` function. No business logic changes. This keeps the migration purely structural.
|
||||
|
||||
### Phase 2 (future): Native cobra flags
|
||||
|
||||
Migrate individual commands to use cobra flags directly instead of rebuilding `[]string`. This is optional and can be done command-by-command over time.
|
||||
|
||||
### What changes
|
||||
|
||||
- `main.go` shrinks from 296 lines to ~10 lines
|
||||
- `runScore()` and `runProbe()` (currently in main.go) move to `cmd/lemcmd/score.go`
|
||||
- `core/go` added as a full dependency (DI, lifecycle, signals, logging, workspace)
|
||||
- Each command gets proper `--help`, shell completion, grouped help output
|
||||
|
||||
### What stays the same
|
||||
|
||||
- All `pkg/lem/Run*` functions — unchanged
|
||||
- All business logic in `pkg/lem/` — untouched
|
||||
- Config loading, probe loading, scoring — unchanged
|
||||
|
||||
### Dependencies
|
||||
|
||||
- `forge.lthn.ai/core/go` (already in replace block, needs adding to require)
|
||||
- Transitively pulls in cobra, charmbracelet — but LEM never imports them directly
|
||||
Loading…
Add table
Reference in a new issue