| package bundle |
|
|
| import ( |
| "path/filepath" |
| "strings" |
|
|
| "huggingface.co/turenlabs/Vigil/source/pkg/types" |
| ) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func resolveReferences(b *Bundle) { |
| for _, f := range b.Files { |
| referenced, sources := fileIsReferenced(b.Skill, b.AllowedTools, f) |
| f.Referenced = referenced |
| f.RefSources = sources |
| } |
| } |
|
|
| |
| |
| |
| func fileIsReferenced(skill *types.SkillFile, allowedTools []string, f *File) (bool, []string) { |
| forms := referenceForms(f.RelPath) |
| if len(forms) == 0 { |
| return false, nil |
| } |
|
|
| var sources []string |
| seen := map[string]bool{} |
| addSource := func(s string) { |
| if !seen[s] { |
| seen[s] = true |
| sources = append(sources, s) |
| } |
| } |
|
|
| |
| for _, tool := range allowedTools { |
| if containsAnyForm(tool, forms) { |
| addSource("allowed-tools") |
| break |
| } |
| } |
|
|
| if skill != nil { |
| |
| |
| |
| |
| bodyHit := containsAnyForm(skill.Body, forms) |
| if bodyHit { |
| addSource("body") |
| if hasMarkdownLink(skill.Body, forms) { |
| addSource("markdown-link") |
| } |
| if hasCodeSpan(skill.Body, forms) { |
| addSource("code-span") |
| } |
| } |
|
|
| |
| |
| if !bodyHit && containsAnyForm(skill.RawContent, forms) { |
| addSource("frontmatter") |
| if hasMarkdownLink(skill.RawContent, forms) { |
| addSource("markdown-link") |
| } |
| if hasCodeSpan(skill.RawContent, forms) { |
| addSource("code-span") |
| } |
| } |
|
|
| if containsAnyForm(skill.Description, forms) { |
| addSource("body") |
| } |
| for _, t := range skill.Triggers { |
| if containsAnyForm(t, forms) { |
| addSource("frontmatter") |
| break |
| } |
| } |
| } |
|
|
| return len(sources) > 0, sources |
| } |
|
|
| |
| |
| |
| |
| func referenceForms(relPath string) []string { |
| rel := filepath.ToSlash(strings.TrimSpace(relPath)) |
| if rel == "" || rel == "." || rel == ".." { |
| return nil |
| } |
| base := pathBase(rel) |
|
|
| forms := []string{} |
| add := func(s string) { |
| s = strings.TrimSpace(s) |
| if s == "" || s == "." { |
| return |
| } |
| for _, existing := range forms { |
| if existing == s { |
| return |
| } |
| } |
| forms = append(forms, s) |
| } |
|
|
| add(base) |
| add(rel) |
| add("./" + rel) |
| if base != rel { |
| add("./" + base) |
| } |
| return forms |
| } |
|
|
| |
| func pathBase(slashPath string) string { |
| if i := strings.LastIndex(slashPath, "/"); i >= 0 { |
| return slashPath[i+1:] |
| } |
| return slashPath |
| } |
|
|
| |
| |
| |
| |
| func containsAnyForm(haystack string, forms []string) bool { |
| if haystack == "" { |
| return false |
| } |
| for _, form := range forms { |
| if tokenMatch(haystack, form) { |
| return true |
| } |
| } |
| return false |
| } |
|
|
| |
| |
| |
| |
| func tokenMatch(haystack, needle string) bool { |
| if needle == "" { |
| return false |
| } |
| |
| |
| if strings.ContainsAny(needle, "/") || strings.HasPrefix(needle, "./") { |
| return strings.Contains(haystack, needle) |
| } |
|
|
| from := 0 |
| for { |
| idx := strings.Index(haystack[from:], needle) |
| if idx < 0 { |
| return false |
| } |
| start := from + idx |
| end := start + len(needle) |
|
|
| leftOK := start == 0 || !isFilenameByte(haystack[start-1]) |
| rightOK := end == len(haystack) || !isFilenameByte(haystack[end]) |
| if leftOK && rightOK { |
| return true |
| } |
| from = start + 1 |
| if from >= len(haystack) { |
| return false |
| } |
| } |
| } |
|
|
| |
| func isFilenameByte(b byte) bool { |
| switch { |
| case b >= 'a' && b <= 'z': |
| return true |
| case b >= 'A' && b <= 'Z': |
| return true |
| case b >= '0' && b <= '9': |
| return true |
| case b == '_' || b == '-' || b == '.' || b == '/': |
| return true |
| } |
| return false |
| } |
|
|
| |
| |
| func hasMarkdownLink(haystack string, forms []string) bool { |
| for _, form := range forms { |
| |
| from := 0 |
| for { |
| idx := strings.Index(haystack[from:], "](") |
| if idx < 0 { |
| break |
| } |
| start := from + idx + 2 |
| closeIdx := strings.Index(haystack[start:], ")") |
| if closeIdx < 0 { |
| break |
| } |
| target := haystack[start : start+closeIdx] |
| if strings.Contains(target, form) { |
| return true |
| } |
| from = start + closeIdx + 1 |
| if from >= len(haystack) { |
| break |
| } |
| } |
| } |
| return false |
| } |
|
|
| |
| func hasCodeSpan(haystack string, forms []string) bool { |
| for _, form := range forms { |
| from := 0 |
| for { |
| open := strings.Index(haystack[from:], "`") |
| if open < 0 { |
| break |
| } |
| openAbs := from + open + 1 |
| close := strings.Index(haystack[openAbs:], "`") |
| if close < 0 { |
| break |
| } |
| span := haystack[openAbs : openAbs+close] |
| if strings.Contains(span, form) { |
| return true |
| } |
| from = openAbs + close + 1 |
| if from >= len(haystack) { |
| break |
| } |
| } |
| } |
| return false |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func parseAllowedTools(rawContent string) []string { |
| fm := frontmatterRegion(rawContent) |
| if fm == "" { |
| return nil |
| } |
|
|
| lines := strings.Split(fm, "\n") |
| var tools []string |
| inBlock := false |
|
|
| for _, line := range lines { |
| trimmedRight := strings.TrimRight(line, " \t\r") |
| trimmed := strings.TrimSpace(trimmedRight) |
|
|
| if inBlock { |
| if strings.HasPrefix(trimmed, "- ") { |
| tools = append(tools, splitToolList(trimmed[2:])...) |
| continue |
| } |
| |
| if isTopLevelKey(trimmedRight) { |
| inBlock = false |
| |
| } else if trimmed == "" { |
| continue |
| } else { |
| inBlock = false |
| } |
| } |
|
|
| key, val, ok := splitKey(trimmedRight) |
| if !ok { |
| continue |
| } |
| if !isAllowedToolsKey(key) { |
| continue |
| } |
|
|
| val = strings.TrimSpace(val) |
| switch { |
| case val == "": |
| |
| inBlock = true |
| case strings.HasPrefix(val, "[") && strings.HasSuffix(val, "]"): |
| inner := strings.TrimSuffix(strings.TrimPrefix(val, "["), "]") |
| tools = append(tools, splitToolList(inner)...) |
| default: |
| tools = append(tools, splitToolList(val)...) |
| } |
| } |
|
|
| return dedupeNonEmpty(tools) |
| } |
|
|
| |
| |
| func frontmatterRegion(rawContent string) string { |
| trimmed := strings.TrimLeft(rawContent, " \t\r\n") |
| if !strings.HasPrefix(trimmed, "---") { |
| return "" |
| } |
| rest := trimmed[3:] |
| |
| if nl := strings.IndexByte(rest, '\n'); nl >= 0 { |
| rest = rest[nl+1:] |
| } else { |
| return "" |
| } |
| idx := strings.Index(rest, "\n---") |
| if idx < 0 { |
| |
| if strings.HasPrefix(rest, "---") { |
| return "" |
| } |
| return "" |
| } |
| return rest[:idx] |
| } |
|
|
| |
| |
| func isAllowedToolsKey(key string) bool { |
| k := strings.ToLower(strings.TrimSpace(key)) |
| return k == "allowed-tools" || k == "allowed_tools" || k == "allowedtools" || k == "tools" |
| } |
|
|
| |
| func isTopLevelKey(rawLine string) bool { |
| if rawLine == "" { |
| return false |
| } |
| if rawLine[0] == ' ' || rawLine[0] == '\t' { |
| return false |
| } |
| _, _, ok := splitKey(rawLine) |
| return ok |
| } |
|
|
| |
| |
| func splitKey(line string) (key, val string, ok bool) { |
| colon := strings.Index(line, ":") |
| if colon <= 0 { |
| return "", "", false |
| } |
| return strings.TrimSpace(line[:colon]), line[colon+1:], true |
| } |
|
|
| |
| |
| |
| func splitToolList(s string) []string { |
| s = strings.TrimSpace(s) |
| if s == "" { |
| return nil |
| } |
| var out []string |
| for _, part := range strings.Split(s, ",") { |
| p := strings.TrimSpace(part) |
| p = strings.Trim(p, "'\"") |
| if p != "" { |
| out = append(out, p) |
| } |
| } |
| return out |
| } |
|
|
| |
| func dedupeNonEmpty(in []string) []string { |
| if len(in) == 0 { |
| return nil |
| } |
| seen := map[string]bool{} |
| var out []string |
| for _, s := range in { |
| s = strings.TrimSpace(s) |
| if s == "" || seen[s] { |
| continue |
| } |
| seen[s] = true |
| out = append(out, s) |
| } |
| return out |
| } |
|
|