Skip to content

Commit 198287f

Browse files
Fix: Phase 0: Add --skill infrastructure (#473)
* Fix: Phase 0: Add --skill infrastructure * Apply changes from Holon * Apply changes from Holon * Delete pkg/builtin/skills/github/solve/SKILL.md * Apply changes from Holon --------- Co-authored-by: holonbot[bot] <250454749+holonbot[bot]@users.noreply.github.com> Co-authored-by: jolestar <jolestar@gmail.com>
1 parent 7b95d0d commit 198287f

File tree

8 files changed

+935
-15
lines changed

8 files changed

+935
-15
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,7 @@ dist
3737

3838
# Exception: allow .claude in agents/claude (needed for agent development)
3939
!agents/claude/.claude/
40+
41+
# Builtin skills (generated copy from repository-level skills/)
42+
# Run 'go generate ./pkg/builtin' to regenerate
43+
pkg/builtin/skills/

Makefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,16 @@ build: build-host
2121
## build-host: Build runner CLI for current OS/Arch with version info
2222
build-host:
2323
@echo "Building runner CLI (Version: $(VERSION), Commit: $(COMMIT))..."
24+
@echo "Generating builtin skills..."
25+
@go generate ./pkg/builtin
2426
@mkdir -p $(BIN_DIR)
2527
go build $(LDFLAGS) -o $(BIN_DIR)/$(BINARY_NAME) ./cmd/holon
2628

2729
## release-build: Build binaries for multiple platforms
2830
release-build:
2931
@echo "Building release binaries..."
32+
@echo "Generating builtin skills..."
33+
@go generate ./pkg/builtin
3034
@mkdir -p $(BIN_DIR)
3135
@echo "Building for linux/amd64..."
3236
GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(BINARY_NAME)-linux-amd64 ./cmd/holon
@@ -42,6 +46,8 @@ test: test-go
4246
## test-go: Run Go tests with structured output
4347
test-go:
4448
@echo "Running Go tests..."
49+
@echo "Generating builtin skills..."
50+
@go generate ./pkg/builtin
4551
@if command -v gotestfmt > /dev/null 2>&1; then \
4652
go test ./... -json -v 2>&1 | gotestfmt; \
4753
else \
@@ -52,11 +58,15 @@ test-go:
5258
## test-raw: Run Go tests without gotestfmt (plain output)
5359
test-raw:
5460
@echo "Running Go tests with plain output..."
61+
@echo "Generating builtin skills..."
62+
@go generate ./pkg/builtin
5563
go test ./... -v
5664

5765
## test-unit: Run unit tests (non-integration) with structured output
5866
test-unit:
5967
@echo "Running unit tests..."
68+
@echo "Generating builtin skills..."
69+
@go generate ./pkg/builtin
6070
@if command -v gotestfmt > /dev/null 2>&1; then \
6171
go test $$(go list ./... | grep -v '^github.com/holon-run/holon/tests/') -short -json -v 2>&1 | gotestfmt; \
6272
else \
@@ -67,6 +77,8 @@ test-unit:
6777
## test-unit-raw: Run unit tests (non-integration) without gotestfmt
6878
test-unit-raw:
6979
@echo "Running unit tests with plain output..."
80+
@echo "Generating builtin skills..."
81+
@go generate ./pkg/builtin
7082
go test $$(go list ./... | grep -v '^github.com/holon-run/holon/tests/') -short -v
7183

7284
## test-agent: Run agent TypeScript tests

cmd/holon/main.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,21 @@ var runCmd = &cobra.Command{
220220
RunE: func(cmd *cobra.Command, args []string) error {
221221
ctx := context.Background()
222222

223+
// Validate --skill/--skills and --mode mutual exclusivity
224+
// Parse CLI skills
225+
cliSkills := skillPaths
226+
for _, s := range skills.ParseSkillsList(skillsList) {
227+
cliSkills = append(cliSkills, s)
228+
}
229+
// Check if --skill/--skills was explicitly provided
230+
hasSkills := cmd.Flags().Changed("skill") || cmd.Flags().Changed("skills")
231+
// Check if --mode was explicitly provided (not default)
232+
hasMode := cmd.Flags().Changed("mode")
233+
if hasSkills && hasMode {
234+
return fmt.Errorf("--skill/--skills and --mode flags are mutually exclusive (both were provided)")
235+
}
236+
// Note: If only one of --mode or --skill/--skills is provided, it will control the behavior
237+
223238
// Resolve workspace path early for auto-detection
224239
absWorkspace, err := filepath.Abs(workspacePath)
225240
if err != nil {

pkg/builtin/embed.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Package builtin provides embedded builtin skills for Holon.
2+
// Builtin skills are embedded in the binary using Go's embed package
3+
// and can be loaded by reference (e.g., "github/solve").
4+
//
5+
//go:generate sh -c "rm -rf skills && cp -r ../../skills ."
6+
package builtin
7+
8+
import (
9+
"embed"
10+
"errors"
11+
"io/fs"
12+
"path/filepath"
13+
)
14+
15+
//go:embed skills/*
16+
var builtinSkills embed.FS
17+
18+
// FS returns the embedded skills filesystem, rooted at the skills directory.
19+
// The returned FS provides access to all builtin skills.
20+
func FS() fs.FS {
21+
// Create a subfilesystem rooted at the skills directory
22+
sub, err := fs.Sub(builtinSkills, "skills")
23+
if err != nil {
24+
// This should never happen if the embed path is correct
25+
// Return a nil filesystem so callers can detect unavailability
26+
return nil
27+
}
28+
return sub
29+
}
30+
31+
// Has checks if a builtin skill exists at the given reference.
32+
// The reference is a slash-separated path like "github/solve".
33+
// Returns true if the skill directory exists and contains SKILL.md.
34+
func Has(ref string) bool {
35+
f := FS()
36+
if f == nil {
37+
return false
38+
}
39+
40+
// Check if SKILL.md exists in the skill directory
41+
skillManifestPath := filepath.Join(ref, "SKILL.md")
42+
_, err := fs.Stat(f, skillManifestPath)
43+
return err == nil
44+
}
45+
46+
// Load reads the SKILL.md file for a builtin skill.
47+
// The reference is a slash-separated path like "github/solve".
48+
// Returns the contents of SKILL.md or an error if the skill doesn't exist.
49+
func Load(ref string) ([]byte, error) {
50+
f := FS()
51+
if f == nil {
52+
return nil, errors.New("builtin skills filesystem not available")
53+
}
54+
55+
skillManifestPath := filepath.Join(ref, "SKILL.md")
56+
return fs.ReadFile(f, skillManifestPath)
57+
}
58+
59+
// LoadDir reads the entire skill directory for a builtin skill.
60+
// Returns a map of filename to content for all files in the skill directory.
61+
func LoadDir(ref string) (map[string][]byte, error) {
62+
f := FS()
63+
if f == nil {
64+
return nil, errors.New("builtin skills filesystem not available")
65+
}
66+
67+
files := make(map[string][]byte)
68+
69+
// Walk the skill directory and read all files
70+
err := fs.WalkDir(f, ref, func(path string, d fs.DirEntry, err error) error {
71+
if err != nil {
72+
return err
73+
}
74+
75+
// Skip directories
76+
if d.IsDir() {
77+
return nil
78+
}
79+
80+
// Read the file
81+
content, err := fs.ReadFile(f, path)
82+
if err != nil {
83+
return err
84+
}
85+
86+
// Store with relative path from skill root
87+
relPath := path
88+
if ref != "." {
89+
// Ensure path is long enough to avoid slicing panic
90+
if len(path) > len(ref)+1 {
91+
relPath = path[len(ref)+1:]
92+
} else {
93+
relPath = filepath.Base(path)
94+
}
95+
}
96+
files[relPath] = content
97+
98+
return nil
99+
})
100+
101+
if err != nil {
102+
return nil, err
103+
}
104+
105+
return files, nil
106+
}
107+
108+
// List returns all builtin skill references.
109+
// A skill reference is returned if the directory contains a SKILL.md file.
110+
func List() ([]string, error) {
111+
f := FS()
112+
if f == nil {
113+
return nil, errors.New("builtin skills filesystem not available")
114+
}
115+
116+
var skills []string
117+
118+
// Walk the entire filesystem
119+
err := fs.WalkDir(f, ".", func(path string, d fs.DirEntry, err error) error {
120+
if err != nil {
121+
return err
122+
}
123+
124+
// Check if this is a SKILL.md file
125+
if !d.IsDir() && d.Name() == "SKILL.md" {
126+
// Get the parent directory as the skill reference
127+
skillRef := filepath.Dir(path)
128+
if skillRef == "." {
129+
skillRef = ""
130+
}
131+
skills = append(skills, skillRef)
132+
}
133+
134+
return nil
135+
})
136+
137+
if err != nil {
138+
return nil, err
139+
}
140+
141+
return skills, nil
142+
}

0 commit comments

Comments
 (0)