Skip to content

feat(workspace): manage pnpm workspaces with Bit, on the workspace-root component - #10729

Open
davidfirst wants to merge 26 commits into
masterfrom
pnpm-vcs-port
Open

davidfirst wants to merge 26 commits into
masterfrom
pnpm-vcs-port

Conversation

@davidfirst

@davidfirst davidfirst commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Builds on #10675, ported onto the workspace-root component model (#10698). What changed from the original, and why:

Original Now
Explicit file lists (useExplicitFiles) for the root and projects Root with rootDir: "." plus trackAllFiles
Topology stored in the tracker config (pnpmVcs.workspace) The root's versioned .bitmap, read through WorkspaceRootMain.listMembers()
bit import --pnpm-vcs-root and bit switch --pnpm-vcs-bootstrap bit clone <root> [--lane]
Sync and the scripts env live in the tracker aspect A core teambit.workspace/pnpm-workspace aspect: it owns bit pnpm sync and the import handling (through an importer slot), and is itself the scripts env, so the env ships with bit and needs no install

Dropped on purpose

  • Toolchain profiles (engines.node / pnpm.vcs.profile checks): they ran on import, but nothing else used their result.
  • The vcs: block in pnpm-workspace.yaml: the root component's .bitmap now holds that identity.
  • Manifest rewriting during sync (migratePnpmWorkspaceDependencies): it turned workspace: specifiers into catalog: entries in the user's own package.json files. Adoption shouldn't edit those files.

The import-side rewrite of workspace: references to catalog: is kept. It now applies only to siblings the target workspace doesn't have, so a bit clone leaves the manifests untouched.

zkochan and others added 15 commits August 29, 2026 17:04
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… component

"bit pnpm sync" now tracks each pnpm project as a component and the root with rootDir ".", under
trackAllFiles, instead of explicit file lists and a topology in tracker config. Drops the
--pnpm-vcs-root/--pnpm-vcs-bootstrap flags, toolchain profiles, the vcs block in
pnpm-workspace.yaml, the manifest migration and the package-scripts env (CI runs the scripts
after "bit clone"). The import plan no longer rewrites package manifests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ionless packages

componentIdToPackageName now honors the dependency-resolver packageName config, so the linker and
the root-components linking agree on the name. A found package with no version (a private pnpm
package) is recorded as "*" instead of failing the dependency detection.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ce refs on import

Adds teambit.envs/pnpm-workspace-env, a plain-JS env on the empty env that compiles and builds
through the packages' own scripts, in a pnpm workspace rebuilt from the workspace-root component.
"bit pnpm sync" assigns it to projects with scripts (--env to override). On import, a "workspace:"
reference to a sibling the target workspace lacks becomes "catalog:", bound to its exact version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Manage pnpm workspaces through workspace-root components

✨ Enhancement 🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Adds idempotent bit pnpm sync adoption through workspace-root components and versioned
 inventories.
• Preserves pnpm package identities, workspace links, catalog bindings, and snapped dependency
 metadata.
• Builds pnpm projects through native workspace scripts locally and in isolated environments.
Diagram

graph TD
  Sync["pnpm sync"] --> Inventory["Component inventory"] --> Graph["Dependency graph"] --> Snap["Snap metadata"]
  Import["Bit import"] --> Catalog["Catalog plan"] --> Inventory
  Inventory --> Env["Workspace env"] --> Pnpm["pnpm scripts"]
Loading
High-Level Assessment

The workspace-root model is the strongest approach because it makes the existing versioned .bitmap the authoritative inventory and avoids duplicating topology in tracker configuration. Explicit file lists, custom VCS manifest blocks, and adoption-time manifest migration were considered but would create parallel state or unexpectedly rewrite user-owned files; delegating script ordering to pnpm also preserves native workspace semantics.

Files changed (36) +2470 / -48

Enhancement (18) +1480 / -24
snap-cmd.tsAdd versioned JSON output for snap +73/-13

Add versioned JSON output for snap

• Separates snap execution from rendering and adds a schema-versioned JSON result containing batch, lane, component, removal, warning, and count data. Empty snap operations now return a stable JSON shape.

scopes/component/snapping/snap-cmd.ts

snapping.main.runtime.tsExpose snap batch identifiers in results +3/-1

Expose snap batch identifiers in results

• Carries the batch identifier returned by version creation into 'SnapResults' for reporting and automation.

scopes/component/snapping/snapping.main.runtime.ts

status-cmd.tsVersion status JSON response schemas +4/-0

Version status JSON response schemas

• Adds 'schemaVersion: 1' to both quick and full status JSON outputs for stable machine consumption.

scopes/component/status/status-cmd.ts

index.tsExpose pnpm synchronization and import APIs +2/-0

Expose pnpm synchronization and import APIs

• Exports pnpm workspace detection, import-plan creation and application, and their public result types from the tracker package.

scopes/component/tracker/index.ts

pnpm-vcs-sync.cmd.tsImplement pnpm workspace adoption and reconciliation +762/-0

Implement pnpm workspace adoption and reconciliation

• Adds 'bit pnpm sync' discovery, idempotent project and workspace-root tracking, environment assignment, stale-project removal, and schema-versioned output. It also tracks referenced catalog bindings and reconciles selective imports without broadly rewriting user manifests.

scopes/component/tracker/pnpm-vcs-sync.cmd.ts

tracker.main.runtime.tsRegister pnpm commands and catalog tracking +6/-1

Register pnpm commands and catalog tracking

• Registers 'bit pnpm' and its sync subcommand, then attaches an on-load hook that records catalog entries used by each component.

scopes/component/tracker/tracker.main.runtime.ts

dependency-resolver.main.runtime.tsSupport declared package names in graph calculation +4/-2

Support declared package names in graph calculation

• Extends variant and graph inputs with an optional package name and uses it instead of Bit's generated package identity when supplied.

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

index.jsExport the pnpm workspace environment +4/-0

Export the pnpm workspace environment

• Provides named and default CommonJS exports for the standalone pnpm workspace environment.

scopes/envs/pnpm-workspace-env/index.js

pnpm-script.task.jsRun package scripts across reconstructed pnpm workspaces +165/-0

Run package scripts across reconstructed pnpm workspaces

• Adds build, test, and lint tasks that reconstruct and install the complete versioned workspace before invoking pnpm recursively. Script outputs are copied back into component capsules as artifacts.

scopes/envs/pnpm-workspace-env/pnpm-script.task.js

pnpm-workspace-tree.jsReconstruct versioned pnpm workspace trees +202/-0

Reconstruct versioned pnpm workspace trees

• Loads workspace-root membership from local workspaces or scopes, validates member paths, writes complete trees, and signatures their contents for reuse.

scopes/envs/pnpm-workspace-env/pnpm-workspace-tree.js

pnpm-workspace.bit-env.jsDefine the package-script pnpm environment +31/-0

Define the package-script pnpm environment

• Introduces a dependency-free JavaScript environment whose compiler and build pipeline delegate build, test, and lint execution to pnpm workspace scripts.

scopes/envs/pnpm-workspace-env/pnpm-workspace.bit-env.js

pnpm-workspace.compiler.jsCompile components through ordered pnpm workspace builds +133/-0

Compile components through ordered pnpm workspace builds

• Runs the workspace build script once per source state, serializes concurrent compile requests, and copies each component's distribution output to its requested destination.

scopes/envs/pnpm-workspace-env/pnpm-workspace.compiler.js

utils.jsAdd pnpm process and filesystem helpers +34/-0

Add pnpm process and filesystem helpers

• Provides pnpm subprocess execution with captured output and a reusable asynchronous path-existence check.

scopes/envs/pnpm-workspace-env/utils.js

init-cmd.tsAdd explicit agent and MCP initialization opt-outs +5/-1

Add explicit agent and MCP initialization opt-outs

• Adds '--no-agent' and '--no-mcp' flags and forwards those choices to workspace initialization.

scopes/harmony/host-initializer/init-cmd.ts

import.cmd.tsExpose pnpm reconciliation plans in import JSON +6/-2

Expose pnpm reconciliation plans in import JSON

• Includes the computed pnpm import plan in JSON output when components were written into the workspace.

scopes/scope/importer/import.cmd.ts

importer.main.runtime.tsReconcile imports through pnpm workspace manifests +14/-2

Reconcile imports through pnpm workspace manifests

• Detects adopted pnpm workspaces and applies package and catalog reconciliation after imports. Non-pnpm workspaces retain the existing workspace configuration cleanup path.

scopes/scope/importer/importer.main.runtime.ts

workspace-root-data.tsCentralize versioned root bitmap parsing +15/-1

Centralize versioned root bitmap parsing

• Adds a reusable helper that locates and parses the '.bitmap' stored among workspace-root component files.

scopes/workspace/workspace-root/workspace-root-data.ts

workspace-root.main.runtime.tsExpose workspace-root component membership +17/-1

Expose workspace-root component membership

• Adds 'listMembers()' to return versioned bitmap entries while excluding the root component itself.

scopes/workspace/workspace-root/workspace-root.main.runtime.ts

Bug fix (6) +217 / -14
component-id-to-package-name.tsPreserve package names declared by pnpm projects +3/-0

Preserve package names declared by pnpm projects

• Uses the dependency-resolver 'packageName' configuration before generated package metadata, preserving package.json identities for adopted projects.

components/modules/component-package-name/component-id-to-package-name.ts

version-maker.tsPersist pnpm workspace links as component dependencies +128/-5

Persist pnpm workspace links as component dependencies

• Maps direct package-manager graph edges to exact Bit component dependencies using declared package names. Promoted dependencies update legacy lists and dependency-resolver data before flattened dependencies are calculated.

scopes/component/snapping/version-maker.ts

auto-detect-deps.tsResolve workspace packages through configured package names +42/-0

Resolve workspace packages through configured package names

• Builds a package-name-to-component map from bitmap configuration or tracked package.json files. Detected package imports are promoted to component dependencies while excluding the workspace root and self-dependencies.

scopes/dependencies/dependencies/dependencies-loader/auto-detect-deps.ts

missing-handler.tsAccept packages without declared versions +2/-2

Accept packages without declared versions

• Falls back to '*' instead of throwing when a resolved private pnpm workspace package has no version.

scopes/dependencies/dependencies/files-dependency-builder/missing-handler.ts

lockfile-deps-graph-converter.tsConvert pnpm workspace links into component graph nodes +33/-6

Convert pnpm workspace links into component graph nodes

• Preserves 'link:' and 'workspace:' references when their package names map to Bit components. It adds corresponding package attributes and graph nodes for later dependency promotion.

scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts

pnpm.package-manager.tsRetain mapped pnpm workspace references +9/-1

Retain mapped pnpm workspace references

• Prevents cleanup from deleting 'link:' or 'workspace:' importer entries when they correspond to known Bit components.

scopes/dependencies/pnpm/pnpm.package-manager.ts

Refactor (2) +4 / -6
add-components.tsExport the workspace-root environment identifier +1/-1

Export the workspace-root environment identifier

• Exports the shared empty-environment constant so pnpm synchronization can configure projects without duplicating the identifier.

scopes/component/tracker/add-components.ts

clone.tsRead clone inventories through workspace-root data utilities +3/-5

Read clone inventories through workspace-root data utilities

• Reuses the centralized workspace-root bitmap reader when loading the component inventory needed for cloning.

scopes/workspace/workspace-root/clone.ts

Tests (8) +751 / -0
component-id-to-package-name.spec.tsTest dependency-resolver package-name precedence +17/-0

Test dependency-resolver package-name precedence

• Verifies default package-name derivation and confirms that a configured dependency-resolver package name takes precedence.

components/modules/component-package-name/component-id-to-package-name.spec.ts

add-harmony.e2e.tsExercise pnpm workspace compilation and builds end to end +58/-0

Exercise pnpm workspace compilation and builds end to end

• Creates a linked two-package pnpm workspace, adopts it with 'bit pnpm sync', and verifies compile, build, and test scripts run in workspace order.

e2e/harmony/add-harmony.e2e.ts

snapping.spec.tsTest snap batch and JSON result contracts +25/-0

Test snap batch and JSON result contracts

• Verifies batch identifiers are returned and validates populated and empty schema-versioned snap JSON payloads.

scopes/component/snapping/snapping.spec.ts

version-maker.spec.tsTest promotion of pnpm graph dependencies +113/-0

Test promotion of pnpm graph dependencies

• Covers direct workspace dependency promotion, lifecycle and optional metadata, snap-version conversion, and prerelease handling.

scopes/component/snapping/version-maker.spec.ts

pnpm-vcs-sync.spec.tsCover pnpm synchronization, imports, and catalogs +457/-0

Cover pnpm synchronization, imports, and catalogs

• Tests project discovery, stable identities, environment selection, removals, nested-project validation, selective import reconciliation, and component-specific catalog tracking.

scopes/component/tracker/pnpm-vcs-sync.spec.ts

missing-handler.spec.tsTest versionless private workspace package detection +36/-0

Test versionless private workspace package detection

• Verifies resolved packages use their manifest version when present and '*' when private workspace packages omit one.

scopes/dependencies/dependencies/files-dependency-builder/missing-handler.spec.ts

lockfile-deps-graph-converter.spec.tsTest workspace link preservation in pnpm graphs +25/-0

Test workspace link preservation in pnpm graphs

• Verifies a 'link:' importer dependency mapped to a workspace component remains a graph edge with component identity metadata.

scopes/dependencies/pnpm/lockfile-deps-graph-converter.spec.ts

workspace-root-data.spec.tsTest workspace-root bitmap inventory parsing +20/-0

Test workspace-root bitmap inventory parsing

• Verifies versioned bitmap entries are read from root component files and that missing bitmap files yield an empty inventory.

scopes/workspace/workspace-root/workspace-root-data.spec.ts

Documentation (1) +4 / -4
SKILL.mdNormalize pnpm engine documentation formatting +4/-4

Normalize pnpm engine documentation formatting

• Fixes indentation and JavaScript punctuation in the pnpm engine skill examples without changing their guidance.

.claude/skills/pnpm-engine/SKILL.md

Other (1) +14 / -0
.bitmapRegister the pnpm workspace environment component +14/-0

Register the pnpm workspace environment component

• Adds 'teambit.envs/pnpm-workspace-env' as a JavaScript component running on the empty environment without a compilation bootstrap.

.bitmap

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

qodo-free-for-open-source-projects Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (14) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Action required

1. One test fixture fails formatting 📘 Rule violation ⚙ Maintainability ⭐ New
Description
The scripts property in the app package fixture remains on one line beyond the configured
120-character print width. Running the canonical Prettier check over the E2E TypeScript files
reformats this changed line, causing the formatting check to fail.
Code

e2e/harmony/add-harmony.e2e.ts[637]

+          scripts: { build: 'node build.js', test: "node -e \"require('./dist') === 3 && console.log('app works')\"" },
Evidence
Compliance rule 4 requires changed code to pass the repository's Prettier check. The added line at
e2e/harmony/add-harmony.e2e.ts[637-637] exceeds the configured print width and is not in
Prettier's canonical layout.

CLAUDE.md: Changed Code Must Pass Canonical Linting, Type Checking, and Formatting Standards
e2e/harmony/add-harmony.e2e.ts[637-637]

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

## Issue description
The app package fixture's `scripts` property does not conform to the repository's Prettier configuration and fails the canonical formatting check.

## Fix Focus Areas
- e2e/harmony/add-harmony.e2e.ts[637-637]

## Recommended Fix
Run Prettier on the file or manually expand the `scripts` object using Prettier's expected multiline layout without changing the fixture values.

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


2. Sync removes every workspace project 🐞 Bug ≡ Correctness ⭐ New
Description
syncPnpmWorkspace() converts an omitted packages field to an empty pattern list, even though the
manifest type permits that field to be absent. Re-running sync on such a pnpm workspace discovers no
projects and passes the empty inventory to removeLeftProjects(), which untracks unsnapped projects
and marks snapped projects as removed.
Code

scopes/workspace/pnpm-workspace/pnpm-workspace-sync.ts[203]

+  const projects = await discoverPnpmProjects(workspace.path, workspaceManifest.packages || []);
Evidence
The manifest model explicitly makes packages optional, while synchronization replaces its absence
with []. Project discovery iterates only positive supplied patterns, and the resulting inventory
is immediately used to remove every previously marked pnpm project whose directory is absent.

scopes/workspace/pnpm-workspace/pnpm-workspace-sync.ts[139-143]
scopes/workspace/pnpm-workspace/pnpm-workspace-sync.ts[203-212]
scopes/workspace/pnpm-workspace/pnpm-workspace-sync.ts[399-413]
scopes/workspace/pnpm-workspace/pnpm-workspace-sync.ts[559-578]

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

## Issue description
`syncPnpmWorkspace()` treats an omitted `packages` field as an explicit empty list. This makes synchronization discover no projects and remove components synchronized by an earlier run.

## Fix Focus Areas
- scopes/workspace/pnpm-workspace/pnpm-workspace-sync.ts[139-143]
- scopes/workspace/pnpm-workspace/pnpm-workspace-sync.ts[193-212]
- scopes/workspace/pnpm-workspace/pnpm-workspace-sync.ts[559-578]

## Recommended Fix
When `packages` is undefined, pass pnpm's default project pattern, such as `['**']`, to discovery while preserving an explicitly configured empty array as empty. Add coverage distinguishing an omitted field from `packages: []`, including a repeat sync that verifies existing projects are not removed.

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


3. Imported projects remain outside pnpm 🐞 Bug ≡ Correctness
Description
applyPnpmImportPlan() appends an imported root to packages without removing an existing negated
pattern that matches the same directory. When a workspace excludes that root, both
readLocalPackageNames() and the next syncPnpmWorkspace() use discoverPnpmProjectManifests()'s
unconditional ignore list, so the imported package is neither locally bound nor synchronized as a
pnpm project.
Code

scopes/component/tracker/pnpm-vcs-sync.cmd.ts[R467-468]

+  const uncoveredRootDirs = plan.components.map(({ rootDir }) => rootDir).filter((dir) => !coveredRootDirs.has(dir));
+  if (uncoveredRootDirs.length) manifest.packages = [...(manifest.packages || []), ...uncoveredRootDirs];
Evidence
Import reconciliation only checks roots returned by discovery and appends missing roots, while
discovery applies every negated pattern as a global glob ignore. The importer invokes this
reconciliation after writing components, and subsequent pnpm sync uses the same discovery function,
so adding a positive root cannot make a negated root discoverable.

scopes/component/tracker/pnpm-vcs-sync.cmd.ts[462-468]
scopes/component/tracker/pnpm-vcs-sync.cmd.ts[560-575]
scopes/scope/importer/importer.main.runtime.ts[86-93]
scopes/component/tracker/pnpm-vcs-sync.cmd.ts[203-217]
scopes/component/tracker/pnpm-vcs-sync.spec.ts[253-261]

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

## Issue description
Imported component roots are appended to `pnpm-workspace.yaml`, but matching negated package patterns still exclude them from pnpm discovery and later synchronization.
## Fix Focus Areas
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[462-468]
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[560-575]
## Recommended Fix
When applying an import plan, reconcile matching negated patterns so every imported root is effectively included. Remove or narrow exclusions that match imported roots, or introduce discovery semantics that explicitly allow imported roots to override those exclusions, and add coverage for importing into a previously excluded directory.

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


View action required (11)
4. Dev workspace dependencies disappear after snaps 🐞 Bug ≡ Correctness
Description
convertLockfileToGraph() creates graph neighbours for link: and workspace: development
dependencies but does not call addWorkspaceComponentPackages() in its normal component-root path.
When a pnpm project is resolved through the root-components importer, those synthetic neighbours
have no package metadata, so componentDependenciesFromGraph() skips them and the snapped component
does not retain its development dependency.
Code

scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[R141-143]

+    directDependencies.push(
+      ...importerDepsToNeighbours(componentDevImporter.devDependencies, 'dev', false, componentIdByPkgName)
+    );
Evidence
The new forwarding produces a synthetic root neighbour for a non-registry development dependency,
but only the capsule branch repairs synthetic workspace entries with component metadata. The normal
branch returns before that repair, while promotion explicitly requires a package entry and a
version.

scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[98-123]
scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[125-184]
scopes/dependencies/pnpm/pnpm.package-manager.ts[545-553]
scopes/component/snapping/version-maker.ts[105-124]

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

## Issue description
The normal `convertLockfileToGraph()` path now preserves `link:` and `workspace:` development dependencies as root neighbours, but it does not add the corresponding workspace component metadata to `graph.packages`. Dependency promotion requires that metadata, so these development dependencies are omitted from the snapped component.
## Fix Focus Areas
- scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[125-184]
- scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[109-123]
## Recommended Fix
After `_convertLockfileToGraph()` returns in the normal component-root branch, call `addWorkspaceComponentPackages(graph, componentIdByPkgName)` before returning the graph, as the capsule conversion branch already does. Add coverage for a development `link:` or `workspace:` dependency in the component-root importer path and assert that its root neighbour has component metadata.

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


5. Snaps replace registry dependencies 🐞 Bug ≡ Correctness
Description
addWorkspaceComponentPackages() overwrites every root graph package whose name matches a workspace
component, even when the lockfile reference resolved to an ordinary registry package. A snap with a
same-named local project therefore records the local component identity and version instead of the
dependency selected by the package manager.
Code

scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[R113-117]

+    const componentId = componentIdByPkgName.get(neighbour.name);
+    if (!componentId) continue;
+    graph.packages.set(neighbour.id, {
+      component: { scope: componentId.scope, name: componentId.fullName },
+      version: componentId.version ? snapToSemver(componentId.version) : undefined,
Evidence
The converter distinguishes normal registry references from non-registry links through
refToRelative() and applies its component fallback only to link or workspace references. The
subsequent new helper loses that distinction and unconditionally replaces graph package metadata
based only on the neighbour name, after which snap promotion treats that metadata as authoritative
component identity.

scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[88-106]
scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[109-123]
scopes/component/snapping/version-maker.ts[105-124]
scopes/component/snapping/version-maker.ts[391-412]

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

## Issue description
The dependency graph relabels registry-resolved packages as workspace components based solely on matching package names.
## Fix Focus Areas
- scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[88-123]
## Recommended Fix
Track which neighbours were created from verified `link:` or `workspace:` references and attach component metadata only to those neighbours. Preserve the existing lockfile package metadata for registry references, even when their names match workspace components.

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


6. Workspace package versions are lost 🐞 Bug ≡ Correctness
Description
convertLockfileToGraph() passes componentIdByPkgName only while converting the project
importer’s development dependencies, then its component-root runtime loop drops every non-registry
link: or workspace: reference when refToRelative() returns null. This occurs when a component
is resolved through a root-components importer, so snapping can persist no component dependency for
a runtime pnpm workspace package and later installs do not receive the required exact component
version.
Code

scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[R141-143]

+    directDependencies.push(
+      ...importerDepsToNeighbours(componentDevImporter.devDependencies, 'dev', false, componentIdByPkgName)
+    );
Evidence
The new fallback is explicitly wired only into development-importer conversion. The subsequent
runtime/optional loop still skips null non-registry references, while the dependency resolver
invokes this converter with a component root when that importer exists.

scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[135-162]
scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[98-123]
scopes/dependencies/pnpm/pnpm.package-manager.ts[500-568]
scopes/component/snapping/version-maker.ts[391-413]

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

## Issue description
The component-root conversion path handles `link:` and `workspace:` references for development importer dependencies, but not for runtime or optional dependencies read from the locked package snapshot. As a result, direct runtime workspace-component dependencies can be omitted from the graph that snapping promotes into persisted Bit dependencies.
## Fix Focus Areas
- scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[141-162]
## Recommended Fix
Apply the same `componentIdByPkgName` fallback used by `importerDepsToNeighbours()` to the runtime and optional dependency loop. When a snapshot reference is a workspace-style non-registry reference and its package name maps to a component, create the snap-semver neighbour and ensure its package attributes identify that target as a component; retain the current behavior for unrelated non-registry references. Add coverage for the `componentRootDir` path with a runtime workspace dependency.

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


7. Init opt-outs still create files 🐞 Bug ≡ Correctness
Description
handleInteractiveMode() writes the selected MCP configuration and agent rules before report()
passes noAgent and noMcp to the initializer. In an interactive Git-repository setup, selecting
an MCP editor therefore creates files even when the corresponding command-line opt-out was supplied.
Code

scopes/harmony/host-initializer/init-cmd.ts[212]

+      { skipAgentInstructions: Boolean(noAgent), skipDefaultMcp: Boolean(noMcp) || userOptedOutOfMcp }
Evidence
The interactive path runs before the initializer and invokes both file-writing methods without
checking the new flags; the skip options only govern the later initializer call.

scopes/harmony/host-initializer/init-cmd.ts[78-119]
scopes/harmony/host-initializer/init-cmd.ts[149-212]
scopes/harmony/host-initializer/host-initializer.main.runtime.ts[116-136]

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

## Issue description
Interactive initialization writes MCP configuration and agent rules before the new `--no-mcp` and `--no-agent` options reach the main initializer, making those opt-outs ineffective.
## Fix Focus Areas
- scopes/harmony/host-initializer/init-cmd.ts[78-119]
- scopes/harmony/host-initializer/init-cmd.ts[149-212]
## Recommended Fix
Pass the opt-out flags into `handleInteractiveMode()` and prevent `setupMcpServer()` when `noMcp` is set and `writeMcpAgentRules()` when `noAgent` is set. Preserve the flags when invoking `HostInitializerMain.init()` so both interactive and non-interactive paths honor them.

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


8. Concurrent builds corrupt pnpm workspaces ⊘ Outdated 🐞 Bug ☼ Reliability
Description
PnpmScriptTask.execute() derives treeDir solely from the shared capsule root and the fixed
.pnpm-workspace name, while prepareTree() removes, rewrites, installs, and marks that directory.
When build concurrency runs distinct environments in parallel, their tasks can overwrite the same
reconstructed tree while scripts and output copying are in progress.
Code

scopes/envs/pnpm-workspace-env/pnpm-script.task.js[R90-94]

+    const treeDir = path.join(context.capsuleNetwork.capsulesRootDir, TREE_DIR);
+    try {
+      await this.prepareTree(tree, treeDir);
+      await this.pnpm(['-r', '--if-present', 'run', this.script], treeDir);
+      await copyOutputsToCapsules(context, tree, treeDir, OUTPUT_DIRS[this.script]);
Evidence
The added task path is fixed for the entire capsule network, and tree preparation destructively
replaces that path before installing. The builder supports concurrent execution and its scheduler
preserves ordering only among tasks in the same environment, so different environments can enter
these operations simultaneously.

scopes/envs/pnpm-workspace-env/pnpm-script.task.js[90-94]
scopes/envs/pnpm-workspace-env/pnpm-script.task.js[115-122]
scopes/pipelines/builder/build-pipe.ts[48-53]
scopes/pipelines/builder/tasks-parallel-scheduler.ts[39-44]

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

## Issue description
All pnpm script tasks reconstruct their workspace at the same `.pnpm-workspace` directory below `capsulesRootDir`. The build scheduler may execute tasks from different environments concurrently, so one task can remove or rewrite the tree while another is installing dependencies, running a script, or copying outputs.
## Fix Focus Areas
- scopes/envs/pnpm-workspace-env/pnpm-script.task.js[90-94]
- scopes/envs/pnpm-workspace-env/pnpm-script.task.js[115-122]
## Recommended Fix
Derive the reconstructed workspace path from a stable environment-specific value as well as the capsule root, such as a sanitized/hash-safe `context.envId`. Ensure build, test, lint, installation-marker, and output-copy operations for an environment use that same private directory, preventing tasks from separate environments from sharing mutable state.

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


9. Ordinary workspaces enter pnpm mode 🐞 Bug ≡ Correctness
Description
isPnpmWorkspaceRoot() and isPnpmWorkspace() treat any workspace-root component beside
pnpm-workspace.yaml as proof that the workspace was adopted by bit pnpm sync, without checking
durable adoption state. An ordinary Bit workspace using pnpm and a root component is therefore
routed through pnpm-specific behavior, causing component writes and clones to skip Bit installation
and compilation while imports may modify its pnpm manifest.
Code

scopes/workspace/workspace-root/workspace-root-data.ts[R37-38]

+export function isPnpmWorkspaceRoot(workspacePath: string, bitMap: BitMap): boolean {
+  return Boolean(findWorkspaceRootMap(bitMap)) && fs.existsSync(path.join(workspacePath, PNPM_WORKSPACE_MANIFEST));
Evidence
The new predicate checks only for a root bitmap entry and manifest existence, while pnpm manifests
and workspace-root tracking are independently supported. Both the writer and cloner use this
predicate to bypass their normal installation paths, and the tracker contains an equivalent broad
predicate used by imports.

scopes/workspace/workspace-root/workspace-root-data.ts[31-39]
scopes/component/tracker/pnpm-vcs-sync.cmd.ts[172-180]
scopes/component/component-writer/component-writer.main.runtime.ts[118-131]
scopes/workspace/workspace-root/clone.ts[84-98]
e2e/harmony/pkg-manager-config.e2e.ts[34-43]

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

## Issue description
Pnpm workspace mode is inferred from the presence of a workspace-root component and `pnpm-workspace.yaml`, a combination ordinary Bit workspaces can also have. This causes installation, compilation, cloning, and import behavior to change even when `bit pnpm sync` never adopted the workspace.
## Fix Focus Areas
- scopes/workspace/workspace-root/workspace-root-data.ts[31-39]
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[176-180]
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[333-347]
## Recommended Fix
Persist an explicit, versioned pnpm-adoption marker on the workspace-root component during sync, and require that marker in both pnpm-workspace predicates instead of inferring adoption from the manifest’s existence. Ensure the marker survives cloning through the versioned bitmap.

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


10. Workspace roots keep stale file lists 🐞 Bug ≡ Correctness
Description
trackPnpmWorkspaceRoot() returns an existing root ID without invoking the tracker scan that
recomputes the root component’s files after nested component directories change. On subsequent
syncs, newly added projects can remain listed in both their component and the root, removed-project
files remain stale, and new root-level files are omitted from the root’s next snapshot.
Code

scopes/component/tracker/pnpm-vcs-sync.cmd.ts[R333-335]

+async function trackPnpmWorkspaceRoot(workspace: Workspace, tracker: TrackerMain): Promise<ComponentID> {
+  const existingId = workspace.consumer.bitMap.getComponentIdByRootPath(WORKSPACE_ROOT_DIR);
+  if (existingId) return existingId;
Evidence
Sync changes project membership before calling the root helper, but the early return skips the only
scan that subtracts nested component roots and replaces the root file set. The reported file count
is then read directly from that unchanged bitmap entry.

scopes/component/tracker/pnpm-vcs-sync.cmd.ts[191-225]
scopes/component/tracker/pnpm-vcs-sync.cmd.ts[333-347]
scopes/component/tracker/pnpm-vcs-sync.cmd.ts[397-420]
scopes/component/tracker/add-components.ts[350-369]
scopes/component/tracker/add-components.ts[1001-1029]

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

## Issue description
An existing workspace-root component bypasses tracking during subsequent pnpm syncs, so its file inventory is never reconciled with current project directories or root files. The root can consequently snapshot stale, missing, or multiply owned files.
## Fix Focus Areas
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[333-347]
- scopes/component/tracker/add-components.ts[1009-1029]
## Recommended Fix
Rescan and update the existing workspace-root component on every sync after project additions and removals, while preserving its existing component ID and root configuration. Add rerun coverage for adding a project, removing a project, and adding a root-level file.

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


11. Imports create duplicate workspace packages 🐞 Bug ≡ Correctness
Description
createPnpmVcsImportPlan() collects imported package names into localPackageNames with
Set.add() but never rejects names that already belong to a local or another imported project.
applyPnpmImportPlan() then adds each imported root directory to pnpm-workspace.yaml, whereas the
sync path explicitly validates duplicate project identities before tracking them.
Code

scopes/component/tracker/pnpm-vcs-sync.cmd.ts[R707-709]

+  const workspaceManifest = await readPnpmWorkspaceManifest(path.join(workspace.path, PNPM_WORKSPACE_MANIFEST));
+  const localPackageNames = await readLocalPackageNames(workspace.path, workspaceManifest.packages || []);
+  plannedComponents.forEach(({ packageName }) => localPackageNames.add(packageName));
Evidence
The import planner records every component directory but collapses package names into a Set, so a
collision is not detected. Applying the plan appends every uncovered directory, while the new sync
path demonstrates that this feature otherwise treats duplicate project identities as invalid input.

scopes/component/tracker/pnpm-vcs-sync.cmd.ts[694-709]
scopes/component/tracker/pnpm-vcs-sync.cmd.ts[430-463]
scopes/component/tracker/pnpm-vcs-sync.cmd.ts[201-207]

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

## Issue description
Import planning silently accepts package names that collide with an existing workspace project or another imported component, then adds all corresponding directories to the pnpm workspace.
## Fix Focus Areas
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[694-709]
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[430-463]
## Recommended Fix
Before returning the import plan, validate that every planned package name is unique and does not collide with package names already discovered in the target workspace. Throw a clear `BitError` identifying the duplicate package name and conflicting component or directory; add coverage for collisions with both local and batch-imported projects.

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


12. Selective imports can fail to install 🐞 Bug ≡ Correctness
Description
createPnpmVcsImportPlan() skips a missing local workspace: reference when
findByPkgNameOrCompId() cannot find that package in Bit's detected dependency data. Importing a
component without that sibling then leaves the manifest's workspace-only specifier unchanged, so
pnpm cannot resolve it in the target workspace.
Code

scopes/component/tracker/pnpm-vcs-sync.cmd.ts[R724-727]

+        const dependency = dependencies.findByPkgNameOrCompId(dependencyName);
+        // declared in package.json but never imported by the code, so bit recorded no dependency - and no
+        // version to bind. the entry stays as the pnpm manifest has it, which is versioned with the root.
+        if (!dependency) continue;
Evidence
The planner recognizes missing-local workspace: references but creates rewrite bindings only from
detected dependencies, while the application stage rewrites only names present in those bindings.
The added test also confirms that package.json-only declarations can be absent from dependency data,
establishing the reachable skip condition.

scopes/component/tracker/pnpm-vcs-sync.cmd.ts[719-747]
scopes/component/tracker/pnpm-vcs-sync.cmd.ts[496-523]
scopes/component/tracker/pnpm-vcs-sync.spec.ts[271-299]

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

## Issue description
Selective imports leave `workspace:` dependencies unchanged when Bit did not detect the declared package, producing a manifest pnpm cannot install when the sibling is absent.
## Fix Focus Areas
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[719-747]
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[496-523]
## Recommended Fix
When a missing local `workspace:` dependency has no detected exact version, do not silently omit it from the plan. Either derive a valid exact binding from persisted component metadata or fail the import before writing files with an actionable error identifying the unresolved package.

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


13. Peer components lose peer semantics 🐞 Bug ≡ Correctness
Description
GraphComponentDependency.lifecycle permits only runtime and development values, and
_promoteGraphComponentDependencies() consequently has no peer-dependency target. A pnpm workspace
component declared as a peer is therefore omitted from graph promotion or retained under the wrong
lifecycle when its source imports that package, including for optional peers.
Code

scopes/component/snapping/version-maker.ts[R96-97]

+  lifecycle: 'runtime' | 'dev';
+  optional: boolean;
Evidence
The new promotion type and target selection represent only runtime and development dependencies,
while the pnpm graph converter reads only dependency, optional-dependency, and
development-dependency sections. The repository's dependency list separately supports peer lifecycle
and optional peer metadata, proving that these semantics are expected downstream.

scopes/component/snapping/version-maker.ts[93-123]
scopes/component/snapping/version-maker.ts[391-412]
scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[64-105]
scopes/dependencies/dependency-resolver/dependencies/dependency-list.ts[165-174]

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

## Issue description
The new graph promotion model cannot represent peer dependencies, so pnpm workspace component peers are not persisted with their declared lifecycle and optional metadata.
## Fix Focus Areas
- scopes/component/snapping/version-maker.ts[93-123]
- scopes/component/snapping/version-maker.ts[391-412]
- scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[64-105]
## Recommended Fix
Extend graph conversion and promotion to represent peer lifecycle explicitly, route promoted peers to `component.peerDependencies`, and preserve `peerDependenciesMeta.optional` in dependency-resolver data.

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


14. Imports can target unavailable components 🐞 Bug ≡ Correctness
Description
getWorkspaceComponentIdsByPackageName() indexes every bitmap entry without checking whether the
component is removed or available on the current lane. When a stale entry exposes the same package
name as a resolved package import, processPackages() replaces that package with a dependency on
the unavailable component and deletes the valid package dependency.
Code

scopes/dependencies/dependencies/dependencies-loader/auto-detect-deps.ts[R515-519]

+    for (const componentMap of this.consumer.bitMap.components) {
+      const config = componentMap.config?.[DependencyResolverAspect.id];
+      const configuredPackageName = config && config !== '-' ? config.packageName : undefined;
+      if (typeof configuredPackageName === 'string' && configuredPackageName) {
+        result.set(configuredPackageName, componentMap.id);
Evidence
The added lookup iterates the complete bitmap and maps package names directly, and the new
package-processing path then converts matching packages to component dependencies. BitMap explicitly
documents that removed and other-lane entries remain in this collection while not owning a directory
in the current workspace.

scopes/dependencies/dependencies/dependencies-loader/auto-detect-deps.ts[489-500]
scopes/dependencies/dependencies/dependencies-loader/auto-detect-deps.ts[512-535]
components/legacy/bit-map/bit-map.ts[244-263]

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

## Issue description
The new package-name lookup includes removed and other-lane bitmap entries, allowing dependency detection to promote installed packages to components unavailable in the current workspace state.
## Fix Focus Areas
- scopes/dependencies/dependencies/dependencies-loader/auto-detect-deps.ts[512-535]
- components/legacy/bit-map/bit-map.ts[244-263]
## Recommended Fix
Populate the package-name map only from components that are available on the current lane and are not removed. Also detect duplicate active package names rather than silently overwriting an earlier mapping.

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



Remediation recommended

15. Other environments lose build outputs 🐞 Bug ≡ Correctness ⭐ New
Description
copyOutputsToCapsules() iterates every member loaded from the workspace root and removes each
matching capsule's output directories before checking whether the pnpm run produced replacements.
When the capsule graph contains components outside context.components, this task can erase dist,
build, or lib directories produced by another environment even during sequential task execution.
Code

scopes/workspace/pnpm-workspace/pnpm-script.task.ts[R154-157]

+          // a capsule is reused across builds, so what an earlier build left must not pass as this one's output
+          const target = path.join(capsule.path, outputDir);
+          await fs.rm(target, { recursive: true, force: true });
+          const source = path.join(treeDir, rootDir, outputDir);
Evidence
The workspace tree contains every member listed by the root, including members loaded from outside
the current build set. The output-copy loop traverses that complete map, resolves any corresponding
graph capsule, and unconditionally removes each target before determining whether a source directory
exists.

scopes/workspace/pnpm-workspace/pnpm-workspace-tree.ts[76-90]
scopes/workspace/pnpm-workspace/pnpm-script.task.ts[47-49]
scopes/workspace/pnpm-workspace/pnpm-script.task.ts[148-163]

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

## Issue description
The pnpm task cleans output directories for every workspace member represented in the capsule graph, including components outside the environment currently executing the task. Outputs created by another environment can therefore be deleted without replacement.

## Fix Focus Areas
- scopes/workspace/pnpm-workspace/pnpm-script.task.ts[141-163]
- scopes/workspace/pnpm-workspace/pnpm-workspace-tree.ts[76-90]

## Recommended Fix
Build a set from `context.components` and restrict capsule output deletion and copying to tree members whose component IDs belong to that set. Add a test with an additional graph capsule outside the task context and verify its existing output directories remain unchanged.

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


16. Source edits retain stale build output 🐞 Bug ≡ Correctness ⭐ New
Description
sourceSignature() records only each file's path, size, and mtimeMs, and
buildIfSourcesChanged() skips pnpm run build when that metadata string matches the prior
successful build. When an edit preserves a source file's byte length and timestamp, such as through
timestamp-restoring tooling, every transpileComponent() call for that workspace copies the prior
dist output instead of recompiling.
Code

scopes/workspace/pnpm-workspace/pnpm-workspace.compiler.ts[R128-129]

+        const stat = await fs.stat(fullPath);
+        entries.push(`${path.relative(workspaceDir, fullPath)}:${stat.size}:${stat.mtimeMs}`);
Evidence
The build path returns before invoking pnpm whenever the cached signature matches, while the
signature is assembled exclusively from relative path, size, and mtime and never reads file
contents. The transpiler calls that cache path before copying dist, so a false cache hit
propagates prior output.

scopes/workspace/pnpm-workspace/pnpm-workspace.compiler.ts[57-71]
scopes/workspace/pnpm-workspace/pnpm-workspace.compiler.ts[82-95]
scopes/workspace/pnpm-workspace/pnpm-workspace.compiler.ts[115-134]

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

## Issue description
The compiler cache key uses only source paths, file sizes, and modification timestamps. Content can change while retaining that metadata, causing the workspace build to be skipped and stale `dist` files to be copied to transpilation output.

## Fix Focus Areas
- scopes/workspace/pnpm-workspace/pnpm-workspace.compiler.ts[115-134]

## Recommended Fix
Make `sourceSignature()` content-sensitive by hashing each included source file's contents, along with its relative path, into a stable digest. Continue excluding generated and dependency directories, then use the digest as the `builtSignatures` cache value so an unchanged-size, unchanged-timestamp edit still triggers `pnpm run build`.

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


Grey Divider

Context sources
Review mode: 🧠 Deep: This is a broad, logic-dense cross-cutting change spanning workspace sync, import/clone behavior, dependency graphs, snapping, build environments, and lifecycle handling, with many independent paths where redundant review could catch subtle defects.

Grey Divider

Tip of the day
💡 Did you know, you can choose which labels appear on a finding, and whether they show icons or text

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread scopes/component/tracker/pnpm-vcs-sync.cmd.ts Outdated
Comment thread scopes/component/tracker/pnpm-vcs-sync.cmd.ts Outdated
Comment thread scopes/component/tracker/pnpm-vcs-sync.cmd.ts Outdated
Comment thread scopes/component/tracker/pnpm-vcs-sync.cmd.ts Outdated
Comment thread scopes/component/tracker/pnpm-vcs-sync.cmd.ts Outdated
Comment thread scopes/component/tracker/pnpm-vcs-sync.cmd.ts Outdated
Comment thread scopes/workspace/pnpm-workspace/pnpm-script.task.ts
…nd clone

In a pnpm-adopted workspace, the component writer and bit clone no longer write dependencies into
the root package.json or run bit's install and compile, which would also run the packages' build
scripts. They hint to run "pnpm install" instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread .claude/skills/pnpm-engine/SKILL.md
Comment thread scopes/workspace/pnpm-workspace/pnpm-workspace-sync.ts
Comment thread scopes/workspace/pnpm-workspace/pnpm-workspace-tree.ts
Comment thread scopes/component/tracker/pnpm-vcs-sync.cmd.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 920cefc

davidfirst and others added 3 commits September 23, 2026 17:15
- mark each synced project in its tracker config, so a later sync removes only its own components
  (nameless ones included) and can move one from the env it assigned to another --env
- drop a package name the package.json no longer gives
- read the default catalog by the same rule as import writes it
- clear a capsule's output dirs before copying a new build's output
- list only removals and a summary in the sync report, with the error symbol for removals
- skip schemaVersion in the e2e clean-status check

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment on lines +724 to +727
const dependency = dependencies.findByPkgNameOrCompId(dependencyName);
// declared in package.json but never imported by the code, so bit recorded no dependency - and no
// version to bind. the entry stays as the pnpm manifest has it, which is versioned with the root.
if (!dependency) continue;

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. Selective imports can fail to install 🐞 Bug ≡ Correctness

createPnpmVcsImportPlan() skips a missing local workspace: reference when
findByPkgNameOrCompId() cannot find that package in Bit's detected dependency data. Importing a
component without that sibling then leaves the manifest's workspace-only specifier unchanged, so
pnpm cannot resolve it in the target workspace.
Agent Prompt
## Issue description
Selective imports leave `workspace:` dependencies unchanged when Bit did not detect the declared package, producing a manifest pnpm cannot install when the sibling is absent.

## Fix Focus Areas
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[719-747]
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[496-523]

## Recommended Fix
When a missing local `workspace:` dependency has no detected exact version, do not silently omit it from the plan. Either derive a valid exact binding from persisted component metadata or fail the import before writing files with an actionable error identifying the unresolved package.

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

Comment on lines +96 to +97
lifecycle: 'runtime' | 'dev';
optional: boolean;

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

2. Peer components lose peer semantics 🐞 Bug ≡ Correctness

GraphComponentDependency.lifecycle permits only runtime and development values, and
_promoteGraphComponentDependencies() consequently has no peer-dependency target. A pnpm workspace
component declared as a peer is therefore omitted from graph promotion or retained under the wrong
lifecycle when its source imports that package, including for optional peers.
Agent Prompt
## Issue description
The new graph promotion model cannot represent peer dependencies, so pnpm workspace component peers are not persisted with their declared lifecycle and optional metadata.

## Fix Focus Areas
- scopes/component/snapping/version-maker.ts[93-123]
- scopes/component/snapping/version-maker.ts[391-412]
- scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[64-105]

## Recommended Fix
Extend graph conversion and promotion to represent peer lifecycle explicitly, route promoted peers to `component.peerDependencies`, and preserve `peerDependenciesMeta.optional` in dependency-resolver data.

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

Comment on lines +515 to +519
for (const componentMap of this.consumer.bitMap.components) {
const config = componentMap.config?.[DependencyResolverAspect.id];
const configuredPackageName = config && config !== '-' ? config.packageName : undefined;
if (typeof configuredPackageName === 'string' && configuredPackageName) {
result.set(configuredPackageName, componentMap.id);

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

3. Imports can target unavailable components 🐞 Bug ≡ Correctness

getWorkspaceComponentIdsByPackageName() indexes every bitmap entry without checking whether the
component is removed or available on the current lane. When a stale entry exposes the same package
name as a resolved package import, processPackages() replaces that package with a dependency on
the unavailable component and deletes the valid package dependency.
Agent Prompt
## Issue description
The new package-name lookup includes removed and other-lane bitmap entries, allowing dependency detection to promote installed packages to components unavailable in the current workspace state.

## Fix Focus Areas
- scopes/dependencies/dependencies/dependencies-loader/auto-detect-deps.ts[512-535]
- components/legacy/bit-map/bit-map.ts[244-263]

## Recommended Fix
Populate the package-name map only from components that are available on the current lane and are not removed. Also detect duplicate active package names rather than silently overwriting an earlier mapping.

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

Comment thread scopes/workspace/pnpm-workspace/pnpm-workspace.compiler.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

…s load as ESM

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment on lines +37 to +38
export function isPnpmWorkspaceRoot(workspacePath: string, bitMap: BitMap): boolean {
return Boolean(findWorkspaceRootMap(bitMap)) && fs.existsSync(path.join(workspacePath, PNPM_WORKSPACE_MANIFEST));

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. Ordinary workspaces enter pnpm mode 🐞 Bug ≡ Correctness

isPnpmWorkspaceRoot() and isPnpmWorkspace() treat any workspace-root component beside
pnpm-workspace.yaml as proof that the workspace was adopted by bit pnpm sync, without checking
durable adoption state. An ordinary Bit workspace using pnpm and a root component is therefore
routed through pnpm-specific behavior, causing component writes and clones to skip Bit installation
and compilation while imports may modify its pnpm manifest.
Agent Prompt
## Issue description
Pnpm workspace mode is inferred from the presence of a workspace-root component and `pnpm-workspace.yaml`, a combination ordinary Bit workspaces can also have. This causes installation, compilation, cloning, and import behavior to change even when `bit pnpm sync` never adopted the workspace.

## Fix Focus Areas
- scopes/workspace/workspace-root/workspace-root-data.ts[31-39]
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[176-180]
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[333-347]

## Recommended Fix
Persist an explicit, versioned pnpm-adoption marker on the workspace-root component during sync, and require that marker in both pnpm-workspace predicates instead of inferring adoption from the manifest’s existence. Ensure the marker survives cloning through the versioned bitmap.

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

Comment on lines +333 to +335
async function trackPnpmWorkspaceRoot(workspace: Workspace, tracker: TrackerMain): Promise<ComponentID> {
const existingId = workspace.consumer.bitMap.getComponentIdByRootPath(WORKSPACE_ROOT_DIR);
if (existingId) return existingId;

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

2. Workspace roots keep stale file lists 🐞 Bug ≡ Correctness

trackPnpmWorkspaceRoot() returns an existing root ID without invoking the tracker scan that
recomputes the root component’s files after nested component directories change. On subsequent
syncs, newly added projects can remain listed in both their component and the root, removed-project
files remain stale, and new root-level files are omitted from the root’s next snapshot.
Agent Prompt
## Issue description
An existing workspace-root component bypasses tracking during subsequent pnpm syncs, so its file inventory is never reconciled with current project directories or root files. The root can consequently snapshot stale, missing, or multiply owned files.

## Fix Focus Areas
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[333-347]
- scopes/component/tracker/add-components.ts[1009-1029]

## Recommended Fix
Rescan and update the existing workspace-root component on every sync after project additions and removals, while preserving its existing component ID and root configuration. Add rerun coverage for adding a project, removing a project, and adding a root-level file.

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

Comment on lines +707 to +709
const workspaceManifest = await readPnpmWorkspaceManifest(path.join(workspace.path, PNPM_WORKSPACE_MANIFEST));
const localPackageNames = await readLocalPackageNames(workspace.path, workspaceManifest.packages || []);
plannedComponents.forEach(({ packageName }) => localPackageNames.add(packageName));

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

3. Imports create duplicate workspace packages 🐞 Bug ≡ Correctness

createPnpmVcsImportPlan() collects imported package names into localPackageNames with
Set.add() but never rejects names that already belong to a local or another imported project.
applyPnpmImportPlan() then adds each imported root directory to pnpm-workspace.yaml, whereas the
sync path explicitly validates duplicate project identities before tracking them.
Agent Prompt
## Issue description
Import planning silently accepts package names that collide with an existing workspace project or another imported component, then adds all corresponding directories to the pnpm workspace.

## Fix Focus Areas
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[694-709]
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[430-463]

## Recommended Fix
Before returning the import plan, validate that every planned package name is unique and does not collide with package names already discovered in the target workspace. Throw a clear `BitError` identifying the duplicate package name and conflicting component or directory; add coverage for collisions with both local and batch-imported projects.

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

Comment on lines +120 to +122
await writePnpmWorkspaceTree(tree, treeDir);
await this.pnpm(['install', '--frozen-lockfile'], treeDir);
await fs.writeFile(markerPath, signature);

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

14. Build tasks reject lockfile-free workspaces 🐞 Bug ☼ Reliability

PnpmScriptTask.prepareTree() always runs pnpm install --frozen-lockfile after reconstructing the
workspace tree. Sync accepts a workspace based only on pnpm-workspace.yaml, so an adopted
workspace without a committed lockfile reaches that frozen installation before any build, test, or
lint script can run.
Agent Prompt
## Issue description
The isolated workspace tree is always installed with `--frozen-lockfile`, even when its reconstructed root contains no lockfile, causing every package-script task to fail before scripts run.

## Fix Focus Areas
- scopes/envs/pnpm-workspace-env/pnpm-script.task.js[115-122]
- scopes/envs/pnpm-workspace-env/pnpm-workspace-tree.js[157-165]

## Recommended Fix
Detect whether `pnpm-lock.yaml` exists in the reconstructed tree. Keep `--frozen-lockfile` when it exists, but perform a normal pnpm install when it does not so pnpm can generate the initial lockfile for the isolated build tree; add a test for a workspace root without a lockfile.

ⓘ 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 d8b6c28

…napped with, one install hint on clone

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
interactiveConfig?.generator || generator,
agentType,
{ skipDefaultMcp: userOptedOutOfMcp }
{ skipAgentInstructions: Boolean(noAgent), skipDefaultMcp: Boolean(noMcp) || userOptedOutOfMcp }

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. Init opt-outs still create files 🐞 Bug ≡ Correctness

handleInteractiveMode() writes the selected MCP configuration and agent rules before report()
passes noAgent and noMcp to the initializer. In an interactive Git-repository setup, selecting
an MCP editor therefore creates files even when the corresponding command-line opt-out was supplied.
Agent Prompt
## Issue description
Interactive initialization writes MCP configuration and agent rules before the new `--no-mcp` and `--no-agent` options reach the main initializer, making those opt-outs ineffective.

## Fix Focus Areas
- scopes/harmony/host-initializer/init-cmd.ts[78-119]
- scopes/harmony/host-initializer/init-cmd.ts[149-212]

## Recommended Fix
Pass the opt-out flags into `handleInteractiveMode()` and prevent `setupMcpServer()` when `noMcp` is set and `writeMcpAgentRules()` when `noAgent` is set. Preserve the flags when invoking `HostInitializerMain.init()` so both interactive and non-interactive paths honor them.

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

Comment on lines +124 to +125
const stat = await fs.stat(fullPath);
entries.push(`${path.relative(workspaceDir, fullPath)}:${stat.size}:${stat.mtimeMs}`);

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

16. Some source edits keep stale builds 🐞 Bug ☼ Reliability

sourceSignature() represents each source file using only its path, size, and modification
timestamp, while buildIfSourcesChanged() skips the package build whenever that metadata matches
its cache. An edit that preserves both size and timestamp, such as a metadata-preserving checkout or
filesystem with coarse timestamp resolution, is treated as unchanged and leaves the previous
compiled output in place.
Agent Prompt
## Issue description
The workspace compiler's cache key uses file size and modification time rather than file contents, allowing changed sources to reuse stale build output.

## Fix Focus Areas
- scopes/envs/pnpm-workspace-env/pnpm-workspace.compiler.js[84-91]
- scopes/envs/pnpm-workspace-env/pnpm-workspace.compiler.js[111-130]

## Recommended Fix
Build the source signature from stable relative paths and file-content hashes instead of size and timestamp metadata. Retain the existing excluded-directory filtering and deterministic path ordering.

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

Comment on lines +90 to +94
const treeDir = path.join(context.capsuleNetwork.capsulesRootDir, TREE_DIR);
try {
await this.prepareTree(tree, treeDir);
await this.pnpm(['-r', '--if-present', 'run', this.script], treeDir);
await copyOutputsToCapsules(context, tree, treeDir, OUTPUT_DIRS[this.script]);

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

2. Concurrent builds corrupt pnpm workspaces 🐞 Bug ☼ Reliability

PnpmScriptTask.execute() derives treeDir solely from the shared capsule root and the fixed
.pnpm-workspace name, while prepareTree() removes, rewrites, installs, and marks that directory.
When build concurrency runs distinct environments in parallel, their tasks can overwrite the same
reconstructed tree while scripts and output copying are in progress.
Agent Prompt
## Issue description
All pnpm script tasks reconstruct their workspace at the same `.pnpm-workspace` directory below `capsulesRootDir`. The build scheduler may execute tasks from different environments concurrently, so one task can remove or rewrite the tree while another is installing dependencies, running a script, or copying outputs.

## Fix Focus Areas
- scopes/envs/pnpm-workspace-env/pnpm-script.task.js[90-94]
- scopes/envs/pnpm-workspace-env/pnpm-script.task.js[115-122]

## Recommended Fix
Derive the reconstructed workspace path from a stable environment-specific value as well as the capsule root, such as a sanitized/hash-safe `context.envId`. Ensure build, test, lint, installation-marker, and output-copy operations for an environment use that same private directory, preventing tasks from separate environments from sharing mutable state.

ⓘ 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 ed5ef76

Comment on lines +113 to +117
const componentId = componentIdByPkgName.get(neighbour.name);
if (!componentId) continue;
graph.packages.set(neighbour.id, {
component: { scope: componentId.scope, name: componentId.fullName },
version: componentId.version ? snapToSemver(componentId.version) : 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.

Action required

1. Snaps replace registry dependencies 🐞 Bug ≡ Correctness

addWorkspaceComponentPackages() overwrites every root graph package whose name matches a workspace
component, even when the lockfile reference resolved to an ordinary registry package. A snap with a
same-named local project therefore records the local component identity and version instead of the
dependency selected by the package manager.
Agent Prompt
## Issue description
The dependency graph relabels registry-resolved packages as workspace components based solely on matching package names.

## Fix Focus Areas
- scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[88-123]

## Recommended Fix
Track which neighbours were created from verified `link:` or `workspace:` references and attach component metadata only to those neighbours. Preserve the existing lockfile package metadata for registry references, even when their names match workspace components.

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

Comment on lines +119 to +120
if (dirent.isDirectory()) {
if (!NON_SOURCE_DIRS.has(dirent.name)) await walk(fullPath);

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

16. Source edits can skip compilation 🐞 Bug ≡ Correctness

sourceSignature() refuses to descend into every directory named lib or build, although those
names can contain authored source rather than generated output. After one successful compilation,
changing such a file leaves the cached signature unchanged and buildIfSourcesChanged() returns
without running the package build script.
Agent Prompt
## Issue description
Incremental compilation excludes directories by basename, causing legitimate source changes under names such as `lib` and `build` to reuse stale build results.

## Fix Focus Areas
- scopes/envs/pnpm-workspace-env/pnpm-workspace.compiler.js[7-9]
- scopes/envs/pnpm-workspace-env/pnpm-workspace.compiler.js[84-91]
- scopes/envs/pnpm-workspace-env/pnpm-workspace.compiler.js[111-130]

## Recommended Fix
Compute the signature from tracked source files or exclude generated output only at known package output paths rather than skipping every directory with these basenames. Ensure changes beneath legitimate `lib` or `build` source directories alter the signature and rerun the build.

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

Comment on lines +141 to +143
directDependencies.push(
...importerDepsToNeighbours(componentDevImporter.devDependencies, 'dev', false, componentIdByPkgName)
);

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

2. Workspace package versions are lost 🐞 Bug ≡ Correctness

convertLockfileToGraph() passes componentIdByPkgName only while converting the project
importer’s development dependencies, then its component-root runtime loop drops every non-registry
link: or workspace: reference when refToRelative() returns null. This occurs when a component
is resolved through a root-components importer, so snapping can persist no component dependency for
a runtime pnpm workspace package and later installs do not receive the required exact component
version.
Agent Prompt
## Issue description
The component-root conversion path handles `link:` and `workspace:` references for development importer dependencies, but not for runtime or optional dependencies read from the locked package snapshot. As a result, direct runtime workspace-component dependencies can be omitted from the graph that snapping promotes into persisted Bit dependencies.

## Fix Focus Areas
- scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[141-162]

## Recommended Fix
Apply the same `componentIdByPkgName` fallback used by `importerDepsToNeighbours()` to the runtime and optional dependency loop. When a snapshot reference is a workspace-style non-registry reference and its package name maps to a component, create the snap-semver neighbour and ensure its package attributes identify that target as a component; retain the current behavior for unrelated non-registry references. Add coverage for the `componentRootDir` path with a runtime workspace dependency.

ⓘ 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 ed5ef76

davidfirst and others added 2 commits September 24, 2026 13:35
…en switching to a lane

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… rewrites

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 24, 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

…atalog entries an import wrote

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment on lines +467 to +468
const uncoveredRootDirs = plan.components.map(({ rootDir }) => rootDir).filter((dir) => !coveredRootDirs.has(dir));
if (uncoveredRootDirs.length) manifest.packages = [...(manifest.packages || []), ...uncoveredRootDirs];

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. Imported projects remain outside pnpm 🐞 Bug ≡ Correctness

applyPnpmImportPlan() appends an imported root to packages without removing an existing negated
pattern that matches the same directory. When a workspace excludes that root, both
readLocalPackageNames() and the next syncPnpmWorkspace() use discoverPnpmProjectManifests()'s
unconditional ignore list, so the imported package is neither locally bound nor synchronized as a
pnpm project.
Agent Prompt
## Issue description
Imported component roots are appended to `pnpm-workspace.yaml`, but matching negated package patterns still exclude them from pnpm discovery and later synchronization.

## Fix Focus Areas
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[462-468]
- scopes/component/tracker/pnpm-vcs-sync.cmd.ts[560-575]

## Recommended Fix
When applying an import plan, reconcile matching negated patterns so every imported root is effectively included. Remove or narrow exclusions that match imported roots, or introduce discovery semantics that explicitly allow imported roots to override those exclusions, and add coverage for importing into a previously excluded directory.

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

Comment on lines +141 to +143
directDependencies.push(
...importerDepsToNeighbours(componentDevImporter.devDependencies, 'dev', false, componentIdByPkgName)
);

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

2. Dev workspace dependencies disappear after snaps 🐞 Bug ≡ Correctness

convertLockfileToGraph() creates graph neighbours for link: and workspace: development
dependencies but does not call addWorkspaceComponentPackages() in its normal component-root path.
When a pnpm project is resolved through the root-components importer, those synthetic neighbours
have no package metadata, so componentDependenciesFromGraph() skips them and the snapped component
does not retain its development dependency.
Agent Prompt
## Issue description
The normal `convertLockfileToGraph()` path now preserves `link:` and `workspace:` development dependencies as root neighbours, but it does not add the corresponding workspace component metadata to `graph.packages`. Dependency promotion requires that metadata, so these development dependencies are omitted from the snapped component.

## Fix Focus Areas
- scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[125-184]
- scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[109-123]

## Recommended Fix
After `_convertLockfileToGraph()` returns in the normal component-root branch, call `addWorkspaceComponentPackages(graph, componentIdByPkgName)` before returning the graph, as the capsule conversion branch already does. Add coverage for a development `link:` or `workspace:` dependency in the component-root importer path and assert that its root neighbour has component metadata.

ⓘ 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 913c006

davidfirst and others added 2 commits September 24, 2026 17:19
…e import handling and the env

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
name: '@acme/app',
main: 'dist/index.js',
dependencies: { '@acme/math': 'workspace:*' },
scripts: { build: 'node build.js', test: "node -e \"require('./dist') === 3 && console.log('app works')\"" },

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. One test fixture fails formatting 📘 Rule violation ⚙ Maintainability

The scripts property in the app package fixture remains on one line beyond the configured
120-character print width. Running the canonical Prettier check over the E2E TypeScript files
reformats this changed line, causing the formatting check to fail.
Agent Prompt
## Issue description
The app package fixture's `scripts` property does not conform to the repository's Prettier configuration and fails the canonical formatting check.

## Fix Focus Areas
- e2e/harmony/add-harmony.e2e.ts[637-637]

## Recommended Fix
Run Prettier on the file or manually expand the `scripts` object using Prettier's expected multiline layout without changing the fixture values.

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

throw new BitError(`unable to find ${PNPM_WORKSPACE_MANIFEST} in ${workspace.path}`);
}
const workspaceManifest = await readPnpmWorkspaceManifest(workspaceManifestPath);
const projects = await discoverPnpmProjects(workspace.path, workspaceManifest.packages || []);

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

2. Sync removes every workspace project 🐞 Bug ≡ Correctness

syncPnpmWorkspace() converts an omitted packages field to an empty pattern list, even though the
manifest type permits that field to be absent. Re-running sync on such a pnpm workspace discovers no
projects and passes the empty inventory to removeLeftProjects(), which untracks unsnapped projects
and marks snapped projects as removed.
Agent Prompt
## Issue description
`syncPnpmWorkspace()` treats an omitted `packages` field as an explicit empty list. This makes synchronization discover no projects and remove components synchronized by an earlier run.

## Fix Focus Areas
- scopes/workspace/pnpm-workspace/pnpm-workspace-sync.ts[139-143]
- scopes/workspace/pnpm-workspace/pnpm-workspace-sync.ts[193-212]
- scopes/workspace/pnpm-workspace/pnpm-workspace-sync.ts[559-578]

## Recommended Fix
When `packages` is undefined, pass pnpm's default project pattern, such as `['**']`, to discovery while preserving an explicitly configured empty array as empty. Add coverage distinguishing an omitted field from `packages: []`, including a repeat sync that verifies existing projects are not removed.

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

Comment on lines +154 to +157
// a capsule is reused across builds, so what an earlier build left must not pass as this one's output
const target = path.join(capsule.path, outputDir);
await fs.rm(target, { recursive: true, force: true });
const source = path.join(treeDir, rootDir, outputDir);

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

15. Other environments lose build outputs 🐞 Bug ≡ Correctness

copyOutputsToCapsules() iterates every member loaded from the workspace root and removes each
matching capsule's output directories before checking whether the pnpm run produced replacements.
When the capsule graph contains components outside context.components, this task can erase dist,
build, or lib directories produced by another environment even during sequential task execution.
Agent Prompt
## Issue description
The pnpm task cleans output directories for every workspace member represented in the capsule graph, including components outside the environment currently executing the task. Outputs created by another environment can therefore be deleted without replacement.

## Fix Focus Areas
- scopes/workspace/pnpm-workspace/pnpm-script.task.ts[141-163]
- scopes/workspace/pnpm-workspace/pnpm-workspace-tree.ts[76-90]

## Recommended Fix
Build a set from `context.components` and restrict capsule output deletion and copying to tree members whose component IDs belong to that set. Add a test with an additional graph capsule outside the task context and verify its existing output directories remain unchanged.

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

Comment on lines +128 to +129
const stat = await fs.stat(fullPath);
entries.push(`${path.relative(workspaceDir, fullPath)}:${stat.size}:${stat.mtimeMs}`);

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

16. Source edits retain stale build output 🐞 Bug ≡ Correctness

sourceSignature() records only each file's path, size, and mtimeMs, and
buildIfSourcesChanged() skips pnpm run build when that metadata string matches the prior
successful build. When an edit preserves a source file's byte length and timestamp, such as through
timestamp-restoring tooling, every transpileComponent() call for that workspace copies the prior
dist output instead of recompiling.
Agent Prompt
## Issue description
The compiler cache key uses only source paths, file sizes, and modification timestamps. Content can change while retaining that metadata, causing the workspace build to be skipped and stale `dist` files to be copied to transpilation output.

## Fix Focus Areas
- scopes/workspace/pnpm-workspace/pnpm-workspace.compiler.ts[115-134]

## Recommended Fix
Make `sourceSignature()` content-sensitive by hashing each included source file's contents, along with its relative path, into a stable digest. Continue excluding generated and dependency directories, then use the digest as the `builtSignatures` cache value so an unchanged-size, unchanged-timestamp edit still triggers `pnpm run build`.

ⓘ 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 2ed18f1

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants