Why: Headless runs need the pi tool names, walked context files, prompt includes, and a trust gate before project settings are loaded. Evidence: go test -count=1 ./runtime/tools/ ./runtime/resources/ ./runtime/skills/ Impact: Existing tool names stay registered. Aliases are added only when the underlying tool exists. Refs: feature/pi-headless N4 Co-authored-by: Cursor <cursoragent@cursor.com>
45 lines
993 B
Go
45 lines
993 B
Go
package resources
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Expand replaces {{var}} placeholders and /name includes.
|
|
// An include reads <dir>/<name>.md. Unknown variables are left intact.
|
|
func Expand(text, dir string, vars map[string]string) (string, error) {
|
|
out := text
|
|
for key, value := range vars {
|
|
out = strings.ReplaceAll(out, "{{"+key+"}}", value)
|
|
}
|
|
for {
|
|
i := strings.Index(out, "/")
|
|
if i < 0 {
|
|
break
|
|
}
|
|
rest := out[i+1:]
|
|
j := 0
|
|
for j < len(rest) && isName(rest[j]) {
|
|
j++
|
|
}
|
|
if j == 0 || (i > 0 && out[i-1] != ' ' && out[i-1] != '\n') {
|
|
break
|
|
}
|
|
name := rest[:j]
|
|
raw, err := os.ReadFile(filepath.Join(dir, name+".md"))
|
|
if err != nil {
|
|
break
|
|
}
|
|
out = out[:i] + string(raw) + out[i+1+j:]
|
|
}
|
|
for key, value := range vars {
|
|
out = strings.ReplaceAll(out, "{{"+key+"}}", value)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func isName(b byte) bool {
|
|
return b == '-' || b == '_' || (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
|
|
}
|