package client import ( "fmt" "sort" "github.com/m3tam3re/agent-lib/internal/discovery" "github.com/m3tam3re/agent-lib/internal/gitsource" "github.com/m3tam3re/agent-lib/internal/lockfile" ) // InventoryItem is one deployable item found in the work repository. type InventoryItem struct { Type string Name string Origin string RelPath string Hash string Warnings []string } // Inventory is the full content of a work-repo revision: own contributions // plus every external area. MCP fragments are inventoried but filtered at // deployment time. type Inventory struct { Revision string Items []InventoryItem } // BuildInventory reads the work repository tree and the lockfile (when // present) and resolves the flat deployed namespace. func BuildInventory(tree *gitsource.GitTree, rev string) (*Inventory, error) { inv := &Inventory{Revision: rev} own, err := discovery.Scan(tree, discovery.Config{}) if err != nil { return nil, fmt.Errorf("scanning own items: %w", err) } for _, it := range own { inv.Items = append(inv.Items, InventoryItem{ Type: it.Type, Name: it.UpstreamID, Origin: "own", RelPath: it.RelPath, }) } var lf *lockfile.Lockfile if data, err := tree.ReadFile(lockfile.FileName); err == nil { lf, err = lockfile.ParseBytes(data) if err != nil { return nil, fmt.Errorf("work repository lockfile is invalid: %w", err) } } else { lf = lockfile.New() } sources := make([]string, 0, len(lf.Sources)) for name := range lf.Sources { sources = append(sources, name) } sort.Strings(sources) for _, name := range sources { sub := &discovery.PrefixTree{Tree: tree, Prefix: "external/" + name} items, err := discovery.Scan(sub, discovery.Config{}) if err != nil { return nil, fmt.Errorf("scanning external area of %q: %w", name, err) } for _, it := range items { inv.Items = append(inv.Items, InventoryItem{ Type: it.Type, Name: it.UpstreamID, Origin: name, RelPath: "external/" + name + "/" + it.RelPath, Warnings: it.Warnings, }) } } sort.Slice(inv.Items, func(i, j int) bool { if inv.Items[i].Type != inv.Items[j].Type { return inv.Items[i].Type < inv.Items[j].Type } return inv.Items[i].Name < inv.Items[j].Name }) return inv, nil } // Deployable filters the inventory down to the types the target deploys. func (inv *Inventory) Deployable() []InventoryItem { var out []InventoryItem for _, it := range inv.Items { if it.Type == lockfile.TypeMcp { continue } out = append(out, it) } return out }