From a51f55efdb6922b1167e5a4c96846aba7c5e6f3b Mon Sep 17 00:00:00 2001 From: stepan_romankov Date: Thu, 21 May 2026 11:08:34 +0200 Subject: [PATCH 1/2] feat(claude-setup): derive skill version from release tag; port SKILL.md lazy-load guidance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the hand-maintained skill version (inline `` marker + `const version`) with a version derived at install time from the module release via runtime/debug.ReadBuildInfo. Cutting a git tag/release is now the only version action — nothing to keep in sync by hand. Removing the inline marker from skill/SKILL.md also fixes a latent double-marker bug: main.go appended a marker to content that already ended with one, so fresh installs wrote it twice. Port three generic SKILL.md improvements that had diverged in a downstream copy: a Good For/Bad For summary, a lazy-load resources heading, and per-file "read only when" guidance for api.md/pitfalls.md. Co-Authored-By: Claude Opus 4.7 --- cmd/claude-setup/main.go | 20 +++++++++++++++----- cmd/claude-setup/skill/SKILL.md | 11 ++++++----- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/cmd/claude-setup/main.go b/cmd/claude-setup/main.go index 06e9b33..9bd76fa 100644 --- a/cmd/claude-setup/main.go +++ b/cmd/claude-setup/main.go @@ -13,6 +13,7 @@ import ( "fmt" "os" "path/filepath" + "runtime/debug" "strings" ) @@ -25,8 +26,17 @@ var apiContent string //go:embed skill/pitfalls.md var pitfallsContent string -const version = "3" -const versionMarker = "") { - fmt.Println("samurai skill is already up to date (v" + version + ")") + if strings.Contains(string(existing), versionMarker+skillVersion()+" -->") { + fmt.Println("samurai skill is already up to date (" + skillVersion() + ")") return } fmt.Println("Updating samurai skill...") @@ -56,7 +66,7 @@ func main() { // Write all skill files. files := map[string]string{ - "SKILL.md": skillContent + "\n" + versionMarker + version + " -->\n", + "SKILL.md": skillContent + "\n" + versionMarker + skillVersion() + " -->\n", "api.md": apiContent, "pitfalls.md": pitfallsContent, } diff --git a/cmd/claude-setup/skill/SKILL.md b/cmd/claude-setup/skill/SKILL.md index 3d8d4db..f3c6143 100644 --- a/cmd/claude-setup/skill/SKILL.md +++ b/cmd/claude-setup/skill/SKILL.md @@ -5,6 +5,9 @@ description: "Samurai scoped testing framework for Go (github.com/zerosixty/samu # Samurai Test Writing Rules +**Good For:** Go tests with a branching state-mutating tree (≥3 leaf paths, mutually exclusive siblings) using `samurai.Run` / `samurai.RunWith`. +**Bad For:** read-only chains, sequential accumulation, same-action-different-input variants, flat independent tests (see "When to Use" below for full criteria). + ## Rules - One action + its assertions = one `Test()`. Don't split assertions into child Tests — assert the result in the same callback that produced it. Child Tests are for new actions (setup, mutations, queries), not for checking fields of a parent's result @@ -54,9 +57,7 @@ Common false positives: `_HappyPath` / `_AccessDenied` siblings (access-denied u Cost: samurai re-executes the full path per leaf — same cost as manually duplicating setup, just automated. Parallel execution offsets wall-clock time. -## Additional resources - -- For the full API example (samurai.Run) and RunWith (custom context), see [api.md](api.md) -- For validation rules (panic conditions) and wrong patterns, see [pitfalls.md](pitfalls.md) +## Additional resources (lazy-load — read only when triggered) - +- **Read [api.md](api.md) only when** you need the worked-out `samurai.Run` / `RunWith` API example (e.g. wiring a new test scope, writing a custom context type for `RunWith`). The "When to Use" / "Detection protocol" sections above are sufficient for review and detection work. +- **Read [pitfalls.md](pitfalls.md) only when** debugging a panic, a validation error, or a wrong pattern (e.g. asserting on a parent's result inside a child Test, declaring vars where they should be assigned). Skip otherwise — the rules above already cover the common authoring decisions. From 47d1c66d895dc6c3648c9126084024f38eb43884 Mon Sep 17 00:00:00 2001 From: stepan_romankov Date: Thu, 21 May 2026 11:12:23 +0200 Subject: [PATCH 2/2] refactor(claude-setup): extract skillMarker, build strings with fmt.Sprintf Replace string concatenation for the version marker with a small skillMarker helper and fmt.Sprintf; compute the version once per run. Co-Authored-By: Claude Opus 4.7 --- cmd/claude-setup/main.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cmd/claude-setup/main.go b/cmd/claude-setup/main.go index 9bd76fa..f774f3b 100644 --- a/cmd/claude-setup/main.go +++ b/cmd/claude-setup/main.go @@ -26,8 +26,6 @@ var apiContent string //go:embed skill/pitfalls.md var pitfallsContent string -const versionMarker = "", version) +} + func main() { dir, err := os.Getwd() if err != nil { @@ -51,10 +55,12 @@ func main() { skillDir := filepath.Join(dir, ".claude", "skills", "samurai") skillFile := filepath.Join(skillDir, "SKILL.md") + version := skillVersion() + // Check if already installed and up to date. if existing, err := os.ReadFile(skillFile); err == nil { - if strings.Contains(string(existing), versionMarker+skillVersion()+" -->") { - fmt.Println("samurai skill is already up to date (" + skillVersion() + ")") + if strings.Contains(string(existing), skillMarker(version)) { + fmt.Printf("samurai skill is already up to date (%s)\n", version) return } fmt.Println("Updating samurai skill...") @@ -66,7 +72,7 @@ func main() { // Write all skill files. files := map[string]string{ - "SKILL.md": skillContent + "\n" + versionMarker + skillVersion() + " -->\n", + "SKILL.md": fmt.Sprintf("%s\n%s\n", skillContent, skillMarker(version)), "api.md": apiContent, "pitfalls.md": pitfallsContent, }