Skip to content

Commit 985b2fc

Browse files
authored
Harden Yield 0.1.28 developer experience (#296)
* Harden Yield init paths and Rust scaffolds * Refresh Yield evaluation receipt
1 parent ad137fc commit 985b2fc

5 files changed

Lines changed: 119 additions & 8 deletions

File tree

labs/22-yield/yield/cmd/yskill/main_test.go

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"flag"
66
"io"
77
"os"
8+
"os/exec"
89
"path/filepath"
910
"runtime"
1011
"runtime/debug"
@@ -210,8 +211,10 @@ func TestScaffoldSkillWritesLanguageSpecificEntrypoints(t *testing.T) {
210211
t.Fatalf("skill.json = version %d language %q", generated.Version, generated.Language)
211212
}
212213
skill := readTestFile(t, filepath.Join(dir, "SKILL.md"))
213-
if !strings.Contains(skill, tt.command) {
214-
t.Fatalf("SKILL.md does not contain %q:\n%s", tt.command, skill)
214+
workflow := shellQuoteForPlatform(dir, runtime.GOOS)
215+
command := strings.Replace(tt.command, " .", " "+workflow, 1)
216+
if !strings.Contains(skill, command) {
217+
t.Fatalf("SKILL.md does not contain %q:\n%s", command, skill)
215218
}
216219
entrypoint := map[string]string{
217220
"typescript": "main.ts",
@@ -244,6 +247,97 @@ func TestScaffoldSkillWritesLanguageSpecificEntrypoints(t *testing.T) {
244247
}
245248
}
246249

250+
func TestPackageScaffoldsPrintCreatedWorkflowInNextCommands(t *testing.T) {
251+
previousVersion := version
252+
version = "0.1.28"
253+
t.Cleanup(func() { version = previousVersion })
254+
255+
for _, tt := range []struct {
256+
language string
257+
launcher string
258+
}{
259+
{language: "typescript", launcher: "npm exec -- yskill"},
260+
{language: "python", launcher: "python -m yieldskill"},
261+
} {
262+
t.Run(tt.language, func(t *testing.T) {
263+
root := filepath.Join(t.TempDir(), "project with space")
264+
dir := filepath.Join(root, "skills", "safe-change")
265+
output := captureStdout(t, func() {
266+
if err := scaffoldSkill(dir, tt.language, "", "Check a safe change before applying it."); err != nil {
267+
t.Fatal(err)
268+
}
269+
})
270+
workflow := shellQuoteForPlatform(dir, runtime.GOOS)
271+
for _, line := range []string{
272+
"test: " + tt.launcher + " doctor " + workflow + " --test",
273+
"then: " + tt.launcher + " register " + workflow,
274+
} {
275+
if !strings.Contains(output, line) {
276+
t.Fatalf("init output does not contain %q:\n%s", line, output)
277+
}
278+
}
279+
})
280+
}
281+
}
282+
283+
func TestShellQuoteForPlatform(t *testing.T) {
284+
for _, tt := range []struct {
285+
name, value, goos, want string
286+
}{
287+
{name: "relative", value: "skills/review", goos: "linux", want: "'skills/review'"},
288+
{name: "explicit relative", value: "./skills/review", goos: "linux", want: "'./skills/review'"},
289+
{name: "absolute", value: "/tmp/project/skills/review", goos: "linux", want: "'/tmp/project/skills/review'"},
290+
{name: "nested with space", value: "workflows/team one/safe change's", goos: "linux", want: `'workflows/team one/safe change'"'"'s'`},
291+
{name: "windows", value: `skills\safe change's`, goos: "windows", want: `'skills\safe change''s'`},
292+
} {
293+
t.Run(tt.name, func(t *testing.T) {
294+
if got := shellQuoteForPlatform(tt.value, tt.goos); got != tt.want {
295+
t.Fatalf("quote = %q, want %q", got, tt.want)
296+
}
297+
})
298+
}
299+
}
300+
301+
func TestRustScaffoldNamesPrimaryBinary(t *testing.T) {
302+
previousVersion := version
303+
version = "0.1.28"
304+
t.Cleanup(func() { version = previousVersion })
305+
dir := filepath.Join(t.TempDir(), "safe-change")
306+
if err := scaffoldSkill(dir, "rust", "", "Check a safe change before applying it."); err != nil {
307+
t.Fatal(err)
308+
}
309+
manifest := readTestFile(t, filepath.Join(dir, "skill.json"))
310+
if manifest != "{\"version\":1,\"language\":\"rust\",\"run\":[\"cargo\",\"run\",\"--quiet\",\"--bin\",\"safe-change\"]}\n" {
311+
t.Fatalf("skill.json = %q", manifest)
312+
}
313+
}
314+
315+
func TestRustScaffoldRunsPrimaryBinaryWhenFixtureAddsAnotherBinary(t *testing.T) {
316+
if _, err := exec.LookPath("cargo"); err != nil {
317+
t.Skip("cargo is not installed")
318+
}
319+
dir := filepath.Join(t.TempDir(), "safe-change")
320+
if err := scaffoldSkill(dir, "rust", "", "Check a safe change before applying it."); err != nil {
321+
t.Fatal(err)
322+
}
323+
writeTestFile(t, filepath.Join(dir, "Cargo.toml"), "[package]\nname = \"safe-change\"\nversion = \"0.1.0\"\nedition = \"2021\"\n")
324+
writeTestFile(t, filepath.Join(dir, "src", "main.rs"), "fn main() { println!(\"workflow\"); }\n")
325+
writeTestFile(t, filepath.Join(dir, "src", "bin", "fixture-helper.rs"), "fn main() { println!(\"fixture\"); }\n")
326+
manifest, err := readSkillManifest(dir)
327+
if err != nil {
328+
t.Fatal(err)
329+
}
330+
cmd := exec.Command(manifest.Run[0], manifest.Run[1:]...)
331+
cmd.Dir = dir
332+
out, err := cmd.CombinedOutput()
333+
if err != nil {
334+
t.Fatalf("generated run command failed: %v\n%s", err, out)
335+
}
336+
if strings.TrimSpace(string(out)) != "workflow" {
337+
t.Fatalf("generated run command selected %q", out)
338+
}
339+
}
340+
247341
func TestGoScaffoldCanResolveItsPinnedModuleOnFirstRun(t *testing.T) {
248342
previousVersion := version
249343
previousTidyGoModule := tidyGoModule
@@ -285,7 +379,7 @@ func TestLocalGoAndRustScaffoldsKeepTheInvokedRuntime(t *testing.T) {
285379
}
286380
skill := readTestFile(t, filepath.Join(dir, "SKILL.md"))
287381
launcher := repositoryRuntimeLauncher(filepath.Join(".yield", "bin", filepath.Base(localRuntimePath(repo))), runtime.GOOS)
288-
workflow := shellQuote("skills/safe-change")
382+
workflow := shellQuote(filepath.Join("skills", "safe-change"))
289383
for _, command := range []string{
290384
launcher + " run " + workflow,
291385
launcher + " respond <run-id> --value <answer> --skill " + workflow,

labs/22-yield/yield/cmd/yskill/scaffold.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func scaffoldCommand(language, dir string) (launcher, workflow string) {
4545
"go": "yskill",
4646
"rust": "yskill",
4747
}[language]
48-
workflow = "."
48+
workflow = shellQuoteForPlatform(dir, runtime.GOOS)
4949
if language != "go" && language != "rust" {
5050
return launcher, workflow
5151
}
@@ -65,7 +65,14 @@ func scaffoldCommand(language, dir string) (launcher, workflow string) {
6565
if err != nil {
6666
return launcher, workflow
6767
}
68-
return repositoryRuntimeLauncher(runtimeRel, runtime.GOOS), shellQuote(filepath.ToSlash(skillRel))
68+
return repositoryRuntimeLauncher(runtimeRel, runtime.GOOS), shellQuoteForPlatform(skillRel, runtime.GOOS)
69+
}
70+
71+
func shellQuoteForPlatform(value, goos string) string {
72+
if goos == "windows" {
73+
return "'" + strings.ReplaceAll(value, "'", "''") + "'"
74+
}
75+
return shellQuote(filepath.ToSlash(value))
6976
}
7077

7178
func scaffoldSkill(dir, language, sdkPath, description string) error {
@@ -155,7 +162,7 @@ func scaffoldFiles(name, language, sdkPath string) map[string]string {
155162
".cargo/config.toml": "[registries.operatorstack]\nindex = \"sparse+https://get.operatorstack.systems/cargo/index/\"\n",
156163
"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),
157164
"src/main.rs": mainRust,
158-
"skill.json": "{\"version\":1,\"language\":\"rust\",\"run\":[\"cargo\",\"run\",\"--quiet\"]}\n",
165+
"skill.json": fmt.Sprintf("{\"version\":1,\"language\":\"rust\",\"run\":[\"cargo\",\"run\",\"--quiet\",\"--bin\",%q]}\n", name),
159166
}
160167
default:
161168
gomod := fmt.Sprintf("module %s\n\ngo 1.26.5\n\nrequire github.com/operatorstack/yield v%s\n", name, v)

labs/22-yield/yield/docs/testing-fixtures.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ first. `teardown` runs after success or failure. Every hook receives
2424

2525
Keep hooks small and repeatable. They should prepare or clean fixture state,
2626
not replace the workflow behavior being tested.
27+
28+
For Rust, a fixture helper may add another binary under `src/bin/`. New
29+
workflows name the primary workflow binary in `skill.json`, so Cargo still
30+
runs the workflow without asking you to choose a binary.

labs/22-yield/yield/evals/results/latest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"schema_version": 2,
33
"methodology_version": "1.1",
4-
"generated_at": "2026-08-02T16:31:06.974Z",
5-
"source_digest": "7e61b35562440679e70f58f61a73d06e13d6b823b72b36498a48a9156775dc20",
4+
"generated_at": "2026-08-02T19:51:34.931Z",
5+
"source_digest": "e5b21f13b239b762064fa3a4bd3d2354fba3dc46feb324f9bddbccad72827c24",
66
"status": "passed",
77
"workflow_conformance": {
88
"passed": 40,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Yield 0.1.28 final DX pass
2+
3+
- Print the created workflow path in TypeScript and Python `init` follow-up commands.
4+
- Quote generated workflow paths for the active shell.
5+
- Name the primary Rust binary in new workflow manifests so fixture helpers do not make `cargo run` ambiguous.
6+
- Keep runtime and SDK behavior unchanged.

0 commit comments

Comments
 (0)