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
26 changes: 21 additions & 5 deletions cmd/claude-setup/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime/debug"
"strings"
)

Expand All @@ -25,8 +26,21 @@ var apiContent string
//go:embed skill/pitfalls.md
var pitfallsContent string

const version = "3"
const versionMarker = "<!-- samurai-skill-v"
// skillVersion returns the samurai module release this binary was built from
// (the git tag, e.g. "v0.4.0"), used to mark and detect installed skill files.
// When run from a local checkout (no module version), it falls back to "dev".
func skillVersion() string {
if info, ok := debug.ReadBuildInfo(); ok && info.Main.Version != "" && info.Main.Version != "(devel)" {
return info.Main.Version
}
return "dev"
}

// skillMarker formats the HTML comment stamped into installed SKILL.md files,
// used to detect whether an installed skill matches this binary's release.
func skillMarker(version string) string {
return fmt.Sprintf("<!-- samurai-skill-%s -->", version)
}

func main() {
dir, err := os.Getwd()
Expand All @@ -41,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+version+" -->") {
fmt.Println("samurai skill is already up to date (v" + version + ")")
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...")
Expand All @@ -56,7 +72,7 @@ func main() {

// Write all skill files.
files := map[string]string{
"SKILL.md": skillContent + "\n" + versionMarker + version + " -->\n",
"SKILL.md": fmt.Sprintf("%s\n%s\n", skillContent, skillMarker(version)),
"api.md": apiContent,
"pitfalls.md": pitfallsContent,
}
Expand Down
11 changes: 6 additions & 5 deletions cmd/claude-setup/skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

<!-- samurai-skill-v3 -->
- **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.