Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,21 @@ repository. Generated adapters never use a global `yskill` from `PATH`.

```bash
# TypeScript
npm install --save-exact @operatorstack/yield@0.1.23 --registry=https://get.operatorstack.systems/npm/
npm install --save-exact @operatorstack/yield@0.1.26 --registry=https://get.operatorstack.systems/npm/
npm exec -- yskill --version

# Python, after creating and activating .venv
python -m pip install yieldskill==0.1.23 --index-url https://get.operatorstack.systems/pip/simple/
python -m pip install yieldskill==0.1.26 --index-url https://get.operatorstack.systems/pip/simple/
python -m yieldskill --version

# Go, from the repository root
mkdir -p .yield/bin
GOBIN="$PWD/.yield/bin" GOPROXY=https://get.operatorstack.systems/go,direct \
go install github.com/operatorstack/yield/cmd/yskill@v0.1.23
go install github.com/operatorstack/yield/cmd/yskill@v0.1.26
.yield/bin/yskill --version

# Rust, from the repository root
cargo install yieldskill@0.1.23 --root .yield \
cargo install yieldskill@0.1.26 --root .yield \
--index sparse+https://get.operatorstack.systems/cargo/index/ --locked
.yield/bin/yskill --version
```
Expand Down
30 changes: 17 additions & 13 deletions UPSTREAM.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
"files": {
".gitignore": "803c5f79d6da7f2c5a1dc0ce27c53b8e5c059309b165782831c4c472af058a4c",
"LICENSE": "fff261ce507eabd57666c283a621f33e183a3aedebda04c4ecbc6309a62f5edf",
"README.md": "ad3d31327720cfaa382211fa13bc3df20134299d54222190307769890915ad23",
"cmd/yskill/agents.go": "3cf1309d3cebfe9a66b664d88992805326f813fa44b523e637afb86555ffd9ce",
"cmd/yskill/agents_test.go": "a167a0a6d59b17ff50a2e3aec010113961b085eb26d008ba4380127a1f801040",
"README.md": "c7af9266ee16bf4c315a3cabdb15f8f0d8b87501bce7ed3d184c53dfb144ddd7",
"cmd/yskill/agents.go": "8129c073b7b56ca7dcd50c5d90640ad80522c88822c629d1bdc79a386a31a8f4",
"cmd/yskill/agents_test.go": "d6202d208036be6b2b50f7d8f9c3a9b4c2b3eea9556477ef9ad4d56201c6b832",
"cmd/yskill/main.go": "5eefea8fdd3e040a940bde80aac793fb97c61c18586bd993dab0395f57e5a81f",
"cmd/yskill/main_test.go": "12a73231e0723fd4e1cee432a08eaa11950401c68a6d59887bac382fb2e5a19a",
"cmd/yskill/main_test.go": "af22b208ffcb6f2ae692132fadd879218d51899c21285bc8e914face61a42ad9",
"cmd/yskill/registry/README.md": "fab385921ee7972f76deb94a3b7ae41184735598ea4145c9d8ccdde2c2b876c4",
"cmd/yskill/registry/VERCEL_SKILLS_LICENSE": "779258e329008bdb9330e6c1daad644ff867f164d11c4cc4404350479f3e92ee",
"cmd/yskill/registry/agents.json": "6edfee31cdc0390516adbb107fc97fc3531eb02e1a28b9d62b5b86fdccaed7c1",
"cmd/yskill/scaffold.go": "fe79b27930673b2810f405474dae50ab494767e75e70821f21e153bd6972829f",
"cmd/yskill/scaffold.go": "404f8418a721b3e179fcd6d814640628a56930f54a9473a5f28dcae433e74ac7",
"cmd/yskill/skillmeta.go": "12b0c25689bf825d676404af7b3fb74fb92db3d9e222d4e553d8800d86aea45e",
"docs/README.md": "b4103bc38ca5b3b313d6cc7eae5f11276c03a8bd6123cfa101cb7372ff43fa72",
"docs/agent-setup.md": "0f73c8322f15a799beb9ad4a88079d8343254c18fe01c59634f4da036d4c5744",
"docs/agent-setup.md": "72d2f7af6ca93b3865c8d7095479320127adc5edfdcce940494adf28f3abe7fc",
"docs/convert-existing-skill.md": "0dd538e908a1f2d3a1958e4b2cc1efede2a8e7a73341e32c1a75fec69a9acb8a",
"docs/examples.md": "f6ef194f1d321692873b5cf35751f369d8215084c5a754b4a6725480ab9b2e37",
"docs/locus-conformance.md": "a71fd5a72678dcad344afc71cd7a788090ef008a8e5e82d925da5a3db41af6a0",
Expand All @@ -37,7 +37,7 @@
"docs/primitives/outcomes.md": "a584976fada6af3772894736e686f5deb466d3cc8fd1ecacf49f6b9042d6a232",
"docs/primitives/require.md": "1ea8c3a9ea11aa61e2baee01cf83fca8c872e846a47d7ef230cf6b76d04c90d8",
"docs/primitives/run-command.md": "af9bcc617f90fd2ee0086aeabb5c98854542efe81636a020699c2f688f8b3b8c",
"docs/quickstart.md": "98d1f4771d9ee9bab06a811aed8098531478d76b26291ddeb8e5ed443a7fc768",
"docs/quickstart.md": "92aad6226da57c89444499ea06f74125008fe486e8f35066b9f4c09597a7e5f6",
"docs/reference/cli.md": "a36189229ebadf0c6b298f56b03013cb23720448bbe2f8c85297933111ef06ea",
"docs/reference/execution-model.md": "58365a7870a081f4ed9e9ba144aae77aa5a1d8676d2bbf6979483be4a06eca80",
"docs/reference/guarantees.md": "fce2006ad46a83c4ae34d221a83d68bc06a776e7c4f54d938bdcdac91977aa50",
Expand Down Expand Up @@ -68,7 +68,7 @@
"evals/package.json": "84ac7bd7d1c0d1db2233dab54754296195b267e5e907d9f6c402d0d44d569adb",
"evals/results/README.md": "65b74bfab83dfc51fc5b8a3a4824c37b722fb3a358a5dcfb217dc2af199f4801",
"evals/results/latest-agent.json": "1484174819ae29bbcffeb615036fa165518669525e6d664123f6a38ca3614b69",
"evals/results/latest.json": "422c0fa860baa94ae336894c15c012beb04d78f46389dac9655a5a5bde4e670b",
"evals/results/latest.json": "54cbe6c70e57a38735ccbb60aaccbb23dad952b80cd852a3b3c1013baa207815",
"evals/scripts/run.mjs": "17a62cd0f7fda73d84a9bfa21c9c3253ba52a4a23eece09523cfea244e3f9940",
"evals/scripts/validate.mjs": "42d51ea75f9418e45dd1797d1fe02cbc046752d3e3c37d41bc8d3a47d5865d4d",
"examples/convert-skill/SKILL.md": "e6376f34365d4ac030d316db55e91f0c606a501668099f4ecf1e27d43ec806a2",
Expand Down Expand Up @@ -292,8 +292,11 @@
"packaging/assemble.mjs": "295e0fa5adc9898cf64ff554524329dedcfe43843804b260173f34a778f3e5ab",
"packaging/cargo-index.mjs": "3c8845ab1b82a568c076229831580d8f3ad684dc6ccb74f4860d69637eb06735",
"packaging/cargo-index.test.mjs": "62a986e7f98d83007bd4b0ca14ed0e613c8ec4e240656da5af23521d7b69653c",
"packaging/rust-launcher.rs": "d7a534609031397c816fb24702002c3c16be41cb1fedbd9c53b5915ba5f670ab",
"packaging/rust-launcher.rs": "cc71e9ab4e471d593f7eaf818a3b9f62b599bc2e846d9c632c8aae4da69c17e8",
"packaging/rust-launcher.test.mjs": "27c04d2aa6b98ef14b507264ce1d215aa6421a8f3ce4a64110f1347c9a9e60d5",
"packaging/targets.mjs": "354e0700cf1c4f1a864e5eaa0fe951717559a381d50ad6e6c4f491449aeb526a",
"packaging/verify-registry-history.mjs": "ba43a9aeb6e80fe23ace41f23623f6d1526369e87489bd5b834991348ba80a6c",
"packaging/verify-registry-history.test.mjs": "c7a0b6a41aa8bdf9b2d2d486c40d14cc3224de7225dea3f6b48e66747e4496fa",
"release-notes/2026-08-01-agent-workflow-evaluation.md": "61fe8b5f893d21dac9dba9fa759818637c81ce2cd844571274e50f763ac72d00",
"release-notes/2026-08-01-docs-and-typescript-package.md": "93382375cb47187092aff9447b280853151c738b140879828c74576a109195b8",
"release-notes/2026-08-01-evaluation-case-guides.md": "570bc996eb4d2892456a02d938fb6299d106c431bb842ce726db1df550f779d7",
Expand All @@ -310,12 +313,13 @@
"release-notes/2026-08-02-go-sdk-pin-detection.md": "d835d0a9efebba16ab782a7b5e8730a873545b395e644b6e7b21cf93bed7d1a4",
"release-notes/2026-08-02-skill-workflows.md": "8cd0cded1922387ef8e81b3e084c12e440f729aa778672f9ec91ffa153df5297",
"release-notes/2026-08-02-v0-1-23-dx.md": "538aaf62beeb15e132cfde35f47217edce7d1e4ef5c4036fd00ed96f8362b2b7",
"sdk/python/README.md": "aeff405ce89928814e93704ad593f9ee976d400c227804343947f09ca8ad3062",
"release-notes/2026-08-02-v0-1-26-dx.md": "b3fafa640e8969351dfb4897686594b2144f3d3609f82dbee4871279ada54735",
"sdk/python/README.md": "c4f0fa62dc741d3f0fb6694308d9cdfafed7c66eab6b86d19fafbe6f38edbae4",
"sdk/python/pyproject.toml": "0a36d0a22a29da6b8c42e3a5a91aed1b6f6d0813712a9e582ab08e8ce4290de6",
"sdk/python/test_cli.py": "ed1d17ffa4190dd3733cc3042cf49a66710ca5ff6303d1202330a2d53cd12fac",
"sdk/python/test_cli.py": "829778867cb18d1be6a8737cd9b9ef51077aad84f30d32bcd80fd83b2b1469a6",
"sdk/python/yieldskill/__init__.py": "c43dbb2a25ed7e8537521561a16e2e5fc7921d60f913b22ac298255bd44fe60e",
"sdk/python/yieldskill/__main__.py": "2f2978db2ba5bf8034466902e0c0f5fa61d0961b262d776598ba0d9bb47d6b62",
"sdk/python/yieldskill/_cli.py": "70efa4d505117bf83e0aeaf9dd729b62864cdb0ff360cfc24390932305093310",
"sdk/python/yieldskill/_cli.py": "3a3caa9008a4bf366bccbbea5d1c22ec86f7d48bc56cdf9c9e28e9ffa9e921a3",
"sdk/rust/Cargo.toml": "da29a554401a316f6a0e7ba53d166f4c3741ac9aefe1e3f2d66af1bfa3a2d88a",
"sdk/rust/src/lib.rs": "581882dca50cf4e27a8f2e05b013b2bc091802b6a20d89d58ffbcdf5c23372a7",
"sdk/typescript/bin/runtime.mjs": "909da102e8ed8ccc917cd17d88acc65869a0a2bdb6f875f76d763383999a2b57",
Expand All @@ -330,7 +334,7 @@
"generator": "operatorstack/yield:project",
"schema_version": 1,
"source": {
"commit": "2d0884460f31e85cda431e7f4e45c6184fcbda7d",
"commit": "ad137fc9d418a58336dfb6faaaccb47fbd8f018b",
"path": "labs/22-yield",
"repository": "operatorstack/intelligence-flow"
}
Expand Down
30 changes: 30 additions & 0 deletions cmd/yskill/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,33 @@ func ensureContainedWrite(repoRoot, destination string) error {

func commandExists(name string) bool { _, err := exec.LookPath(name); return err == nil }

var currentExecutable = os.Executable

func repositoryRootFromLocalRuntime() (string, bool) {
executable := strings.TrimSpace(os.Getenv("YIELD_LAUNCHER_PATH"))
if executable == "" {
var err error
executable, err = currentExecutable()
if err != nil {
return "", false
}
}
executable, err := filepath.Abs(executable)
if err != nil {
return "", false
}
name := strings.ToLower(filepath.Base(executable))
if name != "yskill" && name != "yskill.exe" {
return "", false
}
bin := filepath.Dir(executable)
state := filepath.Dir(bin)
if filepath.Base(bin) != "bin" || filepath.Base(state) != ".yield" {
return "", false
}
return filepath.Dir(state), true
}

func findRepoRoot(skillDir, explicit string) (string, error) {
if explicit != "" {
root, err := filepath.Abs(explicit)
Expand All @@ -528,6 +555,9 @@ func findRepoRoot(skillDir, explicit string) (string, error) {
}
return filepath.Clean(root), nil
}
if root, ok := repositoryRootFromLocalRuntime(); ok && within(root, skillDir) {
return filepath.Clean(root), nil
}
for current := filepath.Clean(skillDir); ; current = filepath.Dir(current) {
if _, err := os.Stat(filepath.Join(current, ".git")); err == nil {
return current, nil
Expand Down
35 changes: 35 additions & 0 deletions cmd/yskill/agents_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,41 @@ func TestRepositoryRuntimeLauncherUsesNativeWindowsPath(t *testing.T) {
}
}

func TestRepositoryRootIsInferredFromLocalRuntimeOutsideGit(t *testing.T) {
repo := t.TempDir()
skill := filepath.Join(repo, "skills", "review")
writeTestFile(t, filepath.Join(skill, "SKILL.md"), "---\nname: review\ndescription: Review a change before it is merged.\n---\n")
previousExecutable := currentExecutable
currentExecutable = func() (string, error) { return localRuntimePath(repo), nil }
t.Cleanup(func() { currentExecutable = previousExecutable })

got, err := findRepoRoot(skill, "")
if err != nil {
t.Fatal(err)
}
if got != repo {
t.Fatalf("repository root = %q, want %q", got, repo)
}
}

func TestRepositoryRootUsesTheRustWrapperPath(t *testing.T) {
repo := t.TempDir()
skill := filepath.Join(repo, "skills", "review")
writeTestFile(t, filepath.Join(skill, "SKILL.md"), "---\nname: review\ndescription: Review a change before it is merged.\n---\n")
t.Setenv("YIELD_LAUNCHER_PATH", localRuntimePath(repo))
previousExecutable := currentExecutable
currentExecutable = func() (string, error) { return filepath.Join(t.TempDir(), "runtime-cache", "yskill"), nil }
t.Cleanup(func() { currentExecutable = previousExecutable })

got, err := findRepoRoot(skill, "")
if err != nil {
t.Fatal(err)
}
if got != repo {
t.Fatalf("repository root = %q, want %q", got, repo)
}
}

func TestRepositoryRuntimeRejectsMissingAndWrongVersions(t *testing.T) {
repo := t.TempDir()
path := localRuntimePath(repo)
Expand Down
36 changes: 36 additions & 0 deletions cmd/yskill/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io"
"os"
"path/filepath"
"runtime"
"runtime/debug"
"strings"
"testing"
Expand Down Expand Up @@ -262,6 +263,41 @@ func TestGoScaffoldCanResolveItsPinnedModuleOnFirstRun(t *testing.T) {
}
}

func TestLocalGoAndRustScaffoldsKeepTheInvokedRuntime(t *testing.T) {
previousVersion := version
previousTidyGoModule := tidyGoModule
previousExecutable := currentExecutable
version = "0.1.23"
tidyGoModule = func(string) error { return nil }
t.Cleanup(func() {
version = previousVersion
tidyGoModule = previousTidyGoModule
currentExecutable = previousExecutable
})

for _, language := range []string{"go", "rust"} {
t.Run(language, func(t *testing.T) {
repo := t.TempDir()
currentExecutable = func() (string, error) { return localRuntimePath(repo), nil }
dir := filepath.Join(repo, "skills", "safe-change")
if err := scaffoldSkill(dir, language, "", "Run safety checks before applying a change."); err != nil {
t.Fatal(err)
}
skill := readTestFile(t, filepath.Join(dir, "SKILL.md"))
launcher := repositoryRuntimeLauncher(filepath.Join(".yield", "bin", filepath.Base(localRuntimePath(repo))), runtime.GOOS)
workflow := shellQuote("skills/safe-change")
for _, command := range []string{
launcher + " run " + workflow,
launcher + " respond <run-id> --value <answer> --skill " + workflow,
} {
if !strings.Contains(skill, command) {
t.Fatalf("SKILL.md does not preserve local runtime command %q:\n%s", command, skill)
}
}
})
}
}

func TestPythonScaffoldUsesRelocatableInterpreter(t *testing.T) {
previousVersion := version
version = "0.1.9"
Expand Down
48 changes: 37 additions & 11 deletions cmd/yskill/scaffold.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os/exec"
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
)
Expand Down Expand Up @@ -37,6 +38,36 @@ func packageVersion() string {
return "0.0.0"
}

func scaffoldCommand(language, dir string) (launcher, workflow string) {
launcher = map[string]string{
"typescript": "npm exec -- yskill",
"python": "python -m yieldskill",
"go": "yskill",
"rust": "yskill",
}[language]
workflow = "."
if language != "go" && language != "rust" {
return launcher, workflow
}
root, ok := repositoryRootFromLocalRuntime()
if !ok {
return launcher, workflow
}
absDir, err := filepath.Abs(dir)
if err != nil || !within(root, absDir) {
return launcher, workflow
}
runtimeRel, err := filepath.Rel(root, localRuntimePath(root))
if err != nil {
return launcher, workflow
}
skillRel, err := filepath.Rel(root, absDir)
if err != nil {
return launcher, workflow
}
return repositoryRuntimeLauncher(runtimeRel, runtime.GOOS), shellQuote(filepath.ToSlash(skillRel))
}

func scaffoldSkill(dir, language, sdkPath, description string) error {
language = strings.ToLower(strings.TrimSpace(language))
if language != "typescript" && language != "python" && language != "go" && language != "rust" {
Expand Down Expand Up @@ -69,14 +100,9 @@ func scaffoldSkill(dir, language, sdkPath, description string) error {
return os.WriteFile(path, []byte(content), 0o644)
}

launcher := map[string]string{
"typescript": "npm exec -- yskill",
"python": "python -m yieldskill",
"go": "yskill",
"rust": "yskill",
}[language]
launcher, workflow := scaffoldCommand(language, dir)
files := scaffoldFiles(name, language, sdkPath)
files["SKILL.md"] = fmt.Sprintf(skillMD, name, yamlString(strings.TrimSpace(description)), launcher, launcher)
files["SKILL.md"] = fmt.Sprintf(skillMD, name, yamlString(strings.TrimSpace(description)), launcher, workflow, launcher, workflow)
files["fixtures/responses.json"] = "{\n \"confirm-start\": {\"value\": \"yes\"}\n}\n"
files["fixtures/test.json"] = "{\n \"version\": 1,\n \"setup\": [],\n \"after_response\": {},\n \"teardown\": []\n}\n"
keys := make([]string, 0, len(files))
Expand All @@ -99,8 +125,8 @@ func scaffoldSkill(dir, language, sdkPath, description string) error {
}
fmt.Printf("init: %s skill %q scaffolded in %s\n", language, name, dir)
fmt.Println("next: replace the starter program and fixtures with the described workflow")
fmt.Printf("test: %s doctor %s --test\n", launcher, shellQuote(dir))
fmt.Printf("then: %s register %s\n", launcher, shellQuote(dir))
fmt.Printf("test: %s doctor %s --test\n", launcher, workflow)
fmt.Printf("then: %s register %s\n", launcher, workflow)
return nil
}

Expand Down Expand Up @@ -151,11 +177,11 @@ description: %s

Run:

%s run .
%s run %s

Follow each returned operation exactly. Answer it directly:

%s respond <run-id> --value <answer> --skill .
%s respond <run-id> --value <answer> --skill %s

Do not skip an operation or invent a response.
`
Expand Down
6 changes: 4 additions & 2 deletions docs/agent-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ answer, the adapter uses `yskill respond`; it does not create `response.json`.
Use `--value` for a person’s answer and `--result-json` for structured agent
work. The file-based `resume --response` command remains available for CI.

Workflow-only `doctor` works without `.git`. For registration in such a
directory, pass `--root` so Yield knows where agent adapters belong.
Workflow-only `doctor` works without `.git`. A Go or Rust runtime under
`.yield/bin` also identifies the project root for `init`, `doctor`, and
registration. For other non-Git layouts, pass `--root` so Yield knows where
agent adapters belong.
```
2 changes: 1 addition & 1 deletion docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ You need Node.js 24 or newer.
mkdir yield-example
cd yield-example
npm init -y
npm install --save-exact @operatorstack/yield@0.1.23 \
npm install --save-exact @operatorstack/yield@0.1.26 \
--registry=https://get.operatorstack.systems/npm/
npm exec -- yskill --version
```
Expand Down
4 changes: 2 additions & 2 deletions evals/results/latest.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"schema_version": 2,
"methodology_version": "1.1",
"generated_at": "2026-08-02T14:55:09.652Z",
"source_digest": "0c8520275d4bd2d0e607c6dbcb0ebf7696106451f2a7fd2acb71ea2c1ed16286",
"generated_at": "2026-08-02T16:31:06.974Z",
"source_digest": "7e61b35562440679e70f58f61a73d06e13d6b823b72b36498a48a9156775dc20",
"status": "passed",
"workflow_conformance": {
"passed": 40,
Expand Down
1 change: 1 addition & 0 deletions packaging/rust-launcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ fn main() {
let mut command = Command::new(path);
command.args(args);
if std::env::var_os("YIELD_LANGUAGE").is_none() { command.env("YIELD_LANGUAGE", "rust"); }
if let Ok(launcher) = std::env::current_exe() { command.env("YIELD_LAUNCHER_PATH", launcher); }
#[cfg(unix)] {
use std::os::unix::process::CommandExt;
let error = command.exec();
Expand Down
9 changes: 9 additions & 0 deletions packaging/rust-launcher.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";

test("Rust wrapper passes its repository-local path to the runtime", async () => {
const source = await readFile(new URL("./rust-launcher.rs", import.meta.url), "utf8");
assert.match(source, /current_exe\(\)/);
assert.match(source, /YIELD_LAUNCHER_PATH/);
});
Loading
Loading