Code as a tree, not as text. fspath is an in-memory AST that you manipulate through filesystem-like path operations — no syntax, no parsing, no formatting. Write nodes, transpile to code.
Built for agents and tools that need to generate code without the brittleness of string templates. The name says it: filesystem paths become programs.
Generating code as strings is fragile. Miss a semicolon, mismatch a bracket, forget indentation — and everything breaks. fspath fixes this by representing code as a typed tree. Each path encodes both structure and type. The transpiler walks the tree and emits valid code in your target language. It's impossible to produce a syntax error in the tree format.
This is the Go community edition. An Erlang/OTP enterprise edition (fspath-beam) adds persistence, actor-model concurrency, and horizontal scale.
write_node("/src/greet.fn/params/name.param/type", "string")
write_node("/src/greet.fn/return_type", "string")
write_node("/src/greet.fn/body/0.return/expr/msg.binop", "+")
write_node("/src/greet.fn/body/0.return/expr/msg.binop/left.literal", "\"Hello, \"")
write_node("/src/greet.fn/body/0.return/expr/msg.binop/right.ident", "name")
Transpiles to:
func greet(name string) string {
return ("Hello, " + name)
}Or JavaScript, or Lua. Same tree, three languages.
- Three target languages: JavaScript, Lua, Go — same tree, different output
- Go is the strongest target: structs, methods, interfaces, goroutines, channels, select, for-range, defer, type assertions, composite literals, typedefs
- JavaScript is solid: functions, closures, if/else chains, loops, ternary, arrow functions, template literals, ES modules
- MCP server: agents can call
write_node,read_node,list_children,transpile,delete_node,save_tree,load_treedirectly - HTTP API: same operations over REST, for non-MCP clients
- Insertion-order tree: parameters and struct fields preserve the order you wrote them
- Persistence: save/load trees to/from JSON files
- Zero dependencies: pure Go stdlib, single binary
| Feature | JavaScript | Lua | Go |
|---|---|---|---|
| Functions, params, returns | ✅ | ✅ | ✅ |
| Variables, assignments | ✅ | ✅ | ✅ |
| If/else/else-if | ✅ | ✅ | ✅ |
| While loops | ✅ | ✅ | ✅ (emits for) |
| For loops | ✅ | ✅ | ✅ |
| Binary/unary operators | ✅ | ✅ | ✅ |
| Function calls | ✅ | ✅ | ✅ |
| Anonymous functions | ✅ | ✅ | ✅ |
| Structs, methods | — | — | ✅ |
| Interfaces | — | — | ✅ |
| Goroutines, channels | ✅¹ | ✅¹ | ✅ |
| Select statements | — | — | ✅ |
| For-range | ✅ | ✅ | ✅ |
| Defer | ✅² | —³ | ✅ |
| Type definitions | — | — | ✅ |
| Composite literals | ✅ | ✅ | ✅ |
| Imports | — | — | ✅ |
| module.exports | ✅ | — | — |
¹ JS uses async IIFE, Lua uses coroutine.wrap — idiomatic, not semantically equivalent to goroutines.
² JS uses try/finally pattern — runs immediately, not at function exit like Go defer.
³ Lua has no try/finally equivalent — deferred expressions emitted as comments.
go build -o fspath ../fspath -port 8080Endpoints:
GET /read?path=— Read node valuePOST /write?path=— Write node value (body = value)GET /list?path=— List childrenDELETE /delete?path=— Delete node and descendantsPOST /move?source=&dest=— Move subtreePOST /copy?source=&dest=— Copy subtreeGET /transpile?lang=go— Transpile to JS (default), Lua, or GoGET /tree— Dump entire tree as JSONPOST /save?path=— Save tree to JSON filePOST /load?path=— Load tree from JSON file
./fspath -mcpMCP tools: read_node, write_node, list_children, delete_node, move_node, copy_node, transpile, export_tree, save_tree, load_tree
go test ./... # All tests
go test -run 'TestGo' ./... # Go transpiler only
go test -run 'TestLua' ./... # Lua transpiler only
go test -run 'TestJS' ./... # JS transpiler onlytree.go (~225 lines) — In-memory tree: Read, Write, ListChildren, Delete, Move, Copy
transpiler.go (~2710 lines) — Walks tree → emits JS/Lua/Go
mcp.go (~410 lines) — MCP stdio server for agent integration
http.go (~427 lines) — HTTP REST API
export.go (~120 lines) — Export helpers
persist.go (~141 lines) — JSON save/load
validate.go (~142 lines) — Tree validation
main.go (~68 lines) — Entry point (HTTP or MCP mode)
Every node is a path. The path encodes both structure and type:
/src/functionname.fn — Function declaration
/src/StructName.struct — Struct declaration
/src/fn.fn/params/name.param/type — Typed parameter
/src/fn.fn/return_type — Return type (Go)
/src/fn.fn/body/0.return/expr/x.ident — Return statement
/src/fn.fn/body/0.decl/name.ident — Variable declaration
/src/fn.fn/body/0.for/init.decl/... — For loop init
/src/fn.fn/body/0.if/condition.expr/... — If condition
Type suffixes (.fn, .struct, .ident, .binop, etc.) tell the transpiler what kind of node it is. Structural names (left, right, body, condition, etc.) tell it where things go.
See docs/architecture/tree-format.md for the complete reference with all 46+ node types.
- docs/INDEX.md — Navigation hub
- docs/architecture/ — System design and tree format specification
- docs/design/ — Design rationale and technology decisions
- docs/reference/ — Go edition implementation details
These are real. They affect real use:
- No import inference: Go output needs
importblocks, but the transpiler won't figure out which imports you need. Add them manually via/src/imports/0. - Multi-target reassignment doesn't work:
data, err = expr(reassignment) can't be expressed. Onlydata, err := expr(declaration) works. Use.rawfor reassignment. - No error handling constructs: No try/catch for JS, no panic/recover for Go.
- Struct field order is sorted alphabetically by name: The tree preserves insertion order for most things, but struct fields render alphabetically.
- Self-hosting doesn't work yet: The transpiler can generate Go, but it can't yet generate itself with perfect fidelity. This is a long-term goal.
This is the Go community edition of fspath. It's a complete, usable transpiler designed for agents and tools that need to generate code through tree manipulation.
For persistence, actor-model concurrency, and horizontal scale, see fspath-beam (Erlang/OTP).
FSL-1.1-MIT — Functional Source License with MIT Future License.
You can use, modify, and redistribute this software for any purpose except building a competing product or service. Two years after each release, the code for that release converts to MIT license.
See LICENSE for full terms.