Add context packs impact analysis and message validation
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (p *Project) Validate(stage string) Report {
|
||||
@@ -165,6 +166,10 @@ func (p *Project) Flatten(id string, depth int, filter Filter) FlattenResult {
|
||||
}
|
||||
|
||||
func (p *Project) Pack(id string, depth, budget int, filter Filter) PackResult {
|
||||
return p.PackFor(id, "", "", depth, budget, filter)
|
||||
}
|
||||
|
||||
func (p *Project) PackFor(id, intent, stage string, depth, budget int, filter Filter) PackResult {
|
||||
flattened := p.Flatten(id, depth, filter)
|
||||
units := make([]*Unit, 0, len(flattened.Units))
|
||||
approx := 0
|
||||
@@ -181,15 +186,131 @@ func (p *Project) Pack(id string, depth, budget int, filter Filter) PackResult {
|
||||
}
|
||||
return PackResult{
|
||||
Entry: id,
|
||||
Intent: intent,
|
||||
Stage: stage,
|
||||
Depth: depth,
|
||||
Budget: budget,
|
||||
Truncated: truncated,
|
||||
Units: units,
|
||||
Summary: summarizeUnits(units, len(flattened.Warnings)),
|
||||
ApproxChars: approx,
|
||||
Warnings: flattened.Warnings,
|
||||
}
|
||||
}
|
||||
|
||||
func summarizeUnits(units []*Unit, missingCount int) Summary {
|
||||
summary := Summary{
|
||||
UnitCount: len(units),
|
||||
TypeCounts: map[string]int{},
|
||||
MissingCount: missingCount,
|
||||
}
|
||||
min := 99
|
||||
max := -1
|
||||
for _, unit := range units {
|
||||
if unit.Type != "" {
|
||||
summary.TypeCounts[unit.Type]++
|
||||
}
|
||||
rank, ok := resolutionValue(unit.Resolution)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if rank < min {
|
||||
min = rank
|
||||
summary.MinResolution = displayResolution(unit.Resolution)
|
||||
}
|
||||
if rank > max {
|
||||
max = rank
|
||||
summary.MaxResolution = displayResolution(unit.Resolution)
|
||||
}
|
||||
}
|
||||
if len(summary.TypeCounts) == 0 {
|
||||
summary.TypeCounts = nil
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
func (p *Project) Impact(id string, depth int) ImpactResult {
|
||||
result := ImpactResult{Entry: id, Depth: depth}
|
||||
if _, ok := p.ByID[id]; !ok {
|
||||
result.Warnings = append(result.Warnings, Issue{Level: "warning", Code: "missing", ID: id, Message: fmt.Sprintf("missing GSP %q", id)})
|
||||
return result
|
||||
}
|
||||
|
||||
reverse := map[string][]ImpactEntry{}
|
||||
for _, unit := range p.Units {
|
||||
if unit.Refines != "" {
|
||||
reverse[unit.Refines] = append(reverse[unit.Refines], impactEntry(unit, 0, unit.Refines, "refines"))
|
||||
}
|
||||
for _, rel := range unit.With {
|
||||
reverse[rel.ID] = append(reverse[rel.ID], impactEntry(unit, 0, rel.ID, "with"))
|
||||
}
|
||||
}
|
||||
for key := range reverse {
|
||||
sort.Slice(reverse[key], func(i, j int) bool {
|
||||
return reverse[key][i].ID < reverse[key][j].ID
|
||||
})
|
||||
}
|
||||
|
||||
seen := map[string]bool{id: true}
|
||||
queue := []ImpactEntry{{ID: id, Depth: 0}}
|
||||
for len(queue) > 0 {
|
||||
current := queue[0]
|
||||
queue = queue[1:]
|
||||
if depth >= 0 && current.Depth >= depth {
|
||||
continue
|
||||
}
|
||||
for _, affected := range reverse[current.ID] {
|
||||
if seen[affected.ID] {
|
||||
continue
|
||||
}
|
||||
seen[affected.ID] = true
|
||||
affected.Depth = current.Depth + 1
|
||||
affected.Via = current.ID
|
||||
if affected.Depth == 1 {
|
||||
result.Direct = append(result.Direct, affected)
|
||||
} else {
|
||||
result.Indirect = append(result.Indirect, affected)
|
||||
}
|
||||
result.Edges = append(result.Edges, GraphEdge{From: affected.ID, To: current.ID, Kind: affected.Kind})
|
||||
queue = append(queue, affected)
|
||||
if affected.Depth > result.Summary.MaxDepth {
|
||||
result.Summary.MaxDepth = affected.Depth
|
||||
}
|
||||
}
|
||||
}
|
||||
sortImpactEntries(result.Direct)
|
||||
sortImpactEntries(result.Indirect)
|
||||
sort.Slice(result.Edges, func(i, j int) bool {
|
||||
return strings.Join([]string{result.Edges[i].From, result.Edges[i].To, result.Edges[i].Kind}, "\x00") < strings.Join([]string{result.Edges[j].From, result.Edges[j].To, result.Edges[j].Kind}, "\x00")
|
||||
})
|
||||
result.Summary.DirectCount = len(result.Direct)
|
||||
result.Summary.IndirectCount = len(result.Indirect)
|
||||
result.Summary.TotalCount = len(result.Direct) + len(result.Indirect)
|
||||
return result
|
||||
}
|
||||
|
||||
func impactEntry(unit *Unit, depth int, via, kind string) ImpactEntry {
|
||||
return ImpactEntry{
|
||||
ID: unit.ID,
|
||||
Title: unit.Title,
|
||||
Type: unit.Type,
|
||||
Resolution: unit.Resolution,
|
||||
File: unit.File,
|
||||
Depth: depth,
|
||||
Via: via,
|
||||
Kind: kind,
|
||||
}
|
||||
}
|
||||
|
||||
func sortImpactEntries(entries []ImpactEntry) {
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
if entries[i].Depth == entries[j].Depth {
|
||||
return entries[i].ID < entries[j].ID
|
||||
}
|
||||
return entries[i].Depth < entries[j].Depth
|
||||
})
|
||||
}
|
||||
|
||||
type walker struct {
|
||||
project *Project
|
||||
depth int
|
||||
|
||||
Reference in New Issue
Block a user