Add context packs impact analysis and message validation

This commit is contained in:
2026-05-07 10:17:24 +08:00
parent f2d0a83705
commit 0c5254eb1b
18 changed files with 780 additions and 13 deletions

View File

@@ -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