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:
@@ -0,0 +1,55 @@
|
||||
package discovery
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FsTree adapts a filesystem root to the Tree interface (used for scanning
|
||||
// the work repository's own items).
|
||||
type FsTree struct {
|
||||
Root string
|
||||
}
|
||||
|
||||
func (f *FsTree) Walk(fn func(rel string, isDir bool) error) error {
|
||||
var paths []string
|
||||
err := filepath.WalkDir(f.Root, func(p string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p == f.Root {
|
||||
return nil
|
||||
}
|
||||
paths = append(paths, p)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Strings(paths)
|
||||
for _, p := range paths {
|
||||
rel, err := filepath.Rel(f.Root, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := os.Stat(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fn(filepath.ToSlash(rel), info.IsDir()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FsTree) ReadFile(rel string) ([]byte, error) {
|
||||
clean := filepath.FromSlash(rel)
|
||||
if strings.Contains(clean, "..") {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
return os.ReadFile(filepath.Join(f.Root, clean))
|
||||
}
|
||||
Reference in New Issue
Block a user