feat: vendor add end-to-end with deterministic lockfile v2

- go-git in-process clone, default-branch/tag resolution, optional token auth
- web-tree URL normalization (github/gitlab) with implied discovery root
- discovery of skills/commands/agents/mcp, lenient frontmatter with warnings
- selection: all by default, --include with missing-entry hard errors
- external/<source>/<type>/ materialization preserving exec bits
- lockfile v2: deterministic JSON, pinned url/ref/rev, inventory, warnings
- offline black-box e2e suite against local fixture git repositories
This commit is contained in:
2026-08-22 21:52:30 +02:00
parent 2a6498dae1
commit 2c7200380b
16 changed files with 1648 additions and 2 deletions
+73
View File
@@ -0,0 +1,73 @@
package discovery
import (
"strings"
)
// Frontmatter holds the leniently parsed leading YAML block of an item.
type Frontmatter struct {
Name string
Description string
Tags []string
Requires []string
}
// ParseFrontmatter extracts name, description, tags and requires from a
// leading `---` fenced block. Parsing is deliberately lenient: a block that
// opens but never closes, or contains lines without a colon, yields warnings
// instead of failures; the parseable prefix still wins. Files without a
// leading fence have no frontmatter and no warning.
func ParseFrontmatter(data []byte) (Frontmatter, []string) {
var fm Frontmatter
var warnings []string
lines := strings.Split(strings.ReplaceAll(string(data), "\r\n", "\n"), "\n")
if len(lines) == 0 || strings.TrimSpace(lines[0]) != "---" {
return fm, nil
}
closed := false
for _, line := range lines[1:] {
if strings.TrimSpace(line) == "---" {
closed = true
break
}
trimmed := strings.TrimSpace(line)
if trimmed == "" {
continue
}
key, value, ok := strings.Cut(trimmed, ":")
if !ok {
warnings = append(warnings, "malformed frontmatter line (missing colon): "+trimmed)
continue
}
key = strings.TrimSpace(key)
value = strings.TrimSpace(value)
switch strings.ToLower(key) {
case "name":
fm.Name = value
case "description":
fm.Description = value
case "tags":
fm.Tags = splitList(value)
case "requires":
fm.Requires = splitList(value)
}
}
if !closed {
warnings = append(warnings, "frontmatter block not closed")
}
return fm, warnings
}
func splitList(v string) []string {
if v == "" {
return nil
}
parts := strings.Split(v, ",")
var out []string
for _, p := range parts {
if t := strings.TrimSpace(p); t != "" {
out = append(out, t)
}
}
return out
}