Skip to content

Release 0.2.3 - #2016

Merged
swapnilpaliwal-sd merged 30 commits into
mainfrom
dev
Nov 5, 2026
Merged

swapnilpaliwal-sd merged 30 commits into
mainfrom
dev

Conversation

@swapnilpaliwal-sd

Copy link
Copy Markdown
Contributor

Releasing 0.2.3

Improvements

Bugs

  • mcp: an execution failure is a tool error (isError), not an answer (#2006) #2007 mcp: a call the CLI could not carry out came back as an ordinary answer; it is now a tool error (isError: true) with the same words, so the client sees the failure and the model still reads the correction mcp
  • link: two links from one call site to two different arrow functions or lambdas collapsed into one, because every anonymous function shares a placeholder name. Links are now keyed on the target's declaration; --remove takes a file:line and refuses a name that covers more than one declaration link

Release

  • Every package and plugin manifest at 0.2.3 (the parser keeps its own version)

CI on dev passes every suite (Java, Python, TypeScript, JavaScript, C#, web).

swapnilpaliwal-sd and others added 30 commits October 10, 2026 14:36
…not the file's

`Models.Report` written inside namespace P, in a file that also declares
P.Other.Models and Z.Models, bound to all three Report types: the relative
candidate (type_qn_tail + module_ns_chain) is keyed per FILE, so every
namespace the file declares a type in qualified every name in it. C# reads the
first segment from the namespaces enclosing the site, innermost outward, and
the innermost level with a match wins.

- type_name_out_of_scope(ns, name, arity, group): per site namespace and
  dotted written name, candidates are ranked by the prefix they were found
  under; only the longest survives, and a full match survives only when no
  enclosing namespace matched. Demand-driven from names a file writes.
- type_ref_resolves, ref_names_type, expr_new_type and heritage_resolves drop
  what the site's enclosing type's namespace rules out. Prune-only: the
  file-level answer is unchanged and a site with no known enclosing type drops
  nothing.

Case a-relative-name-is-read-from-the-sites-namespace fails 5/6 on the base
(the single-namespace control passes on both) and passes 6/6.

Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…thod callees resolve (#1997, #1998, #1999, #2000, #2001)

Five callee shapes resolved to nothing though the compiler names their
target:

- new C() where C is typed by a construct-signature literal or an alias
  of one: construction read expr_type only, the plain-call side already
  read expr_shape (#1997)
- ns.f() where f is an exported const of a function type whose value is
  not a function literal: the namespace read gave var_type, never
  var_shape (#1998)
- a.b.c.m() through nested inline object types: a shape-typed member
  read off a shape qualifier had no field_shape clause (#1999)
- this.m() in an object literal typed X & ThisType<T>: the marker is
  read through intersections and aliases, with the alias parameter and
  the call's explicit type argument substituted (#2000)
- this.m.bind/call/apply and the bound result's call: the Function
  members attach to a method read as a value (#2001)

Two guards keep sites that were right from hedging: the namespace shape
is not added where the const holds a function literal, and the
construct shape is not added where the callee variable holds a class.

Cases 91 to 95 pin each shape with its controls. Goldens moved in 08,
80 and 83 (see the pull request); 83's known-missing edge now resolves.
… keeps its own version)

0.2.2 is released; this PR ships in 0.2.3. package.json (version and the five @axiomcode/engine-* pins) and the four editor plugin and extension manifests move from 0.2.2 to 0.2.3. The parser is unchanged by this PR and stays at 0.2.1.
… keeps its own version)

0.2.2 is released, so what merges into this branch ships in 0.2.3. package.json (version and the five @axiomcode/engine-* pins) and the four editor plugin and extension manifests move from 0.2.2 to 0.2.3. The parser keeps its own version, 0.2.1.
typescript: construct, namespace, inline-chain, ThisType and bound-method callees resolve (#1997, #1998, #1999, #2000, #2001)
…2006)

A call the CLI could not carry out — bash missing, the verb crashed or was killed, an exit no verb
uses, silence, a timeout with no build running, a refusal (exit 2) — came back as an ordinary result
on both server paths; only schema-invalid arguments were flagged. run() now raises the server's own
ToolError with the same words, which the SDK and the fallback both mark isError, so the model still
reads the correction. An answer, path's "no chain" (exit 1 in words) and the note that a graph is
still being built (exit 2 while the build lock is held) stay ordinary results.

AXIOMCODE_MCP_TIMEOUT (seconds) exists so a test can see the timeout. tests/mcp.py checks nine
cases per launch mode, on the fallback and, with AXIOMCODE_SDK_PYTHON, on the SDK.
mcp: an execution failure is a tool error (isError), not an answer (#2006)
Two links from one call site to two anonymous functions collapsed into one:
every arrow function and lambda shares a placeholder name (`<arrow>`,
`<lambda>`, `Owner.<lambda>`), and adding a link replaced any row at the same
site with the same target name, ignoring the file:line that tells them apart.

- add: a row is replaced only when it names the same declaration (same name
  and target_file, or resolving to the same declaration in the graph), so
  re-linking stays idempotent and a second anonymous target is a second link
- --remove <site> <target>: <target> may be a declaration's file:line; a name
  that covers links to more than one declaration is refused with each one's
  file:line instead of removing them all
- link output and --list print a placeholder target with its declaration

Cases for TypeScript arrows, Python lambdas and C# lambdas, with controls:
idempotent re-link, two named targets at one site, remove by name.

Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…as receivers

Three receiver shapes had no type, so a call on them resolved to nothing even
with the declaration staged:

- Arithmetic: `-`, `*`, `/`, `%`, `**`, bitwise/shift operators and unary
  `-`, `~`, `++`, `--` yield Number (BigInt when an operand is a bigint);
  unary `+` is always Number; `+` is Number only when both operands are.
  Typing these also fixes overload choice where the arithmetic result is an
  argument (a numeric constructor overload, a `number` function overload).
- Defaults: `a ?? b` / `a || b` (and `a && b`'s right side) carry the named
  type of the operand the compiler names, as the shape rule already did.
  An un-annotated field initialised by a generic `new` now passes its type
  arguments on, so `this.byKey.get(k) ?? []` substitutes V.
- Namespace consts: `N.v(...)` and `N.v.m(...)` where `v` is a const
  declared in a namespace (local, nested, ambient, or a global the binder
  left unbound) now take the const's declared type or shape. A const that
  holds a function literal keeps its body as the only answer.

Cases 96-98 cover each with controls: a string `+` stays String, a bigint
result never names Number, an annotated default is unchanged, an untyped
left side does not borrow the right's type, a namespace function and a
parameter shadowing the namespace name are unchanged.

Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…ral nested in a typed literal

`const poll: (ctx: Ctx) => R = async ({ data, utils }) => utils.http(..)` and
`define({ lifecycle: { start: async ({ utils }) => utils.http(..) } })` left every call
on `utils` unresolved, while `ctx.utils.http(..)` on the same arrow resolved.

Two causes:
- a bound name reads its pattern's type through param_type, which only an annotation
  fills; contextual typing (lambda_param_type) typed references to the parameter and
  never the names destructured from it. The bound name now takes the type (and shape)
  of the field it names from the contextual type, typed at its uses beside the plain
  parameter's, not through param_type, which overload rejection reads under negation.
- an object literal written as a property of a typed literal had no scope, so functions
  in a nested slot took no parameter types. It now takes the type of the field it fills.

Case 99 pins both, with controls: an `any` pattern and a parameter typed by an object
type literal stay unresolved. Every other TypeScript case is unchanged (109/109).

On a Deno connector repository (~750 endpoint definitions): known_edge rows 7,410 -> 8,923,
ambiguous_unknown 35,820 -> 34,300, multi_inferred 63 -> 77, no edge lost; 194 of 198
hook calls of the framework's HTTP helper now resolve.

Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
… its member's type

`const { port } = opts` with `opts: { port: Port }`, and `({ port }: Opts)` with
`type Opts = { port: Port }`, left `port` untyped, so `port.execute()` was
unresolved while `opts.port.execute()` beside it resolved. A type literal has no
declaration for param_type / expr_type to name: it is a shape. The destructuring
rules for parameters and variables now read the shape too (a shape is its own
scope), for fields, nested shapes and methods.

Case 100 covers inline and aliased literals, parameters and variables, a nested
shape and a shape method, with controls: an untyped source stays unresolved, and
a look-alike literal resolves to its own method, not to the interface. Case 75:
five destructured-prop calls now resolve to the targets the compiler oracle had
listed as known-missing (the shadowing guard still holds); they still show as
missing+extra only because a destructured caller is labelled once per bound name.

On a 1.9k-file Deno TypeScript repo: known_edge 7,410 -> 7,441, ambiguous_unknown
35,820 -> 35,789, 0 resolved edges lost; spot-checked against the source.

Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
The parser emits a row for a destructuring pattern and one per name it binds, all at
the pattern's position. normalize_edges.py turned each into a parameter, so
`aliasParam({ port, clock }: Opts)` was labelled `aliasParam(Opts,Opts,Opts)` while the
compiler oracle prints `aliasParam(Opts)`: the same edge showed as one missing and
one extra. One row per position, the pattern's when present.

Case 100's three known-missing edges and five of case 75's now agree with the oracle;
both debt lists are trimmed. Suite 110/110.

Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
typescript: a name destructured from a type-literal-typed value takes its member's type
…red-params

typescript: a destructured parameter typed by its context, and a literal nested in a typed literal
csharp: a relative qualified name is read from the site's namespace, not the file's (#1906)
…ange

The case landed with four known-missing edges (a destructured parameter's
per-name label and a parameter typed by an object type literal); the change
merged just before it resolves all four, so the suite failed as NOW-FIXED.

Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…s, context keys and request links

The engine now reads what a Hono app declares and what it does with a request, in both the
TypeScript and the JavaScript graphs (each graph on its own, nothing links the two).

What an agent can now ask:

- Routes with their full paths. Every get/post/on/all/route registration becomes a route with
  its verb and its composed path, through basePath, route() mounts several levels deep,
  sub-apps built by factories or registration functions, declarative route lists, and an app
  variable re-pointed at a basePath result (exact when unconditional, partial when it is not). A path
  that is not a literal or a const is reported as undetermined with a reason, never guessed.
- Middleware chains in order. Each route lists the middleware that runs before its handler,
  app-wide use("*") and path-scoped use(..) included, in registration order, with a tag when a
  registration sits inside a branch or a loop.
- Context keys. A c.set(key) in a middleware is linked to the c.get(key) and c.var.key reads in
  the handlers it runs before.
- Typed-client and test-request links. An hc client call, a fetch to a path the program spells,
  and app.request(..) in a test are linked to the route they reach. A request whose method or
  base cannot be decided says so instead of joining at random.
- Validator schemas. A zod schema handed to a validator is linked to the handler that reads it
  through c.req.valid(..), including schemas built by project helpers.
- Declared unknowns. Anything the rules cannot decide (a dynamic path piece, an unresolvable
  middleware list, a run-time base path) is a row that names the reason.

impact, context and path answer on all of these: "how does a request to POST /x run" names the
route, its handler and the middleware in front of it in order; path from a middleware to a
handler goes through the route; impact on a middleware lists every route it runs before.

The rules live in the config-resolution and framework-behavior phases of each engine, and the
framework names only in the knob files. In the TypeScript engine, every router rule that starts
from a program-wide relation runs only when the program imports a module a router knob names, so a
program without such an import pays nothing for them; the served-app fetch lookup is a keyed join. Generic changes outside them, framework-free:

- resolution: what `await import('./m')` loads, and the method a loop over a const array of
  typed elements calls (read by the path evaluation only, it draws no call edge);
- the string evaluation that names a request's path, shared by every HTTP framework, now
  follows class fields, a parameter of a function called once and a zero-argument call, stops
  trusting a variable that is reassigned, and no longer reads a fetch of a module-relative
  file URL as a request;
- a backtick string with no substitution counts as a literal path for entry points.
- the bundle indexes the new router relations the queries read (EXT_QUERIED), as it does the
  other queried ext tables.

The existing Express and Nest route suites give the same results as before.

Tests: graph/test/typescript/hono and graph/test/javascript/hono, every case checked against an
independent oracle built on the TypeScript compiler. Whole request call chains (handler into
service into repository) are not in this change.
…e after next()

A handler that reads a context key with `const { key } = c.var` is now a
reader of the middleware that sets it. A middleware's `c.set` made after
the `next()` that runs on every path is no longer linked to later
functions on the chain: it happens once they have returned. A `next()`
in a branch, loop or try (`if (skip) return next()`) does not count.

The Hono oracle had the same gaps, and also credited a context access in
a callback nested in a handler to the callback; it now credits the
function that owns `c`. New cases cover each shape in TypeScript and
JavaScript.

Fixes #2003
…napi routes built through a project wrapper, one route row per route, and partial routes with a hole in the middle of their path

Route model fixes:

- A function that returns an object holding several sub-apps: each property read of that
  object (rt.stock, or a destructured name) mounts only its own sub-app, not every app the
  function created. This also holds when the object is passed into another function and
  destructured there. It used to put every sub-app under every mount, which made false routes.
- A test helper that writes the init of app.request(path, init) in place now joins on the
  method it writes. A method that is not a literal verb joins any route on that path.
- Middleware read off an object a factory returns (guard.middleware, or a destructured
  name) is the function the factory put there.
- openapi(def, handler) follows a project wrapper that returns createRoute({...config,
  middleware}). Method and path come from the call of the wrapper, also when the wrapper
  passes them on through its own parameters, and middleware comes from the wrapper,
  including a spread of the caller's list. A definition that cannot be read is an
  undetermined route with the reason dynamic_route_path, never dropped. A {} hole is no
  longer turned into a :param segment.
- JavaScript gets the openapi model: openapi(def, handler) registers the route its createRoute
  definition describes (verb, path with {x} read as :x, middleware), directly or through a
  project wrapper. It lives in a new config-resolution/http-router-openapi.dl; the framework
  names are knobs.
- http_route keeps one row per handler, verb, path and app, with the earliest registration
  key. An app factory called from many places used to give one row per call path, about a
  thousand rows per route on a large project.
- An app that a request handler serves only by calling its fetch (a forwarding handler) is a
  partial route with the reason unresolved_mount, not a root served at its own paths.
- context answers a full path with a partial route whose unknown piece sits in the middle of
  its path (/v1/{}/ping), with a tests/run.py case and a control that is not that route.

Hono test oracle: a brace glob alternative that holds a * is read as a glob, each row is
printed once (five goldens lose repeated rows, the row set is unchanged), a destructuring
initializer is evaluated once, and a case can opt into rows that pin the known path of a
partial route, which the runtime check also verifies.

New cases: TypeScript compose/glob-brace-alternatives, compose/partial-path-mid-hole,
compose/returned-object-destructure, compose/returned-object-member,
middleware/factory-object-member, openapi/create-route-wrapper, rpc/helper-request-method;
JavaScript js/cjs-factory-object-mw, js/cjs-returned-object-member, js/esm-create-route-wrapper,
js/esm-helper-request-method, js/esm-openapi-create-route, js/esm-returned-object-destructure.
…t prefix scan and openapi definition lookups do the same work in a fraction of the time

- The loop-mount prefix scan read every middleware key of every app character by character; it now scans only the keys that hold a loop-mount mark.
- Scope and path pairs were built through every (middleware key, handler key) pair; they are now built from each root's distinct scopes and distinct paths. A pair is only consulted beside such a key pair and a segment's kind depends on the segment alone, so the decisions are the same.
- The param_vs_literal_scope declaration starts from the few undecidable (scope, path) pairs instead of every key pair.
- The earliest registration key of a route is taken once per (handler, verb, path, app) instead of once per row of the group.
- Two recursive rules (a route definition read through a namespace import, and a call through a destructured name) start from their recursive atom instead of rescanning every property or variable read each round.
- Object literals and parameters are read only for the expressions a route definition is taken from, never for every object literal and parameter read in the program.

Every output relation is identical, as a sorted multiset, on six projects. On the largest of them the solve CPU goes from 34.6 s to 24.8 s.
…er no longer serves one call's sub-app under another call's app

The returned-object-through-a-parameter rule took every call's object at once, so with attach(host, ordersModule()) and attach(host, invoicesModule()) each host served both modules' routes. When objects returned by two different functions reach a parameter that a mount call in its function reads its sub-app from, that parameter no longer carries them, and the sub-apps they hold are declared unresolved_mount instead. One function's results reaching it (the same factory called at several sites) keep their routes. Which functions reach a parameter is decided from calls and bindings alone, before any app is known.

A tests/run.py case pins both shapes: the two false routes the previous rules printed are gone, and the same-factory control keeps its routes.
… arrows, decided table keys, functions handed to project functions, and context values to the services they hold

Rules that close breaks in request call chains, each framework-free unless named:

- Element types: a value read as an element of an array or tuple, or of what a function returns without a written return type, has the element's type; tuple destructuring gives each name its own position's type, and a member of an object literal shape keeps the type of what was written there.
- this inside an arrow function is the this of the method or function that encloses it, through any depth of nested arrows.
- A table of functions read under a decided key (a literal, a const, or a const-object member) runs only that key's entry. A key known only at run time stays an unknown call instead of reaching every entry; a typed table's entries under such a key are value candidates of its index signature.
- A function handed to a project function (withRetry(2, () => load(id)), apply(upper, s)) is a hop from the hand-over site, under the new tier callback_handed, where the callee's body can run it. It is not emitted for a getter read, for a parameter the body never reads or only compares, or for a keyed registry's slot, which the publish on the same key runs.
- Hono: a value read from the request context (c.get, c.var, destructured c.var) has the type the app's environment declares for its key, or, when it declares none, the type of what the app's own middleware writes under that key. A context key also flows along a partial route's decided chain. JavaScript gets the same context-value link, and exports its dispatch and function-value envelope under the TypeScript file names.
- The string evaluation reads a called function's returned root instead of joining every returned sub-expression with its parents, which keeps the index cost flat.

The bundle schema documents callback_handed as a hand-over tier, and the chain reader ranks it beside callback_registered. The TypeScript edge normalizer skips it in compiler pairs, as it skips callback_registered.

Fourteen TypeScript regression goldens change, each row checked against its fixture. Each added callback_handed row is a function handed to a project function whose body calls or passes on that parameter: apply calls t (08), fromArgument calls pick (09), withSink calls run (34), $constructor calls init (59), makeThing and useMemoLike call f (75), each calls fn (78-hof-callback-at-library-boundary), Set.forEach, Promise.then, Lazy.then and Lazy.map call cb (81), and Pipeline.map hands project to Array.map (82). In 80-generic-return-receiver the elements of a typed table read under a computed key move from value_call to value candidates of the index signature. In 78-callable-collections the two value_call rows of an untyped table read under a computed key are dropped; the call stays ambiguous_unknown.
…Hono oracle and a runtime trace

A chains/ case pins, per request, the chain of functions it runs: middleware in order, the handler, and the services and helpers the handler reaches. Its .chains golden is checked against three independent halves of the truth: a compiler call-chain oracle (truth/cc-oracle.mjs, the TypeScript compiler API over the fixture), the Hono oracle's middleware order, and a runtime trace (truth/chain-runtime.mjs: the fixture instrumented by the repository's runtime oracle and each chain's request sent to the real pinned hono). tools/chain_check.py compares them and lists every difference.

TypeScript cases: ctx-partial-route, ctx-partial-untyped, ctx-services-typed, ctx-set-untyped, function-values, library-generic-params, post-validator-chain, same-name-unrelated, this-in-nested-arrows. JavaScript: esm-services-chain.
…a hop when the class reads the field

`constructor(private readonly onTimeout: () => void) {}` never names the
parameter in the constructor body, so the liveness guard on callback_handed
held the hand-over dead. The class runs it through the field instead:
`this.onTimeout()` in a method, or `setTimeout(this.onTimeout, ms)`. A parameter
that declares a field now counts as run wherever that field is read. A
parameter property that nothing reads stays dead, as before.
…er property

87-handed-parameter-property: the field is invoked in a method (Watchdog) or
handed on to a timer (Ticker), and both hand-overs are callback_handed hops.
The controls are a parameter property no member reads (Silent) and a plain
constructor parameter the body never reads (Ignores); neither gets a hop.
typescript, javascript: Hono route table, middleware chains, context keys, request links and call chains (#2003)
…map key or after jsr:) is hono/<sub> to the router model

An app importing createMiddleware from "@hono/hono/factory" kept it a library call: no project middleware, so the
routes it guards had none. A published alias (router_spec_alias) and its subpaths now read as the package's own
names; a package that only starts with the alias (@hono/hono-kit) is itself. Case deno/jsr-name-subpath: the
factory's argument is the middleware of /admin/users, and a look-alike factory is a project function.
…api(ep.route, ep.handler)

A project helper that takes a route definition and hands back `.handler(fn)` as `{ handler: fn, route }`, with the
route built by createRoute from what the helper was called with (passed on, or spread into an object that adds the
helper's own middleware), registered as `app.openapi(ep.route, ep.handler)`: the route was unread. Each such method
call is now its own route: its definition from the helper call's argument, its handler the method call's argument.
Read only from a registration's `.route` / `.handler` arguments. Case openapi/route-builder (golden checked against the
runtime oracle; the static oracle cannot follow the builder's closure): two routes, the version builder's middleware,
and no route for an endpoint nothing registers or for `.openapi(..)` on an object that is no app.
…builder

typescript, javascript: Hono's JSR name @hono/hono/<sub> and a route builder's { route, handler } in the router model
@swapnilpaliwal-sd
swapnilpaliwal-sd merged commit 29f7ecf into main Nov 5, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants