with one click
gentleman-trainer
// Vim Trainer RPG system patterns for Gentleman.Dots. Trigger: When editing files in installer/internal/tui/trainer/, adding exercises, modules, or game mechanics.
// Vim Trainer RPG system patterns for Gentleman.Dots. Trigger: When editing files in installer/internal/tui/trainer/, adding exercises, modules, or game mechanics.
Bubbletea TUI patterns for Gentleman.Dots installer. Trigger: When editing Go files in installer/internal/tui/, working on TUI screens, or adding new UI features.
Docker-based E2E testing patterns for Gentleman.Dots installer. Trigger: When editing files in installer/e2e/, writing E2E tests, or adding platform support.
Installation step patterns for Gentleman.Dots TUI installer. Trigger: When editing installer.go, adding installation steps, or modifying the installation flow.
System detection and command execution patterns for Gentleman.Dots. Trigger: When editing files in installer/internal/system/, adding OS support, or modifying command execution.
Go testing patterns for Gentleman.Dots, including Bubbletea TUI testing. Trigger: When writing Go tests, using teatest, or adding test coverage.
| name | gentleman-trainer |
| description | Vim Trainer RPG system patterns for Gentleman.Dots. Trigger: When editing files in installer/internal/tui/trainer/, adding exercises, modules, or game mechanics. |
| license | Apache-2.0 |
| metadata | {"author":"gentleman-programming","version":"1.0"} |
Use this skill when:
All modules MUST be defined as ModuleID constants in types.go:
type ModuleID string
const (
ModuleHorizontal ModuleID = "horizontal"
ModuleVertical ModuleID = "vertical"
ModuleTextObjects ModuleID = "textobjects"
ModuleChangeRepeat ModuleID = "cgn"
ModuleSubstitution ModuleID = "substitution"
ModuleRegex ModuleID = "regex"
ModuleMacros ModuleID = "macros"
// Add new modules here
)
Every exercise follows this structure:
type Exercise struct {
ID string // "horizontal_001"
Module ModuleID // Parent module
Level int // 1-10 difficulty
Type ExerciseType // lesson, practice, boss
Code []string // Lines of code shown
CursorPos Position // Initial cursor
CursorTarget *Position // Target position (movement exercises)
Mission string // What user must do
Solutions []string // ALL valid solutions
Optimal string // Best/shortest solution
Hint string // Help text
Explanation string // Post-answer teaching
TimeoutSecs int // Before showing solution
Points int // Base score
}
Modules unlock sequentially - user must defeat boss to unlock next:
var moduleUnlockOrder = []ModuleID{
ModuleHorizontal, // Always unlocked
ModuleVertical, // After horizontal boss
ModuleTextObjects, // After vertical boss
ModuleChangeRepeat, // After textobjects boss
// ... etc
}
Lessons (sequential) ā Practice (80% accuracy) ā Boss Fight ā Next Module
Adding new module?
āāā Add ModuleID constant in types.go
āāā Add to moduleUnlockOrder slice
āāā Add ModuleInfo in GetAllModules()
āāā Create exercises_{module}.go file
āāā Implement GetLessons(moduleID)
āāā Implement GetBoss(moduleID)
āāā Add practice exercises
Adding exercises?
āāā Create Exercise with unique ID format: "{module}_{number}"
āāā Provide multiple Solutions (all valid answers)
āāā Set Optimal to shortest/best solution
āāā Include Hint for learning
āāā Add Explanation for post-answer
Adding boss fight?
āāā Create BossExercise in exercises_{module}.go
āāā Add 5-7 BossSteps (exercise chain)
āāā Set Lives (usually 3)
āāā Include variety of module skills
āāā Return from GetBoss(moduleID)
// exercises_newmodule.go
package trainer
// NewModule lessons
func getNewModuleLessons() []Exercise {
return []Exercise{
{
ID: "newmodule_001",
Module: ModuleNewModule,
Level: 1,
Type: ExerciseLesson,
Code: []string{"function example() {", " return true;", "}"},
CursorPos: Position{Line: 0, Col: 0},
Mission: "Use 'xx' to delete two characters",
Solutions: []string{"xx", "2x", "dl dl"},
Optimal: "xx",
Hint: "x deletes character under cursor",
Explanation: "x is Vim's character delete. 2x or xx deletes two.",
Points: 10,
},
// ... more exercises
}
}
func GetAllModules() []ModuleInfo {
return []ModuleInfo{
// ... existing modules
{
ID: ModuleNewModule,
Name: "New Module",
Icon: "š",
Description: "Commands: xx, yy, zz",
BossName: "The New Boss",
},
}
}
func getNewModuleBoss() *BossExercise {
return &BossExercise{
ID: "newmodule_boss",
Module: ModuleNewModule,
Name: "The New Boss",
Lives: 3,
Steps: []BossStep{
{
Exercise: Exercise{
ID: "newmodule_boss_1",
Module: ModuleNewModule,
Code: []string{"challenge code here"},
CursorPos: Position{Line: 0, Col: 0},
Mission: "First boss challenge",
Solutions: []string{"w", "W"},
Optimal: "w",
},
TimeLimit: 10,
},
// ... more steps (5-7 total)
},
}
}
// Validation checks if answer is in Solutions
func ValidateAnswer(exercise *Exercise, answer string) bool {
answer = strings.TrimSpace(answer)
for _, solution := range exercise.Solutions {
if answer == solution {
return true
}
}
// Also check via simulator for creative solutions
return validateViaSimulator(exercise, answer)
}
// GOOD: Accept all valid variations
Solutions: []string{"w", "W", "e", "E", "f "},
// BAD: Only accept one way
Solutions: []string{"w"},
{module}_{number} ā "horizontal_001"
{module}_boss_{step} ā "horizontal_boss_1"
cd installer && go test ./internal/tui/trainer/... # Run all trainer tests
cd installer && go test -run TestExercise # Test exercises
cd installer && go test -run TestSimulator # Test Vim simulator
cd installer && go test -run TestProgression # Test unlock system
installer/internal/tui/trainer/types.go for data structuresinstaller/internal/tui/trainer/exercises_*.go for patternsinstaller/internal/tui/trainer/simulator.go for Vim emulationinstaller/internal/tui/trainer/validation.go for answer checkinginstaller/internal/tui/trainer/stats.go for persistence