From f09f57cc692f80b1e1878de4f4b0c3f958c56d72 Mon Sep 17 00:00:00 2001 From: bigboateng Date: Sun, 9 Aug 2026 10:09:46 +0100 Subject: [PATCH 1/3] Harden Rust scaffold hygiene --- cmd/yskill/bootstrap.go | 24 +++++++++++ cmd/yskill/bootstrap_templates.go | 1 + cmd/yskill/bootstrap_test.go | 43 +++++++++++++++++++ cmd/yskill/main_test.go | 70 +++++++++++++++++++++++++++++-- cmd/yskill/scaffold.go | 4 ++ 5 files changed, 138 insertions(+), 4 deletions(-) diff --git a/cmd/yskill/bootstrap.go b/cmd/yskill/bootstrap.go index e2365a0..1da0297 100644 --- a/cmd/yskill/bootstrap.go +++ b/cmd/yskill/bootstrap.go @@ -311,6 +311,12 @@ func applyBootstrapPlan(plan bootstrapPlan) error { } sort.Strings(keys) for _, rel := range keys { + if rel == ".gitignore" { + if err := writeBootstrapFileIfAbsent(filepath.Join(plan.SkillDir, filepath.FromSlash(rel)), plan.Files[rel]); err != nil { + return err + } + continue + } if err := writeBootstrapFile(filepath.Join(plan.SkillDir, filepath.FromSlash(rel)), plan.Files[rel]); err != nil { return err } @@ -359,6 +365,24 @@ func writeBootstrapFile(path, content string) error { return os.Rename(temporaryPath, path) } +func writeBootstrapFileIfAbsent(path, content string) error { + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return err + } + file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644) + if errors.Is(err, fs.ErrExist) { + return nil + } + if err != nil { + return err + } + if _, err := file.WriteString(content); err != nil { + file.Close() + return err + } + return file.Close() +} + func readBootstrapProfile(repoRoot string) (bootstrapProfile, error) { path := filepath.Join(repoRoot, ".yield", "bootstrap.json") b, err := os.ReadFile(path) diff --git a/cmd/yskill/bootstrap_templates.go b/cmd/yskill/bootstrap_templates.go index 7428cd0..9e9fe7c 100644 --- a/cmd/yskill/bootstrap_templates.go +++ b/cmd/yskill/bootstrap_templates.go @@ -75,6 +75,7 @@ log. It is not a generated destination file. files["skill.json"] = fmt.Sprintf("{\"version\":1,\"yield_version\":%q,\"language\":\"go\",\"run\":[\"go\",\"run\",\"-mod=readonly\",\".\"]}\n", version) dependency = "go mod tidy (inside skills/yield-workflow-builder)" case "rust": + files[".gitignore"] = rustSkillGitignore files["src/main.rs"] = bootstrapRust files["Cargo.toml"] = fmt.Sprintf("[package]\nname = \"yield-workflow-builder\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nyieldskill = { version = \"=%s\" }\nserde_json = \"1\"\n", version) files["skill.json"] = fmt.Sprintf("{\"version\":1,\"yield_version\":%q,\"language\":\"rust\",\"run\":[\"cargo\",\"run\",\"--quiet\"]}\n", version) diff --git a/cmd/yskill/bootstrap_test.go b/cmd/yskill/bootstrap_test.go index a8fd35f..7b9862d 100644 --- a/cmd/yskill/bootstrap_test.go +++ b/cmd/yskill/bootstrap_test.go @@ -152,6 +152,49 @@ func TestBuilderTemplatesExposeEquivalentOperations(t *testing.T) { } } +func TestBootstrapRustTemplateAddsAndPreservesSkillGitignore(t *testing.T) { + profile := bootstrapProfile{YieldVersion: "1.2.3", Agents: []string{"codex"}} + files, _, err := renderBootstrapSkill("rust", profile) + if err != nil { + t.Fatal(err) + } + if got := files[".gitignore"]; got != rustSkillGitignore { + t.Fatalf("Rust bootstrap .gitignore = %q, want %q", got, rustSkillGitignore) + } + for _, language := range []string{"typescript", "python", "go"} { + files, _, err := renderBootstrapSkill(language, profile) + if err != nil { + t.Fatal(err) + } + if _, found := files[".gitignore"]; found { + t.Fatalf("%s bootstrap template created Rust-specific .gitignore", language) + } + } + + root := t.TempDir() + skillDir := filepath.Join(root, "skills", bootstrapSkillName) + if err := os.MkdirAll(skillDir, 0o755); err != nil { + t.Fatal(err) + } + const existing = "user-owned-rule/\n" + if err := os.WriteFile(filepath.Join(skillDir, ".gitignore"), []byte(existing), 0o644); err != nil { + t.Fatal(err) + } + plan := bootstrapPlan{ + Root: root, + Language: "python", + SkillDir: skillDir, + Profile: bootstrapProfile{Version: 1, YieldVersion: "1.2.3", Language: "python"}, + Files: map[string]string{".gitignore": rustSkillGitignore}, + } + if err := applyBootstrapPlan(plan); err != nil { + t.Fatal(err) + } + if got := readTestFile(t, filepath.Join(skillDir, ".gitignore")); got != existing { + t.Fatalf("existing bootstrap .gitignore changed: %q", got) + } +} + func TestBuilderCreateFixtureDoesNotRequireProjection(t *testing.T) { if strings.Contains(bootstrapFixtureResponses, `"project-semantics"`) { t.Fatal("create mode fixture must remain unchanged by conversion projection") diff --git a/cmd/yskill/main_test.go b/cmd/yskill/main_test.go index dcc0c74..5c9750b 100644 --- a/cmd/yskill/main_test.go +++ b/cmd/yskill/main_test.go @@ -186,11 +186,12 @@ func TestScaffoldSkillWritesLanguageSpecificEntrypoints(t *testing.T) { files []string command string pin string + ignore string }{ - {"typescript", []string{"main.ts", "package.json", "skill.json"}, "npm exec -- yskill run .", `"@operatorstack/yield": "0.1.9"`}, - {"python", []string{"main.py", "requirements.txt", "skill.json"}, "python -m yieldskill run .", "yieldskill==0.1.9"}, - {"go", []string{"main.go", "go.mod", "skill.json"}, "yskill run .", "github.com/operatorstack/yield v0.1.9"}, - {"rust", []string{"src/main.rs", "Cargo.toml", "skill.json"}, "yskill run .", `version = "=0.1.9"`}, + {"typescript", []string{"main.ts", "package.json", "skill.json"}, "npm exec -- yskill run .", `"@operatorstack/yield": "0.1.9"`, ""}, + {"python", []string{"main.py", "requirements.txt", "skill.json"}, "python -m yieldskill run .", "yieldskill==0.1.9", ""}, + {"go", []string{"main.go", "go.mod", "skill.json"}, "yskill run .", "github.com/operatorstack/yield v0.1.9", ""}, + {"rust", []string{"src/main.rs", "Cargo.toml", "skill.json", ".gitignore"}, "yskill run .", `version = "=0.1.9"`, rustSkillGitignore}, } for _, tt := range tests { t.Run(tt.language, func(t *testing.T) { @@ -246,6 +247,14 @@ func TestScaffoldSkillWritesLanguageSpecificEntrypoints(t *testing.T) { if tt.language == "rust" && strings.Contains(manifest, "registry =") { t.Fatalf("public Rust scaffold contains a private package registry:\n%s", manifest) } + ignorePath := filepath.Join(dir, ".gitignore") + if tt.ignore == "" { + if _, err := os.Stat(ignorePath); !os.IsNotExist(err) { + t.Fatalf("%s scaffold created Rust-specific .gitignore: %v", tt.language, err) + } + } else if got := readTestFile(t, ignorePath); got != tt.ignore { + t.Fatalf(".gitignore = %q, want %q", got, tt.ignore) + } }) } if tidyCalls != 1 { @@ -253,6 +262,21 @@ func TestScaffoldSkillWritesLanguageSpecificEntrypoints(t *testing.T) { } } +func TestRustScaffoldPreservesExistingGitignore(t *testing.T) { + dir := filepath.Join(t.TempDir(), "safe-change") + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatal(err) + } + const existing = "user-owned-rule/\n" + writeTestFile(t, filepath.Join(dir, ".gitignore"), existing) + if err := scaffoldSkill(dir, "rust", "", "Check a safe change before applying it."); err != nil { + t.Fatal(err) + } + if got := readTestFile(t, filepath.Join(dir, ".gitignore")); got != existing { + t.Fatalf("existing .gitignore changed: %q", got) + } +} + func TestPackageScaffoldsPrintCreatedWorkflowInNextCommands(t *testing.T) { previousVersion := version version = "0.1.28" @@ -458,6 +482,44 @@ func TestRustScaffoldPinsTheInvokedRuntimeWithoutPrivateRegistryConfig(t *testin } } +func TestCmdInitRustScaffoldIsDoctorValid(t *testing.T) { + if _, err := exec.LookPath("cargo"); err != nil { + t.Skip("cargo is unavailable") + } + previousVersion := version + previousExecutable := currentExecutable + previousInspect := inspectRuntimeVersion + version = "0.1.37" + t.Cleanup(func() { + version = previousVersion + currentExecutable = previousExecutable + inspectRuntimeVersion = previousInspect + }) + + root := t.TempDir() + writeTestFile(t, filepath.Join(root, ".git"), "gitdir: fixture\n") + writeTestFile(t, localRuntimePath(root), "packaged runtime") + currentExecutable = func() (string, error) { return localRuntimePath(root), nil } + inspectRuntimeVersion = func(path string) (string, error) { + resolved, err := filepath.EvalSymlinks(localRuntimePath(root)) + if err != nil { + t.Fatal(err) + } + if path != localRuntimePath(root) && path != resolved { + t.Fatalf("inspected unexpected runtime %s", path) + } + return "0.1.37", nil + } + + dir := filepath.Join(root, "skills", "safe-change") + if err := cmdInit([]string{"--language", "rust", "--description", "Check a safe change before applying it.", dir}); err != nil { + t.Fatal(err) + } + if err := cmdDoctor([]string{dir, "--root", root}); err != nil { + t.Fatalf("doctor rejected Rust scaffold: %v", err) + } +} + func TestPythonScaffoldUsesRelocatableInterpreter(t *testing.T) { previousVersion := version version = "0.1.9" diff --git a/cmd/yskill/scaffold.go b/cmd/yskill/scaffold.go index 3113a39..3977323 100644 --- a/cmd/yskill/scaffold.go +++ b/cmd/yskill/scaffold.go @@ -12,6 +12,9 @@ import ( ) var releaseVersionPattern = regexp.MustCompile(`^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$`) + +const rustSkillGitignore = "target/\n.yield/\n" + var tidyGoModule = func(dir string) error { cmd := exec.Command("go", "mod", "tidy") cmd.Dir = dir @@ -256,6 +259,7 @@ func scaffoldFiles(name, language, sdkPath string) map[string]string { } case "rust": return map[string]string{ + ".gitignore": rustSkillGitignore, "Cargo.toml": fmt.Sprintf("[package]\nname = %q\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nyieldskill = { version = \"=%s\" }\nserde_json = \"1\"\n", name, v), "src/main.rs": mainRust, "skill.json": fmt.Sprintf("{\"version\":1,\"yield_version\":%q,\"language\":\"rust\",\"run\":[\"cargo\",\"run\",\"--quiet\",\"--bin\",%q]}\n", v, name), From 4266077a1fa8f8846b6fa6486644ea0ca71a48c2 Mon Sep 17 00:00:00 2001 From: bigboateng Date: Sun, 9 Aug 2026 10:13:34 +0100 Subject: [PATCH 2/3] Refresh evaluation evidence --- evals/results/latest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evals/results/latest.json b/evals/results/latest.json index 61eac28..dcbc4bc 100644 --- a/evals/results/latest.json +++ b/evals/results/latest.json @@ -1,8 +1,8 @@ { "schema_version": 2, "methodology_version": "1.1", - "generated_at": "2026-08-08T21:55:26.622Z", - "source_digest": "a29a1bff252107e06d7ff43bd14d10abf0cc20a9eb57e3115c250caaf7ef1b19", + "generated_at": "2026-08-09T09:13:26.116Z", + "source_digest": "255b4fcf353708369ba9aaf49d41273c4f7114747418f72d94e8bc7a08c32cad", "status": "passed", "workflow_conformance": { "passed": 40, From fdb4bfeb70cdd0e1202f14571dd83ddd68a918f8 Mon Sep 17 00:00:00 2001 From: bigboateng Date: Sun, 9 Aug 2026 10:22:52 +0100 Subject: [PATCH 3/3] Refresh conversion evaluation evidence --- evals/results/latest-conversion.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/evals/results/latest-conversion.json b/evals/results/latest-conversion.json index d3be4b6..4213480 100644 --- a/evals/results/latest-conversion.json +++ b/evals/results/latest-conversion.json @@ -1,8 +1,8 @@ { "schema_version": 1, "methodology_version": "semantic-disposition-v1", - "generated_at": "2026-08-08T22:03:02.341Z", - "source_hash": "172b2e12624cb922d7f15f03f745d46107e42297b9a3da3c9ca9e4549f46e465", + "generated_at": "2026-08-09T09:21:44.116Z", + "source_hash": "b87e914bc9406c400e82b8dd35c8e2b35df38664cfcf375ea6eba1884fc1adb3", "fixture_source_hash": "9ab03fffe6716da8298b461f79c9eebae7c7bb01151328686ec51ed9c0b77fe5", "status": "passed", "model": { @@ -13,10 +13,10 @@ }, "sessions": 2, "token_usage": { - "input_tokens": 425017, - "cached_input_tokens": 382499, - "output_tokens": 8032, - "reasoning_output_tokens": 1802 + "input_tokens": 795134, + "cached_input_tokens": 732165, + "output_tokens": 10401, + "reasoning_output_tokens": 2874 }, "clause_counts": { "total": 4,