Skip to content

v0.171.3

Choose a tag to compare

@github-actions github-actions released this 17 Oct 23:06
· 24 commits to main since this release

Upgrading: nothing to do (INDEX_VERSION is unchanged). To pin the binary
back: npm i -g @sdsrs/code-graph@0.171.2 (or, from source, cargo install --locked --git https://github.com/sdsrss/code-graph-mcp --tag v0.171.2 --features embed-model), then delete ~/.cache/code-graph/bin/code-graph-mcp
and set CODE_GRAPH_NO_AUTO_UPDATE=1, as described under 0.171.0.

get_ast_node include_impact with a node_id counts that node

Given a node_id, the impact summary counted every definition of the node's
name in its file (cfg twins, overloaded constructors), so it disagreed with
impact --node-id. On leveldb, 12 of 1,152 functions and methods disagreed:
each of the five Slice constructors reported 520 ambiguous call edges
excluded where the CLI reported 104, and the three Status constructors
reported 196 callers where the CLI reported 198. On flask, 13 of 392 (nested
decorator and view functions). The summary now starts from that node, as
impact --node-id does, and on the freshly indexed corpora the two agree on
all 1,544. A lookup by name answers as before: a class still counts its
same-named constructors, as impact <name> does (leveldb Slice: 624 on
both).

A symbol with whitespace around it

No indexed name starts or ends with whitespace, so DBImpl::Write (with a
trailing space) matched no definition. MCP get_call_graph and
find_references answered for leveldb's struct DBImpl::Writer through their
fuzzy step; the CLI's show, callgraph, impact and refs exited 1, and
get_ast_node answered "not found". Both surfaces now drop the whitespace
around a symbol argument before looking it up; whitespace inside a name
(SkipList<Key, Comparator>::Insert) stays, and an argument of whitespace
alone is answered as before. Measured on leveldb and flask over 100 spellings
(46 of them ::, template and blank shapes), each with whitespace before it,
after it, around it or none, through CLI show, callgraph, impact,
refs, similar and MCP get_ast_node, get_call_graph,
find_references, find_similar_code: in 4,920 runs, each answer equals the
one 0.171.2's source gives with the whitespace removed, or, for whitespace
alone, as typed. Where that answer is for another name, it now also carries
the note described next.

A fuzzy answer says which symbol it answers for

When their fuzzy step answers for another name than the one asked (a typo, a
prefix), MCP get_call_graph and find_references answer for that name, as
before, and now say so in the answer: "note": "Resolved 'abot' → 'abort'."
They used to show the switch only by the name in function / symbol. CLI
refs now prints the same Resolved 'abot' → 'abort' on stderr, as
callgraph already did; its stdout is unchanged.

Measured on flask and leveldb with 329 generated names (exact names, typos,
prefixes, Class.method and Class::method for members the class does not
define, missing members; some also with a file_path or --file) through
CLI callgraph and refs and MCP get_call_graph and find_references:
0.171.2 answered for another name without saying so 94 times in MCP and 44
times in refs; this release 0 times. Every other answer of the 1,316 runs
is byte for byte 0.171.2's, and the same names with whitespace around them
(5,264 runs) get exactly the answers they get without it. Where a typo or
prefix was switched, the name answered for was the one it came from in every
case of this sample; a dotted name can be switched to an unrelated one (see
Not covered).

A vector belongs to the text it was computed from

0.171.2 listed this under Not covered. While incremental-index computed
embeddings, another index run beside it that re-indexed a file could give
that file's new nodes the ids of its old ones, and the vectors being computed
from the old text then landed on the new nodes. Nothing re-embeds a node that
has a vector, so semantic search ranked those nodes by text they no longer
had until their file changed again. A vector is now stored only if its node
still holds the text it was computed from. A node re-created meanwhile is
embedded from its new text by the same run when that text is written before
the run finishes its queue, and otherwise by the next embedding run.

Measured with 120 functions in two files, the file of 10 rewritten and
re-indexed by incremental-index --no-embed while 64 of 123 nodes were
embedded: 0.171.2 left all 10 of its nodes with the old text's vectors
(cosine 0.79–0.81 to the new text's), this release all 10 with the new
text's (1.0000). Both end at 123/123 embedded.

Not covered

  • The fuzzy step can answer for an unrelated name; the note above now says
    so, and 0.171.2 gave the same answers without it. It runs:

    • on a dotted name no definition has, over the whole string: flask's
      Flask.route (defined as Scaffold.route) answers for FlaskGroup. The
      CLI drops the qualifier instead (No definition is named 'Flask.route'; answering for 'route'). The 0.152.0 notes say the MCP tools fall back to
      the bare name as well; they do not: get_ast_node, get_call_graph and
      find_references refuse such a name or answer it from the fuzzy step;
    • on a name defined only in test files, which it skips: flask
      find_references {"symbol_name": "after"} answers for after_request;
    • in get_call_graph, on a definition with no edges in the asked
      direction: leveldb Block.size with direction: callees answers for the
      doc/index.md heading Block size, where CLI callgraph answers an
      empty list.

    The same names with whitespace around them now get these answers too.
    Measured with a trailing space, a leading space, a tab and newline, and a
    trailing NBSP, in both tools: all 8 padded spellings of Flask.route and
    Flask::route went from "not found" to FlaskGroup (16 runs). For the 23
    test-only names that get such an answer in flask and leveldb, the same
    paddings make 184 runs: 79 went from a refusal (71) or a candidate list (8)
    to another name; 40 now answer for the name itself (21 had been refused, 2
    a candidate list, 17 another name); 65 answered for another name before and
    after. Every answer for another name carries the note.

  • show --node-id N --impact still counts the callers of every definition
    of N's name in N's file, so it can differ from impact --node-id N and
    from MCP get_ast_node with that node_id: on 45 of leveldb's 1,152
    functions and methods outside test files, and 7 of flask's 392.

  • Vectors an earlier release stored on the wrong text stay until their file
    changes; incremental-index does not replace them. rebuild-index --confirm replaced them in the race above, but it reuses what the
    embedding cache holds for a node's text, and the cache can hold such a
    vector: an MCP server fills an empty cache from the vectors the nodes
    have. In the race above, incremental-index's closing line
    counts each re-created node twice (Embedded 133 nodes (123/123); 0.171.2
    printed 123).

  • A file created or edited after the MCP server starts and before its first
    tool call that indexes (get_index_status is not one) is not seen by that
    call or the ones after it, measured for 120 s: the file watcher starts at
    that call, after the change, and without an event the server rescans every
    5 minutes. module_overview on the file's path indexes it. 0.171.2
    behaves the same.