CoffeeScript implementation of A* algorithm
I am not an expert on the A* algorithm. I'm suggesting changes based on the
Rules of Clarity, Simplicity, and Economy and the code you provided,
not on an extensive knowlege of graph traversal methods.

Extensive documentation inline is tricky to read. Consider using a standardized
docstring format like those in docco or codo for detailed
inline docs. 



###
  returns the goal node or null if path not found. Reconstruct path by
  following the goalNode.src until null

  @param [Object] start The node to start searching from
  @param [Object] options The options object

  @option [Function] neighbors Takes node, returns array of neighbors
  @option [Function] heuristic Takes node, returns A* heuristic
  @option [Function] isGoal Takes node, returns true if node is goal
  @option [Function] distance Takes two nodes returns distance between them

  @returns {Object} Returns the goal node with a tracable path to the start
###


It's not clear which options you expect you be left as their default values
most frequently. If most options will be left as defaults most of the time,
and the function will "just work" without configuration, use an options
object and set sensible defaults.

aStar = (start, opts = {}) -&gt;
  opts.neighbors or= (node) -&gt; []
  opts.heuristic or= (node) -&gt; 0
  opts.isGoal    or= (node) -&gt; opts.heuristic(node) is 0
  opts.distance  or= (a, b) -&gt; 1


If you want to find paths from many start points, you might
setup your function once and allow several calls, each providing a start point.

AStar = (opts = {}) -&gt;
  opts.distance  or= (a, b) -&gt; 1
  # ... and other defaults

  (start) -&gt;
    # logic for calculating path from "start"

# use it like this
astar = AStar
  isGoal: -&gt; yes
  neighbors: -&gt; [1, 2, 3]

# goal is the destination node (as it was in the original logic)
goal = astar {x: 1, y: 2}


Inside the logic function, remember that "Clarity is better than cleverness".
Slightly longer, but much more descriptive names go a long way to making the
operations easier to understand.

(start) -&gt;
  closed = {} # nodes already evaluated
  nodes  = {} # key: node to be evaluated, value: f-score (F = G + H)
  gscore = [] # the exact cost to reach this node from start


Breaking the operations in to related sections makes it easier to follow.

  # constructing path
  addNode = (node, src) -&gt;
    node.src = src
    gscore[node] = if src? then gscore[src] + opts.distance src, node else 0
    nodes[node] = gscore[node] + opts.heuristic(node)


Naming functions with verbs and naming functions that return booleans with is
and has usually increases clarity. Verbs do things, so do functions. You
want the behavior of your code to be unsurprising to the person reading it.

  # selecting best path
  isBetter = (a, b) -&gt;
    a is null or b &lt; nodes[a]

  selectBest = (best = null) -&gt;
    best = node for node, fscore of nodes when isBetter(best, fscore)
    closed[best] = yes if best?
    delete nodes[best]
    best


Break complex boolean questions into parts so the reader (which is also you,
later) can follow (with a minimal cognitive load) what you intend to be
determining with your comparisons.

  # answers about current node
  isOpen = (node) -&gt;
    not closed[node]

  isShorter = (a, b) -&gt;
    nodes[a] is null or gscore[b] + opts.distance(a, b) &lt;= gscore[a]

  isAvailable = (neighbor, node) -&gt;
    isOpen(neighbor) and isShorter(neighbor, node)


The big loop that does all the work would be clearer if you could quickly see
what situation would trigger the loop ending. while true is less communicative
than doneSearching.

  findGoal = (start) -&gt;
    # insert start point
    addNode start, null

    # store the node in use
    node = null

    doneSearching = -&gt;
      node = selectBest()
      return yes if node is null or opts.isGoal node
      no

    # find the shortest path in the current open set
    # stop looking when out of options or at the goal
    until doneSearching()
      for neighbor in getNeighbors node when isAvailable neighbor, node
        addNode neighbor, node
    node


Everything up to here defines the operations that will be used. This is the
single point that triggers action in this function. Reducing the number of
moving parts makes tracing the logic path easier.

  findGoal(start)