Repository: biomejs/biome (language: rust)

Issue:
💅 `noUndeclaredDependencies` reports different results if current working directory changes
### Environment information

```bash
CLI:
  Version:                      1.7.0
  Color support:                true

Platform:
  CPU Architecture:             aarch64
  OS:                           macos

Environment:
  BIOME_LOG_DIR:                unset
  NO_COLOR:                     unset
  TERM:                         "xterm-256color"
  JS_RUNTIME_VERSION:           "v18.19.1"
  JS_RUNTIME_NAME:              "node"
  NODE_PACKAGE_MANAGER:         "npm/10.2.4"

Biome Configuration:
  Status:                       Loaded successfully
  Formatter disabled:           false
  Linter disabled:              false
  Organize imports disabled:    false
  VCS disabled:                 true

Linter:
  Recommended:                  true
  All:                          false
  Rules:                        a11y/noSvgWithoutTitle = "off"
                                a11y/useKeyWithClickEvents = "off"
                                a11y/useKeyWithMouseEvents = "off"
                                a11y/useValidAnchor = "off"
                                complexity/noExcessiveNestedTestSuites = "error"
                                complexity/noUselessLoneBlockStatements = "error"
                                complexity/noUselessTernary = "error"
                                correctness/noInvalidUseBeforeDeclaration = "off"
                                correctness/noNewSymbol = "error"
                                correctness/noUnusedImports = "error"
                                correctness/noUnusedPrivateClassMembers = "error"
                                correctness/noUnusedVariables = "error"
                                correctness/useExhaustiveDependencies = "off"
                                correctness/useHookAtTopLevel = "error"
                                nursery/noConstantMathMinMaxClamp = "error"
                                nursery/noEvolvingAny = "error"
                                nursery/noFlatMapIdentity = "error"
                                nursery/noMisplacedAssertion = "error"
                                nursery/noUndeclaredDependencies = "off"
                                performance/noDelete = "off"
                                style/noNegationElse = "error"
                                style/noNonNullAssertion = "off"
                                style/noRestrictedGlobals = "error"
                                style/noShoutyConstants = "error"
                                style/noUnusedTemplateLiteral = "off"
                                style/useBlockStatements = "error"
                                style/useCollapsedElseIf = "error"
                                style/useForOf = "error"
                                style/useImportType = "error"
                                style/useNodejsImportProtocol = "error"
                                style/useNumberNamespace = "error"
                                style/useShorthandAssign = "error"
                                style/useShorthandFunctionType = "error"
                                style/useSingleCaseStatement = "error"
                                suspicious/noConsoleLog = "error"
                                suspicious/noRedeclare = "off"
                                suspicious/noSkippedTests = "off"

Workspace:
  Open Documents:               0
```


### Rule name

noUndeclaredDependencies

### Playground link

not applicable

### Expected result

I don't really know what additional info to provide. I will try to create a reproducible repo, but for now this all I have.

Note that this is a monorepo. Dependencies are declared NOT in root `package.json` file.
```
/package.json
/apps
    /app-1
        /package.json      <- dependencies used in `File.tsx` are declared here
            /src
                /File.tsx
```

When running `npx biome check` I see no errors. But at the same time WebStorm and VSCode highlight imports as errors, and I see the same error during precommit hook.

`npx biome check`:
<img width="828" alt="image" src="https://github.com/biomejs/biome/assets/576152/a8097a73-408d-4f44-b87c-015d0361b357">

`git commit`:
<img width="980" alt="image" src="https://github.com/biomejs/biome/assets/576152/7c98f781-4234-487e-ac7f-a2cc5dbd64bd">


WebStorm:
![image](https://github.com/biomejs/biome/assets/576152/7946b1ea-947c-4ee6-a474-bf922c157db7)


Let me know if I need to provide more info, thank you!



### Code of Conduct

- [X] I agree to follow Biome's Code of Conduct


Discussion / hints:
Within the monorepo setting include `biome.json` at the root of your monorepo. Third-party tools cannot into multi-root setups like monorepos and cannot read multiple biome config files.
It sucks, especially if you use different configs for different projects within the monorepo, but so far it is the least problematic setup.
I have single `biome.json` and it's is in the repo root. I don't have Biome configuration issue, it just reports things differently in IDE/precommit hook and from CLI
I think this is because the root directory detection (`cwd`) has different results in CLI, LSP and git hooks. I will look into this issue.
I did some debugging but couldn't find time to implement the fix, others are welcome to tackle it. Here is what I found:

1. The problem is not only about LSP, it also happens in CLI commands, see #3202 
2. The reason is that we associate only one manifest file for each project. It will only resolve it once from the workspace root or working directory. However we can have many manifest files in a project like a monorepo in this issue. Relative code at https://github.com/biomejs/biome/blob/42bc279a7282be7bc11778204ec1bc8a822a3703/crates/biome_cli/src/commands/mod.rs#L555 and https://github.com/biomejs/biome/blob/42bc279a7282be7bc11778204ec1bc8a822a3703/crates/biome_lsp/src/session.rs#L552
3. To fix it we may need to revamp the structure of the project/workspace internal structure.
For me, it fails to detect dependencies in a pnpm monorepo workspace even from CLI. It also errors out in VsCode.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 59008fad76d3..3dc7556e0e67 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -797,8 +797,8 @@ dependencies = [
  "biome_js_parser",
  "biome_js_semantic",
  "biome_js_syntax",
+ "biome_package",
  "biome_plugin_loader",
- "biome_project",
  "biome_rowan",
  "biome_string_case",
  "biome_suppression",
@@ -1114,7 +1114,7 @@ dependencies = [
  "biome_json_formatter",
  "biome_json_parser",
  "biome_json_syntax",
- "biome_project",
+ "biome_package",
  "biome_rowan",
  "biome_test_utils",
  "camino",
@@ -1123,6 +1123,26 @@ dependencies = [
  "tests_macros",
 ]
 
+[[package]]
+name = "biome_package"
+version = "0.5.7"
+dependencies = [
+ "biome_console",
+ "biome_deserialize",
+ "biome_deserialize_macros",
+ "biome_diagnostics",
+ "biome_json_parser",
+ "biome_json_syntax",
+ "biome_parser",
+ "biome_rowan",
+ "biome_text_size",
+ "insta",
+ "node-semver",
+ "rustc-hash 2.1.0",
+ "serde",
+ "tests_macros",
+]
+
 [[package]]
 name = "biome_parser"
 version = "0.5.7"
@@ -1157,26 +1177,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "biome_project"
-version = "0.5.7"
-dependencies = [
- "biome_console",
- "biome_deserialize",
- "biome_deserialize_macros",
- "biome_diagnostics",
- "biome_json_parser",
- "biome_json_syntax",
- "biome_parser",
- "biome_rowan",
- "biome_text_size",
- "insta",
- "node-semver",
- "rustc-hash 2.1.0",
- "serde",
- "tests_macros",
-]
-
 [[package]]
 name = "biome_rowan"
 version = "0.5.7"
@@ -1232,8 +1232,8 @@ dependencies = [
  "biome_json_formatter",
  "biome_json_parser",
  "biome_json_syntax",
+ "biome_package",
  "biome_parser",
- "biome_project",
  "biome_rowan",
  "biome_string_case",
  "biome_text_edit",
@@ -1290,7 +1290,7 @@ dependencies = [
  "biome_diagnostics",
  "biome_formatter",
  "biome_json_parser",
- "biome_project",
+ "biome_package",
  "biome_rowan",
  "biome_service",
  "camino",
diff --git a/Cargo.toml b/Cargo.toml
index 3ca05029c80d..668a6c094bb1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -154,8 +154,8 @@ biome_yaml_parser            = { version = "0.0.1", path = "./crates/biome_yaml_
 biome_yaml_syntax            = { version = "0.0.1", path = "./crates/biome_yaml_syntax" }
 
 biome_markup        = { version = "0.5.7", path = "./crates/biome_markup" }
+biome_package       = { version = "0.5.7", path = "./crates/biome_package" }
 biome_parser        = { version = "0.5.7", path = "./crates/biome_parser" }
-biome_project       = { version = "0.5.7", path = "./crates/biome_project" }
 biome_rowan         = { version = "0.5.7", path = "./crates/biome_rowan" }
 biome_string_case   = { version = "0.5.7", path = "./crates/biome_string_case" }
 biome_suppression   = { version = "0.5.7", path = "./crates/biome_suppression" }
diff --git a/crates/biome_cli/src/commands/mod.rs b/crates/biome_cli/src/commands/mod.rs
index b274698543c5..71491edeaf65 100644
--- a/crates/biome_cli/src/commands/mod.rs
+++ b/crates/biome_cli/src/commands/mod.rs
@@ -635,20 +635,6 @@ pub(crate) fn validate_configuration_diagnostics(
     Ok(())
 }
 
-fn resolve_manifest(fs: &dyn FileSystem) -> Result<Option<(BiomePath, String)>, WorkspaceError> {
-    let result = fs.auto_search(
-        &fs.working_directory().unwrap_or_default(),
-        &["package.json"],
-        false,
-    )?;
-
-    if let Some(result) = result {
-        return Ok(Some((BiomePath::new(result.file_path), result.content)));
-    }
-
-    Ok(None)
-}
-
 fn get_files_to_process_with_cli_options(
     since: Option<&str>,
     changed: bool,
@@ -774,7 +760,6 @@ pub(crate) trait CommandRunner: Sized {
     /// - Configure the VCS integration
     /// - Computes the paths to traverse/handle. This changes based on the VCS arguments that were passed.
     /// - Register a project folder using the working directory.
-    /// - Resolves the closets manifest AKA `package.json` and registers it.
     /// - Updates the settings that belong to the project registered
     fn configure_workspace(
         &mut self,
@@ -807,11 +792,6 @@ pub(crate) trait CommandRunner: Sized {
             open_uninitialized: true,
         })?;
 
-        let manifest_data = resolve_manifest(fs)?;
-
-        if let Some((path, content)) = manifest_data {
-            workspace.set_manifest_for_project((project_key, path, content).into())?;
-        }
         workspace.update_settings(UpdateSettingsParams {
             project_key,
             workspace_directory: configuration_path.map(BiomePath::from),
diff --git a/crates/biome_fs/src/interner.rs b/crates/biome_fs/src/interner.rs
index 030170ace7cf..bb4dbb1f1bd9 100644
--- a/crates/biome_fs/src/interner.rs
+++ b/crates/biome_fs/src/interner.rs
@@ -3,11 +3,13 @@ use crossbeam::channel::{unbounded, Receiver, Sender};
 use papaya::HashSet;
 use rustc_hash::FxBuildHasher;
 
+pub type PathInternerSet = HashSet<Utf8PathBuf, FxBuildHasher>;
+
 /// File paths interner cache
 ///
 /// The path interner stores an instance of [PathBuf]
 pub struct PathInterner {
-    storage: HashSet<Utf8PathBuf, FxBuildHasher>,
+    storage: PathInternerSet,
     handler: Sender<Utf8PathBuf>,
 }
 
@@ -32,4 +34,8 @@ impl PathInterner {
         }
         result
     }
+
+    pub fn into_paths(self) -> PathInternerSet {
+        self.storage
+    }
 }
diff --git a/crates/biome_fs/src/lib.rs b/crates/biome_fs/src/lib.rs
index 9d14396bfc21..b1ead3c80fdf 100644
--- a/crates/biome_fs/src/lib.rs
+++ b/crates/biome_fs/src/lib.rs
@@ -9,5 +9,5 @@ pub use fs::{
     FileSystemExt, FsErrorKind, MemoryFileSystem, OpenOptions, OsFileSystem, TraversalContext,
     TraversalScope,
 };
-pub use interner::PathInterner;
+pub use interner::{PathInterner, PathInternerSet};
 pub use path::BiomePath;
diff --git a/crates/biome_js_analyze/Cargo.toml b/crates/biome_js_analyze/Cargo.toml
index 0f7c88b603ca..29b3db59022b 100644
--- a/crates/biome_js_analyze/Cargo.toml
+++ b/crates/biome_js_analyze/Cargo.toml
@@ -23,7 +23,7 @@ biome_glob               = { workspace = true, features = ["biome_deserialize",
 biome_js_factory         = { workspace = true }
 biome_js_semantic        = { workspace = true }
 biome_js_syntax          = { workspace = true }
-biome_project            = { workspace = true }
+biome_package            = { workspace = true }
 biome_rowan              = { workspace = true }
 biome_string_case        = { workspace = true }
 biome_suppression        = { workspace = true }
diff --git a/crates/biome_js_analyze/src/lib.rs b/crates/biome_js_analyze/src/lib.rs
index 0709bc656dd7..c2f6446cdb44 100644
--- a/crates/biome_js_analyze/src/lib.rs
+++ b/crates/biome_js_analyze/src/lib.rs
@@ -9,7 +9,7 @@ use biome_analyze::{
 use biome_aria::AriaRoles;
 use biome_diagnostics::Error as DiagnosticError;
 use biome_js_syntax::{JsFileSource, JsLanguage};
-use biome_project::PackageJson;
+use biome_package::PackageJson;
 use biome_rowan::TextRange;
 use biome_suppression::{parse_suppression_comment, SuppressionDiagnostic};
 use std::ops::Deref;
@@ -164,7 +164,7 @@ mod tests {
     use biome_diagnostics::{print_diagnostic_to_string, Diagnostic, DiagnosticExt, Severity};
     use biome_js_parser::{parse, JsParserOptions};
     use biome_js_syntax::{JsFileSource, TextRange, TextSize};
-    use biome_project::{Dependencies, PackageJson};
+    use biome_package::{Dependencies, PackageJson};
     use std::slice;
 
     use crate::{analyze, AnalysisFilter, ControlFlow};
diff --git a/crates/biome_js_analyze/src/services/manifest.rs b/crates/biome_js_analyze/src/services/manifest.rs
index c471d8da1064..ba39c5a78ca5 100644
--- a/crates/biome_js_analyze/src/services/manifest.rs
+++ b/crates/biome_js_analyze/src/services/manifest.rs
@@ -3,7 +3,7 @@ use biome_analyze::{
     RuleKey, ServiceBag, SyntaxVisitor,
 };
 use biome_js_syntax::{AnyJsRoot, JsLanguage, JsSyntaxNode};
-use biome_project::PackageJson;
+use biome_package::PackageJson;
 use biome_rowan::AstNode;
 use std::sync::Arc;
 
diff --git a/crates/biome_lsp/src/server.rs b/crates/biome_lsp/src/server.rs
index e3769240c4b2..af3c7748d193 100644
--- a/crates/biome_lsp/src/server.rs
+++ b/crates/biome_lsp/src/server.rs
@@ -284,7 +284,6 @@ impl LanguageServer for LSPServer {
         futures::join!(
             self.session.load_extension_settings(),
             self.session.load_workspace_settings(),
-            self.session.load_manifest()
         );
 
         let msg = format!("Server initialized with PID: {}", std::process::id());
@@ -323,14 +322,13 @@ impl LanguageServer for LSPServer {
                 Ok(file_path) => {
                     let base_path = self.session.base_path();
                     if let Some(base_path) = base_path {
-                        let possible_rome_json = file_path.strip_prefix(&base_path);
-                        if let Ok(watched_file) = possible_rome_json {
+                        let possible_biome_json = file_path.strip_prefix(&base_path);
+                        if let Ok(watched_file) = possible_biome_json {
                             if ConfigName::file_names()
                                 .contains(&&*watched_file.display().to_string())
                                 || watched_file.ends_with(".editorconfig")
                             {
                                 self.session.load_workspace_settings().await;
-                                self.session.load_manifest().await;
                                 self.setup_capabilities().await;
                                 self.session.update_all_diagnostics().await;
                                 // for now we are only interested to the configuration file,
@@ -403,10 +401,17 @@ impl LanguageServer for LSPServer {
 
                 match result {
                     Ok(project_key) => {
-                        self.session.insert_project(project_path, project_key);
+                        self.session
+                            .insert_project(project_path.clone(), project_key);
+
+                        self.session
+                            .scan_project_folder(project_key, project_path)
+                            .await;
+
+                        self.session.update_all_diagnostics().await;
                     }
                     Err(err) => {
-                        error!("Failed to add project to the workspace: {}", err);
+                        error!("Failed to add project to the workspace: {err}");
                         self.session
                             .client
                             .log_message(MessageType::ERROR, err)
@@ -622,7 +627,6 @@ impl ServerFactory {
         workspace_method!(builder, scan_project_folder);
         workspace_method!(builder, close_project);
         workspace_method!(builder, open_file);
-        workspace_method!(builder, set_manifest_for_project);
         workspace_method!(builder, get_syntax_tree);
         workspace_method!(builder, get_control_flow_graph);
         workspace_method!(builder, get_formatter_ir);
diff --git a/crates/biome_lsp/src/session.rs b/crates/biome_lsp/src/session.rs
index c691fbd1fac6..7c4bb633d235 100644
--- a/crates/biome_lsp/src/session.rs
+++ b/crates/biome_lsp/src/session.rs
@@ -16,9 +16,10 @@ use biome_service::configuration::{
 };
 use biome_service::file_handlers::{AstroFileHandler, SvelteFileHandler, VueFileHandler};
 use biome_service::projects::ProjectKey;
+use biome_service::workspace::ScanProjectFolderParams;
 use biome_service::workspace::{
     FeaturesBuilder, GetFileContentParams, OpenProjectParams, PullDiagnosticsParams,
-    SetManifestForProjectParams, SupportsFeatureParams,
+    SupportsFeatureParams,
 };
 use biome_service::workspace::{RageEntry, RageParams, RageResult, UpdateSettingsParams};
 use biome_service::Workspace;
@@ -37,6 +38,7 @@ use std::sync::Arc;
 use std::sync::RwLock;
 use tokio::sync::Notify;
 use tokio::sync::OnceCell;
+use tokio::task::spawn_blocking;
 use tower_lsp::lsp_types;
 use tower_lsp::lsp_types::{Diagnostic, Url};
 use tower_lsp::lsp_types::{MessageType, Registration};
@@ -87,7 +89,6 @@ pub(crate) struct Session {
     pub(crate) cancellation: Arc<Notify>,
 
     pub(crate) config_path: Option<Utf8PathBuf>,
-    pub(crate) manifest_path: Option<Utf8PathBuf>,
 }
 
 /// The parameters provided by the client in the "initialize" request
@@ -182,7 +183,6 @@ impl Session {
             extension_settings: config,
             cancellation,
             config_path: None,
-            manifest_path: None,
             notified_broken_configuration: AtomicBool::new(false),
         }
     }
@@ -472,7 +472,7 @@ impl Session {
     /// This function attempts to read the `biome.json` configuration file from
     /// the root URI and update the workspace settings accordingly
     #[tracing::instrument(level = "trace", skip(self))]
-    pub(crate) async fn load_workspace_settings(&self) {
+    pub(crate) async fn load_workspace_settings(self: &Arc<Self>) {
         // Providing a custom configuration path will not allow to support workspaces
         if let Some(config_path) = &self.config_path {
             let base_path = ConfigurationPathHint::FromUser(config_path.clone());
@@ -514,158 +514,133 @@ impl Session {
         }
     }
 
+    pub(crate) async fn scan_project_folder(
+        self: &Arc<Self>,
+        project_key: ProjectKey,
+        project_path: BiomePath,
+    ) {
+        let session = self.clone();
+        let _ = spawn_blocking(move || {
+            let result = session
+                .workspace
+                .scan_project_folder(ScanProjectFolderParams {
+                    project_key,
+                    path: Some(project_path),
+                });
+            if let Err(err) = result {
+                error!("Failed to scan project: {err}");
+            }
+        })
+        .await;
+    }
+
     async fn load_biome_configuration_file(
-        &self,
+        self: &Arc<Self>,
         base_path: ConfigurationPathHint,
     ) -> ConfigurationStatus {
-        match load_configuration(self.workspace.fs(), base_path.clone()) {
-            Ok(loaded_configuration) => {
-                if loaded_configuration.has_errors() {
-                    error!("Couldn't load the configuration file, reasons:");
-                    for diagnostic in loaded_configuration.as_diagnostics_iter() {
-                        let message = PrintDescription(diagnostic).to_string();
-                        self.client.log_message(MessageType::ERROR, message).await;
-                    }
-                    ConfigurationStatus::Error
-                } else {
-                    let LoadedConfiguration {
-                        configuration: fs_configuration,
-                        directory_path: configuration_path,
-                        ..
-                    } = loaded_configuration;
-                    info!("Configuration loaded successfully from disk.");
-                    info!("Update workspace settings.");
-
-                    let fs = self.workspace.fs();
-                    let should_use_editorconfig =
-                        fs_configuration.use_editorconfig().unwrap_or_default();
-                    let mut configuration = if should_use_editorconfig {
-                        let (editorconfig, editorconfig_diagnostics) = {
-                            let search_path = configuration_path
-                                .clone()
-                                .unwrap_or_else(|| fs.working_directory().unwrap_or_default());
-                            match load_editorconfig(fs, search_path) {
-                                Ok(result) => result,
-                                Err(error) => {
-                                    error!(
-                                        "Failed load the `.editorconfig` file. Reason: {}",
-                                        error
-                                    );
-                                    self.client.log_message(MessageType::ERROR, &error).await;
-                                    return ConfigurationStatus::Error;
-                                }
-                            }
-                        };
-                        for diagnostic in editorconfig_diagnostics {
-                            let message = PrintDescription(&diagnostic).to_string();
-                            self.client.log_message(MessageType::ERROR, message).await;
-                        }
-                        editorconfig.unwrap_or_default()
-                    } else {
-                        Default::default()
-                    };
-
-                    configuration.merge_with(fs_configuration);
-
-                    let result =
-                        configuration.retrieve_gitignore_matches(fs, configuration_path.as_deref());
-
-                    match result {
-                        Ok((vcs_base_path, gitignore_matches)) => {
-                            let register_result = match &base_path {
-                                ConfigurationPathHint::FromLsp(path)
-                                | ConfigurationPathHint::FromWorkspace(path) => {
-                                    self.workspace.open_project(OpenProjectParams {
-                                        path: path.as_path().into(),
-                                        open_uninitialized: true,
-                                    })
-                                }
-                                _ => self.workspace.open_project(OpenProjectParams {
-                                    path: fs
-                                        .working_directory()
-                                        .map(BiomePath::from)
-                                        .unwrap_or_default(),
-                                    open_uninitialized: true,
-                                }),
-                            };
-                            let project_key = match register_result {
-                                Ok(result) => result,
-                                Err(error) => {
-                                    error!("Failed to register the project folder: {error}");
-                                    self.client.log_message(MessageType::ERROR, &error).await;
-                                    return ConfigurationStatus::Error;
-                                }
-                            };
-
-                            let result = self.workspace.update_settings(UpdateSettingsParams {
-                                project_key,
-                                workspace_directory: configuration_path.map(BiomePath::from),
-                                configuration,
-                                vcs_base_path: vcs_base_path.map(BiomePath::from),
-                                gitignore_matches,
-                            });
-
-                            if let Err(error) = result {
-                                error!("Failed to set workspace settings: {}", error);
-                                self.client.log_message(MessageType::ERROR, &error).await;
-                                ConfigurationStatus::Error
-                            } else {
-                                ConfigurationStatus::Loaded
-                            }
-                        }
-                        Err(err) => {
-                            error!("Couldn't load the configuration file, reason:\n {}", err);
-                            self.client.log_message(MessageType::ERROR, &err).await;
-                            ConfigurationStatus::Error
-                        }
-                    }
-                }
-            }
-
+        let loaded_configuration = match load_configuration(self.workspace.fs(), base_path.clone())
+        {
+            Ok(loaded_configuration) => loaded_configuration,
             Err(err) => {
-                error!("Couldn't load the configuration file, reason:\n {}", err);
+                error!("Couldn't load the configuration file, reason:\n {err}");
                 self.client.log_message(MessageType::ERROR, &err).await;
-                ConfigurationStatus::Error
+                return ConfigurationStatus::Error;
             }
+        };
+
+        if loaded_configuration.has_errors() {
+            error!("Couldn't load the configuration file, reasons:");
+            for diagnostic in loaded_configuration.as_diagnostics_iter() {
+                let message = PrintDescription(diagnostic).to_string();
+                self.client.log_message(MessageType::ERROR, message).await;
+            }
+            return ConfigurationStatus::Error;
         }
-    }
 
-    #[tracing::instrument(level = "trace", skip(self))]
-    pub(crate) async fn load_manifest(&self) {
-        let Some(base_path) = self
-            .manifest_path
-            .as_deref()
-            .map(Utf8PathBuf::from)
-            .or(self.base_path())
-        else {
-            return;
-        };
+        info!("Configuration loaded successfully from disk.");
+        info!("Update workspace settings.");
+
+        let LoadedConfiguration {
+            configuration: fs_configuration,
+            directory_path: configuration_path,
+            ..
+        } = loaded_configuration;
 
         let fs = self.workspace.fs();
-        match fs.auto_search(&base_path, &["package.json"], false) {
-            Ok(Some(result)) => {
-                let biome_path = BiomePath::new(result.file_path);
-                let Some(project_key) = self.project_for_path(&biome_path) else {
-                    error!("Cannot determine project for package.json file at {biome_path:?}");
-                    return;
-                };
-
-                let result = self
-                    .workspace
-                    .set_manifest_for_project(SetManifestForProjectParams {
-                        project_key,
-                        manifest_path: biome_path.clone(),
-                        content: result.content,
-                        version: 0,
-                    });
-                if let Err(err) = result {
-                    error!("{err}");
+        let should_use_editorconfig = fs_configuration.use_editorconfig().unwrap_or_default();
+        let mut configuration = if should_use_editorconfig {
+            let (editorconfig, editorconfig_diagnostics) = {
+                let search_path = configuration_path
+                    .clone()
+                    .unwrap_or_else(|| fs.working_directory().unwrap_or_default());
+                match load_editorconfig(fs, search_path) {
+                    Ok(result) => result,
+                    Err(error) => {
+                        error!("Failed load the `.editorconfig` file. Reason: {error}");
+                        self.client.log_message(MessageType::ERROR, &error).await;
+                        return ConfigurationStatus::Error;
+                    }
                 }
+            };
+            for diagnostic in editorconfig_diagnostics {
+                let message = PrintDescription(&diagnostic).to_string();
+                self.client.log_message(MessageType::ERROR, message).await;
             }
-            Ok(None) => {}
+            editorconfig.unwrap_or_default()
+        } else {
+            Default::default()
+        };
+
+        configuration.merge_with(fs_configuration);
+
+        let result = configuration.retrieve_gitignore_matches(fs, configuration_path.as_deref());
+        let (vcs_base_path, gitignore_matches) = match result {
+            Ok(result) => result,
             Err(err) => {
-                error!("Couldn't load the package.json file, reason:\n {err}");
+                error!("Couldn't load the configuration file, reason:\n {err}");
+                self.client.log_message(MessageType::ERROR, &err).await;
+                return ConfigurationStatus::Error;
             }
+        };
+
+        let path = match &base_path {
+            ConfigurationPathHint::FromLsp(path) | ConfigurationPathHint::FromWorkspace(path) => {
+                path.as_path().into()
+            }
+            _ => fs
+                .working_directory()
+                .map(BiomePath::from)
+                .unwrap_or_default(),
+        };
+        let register_result = self.workspace.open_project(OpenProjectParams {
+            path: path.clone(),
+            open_uninitialized: true,
+        });
+        let project_key = match register_result {
+            Ok(result) => result,
+            Err(error) => {
+                error!("Failed to register the project folder: {error}");
+                self.client.log_message(MessageType::ERROR, &error).await;
+                return ConfigurationStatus::Error;
+            }
+        };
+
+        let result = self.workspace.update_settings(UpdateSettingsParams {
+            project_key,
+            workspace_directory: configuration_path.map(BiomePath::from),
+            configuration,
+            vcs_base_path: vcs_base_path.map(BiomePath::from),
+            gitignore_matches,
+        });
+
+        self.scan_project_folder(project_key, path).await;
+
+        if let Err(error) = result {
+            error!("Failed to set workspace settings: {error}");
+            self.client.log_message(MessageType::ERROR, &error).await;
+            ConfigurationStatus::Error
+        } else {
+            ConfigurationStatus::Loaded
         }
     }
 
diff --git a/crates/biome_migrate/Cargo.toml b/crates/biome_migrate/Cargo.toml
index efb25a92e84c..4584db5369a8 100644
--- a/crates/biome_migrate/Cargo.toml
+++ b/crates/biome_migrate/Cargo.toml
@@ -19,7 +19,7 @@ biome_console      = { workspace = true }
 biome_diagnostics  = { workspace = true }
 biome_json_factory = { workspace = true }
 biome_json_syntax  = { workspace = true }
-biome_project      = { workspace = true }
+biome_package      = { workspace = true }
 biome_rowan        = { workspace = true }
 camino             = { workspace = true }
 rustc-hash         = { workspace = true }
diff --git a/crates/biome_migrate/src/version_services.rs b/crates/biome_migrate/src/version_services.rs
index 34c3070aedee..d2c8a1bff785 100644
--- a/crates/biome_migrate/src/version_services.rs
+++ b/crates/biome_migrate/src/version_services.rs
@@ -12,11 +12,11 @@ pub(crate) struct VersionServices {
 }
 
 #[derive(Debug, Clone)]
-pub(crate) struct TheVersion(pub(crate) biome_project::Version);
+pub(crate) struct TheVersion(pub(crate) biome_package::Version);
 
 impl TheVersion {
     pub fn new(range: &str) -> Self {
-        Self(biome_project::Version::from(range))
+        Self(biome_package::Version::from(range))
     }
 }
 
diff --git a/crates/biome_project/Cargo.toml b/crates/biome_package/Cargo.toml
similarity index 92%
rename from crates/biome_project/Cargo.toml
rename to crates/biome_package/Cargo.toml
index adf0ce85eade..9a6010a7c529 100644
--- a/crates/biome_project/Cargo.toml
+++ b/crates/biome_package/Cargo.toml
@@ -1,12 +1,12 @@
 [package]
 authors.workspace    = true
 categories.workspace = true
-description          = "Workspace project handler"
+description          = "Workspace package handler"
 edition.workspace    = true
 homepage.workspace   = true
 keywords.workspace   = true
 license.workspace    = true
-name                 = "biome_project"
+name                 = "biome_package"
 publish              = true
 repository.workspace = true
 version              = "0.5.7"
diff --git a/crates/biome_project/src/diagnostics.rs b/crates/biome_package/src/diagnostics.rs
similarity index 100%
rename from crates/biome_project/src/diagnostics.rs
rename to crates/biome_package/src/diagnostics.rs
diff --git a/crates/biome_project/src/lib.rs b/crates/biome_package/src/lib.rs
similarity index 73%
rename from crates/biome_project/src/lib.rs
rename to crates/biome_package/src/lib.rs
index af54e20009dd..14432ceae4eb 100644
--- a/crates/biome_project/src/lib.rs
+++ b/crates/biome_package/src/lib.rs
@@ -1,6 +1,6 @@
 mod diagnostics;
 mod license;
-mod node_js_project;
+mod node_js_package;
 
 pub use crate::diagnostics::{ProjectAnalyzeDiagnostic, ProjectDiagnostic};
 use biome_deserialize::{DeserializationDiagnostic, Deserialized};
@@ -8,43 +8,39 @@ use biome_diagnostics::serde::Diagnostic;
 use biome_parser::diagnostic::ParseDiagnostic;
 use biome_rowan::Language;
 pub use license::generated::*;
-pub use node_js_project::{Dependencies, NodeJsProject, PackageJson, PackageType, Version};
+pub use node_js_package::{Dependencies, NodeJsPackage, PackageJson, PackageType, Version};
 use std::any::TypeId;
 use std::fmt::Debug;
-use std::path::Path;
 
 pub(crate) type LanguageRoot<L> = <L as Language>::Root;
 
-pub(crate) type ProjectRoot<P> =
-    <<<P as Project>::Manifest as Manifest>::Language as Language>::Root;
+pub(crate) type PackageRoot<P> =
+    <<<P as Package>::Manifest as Manifest>::Language as Language>::Root;
 
 pub trait Manifest: Default + Debug {
     type Language: Language;
 
-    /// It loads the manifest of the project. It accepts the path where the manifest should be
+    /// It loads the manifest of the package. It accepts the path where the manifest should be
     fn deserialize_manifest(root: &LanguageRoot<Self::Language>) -> Deserialized<Self>;
 }
 
-/// An internal representation of a project.
-pub trait Project {
+/// An internal representation of a package.
+pub trait Package {
     type Manifest: Manifest;
 
-    /// Use this function to prepare the project, like loading the manifest.
-    fn deserialize_manifest(&mut self, root: &ProjectRoot<Self>);
-
-    /// The home directory of the project
-    fn project_path(&self) -> &Path;
+    /// Use this function to prepare the package, like loading the manifest.
+    fn deserialize_manifest(&mut self, root: &PackageRoot<Self>);
 
     fn manifest(&self) -> Option<&Self::Manifest> {
         None
     }
 
-    fn analyze(&self) -> ProjectAnalyzeResult;
+    fn analyze(&self) -> PackageAnalyzeResult;
 
     fn has_errors(&self) -> bool;
 }
 
-pub struct ProjectAnalyzeResult {
+pub struct PackageAnalyzeResult {
     pub diagnostics: Vec<ProjectAnalyzeDiagnostic>,
 }
 
diff --git a/crates/biome_project/src/license/generated.rs b/crates/biome_package/src/license/generated.rs
similarity index 100%
rename from crates/biome_project/src/license/generated.rs
rename to crates/biome_package/src/license/generated.rs
diff --git a/crates/biome_project/src/license/mod.rs b/crates/biome_package/src/license/mod.rs
similarity index 100%
rename from crates/biome_project/src/license/mod.rs
rename to crates/biome_package/src/license/mod.rs
diff --git a/crates/biome_project/src/node_js_project/mod.rs b/crates/biome_package/src/node_js_package/mod.rs
similarity index 77%
rename from crates/biome_project/src/node_js_project/mod.rs
rename to crates/biome_package/src/node_js_package/mod.rs
index c6e36619de58..64f2ccfbfe40 100644
--- a/crates/biome_project/src/node_js_project/mod.rs
+++ b/crates/biome_package/src/node_js_package/mod.rs
@@ -1,17 +1,17 @@
 mod package_json;
 mod tsconfig_json;
 
-pub use crate::node_js_project::package_json::{Dependencies, PackageJson, PackageType, Version};
-use crate::node_js_project::tsconfig_json::TsConfigJson;
-use crate::{Manifest, Project, ProjectAnalyzeDiagnostic, ProjectAnalyzeResult, LICENSE_LIST};
+pub use package_json::{Dependencies, PackageJson, PackageType, Version};
+
 use biome_rowan::Language;
-use std::path::{Path, PathBuf};
+
+use crate::{Manifest, Package, PackageAnalyzeResult, ProjectAnalyzeDiagnostic, LICENSE_LIST};
+
+use tsconfig_json::TsConfigJson;
 
 #[derive(Default, Debug, Clone)]
 /// A Node.js project.
-pub struct NodeJsProject {
-    /// The path where the project
-    pub manifest_path: PathBuf,
+pub struct NodeJsPackage {
     /// The `package.json` manifest
     pub manifest: PackageJson,
     /// Diagnostics emitted during the operations
@@ -20,7 +20,7 @@ pub struct NodeJsProject {
     pub tsconfig: TsConfigJson,
 }
 
-impl NodeJsProject {
+impl NodeJsPackage {
     pub fn deserialize_tsconfig(&mut self, content: &ProjectLanguageRoot<TsConfigJson>) {
         let tsconfig = TsConfigJson::deserialize_manifest(content);
         let (tsconfig, deserialize_diagnostics) = tsconfig.consume();
@@ -34,7 +34,7 @@ impl NodeJsProject {
 
 pub(crate) type ProjectLanguageRoot<M> = <<M as Manifest>::Language as Language>::Root;
 
-impl Project for NodeJsProject {
+impl Package for NodeJsPackage {
     type Manifest = PackageJson;
 
     fn deserialize_manifest(&mut self, content: &ProjectLanguageRoot<Self::Manifest>) {
@@ -47,15 +47,11 @@ impl Project for NodeJsProject {
             .collect();
     }
 
-    fn project_path(&self) -> &Path {
-        self.manifest_path.as_path()
-    }
-
     fn manifest(&self) -> Option<&Self::Manifest> {
         Some(&self.manifest)
     }
 
-    fn analyze(&self) -> ProjectAnalyzeResult {
+    fn analyze(&self) -> PackageAnalyzeResult {
         let mut diagnostics = vec![];
         if let Some((license, range)) = &self.manifest.license {
             if !LICENSE_LIST.is_valid(license) {
@@ -68,7 +64,7 @@ impl Project for NodeJsProject {
             }
         }
 
-        ProjectAnalyzeResult { diagnostics }
+        PackageAnalyzeResult { diagnostics }
     }
 
     fn has_errors(&self) -> bool {
diff --git a/crates/biome_project/src/node_js_project/package_json.rs b/crates/biome_package/src/node_js_package/package_json.rs
similarity index 100%
rename from crates/biome_project/src/node_js_project/package_json.rs
rename to crates/biome_package/src/node_js_package/package_json.rs
diff --git a/crates/biome_project/src/node_js_project/tsconfig_json.rs b/crates/biome_package/src/node_js_package/tsconfig_json.rs
similarity index 100%
rename from crates/biome_project/src/node_js_project/tsconfig_json.rs
rename to crates/biome_package/src/node_js_package/tsconfig_json.rs
diff --git a/crates/biome_service/Cargo.toml b/crates/biome_service/Cargo.toml
index 4549d0460273..a25238e95b0d 100644
--- a/crates/biome_service/Cargo.toml
+++ b/crates/biome_service/Cargo.toml
@@ -47,8 +47,8 @@ biome_json_analyze      = { workspace = true }
 biome_json_formatter    = { workspace = true, features = ["serde"] }
 biome_json_parser       = { workspace = true }
 biome_json_syntax       = { workspace = true }
+biome_package           = { workspace = true }
 biome_parser            = { workspace = true }
-biome_project           = { workspace = true }
 biome_rowan             = { workspace = true, features = ["serde"] }
 biome_string_case       = { workspace = true }
 biome_text_edit         = { workspace = true }
diff --git a/crates/biome_service/src/file_handlers/mod.rs b/crates/biome_service/src/file_handlers/mod.rs
index d12c4961035b..b3fe1fc21595 100644
--- a/crates/biome_service/src/file_handlers/mod.rs
+++ b/crates/biome_service/src/file_handlers/mod.rs
@@ -37,8 +37,8 @@ use biome_js_syntax::{
 };
 use biome_json_analyze::METADATA as json_metadata;
 use biome_json_syntax::{JsonFileSource, JsonLanguage};
+use biome_package::PackageJson;
 use biome_parser::AnyParse;
-use biome_project::PackageJson;
 use biome_rowan::{FileSourceError, NodeCache};
 use biome_string_case::StrLikeExtension;
 
@@ -684,10 +684,10 @@ impl Features {
     /// Returns the [Capabilities] associated with a [BiomePath]
     pub(crate) fn get_capabilities(
         &self,
-        biome_path: &BiomePath,
+        path: &Utf8Path,
         language_hint: DocumentFileSource,
     ) -> Capabilities {
-        match DocumentFileSource::from_path(biome_path).or(language_hint) {
+        match DocumentFileSource::from_path(path).or(language_hint) {
             DocumentFileSource::Js(source) => match source.as_embedding_kind() {
                 EmbeddingKind::Astro => self.astro.capabilities(),
                 EmbeddingKind::Vue => self.vue.capabilities(),
diff --git a/crates/biome_service/src/lib.rs b/crates/biome_service/src/lib.rs
index 1ae2c352a94d..754fe6c435a4 100644
--- a/crates/biome_service/src/lib.rs
+++ b/crates/biome_service/src/lib.rs
@@ -2,6 +2,7 @@ pub mod documentation;
 pub mod file_handlers;
 
 pub mod matcher;
+pub mod project_layout;
 pub mod projects;
 pub mod settings;
 pub mod workspace;
diff --git a/crates/biome_service/src/project_layout.rs b/crates/biome_service/src/project_layout.rs
new file mode 100644
index 000000000000..a9cfa3a8d06a
--- /dev/null
+++ b/crates/biome_service/src/project_layout.rs
@@ -0,0 +1,106 @@
+use biome_package::{NodeJsPackage, Package, PackageJson};
+use biome_parser::AnyParse;
+use camino::{Utf8Path, Utf8PathBuf};
+use papaya::HashMap;
+use rustc_hash::FxBuildHasher;
+
+/// The layout used across all open projects.
+///
+/// Projects are comprised of zero or more packages. This arrangement is
+/// intended to reflect the common usage of monorepos, where a single repository
+/// may host many packages, and each package is allowed to have its own
+/// settings.
+///
+/// For Biome, a project is where the **top-level** configuration file is, while
+/// packages may have their own nested configuration files.
+///
+/// As a data structure, the project layout is simply a flat mapping from paths
+/// to package data. This means that in order to lookup the package that is
+/// most relevant for a given file, we may need to do a dumb iteration over all
+/// entries to find which is the closest match. This means performance becomes
+/// O(N) with the number of open packages, so if this becomes a bottleneck, we
+/// may want to reconsider this approach. For now though, it makes sense because
+/// it makes it very easy for us to invalidate part of the layout when there are
+/// file system changes.
+#[derive(Debug, Default)]
+pub struct ProjectLayout(HashMap<Utf8PathBuf, PackageData, FxBuildHasher>);
+
+/// The information tracked for each package.
+///
+/// Because Biome is intended to support multiple kinds of JavaScript projects,
+/// the term "package" is somewhat loosely defined. It may be an NPM package,
+/// a JSR package, or simply a directory with its own nested `biome.json`.
+#[derive(Debug, Default)]
+pub struct PackageData {
+    /// The settings of the package.
+    ///
+    /// Usually inferred from a configuration file, e.g. `biome.json`.
+    // TODO: Uncomment this.
+    // Probably best done when Ema has finished with https://github.com/biomejs/biome/pull/4845
+    //settings: Settings,
+
+    /// Optional Node.js-specific package information, if relevant for the
+    /// package.
+    node_package: Option<NodeJsPackage>,
+}
+
+impl ProjectLayout {
+    pub fn get_node_manifest_for_path(&self, path: &Utf8Path) -> Option<PackageJson> {
+        self.0
+            .pin()
+            .iter()
+            .fold(
+                None::<(&Utf8PathBuf, PackageJson)>,
+                |result, (package_path, data)| {
+                    let node_manifest = data
+                        .node_package
+                        .as_ref()
+                        .map(|node_package| &node_package.manifest)?;
+                    if path.strip_prefix(package_path).is_err() {
+                        return None;
+                    }
+
+                    result
+                        .is_none_or(|(matched_package_path, _)| {
+                            package_path.as_str().len() > matched_package_path.as_str().len()
+                        })
+                        .then(|| (package_path, node_manifest.clone()))
+                },
+            )
+            .map(|(_, package_json)| package_json)
+    }
+
+    pub fn insert_node_manifest(&self, path: Utf8PathBuf, manifest: AnyParse) {
+        self.0.pin().update_or_insert_with(
+            path,
+            |data| {
+                let mut node_js_package = NodeJsPackage {
+                    manifest: Default::default(),
+                    diagnostics: Default::default(),
+                    tsconfig: data
+                        .node_package
+                        .as_ref()
+                        .map(|package| package.tsconfig.clone())
+                        .unwrap_or_default(),
+                };
+                node_js_package.deserialize_manifest(&manifest.tree());
+
+                PackageData {
+                    node_package: Some(node_js_package),
+                }
+            },
+            || {
+                let mut node_js_package = NodeJsPackage::default();
+                node_js_package.deserialize_manifest(&manifest.tree());
+
+                PackageData {
+                    node_package: Some(node_js_package),
+                }
+            },
+        );
+    }
+
+    pub fn remove_package(&self, path: &Utf8Path) {
+        self.0.pin().remove(path);
+    }
+}
diff --git a/crates/biome_service/src/projects.rs b/crates/biome_service/src/projects.rs
index 425643d148d4..c2662a2e9b14 100644
--- a/crates/biome_service/src/projects.rs
+++ b/crates/biome_service/src/projects.rs
@@ -1,8 +1,6 @@
 use crate::is_dir;
 use crate::settings::{FilesSettings, Settings, DEFAULT_FILE_SIZE_LIMIT};
 use crate::workspace::FeatureKind;
-use biome_fs::BiomePath;
-use biome_project::{NodeJsProject, PackageJson};
 use camino::{Utf8Path, Utf8PathBuf};
 use papaya::HashMap;
 use rustc_hash::FxBuildHasher;
@@ -41,18 +39,13 @@ impl ProjectKey {
 #[derive(Debug, Default)]
 struct ProjectData {
     /// The root path of the project. This path should be **absolute**.
-    path: BiomePath,
+    path: Utf8PathBuf,
 
-    /// The settings of the project, usually inferred from the configuration
-    /// file e.g. `biome.json`.
-    settings: Settings,
-
-    /// Optional Node.js-specific package information, if relevant for the
-    /// project.
+    /// The "root" settings of the project.
     ///
-    /// TODO: This should be moved into the upcoming `ProjectLayout` service
-    ///       data.
-    package: Option<NodeJsProject>,
+    /// Usually inferred from the **top-level** configuration file,
+    /// e.g. `biome.json`.
+    settings: Settings,
 }
 
 impl Projects {
@@ -61,7 +54,6 @@ impl Projects {
     /// Returns the key of the newly inserted project, or returns an existing
     /// project key if a project with the given path already existed.
     pub fn insert_project(&self, path: Utf8PathBuf) -> ProjectKey {
-        let path = BiomePath::new(path);
         trace!("Insert workspace folder: {path:?}");
 
         let data = self.0.pin();
@@ -77,7 +69,6 @@ impl Projects {
             ProjectData {
                 path,
                 settings: Settings::default(),
-                package: None,
             },
         );
         key
@@ -114,44 +105,20 @@ impl Projects {
         let project_data = ProjectData {
             path: project_data.path.clone(),
             settings,
-            package: project_data.package.clone(),
         };
 
         data.insert(project_key, project_data);
     }
 
-    pub fn get_manifest(&self, project_key: ProjectKey) -> Option<PackageJson> {
-        self.0
-            .pin()
-            .get(&project_key)
-            .and_then(|data| data.package.as_ref())
-            .map(|project| project.manifest.clone())
-    }
-
-    pub fn get_project_path(&self, project_key: ProjectKey) -> Option<BiomePath> {
+    pub fn get_project_path(&self, project_key: ProjectKey) -> Option<Utf8PathBuf> {
         self.0.pin().get(&project_key).map(|data| data.path.clone())
     }
 
-    pub fn insert_manifest(&self, project_key: ProjectKey, manifest: NodeJsProject) {
-        let data = self.0.pin();
-        let Some(project_data) = data.get(&project_key) else {
-            return;
-        };
-
-        let project_data = ProjectData {
-            path: project_data.path.clone(),
-            settings: project_data.settings.clone(),
-            package: Some(manifest),
-        };
-
-        data.insert(project_key, project_data);
-    }
-
     /// Checks whether the given `path` belongs to project with the given path
     /// and no other project.
     pub fn path_belongs_only_to_project_with_path(
         &self,
-        path: &BiomePath,
+        path: &Utf8Path,
         project_path: &Utf8Path,
     ) -> bool {
         let mut belongs_to_project = false;
diff --git a/crates/biome_service/src/settings.rs b/crates/biome_service/src/settings.rs
index 00cb198e05cb..8b0205dfefdb 100644
--- a/crates/biome_service/src/settings.rs
+++ b/crates/biome_service/src/settings.rs
@@ -61,7 +61,7 @@ pub struct Settings {
 }
 
 impl Settings {
-    /// The [PartialConfiguration] is merged into the project.
+    /// Merges the [PartialConfiguration] into the settings.
     #[tracing::instrument(level = "trace", skip(self))]
     pub fn merge_with_configuration(
         &mut self,
diff --git a/crates/biome_service/src/workspace.rs b/crates/biome_service/src/workspace.rs
index a06fcbe706e9..b94a7b307d96 100644
--- a/crates/biome_service/src/workspace.rs
+++ b/crates/biome_service/src/workspace.rs
@@ -542,27 +542,6 @@ pub enum FileContent {
     FromServer,
 }
 
-#[derive(Debug, serde::Serialize, serde::Deserialize)]
-#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
-#[serde(rename_all = "camelCase")]
-pub struct SetManifestForProjectParams {
-    pub project_key: ProjectKey,
-    pub manifest_path: BiomePath,
-    pub content: String,
-    pub version: i32,
-}
-
-impl From<(ProjectKey, BiomePath, String)> for SetManifestForProjectParams {
-    fn from((project_key, manifest_path, content): (ProjectKey, BiomePath, String)) -> Self {
-        Self {
-            project_key,
-            manifest_path,
-            content,
-            version: 0,
-        }
-    }
-}
-
 #[derive(Debug, serde::Serialize, serde::Deserialize)]
 #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
 #[serde(rename_all = "camelCase")]
@@ -1032,16 +1011,6 @@ pub trait Workspace: Send + Sync + RefUnwindSafe {
     /// manage project settings on its own.
     fn update_settings(&self, params: UpdateSettingsParams) -> Result<(), WorkspaceError>;
 
-    /// Sets a new package manifest for the given project.
-    ///
-    /// This method should not be used in combination with
-    /// `scan_project_folder()`. When scanning is enabled, the server will
-    /// manage packages on its own.
-    fn set_manifest_for_project(
-        &self,
-        params: SetManifestForProjectParams,
-    ) -> Result<(), WorkspaceError>;
-
     /// Closes the project with the given key.
     ///
     /// Any settings related to the project are unloaded, and any open files
diff --git a/crates/biome_service/src/workspace/client.rs b/crates/biome_service/src/workspace/client.rs
index 88ead7628625..c296de5c283c 100644
--- a/crates/biome_service/src/workspace/client.rs
+++ b/crates/biome_service/src/workspace/client.rs
@@ -1,7 +1,7 @@
 use crate::workspace::{
     CheckFileSizeParams, CheckFileSizeResult, CloseProjectParams, FileFeaturesResult,
     GetFileContentParams, IsPathIgnoredParams, OpenProjectParams, ProjectKey, RageParams,
-    RageResult, ServerInfo, SetManifestForProjectParams,
+    RageResult, ServerInfo,
 };
 use crate::{TransportError, Workspace, WorkspaceError};
 use biome_formatter::Printed;
@@ -128,13 +128,6 @@ where
         self.request("biome/open_file", params)
     }
 
-    fn set_manifest_for_project(
-        &self,
-        params: SetManifestForProjectParams,
-    ) -> Result<(), WorkspaceError> {
-        self.request("biome/set_manifest_for_project", params)
-    }
-
     fn open_project(&self, params: OpenProjectParams) -> Result<ProjectKey, WorkspaceError> {
         self.request("biome/open_project", params)
     }
diff --git a/crates/biome_service/src/workspace/scanner.rs b/crates/biome_service/src/workspace/scanner.rs
index 3a102c4ca4c1..164e7eed9232 100644
--- a/crates/biome_service/src/workspace/scanner.rs
+++ b/crates/biome_service/src/workspace/scanner.rs
@@ -9,7 +9,7 @@ use std::time::{Duration, Instant};
 
 use biome_diagnostics::serde::Diagnostic;
 use biome_diagnostics::{Diagnostic as _, Error, Severity};
-use biome_fs::{BiomePath, PathInterner, TraversalContext, TraversalScope};
+use biome_fs::{BiomePath, PathInterner, PathInternerSet, TraversalContext, TraversalScope};
 
 use crate::diagnostics::Panic;
 use crate::projects::ProjectKey;
@@ -22,6 +22,9 @@ pub(crate) struct ScanResult {
     /// Diagnostics reported while scanning the project.
     pub diagnostics: Vec<Diagnostic>,
 
+    /// Set containing all scanned paths.
+    pub paths: PathInternerSet,
+
     /// Duration of the full scan.
     pub duration: Duration,
 }
@@ -38,7 +41,7 @@ pub(crate) fn scan(
 
     let collector = DiagnosticsCollector::new();
 
-    let (duration, diagnostics) = thread::scope(|scope| {
+    let (duration, paths, diagnostics) = thread::scope(|scope| {
         let handler = thread::Builder::new()
             .name("biome::scanner".to_string())
             .spawn_scoped(scope, || collector.run(diagnostics_receiver))
@@ -46,9 +49,9 @@ pub(crate) fn scan(
 
         // The traversal context is scoped to ensure all the channels it
         // contains are properly closed once scanning finishes.
-        let (duration, _evaluated_paths) = scan_folder(
+        let (duration, paths, _evaluated_paths) = scan_folder(
             folder,
-            &ScanContext {
+            ScanContext {
                 workspace,
                 project_key,
                 interner,
@@ -60,11 +63,12 @@ pub(crate) fn scan(
         // Wait for the collector thread to finish.
         let diagnostics = handler.join().unwrap();
 
-        (duration, diagnostics)
+        (duration, paths, diagnostics)
     });
 
     Ok(ScanResult {
         diagnostics,
+        paths,
         duration,
     })
 }
@@ -85,21 +89,26 @@ fn init_thread_pool() {
 /// Initiates the filesystem traversal tasks from the provided path and runs it to completion.
 ///
 /// Returns the duration of the process and the evaluated paths.
-fn scan_folder(folder: &Utf8Path, ctx: &ScanContext) -> (Duration, BTreeSet<BiomePath>) {
+fn scan_folder(
+    folder: &Utf8Path,
+    ctx: ScanContext,
+) -> (Duration, PathInternerSet, BTreeSet<BiomePath>) {
     let start = Instant::now();
     let fs = ctx.workspace.fs();
+    let ctx_ref = &ctx;
     fs.traversal(Box::new(move |scope: &dyn TraversalScope| {
-        scope.evaluate(ctx, folder.to_path_buf());
+        scope.evaluate(ctx_ref, folder.to_path_buf());
     }));
 
     let paths = ctx.evaluated_paths();
     fs.traversal(Box::new(|scope: &dyn TraversalScope| {
         for path in paths {
-            scope.handle(ctx, path.to_path_buf());
+            scope.handle(ctx_ref, path.to_path_buf());
         }
     }));
 
-    (start.elapsed(), ctx.evaluated_paths())
+    let evaluated_paths = ctx.evaluated_paths();
+    (start.elapsed(), ctx.interner.into_paths(), evaluated_paths)
 }
 
 struct DiagnosticsCollector {
diff --git a/crates/biome_service/src/workspace/server.rs b/crates/biome_service/src/workspace/server.rs
index ba4f0feda86b..d85c07cc75af 100644
--- a/crates/biome_service/src/workspace/server.rs
+++ b/crates/biome_service/src/workspace/server.rs
@@ -7,13 +7,14 @@ use super::{
     ParsePatternParams, ParsePatternResult, PatternId, ProjectKey, PullActionsParams,
     PullActionsResult, PullDiagnosticsParams, PullDiagnosticsResult, RenameResult,
     ScanProjectFolderParams, ScanProjectFolderResult, SearchPatternParams, SearchResults,
-    SetManifestForProjectParams, SupportsFeatureParams, UpdateSettingsParams,
+    SupportsFeatureParams, UpdateSettingsParams,
 };
 use crate::diagnostics::FileTooLarge;
 use crate::file_handlers::{
     Capabilities, CodeActionsParams, DocumentFileSource, FixAllParams, LintParams, ParseResult,
 };
 use crate::is_dir;
+use crate::project_layout::ProjectLayout;
 use crate::projects::Projects;
 use crate::workspace::{
     FileFeaturesResult, GetFileContentParams, IsPathIgnoredParams, RageEntry, RageParams,
@@ -29,13 +30,13 @@ use biome_diagnostics::{
     serde::Diagnostic as SerdeDiagnostic, Diagnostic, DiagnosticExt, Severity,
 };
 use biome_formatter::Printed;
-use biome_fs::{BiomePath, ConfigName, FileSystem};
+use biome_fs::{BiomePath, ConfigName, FileSystem, PathInternerSet};
 use biome_grit_patterns::{compile_pattern_with_options, CompilePatternOptions, GritQuery};
 use biome_js_syntax::ModuleKind;
-use biome_json_parser::{parse_json, JsonParserOptions};
+use biome_json_parser::JsonParserOptions;
 use biome_json_syntax::JsonFileSource;
+use biome_package::{PackageJson, PackageType};
 use biome_parser::AnyParse;
-use biome_project::{NodeJsProject, PackageJson, PackageType, Project};
 use biome_rowan::NodeCache;
 use camino::{Utf8Path, Utf8PathBuf};
 use papaya::HashMap;
@@ -43,17 +44,21 @@ use rustc_hash::{FxBuildHasher, FxHashMap};
 use std::panic::RefUnwindSafe;
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::Mutex;
-use tracing::{debug, info, info_span};
+use tracing::{debug, info, info_span, warn};
 
 pub(super) struct WorkspaceServer {
     /// features available throughout the application
     features: Features,
 
-    /// global settings object for this workspace
-    settings: Projects,
+    /// Open projects, including their settings, nested packages, and other
+    /// metadata.
+    projects: Projects,
+
+    /// The layout of projects and their internal packages.
+    project_layout: ProjectLayout,
 
     /// Stores the document (text content + version number) associated with a URL
-    documents: HashMap<BiomePath, Document, FxBuildHasher>,
+    documents: HashMap<Utf8PathBuf, Document, FxBuildHasher>,
 
     /// Stores the document sources used across the workspace
     file_sources: AppendOnlyVec<DocumentFileSource>,
@@ -123,7 +128,8 @@ impl WorkspaceServer {
     pub(crate) fn new(fs: Box<dyn FileSystem>) -> Self {
         Self {
             features: Features::new(),
-            settings: Default::default(),
+            projects: Default::default(),
+            project_layout: Default::default(),
             documents: Default::default(),
             file_sources: AppendOnlyVec::default(),
             patterns: Default::default(),
@@ -204,7 +210,7 @@ impl WorkspaceServer {
     }
 
     /// Retrieves the supported language of a file.
-    fn get_file_source(&self, path: &BiomePath) -> DocumentFileSource {
+    fn get_file_source(&self, path: &Utf8Path) -> DocumentFileSource {
         self.documents
             .pin()
             .get(path)
@@ -217,7 +223,7 @@ impl WorkspaceServer {
     /// path.
     fn build_capability_error<'a>(
         &'a self,
-        path: &'a BiomePath,
+        path: &'a Utf8Path,
         // feature_name: &'a str,
     ) -> impl FnOnce() -> WorkspaceError + 'a {
         move || {
@@ -232,18 +238,18 @@ impl WorkspaceServer {
         }
     }
 
-    /// Returns the manifest for the given project.
-    ///
-    /// TODO: This needs to be updated to support multiple packages within a
-    ///       project.
+    /// Returns the parsed `package.json` for a given path.
     ///
     /// ## Errors
     ///
     /// - If no document is found in the workspace. Usually, you'll have to call
     ///   [WorkspaceServer::set_manifest_for_project] to store said document.
     #[tracing::instrument(level = "trace", skip(self))]
-    fn get_manifest(&self, project_key: ProjectKey) -> Result<Option<PackageJson>, WorkspaceError> {
-        Ok(self.settings.get_manifest(project_key))
+    fn get_node_manifest_for_path(
+        &self,
+        path: &Utf8Path,
+    ) -> Result<Option<PackageJson>, WorkspaceError> {
+        Ok(self.project_layout.get_node_manifest_for_path(path))
     }
 
     /// Returns a previously inserted file source by index.
@@ -291,8 +297,9 @@ impl WorkspaceServer {
             persist_node_cache,
         }: OpenFileParams,
     ) -> Result<(), WorkspaceError> {
+        let path: Utf8PathBuf = path.into();
         let mut source = document_file_source.unwrap_or(DocumentFileSource::from_path(&path));
-        let manifest = self.get_manifest(project_key)?;
+        let manifest = self.get_node_manifest_for_path(&path)?;
 
         if let DocumentFileSource::Js(js) = &mut source {
             if let Some(manifest) = manifest {
@@ -310,7 +317,7 @@ impl WorkspaceServer {
         let mut index = self.insert_source(source);
 
         let size = content.as_bytes().len();
-        let limit = self.settings.get_max_file_size(project_key);
+        let limit = self.projects.get_max_file_size(project_key);
         if size > limit {
             self.documents.pin().insert(
                 path,
@@ -336,7 +343,7 @@ impl WorkspaceServer {
             self.node_cache
                 .lock()
                 .unwrap()
-                .insert(path.to_path_buf(), node_cache);
+                .insert(path.clone(), node_cache);
         }
 
         {
@@ -372,7 +379,7 @@ impl WorkspaceServer {
             // the document, which seems hardly worth it.
             // That said, I don't think this code is particularly pretty either
             // :sweat_smile:
-            if let Some(existing) = documents.get(&path) {
+            if let Some(existing) = documents.get(path.as_path()) {
                 if existing.opened_by_scanner {
                     document.opened_by_scanner = true;
                 }
@@ -409,23 +416,23 @@ impl WorkspaceServer {
     /// Retrieves the parser result for a given file.
     ///
     /// Returns an error if no file exists in the workspace with this path.
-    fn get_parse(&self, biome_path: &BiomePath) -> Result<AnyParse, WorkspaceError> {
+    fn get_parse(&self, path: &Utf8Path) -> Result<AnyParse, WorkspaceError> {
         let documents = self.documents.pin();
         let syntax = documents
-            .get(biome_path)
+            .get(path)
             .map(|document| document.syntax.as_ref())
             .ok_or_else(WorkspaceError::not_found)?;
 
         match syntax {
             Ok(syntax) => Ok(syntax.clone()),
-            Err(FileTooLarge { .. }) => Err(WorkspaceError::file_ignored(biome_path.to_string())),
+            Err(FileTooLarge { .. }) => Err(WorkspaceError::file_ignored(path.to_string())),
         }
     }
 
     fn parse(
         &self,
         project_key: ProjectKey,
-        biome_path: &BiomePath,
+        path: &Utf8Path,
         content: &str,
         file_source_index: usize,
         node_cache: &mut NodeCache,
@@ -433,18 +440,18 @@ impl WorkspaceServer {
         let file_source = self
             .get_source(file_source_index)
             .ok_or_else(WorkspaceError::not_found)?;
-        let capabilities = self.features.get_capabilities(biome_path, file_source);
+        let capabilities = self.features.get_capabilities(path, file_source);
 
         let parse = capabilities
             .parser
             .parse
-            .ok_or_else(self.build_capability_error(biome_path))?;
+            .ok_or_else(self.build_capability_error(path))?;
 
         let parsed = parse(
-            biome_path,
+            &BiomePath::new(path),
             file_source,
             content,
-            self.settings.get_settings(project_key).as_ref(),
+            self.projects.get_settings(project_key).as_ref(),
             node_cache,
         );
         Ok(parsed)
@@ -460,7 +467,7 @@ impl WorkspaceServer {
             for feature in features.iter() {
                 // a path is ignored if it's ignored by all features
                 ignored &= self
-                    .settings
+                    .projects
                     .is_ignored_by_feature_config(project_key, path, feature)
             }
             ignored
@@ -475,7 +482,7 @@ impl WorkspaceServer {
 
     /// Check whether a file is ignored in the top-level config `files.ignore`/`files.include`
     fn is_ignored_by_top_level_config(&self, project_key: ProjectKey, path: &Utf8Path) -> bool {
-        let Some(files_settings) = self.settings.get_files_settings(project_key) else {
+        let Some(files_settings) = self.projects.get_files_settings(project_key) else {
             return false;
         };
 
@@ -499,6 +506,31 @@ impl WorkspaceServer {
                 }
             })
     }
+
+    fn update_project_layout_for_paths(&self, paths: PathInternerSet) {
+        for path in paths.pin().iter() {
+            if let Err(error) = self.update_project_layout_for_path(path) {
+                warn!("Error while updating project layout: {error}");
+            }
+        }
+    }
+
+    fn update_project_layout_for_path(&self, path: &Utf8Path) -> Result<(), WorkspaceError> {
+        if path
+            .file_name()
+            .is_some_and(|filename| filename == "package.json")
+        {
+            let package_path = path
+                .parent()
+                .map(|parent| parent.to_path_buf())
+                .ok_or_else(WorkspaceError::not_found)?;
+            let parsed = self.get_parse(path)?;
+            self.project_layout
+                .insert_node_manifest(package_path, parsed);
+        }
+
+        Ok(())
+    }
 }
 
 impl Workspace for WorkspaceServer {
@@ -511,12 +543,12 @@ impl Workspace for WorkspaceServer {
         params: CheckFileSizeParams,
     ) -> Result<CheckFileSizeResult, WorkspaceError> {
         let documents = self.documents.pin();
-        let Some(document) = documents.get(&params.path) else {
+        let Some(document) = documents.get(params.path.as_path()) else {
             return Err(WorkspaceError::not_found());
         };
 
         let file_size = document.content.as_bytes().len();
-        let limit = self.settings.get_max_file_size(params.project_key);
+        let limit = self.projects.get_max_file_size(params.project_key);
         Ok(CheckFileSizeResult { file_size, limit })
     }
 
@@ -529,7 +561,7 @@ impl Workspace for WorkspaceServer {
 
         let capabilities = self.get_file_capabilities(&params.path);
         let language = DocumentFileSource::from_path(path);
-        let settings = self.settings.get_settings(project_key);
+        let settings = self.projects.get_settings(project_key);
         let mut file_features = FileFeaturesResult::new();
 
         let file_name = path.file_name();
@@ -554,7 +586,7 @@ impl Workspace for WorkspaceServer {
         } else {
             for feature in params.features.iter() {
                 if self
-                    .settings
+                    .projects
                     .is_ignored_by_feature_config(project_key, path, feature)
                 {
                     file_features.ignored(feature);
@@ -582,7 +614,7 @@ impl Workspace for WorkspaceServer {
     /// by another thread having previously panicked while holding the lock
     #[tracing::instrument(level = "trace", skip(self))]
     fn update_settings(&self, params: UpdateSettingsParams) -> Result<(), WorkspaceError> {
-        let Some(mut settings) = self.settings.get_settings(params.project_key) else {
+        let Some(mut settings) = self.projects.get_settings(params.project_key) else {
             return Err(WorkspaceError::no_project());
         };
 
@@ -593,7 +625,7 @@ impl Workspace for WorkspaceServer {
             params.gitignore_matches.as_slice(),
         )?;
 
-        self.settings.set_settings(params.project_key, settings);
+        self.projects.set_settings(params.project_key, settings);
 
         Ok(())
     }
@@ -602,32 +634,6 @@ impl Workspace for WorkspaceServer {
         self.open_file_internal(false, params)
     }
 
-    fn set_manifest_for_project(
-        &self,
-        params: SetManifestForProjectParams,
-    ) -> Result<(), WorkspaceError> {
-        let index = self.insert_source(JsonFileSource::json().into());
-
-        let parsed = parse_json(params.content.as_str(), JsonParserOptions::default());
-
-        let mut node_js_project = NodeJsProject::default();
-        node_js_project.deserialize_manifest(&parsed.tree());
-        self.settings
-            .insert_manifest(params.project_key, node_js_project);
-
-        self.documents.pin().insert(
-            params.manifest_path.clone(),
-            Document {
-                content: params.content,
-                version: params.version,
-                file_source_index: index,
-                syntax: Ok(parsed.into()),
-                opened_by_scanner: false,
-            },
-        );
-        Ok(())
-    }
-
     fn open_project(&self, params: OpenProjectParams) -> Result<ProjectKey, WorkspaceError> {
         let path = if params.open_uninitialized {
             let path = params.path.to_path_buf();
@@ -636,7 +642,7 @@ impl Workspace for WorkspaceServer {
             self.find_project_root(params.path)?
         };
 
-        Ok(self.settings.insert_project(path))
+        Ok(self.projects.insert_project(path))
     }
 
     fn scan_project_folder(
@@ -645,7 +651,8 @@ impl Workspace for WorkspaceServer {
     ) -> Result<ScanProjectFolderResult, WorkspaceError> {
         let path = params
             .path
-            .or_else(|| self.settings.get_project_path(params.project_key))
+            .map(Utf8PathBuf::from)
+            .or_else(|| self.projects.get_project_path(params.project_key))
             .ok_or_else(WorkspaceError::no_project)?;
 
         // TODO: Need to register a file watcher. This should happen before we
@@ -658,6 +665,8 @@ impl Workspace for WorkspaceServer {
 
         let result = scan(self, params.project_key, &path)?;
 
+        self.update_project_layout_for_paths(result.paths);
+
         Ok(ScanProjectFolderResult {
             diagnostics: result.diagnostics,
             duration: result.duration,
@@ -666,7 +675,7 @@ impl Workspace for WorkspaceServer {
 
     fn close_project(&self, params: CloseProjectParams) -> Result<(), WorkspaceError> {
         let project_path = self
-            .settings
+            .projects
             .get_project_path(params.project_key)
             .ok_or_else(WorkspaceError::no_project)?;
 
@@ -677,7 +686,7 @@ impl Workspace for WorkspaceServer {
             for (path, document) in documents.iter() {
                 if document.opened_by_scanner
                     && self
-                        .settings
+                        .projects
                         .path_belongs_only_to_project_with_path(path, &project_path)
                 {
                     documents.remove(path);
@@ -686,7 +695,7 @@ impl Workspace for WorkspaceServer {
             }
         }
 
-        self.settings.remove_project(params.project_key);
+        self.projects.remove_project(params.project_key);
 
         Ok(())
     }
@@ -730,7 +739,7 @@ impl Workspace for WorkspaceServer {
             .debug
             .debug_formatter_ir
             .ok_or_else(self.build_capability_error(&params.path))?;
-        let settings = self.settings.get_settings(params.project_key);
+        let settings = self.projects.get_settings(params.project_key);
         let parse = self.get_parse(&params.path)?;
 
         if let Some(settings) = &settings {
@@ -746,7 +755,7 @@ impl Workspace for WorkspaceServer {
     fn get_file_content(&self, params: GetFileContentParams) -> Result<String, WorkspaceError> {
         self.documents
             .pin()
-            .get(&params.path)
+            .get(params.path.as_path())
             .map(|document| document.content.clone())
             .ok_or_else(WorkspaceError::not_found)
     }
@@ -763,7 +772,7 @@ impl Workspace for WorkspaceServer {
     ) -> Result<(), WorkspaceError> {
         let documents = self.documents.pin();
         let (index, opened_by_scanner) = documents
-            .get(&path)
+            .get(path.as_path())
             .map(|document| {
                 debug_assert!(version > document.version);
                 (document.file_source_index, document.opened_by_scanner)
@@ -800,7 +809,7 @@ impl Workspace for WorkspaceServer {
         }
 
         documents
-            .insert(path, document)
+            .insert(path.into(), document)
             .ok_or_else(WorkspaceError::not_found)?;
         Ok(())
     }
@@ -814,10 +823,10 @@ impl Workspace for WorkspaceServer {
         {
             let documents = self.documents.pin();
             let document = documents
-                .get(&params.path)
+                .get(params.path.as_path())
                 .ok_or_else(WorkspaceError::not_found)?;
             if !document.opened_by_scanner {
-                documents.remove(&params.path);
+                documents.remove(params.path.as_path());
             }
         }
 
@@ -844,13 +853,13 @@ impl Workspace for WorkspaceServer {
         }: PullDiagnosticsParams,
     ) -> Result<PullDiagnosticsResult, WorkspaceError> {
         let parse = self.get_parse(&path)?;
-        let manifest = self.get_manifest(project_key)?;
+        let manifest = self.get_node_manifest_for_path(&path)?;
         let (diagnostics, errors, skipped_diagnostics) =
             if let Some(lint) = self.get_file_capabilities(&path).analyzer.lint {
                 info_span!("Pulling diagnostics", categories =? categories).in_scope(|| {
                     let results = lint(LintParams {
                         parse,
-                        workspace: &self.settings.get_settings(project_key).into(),
+                        workspace: &self.projects.get_settings(project_key).into(),
                         max_diagnostics: max_diagnostics as u32,
                         path: &path,
                         only,
@@ -914,12 +923,12 @@ impl Workspace for WorkspaceServer {
             .ok_or_else(self.build_capability_error(&path))?;
 
         let parse = self.get_parse(&path)?;
-        let manifest = self.get_manifest(project_key)?;
+        let manifest = self.get_node_manifest_for_path(&path)?;
         let language = self.get_file_source(&path);
         Ok(code_actions(CodeActionsParams {
             parse,
             range,
-            workspace: &self.settings.get_settings(project_key).into(),
+            workspace: &self.projects.get_settings(project_key).into(),
             path: &path,
             manifest,
             language,
@@ -938,7 +947,7 @@ impl Workspace for WorkspaceServer {
             .formatter
             .format
             .ok_or_else(self.build_capability_error(&params.path))?;
-        let settings = self.settings.get_settings(params.project_key);
+        let settings = self.projects.get_settings(params.project_key);
         let parse = self.get_parse(&params.path)?;
 
         if let Some(settings) = &settings {
@@ -956,7 +965,7 @@ impl Workspace for WorkspaceServer {
             .formatter
             .format_range
             .ok_or_else(self.build_capability_error(&params.path))?;
-        let settings = self.settings.get_settings(params.project_key);
+        let settings = self.projects.get_settings(params.project_key);
         let parse = self.get_parse(&params.path)?;
 
         if let Some(settings) = &settings {
@@ -981,7 +990,7 @@ impl Workspace for WorkspaceServer {
             .format_on_type
             .ok_or_else(self.build_capability_error(&params.path))?;
 
-        let settings = self.settings.get_settings(params.project_key);
+        let settings = self.projects.get_settings(params.project_key);
         let parse = self.get_parse(&params.path)?;
         if let Some(settings) = &settings {
             if !settings.formatter().format_with_errors && parse.has_errors() {
@@ -1021,12 +1030,12 @@ impl Workspace for WorkspaceServer {
             .ok_or_else(self.build_capability_error(&path))?;
         let parse = self.get_parse(&path)?;
 
-        let manifest = self.get_manifest(project_key)?;
+        let manifest = self.get_node_manifest_for_path(&path)?;
         let language = self.get_file_source(&path);
         fix_all(FixAllParams {
             parse,
             fix_file_mode,
-            workspace: self.settings.get_settings(project_key).into(),
+            workspace: self.projects.get_settings(project_key).into(),
             should_format,
             biome_path: &path,
             manifest,
@@ -1092,7 +1101,7 @@ impl Workspace for WorkspaceServer {
             .search
             .search
             .ok_or_else(self.build_capability_error(&path))?;
-        let settings = self.settings.get_settings(project_key);
+        let settings = self.projects.get_settings(project_key);
         let parse = self.get_parse(&path)?;
 
         let document_file_source = self.get_file_source(&path);
diff --git a/crates/biome_service/src/workspace_types.rs b/crates/biome_service/src/workspace_types.rs
index 33e2f6fb85b6..9e505e46a5e1 100644
--- a/crates/biome_service/src/workspace_types.rs
+++ b/crates/biome_service/src/workspace_types.rs
@@ -451,12 +451,11 @@ macro_rules! workspace_method {
 }
 
 /// Returns a list of signature for all the methods in the [Workspace] trait
-pub fn methods() -> [WorkspaceMethod; 22] {
+pub fn methods() -> [WorkspaceMethod; 21] {
     [
         workspace_method!(file_features),
         workspace_method!(update_settings),
         workspace_method!(open_project),
-        workspace_method!(set_manifest_for_project),
         workspace_method!(open_file),
         workspace_method!(change_file),
         workspace_method!(close_file),
diff --git a/knope.toml b/knope.toml
index f3a69658eb30..90da859221cc 100644
--- a/knope.toml
+++ b/knope.toml
@@ -164,9 +164,9 @@ versioned_files = ["crates/biome_markup/Cargo.toml"]
 [packages.biome_parser]
 changelog       = "crates/biome_parser/CHANGELOG.md"
 versioned_files = ["crates/biome_parser/Cargo.toml"]
-[packages.biome_project]
-changelog       = "crates/biome_project/CHANGELOG.md"
-versioned_files = ["crates/biome_project/Cargo.toml"]
+[packages.biome_package]
+changelog       = "crates/biome_package/CHANGELOG.md"
+versioned_files = ["crates/biome_package/Cargo.toml"]
 [packages.biome_rowan]
 changelog       = "crates/biome_rowan/CHANGELOG.md"
 versioned_files = ["crates/biome_rowan/Cargo.toml"]
diff --git a/packages/@biomejs/backend-jsonrpc/src/workspace.ts b/packages/@biomejs/backend-jsonrpc/src/workspace.ts
index c3ccf9936148..20f22797b2c1 100644
--- a/packages/@biomejs/backend-jsonrpc/src/workspace.ts
+++ b/packages/@biomejs/backend-jsonrpc/src/workspace.ts
@@ -2905,12 +2905,6 @@ export interface OpenProjectParams {
 	 */
 	path: BiomePath;
 }
-export interface SetManifestForProjectParams {
-	content: string;
-	manifestPath: BiomePath;
-	projectKey: ProjectKey;
-	version: number;
-}
 export interface OpenFileParams {
 	content: FileContent;
 	documentFileSource?: DocumentFileSource;
@@ -3720,7 +3714,6 @@ export interface Workspace {
 	fileFeatures(params: SupportsFeatureParams): Promise<FileFeaturesResult>;
 	updateSettings(params: UpdateSettingsParams): Promise<void>;
 	openProject(params: OpenProjectParams): Promise<ProjectKey>;
-	setManifestForProject(params: SetManifestForProjectParams): Promise<void>;
 	openFile(params: OpenFileParams): Promise<void>;
 	changeFile(params: ChangeFileParams): Promise<void>;
 	closeFile(params: CloseFileParams): Promise<void>;
@@ -3754,9 +3747,6 @@ export function createWorkspace(transport: Transport): Workspace {
 		openProject(params) {
 			return transport.request("biome/open_project", params);
 		},
-		setManifestForProject(params) {
-			return transport.request("biome/set_manifest_for_project", params);
-		},
 		openFile(params) {
 			return transport.request("biome/open_file", params);
 		},
diff --git a/xtask/codegen/src/generate_license.rs b/xtask/codegen/src/generate_license.rs
index 7f27d68e7f75..cac9cc31295f 100644
--- a/xtask/codegen/src/generate_license.rs
+++ b/xtask/codegen/src/generate_license.rs
@@ -35,7 +35,7 @@ pub(crate) fn generate_license(mode: Mode) -> Result<()> {
     let request = get(URL);
     let result = request.call()?;
     let license_list = result.into_json::<LicenseList>()?;
-    let config_root = project_root().join("crates/biome_project/src/license");
+    let config_root = project_root().join("crates/biome_package/src/license");
 
     let tokens = create_data(license_list).expect("To write data into file");
 


Test patch:
diff --git a/crates/biome_cli/tests/commands/lint.rs b/crates/biome_cli/tests/commands/lint.rs
index 6cce3c48ff7a..10d2d2fe1183 100644
--- a/crates/biome_cli/tests/commands/lint.rs
+++ b/crates/biome_cli/tests/commands/lint.rs
@@ -7,7 +7,8 @@ use crate::configs::{
 };
 use crate::snap_test::{assert_file_contents, markup_to_string, SnapshotPayload};
 use crate::{
-    assert_cli_snapshot, run_cli, run_cli_with_dyn_fs, FORMATTED, LINT_ERROR, PARSE_ERROR,
+    assert_cli_snapshot, run_cli, run_cli_with_dyn_fs, run_cli_with_server_workspace, FORMATTED,
+    LINT_ERROR, PARSE_ERROR,
 };
 use biome_console::{markup, BufferConsole, LogLevel, MarkupBuf};
 use biome_fs::{ErrorEntry, FileSystemExt, MemoryFileSystem, OsFileSystem};
@@ -3841,3 +3842,158 @@ fn linter_shows_the_default_severity_of_rule_on() {
         result,
     ));
 }
+
+#[test]
+fn linter_finds_package_json_for_no_undeclared_dependencies() {
+    let mut console = BufferConsole::default();
+    let mut fs = MemoryFileSystem::default();
+
+    fs.insert(
+        Utf8Path::new("biome.json").into(),
+        r#"{
+    "linter": {
+        "rules": {
+            "correctness": {
+                "noUndeclaredDependencies": "on"
+            }
+        }
+    }
+}"#
+        .as_bytes(),
+    );
+
+    fs.insert(
+        Utf8Path::new("frontend/package.json").into(),
+        r#"{
+    "dependencies": {
+        "react": "19.0.0"
+    }
+}"#
+        .as_bytes(),
+    );
+
+    let file = Utf8Path::new("frontend/file1.js");
+    fs.insert(file.into(), r#"import 'react-dom'"#.as_bytes());
+    let (fs, result) = run_cli_with_server_workspace(
+        fs,
+        &mut console,
+        Args::from(["lint", file.as_str()].as_slice()),
+    );
+    assert_cli_snapshot(SnapshotPayload::new(
+        module_path!(),
+        "linter_finds_package_json_for_no_undeclared_dependencies",
+        fs,
+        console,
+        result,
+    ));
+}
+
+#[test]
+fn linter_finds_nested_package_json_for_no_undeclared_dependencies() {
+    let mut console = BufferConsole::default();
+    let mut fs = MemoryFileSystem::default();
+
+    fs.insert(
+        Utf8Path::new("biome.json").into(),
+        r#"{
+    "linter": {
+        "rules": {
+            "correctness": {
+                "noUndeclaredDependencies": "on"
+            }
+        }
+    }
+}"#
+        .as_bytes(),
+    );
+
+    fs.insert(
+        Utf8Path::new("package.json").into(),
+        r#"{
+    "dependencies": {
+        "react-dom": "19.0.0"
+    }
+}"#
+        .as_bytes(),
+    );
+
+    fs.insert(
+        Utf8Path::new("frontend/package.json").into(),
+        r#"{
+    "dependencies": {
+        "react": "19.0.0"
+    }
+}"#
+        .as_bytes(),
+    );
+
+    let file = Utf8Path::new("frontend/file1.js");
+    fs.insert(file.into(), r#"import 'react-dom'"#.as_bytes());
+    let (fs, result) = run_cli_with_server_workspace(
+        fs,
+        &mut console,
+        Args::from(["lint", file.as_str()].as_slice()),
+    );
+    assert_cli_snapshot(SnapshotPayload::new(
+        module_path!(),
+        "linter_finds_nested_package_json_for_no_undeclared_dependencies",
+        fs,
+        console,
+        result,
+    ));
+}
+
+#[test]
+fn linter_finds_nested_package_json_for_no_undeclared_dependencies_inversed() {
+    let mut console = BufferConsole::default();
+    let mut fs = MemoryFileSystem::default();
+
+    fs.insert(
+        Utf8Path::new("biome.json").into(),
+        r#"{
+    "linter": {
+        "rules": {
+            "correctness": {
+                "noUndeclaredDependencies": "on"
+            }
+        }
+    }
+}"#
+        .as_bytes(),
+    );
+
+    fs.insert(
+        Utf8Path::new("package.json").into(),
+        r#"{
+    "dependencies": {
+        "react": "19.0.0"
+    }
+}"#
+        .as_bytes(),
+    );
+
+    fs.insert(
+        Utf8Path::new("frontend/package.json").into(),
+        r#"{
+    "dependencies": {
+        "react-dom": "19.0.0"
+    }
+}"#
+        .as_bytes(),
+    );
+
+    let file = Utf8Path::new("frontend/file1.js");
+    fs.insert(file.into(), r#"import 'react-dom'"#.as_bytes());
+    let (fs, result) = run_cli_with_server_workspace(
+        fs,
+        &mut console,
+        Args::from(["lint", file.as_str()].as_slice()),
+    );
+    assert_cli_snapshot(SnapshotPayload::new(
+        module_path!(),
+        "linter_finds_nested_package_json_for_no_undeclared_dependencies_inversed",
+        fs,
+        console,
+        result,
+    ));
+}
diff --git a/crates/biome_cli/tests/main.rs b/crates/biome_cli/tests/main.rs
index f1e2f4fdd194..c5477e4aac27 100644
--- a/crates/biome_cli/tests/main.rs
+++ b/crates/biome_cli/tests/main.rs
@@ -389,3 +389,40 @@ pub(crate) fn run_cli_with_dyn_fs(
         }
     }
 }
+
+/// Create an [App] instance using the provided [FileSystem] and [Console]
+/// instance, and using an in-process server instance of the workspace
+pub(crate) fn run_cli_with_server_workspace(
+    fs: MemoryFileSystem,
+    console: &mut dyn Console,
+    args: bpaf::Args,
+) -> (MemoryFileSystem, Result<(), CliDiagnostic>) {
+    use biome_service::{workspace, WorkspaceRef};
+
+    let files = fs.files.clone();
+
+    let workspace = workspace::server(Box::new(fs));
+    let app = App::new(console, WorkspaceRef::Owned(workspace));
+
+    let mut session = CliSession { app };
+    let command = biome_command().run_inner(args);
+    let result = match command {
+        Ok(command) => session.run(command),
+        Err(failure) => {
+            if let ParseFailure::Stdout(help, _) = &failure {
+                let console = &mut session.app.console;
+                console.log(markup! {{help.to_string()}});
+                Ok(())
+            } else {
+                Err(CliDiagnostic::parse_error_bpaf(failure))
+            }
+        }
+    };
+
+    // This is a little bit of a workaround to allow us to easily create
+    // a snapshot of the files even though the original file system was
+    // consumed by the workspace.
+    let fs = MemoryFileSystem::from_files(files);
+
+    (fs, result)
+}
diff --git a/crates/biome_cli/tests/snapshots/main_commands_lint/linter_finds_nested_package_json_for_no_undeclared_dependencies.snap b/crates/biome_cli/tests/snapshots/main_commands_lint/linter_finds_nested_package_json_for_no_undeclared_dependencies.snap
new file mode 100644
index 000000000000..7ef880891a32
--- /dev/null
+++ b/crates/biome_cli/tests/snapshots/main_commands_lint/linter_finds_nested_package_json_for_no_undeclared_dependencies.snap
@@ -0,0 +1,64 @@
+---
+source: crates/biome_cli/tests/snap_test.rs
+expression: content
+---
+## `biome.json`
+
+```json
+{
+  "linter": {
+    "rules": {
+      "correctness": {
+        "noUndeclaredDependencies": "on"
+      }
+    }
+  }
+}
+```
+
+## `frontend/file1.js`
+
+```js
+import 'react-dom'
+```
+
+## `frontend/package.json`
+
+```json
+{
+    "dependencies": {
+        "react": "19.0.0"
+    }
+}
+```
+
+## `package.json`
+
+```json
+{
+    "dependencies": {
+        "react-dom": "19.0.0"
+    }
+}
+```
+
+# Emitted Messages
+
+```block
+frontend/file1.js:1:8 lint/correctness/noUndeclaredDependencies ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+  i The current dependency isn't specified in your package.json.
+  
+  > 1 │ import 'react-dom'
+      │        ^^^^^^^^^^^
+  
+  i This could lead to errors.
+  
+  i Add the dependency in your manifest.
+  
+
+```
+
+```block
+Checked 1 file in <TIME>. No fixes applied.
+```
diff --git a/crates/biome_cli/tests/snapshots/main_commands_lint/linter_finds_nested_package_json_for_no_undeclared_dependencies_inversed.snap b/crates/biome_cli/tests/snapshots/main_commands_lint/linter_finds_nested_package_json_for_no_undeclared_dependencies_inversed.snap
new file mode 100644
index 000000000000..4947e9cc9ec8
--- /dev/null
+++ b/crates/biome_cli/tests/snapshots/main_commands_lint/linter_finds_nested_package_json_for_no_undeclared_dependencies_inversed.snap
@@ -0,0 +1,49 @@
+---
+source: crates/biome_cli/tests/snap_test.rs
+expression: content
+---
+## `biome.json`
+
+```json
+{
+  "linter": {
+    "rules": {
+      "correctness": {
+        "noUndeclaredDependencies": "on"
+      }
+    }
+  }
+}
+```
+
+## `frontend/file1.js`
+
+```js
+import 'react-dom'
+```
+
+## `frontend/package.json`
+
+```json
+{
+    "dependencies": {
+        "react-dom": "19.0.0"
+    }
+}
+```
+
+## `package.json`
+
+```json
+{
+    "dependencies": {
+        "react": "19.0.0"
+    }
+}
+```
+
+# Emitted Messages
+
+```block
+Checked 1 file in <TIME>. No fixes applied.
+```
diff --git a/crates/biome_cli/tests/snapshots/main_commands_lint/linter_finds_package_json_for_no_undeclared_dependencies.snap b/crates/biome_cli/tests/snapshots/main_commands_lint/linter_finds_package_json_for_no_undeclared_dependencies.snap
new file mode 100644
index 000000000000..06520b6470ff
--- /dev/null
+++ b/crates/biome_cli/tests/snapshots/main_commands_lint/linter_finds_package_json_for_no_undeclared_dependencies.snap
@@ -0,0 +1,54 @@
+---
+source: crates/biome_cli/tests/snap_test.rs
+expression: content
+---
+## `biome.json`
+
+```json
+{
+  "linter": {
+    "rules": {
+      "correctness": {
+        "noUndeclaredDependencies": "on"
+      }
+    }
+  }
+}
+```
+
+## `frontend/file1.js`
+
+```js
+import 'react-dom'
+```
+
+## `frontend/package.json`
+
+```json
+{
+    "dependencies": {
+        "react": "19.0.0"
+    }
+}
+```
+
+# Emitted Messages
+
+```block
+frontend/file1.js:1:8 lint/correctness/noUndeclaredDependencies ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+  i The current dependency isn't specified in your package.json.
+  
+  > 1 │ import 'react-dom'
+      │        ^^^^^^^^^^^
+  
+  i This could lead to errors.
+  
+  i Add the dependency in your manifest.
+  
+
+```
+
+```block
+Checked 1 file in <TIME>. No fixes applied.
+```
diff --git a/crates/biome_js_analyze/tests/spec_tests.rs b/crates/biome_js_analyze/tests/spec_tests.rs
index e3db091f9b6a..f62324e256d5 100644
--- a/crates/biome_js_analyze/tests/spec_tests.rs
+++ b/crates/biome_js_analyze/tests/spec_tests.rs
@@ -6,8 +6,8 @@ use biome_diagnostics::{DiagnosticExt, Severity};
 use biome_fs::OsFileSystem;
 use biome_js_parser::{parse, JsParserOptions};
 use biome_js_syntax::{JsFileSource, JsLanguage, ModuleKind};
+use biome_package::PackageType;
 use biome_plugin_loader::AnalyzerGritPlugin;
-use biome_project::PackageType;
 use biome_rowan::AstNode;
 use biome_test_utils::{
     assert_errors_are_absent, code_fix_to_string, create_analyzer_options, diagnostic_to_string,
diff --git a/crates/biome_project/tests/manifest/invalid/license_invalid.json b/crates/biome_package/tests/manifest/invalid/license_invalid.json
similarity index 100%
rename from crates/biome_project/tests/manifest/invalid/license_invalid.json
rename to crates/biome_package/tests/manifest/invalid/license_invalid.json
diff --git a/crates/biome_project/tests/manifest/invalid/license_invalid.json.snap b/crates/biome_package/tests/manifest/invalid/license_invalid.json.snap
similarity index 87%
rename from crates/biome_project/tests/manifest/invalid/license_invalid.json.snap
rename to crates/biome_package/tests/manifest/invalid/license_invalid.json.snap
index 934785819bdc..3a1a24f89d7f 100644
--- a/crates/biome_project/tests/manifest/invalid/license_invalid.json.snap
+++ b/crates/biome_package/tests/manifest/invalid/license_invalid.json.snap
@@ -1,5 +1,5 @@
 ---
-source: crates/biome_project/tests/manifest_spec_tests.rs
+source: crates/biome_package/tests/manifest_spec_tests.rs
 expression: license_invalid.json
 ---
 license_invalid.json:2:13 project ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
diff --git a/crates/biome_project/tests/manifest/invalid/license_not_string.json b/crates/biome_package/tests/manifest/invalid/license_not_string.json
similarity index 100%
rename from crates/biome_project/tests/manifest/invalid/license_not_string.json
rename to crates/biome_package/tests/manifest/invalid/license_not_string.json
diff --git a/crates/biome_project/tests/manifest/invalid/license_not_string.json.snap b/crates/biome_package/tests/manifest/invalid/license_not_string.json.snap
similarity index 88%
rename from crates/biome_project/tests/manifest/invalid/license_not_string.json.snap
rename to crates/biome_package/tests/manifest/invalid/license_not_string.json.snap
index e65a34ac5c75..266f2c581835 100644
--- a/crates/biome_project/tests/manifest/invalid/license_not_string.json.snap
+++ b/crates/biome_package/tests/manifest/invalid/license_not_string.json.snap
@@ -1,5 +1,5 @@
 ---
-source: crates/biome_project/tests/manifest_spec_tests.rs
+source: crates/biome_package/tests/manifest_spec_tests.rs
 expression: license_not_string.json
 ---
 license_not_string.json:2:13 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
diff --git a/crates/biome_project/tests/manifest/invalid/name.json b/crates/biome_package/tests/manifest/invalid/name.json
similarity index 100%
rename from crates/biome_project/tests/manifest/invalid/name.json
rename to crates/biome_package/tests/manifest/invalid/name.json
diff --git a/crates/biome_project/tests/manifest/invalid/name.json.snap b/crates/biome_package/tests/manifest/invalid/name.json.snap
similarity index 88%
rename from crates/biome_project/tests/manifest/invalid/name.json.snap
rename to crates/biome_package/tests/manifest/invalid/name.json.snap
index c9a33ef8baa7..a29f91db84d2 100644
--- a/crates/biome_project/tests/manifest/invalid/name.json.snap
+++ b/crates/biome_package/tests/manifest/invalid/name.json.snap
@@ -1,5 +1,5 @@
 ---
-source: crates/biome_project/tests/manifest_spec_tests.rs
+source: crates/biome_package/tests/manifest_spec_tests.rs
 expression: name.json
 ---
 name.json:2:10 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
diff --git a/crates/biome_project/tests/manifest/invalid/name.snap b/crates/biome_package/tests/manifest/invalid/name.snap
similarity index 100%
rename from crates/biome_project/tests/manifest/invalid/name.snap
rename to crates/biome_package/tests/manifest/invalid/name.snap
diff --git a/crates/biome_project/tests/manifest_spec_tests.rs b/crates/biome_package/tests/manifest_spec_tests.rs
similarity index 93%
rename from crates/biome_project/tests/manifest_spec_tests.rs
rename to crates/biome_package/tests/manifest_spec_tests.rs
index 468420cf7866..2b375a921633 100644
--- a/crates/biome_project/tests/manifest_spec_tests.rs
+++ b/crates/biome_package/tests/manifest_spec_tests.rs
@@ -1,6 +1,6 @@
 use biome_diagnostics::{print_diagnostic_to_string, DiagnosticExt};
 use biome_json_parser::{parse_json, JsonParserOptions};
-use biome_project::{NodeJsProject, Project};
+use biome_package::{NodeJsPackage, Package};
 use std::ffi::OsStr;
 use std::fs::read_to_string;
 use std::path::Path;
@@ -20,25 +20,25 @@ fn run_invalid_manifests(input: &'static str, _: &str, _: &str, _: &str) {
     let input_code = read_to_string(input_file)
         .unwrap_or_else(|err| panic!("failed to read {input_file:?}: {err:?}"));
 
-    let mut project = NodeJsProject::default();
+    let mut package = NodeJsPackage::default();
     match input_file.extension().map(OsStr::as_encoded_bytes) {
         Some(b"json") => {
             let parsed = parse_json(input_code.as_str(), JsonParserOptions::default());
-            project.deserialize_manifest(&parsed.tree());
+            package.deserialize_manifest(&parsed.tree());
         }
         _ => {
             panic!("Extension not supported");
         }
     };
 
-    let result = project.analyze();
+    let result = package.analyze();
 
     assert!(
-        project.has_errors() || !result.diagnostics.is_empty(),
+        package.has_errors() || !result.diagnostics.is_empty(),
         "The file {input} should have diagnostics, but it doesn't have any"
     );
 
-    let mut diagnostics_string = project
+    let mut diagnostics_string = package
         .diagnostics
         .into_iter()
         .map(|diagnostic| {
@@ -74,7 +74,7 @@ fn run_invalid_tsconfig(input: &'static str, _: &str, _: &str, _: &str) {
     let input_code = read_to_string(input_file)
         .unwrap_or_else(|err| panic!("failed to read {input_file:?}: {err:?}"));
 
-    let mut project = NodeJsProject::default();
+    let mut project = NodeJsPackage::default();
     match input_file.extension().map(OsStr::as_encoded_bytes) {
         Some(b"json") => {
             let parsed = parse_json(
@@ -131,7 +131,7 @@ fn run_valid_tsconfig(input: &'static str, _: &str, _: &str, _: &str) {
     let input_code = read_to_string(input_file)
         .unwrap_or_else(|err| panic!("failed to read {input_file:?}: {err:?}"));
 
-    let mut project = NodeJsProject::default();
+    let mut project = NodeJsPackage::default();
     match input_file.extension().map(OsStr::as_encoded_bytes) {
         Some(b"json") => {
             let parsed = parse_json(
diff --git a/crates/biome_project/tests/tsconfig/invalid/tsconfig.invalid.baseUrl.json b/crates/biome_package/tests/tsconfig/invalid/tsconfig.invalid.baseUrl.json
similarity index 100%
rename from crates/biome_project/tests/tsconfig/invalid/tsconfig.invalid.baseUrl.json
rename to crates/biome_package/tests/tsconfig/invalid/tsconfig.invalid.baseUrl.json
diff --git a/crates/biome_project/tests/tsconfig/invalid/tsconfig.invalid.baseUrl.json.snap b/crates/biome_package/tests/tsconfig/invalid/tsconfig.invalid.baseUrl.json.snap
similarity index 87%
rename from crates/biome_project/tests/tsconfig/invalid/tsconfig.invalid.baseUrl.json.snap
rename to crates/biome_package/tests/tsconfig/invalid/tsconfig.invalid.baseUrl.json.snap
index 78e6cd7e72c7..7db400cfc322 100644
--- a/crates/biome_project/tests/tsconfig/invalid/tsconfig.invalid.baseUrl.json.snap
+++ b/crates/biome_package/tests/tsconfig/invalid/tsconfig.invalid.baseUrl.json.snap
@@ -1,5 +1,5 @@
 ---
-source: crates/biome_project/tests/manifest_spec_tests.rs
+source: crates/biome_package/tests/manifest_spec_tests.rs
 expression: tsconfig.invalid.baseUrl.json
 ---
 tsconfig.invalid.baseUrl.json:2:14 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
diff --git a/crates/biome_project/tests/tsconfig/valid/tsconfig.valid.baseUrl.json b/crates/biome_package/tests/tsconfig/valid/tsconfig.valid.baseUrl.json
similarity index 100%
rename from crates/biome_project/tests/tsconfig/valid/tsconfig.valid.baseUrl.json
rename to crates/biome_package/tests/tsconfig/valid/tsconfig.valid.baseUrl.json
diff --git a/crates/biome_project/tests/tsconfig/valid/tsconfig.valid.baseUrl.json.snap b/crates/biome_package/tests/tsconfig/valid/tsconfig.valid.baseUrl.json.snap
similarity index 75%
rename from crates/biome_project/tests/tsconfig/valid/tsconfig.valid.baseUrl.json.snap
rename to crates/biome_package/tests/tsconfig/valid/tsconfig.valid.baseUrl.json.snap
index 887d5fed0c03..e7e496329fa8 100644
--- a/crates/biome_project/tests/tsconfig/valid/tsconfig.valid.baseUrl.json.snap
+++ b/crates/biome_package/tests/tsconfig/valid/tsconfig.valid.baseUrl.json.snap
@@ -1,5 +1,5 @@
 ---
-source: crates/biome_project/tests/manifest_spec_tests.rs
+source: crates/biome_package/tests/manifest_spec_tests.rs
 expression: tsconfig.valid.baseUrl.json
 ---
 ## Input
diff --git a/crates/biome_project/tests/tsconfig/valid/tsconfig.valid.paths.json b/crates/biome_package/tests/tsconfig/valid/tsconfig.valid.paths.json
similarity index 100%
rename from crates/biome_project/tests/tsconfig/valid/tsconfig.valid.paths.json
rename to crates/biome_package/tests/tsconfig/valid/tsconfig.valid.paths.json
diff --git a/crates/biome_project/tests/tsconfig/valid/tsconfig.valid.paths.json.snap b/crates/biome_package/tests/tsconfig/valid/tsconfig.valid.paths.json.snap
similarity index 85%
rename from crates/biome_project/tests/tsconfig/valid/tsconfig.valid.paths.json.snap
rename to crates/biome_package/tests/tsconfig/valid/tsconfig.valid.paths.json.snap
index 59f5267e17b8..0d3d96f2a296 100644
--- a/crates/biome_project/tests/tsconfig/valid/tsconfig.valid.paths.json.snap
+++ b/crates/biome_package/tests/tsconfig/valid/tsconfig.valid.paths.json.snap
@@ -1,5 +1,5 @@
 ---
-source: crates/biome_project/tests/manifest_spec_tests.rs
+source: crates/biome_package/tests/manifest_spec_tests.rs
 expression: tsconfig.valid.paths.json
 ---
 ## Input
diff --git a/crates/biome_test_utils/Cargo.toml b/crates/biome_test_utils/Cargo.toml
index 45063dedc335..6ac61a65b172 100644
--- a/crates/biome_test_utils/Cargo.toml
+++ b/crates/biome_test_utils/Cargo.toml
@@ -21,7 +21,7 @@ biome_deserialize   = { workspace = true }
 biome_diagnostics   = { workspace = true }
 biome_formatter     = { workspace = true }
 biome_json_parser   = { workspace = true }
-biome_project       = { workspace = true }
+biome_package       = { workspace = true }
 biome_rowan         = { workspace = true }
 biome_service       = { workspace = true }
 camino              = { workspace = true }
diff --git a/crates/biome_test_utils/src/lib.rs b/crates/biome_test_utils/src/lib.rs
index 907a5f817d69..2c50e80b7d0a 100644
--- a/crates/biome_test_utils/src/lib.rs
+++ b/crates/biome_test_utils/src/lib.rs
@@ -6,7 +6,7 @@ use biome_console::markup;
 use biome_diagnostics::termcolor::Buffer;
 use biome_diagnostics::{DiagnosticExt, Error, PrintDiagnostic};
 use biome_json_parser::{JsonParserOptions, ParseDiagnostic};
-use biome_project::PackageJson;
+use biome_package::PackageJson;
 use biome_rowan::{SyntaxKind, SyntaxNode, SyntaxSlot};
 use biome_service::configuration::to_analyzer_rules;
 use biome_service::file_handlers::DocumentFileSource;