[agent/codex:gpt-5.4-mini] Read ~/spec/code/core/go/cli/RFC.md fully. Find ONE feature ... #50

Merged
Virgil merged 1 commit from agent/read---spec-code-core-go-cli-rfc-md-full into dev 2026-04-02 05:23:54 +00:00
9 changed files with 81 additions and 25 deletions
Showing only changes of commit 8b30e80688 - Show all commits

12
go.sum
View file

@ -6,13 +6,10 @@ forge.lthn.ai/core/go-i18n v0.1.7 h1:aHkAoc3W8fw3RPNvw/UszQbjyFWXHszzbZgty3SwyAA
forge.lthn.ai/core/go-i18n v0.1.7/go.mod h1:0VDjwtY99NSj2iqwrI09h5GUsJeM9s48MLkr+/Dn4G8=
forge.lthn.ai/core/go-inference v0.1.7 h1:9Dy6v03jX5ZRH3n5iTzlYyGtucuBIgSe+S7GWvBzx9Q=
forge.lthn.ai/core/go-inference v0.1.7/go.mod h1:jfWz+IJX55wAH98+ic6FEqqGB6/P31CHlg7VY7pxREw=
forge.lthn.ai/core/go-io v0.1.6/go.mod h1:3MSuQZuzhCi6aefECQ/LxhM8ooVLam1KgEvgeEjYZVc=
forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0=
forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
@ -23,12 +20,10 @@ github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
@ -36,7 +31,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@ -69,7 +63,6 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
@ -77,8 +70,6 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJu
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
@ -87,9 +78,6 @@ golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View file

@ -76,9 +76,7 @@ func TestRender_ColorEnabled_Good(t *testing.T) {
}
func TestUseASCII_Good(t *testing.T) {
// Save original state
original := ColorEnabled()
defer SetColorEnabled(original)
restoreThemeAndColors(t)
// Enable first, then UseASCII should disable colors
SetColorEnabled(true)

View file

@ -3,6 +3,7 @@ package cli
import "testing"
func TestCheckBuilder(t *testing.T) {
restoreThemeAndColors(t)
UseASCII() // Deterministic output
// Pass

View file

@ -3,6 +3,7 @@ package cli
import "testing"
func TestGlyph(t *testing.T) {
restoreThemeAndColors(t)
UseUnicode()
if Glyph(":check:") != "✓" {
t.Errorf("Expected ✓, got %s", Glyph(":check:"))
@ -15,6 +16,7 @@ func TestGlyph(t *testing.T) {
}
func TestCompileGlyphs(t *testing.T) {
restoreThemeAndColors(t)
UseUnicode()
got := compileGlyphs("Status: :check:")
if got != "Status: ✓" {

View file

@ -27,6 +27,7 @@ func captureOutput(f func()) string {
}
func TestSemanticOutput(t *testing.T) {
restoreThemeAndColors(t)
UseASCII()
// Test Success
@ -102,6 +103,7 @@ func TestSemanticOutput(t *testing.T) {
}
func TestSemanticOutput_GlyphShortcodes(t *testing.T) {
restoreThemeAndColors(t)
UseASCII()
out := captureOutput(func() {

View file

@ -12,8 +12,9 @@ import (
"golang.org/x/term"
)
// Spinner frames (braille pattern).
var spinnerFrames = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
// Spinner frames for the live tracker.
var spinnerFramesUnicode = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
var spinnerFramesASCII = []string{"-", "\\", "|", "/"}
// taskState tracks the lifecycle of a tracked task.
type taskState int
@ -227,7 +228,7 @@ func (tr *TaskTracker) renderLine(idx, frame int) {
case taskPending:
icon = DimStyle.Render(Glyph(":pending:"))
case taskRunning:
icon = InfoStyle.Render(spinnerFrames[frame%len(spinnerFrames)])
icon = InfoStyle.Render(trackerSpinnerFrame(frame))
case taskDone:
icon = SuccessStyle.Render(Glyph(":check:"))
case taskFailed:
@ -304,16 +305,24 @@ func (tr *TaskTracker) String() string {
var sb strings.Builder
for _, t := range tasks {
name, status, state := t.snapshot()
icon := "…"
icon := Glyph(":pending:")
switch state {
case taskDone:
icon = "✓"
icon = Glyph(":check:")
case taskFailed:
icon = "✗"
icon = Glyph(":cross:")
case taskRunning:
icon = "⠋"
icon = Glyph(":spinner:")
}
fmt.Fprintf(&sb, "%s %s %s\n", icon, Pad(name, nameW), status)
}
return sb.String()
}
func trackerSpinnerFrame(frame int) string {
frames := spinnerFramesUnicode
if currentTheme == ThemeASCII {
frames = spinnerFramesASCII
}
return frames[frame%len(frames)]
}

View file

@ -10,6 +10,17 @@ import (
"github.com/stretchr/testify/require"
)
func restoreThemeAndColors(t *testing.T) {
t.Helper()
prevTheme := currentTheme
prevColor := ColorEnabled()
t.Cleanup(func() {
currentTheme = prevTheme
SetColorEnabled(prevColor)
})
}
func TestTaskTracker_Good(t *testing.T) {
t.Run("add and complete tasks", func(t *testing.T) {
tr := NewTaskTracker()
@ -159,6 +170,25 @@ func TestTaskTracker_Good(t *testing.T) {
assert.Contains(t, out, "✗")
assert.Contains(t, out, "⠋")
})
t.Run("ASCII theme uses ASCII symbols", func(t *testing.T) {
restoreThemeAndColors(t)
UseASCII()
tr := NewTaskTracker()
tr.out = &bytes.Buffer{}
tr.Add("repo-a").Done("clean")
tr.Add("repo-b").Fail("dirty")
tr.Add("repo-c").Update("pulling")
out := tr.String()
assert.Contains(t, out, "[OK]")
assert.Contains(t, out, "[FAIL]")
assert.Contains(t, out, "-")
assert.NotContains(t, out, "✓")
assert.NotContains(t, out, "✗")
})
}
func TestTaskTracker_Bad(t *testing.T) {

View file

@ -90,13 +90,17 @@ func (n *TreeNode) renderLabel() string {
}
func (n *TreeNode) writeChildren(sb *strings.Builder, prefix string) {
tee := Glyph(":tee:") + Glyph(":dash:") + Glyph(":dash:") + " "
corner := Glyph(":corner:") + Glyph(":dash:") + Glyph(":dash:") + " "
pipe := Glyph(":pipe:") + " "
for i, child := range n.children {
last := i == len(n.children)-1
connector := "├── "
next := "│ "
connector := tee
next := pipe
if last {
connector = "└── "
connector = corner
next = " "
}

View file

@ -103,6 +103,28 @@ func TestTree_Good(t *testing.T) {
"└── child\n"
assert.Equal(t, expected, tree.String())
})
t.Run("ASCII theme uses ASCII connectors", func(t *testing.T) {
prevTheme := currentTheme
prevColor := ColorEnabled()
UseASCII()
t.Cleanup(func() {
currentTheme = prevTheme
SetColorEnabled(prevColor)
})
tree := NewTree("core-php")
tree.Add("core-tenant").Add("core-bio")
tree.Add("core-admin")
tree.Add("core-api")
expected := "core-php\n" +
"+-- core-tenant\n" +
"| `-- core-bio\n" +
"+-- core-admin\n" +
"`-- core-api\n"
assert.Equal(t, expected, tree.String())
})
}
func TestTree_Bad(t *testing.T) {