2026-05-07 10:17:24 +08:00
|
|
|
package gsp
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"sort"
|
|
|
|
|
"strings"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func (p PackResult) Markdown() string {
|
|
|
|
|
var builder strings.Builder
|
|
|
|
|
builder.WriteString("# GSP Context Pack\n\n")
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- Entry: `%s`\n", p.Entry))
|
|
|
|
|
if p.Intent != "" {
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- Intent: `%s`\n", p.Intent))
|
|
|
|
|
}
|
|
|
|
|
if p.Stage != "" {
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- Stage: `%s`\n", p.Stage))
|
|
|
|
|
}
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- Units: `%d`\n", p.Summary.UnitCount))
|
2026-05-07 10:41:38 +08:00
|
|
|
if p.Summary.LinkCount > 0 {
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- Links: `%d`\n", p.Summary.LinkCount))
|
|
|
|
|
}
|
2026-05-07 10:17:24 +08:00
|
|
|
if p.Summary.MinResolution != "" {
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- Resolution: `%s` to `%s`\n", p.Summary.MinResolution, p.Summary.MaxResolution))
|
|
|
|
|
}
|
|
|
|
|
if p.Budget > 0 {
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- Budget: `%d`\n", p.Budget))
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- Approx chars: `%d`\n", p.ApproxChars))
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- Truncated: `%v`\n", p.Truncated))
|
|
|
|
|
}
|
|
|
|
|
builder.WriteString("\n## Units\n\n")
|
|
|
|
|
for _, unit := range p.Units {
|
|
|
|
|
builder.WriteString(fmt.Sprintf("### %s\n\n", unit.DisplayTitle()))
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- id: `%s`\n", unit.ID))
|
|
|
|
|
if unit.Type != "" {
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- type: `%s`\n", unit.Type))
|
|
|
|
|
}
|
|
|
|
|
if unit.Resolution != "" {
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- resolution: `%s`\n", unit.Resolution))
|
|
|
|
|
}
|
|
|
|
|
if unit.Refines != "" {
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- refines: `%s`\n", unit.Refines))
|
|
|
|
|
}
|
|
|
|
|
if len(unit.With) > 0 {
|
|
|
|
|
var with []string
|
|
|
|
|
for _, rel := range unit.With {
|
|
|
|
|
with = append(with, "`"+rel.ID+"`")
|
|
|
|
|
}
|
|
|
|
|
sort.Strings(with)
|
|
|
|
|
builder.WriteString("- with: " + strings.Join(with, ", ") + "\n")
|
|
|
|
|
}
|
|
|
|
|
if unit.Context != "" {
|
|
|
|
|
builder.WriteString("\n")
|
|
|
|
|
builder.WriteString(unit.Context)
|
|
|
|
|
builder.WriteString("\n")
|
|
|
|
|
}
|
|
|
|
|
builder.WriteString("\n")
|
|
|
|
|
}
|
2026-05-07 10:41:38 +08:00
|
|
|
if len(p.Links) > 0 {
|
|
|
|
|
builder.WriteString("## Links\n\n")
|
|
|
|
|
for _, link := range p.Links {
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- `%s` %s\n", link.Owner, link.Path))
|
|
|
|
|
builder.WriteString(fmt.Sprintf(" - role: `%s`\n", link.Role))
|
|
|
|
|
builder.WriteString(fmt.Sprintf(" - kind: `%s`\n", link.Kind))
|
|
|
|
|
builder.WriteString(fmt.Sprintf(" - status: `%s`\n", link.Status))
|
|
|
|
|
}
|
|
|
|
|
builder.WriteString("\n")
|
|
|
|
|
}
|
2026-05-07 10:17:24 +08:00
|
|
|
if len(p.Warnings) > 0 {
|
|
|
|
|
builder.WriteString("## Warnings\n\n")
|
|
|
|
|
for _, warning := range p.Warnings {
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- `%s`: %s\n", warning.Code, warning.Message))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return builder.String()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r ImpactResult) Markdown() string {
|
|
|
|
|
var builder strings.Builder
|
|
|
|
|
builder.WriteString("# GSP Impact\n\n")
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- Entry: `%s`\n", r.Entry))
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- Direct affected: `%d`\n", r.Summary.DirectCount))
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- Indirect affected: `%d`\n", r.Summary.IndirectCount))
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- Max depth: `%d`\n", r.Summary.MaxDepth))
|
|
|
|
|
writeImpactSection(&builder, "Direct", r.Direct)
|
|
|
|
|
writeImpactSection(&builder, "Indirect", r.Indirect)
|
|
|
|
|
if len(r.Warnings) > 0 {
|
|
|
|
|
builder.WriteString("## Warnings\n\n")
|
|
|
|
|
for _, warning := range r.Warnings {
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- `%s`: %s\n", warning.Code, warning.Message))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return builder.String()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func writeImpactSection(builder *strings.Builder, title string, entries []ImpactEntry) {
|
|
|
|
|
builder.WriteString("\n## " + title + "\n\n")
|
|
|
|
|
if len(entries) == 0 {
|
|
|
|
|
builder.WriteString("None.\n")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
for _, entry := range entries {
|
|
|
|
|
label := entry.Title
|
|
|
|
|
if label == "" {
|
|
|
|
|
label = entry.ID
|
|
|
|
|
}
|
|
|
|
|
builder.WriteString(fmt.Sprintf("- `%s` %s", entry.ID, label))
|
|
|
|
|
if entry.Kind != "" || entry.Via != "" {
|
|
|
|
|
builder.WriteString(fmt.Sprintf(" via `%s` `%s`", entry.Kind, entry.Via))
|
|
|
|
|
}
|
|
|
|
|
if entry.Type != "" {
|
|
|
|
|
builder.WriteString(fmt.Sprintf(" type `%s`", entry.Type))
|
|
|
|
|
}
|
|
|
|
|
if entry.Resolution != "" {
|
|
|
|
|
builder.WriteString(fmt.Sprintf(" resolution `%s`", entry.Resolution))
|
|
|
|
|
}
|
|
|
|
|
builder.WriteString("\n")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (u Unit) DisplayTitle() string {
|
|
|
|
|
if strings.TrimSpace(u.Title) != "" {
|
|
|
|
|
return u.Title
|
|
|
|
|
}
|
|
|
|
|
return u.ID
|
|
|
|
|
}
|