This document provides structured information for AI assistants working with the YankRun codebase.
| Item | Value |
|---|---|
| Purpose | CLI, TUI, and local web workbench for template value replacement in files and git repositories |
| Language | Go 1.25+ |
| Repository | https://github.com/AxeForging/yankrun |
| License | MIT |
| Binary Size | ~12-13MB (includes go-git library) |
yankrun/
├── main.go # Entry point, CLI app setup
├── flags.go # CLI flag definitions
├── actions/ # Command handlers
│ ├── clone.go # `clone` command
│ ├── generate.go # `generate` command
│ ├── setup.go # `setup` command
│ ├── serve.go # `serve` command wrapper
│ └── template.go # `template` command
├── internal/
│ ├── tui/ # Terminal preview workflow
│ ├── web/ # Embedded web UI, handlers, static assets
│ └── workflow/ # Shared scan/apply/clone/generate workflow layer
├── services/ # Business logic
│ ├── cloner.go # Git clone operations
│ ├── configio.go # Config file I/O (~/.yankrun/config.yaml)
│ ├── filesystem.go # File system abstraction
│ ├── github.go # GitHub API for template discovery
│ ├── parser.go # YAML/JSON input parsing
│ ├── replacer.go # Core replacement logic + transformations
│ └── replacer_test.go # Unit tests for replacer
├── domain/ # Data models
│ ├── config.go # Config structs
│ └── replacements.go # Replacement structs
├── helpers/ # Utilities
│ ├── error.go # Error handling helpers
│ └── logger.go # Zerolog setup
├── integration/ # Integration tests
│ ├── integration_test.go
│ ├── case_transformations_test.go
│ └── template_processing_test.go
├── docs/ # Documentation
│ ├── user/README.md # User guide
│ └── AI/README.md # This file
└── doc/
└── functions.md # Transformation functions reference
User Command → main.go → actions/*.go → internal/workflow → services/*.go → File System
↓
flags.go (parse flags)
↓
services/configio.go (load defaults from ~/.yankrun/config.yaml)
↓
services/parser.go (parse input JSON/YAML)
↓
services/replacer.go (scan + replace placeholders)
| File | Purpose | Key Functions |
|---|---|---|
services/replacer.go |
Placeholder scanning, evaluated previews, and replacement | ReplaceInDir(), AnalyzeDirDetails(), EvaluatePlaceholder(), ProcessTemplateFiles() |
services/parser.go |
Parse JSON/YAML input files | Parse() |
services/cloner.go |
Git clone operations | CloneRepository(), CloneRepositoryBranch() |
services/configio.go |
Config file management | Load(), Save(), Reset() |
internal/workflow/workflow.go |
Shared workflow used by CLI/TUI/web | ScanDir(), ApplyDir(), CloneAndApply() |
internal/web/server.go |
Embedded local workbench API | Scan(), Apply(), Clone(), Generate(), SetDelimiters(), ValidateDelimiters() |
internal/tui/tui.go |
Preview-first terminal workflow | Run() |
actions/clone.go |
Clone command handler | Execute() |
actions/template.go |
Template command handler | Execute() |
serveembedsinternal/web/templatesandinternal/web/staticinto the single binary.- The web UI supports local scan/apply, direct clone, and generate from configured templates.
- Preview responses include file-level placeholder trees and evaluated transform previews.
POST /api/delimiterslets the browser change the active start/end delimiter pair at runtime (Server.SetDelimiters); it validates withValidateDelimiters(rejects empty, equal, or mutually-containing pairs — an empty pair would otherwise hang the literal scan inservices/replacer.go), updatesServer.startDelim/endDelimunderServer.mu, and returns a fresh scan. The new pair applies to Local, Clone, and Generate alike since they all read it from the sameServer.settings().- Browser IndexedDB stores saved presets locally; JSON import/export is client-side only.
tuiuses the same workflow engine for local directory scan/apply and remains preview-first.
Approach: Build-first integration testing
The tests compile the actual binary and execute it, verifying real end-to-end behavior:
func buildBinary(t *testing.T) string {
bin := filepath.Join(t.TempDir(), "yankrun-test")
if runtime.GOOS == "windows" {
bin += ".exe"
}
cmd := exec.Command("go", "build", "-o", bin, ".")
cmd.Dir = repoRoot(t)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("build failed: %v\n%s", err, string(out))
}
return bin
}| File | Tests |
|---|---|
integration/integration_test.go |
TestCloneNonInteractive, TestTemplateNonInteractive |
integration/case_transformations_test.go |
TestCaseTransformations (toUpperCase, toLowerCase, gsub) |
integration/template_processing_test.go |
TestTemplateProcessingIntegration, TestCloneWithTemplateProcessing |
services/replacer_test.go |
Unit tests for .tpl processing |
# All tests
go test ./... -v
# Specific test
go test ./integration -run TestCloneNonInteractive -v
# With coverage
go test ./... -cover -coverprofile=coverage.out
go tool cover -html=coverage.out# Build for current platform
go build -o yankrun .
# Build all platforms
make build
# Clean build artifacts
make clean
# Show version info
make versionVersion info is injected via ldflags:
go build -ldflags="-s -w -X main.Version=v1.0.0 -X main.BuildTime=$(date -u '+%Y-%m-%d_%H:%M:%S') -X main.GitCommit=$(git rev-parse --short HEAD)" -o yankrun .Variables in main.go:
var (
Version = "dev"
BuildTime = "unknown"
GitCommit = "unknown"
)| OS | Architectures |
|---|---|
| Linux | amd64, arm64, 386, arm |
| macOS | amd64, arm64 |
| Windows | amd64, arm64, 386 |
- Triggers: All pushes, all PRs
- Action:
go test ./... -v
- Triggers: Manual dispatch with
taginput - Action: Build all platforms, create GitHub Release with archives
# Trigger release
gh workflow run release.yml -f tag=v1.0.0- Define flags in
flags.go - Create action handler in
actions/newcommand.go - Register command in
main.go - Add integration tests in
integration/
- Edit
services/replacer.go- find theapplyTransformations()function - Add new case in the switch statement
- Add test in
integration/case_transformations_test.go - Document in
doc/functions.md
Example:
// In services/replacer.go
case "capitalize":
if len(value) > 0 {
value = strings.ToUpper(string(value[0])) + strings.ToLower(value[1:])
}Core logic is in services/replacer.go:
AnalyzeDir()- Scans directory for placeholdersReplaceInDir()- Performs replacementsProcessTemplateFiles()- Handles.tplfiles
# Run single test with verbose output
go test ./integration -run TestCloneNonInteractive -v
# Build with debug symbols
go build -gcflags="all=-N -l" -o yankrun .
# Check verbose command output
yankrun clone --repo <url> --outputDir /tmp/test --input values.yaml --verbose| Package | Purpose |
|---|---|
github.com/urfave/cli/v3 |
CLI framework (context-aware; migrated from v1) |
github.com/go-git/go-git/v5 |
Pure Go git implementation |
github.com/rs/zerolog |
Structured logging (stderr) |
github.com/charmbracelet/{bubbletea,bubbles,lipgloss,huh} |
TUI + interactive prompts + terminal theme |
github.com/modelcontextprotocol/go-sdk |
MCP server (yankrun mcp) |
github.com/pmezard/go-difflib |
Unified dry-run diffs |
gopkg.in/yaml.v3 |
YAML parsing |
github.com/mitchellh/go-homedir |
Home directory resolution |
Pin exact versions from go.mod; do not hardcode them here.
The machine-readable surface is a stable, versioned contract — treat it as public API.
- JSON envelope (
internal/schema): every--jsoncommand prints one{schemaVersion, command, ok, data|error}object to stdout.datawraps the sameworkflow.Summary/workflow.ApplyResultstructs the human path uses. - Exit codes (
helpers/exit.go): 0 ok · 1 internal · 2 usage · 3 validation · 4 not-found · 5 git · 130 cancelled. Constructors:UsageErr,ValidationErr,NotFoundErr,GitErr,CancelledErr; mapped once inmain'sExitErrHandler. - Value precedence (
workflow.ResolveValues): manifest defaults <--inputfile (or stdin-) <YANKRUN_VAR_*env < interactive answers. - Manifest (
domain.Manifest,services.LoadManifest/ValidateValues): optionalyankrun.yaml; drives prompts, validation, andscan --json. - MCP (
internal/mcp): thin wrappers overworkflow.Engine; outputs are the same JSON-tagged types as--json. - Non-interactive guarantee: prompts (huh forms, the TUI) are gated behind
helpers.IsInteractive()so agents/CI never hang.
// Fatal errors - log and exit
if err != nil {
helpers.LogAndExit("operation failed", err)
}
// Non-fatal logging
log.Info().Str("file", path).Msg("processing file")
log.Warn().Err(err).Msg("skipping file")
log.Debug().Int("count", n).Msg("replacements made")// domain/config.go
type Config struct {
StartDelim string `yaml:"start_delim"`
EndDelim string `yaml:"end_delim"`
FileSizeLimit string `yaml:"file_size_limit"`
Templates []TemplateRepo `yaml:"templates"`
GitHub GitHubConfig `yaml:"github"`
}
type TemplateRepo struct {
Name string `yaml:"name"`
URL string `yaml:"url"`
Description string `yaml:"description"`
DefaultBranch string `yaml:"default_branch"`
}
type GitHubConfig struct {
User string `yaml:"user"`
Orgs []string `yaml:"orgs"`
Topic string `yaml:"topic"`
Prefix string `yaml:"prefix"`
IncludePrivate bool `yaml:"include_private"`
Token string `yaml:"token"`
}- Edit
services/replacer.go - Find
applyTransformations()function - Add case in switch statement
- Add test in
integration/case_transformations_test.go - Update
doc/functions.md
- Check
services/replacer.go-ReplaceInDir()andreplaceInFile() - Run existing tests:
go test ./... -v - Add regression test if needed
- Add flag definition in
flags.go - Use flag in appropriate action handler in
actions/ - Update documentation in README and docs/user/
go get -u ./...
go mod tidy
go test ./... -v # Verify nothing broke# Check for outdated dependencies
go list -m -u all
# Run linter
golangci-lint run
# Format code
gofmt -w .
# Check for vulnerabilities
govulncheck ./...
# Generate test coverage report
go test ./... -coverprofile=coverage.out
go tool cover -func=coverage.out- Repository: https://github.com/AxeForging/yankrun
- Releases: https://github.com/AxeForging/yankrun/releases
- Issues: https://github.com/AxeForging/yankrun/issues
- Test Fixtures: https://github.com/AxeForging/template-tester