diff --git a/.github/workflows/yield-lab.yml b/.github/workflows/yield-lab.yml index 0fd1466e..28d5a1a3 100644 --- a/.github/workflows/yield-lab.yml +++ b/.github/workflows/yield-lab.yml @@ -11,6 +11,25 @@ permissions: contents: read jobs: + agent-registration: + name: Agent registration (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + - uses: ./.github/actions/go-setup + with: + go-version-file: labs/22-yield/yield/go.mod + cache-dependency-path: labs/22-yield/yield/go.sum + - name: Test registry and generated adapters + working-directory: labs/22-yield/yield + env: + GOWORK: "off" + run: go test ./cmd/yskill + validate: runs-on: ubuntu-latest steps: diff --git a/labs/22-yield/distribution/release-notes/2026-08-02-cross-agent-registration.md b/labs/22-yield/distribution/release-notes/2026-08-02-cross-agent-registration.md new file mode 100644 index 00000000..fd38f608 --- /dev/null +++ b/labs/22-yield/distribution/release-notes/2026-08-02-cross-agent-registration.md @@ -0,0 +1,8 @@ +# Use one workflow from several coding agents + +- Add `yskill register` to create small adapters for coding-agent skill directories. +- Add `yskill agents` to show supported agents, paths, detection state, and verification level. +- Add `yskill doctor` to check a workflow, its launcher, and generated adapters; `--test` also runs its fixture. +- Keep workflow code and dependencies in one canonical directory instead of copying them per agent. +- Verify Cursor, Codex, and Claude paths end to end, while keeping other registry entries explicitly selectable. +- Require a useful description when scaffolding a new workflow and print the exact register, doctor, and test commands. diff --git a/labs/22-yield/public-readme/README.md b/labs/22-yield/public-readme/README.md index ae70c2a2..163512a5 100644 --- a/labs/22-yield/public-readme/README.md +++ b/labs/22-yield/public-readme/README.md @@ -41,6 +41,28 @@ cargo install yieldskill \ yskill --version ``` +## Create and register a workflow + +Keep the real workflow beside the language dependencies it uses. Yield writes +small adapters into each coding agent's project skill directory; it does not +copy the workflow or install its dependencies again. + +```bash +# TypeScript example +npm exec -- yskill init skills/review \ + --language typescript \ + --description "Review changed code when the user wants a branch checked before shipping." + +# Detect installed agents, or pass --agent cursor,codex,claude-code +npm exec -- yskill register skills/review +npm exec -- yskill doctor skills/review --test +``` + +`yskill agents` lists the available agent IDs and project paths. Cursor, +Codex, and Claude Code are verified. Remaining entries support explicit path +registration from the pinned open registry; they are not presented as +end-to-end verified. + ## How it works Deterministic re-execution: on every run/resume, `yskill` re-executes the @@ -82,8 +104,8 @@ four languages and asserts identical observable protocol behavior. | Python | `sdk/python` (`yieldskill`) | `examples/env-doctor` — probe, branch, resume after the human | | Rust | `sdk/rust` (`yieldskill`) | `examples/data-migration` — dry-run → approve → apply → verify | -Non-Go skills declare their runner in `skill.json`: -`{"run": ["node", "main.ts"]}`. +Skills declare their language and runner in `skill.json`: +`{"version": 1, "language": "typescript", "run": ["node", "main.ts"]}`. ## Ten workflows, every language @@ -106,6 +128,8 @@ use the documentation by job: - [tutorials](docs/tutorials/README.md) — review, approval, environment repair, bounded debugging, and migration; - [examples](docs/examples.md) — working programs in all four languages; +- [coding-agent setup](docs/agent-setup.md) — register one workflow with the + agents used by the project; - [evaluations](evals/README.md) — first-party workflow conformance and runtime invariant results, including the exact claim boundary; - [convert an existing skill](docs/convert-existing-skill.md) — move @@ -127,7 +151,9 @@ YSKILL="$PWD/yskill" bash ./examples/library/test-all.sh ./yskill test examples/env-doctor # Python 3.10+ ./yskill test examples/data-migration # Rust (cargo) ./yskill run examples/investigate # prints the first operation envelope -./yskill init my-skill # scaffold, or wrap an existing prose skill +./yskill init my-skill --description "Run this workflow when ..." +./yskill register my-skill --agent codex # write a thin project adapter +./yskill doctor my-skill --agent codex # verify package + adapter wiring ``` The reference skill, `examples/investigate`, encodes an investigation @@ -144,7 +170,7 @@ rejection, evidence-bound completion. Not guaranteed: that the agent performed *only* the requested operation, or that a schema-valid `agent_task` result is true — schema validity is not truth. `RunCommand` is the exception by construction: commands are -executed by the supervisor, so exit codes and output enter the log as +executed by the Yield CLI, so exit codes and output enter the log as observed fact. The formal analysis behind this line is in `docs/locus-yield.md`. diff --git a/labs/22-yield/yield/cmd/yskill/agents.go b/labs/22-yield/yield/cmd/yskill/agents.go new file mode 100644 index 00000000..62c02780 --- /dev/null +++ b/labs/22-yield/yield/cmd/yskill/agents.go @@ -0,0 +1,538 @@ +package main + +import ( + _ "embed" + "encoding/json" + "errors" + "flag" + "fmt" + "io/fs" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + + "github.com/operatorstack/yield/internal/protocol" +) + +//go:embed registry/agents.json +var agentRegistryJSON []byte + +const generatedAdapterPrefix = " + +This adapter exposes the canonical Yield workflow at %s. +Read its SKILL.md, then run from the repository root: + + %s run %s + +Follow each returned operation exactly. Resume after each response: + + %s resume --response response.json --skill %s + +Do not skip an operation or invent its response. +`, metadata.Name, yamlString(metadata.Description), generatedAdapterPrefix, sourceRel, digest, runtimeVersion(), "`"+sourceRel+"`", launcher, path, launcher, path) +} + +func writeGeneratedAdapter(path, sourceRel, content string) error { + if existing, err := os.ReadFile(path); err == nil { + marker := generatedAdapterPrefix + sourceRel + ";" + if !strings.Contains(string(existing), marker) { + return fmt.Errorf("refusing to overwrite user-owned or differently sourced adapter %s", path) + } + if string(existing) == content { + return nil + } + } else if !errors.Is(err, fs.ErrNotExist) { + return err + } + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return err + } + tmp, err := os.CreateTemp(filepath.Dir(path), ".yskill-adapter-*") + if err != nil { + return err + } + tmpName := tmp.Name() + defer os.Remove(tmpName) + if _, err := tmp.WriteString(content); err != nil { + tmp.Close() + return err + } + if err := tmp.Chmod(0o644); err != nil { + tmp.Close() + return err + } + if err := tmp.Close(); err != nil { + return err + } + return os.Rename(tmpName, path) +} + +func cmdDoctor(args []string) error { + fs := flag.NewFlagSet("doctor", flag.ContinueOnError) + var agents agentListFlag + fs.Var(&agents, "agent", "agent id, comma-separated ids, or auto") + root := fs.String("root", "", "repository root (detected from .git by default)") + runTest := fs.Bool("test", false, "run the workflow fixture after static checks") + if err := parseOnePositional(fs, args); err != nil { + return err + } + if fs.NArg() != 1 { + return fmt.Errorf("doctor takes exactly one skill directory") + } + skillDir, repoRoot, sourceRel, _, manifest, _, selected, err := registrationInputs(fs.Arg(0), *root, agents) + if err != nil { + return err + } + if _, err := launcherFor(manifest.Language, skillDir, repoRoot); err != nil { + return err + } + digest, err := protocol.DigestSkillDir(skillDir) + if err != nil { + return err + } + for _, agent := range selected { + path := filepath.Join(repoRoot, filepath.FromSlash(agent.ProjectDir), filepath.Base(skillDir), "SKILL.md") + if err := ensureContainedWrite(repoRoot, path); err != nil { + return err + } + b, err := os.ReadFile(path) + if err != nil { + return fmt.Errorf("%s adapter missing at %s; run yskill register", agent.ID, path) + } + text := string(b) + if !strings.Contains(text, generatedAdapterPrefix+sourceRel+";") || !strings.Contains(text, "digest: "+digest+";") { + return fmt.Errorf("%s adapter is stale or points elsewhere; run yskill register", agent.ID) + } + fmt.Printf("ok: %-22s %s\n", agent.ID, filepath.ToSlash(path)) + } + if *runTest { + if err := cmdTest([]string{skillDir}); err != nil { + return err + } + } + fmt.Printf("doctor: %s is ready for %d agent(s)\n", filepath.Base(skillDir), len(selected)) + return nil +} diff --git a/labs/22-yield/yield/cmd/yskill/agents_test.go b/labs/22-yield/yield/cmd/yskill/agents_test.go new file mode 100644 index 00000000..e04bba1d --- /dev/null +++ b/labs/22-yield/yield/cmd/yskill/agents_test.go @@ -0,0 +1,241 @@ +package main + +import ( + "os" + "path/filepath" + "runtime" + "strings" + "testing" +) + +func TestRegistryIsBroadValidAndVerifiedPathsArePinned(t *testing.T) { + registry, err := loadAgentRegistry() + if err != nil { + t.Fatal(err) + } + if len(registry.Agents) < 70 { + t.Fatalf("agent registry has %d entries, want broad registry", len(registry.Agents)) + } + want := map[string]string{ + "cursor": ".cursor/skills", + "codex": ".agents/skills", + "claude-code": ".claude/skills", + } + for _, agent := range registry.Agents { + if path, ok := want[agent.ID]; ok { + if agent.ProjectDir != path || agent.Tier != "verified" { + t.Fatalf("%s = path %q tier %q", agent.ID, agent.ProjectDir, agent.Tier) + } + delete(want, agent.ID) + } + } + if len(want) != 0 { + t.Fatalf("verified agents missing: %v", want) + } +} + +func TestRegisterWritesThinAdaptersAndDeduplicatesSharedDestination(t *testing.T) { + repo := t.TempDir() + writeTestFile(t, filepath.Join(repo, ".git"), "gitdir: fixture\n") + skill := createTypeScriptSkill(t, repo, "review") + registrations, err := registerSkill(skill, repo, []string{"codex", "amp", "cursor", "claude-code"}) + if err != nil { + t.Fatal(err) + } + if len(registrations) != 3 { + t.Fatalf("registrations = %d, want three unique destinations: %+v", len(registrations), registrations) + } + for _, rel := range []string{ + ".agents/skills/review/SKILL.md", + ".cursor/skills/review/SKILL.md", + ".claude/skills/review/SKILL.md", + } { + adapter := readTestFile(t, filepath.Join(repo, filepath.FromSlash(rel))) + if !strings.Contains(adapter, "source: skills/review;") || !strings.Contains(adapter, "npm exec -- yskill run 'skills/review'") { + t.Fatalf("adapter %s does not point to canonical workflow:\n%s", rel, adapter) + } + if strings.Contains(adapter, "defineSkill") || strings.Contains(adapter, "fixtures") { + t.Fatalf("adapter %s duplicated workflow content", rel) + } + } +} + +func TestEveryRegistryAgentGeneratesAContainedAdapter(t *testing.T) { + repo := t.TempDir() + writeTestFile(t, filepath.Join(repo, ".git"), "gitdir: fixture\n") + writeTestFile(t, filepath.Join(repo, "package.json"), `{"dependencies":{"@operatorstack/yield":"0.1.17"}}`) + skill := filepath.Join(repo, "workflows", "review") + writeTestFile(t, filepath.Join(skill, "SKILL.md"), "---\nname: review\ndescription: Review the branch when the user wants code checked before shipping.\n---\n") + writeTestFile(t, filepath.Join(skill, "skill.json"), `{"version":1,"language":"typescript","run":["node","main.ts"]}`) + writeTestFile(t, filepath.Join(skill, "main.ts"), "export {}\n") + registry, err := loadAgentRegistry() + if err != nil { + t.Fatal(err) + } + ids := make([]string, 0, len(registry.Agents)) + for _, agent := range registry.Agents { + ids = append(ids, agent.ID) + } + registrations, err := registerSkill(skill, repo, ids) + if err != nil { + t.Fatal(err) + } + if len(registrations) == 0 || len(registrations) >= len(ids) { + t.Fatalf("unique registrations = %d for %d agents; expected shared destinations to deduplicate", len(registrations), len(ids)) + } + for _, registration := range registrations { + path := filepath.Join(repo, filepath.FromSlash(registration.Path)) + if !within(repo, path) { + t.Fatalf("adapter escaped repository: %s", path) + } + if _, err := readSkillMetadata(filepath.Dir(path)); err != nil { + t.Fatalf("adapter %s is not a portable skill: %v", registration.Path, err) + } + } +} + +func TestRegisterUpdatesOwnedAdapterAndRefusesForeignCollision(t *testing.T) { + repo := t.TempDir() + writeTestFile(t, filepath.Join(repo, ".git"), "gitdir: fixture\n") + skill := createTypeScriptSkill(t, repo, "review") + if _, err := registerSkill(skill, repo, []string{"codex"}); err != nil { + t.Fatal(err) + } + canonical := filepath.Join(skill, "SKILL.md") + updated := strings.Replace(readTestFile(t, canonical), "Review the branch", "Review changed code", 1) + writeTestFile(t, canonical, updated) + if _, err := registerSkill(skill, repo, []string{"codex"}); err != nil { + t.Fatalf("update generated adapter: %v", err) + } + adapterPath := filepath.Join(repo, ".agents", "skills", "review", "SKILL.md") + writeTestFile(t, adapterPath, "user owned\n") + if _, err := registerSkill(skill, repo, []string{"codex"}); err == nil || !strings.Contains(err.Error(), "refusing to overwrite") { + t.Fatalf("foreign collision error = %v", err) + } +} + +func TestRegisterRejectsOutsideRepositoryAndAgentDirectoryCanonical(t *testing.T) { + repo := t.TempDir() + writeTestFile(t, filepath.Join(repo, ".git"), "gitdir: fixture\n") + outside := createTypeScriptSkill(t, t.TempDir(), "outside") + if _, err := registerSkill(outside, repo, []string{"codex"}); err == nil || !strings.Contains(err.Error(), "inside repository root") { + t.Fatalf("outside repository error = %v", err) + } + insideAgent := createTypeScriptSkill(t, filepath.Join(repo, ".cursor"), "inside-agent") + if _, err := registerSkill(insideAgent, repo, []string{"cursor"}); err == nil || !strings.Contains(err.Error(), "must not live inside agent discovery") { + t.Fatalf("agent directory error = %v", err) + } +} + +func TestRegisterRejectsSymlinkEscapes(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("unprivileged Windows test environments cannot reliably create symlinks") + } + repo := t.TempDir() + writeTestFile(t, filepath.Join(repo, ".git"), "gitdir: fixture\n") + outside := t.TempDir() + skill := createTypeScriptSkill(t, outside, "review") + if err := os.Symlink(skill, filepath.Join(repo, "review-link")); err != nil { + t.Fatal(err) + } + if _, err := registerSkill(filepath.Join(repo, "review-link"), repo, []string{"codex"}); err == nil || !strings.Contains(err.Error(), "inside repository root") { + t.Fatalf("source symlink escape error = %v", err) + } + + canonical := createTypeScriptSkill(t, repo, "safe-review") + if err := os.Symlink(outside, filepath.Join(repo, ".agents")); err != nil { + t.Fatal(err) + } + if _, err := registerSkill(canonical, repo, []string{"codex"}); err == nil || !strings.Contains(err.Error(), "resolves outside repository") { + t.Fatalf("destination symlink escape error = %v", err) + } +} + +func TestAutoDetectionUsesProjectAgentDirectory(t *testing.T) { + repo := t.TempDir() + if err := os.MkdirAll(filepath.Join(repo, ".cursor"), 0o755); err != nil { + t.Fatal(err) + } + registry, err := loadAgentRegistry() + if err != nil { + t.Fatal(err) + } + selected, err := selectAgents(registry, []string{"auto"}, repo) + if err != nil { + t.Fatal(err) + } + found := false + for _, agent := range selected { + found = found || agent.ID == "cursor" + } + if !found { + t.Fatal("project-local .cursor directory was not auto-detected") + } +} + +func TestSkillMetadataValidation(t *testing.T) { + dir := filepath.Join(t.TempDir(), "bad-name") + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatal(err) + } + writeTestFile(t, filepath.Join(dir, "SKILL.md"), "---\nname: other\ndescription: TODO\n---\n") + if _, err := readSkillMetadata(dir); err == nil { + t.Fatal("invalid metadata was accepted") + } +} + +func TestDoctorDetectsCurrentAndStaleAdapters(t *testing.T) { + repo := t.TempDir() + writeTestFile(t, filepath.Join(repo, ".git"), "gitdir: fixture\n") + skill := createTypeScriptSkill(t, repo, "review") + if _, err := registerSkill(skill, repo, []string{"codex"}); err != nil { + t.Fatal(err) + } + if err := cmdDoctor([]string{skill, "--root", repo, "--agent", "codex"}); err != nil { + t.Fatalf("doctor current adapter: %v", err) + } + writeTestFile(t, filepath.Join(skill, "main.ts"), "export const changed = true\n") + if err := cmdDoctor([]string{skill, "--root", repo, "--agent", "codex"}); err == nil || !strings.Contains(err.Error(), "stale") { + t.Fatalf("stale adapter error = %v", err) + } +} + +func TestShellQuoteEscapesSingleQuote(t *testing.T) { + if got := shellQuote("skills/team's-review"); got != `'skills/team'"'"'s-review'` { + t.Fatalf("shellQuote = %q", got) + } +} + +func TestPythonLauncherUsesRepositoryVirtualEnvironment(t *testing.T) { + repo := t.TempDir() + python := filepath.Join(repo, ".venv", "bin", "python") + t.Setenv("YIELD_PYTHON", python) + got, err := launcherFor("python", filepath.Join(repo, "skills", "review"), repo) + if err != nil { + t.Fatal(err) + } + want := shellQuote(filepath.ToSlash(filepath.Join(".venv", "bin", "python"))) + " -m yieldskill" + if got != want { + t.Fatalf("python launcher = %q, want %q", got, want) + } +} + +func createTypeScriptSkill(t *testing.T, repo, name string) string { + t.Helper() + writeTestFile(t, filepath.Join(repo, "package.json"), `{"dependencies":{"@operatorstack/yield":"0.1.17"}}`) + skill := filepath.Join(repo, "skills", name) + writeTestFile(t, filepath.Join(skill, "SKILL.md"), "---\nname: "+name+"\ndescription: Review the branch when the user wants code checked before shipping.\n---\n") + writeTestFile(t, filepath.Join(skill, "skill.json"), `{"version":1,"language":"typescript","run":["node","main.ts"]}`) + writeTestFile(t, filepath.Join(skill, "main.ts"), "export {}\n") + return skill +} + +func writeTestFile(t *testing.T, path, content string) { + t.Helper() + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte(content), 0o644); err != nil { + t.Fatal(err) + } +} diff --git a/labs/22-yield/yield/cmd/yskill/main.go b/labs/22-yield/yield/cmd/yskill/main.go index 0478795d..989d378a 100644 --- a/labs/22-yield/yield/cmd/yskill/main.go +++ b/labs/22-yield/yield/cmd/yskill/main.go @@ -1,6 +1,6 @@ -// yskill is the supervisor CLI for Yield: it starts runs, validates and +// yskill is the command-line interface for Yield: it starts runs, validates and // accepts responses, executes commands as observed fact, and owns the -// append-only run log. The coding agent drives it through six verbs. +// append-only run log. The coding agent drives it through a small set of verbs. package main import ( @@ -21,7 +21,12 @@ const usage = `yskill — turn SKILL.md workflows into resumable programs Usage: yskill init scaffold a skill (or wrap an existing prose skill) - [--language typescript|python|go|rust] + [--language typescript|python|go|rust] [--description text] + yskill register expose one workflow to coding agents + [--agent cursor,codex,...|auto] [--root repo] + yskill agents list supported coding agents and paths + yskill doctor check package, workflow, and adapters + [--agent cursor,codex,...|auto] [--root repo] [--test] yskill run [--input file] start a run; prints the first operation envelope yskill resume --response file feed a response; prints the next operation [--skill dir] [--accept-new-digest] @@ -54,6 +59,12 @@ func main() { switch os.Args[1] { case "init": err = cmdInit(os.Args[2:]) + case "register": + err = cmdRegister(os.Args[2:]) + case "agents": + err = cmdAgents(os.Args[2:]) + case "doctor": + err = cmdDoctor(os.Args[2:]) case "run": err = cmdRun(os.Args[2:]) case "resume": @@ -279,11 +290,12 @@ func cmdInit(args []string) error { fs := flag.NewFlagSet("init", flag.ExitOnError) sdkPath := fs.String("sdk", "", "filesystem path to the yield module (written as a go.mod replace directive)") language := fs.String("language", defaultLanguage(), "workflow language: typescript, python, go, or rust") + description := fs.String("description", "", "what the skill does and when an agent should use it") if err := parseOnePositional(fs, args); err != nil { return err } if fs.NArg() != 1 { return fmt.Errorf("init takes exactly one directory") } - return scaffoldSkill(fs.Arg(0), *language, *sdkPath) + return scaffoldSkill(fs.Arg(0), *language, *sdkPath, *description) } diff --git a/labs/22-yield/yield/cmd/yskill/main_test.go b/labs/22-yield/yield/cmd/yskill/main_test.go index b4e84ac0..f4cc6aea 100644 --- a/labs/22-yield/yield/cmd/yskill/main_test.go +++ b/labs/22-yield/yield/cmd/yskill/main_test.go @@ -78,8 +78,8 @@ func TestScaffoldSkillWritesLanguageSpecificEntrypoints(t *testing.T) { } for _, tt := range tests { t.Run(tt.language, func(t *testing.T) { - dir := filepath.Join(t.TempDir(), "My Skill") - if err := scaffoldSkill(dir, tt.language, ""); err != nil { + dir := filepath.Join(t.TempDir(), "my-skill") + if err := scaffoldSkill(dir, tt.language, "", "Run the test workflow when checking Yield setup."); err != nil { t.Fatal(err) } for _, rel := range append(tt.files, "SKILL.md", "fixtures/responses.json") { @@ -87,6 +87,13 @@ func TestScaffoldSkillWritesLanguageSpecificEntrypoints(t *testing.T) { t.Fatalf("%s: %v", rel, err) } } + generated, err := readSkillManifest(dir) + if err != nil { + t.Fatal(err) + } + if generated.Version != 1 || generated.Language != tt.language { + t.Fatalf("skill.json = version %d language %q", generated.Version, generated.Language) + } skill := readTestFile(t, filepath.Join(dir, "SKILL.md")) if !strings.Contains(skill, tt.command) { t.Fatalf("SKILL.md does not contain %q:\n%s", tt.command, skill) @@ -122,11 +129,11 @@ func TestGoScaffoldCanResolveItsPinnedModuleOnFirstRun(t *testing.T) { tidyGoModule = previousTidyGoModule }) dir := filepath.Join(t.TempDir(), "go-skill") - if err := scaffoldSkill(dir, "go", ""); err != nil { + if err := scaffoldSkill(dir, "go", "", "Run the Go workflow when checking Yield setup."); err != nil { t.Fatal(err) } manifest := readTestFile(t, filepath.Join(dir, "skill.json")) - if manifest != "{\"run\":[\"go\",\"run\",\"-mod=readonly\",\".\"]}\n" { + if manifest != "{\"version\":1,\"language\":\"go\",\"run\":[\"go\",\"run\",\"-mod=readonly\",\".\"]}\n" { t.Fatalf("skill.json = %q", manifest) } } @@ -137,7 +144,7 @@ func TestPythonScaffoldUsesInvokingInterpreter(t *testing.T) { t.Cleanup(func() { version = previousVersion }) t.Setenv("YIELD_PYTHON", "/opt/yield/.venv/bin/python") dir := filepath.Join(t.TempDir(), "python-skill") - if err := scaffoldSkill(dir, "python", ""); err != nil { + if err := scaffoldSkill(dir, "python", "", "Run the Python workflow when checking Yield setup."); err != nil { t.Fatal(err) } skill := readTestFile(t, filepath.Join(dir, "skill.json")) @@ -148,20 +155,27 @@ func TestPythonScaffoldUsesInvokingInterpreter(t *testing.T) { func TestScaffoldSkillPreservesExistingSkillAndRejectsUnknownLanguage(t *testing.T) { dir := t.TempDir() - if err := os.WriteFile(filepath.Join(dir, "SKILL.md"), []byte("keep me\n"), 0o644); err != nil { + existing := "---\nname: " + filepath.Base(dir) + "\ndescription: Keep this existing workflow when wrapping it with Yield.\n---\n\nKeep me.\n" + if err := os.WriteFile(filepath.Join(dir, "SKILL.md"), []byte(existing), 0o644); err != nil { t.Fatal(err) } - if err := scaffoldSkill(dir, "typescript", ""); err != nil { + if err := scaffoldSkill(dir, "typescript", "", ""); err != nil { t.Fatal(err) } - if got := readTestFile(t, filepath.Join(dir, "SKILL.md")); got != "keep me\n" { + if got := readTestFile(t, filepath.Join(dir, "SKILL.md")); got != existing { t.Fatalf("existing SKILL.md changed: %q", got) } - if err := scaffoldSkill(t.TempDir(), "java", ""); err == nil || !strings.Contains(err.Error(), "unsupported language") { + if err := scaffoldSkill(t.TempDir(), "java", "", "A valid description for the invalid language test."); err == nil || !strings.Contains(err.Error(), "unsupported language") { t.Fatalf("unknown language error = %v", err) } } +func TestScaffoldRequiresDescriptionForNewSkill(t *testing.T) { + if err := scaffoldSkill(filepath.Join(t.TempDir(), "new-skill"), "typescript", "", ""); err == nil || !strings.Contains(err.Error(), "--description is required") { + t.Fatalf("missing description error = %v", err) + } +} + func TestPackageVersionFallsBackForDevelopmentBuilds(t *testing.T) { previousVersion := version version = "dev" diff --git a/labs/22-yield/yield/cmd/yskill/registry/README.md b/labs/22-yield/yield/cmd/yskill/registry/README.md new file mode 100644 index 00000000..d1106ff4 --- /dev/null +++ b/labs/22-yield/yield/cmd/yskill/registry/README.md @@ -0,0 +1,12 @@ +# Agent path registry + +`agents.json` is a Go-embedded snapshot of project skill directories from +[`vercel-labs/skills`](https://github.com/vercel-labs/skills), pinned to the +commit recorded in the file. That project is MIT licensed. + +Yield overrides the verified Cursor, Codex, and Claude Code project paths with +the paths exercised by its integration fixtures. Other entries provide broad, +explicit path registration and are labelled `registry`, not end-to-end +verified. + +The pinned upstream license is included in `VERCEL_SKILLS_LICENSE`. diff --git a/labs/22-yield/yield/cmd/yskill/registry/VERCEL_SKILLS_LICENSE b/labs/22-yield/yield/cmd/yskill/registry/VERCEL_SKILLS_LICENSE new file mode 100644 index 00000000..23f6a8fb --- /dev/null +++ b/labs/22-yield/yield/cmd/yskill/registry/VERCEL_SKILLS_LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Vercel, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/labs/22-yield/yield/cmd/yskill/registry/agents.json b/labs/22-yield/yield/cmd/yskill/registry/agents.json new file mode 100644 index 00000000..1ca3d8cf --- /dev/null +++ b/labs/22-yield/yield/cmd/yskill/registry/agents.json @@ -0,0 +1,466 @@ +{ + "source": { + "repository": "https://github.com/vercel-labs/skills", + "commit": "1164afa5f0e21ebd01e6fc11249759353f494ad1", + "license": "MIT", + "path": "src/agents.ts" + }, + "agents": [ + { + "id": "adal", + "display_name": "AdaL", + "project_dir": ".adal/skills", + "tier": "registry" + }, + { + "id": "aider-desk", + "display_name": "AiderDesk", + "project_dir": ".aider-desk/skills", + "tier": "registry" + }, + { + "id": "amp", + "display_name": "Amp", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "antigravity", + "display_name": "Antigravity", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "antigravity-cli", + "display_name": "Antigravity CLI", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "astrbot", + "display_name": "AstrBot", + "project_dir": "data/skills", + "tier": "registry" + }, + { + "id": "augment", + "display_name": "Augment", + "project_dir": ".augment/skills", + "tier": "registry" + }, + { + "id": "autohand-code", + "display_name": "Autohand Code CLI", + "project_dir": ".autohand/skills", + "tier": "registry" + }, + { + "id": "bob", + "display_name": "IBM Bob", + "project_dir": ".bob/skills", + "tier": "registry" + }, + { + "id": "claude-code", + "display_name": "Claude Code", + "project_dir": ".claude/skills", + "tier": "verified" + }, + { + "id": "cline", + "display_name": "Cline", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "codearts-agent", + "display_name": "CodeArts Agent", + "project_dir": ".codeartsdoer/skills", + "tier": "registry" + }, + { + "id": "codebuddy", + "display_name": "CodeBuddy", + "project_dir": ".codebuddy/skills", + "tier": "registry" + }, + { + "id": "codemaker", + "display_name": "Codemaker", + "project_dir": ".codemaker/skills", + "tier": "registry" + }, + { + "id": "codestudio", + "display_name": "Code Studio", + "project_dir": ".codestudio/skills", + "tier": "registry" + }, + { + "id": "codex", + "display_name": "Codex", + "project_dir": ".agents/skills", + "tier": "verified" + }, + { + "id": "command-code", + "display_name": "Command Code", + "project_dir": ".commandcode/skills", + "tier": "registry" + }, + { + "id": "continue", + "display_name": "Continue", + "project_dir": ".continue/skills", + "tier": "registry" + }, + { + "id": "cortex", + "display_name": "Cortex Code", + "project_dir": ".cortex/skills", + "tier": "registry" + }, + { + "id": "crush", + "display_name": "Crush", + "project_dir": ".crush/skills", + "tier": "registry" + }, + { + "id": "cursor", + "display_name": "Cursor", + "project_dir": ".cursor/skills", + "tier": "verified" + }, + { + "id": "deepagents", + "display_name": "Deep Agents", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "devin", + "display_name": "Devin for Terminal", + "project_dir": ".devin/skills", + "tier": "registry" + }, + { + "id": "dexto", + "display_name": "Dexto", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "droid", + "display_name": "Droid", + "project_dir": ".factory/skills", + "tier": "registry" + }, + { + "id": "eve", + "display_name": "Eve", + "project_dir": "agent/skills", + "tier": "registry" + }, + { + "id": "firebender", + "display_name": "Firebender", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "forgecode", + "display_name": "ForgeCode", + "project_dir": ".forge/skills", + "tier": "registry" + }, + { + "id": "gemini-cli", + "display_name": "Gemini CLI", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "github-copilot", + "display_name": "GitHub Copilot", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "goose", + "display_name": "Goose", + "project_dir": ".goose/skills", + "tier": "registry" + }, + { + "id": "grok", + "display_name": "Grok Build", + "project_dir": ".grok/skills", + "tier": "registry" + }, + { + "id": "hermes-agent", + "display_name": "Hermes Agent", + "project_dir": ".hermes/skills", + "tier": "registry" + }, + { + "id": "iflow-cli", + "display_name": "iFlow CLI", + "project_dir": ".iflow/skills", + "tier": "registry" + }, + { + "id": "inference-sh", + "display_name": "inference.sh", + "project_dir": ".inferencesh/skills", + "tier": "registry" + }, + { + "id": "jazz", + "display_name": "Jazz", + "project_dir": ".jazz/skills", + "tier": "registry" + }, + { + "id": "junie", + "display_name": "Junie", + "project_dir": ".junie/skills", + "tier": "registry" + }, + { + "id": "kilo", + "display_name": "Kilo Code", + "project_dir": ".kilocode/skills", + "tier": "registry" + }, + { + "id": "kimchi", + "display_name": "Kimchi", + "project_dir": ".kimchi/skills", + "tier": "registry" + }, + { + "id": "kimi-code-cli", + "display_name": "Kimi Code CLI", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "kiro-cli", + "display_name": "Kiro CLI", + "project_dir": ".kiro/skills", + "tier": "registry" + }, + { + "id": "kode", + "display_name": "Kode", + "project_dir": ".kode/skills", + "tier": "registry" + }, + { + "id": "lingma", + "display_name": "Lingma", + "project_dir": ".lingma/skills", + "tier": "registry" + }, + { + "id": "loaf", + "display_name": "Loaf", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "mcpjam", + "display_name": "MCPJam", + "project_dir": ".mcpjam/skills", + "tier": "registry" + }, + { + "id": "minimax-code", + "display_name": "MiniMax Code", + "project_dir": ".minimax/skills", + "tier": "registry" + }, + { + "id": "mistral-vibe", + "display_name": "Mistral Vibe", + "project_dir": ".vibe/skills", + "tier": "registry" + }, + { + "id": "moxby", + "display_name": "Moxby", + "project_dir": ".moxby/skills", + "tier": "registry" + }, + { + "id": "mux", + "display_name": "Mux", + "project_dir": ".mux/skills", + "tier": "registry" + }, + { + "id": "neovate", + "display_name": "Neovate", + "project_dir": ".neovate/skills", + "tier": "registry" + }, + { + "id": "ona", + "display_name": "Ona", + "project_dir": ".ona/skills", + "tier": "registry" + }, + { + "id": "openclaw", + "display_name": "OpenClaw", + "project_dir": "skills", + "tier": "registry" + }, + { + "id": "opencode", + "display_name": "OpenCode", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "openhands", + "display_name": "OpenHands", + "project_dir": ".openhands/skills", + "tier": "registry" + }, + { + "id": "pi", + "display_name": "Pi", + "project_dir": ".pi/skills", + "tier": "registry" + }, + { + "id": "pochi", + "display_name": "Pochi", + "project_dir": ".pochi/skills", + "tier": "registry" + }, + { + "id": "promptscript", + "display_name": "PromptScript", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "qoder", + "display_name": "Qoder", + "project_dir": ".qoder/skills", + "tier": "registry" + }, + { + "id": "qoder-cn", + "display_name": "Qoder CN", + "project_dir": ".qoder/skills", + "tier": "registry" + }, + { + "id": "qwen-code", + "display_name": "Qwen Code", + "project_dir": ".qwen/skills", + "tier": "registry" + }, + { + "id": "reasonix", + "display_name": "Reasonix", + "project_dir": ".reasonix/skills", + "tier": "registry" + }, + { + "id": "replit", + "display_name": "Replit", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "roo", + "display_name": "Roo Code", + "project_dir": ".roo/skills", + "tier": "registry" + }, + { + "id": "rovodev", + "display_name": "Rovo Dev", + "project_dir": ".rovodev/skills", + "tier": "registry" + }, + { + "id": "tabnine-cli", + "display_name": "Tabnine CLI", + "project_dir": ".tabnine/agent/skills", + "tier": "registry" + }, + { + "id": "terramind", + "display_name": "Terramind", + "project_dir": ".terramind/skills", + "tier": "registry" + }, + { + "id": "tinycloud", + "display_name": "Tinycloud", + "project_dir": ".tinycloud/skills", + "tier": "registry" + }, + { + "id": "trae", + "display_name": "Trae", + "project_dir": ".trae/skills", + "tier": "registry" + }, + { + "id": "trae-cn", + "display_name": "Trae CN", + "project_dir": ".trae/skills", + "tier": "registry" + }, + { + "id": "universal", + "display_name": "Universal", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "warp", + "display_name": "Warp", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "windsurf", + "display_name": "Windsurf", + "project_dir": ".windsurf/skills", + "tier": "registry" + }, + { + "id": "zcode", + "display_name": "ZCode", + "project_dir": ".zcode/skills", + "tier": "registry" + }, + { + "id": "zed", + "display_name": "Zed", + "project_dir": ".agents/skills", + "tier": "registry" + }, + { + "id": "zencoder", + "display_name": "Zencoder", + "project_dir": ".zencoder/skills", + "tier": "registry" + }, + { + "id": "zenflow", + "display_name": "Zenflow", + "project_dir": ".zencoder/skills", + "tier": "registry" + } + ] +} diff --git a/labs/22-yield/yield/cmd/yskill/scaffold.go b/labs/22-yield/yield/cmd/yskill/scaffold.go index 290342bd..b28735e9 100644 --- a/labs/22-yield/yield/cmd/yskill/scaffold.go +++ b/labs/22-yield/yield/cmd/yskill/scaffold.go @@ -11,7 +11,6 @@ import ( ) var releaseVersionPattern = regexp.MustCompile(`^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$`) -var unsafePackageCharacter = regexp.MustCompile(`[^a-z0-9_-]+`) var tidyGoModule = func(dir string) error { cmd := exec.Command("go", "mod", "tidy") cmd.Dir = dir @@ -38,7 +37,7 @@ func packageVersion() string { return "0.0.0" } -func scaffoldSkill(dir, language, sdkPath string) error { +func scaffoldSkill(dir, language, sdkPath, description string) error { language = strings.ToLower(strings.TrimSpace(language)) if language != "typescript" && language != "python" && language != "go" && language != "rust" { return fmt.Errorf("unsupported language %q; choose typescript, python, go, or rust", language) @@ -46,7 +45,16 @@ func scaffoldSkill(dir, language, sdkPath string) error { if sdkPath != "" && language != "go" { return fmt.Errorf("--sdk is only valid with --language go") } - name := safePackageName(filepath.Base(filepath.Clean(dir))) + name := filepath.Base(filepath.Clean(dir)) + if !portableSkillName.MatchString(name) || len(name) > 64 { + return fmt.Errorf("skill directory name must match [a-z0-9]+(-[a-z0-9]+)* and be at most 64 characters") + } + skillPath := filepath.Join(dir, "SKILL.md") + if _, err := os.Stat(skillPath); os.IsNotExist(err) && strings.TrimSpace(description) == "" { + return fmt.Errorf("--description is required for a new skill; describe what it does and when an agent should use it") + } else if err != nil && !os.IsNotExist(err) { + return err + } writeIfAbsent := func(rel, content string) error { path := filepath.Join(dir, filepath.FromSlash(rel)) if _, err := os.Stat(path); err == nil { @@ -68,7 +76,7 @@ func scaffoldSkill(dir, language, sdkPath string) error { "rust": "yskill", }[language] files := scaffoldFiles(name, language, sdkPath) - files["SKILL.md"] = fmt.Sprintf(skillMD, name, launcher, launcher) + files["SKILL.md"] = fmt.Sprintf(skillMD, name, yamlString(strings.TrimSpace(description)), launcher, launcher) files["fixtures/responses.json"] = "{\n \"confirm-start\": {\"value\": \"yes\"}\n}\n" keys := make([]string, 0, len(files)) for key := range files { @@ -85,7 +93,12 @@ func scaffoldSkill(dir, language, sdkPath string) error { return err } } + if _, err := readSkillMetadata(dir); err != nil { + return fmt.Errorf("validate SKILL.md: %w", err) + } fmt.Printf("init: %s skill %q scaffolded in %s\n", language, name, dir) + fmt.Printf("next: %s register %s\n", launcher, shellQuote(dir)) + fmt.Printf("check: %s doctor %s --test\n", launcher, shellQuote(dir)) return nil } @@ -101,7 +114,7 @@ func scaffoldFiles(name, language, sdkPath string) map[string]string { "dependencies": { "@operatorstack/yield": "%s" } } `, v), - "skill.json": "{\"run\":[\"node\",\"main.ts\"]}\n", + "skill.json": "{\"version\":1,\"language\":\"typescript\",\"run\":[\"node\",\"main.ts\"]}\n", } case "python": python := strings.TrimSpace(os.Getenv("YIELD_PYTHON")) @@ -111,14 +124,14 @@ func scaffoldFiles(name, language, sdkPath string) map[string]string { return map[string]string{ "main.py": mainPython, "requirements.txt": fmt.Sprintf("--index-url https://get.operatorstack.systems/pip/simple/\nyieldskill==%s\n", v), - "skill.json": fmt.Sprintf("{\"run\":[%q,\"main.py\"]}\n", python), + "skill.json": fmt.Sprintf("{\"version\":1,\"language\":\"python\",\"run\":[%q,\"main.py\"]}\n", python), } case "rust": return map[string]string{ ".cargo/config.toml": "[registries.operatorstack]\nindex = \"sparse+https://get.operatorstack.systems/cargo/index/\"\n", "Cargo.toml": fmt.Sprintf("[package]\nname = %q\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nyieldskill = { version = \"=%s\", registry = \"operatorstack\" }\nserde_json = \"1\"\n", name, v), "src/main.rs": mainRust, - "skill.json": "{\"run\":[\"cargo\",\"run\",\"--quiet\"]}\n", + "skill.json": "{\"version\":1,\"language\":\"rust\",\"run\":[\"cargo\",\"run\",\"--quiet\"]}\n", } default: gomod := fmt.Sprintf("module %s\n\ngo 1.26.5\n\nrequire github.com/operatorstack/yield v%s\n", name, v) @@ -128,23 +141,14 @@ func scaffoldFiles(name, language, sdkPath string) map[string]string { return map[string]string{ "main.go": mainGo, "go.mod": gomod, - "skill.json": "{\"run\":[\"go\",\"run\",\"-mod=readonly\",\".\"]}\n", + "skill.json": "{\"version\":1,\"language\":\"go\",\"run\":[\"go\",\"run\",\"-mod=readonly\",\".\"]}\n", } } } -func safePackageName(value string) string { - value = unsafePackageCharacter.ReplaceAllString(strings.ToLower(value), "-") - value = strings.Trim(value, "-_") - if value == "" { - return "yield-skill" - } - return value -} - const skillMD = `--- name: %s -description: TODO — one line on what this skill does. +description: %s --- Run: diff --git a/labs/22-yield/yield/cmd/yskill/skillmeta.go b/labs/22-yield/yield/cmd/yskill/skillmeta.go new file mode 100644 index 00000000..de399e6a --- /dev/null +++ b/labs/22-yield/yield/cmd/yskill/skillmeta.go @@ -0,0 +1,86 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + + "gopkg.in/yaml.v3" +) + +var portableSkillName = regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$`) + +type skillManifest struct { + Version int `json:"version"` + Language string `json:"language"` + Run []string `json:"run"` +} + +type skillMetadata struct { + Name string `yaml:"name"` + Description string `yaml:"description"` +} + +func readSkillManifest(dir string) (skillManifest, error) { + b, err := os.ReadFile(filepath.Join(dir, "skill.json")) + if err != nil { + return skillManifest{}, fmt.Errorf("read skill.json: %w", err) + } + var manifest skillManifest + if err := json.Unmarshal(b, &manifest); err != nil { + return skillManifest{}, fmt.Errorf("skill.json does not decode: %w", err) + } + if manifest.Version != 1 { + return skillManifest{}, fmt.Errorf("skill.json version must be 1") + } + switch manifest.Language { + case "typescript", "python", "go", "rust": + default: + return skillManifest{}, fmt.Errorf("skill.json language must be typescript, python, go, or rust") + } + if len(manifest.Run) == 0 { + return skillManifest{}, fmt.Errorf("skill.json run must not be empty") + } + return manifest, nil +} + +func readSkillMetadata(dir string) (skillMetadata, error) { + b, err := os.ReadFile(filepath.Join(dir, "SKILL.md")) + if err != nil { + return skillMetadata{}, fmt.Errorf("read SKILL.md: %w", err) + } + text := strings.ReplaceAll(string(b), "\r\n", "\n") + if !strings.HasPrefix(text, "---\n") { + return skillMetadata{}, fmt.Errorf("SKILL.md must start with YAML frontmatter") + } + rest := text[4:] + end := strings.Index(rest, "\n---\n") + if end < 0 { + return skillMetadata{}, fmt.Errorf("SKILL.md frontmatter is not closed") + } + var metadata skillMetadata + if err := yaml.Unmarshal([]byte(rest[:end]), &metadata); err != nil { + return skillMetadata{}, fmt.Errorf("SKILL.md frontmatter does not decode: %w", err) + } + metadata.Name = strings.TrimSpace(metadata.Name) + metadata.Description = strings.TrimSpace(metadata.Description) + if !portableSkillName.MatchString(metadata.Name) || len(metadata.Name) > 64 { + return skillMetadata{}, fmt.Errorf("skill name must match [a-z0-9]+(-[a-z0-9]+)* and be at most 64 characters") + } + if metadata.Name != filepath.Base(filepath.Clean(dir)) { + return skillMetadata{}, fmt.Errorf("skill name %q must match directory %q", metadata.Name, filepath.Base(filepath.Clean(dir))) + } + if metadata.Description == "" || strings.Contains(strings.ToLower(metadata.Description), "todo") { + return skillMetadata{}, fmt.Errorf("skill description must explain what the skill does and when to use it") + } + if len(metadata.Description) > 1024 { + return skillMetadata{}, fmt.Errorf("skill description must be at most 1024 characters") + } + return metadata, nil +} + +func yamlString(value string) string { return strconv.Quote(value) } diff --git a/labs/22-yield/yield/docs/README.md b/labs/22-yield/yield/docs/README.md index 23d3b5f4..b43fe380 100644 --- a/labs/22-yield/yield/docs/README.md +++ b/labs/22-yield/yield/docs/README.md @@ -14,13 +14,15 @@ is finished. 1. [Build and run your first skill](quickstart.md) — a TypeScript workflow you can test in about ten minutes. -2. [Learn the primitives](primitives/README.md) — commands, model work, human +2. [Register it with your coding agents](agent-setup.md) — keep one workflow + and generate the small discovery adapters each agent needs. +3. [Learn the primitives](primitives/README.md) — commands, model work, human input, gates, and honest outcomes. -3. [Follow a complete tutorial](tutorials/README.md) — review, approval, +4. [Follow a complete tutorial](tutorials/README.md) — review, approval, environment repair, bounded debugging, and migration. -4. [Browse the examples](examples.md) — working programs in Go, TypeScript, +5. [Browse the examples](examples.md) — working programs in Go, TypeScript, Python, and Rust. -5. [Convert an existing prose skill](convert-existing-skill.md) — use Yield's +6. [Convert an existing prose skill](convert-existing-skill.md) — use Yield's verified converter after you understand one ordinary workflow. ## The split to remember @@ -41,6 +43,7 @@ next unanswered operation. ## Reference - [CLI commands](reference/cli.md) +- [Coding-agent registration](agent-setup.md) - [Run, pause, resume, and replay](reference/execution-model.md) - [The four SDKs](reference/sdk-parity.md) - [Guarantees and limits](reference/guarantees.md) diff --git a/labs/22-yield/yield/docs/agent-setup.md b/labs/22-yield/yield/docs/agent-setup.md new file mode 100644 index 00000000..3d3ffe61 --- /dev/null +++ b/labs/22-yield/yield/docs/agent-setup.md @@ -0,0 +1,61 @@ +# Use one Yield workflow from your coding agents + +Yield workflows belong beside the application and language dependencies they +use. `yskill register` creates only the small `SKILL.md` adapters required for +agent discovery. + +## Register an existing workflow + +```bash +# Detect installed verified agents +yskill register skills/review + +# Or choose agents explicitly +yskill register skills/review --agent cursor,codex,claude-code + +# Check the package and adapters; add --test to run fixture responses +yskill doctor skills/review --agent cursor,codex,claude-code --test +``` + +Use the launcher installed by the selected language package: + +| Language | Launcher | +|---|---| +| TypeScript | `npm exec -- yskill` | +| Python | `python -m yieldskill` | +| Go | `yskill` | +| Rust | `yskill` | + +Run `yskill agents` to see every supported ID and project directory. Cursor, +Codex, and Claude Code are verified. Other entries use paths from a pinned +snapshot of the open `vercel-labs/skills` registry and are labelled +`registry`: path generation is tested, but the product itself has not been run +end to end by Yield. + +Generated adapters are safe to commit. Regenerate them after changing the +canonical workflow. Yield refuses to overwrite a user-owned skill with the +same name. + +## Copy this to your agent + +Replace the bracketed values, then paste this into the coding agent already +open in the project: + +```text +Set up a Yield workflow named [skill-name] in skills/[skill-name]. + +1. Detect whether this project uses TypeScript, Python, Go, or Rust. +2. Install that language's Yield package using the project's existing package + manager. Do not install a second global runtime. +3. Run yskill init with the detected language and this description: + [what the workflow does and when it should run] +4. Keep the workflow beside the project's language dependencies. +5. Run yskill register for the coding agent you are currently using. +6. Use the launcher from the installed language package for every yskill + command: npm exec -- yskill, python -m yieldskill, or yskill. +7. Run yskill doctor with --test. +8. Report the commands, generated adapter path, and every changed file. + +Do not move the workflow into an agent discovery directory and do not copy its +dependencies into an adapter. +``` diff --git a/labs/22-yield/yield/docs/primitives/agent-task.md b/labs/22-yield/yield/docs/primitives/agent-task.md index 7da56dcd..6cd82f11 100644 --- a/labs/22-yield/yield/docs/primitives/agent-task.md +++ b/labs/22-yield/yield/docs/primitives/agent-task.md @@ -29,7 +29,7 @@ The arguments are: 3. optional structured context; 4. an optional JSON Schema for the response. -The supervisor validates the response schema before accepting it. Schema-valid +The Yield CLI validates the response schema before accepting it. Schema-valid does not mean true; use `RunCommand`, human approval, or another explicit check when the workflow needs stronger evidence. diff --git a/labs/22-yield/yield/docs/quickstart.md b/labs/22-yield/yield/docs/quickstart.md index 890d1a42..03c29e5c 100644 --- a/labs/22-yield/yield/docs/quickstart.md +++ b/labs/22-yield/yield/docs/quickstart.md @@ -1,45 +1,43 @@ # Run your first Yield skill This tutorial turns a repeated review checklist into a small TypeScript -program: - -1. run a real check; -2. ask the coding agent to review the branch; -3. stop unless the result has zero critical findings; -4. save the structured review. +program: run a real check, ask the coding agent to review the branch, stop on +critical findings, and save the structured result. You need Node.js 24 or newer. ## 1. Install Yield ```bash -mkdir review-skill -cd review-skill +mkdir yield-example +cd yield-example npm init -y npm install @operatorstack/yield \ --registry=https://get.operatorstack.systems/npm/ -npm exec -- yskill init . --language typescript +npm exec -- yskill --version ``` The package includes the TypeScript SDK and its matching Yield runtime. -## 2. Create the package +## 2. Create the canonical workflow + +```bash +npm exec -- yskill init skills/review \ + --language typescript \ + --description "Check and review the current branch before it is shipped." +``` -Create `package.json`: +The workflow stays under `skills/review`, inside the same dependency tree as +`@operatorstack/yield`. The generated `skill.json` records the language and +program entry point: ```json -{ - "private": true, - "type": "module", - "scripts": { - "check": "node --check main.ts" - } -} +{"version":1,"language":"typescript","run":["node","main.ts"]} ``` -## 3. Add the workflow +## 3. Add the workflow logic -Create `main.ts`: +Replace `skills/review/main.ts`: ```ts import { defineSkill } from "@operatorstack/yield"; @@ -72,37 +70,41 @@ defineSkill((ctx) => { }); ``` -Add `skill.json` so Yield knows how to start the program: +Add the real project check to the root `package.json`: ```json -{"run":["node","main.ts"]} +{"scripts":{"check":"node --check skills/review/main.ts"}} ``` -## 4. Add the thin skill file +The generated `skills/review/SKILL.md` remains short. It tells the agent when +to use the workflow and how to follow the yielded operations; the program owns +the order and finish rule. -Create `SKILL.md`: +## 4. Register it with coding agents -```markdown ---- -name: review -description: Check and review the current branch before it is shipped. ---- +```bash +# Detect installed verified agents +npm exec -- yskill register skills/review -Run `npm exec -- yskill run .` and follow each returned operation exactly. +# Or select them explicitly +npm exec -- yskill register skills/review \ + --agent cursor,codex,claude-code +``` -For `agent_task`, perform the task and return schema-valid JSON. Resume with -`npm exec -- yskill resume --response response.json --skill .`. +Yield keeps one workflow and writes only generated adapters: -Do not skip an operation or invent a response. The program owns the order and -the finish rule. +```text +.cursor/skills/review/SKILL.md # Cursor +.agents/skills/review/SKILL.md # Codex +.claude/skills/review/SKILL.md # Claude Code ``` -The file still tells the agent what the skill is for. It no longer has to -describe every branch and gate in prose. +Start a new agent session after registration, then invoke `/review` or ask for +the task described by the skill. ## 5. Prove the workflow locally -Create `fixtures/responses.json`: +Replace `skills/review/fixtures/responses.json`: ```json { @@ -113,24 +115,33 @@ Create `fixtures/responses.json`: } ``` -Run: +Run the complete setup check: ```bash -npm exec -- yskill test . +npm exec -- yskill doctor skills/review \ + --agent cursor,codex,claude-code \ + --test ``` -`run_command` operations execute for real. The fixture supplies only the model -and user responses. A successful result ends with: +`run_command` operations execute for real. The fixture supplies only model and +user responses. A successful result ends with `reached completed` and a doctor +summary. -```text -test: run reached completed -``` +## Run an existing workflow -## 6. Use it from your coding agent +Initialization is only for creating or wrapping a workflow. For an existing +workflow, install the matching language package, register it for the agents in +the project, then run it directly when needed: + +```bash +npm exec -- yskill register skills/review --agent cursor +npm exec -- yskill run skills/review +``` -Ask the agent to run the `review` skill in this directory. The agent reads -`SKILL.md`, starts `yskill`, performs the review operation, and resumes the -saved run. If the session closes, the run remains on disk. +The agent reads the generated adapter, starts the canonical workflow, performs +each yielded operation, and resumes the saved run. If the session closes, the +run remains on disk. -Next: [understand each primitive](primitives/README.md), or follow the -[complete review tutorial](tutorials/code-review.md). +Next: [set up coding agents](agent-setup.md), [understand each +primitive](primitives/README.md), or follow the [complete review +tutorial](tutorials/code-review.md). diff --git a/labs/22-yield/yield/docs/reference/cli.md b/labs/22-yield/yield/docs/reference/cli.md index df4a004f..5f4239d7 100644 --- a/labs/22-yield/yield/docs/reference/cli.md +++ b/labs/22-yield/yield/docs/reference/cli.md @@ -6,11 +6,50 @@ commands, and starts the skill program. It comes with each language package. ## `init` ```bash -yskill init --language typescript|python|go|rust +yskill init --description "What it does and when to use it" + [--language typescript|python|go|rust] ``` Scaffolds a new skill or adds a Yield program beside an existing prose skill. Generated dependencies are pinned to the installed `yskill` version. +New skills require a real trigger-oriented description. Existing `SKILL.md` +files are preserved and validated. + +## `register` + +```bash +yskill register [--agent cursor,codex,...|auto] + [--root repository] +``` + +Writes generated, project-local `SKILL.md` adapters for the selected coding +agents. Without `--agent`, Yield detects verified agents. Explicit IDs work for +every entry printed by `yskill agents`. Workflow code, dependencies, fixtures, +and run state remain in the canonical skill directory. + +Registration updates only adapters previously generated from the same source. +It refuses user-owned files, workflows outside the repository, and canonical +workflows stored inside a selected agent's discovery directory. + +## `agents` + +```bash +yskill agents +``` + +Lists agent IDs, project skill directories, detection state, and whether each +entry is verified or registry-supported. + +## `doctor` + +```bash +yskill doctor [--agent cursor,codex,...|auto] + [--root repository] [--test] +``` + +Checks the manifest, package launcher, portable metadata, adapter ownership, +source path, and source digest. `--test` also runs the workflow against +`fixtures/responses.json`. ## `version` diff --git a/labs/22-yield/yield/docs/reference/guarantees.md b/labs/22-yield/yield/docs/reference/guarantees.md index e89a5f13..7f27a744 100644 --- a/labs/22-yield/yield/docs/reference/guarantees.md +++ b/labs/22-yield/yield/docs/reference/guarantees.md @@ -7,7 +7,7 @@ - persistent append-only run state; - per-step digest checks during replay; - rejection of stale, duplicate, wrong-run, and schema-invalid responses; -- real command execution by the supervisor; +- real command execution by the Yield CLI; - requirements that prevent later completion after failure; - recorded completed, blocked, and refused outcomes. diff --git a/labs/22-yield/yield/docs/reference/sdk-parity.md b/labs/22-yield/yield/docs/reference/sdk-parity.md index 87e6102e..891b4957 100644 --- a/labs/22-yield/yield/docs/reference/sdk-parity.md +++ b/labs/22-yield/yield/docs/reference/sdk-parity.md @@ -10,10 +10,10 @@ contract. | Go | `github.com/operatorstack/yield/sdk/yield` | `yield.Main(program)` | | Rust | `yieldskill` | `yieldskill::define_skill(program)` | -Non-Go skills declare their runner in `skill.json`, for example: +Skills declare their language and runner in `skill.json`, for example: ```json -{"run":["node","main.ts"]} +{"version":1,"language":"typescript","run":["node","main.ts"]} ``` The conformance suite runs the same workflow in all four languages and compares diff --git a/labs/22-yield/yield/evals/results/latest.json b/labs/22-yield/yield/evals/results/latest.json index 1fcd04c8..d02e5334 100644 --- a/labs/22-yield/yield/evals/results/latest.json +++ b/labs/22-yield/yield/evals/results/latest.json @@ -1,8 +1,8 @@ { "schema_version": 2, "methodology_version": "1.0", - "generated_at": "2026-08-01T22:15:26.319Z", - "source_digest": "18c4620bc76291a4e8580b488f746cfb0085bf06629cf73ac38c5dbbec4bf099", + "generated_at": "2026-08-02T00:47:04.974Z", + "source_digest": "760c09c309aac72fc15ff1b6b35663717b32a96917bca1be0c61e8536354467a", "status": "passed", "workflow_conformance": { "passed": 40, diff --git a/labs/22-yield/yield/examples/convert-skill/skill.json b/labs/22-yield/yield/examples/convert-skill/skill.json new file mode 100644 index 00000000..da0e8782 --- /dev/null +++ b/labs/22-yield/yield/examples/convert-skill/skill.json @@ -0,0 +1,5 @@ +{ + "version": 1, + "language": "go", + "run": ["go", "run", "."] +} diff --git a/labs/22-yield/yield/examples/data-migration/skill.json b/labs/22-yield/yield/examples/data-migration/skill.json index 5b767f7b..fb837d09 100644 --- a/labs/22-yield/yield/examples/data-migration/skill.json +++ b/labs/22-yield/yield/examples/data-migration/skill.json @@ -1,3 +1,9 @@ { - "run": ["cargo", "run", "--quiet"] + "version": 1, + "language": "rust", + "run": [ + "cargo", + "run", + "--quiet" + ] } diff --git a/labs/22-yield/yield/examples/env-doctor/skill.json b/labs/22-yield/yield/examples/env-doctor/skill.json index 2334634a..5a7ec2ab 100644 --- a/labs/22-yield/yield/examples/env-doctor/skill.json +++ b/labs/22-yield/yield/examples/env-doctor/skill.json @@ -1,3 +1,8 @@ { - "run": ["python3", "main.py"] + "version": 1, + "language": "python", + "run": [ + "python3", + "main.py" + ] } diff --git a/labs/22-yield/yield/examples/investigate/skill.json b/labs/22-yield/yield/examples/investigate/skill.json new file mode 100644 index 00000000..da0e8782 --- /dev/null +++ b/labs/22-yield/yield/examples/investigate/skill.json @@ -0,0 +1,5 @@ +{ + "version": 1, + "language": "go", + "run": ["go", "run", "."] +} diff --git a/labs/22-yield/yield/examples/library/go/audit-security/skill.json b/labs/22-yield/yield/examples/library/go/audit-security/skill.json index 8e95b8a5..9039b2dd 100644 --- a/labs/22-yield/yield/examples/library/go/audit-security/skill.json +++ b/labs/22-yield/yield/examples/library/go/audit-security/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "go", "run": [ "go", "run", diff --git a/labs/22-yield/yield/examples/library/go/investigate-failure/skill.json b/labs/22-yield/yield/examples/library/go/investigate-failure/skill.json index 3eec0b06..e8e261ba 100644 --- a/labs/22-yield/yield/examples/library/go/investigate-failure/skill.json +++ b/labs/22-yield/yield/examples/library/go/investigate-failure/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "go", "run": [ "go", "run", diff --git a/labs/22-yield/yield/examples/library/go/migrate-database/skill.json b/labs/22-yield/yield/examples/library/go/migrate-database/skill.json index 752dae24..3162de17 100644 --- a/labs/22-yield/yield/examples/library/go/migrate-database/skill.json +++ b/labs/22-yield/yield/examples/library/go/migrate-database/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "go", "run": [ "go", "run", diff --git a/labs/22-yield/yield/examples/library/go/publish-ios/skill.json b/labs/22-yield/yield/examples/library/go/publish-ios/skill.json index 1ab9b9d0..dba90094 100644 --- a/labs/22-yield/yield/examples/library/go/publish-ios/skill.json +++ b/labs/22-yield/yield/examples/library/go/publish-ios/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "go", "run": [ "go", "run", diff --git a/labs/22-yield/yield/examples/library/go/qa-web-change/skill.json b/labs/22-yield/yield/examples/library/go/qa-web-change/skill.json index b5fc4704..677da9f0 100644 --- a/labs/22-yield/yield/examples/library/go/qa-web-change/skill.json +++ b/labs/22-yield/yield/examples/library/go/qa-web-change/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "go", "run": [ "go", "run", diff --git a/labs/22-yield/yield/examples/library/go/release-package/skill.json b/labs/22-yield/yield/examples/library/go/release-package/skill.json index 14c49d95..16159104 100644 --- a/labs/22-yield/yield/examples/library/go/release-package/skill.json +++ b/labs/22-yield/yield/examples/library/go/release-package/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "go", "run": [ "go", "run", diff --git a/labs/22-yield/yield/examples/library/go/repair-ci/skill.json b/labs/22-yield/yield/examples/library/go/repair-ci/skill.json index e2b15eed..a4debd9f 100644 --- a/labs/22-yield/yield/examples/library/go/repair-ci/skill.json +++ b/labs/22-yield/yield/examples/library/go/repair-ci/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "go", "run": [ "go", "run", diff --git a/labs/22-yield/yield/examples/library/go/review-branch/skill.json b/labs/22-yield/yield/examples/library/go/review-branch/skill.json index 5ce6c004..1a40e27b 100644 --- a/labs/22-yield/yield/examples/library/go/review-branch/skill.json +++ b/labs/22-yield/yield/examples/library/go/review-branch/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "go", "run": [ "go", "run", diff --git a/labs/22-yield/yield/examples/library/go/triage-issue/skill.json b/labs/22-yield/yield/examples/library/go/triage-issue/skill.json index 748427d8..2b180596 100644 --- a/labs/22-yield/yield/examples/library/go/triage-issue/skill.json +++ b/labs/22-yield/yield/examples/library/go/triage-issue/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "go", "run": [ "go", "run", diff --git a/labs/22-yield/yield/examples/library/go/upgrade-dependency/skill.json b/labs/22-yield/yield/examples/library/go/upgrade-dependency/skill.json index 9e25f22f..df74f8fa 100644 --- a/labs/22-yield/yield/examples/library/go/upgrade-dependency/skill.json +++ b/labs/22-yield/yield/examples/library/go/upgrade-dependency/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "go", "run": [ "go", "run", diff --git a/labs/22-yield/yield/examples/library/python/audit-security/skill.json b/labs/22-yield/yield/examples/library/python/audit-security/skill.json index e5e59ab7..57b14bf6 100644 --- a/labs/22-yield/yield/examples/library/python/audit-security/skill.json +++ b/labs/22-yield/yield/examples/library/python/audit-security/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "python", "run": [ "python3", "../src/audit-security.py" diff --git a/labs/22-yield/yield/examples/library/python/investigate-failure/skill.json b/labs/22-yield/yield/examples/library/python/investigate-failure/skill.json index c1c33c35..44c2e785 100644 --- a/labs/22-yield/yield/examples/library/python/investigate-failure/skill.json +++ b/labs/22-yield/yield/examples/library/python/investigate-failure/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "python", "run": [ "python3", "../src/investigate-failure.py" diff --git a/labs/22-yield/yield/examples/library/python/migrate-database/skill.json b/labs/22-yield/yield/examples/library/python/migrate-database/skill.json index db517239..f9f04e37 100644 --- a/labs/22-yield/yield/examples/library/python/migrate-database/skill.json +++ b/labs/22-yield/yield/examples/library/python/migrate-database/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "python", "run": [ "python3", "../src/migrate-database.py" diff --git a/labs/22-yield/yield/examples/library/python/publish-ios/skill.json b/labs/22-yield/yield/examples/library/python/publish-ios/skill.json index efad712d..4d1c494a 100644 --- a/labs/22-yield/yield/examples/library/python/publish-ios/skill.json +++ b/labs/22-yield/yield/examples/library/python/publish-ios/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "python", "run": [ "python3", "../src/publish-ios.py" diff --git a/labs/22-yield/yield/examples/library/python/qa-web-change/skill.json b/labs/22-yield/yield/examples/library/python/qa-web-change/skill.json index 8f2774d4..c8b2ed35 100644 --- a/labs/22-yield/yield/examples/library/python/qa-web-change/skill.json +++ b/labs/22-yield/yield/examples/library/python/qa-web-change/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "python", "run": [ "python3", "../src/qa-web-change.py" diff --git a/labs/22-yield/yield/examples/library/python/release-package/skill.json b/labs/22-yield/yield/examples/library/python/release-package/skill.json index a5942f58..39707cab 100644 --- a/labs/22-yield/yield/examples/library/python/release-package/skill.json +++ b/labs/22-yield/yield/examples/library/python/release-package/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "python", "run": [ "python3", "../src/release-package.py" diff --git a/labs/22-yield/yield/examples/library/python/repair-ci/skill.json b/labs/22-yield/yield/examples/library/python/repair-ci/skill.json index eba3c652..a17bf942 100644 --- a/labs/22-yield/yield/examples/library/python/repair-ci/skill.json +++ b/labs/22-yield/yield/examples/library/python/repair-ci/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "python", "run": [ "python3", "../src/repair-ci.py" diff --git a/labs/22-yield/yield/examples/library/python/review-branch/skill.json b/labs/22-yield/yield/examples/library/python/review-branch/skill.json index 8400ee25..223fd5c4 100644 --- a/labs/22-yield/yield/examples/library/python/review-branch/skill.json +++ b/labs/22-yield/yield/examples/library/python/review-branch/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "python", "run": [ "python3", "../src/review-branch.py" diff --git a/labs/22-yield/yield/examples/library/python/triage-issue/skill.json b/labs/22-yield/yield/examples/library/python/triage-issue/skill.json index 38d476f6..7a84c88d 100644 --- a/labs/22-yield/yield/examples/library/python/triage-issue/skill.json +++ b/labs/22-yield/yield/examples/library/python/triage-issue/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "python", "run": [ "python3", "../src/triage-issue.py" diff --git a/labs/22-yield/yield/examples/library/python/upgrade-dependency/skill.json b/labs/22-yield/yield/examples/library/python/upgrade-dependency/skill.json index 50fd9b5a..6512802f 100644 --- a/labs/22-yield/yield/examples/library/python/upgrade-dependency/skill.json +++ b/labs/22-yield/yield/examples/library/python/upgrade-dependency/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "python", "run": [ "python3", "../src/upgrade-dependency.py" diff --git a/labs/22-yield/yield/examples/library/rust/audit-security/skill.json b/labs/22-yield/yield/examples/library/rust/audit-security/skill.json index d69c8273..1bd1f2d7 100644 --- a/labs/22-yield/yield/examples/library/rust/audit-security/skill.json +++ b/labs/22-yield/yield/examples/library/rust/audit-security/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "rust", "run": [ "cargo", "run", diff --git a/labs/22-yield/yield/examples/library/rust/investigate-failure/skill.json b/labs/22-yield/yield/examples/library/rust/investigate-failure/skill.json index bb2ddc8e..a358d69d 100644 --- a/labs/22-yield/yield/examples/library/rust/investigate-failure/skill.json +++ b/labs/22-yield/yield/examples/library/rust/investigate-failure/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "rust", "run": [ "cargo", "run", diff --git a/labs/22-yield/yield/examples/library/rust/migrate-database/skill.json b/labs/22-yield/yield/examples/library/rust/migrate-database/skill.json index ca11a48b..0fd48ddc 100644 --- a/labs/22-yield/yield/examples/library/rust/migrate-database/skill.json +++ b/labs/22-yield/yield/examples/library/rust/migrate-database/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "rust", "run": [ "cargo", "run", diff --git a/labs/22-yield/yield/examples/library/rust/publish-ios/skill.json b/labs/22-yield/yield/examples/library/rust/publish-ios/skill.json index 25f60730..507524cf 100644 --- a/labs/22-yield/yield/examples/library/rust/publish-ios/skill.json +++ b/labs/22-yield/yield/examples/library/rust/publish-ios/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "rust", "run": [ "cargo", "run", diff --git a/labs/22-yield/yield/examples/library/rust/qa-web-change/skill.json b/labs/22-yield/yield/examples/library/rust/qa-web-change/skill.json index 8f940f3f..7ce1e221 100644 --- a/labs/22-yield/yield/examples/library/rust/qa-web-change/skill.json +++ b/labs/22-yield/yield/examples/library/rust/qa-web-change/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "rust", "run": [ "cargo", "run", diff --git a/labs/22-yield/yield/examples/library/rust/release-package/skill.json b/labs/22-yield/yield/examples/library/rust/release-package/skill.json index 6bebf4d4..f5c4340c 100644 --- a/labs/22-yield/yield/examples/library/rust/release-package/skill.json +++ b/labs/22-yield/yield/examples/library/rust/release-package/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "rust", "run": [ "cargo", "run", diff --git a/labs/22-yield/yield/examples/library/rust/repair-ci/skill.json b/labs/22-yield/yield/examples/library/rust/repair-ci/skill.json index 0a034f10..6e6271c6 100644 --- a/labs/22-yield/yield/examples/library/rust/repair-ci/skill.json +++ b/labs/22-yield/yield/examples/library/rust/repair-ci/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "rust", "run": [ "cargo", "run", diff --git a/labs/22-yield/yield/examples/library/rust/review-branch/skill.json b/labs/22-yield/yield/examples/library/rust/review-branch/skill.json index ca6384b7..76823f4d 100644 --- a/labs/22-yield/yield/examples/library/rust/review-branch/skill.json +++ b/labs/22-yield/yield/examples/library/rust/review-branch/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "rust", "run": [ "cargo", "run", diff --git a/labs/22-yield/yield/examples/library/rust/triage-issue/skill.json b/labs/22-yield/yield/examples/library/rust/triage-issue/skill.json index a2994fbe..4ff4f096 100644 --- a/labs/22-yield/yield/examples/library/rust/triage-issue/skill.json +++ b/labs/22-yield/yield/examples/library/rust/triage-issue/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "rust", "run": [ "cargo", "run", diff --git a/labs/22-yield/yield/examples/library/rust/upgrade-dependency/skill.json b/labs/22-yield/yield/examples/library/rust/upgrade-dependency/skill.json index 5d80d023..a4694f56 100644 --- a/labs/22-yield/yield/examples/library/rust/upgrade-dependency/skill.json +++ b/labs/22-yield/yield/examples/library/rust/upgrade-dependency/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "rust", "run": [ "cargo", "run", diff --git a/labs/22-yield/yield/examples/library/typescript/audit-security/skill.json b/labs/22-yield/yield/examples/library/typescript/audit-security/skill.json index 86e81d90..71602c42 100644 --- a/labs/22-yield/yield/examples/library/typescript/audit-security/skill.json +++ b/labs/22-yield/yield/examples/library/typescript/audit-security/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "typescript", "run": [ "node", "../src/audit-security.ts" diff --git a/labs/22-yield/yield/examples/library/typescript/investigate-failure/skill.json b/labs/22-yield/yield/examples/library/typescript/investigate-failure/skill.json index 291e572e..25f2c122 100644 --- a/labs/22-yield/yield/examples/library/typescript/investigate-failure/skill.json +++ b/labs/22-yield/yield/examples/library/typescript/investigate-failure/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "typescript", "run": [ "node", "../src/investigate-failure.ts" diff --git a/labs/22-yield/yield/examples/library/typescript/migrate-database/skill.json b/labs/22-yield/yield/examples/library/typescript/migrate-database/skill.json index e436e406..8cf2dd58 100644 --- a/labs/22-yield/yield/examples/library/typescript/migrate-database/skill.json +++ b/labs/22-yield/yield/examples/library/typescript/migrate-database/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "typescript", "run": [ "node", "../src/migrate-database.ts" diff --git a/labs/22-yield/yield/examples/library/typescript/publish-ios/skill.json b/labs/22-yield/yield/examples/library/typescript/publish-ios/skill.json index 0ec603d0..d9709e5a 100644 --- a/labs/22-yield/yield/examples/library/typescript/publish-ios/skill.json +++ b/labs/22-yield/yield/examples/library/typescript/publish-ios/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "typescript", "run": [ "node", "../src/publish-ios.ts" diff --git a/labs/22-yield/yield/examples/library/typescript/qa-web-change/skill.json b/labs/22-yield/yield/examples/library/typescript/qa-web-change/skill.json index 47999973..e1bce273 100644 --- a/labs/22-yield/yield/examples/library/typescript/qa-web-change/skill.json +++ b/labs/22-yield/yield/examples/library/typescript/qa-web-change/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "typescript", "run": [ "node", "../src/qa-web-change.ts" diff --git a/labs/22-yield/yield/examples/library/typescript/release-package/skill.json b/labs/22-yield/yield/examples/library/typescript/release-package/skill.json index 290467b2..6ce7a808 100644 --- a/labs/22-yield/yield/examples/library/typescript/release-package/skill.json +++ b/labs/22-yield/yield/examples/library/typescript/release-package/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "typescript", "run": [ "node", "../src/release-package.ts" diff --git a/labs/22-yield/yield/examples/library/typescript/repair-ci/skill.json b/labs/22-yield/yield/examples/library/typescript/repair-ci/skill.json index 7324bf8f..71c33768 100644 --- a/labs/22-yield/yield/examples/library/typescript/repair-ci/skill.json +++ b/labs/22-yield/yield/examples/library/typescript/repair-ci/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "typescript", "run": [ "node", "../src/repair-ci.ts" diff --git a/labs/22-yield/yield/examples/library/typescript/review-branch/skill.json b/labs/22-yield/yield/examples/library/typescript/review-branch/skill.json index 84488ecf..1d897e43 100644 --- a/labs/22-yield/yield/examples/library/typescript/review-branch/skill.json +++ b/labs/22-yield/yield/examples/library/typescript/review-branch/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "typescript", "run": [ "node", "../src/review-branch.ts" diff --git a/labs/22-yield/yield/examples/library/typescript/triage-issue/skill.json b/labs/22-yield/yield/examples/library/typescript/triage-issue/skill.json index 67bb078d..584ce676 100644 --- a/labs/22-yield/yield/examples/library/typescript/triage-issue/skill.json +++ b/labs/22-yield/yield/examples/library/typescript/triage-issue/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "typescript", "run": [ "node", "../src/triage-issue.ts" diff --git a/labs/22-yield/yield/examples/library/typescript/upgrade-dependency/skill.json b/labs/22-yield/yield/examples/library/typescript/upgrade-dependency/skill.json index c35ca5c2..536fd2d1 100644 --- a/labs/22-yield/yield/examples/library/typescript/upgrade-dependency/skill.json +++ b/labs/22-yield/yield/examples/library/typescript/upgrade-dependency/skill.json @@ -1,4 +1,6 @@ { + "version": 1, + "language": "typescript", "run": [ "node", "../src/upgrade-dependency.ts" diff --git a/labs/22-yield/yield/examples/release-checklist/skill.json b/labs/22-yield/yield/examples/release-checklist/skill.json index 36115dd2..a09b572c 100644 --- a/labs/22-yield/yield/examples/release-checklist/skill.json +++ b/labs/22-yield/yield/examples/release-checklist/skill.json @@ -1,3 +1,8 @@ { - "run": ["node", "main.ts"] + "version": 1, + "language": "typescript", + "run": [ + "node", + "main.ts" + ] } diff --git a/labs/22-yield/yield/go.mod b/labs/22-yield/yield/go.mod index 60e26d9d..88e2725c 100644 --- a/labs/22-yield/yield/go.mod +++ b/labs/22-yield/yield/go.mod @@ -2,6 +2,9 @@ module github.com/operatorstack/yield go 1.26.5 -require github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 +require ( + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 + gopkg.in/yaml.v3 v3.0.1 +) require golang.org/x/text v0.14.0 // indirect diff --git a/labs/22-yield/yield/go.sum b/labs/22-yield/yield/go.sum index 8fee20f8..7e38d45a 100644 --- a/labs/22-yield/yield/go.sum +++ b/labs/22-yield/yield/go.sum @@ -4,3 +4,7 @@ github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEV github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=