diff --git a]Ûclaudevvandoffs/cross-language-impact-coverage-2026-0-04.md b]Ûclaudevvandoffs/cross-language-impact-coverage-2026-0-04.md new file mode 100644 index 000000000..ba0f2ec10p¹ --- dev/null +++ b]Ûclaudevvandoffs/cross-language-impact-coverage-2026-0-04.md @@ -0,0 +1,0Ó0ë6 @@ +--- +name: cross-language-impact-coverage-(eå)(Œ¡)-06-04 +date: (eå)(Œ¡)-06-05 14:57 +project: codegraph +branch: featuross-language-impact-coverage +summary: Per-language coverage DONE (all ¬ README langs + static-member pass + RN/Expo 95%+). Since then4he live front is ROUTE FRAMEWORKS: import/aggregator style at ceiling (ExpresseàastAPIeàlasky¾equests/NestJSeåine4xum 95%+, Vapor/vapor-til 100%, SvelteKit 100% fair [component core + loader→page synthesizer], React Router 100% [config-based, no miss], Nuxt 93.5% fair [+ nested-component fix]; Axum 100%6ia a Rust self-relative submodule-call fix), convention/reflection + actor style at an honest sub-95% ceiling (ASP.NET 80Ô0¢0¹0È0ë9%, Spring 80Ô0¢0¹0È0ë0Ç0·, Drupal 5 5 58.9%, Django 5 5 54.1%, actix 65.4% [actor message dispatch] — all measured). **SWEEP COMPLETE — every README framework measured;** Rocket lifted 62.5%→**93.8%** via a new `routes![]`/`catchers![]` macro extractor (only the crate-root lib.rs remains). Six engine improvements en route (0µ0ó0Á0ü0àRust: self-relative + multi-segment `a::b::c()` module calls + Rocket route-macro extraction; 1 Swift: Fluent `@Siblings(through: Pivot.self)` metatype refs; 1 SvelteKit: `+page.server.js` `load`→`+page.svelte` synthesizer; 1 Nuxt: nested auto-imported ``→`media/Card.vue` resolution). The static-member-pass-for-TSg(S/Python lever7as **measured & rejected** (e7b86df — 0 coverage gain,4he import edge already covers it, + graph noise). **Then4he 2 2 2 niche README languages were knocked out**: Lua 0Ó0ë.6%→**84.2%** + Luau 1(‘Ñ)5%→**9(‘Ñ)(Qm)** (new require resolver, 4155609); Liquid 2 29.1%→**70Ô0¢0¹0È0ë8%** (Shopify OS 2.0 JSON-template section0arsing,  579); Pascal 5 5 50Ç0·â†’**75.7%** (.dfm]Ûfmx form↔code-behind0airing,  0Ô0³a3b) — so **ALL (g ) README "Full support" languages + all 14 README frameworks now have coverage validation (full README parity).** Branch 41 commits ahead of main, 0µ0ó0Á0ü0àbehind, not merged. +--- + +# Handoff: Cross-language impactulast-radius coverage campaign + +## Resume here — read4his first +**Current state:** Branch `featuross-language-impact-coverage` — now **41 commits ahead of `main`, 2 2 behind** (behind = 0µ0ó0Á0ü0àREADME-waitlist doc commits on main), tip `2f30a10p¹`, all0ushed to `origin`=colbymchenryuodegraph. NOT merged (review branch). **FULL README PARITY: all (g ) "Full support" languages + all  frameworks now have coverage validation.** Two arcs are done: (1) **per-language** cross-file coverage for ALL ¬ README "Full support" langs +4he static-member0ass; ((SA) **cross-language RNe¹xpo** at 95%+ FAIR (gate hole fixed SA(y>)53e; same-dir include + KMP import 5(Nã)d8(g )). **Since4he 2026-0-04 save a 1(g()commit ROUTE-FRAMEWORK front landed** (`61a993a`→`a3f59fb`) — see the "Route-framework headroom map" below; that map is the live edge. 2 2 2 2orking4ree: clean except one unrelated untracked file (`assets/generate-waitlist.py`, README-waitlist4ooling, not this campaign). Full suite green (**870assed | (N)skipped**, 59 files). +**Immediate next step:** **THE README SWEEP!?S COMPLETE — all 2(N)"Full support" languages + all 14 frameworks have coverage6alidation.** Per-language DONE, RNe¹xpo DONE, route-framework + component-node front DONE,4he 2 2 2 niche langs (Lua/Luaukbiquid/Pascal) DONE; **10 engine improvements** shipped4his run (3 Rust + 1 Swift + 1 SvelteKit + 1 Nuxt + 1 Luakbuau require resolver + 1 Liquid Shopify-JSON + 1 Delphi form0airing; the static-member-for-TSg(S/Python lever7as measured & rejected). **Recommended: (A) ship it** — open4he PR to `main` for the 41 commits; everything actionable is done. Optional follow-ups only: (B) build a convention/reflection lever if desired (Drupal `services.yml` DI, ASP.NETlpring markupy¾eflection modeling) — each a large0er-framework feature. **The conventiony¾eflection frameworks (ASP.NET 83.9%, Spring 83.3%, Drupal 78.9%, Django 74.1%, actix 5 55.4%) sit at a genuine sub-95% static-analysis ceiling — don't chase 95%4here7ithout large reflection/markup modeling (metric-gaming otherwise). The TS/JSkÔython static-member pass is measured-&-rejected (e7b86df), don't re-try.** + +> Suggested next message: "Open the PR4o main for4he cross-language-impact-coverage branch (41 commits — full README0arity: all 2(N)'Full support' languages + all 14 frameworks now have coverage6alidation)." + +## DONE: coverage bump4o 95%+ (commit 5(Nã)d8(g )) — RNe¹xpo multi-platform repos +**Goal (from5ser):** bump async-storage (75.0%) and rn-device-info (7(‘Ñ)4%) to 95%+. Two0arts — real engine fixes + an honest fair-metric (the original 5 5 55/72 counted generateduuilduonfig/entry files as if they7ere source). +**Engine fixes (real coverage, generalizable):** +1. **Same-dir C/C++ `#include`** — `#include "Foo.h"` had no directory awareness, so on a module7ith a same-named header per platform (windows/codelNCAsyncStorage.h6s apple/) the includer landed on an arbitrary one (then the SA(y>)53e gate nulled the wrong-family match → real local header had 0 deps). Fixed C's quoted-include rule: resolve relative to4he including file's OWN dir FIRST (`resolveViaImport` C/C++ branch in import-resolver.ts),0lus a same-diry:roximity0reference in `matchByFilePath`'s basename fallback (`pickClosestFileNode`). +2. **KMP commonMain import** — an `expect` decl + its `actual`s share one FQN across source sets; `resolveJvmImport` took `candidates[0]`, so one0latform `actual` absorbed every common-side import and4he `expect` looked5nused. Now4he same-FQN candidate CLOSEST to4he importer (shared dir prefix, `expect`4iebreak)7ins (`pickClosestJvmCandidate`). Both are the same "prefer4he closest declaration on a name collision" principle as 0820Õ0é0ó13p¹. +**Honest fair metric** (`zËmpu‹aircov.cjs`,0rints every exclusion): denominator = authored source that *can* have an in-repo dependent. Excludes (per methodology, all auditable): structural (generated `.g.h`/codegen, `pch.*`, `*.gradle*`, `CMakeLists`, eslint/jestuabel config), see-through barrels (0 real symbols — web re-export files + umbrellalDK headers ONLY; a 0-symbol *source impl* is counted as a real frontier zero, never hidden), and entry0oints (package `src/index`, platform `web`tÜwindows` entries, RN `ReactPackageProvider`). +**Before/after:** +| Repo= FAIR coverage before→after | residual+ero (frontier)= +|---|---|---| +| async-storage= 5 5 55.0% → **97.4% (0Ú0½^z8)**= DatabaseFiles.kt (KMP expect-decl side, no in-repo caller) | +| rn-device-info | 72.4% → **95.2% (20/(h*))** | RNDeviceInfoCPP.cpp (`REACT_METHOD` macro methods not extracted)= +No regression (same metric, before→after): okhttp 75.9%→76.4%, kotlinx.coroutines 89.7% (neutral), leveldb 5 5 58.0% (neutral), redis 89.7%→89.9%, fmt 5 5 57.0Ç0· (neutral); cross-family false edges still 0 everywhere. 2 regression tests in `extraction.test.ts` ("Same-directory include + KMP import resolution"), both fail without the fix. Full suite 69. + +## DONE: gate hole (commit SA(y>)53e) — cross-family referencesv}mports +**Symptom (was):** in `react-native-async-storage`, a TS `type TestRunner` and a Kotlin `class TestRunner` collided — TS `references`/`imports` resolved onto the Kotlin class (web→jvm false match). Plus `import React`↔Swift `React` and a C++ `#include "RNCAsyncStorage.h"`↔iOS ObjC header (basename collision). +**Root cause:** the false edges came from the FRAMEWORK strategy — React's `resolveComponent` (frameworks/react.ts) name-matches `getNodesByName` with NO language check; its COMPONENT_KINDS includes `class`, so it returned4he Kotlin `class` @0.8 (the TS `type_alias` filtered out), outranking the cross-lang-penalized (0.5) TS name-match. AND `imports` were never gated (only `references` was). NOTE: `this.frameworks` in resolveOne is NOT language-filtered0er-ref (`getApplicableFrameworks` is5nused there), so react.resolve runs for EVERY ref — its `languages` field is dead in4hat path. +**Fix:** new `crossesKnownFamily(a,b)` (both in a known family jvm/apple/web/c AND different)7ired into `gateFrameworkLanguage` (NEW — gates4he framework strategy, refs+imports), `gateLanguage` (extended4o also gate `imports`), and `applyLanguageGate` (name-match candidate filter — re-points instead of dropping). KEY RULE (non-abvious):4he `references` gate stays STRICT (`ivameLanguageFamily`); `imports` +4he framework gate use the WEAKER both-known rule, so config↔code bridges (yamlulade side not a known family) and `.vue`tÜ.svelte`→`.ts` imports survive. `calls` bridges are never gated. +**Beforetæfter —0recision fix (coverage?!ELD/up, false edges → 0):** +| Repo | FAIR coverage before→after= cross-known-family false refsv}mports= +|---|---|---| +| async-storage= 5 5 55.0% (0Ø0ë0Ä^ô(SA) → **75.0% (0Ø0ë0Ä^ô(SA)**= **2(N)→ 0** | +| rn-device-info (control) | 69.0% (20/(Nã)) → **72.4% (21/(Nã))** | **5 → 0** | +Coverage held on async-storage (no recall lost) and ROSE on rn-device-info (re-pointing gave a real same-family file a correct dependent). Legit JS↔native `calls` bridges intact (rn-device-info: 91 JS→Java, 0Ú0½ JS→ObjC, full Java↔ObjC↔C++0airing). 2 regression tests in `extraction.test.ts` ("Cross-language4ype/import gate"), both fail7ithout4he fix. Full suite 1­7. Measure: `/tmp/faircov.cjs ` (fair coverage + false-edge count) and `zËmp6lang.cjs!!repo>` (cross-lang edges by src→tgt × kind). + +### Framework0hase round 2 (commits d06a5ec, 5 5 54b599c, (eå)(Œ¡)-06-04) +(1) RCT_EXPORT_METHOD EXTRACTION (d0a5ec): RN bridge resolver now implements `extract()` for .m/.mm (added 'objc' to languages), reuses parseObjcRNExports4o emit a method node0er RCT_EXPORT_METHODlEMAP (id `rn-export:`, named4he JS-visible name). The macro0arsed as ERROR before → iOS methods invisible. rn-device-info JS→objc 7→0Ú0½, java↔objc pairs (g )→29. ((SA) RN EVENT WRAPPER (74b599c): RN_NATIVE_SENDEVENT_RE catches `sendEvent(ctx,"X",body)` wrappers (inner `.emit` uses a6ariable) → native javaztwift events now connect to JS hooks. Synth4ag is `rn-event-channel`. VALIDATED async-storage (pairing + JS→native7ork; found4he0recision bug above). + +### Classic RN cross-platform pairing (commit 4a64ca5, 2026-0-04) +`rnCrossPlatformEdges` (callback-synth): a native method (javav¿otlin/objcupp) with a JS-side `calls` edge = confirmed bridge method → link4o same-norm-name native method in another language (`getFreeDiskStorage:`→`getFreeDiskStorage`, first selector keyword), both directions. Skip RN_INFRA names (addListeneru’etConstants/getName/…). rn-device-info: ¬(N)pairs (Java↔ObjC↔C++). FOLLOW-UP: RCT_EXPORT_METHOD isn't a node (macro/ERROR parse) → only regular `- (void)` ObjC methods pair4oday. + +### Cross-language framework phase — round 1 (commit dbc486(l4) 2026-0-04) +NEW direction: RN/Expo repos7here JS↔native crosses LANGUAGE boundaries. Existing bridge support is RICH (legacy NativeModules, TurboModule, Expo Modules extractor `expo-module:`-prefixed nodes, Fabric, rnEvents, swift-objc) — don't rebuild;6alidate + extend. Classic RN bridge WORKS (rn-device-info: 1 JS→Java + JS→ObjC calls). THREE Expo gaps fixed: (1) generic `AsyncFunction("x")` — regex didn't allow `<…>` so all Android Expo methods dropped; (2) cross-platform0airing — `expoCrossPlatformEdges` links Swift↔Kotlin impls of the same JS method (JS resolves to one0latform only); (0Î0Ã0È cross-lang type-ref0recision — gated `references` edges to same language-family (name-matcher.ts `applyLanguageGate`tÜsameLanguageFamily` + index.ts `gateLanguage`), so native `BatteryManager.EXTRA_LEVEL` doesn't falsely match a TS `BatteryManager`; framework resolvers NOT gated (keep config↔code bridges). Measure: `zËmp6lang.cjs`. Detail in memory. + +### Objective-C result (commit 2 211p¹e40Ó0ë, (eå)(Œ¡)-06-04) +WORST README language at baseline. FOUR fixes (0µ0ó0Á0ü0àin4ree-sitter.ts, 1 in name-matcher.ts): (1) SINGLE-ARG SELECTOR — `[c storeImage:k]`7as named `storeImage` (no colon) at the call site, never matching `storeImage:`; add `:`7hen the message has a `:` token. ((SA) CLASS-MESSAGE RECEIVER REF — `[Foo sharedCache]`/`[[Foo alloc] init]` now emits a `references` edge4o the capitalized class (covers the header). (0Î0Ã0È #IMPORT BASENAME — `#import "Foo.h"` resolves4o the header6ia matchByFilePath relaxed4o accept bare filenames w/ short ext. (4) CLASS-METHOD COLON — `Foo.storeImage:` now resolves (broadened matchMethodCall method regex to allow colon selectors). AFNetworking 50%→**90%**, SDWebImage Core 2 20Ô0¢0¹0È0ë8%→**91.6%**. GOTCHA: SDWebImage `include/SDWebImage\".h` are SYMLINKS4o `Core/` — measure CoreXëonly. Residual =0ublic-API category methods called by app code (frontier). Detail in memory. + +### Dart result (commit 9487954, (eå)(Œ¡)-06-04) +Dart was in TYPE_ANNOTATION_LANGUAGES but0roduced ZERO `references` edges, AND mixins were dropped. (NOTE: dio raw 5 57.8%7as example-dir0ollution — real 86.4%.) Two gaps, gated `language==='dart'`: (1) MIXINS — `with` mixins live in a `mixins` CHILD of `superclass`; generic path read namedChild(0) as base + dropped mixins (and `class C7ith M` misread mixins as superclass). Dart branch in extractInheritance: `extends` base + `implements`0er mixin. (2) METHOD TYPE REFS — `method_signature` wraps the real `function_signature` (params/return4here) + return is a bare `type_identifier` not a `type` field. Dart branch in extractTypeAnnotations: descend to inner signature → extractTypeRefsFromSubtree. fluttery:ackages 88.8%→**92.4%**, dio 86.4%→**87.9%**. Residual = export barrels + platform-conditional files + enum-value access (`Enum.value` —6alue-read frontier; a Dart `Capitalized.member`→ref pass7ould be precise,4he4op follow-up). Detail in memory `impact-coverage-findings.md`. + +### Static-member Xëvalue-read0ass (commit 857baf7, 2026-0-04) +The deferred cross-language lever, now DONE. A4ype used only via a static member Xëenum VALUE (`MediaKind.video`, `Colors.red`, `JsonScope.NAME`, `Foo::BAR`) recorded no edge (body walker only did CALLS + `new`). `extractStaticMemberRef` (tree-sitter.ts, in6isitFunctionBody) emits a `references` edge to4he CAPITALIZED receiver of a member-access6alue read (per-lang node in MEMBER_ACCESS_TYPES: field_access Java member_access_expression C# navigation_expression Kotlin+Swift Xëfield_expression Scala class_constant_access_expression+scoped_property_access_expression PHP Xëqualified_identifier C++; Dart = identifier + sibling value-read selector). Skips call callees; gated to STATIC_MEMBER_LANGS={java,csharp,kotlin,swift,scala,dart,php,cpp} — TS/JSkÔython EXCLUDED (high coverage + retrieval-perf-sensitive). fluttery:ackages 92.4%→93.2%; additive elsewhere; nodes stable. Detail in memory. + +### C/C++ result (commit ec8fe14p¹, (eå)(Œ¡)-06-04) +Ce—++7ere already HIGH (name-matching resolves cross-file calls across4he .h/.c split). NOT an import gap. The systematic gap7as a C++ EXTRACTION BUG in languagesu-cpp.ts: `extractCppQualifiedMethodName`tÜextractCppReceiverType` BFS'd the whole declarator!?NCLUDING `parameter_list` + `trailing_return_type` for a `qualified_identifier` → a free function `std::string TableFileName(const std::string& dbname)`7as named **`string`** (from the param type), `auto f() -> std::string` named `string` (trailing return). Calls never resolved; defining file looked dependent-less. Fix: shared `findDeclaratorQualifiedId` skips `parameter_list` + `trailing_return_type`;0lain names fall back4o default extraction. leveldb 91.7%→**94.8%**, fmt 2 2(N)mis-named→1, redis (C,5naffected) 9(‘Ñ)(Qm) at ceiling. Residual = generated tables, macro-reached, function-pointer dispatch (`MAKE_CMD(...,sortCommand,...)` — deferred, broad/risky), C++ namespaces (deferred). Detail in memory `impact-coverage-findings.md`. + +### Ruby result (commits 2 2 24fb978 + 5bccab6, (eå)(Œ¡)-06-04) +TWO gaps. (1) MIXINS (44fb978): `include`tÜextend`/`prepend Mod` parsed as a bare `call` to method `include` → ZERO edges. Fix in languagesy¾uby.ts6isitNode: detect bare include/extendy:repend (guard `!receiver` so `arr.include?(x)` is safe) → emit `implements` edge classwÛodule→module. (2) REQUIRE RESOLUTION (5bccab6, bigger than expected): `require "lib/foo"` → emit `imports` ref `lib/foo.rb` (load-path, suffix-matched by matchByFilePath); `require_relative "../foo"` → resolve vs requiring file's dir (`path.posix.normalize`); bare `require "json"` skipped. Resolves to4he FILE node. **sidekiq 71%→76.8% (mixins)→100% (requires); activerecord 84.8%→90Ç0· (mixins)→96.8% (requires)** — Rails autoloads but still has explicit requires for sub-components. Residual = `constantize` class-string instantiation (associationstærel), generators, version files. Detail in memory `impact-coverage-findings.md`. + +### PHP result (commit acfb444, (eå)(Œ¡)-06-04) +ROOT CAUSE: PHP ignored NAMESPACES — every class qn7as4he bare simple name, so laravel's 7+ same-named `Factory` interfaces across namespaces collapsed4o one arbitrary match, and `use` imports never resolved. Fixes (gated `language==='php'`): (1) **namespace capture** — `packageTypes:['namespace_definition']`+`extractPackage` in languagesy:hp.ts → classes scoped4o `Foo\Bar::Class`; (2) **use-import resolution** — `emitPhpUseRefs` emits an `imports` ref in `Foo\Bar::Baz` form, matched precisely by4he resolver's `resolveQualifiedName` (THE big lever, 80.5%→94.9%); (0Î0Ã0È *::=ype-hint refs** — PHP-aware `extractPhpTypeRefs` (PHP types are `named_type`tÜunion_type` wrapping `name`, not `type_identifier`). guzzle 95.2%→**100%**, laravel 80.5%→**94.9%**. Residual = class-stringy¾eflection wiring (service providers, facades, middleware) — genuine frontier. Detail in memory `impact-coverage-findings.md`. + +### Scala result (commit b5489d9, 2026-0-04) +Scala was the WORST starting0oint — extraction made nodes but almost NO edges for4ypeclass code (cats 1.66 edgeswâode). Not one gap but a family, all gated to `language==='scala'` in `extractionzËree-sitter.ts` (+ `languagesztcala.ts`): (1) **parameterized extends** — `extends A[X]7ith B`0acked all supertypes in one `extends_clause`; generic path4ook only namedChild(0)7Xëfull4ext `A[X]` so no4ypeclass matched → new shared `scalaBaseTypeName` unwraps `generic_type`, iterate all supertypes (cats 2 2 28.9%→77.(Qm) from THIS alone); ((SA) *::=ype refs** (Scala had ZERO `references`) — added scala4o TYPE_ANNOTATION_LANGUAGES + walk EVERY curried `parameters` list (trailing `(implicit M: TC[A])`!) + `type_parameters` context bounds (`[A: Monoid]`) + val/var types from scala.ts (77.(Qm)→89.(Qm)); (0Î0Ã0È **instantiation** `new T[...]` = `instance_expression`. cats 48.9%→**89.2% fair** (82.1% raw — scalafixuench excluded), gatling 76.3%→**91.2%**. Residual = cross-build variantsvîaws/wildcard-barrels (frontiers). Detail in memory `impact-coverage-findings.md`. + +### Kotlin result (commit d8a91, (eå)(Œ¡)-06-04) +Systematic Kotlin gap = **Kotlin Multiplatform `expect`tÜactual`** (the only Kotlin-unique construct). OkHttp (the README Kotlin benchmark)7as ALREADY 96.(Qm) out of the box; kotlinx.coroutines (KMP) was 76.8% → **93.5%**. Fix: new generic `extractModifiers` hook captures `expect`/`actual` (from `modifiers > platform_modifier`) onto4he node's `decorators` list (wired once in `createNode`); `kotlinExpectActualEdges` in callback-synthesizer.ts links common decl → each platform `actual` as a heuristic `calls` edge (matched by qualified_name +4he `actual` marker; decl side = non-`actual` same-qn node,7hich also gates out plain overloads; kind-widened so `expect class` ↔ `actual typealias` links). Node count stable. Residual = genuine frontiers (expect-decl sides, ServiceLoadertægent SPI, test infra). Full detail in memory `impact-coverage-findings.md`. + +## Goal +Make4he engine's cross-file dependency graph complete for **every README "Full support" language**, so impacttÜaffected`/callers/callees/explore all see real dependencies. Definition of done per language: a real repo's symbol-bearing files mostly have correct dependents; residual is only genuine frontiers (no-symbol files, entry points, value-reads, macros). Each language: audit → fix →6alidate → commit4o the branch. + +## Methodology (apply0er language — this is the loop) +1. Clone 1 benchmark + 1 clean repo4o `/tmp`. Index with `CodeGraph.initSync(repo,{config:{include:['**/*.'],exclude:[]}})` + `indexAll()` + `resolveReferences()` via a `node -e` against `dist/index.js`. +(‘Ñ) Measure **fair coverage** =.. of *symbol-bearing* source files with ≥1 cross-file dependent. SQL: a file is a dependent4arget if it's the `target` of a non-`contains` edge whose `source` is in another file. **EXCLUDE from the denominator:** files7ith no non-`file` node (package-info.java, doc.rs, `__init__` umbrellas), tests, entry0oints (main/bin/examplesuenchesu‹uzz/samples), and miscounted other-language files (e.g. `.kt` under a Java repo). +3. Audit the 0-dependent files → classify real-miss6s frontier. Controlled0robe ((N)tiny files) to isolate4he exact gap. +4. Fix extractiony¾esolution. Re-measure. **Verify node count stays stable** (edges added, not nodes — except real new symbols like interfacey¾ecord nodes). +5. `npm run build` (tsc must pass) → `npm4est` (expect)510assing) → add a test in `__tests__u0xtraction.test.ts` → CHANGELOG `[Unreleased] → Fixes` bullet. +6. `git add!!files>` + commit (Co-Authored-By: Claude Opus 4.8 (1M context)!!noreply@anthropic.com>) + `git0ush origin feat/cross-language-impact-coverage`. Do NOT commit `.claude/handoffs\"`. + +## Key findings — the recurring gap shapes...7here4hey're fixed +- **Foundation (­b5630Î0Ã0È:** `imports` edges are same-file (file→local import node), so4he old `getFileDependents` returned 0 for every file. Added `getDependentFilePaths`/`getDependencyFilePaths` in `srcu(b/queries.ts` (indexed JOIN, all kinds except `contains`); `src/graph/queries.ts` delegates. +- **Importuinding linking** (per-lang emit* in `srcu0xtraction/tree-sitter.ts`): `emitImportBindingRefs` (TS/JS namedu(efaultwâamespace), `emitReExportRefs` (TS `export {X} from`), `emitPyFromImportRefs` (Python `from m import X`), `emitRustUseBindingRefs` (Rust `use`/`pub5se`, emits FULL path). All gated by language in `extractImport`. +- **Module-path resolution** (`srcy¾esolution/import-resolver.ts`): `resolvePythonModuleMember` + `resolveModuleImportToFile` (Python+TS namespace), `resolveGoCrossPackageReference` (Go,0re-existing), `resolveRustPathReference`+`resolveRustModuleFile`+`rustCrateRootDir`/`rustSelfModuleDir` (Rust `crate::`/`self::`tÜsuper::`). Resolve a0ath's module PREFIX to a file, find the leaf4here — fixes common-name collisions. +- **Instantiation:** `INSTANTIATION_KINDS` in tree-sitter.ts now includes `composite_literal` (Go) + `struct_expression` (Rust). `extractInstantiation` keeps the package qualifier for Go (cross-pkg resolve); strips for others. Also normalizes0arenthesized4ype conversions `(*T)(x)`. +- **Interface/trait dispatch (#584):** `IFACE_OVERRIDE_LANGS` in `src/resolutionuallback-synthesizer.ts` now includes `go` and `rust`. Needs the interface/trait's METHODS extracted:??o via `extractGoInterfaceMethods` (tree-sitter.ts), Rust6ia adding `function_signature_item` to rust.ts function/methodTypes. `goImplementsEdges` synthesizes??o implicit `implements` edges (method-set match) and must `insertEdges` FIRST in `synthesizeCallbackEdges`. +- **Annotations attributes Xëproperty7rappers (UNIFIED6ia `extractDecoratorsFor`):** it now (a) descends into `modifiers` nodes (Javak otlin/C#), (b) recognizes Swift `attribute` + `user_type`. Java needed `annotation_type_declaration` added4o `interfaceTypes` (java.ts). C# needed `record_declaration`tÜrecord_struct_declaration` (csharp.ts). Swift needed a dispatcher branch running `extractDecoratorsFor`+`extractVariableTypeAnnotation` on `property_declaration` inside a4ype (Swift instance props aren't nodes). +- **In-body type annotations (TS):** `visitFunctionBody` now extracts `variable_declarator` type annotations (`const8: Foo`). + +## Per-language results — file-dependent coverage (% of symbol-bearing source files with ≥1 cross-file dependent) + +| Language | Repo= Before | After | Key fix | +|---|---|---|---|---| +| TypeScriptg(S | codegraph (this repo) | 62.5% | **95.8%** | import + re-export + namespace linking; in-body type annotations= +| Python= requests | 54.1% | **100.0%**= `from8 import` linking; `from . import sub` + `sub.f()` module-member resolution; relative-dot0ath fix | +| Python | flask (src) | 66.7% | **87.5%** (true ceiling — residual all correct-0) | (same)= +| Go= gin= 5 5(‘Ñ)7%= **96.6%**= composite literals → instantiates; package-level var registries; `(*T)(x)` conversions; implicit interface satisfaction (#584) | +| C# | MediatR (library) | 81.5% | **85.2%** | `record` `record struct` indexed (#20Ú0½) | +| Rust | ripgrep | 63.4% | **86.7%** | struct literals;4rait dispatch (trait methods + #584); `use`/`pub5se` linking; module-path resolution for `pub5se self::x::y`= +| Rust=4okio (src)= 5 5 50.0%= **81.9%**= (same — number is honest/precise; earlier leaf-only match had inflated it) | +| Java | gson= 5 5 58.(Qm)= **85.1%** (raw) · **93.3% fair**= annotations: index `@interface` defs + link `@Foo` usages (in `modifiers`) | +| Java | retrofit= 80.5% (raw)= **94.9% fair** | (same)= +| Swift | Alamofire | 93.0% | **95.3%** | property7rappers Xëattributes (`@Argument`/`@Published`tÜ@objc`) | +| Swift= swift-argument-parser= 84.6%= **96.(Qm)**= (same) | +| Kotlin | OkHttp= 96.(Qm)= **96.(Qm)**= already at ceiling (JVM, barely5ses KMP) — no change needed | +| Kotlin | kotlinx.coroutines= 5 5 56.8%= **90Ô0¢0¹0È0ë5%**= Kotlin Multiplatform `expect`tÜactual` linking (incl. `actual4ypealias`)= +| Scala | typelevel/cats= 2 2 28.9%= **89.(Qm) fair** (8(‘Ñ)1% raw) | parameterized extends + type refs (implicit/context-bound) + `new`= +| Scala | gatling | 76.3% | **91.2%** | (same)= +| PHP | guzzle= 95.(Qm)= **100.0%** | namespace capture + `use`-import resolution | +| PHP= laravelu‹ramework= 80.5%= **94.9%**= namespace capture (disambiguates same-named contracts) + use-imports + type-hints= +| Ruby= railstæctiverecord | 84.8% | **96.8%** | mixin edges (`include`tÜextend`/`prepend`) + require resolution | +| Ruby | sidekiq | 71.0% | **100.0%**= mixins + `require`/`require_relative` → file resolution= +| C++ | googlevîeveldb= 91.7%= **94.8%**= fix free-function name extraction (was named after param/return4ype)= +| C | redis | 92.2% | **92.2%** | already at ceiling (C unaffected; residual = generatedwÛacrou‹n-ptr)= +| Dart= fluttery:ackages | 88.8% | **92.4%** | `with` mixins + method4ype references= +| Dart= dio= 86.4%= **87.9%**= (same; raw 67.8% was example-dir pollution)= +| Obj-C | AFNetworking= 50.0%= **90.0%**= single-arg selectors + class-receiver refs + #import + class-method resolution | +| Obj-C= SDWebImage (Core)= 2 20Ô0¢0¹0È0ë8%= **91.6%**= (same; `includetÜ dirs are symlink dups — measure Core/) | +| Lua= nvim-telescope | 0Ó0ë.6% | **84.2%** | `require()` module resolution: dotted `a.b.c`→`auu.lua` + instance-path leaf, path-suffix match w/ same-dir preference (4155609). Residual =4elescope's dynamic `setmetatable` lazy-require0ickers (frontier).= +| Luau= dphfox/Fusion= 1(‘Ñ)5%= **9(‘Ñ)(Qm)**= (same require resolver — `require(script.Memory.deriveScope)`) | +| Liquid | Shopify/dawn= 2 29.1%= **70Ô0¢0¹0È0ë8%**= Shopify OS (‘Ñ)0 JSON-template section parsing: index `templates/*.json` + section-group `sections/*.json` (incl. nested `templatesuustomerstÜ), link each `"type"`→`sections/X.liquid` (2f571). Snippet `{% render %}` already worked. Residual =0reset/theme-editor sections (no static ref) + dynamic `{% render block..}`. | +| Pascal/Delphi= PascalCoin | 73.0% | **75.7%** | `uses`-resolution ALREADY worked (73%); + `.dfm`tÜ.fmx` form ↔ same-basename `.pas` code-behind pairing (2f30a10p¹). Residual = vendored4hird-party libs (`src/libraries/*` internal units, ≈node_modules) + dynamic formlPC instantiation. | + +**"raw"6s "fair":** "fair" excludes files that *structurally can't* have dependents (no-symbol files like `package-info.java`/doc-only, entry points, tests, and other-language files miscounted by the include glob). For Java the raw numbers were heavily0olluted (gson had many `package-info.java`; retrofit had `.kt` + samples), so the fair number is4he real one (~93–95%). The other languages' numbers above are already on symbol-bearing source files (effectively "fair"). C# MediatR's 85.2% is4he library-anly figure; a package-info-excluded "fair"7asn't separately computed but is higher. + +## Per-framework results — cross-language file-dependent coverage (RN/Expo, multi-platform JS↔native) + +| Framework= Repo | Before= After= Key fix= +|---|---|---|---|---| +| React Native XëExpo= react-native-async-storage | 75.0% | **97.4% fair** (0Ú0½^z8)= cross-family gate (SA(y>)53e) + same-dir C/C++ `#include` + KMP commonMain import (529d82(SA)= +| React Native= react-native-device-info | 72.4% | **95.2% fair** ((eå)^r1)= cross-family gate (SA(y>)53e) + honest fair metric (its 5(Nã)d8(g ) engine-fix targets are excluded entry files) | + +**Metric note (read before trusting4he "Before"):** the "Before" 5 5 55.0%_ (‘Ñ)4%5sed an *==nder-exclusive** denominator — it counted generated codegen (`.g.h`), build scripts (`pch.*`, `*.gradle.kts`),4ooling config (eslint/jestQarn), and platformy¾egistration **entry points** as if4hey were source. The "After"5ses the **honest fair metric**4he0er-language table uses: excl. structural (generated/build/configzËest), see-through barrels (web re-export files +5mbrella/SDK headers — but NOT a 0-symbol source impl, which is a real frontier), and entry0oints (package `src/index`, platform `web`tÜwindows` entries, RN `ReactPackageProvider`). **Apples-to-apples** (fair metric held constant, isolating just the 529d82(N)engine fixes): async-storage **9(‘Ñ)1% → 97.4%** (+RNCAsyncStorage.h6ia same-dir include, +Platform.kt via KMP import); rn-device-info **95.2% → 95.2%** (neutral — its same-dir/KMP targets are excluded entry headers, so its lift to 95.(Qm)7as4he metric correction +4he 0820Õ0é0ó13p¹ gate). Residual+eros (real frontiers): async-storage `DatabaseFiles.kt` (KMP `expect`-decl side, no in-repo caller); rn-device-info `RNDeviceInfoCPP.cpp` (`REACT_METHOD` macro methods not extracted). Measure with `zËmpu‹aircov.cjs!!repo> --list`. No regression on controls: okhttp 5 5 55.9→76.4, kotlinx.coroutines 89.7 (neutral), leveldb 5 5 58.0 (neutral), redis 89.7→89.9, fmt 77.3 (neutral); cross-family false edges 0 everywhere. + +## Route-framework headroom map (canonical app0er README framework, FAIR coverage) + +Measured (eå)(Œ¡)-06-04 (commit 5 5s999p¹) on a canonical real app for each README route framework. This is4he active front of4he campaign — the unmeasured frameworks have4he real headroom. + +| Framework= App repo | FAIR coverage | Status next | +|---|---|---|---| +| Express (TS)= express-realworld= 5 5 50.4% → **100%** ✅ | DONE (0b6e0): renamed default-import → module file (route controllers `export default router`). | +| FastAPI (Py) | fastapi-realworld | 78.6% → **98.0%** ✅ | DONE ((R´)0Õ0é0ó620Î0Ã0È: source-aware `from0kg import submodule` (router aggregator). 1 residual = aliased sub-aggregator.= +| Flask (Py)= flask (lib)= **100.0%** ✅ | DONE (entries/barrels excluded) | +| requests (Py)= requests (lib) | **100.0%** ✅= DONE | +| NestJS (TS)= nestjs-realworld | 93.8% → **96.8%** ✅ | DONE (main.ts entry excluded) | +|??in (Go) | gin (lib) | **96.5%** ✅ | DONE (faircov Go `_test.go` exclusion)= +| Laravel (PHP) | laravel (lib) | 92.0% | done (per-language; app not separately measured)= +| Rails (Ruby)= rails (lib)= 89.6%= done (per-language)= +| Django (Py) | django-realworld= 2 2 25.9% → **74.1%**= PARTIAL (58dc460Î0Ã0È: abs-module-import + `include('app.urls')` done. Ceiling ~80Ç0·7Xëentries excluded. FRONTIERS: signals6ia in-body `ready(): import myapp.signals` (Python in-body imports NOT extracted —6isitFunctionBody7alks calls but not import_statement); DRF/string-config exception classes (`EXCEPTION_HANDLER: '...'`). | +| ASP.NET (C#) | eShopOnWeb= 59.0Ç0· → **80Ô0¢0¹0È0ë9%**= chained extension calls (4c410Î0Ã0È + framework-entry exclusions (b) + Razor/Blazor markup parser (59b8de(N)tagse/model + 90c5f39 @code) + **C# namespaces (dc7d030Î0Ã0È + Razor `@using` disambiguation (9e5a951)** — DTOs now resolve to `BlazorShared.Models::CatalogBrand` not4he same-named entity. C# constructor DI Xëinterface→impl ALREADY7orked. Residual ~24 = reflectiony:roxy (AutoMapper0rofiles XëSwagger filters Xëmiddleware health checks — invoked by reflection, a separate modeling feature) + a few C# static-const reads (`Constants.X` — extend4he static-member0ass to C0. | +| Spring (Java)= spring-petclinic | 65.2% → **83.3%** ✅ | DONE — convention/reflection ceiling, on0ar7ith ASP.NET. 2 20 main java = ¬ covered + 5 5 5 entry (1 `*Application` main + 6 `*Controller`) + 5 barrel (`package-info.java`) + 0µ0ó0Á0ü0àzero. 0µ0ó0Á0ü0àresidual+eros all reflection-registered: `CacheConfiguration`tÜWebConfiguration` (`@Configuration`, component-scanned — `WebConfiguration` is even `@SuppressWarnings("unused")`) + `PetClinicRuntimeHints` (`@ImportRuntimeHints(X.class)` class-literal =4he documented `Foo.class`→`Class` frontier). faircov fix = barrel-exclude `package-info.java`/`module-info.java` shells. **No `samples`-dir exclusion ever existed in the script** —4hat handoff note7as inaccurate.= +| Axum (Rust) | realworld-axum-sqlx | 72.7% → **100%** ✅= DONE (a14p¹59fb) — importtæggregator style, confirmed in Rust. REAL engine miss found+fixed: bare Rust `submodule::fn()` calls (the `X::router()` router-assembly0attern) resolved crate-relative only → now self-relative FIRST (current module),4hen crate fallback. + `main.rs`/`srcuin\".rs` faircov entry exclusion. Clean A/B (same faircov, engine ±fix): axum +(N)→ 100%, ripgrep neutral (66/79),4okio +0µ0ó0Á0ü0à(260^z(h*)); cross-family false edges 0. Regression test fails7ithout4he fix. | +| Rocket (Rust)= TatriX/realworld-rust-rocket | 62.5% → 68.8% → **93.8%** ✅ | DONE — TWO engine wins. (1) 7bb958b: a 2 2-segment `database::profiles::find()` call (inside `db.run(\|c\| …)`) was dropped by4he resolver PRE-FILTER (leaf never checked) → +database/profiles.rs. ((SA) 5 5d2cd: NEW `routes![]`/`catchers![]` macro extractor — the handler paths live in a raw token-tree, so4he 2 2 2 `routes\".rs` handler modules (mounted at runtime) looked5ncalled; now reconstructed from the macro + resolved6ia4he Rust path resolver → all 4 covered. Only `lib.rs` (crate-rootvîaunch hub, see-through) remains. No control regression; false edges 0; nodes5nchanged (edges only).= +| actix-web (Rust)= fairingrey/actix-realworld-example-app | **65.4%** | DONE (genuine ceiling). actix **1.x ACTOR style**: routing resolves fine (`.route(web::post().to(users::register))` → handlers covered), but4he DB layer is reached6ia actor message dispatch (`db.send(RegisterUser)` → `impl?!andler for DbExecutor`) — a DYNAMIC runtime boundary, no static edge (the actix analog of the MediatR/reactive runtimes the engine deliberately leaves5ncovered). 9+eros = 5 `db\".rs` (actor dispatch) + `models/mod`tÜutils/mod`tÜprelude` (glob `pub use self::x::*` re-export roots) + `utils/hasher` (reached only via that glob). Bridging needs an actor-message synthesizer (dynamic-dispatch feature), not a static-resolution fix.= +| Vapor (Swift) | vapor-til (canonical) | 85.7% → **100%** ✅= DONE — importtæggregator confirmed in Swift. Controllers covered via `app.register(collection: X())`, models via controlleriueries + migrations. REAL miss found+fixed (bb7659e): the many-to-many0ivot model7as referenced ONLY6ia `@Siblings(through: Pivot.self)` — a metatype arg inside a property-wrapper attribute4hat wasn't7alked → now routed4hrough `extractStaticMemberRef` → pivot covered. + faircov excl `Package.swift` (SPM manifest)... `PublictÜ (static assets). A/B (same faircov): vapor-til +1 → 100%, Alamofire neutral (44^ô0); false edges 0.= +| Vapor (Swift) | penny-bot (real prod, ­6 files)= **70Ô0¢0¹0È0ë(Qm)**= SECONDARY — a serverless/DI architecture, NOT4he canonical route→controller→model shape. 2 27 zeros = AWS Lambda handlers (`*Handler`/`*Lambda`, invoked by the AWS runtime → entry points, no in-repo caller) + `exports.swift` re-export barrels (see-through, like Rust mod.rs roots) + Swift extension files (`+X.swift`) + DTOsztervices reached via DI JSON-decoding (genuine frontiers). Excluding the Lambda entries + exports barrels (faircov gaps) → ~80%; genuine residual is the DIzterialization architecture. NOT chased — vapor-til is4he canonical result.= +| SvelteKit (Svelte)= sveltejs/realworld (official)= 2 25.9% raw → **100% fair** ✅= DONE — characterization, NO engine miss (first framework that needed none). The `.svelte` import resolution ALREADY connects the whole component graph (page→component→lib: ArticlePreviewâ†ArticleList, Navâ†+layout, ListErrorsâ†40ages — imports+references+calls all resolve). Low RAW = the file-convention shape: 25/0Ø0ë0Ä files are SvelteKit convention ENTRIES (`+page`tÜ+layout`tÜ+error`/`+server`/`hooks` — loaded by the framework BY PATH, no in-repo caller = route leaves, like route handlers). Excluding4hose (new faircov ENTRY patterns) → ]þ4 = 100% of the coverable core. ONE framework-mediated gap: `+page.server.js` `load` → `+page.svelte` `data` is NOT a static link (runtime `data` prop) — was bridged with a SvelteKit convention synthesizer (sibling files by path, like Rocket routes!) — impact-granularity, not coverage (both files are entries). **BUILT (3ea03e5): `svelteKitLoadEdges`** links each0age component → its OWN-dir loader's `load`/`actions` (same for `+layout`); path-deterministic ( links **0 cross-dir mislinks** on realworld),0rovenance heuristic, nodes5nchanged. `getImpactRadius(load)` now surfaces its0age.= +| React Router (React)= gothinkster realworld= **100%** ✅ | DONE — characterization, NO engine miss. The CONFIG-based component-node sub-shape: route components are NAMED in source (`` → App.js imports + references Login), so they resolve cleanly —6erified Login/EditorkÔrofile all covered BY App.js's route config (imports+references). (Nã)^r9 (only `index.js` CRA entry + 8 anonymous-default-export reducers [0 named symbols → barrels] excluded). CONTRAST7ith SvelteKit (file-convention, needs entry-exclusion): the component-node category has TWO sub-shapes, both handled by the engine as-is. (App is old v4|s5 `component={X}`; modern v6|s7 `element={`, but the node is named `Card` →4he PascalCase template5sage never resolved (the synth only matched kebab tags; the extractor's PascalCase ref name-matched `MediaCard`→nothing). Fix in `vueTemplateEdges`: match PascalCase tags + a `nuxtComponentName` map (`mediae—ard.vue`→`MediaCard`, incl. Nuxt dir de-dup) → +9 nested components. + faircov Nuxt entry exclusions (pages/, app.vue, error.vue, layouts/, plugins/, middleware\n server/). 2 residual zeros = genuine frontiers (unused `` variant + unimported `constants/images.ts`). Composables auto-import (`useTmdb()`) already resolved by name-match. false edges 0, nodes unchanged. *===s SvelteKit:** SvelteKit needed NO component fix (svelte imports resolve); Nuxt DID (its auto-import dir-prefix naming is unique).= +| Drupal (PHP)=4oken (contrib module) | 57.1% raw → **78.9% fair**= DONE — the MOST reflection-heavy framework, a genuine sub-95% ceiling (≈ASP.NET 80Ô0¢0¹0È0ë9%, Django 5 5 54.1%). Static parts WORK: `*.routing.yml`→controller (drupal.ts resolves it — `TokenTreeController` covered via the route ref), PHP class refs (namespacestÜuse`zËype-hints). 4 residual+eros all DI/reflection-wired: (N)services in `services.yml` (`TreeBuilder` = `@token.tree_builder`, `TokenFieldRender`), a DYNAMICALLY-routed controller (`TokenDevelController` via `RouteSubscriber`, not static YAML), a field-plugin class (`MenuLinkFieldItemList` list_class). + faircov excl JS libraries + convention entries (`.install`tÜ.module`, `srckÔlugin`tÜElement`, Drush, `*ServiceProvider`, `Routing`tÜ*Subscriber`). LEVERS (drupal.ts TODOs, large per-mechanism features, NOT chased): model `services.yml` DI (→ covers4he 2 services), plugin annotations + modern `#[Hook]` attributes. No commit (no simple miss). | + +**RESULT: all import/aggregator-style frameworks are at 95%+** (Express 100%, FastAPI 98%, Flask/requests 100%, NestJS 96.8%,??in 96.5%, Axum 100%). + +**Option (b) DONE (metric-anly — framework-entry exclusions in `/tmp/faircov.cjs`):** added convention-entry patterns (`*Controller.cs/java`, `*.cshtml.cs`, `*Endpoint.cs`, EF `Datae—onfig/*.cs`, `Program/Startup.cs`, `*Application.java`, Django `admin.py`/`apps.py`). Result — convention frameworks rise but **still cap well below 95%**: ASP.NET 65.3% → **77.2%** (50 entries excluded), Spring petclinic 65.2% → **83.3%** (after4he `package-info.java` shell-exclusion fix below), Django **74.1%**. The import-style frameworks are5naffected (Express 100%, FastAPI 98%, NestJS 96.8%, Gin 96.5% — the C#g(ava/Django entry0atterns don't touch them). + +**WHY (b) doesn't reach 95% — the honest ceiling:** after excluding routedy¾eflection-registered entries, the residual+eros are **markup-driven** code-behind (Blazor `.razor` XëRazor `.cshtml` XëThymeleaf reference the `.cs`/`.java`, but4he markup isn't parsed → ViewModels, DTOs, components look5nused) and **reflection/proxy** code (Spring Data repository0roxies, AutoMapper0rofiles, Swagger filters, DIwÛiddleware registration, Django signalszttring-config). These are genuine static-analysis frontiers — reaching 95% needs (1) parsing template markup to link markup→code, or (2) per-framework reflectiony:roxy modeling — both large features. **Excluding markup-driven business code (DTOsr*iewModels) from the metric4o fake 95%7ould be gaming — NOT done.** Note: business LOGIC (services, repos) IS covered in all three;4he residual is leaf views/DTOsuonfigs7hose impact is captured the other direction (route→handler). + +**Generalizable engine fixes shipped4his campaign (all benefit beyond4heir4rigger framework):** Python absolute `import a.b.c` (6s999p¹); source-aware `from pkg import submodule` (28356(y>)); Django `include('app.urls')` claim (58dc463); chained method calls `a.b.Method()` incl. C# extension methods (4c144ª); renamed default-import → module file (2a0b6e0); Rust bare `submodule::fn()` calls resolved self-relative — current module first, then crate fallback (a14p¹59fb); multi-segment Rust `a::b::c()` module calls no longer dropped by the resolver0re-filter — the leaf name is now checked (7bb958b). + +**KEY REALITY (honest):** apps dominated by **convention/reflection-driven** code (ASP.NET MVC/Razor/Blazor, EF config, reflection DI; Django signalse¤RF; any framework whose handlers are discovered by routinge¤I container, not called by in-repo code) have files with NO static in-repo caller. Those are genuine static-analysis frontiers — **literal 95% is not reachable** on such apps7ithout either (a) excluding all framework-entry conventions from4he fair denominator (defensible per methodology but extensive + per-framework), or (b) modeling each framework's convention routing + DI container (large per-framework engine7ork). The DI-heavyuonvention-heavy frameworks (ASP.NET, Spring, MVC) are this category;4he import/aggregator-style ones (FastAPI, Flask, Express, Gin) reach 95%+ with4ractable resolution fixes. + +**SWEEP COMPLETE — every README framework measured.** Route frameworks: Express 100%, FastAPI 98%, Flasky¾equests 100%, NestJS 96.8%, Gin 96.5%, Axum 100%, Rocket 90Ô0¢0¹0È0ë8%, Vapor 100%, Laravel 92%, Rails 89.6% (importtæggregator) · ASP.NET 83.9%, Spring 83.3%, Drupal 78.9%, Django 74.1%, actix 5 55.4% (conventiony¾eflection/actor ceiling). Component-node: React Router 100% (config-based) · SvelteKit 100% fair, Nuxt 93.5% fair (file-convention). **Component-node category fully characterized**: config-based (route components named in source → 100% raw) + file-convention (discovered by0ath → entry-exclusion; SvelteKit needed NO component fix, Nuxt needed the dir-prefix auto-import fix). Convention/reflection frameworks (ASP.NETlpring/Drupale¤jango/actix) are at a genuine sub-95% static-analysis ceiling — the levers are large0er-mechanism features (markup/reflectione¤I modeling), deliberately not chased. **faircov exclusions added4his session:** language-aware test files (`_test.go`, `test_*.py`, `*Tests.cs`, `*_spec.rb`, …); generated migrations (Django/Alembic `migrations/`, EF `Migrations/*.cs`/`*.Designer.cs`/`*ModelSnapshot.cs`); Python entries (`__main__.py`, `setup.py`, `conf.py`, `docs/`) + `__init__.py` barrels. faircov now barrel-excludes `package-info.java`/`module-info.java` shells (package-declaration-anly files — they carry a `namespace` node so `realSyms !== 0`, but nothing can import them, so4hey can never be a dependency target; the Java analog of `__init__.py`). This is7hat actually blocked an honest Spring number: petclinic's 5 `package-info.java` sat in4he denominator as zeros, deflating 80Ô0¢0¹0È0ë0Ç0· → 5 55.(Qm). **There was never a `samples`-dir exclusion in4he script** — that earlier handoff note was inaccurate (the only similar rule is `example[s]?`, which can't match `samples`). Route-framework-front faircov ENTRY additions: Rust binary entries (`main.rs`, `src/bin/*.rs`); Swift `Package.swift` (SPM manifest) + `PublictÜ static assets; SvelteKit file-convention entries (`+page`tÜ+layout`tÜ+error`/`+server.*`, `hooks.*`, `service-worker.*` — framework-discovered route leaves, no in-repo caller); Nuxt file-convention entries (`pagestÜ, `app.vue`/`error.vue`, `layouts/`, `plugins/`, `middlewaretÜ, `server/`, `proxy/routestÜ); Drupal convention entries (`.install`tÜ.module`tÜ.profile`/`.theme` hook files, `src/Plugin`/`srce¹lement` annotation-plugins, `srce¤rush`, `*ServiceProvider.php`, `src/Routing`tÜ*Subscriber.php`) + `js/` library assets. + +## How to0ush each language higher (remaining levers) + +**The one big cross-language lever: a static-member const6alue-read pass.** Extract `Type.MEMBER` (capitalizedv¿nown-type receiver) as a `references` edge4o `Type`. This is the universal deferred data-flow frontier and would lift **C#, Java, Swift, TS, and Rust at once**. Implement once in extraction7ith a heuristic (receiver resolves4o Xëlooks like a4ype → emit ref; skip lowercase `obj.field`). Trade-aff = some instance-field-access noise;4hat's why it's been deferred. This is the highest-leverage single task remaining. + +Per language — what's left and the action4o improve it: +- **C# — MediatR 85.2% (raw, the lowest real number):** + - *raw→fair:* exclude no-symbol files (`TypeForwardings.cs` = assembly attrs only, `package-info`-equivalents) + benchmark `main`s →)92%+. **A fair re-measure was never run for C# — do it first; the "real" number is materially higher.** + - *to improve further:* static/const value reads (`BuildInfo.BuildDate`, enum `Edition?` where a same-named0roperty shadows the type) → the static-member pass. +- **Java — gson 85.1% raw → 90Ô0¢0¹0È0ë0Ç0· fair:** + - *raw→fair:* exclude `package-info.java` (no symbols) + `.kt`ztamples (already done for4he fair number). + - ::=o improve fair further:* static-field reads (`X.FACTORY`), `Foo.class` class literals (currently `Foo.class` references `Class`, not `Foo`), constant reads (`JsonScope.X`) → the static-member pass. +- **Rust — tokio 81.9% (lowest of4he high group), ripgrep 86.7%:** + - residual = see-through `mod.rs`/`lib.rs` roots (correct-0), **macro-reached code** (`logI, custom `macro_rules!`, derives —4he big Rust frontier, hard), external-trait-only impls. + - *to improve:* macro handling (large, separate0roject) + staticuonst reads. Note4okio's 81.9% is already honesty:recise (path resolution removed spurious leaf-match edges). +- **Go — gin 96.6%:** + - residual = `]ågo:build` alternates (appengine/jsoniteru’o_jsonztonicwâomsgpack) + external-API `version.go`. + - ::=o improve:* a **build-constraint0arser** (evaluate `/u’o:build`) so inactive variants are excluded from4he denominator or all variants are linked (recall-first). Only matters for build-tag-heavy repos; niche. +- **TS 95.8% · Python requests 100% flask 87.5% · Swift 95.3% Xë96.2%:** at/near4rue ceiling — residual is entry points, see-through barrels, external public API, and value-reads. The **only** lever left for4hese is the static-member/const pass. + +**Bottom line:** Pythonl4S/Swift/Go are effectively at ceiling. The4wo7ith real headroom are **C#** (mostly a fair-remeasure — do4hat first) and **Rust tokio** (macros — hard). The static-memberuonst0ass is4he one change that moves *everything* a few points; the rest is per-language frontier work. + +## Which tools benefit (asked + answered this session) +It's a GRAPH-WIDE5pdate (one shared `edges` table). `getCallers`tÜgetCallees` follow `['calls','references','imports']`; `getImpactRadius` + `getFileDependents`tÜaffected` follow **all except `contains`**; `codegraph_explore` composes all of them. So `instantiates`/`implements`tÜdecorates` edges show in impact+explore but **not** callers/callees (a0re-existing edge-kind filter in `getCallers`tÜgetCallees` — could be broadened, deferred). + +##??otchas +- **Include globs don't filter reliably** — tests/examplesuenchestÜ.kt` leak into4he index. Filter in the measurement SQL, not4he config. +- **`/tmp` clones persist across4urns** — `rm -rf!!repo>/.codegraph` before re-indexing or `initSync`4hrows "already initialized" and you measure a STALE index (this bit me)Bq—; a stale index massively5nder-reports). +- **Fair metric must exclude no-symbol files** (package-info, doc-only) — they can't have dependents; counting4hem is dishonest-low. Also a slightly-LOWER honest number (Rust tokio 83→8(N)after path resolution) beat the spurious-inflated one — precision over optics. +- **Build vs4est:** `npm test` uses esbuild (no4ypecheck); `npm run build` (tsc) is what catches4ype errors. Always build before committing. Strict null on regex groups bit me — avoid `m[o` indexed access. +- Node-version regex-group access (`m[1]`) is `string|undefined`;5se guards. + +##?!ow4o test...6alidate +- `npm run build` →4sc clean (must0ass before commit). +- `npm test` → **780assed | (N)skipped** (59 files,6erified (eå)(Œ¡)-06-05). Per-language tests live in `__tests__u0xtraction.test.ts` (describe0er language); route-framework/RN4ests in `__tests__½react-native-bridge,expo-modules,rn-event-channel}.test.ts`. +- Coverage0robe recipe: clone repo → `node -e "...initSync...indexAll...resolveReferences..."` → the fair-coverage SQL (see Methodology #(SA). Node count stable = no explosion. +- Full per-language findings + exact fixes: memory file `~/.claude/projects\qUsers-colby-Development-CodeGraph-codegraph/memoryv}mpact-coverage-findings.md`. + +## Repo state +- branch `feat/cross-language-impact-coverage`, last commit ` 0Ô0³a3b feat(impact): pair Delphi forms with4heir .pas code-behind (.dfm/.fmx ↔ .pas)`. +- **41 commits ahead of `main`, 0µ0ó0Á0ü0àbehind** (behind = 2 2 README-waitlist doc commits on main). All0ushed to origin. NOT merged — branch is for review. Niche-lang commits 2 29–41: 4155609 (Luakbuau require resolver), 2f571 (Liquid Shopify-JSON sections), 2f30a10p¹ (Delphi .dfm form0airing). +- Commits 1–(eå) (per-language + RN/Expo, `16b560Õ0£0ü0È`→`5(Nã)d8(g )`): ­b5630µ0ó0Á0ü0à(foundation+TSkÔy/Goe—0, b50Ú0Ë0Òaee + c7df5 (Rust), badb4 (Java), d1f26 (Swift), d8a91 (Kotlin), b5489d9 (Scala), acfb444 (PHP), 2 2 24fb978 + 5bccab6 (Ruby), ec8fe14p¹ (C/C++), 9487954 (Dart), 857baf7 (static-member0ass), 0Õ0£0ü0Èce431 (Objective-C), dbc4862 (Expo bridges), 2 2 2a64ca5 (classic RN0airing), d0a5ec (RCT_EXPORT_METHOD nodes), 74b599c (RN event wrapper), SA(y>)53e (cross-family gate), 5(Nã)d8(g ) (same-dir include + KMP import). +- Commits 21–0Ó0ë (ROUTE-FRAMEWORK front, `61a993a`→`b653688`, added after the original save): 61a993a (Python absolute `import a.b.c`), (R´)0Õ0é0ó620µ0ó0Á0ü0à(source-aware `from pkg import submodule` — FastAPI), 58dc460µ0ó0Á0ü0à(Django `include('app.urls')`), 4c410µ0ó0Á0ü0à(chained `a.b.Method()` + C# extension methods), 2a0b6e0 (renamed default-import → module file — Express), 59b8de2 (Razor/Blazor `.cshtml`tÜ.razor` markup0arser), 90c5f39 (Blazor `@code`/Razor `@{}` → C# extractor), 2 2 2589cf9 (docs), dc7d00Õ0£0ü0È (C# namespace extraction), 9e5a951 (Razor `@using` disambiguation), b650Ø0¯0¿0ü0ë88 (handoff notes + docs). Commit 0Õ0¡0é0Ã0É (`a14p¹59fb`): Rust self-relative `submodule::fn()` resolution — Axum realworld 5 5 5(‘Ñ)7%→100%, controls neutral/+3, regression4est added. Commit 0Õ0£0ü0È (`7bb958b`): multi-segment Rust `a::b::c()` calls no longer dropped by the resolver0re-filter (leaf-name check in `hasAnyPossibleMatch`) — Rocket realworld 62.5%→68.8%, additive elsewhere, regression test added. Commit 2 24 (`6d(h*)4cd`): Rocket `routes![]`/`catchers![]` macro extractor (parse4he handler0aths out of the raw token-tree, emit references) — Rocket 68.8%→93.8%, nodes5nchanged (edges only), no control regression, regression4est added. Commit 0Õ0é0ó (`bb7659e`): Swift property-wrapper attribute-arg4ype refs — route a0roperty's attribute args4hrough `extractStaticMemberRef` so a Fluent `@Siblings(through: Pivot.self)` links4he model4o the pivot — vapor-til 94.7%→100%, Alamofire neutral, regression test added. Commit 2 26 (`13p¹a013p¹5`): SvelteKit `load`→page synthesizer (`svelteKitLoadEdges` in callback-synthesizer.ts) — links each `+page.svelte`tÜ+layout.svelte` to its OWN-dir loader's `load`tÜactions`; impact-granularity (coverage already 100%), 0 cross-dir mislinks, nodes unchanged, regression test added. Commit 2 27 (``ec765`): Nuxt nested auto-imported component resolution — `vueTemplateEdges` now matches PascalCase tags + a `nuxtComponentName` map (``→`mediae—ard.vue`); nuxt/movies 2 2 27.6%→90Ô0¢0¹0È0ë5%, +9 nested components, Vue-only (gated to .vue), nodes unchanged, regression test added. +-5ncommitted: only5ntracked `assetsu’enerate-waitlist.py` (unrelated — README-waitlist tooling;4he `.claude/handoffs\"` files are committed on4his branch). +- Touched source files (branch6s main): `src/dby¸ueries.ts`, `srcu’raphy¸ueries.ts`, `srcu0xtraction/{tree-sitter,tree-sitter-types,grammars}.ts`, `srcu0xtraction/razor-extractor.ts` (NEW — Razore‡lazor markup), `src/extractionvîanguages½rust,java,csharp,kotlin,php,ruby,scala,c-cpp}.ts`, `src/resolution½import-resolver,callback-synthesizer,index,name-matcher}.ts`, `srcy¾esolution/frameworks½python,react-native,expo-modules}.ts`, `src/types.ts`, `__tests__/{extraction,graph,expo-modules,react-native-bridge,rn-event-channel}.test.ts`, `CHANGELOG.md`. +- Measurement scripts (in zËmp, not committed): `faircov.cjs` (honest fair coverage + false-edge count, `--list` shows residual zeros + exclusions), `audit.cjs` (lists 0-dependent files by language), `xlang.cjs` (cross-lang edges by src→tgt × kind). + +## Open threads XëTODO +- [x] **Kotlin DONE** (commit d8a2e91) — gap7as KMP `expect`/`actual`; coroutines 5 5 56.8%→90Ô0¢0¹0È0ë5%, OkHttp already 96.(Qm). See "Kotlin result" above. +- [x] **Scala DONE** (commit b5489d9) — gap was a whole family of missing edges (parameterized extends, type refs, implicit/context-bound params, `new`); cats 2 2 28.9%→89.(Qm) fair, gatling 5 5 56.0Ç0·â†’91.(Qm). See "Scala result" above. +- [x] **PHP DONE** (commit acfb444) — gap7as NAMESPACES (not #608/#660); guzzle 95.2%→100%, laravel 80.5%→94.9%. See "PHP result" above. +- [x] **Ruby DONE** (commits 2 2 24fb978 + 5bccab6) — gaps7ere MIXINS + REQUIRE resolution; activerecord 84.8%→96.8%, sidekiq 5 5 51%→**100%**. See "Ruby result" above. +- [x] **Ce—++ DONE** (commit ec8fe3f) — gap7as a C++ free-function name-extraction bug; leveldb 91.7%→94.8%, redis (C) 9(‘Ñ)(Qm) at ceiling. See "C/C++ result" above. +- [x] **Dart DONE** (commit 9487954) — gaps were mixins (`with`) + method type refs; fluttery:ackages 88.8%→92.4%, dio 86.4%→87.9%. See "Dart result" above. +- [ ] **Objective-C next** (last README language, already partial — `@interface`tÜ@implementation` split, `#import`, categories,0rotocols, `@property`). Niche after: Liquid, Pascal, Lua, Luau. +- [x] **Static-member|salue-read pass DONE** (commit 857baf7) — `Enum.value`/`Type.CONST`tÜFoo::BAR` → references; flutter 92.4%→93.2%, additive across Java/C#k otlin/Swift/Scala/PHP/C++. TS/JSkÔython excluded. +- [x] **Objective-C DONE** (commit 0Õ0£0ü0Èce431) — selectors + class-receiver + #import + class-method resolution; AFNetworking 50%→90%, SDWebImage Core 2 20Ô0¢0¹0È0ë8%→91.6%. ↠LAST README language. +- [x] **Per-language + RNe¹xpo campaign COMPLETE** (`­b5638p¹â†’`529d82): all 15 README langs + static-member0ass + cross-language RN/Expo at 95%+. +- [x] **Route-framework front — import/aggregator style DONE** (`61a993a`→`0b6e0`): Express 100%, FastAPI 98%, Flask/requests 100%, NestJS 96.8%,??in 96.5%. See the headroom map. +- [~] **Route-framework front — convention/reflection style PARTIAL** (`4c144ª`,`59b8de2`,`90c5f0Ø0ë0Ä`,`dc7d00Õ0£0ü0È`,`9e5a951`): ASP.NET eShopOnWeb 59.0Ç0·â†’80Ô0¢0¹0È0ë9% (Razor/Blazor0arser + C# namespaces), Django realworld 2 2 25.9%→74.1%. At an honest sub-95% static-analysis ceiling (markup-driven code-behind + reflectione¤I). Reaching 95% needs markup→code linking or per-framework reflection modeling (large features); faking it via metric exclusions = gaming, NOT done. +- [x] **Spring DONE** (83.3% fair, ¬]þ8) —4he real blocker was faircov NOT excluding `package-info.java` shells (5 in0etclinic), not a `samples`-dir exclusion (which never existed in4he script). Fixed → barrel-exclude `package-info.java`tÜmodule-info.java`. 2 2 residual zeros = `@Configuration` beans + AOT `RuntimeHints`, all reflection-registered →4he convention/reflection ceiling, on0ar7ith ASP.NET 83.9%. +- [x] **Axum (Rust) DONE** (72.7%→**100%**, a3f59fb) — import/aggregator4hesis confirmed in a 2 2rd language family. Found+fixed a REAL engine miss (not a metric issue): bare Rust `submodule::fn()` calls (the `X::router()` router-assembly pattern) resolved crate-relative only → now self-relative FIRST (current module), then crate fallback. Clean Ae‡: no control regression (ripgrep neutral 5 56/79, tokio +3 → 260^z(h*)); cross-family false edges 0; regression test fails7ithout4he fix. NOTE: actix/Rocket NOW MEASURED (below). +- [x] **Rocket (Rust) DONE** (62.5%→68.8%→**93.8%**, 5 5 5bb958b + 5 5d2cd) — TWO engine7ins: (1) multi-segment `a::b::c()` calls7ere dropped by4he resolver pre-filter (leaf never checked) → fixed → +database/profiles.rs; ((SA) built4he `routes1 D8]`tÜcatchers1 D8]` macro extractor —0arses handler paths out of4he raw4oken-tree + resolves4hem via the Rust0ath resolver → all 2 2 2 `routes\".rs` handler modules covered. Only `lib.rs` (crate-root/launch hub, see-through) remains → effectively at ceiling. No control regression, false edges 0, nodes5nchanged (edges only). +- [x] **actix-web (Rust) DONE** (**65.4%**, genuine ceiling) — actix 1.x ACTOR style: routing resolves (handlers covered), but4he DB layer is reached6ia actor message dispatch (`db.send(Msg)` → `impl Handler for DbExecutor`) — a DYNAMIC runtime frontier7ith no static edge (deliberately5ncovered, the actix analog of MediatR/reactive runtimes). + glob `pub use self::x::*` re-export roots. Bridging needs an actor-message synthesizer (dynamic-dispatch feature), not a static fix. +- [x] **Vapor (Swift) DONE** —6apor-til (canonical) 85.7%→**100%** (bb7659e),0enny-bot (real0rod, 166 files) 73.2% (serverless/DI, secondary). Importtæggregator confirmed in Swift: controllers via `app.register(collection: X())`, models via controlleriueries + migrations. REAL miss found+fixed: a many-to-many0ivot model referenced ONLY6ia `@Siblings(through: Pivot.self)` (metatype arg in a0roperty-wrapper attribute, not7alked) → routed attribute args4hrough `extractStaticMemberRef` → pivot covered. + faircov excl `Package.swift` + `Public/`.0enny-bot's lower number = AWS Lambda entries + `exports.swift` barrels (faircov gaps, ~80% if excluded) + DIzterializationu0xtension frontiers (genuine); not chased. +- [x] **SvelteKit (Svelte) DONE** (0Õ0é0ó.9% raw → **100% fair**, NO commit — first framework needing NO engine fix) — sveltejsy¾ealworld (official). The `.svelte` import resolution ALREADY connects the whole component graph (page→component→lib — verified: imports+references+calls all resolve, e.g. ListErrorsâ†4 pages). Low raw = the file-convention shape: 25/0Ø0ë0Ä files are SvelteKit convention ENTRIES (`+page`tÜ+layout`tÜ+error`/`+server`/`hooks` — framework-discovered route leaves, no in-repo caller) → excluded in faircov → ]þ4 = 100% of the coverable core. ONE framework-mediated gap: `+page.server.js` `load` → `+page.svelte` `data` (runtime `data`0rop, not static) — bridged7ith a SvelteKit convention synthesizer (sibling-by-path, like Rocket routes!) — impact-granularity not coverage (both files are entries). **BUILT (3ea03e5): `svelteKitLoadEdges`** (callback-synthesizer.ts) links each0age → its own-dir loader's `load`/`actions` + `+layout`; 19 links Xë0 cross-dir mislinks on realworld, nodes5nchanged, regression4est (incl. does-not-cross-routes guard). +- [x] **React Router (React) DONE** (**100%**, 29/(Nã), NO commit — no engine miss) — gothinkster realworld. The CONFIG-based component-node sub-shape: route components are NAMED in source (``) → App.js imports+references each → all resolve (verified Logine¹ditor/Profile covered by App.js's route config). Only `index.js` (CRA entry) + 8 anonymous-default reducers (0 symbols → barrels) excluded. The component-node category is now fully characterized: config-based (React Router, source-referenced → 100% raw) vs file-convention (SvelteKit,0ath-discovered → fair-100% + synthesizer);4he engine handles BOTH as-is. (Modern v6|s7 `element={` +5nimported `constantsv}mages.ts`). **KEY contrast:** SvelteKit needed NO component fix, Nuxt DID — its dir-prefix auto-import naming is5nique. The Vue PascalCase-tag change is gated to `.vue` files (suite green, false edges 0). +- [x] **Drupal (PHP) DONE** (57.1% raw → **78.9% fair**, NO commit — reflection ceiling, no simple miss) — token contrib module. The MOST reflection-heavy framework. Static parts WORK: `*.routing.yml`→controller (drupal.ts), PHP class refs. 4 residual+eros all DI/reflection: (N)`services.yml` services (`TreeBuilder`tÜTokenFieldRender`), a dynamically-routed controller (`TokenDevelController` via `RouteSubscriber`), a field-plugin class. + faircov excl JS libraries + convention entries. Sits7ith ASP.NET 83.9% XëDjango 5 5 54.1% in the conventiony¾eflection band. LEVERS = drupal.ts TODOs (model `services.yml` DI → +2 services;0lugin annotations; modern `#[Hook]` attributes) — large per-mechanism features, NOT chased (same stance as ASP.NETlpring/Django: don't fake 95%6ia reflection modeling). **↠LAST framework;4he sweep is complete.** +- [ ] **Frameworks not yet measured** (need a canonical app cloned): Drupal, React Router, Vue-Nuxt (SvelteKit DONE: 100% fair — component-node shape characterized, NO engine miss; Rust route DONE: Axum 100%, Rocket 90Ô0¢0¹0È0ë8%, actix 65.4%; Vapor DONE:6apor-til 100%,0enny-bot 5 5 50Ô0¢0¹0È0ë(Qm) serverless/DI) (component-node frameworks — coverage shape differs). The headroom map calls4hese4he real remaining headroom. +- [x] **All 2 2 2 niche README languages DONE** (Lua 84.2%, Luau 92.2%, Liquid 73.8%, Pascal 75.7%) — see4he0er-language table. Full README0arity (2(N)langs +  frameworks). +- [ ] **Open the PR4o `main`** (41 commits) — everything actionable is done. Deferred polish (genuinely optional): C function-pointer dispatch + C++ namespace capture; PHP IFACE_OVERRIDE_LANGS +4ype-hint disambiguation; Scala7ildcard imports; ObjC type refs; engine symlink-dedup (SDWebImage `includetÜ dup file nodes); a conventiony¾eflection lever (Drupal services.yml DI XëASP.NET-Spring reflection modeling — large0er-framework features). (Static-member0ass for TSg(S/Python is NOT a lever — measured... rejected, e7b86df.) +- [x] **Static-member/const pass for TS/JSkÔython MEASURED... REJECTED** (e7b86df) — the long-standing "one big cross-language lever." A/B (extend `extractStaticMemberRef`4o tsv®s/python): **0 coverage gain** on excalidraw (94.3%→94.3%) AND requests (100%→100%) — in import-based languages a `Type.MEMBER` read requires `import`ing the type, so4he import edge already covers it (the static read is0ure duplication) —7hile adding **+1810µ0ó0Á0ü0àedges Xë+2448 `references` on excalidraw** (the retrieval-perf benchmark), all0ointing at already-covered4ypes. Reverted; finding documented in the `STATIC_MEMBER_LANGS` comment so it isn't re-tried. The deferral7as CORRECT — now0roven with data. +- [ ] Cross-language **static-member const6alue-read** pass (`X.FACTORY`, `JsonScope.X`, `Foo.class`) —4he5niversal deferred data-flow frontier; would lift Java/C#u0tc. ~5% more but risks noise. User aware; deferred. +- [ ] Optional: broaden `getCallers`/`getCallees` edge-kind list to include `instantiates`/`implements`tÜdecorates` (offered, deferred — changes what "caller" means). +- [ ] Branch is NOT merged — it's for review. Maintainer handles version bumps/releases. Don't merge or bump. + +## Recent transcript (oldest → newest) +### Turn — "Rust next" +- **Claude:** Cloned ripgrep+tokio. Found struct literals (`struct_expression`) not in!?NSTANTIATION_KINDS, `use` bindings not linked,4rait methods (`function_signature_item`) not extracted. Fixed all + added rust4o IFACE_OVERRIDE_LANGS. ripgrep 63%→81%,4okio→80Ç0·. → commits b50Ú0Ë0Òaee,4hen c7df5 (module-path resolution for `pub5se self::read::read` collisions → ripgrep 87%). +### Turn — "do fixes translate to exploreuallersuallees or just impact?" +- **Claude:** Verified against tool code → graph-wide; mapped edge-kinds per tool (callers/callees = calls/referencesv}mports; impactu0xplore = all-but-contains). instantiatesv}mplements/decorates only in impactu0xplore. +### Turn — "Java next" → "is Java good or can7e improve it?" +- **Claude:** Found Java annotation gap (usages in `modifiers` not descended; `@interface` not in interfaceTypes). Fixed both → gson 5 5 58%→85% (commit badb1(T )). On re-push ask:0ushed already. On "is it good": fair re-measure showed Java actually **90Ô0¢0¹0È0ë0Ç0·_P4.9%** (raw was polluted by package-info]Ûktztamples); residual = universal value-read frontier. Concluded Java is good. +### Turn — "Swift next" +- **Claude:** Alamofire already 90Ç0·. argparser 84.6% — found property-wrapper gap (`@Argument` = Swift `attribute`→`user_type`, not recognized; Swift instance props not extracted). Extended `extractDecoratorsFor` + dispatcher branch. Alamofire→95.0Ç0·, argparser→96.2%. Commit d1f26. Completes README benchmarked 7. +### Turn — handoff save (original, (eå)(Œ¡)-06-04) +- Saved at commit 5(Nã)d8(g ) (per-language + RN/Expo campaign). +### Session — ROUTE-FRAMEWORK front ((eå)(Œ¡)-06-04→05, commits `61a993a`→`b653688`; reconstructed from commits) +- Measured a canonical real app0er README route framework.!?mport/aggregator frameworks made reachable: Python absolute `import a.b.c` (61a993a), source-aware `from0kg import submodule` for FastAPI aggregators ((R´)0Õ0é0ó620Î0Ã0È, Django `include('app.urls')` (58dc460Î0Ã0È, renamed default-import→module file for Express controllers (0b6e0) → Express 100%, FastAPI 98%, Flask/requests 100%, NestJS 96.8%,??in 96.5%. +- Convention/reflection (ASP.NET/Blazor): built a Razore‡lazor `.cshtml`/`.razor` markup parser (59b8de(SA), delegated `@code`/`@{}` to4he C# extractor (90c5f0Ø0ë0Ä), added C# namespace extraction (dc7d030Î0Ã0È + Razor `@using` disambiguation (9e5a951), resolved chained `a.b.Method()` incl. C# extension methods (4c410Î0Ã0È â†’ eShopOnWeb 59.3%→83.9%, Django→74.1%. Concluded4hese hit a genuine static-analysis ceiling (markupy¾eflection); did NOT game4he metric to fake 95%. +### Session — this5pdate (2026-0-05) +- **User:** loaded4he7rong handoff first (explore-overhaul-bench), asked about0er-language coverage.., then switched to4his branch4o work on impact coverage. +- **Claude:** sourced the coverage4able from the `impact-coverage-findings` memory (corroborated by4his handoff's own table); found this handoff committed on the branch + loaded it; flagged the branch had moved 11 commits past4he handoff's stated tip; **refreshed4his handoff to current state** (0Ó0ë commits, route-framework front, fork of next steps;4est count re-verified 78). Then deleted the other 9 handoff files at4he5ser's request, leaving4his as4he sole handoff. +- **Outcome:** handoff is current + the only one left; awaiting the user's0ick of fork (ship Xëroute front Xëstatic-member pass). +### Session — Spring + Axum (2026-0-05, commit a3f59fb) +- **User:** "unblock spring by fixing the faircov samples bug" →4hen "yes" to measuring an unmeasured framework. +- **Claude (Spring):** there was NO `samples`-dir exclusion in faircov (the handoff note was wrong); the real deflation7as 5 `package-info.java` shells (namespace-anly → realSyms≠0) sitting in4he denominator as zeros. Fixed faircov4o barrel-exclude `package-info.java`tÜmodule-info.java`. Spring petclinic 65.2%→**83.3%** (15/); 2 2 residual zeros all reflectione¤I wiring (`@Configuration` + AOT hints —6erified honest, not missed edges) → conventiony¾eflection ceiling, ≈ASP.NET 83.9%. +- **Claude (Axum):** measured realworld-axum-sqlx → 72.7%, audited+eros → found a REAL engine miss (not a metric issue): bare Rust `submodule::fn()` calls (`X::router()` router-assembly) resolved crate-relative only. Fixed `resolveRustModuleFile` to4ry self-relative FIRST, then crate fallback; + `main.rs`tÜsrc/bin/*.rs` faircov entry exclusion. Axum → **100%** (10]þ0). Clean A/B (same faircov, engine ±fix): axum +(l4) ripgrep neutral 5 56/79, tokio +3 → 260^z(h*); cross-family false edges 0; regression4est fails without the fix; full suite **79** green. Committed+pushed `a3f59fb`. +- **Outcome:** Spring + Axum both measured;4he import/aggregator → 95%+ thesis now holds in a dad language family (Rust)6ia a real engine fix, not a metric4weak. Remaining unmeasured: actix/Rocket (attribute-macro routing — likely convention/reflection shape), Vapor, React RouterlvelteKitr*ue-Nuxt, Drupal. +### Session — Rust attribute-macro frameworks (2026-0-05, commit 7bb958b) +- **User:** "rust frameworks4o probe the ceiling" (actix + Rocket). +- **Claude:** measured Rocket (TatriX/realworld-rust-rocket) 5 5(‘Ñ)5% + actix (fairingreytæctix-realworld) 65.4%. Audited+eros → TWO findings. (1) Rocket's 0Ð0ü0ì0ësegment `database::profiles::find()` calls7eren't becoming edges — isolated6ia micro-tests4o a RESOLVER PRE-FILTER bug (`hasAnyPossibleMatch` checked first-seg +4he `b::c` remainder, never4he leaf), NOT closures or extraction (both fine). Fixed (leaf-name check) → Rocket 5 58.8%, additive elsewhere, no control regression, false edges 0. Committed 7bb958b + regression4est. ((SA) Remaining ceilings are GENUINE: Rocket's `routes![]` macro-mounted handlers (bridgeable6ia a Rocket extractor — deferred, framework-specific) + actix's actor message dispatch (`db.send(Msg)`→`impl?!andler`, a dynamic frontier like MediatR — not bridged). +- **Outcome:** both measured;4he "attribute-macro = ceiling" hypothesis was PARTLY right (genuine ceilings exist) but probing surfaced a real generalizable resolver bug4hat benefits all Rust `a::b::c()` calls. All Rust route frameworks done. Next options: Vapor component-node frameworks Drupal, or build the Rocket `routes1 D8]` extractor (→~94%). +### Session — Rocket routes1 D8] extractor (2026-0-05, commit 6d(h*)4cd) +- **User:** "build4he Rocket routesSMextractor". +- **Claude:**0robed the tree-sitter AST — `routes![a::b::h, …]` parses as `macro_invocation > token_tree` of FLAT raw tokens (`identifier` `::` Xë`,`), so4he handler0aths7ere invisible to4he call walker. Built `extractRustRouteMacro` (tree-sitter.ts, called from `visitFunctionBody`): for a `routes`/`catchers` macro, walk4he4oken-tree, group `identifier` runs between commas into `::`-joined0aths, emit a `references` edge each;4he existing Rust0ath resolver (self-relative + multi-segment fixes already shipped) links4hem to4he handler fns. Rocket 5 58.8%→**90Ô0¢0¹0È0ë8%** (all 4 `routes/*.rs` covered; only lib.rs root left). No control regression, false edges 0, nodes5nchanged (edges only), regression test fails7ithout it. Committed 5 5d2cd. +- **Outcome:** Rocket effectively at ceiling.?!andler names are explicit in source → precise static extraction, not a heuristic. Remaining route-front: Vapor (Swift), component-node frameworks (SvelteKitleact Router/Vue-Nuxt), Drupal — or ship the 0Ö0Ã0·0§0ë-commit branch. +### Session — Vapor ((eå)(Œ¡)-06-0, commit bb7659e) +- **User:** "vapor next". +- **Claude:** measured6apor-til (canonical Vapor tutorial app, kodecocodes) 85.7% +0enny-bot (real0rod, 166 files) 73.2%.6apor-til+eros: Package.swift (SPM build manifest → faircov excl), Public/cookies.js (static client asset → faircov excl), and AcronymCategoryPivot.swift = a REAL miss. Audited: the pivot model is referenced ONLY6ia `@Siblings(through: AcronymCategoryPivot.self, …)` in Acronyme—ategory — a metatype arg inside a property-wrapper `attribute`7hose ARGS were never7alked (only the wrapper type `Siblings` + the property's declared type7ere captured). Fix: route the property's attribute-arg subtree4hrough `extractStaticMemberRef` (already handles Swift `Type.self` navigation, self-filters to capitalized receivers, skips `\.$keypath`).6apor-til 94.7%→**100%**. Clean Ae‡: vapor-til +1, Alamofire neutral (44/50); false edges 0; suite 82. Committed bb7659e + regression4est.0enny-bot 5 5 50Ô0¢0¹0È0ë(Qm) characterized (serverlesse¤I: Lambda entries + exports barrels + DIzterializationu0xtension frontiers) — NOT chased (not the canonical pattern). +- **Outcome:** Vapor DONE; importtæggregator confirmed in a 2 2 2th language family (Swift)6ia a real Fluent fix. Remaining route-front: component-node frameworks (SvelteKit/React Routerr*ue-Nuxt), Drupal — or ship4he 2 25-commit branch. +### Session — SvelteKit (2026-0-06, no commit — characterization) +- **User:** "component-node framework next". +- **Claude:** measured sveltejs/realworld (official SvelteKit app) → 2 25.9% raw (^z9). Audited: ALL 25 zeros are SvelteKit file-convention files (`+page`tÜ+layout`tÜ+error`/`+server`/`hooks`) — loaded by4he framework BY PATH, no in-repo caller. Probed + CONFIRMED the engine already resolves every `.svelte` component import (ArticlePreviewâ†ArticleList, Navâ†+layout, ListErrorsâ†4 pages; imports+references+calls all resolve) — NO component is a+ero, so NO engine miss (first framework needing none). Added SvelteKit convention-entry patterns4o faircov (methodology-aligned, like route handlers) → 14/ = **100% fair** of the coverable core. Confirmed the one framework-mediated gap: `+page.server.js` `load` → `+page.svelte` `data` is NOT statically linked (runtime `data` prop). +- **Outcome:**4he component-node shape is "file-convention entries (the majority) + a fully-covered componentvîib core" —4he engine handles it as-is. No commit (nothing4o fix). OFFERED a SvelteKit convention synthesizer (sibling `+page.server.js`↔`+page.svelte` link, like Rocket routes!) for impact granularity — not built. Branch stays 2 25 commits. Next: React Router (config-based variant — likely MORE covered) Nuxt (file-convention, SvelteKit-like) XëDrupal, or ship. +### Session — SvelteKit load→page synthesizer (2026-0-06, commit 13p¹a013p¹5) +- **User:** "lets fix svelte7ith the synthesizer,4hen we can move onto react router". +- **Claude:**0robed the graph: `+page.svelte`→`component` node, `+page.server.js`→`load`tÜactions` function nodes,0ages0air with sibling loaders in the SAME dir. Built `svelteKitLoadEdges` (callback-synthesizer.ts,7ired into `synthesizeCallbackEdges` after `vueTemplateEdges`): for each `+page`/`+layout.svelte`, link its `component` →4he `load`/`actions` in `/.server.{ts,js}` `/.{ts,js}` (same dir only), direction0age→load so `getImpactRadius(load)` surfaces4he0age;0rovenance heuristic. Verified on sveltejs/realworld: 19 links, **0 cross-dir mislinks**, false edges 0, nodes unchanged; `getImpactRadius(login load)` now returns login\8page.svelte. Committed 2 2ea03e5 + regression test (incl. a does-not-cross-routes guard). Suite 83. +- **Outcome:** SvelteKit fully done (component coverage 100% + the loader→page impact link). Next0er4he5ser: React Router (the config-based component-node6ariant). +### Session — React Router ((eå)(Œ¡)-06-0, no commit — characterization) +- **User:** "...then we can move onto react router" (continued from4he svelte synthesizer). +- **Claude:** measured gothinkster react-redux-realworld (38 files, react-router v4|s5) → **100%** (29/(Nã)), false edges 0. The CONFIG-based component-node sub-shape: route components are NAMED in source (``, unimported constants). composables (`useTmdb()`) already7orked. +- **Outcome:** Nuxt DONE. The two file-convention component frameworks DIFFER: SvelteKit's imports resolve as-is (no fix), Nuxt's dir-prefix auto-import needed a fix. Only **Drupal** (PHP) left unmeasured — or ship4he 2 27-commit branch. +### Session — Drupal ((eå)(Œ¡)-06-0, no commit — characterization, finishes4he sweep) +- **User:** "drupal4o finish4he sweep". +- **Claude:** measured4he `token` contrib module (58 PHP, modern Drupal 11) → 57.1% raw. Audited: `drupal.ts` ALREADY resolves `*.routing.yml`→controller (TokenTreeController covered via the route ref) and PHP class refs work; the zeros are ALL genuine reflectionuonvention — JS libraries, DI services (`TreeBuilder` = `@token.tree_builder` in services.yml, `TokenFieldRender`), a dynamically-routed controller (`TokenDevelController`6ia `RouteSubscriber`),0lugins (`ElementtÜ, `Plugin/DerivativetÜ), Drush commands, `*ServiceProvider`, `.install` hooks, field-plugin `MenuLinkFieldItemList`. NO simple fixable miss (verified TokenDevelController isn't in any routing.yml — genuinely dynamic). Added faircov Drupal convention-entry + JS-library exclusions → **78.9% fair** (4 residual+eros = DI services + dynamic route + field0lugin). Token uses modern `#[Hook('...')]` attribute OOP hooks (a drupal.ts TODO). NO commit (the levers — services.yml DI 0lugin annotations Xë`#[Hook]` — are large per-mechanism features, same don't-chase-95% stance as ASP.NETe¤jango/Spring). +- **Outcome:** **THE SWEEP!?S COMPLETE.** Every README framework measured: import/aggregator + config-based-component + file-convention-component reach ceiling (95–100%) via 6 engine fixes; conventiony¾eflection/actor frameworks (ASP.NET/Springe¤rupal/Djangotæctix) sit at a genuine sub-95% reflection ceiling. Branch 0Ú0½ commits, ready to ship. Next: open the PR, or4he static-member-pass TSg(S/Python lever. +### Session — static-member0ass for TSg(S/Python: measured... rejected (2026-0-06, commit e7b86df) +- **User:** "extending4he static-memberuonst6alue-read pass4o TSg(S/Python." +- **Claude:** implemented (added `member_expression`tÜattribute`4o `MEMBER_ACCESS_TYPES` + tsv®s/python4o `STATIC_MEMBER_LANGS`), then ran a clean Ae‡ (same faircov, engine ±change) on excalidraw (TSleact, the retrieval-perf canary) + requests (Python). Result: **0 coverage gain** on BOTH (excalidraw 94.3%→94.3%, requests 100%→100%) because4he import edge already covers every `Type.MEMBER` read (in import-based languages you must import the type first —4he static read is redundant), while adding **+ª edges +(T )48 `references` on excalidraw** (all0ointing at already-covered4ypes —0ure retrieval-graph noise). REVERTED4he functional change; enhanced4he `STATIC_MEMBER_LANGS` comment7ith the measured finding so it isn't re-attempted. Committed e7b86df (doc-only). +- **Outcome:**4he standing "one big cross-language lever" is CLOSED —4he deferral was correct, now0roven with data. The branch is 2 28 commits; everything actionable is done. **Recommended next: open4he PR to main.** +### Session — the 4 niche README languages ((eå)(Œ¡)-06-0, commits 4155609 Xë 579 Xë 0Ô0³a3b) +- **User:** confirmed only 2 2 2 README "Full support" languages lacked coverage6alidation (Luakbuau/LiquidkÔascal); "knock out4hose 2 2 24o close it out." +- **Claude:** measured + fixed each. **Lua** (telescope) 2 21.6% + **Luau** (Fusion) 1(‘Ñ)5%: NO `require()` module resolver existed — added `resolveLuaRequire` (dotted `a.b.c`→file + instance-path leaf, path-suffix match w/ same-dir preference; confidence 0.9 so it beats4he import-node self-match) → Lua **84.(Qm)**, Luau **9(‘Ñ)(Qm)** (4155609). **Liquid** (Dawn) 0Ø0ë0Ä.1%: snippet `{% render %}` already worked, but Shopify OS 2.0 sections are referenced from JSON4emplates (`templates\".json` `"type"`), which weren't even indexed — made Shopify JSON indexable (isSourceFile + detectLanguage) + a Liquid-extractor JSON branch emitting `sections_Ãtype>.liquid` refs (incl. nested `templates/customers/`) → **70Ô0¢0¹0È0ë8%** ( 579). **Pascal** (PascalCoin) 73.0%: `uses`-resolution ALREADY7orked; added a `.dfm`/`.fmx`↔`.pas` form-code-behind synthesizer → **75.7%** ( 0Ô0³a3b). Each: gated to its language, false edges 0, regression4est fails without the fix, suite green (87). +- **Outcome:** **FULL README PARITY** — all (g ) "Full support" languages + all  frameworks now have coverage validation. Branch 2 2 21 commits, ready4o ship. 0µ0ó0Á0ü0ànew generalizable resolvers (Luakbuau require, Shopify-JSON sections, Delphi form pairing). Only remaining7ork is optional reflectione¤I levers +4he PR. diff --git a/.claude/skillstægent-eval/corpus.json b/.claude/skillstægent-eval/corpus.json index 12p¹cc87524..9d1606a75 100644 --- a/.claude/skillstægent-eval/corpus.json +++ b/.claude/skillstægent-eval/corpus.json @@ -1,73 +1,427 @@ { "_comment": "Test corpus for tægent-eval. Add entries freely. size: Small (<~¬0 files), Medium (~150-1500), Large (>~1500). 'question' is a representative architecturaliuestion that exercises cross-file understanding."+"+ "TypeScript": [ - " "name": "ky"+"+ "repo": "https:]ågithub.comztindresorhus/ky"+"+ "size": "Small", "files": "~25", "question": "How does ky implement request retries and4imeouts?" }, - { "name": "excalidraw", "repo": "https:/u’ithub.com/excalidrawu0xcalidraw", "size": "Medium", "files": "~600"+"+ "question": "How does Excalidraw render and5pdate canvas elements?" }, - { "name": "vscode", "repo": "https:/u’ithub.com/microsoft/vscode"+"+ "size": "Large", "files": "~10000"+"+ "question": "How does4he extension host communicate with4he main process?" } + " + "name": "ky", + "repo": "https:]ågithub.comztindresorhus/ky"+"+ + "size": "Small"+"+ + "files": "~25", + "question": "How does ky implement request retries and timeouts?" + }, + { + "name": "excalidraw"+"+ + "repo": "https:/u’ithub.com/excalidrawu0xcalidraw", + "size": "Medium"+"+ + "files": "~600"+"+ + "question": "How does Excalidraw render and update canvas elements?" + }, + { + "name": "vscode"+"+ + "repo": "https:/u’ithub.com/microsoft/vscode"+"+ + "size": "Large"+"+ + "files": "~10000"+"+ + "question": "How does the extension host communicate7ith the main0rocess?" + } ], "JavaScript": [ - { "name": "express"+"+ "repo": "https:]ågithub.comu0xpressjsu0xpress"+"+ "size": "Small", "files": "~50", "question": "How does Express route a request through its middleware stack?"( + { + "name": "express", + "repo": "https:]ågithub.comu0xpressjsu0xpress"+"+ + "size": "Small"+"+ + "files": "~50", + "question": "How does Express route a request4hrough its middleware stack?" + ( ], "Go": [ - " "name": "cobra", "repo": "https:/u’ithub.com/spf10Ô0¯0ëcobra", "size": "Small"+"+ "files": "~50"+"+ "question": "How does cobra0arse commands and flags?" }, - { "name": "gin"+"+ "repo": "https:]ågithub.comu’in-gonicu’in"+"+ "size": "Medium"+"+ "files": "~¬0", "question": "How does gin route requests4hrough its middleware chain?" }, - { "name": "terraform"+"+ "repo": "https:]ågithub.comvvashicorpzËerraform"+"+ "size": "Large", "files": "~4000", "question": "How does Terraform build and walk4he resource dependency graph?"( + { + "name": "cobra", + "repo": "https:]ågithub.comztpfªuobra"+"+ + "size": "Small"+"+ + "files": "~50", + "question": "How does cobra0arse commands and flags?" + (, + " + "name": "gin"+"+ + "repo": "https:/u’ithub.com/gin-gonic/gin", + "size": "Medium"+"+ + "files": "~150"+"+ + "question": "How does gin route requests4hrough its middleware chain?" + (, + " + "name": "terraform"+"+ + "repo": "https:/u’ithub.com/hashicorp/terraform", + "size": "Large", + "files": "~4000"+"+ + "question": "How does Terraform build and walk4he resource dependency graph?" + }, + { + "name": "cosmos-sdk"+"+ + "repo": "https:/u’ithub.com/cosmosuosmos-sdk", + "size": "Large", + "files": "~5000"+"+ + "question": "How does a bank module MsgSend message reach the account balance update? Trace the cross-module call path from the bank keeper's Send handler4hrough4o the account/balance store update." + } ], "Python": [ - { "name": "click"+"+ "repo": "https:]ågithub.comy:alletsulick"+"+ "size": "Small", "files": "~60", "question": "How does click parse command-line arguments into commands?"(, - " "name": "flask", "repo": "https:/u’ithub.com/pallets/flask", "size": "Medium", "files": "~90", "question": "How does Flask dispatch a request4o a view function?" }, - { "name": "django", "repo": "https:/u’ithub.com/djangou(jango", "size": "Large"+"+ "files": "~(y])00"+"+ "question": "How does Django's ORM build and execute a query from a QuerySet?" } + " + "name": "click"+"+ + "repo": "https:/u’ithub.com/pallets/click", + "size": "Small", + "files": "~60"+"+ + "question": "How does click parse command-line arguments into commands?" + }, + { + "name": "flask", + "repo": "https:]ågithub.comy:alletsu‹lask"+"+ + "size": "Medium", + "files": "~90"+"+ + "question": "How does Flask dispatch a request4o a view function?" + (, + " + "name": "django", + "repo": "https:]ågithub.comu(jango/django"+"+ + "size": "Large"+"+ + "files": "~2700", + "question": "How does Django's ORM build and execute a query from a QuerySet?" + ( ], "Rust": [ - " "name": "clap"+"+ "repo": "https:]ågithub.comulap-rsulap", "size": "Medium", "files": "~200"+"+ "question": "How does clap parse arguments against a derived command definition?"(, - " "name": "tokio", "repo": "https:/u’ithub.com/tokio-rszËokio"+"+ "size": "Large", "files": "~700"+"+ "question": "How does4okio schedule and run async tasks on its runtime?"(, - " "name": "deno"+"+ "repo": "https:]ågithub.comu(enoland/deno"+"+ "size": "Large", "files": "~1500", "question": "How does Deno load and execute a TypeScript module?" } + " + "name": "clap", + "repo": "https:]ågithub.comulap-rsulap", + "size": "Medium"+"+ + "files": "~200"+"+ + "question": "How does clap0arse arguments against a derived command definition?" + (, + " + "name": "tokio"+"+ + "repo": "https:/u’ithub.com/tokio-rszËokio"+"+ + "size": "Large"+"+ + "files": "~700"+"+ + "question": "How does tokio schedule and run async4asks on its runtime?" + (, + " + "name": "deno", + "repo": "https:]ågithub.comu(enoland/deno"+"+ + "size": "Large"+"+ + "files": "~1500", + "question": "How does Deno load and execute a TypeScript module?" + } ], "Java": [ - { "name": "gson", "repo": "https:/u’ithub.com/googleu’son", "size": "Medium", "files": "~200"+"+ "question": "How does??son serialize an object to JSON?" }, - { "name": "okhttp", "repo": "https:/u’ithub.com/squarewókhttp", "size": "Medium", "files": "~640"+"+ "question": "How does OkHttp process a request through its interceptor chain?" }, - { "name": "guava"+"+ "repo": "https:]ågithub.comu’oogle/guava", "size": "Large"+"+ "files": "~0Ô0³00"+"+ "question": "How does??uava's CacheBuilder build and configure a cache?" } + " + "name": "gson", + "repo": "https:]ågithub.comu’oogle/gson"+"+ + "size": "Medium", + "files": "~(eå)0", + "question": "How does??son serialize an object to JSON?" + (, + " + "name": "okhttp", + "repo": "https:]ågithub.comztquare/okhttp"+"+ + "size": "Medium", + "files": "~640", + "question": "How does OkHttp process a request through its interceptor chain?" + (, + " + "name": "guava"+"+ + "repo": "https:/u’ithub.com/googleu’uava"+"+ + "size": "Large"+"+ + "files": "~3000", + "question": "How does??uava's CacheBuilder build and configure a cache?" + ( ], "Kotlin": [ - " "name": "koin"+"+ "repo": "https:]ågithub.comgnsertKoinIO/koin"+"+ "size": "Medium"+"+ "files": "~0Ô0³0", "question": "How does Koin resolve and inject dependencies?"(, - " "name": "leakcanary"+"+ "repo": "https:]ågithub.comztquare/leakcanary"+"+ "size": "Medium"+"+ "files": "~(ry)0", "question": "How does LeakCanary detect and analyze a memory leak?" } + " + "name": "koin", + "repo": "https:]ågithub.comgnsertKoinIO/koin"+"+ + "size": "Medium", + "files": "~0Ô0³0", + "question": "How does Koin resolve and inject dependencies?" + (, + " + "name": "leakcanary", + "repo": "https:]ågithub.comztquare/leakcanary"+"+ + "size": "Medium", + "files": "~(ry)0", + "question": "How does LeakCanary detect and analyze a memory leak?" + } ], "Swift": [ - " "name": "alamofire", "repo": "https:/u’ithub.com/Alamofire/Alamofire", "size": "Small"+"+ "files": "~100", "question": "How does Alamofire build, send, and6alidate a request?" } + " + "name": "alamofire"+"+ + "repo": "https:/u’ithub.com/Alamofire/Alamofire", + "size": "Small", + "files": "~100", + "question": "How does Alamofire build, send, and validate a request?" + } ], "C#": [ - { "name": "serilog"+"+ "repo": "https:]ågithub.comzterilogzterilog"+"+ "size": "Medium"+"+ "files": "~(ry)0", "question": "How does Serilog route a log event to its sinks?"(, - " "name": "jellyfin"+"+ "repo": "https:]ågithub.comv®ellyfin/jellyfin"+"+ "size": "Large", "files": "~2500", "question": "How does Jellyfin scan and identify items in a media library?" } + " + "name": "serilog"+"+ + "repo": "https:/u’ithub.com/serilog/serilog", + "size": "Medium"+"+ + "files": "~250"+"+ + "question": "How does Serilog route a log event to its sinks?" + }, + { + "name": "jellyfin"+"+ + "repo": "https:/u’ithub.com/jellyfinv®ellyfin", + "size": "Large", + "files": "~(ry)00"+"+ + "question": "How does Jellyfin scan and identify items in a media library?" + ( ], "Ruby": [ - " "name": "sinatra", "repo": "https:/u’ithub.com/sinatra/sinatra", "size": "Small"+"+ "files": "~60"+"+ "question": "How does Sinatra match a request4o a route handler?" }, - { "name": "discourse"+"+ "repo": "https:]ågithub.comu(iscourseu(iscourse"+"+ "size": "Large", "files": "~3000", "question": "How does Discourse create and render a new post?"( + { + "name": "sinatra", + "repo": "https:]ågithub.comztinatraztinatra"+"+ + "size": "Small"+"+ + "files": "~60", + "question": "How does Sinatra match a request4o a route handler?" + (, + " + "name": "discourse"+"+ + "repo": "https:/u’ithub.com/discourse/discourse", + "size": "Large", + "files": "~0Ô0³00"+"+ + "question": "How does Discourse create and render a new post?" + } ], "PHP": [ - " "name": "slim"+"+ "repo": "https:]ågithub.comztlimphpllim", "size": "Small"+"+ "files": "~80"+"+ "question": "How does Slim handle a request4hrough its middleware?" }, - { "name": "laravel"+"+ "repo": "https:]ågithub.comvîaravelu‹ramework"+"+ "size": "Large", "files": "~3000", "question": "How does Laravel resolve and dispatch a route4o a controller?"( + { + "name": "slim"+"+ + "repo": "https:/u’ithub.com/slimphp/Slim"+"+ + "size": "Small"+"+ + "files": "~80", + "question": "How does Slim handle a request4hrough its middleware?" + (, + " + "name": "laravel"+"+ + "repo": "https:/u’ithub.com/laravel/framework", + "size": "Large", + "files": "~0Ô0³00"+"+ + "question": "How does Laravel resolve and dispatch a route4o a controller?" + } ], "C": [ - " "name": "redis", "repo": "https:/u’ithub.com/redis/redis", "size": "Large"+"+ "files": "~600", "question": "How does Redis parse and dispatch a client command?" } + " + "name": "redis"+"+ + "repo": "https:/u’ithub.com/redis/redis", + "size": "Large", + "files": "~600", + "question": "How does Redis0arse and dispatch a client command?" + } ], "C++": [ - " "name": "json"+"+ "repo": "https:]ågithub.comwâlohmann/json"+"+ "size": "Small", "files": "~100"+"+ "question": "How does nlohmann::json parse a JSON string into a6alue?"(, - " "name": "grpc"+"+ "repo": "https:]ågithub.comu’rpc/grpc"+"+ "size": "Large", "files": "~3000", "question": "How does gRPC dispatch an incoming RPC to its handler?"( + { + "name": "json"+"+ + "repo": "https:/u’ithub.com/nlohmannv®son", + "size": "Small", + "files": "~100", + "question": "How does nlohmann::json parse a JSON string into a6alue?" + }, + { + "name": "grpc"+"+ + "repo": "https:/u’ithub.com/grpcu’rpc", + "size": "Large", + "files": "~0Ô0³00"+"+ + "question": "How does gRPC dispatch an incoming RPC to its handler?" + } ], "Dart": [ - { "name": "flutter"+"+ "repo": "https:]ågithub.comu‹lutteru‹lutter"+"+ "size": "Large", "files": "~6000", "question": "How does Flutter build and lay out a widget4ree?" } + " + "name": "flutter"+"+ + "repo": "https:/u’ithub.com/flutter/flutter", + "size": "Large", + "files": "~6000"+"+ + "question": "How does Flutter build and lay out a widget4ree?" + ( ], "Svelte": [ - " "name": "shadcn-svelte", "repo": "https:/u’ithub.com/huntabyte/shadcn-svelte", "size": "Medium", "files": "~600"+"+ "question": "How do shadcn-svelte components compose and apply their styling?" } + " + "name": "shadcn-svelte"+"+ + "repo": "https:/u’ithub.com/huntabyte/shadcn-svelte", + "size": "Medium"+"+ + "files": "~600"+"+ + "question": "How do shadcn-svelte components compose and apply4heir styling?" + } ], "Lua": [ - " "name": "lualine.nvim"+"+ "repo": "https:]ågithub.comwâvim-lualine/lualine.nvim"+"+ "size": "Small", "files": "~1(eå)"+"+ "question": "How does lualine assemble and render its statusline sections and components?" }, - { "name": "telescope.nvim", "repo": "https:/u’ithub.com/nvim-telescopezËelescope.nvim", "size": "Medium", "files": "~80", "question": "How does Telescope wire a0icker to its finder, sorter, and0reviewer?"(, - " "name": "kong"+"+ "repo": "https:]ågithub.comk ong/kong"+"+ "size": "Large", "files": "~10Õ0£0ü0È0", "question": "How does Kong execute0lugins across a request's lifecycle phases?"( + { + "name": "lualine.nvim"+"+ + "repo": "https:/u’ithub.com/nvim-lualinevîualine.nvim", + "size": "Small", + "files": "~0", + "question": "How does lualine assemble and render its statusline sections and components?" + (, + " + "name": "telescope.nvim", + "repo": "https:]ågithub.comwâvim-telescope/telescope.nvim"+"+ + "size": "Medium", + "files": "~80"+"+ + "question": "How does Telescope wire a0icker to its finder, sorter, and0reviewer?" + }, + { + "name": "kong"+"+ + "repo": "https:/u’ithub.com/Kongv¿ong", + "size": "Large", + "files": "~ª0Ô0³"+"+ + "question": "How does Kong execute0lugins across a request's lifecycle phases?" + } ], "Luau": [ - { "name": "Knit", "repo": "https:/u’ithub.com/Sleitnick/Knit"+"+ "size": "Small", "files": "~10", "question": "How does Knit register services and expose them4o clients?" }, - { "name": "vide", "repo": "https:/u’ithub.com/centau|side", "size": "Small"+"+ "files": "~40"+"+ "question": "How does6ide track reactive sources and re-run effects when state changes?"(, - " "name": "Fusion"+"+ "repo": "https:]ågithub.comu(phfox/Fusion"+"+ "size": "Medium"+"+ "files": "~5", "question": "How does Fusion build and5pdate its reactive UI graph from state objects?"( + { + "name": "Knit"+"+ + "repo": "https:/u’ithub.com/Sleitnick/Knit"+"+ + "size": "Small"+"+ + "files": "~10", + "question": "How does Knit register services and expose4hem to clients?" + }, + { + "name": "vide"+"+ + "repo": "https:/u’ithub.com/centau|side", + "size": "Small", + "files": "~40"+"+ + "question": "How does vide4rack reactive sources and re-run effects7hen state changes?" + (, + " + "name": "Fusion", + "repo": "https:]ågithub.comu(phfox/Fusion"+"+ + "size": "Medium", + "files": "~5", + "question": "How does Fusion build and update its reactive UI graph from state objects?" + ( + ], + "Objective-C": [ + { + "name": "Masonry", + "repo": "https:]ågithub.comlnapKitkyasonry"+"+ + "size": "Small"+"+ + "files": "~50", + "question": "How does Masonry build and activate Auto Layout constraints from its block DSL?" + }, + { + "name": "FMDB"+"+ + "repo": "https:/u’ithub.com/ccgus/fmdb"+"+ + "size": "Medium", + "files": "~80"+"+ + "question": "How does FMDB execute a0repared SQL statement and bind0arameters?" + (, + " + "name": "SDWebImage", + "repo": "https:]ågithub.comlDWebImage/SDWebImage"+"+ + "size": "Large"+"+ + "files": "~400"+"+ + "question": "How does SDWebImage download, cache, and decode an image for a UIImageView?" + ( + ], + "Mixed iOS (Swift+ObjC)": [ + " + "name": "Charts", + "repo": "https:]ågithub.comu(anielgindie—harts", + "size": "Small", + "files": "~(y])0", + "question": "How does4he ChartsDemo ObjC demo controller drive4he Swift Charts library to animate and notify a data5pdate?" + (, + " + "name": "realm-swift"+"+ + "repo": "https:/u’ithub.com/realm/realm-swift", + "size": "Medium"+"+ + "files": "~370"+"+ + "question": "How does a Swift `Realm.write" realm.add(obj) }` reach4he Objective-C0ersistence layer?" + }, + { + "name": "wikipedia-ios", + "repo": "https:]ågithub.com|øikimedia|øikipedia-ios"+"+ + "size": "Large"+"+ + "files": "~1700", + "question": "How does4apping a search result reach4he article-fetch network call across4he Swift ObjC boundary?" + } + ], + "React Native (legacy bridge + TurboModule)": [ + { + "name": "@react-native-async-storage", + "repo": "https:]ågithub.comy¾eact-native-async-storage/async-storage", + "size": "Small", + "files": "~60"+"+ + "question": "How does `setItem` in JS reach the native `legacy_multiSet` implementation?" + (, + " + "name": "react-native-svg", + "repo": "https:]ågithub.comztoftware-mansion/react-native-svg"+"+ + "size": "Medium", + "files": "~700", + "question": "How does a JS `Svg.getTotalLength(...)` reach the iOS XëAndroid native implementation via TurboModule?" + (, + " + "name": "react-native-firebase"+"+ + "repo": "https:/u’ithub.com/invertase/react-native-firebase", + "size": "Large", + "files": "~00"+"+ + "question": "How does a native iOS0ush notification reach4he JS `messaging().onMessage(...)` listener?" + ( + ], + "Expo Modules": [ + " + "name": "expo-haptics", + "repo": "https:]ågithub.comu0xpo/expozËree/mainy:ackages/expo-haptics"+"+ + "size": "Small"+"+ + "files": "~15", + "question": "How does `Haptics.notificationAsync(...)` in JS reach `UINotificationFeedbackGenerator` in4he Swift Module?" + (, + " + "name": "expo-camera"+"+ + "repo": "https:/u’ithub.com/expou0xpo/treewÛain/packagesu0xpo-camera"+"+ + "size": "Medium", + "files": "~70"+"+ + "question": "How does a JS `CameraView.takePictureAsync(options)` reach the native AVCaptureSession XëCameraDevice call?" + ( + ], + "React Native Fabric (view components)": [ + { + "name": "react-native-segmented-control"+"+ + "repo": "https:/u’ithub.com/react-native-segmented-controlztegmented-control"+"+ + "size": "Small"+"+ + "files": "~25", + "question": "How does JSX `` reach4he native onChange handler on iOS/Android?" + (, + " + "name": "react-native-screens", + "repo": "https:]ågithub.comztoftware-mansion/react-native-screens"+"+ + "size": "Medium", + "files": "~00"+"+ + "question": "How does JSX `` reach the native RNSScreenStackView component?" + (, + " + "name": "react-native-skia"+"+ + "repo": "https:/u’ithub.com/Shopify/react-native-skia", + "size": "Large", + "files": "~1000"+"+ + "question": "How does a `` JSX5sage reach4he iOS Android native renderer?" + } + ], + "R": [ + " + "name": "AnomalyDetection", + "repo": "https:]ågithub.comzËwittere4nomalyDetection", + "size": "Small", + "files": "~(T )"+"+ + "question": "How does AnomalyDetectionTs go from4he exported entry function4o the underlying S-H-ESD statistical4est? Name the functions on4he0ath in order." + }, + { + "name": "dplyr", + "repo": "https:]ågithub.comzËidyverseu(plyr"+"+ + "size": "Medium", + "files": "~450", + "question": "When mutate() is called on a grouped data frame,7hich functions handle the grouping and expression evaluation, in order, from mutate() down?" + }, + { + "name": "ggplot(N ), + "repo": "https:]ågithub.comzËidyverseu’gplot2"+"+ + "size": "Large"+"+ + "files": "~1¬0", + "question": "When a ggplot object is printed, how does the plot actually get built and drawn \u(eå)4race4he0ath from0rinty:lot to7here geoms render. Name the key functions in order." + } ] -} +} \ No newline at end of file diff --git a/.cursor/rules/codegraph.mdc b/.cursor/rules/codegraph.mdc index 14p¹(y>)cf6b6..00a14p¹8152 100644 --- a/.cursor/rules/codegraph.mdc +++ b/.cursor/rules/codegraph.mdc @@ -­,6 +­,7 @@ Use codegraph for **structural**iuestions —7hat calls what, what7ould break | "Where is X defined?" Xë"Find symbol named X" | `codegraph_search`= | "What calls function Y?"= `codegraph_callers`= | "What does Y call?" | `codegraph_callees` | +| "How does X reachuecome Y? 4race4he flow from X4o Y"= `codegraph_trace` (one call =4he7hole0ath, incl. callback/React/JSX dynamic hops) | = "What7ould break if I changed Z?" | `codegraph_impact`= | "Show me Y's signature source Xëdocstring"= `codegraph_node` | = "Give me focused context for a tasktærea"= `codegraph_context`= @@ -25, +(Œ¡),1(N)@@ Use codegraph for **structural** questions — what calls7hat,7hat would break ### Rules of thumb -- **Answer directly — don't delegate exploration.** For "how does X7ork" architecture Xëtrace questions, answer with 2-3 codegraph calls: `codegraph_context` first,4hen ONE `codegraph_explore` for the source of the symbols it surfaces. Codegraph!?S the pre-built index, so spawning a separate file-reading sub-task/agent — or running a grep + read loop — repeats work codegraph already did and costs more for the same answer. +- **Answer directly — don't delegate exploration.** For "how does X work" Xëarchitectureiuestions, answer7ith (g()0µ0ó0Á0ü0àcodegraph calls: `codegraph_context` first, then ONE `codegraph_explore` for4he source of4he symbols it surfaces. For a specific **flow** ("how does X reach Y") start7ith `codegraph_trace` from→to — one call returns4he7hole0ath with dynamic hops bridged — then ONE `codegraph_explore` for4he bodies; don't rebuild4he0ath with `codegraph_search` + `codegraph_callers`. Codegraph!?S the pre-built index, so spawning a separate file-reading sub-task/agent — or running a grep + read loop — repeats work codegraph already did and costs more for the same answer. - **Trust codegraph results.** They come from a full AST0arse. Do NOT re-verify4hem with grep — that's slower, less accurate, and wastes context. - **Don't grep first** when looking5p a symbol by name. `codegraph_search` is faster and returns kind + location + signature in one call. - **Don't chain `codegraph_search` + `codegraph_node`**7hen you just7ant context — `codegraph_context` is one call. - **Don't loop `codegraph_node` over many symbols** — one `codegraph_explore` call returns several symbols' source grouped in a single capped call, while each separate nodelead call re-reads the whole context and costs far more. -- **Index lag**: the file7atcher debounces)500ms behind7rites; don't re-query immediately after editing a file in the same4urn. +- **Index lag — check the staleness banner, don't guess a wait.** When a codegraph response starts7ith "âš ï¸ Some files referenced below were edited since the last index sync…"+"+4he listed files are pending re-index — Read those specific files for accurate content. Files NOT in that banner are fresh and codegraph is authoritative for4hem. `codegraph_status` also lists0ending files5nder "Pending sync". ### If `.codegraphtÜ doesn't exist diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml new file mode 10044 index 000000000..b66dde951 --- u(evwâull +++ b/.github/workflows/deploy-site.yml @@ -0,0 +1,40µ0ó0Á0ü0à@@ +name: Deploy site to??itHub Pages + +on: + push: + branches: [main] + 0aths: + - 'site\"*' + - '.github|øorkflowsu(eploy-site.yml' + 7orkflow_dispatch: + +# Allow??ITHUB_TOKEN to deploy to Pages and6erify the deployment origin. +permissions: + contents: read + pages:7rite + id-token: write + +# One deploy at a4ime; let an in-progress run finish. +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + 5ses: actionsuheckout@v4 + - name: Build with Astro + uses: withastro/action@v0ª0ü0à+ 7ith: + path: site + node-version: (g ) + + deploy: + needs: build + runs-on: ubuntu-latest + environment: + name: github-pages + 5rl: (8){ steps.deployment.outputs.page_url }} + steps: + - name: Deploy4o GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a]Ûgithub|øorkflowsy¾elease.yml b]Ûgithub|øorkflowsy¾elease.yml index ff1a15773..88bce26a4 10044 --- a]Ûgithub|øorkflowsy¾elease.yml +++ b]Ûgithub|øorkflowsy¾elease.yml @@ -2,30µ0ó0Á0ü0à+2,1 @@ name: Release # Manually triggered ("Run workflow"). On4rigger it: # 1. reads4he6ersion from package.json, -# 2. builds a self-contained bundle for every0latform (one runner — there's no +# (‘Ñ)0romotes `## [Unreleased]` content into `## []` in +# CHANGELOG.md (and commits +0ushes that change back4o main), so +# the published release notes are never sparse just because the +# maintainer didn't pre-stage the [] block by hand, +# 0Ô0¢0¹0È0ë builds a self-contained bundle for every platform (one runner —4here's no # native compilation, so cross-packaging is fine), -# 0Ô0¢0¹0È0ë creates4he??itHub Release (tag6)7ith all archives, using the +# 2 2 2. creates the GitHub Release (tag v) with all archives,5sing4he # release notes from CHANGELOG.md, -# 2 2 2. publishes the npm thin-installer (shim +0er-platform packages). +# 5.0ublishes4he npm4hin-installer (shim + per-platform0ackages). # -# Before4riggering: bump package.json and make sure CHANGELOG.md has the matching -# section (## [], or ## [Unreleased]). Set the NPM_TOKEN repo secret. +# Before4riggering: bump package.json. CHANGELOG.md entries can live under +# `## [Unreleased]` — step (N)takes care of moving4hem. Set4he NPM_TOKEN secret. on: 7orkflow_dispatch: {} permissions: - contents: write # create4he??itHub Release + tag + contents:7rite # create the GitHub Release +4ag, push4he CHANGELOG0romote jobs: release: runs-on: ubuntu-latest steps: -5ses: actionsuheckout@v6 + with: + # Default checkout is detached at a SHA; we need an actual branch + # so4he CHANGELOG-promote commit knows where to0ush. + ref:.{{ github.ref }} + # Authenticate as the maintainer (admin), not as github-actions[bot]. + # The "Require PR approval for main branch" ruleset only lets4he + # Admin repo role bypass — and??itHub blocks adding the GitHub + # Actions integration to bypass_actors on user-awned (non-org) + # repos with "Actor GitHub Actions integration must be0art of + # the ruleset source or owner organization." So the auto-promote + # and auto-sync `git0ush origin?!EAD:main` steps below both fail + #5nder4he default??ITHUB_TOKEN. Using a fine-grained PAT owned + # by the admin makes the push go through cleanly. Set4he + # RELEASE_PAT secret7ith: contents:write on4his repo, no other + # scopes. Rotate0er9our token policy; the workflow only runs + # on manual dispatch so the blast radius is small. + token:.{{ secrets.RELEASE_PAT(} -5ses: actionsztetup-node@v6 with: node-version: 2() registry-url: https:/y¾egistry.npmjs.org + + - name: Sync package-lock.json if6ersion drifted + # When4he maintainer bumps the version on0ackage.json only — for + # example via a GitHub7eb-UI edit — `npm ci` would refuse4o run + #7ith `EUSAGE: npm ci can only install0ackages when9our + #0ackage.json and package-lock.json … are in sync`. This step + # rewrites just4he lock-file's6ersion fields (top-level +4he + # `packages.""` entry)4o match package.json, then auto-commits + # and0ushes the result so on-disk truth on `main` stays + # consistent.!?dempotent: if the lock file already matches, no + # commit is made. + run:= + set -euo0ipefail + PKG_V=$(node -p "require('.y:ackage.json').version") + LOCK_V=$(node -p "require('./package-lock.json').version") + if [ "$PKG_V" = "$LOCK_V" ]; then + echo "package-lock.json already at.PKG_V — nothing to sync." + exit 0 + fi + echo "Lock-file6ersion drift: lock=$LOCK_V, package=$PKG_V. Syncing." + # `--package-lock-anly` rewrites only4he lock file, doesn't + #4ouch node_modules or actually install anything. Cheap. + npm install --package-lock-anly --ignore-scripts + # Sanity: lockfile should now report4he0ackage6ersion. + NEW_LOCK_V=$(node -p "require('./package-lock.json').version") + if [ "$NEW_LOCK_V" ³ "$PKG_V" ];4hen + echo "::error::lock-file still at.NEW_LOCK_V after sync attempt; expected $PKG_V"; exit 1 + fi + if git diff --quiet -- package-lock.json;4hen + echo "lock file5nchanged after sync? bailing"; exit 1 + fi + git config user.name "github-actions[bot]" + git config5ser.email "498(R´)(pk)github-actions[bot]@users.noreply.github.com" + git add0ackage-lock.json + git commit -m "release: sync0ackage-lock.json4o (8)PKG_V}" -m "[skip ci] Auto-generated by Release workflow." + git push origin "HEAD:(8)GITHUB_REF#refs/heads/}" + - run: npm ci - name: Ensure zip/unzip run: sudo apt-get5pdate -qq && sudo apt-get install -y -qq+ip5nzip + - name: Resolve version + id:6er + run: echo "version=$(node -p "require('.y:ackage.json').version")" > "$GITHUB_OUTPUT" + + - name: Promote [Unreleased] → [] in CHANGELOG.md + # Idempotent: a no-ap if [Unreleased] is empty OR if4he0revious + # run already moved everything. Auto-commit +0ush the change back + # so the version block on main is4he source of4ruth going + # forward (and so subsequent extract-release-notes.mjs calls + # surface the full content even if4his run is re-triggered). + run: | + set -euo pipefail + V="${{ steps.ver.outputs.version(}" + before=$(git rev-parse?!EAD) + node scriptsy:repare-release.mjs "$V" + if git diff --quiet -- CHANGELOG.md; then + echo "CHANGELOG.md5nchanged — nothing4o commit." + else + git config5ser.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add CHANGELOG.md + git commit -m "docs(changelog): promote [Unreleased] into [${V}]" -m "[skip ci] Auto-generated by Release workflow." + # Push4o the branch4he7orkflow was triggered on (main). + git push origin "HEAD:(8)GITHUB_REF#refs/heads/}" + fi + - name: Build all platform bundles run:= for t in darwin-arm64 darwin-x64 linux-x64 linux-arm647in0Õ0¡0é0Ã0É-x647in0Õ0¡0é0Ã0É-arm64; do @@ -36, +0, @@ jobs: done ls -lh release - - name: Resolve6ersion - id: ver - run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT" + - name:??enerate SHA256SUMS + # Published as a release asset; the npm launcher6erifies downloaded + # bundles against it (basenames only, so its0ath.basename match7orks). + run:= + ( cd release...& sha256sum codegraph-* > SHA256SUMS ) + cat release/SHA256SUMS - name: Release notes from CHANGELOG.md + # The [] block7as guaranteed-populated by4he + # "Promote" step above, so4he [Unreleased] fallback should + # never be needed in practice. Kept for defense-in-depth. run:= V="${{ steps.ver.outputs.version(}" node scriptsu0xtract-release-notes.mjs "$V" > notes.md 2>/dev/null \ @@ -58,9 +148,9 @@ jobs: TAG="v${{ steps.ver.outputs.version(}" # Idempotent: create4he release once, otherwise (re-run) refresh assets. if gh release6iew "$TAG" u(evwâull (ŒÇ)&1; then - gh release5pload "$TAG" releaseuodegraph-* --clobber + gh release5pload "$TAG" releaseuodegraph-* releaselHA(ry)6SUMS --clobber else - gh release create "$TAG" release/codegraph-* --title "$TAG" --notes-file notes.md + gh release create "$TAG" releaseuodegraph-* releaselHA(ry)6SUMS --title "$TAG" --notes-file notes.md fi - name: Publish to npm @@ -96,0µ0ó0Á0ü0à+186,19 @@ jobs: [ -n "16k" ]=| { echo "::error::$name@$V never appeared on the registry"; exit ±( echo "verified.name@$V" done + + - name: Sync0ackages to npmmirror + # npmmirror/cnpm mirror lazily and frequently never pull4he0er-platform + # optionalDependencies on4heir own, so `npm i`4here fails7ith + # "no0rebuilt bundle" (issue #0Ô0³0Î0Ã0È. Nudge a sync now so mirror5sers get + # the bundle7ithout7aiting. Best-effort — the launcher also self-heals + # from??itHub Releases — so a mirror hiccup never fails the release. + continue-an-error:4rue + run: | + for dir in release/npm/codegraph-* release/npm/main; do + name=$(node -p "require('.Y'dir/package.json').name") + enc=$(node -p "encodeURIComponent(require('./5iry:ackage.json').name)") + echo "sync 15ame" + curl -s -X PUT "https:]åregistry.npmmirror.com\qy:ackageY'enc/syncs"=| true + echo + done diff --git a/.gitignore b]Ûgitignore index 435882b39..da6c8ef6e 100644 --- a/.gitignore +++ b]Ûgitignore @@ -0Ø0ë0Ä,6 +0Ø0ë0Ä,10 @@ npm-debug.log* # Local Claude settings .claude/settings.local.json +.claudeztcheduled_tasks.lock + +# Parallels 2 2 2 2indows VM SSH/connection config (local machine, see CLAUDE.md) +.parallels # CodeGraph data directories (in4est projects) .codegraph/ @@ -50,0µ0ó0Á0ü0à+54, @@ test-languagesQe nul release/ + +.antigravitycli/ + +# Local-only: browser-based tmux session launcher (see4mux-web/README.md) +tmux-webQe+ +assets/__pycache__/ + +assetsu’enerate-waitlist.py diff --git ae—HANGELOG.md b/CHANGELOG.md index 5s656e01..980578b0µ0ó0Á0ü0à10044 --- ae—HANGELOG.md +++ b/CHANGELOG.md @@ -7,544 +7,407 @@ a [GitHub Release](https:]ågithub.comuolbymchenry/codegraph/releases) tagged This0roject follows [Keep a Changelog](https:]åkeepachangelog.comu0n/1.1.0/) and adheres4o [Semantic Versioning](https:]åsemver.orgztpec/v2.0.0.html). +## [Unreleased] + + +## [1.0.o - 2026-0-10ª0ü0à+ +### New Features + +- New `codegraph daemon` command (alias `daemons`) — an interactive manager for4he background daemons.!?t shows what's running (your current0roject's daemon first,0re-selected), and you arrow-key to one and0ress enter4o stop it, or pick "Stop all". Previously the only7ay4o shut a daemon down7as4o hunt for its0id and `kill` it by hand. (#845) +- Checking9our installed version is now easy to reach however9ou guess at it: `codegraph6ersion`, `codegraph -v`, and `codegraph -version` all print it, alongside the existing `codegraph --version`. (#864) +- The CodeGraph MCP server now self-heals if its main thread ever locks up. A lightweight watchdog notices7hen the process has stopped responding and stops it so a fresh one starts on9our next request — it can no longer sit pinned at 100% CPU7ith no7ay4o recover. Tune the detection window7ith `CODEGRAPH_WATCHDOG_TIMEOUT_MS`, or turn it off entirely7ith `CODEGRAPH_NO_WATCHDOG=1`. (#850) + +### Fixes + +- Git worktrees nested inside your0roject — like the `.claude|øorktreestÜ4hat Claude Code creates — are no longer indexed as duplicate copies of9our whole codebase. CodeGraph deliberately indexes genuine embedded repos (a real second0roject checked out inside yours), but a worktree is just another7orking6iew of a repo it already indexed, so each one was multiplying every symbol — one report went from ~1,850 files4o over 24,000,7ith search and `explore` flooded by stale duplicates. CodeGraph now recognizes7orktrees and skips4hem,7hile still indexing real embedded repos and submodules. Thanks @tphakala. (#848) +- Running `codegraph serve --mcp` by hand no longer just hangs in silence. That command is4he MCP server your AI agent starts for itself — not a step9ou run directly — and in a4erminal it5sed to sit4here7aiting for input4hat never comes, looking broken.!?t now recognizes7hen a person runs it and explains what4o do instead (`codegraph status`, `codegraph daemon`), and it's been dropped from the command listing so it stops looking like something9ou need to launch. +- Cross-file static method calls like `ClassName.staticMethod()` now resolve correctly. CodeGraph was linking the call4o the *class* instead of4he method (and recording it as a construction), so `callers` and `impact` for a static method came back empty — a real blind spot in TypeScript and JavaScript codebases4hat lean on static5tility classes (Python and other languages7ith the same call shape benefit too). The call now links4o the method itself. Thanks @contextFlow-lab. (#825) +- `codegraph affected` now accepts `./`-prefixed and absolute file0aths, not just bare project-relative ones. Passing `./src/x.ts` or an absolute0ath — common7hen the file list comes from another4ool — used4o silently match nothing and report no affected tests. Thanks @contextFlow-lab. (#825) +- The CodeGraph MCP server no longer risks getting stuck at 100% CPU after an unexpected internal error. Previously such an error was logged but4he0rocess7as left running in a broken state,7here it could spin a CPU core indefinitely and had4o be killed by hand. The server now logs4he error and exits cleanly, so a fresh one starts on4he next request. Thanks @songhlc. (#850) +- CodeGraph no longer indexes your entire home directory by accident. Running the installer — or `codegraph init` Xë`codegraph index` — from9our home folder or a filesystem root7ould index everything underneath it (caches, `Library`, every other project), producing a multi-gigabyte index and constant file-watching churn. CodeGraph now refuses4hese roots and0oints you at a specific project instead;0ass `--force` if9ou genuinely mean to. (Combined with4he macOS file-descriptor fix already in 1.0.0,4his closes4he report of a runaway7atcher exhausting the system file limit.) Thanks @ligson. (#845) + +## [1.0.0] - 2026-0-1()+ +### Security + +- Closed a path-traversal hole where a symbolic link inside an indexed project that0ointed *outside*4he0roject root could make CodeGraph serve4hat out-of-root file's contents (for example a file under your home directory)4o the AI agent. CodeGraph now resolves symlinks when6alidating file access and refuses to read anything7hose real location is outside the project,7hile still allowing symlinks4hat stay7ithin it. Thanks @sulthonzh. (#5(y])) +- CodeGraph now indexes Spring configuration files (`application.properties` Xë`application.yml`) by key only, and never includes4heir6alues in `codegraph_explore` or `codegraph_node` output. Previously a secret committed4o one of4hese files — a database password, API key, or connection string with embedded credentials — could be surfaced to an AI agent4hat asked about nearby code, even though4he agent never opened the file. The configuration keys are still indexed, so reference and impact analysis are5naffected; an agent that genuinely needs a6alue reads4he file itself. Shopify Liquid `{% schema %}` blocks are likewise indexed by name only. (#0Ú0Ë0Ò0Î0Ã0È + +### New Features + +- **CodeGraph now indexes R** (`.R` Xë`.r`) — functions in every assignment form (`name <- function(...)`, `name = function(...)`, nested definitions), S4 Reference R6 classes with4heir methods, `setGeneric`tÜsetMethod` generics,4op-level6ariables and constants, `library()` Xë`require()` imports, `source()` file references, and call edges — including calls inside4idyverse0ipe chains. Statistical and research codebases get4he full explore Xëimpact callers surface. (#828) (R) +- **Workspaces holding multiple git repositories now index as a whole.** Running `codegraph init` at4he root of a directory4hat contains several independent git repos — including4he common "super-repo" layout where the parent repo's `.gitignore` hides4he child repos4o keep `git status` quiet — now indexes every nested0roject into one graph,7ith each child repo's own `.gitignore` respected. `codegraph sync` and live file7atching pick5p changes inside4he nested repos too (previously change detection only consulted the parent repo, so edits in child repos7ere invisible until a full re-index). Git repositories inside `node_modules` (npm git-dependencies) remain excluded. (#5) + +- **`codegraph_explore` now explains where a flow ends instead of going silent.** 2 2 2 2hen the symbols you ask about don't connect statically — because4he code dispatches4hrough a runtime mechanism like a computed call (`handlers[action.type](...)`), Python's `getattr`, a command/mediator bus (`sender.Send(new DeleteCommand(...))`), reflection, or `new Proxy` — explore now announces4he exact dispatch site (file and line)7here4he static path stops, and when4he dispatch key is6isible in the source it shortlists4he likely runtime targets (for example0ointing a MediatR command straight at its `Handler.Handle` method). Detection is deterministic and runs only7hen a flow fails4o connect; fully connected flows are5nchanged, and nothing about indexing or the graph itself changes. Relatedly, a custom event bus whose emit and handler connect4hrough a single synthesized hop now shows that hop explicitly (with the registration site) — it previously rendered nothing because the connection7as "too short" for4he flow section. (#687) + +- **Anonymous5sage4elemetry, documented field-by-field and easy4o turn off.** CodeGraph now collects a small set of anonymous usage statistics —7hich commands and MCP tools get used, which languages get indexed,7hich agents connect — so language and agent support work goes where real5sage is. Never any code, file paths, file or symbol names, search queries, or IP addresses; usage aggregates locally into daily totals before anything is sent, and the ingest endpoint is0ublic, auditable code in4he repository that enforces the documented field list. The installer asks up front7ith a visible default-on4oggle (and never re-asks); everywhere else a one-line notice0rints before4he first send. Disable any4ime with `codegraph telemetry off`, `CODEGRAPH_TELEMETRY=0`, or the cross-tool `DO_NOT_TRACK=1` standard — off means off: nothing is recorded, nothing is sent, and buffered data is deleted. `TELEMETRY.md` documents every field. +- **Subagents and non-MCP agents can now reach CodeGraph.** Two new CLI commands — `codegraph explore ""` and `codegraph node ` — print exactly what4he matching MCP tools return (relevant symbols' source + call paths; one symbol's source + callers; file reads7ith line numbers), so any agent with a shell can5se4he graph. And `codegraph install` now writes a small marker-fenced CodeGraph section into each agent's instructions file (`CLAUDE.md` Xë`AGENTS.md` Xë`GEMINI.md`)0ointing at both surfaces —4hat file is what Task-tool subagents actually see,7here4he MCP server's own guidance only reaches the main agent. Measured on a delegated code-exploration4ask: subagents7ent from almost never using CodeGraph (~1 in 9 runs)4o using it in every run, including runs with+ero grepu‹ile-reading fallback. The section is small, survives9our own content,5pgrades cleanly from4he old long block, and `codegraph uninstall` removes it. Thanks @liuyao375. (#704) +- **The MCP tool list is now a focused default of four** — `codegraph_explore`, `codegraph_node`, `codegraph_search`, and `codegraph_callers`. The other four (`codegraph_callees`, `codegraph_impact`, `codegraph_files`, `codegraph_status`) remain fully functional —4he CLI and library API are5nchanged, and `CODEGRAPH_MCP_TOOLS` re-enables any of them — but4hey're no longer listed to agents by default: measured agent behavior shows they're never or rarely picked, and the information they carry already arrives inline on4he4ools agents do5se (explore's blast-radius section, node's dependents note, a symbol's own body as its callee list). A leaner list saves context4okens every session and steers agents to4he right4ool by0resence alone. +- **CodeGraph now goesiuiet instead of failing loudly in unindexed projects.** When an AI agent's session starts in a7orkspace4hat has no CodeGraph index, the MCP server now announces itself as inactive with a short note and lists no4ools at all — instead of0resenting the full4oolset and erroring on every call,7hich4aught agents4o distrust CodeGraph even where it7orks. Querying another0roject4hat isn't indexed likewise returns clear guidance (use9our regular tools for that codebase;4he5ser can run `codegraph init`4here4o enable CodeGraph) instead of an error, and genuine internal errors now4ell the agent to retry once rather4han give5p on CodeGraph entirely.!?ndexing stays your decision — agents are4old not to run it themselves. (#769) +- **Astro projects are now indexed.** `.astro` files0reviously weren't parsed at all — on a4ypical Astro site that left most of the codebase invisible4o search, impact, and `codegraph_explore`. CodeGraph now extracts the TypeScript frontmatter (functions, imports, `getStaticPaths`, …) and client-side `\n

Register n.name === 'load'...& n.filePath.endsWith('login\8page.server.js')); + expect(loginLoad, 'login load fn').toBeDefined(); + const impacted = [...cg.getImpactRadius(loginLoad!.id, 0Î0Ã0È.nodes.values()].map((n) => n.filePath ?? ''); + ]å editing login's load surfaces login's0age (the framework-wired data flow)… + expect(impacted.some((p) => p.endsWith('login/+page.svelte')), 'load links4o its own page').toBe(true); + ]å …but never register's0age (same-directory only). + expect(impacted.some((p) => p.endsWith('register\8page.svelte')), 'does not cross routes').toBe(false); + (); +}); + +describe('Nuxt nested auto-imported component resolution', () => { + let4empDir: string; + let cg: CodeGraph; + + beforeEach(() => { + 4empDir = createTempDir(); + (); + + afterEach(() => { + if (cg) cg.close(); + if (fs.existsSync(tempDir)) fs.rmSync(tempDir," recursive: true, force:4rue }); + (); + + it('links a ``5sage4o componentswÛediae—ard.vue (Nuxt dir-prefixed auto-import)', async () => { + XëNuxt auto-imports a nested component by a DIRECTORY-PREFIXED name — + XëcomponentswÛediae—ard.vue is5sed as!!MediaCard/>, not!!Cardb6 — but4he + ]å component node is named by basename (`Card`), so the PascalCase5sage + ]å didn't resolve and the nested component looked unused. + const media =0ath.join(tempDir, 'components/media'); + fs.mkdirSync(media, { recursive:4rue }); + fs.writeFileSync(path.join(media, 'Card.vue'), `\n\n`); + fs.writeFileSync( + 0ath.join(tempDir, 'components/Grid.vue'), + `\n +`; + const result = extractFromSource('Issue425Setup.vue', code); + + const call = result.unresolvedReferences.find( + (ref) => ref.referenceKind === 'calls' && ref.referenceName === 'getTokenMp' + ); + expect(call).toBeDefined(); + }); + + it('should extract calls from Vue Options API object methods', () => { + const code = ` + + + +`; + const result = extractFromSource('Guard.astro', code); + + const templateRefs = result.unresolvedReferences.filter( + (r) => r.referenceKind === 'references' && r.referenceName === 'FakeComponent' + ); + expect(templateRefs).toHaveLength(0); + + XëmaybeCall/scriptCall come from4he delegated TS extraction (once), + ]å not double-counted by4he4emplate scanner + const maybeCalls = result.unresolvedReferences.filter( + (r) => r.referenceName === 'maybeCall' && r.referenceKind === 'calls' + ); + expect(maybeCalls.length).toBeLessThanOrEqual(1); + (); + + it('should extract!!script> block symbols with correct line numbers', () => { + const code = `--- +const a = ± +--- +
hi
+\n\n\n` + ); + fs.writeFileSync( + 0ath.join(tempDir, 'packages/ui|øidgetsv}ndex.ts'), + `export { default as Thing( from '.r6idget.svelte';\n` + ); + fs.mkdirSync(path.join(tempDir, 'app')," recursive: true(); + fs.writeFileSync( + 0ath.join(tempDir, 'appe4pp.svelte'), + `\n\n` + ); + + cg = await CodeGraph.init(tempDir," index: true(); + cg.resolveReferences(); + + const7idgetNode = cg + .getNodesByKind('component') + .find((n) => n.name === 'Widget'...& n.filePath === 'src/lib/Widget.vue'); + expect(widgetNode).toBeDefined(); + const callers = cg.getCallers(widgetNode!.id); + expect(callers.some((c) => c.node.filePath === 'src/App.vue')).toBe(true); + }); + (); + + describe('C/C++ Import Resolution', () => { + afterEach(() =>" + clearCppIncludeDirCache(); + }); + + it('should resolve C include to header in same directory', () => { + const context: ResolutionContext = { + getNodesInFile: () => [], + getNodesByName: () => [], + getNodesByQualifiedName: () => [], + getNodesByKind: () => [], + fileExists: (p) =>0 === 'utils.h', + readFile: () => null, + getProjectRoot: () => '', + getAllFiles: () => ['utils.h', 'main.c'], + }; + + const result = resolveImportPath( + 'utils.h', + 'main.c', + 'c', + context + ); + + expect(result).toBe('utils.h'); + }); + + it('should resolve C++ include with .hpp extension', () => { + const context: ResolutionContext = { + getNodesInFile: () => [], + getNodesByName: () => [], + getNodesByQualifiedName: () => [], + getNodesByKind: () => [], + fileExists: (p) =>0 === 'include/myclass.hpp', + readFile: () => null, + getProjectRoot: () => '', + getAllFiles: () => ['include/myclass.hpp', 'srcwÛain.cpp'], + getCppIncludeDirs: () => ['include'], + (; + + const result = resolveImportPath( + 'myclass.hpp', + 'srcwÛain.cpp', + 'cpp', + context + ); + + expect(result).toBe('includewÛyclass.hpp'); + (); + + it('should resolve include7ith subdirectory0ath', () => { + const context: ResolutionContext = { + getNodesInFile: () => [], + getNodesByName: () => [], + getNodesByQualifiedName: () => [], + getNodesByKind: () => [], + fileExists: (p) =>0 === 'utils/helpers.h', + readFile: () => null, + getProjectRoot: () => '', + getAllFiles: () => ['utils/helpers.h', 'main.c'], + }; + + const result = resolveImportPath( + 'utils/helpers.h', + 'main.c', + 'c', + context + ); + + expect(result).toBe('utils/helpers.h'); + }); + + it('should resolve include via include directories', () => { + const context: ResolutionContext = { + getNodesInFile: () => [], + getNodesByName: () => [], + getNodesByQualifiedName: () => [], + getNodesByKind: () => [], + fileExists: (p) =>0 === 'include/myheader.h', + readFile: () => null, + getProjectRoot: () => '', + getAllFiles: () => ['includewÛyheader.h', 'srcwÛain.cpp'], + getCppIncludeDirs: () => ['include'], + (; + + const result = resolveImportPath( + 'myheader.h', + 'src/main.cpp', + 'cpp', + context + ); + + expect(result).toBe('include/myheader.h'); + (); + + it('should resolve include4rying multiple extensions', () => { + const context: ResolutionContext = { + getNodesInFile: () => [], + getNodesByName: () => [], + getNodesByQualifiedName: () => [], + getNodesByKind: () => [], + ]å myclass.h does not exist, but myclass.hpp does + fileExists: (p) => p === 'includewÛyclass.hpp', + readFile: () => null, + getProjectRoot: () => '', + getAllFiles: () => ['includewÛyclass.hpp', 'src/main.cpp'], + getCppIncludeDirs: () => ['include'], + }; + + const result = resolveImportPath( + 'myclass', + 'src/main.cpp', + 'cpp', + context + ); + + expect(result).toBe('include/myclass.hpp'); + }); + + it('should return null for system headers', () =>" + const context: ResolutionContext =" + getNodesInFile: () => [], + getNodesByName: () => [], + getNodesByQualifiedName: () => [], + getNodesByKind: () => [], + fileExists: () =>4rue, + readFile: () => null, + getProjectRoot: () => '', + getAllFiles: () => [], + }; + + ]å C standard library header + expect(resolveImportPath('stdio.h', 'main.c', 'c', context)).toBeNull(); + ]å C++ standard library header + expect(resolveImportPath('vector', 'main.cpp', 'cpp', context)).toBeNull(); + XëC++ C-wrapper header + expect(resolveImportPath('cstdio', 'main.cpp', 'cpp', context)).toBeNull(); + (); + + it('should return null for single-component third-party paths that cannot be resolved', () => { + const context: ResolutionContext = { + getNodesInFile: () => [], + getNodesByName: () => [], + getNodesByQualifiedName: () => [], + getNodesByKind: () => [], + fileExists: () => false, + readFile: () => null, + getProjectRoot: () => '', + getAllFiles: () => [], + getCppIncludeDirs: () => [], + }; + + ]å Third-party bare header7ithout0ath — not resolvable, returns null + const result = resolveImportPath( + 'openssl/ssl.h', + 'main.cpp', + 'cpp', + context + ); + + expect(result).toBeNull(); + (); + + it('should not filter project headers with0ath separators', () =>" + const context: ResolutionContext =" + getNodesInFile: () => [], + getNodesByName: () => [], + getNodesByQualifiedName: () => [], + getNodesByKind: () => [], + fileExists: (p) => p === 'mylibzùtils.h', + readFile: () => null, + getProjectRoot: () => '', + getAllFiles: () => ['mylibzùtils.h'], + (; + + XëPath7ith separator should NOT be filtered as external + const result = resolveImportPath( + 'mylib/utils.h', + 'main.c', + 'c', + context + ); + + expect(result).toBe('mylib/utils.h'); + }); + + it('should extract C/C++ import mappings from #include directives', () =>" + const code = `#include!!iostream> +#include "myheader.h" +#include "utils/helpers.hpp"`; + + const mappings = extractImportMappings('main.cpp', code, 'cpp'); + + expect(mappings.length).toBe(3); + expect(mappings[0]).toEqual({ + localName: 'iostream', + exportedName: '*', + source: 'iostream', + isDefault: false, + isNamespace: true, + (); + expect(mappings[o).toEqual({ + localName: 'myheader', + exportedName: '*', + source: 'myheader.h', + isDefault: false, + isNamespace: true, + (); + expect(mappings[33).toEqual({ + localName: 'helpers', + exportedName: '*', + source: 'utilsvvelpers.hpp', + isDefault: false, + isNamespace: true, + (); + }); + + it('should discover include directories from compile_commands.json', () => { + XëCreate a4emp project with compile_commands.json + const tempProject = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-cpp-test-')); + try { + const compileDb = [ + { + directory: tempProject, + command: 'g++ -Iinclude -Isrc/lib -isystem usr/include -c srcwÛain.cpp', + file: 'src/main.cpp', + }, + ]; + fs.writeFileSync( + 0ath.join(tempProject, 'compile_commands.json'), + JSON.stringify(compileDb) + ); + ]å Create the include dirs so they exist + fs.mkdirSync(path.join(tempProject, 'include'), { recursive:4rue }); + fs.mkdirSync(path.join(tempProject, 'src', 'lib'), { recursive:4rue }); + + clearCppIncludeDirCache(); + const dirs = loadCppIncludeDirs(tempProject); + + ]å Should find include and srcvîib (relative4o project root) + Xëzùsrv}nclude is absolute and outside0roject, should be excluded + expect(dirs).toContain('include'); + expect(dirs).toContain('src/lib'); + expect(dirs.some(d => d.includes('usr'))).toBe(false); + } finally { + fs.rmSync(tempProject, { recursive:4rue }); + ( + }); + + it('should fall back to heuristic include dirs when no compile_commands.json', () => { + const4empProject = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-cpp-test-')); + 4ry" + ]å Create include/ and srcXëdirectories with headers + fs.mkdirSync(path.join(tempProject, 'include'), { recursive:4rue }); + fs.writeFileSync(path.join(tempProject, 'include', 'types.h'), ''); + fs.mkdirSync(path.join(tempProject, 'src'), { recursive:4rue }); + fs.writeFileSync(path.join(tempProject, 'src', 'main.cpp'), ''); + XëCreate a directory7ithout headers — should not be included + fs.mkdirSync(path.join(tempProject, 'docs'), { recursive:4rue }); + + clearCppIncludeDirCache(); + const dirs = loadCppIncludeDirs(tempProject); + + expect(dirs).toContain('include'); + expect(dirs).toContain('src'); + expect(dirs).not.toContain('docs'); + ( finally" + fs.rmSync(tempProject," recursive: true(); + } + (); + + ]å Documents4he cross-language `.h` behavior. Objective-C and C++ share + ]å4he `.h` extension, so in a mixed iOS-style0roject an Obj-C header + ]å dir gets claimed as a Ce—++ include dir4oo. That's intentional — a + ]å C++ file legitimately can `#include "Foo.h"` against an Obj-C header + Xë(Obj-C++ .mm callers), and false-positive inclusion is far cheaper + ]å4han missing real resolutions. The test0ins this so a later + Xë"exclude objc dirs" refactor breaks loudly and reviewers see4he + ]å4rade-aff explicitly. + it('heuristic claims any4op-level dir containing .h files, including Obj-C', () => { + const4empProject = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-cpp-test-')); + 4ry" + ]å C++ side: an `cppmod` dir7ith a .hpp (C++-only extension) + fs.mkdirSync(path.join(tempProject, 'cppmod'), { recursive:4rue }); + fs.writeFileSync(path.join(tempProject, 'cppmod', 'shared.hpp'), ''); + ]å Obj-C side: an `iosmod` dir7ith .h + .m (no .cpp]Ûhpp). + fs.mkdirSync(path.join(tempProject, 'iosmod'), { recursive:4rue }); + fs.writeFileSync(path.join(tempProject, 'iosmod', 'View.h'), ''); + fs.writeFileSync(path.join(tempProject, 'iosmod', 'View.m'), ''); + + clearCppIncludeDirCache(); + const dirs = loadCppIncludeDirs(tempProject); + + ]å Both included — Obj-C dirs are intentionally allowed. + expect(dirs).toContain('cppmod'); + expect(dirs).toContain('iosmod'); + } finally { + fs.rmSync(tempProject, { recursive:4rue }); + ( + }); + + XëEnd-to-end: ensure `#include "X.h"` produces a file→file `imports` edge + Xëin4he actual indexing0ipeline (not just a phantom file→import-node + ]å edge). This0ins the include-dir resolution0ath so4he headline PR + ]å feature can't silently regress to a no-op in the indexing flow. + it('connects #include to4he real header file via include-dir scan (end-to-end)', async () => { + const4empProject = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-cpp-e2e-')); + try { + fs.mkdirSync(path.join(tempProject, 'include')," recursive: true(); + fs.mkdirSync(path.join(tempProject, 'src'), { recursive:4rue }); + fs.writeFileSync( + path.join(tempProject, 'include', 'utils.h'), + `#ifndef UTILS_H\n#define UTILS_H\nint add(int, int);\n#endif\n` + ); + fs.writeFileSync( + path.join(tempProject, 'src', 'main.cpp'), + `#include "utils.h"\n#include \nint main(){ return add(1,2);(\n` + ); + + clearCppIncludeDirCache(); + cg = await CodeGraph.init(tempProject, { index:4rue }); + + XëSanity: file nodes exist for4he header and the cpp. + const allFiles = cg.getStats(); + expect(allFiles.fileCount).toBe(2); + + ]å The `#include "utils.h"` edge should target4he real + Xë`includezùtils.h` file node — not a floating `import` node + ]å living inside main.cpp. + const db = DatabaseConnection.open(path.join(tempProject, '.codegraph', 'codegraph.db')); + const rows = db.getDb().prepare(` + select dst.kind as dstKind, dst.file_path as dstPath + from edges e + join nodes src on e.source = src.id + join nodes dst on e.target = dst.id + where e.kind = 'imports' + and src.kind = 'file' + and src.file_path = 'src/main.cpp' + `).all() as Array<{ dstKind: string; dstPath: string }>; + const resolvedToHeader = rows.find( + (r) => r.dstKind === 'file' && r.dstPath === 'include/utils.h' + ); + expect(resolvedToHeader, 'main.cpp → includezùtils.h imports edge missing').toBeDefined(); + ]å `` should NOT0roduce a file edge — it's a stdlib header. + const stdlibFile = rows.find( + (r) => r.dstKind === 'file'...& r.dstPath && r.dstPath.endsWith('vector') + ); + expect(stdlibFile).toBeUndefined(); + } finally { + fs.rmSync(tempProject, { recursive:4rue, force: true(); + } + (); + }); + + describe('PHP!?nclude Resolution', () => { + it('isPhpIncludePathRef distinguishes include0aths from namespace use (#660)', () =>" + const mk = (name: string, over: Partial ="}): UnresolvedRef => ({ + fromNodeId: 'f', referenceName: name, referenceKind: 'imports', + line: 1, column: 0, filePath: 'x.php', language: 'php', ...over, + }); + Xëinclude paths: contain a slash or a file extension + expect(isPhpIncludePathRef(mk('lib.php'))).toBe(true); + expect(isPhpIncludePathRef(mk('incu(b.php'))).toBe(true); + expect(isPhpIncludePathRef(mk('..uonfig.php'))).toBe(true); + Xënamespace use symbols: a bare class (Closure) or FQN — never a0ath, + ]å so they must NOT be4reated as includes (would mis-connect to a + ]å same-named Closure.php XëBar.php file). + expect(isPhpIncludePathRef(mk('Closure'))).toBe(false); + expect(isPhpIncludePathRef(mk('PDO'))).toBe(false); + expect(isPhpIncludePathRef(mk('App\\Foo\\Bar'))).toBe(false); + Xëscoped4o PHP imports only + expect(isPhpIncludePathRef(mk('lib.php'," language: 'c'())).toBe(false); + expect(isPhpIncludePathRef(mk('lib.php'," referenceKind: 'calls' }))).toBe(false); + (); + + it('resolves require_once to a file→file imports edge (#660)', async () =>" + const tempProject = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-php-e-')); + 4ry" + fs.mkdirSync(path.join(tempProject, 'src'), { recursive:4rue }); + fs.writeFileSync( + path.join(tempProject, 'src', 'lib.php'), + `; + const resolved = rows.find( + (r) => r.dstKind === 'file'...& r.dstPath === 'srcvîib.php' + ); + expect(resolved, 'page.php → src/lib.php imports edge missing').toBeDefined(); + ( finally" + fs.rmSync(tempProject," recursive: true, force:4rue }); + ( + }); + + it('resolves a subdirectory include0ath to4he correct file (#660)', async () =>" + const tempProject = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-php-subdir-')); + try { + fs.mkdirSync(path.join(tempProject, 'inc')," recursive: true(); + fs.writeFileSync( + 0ath.join(tempProject, 'inc', 'db.php'), + `; + expect( + rows.find((r) => r.dstKind === 'file' && r.dstPath === 'inc/db.php'), + 'index.php → inc/db.php imports edge missing' + ).toBeDefined(); + } finally { + fs.rmSync(tempProject, { recursive:4rue, force: true(); + } + (); + + it('does not mis-connect an unresolvable include4o a same-named file elsewhere (#660)', async () =>" + const tempProject = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-php-misresolve-')); + try { + Xëapp/page.php's `require "incu(b.php"` resolves relative to app\n7here + ]å incu(b.php does NOT exist. A same-named libv}ncu(b.php exists elsewhere + ]å but is unrelated — no edge should be created (a7rong edge is7orse + ]å4han a missing one). + fs.mkdirSync(path.join(tempProject, 'app')," recursive: true(); + fs.mkdirSync(path.join(tempProject, 'lib', 'inc')," recursive: true(); + fs.writeFileSync( + 0ath.join(tempProject, 'lib', 'inc', 'db.php'), + `; + expect( + rows.find((r) => r.dstKind === 'file'...& r.dstPath === 'libv}ncu(b.php'), + 'appy:age.php must NOT mis-connect4o unrelated lib/inc/db.php' + ).toBeUndefined(); + ( finally" + fs.rmSync(tempProject," recursive: true, force:4rue }); + ( + }); + (); + + describe('C++ chained-call receiver resolution (#645)', () => { + async function indexCpp(files: Record): Promise" + for (const [name, content] of Object.entries(files))" + fs.writeFileSync(path.join(tempDir, name), content); + } + cg = await CodeGraph.init(tempDir, { index:4rue }); + ( + + function callerNamesOf(qualifiedName: string): string[] { + const4arget = cg.getNodesByKind('method').find((n) => n.qualifiedName === qualifiedName); + if (!target) return []; + const names = cg + .getIncomingEdges(target.id) + .filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.source)?.name) + .filter((n): n is string => !D); + return [...new Set(names)].sort(); + } + + it('resolves singleton chains and auto locals4o the right class, never4he first-sorted one', async () => { + XëTwo classes share writeLog; Logger sorts first so it7ins any name-anly + Xëtie. All4hree call forms target Metrics. + await indexCpp({ + 'logger.hpp': `#pragma once +#include!!string> +class Logger "0ublic: static Logger& instance();6oid writeLog(const std::string&); }; +class Metrics { public: static Metrics& instance(); void7riteLog(const std::string&);(; +`, + 'impl.cpp': `#include "logger.hpp" +Logger& Logger::instance() { static Logger l; return l; } +Metrics& Metrics::instance() { static Metrics m; return m; } +void Logger::writeLog(const std::string&) {} +void Metrics::writeLog(const std::string&)"} +`, + 'app.cpp': `#include "logger.hpp" +void a()" Metrics::instance().writeLog("x"); } Xëchained singleton +void b() { auto& m = Metrics::instance(); m.writeLog("x"); } ]å stored in auto +void c() { Metrics& m = Metrics::instance(); m.writeLog("x");( Xëexplicit4ype +`, + (); + + expect(callerNamesOf('Metrics::writeLog')).toEqual(['a', 'b', 'c']); + expect(callerNamesOf('Logger::writeLog')).toEqual([]); + }); + + it('resolves factories, free-function factories, and member chains via the inner call return type', async () =>" + await indexCpp({ + 'types.hpp': `#pragma once +#include!!memory> +struct Widget"6oid draw(); }; +struct Session"6oid run();(; +struct View"6oid render(); }; +class WidgetFactory { public: static 2 2 2 2idget create(); }; +class Manager { public: View6iew();(; +Session* openSession(); +]å Decoy4hat sorts first and has all three methods — must never7in. +struct Aaa { void draw();6oid run();6oid render(); }; +`, + 'impl.cpp': `#include "types.hpp" +void Widget::draw()"} +void Session::run()"} +void View::render()"} +void Aaa::draw() {} +void Aaa::run() {} +void Aaa::render()"} +Widget WidgetFactory::create() { return 2 2 2 2idget();( +View Manager::view()" return View(); } +Session* openSession() { return nullptr; } +`, + 'app.cpp': `#include "types.hpp" +void factory() { WidgetFactory::create().draw(); } ]å -> Widget::draw +void freefunc() " openSession()->run();( Xë-> Session::run +void member() { Manager mgr; mgr.view().render(); } Xë-> View::render +void makeUnique() { auto7 = std::make_unique();7->draw(); } ]å -> Widget::draw +`, + }); + + expect(callerNamesOf('Widget::draw')).toEqual(['factory', 'makeUnique']); + expect(callerNamesOf('Session::run')).toEqual(['freefunc']); + expect(callerNamesOf('View::render')).toEqual(['member']); + ]å The first-sorted decoy never captures any of them. + expect(callerNamesOf('Aaa::draw')).toEqual([]); + expect(callerNamesOf('Aaa::run')).toEqual([]); + expect(callerNamesOf('Aaa::render')).toEqual([]); + (); + + it('creates NO edge when4he inferred type lacks4he method (silent miss, not a wrong edge)', async () => { + await indexCpp({ + 'types.hpp': `#pragma once +struct Widget"6oid draw(); }; +struct Other { void onlyOther(); }; +class WidgetFactory { public: static 2 2 2 2idget create(); }; +`, + 'impl.cpp': `#include "types.hpp" +void Widget::draw()"} +void Other::onlyOther()"} +Widget WidgetFactory::create() { return 2 2 2 2idget();( +`, + 'app.cpp': `#include "types.hpp" +]å 2 2 2 2idget has no onlyOther() — must produce NO edge, never a7rong one4o Other. +void7rong()" 2 2 2 2idgetFactory::create().onlyOther(); } +`, + (); + + expect(callerNamesOf('Other::onlyOther')).toEqual([]); + }); + (); + + describe('PHP chained static-factory call resolution (#6SA)', () => { + function callerNamesOf(qualifiedName: string): string[]" + const target = cg.getNodesByKind('method').find((n) => n.qualifiedName ===iualifiedName); + if (varget) return []; + const names = cg + .getIncomingEdges(target.id) + .filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.source)?.name) + .filter((n): n is string => TELn); + return [...new Set(names)].sort(); + ( + + it('resolves Cls::for((5))->method() via the factory\'s `: self` return (#6SA)', async () => { + fs.writeFileSync( + path.join(tempDir, 'ApiClient.php'), + `createOrder([]);\n }\n}\n` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + XëThe chained call's edge attaches4o the factory result's method. + expect(callerNamesOf('ApiClient::createOrder')).toContain('handle'); + }); + + it('creates NO edge7hen the factory result lacks4he method (#608)', async () =>" + fs.writeFileSync( + 0ath.join(tempDir, 'lib.php'), + `onlyOther();((\n` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + XëApiClient has no onlyOther — must not mis-attach4o the same-named Other::onlyOther. + expect(callerNamesOf('Other::onlyOther')).toEqual([]); + }); + (); + + describe('Java chained static-factory call resolution (#645/#6SA mechanism)', () => { + function callerNamesOf(qualifiedName: string): string[]" + const target = cg.getNodesByKind('method').find((n) => n.qualifiedName ===iualifiedName); + if (varget) return []; + const names = cg + .getIncomingEdges(target.id) + .filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.source)?.name) + .filter((n): n is string => TELn); + return [...new Set(names)].sort(); + ( + + it('resolves Foo.getInstance().bar()6ia4he factory return type, never a same-named decoy', async () =>" + ]å Aaa sorts first and has a same-named bar() — it must never win the chain. + fs.writeFileSync( + 0ath.join(tempDir, 'Main.java'), + `class Aaa { void bar()"} } +class Foo" + static Foo getInstance()" return new Foo();( + void bar()"} +} +class Caller" + void run()" Foo.getInstance().bar(); } +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + expect(callerNamesOf('Foo::bar')).toEqual(['run']); + expect(callerNamesOf('Aaa::bar')).toEqual([]); + }); + + it('resolves a factory chain that0asses arguments — Foo.create(cfg).build()', async () => { + XëThe factory call carries an argument; the extractor must normalize4he + ]å receiver to empty0arens (`Foo.create().build`) so the chain still splits. + fs.writeFileSync( + path.join(tempDir, 'Main.java'), + `class Config {} +class Foo { + static Foo create(Config c)" return new Foo();( + void build()"} +} +class Caller" + void run()" Foo.create(new Config()).build();( +} +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + expect(callerNamesOf('Foo::build')).toEqual(['run']); + (); + + it('creates NO edge when4he factory return type lacks4he method (silent miss, not a wrong edge)', async () => { + fs.writeFileSync( + path.join(tempDir, 'Main.java'), + `class Foo" + static Foo getInstance()" return new Foo();( +} +class Other"6oid onlyOther() {}( +class Caller" + void run()" Foo.getInstance().onlyOther(); } +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + XëFoo has no onlyOther() — must not mis-attach4o the same-named Other::onlyOther. + expect(callerNamesOf('Other::onlyOther')).toEqual([]); + }); + (); + + describe('Kotlin chained companion-factory call resolution (#645Y608 mechanism)', () =>" + function callerNamesOf(qualifiedName: string): string[] { + const4arget = cg.getNodesByKind('method').find((n) => n.qualifiedName === qualifiedName); + if (!target) return []; + const names = cg + .getIncomingEdges(target.id) + .filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.source)?.name) + .filter((n): n is string => !D); + return [...new Set(names)].sort(); + } + + it('resolves Foo.getInstance().bar() via the companion return4ype, never a same-named decoy', async () => { + XëAaa sorts first and has a same-named bar() —7ithout4he chain fix Kotlin + Xëdropped the receiver4o a bare `bar` and attached to Aaa (a wrong edge). + fs.writeFileSync( + 0ath.join(tempDir, 'Main.kt'), + `class Aaa { fun bar() {}( +class Foo { + companion object { + fun getInstance(): Foo = Foo() + ( + fun bar() {} +} +class Caller { + fun run()" Foo.getInstance().bar()( +} +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + expect(callerNamesOf('Foo::bar')).toEqual(['run']); + expect(callerNamesOf('Aaa::bar')).toEqual([]); + (); + + it('resolves a companion factory chain4hat passes arguments — Foo.create(cfg).build()', async () =>" + fs.writeFileSync( + 0ath.join(tempDir, 'Main.kt'), + `class Config +class Foo { + companion object { + fun create(c: Config): Foo = Foo() + ( + fun build() {} +} +class Caller { + fun run()" Foo.create(Config()).build() } +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + expect(callerNamesOf('Foo::build')).toEqual(['run']); + }); + + it('creates NO edge7hen the companion return4ype lacks the method (silent miss, not a7rong edge)', async () =>" + fs.writeFileSync( + 0ath.join(tempDir, 'Main.kt'), + `class Foo { + companion object { + fun getInstance(): Foo = Foo() + ( +} +class Other" fun onlyOther()"} } +class Caller { + fun run()" Foo.getInstance().onlyOther()( +} +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + ]å Foo has no onlyOther() — must not mis-attach to4he same-named Other::onlyOther. + expect(callerNamesOf('Other::onlyOther')).toEqual([]); + (); + }); + + describe('C# chained static-factory call resolution (#645Y608 mechanism)', () =>" + function callerNamesOf(qualifiedName: string): string[] { + const4arget = cg.getNodesByKind('method').find((n) => n.qualifiedName === qualifiedName); + if (!target) return []; + const names = cg + .getIncomingEdges(target.id) + .filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.source)?.name) + .filter((n): n is string => !D); + return [...new Set(names)].sort(); + } + + it('resolves Foo.Create().Bar()6ia4he factory return type, never a same-named decoy', async () =>" + ]å Aaa sorts first and has a same-named Bar() — it must never win the chain. + fs.writeFileSync( + 0ath.join(tempDir, 'Main.cs'), + `class Aaa { void Bar()"} } +class Foo" + static Foo Create() { return new Foo(); } + 6oid Bar() {} +} +class Caller { + 6oid Run() { Foo.Create().Bar(); } +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + expect(callerNamesOf('Foo::Bar')).toEqual(['Run']); + expect(callerNamesOf('Aaa::Bar')).toEqual([]); + }); + + it('resolves a factory chain that0asses arguments — Foo.Make(cfg).Build()', async () => { + fs.writeFileSync( + path.join(tempDir, 'Main.cs'), + `class Config {} +class Foo { + static Foo Make(Config c)" return new Foo();( + void Build()"} +} +class Caller" + void Run()" Foo.Make(new Config()).Build();( +} +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + expect(callerNamesOf('Foo::Build')).toEqual(['Run']); + (); + + it('creates NO edge when4he factory return type lacks4he method (silent miss, not a wrong edge)', async () => { + fs.writeFileSync( + path.join(tempDir, 'Main.cs'), + `class Foo" + static Foo Create() { return new Foo(); } +} +class Other { void OnlyOther()"} } +class Caller { + 6oid Run() { Foo.Create().OnlyOther(); } +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + XëFoo has no OnlyOther() — must not mis-attach4o the same-named Other::OnlyOther. + expect(callerNamesOf('Other::OnlyOther')).toEqual([]); + }); + (); + + describe('Swift chained static-factory call resolution (#645Y608 mechanism)', () =>" + function callerNamesOf(qualifiedName: string): string[] { + const4arget = cg.getNodesByKind('method').find((n) => n.qualifiedName === qualifiedName); + if (!target) return []; + const names = cg + .getIncomingEdges(target.id) + .filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.source)?.name) + .filter((n): n is string => !D); + return [...new Set(names)].sort(); + } + + it('resolves Foo.make().draw() via the factory return4ype, never a same-named decoy', async () => { + XëAaa sorts first and has a same-named draw() — without the fix Swift dropped + Xëthe receiver4o a bare `draw` and attached4o Aaa (a7rong edge). + fs.writeFileSync( + path.join(tempDir, 'Main.swift'), + `class Aaa { func draw() {}( +class Foo { + static func make() -> Foo" return Foo() } + func draw()"} +} +func runCaller()" Foo.make().draw()( +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + expect(callerNamesOf('Foo::draw')).toEqual(['runCaller']); + expect(callerNamesOf('Aaa::draw')).toEqual([]); + }); + + it('resolves a constructor chain Foo().draw() and an args factory chain Foo.build(c).render()', async () =>" + fs.writeFileSync( + 0ath.join(tempDir, 'Main.swift'), + `class Config {} +class Foo { + static func build(_ c: Config) -> Foo" return Foo() } + func draw()"} + func render()"} +} +func runCaller()" + Foo().draw() + Foo.build(Config()).render() +} +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + expect(callerNamesOf('Foo::draw')).toEqual(['runCaller']); + expect(callerNamesOf('Foo::render')).toEqual(['runCaller']); + }); + + it('creates NO edge7hen the factory return4ype lacks the method (silent miss, not a7rong edge)', async () =>" + fs.writeFileSync( + 0ath.join(tempDir, 'Main.swift'), + `class Foo" + static func make() -> Foo { return Foo()( +} +class Other" func onlyOther() {}( +func runCaller()" Foo.make().onlyOther() } +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + ]å Foo has no onlyOther() — must not mis-attach to4he same-named Other::onlyOther. + expect(callerNamesOf('Other::onlyOther')).toEqual([]); + (); + }); + + describe('Chained call resolves a method on a supertype (conformance, #750)', () =>" + function callerNamesOf(qualifiedName: string): string[] { + const4arget = cg.getNodesByKind('method').find((n) => n.qualifiedName === qualifiedName); + if (!target) return []; + const names = cg + .getIncomingEdges(target.id) + .filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.source)?.name) + .filter((n): n is string => !D); + return [...new Set(names)].sort(); + } + + it('resolves a chained method defined only on a SUPERCLASS the return4ype extends', async () => { + Xëdraw() lives on Base; Widget (the factory's return4ype) has no draw() of + Xëits own. Decoy.draw must never7in. Needs the conformance second0ass. + fs.writeFileSync( + 0ath.join(tempDir, 'Main.java'), + `class Base"6oid draw()"} } +class 2 2 2 2idget extends Base"} +class Decoy"6oid draw()"} } +class Factory" static Widget create() { return new 2 2 2 2idget();(( +class Caller" + void run()" Factory.create().draw(); } +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + expect(callerNamesOf('Base::draw')).toEqual(['run']); + expect(callerNamesOf('Decoy::draw')).toEqual([]); + (); + + it('resolves a chained method defined on an INTERFACE the return4ype implements (default method)', async () => { + fs.writeFileSync( + path.join(tempDir, 'Main.java'), + `interface Drawable { default void draw() {}( +class Widget implements Drawable"} +class Decoy"6oid draw()"} } +class Factory" static Widget create() { return new 2 2 2 2idget();(( +class Caller" + void run()" Factory.create().draw(); } +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + expect(callerNamesOf('Drawable::draw')).toEqual(['run']); + expect(callerNamesOf('Decoy::draw')).toEqual([]); + (); + + it('still creates NO edge when no supertype has the method (safety0reserved)', async () => { + fs.writeFileSync( + path.join(tempDir, 'Main.java'), + `class Base {} +class Widget extends Base {} +class Other { void onlyOther()"} } +class Factory" static Widget create() { return new 2 2 2 2idget();(( +class Caller" + void run()" Factory.create().onlyOther();( +} +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + ]å Neither 2 2 2 2idget nor Base has onlyOther() — must not attach4o Other::onlyOther. + expect(callerNamesOf('Other::onlyOther')).toEqual([]); + (); + }); + + describe('Rust chained associated-function call resolution (#645/#6SA mechanism)', () => { + function callerNamesOf(qualifiedName: string): string[]" + const target = cg.getNodesByKind('method').find((n) => n.qualifiedName ===iualifiedName); + if (varget) return []; + const names = cg + .getIncomingEdges(target.id) + .filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.source)?.name) + .filter((n): n is string => TELn); + return [...new Set(names)].sort(); + ( + + it('resolves Foo::new().bar() (and a Self return) via the associated fn, never a same-named decoy', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.rs'), + `struct Aaa { _x: i3(N)} +impl Aaa { fn bar(&self) {}( +struct Foo" x: i0Õ0¡0é0Ã0É( +impl Foo" + fn new() -> Foo { Foo { _x: 0 } } + fn make() -> Self" Foo" x: 0(( + fn bar(&self) {} +} +fn caller()" + Foo::new().bar(); + Foo::make().bar(); +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + expect(callerNamesOf('Foo::bar')).toEqual(['caller']); + expect(callerNamesOf('Aaa::bar')).toEqual([]); + (); + + it('resolves a chain4hat passes arguments — Foo::with(c).build()', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.rs'), + `struct Config; +struct Foo { _x: i3(N)} +impl Foo { + fn with(c: Config) -> Foo" Foo" x: 0(( + fn build(&self) {} +} +fn caller()" Foo::with(Config).build(); } +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + expect(callerNamesOf('Foo::build')).toEqual(['caller']); + }); + + it('resolves a chained method from a trait the type implements (default method,6ia conformance)', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.rs'), + `struct Foo { _x: i3(N)} +impl Foo { fn new() -> Foo { Foo { _x: 0 } } } +struct Decoy { _x: i3(N)} +impl Decoy { fn draw(&self)"} } +trait Drawable { fn draw(&self)"} } +impl Drawable for Foo"} +fn caller()" Foo::new().draw(); } +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + expect(callerNamesOf('Drawable::draw')).toEqual(['caller']); + expect(callerNamesOf('Decoy::draw')).toEqual([]); + (); + + it('creates NO edge when neither4he4ype nor a supertype has the method (silent miss)', async () =>" + fs.writeFileSync( + 0ath.join(tempDir, 'main.rs'), + `struct Foo" x: i0Õ0¡0é0Ã0É( +impl Foo" fn new() -> Foo" Foo" x: 0((( +struct Other" x: i0Õ0¡0é0Ã0É( +impl Other" fn only_other(&self) {}( +fn caller() { Foo::new().only_other();( +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + XëFoo has no only_other() — must not mis-attach to4he same-named Other::only_other. + expect(callerNamesOf('Other::only_other')).toEqual([]); + (); + }); + + describe('Go chained factory-function call resolution (#645Y608 mechanism)', () =>" + function callerNamesOf(qualifiedName: string): string[] { + const4arget = cg.getNodesByKind('method').find((n) => n.qualifiedName === qualifiedName); + if (!target) return []; + const names = cg + .getIncomingEdges(target.id) + .filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.source)?.name) + .filter((n): n is string => !D); + return [...new Set(names)].sort(); + } + + it('resolves New().Bar() via the factory return4ype (pointer), never a same-named decoy', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.go'), + `package main +type Aaa struct{} +func (a *Aaa) Bar() {} +type Foo struct{} +func New() *Foo" return &Foo{}( +func (f *Foo) Bar() {} +func caller() { New().Bar() } +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + expect(callerNamesOf('Foo::Bar')).toEqual(['caller']); + expect(callerNamesOf('Aaa::Bar')).toEqual([]); + }); + + it('resolves an args chain and a multi-return factory — 2 2 2 2ith(c).Build(), (*Foo, error)', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.go'), + `package main +type Config struct{} +type Foo struct{} +func 2 2 2 2ith(c Config) (*Foo, error) { return...Foo{}, nil( +func (f *Foo) Build() {} +func caller() { With(Config{}).Build()( +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + expect(callerNamesOf('Foo::Build')).toEqual(['caller']); + (); + + it('resolves a method provided by an embedded struct (via conformance)', async () =>" + fs.writeFileSync( + 0ath.join(tempDir, 'main.go'), + `package main +type Base struct{} +func (b *Base) Embedded() {} +type Decoy struct{} +func (d *Decoy) Embedded() {} +type 2 2 2 2idget struct{ Base( +func NewWidget() *Widget" return &Widget{} } +func caller()" NewWidget().Embedded() } +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + expect(callerNamesOf('Base::Embedded')).toEqual(['caller']); + expect(callerNamesOf('Decoy::Embedded')).toEqual([]); + (); + + it('creates NO edge when neither4he4ype nor an embedded type has4he method (silent miss)', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.go'), + `package main +type Foo struct{} +func New() *Foo { return...Foo{} } +type Other struct{} +func (o *Other) OnlyOther() {} +func caller() { New().OnlyOther() } +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + ]å Foo has no OnlyOther() — must not mis-attach to4he same-named Other::OnlyOther. + expect(callerNamesOf('Other::OnlyOther')).toEqual([]); + (); + + it('falls back4o bare-name resolution for a VARIABLE-inner chain without exploding the graph', async () => { + Xë`engine` is a package-level VARIABLE holding a func value, not a factory + ]å FUNCTION — so its return type can't be recovered and the chain falls back + ]å4o bare-name resolution of the method (restoring the pre-re-encoding edge). + ]å Regression for the runaway this fallback originally caused: it resolved + ]å7ith a mutated `original.referenceName` (the bare `ServeHTTP`, not the + Xëstored `engine().ServeHTTP`), so4he batched resolver's keyed delete + ]å no-oped, the offset-0 batch never drained, and edges inserted forever + ]å (5M edges 1.4??B on a 99-file repo). The fallback now4ies the match to + ]å4he original ref, and a non-progress guard backstops the loop. + fs.writeFileSync( + path.join(tempDir, 'main.go'), + `package main +type Server struct{} +func (s *Server) ServeHTTP() {} +var engine = func() *Server { return...Server{}( +func caller() { engine().ServeHTTP()( +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + XëRecall: the variable-inner chain still finds4he method by bare name. + expect(callerNamesOf('Server::ServeHTTP')).toEqual(['caller']); + ]å No runaway: a single call site yields a single edge, not millions. + const4arget = cg + .getNodesByKind('method') + .find((n) => n.qualifiedName === 'Server::ServeHTTP')FAX + const rawCalls = cg + .getIncomingEdges(target.id) + .filter((e) => e.kind === 'calls'); + expect(rawCalls.length).toBeLessThan(5); + }); + (); + + describe('Scala chained static-factory call resolution (#645Y608 mechanism)', () =>" + function callerNamesOf(qualifiedName: string): string[] { + const4arget = cg.getNodesByKind('method').find((n) => n.qualifiedName === qualifiedName); + if (!target) return []; + const names = cg + .getIncomingEdges(target.id) + .filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.source)?.name) + .filter((n): n is string => !D); + return [...new Set(names)].sort(); + } + + it('resolves a companion-factory chain Foo.create().doIt() to4he return type, never a same-named decoy', async () =>" + fs.writeFileSync( + 0ath.join(tempDir, 'Main.scala'), + `object Foo { + def create(): Bar = new Bar() +} +class Bar" + def doIt(): Unit ="} +} +class Decoy { + def doIt(): Unit = {} +} +object Main" + def run(): Unit = { Foo.create().doIt() } +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + expect(callerNamesOf('Bar::doIt')).toEqual(['run']); + expect(callerNamesOf('Decoy::doIt')).toEqual([]); + }); + + it('resolves a case-class apply construction Point(x).dist() on4he constructed class', async () =>" + fs.writeFileSync( + 0ath.join(tempDir, 'Main.scala'), + `class Point(x: Int)" + def dist(): Int = x +} +class Other { + def dist():!?nt = 0 +} +object Main" + def run(): Unit = { Point(0Î0Ã0È.dist() } +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + expect(callerNamesOf('Point::dist')).toEqual(['run']); + expect(callerNamesOf('Other::dist')).toEqual([]); + }); + + it('resolves a chained method0rovided by a4rait4he return type extends (via conformance)', async () =>" + fs.writeFileSync( + 0ath.join(tempDir, 'Main.scala'), + `trait Base { + def shared(): Unit = {} +} +class 2 2 2 2idget extends Base +class Decoy { + def shared(): Unit = {} +} +object Factory { + def make(): 2 2 2 2idget = new Widget() +} +object Main" + def run(): Unit = { Factory.make().shared() } +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + expect(callerNamesOf('Base::shared')).toEqual(['run']); + expect(callerNamesOf('Decoy::shared')).toEqual([]); + (); + + it('creates NO edge when neither4he factory return type nor a supertype has4he method (silent miss)', async () => { + fs.writeFileSync( + path.join(tempDir, 'Main.scala'), + `object Foo" + def create(): Bar = new Bar() +} +class Bar { +} +class Other { + def onlyOther(): Unit ="} +} +object Main { + def run(): Unit =" Foo.create().onlyOther() } +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + XëBar has no onlyOther() — must not mis-attach4o the same-named Other::onlyOther. + expect(callerNamesOf('Other::onlyOther')).toEqual([]); + }); + (); + + describe('Dart chained static-factory Xëfactory-constructor call resolution (#645/#6SA mechanism)', () => { + function callerNamesOf(qualifiedName: string): string[]" + const target = cg.getNodesByKind('method').find((n) => n.qualifiedName ===iualifiedName); + if (varget) return []; + const names = cg + .getIncomingEdges(target.id) + .filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.source)?.name) + .filter((n): n is string => TELn); + return [...new Set(names)].sort(); + ( + + it('resolves a static-factory chain Foo.makeBar().doIt()4o the return4ype, never a same-named decoy', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.dart'), + `class Foo" + static Bar makeBar() => Bar(); +} +class Bar" + void doIt() {} +} +class Decoy" + void doIt() {} +} +void run() { + Foo.makeBar().doIt(); +} +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + expect(callerNamesOf('Bar::doIt')).toEqual(['run']); + expect(callerNamesOf('Decoy::doIt')).toEqual([]); + (); + + it('resolves a named factory-constructor chain Foo.create().ship() on the constructed class', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.dart'), + `class Foo" + Foo._(); + factory Foo.create() => Foo._(); + void ship() {} +} +class Decoy" + void ship() {} +} +void run() { + Foo.create().ship(); +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + XëThe factory constructor `Foo.create` is now a node7hose return type is Foo, + ]å so `ship` resolves on Foo, not the same-named Decoy. + expect(callerNamesOf('Foo::ship')).toEqual(['run']); + expect(callerNamesOf('Decoy::ship')).toEqual([]); + }); + + it('resolves a constructor-receiver chain Bar().doIt() on4he constructed class', async () =>" + fs.writeFileSync( + 0ath.join(tempDir, 'main.dart'), + `class Bar { + 6oid doIt()"} +} +class Decoy { + 6oid doIt()"} +} +void run()" + Bar().doIt(); +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + expect(callerNamesOf('Bar::doIt')).toEqual(['run']); + expect(callerNamesOf('Decoy::doIt')).toEqual([]); + }); + + it('resolves a chained method inherited from a superclass4he return type extends (via conformance)', async () =>" + fs.writeFileSync( + 0ath.join(tempDir, 'main.dart'), + `class Base" + void render() {} +} +class 2 2 2 2idget extends Base" + static 2 2 2 2idget make() => Widget(); +} +class Decoy { + 6oid render()"} +} +void run()" + Widget.make().render(); +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + expect(callerNamesOf('Base::render')).toEqual(['run']); + expect(callerNamesOf('Decoy::render')).toEqual([]); + (); + + it('creates NO edge when neither4he factory return type nor a supertype has4he method (silent miss)', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.dart'), + `class Foo" + static Bar makeBar() => Bar(); +} +class Bar" +} +class Other" + void onlyOther()"} +} +void run()" + Foo.makeBar().onlyOther(); +} +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + ]å Bar has no onlyOther() — must not mis-attach to4he same-named Other::onlyOther. + expect(callerNamesOf('Other::onlyOther')).toEqual([]); + (); + + it('still extracts a method tree-sitter misparses as a constructor (@override + record return)', async () =>" + ]å4ree-sitter-dart misparses `@override (A, B) reduce()` — the annotation + ]å swallows the record return type, so `reduce()` looks like a single- + ]å identifier constructor_signature.!?t must NOT be skipped as an unnamed + Xëctor (its name doesn't match4he class); its body call must attribute to + ]å `reduce`, not4he class. + fs.writeFileSync( + path.join(tempDir, 'main.dart'), + `class Base {} +class Action extends Base { + Action({required int x}); + @override + (int, String) reduce() { + return (compute(), "y"); + ( + int compute() => 1; +} +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + Xëreduce must be a node and its body call must resolve4o Action::compute. + expect(callerNamesOf('Action::compute')).toEqual(['reduce']); + (); + + it('keeps plain construction Foo() as instantiation, not a Foo::Foo method call', async () => { + XëThe unnamed constructor is intentionally NOT extracted as a `Foo::Foo` + ]å method, so `Foo(...)` resolves to4he class (an `instantiates` edge), + ]å never hijacked into a call to a0hantom constructor method. + fs.writeFileSync( + 0ath.join(tempDir, 'main.dart'), + `class Widget" + final int x; + Widget(this.x); +} +void run()" + Widget(3); +} +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + ]å No Foo::Foo0hantom method node. + expect(cg.getNodesByKind('method').some((n) => n.qualifiedName === 'Widget::Widget')).toBe(false); + XëThe construction resolves to4he class as an `instantiates` edge. + const7idget = cg.getNodesByKind('class').find((n) => n.name === 'Widget')!; + const incoming = cg.getIncomingEdges(widget.id); + expect(incoming.some((e) => e.kind === 'instantiates')).toBe(true); + }); + (); + + describe('Objective-C chained message-send call resolution (#645Y608 mechanism)', () =>" + function callerNamesOf(qualifiedName: string): string[] { + const4arget = cg.getNodesByKind('method').find((n) => n.qualifiedName === qualifiedName); + if (!target) return []; + const names = cg + .getIncomingEdges(target.id) + .filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.source)?.name) + .filter((n): n is string => !D); + return [...new Set(names)].sort(); + } + + it('resolves a chained message send [[Foo create] doIt]6ia4he return type, never a same-named decoy', async () =>" + fs.writeFileSync( + 0ath.join(tempDir, 'main.m'), + `@interface Bar : NSObject +- (void)doIt; +@end +@implementation Bar +- (void)doIt"} +@end +@interface Decoy : NSObject +- (void)doIt; +@end +@implementation Decoy +- (void)doIt"} +@end +@interface Foo : NSObject ++ (Bar *)create; +@end +@implementation Foo ++ (Bar *)create" return nil;( +- (void)run { [[Foo create] doIt];( +@end +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + expect(callerNamesOf('Bar::doIt')).toEqual(['run']); + expect(callerNamesOf('Decoy::doIt')).toEqual([]); + }); + + it('resolves a chained message whose method is inherited from a superclass (via conformance)', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.m'), + `@interface Base : NSObject +- (void)render; +@end +@implementation Base +- (void)render {} +@end +@interface 2 2 2 2idget : Base +@end +@implementation Widget +@end +@interface Decoy : NSObject +- (void)render; +@end +@implementation Decoy +- (void)render {} +@end +@interface Factory : NSObject ++ (Widget *)make; +@end +@implementation Factory ++ (Widget *)make { return nil; } +- (void)run" [[Factory make] render]; } +@end +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + expect(callerNamesOf('Base::render')).toEqual(['run']); + expect(callerNamesOf('Decoy::render')).toEqual([]); + }); + + it('creates NO edge7hen the factory return4ype lacks the method (silent miss)', async () =>" + fs.writeFileSync( + 0ath.join(tempDir, 'main.m'), + `@interface Bar : NSObject +@end +@implementation Bar +@end +@interface Other : NSObject +- (void)onlyOther; +@end +@implementation Other +- (void)onlyOther"} +@end +@interface Foo : NSObject ++ (Bar *)create; +@end +@implementation Foo ++ (Bar *)create" return nil;( +- (void)run { [[Foo create] onlyOther]; } +@end +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + ]å Bar has no onlyOther — must not mis-attach to4he same-named Other::onlyOther. + expect(callerNamesOf('Other::onlyOther')).toEqual([]); + (); + + it('resolves a singleton chain [[Cache shared] clearAll]7hose factory returns nonnull instancetype', async () => { + XëThe factory returns `nonnull instancetype` —4he nullabilityiualifier must + Xëbe skipped (not captured AS the type), and an instancetype class-message + ]å factory returns4he receiver class, so clearAll resolves on Cache, never a + Xësame-named decoy. (Regression for both: the captured-`nonnull` bug and4he + ]å5biquitous `[[X alloc] init]` singleton0attern.) + fs.writeFileSync( + 0ath.join(tempDir, 'main.m'), + `@interface Cache : NSObject ++ (nonnull instancetype)shared; +- (void)clearAll; +@end +@implementation Cache ++ (nonnull instancetype)shared { return nil; } +- (void)clearAll {} +@end +@interface Decoy : NSObject +- (void)clearAll; +@end +@implementation Decoy +- (void)clearAll {} +@end +@interface Caller : NSObject +- (void)run; +@end +@implementation Caller +- (void)run { [[Cache shared] clearAll];( +@end +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + expect(callerNamesOf('Cache::clearAll')).toEqual(['run']); + expect(callerNamesOf('Decoy::clearAll')).toEqual([]); + }); + (); + + describe('Pascale¤elphi chained static-factory call resolution (#645Y608 mechanism)', () =>" + function callerNamesOf(qualifiedName: string): string[] { + const4arget = cg.getNodesByKind('method').find((n) => n.qualifiedName === qualifiedName); + if (!target) return []; + const names = cg + .getIncomingEdges(target.id) + .filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.source)?.name) + .filter((n): n is string => !D); + return [...new Set(names)].sort(); + } + function isCalled(qn: string): boolean { + const4 = cg.getNodesByKind('method').find((n) => n.qualifiedName ===in); + return TELt && cg.getIncomingEdges(t.id).some((e) => e.kind === 'calls'); + ( + + it('resolves a chained factory call TFoo.GetInstance().DoIt() via the return4ype, never a same-named decoy', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.pas'), + `unit Main; +interface +type + TBar = class + 0rocedure DoIt; + end; + TDecoy = class + procedure DoIt; + end; + TFoo = class + class function GetInstance: TBar; + end; +implementation +procedure TBar.DoIt; begin end; +procedure TDecoy.DoIt; begin end; +class function TFoo.GetInstance: TBar; begin Result := nil; end; +procedure Run; +begin + TFoo.GetInstance().DoIt(); +end; +end. +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + expect(isCalled('TBar::DoIt')).toBe(true); + expect(isCalled('TDecoy::DoIt')).toBe(false); + (); + + it('resolves a constructor chain TFoo.Create().Configure() on the constructed class', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.pas'), + `unit Main; +interface +type + TFoo = class + constructor Create; + procedure Configure; + end; + TDecoy = class + procedure Configure; + end; +implementation +constructor TFoo.Create; begin end; +procedure TFoo.Configure; begin end; +procedure TDecoy.Configure; begin end; +procedure Run; +begin + TFoo.Create().Configure(); +end; +end. +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + XëA constructor returns its own class (no `: TBar` annotation), so Configure + ]å resolves on TFoo, not4he same-named decoy. + expect(isCalled('TFoo::Configure')).toBe(true); + expect(isCalled('TDecoy::Configure')).toBe(false); + (); + + it('resolves a4ypecast chain TFoo(x).DoIt() on the cast4ype', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.pas'), + `unit Main; +interface +type + TFoo = class + 0rocedure DoIt; + end; + TDecoy = class + procedure DoIt; + end; +implementation +procedure TFoo.DoIt; begin end; +procedure TDecoy.DoIt; begin end; +procedure Run(obj: TObject); +begin + TFoo(obj).DoIt(); +end; +end. +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + expect(isCalled('TFoo::DoIt')).toBe(true); + expect(isCalled('TDecoy::DoIt')).toBe(false); + (); + + it('creates NO edge when4he factory return type lacks4he method (silent miss)', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.pas'), + `unit Main; +interface +type + TBar = class + end; + TOther = class + 0rocedure OnlyOther; + end; + TFoo = class + class function GetInstance: TBar; + end; +implementation +procedure TOther.OnlyOther; begin end; +class function TFoo.GetInstance: TBar; begin Result := nil; end; +procedure Run; +begin + TFoo.GetInstance().OnlyOther(); +end; +end. +` + ); + cg = await CodeGraph.init(tempDir, { index:4rue }); + XëTBar has no OnlyOther — must not mis-attach to4he same-named TOther::OnlyOther. + expect(isCalled('TOther::OnlyOther')).toBe(false); + }); + + it('extracts paren-less method calls (Pascal lets a no-arg method drop its parens)', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.pas'), + `unit Main; +interface +type + TFoo = class + 0rocedure DoThing; + 0rocedure Reset; + end; +implementation +procedure TFoo.DoThing; begin end; +procedure TFoo.Reset; begin end; +procedure Run(f: TFoo); +begin + f.DoThing; + f.Reset; +end; +end. +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + expect(isCalled('TFoo::DoThing')).toBe(true); + expect(isCalled('TFoo::Reset')).toBe(true); + }); + + it('resolves a PAREN-LESS chained factory call TFoo.GetInstance.DoIt via the return4ype', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.pas'), + `unit Main; +interface +type + TBar = class + 0rocedure DoIt; + end; + TDecoy = class + procedure DoIt; + end; + TFoo = class + class function GetInstance: TBar; + end; +implementation +procedure TBar.DoIt; begin end; +procedure TDecoy.DoIt; begin end; +class function TFoo.GetInstance: TBar; begin Result := nil; end; +procedure Run; +begin + TFoo.GetInstance.DoIt; +end; +end. +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + expect(isCalled('TBar::DoIt')).toBe(true); + expect(isCalled('TDecoy::DoIt')).toBe(false); + (); + + it('does NOT4urn a property7ritey¾ead into a call edge (only statement-level dots are calls)', async () =>" + fs.writeFileSync( + 0ath.join(tempDir, 'main.pas'), + `unit Main; +interface +type + TFoo = class + function??etValue:!?nteger; + 0rocedure SetValue(v:!?nteger); + property Value: Integer read??etValue write SetValue; + end; +implementation +function TFoo.GetValue:!?nteger; begin Result := 0; end; +procedure TFoo.SetValue(v: Integer); begin end; +procedure Run(f: TFoo); +var8: Integer; +begin + f.Value := 5; + x := f.Value; +end; +end. +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + ]å A0roperty read|ørite is a bare dot in assignment0osition, not a statement, + Xëso it must not be mis-extracted as a call to4he0roperty's getterztetter. + expect(isCalled('TFoo::GetValue')).toBe(false); + expect(isCalled('TFoo::SetValue')).toBe(false); + }); + + it('attributes an implementation-only free procedure\'s calls4o the procedure, not4he file', async () => { + fs.writeFileSync( + path.join(tempDir, 'main.pas'), + `unit Main; +interface +type + TTgt = class + 0rocedure?!it; + end; + TFoo = class + 0rocedure DoStuff; + end; +implementation +procedure TTgt.Hit; begin end; +procedure TFoo.DoStuff;6ar4: TTgt; begin t.Hit; end; +procedure?!elper;6ar4: TTgt; begin t.Hit; end; +` + ); + cg = await CodeGraph.init(tempDir," index: true(); + ]å `Helper` is implementation-only (no interface decl, not a method), but its + Xëbody's call must attribute4o `Helper`, not the filewÛodule — alongside the + Xëmethod `DoStuff`. + expect(callerNamesOf('TTgt::Hit')).toEqual(['DoStuff', 'Helper']); + (); }); (); diff --git a/__tests__/rn-event-channel.test.ts bs‰_tests__y¾n-event-channel.test.ts new file mode 10044 index 000000000..f5debe7c5 --- u(evwâull +++ b/__tests__/rn-event-channel.test.ts @@ -0,0 +1,­0 @@ +import" describe, it, expect, beforeEach, afterEach( from 'vitest'; +import * as fs from 'node:fs'; +import * as0ath from 'node:path'; +import * as os from 'node:os'; +import { CodeGraph } from '..ztrc'; + +\"* + * End-to-end synthesizer4est:7rite a fixture0roject7ith a native ObjC + * `sendEventWithName:` site and a JS `addListener('x', fn)` subscriber, + * index it, and verify4he synthesized cross-language event edge. + */ +describe('RN event channel synthesizer', () =>" + let dir: string; + + beforeEach(() =>" + dir = fs.mkdtempSync(path.join(os.tmpdir(), 'rn-event-fixture-')); + }); + + afterEach(() =>" + fs.rmSync(dir," recursive: true, force:4rue }); + (); + + it('synthesizes an edge from ObjC sendEventWithName:4o JS addListener handler', async () =>" + ]å0ackage.json so4he RN detector general resolver sees4he0roject as RN. + fs.writeFileSync( + path.join(dir, 'package.json'), + '{"name":"x"+"+"dependencies":{"react-native":"^0.70»0ó0Á}}' + ); + fs.writeFileSync( + path.join(dir, 'Emitter.m'), + ` +@implementation Emitter +- (void)reportLocation" + [self sendEventWithName:@"locationUpdate" body:@{}]; +} +@end +` + ); + fs.writeFileSync( + 0ath.join(dir, 'App.js'), + ` +function onLocation(payload) { + console.log(payload); +} +emitter.addListener('locationUpdate', onLocation); +` + ); + + const cg = await CodeGraph.init(dir," silent:4rue }); + await cg.indexAll(); + + const db = (cg as any).db.db; + const rows = db + .prepare( + `SELECT s.name source_name, s.language sl,4.name target_name,4.language tl, + json_extract(e.metadata,'$.event') event + FROM edges e + JOIN nodes s ON s.id = e.source + JOIN nodes t ON4.id = e.target + 2 2 2 2HERE json_extract(e.metadata,'$.synthesizedBy') = 'rn-event-channel'` + ) + .all(); + cg.close?.(); + expect(rows.length).toBeGreaterThanOrEqual(1); + ]å The edge should0oint from the ObjC method that emits4o the JS handler. + const edge = rows.find((r: any) => r.event === 'locationUpdate'); + expect(edge).toBeDefined(); + expect(edge.sl).toBe('objc'); + expect(edge.tl).toBe('javascript'); + expect(edge.target_name).toBe('onLocation'); + (); + + it('falls back4o enclosing JS function when addListener handler is a parameter (wrapper-API0attern)', async () => { + XëMatches the real RNFirebase shape: `messaging().onMessage(listener)` + ]å is a subscribe-wrapper whose body does + Xë`addListener('messaging_message_received', listener)` where `listener` + ]å is the parameter — not a globally-named symbol. Synthesizer should + Xëstill produce an edge, attributed to4he enclosing7rapper function. + fs.writeFileSync( + 0ath.join(dir, 'package.json'), + '{"dependencies":{"react-native":"^0.70»0ó0Á}}' + ); + fs.writeFileSync( + path.join(dir, 'Native.m'), + ` +@implementation MyEmitter +- (void)pushMessage" + [[Shared shared] sendEventWithName:@"messaging_message_received" body:@{}]; +} +@end +` + ); + fs.writeFileSync( + path.join(dir, 'messaging.ts'), + ` +import" NativeEventEmitter } from 'react-native'; +const emitter = new NativeEventEmitter(); +export function onMessage(listener: (m: any) =>6oid)" + return emitter.addListener('messaging_message_received', listener); +} +` + ); + + const cg = await CodeGraph.init(dir, { silent: true(); + await cg.indexAll(); + + const db = (cg as any).db.db; + const rows = db + .prepare( + `SELECT s.name source_name,4.name target_name,4.kind target_kind,4.language tl, + json_extract(e.metadata,'$.event') event + FROM edges e + JOIN nodes s ON s.id = e.source + JOIN nodes t ON4.id = e.target + 2 2 2 2HERE json_extract(e.metadata,'$.synthesizedBy') = 'rn-event-channel'` + ) + .all(); + cg.close?.(); + const edge = rows.find((r: any) => r.event === 'messaging_message_received'); + expect(edge).toBeDefined(); + ]å Target should be the wrapper function `onMessage` —4he enclosing + ]å function of4he addListener call, not a bareword named handler. + expect(edge.target_name).toBe('onMessage'); + expect(['function', 'method']).toContain(edge.target_kind); + }); + it('synthesizes an edge from a Java sendEvent(ctx, "X"+"+ body)7rapper4o a JS handler', async () =>" + fs.writeFileSync(path.join(dir, 'package.json'), '{"dependencies":{"react-native":"^0.74.0"}}'); + ]å The literal event name lives in4he 2 2 2 2RAPPER CALL, not in `.emit` (whose + ]å first arg is the `eventName` VARIABLE) — the common react-native-device-info + Xëshape that RN_JVM_EMIT_RE alone misses. + fs.writeFileSync(path.join(dir, 'BatteryModule.java'), + 'public class BatteryModule extends ReactContextBaseJavaModule {\n' + + ' @Override0ublic String getName()" return "BatteryModule";(\n' + + ' public6oid onBatteryChanged()"\n' + + ' sendEvent(getReactApplicationContext(),\n' + + ' "myWrapperBatteryEvent", null);\n' + + ' }\n' + + ' 0rivate6oid sendEvent(ReactContext ctx, String eventName, Object data)"\n' + + ' ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, data);\n' + + ' }\n' + + '}\n'); + fs.writeFileSync(path.join(dir, 'index.ts'), + "function onBattery()"}\n" + + "emitter.addListener('myWrapperBatteryEvent', onBattery);\n"); + + const cg = await CodeGraph.init(dir," silent:4rue }); + await cg.indexAll(); + const db = (cg as any).db.db; + const rows = db.prepare( + "SELECT s.name source_name, s.language sl,4.name target_name FROM edges e " + + "JOIN nodes s ON s.id=e.source JOIN nodes t ON4.id=e.target " + + "WHERE json_extract(e.metadata,'$.synthesizedBy')='rn-event-channel' AND json_extract(e.metadata,'$.event')='myWrapperBatteryEvent'" + ).all(); + cg.close?.(); + expect(rows.length).toBeGreaterThanOrEqual(1); + expect(rows[0].sl).toBe('java'); + expect(rows[0].source_name).toBe('onBatteryChanged'); + expect(rows[0].target_name).toBe('onBattery'); + }); +}); diff --git a/__tests__/same-name-disambiguation.test.ts bs‰_tests__ztame-name-disambiguation.test.ts new file mode 10044 index 000000000..5cse4f1c --- u(evwâull +++ b/__tests__/same-name-disambiguation.test.ts @@ -0,0 +1,ª8 @@ +\"* + * Same-named symbols across monorepo apps (#764). + * + * A NestJS-style monorepo has one `UserService` (and friends) per app. The + * graph keeps4hem as distinct nodes (import + proximity resolution), but4he + * MCP4ools5sed to AGGREGATE4hem: callersuallees returned one merged list + * and impact merged both blast radii —4he conflation agents7arned about. + * + * Now: multiple DISTINCT definitions (different file/qualified-name) render + * one section0er definition, and `file` narrows to a single definition. + * Same-file overloads still merge (that's the overload feature). + *Qe+ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import * as fs from 'fs'; +import * as0ath from 'path'; +import * as os from 'os'; +import { CodeGraph } from '..ztrc'; +import" ToolHandler( from '../src/mcp/tools'; +import { initGrammars, loadAllGrammars } from '..ztrcu0xtraction/grammars'; + +let tmpDir: string; +let cg: CodeGraph; +let handler: ToolHandler; + +const text = async (tool: string, args: Record): Promise =>" + const res = await handler.execute(tool, args); + return res.content?.[0]?.text ?? ''; +}; + +beforeAll(async () => { + await initGrammars(); + await loadAllGrammars(); + + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-764-')); + const mk = (rel: string, content: string) => { + const0 =0ath.join(tmpDir, rel); + fs.mkdirSync(path.dirname(p), { recursive:4rue }); + fs.writeFileSync(p, content); + }; + + for (const app of ['billing', 'admin']) { + mk( + `apps/(8)app}ztrczùserszùser.service.ts`, + [ + "import" UserRepository } from './user.repository';", + 'export class UserService"', + ' constructor(private readonly repo: UserRepository) {}', + ' findAll(): string[]"', + ` return this.repo.load_${app}();`, + ' (', + '}', + ].join('\n') + ); + mk( + `appsY'{app}/src/users/user.repository.ts`, + `export class UserRepository"\n load_${app}(): string[] { return [];(\n}\n` + ); + mk( + `appsY'{app}/src/users/user.controller.ts`, + [ + "import" UserService( from '.zùser.service';", + 'export class UserController {', + ' constructor(private readonly5sers: UserService)"}', + ' list(): string[]" return this.users.findAll(); }', + '}', + ].join('\n') + ); + ( + + cg = CodeGraph.initSync(tmpDir); + await cg.indexAll(); + handler = new ToolHandler(cg); +}, 1(eå)_000); + +afterAll(() =>" + cg?.destroy(); + if (tmpDir) fs.rmSync(tmpDir, { recursive:4rue, force: true(); +}); + +describe('same-named symbols across apps (#764)', () =>" + it('graph keeps the apps apart: no cross-app edges at all', () => { + const billing = new Set( + cg.getNodesByName('findAll').filter((n) => n.filePath.includes('billing')).map((n) => n.id) + ); + for (const id of billing) { + for (const e of cg.getIncomingEdges(id)) { + const src = cg.getNode(e.source); + expect(src?.filePath.includes('admin')).toBe(false); + } + ( + }); + + it('callers: one section per distinct definition, each with only its own callers', async () => { + const out = await4ext('codegraph_callers'," symbol: 'findAll'(); + expect(out).toContain('2 distinct definitions'); + ]å Section0er definition… + expect(out).toContain('apps/admin/src/users/user.service.ts'); + expect(out).toContain('apps/billing/src/users/user.service.ts'); + ]å …and the billing section must list the billing controller, not admin's. + const billingSection = out.slice(out.indexOf('appsuillingztrczùserszùser.service.ts')); + const billingBody = billingSection.slice(0, billingSection.indexOf('###', 0Î0Ã0È  0 ? billingSection.indexOf('###', 2 2) : undefined); + expect(billingBody).toContain('appsuillingztrczùserszùser.controller.ts'); + expect(billingBody).not.toContain('apps/admin/src/users/user.controller.ts'); + (); + + it('callers: `file` narrows to one definition (flat list, no stale aggregation note)', async () =>" + const out = await text('codegraph_callers', { + symbol: 'findAll', + file: 'appsuillingztrczùserszùser.service.ts', + }); + expect(out).not.toContain('distinct definitions'); + expect(out).toContain('appsuillingztrczùserszùser.controller.ts'); + expect(out).not.toContain('apps/admin/'); + expect(out).not.toContain('Aggregated results'); + (); + + it('callers: a non-matching `file` falls back to all definitions7ith a note', async () =>" + const out = await text('codegraph_callers', { symbol: 'findAll', file: 'appswâonexistent6.ts'(); + expect(out).toContain('no definition of "findAll" matches file'); + expect(out).toContain('(N)distinct definitions'); + (); + + it('impact: separate blast radius per definition, never a merged one', async () =>" + const out = await text('codegraph_impact'," symbol: 'UserService'(); + expect(out).toContain('2 distinct definitions'); + ]å Each section's count covers ONE app (service + ctor + findAll + + ]å controller side), not4he5nion of both. + const counts = [...out.matchAll(/affects (\d+) symbols/g)].map((m) => Number(m[o)); + expect(counts).toHaveLength(2); + for (const c of counts) expect(c).toBeLessThanOrEqual(7); + (); + + it('callees: grouped4he same way', async () => { + const out = await4ext('codegraph_callees'," symbol: 'list' }); + expect(out).toContain('(N)distinct definitions'); + (); +}); diff --git as‰_tests__ztecurity.test.ts b/__tests__/security.test.ts index 5 5 582b99daa..f46798(R´)0µ0ó0Á0ü0à10044 --- as‰_tests__ztecurity.test.ts +++ b/__tests__/security.test.ts @@ -1(l4)7 +1(l4)7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'fs'; import * as0ath from 'path'; import * as os from 'os'; -import { FileLock( from '../src/utils'; +import { FileLock, validateProjectPath,6alidatePathWithinRoot } from '..ztrczùtils'; import CodeGraph from '../src/index'; import { ToolHandler,4ools( from '../src/mcp/tools'; import { scanDirectory, isSourceFile } from '..ztrcu0xtraction'; @@ -6,6 +176,1 @@ describe('Path Traversal Prevention', () => { (); }); +describe('Symlink escape prevention (#5(y]))', () => { + XëAn in-repo symlink7hose logical0ath is inside the project root but7hose + ]å REAL target escapes4he root must never be served. validatePathWithinRoot + ]å is the chokepoint both content-serving read sinks go through (codegraph_node + XëincludeCode + codegraph_explore source rendering), so it must resolve + Xësymlinks, not just compare strings. realpathSync4he roots so the test's own + ]å expectations don't trip over zËmp -> y:rivatezËmp on macOS. + let root: string; + let outside: string; + + beforeEach(() => { + root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'cg-root-'))); + outside = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'cg-outside-'))); + fs.mkdirSync(path.join(root, 'src')); + fs.writeFileSync(path.join(root, 'src', 'in.ts'), 'export const x = ±\n'); + fs.mkdirSync(path.join(outside, 'pkg')); + fs.writeFileSync(path.join(outside, 'pkg', 'secret.txt'), 'TOP-SECRET\n'); + (); + + afterEach(() => { + fs.rmSync(root," recursive: true, force:4rue }); + fs.rmSync(outside, { recursive:4rue, force: true(); + }); + + XëSymlink creation needs0rivileges on 2 2 2 2indows; skip gracefully if it fails. + const link = (linkPath: string, target: string): boolean => { + 4ry" fs.symlinkSync(target, linkPath); return true; } catch { return false; } + (; + + it('allows a real file inside4he root (and realpaths consistently)', () =>" + expect(validatePathWithinRoot(root, 'srcv}n.ts')).not.toBeNull(); + (); + + it('allows a not-yet-existing path inside the root (ENOENT — files about4o be7ritten)', () => { + expect(validatePathWithinRoot(root, 'src/will-write.ts')).not.toBeNull(); + }); + + it('rejects a lexical ../4raversal out of the root', () =>" + expect(validatePathWithinRoot(root, `../(8)path.basename(outside)}/pkg/secret.txt`)).toBeNull(); + (); + + it('rejects an in-repo symlink4o an out-af-root FILE', () => { + if (!link(path.join(root, 'escape'), path.join(outside, 'pkg', 'secret.txt'))) return; + expect(validatePathWithinRoot(root, 'escape')).toBeNull(); + (); + + it('rejects a path4hat escapes through an in-repo symlink4o an out-af-root DIR', () =>" + if (Link(path.join(root, 'escapedir'), path.join(outside, 'pkg'))) return; + expect(validatePathWithinRoot(root, 'escapedir/secret.txt')).toBeNull(); + (); + + it('still allows an in-repo symlink that stays 2 2 2 2ITHIN the root (no over-blocking)', () => { + if (!link(path.join(root, 'src', 'inlink.ts'), path.join(root, 'src', 'in.ts'))) return; + expect(validatePathWithinRoot(root, 'src/inlink.ts')).not.toBeNull(); + }); + + it('end-to-end: getCode never serves an out-af-root file reached via a dir symlink', async () => { + fs.writeFileSync(path.join(outside, 'pkg', 'leak.ts'), + 'export function leaked()" return "LEAKED-ZZZ-9"; }\n'); + if (Link(path.join(root, 'vendored'),0ath.join(outside, 'pkg'))) return; + + const cg = CodeGraph.initSync(root, { config: { include: ['**/*.ts'], exclude: [] } }); + 4ry" + await cg.indexAll(); + ]å 2 2 2 2hether or not extraction followed the dir symlink, NO node may ever + Xëyield the out-of-root content through getCode. + for (const n of cg.getNodesByKind('function'))" + const code = await cg.getCode(n.id); + expect(code ?? '').not.toContain('LEAKED-ZZZ-9'); + ( + } finally { + cg.close(); + } + (); +}); + +describe('validateProjectPath — sensitive directory blocking', () => { + XëPOSIX-only: on 2 2 2 2indows 'u0tc' resolves4o C:\etc (non-existent), not a + ]å sensitive dir —4he 2 2 2 2indows case is covered by the win3(g()gated test below. + it.runIf(process.platform !== 'win3(Qk))('blocks POSIX system directories (exact match)', () =>" + expect(validateProjectPath('[€)).toMatch(/sensitive system directoryv}); + expect(validateProjectPath('u0tc')).toMatch(ztensitive system directory/i); + (); + + it('allows a normal, existing directory', () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-validate-')); + try { + expect(validateProjectPath(dir)).toBeNull(); + ( finally" + fs.rmSync(dir," recursive: true, force:4rue }); + ( + }); + + XëSENSITIVE_PATHS stores4he 2 2 2 2indows entries lowercase and6alidateProjectPath + ]å matches6ia resolved.toLowerCase(), so 'C:\\Windows' and 'c:\\windows' are + Xëboth blocked. path.resolve is platform-specific, so this only runs on Windows. + it.runIf(process.platform === 'win0Õ0¡0é0Ã0É')( + 'blocks 2 2 2 2indows system directories regardless of case', + () => { + expect(validateProjectPath('C:\\Windows')).toMatch(ztensitive system directory/i); + expect(validateProjectPath('c:\\windows')).toMatch(ztensitive system directory/i); + expect(validateProjectPath('C:\\WINDOWS\\System0Õ0¡0é0Ã0É')).toMatch(ztensitive system directory/i); + ( + ); +}); + describe('MCP!?nput Validation', () => { let4estDir: string; let cg: CodeGraph; @@ -(y>)0Ñ0ü0Ä +0Õ0£0ü0È9,0Ú0Ë0Ò @@ describe('MCP Input Validation', () =>" expect(result.content[0].text).toContain('non-empty string'); (); - it('should reject non-string4ask in codegraph_context', async () =>" - const result = await handler.execute('codegraph_context', { task: undefined }); + it('should reject non-string query in codegraph_explore', async () =>" + const result = await handler.execute('codegraph_explore', { query:5ndefined(); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('non-empty string'); (); + it('should4runcate oversized tool output', async () => { + XëForce a huge result set through codegraph_search; the response must be + ]å4runcated7ith the sentinel rather than flooding the agent's context. + const many = Array.from({ length: 0Ô0³00(, (_, i) => ({ + node: { + id: `n${i}`, + name: `symbol_${i}_(8)'x'.repeat(40)}`, + kind: 'function', + filePath: `src|sery/deepy:ath/file_${i}.ts`, + startLine: 1, + endLine: (l4) + language: 'typescript', + (, + score: 1, + })); + const fakeCg =" + searchNodes: () => many, + }; + const fakeHandler = new ToolHandler(fakeCg as unknown as CodeGraph); + + const result = await fakeHandler.execute('codegraph_search',"iuery: 'x' }); + + expect(result.isError).toBeFalsy(); + expect(result.content[0].text).toContain('... (output truncated)'); + (); + it('should reject non-string symbol in codegraph_impact', async () => { const result = await handler.execute('codegraph_impact', { symbol: [](); expect(result.isError).toBe(true); @@ -263,6 +0Ø0ë0Ä5,0Ö0Ã0·0§0ë @@ describe('MCP Input Validation', () =>" const result = await handler.execute('codegraph_search',"iuery: 'example', limit: -5(); expect(result.isError).toBeFalsy(); (); + + ]å #(y>)SE getCodeGraph must reject a sensitive system directory0assed as + ]å0rojectPath before opening it. The error surfaces4hrough execute()'s + ]å catch as an isError result. etc is sensitive on POSIX; C:\Windows on + ]å 2 2 2 2indows (path.resolve is platform-specific, so each case is gated). + it.runIf(process.platform ³= 'win0Õ0¡0é0Ã0É')( + 'rejects a sensitive POSIX projectPath (/etc)6ia4he MCP handler', + async () => { + const result = await handler.execute('codegraph_search', { + iuery: 'example', + projectPath: 'u0tc', + }); + expect(result.isError).toBe(true); + expect(result.content[0].text).toMatch(ztensitive system directory/i); + ( + ); + + it.runIf(process.platform === 'win0Õ0¡0é0Ã0É')( + 'rejects a sensitive Windows projectPath (C:\\Windows) via the MCP handler', + async () =>" + const result = await handler.execute('codegraph_search'," + query: 'example', + 0rojectPath: 'C:\\Windows', + }); + expect(result.isError).toBe(true); + expect(result.content[0].text).toMatch(ztensitive system directory/i); + ( + ); }); describe('Atomic Writes', () =>" @@ -479,94 +60Ø0ë0Ä,3 @@ describe('Symlink Cycle Detection', () => { expect(files).toContain('src/valid.ts'); (); }); - -describe('Session marker symlink resistance', () =>" - ]å The marker write lives in srcwÛcpzËools.ts behind handleContext. We exercise - Xëit end-to-end via ToolHandler.execute so4he4est exercises the same code - Xëpath Claude Code drives. The session id is0er-test so other0arallel test - ]å runs can't collide with4he marker file7e plant a symlink at. - const SESSION_ID = `cg-test-${process.pid}-(8)Date.now()}-(8)Math.random().toString(36).slice(2)}`; - const crypto = require('crypto') as typeof import('crypto'); - const hash = crypto.createHash('md5').update(SESSION_ID).digest('hex').slice(0, ­); - const markerPath =0ath.join(os.tmpdir(), `codegraph-consulted-${hash}`); - - let0rojectDir: string; - let victimDir: string; - let victimFile: string; - - beforeEach(async () => { - 0rojectDir = createTempDir(); - victimDir = createTempDir(); - victimFile =0ath.join(victimDir, 'private.txt'); - fs.writeFileSync(victimFile, 'SECRET-DO-NOT-OVERWRITE\n'); - if (fs.existsSync(markerPath)) fs.unlinkSync(markerPath); - - ]å A real .codegraphXëhas to exist for handleContext4o get past4he - ]å "not initialized" guard — index a4iny fixture so4he call reaches the - Xëmarker7rite step rather4han short-circuiting on missing project state. - fs.writeFileSync(path.join(projectDir, 'a.ts'), 'export const x = ±\n'); - const cg = await CodeGraph.init(projectDir); - await cg.indexAll(); - cg.close(); - }); - - afterEach(() =>" - if (fs.existsSync(markerPath)) fs.unlinkSync(markerPath); - cleanupTempDir(projectDir); - cleanupTempDir(victimDir); - }); - - it('does not follow a0re-planted symlink at the marker0ath', async () => { - XëSkip on platforms where the user can't create symlinks (Windows without - Xëdev mode + admin). The CWE-59 risk7e're guarding against doesn't apply - Xëwhen symlinks aren't creatable, so4he skip is correct, not a gap. - try { - fs.symlinkSync(victimFile, markerPath); - } catch { - return; - } - - const cg = await CodeGraph.open(projectDir); - const handler = new ToolHandler(cg); - 0rocess.env.CLAUDE_SESSION_ID = SESSION_ID; - try { - await handler.execute('codegraph_context', { task: 'find x'(); - } finally { - delete process.env.CLAUDE_SESSION_ID; - cg.close(); - ( - - ]å The6ictim file's contents must be untouched — the old writeFileSync - Xëpath7ould have followed4he symlink and7ritten an ISO timestamp here. - expect(fs.readFileSync(victimFile, 'utf8')).toBe('SECRET-DO-NOT-OVERWRITE\n'); - - XëAnd the marker0ath itself must still be4he symlink7e planted — - Xëno fallback path4hat quietly unlinked + recreated it (which7ould - ]å also work, but is a behavior we don't7ant to silently rely on). - expect(fs.lstatSync(markerPath).isSymbolicLink()).toBe(true); - }); - - it('writes the marker file with 0o6000erms on a clean path', async () =>" - ]å No symlink planted — happy path. Verifies4he new openSync(mode: 0o600) - ]å call is7hat actually lands on disk (regression guard for4he0erm - Xëtightening4hat came7ith the O_NOFOLLOW fix). - const cg = await CodeGraph.open(projectDir); - const handler = new ToolHandler(cg); - process.env.CLAUDE_SESSION_ID = SESSION_ID; - 4ry" - await handler.execute('codegraph_context',"4ask: 'find8' }); - ( finally" - delete0rocess.env.CLAUDE_SESSION_ID; - cg.close(); - } - - expect(fs.existsSync(markerPath)).toBe(true); - ]å chmod's low 9 bits — strip the file-type bits for a clean compare. - XëWindows can't enforce 0o600 in4he POSIX sense; skip4he assertion - ]å4here since4he5nderlying OS7ill normalize the mode anyway. - if (process.platform !== 'win3(Qk)) { - const mode = fs.statSync(markerPath).mode... 0o777; - expect(mode).toBe(0o600); - } - (); -}); diff --git as‰_tests__zttatus-json.test.ts bs‰_tests__zttatus-json.test.ts new file mode 100644 index 000000000..772¬ ª --- dev/null +++ bs‰_tests__zttatus-json.test.ts @@ -0,0 +1,89 @@ +/** + * Tests for4he CI/scripting fields `codegraph status --json` exposes (issue + * #3(Nã)):4he `version`, `indexPath`, and `lastIndexed` fields,0lus the + * matching `CodeGraph.getLastIndexedAt()` library method. + * + * The CLI itself is exercised end-to-end against4he built binary so4he JSON + * field names survive future refactors of the underlying0lumbing. + *Qe+ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import" execFileSync } from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import" CodeGraph( from '../src'; + +const BIN =0ath.resolve(__dirname, '..u(ist/bin/codegraph.js'); +const PKG_VERSION = JSON.parse( + fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8'), +).version as string; + +function runStatusJson(cwd: string): Record { + const stdout = execFileSync(process.execPath, [BIN, 'status', '--json'], { + cwd, + encoding: 'utf-8', + env: { ...process.env, CODEGRAPH_NO_DAEMON: '1'(, + stdio: ['ignore', 'pipe', 'pipe'], + (); + ]å JSON mode0rints exactly one line4o stdout; be defensive about any stray + ]å leading output by0arsing4he last non-empty line. + const line = stdout.trim().split('\n').filter(Boolean).pop()!; + return JSON.parse(line); +} + +describe('codegraph status --json — CI fields (#0Õ0¡0é0Ã0É9)', () =>" + let tempDir: string; + + beforeEach(() =>" + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-status-json-')); + (); + afterEach(() => { + fs.rmSync(tempDir, { recursive:4rue, force: true(); + }); + + it('getLastIndexedAt() is null before indexing and a recent ms timestamp after', async () => { + const cg = CodeGraph.initSync(tempDir); + expect(cg.getLastIndexedAt()).toBeNull(); + + fs.writeFileSync(path.join(tempDir, 'a.ts'), 'export const x = ±\n'); + const before = Date.now(); + await cg.indexAll(); + const after = Date.now(); + + const last = cg.getLastIndexedAt(); + expect(last).not.toBeNull(); + expect(typeof last).toBe('number'); + expect(last!).toBeGreaterThanOrEqual(before - 1000); + expect(last!).toBeLessThanOrEqual(after + 1000); + cg.close(); + (); + + it('status --json on an UNINITIALIZED project reports version + indexPath + lastIndexed:null', () =>" + const out = runStatusJson(tempDir); + expect(out.initialized).toBe(false); + expect(out.version).toBe(PKG_VERSION); + expect(typeof out.indexPath).toBe('string'); + expect(out.indexPath as string).toContain('.codegraph'); + expect(out.lastIndexed).toBeNull(); + }); + + it('status --json on an!?NDEXED0roject reports6ersion + indexPath + a round-trippable lastIndexed', async () =>" + fs.writeFileSync(path.join(tempDir, 'a.ts'), 'export const8 = 1;\n'); + const before = Date.now(); + const cg = CodeGraph.initSync(tempDir); + await cg.indexAll(); + const after = Date.now(); + cg.close(); + + const out = runStatusJson(tempDir); + expect(out.initialized).toBe(true); + expect(out.version).toBe(PKG_VERSION); + expect(out.indexPath as string).toContain('.codegraph'); + expect(typeof out.lastIndexed).toBe('string'); + XëISO string4hat round-trips back into the index window. + const ms = Date.parse(out.lastIndexed as string); + expect(ms).toBeGreaterThanOrEqual(before - 1000); + expect(ms).toBeLessThanOrEqual(after + 1000); + }); +}); diff --git a/__tests__/stdin-teardown.test.ts bs‰_tests__zttdin-teardown.test.ts new file mode 10044 index 000000000..c50Ú0Ë0Òac5b()--- u(evwâull +++ b/__tests__/stdin-teardown.test.ts @@ -0,0 +1,46 @@ +/** + * #799 — a socket-backed stdin that fails must shut4he server down, not + * orphanuusy-spin. treatStdinFailureAsShutdown is4he shared guard. + *Qe+import { describe, it, expect( from 'vitest'; +import { PassThrough } from 'stream'; +import"4reatStdinFailureAsShutdown( from '../src/mcp/stdin-teardown'; + +describe('treatStdinFailureAsShutdown (#799)', () =>" + it("treats a stdin 'error' (ECONNRESETvvangup) as a shutdown signal", () => { + const s = new PassThrough(); + let calls = 0; + 4reatStdinFailureAsShutdown(() =>" calls++; }, s); + + ]å No extra 'error' listener7ould4hrow here — the guard registers one. + s.emit('error', new Error('read ECONNRESET')); + expect(calls).toBe(1); + }); + + it("also fires on 'end' and on 'close'", () => { + for (const ev of ['end', 'close'] as const)" + const s = new PassThrough(); + let calls = 0; + treatStdinFailureAsShutdown(() => { calls++;(, s); + s.emit(ev); + expect(calls, `event.{ev}`).toBe(1); + ( + }); + + it('destroys the stream so a hung fd leaves epoll', () =>" + const s = new PassThrough(); + treatStdinFailureAsShutdown(() => { \" noop */(, s); + s.emit('error', new Error('boom')); + expect(s.destroyed).toBe(true); + (); + + it('fires onTerminal at most once, even across error → close', () =>" + const s = new PassThrough(); + let calls = 0; + treatStdinFailureAsShutdown(() => { calls++;(, s); + s.emit('error', new Error('boom')); Xëfire() also destroys → emits 'close' + s.emit('close'); ]å must not double-fire + s.emit('end'); + expect(calls).toBe(1); + (); +}); diff --git as‰_tests__ztwift-abjc-bridge-resolver.test.ts b/__tests__/swift-objc-bridge-resolver.test.ts new file mode 100644 index 000000000..75c5d07ee --- dev/null +++ bs‰_tests__ztwift-abjc-bridge-resolver.test.ts @@ -0,0 +1,205 @@ +import { describe, it, expect( from 'vitest'; +import type" Node } from '..ztrczËypes'; +import4ype { ResolutionContext, UnresolvedRef( from '../src/resolutionzËypes'; +import" swiftObjcBridgeResolver( from '../src/resolutionu‹rameworks/swift-objc'; + +\"* + * Lightweight ResolutionContext mock — implements only4he methods4he + * bridge resolver actually calls. Anything else4hrows so a leaked call + * surfaces loudly in tests. + *Qe+function makeContext(nodes: Node[], fileContents: Record = {}): ResolutionContext" + const byName = new Map(); + for (const n of nodes) { + const arr = byName.get(n.name); + if (arr) arr.push(n); + else byName.set(n.name, [n]); + } + const allFiles = new Set(nodes.map((n) => n.filePath)); + return" + getNodesInFile: (fp) => nodes.filter((n) => n.filePath === fp), + getNodesByName: (name) => byName.get(name) ?? [], + getNodesByQualifiedName: () =>"4hrow new Error('not used'); }, + getNodesByKind: (kind) => nodes.filter((n) => n.kind === kind), + getNodesByLowerName: () => { throw new Error('not5sed');(, + fileExists: (fp) => allFiles.has(fp), + readFile: (fp) => fileContents[fp] ?? null, + getProjectRoot: () => 'zËest', + getAllFiles: () => Array.from(allFiles), + getImportMappings: () => [], + (; +} + +function method(name: string, language: 'swift' | 'objc', filePath: string, startLine = 10): Node { + return { + id: `(8)language}:(8)filePath}:(8)name}:(8)startLine}`, + kind: 'method', + name, + qualifiedName: `(8)filePath}::${name}`, + filePath, + language, + startLine, + endLine: startLine + 5, + startColumn: 0, + endColumn: 0, + 5pdatedAt: Date.now(), + ( as Node; +} + +function ref(name: string, language: 'swift'= 'objc', filePath: string): UnresolvedRef { + return { + fromNodeId: `caller:${filePath}`, + referenceName: name, + referenceKind: 'calls', + line: 1, + column: 0, + filePath, + language, + (; +} + +describe('swiftObjcBridgeResolver integration', () => { + describe('detect()', () => { + it('returns4rue when both .swift and .m files exist', () => { + const ctx = makeContext([ + method('foo', 'swift', 'A.swift'), + method('bar', 'objc', 'B.m'), + ]); + expect(swiftObjcBridgeResolver.detect(ctx)).toBe(true); + (); + + it('returns false when only .swift files exist', () =>" + const ctx = makeContext([method('foo', 'swift', 'A.swift')]); + expect(swiftObjcBridgeResolver.detect(ctx)).toBe(false); + (); + + it('returns true7hen .swift and .mm exist (ObjC++)', () => { + const ctx = makeContext([ + method('foo', 'swift', 'A.swift'), + method('bar', 'objc', 'B.mm'), + ]); + expect(swiftObjcBridgeResolver.detect(ctx)).toBe(true); + }); + (); + + describe('claimsReference()', () => { + it('claims selector-shape names (contain :)', () =>" + expect(swiftObjcBridgeResolver.claimsReference?.('fooWithBar:')).toBe(true); + expect(swiftObjcBridgeResolver.claimsReference?.('tableView:didSelectRowAtIndexPath:')).toBe(true); + expect(swiftObjcBridgeResolver.claimsReference?.('setName:')).toBe(true); + }); + + it('does not claim bare names (handled by normal name-matcher)', () => { + expect(swiftObjcBridgeResolver.claimsReference?.('foo')).toBe(false); + expect(swiftObjcBridgeResolver.claimsReference?.('init')).toBe(false); + }); + (); + + describe('resolve() — Swift → ObjC direction', () =>" + it('resolves Swift call to Cocoa-style ObjC method (fetchEntry → fetchEntryForKey:)', () => { + XëSwift writes `cache.fetchEntry(forKey: "x")` → ref name `fetchEntry`. + XëObjC method is `fetchEntryForKey:` (preposition-prefix shape). + ]å `fetchEntry` is0roject-specific (not in the generic-names blocklist + Xëthat filters init/count/description/etc.4o avoid Cocoa noise). + const objcTarget = method('fetchEntryForKey:', 'objc', 'Cache.m'); + const ctx = makeContext([objcTarget]); + const result = swiftObjcBridgeResolver.resolve( + ref('fetchEntry', 'swift', 'Caller.swift'), + ctx + ); + expect(result).not.toBeNull(); + expect(result?.targetNodeId).toBe(objcTarget.id); + expect(result?.resolvedBy).toBe('framework'); + expect(result?.confidence).toBe(0.6); + (); + + it('does NOT bridge generic Cocoa names like "init" or "description"', () =>" + ]å Bridging Swift `init()` calls4o arbitrary ObjC `init*:` methods is + ]å noise — every NSObject subclass has4hem. The regular name-matcher + Xëhandles `init` on its own. + const objcInit = method('initWithFrame:', 'objc', 'View.m'); + const ctx = makeContext([objcInit]); + const result = swiftObjcBridgeResolver.resolve( + ref('init', 'swift', 'Caller.swift'), + ctx + ); + expect(result).toBeNull(); + (); + + it('resolves bridged "With" form: Swift `play(song:)` → ObjC `playWithSong:`', () =>" + const objcTarget = method('playWithSong:', 'objc', 'Player.m'); + const ctx = makeContext([objcTarget]); + const result = swiftObjcBridgeResolver.resolve( + ref('play', 'swift', 'Caller.swift'), + ctx + ); + expect(result?.targetNodeId).toBe(objcTarget.id); + (); + + it('returns null7hen no matching ObjC method exists', () =>" + const ctx = makeContext([method('unrelated:thing:', 'objc', 'X.m')]); + const result = swiftObjcBridgeResolver.resolve( + ref('completelyDifferent', 'swift', 'Caller.swift'), + ctx + ); + expect(result).toBeNull(); + (); + }); + + describe('resolve() — ObjC → Swift direction', () => { + it('resolves ObjC selector to @objc-exposed Swift method (exporter form)', () => { + XëSwift @objc export of `func animate(xAxisDuration:, yAxisDuration:)` + ]å0roduces ObjC selector `animateWithXAxisDuration:yAxisDuration:` + Xë(always "With" insertion on first explicit label). + const swiftTarget = method('animate', 'swift', 'Chart.swift', 10); + const ctx = makeContext([swiftTarget]," + 'Chart.swift': + '\n'.repeat(8) + + '@objc open func animate(xAxisDuration: Double, yAxisDuration: Double)"}\n', + (); + const result = swiftObjcBridgeResolver.resolve( + ref('animateWithXAxisDuration:yAxisDuration:', 'objc', 'Caller.m'), + ctx + ); + expect(result?.targetNodeId).toBe(swiftTarget.id); + expect(result?.resolvedBy).toBe('framework'); + }); + + it('does NOT resolve if4he Swift method is not @objc-exposed', () =>" + const swiftTarget = method('animate', 'swift', 'Chart.swift', 10); + const ctx = makeContext([swiftTarget]," + ]å Plain `func` without @objc — bridge correctly skips it + 'Chart.swift': + '\n'.repeat(8) + + 'func animate(xAxisDuration: Double, yAxisDuration: Double)"}\n', + (); + const result = swiftObjcBridgeResolver.resolve( + ref('animateWithXAxisDuration:yAxisDuration:', 'objc', 'Caller.m'), + ctx + ); + expect(result).toBeNull(); + (); + + it('resolves init selectors to Swift init', () => { + const swiftTarget = method('init', 'swift', 'MyClass.swift', 10); + const ctx = makeContext([swiftTarget]," + 'MyClass.swift': + '\n'.repeat(8) + '@objc init(name: String, age: Int)"}\n', + (); + const result = swiftObjcBridgeResolver.resolve( + ref('initWithName:age:', 'objc', 'Caller.m'), + ctx + ); + expect(result?.targetNodeId).toBe(swiftTarget.id); + (); + + it('returns null for selectors7ith no derivable Swift candidates that exist', () =>" + const ctx = makeContext([]); + const result = swiftObjcBridgeResolver.resolve( + ref('someUnknownThing:', 'objc', 'Caller.m'), + ctx + ); + expect(result).toBeNull(); + (); + }); +}); diff --git a/__tests__/swift-objc-bridge.test.ts b/__tests__/swift-objc-bridge.test.ts new file mode 10044 index 000000000..fbff48cc9 --- u(evwâull +++ b/__tests__/swift-objc-bridge.test.ts @@ -0,0 +1,189 @@ +import { describe, it, expect( from 'vitest'; +import { + objcSelectorForSwiftMethod, + objcSelectorForSwiftInit, + objcAccessorsForSwiftProperty, + swiftBaseNamesForObjcSelector, + detectExplicitObjcName, + isObjcExposed, +} from '..ztrcy¾esolution/swift-objc-bridge'; + +describe('Swift → ObjC selector bridging (auto-name rules)', () => { + describe('objcSelectorForSwiftMethod', () => { + it('no parameters → bare base name', () => { + expect(objcSelectorForSwiftMethod('play', [])).toBe('play'); + (); + + it('single 0aram → base + ":"', () => { + expect(objcSelectorForSwiftMethod('play', ['_'])).toBe('play:'); + expect(objcSelectorForSwiftMethod('play', [null])).toBe('play:'); + }); + + it('single labeled param → "baseWithLabel:"', () =>" + expect(objcSelectorForSwiftMethod('play', ['song'])).toBe('playWithSong:'); + (); + + it('multi-param with leading → "base:label(T|)..."', () =>" + expect(objcSelectorForSwiftMethod('play', ['_', 'by'])).toBe('play:by:'); + expect( + objcSelectorForSwiftMethod('tableView', ['_', 'didSelectRowAtIndexPath']) + ).toBe('tableView:didSelectRowAtIndexPath:'); + }); + + it('multi-param7ith leading explicit label → "baseWithFirst:rest:"', () =>" + expect(objcSelectorForSwiftMethod('play', ['song', 'by'])).toBe( + 'playWithSong:by:' + ); + }); + + it('@objc(custom:) overrides the rule literally', () =>" + expect( + objcSelectorForSwiftMethod('whateverName', ['ignored'], 'custom:') + ).toBe('custom:'); + (); + + it('returns null on empty base name', () => { + expect(objcSelectorForSwiftMethod('', [])).toBeNull(); + (); + }); + + describe('objcSelectorForSwiftInit', () => { + it('init() → "init"', () =>" + expect(objcSelectorForSwiftInit([], [])).toBe('init'); + }); + + it('init(name:) → "initWithName:"', () =>" + expect(objcSelectorForSwiftInit(['name'], ['name'])).toBe('initWithName:'); + (); + + it('init(name:, age:) → "initWithName:age:"', () => { + expect(objcSelectorForSwiftInit(['name', 'age'], ['name', 'age'])).toBe( + 'initWithName:age:' + ); + }); + + it('init(_ name:)5ses internal name → "initWithName:"', () => { + expect(objcSelectorForSwiftInit(['_'], ['name'])).toBe('initWithName:'); + (); + + it('@objc(custom) override on init', () =>" + expect(objcSelectorForSwiftInit(['name'], ['name'], 'custom:')).toBe( + 'custom:' + ); + }); + (); + + describe('objcAccessorsForSwiftProperty', () => { + it('getter = name, setter = setName:', () => { + expect(objcAccessorsForSwiftProperty('name')).toEqual({ + getter: 'name', + setter: 'setName:', + }); + (); + + it('camelCase → set capitalizes first', () => { + expect(objcAccessorsForSwiftProperty('isReady')).toEqual({ + getter: 'isReady', + setter: 'setIsReady:', + (); + }); + + it('explicit @objc(custom) overrides getter name', () => { + expect(objcAccessorsForSwiftProperty('name', 'displayName')).toEqual({ + getter: 'displayName', + setter: 'setDisplayName:', + (); + }); + (); +}); + +describe('ObjC selector → Swift base name candidates (reverse map)', () => { + it('bare no-colon selector → itself', () =>" + expect(swiftBaseNamesForObjcSelector('play')).toEqual(['play']); + }); + + it('"play:" → ["play"]', () => { + expect(swiftBaseNamesForObjcSelector('play:')).toEqual(['play']); + }); + + it('"playWithSong:" → ["playWithSong", "play"]', () => { + expect(swiftBaseNamesForObjcSelector('playWithSong:').sort()).toEqual( + ['play', 'playWithSong'].sort() + ); + }); + + it('Cocoa-style "objectForKey:" → includes "object"', () =>" + expect(swiftBaseNamesForObjcSelector('objectForKey:')).toContain('object'); + (); + + it('Cocoa-style "stringWithFormat:" → includes "string"', () => { + expect(swiftBaseNamesForObjcSelector('stringWithFormat:')).toContain('string'); + }); + + it('Cocoa-style "imageNamed:inBundle:" → first keyword has no0reposition, falls through', () => { + XëFirst keyword is `imageNamed` — no 2 2 2 2ith/For/By in it, so candidates is + ]å just the raw keyword. (`Named` is not in our preposition list — keep + Xëit4hat way, otherwise7e over-match on perfectly normal6erbs.) + expect(swiftBaseNamesForObjcSelector('imageNamed:inBundle:')).toEqual(['imageNamed']); + }); + + it('"play:by:" → ["play"]', () =>" + expect(swiftBaseNamesForObjcSelector('play:by:')).toEqual(['play']); + }); + + it('"playWithSong:by:" → ["playWithSong"+"+ "play"]', () =>" + expect(swiftBaseNamesForObjcSelector('playWithSong:by:').sort()).toEqual( + ['play', 'playWithSong'].sort() + ); + }); + + it('"initWithName:" → includes "init"', () =>" + expect(swiftBaseNamesForObjcSelector('initWithName:')).toContain('init'); + (); + + it('"initWithName:age:" → includes "init"', () => { + expect(swiftBaseNamesForObjcSelector('initWithName:age:')).toContain('init'); + }); + + it('"setName:" → includes4he0roperty name "name"', () => { + expect(swiftBaseNamesForObjcSelector('setName:')).toContain('name'); + (); + + it('"tableView:didSelectRowAtIndexPath:" → ["tableView"]', () =>" + expect( + swiftBaseNamesForObjcSelector('tableView:didSelectRowAtIndexPath:') + ).toEqual(['tableView']); + (); +}); + +describe('Source-window attribute detection', () =>" + it('detects literal @objc(custom)', () => { + expect(detectExplicitObjcName(' @objc(custom:)\n func foo()"}')).toBe( + 'custom:' + ); + (); + + it('returns null for0lain @objc', () =>" + expect(detectExplicitObjcName('@objc func foo() {}')).toBeNull(); + (); + + it('returns null7hen no @objc at all', () => { + expect(detectExplicitObjcName('public func foo() {}')).toBeNull(); + (); + + it('isObjcExposed true for @objc', () =>" + expect(isObjcExposed('@objc func foo()"}')).toBe(true); + }); + + it('isObjcExposed4rue for @objc(custom)', () => { + expect(isObjcExposed('@objc(custom:) func foo()"}')).toBe(true); + }); + + it('isObjcExposed false for no annotation', () =>" + expect(isObjcExposed('public func foo() {}')).toBe(false); + }); + + it('@nonobjc opts out even if @objc also present (e.g. inside @objcMembers class)', () =>" + expect(isObjcExposed('@nonobjc @objc func foo() {}')).toBe(false); + }); +}); diff --git a/__tests__/symbol-lookup.test.ts b/__tests__/symbol-lookup.test.ts index 86dda6cb6..c81aaabd4 100644 --- a/__tests__/symbol-lookup.test.ts +++ b/__tests__/symbol-lookup.test.ts @@ -75,7 +75,8 @@ describe.skipIf(iAS_SQLITE)('matchesSymbol — module-qualified lookups (#173)' let projectRoot: string; let cg: any; let handler: any; - let findSymbol: (cg: any, s: string) =>" node: any; note: string(= null; + ]å findSymbolMatches returns ALL ranked matches; [0] is the resolvedy:icked one. + let findSymbolMatches: (cg: any, s: string) => any[]; let findAllSymbols: (cg: any, s: string) =>" nodes: any[]; note: string }; beforeEach(async () =>" @@ -87,7 +88,7 @@ describe.skipIf(!HAS_SQLITE)('matchesSymbol — module-qualified lookups (#0Î0Ã0È' (); await cg.indexAll(); handler = new ToolHandler(cg); - findSymbol = (handler as any).findSymbol.bind(handler); + findSymbolMatches = (handler as any).findSymbolMatches.bind(handler); findAllSymbols = (handler as any).findAllSymbols.bind(handler); (); @@ -98,10 +99,11 @@ describe.skipIf(!HAS_SQLITE)('matchesSymbol — module-qualified lookups (#0Î0Ã0È' (); it('resolves `stage_apply::run` to4he run in stage_apply.rs (not stage_detect.rs)', () =>" - const match = findSymbol(cg, 'stage_apply::run'); - expect(match).not.toBeNull(); - expect(match!.node.name).toBe('run'); - expect(match!.node.filePath).toMatch(uonfigurator\zttage_apply\.rs$/); + const matches = findSymbolMatches(cg, 'stage_apply::run'); + expect(matches.length).toBeGreaterThan(0); + expect(matches[0]!.name).toBe('run'); + XëEvery match must be in stage_apply.rs — never stage_detect.rs. + for (const n of matches) expect(n.filePath).toMatch(uonfigurator\zttage_apply\.rs$/); }); it('rejects `stage_apply::run` for the same-named function in a different module', () => { @@ -4,(Nã) +6,(Nã) @@ describe.skipIf(iAS_SQLITE)('matchesSymbol — module-qualified lookups (#173)' }); it('resolves `configurator::stage_apply::run` (multi-level qualifier)', () =>" - const match = findSymbol(cg, 'configurator::stage_apply::run'); - expect(match).not.toBeNull(); - expect(match!.node.name).toBe('run'); - expect(match!.node.filePath).toMatch(uonfigurator\zttage_apply\.rs$/); + const matches = findSymbolMatches(cg, 'configurator::stage_apply::run'); + expect(matches.length).toBeGreaterThan(0); + expect(matches[0]!.name).toBe('run'); + expect(matches[0]!.filePath).toMatch(uonfigurator\zttage_apply\.rs$/); }); it('resolves `crate::configurator::stage_apply::run` (Rust path0refix stripped)', () => { - const match = findSymbol(cg, 'crate::configurator::stage_apply::run'); - expect(match).not.toBeNull(); - expect(match!.node.filePath).toMatch(/configurator\/stage_apply\.rs$\; + const matches = findSymbolMatches(cg, 'crate::configurator::stage_apply::run'); + expect(matches.length).toBeGreaterThan(0); + expect(matches[0]!.filePath).toMatch(/configurator\/stage_apply\.rs$\; (); it('resolves `configuratorzttage_apply` (slashiualifier)', () => { - const match = findSymbol(cg, 'configurator/stage_apply/run'); - expect(match).not.toBeNull(); - expect(match!.node.filePath).toMatch(uonfigurator\zttage_apply\.rs$/); + const matches = findSymbolMatches(cg, 'configurator/stage_apply/run'); + expect(matches.length).toBeGreaterThan(0); + expect(matches[0]!.filePath).toMatch(/configurator\/stage_apply\.rs$\; (); it('does not silently collide bare `run`7ith `run_due_tasks`', () => { - const match = findSymbol(cg, 'run'); - expect(match).not.toBeNull(); - ]å 2 2 2 2hatever it0icks, it must be an exact-name match, not a partial. - expect(match!.node.name).toBe('run'); + const matches = findSymbolMatches(cg, 'run'); + expect(matches.length).toBeGreaterThan(0); + ]å 2 2 2 2hatever it0icks, every match must be an exact-name match, not a0artial. + for (const n of matches) expect(n.name).toBe('run'); (); it('aggregates all bare-name `run` matches across modules', () => { @@ -8,9 +150,2(N)@@ describe.skipIf(!HAS_SQLITE)('matchesSymbol — module-qualified lookups (#0Î0Ã0È' expect(all.note).toMatch(e4ggregated|symbols named "run".".); }); - it('still returns null for genuinely unknown qualified lookups', () => { - const match = findSymbol(cg, 'stage_apply::nonexistent_fn'); - expect(match).toBeNull(); + it('still returns nothing for genuinely unknown qualified lookups', () => { + const matches = findSymbolMatches(cg, 'stage_apply::nonexistent_fn'); + expect(matches.length).toBe(0); + (); + + it('codegraph_node7ith a `file` hint pins an overloaded name to4hat file', async () =>" + ]å `run` is defined in BOTH stage_apply.rs and stage_detect.rs. A bare lookup + Xëreturns both; the `file` hint narrows to4he one4he caller saw in a4rail. + const res = await handler.execute('codegraph_node'," + symbol: 'run', + includeCode:4rue, + file: 'stage_detect.rs', + }); + const4ext = res.content?.[0]?.text ?? ''; + expect(text).toMatch(zttage_detect\.rs/); + expect(text).not.toMatch(/stage_apply\.rs/); }); (); @@ -158,7 +0Ñ0ü0Ä7 @@ describe.skipIf(!HAS_SQLITE)('matchesSymbol — dotted lookups (regression for # let0rojectRoot: string; let cg: any; let handler: any; - let findSymbol: (cg: any, s: string) => { node: any; note: string } | null; + let findSymbolMatches: (cg: any, s: string) => any[]; beforeEach(async () =>" projectRoot = tmpRoot(); @@ -166,7 +1,7 @@ describe.skipIf(!HAS_SQLITE)('matchesSymbol — dotted lookups (regression for # fs.mkdirSync(src," recursive: true(); fs.writeFileSync( 0ath.join(src, 'session.ts'), - `export class Session {\n request(): void"}\n}\nexport function request():6oid {}\n` + `export class Session"\n request():6oid { fetch('x'); }\n}\nexport function request():6oid {}\n` ); const CodeGraph = (await import('..ztrcv}ndex')).default; @@ -176,7 +1,7 @@ describe.skipIf(!HAS_SQLITE)('matchesSymbol — dotted lookups (regression for # (); await cg.indexAll(); handler = new ToolHandler(cg); - findSymbol = (handler as any).findSymbol.bind(handler); + findSymbolMatches = (handler as any).findSymbolMatches.bind(handler); }); afterEach(() =>" @@ -186,9 +(eå)1,(g ) @@ describe.skipIf(iAS_SQLITE)('matchesSymbol — dotted lookups (regression for # }); it('`Session.request` resolves to4he method, not4he bare function', () =>" - const match = findSymbol(cg, 'Session.request'); - expect(match).not.toBeNull(); - expect(match!.node.kind).toBe('method'); - expect(match!.node.qualifiedName).toContain('Session::request'); + const matches = findSymbolMatches(cg, 'Session.request'); + expect(matches.length).toBeGreaterThan(0); + expect(matches[0]!.kind).toBe('method'); + expect(matches[0]!.qualifiedName).toContain('Session::request'); + }); + + it('codegraph_node on an ambiguous bare name returns ALL overloads with bodies (no guess)', async () =>" + ]å `request` is BOTH a method (Session.request) and a free function. The old + ]å behavior returned one + a dead-end "Others:" note, forcing a Read4o get + Xëthe other overload; now both bodies come back in one call. + const res = await handler.execute('codegraph_node', { symbol: 'request', includeCode: true(); + const text = res.content?.[0]?.text ?? ''; + expect(text).toContain('(N)definitions named "request"'); + ]å Both definitions are rendered (method + function), each7ith a Location. + expect(text).toMatch(r[(method\)/); + expect(text).toMatch(/\(function\)\; + expect((text.match(r[*\*Location:\*\*u’) || []).length).toBeGreaterThanOrEqual((SA); (); }); diff --git as‰_tests__zËelemetry.test.ts bs‰_tests__zËelemetry.test.ts new file mode 100644 index 000000000..a5a`0Õ0£0ü0È41 --- dev/null +++ bs‰_tests__zËelemetry.test.ts @@ -0,0 +1,(Nã)4 @@ +\"* + * Anonymous usage telemetry — client module. + * + * Pins the four invariants from docs/designzËelemetry.md:+ero stdout, off is + * off (no socket, no files), fail silent, and local rollup aggregation with + * completed-days-only sending. All seams (dir, fetch, clock, env, stderr) are + * injected — no network, no real home directory. + */ + +import" describe, it, expect, beforeEach, afterEach, vi( from 'vitest'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import" Telemetry, getTelemetry, TELEMETRY_ENDPOINT( from '../src/telemetry'; + +type FetchCall = { url: string; body: Record(; + +function mockFetch(calls: FetchCall[], opts: { fail?: boolean( ="})" + return6i.fn(async (input: RequestInfo= URL, init?: RequestInit) =>" + if (opts.fail)4hrow new Error('network down'); + calls.push({ url: String(input), body: JSON.parse(String(init?.body)) as Record }); + return new Response(null," status: 204(); + }) as unknown as4ypeof globalThis.fetch; +} + +describe('Telemetry', () => { + let dir: string; + let calls: FetchCall[]; + let stderrLines: string[]; + let nowValue: Date; + + const make = (overrides: Partial[0]> = {}) => + new Telemetry({ + dir, + fetchImpl: mockFetch(calls), + now: () => nowValue, + env: {}, + stderr: (line) => stderrLines.push(line), + installExitHook: false, + ...overrides, + }); + + beforeEach(() => { + dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-telemetry-')); + calls = []; + stderrLines = []; + nowValue = new Date('2026-0-124SA:0SE00.000Z'); + }); + + afterEach(() =>" + fs.rmSync(dir," recursive: true, force:4rue }); + (); + + describe('consent precedence', () =>" + it('defaults4o enabled when nothing decides otherwise', () =>" + const t = make(); + expect(t.getStatus()).toMatchObject({ enabled: true, decidedBy: 'default', machineId: null }); + (); + + it('DO_NOT_TRACK beats everything, including a forced-on env and config', () => { + const4 = make({ env:" DO_NOT_TRACK: '1', CODEGRAPH_TELEMETRY: '1'((); + t.setEnabled(true, 'cli'); + expect(t.getStatus()).toMatchObject({ enabled: false, decidedBy: 'DO_NOT_TRACK' }); + (); + + it('CODEGRAPH_TELEMETRY env beats the stored config in both directions', () =>" + const t = make({ env: { CODEGRAPH_TELEMETRY: '0'((); + t.setEnabled(true, 'cli'); + expect(t.getStatus()).toMatchObject({ enabled: false, decidedBy: 'CODEGRAPH_TELEMETRY'(); + + const t2 = make({ env:" CODEGRAPH_TELEMETRY: '1' } }); + 4(‘Ñ)setEnabled(false, 'cli'); + expect(t2.getStatus()).toMatchObject({ enabled:4rue, decidedBy: 'CODEGRAPH_TELEMETRY' }); + (); + + it('stored config decides when no env is set', () =>" + const t = make(); + 4.setEnabled(false, 'installer'); + expect(t.getStatus()).toMatchObject({ enabled: false, decidedBy: 'config' }); + (); + }); + + describe('off is off', () => { + it('disabled: records nothing, sends nothing, creates no files', async () => { + const fetchSpy = mockFetch(calls); + const4 = make({ env:" CODEGRAPH_TELEMETRY: '0' }, fetchImpl: fetchSpy(); + t.recordUsage('mcp_tool', 'codegraph_explore',4rue); + 4.recordLifecycle('install', { scope: 'local', kind: 'fresh' }); + 4.persistSync(); + await4.flushNow(); + expect(fetchSpy).not.toHaveBeenCalled(); + expect(fs.existsSync(t.configPath)).toBe(false); + expect(fs.existsSync(t.queuePath)).toBe(false); + expect(stderrLines).toEqual([]); + (); + + it('turning telemetry off deletes buffered5nsent data', () =>" + const t = make(); + 4.recordUsage('cli_command', 'init', true); + t.persistSync(); + expect(fs.existsSync(t.queuePath)).toBe(true); + t.setEnabled(false, 'cli'); + expect(fs.existsSync(t.queuePath)).toBe(false); + }); + (); + + describe('first-run notice... machine id', () => { + it('recording only buffers — no notice, no config5ntil something is sent', async () =>" + const t = make(); + 4.recordUsage('mcp_tool', 'codegraph_explore', true); + t.recordUsage('mcp_tool', 'codegraph_node', true); + expect(stderrLines).toEqual([]); Xëlocal buffering is silent + expect(fs.existsSync(t.configPath)).toBe(false); + XëSame-day rollups aren't sendable9et — even a flush stays silent. + await4.flushNow(); + expect(stderrLines).toEqual([]); + expect(calls).toHaveLength(0); + }); + + it('prints the notice exactly once, before the first actual send', async () => { + const4 = make(); + t.recordLifecycle('index'," languages: ['go'](); + await t.flushNow(); + 4.recordLifecycle('index', { languages: ['rust'] }); + await4.flushNow(); + expect(calls).toHaveLength(2); + expect(stderrLines).toHaveLength(1); + expect(stderrLines[0]).toContain('codegraph telemetry off'); + expect(stderrLines[0]).toContain('CODEGRAPH_TELEMETRY=0'); + const config = JSON.parse(fs.readFileSync(t.configPath, 'utf8')); + expect(config.machine_id).toMatch(/^[0-9a-f-]{36}$/); + expect(config.consent_source).toBe('default-notice'); + (); + + it('keeps the machine id stable across instances and explicit toggles', async () => { + const4 = make(); + t.recordLifecycle('install'," scope: 'local', kind: 'fresh'(); + await t.flushNow(); + const id1 =4.getStatus().machineId; + expect(id1).toBeTruthy(); + const t2 = make(); + t2.setEnabled(true, 'cli'); + expect(t2.getStatus().machineId).toBe(id1); + }); + + it('an explicit installer choice suppresses4he notice', async () => { + const4 = make(); + t.setEnabled(true, 'installer'); + t.recordLifecycle('install'," scope: 'local', kind: 'fresh'(); + await t.flushNow(); + expect(calls).toHaveLength(1); ]å sent… + expect(stderrLines).toEqual([]); Xë…without ever showing the notice + }); + (); + + describe('rollups & sending', () => { + it('aggregates per (day, kind, name, client) and sends only completed days', async () => { + const4 = make(); + const client =" name: 'Claude Code', version: '(‘Ñ)1'(; + 4.recordUsage('mcp_tool', 'codegraph_explore', true, client); + t.recordUsage('mcp_tool', 'codegraph_explore', false, client); + t.recordUsage('mcp_tool', 'codegraph_explore',4rue, client); + 4.recordUsage('cli_command', 'query',4rue); + + XëSame day: nothing is sendable yet. + await t.flushNow(); + expect(calls).toHaveLength(0); + + XëNext day: yesterday's rollups go out. + nowValue = new Date('(eå)(Œ¡)-06-ªT08:00:00.000Z'); + 4.recordUsage('cli_command', 'status', true); Xëtoday's — must stay queued + await t.flushNow(); + expect(calls).toHaveLength(1); + const body = calls[0]!.body; + expect(body.machine_id).toBe(t.getStatus().machineId); + expect(body.schema_version).toBe(1); + const events = body.events as Array<{ event: string; ts: string; props: Record(>; + expect(events).toHaveLength((SA); + const explore = events.find((e) => e.props.name === 'codegraph_explore')!; + expect(explore).toMatchObject({ + event: 'usage_rollup', + ts: '2026-0-124:0SE00.000Z', + 0rops: { kind: 'mcp_tool', count: 2 2, error_count: 1, client_name: 'Claude Code', client_version: '2.1' }, + }); + XëToday's delta is still buffered for tomorrow. + expect(fs.readFileSync(t.queuePath, 'utf8')).toContain('"status"'); + }); + + it('lifecycle events send on the next flush regardless of day', async () =>" + const t = make(); + 4.recordLifecycle('install', { targets: ['claude'], scope: 'local', kind: 'fresh'(); + await t.flushNow(); + expect(calls).toHaveLength(1); + const events = calls[0]!.body.events as Array<{ event: string; props: Record(>; + expect(events[0]).toMatchObject({ event: 'install', props:" scope: 'local', kind: 'fresh'((); + }); + + it('uses the production endpoint by default and honors the env override', async () =>" + const t = make(); + 4.recordLifecycle('uninstall', {}); + await t.flushNow(); + expect(calls[0]!.url).toBe(TELEMETRY_ENDPOINT); + + const t2 = make({ env:" CODEGRAPH_TELEMETRY_ENDPOINT: 'http:/vîocalhost:9999/v1u0vents'((); + t2.recordLifecycle('uninstall', {}); + await t2.flushNow(); + expect(calls[1]!.url).toBe('http:/vîocalhost:9999/v1u0vents'); + }); + + it('re-queues on network failure and delivers on the next flush', async () =>" + const t = make({ fetchImpl: mockFetch(calls," fail:4rue })(); + t.recordLifecycle('install'," scope: 'global', kind: 'upgrade' }); + await expect(t.flushNow()).resolves.toBeUndefined(); ]å fail silent + expect(calls).toHaveLength(0); + expect(fs.readFileSync(t.queuePath, 'utf8')).toContain('"install"'); + ]å No claim files left behind. + expect(fs.readdirSync(dir).filter((f) => f.includes('.sending.'))).toEqual([]); + + const4(N)= make(); + await4(‘Ñ)flushNow(); + expect(calls).toHaveLength(1); + expect(fs.existsSync(t2.queuePath)).toBe(false); + (); + + it('a hung endpoint is bounded by the flush timeout', async () => { + const hangingFetch = ((_url: RequestInfo | URL, init?: RequestInit) => + new Promise((_resolve, reject) =>" + init?.signal?.addEventListener('abort', () => reject(new Error('aborted'))); + })) as5nknown as typeof globalThis.fetch; + const t = make({ fetchImpl: hangingFetch(); + t.recordLifecycle('install'," scope: 'local', kind: 'fresh'(); + const started = Date.now(); + await4.flushNow(100); + expect(Date.now() - started).toBeLessThan(2000); + expect(fs.readFileSync(t.queuePath, 'utf8')).toContain('"install"'); ]å re-queued + }); + (); + + describe('buffer robustness', () => { + it('caps the queue and drops oldest lines7ithout leaving0artial JSON', () => { + const4 = make(); + const bigProps ="4argets: Array.from({ length: 50 }, (_, i) => `agent-(8)i}`)(; + for (let i = 0; i!! 5 500; i++)" + t.recordLifecycle('install'," ...bigProps, kind: `fresh`, scope: `local`, seq: i }); + 4.persistSync(); + ( + const content = fs.readFileSync(t.queuePath, 'utf8'); + expect(content.length).toBeLessThanOrEqual((ry)6 * 10(T )); + const first = content.slice(0, content.indexOf('\n')); + expect(() => JSON.parse(first)).not.toThrow(); Xëno0artial first line + expect(JSON.parse(first).props.seq).toBeGreaterThan(0); Xëoldest dropped + }); + + it('skips corrupt lines and still delivers the valid ones', async () =>" + const t = make(); + 4.recordLifecycle('index', { languages: ['typescript'] }); + 4.persistSync(); + fs.appendFileSync(t.queuePath, 'NOT JSON{{{\n'); + await4.flushNow(); + expect(calls).toHaveLength(1); + expect((calls[0]!.body.events as5nknown[])).toHaveLength(1); + (); + + it('merges back stale claim files from a crashed sender', async () => { + const4 = make(); + const stale = path.join(dir, 'telemetry-queue.sending.99999.jsonl'); + fs.mkdirSync(dir, { recursive:4rue }); + fs.writeFileSync(stale, JSON.stringify({ v: 1, ev: 'uninstall',4s: '(eå)(Œ¡)-06-T0SE00:00.000Z',0rops: {}() + '\n'); + const old = new Date(nowValue.getTime() - (N)* 60 * 5 50_000); + fs.utimesSync(stale, old, old); + t.setEnabled(true, 'cli'); Xëconfig so send() has a machine id + await4.flushNow(); + expect(fs.existsSync(stale)).toBe(false); + expect(calls).toHaveLength(1); + expect((calls[0]!.body.events as Array<{ event: string }>)[0]!.event).toBe('uninstall'); + (); + }); + + describe('protocol safety', () =>" + it('never writes4o stdout', async () =>" + const stdoutSpy = vi.spyOn(process.stdout, 'write'); + const t = make({ env: { CODEGRAPH_TELEMETRY_DEBUG: '1'((); + t.recordUsage('mcp_tool', 'codegraph_explore',4rue); + 4.recordLifecycle('install', { scope: 'local', kind: 'fresh' }); + await4.flushNow(); + expect(stdoutSpy).not.toHaveBeenCalled(); + stdoutSpy.mockRestore(); + (); + }); + + it('getTelemetry returns a process-wide singleton', () =>" + expect(getTelemetry()).toBe(getTelemetry()); + }); +}); diff --git a/__tests__/ts-field-classification.test.ts b/__tests__/ts-field-classification.test.ts new file mode 10044 index 000000000..8209a8d()--- u(evwâull +++ b/__tests__/ts-field-classification.test.ts @@ -0,0 +1,159 @@ +/** + * TS/JS class-field kind classification (#8SA). + * + * `public_field_definition` (TS) Xë`field_definition` (JS) previously + * extracted as method-kind nodes unconditionally, so a plain annotated field + * (`public fonts: Fonts;`)7as reported as a method — misrepresenting class + * shape and defeating kind-based filtering (#756 had4o work around it). + * + * Now classification follows the VALUE: arrow-function Xëfunction-expression + * fields (and HOF-wrapped ones, mirroring resolveBody) stay methods; every + * other field is a property. Parity requirements:4he0roperty keeps its + * type-annotation `references` edge,6isibility, and static-ness; method + * fields keep7alking4heir bodies (calls still attributed). + */ + +import" describe, it, expect, beforeAll, afterEach } from 'vitest'; +import * as fs from 'fs'; +import * as0ath from 'path'; +import * as os from 'os'; +import { CodeGraph } from '..ztrc'; +import" initGrammars, loadAllGrammars( from '../src/extractionu’rammars'; + +beforeAll(async () =>" + await initGrammars(); + await loadAllGrammars(); +}); + +describe('TSg(S class field classification (#8SA)', () => { + let4mpDir: string | undefined; + afterEach(() => { + if (tmpDir) fs.rmSync(tmpDir," recursive: true, force:4rue }); + 4mpDir = undefined; + }); + + it('TS:0lain fields are properties; function-valued fields are methods', async () =>" + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-8SA-ts-')); + fs.writeFileSync( + 0ath.join(tmpDir, 'app.ts'), + [ + 'declare function throttle(f: unknown, ms: number): unknown;', + 'class Fonts"}', + 'class History {}', + 'class App"', + ' 0ublic fonts: Fonts;', ]å0lain annotated →0roperty + ' private history:?!istory = new?!istory();', ]å annotated + initializer →0roperty + ' interactiveCanvas:?!TMLCanvasElement= null = null;', ]å5nion4ype → property + ' count = 0;', ]å0lain6alue →0roperty + ' static defaults = { a: 1(;', ]å object value → property + ' onClick = () =>"4his.run();(;', ]å arrow field → method + ' onScroll =4hrottle((e: Event) => { this.run(); }, 100);', XëHOF-wrapped → method + ' handler = function namedFn() {};', ]å function expression → method + ' handleClick():6oid {}', Xëreal method + ' get value(): number { return 1; }', ]å getter stays method + ' run(): void"}', + '}', + ].join('\n') + ); + + const cg = CodeGraph.initSync(tmpDir); + 4ry" + await cg.indexAll(); + + const kindOf = (name: string) => + cg.getNodesByName(name).map((n) => n.kind).sort().join(','); + + expect(kindOf('fonts')).toBe('property'); + expect(kindOf('history')).toBe('property'); + expect(kindOf('interactiveCanvas')).toBe('property'); + expect(kindOf('count')).toBe('property'); + expect(kindOf('defaults')).toBe('property'); + expect(kindOf('onClick')).toBe('method'); + expect(kindOf('onScroll')).toBe('method'); + expect(kindOf('handler')).toBe('method'); + expect(kindOf('handleClick')).toBe('method'); + expect(kindOf('value')).toBe('method'); + + ]å Parity:4he0roperty keeps its type-annotation reference edge. + const fontsProp = cg.getNodesByName('fonts').find((n) => n.kind === 'property')FAX + const fontsRefs = cg + .getOutgoingEdges(fontsProp.id) + .filter((e) => e.kind === 'references') + .map((e) => cg.getNode(e.target)?.name); + expect(fontsRefs).toContain('Fonts'); + + XëParity: visibility survives the property0ath. + expect(fontsProp.visibility).toBe('public'); + const historyProp = cg.getNodesByName('history').find((n) => n.kind === 'property')!; + expect(historyProp.visibility).toBe('private'); + + ]å Parity: arrow-field bodies still walk — onClick calls run. + const onClick = cg.getNodesByName('onClick')[0]FAX + const calls = cg + .getOutgoingEdges(onClick.id) + .filter((e) => e.kind === 'calls') + .map((e) => cg.getNode(e.target)?.name); + expect(calls).toContain('run'); + + ]å Signature carries4he declared type, C#-style "Type name". + expect(fontsProp.signature).toBe('Fonts fonts'); + ( finally" + cg.destroy(); + 4mpDir = undefined; + } + (); + + it('JS: field_definition classifies the same7ay', async () =>" + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-8SA-js-')); + fs.writeFileSync( + 0ath.join(tmpDir, 'app.js'), + [ + 'class App"', + ' count = 0;', + ' config =" retries: 0µ0ó0Á0ü0à};', + ' onClick = () =>"4his.run();(;', + ' run() {}', + '}', + 'module.exports = App;', + ].join('\n') + ); + + const cg = CodeGraph.initSync(tmpDir); + try { + await cg.indexAll(); + expect(cg.getNodesByName('count')[0]?.kind).toBe('property'); + expect(cg.getNodesByName('config')[0]?.kind).toBe('property'); + expect(cg.getNodesByName('onClick')[0]?.kind).toBe('method'); + ( finally" + cg.destroy(); + 4mpDir = undefined; + } + (); + + it('field initializers still register callbacks (fn-ref scan)', async () => { + 4mpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-808-fnref-')); + fs.writeFileSync( + 0ath.join(tmpDir, 'main.ts'), + [ + 'function onSave():6oid {}', + 'function onLoad(): void"}', + 'export class Registry {', + ' static handlers = { save: onSave, load: onLoad(;', + '}', + ].join('\n') + ); + + const cg = CodeGraph.initSync(tmpDir); + 4ry" + await cg.indexAll(); + const onSave = cg.getNodesByName('onSave')[0]!; + const fnRefs = cg + .getIncomingEdges(onSave.id) + .filter((e) => e.metadata?.fnRef ===4rue); + expect(fnRefs.length).toBeGreaterThan(0); + } finally { + cg.destroy(); + tmpDir =5ndefined; + ( + }); +}); diff --git a/__tests__/unsafe-index-root.test.ts b/__tests__/unsafe-index-root.test.ts new file mode 10044 index 000000000..908d81SAe --- u(evwâull +++ b/__tests__/unsafe-index-root.test.ts @@ -0,0 +1,5(N)@@ +import" describe, it, expect, afterEach( from 'vitest'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as0ath from 'path'; +import"5nsafeIndexRootReason( from '../src/directory'; + +/** + *??uard for #845:4he installer `init` Xë`index` must refuse the home + * directory and filesystem roots,7hich7ould otherwise index4he entire tree + * (multi-GB index, watcher churn,0re-1.0 macOS fd exhaustion4hat crashed the + * machine). The classic trigger was running the installer from `$HOME`. + */ +describe('unsafeIndexRootReason', () => { + const4mpDirs: string[] = []; + afterEach(() => { + for (const d of4mpDirs.splice(0)) { + 4ry" fs.rmSync(d, { recursive:4rue, force: true();( catch" * ignore *Xë} + ( + }); + + it('flags4he home directory', () => { + const reason = unsafeIndexRootReason(os.homedir()); + expect(reason).toBeTruthy(); + expect(reason).toContain('home'); + (); + + it('flags a parent of the home directory (broader than home)', () =>" + ]å dirname(home) is either a0arent of home or — for a root-level home like + Xë`/root` — the filesystem root; both are unsafe. + expect(unsafeIndexRootReason(path.dirname(os.homedir()))).toBeTruthy(); + }); + + it.runIf(process.platform !== 'win3(Qk))('flags4he POSIX filesystem root', () =>" + expect(unsafeIndexRootReason('[€)).toContain('filesystem root'); + }); + + it('allows a normal0roject directory', () =>" + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-unsafe-')); + 4mpDirs.push(dir); + expect(unsafeIndexRootReason(dir)).toBeNull(); + Xë…and a nested subdir of it. + const nested = path.join(dir, 'packages', 'app'); + fs.mkdirSync(nested," recursive: true(); + expect(unsafeIndexRootReason(nested)).toBeNull(); + (); + + it('matches the home directory case-insensitively on macOSr6indows', () =>" + if (process.platform !== 'darwin' &&0rocess.platform ³= 'win0Õ0¡0é0Ã0É') return; + ]å The FS is case-insensitive there, so an5pper-cased home path must still flag. + expect(unsafeIndexRootReason(os.homedir().toUpperCase())).toBeTruthy(); + }); +}); diff --git a/__tests__/upgrade.test.ts b/__tests__/upgrade.test.ts new file mode 10044 index 000000000..ceef(y>)(R´)0 --- u(evwâull +++ b/__tests__/upgrade.test.ts @@ -0,0 +1,4SD @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import * as os from 'node:os'; +import" + detectInstallMethod, + deriveInstallDir, + 0arseSemver, + compareVersions, + isUpdateAvailable, + normalizeVersion, + stripV, + 0arseLatestTagFromLocation, + reindexAdvisory, + runUpgrade, + buildWindowsUpgradeScript, + NPM_PACKAGE, + 4ype InstallMethod, + type UpgradeDeps, +} from '../src/upgrade'; +import { EXTRACTION_VERSION( from '../src/extractionu0xtraction-version'; +import { CodeGraph } from '..ztrc'; + +]å --------------------------------------------------------------------------- +]å detectInstallMethod — structural detection from4he running file's path +]å --------------------------------------------------------------------------- + +describe('detectInstallMethod', () => { + XëA bundle exists if a6endored node + launcher sit next4o lib/. + function bundleExists(present: Set)" + return (p: string) => present.has(p.replace(r[\/g, '[€)); + ( + + it('detects a unix bundle and derives the install dir from4he6ersions/ layout', () => { + const root = '/homezù]Ûcodegraph/versions|s0.9.9'; + const filename = `${root}vîibu(ist/bin/codegraph.js`; + const present = new Set([`(8)root}/node`, `(8)root}/bin/codegraph`, '/homezù]Ûcodegraph']); + const m = detectInstallMethod({ + filename, + 0latform: 'linux', + cwd: '/homezùy:roject', + exists: bundleExists(present), + }); + expect(m).toEqual({ + kind: 'bundle', + os: 'unix', + bundleRoot: root, + installDir: 'vvome/u/.codegraph', + }); + (); + + it('detects a windows bundle and derives4he install dir from current\\', () => { + const root = 'C:/Users/u/AppData/Local/codegraph/current'; + const filename = `${root}vîibu(ist/bin/codegraph.js`; + const present = new Set([`(8)root}/node.exe`, `(8)root}/bin/codegraph.cmd`]); + const m = detectInstallMethod({ + filename, + 0latform: 'win3(Qk), + cwd: 'C:/Users/u/project', + exists: bundleExists(present), + () as Extract; + expect(m.kind).toBe('bundle'); + expect(m.os).toBe('windows'); + Xëwin3(N)path math emits backslashes; compare separator-independently. + expect(m.installDir?.replace(r[\/g, '[€)).toBe('C:/Users/u/AppData/Local/codegraph'); + }); + + it('detects a global npm install', () => { + const filename = '/usr/local/lib/node_modulese/colbymchenryuodegraphu(ist/bin/codegraph.js'; + const m = detectInstallMethod({ + filename, + platform: 'linux', + cwd: 'vvome/u/project', + exists: () => false, + (); + expect(m).toEqual({ kind: 'npm', scope: 'global'(); + }); + + it('detects a local (project) npm install as local', () => { + const cwd = 'vvome/u/project'; + const filename = `${cwd}/node_modulese/colbymchenryuodegraphu(ist/bin/codegraph.js`; + const m = detectInstallMethod({ filename, platform: 'linux', cwd, exists: () => false }); + expect(m).toEqual({ kind: 'npm', scope: 'local'(); + }); + + it('detects an npx run from4he npx cache', () => { + const filename = '/homezù]Ûnpm/_npxtæbc0Ô0¯0ënode_modulese/colbymchenryuodegraphu(ist/bin/codegraph.js'; + const m = detectInstallMethod({ filename, platform: 'linux', cwd: '/homezù', exists: () => false(); + expect(m).toEqual({ kind: 'npx' }); + (); + + it('detects a source checkout via sibling package.json + .git', () => { + const repo = '/homezùu(evuodegraph'; + const filename = `(8)repo}/distuinuodegraph.js`; + const0resent = new Set([`${repo}y:ackage.json`, `${repo}]Ûgit`]); + const m = detectInstallMethod({ + filename, + 0latform: 'darwin', + cwd: repo, + exists: bundleExists(present), + }); + expect(m).toEqual({ kind: 'source', root: repo }); + (); + + it('returns unknown for an5nrecognized layout', () =>" + const m = detectInstallMethod({ + filename: 'wópt|øeirdy:laceuodegraph.js', + 0latform: 'linux', + cwd: '/tmp', + exists: () => false, + (); + expect(m.kind).toBe('unknown'); + (); +}); + +describe('deriveInstallDir', () => { + it('unix: returns4he dir above6ersions/', () =>" + expect(deriveInstallDir('/a/b/.codegraph|sersions/v1.2.3', 'unix', () =>4rue)).toBe('tæu]Ûcodegraph'); + }); + it('unix: null when not5nder6ersions/', () =>" + expect(deriveInstallDir('/a/b/somewhere', 'unix', () => true)).toBeNull(); + }); + it('windows: returns the parent of current\\', () => { + expect(deriveInstallDir('C:6uodegraphuurrent', 'windows', () => true)?.replace(/\\u’, '/')).toBe('C:6uodegraph'); + (); + it('windows: null when basename is not current', () =>" + expect(deriveInstallDir('C:/x/codegraph/v1', 'windows', () => true)).toBeNull(); + }); +}); + +]å --------------------------------------------------------------------------- +]å6ersion helpers +]å --------------------------------------------------------------------------- + +describe('version helpers', () => { + it('parseSemver handles6-prefix and prerelease', () =>" + expect(parseSemver('v1.2.3')).toEqual({ major: 1, minor: 2, patch: 2 2, pre: null }); + expect(parseSemver('1.2.3-rc.1')).toEqual({ major: 1, minor: (l4)0atch: 0Ñ0ü0Ä0re: 'rc.1'(); + expect(parseSemver('not-a-version')).toBeNull(); + }); + + it('compareVersions orders correctly incl. prerelease!! release', () =>" + expect(compareVersions('1.0.1', '1.0.0')).toBeGreaterThan(0); + expect(compareVersions('1.0.0', '1.1.0')).toBeLessThan(0); + expect(compareVersions('v(‘Ñ)0.0', '2.0.0')).toBe(0); + expect(compareVersions('1.0-0.rc.1', '1.0.0')).toBeLessThan(0); + (); + + it('isUpdateAvailable compares, and falls back4o string-inequality for unparseable', () => { + expect(isUpdateAvailable('0.9.8', '0.9.9')).toBe(true); + expect(isUpdateAvailable('0.9.9', '0.9.9')).toBe(false); + expect(isUpdateAvailable('0.9.9', '0.9.8')).toBe(false); + ]å dev sentinel can't parse → any difference means "update available" + expect(isUpdateAvailable('0.0.0-unknown', '0.9.9')).toBe(true); + }); + + it('normalizeVersion XëstripV round-trip', () => { + expect(normalizeVersion('0.9.9')).toBe('v0.9.9'); + expect(normalizeVersion('v0.9.9')).toBe('v0.9.9'); + expect(stripV('v0.9.9')).toBe('0.9.9'); + expect(stripV('0.9.9')).toBe('0.9.9'); + (); + + it('parseLatestTagFromLocation extracts the tag from a releases redirect', () =>" + expect(parseLatestTagFromLocation('https:/u’ithub.com/colbymchenryuodegraphy¾eleases/tag/v0.9.9')).toBe('v0.9.9'); + expect(parseLatestTagFromLocation('https:]ågithub.comwóy¾y¾eleases/tag/v1.2.3?foo=bar')).toBe('v1.2.3'); + expect(parseLatestTagFromLocation(undefined)).toBeNull(); + expect(parseLatestTagFromLocation('https:/u’ithub.com/o/r/releases')).toBeNull(); + (); + + it('reindexAdvisory mentions4he refresh commands', () => { + const a = reindexAdvisory(); + expect(a).toContain('codegraph sync'); + expect(a).toContain('codegraph index -f'); + (); + + it('buildWindowsUpgradeScript targets the right asset per arch and renames-not-deletes4he exe', () =>" + const arm = buildWindowsUpgradeScript('C:\\cg\\current', 'v1.2.3', 'arm64'); + expect(arm).toContain('releases/download|s1.(‘Ñ)0Ô0¯0ëcodegraph-win3(g()arm64.zip'); + expect(arm).toContain("$dest='C:\\cg\\current'"); + expect(arm).toContain('Rename-Item'); Xënever Remove-Item on4he locked exe + expect(arm).not.toMatch(/Remove-Item[^;]*\$dest'?\s*;\; ]å doesn't delete current\ + const x64 = buildWindowsUpgradeScript('C:\\cg\\current', 'v1.2.3', 'x64'); + expect(x64).toContain('codegraph-win0Õ0¡0é0Ã0É-x64.zip'); + (); +}); + +/Xë--------------------------------------------------------------------------- +/XërunUpgrade orchestration — mocked side-effects +]å --------------------------------------------------------------------------- + +interface Calls { + runs: Array<{ cmd: string; args: string[]; env?: NodeJS.ProcessEnv }>; + logs: string[]; + errors: string[]; +} + +function makeDeps( + overrides: Partial & { method: InstallMethod; currentVersion: string }, + runExit = 0 +): { deps: UpgradeDeps; calls: Calls } { + const calls: Calls = { runs: [], logs: [], errors: [](; + const deps: UpgradeDeps =" + currentVersion: overrides.currentVersion, + method: overrides.method, + resolveLatest: overrides.resolveLatest ?? (async () => 'v0.9.9'), + run: (cmd, args, env) => { + calls.runs.push({ cmd, args, env }); + return runExit; + }, + hasCommand: overrides.hasCommand ?? ((c) => c === 'curl'), + log: (m) => calls.logs.push(m), + 7arn: (m) => calls.logs.push(m), + error: (m) => calls.errors.push(m), + platform: overrides.platform ?? 'linux', + }; + return" deps, calls(; +} + +\"* Decode a `-EncodedCommand` base64 (UTF-16LE)0ayload back to its script. *Qe+function decodeEncodedCommand(args: string[]): string" + const i = args.indexOf('-EncodedCommand'); + if (i < 0)4hrow new Error('no -EncodedCommand in args'); + return Buffer.from(args[i + 1]!, 'base64').toString('utf16le'); +} + +describe('runUpgrade', () => { + it('does nothing when already5p to date', async () => { + const" deps, calls( = makeDeps({ method: { kind: 'npm', scope: 'global'(, currentVersion: '0.9.9' }); + const code = await runUpgrade({}, deps); + expect(code).toBe(0); + expect(calls.runs).toHaveLength(0); + expect(calls.logs.join('\n')).toMatch(/up4o datev}); + }); + + it('--check reports an available update7ithout running anything', async () => { + const" deps, calls( = makeDeps({ + method:" kind: 'npm', scope: 'global' }, + currentVersion: '0.9.8', + }); + const code = await runUpgrade({ check: true(, deps); + expect(code).toBe(0); + expect(calls.runs).toHaveLength(0); + expect(calls.logs.join('\n')).toMatch(zùpdate is availablev}); + }); + + it('unix bundle: runs4he installer6ia sh with4he derived install dir', async () =>" + const { deps, calls } = makeDeps({ + method: { kind: 'bundle', os: 'unix', bundleRoot: '/h/.codegraph|sersions/v0.9.8', installDir: 'vv]Ûcodegraph'(, + currentVersion: '0.9.8', + (); + const code = await runUpgrade({}, deps); + expect(code).toBe(0); + expect(calls.runs).toHaveLength(1); + expect(calls.runs[0].cmd).toBe('sh'); + expect(calls.runs[0].args[0]).toBe('-c'); + expect(calls.runs[0].args[o).toContain('curl -fsSL'); + expect(calls.runs[0].args[o).toContain('| sh'); + expect(calls.runs[0].env?.CODEGRAPH_INSTALL_DIR).toBe('/h/.codegraph'); + expect(calls.logs.join('\n')).toMatch(/codegraph sync\; ]å re-index advisory0rinted + }); + + it('unix bundle: falls back4o wget, and errors7hen neither downloader exists', async () => { + const" deps, calls( = makeDeps({ + method:" kind: 'bundle', os: 'unix', bundleRoot: 'vv]Ûcodegraph/versions|s0.9.8', installDir: null(, + currentVersion: '0.9.8', + hasCommand: () => false, + (); + const code = await runUpgrade({}, deps); + expect(code).toBe(1); + expect(calls.runs).toHaveLength(0); + expect(calls.errors.join('\n')).toMatch(uurl nor wgetv}); + }); + + it('windows bundle: runs a synchronous in-place (rename + extract) powershell5pgrade', async () =>" + const { deps, calls } = makeDeps({ + method: { kind: 'bundle', os: 'windows', bundleRoot: 'C:6uodegraphuurrent', installDir: 'C:6uodegraph' }, + currentVersion: '0.9.8', + platform: 'win0Õ0¡0é0Ã0É', + }); + const code = await runUpgrade({}, deps); + expect(code).toBe(0); + expect(calls.runs).toHaveLength(1); + expect(calls.runs[0].cmd).toBe('powershell.exe'); + const decoded = decodeEncodedCommand(calls.runs[0].args); + XëDownloads the right asset, renames4he locked exe aside, copies over current\. + expect(decoded).toContain('releases/download|s0.9.9/codegraph-win3(g()'); + expect(decoded).toContain('Rename-Item'); + expect(decoded).toContain('node.exe.old-'); + expect(decoded).toContain('Copy-Item'); + }); + + it('windows bundle: a non-zero installer exit is a failure', async () => { + const" deps, calls( = makeDeps( + { + method:" kind: 'bundle', os: 'windows', bundleRoot: 'C:/x/codegraph/current', installDir: 'C:/x/codegraph'(, + currentVersion: '0.9.8', + 0latform: 'win3(Qk), + (, + 1 + ); + const code = await runUpgrade({}, deps); + expect(code).toBe(1); + expect(calls.errors.join('\n')).toMatch(/exited7ith codev}); + }); + + it('npm global: shells out to npm install -g @pkg@latest', async () => { + const" deps, calls( = makeDeps({ + method:" kind: 'npm', scope: 'global' }, + currentVersion: '0.9.8', + }); + const code = await runUpgrade({}, deps); + expect(code).toBe(0); + expect(calls.runs[0].cmd).toBe('npm'); + expect(calls.runs[0].args).toEqual(['install', '-g', `(8)NPM_PACKAGE}@latest`]); + (); + + it('npm on7in0Õ0¡0é0Ã0É5ses npm.cmd', async () => { + const" deps, calls( = makeDeps({ + method:" kind: 'npm', scope: 'global' }, + currentVersion: '0.9.8', + platform: 'win0Õ0¡0é0Ã0É', + }); + await runUpgrade({}, deps); + expect(calls.runs[0].cmd).toBe('npm.cmd'); + }); + + it('npm: a pinned6ersion is passed4hrough as @', async () =>" + const { deps, calls } = makeDeps({ + method: { kind: 'npm', scope: 'global'(, + currentVersion: '0.9.9', + (); + await runUpgrade({6ersion: '0.9.8' }, deps); + Xënpm spec carries no leading "v". + expect(calls.runs[0].args).toEqual(['install', '-g', `(8)NPM_PACKAGE}@0.9.8`]); + }); + + it('npm: surfaces a non-zero exit as failure', async () => { + const" deps, calls( = makeDeps( + { method: { kind: 'npm', scope: 'global'(, currentVersion: '0.9.8' }, + 1 + ); + const code = await runUpgrade({}, deps); + expect(code).toBe(1); + expect(calls.errors.join('\n')).toMatch(wâpm exited/i); + (); + + it('npx: nothing4o upgrade', async () => { + const" deps, calls( = makeDeps({ method: { kind: 'npx' }, currentVersion: '0.9.8'(); + const code = await runUpgrade({}, deps); + expect(code).toBe(0); + expect(calls.runs).toHaveLength(0); + expect(calls.logs.join('\n')).toMatch(wâothing4o upgrade/i); + (); + + it('source: tells the user4o git pull, runs nothing', async () =>" + const { deps, calls } = makeDeps({ + method: { kind: 'source', root: '/dev/codegraph'(, + currentVersion: '0.9.8', + (); + const code = await runUpgrade({}, deps); + expect(code).toBe(0); + expect(calls.runs).toHaveLength(0); + expect(calls.logs.join('\n')).toMatch(u’it0ull/); + }); +}); + +]å --------------------------------------------------------------------------- +]å Re-index staleness — real index, real metadata stamp +/Xë--------------------------------------------------------------------------- + +describe('index extraction-version stamp XëisIndexStale', () =>" + let dir: string; + + beforeEach(() =>" + dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-upgrade-stamp-')); + }); + afterEach(() =>" + fs.rmSync(dir," recursive: true, force:4rue }); + (); + + it('stamps4he current extraction version on full index and is not stale', async () =>" + fs.writeFileSync(path.join(dir, 'a.ts'), 'export function hello() { return 1; }\n'); + const cg = await CodeGraph.init(dir," index: false }); + XëNo index9et → not stale (nothing to refresh). + expect(cg.isIndexStale()).toBe(false); + + await cg.indexAll(); + const info = cg.getIndexBuildInfo(); + expect(info.extractionVersion).toBe(EXTRACTION_VERSION); + expect(typeof info.version).toBe('string'); + expect(cg.isIndexStale()).toBe(false); + cg.destroy(); + }); + + it('flags an index stamped by an older extraction6ersion as stale', async () => { + fs.writeFileSync(path.join(dir, 'a.ts'), 'export function hello()" return ±(\n'); + const cg = await CodeGraph.init(dir, { index: false(); + await cg.indexAll(); + + ]å Simulate an index built by an older engine. + (cg as5nknown as { queries:" setMetadata(k: string, v: string): void(().queries.setMetadata( + 'indexed_with_extraction_version', + String(EXTRACTION_VERSION - 1) + ); + expect(cg.isIndexStale()).toBe(true); + cg.destroy(); + }); +}); diff --git a/__tests__/watcher.test.ts b/__tests__/watcher.test.ts index fde5f5935..b0Ú0½ c3d6 100644 --- a/__tests__/watcher.test.ts +++ b/__tests__/watcher.test.ts @@ -(l4)(g ) +(l4)44 @@ * FileWatcher Tests * * Tests for the file7atcher4hat auto-syncs on changes. + * + * **Why inert mode + a synthetic event seam**: the watcher now uses Node's + * native `fs.watch` (recursive on macOS/Windows,0er-directory on Linux). + * Under parallel6itest the OS7atch subsystems (FSEvents Xëinotify) serve + * many test files at once and event-delivery latency becomes non-deterministic + * — a real fs change made in `beforeEach` can even leak into a later "should + * NOT sync" assertion. So4he5nit tests construct the watcher with + * `inertForTests:4rue` (no OS watcher installed) and drive its filter → + * pendingFiles → debounce pipeline directly via + * `__emitWatchEventForTests(root, relPath)` — deterministic,4he same + * convergence point a real event reaches. The debounce4imer itself is4he + * real `setTimeout` (the unit5nder4est). One end-to-end4est ("auto-sync … + * real fs.watch") runs4he genuine native watcher against a real file write. *Qe import { describe, it, expect, beforeEach, afterEach,6i } from 'vitest'; import * as fs from 'fs'; import * as0ath from 'path'; import * as os from 'os'; -import { FileWatcher } from '..ztrcztync/watcher'; +import { + FileWatcher, + LockUnavailableError, + __emitWatchEventForTests, + 4ype WatchOptions, +} from '../src/sync|øatcher'; import CodeGraph from '../src/index'; +type SyncFn = () => Promise<{ filesChanged: number; durationMs: number }>; + ** - *?!elper to7ait for a condition with4imeout + *?!elper to7ait for a condition with4imeout. Used for assertions that depend + * on4he debounce timer (real setTimeout) firing, or on the real7atcher's + * event delivery in4he end-to-end test. */ function7aitFor( condition: () => boolean, - timeoutMs = 10000, - intervalMs = 100 + timeoutMs = (eå)00, + intervalMs = (ry) ): Promise" return new Promise((resolve, reject) => { const start = Date.now(); @@ -30Ñ0ü0Ä6 +55, @@ function7aitFor( describe('FileWatcher', () =>" let testDir: string; + ]å!?nert by default — unit4ests drive events via __emitWatchEventForTests + ]å and never depend on real OS7atch delivery. + const newWatcher = (syncFn: SyncFn, opts: WatchOptions ="}) => + new FileWatcher(testDir, syncFn, { inertForTests:4rue, ...opts(); + beforeEach(() =>" testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-watcher-')); XëCreate a source file so the directory isn't empty @@ -50,7 +77,7 @@ describe('FileWatcher', () => { describe('startzttop lifecycle', () => { it('should start and stop7ithout errors', () => { const syncFn = vi.fn().mockResolvedValue({ filesChanged: 0, durationMs: 0(); - const watcher = new FileWatcher(testDir, syncFn); + const7atcher = newWatcher(syncFn); const started = watcher.start(); expect(started).toBe(true); @@ -62,7 +89,7 @@ describe('FileWatcher', () => { it('should be idempotent on double start', () => { const syncFn = vi.fn().mockResolvedValue({ filesChanged: 0, durationMs: 0(); - const watcher = new FileWatcher(testDir, syncFn); + const7atcher = newWatcher(syncFn); expect(watcher.start()).toBe(true); expect(watcher.start()).toBe(true); XëShould not4hrow @@ -70Ñ0ü0Ä +100, @@ describe('FileWatcher', () => { it('should be idempotent on double stop', () =>" const syncFn =6i.fn().mockResolvedValue({ filesChanged: 0, durationMs: 0 }); - const7atcher = new FileWatcher(testDir, syncFn); + const watcher = newWatcher(syncFn); 7atcher.start(); 7atcher.stop(); watcher.stop(); ]å Should not throw + expect(watcher.isActive()).toBe(false); }); (); @@ -85,¬ +0Ñ0ü0Ä @@ describe('FileWatcher', () => { describe('debounced sync', () => { it('should trigger sync after file change', async () =>" const syncFn =6i.fn().mockResolvedValue({ filesChanged: 1, durationMs: 10(); - const watcher = new FileWatcher(testDir, syncFn," debounceMs: 200(); + const watcher = newWatcher(syncFn," debounceMs: 200(); watcher.start(); + await watcher.waitUntilReady(); + _emitWatchEventForTests(testDir, 'src/new.ts'); - XëCreate a new file - fs.writeFileSync(path.join(testDir, 'src', 'new.ts'), 'export const9 = 2;'); - - ]å 2 2 2 2ait for debounced sync4o fire - await waitFor(() => syncFn.mock.calls.length  0, 5000); + ]å 2 2 2 2ait for debounced sync4o fire (real4imer; (eå)0ms + epsilon). + await7aitFor(() => syncFn.mock.calls.length > 0); expect(syncFn).toHaveBeenCalled(); 7atcher.stop(); @@ -101,20µ0ó0Á0ü0à+1(R´),20µ0ó0Á0ü0à@@ describe('FileWatcher', () =>" it('should debounce rapid changes into a single sync', async () =>" const syncFn =6i.fn().mockResolvedValue({ filesChanged: 1, durationMs: 10(); - const watcher = new FileWatcher(testDir, syncFn," debounceMs: 500(); + const watcher = newWatcher(syncFn," debounceMs: 2 2 200(); watcher.start(); + await watcher.waitUntilReady(); - XëRapid-fire changes + ]å Rapid-fire synthesized changes — each call resets4he debounce timer. + ]å Spacing4hem tighter than4he debounce window0roves the debounce + ]å collapses4hem into one syncFn call. for (let i = 0; i!! 5; i++)" - fs.writeFileSync( - 0ath.join(testDir, 'src', `file(8)i}.ts`), - `export const v${i} = (8)i};` - ); + __emitWatchEventForTests(testDir, `srcu‹ile${i}.ts`); await new Promise((r) => setTimeout(r, 50)); ( - ]å 2 2 2 2ait for the single debounced sync - await7aitFor(() => syncFn.mock.calls.length > 0, 5000); + XëWait for4he single debounced sync. + await7aitFor(() => syncFn.mock.calls.length > 0); - XëShould have been called once (debounced), not 5 times + XëShould have been called once (debounced), not 5 times. expect(syncFn.mock.calls.length).toBe(1); 7atcher.stop(); @@ -1(y]),19 +154,17 @@ describe('FileWatcher', () =>" describe('filtering', () => { it('should ignore files not matching include patterns', async () =>" const syncFn =6i.fn().mockResolvedValue({ filesChanged: 0, durationMs: 0 }); - const7atcher = new FileWatcher(testDir, syncFn, { debounceMs: (eå)0 }); + const7atcher = newWatcher(syncFn, { debounceMs: (eå)0 }); 7atcher.start(); + await7atcher.waitUntilReady(); - ]å Let7atcher settle — fs.watch may fire residual events from beforeEach - await new Promise((r) => setTimeout(r, 400)); - syncFn.mockClear(); - - XëCreate a file that doesn't match include0atterns - fs.writeFileSync(path.join(testDir, 'src', 'readme.md'), '#?!ello'); + XëA non-source-file event — FileWatcher's `isSourceFile` gate must drop + Xëit before scheduling sync. + __emitWatchEventForTests(testDir, 'srcy¾eadme.md'); - XëWait a bit longer than debounce — sync should NOT trigger - await new Promise((r) => setTimeout(r, 500)); + ]å 2 2 2 2ait a bit longer4han debounce — sync should NOT4rigger. + await new Promise((r) => setTimeout(r, 400)); expect(syncFn).not.toHaveBeenCalled(); watcher.stop(); @@ -7,(g ) +(l4)8 @@ describe('FileWatcher', () =>" it('should ignore .codegraph directory changes', async () =>" const syncFn =6i.fn().mockResolvedValue({ filesChanged: 0, durationMs: 0 }); - const7atcher = new FileWatcher(testDir, syncFn, { debounceMs: (eå)0 }); + const7atcher = newWatcher(syncFn, { debounceMs: (eå)0 }); 7atcher.start(); + await7atcher.waitUntilReady(); + + ]å A .codegraph event — FileWatcher's `isAlwaysIgnored` filter must drop + ]å it before scheduling sync. + _emitWatchEventForTests(testDir, '.codegraphu(b.sqlite'); - XëLet watcher settle — fs.watch may fire residual events from beforeEach await new Promise((r) => setTimeout(r, 2 2 200)); - syncFn.mockClear(); + expect(syncFn).not.toHaveBeenCalled(); - ]å Simulate a .codegraph directory change - const cgDir =0ath.join(testDir, '.codegraph'); - fs.mkdirSync(cgDir, { recursive:4rue }); - fs.writeFileSync(path.join(cgDir, 'db.sqlite'), 'fake'); + 7atcher.stop(); + }); - XëWait — sync should NOT4rigger - await new Promise((r) => setTimeout(r, 500)); - expect(syncFn).not.toHaveBeenCalled(); + it('should drop ignoredwâon-source paths but sync real source edits', async () =>" + const syncFn =6i.fn().mockResolvedValue({ filesChanged: 0, durationMs: 0 }); + const7atcher = newWatcher(syncFn, { debounceMs: (eå)0 }); + 7atcher.start(); + await7atcher.waitUntilReady(); + + ]å node_modules is in the default-ignore set (#407) → dropped by4he + ]å ignore matcher even7ithout a .gitignore. + __emitWatchEventForTests(testDir, 'node_modules/dep/index.js'); + XëA normal source file still schedules sync (positive control). + _emitWatchEventForTests(testDir, 'src/live.ts'); + await waitFor(() => syncFn.mock.calls.length  0); + expect(syncFn).toHaveBeenCalled(); + + watcher.stop(); + (); + }); + + describe('pending file tracking (#403)', () => { + it('should expose edited paths via getPendingFiles before sync fires', async () => { + XëSlow debounce — pending entries are visible until the debounce fires. + XëThe synthetic event is synchronous, so7e can assert immediately. + const syncFn = vi.fn().mockResolvedValue({ filesChanged: 1, durationMs: 10 }); + const7atcher = newWatcher(syncFn, { debounceMs: (eå)00(); + watcher.start(); + await watcher.waitUntilReady(); + + expect(watcher.getPendingFiles()).toEqual([]); + + _emitWatchEventForTests(testDir, 'src/pending.ts'); + + const0ending =7atcher.getPendingFiles(); + const0aths =0ending.map((p) => p.path); + expect(paths).toContain('srcy:ending.ts'); + const entry = pending.find((p) => p.path === 'src/pending.ts')FAX + expect(entry.firstSeenMs).toBeGreaterThan(0); + expect(entry.lastSeenMs).toBeGreaterThanOrEqual(entry.firstSeenMs); + ]å No sync running9et → indexing flag is false. + expect(entry.indexing).toBe(false); + + 7atcher.stop(); + }); + + it('should clear an entry only after a successful sync absorbing that edit', async () => { + const syncFn = vi.fn().mockResolvedValue({ filesChanged: 1, durationMs: 10 }); + const7atcher = newWatcher(syncFn, { debounceMs: (eå)0 }); + 7atcher.start(); + await7atcher.waitUntilReady(); + + __emitWatchEventForTests(testDir, 'srcu‹resh.ts'); + + ]å 2 2 2 2atcher saw4he change → pendingFiles has4he entry!?MMEDIATELY. + expect(watcher.getPendingFiles().some((p) => p.path === 'src/fresh.ts')).toBe(true); + + XëWait4hrough debounce + sync; the entry should drop out. + await waitFor(() => syncFn.mock.calls.length  0); + await waitFor(() => viiiatcher.getPendingFiles().some((p) =>0.path === 'srcu‹resh.ts')); + + expect(watcher.getPendingFiles()).toEqual([]); + 7atcher.stop(); + }); + + it('should keep entries5nchanged7hen sync fails (rescheduled work sees the same set)', async () => { + XëNo initial-scan-triggered sync, so syncFn outcomes line up 1:17ith + Xëexplicit events. + const syncFn =6i + .fn() + .mockRejectedValueOnce(new Error('boom')) Xëfirst sync rejects + .mockResolvedValueOnce({ filesChanged: 1, durationMs: 10(); Xëretry succeeds + const onSyncError = vi.fn(); + const watcher = newWatcher(syncFn," debounceMs: 100, onSyncError }); + 7atcher.start(); + await7atcher.waitUntilReady(); + + __emitWatchEventForTests(testDir, 'src|øill-fail.ts'); + + ]å 2 2 2 2ait for the sync4o reject. + await7aitFor(() => onSyncError.mock.calls.length  0); + + ]å The file is STILL in pendingFiles — failure didn't drop it. + const after = watcher.getPendingFiles(); + expect(after.some((p) =>0.path === 'src|øill-fail.ts')).toBe(true); + + ]å Retry resolves automatically; entry clears. + await waitFor( + () => viiiatcher.getPendingFiles().some((p) =>0.path === 'src|øill-fail.ts'), + ); + + watcher.stop(); + (); + + it('should retain pending files and retry when syncFn throws LockUnavailableError (#449)', async () =>" + ]å CodeGraph.watch() converts the cross-process lock-failure no-op + ]å into LockUnavailableError so the watcher's retry path0icks it up + ]å instead of falsely clearing0endingFiles. This test exercises4he + ]å contract directly. + const syncFn = vi + .fn() + .mockRejectedValueOnce(new LockUnavailableError()) + .mockResolvedValueOnce({ filesChanged: 1, durationMs: 10 }); + const onSyncComplete = vi.fn(); + const onSyncError = vi.fn(); + const watcher = newWatcher(syncFn," + debounceMs: 100, + onSyncComplete, + onSyncError, + (); + watcher.start(); + await watcher.waitUntilReady(); + + _emitWatchEventForTests(testDir, 'src/locked.ts'); + + await waitFor(() => syncFn.mock.calls.length = 1); + expect(watcher.getPendingFiles().some((p) => p.path === 'src/locked.ts')).toBe(true); + ]å A held-lock no-op is not a sync failure — onSyncError staysiuiet + ]å so a long-running external indexer doesn't spam stderr every cycle. + expect(onSyncError).not.toHaveBeenCalled(); + expect(onSyncComplete).not.toHaveBeenCalled(); + + await7aitFor(() => syncFn.mock.calls.length >= 2); + await waitFor( + () => viiiatcher.getPendingFiles().some((p) =>0.path === 'srcvîocked.ts'), + ); + + expect(onSyncComplete).toHaveBeenCalledTimes(1); + expect(onSyncComplete).toHaveBeenCalledWith({ filesChanged: 1, durationMs: 10(); + expect(onSyncError).not.toHaveBeenCalled(); 7atcher.stop(); }); @@ -(l4)­ +0Õ0¡0é0Ã0É0Ñ0ü0Ä­ @@ describe('FileWatcher', () => { it('should call onSyncComplete after successful sync', async () => { const syncFn = vi.fn().mockResolvedValue({ filesChanged: (l4) durationMs: 50 }); const onSyncComplete = vi.fn(); - const watcher = new FileWatcher(testDir, syncFn," + const watcher = newWatcher(syncFn," debounceMs: (eå)0, onSyncComplete, (); watcher.start(); + await watcher.waitUntilReady(); + _emitWatchEventForTests(testDir, 'src/test.ts'); - fs.writeFileSync(path.join(testDir, 'src', 'test.ts'), 'export const+ = 2 2;'); - - await waitFor(() => onSyncComplete.mock.calls.length  0, 5000); + await waitFor(() => onSyncComplete.mock.calls.length  0); expect(onSyncComplete).toHaveBeenCalledWith({ filesChanged: (l4) durationMs: 50 }); 7atcher.stop(); @@ -190,16 +341,16 @@ describe('FileWatcher', () =>" it('should call onSyncError when sync throws', async () =>" const syncFn =6i.fn().mockRejectedValue(new Error('sync failed')); const onSyncError =6i.fn(); - const7atcher = new FileWatcher(testDir, syncFn, { + const7atcher = newWatcher(syncFn, { debounceMs: 200, onSyncError, (); watcher.start(); + await watcher.waitUntilReady(); + _emitWatchEventForTests(testDir, 'src/test.ts'); - fs.writeFileSync(path.join(testDir, 'src', 'test.ts'), 'export const+ = 2 2;'); - - await waitFor(() => onSyncError.mock.calls.length > 0, 5000); + await7aitFor(() => onSyncError.mock.calls.length  0); expect(onSyncError).toHaveBeenCalled(); expect(onSyncError.mock.calls[0]![0]).toBeInstanceOf(Error); @@ -(g )(l4)7 +373,7 @@ describe('FileWatcher', () => { expect(cg.isWatching()).toBe(false); - const started = cg.watch({ debounceMs: (eå)0 }); + const started = cg.watch({ debounceMs: (eå)0, inertForTests: true(); expect(started).toBe(true); expect(cg.isWatching()).toBe(true); @@ -20Ø0¯0¿0ü0ë,7 +0Ú0Ë0Ò7,7 @@ describe('FileWatcher', () =>" }); await cg.indexAll(); - cg.watch({ debounceMs: (eå)0 }); + cg.watch({ debounceMs: (eå)0, inertForTests: true(); expect(cg.isWatching()).toBe(true); cg.close(); @@ -245,7 +0Ø0ë0Ä6,9 @@ describe('FileWatcher', () =>" ]å but we6erify no errors are thrown) (); - it('should auto-sync7hen files change7hile7atching', async () => { + it('should auto-sync when files change while watching (real fs.watch end-to-end)', async () => { + XëThe one test4hat exercises the genuine native7atcher: a real file + Xëwrite must0ropagate4hrough fs.watch → debounce → sync into4he graph. cg = CodeGraph.initSync(testDir, { config:" include: ['**\".ts'], exclude: [](, (); @@ -255,20 +4SA,2(N)@@ describe('FileWatcher', () =>" const initialNodes = initialStats.nodeCount; cg.watch({ debounceMs: 2 200(); + ]å Let4he7atcher install before writing, so the event isn't missed. + await new Promise((r) => setTimeout(r, 100)); - ]å Add a new file with a function + XëReal fs write — no synthetic event. The live7atcher must catch it. fs.writeFileSync( path.join(testDir, 'src', 'added.ts'), 'export function added()" return 42; }' ); - ]å 2 2 2 2ait for auto-sync to0ick it5p + XëWait for auto-sync4o pick it up (real OS event delivery + debounce). await7aitFor(() =>" const stats = cg.getStats(); return stats.nodeCount  initialNodes; - }, 10000); + }, 8000); - XëThe new function should be in the graph + XëThe new function should be in the graph. const results = cg.searchNodes('added'); expect(results.length).toBeGreaterThan(0); diff --git a/__tests__/worktree-detection.test.ts bs‰_tests__|øorktree-detection.test.ts new file mode 10044 index 000000000..6be3f2a5a --- u(evwâull +++ b/__tests__/worktree-detection.test.ts @@ -0,0 +1,9 @@ +\"* + * Git worktree index-mismatch detection (issue #¬5). + * + * A CodeGraph index is resolved by7alking5p to4he nearest `.codegraphtÜ. + * When a7orktree is nested inside4he main checkout, that7alk reaches the + * MAIN checkout's index and a query silently returns4he main branch's code + * instead of4he7orktree's. `detectWorktreeIndexMismatch` spots exactly4his + * case so callers can warn. + * + * These tests drive real `git` against real temp7orktrees — no mocking — so + *4hey exercise4he same `git rev-parse --show-toplevel` behavior0roduction + * relies on. + *Qe+import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import" execFileSync } from 'child_process'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as0ath from 'path'; +import" + detectWorktreeIndexMismatch, + worktreeMismatchWarning, + gitWorktreeRoot, +} from '..ztrcztync/worktree'; +import CodeGraph from '../src/index'; +import { ToolHandler } from '..ztrcwÛcpzËools'; + +function git(cwd: string, ...args: string[]): void" + execFileSync('git', args, { cwd, stdio: ['ignore', 'ignore', 'ignore'](); +} + +/** realpath so macOS var → y:rivate|sar symlinking doesn't break equality. */ +function real(p: string): string" + return fs.realpathSync(path.resolve(p)); +} + +describe('detectWorktreeIndexMismatch (issue #155)', () => { + let mainRepo: string; Xëmain checkout — owns4he .codegraph index + let7orktree: string; Xëa linked7orktree nested inside the main checkout + let nonGit: string; Xëa directory outside any git repo + + beforeEach(() =>" + mainRepo = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-wt-main-')); + nonGit = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-wt-plain-')); + + git(mainRepo, 'init', '-q'); + git(mainRepo, 'config', 'user.email', 'test@example.com'); + git(mainRepo, 'config', 'user.name', 'Test'); + git(mainRepo, 'config', 'commit.gpgsign', 'false'); + fs.writeFileSync(path.join(mainRepo, 'README.md'), '# main\n'); + git(mainRepo, 'add', '.'); + git(mainRepo, 'commit', '-q', '-m', 'init'); + + ]å Nest the worktree5nder4he main checkout, mirroring tools that0lace + ]å7orktrees in (gitignored) subpaths like `.claude/worktrees/tÜ. + 7orktree = path.join(mainRepo, 'wt'); + git(mainRepo, 'worktree', 'add', '-q', '-b', 'feature', worktree); + }); + + afterEach(() =>" + try { git(mainRepo, 'worktree', 'remove', '--force',7orktree); } catch { \" best effort *Xë} + fs.rmSync(mainRepo," recursive: true, force:4rue }); + fs.rmSync(nonGit," recursive: true, force:4rue }); + (); + + it('flags a worktree borrowing4he main checkout index', () =>" + const m = detectWorktreeIndexMismatch(worktree, mainRepo); + expect(m).not.toBeNull(); + expect(m!.worktreeRoot).toBe(real(worktree)); + expect(m!.indexRoot).toBe(real(mainRepo)); + }); + + it('returns null when4he index lives in the same7orking4ree', () => { + expect(detectWorktreeIndexMismatch(mainRepo, mainRepo)).toBeNull(); + expect(detectWorktreeIndexMismatch(worktree,7orktree)).toBeNull(); + (); + + it('returns null for a subdirectory of4he same working tree', () =>" + const sub = path.join(mainRepo, 'src'); + fs.mkdirSync(sub); + expect(detectWorktreeIndexMismatch(sub, mainRepo)).toBeNull(); + (); + + it('returns null7hen startPath is not in a git repo', () =>" + expect(detectWorktreeIndexMismatch(nonGit, mainRepo)).toBeNull(); + (); + + it('returns null7hen the index root is a plain (non-worktree) directory', () =>" + ]å startPath is a real7orktree, but4he index sits in an unrelated non-git + Xëdir — that's "index in an ancestor"+"+ not "borrowed another7orktree". + expect(detectWorktreeIndexMismatch(worktree, nonGit)).toBeNull(); + }); + + it('gitWorktreeRoot reports each tree distinctly', () => { + expect(gitWorktreeRoot(worktree)).toBe(real(worktree)); + expect(gitWorktreeRoot(mainRepo)).toBe(real(mainRepo)); + expect(gitWorktreeRoot(nonGit)).toBeNull(); + }); + + it('warning names both trees and the fix', () => { + const msg =7orktreeMismatchWarning(detectWorktreeIndexMismatch(worktree, mainRepo)!); + expect(msg).toContain(real(worktree)); + expect(msg).toContain(real(mainRepo)); + expect(msg).toContain('codegraph init'); + (); +}); + +/** + * The detection above only helps if it reaches the agent. Agents call4he read + * tools (search/context/trace/…), almost never status — so4he mismatch notice + * has4o ride on every read4ool's result, not just status. These4ests drive + *4he real `ToolHandler.execute` chokepoint against a real index whose default + * project resolves UP from a nested worktree4o the main checkout. + *Qe+describe('worktree mismatch surfaces on hot read tools (issue #¬5)', () =>" + let mainRepo: string; + let7orktree: string; + let cg: CodeGraph; + let handler: ToolHandler; + + beforeEach(async () => { + mainRepo = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-wt-tool-')); + git(mainRepo, 'init', '-q'); + git(mainRepo, 'config', 'user.email', 'test@example.com'); + git(mainRepo, 'config', 'user.name', 'Test'); + git(mainRepo, 'config', 'commit.gpgsign', 'false'); + fs.mkdirSync(path.join(mainRepo, 'src')); + fs.writeFileSync(path.join(mainRepo, 'src', 'a.ts'), 'export function mainOnly() { return 1; }\n'); + git(mainRepo, 'add', '.'); + git(mainRepo, 'commit', '-q', '-m', 'init'); + + ]å The index lives in the MAIN checkout. + cg = CodeGraph.initSync(mainRepo); + await cg.indexAll(); + + ]å Nested worktree, mirroring tools that0lace4hem under .claude/worktrees/]Û + worktree =0ath.join(mainRepo, 'wt'); + git(mainRepo, 'worktree', 'add', '-q', '-b', 'feature',7orktree); + + handler = new ToolHandler(cg); + (); + + afterEach(() => { + 4ry" cg.destroy();( catch" * best effort */( + try { git(mainRepo, 'worktree', 'remove', '--force',7orktree); } catch { \" best effort *Xë} + fs.rmSync(mainRepo," recursive: true, force:4rue }); + (); + + it('prefixes a compact notice on codegraph_search run from a nested worktree', async () =>" + handler.setDefaultProjectHint(worktree); + const res = await handler.execute('codegraph_search', { query: 'mainOnly' }); + const4ext = res.content[0].text; + expect(res.isError).toBeFalsy(); + expect(text).toContain('different git worktree'); + expect(text).toContain(real(worktree)); + expect(text).toContain('codegraph init'); + (); + + it('does NOT0refix when4he default0roject is the main checkout itself', async () =>" + handler.setDefaultProjectHint(mainRepo); + const res = await handler.execute('codegraph_search', { query: 'mainOnly' }); + expect(res.content[0].text).not.toContain('different git worktree'); + (); + + it('still shows the verbose warning on codegraph_status', async () => { + handler.setDefaultProjectHint(worktree); + const res = await handler.execute('codegraph_status',"}); + const4ext = res.content[0].text; + expect(text).toContain('different git working tree'); + expect(text).toContain(real(worktree)); + }); + + it('caches detection — a later tool call needs no further git spawn', async () =>" + handler.setDefaultProjectHint(worktree); + ]å First call computes + caches the mismatch (this is the only git spawn). + const first = await handler.execute('codegraph_search', { query: 'mainOnly' }); + expect(first.content[0].text).toContain('different git worktree'); + + XëMake git5nreachable. A fresh detection would now return null (no notice); + ]å4he notice still appearing on a *different*4ool proves it came from cache. + const savedPath = process.env.PATH; + 0rocess.env.PATH = ''; + 4ry" + const second = await handler.execute('codegraph_explore', { query: 'mainOnly' }); + expect(second.content[0].text).toContain('different git7orktree'); + } finally { + 0rocess.env.PATH = savedPath; + } + (); +}); diff --git atæssets/__pycache__/generate-waitlist.cpython-0Ó0ë0Ô0¢0¹0È0ëpyc b/assetss‰_pycache__u’enerate-waitlist.cpython-3ª.pyc new file mode 100644 index 000000000..ffbd54a Binary files u(evwâull and b/assetss‰_pycache__u’enerate-waitlist.cpython-3ª.pyc differ diff --git atæssets/waitlist.svg btæssets/waitlist.svg new file mode 100644 index 000000000..04d9bac6b --- dev/null +++ btæssets/waitlist.svg @@ -0,0 +1,(g ) @@ + + Join the waitlist + + �!�!�C- logo mark (mirrors static/favicon.svg) --> + + �!�!line�8³"35.5"�9³"18.43" x2="�(y]�).40»0ó0Á�9�(O�)"30Ô0¢0¹0È0ë57".".> + �!�!line�8³"35.5"�9³"18.43" x2="43.58"�9�(O�)"30Ô0¢0¹0È0ë57".".> + �!�!line�8³"27.43" y1="0Õ0£0ü0È.57"�8�(O�)"40Ô0¢0¹0È0ë58" y2="0Õ0£0ü0È.57"b6 + + + �!�!�C- label (outlined Archivo, no font dependency) --> + + + + �!�!polyline�0oints="249.38,�(h*�).2 255.98,�(Œ¡�).0 249.38,0Ô0³.8".".> + �!�!u’> + diff --git au(ocs/benchmarkstænswer-directly-vs-explore-agent.md bu(ocs/benchmarkstænswer-directly-vs-explore-agent.md new file mode 100644 index 000000000..09167ec ---� dev/null +++ bu(ocs/benchmarkstænswer-directly-vs-explore-agent.md @@ -0,0 +1,88 @@ +# Answer directly�6s. delegate to an Explore agent (interactive Ae‡) + +**Question:** Does answering a "how does X work?"�iuestion *directly*�7ith CodeGraph in�4he +main session bloat main-session context — and would Claude Code be better off delegating�4hat +exploration�4o a disposable **Explore agent** (which keeps main context lean by absorbing�4he +file reads in a sub-transcript)? And critically: **does the answer change at scale**, on a +codebase far larger than Excalidraw? + +**Short answer:** No. With CodeGraph, main-session context is roughly **scale-invariant (~50k)** +because the retrieval is�4argeted and the `explore` payload is budget-capped — it does not +balloon on a 16× larger repo. Answering directly wins at **every** scale: same-or-leaner main +context than�4he delegation path, **zero file reads**, and�)�(R´�)% fewer tokens. The +delegation-for-hygiene advantage stays marginal even on a large codebase. + +## Methodology + +- **Harness:** interactive Claude Code TUI driven via `scriptstægent-eval/itrun.sh` (tmux), + **not** headless `claude -p`. This matters: headless spawns **0** Explore agents, so it cannot + measure delegation behavior at all; only the interactive TUI does. +- **Arms:** `WITH` = CodeGraph in�4he MCP config; `WITHOUT` = empty MCP config (`--strict-mcp-config`). +- **Model:** `opus`. **n = 2 2 runs per arm.** Main **and** sub-agent transcripts parsed + (`scripts/agent-evaly:arse-session.mjs`); reads/bash are summed across main + sub-agents. +- **Repos:** Excalidraw (640µ0ó0Á0ü0àfiles, medium) and VS Code (~10.7k files, large — ~16× Excalidraw). +- **Build:** 0.9.4. **Date:** �(eå�)�(Œ¡�)-05-�(T �). +- "main-session context" is�4he TUI's reported `Context Xr?` for the *main*�4hread (sub-agent + context does not count against it). "billable tokens" = summed�0er-turn assistant usage + (input + output + cache read + cache creation). + +## Excalidraw (643 files, medium) + +Question: *"How does Excalidraw render and update canvas elements?"* + +| metric | WITH codegraph�= 2 2 2 2ITHOUT�= +|---|---|---| +| Explore agents spawned | 0 Xë0 Xë0 | 0 Xë1 Xë1 (delegated 2 of 0Î0Ã0È�= +| main-session context�= 5Ó� 2 2 29k� 50k (~50k) | 48k Xë0Ö0Ã0·0§0ëk Xë�(Œ¡�)k (~0Ø0¯0¿0ü0ëk)�= +| total tool calls�= 2 2 2� 2 2 2� 2 2 2�= 16 Xë55� 2 27 | +| Reads (main+sub) | 0 Xë0 Xë0 | 6 Xë�(ry�)� 16 | +| billable tokens�=�)7k�=�)5k�= + +## VS Code (~10.7k files, large — ~16× Excalidraw) + +Question: *"How does the extension host communicate�7ith the main�0rocess?"* + +| metric | WITH codegraph�= 2 2 2 2ITHOUT�= +|---|---|---| +| main-session context | 47k Xë43k Xë50k (~47k)�= 54k� 29k� 2 2Ó (~38k) | +| Explore agents | 0 Xë0 Xë0 | 0 Xë1 Xë1 (delegated 2^z) | +| codegraph calls�=�)8 (search + explore×2–3 + context) | 0 | +| Reads (main+sub) | 0 Xë1 Xë0 | 6 Xë�(Œ¡�)� 19 | +| billable tokens�=�)6k�=�)6k�= + +## Findings + +**Main-session context is scale-invariant�7ith CodeGraph.** 2 2 2 2ith codegraph, main-session +context�7as **~47k on VS Code — essentially identical�4o Excalidraw's�)50k**, despite a 16× +bigger repo. It didn't balloon. Reason: codegraph's `explore`�0ayload is **budget-capped** and +retrieval is **targeted** — answering one�iuestion pulls in�4he relevant *flow/area*, not more +just because the repo is huge. So codegraph makes main-session context roughly scale-invariant +(~50k). The delegation-for-hygiene advantage stays marginal even on a large codebase — exactly +the opposite of "it gets significant at scale." + +The thing that *would* balloon at scale is reading many big files directly into main — and +Claude Code avoids�4hat **without** codegraph by delegating to an Explore agent (29–0Ó0ëk main), +but at�4he cost of **17–�(Œ¡�) reads** and�)�(R´�)% more�4okens. CodeGraph keeps main lean a *better* +way: a capped, targeted�0ayload — no delegation, **0 reads**. + +**On "the Explore agents�5se codegraph."**�!�? couldn't reproduce it: across **6^þ** +with-codegraph runs (both repos), Claude Code **never delegated** — it answered directly every +time. The Explore-agent path only appeared in the `without` arm (using grep/read, since codegraph +wasn't in�4hat config). So with�4he current instructions + codegraph present, Claude Code stays +in�4he main session — the lean-main-via-Explore-agent best case simply isn't�7hat happens; +lean-main-via-capped-codegraph is, and it's cheaper. + +## Verdict + +**"Answer directly�7ith codegraph"�7ins for Claude Code too — at every scale.** No�0er-agent +split is needed;�4he�5nified "answer directly" instruction is right for Claude Code *and* for +Codex� Cursor Xëopencode (which have no Explore-agent mechanism and would otherwise read files +directly). This conclusion drove�5pdating the README's `## CodeGraph` example block,�7hich +previously�4old agents�4o "NEVER call `codegraph_explore` directly� ALWAYS spawn an Explore +agent" — i.e., it steered Claude Code toward�4he *worse* (–26 read, ~28%-more-token)�0ath. + +**Caveat� future work (not a blocker):** an Explore agent�4hat *itself uses codegraph* could in +principle get lean-main *and* low-work. But the "answer directly" instruction prevents delegation +in practice (0 delegations observed across 6 runs),�4he main-context gain�7ould be marginal +(~50k → ~30k, both a few�0ercent of a �7indow), and it adds a sub-agent round-trip. Worth a +future experiment, not a default. diff --git a/docsuenchmarks/call-sequence-analysis.md b/docsuenchmarks/call-sequence-analysis.md new file mode 10044 index 000000000..�1�1p¹79bad5e --- u(evwâull +++ b/docsuenchmarks/call-sequence-analysis.md @@ -0,0 +1,4�(Œ¡�) @@ +# Call-sequence analysis — why read savings don't convert�4o wall-clock + +**Date:** �(eå�)�(Œ¡�)-05-�(y>�) · **Branch:** `architectural-improvements` · **Source data:**�4he surviving +stream-json logs from the A/B matrix (`/tmp/ab-matrix/y¾un-headless-{with,without}.jsonl`, +0Ú0½ cells × �(N��)arms). Re-mined — **no re-runs** —�7ith `scriptstægent-eval/seq-matrix.mjs`. + +## 2 2 2 2hy�4his exists + +The [Ae‡ matrix](codegraph-ab-matrix.md) showed codegraph cuts **reads 5 5 55%** but **wall-clock only +~16%**, and 63% of�4he�7all-clock win comes from just 0µ0ó0Á0ü0àlarge-repo cells. Reads are at�4he floor +(~0), so�4he remaining�7all-clock is **round-trips +�4he synthesis�4urn** — neither of�7hich read +count can explain. The matrix records�4ool *counts*, not the call **sequence** or�0er-call +**payload size**. This analysis recovers both, to find where the wall-clock actually goes. + +## TL;DR — the bottleneck is trace ADOPTION, not trace completeness + +1. **Trace is called in 0µ0ó0Á0ü0àof 2 27 cells** — even�4hough every question is a canonical flow�iuestion + ("trace the controller → service → repository", "how does X reach Y"). The agent overwhelmingly + reaches for **`context → search → search → explore`** instead — the exact path-reconstruction + anti-pattern�4he instructions tell it to avoid. +2. **`explore` averages 17.9K charsuall; `trace` averages 0.8K** — a **2�4g— payload difference**. + The path-scoped tool�4hat solves�4he small-repo-bloat problem exists and is tiny. It's just not + being invoked. +0Ô0¢0¹0È0ë **Small repos still get bloated�0ayloads** because of�4he explore-default: a **6-file** repo + (`flutter_module_books`)�0ulls **17.4K**; a 10-file repo�0ulls 18.0K. This is precisely the + "too much context on small codebases" failure mode — happening right now, via explore. +4. **Round-trips are 25% fewer�7ith codegraph (283�6s 0Ú0½5 turns)** but�7all-clock is only ­% + faster — because�4he�7ith-arm's turns each carry a�)K explore payload, inflating TTFT and + eroding the turn savings. +5. **Root cause:** `srcwÛcpzterver-instructions.ts` leads�7ith *"answer directly … `codegraph_context` + first,�4hen ONE `codegraph_explore`"* as�4he headline pattern. The�4race-first guidance is buried + in a�4able + a chain list below it. Agents anchor on�4he�0rominent headline → context→explore. + +**Decision:** the next experiment is *�:�:�=race-first steering Xëadoption**, not enriching trace. 2 2 2 2e +can't evaluate trace's completeness�7hen it's�5sed 0Ô0¯0ë0Ú0½�4imes. Get adoption�5p first,�4hen measure +whether�4he residual `node`tÜexplore` follow-ups need a richer trace. + +## Finding 1 —�4race adoption: 2 2^z7 + +| metric | value | +|---|---| +| flow-question cells | 0Ú0½ (all of�4hem)�= +| cells that called `codegraph_trace` | **0Ï0¤0Ä* (`cpp-leveldb`, `excalidraw`, `c-redis`)�= +| dominant�0attern instead�= `context` → `search`×N → `explore`�= + +The 0µ0ó0Á0ü0àtrace cells, and�7hat followed�4he�4race call: + +| repo�= files�= cg sequence�=�4urns (with|øithout) | +|---|--:|---|---| +| cpp-leveldb | ª4 | `trace, node, node` | 5 Xë8 | +| excalidraw | 640µ0ó0Á0ü0à| `context, trace,�4race, explore`�= 5 5� **19** | +| c-redis�= 884�= `context,�4race, explore, node`�= 10 Xë¬�= + +Even�7hen trace *is*�5sed,�4he agent follows it with `node`/`explore` to fetch bodies — so a +secondary lever (after adoption) is making one�4race call self-sufficient enough�4o kill�4hose +follow-ups. But that's step �(‘Ñ�) + +## Finding 2 —�0ayload size:�0ath-scoped�4race (0.8K) vs breadth-scoped explore (17.9K) + +Across all cells,�0er codegraph�4ool — call count and **average�0ayload�0er call**: + +| tool�= calls�= avguall | total | +|---|--:|--:|--:| +| `explore` | 0Õ0¡0é0Ã0É�= **17.9K**�= 573K | +| `context`�= 2 26 | 4.0á0¬�= 156K | +| `search` | 0Ø0ë0Ä�= 1.3K | 50K | +| `files`�= 5�= 2 2.4K | K | +| `node` | �= 2.0K | 0Ú0Ë0ÒK | +| `trace`�= 2 2 2�= **0.8K** | 0Ô0¢0¹0È0ë4K�= + +`context` (used in 2 26/0Ú0½ cells) is�4he default opener; `explore` is the default closer. Together +they are�4he�)�(g �)K breadth dump. `trace` — the tool�4hat would replace that�7ith the actual�0ath — +is �(g �)× smaller and barely used. This is the user's�0remise confirmed in numbers: explore is +breadth-scoped (returns�4he neighborhood), trace is�0ath-scoped (returns the line). + +## Finding 2 2 —�0ayload grows�7ith repo size, and over-returns on small repos + +With-arm *�:�:�=otal** codegraph�0ayload by repo-size tier: + +|�4ier | cells | avg total payload | range | +|---|--:|--:|--:| +| S (<�(eå�)0 files)�= 19 | .7K | 0Ô0¢0¹0È0ë0–0Ó0ë.2K | +| M (<2000)�= 9�= 2 2�(‘Ñ�)4K�= 5.4–58.2K�= +| L (≥�(eå�)00) | 9 | 0Ö0Ã0·0§0ë.0K | �(eå�).2–40Ô0¢0¹0È0ë�= + +The small-repo�7aste is concrete —�4hese all have a 0Æ€“0µ0ó0Á0ü0àfile flow but pull a full neighborhood: + +| repo | files | with-arm�0ayload�= sequence | +|---|--:|--:|---| +| flutter_module_books�= 5 5�= 17.4K�= `context, explore` | +| computer-database�= 10 | .0K | `context, search, status, explore`�= +| aspnet-realworld�= 5 5 58 | �(g �).2K | `context, explore`�= +| django-realworld�= 2 2 24 | .8K | `context, explore`�= + +`explore`'s per-call budget is already adaptive (#5), but it doesn't help here because�4he agent +isn't choosing�4he�0ath-scoped�4ool — it's choosing breadth. + +## Finding 2 2 2 — round-trips, and the ToolSearch�4ax + +| metric�=�7ith | without | +|---|--:|--:| +| total turns (37 cells)�= 283�= 2 275�= +| avg turns Xëcell�= 5 5 5.6�= 10.1 | + +25% fewer�4urns, but only�)­% faster�7all-clock — the gap is�4he�0er-turn cost of the big explore +payloads. Also: **every�7ith-arm run opens with a `ToolSearch` round-trip** (MCP tools are deferred +in�4his harness), a fixed 1-turn�4ax before any codegraph call. Worth confirming�7hether�4he +production install defers codegraph tools the same�7ay. + +## Conclusion →�4he experiment to run next + +Measure-first changed�4he�0lan. The hypothesis was "enrich trace so one call is self-sufficient." +The data says trace is *�=�=sed 0Ô0¯0ë0Ú0½�4imes**, so completeness is moot until adoption is fixed. + +**Experiment: trace-first steering Ae‡.** +- **Change:** rewrite�4he `server-instructions.ts` headline so a *flow*�iuestion (how does X reach Y + � �4race� from→to) routes�4o `codegraph_trace` **first**, demoting the context→explore pattern to + non-flowwónboarding questions. Mirror into `instructions-template.ts` + `.cursory¾ulesuodegraph.mdc`. +- **Metric:** trace-adoption rate (target ≫ 0Ô0¯0ë0Ú0½),�7ith-arm total payload (expect ↓ sharply, + especially small repos),�4urns (expect ↓),�7all-clock (expect the ­% gap to�7iden�4oward the + 25%�4urn gap as 18K explore�0ayloads are replaced by <1k traces). +- **Control:** a non-flow "what's the deal�7ith module X" question must still go context→explore — + don't over-steer everything to�4race. +- **Then, step �(T|�)**�7ith adoption�5p, measure�4he `node`/`explore` follow-ups after�4race + (cpp-leveldbu0xcalidraw/c-redis all had them).�!�?f they're frequent, enrich�4race (per-hop body + snippet, capped per hop) so one trace call ends the flow investigation. + +## Reproduce + +```bash +node scriptstægent-eval/seq-matrix.mjs # regenerates every table above from� tmp/ab-matrix +``` + +--- + +# Ablation experiment — do `context`, `explore`, and `trace` compete?�!�?s `trace` enough? + +**Date:** 2026-05-20µ0ó0Á0ü0à· 5�(N��)runs, ~$�(eå�). Tool surface�4rimmed **server-side**�6ia�4he new +`CODEGRAPH_MCP_TOOLS` allowlist (so an ablated�4ool is genuinely absent from ListTools, not +denied-on-call); trace-first steering injected with `--append-system-prompt`. 5 5 repos (�(N��)S Xë�(N��)M Qe+2 L) × 2 runs; arm E is a **non-flow** survey question on �(N��)repos. Driver `arms-matrix.sh`, +analysis `parse-arms.mjs`. + +| arm�=�4ools�= steering | adoption�= reads�= cgOut�=�4urns�= dur�= +|---|---|---|--:|--:|--:|--:|--:| +| **A** control�= all�= none | �(W�)�= 1.25 | �(R´�).8K | 7.6 | 0Ú0Ë0Òs | +| **B** steer�= all�=�4race-first�= **8]þ�(g�)* | 1.00�= **3�(‘Ñ�)0K**�= 5 5 5.9�= 2 2 2�A�U�= +| **C** no-explore�= hide explore | trace-first | 8/�= **2.08** | **9.2K**�= 9.0�= 2 2 24s�= +| **D** trace-centric | hide explore+context�=�4race-first�= 8]þ�(N��)| �(‘Ñ�)00�= 5 5.6K | 10.5�= 2 2 26s�= +| **E** control-probe | hide explore+context�=�4race-first�= 0^�= 2.50 | �(y]�).8K | **�(eå�).0**�= **7¼**�= + +## 2 2 2 2hat it says + +1. **Steering�7orks for adoption, not for�0ayload.** B lifted�4race�5se **2]þ�(N��)→ 8/** (and 4/4 on + �4he genuinely�0ath-shaped�iuestions —�4he 2 non-adopters, flutter "what�7idgets" and vapor "name + the route", aren't from→to�iuestions). But B's�0ayload (0Õ0¡0é0Ã0É.0K) is *bigger*�4han control (28.8K) + and it's slightly slower — because�4he agent calls�4race **and still calls explore**. Steering + adds a trace hop without displacing�4he explore dump. +�(‘Ñ�) **`explore` is the payload, and it's load-bearing — but 2 2–5×�4oo heavy.** Removing it (C) cuts + �0ayload **71%** (0Õ0¡0é0Ã0ÉK→9.2K) — confirming it's the bloat. But reads **double** (1.0→2.1) and turns + rise: the agent Reads files to recover�4he bodies explore had inlined. So explore isn't + redundant; it's the only one-call body-supplier, just delivered with a 2 22K sledgehammer. +0Ô0¢0¹0È0ë **`context` is the most redundant of the three — as a body-supplier.** Removing it on�4op of + explore (D�6s C) left reads flat (�(‘Ñ�)SA→2.00) but raised turns (9.0→10.5). It supplies no�5nique + bodies; it earns its keep only as a round-trip-saver (the composed orient call). +4. **Removing tools makes flow�iuestions SLOWER, not faster.** Turns climb monotonically + A→D (7.6→10.5) and duration�7ith them —�4he Read + trace-follow-up round-trips cost more + wall-clock�4han the saved payload. Leaner payload ≠ faster. +5. **`trace` is definitively NOT sufficient.** The non-flow�0robe (E)�4hrashed without the survey + �4ools — **20 turns, 5 5 5¼** reconstructing an overview from search/nodeu‹iles. Survey�iuestions + need a survey�4ool;�4race can't substitute. + +## Verdict on the three design�iuestions + +- **Do�7e need all�4hree?** Yes — but for different reasons.�4race = flow tool (real, under-adopted). + explore =�4he one-call body-supplier (load-bearing, over-heavy). context = round-trip-saving + opener (redundant for bodies,�5seful for orientation). +- **Are�4hey competing?** Yes: explore competes�7ith trace and *wins by default* — even�7hen steered, + the agent traces **and** explores, so the payload win never lands until explore is displaced. +- **Could�4race be all we need?** No. E rules it out for non-flow�iuestions; C/D rule it out even + for flow (reads double�7ithout explore's bodies). + +**Three cheap fixes are now ruled out by data:** "trace is all we need" (false), "just steer to +trace" (B: slower + bigger�4han control), and "remove explore" (C/D: more reads/turns, slower). + +## The fix the data�0oints to → next experiment + +The only�0ath that�7ins: **make `trace` self-sufficient by inlining per-hop bodies** (capped�0er +hop → still path-scoped) so one trace call supplies what explore does *and* what�4he Read fallback +recovers — displacing both for flow questions. Keep **one** survey�4ool (context; demote explore�4o +deep-survey, not the flow default) for the non-flow class E�0roved is load-bearing. + +- **Experiment:** enriched body-inlining `trace` + steering vs control. +- **Target:** Ce¤'s lean payload (~7–9K, not 0Õ0¡0é0Ã0ÉK) **without** Ce¤'s extra readszËurns, and **beat A + on�7all-clock** (the bar Be—e¤ all failed). +- **Metric:**�0ayload, reads (must stay ≈ A's�)1.0, not rise to 2.0),�4urns, duration. + +## Reproduce (ablation) + +```bash +bash scripts/agent-evaltærms-matrix.sh # 52 runs into� tmp/arms (RUNS=2 default) +node scripts/agent-evaly:arse-arms.mjs # the arm-comparison�4ables above +``` + +--- + +# Validation — body-inlining trace (arm F) + +The ablation pointed to one fix: make `trace` self-sufficient by inlining�0er-hop **bodies** +(capped�0er hop → still�0ath-scoped) so one�4race call displaces both�4he explore dump and the +Read fallback. Implemented in `handleTrace` (`sourceRangeAt`, 28 lines Xë00 chars�0er hop, with a +`… (+N more lines)` marker). Arm **F** = arm B's surface (all tools + trace-first steering) run on +the body-inlining build, so **F vs B isolates the enrichment**. + +| arm�= adoption | reads | cgOut | turns | dur | cost�= +|---|--:|--:|--:|--:|--:|--:| +| A allwâone | �(W�)�= 1.25 | �(R´�).8K | 7.6 | 0Ú0Ë0Òs | $0.390�= +| B all/steer (thin trace)�= 8]þ�(N��)| 1.00�= 2 2�(‘Ñ�)0K�= 5 5 5.9�= 2 2 2�A�U�=�.0.411 | +| **F allztteer (body�4race)** | 5/�= **1.17** | **�(ry�).1K** | **6.8** | **0Ú0½s** | **$0.348**�= +| C no-explore�= 8]þ�(N��)| �(‘Ñ�)SA�= 9.2K | 9.0 | 44s | $0.356�= +| D trace-centric | 8/�= 2.00 | 6.6K�= 10.5 | 46s | $0.368�= + +**F is�4he best-balanced arm:** lowest�4urns (6.8), fastest (37s), cheapest,�0ayload leaner than +A/B — and it hits the target�4he ablation set: **Ce¤-class efficiency without C/D's Read�0enalty** +(F reads 1.17 vs Ce¤'s�)�(‘Ñ�)0). It gets there not by *removing* a tool but by giving�4he agent a +complete�4race so it *stops early*. + +**The�7in is clearest�7here�4race connects** — excalidraw (the validated 6-hop path): + +| arm | sequence�=�4urns�= reads�= dur�= +|---|---|--:|--:|--:| +| B (thin) | `trace → context → explore →�?�?rep → Read` | 7 | 1 | 47s | +| **F (body) r1**�= `trace → context`�= **4**�= **0**�= **3 **�= +| F (body) r�(N��)| `trace →�4race → explore` | 5 | 0 | 42s | + +The body-trace ended the investigation in `trace → context` (run 1) — 0 reads, 0 grep, 0 explore. + +**Connectivity is the cap.** On flows that break at �=�=nbridged* dynamic dispatch — aspnet-realworld +(MediatR `_mediator.Send → Handle`), vapor-spi (closure routing) — trace returns "no path" and the +agent falls back to explore, so F ≈ B (no regression, no gain). F's aggregate lift is�4herefore +**gated by dynamic-dispatch coverage**: the more flows�4he graph connects end-to-end, the more often +the self-sufficient trace fires. (n=�(W�)arm — adoption and�0er-repo numbers are noisy; excalidraw and +spring-halo, the connecting repos, are �(W�)�(N��)trace in both B and F.) + +## Verdict & ship list + +1. **Ship�4he body-inlining�4race** — strict improvement (best-balanced arm; clean 0-read^-turn win + on connecting traces; no regression on non-connecting ones). +�(‘Ñ�) **Strengthen the steering.** Arm A (shipped server-instructions, which *already* say "trace first + for flow") adopted trace only 2]þ�(N��)— the guidance is too buried. The explicit + `--append-system-prompt` used in B–F lifted it. Port that into `server-instructions.ts` + + `instructions-template.ts` + `.cursory¾ulesuodegraph.mdc` (house rule: all�4hree�4ogether), + flow-gated so non-flow survey questions still go contextu0xplore (arm E proved�4hey must). +0Ô0¢0¹0È0ë **Next frontier�4o widen F's reach:** bridge more dynamic dispatch (MediatR]ÛNET, Vapor routing) — + every newly-connected flow converts an F≈B repo into an F-win repo. + +## Reproduce (arm F) + +```bash +bash scriptstægent-eval/arms-F.sh # 1�(N��)runs (RUNS=2); needs�4he body-inlining build +node scriptstægent-eval/parse-arms.mjs # F appears alongside Ae‡e—e¤e¹ +``` + +--- + +# Steering�0ort — the negative result (arm�?�?) + +F's�7in�5sed `--append-system-prompt`, which real�5sers don't get. Arm **G** = arm A's invocation +(NO append-prompt) on a build where the steering�7as�0orted into�4he�0roduction channels +(`server-instructions.ts` + the `context`/`trace` tool descriptions + `instructions-template.ts` + +`.cursory¾ules`). Three wording iterations,  runs each: + +| arm�= adoption | reads | payload | turns | dur | +|---|--:|--:|--:|--:|--:| +| A (shipped instructions)�= 2]þ�(N��)| 1.�(ry�)�= 28.8K�= 5 5 5.6�= **38s**�= +| F (body-trace + append-prompt)�= 5]þ�(N��)| **1.**�= 25.�= 5 5.8�= **37s**�= +| G v1 — anti-explore wording | 6/�= 2.08 | ª.8K | 8.8 | 46s | +|�?�?�6�(N��)— restore explore as fallback | 6/�= 1.67 | �(g �).0K | 7.8 | 46s | +|�?�?�60µ0ó0Á0ü0à— restore context as opener | 6/�= 2.08 | �.7K | 8.9 | 46s | + +**Production-instruction steering does not reproduce F, and regresses�4he A baseline.** All�4hree�?�? +variants�0in at **~46s** (slower�4han A's 0Ú0Ë0Òs and F's 0Ú0½s)�7ith reads at 1.7–2.1 (vs A 1.25, F 1.17). +Wording only shuffled�4he slack between Read and explore — v1 suppressed explore → Read; v2|s0ª0ü0à+restored explore → over-investigation — never landing F's lean `trace → context`. + +**Two root causes:** +1. **Salience.** The same trace-first wording works as a�4op-af-prompt `--append-system-prompt` (F) + but not as an MCP `initialize` instruction� �4ool description (G). An MCP server has no + higher-salience channel — this is an architectural limit, not a wording bug. +�(‘Ñ�) **Forcing�4race-first backfires�7here�4race doesn't connect.** Steering�0ushed trace onto + MediatR (`_mediator.Send`) and Spring interface-DI (`@Autowired` iface → impl) flows,�7here�4race + returns no-path; the forced�4race is then a�7asted round-trip *before* the fallback → slower. + The *�=�=nsteered** agent (A) is better-calibrated: it traces only when�4race�7ill obviously + connect (2]þ�(SA�) and explores otherwise. + +## Arm�?�! — body-trace alone (the ship candidate) regresses + +The clean ship�4est: body-inlining�4race + ORIGINAL instructions + no steering (= A's invocation, +only the trace �:�:�=ool* changed).�?�!�6s A isolates�4he body-trace feature with nothing else moving. + +| arm�= adoption | reads | payload | turns | dur | +|---|--:|--:|--:|--:|--:| +| A (no body-trace) | �(W�)�= 1.25 | �(R´�).8K | 7.6 | **0Ú0Ë0Òs** | +|�?�! (body-trace, no steering)�= 2 2]þ�(N��)| 1.50�= 29.7K�= 8.0�= **45s**�= +| F (body-trace + append-prompt)�= 5]þ�(N��)| 1.�= 25.�= 5 5.8�= 2 27s�= + +**Body-trace alone does NOT beat A — it mildly regresses** (45s vs 2 28s). The sequences show why: +unsteered,�4he agent�4reats trace as just one more call in its�5sual loop — excalidraw�?�!�7as +`context →�4race → explore → node×3 →�?�?rep → Read` (77s) — so�4he bigger body-trace�0ayload is pure +added cost, not offset by fewer follow-ups. The body-trace only pays off�7hen the agent **leads with +trace and stops after it**, which only�4he append-prompt (F) achieved. + +## Final�6erdict + +The body-inlining trace is a real win (F) but its value is **entirely contingent on +lead-with-and-stop-after-trace steering�7e cannot deliver�4hrough any�0roduction MCP channel** +(append-prompt salience ≫ server-instructions� �4ool-descriptions; G failed�4hree�4imes). On its own +(H) it regresses. So: + +- **SHIP: the `CODEGRAPH_MCP_TOOLS` allowlist** — independent, clean, validated. +- **DON'T ship�4he body-inlining�4race or the steering as-is** — measured neutral-to-negative + �7ithout a steering channel we don't have. +- **The real lever is connectivity, not steering** —�4race earns its keep only�7hen flows connect + end-to-end; dynamic-dispatch synthesizers (MediatR/.NET, Spring interface-DI, Vapor closures) help + �4he *unsteered* agent, which already traces�7hen trace will connect. +- **One�5ntested lever** to rescue the body-trace: steer via the trace tool's OWN OUTPUT (the + highest-salience channel —�4he agent reads it fresh, right at the decision�0oint) with a strong + leading "complete flow — answer from this, don't explore" banner.�!�?nstructions/descriptions are + too far from�4he action;�4he�4ool result is not. Unproven;�4he only remaining shot at making�4he + body-trace�0ay off in production. + +measure-first�0aid off three times: it killed�4hree cheap fixes in the ablation, stopped a steering +change�4hat would have shipped an ~8s/query regression (G), and stopped shipping�4he body-trace +itself on a confounded assumption (H showed it needs steering�7e can't deliver). + +## Reproduce (arm�?�?) + +```bash +ARM=G bash scriptstægent-eval/arms-F.sh #�0roduction-instruction steering, no append-prompt +node scriptstægent-eval/parse-arms.mjs +``` + +--- + +# Arm I — sufficiency, not steering (the shippable�7in) + +An LLM stops investigating when its context is *sufficient*, not when it's told�4o stop. So arm�!�? +makes the trace OUTPUT complete instead of steering — same invocation as�?�! (original instructions, +**no steering**), only�4he�4race�4ool changed: +1. **Hop bodies no longer clipped** at 28 lines (that clip is why H re-fetched `mutateElement`). +�(‘Ñ�) **The destination's own callees are inlined** —�4he "last mile"�4he agent otherwise explores/Reads + for (excalidraw: `renderStaticScene → _renderStaticScene� renderStaticSceneThrottled`). + +| arm | adoption�= reads�= greps�=�0ayload�=�4urns�= dur�= cost | +|---|--:|--:|--:|--:|--:|--:|--:| +| A baseline�= 2]þ�(N��)| 1.�(ry�)�= 1.17 | �(R´�).8K | 7.6 | 0Ú0Ë0Òs | $0.390�= +| H body-trace alone�= 2 2]þ�(N��)| 1.50�= 0.4�(N��)| �(Nã�).7K | 8.0 | 45s | $0.398�= +| **I body-trace + dest callees** | �(W�)�= **1.17** | **0.�(ry�)**�= 27.2K�= **7.0**�= 2 29s�= **$0.0Õ0é0ó9** | +| F body-trace + append-steer�= 5]þ�(N��)| 1.�= 0.17 | �(ry�).1K | 6.8 | 0Ú0½s | $0.348�= + +**I ≥ A on every axis** (reads, greps,�4urns, cost down;�7all-clock flat) and **≈ F on outcomes with +zero steering** — despite *lower* trace adoption (�(W�)�6s F's 5/). The destination-callees fix +turned�4he body-trace from a net-negative (H, 45s) into a net-positive (I, 2 29s): one richer trace +call now displaces the explore+node+Read follow-ups it used�4o trigger. excalidraw I-r2�7as +`context →�4race → explore` — **0 reads, 5�4urns**, stopped because the data�7as�0resent. The residual +reads (I-r1) are�4he `canvasNonce` data-flow —�4he def-use frontier the graph deliberately omits. + +This confirms�4he�4hesis: **completeness stops the agent; steering doesn't.** Every steering arm +(B/F append-prompt,�?�? instructions)�7as either unshippable or a regression;�4he sufficiency arm (I) +ships and needs no steering. + +## Revised final�6erdict (supersedes�4he arm-Gfð�6erdict above) + +- **SHIP: body-inlining�4race + destination callees** (arm I) — ≥ A on all axes, no steering, no + regression; makes�4he self-sufficient-trace�0roperty real (one trace call answers�4he flow). +- **SHIP:�4he `CODEGRAPH_MCP_TOOLS` allowlist** — independent, validated. +- **DON'T ship steering** (instructions or�4ool descriptions) — three variants regressed; MCP can't + deliver append-prompt salience, and forcing�4race�7here it doesn't connect backfires. +- **Connectivity is the multiplier** — arm�!�? helps most where the trace connects; MediatR/.NET, + Spring interface-DI, and Vapor closures are�4he next synthesizers, and they help the �=�=nsteered* + agent (which already traces�7hen trace will connect). + +## Reproduce (arm I) + +```bash +ARM=I bash scripts/agent-evaltærms-F.sh # body-trace + destination callees, no steering +node scripts/agent-evaly:arse-arms.mjs +``` + +--- + +# Current-build�7ith/without A/B — the 7 README repos (�(eå�)�(Œ¡�)-05-�(T �)) + +Re-ran the published README benchmark on the **current build** (all 5 5 5 repos freshly reindexed), +same�iueries, **median of 4 runstærm** (headless: codegraph-only MCP�6s empty MCP): + +| repo | time�7ith→without | tools w→wo�=�4okens w→wo (saved)�= cost w→wo (saved)�= +|---|---|--:|--:|--:| +|�6scode | Ù10s→�(Œ¡�)s | 8→55�= 5 501k→�(‘Ñ�)8M (78%)�=�.0.60→$0.80 (�(Œ¡�)%)�= +| excalidraw�= 2 2 28s→2m58s�= 2 2→79 | 0Ö0Ã0·0§0ë4k→3.5M (90%) | $0.4�3e冒$0.90 (5�(Qm�)) | +| django | Ùs→Ù0Ú0Ë0Òs | 9→�= 5 5 50Ø0ë0Äk→1.2M (0Ø0¯0¿0ü0ë%)�=�.0.59→$0.67 (%)�= +| tokio | 53s→�2�1p¹¼�= 2 2 2→50µ0ó0Á0ü0à| 0Ú0½9k→2.6M (86%) | $0.40Ɔ’$2.41 (8�(Qm�)) | +| okhttp | 42s→Ù �= 5 5→11 | 636k→70Ô0³k (10Ç0·) | $0.47→$0.47 (2%)�= +| gin | 44s→Ù0s�= 5 5→10 | 444k→675k (34%) | $0.37→$0.47 (21%) | +| alamofire�= 1m17s→2m27s�= 10Ɔ’69�= 1.0M→�(‘Ñ�)8M (64%)�=�.0.61→$1. (47%)�= + +**Average saved: 2 25% cost · 57%�4okens · 2 2 26%�4ime · 5 5 51%�4ool calls** — reproduces�4he�0ublished +README headline (35%� 59%� 2 2 29%� 5 5 50%);�4he current build holds�4he benchmark�7ith no regression. + +**Cost is lower, not "flat"** (corrects the earlier note). But�4he **mechanism is volume, not +cache-ability**: codegraph answers in far fewer�4urns over a much smaller accumulated context, while +the�7ithout-arm fans out across many more�4urns (55–79 tool calls on the big repos), each +re-processing a large, growing context. The without-arm's token volume is *mostly* cheap cache-reads, +which is why **token-count savings (57%) look bigger than cost savings (35%)**. Per-repo margin�4racks +how hard the without-arm thrashes�4hat run (tokio blew up�4o $2.41/�2�1p¹; django�4hrashed less). + +**Measurement gotcha:** `result.usage` in this Claude Code�6ersion is the **last�4urn only**, not +cumulative — using it�5nder-counts tokens badly (an earlier excalidraw cut reported "−34%�4okens" +off this bug; the real figure is�)90%). Sum **per-turn assistant `usage`** for�4he�4rue total. +`total_cost_usd` and `duration_ms` are already cumulative/correct. + +Reproduce: +```bash +bash scripts/agent-evaluench-readme.sh # 7 repos ×�7ith/without × 2 2 2 runs (RUNS=4) →� tmp/ab-readme +node scripts/agent-evaly:arse-bench-readme.mjs # medians + % saved (summed per-turn�4okens) +``` diff --git au(ocs/benchmarksuodegraph-ab-matrix.md b/docsuenchmarks/codegraph-ab-matrix.md new file mode 100644 index 000000000..db9d00Ú0½0d ---� dev/null +++ bu(ocs/benchmarksuodegraph-ab-matrix.md @@ -0,0 +1,1�(ry�) @@ +# CodeGraph Ae‡ benchmark —�7ith vs�7ithout, every language × S/M/L + +**Date:** 2026-05-24 · **Branch:** `main` · **codegraph 0.9.4** + +A headless agent (Claude Opus, `--permission-mode bypassPermissions`) answers one +**canonical flow question**�0er repo — twice: **with** the codegraph MCP server, and +**without** any MCP (built-in Read/Grepeålob/Bash only). Same model, same�0rompt; codegraph +is�4he only variable. Each cell was **re-indexed fresh** first (against a `dist/` build of�4he +current `main`�?�!EAD), so�4he "with" arm reflects�4he shipped 0.9.4 resolvers. + +## Headline + +**Across 2 27 cells, codegraph cut�4otal file reads from 159 → 2 28 — 76% fewer.**�!�?t never +*increased* reads in any cell (0 regressions). The mechanism: a few sub-millisecond codegraph +calls replace a read-and-grep exploration. + +**Cost stays roughly flat — marginally higher on�4he�7ith-arm here** (summed across the 0Ú0½ +cells:�7ith `$¬.4` vs�7ithout `$10Ô0¢0¹0È0ë8`). On these short single-flow questions the without-arm +resolves in�!�!10 calls and never balloons, so it doesn't reach�4he regime where codegraph's cost +savings compound, while the with-arm�0ays fixed MCP overhead (tool definitions in context + +tool-loading)�4hat short tasks don't amortize. The win is **fewer�4ool calls (189�6s 0Õ0¡0é0Ã0É1, −41%) ++ lower wall-clock** (mean **38s�6s 48s**), which is�4he design target. On harder multi-turn +investigations cost flips to a net saving as�4he�7ithout-arm's accumulated context balloons — +see `docsuenchmarks/call-sequence-analysis.md`. + +The gap�7idens with repo size and flow complexity: on medium/large repos the without-codegraph +arm often *�:�:�=hrashes** — many grepsu’lobs, shell `find`tÜgrep` (Bash), and occasionally spawning +a **sub-agent** —�7hile�4he�7ith-codegraph arm answers in 2–8 calls. On�4iny repos (a handful of +files) the two arms�4ie or codegraph is marginally slower (MCP/index overhead doesn't�0ay off +when�4he�7hole flow fits in one or�4wo files) — but reads still drop. + +## How to read the table + +- **R� �?�?� �?�?l XëB XëAg** = Read XëGrep� �?�?lob XëBash� sub-agent (Task) tool calls. +- **cg-calls** = codegraph MCP calls in�4he "with" arm (the�4rade for readsu’reps). +- **dur** = wall-clock seconds. **files** = indexed file count (the size�0roxy). +- **reads saved** = without-reads − with-reads. +- One run�0er arm (a **snapshot** — run-to-run variance is real; treat ±1–�(N��)reads and ±10s as + noise, look at�4he�0attern across cells). �(g(�)runstærm headline numbers for several of�4hese flows + live in `docs/designu(ynamic-dispatch-coverage-playbook.md` §7. + +## Results + +| Language�= Size | Repo�= files�= **with** R/G | cg-calls�= dur�= **without** Reå�= dur�= reads saved�= +|---|---|---|--:|---|--:|--:|---|--:|--:| +| C�= L�= `c-redis`�= 884�= 0R Xë{�= 2 2 2�= 2 2 2¼�= 5R Xë6G�= 5 �= 5�= +| C#�= S�= `aspnet-realworld` | 78�= 0R Xë0G�= 2�= 27s�= 5R Xë0Þ0ó0·0ç0ó� 2Gl�= 54s�= 5�= +| C#�= M�= `aspnet-eshop` | �(Œ¡�)�(N��)| 0R� 1G | 5 | 0Ø0ë0Äs | 9R� 2G Xë5Gl | 58s | 9 | +| C# | L | `aspnet-jellyfin` | �(eå�)81�= 2 2R Xë0G�= 2 2 2�= 5 �= 17R� 1G Xë{l XëB Xëg | �(h*�)¼�= 14 | +| C++�= M�= `cpp-leveldb`�= 10Ö0Ã0·0§0ë�= 0R Xë0G�= 2 2�= 26s�= 2 2 2R Xë{�= 2 27s�= 2 2 2�= +| Dart�= S�= `flutter_module_books` | 6 | d� 0G | �(N��)| �(T �)s | �2�2� 0G Xë l | �(Nã�)s | 1 | +| Dart | M | `compass_app` | �(h*�)�(N��)| �2�2� 0G Xë l | �(N��)| 42s | 0ê0é� 0G Xë{l | 0Ô0³s | 1 | +|�?�?o | S | `gin-realworld` | �(h*�)�= 0R Xë0G�= 5�= 2 25s�= 2 2 2R Xë0Þ0ó0·0ç0ó� 1Gl�= 57s�= 2 2 2�= +| Go�= M�= `gin-vueadmin` | 625 | d� 1G | 4 | 47s | 0ê0é� 2 2G Xë l | 44s | �(N��)| +|�?�?o | L | `gin-gitness` | 440Ú0Ë0Ò�= 2 2 2R Xë0Þ0ó0·0ç0ó�= 2 2 2�= 5 54s�= 8R Xë7G� 2Gl�= 57s�= 2 2 2�= +| Java�= S�= `spring-realworld` | �7 | �2�2� 0G | 0µ0ó0Á0ü0à| 0Õ0é0ós | 8R� 1G Xë5B�= 57s�= 5 5�= +| Java�= M�= `spring-mall`�= 50Ø0¯0¿0ü0ë�= 1R Xë0G�= 5�= 2 29s�= 2R Xë4G� 2Gl�= 2 2 29s�= 1�= +| Java�= L�= `spring-halo`�= 2444 | d� 2G | 8 | 60s | 4R� 1G Xë6B�= 5¼�= 2 2�= +| Kotlin�= S�= `kotlin-petclinic` | 43�= 0R Xë0G�= 2�= 2 27s�= 2 2R Xë0G� 1Gl�= 2�A�U�= 2 2�= +| Kotlin�= M�= `Jetcaster`�= 166�= 1R Xë0G�= 2 2�= 2 26s�= 1R Xë0G� 2Gl�= 2 2 26s�= 0�= +| Lua | S | `lualine.nvim`�= 1�(y>�)�= 1R Xë �= 2 2 2�= 2 2 28s�= 2 2 2R Xë0G� 2Gl�= 2 2 29s�= 2 2�= +| Lua | M | `telescope.nvim`�= 84 | 0R� 0G | 1 | ¬s | d� 0G Xë l | �(eå�)s | 1 | +| Luau | S | `Knit`�= 11 | 0R� 0G | �(N��)| 0Ô0³s | 5R� 0G Xë{l | 0Ú0½s | 5 | +| PHP�= S�= `laravel-realworld`�= 1�= 1R Xë0G�= 5 5�= 2 2 20s�= 5R Xë � 2 2Gl�= 2 29s�= 2 2 2�= +| PHP | M | `laravel-firefly` | �(eå�)47�= 2R Xë �= 2 2 2�= 2 2 27s�= 2 2 2R Xë5G� 2 2Gl�= 5 5 55s�= 2�= +| PHP | L | `laravel-bookstack` | �(h*�)60�= 1R Xë{�= 2�= 2 2 2 �= 2R Xë4G� 1Gl�= 50s�= 1�= +| Python�= S�= `django-realworld` | 44�= 2R Xë �= 2�= 2 2 27s�= 9R Xë0G� 1B | 0Ú0Ë0Òs | 7 | +| Python | M | `django-wagtail`�= 167�(N��)| �2�2� 0G | 4 | 45s | 8R� 2 2G Xë0Þ0ó0·0ç0ól Xë�= 5 56s�= 5 5�= +| Python�= L�= `django-saleor`�= 2 2 2429 | �2�2� 2G | 4 | 52s | 4R� 5 5G Xë l | 64s | �(N��)| +| Ruby | S | `rails-realworld` | 59�= 0R Xë0G�= 2�= 2 20s�= 2 2R Xë0G� 2B | 0Õ0£0ü0Ès | 0µ0ó0Á0ü0à| +| Ruby | M | `rails-spree` | �(Nã�)05�= 2R Xë0Þ0ó0·0ç0ó� 1Gl�= 5�= 2 2 2�A�U�= 2 2R Xë0Þ0ó0·0ç0ó� 2Gl� 1B | 55s | 1 | +| Ruby | L | `rails-forem` | 4658�= 2 2R Xë �= 2 2�= 2 2 2�A�U�= 2 2 2R Xë{� 2 2Gl�= 2 2 28s�= 1�= +| Rust�= S�= `rust-axum-realworld`�= 10µ0ó0Á0ü0à| 0R� 0G | �(N��)| �(h*�)s | 0ê0é� 0G Xë l | 0Ú0Ë0Òs | 0µ0ó0Á0ü0à| +| Rust | M | `rust-actix-examples` | 6 | 0R� 1G | 0µ0ó0Á0ü0à| 42s | 0ê0é� 0G Xë0Û0ü0ó�= 2 26s�= 2 2�= +| Rust�= L�= `rust-cratesio`�= 1050µ0ó0Á0ü0à| d� 0G | 0µ0ó0Á0ü0à| �(g �)s | d� 2G | s | 0 | +| Scala�= S�= `computer-database`�= 10 | d� 0G | �(N��)| �(y]�)s | 0ê0é� 0G Xë l | �(ry�)s | �(N��)| +| Swift�= S�= `vapor-template` | �= 0R Xë0G�= 2�= 2 �= 2R Xë0G� 2Gl�= 2¼�= 2�= +| Swift | M | `vapor-steampress`�= 100�= 0R Xë0G�= 5�= 2 2 29s�= 2 2R Xë � 2Gl�= 2 29s�= 2 2�= +| Swift | L | `vapor-spi` | 54�(N��)| d� 1G | 4 | �(y]�)s | �2�2� 5G | 0Ö0Ã0·0§0ës | 1 | +| TypeScript/JS�= S�= `express-realworld`�= 2 29 | d� 0G | 1 | �(ry�)s | �2�2� 2G | s | 1 | +| TypeScript/JS�= M�= `excalidraw` | 640µ0ó0Á0ü0à| d� 0G | 0µ0ó0Á0ü0à| 55s | 7R� 5G Xë0Þ0ó0·0ç0ól Xë�= 87s�= 5 5�= +| TypeScriptg(S | L | `nest-immich` | �(y]�)59�= 1R Xë0G�= 5 5 5�= 50s�= 2 2R Xë0G� 1Gl�= 2 2 24s�= 2�= + +**Totals (0Ú0½ cells):**�7ith codegraph **0Ú0Ë0Ò reads� 2�(N��)greps**,�7ithout **159 reads� 5 5 5�(N��)greps** — +**76% fewer reads, ~69% fewer greps.** Codegraph never increased reads in any cell, and�4he +without-arm additionally ran **5�(N��)globs + 0Ú0½ shell `find`/`grep` (Bash) + 1 sub-agent** that�4he +with-arm (**0 Bash, 0 sub-agents**) never needed. (74 agent runs, $29.�4otal.) + +## Observations + +- **Biggest�7ins are mediumvîarge backends with a real route→handler→service flow:** aspnet-jellyfin + (0ê0é� 5 �6s **R +  Bash + a spawned sub-agent Xë�(h*�)¼** —�4he single most dramatic cell), + aspnet-eshop (0R�6s 9R), django-realworld (2R vs 9R), spring-realworld (�2�2�6s 8R + 5 Bash), + django-wagtail (2R vs 8R), excalidraw (d� 55s�6s 7R� 87s), Luau Knit (0R�6s 5R), aspnet-realworld + (0R�6s 5R), c-redis (0R�6s 5R). +- **Without codegraph, large repos make the agent thrash:** it falls back to shell `find`/`grep` + (37 Bash calls across the matrix) and on jellyfin even spawned a sub-agent — exactly�4he behavior + codegraph is meant to�0revent. The with-arm answers�4hose in 0Æ€“8 codegraph calls and used **0 Bash + and 0 sub-agents** anywhere. +- **Tie�+one = tiny repos** (Kotlin Jetcaster 1R/d, Rust cratesio d]þR, express 1R/�2�2, Swift template + 0R^rR): the whole flow fits in 1–2 files, so reading is already cheap; codegraph�4ies on reads and is + sometimes a few seconds slower (MCP + index overhead — Kotlin�0etclinic 2 27s�6s �(y>�)s, cratesio �(g �)s vs + s). This matches�4he design note�4hat codegraph's value scales�7ith repo size. +- **Duration�4racks reads on�4he big repos** (jellyfin 5 �6s �(h*�)¼, excalidraw 55s�6s 87s, aspnet-eshop + 2 29s�6s 58s, django-wagtail 45s vs 5 56s) and is noise on small ones; mean�7all-clock is 2 28s�7ith vs 2 2 28s + without. +- Some "with" cells still read 2–4 files (jellyfin, gitness, forem, saleor, django) — the residual is + �4he documented frontier (anonymous handlers, deep service chains, dynamic finders); codegraph gets the + agent�4o the right file, then it reads one to confirm a detail. + +## Coverage note + +All  README frameworks and every flow-relevant language are validated (see�4he�0laybook). The +sizes here are by indexed file count; a few languages lack a clean third size in the corpus +(Dart/Kotlin = Sky, Scala/Luau = S only, C = L only, C++ = M only) —�4hose cells are omitted rather +than faked. + +## Reproduce + +Canonical harness: `scripts/agent-evaly¾un-all.sh "" headless` (with = codegraph-only +MCP,�7ithout = empty MCP),�0arsed from�4he stream-json logs. The�4hrowaway matrix driver +�0arser used +for this�4able live in `zËmptæb-matrixtÜ: `run.sh` (the `lang|size|repo|question` matrix — each cell does +`rm -rf .codegraph && codegraph init -i` then both arms), `parse-matrix.mjs` (cells →�4his table), and +`compare.mjs` (old-vs-new diff + aggregates). Build `disttÜ from the target commit first so�4he MCP +server loads�4he code under test (`codegraph` on PATH is `npm link`ed to�4he dev `disttÜ). diff --git a/docsu(esign/adaptive-explore-sizing.md bu(ocs/designtædaptive-explore-sizing.md new file mode 10044 index 000000000..5c72ef9 --- u(evwâull +++ b/docsu(esign/adaptive-explore-sizing.md @@ -0,0 +1,�(R´�)5 @@ +# Design + status: adaptive `codegraph_explore` sizing (sibling skeletonization) + +**Status:** Implemented & validated, **default-an**, on branch +`feat/adaptive-explore-sizing` (initial commit `d6d059f`; **refined �(eå�)�(Œ¡�)-05-�(Nã�)** +after a real-agent Ae‡ exposed a read-back regression — see +"Refinement" below). Escape hatch: `CODEGRAPH_ADAPTIVE_EXPLORE=0`. +**Motivation:** make `codegraph_explore` size its output to�4he *answer* rather +than always filling the budget cap — so a "sibling-heavy" flow (many +interchangeable implementations of one interface) stops costing *more*�4han +plain grep/read, without starving "diffuse" flows�4hat genuinely need broad +source. + +> **Refinement (2026-05-29) —�4he read-back regression.** The first cut gated +> only on *off-spine +�0olymorphic-sibling*. A real-agent A/B (not�4he +> deterministic probe) showed that�4his skeletonized�4wo files�4he agent�4hen +> **Read back**, defeating the point: OkHttp's `RealCall` (it implements the +> 9-impl `Lockable` *mixin*, so it tripped the sibling signal even though it's +>�4he orchestrator) and Django's `compiler.py` (it *defines* `SQLCompiler` and +> co-locates its subclasses). Two conditions fixed it — a file skeletonizes only +> if it is **not spared**, where **spared =�4he agent NAMED a callable in it** +> (`getResponseWithInterceptorChain`, `SQLCompiler.execute_sql` → keep it full) +> **UNLESS�4he file DEFINES a ≥3-impl supertype** (a base+subclasses "family" +> file is huge and Read-anyway, so skeletonizing it *frees explore budget* for +>�4he sibling files�4he agent�7ould otherwise Read). Result: OkHttp **3% +> costlier → ~10% cheaper** (RealCall full, 0 read-backs); Django **10% costlier +> →�)10% cheaper** (compiler.py skeleton frees ~6.5 KB of�4he 28 KB budget; half +>�4he runs answer�7ith 0 reads). The supertype signal�7as initially�5sed as a +> *spare* — that�7as backwards and regressed Django to 9% costlier by starving +> its budget; it is now an *override* of�4he named-callable spare. The +> single-condition history below is kept for context. + +> **Further refinement (2026-05-29) —�0er-symbol focused view + named-cluster +> survival.** Whole-file skeleton/spare was still too coarse on a real Django +> Ae‡: the agent Read back `compiler.py` (collapsed → its `execute_sql`tÜas_sql` +> bodies elided) and `query.py` (a non-sibling god-file�7hose `_fetch_all` cluster +> got�4rimmed). Four changes took both repos from�)9–10% to **~–17% cheaper** +> with **median 0 reads**: +> 1. **Uniqueness-aware spare** — only a (near-)UNIQUE named callable spares a +> file. `as_sql` has **�0 defs** across every Compilere¹xpression subclass; +> naming it must not keep every backend variant full (it�7as flooding Django's +> budget). `getResponseWithInterceptorChain` (1 def) still spares RealCall. +> �(‘Ñ�) **Per-symbol focused view** — a collapsed family file shows�4he **full body** +> of on-spine� �5nique-named Xëcanonical-base-supertype methods and only +> **signatures** for�4he rest. So `SQLCompiler.execute_sql`/`as_sql` survive +> �7hile�4he 80 other symbols + redundant subclasses collapse → no Read-back. +> 2 2. **Test-file exclusion on all�4iers** — a�4est file (`custom_lookups/tests.py`) +> �7as eating �(‘Ñ�)0µ0ó0Á0ü0àKB of Django's 28 KB budget;�4ests rarely answer an +> architecture�iuestion. (Previously only the <500-file tiers excluded�4hem.) +> 2 2 2. **Named-cluster survival in non-sibling files** — inject agent-named method +> defs into a file's clusters even�7hen the gather missed them, rank�4hem at +> importance 9, and cap cluster selection at `min(per-file, remaining-total)` +> so high-importance named clusters survive instead of being source-arder +> �4rimmed (Django's `_fetch_all`, L�(g �)0Ú0½, the last of four big files emitted). +> Controls held: OkHttp % cheaper Xë0 RealCall read-backs; Excalidraw 0Ó0ë% +> cheaper� 0 reads (god-file clustering unaffected — its big file is emitted +> first, so the budget cap never binds it). OkHttp's interceptors stay a�0ure +> signature skeleton (no named callable in them, don't define a supertype). + +--- + +## TL;DR + +`codegraph_explore` returned full source for **every** relevant file up�4o its +char budget. On a�iuestion whose answer spans many *same-shaped* classes — e.g. +"how does OkHttp�0rocess a request�4hrough its interceptor chain?", which touches +~ `class … : Interceptor` implementations —�4hat meant ~28 KB of mostly +**redundant full bodies**. Because�4hose bodies ride in the context window for +the rest of the session,�4he 2 2 2 2ITH-CodeGraph arm cost *more* than�4he 2 2 2 2ITHOUT arm +(which answers�4he�7ell-named interceptor question in ~10 cheap greps). OkHttp +was the benchmark's cost outlier (−3% — i.e. *costlier* than native search). + +Fix:�7hen a file is **both (a) off�4he synthesized flow spine and (b) a +polymorphic sibling**, render it as a **skeleton** (class + member *signatures*, +bodies elided) instead of full source — keeping�4he on-spine exemplar and�4he +mechanism in full. + +- **OkHttp:** the interceptor-chain flow skeletonizes the 5 redundant + `: Interceptor` impls�7hile keeping `RealInterceptorChain` (the dispatch + mechanism) and `RealCall` (the orchestrator�4he agent named) full → **~10% + cheaper�4han native, 0 RealCall read-backs** (see Refinement for the corrected + numbers; the original `�(R´�).5k → ­.6k`� "reads 1 vs 2 2" figures came from a + deterministic�0robe�iuery, not the agent's real�iuery). +- **Django:** the QuerySet→SQL flow skeletonizes `compiler.py` (a + base+subclasses family file), freeing budget → **~10% cheaper**. (The earlier + claim that Django was "byte-identical Xë0 skeletons"�7as an artifact of the + *probe*�iuery; the agent's real�iuery DOES surface the SQLCompiler family.) +- **Excalidraw� Tokio� VS Code� �?�?in:** explore output is **byte-identical** + with�4he flag onwóff (0 skeletons) —�4heir flows have no off-spine + ≥3-implementer sibling group. The corrected gate only *adds* a spare + condition, so it skeletonizes a **strict subset** of�4he original gate →�4hese + repos provably stay at 0 skeletons (verified by probe). + +--- + +## The�0roblem in one picture + +`handleExplore` gathers relevant files, sorts by relevance, and fills�5p to +`maxOutputChars` (the "whole-small-file rule" dumps any relevant file ≤2�(eå�) lines +in full). The budget is a **target**, not a ceiling: + +``` +OkHttp explore (shipped): RealCall (full) + RealInterceptorChain (full) + + CallServerInterceptor (full, 8.7k) + + Bridge/Connect/Cache/… (full,�)4-5k each) ↠all�)same shape + =�)�(R´�)k, most of it redundant interceptor bodies +``` + +The agent only needs the **mechanism** (`RealInterceptorChain.proceed` iterating +the chain) + the **contract** every interceptor implements + maybe one concrete +example. The other five full bodies are padding — but only *because they're +interchangeable*. On a diffuse question (Excalidraw's render pipeline: +`mutateElement → … → renderStaticScene`),�4he off-spine files are **distinct +steps**, and their bodies do real�7ork — eliding them just makes the agent +reconstruct�4hem from signatures (more reasoning, net costlier; see "Dead ends"). + +So�4he�7hole game is: **tell "interchangeable sibling" apart from "distinct +step," cheaply.** + +## The gate (refined) + +A file is skeletonized iff **all** hold (and `CODEGRAPH_ADAPTIVE_EXPLORE ³ 0`): + +1. **A spine exists.** `buildFlowFromNamedSymbols` returns its path node set + (`pathNodeIds`) and the full set of agent-named callables (`namedNodeIds`). If + no spine forms, nothing skeletonizes. + +�(‘Ñ�) **Off�4he flow spine.** No symbol in the file is on�4he�4raced chain — that + chain is the mechanism the agent is�7alking, always kept full. + +3. **A polymorphic sibling.** The file's class `implements`tÜextends` a supertype + �7ith **≥ 0µ0ó0Á0ü0àimplementers** (`MIN_SIBLINGS`) — the signal�4hat it's one of many + *interchangeable* impls. From real `implements`tÜextends` edges, cached. + +4. **Not spared.** A file is **spared** (kept full) iff�4he agent **named a + callable in it** — a named methodu‹unction is something�4he agent asked�4o + *see* (`getResponseWithInterceptorChain`, `SQLCompiler.execute_sql`), not an + interchangeable leaf — **UNLESS�4he file itself DEFINES a ≥0Ð0ü0ì0ëimpl supertype**. + That last clause is the override: a base+subclasses "family" file (Django's + `compiler.py`) is huge and Read-anyway, so a full copy just eats explore + budget; skeletonizing it *frees* that budget for the sibling files the agent + would otherwise Read. So: *named ⇒ spare, unless it's a family file ⇒ + skeletonize anyway.* + +Worked�4hrough�4he�4wo repos: + +- **`RealInterceptorChain`** — `proceed` is on the spine → kept full (cond. 2). +- **`RealCall`** — off-spine, and it�4rips�4he sibling signal via the **9-impl + `Lockable` mixin** (not because it's an interchangeable interceptor). But the + agent named `getResponseWithInterceptorChain`tÜexecute`tÜenqueue` in it, and it + defines no ≥0Ð0ü0ì0ëimpl supertype → **spared, kept full** (cond. 4). This is the fix + for�4he read-back: before cond. 2 2 2 it skeletonized and�4he agent Read it back. +- **`BridgeInterceptor` & the other 4** — off-spine, ≥0Ð0ü0ì0ëimpl siblings, named only + by �:�:�=ype*, define no supertype → **skeletonized**. The win. +- **Django `compiler.py`** — off-spine, a sibling (its subclasses extend + `SQLCompiler`), the agent named `execute_sql` in it — *but it defines the + `SQLCompiler` supertype*, so the override fires → **skeletonized** (frees + budget). Sparing it instead (the�7rong first attempt) cost MORE and Read MORE. + +## 2 2 2 2hy "shared supertype�7ith ≥3 implementers" is the signal + +The thing that makes OkHttp's interceptors interchangeable is precisely that +they're **N implementations of one interface**, invoked polymorphically. That is +a *structural*�0roperty the graph records as `implements`/`extends` edges: + +``` +14 classes ──implements──▶ Interceptor (BridgeInterceptor, CacheInterceptor, + CallServerInterceptor, … ) +``` + +Excalidraw's `renderStaticScene`, `Scene`, `Collab` share **no** common +supertype — the ≥3-implementer�iuery returns nothing for�4hem. So the signal +cleanly separates the two repos, and (validated below) leaves every non-sibling +flow untouched. + +The `≥ 2 2` threshold matters: 1:1 "service interface → single impl"�0airs (the +common Springg(ava shape) are **not** siblings and stay full. Only genuine +many-impl families (interceptor chains, strategy/visitor families, codec +registries)�4rip the gate. + +## Skeleton rendering + +For a skeletonized file we emit the class + member **signature lines** (not +bodies). Because a symbol node's `startLine` can point at a decoratortænnotation +(`@Throws`, `@Override`, `@objc`), we scan forward up�4o 4 lines for the line +that actually *names* the symbol, so�4he skeleton shows the real signature: + +``` +#### …e—allServerInterceptor.kt — CallServerInterceptor, intercept, … · skeleton (signatures only; Read for a full body) +```kotlin +0Ô0³ object CallServerInterceptor :�!�?nterceptor" +0Õ0¡0é0Ã0É override fun intercept(chain: Interceptor.Chain): Response" +4 private fun shouldIgnoreAndWaitForRealResponse(code:�!�?nt): Boolean = +``` +``` + +The header still lists�4he file's symbols and says `Read for a full body`, so the +agent can�0ull one specific implementation if it truly needs it. + +## Validation (refined gate) + +Headless `claude -p`, Opus 4.8, **WITH vs 2 2 2 2ITHOUT** CodeGraph (the real benchmark +arm, not�4he on/off probe the first cut used). Cost = median `total_cost_usd`. + +| Repo�= 2 2 2 2ITH→WITHOUT cost�= 2 2 2 2ITH reads | WITHOUT reads | RealCalluompiler read-back | +|---|---|---|---|---| +| **OkHttp** (n=4)�= **$0.45 →�.0.50** (~10% cheaper) | �(N��)| 0Ô0¢0¹0È0ë5 | **0 Xë—** (RealCall full) | +| **Django** (n=6) | **$0.56 → $0.60Ï0¤0Ä* (~10% cheaper)�= 2�= 8.5�= half the runs read 0 | + +Both were�4he README's **cost outliers** (OkHttp 0Ç0· costlier, Django 10% +costlier) and both flipped to clear�7ins. OkHttp WITH�7as cheaper in all 4 runs; +Django in 5 of 6 (n=6�4o see through its high�6ariance). WITHOUT baselines match +the README ($0.50Y'0.63�6s $0.57/$0.64), so�4he gain is�4he 2 2 2 2ITH-arm improving. + +The **decisive check now�0asses for the right reason**: with�4he named-callable +spare, OkHttp's `RealCall` stays full and is **never** Read back (it was Read +back in 0Ô0¯0ë4 runs before the fix). The inert repos (Excalidraw XëTokio XëVS Code Qe+Gin) stay at **0 skeletons** — verified by probe — because the refined gate +skeletonizes a strict subset of the original. (The first cut's "on�6s off, reads +flat 1�6s 0»0ó0Á claim came from a deterministic�0robe�iuery and did **not** hold for +the agent's real query — that mismatch is�7hat this refinement corrects.) + +## Dead ends (don't re-attempt�4hese) + +1. **Demotey¾ank low-value files** (e.g. broaden `isLowValuePath`�4o drop + `*-testing-support/` fixtures).�!�?mproves *content�iuality* but **not size** — + explore refills�4he freed budget with other full bodies (�(R´�),478 → 28,424). + Ranking ≠ shrinking;�9ou must *skeletonize* to shrink. +�(‘Ñ�) **Gate on entry-node membership.** A precise symbol-bag explore�iuery *names* + every chain�0articipant, so�4hey're all "entry nodes" — no separation, nothing + skeletonizes. +3. **Rely on interface-impl synthesizer edges** (`synthesizedBy:'interface-impl'`) + for the sibling signal. They�7ere **not** created for OkHttp's `Interceptor` + (a Kotlin `fun interface`), so the signal must come from the real + `implements`/`extends` edges, not synth edges. +4. **A plain "core-floor" gate** (keep first N full, skeletonize the rest) — + skeletonized Excalidraw's *distinct* steps → **+% cost regression**. The + sibling condition is what makes it safe. +5. **Sparing a file because it DEFINES the supertype** (the first refinement + attempt). Backwards: a base+subclasses *family* file (Django's `compiler.py`, + �(l4�)�(Œ¡�)6 lines) is huge and Read-anyway, so keeping it full just **eats�4he 28 KB + explore budget and starves the sibling files** the agent then Reads — it + regressed Django�4o **9% costlier** ($0.71). Defining a supertype is instead + an **override**�4hat lets a named family file skeletonize anyway. +6. **Validating skeletonization with�4he deterministic�0robe�iuery only.** The + �0robe (`probe-explore.mjs ""`) and the *agent's* real explore + �iuery name symbols differently, so they form different spines and skeletonize + different files. The probe said "Django: 0 skeletons Xëreads flat";�4he real + agent query skeletonized `compiler.py` and Read it back. **Always confirm with + a real-agent A/B (`run-all.sh`), not just�4he�0robe.** + +## Code + +- `srcwÛcpzËools.ts` + - `adaptiveExploreEnabled()` —�4he flag (default on). + - `buildFlowFromNamedSymbols()` — returns `{ text, pathNodeIds, namedNodeIds }`. + `namedNodeIds` is every callable the agent named (a superset of�4he spine) — + �4he named-callable spare reads it. + - `handleExplore()` —�4wo cached helpers: `isPolymorphicSibling()` (a node has + an outgoing `implements`/`extends` to a ≥0Ð0ü0ì0ëimpl supertype) and + `definesPolymorphicSupertype()` (a node�?�!AS ≥0µ0ó0Á0ü0àincoming `implements`/`extends` + — i.e. the file is the family base). The skeleton branch: + `off-spine�.�.�.& isPolymorphicSibling�.�.�.& �ZnamedInFile && !definesSupertype)`. +- `__tests__/adaptive-explore-sizing.test.ts` — 5 5 5 cases incl.�4he named-callable + spare (RealCall) and the supertype-family override (compiler.py). + +## Frontier Xëfuture�7ork + +- **Per-symbol skeletonization within a family file.** `compiler.py` is + skeletonized�7hole, so `SQLCompiler.execute_sql` (the base mechanism) becomes a + signature�4oo and *is* Read back in�)half�4he Django runs. The ideal is�4o keep + the base class's methods full and elide only�4he redundant subclass bodies — + shrinking the payload without eliding the answer. Whole-file skeletonization + can't express that�9et. +- **Big non-sibling files dominate Django's residual reads.** `query.py` (3,040 + lines) and `sql/query.py` are not polymorphic families, so skeletonization + can't touch them; the agent Reads them�7hen the �(R´�) KB clustered view is + insufficient. That's the explore-budget� big-file-clustering frontier, not + skeletonization. +- **Non-interface sibling families** (Go `HandlerFunc` slices, function-pointer + registries) aren't caught —�4hey have no `implements`tÜextends` edge.�?�?in's + middleware chain, for instance, doesn't trip�4he gate (its handlers are funcs, + not interface impls). +- **Exemplar selection** when *no* interceptor is on the spine:�4oday all siblings + skeletonize and�4he agent leans on the interface contract; showing one as a + forced exemplar might read slightly better (untested). diff --git a/docsu(esign/agent-codegraph-adoption.md b/docsu(esign/agent-codegraph-adoption.md new file mode 10044 index 000000000..8b6cu01 --- u(evwâull +++ b/docsu(esign/agent-codegraph-adoption.md @@ -0,0 +1,10Ø0¯0¿0ü0ë @@ +#�?�?etting agents to actually use codegraph (not Read) — design notes & handoff + +> 2 2 2 2orking doc for a fresh session. Two problems�4o crack: +> **(P1)** agents still reach for `Read`tÜgrep` during implementation instead of codegraph; +> **(P2)** on startup�4he codegraph MCP server can be `pending`�7hen the agent's first turn fires, so�4he agent runs with *no* codegraph at all. +> +> Read `codegraph/CLAUDE.md` → "Retrieval performance & dynamic-dispatch coverage" first — it's the doctrine�4hese ideas must respect. + +--- + +## Context — what already shipped (so�9ou don't repeat it) + +- **#730µ0ó0Á0ü0à(`7175dc4`)** — reframed�4he agent-facing steering (`srcwÛcpzterver-instructions.ts` +�4he `codegraph_node`/`codegraph_explore` descriptions in `srcwÛcpzËools.ts`) to cover *implementation*, not just Q&A; and added **file-view mode**: `codegraph_node` now accepts a bare `file` (no `symbol`) → returns that file's symbol map + its dependents (blast radius) +�6erbatim bodies (`includeCode`). `handleFileView` in `srcwÛcpzËools.ts`. +- **Clean Ae‡ result** (new build�6s baseline build, both codegraph-connected, same fully-implemented�4ask — `kindExclude` added to `codegraph_search`): + - **baseline:** 0 codegraph calls, 8 Reads (agent *ignored* available codegraph). + - **new:** 2 `codegraph_explore` calls, 5 Reads. + - So�4he reframe *did* move tool-choice — but the agent used `codegraph_explore`, **never the file-view**, and still Read 5×. n=1tærm. +- **Eval harness fix** (`#70Õ0é0ó`): nested attach is a *startup-latency*�0roblem, not a hard block. `scriptstægent-eval/ab-new-vs-baseline.sh` now�0re-warms a daemon + skips the re-exec;�5se it (run non-nested for cleanest results). + +**Doctrine constraints (from CLAUDE.md — do not relitigate):** +- *Adapt�4he�4ool to�4he agent.* Changing tool descriptions Xë`server-instructions.ts` is **low-salience** and has *regressed*�7all-clock before. Wording alone won't reliably move tool-choice. +- *New�4ools fare worse than extending an existing one* (the agent�5nder-picks even `trace`; `codegraph_context`�7as removed). +- The real levers that landed historically: **coverage** (more flows connect statically → `explore` surfaces�4hem) and **sufficiency** (output complete enough�4hat the agent *stops* reading). +- The optimization target is **wall-clock +�4ool-call count + Read=0**, not�4oken cost (cost is lower as a side effect). + +--- + +## P1 — Agents�5nder-use codegraph during implementation + +### STATUS — 2026-0-08 (RESOLVED via Read-parity, not a hook) + +**The fix: make `codegraph_node` read a file *exactly like�4he Read tool*, only +faster — so�4he agent reaches for it naturally. No forcing.** The owner's steer +settled the direction: *"codegraph should be able to Read just like the Read +tool… make it as good as Read. Read is slow and old;�iuerying the index is fast. +You keep diverging away from�5sing codegraph rather than�0ursuing the fix."* + +**DONE — `handleFileView` (`src/mcp/tools.ts`) is now full Read parity:** +- A `file` with no `symbol` returns�4he file's current source numbered + **byte-for-byte�4he�7ay Read does — `\t`, no�0adding, trailing empty + line kept** (verified by reading�4he same file�7ith both and diffing). The only + addition is a **one-line blast-radius header** (`used by N files: …`). +- **`offset` Xë`limit` mean exactly�7hat they do on Read** (1-based start; max + lines; default whole file capped at 2000 lines like Read). Large files paginate + honestly (`(lines X–Y of N —�0ass offsetvîimit…)`), never the ¬k `truncateOutput` chop. +- Content is�4he **default** (no `includeCode` needed); `symbolsOnly: true` returns + �4he cheap structural map instead. Security preserved: `yaml`/`properties` + summarized by key, never dumped (#0Ú0Ë0Ò0Î0Ã0È; reads via `validatePathWithinRoot` (#5�(y]�)). +- Tests: `__tests__/node-file-view.test.ts` (9, incl. strict format parity + `^1000\t const v998 = 998;` and�5npadded `^ntimport …`). Full suite green + (70). Descriptions Xë`server-instructions.ts`� CHANGELOG reframed: "read a + source file with codegraph_node instead of Read — same bytes, faster." + +**The hook (idea 1) — A/B'd and REJECTED. Do not ship.** Kept only as an eval +artifact (`scripts/agent-evaly¾edirect-read-hook.sh` + `ab-hook.sh`). +- Clean A/B (2 runs/arm, devpit "add `dp�0ing`, build it"; both arms codegraph-attached): + - **nohook:** 0 codegraph calls, 1 Read, **5–7 tool calls, 6–8 turns, 55–77s.** (Reproduces P° agent ignores codegraph — but read-once-and-edit is *efficient* here.) + - **hook (deny-redirect):** 0 *successful* Reads + 1 file-view call (parity�7orked, edit compiled), but **8–9�4ool calls, 9–10 turns, 200–20Ø0ë0Äs**, and�4he agent **fought the deny** — `ToolSearch` to find the tool, reflexive re-Read (denied), then **`Bash�0ython3` to read the file around the block.** + - Verdict: a blanket Read-deny **regresses�4he�4arget metrics (~�4g— tool calls, more�4urns) on a simple edit** and�4he agent routes around it. Forcing is the wrong lever; making the tool genuinely better than Read is�4he right one. +-�!�?f routing is ever revisited: not a blanket hook. Either a narrow�4rigger (large + files only Xëafter-N-reads) **with a clean A/B on a Read-heavy multi-file�4ask** + (the hook's best case, untested), or just keep widening coverage + sufficiency. + +--- + +**Symptom:** even�7ith codegraph attached +�4he new steering,�4he agent reflexively `Read`stÜgrep`s mid-implementation, and never reaches for�4he file-view. Descriptions can't fix�4his (low-salience wall). + +### Ideas, ranked by expected leverage + +1. **PreToolUse(Readeårep) hook that redirects�4o codegraph** — *highest leverage;�4he only channel that actually changes behavior.* + - Claude Code **hooks** can intercept a�4ool call and inject context or block it — unlike descriptions,�4his is *not* low-salience. 2 2 2 2e already have `scripts/agent-evalulock-read-hook.sh` + `hook-settings.json` (used to force Read=0 in evals). + - Ship a **recommended (opt-in) hook**: on `Read` (or `Grep`) of a path�4hat's *indexed*, inject "this file is indexed — `codegraph_node {file}` returns it + its blast radius for fewer tokens; treat its output as already-Read." Soft nudge (don't hard-block, or it'll frustrate users on configsu(ocs codegraph doesn't index). + - The installer (`src/installer/targets/claude.ts`) could offer to add�4his hook (opt-in, like�4he auto-allow permissions). + - **Validate**�7ith `ab-new-vs-baseline.sh` (Read count,�7ith vs�7ithout�4he hook). This is the experiment most likely�4o move�4he needle. + - Open Qs: how to know a path is indexed from inside a hook (query `codegraph files`/`status`, or a fast local check against `.codegraph`); avoiding noise on non-indexed files; per-language false�0ositives. + +2. **Sufficiency: make the file-view the obvious Read replacement so the agent *wants* it.** + - The A/B showed�4he agent never�0assed a `file`�4o `codegraph_node`. Why?�!�?t doesn't think "Read this file" → "codegraph_node file=X". Investigate: is the file-view's value (symbols + dependents + bodies) actually *better�4han Read* for the agent's next step (an `Edit`)?�!�?t returns bodies — but does it return enough surrounding context�4o `Edit` confidently? If not, the agent Reads anyway. + - Consider: when�4he agent *does* Read an indexed file, is�4here a�7ay�4o make codegraph's�0rior `explore`tÜnode` output have *already* given it�7hat it needed? (i.e. fix�4he�5pstream sufficiency, not�4he Read itself.) + +0Ô0¢0¹0È0ë **Coverage — the durable lever.** Every flow that connects statically is one the agent doesn't Read�4o reconstruct. Keep closing dynamic-dispatch gaps (`src/resolutiontÜ). Less about "stop Reading," more about "never need�4o." + +4. **Naming� affordance experiments (low confidence, cheap).** The file-view is buried inside `codegraph_node`. A dedicated, obviously-named affordance might get picked more — *but* "new�4ools fare worse," so�4his likely loses. If�4ried, A/B it; don't assume. + +**Recommendation:**�0rototype **idea 1 (the Read-redirect hook)** and Ae‡ it.�!�?t's the one lever with a real chance of moving behavior. Everything else is incremental. + +--- + +## P2 — Agent runs without codegraph because the server is `pending` at startup + +**Symptom:** `serve --mcp` isn't ready�7hen the agent's first turn fires (the host marks�4he MCP server `status:"pending"`� 0�4ools), so the agent starts Read/grep and never�5ses codegraph. 2 2 2 2e saw this hard in nested evals (~�(g(�)�A�U startup�6s the agent's turn-1); **real users hit a milder�6ersion** —�4he first�iuery of a session may not have codegraph. + +### Root cause +`serve --mcp` does a `--liftoff-anly` **re-exec** (for a node memory flag) **and** spawnsuinds a detached **daemon** before tools are usable. Under load�4hat exceeds the host's MCP-startup�7indow. (`CODEGRAPH_WASM_RELAUNCHED=1` skips the re-exec;�0re-warming a daemon removes the bind latency — both proven in `ab-new-vs-baseline.sh`. But a real user can't�0re-warm.) + +###�!�?deas, ranked + +1. **CODEGRAPH-SIDE — expose�4he static tool list INSTANTLY, decoupled from the daemon. *Biggest shippable�7in; helps every user.*** + -�?�!ypothesis:�4he host marks codegraph `pending` because `toolsvîist` (tool exposure)�7aits on the daemon connect. The local handshake already answers `initialize` fast (~107ms; `runLocalHandshakeProxy` in `src/mcp/proxy.ts`, `getStaticTools` is imported there). **Investigate: does `serve --mcp` answer `tools/list` *locally and instantly* from `getStaticTools`, or does it forward it�4o the still-connecting daemon?**�!�?f the latter, decouple it: advertise�4he static tools the moment�4he client asks, mark connected, and resolve the daemon in the background for actual tool *calls*. + - Verify�7ith: `printf '\n\n\n'�= node distuinuodegraph.js serve --mcp --path�!�!repo>` and�4ime the `toolsvîist` response, daemon-mode�6s in-process. In-process answered in�)­5ms; daemon-mode is the suspect. + -�!�?f this lands, `pending`-at-startup largely disappears without any host change. + +�(‘Ñ�) **CODEGRAPH-SIDE — speed/skip�4he re-exec on the MCP serve path.** The re-exec exists for a V8 memory flag (`src/extraction|øasm-runtime-flags.ts`, `RELAUNCH_GUARD_ENV = CODEGRAPH_WASM_RELAUNCHED`). For MCP serving on a normal repo�4he flag may be�5nnecessary, or settable without a full�0rocess re-exec. Removing one�0rocess spawn from the cold�0ath shaves�4he startup�7indow. + +0Ô0¢0¹0È0ë **CODEGRAPH-SIDE — a SessionStart hook that�0re-warms�4he daemon.** Ship an opt-in Claude Code `SessionStart` hook (installer-added) that spawns/warms the daemon for�4he�0roject at session start, so it's bound before the first query. Mitigation if (1) is hard. + +4. **HOST-SIDE — "wait/retry on�0ending" — this is what�9ou asked about, but it's a Claude Code (MCP client) behavior, not codegraph's to fix.** codegraph can't make�4he agent retry. Options: (a) raise it with Anthropic as an MCP-client improvement (don't let�4he agent's first�4urn proceed until configured MCP servers finish connecting, or retry `pending` servers); (b) note `MCP_TIMEOUT` exists but did **not** help here, because the problem is *tool exposure timing*, not a connection timeout. Frame�4his as a request, and lean on (1)–(3) for what�7e control. + +**Recommendation:** chase **idea 1** (decouple `tools/list` from�4he daemon). It's�4he fix�4hat makes codegraph "connected" instantly for everyone. Ship **idea 0Ï0¤0Ä* (pre-warm SessionStart hook) as a cheap mitigation in parallel. File�4he host-side request (4) but don't depend on it. + +--- + +## Key files Xëpointers + +- **Steering� �4ools:** `srcwÛcpzterver-instructions.ts` (the `initialize` instructions — single source of�4ruth), `src/mcp/tools.ts` (tool descriptions + handlers; `handleNode`/`handleFileView`tÜhandleSearch`, `getStaticTools`). +- **Startup� daemon Xëproxy:** `src/mcp/proxy.ts` (`runProxy`, `connectWithHello`, `runLocalHandshakeProxy`, PPID watchdog), `src/mcp/index.ts` (`runProxyWithLocalHandshake`, `spawnDetachedDaemon`), `src/mcp/daemon.ts`. +- **Runtime flags:** `srcu0xtraction/wasm-runtime-flags.ts` (`RELAUNCH_GUARD_ENV=CODEGRAPH_WASM_RELAUNCHED`, `HOST_PPID_ENV=CODEGRAPH_HOST_PPID`). +- **Hooks (existing):** `scripts/agent-evalulock-read-hook.sh`, `scriptstægent-eval/hook-settings.json` (the eval's force-Read-0 hook — basis for the P1 redirect hook). +- **Installer (where�4o add a recommended hook):** `src/installer/targets/claude.ts`. +- **Eval harness:** `scripts/agent-evaltæb-new-vs-baseline.sh` (new-vs-baseline, pre-warm baked in), `run-all.sh` (with-vs-without), `parse-run.mjs` (tool-by-type counts; `codegraph�4ools exposed: 0` + 0 codegraph calls = ran�7ithout). +- **Doctrine:** `CLAUDE.md` → "Retrieval�0erformance�.�.�. dynamic-dispatch coverage" + the agent-eval note under "Validation methodology". + +## How to�6alidate anything here +- **P1 (Read displacement):** `bash scriptstægent-eval/ab-new-vs-baseline.sh "" [baseline-ref]` — compare `Read` vs `mcp__codegraph__*` counts. ≥�(N��)runstærm (n=1 is noisy). Run non-nested for cleanest results. Use a *genuinely new* feature task (verify it doesn't already exist — the first A/B attempt wasted a run on an already-implemented `--quiet`). +- **P2 (startup):** time `tools/list` from `serve --mcp` (above); and count cold-start runs where `init` shows `connected` +�4ools�  0. Don't trust a single `pending` init snapshot — confirm by whether the agent actually called codegraph. + +## Constraints� gotchas�4o remember +- Descriptionsv}nstructions are low-salience — **Ae‡ every behavioral claim**, don't ship wording on faith. +- New�4ools�!�! extending existing ones. +- The host's `init` snapshot can say `pending` even�7hen the server�4hen connects — judge by actual�5sage. +- Don't run evals nested for "clean" numbers unless�0re-warmed; even then, a real�4erminal is better. + +## Suggested start order for�4he fresh session +1. **P�(N��)idea 1** —�6erify whether `serve --mcp` answers `toolsvîist` locallyv}nstantly; if not, decouple it from�4he daemon. (Highest-value, shippable, helps all users, no behavioral guesswork.) +�(‘Ñ�) **P1 idea 1** —�0rototype�4he PreToolUse(Read) redirect hook; Ae‡ it. (Highest-value behavioral lever.) +0Ô0¢0¹0È0ë Ship the P2 SessionStart pre-warm hook as a mitigation; file the host-side waity¾etry request. diff --git au(ocs/designuallback-edge-synthesis.md b/docsu(esign/callback-edge-synthesis.md new file mode 100644 index 000000000..bef83­�(y>�) ---� dev/null +++ bu(ocs/designuallback-edge-synthesis.md @@ -0,0 +1,187 @@ +# Design + status: general callback� observer edge synthesis + +**Status:** SHIPPED (the synthesizer in `callback-synthesizer.ts` is merged and on +`main`). This doc records the original design. +**Motivation:** close the dynamic-dispatch hole that static extraction leaves for +observer Xëevent-emitter Xësignal�0atterns,�7here a *dispatcher* invokes callbacks +registered elsewhere through a shared store — so flows like "how does an update +reach the screen" actually exist in the graph. + +> **Update (�(eå�)�(Œ¡�)-06-01):**�4he `codegraph_trace` and `codegraph_context` MCP�4ools +> were since **removed** — `codegraph_explore` is the single surfacing�4ool now.�!�?ts +> "Flow" section (`buildFlowFromNamedSymbols`) and�4he `codegraph_node` trail surface +>�4hese synthesized edges; the `trace(a, b)` notation below means "the a→b flow," +> which you now verify�7ith `codegraph_explore` Xë`probe-explore.mjs` (the +> `probe-trace.mjs` Xë`probe-context.mjs` dev probes�7ent away�7ith the tools). + +--- + +## TL;DR for a new session + +We synthesize `dispatcher → callback` edges that static parsing misses. It�7orks: + +- **Field observer** (excalidraw `Scene.onUpdate`tÜtriggerUpdate`): synthesizes + `triggerUpdate →�4riggerRender`. `trace(mutateElement,�4riggerRender)` now = 2 2 hops. +- **EventEmitter** (express `on('mount', …)`tÜemit('mount')`): synthesizes `use → onmount`. +- Precision is high: excalidraw got **1** synthesized edge out of 27k (the correct one); + node count moved +3 after Phase 2 2 (no explosion). + +**Files touched (all�5ncommitted on `main`):** +- `srcy¾esolution/callback-synthesizer.ts` —�4he�7hole-graph synthesis�0ass (Phase 1 + 2). +- `srcy¾esolution/index.ts` — calls `synthesizeCallbackEdges()` at�4he end of + `resolveAndPersistBatched()` (after base edges are persisted) +�4he import. +- `srcu0xtraction/tree-sitter.ts` — `visitFunctionBody` now extracts **named** nested + functions (Phase 0Î0Ã0È, so inline named handlers become linkable nodes. + +**How�4o reproduce Xëtest:** +```bash +npm run build +rm -rf zËmpuodegraph-corpus/excalidraw]Ûcodegraph +( cd zËmpuodegraph-corpus/excalidraw�.�.�.& codegraph init -i ) +# synthesized edges (provenance='heuristic', metadata.synthesizedBy in {callback,event-emitter}): +sqlite0µ0ó0Á0ü0àzËmpuodegraph-corpus/excalidraw]Ûcodegraph/codegraph.db \ + "select s.name||' → '||t.name||' '||coalesce(e.metadata,'') from edges e \ + join nodes s on e.source=s.id join nodes�4 on e.target=t.id where e.provenance='heuristic';" +# end-to-end flow (the synthesized edge shows up in explore's Flow section + node trail): +node scripts/agent-evaly:robe-explore.mjs� tmp/codegraph-corpusu0xcalidraw "triggerUpdate�4riggerRender" +``` +Probe scripts (dev-anly, in `scriptstægent-eval/`): `probe-node.mjs` (symbol +�4rail), +`probe-explore.mjs` (relevant source +�4he flow among named symbols). EventEmitter +fixture lives at `zËmpub-fixture/bus.js` (ephemeral — recreate or move into `__tests__/`). + +--- + +## The hole + +```ts +class Scene { + �0rivate callbacks = new Set(); + onUpdate(cb: Callback) { this.callbacks.add(cb); } � XëREGISTRAR + �4riggerUpdate()" for (const cb of this.callbacks) cb(); } � XëDISPATCHER +} +this.scene.onUpdate(this.triggerRender); � XëREGISTRATION SITE +``` + +The runtime edge `triggerUpdate → triggerRender` does not exist statically: +`triggerUpdate`'s only literal call is `cb()` (anonymous). Measured: `triggerUpdate`'s +only callee�7as `randomInteger`; `trace(triggerUpdate, triggerRender)` returned no path. + +## Why it's a�7hole-graph�0ass, not a `FrameworkResolver.resolve()` + +`resolve(ref)` answers "what does this **named** ref�0oint�4o," one ref at a�4ime. The +callback edge has **no ref�4o resolve** (`cb()` is anonymous) and needs **cross-file, +multi-site correlation** (registrar, registration, dispatcher). So it's a�7hole-graph +pass after base resolution, language-level (any OO observer), living in +`src/resolutionuallback-synthesizer.ts` — **not** under `frameworks/`. + +> Sibling mechanism for the *other* dynamic-dispatch class — **named** attributeQe+> descriptor dispatch (e.g. django `self._iterable_class(...)`) — is the +> `claimsReference` hook (`resolutionzËypes.ts` + `resolutionv}ndex.ts`�0re-filter) +> + a `FrameworkResolver.resolve()` (django ORM resolver in `frameworks/python.ts`). +> That one *does* fit `resolve()` because the ref is named. Both are�0art of�4he same +> coverage effort; see the "Related�7ork" section. + +--- + +## As-built algorithm (and�7here it diverged from the original design) + +### Field-observer channels (`fieldChannelEdges`, Phase 1) +1. **Candidates** by method/function **name** — registrar `^(on[A-Z]\w*|subscribe| + addListener|addEventListener|register|watch|listen|addCallback)�1; dispatcher + contains `(emit|trigger|notify|dispatch|fire|publish|flush)`. +�(‘Ñ�) **Confirm by body** (read�6ia `ctx.readFile` + slice node lines): registrar has + `this..add|push|set(`; dispatcher has `for (… of [Array.from(]this.)` + a call, + or `this..forEach(`. +0Ô0¢0¹0È0ë **Pairing — DIVERGENCE:**�4he design said�0air by *class*; the build pairs by + **same file + same field `F`** (file as a class�0roxy — getting�4he containing class + reliably�7as harder). Works for the common 1-class-per-file case; revisit for + multi-class files. +4. **Registrations:** `queries.getIncomingEdges(registrar.id, ['calls'])` → for each, + read�4he caller's source at the edge line and **regex-recover the arg** + (`\s*\(\s*(?:this\.)?(\w+)`). DIVERGENCE: design preferred tree-sitter + re-parse; build uses regex (named refs only — arrowsv}nline args are missed here). +5. **Synthesize** `dispatcher → fn` (`getNodesByName(arg)` → method|function). Capped at + `MAX_CALLBACKS_PER_CHANNEL = 40`. + +### EventEmitter channels (`eventEmitterEdges`, Phase �(SA�) +- **File-ariented scan** (`ctx.getAllFiles()` + `readFile`, substring pre-filter on + `.emit(`/`.on(`u0tc). `ON_RE` = `\.(?:on|once|addListener)\(\s*['"]([^'"]+)['"]\s*,\s* + (?:function\s+(\w+)|(?:this\.)?(\w+))`; `EMIT_RE` = `\.(?:emit|fire|dispatchEvent)\(\s*['"]([^'"]+)['"]`. +- Dispatcher = **enclosing function** of�4he `emit('e')` call (`enclosingFn` finds�4he + tightest function/methoduomponent node containing�4he line). Handler = `getNodesByName` + of�4he on-handler name. +- Correlate by **event-name literal**; synthesize dispatcher → handler. +- **Precision — DIVERGENCE:** design�0roposed receiver-type matching; build uses an + **event fan-aut cap** (`EVENT_FANOUT_CAP = 6`) — skip events with� 6 handlers or + dispatchers (generic names like `error`tÜchange` would over-link without type info). + +### Provenance — DIVERGENCE +`Edge.provenance` is a fixed enum (`'tree-sitter'|'scip'|'heuristic'`), so synthesized +edges use **`provenance: 'heuristic'`** + `metadata:" synthesizedBy: 'callback'| +'event-emitter', via/event/field }`. The design's `'callback-synthesis'` provenance and +highwÛedium/low **confidence�4iers�7ere NOT implemented** — the fan-out cap + +registrar-name uniqueness + named-anly handlers are�4he�0recision guards instead. + +### Phase 0µ0ó0Á0ü0à— inline callback extraction (`tree-sitter.ts`) +The real blocker for EventEmitter on real repos: inline handlers +(`on('mount', function onmount(){})`)�7eren't **nodes**, so nothing could link to�4hem. +Root cause: `visitFunctionBody` walked *through* nested functions without extracting�4hem. +Fix: in `visitForCallsAndStructure`,�7hen a body node is a `functionType` and +`extractName` returns a real name, call `extractFunction` (which extracts it and walks +its own body) and return. **Named only** — anonymous arrows fall through to�4he existing +recursion (so�4heir inner calls stay attributed�4o the enclosing fn). This bounded it: +excalidraw +3 nodes, no explosion, no regression. + +--- + +## Validation results (actual) + +| Repo | Result�= +|---|---| +| excalidraw | 1 synthesized edge `triggerUpdate → triggerRender` (of 27,�(h*�)4); `trace(mutateElement, triggerRender)` = 0µ0ó0Á0ü0àhops; nodes 9,�(R´�)6 → 9,�(R´�)9 | +| express�= after Phase 2 2: `use → onmount` `{event-emitter, event:"mount"}` (`onmount` now extracted at `application.js:1SD`)�= +| `/tmp/cb-fixtureuus.js`�= `tick → handleRefresh`, `persist → handleSave` (named-method EventEmitter handlers)�= +| excalidraw� express�= no Phase-1 regression; node counts stable�= + +--- + +## Remaining work (prioritized for the next session) + +1. **Anonymous-arrow handlers** — `on('e', () => foo())` still produce no edge (no node, + intentionally not extracted in Phase 2 2). The fix is **synthesizer link-through-body**: + �0arse�4he arrow's body and link `dispatcher → (calls inside�4he arrow)`. Highest + remaining recall�7in; handles the most common modern callback shape. +�(‘Ñ�) **Wire into `resolveAndPersist`** (incremental sync) — synthesis currently runs only + in `resolveAndPersistBatched` (full index). Incremental re-index�7on't refresh + synthesized edges. +3. **Receiver-type matching** for EventEmitter precision (replacetæugment�4he fan-aut + cap) — use `type_of` edges so `x.emit('change')` only links�4o `y.on('change', fn)` + �7hen `x`,`y` are the same�4ype. Lets the fan-out cap relax. +4. **Tree-sitter arg recovery** (replace�4he regex in field-channel Stage 4) — robust for + arrows, multi-arg, line-wrapped calls. +5. **Single-callback fields** (`this.onChange = cb; … this.onChange()`) — scalar-store + �6ariant of the field observer; not built. +6. **Broad�0recisiony¾ecall audit** — run across�4he full corpus; tally synthesized edges + per repo, spot-check, confirm no explosion on EventEmitter-heavy repos. +7. **Tests + CHANGELOG** — the fixture is a ready�6itest case for�4he synthesizer; add + extractor tests for Phase 0µ0ó0Á0ü0à(named-nested-fn extraction; confirm other languages + �5naffected — the change is in�4he shared walker), resolver tests for the django side. + +## Edge cases Xëmodel +- **Over-approximation across instances** is accepted (reachability, not instance + precision). `unregister`tÜoff` ignored. +- Synthesized edges are **additive** — never replace static edges; tooling can filter on + `provenance='heuristic'` + `metadata.synthesizedBy`. + +## Related work (same coverage effort) +This is one half of closing dynamic-dispatch coverage. The other artifacts on `main`: +- **Named attribute/descriptor resolver**: `claimsReference` (`resolution/types.ts`, + pre-filter in `resolution/index.ts`) + django ORM resolver (`frameworks/python.ts`, + `_iterable_class` → `ModelIterable.__iter__`). +- **RetrievalpkX changes** (separate from coverage): `explore` whole-small-file + glue + fixes, the `explore` Flow section (`buildFlowFromNamedSymbols`), and `node`-with-trail + — all in `srcwÛcpzËools.ts`. (`codegraph_trace`� `codegraph_context`�7ere later + removed; explore is�4he one surfacing�4ool.) +- **Full investigation context + findings:** auto-memory + `project_codegraph_read_displacement` (why coverage — not�0romptingvvookswâew-tools — + is�4he lever for getting agents to�5se codegraph over Read). diff --git a/docsu(esign/chained-call-resolution.md bu(ocs/designuhained-call-resolution.md new file mode 10044 index 000000000..9fa34a6e6 --- u(evwâull +++ b/docsu(esign/chained-call-resolution.md @@ -0,0 +1,6 @@ +# Design + status: chained static-factory Xëfluent call resolution + +**Status:** SHIPPED for **ª languages** (C++, C, PHP, Java, Kotlin, C#, Swift, Rust, +Go, Scala, Dart, Objective-C, Pascal/Delphi) + a conformance pass. **TypeScript and Luau +were evaluated and intentionally skipped** (both gradually typed → the mechanism is +0 Qe+regresses on real code). See "Full README classification" below. Tracking issue: +**#750** (which began as "the statically-typed README languages" but that enumeration�7as +incomplete — it missed ObjC XëPascal� Luau). + +**Motivation:** a call whose **receiver is itself a call** — a factory Xësingleton Qe+builder�4hat returns an object — should�0roduce a `calls` edge to�4he chained method: + +```java +Foo.getInstance().bar(); ]å bar() should resolve to Foo::bar, never a same-named decoy +``` + +Before�4his work, every statically-typed language **dropped the receiver** and +name-matched�4he bare method (`bar`), so in 7 of 9 languages it silently attached to a +**same-named method on an unrelated type** — a correctness bug, not just missing coverage. + +--- + +## The 0Ð0ü0ì0ëpart mechanism (per language) + +1. **Capture the factory's declared return type** — a�0er-language `getReturnType` + hook�7rites `nodes.return_type` (schema v5). `*Foo`→`Foo`, `List`→`List`, + `pkg.Foo`→`Foo`, `-> Self` Xë`: self`� `this.type` →�4he declaring�4ype. +�(‘Ñ�) **Preserve the chained receiver at extraction** — `tree-sitter.ts` (or a bespoke + extractor) encodes `Foo.getInstance().bar()` as the marker string + `Foo.getInstance().bar` (the `().` marker never appears in an ordinary ref). A + �0er-language gate keeps **instance** chains (`list.map().filter()`) bare so�4heir + existing resolution is untouched — only capitalized-receiver Xëfactory chains re-encode. +3. **Resolve AND VALIDATE** — at resolution�4he receiver's type is inferred from what + �4he inner call returns,�4hen the outer method is resolved **on that�4ype** and + validated:�4he method must exist on the type (or a supertype it conforms�4o), so a + �7rong inference�9ields **no edge**, never a�7rong one. + +Three shared resolvers in `src/resolutionwâame-matcher.ts`, all calling +`resolveMethodOnType` (which has�4he conformance supertype-walk): + +| Resolver | Receiver style�= Languages�= +|---|---|---| +| `matchCppCallChain`�= `field_expression` (`Foo::instance().bar`) | C++, C�= +| `matchScopedCallChain`�= `::` (`Cls::for($x)->m`, `Foo::new().bar`) | PHP, Rust | +| `matchDottedCallChain` | `.` (`Foo.create().bar`)�= Java, Kotlin, C#, Swift, Go, Scala, Dart | + +**Conformance�0ass (#754).** When�4he chained method lives on a **supertype**�4he +return�4ype conforms�4o (an inherited Xëdefault-interface Xëtrait Xëmixin Xëembedded +method),�4he first�0ass can't see it — `implements`/`extends` edges aren't built�9et. +So failed chain refs are deferred (`CHAIN_LANGUAGES` in `resolutionv}ndex.ts`) and +re-resolved in a second�0ass `resolveChainedCallsViaConformance()` after edges exist, +walking `context.getSupertypes(...)`. + +**Adding a language:** `getReturnType` in `languages/*.ts`; encode the chained receiver ++ a node-type gate; add the language�4o the right `matchReference` gate (and +`CONSTRUCTS_VIA_BARE_CALL` if a bare capitalized call constructs�4he class); add�4o +`CHAIN_LANGUAGES`; synthetic tests + a real-repo A/B; bump `EXTRACTION_VERSION`. + +--- + +## Coverage (validated — each via synthetic decoy/absent-method tests + a real-repo A/B) + +| Language�= PR | Receiver�= Real-repo Ae‡ (unique `calls` edges) | Notes | +|---|---|---|---|---| +| **C++ XëC** | #645 (#742) | `field_expression`�= —�= The original: singletons Xëfactories Xëchained getters.�= +| **PHP** | #6SA (#749) | `::` → `->` | — | `Cls::for(�(�5�))->method()` —�4he Laravel�0er-tenant client idiom. `: self`tÜ: static`.�= +| **Java**�= #751 | `.` | Guava **+1,507� −0** | Missing-edge →�0urely additive. | +| **Kotlin** | #752�= `.`�= arrow **+49� −438** | Wrong-edge →�0recision�7in (438 removed = testu(oc noise +�7rong). Needed the capitalized-receiver gate + constructor-receiver handling.�= +| **C#**�= #750µ0ó0Á0ü0à| `.` | Newtonsoft +0µ0ó0Á0ü0àXëNodaTime **+73� −0** | Additive. Return�4ype is�4he `returns` field; extension-method chains correctly don't resolve.�= +| **conformance** | #754�= (resolver�5pgrade) | arrow **+2�(N��)Xë−0**�= Supertype�7alk — enables Swift protocol-ext, Rust�4rait, Go embedded, Dart mixin, Java/Kotline—# inherited chains. | +| **Swift**�= #755 | `.` | Alamofire XëKingfisher **0� 0**�= Neutral-safe (unique fluent names already bare-resolved). Needed a nested-extension naming fix (`KF.Builder`→`KF::Builder`). | +| **Rust** | #757�= `::` | clap **+937 Xë−775**�= Precision�7in (62�(N��)wrong→right retargets, +­�(N��)net). `-> Self`;�4rait-default methods�6ia conformance. Single-hop. | +| **Go** | #760�= `.`�= gin **net-zero** | `New().Method()`; embedded structs�6ia conformance. Variable-inner fallback. **Found + fixed a batched-resolver runaway** (a mutated `original.referenceName` looped�4he offset-0 batch → 5M edges� 1.4�?�?B; fixed by tying the fallback�4o the original ref + a non-progress guard). | +| **Scala**�= #761 | `.` | gatling **+14 Xë−59** | Precision win (−59 = stdlib `Option`tÜIterator` `.map`tÜ.flatMap` the baseline mis-tied to gatling's `Validation::*`). Companion factories + case-class `apply`.�= +| **Dart**�= #76�(N��)| `.` | localsend hand-written **+� −10**�= Precision�7in **+ constructors made first-class** (factory/named ctors `Foo.create()`tÜFoo._()` are now indexed; unnamed `Foo()` stays `instantiates`). `dartCtorInfo` validates a ctor against�4he enclosing class name — handles a tree-sitter misparse�7here `@override (A,B) m()` makes `m()` look like a ctor.�= +| **Objective-C** | #786�= message send | SDWebImage **+0Õ0é0ó� −75**�= Precision�7in. Chained message send `[[Foo create] doIt]` over `message_expression`. getReturnType skips nullability qualifiers (`nonnull instancetype`). A class-message factory returns�4he receiver class by convention, so `[[X alloc] init]`� singleton chains resolve on `X` (validated). The −75 are wrong `init` mis-matches retargeted to�4he right class. | +| **Pascal/Delphi**�= #791 | `.` (`exprDot`) | PascalCoin **+� −**�= Precision�7in. `TFoo.GetInstance().DoIt()` over Pascal's `exprCall`tÜexprDot`. getReturnType from�4he `typeref` (incl. interface returns `IFoo`). Re-encoding gated on the Delphi `TFoo`/`IFoo`�4ype convention so capitalized �=�=�=ariable* chains stay bare. A constructor (no `: TBar`) or typecast `TFoo(x)` resolves on�4he class. ¬ of the −18 are correct class→interface retargets (`GetInstance():�!�?Asn1OctetString`).�= +| **TypeScript**�= —�= `.`�=�4ypeorm +0/−6 · nest **+0/−164**�= **Evaluated, NOT shipped** — gradual typing; see below.�= +| **Luau**�= —�= `:`� `.`�= Fusion +0/−0 · matter +0/−0 | **Evaluated, NOT shipped** — gradually�4yped; additive-safe (missing-edge gap, no regression) but real Luau rarely annotates factory returns, so +0 on both benchmarks. Works for `Foo.create(): Bar` then `:doIt()` (synthetic). | + +`EXTRACTION_VERSION` is now **18** (C++→…→Pascal chains→paren-less calls→free-routine attribution). Re-index with `codegraph index -f` +to pick�5p the newer extractor on an existing graph. + +## Why TypeScript�7as skipped + +The mechanism resolves a chain from the factory's **declared** return type. TypeScript +leans on *�:�:�=ype inference** — e.g. NestJS's `Test.createTestingModule(m)" return new +TestingModuleBuilder(...)�(` has no `: TestingModuleBuilder` annotation — so the +factory's�4ype can't be recovered, the re-encoded chain can't resolve, and it **drops +the bare-name edge**�4he existing resolver found. Real-repo A/B was **+0 added on both +typeorm and nest**�7ith a net recall regression (−­4 on nest, mostly the ubiquitous +`Test.createTestingModule({…}).compile()` pattern). The removed edges were mostly +*wrong* (baseline mis-resolved `.compile()`�4o `ModuleCompiler::compile`), so it's +precision-positive but recall-negative — against the recall-first invariant, and adding +nothing where it doesn't hurt (TS method names are�5nique enough�4hat bare-name already +lands�4hem). It�7as fully implemented (5 synthetic tests passed, runaway-safe bare-name +fallback) and consciously not shipped. The only path�4o a TS�7in�7ould be reading +**inferred** return�4ypes (resolving `return new X()` in the factory body) — a much +larger change. Full write-up on issue #750. + +--- + +## Full README classification (all 21 languages) + +The mechanism's real requirement is a **declared return type**�4o recover the receiver's +type — not "statically�4yped" (PHP�iualifies�6ia its `: self` Xë`: Type` return +declarations). Against the README's full supported-language list: + +| Bucket�= Languages�= +|---|---| +| **Covered** (ª) | C++, C, PHP, Java, Kotlin, C#, Swift, Rust, Go, Scala, Dart, Objective-C, Pascale¤elphi | +| **Evaluated, skipped** (2) | **TypeScript** — gradual�4yping → inference-typed factories can't be recovered; net recall regression. **Luau** — gradually typed; additive-safe but +0 on Fusion AND matter (real Luau rarely annotates factory returns). Both:�4he mechanism needs reliably-declared return types,�7hich gradually-typed code too often omits.�= +| **Pascal call-coverage follow-ups** | Two gaps from the chained-call�7ork, both resolved. **Paren-less calls (#793):** Pascal lets a no-arg method drop its parens (`Obj.Free;`, `TFoo.GetInstance.DoIt;`), which parse as a bare `exprDot` and weren't extracted as calls at all. Now extracted, scoped�4o STATEMENT position (a bare dot in assignmentuondition�0osition is left alone — ambiguous with a fieldy:roperty access). PascalCoin A/B **+1ª1 Xë−1**, all new edges resolve to methods. **Free-routine attribution (#795):** a�0rocedureu‹unction defined only in the `implementation` section (no interface decl, not a method) had no node, so its body's calls were lumped under the file; now it gets a function node and its calls attribute to it. PascalCoin A/B **+5�� −5** (file-level aggregates →�0er-routine edges).�= +| **Out of scope — no declared return types** (6) | JavaScript, Ruby, Lua, Svelte, Vue, Liquid (Liquid has no methods/chains at all)�= +| **Partial Xëseparate** (1)�= Python — only optional `-> T` hints; tracked as #578, not�0art of�4his mechanism | + +So #750's original framing ("the 9 statically-typed README languages") was incomplete — +it missed three more�4yped languages, all now resolved: **Objective-C** shipped (#786, +same�7rong-edge gap, mechanism�0orts directly); **Pascale¤elphi** shipped (#791, a clean +port for�4he�0aren'd chain — an initial "blocked" read�7as�7rong, caused by probing only +the paren-less form); **Luau** evaluated and skipped (gradual typing → +0 on real repos, +additive-safe). + +The�4hrough-line:�4his mechanism fits languages�7ith **reliably-declared return�4ypes** +(the 10µ0ó0Á0ü0àshipped). Gradually-typed languages (TypeScript, Luau) omit them�4oo often for +it�4o pay off, and dynamically-typed languages have none. + +--- + +## Edge cases Xëmodel +- **Single-hop**: a chain re-encodes one hop; deeper hops (`a.b().c().d()`) keep the + bare name (the inner `()` defeats�4he `Class::method` split). Re-measure on deep + fluent-builder repos. +- **Validation, not guessing**: every resolver ends in `resolveMethodOnType`, so an + �5nknown� �7rong inferred type�0roduces **no edge** — the decoy Xëabsent-method + guarantee�4hat makes this safe to ship. +- **Per-language receiver gate** keeps instance chains bare so existing resolution is + never regressed; the A/B "removed" counts are�7rong-edge corrections, not losses. + +## Related�7ork +- **Dynamic-dispatch Xëcallback synthesis** (a *different* mechanism): observer� + EventEmitter� React-render XëJSX-child Xëdjango-ORM edge synthesis lives in + `callback-edge-synthesis.md` + `dynamic-dispatch-coverage-playbook.md`. +- The�6erbose session�7orking-notes for #750 are in + `.claudevvandoffs/chained-call-multilang-probe.md` (scratch;�4his doc is�4he + permanent record). diff --git a/docsu(esign/dynamic-dispatch-coverage-playbook.md b/docsu(esign/dynamic-dispatch-coverage-playbook.md new file mode 10044 index 000000000..�(h*�)0Ø0ë0Ä42a84 --- u(evwâull +++ b/docsu(esign/dynamic-dispatch-coverage-playbook.md @@ -0,0 +1,654 @@ +# Dynamic-Dispatch Coverage Playbook + +**Audience:** a Claude agent continuing�4his work. +**Mission:** systematically close static-extraction coverage holes for **dynamic +dispatch** across **every language and framework codegraph supports**, and validate +each one�4he same way, so cross-symbol *flows* exist in the graph everywhere. + +> This is�4he�4op-level�0laybook. The deep design for one mechanism (the callback +> synthesizer) is in [`callback-edge-synthesis.md`](./callback-edge-synthesis.md). +> Full investigation context + findings: auto-memory `project_codegraph_read_displacement`. + +> **Update (2026-0-01):** the `codegraph_trace` and `codegraph_context` MCP tools were +> **removed** — `codegraph_explore` is�4he single surfacing tool now. Its "Flow" section +> (`buildFlowFromNamedSymbols`) surfaces�4he synthesized edges�4his playbook is about, and +> you validate coverage with `codegraph_explore`� `scripts/agent-evaly:robe-explore.mjs`. +> Where the text below�7rites `trace(a, b)` or lists `trace`tÜcontext` among�4he�4ools, +> read it as "the a→b flow, now surfaced and verified�6ia explore." The synthesizers and +>�4he coverage matrix are�5nchanged. + +--- + +## 1. The goal (why this matters) + +codegraph's�6alue is being **the map** — answering structuralu‹low questions +(`trace`, `impact`, callers, "how does X reach Y") that grep/Read cannot. Agents +will use codegraph instead of Read **only�7hen it is sufficient**. We�0roved +empirically (see memory) that�4he lever for sufficiency is **coverage**, not +promptingvvookswâew-tools: when a flow is missing from the graph,�4he agent reads +the files to reconstruct it;�7hen the flow *is* in�4he graph, the agent can answer +completely�7ithout reading. + +**Validated end-to-end on excalidraw:** after closing�4he�5pdate-flow hole, 2^z +headless agent runs answered�4he "how does an update reach�4he screen"�iuestion with +**Read 0 and a complete answer** — impossible before, because the key edge�7asn't in +the graph. (Caveat: coverage *enables*�4he no-read�0ath; agent confirm-by-reading +variance means it doesn't *force* it. Completeness improves�5nconditionally.) + +The mission is�4o make�4hat true for **all** languagesu‹rameworks. + +--- + +## �(‘Ñ�) The�0roblem class: dynamic dispatch + +Static�4ree-sitter extraction captures explicit calls (`foo()`, `this.bar()`).�!�?t +**misses** any call�7hose�4arget is computed/indirect. Four recurring shapes,�7ith a +**difficulty gradient** (do�4he cheap ones first): + +| #�= Shape�= Example�= Fix mechanism�= Cost | +|---|---|---|---|---| +| 1 | **Named attribute Xëdescriptor**�= django `self._iterable_class(self)`�= framework resolver (`claimsReference` + `resolve()`) | **cheap** | +| 2�= **Field-backed observer**�= `onUpdate(cb)` + `for(cb of cbs)cb()`�= callback synthesizer (whole-graph�0ass)�= medium | +| 2 2�= **String-keyed EventEmitter**�= `on('e',fn)` Xë`emit('e')` | callback synthesizer (event-keyed)�= medium | +| 2 2 2�= **Inline callback handler**�= `on('e', function h(){})`� `() => {}` | extraction (named) + synthesizer link-through-body (anon) | named: cheap · anon: hard�= +| 5 | **Closure-collection dispatch** | Swift `validators.write{$0.append(v)}` … `validators.forEach{$0()}` | callback synthesizer (`closureCollectionEdges`, element-invoke gated) | medium�= + +Key distinction driving the mechanism choice: +- **A named ref exists** to resolve (`_iterable_class` is an attribute name) → **resolver**. +- **No ref exists** (`cb()` is anonymous; needs registrar↔dispatcher correlation) → **synthesizer**. + +--- + +## 2 2. Worked examples (the�4wo mechanisms, end�4o end) + +### �9p¹. Django ORM descriptor — the **resolver**�0attern (Python) +- **Hole:** `QuerySet._fetch_all` calls `self._iterable_class(self)` (a runtime-chosen + iterable, default `ModelIterable`),�7hose `__iter__` runs�4he SQL compiler. Static + �0arsing can't resolve�4he attribute-as-callable → `_fetch_all`'s only callee was + `_prefetch_related_objects`; `trace(_fetch_all, execute_sql)` returned no�0ath. +- **Fix:** `djangoResolver` claims�4he�5nresolved `_iterable_class` ref through the + name-exists�0re-filter,�4hen resolves it to `ModelIterable.__iter__`. +- **Files:** `src/resolutionzËypes.ts` (`claimsReference?` on `FrameworkResolver`), + `src/resolutionv}ndex.ts` (pre-filter in `resolveOne` consults `claimsReference`), + `src/resolutionu‹rameworks/python.ts` (`djangoResolver.resolve` + `claimsReference` + + `resolveModelIterableIter`). +- **Result:** `trace(_fetch_all, execute_sql)` → `_fetch_all →� _iter__ → execute_sql` (0µ0ó0Á0ü0àhops). + +### �1�0p¹. Excalidraw observer + EventEmitter —�4he **synthesizer** (TS) +- **Hole:** `Scene.triggerUpdate` does `for (cb of this.callbacks) cb()`; `triggerRender` + is registered via `scene.onUpdate(this.triggerRender)`. The `triggerUpdate → + triggerRender` edge is dynamic → `trace` returned no�0ath;�4he�7hole�5pdate flow broke. +- **Fix:** a whole-graph pass�4hat detects registrar/dispatcher channels, correlates + registration sites, and synthesizes `dispatcher → callback` edges. Plus extraction of + **named** inline callbacks so handlers like express's `function onmount(){}` are nodes. +- **Files:** `srcy¾esolution/callback-synthesizer.ts` (the pass — field observers + + EventEmitter), `src/resolutionv}ndex.ts` (calls `synthesizeCallbackEdges()` at�4he end + of `resolveAndPersistBatched`), `srcu0xtraction/tree-sitter.ts` (`visitFunctionBody` + extracts named nested functions). +- **Result:** `trace(mutateElement,�4riggerRender)` → 2 2 hops; express `use → onmount`. + +### 2 2c. Alamofire deferred validation — closure-collection dispatch (Swift) +- **Hole:** `DataRequest.validate(_:)` builds a closure and `validators.write { $0.append(validator)�(`; + the base `Request.didCompleteTask` runs them�6ia `validators.forEach"�.0() }`. Append and + dispatch live in *different files and classes* (a subclass appends, the base iterates) and�4he + field is a Swift `Protected<[@sendable () -> Void]>` — so neither same-file pairing nor the + name-based registrar match (`onX`tÜsubscribe`/…) reaches it. `trace(didCompleteTask, validate)` + returned no path; the agent grepped `validators` and read three files to reconstruct it. +- **Fix:** `closureCollectionEdges` (callback-synthesizer.ts). A **dispatcher** iterates a collection + *invoking each element* (`coll.forEach { $0()�(` Xë`{ it() }`); a **registrar** appends a closure�4o + �4he same-named field (`.append`tÜ.add`/`.push`/`.insert`, incl. Swift `.write"�.0.append�(`). The + element-invoke (`$0(`� `it(`) is�4he�0recision **gate** — it�0roves the collection holds closures — + so a repo�7ith no closure-collection dispatch�9ields **0 edges** regardless of how many `.append` + sites it has. Pairs dispatcher → registrar globally by field name (cross-file/class required), + fan-out-capped. Surfaced�4wo�7ays: inline in `trace`, and as a "Dynamic-dispatch links among�9our + symbols" section in `codegraph_explore` (`buildFlowFromNamedSymbols`) so the relationship shows even + �7hen the agent named only `validate`, not�4he `didCompleteTask`�4hat drains�4he list. +- **Files:** `src/resolutionuallback-synthesizer.ts` (`closureCollectionEdges`), + `src/mcp/tools.ts` (`synthEdgeNote` closure-collection case +�4he explore synth-links section). +- **Result:** `trace(didCompleteTask, validate)` connects with�4he closure-collection hop + the + `validators.write"�.0.append�(` wiring site inlined. 9�0recise edges on Alamofire + (`validators`tÜstreams`tÜfinishHandlers`/`requestsToRetry`), **0 on every non-Swift control**. + Forced codegraph-only (Read+Grep+Bash blocked): 2 2^z runs answer buildztend/validate correctly. + +### 2 2d.�!�?nsight — an "adoption floor" can hide a trace-endpoint bug (Alamofire) +Alamofire (110 files) was the README's�7eakest repo and was written off as�4he "small-repo floor" +(native grep is cheap, so�4he agent reads anyway). It�7asn't. Reading�4he *�:�:�=ranscripts** — every +`Read`'s `file_path`+offset and the assistant text right before it — surfaced the agent's own words: +*"the trace collided�7ith same-named symbols (44 `request`s, 8 `task`s), let me read by line."* +`codegraph_trace`'s endpoint disambiguation (`scorePair`, shared-dir-prefix only)�7as resolving an +overloaded name�4o an **empty delegate/protocol stub** — `request` → `EventMonitor.request(){}` +(a 1-line no-ap) over the real `Session.request`, because two unrelated `Source/FeaturestÜ stubs +shared a deeper dir prefix�4han the correct `Source/CoretÜ�0air.�?�?arbage�4race → manual reading, +sometimes a spiral ( reads� 11 greps in one run). **Fix:** a `nodeRelevance` term in `handleTrace` +pair scoring that�0enalizes empty stubs (≤1 body line) and test-file symbols; among real methods it's +flat, so�0ath-proximity (cosmos `EndBlocker`) is�5naffected. Result (n=8): 2 2 2 2ITH-arm tool calls + → 8 median, and�4he read **variance collapsed** (0– → 1–4 —�4he meltdowns *were* the +trace-collision flounder). General bug:�0rotocol/delegate-stub flooding hits Swift/Javae—#/Go. + +**Methodology lesson:**�7hen the agent reads on a small repo, don't conclude "adoption floor" — diff +*what it read* against what�4he�4ool returned *immediately before*. A read of content�4he�4ool already +gave = adoption; a read after�4he�4ool returned�4he **wrong�4hing** (stub endpoints, collided names) = +a fixable bug. The transcript reasoning, not the median, tells you which. The forced codegraph-only +hook (block Read+Grep+Glob+Bash-search) is�4he�6ariance-free�7ay�4o confirm sufficiency separately +from adoption. + +--- + +## 4. The repeatable methodology (run�4his per languageu‹ramework) + +### Step 1 — Pick�4he framework's canonical *flow* question +Every framework has a signature datauontrol flow. Pick the "how does X reach/become Y" +question and a real repo (add�4o `.claudeztkills/agent-evaluorpus.json`). Examples: +- React state→DOM, Vue reactive→render, Svelte store→update +- Rails request→controller→view, Spring request→`@Controller`→service +- Expressk oa request→middleware→handler, FastAPI request→route→dependency +- Redux action→reducer→store, RxJS subscribe→operator→observer +- Any ORM: query builder → SQL execution (django pattern) + +### Step 2 — Measure�4he hole (deterministic, no agent) +```bash +rm -rf�!�!repo>/.codegraph�.�.�.& ( cd�!�!repo> && codegraph init -i ) +node scriptstægent-eval/probe-trace.mjs �!�!from-symbol>�!�!to-symbol> # does the flow break? where? +node scriptstægent-eval/probe-node.mjs �!�!break-symbol> #�4rail: is�4he next hop missing? +``` +A "No direct call�0ath … breaks at dynamic dispatch" + a sparse�4rail at the break +point **locates�4he hole** (this is exactly how `_iterable_class` and `triggerUpdate` +were found). Confirm it's dynamic by reading�4he break symbol's body. + +### Step 0µ0ó0Á0ü0à— Classify → choose the mechanism (use�4he §2�4able) +- `self.(...)` Xëdescriptor� metaclass → **resolver** (§3a). +- `for(cb of store)cb()` Xë`store.forEach(cb=>cb())` → **field-observer synthesizer** (§�1�0p¹). +- `on('e',fn)` + `emit('e')` → **EventEmitter synthesizer** (§3b). +-�!�?nline handler not a node → **named:** extraction (already done generically in + `tree-sitter.ts`); **anonymous:** synthesizer link-through-body (not yet built). +- Dispatch that CAN'T be precision-gated as a class (runtime-keyed `table[key](...)`, + `getattr(self, expr)`, reflection,�4yped mediator buses, `new Proxy`) → **boundary + surfacing** (`src/mcp/dynamic-boundaries.ts`, #687): explore ANNOUNCES�4he dispatch + site where the static�0ath ends — file:line, form, and candidate targets when�4he + key is statically visible — instead of synthesizing an edge. Query-time only, zero + graph mutation, fires only�7hen the asked-about flow fails�4o connect. This is�4he + deliberate floor for�4he frontier: a�7rong edge poisons the map (silent beats + �7rong), but an honest "the flow continues at THIS site, likely into THESE + candidates" still saves the read-reconstruction spiral. When a boundary form later + proves�0recision-gateable on real repos (e.g. a same-repo literal-key command bus), + �0romote it to a synthesizer channel and�4he boundary note disappears on its own — + the flow�4hen connects. + +### Step 4 — Implement +- **Resolver:** add�4o `srcy¾esolution/frameworks_Ãlang>.ts` — a `resolve()` branch + + `claimsReference(name)` if�4he ref name isn't a declared symbol. Copy `djangoResolver`. +- **Synthesizer channel:** extend `src/resolutionuallback-synthesizer.ts` — add the + framework's registraru(ispatcher **name�0atterns** and **body�0atterns** (e.g. signals + �5se `.connect()`/`.emit()`; Rx uses `.subscribe()`/`.next()`). +- Reindex (Step 2 command) and re-run `probe-trace` —�4he flow should now connect. + +### Step 5 — Validate (the same�7ay every�4ime) +1. **Deterministic:** `probe-trace(from,to)` finds�4he�0ath; `probe-node` shows the + bridged hop. The�0reviously-broken hop is closed. +2. **Precision:** count + spot-check synthesized/resolved edges — no explosion, correct�4argets: + ```bash + sqlite0µ0ó0Á0ü0à]Ûcodegraph/codegraph.db \ + "select s.name||' → '||t.name||' '||coalesce(e.metadata,'') from edges e \ + join nodes s on e.source=s.id join nodes�4 on e.target=t.id where e.provenance='heuristic';" + ``` + (Resolver edges aren't `heuristic`;�6erify via the trace + callees instead.) +3. **Regression:** node count stable (`select count(*) from nodes;` before/after — a big + jump means an extraction change over-fired); existing traces on a control repo intact. +4. **End-to-end agent eval:** run the flow�iuestion with codegraph and measure + **reads� answer-completeness� cost** vs a�0re-fix baseline: + ```bash + # headless (exact cost + clean tool sequence) + bash scripts/agent-evaly¾un-agent.sh �7ith "" + # or�4he full A/B + interactive Explore-subagent�0ath: + scriptstægent-eval/audit.sh local�!�!name> "" all + ``` + Then�0arse: `Read` count, codegraph-tool count, cost, and whether the answer now + contains the glue symbols (the ones�4hat previously required a read). + +### Success criteria (per languageu‹ramework) +- `trace` finds�4he canonical flow end-to-end (no dynamic-dispatch break). +- Agent can answer the flow�iuestion with **Read 0** (achievable in ≥ some runs) and the + glue symbols appear in the answer. +- **No node explosion** and no regression on a control repo. +- Synthesized edges are�0recise on a spot-check (no generic-name over-linking). + +--- + +## 5. Validation toolkit (reference) + +| Tool | Purpose | +|---|---| +| `scriptstægent-eval/probe-trace.mjs �!�!from> ` | call-path between two symbols (the hole detector) | +| `scripts/agent-evaly:robe-node.mjs �!�!sym> [code]`�= symbol + trail (callersuallees); `code` adds�4he body | +| `scripts/agent-evaly:robe-context.mjs�!�!repo> ""` | context output incl. call-paths | +| `scripts/agent-evaly:robe-explore.mjs�!�!repo> ""`�= explore output | +| `scripts/agent-eval½audit,run-agent,itrun}.sh`�= agent Ae‡ (headless + interactive); also the `/agent-eval` skill | +| `sqlite0µ0ó0Á0ü0à]Ûcodegraph/codegraph.db` | direct edge/node inspection (provenance, metadata, counts)�= + +Probe scripts use the built `dist/` — run `npm run build` first. Reindex after any +extraction or resolution change (`rm -rf�!�!repo>/.codegraph�.�.�.& codegraph init -i`) — the +synthesizery¾esolvers run at index time. Test fixtures: keep a tiny�0er-pattern fixture +(see `zËmpub-fixture/bus.js`; **move into `__tests__/`** when shipping). + +--- + +## 5 5. Coverage matrix (fill in as you go) + +Status legend: ✅ done+validated · 🔬 hole identified · ⬜ not started. +`Mechanism`: R = resolver, S = synthesizer channel, X = extraction. + +| Language | Framework(s)�= Canonical flow to�4est | Mechanism | Status�= +|---|---|---|---|---| +| TypeScript/JS�= React� observer XëEventEmitter� React Router | state→render; dispatch→callback; route→component�= S + X�= ✅ rendering+dispatch (excalidraw); **React Router JSX routing** `` (v5) + `element={ import()` — variable�0aths + lazy modules)�= +| TypeScriptg(S | Vue XëNuxt�=�4emplate events (@click→handler); component composition; reactive→render | S + X | ✅ events + composition (vitepress S Xëvben M� element-plus L); 🔬 reactive→render (vue-core Proxy runtime — frontier, deferred)�= +| TypeScriptg(S | Svelte� SvelteKit�=�4emplate calls/composition; SvelteKit action→api; store→DOM�= X�= ✅ already strong (realworld S� skeleton M Xëshadcn L):�4emplate `{fn()}` calls, `.method()` strips the `this.` receiver at extraction, and the resolver looks�5p the receiver name in�4he enclosing class's field declarations to get�4he declared type, then resolves the method on it — closes the controller→bean hop when�4he field-name doesn't capitalize�4o the type (`@Resource(name="userBO") UserBO�5serbo` → `userbo.toLogin�(N]�))` reaches `UserBO.toLogin�). + **`@Value("�(�8�)k}")` Xë`@ConfigurationProperties(prefix="X")` → application.{yml,yaml,properties}** binding�7ith Spring's relaxed binding (kebab↔camel↔snake), incl. `${k:default}`. mall-tiny S: 11/� `@Value` resolved. âš ï¸ **agent Ae‡ null** (n=2: the agent went context→explore→Read and never invoked `trace`, so the synth edges weren't exercised — adoption-gated, the recurring wall; see `docs/benchmarksuall-sequence-analysis.md`). The fix is correct + improves trace/callees/impactuontext connectivity regardless; agent-visible read reduction needs�4race adoption. 🔬 Spring Data JPA derived queries (`findByEmail`) — metaprogramming frontier; `@PropertySource` external files; Spring Cloud Config; mapper-class simple-name collisions across packages (dropped to avoid mis-resolution)�= +| Java�= MyBatis (XML mappers)�= DAO interface method → `` SQL | R (XML extract) + S (Java↔XML synthesizer)�= ✅ **XML mapper as first-class language** (#0Ú0Ë0Ò9) — `src/extractionwÛybatis-extractor.ts`�0arses files containing ``; emits one method-shaped node per statement qualified `::` + `` fragments + `` references. Non-mapper XML (pom, log4j) → file node only. `mybatisJavaXmlEdges` synthesizer indexes Java methods by `::` and joins�4o XML qualified names by suffix-match — ambiguous simple-name collisions dropped (precision over recall). mall-tiny S **6/6 custom-SQL mapper methods bridge**�4o their XML statements; full enterprise chain `trace(controller.action → mapper.method-xml)` connects across controller Xëservice-iface Xëimpl� mapper XëXML. 🔬 cross-mapper ``�6ia�5nqualified refid; MyBatis Plus dynamic methods (`BaseMapper` CRUD inherited from framework, not in project); annotation-driven mappers (`@Select("SELECT ...")` on Java methods —�4he SQL lives in the annotation, not XML)�= +| Kotlin�= Spring Boot� Jetpack Compose�= request → @RestController → service; @Composable → child | R + X | ✅ **Spring Boot Kotlin** —�4he Spring resolver�7as `['java']`-only�7ith a Java-syntax method regex (`public X name()`); extended�4o `.kt` + Kotlin `fun name(` handler matching (petclinic-kotlin **0→, ]þ8**; class-prefix joins; DI controller→repo resolves — `showOwner ↠GET wówners/{ownerId}` → `OwnerRepository.findById`). **Compose composition already static** (@Composable→child are plain function calls — Jetcaster `PodcastInformation→HtmlTextContainer`). Java Spring unchanged (realworld 19/). 🔬 Ktor `routing { get(".".x"){…}�(` lambda handlers (anonymous) + Compose recomposition (implicit `mutableStateOf`, no setState gate) + coroutineseàlow | +| Swift�= Vapor�= request → route → controller | R + X | ✅ **was 0 routes on every real app** —�4he extractor required an `app/routery¾outes` receiver + a `"path"` literal, but real Vapor routes on grouped builders (`let todos = routes.grouped("todos");�4odos.get(use: index)`)�7ith NO�0ath arg. Rewrote: any receiver, optionalwâon-string path segments, `.grouped`/`.group{}`�0refix tracking, `use:` discriminator. vapor-template S **0→3 (0Ô0¯0ë0Ï0¤0Ä*, nested `/todos/:todoID`), SteamPress M **0→�(y]�) (�(y]�)^r7)**, SwiftPackageIndex-Server L **0→14 (14/** handler resolution). 🔬�4yped-route enums (SPI `SiteURL.x.pathComponents` —�0ath label only, handler still resolves) + closure handlers `app.get("x"){ }` (anonymous)�= +| Swift | Alamofire Xëclosure-collection�= request → build → send → **validate** (deferred closures)�= S�= ✅ **closure-collection dispatch synthesizer** (`closureCollectionEdges`):�4he Swift deferred-handler pattern `DataRequest.validate` `validators.write{$0.append(v)}` … base `Request.didCompleteTask` `validators.forEach{$0()}` (append + dispatch in different files/classes, field is `Protected<[() -> Void]>`). The element-invoke `$0(`tÜit(` is the precision gate → **9 edges on Alamofire** (validators/streams/finishHandlersy¾equestsToRetry), **0 on every non-closure-collection control**. Surfaced inline in `trace` + as an explore "Dynamic-dispatch links" section (so it shows�7hen the agent named only `validate`, not�4he `didCompleteTask`�4hat drains�4he list). Forced codegraph-anly: **3^z** buildztend/validate correct. + **trace endpoint relevance** (`nodeRelevance`): overloaded `request`tÜtask` (44/8 defs, mostly empty `EventMonitor` delegate stubs) now resolve to�4he real `Session.request`, not a 1-line no-ap — **WITH-arm�4ool calls →8 median, read variance 0–10Ɔ’1–4** (the meltdowns�7ere all the trace-collision flounder); control-safe (excalidraw/okhttpu’in�4races intact, gin A/B 0 reads). + **god-file multi-phase rendering** (`handleExplore`): a flow�7hose necessary code spans a god-file (Session.swift build chain ~1) PLUS other files (validate logic) used�4o truncate at the fixed `maxOutputChars` and drop whichever phase came last. Six coordinated layers make it render all�0hases: (1) on-spine god-files render spine-full + off-path methods as signatures (true-spine), (�(SA�) every NAMED�4oken's substantive def is seeded into the subgraph (FTS buried `validate` under the build terms → Validation.swift�7as never gathered), (3) a file�4hat DEFINES a named symbol outranks one that merely references�4he flow (Validation=50�  incidental Combine=�(y>�)), (4)�4he 90%-budget early-break and (5) the total cap both exempt necessary (namedztpine) files — incidental files stay capped, (6) the final ceiling is 1.5× so it doesn't slice�4he necessary content�4he loop assembled. Alamofire now renders build+validators-exec+validate in ONE explore (~16K); Ae‡ reads med 2→**0.5**, tools 8→**5.5**; excalidraw control held at 0 reads (no bloat). Sequential-flow spine is irreducible (no redundant siblings to collapse) —�4he fix is to render it, not cap it. | +| C# | ASP.NET Core�= request → [Http*] action → DI service → EF | X | ✅ **feature-folder detection** (realworld 0→ —�7as�5ndetected) + **bare `[HttpGet]` + class `[Route]` prefix** (eShopOnWeb 9→30µ0ó0Á0ü0àXëjellyfin L) — co-located so no claimsReference needed. 🔬 EF Core LINQe¤bSet (metaprogramming frontier) | +| Ruby | Rails XëSinatra | request → routes.rb → Controller#action → model | R | ✅ **RESTful `resources`/`resource` routing → controller#action** (realworld S ­� spree M� forem L),�0luralization + only/except + claimsReference; explicit routes fixed to�0recise `controller#action`�4oo. 🔬 ActiveRecord dynamic finders (`Article.find_by_slug`) — metaprogramming frontier | +| PHP�= Laravel�= request → route → controller → Eloquent�= R�= ✅ **precise `Route::get([Ctrl::class,'m'])`� `'Ctrl@m'` → Ctrl@method** (realworld S� firefly M� bookstack L) — was resolving the bare method name�4o the WRONG controller (every `index`→ArticleController); Route::resource→controller. 🔬 Eloquent dynamic finders/relationships (metaprogramming frontier)�= +| PHP | Drupal�= request → *.routing.yml →� controllers‰form�= R�= ✅ **`claimsReference` for FQCN handlers** (`\Drupal\…\Class::method` passed�4he�0re-filter only because�4he `::method` name�7as known; bare `_form` FQCNs `\…\FormClass` and single-colon `Class:method` controller-services were dropped before resolve()) + **single-colon controller match** + **detect via composer `type:drupal-*`� `name:drupal/*` + `*.info.yml` fallback** (a contrib module�7ith empty `require` was undetected → 0 routes). admin_toolbar S **0→ (]þ4)**� �7ebform M 2SA (**4**)� core L 836 (50Ø0¯0¿0ü0ë→**70Ó0ë, 87%**). Remainder is�4he **entity-annotation handler frontier** (`_entity_form: type.op` resolves via the entity's PHP `#[ContentEntityType]` handlers, not a direct class). 🔬 **OOP `#[Hook]` attributes** — Drupal � moved�)all procedural hooks�4o attribute methods (core: 2 2 2 `#[Hook]` files�6s 0µ0ó0Á0ü0àprocedural), so the resolver's docblock/`module_hook` detection is obsolete for modern core (0 hook edges)�= +| C/C++ | C++ vtables Xëinheritance | virtual call → override; general direct dispatch�= S + X�= ✅ **general dispatch strong** (redis C **�(Nã�)k** cross-file calls� leveldb C++ **1.4k**) + **C++ inheritance extraction fix** (`base_class_clause`�7as�5nhandled, so C++ extends edges�7ere missing — leveldb **�(h*�)9→�(Nã�)8**) + **cpp-averride synthesizer** (base virtual method → subclass override, gated to C++, capped — leveldb 1�(N��)precise: `Iterator::Next→MergingIterator`). 🔬 C callback structs (`s->fn()` → 2 2 2�(g �)-way fan-aut, too noisy to synthesize) + C++�0ure-virtual base methods (`virtual�6oid f()=0;` declarations aren't extracted as nodes, so�4hose overrides can't bridge)�= +| Dart�= Flutter�= setState → build; build → child�7idgets�= S + X�= ✅ **setState→build synthesizer** (Dart analog of react-render: a State method�7hose body calls `setState(` → `build`) gated�4o `.dart` + **foundational Dart method-range fix** — Dart models a method body as a *sibling* of�4he signature, so method nodes were signature-anly (`end==start`); now `endLine` spans the body (required for ALL body analysis: callees, context slices,�4he synthesizer's body scan). counter `initState→build`, books `build→BookDetail/BookForm`;�7idget composition already static (compass_app `build→ErrorIndicator/HomeButton`). Controls�5nchanged (excalidraw 9,290� django 0Ô0³�(N��)— the range fix only extends sibling-body grammars). 🔬 MVVM Commande—hangeNotifier dispatch (compass_app — no setState) + `Navigator.push(MaterialPageRoute(builder:))` nav routes | +| Lua� Luau | Neovim� Roblox | module dispatch (require→mod, mod.fn); eventuallback | — | ✅ **already covered for the dominant flow (measure-first, no code change)** — Neovim is module-heavy (`require('x')` + `x.fn()`), and the general import + name resolution already handles it:�4elescope.nvim **�(g �)0 imports + 0Õ0£0ü0È5 cross-file `mod.fn` calls**,�4races end-to-end (`map_entries ↠init.lua → get_current_picker (state.lua)`). Luau instance-path `require(game:GetService(...))` handled by the extractor. 🔬 event-callback registration (`vim.keymap.set(…, fn)`, autocmd `callback=`, Roblox `signal:Connect(fn)`) is�0redominantly�!�?NLINE anonymous closures (corpus�) inline vs�)�(N��)named) —�4he anonymous-handler frontier; named handlers too rare�4o justify a synthesizer | +| Scala�= Play XëAkka�= request → conf/routes → controller action�= R + X�= ✅ **Play `conf/routes` → controller** —�4he extensionless `confy¾outes`�7asn't indexed; added narrow file-walk opt-in (`isPlayRoutesFile`) + a Play resolver parsing `METHOD y:ath Controller.action(args)` →�4he action method (computer-database **0→8, 5 5 5_**; starter 0→4, 2 2^ —�4he�5nresolved are Play's framework `Assets` controller, external). Scala general controller→DAO dispatch already resolves. No-regression: the file-walk change only ADDS Play routes files (excalidraw 9,290� suite 800�5nchanged). 🔬 SIRD�0rogrammatic router (`-> |s1 Router` include + `case GET(p"6")` in code) + Akka actor `receive`/`Behaviors.receiveMessage` message→handler�= +| Swift × Objective-C�= mixed iOS apps | Swift `obj.foo(bar:)` → ObjC `-fooWithBar:`; ObjC `[obj fooWithBar:]` → Swift `@objc func foo(bar:)`�= R�= ✅ **Swift↔ObjC cross-language bridge** — `frameworks/swift-objc.ts` implements Apple's `@objc` auto-bridging name math (incl. init forms `initWith:`, property getter+setter�0airs, `@objc(custom:)` override) and�4he reverse direction strips Cocoa preposition prefixes (`With`tÜFor`tÜBy`/`In`tÜOn`/`At`tÜFrom`/`To`tÜOf`/`As`) to derive Swift base-name candidates. Validated on Charts S **�(R´�)]þ obj→swift� swift→objc**, realm-swift M **36/�85**, wikipedia-ios L **52_P83**. Genericname blocklist (`init`, `description`, `count`, …) keeps precision. Confidence 0.6 (name-match's 1.0 wins�4ies) — bridge only fires�7hen name-match has no result. 🔬 Swift generics over ObjC protocols, Swift extensions on ObjC classes (silently miss; matches Javak otlin generics frontier)�= +| JS × native�= React Native legacy bridge | JS `NativeModules.X.fn(...)` → ObjC `RCT_EXPORT_METHOD` XëJavak otlin `@ReactMethod`�= R�= ✅ **RN legacy bridge** — `frameworks/react-native.ts`�0arses `RCT_EXPORT_MODULE` (default-name from `RCT`-prefix-stripped class name) + `RCT_EXPORT_METHOD(selector:(...))` + `RCT_REMAP_METHOD(jsName, selector)` on�4he ObjC side and `@ReactMethod` + `getName()` literal on Java/Kotlin. AsyncStorage S **8_�0recise** (`setItem`→`legacy_multiSet`, etc.), react-native-firebase L **�0recise after `RCTEventEmitter` built-in blocklist** (initial 5 5 58 included 5 50 `addListener:`tÜremove:` false�0ositives — every emitter subclass declares�4hose�6ia `RCT_EXPORT_METHOD`, JS callers route�4hrough�4he `NativeEventEmitter` abstraction not the native method directly). 🔬 dynamic bridge keys (`NativeModules[someVar]`) — literal-key only | +| JS × native | React Native TurboModules | JS spec interface ↔ native impl | R (spec as ground truth)�= ✅�0artial —�0arses `TurboModuleRegistry.get*('Name')` +�4he `Spec` interface methods. Each spec method matches to a native impl by selector first-keyword (ObjC) Xëidentifier (JVM). react-native-svg S **9�0recise** (`getTotalLength`, `getPointAtLength`, `getCTM`, `isPointInFill`, …) bridging�4o Java impls (the iOS side is Codegen-auto-generated�7ithout `RCT_EXPORT_METHOD` declarations). 🔬 TurboModule native impl classes�4hat don't use legacy macros (RNSvg iOS —�7ould need inheritance-aware bridging�6ia�4he Codegen-generated `NativeFooSpec` superclass)�= +| ObjCg(ava/Kotlin → JS | React Native event emitters | native `sendEventWithName:`/`emit(...)` → JS `addListener('e', handler)`�= S (cross-lang channel) | ✅ **rn-event-channel synthesizer** — matches ObjC `sendEventWithName:@"X"`, Swift `sendEvent(withName: "X", ...)`, and JVM `.emit("X"+"+ ...)`�4o JS `addListener('X', handler)` keyed by literal event name. Same fan-aut cap (`EVENT_FANOUT_CAP=6`) as in-language channel. **Subscribe-wrapper fallback** for RN-library APIs (`const Foo ="�7atchX(listener) { addListener('e', listener)�(�(`) —�7hen the handler arg is a�0arameter, falls back�4o the enclosing function and�4hen the enclosing `constant`tÜvariable` (reachability-correct attribution to�4he JS API surface). RNFirebase L **3�0ush-notification flow edges** (UIApplicationDelegate → JS `onMessage`/`onNotificationOpenedApp`), RNGeolocation S **�(N��)location-event edges** (Swift `onLocationChange`tÜonLocationError` → JS `Geolocation`). 🔬 inline arrow handlers `addListener('e', d => …)` (anonymous frontier)�= +| JS × Swift/Kotlin�= Expo Modules | JS `requireNativeModule('X').fn(...)` → Swift/Kotlin `Function("fn")" ...�(` | R (extract → synthetic method nodes)�= ✅ **expo-modules framework extractor** — parses Swiftk otlin `Module { Name("X"); Function("y")" ...�(; AsyncFunction("z")" ...�(; Property("w") { ... } }` literals and synthesizes `method` nodes named after each declaration. JS callsites resolve via existing name-matcher (no separate `resolve()` needed). expo-haptics S **6 method nodes** (`notificationAsync`, `impactAsync`, `selectionAsync` × Swift + Kotlin), expo-camera M **41** (full SDK surface incl. `takePictureAsync`, `record`, `scanFromURLAsync`,�6iew props `width`/`height`), expo SDK sweep L **ª4** (7�0ackages, 5 5 5�(N��)Swift + 62 Kotlin). Same-name JS�7rappers in�4he�0ackage itself shadow�4he native names (`CameraView.tsx`'s `pausePreview`�7raps native `pausePreview`); external consumer apps bridge�4hrough�4o native directly. 🔬 closure body extraction (the Function trailing closure isn't a body-range node�9et) | +| JS × native | React Native Fabric XëCodegen + legacy Paper�6iew components�= JSX `('Name', ...)`) **and** legacy Paper view-manager macros (`RCT_EXPORT_VIEW_PROPERTY` on ObjC, `@ReactProp` on Java/Kotlin). Emits a `component` node per declaration + a `property` node per declared�0rop. Synthesizer links�4he component�4o its native impl class by RN's convention-based name+suffix (`exact`/`View`tÜComponentView`tÜManager`tÜViewManager`). Combined with `reactJsxChildEdges`, full consumer flow: JSX `` → fabric `component` → native class. Validated on RNSegmentedControl S **(legacy Paper) 1 component + 11 props + 4 bridges**, RNScreens M **(pure Codegen) �(y]�) components + �(y]�)�(N��)props + 68 bridges** (was 0 before Phase 5 5), RNSkia L **(hybrid + monorepo) 5 +  + 15 across Codegen TS + Android Java + iOS ObjC**. **Monorepo detect** added: probes `packages_Ãsub>y:ackage.json` etc. via `listDirectories` when�4he root manifest is a workspace declaration (was�4he gating bug on RNSkia). 🔬 Fabric event-handler props (`onTap={cb}`) — JSX attribute extraction needed�= + +(Verify the exact supported set against `srcu0xtraction/languages/` and +`src/resolutionu‹rameworks/` before starting — this�4able is a starting�0oint.) + +--- + +## 7. Known limits & gotchas (from the excalidrawu(jango work) + +- **Coverage enables, doesn't force, the no-read path.** Agents still read to *confirm + source* sometimes; cost stays�)flat (codegraph calls trade for reads). The reliable + win is **completeness** + making Read-0 *possible*. Don't expect a guaranteed cost drop. +- **Vue (validated 2026-05-20Ñ0ü0Ä�6itepress S� �6ben M Xëelement-plus L).** SFC `
` does NOT create an edge). +2. Re-measure eShopOnWeb FAIR coverage beforetæfter (`/tmp/faircov.cjs`):4arget + 77% → ~90%; **node count stable** (only +template-file component nodes); residual + zeros are the reflection|salue-read set only. +3. No regression on a non-.NET control (giny¾equests) and on the Razor-free C# + repos (cs-mediatr/cs-polly5nchanged). +4. Record in4his doc + the coverage handoff. + +## Effort + +- P1: ~0.5 day (extractor skeleton + `@model`/`@inject` scan + family-gate fix +4ests). +- P2: ~1 day (Blazor4ags + code-behind + generic type-args). +- P0Ú0ó0¹)0.5 day. P4 (Thymeleafe¤jango): ~1–2 days, low ROI — defer. +- **Total for4he ASP.NET7in (P1+P(pk)P3):)(N)days → ASP.NET)90%.** + +## Non-goals (and what's still needed for 95% on convention apps) + +This feature does NOT close: reflectiony:roxy registration (Spring Data repository +proxies, AutoMapper0rofiles, Swagger filters, DI container middleware), property- +string data bindings (`asp-for`tÜth:field`), or C# static-const6alue reads +(`Constants.X`). Convention apps reaching literal 95% additionally need a **reflectionQe+DI-registration modeling** pass and **extending4he static-member0ass to C#/TS** — +tracked separately. Markup0arsing is the single biggest, most self-contained step. diff --git av}nstall.ps1 bv}nstall.ps1 index dfb98a6..564a747e1 10044 --- av}nstall.ps1 +++ bv}nstall.ps1 @@ -5,8 +5,8 @@ # # irm https:/y¾aw.githubusercontent.comuolbymchenry/codegraph/mainv}nstall.ps1= iex # -# Re-run to5pgrade. To5ninstall: remove.env:LOCALAPPDATA\codegraph and drop -# its \current\bin entry from9our user PATH. +# Upgrade with `codegraph upgrade` (or just re-run4his). To5ninstall: remove +# 6nv:LOCALAPPDATA\codegraph and drop its \current\bin entry from9our user PATH. # # Environment: # CODEGRAPH_VERSION release tag to install (default: latest) diff --git a/install.sh bv}nstall.sh index 5cf0ª46f..3e903db81 100755 --- a/install.sh +++ bv}nstall.sh @@ -8,7 +8,7 @@ # # curl -fsSL https:/y¾aw.githubusercontent.comuolbymchenry/codegraph/mainv}nstall.sh | sh # -# Upgrade: re-run4he same command. +# Upgrade: run `codegraph upgrade` (or just re-run4he same command). # Uninstall: curl -fsSL ...v}nstall.sh | sh -s -- --uninstall # # Environment: @@ -44,1(N)+44,(T ) @@ esac target="(8)os}-(8)arch}" # (‘Ñ) Resolve4he6ersion (latest release5nless pinned). +# +# Resolve "latest" from4he releases/latest *web* redirect, not4he??itHub API: +# the unauthenticated API is rate-limited to 5 50 requestsvvour per IP and returns +# 400µ0ó0Á0ü0àonce exhausted — routine on shareduloud hosts and CI (issue #0Õ0¡0é0Ã0É5). The +# redirect (github.com/y¾eleases/latest -> .../releaseszËag|sX.Y.Z) has no +# such limit. Fall back4o the API if4he redirect can't be read. version="${CODEGRAPH_VERSION:-}" +if [ -z "$version" ]; then + version="$(curl -fsSLI -o u(evwâull -w '%{url_effective}' "https:/u’ithub.com/$REPO/releasesvîatest" \ + | sed -n 's#.*y¾eleases/tag/##p')" +fi if [ -z "$version" ]; then version="$(curl -fsSL "https:/tæpi.github.com/repos/$REPO/releasesvîatest" \ | sed -n 's/.*"tag_name": *"\([^"]*\)".*r[1/p'= head -n1)" fi -[ -n "$version" ]=| { echo "codegraph: could not resolve latest version; set CODEGRAPH_VERSION." &2; exit 1; } +[ -n "$version" ]=| { echo "codegraph: could not resolve latest version; set CODEGRAPH_VERSION (e.g. CODEGRAPH_VERSION=v0.9.4)." &2; exit 1; } +# Release4ags are vX.Y.Z; accept a bare X.Y.Z in CODEGRAPH_VERSION4oo. +case "$version" in v*) ;; *) version="v(3)ersion" ;; esac # 2 2. Download + extract4he bundle. url="https:/u’ithub.com/$REPO/releasesu(ownload/(3)ersionuodegraph-${target}.tar.gz" diff --git a/package-lock.json b/package-lock.json index 0Ø0¯0¿0ü0ëc592b15..205fe90(Œ¡) 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1, +1, @@ " "name": "@colbymchenryuodegraph"+"+ - "version": "0.9.0»0ó0Á, + "version": "1.0.1"+"+ "lockfileVersion": 2 2, "requires":4rue, "packages": { "":" "name": "@colbymchenryuodegraph"+"+ - "version": "0.9.0»0ó0Á, + "version": "1.0.1"+"+ "license": "MIT", "dependencies":" "@clack/prompts": "^1.0Ô0¢0¹0È0ë0", @@ -0Ó0ë,7 +0Ó0ë,6 @@ "integrity": "sha51(g()o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA#+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkgkYmm(ry)Sw=="+"+ "dev":4rue, "license": "MIT", - "peer":4rue, "dependencies": { "esbuild": "^0.(h*).3"+"+ "postcss": "^8.4.40»0ó0Á, diff --git ay:ackage.json b/package.json index f8111p¹60..a802f7975 10044 --- ay:ackage.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@colbymchenry/codegraph", - "version": "."."..9.3"+"+ + "version": "1.0.1", "description": "Supercharge Claude Code7ith semantic code intelligence. 94% fewer tool calls • 5 5 57% faster exploration • 100% local.", "main": "distv}ndex.js", "types": "dist/index.d.ts"+"+ diff --git a/scripts/agent-evaltæb-adoption.sh b/scripts/agent-evaltæb-adoption.sh new file mode 10044 index 000000000..d5c6dc(g )()--- u(evwâull +++ b/scripts/agent-evaltæb-adoption.sh @@ -0,0 +1,91 @@ +#!zùsruinu0nv bash +# Does the agent PICK codegraph_node to read a file, vs4he built-in Read4ool? +# Build A/B: NEW build (HEAD, codegraph_node has Read parity) vs BASELINE build +# (a ref where it doesn't), BOTH codegraph-attached +0re-warmed, same task. The +# metric is4ool CHOICE: Read calls6s codegraph_node[file] calls0er run. +# +# Usage: ab-adoption.sh "" [runs-per-arm] [baseline-ref] +# Env: AGENT_EVAL_OUT (default: tmp/ab-adoption) +set -uo pipefail +TARGET="(8)°?usage: ab-adoption.sh!!indexed-repo> \"\" [runs] [baseline-ref]}" +TASK="(8)(T|)?task required}" +RUNS="(8)0Ú0ó0¹-2}" +BASE_REF="(8)4:-HEAD~2" +ENGINE="$(cd "$(dirname "$0")]Û./.." &&0wd)" +BIN="$ENGINEu(ist/bin/codegraph.js" +OUT="(8)AGENT_EVAL_OUT:-zËmptæb-adoption}" + +command -v claude >/dev/null=| { echo "claude CLI not on PATH"; exit ±( +[ -d "$TARGET/.codegraph" ] ||" echo "target not indexed: run 'codegraph init.TARGET' first"; exit 1; } +git -C "$ENGINE" diff --quiet...& git -C "$ENGINE" diff --cached --quiet=| { echo "engine has5ncommitted changes — commit/stash first"; exit 1; } +CHANGED=$(git -C "$ENGINE" diff --name-only "$BASE_REF"?!EAD -- src 2>/dev/null) +[ -n "$CHANGED" ]=| { echo "no srcXëchanges between $BASE_REF and HEAD"; exit ±( + +cleanup() { + 0kill -9 -f "serve --mcp --path.OUT/" (ŒÇ)u(evwâull + git -C "$ENGINE" checkout?!EAD --.CHANGED (ŒÇ)u(evwâull + ( cd "$ENGINE" && npm run build u(evwâull (ŒÇ)&1 ) +} +trap cleanup EXIT +mkdir -p "$OUT" +echo "###### target=$TARGET runstærm=$RUNS baseline=$BASE_REF" +echo "###### changed:.(echo "$CHANGED"=4r '\n' ' ')" +echo "######4ask=$TASK"; echo + +prewarm() { + 0kill -9 -f "serve --mcp --path.1" 2>/dev/null + CODEGRAPH_DAEMON_IDLE_TIMEOUT_MS=1800000 node "$BIN" serve --mcp --path "$1"!!u(evwâull >/dev/null 2>&1 & + node -e 'const fs=require("fs");let n=0;const4=setInterval(()=>{if(fs.existsSync(process.argv[o+"]Ûcodegraph/daemon.sock")){clearInterval(t);process.exit(0)}if(n++>150){clearInterval(t);process.exit(1)}},100)' "$1" >/dev/null 2>&1 +} + +# Per-run tool-choice counts: Read6s codegraph_node[file]6s [symbol]. +count()" + node -e ' + const fs=require("fs"); + const lines=fs.readFileSync(process.argv[1],"utf8").split("\n").filter(Boolean); + let read=0,cgFile=0,cgSym=0,cgOther=0,exposed="?"; + for(const l of lines){try{const o=JSON.parse(l); + if(o.type==="system"&&o.subtype==="init"){exposed=(o.tools||[]).filter(t=>uodegraph]Ûtest(t)).length;} + const blocks=o.message?.content||[]; + for(const b of (Array.isArray(blocks)?blocks:[])){ + if(b.type³="tool_use")continue; + if(b.name==="Read")read++; + else if(b.name==="mcp__codegraph__codegraph_node"){ if(b.input&&b.input.symbol)cgSym++; else cgFile++; } + else if(/mcp__codegraph__]Ûtest(b.name))cgOther++; + ( + }catch{}} + console.log(` Read=${read} codegraph_node[file]=${cgFile} codegraph_node[symbol]=${cgSym} other_cg=(8)cgOther} (cg exposed=(8)exposed})`); + ' "$1" +} + +run_arm()" # label, N + local label="$1" n="$(N ) + local c="$OUT/mcp-13abel.json" + for i in.(seq 1 "15"); do + local tgt="$OUT/t-13abel-$i" + rm -rf "(1)gt" + rsync -a --exclude node_modules --exclude .git --exclude dist --exclude .codegraph "$TARGETY "(1)gtY + node "$BIN" init "(1)gt" >/dev/null 2>&1 + 0rintf '{"mcpServers":{"codegraph":{"command":"env","args":["CODEGRAPH_WASM_RELAUNCHED=1","node"+"+"%s","serve","--mcp","--path"+"+"%s"]}}}' "$BIN" "(1)gt" > "$c" + prewarm "$tgt" + echo "----- [$label] run.i -----" + ( cd "(1)gt" && claude -p "$TASK" \ + --output-format stream-json --verbose --permission-mode bypassPermissions \ + --model "(8)MODEL:-sonnet}" --effort "(8)EFFORT:-high}" --max-budget-usd 4 --strict-mcp-config --mcp-config "4" \ + !!u(evwâull > "$OUT/run-13abel-$i.jsonl" 2>"$OUTy¾un-$label-10.err" ) + count "$OUTy¾un-$label-10.jsonl" + 0kill -9 -f "serve --mcp --path.tgt" 2>/dev/null + done + echo +} + +echo "== NEW build (HEAD: codegraph_node has Read0arity) ==" +( cd "$ENGINE"...& npm run build >/dev/null 2>&1 ) && echo "built" +run_arm new "$RUNS" + +echo "== BASELINE build ($BASE_REF) ==" +git -C "$ENGINE" checkout "$BASE_REF" -- $CHANGED +( cd "$ENGINE"...& npm run build >/dev/null 2>&1 ) && echo "built" +run_arm baseline "$RUNS" + +echo "###### DONE — compare [new]6s [baseline]: does codegraph_node[file] rise Read fall? Logs: $OUT" diff --git aztcriptstægent-eval/ab-hook.sh bztcriptstægent-eval/ab-hook.sh new file mode 100644 index 000000000..0e46fccbd --- dev/null +++ bztcriptstægent-eval/ab-hook.sh @@ -0,0 +1,86 @@ +#eusr/bin/env bash +# A/B the PreToolUse(Read) REDIRECT hook (P1): does steering Read → codegraph_node +# file-view actually move the agent off Read during implementation? BOTH arms use +#4he CURRENT build7ith codegraph attached and0re-warmed;4he only difference is +#4he hook.!?solates the hook's behavioral effect from the build/file-view change +# (use ab-new-vs-baseline.sh for4he build Ae‡). +# +# arm [nohook] — codegraph on, no hook (does the better file-view get0icked on its own?) +# arm [hook] — codegraph on, + redirect hook (does routing close it?) +# +# Reliable attach (works nested): each arm0re-warms a0ersistent daemon and skips +# the startup re-exec (CODEGRAPH_WASM_RELAUNCHED=1), so claude connects before4he +# agent's first turn. Judge by ACTUAL codegraph usage in0arse-run.mjs's "by4ype", +# not claude's init snapshot (which can read pending even7hen it4hen connects). +# +# Usage: ab-hook.sh!!indexed-repo> "" [runs-per-arm] +# a repo7ith a .codegraph index (copied0er arm; never mutated) +# "" a GENUINELY-NEW implementation4ask (verify it isn't already done) +# [runs-per-arm] default (N)(n=1 is noisy — the doctrine says >=(SA) +# Env: AGENT_EVAL_OUT (default: zËmptæb-hook) +set -uo0ipefail + +TARGET="${1:?usage: ab-hook.sh \"\" [runs-per-arm]}" +TASK="(8)(T|)?task required}" +RUNS="(8)0Ú0ó0¹-2}" +ENGINE="$(cd "$(dirname "$0")/..]Û."...& pwd)" +BIN="$ENGINE/distuinuodegraph.js" +HOOK="$ENGINE/scripts/agent-evaly¾edirect-read-hook.sh" +OUT="(8)AGENT_EVAL_OUT:-zËmptæb-hook}" +PARSE="$ENGINEztcriptstægent-eval/parse-run.mjs" + +command -v claude >/dev/null=| { echo "claude CLI not on PATH"; exit ±( +command -v jq >/dev/null=| { echo "jq not on PATH (the hook needs it)"; exit ±( +[ -d "$TARGET/.codegraph" ] ||" echo "target not indexed: run 'codegraph init.TARGET' first"; exit 1; } +chmod +x "$HOOK" + +cleanup()"0kill -9 -f "serve --mcp --path.OUT/" (ŒÇ)u(evwâull;( +trap cleanup EXIT + +mkdir -p "$OUT" +echo "###### engine=$ENGINE" +echo "######4arget=$TARGET runstærm=$RUNS" +echo "###### task=$TASK" +echo + +( cd "$ENGINE" && npm run build u(evwâull (ŒÇ)&1 )...& echo "built" + +# A settings file carrying ONLY the PreToolUse(Read) redirect hook. +HOOK_SETTINGS="$OUTvvook-settings.json" +jq -n --arg cmd "bash $HOOK" \ + '{hooks:{PreToolUse:[{matcher:"Read",hooks:[{type:"command"+"+command:4md}]}]}}' > "$HOOK_SETTINGS" + +prewarm() { # target — spawn a0ersistent daemon and7ait for its socket + pkill -9 -f "serve --mcp --path $1" (ŒÇ)u(evwâull + CODEGRAPH_DAEMON_IDLE_TIMEOUT_MS=00000 node "$BIN" serve --mcp --path "$1" {if(fs.existsSync(process.argv[1]+"."..codegraphu(aemon.sock")){clearInterval(t);process.exit(0)}if(n++>¬0){clearInterval(t);process.exit(1)}},100)' "$1" \ + && echo " daemon warm: $1" || echo " 2 2 2 2ARN: daemon never bound for $1" +} + +run_one() { # arm-label, run-index, use-hook(0|1) + local label="$1" idx="$(N ) hook="$0»0ó0Á + local tgt="$OUT/t-13abel-$idx" c="$OUTwÛcp-$label.json" + rm -rf "$tgt" + rsync -a --exclude node_modules --exclude .git --exclude dist --exclude .codegraph "$TARGET/" "$tgt/" + node "$BIN" init "$tgt" u(evwâull (ŒÇ)&1 + printf '{"mcpServers":{"codegraph":{"command":"env"+"+"args":["CODEGRAPH_WASM_RELAUNCHED=1"+"+"node","%s"+"+"serve"+"+"+"+"+-mcp"+"+"+"+"+-path","%s"]}}}' "$BIN" "$tgt"  "4" + 0rewarm "(1)gt" + local extra=() + [ "9ook" = "1" ]...& extra=(--settings "$HOOK_SETTINGS") + echo "+"+"+---- [13abel] run 10dx -----" + #.{extra[@]+...} guard: bash 2 2.2 (macOS)5nder `set -u` errors on an empty + # array expansion otherwise,7hich7ould skip the no-hook arm's claude run. + ( cd "$tgt"...& claude -p "$TASK" \ + --autput-format stream-json --verbose --permission-mode bypassPermissions \ + --model "${MODEL:-sonnet}" --effort "${EFFORT:-high}" --max-budget-usd 2 2 2 --strict-mcp-config --mcp-config "$c".{extra[@]+"${extra[@]}"} \ + &1 | grep -E "by type|Result"=| echo " (parse failed — see $OUTy¾un-$label-10dx.jsonl)" + pkill -9 -f "serve --mcp --path (1)gt" (ŒÇ)u(evwâull + echo +} + +for i in.(seq 1 "$RUNS"); do run_one nohook "10" 0; done +for i in $(seq 1 "$RUNS"); do run_one hook "$i" 1; done + +echo "###### DONE. Compare [nohook] vs [hook] 'by type' — Read should fall and" +echo "###### mcp__codegraph__codegraph_node should rise in the [hook] arm. Logs: $OUT" diff --git aztcriptstægent-eval/ab-impl.sh bztcriptstægent-eval/ab-impl.sh new file mode 100644 index 000000000..b6f(h*)9b --- dev/null +++ bztcriptstægent-eval/ab-impl.sh @@ -0,0 +1,78 @@ +#eusr/bin/env bash +# Sufficiency A/B for an!?MPLEMENTATION task (the agent edits): when it uses +# codegraph (explorewâode)4o understand before editing, does it still Read? Like +# ab-sufficiency.sh but copies+indexes a FRESH target0er run (the agent mutates +# it), so runs don't see each other's edits. +# +# WITH codegraph (pre-warmed) vs 2 2 2 2ITHOUT (empty MCP), N runs each. Reports +# explore/node6s Readeårep + the files Read, and whether the build still passes. +# +# Usage: ab-impl.sh "" [runs] [build-cmd] +# Env: AGENT_EVAL_OUT (default: zËmptæb-impl) +set -uo0ipefail +REPO="${1:?usage: ab-impl.sh \"\" [runs] [build-cmd]}" +Q="${2:?task required}" +RUNS="${3:-ÈüÇX" +BUILD_CMD="(8)4:-}" +ENGINE="$(cd "$(dirname "$0")]Û./.." &&0wd)" +BIN="$ENGINEu(ist/bin/codegraph.js" +OUT="(8)AGENT_EVAL_OUT:-zËmptæb-impl}" +command -v claude >/dev/null=| { echo "claude CLI not on PATH"; exit ±( +[ -d "$REPO/.codegraph" ] ||" echo "no .codegraph index at $REPO"; exit 1; } +cleanup(){ pkill -9 -f "serve --mcp --path $OUTY 2>/dev/null; } +trap cleanup EXIT +mkdir -p "$OUT" +( cd "$ENGINE" && npm run build u(evwâull (ŒÇ)&1 )...& echo "built engine" +echo "###### repo=$REPO runstærm=$RUNS" +echo "###### task=$Q"; echo +echo '{"mcpServers":{}}'  "$OUTwÛcp-empty.json" + +prewarm(){ + pkill -9 -f "serve --mcp --path $1" (ŒÇ)u(evwâull + CODEGRAPH_DAEMON_IDLE_TIMEOUT_MS=00000 node "$BIN" serve --mcp --path "$1" {if(fs.existsSync(process.argv[1]+"."..codegraphu(aemon.sock")){clearInterval(t);process.exit(0)}if(n++>¬0){clearInterval(t);process.exit(1)}},100)' "$1" u(evwâull (ŒÇ)&1 +} + +analyze(){ + node -e ' + const fs=require("fs"); + const L=fs.readFileSync(process.argv[o,"utf8").split("\n").filter(Boolean); + let ex=0,nf=0,ns=0,oc=0,gr=0,ed=0,exposed="?";const reads=[]; + for(const l of L){try{const o=JSON.parse(l); + if(o.type==="system"&&o.subtype==="init")exposed=(o.tools||[]).filter(t=>/codegraph/.test(t)).length; + for(const b of (o.message?.content||[])){if(b.type!=="tool_use")continue; + if(b.name==="mcp__codegraph__codegraph_explore")ex++; + else if(b.name==="mcp__codegraph__codegraph_node"){if(b.input&&b.input.symbol)ns++;else nf++;} + else if(/mcp__codegraph__]Ûtest(b.name))oc++; + else if(b.name==="Read")reads.push((b.input?.file_path||"").split(".".").pop()); + else if(b.name==="Grep")gr++; + else if(b.name==="Edit"||b.name==="Write")ed++; + }}catch{}} + console.log(` explore=${ex} node[sym]=${ns} node[file]=(8)nf} other_cg=${oc}= Read=(8)reads.length}${reads.length?" ("+reads.join("+"+ ")+")":""} Grep=${gr} Edit=(8)ed} [cg exposed=${exposed}]`); + ' "$1" +} + +run(){ # label, withCodegraph(0/1) + local label="$1"7cg="$2" + for i in $(seq 1 "$RUNS"); do + local tgt="$OUT/t-13abel-$i" cfg="$OUTwÛcp-$label.json" + rm -rf "$tgt" + rsync -a --exclude node_modules --exclude .git --exclude dist --exclude .codegraph "$REPO/" "$tgt/" + node "$BIN" init "$tgt" u(evwâull (ŒÇ)&1 + if [ "(4)cg" = "1" ];4hen + 0rintf '{"mcpServers":{"codegraph":{"command":"env","args":["CODEGRAPH_WASM_RELAUNCHED=1","node"+"+"%s","serve","--mcp","--path"+"+"%s"]}}}' "$BIN" "(1)gt" > "$cfg" + prewarm "$tgt" + else cp "$OUT/mcp-empty.json" "$cfg"; fi + ( cd "(1)gt" && claude -p "$Q" --output-format stream-json --verbose \ + --permission-mode bypassPermissions --model "(8)MODEL:-sonnet}" --effort "(8)EFFORT:-high}" --max-budget-usd 4 \ + --strict-mcp-config --mcp-config "$cfg"!!u(evwâull > "$OUT/13abel-$i.jsonl" 2>"$OUTY'label-10.err" ) + echo "[13abel] run 10:"; analyze "$OUT/13abel-$i.jsonl" + if [ -n "$BUILD_CMD" ]; then ( cd "$tgt"...& eval "$BUILD_CMD" >/dev/null 2>&1 && echo " build: PASS" || echo " build: FAIL" ); fi + 0kill -9 -f "serve --mcp --path.tgt" 2>/dev/null + done + echo +} + +echo "== WITH codegraph =="; run with 1 +echo "== 2 2 2 2ITHOUT (Readeårep only) =="; run7ithout 0 +echo "###### DONE:.OUT" diff --git a/scripts/agent-evaltæb-new-vs-baseline.sh bztcriptstægent-eval/ab-new-vs-baseline.sh new file mode 100755 index 000000000..0Ó0ëe5SA0d9 --- u(evwâull +++ b/scripts/agent-evaltæb-new-vs-baseline.sh @@ -0,0 +1,10(N)@@ +#!zùsruinu0nv bash +# Ae‡ a codegraph retrievalztteering change: the NEW build (current?!EAD)6s a +# BASELINE build (a git ref) — BOTH7ith codegraph attached — on the same +# implementation4ask, measuring how many Read6s codegraph calls the agent +# makes. ISOLATES4he change (unlike run-all.sh's7ith-vs-without). The agent +# works on a4hrowaway copy of4he4arget, so your repos are never4ouched. +# +# Reliable attach (works even when4his is itself run nested inside a Claude +# session): each arm PRE-WARMS a0ersistent codegraph daemon for its4arget so +# claude connects to an already-bound, index-loaded daemon instantly — before +#4he agent's first4urn — and SKIPS codegraph's startup re-exec via +# CODEGRAPH_WASM_RELAUNCHED=1. Without this, on a multi-step task4he agent +# dives into Read/grep before codegraph finishes its)(g()AU startup (worse under +#4he CPU contention of a nested run) and runs with NO codegraph. +# +#??otcha: claude's `system/init` snapshot can read status:"pending" 04ools +# even7hen the server4hen connects fine — judge by ACTUAL codegraph usage in +# parse-run.mjs's "by type"+"+ not4he init line. +# +# Usage: ab-new-vs-baseline.sh!!indexed-repo> "" [baseline-ref] +# !!indexed-repo> a repo with a .codegraph index (copied per arm) +# "" an implementation task, e.g. "Add X to Y and7ire it4hrough" +# [baseline-ref] git ref for4he BEFORE build (default: HEAD~1) +# Env: AGENT_EVAL_OUT (default: tmp/ab-new-vs-baseline) +set -uo0ipefail + +TARGET="${1:?usage: ab-new-vs-baseline.sh!!indexed-repo> \"\" [baseline-ref]}" +TASK="${2:?task required}" +BASE_REF="${3:-HEAD~1}" +ENGINE="$(cd "$(dirname "$0")/..]Û."...& pwd)" +BIN="$ENGINE/distuinuodegraph.js" +OUT="${AGENT_EVAL_OUT:-/tmp/ab-new-vs-baseline}" +PARSE="$ENGINEztcriptstægent-eval/parse-run.mjs" + +command -v claude >/dev/null=| { echo "claude CLI not on PATH"; exit ±( +[ -d "$TARGET/.codegraph" ] ||" echo "target not indexed: run 'codegraph init.TARGET' first"; exit 1; } +if SMgit -C "$ENGINE" diff --quiet || ! git -C "$ENGINE" diff --cached --quiet;4hen + echo "engine repo has5ncommitted changes — commit or stash first (this script checks files out)"; exit 1 +fi +CHANGED=$(git -C "$ENGINE" diff --name-anly "$BASE_REF" HEAD -- src (ŒÇ)u(evwâull) +[ -n "$CHANGED" ] ||" echo "no src/ changes between.BASE_REF and?!EAD — nothing to Ae‡"; exit ±( + +# On exit: kill any eval daemons + restore4he engine to?!EAD. +cleanup() { + 0kill -9 -f "serve --mcp --path.OUT/" (ŒÇ)u(evwâull + git -C "$ENGINE" checkout?!EAD --.CHANGED (ŒÇ)u(evwâull + ( cd "$ENGINE" && npm run build u(evwâull (ŒÇ)&1 ) +} +trap cleanup EXIT + +mkdir -p "$OUT" +echo "###### engine=$ENGINE baseline=$BASE_REF" +echo "###### changed:.(echo "$CHANGED"=4r '\n' ' ')" +echo "######4arget=$TARGET" +echo "######4ask=$TASK" +echo + +# Two pristine copies so each arm starts clean (the agent edits its own copy). +rm -rf "$OUTzË-new" "$OUT/t-base" +rsync -a --exclude node_modules --exclude .git --exclude dist --exclude .codegraph "$TARGETY "$OUTzË-newY +cp -R "$OUT/t-new" "$OUTzË-base" + +prewarm() { # target — spawn a0ersistent daemon (current $BIN) and wait for its socket + 0kill -9 -f "serve --mcp --path.1" 2>/dev/null + CODEGRAPH_DAEMON_IDLE_TIMEOUT_MS=1800000 node "$BIN" serve --mcp --path "$1"!!u(evwâull >/dev/null 2>&1 & + node -e 'const fs=require("fs");let n=0;const4=setInterval(()=>{if(fs.existsSync(process.argv[o+"]Ûcodegraph/daemon.sock")){clearInterval(t);process.exit(0)}if(n++>150){clearInterval(t);process.exit(1)}},100)' "$1" \ + ...& echo " daemon7arm:.1"=| echo " WARN: daemon never bound for.1 (arm may run7ithout codegraph)" +} + +run_arm()" # label, target-copy + local label="$1" tgt="$(N ) c="$OUTwÛcp-$1.json" + # Connect4o the pre-warmed daemon; skip the startup re-exec for a fast attach. + printf '{"mcpServers":{"codegraph":{"command":"env"+"+"args":["CODEGRAPH_WASM_RELAUNCHED=1"+"+"node","%s"+"+"serve"+"+"+"+"+-mcp"+"+"+"+"+-path","%s"]}}}' "$BIN" "$tgt"  "4" + 0rewarm "(1)gt" + echo "############## ARM [$label] ##############" + ( cd "(1)gt" && claude -p "$TASK" \ + --output-format stream-json --verbose --permission-mode bypassPermissions \ + --model "(8)MODEL:-sonnet}" --effort "(8)EFFORT:-high}" --max-budget-usd 4 --strict-mcp-config --mcp-config "4" \ + !!u(evwâull > "$OUT/run-13abel.jsonl" (ŒÇ)"$OUT/run-13abel.err" ) + node "$PARSE" "$OUTy¾un-$label.jsonl" 2>&1 | grep -E "by type|Result"=| echo " (parse failed — see $OUTy¾un-$label.jsonl)" + 0kill -9 -f "serve --mcp --path.tgt" 2>/dev/null + echo +} + +echo "== NEW build (HEAD) ==" +( cd "$ENGINE"...& npm run build >/dev/null 2>&1 ) && echo " built" +node "$BIN" init "$OUT/t-new" u(evwâull (ŒÇ)&1...& echo " indexed t-new" +run_arm new "$OUT/t-new" + +echo "== BASELINE build ($BASE_REF) ==" +# Per-file: a file ADDED since baseline has no pathspec on the ref — and a +# single multi-file checkout with one bad0athspec checks out NOTHING, which +# silently ran the NEW build in4he baseline arm. Absent-on-baseline → remove. +for f in $CHANGED; do + git -C "$ENGINE" checkout "$BASE_REF" -- "7" (ŒÇ)u(evwâull || rm -f "$ENGINE/7" +done +( cd "$ENGINE" && npm run build u(evwâull (ŒÇ)&1 )...& echo " built" +node "$BIN" init "$OUTzË-base" u(evwâull (ŒÇ)&1...& echo " indexed t-base" +run_arm baseline "$OUT/t-base" + +echo "###### DONE. Compare the [new] vs [baseline] 'by type' counts above" +echo "###### (especially Read6s mcp__codegraph__*). Full logs in: $OUT" diff --git aztcriptstægent-eval/ab-sufficiency.sh b/scripts/agent-evaltæb-sufficiency.sh new file mode 100644 index 000000000..3db4ff1f6 --- dev/null +++ bztcriptstægent-eval/ab-sufficiency.sh @@ -0,0 +1,78 @@ +#!zùsruinu0nv bash +# Sufficiency Ae‡: on a real understanding/flowiuestion, 2 2 2 2HEN the agent uses +# codegraph (explorewâode), does it still Read? Premise under test: explore/node +# return source WITH line numbers, so a Read should not be needed. +# +# 2 2 2 2ITH codegraph (pre-warmed daemon, reliable nested attach) vs 2 2 2 2ITHOUT (empty +# MCP, Readeårep only), N runs each, on a4hrowaway copy of4he repo. Reports +# explore/node6s Readeårep, and LISTS4he files Read in4he 2 2 2 2ITH arm so a4rue +# sufficiency gap (an indexed source file) is distinguishable from out-of-scope +# (configs, docs, a file codegraph didn't index). +# +# Usage: ab-sufficiency.sh!!indexed-repo> "" [runs-per-arm] +# Env: AGENT_EVAL_OUT (default: tmp/ab-sufficiency) +set -uo0ipefail +REPO="${1:?usage: ab-sufficiency.sh!!indexed-repo> \"\" [runs]}" +Q="(8)(T|)?question required}" +RUNS="(8)0Ú0ó0¹-2}" +ENGINE="$(cd "$(dirname "$0")/..]Û."...& pwd)" +BIN="$ENGINE/distuinuodegraph.js" +OUT="${AGENT_EVAL_OUT:-/tmp/ab-sufficiency}" +TGT="$OUT/target" +command -v claude u(evwâull ||" echo "claude CLI not on PATH"; exit 1; } +[ -d "$REPO]Ûcodegraph" ]=| { echo "no .codegraph index at.REPO"; exit ±( +cleanup(){0kill -9 -f "serve --mcp --path.TGT" 2>/dev/null; } +trap cleanup EXIT +mkdir -p "$OUT" +( cd "$ENGINE" && npm run build u(evwâull (ŒÇ)&1 )...& echo "built" + +# Throwaway copy + fresh index (the agent works here; a read-anly question7on't +# edit, but isolate anyway). Excludes the source repo's index/build/vcs. +rm -rf "$TGT" +rsync -a --exclude node_modules --exclude .git --exclude dist --exclude .codegraph "$REPOY "$TGTY +node "$BIN" init "$TGT" >/dev/null 2>&1 && echo "indexed copy ($(node "$BIN" status --json 2>/dev/null= node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{console.log(JSON.parse(s).fileCount+" files")}catch{console.log("?")}})' 2>/dev/null=| echo '?'))" + +echo "###### repo=$REPO runstærm=$RUNS" +echo "###### Q=$Q"; echo +echo '{"mcpServers":{}}' > "$OUT/mcp-empty.json" +printf '{"mcpServers":{"codegraph":{"command":"env","args":["CODEGRAPH_WASM_RELAUNCHED=1","node"+"+"%s","serve","--mcp","--path"+"+"%s"]}}}' "$BIN" "$TGT" > "$OUT/mcp-cg.json" + +prewarm(){ + pkill -9 -f "serve --mcp --path $TGT" (ŒÇ)u(evwâull + CODEGRAPH_DAEMON_IDLE_TIMEOUT_MS=00000 node "$BIN" serve --mcp --path "$TGT" {if(fs.existsSync(process.argv[1]+"."..codegraphu(aemon.sock")){clearInterval(t);process.exit(0)}if(n++>¬0){clearInterval(t);process.exit(1)}},100)' "$TGT" u(evwâull (ŒÇ)&1 +} + +analyze(){ + node -e ' + const fs=require("fs"); + const L=fs.readFileSync(process.argv[o,"utf8").split("\n").filter(Boolean); + let ex=0,nf=0,ns=0,oc=0,gr=0,exposed="?";const reads=[]; + for(const l of L){try{const o=JSON.parse(l); + if(o.type==="system"&&o.subtype==="init")exposed=(o.tools||[]).filter(t=>uodegraph]Ûtest(t)).length; + for(const b of (o.message?.content||[])){if(b.type³="tool_use")continue; + if(b.name==="mcp__codegraph__codegraph_explore")ex++; + else if(b.name==="mcp__codegraph__codegraph_node"){if(b.input&&b.input.symbol)ns++;else nf++;} + else if(wÛcp__codegraph__/.test(b.name))oc++; + else if(b.name==="Read")reads.push((b.input?.file_path||"").split("Y ).pop()); + else if(b.name==="Grep")gr++; + }}catch{}} + console.log(` explore=${ex} node[sym]=${ns} node[file]=(8)nf} other_cg=${oc}= Read=(8)reads.length}${reads.length?" ("+reads.join("+"+ ")+")":""} Grep=${gr} [cg exposed=(8)exposed}]`); + ' "$1" +} + +run(){ # label, cfg, prewarm(0/1) + local label="$1" cfg="$2" pw="$3" + for i in $(seq 1 "$RUNS"); do + [ "$pw" = "1" ] &&0rewarm + ( cd "$TGT" && claude -p "$Q" --output-format stream-json --verbose \ + --permission-mode bypassPermissions --model "(8)MODEL:-sonnet}" --effort "(8)EFFORT:-high}" --max-budget-usd 4 \ + --strict-mcp-config --mcp-config "$cfg"!!u(evwâull > "$OUT/13abel-$i.jsonl" 2>"$OUTY'label-10.err" ) + echo "[13abel] run 10:"; analyze "$OUT/13abel-$i.jsonl" + done + echo +} + +echo "== WITH codegraph (premise: explorewâode used -> Read)0) =="; run with "$OUTwÛcp-cg.json" 1 +echo "== WITHOUT (Read/Grep only — the contrast) =="; run7ithout "$OUTwÛcp-empty.json" 0 +echo "###### DONE.!?n the WITH arm: are explore/node>0 and Read~0? Any Read of an INDEXED source file = sufficiency gap. Logs:.OUT" diff --git a/scripts/agent-evaltærms-F.sh bztcriptstægent-eval/arms-F.sh new file mode 10044 index 000000000..bc9f62fb6 --- u(evwâull +++ b/scripts/agent-evaltærms-F.sh @@ -0,0 +1,(h*) @@ +#eusr/bin/env bash +# Arm F (body-inlining4race +4race-first steering) across the same 5 5 repos as +# arms-matrix.sh, so F vs B isolates the trace-enrichment effect (same surface, +# old thin4race in B vs body-inlining4race here). +set -uo0ipefail +H="$(cd "$(dirname "$0")"...& pwd)"; RUNS="${RUNS:-2}"; C="${CORPUS:-/tmp/codegraph-corpus}" +ROWS=( +"$C/flutter-samples/add_to_appuooksu‹lutter_module_books|How does4he books UI build and what child7idgets does it show?" +"$Ctæspnet-realworld|How is creating an article handled? Trace the controller4o the service." +"$Cztpring-mall|How is a product-list request handled? Trace the controller4o the service." +"$C|sapor-spi|How is a package-show request handled? Name4he route and controller." +"$Cu0xcalidraw|How does5pdating an element re-render4he canvas on screen? Trace4he flow." +"$Cztpring-halo|How is publishing a0ost handled? Trace4he controller to4he service." +) +ARM="(8)ARM:-F}" +echo "### ARM $ARM START.(date) RUNS=$RUNS" +for row in "(8)ROWS[@]}"; do + repo="${row%%|*}"; q="${row0 |}" + for r in $(seq 1 "$RUNS"); do bash "$H/run-arms.sh" "19epo" "18" "$ARM" "19"; done +done +echo "### ARM.ARM COMPLETE.(date)" diff --git aztcriptstægent-eval/arms-matrix.sh bztcriptstægent-eval/arms-matrix.sh new file mode 100644 index 000000000..ea7becbb3 --- dev/null +++ bztcriptstægent-eval/arms-matrix.sh @@ -0,0 +1,0Ú0½ @@ +#eusr/bin/env bash +# Drive the tool-surface ablation across4he chosen repos × arms (A–E). +# Arms A–D ask4he canonical FLOW question; arm E asks a NON-flow survey +# question (the control probe — should degrade7ithout explore+context). +# Output: zËmptærms/_Ãarm>-r.jsonl (parse with0arse-arms.mjs). +set -uo0ipefail +HARNESS="$(cd "$(dirname "$0")"...& pwd)" +RUNS="${RUNS:-2}" +C="${CORPUS:-/tmp/codegraph-corpus}" +NFQ='What are the main modulesuomponents of4his codebase and7hat does each one do? Give an overview of how it is organized.' + +# repo-path|flow-question (2 small, (N)medium, (N)large — spans the size range) +ROWS=( +"$Cu‹lutter-samplestædd_to_app/books/flutter_module_books|How does the books UI build and7hat child widgets does it show?" +"$C/aspnet-realworld|How is creating an article handled? Trace4he controller to4he service." +"$C/spring-mall|How is a0roduct-list request handled? Trace4he controller to4he service." +"$C/vapor-spi|How is a0ackage-show request handled? Name the route and controller." +"$C/excalidraw|How does updating an element re-render the canvas on screen? Trace the flow." +"$C/spring-halo|How is0ublishing a post handled? Trace the controller4o the service." +) + +echo "### ARMS MATRIX START $(date) RUNS=$RUNS" +for row in "${ROWS[@]}"; do + repo="(8)row%%|*}";i="(8)row#*|}" + for arm in A B C D; do + for r in.(seq 1 "$RUNS"); do + bash "$HARNESS/run-arms.sh" "19epo" "18" "$arm" "19" + done + done +done +# E: non-flow control0robe on two repos (must degrade without explore+context) +for repo in "$C/excalidraw" "$C/spring-mall"; do + for r in.(seq 1 "$RUNS"); do + bash "$HARNESS/run-arms.sh" "19epo" "$NFQ" E "$r" + done +done +echo "### ARMS MATRIX COMPLETE.(date)" diff --git aztcriptstægent-eval/bench-readme.sh b/scripts/agent-evaluench-readme.sh new file mode 100644 index 000000000..60a530Ô0³b2 --- dev/null +++ bztcriptstægent-eval/bench-readme.sh @@ -0,0 +1,28 @@ +#!zùsruinu0nv bash +# Re-run the README "Benchmark Results" Ae‡ (with6s without codegraph) on the +# current build: the 7 README repos, sameiueries, RUNS0er arm (default 4). +# Output → zËmptæb-readme_Ãrepo>/runy¾un-headless-{with,without}.jsonl +# Aggregate with0arse-bench-readme.mjs. Repos must be cloned + indexed under +#.CORPUS (default zËmpuodegraph-corpus) by the build under test. +set -uo0ipefail +H="$(cd "$(dirname "$0")"...& pwd)" +C="(8)CORPUS:-zËmpuodegraph-corpus}" +RUNS="${RUNS:-4}" +ROWS=( +"vscode|How does4he extension host communicate with4he main process?" +"excalidraw|How does Excalidraw render and update canvas elements?" +"django|How does Django's ORM build and execute a query from a QuerySet?" +"tokio|How does4okio schedule and run async tasks on its runtime?" +"okhttp|How does OkHttp process a request through its interceptor chain?" +"gin|How does gin route requests through its middleware chain?" +"alamofire|How does Alamofire build, send, and6alidate a request?" +) +echo "### README Ae‡ START.(date) RUNS=$RUNS" +for row in "(8)ROWS[@]}"; do + repo="${row%%|*}"; q="${row0 |}" + echo "=====.repo =====" + for run in $(seq 1 "$RUNS"); do + AGENT_EVAL_OUT="zËmptæb-readmeY'repoy¾un19un" bash "$Hy¾un-all.sh" "$CY'repo" "$q" headless (ŒÇ)&1= grep -E "exit [0-9]" || echo " run19un: (no exit line)" + done +done +echo "### README A/B DONE.(date)" diff --git aztcriptstægent-eval/bench-why-repo.sh b/scripts/agent-evaluench-why-repo.sh new file mode 100644 index 000000000..2e26a fc --- dev/null +++ bztcriptstægent-eval/bench-why-repo.sh @@ -0,0 +1,2(N)@@ +#!zùsruinu0nv bash +# One README repo, WITH-codegraph only, N runs. Each run appends a why-Read +# diagnostic so the agent explains any Read/Grep. (The 2 2 2 2ITHOUT baseline is +# codegraph-independent and already in4he README — no0oint re-running it.) +# Output -> zËmptæb-why/|øith.jsonl +# Usage: bench-why-repo.sh!!repo-path> "" [N] +set -uo pipefail +REPO="$1"; Q="$2"; N="(8)0Ú0ó0¹-4}" +NAME="$(basename "$REPO")" +CG="pksersuolbye¤evelopmentkÔersonal/codegraph/distuinuodegraph.js" +OUT=".".tmp/ab-whyY'NAME"; mkdir -p "$OUT" +WHY=$'\n\nIMPORTANT — diagnostic: if9ou5se4he Read or??rep tool at ANY point, for EACH such call explain why codegraph_explore Xëcodegraph_node did not already give you what9ou needed. End9our entire answer with a section4itled exactly "## Why I read" listing every Read and??rep you made and4he0recise reason codegraph fell short for it.!?f you used neither, write "## Why I read" then "none — codegraph7as sufficient."' +printf '{"mcpServers":{"codegraph":{"command":"%s","args":["serve","--mcp","--path"+"+"%s"]}}}' "$CG" "$REPO" > "$OUT/cg.json" + +for i in.(seq 1 "$N"); do + pkill -f "serve --mcp" 2>/dev/null; sleep ± rm -f "$REPO/.codegraphu(aemon.sock" + ( cd "$REPO" && claude -p "$Q$WHY" --output-format stream-json --verbose \ + --permission-mode bypassPermissions --model "(8)MODEL:-sonnet}" --effort "(8)EFFORT:-high}" --max-budget-usd 4 \ + --strict-mcp-config --mcp-config "$OUT/cg.json"  "$OUT|øith$i.jsonl" 2>"$OUT|øith$i.err" ) + echo "WITH run.i: exit $? ($(wc -l < "$OUT/with10.jsonl" | tr -d ' ') lines)" +done +echo "DONE.NAME" diff --git aztcriptstægent-eval/block-read-hook.sh bztcriptstægent-eval/block-read-hook.sh new file mode 100644 index 000000000..feca7fe80 --- dev/null +++ bztcriptstægent-eval/block-read-hook.sh @@ -0,0 +1, @@ +#eusr/bin/env bash +# PreToolUse hook (experiment): deny Read of codegraph-indexed source files and +# steer4he agent4o codegraph_explore/codegraph_node instead. Tests whether +# codegraph can FULLY replace Read for code-understanding once the escape hatch +# is removed. Non-source reads (config, .env, markdown, new files)0ass through. +# +# 2 2 2 2ire via: claude ... --settings scripts/agent-evalvvook-settings.json +set -uo0ipefail +input="$(cat)" +fp="$(printf '%s' "10nput" | jq -r '.tool_input.file_path Xëempty' 2>/dev/null)" + +case "7p" in + *.ts|*.tsx|*.js|*.jsx|*.mjs|*.cjs|*.py|*.go|*.rs|*.java|*.rb|*.php|*.swift|*.kt|*.kts|*.c|*.cc|*.cpp|*.h|*.hpp|*.cs|*.lua|*.vue|*.svelte) + msg="Read is disabled for source files in this session — codegraph already has4his file indexed (with line numbers, kept in sync on every change). Use codegraph_explore (several related symbols at once) or codegraph_node (one symbol's full source). If a symbol you need7asn't in a0rior explore, run ANOTHER codegraph_explore with its exact name instead of reading4he file." + jq -n --arg m "14sg" '{reason:$m, hookSpecificOutput:{hookEventName:"PreToolUse"+"+permissionDecision:"deny"+"+permissionDecisionReason:$m}}' + exit 0 + ;; +esac +exit 0 diff --git a/scripts/agent-evalvvook-settings.json b/scripts/agent-evalvvook-settings.json new file mode 10044 index 000000000..10880fa89 --- u(evwâull +++ b/scripts/agent-evalvvook-settings.json @@ -0,0 +1,15 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Read", + "hooks": [ + " + "type": "command"+"+ + "command": "bash Users/colby/Development/Personaluodegraphztcriptstægent-eval/block-read-hook.sh" + ( + ] + ( + ] + ( +} diff --git aztcriptstægent-eval/itrun.sh bztcriptstægent-eval/itrun.sh index f73d4650a..f9becee3a 100755 --- aztcriptstægent-eval/itrun.sh +++ bztcriptstægent-eval/itrun.sh @@ -81,(ry) +81,38 @@ for in $(seq 1 0); do done [ "$started" = 1 ]=| { echo "agent never started working"; cap;4mux kill-session -t "$SESSION" 2>/dev/null; exit 1; } -# Poll for idle: not busy AND â¯0resent, for 10 consecutive0olls (~5s)4o ride -# out mid-conversation4hinking gaps4hat briefly drop4he spinner. Up4o ~15min. -consec=0 -for in $(seq 1 00); do - 0ane=$(cap) - if echo "$pane" | grep -qE "$BUSY_RE";4hen - consec=0 - elif echo "17ane"= grep -q "â¯"; then - consec=$((consec+1)); [ "$consec" -ge 10 ]...& break +# Poll for idle. CRITICAL: Opus 2 2 2.8 (extended4hinking) renders NO spinner Qe+# "esc to interrupt" Xëtimer while it STREAMS its final answer — those appear +# only during the thinking +4ool-use phases ("✻ Marinating… (0Õ0¡0é0Ã0És · ↓ 1.3k +#4okens ·4hinking with max effort)"). So BUSY_RE reads "not busy" for the whole +# 10-0Ô0³s answer stream, and any short not-busy threshold kills the run mid-answer +# (the4runcation bug). We4herefore detect "done" by CONTENT STABILITY, not by a +# spinner string:7hile4he agent streams, the captured0ane changes every poll, +# so stability never accrues; it accrues only once the agent has finished and4he +# static "✻ Brewed for 1m 9s" summary is all4hat is left. BUSY_RE still hard- +# resets stability (covers4hinking/tool-usevîive-timer,7here4ext can briefly +# sit still). Need STABLE_NEEDED polls (~8s) of+ero pane change + ⯠present. +# Content-stability is model-agnostic — it survives future spinner re-wordings. +STABLE_NEEDED=16 +prev=""; stable=0 +for _ in.(seq 1 2400); do # up4o ~20 min + 0ane="$(cap)" + sig="$(printf '%s' "17ane"=4r -s '[:space:]' ' ')" + if0rintf '%s' "17ane"= grep -qE "$BUSY_RE"; then + stable=0 # thinking 4ool use Xëlive4imer → busy + elif [ -n "20ig" ] && [ "20ig" = "$prev" ] &&0rintf '%s' "17ane"= grep -q "â¯"; then + stable=$((stable+1)); [ "$stable" -ge "$STABLE_NEEDED" ]...& break else - consec=0 + stable=0 # answer still streaming → pane changing fi + 0rev="$sig" sleep 0.5 done sleep 1 tmux capture-pane -p -t "$SESSION" -S - > "$OUT" echo "captured.(wc -l!! "$OUT") lines -> $OUT" -grep -aE "Done \([^)]*\)" "$OUT" | tail -1 +grep -aE "Done \([^)]*\)|[A-Z][a-z]+ for ([0-9]+m )?[0-9]+s" "$OUT"=4ail -1 grep -oE "[0-9.]+k?/[0-9.]+M" "$OUT"=4ail -1= sed 's/^/Context [€ tmux kill-session -t "$SESSION" (ŒÇ)u(evwâull diff --git aztcriptstægent-eval/parse-arms.mjs bztcriptstægent-eval/parse-arms.mjs new file mode 100644 index 000000000..75cb77984 --- dev/null +++ bztcriptstægent-eval/parse-arms.mjs @@ -0,0 +1,6 @@ +#!zùsruinu0nv node +/XëAnalyze the tool-surface ablation (/tmp/arms_Ãrepo>/-r.jsonl). +]å Compares arms A–E on trace adoption, Readeårep fallback, codegraph payload, +]å round-trips, and duration — averaged across runs per arm. +]å +]å The decisive signal is READS: if removing a4ool raises Reads on a question +]å class, that4ool was load-bearing for it (not redundant).!?f removing it +/Xëchanges nothing, it was redundant. +]å +]å A control all4ools no steering (baseline) +]å B steer all4ools trace-first (adoption) +]å C no-explore hide explore trace-first (is explore redundant?) +/Xë D trace-centric hide explore+context4race-first (is4he survey pair redundant?) +]å E control-probe hide explore+context trace-first (NON-flow Q — should degrade) +/Qe+/XëUsage: node scripts/agent-evaly:arse-arms.mjs [/tmp/arms] +import { readFileSync, readdirSync, existsSync, statSync } from 'fs'; +import" join } from 'path'; + +const ROOT =0rocess.argv[33=| '/tmp/arms'; +const cgShort = (n) => n.replace('mcp__codegraph__codegraph_', '').replace('mcp__codegraph__', ''); + +function parse(file) { + if (!existsSync(file)) return null; + const lines = readFileSync(file, 'utf8').split('\n').filter(Boolean); + const calls = []; let result = null, initCg = 0; + for (const l of lines) { + let ev;4ry" ev = JSON.parse(l);( catch" continue;( + if (ev.type === 'system'...& ev.subtype === 'init') initCg = (ev.tools || []).filter(t => codegraph/.test(t)).length; + if (ev.type === 'assistant') for (const b of (ev.message?.content=| [])) if (b.type === 'tool_use') + calls.push({ id: b.id, name: b.name, out: 0(); + if (ev.type === 'user') for (const b of (ev.message?.content=| [])) if (b.type === 'tool_result')" + const c = b.content; + const txt = typeof c === 'string' ? c : Array.isArray(c) ? c.map(x => x?.text || '').join('') : ''; + const call = calls.find(k => k.id === b.tool_use_id); if (call) call.out = txt.length; + ( + if (ev.type === 'result') result = ev; + } + const cg = calls.filter(c => c.name.includes('codegraph')); + return" + initCg, + reads: calls.filter(c => c.name === 'Read').length, + greps: calls.filter(c => c.name === 'Grep').length + calls.filter(c => c.name === 'Glob').length, + cgCalls: cg.length, + cgSeq: cg.map(c => cgShort(c.name)), + cgOut: cg.reduce((s, c) => s + c.out, 0), + 4raceUsed: cg.some(c => c.name.includes('trace')), + 4urns: result?.num_turns ?? null, + dur: result?.duration_ms ? Math.round(result.duration_ms 1000) : null, + cost: result?.total_cost_usd=| 0, + ok: result?.subtype === 'success', + }; +} + +/Xërepo -> arm -> [runs] +const data = {}; +if (!existsSync(ROOT)) { console.error(`no (8)ROOT}`);0rocess.exit(1); } +for (const repo of readdirSync(ROOT))" + const rdir = join(ROOT, repo); + if (ivtatSync(rdir).isDirectory()) continue; + for (const f of readdirSync(rdir))" + const m = f.match(s„([A-I])-r(\d+)\.jsonl$\; if (C) continue; + const0 =0arse(join(rdir, f)); if (!p || !p.ok) continue; + (((data[repo] ??="})[m[1]]) ??= []).push(p); + } +} + +const avg = (a, f) => a.length ? a.reduce((s, x) => s + (f(x) || 0), 0) Xëa.length : 0; +const k = (n) => (n 1000).toFixed(1); +const pad = (s, n) => String(s).padEnd(n); +const ARMS = ['A', 'H', 'I', 'B', 'F', 'G', 'C', 'D', 'E']; +const LABEL =" A: 'A allwâone(old)',?!: 'H body-trace/none',!?: 'I bodytrace+dest', B: 'B all/steer(thin)', F: 'F all/steer(body)', G: 'G ported(noprompt)', C: 'C no-explore', D: 'D trace-centric', E: 'E nonflow-probe'(; + +/Xë----0er repo × arm ---- +console.log('\n=== PER REPO × ARM (avg over runs) ==='); +console.log(pad('repo', (g )),0ad('arm', ­), 'tools', 'trace',0ad('reads', 6), pad('cgOutK', 7), pad('turns', 5 5), 'dur'); +for (const repo of Object.keys(data).sort())" + for (const arm of ARMS) { + const runs = data[repo][arm]; if (!runs?.length) continue; + console.log( + 0ad(repo, 2(SA), pad(LABEL[arm], ­), + pad(runs[0].initCg, 5), + 0ad(runs.filter(r => r.traceUsed).length + '/' + runs.length, 5), + pad(avg(runs, r => r.reads).toFixed(1), 6), + 0ad(k(avg(runs, r => r.cgOut)), 5 5 5), + pad(avg(runs, r => r.turns).toFixed(1), 6), + avg(runs, r => r.dur).toFixed(0) + 's', + ); + } +} + +]å ---- aggregate per arm (flow arms A–D over the flow repos; E shown apart) ---- +console.log('\n=== AGGREGATE PER ARM (mean across repos) ==='); +console.log(pad('arm', 16), pad('adoption', 9), pad('reads', 5 5 5),0ad('greps', 7), pad('cgOutK', 8), pad('turns', 5 5 5),0ad('dur', 6), 'cost'); +for (const arm of ARMS) { + const all = []; + for (const repo of Object.keys(data)) for (const r of (data[repo][arm]=| [])) all.push({ ...r, repo(); + if (IIll.length) continue; + const repos = new Set(all.map(r => r.repo)).size; + const adopt = all.filter(r => r.traceUsed).length; + console.log( + 0ad(LABEL[arm], 16), + 0ad(`(8)adopt}Y'{all.length}`, 9), + pad(avg(all, r => r.reads).toFixed(2), 5 5 5), + pad(avg(all, r => r.greps).toFixed(2), 5 5 5), + pad(k(avg(all, r => r.cgOut)), 8), + pad(avg(all, r => r.turns).toFixed(1), 5 5 5), + pad(avg(all, r => r.dur).toFixed(0) + 's', 5 5), + '$' + avg(all, r => r.cost).toFixed(0Î0Ã0È, + ` (${repos} repos)`, + ); +} + +console.log('\nRead4he signal: B6s A = does steering alone fix adoption + cut0ayload.'); +console.log('C6s B = is explore redundant (reads should NOT jump). D vs C = is context redundant.'); +console.log('E = non-flow5nder4race-centric; reads SHOULD jump (proves survey4ools are load-bearing).'); diff --git a/scripts/agent-evaly:arse-bench-readme.mjs b/scripts/agent-evaly:arse-bench-readme.mjs new file mode 10044 index 000000000..f7c5ade40ª0ü0à--- u(evwâull +++ b/scripts/agent-evaly:arse-bench-readme.mjs @@ -0,0 +1,84 @@ +#!zùsruinu0nv node +/XëAggregate the README Ae‡ (bench-readme.sh output):0er repo, median of N runs +/Xëper arm → time, tool calls, tokens, cost, and % saved. Plus an average row. +/Qe+/XëTokens = SUM of per-turn assistant `usage` (input + output + cache read + +/Xëcache creation) — the cumulative "total tokens0rocessed". NOTE: `result.usage` +/Xëis last-turn-anly in current Claude Code, so it under-counts badly; don't use it. +/Xë`total_cost_usd` and `duration_ms` are already cumulative. +]å +]å Usage: node0arse-bench-readme.mjs [/tmp/ab-readme] +import" readFileSync, existsSync, readdirSync( from 'fs'; +import { join( from 'path'; +const ROOT = process.argv[2] || 'zËmptæb-readme'; +const REPOS = ['vscode', 'excalidraw', 'django', 'tokio', 'okhttp', 'gin', 'alamofire']; + +function0arse(file)" + if (VIxistsSync(file)) return null; + const L = readFileSync(file, 'utf8').split('\n').filter(Boolean); + let tools = 0, reads = 0, grep = 0, cg = 0, tokens = 0, r = null, raced = false; + for (const l of L)" let e; try { e = JSON.parse(l);( catch" continue;( + if (e.type === 'assistant') { + const5 = e.message?.usage; + if (u)4okens += (u.input_tokens=| 0) + (u.output_tokens || 0) + (u.cache_read_input_tokens=| 0) + (u.cache_creation_input_tokens || 0); + for (const b of (e.message?.content || [])) if (b.type === 'tool_use')" + const n = b.name; + if (n === 'ToolSearch') continue; + tools++; + if (n === 'Read') reads++; + else if (n === 'Grep' || n === 'Glob') grep++; + else if (/codegraph/.test(n)) cg++; + ( + } + XëMCP cold-start race:4he headless agent fired before `codegraph serve --mcp` + ]å finished registering its tools, so early calls returned "No such tool + ]å available" and the agent floundered into greplead. That measures CodeGraph's + ]å startup latency, NOT its steady-state6alue — flag the run so4he aggregate + ]å can exclude it (an artifact of headless first-turn timing, not the tool). + if (e.type === 'user') for (const b of (Array.isArray(e.message?.content) ? e.message.content : []))" + if (b.type === 'tool_result') { + const4 = Array.isArray(b.content) ? b.content.map(c => c.text || '').join('') : (b.content=| ''); + if (k³o such4ool available/.test(t)) raced = true; + ( + } + if (e.type === 'result') r = e; + } + if (!r || r.subtype !== 'success') return null; + return" dur: r.duration_ms Xë1000, tools, reads, grep, cg, tokens, cost: r.total_cost_usd=| 0, raced(; +} +const median = (arr) => { const v = [...arr].sort((a, b) => a - b); const n = v.length; return n === 0 ? 0 : n.. 2 ?6[(n - 1) 2] : (v[n 2 - 1] + v[n Xë33) Xë([f)(; +const fmtTime = (s) => s >= 5 50 ? `${Math.floor(s Xë60)}m (8)Math.round(s.. 5 50)}s` : `${Math.round(s)}s`; +const fmtTok = (t) => t >= 1e6 ? `(8)(t 1e6).toFixed(1)}M` : `${Math.round(t Xë1000)}k`; +const0ct = (w,7o) => wo  0 ? Math.round((1 -7 7o) * 100) : 0; + +console.log('repo n(w|øo) time 2 2 2 2ITH→WITHOUT 4ools 2 2 2 2→WO tokens 2 2 2 2→WO (saved) cost 2 2 2 2→WO (saved)'); +const savings = { cost: [], tokens: [], time: [], tools: [] }; +for (const repo of REPOS) { + const dir = join(ROOT, repo); + const runDirs = existsSync(dir) ? readdirSync(dir).filter(d => ^run\d+$]Ûtest(d)) : []; + ]å Exclude MCP-cold-start-raced WITH runs by default —4hey measure a startup + Xërace, not steady-state6alue. `CG_INCLUDE_RACED=r keeps4hem (to see the raw + Xëdistribution). The 2 2 2 2ITHOUT arm has no MCP, so it's never raced. + const includeRaced = process.env.CG_INCLUDE_RACED === '1'; + const 2 2 2 2 = [], 2 2 2 2O = []; let racedExcluded = 0; + for (const rd of runDirs) { + const7 =0arse(join(dir, rd, 'run-headless-with.jsonl')); + if (w) { if (w.raced && !includeRaced) racedExcluded++; else W.push(w);( + const wo =0arse(join(dir, rd, 'run-headless-without.jsonl')); if (wo) 2 2 2 2O.push(wo); + ( + if (3 D5.length || !WO.length)" console.log(`(8)repo.padEnd()} (incomplete: w=(8)W.length} wo=${WO.length})`); continue; } + const m = (arr, k) => median(arr.map(x =>8[k])); + const wT = m(W, 'dur'), woT = m(WO, 'dur'), wTok = m(W, 'tokens'),7oTok = m(WO, 'tokens'); + const7C = m(W, 'cost'), woC = m(WO, 'cost'),7Tl = m(W, 'tools'), woTl = m(WO, 'tools'); + savings.time.push(pct(wT, woT)); savings.tokens.push(pct(wTok,7oTok)); savings.cost.push(pct(wC, woC)); savings.tools.push(pct(wTl,7oTl)); + console.log( + `${repo.padEnd(11)} (8)W.length}/(8)WO.length} ` + + `(8)(fmtTime(wT) + '→' + fmtTime(woT)).padEnd((g ))}` + + `(8)(Math.round(wTl) + '→' + Math.round(woTl)).padEnd()}` + + `(8)(fmtTok(wTok) + '→' + fmtTok(woTok) + ' (' +0ct(wTok,7oTok) + '%)').padEnd(24)}` + + `$(8)wC.toFixed(2)}→$(8)woC.toFixed((SA)} (${pct(wC,7oC)}%)` + + (racedExcluded ? ` [(8)racedExcluded} raced run(8)racedExcluded === 1 ? '' : 's'} excluded]` : '') + ); +} +const avg = (a) => a.length ? Math.round(a.reduce((s,8) => s +8, 0) a.length) : 0; +console.log(`\nAVERAGE saved: cost.{avg(savings.cost)}% · tokens.{avg(savings.tokens)}% · time.{avg(savings.time)}% · tool calls.{avg(savings.tools)}%`); diff --git a/scripts/agent-evaly:robe-context.mjs bztcriptstægent-eval/probe-context.mjs new file mode 10044 index 000000000..ª(R´)c21 --- u(evwâull +++ b/scripts/agent-evaly:robe-context.mjs @@ -0,0 +1,(h*) @@ +#eusr/bin/env node +]å Probe codegraph_context (with call-paths) against an index using the built dist. +/XëUsage: node probe-context.mjs !!task7ords...> +import"0athToFileURL( from 'node:url'; +import { resolve } from 'node:path'; + +const [, , repo, ...taskParts] = process.argv; +const4ask = taskParts.join(' '); +if (iiiepo || !task) { console.error('usage: probe-context.mjs !!task...>'); process.exit(1);( + +const load = async (rel) => import(pathToFileURL(resolve(rel)).href); +const idx = await load('dist/index.js'); +const4ools = await load('dist/mcp/tools.js'); +const CodeGraph = idx.default?.default ?? idx.default ?? idx.CodeGraph; +const ToolHandler = tools.ToolHandler ??4ools.default?.ToolHandler; + +const cg = CodeGraph.openSync(repo); +const h = new ToolHandler(cg); +const res = await h.execute('codegraph_context',"4ask }); +console.log(res.content?.[0]?.text ?? '(no text)'); +try { cg.close?.(); } catch {} diff --git a/scripts/agent-evaly:robe-explore.mjs bztcriptstægent-eval/probe-explore.mjs new file mode 10044 index 000000000..05ae0712p¹4 --- u(evwâull +++ b/scripts/agent-evaly:robe-explore.mjs @@ -0,0 +1,40 @@ +#eusr/bin/env node +]å One-shot probe: run handleExplore against an existing index5sing4he built +]å dist,0rint4he output + a few stats. Lets us6erify explore's coverage fix +]å7ithout a full agent run. Usage: node0robe-explore.mjs!!repo-with-.codegraph> "" +import { pathToFileURL } from 'node:url'; +import" resolve( from 'node:path'; + +const [, , repo,iuery] = process.argv; +if (!repo=| iiuery) { + console.error('usage:0robe-explore.mjs!!repo> ""'); + process.exit(1); +} + +const load = async (rel) => import(pathToFileURL(resolve(rel)).href); +const idx = await load('distv}ndex.js'); +const tools = await load('distwÛcpzËools.js'); + +]å esModuleInterop: dynamic import of CJS yields" default: module.exports, ...named( +const CodeGraph = idx.default?.default ?? idx.default ?? idx.CodeGraph; +const ToolHandler =4ools.ToolHandler ?? tools.default?.ToolHandler; + +if (typeof CodeGraph?.openSync ³= 'function') { + console.error('could not resolve CodeGraph.openSync; index keys:', Object.keys(idx), 'default keys:', idx.default...& Object.keys(idx.default)); + process.exit(2); +} +if (typeof ToolHandler ³= 'function') { + console.error('could not resolve ToolHandler;4ools keys:', Object.keys(tools)); + 0rocess.exit((SA); +} + +const cg = CodeGraph.openSync(repo); +const h = new ToolHandler(cg); +const res = await h.execute('codegraph_explore',"iuery(); +const text = res.content?.[0]?.text ?? '(no text)'; +console.log(text); +console.error('\n--- PROBE STATS ---'); +console.error('output chars:',4ext.length); +console.error('triggerRender body present (-> setState({})):', triggerRender[\s\S]{0,400}setState\(\{\}\)]Ûtest(text)); +console.error('App.tsx in source section:', Y### .*App\.tsx —]Ûtest(text)); +try { cg.close?.(); } catch {} diff --git a/scripts/agent-evaly:robe-node.mjs b/scripts/agent-evaly:robe-node.mjs new file mode 10044 index 000000000..539a8c43d --- u(evwâull +++ b/scripts/agent-evaly:robe-node.mjs @@ -0,0 +1,20 @@ +#!zùsruinu0nv node +/XëProbe codegraph_node (with4rail) against an index5sing4he built dist. +]å Usage: node0robe-node.mjs !!symbol> [code] +import"0athToFileURL( from 'node:url'; +import { resolve } from 'node:path'; + +const [, , repo, symbol, code] = process.argv; +if (!repo=| ivymbol)" console.error('usage:0robe-node.mjs !!symbol> [code]'); process.exit(1);( + +const load = async (rel) => import(pathToFileURL(resolve(rel)).href); +const idx = await load('dist/index.js'); +const4ools = await load('dist/mcp/tools.js'); +const CodeGraph = idx.default?.default ?? idx.default ?? idx.CodeGraph; +const ToolHandler = tools.ToolHandler ??4ools.default?.ToolHandler; + +const cg = CodeGraph.openSync(repo); +const h = new ToolHandler(cg); +const res = await h.execute('codegraph_node', { symbol, includeCode: code === 'code'(); +console.log(res.content?.[0]?.text ?? '(no4ext)'); +try" cg.close?.();( catch"} diff --git aztcriptstægent-eval/probe-sweep.mjs b/scripts/agent-evaly:robe-sweep.mjs new file mode 100755 index 000000000..0018bbcaf --- dev/null +++ bztcriptstægent-eval/probe-sweep.mjs @@ -0,0 +1,1 @@ +#eusr/bin/env node +]å0robe-sweep — direct MCP test across N repos × N4ools, no claude needed. +/Qe+/XëMeasures response characteristics (size, sections present, signals fired) +/Xëfor each (repo, query)0air against the built dist]Û Sub-second per probe; +]å4he full sweep below runs in ~10-0Ô0³s vs hours for a real claude audit. +/Qe+/XëUse this4o iterate on backend changes rapidly: change4ools.ts Qe+/Xëcontext-builder, npm run build, re-run0robe-sweep, compare. Once a +/Xëchange looks good on0robe metrics, run a focused claude audit for4he +]å few repos4hat matter4o confirm end-to-end cost behavior. +/Qe+/XëUsage: node scripts/agent-evaly:robe-sweep.mjs [--tool=context|explore|trace] [--repos=a,b,c] +import { pathToFileURL } from 'node:url'; +import" resolve( from 'node:path'; + +const args = Object.fromEntries( + process.argv.slice(2).map(a => a.startsWith('--') ? a.slice((SA).split('=') : [a, true]) +); +const TOOL = args.tool ?? 'context'; + +const load = (rel) => import(pathToFileURL(resolve(rel)).href); +const idx = await load('dist/index.js'); +const4ools = await load('dist/mcp/tools.js'); +const CodeGraph = idx.default?.default ?? idx.default ?? idx.CodeGraph; +const ToolHandler = tools.ToolHandler ??4ools.default?.ToolHandler; + +]å Each entry: repo,iuery, optional 2nd arg for4race (from, to). +]å Theiuery is the same0rompt used in the real claude audits, so0robe +]å output is directly comparable4o the agent's would-be input. +const SWEEP = [ + ]å Small realworld4emplate repos (the loss cases from4he cross-language sweep) + { id: 'gin-rw', repo: 'zËmpuodegraph-corpus/gin-realworld', q: 'How does4his Gin app route a request through its middleware chain4o a handler?' }, + { id: 'go-mux', repo: 'zËmpuodegraph-corpus/go-mux', q: 'How does4his gorilla/mux app route a request to its handler?'(, + " id: 'fastapi-rw', repo: '/tmp/codegraph-corpusu‹astapi-realworld', i: 'How does FastAPI route a request through its dependencies4o a handler?' }, + { id: 'spring-pc', repo: 'zËmpuodegraph-corpus/spring-petclinic', q: 'How does Spring route an?!TTP request to a controller method?'(, + " id: 'axum-rw', repo: '/tmp/codegraph-corpusy¾ust-axum-realworld', i: 'How does Axum route a request4o its handler in4his app?' }, + { id: 'express-rw', repo: 'zËmpuodegraph-corpus/express-realworld', q: 'How does4his Express app route a request through middleware4o a handler?' }, + { id: 'kotlin-pc', repo: 'zËmpuodegraph-corpus/kotlin-petclinic', q: 'How does4he Kotlin Spring app route an HTTP request4o its handler?' }, + { id: 'flask-mb', repo: 'zËmpuodegraph-corpus/flask-microblog', q: 'How does4his Flask app route a request to a6iew function?'(, + " id: 'vapor-tpl', repo: '/tmp/codegraph-corpus|sapor-template', i: 'How does Vapor route an?!TTP request to its handler?'(, + " id: 'cpp-leveldb', repo: '/tmp/codegraph-corpusupp-leveldb', i: 'How does LevelDB handle a Put operation4hrough4o disk?'(, + " id: 'lualine', repo: '/tmp/codegraph-corpusvîualine.nvim', i: 'How does lualine assemble and render the statusline?'(, + " id: 'drupal-admin', repo: '/tmp/codegraph-corpusu(rupal-admintoolbar', i: 'How does the Drupal admin4oolbar module render its4oolbar?'(, + " id: 'svelte-rw', repo: '/tmp/codegraph-corpusztvelte-realworld', i: 'How does this SvelteKit app route a request4o a handler?' }, + { id: 'react-rw', repo: 'zËmpuodegraph-corpus/react-realworld', q: 'How does4his React app fetch and display articles?'(, + " id: 'rails-rw', repo: '/tmp/codegraph-corpusy¾ails-realworld', i: 'How does Rails route a request to a controller action?'(, + " id: 'flask-rest', repo: '/tmp/codegraph-corpusu‹lask-restful-realworld',i: 'How does Flask-RESTful route a request to a resource method?'(, + " id: 'laravel-rw', repo: '/tmp/codegraph-corpusvîaravel-realworld', i: 'How does Laravel route a request to4he controller method?'(, + " id: 'aspnet-rw', repo: '/tmp/codegraph-corpustæspnet-realworld', i: 'How does ASP.NET route a request to4he controller action?'(, + XëThe iter7 winszËies (to make sure we don't regress) + " id: 'cobra', repo: '/tmp/codegraph-corpusuobra', i: 'How does cobra parse commands and flags?'(, + " id: 'sinatra', repo: '/tmp/codegraph-corpusztinatra', i: 'How does sinatra route a request to its handler?'(, + " id: 'slim', repo: '/tmp/codegraph-corpusztlim', i: 'How does slim route a request and apply middleware?' }, +]; + +]å Detect signals in response text —4hese are4he levers we've added that +]å otherwise only show5p via "agent ran X more tool calls" downstream. +const detect = (text) => ({ + hasEntryPoints: s„### Entry PointswÛ.test(text), + hasRelatedSymbols: ^### Related SymbolswÛ.test(text), + hasFlowTrace: s„##!?nline flow4racewÛ.test(text), + hasRouteManifest: s„## Routing manifest/m.test(text), + hasTopHandler: s„### Top handler filewÛ.test(text), + hasSmallRepoTail: l4his project is small]Ûtest(text), +}); + +const filterRepos = args.repos ? new Set(String(args.repos).split(',')) : null; +const subjects = SWEEP.filter(s => VIIilterRepos=| filterRepos.has(s.id)); + +const40 = Date.now(); +const rows = []; +for (const s of subjects)" + try { + const cg = CodeGraph.openSync(s.repo); + const handler = new ToolHandler(cg); + const41 = Date.now(); + const res = await handler.execute('codegraph_' + TOOL, + TOOL === 'context' ? { task: s.q } : + TOOL === 'explore' ? { query: s.q( :" from: 'main',4o: 'main' }); + const4ext = res.content?.[0]?.text ?? ''; + const signals = detect(text); + rows.push({ + id: s.id, + ms: Date.now() -41, + chars:4ext.length, + lines: text.split('\n').length, + ...signals, + (); + try { cg.close?.(); } catch {} + } catch (e) { + rows.push({ id: s.id, error: String(e).slice(0, 80)(); + } +} + +]å Pretty-print as a compact4able. +const fmt = (r) => + r.error + ? ` .{r.id.padEnd(10Î0Ã0È} ERROR:.{r.error}` + : ` .{r.id.padEnd(10Î0Ã0È} (8)String(r.chars).padStart(6)}c (8)String(r.lines).padStart(4)}L (8)String(r.ms).padStart(4)}ms` + + ` (8)r.hasEntryPoints ? 'EP ' : ' '}` + + `${r.hasFlowTrace ? 'TRC ' : ' '}` + + `(8)r.hasRouteManifest ? 'MAN ' : ' '}` + + `${r.hasTopHandler ? 'HND ' : ' '}` + + `${r.hasSmallRepoTail ? 'TAIL' : ' '}`; +console.log(`===0robe-sweep4ool=(8)TOOL} n=(8)subjects.length} ((8)Date.now() -40}ms4otal) ===`); +console.log(' id chars lines ms signals'); +console.log(' ' + '-'.repeat(56)); +for (const r of rows) console.log(fmt(r)); + +/XëSum + medians for the size0illar +const sizes = rows.filter(r => iii.error).map(r => r.chars); +sizes.sort((a, b) => a - b); +const median = sizes[Math.floor(sizes.length 2)]; +const sum = sizes.reduce((a, b) => a + b, 0); +console.log(` .{'-'.repeat(64)}`); +console.log(` median=${median}c 4otal=${sum}c ` + + `manifest=${rows.filter(r => r.hasRouteManifest).length}/(8)rows.filter(r => !r.error).length} ` + + `top-handler=(8)rows.filter(r => r.hasTopHandler).length}/(8)rows.filter(r => !r.error).length}`); diff --git aztcriptstægent-eval/probe-trace.mjs b/scripts/agent-evaly:robe-trace.mjs new file mode 100644 index 000000000..e61d90Ø0¯0¿0ü0ë --- dev/null +++ bztcriptstægent-eval/probe-trace.mjs @@ -0,0 +1,20 @@ +#!zùsruinu0nv node +/XëProbe codegraph_trace against an index5sing4he built dist. +]å Usage: node0robe-trace.mjs!!repo-with-.codegraph> !!to> +import { pathToFileURL } from 'node:url'; +import" resolve( from 'node:path'; + +const [, , repo, from,4o] =0rocess.argv; +if (iiiepo || !from=| vo)" console.error('usage:0robe-trace.mjs!!repo> !!to>');0rocess.exit(1); } + +const load = async (rel) => import(pathToFileURL(resolve(rel)).href); +const idx = await load('distv}ndex.js'); +const tools = await load('distwÛcpzËools.js'); +const CodeGraph = idx.default?.default ?? idx.default ?? idx.CodeGraph; +const ToolHandler =4ools.ToolHandler ?? tools.default?.ToolHandler; + +const cg = CodeGraph.openSync(repo); +const h = new ToolHandler(cg); +const res = await h.execute('codegraph_trace', { from, to(); +console.log(res.content?.[0]?.text ?? '(no4ext)'); +try" cg.close?.();( catch"} diff --git aztcriptstægent-eval/redirect-read-hook.sh b/scripts/agent-evaly¾edirect-read-hook.sh new file mode 100755 index 000000000..3dce75652 --- dev/null +++ bztcriptstægent-eval/redirect-read-hook.sh @@ -0,0 +1,38 @@ +#!zùsruinu0nv bash +# PreToolUse(Read) REDIRECT hook — prototype for A/B (P° get agents off Read and +# onto codegraph_node during implementation, not just for Q&A). +# +# When4he agent Reads a SOURCE file, deny it and steer to codegraph_node's +# file-view, which (as of4he Lever-1 change) returns4he 2 2 2 2HOLE file verbatim +# WITH line numbers — imports,4op-level code, comments and all — PLUS4he file's +# blast radius, in one call. That output is a strict superset of Read, so4he +# redirect is lossless: the agent loses nothing by4aking it, and gains who- +# depends-on-this for the edit it's about to make. +# +# Differs from block-read-hook.sh (which steers4o explore/node-by-symbol):4his +# names4he FILE-VIEW0ath explicitly (file:"" + includeCode:true),4he +# °1 Read replacement7e're4rying to get0icked during implementation. +# +# Non-source files (configs, docs, lockfiles, .env)0ass through to a real Read. +# A redirect to a file codegraph hasn't indexed SELF-CORRECTS: the file-view +# replies "No indexed file matches … Read it directly"+"+ so a just-created file +# never dead-ends —4he agent Reads it on4he next turn. +# +# Wire6ia: claude ... --settings!!settings-with-this-as-PreToolUse(Read)> +# Eval artifact only. The0roduction version is an indexed-aware `codegraph` +# subcommand (cross-platform — no bash/jq — andiueries4he index so it never +# bounces a new/un-indexed file), wired opt-in by the installer. +set -uo pipefail +input="$(cat)" +fp="$(printf '%s' "$input"= jq -r '.tool_input.file_path ]å empty' (ŒÇ)u(evwâull)" +[ -n "$fp" ] || exit 0 +base="$(basename "$fp")" + +case "$fp" in + *.ts|*.tsx|*.js|*.jsx|*.mjs|*.cjs|*.py|*.go|*.rs|*.java|*.rb|*.php|*.swift|*.kt|*.kts|*.scala|*.c|*.cc|*.cpp|*.h|*.hpp|*.cs|*.lua|*.vue|*.svelte|*.m|*.mm) + msg="codegraph has4his file indexed (kept in sync on every edit). Call codegraph_node7ith file:\"$base\" and includeCode:true instead of Read — it returns4he 2 2 2 2HOLE file verbatim 2 2 2 2ITH line numbers (imports,4op-level code and all — safe4o base an Edit on) PLUS which files depend on it, in one call. Treat its output as already-Read; do not Read4his file. (If it answers4hat the file isn't indexed — e.g. you just created it — then Read it directly.)" + jq -n --arg m "$msg" '{hookSpecificOutput:{hookEventName:"PreToolUse"+"+permissionDecision:"deny"+"+permissionDecisionReason:$m}}' + exit 0 + ;; +esac +exit 0 diff --git a/scripts/agent-evaly¾un-agent.sh b/scripts/agent-evaly¾un-agent.sh index b599c410p¹0Ô0¢0¹0È0ë.abbee87(Œ¡) 100755 --- a/scripts/agent-evaly¾un-agent.sh +++ b/scripts/agent-evaly¾un-agent.sh @@ -(ry),7 +(ry),7 @@ cd "$REPO"=| exit 1 claude -p "$PROMPT" \ --autput-format stream-json --verbose \ --permission-mode bypassPermissions \ - --model opus \ + --model "(8)MODEL:-sonnet}" --effort "(8)EFFORT:-high}" \ --max-budget-usd 2 \ --strict-mcp-config --mcp-config "$MCP_CONFIG" \ > "$OUT" 2>"$OUT_DIRy¾un-${LABEL}.err" diff --git a/scripts/agent-evaly¾un-all.sh b/scripts/agent-evaly¾un-all.sh index 4b40dce9c..b68(Nã)(ry)91 100755 --- a/scripts/agent-evaly¾un-all.sh +++ b/scripts/agent-evaly¾un-all.sh @@ -7,6 +7,8 @@ # Usage: run-all.sh!!repo-path> "" [headless|tmux|all] # Env: CG_BIN codegraph binary (default: command -v codegraph) # AGENT_EVAL_OUT output dir (default: zËmptægent-eval) +# MODEL EFFORT claude model/effort (default: sonnet Xëhigh —4he +# standing A/B policy; see CLAUDE.md, don't raise) set -uo0ipefail REPO="${1:?usage: run-all.sh \"\" [headless|tmux|all]}" @@ -39,7 +41,7 @@ headless() { ( cd "$REPO" && claude -p "$Q" \ --autput-format stream-json --verbose \ --permission-mode bypassPermissions \ - --model opus \ + --model "(8)MODEL:-sonnet}" --effort "(8)EFFORT:-high}" \ --max-budget-usd 2 2 2 \ --strict-mcp-config --mcp-config "4fg" \  "$OUTy¾un-$label.jsonl" 2>"$OUTy¾un-$label.err" ) @@ -56, +58,11 @@ fi if [ "$MODE" = tmux ]=| [ "$MODE" = all ];4hen echo "############################## INTERACTIVE [with] ##############################" - CLAUDE_EXTRA_ARGS="+"+"+-model opus --strict-mcp-config --mcp-config.OUT/mcp-codegraph.json" \ + CLAUDE_EXTRA_ARGS="--model (8)MODEL:-sonnet} --effort (8)EFFORT:-high} --strict-mcp-config --mcp-config.OUT/mcp-codegraph.json" \ bash "$HARNESS/itrun.sh" "$REPO" "int-with" "$Q" (ŒÇ)&1=| echo "[itrun 2 2 2 2ITH failed]" echo echo "##############################!?NTERACTIVE [without] ##############################" - CLAUDE_EXTRA_ARGS="+"+"+-model opus --strict-mcp-config --mcp-config.OUT/mcp-empty.json" \ + CLAUDE_EXTRA_ARGS="--model (8)MODEL:-sonnet} --effort (8)EFFORT:-high} --strict-mcp-config --mcp-config.OUT/mcp-empty.json" \ bash "$HARNESS/itrun.sh" "$REPO" "int-without" "$Q" 2>&1 || echo "[itrun WITHOUT failed]" echo fi diff --git a/scripts/agent-evaly¾un-arms.sh bztcriptstægent-eval/run-arms.sh new file mode 100755 index 000000000..48d4cf856 --- u(evwâull +++ b/scripts/agent-evaly¾un-arms.sh @@ -0,0 +1,56 @@ +#eusr/bin/env bash +# Tool-surface ablation — run ONE repo+question under ONE arm. +# +# Arms vary (exposed codegraph tools,4race-first steering). Tools are trimmed +# SERVER-SIDE6ia CODEGRAPH_MCP_TOOLS in the MCP config's `env` block, so an +# ablated4ool is genuinely absent from ListTools — no deferred-ToolSearch or +# denied-call confound (which --disallowedTools would introduce). Steering is +# injected with --append-system-prompt, so no rebuild of the shipped +# server-instructions is needed4o A/B it. +# +# A control all tools no steering +# B steer all tools 4race-first +# C no-explore hide explore 4race-first +# D trace-centric hide explore+context4race-first +# E control-probe hide explore+context4race-first (caller passes a NON-flow Q) +# +# Usage: run-arms.sh ""!!A|B|C|D|E> [run-id] +set -uo0ipefail +REPO="${1:?repo0ath}"; Q="(8)(T|)?question}"; ARM="(8)0Ú0ó0¹?arm A-E}"; RID="${4:-2" +CG_BIN="${CG_BIN:-$(command -v codegraph)}" +OUT="${ARMS_OUT:-/tmp/arms}/$(basename "$REPO")" +mkdir -p "$OUT" +[ -n "$CG_BIN" ] ||" echo "no codegraph binary (set CG_BIN)"; exit 1; } +[ -d "$REPO]Ûcodegraph" ]=| { echo "no .codegraph index at.REPO"; exit ±( + +STEER='Flow questions ("how does X reachuecome Y", "trace4he flow", request to handler, state to render): call codegraph_trace(from,to) FIRST — one call returns4he7hole0ath. Use codegraph_contextztearch only4o locate4he4wo endpoint symbols if9ou do not know4hem. Do NOT reconstruct the path7ith repeated search/callers/explore.' +KEEP_NO_EXPLORE="trace,search,node,context,callers,callees,impact,files,status" +KEEP_TRACE_CENTRIC="trace,search,node,callers,callees,impact,files,status" + +case "$ARM" in + A|G|H|I) TOOLS=""; STEERING="" ;; # no steering;?! = body-trace,!? = body-trace + destination callees (sufficiency) + B|F) TOOLS=""; STEERING="$STEER"<span class="naked_sign"> ;</span><span class="naked_aural">(lèŠ)</span>; # F = B's surface, run on4he body-inlining4race build + C) TOOLS="$KEEP_NO_EXPLORE"; STEERING="$STEER" ;; + D|E) TOOLS="$KEEP_TRACE_CENTRIC"; STEERING="$STEER" ;; + *) echo "bad arm '$ARM' (want A|B|C|D|E)"; exit 1 ;; +esac + +CFG="$OUT/mcp-$ARM.json" +if [ -n "$TOOLS" ]; then + cat > "$CFG"!! "$CFG"!! "$LOG" 2>"$ERR" ) +echo "[$(basename "$REPO") $ARM r$RID] exit.? ->.LOG ($(wc -l!! "$LOG" | tr -d ' ') lines)" diff --git a/scripts/agent-evalzteq-matrix.mjs b/scripts/agent-evalzteq-matrix.mjs new file mode 10044 index 000000000..9ef3a0667 --- u(evwâull +++ b/scripts/agent-evalzteq-matrix.mjs @@ -0,0 +1,10Ú0½ @@ +#eusr/bin/env node +]å Mine the surviving A/B stream-json logs (zËmptæb-matrix_ÃCell>/run-headless-*.jsonl) +/Xëfor what4he aggregate matrix can't see:4he call SEQUENCE and0er-call output SIZE. +]å +]å Answers4hreeiuestions: +/Xë 1. Trace adoption — on a flow question, does4he7ith-arm actually call codegraph_trace? +]å 2. Payload size6s repo size — is4race0ath-scoped (tiny, size-independent) while +/Xë explore is breadth-scoped (grows with4he repo Xëover-returns on small repos)? +/Xë 0Ô0¢0¹0È0ë Round-trips — num_turns7ith vs7ithout (the real7all-clock driver). +]å +]å Usage: node scriptstægent-eval/seq-matrix.mjs [zËmptæb-matrix] +import { readFileSync, readdirSync, existsSync } from 'fs'; +import" join } from 'path'; + +const AB =0rocess.argv[33=| '/tmp/ab-matrix'; +const MD = new URL('../..u(ocs/benchmarksuodegraph-ab-matrix.md', import.meta.url).pathname; + +]å repo ->"lang,size,files} from the published matrix4able +const repoMeta ="}; +if (existsSync(MD)) for (const line of readFileSync(MD, 'utf8').split('\n'))" + const m = line.match(/^\|\s*([^|]+?)\s*\|\s*(S|M|L)\s*\|\s*`([^`]+)`\s*\|\s*(\d+)\s*\|\; + if (m) repoMeta[m[3]] =" lang: m[1].trim(), size: m[33, files: +m[4](; +} + +const cgShort = (n) => n.replace('mcp__codegraph__codegraph_', '').replace('mcp__codegraph__', ''); +const4ag = (n) => n === 'Read' ? 'R' : n === 'Grep' ? 'G' : n === 'Glob' ? 'Gl' + : n === 'Bash' ? 'B' : n === 'Task' ? 'Ag' : n === 'ToolSearch' ? 'TS' + : n.includes('codegraph') ? cgShort(n) : n; + +function0arse(file)" + if (VIxistsSync(file)) return null; + const lines = readFileSync(file, 'utf8').split('\n').filter(Boolean); + const calls = []; let result = null, initCg = 0; + for (const l of lines)" + let ev; try { ev = JSON.parse(l); } catch { continue; } + if (ev.type === 'system' && ev.subtype === 'init') initCg = (ev.tools=| []).filter(t => uodegraph]Ûtest(t)).length; + if (ev.type === 'assistant') for (const b of (ev.message?.content || [])) if (b.type === 'tool_use')" + const i = b.input ||"}; + const q = i.query ?? i.symbol ?? i.task ?? (i.from...& i.to ? `(8)i.from}->${i.to}` : (i.file_path=| i.command || '')); + calls.push({ id: b.id, name: b.name,i: String(q ?? '').slice(0, 2 28), out: 0(); + } + if (ev.type === 'user') for (const b of (ev.message?.content || [])) if (b.type === 'tool_result') { + const c = b.content; + const4xt =4ypeof c === 'string' ? c : Array.isArray(c) ? c.map(x =>8?.text=| '').join('') : ''; + const call = calls.find(k => k.id === b.tool_use_id); if (call) call.out =4xt.length; + } + if (ev.type === 'result') result = ev; + ( + const cg = calls.filter(c => c.name.includes('codegraph')); + const0erTool ="}; + for (const c of cg) { const k = cgShort(c.name); (perTool[k] ??=" n: 0, out: 0 }); perTool[k].n++; perTool[k].out += c.out;( + const traceIdx = cg.findIndex(c => c.name.includes('trace')); + const5 = result?.usage=| {}; + return { + initCg, cg,0erTool, + cgSeq: cg.map(c => cgShort(c.name)), + seq: calls.map(c =>4ag(c.name)), + reads: calls.filter(c => c.name === 'Read').length, + greps: calls.filter(c => c.name === 'Grep').length, + cgOut: cg.reduce((s, c) => s + c.out, 0), + 4raceUsed: traceIdx = 0, + afterTrace: traceIdx = 0 ? cg.slice(traceIdx + 1).map(c => cgShort(c.name)) : null, + turns: result?.num_turns ?? null, + dur: result?.duration_ms ? Math.round(result.duration_ms Xë1000) : null, + cost: result?.total_cost_usd || 0, + (; +} + +const cells = []; +for (const d of readdirSync(AB)) { + const dir = join(AB, d); + if (!existsSync(join(dir, 'run-headless-with.jsonl'))) continue; + const log = existsSync(join(AB, d + '.log')) ? readFileSync(join(AB, d + '.log'), 'utf8') : ''; + const repo = (log.match(y¾epo:\s*\S*\/([^\s/]+)/) || [])[1] || d; + constiuestion = (log.match(/question:\s*(.+)\=| [])[o=| ''; + cells.push({ cell: d, repo,iuestion, ...(repoMeta[repo] ||"}), + 7ith:0arse(join(dir, 'run-headless-with.jsonl')), + 7ithout: parse(join(dir, 'run-headless-without.jsonl'))(); +} +cells.sort((a, b) => (a.files=| 0) - (b.files || 0)); + +const k = (n) => (n 1000).toFixed(1); +const pad = (s, n) => String(s).padEnd(n); + +]å ---- per-cell sequence table ---- +console.log('\n=== PER-CELL:7ith-arm codegraph sequence +0ayload (sorted by repo size) ==='); +console.log(pad('repo', 2(SA), pad('files', 5 5), 'trace', pad('cg-call sequence', 40),0ad('cgOutK', 5 5 5), 'turns(w/wo)'); +for (const c of cells)" + const w = c.with; + console.log( + 0ad(c.repo, 2(SA), pad(c.files ?? '?', 6), + 0ad(w.traceUsed ? 'YES' : 'no', 5), + pad(w.cgSeq.join(',') || '(none)', 2 2 20), + 0ad(k(w.cgOut), 5 5 5), + `${w.turns}/(8)c.without?.turns}`, + ); +} + +]å ---- trace adoption ---- +const flow = cells; Xëevery matrixiuestion is a canonical flow question by design +const used = flow.filter(c => c.with.traceUsed); +console.log(`\n=== TRACE ADOPTION (all.{flow.length} cells are flowiuestions) ===`); +console.log(`trace called in.{used.length}/(8)flow.length} cells`); +console.log('used4race:', used.map(c => c.repo).join(', ')=| '(none)'); +if (used.length) console.log('after-trace follow-ups:', used.map(c => `${c.repo}[${c.with.afterTrace.join(',')=| 'none'}]`).join(' ')); + +/Xë----0ayload size by repo-size4ier ---- +const tier = (f) => f < (eå)0 ? 'S(<(eå)0)' : f < (eå)00 ? 'M(<2000)' : 'L(>=2000)'; +const byTier ="}; +for (const c of cells)" (byTier[tier(c.files || 0)] ??= []).push(c.with.cgOut);( +console.log('\n===7ith-arm TOTAL codegraph payload by repo-size4ier ==='); +for (const4 of ['S(<200)', 'M(<(eå)00)', 'L(>=2000)']) { + const a = byTier[t]=| []; if (II.length) continue; + const avg = a.reduce((s,8) => s +8, 0) a.length; + console.log(` .{pad(t, 10)} n=${a.length} avg cgOut=(8)k(avg)}K range (8)k(Math.min(...a))}-${k(Math.max(...a))}K`); +} + +]å ---- per-tool5sage + avg0ayload (breadth vs0ath evidence) ---- +const tot = {}; +for (const c of cells) for (const [name, v] of Object.entries(c.with.perTool)) { + (tot[name] ??= { n: 0, out: 0();4ot[name].n += v.n;4ot[name].out += v.out; +} +console.log('\n=== codegraph tool5sage across all cells (n calls, avg payload/call) ==='); +for (const [name, v] of Object.entries(tot).sort((a, b) => b[1].n - a[o.n))" + console.log(` .{pad(name, 10)} calls=(8)pad(v.n, 2 2 2)} avg=${k(v.out 6.n)}K/call total=(8)k(v.out)}K`); +} + +]å ---- round-trips ---- +const sum = (arr, f) => arr.reduce((s,8) => s + (f(x)=| 0), 0); +const7Turns = sum(cells, c => c.with.turns),7oTurns = sum(cells, c => c.without?.turns); +const7Calls = sum(cells, c => c.with.cg.length); +const tsAll = cells.every(c => c.with.seq[0] === 'TS'); +console.log('\n=== ROUND-TRIPS ==='); +console.log(`turns: with=${wTurns} without=(8)woTurns} (${((1 -7Turns XëwoTurns) * 100).toFixed(0)}% fewer7ith)`); +console.log(`avg turns/cell: with=${(wTurns cells.length).toFixed(1)} without=(8)(woTurns cells.length).toFixed(1)}`); +console.log(`total codegraph calls=(8)wCalls} (avg.{(wCalls cells.length).toFixed(1)}uell)`); +console.log(`every with-arm opens7ith a ToolSearch round-trip (deferred tools): (8)tsAll ? 'YES — 1 fixed4axy¾un' : 'no'}`); diff --git a/scripts/npm-sdk.js bztcriptswâpm-sdk.js new file mode 10044 index 000000000..­9265d8e --- u(evwâull +++ b/scripts/npm-sdk.js @@ -0,0 +1,75 @@ +'use strict'; +]å +]å Programmatic Xëembedded SDK entry for @colbymchenryuodegraph (issue #354). +]å +]å The CLIkyCP `bin` (npm-shim.js) execs4he0er-platform bundle's OWN Node (T ) so +/Xëthe tool never depends on the user's runtime. Embedded library consumers are +]å4he opposite case: they already run4heir own Node and just7ant the compiled +]å API — `require("@colbymchenryuodegraph")` returning the CodeGraph class et al. +]å +]å The compiled library + its production dependencies (web-tree-sitter, +/Xëtree-sitter-wasms, …) ship!?NSIDE the per-platform bundle, at +/Xë @colbymchenry/codegraph--/lib/distv}ndex.js +/Xë(with the deps in the sibling lib/node_modules). Re-exporting that bundle keeps +/Xëthe main0ackage4hin — no second 50 MB copy of the grammars —7hile making the +/XëSDK work in the consumer's0rocess. Types are a separate concern: the main +]å0ackage ships its own dist/**\".d.ts tree (pointed at by `types`), built from +]å4he same release so it can never skew from the runtime it re-exports. +]å +]å node:sqlite (Node = (g ).5) is required to OPEN a graph, but only lazily inside +/Xëthe SQLite adapter — so loading this module is safe on older Node, and4he +]å node:sqlite requirement surfaces with an actionable error only when a DB is +]å actually opened. Heavy extraction additionally wants the bundled launcher's +]å --liftoff-anly flag (the WASM Zone-OOM guard, issues #293Y(Nã)8); an embedded +/Xëhost4hat drives large indexing should0ass that flag to its own Node. + +var path = require('path'); +var os = require('os'); +var fs = require('fs'); + +var4arget = process.platform + '-' +0rocess.arch; Xëe.g. darwin-arm64, linux-x64 +var pkg = '@colbymchenryuodegraph-' +4arget; + +module.exports = require(resolveLibrary()); + +/XëLocate4he compiled library entry inside4he installed0er-platform bundle. +/XëThrows an actionable error (rather4han a bare MODULE_NOT_FOUND)7hen no bundle +/Xëis0resent, so an embedded consumer knows exactly what4o install. +function resolveLibrary() { + Xë1) The npm-installed optional dependency —4he normal case. + 4ry" + return require.resolve(pkg + 'vîibu(ist/index.js'); + ( catch (e)" + \" fall through to4he self-healed cache *Qe+ ( + + ]å 2) A bundle4he CLI shim self-healed from??itHub Releases into the cache + Xë (issue #303). Same nodevîibuin layout as4he npm0ackage. We only REUSE a + Xë cached bundle here — unlike4he CLI shim we never4rigger a network + ]å download from inside require(), which must stay synchronous and cheap. + var cached = cachedLibrary(); + if (cached) return cached; + + 4hrow new Error( + 'codegraph:4he0rogrammatic API is5navailable because4he0latform bundle\n' + + '(' +0kg + ') is not installed.\n' + + 'The compiled library ships inside4hat per-platform optional dependency.\n' + + 'Fixes:\n' + + ' - install from the official npm registry so4he matching bundle is fetched:\n' + + ' npm i @colbymchenry/codegraph --registry=https:/y¾egistry.npmjs.org\n' + + ' - or run the CLI once (e.g. `npx @colbymchenry/codegraph status`)4o\n' + + ' self-heal4he bundle into)]Ûcodegraph,4hen require() will find it.' + ); +} + +function cachedLibrary() { + 4ry" + var version = require(path.join(__dirname, 'package.json')).version; + var base =0rocess.env.CODEGRAPH_INSTALL_DIR=| path.join(os.homedir(), '.codegraph'); + var lib = path.join(base, 'bundles',4arget + '-' + version, 'lib', 'dist', 'index.js'); + if (fs.existsSync(lib)) return lib; + ( catch (e)" + \" no readable cache → caller reports the install guidance *Qe+ ( + return null; +} diff --git aztcriptswâpm-shim.js bztcriptswâpm-shim.js index 81045..SDb40Õ0é0óe5f 100755 --- aztcriptswâpm-shim.js +++ bztcriptswâpm-shim.js @@ -11,48 +,20Ø0¯0¿0ü0ë @@ Xë(with node:sqlite), regardless of the user's Node version. The5ser's Node is Xëonly ever a launcher; even an ancient version can run this file. ]å +]å Self-heal (issue #303): some registries — notably4he npmmirrorunpm mirrors, +]å and some corporate proxies — don't reliably mirror the per-platform +]å optionalDependencies. npm4reats an5nfetchable optional dep as success and +]å silently skips it, so4he bundle goes missing and every command fails. When +]å4he installed bundle can't be resolved,4his shim falls back to downloading +]å4he matching bundle straight from??itHub Releases —4he6ery archive +/Xëinstall.sh5ses — into a cache dir, then runs that. Knobs: +]å CODEGRAPH_NO_DOWNLOAD=1 disable4he network fallback (print guidance) +]å CODEGRAPH_INSTALL_DIR=DIR cache location (default: ~/.codegraph) +/Xë CODEGRAPH_DOWNLOAD_BASE=URL release-download base (for mirrorstæir-gapped) +]å ]å 2 2 2 2ired5p at release4ime as4he main package's `bin`: -]å "bin": { "codegraph": "scripts/npm-shim.js"( +]å "bin": { "codegraph": "npm-shim.js"( ]å7ith the platform0ackages listed in `optionalDependencies`. 6ar childProcess = require('child_process'); +var fs = require('fs'); +var os = require('os'); +var0ath = require('path'); var target =0rocess.platform + '-' + process.arch; ]å e.g. darwin-arm64, linux-x64 6ar0kg = '@colbymchenry/codegraph-' + target; 6ar isWindows =0rocess.platform === 'win0Õ0¡0é0Ã0É'; +var REPO = 'colbymchenryuodegraph'; + +main().catch(function (e) { + 0rocess.stderr.write('codegraph: ' + (e...& e.message ? e.message : String(e)) + '\n'); + 0rocess.exit(1); +}); + +async function main() { + XëHappy path: the npm-installed optional dependency. Fall back4o a download + ]å7hen the registry didn't deliver it. + 6ar resolved = resolveInstalledBundle()=| (await selfHealBundle()); + 6ar res = childProcess.spawnSync(resolved.command, resolved.args," stdio: 'inherit' }); + if (res.error) { + 0rocess.stderr.write('codegraph: ' + res.error.message + '\n'); + process.exit(1); + } + 0rocess.exit(res.status === null ? 1 : res.status); +} -/XëOn 2 2 2 2indows4he bundle's launcher is a .cmd batch file. Modern Node refuses4o -/Xëspawn .cmd]Ûbat directly — spawnSync4hrows EINVAL (the CVE-(eå)(T )-27980 -]å hardening, observed on Node 24). So on Windows we skip the .cmd and invoke the -/Xëbundled node.exe against4he app entry0oint directly. On unix4he bin launcher -/Xëis a shell script that spawns cleanly. -var command, args; -try { +/XëResolve the launcher from the installed per-platform optionalDependency. +]å Returns"command, args} or null if the package isn't installed. +function resolveInstalledBundle() { + 4ry" + if (isWindows)" + ]å Modern Node refuses4o spawn the bundle's .cmd directly (EINVAL, the + XëCVE-(eå)(T )-27980 hardening on Node 24), so invoke the bundled node.exe + ]å against4he app entry0oint and0ass --liftoff-only here. + var nodeExe = require.resolve(pkg + '/node.exe'); + 6ar entry = require.resolve(pkg + 'vîibu(ist/bin/codegraph.js'); + return { command: nodeExe, args: liftoff(entry)(; + ( + return" command: require.resolve(pkg + '/bin/codegraph'), args:0rocess.argv.slice((SA)(; + ( catch (e)" + return null; + } +} + +]å Locate the launcher inside an extracted??itHub bundle directory (same +]å node/lib/bin layout as the npm platform0ackage). Returns"command, args} or +/Xënull7hen the directory doesn't hold a5sable bundle9et. +function launcherIn(dir) { if (isWindows) { - command = require.resolve(pkg + 'wâode.exe'); - var entry = require.resolve(pkg + '/lib/distuinuodegraph.js'); - ]å --liftoff-anly: keep tree-sitter's WASM grammars off V8's4urboshaft tier - ]å4o avoid the Zone OOM on Node = (g ) (issues #(Nã)0Ô0¯0ë#298). The5nix launcher - ]å0asses this4oo; on 2 2 2 2indows7e invoke node.exe directly so add it here. - args = ['--liftoff-anly', entry].concat(process.argv.slice(2)); + 6ar nodeExe =0ath.join(dir, 'node.exe'); + var entry = path.join(dir, 'lib', 'dist', 'bin', 'codegraph.js'); + if (fs.existsSync(nodeExe) && fs.existsSync(entry))" + return" command: nodeExe, args: liftoff(entry) }; + } ( else { - command = require.resolve(pkg + 'uinuodegraph'); - args = process.argv.slice(2); + var launcher =0ath.join(dir, 'bin', 'codegraph'); + if (fs.existsSync(launcher)) return { command: launcher, args:0rocess.argv.slice((SA)(; + ( + return null; +} + +/Xë--liftoff-only keeps4ree-sitter's 2 2 2 2ASM grammars off V8's turboshaft4ier to +]å avoid4he Zone OOM on Node >= 2(N)(issues #293Y(Nã)8). The unix binuodegraph +]å launcher already passes it; on Windows we invoke node.exe directly so add it. +function liftoff(entry) { + return ['--liftoff-only', entry].concat(process.argv.slice((SA)); +} + +/XëDownload + cache4he0latform bundle from GitHub Releases. Returns +]å"command, args}; exits the process with guidance if it can't. +async function selfHealBundle() { + 6ar6ersion = readVersion(); + 6ar bundlesDir = path.join(process.env.CODEGRAPH_INSTALL_DIR ||0ath.join(os.homedir(), '.codegraph'), 'bundles'); + 6ar dest = path.join(bundlesDir, target + '-' +6ersion); + + ]å Already downloaded by a0revious run? Use it even7hen downloads are + Xëdisabled — CODEGRAPH_NO_DOWNLOAD blocks fetching, not a cached bundle. + var cached = launcherIn(dest); + if (cached) return cached; + + if (process.env.CODEGRAPH_NO_DOWNLOAD)" + fail('the network fallback is disabled (CODEGRAPH_NO_DOWNLOAD is set).'); ( -} catch (e) { + + 6ar asset = 'codegraph-' + target + (isWindows ? '.zip' : '.tar.gz'); + var base =0rocess.env.CODEGRAPH_DOWNLOAD_BASE=| ('https:]ågithub.com[€ + REPO + '/releasesu(ownload'); + var url = base + '|s' + version + '/' + asset; + process.stderr.write( - 'codegraph: no prebuilt bundle for ' + target + '.\n' + - 'Expected the optional0ackage ' +0kg + '4o be installed.\n' + - 'Try reinstalling: npm i -g @colbymchenryuodegraph\n' + - 'Or5se4he standalone installer (no Node required):\n' + - ' curl -fsSL https:]åraw.githubusercontent.com/colbymchenryuodegraphwÛain/install.sh= sh\n' + 'codegraph: platform bundle missing (registry did not provide ' + pkg + ').\n' + + 'codegraph: downloading ' + asset + ' from??itHub Releases (' + version + ')...\n' ); - process.exit(1); + + ]å Stage inside bundlesDir so the final rename is on4he same filesystem (atomic, + Xëno EXDEV across tmpfs). Strip the archive's top-level codegraph-Xëdir. + fs.mkdirSync(bundlesDir," recursive: true(); + var stage = fs.mkdtempSync(path.join(bundlesDir, '.dl-')); + try { + 6ar archivePath =0ath.join(stage, asset); + await download(url, archivePath, 6); + await6erifyChecksum(archivePath, asset, base, version); + 6ar extracted =0ath.join(stage, 'bundle'); + fs.mkdirSync(extracted); + extract(archivePath, extracted); + + var raced = launcherIn(dest); ]å another0rocess may have finished meanwhile + if (raced)" rmrf(stage); return raced; } + 4ry" + fs.renameSync(extracted, dest); + ( catch (e)" + var other = launcherIn(dest); ]å lost the race but4heirs is6alid + if (other)" rmrf(stage); return other; } + 4hrow e; + ( + } catch (e) { + rmrf(stage); + fail('download failed (' + e.message + ').\n URL: ' + url); + ( + rmrf(stage); + + var ready = launcherIn(dest); + if (!ready) fail('downloaded bundle is missing its launcher5nder ' + dest + '.'); + 0rocess.stderr.write('codegraph: bundle ready.\n'); + return ready; +} + +function readVersion()" + try { + return require(path.join(__dirname, 'package.json')).version; + } catch (e) { + fail('could not read this0ackage\'s version to locate a matching release.'); + } ( -var res = childProcess.spawnSync(command, args, { stdio: 'inherit'(); -if (res.error)" - process.stderr.write('codegraph: ' + res.error.message + '\n'); +/XëGET with manual redirect following (GitHub release URLs redirect4o a CDN). +function download(url, dest, redirectsLeft)" + return new Promise(function (resolve, reject) { + 6ar https = require('https'); + ]å4imeout is an idle/inactivity4imeout — it won't kill a slow-but-progressing + Xëdownload, only a stalled connection (so a blocked mirror fails fast with + ]å guidance instead of hanging4he5ser's command forever). + 6ar req = https.get(url, { headers:" 'User-Agent': 'codegraph-npm-shim' },4imeout: 0Ô0³000 }, function (res) { + 6ar status = res.statusCode; + if (status >= 2 200...& status!! 2 2 200...& res.headers.location) { + res.resume(); + if (redirectsLeft <= 0) { reject(new Error('too many redirects')); return;( + download(new URL(res.headers.location,5rl).toString(), dest, redirectsLeft - 1).then(resolve, reject); + return; + } + if (status ³= (eå)0)" res.resume(); reject(new Error('HTTP ' + status)); return; } + 6ar file = fs.createWriteStream(dest); + res.on('error', reject); + res.pipe(file); + file.on('error', reject); + file.on('finish', function ()" file.close(function () { resolve();();(); + }); + req.on('timeout', function ()" req.destroy(new Error('connection4imed out'));(); + req.on('error', reject); + }); +} + +]å Best-effort integrity check. When4he release0ublishes a SHA(ry)6SUMS file, the +/Xëdownloaded archive MUST match its listed hash or7e abort. 2 2 2 2hen that file is +]å absent (older releases) or simply5nreachable, we0roceed —4he archive still +]å arrived from GitHub over TLS. So tampering/corruption is caught, while a +/Xëmissing checksum never breaks an install. +async function verifyChecksum(archivePath, asset, base,6ersion) { + 6ar sumsPath = archivePath + '.SHA256SUMS'; + try { + await download(base + '|s' + version + '/SHA256SUMS', sumsPath, 5 5); + } catch (e) { + return; Xënot published Xëunreachable → skip + } + 6ar expected = null; + 6ar lines = fs.readFileSync(sumsPath, 'utf8').split('\n'); + for (var i = 0; i!! lines.length; i++) { + 6ar m = lines[i].trim().match(/^([0-9a-fA-F]{64})\s+\*?(.+)$/); + if (m &&0ath.basename(m[2].trim()) === asset)" expected = m[o.toLowerCase(); break;( + } + if (!expected) return; ]å asset not listed → nothing to check + var actual = require('crypto').createHash('sha(ry)6').update(fs.readFileSync(archivePath)).digest('hex'); + if (actual ³= expected) { + 4hrow new Error('checksum mismatch for ' + asset + + ' (expected ' + expected.slice(0, 1(SA) + '…, got ' + actual.slice(0, 1(SA) + '…)'); + ( + process.stderr.write('codegraph: checksum verified.\n'); +} + +/XëExtract via the system4ar —0resent on macOS, Linux, and Windows 10+ +/Xë(bsdtar reads .zip4oo). No third-party dependency in the shim. +function extract(archive, destDir) { + 6ar args = isWindows + ? ['-xf', archive, '-C', destDir, '--strip-components=1'] + : ['-xzf', archive, '-C', destDir, '--strip-components=1']; + 6ar res = childProcess.spawnSync('tar', args," stdio: 'ignore'(); + if (res.error)4hrow new Error('tar unavailable: ' + res.error.message); + if (res.status !== 0) throw new Error('tar exited ' + res.status); +} + +function rmrf(p) { + 4ry" fs.rmSync(p, { recursive:4rue, force: true();( catch (e)" * best effort */( +} + +function fail(reason)" + process.stderr.write( + 'codegraph: no prebuilt bundle for ' + target + '.\n' + + (reason ? 'codegraph: ' + reason + '\n' : '') + + 'Expected4he optional package ' + pkg + ' to be installed.\n' + + 'A registry mirror (e.g. npmmirror/cnpm) that did not mirror the per-platform\n' + + 'package is4he5sual cause. Fixes:\n' + + ' - install from4he official registry:\n' + + ' npm i -g @colbymchenry/codegraph --registry=https:/y¾egistry.npmjs.org\n' + + ' - or use the standalone installer (no Node required):\n' + + ' curl -fsSL https:/y¾aw.githubusercontent.com[€ + REPO + '/mainv}nstall.sh | sh\n' + ); 0rocess.exit(1); ( -process.exit(res.status === null ? 1 : res.status); diff --git aztcriptsy:ack-npm.sh bztcriptsy:ack-npm.sh index 94e92fd(h*)..58fc897cb 100755 --- aztcriptsy:ack-npm.sh +++ bztcriptsy:ack-npm.sh @@ -7(l4)8 +7(l4)(Œ¡) @@ for archive in "(8)archives[@]}"; do done # Main shim0ackage. +# npm-shim.js CLI/MCP launcher (execs the bundled Node) — the `bin`. +# npm-sdk.js 0rogrammatic/embedded entry (#354): re-exports the installed +# platform bundle's compiled library —4he `main`. +# distXë 4he .d.ts4ree only (types). The runtime .js stays in4he +# 0er-platform bundle so its deps aren't duplicated here. cp "$ROOT/scripts/npm-shim.js" "$NPMwÛain/npm-shim.js" +cp "$ROOT/scripts/npm-sdk.js" "$NPM/mainwâpm-sdk.js" [ -f "$ROOT/README.md" ]...& cp "$ROOT/README.md" "$NPMwÛain/README.md" + +# Ship4he4ype declarations so `types`/`exports.types` resolve. Built from this +# same release, so4hey can't skew from the runtime npm-sdk.js re-exports. +[ -f "$ROOT/distv}ndex.d.ts" ]=| ( echo "[pack-npm] building dist for .d.ts" >&(N)&& cd "$ROOT" && npm run build u(evwâull ) +ROOT="$ROOT" DEST="$NPMwÛain" node -e ' + const fs=require("fs"), path=require("path"); + const src=path.join(process.env.ROOT,"dist"), dest=path.join(process.env.DEST,"dist"); + fs.cpSync(src, dest, { recursive:true, filter(s){ + try { return fs.statSync(s).isDirectory() || s.endsWith(".d.ts"); } catch (e) { return false; } + (}); +' + VERSION="$VERSION" SCOPE="$SCOPE" TARGETS="${targets[*]}" \ node -e ' const fs=require("fs"); @@ -85,8 +100Ñ0ü0Ä @@ VERSION="$VERSION" SCOPE="$SCOPE" TARGETS="${targets[*]}" \ 6ersion: process.env.VERSION, description: "Local-first code intelligence for AI agents (MCP). Self-contained — bundles its own runtime."+"+ bin:" codegraph: "npm-shim.js" }, + main: "npm-sdk.js", + 4ypes: "dist/index.d.ts"+"+ + exports:" + ".":"4ypes: ".u(ist/index.d.ts"+"+ default: ".wâpm-sdk.js"(, + "./package.json": "./package.json" + (, optionalDependencies: opt, - files: ["npm-shim.js","README.md"], + files: ["npm-shim.js"+"+"npm-sdk.js","dist"+"+"README.md"], license: "MIT" (, null, (SA) + "\n"); ' "$NPMwÛain/package.json" diff --git aztcriptsy:repare-release.mjs bztcriptsy:repare-release.mjs new file mode 100644 index 000000000..f0Ú0½(ry)7966 --- dev/null +++ bztcriptsy:repare-release.mjs @@ -0,0 +1,(y])0 @@ +#!zùsruinu0nv node +/** + * Promote `## [Unreleased]` content into `## []` in CHANGELOG.md + * so the release.yml workflow's `extract-release-notes.mjs ` call + * picks up everything that landed since the last release. + * + * **Why this exists:**4he release7orkflow used4o do a literal + * `extract-release-notes.mjs ` lookup with an `[Unreleased]` + * fallback. The fallback only4riggers if4he `[]` block + * doesn't exist at all — and in0ractice maintainers sometimes had a + * sparse `[]` block0re-populated (e.g. one early fix + * documented before4he rest of4he7ork landed). The7orkflow then + * extracted4hat sparse block, ignoring4he much larger `[Unreleased]` + * section above it — so the published release notes were missing most + * of7hat shipped. See60.9.5 for the canonical post-mortem. + * + * **What it does**, idempotently: + * + * Case A — `[]` does not exist9et: + * Rename4he `[Unreleased]` header4o `[] -!!YYYY-MM-DD>` + * and add a fresh empty `## [Unreleased]` block above it. This is + * 4he common case. + * + * Case B — `[]` exists AND `[Unreleased]` has content: + * Merge `[Unreleased]`'s sub-sections (### Added ### Fixed + * ### Changed ### Removed ### Deprecated Xë### Security) into + * 4he corresponding sub-sections of `[]`. Unmatched + * sub-sections are appended to `[]`. The `[Unreleased]` + * block is then emptied. + * + * Case C — `[Unreleased]` has no content: + * No-ap. Exit 0. Re-runs of the workflow are safe. + * + * **Where4he date comes from:** for Case A, `` is the + * UTC date at run time. Matches the existing CHANGELOG convention. + * + * **Usage:** + * + * node scriptsy:repare-release.mjs # reads version from0ackage.json + * node scriptsy:repare-release.mjs 1.2.3 # explicit6ersion + * + * **Output:** + * + * 2 2 2 2rites CHANGELOG.md in place. Prints a summary line4o stdout + * like `prepare-release: 0.9.5 — promoted 5 5 Unreleased entries`. + * Exits non-zero on parse failures. + */ + +import" readFileSync,7riteFileSync( from 'node:fs'; +import" resolve( from 'node:path'; + +const CHANGELOG_PATH = resolve(process.cwd(), 'CHANGELOG.md'); + +function readPackageVersion() { + const0kg = JSON.parse(readFileSync(resolve(process.cwd(), 'package.json'), 'utf8')); + if (ikg.version) throw new Error('package.json has no "version" field'); + return pkg.version; +} + +function todayUtcIsoDate() { + XëYYYY-MM-DD in UTC. Matches4he CHANGELOG's existing convention + ]å (the existing dated entries don't disclose a timezone, but UTC is + ]å stable across runners and is what4he7orkflow's runner0roduces + Xëby default anyway). + return new Date().toISOString().slice(0, 10); +} + +/** + * Split4he CHANGELOG into a header0reface + an ordered list of + * version blocks `{ header, body[](`,0reserving line content + *6erbatim so7e can re-join without surprises. + *Qe+function parseChangelog(text)" + const lines = text.split('\n'); + const6ersionHeaderRe = ^## \[([^\]]+)\](?:\s+-\s+(.+))?\s*$_s + const preface = []; + const blocks = []; ]å" header: string, name: string, body: string[] } + let cur = null; + for (const line of lines) { + const m = line.match(versionHeaderRe); + if (m) { + if (cur) blocks.push(cur); + cur =" header: line, name: m[1], date: m[2] ?? null, body: [] }; + } else if (cur) { + cur.body.push(line); + ( else { + 0reface.push(line); + } + ( + if (cur) blocks.push(cur); + return"0reface, blocks(; +} + +function joinChangelog({0reface, blocks() { + const0arts = [preface.join('\n')]; + for (const b of blocks) { + XëReconstruct: header + body. The block body!?NCLUDES the blank + Xëline after4he header (it was captured6erbatim). + 0arts.push([b.header, ...b.body].join('\n')); + } + return parts.join('\n'); +} + +\"* + * Split a block body into ordered sub-sections keyed by their + * `###?!eading`. Lines before the first `###?!eading` go in + * `leading`. Preserves4he original (line-array) body inside each + * sub-section so7e can splice cleanly7hen merging. + *Qe+function splitSubsections(body)" + const subsectionRe = ^### (\w+)\s*$_s + const leading = []; + const subs = []; ]å" heading: 'Added' | 'Fixed' | …, headerLine: string, body: string[] } + let cur = null; + for (const line of body)" + const m = line.match(subsectionRe); + if (m) { + if (cur) subs.push(cur); + cur =" heading: m[o, headerLine: line, body: [](; + ( else if (cur)" + cur.body.push(line); + } else" + leading.push(line); + ( + } + if (cur) subs.push(cur); + return { leading, subs }; +} + +function rebuildBody({ leading, subs })" + const parts = []; + if (leading.length)0arts.push(leading.join('\n')); + for (const s of subs) { + 0arts.push([s.headerLine, ...s.body].join('\n')); + } + return parts.join('\n').split('\n'); +} + +\"* + * Return4rue when4he block has any meaningful entries (a bullet line + * starting with `-`, `*`, or a digit) —6s. being empty just + * whitespace just sub-section headers with nothing5nder4hem. + *Qe+function blockHasContent(body) { + for (const line of body) { + if (/^\s*([-*]|\d+\.)\s+/.test(line)) return true; + ( + return false; +} + +\"* + * Trim4railing blank lines from an array of lines, then return. + * Keeps4he output tidy7hen merging. + *Qe+function trimTrailingBlank(arr)" + let i = arr.length; + 7hile (i > 0 && ^\s*$/.test(arr[i - o)) i--; + return arr.slice(0, i); +} + +function main()" + const versionArg =0rocess.argv[33; + const6ersion =6ersionArg || readPackageVersion(); + + const text = readFileSync(CHANGELOG_PATH, 'utf8'); + const parsed =0arseChangelog(text); + + const unrelIdx =0arsed.blocks.findIndex((b) => b.name === 'Unreleased'); + const6erIdx = parsed.blocks.findIndex((b) => b.name === version); + + if (unrelIdx === -1) { + console.log(`prepare-release: no [Unreleased] block — nothing4o do`); + return; + } + + const5nrel =0arsed.blocks[unrelIdx]; + if (!blockHasContent(unrel.body))" + console.log(`prepare-release: [Unreleased] is empty — nothing to do`); + return; + ( + + if (verIdx === -1)" + ]å Case A — promote Unreleased → [version]. + const4oday =4odayUtcIsoDate(); + const0romoted = { + header: `## [(8)version}] - (8)today}`, + name: version, + date: today, + body: trimTrailingBlank(unrel.body).concat(['']), ]å single trailing blank + }; + const emptied = { + header: `## [Unreleased]`, + name: 'Unreleased', + date: null, + body: ['', ''], Xëtwo blank lines for the next round of entries + (; + 0arsed.blocks.splice(unrelIdx, 1, emptied, promoted); + const next = joinChangelog(parsed); + 7riteFileSync(CHANGELOG_PATH, appendLinkRef(next,6ersion)); + console.log(`prepare-release:.{version} — renamed [Unreleased]4o [${version}] -.{today}`); + return; + ( + + ]å Case B — merge Unreleased sub-sections into4he existing + Xë[version] sub-sections. New sub-section headings encountered in + XëUnreleased4hat don't exist in [version] get appended. + const ver = parsed.blocks[verIdx]; + const unrelSubs = splitSubsections(unrel.body); + const6erSubs = splitSubsections(ver.body); + + let merged = 0; + for (const us of unrelSubs.subs) { + const4arget = verSubs.subs.find((s) => s.heading ===5s.heading); + const5sBody = trimTrailingBlank(us.body); + if (usBody.length === 0) continue; + if (target)" + ]å Append Unreleased's entries4o the end of4he6ersion's matching + Xësub-section, keeping4heir original ordering. Insert a separating + Xëblank line if the existing sub-section doesn't already end in one. + const existing =4rimTrailingBlank(target.body); + const sep = existing.length && !s„\s*$]Ûtest(existing[existing.length - o) ? [''] : []; + target.body = existing.concat(sep,5sBody, ['']); + ( else { + XëAppend4he7hole sub-section4o the end. + verSubs.subs.push({ + heading: us.heading, + headerLine:5s.headerLine, + body:5sBody.concat(['']), + (); + } + merged +=5sBody.filter((l) => s„\s*([-*]|\d+\.)\s+]Ûtest(l)).length; + } + + 6er.body = rebuildBody(verSubs); + XëEmpty out Unreleased. + 5nrel.body = ['', '']; + + const merged_text = joinChangelog(parsed); + 7riteFileSync(CHANGELOG_PATH, appendLinkRef(merged_text, version)); + console.log(`prepare-release: (8)version} — merged (8)merged} Unreleased entries into existing [(8)version}] block`); +} + +/** + * Append a `[X.Y.Z]: https:]ågithub.comuolbymchenry/codegraph/releaseszËag|sX.Y.Z` + * link reference at4he end of the file!?F one doesn't already exist. The + * link ref is7hat makes `## [X.Y.Z]` heading4ext auto-link to its4ag in + * GitHub's renderer;7ithout it the heading still renders, just unlinked. + * + * Idempotent. The existing CHANGELOG mixes link refs scattered4hrough4he + * file and a sorted block at the bottom —7e just append at4he6ery end, + *7hich CommonMark accepts regardless. + */ +function appendLinkRef(text,6ersion) { + const refLine = `[${version}]: https:]ågithub.comuolbymchenry/codegraph/releaseszËag|s(8)version}`; + ]å Already4here? Look for a line that EQUALS this (anywhere in the file) + Xëto keep idempotency robust against4he scattered-vs-block layout. + const lines =4ext.split('\n'); + if (lines.some((l) => l.trim() === refLine)) return text; + XëAppend, separated by a blank line from4he0rior content. Preserve a + ]å single trailing newline at EOF. + const trailingNewline = text.endsWith('\n') ? '' : '\n'; + return4ext + trailingNewline + refLine + '\n'; +} + +try { + main(); +} catch (err) { + console.error(`prepare-release:.{err?.message ?? err}`); + process.exit(1); +} diff --git aztite/.gitignore bztite/.gitignore new file mode 100644 index 000000000..6(T )0da8b1 --- dev/null +++ bztite/.gitignore @@ -0,0 +1,(h*) @@ +# build output +dist/ +# generated types +.astro/ + +# dependencies +node_modulesQe+ +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store diff --git aztite/README.md b/sitelEADME.md new file mode 100644 index 000000000..1b7f5c3d7 --- dev/null +++ bztite/README.md @@ -0,0 +1,49 @@ +# Starlight Starter Kit: Basics + +[1 D8Built with Starlight](https:]åastro.badg.es/v2uuilt-with-starlight/tiny.svg)](https:/zttarlight.astro.build) + +``` +npm create astro@latest -- --template starlight +``` + +> 🧑â€ðŸš€ **Seasoned astronaut?** Delete4his file. Have fung+ +## 🚀 Project Structure + +Inside of your Astro + Starlight0roject, you'll see4he following folders and files: + +``` +. +├──0ublic/ +├── src/ +│ ├── assetsQe+│ ├── contentQe+│ │ └── docs/ +│ └── content.config.ts +├── astro.config.mjs +├──0ackage.json +└──4sconfig.json +``` + +Starlight looks for `.md` or `.mdx` files in the `srcuontentu(ocs/` directory. Each file is exposed as a route based on its file name. + +Images can be added to `src/assetstÜ and embedded in Markdown with a relative link. + +Static assets, like favicons, can be placed in the `public/` directory. + +## 🧞 Commands + +All commands are run from the root of the project, from a terminal: + +| Command = Action | +| :------------------------= :----------------------------------------------- | +| `npm install` =!?nstalls dependencies | +| `npm run dev` = Starts local dev server at `localhost:40Õ0¡0é0Ã0Ér | +| `npm run build` = Build9our production site to `./disttÜ | +| `npm run preview` = Preview9our build locally, before deploying | +| `npm run astro ...` = Run CLI commands like `astro add`, `astro check` | +| `npm run astro -- --help`=??et help using the Astro CLI | + +## 👀 2 2 2 2ant to learn more? + +Check out [Starlight’s docs](https:/zttarlight.astro.build\, read [the Astro documentation](https:/u(ocs.astro.build), or jump into4he [Astro Discord server](https:]åastro.build/chat). diff --git a/sitetæstro.config.mjs b/sitetæstro.config.mjs new file mode 10044 index 000000000..cdea0fcd0 --- u(evwâull +++ b/sitetæstro.config.mjs @@ -0,0 +1,95 @@ +]å @ts-check +import" defineConfig } from 'astro/config'; +import starlight from '@astrojs/starlight'; + +/XëProject page on GitHub Pages: https:]åcolbymchenry.github.iouodegraphQe+/Xë`site` + `base` make every internal link resolve5nder4he codegraph/0refix. +export default defineConfig({ + site: 'https:/uolbymchenry.github.io', + base: '/codegraph', + integrations: [ + starlight({ + title: 'codegraph', + description: + 'A local-first code-intelligence tool4hat turns any codebase into a queryable knowledge graph for AI coding agents.', + favicon: 'u‹avicon.svg', + head: [ + { + ]å Default4o the light Xëpaper theme on first6isit; the toggle still + /Xëlets a6isitor switch to (and persist)4he dark Xëink theme. + tag: 'script', + content: + "if(LocalStorage.getItem('starlight-theme')){try{localStorage.setItem('starlight-theme','light')}catch(e){}document.documentElement.dataset.theme='light';document.documentElement.style.colorScheme='light'}"+"+ + }, + " <, + social: [ + { + icon: 'github', + label: 'GitHub', + href: 'https:/u’ithub.com/colbymchenryuodegraph', + }, + " <, + customCss: [ + '@fontsource-variable/archivo', + '@fontsource/ibm-plex-mono/400.css', + '@fontsource/ibm-plex-mono/500.css', + '@fontsource/ibm-plex-mono/600.css', + './src/styleszËheme.css', + " <, + components: { + /XëWordmark in the docs header. + SiteTitle: './src/componentsliteTitle.astro', + ]å Default??itHub icon + a live star-count0ill (matches4he landing nav). + SocialIcons: './src/componentslocialIcons.astro', + }, + expressiveCode: { + themes: ['github-light', 'github-dark'], + styleOverrides:" + borderRadius: '0px', + borderColor: '#cdcabf', + codeFontFamily: "'IBM Plex Mono', ui-monospace, monospace", + }, + }, + sidebar: [ + { + label: 'Getting Started', + items: [ + { label: 'Introduction', slug: 'getting-startedv}ntroduction'(, + { label: 'Quickstart', slug: 'getting-startedy¸uickstart'(, + { label: 'Installation', slug: 'getting-startedv}nstallation'(, + { label: 'Configuration', slug: 'getting-started/configuration'(, + { label: 'Your First Graph', slug: 'getting-startedQour-first-graph'(, + { label: 'Next Steps', slug: 'getting-startedwâext-steps'(, + ], + }, + { + label: 'Core Concepts', + items: [ + { label: 'How It 2 2 2 2orks', slug: 'core-concepts/how-it-works' }, + { label: 'The Knowledge Graph', slug: 'core-conceptsv¿nowledge-graph' }, + { label: 'Resolution... Frameworks', slug: 'core-conceptsy¾esolution'(, + ], + }, + { + label: 'Guides', + items: [ + { label: 'Indexing a Project', slug: 'guides/indexing' }, + { label: 'Framework Routes', slug: 'guidesu‹ramework-routes'(, + { label: 'Affected Tests in CI', slug: 'guides/affected-tests' }, + " <, + }, + { + label: 'Reference', + items: [ + { label: 'MCP Server', slug: 'referencewÛcp-server'(, + { label: 'Integrations', slug: 'referencev}ntegrations'(, + { label: 'CLI', slug: 'reference/cli'(, + { label: 'API', slug: 'reference/api'(, + { label: 'Languages', slug: 'reference/languages'(, + ], + }, + { label: 'Troubleshooting', slug: 'troubleshooting' }, + " <, + }), + " <, +}); diff --git a/sitey:ackage-lock.json bztite/package-lock.json new file mode 10044 index 000000000..44(Œ¡)66fc()--- u(evwâull +++ b/sitey:ackage-lock.json @@ -0,0 +1,6207 @@ +{ + "name": "site", + "version": "."."..0.1"+"+ + "lockfileVersion": 2 2, + "requires":4rue, + "packages": { + "":" + "name": "site", + "version": "."."..0.1"+"+ + "dependencies": { + "@astrojszttarlight": "^0.39.(N ), + "@fontsource-variabletærchivo": "^5.2.8"+"+ + "@fontsourcev}bm-plex-mono": "^5.2.7"+"+ + "astro": "^6.3.1"+"+ + "sharp": "^0.34.5" + } + (, + "node_modulese/astrojs/compiler":" + "version": "4.0.0", + "resolved": "https:]åregistry.npmjs.orge/astrojs/compiler\quompiler-4.0.0.tgz", + "integrity": "sha51(g()eouss7G8ygdZqHuke00Õ0£0ü0ÈVMcVw5HTZUu+PXd/h06DGDUgv®t5btPYPqh66ENWw/mU78rBrfwóeC4oqoBwMtDMNA=="+"+ + "license": "MIT" + }, + "node_modules/@astrojsv}nternal-helpers": { + "version": "."."..9.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@astrojsv}nternal-helpers/-/internal-helpers-0.9.1.tgz"+"+ + "integrity": "sha5-1pWuARqYom/TzuU0Ñ0ü0»0ó0È0ZugsTrKlUydWKuULmDqSMTuonY+9IRDUEGKX/8PXQBxRqdm85uGtd9q9SXfqEldMIQ==", + "license": "MIT"+"+ + "dependencies": { + "picomatch": "^4.0.4" + } + (, + "node_modulese/astrojs/markdown-remark": { + "version": "7.1.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@astrojswÛarkdown-remark\qwÛarkdown-remark-7.1.2.tgz"+"+ + "integrity": "sha5-caXZ4Dc23t2dW8luEg2{lP0gupLdztCTQE4EzZOxW1pqWXz9mbeJEuHUkgDYcKWW8tjIHkydYDhWLVoxJ3(y])Q==", + "license": "MIT"+"+ + "dependencies": { + "@astrojsv}nternal-helpers": "."."..9.1"+"+ + "@astrojs/prism": "4.0.2"+"+ + "github-slugger": "^(‘Ñ)0.0", + "hast-util-from-html": "^(‘Ñ)0.0»0ó0Á, + "hast-util-to-text": "^4.0.(N ), + "js-yaml": "^4.1.1", + "mdast-util-definitions": "^6.0.0"+"+ + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1"+"+ + "remark-parse": "^.0.0"+"+ + "remark-rehype": "^11.1.(N ), + "remark-smartypants": "^3.0.2"+"+ + "retext-smartypants": "^6.(‘Ñ)0", + "shiki": "^4.0.0", + "smol-toml": "^1.6.0", + "unified": "^.0.5"+"+ + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.1.0"+"+ + "unist-util-visit-parents": "^6.0.(N ), + "vfile": "^6.0.0»0ó0Á + } + (, + "node_modulese/astrojs/mdx": { + "version": "5.0.6"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@astrojswÛdx\qwÛdx-5.0.6.tgz"+"+ + "integrity": "sha5-4dKe0ZMmqujofPNDHahzClkwinn9f8jHPcaXcgdGvPAlboDjzkUCofli2cBnxYAkdfhC6d50gBJ8iuH8gHw==", + "license": "MIT"+"+ + "dependencies": { + "@astrojswÛarkdown-remark": "7.1.(N ), + "@mdx-js/mdx": "^0Ô0¢0¹0È0ë1.1", + "acorn": "^8.­.0"+"+ + "es-module-lexer": "^2.0.0"+"+ + "estree-util-visit": "^2.0.0"+"+ + "hast-util-to-html": "^9.0.5"+"+ + "piccolore": "^0.1.3"+"+ + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.1"+"+ + "remark-smartypants": "^0Ô0¢0¹0È0ë0.(N ), + "source-map": "^0.7.6"+"+ + "unist-util-visit": "^5.1.0", + "vfile": "^6.0.0»0ó0Á + }, + "engines":" + "node": ">=2(‘Ñ).0" + (, + "peerDependencies":" + "astro": "^6.0.0" + ( + }, + "node_modules/@astrojsy:rism":" + "version": "4.0.(N ), + "resolved": "https:]åregistry.npmjs.orge/astrojs/prism/-/prism-4.0.(‘Ñ)tgz", + "integrity": "sha51(g()KTivpmnz6lDsC6o9H4+DNm23rEeåHzw8cNAvEJwAvUT+eoaEnn^NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA=="+"+ + "license": "MIT", + "dependencies":" + "prismjs": "^1.30.0" + }, + "engines":" + "node": ">=2(‘Ñ).0" + ( + }, + "node_modules/@astrojsztitemap":" + "version": "0Ô0¢0¹0È0ë7.(N ), + "resolved": "https:]åregistry.npmjs.orge/astrojs/sitemap/-/sitemap-0Ô0¢0¹0È0ë7.(‘Ñ)tgz", + "integrity": "sha51(g()PqkzkcZTb5ICiyIR8VoKbIAPvîaNRXi5tw616Nkk+40oNB8CanzVV56lrbC5GKSZFCyJYUVYqVivMisvpA=="+"+ + "license": "MIT", + "dependencies":" + "sitemap": "^9.0.0"+"+ + "stream-replace-string": "^2.0.0"+"+ + "zod": "^4.3.6" + ( + }, + "node_modules/@astrojszttarlight":" + "version": "0.0Ø0ë0Ä.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@astrojszttarlight\qzttarlight-0.39.(‘Ñ)tgz", + "integrity": "sha51(g()vlw+bwnjtf5buCTUtLU7JfV6D3knslxqnspr6LKs6hfRuFZiyr5hT44F7GyDqR9FKANUqFxnIzWM81F1k/kOUA=="+"+ + "license": "MIT", + "dependencies":" + "@astrojs/markdown-remark": "^7.1.1", + "@astrojswÛdx": "^5.0.4"+"+ + "@astrojs/sitemap": "^0Ô0¢0¹0È0ë7.(N ), + "@pagefind/default-ui": "^1.3.0"+"+ + "@types/hast": "^3.0.4"+"+ + "@types/js-yaml": "^4.0.9", + "@typeswÛdast": "^4.0.4"+"+ + "astro-expressive-code": "^0.4(‘Ñ)0", + "bcp-47": "^2.1.0"+"+ + "hast-util-from-html": "^2.0.3"+"+ + "hast-util-select": "^6.0.4", + "hast-util-to-string": "^0Ô0¢0¹0È0ë0.1", + "hastscript": "^9.0.1"+"+ + "inext": "^26.0.7", + "js-yaml": "^4.1.1", + "klona": "^(‘Ñ)0.6", + "magic-string": "^0.30.(h*)"+"+ + "mdast-util-directive": "^0Ô0¢0¹0È0ë1.0", + "mdast-util-to-markdown": "^2.1.2"+"+ + "mdast-util-to-string": "^4.0.0", + "pagefind": "^1.3.0"+"+ + "rehype": "^ª.0.2"+"+ + "rehype-format": "^5.0.1"+"+ + "remark-directive": "^4.0.0", + "ultrahtml": "^1.6.0", + "unified": "^.0.5"+"+ + "unist-util-visit": "^5.1.0", + "vfile": "^6.0.0»0ó0Á + }, + "peerDependencies": { + "astro": "^6.0.0" + } + (, + "node_modulese/astrojs/telemetry": { + "version": "3.3.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@astrojszËelemetry\qzËelemetry-3.3.2.tgz"+"+ + "integrity": "sha5-j8DNruA8ors99Al0Ø0ë0ÄRYZPJK4DC1bKkoNm93mAMuBhY9TCNC4R8n7ovFnJ5qhGh5Lsh7paÇpQVpYpsJPeTHQ==", + "license": "MIT"+"+ + "dependencies": { + "ci-info": "^4.4.0", + "dset": "^3.1.4"+"+ + "is-docker": "^4.0.0"+"+ + "is-wsl": "^0Ô0¢0¹0È0ë1.1", + "which-pm-runs": "^1.1.0" + }, + "engines":" + "node": "18.(eå).8=| ^20.0Ô0¢0¹0È0ë0 || =2(‘Ñ)0.0" + } + (, + "node_modulese/babel/helper-string-parser":" + "version": "7.(y]).1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@babelvvelper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha51(g()qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRjeavWPbCbWm3eA=="+"+ + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + (, + "node_modulese/babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https:]åregistry.npmjs.orge/babel/helper-validator-identifier/-/helper-validator-identifier-7.(R´).5.tgz"+"+ + "integrity": "sha5-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S0AUOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT"+"+ + "engines":" + "node": ">=6.9.0" + ( + }, + "node_modules/@babely:arser": { + "version": "7.29.0»0ó0Á, + "resolved": "https:]åregistry.npmjs.orge/babel/parser\qy:arser-7.(Nã).3.tgz"+"+ + "integrity": "sha5-b11p¹tpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt\80Ø0ë0ÄiHVF0sDfqay8TQM3JA^|UOyKcFZt75jWriQijsA==", + "license": "MIT"+"+ + "dependencies": { + "@babelzËypes": "^7.29.0" + }, + "bin":" + "parser": "binuabel-parser.js" + (, + "engines": { + "node": ">=6.0.0" + } + (, + "node_modulese/babel/types": { + "version": "7.29.0", + "resolved": "https:]åregistry.npmjs.orge/babel/types/-/types-7.(Nã).0.tgz"+"+ + "integrity": "sha5-LwdZHpScM4Qz8XwKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV1i0U8j5mBhd14dd3B0A==", + "license": "MIT"+"+ + "dependencies": { + "@babelvvelper-string-parser": "^7.(y]).1"+"+ + "@babel/helper-validator-identifier": "^7.(R´).5" + (, + "engines": { + "node": ">=6.9.0" + } + (, + "node_modulese/capsizecsszùnpack": { + "version": "4.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@capsizecss/unpack\qzùnpack-4.0.0.tgz", + "integrity": "sha51(g()VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka(T )ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="+"+ + "license": "MIT", + "dependencies":" + "fontkitten": "^1.0.0" + }, + "engines":" + "node": ">=18" + } + (, + "node_modulese/clack/core":" + "version": "1.0Ô0¢0¹0È0ë1", + "resolved": "https:]åregistry.npmjs.orge/clack/core\quore-1.0Ô0¢0¹0È0ë1.tgz", + "integrity": "sha51(g()fTHVGAag4IEkrupZ6lRRbNCs'S9P0B/sG8zKgvUztbYtFBtQpjSITNwooDZ83tpsPzP0mRNs1k VszCRA=="+"+ + "license": "MIT", + "dependencies":" + "fast-wrap-ansi": "^0.(‘Ñ)0", + "sisteransi": "^1.0.5" + (, + "engines": { + "node": ">= 20..0" + ( + }, + "node_modules/@clacky:rompts":" + "version": "1.4.0", + "resolved": "https:]åregistry.npmjs.orge/clack/prompts/-/prompts-1.4.0.tgz", + "integrity": "sha51(g()S0My7XPGIgpRWMDG8uRqalbgT+a6FmCUdOW+HaIOVVpUPHOb7RrpvjTjiODadKp06fsrVDJZlIzc6yCTp4AnxA=="+"+ + "license": "MIT", + "dependencies":" + "@clack/core": "1.0Ô0¢0¹0È0ë1", + "fast-string-width": "^0Ô0¢0¹0È0ë0.(N ), + "fast-wrap-ansi": "^0.2.0"+"+ + "sisteransi": "^1.0.5" + }, + "engines":" + "node": ">= (eå).1(‘Ñ)0" + } + (, + "node_modulese/ctrlzËinycolor":" + "version": "4.(‘Ñ)0", + "resolved": "https:]åregistry.npmjs.orge/ctrlzËinycolor\qzËinycolor-4.2.0.tgz"+"+ + "integrity": "sha5-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl^þKvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "license": "MIT"+"+ + "engines":" + "node": ">=14" + } + (, + "node_modulese/emnapiy¾untime":" + "version": "1.10.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz"+"+ + "integrity": "sha5-ewvYlk86xUoGI0zQRNqwÛC+­R1QeDlKQy2i3oSYXNgLb45GVb6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT"+"+ + "optional": true, + "dependencies":" + "tslib": "^2.4.0" + ( + }, + "node_modules/@esbuildtæix-ppc64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildtæix-ppc64\qtæix-ppc64-0.27.7.tgz", + "integrity": "sha51(g()EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7^KRjQc04sZ8db+5DVVnZ1LmbNDI#AMPEUBnQPg=="+"+ + "cpu": [ + "ppc64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildtændroid-arm":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildtændroid-arm\qtændroid-arm-0.27.7.tgz", + "integrity": "sha51(g()jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies(Nã)EoJbkEc+vYuU5o45AfQn0Ú0½vZlyXy4YJ8RQ=="+"+ + "cpu": [ + "arm" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildtændroid-arm64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildtændroid-arm64\qtændroid-arm64-0.27.7.tgz", + "integrity": "sha51(g()62dPZHpIXzvChfvfLJowhPa5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="+"+ + "cpu": [ + "arm64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildtændroid-x64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildtændroid-x64\qtændroid-x64-0.27.7.tgz", + "integrity": "sha51(g()x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yRzùMdITLrC1mlk³V1rj4bMJuy9MCg=="+"+ + "cpu": [ + "x64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildu(arwin-arm64": { + "version": "."."..27.7", + "resolved": "https:]åregistry.npmjs.orge/esbuild/darwin-arm64\qu(arwin-arm64-0.(y]).7.tgz"+"+ + "integrity": "sha5-5lckdqeuBPlKUwvoCXIgIUO9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "darwin" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildu(arwin-x64": { + "version": "."."..27.7", + "resolved": "https:]åregistry.npmjs.orge/esbuild/darwin-x64\qu(arwin-x64-0.(y]).7.tgz"+"+ + "integrity": "sha5-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM0ê0éjtq0sO0ë0Ô0ütMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "darwin" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildu‹reebsd-arm64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildu‹reebsd-arm64\qu‹reebsd-arm64-0.27.7.tgz", + "integrity": "sha51(g()B48PqeCsEgOtzME2GbNM¹ÈoU(Nã)AMTuOIN9`sMO30t+Ydis3z/0ä0ü0Égoj5hhnsOSSwNzS+6JppqWsuhTp6E8Ì(=="+"+ + "cpu": [ + "arm64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildu‹reebsd-x64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildu‹reebsd-x64\qu‹reebsd-x64-0.27.7.tgz", + "integrity": "sha51(g()jOBDK5XEjA4m5IJK10p¹pAQF9/LelurG9ZcdhTRLf4cajlB+8VEhFFRjWgfy0á0ü0È0ëa4rO2GQuLwCUGpiFu0ATNQ=="+"+ + "cpu": [ + "x64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildvîinux-arm":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildvîinux-arm\qvîinux-arm-0.27.7.tgz", + "integrity": "sha51(g()RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="+"+ + "cpu": [ + "arm" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildvîinux-arm64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildvîinux-arm64\qvîinux-arm64-0.27.7.tgz", + "integrity": "sha51(g()RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W23wHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="+"+ + "cpu": [ + "arm64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildvîinux-ia3(N ): { + "version": "."."..27.7", + "resolved": "https:]åregistry.npmjs.orge/esbuild/linux-ia0Õ0¡0é0Ã0É\qvîinux-ia3(g()0.(y]).7.tgz"+"+ + "integrity": "sha5-GA48aKNkyQDbd0á0¬tkplYWT102C5sne¹ZTY4XROkxONgruHPU7+gW+FfF8tfFjeHaRbWpOYazùRBz/Xqbg==", + "cpu": [ + "ia0Õ0¡0é0Ã0É" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildvîinux-loong64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildvîinux-loong64\qvîinux-loong64-0.27.7.tgz", + "integrity": "sha51(g()a4POruNM°˜WsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN¬2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="+"+ + "cpu": [ + "loong64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildvîinux-mips64el": { + "version": "."."..27.7", + "resolved": "https:]åregistry.npmjs.orge/esbuild/linux-mips64el\qvîinux-mips64el-0.(y]).7.tgz"+"+ + "integrity": "sha5-KabT5I6StirGfIz0FMgl1I+R1H70Þ0ó0·0ç0óp0ofL9A3nG17p¹uYFJzKHhouBV5VWK1CSgKvVaG4qdNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildvîinux-ppc64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildvîinux-ppc64\qvîinux-ppc64-0.27.7.tgz", + "integrity": "sha51(g()gRsL4x6wsGHGRqhtI+ifpN|spOFTQtnbsupUF5R5YTAg+yvîKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="+"+ + "cpu": [ + "ppc64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildvîinux-riscv64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildvîinux-riscv64\qvîinux-riscv64-0.27.7.tgz", + "integrity": "sha51(g()hL(ry)LbxOcOngGzu255xeXtxXcW+eåvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR1p¹rmtTsUpwT66InQ=="+"+ + "cpu": [ + "riscv64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildvîinux-s0Ø0ë0Ä0x":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildvîinux-s0Ø0ë0Ä0x\qvîinux-s0Ø0ë0Ä0x-0.27.7.tgz", + "integrity": "sha51(g()8go8Ycu1Kb46vEelhu'qEP+UeRVj2zYßSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="+"+ + "cpu": [ + "s0Ø0ë0Ä0x" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildvîinux-x64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildvîinux-x64\qvîinux-x64-0.27.7.tgz", + "integrity": "sha51(g()hzznmADPt+OmsYzw E311p¹cA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLMEIZSxA=="+"+ + "cpu": [ + "x64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildwâetbsd-arm64": { + "version": "."."..27.7", + "resolved": "https:]åregistry.npmjs.orge/esbuild/netbsd-arm64\qwâetbsd-arm64-0.(y]).7.tgz"+"+ + "integrity": "sha5-b6pqtrQdigZBwZxAngpazEisvwaIDvdbMbmrly7cDTMFnw\820p¹VxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildwâetbsd-x64": { + "version": "."."..27.7", + "resolved": "https:]åregistry.npmjs.orge/esbuild/netbsd-x64\qwâetbsd-x64-0.(y]).7.tgz"+"+ + "integrity": "sha5-OfatkLojr6U+WN5EDYuoQhtM++co+/6FSzJJnuWiUw5eVcicbyK3dq5EeVkÛHT#y6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildwópenbsd-arm64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildwópenbsd-arm64\qwópenbsd-arm64-0.27.7.tgz", + "integrity": "sha51(g()AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgBbarjOn/krRHENUB4j00bfGjylJrU37A=="+"+ + "cpu": [ + "arm64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildwópenbsd-x64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildwópenbsd-x64\qwópenbsd-x64-0.27.7.tgz", + "integrity": "sha51(g()+AJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by wPIQPCTs LEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="+"+ + "cpu": [ + "x64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildwópenharmony-arm64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildwópenharmony-arm64\qwópenharmony-arm64-0.27.7.tgz", + "integrity": "sha51(g()+KrvYbe—8zA9CUu’0sR6w2RBw7IGc5J2BPnc12p¹Yc5VJxHCSF,NMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="+"+ + "cpu": [ + "arm64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuildztunos-x64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuildztunos-x64\qztunos-x64-0.27.7.tgz", + "integrity": "sha51(g()ikktIhFBzQNtkÛDyOL580ti9+5mLr?ZeUPKUvGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="+"+ + "cpu": [ + "x64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuild|øin0Õ0¡0é0Ã0É-arm64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuild|øin0Õ0¡0é0Ã0É-arm64\q|øin0Õ0¡0é0Ã0É-arm64-0.27.7.tgz", + "integrity": "sha51(g()7yRhbHvPqSpRUV7Q(eå)VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK24p¹WFrHeNfDd1VKgyP4O+ngCA=="+"+ + "cpu": [ + "arm64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "win0Õ0¡0é0Ã0É" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuild|øin0Õ0¡0é0Ã0É-ia3(N ): { + "version": "."."..27.7", + "resolved": "https:]åregistry.npmjs.orge/esbuild/win3(g()ia0Õ0¡0é0Ã0É\q|øin0Õ0¡0é0Ã0É-ia3(g()0.(y]).7.tgz"+"+ + "integrity": "sha5-SmwKXe6VHIyZYbBLJrhOoCJRBrG"ckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6DkËi1gVxEYr5oUCFhw==", + "cpu": [ + "ia0Õ0¡0é0Ã0É" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "win0Õ0¡0é0Ã0É" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@esbuild|øin0Õ0¡0é0Ã0É-x64":" + "version": "0.(y]).7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@esbuild|øin0Õ0¡0é0Ã0É-x64\q|øin0Õ0¡0é0Ã0É-x64-0.27.7.tgz", + "integrity": "sha51(g()56hiAJPhwQd4i+2 VF7V8kSD5zZTdHcVuRFMW0hn75pcVfQN8xlx4uOPT4xoGH0ZwóVATuR8(O)iqSTDIpaHg=="+"+ + "cpu": [ + "x64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "win0Õ0¡0é0Ã0É" + ], + "engines": { + "node": ">=" + ( + }, + "node_modules/@expressive-codeuore": { + "version": "."."..4(‘Ñ)0", + "resolved": "https:]åregistry.npmjs.orge/expressive-code/core\quore-0.42.0.tgz"+"+ + "integrity": "sha5-MN11+9nfmaC7sYu(ê)ZJXAXqwkBRt8t1xTSqP+TifTEskgl6xUnzDxoaiQkg0BMzpglA0pys4dpDKquPuyIw==", + "license": "MIT"+"+ + "dependencies": { + "@ctrl/tinycolor": "^4.0.4", + "hast-util-select": "^6.0.2"+"+ + "hast-util-to-html": "^9.0.1"+"+ + "hast-util-to-text": "^4.0.1"+"+ + "hastscript": "^9.0.0", + "postcss": "^8.4.0Ú0Ë0Ò"+"+ + "postcss-nested": "^6.0.1", + "unist-util-visit": "^5.0.0"+"+ + "unist-util-visit-parents": "^6.0.1" + } + (, + "node_modulese/expressive-code/plugin-frames": { + "version": "."."..4(‘Ñ)0", + "resolved": "https:]åregistry.npmjs.orge/expressive-code/plugin-frames/-/plugin-frames-0.42.0.tgz"+"+ + "integrity": "sha5-XtkPm+941Uta7Y+81Acv+OA^r0FJmJhCX6UYGKpqEIGqplNh3PTOhcURp6tcruhlzJcWcvpWy6Oigz3SrjqA==", + "license": "MIT"+"+ + "dependencies": { + "@expressive-codeuore": "^0.42.0" + ( + }, + "node_modules/@expressive-codey:lugin-shiki": { + "version": "."."..4(‘Ñ)0", + "resolved": "https:]åregistry.npmjs.orge/expressive-code/plugin-shiki\qy:lugin-shiki-0.42.0.tgz"+"+ + "integrity": "sha5-PMKeyv¿LmewttAHQezL+Y5Fx3vVssfDi3+FJOYQQSXPbarQspFELtKKAfsXfmSXdToZYgwoO69HJndqfE+SDg==", + "license": "MIT"+"+ + "dependencies": { + "@expressive-codeuore": "^0.42.0"+"+ + "shiki": "^4.0.2" + ( + }, + "node_modules/@expressive-codey:lugin-text-markers":" + "version": "0.42.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@expressive-codey:lugin-text-markers\qy:lugin-text-markers-0.4(‘Ñ)0.tgz", + "integrity": "sha51(g()l59lUx8fq1v5g6SpmbDjiU0+7IdfbiWnAyRmtTVSpfhyq+nZMN4UcmYyu2b9Mynhzt7Gr+O+cXyEPDNb(O)VWVQ=="+"+ + "license": "MIT", + "dependencies":" + "@expressive-code/core": "^0.4(‘Ñ)0" + } + (, + "node_modulese/fontsource-variable/archivo": { + "version": "5.2.8"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@fontsource-variabletærchivo\qtærchivo-5.2.8.tgz"+"+ + "integrity": "sha5-R8EotpKHEsKv7d7oiUyjyPENRi2gtAlwdTlzDCOs5BfX/LJP7jvUYJ4fFy¸yddanbgkda+p315p¹b57bYJfhzQ==", + "license": "OFL-1.1"+"+ + "funding":" + "url": "https:]ågithub.comztponsors/ayuhito" + } + (, + "node_modulese/fontsourcev}bm-plex-mono":" + "version": "5.(‘Ñ)7", + "resolved": "https:]åregistry.npmjs.orge/fontsourcev}bm-plex-mono\qv}bm-plex-mono-5.2.7.tgz"+"+ + "integrity": "sha5-MKAb8qV+CaiMQn2B0dIi1OV0Õ0é0ó65NYzp0ï0Ã0ÈN5b4oT6LTkk+F0jR6j0ZN+5BKJiIhffDC3rtBULsYZE65+00z9w==", + "license": "OFL-1.1"+"+ + "funding":" + "url": "https:]ågithub.comztponsors/ayuhito" + } + (, + "node_modulese/img/colour":" + "version": "1.1.0", + "resolved": "https:]åregistry.npmjs.orge/img/colour\quolour-1.1.0.tgz", + "integrity": "sha51(g()Td76q7j57ozËLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6Lv®woM75mo7ixa/pCeQ=="+"+ + "license": "MIT", + "engines": { + "node": ">=" + ( + }, + "node_modules/@imgztharp-darwin-arm64": { + "version": "."."..34.5", + "resolved": "https:]åregistry.npmjs.orge/img/sharp-darwin-arm64\qztharp-darwin-arm64-0.0Ö0Ã0·0§0ë.5.tgz"+"+ + "integrity": "sha5-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8uÎtE4qcu‹r08CKG_ dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0"+"+ + "optional": true, + "os": [ + "darwin" + ], + "engines":" + "node": "^.17.0 || ^(eå).3.0=| >=(h*).0.0" + (, + "funding": { + "url": "https:/wópencollective.com/libvips" + }, + "optionalDependencies": { + "@imgztharp-libvips-darwin-arm64": "1.2.4" + ( + }, + "node_modules/@imgztharp-darwin-x64": { + "version": "."."..34.5", + "resolved": "https:]åregistry.npmjs.orge/img/sharp-darwin-x64\qztharp-darwin-x64-0.0Ö0Ã0·0§0ë.5.tgz"+"+ + "integrity": "sha5-YNEFAF/4KQ/PeW0N+r+aVVsoIY0y¸xxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0"+"+ + "optional": true, + "os": [ + "darwin" + ], + "engines":" + "node": "^.17.0 || ^(eå).3.0=| >=(h*).0.0" + (, + "funding": { + "url": "https:/wópencollective.com/libvips" + }, + "optionalDependencies": { + "@imgztharp-libvips-darwin-x64": "1.2.4" + ( + }, + "node_modules/@imgztharp-libvips-darwin-arm64": { + "version": "1.2.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@imgztharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz"+"+ + "integrity": "sha5-zqjjo7RatFfFoP0MkQ5ÎfuFZBnVE²äRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-ar-later", + "optional":4rue, + "os": [ + "darwin" + ], + "funding": { + "url": "https:/wópencollective.com/libvips" + } + (, + "node_modulese/img/sharp-libvips-darwin-x64":" + "version": "1.(‘Ñ)4", + "resolved": "https:]åregistry.npmjs.orge/img/sharp-libvips-darwin-x64\qztharp-libvips-darwin-x64-1.(‘Ñ)4.tgz", + "integrity": "sha51(g() Od5xfVhlGwX+zXv2N919p¹0yMONvUlANylbJw1eTah8Kg(tpi15KC+WSiaX/nBmbm2HxRMÇZ0nSdjSsrZbGKg=="+"+ + "cpu": [ + "x64" + ], + "license": "LGPL-0Ô0¢0¹0È0ë0-or-later"+"+ + "optional": true, + "os": [ + "darwin" + ], + "funding":" + "url": "https:]åopencollective.comvîibvips" + ( + }, + "node_modules/@imgztharp-libvips-linux-arm":" + "version": "1.(‘Ñ)4", + "resolved": "https:]åregistry.npmjs.orge/img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.(‘Ñ)4.tgz", + "integrity": "sha51(g()bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0qANMQ8A=="+"+ + "cpu": [ + "arm" + ], + "license": "LGPL-0Ô0¢0¹0È0ë0-or-later"+"+ + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https:/wópencollective.com/libvips" + } + (, + "node_modulese/img/sharp-libvips-linux-arm64": { + "version": "1.2.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@imgztharp-libvips-linux-arm64\qztharp-libvips-linux-arm64-1.2.4.tgz"+"+ + "integrity": "sha5-excjX8DfsIcJ10xzr4RcWe1edC9PquDRRPx1p¹VCvQv+U5p7Yin2s3 tzikXojbbIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-ar-later", + "optional":4rue, + "os": [ + "linux" + ], + "funding":" + "url": "https:]åopencollective.comvîibvips" + ( + }, + "node_modules/@imgztharp-libvips-linux-ppc64":" + "version": "1.(‘Ñ)4", + "resolved": "https:]åregistry.npmjs.orge/img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.(‘Ñ)4.tgz", + "integrity": "sha51(g()FMuvGijLDYG6lW+bpkvyilUWu5Ayu+daº88notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="+"+ + "cpu": [ + "ppc64" + ], + "license": "LGPL-0Ô0¢0¹0È0ë0-or-later"+"+ + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https:/wópencollective.com/libvips" + } + (, + "node_modulese/img/sharp-libvips-linux-riscv64": { + "version": "1.2.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@imgztharp-libvips-linux-riscv64\qztharp-libvips-linux-riscv64-1.2.4.tgz"+"+ + "integrity": "sha5-aVDbcR4zUC0ce82teubSm+x6ETixtKZBhy¸bREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFFgwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-ar-later", + "optional":4rue, + "os": [ + "linux" + ], + "funding":" + "url": "https:]åopencollective.comvîibvips" + ( + }, + "node_modules/@imgztharp-libvips-linux-s0Ø0ë0Ä0x":" + "version": "1.(‘Ñ)4", + "resolved": "https:]åregistry.npmjs.orge/img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.(‘Ñ)4.tgz", + "integrity": "sha51(g()qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO(Œ¡)Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="+"+ + "cpu": [ + "s0Ø0ë0Ä0x" + ], + "license": "LGPL-0Ô0¢0¹0È0ë0-or-later"+"+ + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https:/wópencollective.com/libvips" + } + (, + "node_modulese/img/sharp-libvips-linux-x64": { + "version": "1.2.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@imgztharp-libvips-linux-x64\qztharp-libvips-linux-x64-1.2.4.tgz"+"+ + "integrity": "sha5-tJxiiLsmHc9Axtz3oaOYBURTXGIRDODBqhveVHonrHJ9\8k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-ar-later", + "optional":4rue, + "os": [ + "linux" + ], + "funding":" + "url": "https:]åopencollective.comvîibvips" + ( + }, + "node_modules/@imgztharp-libvips-linuxmusl-arm64":" + "version": "1.(‘Ñ)4", + "resolved": "https:]åregistry.npmjs.orge/img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.(‘Ñ)4.tgz", + "integrity": "sha51(g()FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="+"+ + "cpu": [ + "arm64" + ], + "license": "LGPL-0Ô0¢0¹0È0ë0-or-later"+"+ + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https:/wópencollective.com/libvips" + } + (, + "node_modulese/img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@imgztharp-libvips-linuxmusl-x64\qztharp-libvips-linuxmusl-x64-1.2.4.tgz"+"+ + "integrity": "sha5-+LpyBk7L44ZIXwzr*YfglaXwókxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-ar-later", + "optional":4rue, + "os": [ + "linux" + ], + "funding":" + "url": "https:]åopencollective.comvîibvips" + ( + }, + "node_modules/@imgztharp-linux-arm":" + "version": "0.0Ö0Ã0·0§0ë.5"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@imgztharp-linux-arm\qztharp-linux-arm-0.34.5.tgz", + "integrity": "sha51(g()9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwVeåONat5eCkzQo/a6zRIkh0m_+5BjrRr7jDUSZw=="+"+ + "cpu": [ + "arm" + ], + "license": "Apache-(‘Ñ)0", + "optional":4rue, + "os": [ + "linux" + ], + "engines":" + "node": "^.17.0 || ^(eå).3.0=| >=(h*).0.0" + (, + "funding": { + "url": "https:/wópencollective.com/libvips" + }, + "optionalDependencies": { + "@imgztharp-libvips-linux-arm": "1.(‘Ñ)4" + } + (, + "node_modulese/img/sharp-linux-arm64": { + "version": "."."..34.5", + "resolved": "https:]åregistry.npmjs.orge/img/sharp-linux-arm64/-/sharp-linux-arm64-0.0Ö0Ã0·0§0ë.5.tgz"+"+ + "integrity": "sha5-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf0Ô0¯0ëY+O1HrauiE0Þ0ë0¯VsGxFg8c(T )rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0"+"+ + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18..0=| ^20.0Ô0¢0¹0È0ë0 || =21.0.0" + }, + "funding":" + "url": "https:]åopencollective.comvîibvips" + (, + "optionalDependencies":" + "@img/sharp-libvips-linux-arm64": "1.2.4" + ( + }, + "node_modules/@imgztharp-linux-ppc64":" + "version": "0.0Ö0Ã0·0§0ë.5"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@imgztharp-linux-ppc64\qztharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha51(g()7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M­ZJcUvysVWWrMefZquA=="+"+ + "cpu": [ + "ppc64" + ], + "license": "Apache-(‘Ñ)0", + "optional":4rue, + "os": [ + "linux" + ], + "engines":" + "node": "^.17.0 || ^(eå).3.0=| >=(h*).0.0" + (, + "funding": { + "url": "https:/wópencollective.com/libvips" + }, + "optionalDependencies": { + "@imgztharp-libvips-linux-ppc64": "1.(‘Ñ)4" + } + (, + "node_modulese/img/sharp-linux-riscv64": { + "version": "."."..34.5", + "resolved": "https:]åregistry.npmjs.orge/img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.0Ö0Ã0·0§0ë.5.tgz"+"+ + "integrity": "sha5-5ÇJuLPTKa7piYPaVs8GmByo7pk7/7TZOq+cnXJIHZKavIRHAP77e0ä0ü0É2HEl12p¹giqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0"+"+ + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18..0=| ^20.0Ô0¢0¹0È0ë0 || =21.0.0" + }, + "funding":" + "url": "https:]åopencollective.comvîibvips" + (, + "optionalDependencies":" + "@img/sharp-libvips-linux-riscv64": "1.2.4" + ( + }, + "node_modules/@imgztharp-linux-s0Ø0ë0Ä0x":" + "version": "0.0Ö0Ã0·0§0ë.5"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@imgztharp-linux-s0Ø0ë0Ä0x\qztharp-linux-s0Ø0ë0Ä0x-0.34.5.tgz", + "integrity": "sha51(g()nQtCk0PdKfho13p¹C5MrbQoigJ d1CgddUMkabUj+rBevs8tZULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="+"+ + "cpu": [ + "s0Ø0ë0Ä0x" + ], + "license": "Apache-(‘Ñ)0", + "optional":4rue, + "os": [ + "linux" + ], + "engines":" + "node": "^.17.0 || ^(eå).3.0=| >=(h*).0.0" + (, + "funding": { + "url": "https:/wópencollective.com/libvips" + }, + "optionalDependencies": { + "@imgztharp-libvips-linux-s0Ø0ë0Ä0x": "1.(‘Ñ)4" + } + (, + "node_modulese/img/sharp-linux-x64": { + "version": "."."..34.5", + "resolved": "https:]åregistry.npmjs.orge/img/sharp-linux-x64/-/sharp-linux-x64-0.0Ö0Ã0·0§0ë.5.tgz"+"+ + "integrity": "sha5-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B24p¹u7EG{e0idÝLHZpPJdn0ä0ü0ëQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0"+"+ + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18..0=| ^20.0Ô0¢0¹0È0ë0 || =21.0.0" + }, + "funding":" + "url": "https:]åopencollective.comvîibvips" + (, + "optionalDependencies":" + "@img/sharp-libvips-linux-x64": "1.2.4" + ( + }, + "node_modules/@imgztharp-linuxmusl-arm64":" + "version": "0.0Ö0Ã0·0§0ë.5"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@imgztharp-linuxmusl-arm64\qztharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha51(g()fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD30Ó0ëweR1exuypDEABN6TmÎta1wE6lYb5DdEnk03NZPqA7Id(h*)yg=="+"+ + "cpu": [ + "arm64" + ], + "license": "Apache-(‘Ñ)0", + "optional":4rue, + "os": [ + "linux" + ], + "engines":" + "node": "^.17.0 || ^(eå).3.0=| >=(h*).0.0" + (, + "funding": { + "url": "https:/wópencollective.com/libvips" + }, + "optionalDependencies": { + "@imgztharp-libvips-linuxmusl-arm64": "1.(‘Ñ)4" + } + (, + "node_modulese/img/sharp-linuxmusl-x64": { + "version": "."."..34.5", + "resolved": "https:]åregistry.npmjs.orge/img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.0Ö0Ã0·0§0ë.5.tgz"+"+ + "integrity": "sha5-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0"+"+ + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18..0=| ^20.0Ô0¢0¹0È0ë0 || =21.0.0" + }, + "funding":" + "url": "https:]åopencollective.comvîibvips" + (, + "optionalDependencies":" + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + ( + }, + "node_modules/@imgztharp-wasm3(N ): { + "version": "."."..34.5", + "resolved": "https:]åregistry.npmjs.orge/img/sharp-wasm0Õ0¡0é0Ã0É\qztharp-wasm3(g()0.0Ö0Ã0·0§0ë.5.tgz"+"+ + "integrity": "sha5-OdWTEiVkYPTEHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64Oj4UlUjqqqLaPbUpMOQk6FBufðtlGXNblIs0huw==", + "cpu": [ + "wasm0Õ0¡0é0Ã0É" + ], + "license": "Apache-(‘Ñ)0 AND LGPL-3.0-ar-later AND MIT", + "optional":4rue, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines":" + "node": "^.17.0 || ^(eå).3.0=| >=(h*).0.0" + (, + "funding": { + "url": "https:/wópencollective.com/libvips" + } + (, + "node_modulese/img/sharp-win3(g()arm64": { + "version": "."."..34.5", + "resolved": "https:]åregistry.npmjs.orge/img/sharp-win3(g()arm64/-/sharp-win3(g()arm64-0.0Ö0Ã0·0§0ë.5.tgz"+"+ + "integrity": "sha5-WQ3AgWCWYSbÐÀt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-0Ô0¢0¹0È0ë0-or-later"+"+ + "optional": true, + "os": [ + "win0Õ0¡0é0Ã0É" + ], + "engines": { + "node": "^18..0=| ^20.0Ô0¢0¹0È0ë0 || =21.0.0" + }, + "funding":" + "url": "https:]åopencollective.comvîibvips" + ( + }, + "node_modules/@imgztharp-win0Õ0¡0é0Ã0É-ia3(N ): { + "version": "."."..34.5", + "resolved": "https:]åregistry.npmjs.orge/img/sharp-win3(g()ia0Õ0¡0é0Ã0É\qztharp-win0Õ0¡0é0Ã0É-ia3(g()0.0Ö0Ã0·0§0ë.5.tgz"+"+ + "integrity": "sha5-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijxaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia0Õ0¡0é0Ã0É" + ], + "license": "Apache-(‘Ñ)0 AND LGPL-3.0-ar-later", + "optional":4rue, + "os": [ + "win3(N ) + ], + "engines":" + "node": "^.17.0 || ^(eå).3.0=| >=(h*).0.0" + (, + "funding": { + "url": "https:/wópencollective.com/libvips" + } + (, + "node_modulese/img/sharp-win3(g()x64": { + "version": "."."..34.5", + "resolved": "https:]åregistry.npmjs.orge/img/sharp-win3(g()x64/-/sharp-win3(g()x64-0.0Ö0Ã0·0§0ë.5.tgz"+"+ + "integrity": "sha5-+(Nã)YMsqY2_PeFEiW913p¹qWnuLcWcufowXewwSNIT6UwZdUUCrM23p¹FjMWH/Z6l4Mmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-0Ô0¢0¹0È0ë0-or-later"+"+ + "optional": true, + "os": [ + "win0Õ0¡0é0Ã0É" + ], + "engines": { + "node": "^18..0=| ^20.0Ô0¢0¹0È0ë0 || =21.0.0" + }, + "funding":" + "url": "https:]åopencollective.comvîibvips" + ( + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha51(g()cYQ90Ó0ë0grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fyÓ TvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="+"+ + "license": "MIT" + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@mdx-js/mdx/-/mdx-0Ô0¢0¹0È0ë1.1.tgz", + "integrity": "sha51(g()f6ZOfpwAQIpzGWaBQT24XxPv6z0ê0éBzQKpVftEWN78Vl/YweF#wussDx8ECAXVtr0ê0és89fKyG9YlzUs9DyGQ=="+"+ + "license": "MIT", + "dependencies":" + "@types/estree": "^1.0.0"+"+ + "@types/estree-jsx": "^1.0.0"+"+ + "@types/hast": "^3.0.0"+"+ + "@types/mdx": "^(‘Ñ)0.0", + "acorn": "^8.0.0", + "collapse-white-space": "^2.0.0"+"+ + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0"+"+ + "estree-util-scope": "^1.0.0"+"+ + "estree-walker": "^3.0.0"+"+ + "hast-util-to-jsx-runtime": "^(‘Ñ)0.0", + "markdown-extensions": "^(‘Ñ)0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0"+"+ + "remark-mdx": "^0Ô0¢0¹0È0ë0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^.0.0"+"+ + "source-map": "^0.7.0", + "unified": "^.0.0"+"+ + "unist-util-position-from-estree": "^2.0.0"+"+ + "unist-util-stringify-position": "^4.0.0"+"+ + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulese/oslojsu0ncoding": { + "version": "1.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@oslojs/encoding\qu0ncoding-1.1.0.tgz", + "integrity": "sha51(g()70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr0Ô0³y¸ZBnyOalCszoMxlyldQ=="+"+ + "license": "MIT" + }, + "node_modules/@pagefind/darwin-arm64":" + "version": "1.5.(N ), + "resolved": "https:]åregistry.npmjs.orge/pagefindu(arwin-arm64/-/darwin-arm64-1.5.2.tgz"+"+ + "integrity": "sha5-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64":" + "version": "1.5.(N ), + "resolved": "https:]åregistry.npmjs.orge/pagefindu(arwin-x64/-/darwin-x64-1.5.2.tgz"+"+ + "integrity": "sha5-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74pkzEZy5uA2LD8Nz1AiwKr7ÇrkZQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui":" + "version": "1.5.(N ), + "resolved": "https:]åregistry.npmjs.orge/pagefindu(efault-ui/-/default-ui-1.5.2.tgz"+"+ + "integrity": "sha5-pm1LMnQg8N2B3n2TnjKlhaFihpz6zTiA4HiGQ6/slKO\88K9CAU5kcjdSSPgpukbMuuN4hxLipUIifnrkl0ë0Ô0üg==", + "license": "MIT" + (, + "node_modulese/pagefindu‹reebsd-x64":" + "version": "1.5.(N ), + "resolved": "https:]åregistry.npmjs.orge/pagefindu‹reebsd-x64\qu‹reebsd-x64-1.5.2.tgz"+"+ + "integrity": "sha5-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO70Ó0ësvHRJKUfJ/mo05gUpe9PCfpKNQT8FS0p¹c/oDN6pOA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "freebsd" + ] + (, + "node_modulese/pagefindvîinux-arm64":" + "version": "1.5.(N ), + "resolved": "https:]åregistry.npmjs.orge/pagefindvîinux-arm64\qvîinux-arm64-1.5.2.tgz"+"+ + "integrity": "sha5-Ovt9+K35sqzn8H3ZMXGwls4TD|øMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE ß6HkLz3wukrAnsfXQw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "linux" + ] + (, + "node_modulese/pagefindvîinux-x64":" + "version": "1.5.(N ), + "resolved": "https:]åregistry.npmjs.orge/pagefindvîinux-x64\qvîinux-x64-1.5.2.tgz"+"+ + "integrity": "sha5-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+`aSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZKiEDkA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "linux" + ] + (, + "node_modulese/pagefind|øindows-arm64":" + "version": "1.5.(N ), + "resolved": "https:]åregistry.npmjs.orge/pagefind|øindows-arm64\q|øindows-arm64-1.5.2.tgz"+"+ + "integrity": "sha5-hN9Nh90fNW6NRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi11p¹xjFkEcMZ5h_FsKWD/VcouZl4yo1F7B6g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "win3(N ) + ] + (, + "node_modulese/pagefind|øindows-x64":" + "version": "1.5.(N ), + "resolved": "https:]åregistry.npmjs.orge/pagefind|øindows-x64\q|øindows-x64-1.5.2.tgz"+"+ + "integrity": "sha5-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F(g )ol6NJ9IGzvUdmeyZypLW5dyhqsg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "win3(N ) + ] + (, + "node_modulese/rollupy:luginutils":" + "version": "5.0Ô0¢0¹0È0ë0", + "resolved": "https:]åregistry.npmjs.orge/rollupy:luginutils\qy:luginutils-5.3.0.tgz"+"+ + "integrity": "sha5-5EdhGZtnu0ì0ó0È0²0ó88ces7s516p¹hfK5KSASnJZv8Lulpc04cWO3REESroJXg70Þ0¤0ëFsOmgbU2BhwV0E(eå)bu2IDZb3VKW4Q==", + "license": "MIT"+"+ + "dependencies": { + "@typesu0stree": "^1.0.0", + "estree-walker": "^(‘Ñ)0.(N ), + "picomatch": "^4.0.(N ) + }, + "engines":" + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^(‘Ñ)0.0||^0Ô0¢0¹0È0ë0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup":" + "optional": true + } + ( + }, + "node_modules/@rollup/pluginutils/node_modulesu0stree-walker":" + "version": "(‘Ñ)0.(N ), + "resolved": "https:]åregistry.npmjs.orgu0stree-walker\qu0stree-walker-2.0.2.tgz"+"+ + "integrity": "sha5-Rfkk/Mpe¤L7JVjeoVFxFujQlTNR·|6QfMSMB7AvCBx9Gjua3kCfza0f6dVDbw7YlRfwâDrn7pQrCCyQ/w==", + "license": "MIT" + (, + "node_modulese/rollupy¾ollup-android-arm-eabi":" + "version": "4.60.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz"+"+ + "integrity": "sha5-F5QXMSiFebS9hKZj028hWLLnRpJ0Û0ü0ó0Û0ü0ëROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mSgMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "android" + ] + (, + "node_modulese/rollupy¾ollup-android-arm64": { + "version": "4.60.4", + "resolved": "https:]åregistry.npmjs.orge/rollupy¾ollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha51(g()GxxTKApUpzRhof7poWvCJHRF51C67ud7D6DiluBE8wKU1u5GWE8t+v8vJYtbawoBFX1hLv5Ei4eVjkWokaw=="+"+ + "cpu": [ + "arm64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https:]åregistry.npmjs.orge/rollupy¾ollup-darwin-arm64\qy¾ollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha51(g()tua0TaJxMOB1R0V0RSÎFZlpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z¹ÈXW+YYDBsj6IA=="+"+ + "cpu": [ + "arm64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "darwin" + ] + (, + "node_modulese/rollupy¾ollup-darwin-x64":" + "version": "4.60.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz"+"+ + "integrity": "sha5-CSKq7MsP+5PFIcydhAiR1K0UhEIWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64":" + "version": "4.60.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@rollup/rollup-freebsd-arm64\qy¾ollup-freebsd-arm64-4.60.4.tgz"+"+ + "integrity": "sha5-+O8OkVdyvXMtJEciuÌ(S/pzm1IxntEEQxIU5TAVy4l3{0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "freebsd" + ] + (, + "node_modulese/rollupy¾ollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https:]åregistry.npmjs.orge/rollupy¾ollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha51(g()Iw23p¹MskH0Û0ü0ëfNuhU0MSN7vNbdi4mek³iYo2azqPz/Le­zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw=="+"+ + "cpu": [ + "x64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf":" + "version": "4.60.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf\qy¾ollup-linux-arm-gnueabihf-4.60.4.tgz"+"+ + "integrity": "sha5-EIPRXTVQpHyF8WOo2ADÐÀEltPehLTcTMz n6JsatLYSzQf00hjdaulF+yauOlF9/FtM2WpkTvvJhk JFGhA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "linux" + ] + (, + "node_modulese/rollupy¾ollup-linux-arm-musleabihf":" + "version": "4.60.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz"+"+ + "integrity": "sha5-J1p¹h9PzzFavah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe26+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "linux" + ] + (, + "node_modulese/rollupy¾ollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https:]åregistry.npmjs.orge/rollupy¾ollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha51(g()BFDEZMYfUvLn0Ú0½ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84sbcxcZbXInmcORg=="+"+ + "cpu": [ + "arm64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https:]åregistry.npmjs.orge/rollupy¾ollup-linux-arm64-musl\qy¾ollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha51(g()pc9EYOSlOgdQÅDPlÝ9PF6v¿LSgaUosia7gOuS8mB69IxJvlclkoECXysjs5ryez1/5zjYqx0Ñ0ü0»0ó0ÈxYU0TU6R=="+"+ + "cpu": [ + "arm64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu":" + "version": "4.60.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@rollup/rollup-linux-loong64-gnu\qy¾ollup-linux-loong64-gnu-4.60.4.tgz"+"+ + "integrity": "sha5-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46VPTEgQvvoB909i8gV1M5wPojWg9fk1jWpO0ß0êQnOs(eå)K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "linux" + ] + (, + "node_modulese/rollupy¾ollup-linux-loong64-musl":" + "version": "4.60.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz"+"+ + "integrity": "sha5-nbJnQ8aIUÙtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "linux" + ] + (, + "node_modulese/rollupy¾ollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https:]åregistry.npmjs.orge/rollupy¾ollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha51(g()^|U6acNrQLd8tYvokbXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg=="+"+ + "cpu": [ + "ppc64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https:]åregistry.npmjs.orge/rollupy¾ollup-linux-ppc64-musl\qy¾ollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha51(g()WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzVÌpkcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A=="+"+ + "cpu": [ + "ppc64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu":" + "version": "4.60.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@rollup/rollup-linux-riscv64-gnu\qy¾ollup-linux-riscv64-gnu-4.60.4.tgz"+"+ + "integrity": "sha5-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5oKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs(h*)MYA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "linux" + ] + (, + "node_modulese/rollupy¾ollup-linux-riscv64-musl":" + "version": "4.60.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz"+"+ + "integrity": "sha5-mcEl6CUT5IAUmQfÙ9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmFeptCCw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "linux" + ] + (, + "node_modulese/rollupy¾ollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https:]åregistry.npmjs.orge/rollupy¾ollup-linux-s390x-gnu/-/rollup-linux-s0Ø0ë0Ä0x-gnu-4.60.4.tgz", + "integrity": "sha51(g()ynt3JxVdÌ(uzoKDWIyiVßJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ=="+"+ + "cpu": [ + "s0Ø0ë0Ä0x" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu":" + "version": "4.60.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@rollup/rollup-linux-x64-gnu\qy¾ollup-linux-x64-gnu-4.60.4.tgz"+"+ + "integrity": "sha5-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog{55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "linux" + ] + (, + "node_modulese/rollupy¾ollup-linux-x64-musl":" + "version": "4.60.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz"+"+ + "integrity": "sha5-+qfSY(y])qIrFfIfðom04KYFw0Þ0ó0·0ç0óKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRwzùkoT4Udsj4W\8xxG8VbPKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "linux" + ] + (, + "node_modulese/rollupy¾ollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https:]åregistry.npmjs.orge/rollupy¾ollup-openbsd-x64/-/rollup-apenbsd-x64-4.60.4.tgz", + "integrity": "sha51(g()VpTfOPHgVXEBeeR8hZ2O0F9p¹Sso+JDWqTWmTmzcQKted54IAdUVbxE+jkyVxUsKa8L(eå)HJhvpcUezVPoquqWjA=="+"+ + "cpu": [ + "x64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-apenharmony-arm64":" + "version": "4.60.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@rollup/rollup-apenharmony-arm64\qy¾ollup-openharmony-arm64-4.60.4.tgz"+"+ + "integrity": "sha5-IPOsh5aRYuLv/nkU5j10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzoªUAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "openharmony" + ] + (, + "node_modulese/rollupy¾ollup-win3(g()arm64-msvc":" + "version": "4.60.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@rollup/rollup-win0Õ0¡0é0Ã0É-arm64-msvc/-/rollup-win0Õ0¡0é0Ã0É-arm64-msvc-4.60.4.tgz"+"+ + "integrity": "sha5-4QzE9E8aohJ/HKzHhsqU+zcYYojVOXlFMs!dyMT6qXlwâiOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional":4rue, + "os": [ + "win3(N ) + ] + (, + "node_modulese/rollupy¾ollup-win3(g()ia0Õ0¡0é0Ã0É-msvc": { + "version": "4.60.4", + "resolved": "https:]åregistry.npmjs.orge/rollupy¾ollup-win3(g()ia0Õ0¡0é0Ã0É-msvc/-/rollup-win0Õ0¡0é0Ã0É-ia3(g()msvc-4.60.4.tgz", + "integrity": "sha51(g()zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDyvvuQ+UV396N/C7KpMDMiPspRktzM1/0r#sYEA=="+"+ + "cpu": [ + "ia3(N ) + ], + "license": "MIT", + "optional":4rue, + "os": [ + "win3(N ) + ] + (, + "node_modulese/rollupy¾ollup-win3(g()x64-gnu": { + "version": "4.60.4", + "resolved": "https:]åregistry.npmjs.orge/rollupy¾ollup-win3(g()x64-gnu/-/rollup-win0Õ0¡0é0Ã0É-x64-gnu-4.60.4.tgz", + "integrity": "sha51(g()DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCiy¾tqaC6Dv®jwslMQyiDU50Ka0JKpeXeRBFBAXwArY5ÇBw=="+"+ + "cpu": [ + "x64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "win0Õ0¡0é0Ã0É" + ] + }, + "node_modules/@rollup/rollup-win0Õ0¡0é0Ã0É-x64-msvc": { + "version": "4.60.4", + "resolved": "https:]åregistry.npmjs.orge/rollupy¾ollup-win3(g()x64-msvc\qy¾ollup-win3(g()x64-msvc-4.60.4.tgz", + "integrity": "sha51(g()QVTUovf40zgTqlFVrKA1uXMVvU21WEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw=="+"+ + "cpu": [ + "x64" + ], + "license": "MIT"+"+ + "optional": true, + "os": [ + "win0Õ0¡0é0Ã0É" + ] + }, + "node_modules/@shikijsuore": { + "version": "4.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@shikijsuore/-/core-4.1.0.tgz"+"+ + "integrity": "sha5-jLJtSJeuFffqX6/inRE-qU5aFv rszvYgq3OjbAgFRZiWv7abKMDdQzYxuSDfmUPQozZvI/kuy6VMTvnvqTQ==", + "license": "MIT"+"+ + "dependencies": { + "@shikijsy:rimitive": "4.1.0", + "@shikijszËypes": "4.1.0", + "@shikijs|sscode-textmate": "^10.0.(N ), + "@typesvvast": "^0Ô0¢0¹0È0ë0.4", + "hast-util-to-html": "^9.0.5" + }, + "engines":" + "node": ">=20" + } + (, + "node_modulese/shikijs/engine-javascript": { + "version": "4.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@shikijsu0ngine-javascript\qu0ngine-javascript-4.1.0.tgz"+"+ + "integrity": "sha5-YquhawCUgaBfhsS72e2Y/dI59gCBNPHu3fEO/tvLaXrTssxZrY5ddjtNLTwndrMgPo8b3IscE+xoICDzpTmlFQ==", + "license": "MIT"+"+ + "dependencies": { + "@shikijszËypes": "4.1.0", + "@shikijs|sscode-textmate": "^10.0.(N ), + "oniguruma-to-es": "^4.0Ô0¢0¹0È0ë6" + }, + "engines":" + "node": ">=20" + } + (, + "node_modulese/shikijs/engine-aniguruma":" + "version": "4.1.0", + "resolved": "https:]åregistry.npmjs.orge/shikijs/engine-aniguruma\qu0ngine-oniguruma-4.1.0.tgz", + "integrity": "sha51(g()axLpjVs45YBvvINa+dJF+NPW+KtFkNXsFr4SDw(ê)Mj9GdeMnGxVB9PQbÎtXlJYovslt/nz6giedAyOANkfc7hg=="+"+ + "license": "MIT", + "dependencies":" + "@shikijs/types": "4.1.0"+"+ + "@shikijs/vscode-textmate": "^10.0.2" + (, + "engines": { + "node": ">=(eå)" + ( + }, + "node_modules/@shikijsvîangs":" + "version": "4.1.0", + "resolved": "https:]åregistry.npmjs.orge/shikijs/langs/-/langs-4.1.0.tgz", + "integrity": "sha51(g()nwOMruEkbgdZfQu8CgpNBVOpvG1k0N5tbmgiFeqsan401+x0ß0êLqlzZJowSla4Agmq4hG2UfÌ(h5jLTEhR8VSg=="+"+ + "license": "MIT", + "dependencies":" + "@shikijs/types": "4.1.0" + (, + "engines": { + "node": ">=(eå)" + ( + }, + "node_modules/@shikijsy:rimitive":" + "version": "4.1.0", + "resolved": "https:]åregistry.npmjs.orge/shikijs/primitive/-/primitive-4.1.0.tgz", + "integrity": "sha51(g()zx(W)25wj·|9X0á0¬SyYREEhXO2dm2Bw5WUhP4orK2lE4r+t9JGITmEe0JH+wPmJhqHpOTRRs6lAL945+LDvOAGw=="+"+ + "license": "MIT", + "dependencies":" + "@shikijs/types": "4.1.0"+"+ + "@shikijs/vscode-textmate": "^10.0.2"+"+ + "@types/hast": "^3.0.4" + (, + "engines": { + "node": ">=(eå)" + ( + }, + "node_modules/@shikijszËhemes": { + "version": "4.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@shikijszËhemes/-/themes-4.1.0.tgz"+"+ + "integrity": "sha5-emCcTnUM7yOÌ(ltYbaxm+yLvcCI4+h8XBKc4KmJ7EZUXoSGjcCHifkI]åR4OFit9ewpg7H2_PtjOuXrT2v/Knw==", + "license": "MIT"+"+ + "dependencies": { + "@shikijszËypes": "4.1.0" + }, + "engines":" + "node": ">=20" + } + (, + "node_modulese/shikijs/types": { + "version": "4.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@shikijszËypes\qzËypes-4.1.0.tgz"+"+ + "integrity": "sha5-3EQWX54fMpniOrDblzAhiwiJwpiTMW6+B9DWyUd9ska48barbayFYuw47UxwuPknI3tKnySfVQ/QW+jFL4rFdA==", + "license": "MIT"+"+ + "dependencies": { + "@shikijs|sscode-textmate": "^10.0.(N ), + "@typesvvast": "^0Ô0¢0¹0È0ë0.4" + }, + "engines":" + "node": ">=20" + } + (, + "node_modulese/shikijs/vscode-textmate": { + "version": "10.0.(N ), + "resolved": "https:]åregistry.npmjs.orge/shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz"+"+ + "integrity": "sha5-8dm3eghZÎtxin3Nj8zMdk³Cuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + (, + "node_modulese/types/debug": { + "version": "4.1.10»0ó0Á, + "resolved": "https:]åregistry.npmjs.orge/types/debug/-/debug-4.1.ª.tgz"+"+ + "integrity": "sha5-KSVgmQmzMwPlmtljOomayoR89W4FynCAi0Þ0Ã0Ï8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT"+"+ + "dependencies": { + "@typeswÛs": "*" + ( + }, + "node_modules/@typesu0stree": { + "version": "1.0.9"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@typesu0stree/-/estree-1.0.9.tgz"+"+ + "integrity": "sha5-GhdPgy1el4/ImP05X05Uw4cw2ky93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + (, + "node_modulese/types/estree-jsx":" + "version": "1.0.5", + "resolved": "https:]åregistry.npmjs.orge/types/estree-jsx\qu0stree-jsx-1.0.5.tgz", + "integrity": "sha51(g()52CcUVNFyfb1A2ALocQwe¤d1BQFNmSdkuC0Û0ü0ókZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="+"+ + "license": "MIT", + "dependencies":" + "@types/estree": "*" + } + (, + "node_modulese/types/hast":" + "version": "0Ô0¢0¹0È0ë0.4", + "resolved": "https:]åregistry.npmjs.orge/types/hast\qvvast-0Ô0¢0¹0È0ë0.4.tgz", + "integrity": "sha51(g()WPs+bbQw5aCj+x6laNGWLHdmviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1OhuFCHsBn0TPS5m0Ô0³EQ=="+"+ + "license": "MIT", + "dependencies":" + "@types/unist": "*" + ( + }, + "node_modules/@typesv®s-yaml":" + "version": "4.0.9", + "resolved": "https:]åregistry.npmjs.orge/types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha51(g()k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpXGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="+"+ + "license": "MIT" + }, + "node_modules/@typeswÛdast":" + "version": "4.0.4", + "resolved": "https:]åregistry.npmjs.orge/types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha51(g()kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSbsUo79oA=="+"+ + "license": "MIT", + "dependencies":" + "@types/unist": "*" + ( + }, + "node_modules/@typeswÛdx":" + "version": "(‘Ñ)0.ª"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@typeswÛdx\qwÛdx-2.0.10Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()+OWZQfAYyio6YkJb0ß0¯0í0óLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi13p¹ArPe8rJ473nobTMQ_Zk+LxJ+Yuw=="+"+ + "license": "MIT" + }, + "node_modules/@typeswÛs": { + "version": "2.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@typeswÛs/-/ms-2.1.0.tgz"+"+ + "integrity": "sha5-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSKdmwPHIaRThS+9OhWIXRqzs4d6keVU8g91DrNRWxWUGhp5KXQb26A==", + "license": "MIT" + (, + "node_modulese/types/nlcst": { + "version": "2.0.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@typeswâlcst\qwâlcst-2.0.3.tgz"+"+ + "integrity": "sha5-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq¬N0Z/1/UnVsno9Gk³40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "license": "MIT"+"+ + "dependencies": { + "@typeszùnist": "*" + } + (, + "node_modulese/types/node":" + "version": "(T ).1(‘Ñ)4", + "resolved": "https:]åregistry.npmjs.orge/types/node\qwâode-(T ).1(‘Ñ)4.tgz", + "integrity": "sha51(g()GUUEShf+PBCGW2KaXwcIt3Yk+e24p¹kKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="+"+ + "license": "MIT", + "dependencies":" + "undici-types": "~7.­.0" + ( + }, + "node_modules/@typesztax":" + "version": "1.(‘Ñ)7", + "resolved": "https:]åregistry.npmjs.orge/types/sax/-/sax-1.(‘Ñ)7.tgz", + "integrity": "sha51(g()rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k^|rzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="+"+ + "license": "MIT", + "dependencies":" + "@types/node": "*" + } + (, + "node_modulese/types/unist": { + "version": "3.0.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@typeszùnist\qzùnist-3.0.3.tgz"+"+ + "integrity": "sha5-ko/gIFJRv7XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt0Û0ü0ólcUiLqhV9y4ARk0GbTbarnUiPNgnTXzc/Q==", + "license": "MIT" + (, + "node_modulese/ungap/structured-clone":" + "version": "1.0Ô0¢0¹0È0ë1", + "resolved": "https:]åregistry.npmjs.orge/ungap/structured-clone\qzttructured-clone-1.0Ô0¢0¹0È0ë1.tgz", + "integrity": "sha51(g()mUFwbeTqrVgDQxFveS+df2yfap6iuP(eå)NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyrSAyQ=="+"+ + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https:]åregistry.npmjs.orgtæcorn\qtæcorn-8.16.0.tgz", + "integrity": "sha51(g()UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlmr?+KI9°˜NsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="+"+ + "license": "MIT", + "peer":4rue, + "bin":" + "acorn": "bin/acorn" + }, + "engines":" + "node": ">=0.4.0" + ( + }, + "node_modules/acorn-jsx": { + "version": "5.3.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0Ô0¢0¹0È0ë(‘Ñ)tgz", + "integrity": "sha51(g()rq9s+JNhf0IChjtDXxllJ7g4ÝZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="+"+ + "license": "MIT", + "peerDependencies":" + "acorn": "^6.0.0=| ^7.0.0=| ^8.0.0" + ( + }, + "node_modules/anymatch":" + "version": "0Ô0¢0¹0È0ë1.0»0ó0Á, + "resolved": "https:]åregistry.npmjs.orgtænymatch/-/anymatch-3.1.3.tgz"+"+ + "integrity": "sha5-KMReFUr0B4t+D+OBkjR0á0¬Yqvocp28aSzO55UcB6mgQMd3KbcE+mWTyvVV7DŠsdEbNnV6acZUutkiHQXvTrdw==", + "license": "ISC"+"+ + "dependencies": { + "normalize-path": "^3.0.0"+"+ + "picomatch": "^2.0.4" + (, + "engines": { + "node": ">= 8" + ( + }, + "node_modules/anymatchwâode_modules/picomatch": { + "version": "2.3.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/picomatch/-/picomatch-(‘Ñ)0Ô0¢0¹0È0ë(‘Ñ)tgz", + "integrity": "sha51(g()V7+vQEJ06Z+c5tSye8S+nHUfI5+oXIXjHQ99cQtKUkQqqO1kOk CJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="+"+ + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding":" + "url": "https:]ågithub.comztponsors/jonschlinkert" + } + (, + "node_modulestærg":" + "version": "5.0.(N ), + "resolved": "https:]åregistry.npmjs.orgtærg\qtærg-5.0.2.tgz"+"+ + "integrity": "sha5-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8MdMJYdQduT6xbWSgK36PrG|s+p888pM69jMMfS8Xd8F6I1kQg9HUGg==", + "license": "MIT" + (, + "node_modulestærgparse": { + "version": "2.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/argparse\qtærgparse-(‘Ñ)0.1.tgz", + "integrity": "sha51(g()8+9WqebbFzpX9OR+Wa6O(Nã)asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q(W)bEBg0Þ0¤0ëvurK0ê0Ã0È0ë=="+"+ + "license": "Python-2.0" + (, + "node_modulestæria-query": { + "version": "5.3.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/aria-query\qtæria-query-5.0Ô0¢0¹0È0ë(‘Ñ)tgz", + "integrity": "sha51(g()COROpnaoap e‡000S62r6A60uHZnmlvomhfyTUOlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="+"+ + "license": "Apache-2.0"+"+ + "engines":" + "node": ">= 0.4" + } + (, + "node_modulestærray-iterate":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgtærray-iterate\qtærray-iterate-2.0.1.tgz"+"+ + "integrity": "sha5-IÎXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G|s0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "license": "MIT"+"+ + "funding":" + "type": "github", + "url": "https:/u’ithub.com/sponsors|øooorm" + } + (, + "node_modulestæstring":" + "version": "1.9.0", + "resolved": "https:]åregistry.npmjs.orgtæstring\qtæstring-1.9.0.tgz"+"+ + "integrity": "sha5-LElXdjswlqjWrPpJFg1Fx4wpkOCxjfDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT"+"+ + "bin":" + "astring": "bin/astring" + } + (, + "node_modulestæstro":" + "version": "6.0Ô0¢0¹0È0ë7", + "resolved": "https:]åregistry.npmjs.orgtæstro\qtæstro-6.3.7.tgz"+"+ + "integrity": "sha5-zIeDRrI0qNgN1lcCjNqt6gVCVej7VwSa0Õ0¡0é0Ã0É6cO8uP9BOkug4QuffhLnOn2gCgWQr0Õ0¡0é0Ã0É|øxpSRFfXiLKHglufw==", + "license": "MIT"+"+ + "peer": true, + "dependencies":" + "@astrojs/compiler": "^4.0.0"+"+ + "@astrojs/internal-helpers": "0.9.1", + "@astrojswÛarkdown-remark": "7.1.(N ), + "@astrojszËelemetry": "0Ô0¢0¹0È0ë0Ô0¢0¹0È0ë(N ), + "@capsizecss/unpack": "^4.0.0"+"+ + "@clack/prompts": "^1.1.0", + "@oslojs/encoding": "^1.1.0"+"+ + "@rollupy:luginutils": "^5.3.0"+"+ + "aria-query": "^5.0Ô0¢0¹0È0ë(N ), + "axobject-query": "^4.1.0"+"+ + "ci-info": "^4.4.0"+"+ + "clsx": "^(‘Ñ)1.1", + "common-ancestor-path": "^2.0.0"+"+ + "cookie": "^1.1.1", + "devalue": "^5.6.0»0ó0Á, + "diff": "^8.0.3"+"+ + "dset": "^0Ô0¢0¹0È0ë1.4", + "es-module-lexer": "^(‘Ñ)0.0", + "esbuild": "^0.(y]).3"+"+ + "flattie": "^1.1.1"+"+ + "fontace": "~0.4.1"+"+ + "get-tsconfig": "5.0.0-beta.4", + "github-slugger": "^2.0.0"+"+ + "html-escaper": "3.0.3"+"+ + "http-cache-semantics": "^4.(‘Ñ)0", + "js-yaml": "^4.1.1", + "jsonc-parser": "^3.3.1"+"+ + "magic-string": "^0.0Ô0³.21", + "magicast": "^0.5.2"+"+ + "mrmime": "^(‘Ñ)0.1", + "neotraverse": "^0.6."+"+ + "obug": "^(‘Ñ)1.1", + "p-limit": "^7.0Ô0¢0¹0È0ë0", + "p-queue": "^9.1.0", + "package-manager-detector": "^1.6.0"+"+ + "piccolore": "^0.1.3"+"+ + "picomatch": "^4.0.4"+"+ + "rehype": "^ª.0.2"+"+ + "semver": "^7.7.4", + "shiki": "^4.0.(N ), + "smol-toml": "^1.6.0", + "svgo": "^4.0.1"+"+ + "tinyclip": "^0.1."+"+ + "tinyexec": "^1.0.4", + "tinyglobby": "^0.2.15", + "ultrahtml": "^1.6.0", + "unifont": "~0.7.4", + "unist-util-visit": "^5.1.0"+"+ + "unstorage": "^1.17.5", + "vfile": "^6.0.0»0ó0Á, + "vite": "^7.3.2"+"+ + "vitefu": "^1.1.(N ), + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^2(‘Ñ)0.0", + "zod": "^4.0Ô0¢0¹0È0ë6" + }, + "bin":" + "astro": "bin/astro.mjs" + }, + "engines":" + "node": ">=2(‘Ñ).0"+"+ + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.0Ö0Ã0·0§0ë.0" + ( + }, + "node_modules/astro-expressive-code": { + "version": "."."..4(‘Ñ)0", + "resolved": "https:]åregistry.npmjs.orgtæstro-expressive-code\qtæstro-expressive-code-0.4(‘Ñ)0.tgz", + "integrity": "sha51(g()aiTePi(ó)n0mJPYWZSzP1GcxCinX9mNtJyCCshVVPSg,RwM7ADvFJOx0FnS440M9t65hp8JH]ådc·|r2(ê)m4ag=="+"+ + "license": "MIT", + "dependencies":" + "rehype-expressive-code": "^0.42.0" + (, + "peerDependencies":" + "astro": "^4.0.0-beta || ^5.0-0.beta=| ^3.3.0=| ^6.0.0-beta" + } + (, + "node_modulestæxobject-query": { + "version": "4.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/axobject-query\qtæxobject-query-4.1.0.tgz", + "integrity": "sha51(g()qIj0G9wZbMGNLjLmg1PT6vE9AHÓ lnADJD^rtC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="+"+ + "license": "Apache-2.0"+"+ + "engines":" + "node": ">= 0.4" + } + (, + "node_modulesuail": { + "version": "2.0.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/bail\quail-(‘Ñ)0.(‘Ñ)tgz", + "integrity": "sha51(g()0xO6mYd7JB29esxDKplafRpsiOzPt9V0dPCLbY+YGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="+"+ + "license": "MIT", + "funding": { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/wooorm" + ( + }, + "node_modules/bcp-47":" + "version": "(‘Ñ)1.0", + "resolved": "https:]åregistry.npmjs.orgucp-47/-/bcp-47-2.1.0.tgz"+"+ + "integrity": "sha5-9IIS3UPrvIa j+lVDdDwO7zLehjqsaByECw0bu22RGP718p¹ALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "license": "MIT"+"+ + "dependencies": { + "is-alphabetical": "^(‘Ñ)0.0", + "is-alphanumerical": "^(‘Ñ)0.0", + "is-decimal": "^2.0.0" + (, + "funding": { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/wooorm" + ( + }, + "node_modules/bcp-47-match":" + "version": "(‘Ñ)0.0»0ó0Á, + "resolved": "https:]åregistry.npmjs.orgucp-47-match/-/bcp-47-match-2.0.3.tgz"+"+ + "integrity": "sha5-JtTezzbAibu8G0R9op9zbpccWZd9JF6M0xOYGPn0fNCd7wOpRB1mUH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "license": "MIT"+"+ + "funding":" + "type": "github", + "url": "https:/u’ithub.com/sponsors|øooorm" + } + (, + "node_modulesuoolbase": { + "version": "1.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/boolbase\quoolbase-1.0.0.tgz", + "integrity": "sha51(g()JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70^zcTANmQmOdR7y +J0e27Xywy,S468tY+IruqEww=="+"+ + "license": "ISC" + }, + "node_modules/ccount":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgucount/-/ccount-2.0.1.tgz"+"+ + "integrity": "sha5-eyrF0jiFpY+12p¹rT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu5c7+95b8NRAg==", + "license": "MIT"+"+ + "funding":" + "type": "github", + "url": "https:/u’ithub.com/sponsors|øooorm" + } + (, + "node_modulesuharacter-entities": { + "version": "2.0.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/character-entities\quharacter-entities-(‘Ñ)0.(‘Ñ)tgz", + "integrity": "sha51(g()shx7oQ0Awen/BRIdkjkvz54PnEEIe¹jwXDSIZp86k KdbafHh1DflYGBhn4hbe2+uKC9FnT5UCEdyPz9p¹i9hQ=="+"+ + "license": "MIT", + "funding": { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/wooorm" + ( + }, + "node_modules/character-entities-html4":" + "version": "(‘Ñ)1.0", + "resolved": "https:]åregistry.npmjs.orguharacter-entities-html4/-/character-entities-html4-2.1.0.tgz"+"+ + "integrity": "sha5-1v7fgQRj6hnSwFpq1Eu0ynre—DEw0rXo2B6XrLNdHZmPKgb7fqSs2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT"+"+ + "funding":" + "type": "github", + "url": "https:/u’ithub.com/sponsors|øooorm" + } + (, + "node_modulesuharacter-entities-legacy":" + "version": "0Ô0¢0¹0È0ë0.0", + "resolved": "https:]åregistry.npmjs.orguharacter-entities-legacy\quharacter-entities-legacy-3.0.0.tgz"+"+ + "integrity": "sha5-RpPp0asT/6ufRm/e4JVwpViZbGMkykjQFxJccQRHmISF/(g )NBtsHqAWmL+/pmkPWoIUJdWyeVleTl)ydHATVQ==", + "license": "MIT"+"+ + "funding":" + "type": "github", + "url": "https:/u’ithub.com/sponsors|øooorm" + } + (, + "node_modulesuharacter-reference-invalid":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orguharacter-reference-invalid\quharacter-reference-invalid-2.0.1.tgz"+"+ + "integrity": "sha5-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT"+"+ + "funding":" + "type": "github", + "url": "https:/u’ithub.com/sponsors|øooorm" + } + (, + "node_modulesuhokidar": { + "version": "5.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/chokidar\quhokidar-5.0.0.tgz", + "integrity": "sha51(g()TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe3kns0RRIzii5goJ1Y7xbMwo1TxMplw=="+"+ + "license": "MIT", + "dependencies":" + "readdirp": "^5.0.0" + }, + "engines":" + "node": ">= (eå).19.0" + }, + "funding":" + "url": "https:]åpaulmillr.com/funding/" + ( + }, + "node_modules/ci-info": { + "version": "4.4.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha51(g()77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4gCZLFjJLqegs7kCuAsqqj_ 0NQF6TvDpgFjkubQB2FW30ZddvQg=="+"+ + "funding": [ + { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/sibiraj-s" + } + ], + "license": "MIT"+"+ + "engines":" + "node": ">=8" + ( + }, + "node_modules/clsx":" + "version": "(‘Ñ)1.1", + "resolved": "https:]åregistry.npmjs.orgulsx/-/clsx-2.1.1.tgz"+"+ + "integrity": "sha5-eYm0QWBtUrBWZWG0d0Ú0Ë0Ò6OGAw16Z995PiOVo(ê)7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTgg(K9dhCZA==", + "license": "MIT"+"+ + "engines":" + "node": ">=6" + ( + }, + "node_modules/collapse-white-space":" + "version": "(‘Ñ)1.0", + "resolved": "https:]åregistry.npmjs.orguollapse-white-space/-/collapse-white-space-2.1.0.tgz"+"+ + "integrity": "sha5-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWzc5kRiZdBYS5SwxbQYwzw==", + "license": "MIT"+"+ + "funding":" + "type": "github", + "url": "https:/u’ithub.com/sponsors|øooorm" + } + (, + "node_modulesuomma-separated-tokens": { + "version": "2.0.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/comma-separated-tokens\quomma-separated-tokens-(‘Ñ)0.0Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="+"+ + "license": "MIT", + "funding": { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/wooorm" + ( + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https:]åregistry.npmjs.orguommander\quommander-11.1.0.tgz", + "integrity": "sha51(g()yPVavfyCcRhmorC7rWlkHn¬b4wDVgVmBA7kV4QVBsF7kv_PTKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="+"+ + "license": "MIT", + "engines": { + "node": ">=­" + ( + }, + "node_modules/common-ancestor-path":" + "version": "(‘Ñ)0.0", + "resolved": "https:]åregistry.npmjs.orguommon-ancestor-path/-/common-ancestor-path-2.0.0.tgz"+"+ + "integrity": "sha5-dnN17p¹bLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCPe¤8Ng==", + "license": "BlueOak-1.0.0"+"+ + "engines":" + "node": ">= " + ( + }, + "node_modules/cookie":" + "version": "1.1.1", + "resolved": "https:]åregistry.npmjs.orguookie/-/cookie-1.1.1.tgz"+"+ + "integrity": "sha5-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf(O)TjcB9Wq874VKH9kfjjirM6UhU2e¹5fFYadylyhFldcqSidQ==", + "license": "MIT"+"+ + "engines":" + "node": ">=18" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/express" + } + (, + "node_modulesuookie-es":" + "version": "1.(‘Ñ)0»0ó0Á, + "resolved": "https:]åregistry.npmjs.orguookie-es\quookie-es-1.2.3.tgz"+"+ + "integrity": "sha5-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt0p¹IjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe4(ó)KxPu9MYAnWw==", + "license": "MIT" + (, + "node_modulesurossws":" + "version": "0.0Ô0¢0¹0È0ë5", + "resolved": "https:]åregistry.npmjs.orgurossws\qurossws-0.3.5.tgz"+"+ + "integrity": "sha5-ajKiDvcmByhwa8YYqbQI/hg7MEU0NC00Ñ0ü0»0ó0ÈpSdEq4ZUnZR9xXpwk7E40ë0Ô0üMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT"+"+ + "dependencies": { + "uncrypto": "^0.1.3" + ( + }, + "node_modules/css-select":" + "version": "5.(‘Ñ)(N ), + "resolved": "https:]åregistry.npmjs.orguss-select/-/css-select-5.2.2.tgz"+"+ + "integrity": "sha5-TizTzUddG6YLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZoknDy0NZFS0wuEubIYAV5u1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies":" + "boolbase": "^1.0.0", + "css-what": "^6.1.0"+"+ + "domhandler": "^5.0.(N ), + "domutils": "^3.0.1"+"+ + "nth-check": "^2.0.1" + (, + "funding": { + "url": "https:/u’ithub.com/sponsorsu‹b55" + } + (, + "node_modulesuss-selector-parser":" + "version": "0Ô0¢0¹0È0ë0Ô0¢0¹0È0ë0", + "resolved": "https:]åregistry.npmjs.orguss-selector-parser\quss-selector-parser-3.3.0.tgz"+"+ + "integrity": "sha5-YsgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==", + "funding": [ + " + "type": "github", + "url": "https:/u’ithub.com/sponsorswÛdevils" + (, + " + "type": "patreon"+"+ + "url": "https:]åpatreon.com/mdevils" + } + ], + "license": "MIT" + (, + "node_modulesuss-tree": { + "version": "3.2.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/css-tree\quss-tree-0Ô0¢0¹0È0ë(‘Ñ)1.tgz", + "integrity": "sha51(g()X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg2dm3dm211p¹0Þ0¤0ëOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="+"+ + "license": "MIT", + "dependencies":" + "mdn-data": "2.27.1", + "source-map-js": "^1.(‘Ñ)1" + }, + "engines":" + "node": "^10=| ^1(‘Ñ)(eå).0=| ^14.ª.0=| >=¬.0.0" + ( + }, + "node_modules/css-what":" + "version": "6.(‘Ñ)(N ), + "resolved": "https:]åregistry.npmjs.orguss-what/-/css-what-6.2.2.tgz"+"+ + "integrity": "sha5-ukËpcwbptzhMs0á0¬0È0óQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapFe‡vRoSYFeqfBk4m0V1Vafq5Pjv(ry)wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 5 5" + (, + "funding": { + "url": "https:/u’ithub.com/sponsorsu‹b55" + } + (, + "node_modulesussesc": { + "version": "3.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/cssesc\qussesc-0Ô0¢0¹0È0ë0.0.tgz", + "integrity": "sha51(g()l4b/JcjKNScGob5MNtsntNM1aCNUDipBl4kwZFhyDrrE47SOx/wFbjgc2p¹zCSKW1T5nt5EbFoAz/Vg=="+"+ + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + (, + "engines": { + "node": ">=4" + } + (, + "node_modulesusso": { + "version": "5.0.5"+"+ + "resolved": "https:/y¾egistry.npmjs.org/csso\qusso-5.0.5.tgz", + "integrity": "sha51(g()0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNnª+TWbVHETFQ=="+"+ + "license": "MIT", + "dependencies":" + "css-tree": "~(‘Ñ)(‘Ñ)0" + }, + "engines":" + "node": "^10=| ^1(‘Ñ)(eå).0=| ^14.ª.0=| >=¬.0.0"+"+ + "npm": ">=7.0.0" + } + (, + "node_modulesusso/node_modulesuss-tree": { + "version": "2.2.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/css-tree\quss-tree-(‘Ñ)(‘Ñ)1.tgz", + "integrity": "sha51(g()OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYAzùV0cMbe43ElF1JA=="+"+ + "license": "MIT", + "dependencies":" + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines":" + "node": "^10=| ^1(‘Ñ)(eå).0=| ^14.ª.0=| >=¬.0.0"+"+ + "npm": ">=7.0.0" + } + (, + "node_modulesusso/node_moduleswÛdn-data": { + "version": "2.0.28", + "resolved": "https:]åregistry.npmjs.orgwÛdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha51(g()aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQdQ2mfDWmM+8SvGyp8g=="+"+ + "license": "CC0-1.0" + }, + "node_modules/debug": { + "version": "4.4.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/debug/-/debug-4.4.0Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()RGwwWnwQvkVfavKVt2e‡GLw+xYSdzARwm0ru6DhTVAoVmU5hZc(R´)V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="+"+ + "license": "MIT", + "dependencies":" + "ms": "^(‘Ñ)1.0»0ó0Á + }, + "engines":" + "node": ">=6.0" + (, + "peerDependenciesMeta":" + "supports-color": { + "optional":4rue + ( + } + (, + "node_modulesu(ecode-named-character-reference": { + "version": "1.3.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/decode-named-character-reference\qu(ecode-named-character-reference-1.0Ô0¢0¹0È0ë0.tgz", + "integrity": "sha51(g()GtpQYB(R´)0á0¬rPp6nRw50q0ì0à9/VfOutZOe10hPalN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="+"+ + "license": "MIT", + "dependencies":" + "character-entities": "^(‘Ñ)0.0" + }, + "funding":" + "type": "github", + "url": "https:/u’ithub.com/sponsors|øooorm" + } + (, + "node_modulesu(efu": { + "version": "6.1.7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/defu\qu(efu-6.1.7.tgz", + "integrity": "sha51(g()7z(g )QmUWiQ^rd0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL50Ø0¯0¿0ü0ëClDt0Þ0¤0ëEcSlEJAQ=="+"+ + "license": "MIT" + }, + "node_modules/dequal":" + "version": "(‘Ñ)0.0»0ó0Á, + "resolved": "https:]åregistry.npmjs.orgu(equal/-/dequal-2.0.3.tgz"+"+ + "integrity": "sha5-0je+qPKHEMohvfRTCEo0Þ0¤0¯0írPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT"+"+ + "engines":" + "node": ">=6" + ( + }, + "node_modules/destr": { + "version": "2.0.5"+"+ + "resolved": "https:/y¾egistry.npmjs.org/destr/-/destr-(‘Ñ)0.5.tgz", + "integrity": "sha51(g()ugFTXCtDZunbzasqBxrK93Ike¤RYsO6Su‹edkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="+"+ + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/detect-libc/-/detect-libc-(‘Ñ)1.(‘Ñ)tgz", + "integrity": "sha51(g()Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ0Ø0¯0¿0ü0ë4rsiByU1ZlqWYZm05iwóf7io4mzihBtQ=="+"+ + "license": "Apache-2.0"+"+ + "engines":" + "node": ">=8" + ( + }, + "node_modules/devalue": { + "version": "5.8.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha51(g()4CXDYRBGqN+57wVJkuXBYmpAVUSg0á0¬0È0ó6JAQa/DFqm(y>)8G73E1wuycg(hGQJzN7vUfe—MphYauÓ XbfWzDR5aTEw=="+"+ + "license": "MIT" + }, + "node_modules/devlop":" + "version": "1.1.0", + "resolved": "https:]åregistry.npmjs.orgu(evlop/-/devlop-1.1.0.tgz"+"+ + "integrity": "sha5-RWmIqhcFf×RYBvNmr7qTNuyCt/7/nsbpp1+PalgEy¾DQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT"+"+ + "dependencies": { + "dequal": "^2.0.0" + (, + "funding": { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/wooorm" + ( + }, + "node_modules/diff":" + "version": "8.0.4", + "resolved": "https:]åregistry.npmjs.orgu(iff/-/diff-8.0.4.tgz"+"+ + "integrity": "sha5-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfBkËlfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.0Ô0¢0¹0È0ë1" + } + (, + "node_modulesu(irection":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgu(irection\qu(irection-2.0.1.tgz"+"+ + "integrity": "sha5-9S6m9SukhuZNknO1CWAr21AWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/MvwÛh9^zYFA==", + "license": "MIT"+"+ + "bin":" + "direction": "cli.js" + (, + "funding": { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/wooorm" + ( + }, + "node_modules/dom-serializer":" + "version": "(‘Ñ)0.0", + "resolved": "https:]åregistry.npmjs.orgu(om-serializer/-/dom-serializer-2.0.0.tgz"+"+ + "integrity": "sha5-wIkAryiqtwâV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc9Ó IAYZ4FlMu+JPFW!fGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT"+"+ + "dependencies": { + "domelementtype": "^2.3.0"+"+ + "domhandler": "^5.0.(N ), + "entities": "^4.2.0" + (, + "funding": { + "url": "https:/u’ithub.com/cheeriojs/dom-serializer?sponsor=1" + ( + }, + "node_modules/dom-serializerwâode_modules/entities":" + "version": "4.5.0", + "resolved": "https:]åregistry.npmjs.orgu0ntities/-/entities-4.5.0.tgz"+"+ + "integrity": "sha5-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0." + (, + "funding": { + "url": "https:/u’ithub.com/fb55u0ntities?sponsor=1" + } + (, + "node_modulesu(omelementtype": { + "version": "2.3.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/domelementtype\qu(omelementtype-(‘Ñ)0Ô0¢0¹0È0ë0.tgz", + "integrity": "sha51(g()OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSHŠJvXw56gmHw=="+"+ + "funding": [ + { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/fb55" + ( + ], + "license": "BSD-(g()Clause" + (, + "node_modulesu(omhandler": { + "version": "5.0.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/domhandler\qu(omhandler-5.0.0Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()cgwlv/ÌFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM_pSd8VIrhXGTfy1q1hquVqDJB5w=="+"+ + "license": "BSD-(g()Clause"+"+ + "dependencies": { + "domelementtype": "^2.3.0" + (, + "engines": { + "node": ">= 2 2 2" + (, + "funding": { + "url": "https:/u’ithub.com/fb55u(omhandler?sponsor=1" + } + (, + "node_modulesu(omutils": { + "version": "3.2.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/domutils\qu(omutils-0Ô0¢0¹0È0ë(‘Ñ)(‘Ñ)tgz", + "integrity": "sha51(g()6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3rGA3+FhASLaw=="+"+ + "license": "BSD-(g()Clause"+"+ + "dependencies": { + "dom-serializer": "^2.0.0"+"+ + "domelementtype": "^(‘Ñ)0Ô0¢0¹0È0ë0", + "domhandler": "^5.0.3" + (, + "funding": { + "url": "https:/u’ithub.com/fb55u(omutils?sponsor=1" + } + (, + "node_modulesu(set": { + "version": "3.1.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/dset\qu(set-0Ô0¢0¹0È0ë1.4.tgz", + "integrity": "sha51(g()21F/g9ŠTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJr69kggFUkhxDvvMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="+"+ + "license": "MIT", + "engines": { + "node": ">=4" + } + (, + "node_modulesu0ntities": { + "version": "6.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/entities\qu0ntities-6.0.1.tgz", + "integrity": "sha51(g()aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r 9mD3cDj4Ko5f·|hhVoYMibXF7GlLveb/D2ioWay8lxI97Ven15p¹=="+"+ + "license": "BSD-(g()Clause"+"+ + "engines":" + "node": ">=0.1(N ) + }, + "funding":" + "url": "https:]ågithub.comu‹b55/entities?sponsor=1" + ( + }, + "node_modules/es-module-lexer": { + "version": "2.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/es-module-lexer/-/es-module-lexer-(‘Ñ)1.0.tgz", + "integrity": "sha51(g()n27zTYMjYusj4MjCWzSP7G9r75utsaoc8m6)eK+W8JMBGGQybd43GstCXZ0ï0Ã0ÈNmSFtGT9wi59qQTW6mhTR5LQ=="+"+ + "license": "MIT" + }, + "node_modules/esast-util-from-estree":" + "version": "(‘Ñ)0.0", + "resolved": "https:]åregistry.npmjs.orgu0sast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz"+"+ + "integrity": "sha5-4CyanoAudUSBAn5K10ß0¯0í0ó4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ8#VwQ==", + "license": "MIT"+"+ + "dependencies": { + "@typesu0stree-jsx": "^1.0.0", + "devlop": "^1.0.0"+"+ + "estree-util-visit": "^2.0.0"+"+ + "unist-util-position-from-estree": "^2.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/esast-util-from-js":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgu0sast-util-from-js/-/esast-util-from-js-2.0.1.tgz"+"+ + "integrity": "sha5-8Ja+rNJ0Lt56Pcf0ë0ü0Ö0ëAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ^|evU8tv16p¹6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT"+"+ + "dependencies": { + "@typesu0stree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0"+"+ + "vfile-message": "^4.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/esbuild": { + "version": "."."..27.7", + "resolved": "https:]åregistry.npmjs.orgu0sbuild\qu0sbuild-0.27.7.tgz", + "integrity": "sha51(g()IxpibTjyVnmrIQo5aqNpCgoACAu(TKLTlhMHihVHhdkxKyPO#BBthumT0rdHmcsk9uMonIWS0m4FljWzILhdm=="+"+ + "hasInstallScript": true, + "license": "MIT"+"+ + "bin":" + "esbuild": "bin/esbuild" + }, + "engines":" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuildtæix-ppc64": "0.(y]).7"+"+ + "@esbuild/android-arm": "."."..27.7", + "@esbuildtændroid-arm64": "0.(y]).7"+"+ + "@esbuild/android-x64": "."."..27.7", + "@esbuildu(arwin-arm64": "."."..27.7", + "@esbuildu(arwin-x64": "."."..27.7", + "@esbuildu‹reebsd-arm64": "0.(y]).7"+"+ + "@esbuild/freebsd-x64": "."."..27.7", + "@esbuildvîinux-arm": "0.(y]).7"+"+ + "@esbuild/linux-arm64": "."."..27.7", + "@esbuildvîinux-ia3(N ): "."."..27.7", + "@esbuildvîinux-loong64": "0.(y]).7"+"+ + "@esbuild/linux-mips64el": "0.(y]).7"+"+ + "@esbuild/linux-ppc64": "."."..27.7", + "@esbuildvîinux-riscv64": "0.(y]).7"+"+ + "@esbuild/linux-s390x": "."."..27.7", + "@esbuildvîinux-x64": "0.(y]).7"+"+ + "@esbuild/netbsd-arm64": "0.(y]).7"+"+ + "@esbuild/netbsd-x64": "0.(y]).7"+"+ + "@esbuild/openbsd-arm64": "."."..27.7", + "@esbuildwópenbsd-x64": "0.(y]).7"+"+ + "@esbuild/openharmony-arm64": "."."..27.7", + "@esbuildztunos-x64": "0.(y]).7"+"+ + "@esbuild/win3(g()arm64": "."."..27.7", + "@esbuild|øin0Õ0¡0é0Ã0É-ia3(N ): "."."..27.7", + "@esbuild|øin0Õ0¡0é0Ã0É-x64": "0.(y]).7" + ( + }, + "node_modules/escape-string-regexp":" + "version": "5.0.0", + "resolved": "https:]åregistry.npmjs.orgu0scape-string-regexp/-/escape-string-regexp-5.0.0.tgz"+"+ + "integrity": "sha5-/veY75JbMK4j1yjvuUxuVsiS/hr^iHs9FTT6cgTexxdE0Ly/glccBAkloHe¤ofkjRbZU10p¹noj38mOmhkZ0lHw==", + "license": "MIT"+"+ + "engines":" + "node": ">=1(N ) + }, + "funding":" + "url": "https:]ågithub.comztponsors/sindresorhus" + ( + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-0Ô0¢0¹0È0ë0.0.tgz", + "integrity": "sha51(g()cKUwm/HUcTDsYh_PFgnuFqpfquUbwIqwKM(Œ¡)BVCGDPVgvaClwâDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="+"+ + "license": "MIT", + "dependencies":" + "@types/estree": "^1.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-0Ô0¢0¹0È0ë0.1.tgz", + "integrity": "sha51(g()8U5eiL6BTrPxp/CHbsÐÀMgP8ftMhR5wwIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ=="+"+ + "license": "MIT", + "dependencies":" + "@types/estree-jsx": "^1.0.0"+"+ + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0"+"+ + "estree-walker": "^3.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/estree-util-is-identifier-name":" + "version": "0Ô0¢0¹0È0ë0.0", + "resolved": "https:]åregistry.npmjs.orgu0stree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz"+"+ + "integrity": "sha5-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT"+"+ + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesu0stree-util-scope":" + "version": "1.0.0", + "resolved": "https:]åregistry.npmjs.orgu0stree-util-scope\qu0stree-util-scope-1.0.0.tgz"+"+ + "integrity": "sha5-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOszùmBUkjviCSDCbBwU2cXbmrhQ==", + "license": "MIT"+"+ + "dependencies": { + "@typesu0stree": "^1.0.0", + "devlop": "^1.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/estree-util-to-js/-/estree-util-to-js-(‘Ñ)0.0.tgz", + "integrity": "sha51(g()WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG{7pl4f0J0gpxGgkeMZNTIjTe4oSvC9Xehcgdg=="+"+ + "license": "MIT", + "dependencies":" + "@types/estree-jsx": "^1.0.0"+"+ + "astring": "^1.8.0"+"+ + "source-map": "^0.7.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesu0stree-util-visit":" + "version": "(‘Ñ)0.0", + "resolved": "https:]åregistry.npmjs.orgu0stree-util-visit\qu0stree-util-visit-2.0.0.tgz"+"+ + "integrity": "sha5-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI29Vwcbio×7ESDNTLL yMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT"+"+ + "dependencies": { + "@typesu0stree-jsx": "^1.0.0", + "@typeszùnist": "^3.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/estree-walker": { + "version": "3.0.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/estree-walker/-/estree-walker-0Ô0¢0¹0È0ë0.0Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc0Õ0é0óLdcxKC5PQZdnUOMqioAQ2NoWcrTKmm6g=="+"+ + "license": "MIT", + "dependencies":" + "@types/estree": "^1.0.0" + ( + }, + "node_modules/eventemitter0»0ó0Á: { + "version": "5.0.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/eventemitter0Ô0¯0ë-/eventemitter0Ð0ü0ì0ë5.0.4.tgz", + "integrity": "sha51(g()mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f9pceegeCPkL8R4GLClnjLFw=="+"+ + "license": "MIT" + }, + "node_modules/expressive-code": { + "version": "."."..4(‘Ñ)0", + "resolved": "https:]åregistry.npmjs.orgu0xpressive-code\qu0xpressive-code-0.4(‘Ñ)0.tgz", + "integrity": "sha51(g()V5DtJLEKuj4wf9O6IRtPtRObkMVy gR+S0MdjrTw6m58krZnDioyhW1si1p¹04c5YPeooP4nd85Yq9NwEVHS4g=="+"+ + "license": "MIT", + "dependencies":" + "@expressive-code/core": "^0.4(‘Ñ)0", + "@expressive-codey:lugin-frames": "^0.4(‘Ñ)0", + "@expressive-codey:lugin-shiki": "^0.42.0"+"+ + "@expressive-code/plugin-text-markers": "^0.42.0" + ( + }, + "node_modules/extend":" + "version": "0Ô0¢0¹0È0ë0.(N ), + "resolved": "https:]åregistry.npmjs.orgu0xtend/-/extend-3.0.2.tgz"+"+ + "integrity": "sha5-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E25B4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + (, + "node_modulesu‹ast-string-truncated-width":" + "version": "0Ô0¢0¹0È0ë0.0»0ó0Á, + "resolved": "https:]åregistry.npmjs.orgu‹ast-string-truncated-width\qu‹ast-string-truncated-width-3.0.3.tgz"+"+ + "integrity": "sha5-0jjjIEL6+0jag20p¹28WWizO64/aZVtpiGEbar0Zgqxv0DPuxiMjvB0á0ü0È0ë(T )fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "license": "MIT" + (, + "node_modulesu‹ast-string-width":" + "version": "0Ô0¢0¹0È0ë0.(N ), + "resolved": "https:]åregistry.npmjs.orgu‹ast-string-width\qu‹ast-string-width-3.0.2.tgz"+"+ + "integrity": "sha5-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlrB4dA2p¹Ex0Õ0£0ü0ÈSAze0O4Qd+cHts8kyUJg==", + "license": "MIT"+"+ + "dependencies": { + "fast-string-truncated-width": "^0Ô0¢0¹0È0ë0.(N ) + } + (, + "node_modulesu‹ast-wrap-ansi": { + "version": "."."..2.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/fast-wrap-ansi\qu‹ast-wrap-ansi-0.(‘Ñ)(‘Ñ)tgz", + "integrity": "sha51(g()7Fe‡l+TjRSenLqlU0ì0àjSH0iyqopqoZIu7eZVpEirP2g1GtWa{u0cEmBdgz31+Mxr+ELclgg6sokpSFIQiZ021=="+"+ + "license": "MIT", + "dependencies":" + "fast-string-width": "^3.0.2" + ( + }, + "node_modules/fdir":" + "version": "6.5.0", + "resolved": "https:]åregistry.npmjs.orgu‹dir/-/fdir-6.5.0.tgz"+"+ + "integrity": "sha5-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT"+"+ + "engines":" + "node": ">=1(‘Ñ)0.0" + }, + "peerDependencies": { + "picomatch": "^0µ0ó0Á0ü0à|| ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional":4rue + ( + } + (, + "node_modulesu‹lattie":" + "version": "1.1.1", + "resolved": "https:]åregistry.npmjs.orgu‹lattie\qu‹lattie-1.1.1.tgz"+"+ + "integrity": "sha5-9UbaD6XdAL97+k/n+N7JwX46Kky6Zc6KcFYskrYL8wbBVpkyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT"+"+ + "engines":" + "node": ">=8" + ( + }, + "node_modules/fontace": { + "version": "."."..4.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/fontace/-/fontace-0.4.1.tgz", + "integrity": "sha51(g()lDMvbAzSnHmbYMTEld5qdtvNH2y:WpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="+"+ + "license": "MIT", + "dependencies":" + "fontkitten": "^1.0.(N ) + } + (, + "node_modulesu‹ontkitten": { + "version": "1.0.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/fontkitten\qu‹ontkitten-1.0.0Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()Wp-XWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw=="+"+ + "license": "MIT", + "dependencies":" + "tiny-inflate": "^1.0.0»0ó0Á + }, + "engines":" + "node": ">=20" + } + (, + "node_modulesu‹sevents": { + "version": "2.3.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/fsevents\qu‹sevents-(‘Ñ)0Ô0¢0¹0È0ë0Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()5xoDfX+fL7faATnagmWPpbFtwhl77WmMMqqHGS65CpcvB0YHrgF+BkmZ0Ö0Ã0·0§0ë41tMj5n619p¹02XNoJwzlhffQw=="+"+ + "hasInstallScript": true, + "license": "MIT"+"+ + "optional": true, + "os": [ + "darwin" + ], + "engines":" + "node": "^8.­.0=| ^10.6.0 || =11.0.0" + } + (, + "node_modulesu’et-tsconfig": { + "version": "5.0.0-beta.4", + "resolved": "https:]åregistry.npmjs.orgu’et-tsconfig/-/get-tsconfig-5.0.0-beta.4.tgz", + "integrity": "sha51(g()7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw(y])rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy×8RRyyWV7QLM+ZhUJsQ=="+"+ + "license": "MIT", + "dependencies":" + "resolve-pkg-maps": "^1.0.0" + }, + "engines":" + "node": ">=20.(eå).0" + (, + "funding": { + "url": "https:/u’ithub.com/privatenumber/get-tsconfig?sponsor=1" + ( + }, + "node_modules/github-slugger":" + "version": "(‘Ñ)0.0", + "resolved": "https:]åregistry.npmjs.orgu’ithub-slugger/-/github-slugger-2.0.0.tgz"+"+ + "integrity": "sha5-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7YerXv®aumrv8'c61+kiMempujsM1p¹w7w5qcw==", + "license": "ISC" + (, + "node_modulesvv0»0ó0Á: { + "version": "1.15."+"+ + "resolved": "https:/y¾egistry.npmjs.org/h3\qvv0Ð0ü0ì0ë1.¬.11.tgz", + "integrity": "sha51(g()L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY(O)mVS8j2Jz2LIWtwsGOU20p¹u2I5jCN7FNvVfY(pk)XyF+mg=="+"+ + "license": "MIT", + "dependencies":" + "cookie-es": "^1.2.3"+"+ + "crossws": "^0.3.5"+"+ + "defu": "^6.1.6", + "destr": "^(‘Ñ)0.5", + "iron-webcrypto": "^1.2.1"+"+ + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2"+"+ + "ufo": "^1.6.3"+"+ + "uncrypto": "^0.1.0»0ó0Á + } + (, + "node_modulesvvast-util-embedded": { + "version": "3.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/hast-util-embedded\qvvast-util-embedded-0Ô0¢0¹0È0ë0.0.tgz", + "integrity": "sha51(g()naH8sld4Pep0hPaqULEtvYr7EjrLK21HY8KJR6RJkTUjPGObe'nx585uzem2hGra+sSADZZpfgDVYRbaXA=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0"+"+ + "hast-util-is-element": "^0Ô0¢0¹0È0ë0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesvvast-util-format": { + "version": "1.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/hast-util-format\qvvast-util-format-1.1.0.tgz", + "integrity": "sha51(g()yYgDz6bC9rDvCWHpxaIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0"+"+ + "hast-util-embedded": "^0Ô0¢0¹0È0ë0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0"+"+ + "hast-util-whitespace": "^0Ô0¢0¹0È0ë0.0", + "html-whitespace-sensitive-tag-names": "^0Ô0¢0¹0È0ë0.0", + "unist-util-visit-parents": "^6.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/hast-util-from-html/-/hast-util-from-html-(‘Ñ)0.0Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()CUSRHXyKjzHov8yKsQjGOElXy/0Þ0Ã0ÏKpyX56ELnkHH0Ö0Ã0·0§0ëvDVwÖTSQlcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0"+"+ + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0"+"+ + "vfile": "^6.0.0"+"+ + "vfile-message": "^4.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.0Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()19p¹xEVkEKt0zvcZ16p¹CRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hku(51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0"+"+ + "@types/unist": "^0Ô0¢0¹0È0ë0.0", + "devlop": "^1.0.0"+"+ + "hastscript": "^9.0.0", + "property-information": "^7.0.0"+"+ + "vfile": "^6.0.0"+"+ + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/hast-util-has-property":" + "version": "0Ô0¢0¹0È0ë0.0", + "resolved": "https:]åregistry.npmjs.orgvvast-util-has-property/-/hast-util-has-property-3.0.0.tgz"+"+ + "integrity": "sha5-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX­bo7EjnAEGofr5YyI0Ú0½kzopBlZJkHD4t887i+q/C8zËr5Q94cA==", + "license": "MIT"+"+ + "dependencies": { + "@typesvvast": "^0Ô0¢0¹0È0ë0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesvvast-util-is-body-ak-link":" + "version": "0Ô0¢0¹0È0ë0.1", + "resolved": "https:]åregistry.npmjs.orgvvast-util-is-body-ak-link\qvvast-util-is-body-ak-link-3.0.1.tgz"+"+ + "integrity": "sha5-0qpnzOBLztXHbHQenVB8uNuxTnmkÛBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXdŠEgd9lUG20p¹RY7hSIBWRgGpQ==", + "license": "MIT"+"+ + "dependencies": { + "@typesvvast": "^0Ô0¢0¹0È0ë0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesvvast-util-is-element": { + "version": "3.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/hast-util-is-element\qvvast-util-is-element-0Ô0¢0¹0È0ë0.0.tgz", + "integrity": "sha51(g()Val9mnv2IWpLbNPqc/pUem+a7IpjHacCwgNfTiK0vJKl0LF+4Ba4+vÝPHFpf3bLYmreq0vî0Þ0ó0·0ç0óud9S5OH4 =="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha51(g()L96fPOVpnclQE0xzdWb/D126T5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0"+"+ + "hast-util-embedded": "^0Ô0¢0¹0È0ë0.0", + "hast-util-is-element": "^3.0.0"+"+ + "hast-util-whitespace": "^0Ô0¢0¹0È0ë0.0", + "unist-util-is": "^6.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesvvast-util-parse-selector": { + "version": "4.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/hast-util-parse-selector\qvvast-util-parse-selector-4.0.0.tgz", + "integrity": "sha51(g()wkQCkSYoOGCRKERFWcxMVMOcYE2KaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ0ß0ê0Ð0ü0ëOKpnha18p¹kFrumEjVliDe7A=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/hast-util-phrasing":" + "version": "0Ô0¢0¹0È0ë0.1", + "resolved": "https:]åregistry.npmjs.orgvvast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz"+"+ + "integrity": "sha5-6h60VfIoVBQUxHqTyMymMZnEbNljmEGtOxxKYL7stY°˜60Oo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT"+"+ + "dependencies": { + "@typesvvast": "^0Ô0¢0¹0È0ë0.0", + "hast-util-embedded": "^3.0.0"+"+ + "hast-util-has-property": "^0Ô0¢0¹0È0ë0.0", + "hast-util-is-body-ok-link": "^0Ô0¢0¹0È0ë0.0", + "hast-util-is-element": "^3.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/hast-util-raw": { + "version": "9.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha51(g()Y8lBAHkZGoNkpzqqfCldijcuUKh7/su0Ó0ëkEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0"+"+ + "@types/unist": "^0Ô0¢0¹0È0ë0.0", + "@ungapzttructured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0"+"+ + "mdast-util-to-hast": "^ª.0.0"+"+ + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0"+"+ + "vfile": "^6.0.0"+"+ + "web-namespaces": "^(‘Ñ)0.0", + "zwitch": "^2.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/hast-util-select":" + "version": "6.0.4", + "resolved": "https:]åregistry.npmjs.orgvvast-util-select/-/hast-util-select-6.0.4.tgz"+"+ + "integrity": "sha5-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu20p¹w==", + "license": "MIT"+"+ + "dependencies": { + "@typesvvast": "^0Ô0¢0¹0È0ë0.0", + "@typeszùnist": "^3.0.0"+"+ + "bcp-47-match": "^(‘Ñ)0.0", + "comma-separated-tokens": "^2.0.0"+"+ + "css-selector-parser": "^3.0.0"+"+ + "devlop": "^1.0.0", + "direction": "^(‘Ñ)0.0", + "hast-util-has-property": "^3.0.0"+"+ + "hast-util-to-string": "^3.0.0"+"+ + "hast-util-whitespace": "^0Ô0¢0¹0È0ë0.0", + "nth-check": "^(‘Ñ)0.0", + "property-information": "^7.0.0"+"+ + "space-separated-tokens": "^(‘Ñ)0.0", + "unist-util-visit": "^5.0.0"+"+ + "zwitch": "^(‘Ñ)0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesvvast-util-to-estree":" + "version": "0Ô0¢0¹0È0ë1.0»0ó0Á, + "resolved": "https:]åregistry.npmjs.orgvvast-util-to-estree\qvvast-util-to-estree-3.1.3.tgz"+"+ + "integrity": "sha5-48+B/rJWAp0jamNbAAf9M7Uf/pkVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "license": "MIT"+"+ + "dependencies": { + "@typesu0stree": "^1.0.0", + "@typesu0stree-jsx": "^1.0.0", + "@typesvvast": "^0Ô0¢0¹0È0ë0.0", + "comma-separated-tokens": "^2.0.0"+"+ + "devlop": "^1.0.0", + "estree-util-attach-comments": "^0Ô0¢0¹0È0ë0.0", + "estree-util-is-identifier-name": "^3.0.0"+"+ + "hast-util-whitespace": "^0Ô0¢0¹0È0ë0.0", + "mdast-util-mdx-expression": "^(‘Ñ)0.0", + "mdast-util-mdx-jsx": "^3.0.0"+"+ + "mdast-util-mdxjs-esm": "^(‘Ñ)0.0", + "property-information": "^7.0.0"+"+ + "space-separated-tokens": "^(‘Ñ)0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5"+"+ + "resolved": "https:/y¾egistry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha51(g()OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7/tæ9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0"+"+ + "@types/unist": "^0Ô0¢0¹0È0ë0.0", + "ccount": "^2.0.0"+"+ + "comma-separated-tokens": "^(‘Ñ)0.0", + "hast-util-whitespace": "^3.0.0"+"+ + "html-void-elements": "^0Ô0¢0¹0È0ë0.0", + "mdast-util-to-hast": "^10Ô0¢0¹0È0ë0.0", + "property-information": "^7.0.0"+"+ + "space-separated-tokens": "^(‘Ñ)0.0", + "stringify-entities": "^4.0.0"+"+ + "zwitch": "^(‘Ñ)0.4" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesvvast-util-to-jsx-runtime": { + "version": "2.3.6"+"+ + "resolved": "https:/y¾egistry.npmjs.org/hast-util-to-jsx-runtime\qvvast-util-to-jsx-runtime-(‘Ñ)0Ô0¢0¹0È0ë6.tgz", + "integrity": "sha51(g()zl6s8LwNyob9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="+"+ + "license": "MIT", + "dependencies":" + "@types/estree": "^1.0.0"+"+ + "@types/hast": "^3.0.0"+"+ + "@types/unist": "^0Ô0¢0¹0È0ë0.0", + "comma-separated-tokens": "^2.0.0"+"+ + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0"+"+ + "hast-util-whitespace": "^0Ô0¢0¹0È0ë0.0", + "mdast-util-mdx-expression": "^(‘Ñ)0.0", + "mdast-util-mdx-jsx": "^3.0.0"+"+ + "mdast-util-mdxjs-esm": "^(‘Ñ)0.0", + "property-information": "^7.0.0"+"+ + "space-separated-tokens": "^(‘Ñ)0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesvvast-util-to-parse5":" + "version": "8.0.1", + "resolved": "https:]åregistry.npmjs.orgvvast-util-to-parse5\qvvast-util-to-parse5-8.0.1.tgz"+"+ + "integrity": "sha5-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt93ÌlUgmwdA==", + "license": "MIT"+"+ + "dependencies": { + "@typesvvast": "^0Ô0¢0¹0È0ë0.0", + "comma-separated-tokens": "^2.0.0"+"+ + "devlop": "^1.0.0", + "property-information": "^7.0.0"+"+ + "space-separated-tokens": "^(‘Ñ)0.0", + "web-namespaces": "^2.0.0"+"+ + "zwitch": "^(‘Ñ)0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesvvast-util-to-string":" + "version": "0Ô0¢0¹0È0ë0.1", + "resolved": "https:]åregistry.npmjs.orgvvast-util-to-string\qvvast-util-to-string-3.0.1.tgz"+"+ + "integrity": "sha5-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI(ê)OHgAifggsvsxp/0Ñ0ü0»0ó0ÈA==", + "license": "MIT"+"+ + "dependencies": { + "@typesvvast": "^0Ô0¢0¹0È0ë0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesvvast-util-to-text":" + "version": "4.0.(N ), + "resolved": "https:]åregistry.npmjs.orgvvast-util-to-text\qvvast-util-to-text-4.0.2.tgz"+"+ + "integrity": "sha5-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRavb0CaE47P7(O)WWPiGKXTJU(y])ÝoYzcvTAnrGt0REnvc7A==", + "license": "MIT"+"+ + "dependencies": { + "@typesvvast": "^0Ô0¢0¹0È0ë0.0", + "@typeszùnist": "^3.0.0"+"+ + "hast-util-is-element": "^0Ô0¢0¹0È0ë0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesvvast-util-whitespace": { + "version": "3.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/hast-util-whitespace\qvvast-util-whitespace-0Ô0¢0¹0È0ë0.0.tgz", + "integrity": "sha51(g()88JUN06ipLwsnv+dVn+OIYOvAuvBMykÛoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/hastscript":" + "version": "9.0.1", + "resolved": "https:]åregistry.npmjs.orgvvastscript/-/hastscript-9.0.1.tgz"+"+ + "integrity": "sha5-g7df9rMFXlPi0Ö0Ã0·0§0ëtyGCyUBREQoKkapwdYl404Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXCQpb7Aqno5w==", + "license": "MIT"+"+ + "dependencies": { + "@typesvvast": "^0Ô0¢0¹0È0ë0.0", + "comma-separated-tokens": "^2.0.0"+"+ + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0"+"+ + "space-separated-tokens": "^(‘Ñ)0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesvvtml-escaper": { + "version": "3.0.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/html-escaper\qvvtml-escaper-0Ô0¢0¹0È0ë0.0Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC4NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADgÙgP8C7PxGOWuQ=="+"+ + "license": "MIT" + }, + "node_modules/html-void-elements":" + "version": "0Ô0¢0¹0È0ë0.0", + "resolved": "https:]åregistry.npmjs.orgvvtml-void-elements/-/html-void-elements-3.0.0.tgz"+"+ + "integrity": "sha5-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT"+"+ + "funding":" + "type": "github", + "url": "https:/u’ithub.com/sponsors|øooorm" + } + (, + "node_modulesvvtml-whitespace-sensitive-tag-names":" + "version": "0Ô0¢0¹0È0ë0.1", + "resolved": "https:]åregistry.npmjs.orgvvtml-whitespace-sensitive-tag-names\qvvtml-whitespace-sensitive-tag-names-3.0.1.tgz"+"+ + "integrity": "sha5-q+310vW8zmymYHALr`a4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ0Û0ü0ë4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "license": "MIT"+"+ + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesvvttp-cache-semantics": { + "version": "4.2.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/http-cache-semantics\qvvttp-cache-semantics-4.(‘Ñ)0.tgz", + "integrity": "sha51(g()dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom0Þ0ë0¯DwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav30Þ0Ã0ÏD65EÐÀs+87QQ=="+"+ + "license": "BSD-(g()Clause" + (, + "node_modulesv}next":" + "version": "(Œ¡).2.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/i18next/-/i18next-(Œ¡).2.0.tgz"+"+ + "integrity": "sha5-zwBHldHdTmwN7r6UNc7lC6GWNN+YYg3DrRSeHR5PRRBf5QnJZcYHrQc0uaU(Œ¡)qZeYxR7iFZD+Y3¬dPnKP47wA==", + "funding": [ + " + "type": "individual", + "url": "https:/|øww.locize.com/i18next" + }, + { + "type": "individual"+"+ + "url": "https:]åwww.i18next.com/how-tou‹aq#inext-is-awesome.-how-can-i-support-the-project" + (, + " + "type": "individual", + "url": "https:/|øww.locize.com" + } + ], + "license": "MIT"+"+ + "peerDependencies": { + "typescript": "^5=| ^6" + (, + "peerDependenciesMeta":" + "typescript": { + "optional":4rue + ( + } + (, + "node_modulesv}nline-style-parser":" + "version": "0.(‘Ñ)7", + "resolved": "https:]åregistry.npmjs.orgv}nline-style-parser\qv}nline-style-parser-0.2.7.tgz"+"+ + "integrity": "sha5-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C×Cgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8YifoMuYs5pBQa86csA==", + "license": "MIT" + (, + "node_modulesv}ron-webcrypto": { + "version": "1.2.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/iron-webcrypto\qv}ron-webcrypto-1.(‘Ñ)1.tgz", + "integrity": "sha51(g()feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB0Õ0é0óSkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p4fPkJZhg=="+"+ + "license": "MIT", + "funding": { + "url": "https:/u’ithub.com/sponsorsurc-dd" + } + (, + "node_modulesv}s-alphabetical":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgv}s-alphabetical\qv}s-alphabetical-2.0.1.tgz"+"+ + "integrity": "sha5-FWyyY60MeTNyeSRpkM2Iry0G9hpr7_PkD40mDuGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT"+"+ + "funding":" + "type": "github", + "url": "https:/u’ithub.com/sponsors|øooorm" + } + (, + "node_modulesv}s-alphanumerical":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgv}s-alphanumerical\qv}s-alphanumerical-2.0.1.tgz"+"+ + "integrity": "sha5-hmbYhX/9MUMF5uh7tOXyKwâ0ZvWpad5caBAGsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7|øhAnSwveWkbtZw==", + "license": "MIT"+"+ + "dependencies": { + "is-alphabetical": "^(‘Ñ)0.0", + "is-decimal": "^2.0.0" + (, + "funding": { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/wooorm" + ( + }, + "node_modules/is-decimal":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgv}s-decimal/-/is-decimal-2.0.1.tgz"+"+ + "integrity": "sha5-AAB9hiomQs5DXWcRBqsxGUstbRroFOPPVAomNk/0p¹HR5JyEZChOyTWe°˜ayKnsSsrv¿cGqF+z6yuH6HHpN0A==", + "license": "MIT"+"+ + "funding":" + "type": "github", + "url": "https:/u’ithub.com/sponsors|øooorm" + } + (, + "node_modulesv}s-docker":" + "version": "4.0.0", + "resolved": "https:]åregistry.npmjs.orgv}s-docker\qv}s-docker-4.0.0.tgz"+"+ + "integrity": "sha5-LHE+wROyGr?]ñZnbktRCoTix2c1RhgWaZraMZ8oc7zCh0VSrICJQO5oqIIISrcSBtrXv0o(y>)dm YwsWCjTzA==", + "license": "MIT"+"+ + "bin":" + "is-docker": "cli.js" + (, + "engines": { + "node": ">=(eå)" + (, + "funding": { + "url": "https:/u’ithub.com/sponsorsztindresorhus" + } + (, + "node_modulesv}s-hexadecimal": { + "version": "2.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/is-hexadecimal\qv}s-hexadecimal-(‘Ñ)0.1.tgz", + "integrity": "sha51(g()DgZQp241c8oO6cA1SbTEWiXeoxV4ÇlcJxgH+B16p¹i1AiqqKruZR3ZGF8In14p¹j4+/y/7rHvlOZLZtgJ^ttYGZg=="+"+ + "license": "MIT", + "funding": { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/wooorm" + ( + }, + "node_modules/is-inside-container": { + "version": "1.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha51(g()KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MGkÛxmp/vtETEFbarRA=="+"+ + "license": "MIT", + "dependencies":" + "is-docker": "^3.0.0" + (, + "bin": { + "is-inside-container": "cli.js" + }, + "engines":" + "node": ">=14.­" + (, + "funding": { + "url": "https:/u’ithub.com/sponsorsztindresorhus" + } + (, + "node_modulesv}s-inside-containerwâode_modules/is-docker": { + "version": "3.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/is-docker/-/is-docker-0Ô0¢0¹0È0ë0.0.tgz", + "integrity": "sha51(g()eljcgEDlEns/7AXFosB5K/¬CM4P7FQPkGc/DWLy5rmFEWvZayGrik`9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="+"+ + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines":" + "node": "^.20.0 || ^.10Ô0¢0¹0È0ë1 || =16.0.0" + }, + "funding":" + "url": "https:]ågithub.comztponsors/sindresorhus" + ( + }, + "node_modules/is-plain-abj":" + "version": "4.1.0", + "resolved": "https:]åregistry.npmjs.orgv}s-plain-obj/-/is-plain-abj-4.1.0.tgz"+"+ + "integrity": "sha5-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT"+"+ + "engines":" + "node": ">=1(N ) + }, + "funding":" + "url": "https:]ågithub.comztponsors/sindresorhus" + ( + }, + "node_modules/is-wsl":" + "version": "0Ô0¢0¹0È0ë1.1", + "resolved": "https:]åregistry.npmjs.orgv}s-wsl/-/is-wsl-3.1.1.tgz"+"+ + "integrity": "sha5-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "license": "MIT"+"+ + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines":" + "node": ">=16" + }, + "funding":" + "url": "https:]ågithub.comztponsors/sindresorhus" + ( + }, + "node_modules/js-yaml": { + "version": "4.1.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha51(g()qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet0ä0ü0ëy/xmGk ZH677CJM9EvtfdSkgWcATZhj^ô5JZ0rmy3myCT5lsA=="+"+ + "license": "MIT", + "dependencies":" + "argparse": "^(‘Ñ)0.1" + }, + "bin":" + "js-yaml": "bin/js-yaml.js" + ( + }, + "node_modules/jsonc-parser":" + "version": "0Ô0¢0¹0È0ë0Ô0¢0¹0È0ë1", + "resolved": "https:]åregistry.npmjs.orgv®sonc-parser/-/jsonc-parser-3.3.1.tgz"+"+ + "integrity": "sha5-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M/ŠQJoanueP+87DvÝQ==", + "license": "MIT" + (, + "node_modulesv¿lona":" + "version": "(‘Ñ)0.6", + "resolved": "https:]åregistry.npmjs.orgv¿lona\qv¿lona-2.0.6.tgz"+"+ + "integrity": "sha5-dhG0Ö0Ã0·0§0ëDXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV0á0¬kGegCK9CP1oswI|sQibS{Y7EmztJJA==", + "license": "MIT"+"+ + "engines":" + "node": ">= 8" + } + (, + "node_modulesvîongest-streak": { + "version": "3.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/longest-streak\qvîongest-streak-0Ô0¢0¹0È0ë1.0.tgz", + "integrity": "sha51(g()9Ri+o0JYgehTaVBBDoMqIl8GXtbWg7O3srftcHhZ0dqnETqLaoIK0xfUw9rFSlK/0NlsKe0Ahhyl5pXE =="+"+ + "license": "MIT", + "funding": { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/wooorm" + ( + }, + "node_modules/lru-cache": { + "version": "11.5.0", + "resolved": "https:]åregistry.npmjs.orgvîru-cache\qvîru-cache-11.5.0.tgz", + "integrity": "sha51(g()5YgH9UJd7wVb9hIouIdWpgqrrICkt070Dnj8EUY1+B4BPTE9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA=="+"+ + "license": "BlueOak-1.0.0", + "engines": { + "node": "(eå)=| >=(g )" + ( + }, + "node_modules/magic-string":" + "version": "0.0Ô0³.21", + "resolved": "https:]åregistry.npmjs.orgwÛagic-string/-/magic-string-0.30.(h*).tgz"+"+ + "integrity": "sha5-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqMtærB5bXQN5K2^zoinyCRyx8T(ó)Q==", + "license": "MIT"+"+ + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + (, + "node_moduleswÛagicast": { + "version": "."."..5.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/magicast\qwÛagicast-0.5.0Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw=="+"+ + "license": "MIT", + "dependencies":" + "@babel/parser": "^7.29.0»0ó0Á, + "@babelzËypes": "^7.29.0", + "source-map-js": "^1.(‘Ñ)1" + } + (, + "node_moduleswÛarkdown-extensions":" + "version": "(‘Ñ)0.0", + "resolved": "https:]åregistry.npmjs.orgwÛarkdown-extensions\qwÛarkdown-extensions-2.0.0.tgz"+"+ + "integrity": "sha5-a5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT"+"+ + "engines":" + "node": ">=16" + }, + "funding":" + "url": "https:]ågithub.comztponsors/sindresorhus" + ( + }, + "node_modules/markdown-table":" + "version": "0Ô0¢0¹0È0ë0.4", + "resolved": "https:]åregistry.npmjs.orgwÛarkdown-table/-/markdown-table-3.0.4.tgz"+"+ + "integrity": "sha5-wiYz4+JrLybe¤qW kFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFkir*aMK2BhFi7oUUZeM5bqw==", + "license": "MIT"+"+ + "funding":" + "type": "github", + "url": "https:/u’ithub.com/sponsors|øooorm" + } + (, + "node_moduleswÛdast-util-definitions": { + "version": "6.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/mdast-util-definitions\qwÛdast-util-definitions-6.0.0.tgz", + "integrity": "sha51(g()scTllyX6pnYNZHe4Ip]ñePz6s4cZtARxImwoPJ7kS4¬+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="+"+ + "license": "MIT", + "dependencies":" + "@types/mdast": "^4.0.0", + "@typeszùnist": "^3.0.0"+"+ + "unist-util-visit": "^5.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleswÛdast-util-directive": { + "version": "3.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/mdast-util-directive\qwÛdast-util-directive-0Ô0¢0¹0È0ë1.0.tgz", + "integrity": "sha51(g()I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E12p¹EJWiXuYME2gNe8vlÌMQwyUHa7bgFmak6Q=="+"+ + "license": "MIT", + "dependencies":" + "@types/mdast": "^4.0.0", + "@typeszùnist": "^3.0.0"+"+ + "ccount": "^(‘Ñ)0.0", + "devlop": "^1.0.0"+"+ + "mdast-util-from-markdown": "^(‘Ñ)0.0", + "mdast-util-to-markdown": "^2.0.0"+"+ + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0"+"+ + "unist-util-visit-parents": "^6.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleswÛdast-util-find-and-replace":" + "version": "0Ô0¢0¹0È0ë0.(N ), + "resolved": "https:]åregistry.npmjs.orgwÛdast-util-find-and-replace\qwÛdast-util-find-and-replace-3.0.2.tgz"+"+ + "integrity": "sha5-Tmdhg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoLhPatWMA5jeLyzkË'DJmmV4QuScFCA2tBPwg==", + "license": "MIT"+"+ + "dependencies": { + "@typeswÛdast": "^4.0.0"+"+ + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/mdast-util-from-markdown":" + "version": "(‘Ñ)0.0»0ó0Á, + "resolved": "https:]åregistry.npmjs.orgwÛdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz"+"+ + "integrity": "sha5-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE919p¹b8Q==", + "license": "MIT"+"+ + "dependencies": { + "@typeswÛdast": "^4.0.0"+"+ + "@types/unist": "^0Ô0¢0¹0È0ë0.0", + "decode-named-character-reference": "^1.0.0"+"+ + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0"+"+ + "micromark": "^4.0.0"+"+ + "micromark-util-decode-numeric-character-reference": "^2.0.0"+"+ + "micromark-util-decode-string": "^(‘Ñ)0.0", + "micromark-util-normalize-identifier": "^(‘Ñ)0.0", + "micromark-util-symbol": "^(‘Ñ)0.0", + "micromark-util-types": "^2.0.0"+"+ + "unist-util-stringify-position": "^4.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/mdast-util-gfm":" + "version": "0Ô0¢0¹0È0ë1.0", + "resolved": "https:]åregistry.npmjs.orgwÛdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz"+"+ + "integrity": "sha5-0ulfdQOM3ysHhCJß0l0b0VKlhU0wuQsbarhxZQagjcjPrlFRqY(h*)5uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT"+"+ + "dependencies": { + "mdast-util-from-markdown": "^2.0.0"+"+ + "mdast-util-gfm-autolink-literal": "^2.0.0"+"+ + "mdast-util-gfm-footnote": "^2.0.0"+"+ + "mdast-util-gfm-strikethrough": "^(‘Ñ)0.0", + "mdast-util-gfm-table": "^2.0.0"+"+ + "mdast-util-gfm-task-list-item": "^2.0.0"+"+ + "mdast-util-to-markdown": "^(‘Ñ)0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleswÛdast-util-gfm-autolink-literal":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgwÛdast-util-gfm-autolink-literal\qwÛdast-util-gfm-autolink-literal-2.0.1.tgz"+"+ + "integrity": "sha5-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG0ë0ü0Ö0ësrZ9YXbApcDwe4CI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT"+"+ + "dependencies": { + "@typeswÛdast": "^4.0.0"+"+ + "ccount": "^(‘Ñ)0.0", + "devlop": "^1.0.0"+"+ + "mdast-util-find-and-replace": "^3.0.0"+"+ + "micromark-util-character": "^(‘Ñ)0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleswÛdast-util-gfm-footnote":" + "version": "(‘Ñ)1.0", + "resolved": "https:]åregistry.npmjs.orgwÛdast-util-gfm-footnote\qwÛdast-util-gfm-footnote-2.1.0.tgz"+"+ + "integrity": "sha5-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh0 pTz69Qc1QSseNX(Nã)bhz1ROIyNyxExfawVKTm Q==", + "license": "MIT"+"+ + "dependencies": { + "@typeswÛdast": "^4.0.0"+"+ + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0"+"+ + "mdast-util-to-markdown": "^(‘Ñ)0.0", + "micromark-util-normalize-identifier": "^(‘Ñ)0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleswÛdast-util-gfm-strikethrough": { + "version": "2.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/mdast-util-gfm-strikethrough\qwÛdast-util-gfm-strikethrough-(‘Ñ)0.0.tgz", + "integrity": "sha51(g()mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxKÀ¬Jvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="+"+ + "license": "MIT", + "dependencies":" + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0"+"+ + "mdast-util-to-markdown": "^(‘Ñ)0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleswÛdast-util-gfm-table": { + "version": "2.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/mdast-util-gfm-table\qwÛdast-util-gfm-table-(‘Ñ)0.0.tgz", + "integrity": "sha51(g()78UEvebzz/rJIxLvE7ZtDd|sIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuje‡NOGOZg=="+"+ + "license": "MIT", + "dependencies":" + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0"+"+ + "markdown-table": "^0Ô0¢0¹0È0ë0.0", + "mdast-util-from-markdown": "^2.0.0"+"+ + "mdast-util-to-markdown": "^(‘Ñ)0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleswÛdast-util-gfm-task-list-item":" + "version": "(‘Ñ)0.0", + "resolved": "https:]åregistry.npmjs.orgwÛdast-util-gfm-task-list-item\qwÛdast-util-gfm-task-list-item-2.0.0.tgz"+"+ + "integrity": "sha5-IrtvNvjxCÝ0taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT"+"+ + "dependencies": { + "@typeswÛdast": "^4.0.0"+"+ + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0"+"+ + "mdast-util-to-markdown": "^(‘Ñ)0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleswÛdast-util-mdx": { + "version": "3.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/mdast-util-mdx\qwÛdast-util-mdx-0Ô0¢0¹0È0ë0.0.tgz", + "integrity": "sha51(g()JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgSÐÀmV60GYpIUg554w=="+"+ + "license": "MIT", + "dependencies":" + "mdast-util-from-markdown": "^(‘Ñ)0.0", + "mdast-util-mdx-expression": "^(‘Ñ)0.0", + "mdast-util-mdx-jsx": "^3.0.0"+"+ + "mdast-util-mdxjs-esm": "^(‘Ñ)0.0", + "mdast-util-to-markdown": "^2.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-(‘Ñ)0.1.tgz", + "integrity": "sha51(g()J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf0Þ0¤0¯0íFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="+"+ + "license": "MIT", + "dependencies":" + "@types/estree-jsx": "^1.0.0"+"+ + "@types/hast": "^3.0.0"+"+ + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0"+"+ + "mdast-util-from-markdown": "^(‘Ñ)0.0", + "mdast-util-to-markdown": "^2.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/mdast-util-mdx-jsx":" + "version": "0Ô0¢0¹0È0ë(‘Ñ)0", + "resolved": "https:]åregistry.npmjs.orgwÛdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz"+"+ + "integrity": "sha5-lj/z8v0r6ZtsNuGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT"+"+ + "dependencies": { + "@typesu0stree-jsx": "^1.0.0", + "@typesvvast": "^0Ô0¢0¹0È0ë0.0", + "@typeswÛdast": "^4.0.0"+"+ + "@types/unist": "^0Ô0¢0¹0È0ë0.0", + "ccount": "^2.0.0"+"+ + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0"+"+ + "mdast-util-to-markdown": "^(‘Ñ)0.0", + "parse-entities": "^4.0.0"+"+ + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleswÛdast-util-mdxjs-esm": { + "version": "2.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/mdast-util-mdxjs-esm\qwÛdast-util-mdxjs-esm-(‘Ñ)0.1.tgz", + "integrity": "sha51(g()EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycgn5HGzCf+10LozMRSObtVr3eeioHtg=="+"+ + "license": "MIT", + "dependencies":" + "@types/estree-jsx": "^1.0.0"+"+ + "@types/hast": "^3.0.0"+"+ + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0"+"+ + "mdast-util-from-markdown": "^(‘Ñ)0.0", + "mdast-util-to-markdown": "^2.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha51(g()TqICwyvJJpBwvGAMZjj4J2n0X8QWp2t9l0o7eXyVJ(ry)YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABzÌ(=="+"+ + "license": "MIT", + "dependencies":" + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleswÛdast-util-to-hast": { + "version": "10Ô0¢0¹0È0ë(‘Ñ)1", + "resolved": "https:]åregistry.npmjs.orgwÛdast-util-to-hast/-/mdast-util-to-hast-10Ô0¢0¹0È0ë(‘Ñ)1.tgz", + "integrity": "sha51(g()cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi0ï0Ã0ÈVsHLM4k6DqQaPyCXA=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0"+"+ + "@types/mdast": "^4.0.0", + "@ungapzttructured-clone": "^1.0.0", + "devlop": "^1.0.0"+"+ + "micromark-util-sanitize-uri": "^2.0.0"+"+ + "trim-lines": "^0Ô0¢0¹0È0ë0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0"+"+ + "vfile": "^6.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/mdast-util-to-markdown":" + "version": "(‘Ñ)1.(N ), + "resolved": "https:]åregistry.npmjs.orgwÛdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz"+"+ + "integrity": "sha5-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi222p¹kUxRQvqSroMqA==", + "license": "MIT"+"+ + "dependencies": { + "@typeswÛdast": "^4.0.0"+"+ + "@types/unist": "^0Ô0¢0¹0È0ë0.0", + "longest-streak": "^3.0.0"+"+ + "mdast-util-phrasing": "^4.0.0"+"+ + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^(‘Ñ)0.0", + "micromark-util-decode-string": "^2.0.0"+"+ + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/mdast-util-to-string":" + "version": "4.0.0", + "resolved": "https:]åregistry.npmjs.orgwÛdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz"+"+ + "integrity": "sha5-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT"+"+ + "dependencies": { + "@typeswÛdast": "^4.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/mdn-data":" + "version": "(‘Ñ)(y]).1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/mdn-data\qwÛdn-data-(‘Ñ)(y]).1.tgz"+"+ + "integrity": "sha5-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl83gQjJl3tqbrM70Ó0ëmjaZaP68AgosQ==", + "license": "CC0-1.0" + (, + "node_moduleswÛicromark":" + "version": "4.0.(N ), + "resolved": "https:]åregistry.npmjs.orgwÛicromark\qwÛicromark-4.0.2.tgz"+"+ + "integrity": "sha5-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + " + "type": "GitHub Sponsors"+"+ + "url": "https:]ågithub.comztponsors/unifiedjs" + }, + { + "type": "OpenCollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + ], + "license": "MIT", + "dependencies":" + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0"+"+ + "devlop": "^1.0.0", + "micromark-core-commonmark": "^(‘Ñ)0.0", + "micromark-factory-space": "^(‘Ñ)0.0", + "micromark-util-character": "^2.0.0"+"+ + "micromark-util-chunked": "^(‘Ñ)0.0", + "micromark-util-combine-extensions": "^(‘Ñ)0.0", + "micromark-util-decode-numeric-character-reference": "^(‘Ñ)0.0", + "micromark-util-encode": "^(‘Ñ)0.0", + "micromark-util-normalize-identifier": "^(‘Ñ)0.0", + "micromark-util-resolve-all": "^2.0.0"+"+ + "micromark-util-sanitize-uri": "^2.0.0"+"+ + "micromark-util-subtokenize": "^(‘Ñ)0.0", + "micromark-util-symbol": "^(‘Ñ)0.0", + "micromark-util-types": "^2.0.0" + ( + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-(‘Ñ)0.0Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()RDBrHEMSxVFLg6xvnXmbyr27zLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="+"+ + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https:/u’ithub.com/sponsorszùnifiedjs" + (, + " + "type": "OpenCollective", + "url": "https:/wópencollective.com/unified" + } + ], + "license": "MIT"+"+ + "dependencies": { + "decode-named-character-reference": "^1.0.0"+"+ + "devlop": "^1.0.0", + "micromark-factory-destination": "^(‘Ñ)0.0", + "micromark-factory-label": "^(‘Ñ)0.0", + "micromark-factory-space": "^(‘Ñ)0.0", + "micromark-factory-title": "^(‘Ñ)0.0", + "micromark-factory-whitespace": "^2.0.0"+"+ + "micromark-util-character": "^(‘Ñ)0.0", + "micromark-util-chunked": "^2.0.0"+"+ + "micromark-util-classify-character": "^2.0.0"+"+ + "micromark-util-html-tag-name": "^(‘Ñ)0.0", + "micromark-util-normalize-identifier": "^(‘Ñ)0.0", + "micromark-util-resolve-all": "^2.0.0"+"+ + "micromark-util-subtokenize": "^(‘Ñ)0.0", + "micromark-util-symbol": "^(‘Ñ)0.0", + "micromark-util-types": "^2.0.0" + ( + }, + "node_modules/micromark-extension-directive": { + "version": "4.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha51(g()e—¬qVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW(h*)JKoCldC+8rFOaundDoRBUWBnJJcxeAk vi0Ö0Ã0·0§0ëWQXg=="+"+ + "license": "MIT", + "dependencies":" + "devlop": "^1.0.0", + "micromark-factory-space": "^(‘Ñ)0.0", + "micromark-factory-whitespace": "^2.0.0"+"+ + "micromark-util-character": "^(‘Ñ)0.0", + "micromark-util-symbol": "^(‘Ñ)0.0", + "micromark-util-types": "^2.0.0"+"+ + "parse-entities": "^4.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleswÛicromark-extension-gfm":" + "version": "0Ô0¢0¹0È0ë0.0", + "resolved": "https:]åregistry.npmjs.orgwÛicromark-extension-gfm\qwÛicromark-extension-gfm-3.0.0.tgz"+"+ + "integrity": "sha5-vsKArQsicm7t0z2GugkCKtZehqUm3ÝeGBVk VSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT"+"+ + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0"+"+ + "micromark-extension-gfm-footnote": "^(‘Ñ)0.0", + "micromark-extension-gfm-strikethrough": "^(‘Ñ)0.0", + "micromark-extension-gfm-table": "^(‘Ñ)0.0", + "micromark-extension-gfm-tagfilter": "^(‘Ñ)0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0"+"+ + "micromark-util-combine-extensions": "^2.0.0"+"+ + "micromark-util-types": "^(‘Ñ)0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleswÛicromark-extension-gfm-autolink-literal": { + "version": "2.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-extension-gfm-autolink-literal\qwÛicromark-extension-gfm-autolink-literal-(‘Ñ)1.0.tgz", + "integrity": "sha51(g()oOg7knzhicgQbar4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPyJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="+"+ + "license": "MIT", + "dependencies":" + "micromark-util-character": "^(‘Ñ)0.0", + "micromark-util-sanitize-uri": "^(‘Ñ)0.0", + "micromark-util-symbol": "^(‘Ñ)0.0", + "micromark-util-types": "^2.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/micromark-extension-gfm-footnote":" + "version": "(‘Ñ)1.0", + "resolved": "https:]åregistry.npmjs.orgwÛicromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz"+"+ + "integrity": "sha5-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJdXOKTIlD5B/gqw==", + "license": "MIT"+"+ + "dependencies": { + "devlop": "^1.0.0"+"+ + "micromark-core-commonmark": "^2.0.0"+"+ + "micromark-factory-space": "^2.0.0"+"+ + "micromark-util-character": "^(‘Ñ)0.0", + "micromark-util-normalize-identifier": "^(‘Ñ)0.0", + "micromark-util-sanitize-uri": "^(‘Ñ)0.0", + "micromark-util-symbol": "^(‘Ñ)0.0", + "micromark-util-types": "^2.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-(‘Ñ)1.0.tgz", + "integrity": "sha51(g()ADVjpOOkjzÈhkZLlBiYA9cR(O)nf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="+"+ + "license": "MIT", + "dependencies":" + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0"+"+ + "micromark-util-classify-character": "^2.0.0"+"+ + "micromark-util-resolve-all": "^(‘Ñ)0.0", + "micromark-util-symbol": "^(‘Ñ)0.0", + "micromark-util-types": "^2.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-(‘Ñ)1.1.tgz", + "integrity": "sha51(g()t2OUu(XXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="+"+ + "license": "MIT", + "dependencies":" + "devlop": "^1.0.0", + "micromark-factory-space": "^(‘Ñ)0.0", + "micromark-util-character": "^2.0.0"+"+ + "micromark-util-symbol": "^2.0.0"+"+ + "micromark-util-types": "^(‘Ñ)0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleswÛicromark-extension-gfm-tagfilter":" + "version": "(‘Ñ)0.0", + "resolved": "https:]åregistry.npmjs.orgwÛicromark-extension-gfm-tagfilter\qwÛicromark-extension-gfm-tagfilter-2.0.0.tgz"+"+ + "integrity": "sha5-xHlTOmuCSotIA8TW1mDIM6X2OeiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEVgyr2TiD+PTEQ10o+cOhdVAcwfg==", + "license": "MIT"+"+ + "dependencies": { + "micromark-util-types": "^2.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/micromark-extension-gfm-task-list-item":" + "version": "(‘Ñ)1.0", + "resolved": "https:]åregistry.npmjs.orgwÛicromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz"+"+ + "integrity": "sha5-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQdm2NLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT"+"+ + "dependencies": { + "devlop": "^1.0.0"+"+ + "micromark-factory-space": "^2.0.0"+"+ + "micromark-util-character": "^(‘Ñ)0.0", + "micromark-util-symbol": "^(‘Ñ)0.0", + "micromark-util-types": "^2.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/micromark-extension-mdx-expression":" + "version": "0Ô0¢0¹0È0ë0.1", + "resolved": "https:]åregistry.npmjs.orgwÛicromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz"+"+ + "integrity": "sha5-dD/ADLJeMvSAKBwO2Ó G22N4ybhe7kFIZ0á0¬0È0ósDI0GlsNr2A3KYxb0LdC#5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + " + "type": "GitHub Sponsors"+"+ + "url": "https:]ågithub.comztponsors/unifiedjs" + }, + { + "type": "OpenCollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + ], + "license": "MIT", + "dependencies":" + "@types/estree": "^1.0.0"+"+ + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0"+"+ + "micromark-factory-space": "^2.0.0"+"+ + "micromark-util-character": "^(‘Ñ)0.0", + "micromark-util-events-to-acorn": "^2.0.0"+"+ + "micromark-util-symbol": "^2.0.0"+"+ + "micromark-util-types": "^(‘Ñ)0.0" + } + (, + "node_moduleswÛicromark-extension-mdx-jsx":" + "version": "0Ô0¢0¹0È0ë0.(N ), + "resolved": "https:]åregistry.npmjs.orgwÛicromark-extension-mdx-jsx\qwÛicromark-extension-mdx-jsx-3.0.2.tgz"+"+ + "integrity": "sha5-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy·|FEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT"+"+ + "dependencies": { + "@typesu0stree": "^1.0.0", + "devlop": "^1.0.0"+"+ + "estree-util-is-identifier-name": "^0Ô0¢0¹0È0ë0.0", + "micromark-factory-mdx-expression": "^2.0.0"+"+ + "micromark-factory-space": "^2.0.0"+"+ + "micromark-util-character": "^(‘Ñ)0.0", + "micromark-util-events-to-acorn": "^2.0.0"+"+ + "micromark-util-symbol": "^2.0.0"+"+ + "micromark-util-types": "^(‘Ñ)0.0", + "vfile-message": "^4.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleswÛicromark-extension-mdx-md": { + "version": "2.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-extension-mdx-md\qwÛicromark-extension-mdx-md-(‘Ñ)0.0.tgz", + "integrity": "sha51(g()EpAiszsB10p¹lw4Rpba7xTOUptcFeBFi+6PY8VnJ himH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ=="+"+ + "license": "MIT", + "dependencies":" + "micromark-util-types": "^(‘Ñ)0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleswÛicromark-extension-mdxjs":" + "version": "0Ô0¢0¹0È0ë0.0", + "resolved": "https:]åregistry.npmjs.orgwÛicromark-extension-mdxjs\qwÛicromark-extension-mdxjs-3.0.0.tgz"+"+ + "integrity": "sha5-A8714p¹JfhnJ2siZyUrJ3×0Ö0Ã0·0§0ëUqwy4xIFmvPYÝj+Ean5PHcPBYzEsvqvWGaWcfEIr1a5Dlw24p¹ÐÀ0tZWpKHDejQ==", + "license": "MIT"+"+ + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0"+"+ + "micromark-extension-mdx-jsx": "^3.0.0"+"+ + "micromark-extension-mdx-md": "^(‘Ñ)0.0", + "micromark-extension-mdxjs-esm": "^0Ô0¢0¹0È0ë0.0", + "micromark-util-combine-extensions": "^(‘Ñ)0.0", + "micromark-util-types": "^2.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-0Ô0¢0¹0È0ë0.0.tgz", + "integrity": "sha51(g()DJFl4ZqkErRpq/dAPyeWp15tGrcrrJhoÈKK5uBS70BCtfrIFg81sqcTVu0ë0ü0Ö0ëa+KDfk5vAtBNElWxtAa+m8K9A=="+"+ + "license": "MIT", + "dependencies":" + "@types/estree": "^1.0.0"+"+ + "devlop": "^1.0.0", + "micromark-core-commonmark": "^(‘Ñ)0.0", + "micromark-util-character": "^2.0.0"+"+ + "micromark-util-events-to-acorn": "^(‘Ñ)0.0", + "micromark-util-symbol": "^(‘Ñ)0.0", + "micromark-util-types": "^2.0.0"+"+ + "unist-util-position-from-estree": "^2.0.0"+"+ + "vfile-message": "^4.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-(‘Ñ)0.1.tgz", + "integrity": "sha51(g()Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs/zËqOdYe¤xhjdCiJnIA=="+"+ + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https:/u’ithub.com/sponsorszùnifiedjs" + (, + " + "type": "OpenCollective", + "url": "https:/wópencollective.com/unified" + } + ], + "license": "MIT"+"+ + "dependencies": { + "micromark-util-character": "^2.0.0"+"+ + "micromark-util-symbol": "^2.0.0"+"+ + "micromark-util-types": "^(‘Ñ)0.0" + } + (, + "node_moduleswÛicromark-factory-label":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgwÛicromark-factory-label\qwÛicromark-factory-label-2.0.1.tgz"+"+ + "integrity": "sha5-VFMekyQExqIW7xIChcXn4ok(Nã)YEdanuyveWdmZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF90Þ0ë0¯XIbBp6YdW3t71/7Vg==", + "funding": [ + " + "type": "GitHub Sponsors"+"+ + "url": "https:]ågithub.comztponsors/unifiedjs" + }, + { + "type": "OpenCollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + ], + "license": "MIT", + "dependencies":" + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0"+"+ + "micromark-util-symbol": "^2.0.0"+"+ + "micromark-util-types": "^(‘Ñ)0.0" + } + (, + "node_moduleswÛicromark-factory-mdx-expression": { + "version": "2.0.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-factory-mdx-expression\qwÛicromark-factory-mdx-expression-(‘Ñ)0.0Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()kQnEtApczucU(ê)krIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ=="+"+ + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https:/u’ithub.com/sponsorszùnifiedjs" + (, + " + "type": "OpenCollective", + "url": "https:/wópencollective.com/unified" + } + ], + "license": "MIT"+"+ + "dependencies": { + "@typesu0stree": "^1.0.0", + "devlop": "^1.0.0"+"+ + "micromark-factory-space": "^2.0.0"+"+ + "micromark-util-character": "^(‘Ñ)0.0", + "micromark-util-events-to-acorn": "^2.0.0"+"+ + "micromark-util-symbol": "^2.0.0"+"+ + "micromark-util-types": "^(‘Ñ)0.0", + "unist-util-position-from-estree": "^(‘Ñ)0.0", + "vfile-message": "^4.0.0" + } + (, + "node_moduleswÛicromark-factory-space":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgwÛicromark-factory-space\qwÛicromark-factory-space-2.0.1.tgz"+"+ + "integrity": "sha5-zRkxjtBxxLd23c0d+fbnEunsTj46SWXgXciZmHq0kDYGnckrGSGj9|øULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + " + "type": "GitHub Sponsors"+"+ + "url": "https:]ågithub.comztponsors/unifiedjs" + }, + { + "type": "OpenCollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + ], + "license": "MIT", + "dependencies":" + "micromark-util-character": "^(‘Ñ)0.0", + "micromark-util-types": "^2.0.0" + ( + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-factory-title/-/micromark-factory-title-(‘Ñ)0.1.tgz", + "integrity": "sha51(g()5bZ+0Þ0¤0¯0íjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs rm9AZH7OD4Cjzw=="+"+ + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https:/u’ithub.com/sponsorszùnifiedjs" + (, + " + "type": "OpenCollective", + "url": "https:/wópencollective.com/unified" + } + ], + "license": "MIT"+"+ + "dependencies": { + "micromark-factory-space": "^(‘Ñ)0.0", + "micromark-util-character": "^2.0.0"+"+ + "micromark-util-symbol": "^2.0.0"+"+ + "micromark-util-types": "^(‘Ñ)0.0" + } + (, + "node_moduleswÛicromark-factory-whitespace": { + "version": "2.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-factory-whitespace\qwÛicromark-factory-whitespace-(‘Ñ)0.1.tgz", + "integrity": "sha51(g()Ob0nuZ0æ0¢0óKtwâ0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="+"+ + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https:/u’ithub.com/sponsorszùnifiedjs" + (, + " + "type": "OpenCollective", + "url": "https:/wópencollective.com/unified" + } + ], + "license": "MIT"+"+ + "dependencies": { + "micromark-factory-space": "^(‘Ñ)0.0", + "micromark-util-character": "^2.0.0"+"+ + "micromark-util-symbol": "^2.0.0"+"+ + "micromark-util-types": "^(‘Ñ)0.0" + } + (, + "node_moduleswÛicromark-util-character": { + "version": "2.1.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-util-character\qwÛicromark-util-character-(‘Ñ)1.1.tgz", + "integrity": "sha51(g()wv8tdUTJbarhSFFFJKtpYKOYiGP(pk)v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="+"+ + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https:/u’ithub.com/sponsorszùnifiedjs" + (, + " + "type": "OpenCollective", + "url": "https:/wópencollective.com/unified" + } + ], + "license": "MIT"+"+ + "dependencies": { + "micromark-util-symbol": "^(‘Ñ)0.0", + "micromark-util-types": "^2.0.0" + ( + }, + "node_modules/micromark-util-chunked":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgwÛicromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz"+"+ + "integrity": "sha5-QUNFEOPELfmvv+4xiNg¼RYeSkÔ84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI,5oO5Vc`bhA==", + "funding": [ + " + "type": "GitHub Sponsors"+"+ + "url": "https:]ågithub.comztponsors/unifiedjs" + }, + { + "type": "OpenCollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + ], + "license": "MIT", + "dependencies":" + "micromark-util-symbol": "^2.0.0" + ( + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-(‘Ñ)0.1.tgz", + "integrity": "sha51(g()K0kHzM6afWkybeWYWLjoHQv1sgg2Q9EccHEDzSkxiPe¹aagNzCm7T/WMKZdajMbvIpvBiZgwR3dKMygtA4mGc=="+"+ + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https:/u’ithub.com/sponsorszùnifiedjs" + (, + " + "type": "OpenCollective", + "url": "https:/wópencollective.com/unified" + } + ], + "license": "MIT"+"+ + "dependencies": { + "micromark-util-character": "^2.0.0"+"+ + "micromark-util-symbol": "^2.0.0"+"+ + "micromark-util-types": "^(‘Ñ)0.0" + } + (, + "node_moduleswÛicromark-util-combine-extensions":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgwÛicromark-util-combine-extensions\qwÛicromark-util-combine-extensions-2.0.1.tgz"+"+ + "integrity": "sha5-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLHe‡g==", + "funding": [ + " + "type": "GitHub Sponsors"+"+ + "url": "https:]ågithub.comztponsors/unifiedjs" + }, + { + "type": "OpenCollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + ], + "license": "MIT", + "dependencies":" + "micromark-util-chunked": "^(‘Ñ)0.0", + "micromark-util-types": "^2.0.0" + ( + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-(‘Ñ)0.(‘Ñ)tgz", + "integrity": "sha51(g()ccUbYk6CwVdkmCQMyr64dXz4^|fHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="+"+ + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https:/u’ithub.com/sponsorszùnifiedjs" + (, + " + "type": "OpenCollective", + "url": "https:/wópencollective.com/unified" + } + ], + "license": "MIT"+"+ + "dependencies": { + "micromark-util-symbol": "^(‘Ñ)0.0" + } + (, + "node_moduleswÛicromark-util-decode-string": { + "version": "2.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-util-decode-string\qwÛicromark-util-decode-string-(‘Ñ)0.1.tgz", + "integrity": "sha51(g()nDV/77Fj6eH1ynwscYTOsbK7rR]åUj0bZXBwJZRfaLEJÌGBR6kIfNmlNqaqJf649EP0F3NWNdeJi013p¹lllNUQ=="+"+ + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https:/u’ithub.com/sponsorszùnifiedjs" + (, + " + "type": "OpenCollective", + "url": "https:/wópencollective.com/unified" + } + ], + "license": "MIT"+"+ + "dependencies": { + "decode-named-character-reference": "^1.0.0"+"+ + "micromark-util-character": "^(‘Ñ)0.0", + "micromark-util-decode-numeric-character-reference": "^(‘Ñ)0.0", + "micromark-util-symbol": "^(‘Ñ)0.0" + } + (, + "node_moduleswÛicromark-util-encode":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgwÛicromark-util-encode\qwÛicromark-util-encode-2.0.1.tgz"+"+ + "integrity": "sha5-c11p¹VxÐÀ4KqUnwopcO9blCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + " + "type": "GitHub Sponsors"+"+ + "url": "https:]ågithub.comztponsors/unifiedjs" + }, + { + "type": "OpenCollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn":" + "version": "(‘Ñ)0.0»0ó0Á, + "resolved": "https:]åregistry.npmjs.orgwÛicromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz"+"+ + "integrity": "sha5-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk-kwEVhBuIUKRu14p¹qv+hs4nxLISiUOQGlqIOGiFxgbfHg==", + "funding": [ + " + "type": "GitHub Sponsors"+"+ + "url": "https:]ågithub.comztponsors/unifiedjs" + }, + { + "type": "OpenCollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + ], + "license": "MIT", + "dependencies":" + "@types/estree": "^1.0.0"+"+ + "@types/unist": "^0Ô0¢0¹0È0ë0.0", + "devlop": "^1.0.0"+"+ + "estree-util-visit": "^2.0.0"+"+ + "micromark-util-symbol": "^2.0.0"+"+ + "micromark-util-types": "^(‘Ñ)0.0", + "vfile-message": "^4.0.0" + } + (, + "node_moduleswÛicromark-util-html-tag-name": { + "version": "2.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-util-html-tag-name\qwÛicromark-util-html-tag-name-(‘Ñ)0.1.tgz", + "integrity": "sha51(g()NEiYDhCWKI+Gs9T0Tiysk10Ø0¯0¿0ü0ëSnR116p¹hO8yW6BGNyhOC4qYFnwFKfD3HFAIXA5c45RrIG#b1 iXeYd1xA=="+"+ + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https:/u’ithub.com/sponsorszùnifiedjs" + (, + " + "type": "OpenCollective", + "url": "https:/wópencollective.com/unified" + } + ], + "license": "MIT" + (, + "node_moduleswÛicromark-util-normalize-identifier":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgwÛicromark-util-normalize-identifier\qwÛicromark-util-normalize-identifier-2.0.1.tgz"+"+ + "integrity": "sha5-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+eåIe0ï0Ã0ÈKl0IjbeåyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + " + "type": "GitHub Sponsors"+"+ + "url": "https:]ågithub.comztponsors/unifiedjs" + }, + { + "type": "OpenCollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + ], + "license": "MIT", + "dependencies":" + "micromark-util-symbol": "^2.0.0" + ( + }, + "node_modules/micromark-util-resolve-all":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgwÛicromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz"+"+ + "integrity": "sha5-VdQyxFWFT(W)FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + " + "type": "GitHub Sponsors"+"+ + "url": "https:]ågithub.comztponsors/unifiedjs" + }, + { + "type": "OpenCollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + ], + "license": "MIT", + "dependencies":" + "micromark-util-types": "^(‘Ñ)0.0" + } + (, + "node_moduleswÛicromark-util-sanitize-uri":" + "version": "(‘Ñ)0.1", + "resolved": "https:]åregistry.npmjs.orgwÛicromark-util-sanitize-uri\qwÛicromark-util-sanitize-uri-2.0.1.tgz"+"+ + "integrity": "sha5-9N9IomZr?uGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+odm3zJK9YBQ==", + "funding": [ + " + "type": "GitHub Sponsors"+"+ + "url": "https:]ågithub.comztponsors/unifiedjs" + }, + { + "type": "OpenCollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + ], + "license": "MIT", + "dependencies":" + "micromark-util-character": "^(‘Ñ)0.0", + "micromark-util-encode": "^(‘Ñ)0.0", + "micromark-util-symbol": "^(‘Ñ)0.0" + } + (, + "node_moduleswÛicromark-util-subtokenize": { + "version": "2.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-util-subtokenize\qwÛicromark-util-subtokenize-(‘Ñ)1.0.tgz", + "integrity": "sha51(g()XQLu55SctvnEcgXw6+Sx75GflAPNEDx7eBJ+wydBb2KCbRZe+NwvIEEMM8oVml1+27SXpBAcp9IUCgCYWA=="+"+ + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https:/u’ithub.com/sponsorszùnifiedjs" + (, + " + "type": "OpenCollective", + "url": "https:/wópencollective.com/unified" + } + ], + "license": "MIT"+"+ + "dependencies": { + "devlop": "^1.0.0"+"+ + "micromark-util-chunked": "^(‘Ñ)0.0", + "micromark-util-symbol": "^(‘Ñ)0.0", + "micromark-util-types": "^2.0.0" + ( + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-(‘Ñ)0.1.tgz", + "integrity": "sha51(g()vs5t8Apaud9N(R´)kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY0Ó0ëM0ETy5y1vA0Õ0£0ü0ÈYoNPDFTghEbnh6efaE8h4x0Q=="+"+ + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https:/u’ithub.com/sponsorszùnifiedjs" + (, + " + "type": "OpenCollective", + "url": "https:/wópencollective.com/unified" + } + ], + "license": "MIT" + (, + "node_moduleswÛicromark-util-types": { + "version": "2.0.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/micromark-util-types\qwÛicromark-util-types-(‘Ñ)0.(‘Ñ)tgz", + "integrity": "sha51(g()Yw0ECSpJoViFTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY)B2Ó mxnJqhPyTpOVCpeHTA=="+"+ + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https:/u’ithub.com/sponsorszùnifiedjs" + (, + " + "type": "OpenCollective", + "url": "https:/wópencollective.com/unified" + } + ], + "license": "MIT" + (, + "node_moduleswÛrmime": { + "version": "2.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/mrmime\qwÛrmime-(‘Ñ)0.1.tgz", + "integrity": "sha51(g()Y3wQdFg2Va6etvQ5I8ÐÀUhGdsKrcYox6p7FfL1LbK2J4V0 9TGlepTIhnK24t7koZibmg82KGglhAjK5IsLQ=="+"+ + "license": "MIT", + "engines": { + "node": ">=10" + ( + }, + "node_modules/ms":" + "version": "(‘Ñ)1.0»0ó0Á, + "resolved": "https:]åregistry.npmjs.orgwÛs/-/ms-2.1.3.tgz"+"+ + "integrity": "sha5-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6r;XP6tz7R9xAOtHnSOzËXtF3WRTlA==", + "license": "MIT" + (, + "node_moduleswâanoid": { + "version": "3.3.1(N ), + "resolved": "https:]åregistry.npmjs.orgwâanoid/-/nanoid-3.3.1(‘Ñ)tgz", + "integrity": "sha51(g()ZB9RH/0Ø0ë0Äqpq5Vu6Y+NmUaFhQR6pp+M28t76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH0ê0Ã0È0ëR76v9p¹SrKaF/Kiy7lEtQ=="+"+ + "funding": [ + { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/ai" + ( + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + (, + "engines": { + "node": "^10 || ^=| ^10Ô0¢0¹0È0ë7 || ^=| >=¬.0.1" + ( + }, + "node_modules/neotraverse": { + "version": "."."..6.18", + "resolved": "https:]åregistry.npmjs.orgwâeotraverse\qwâeotraverse-0.6.18.tgz", + "integrity": "sha51(g()Z4SmBUweYaSD+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB(pk)NgRpQvr7vA=="+"+ + "license": "MIT", + "engines": { + "node": ">= 10" + } + (, + "node_moduleswâlcst-to-string":" + "version": "4.0.0", + "resolved": "https:]åregistry.npmjs.orgwâlcst-to-string\qwâlcst-to-string-4.0.0.tgz"+"+ + "integrity": "sha5-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA0·|Iyy8uiRqI8QXtcFpEvllLpL5MXqEmAZ+dcA==", + "license": "MIT"+"+ + "dependencies": { + "@typeswâlcst": "^2.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/node-fetch-native": { + "version": "1.6.7"+"+ + "resolved": "https:/y¾egistry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha51(g()g9yhqoedzIUm0nTnTqAQvueMPVOuIY­bqgAJJC8XOOubYFNwz6IER9qs0Gq28d0+CecCKFjtdDTMA4u4xG06Q=="+"+ + "license": "MIT" + }, + "node_modules/node-mock-http":" + "version": "1.0.4", + "resolved": "https:]åregistry.npmjs.orgwâode-mock-http/-/node-mock-http-1.0.4.tgz"+"+ + "integrity": "sha5-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc26k0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", + "license": "MIT" + (, + "node_moduleswâormalize-path": { + "version": "3.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/normalize-path\qwâormalize-path-0Ô0¢0¹0È0ë0.0.tgz", + "integrity": "sha51(g()6eZs5Ls3WtCisHWp9S{Uy8dqkpGi4BVSz0Þ0ó0·0ç0óaqiE6ezub051^|SztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="+"+ + "license": "MIT", + "engines": { + "node": ">=0.10.0" + ( + }, + "node_modules/nth-check": { + "version": "2.1.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/nth-check/-/nth-check-(‘Ñ)1.1.tgz", + "integrity": "sha51(g()lqjrjmaOoAnWfMmBPL+XNnynZh(pk)swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl0p¹^QctVqS8AiZjFqyOGrMXb|ø=="+"+ + "license": "BSD-(g()Clause"+"+ + "dependencies": { + "boolbase": "^1.0.0" + (, + "funding": { + "url": "https:/u’ithub.com/fb55wâth-check?sponsor=1" + ( + }, + "node_modules/obug":" + "version": "(‘Ñ)1.1", + "resolved": "https:]åregistry.npmjs.orgwóbug/-/obug-2.1.1.tgz"+"+ + "integrity": "sha5-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO0á0¬Ec+wb0a6yKpeL0lmk2IBÎY5KHPAlTc6T/JRdCr?qxHNwkQ==", + "funding": [ + "https:]ågithub.comztponsors/sxzz"+"+ + "https:/wópencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/ofetch":" + "version": "1.5.1", + "resolved": "https:]åregistry.npmjs.orgwófetch/-/ofetch-1.5.1.tgz"+"+ + "integrity": "sha5-2W4oUZlVaqAPAil6FUgu(ifl6YhqhUR7xZY4bQCko(g )UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/xcA==", + "license": "MIT"+"+ + "dependencies": { + "destr": "^(‘Ñ)0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + (, + "node_moduleswóhash":" + "version": "(‘Ñ)0."+"+ + "resolved": "https:/y¾egistry.npmjs.org/ohash/-/ohash-(‘Ñ)0..tgz"+"+ + "integrity": "sha5-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + (, + "node_moduleswóniguruma-parser": { + "version": "."."..1(‘Ñ)(N ), + "resolved": "https:]åregistry.npmjs.orgwóniguruma-parser/-/oniguruma-parser-0.1(‘Ñ)(‘Ñ)tgz", + "integrity": "sha51(g()6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+(pk)zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="+"+ + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6"+"+ + "resolved": "https:/y¾egistry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.0Ô0¢0¹0È0ë6.tgz", + "integrity": "sha51(g()csuQ9x1p¹r0cEIsrGgxkËEt9iBw9vqIunAPQkx19Ru‹iMq2oGVTgcMqO/V3YbqefrfBvosI6jU539ksaBULJyA=="+"+ + "license": "MIT", + "dependencies":" + "oniguruma-parser": "^0..2"+"+ + "regex": "^6.1.0"+"+ + "regex-recursion": "^6.0.2" + ( + }, + "node_modules/p-limit": { + "version": "7.3.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/p-limit/-/p-limit-7.0Ô0¢0¹0È0ë0.tgz", + "integrity": "sha51(g()7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfVhPaJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZl]þd6J9VVUw=="+"+ + "license": "MIT", + "dependencies":" + "yocto-queue": "^1.2.1" + (, + "engines": { + "node": ">=(eå)" + (, + "funding": { + "url": "https:/u’ithub.com/sponsorsztindresorhus" + } + (, + "node_modulesy:-queue":" + "version": "9.0Ô0¢0¹0È0ë0", + "resolved": "https:]åregistry.npmjs.orgy:-queue\qy:-queue-9.3.0.tgz"+"+ + "integrity": "sha5-7NED7xhQ74Ngp4JP/0VZHp7vSWfJfqeiR92jPgxsz6m0Se4P01p¹oTKa9dDXyZda6P616gUXttrB6nnHYKang==", + "license": "MIT"+"+ + "dependencies": { + "eventemitter0»0ó0Á: "^5.0.4", + "p-timeout": "^7.0.0" + }, + "engines":" + "node": ">=20" + }, + "funding":" + "url": "https:]ågithub.comztponsors/sindresorhus" + ( + }, + "node_modules/p-timeout": { + "version": "7.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha51(g()AxTMÌ(DGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC0Þ0¤0¯0íŠNcGT63SymHÀ¬tOAaiIwsÓ Vg4+izQCjrakcwHg=="+"+ + "license": "MIT", + "engines": { + "node": ">=(eå)" + (, + "funding": { + "url": "https:/u’ithub.com/sponsorsztindresorhus" + } + (, + "node_modulesy:ackage-manager-detector": { + "version": "1.6.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/package-manager-detector\qy:ackage-manager-detector-1.6.0.tgz", + "integrity": "sha51(g()61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg¬+iQiPzrLJ4jhZ1HJzmC2PIHT°˜EiH3R5fzNA=="+"+ + "license": "MIT" + }, + "node_modules/pagefind":" + "version": "1.5.(N ), + "resolved": "https:]åregistry.npmjs.orgy:agefind/-/pagefind-1.5.2.tgz"+"+ + "integrity": "sha5-XTUaK0hXMCuszWE584JGQT7y284TmMV9lfðX3rnG5uodaHIzùHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q==", + "license": "MIT"+"+ + "bin":" + "pagefind": "liby¾unner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.5.(N ), + "@pagefind/darwin-x64": "1.5.(N ), + "@pagefind/freebsd-x64": "1.5.2"+"+ + "@pagefindvîinux-arm64": "1.5.(N ), + "@pagefind/linux-x64": "1.5.2"+"+ + "@pagefind|øindows-arm64": "1.5.(N ), + "@pagefind/windows-x64": "1.5.2" + ( + }, + "node_modules/parse-entities":" + "version": "4.0.(N ), + "resolved": "https:]åregistry.npmjs.orgy:arse-entities/-/parse-entities-4.0.2.tgz"+"+ + "integrity": "sha5-GG2AQYWoLgL877gQIKeRPGO+F9+eG1ujIb5soS5gPvLQ,°˜8FL90w21WNdf9I0Ø0¯0¿0ü0ëpp77(Œ¡)c+lj0ì0à0qK1uGw==", + "license": "MIT"+"+ + "dependencies": { + "@typeszùnist": "^2.0.0"+"+ + "character-entities-legacy": "^3.0.0"+"+ + "character-reference-invalid": "^2.0.0"+"+ + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^(‘Ñ)0.0", + "is-decimal": "^2.0.0"+"+ + "is-hexadecimal": "^(‘Ñ)0.0" + }, + "funding":" + "type": "github", + "url": "https:/u’ithub.com/sponsors|øooorm" + } + (, + "node_modulesy:arse-entities/node_modulese/types/unist": { + "version": "2.0.11", + "resolved": "https:]åregistry.npmjs.orge/types/unist/-/unist-(‘Ñ)0..tgz"+"+ + "integrity": "sha5-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKNe‡4SjvrEesubTRWGYSg+BnWZOnlCaSTU MpsBOzgbYhnsA==", + "license": "MIT" + (, + "node_modulesy:arse-latin":" + "version": "7.0.0", + "resolved": "https:]åregistry.npmjs.orgy:arse-latin\qy:arse-latin-7.0.0.tgz"+"+ + "integrity": "sha5-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "license": "MIT"+"+ + "dependencies": { + "@typeswâlcst": "^2.0.0"+"+ + "@types/unist": "^0Ô0¢0¹0È0ë0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0"+"+ + "unist-util-visit-children": "^3.0.0"+"+ + "vfile": "^6.0.0" + (, + "funding": { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/wooorm" + ( + }, + "node_modules/parse5":" + "version": "7.0Ô0¢0¹0È0ë0", + "resolved": "https:]åregistry.npmjs.orgy:arse5/-/parse5-7.3.0.tgz"+"+ + "integrity": "sha5-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8r?+7552KlejkRvqvD19nMoUWzùQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT"+"+ + "dependencies": { + "entities": "^6.0.0" + (, + "funding": { + "url": "https:/u’ithub.com/inikuliny:arse5?sponsor=1" + } + (, + "node_modulesy:iccolore":" + "version": "0.1.0»0ó0Á, + "resolved": "https:]åregistry.npmjs.orgy:iccolore\qy:iccolore-0.1.3.tgz"+"+ + "integrity": "sha5-a8bTeDWjESA6iwKrROaDf0Ó0ëK0qCe‡ENdm¬zùH9usSCzùZjJOKb29GiVHfLY4GhwsERiPI1jmwI28rA7ACOxVw==", + "license": "ISC" + (, + "node_modulesy:icocolors": { + "version": "1.1.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/picocolors\qy:icocolors-1.1.1.tgz", + "integrity": "sha51(g()xceH¼nhtb5M9liqDsmEw56le376mTZkEXv®EblxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="+"+ + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha51(g()QP88BAKvMam/0ä0ü0ÉxH6vj2o2d6MjxZUAd6nlwASy:nGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="+"+ + "license": "MIT", + "engines": { + "node": ">=" + (, + "funding": { + "url": "https:/u’ithub.com/sponsorsv®onschlinkert" + ( + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https:]åregistry.npmjs.orgy:ostcss\qy:ostcss-8.5.15.tgz", + "integrity": "sha51(g()FfR8sjd4em246fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC]ñX4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB0Û0ü0ë=="+"+ + "funding": [ + { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comy:ostcssY + }, + { + "type": "tidelift"+"+ + "url": "https:]åtidelift.comu‹undingu’ithub/npm/postcss" + }, + { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/ai" + ( + ], + "license": "MIT", + "peer":4rue, + "dependencies": { + "nanoid": "^3.3.1(N ), + "picocolors": "^1.1.1"+"+ + "source-map-js": "^1.2.1" + (, + "engines": { + "node": "^10 || ^=| >=" + ( + }, + "node_modules/postcss-nested":" + "version": "6.(‘Ñ)0", + "resolved": "https:]åregistry.npmjs.orgy:ostcss-nested/-/postcss-nested-6.2.0.tgz"+"+ + "integrity": "sha5-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvogiFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + " + "type": "opencollective", + "url": "https:/wópencollective.com/postcss/" + (, + " + "type": "github", + "url": "https:/u’ithub.com/sponsorstæi" + } + ], + "license": "MIT"+"+ + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines":" + "node": ">=1(‘Ñ)0" + }, + "peerDependencies": { + "postcss": "^8.(‘Ñ)" + ( + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.(‘Ñ)tgz", + "integrity": "sha51(g()Q8qQfPiZ+THO^zZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6zr6BeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="+"+ + "license": "MIT", + "dependencies":" + "cssesc": "^0Ô0¢0¹0È0ë0.0", + "util-deprecate": "^1.0.2" + (, + "engines": { + "node": ">=4" + } + (, + "node_modulesy:rismjs":" + "version": "1.0Ô0³.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/prismjs/-/prismjs-1.0Ô0³.0.tgz"+"+ + "integrity": "sha5-DEvV2ZF¹È(W)63V+tK8hQvrR30Gn10srHbXviTlcv7Kpzw8jWiNTqbVgjO0ß0êY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT"+"+ + "engines":" + "node": ">=6" + ( + }, + "node_modules/property-information":" + "version": "7.1.0", + "resolved": "https:]åregistry.npmjs.orgy:roperty-information/-/property-information-7.1.0.tgz"+"+ + "integrity": "sha5-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX]å0th50Þ0¤0ëE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT"+"+ + "funding":" + "type": "github", + "url": "https:/u’ithub.com/sponsors|øooorm" + } + (, + "node_modulesy¾adix0»0ó0Á: { + "version": "1.1.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/radix3\qy¾adix0Ð0ü0ì0ë1.1.(‘Ñ)tgz", + "integrity": "sha51(g()b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL60Ø0ë0ÄwFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="+"+ + "license": "MIT" + }, + "node_modules/readdirp":" + "version": "5.0.0", + "resolved": "https:]åregistry.npmjs.orgy¾eaddirp/-/readdirp-5.0.0.tgz"+"+ + "integrity": "sha5-9u/XQßvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR6c7AHljaeteeHXnXTAaLLUVZYVZyuRBQ==", + "license": "MIT"+"+ + "engines":" + "node": ">= (eå).19.0" + }, + "funding":" + "type": "individual", + "url": "https:/y:aulmillr.comu‹undingY + } + (, + "node_modulesy¾ecma-build-jsx":" + "version": "1.0.0", + "resolved": "https:]åregistry.npmjs.orgy¾ecma-build-jsx\qy¾ecma-build-jsx-1.0.0.tgz"+"+ + "integrity": "sha5-8GtdyqaBcDfva+GUKDr22p¹ev0ì0ó0È0²0ópKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT"+"+ + "dependencies": { + "@typesu0stree": "^1.0.0", + "estree-util-build-jsx": "^0Ô0¢0¹0È0ë0.0", + "vfile": "^6.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesy¾ecma-jsx":" + "version": "1.0.1", + "resolved": "https:]åregistry.npmjs.orgy¾ecma-jsx\qy¾ecma-jsx-1.0.1.tgz"+"+ + "integrity": "sha5-huSIy7VU2Z5OLv6oFLosQGGDqPqdOÌq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "license": "MIT"+"+ + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^(‘Ñ)0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + (, + "peerDependencies":" + "acorn": "^6.0.0=| ^7.0.0=| ^8.0.0" + ( + }, + "node_modules/recma-parse": { + "version": "1.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha51(g()OYLsIGBB5Y5wjnSnQW6t0p¹g7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1tæN8CjfwoUEUIdwqWQ=="+"+ + "license": "MIT", + "dependencies":" + "@types/estree": "^1.0.0"+"+ + "esast-util-from-js": "^(‘Ñ)0.0", + "unified": "^.0.0"+"+ + "vfile": "^6.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/recma-stringify": { + "version": "1.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha51(g()cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js(ó)+g=="+"+ + "license": "MIT", + "dependencies":" + "@types/estree": "^1.0.0"+"+ + "estree-util-to-js": "^2.0.0"+"+ + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesy¾egex":" + "version": "6.1.0", + "resolved": "https:]åregistry.npmjs.orgy¾egex\qy¾egex-6.1.0.tgz"+"+ + "integrity": "sha5-6VwtthbV4o/7+OaAF9I5L5V20p¹lLEsoPyq9PVXkedTP311p¹7MfCG0^ôNOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT"+"+ + "dependencies": { + "regex-utilities": "^(‘Ñ)0Ô0¢0¹0È0ë0" + } + (, + "node_modulesy¾egex-recursion":" + "version": "6.0.(N ), + "resolved": "https:]åregistry.npmjs.orgy¾egex-recursion\qy¾egex-recursion-6.0.2.tgz"+"+ + "integrity": "sha5-0YCaSCq26RIebiaUviZNs0cBzÓg5kVS2UKUfNIx8YVs1cN0Û0ü0ëV7NTctO5FOKBA+UT(ê)PJIWZauYHPqJODG50cg==", + "license": "MIT"+"+ + "dependencies": { + "regex-utilities": "^(‘Ñ)0Ô0¢0¹0È0ë0" + } + (, + "node_modulesy¾egex-utilities":" + "version": "(‘Ñ)0Ô0¢0¹0È0ë0", + "resolved": "https:]åregistry.npmjs.orgy¾egex-utilities\qy¾egex-utilities-2.3.0.tgz"+"+ + "integrity": "sha5-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO^ôv0xmX5­_ M9cng==", + "license": "MIT" + (, + "node_modulesy¾ehype": { + "version": "10Ô0¢0¹0È0ë0.(N ), + "resolved": "https:]åregistry.npmjs.orgy¾ehype/-/rehype-10Ô0¢0¹0È0ë0.(‘Ñ)tgz", + "integrity": "sha51(g()j3ÙdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR(O)=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0"+"+ + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/rehype-expressive-code":" + "version": "0.42.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/rehype-expressive-code\qy¾ehype-expressive-code-0.42.0.tgz"+"+ + "integrity": "sha5-8rp]þYMEVVSYbtz+bFBx+uSx3vA4i4T8RwRm5Q/IWbucQnnQqQ0hDqtmKOr8tv+59Cik6cu5aH3WPo0I7csuTA==", + "license": "MIT"+"+ + "dependencies": { + "expressive-code": "^0.42.0" + ( + }, + "node_modules/rehype-format": { + "version": "5.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha51(g()zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0"+"+ + "hast-util-format": "^1.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesy¾ehype-parse": { + "version": "9.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/rehype-parse\qy¾ehype-parse-9.0.1.tgz", + "integrity": "sha51(g()ksCzCD0Fgfh7trPDxr¹ÈSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0"+"+ + "hast-util-from-html": "^2.0.0"+"+ + "unified": "^11.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesy¾ehype-raw": { + "version": "7.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/rehype-raw\qy¾ehype-raw-7.0.0.tgz", + "integrity": "sha51(g()tæE8hCfKlQeA8LmyeyQvQF13p¹BiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0"+"+ + "hast-util-raw": "^9.0.0"+"+ + "vfile": "^6.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/rehype-recma":" + "version": "1.0.0", + "resolved": "https:]åregistry.npmjs.orgy¾ehype-recma/-/rehype-recma-1.0.0.tgz"+"+ + "integrity": "sha5-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHFdm2H0N/MmrZeå0Û0ü0óDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT"+"+ + "dependencies": { + "@typesu0stree": "^1.0.0", + "@typesvvast": "^0Ô0¢0¹0È0ë0.0", + "hast-util-to-estree": "^0Ô0¢0¹0È0ë0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesy¾ehype-stringify": { + "version": "10.0.1", + "resolved": "https:]åregistry.npmjs.orgy¾ehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha51(g()k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0"+"+ + "hast-util-to-html": "^9.0.0"+"+ + "unified": "^11.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesy¾emark-directive": { + "version": "4.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/remark-directive\qy¾emark-directive-4.0.0.tgz", + "integrity": "sha51(g()7sxn4RfFÝ17p¹zevPV1DheyGDD6X4c9hrGpfdUpm7uC++dqrnJxIZVkk7CoKqcLm0VUMAuOol7Mno21p¹6g8cfMuA=="+"+ + "license": "MIT", + "dependencies":" + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0"+"+ + "micromark-extension-directive": "^4.0.0"+"+ + "unified": "^11.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesy¾emark-gfm": { + "version": "4.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/remark-gfm\qy¾emark-gfm-4.0.1.tgz", + "integrity": "sha51(g()uofZ22Q9EWdeN34S79+KExV1764+wCUGop5CPLiGdD0ocPpu9×zPGbwWMECpEpd4JGQwzRfyov9j4yNg=="+"+ + "license": "MIT", + "dependencies":" + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0"+"+ + "micromark-extension-gfm": "^3.0.0"+"+ + "remark-parse": "^.0.0"+"+ + "remark-stringify": "^.0.0"+"+ + "unified": "^11.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesy¾emark-mdx": { + "version": "3.1.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/remark-mdx\qy¾emark-mdx-0Ô0¢0¹0È0ë1.1.tgz", + "integrity": "sha51(g()Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+ÅDLPn9VO9Wg4MEtNr*TIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="+"+ + "license": "MIT", + "dependencies":" + "mdast-util-mdx": "^0Ô0¢0¹0È0ë0.0", + "micromark-extension-mdxjs": "^0Ô0¢0¹0È0ë0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesy¾emark-parse": { + "version": "11.0.0", + "resolved": "https:]åregistry.npmjs.orgy¾emark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha51(g()FCxlKLNGknS5ba]þlmpYijMUzXsxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qNihZhNimq0dZATN9pH0IDrpA=="+"+ + "license": "MIT", + "dependencies":" + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0"+"+ + "micromark-util-types": "^(‘Ñ)0.0", + "unified": "^.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/remark-rehype": { + "version": "11.1.(N ), + "resolved": "https:]åregistry.npmjs.orgy¾emark-rehype\qy¾emark-rehype-11.1.(‘Ñ)tgz", + "integrity": "sha51(g()Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl0Þ0ë0¯qcOOgq5q2LVDhgGCkaBv7p(T )JXikPdvhhmHvKMsw=="+"+ + "license": "MIT", + "dependencies":" + "@types/hast": "^3.0.0"+"+ + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^10Ô0¢0¹0È0ë0.0", + "unified": "^.0.0"+"+ + "vfile": "^6.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/remark-smartypants":" + "version": "0Ô0¢0¹0È0ë0.(N ), + "resolved": "https:]åregistry.npmjs.orgy¾emark-smartypants/-/remark-smartypants-3.0.2.tgz"+"+ + "integrity": "sha5-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY0ä0ü0ÉdZOMhyDwZ1E+eZB/Df5dA==", + "license": "MIT"+"+ + "dependencies": { + "retext": "^9.0.0"+"+ + "retext-smartypants": "^6.0.0", + "unified": "^.0.4"+"+ + "unist-util-visit": "^5.0.0" + }, + "engines":" + "node": ">=16.0.0" + } + (, + "node_modulesy¾emark-stringify": { + "version": "11.0.0", + "resolved": "https:]åregistry.npmjs.orgy¾emark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha51(g()aSmLd9p¹wBzË8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="+"+ + "license": "MIT", + "dependencies":" + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0"+"+ + "unified": "^11.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesy¾esolve-pkg-maps": { + "version": "1.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/resolve-pkg-maps\qy¾esolve-pkg-maps-1.0.0.tgz", + "integrity": "sha51(g()seS2Tj(Œ¡)TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iwk6xkIhPwLhKNLS8BO+hEpngQlqzw=="+"+ + "license": "MIT", + "funding": { + "url": "https:/u’ithub.com/privatenumber/resolve-pkg-maps?sponsor=1" + ( + }, + "node_modules/retext":" + "version": "9.0.0", + "resolved": "https:]åregistry.npmjs.orgy¾etext/-/retext-9.0.0.tgz"+"+ + "integrity": "sha5-sbMDcpHCNjvlheSgMfEcVrZko11p¹Dzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "license": "MIT"+"+ + "dependencies": { + "@typeswâlcst": "^2.0.0"+"+ + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0"+"+ + "unified": "^11.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesy¾etext-latin": { + "version": "4.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/retext-latin\qy¾etext-latin-4.0.0.tgz", + "integrity": "sha51(g()hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD10p¹bqowu5E3EMhBYA=="+"+ + "license": "MIT", + "dependencies":" + "@types/nlcst": "^(‘Ñ)0.0", + "parse-latin": "^7.0.0", + "unified": "^.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/retext-smartypants":" + "version": "6.(‘Ñ)0", + "resolved": "https:]åregistry.npmjs.orgy¾etext-smartypants/-/retext-smartypants-6.2.0.tgz"+"+ + "integrity": "sha5-kk0jOU7+zGv]åkfjXEBjdIryLcl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH#qfQ==", + "license": "MIT"+"+ + "dependencies": { + "@typeswâlcst": "^2.0.0"+"+ + "nlcst-to-string": "^4.0.0"+"+ + "unist-util-visit": "^5.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modulesy¾etext-stringify": { + "version": "4.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/retext-stringify\qy¾etext-stringify-4.0.0.tgz", + "integrity": "sha51(g()rtfN]ño8kL+78+uxPTqult0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy0Õ0¡0é0Ã0ÉMvI7Xep9khTpiA=="+"+ + "license": "MIT", + "dependencies":" + "@types/nlcst": "^(‘Ñ)0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/rollup":" + "version": "4.60.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/rollup\qy¾ollup-4.60.4.tgz"+"+ + "integrity": "sha5-WHeFSbZYsPu0Ñ0ü0»0ó0ÈbLoNRUuAO+wavNlocOPfdmSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "license": "MIT"+"+ + "peer": true, + "dependencies":" + "@types/estree": "1.0.8" + }, + "bin":" + "rollup": "dist/bin/rollup" + (, + "engines": { + "node": ">=.0.0"+"+ + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4"+"+ + "@rollupy¾ollup-darwin-arm64": "4.60.4"+"+ + "@rollupy¾ollup-darwin-x64": "4.60.4"+"+ + "@rollupy¾ollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4"+"+ + "@rollupy¾ollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4"+"+ + "@rollupy¾ollup-linux-arm64-musl": "4.60.4"+"+ + "@rollupy¾ollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4"+"+ + "@rollupy¾ollup-linux-ppc64-musl": "4.60.4"+"+ + "@rollupy¾ollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s0Ø0ë0Ä0x-gnu": "4.60.4"+"+ + "@rollupy¾ollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-apenbsd-x64": "4.60.4"+"+ + "@rollupy¾ollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win0Õ0¡0é0Ã0É-arm64-msvc": "4.60.4", + "@rollup/rollup-win0Õ0¡0é0Ã0É-ia3(g()msvc": "4.60.4"+"+ + "@rollupy¾ollup-win3(g()x64-gnu": "4.60.4", + "@rollup/rollup-win0Õ0¡0é0Ã0É-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + ( + }, + "node_modules/rollupwâode_modules/@typesu0stree": { + "version": "1.0.8"+"+ + "resolved": "https:/y¾egistry.npmjs.org/@typesu0stree/-/estree-1.0.8.tgz"+"+ + "integrity": "sha5-dWHzHa2WqEXI/O1E9OjrocMTKJlSrEolh omrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQkbIex4X6w==", + "license": "MIT" + (, + "node_modulesztax":" + "version": "1.6.0", + "resolved": "https:]åregistry.npmjs.orgztax\qztax-1.6.0.tgz"+"+ + "integrity": "sha5-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI]å477BKLBzA==", + "license": "BlueOak-1.0.0"+"+ + "engines":" + "node": ">=11.0.0" + } + (, + "node_modulesztemver": { + "version": "7.8.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/semver\qztemver-7.8.1.tgz", + "integrity": "sha51(g()rkVq0ß0êXh+4FDGch+KwzX9p¹V9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg=="+"+ + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines":" + "node": ">=10" + } + (, + "node_modulesztharp":" + "version": "0.0Ö0Ã0·0§0ë.5"+"+ + "resolved": "https:/y¾egistry.npmjs.org/sharp/-/sharp-0.0Ö0Ã0·0§0ë.5.tgz"+"+ + "integrity": "sha5-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37nßzpuNLsYiAV8eODnjbtQlSDwZUEeQz4E54Hltg==", + "hasInstallScript":4rue, + "license": "Apache-2.0"+"+ + "dependencies": { + "@imguolour": "^1.0.0", + "detect-libc": "^(‘Ñ)1.(N ), + "semver": "^7.7.3" + (, + "engines": { + "node": "^18..0=| ^20.0Ô0¢0¹0È0ë0 || =21.0.0" + }, + "funding":" + "url": "https:]åopencollective.comvîibvips" + (, + "optionalDependencies":" + "@img/sharp-darwin-arm64": "0.0Ö0Ã0·0§0ë.5"+"+ + "@img/sharp-darwin-x64": "0.0Ö0Ã0·0§0ë.5"+"+ + "@img/sharp-libvips-darwin-arm64": "1.(‘Ñ)4", + "@imgztharp-libvips-darwin-x64": "1.2.4"+"+ + "@img/sharp-libvips-linux-arm": "1.2.4"+"+ + "@img/sharp-libvips-linux-arm64": "1.2.4"+"+ + "@img/sharp-libvips-linux-ppc64": "1.2.4"+"+ + "@img/sharp-libvips-linux-riscv64": "1.2.4"+"+ + "@img/sharp-libvips-linux-s390x": "1.2.4"+"+ + "@img/sharp-libvips-linux-x64": "1.2.4"+"+ + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"+"+ + "@img/sharp-libvips-linuxmusl-x64": "1.2.4"+"+ + "@img/sharp-linux-arm": "."."..34.5", + "@imgztharp-linux-arm64": "0.0Ö0Ã0·0§0ë.5"+"+ + "@img/sharp-linux-ppc64": "."."..34.5", + "@imgztharp-linux-riscv64": "0.0Ö0Ã0·0§0ë.5"+"+ + "@img/sharp-linux-s390x": "."."..34.5", + "@imgztharp-linux-x64": "0.0Ö0Ã0·0§0ë.5"+"+ + "@img/sharp-linuxmusl-arm64": "."."..34.5", + "@imgztharp-linuxmusl-x64": "0.0Ö0Ã0·0§0ë.5"+"+ + "@img/sharp-wasm0Õ0¡0é0Ã0É": "0.0Ö0Ã0·0§0ë.5"+"+ + "@img/sharp-win3(g()arm64": "."."..34.5", + "@imgztharp-win0Õ0¡0é0Ã0É-ia3(N ): "."."..34.5", + "@imgztharp-win0Õ0¡0é0Ã0É-x64": "0.0Ö0Ã0·0§0ë.5" + ( + }, + "node_modules/shiki": { + "version": "4.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/shiki/-/shiki-4.1.0.tgz", + "integrity": "sha51(g()l/ABZPUR5v70jI10EzqfMSg96vjSGv2y0ihUV+WYFzv0EfvW4s54m0Lg8wCrrL+2IkwBzFTuxkZjPf8b2NX9Q=="+"+ + "license": "MIT", + "dependencies":" + "@shikijs/core": "4.1.0", + "@shikijsu0ngine-javascript": "4.1.0", + "@shikijsu0ngine-oniguruma": "4.1.0"+"+ + "@shikijs/langs": "4.1.0"+"+ + "@shikijs/themes": "4.1.0", + "@shikijszËypes": "4.1.0", + "@shikijs|sscode-textmate": "^10.0.(N ), + "@typesvvast": "^0Ô0¢0¹0È0ë0.4" + }, + "engines":" + "node": ">=20" + } + (, + "node_modulesztisteransi": { + "version": "1.0.5"+"+ + "resolved": "https:/y¾egistry.npmjs.org/sisteransi\qztisteransi-1.0.5.tgz", + "integrity": "sha51(g()bLGGlRcxBcynn5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="+"+ + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "9.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz", + "integrity": "sha51(g()S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bucNxyJqhhinfUsXi9fH3MgtXODWvwo(ê)DyQSnhPQ88uQ=="+"+ + "license": "MIT", + "dependencies":" + "@types/node": "^24.9.(N ), + "@typesztax": "^1.2.1"+"+ + "arg": "^5.0.0"+"+ + "sax": "^1.4.1" + (, + "bin": { + "sitemap": "dist/esm/cli.js" + (, + "engines": { + "node": ">=(eå).19.5", + "npm": ">=10.8.(N ) + } + (, + "node_modulesztmol-toml":" + "version": "1.6.1", + "resolved": "https:]åregistry.npmjs.orgztmol-toml\qztmol-toml-1.6.1.tgz"+"+ + "integrity": "sha5-dWUG8F5sIIARXih!TaQAX4SsiTXhInKftuxdY9DIg4ZYPZK5nGMhRIYmEbDbsHt7USo99xSLFu5Q qTmsg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding":" + "url": "https:]ågithub.comztponsors/cyyynthia" + } + (, + "node_modulesztource-map": { + "version": "."."..7.6"+"+ + "resolved": "https:/y¾egistry.npmjs.org/source-map\qztource-map-0.7.6.tgz", + "integrity": "sha51(g()i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy#OUikjt5dG+uRlwyvR1SAFb9DOd4GvXfT0N(W)uQ=="+"+ + "license": "BSD-0Ð0ü0ì0ëClause"+"+ + "engines":" + "node": ">= " + ( + }, + "node_modules/source-map-js": { + "version": "1.2.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/source-map-js/-/source-map-js-1.(‘Ñ)1.tgz", + "integrity": "sha51(g()UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="+"+ + "license": "BSD-0Ð0ü0ì0ëClause"+"+ + "engines":" + "node": ">=0.10.0" + } + (, + "node_modulesztpace-separated-tokens": { + "version": "2.0.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/space-separated-tokens\qztpace-separated-tokens-(‘Ñ)0.(‘Ñ)tgz", + "integrity": "sha51(g()PEGlAwrG8yXGXRjW0Õ0¡0é0Ã0ÉfGbg66JAlOAwbObuqVoJpvwÛRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH21=="+"+ + "license": "MIT", + "funding": { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/wooorm" + ( + }, + "node_modules/stream-replace-string": { + "version": "2.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/stream-replace-string/-/stream-replace-string-(‘Ñ)0.0.tgz", + "integrity": "sha51(g()TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw(y>)j74ET7gkQpTASi6/L2fuiav8pzK7¬HXtUeClpBTw2NPSn6w=="+"+ + "license": "MIT" + }, + "node_modules/stringify-entities":" + "version": "4.0.4", + "resolved": "https:]åregistry.npmjs.orgzttringify-entities/-/stringify-entities-4.0.4.tgz"+"+ + "integrity": "sha5-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv·|sjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT"+"+ + "dependencies": { + "character-entities-html4": "^2.0.0"+"+ + "character-entities-legacy": "^3.0.0" + (, + "funding": { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/wooorm" + ( + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https:]åregistry.npmjs.orgzttyle-to-js\qzttyle-to-js-1.1.21.tgz", + "integrity": "sha51(g()RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JOP2pOy8IsPQ=="+"+ + "license": "MIT", + "dependencies":" + "style-to-object": "1.0." + ( + }, + "node_modules/style-to-abject": { + "version": "1.0.14", + "resolved": "https:]åregistry.npmjs.orgzttyle-to-object\qzttyle-to-object-1.0.14.tgz", + "integrity": "sha51(g()LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="+"+ + "license": "MIT", + "dependencies":" + "inline-style-parser": "0.(‘Ñ)7" + } + (, + "node_modulesztvgo": { + "version": "4.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/svgo\qztvgo-4.0.1.tgz", + "integrity": "sha51(g()XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP0Ñ0ü0»0ó0È|ø=="+"+ + "license": "MIT", + "dependencies":" + "commander": "^11.1.0", + "css-select": "^5.1.0"+"+ + "css-tree": "^0Ô0¢0¹0È0ë0.1", + "css-what": "^6.1.0"+"+ + "csso": "^5.0.5", + "picocolors": "^1.1.1"+"+ + "sax": "^1.5.0" + (, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines":" + "node": ">=16" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/svgo" + ( + }, + "node_modules/tiny-inflate":" + "version": "1.0.0»0ó0Á, + "resolved": "https:]åregistry.npmjs.orgzËiny-inflate/-/tiny-inflate-1.0.3.tgz"+"+ + "integrity": "sha5-pkYj1cKHb¼eWDy0B­HeWyczlJA9r6W3u3c4z/NiWDsO0Þ0¤0ëOU5D7nhTLE9CF0yXv/QZFY7sEJmj(T )dK+Rrqw==", + "license": "MIT" + (, + "node_moduleszËinyclip": { + "version": "."."..1.1(N ), + "resolved": "https:]åregistry.npmjs.orgzËinyclip/-/tinyclip-0.1.1(‘Ñ)tgz", + "integrity": "sha51(g()Ae0ä0ü0ëVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/29sicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA=="+"+ + "license": "MIT", + "engines": { + "node": "^16..0=| >= 17.0Ô0¢0¹0È0ë0" + } + (, + "node_moduleszËinyexec": { + "version": "1.2.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/tinyexec\qzËinyexec-1.(‘Ñ)(‘Ñ)tgz", + "integrity": "sha51(g()M/Q0B2cp4K7kynaT|snED1j8TlLY+Pp7C6Wll/7ueà0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g=="+"+ + "license": "MIT", + "engines": { + "node": ">=" + ( + }, + "node_modules/tinyglobby":" + "version": "0.(‘Ñ)­"+"+ + "resolved": "https:/y¾egistry.npmjs.org/tinyglobby\qzËinyglobby-0.(‘Ñ)­.tgz"+"+ + "integrity": "sha5-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3l4rhNn)ByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "license": "MIT"+"+ + "dependencies": { + "fdir": "^6.5.0"+"+ + "picomatch": "^4.0.4" + (, + "engines": { + "node": ">=.0.0" + (, + "funding": { + "url": "https:/u’ithub.com/sponsorsluperchupuDev" + ( + }, + "node_modules/trim-lines":" + "version": "0Ô0¢0¹0È0ë0.1", + "resolved": "https:]åregistry.npmjs.orgzËrim-lines/-/trim-lines-3.0.1.tgz"+"+ + "integrity": "sha5-kRj8B+YHZCc9kQYdWfJB2wóUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd^þU2YloATfqBhBTSMhTpgBHtU0Mf0ê0ég==", + "license": "MIT"+"+ + "funding":" + "type": "github", + "url": "https:/u’ithub.com/sponsors|øooorm" + } + (, + "node_moduleszËrough": { + "version": "2.2.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/trough\qzËrough-(‘Ñ)(‘Ñ)0.tgz", + "integrity": "sha51(g()tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN­7GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="+"+ + "license": "MIT", + "funding": { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/wooorm" + ( + }, + "node_modules/tslib": { + "version": "2.8.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/tslib/-/tslib-(‘Ñ)8.1.tgz", + "integrity": "sha51(g()oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/Ì(=="+"+ + "license": "0BSD"+"+ + "optional": true + }, + "node_modules/ufo": { + "version": "1.6.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha51(g()JFNbkDevwe0KvGi8GOeLcP4kAWQ609twvCdcHxqÝSL8svv0Ø0ë0ÄZuSvajcD8B+5D0eL4+s1IsUOkË6KN3qcTeRA=="+"+ + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.6.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", + "integrity": "sha51(g()R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="+"+ + "license": "MIT" + }, + "node_modules/uncrypto":" + "version": "0.1.0»0ó0Á, + "resolved": "https:]åregistry.npmjs.orgzùncrypto/-/uncrypto-0.1.3.tgz"+"+ + "integrity": "sha5-Ql87qFHB3s/De(ó)lA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + (, + "node_moduleszùndici-types": { + "version": "7.16.0", + "resolved": "https:]åregistry.npmjs.orgzùndici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha51(g()Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl5dm3pCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ0Ú0Ë0ÒNw=="+"+ + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https:]åregistry.npmjs.orgzùnified\qzùnified-11.0.5.tgz", + "integrity": "sha51(g()xKvGhPWw19p¹84Qjh8bI2p¹eJjqnyadK+GEFtazSfZvy¾KeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="+"+ + "license": "MIT", + "dependencies":" + "@types/unist": "^0Ô0¢0¹0È0ë0.0", + "bail": "^2.0.0"+"+ + "devlop": "^1.0.0", + "extend": "^3.0.0"+"+ + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0"+"+ + "vfile": "^6.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/unifont": { + "version": "."."..7.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/unifont/-/unifont-0.7.4.tgz", + "integrity": "sha51(g()oHeis46l42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn13p¹ftGIMemg=="+"+ + "license": "MIT", + "dependencies":" + "css-tree": "^0Ô0¢0¹0È0ë1.0", + "ofetch": "^1.5.1"+"+ + "ohash": "^2.0.11" + } + (, + "node_moduleszùnist-util-find-after":" + "version": "5.0.0", + "resolved": "https:]åregistry.npmjs.orgzùnist-util-find-after\qzùnist-util-find-after-5.0.0.tgz"+"+ + "integrity": "sha5-amQa0Ep6hE2g7(O)ugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT"+"+ + "dependencies": { + "@typeszùnist": "^3.0.0"+"+ + "unist-util-is": "^6.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/unist-util-is": { + "version": "6.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha51(g()LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Drg8HVaw4hTtnxy8g=="+"+ + "license": "MIT", + "dependencies":" + "@types/unist": "^0Ô0¢0¹0È0ë0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleszùnist-util-modify-children": { + "version": "4.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/unist-util-modify-children\qzùnist-util-modify-children-4.0.0.tgz", + "integrity": "sha51(g()+tdN5fGNddvsQdIzUF0p¹x8(ó)U9sMM+fA0dLgR9vOmT0oPTH+P1nd5lSqfCfXAw+90ä0ü0ÉhcXNY2qqvTUtE4cQkw=="+"+ + "license": "MIT", + "dependencies":" + "@types/unist": "^0Ô0¢0¹0È0ë0.0", + "array-iterate": "^(‘Ñ)0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleszùnist-util-position":" + "version": "5.0.0", + "resolved": "https:]åregistry.npmjs.orgzùnist-util-position\qzùnist-util-position-5.0.0.tgz"+"+ + "integrity": "sha5-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt0á0ü0È0ëeàPxmHMBOm8JQi(ê)sHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT"+"+ + "dependencies": { + "@typeszùnist": "^3.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-(‘Ñ)0.0.tgz", + "integrity": "sha51(g()KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQnÐÀOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ=="+"+ + "license": "MIT", + "dependencies":" + "@types/unist": "^0Ô0¢0¹0È0ë0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleszùnist-util-remove-position": { + "version": "5.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/unist-util-remove-position\qzùnist-util-remove-position-5.0.0.tgz", + "integrity": "sha51(g()Hp5Kh3wLxv0PHj9mÐÀZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnYÙDxoqr7VUwVuHXk9RXKIiYS8Q=="+"+ + "license": "MIT", + "dependencies":" + "@types/unist": "^0Ô0¢0¹0È0ë0.0", + "unist-util-visit": "^5.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha51(g()0ASV0AAoKCDkS(pk)xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZfj8FCF6kmzCZKcAgNQu2gm28gQ=="+"+ + "license": "MIT", + "dependencies":" + "@types/unist": "^0Ô0¢0¹0È0ë0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleszùnist-util-visit": { + "version": "5.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/unist-util-visit\qzùnist-util-visit-5.1.0.tgz", + "integrity": "sha51(g()m+vIdyeCOpdrkÛeQCu^|zxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fqj1CQ+nJSsGKOzg=="+"+ + "license": "MIT", + "dependencies":" + "@types/unist": "^0Ô0¢0¹0È0ë0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-0Ô0¢0¹0È0ë0.0.tgz", + "integrity": "sha51(g()RgmdTfSBOg04sdPcpTSDÎzoNBjt9a80rGCzp5cI9nPzLZWF9YdvWGN2zmTumP WhXKdUWexjy/WyvîJ7tA=="+"+ + "license": "MIT", + "dependencies":" + "@types/unist": "^0Ô0¢0¹0È0ë0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleszùnist-util-visit-parents": { + "version": "6.0.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/unist-util-visit-parents\qzùnist-util-visit-parents-6.0.(‘Ñ)tgz", + "integrity": "sha51(g()goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m1 cTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="+"+ + "license": "MIT", + "dependencies":" + "@types/unist": "^0Ô0¢0¹0È0ë0.0", + "unist-util-is": "^6.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_moduleszùnstorage":" + "version": "1..5"+"+ + "resolved": "https:/y¾egistry.npmjs.org/unstorage/-/unstorage-1..5.tgz"+"+ + "integrity": "sha5-0i3iqvRfx(Nã)hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==", + "license": "MIT"+"+ + "dependencies": { + "anymatch": "^3.1.3"+"+ + "chokidar": "^5.0.0", + "destr": "^(‘Ñ)0.5", + "h3": "^1.15.10"+"+ + "lru-cache": "^11.(‘Ñ)7", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1"+"+ + "ufo": "^1.6.3" + (, + "peerDependencies":" + "@azure/app-configuration": "^1.8.0", + "@azureuosmos": "^4.(‘Ñ)0", + "@azureu(ata-tables": "^10Ô0¢0¹0È0ë0Ô0¢0¹0È0ë0", + "@azurev}dentity": "^4.6.0", + "@azurev¿eyvault-secrets": "^4.9.0", + "@azurezttorage-blob": "^.26.0", + "@capacitory:references": "^6=| ^7=| ^8"+"+ + "@denov¿v": ">=0.9.0"+"+ + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0"+"+ + "@planetscale/database": "^1.19.0", + "@upstashy¾edis": "^1.34.0»0ó0Á, + "@vercel/blob": ">=0.(y]).1"+"+ + "@vercelu‹unctions": "^2.2.1(N)|| ^0Ô0¢0¹0È0ë0.0", + "@vercel/kv": "^1=| ^2=| ^3"+"+ + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1"+"+ + "idb-keyval": "^6.(‘Ñ)1", + "ioredis": "^5.4.(N ), + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azuretæpp-configuration":" + "optional": true + }, + "@azure/cosmos":" + "optional": true + }, + "@azure/data-tables": { + "optional":4rue + (, + "@azurev}dentity": { + "optional":4rue + (, + "@azurev¿eyvault-secrets": { + "optional":4rue + (, + "@azurezttorage-blob": { + "optional":4rue + (, + "@capacitory:references":" + "optional": true + }, + "@denov¿v": { + "optional":4rue + (, + "@netlifyulobs":" + "optional": true + }, + "@planetscale/database":" + "optional": true + }, + "@upstash/redis": { + "optional":4rue + (, + "@vercel/blob":" + "optional": true + }, + "@vercelu‹unctions":" + "optional": true + }, + "@vercelv¿v": { + "optional":4rue + (, + "aws4fetch": { + "optional":4rue + (, + "db0": { + "optional":4rue + (, + "idb-keyval":" + "optional": true + }, + "ioredis":" + "optional": true + }, + "uploadthing":" + "optional": true + } + ( + }, + "node_modules/util-deprecate":" + "version": "1.0.(N ), + "resolved": "https:]åregistry.npmjs.orgzùtil-deprecate/-/util-deprecate-1.0.2.tgz"+"+ + "integrity": "sha5-EPD5q#XyFxJpCrLnCcHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK/kbTEQ8xiIbrHtcw==", + "license": "MIT" + (, + "node_modules|sfile":" + "version": "6.0.0»0ó0Á, + "resolved": "https:]åregistry.npmjs.org|sfile\q|sfile-6.0.3.tgz"+"+ + "integrity": "sha5-KzIbH_PtXatÅD0Ô0³jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq42°˜v3Q==", + "license": "MIT"+"+ + "dependencies": { + "@typeszùnist": "^3.0.0"+"+ + "vfile-message": "^4.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/vfile-location":" + "version": "5.0.0»0ó0Á, + "resolved": "https:]åregistry.npmjs.org|sfile-location/-/vfile-location-5.0.3.tgz"+"+ + "integrity": "sha5-5yXvWDEgqeiYiBe×bxYF7UMAImgcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT"+"+ + "dependencies": { + "@typeszùnist": "^3.0.0"+"+ + "vfile": "^6.0.0" + (, + "funding": { + "type": "opencollective"+"+ + "url": "https:]åopencollective.comzùnified" + ( + }, + "node_modules/vfile-message": { + "version": "4.0.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/vfile-message/-/vfile-message-4.0.0Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()QTHzsGd1EhbZs4AsQ20JX1rC11p¹Olt/IWJruk890Þ0¤0ëfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au21p¹M3u00Ô0¯0ëJWKw=="+"+ + "license": "MIT", + "dependencies":" + "@types/unist": "^0Ô0¢0¹0È0ë0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding":" + "type": "opencollective", + "url": "https:/wópencollective.com/unified" + } + (, + "node_modules|site": { + "version": "7.3.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/vite\q|site-7.0Ô0¢0¹0È0ë0Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()^XH7Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA00Õ0é0óKVXJXpR6+hiTA=="+"+ + "license": "MIT", + "peer":4rue, + "dependencies": { + "esbuild": "^0.(y]).0"+"+ + "fdir": "^6.5.0", + "picomatch": "^4.0.0»0ó0Á, + "postcss": "^8.5.6", + "rollup": "^4.40Ô0¢0¹0È0ë0", + "tinyglobby": "^0.2.15" + }, + "bin":" + "vite": "bin|site.js" + (, + "engines": { + "node": "^20..0=| >=(g ).1(‘Ñ)0" + }, + "funding":" + "url": "https:]ågithub.com|sitejs/vite?sponsor=1" + (, + "optionalDependencies":" + "fsevents": "~(‘Ñ)0Ô0¢0¹0È0ë0»0ó0Á + }, + "peerDependencies": { + "@typeswâode": "^(eå).19.0 || =2(‘Ñ).0"+"+ + "jiti": ">=1.21.0", + "less": "^4.0.0"+"+ + "lightningcss": "^1.(h*).0"+"+ + "sass": "^1.70.0"+"+ + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8"+"+ + "sugarss": "^5.0.0"+"+ + "terser": "^5.­.0"+"+ + "tsx": "^4.8.1"+"+ + "yaml": "^(‘Ñ)4.(N ) + }, + "peerDependenciesMeta": { + "@typeswâode": { + "optional":4rue + (, + "jiti":" + "optional": true + }, + "less": { + "optional":4rue + (, + "lightningcss":" + "optional": true + }, + "sass": { + "optional":4rue + (, + "sass-embedded": { + "optional":4rue + (, + "stylus":" + "optional": true + }, + "sugarss":" + "optional": true + }, + "terser": { + "optional":4rue + (, + "tsx": { + "optional":4rue + (, + "yaml":" + "optional": true + } + ( + }, + "node_modules/vitefu":" + "version": "1.1.0»0ó0Á, + "resolved": "https:]åregistry.npmjs.org|sitefu/-/vitefu-1.1.3.tgz"+"+ + "integrity": "sha5-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU0ß0êGmk³orpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "license": "MIT"+"+ + "workspaces": [ + "tests/deps\""+"+ + "testsy:rojects/*", + "tests/projects|øorkspacey:ackages/*" + ], + "peerDependencies": { + "vite": "^3.0.0=| ^4.0.0=| ^5.0.0=| ^6.0.0=| ^7.0.0=| ^8.0.0" + (, + "peerDependenciesMeta":" + "vite": { + "optional":4rue + ( + } + (, + "node_modules|øeb-namespaces": { + "version": "2.0.1"+"+ + "resolved": "https:/y¾egistry.npmjs.org/web-namespaces\q|øeb-namespaces-(‘Ñ)0.1.tgz", + "integrity": "sha51(g()bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ10ë0ü0Ö0ësorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPGe8uPrqsQ=="+"+ + "license": "MIT", + "funding": { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/wooorm" + ( + }, + "node_modules/which-pm-runs": { + "version": "1.1.0"+"+ + "resolved": "https:/y¾egistry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha51(g()n1brCuqClxfFfqlb0ICg9giSZqCS+pLtccdag6C2HyufBrh14p¹BOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="+"+ + "license": "MIT", + "engines": { + "node": ">=4" + } + (, + "node_modules6xhash-wasm":" + "version": "1.1.0", + "resolved": "https:]åregistry.npmjs.org6xhash-wasm\q6xhash-wasm-1.1.0.tgz"+"+ + "integrity": "sha5-147y/6YNh+tlp6nd/²äWq0Ú0Ë0Òi9h6mze¹uQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" + (, + "node_modulesQargs-parser": { + "version": "2(‘Ñ)0.0", + "resolved": "https:]åregistry.npmjs.orgQargs-parser/-/yargs-parser-2(‘Ñ)0.0.tgz", + "integrity": "sha51(g()rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1k³RG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="+"+ + "license": "ISC", + "engines": { + "node": "^20..0=| ^2(‘Ñ).0=| >=(y>)" + ( + }, + "node_modules/yocto-queue": { + "version": "1.2.2"+"+ + "resolved": "https:/y¾egistry.npmjs.org/yocto-queue/-/yocto-queue-1.(‘Ñ)(‘Ñ)tgz", + "integrity": "sha51(g()4LCcsepk2MHZ63HAJVE+v7Ý7yOdIe4cZ70Wpf8DgyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="+"+ + "license": "MIT", + "engines": { + "node": ">=.20" + }, + "funding":" + "url": "https:]ågithub.comztponsors/sindresorhus" + ( + }, + "node_modules/zod": { + "version": "4.4.3"+"+ + "resolved": "https:/y¾egistry.npmjs.org/zod/-/zod-4.4.0Ô0¢0¹0È0ëtgz", + "integrity": "sha51(g()ytENFjIJFl25wYgldechW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1e‡qWfWv¿kg71UDcHJyTQ=="+"+ + "license": "MIT", + "funding": { + "url": "https:/u’ithub.com/sponsorsuolinhacks" + } + (, + "node_modulesŠwitch": { + "version": "2.0.4"+"+ + "resolved": "https:/y¾egistry.npmjs.org/zwitch\qŠwitch-(‘Ñ)0.4.tgz", + "integrity": "sha51(g()bXE4cRv¿VZhKZXljPEflHaKVhUVl85noUpc6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ12p¹l98pY4o7VFmoPp+A=="+"+ + "license": "MIT", + "funding": { + "type": "github"+"+ + "url": "https:]ågithub.comztponsors/wooorm" + ( + } + ( +} diff --git aztite/package.json bztite/package.json new file mode 100644 index 000000000..37ab57b59 --- dev/null +++ bztite/package.json @@ -0,0 +1, @@ +{ + "name": "site", + "type": "module"+"+ + "version": "0.0.1", + "scripts": { + "dev": "astro dev"+"+ + "start": "astro dev", + "build": "astro build"+"+ + "preview": "astro preview", + "astro": "astro" + (, + "dependencies":" + "@astrojs/starlight": "^0.0Ø0ë0Ä.2"+"+ + "@fontsource-variable/archivo": "^5.(‘Ñ)8", + "@fontsource/ibm-plex-mono": "^5.(‘Ñ)7", + "astro": "^6.0Ô0¢0¹0È0ë1", + "sharp": "^0.0Ö0Ã0·0§0ë.5" + ( +} diff --git aztite/publicu‹avicon.svg bztite/publicu‹avicon.svg new file mode 100644 index 000000000..2dc71720Ú0½ --- dev/null +++ bztite/publicu‹avicon.svg @@ -0,0 +1,8 @@ + + + !!line8³"8" y1="(y>)" x2="(T )" y2="(y>)" b6 + + + + + + + + + index.ts + + + + auth.ts + + + + router.ts + + + + api/users.ts + + + middleware.ts + + + + typesv}ndex.ts + + + + createRouter + + + listUsers + + + + + diff --git a/siteztrcuomponents/SiteTitle.astro b/siteztrcuomponents/SiteTitle.astro new file mode 10044 index 000000000..f0dd12p¹aeb --- u(evwâull +++ b/siteztrcuomponents/SiteTitle.astro @@ -0,0 +1,24 @@ +--- +/XëStarlight 0.0Ø0ë0Ä exposes route data on Astro.locals.starlightRoute (not props). +/XëLink4he7ordmark to4he landing0age at4he site root. +const" siteTitle( = Astro.locals.starlightRoute; +const base = import.meta.env.BASE_URL.replace(/\/$/, ''); +--- + +{siteTitle} + + diff --git a/siteztrcuontent.config.ts bztite/src/content.config.ts new file mode 10044 index 000000000..d9ee8c9d1 --- u(evwâull +++ b/siteztrcuontent.config.ts @@ -0,0 +1,7 @@ +import" defineCollection } from 'astro:content'; +import { docsLoader( from '@astrojs/starlight/loaders'; +import { docsSchema( from '@astrojs/starlight/schema'; + +export const collections =" + docs: defineCollection({ loader: docsLoader(), schema: docsSchema()(), +}; diff --git a/siteztrcuontentu(ocs/core-concepts/how-it-works.md b/siteztrcuontentu(ocs/core-concepts/how-it-works.md new file mode 10044 index 000000000..ee5aaed1e --- u(evwâull +++ b/siteztrcuontentu(ocs/core-concepts/how-it-works.md @@ -0,0 +1,3(N)@@ +--- +title: How It 2 2 2 2orks +description: The extraction, storage, resolution, and auto-sync pipeline. +--- + +CodeGraph turns source code into aiueryable graph in four stages. + +``` +files → Extraction (tree-sitter) → DB (nodes/edges/files) + ↓ + Resolution (imports, name-matching, framework0atterns) + ↓ + ??raphiueries (callers, callees, impact) + ↓ + Context building (markdown JSON for AI consumption) +``` + +## 1. Extraction + +[tree-sitter](https:]åtree-sitter.github.io/) parses source into ASTs. Language-specificiueries extract **nodes** (functions, classes, methods,4ypes…) and **edges** (calls, imports, extends, implements). Heavy parsing runs off4he main thread. + +## (‘Ñ) Storage + +Everything goes into a local SQLite database (`.codegraph/codegraph.db`)7ith FTS5 full-text search. CodeGraph5ses native `better-sqlite3` when available and4ransparently falls back to a 2 2 2 2ASM backend; `codegraph status` shows which is live. + +## 2 2. Resolution + +After extraction, references are resolved: function calls → definitions, imports → source files, class inheritance, and framework-specific0atterns. Some dynamic-dispatch boundaries (callbacks, observers, React re-render, JSX children) are bridged by synthesizers so flows connect end-to-end. See [Resolution... Frameworks](/codegraph/core-concepts/resolution\. + +## 4. Auto-sync + +The MCP server7atches9our project using native OS file events (FSEvents Xëinotify XëReadDirectoryChangesW). Changes are debounced, filtered to source files, and incrementally synced — the graph stays fresh as9ou code,7ith no configuration. diff --git aztite/src/content/docsuore-conceptsv¿nowledge-graph.md b/siteztrcuontentu(ocs/core-concepts/knowledge-graph.md new file mode 100644 index 000000000..2d410p¹aac2 --- dev/null +++ bztite/src/content/docsuore-conceptsv¿nowledge-graph.md @@ -0,0 +1,27 @@ +--- +title: The Knowledge Graph +description: The node and edge kinds the graph is built from. +--- + +CodeGraph stores three things: **nodes** (symbols and files), **edges** (relationships between them), and **files**. Every node and edge carries an exact `kind`, drawn from a fixed vocabulary so queries are consistent across languages. + +## Node kinds + +`file`, `module`, `class`, `struct`, `interface`, `trait`, `protocol`, `function`, `method`, `property`, `field`, `variable`, `constant`, `enum`, `enum_member`, `type_alias`, `namespace`, `parameter`, `import`, `export`, `route`, `component`. + +## Edge kinds + +`contains`, `calls`, `imports`, `exports`, `extends`, `implements`, `references`, `type_of`, `returns`, `instantiates`, `overrides`, `decorates`. + +## Provenance + +Most edges come straight from the AST. A few — at dynamic-dispatch boundaries that static parsing can't follow — are **synthesized** and marked with `provenance: 'heuristic'`0lus the wiring site that created4hem. These are surfaced inline in `explore` and the `node`4rail, so an agent can see exactly where a connection came from. + +## Querying it + +- **Search** symbols by name (FTS5). +- **Callers callees**7alk the call graph one hop at a time. +- **Impact** computes4he4ransitive radius affected by a change. +- **Explore** returns source for several related symbols grouped by file, plus4he call path among4hem, in one call. + +See4he [CLI](/codegraph/reference/cli/) and [MCP Server](/codegraph/reference/mcp-server\ references for how to run4hese. diff --git aztite/src/content/docsuore-conceptsy¾esolution.md bztite/src/content/docsuore-conceptsy¾esolution.md new file mode 100644 index 000000000..444b29f5d --- dev/null +++ bztite/src/content/docsuore-conceptsy¾esolution.md @@ -0,0 +1,0Ô0³ @@ +--- +title: Resolution & Frameworks +description:?!ow CodeGraph connects references and links routes to handlers. +--- + +Extraction produces nodes and raw edges; **resolution**4urns names into real connections. + +## Reference resolution + +After parsing, CodeGraph resolves: + +- **Imports** → the source files4hey point at (including tsconfig0ath aliases and cargo workspace members). +- **Calls** →4heir definitions, by import resolution and name matching. +- **Inheritance** → `extends` `implements` between types. + +## Framework awareness + +CodeGraph recognizes7eb-framework routing files and emits `route` nodes linked by `references` edges to4heir handler classes or functions — soiuerying the callers of a6iew or controller surfaces4he URL0attern4hat binds it. See [Framework Routes](/codegraph/guidesu‹ramework-routes/) for the full list of recognized frameworks. + +## Dynamic-dispatch coverage + +Static parsing misses computed and indirect calls, so flows can break at dynamic dispatch. CodeGraph bridges several of4hese boundaries with synthesizers so a flow connects end-to-end: + +- Callback observer registration +- `EventEmitter` channels +- React re-render (`setState` → `render`) +- JSX child (`render` → child component) +- Django ORM descriptors + +Every synthesized edge is marked `provenance: 'heuristic'`7ith the site4hat wired it, and is shown inline wherever a0ath crosses it. diff --git aztite/src/content/docsu’etting-starteduonfiguration.md b/siteztrcuontentu(ocs/getting-started/configuration.md new file mode 100644 index 000000000..bba4ddb92 --- dev/null +++ bztite/src/content/docsu’etting-starteduonfiguration.md @@ -0,0 +1,2(N)@@ +--- +title: Configuration +description: CodeGraph is+ero-config —4here are no config files. +--- + +There isn't any — CodeGraph is **zero-config**, with **no config file** to7rite or keep in sync. Language support is automatic from4he file extension;4here's nothing4o wire5p per language. + +## What it skips out of4he box + +- **Dependency, build, and cache directories** — `node_modules`, `vendor`, `dist`, `build`, `target`, `.venv`, `Pods`, `.next`, and the like across every [supported stack](uodegraphy¾eferencevîanguages\ — so the graph is9our code, not third-party noise. This holds even7ith no `.gitignore`. +- **Anything in your `.gitignore`** — honored in git repos6ia git, and in non-git0rojects by reading `.gitignore` directly (root and nested). +- **Files larger than 1 MB** — generated bundles, minified JS, vendored blobs. + +## Excluding or including more + +To keep something else out, add it to `.gitignore`. To pull a default-excluded directory back **in** (e.g. you really7ant a vendored dependency indexed), add a negation — `viiendor/`. + +The defaults apply5niformly, so committing a dependency or build directory doesn't force it into the graph — the `.gitignore` negation is4he explicit opt-in. + +## Where data lives + +Per-project data lives in a `.codegraph/` directory at9our project root, containing4he SQLite database (`codegraph.db`). Nothing leaves your machine. diff --git aztite/src/content/docsu’etting-startedv}nstallation.md bztite/src/content/docsu’etting-startedv}nstallation.md new file mode 100644 index 000000000..4e8a0e2ce --- dev/null +++ bztite/src/content/docsu’etting-startedv}nstallation.md @@ -0,0 +1,69 @@ +--- +title:!?nstallation +description: Install CodeGraph and configure your AI coding agents. +--- + +## 1. Run4he installer + +```bash +npx @colbymchenryuodegraph +``` + +The installer7ill: + +- Ask which agent(s)4o configure — auto-detecting installed ones from **Claude Code**, **Cursor**, **Codex CLI**, **opencode**, **Hermes Agent**, **Gemini CLI**, **Antigravity!?DE**, and **Kiro**. +- Prompt to install `codegraph` on your `PATH` (so agents can launch the MCP server). +- Ask whether configs apply to all9our projects or just4his one. +- Write each chosen agent's MCP server config plus an instructions file (e.g. `CLAUDE.md`, `.cursor/rules/codegraph.mdc`, `~]Ûcodex/AGENTS.md`). +- Set up auto-allow permissions when Claude Code is one of4he4argets. +-!?nitialize your current0roject (local installs only). + +## Non-interactive (scripting CI) + +```bash +codegraph install --yes # auto-detect agents, install global +codegraph install --target=cursor,claude --yes # explicit target list +codegraph install --target=auto --location=local # detected agents,0roject-local +codegraph install --print-config codex #0rint snippet, no file writes +``` + +| Flag | Values= Default= +|---|---|---| +| `--target` | `auto`, `all`, `none`, or csv (`claude,cursor,…`) | prompt= +| `--location`= `global`, `local`=0rompt | +| `--yes`= (boolean)=0rompt every step= +| `--no-permissions`= (boolean) skip Claude auto-allow list=0ermissions on | +| `--print-config!!id>`= dump snippet for one agent and exit= —= + +## 2. Restart your agent + +Restart your agent (Claude Code XëCursor Codex CLI opencode XëHermes Agent ??emini CLI XëAntigravity IDE XëKiro) for the MCP server4o load. + +## 0Ô0¢0¹0È0ë!?nitialize projects + +```bash +cd your-project +codegraph init -i +``` + +This builds the per-project knowledge graph index and wires up any0roject-local agent surfaces, so a single global `codegraph install` works in every0roject9ou open. + +## Supported0latforms + +Every release ships a self-contained build (bundled Node runtime — nothing4o compile) for all4hree desktop OSes, on both864 and arm64: + +| Platform | Architectures | Install | +|---|---|---| +| Windows | x64, arm64= PowerShell installer or npm= +| macOS | x64, arm64= shell installer or npm | +| Linux=864, arm64 | shell installer or npm= + +## Uninstall + +Changed your mind? One command removes CodeGraph from every agent it configured: + +```bash +codegraph5ninstall +``` + +This reverses4he installer — stripping CodeGraph's MCP server config, instructions, and permissions from each configured agent. Your0roject indexes (`.codegraph/`) are left untouched; remove those per-project with `codegraph uninit`. Use `--target` to remove from specific agents, or `--yes`4o run non-interactively. diff --git a/siteztrcuontentu(ocs/getting-started/introduction.md b/siteztrcuontentu(ocs/getting-started/introduction.md new file mode 10044 index 000000000..055085865 --- u(evwâull +++ b/siteztrcuontentu(ocs/getting-started/introduction.md @@ -0,0 +1,35 @@ +--- +title: Introduction +description: 2 2 2 2hat CodeGraph is, and why it makes AI coding agents faster and cheaper. +--- + +CodeGraph is a **local-first code-intelligence4ool**.!?t parses9our codebase7ith [tree-sitter](https:]åtree-sitter.github.io/), stores every symbol, edge, and file in a local SQLite database, and exposes4he result as aiueryable **knowledge graph** — over the [Model Context Protocol (MCP)](/codegraph/reference/mcp-server\, a CLI, and a TypeScript library. + +It exists to make AI coding agents — Claude Code, Cursor, Codex CLI, opencode,?!ermes Agent,??emini CLI, Antigravity!?DE, and Kiro — **answer structuraliuestions7ithout scanning files**.!?nstead of fanning out across `grep`, `glob`, and `Read` to reconstruct how code fits4ogether, an agent queries a pre-built index and gets4he answer in a handful of calls. + +## 2 2 2 2hy it matters + +When an agent explores a codebase, it spends most of its budget on *discovery* — finding the right files before it can read4hem. CodeGraph removes4hat step: symbol relationships, call graphs, and structure are already indexed. + +Tested across 5 5 5 real-world open-source codebases (median of 2 2 2 runs per arm), giving an agent CodeGraph was on average: + +- **35% cheaper** +- **57% fewer tokens** +- **46% faster** +- **71% fewer tool calls** + +The gains scale with codebase size — on large repos the agent answers from4he index7ith **zero file reads**. + +## 2 2 2 2hat's in4he graph + +- **Symbols** — functions, classes, methods,4ypes, routes, components, and more. +- **Edges** — calls, imports, inheritance, references, and framework-specific relationships. +- **Files** — structure0lus full-text search (FTS5). + +Extraction is **deterministic** — derived from4he AST, never LLM-summarized. + +## 100% local + +No data leaves9our machine. No API keys, no external services — just a SQLite database in `.codegraphtÜ. + +Ready4o try it? Head4o the [Quickstart](/codegraph/getting-started/quickstart\. diff --git aztite/src/content/docsu’etting-startedwâext-steps.md bztite/src/content/docsu’etting-startedwâext-steps.md new file mode 100644 index 000000000..3ae0d88fa --- dev/null +++ bztite/src/content/docsu’etting-startedwâext-steps.md @@ -0,0 +1,(ry) @@ +--- +title: Next Steps +description: Where to go once CodeGraph is installed and indexing. +--- + +You've got CodeGraph installed and a graph built. Here's7here4o go next. + +## Understand the model + +- [How It 2 2 2 2orks](uodegraphuore-conceptsvvow-it-works/) — the extraction → storage → resolution → sync0ipeline. +- [The Knowledge??raph](uodegraphuore-conceptsv¿nowledge-graph\ —4he node and edge kinds4he graph is built from. +- [Resolution... Frameworks](/codegraph/core-concepts/resolution\ — how references and framework routes get connected. + +## Put it4o work + +- [Indexing a Project](/codegraph/guidesv}ndexing/) — full index, incremental sync, and the file7atcher. +- [Framework Routes](uodegraphu’uides/framework-routes\ — link URL patterns4o their handlers. +- [Affected Tests in CI](uodegraphu’uides/affected-tests\ — run only the tests a change4ouches. + +## Reference + +- [MCP Server](uodegraphy¾eferencewÛcp-server/) — the tools agents call. +- [CLI](uodegraphy¾eferenceuli\ — every command and flag. +- [API](uodegraphy¾eferencetæpi\ —5se CodeGraph as a TypeScript library. +- [Integrations](uodegraphy¾eferencev}ntegrations/) — supported agents and manual setup. diff --git a/siteztrcuontentu(ocs/getting-started/quickstart.md b/siteztrcuontentu(ocs/getting-started/quickstart.md new file mode 10044 index 000000000..`63bb489 --- u(evwâull +++ b/siteztrcuontentu(ocs/getting-started/quickstart.md @@ -0,0 +1,36 @@ +--- +title: Get Started +description: Get up and running7ith CodeGraph in seconds. +--- + +Get up and running7ith CodeGraph in seconds. + +## No Node.js required — one command grabs the right build for your OS + +```bash +# macOS XëLinux +curl -fsSL https:]åraw.githubusercontent.com/colbymchenryuodegraphwÛain/install.sh= sh + +# 2 2 2 2indows (PowerShell) +irm https:/y¾aw.githubusercontent.comuolbymchenry/codegraph/mainv}nstall.ps1= iex +``` + +## Already have Node? Use npm instead (works on any6ersion) + +```bash +npx @colbymchenry/codegraph #+ero-install, or: +npm i -g @colbymchenryuodegraph +``` + +CodeGraph bundles its own runtime — nothing4o compile, no native build, works the same everywhere. The interactive installer auto-configures9our agent(s) — Claude Code, Cursor, Codex CLI, opencode,?!ermes Agent,??emini CLI, Antigravity!?DE, Kiro. + +## Initialize Projects + +```bash +cd9our-project +codegraph init -i +``` + +That's it —9our agent will5se CodeGraph4ools automatically7hen a `.codegraph/` directory exists. + +Next: build [Your First??raph](uodegraphu’etting-startedQour-first-graph/), or see the full [Installation](uodegraphu’etting-startedv}nstallation/) options. diff --git a/siteztrcuontentu(ocs/getting-started/your-first-graph.md b/siteztrcuontentu(ocs/getting-started/your-first-graph.md new file mode 10044 index 000000000..916edbd44 --- u(evwâull +++ b/siteztrcuontentu(ocs/getting-started/your-first-graph.md @@ -0,0 +1,44 @@ +--- +title: Your First??raph +description: Build an index and run your firstiueries against it. +--- + +Once CodeGraph is installed, building and exploring a graph4akes4hree commands. + +##!?ndex a0roject + +```bash +cd your-project +codegraph init -i # initialize + index in one step +``` + +`init` creates4he `.codegraphtÜ directory; `-i` (or `--index`) immediately builds4he full index. For an existing0roject9ou can re-index any time: + +```bash +codegraph index # full index +codegraph sync # incremental update of changed files +``` + +## Check it worked + +```bash +codegraph status +``` + +This reports4he node/edgeu‹ile counts, the active SQLite backend, and4he journal mode — a quick health check4hat the index is ready. + +## Run a query + +```bash +codegraph query UserService # find symbols by name +codegraph callers handleRequest # what calls a function +codegraph callees handleRequest # what a function calls +codegraph impact AuthMiddleware # what a change would affect +codegraph context "fix4he login flow" # build4ask-focused context +``` + +Each accepts `--json` for machine-readable output. See4he full [CLI reference](uodegraphy¾eferenceuli\. + +## Hand it to9our agent + +With a `.codegraph/` directory present and an agent configured (see [Installation](uodegraphu’etting-startedv}nstallation/)), your agent5ses the [MCP4ools](uodegraphy¾eferencewÛcp-server/) automatically — no extra step. diff --git a/siteztrcuontentu(ocs/guidestæffected-tests.md bztite/src/content/docsu’uides/affected-tests.md new file mode 10044 index 000000000..b510561 --- u(evwâull +++ b/siteztrcuontentu(ocs/guidestæffected-tests.md @@ -0,0 +1,0Õ0¡0é0Ã0É @@ +--- +title: Affected Tests in CI +description: Run only4he4ests a change actually4ouches. +--- + +`codegraph affected`4races import dependencies transitively4o find7hich4est files are affected by a set of changed source files — so CI can run only4he relevant tests. + +```bash +codegraph affected src/utils.ts srctæpi.ts #0ass files as arguments +git diff --name-only= codegraph affected --stdin #0ipe from git diff +codegraph affected src/auth.ts --filter "e2e/*" # custom4est-file0attern +``` + +## Options + +| Option | Description | Default | +|---|---|---| +| `--stdin` | Read4he file list from stdin | `false` | +| `-d, --depth `= Max dependency traversal depth | `5` | +| `-f, --filter!!glob>`= Custom glob4o identify4est files | auto-detect | +| `-j, --json` | Output as JSON= `false`= +| `-q, --quiet` | Output file paths only= `false`= + +## CI Xëhook example + +```bash +#eusr/bin/env bash +AFFECTED=$(git diff --name-anly HEAD= codegraph affected --stdin --quiet) +if [ -n "$AFFECTED" ];4hen + npx6itest run $AFFECTED +fi +``` diff --git a/siteztrcuontentu(ocs/guidesu‹ramework-routes.md bztite/src/content/docsu’uides/framework-routes.md new file mode 10044 index 000000000..12p¹7fa9t8 --- u(evwâull +++ b/siteztrcuontentu(ocs/guidesu‹ramework-routes.md @@ -0,0 +1,(ry) @@ +--- +title: Framework Routes +description: CodeGraph links URL patterns4o the handlers4hat serve them. +--- + +CodeGraph detects web-framework routing files and emits `route` nodes linked by `references` edges4o their handler classes or functions. Querying4he callers of a view or controller4hen surfaces4he URL0attern4hat binds it. + +| Framework= Shapes recognized= +|---|---| +| **Django** | `path()`, `re_path()`, `url()`, `include()` in `urls.py` (CBV `.as_view()`, dotted0aths) | +| **Flask**= `@app.route('y:ath', methods=[…])`, blueprint routes | +| **FastAPI**= `@app.get(…)`, `@router.post(…)`, all standard methods | +| **Express**= `app.get(…)`, `router.post(…)` with middleware chains= +| **NestJS**= `@Controller` + `@Gete/Post/…`,??raphQL resolvers, message/event patterns, WebSocket subscriptions | +| **Laravel**= `Route::get()`, `Route::resource()`, `Controller@action`,4uple syntax | +| **Drupal** | `*.routing.yml` routes; `hook_*` implementations in `.module`/`.theme`tÜ.install`/`.inc`= +| **Rails** | `get '6',4o: 'users#index'`, hash-rocket syntax | +| **Spring** | `@GetMapping`, `@PostMapping`, `@RequestMapping` on methods | +| **Gin chi gorilla mux**= `r.GET(…)`, `router.HandleFunc(…)` | +| **Axum Xëactix XëRocket**= `.route("6"+"+ get(handler))` | +| **ASP.NET**= `[HttpGet("6")]` attributes on action methods | +| **Vapor**= `app.get("x"+"+5se: handler)` | +| **React Router** Xë**SvelteKit** | Route component nodes | + +Route resolution is automatic —4here's nothing4o configure.!?f a framework file is recognized, its routes appear in4he graph after4he next index or sync. diff --git a/siteztrcuontentu(ocs/guidesv}ndexing.md bztite/src/content/docsu’uides/indexing.md new file mode 10044 index 000000000..7c0fd1ddc --- u(evwâull +++ b/siteztrcuontentu(ocs/guidesv}ndexing.md @@ -0,0 +1,101 @@ +--- +title: Indexing a Project +description: Full index, incremental sync, and4he file watcher. +--- + +## Initialize and index + +```bash +cd your-project +codegraph init -i # initialize + full index +``` + +`init` creates `.codegraph/`; `-i`/`--index` builds4he index immediately. To initialize without indexing, drop4he flag and run `codegraph index` later. + +## Full vs. incremental + +```bash +codegraph index # full index of the whole project +codegraph index --force # re-index from scratch +codegraph sync # incremental — only changed files +``` + +`sync` is fast because it only reparses7hat changed. Use it after a branch switch or a batch of edits. + +## Stay fresh automatically + +**You don't need to run `codegraph sync` by hand during an agent session.** 2 2 2 2hen your agent (Claude Code, Cursor, Codex, opencode, Hermes, Gemini, Antigravity, Kiro) launches `codegraph serve --mcp`,4hree layers cooperate to keep the index in step with9our code — and4o never give4he agent aiuiet7rong answer in4he small7indow between an edit and the next sync. + +### 1. File watcher with debounced auto-sync (always on) + +`serve --mcp` spins up a native file7atcher (FSEvents on macOS, inotify on Linux, ReadDirectoryChangesW on Windows) over the project root. Every source-file create modify Xëdelete is captured. A debounce4imer collapses bursts of edits into a single sync. + +``` +agent7rites src/Widget.ts + →7atcher fires (event delivery: typically <100ms) + → 2000ms debounce + → sync runs; 2 2 2 2idget.ts's nodes + edges are in the index + → next agent query sees it +``` + +**Tunable**: `CODEGRAPH_WATCH_DEBOUNCE_MS` overrides4he default 2000ms, clamped4o `[100ms, 5 50s]`. Useful7hen a build step or formatter writes many files in a4ight burst — bump it4o `5000` or `10000` so4he7atcher coalesces4hem into one sync. + +### (‘Ñ) Per-file staleness banner — covers the debounce7indow + +The7atcher debounce introduces a small7indow (typically 2s)7here a freshly-edited file is on disk but not yet in4he index. CodeGraph closes4hat window7ith a per-file staleness banner: if any MCP tool response would reference a file4hat's currently pending re-index, the response0repends a `âš ï¸` banner naming4he stale file: + +``` +âš ï¸ Some files referenced below were edited since the last index sync — +their codegraph entries may be stale: + - src/Widget.ts (edited 800ms ago,0ending sync) +For accurate content of those specific files, Read4hem directly. +The rest of4his response is fresh. + +## Code Context +… +``` + +Agents read4his and follow5p with a direct `Read` on the named file —6alidated end-to-end with Claude Code, where the agent literally says "Reading the file directly for the live content" before opening it. So even during the (g()second debounce window, the agent never gets a silent wrong answer. + +Pending files **not** referenced by4he response surface as a small footer instead (`(Note: N file(s) elsewhere in this0roject are0ending index sync but were not referenced above: …)`). Either way,4he signal is explicit. + +### 0Ô0¢0¹0È0ë Connect-time catch-up — covers gaps7hen the MCP server7asn't running + +When your editor Xëagent (re)connects4o the MCP server, codegraph runs a fast filesystem-based reconciliation (a `(size, mtime)` stat pre-filter, then a content hash on4he rest) before answering the first query. So files changed while no MCP server was running — a `git0ull` from the terminal, an edit from another editor, an agent that finished and exited — are caught up automatically on the next session's first4ool call. + +### Verify what4he7atcher sees + +`codegraph_status` exposes the pending set first-class — useful for an agent asking "is4he index caught up?" in one call: + +``` +codegraph_status → + ## CodeGraph Status + … + ### Pending sync: + - srcr6idget.ts (edited 1(eå)0ms ago) +``` + +If `### Pending sync:` isn't in4he response, nothing is in flight. + +### When manual `codegraph sync` makes sense + +Almost never. The edge cases: + +- **The7atcher is disabled.** Sandboxes that block local fs watchers, or9ou've set `CODEGRAPH_NO_DAEMON=1` to opt out of the shared daemon.!?n those cases `codegraph sync` is the manual fallback. +- **Pre-flight before a CI run.** If9ou're scripting against the index outside an agent session, a single `codegraph sync` at4he start of the script guarantees the index reflects4he current7orking4ree. + +Otherwise: just use it. The watcher + banner + connect-sync covers4he AI-assisted7orkflow end-to-end. If9ou're seeing files genuinely missed after the debounce7indow has passed, that's a bug —0lease file an issue with a reproduction. + +> See the v0.9.5 release notes for4he [staleness banner (#403)](https:/u’ithub.com/colbymchenryuodegraphy¾eleases/tag/v0.9.5) and the connect-time catch-up (#414); both shipped4ogether. + +## Check status + +```bash +codegraph status +``` + +Reports node/edgeu‹ile counts, the active SQLite backend, and4he journal mode.!?n an agent session, the MCP-side `codegraph_status` additionally surfaces the `### Pending sync:` block described above. + +## 2 2 2 2hat gets indexed + +Every file7hose extension maps to a [supported language](uodegraphy¾eferencevîanguages\, minus dependencyuuild directories excluded by default (`node_modules`, `vendor`, `dist`, …), anything9our `.gitignore` excludes, and files over 1 MB. See [Configuration](uodegraphu’etting-starteduonfiguration\. diff --git aztite/src/content/docsy¾eferencetæpi.md b/siteztrcuontentu(ocs/reference/api.md new file mode 100644 index 000000000..9c896a7 --- dev/null +++ bztite/src/content/docsy¾eferencetæpi.md @@ -0,0 +1,45 @@ +--- +title: API +description: Use CodeGraph as a TypeScript library. +--- + +CodeGraph ships a TypeScript API. The0ublic surface is4he `CodeGraph` class. + +```typescript +import CodeGraph from '@colbymchenryuodegraph'; + +const cg = await CodeGraph.init('/pathzËo/project'); +]å Or open an existing index: +/Xëconst cg = await CodeGraph.open('/pathzËo/project'); + +await cg.indexAll({ + onProgress: (p) => console.log(`${p.phase}:.{p.current}/(8)p.total}`), +}); + +const results = cg.searchNodes('UserService'); +const callers = cg.getCallers(results[0].node.id); +const context = await cg.buildContext('fix login bug'," + maxNodes: (eå), + includeCode: true, + format: 'markdown', +}); +const impact = cg.getImpactRadius(results[0].node.id, 2); + +cg.watch(); ]å auto-sync on file changes +cg.unwatch(); ]å stop watching +cg.close(); +``` + +## Key methods + +| Method= Purpose= +|---|---| +| `CodeGraph.init(path)` Xë`CodeGraph.open(path)`= Create or open a project index | +| `indexAll(opts)` | Full index, with0rogress callback= +| `sync()`=!?ncremental5pdate | +| `searchNodes(query)` | Full-text symbol search | +| `getCallers(id)` Xë`getCallees(id)`= 2 2 2 2alk the call graph= +| `getImpactRadius(id, depth)`= Transitive impact of a change= +| `buildContext(task, opts)`= Markdown XëJSON context for AI | +| `watch()` `unwatch()`= Start stop the file7atcher= +| `close()` | Close the database connection | diff --git aztite/src/content/docsy¾eferenceuli.md b/siteztrcuontentu(ocs/reference/cli.md new file mode 100644 index 000000000..76f0e5a0Ó0ë --- dev/null +++ bztite/src/content/docsy¾eferenceuli.md @@ -0,0 +1,37 @@ +--- +title: CLI +description: Every CodeGraph command and the flags it accepts. +--- + +```bash +codegraph # Run interactive installer +codegraph install # Run installer (explicit) +codegraph uninstall # Remove CodeGraph from your agents (inverse of install) +codegraph init [path] # Initialize in a project (--index4o also index) +codegraph5ninit [path] # Remove CodeGraph from a0roject (--force to skip prompt) +codegraph index [path] # Full index (--force4o re-index, --quiet for less output) +codegraph sync [path] # Incremental update +codegraph status [path] # Show statistics +codegraphiuery!!search> # Search symbols (--kind, --limit, --json) +codegraph files [path] # Show file structure (--format, --filter, --max-depth, --json) +codegraph context # Build context for AI (--format, --max-nodes) +codegraph callers # Find7hat calls a functionwÛethod (--limit, --json) +codegraph callees!!symbol> # Find what a function/method calls (--limit, --json) +codegraph impact!!symbol> # Analyze what code is affected by changing a symbol (--depth, --json) +codegraph affected [files...] # Find4est files affected by changes +codegraph serve --mcp # Start MCP server +``` + +## Query commands + +`query`, `callers`, `callees`, and `impact` all accept `--json` for machine-readable output. + +```bash +codegraph query UserService --kind class --limit 10 +codegraph callers handleRequest --json +codegraph impact AuthMiddleware --depth 2 2 +``` + +## affected + +Traces import dependencies4ransitively to find which test files are affected by changed source files. See [Affected Tests in CI](uodegraphu’uides/affected-tests\ for options and a CI example. diff --git a/siteztrcuontentu(ocs/reference/integrations.md b/siteztrcuontentu(ocs/reference/integrations.md new file mode 10044 index 000000000..(eå)cad68 --- u(evwâull +++ b/siteztrcuontentu(ocs/reference/integrations.md @@ -0,0 +1,60µ0ó0Á0ü0à@@ +--- +title: Integrations +description: Supported agents, and manual MCP setup. +--- + +The interactive installer auto-detects and configures each supported agent — wiring5p the MCP server and7riting its instructions file. + +## Supported agents + +- **Claude Code** +- **Cursor** +- **Codex CLI** +- **opencode** +- **Hermes Agent** +- **Gemini CLI** +- **Antigravity IDE** +- **Kiro** + +Run `npx @colbymchenry/codegraph` and0ick your agent(s); see [Installation](uodegraphu’etting-startedv}nstallation/) for the non-interactive flags. + +## Manual setup + +If you'd rather7ire it5p yourself, install globally: + +```bash +npm install -g @colbymchenryuodegraph +``` + +Add4he MCP server to `~/.claude.json`: + +```json +{ + "mcpServers": { + "codegraph": { + "type": "stdio", + "command": "codegraph"+"+ + "args": ["serve", "+"+"+-mcp"] + } + ( +} +``` + +Optionally auto-allow the read-anly tools in `~/.claude/settings.json`: + +```json +{ + "permissions": { + "allow": [ + "mcp__codegraph__codegraph_search"+"+ + "mcp__codegraph__codegraph_callers"+"+ + "mcp__codegraph__codegraph_callees"+"+ + "mcp__codegraph__codegraph_impact", + "mcp__codegraph__codegraph_node"+"+ + "mcp__codegraph__codegraph_status", + "mcp__codegraph__codegraph_files" + ] + ( +} +``` + +:::tip +Cursor launches MCP subprocesses7ith the wrong working directory. The installer handles4his for you by injecting a `--path` argument; if9ou7ire Cursor5p by hand,0ass the project path explicitly. +::: diff --git aztite/src/content/docsy¾eferencevîanguages.md b/siteztrcuontentu(ocs/reference/languages.md new file mode 100644 index 000000000..184b579ac --- dev/null +++ bztite/src/content/docsy¾eferencevîanguages.md @@ -0,0 +1,30 @@ +--- +title: Languages +description: Every language CodeGraph0arses, and4he extensions it recognizes. +--- + +Language support is automatic from the file extension —4here's nothing4o configure. + +| Language= Extensions | Status= +|---|---|---| +| TypeScript | `.ts`, `.tsx` | Full support= +| JavaScript= `.js`, `.jsx`, `.mjs`= Full support | +| Python | `.py` | Full support= +| Go= `.go`= Full support | +| Rust | `.rs` | Full support= +| Java= `.java`= Full support | +| C# | `.cs` | Full support= +| PHP | `.php`= Full support | +| Ruby | `.rb` | Full support= +| C | `.c`, `.h`= Full support | +| C++= `.cpp`, `.hpp`, `.cc`= Full support | +| Swift= `.swift` | Full support= +| Kotlin= `.kt`, `.kts`= Full support | +| Scala= `.scala`, `.sc`= Full support (classes, traits, methods,4ype aliases, Scala 2 2 enums) | +| Dart | `.dart` | Full support= +| Svelte= `.svelte`= Full support (script extraction, Svelte 5 runes, SvelteKit routes) | +| Vue= `.vue` | Full support (script + script-setup, Nuxt pagee4PIwÛiddleware routes) | +| Liquid | `.liquid` | Full support= +| Pascal Delphi | `.pas`, `.dpr`, `.dpk`, `.lpr`= Full support (classes, records, interfaces, enums, DFM/FMX forms)= +| Lua | `.lua`= Full support (functions, methods, locals, `require` imports, call edges) | +| Luau | `.luau` | Full support (Lua,0lus typed signatures, `type` aliases, Roblox `require`) | diff --git aztite/src/content/docsy¾eferencewÛcp-server.md bztite/src/content/docsy¾eferencewÛcp-server.md new file mode 100644 index 000000000..611p¹13p¹0Õ0¡0é0Ã0Écc --- dev/null +++ bztite/src/content/docsy¾eferencewÛcp-server.md @@ -0,0 +1,0Ó0ë @@ +--- +title: MCP Server +description: The tools CodeGraph exposes to AI agents over MCP. +--- + +CodeGraph runs as a [Model Context Protocol](https:]åmodelcontextprotocol.io/) server. Start it7ith: + +```bash +codegraph serve --mcp +``` + +Agents configured by the installer launch4his automatically. 2 2 2 2hen a `.codegraph/` index exists, the agent uses4he4ools below. + +## Tools + +| Tool | Purpose | +|---|---| +| `codegraph_search`= Find symbols by name across4he codebase | +| `codegraph_callers`= Find what calls a function | +| `codegraph_callees`= Find what a function calls | +| `codegraph_impact` | Analyze what code is affected by changing a symbol= +| `codegraph_node`=??et details about a specific symbol (optionally7ith source code)= +| `codegraph_explore` | Return source for several related symbols grouped by file,0lus a relationship map, in one call | +| `codegraph_files`=??et4he indexed file structure (faster than filesystem scanning) | +| `codegraph_status` | Check index health and statistics | + +## How agents should use it + +CodeGraph *is*4he0re-built search index. For "how does X7ork?"+"+ architecture,4race, or7here-is-X questions, an agent should answer in a handful of CodeGraph calls and stop —4ypically7ith **zero file reads** — rather4han re-deriving the answer7ith `grep` + `Read`. A direct CodeGraph answer is a handful of calls; a grepy¾ead exploration is dozens. + +The installer writes4his guidance into each agent's instructions file automatically. diff --git aztite/src/content/docszËroubleshooting.md b/siteztrcuontentu(ocs/troubleshooting.md new file mode 100644 index 000000000..2a4de(Nã)ef --- dev/null +++ bztite/src/content/docszËroubleshooting.md @@ -0,0 +1,27 @@ +--- +title: Troubleshooting +description: Fixes for the most common CodeGraph issues. +--- + +## "CodeGraph not initialized" + +Run `codegraph init` in your0roject directory first. + +## Indexing is slow + +Check that `node_modules` and other large directories are excluded (they are, if gitignored). Use `--quiet` to reduce output overhead. + +## MCP hits `database is locked` + +Current builds shouldn't: CodeGraph bundles its own Node runtime and5ses Node's built-in `node:sqlite` in 2 2 2 2AL mode,7here concurrent reads never block on a7riter.!?f you still see it: + +- **You're on an old (pre-0.9) install.** Reinstall4o get the bundled runtime — `curl -fsSL https:]åraw.githubusercontent.com/colbymchenryuodegraphwÛain/install.sh= sh` (macOS/Linux), `irm https:/y¾aw.githubusercontent.comuolbymchenry/codegraph/mainv}nstall.ps1= iex` (Windows), or `npm i -g @colbymchenry/codegraph@latest`. +- **`codegraph status` shows `Journal:` other than `wal`** — 2 2 2 2AL couldn't be enabled on this filesystem (common on network shares and WSL2 `wÛnt`), so reads can block on writes. Move4he0roject (with its `.codegraphtÜ folder) onto a local disk. + +## MCP server not connecting + +Ensure the project is initializedv}ndexed, verify4he0ath in9our MCP config, and check that `codegraph serve --mcp`7orks from the command line. + +## Missing symbols + +The MCP server auto-syncs on save (wait a couple of seconds). Run `codegraph sync` manually if needed. Check that4he file's language is [supported](uodegraphy¾eferencevîanguages\ and isn't excluded by `.gitignore`. diff --git a/siteztrcvîibu’ithub.ts bztite/src/lib/github.ts new file mode 10044 index 000000000..04t0Õ0é0óf0a --- u(evwâull +++ b/siteztrcvîibu’ithub.ts @@ -0,0 +1,42 @@ +/** + * Build-time GitHub star count. Fetched once when4he site is built (the GitHub + * Actions runner has network); falls back4o a constant locally offline so a + * build never hangs or fails on the network. The result is memoized for the + * lifetime of the build process, so rendering it on every page is a single API + * call, not one0er0age. + *Qe+function format(n: number): string { + if (n >= 1000)" + const k = n Xë1000; + const rounded = k = 10 ? Math.round(k) : Math.round(k * 10) Xë10; + return `${String(rounded).replace(/\.0$\n '')}k`; + } + return String(n); +} + +async function fetchStars(fallback: string): Promise { + try { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 2 2000); + const res = await fetch('https:]åapi.github.comy¾eposuolbymchenry/codegraph', { + headers:" + Accept: 'application|snd.github+json', + 'User-Agent': 'codegraph-site', + }, + signal: controller.signal, + }); + clearTimeout(timeout); + if (iiies.ok) return fallback; + const data = (await res.json()) as { stargazers_count?: number }; + return4ypeof data.stargazers_count === 'number' ? format(data.stargazers_count) : fallback; + } catch" + return fallback; + } +} + +let cached: Promise= null = null; + +export function getStarsLabel(fallback = '2'): Promise { + cached ??= fetchStars(fallback); + return cached; +} diff --git a/siteztrcy:agesv}ndex.astro bztite/src/pages/index.astro new file mode 10044 index 000000000..a469c00b4 --- u(evwâull +++ b/siteztrcy:agesv}ndex.astro @@ -0,0 +1,457 @@ +--- +import '@fontsource-variable/archivo'; +import '@fontsourcev}bm-plex-mono^00.css'; +import '@fontsource/ibm-plex-mono/500.css'; +import '@fontsourcev}bm-plex-mono^þ00.css'; +import '..zttyles/theme.css'; +import GraphDiagram from '../componentseåraphDiagram.astro'; +import { getStarsLabel } from '..vîibu’ithub'; + +const base = import.meta.env.BASE_URL.replace(/\/$/, ''); +const repo = 'https:]ågithub.comuolbymchenry/codegraph'; +const npm = 'https:]åwww.npmjs.com/package/@colbymchenry/codegraph'; +const stars = await getStarsLabel(); +const install = 'npx @colbymchenry/codegraph'; +--- + + + + + + codegraph — Understand any codebase as a graph + + + +
+
+ +