Skip to content

feat(workspace): workspace-root component (rootDir ".") and the trackAllFiles flag - #10698

Open
davidfirst wants to merge 110 commits into
masterfrom
feat/workspace-root-component-nesting
Open

davidfirst wants to merge 110 commits into
masterfrom
feat/workspace-root-component-nesting

Conversation

@davidfirst

@davidfirst davidfirst commented Sep 10, 2026

Copy link
Copy Markdown
Member

Context: the Bit side of pnpm/rfcs#33, Bit version control for pnpm workspaces: each pnpm project is a component and the unclaimed files belong to a root component. This PR lands that root-component model in bit; the adoption command and the pnpm-specific pieces follow, based on #10675.

Lets a single component own the workspace root (rootDir: "."), and adds a workspace flag that tracks the files bit normally treats as generated. Together they make a git-free workspace restorable from its scope: the root component carries the repository-level files and .bitmap, and the flag keeps package.json and friends.

Workspace-root component

On the name: "root component" already means the dependency-resolver's rootComponents (envs and apps installed as roots under node_modules/.bit_roots), and "workspace component" is every component loaded from a workspace (WorkspaceComponent). "Workspace-root component" is what rootDir: "." says, clashes with neither, and pairs with "nested components" for the ones inside it. Code uses the WORKSPACE_ROOT_DIR constant and the workspaceRoot prefix.

  • rootDir: "." is valid and is the only root-dir allowed to contain other components. Its file-set is everything under the root minus the nested components' root-dirs, re-scanned like any other component, so files added later are picked up. .bit/, .git/ and node_modules are never claimed.
  • It tracks .bitmap, with versions normalized on load so it converges after a snap. The writer never writes .bitmap back, so an imported root cannot create a phantom nested workspace.
  • bit add . --root tracks it. The flag spells out the intent, since bit add . is one keystroke from git add . and means something else entirely; without it the add is refused with a message naming the flag, and passing it where no path is the workspace root is refused too rather than ignored. Only creating a root needs it — re-adding one that exists does not, so the existing "already tracked by" error still surfaces. It is tracked with teambit.harmony/empty-env as explicit config (so env resolution and the dependency policy agree), and it is excluded from install and link. Its files are not parsed for dependencies either: nothing installs, links or builds the root, and repo scripts may require anything, so detection would only produce blocking issues with no consumer for the result. Its main file defaults to workspace.jsonc, the root has no entry point of its own; --main still overrides. bit remove and bit eject do not delete the workspace. Re-adding it is a no-op; a second root component is rejected at add time.
  • New core aspect teambit.workspace/workspace-root owns the concept. The root marks itself in its aspect data ({ "isRoot": true }), and that marker, not the files it carries, is what tells a root apart, e.g. on import onto .. On snap, every member of the workspace records the root it was snapped in, at the root's version after that snap: { "root": "scope/root@version" }. A new or modified root joins every bit tag and bit snap of its members, so the recorded version always has the files the member was made with; a root tagged along gets a patch bump of its own, whatever --ver the members got, and the command output says so. Both are data, not config, so they never make a component modified and the root moving on does not touch its members. The record tells a CI or a clone which root files (lockfile, tsconfig, scripts) a version was made with, and bit show prints it as "workspace root".
  • bit clone <root-id> [dir] makes a workspace out of it, the way git clone makes a working tree out of a repository. It runs outside a workspace, in an empty or absent directory (default: the component name), needs no bit init, lands the root files at the root (workspace.jsonc included; nothing bit init generates is added), imports every component the root's versioned .bitmap lists into the directory it records, then installs and compiles (-x to skip). The versioned .bitmap has no versions, so the components come at their heads on main; --lane <scope>/<name> clones the workspace as it is on a lane and comes out on it. A version on the root id pins the root files only. A component the root lists that its remote does not have is reported and skipped. --remote <url> registers a self-hosted scope in the new workspace first. bit import <root> --path . stays as the low-level primitive.
  • Importing the root component onto . without --override is accepted only in a fresh workspace (nothing else tracked), which is the restore flow; an established workspace gets the usual conflict error listing the root files that would be overwritten.

trackAllFiles

"trackAllFiles": true under teambit.workspace/workspace stops bit from dropping package.json, a root-level tsconfig.json and lint configs, and the npm/yarn lockfiles. Only git-ignored files and the hard exclusions stay out. Meant for workspaces adopted from an existing monorepo, where those files are the source of truth. Import writes the model's files regardless, so a component with a tracked package.json shows as modified in a workspace without the flag.

Tests

  • unit: bit-map.spec.ts (nesting rules, getNestedRootDirs, .bitmap normalization and the versioned-map reader), component-map.spec.ts (ignore logic with and without the flag) and determine-main-file.spec.ts (the root's main-file default), workspace-root-data.spec.ts (the root marker and the snapped-in root record).
  • e2e: add-harmony.e2e.ts covers root tracking, .bitmap convergence, a modified root joining a member's snap and tag, remove, re-add, checkout, import into another workspace and onto ., env defaults, adopt → export → bit clone with the flag, and bit clone --lane.

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

Copy link
Copy Markdown

PR Summary by Qodo

Allow components to own the workspace root

✨ Enhancement 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Permit one component to use the workspace root while preserving nesting restrictions elsewhere.
• Exclude nested components and Bit internals from the root component’s dynamic file set.
• Cover root ownership, rescanning, exclusion, and nesting behavior with unit and end-to-end tests.
Diagram

graph TD
  A["bit add ."] --> B["AddComponents"] --> C{"Root path?"}
  C -->|"Yes"| D["rootDir ."] --> F["BitMap exclusions"] --> G["Directory scan"] --> H["Owned files"]
  C -->|"No"| E["Component root"] --> F
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Explicit repository-file manifest
  • ➕ Avoids scanning the entire workspace root
  • ➕ Makes ownership immediately visible in configuration
  • ➖ New root files would require manual registration
  • ➖ Conflicts with normal component rescanning behavior
2. Resolve overlaps after scanning
  • ➕ Keeps the scanner API unchanged
  • ➕ Centralizes ownership conflict resolution
  • ➖ Scans nested component trees unnecessarily
  • ➖ Temporarily creates duplicate claims and increases memory usage

Recommendation: Keep the PR’s exclusion-based scanning approach. It preserves dynamic component discovery, prevents duplicate ownership at the source, and reuses the existing rescan lifecycle; explicit manifests would freeze membership, while post-scan reconciliation would add avoidable work and ambiguity.

Files changed (7) +181 / -22

Enhancement (5) +111 / -22
bit-map.tsSupport root ownership in BitMap nesting rules +28/-3

Support root ownership in BitMap nesting rules

• Exempts the workspace-root component from parent-directory conflicts and adds 'getNestedRootDirs()' to calculate exclusion boundaries. Both bitmap file loading and rescanning now pass those exclusions to the directory scanner.

components/legacy/bit-map/bit-map.ts

component-map.tsScan workspace-root components without overlapping files +54/-12

Scan workspace-root components without overlapping files

• Defines '.' as the canonical workspace-root directory and permits it during validation. Extends directory rescanning to exclude nested component roots, Bit metadata, Git metadata, and all nested 'node_modules' paths while retaining workspace-relative file paths.

components/legacy/bit-map/component-map.ts

index.tsExport the workspace-root directory constant +1/-0

Export the workspace-root directory constant

• Exports 'WORKSPACE_ROOT_DIR' from the bit-map package for consistent root-path handling across consumers.

components/legacy/bit-map/index.ts

consumer-component.tsExclude nested roots during component loading +5/-1

Exclude nested roots during component loading

• Passes BitMap-derived nested root directories into component file rescanning so loaded root components cannot claim nested component files.

components/legacy/consumer-component/consumer-component.ts

add-components.tsNormalize and track the workspace root safely +23/-6

Normalize and track the workspace root safely

• Normalizes an empty workspace-relative path to '.' and exempts the root owner from ordinary parent-directory conflicts. Initial file discovery subtracts existing nested component roots and accepts all remaining workspace files as being inside the tracked root.

scopes/component/tracker/add-components.ts

Tests (2) +70 / -0
bit-map.spec.tsTest workspace-root nesting and exclusion discovery +44/-0

Test workspace-root nesting and exclusion discovery

• Adds unit coverage proving that a '.' root component can coexist with nested components regardless of add order. It also verifies that non-root nesting remains invalid and nested root directories are calculated correctly.

components/legacy/bit-map/bit-map.spec.ts

add-harmony.e2e.tsVerify workspace-root tracking end to end +26/-0

Verify workspace-root tracking end to end

• Tests that 'bit add .' persists 'rootDir' as '.', discovers root files added after tracking, and excludes nested component files and Bit internals.

e2e/harmony/add-harmony.e2e.ts

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

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

Copy link
Copy Markdown

Code Review by Qodo

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

⚠️ 5 lower-priority findings omitted to fit the comment size limit; re-run the review or view the findings in the Qodo portal.

Grey Divider


Action required

1. Nested-ignore sorting fails formatting 📘 Rule violation ⚙ Maintainability ⭐ New
Description
The byDepth declaration exceeds the configured 120-character print width and has not been wrapped
as Prettier requires. Running the repository formatting check against this changed file reformats
that declaration, causing the check to fail.
Code

components/legacy/bit-map/component-map.ts[198]

+  const byDepth = Array.from(ignoreFileByDir).sort(([dirA], [dirB]) => dirA.split('/').length - dirB.split('/').length);
Evidence
Compliance rule 3 requires changed code to pass the repository formatting check. The added
declaration at line 198 exceeds the 120-character Prettier width configured by the repository and
remains unformatted.

CLAUDE.md: Code Changes Must Pass Repository Linting, Type Checking, and Formatting Standards
components/legacy/bit-map/component-map.ts[198-198]

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 `byDepth` declaration exceeds the repository's configured Prettier print width, so the formatting check fails.

## Fix Focus Areas
- components/legacy/bit-map/component-map.ts[198-198]

## Recommended Fix
Run Prettier on the file or manually wrap the `Array.from(ignoreFileByDir).sort(...)` expression across multiple lines in the form Prettier produces.

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


2. New versions retain obsolete root roles 🐞 Bug ≡ Correctness
Description
markWorkspaceRoot() returns undefined for every non-root and the component loader ignores falsy
data while shallow-merging truthy data, so the aspect entry loaded from the prior version is never
replaced. When a former root is moved under a directory, or a member becomes the root or is snapped
in a rootless workspace, the next version can retain isRoot or root, causing bit show and
clone eligibility to describe the obsolete workspace relationship.
Code

scopes/workspace/workspace-root/workspace-root.main.runtime.ts[R104-106]

+async function markWorkspaceRoot(component: Component): Promise<WorkspaceRootData | undefined> {
+  const consumerComponent = component.state._consumer as ConsumerComponent;
+  return consumerComponent.componentMap?.rootDir === WORKSPACE_ROOT_DIR ? { isRoot: true } : undefined;
Evidence
The new callback returns only { isRoot: true } or undefined, while the workspace loader skips
falsy results and uses Object.assign for truthy ones, so neither path removes old fields. The new
versioning method also returns immediately when no current root exists, and both public readers
trust these persisted fields directly.

scopes/workspace/workspace-root/workspace-root.main.runtime.ts[104-106]
scopes/workspace/workspace/workspace-component/workspace-component-loader.ts[1198-1215]
scopes/component/snapping/version-maker.ts[785-800]
scopes/workspace/workspace-root/workspace-root-data.ts[37-50]

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

## Issue description
Workspace-root aspect data from the prior component version is currently retained because the component-load callback returns no data for non-roots and the loader merges returned objects. Role changes and snapshots in rootless workspaces must remove obsolete `isRoot` and `root` fields before the new version is created.
## Fix Focus Areas
- scopes/workspace/workspace-root/workspace-root.main.runtime.ts[104-106]
- scopes/component/snapping/version-maker.ts[785-800]
- scopes/workspace/workspace/workspace-component/workspace-component-loader.ts[1198-1215]
## Recommended Fix
Explicitly replace or clear workspace-root aspect data when the current bitmap role differs from the loaded version: remove `root` when a component becomes the root, remove `isRoot` when it ceases to be the root, and clear the prior `root` pointer from components being versioned when the workspace has no current root. Avoid relying on the loader's shallow merge or an `undefined` callback result to remove fields.

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


3. Added components lose their main file 🐞 Bug ≡ Correctness
Description
addOneComponent() only rejects a scan-excluded main when it was already present in
filteredMatchedFiles, although _addMainFileToFiles() manually adds an existing excluded main
after the scan. Selecting a nested .bitmap, .git file, or another scan-excluded path as --main
therefore succeeds at add time and the next rescan removes that main file from the component.
Code

scopes/component/tracker/add-components.ts[R670-671]

+      if ((inFileSet && excludedFromScan) || excludedByIgnoreRules) {
+        throw new ExcludedMainFile(relativeToComponent(mainNormalized));
Evidence
The add path appends an existing requested main that was absent from the scan, while the new
condition only treats scan exclusion as an error if the main was already in the scanned result. The
normal rescan reapplies the same scan exclusions, so the persisted main is subsequently dropped.

scopes/component/tracker/add-components.ts[537-566]
scopes/component/tracker/add-components.ts[657-671]
components/legacy/bit-map/component-map.ts[94-99]
components/legacy/bit-map/component-map.ts[573-595]

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 main-file validation lets an explicit main that was excluded from the directory scan be appended to the component's initial file list. A later rescan consistently removes that file, leaving the component without its declared main file.
## Fix Focus Areas
- scopes/component/tracker/add-components.ts[650-672]
- components/legacy/bit-map/component-map.ts[94-99]
## Recommended Fix
Reject an explicit main whenever it is excluded by `filterByScanIgnorePatterns()`, regardless of whether it was in the filtered scan result. Keep the existing ignore-rule validation, but do not gate the scan-exclusion result on `inFileSet`.

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


View action required (5)
4. Root updates overwrite nested source files 🐞 Bug ≡ Correctness
Description
isOwnedByNestedComponent() compares incoming root file paths to nested root directories with a
case-sensitive prefix check. On a case-insensitive filesystem, casing that differs between an older
root version and the current nested root directory makes a nested file appear root-owned, so writing
that root version persists over the nested component's source.
Code

scopes/component/component-writer/component-writer.ts[R37-42]

+export function isOwnedByNestedComponent(
+  relativePath: PathLinuxRelative,
+  nestedRootDirs: PathLinuxRelative[]
+): boolean {
+  return nestedRootDirs.some((nestedRootDir) => relativePath.startsWith(`${nestedRootDir}/`));
+}
Evidence
The writer obtains nested directories from the live bitmap but only normalizes separators on
incoming file paths, then performs a case-sensitive startsWith check. A file that fails that check
is added to the persistence set and written by the root importer or checkout.

scopes/component/component-writer/component-writer.ts[37-42]
scopes/component/component-writer/component-writer.ts[121-129]
components/legacy/bit-map/bit-map.ts[269-284]

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

## Issue description
Workspace-root writes exclude files owned by nested components using a case-sensitive prefix comparison. On case-insensitive filesystems, paths that differ only by case can refer to the same nested file yet bypass that exclusion and be written from the root version.
## Fix Focus Areas
- scopes/component/component-writer/component-writer.ts[37-42]
- scopes/component/component-writer/component-writer.ts[121-129]
## Recommended Fix
Make nested ownership comparison filesystem-aware: normalize both paths to a common case on case-insensitive platforms before checking complete directory-segment ownership. Use that comparison consistently before adding root files to `DataToPersist`.

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


5. Pinned clones leave workspace residue ✓ Resolved 📘 Rule violation ☼ Reliability
Description
before() assigns clonePath directly under helper.scopes.e2eDir instead of using
helper.fs.createNewDirectory(), and only deletes that path before cloning. After the
pinned-version clone completes, the new Bit workspace remains unregistered with helper teardown and
persists in the shared end-to-end directory.
Code

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

+        clonePath = path.join(helper.scopes.e2eDir, 'pinned-root');
Evidence
Compliance rule 4 requires e2e workspaces to use helper-managed temporary directories with automatic
cleanup. The test manually selects and removes its clone directory before execution, while
createNewDirectory() registers generated directories in externalDirsArray so
cleanExternalDirs() removes them during teardown.

CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces: CLAUDE.md: Use E2E Workspace Helpers for Temporary Workspaces
e2e/harmony/add-harmony.e2e.ts[589-598]
components/legacy/e2e-helper/e2e-fs-helper.ts[163-175]
components/legacy/e2e-helper/e2e-fs-helper.ts[187-191]

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 pinned-version clone creates a workspace at a manually managed path that is not registered for automatic e2e cleanup.
## Fix Focus Areas
- e2e/harmony/add-harmony.e2e.ts[591-597]
## Recommended Fix
Create `clonePath` with `helper.fs.createNewDirectory()` and remove the manual pre-test deletion. The helper accepts the resulting empty directory for cloning and registers it for teardown cleanup.

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


6. Cloned members overwrite each other ✓ Resolved 🐞 Bug ≡ Correctness
Description
writeMembers() accepts every member destination from the versioned .bitmap without checking
whether two resolved directories are equal or one contains another. When such entries are imported
together, the writer temporarily relocates their paths but moves them back before persistence, so
colliding files are written to the same location and the later component replaces the earlier
component's content.
Code

scopes/workspace/workspace-root/clone.ts[185]

+      writeToPathPerId[entry.id] = resolveComponentDir(this.workspacePath, entry);
Evidence
The clone builds writeToPathPerId directly from every remote entry and submits all destinations in
one import. The component writer disambiguates equal or nested paths while preparing writers, but
moveComponentsIfNeeded() retrieves each original per-ID destination and schedules the component
back there before the accumulated data is persisted, allowing duplicate or nested targets to
converge on the same file path.

scopes/workspace/workspace-root/clone.ts[182-194]
scopes/component/component-writer/component-writer.main.runtime.ts[157-163]
scopes/component/component-writer/component-writer.main.runtime.ts[204-214]
scopes/component/component-writer/component-writer.main.runtime.ts[325-348]
scopes/component/component-writer/component-writer.main.runtime.ts[116-120]

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

## Issue description
A versioned workspace map can assign duplicate or nested directories to different members. Importing that map can persist multiple components into the same paths and overwrite member files.
## Fix Focus Areas
- scopes/workspace/workspace-root/clone.ts[182-194]
## Recommended Fix
Resolve and validate all member destinations before invoking the importer. Reject duplicate destinations and any ancestor/descendant pair with a clear clone error, using normalized path comparisons that respect platform path semantics.

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


7. Clones can corrupt internal storage ✓ Resolved 🐞 Bug ⛨ Security
Description
resolveComponentDir() verifies only that a member path stays lexically inside the workspace, so
reserved destinations such as .bit/evil, .git/evil, or node_modules/evil remain valid. A
crafted versioned .bitmap therefore reaches the importer and writer with an internal destination,
where missing subdirectories are created and populated with component files.
Code

scopes/workspace/workspace-root/clone.ts[R299-302]

+  // a leading ".." is only a way out when it is the whole segment: a directory may be named "..cache"
+  const climbsOut = relative === '..' || relative?.startsWith(`..${path.sep}`);
+  if (!target || !relative || climbsOut || path.isAbsolute(relative)) {
+    throw new BitError(
Evidence
The new validator checks type, absoluteness, workspace escape, and an empty relative path, but never
rejects reserved internal prefixes. Accepted paths are forwarded as component write destinations;
non-root writer checks allow absent directories, and persistence creates missing parent directories
when writing component files.

scopes/workspace/workspace-root/clone.ts[288-306]
scopes/workspace/workspace-root/clone.ts[182-194]
scopes/scope/importer/import-components.ts[1103-1113]
scopes/component/component-writer/component-writer.main.runtime.ts[476-502]
scopes/component/component-writer/component-writer.ts[121-129]
scopes/component/sources/abstract-vinyl.ts[41-55]

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

## Issue description
Remote workspace-map entries are allowed to place member components below Bit, Git, package-manager, or temporary metadata directories. Clone then writes component files into storage that must remain exclusively owned by those systems.
## Fix Focus Areas
- scopes/workspace/workspace-root/clone.ts[288-306]
## Recommended Fix
After resolving each member directory, reject paths whose workspace-relative first segment is a reserved scan directory such as `.bit`, `.git`, `.bitTmp`, or `node_modules`, along with legacy map locations. Apply normalized, platform-appropriate comparisons and return a clear malformed workspace-map error before importing any members.

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


8. Root tracking fails with nested ignores 🐞 Bug ≡ Correctness
Description
filterByIgnoreFiles() passes the Ignore matcher held in gitIgnore to ignore().add() instead
of passing ignore-pattern strings. Any workspace-root scan that discovers a nested .gitignore or
.bitignore reaches this branch during add or rescan, so the root component cannot be tracked or
refreshed.
Code

components/legacy/bit-map/component-map.ts[127]

+  const filteredByUserRules: PathLinux[] = ignore().add(gitIgnore).add(nestedPatterns).filter(relativePaths);
Evidence
The changed branch is entered whenever nested patterns are found, but gitIgnore is already a
matcher rather than a pattern collection. The new tests directly invoke this path with ignore() as
gitIgnore and nested ignore files, demonstrating that the workspace-root nested-ignore scenario
reaches the faulty call.

components/legacy/bit-map/component-map.ts[116-131]
components/legacy/bit-map/component-map.ts[612-619]
components/legacy/bit-map/bit-map.spec.ts[496-519]
components/legacy/bit-map/component-map.ts[573-603]

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

The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution
Issue description
`filterByIgnoreFiles()` constructs a new ignore matcher with an existing `Ignore` object passed to `add()`. Keep the root user patterns separately (or retrieve them again) and add only pattern strings before applying the rebased nested patterns; preserve applying Bit-owned generated-file exclusions last.
Fix Focus Areas
- components/legacy/bit-map/component-map.ts[116-131]
- components/legacy/bit-map/component-map.ts[612-639]
Recommended Fix
Change the data flow so `filterByIgnoreFiles()` receives or derives the root user ignore-pattern array, then build the combined matcher with that array plus `nestedPatterns`. Do not pass the matcher returned by `getGitIgnoreHarmony()` into `ignore().add()`.

ⓘ 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, high-density feature spanning tracking, importing/cloning, component writing, snapping, installation, and workspace state, with many independent logic paths and substantial blast radius.

Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread scopes/component/tracker/add-components.ts
Comment thread scopes/component/tracker/add-components.ts
Comment thread components/legacy/bit-map/component-map.ts Outdated
Comment thread scopes/component/tracker/add-components.ts Outdated
Comment thread components/legacy/bit-map/bit-map.ts Outdated
Comment thread components/legacy/bit-map/component-map.ts Outdated
@davidfirst

Copy link
Copy Markdown
Member Author

Follow-up: the root component now tracks .bitmap as well.

Tracking it verbatim does not converge — snapping rewrites every entry's version, including the root component's own, so the component is modified again the instant it is snapped, forever. Confirmed on a scratch workspace: the post-snap diff was nothing but version fields.

So only the durable part of the map is versioned: version and scope are emptied before the content is hashed (normalizeBitmapContentForVersioning), while name, defaultScope, mainFile and rootDir are kept. Versions are restored from the component heads on import, which is the correct source for them anyway. The .bitmap on disk is untouched — only the versioned copy is normalized.

Also fixed: adding a component inside the workspace root used to fail with "files already used by component", because the root had already claimed them. The root now yields to the more specific component and drops those files on its next scan.

Comment thread components/legacy/bit-map/bit-map.ts Outdated
Comment thread components/legacy/bit-map/bit-map.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

@davidfirst

Copy link
Copy Markdown
Member Author

Went through all 24 component issues one by one against the workspace-root component. The result was not "none of them are relevant" — testing changed the answer.

I first ignored everything dependency-derived (18 issues). That made things worse: with RelativeComponents suppressed, a root-level file with a relative import into a component dir gets past the friendly issue and dies at the model layer with unable to save Version object [...] dependencies should not have relativePaths followed by This error should have never happened. Please report this issue on Github. The issue was the only thing producing an actionable message for a real, unsupported situation.

So the list is narrowed to the three that misfire for a structural reason — the root component has no env toolchain, no compiler, and nothing imports it as a package:

  • MissingManuallyConfiguredPackages — the env dependency policy (@types/node and friends) is not installed for a component with no env toolchain. This was the actual blocker.
  • MissingDists — no compiler, so never any dist output.
  • MissingLinksFromNodeModulesToSrc — nothing resolves it as a package.

Everything else is kept. The dependency-related issues never fire for a component whose files hold no imports, so ignoring them buys nothing and costs the guard when they do fire.

Net effect: bit snap ws-root now works with no --ignore-issues flag, and bit status reports the root component as clean while still reporting real problems on it.

Comment thread scopes/component/tracker/add-components.ts Outdated
Comment thread components/legacy/bit-map/component-map.ts Outdated
Comment thread scopes/component/issues/issues.main.runtime.ts Outdated
Comment thread scopes/component/tracker/add-components.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

…nd write paths

the workspace-root component (rootDir ".") is a bag of the workspace's own config
files. three things treated it as a regular source component:

- env: it defaulted to the regular default env, giving it a compiler and a
  dependency policy it can never use. it now defaults to the empty env. an env
  set explicitly on it still wins.
- install: its dir is the workspace root, so handing it to the package manager
  collided with the root project - pnpm resolved it to an empty "file:" spec and
  failed to build the lockfile, breaking "bit install" entirely.
- write: importing it into another workspace wrote a .bitmap into a
  sub-directory, silently turning that dir into a broken nested workspace, and
  checking out an earlier version of it crashed on a non-BitError.

the empty env removes the compiler-derived issue structurally, so the
issue-ignore list added for this component is no longer needed and is reverted.
@davidfirst

Copy link
Copy Markdown
Member Author

Follow-up on two questions raised in review: what happens when a workspace-root component is imported, and what env it should get.

Env. It was defaulting to the regular default env, which hands a bag of config files a compiler and a dependency policy it can never satisfy. It now defaults to teambit.harmony/empty-env (which already exists as a core aspect). An env set explicitly on the component still wins — only the fallback changed.

This turned out to be the better fix for the component-issues question. With no compiler, MissingDists can't fire at all, so it's handled structurally rather than suppressed. And MissingManuallyConfiguredPackages was never root-specific — it fires for every component in a workspace that hasn't been installed yet, and clears on bit install. So the issue-ignore list from the previous commit is reverted: no issue-level special-casing is needed.

Import. Two real bugs, both reproduced:

  1. Importing a workspace-root component into another workspace wrote its .bitmap into the target sub-directory. .bitmap is what marks a workspace root, so that directory became a broken nested workspace — running any bit command from there operated on it instead of the real workspace, reporting the foreign components as new/invalid.
  2. bit checkout <version> and bit checkout reset on the component crashed with a raw addComponentToBitMap: rootDir cannot be "." — a plain Error, so it surfaced as an internal failure.

Fixed by never writing .bitmap from the model (writing it into a sub-directory corrupts, writing it onto the root would clobber the live map with a stale one while the operation is mutating it), and by allowing . as a rootDir only for the component that owns this workspace's root, with a proper BitError otherwise.

Third bug found on the way: bit install failed outright in any workspace with a root component — its dir is the workspace root, so it collided with the package manager's root project and pnpm produced an empty file: spec (Failed to parse suffix: Empty path after 'file:' scheme). It's now excluded from the install/link machinery.

17 e2e + 10 unit passing, lint clean.

Comment thread scopes/component/component-writer/component-writer.ts Outdated
Comment thread scopes/component/tracker/add-components.ts Outdated
Comment thread scopes/component/tracker/add-components.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 016b3c4

- remove/eject: rootDir "." was passed to RemovePath with recursive deletion, so
  removing the root component wiped the entire workspace - nested components,
  .bit, .bitmap and unrelated files. its files are the workspace's own, so
  untracking it now leaves them in place.
- re-adding "bit add ." threw, since files were compared against a "./" prefix
  they never have.
- a second component claiming the workspace root was accepted, then failed
  .bitmap's duplicate-rootDir validation on the next load. now rejected with a
  message naming the current owner.
- "bit add ." skipped dotfiles and enumerated node_modules; it now uses the same
  ignore list as the rescan, so both agree on what the root component owns.
- .bitTmp and the legacy .bit.map.json are excluded from the root file-set.
- the .bitignore/.gitignore lookup resolved against the process cwd rather than
  the workspace.
- the writer rejected a rootDir of "." whenever no .bitmap entry existed yet,
  which also blocked restoring a stashed root component. it now rejects only
  when a different component owns the root.
- .bitmap normalization no longer clears "scope": unlike "version" it is stable
  after the first export, and clearing it collapsed components from other scopes
  onto the workspace default on restore.
Comment thread e2e/harmony/add-harmony.e2e.ts
Comment thread components/legacy/bit-map/component-map.ts Outdated
Comment thread scopes/component/component-writer/component-writer.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

…nto "."

"bit import <root-component> --path ." crashed with an undefined path: "--path ."
resolves to an empty relative path, which was stored as an empty rootDir. it is
now normalized to ".", and the workspace root - which always holds .bit, .bitmap
and workspace.jsonc - is no longer rejected as "not empty" for the component
that owns it. this is the flow that restores a git-free workspace from its scope.
Comment thread components/legacy/bit-map/bit-map.ts Outdated
Comment thread components/legacy/bit-map/component-map.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 99c1fd8

Comment thread e2e/harmony/add-harmony.e2e.ts
Comment thread scopes/component/tracker/add-components.ts Outdated
Comment thread scopes/component/component-writer/component-writer.main.runtime.ts Outdated
Comment thread scopes/component/tracker/add-components.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

…t-generated files

bit drops package.json, a root-level tsconfig.json and lint configs, and the npm/yarn
lockfiles from every component because it generates them. a workspace adopted from an
existing monorepo owns those files, and without them a workspace restored from the scope
can be neither installed nor built. with "trackAllFiles": true in teambit.workspace/workspace,
only the git-ignored files and the hard exclusions (node_modules, .env, ...) are left out.
@davidfirst davidfirst changed the title feat(bit-map): allow a component to own the workspace root (rootDir ".") feat(workspace): workspace-root component (rootDir ".") and the trackAllFiles flag Sep 11, 2026
Comment thread components/legacy/bit-map/component-map.ts Outdated
Comment thread components/legacy/consumer-component/consumer-component.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

Comment thread components/legacy/bit-map/component-map.ts
Comment thread scopes/component/tracker/add-components.ts
Comment thread scopes/component/tracker/add-components.ts
Comment thread scopes/component/component-writer/component-writer.main.runtime.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 6ff664e

…with no root

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread e2e/harmony/add-harmony.e2e.ts
Comment thread scopes/workspace/watcher/watcher.ts
Comment thread scopes/component/snapping/version-maker.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

…inherited

the data is supplied fresh by the loader on each load and replaced wholesale by
writeWorkspaceRoot, so neither clear had anything to undo. verified by stubbing
both to no-ops: the add-harmony e2e file passed all 64 cases unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread e2e/harmony/add-harmony.e2e.ts
Comment thread scopes/workspace/workspace-root/workspace-root.main.runtime.ts
Comment thread components/legacy/bit-map/component-map.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 8b59694

"bit add ." is one keystroke from "git add ." and meant something else until now,
so the intent is spelled out. a workspace that already has a root does not ask
again - the existing "already tracked by" message is the useful one there.

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

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread e2e/harmony/add-harmony.e2e.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

…view

the symlink guard for a root write now accounts for the config file the writer
enables by itself when one is already at the rootDir; an explicit main file is
checked against the scan exclusions, not only the ignore rules, so it cannot be
tracked and then dropped by the next rescan; a merge snap brings a new or
modified root along as tag and snap do; and the watcher keeps ignoring the
never-tracked files when trackAllFiles is on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread scopes/workspace/workspace-root/clone.ts
Comment thread scopes/component/tracker/add-components.ts
Comment thread scopes/component/component-writer/component-writer.main.runtime.ts
Comment thread scopes/component/tracker/add-components.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 113ad10

…ut the flag

naming "." as the rootDir is already the intent, as a resolved track-data entry
declaring it is. the flag is for "bit add", where the path can be typed out of
git habit - asking a caller of track() for it answered with a command it is not
running. also covers the clone report for members their remote does not have,
and pins that a component tracked at the root skips the dir-conflict check like
any other tracked directory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread scopes/component/snapping/snapping.main.runtime.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 49aefcb

the tag-along is for the members a merge snaps. a batch of hidden lane entries
has no workspace state to snap the root against and records no root, so there is
nothing for a root version to make right.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread e2e/harmony/add-harmony.e2e.ts
Comment thread scopes/workspace/workspace-root/clone.ts
Comment thread scopes/workspace/workspace-root/clone.ts
Comment thread components/legacy/bit-map/component-map.ts
Comment thread scopes/workspace/workspace-root/clone.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

…t name

the versioned .bitmap comes from a remote, so a member is no longer written into
a directory bit or git keeps for itself (.bit holds the objects the clone reads
from), nor into one another member already owns - the writer undoes its own
relocation, so the later component would land on the earlier one's files.

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

Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

Sorry, something went wrong

We weren't able to complete the code review on our side. Please try again manually by commenting /agentic_review on this PR.

Grey Divider

Qodo Logo

it patched five filenames of a general case: a workspace-root component owns the
whole tree, so every file at the root it does not track is reported as inside a
component that ignores it - git-ignored output included, which the watcher never
consulted. naming a handful of them fixes nothing and hides component.json,
which people edit by hand.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment on lines +104 to +106
async function markWorkspaceRoot(component: Component): Promise<WorkspaceRootData | undefined> {
const consumerComponent = component.state._consumer as ConsumerComponent;
return consumerComponent.componentMap?.rootDir === WORKSPACE_ROOT_DIR ? { isRoot: true } : 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. New versions retain obsolete root roles 🐞 Bug ≡ Correctness

markWorkspaceRoot() returns undefined for every non-root and the component loader ignores falsy
data while shallow-merging truthy data, so the aspect entry loaded from the prior version is never
replaced. When a former root is moved under a directory, or a member becomes the root or is snapped
in a rootless workspace, the next version can retain isRoot or root, causing bit show and
clone eligibility to describe the obsolete workspace relationship.
Agent Prompt
## Issue description
Workspace-root aspect data from the prior component version is currently retained because the component-load callback returns no data for non-roots and the loader merges returned objects. Role changes and snapshots in rootless workspaces must remove obsolete `isRoot` and `root` fields before the new version is created.

## Fix Focus Areas
- scopes/workspace/workspace-root/workspace-root.main.runtime.ts[104-106]
- scopes/component/snapping/version-maker.ts[785-800]
- scopes/workspace/workspace/workspace-component/workspace-component-loader.ts[1198-1215]

## Recommended Fix
Explicitly replace or clear workspace-root aspect data when the current bitmap role differs from the loaded version: remove `root` when a component becomes the root, remove `isRoot` when it ceases to be the root, and clear the prior `root` pointer from components being versioned when the workspace has no current root. Avoid relying on the loader's shallow merge or an `undefined` callback result to remove fields.

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

Comment on lines +670 to +671
if ((inFileSet && excludedFromScan) || excludedByIgnoreRules) {
throw new ExcludedMainFile(relativeToComponent(mainNormalized));

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. Added components lose their main file 🐞 Bug ≡ Correctness

addOneComponent() only rejects a scan-excluded main when it was already present in
filteredMatchedFiles, although _addMainFileToFiles() manually adds an existing excluded main
after the scan. Selecting a nested .bitmap, .git file, or another scan-excluded path as --main
therefore succeeds at add time and the next rescan removes that main file from the component.
Agent Prompt
## Issue description
The new main-file validation lets an explicit main that was excluded from the directory scan be appended to the component's initial file list. A later rescan consistently removes that file, leaving the component without its declared main file.

## Fix Focus Areas
- scopes/component/tracker/add-components.ts[650-672]
- components/legacy/bit-map/component-map.ts[94-99]

## Recommended Fix
Reject an explicit main whenever it is excluded by `filterByScanIgnorePatterns()`, regardless of whether it was in the filtered scan result. Keep the existing ignore-rule validation, but do not gate the scan-exclusion result on `inFileSet`.

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

Comment on lines +37 to +42
export function isOwnedByNestedComponent(
relativePath: PathLinuxRelative,
nestedRootDirs: PathLinuxRelative[]
): boolean {
return nestedRootDirs.some((nestedRootDir) => relativePath.startsWith(`${nestedRootDir}/`));
}

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. Root updates overwrite nested source files 🐞 Bug ≡ Correctness

isOwnedByNestedComponent() compares incoming root file paths to nested root directories with a
case-sensitive prefix check. On a case-insensitive filesystem, casing that differs between an older
root version and the current nested root directory makes a nested file appear root-owned, so writing
that root version persists over the nested component's source.
Agent Prompt
## Issue description
Workspace-root writes exclude files owned by nested components using a case-sensitive prefix comparison. On case-insensitive filesystems, paths that differ only by case can refer to the same nested file yet bypass that exclusion and be written from the root version.

## Fix Focus Areas
- scopes/component/component-writer/component-writer.ts[37-42]
- scopes/component/component-writer/component-writer.ts[121-129]

## Recommended Fix
Make nested ownership comparison filesystem-aware: normalize both paths to a common case on case-insensitive platforms before checking complete directory-segment ownership. Use that comparison consistently before adding root files to `DataToPersist`.

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

Comment on lines +183 to +189
const writeToPathPerId: Record<string, string> = {};
entries.forEach((entry) => {
if (entry.rootDir === WORKSPACE_ROOT_DIR) return;
writeToPathPerId[entry.id] = resolveComponentDir(this.workspacePath, entry);
});
throwForOverlappingDirs(writeToPathPerId);
const ids = Object.keys(writeToPathPerId);

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

12. A clone silently omits a listed member 🐞 Bug ☼ Reliability

writeMembers() indexes destinations in a normal object using the unvalidated member ID, so
assigning an entry named __proto__ invokes the inherited prototype setter instead of creating an
enumerable destination. Object.keys() then excludes that listed member from the import request,
leaving it absent from both the cloned components and the missing-members report.
Agent Prompt
## Issue description
Clone member destinations are keyed by IDs parsed from the versioned bitmap in a normal JavaScript object. The special `__proto__` key is not retained as an enumerable own property, so that member is omitted before importing and is never reported as missing.

## Fix Focus Areas
- scopes/workspace/workspace-root/clone.ts[180-199]
- components/legacy/bit-map/bit-map.ts[1212-1228]

## Recommended Fix
Store destinations in `Object.create(null)` or a `Map`, and derive the import ID list from that safe collection. Validate parsed member IDs before use so malformed bitmap entries fail explicitly rather than being silently skipped.

ⓘ 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 4ba95d3

// git reads the ignore files of a path from the shallowest down, so a deeper rule decides over the
// one above it. the scan hands them over in whatever order it walked, so they are ordered here
// rather than by that: the patterns are applied in the order they are returned.
const byDepth = Array.from(ignoreFileByDir).sort(([dirA], [dirB]) => dirA.split('/').length - dirB.split('/').length);

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. Nested-ignore sorting fails formatting 📘 Rule violation ⚙ Maintainability

The byDepth declaration exceeds the configured 120-character print width and has not been wrapped
as Prettier requires. Running the repository formatting check against this changed file reformats
that declaration, causing the check to fail.
Agent Prompt
## Issue description
The `byDepth` declaration exceeds the repository's configured Prettier print width, so the formatting check fails.

## Fix Focus Areas
- components/legacy/bit-map/component-map.ts[198-198]

## Recommended Fix
Run Prettier on the file or manually wrap the `Array.from(ignoreFileByDir).sort(...)` expression across multiple lines in the form Prettier produces.

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

Comment on lines +124 to +126
// the live map is never written from a versioned copy, see isWorkspaceMapFile
if (isWorkspaceMapFile(relativePath)) return;
if (isOwnedByNestedComponent(relativePath, nestedRootDirs)) return;

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

12. Imported older components lose their map 🐞 Bug ≡ Correctness

ComponentWriter.populateComponentsFilesToWrite excludes every file named .bitmap before
persisting it, without checking whether the component is a workspace-root component. An ordinary
component version that contains a top-level map file is therefore imported without that file, while
checkout deletion treats only components with rootDir === "." as special.
Agent Prompt
## Issue description
The component writer now suppresses `.bitmap` for every component. Suppression is required for workspace-root components so importing one cannot create a nested workspace, but ordinary component versions that contain this file must retain their versioned contents when imported or checked out.

## Fix Focus Areas
- scopes/component/component-writer/component-writer.ts[121-129]
- scopes/component/checkout/checkout-version.ts[127-134]

## Recommended Fix
Make the write-time `.bitmap` exclusion conditional on the component being a workspace-root component (using its root marker or equivalent root-specific state), rather than on the filename alone. Keep the corresponding checkout/remove behavior aligned so ordinary components preserve and update their versioned `.bitmap` files.

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

Comment on lines +183 to +187
const writeToPathPerId: Record<string, string> = {};
entries.forEach((entry) => {
if (entry.rootDir === WORKSPACE_ROOT_DIR) return;
writeToPathPerId[entry.id] = resolveComponentDir(this.workspacePath, entry);
});

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

13. Malformed root maps omit listed members 🐞 Bug ≡ Correctness

writeMembers() stores destinations in writeToPathPerId under entry.id, so a later parsed entry
silently overwrites an earlier entry with the same logical ID. readVersionedBitmapEntries()
derives IDs from independently supplied map keys and name/scope fields without rejecting duplicates,
leaving the overwritten listed member absent from both the import request and the missing-members
result.
Agent Prompt
## Issue description
Workspace cloning silently collapses two versioned `.bitmap` entries that resolve to the same logical component ID. The second destination overwrites the first in the per-ID import map, so the clone reports neither an error nor the discarded entry as missing.

## Fix Focus Areas
- scopes/workspace/workspace-root/clone.ts[180-198]
- components/legacy/bit-map/bit-map.ts[1212-1228]

## Recommended Fix
Validate that parsed versioned bitmap entries have unique logical IDs before constructing the per-ID destination map, and throw a clear BitError identifying the duplicate entries. Alternatively, perform the same duplicate check in `readVersionedBitmapEntries()` so all consumers receive validated entries.

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

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