RepoPilotOpen in app →

robertkrimen/otto

A JavaScript interpreter in Go (golang)

Healthy

Healthy across all four use cases

weakest axis
Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 11mo ago
  • 28+ active contributors
  • MIT licensed
Show all 7 evidence items →
  • CI configured
  • Tests present
  • Slowing — last commit 11mo ago
  • Concentrated ownership — top contributor handles 50% of recent commits

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/robertkrimen/otto)](https://repopilot.app/r/robertkrimen/otto)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/robertkrimen/otto on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: robertkrimen/otto

Generated by RepoPilot · 2026-05-09 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/robertkrimen/otto shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

GO — Healthy across all four use cases

  • Last commit 11mo ago
  • 28+ active contributors
  • MIT licensed
  • CI configured
  • Tests present
  • ⚠ Slowing — last commit 11mo ago
  • ⚠ Concentrated ownership — top contributor handles 50% of recent commits

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live robertkrimen/otto repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/robertkrimen/otto.

What it runs against: a local clone of robertkrimen/otto — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in robertkrimen/otto | Confirms the artifact applies here, not a fork | | 2 | License is still MIT | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 358 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>robertkrimen/otto</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of robertkrimen/otto. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/robertkrimen/otto.git
#   cd otto
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of robertkrimen/otto and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "robertkrimen/otto(\\.git)?\\b" \\
  && ok "origin remote is robertkrimen/otto" \\
  || miss "origin remote is not robertkrimen/otto (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
  && ok "license is MIT" \\
  || miss "license drift — was MIT at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "otto.go" \\
  && ok "otto.go" \\
  || miss "missing critical file: otto.go"
test -f "runtime.go" \\
  && ok "runtime.go" \\
  || miss "missing critical file: runtime.go"
test -f "parser/parser.go" \\
  && ok "parser/parser.go" \\
  || miss "missing critical file: parser/parser.go"
test -f "cmpl_evaluate.go" \\
  && ok "cmpl_evaluate.go" \\
  || miss "missing critical file: cmpl_evaluate.go"
test -f "object.go" \\
  && ok "object.go" \\
  || miss "missing critical file: object.go"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 358 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~328d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/robertkrimen/otto"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

Otto is a JavaScript interpreter written natively in Go that parses and executes JavaScript code within Go programs. It provides a complete ES5-compatible JavaScript runtime without requiring Node.js or V8, allowing Go applications to embed a lightweight JavaScript VM with bidirectional value exchange between Go and JavaScript contexts. Single-package monolith structure: core interpreter logic in root (vm.go, cmpl.go, evaluate.go), builtin functions split by type (builtin_array.go, builtin_string.go, builtin_date.go, etc.), AST representation in ast/ subdirectory (ast/node.go, ast/walk.go), parser in parser/ package, and a CLI tool in otto/ subdirectory for interactive REPL/script execution.

👥Who it's for

Go developers building applications that need to execute untrusted or dynamic JavaScript code (e.g., configuration engines, plugin systems, serverless functions), and teams wanting to avoid the complexity and overhead of embedding Node.js or V8 in Go binaries.

🌱Maturity & risk

Production-ready and stable. The repo shows comprehensive test coverage (array_test.go, builtin_test.go, call_test.go, etc.), active CI/CD pipelines (.github/workflows/test-lint.yml, release-build.yml), and dependencies on established libraries (testify, golang.org/x/text, readline). However, commit recency suggests it's mature and feature-complete rather than rapidly evolving.

Low risk for core stability but with caveats: single maintainer (robertkrimen) creates succession risk; minimal dependencies (6 total, mostly stable) reduces supply-chain exposure. ES5-only compliance means modern JavaScript (ES6+, async/await, Promises, classes) won't execute natively. Last major updates appear older, so cutting-edge JS features won't land without community forks.

Active areas of work

Repository appears in maintenance mode rather than active development. Test coverage is comprehensive, CI/CD is properly configured, and no critical gaps are visible. Most activity is likely around bug fixes and compatibility improvements rather than new feature development.

🚀Get running

Clone and verify with: git clone https://github.com/robertkrimen/otto.git && cd otto && go test ./... && go install ./otto@latest. Then run otto to start the interactive interpreter or otto script.js to execute files.

Daily commands: Interactive REPL: go run ./otto. Execute file: go run ./otto script.js. Run test suite: go test ./... -v. No build server or external services required.

🗺️Map of the codebase

  • otto.go — Main entry point and VM struct—every integration starts here with otto.New() and Run()
  • runtime.go — Core runtime execution engine that interprets AST nodes and manages call stack
  • parser/parser.go — JavaScript parser that transforms source code into AST—foundation for all script execution
  • cmpl_evaluate.go — Statement and expression evaluator that bridges parsed AST to runtime execution
  • object.go — Object model implementation—every JavaScript value and property access flows through this
  • builtin.go — Global built-in functions and objects (Array, Object, String, etc.) initialization

🛠️How to make changes

Add a New Built-in Object Method

  1. Identify the target built-in class file (e.g., builtin_array.go for Array methods) (builtin_array.go)
  2. Register the method in the init function that sets up the prototype, following the pattern of existing methods like 'push' or 'map' (builtin_array.go)
  3. Implement the method as a native function that receives (call otto.FunctionCall) and returns (otto.Value, error) (builtin_array.go)
  4. Add test cases in the corresponding test file to verify behavior with edge cases (array_test.go)

Add a New Global Function

  1. Add the function definition to global.go in the appropriate init function section (global.go)
  2. Implement the Go function to handle JavaScript arguments via otto.FunctionCall.Argument(index) (global.go)
  3. Register it with otto.Set() on the global object in the init flow (global.go)
  4. Write test coverage in global_test.go (global_test.go)

Support a New Statement Type

  1. Define the AST node type in ast/node.go by adding a struct that embeds ast.Node (ast/node.go)
  2. Add parsing logic in parser/statement.go to recognize and construct your AST node (parser/statement.go)
  3. Implement evaluation in cmpl_evaluate_statement.go by adding a case for your node type (cmpl_evaluate_statement.go)
  4. Add integration tests in parser_test.go or a dedicated statement test file (parser_test.go)

Add Custom Go Type Interop

  1. Define how your Go type converts to/from JavaScript in otto_.go by implementing reflect-based conversion (otto_.go)
  2. Use vm.Set(name, goValue) in your application code to expose the value to scripts (otto.go)
  3. Test round-trip conversions via vm.Get() to verify data integrity (reflect_test.go)

🔧Why these technologies

  • Go (golang) — Enables single-binary distribution, cross-platform compatibility, and high performance for interpreting JavaScript without external dependencies
  • Recursive descent parser — Straightforward to implement, maintain, and extend for JavaScript grammar; provides good error messages and AST structure
  • Tree-walking interpreter — Simple to understand and debug; sufficient for most use cases; avoids complexity of bytecode compilation while keeping code maintainable
  • Go reflection + type conversion — Allows seamless interoperability between Go types and JavaScript values without manual marshaling boilerplate

⚖️Trade-offs already made

  • Tree-walking interpretation instead of bytecode VM

    • Why: Simpler implementation and easier to debug; suitable for embedded scripting
    • Consequence: Slower execution than V8 or JIT-compiled engines; acceptable for configuration scripts and light automation
  • Single-threaded execution model

    • Why: Avoids complex synchronization and keeps the API simple
    • Consequence: No native async/await or Promise support; blocking operations required; suitable for synchronous use cases only
  • Full ES5 support without full ES6+

    • Why: Reduces implementation scope while covering most practical use cases
    • Consequence: Scripts using classes, arrow functions, or destructuring will not work; users must write ES5-compatible code
  • Scope chain via stash structure instead of environment frames

    • Why: Optimizes property lookup and reduces memory overhead for deeply nested scopes
    • Consequence: More complex scope management logic; potential for subtle binding bugs if not careful

🚫Non-goals (don't propose these)

  • Does not implement full ES6+ features (classes, async/await, Promises, arrow functions, destructuring)
  • Does not provide JIT compilation or bytecode caching for performance optimization
  • Not suitable for production execution of unt

🪤Traps & gotchas

No required env vars. Key gotchas: (1) ES5-only—async/await, Promises, arrow functions, destructuring, classes will throw syntax errors; (2) otto.Value must be explicitly converted via ToInteger()/ToString() etc., implicit coercion differs subtly from browser JS; (3) Set() with Go functions requires otto.FunctionCall receiver pattern, not bare func signatures; (4) Exceptions thrown in Go callbacks don't automatically propagate as JS errors—must be caught and wrapped manually.

🏗️Architecture

💡Concepts to learn

  • Tree-Walking Interpreter — Otto evaluates JavaScript by recursively walking the AST (cmpl_evaluate*.go) instead of compiling to bytecode; understanding this pattern is essential to following control flow and adding language features
  • Lexical Scoping & Closure — Otto implements JavaScript's function-level closures via environment chains (environment.go); critical for understanding how variable lookup and nested functions work
  • Prototype Chain — Otto models JavaScript's prototype-based inheritance; builtin objects (builtin_array.go, builtin_object.go) set up prototype chains for method lookup, essential for understanding object semantics
  • Value Coercion & Type Conversion — Otto's value.go implements JavaScript's implicit type coercion rules (ToNumber, ToString, ToBoolean); misunderstanding these leads to subtle bugs in builtin implementations
  • Abstract Syntax Tree (AST) — The parser (cmpl_parse.go) produces an AST (ast/node.go) which is then evaluated; understanding AST structure is required to modify parser or add language features
  • Recursive Descent Parsing — Otto uses recursive descent (cmpl_parse.go) to build the AST from tokens; the pattern of mutually-recursive parsing functions defines JavaScript grammar rules
  • Go-JavaScript Interoperability (FFI) — Otto's otto.Value type and FunctionCall interface (function.go) enable bidirectional calls between Go and JavaScript; understanding this boundary is essential for embedding in Go applications
  • goja/goja — Alternative ECMAScript 5.1+ interpreter in Go with better ES6+ support; direct competitor with similar use cases
  • dop251/goja — Fork/variant of goja focusing on compliance and performance; another production-grade JS-in-Go option
  • v8/v8 — The reference V8 JavaScript engine that otto's design is implicitly compared against; otto is intentionally simpler and pure-Go
  • golang/go — Core Go runtime and standard library that otto depends on; understanding Go concurrency and error handling is prerequisite knowledge

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add comprehensive test coverage for builtin_regexp.go with missing edge cases

The repository has builtin_regexp.go but no corresponding builtin_regexp_test.go file. Given that regexp is a complex JavaScript feature with many edge cases (flags, lookarounds, special characters, performance), this is a high-value gap. Similar test files exist for other builtins (builtin_string.go → builtin_test.go handles some cases, but regexp deserves dedicated tests). This would improve reliability for a commonly-used JavaScript feature.

  • [ ] Create builtin_regexp_test.go following the pattern of builtin_string_test.go and builtin_array_test.go
  • [ ] Add tests for RegExp constructor, exec(), test(), match(), replace() with various flags (g, i, m, u, y)
  • [ ] Add edge case tests: empty patterns, special characters, backreferences, lookaheads/lookbehinds, and performance with large inputs
  • [ ] Run test suite to ensure all new tests pass

Add GitHub Actions workflow for cross-platform Go version testing

The repo specifies 'go 1.22' in go.mod but .github/workflows only has test-lint.yml and release-build.yml. There's no matrix testing across Go versions (1.20, 1.21, 1.22, latest) or operating systems (Linux, Windows, macOS). Given this is a widely-used interpreter library, cross-platform compatibility testing is critical for catching version-specific regressions early.

  • [ ] Create .github/workflows/test-matrix.yml with Go version matrix (1.20, 1.21, 1.22) and OS matrix (ubuntu-latest, windows-latest, macos-latest)
  • [ ] Configure the workflow to run 'go test ./...' and 'go vet ./...' on each combination
  • [ ] Add lint checks using golangci-lint (already referenced in .golangci.yml) as part of the matrix
  • [ ] Ensure the workflow runs on push to main/master and on pull requests

Add integration tests for otto CLI (otto/main.go) with REPL functionality

The repository has an otto CLI tool (otto/main.go) but no dedicated integration tests for it. The .gitignore and file structure suggest this is a user-facing tool, yet there are no tests validating REPL behavior, script execution, or error handling. This is especially important given the dependency on gopkg.in/readline.v1 for REPL support. Adding tests would catch regressions in the CLI user experience.

  • [ ] Create otto_cli_test.go in the root directory to test CLI invocation patterns
  • [ ] Add tests for: executing JavaScript files, REPL mode with various inputs, error messages, and exit codes
  • [ ] Test integration with the readline library by mocking stdin/stdout for REPL scenarios
  • [ ] Validate that vm.Run() output from otto/main.go matches expected console.log() behavior from otto_test.go

🌿Good first issues

  • Add test coverage for edge cases in cmpl_evaluate_expression.go (binary operators, unary operators) — many operator combinations lack explicit test cases in the *_test.go files
  • Document the AST walking pattern (ast/walk.go) with a tutorial in DESIGN.markdown showing how to implement a custom AST visitor (e.g., code transformer or linter) — currently walk.go has minimal docstrings
  • Implement missing Error constructor builtin methods (Error, TypeError, ReferenceError prototypes) — builtin_error.go is minimal and doesn't support instanceof checks or proper prototype chains like real JavaScript

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 3ca7298 — docs: improve grammar, clarity, and code examples (#548) (dev-kas)
  • 085142e — feat: object assign (#547) (harrisonho99)
  • 43d0d58 — docs: clean up readme (#539) (stevenh)
  • c9ce175 — ci: update goreleaser args (#538) (stevenh)
  • c136deb — fix: string slice and replace (#535) (stevenh)
  • aefc75a — chore: update go and tools (#537) (stevenh)
  • a81d9a5 — chore: update underscore to 1.13.7 (#536) (stevenh)
  • a39e65d — Fix missing flag from regex literal in corner case (#529) (psve)
  • d4edd51 — fix(parser/lexer): ID_Start & ID_Continue checks (#524) (filips)
  • 2d23528 — feat: support trailing comma after the last argument (#520) (linuxerwang)

🔒Security observations

The otto JavaScript interpreter codebase demonstrates a foundational security posture with several areas for improvement. The primary concerns are: (1) outdated unmaintained dependencies (readline from 2016), (2) lack of security documentation around safe usage patterns for code execution, and (3) missing vulnerability disclosure process. The codebase itself appears well-structured without obvious hardcoded secrets or major architectural flaws. However, as a code execution engine, proper documentation of limitations and secure usage patterns is critical. The project should prioritize updating dependencies, establishing a security policy, and providing clear security guidance to users executing JavaScript through this interpreter.

  • Medium · Outdated Dependency: golang.org/x/text — go.mod - golang.org/x/text v0.4.0. The golang.org/x/text dependency is pinned to version v0.4.0, which may contain known security vulnerabilities. This package handles text processing and encoding, which could be relevant for security-sensitive operations. Fix: Update golang.org/x/text to the latest stable version (currently v0.14.0+). Run 'go get -u golang.org/x/text' to upgrade and test thoroughly.
  • Medium · Outdated Dependency: gopkg.in/readline.v1 — go.mod - gopkg.in/readline.v1 v1.0.0-20160726135117. The readline dependency is pinned to a very old version (v1.0.0-20160726135117) from 2016. This is unmaintained and may contain security vulnerabilities, especially relevant for the REPL interactive shell component. Fix: Consider migrating to a maintained alternative like 'github.com/chzyer/readline' or similar maintained input library. If continuation is necessary, audit the code for known vulnerabilities.
  • Low · No Input Validation Documentation for JavaScript Execution — README.md - Basic Usage section. The otto interpreter executes arbitrary JavaScript code. While this is by design, there is no visible documentation or guardrails in the README examples regarding safe usage patterns when executing untrusted input. The README example shows direct code execution without discussing sandbox limitations. Fix: Add security documentation explaining: (1) that otto is NOT a sandbox, (2) untrusted code execution risks, (3) recommended usage patterns for safely executing user-provided scripts, (4) resource limits and timeout recommendations.
  • Low · Potential Code Injection via vm.Run() — otto.go - vm.Run() method usage patterns. The vm.Run() method accepts raw code strings. If user input is directly concatenated into code strings without proper escaping or validation, this could lead to JavaScript code injection attacks. No visible sanitization in the README examples. Fix: Document secure usage patterns. Recommend using vm.Set() to pass data to scripts rather than string concatenation. Provide examples of parameterized script execution. Consider adding security warnings in package documentation.
  • Low · Missing CODEOWNERS and Security Policy — Repository root - missing SECURITY.md. No visible SECURITY.md file or security policy is referenced in the repository structure. No CODEOWNERS file for managing security reviews. This makes it difficult for security researchers to report vulnerabilities responsibly. Fix: Create a SECURITY.md file documenting vulnerability disclosure process. Add CODEOWNERS file to ensure security-relevant changes are reviewed appropriately. Consider enabling GitHub security features like Dependabot.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · robertkrimen/otto — RepoPilot