Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

156 lines
4.3 KiB
Go

package main
import (
"fmt"
"io"
"strconv"
"strings"
"github.com/zzet/gortex/internal/agents"
"github.com/zzet/gortex/internal/progress"
)
// emitAgentSummary writes the post-install / post-init summary as a styled
// block: totals strip, configured editors with per-action breakdown, a chip
// line of editors that weren't detected, and a numbered list of next steps.
// Used by both `gortex install` and `gortex init` for visual consistency.
func emitAgentSummary(w io.Writer, results []*agents.Result, opts agents.ApplyOpts, nextSteps []string) {
var detected []*agents.Result
var notDetected []string
var totals actionCounts
for _, r := range results {
if r == nil {
continue
}
if !r.Detected {
notDetected = append(notDetected, r.Name)
continue
}
detected = append(detected, r)
totals.add(r.Files)
}
fmt.Fprintln(w)
if !totals.zero() || opts.DryRun {
fmt.Fprintln(w, " "+totalsStrip(totals, opts.DryRun))
fmt.Fprintln(w)
}
if len(detected) > 0 {
fmt.Fprintln(w, " "+progress.Heading("configured", strconv.Itoa(len(detected))))
for _, r := range detected {
detail := perAdapterDetail(r.Files)
fmt.Fprintln(w, " "+progress.Row(r.Name, detail, 14))
}
fmt.Fprintln(w)
}
// Non-fatal problems an adapter continued past (e.g. a profile config
// that failed to write) — surfaced here rather than buried in stderr.
var warned []*agents.Result
for _, r := range detected {
if len(r.Warnings) > 0 {
warned = append(warned, r)
}
}
if len(warned) > 0 {
fmt.Fprintln(w, " "+progress.Heading("warnings"))
for _, r := range warned {
for _, msg := range r.Warnings {
fmt.Fprintln(w, " "+progress.Row(r.Name, msg, 14))
}
}
fmt.Fprintln(w)
}
if len(notDetected) > 0 {
names := progress.SortStrings(notDetected)
fmt.Fprintln(w, " "+progress.Heading("not detected", strconv.Itoa(len(notDetected))))
fmt.Fprintln(w, " "+progress.Chips(names, 0))
fmt.Fprintln(w)
}
if len(nextSteps) > 0 {
fmt.Fprintln(w, " "+progress.Heading("next steps"))
for i, s := range nextSteps {
fmt.Fprintln(w, " "+progress.NumberedStep(i+1, s))
}
fmt.Fprintln(w)
}
}
// actionCounts tallies file actions across all adapters.
type actionCounts struct {
create, merge, skip, wouldCreate, wouldMerge int
}
func (c *actionCounts) add(files []agents.FileAction) {
for _, f := range files {
switch f.Action {
case agents.ActionCreate:
c.create++
case agents.ActionMerge:
c.merge++
case agents.ActionSkip:
c.skip++
case agents.ActionWouldCreate:
c.wouldCreate++
case agents.ActionWouldMerge:
c.wouldMerge++
}
}
}
func (c actionCounts) zero() bool {
return c.create+c.merge+c.skip+c.wouldCreate+c.wouldMerge == 0
}
// totalsStrip renders e.g. "21 would-create · 2 would-merge · 12 skip (dry-run)".
func totalsStrip(c actionCounts, dryRun bool) string {
var stats []string
if c.wouldCreate > 0 {
stats = append(stats, progress.Stat(strconv.Itoa(c.wouldCreate), "would-create", progress.StatGood))
}
if c.create > 0 {
stats = append(stats, progress.Stat(strconv.Itoa(c.create), "created", progress.StatGood))
}
if c.wouldMerge > 0 {
stats = append(stats, progress.Stat(strconv.Itoa(c.wouldMerge), "would-merge", progress.StatNeutral))
}
if c.merge > 0 {
stats = append(stats, progress.Stat(strconv.Itoa(c.merge), "merged", progress.StatNeutral))
}
if c.skip > 0 {
stats = append(stats, progress.Stat(strconv.Itoa(c.skip), "skipped", progress.StatNeutral))
}
out := progress.StatStrip(stats...)
if dryRun {
out += " " + progress.Caption("dry-run — no files written")
}
return out
}
// perAdapterDetail compresses a single adapter's file list into a readable
// "create 3 · merge 1 · skip 2" string, ordered by significance.
func perAdapterDetail(files []agents.FileAction) string {
var c actionCounts
c.add(files)
parts := []string{}
if c.wouldCreate > 0 {
parts = append(parts, fmt.Sprintf("would-create %d", c.wouldCreate))
}
if c.create > 0 {
parts = append(parts, fmt.Sprintf("created %d", c.create))
}
if c.wouldMerge > 0 {
parts = append(parts, fmt.Sprintf("would-merge %d", c.wouldMerge))
}
if c.merge > 0 {
parts = append(parts, fmt.Sprintf("merged %d", c.merge))
}
if c.skip > 0 {
parts = append(parts, fmt.Sprintf("skipped %d", c.skip))
}
return strings.Join(parts, " · ")
}