Skip to content

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

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

davidfirst wants to merge 43 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 . tracks it 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. On snap, every member of the workspace records the root it was snapped in, at the root's version after that snap, as aspect data: { "root": "scope/root@version" }. Data, not config, so it never makes a member modified and the root moving on does not touch them. It 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". The root itself records nothing.
  • bit import <root> --path . restores it onto an empty workspace.
  • 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), 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 snapped-in root record).
  • e2e: add-harmony.e2e.ts covers root tracking, .bitmap convergence, remove, re-add, checkout, import into another workspace and onto ., env defaults, and adopt → export → restore with the flag.

@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 (6) 📘 Rule violations (3) 📜 Skill insights (0)

⚠️ 4 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. Redundant add flows slow test runs 📘 Rule violation ➹ Performance ⭐ New
Description
add-harmony.e2e.ts invokes the CLI to verify main-file selection and same-component re-add
behavior already covered by colocated unit specifications. These single-workspace variants require
neither a remote scope nor a cross-command restore flow, so every E2E run executes unnecessary
workspace commands.
Code

e2e/harmony/add-harmony.e2e.ts[R197-199]

+    it('should allow re-adding the same component', () => {
+      helper.fs.outputFile('extra.md', 'extra\n');
+      expect(() => helper.command.addComponent('.', { i: 'ws-root' })).to.not.throw();
Evidence
Rule 2 reserves E2E coverage for behavior requiring real cross-command workspace or remote-scope
flows. The changed E2E invokes another add command for behavior directly exercised by the colocated
main-file and bitmap unit specifications.

CLAUDE.md: Prefer Minimal Unit Tests and Reserve E2E Tests for Cross-Command Workspace Flows
e2e/harmony/add-harmony.e2e.ts[188-216]
scopes/component/tracker/determine-main-file.spec.ts[23-31]
components/legacy/bit-map/bit-map.spec.ts[199-216]

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 added E2E cases exercise main-file selection and repeated component addition that colocated unit specifications already cover, increasing E2E runtime without testing a cross-command or remote-scope flow.

## Fix Focus Areas
- e2e/harmony/add-harmony.e2e.ts[188-216]
- scopes/component/tracker/determine-main-file.spec.ts[23-31]
- components/legacy/bit-map/bit-map.spec.ts[199-216]

## Recommended Fix
Remove the redundant E2E variants and retain or extend the existing unit specifications for explicit main-file selection, preserving an existing main file during re-tracking, and rejecting or accepting duplicate root ownership.

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


2. Scanner formatting fails the gate 📘 Rule violation ⚙ Maintainability ⭐ New
Description
getNestedIgnorePatterns() and its new scanner tests leave expressions beyond the configured
120-column layout instead of Prettier's wrapped form. When npm run prettier:check scans the
changed component files, Prettier lists them as different and the formatting gate exits
unsuccessfully.
Code

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

+      const patterns = name === BIT_IGNORE ? await getBitIgnoreFile(absoluteDir) : await getGitIgnoreFile(absoluteDir);
Evidence
Rule 4 requires every changed file to pass npm run prettier:check. The cited changed source and
test regions contain single-line expressions that the repository's 120-column Prettier configuration
reformats into multiline layouts.

CLAUDE.md: Code Must Conform to Repository Prettier Formatting
components/legacy/bit-map/component-map.ts[180-180]
components/legacy/bit-map/component-map.spec.ts[123-125]
components/legacy/bit-map/component-map.spec.ts[155-156]

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

## Issue description
Changed scanner source and test expressions do not match the repository's configured Prettier layout, causing the canonical formatting check to fail.

## Fix Focus Areas
- components/legacy/bit-map/component-map.ts[127-193]
- components/legacy/bit-map/component-map.spec.ts[15-156]

## Recommended Fix
Run the repository Prettier formatter on both files and commit its multiline wrapping, then verify them with `npm run prettier:check`.

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


3. Root checkouts overwrite outside files 🐞 Bug ⛨ Security
Description
getWriteParamsOfOneComponent() invokes throwForSymlinksInTheWay() only when its initial path is
., before an existing bitmap entry changes the writer's destination to the workspace root. A
checkout supplies no explicit path, so checking out a root file whose destination or ancestor is a
symlink writes through that link to its target outside the workspace.
Code

scopes/component/component-writer/component-writer.main.runtime.ts[R282-285]

+    if (componentRootDir === WORKSPACE_ROOT_DIR) {
+      this.throwForNonWorkspaceRootComponent(component);
+      this.throwForSymlinksInTheWay(component);
+    }
Evidence
Checkout calls the writer without writeToPath, while ComponentWriter later replaces the default
destination with the existing bitmap root. The added protection therefore does not execute even
though persistence ultimately writes the files at ..

scopes/component/checkout/checkout.main.runtime.ts[195-207]
scopes/component/component-writer/component-writer.main.runtime.ts[278-301]
scopes/component/component-writer/component-writer.ts[78-90]
scopes/component/component-writer/component-writer.ts[157-167]
scopes/component/sources/data-to-persist.ts[120-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
Workspace-root checkouts bypass symlink validation because the check runs before the existing bitmap entry changes the final write path to `.`.
## Fix Focus Areas
- scopes/component/component-writer/component-writer.main.runtime.ts[278-301]
- scopes/component/component-writer/component-writer.ts[78-90]
## Recommended Fix
Resolve the existing component map and final write destination before applying workspace-root validation, then run the symlink check whenever that final destination is `.`.

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


View action required (11)
4. Removing the root drops a dependency 🐞 Bug ≡ Correctness
Description
removeLocal() still passes the workspace-root component to removeComponentsFromDependencies(),
although the new filtering excludes it only from node_modules cleanup. When the workspace declares
an unrelated dependency with the root component's derived package name, removing the component
deletes that dependency from the root package.json.
Code

scopes/component/remove/remove-components.ts[R192-194]

+  const linkedComponents = components.filter(
+    (c) => consumer.bitMap.getComponentIfExist(c.id, { ignoreVersion: true })?.rootDir !== WORKSPACE_ROOT_DIR
+  );
Evidence
The remove flow invokes package.json cleanup before the new root-aware node_modules filter. That
cleanup derives a package name for every supplied component and deletes the matching dependency key
without checking its root directory.

scopes/component/remove/remove-components.ts[167-175]
scopes/component/remove/remove-components.ts[188-200]
components/legacy/consumer/consumer.ts[80-88]
scopes/component/sources/package-json-file.ts[241-249]
components/modules/component-package-name/component-id-to-package-name.ts[24-39]

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 workspace-root component is excluded from node_modules removal but still reaches package.json dependency cleanup, which can delete an unrelated dependency sharing its derived name.
## Fix Focus Areas
- scopes/component/remove/remove-components.ts[167-195]
## Recommended Fix
Partition workspace-root components before cleanup and exclude them from both `removeComponentsFromDependencies()` and `removeComponentsFromNodeModules()`, while retaining normal bitmap untracking.

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


5. Nested rules cannot restore files ✓ Resolved 🐞 Bug ≡ Correctness
Description
filterByIgnoreFiles() removes paths with gitIgnore.filter(relativePaths) before evaluating the
nested ignore patterns. When a root ignore rule excludes a file and a nested .gitignore or
.bitignore later negates that rule, the file is already absent and cannot be re-included in the
workspace-root component.
Code

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

+  const filteredByRoot: PathLinux[] = gitIgnore.filter(relativePaths);
+  if (dir !== WORKSPACE_ROOT_DIR) return filteredByRoot;
+  const nestedPatterns = await getNestedIgnorePatterns(consumerPath, gitIgnore, relativePaths);
+  if (!nestedPatterns.length) return filteredByRoot;
+  const filteredByUserRules: PathLinux[] = ignore().add(gitIgnore).add(nestedPatterns).filter(relativePaths);
Evidence
The implementation discards root-matched files before passing the remaining paths to the matcher
containing nested patterns, so nested negations have no input to restore. The new scan test fixture
explicitly defines this precedence case and expects the re-included file to be tracked.

components/legacy/bit-map/component-map.ts[103-130]
components/legacy/bit-map/component-map.spec.ts[77-79]
components/legacy/bit-map/component-map.spec.ts[103-106]
components/legacy/bit-map/component-map.spec.ts[155-169]

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 workspace-root scan filters root-ignored files out before it evaluates nested ignore files. This prevents a nested negated pattern, such as `docs/.gitignore` containing `!keep.txt`, from restoring a file excluded by a root-level pattern.
## Fix Focus Areas
- components/legacy/bit-map/component-map.ts[123-130]
- components/legacy/bit-map/component-map.spec.ts[155-169]
## Recommended Fix
Evaluate the ordered root user patterns and rebased nested patterns against the original `relativePaths`, rather than applying the nested rules only to `filteredByRoot`. Preserve the separate final application of Bit-owned hard/default exclusions so user negations cannot re-include those files; add a regression test covering a root exclusion followed by a nested negation.

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


6. Formatting checks fail on new tests 📘 Rule violation ⚙ Maintainability
Description
The chained assertion at add-harmony.e2e.ts:263 remains on a line longer than the configured
120-character Prettier width instead of using the formatter's multiline layout. When `npm run
prettier:check` processes the changed E2E files, it reports this file and prevents the canonical
formatting gate from passing.
Code

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

+        expect(path.join(helper.scopes.localPath, 'README.md')).to.be.a.file().with.content('# workspace root v2\n');
Evidence
PR Compliance ID 4 requires changed code to pass npm run prettier:check. The cited assertion
exceeds the repository's configured 120-character print width and has not been laid out as Prettier
formats the chain.

CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards: CLAUDE.md: Code Changes Must Pass Canonical Linting, Type Checking, and Formatting Standards
e2e/harmony/add-harmony.e2e.ts[263-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
A newly added chained assertion does not conform to the repository's Prettier configuration, causing the canonical formatting check to fail.
## Fix Focus Areas
- e2e/harmony/add-harmony.e2e.ts[263-263]
## Recommended Fix
Run Prettier on the changed E2E file and commit its multiline formatting for the chained assertion.

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


7. Root scans fail with nested ignore files 🐞 Bug ≡ Correctness
Description
filterByIgnoreFiles() passes the ignore matcher object in gitIgnore to ignore().add()
instead of passing the root ignore-pattern list. A workspace-root scan enters this branch whenever
it finds a nested .gitignore or .bitignore, so tracking or rescanning a root component with
nested ignore rules fails rather than applying those rules.
Code

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

+  const nestedPatterns = await getNestedIgnorePatterns(consumerPath, gitIgnore, relativePaths);
+  if (!nestedPatterns.length) return filteredByRoot;
+  const filteredByUserRules: PathLinux[] = ignore().add(gitIgnore).add(nestedPatterns).filter(relativePaths);
Evidence
getGitIgnoreHarmony() constructs gitIgnore as an ignore matcher, while the new root-only
branch tries to use that matcher as input to another matcher's add() call. The branch is activated
by nested ignore files discovered during the new recursive root scan.

components/legacy/bit-map/component-map.ts[116-130]
components/legacy/bit-map/component-map.ts[160-184]
components/legacy/bit-map/component-map.ts[590-596]
components/legacy/bit-map/bit-map.ts[260-273]

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
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()` rebuilds the matcher used for workspace-root scans by passing an existing `ignore` matcher object to `ignore().add()`. That API needs ignore patterns, so root scans that discover nested ignore files cannot combine the root and rebased nested rules correctly.
Fix Focus Areas
- components/legacy/bit-map/component-map.ts[116-130]
- components/legacy/bit-map/component-map.ts[160-184]
Recommended Fix
Keep or pass the root user-ignore pattern list alongside the matcher, then build the combined matcher from that string list plus `nestedPatterns`. Apply Bit-owned `ALWAYS_IGNORE_LIST` or `IGNORE_LIST` afterward as the current code intends; do not pass the matcher instance to `add()`.

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


8. Ignored component files get versioned ✓ Resolved 🐞 Bug ≡ Correctness
Description
filterByOwnIgnoreFile() chooses .bitignore only when that filename survives the already-applied
workspace ignore filter, otherwise loading .gitignore. When a parent rule ignores **/.bitignore,
both add and rescans retain files excluded by the component's .bitignore, so those files can enter
versions.
Code

components/legacy/bit-map/component-map.ts[R147-149]

+  const ownIgnoreFile = relativePaths.includes(BIT_IGNORE)
+    ? await getBitIgnoreFile(ignoreFileDir)
+    : await getGitIgnoreFile(ignoreFileDir);
Evidence
The helper receives paths only after workspace filtering and uses that filtered collection to decide
which ignore file exists. The repository's user-ignore loader establishes that filesystem presence,
not whether the ignore file is itself tracked, determines .bitignore precedence; both scanning and
add-time tracking call the faulty helper after filtering.

components/legacy/bit-map/component-map.ts[140-150]
components/legacy/bit-map/component-map.ts[566-574]
scopes/component/tracker/add-components.ts[576-591]
scopes/git/modules/ignore-file-reader/ignore.ts[28-34]

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

## Issue description
`filterByOwnIgnoreFile()` determines whether `.bitignore` exists from paths that workspace ignore rules have already filtered. A parent rule can therefore hide `.bitignore`, causing Bit to apply `.gitignore` instead and track files the component explicitly excludes.
## Fix Focus Areas
- components/legacy/bit-map/component-map.ts[140-150]
- scopes/git/modules/ignore-file-reader/ignore.ts[28-34]
## Recommended Fix
Resolve the component's own ignore rules directly from its directory, using the existing `.bitignore`-before-`.gitignore` filesystem lookup rather than `relativePaths.includes(BIT_IGNORE)`. Add coverage where the workspace `.gitignore` excludes nested `.bitignore` files but the nested component's rules still apply during add and rescan.

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


9. Fresh restores erase workspace settings 🐞 Bug ≡ Correctness
Description
throwForOccupiedWorkspaceRoot() treats workspace.jsonc as generated whenever no other component
is tracked, without checking whether its current contents still match an initialized file. Importing
a root component onto an otherwise empty workspace therefore skips the conflict for a user-edited
workspace configuration and proceeds without requiring --override.
Code

scopes/component/component-writer/component-writer.main.runtime.ts[395]

+    const generatedByInit = isFreshWorkspace ? [WORKSPACE_JSONC] : [];
Evidence
The freshness check is based solely on bitmap component ownership, and line 399 then excludes
workspace.jsonc from all filesystem and byte-content checks. The writer subsequently adds every
non-map component file to persistence, so an edited configuration is not protected by another check.

scopes/component/component-writer/component-writer.main.runtime.ts[390-410]
scopes/component/component-writer/component-writer.ts[104-113]

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

## Issue description
Fresh root-component restores unconditionally exempt `workspace.jsonc` from overwrite conflict detection when no other component is tracked, even when the user edited that file after initialization.
## Fix Focus Areas
- scopes/component/component-writer/component-writer.main.runtime.ts[390-410]
## Recommended Fix
Only exempt `workspace.jsonc` when it can be verified as the untouched initialization output. Otherwise compare it with the incoming file like every other root file and require `--override` when the contents differ.

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


10. Root imports overwrite local edits ✓ Resolved 🐞 Bug ≡ Correctness
Description
throwErrorWhenDirectoryNotEmpty() calls shouldSkipDirConflictCheck() before reaching its new
workspace-root branch, and that helper returns true when the existing map owns the explicitly
requested . directory. Re-importing an already tracked root component with --path . therefore
bypasses throwForOccupiedWorkspaceRoot() and writes incoming root files over local changes without
requiring --override.
Code

scopes/component/component-writer/component-writer.main.runtime.ts[R443-445]

+    if (componentDirRelative === WORKSPACE_ROOT_DIR) {
+      this.throwForOccupiedWorkspaceRoot(component, opts);
+      return;
Evidence
The new root branch is intended to perform per-file conflict detection, but the pre-existing early
return prevents it whenever the same component already owns the target directory. The writer then
adds all non-.bitmap component files with its normal override setting, so no later conflict check
protects those files.

scopes/component/component-writer/component-writer.main.runtime.ts[421-445]
scopes/component/component-writer/component-writer.main.runtime.ts[328-340]
scopes/component/component-writer/component-writer.ts[104-113]

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
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
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
The root-specific overwrite check is unreachable when the incoming component is already tracked at `.` because the generic same-directory shortcut returns first. This permits a second root import to overwrite user-modified workspace files without `--override`.
Fix Focus Areas
- scopes/component/component-writer/component-writer.main.runtime.ts[421-445]
- scopes/component/component-writer/component-writer.main.runtime.ts[328-340]
Recommended Fix
Handle `componentDirRelative === WORKSPACE_ROOT_DIR` before evaluating `shouldSkipDirConflictCheck()`, and always call `throwForOccupiedWorkspaceRoot()` for filesystem-writing root imports. Preserve the generic skip behavior for non-root component directories, while retaining `skipWritingToFs` as an explicit no-write exception if needed.

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


11. Root imports overwrite outside files ✓ Resolved 🐞 Bug ⛨ Security
Description
throwForSymlinkedAncestors() builds its set with slice(0, -1), so it never checks whether the
incoming file path itself is a symbolic link. With --override, root conflict detection is disabled
and fs.outputFile() follows that link, allowing a restored file such as README.md to replace its
target outside the workspace.
Code

scopes/component/component-writer/component-writer.main.runtime.ts[R351-352]

+      const segments = pathNormalizeToLinux(file.relative).split('/').slice(0, -1);
+      segments.forEach((_, index) => ancestors.add(segments.slice(0, index + 1).join('/')));
Evidence
The new validation only adds directory segments before the filename to its checked set. Root imports
invoke this validation, but --override sets throwForExistingDir to false and bypasses the
occupied-root check; the writer then adds the file without an explicit destination removal, and
persistence writes it through fs.outputFile(), which follows the final symlink.

scopes/component/component-writer/component-writer.main.runtime.ts[282-285]
scopes/component/component-writer/component-writer.main.runtime.ts[348-360]
scopes/component/component-writer/component-writer.main.runtime.ts[389-390]
scopes/scope/importer/import-components.ts[1094-1104]
scopes/component/component-writer/component-writer.ts[108-113]
scopes/component/sources/abstract-vinyl.ts[41-54]

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 imports check symlinked ancestor directories but omit each final destination path. With `--override`, the conflict check is bypassed and persistence follows a destination symlink, potentially writing outside the workspace.
## Fix Focus Areas
- scopes/component/component-writer/component-writer.main.runtime.ts[348-360]
- scopes/component/component-writer/component-writer.ts[108-113]
## Recommended Fix
Extend the root-import safety validation to `lstat` every incoming final destination as well as its ancestors, and reject symbolic links regardless of `--override`. Add an end-to-end test where an incoming root file is a symlink to an outside file and verify that `--override` fails without modifying the target.

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


12. Nested tracking can break root loads ✓ Resolved 🐞 Bug ≡ Correctness
Description
addOrUpdateComponentInBitMap() checks the workspace-root main-file conflict only while iterating
files retained after the nested component's ignore and generated-file filters. If that main file is
excluded but another nested file remains, adding the nested component succeeds, its directory
disappears from the root's next scan, and loading the root throws MainFileRemoved.
Code

scopes/component/tracker/add-components.ts[R272-274]

+      if (workspaceRootMap && idOfFileIsDifferent && ownedByWorkspaceRoot) {
+        throwForTakingWorkspaceRootMainFile(workspaceRootMap, parsedBitId, file.relativePath);
+      }
Evidence
The add path applies workspace and component-local filtering before the ownership loop, so an
ignored root main file never reaches the new guard. Root rescans subsequently exclude every nested
root directory, while getLoadedFiles() explicitly throws when the rescanned file list no longer
contains the configured main file.

scopes/component/tracker/add-components.ts[575-596]
scopes/component/tracker/add-components.ts[257-275]
scopes/component/tracker/add-components.ts[937-942]
components/legacy/consumer-component/consumer-component.ts[604-614]

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 nested component can claim the directory containing the workspace-root component's main file when that file is filtered out of the nested component's retained files. The next root scan excludes the entire nested directory, leaving the root component without its main file and causing component loads to fail.
## Fix Focus Areas
- scopes/component/tracker/add-components.ts[251-275]
- scopes/component/tracker/add-components.ts[882-891]
- scopes/component/tracker/add-components.ts[914-924]
## Recommended Fix
Before filtering or iterating component files, compare the workspace-root component's main-file path against the prospective nested root directory. Reject the addition whenever the main file is inside that directory, regardless of whether ignore rules or generated-file filtering retain it; apply the same directory-level validation to both tracking entry points and add regression coverage for an ignored root main file.

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


13. Older components lose their map files 🐞 Bug ≡ Correctness
Description
ComponentWriter.populate() now skips every file whose relative path is .bitmap, without checking
whether the component being written is the workspace-root component. The root-import validation
explicitly recognizes older ordinary component versions may carry that file, so importing or
checking out one of those versions omits its versioned file and leaves the component's recorded file
set inconsistent.
Code

scopes/component/component-writer/component-writer.ts[R108-113]

+    this.component.files.forEach((file) => {
+      // the live map is never written from a versioned copy, see isWorkspaceMapFile
+      if (isWorkspaceMapFile(pathNormalizeToLinux(file.relative))) return;
+      file.override = this.override;
+      this.component.dataToPersist.addFile(file);
+    });
Evidence
The changed writer guard tests only the relative filename, so it excludes .bitmap from every
component write. The new root-target validation documents that an ordinary component snapped before
map exclusions can carry .bitmap, directly establishing the affected compatibility case; checkout
also applies the same filename-only exclusion when removing files.

scopes/component/component-writer/component-writer.ts[104-113]
scopes/component/component-writer/component-writer.main.runtime.ts[363-375]
scopes/component/checkout/checkout-version.ts[127-133]

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
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
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
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
The write guard drops `.bitmap` for all components, although only the workspace-root component must preserve the live workspace map. Older ordinary component versions can legitimately contain a `.bitmap` file and must still restore it.
Fix Focus Areas
- scopes/component/component-writer/component-writer.ts[108-113]
- scopes/component/component-writer/component-writer.main.runtime.ts[363-375]
- scopes/component/checkout/checkout-version.ts[128-133]
Recommended Fix
Determine whether the component being written or checked out is the workspace-root component, and skip `.bitmap` only in that case. Preserve normal write and removal behavior for `.bitmap` files belonging to ordinary historical components.

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


14. Nested additions include ignored files ✓ Resolved 🐞 Bug ≡ Correctness
Description
addOneComponent() applies filterByIgnoreFiles() but never applies filterByOwnIgnoreFile() to
the resulting nested-component paths. For a component with a local .bitignore or .gitignore, its
initial bitmap entry includes files that the next getFilesByDir() rescan removes, allowing locally
ignored content to be tracked and versioned.
Code

scopes/component/tracker/add-components.ts[R575-578]

+    const matchesNotIgnored = await filterByIgnoreFiles(
+      relativeComponentPath,
+      this.consumer.getPath(),
+      this.gitIgnore,
Evidence
The changed add path invokes only the workspace-level filter. That filter explicitly returns early
for non-root directories, whereas the rescan path subsequently invokes the local-ignore filter that
reads the component's .bitignore or .gitignore.

scopes/component/tracker/add-components.ts[575-584]
components/legacy/bit-map/component-map.ts[123-125]
components/legacy/bit-map/component-map.ts[140-150]
components/legacy/bit-map/component-map.ts[566-575]

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

## Issue description
`addOneComponent()` filters discovered files using workspace-level rules only. Unlike the rescan path, it does not apply the nested component's own `.bitignore` or `.gitignore`, so the initial tracked file set can include content that later disappears on rescan.
## Fix Focus Areas
- scopes/component/tracker/add-components.ts[575-584]
- components/legacy/bit-map/component-map.ts[566-575]
## Recommended Fix
After `filterByIgnoreFiles()` returns the workspace-relative matches, convert them to component-relative paths as needed and call `filterByOwnIgnoreFile(relativeComponentPath, this.consumer.getPath(), ...)` before building `filteredMatches`. Keep the root-only generated-file filtering and ensure the add-time ordering matches `getFilesByDir()` so both paths produce the same file set.

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



Remediation recommended

15. New workspace files evade quick status 🐞 Bug ≡ Correctness ⭐ New
Description
Workspace.getFilesModification() builds CompFiles from cached bitMapEntry.files and only
normalizes the contents of files already in that list, without calling trackDirectoryChanges().
When a file is added to a workspace-root component after the workspace is loaded, status --quick
and other callers of this method omit it until a full component load happens to rescan the root.
Code

scopes/workspace/workspace/workspace.ts[775]

+      sourceFile.contents = fileContentsForVersioning(bitMapEntry, file.relativePath, sourceFile.contents);
Evidence
The new root-aware scan replaces a component map's files and is invoked by component loading, but
the quick-status modification path directly maps the cached list. Status mini calls that path for
every listed component, so files added after the initial load are absent from its comparison.

components/legacy/bit-map/bit-map.ts[260-283]
scopes/workspace/workspace/workspace.ts[768-788]
scopes/component/status/status.main.runtime.ts[201-209]

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.getFilesModification()` compares the cached bitmap file list directly. A workspace-root component owns newly added unclaimed files only after its directory is rescanned, so quick status can report it clean while it has new root files.

Fix Focus Areas
- scopes/workspace/workspace/workspace.ts[768-776]
- components/legacy/bit-map/bit-map.ts[260-269]

Recommended Fix

Before deriving `compDir` and mapping `bitMapEntry.files`, detect `bitMapEntry.rootDir === WORKSPACE_ROOT_DIR` and call `await this.consumer.bitMap.trackDirectoryChanges(bitMapEntry)`. This refreshes the root component's owned file set and path index before `CompFiles` compares workspace files with the model, while preserving the existing behavior for ordinary components.

ⓘ 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-risk workspace model change spanning tracking, mapping, importing/writing, dependency handling, snapping, and filesystem ignore behavior, with 122 independent hunks and multiple previously identified defect classes.

Grey Divider

Tip of the day
💡 Did you know, you can reply 'qodo' on any finding to push back, ask questions, or dig deeper

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

…t, regardless of --override

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 176b92c

The root component has no entry point of its own, so "bit add ." no longer
needs --main; workspace.jsonc stands in for it, and an explicit --main still
wins. The bulk tracking API defaults it the same way.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

…ot merely harmless

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread components/legacy/bit-map/bit-map.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 71d06d9

…root component

It is never linked into node_modules, so the issue and its "run bit link" advice
cannot apply to it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread scopes/component/component-writer/component-writer.main.runtime.ts
Comment thread scopes/workspace/watcher/watcher.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

…-component-nesting

# Conflicts:
#	components/legacy/bit-map/bit-map.spec.ts
#	components/legacy/consumer-component/consumer-component.ts
Comment thread components/legacy/bit-map/component-map.ts Outdated
Comment thread scopes/workspace/install/install.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 f19a107

… scan exclusions to bulk-tracked paths, skip the root in the duplicate-package check
Comment thread components/legacy/bit-map/bit-map.ts Outdated
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 0d90dd0

Comment thread e2e/harmony/add-harmony.e2e.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 481443d

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 c0cb2f7

…mponent as well

The root component's files are no longer parsed for dependencies, so the relative-import issue that the codemod fixes cannot be raised for it.
Comment on lines +282 to +285
if (componentRootDir === WORKSPACE_ROOT_DIR) {
this.throwForNonWorkspaceRootComponent(component);
this.throwForSymlinksInTheWay(component);
}

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. Root checkouts overwrite outside files 🐞 Bug ⛨ Security

getWriteParamsOfOneComponent() invokes throwForSymlinksInTheWay() only when its initial path is
., before an existing bitmap entry changes the writer's destination to the workspace root. A
checkout supplies no explicit path, so checking out a root file whose destination or ancestor is a
symlink writes through that link to its target outside the workspace.
Agent Prompt
## Issue description
Workspace-root checkouts bypass symlink validation because the check runs before the existing bitmap entry changes the final write path to `.`.

## Fix Focus Areas
- scopes/component/component-writer/component-writer.main.runtime.ts[278-301]
- scopes/component/component-writer/component-writer.ts[78-90]

## Recommended Fix
Resolve the existing component map and final write destination before applying workspace-root validation, then run the symlink check whenever that final destination is `.`.

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

Comment on lines +192 to +194
const linkedComponents = components.filter(
(c) => consumer.bitMap.getComponentIfExist(c.id, { ignoreVersion: true })?.rootDir !== WORKSPACE_ROOT_DIR
);

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. Removing the root drops a dependency 🐞 Bug ≡ Correctness

removeLocal() still passes the workspace-root component to removeComponentsFromDependencies(),
although the new filtering excludes it only from node_modules cleanup. When the workspace declares
an unrelated dependency with the root component's derived package name, removing the component
deletes that dependency from the root package.json.
Agent Prompt
## Issue description
The workspace-root component is excluded from node_modules removal but still reaches package.json dependency cleanup, which can delete an unrelated dependency sharing its derived name.

## Fix Focus Areas
- scopes/component/remove/remove-components.ts[167-195]

## Recommended Fix
Partition workspace-root components before cleanup and exclude them from both `removeComponentsFromDependencies()` and `removeComponentsFromNodeModules()`, while retaining normal bitmap untracking.

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

Comment on lines +1145 to +1148
if (key === SCHEMA_FIELD || !entry || typeof entry !== 'object') return;
if (entry.version !== undefined) entry.version = '';
delete entry.config;
});

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. Persisted soft tags stay modified 🐞 Bug ≡ Correctness

normalizeBitmapContentForVersioning() removes versions and pending config but leaves each entry's
transient nextVersion field in the versioned map. Persisting a soft tag captures that field in the
root version and then clears it from the live bitmap, so status immediately detects different file
contents.
Agent Prompt
## Issue description
The normalized version of `.bitmap` retains `nextVersion`, even though soft-tag persistence clears that transient field from the live map after loading component files.

## Fix Focus Areas
- components/legacy/bit-map/bit-map.ts[1137-1149]
- components/legacy/bit-map/bit-map.spec.ts[145-189]

## Recommended Fix
Delete `nextVersion` from every component entry during bitmap normalization and add a convergence test covering soft tag followed by persist and status.

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

Comment on lines +594 to +598
const filteredMatches = matchesNotIgnored.filter(
(match) =>
keptByOwnIgnoreFile.has(relativeToComponent(match)) &&
(this.consumer.config.trackAllFiles || !generatedAtRoot.has(match))
);

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. Some root main choices break loading 🐞 Bug ≡ Correctness

addOneComponent() removes root-only generated files before _addMainFileToFiles(), but that
method can append an explicitly selected file without applying the same exclusion. Running `bit add
. --main tsconfig.json without trackAllFiles` therefore succeeds initially, while the next bitmap
rescan drops the main file and leaves the root component invalid.
Agent Prompt
## Issue description
An explicit workspace-root main file can be reintroduced after the add-time scan excluded it, producing a bitmap that fails after the next rescan.

## Fix Focus Areas
- scopes/component/tracker/add-components.ts[572-607]
- scopes/component/tracker/determine-main-file.spec.ts[19-33]

## Recommended Fix
Validate an explicit main file against all scan exclusions, including root-only generated files and component-local ignore rules, before appending it; reject excluded choices with `ExcludedMainFile`.

ⓘ 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 1208b92

Comment on lines +197 to +199
it('should allow re-adding the same component', () => {
helper.fs.outputFile('extra.md', 'extra\n');
expect(() => helper.command.addComponent('.', { i: 'ws-root' })).to.not.throw();

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. Redundant add flows slow test runs 📘 Rule violation ➹ Performance

add-harmony.e2e.ts invokes the CLI to verify main-file selection and same-component re-add
behavior already covered by colocated unit specifications. These single-workspace variants require
neither a remote scope nor a cross-command restore flow, so every E2E run executes unnecessary
workspace commands.
Agent Prompt
## Issue description
The added E2E cases exercise main-file selection and repeated component addition that colocated unit specifications already cover, increasing E2E runtime without testing a cross-command or remote-scope flow.

## Fix Focus Areas
- e2e/harmony/add-harmony.e2e.ts[188-216]
- scopes/component/tracker/determine-main-file.spec.ts[23-31]
- components/legacy/bit-map/bit-map.spec.ts[199-216]

## Recommended Fix
Remove the redundant E2E variants and retain or extend the existing unit specifications for explicit main-file selection, preserving an existing main file during re-tracking, and rejecting or accepting duplicate root ownership.

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

const patternsPerDir = await Promise.all(
Array.from(ignoreFileByDir, async ([fileDir, name]) => {
const absoluteDir = path.join(consumerPath, fileDir);
const patterns = name === BIT_IGNORE ? await getBitIgnoreFile(absoluteDir) : await getGitIgnoreFile(absoluteDir);

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. Scanner formatting fails the gate 📘 Rule violation ⚙ Maintainability

getNestedIgnorePatterns() and its new scanner tests leave expressions beyond the configured
120-column layout instead of Prettier's wrapped form. When npm run prettier:check scans the
changed component files, Prettier lists them as different and the formatting gate exits
unsuccessfully.
Agent Prompt
## Issue description
Changed scanner source and test expressions do not match the repository's configured Prettier layout, causing the canonical formatting check to fail.

## Fix Focus Areas
- components/legacy/bit-map/component-map.ts[127-193]
- components/legacy/bit-map/component-map.spec.ts[15-156]

## Recommended Fix
Run the repository Prettier formatter on both files and commit its multiline wrapping, then verify them with `npm run prettier:check`.

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

const filePath = path.join(compDirAbs, file.relativePath);
return SourceFile.load(filePath, compDirAbs, this.path, {});
const sourceFile = SourceFile.load(filePath, compDirAbs, this.path, {});
sourceFile.contents = fileContentsForVersioning(bitMapEntry, file.relativePath, sourceFile.contents);

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. New workspace files evade quick status 🐞 Bug ≡ Correctness

Workspace.getFilesModification() builds CompFiles from cached bitMapEntry.files and only
normalizes the contents of files already in that list, without calling trackDirectoryChanges().
When a file is added to a workspace-root component after the workspace is loaded, status --quick
and other callers of this method omit it until a full component load happens to rescan the root.
Agent Prompt
Issue description

`Workspace.getFilesModification()` compares the cached bitmap file list directly. A workspace-root component owns newly added unclaimed files only after its directory is rescanned, so quick status can report it clean while it has new root files.

Fix Focus Areas
- scopes/workspace/workspace/workspace.ts[768-776]
- components/legacy/bit-map/bit-map.ts[260-269]

Recommended Fix

Before deriving `compDir` and mapping `bitMapEntry.files`, detect `bitMapEntry.rootDir === WORKSPACE_ROOT_DIR` and call `await this.consumer.bitMap.trackDirectoryChanges(bitMapEntry)`. This refreshes the root component's owned file set and path index before `CompFiles` compares workspace files with the model, while preserving the existing behavior for ordinary components.

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

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