Skip to content

feat: compile route-graph IR and inspect focus (#93) - #119

Merged
ScriptedAlchemy merged 9 commits into
mainfrom
cursor/route-graph-ir-9bc6
Sep 1, 2026
Merged

feat: compile route-graph IR and inspect focus (#93)#119
ScriptedAlchemy merged 9 commits into
mainfrom
cursor/route-graph-ir-9bc6

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Owner

Towards #93. Refs #107 and #105.

Wave 1 Lane B PR-1: the conventional route tree compiles into one immutable route-graph IR. This PR is consumer-invisible — discovery attaches nothing for route-free projects, packaging is unchanged, and the route config extractor follows in PR-2 (so Fixes #93 deliberately does not apply yet).

Route compiler (packages/agent-bundle/src/routes/)

  • graph.ts compiles the conventional roots with the same glob discipline as config/discover.ts (absolute, cwd, dot, followSymbolicLinks: false, onlyFiles):
    • MCP: src/mcp/<server>/{tools,resources,prompts,apps}/*.{ts,tsx} (direct children per kind), ids tool:<server>/<name> etc., serverId: mcp:<server>.
    • Events: src/events/<family>/*.{ts,tsx}event:<family>/<name>.
    • Providers: src/providers/*.{ts,tsx} as a separate CompiledProvider collection, not a route kind.
    • CLI/scripts: src/cli/** and src/scripts/** with nested identity (cli:library/audit).
    • Project ignore rules, private _/. segments, and *.d.ts files are skipped.
  • The conventional-entry probe is a documented local copy: importing config/normalize.ts would close the cycle discover.ts → routes/graph.ts → normalize.ts → discover.ts.
  • The graph is deep-frozen, every route carries the shared frozen config: {} (emptyRouteConfig), and digest covers project-relative identity only, so equal trees hash equally on every machine.
  • Collision errors — the compiler never chooses silently:
    • AB4800 routed server vs src/mcp/<id>.ts or a declared entry/command/url;
    • AB4801 src/cli.ts vs src/cli/;
    • AB4802 duplicate route id;
    • AB4803 unsafe identity segment (^[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?$ per segment);
    • AB4804 invalid routes mode override.
  • Power-tier overrides ride the config index signature (types.ts unchanged): routes.servers.<id> = 'generated'|'custom'|'command'|'remote', routes.cli = 'generated'|'conventional'. Explicit custom/command/remote omits that server's routes; explicit generated keeps them and is never an error against leftover files. A conflict without a mode keeps the discovered routes in conflict mode and errors — discovery is not a packaging choice.
  • types.ts defines CompiledAgentRoute, RouteProvenance, CapabilityState/CapabilityEvidence (exported, not populated — Compile capability-aware host component bundles #100), CompiledProvider, CompiledServerSurface (generated|custom|command|remote|conflict), CompiledCliSurface, CompiledRouteGraph, and emptyRouteConfig.
  • inspect.ts is the dump facade (RouteGraphInspection = CompiledRouteGraph), index.ts re-exports.

Wiring (additive)

  • config/discover.ts: discoverProject compiles the graph and spreads routeGraph only when isEmptyRouteGraph is false.
  • config/validate.ts: validateSource appends discovered.routeGraph?.diagnostics ?? [], so route collisions hard-fail inspect/build.
  • api.ts: focus?: 'bundler'|'hooks'|'routes'|'skills'; on focus === 'routes' it runs loadConfig + compileRouteGraph + inspectRouteGraph into selected.routes; routes surface re-exported from the advanced API.
  • cli.ts: inspect --routes with the existing at-most-one-focus rule; the human writer dumps the graph JSON like the bundler focus.

Lane D riding this PR

Tests

packages/agent-bundle/tests/route-graph.test.ts (unit pool, mkdtemp fixtures, not added to rstest.integration-tests.ts): 12 cases covering conventional discovery (frozen IR, machine-independent digest, config: {}), skip rules, all five diagnostic codes, generated/custom override behavior, the missing routeGraph key on skill-only projects, the routes inspect focus, and the in-process CLI --routes --json dump plus the ambiguous-focus rejection.

Verified locally: unit pool 1793 passed / 0 failed (includes the 12 new cases), rslint clean, tsc typecheck clean.

Do not merge.

Open in Web Open in Cursor 

cursoragent and others added 2 commits September 1, 2026 01:14
Compile the conventional route tree (src/mcp/<server>/{tools,resources,
prompts,apps}, src/events, src/providers, src/cli, src/scripts) into an
immutable, digest-stable route-graph IR with hard collision errors
(AB4800-AB4804) and explicit routes.servers/<id> + routes.cli mode
overrides. discoverProject attaches the graph only when non-empty,
validateSource surfaces its diagnostics, and inspect gains a routes
focus (--routes) that dumps the graph bundler-style.

Towards #93.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
…ench spec status note (#105, #107)

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
@changeset-bot

changeset-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6654dcc

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
agent-bundle Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T01:19:31.731395Z d4e22a5 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d4e22a52f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/agent-bundle/src/api.ts Outdated
ScriptedAlchemy and others added 3 commits September 1, 2026 01:35
…ared route graph to inspect

Reconciliation of the two Lane B PR-1 implementations (#116 closed as
duplicate, #119 the keeper):

- Config always wins: modules referenced by explicit scripts/hooks/bin/
  lib/mcp configuration are claimed by that declaration and never become
  routes. This keeps examples/hooks-and-scripts and examples/mcp-app —
  both with src/scripts/* files, all config-claimed — route-free and
  building unchanged (proved by pnpm examples:check).
- Codex P2 fix: the inspect routes focus no longer reloads the config and
  recompiles the graph (a non-idempotent config factory could diverge from
  the validated model, and second-pass diagnostics were mishandled).
  PreparedProject now carries the graph compiled during discovery, and the
  focus serves it directly; route-free projects serve the shared frozen
  emptyCompiledRouteGraph.
- Test coverage ported from #116: the claim rule (examples-shaped
  fixture), the declared entry/command/url server-conflict branch of
  AB4800, and the route-free inspect focus.

Part of #93
Carry discovery's graph on the invalid-source prepare path and pin
inspect --routes to a single config-factory evaluation.
@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

Deslop/simplify audit (orchestrated): reviewed this PR's full diff against the deslop rubric. No commits pushed — the branch head is untouched for the lander's exact-head gate; findings only. One item worth folding in before merge, three post-merge cleanups. Everything else checked clean: the conventional-entry probe copy is properly documented with its cycle rationale, CapabilityState/CapabilityEvidence are exported types only (no dead runtime code, per the #107 stage plan), route diagnostics ride the existing validateSource plumbing, and the glob discipline matches config/discover.ts rather than reimplementing it.

Worth fixing before merge

  1. src/routes/graph.ts:295-303 — the local deepFreeze reimplements the shared deepFreeze already exported from src/core/freeze.ts (added in 122e0b5 exactly to end these copies). graph.ts already imports from core/ (diagnostics, digest, strict-json, types) and core/freeze.ts imports nothing, so import { deepFreeze } from '../core/freeze.ts' is cycle-free — unlike the documented conventionalEntryAt copy, this duplication has no cycle justification. Behavior is equivalent for the fresh plain-object IR (the shared version is additionally cycle-safe and symbol-aware). Net −9 lines. (config/normalize.ts:585 carries the same pre-existing local copy this was likely pasted from; unifying that one is out of scope for this PR.)

Post-merge cleanup

  1. src/routes/graph.ts:175configClaimedSources is exported but nothing outside graph.ts consumes it (not api.ts, not routes/index.ts, not the tests). Drop the export keyword until PR-2 actually needs it, or re-export it from the barrel deliberately.
  2. src/dev/project-service.ts:751-767 — appending routeGraph as the 16th positional parameter of preparedProject forces the invalid-source call site to pass five consecutive undefined placeholders. The parameter train predates this PR, but it has now crossed the readability threshold; an options-object signature would make both call sites self-describing.
  3. src/api.ts:12-13 — two consecutive export {…} from './routes/graph.ts' statements; fold into one.

Not flagged (checked, judged deliberate): the inspectRouteGraph identity facade with the RouteGraphInspection alias (documented contract-stability seam), the currently unconsumed routes/index.ts barrel (matches the consumed config/dev/eval barrel convention, though public-api.test.ts does not cover the new barrel), and the try/catch in the entry probe (faithful to the normalize.ts original it documents copying).

…uplicate api re-export

Deslop audit items 1 and 4: graph.ts drops its local deepFreeze copy for
the shared core/freeze.ts implementation (cycle-free import; behavior
equivalent for the fresh plain-object IR), and api.ts folds the two
consecutive routes/graph re-export statements into one.

Part of #93
@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

Deslop audit triage (Lane B lander):

  • Item 1 (local deepFreeze) — fixed in 055b004: graph.ts now imports the shared cycle-safe deepFreeze from core/freeze.ts; the local copy is deleted.
  • Item 4 (duplicate api.ts re-export) — folded into one statement in the same commit (trivial, and a push was happening anyway).
  • Item 2 (configClaimedSources export) — acknowledged, deliberately left exported: PR-2 (the static config-export extractor) builds directly on this seam; will re-export it from the barrel there or drop the keyword if plans change.
  • Item 3 (preparedProject positional train) — acknowledged as post-merge cleanup; the train predates this PR and an options-object refactor touches call sites beyond this diff, so it stays out of the exact-head gate.

@pkg-pr-new

pkg-pr-new Bot commented Sep 1, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@119
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@119
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/rsc-runtime@119

commit: d9a2bb0

…ore types for consumers

Route-graph compilation reads config sections (scripts, hooks, bin, lib, mcp)
whose getters are untrusted until validation runs; a throwing getter must
degrade to an absent graph so validation still reports AB7001 instead of the
load-failure path reporting AB7000. The public-api consumer fixture also gains
an ignore symlink because routes/graph.d.ts now pulls config/ignore.d.ts into
the root declaration graph (ignore is a runtime dependency, matching the ajv
precedent).
Keep discovery tolerant of hostile config-claim getters while forcing hostile routes access through source validation, so inspect reports AB7001 instead of silently returning ready.
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