Skip to content

feat(envs): remove core envs from the manifest and their sources from the workspace - #10465

Open
davidfirst wants to merge 213 commits into
masterfrom
remove-core-envs-from-manifest
Open

feat(envs): remove core envs from the manifest and their sources from the workspace#10465
davidfirst wants to merge 213 commits into
masterfrom
remove-core-envs-from-manifest

Conversation

@davidfirst

@davidfirst davidfirst commented Jul 2, 2026

Copy link
Copy Markdown
Member

Removes the env aspects (teambit.react/react, teambit.harmony/node, teambit.harmony/aspect, teambit.envs/env, teambit.mdx/mdx, teambit.mdx/readme) from the core manifest to slim Bit. They now act like any other env, installed from the registry.

New default env: teambit.harmony/empty-env (core). A totally empty env - no compiler, no tester, no preview, no dependency policy. Components with no env configured use it and work fully offline out of the box (add → compile no-op → tag/snap → export). Since it has no behavior, it has nothing to drift when bit itself changes - the one env that is safe to keep core (and versionless in models) forever. To get a dev experience, users configure a real env (bit create flows already do).

teambit.harmony/aspect and teambit.envs/env are removed like the rest, with zero behavior change. Their implementation is untouched (react-based, preview and all) - users get the exact released behavior after bit install (the pinned-version machinery auto-installs them). New envs are created from the bitdev env packages (bit create react-env etc.), so these built-in envs are legacy surface. The bit-aspect template and the harmony starters moved to the core generator aspect, so bit create bit-aspect and bit new keep working out of the box (the created aspect needs bit install before it loads, like any env).

Versionless by design. Config entries for the removed env ids are persisted by name, without a version - exactly as they were when core (registered as core-extension names). Keeping them versionless is deliberate on two counts. First, it keeps the env from becoming a dependency edge of its own components; otherwise an env such as react, whose dependency closure includes components that use it as their env, creates circular TS project references and breaks lane/tag builds. Second, it preserves forward compatibility: a re-tag under the new bit keeps the env id versionless, so a teammate who has not upgraded yet (whose bit still ships these as core) can import the re-tagged component and resolve the env - instead of receiving a versioned id their bit has no component for. The alternative (showing the component as modified and pinning the env on the next tag) would silently break not-yet-upgraded consumers.

Backward compatibility. Old components have the removed envs saved without a version. legacy-core-envs.ts maps them to pinned versions, applied only at the resolution/loading/install level - stored objects are never mutated. Versionless legacy ids match the env slot ignoring version, bit install auto-adds their packages, and single-instance semantics are enforced (a loaded version is reused rather than loading another copy). Not-installed legacy envs fail fast with a NonLoadedEnv issue suggesting bit install - no scope-capsule isolation in workspace context (which used to take minutes). Old components load without being reported as modified, and re-tagging keeps the env versionless - covered end-to-end by e2e/harmony/legacy-core-env-back-compat.e2e.ts, which imports a component exported by a pre-removal bit (env saved versionless) and asserts it is not modified and stays versionless after a re-tag.

Relocated core wiring: the bit aspect CLI command moved to teambit.workspace/workspace; validateBeforePersistHook moved to teambit.dependencies/dependency-resolver; the dead @teambit/legacy link is now skipped instead of crashing.

Also fixes latent issues this path exposed: versionless seeders filtering out all manifests in loadExtensionsByManifests, circular env chains causing infinite component-load recursion, versioned core-aspect ids escaping core filters and doRequire mutating shared core manifests, stack overflows from recursive graph traversal, and a spurious MissingDists issue for compiler-less envs.

Verified locally: fresh workspace (JS and TS components) - clean status in ~1s, tag/snap/export offline, bit envs/bit test graceful; this repo's workspace - status/insights/list-core clean; the seven repo components that relied on the default env are now explicitly set to the node env. bit create <template> --env <removed-env> loads the env's templates on demand from the global scope (pinned version); this path also loads the full manifest graph, and binds manifest deps of legacy envs to their pinned versions (models built when these envs were core don't list them as dependencies). The e2e setCustomEnv helper installs the env package the fixture imports (e.g. @teambit/node).


Also removes the former-core env sources from this repo's workspace (scopes/harmony/node, scopes/react/react, scopes/harmony/aspect, scopes/envs/env, scopes/mdx/mdx, scopes/docs/readme) - bit now dogfoods them as installed packages like any consumer, and the source-vs-installed duality is gone. Making this pass end-to-end surfaced several general fixes that ride along:

  • workspace-aspects-loader: an unresolvable dependency-env no longer aborts the whole load group (it degrades to a reported load failure for that env only), and on aspect-path collisions the dedup keeps the def matching the requested id instead of the first one seen.
  • dependency-resolver: new fallback md/mdx import detector, so .docs.mdx imports are detected even when the mdx aspect isn't loaded (latent gap once mdx is no longer core - without it, docs deps silently drop from dependency computation and preview bundling fails).
  • builder: Module._extensions require hooks are restored after each build task. An in-process tester leaves @babel/register's pirates hook installed; the hook claims all .js files (including node_modules, regardless of babel ignore config) and breaks require() of ESM-only packages in every later task in the process (pirates drops the format arg node >=22.12 uses to route require(esm)).
  • preview: pre-bundle loads the mdx options via a native import() instead of a top-level require, immune to the same stale-hook hazard.
  • e2e: fixture env extensions marked @bit-no-check; timings manifest covers the split spec files so shard balancing accounts for the heavier env-install suites.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Load former core envs as regular registry envs with legacy version pinning

✨ Enhancement 🐞 Bug fix 🕐 40+ Minutes

Grey Divider

AI Description

• Remove env aspects from core manifest; load them as regular, versioned env components.
• Add legacy core-env mapping to pin versions and auto-install missing packages.
• Prevent recursion/stack overflows in aspect/env loading and graph traversal paths.
Diagram

graph TD
  A["Component env id (may be versionless)"] --> C["EnvsMain (env resolution)"] --> D["Aspects loaders (ws/scope)"] --> E["InstallMain (workspace policy)"] --> F["Registry packages (@teambit/*)"]
  C --> B["legacy-core-envs.ts (pinned versions)"] --> D
  C --> G["Fallback TS compiler"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Migrate stored component env ids to include versions
  • ➕ Eliminates ongoing special-casing for versionless ids
  • ➕ Makes resolution/slot lookups simpler and more consistent
  • ➖ Mutates historical objects/models (explicitly avoided by this PR)
  • ➖ Requires migration tooling and careful rollout across scopes/workspaces
2. Resolve legacy envs to a semver range (e.g. ^1.x) instead of pinned
  • ➕ Reduces maintenance of pinned versions
  • ➕ Allows automatic uptake of compatible env fixes
  • ➖ Less deterministic; can break builds when env behavior changes
  • ➖ Harder to reproduce old snapshots and debug regressions
3. Keep env aspects in core manifest but lazy-load/bundle-split
  • ➕ Avoids registry dependency for default/basic envs
  • ➕ Minimizes behavior change in resolution codepaths
  • ➖ Does not achieve the same binary/core slimming goal
  • ➖ Still couples env release cadence to core distribution

Recommendation: The PR’s approach (treat former core envs as regular external envs, while preserving backward compatibility via a non-mutating legacy-id resolver + pinned versions) is the best tradeoff for slimming the core without breaking old components. The main follow-up to ensure long-term health is to formalize the pinned-version bump as part of the release workflow (as noted in the PR description) and consider adding a small regression test matrix around versionless legacy env ids + fallback-default-env behavior.

Files changed (15) +503 / -74

Enhancement (7) +352 / -30
environments.main.runtime.tsAdd legacy core env compatibility and fallback default env +153/-25

Add legacy core env compatibility and fallback default env

• Introduces legacy-core-env detection, slot lookups that ignore version, and special handling for versionless legacy ids. Adds a minimal fallback default env (with TS transpiler) to keep commands working before env installation and prevents self-referential env component loading loops.

scopes/envs/envs/environments.main.runtime.ts

fallback-typescript-compiler.tsAdd minimal transpile-only TypeScript compiler for fallback env +43/-0

Add minimal transpile-only TypeScript compiler for fallback env

• Implements a lightweight TypeScript transpiler (no type-checking) used by the fallback default env to produce requirable dists in capsules when the real env is not installed/loaded yet.

scopes/envs/envs/fallback-typescript-compiler.ts

index.tsExport legacy core env utilities from envs public API +7/-0

Export legacy core env utilities from envs public API

• Re-exports helper functions for legacy core env identification, pinning, package naming, and id resolution so workspace/scope/install hosts can share the same compatibility logic.

scopes/envs/envs/index.ts

legacy-core-envs.tsDefine pinned versions and helpers for legacy core env ids +59/-0

Define pinned versions and helpers for legacy core env ids

• Adds a central mapping from legacy core env ids to pinned versions plus helpers to resolve versionless ids and derive registry package names. Includes a list of older removed env ids to suppress invalid-config errors even without a pinned package.

scopes/envs/envs/legacy-core-envs.ts

scope-aspects-loader.tsNormalize legacy core env ids to pinned versions in scope loading +10/-1

Normalize legacy core env ids to pinned versions in scope loading

• Resolves versionless legacy core env ids to pinned versions before importing/loading, enabling external env loading from registry. Improves core-aspect filtering to exclude core aspects even when requested with versions (dependency-induced).

scopes/scope/scope/scope-aspects-loader.ts

install.main.runtime.tsAuto-install legacy core env packages via workspace policy pinning +42/-1

Auto-install legacy core env packages via workspace policy pinning

• Adds legacy core envs used by components (without versions) to the workspace policy using pinned versions and derived @teambit/* package names. Extends missing-env package resolution to install pinned legacy env packages when env ids are versionless and not in workspace.

scopes/workspace/install/install.main.runtime.ts

workspace-component-loader.tsEnsure legacy core env extensions and default env participate in load groups +38/-3

Ensure legacy core env extensions and default env participate in load groups

• Collects name-only legacy env extensions so they are resolved and loaded before dependent components, and ensures DEFAULT_ENV is included for components without explicit env configuration. Treats legacy core env components as env aspects even when env-data is computed via fallback env.

scopes/workspace/workspace/workspace-component/workspace-component-loader.ts

Bug fix (5) +149 / -23
dev-files.main.runtime.tsSkip env manifest detection for legacy core env ids +3/-0

Skip env manifest detection for legacy core env ids

• Avoids fetching legacy core env components solely to look for env.jsonc, since old-style envs intentionally lack it. Keeps core/legacy envs out of dev-files env-manifest logic for faster/safer resolution.

scopes/component/dev-files/dev-files.main.runtime.ts

dependency-resolver.main.runtime.tsHarden env-root module resolution and legacy env policy handling +19/-4

Harden env-root module resolution and legacy env policy handling

• Guards getPackageDirInEnvRoot against cases where component env cannot be determined, falling back to root node_modules. Extends legacy peer-policy inclusion and env.jsonc detection to treat legacy core envs like core envs (no env.jsonc fetch).

scopes/dependencies/dependency-resolver/dependency-resolver.main.runtime.ts

aspect-loader.main.runtime.tsAvoid mutating shared core manifests when requiring aspects +7/-0

Avoid mutating shared core manifests when requiring aspects

• Prevents overriding manifest.id when require() resolves to a core aspect module, avoiding shared-object mutation that can break core aspect resolution (e.g. accidentally searching core ids with a version suffix).

scopes/harmony/aspect-loader/aspect-loader.main.runtime.ts

workspace-aspects-loader.tsLoad legacy envs reliably and guard against circular/aspect-graph recursion +93/-11

Load legacy envs reliably and guard against circular/aspect-graph recursion

• Adds versionless-legacy env matching when checking whether aspects are already loaded, resolves pinned versions for non-workspace legacy envs, and includes resolved ids as seeders to prevent manifest filtering bugs. Introduces in-flight load tracking to break circular env chains and replaces recursive predecessor traversal with safer inEdges-based logic to avoid stack overflows on large graphs.

scopes/workspace/workspace/workspace-aspects-loader.ts

workspace.tsTrack in-flight aspect loads and avoid recursive dependent traversal +27/-8

Track in-flight aspect loads and avoid recursive dependent traversal

• Adds a workspace-level inFlightAspectsLoads set used to prevent circular env/aspect load chains. Reworks getDependentsIds to iterative traversal to avoid maximum call stack errors, and skips misconfigured-env warnings for legacy core env ids.

scopes/workspace/workspace/workspace.ts

Refactor (1) +1 / -2
ui.main.runtime.tsDrop unused AspectMain dependency from UI deps tuple +1/-2

Drop unused AspectMain dependency from UI deps tuple

• Simplifies UI aspect dependency typing by removing an unused AspectMain type from UIDeps.

scopes/ui-foundation/ui/ui.main.runtime.ts

Tests (1) +1 / -7
core-aspects-ids.jsonUpdate core aspect id list to exclude former core envs +1/-7

Update core aspect id list to exclude former core envs

• Removes env aspect ids from the core-aspects test fixture list to reflect the slimmer core manifest set.

scopes/harmony/testing/load-aspect/core-aspects-ids.json

Other (1) +0 / -12
manifests.tsRemove env aspects from core manifests map +0/-12

Remove env aspects from core manifests map

• Stops bundling former core env aspects (node/react/mdx/readme/env/aspect-related) as core manifests, aligning with the new model where they are installed and loaded as regular env components.

scopes/harmony/bit/manifests.ts

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jul 2, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Stale installed-aspect cache 🐞 Bug ☼ Reliability
Description
getInstalledAspectResolver() now suppresses errors for non-requested dependency aspects
(throwOnError gated by requestedIds), but resolveInstalledAspectRecursively memoizes failures as
null and returns the cached null on later attempts. If an env/aspect package becomes available
later in the same process (e.g. during multi-cycle bit install), the loader won’t retry resolution
and the aspect can remain unresolved until cache invalidation/restart.
Code

scopes/workspace/workspace/workspace-aspects-loader.ts[R834-836]

+      const localPath = await this.resolveInstalledAspectRecursively(component, rootIds, graph, {
+        throwOnError: opts.throwOnError && isRequested,
+      });
Evidence
The new requestedIds gating makes resolution failures for dependency aspects non-fatal, allowing
them to flow into the negative-cache (null) write; later attempts short-circuit on the cache and
do not retry. Workspace cache clearing does not clear this map, so the stale negative result can
persist within the same process even after packages become available.

scopes/workspace/workspace/workspace-aspects-loader.ts[823-868]
scopes/workspace/workspace/workspace-aspects-loader.ts[925-933]
scopes/workspace/workspace/workspace.ts[871-890]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`WorkspaceAspectsLoader.resolveInstalledAspectRecursively()` caches failed resolutions as `null` in `resolvedInstalledAspects`. This PR also introduces a path where dependency aspects are resolved with `throwOnError: false` (based on `requestedIds`), so transient resolution failures during install can be cached and then never retried after packages are installed in the same process.
## Issue Context
- The installed-aspect resolver memoizes both successes and failures.
- Workspace cache clearing (`workspace.clearCache`) does not clear `resolvedInstalledAspects`.
- During `bit install` (and other multi-stage flows), aspects may become resolvable after `node_modules` changes, but the loader will still return cached `null`.
## Fix Focus Areas
- Add an explicit invalidation method on `WorkspaceAspectsLoader` (e.g. `clearResolvedInstalledAspectsCache()`), and call it from `Workspace.clearCache()` (and/or other places that mutate/refresh node_modules, such as post-install hooks).
- Alternatively, avoid caching `null` (or cache it only for the duration of a single load trace), so subsequent attempts can re-resolve after installation.
### Code references
- scopes/workspace/workspace/workspace-aspects-loader.ts[823-937]
- scopes/workspace/workspace/workspace.ts[871-890]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Empty-env main points dist 🐞 Bug ≡ Correctness
Description
NodeModuleLinker.createPackageJson rewrites TS/TSX mains to dist/*.js unless it can positively
detect teambit.harmony/empty-env, but linkToNodeModulesByIds loads components with
loadExtensions: false so empty-env components with no explicit env config can be misdetected and
get a non-existent dist main. This breaks requiring/importing such components from node_modules
because only source files are linked and empty-env has no compiler to generate dists.
Code

scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[R280-283]

+    if (
+      !isCompilerLessEnv &&
+      typeof mainFile === 'string' &&
+      /\.(ts|tsx|jsx|mts|cts)$/.test(mainFile) &&
Evidence
The linking path explicitly loads components with extensions disabled, then calls createPackageJson,
which uses envs extension data/config to decide whether to rewrite main. For default empty-env
components without explicit env config, both values can be absent, so isCompilerLessEnv becomes
false and main is rewritten to dist/..., but the linker only symlinks bitmap/source files and
empty-env is defined to provide no compiler/dists.

scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[368-383]
scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[49-101]
scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[160-167]
scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[169-177]
scopes/envs/envs/environments.main.runtime.ts[112-115]
scopes/harmony/empty-env/empty-env.main.runtime.ts[8-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`NodeModuleLinker.createPackageJson()` rewrites a TS/TSX/etc `main` field to `dist/<main>.js` unless `isCompilerLessEnv` is true. In the `linkToNodeModulesByIds()` flow, components are loaded with `loadExtensions: false`, so `envsExt.data.id` is typically unset; for components that rely on the new default env (empty-env) and have no explicit env config, `configuredEnvId` is also unset, making `isCompilerLessEnv` false and forcing a dist main that will never exist under empty-env.
### Issue Context
This breaks consumers that import these linked packages via node resolution because the linker symlinks only bitmap/source files (not compiled `dist`), and empty-env intentionally has no compiler/dists.
### How to fix
1. Treat “no env configured / env data missing” as empty-env in this linking path (since `DEFAULT_ENV` is now `teambit.harmony/empty-env`).
2. Keep the existing safety behavior when empty-env is only a fallback for a *configured* non-empty env (i.e., if `configuredEnvId` exists and is not empty-env, do **not** treat it as compiler-less).
A concrete approach:
- Compute an `effectiveConfiguredEnvId = envsExt?.config?.env?.split('@')[0] ?? 'teambit.harmony/empty-env'`.
- Compute `effectiveDataEnvId = envsExt?.data?.id?.split('@')[0]`.
- Set `isCompilerLessEnv = (effectiveConfiguredEnvId === 'teambit.harmony/empty-env') && ((effectiveDataEnvId ?? effectiveConfiguredEnvId) === 'teambit.harmony/empty-env')`.
- Only rewrite `main` when `!isCompilerLessEnv`.
### Fix Focus Areas
- scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[263-287]
- scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[368-383]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Versioned env lookup fails ✓ Resolved 🐞 Bug ≡ Correctness
Description
EnvsMain.getEnvDefinitionById() can no longer resolve a versioned env ID to an env registered in the
slot under its versionless ID, because getEnvDefinitionByStringId() only performs legacy-core
fallbacks for versionless IDs. This can cause env resolution to fail (and fall back to default env /
warnings) in flows where aspect-entry IDs become versioned (e.g. during tag) while the env slot
entry remains versionless.
Code

scopes/envs/envs/environments.main.runtime.ts[R1209-1212]

+    if (!envId.includes('@')) {
+      // versionless references hit the slot only for legacy core envs, which old components store
+      // without a version by design while the loaded env registers versioned. any other env must
+      // be looked up with its exact version: two components in the same workspace may use the
Evidence
calculateEnv() explicitly relies on getEnvDefinitionById(matchedEntry.id) because aspect-entry
IDs can change versions during tag and not match the env slot registration; with the new
getEnvDefinitionByStringId() behavior, that lookup can no longer succeed when the slot key is
versionless. Additionally, isEnvRegistered() documents/implements that versioned IDs should match
a versionless slot entry, but getEnvDefinitionByStringId() does not provide the analogous fallback
for env definition retrieval.

scopes/envs/envs/environments.main.runtime.ts[847-864]
scopes/envs/envs/environments.main.runtime.ts[1197-1219]
scopes/envs/envs/environments.main.runtime.ts[1247-1253]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`EnvsMain.getEnvDefinitionById()` calls `getEnvDefinitionByStringId(id.toString())` and then `getEnvDefinitionByStringId(id.toString({ ignoreVersion: true }))`. After this PR, `getEnvDefinitionByStringId()` only performs a special lookup for *versionless* IDs (and only for legacy core envs). This means a **versioned** ID (e.g. `my-scope/my-env@1.0.0`) will not match an env that is registered in the slot under `my-scope/my-env`.
This breaks env resolution in scenarios explicitly documented in `calculateEnv()` where aspect-entry IDs can become versioned during tag even though the slot registration isn’t.
### Issue Context
The code already acknowledges that versioned IDs should match versionless slot entries (see `isEnvRegistered()`), but `getEnvDefinitionById()` / `getEnvDefinitionByStringId()` do not implement the same matching behavior.
### Fix Focus Areas
- scopes/envs/envs/environments.main.runtime.ts[1197-1220]
### Suggested fix
Implement a safe fallback for **versioned -> versionless** lookup when the exact lookup misses:
- In `getEnvDefinitionById()` (preferred):
- After failing exact match, try `id.toStringWithoutVersion()` **only if** `this.envSlot.get(id.toStringWithoutVersion())` exists, and return that `EnvDefinition`.
- Or in `getEnvDefinitionByStringId()`:
- If `envId.includes('@')` and `this.envSlot.get(envId.split('@')[0])` exists, return that.
This preserves the PR’s intent of avoiding ambiguous ignore-version scans across multiple versions, while still supporting the explicit versionless-slot contract used by core/workspace envs.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Install masks env errors 🐞 Bug ☼ Reliability
Description
InstallMain.reloadOneAspectsGroup() treats broad module-not-found failures from an env/aspect
runtime provider as “not requirable yet” and only warns, so installs can succeed even when a
non-workspace env is genuinely broken (e.g. missing runtime dependency). This can leave the
workspace in a misleading “installed” state until later commands fail when the env is actually
needed.
Code

scopes/workspace/install/install.main.runtime.ts[R759-762]

+            err.code === 'ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING' ||
+            err.code === 'ERR_REQUIRE_ESM' ||
+            err.message?.includes('Cannot find module') ||
+            // a CJS dist evaluated as ESM (or vice versa) - happens when the package manager
Evidence
The provider call catches and suppresses MODULE_NOT_FOUND/Cannot find module errors and only
logs a warning, which can hide real runtime failures. The same reload path is invoked for aspects in
the scope group (not in the workspace), so this suppression applies to external envs/aspects too,
where such errors are not expected to be transient compilation gaps.

scopes/workspace/install/install.main.runtime.ts[744-770]
scopes/workspace/install/install.main.runtime.ts[787-799]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`InstallMain.reloadOneAspectsGroup()` suppresses provider failures for *all* reloaded aspects/envs when the error looks like module-not-found. This suppression is appropriate only for workspace aspects that may not be compiled yet during early install cycles; for non-workspace aspects (coming from scope/node_modules) a `MODULE_NOT_FOUND`/`Cannot find module` typically indicates a real broken env that should fail the install.
## Issue Context
The grouping logic explicitly creates a `scope` group for aspects not in the workspace, but the provider error suppression does not differentiate between workspace and non-workspace groups.
## Fix Focus Areas
- scopes/workspace/install/install.main.runtime.ts[744-772]
- scopes/workspace/install/install.main.runtime.ts[787-799]
## Suggested fix
1. Carry enough context into the `loadedPlugins` entries (e.g. `{ id, plugins, isWorkspace: group.workspace }`, optionally `localPath`).
2. In the provider `catch`, only apply the "not requirable yet" suppression when `group.workspace === true` (and possibly `group.envOfAspect === true`).
3. For `group.workspace === false` (scope/node_modules aspects), rethrow `MODULE_NOT_FOUND` / `Cannot find module` errors so `bit install` fails fast with an actionable message.
4. (Optional) If you still need some tolerance for non-workspace aspects, narrow it to very specific transient cases (e.g. ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING for a TS main) rather than the generic "Cannot find module" substring.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Hardcoded dist main path 🐞 Bug ≡ Correctness
Description
WorkspaceAspectsLoader.getDistMain() hardcodes the compiled main lookup to /dist/..., so the
Node 22 fallback can fail to find an existing compiled entry when an aspect/env compiler outputs to
a different distDir. In that case, requiring the aspect still fails even though compiled JS
exists, preventing aspect/env loading.
Code

scopes/workspace/workspace/workspace-aspects-loader.ts[R724-727]

+    const mainFile = component.state._consumer.mainFile;
+    if (!mainFile) return undefined;
+    const distMain = join(localPath, DEFAULT_DIST_DIRNAME, mainFile.replace(/\.(ts|tsx|mts|cts|jsx)$/, '.js'));
+    return fs.pathExistsSync(distMain) ? distMain : undefined;
Evidence
The fallback require path is computed only as /dist/.js and does not consult the component
compiler’s getDistPathBySrcPath(), even though the compiler contract supports arbitrary distDir
and path mapping. The aspect-loader already demonstrates the correct approach (use
getDistPathBySrcPath() when a compiler exists), so this omission can cause fallback loading to
miss the actual compiled output location.

scopes/workspace/workspace/workspace-aspects-loader.ts[679-727]
scopes/harmony/aspect-loader/aspect-loader.main.runtime.ts[173-224]
scopes/compilation/compiler/types.ts[45-49]
scopes/compilation/compiler/types.ts[119-123]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`WorkspaceAspectsLoader.getDistMain()` builds a fallback path using `DEFAULT_DIST_DIRNAME` (`dist`) and `mainFile.replace(... => .js)`. This ignores the compiler’s `getDistPathBySrcPath()` mapping, so aspects compiled to a non-`dist/` output directory won’t be found and the fallback require will still fail.
## Issue Context
`AspectLoaderMain.getAspectFilePath()` / `getRuntimePath()` already implement the correct pattern: try to obtain the component compiler and call `compiler.getDistPathBySrcPath(srcRelativePath)`, falling back to `DEFAULT_DIST_DIRNAME` only when the compiler isn't available.
## Fix Focus Areas
- scopes/workspace/workspace/workspace-aspects-loader.ts[679-728]
- scopes/harmony/aspect-loader/aspect-loader.main.runtime.ts[173-224]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Previous review results

Review updated until commit 9720e5c ⚖️ Balanced

Results up to commit 4eeff58


🐞 Bugs (4) 📘 Rule violations (0) 📜 Skill insights (0)


Action required
1. Stale installed-aspect cache 🐞 Bug ☼ Reliability ⭐ New
Description
getInstalledAspectResolver() now suppresses errors for non-requested dependency aspects
(throwOnError gated by requestedIds), but resolveInstalledAspectRecursively memoizes failures as
null and returns the cached null on later attempts. If an env/aspect package becomes available
later in the same process (e.g. during multi-cycle bit install), the loader won’t retry resolution
and the aspect can remain unresolved until cache invalidation/restart.
Code

scopes/workspace/workspace/workspace-aspects-loader.ts[R834-836]

+      const localPath = await this.resolveInstalledAspectRecursively(component, rootIds, graph, {
+        throwOnError: opts.throwOnError && isRequested,
+      });
Evidence
The new requestedIds gating makes resolution failures for dependency aspects non-fatal, allowing
them to flow into the negative-cache (null) write; later attempts short-circuit on the cache and
do not retry. Workspace cache clearing does not clear this map, so the stale negative result can
persist within the same process even after packages become available.

scopes/workspace/workspace/workspace-aspects-loader.ts[823-868]
scopes/workspace/workspace/workspace-aspects-loader.ts[925-933]
scopes/workspace/workspace/workspace.ts[871-890]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`WorkspaceAspectsLoader.resolveInstalledAspectRecursively()` caches failed resolutions as `null` in `resolvedInstalledAspects`. This PR also introduces a path where dependency aspects are resolved with `throwOnError: false` (based on `requestedIds`), so transient resolution failures during install can be cached and then never retried after packages are installed in the same process.

## Issue Context
- The installed-aspect resolver memoizes both successes and failures.
- Workspace cache clearing (`workspace.clearCache`) does not clear `resolvedInstalledAspects`.
- During `bit install` (and other multi-stage flows), aspects may become resolvable after `node_modules` changes, but the loader will still return cached `null`.

## Fix Focus Areas
- Add an explicit invalidation method on `WorkspaceAspectsLoader` (e.g. `clearResolvedInstalledAspectsCache()`), and call it from `Workspace.clearCache()` (and/or other places that mutate/refresh node_modules, such as post-install hooks).
- Alternatively, avoid caching `null` (or cache it only for the duration of a single load trace), so subsequent attempts can re-resolve after installation.

### Code references
- scopes/workspace/workspace/workspace-aspects-loader.ts[823-937]
- scopes/workspace/workspace/workspace.ts[871-890]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Empty-env main points dist 🐞 Bug ≡ Correctness
Description
NodeModuleLinker.createPackageJson rewrites TS/TSX mains to dist/*.js unless it can positively
detect teambit.harmony/empty-env, but linkToNodeModulesByIds loads components with
loadExtensions: false so empty-env components with no explicit env config can be misdetected and
get a non-existent dist main. This breaks requiring/importing such components from node_modules
because only source files are linked and empty-env has no compiler to generate dists.
Code

scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[R280-283]

+    if (
+      !isCompilerLessEnv &&
+      typeof mainFile === 'string' &&
+      /\.(ts|tsx|jsx|mts|cts)$/.test(mainFile) &&
Evidence
The linking path explicitly loads components with extensions disabled, then calls createPackageJson,
which uses envs extension data/config to decide whether to rewrite main. For default empty-env
components without explicit env config, both values can be absent, so isCompilerLessEnv becomes
false and main is rewritten to dist/..., but the linker only symlinks bitmap/source files and
empty-env is defined to provide no compiler/dists.

scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[368-383]
scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[49-101]
scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[160-167]
scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[169-177]
scopes/envs/envs/environments.main.runtime.ts[112-115]
scopes/harmony/empty-env/empty-env.main.runtime.ts[8-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`NodeModuleLinker.createPackageJson()` rewrites a TS/TSX/etc `main` field to `dist/<main>.js` unless `isCompilerLessEnv` is true. In the `linkToNodeModulesByIds()` flow, components are loaded with `loadExtensions: false`, so `envsExt.data.id` is typically unset; for components that rely on the new default env (empty-env) and have no explicit env config, `configuredEnvId` is also unset, making `isCompilerLessEnv` false and forcing a dist main that will never exist under empty-env.
### Issue Context
This breaks consumers that import these linked packages via node resolution because the linker symlinks only bitmap/source files (not compiled `dist`), and empty-env intentionally has no compiler/dists.
### How to fix
1. Treat “no env configured / env data missing” as empty-env in this linking path (since `DEFAULT_ENV` is now `teambit.harmony/empty-env`).
2. Keep the existing safety behavior when empty-env is only a fallback for a *configured* non-empty env (i.e., if `configuredEnvId` exists and is not empty-env, do **not** treat it as compiler-less).
A concrete approach:
- Compute an `effectiveConfiguredEnvId = envsExt?.config?.env?.split('@')[0] ?? 'teambit.harmony/empty-env'`.
- Compute `effectiveDataEnvId = envsExt?.data?.id?.split('@')[0]`.
- Set `isCompilerLessEnv = (effectiveConfiguredEnvId === 'teambit.harmony/empty-env') && ((effectiveDataEnvId ?? effectiveConfiguredEnvId) === 'teambit.harmony/empty-env')`.
- Only rewrite `main` when `!isCompilerLessEnv`.
### Fix Focus Areas
- scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[263-287]
- scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[368-383]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Versioned env lookup fails ✓ Resolved 🐞 Bug ≡ Correctness
Description
EnvsMain.getEnvDefinitionById() can no longer resolve a versioned env ID to an env registered in the
slot under its versionless ID, because getEnvDefinitionByStringId() only performs legacy-core
fallbacks for versionless IDs. This can cause env resolution to fail (and fall back to default env /
warnings) in flows where aspect-entry IDs become versioned (e.g. during tag) while the env slot
entry remains versionless.
Code

scopes/envs/envs/environments.main.runtime.ts[R1209-1212]

+    if (!envId.includes('@')) {
+      // versionless references hit the slot only for legacy core envs, which old components store
+      // without a version by design while the loaded env registers versioned. any other env must
+      // be looked up with its exact version: two components in the same workspace may use the
Evidence
calculateEnv() explicitly relies on getEnvDefinitionById(matchedEntry.id) because aspect-entry
IDs can change versions during tag and not match the env slot registration; with the new
getEnvDefinitionByStringId() behavior, that lookup can no longer succeed when the slot key is
versionless. Additionally, isEnvRegistered() documents/implements that versioned IDs should match
a versionless slot entry, but getEnvDefinitionByStringId() does not provide the analogous fallback
for env definition retrieval.

scopes/envs/envs/environments.main.runtime.ts[847-864]
scopes/envs/envs/environments.main.runtime.ts[1197-1219]
scopes/envs/envs/environments.main.runtime.ts[1247-1253]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`EnvsMain.getEnvDefinitionById()` calls `getEnvDefinitionByStringId(id.toString())` and then `getEnvDefinitionByStringId(id.toString({ ignoreVersion: true }))`. After this PR, `getEnvDefinitionByStringId()` only performs a special lookup for *versionless* IDs (and only for legacy core envs). This means a **versioned** ID (e.g. `my-scope/my-env@1.0.0`) will not match an env that is registered in the slot under `my-scope/my-env`.
This breaks env resolution in scenarios explicitly documented in `calculateEnv()` where aspect-entry IDs can become versioned during tag even though the slot registration isn’t.
### Issue Context
The code already acknowledges that versioned IDs should match versionless slot entries (see `isEnvRegistered()`), but `getEnvDefinitionById()` / `getEnvDefinitionByStringId()` do not implement the same matching behavior.
### Fix Focus Areas
- scopes/envs/envs/environments.main.runtime.ts[1197-1220]
### Suggested fix
Implement a safe fallback for **versioned -> versionless** lookup when the exact lookup misses:
- In `getEnvDefinitionById()` (preferred):
- After failing exact match, try `id.toStringWithoutVersion()` **only if** `this.envSlot.get(id.toStringWithoutVersion())` exists, and return that `EnvDefinition`.
- Or in `getEnvDefinitionByStringId()`:
- If `envId.includes('@')` and `this.envSlot.get(envId.split('@')[0])` exists, return that.
This preserves the PR’s intent of avoiding ambiguous ignore-version scans across multiple versions, while still supporting the explicit versionless-slot contract used by core/workspace envs.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
4. Install masks env errors 🐞 Bug ☼ Reliability
Description
InstallMain.reloadOneAspectsGroup() treats broad module-not-found failures from an env/aspect
runtime provider as “not requirable yet” and only warns, so installs can succeed even when a
non-workspace env is genuinely broken (e.g. missing runtime dependency). This can leave the
workspace in a misleading “installed” state until later commands fail when the env is actually
needed.
Code

scopes/workspace/install/install.main.runtime.ts[R759-762]

+            err.code === 'ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING' ||
+            err.code === 'ERR_REQUIRE_ESM' ||
+            err.message?.includes('Cannot find module') ||
+            // a CJS dist evaluated as ESM (or vice versa) - happens when the package manager
Evidence
The provider call catches and suppresses MODULE_NOT_FOUND/Cannot find module errors and only
logs a warning, which can hide real runtime failures. The same reload path is invoked for aspects in
the scope group (not in the workspace), so this suppression applies to external envs/aspects too,
where such errors are not expected to be transient compilation gaps.

scopes/workspace/install/install.main.runtime.ts[744-770]
scopes/workspace/install/install.main.runtime.ts[787-799]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`InstallMain.reloadOneAspectsGroup()` suppresses provider failures for *all* reloaded aspects/envs when the error looks like module-not-found. This suppression is appropriate only for workspace aspects that may not be compiled yet during early install cycles; for non-workspace aspects (coming from scope/node_modules) a `MODULE_NOT_FOUND`/`Cannot find module` typically indicates a real broken env that should fail the install.
## Issue Context
The grouping logic explicitly creates a `scope` group for aspects not in the workspace, but the provider error suppression does not differentiate between workspace and non-workspace groups.
## Fix Focus Areas
- scopes/workspace/install/install.main.runtime.ts[744-772]
- scopes/workspace/install/install.main.runtime.ts[787-799]
## Suggested fix
1. Carry enough context into the `loadedPlugins` entries (e.g. `{ id, plugins, isWorkspace: group.workspace }`, optionally `localPath`).
2. In the provider `catch`, only apply the "not requirable yet" suppression when `group.workspace === true` (and possibly `group.envOfAspect === true`).
3. For `group.workspace === false` (scope/node_modules aspects), rethrow `MODULE_NOT_FOUND` / `Cannot find module` errors so `bit install` fails fast with an actionable message.
4. (Optional) If you still need some tolerance for non-workspace aspects, narrow it to very specific transient cases (e.g. ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING for a TS main) rather than the generic "Cannot find module" substring.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Hardcoded dist main path 🐞 Bug ≡ Correctness
Description
WorkspaceAspectsLoader.getDistMain() hardcodes the compiled main lookup to /dist/..., so the
Node 22 fallback can fail to find an existing compiled entry when an aspect/env compiler outputs to
a different distDir. In that case, requiring the aspect still fails even though compiled JS
exists, preventing aspect/env loading.
Code

scopes/workspace/workspace/workspace-aspects-loader.ts[R724-727]

+    const mainFile = component.state._consumer.mainFile;
+    if (!mainFile) return undefined;
+    const distMain = join(localPath, DEFAULT_DIST_DIRNAME, mainFile.replace(/\.(ts|tsx|mts|cts|jsx)$/, '.js'));
+    return fs.pathExistsSync(distMain) ? distMain : undefined;
Evidence
The fallback require path is computed only as /dist/.js and does not consult the component
compiler’s getDistPathBySrcPath(), even though the compiler contract supports arbitrary distDir
and path mapping. The aspect-loader already demonstrates the correct approach (use
getDistPathBySrcPath() when a compiler exists), so this omission can cause fallback loading to
miss the actual compiled output location.

scopes/workspace/workspace/workspace-aspects-loader.ts[679-727]
scopes/harmony/aspect-loader/aspect-loader.main.runtime.ts[173-224]
scopes/compilation/compiler/types.ts[45-49]
scopes/compilation/compiler/types.ts[119-123]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`WorkspaceAspectsLoader.getDistMain()` builds a fallback path using `DEFAULT_DIST_DIRNAME` (`dist`) and `mainFile.replace(... => .js)`. This ignores the compiler’s `getDistPathBySrcPath()` mapping, so aspects compiled to a non-`dist/` output directory won’t be found and the fallback require will still fail.
## Issue Context
`AspectLoaderMain.getAspectFilePath()` / `getRuntimePath()` already implement the correct pattern: try to obtain the component compiler and call `compiler.getDistPathBySrcPath(srcRelativePath)`, falling back to `DEFAULT_DIST_DIRNAME` only when the compiler isn't available.
## Fix Focus Areas
- scopes/workspace/workspace/workspace-aspects-loader.ts[679-728]
- scopes/harmony/aspect-loader/aspect-loader.main.runtime.ts[173-224]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Results up to commit 1e4e994


🐞 Bugs (3) 📘 Rule violations (0) 📜 Skill insights (0)


Action required
1. Empty-env main points dist 🐞 Bug ≡ Correctness
Description
NodeModuleLinker.createPackageJson rewrites TS/TSX mains to dist/*.js unless it can positively
detect teambit.harmony/empty-env, but linkToNodeModulesByIds loads components with
loadExtensions: false so empty-env components with no explicit env config can be misdetected and
get a non-existent dist main. This breaks requiring/importing such components from node_modules
because only source files are linked and empty-env has no compiler to generate dists.
Code

scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[R280-283]

+    if (
+      !isCompilerLessEnv &&
+      typeof mainFile === 'string' &&
+      /\.(ts|tsx|jsx|mts|cts)$/.test(mainFile) &&
Evidence
The linking path explicitly loads components with extensions disabled, then calls createPackageJson,
which uses envs extension data/config to decide whether to rewrite main. For default empty-env
components without explicit env config, both values can be absent, so isCompilerLessEnv becomes
false and main is rewritten to dist/..., but the linker only symlinks bitmap/source files and
empty-env is defined to provide no compiler/dists.

scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[368-383]
scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[49-101]
scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[160-167]
scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[169-177]
scopes/envs/envs/environments.main.runtime.ts[112-115]
scopes/harmony/empty-env/empty-env.main.runtime.ts[8-16]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`NodeModuleLinker.createPackageJson()` rewrites a TS/TSX/etc `main` field to `dist/<main>.js` unless `isCompilerLessEnv` is true. In the `linkToNodeModulesByIds()` flow, components are loaded with `loadExtensions: false`, so `envsExt.data.id` is typically unset; for components that rely on the new default env (empty-env) and have no explicit env config, `configuredEnvId` is also unset, making `isCompilerLessEnv` false and forcing a dist main that will never exist under empty-env.
### Issue Context
This breaks consumers that import these linked packages via node resolution because the linker symlinks only bitmap/source files (not compiled `dist`), and empty-env intentionally has no compiler/dists.
### How to fix
1. Treat “no env configured / env data missing” as empty-env in this linking path (since `DEFAULT_ENV` is now `teambit.harmony/empty-env`).
2. Keep the existing safety behavior when empty-env is only a fallback for a *configured* non-empty env (i.e., if `configuredEnvId` exists and is not empty-env, do **not** treat it as compiler-less).
A concrete approach:
- Compute an `effectiveConfiguredEnvId = envsExt?.config?.env?.split('@')[0] ?? 'teambit.harmony/empty-env'`.
- Compute `effectiveDataEnvId = envsExt?.data?.id?.split('@')[0]`.
- Set `isCompilerLessEnv = (effectiveConfiguredEnvId === 'teambit.harmony/empty-env') && ((effectiveDataEnvId ?? effectiveConfiguredEnvId) === 'teambit.harmony/empty-env')`.
- Only rewrite `main` when `!isCompilerLessEnv`.
### Fix Focus Areas
- scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[263-287]
- scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[368-383]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Versioned env lookup fails ✓ Resolved 🐞 Bug ≡ Correctness
Description
EnvsMain.getEnvDefinitionById() can no longer resolve a versioned env ID to an env registered in the
slot under its versionless ID, because getEnvDefinitionByStringId() only performs legacy-core
fallbacks for versionless IDs. This can cause env resolution to fail (and fall back to default env /
warnings) in flows where aspect-entry IDs become versioned (e.g. during tag) while the env slot
entry remains versionless.
Code

scopes/envs/envs/environments.main.runtime.ts[R1209-1212]

+    if (!envId.includes('@')) {
+      // versionless references hit the slot only for legacy core envs, which old components store
+      // without a version by design while the loaded env registers versioned. any other env must
+      // be looked up with its exact version: two components in the same workspace may use the
Evidence
calculateEnv() explicitly relies on getEnvDefinitionById(matchedEntry.id) because aspect-entry
IDs can change versions during tag and not match the env slot registration; with the new
getEnvDefinitionByStringId() behavior, that lookup can no longer succeed when the slot key is
versionless. Additionally, isEnvRegistered() documents/implements that versioned IDs should match
a versionless slot entry, but getEnvDefinitionByStringId() does not provide the analogous fallback
for env definition retrieval.

scopes/envs/envs/environments.main.runtime.ts[847-864]
scopes/envs/envs/environments.main.runtime.ts[1197-1219]
scopes/envs/envs/environments.main.runtime.ts[1247-1253]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`EnvsMain.getEnvDefinitionById()` calls `getEnvDefinitionByStringId(id.toString())` and then `getEnvDefinitionByStringId(id.toString({ ignoreVersion: true }))`. After this PR, `getEnvDefinitionByStringId()` only performs a special lookup for *versionless* IDs (and only for legacy core envs). This means a **versioned** ID (e.g. `my-scope/my-env@1.0.0`) will not match an env that is registered in the slot under `my-scope/my-env`.
This breaks env resolution in scenarios explicitly documented in `calculateEnv()` where aspect-entry IDs can become versioned during tag even though the slot registration isn’t.
### Issue Context
The code already acknowledges that versioned IDs should match versionless slot entries (see `isEnvRegistered()`), but `getEnvDefinitionById()` / `getEnvDefinitionByStringId()` do not implement the same matching behavior.
### Fix Focus Areas
- scopes/envs/envs/environments.main.runtime.ts[1197-1220]
### Suggested fix
Implement a safe fallback for **versioned -> versionless** lookup when the exact lookup misses:
- In `getEnvDefinitionById()` (preferred):
- After failing exact match, try `id.toStringWithoutVersion()` **only if** `this.envSlot.get(id.toStringWithoutVersion())` exists, and return that `EnvDefinition`.
- Or in `getEnvDefinitionByStringId()`:
- If `envId.includes('@')` and `this.envSlot.get(envId.split('@')[0])` exists, return that.
This preserves the PR’s intent of avoiding ambiguous ignore-version scans across multiple versions, while still supporting the explicit versionless-slot contract used by core/workspace envs.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended
3. Install masks env errors 🐞 Bug ☼ Reliability
Description
InstallMain.reloadOneAspectsGroup() treats broad module-not-found failures from an env/aspect
runtime provider as “not requirable yet” and only warns, so installs can succeed even when a
non-workspace env is genuinely broken (e.g. missing runtime dependency). This can leave the
workspace in a misleading “installed” state until later commands fail when the env is actually
needed.
Code

scopes/workspace/install/install.main.runtime.ts[R759-762]

+            err.code === 'ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING' ||
+            err.code === 'ERR_REQUIRE_ESM' ||
+            err.message?.includes('Cannot find module') ||
+            // a CJS dist evaluated as ESM (or vice versa) - happens when the package manager
Evidence
The provider call catches and suppresses MODULE_NOT_FOUND/Cannot find module errors and only
logs a warning, which can hide real runtime failures. The same reload path is invoked for aspects in
the scope group (not in the workspace), so this suppression applies to external envs/aspects too,
where such errors are not expected to be transient compilation gaps.

scopes/workspace/install/install.main.runtime.ts[744-770]
scopes/workspace/install/install.main.runtime.ts[787-799]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`InstallMain.reloadOneAspectsGroup()` suppresses provider failures for *all* reloaded aspects/envs when the error looks like module-not-found. This suppression is appropriate only for workspace aspects that may not be compiled yet during early install cycles; for non-workspace aspects (coming from scope/node_modules) a `MODULE_NOT_FOUND`/`Cannot find module` typically indicates a real broken env that should fail the install.
## Issue Context
The grouping logic explicitly creates a `scope` group for aspects not in the workspace, but the provider error suppression does not differentiate between workspace and non-workspace groups.
## Fix Focus Areas
- scopes/workspace/install/install.main.runtime.ts[744-772]
- scopes/workspace/install/install.main.runtime.ts[787-799]
## Suggested fix
1. Carry enough context into the `loadedPlugins` entries (e.g. `{ id, plugins, isWorkspace: group.workspace }`, optionally `localPath`).
2. In the provider `catch`, only apply the "not requirable yet" suppression when `group.workspace === true` (and possibly `group.envOfAspect === true`).
3. For `group.workspace === false` (scope/node_modules aspects), rethrow `MODULE_NOT_FOUND` / `Cannot find module` errors so `bit install` fails fast with an actionable message.
4. (Optional) If you still need some tolerance for non-workspace aspects, narrow it to very specific transient cases (e.g. ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING for a TS main) rather than the generic "Cannot find module" substring.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Hardcoded dist main path 🐞 Bug ≡ Correctness
Description
WorkspaceAspectsLoader.getDistMain() hardcodes the compiled main lookup to /dist/..., so the
Node 22 fallback can fail to find an existing compiled entry when an aspect/env compiler outputs to
a different distDir. In that case, requiring the aspect still fails even though compiled JS
exists, preventing aspect/env loading.
Code

scopes/workspace/workspace/workspace-aspects-loader.ts[R724-727]

+    const mainFile = component.state._consumer.mainFile;
+    if (!mainFile) return undefined;
+    const distMain = join(localPath, DEFAULT_DIST_DIRNAME, mainFile.replace(/\.(ts|tsx|mts|cts|jsx)$/, '.js'));
+    return fs.pathExistsSync(distMain) ? distMain : undefined;
Evidence
The fallback require path is computed only as /dist/.js and does not consult the component
compiler’s getDistPathBySrcPath(), even though the compiler contract supports arbitrary distDir
and path mapping. The aspect-loader already demonstrates the correct approach (use
getDistPathBySrcPath() when a compiler exists), so this omission can cause fallback loading to
miss the actual compiled output location.

scopes/workspace/workspace/workspace-aspects-loader.ts[679-727]
scopes/harmony/aspect-loader/aspect-loader.main.runtime.ts[173-224]
scopes/compilation/compiler/types.ts[45-49]
scopes/compilation/compiler/types.ts[119-123]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`WorkspaceAspectsLoader.getDistMain()` builds a fallback path using `DEFAULT_DIST_DIRNAME` (`dist`) and `mainFile.replace(... => .js)`. This ignores the compiler’s `getDistPathBySrcPath()` mapping, so aspects compiled to a non-`dist/` output directory won’t be found and the fallback require will still fail.
## Issue Context
`AspectLoaderMain.getAspectFilePath()` / `getRuntimePath()` already implement the correct pattern: try to obtain the component compiler and call `compiler.getDistPathBySrcPath(srcRelativePath)`, falling back to `DEFAULT_DIST_DIRNAME` only when the compiler isn't available.
## Fix Focus Areas
- scopes/workspace/workspace/workspace-aspects-loader.ts[679-728]
- scopes/harmony/aspect-loader/aspect-loader.main.runtime.ts[173-224]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Qodo Logo

Comment thread scopes/workspace/workspace/workspace.ts Outdated
Comment thread scopes/envs/envs/fallback-typescript-compiler.ts
Comment thread scopes/workspace/workspace/workspace-aspects-loader.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit c7dd1a7

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jul 2, 2026

Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

Comment thread scopes/workspace/workspace/workspace-aspects-loader.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit e6418b9

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit c9eca3d

Comment thread scopes/harmony/empty-env/empty-env.aspect.ts
Comment thread scopes/envs/envs/environments.main.runtime.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 3f5c24e

…nv templates on demand

- register legacy core env ids as core extension names so their config entries stay name-only
  (versionless): prevents env-as-dependency edges that created circular TS project references
  in lane/tag builds
- require the aspect env eslint/prettier configs lazily (saves ~400 file reads per bit command)
- bit create: fall back to --env for template lookup, incl. templates registered on the
  generator slot by envs loaded from the global scope
- rewrite e2e node-env fixtures to compose on the core aspect env instead of @teambit/node
Comment thread scopes/harmony/aspect/aspect.env.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 0607c7d

- teambit.harmony/aspect and teambit.envs/env become regular envs with pinned
  legacy versions. components using them get the exact released behavior after
  bit install (the react-free aspect env rewrite is reverted)
- move the bit-aspect template and harmony starters to the generator aspect so
  'bit create bit-aspect' and 'bit new' work without loading the env
- bind manifest deps of legacy envs to pinned versions in scope context (models
  built when these envs were core don't list them as dependencies)
- load the full manifest graph when loading aspects from the global scope
- keep legacy core env ids versionless when configured via bit create/env set
Comment thread scopes/workspace/workspace/workspace.ts
Comment thread components/legacy/e2e-helper/e2e-env-helper.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit b23b273

…ion when available

- fixes the ci snap failure: pinned-version copies of workspace components
  leaked into the load groups and into the snap list
- review fixes: index-based BFS queue in getDependentsIds, guard the
  typescript require in the fallback compiler, suppress legacy-env load
  failures only when the env package itself is missing, match both quote
  styles when detecting fixture env packages
Comment thread scopes/generator/generator/builtin-templates.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 94eddce

Comment thread scopes/compilation/compiler/compiler.main.runtime.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 86d1aed

…om-manifest

# Conflicts:
#	.bitmap
#	.circleci/config.yml
#	e2e/harmony/dependency-resolver.e2e.ts
#	pnpm-lock.yaml
#	scopes/envs/envs/environments.main.runtime.ts
#	scopes/harmony/aspect/babel/babel-config.ts
#	scopes/harmony/bit/manifests.ts
#	scopes/harmony/testing/load-aspect/core-aspects-ids.json
Comment on lines +142 to +147
const compsToImportDepsFor = useLazyImport
? components
: components.filter((comp) => workspaceIds.find((id) => id.isEqual(comp.id)));

const allDeps = (await Promise.all(compOnWorkspaceOnly.map(getDepsFunc))).flat();
const allDepsNotImported = allDeps.filter((d) => !this.importedIds.includes(d.toString()));
const allDeps = (await Promise.all(compsToImportDepsFor.map((c) => this.getAllDepsUnfiltered(c)))).flat();
const allDepsNotImported = allDeps.filter((d) => !this.importedIds.has(d.toString()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Lazy import contradicts docs 🐞 Bug ⚙ Maintainability

GraphFromFsBuilder.importObjects() documents that lazy mode "only import[s] filtered dependencies"
to avoid fetching huge trees, but the updated implementation always prefetches direct deps via
getAllDepsUnfiltered() when shouldLoadItsDeps is set. This mismatch can mislead maintainers and can
increase work when building aspects-only graphs (e.g. from
WorkspaceAspectsLoader.getAspectsGraphWithoutCore).
Agent Prompt
### Issue description
`GraphFromFsBuilder.importObjects()` states that in lazy-import mode it will "only import filtered dependencies", but the current implementation collects dependencies using `getAllDepsUnfiltered()` when `useLazyImport` is true. This contradicts the method’s own docstring and the intended purpose of `shouldLoadItsDeps` (building a filtered/aspects-only graph without pulling large dependency trees).

### Issue Context
This graph builder is used by `WorkspaceAspectsLoader.getAspectsGraphWithoutCore()` to build an aspects-only graph.

### Fix Focus Areas
- scopes/workspace/workspace/build-graph-from-fs.ts[121-147]

### What to change
Choose one of the following (either is acceptable, but it should be explicit):
1) **Update the docstring/comments** to reflect the actual behavior (lazy mode batch-prefetches *unfiltered direct deps* to avoid per-dep network round-trips caused by `shouldLoadItsDeps`), *or*
2) **Change the lazy-mode dep collection** to match the documentation (only prefetch deps that pass the `shouldLoadItsDeps` filter), and ensure this doesn’t regress performance (e.g., avoid per-dep remote fetches by batching where possible).

Add/adjust a regression test (if available for graph building) asserting the intended behavior for lazy mode with a `shouldLoadItsDeps` filter.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 53f7c45

…om-manifest

# Conflicts:
#	.bitmap
#	e2e/harmony/aspect.e2e.ts
#	e2e/harmony/create.e2e.ts
#	e2e/harmony/dependency-resolver.e2e.ts
#	e2e/harmony/root-components.e2e.ts
#	pnpm-lock.yaml
#	scopes/envs/envs/environments.main.runtime.ts
#	scopes/generator/generator/templates/aspect/files/aspect-file.ts
#	scopes/generator/generator/templates/aspect/files/config-file.ts
#	scopes/generator/generator/templates/aspect/files/index.ts
#	scopes/generator/generator/templates/aspect/files/main-runtime.ts
#	scopes/generator/generator/templates/aspect/index.ts
#	scopes/harmony/aspect/aspect.templates.ts
#	scripts/e2e-test-timings.json
Comment on lines +1209 to +1212
if (!envId.includes('@')) {
// versionless references hit the slot only for legacy core envs, which old components store
// without a version by design while the loaded env registers versioned. any other env must
// be looked up with its exact version: two components in the same workspace may use the

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Versioned env lookup fails 🐞 Bug ≡ Correctness

EnvsMain.getEnvDefinitionById() can no longer resolve a versioned env ID to an env registered in the
slot under its versionless ID, because getEnvDefinitionByStringId() only performs legacy-core
fallbacks for versionless IDs. This can cause env resolution to fail (and fall back to default env /
warnings) in flows where aspect-entry IDs become versioned (e.g. during tag) while the env slot
entry remains versionless.
Agent Prompt
### Issue description
`EnvsMain.getEnvDefinitionById()` calls `getEnvDefinitionByStringId(id.toString())` and then `getEnvDefinitionByStringId(id.toString({ ignoreVersion: true }))`. After this PR, `getEnvDefinitionByStringId()` only performs a special lookup for *versionless* IDs (and only for legacy core envs). This means a **versioned** ID (e.g. `my-scope/my-env@1.0.0`) will not match an env that is registered in the slot under `my-scope/my-env`.

This breaks env resolution in scenarios explicitly documented in `calculateEnv()` where aspect-entry IDs can become versioned during tag even though the slot registration isn’t.

### Issue Context
The code already acknowledges that versioned IDs should match versionless slot entries (see `isEnvRegistered()`), but `getEnvDefinitionById()` / `getEnvDefinitionByStringId()` do not implement the same matching behavior.

### Fix Focus Areas
- scopes/envs/envs/environments.main.runtime.ts[1197-1220]

### Suggested fix
Implement a safe fallback for **versioned -> versionless** lookup when the exact lookup misses:
- In `getEnvDefinitionById()` (preferred):
  - After failing exact match, try `id.toStringWithoutVersion()` **only if** `this.envSlot.get(id.toStringWithoutVersion())` exists, and return that `EnvDefinition`.
- Or in `getEnvDefinitionByStringId()`:
  - If `envId.includes('@')` and `this.envSlot.get(envId.split('@')[0])` exists, return that.

This preserves the PR’s intent of avoiding ambiguous ignore-version scans across multiple versions, while still supporting the explicit versionless-slot contract used by core/workspace envs.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 9e78b34

- setup_harmony: reconcile node_modules with a dev-binary install; the released
  bbit filters the legacy-core-env packages out of the manifest, leaving the dev
  binary unable to load those envs (workspace-status verification failed)
- e2e_test/e2e_test_bbit: bump to medium+; installs pulling a legacy env's full
  closure peak over 3GB and got OOM-killed on medium
- aspect.e2e.ts: install the aspect env before exercising load failures - on
  this branch the env is not core, so without the install the test hits an
  env-loading error instead of the one it verifies
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit c289284

Comment on lines +280 to +283
if (
!isCompilerLessEnv &&
typeof mainFile === 'string' &&
/\.(ts|tsx|jsx|mts|cts)$/.test(mainFile) &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Empty-env main points dist 🐞 Bug ≡ Correctness

NodeModuleLinker.createPackageJson rewrites TS/TSX mains to dist/*.js unless it can positively
detect teambit.harmony/empty-env, but linkToNodeModulesByIds loads components with
loadExtensions: false so empty-env components with no explicit env config can be misdetected and
get a non-existent dist main. This breaks requiring/importing such components from node_modules
because only source files are linked and empty-env has no compiler to generate dists.
Agent Prompt
### Issue description
`NodeModuleLinker.createPackageJson()` rewrites a TS/TSX/etc `main` field to `dist/<main>.js` unless `isCompilerLessEnv` is true. In the `linkToNodeModulesByIds()` flow, components are loaded with `loadExtensions: false`, so `envsExt.data.id` is typically unset; for components that rely on the new default env (empty-env) and have no explicit env config, `configuredEnvId` is also unset, making `isCompilerLessEnv` false and forcing a dist main that will never exist under empty-env.

### Issue Context
This breaks consumers that import these linked packages via node resolution because the linker symlinks only bitmap/source files (not compiled `dist`), and empty-env intentionally has no compiler/dists.

### How to fix
1. Treat “no env configured / env data missing” as empty-env in this linking path (since `DEFAULT_ENV` is now `teambit.harmony/empty-env`).
2. Keep the existing safety behavior when empty-env is only a fallback for a *configured* non-empty env (i.e., if `configuredEnvId` exists and is not empty-env, do **not** treat it as compiler-less).

A concrete approach:
- Compute an `effectiveConfiguredEnvId = envsExt?.config?.env?.split('@')[0] ?? 'teambit.harmony/empty-env'`.
- Compute `effectiveDataEnvId = envsExt?.data?.id?.split('@')[0]`.
- Set `isCompilerLessEnv = (effectiveConfiguredEnvId === 'teambit.harmony/empty-env') && ((effectiveDataEnvId ?? effectiveConfiguredEnvId) === 'teambit.harmony/empty-env')`.
- Only rewrite `main` when `!isCompilerLessEnv`.

### Fix Focus Areas
- scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[263-287]
- scopes/workspace/modules/node-modules-linker/node-modules-linker.ts[368-383]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit baa9c2b

Comment on lines +724 to +727
const mainFile = component.state._consumer.mainFile;
if (!mainFile) return undefined;
const distMain = join(localPath, DEFAULT_DIST_DIRNAME, mainFile.replace(/\.(ts|tsx|mts|cts|jsx)$/, '.js'));
return fs.pathExistsSync(distMain) ? distMain : undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Hardcoded dist main path 🐞 Bug ≡ Correctness

WorkspaceAspectsLoader.getDistMain() hardcodes the compiled main lookup to <localPath>/dist/...,
so the Node 22 fallback can fail to find an existing compiled entry when an aspect/env compiler
outputs to a different distDir. In that case, requiring the aspect still fails even though
compiled JS exists, preventing aspect/env loading.
Agent Prompt
## Issue description
`WorkspaceAspectsLoader.getDistMain()` builds a fallback path using `DEFAULT_DIST_DIRNAME` (`dist`) and `mainFile.replace(... => .js)`. This ignores the compiler’s `getDistPathBySrcPath()` mapping, so aspects compiled to a non-`dist/` output directory won’t be found and the fallback require will still fail.

## Issue Context
`AspectLoaderMain.getAspectFilePath()` / `getRuntimePath()` already implement the correct pattern: try to obtain the component compiler and call `compiler.getDistPathBySrcPath(srcRelativePath)`, falling back to `DEFAULT_DIST_DIRNAME` only when the compiler isn't available.

## Fix Focus Areas
- scopes/workspace/workspace/workspace-aspects-loader.ts[679-728]
- scopes/harmony/aspect-loader/aspect-loader.main.runtime.ts[173-224]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit bfb50f4

Comment on lines +759 to +762
err.code === 'ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING' ||
err.code === 'ERR_REQUIRE_ESM' ||
err.message?.includes('Cannot find module') ||
// a CJS dist evaluated as ESM (or vice versa) - happens when the package manager

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Install masks env errors 🐞 Bug ☼ Reliability

InstallMain.reloadOneAspectsGroup() treats broad module-not-found failures from an env/aspect
runtime provider as “not requirable yet” and only warns, so installs can succeed even when a
non-workspace env is genuinely broken (e.g. missing runtime dependency). This can leave the
workspace in a misleading “installed” state until later commands fail when the env is actually
needed.
Agent Prompt
## Issue description
`InstallMain.reloadOneAspectsGroup()` suppresses provider failures for *all* reloaded aspects/envs when the error looks like module-not-found. This suppression is appropriate only for workspace aspects that may not be compiled yet during early install cycles; for non-workspace aspects (coming from scope/node_modules) a `MODULE_NOT_FOUND`/`Cannot find module` typically indicates a real broken env that should fail the install.

## Issue Context
The grouping logic explicitly creates a `scope` group for aspects not in the workspace, but the provider error suppression does not differentiate between workspace and non-workspace groups.

## Fix Focus Areas
- scopes/workspace/install/install.main.runtime.ts[744-772]
- scopes/workspace/install/install.main.runtime.ts[787-799]

## Suggested fix
1. Carry enough context into the `loadedPlugins` entries (e.g. `{ id, plugins, isWorkspace: group.workspace }`, optionally `localPath`).
2. In the provider `catch`, only apply the "not requirable yet" suppression when `group.workspace === true` (and possibly `group.envOfAspect === true`).
3. For `group.workspace === false` (scope/node_modules aspects), rethrow `MODULE_NOT_FOUND` / `Cannot find module` errors so `bit install` fails fast with an actionable message.
4. (Optional) If you still need some tolerance for non-workspace aspects, narrow it to very specific transient cases (e.g. ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING for a TS main) rather than the generic "Cannot find module" substring.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 81d5756

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 88179c7

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 1e4e994

Comment on lines +834 to +836
const localPath = await this.resolveInstalledAspectRecursively(component, rootIds, graph, {
throwOnError: opts.throwOnError && isRequested,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Stale installed-aspect cache 🐞 Bug ☼ Reliability

getInstalledAspectResolver() now suppresses errors for non-requested dependency aspects
(throwOnError gated by requestedIds), but resolveInstalledAspectRecursively memoizes failures as
null and returns the cached null on later attempts. If an env/aspect package becomes available
later in the same process (e.g. during multi-cycle bit install), the loader won’t retry resolution
and the aspect can remain unresolved until cache invalidation/restart.
Agent Prompt
## Issue description
`WorkspaceAspectsLoader.resolveInstalledAspectRecursively()` caches failed resolutions as `null` in `resolvedInstalledAspects`. This PR also introduces a path where dependency aspects are resolved with `throwOnError: false` (based on `requestedIds`), so transient resolution failures during install can be cached and then never retried after packages are installed in the same process.

## Issue Context
- The installed-aspect resolver memoizes both successes and failures.
- Workspace cache clearing (`workspace.clearCache`) does not clear `resolvedInstalledAspects`.
- During `bit install` (and other multi-stage flows), aspects may become resolvable after `node_modules` changes, but the loader will still return cached `null`.

## Fix Focus Areas
- Add an explicit invalidation method on `WorkspaceAspectsLoader` (e.g. `clearResolvedInstalledAspectsCache()`), and call it from `Workspace.clearCache()` (and/or other places that mutate/refresh node_modules, such as post-install hooks).
- Alternatively, avoid caching `null` (or cache it only for the duration of a single load trace), so subsequent attempts can re-resolve after installation.

### Code references
- scopes/workspace/workspace/workspace-aspects-loader.ts[823-937]
- scopes/workspace/workspace/workspace.ts[871-890]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 4eeff58

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 9720e5c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant