fix: add internal/opsctl/tasks and narrow .gitignore tasks/ pattern

internal/opsctl/tasks was untracked because .gitignore had 'tasks/'
which matched any tasks directory. Change to '/tasks/' to only ignore
the root-level tasks/ dir (Ralph workspace), allowing internal/opsctl/tasks.
This commit is contained in:
ZanzyTHEbar 2026-02-22 15:30:06 +00:00
parent 7f6298e240
commit 3ead775a10
3 changed files with 763 additions and 2 deletions

4
.gitignore vendored
View file

@ -11,7 +11,7 @@ bin/
/dragonscale-test /dragonscale-test
cmd/dragonscale/workspace cmd/dragonscale/workspace
# Picoclaw specific # DragonScale specific
# DragonScale # DragonScale
.dragonscale/ .dragonscale/
@ -34,7 +34,7 @@ coverage.html
# Ralph workspace # Ralph workspace
ralph/ ralph/
.ralph/ .ralph/
tasks/ /tasks/
# IDE / Editors # IDE / Editors
.vscode/ .vscode/

View file

@ -0,0 +1,508 @@
package tasks
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/ZanzyTHEbar/dragonscale/internal/opsctl/app"
"github.com/ZanzyTHEbar/dragonscale/internal/opsctl/format"
"github.com/ZanzyTHEbar/dragonscale/internal/opsctl/runner"
)
const (
defaultBinaryName = "dragonscale"
defaultBuildDir = "bin"
defaultCmdDir = "cmd/dragonscale"
)
type shellTask struct {
name string
description string
script func(*app.Context) string
env func(*app.Context) []string
}
func (t shellTask) Name() string {
return t.name
}
func (t shellTask) Description() string {
return t.description
}
func (t shellTask) Run(ctx context.Context, rnr runner.Runner, c *app.Context) (app.Result, error) {
script := t.script(c)
fullScript := applyDevcontainerWrapper(script, c)
workDir := c.Root
if c.Cwd != "" {
workDir = c.Cwd
}
spec := runner.CommandSpec{
Name: "bash",
Args: []string{"-lc", fullScript},
Dir: workDir,
Env: mergeEnv(c, t.env),
InheritOutput: c.Format == format.OutputRaw,
}
result, err := rnr.Run(ctx, spec)
res := app.Result{
Task: t.name,
Command: script,
Stdout: result.Stdout,
Stderr: result.Stderr,
Err: func() string {
if err != nil {
return err.Error()
}
return ""
}(),
ExitCode: result.ExitCode,
}
if err == nil {
return res, nil
}
return res, err
}
type helpTask struct {
app *app.App
}
func (t helpTask) Name() string {
return "help"
}
func (t helpTask) Description() string {
return "Show task help"
}
func (t helpTask) Run(_ context.Context, _ runner.Runner, c *app.Context) (app.Result, error) {
if c == nil || t.app == nil {
return app.Result{
Task: "help",
Command: "help",
ExitCode: 0,
}, nil
}
lines := []string{
"Usage: opsctl [global flags] <command> [args]",
"",
"Global flags:",
" --format string output mode: text, json, raw (default text)",
" --json shortcut for --format json",
" --raw shortcut for --format raw",
" --no-color disable color output",
" --quiet suppress non-error output",
" --root string repository root path (default current directory)",
" --cwd string working directory for command execution",
" --timeout value command timeout (ex: 2m, 30s)",
"",
"Tasks:",
}
lines = append(lines, t.app.TaskHelp()...)
output := strings.Join(lines, "\n")
return app.Result{
Task: "help",
Command: "help",
Stdout: output,
ExitCode: 0,
}, nil
}
func NewHelpTask(app *app.App) app.Task {
return helpTask{app: app}
}
func mergeEnv(c *app.Context, custom func(*app.Context) []string) []string {
base := defaultEnv(c)
if custom == nil {
return base
}
return append(base, custom(c)...)
}
func NewShellTask(name, description string, script func(*app.Context) string, env func(*app.Context) []string) shellTask {
return shellTask{name: name, description: description, script: script, env: env}
}
func NewRegistry(_ string) []app.Task {
tasks := []app.Task{
NewShellTask("all", "Build the dragonscale binary for current platform", buildScript, nil),
NewShellTask("generate", "Run generate", generateScript, nil),
NewShellTask("build", "Build the dragonscale binary for current platform", buildScript, nil),
NewShellTask("build-all", "Build dragonscale for all platforms", buildAllScript, nil),
NewShellTask("install", "Install dragonscale to system and copy builtin skills", installScript, nil),
NewShellTask("uninstall", "Uninstall dragonscale from system", uninstallScript, nil),
NewShellTask("uninstall-all", "Uninstall dragonscale and workspace data", uninstallAllScript, nil),
NewShellTask("clean", "Remove build artifacts", cleanScript, nil),
NewShellTask("vet", "Run go vet for static analysis", staticGoScript("vet ./..."), nil),
NewShellTask("test", "Run tests", staticGoScript("test ./..."), nil),
NewShellTask("fmt", "Format Go code", staticGoScript("fmt ./..."), nil),
NewShellTask("lint", "Run all linting checks", lintScript, nil),
NewShellTask("hooks", "Install git hooks", hooksScript, nil),
NewShellTask("deps", "Download dependencies", staticGoScript("mod download && mod verify"), nil),
NewShellTask("update-deps", "Update dependencies", staticGoScript("get -u ./... && mod tidy"), nil),
NewShellTask("sqlc-check", "Verify sqlc generation is idempotent", sqlcCheckScript, nil),
NewShellTask("flatc-check", "Verify flatc generation is idempotent", flatcCheckScript, nil),
NewShellTask("sqlc-vet", "Run sqlc vet rules", sqlcVetScript, nil),
NewShellTask("fantasy-check", "Compare vendored Fantasy SDK against upstream", simpleScript("./scripts/sync-fantasy.sh --check"), nil),
NewShellTask("fantasy-diff", "Show diff between vendored and upstream", fantasyDiffScript, nil),
NewShellTask("fantasy-sync", "Full sync of vendored Fantasy SDK", fantasySyncScript, nil),
NewShellTask("fantasy-patch", "Save local modifications as a patch", fantasyPatchScript, nil),
NewShellTask("test-integration", "Run integration tests", staticGoScript("-tags integration -count=1 -timeout 120s -v ./pkg/memory/..."), nil),
NewShellTask("check", "Run deps, formatting, linting and tests", checkScript, nil),
NewShellTask("run", "Build and run dragonscale", runScript, runTaskEnv),
NewShellTask("devcontainer-build", "Build the local devcontainer image via npx", devcontainerBuildScript, nil),
NewShellTask("devcontainer-up", "Start/update the local devcontainer", devcontainerUpScript, nil),
NewShellTask("devcontainer-generate", "Run generation in devcontainer", devcontainerGenerateScript, nil),
NewShellTask("devcontainer-verify", "Verify generated code in devcontainer", devcontainerVerifyScript, nil),
NewShellTask("eval-build", "Build the eval runner from current branch", evalBuildScript, nil),
NewShellTask("eval", "Run the eval suite", evalRunScript, nil),
NewShellTask("eval-fixtures", "Prepare eval fixture workspace", evalFixturesScript, nil),
NewShellTask("eval-view", "Open the promptfoo results viewer", evalViewScript, nil),
NewShellTask("eval-clean", "Cleanup eval artifacts", simpleScript("rm -rf eval/results eval/bin"), nil),
NewShellTask("eval-compare", "Run A/B comparison of current branch vs main", simpleScript("cd eval && DEVCONTAINER_EXEC= EVAL_NPM_CMD=$(npx --yes) ./scripts/compare.sh --repeat 3"), nil),
NewShellTask("eval-test", "Run Go-native component evals", staticGoScript("-v ./eval/go_evals/..."), nil),
}
return tasks
}
func defaultEnv(c *app.Context) []string {
env := []string{
fmt.Sprintf("GO=%s", cEnv(c, "GO", "go")),
fmt.Sprintf("GOFLAGS=%s", cEnv(c, "GOFLAGS", "-v -trimpath -tags stdjson")),
fmt.Sprintf("CGO_ENABLED=%s", cEnv(c, "CGO_ENABLED", "1")),
fmt.Sprintf("BINARY_NAME=%s", cEnv(c, "BINARY_NAME", defaultBinaryName)),
fmt.Sprintf("BUILD_DIR=%s", cEnv(c, "BUILD_DIR", defaultBuildDir)),
fmt.Sprintf("CMD_DIR=%s", cEnv(c, "CMD_DIR", defaultCmdDir)),
fmt.Sprintf("WORKSPACE_DIR=%s", cEnv(c, "WORKSPACE_DIR", filepath.Join(homeDir(), ".dragonscale", "workspace"))),
}
if value := cEnv(c, "GOOS", ""); value != "" {
env = append(env, fmt.Sprintf("GOOS=%s", value))
}
if value := cEnv(c, "GOARCH", ""); value != "" {
env = append(env, fmt.Sprintf("GOARCH=%s", value))
}
if value := cEnv(c, "PLATFORM", ""); value != "" {
env = append(env, fmt.Sprintf("PLATFORM=%s", value))
}
if value := cEnv(c, "ARCH", ""); value != "" {
env = append(env, fmt.Sprintf("ARCH=%s", value))
}
if v := cEnv(c, "DEVCONTAINER_EXEC", ""); v != "" {
env = append(env, "DEVCONTAINER_EXEC="+v)
}
if value := cEnv(c, "DRAGONSCALE_EVAL_HOST_HOME", ""); value != "" {
env = append(env, fmt.Sprintf("DRAGONSCALE_EVAL_HOST_HOME=%s", value))
}
if value := cEnv(c, "DRAGONSCALE_EVAL_BASE_CONFIG", ""); value != "" {
env = append(env, fmt.Sprintf("DRAGONSCALE_EVAL_BASE_CONFIG=%s", value))
}
if value := cEnv(c, "DRAGONSCALE_EVAL_CONFIG", ""); value != "" {
env = append(env, fmt.Sprintf("DRAGONSCALE_EVAL_CONFIG=%s", value))
}
if value := cEnv(c, "DRAGONSCALE_EVAL_DEBUG", ""); value != "" {
env = append(env, fmt.Sprintf("DRAGONSCALE_EVAL_DEBUG=%s", value))
}
if value := cEnv(c, "VERSION", ""); value != "" {
env = append(env, fmt.Sprintf("VERSION=%s", value))
}
if value := cEnv(c, "FANTASY_VERSION", ""); value != "" {
env = append(env, fmt.Sprintf("FANTASY_VERSION=%s", value))
}
if value := cEnv(c, "NAME", ""); value != "" {
env = append(env, fmt.Sprintf("NAME=%s", value))
}
if value := cEnv(c, "ARGS", ""); value != "" {
env = append(env, fmt.Sprintf("ARGS=%s", value))
}
if c.Root != "" {
env = append(env, fmt.Sprintf("DRAGONSCALE_HOME=%s", filepath.Join(homeDir(), ".dragonscale")))
}
return env
}
func cEnv(c *app.Context, key string, fallback string) string {
if v, ok := c.ExtraEnv[key]; ok {
if strings.TrimSpace(v) != "" {
return v
}
}
return fallback
}
func cValue(c *app.Context, key string, fallback string) string {
if c == nil || c.ExtraEnv == nil {
return fallback
}
if v, ok := c.ExtraEnv[key]; ok {
return v
}
return fallback
}
func applyDevcontainerWrapper(script string, c *app.Context) string {
if value := cValue(c, "SKIP_DEVCONTAINER_WRAPPER", ""); value != "" {
if value != "0" {
return script
}
}
if _, err := os.Stat("/.dockerenv"); err == nil {
return script
}
execCmd := cEnv(c, "DEVCONTAINER_EXEC", "")
if execCmd == "" {
execCmd = detectDevcontainerExec(c.Root)
}
if execCmd == "" {
return script
}
if strings.Contains(script, "@devcontainers/cli") || strings.Contains(script, "devcontainer exec ") {
return script
}
workDir := c.Root
if c.Cwd != "" {
workDir = c.Cwd
}
if strings.TrimSpace(workDir) != "" {
script = fmt.Sprintf("cd %s\n%s", strconv.Quote(workDir), script)
}
return fmt.Sprintf("%s -- bash -lc %s", execCmd, strconv.Quote(script))
}
func detectDevcontainerExec(root string) string {
root = strings.TrimSpace(root)
if root == "" {
root = os.Getenv("DEVCONTAINER_WORKSPACE")
}
if root == "" {
if wd, err := os.Getwd(); err == nil {
root = wd
}
}
if root == "" {
return ""
}
devcontainerJSON := filepath.Join(root, ".devcontainer", "devcontainer.json")
if _, err := os.Stat(devcontainerJSON); err != nil {
legacyDevcontainer := filepath.Join(root, ".devcontainer", "devcontainer.yaml")
if _, legacyErr := os.Stat(legacyDevcontainer); legacyErr != nil {
return ""
}
}
if _, err := os.Stat(filepath.Join(root, ".devcontainer")); err != nil {
return ""
}
if _, err := exec.LookPath("npx"); err != nil {
return ""
}
workspace := os.Getenv("DEVCONTAINER_WORKSPACE")
if strings.TrimSpace(workspace) == "" {
workspace = root
}
cli := "npx --yes @devcontainers/cli"
return fmt.Sprintf("%s up --workspace-folder %q && %s exec --workspace-folder %q", cli, workspace, cli, workspace)
}
func homeDir() string {
home, _ := os.UserHomeDir()
return home
}
func scriptHeader() string {
return "set -euo pipefail\ngit config --global --add safe.directory \"$PWD\" >/dev/null 2>&1 || true\n"
}
func staticGoScript(rest string) func(*app.Context) string {
return func(*app.Context) string {
return scriptHeader() + "$GO " + rest + "\n"
}
}
func generateScript(*app.Context) string {
return scriptHeader() +
"rm -rf ./$(CMD_DIR)/workspace 2>/dev/null || true\n" +
"$GO generate ./...\n"
}
func buildScript(*app.Context) string {
version := "$(git describe --tags --always --dirty 2>/dev/null || echo \"dev\")"
gitCommit := "$(git rev-parse --short=8 HEAD 2>/dev/null || echo \"dev\")"
buildTime := "$(date +%FT%T%z)"
goVersion := "$($GO version | awk '{print \"$3\"}')"
ldFlags := fmt.Sprintf("-X main.version=%s -X main.gitCommit=%s -X main.buildTime=%s -X main.goVersion=%s -s -w", version, gitCommit, buildTime, goVersion)
return scriptHeader() +
"mkdir -p $(BUILD_DIR)\n" +
"GOOS=$($GO env GOOS)\n" +
"GOARCH=$($GO env GOARCH)\n" +
fmt.Sprintf("$GO build $GOFLAGS -ldflags \"%s\" -o $(BUILD_DIR)/$(BINARY_NAME)-${GOOS}-${GOARCH} ./$(CMD_DIR)\n", ldFlags) +
"ln -sf $(BINARY_NAME)-$(GOOS)-$(GOARCH) $(BUILD_DIR)/$(BINARY_NAME)\n"
}
func buildAllScript(*app.Context) string {
ldFlags := "-ldflags '-s -w'"
return scriptHeader() +
"mkdir -p $(BUILD_DIR)\n" +
fmt.Sprintf("GOOS=linux GOARCH=amd64 $GO build $GOFLAGS %s -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 ./$(CMD_DIR)\n", ldFlags) +
fmt.Sprintf("GOOS=linux GOARCH=arm64 $GO build $GOFLAGS %s -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64 ./$(CMD_DIR)\n", ldFlags) +
fmt.Sprintf("GOOS=linux GOARCH=loong64 $GO build $GOFLAGS %s -o $(BUILD_DIR)/$(BINARY_NAME)-linux-loong64 ./$(CMD_DIR)\n", ldFlags) +
fmt.Sprintf("GOOS=linux GOARCH=riscv64 $GO build $GOFLAGS %s -o $(BUILD_DIR)/$(BINARY_NAME)-linux-riscv64 ./$(CMD_DIR)\n", ldFlags) +
fmt.Sprintf("GOOS=darwin GOARCH=arm64 $GO build $GOFLAGS %s -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-arm64 ./$(CMD_DIR)\n", ldFlags) +
fmt.Sprintf("GOOS=windows GOARCH=amd64 $GO build $GOFLAGS %s -o $(BUILD_DIR)/$(BINARY_NAME)-windows-amd64.exe ./$(CMD_DIR)\n", ldFlags)
}
func installScript(*app.Context) string {
prefix := filepath.Join(homeDir(), ".local")
return scriptHeader() +
"mkdir -p ${INSTALL_PREFIX:-" + prefix + "}/bin\n" +
"cp $(BUILD_DIR)/$(BINARY_NAME) ${INSTALL_PREFIX:-" + prefix + "}/bin/$(BINARY_NAME)\n" +
"chmod +x ${INSTALL_PREFIX:-" + prefix + "}/bin/$(BINARY_NAME)\n"
}
func uninstallScript(*app.Context) string {
return "rm -f ${INSTALL_PREFIX:-" + filepath.Join(homeDir(), ".local") + "}/bin/$(BINARY_NAME)\n"
}
func uninstallAllScript(*app.Context) string {
return "rm -rf $(DRAGONSCALE_HOME)\n"
}
func cleanScript(*app.Context) string {
return "rm -rf $(BUILD_DIR)\n"
}
func lintScript(*app.Context) string {
return scriptHeader() +
"$GO fmt ./...\n$GO vet ./...\n$GO build ./...\n"
}
func hooksScript(*app.Context) string {
return "ln -sf ../../scripts/hooks/pre-commit .git/hooks/pre-commit\nln -sf ../../scripts/hooks/commit-msg .git/hooks/commit-msg\nchmod +x .git/hooks/pre-commit .git/hooks/commit-msg\n"
}
func sqlcCheckScript(*app.Context) string {
return scriptHeader() +
"repo=\"$PWD\"\n" +
"before=\"$(mktemp)\"\nafter=\"$(mktemp)\"\n" +
"snapshot() { git -c safe.directory=\"$repo\" diff --binary -- pkg/memory/sqlc/; git -c safe.directory=\"$repo\" ls-files --others --exclude-standard -- pkg/memory/sqlc/ | LC_ALL=C sort | while IFS= read -r f; do [ -f \"$f\" ] && sha256sum \"$f\"; done; };\n" +
"snapshot > \"$before\"\nsqlc generate -f pkg/memory/sqlc/sqlc.yaml\nsnapshot > \"$after\"\n" +
"if ! cmp -s \"$before\" \"$after\"; then echo \"::error::sqlc generated code is stale. Run 'sqlc generate -f pkg/memory/sqlc/sqlc.yaml' and commit.\"; rm -f \"$before\" \"$after\"; exit 1; fi\nrm -f \"$before\" \"$after\"\n"
}
func flatcCheckScript(*app.Context) string {
return scriptHeader() +
"repo=\"$PWD\"\n" +
"before=\"$(mktemp)\"\nafter=\"$(mktemp)\"\n" +
"snapshot() { git -c safe.directory=\"$repo\" diff --binary -- pkg/itr/itrfb/ pkg/tools/mapopsfb/; git -c safe.directory=\"$repo\" ls-files --others --exclude-standard -- pkg/itr/itrfb/ pkg/tools/mapopsfb/ | LC_ALL=C sort | while IFS= read -r f; do [ -f \"$f\" ] && sha256sum \"$f\"; done; };\n" +
"snapshot > \"$before\"\n" +
"$GO generate ./pkg/itr ./pkg/tools\nsnapshot > \"$after\"\n" +
"if ! cmp -s \"$before\" \"$after\"; then echo \"::error::FlatBuffers generated code is stale. Run 'go generate ./pkg/itr ./pkg/tools' and commit.\"; rm -f \"$before\" \"$after\"; exit 1; fi\nrm -f \"$before\" \"$after\"\n"
}
func sqlcVetScript(*app.Context) string {
return "sqlc vet -f pkg/memory/sqlc/sqlc.yaml\n"
}
func simpleScript(cmd string) func(*app.Context) string {
return func(*app.Context) string { return cmd + "\n" }
}
func fantasyDiffScript(c *app.Context) string {
return simpleScript("./scripts/sync-fantasy.sh --diff " + cEnv(c, "FANTASY_VERSION", ""))(c)
}
func fantasySyncScript(c *app.Context) string {
return simpleScript("./scripts/sync-fantasy.sh --sync " + cEnv(c, "FANTASY_VERSION", ""))(c)
}
func fantasyPatchScript(c *app.Context) string {
return simpleScript("./scripts/sync-fantasy.sh --save-patch " + cEnv(c, "NAME", ""))(c)
}
func checkScript(*app.Context) string {
return staticGoScript("mod download && mod verify")(&app.Context{}) +
staticGoScript("fmt ./...")(&app.Context{}) +
staticGoScript("vet ./...")(&app.Context{}) +
staticGoScript("test ./...")(&app.Context{})
}
func runTaskEnv(c *app.Context) []string {
if len(c.Argv) == 0 {
return nil
}
return []string{"ARGS=" + strings.Join(c.Argv, " ")}
}
func runScript(c *app.Context) string {
return scriptHeader() + "$(BUILD_DIR)/$(BINARY_NAME) " + strings.Join(quoteArgs(c.Argv), " ") + "\n"
}
func quoteArgs(args []string) []string {
quoted := make([]string, 0, len(args))
for _, arg := range args {
quoted = append(quoted, strconv.Quote(arg))
}
return quoted
}
func devcontainerBuildScript(*app.Context) string {
return "npx --yes @devcontainers/cli build --workspace-folder \"$(pwd)\"\n"
}
func devcontainerUpScript(*app.Context) string {
return "npx --yes @devcontainers/cli up --workspace-folder \"$(pwd)\"\n"
}
func devcontainerGenerateScript(*app.Context) string {
return "npx --yes @devcontainers/cli exec --workspace-folder \"$(pwd)\" -- bash -lc \"go generate ./pkg/itr ./pkg/tools && sqlc generate -f pkg/memory/sqlc/sqlc.yaml\"\n"
}
func devcontainerVerifyScript(*app.Context) string {
return "npx --yes @devcontainers/cli exec --workspace-folder \"$(pwd)\" -- bash -lc \"make flatc-check sqlc-check\"\n"
}
func evalBuildScript(*app.Context) string {
ldFlags := "-X main.version=$(git describe --tags --always --dirty 2>/dev/null || echo \"dev\") -X main.gitCommit=$(git rev-parse --short=8 HEAD 2>/dev/null || echo \"dev\") -X main.buildTime=$(date +%FT%T%z) -X main.goVersion=\"$($GO version | awk '{print \"$3\"}')\" -s -w"
return scriptHeader() +
"$GO generate ./...\n" +
"mkdir -p eval/bin\n" +
fmt.Sprintf("$GO build $GOFLAGS -ldflags \"%s\" -o eval/bin/eval-runner ./eval/cmd/eval-runner\n", ldFlags)
}
func evalRunScript(*app.Context) string {
return `if [ -n "${DRAGONSCALE_EVAL_BASE_CONFIG:-}" ] && [ -n "${DRAGONSCALE_EVAL_DEBUG:-}" ]; then
echo "DRAGONSCALE_EVAL_BASE_CONFIG=${DRAGONSCALE_EVAL_BASE_CONFIG}"
fi
if [ -n "${DRAGONSCALE_EVAL_DEBUG:-}" ]; then
echo "DRAGONSCALE_EVAL_CONFIG=${DRAGONSCALE_EVAL_CONFIG:-./configs/default.json}"
fi
DRAGONSCALE_EVAL_HOST_HOME=/host_home DRAGONSCALE_EVAL_CONFIG="${DRAGONSCALE_EVAL_CONFIG:-./configs/default.json}" npx --yes promptfoo eval --config promptfooconfig.yaml --no-cache --no-progress-bar
`
}
func evalFixturesScript(*app.Context) string {
return scriptHeader() +
"mkdir -p \"$HOME/.local/share/dragonscale/sandbox\"\n" +
"rm -f \"$HOME/.local/share/dragonscale/sandbox/eval_test_output.txt\" \"$HOME/.local/share/dragonscale/sandbox/test_steps.txt\" \"$HOME/.local/share/dragonscale/sandbox/eval_checkpoint.txt\" \"$HOME/.local/share/dragonscale/sandbox/chain_test.txt\" \"$HOME/.local/share/dragonscale/sandbox/current_year.txt\" \"$HOME/.local/share/dragonscale/sandbox/result.txt\" \"$HOME/.local/share/dragonscale/sandbox/progressive_test.txt\" \"$HOME/.local/share/dragonscale/sandbox/os_name.txt\"\n" +
"rm -rf \"$HOME/.local/share/dragonscale/sandbox/project\"\n" +
"printf 'dragonscale eval fixture — hello from the eval harness\\nThis is line two of the fixture file.\\n' > \"$HOME/.local/share/dragonscale/sandbox/eval_fixture.txt\"\n" +
"cp -f eval/fixtures/sample_data.txt \"$HOME/.local/share/dragonscale/sandbox/sample_data.txt\"\n" +
"mkdir -p \"$HOME/.local/share/dragonscale/skills\"\n" +
"cp -rf eval/fixtures/skills/* \"$HOME/.local/share/dragonscale/skills/\" 2>/dev/null || true\n"
}
func evalViewScript(*app.Context) string {
return "cd eval && npx --yes promptfoo view\n"
}

View file

@ -0,0 +1,253 @@
package tasks
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"github.com/ZanzyTHEbar/dragonscale/internal/opsctl/app"
"github.com/ZanzyTHEbar/dragonscale/internal/opsctl/runner"
"github.com/stretchr/testify/require"
)
func TestNewRegistryContainsCoreTasks(t *testing.T) {
t.Parallel()
tasks := NewRegistry("/workspaces/picoclaw")
names := make(map[string]struct{}, len(tasks))
for _, task := range tasks {
names[task.Name()] = struct{}{}
}
required := []string{
"build",
"build-all",
"check",
"clean",
"deps",
"devcontainer-build",
"devcontainer-up",
"devcontainer-generate",
"devcontainer-verify",
"eval",
"eval-build",
"eval-compare",
"eval-fixtures",
"eval-test",
"eval-view",
"eval-clean",
"generate",
"fmt",
"flatc-check",
"hooks",
"install",
"lint",
"run",
"sqlc-check",
"sqlc-vet",
"test",
"test-integration",
"uninstall",
"uninstall-all",
"update-deps",
"vet",
}
for _, name := range required {
_, ok := names[name]
require.Truef(t, ok, "missing core task %q in registry", name)
}
}
func TestRunTaskEnvUsesArgv(t *testing.T) {
t.Parallel()
ctx := &app.Context{
Argv: []string{"--verbose", "foo bar", "baz"},
}
got := runTaskEnv(ctx)
require.Len(t, got, 1)
require.Equal(t, "ARGS=--verbose foo bar baz", got[0])
}
func TestQuoteArgs(t *testing.T) {
t.Parallel()
got := quoteArgs([]string{"foo bar", "x\ty", `a"b`})
require.Equal(t, []string{"\"foo bar\"", "\"x\\ty\"", `"a\"b"`}, got)
}
func TestBuildAllTaskPreservesGoEnvironmentForwarding(t *testing.T) {
t.Parallel()
ctx := &app.Context{
Root: t.TempDir(),
ExtraEnv: map[string]string{
"GOOS": "freebsd",
"GOARCH": "sparc64",
"GOFLAGS": "-mod=mod",
"DEVCONTAINER_EXEC": "echo devcontainer exec",
},
}
fake := &runner.FakeRunner{
Result: runner.CommandResult{ExitCode: 0},
}
var buildAll app.Task
for _, task := range NewRegistry(ctx.Root) {
if task.Name() == "build-all" {
buildAll = task
break
}
}
require.NotNil(t, buildAll)
_, err := buildAll.Run(context.Background(), fake, ctx)
require.NoError(t, err)
require.Len(t, fake.Calls, 1)
script := strings.Join(fake.Calls[0].Args, " ")
require.Contains(t, script, "GOOS=linux GOARCH=amd64 $GO build")
require.Contains(t, script, "GOOS=darwin GOARCH=arm64 $GO build")
require.Contains(t, script, "GOOS=windows GOARCH=amd64 $GO build")
require.Contains(t, script, "GOOS=linux GOARCH=arm64 $GO build")
joinedEnv := strings.Join(fake.Calls[0].Env, " ")
require.Contains(t, joinedEnv, "GOOS=freebsd")
require.Contains(t, joinedEnv, "GOARCH=sparc64")
require.Contains(t, joinedEnv, "GOFLAGS=-mod=mod")
require.Contains(t, joinedEnv, "DEVCONTAINER_EXEC=echo devcontainer exec")
}
func TestEvalRunScriptPreservesEvalConfig(t *testing.T) {
t.Parallel()
script := evalRunScript(&app.Context{
ExtraEnv: map[string]string{
"DRAGONSCALE_EVAL_DEBUG": "1",
"DRAGONSCALE_EVAL_CONFIG": "./configs/override.json",
},
})
require.Contains(t, script, "DRAGONSCALE_EVAL_CONFIG=\"${DRAGONSCALE_EVAL_CONFIG:-./configs/default.json}\"")
require.Contains(t, script, "npx --yes promptfoo eval --config promptfooconfig.yaml")
require.NotContains(t, script, "DRAGONSCALE_EVAL_CONFIG=\"./configs/default.json\"")
compareScript, err := os.ReadFile(filepath.Clean(filepath.Join("..", "..", "..", "eval", "scripts", "compare.sh")))
require.NoError(t, err)
content := string(compareScript)
require.Contains(t, content, "EVAL_CONFIG=\"${DRAGONSCALE_EVAL_CONFIG:-./configs/default.json}\"")
require.Contains(t, content, "trap cleanup_compare_config EXIT INT TERM")
require.Contains(t, content, "DRAGONSCALE_EVAL_CONFIG: \"${EVAL_CONFIG}\"")
require.Contains(t, content, "TEMP_CONFIG")
}
func TestEvalRunScriptUsesBaseConfigWhenSet(t *testing.T) {
t.Parallel()
script := evalRunScript(&app.Context{
ExtraEnv: map[string]string{
"DRAGONSCALE_EVAL_BASE_CONFIG": "/tmp/base.json",
},
})
require.Contains(t, script, `if [ -n "${DRAGONSCALE_EVAL_BASE_CONFIG:-}" ] && [ -n "${DRAGONSCALE_EVAL_DEBUG:-}" ]; then`)
}
func TestEvalCompareTaskDisablesNestedDevcontainerExecution(t *testing.T) {
t.Parallel()
ctx := &app.Context{
Root: t.TempDir(),
ExtraEnv: map[string]string{
"DEVCONTAINER_EXEC": "npx --yes @devcontainers/cli exec --workspace-folder \"$PWD\" --",
"DRAGONSCALE_EVAL_DEBUG": "1",
},
}
fake := &runner.FakeRunner{
Result: runner.CommandResult{ExitCode: 0},
}
var evalCompare app.Task
for _, task := range NewRegistry(ctx.Root) {
if task.Name() == "eval-compare" {
evalCompare = task
break
}
}
require.NotNil(t, evalCompare)
_, err := evalCompare.Run(context.Background(), fake, ctx)
require.NoError(t, err)
require.Len(t, fake.Calls, 1)
script := strings.Join(fake.Calls[0].Args, " ")
require.Contains(t, script, "cd eval && DEVCONTAINER_EXEC= EVAL_NPM_CMD=$(npx --yes) ./scripts/compare.sh --repeat 3")
joinedEnv := strings.Join(fake.Calls[0].Env, " ")
require.Contains(t, joinedEnv, "DEVCONTAINER_EXEC=npx --yes @devcontainers/cli exec --workspace-folder \"$PWD\" --")
compareScript, err := os.ReadFile(filepath.Clean(filepath.Join("..", "..", "..", "eval", "scripts", "compare.sh")))
require.NoError(t, err)
content := string(compareScript)
require.Contains(t, content, "export DEVCONTAINER_EXEC=\"\"")
require.Contains(t, content, "make DEVCONTAINER_EXEC= eval-build")
}
func TestEvalCompareTaskPassesBaseConfigEnvToRunner(t *testing.T) {
t.Parallel()
cases := []struct {
name string
baseConfig *string
expectedIncluded bool
expectedValue string
}{
{name: "unset", baseConfig: nil, expectedIncluded: false},
{name: "empty", baseConfig: ptrString(""), expectedIncluded: false},
{name: "explicit", baseConfig: ptrString("/tmp/explicit.json"), expectedIncluded: true, expectedValue: "/tmp/explicit.json"},
{name: "invalid", baseConfig: ptrString("/tmp/missing-base-config.json"), expectedIncluded: true, expectedValue: "/tmp/missing-base-config.json"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
ctx := &app.Context{
Root: t.TempDir(),
ExtraEnv: map[string]string{
"DRAGONSCALE_EVAL_DEBUG": "1",
},
}
if tc.baseConfig != nil {
ctx.ExtraEnv["DRAGONSCALE_EVAL_BASE_CONFIG"] = *tc.baseConfig
}
fake := &runner.FakeRunner{
Result: runner.CommandResult{ExitCode: 0},
}
var evalCompare app.Task
for _, task := range NewRegistry(ctx.Root) {
if task.Name() == "eval-compare" {
evalCompare = task
break
}
}
require.NotNil(t, evalCompare)
_, err := evalCompare.Run(context.Background(), fake, ctx)
require.NoError(t, err)
require.Len(t, fake.Calls, 1)
joinedEnv := strings.Join(fake.Calls[0].Env, " ")
if tc.expectedIncluded {
require.Contains(t, joinedEnv, "DRAGONSCALE_EVAL_BASE_CONFIG="+tc.expectedValue)
} else {
require.NotContains(t, joinedEnv, "DRAGONSCALE_EVAL_BASE_CONFIG=")
}
})
}
}
func ptrString(value string) *string {
return &value
}