UNPKG

socraticode

Version:

SocratiCode — MCP server for local codebase indexing, semantic search, and code dependency graphs. All private, all local via Docker.

584 lines • 32.7 kB
import type { PathAliases } from "./graph-aliases.js"; import type { GraphInputRecorder } from "./graph-inputs.js"; /** * Whether `seg` names a JVM source-set directory in a `src/<sourceSet>/<lang>` * layout. `main` covers the classic Maven/Gradle convention; the `*Main` forms * cover Kotlin Multiplatform and Gradle source sets (`commonMain`, `androidMain`, * `iosMain`, `jvmMain`, `nativeMain`, `desktopMain`, `appleMain`, `iosArm64Main`, ...). */ export declare function isJvmSourceSetDir(seg: string): boolean; /** * Build a suffix lookup map for JVM (Java/Kotlin/Scala) files in multi-module projects. * * For a Maven/Gradle multi-module layout such as: * module-a/sub-module/src/main/java/com/example/Foo.java * the map entry is: * key: "com/example/Foo.java" (forward-slash-normalized) * value: "module-a/sub-module/src/main/java/com/example/Foo.java" * * This enables O(1) resolution of fully-qualified class names that cannot be * found via the standard prefix-based scan (e.g. src/main/java/…). * * Call this once per graph build and pass the result to resolveImport. * * Classic src/main sources take precedence over KMP source sets. The map view * retains the first file within each group; resolveImport can also select a * retained KMP candidate matching the importing source set. Pass a * lexicographically ordered set (as buildCodeGraph does) for stable picks. * Keys are normalized, but values retain the input fileSet paths. */ export declare function buildJvmSuffixMap(fileSet: Set<string>): Map<string, string>; /** * Build a namespace lookup map for C# files. * * Scans every `.cs` file in the project for `namespace X.Y.Z` declarations * (both block-scoped `namespace X { ... }` and file-scoped `namespace X;` * introduced in C# 10) and builds: * * key: "App.Services" * value: ["src/Services/OrderService.cs", "src/Services/UserService.cs"] * * Used to resolve `using App.Services;` to the candidate files that * contribute to that namespace. Without this, every C# `using` resolved * to `null` and C# projects produced an empty file-import graph. * * Files are processed in lexicographic order so the resulting candidate * lists are deterministic across machines and runs. This matters because * multi-file namespaces resolve to `candidates[0]` in `resolveImport`, * and a stable "first" file is required for reproducible graphs. * * Cost: O(n) reads at graph-build time (negligible vs. AST parsing). Files * with no `namespace` declaration are silently skipped. Read failures are * swallowed since this is best-effort. */ export declare function buildCsNamespaceMap(fileSet: Set<string>, projectPath: string, recorder?: GraphInputRecorder): Map<string, string[]>; /** * Build a PSR-4 prefix map for PHP projects from every in-repo `composer.json`. * * PHP namespaces carry no path information — `App\Models\User` only maps to * `app/Models/User.php` because `composer.json` says so. The convention-based * fallback in `resolveImport` guesses that mapping from the directory layout, * which works for a single-package project whose namespace root happens to * match a real directory name, and silently drops every import that does not: * * - `Database\Seeders\Foo` → the directory is `database/seeders`, lowercase, * so the case-sensitive guess misses it * - `Acme\Auth\Models\Role` → lives in `packages/auth/src/Models/Role.php`, * a path no namespace-shaped guess can reach * * The second case is the norm in Composer monorepos (path repositories), where * every domain package declares its own PSR-4 root. Those imports resolved to * nothing, so package-to-package edges were absent from the graph entirely and * impact analysis reported "no callers" for code with many callers. * * Reads the root manifest plus each nested one (`autoload` and `autoload-dev`), * mapping every prefix to directories relative to the manifest that declared * it. * * Call this once per graph build and pass the result to resolveImport. */ export declare function buildPhpPsr4Map(projectPath: string, recorder?: GraphInputRecorder): Map<string, string[]>; /** * Build a fully-qualified-class-name lookup map for PHP files, derived from * the declarations themselves rather than from any manifest. * * PSR-4 stays the authority wherever it exists, and this map is consulted only * after it misses. It exists for the code PSR-4 cannot describe: a package that * ships no autoload map at all and registers its namespaces at run time — * * $loader->addNamespace('Acme', PLUGIN_DIR . '/src/acme'); * * — which is how WordPress plugins overwhelmingly do it, and which a * composer.json reader cannot see. Such a package declares `"autoload": {}` or * no manifest at all, so every `use` it makes and every `use` of it resolved to * nothing, leaving whole trees orphaned in the graph while their symbols * extracted perfectly (issue #120). Interpreting `spl_autoload_register` is not * needed to fix that: the file that declares `Acme\Schema\Role` is a fact the * source states outright. * * key: "Acme\\Schema\\Role" * value: ["src/acme/schema/RoleSchema.php"] * * Scans every PHP file unconditionally rather than only those under roots no * PSR-4 prefix covers, following `buildCsNamespaceMap`. One map means one * resolution regime everywhere, and it keeps working where a composer.json * exists but is incomplete — a partial map is the common case, not an edge one. * * The key is the exact FQCN, declared namespace and declared name together, * with no tail matching, so a collision means two files literally declaring the * same class — in practice a vendored duplicate. Files are read in * lexicographic order and `resolveImport` takes `candidates[0]`, so the pick is * deterministic across machines; an edge to either copy is a true "depends on * this FQCN" edge, which is why this guesses rather than refusing, matching the * C# resolver. * * Reads `fileSet`, which is already ignore-filtered, so a `vendor/` tree the * project excluded contributes nothing and one it deliberately re-included is a * legitimate target — no unconditional skip is needed here, unlike the * manifest walk in `findComposerManifests`. * * Declarations are matched by regex at line start, as in `buildCsNamespaceMap`, * which costs one read per file instead of a parse. Positional association * carries the braced multi-namespace form (`namespace A { … } namespace B { … }`) * as well as the file-scoped one. A `class` line inside a heredoc would be * matched as a declaration; a false FQCN nobody imports resolves nothing. */ export declare function buildPhpFqcnMap(fileSet: Set<string>, projectPath: string, recorder?: GraphInputRecorder): Map<string, string[]>; /** * Information needed to resolve Go imports to local files for ONE module. * * A project may contain several Go modules (a monorepo with nested * `go.mod` files), so {@link buildGoModuleInfo} returns one of these per * `go.mod` it discovers. `modulePath` is the value of the `module` * directive in `go.mod` (e.g. `github.com/user/repo`); imports starting * with this prefix are local to that module. `moduleDir` is the * project-relative directory that contains `go.mod` ("." when it sits at * the indexed root). `packageMap` maps a Go package's directory *relative * to the module directory* (with "." for the module's own root package) * to the lex-smallest non-test `.go` file in that directory (stored * project-relative), used as a representative target for file-level edges. * * {@link buildGoModuleInfo} returns an empty array when no `go.mod` is * found or none parse. Callers must treat an empty result as "no Go * resolution available" and return null for all Go imports. */ export interface GoModuleInfo { modulePath: string; moduleDir: string; packageMap: Map<string, string>; } /** Map each in-project Elixir `defmodule` name to its files, deterministically. */ export declare function buildElixirModuleMap(fileSet: Set<string>, projectPath: string, recorder?: GraphInputRecorder): Map<string, string[]>; /** * Build Go module-resolution info for a project, one entry per `go.mod`. * * Discovers EVERY `go.mod` under the project root (so a monorepo with * nested modules is supported, not just a single root-level `go.mod`), * parses each module path with a regex, and constructs a per-module * directory-to-representative-file map across the `.go` files owned by * that module. `_test.go` files are excluded — Go does not allow them to * be imported from non-test code in other packages. Files are sorted * lexicographically before each map is built so the representative * chosen for a multi-file package is deterministic across machines/runs. * * `go.mod` is discovered by walking the tree independently of `fileSet`: * `go.mod` has no AST grammar and is not an extra extension, so it is * NEVER admitted by `getGraphableFiles` and therefore never present in * `fileSet`. An earlier attempt scanned `fileSet` for `go.mod` entries, * which matched nothing in a real build and silently broke Go resolution * for every project (issue #82, including the root-level #45 case). The * walk reuses the same ignore filter `getGraphableFiles` uses, so a * `go.mod` inside `node_modules/`, `.git/`, or a gitignored path is * skipped. * * Each `.go` file is attributed to its DEEPEST owning module (the module * whose `moduleDir` is the longest directory prefix of the file). The * tie-break is directory DEPTH, not string length: the root module * (`"."`, depth 0) must never win over a single-segment nested module * whose directory name happens to be short (e.g. `z`, which is string * length 1 just like `"."`). * * `packageMap` keys are MODULE-relative (the package directory with the * module directory stripped), because a Go import strips the module path * down to a module-relative directory. The map VALUES stay * project-relative (they are the `fileSet` entries), so resolution needs * no further translation even for a nested module. * * Cost: one tree walk (ignore-filtered) + one `readFileSync` per module * plus an O(n) walk over `.go` files at graph-build time. Lookups during * resolution are O(1). * * Limitations (deferred to follow-up issues if reported): * - Parenthesized `module ( ... )` form (rare; not used by any * mainstream Go project). * - `vendor/` directory shadowing of external imports. * - `replace` directives in `go.mod`. * - `go.work` multi-module workspaces. */ export declare function buildGoModuleInfo(fileSet: Set<string>, projectPath: string, recorder?: GraphInputRecorder): GoModuleInfo[]; /** * Map every in-repo Dart package name to its package root directory * (project-relative, forward-slash; `"."` for a root-level `pubspec.yaml`). * * Dart/Flutter code imports intra-project files by package URI almost * exclusively — `import 'package:my_app/src/service.dart';` is the layout * convention pub itself generates — and a package URI carries no path * information: `package:<name>/<rest>` maps to `<package_root>/lib/<rest>` * only because `<name>`'s pubspec lives at `<package_root>`. Without this * map every such import resolved to null (issue #106), so Flutter projects * lost nearly all file-graph edges and impact analysis reported "no callers" * for files with many consumers. Nested manifests are read too, which is what * resolves cross-package `package:<sibling>/...` imports in pub-workspace and * melos monorepos. * * The `name:` field is matched only at column 0: pubspec is YAML, so a * nested `name:` key legitimately appears indented inside dependency blocks * (`dependencies: { foo: { hosted: { name: ... } } }`), and an unanchored * match could map a dependency's name to the wrong root. Pub package names * are lowercase identifiers (`[a-z0-9_]`); the optional quote accepts the * YAML-quoted spelling of the same name. A manifest without a matching * `name:` contributes nothing; the first manifest (in sorted path order) to * declare a name wins, mirroring `buildJvmSuffixMap`'s first-wins * determinism. * * Call this once per graph build and pass the result to resolveImport. */ export declare function buildDartPackageMap(projectPath: string, recorder?: GraphInputRecorder): Map<string, string>; /** * One `pyproject.toml` found in the tree: where it sits, the import roots it * implies, and the sibling packages it declares importable. */ export interface PythonManifest { /** Project-relative directory holding the manifest; `"."` at the root. */ dir: string; /** Import roots it implies: its own directory and its `src/`. */ roots: string[]; /** * Project-relative directories of the workspace members it declares, * resolved against the manifests actually discovered. Empty for a manifest * with no `[tool.uv.workspace]` section. */ members: string[]; } /** * Discover every `pyproject.toml` in the tree and record, for each, the import * roots it implies and the workspace members it declares. * * A packaged Python project puts its importable modules under the manifest's * directory in one of two layouts: `src/` (what `uv init --lib`, hatchling * and setuptools all generate) or flat, directly beside the manifest. Neither * is derivable from the import itself. `from usa_wa_adapter_sos.house import * build` names no directory that appears in * `packages/usa-wa-adapter-sos/src/usa_wa_adapter_sos/house/build.py`: the * distribution directory is dashed, the module is underscored, and `src/` * sits between them. The resolver's existing project-root `src/`+`lib/` probe * only reaches a single-package repo, so in a workspace every cross-package * import — and every package's own absolute self-import — resolved to null * (issue #107). A 362-file uv workspace built 3 edges. * * Both roots are recorded per manifest without probing the filesystem for * which layout is in use: a root that does not exist holds no files, so it * matches nothing, and the check would cost a `stat` per manifest to remove * lookups that already miss. * * Roots are recorded rather than module names enumerated under them. Names * would have to come from the directories actually present — a distribution * name and its import name are not reliably related (Pillow imports as `PIL`), * so `[project] name` cannot supply them — and enumerating directories alone * would miss single-module distributions, where `src/mymodule.py` is the whole * importable surface and no directory bears the module's name. Trying each * root in turn covers both, and covers PEP 420 namespace packages (no * `__init__.py`) for free, since it never asks what a directory contains. * * The only part of a manifest that is read is its `[tool.uv.workspace]` * member list, which {@link pythonRootsForFile} needs to tell a sibling * package apart from an unrelated project that merely carries a manifest. * * Sorted by directory so that when two manifests contribute a root holding the * same top-level module name, the same one wins on every machine. * * Call this once per graph build and pass each file's scoped roots (see * {@link pythonRootsForFile}) to resolveImport. */ export declare function buildPythonManifests(projectPath: string, recorder?: GraphInputRecorder): PythonManifest[]; /** * The import roots that apply to one source file, nearest first. * * Two rules, each fixing a way a flat list of every root in the tree resolves * an import to the wrong file: * * **Scope.** A manifest applies to a file only when it sits on the file's * ancestor path, or when an ancestor manifest declares it as a workspace * member. A repo's `examples/demo/pyproject.toml`, a checked-in `third_party/` * sdist and an editable checkout inside an environment directory all carry * manifests while sitting on no `sys.path` the file could import through; * without this rule each one registers roots globally and turns an import that * correctly resolved to nothing into a fabricated edge. Workspace members are * the exception because that is exactly what a member declaration states: the * sibling package IS importable from here, and resolving cross-package imports * is what issue #107 is about. * * **Order.** Roots containing the file come first, deepest first, so a package * resolves its own modules before a sibling's. Without this, a per-service * monorepo of flat `uv init --app` services — each with its own `config.py` — * resolved `import config` to whichever service sorted first alphabetically, * drawing a confident edge into another service's file. Remaining in-scope * roots follow lexicographically: they are the cross-package candidates, where * nothing about the import says which package was meant, so the tie is broken * the same way on every machine rather than left to walk order. * * `relSourceDir` is the file's directory, project-relative and * forward-slashed, `"."` for a file at the root. */ export declare function pythonRootsForFile(manifests: PythonManifest[], relSourceDir: string): string[]; /** One crate found in the tree: what it is called, and where its modules start. */ export interface RustCrate { /** Project-relative directory holding its `Cargo.toml`; `"."` at the root. */ dir: string; /** * The name other crates import it by — `[package] name` with dashes turned * into underscores, which is the translation Cargo itself performs. Null for * a manifest that declares no package (a `[workspace]`-only root) or no * library target, neither of which anything can import by name. */ name: string | null; /** * `[package] name` with dashes turned into underscores, which is the name a * dependency on this crate is declared under — while `name` above is what * the code writes, and the two differ wherever `[lib] name` is set. Null for * a manifest that declares no package. */ packageName: string | null; /** * Set when the manifest could not be parsed. The crate still contributes its * convention targets, but nothing is known about what it declares, and a * check that reads "declares nothing" off an unread manifest would cost the * package every cross-crate edge — one unreadable `Cargo.toml` must not. */ manifestUnread?: true; /** Project-relative path of the library root module, when the crate has one. */ libRoot: string | null; /** * Project-relative paths of every root module the manifest implies: the * library, plus each binary, integration test, example and benchmark, plus * the build script. Each is the top of its own module tree, which is what * `crate::` is relative to. */ roots: string[]; /** * The Rust edition the manifest declares, defaulting to `"2015"` when the * key is absent, as Cargo does. It decides both target autodiscovery and * where an unanchored path in a `use` starts counting from. */ edition: string; /** * Map from local dependency name / alias (with dashes turned to underscores) * to the target crate's package name (with dashes turned to underscores). * * Covers: * [dependencies] * my_alias = { package = "actual-package-name", ... } * as well as `[workspace.dependencies]` referenced via `workspace = true`. */ aliases?: Record<string, string>; /** * The dependency names this manifest fetches from outside the project — a * registry or a git remote — rather than from a directory of its own. * * A dependency with no `path` is a different crate from a project crate that * happens to carry the same name, and cargo compiles against the fetched one. * `log = "0.4"` beside a workspace member also called `log` drew an edge into * that member: verified on cargo 1.98.0 with a `compile_error!` in the local * crate, which builds clean because rustc never reads it. */ externalDeps?: string[]; } /** * Build the crate map for a Rust project, one entry per `Cargo.toml`. * * Rust names its own code three ways, and only one of them says anything about * the filesystem. `mod foo;` names a sibling file, which the resolver already * followed. `crate::`, `super::` and `self::` name a position in the module * tree, whose root is a target's root module — `src/lib.rs`, `src/main.rs`, or * whatever `[[bin]] path` declares. `some_crate::` names a whole other crate, * which is only in this project if a manifest here declares it, under a name * whose dashes Cargo turns into underscores (`sailor-core` is imported as * `sailor_core`). None of the three resolved before this map existed: every * specifier containing `::` returned null, so a Rust project's file graph came * out as its bare `mod` declarations and nothing else. * * Targets are read from the manifest where declared and taken by convention * otherwise, matching Cargo's own autodiscovery rules per edition: * - `autobins`, `autotests`, `autoexamples`, `autobenches`, `autolib` flags * turn off autodiscovery for their respective directories when set to `false`. * - In edition 2015, autodiscovery is turned off by default for a target type * when at least one target of that type is manually declared. In edition * 2018+, autodiscovery remains enabled unless explicitly set to `false`. * - `[package] build = false` disables the build script target (`build.rs`). * - `[workspace] exclude` patterns exclude matching directories from being * registered as importable workspace members (`name = null`). * * The manifest is parsed with the same TOML reader the Python side uses rather * than scanned, so a `path` key belonging to a dependency is a different key * rather than nearby text. A manifest that cannot be parsed contributes its * convention targets and no name: one unreadable `Cargo.toml` must not cost the * whole project its Rust edges. * * Call this once per graph build and pass the result to resolveImport. */ export declare function buildRustCrateMap(fileSet: Set<string>, projectPath: string, recorder?: GraphInputRecorder): RustCrate[]; /** * Resolve one Rust module path to the project file it names. * * `relSourceFile` is the importing file, project-relative and forward-slashed. * `crates` comes from {@link buildRustCrateMap}; an empty list leaves * `crate::` and cross-crate paths unresolved, which is what a tree carrying no * `Cargo.toml` should produce. * * `super::` and `self::` are answered from the file's own position rather than * from its crate root, so they resolve in a manifest-less tree too. Climbing * stops at the crate root's directory when a root is known: a path cannot leave * its crate through `super`, and without the guard `super::super::x` at the top * of a workspace member would reach into a sibling's files. */ export declare function resolveRustImport(specifier: string, relSourceFile: string, fileSet: Set<string>, crates: RustCrate[], declaredMods?: Map<string, string>, isDeclaration?: boolean): string | null; /** * Pre-built index mapping GDScript class_name declarations to their file paths. * Normal graph construction builds one scoped index per Godot project with * buildGodotProjectIndexes() to avoid repeated file reads and cross-project * name leakage. */ export type ClassNameIndex = Map<string, string>; /** * Scan all .gd files in a fileSet for `class_name X` declarations and build * a global Map<className, relativePath>. * * @deprecated Legacy helper retained for compatibility. New graph-building * code should use buildGodotProjectIndexes() so class names remain scoped to * their nearest Godot project. */ export declare function buildClassNameIndex(projectPath: string, fileSet: Set<string>): ClassNameIndex; /** * A per-Godot-project class_name index. Maps each Godot project root * (absolute path to the directory containing project.godot) to a * ClassNameIndex scoped to the .gd files under that root. * * This supports repositories with multiple Godot projects (nested or * sibling), where each project has its own class_name registry and * res:// root. A class_name in one project must not resolve extends * in another project. */ export type GodotProjectIndexes = Map<string, ClassNameIndex>; /** * A per-Godot-project UID index. Maps `uid://...` strings to relative * file paths within the project. Built from `.uid` sidecar files and * `uid="uid://..."` attributes in `.tscn`/`.tres` file headers. * * Godot prefers UIDs over text paths for resource loading. When both * are present in an `[ext_resource]` declaration, the UID takes priority * and the text path is used only as a fallback when the UID cannot be * resolved. */ export type GodotUidIndex = Map<string, string>; export type GodotProjectUidIndexes = Map<string, GodotUidIndex>; /** Per-graph-build cache for nearest project.godot lookups. */ export type GodotRootCache = Map<string, string | null>; /** * Build per-Godot-project UID indexes from `.uid` sidecar files and * `.tscn`/`.tres` file headers. * * For each `.uid` sidecar file (e.g. `Player.gd.uid`), reads the UID * string and maps it to the corresponding resource file. For `.tscn` * and `.tres` files, parses the `uid="uid://..."` attribute from the * file header. * * @param projectPath - Absolute path to the SocratiCode indexing root * @param fileSet - Set of relative file paths * @returns Map of Godot project root (absolute) → UID index (uid:// → relative path) */ export declare function buildGodotUidIndexes(projectPath: string, fileSet: Set<string>, rootCache?: GodotRootCache, recorder?: GraphInputRecorder): GodotProjectUidIndexes; /** * Build per-Godot-project class_name indexes for a set of files. * * For each .gd file, finds the nearest project.godot ancestor and adds * the file's class_name to that project's index. Files not under any * project.godot are skipped (they have no Godot project context). * * @param projectPath - Absolute path to the SocratiCode indexing root * @param fileSet - Set of relative file paths * @returns Map of Godot project root (absolute) → ClassNameIndex (relative paths) */ export declare function buildGodotProjectIndexes(projectPath: string, fileSet: Set<string>, rootCache?: GodotRootCache, recorder?: GraphInputRecorder): GodotProjectIndexes; /** * Find the nearest Godot project root for a given source file. * * Walks up from the file's directory looking for project.godot. * Returns the absolute path to the directory containing it, or null * if no project.godot is found. This is per-file (not per-repo) to * support repositories with multiple Godot projects. * * @param sourceFile - Absolute path to the source file * @param godotProjectIndexes - Retained for compatibility with existing callers. * @param rootCache - Optional cache scoped to one graph build. */ export declare function findGodotRootForFile(sourceFile: string, _godotProjectIndexes?: GodotProjectIndexes, rootCache?: GodotRootCache): string | null; /** * Resolve a module specifier to a relative file path within the project. * Returns null if the module is external (e.g., npm package, stdlib). * * `language` is a display label as produced by `getLanguageFromExtension` * (e.g. "shell", "typescript") — that is what `buildCodeGraph` passes. "bash" * has its own case below as a synonym for "shell". The capitalised `Lang` * grammar names ("JavaScript", "TypeScript", "Tsx", "Html", "Css") match no * case. Not every display label has one either, so a switch miss is always * possible, and it returns the same null an external module does. * * @param aliases - Optional path aliases (tsconfig/jsconfig paths) for JS/TS resolution * @param classNameIndex - Pre-built GDScript class_name → file path index for O(1) extends resolution * @param godotProjectRoot - Pre-resolved Godot project root (directory containing project.godot). * When provided, res:// paths resolve relative to this instead of walking the filesystem per call. * @param godotUidIndex - Pre-built UID → relative path index for uid:// resolution. * When provided, uid:// paths are resolved via this index. When absent, * uid:// paths cannot be resolved and return null. * @param fallbackSpecifier - Optional path fallback for a primary uid:// specifier. * @param godotImportKind - GDScript construct that supplied the path. Runtime * load paths use the Godot project root; extends and preload use the source directory. */ export declare function resolveImport(moduleSpecifier: string, sourceFile: string, projectPath: string, fileSet: Set<string>, language: string, aliases?: PathAliases, jvmSuffixMap?: Map<string, string>, csNamespaceMap?: Map<string, string[]>, goModuleInfo?: GoModuleInfo[] | null, phpPsr4Map?: Map<string, string[]>, dartPackageMap?: Map<string, string>, pythonImportRoots?: string[], elixirModuleMap?: Map<string, string[]>, phpFqcnMap?: Map<string, string[]>, rustCrates?: RustCrate[], rustDeclaredMods?: Map<string, string>, rustIsDeclaration?: boolean, classNameIndex?: ClassNameIndex, godotProjectRoot?: string | null, godotUidIndex?: GodotUidIndex, fallbackSpecifier?: string, godotImportKind?: "extends" | "preload" | "load"): string | null; /** * Whether a shell `source` specifier has the shape of a literal path. This reads * the text only — a well-shaped specifier naming a file that does not exist is * still true here, and fails later at the file-set lookup. * * None of the shapes below can be told apart from a literal path here, and each * has to be screened before resolution rather than after, because normalising * them lands on a file the shell would not open — and a match there is the * failure being prevented rather than a salvage: * * - extractImports captures the whole argument list, so any whitespace-class * character means the text cannot be told apart from a word list or a path * still carrying its quotes. Bash splits on fewer of them than `\s` matches — * its default IFS is space, tab and newline — but skipping all of them errs * toward dropping an edge rather than inventing one. * - A backslash is a shell escape and never a separator, so the unescaped path * is unknowable here; `path.resolve` treats it as a separator on win32, which * would cancel `./x\..\lib.sh` down to a file the script never names. * - A trailing `/` or `/.` names a directory, which cannot be sourced, and * normalisation would drop that trailing segment, landing on the same-named * file. * - A `..` following a named segment cancels it lexically during normalisation, * so `./x/../lib.sh` lands on lib.sh beside the script. The shell walks * components instead and loads nothing when `x` is absent or is not a * directory. Only a leading run of `.`/`..` anchors, and an empty segment from * `//` does not end that run. * * These screen the raw captured text, so a change that honours quoting and * strips arguments has to unquote and split upstream of here. */ export declare function hasLiteralShellPathShape(specifier: string): boolean; /** * Find the Godot project root for a set of files by checking if project.godot * exists at the project path or any parent. Called once per graph build so * that res:// resolution doesn't walk the filesystem for every import. * Returns the absolute path to the directory containing project.godot, * or null if not found. */ export declare function findGodotProjectRootForProject(projectPath: string): string | null; /** * Parse the `[autoload]` section from a Godot `project.godot` file and build * a map of autoload name → relative script path. * * The `[autoload]` section has the form: * [autoload] * GameManager="*res://scripts/core/GameManager.gd" * InputManager="res://scripts/core/InputManager.gd" * * The leading `*` means the autoload is enabled (singleton). Both `*` and * non-`*` entries are included — the autoload name is still a global type * regardless of singleton status. * * @param godotProjectRoot - Absolute path to the directory containing project.godot * @returns Map of autoload name → relative path (forward-slash, relative to project root) */ export declare function parseGodotAutoloads(godotProjectRoot: string, recorder?: GraphInputRecorder): Map<string, string>; //# sourceMappingURL=graph-resolution.d.ts.map