Skip to content

fix: enhance findProjectRoot to traverse parent directories#1302

Merged
Crunchyman-ralph merged 6 commits into
eyaltoledano:nextfrom
bjcoombs:fix/parent-directory-traversal
Oct 14, 2025
Merged

fix: enhance findProjectRoot to traverse parent directories#1302
Crunchyman-ralph merged 6 commits into
eyaltoledano:nextfrom
bjcoombs:fix/parent-directory-traversal

Conversation

@bjcoombs

@bjcoombs bjcoombs commented Oct 13, 2025

Copy link
Copy Markdown
Contributor

Description

Fixes #1301

Enhances the findProjectRoot() function to correctly traverse parent directories when running task-master commands from subdirectories, enabling Task Master to work seamlessly in multi-repo projects and deep directory structures.

Changes Made

Core Improvements

  1. Enhanced Error Handling

    • Added try-catch around fs.existsSync() to gracefully handle permission errors
    • Allows traversal to continue even if some directories are inaccessible
  2. Improved Root Detection

    • Added safety check for when path.dirname() returns the same path
    • Prevents infinite loops when reaching filesystem root
  3. Better Project Marker Ordering

    • Prioritized Task Master-specific markers (.taskmaster, config files)
    • Ensures Task Master projects are detected before generic markers
  4. Expanded Project Support

    • Added markers for: Rust (Cargo.toml), Go (go.mod), Python (pyproject.toml, requirements.txt), Ruby (Gemfile), PHP (composer.json)
    • Improves cross-platform and multi-language project compatibility
  5. Enhanced Documentation

    • Improved code comments and function documentation
    • Clarified traversal logic and fallback behavior

Testing

  • Added comprehensive unit tests in tests/unit/path-utils-find-project-root.test.js
  • 12 test cases covering:
    • Parent directory traversal for various markers
    • Multi-level directory traversal
    • Permission error handling
    • Filesystem root detection
    • Edge cases (empty paths, relative paths, max depth)
  • All tests passing ✅

Before This Fix

cd /project/subdirectory
task-master list
# Output: Listing tasks from: /project/subdirectory/.taskmaster/tasks/tasks.json
# ⚠ No tasks found

After This Fix

cd /project/subdirectory
task-master list
# Output: Listing tasks from: /project/.taskmaster/tasks/tasks.json
# [Shows tasks from parent project]

Use Cases Enabled

  • Multi-repo monorepos: Global Task Master configuration at root coordinating work across multiple repositories
  • Deep directory structures: Developers can run commands from any subdirectory
  • Microservices architectures: Single Task Master instance managing multiple service repositories
  • Team workflows: Consistent experience regardless of current working directory

Testing Instructions

  1. Initialize Task Master in a project root:

    task-master init
  2. Navigate to a subdirectory:

    cd some/deep/subdirectory
  3. Run any task-master command:

    task-master list
  4. Verify it finds the parent .taskmaster directory

Checklist

  • Code changes implemented
  • Unit tests added
  • All tests passing
  • Documentation updated (code comments)
  • No breaking changes
  • Maintains backward compatibility

Related Issues

Closes #1301

Summary by CodeRabbit

  • Bug Fixes

    • More reliable project root detection from nested directories with broader marker support.
    • Graceful handling of permission errors and safe traversal with depth limits and root detection.
    • Consistent fallback to the current directory when no markers are found.
  • Documentation

    • Updated changelogs, including 0.29.0 notes and migration guidance.
  • Chores

    • Version bumps (core app to 0.29.0, extension to 0.25.6, docs to 0.0.6); added plugin changelog entries.
  • Tests

    • Added comprehensive unit tests covering traversal, marker precedence, errors, and edge cases.

@changeset-bot

changeset-bot Bot commented Oct 13, 2025

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: af3004c

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Oct 13, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Updates findProjectRoot to traverse parent directories with a depth cap, expanded marker set, prioritized Task Master markers, root-detection safety, and permission-error tolerance; adds unit tests for traversal and edge cases; updates changelogs and bumps versions across packages.

Changes

Cohort / File(s) Summary
Project root detection logic
src/utils/path-utils.js
Refactors findProjectRoot: adds bounded upward traversal (max 50), prioritizes Task Master markers, expands marker list, guards root no-op, wraps fs.existsSync in try/catch, ensures string return (fallback to CWD), and updates comments/docstring.
Unit tests for findProjectRoot
tests/unit/path-utils-find-project-root.test.js
Introduces comprehensive tests covering traversal, marker precedence, permission errors, root handling, relative/empty start paths, and fallback to process.cwd().
Changelog and changeset docs
.changeset/fix-parent-directory-traversal.md, CHANGELOG.md, apps/cli/CHANGELOG.md, apps/docs/CHANGELOG.md, apps/extension/CHANGELOG.md, packages/ai-sdk-provider-grok-cli/CHANGELOG.md, packages/build-config/CHANGELOG.md, packages/claude-code-plugin/CHANGELOG.md, packages/tm-core/CHANGELOG.md
Adds/updates changelog sections, including new headers (some “null” placeholders) and release notes; no code behavior changes.
Version bumps
package.json, apps/docs/package.json, apps/extension/package.json, packages/claude-code-plugin/package.json
Bumps versions: root 0.29.0-rc.1 → 0.29.0; docs 0.0.5 → 0.0.6; extension 0.25.6-rc.0 → 0.25.6; claude-code-plugin 0.0.1 → 0.0.2.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Caller
  participant PU as PathUtils.findProjectRoot(startPath)
  participant P as path
  participant FS as fs
  participant F as Filesystem

  C->>PU: findProjectRoot(startPath?)
  PU->>PU: dir = startPath || process.cwd()
  loop up to 50 levels
    PU->>PU: Check marker list (Task Master first)
    alt For each marker
      PU->>P: join(dir, marker)
      PU->>FS: existsSync(candidate)
      opt Permission error
        FS-->>PU: throw EACCES
        PU->>PU: catch and continue
      end
      alt Marker exists
        FS-->>PU: true
        PU-->>C: return dir
      else Not found
        FS-->>PU: false
      end
    end
    PU->>P: parent = dirname(dir)
    alt Root reached (parent === dir)
      PU-->>C: return startPath||process.cwd()
    else Continue upward
      PU->>PU: dir = parent
    end
  end
  PU-->>C: return startPath||process.cwd()  Note over PU: No markers within depth
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • eyaltoledano

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning This pull request includes widespread version bumps and changelog updates across multiple packages that are unrelated to the findProjectRoot enhancement defined in issue #1301, introducing out-of-scope documentation and metadata changes. Please separate the version and changelog modifications into a dedicated release or housekeeping pull request and limit this PR to the path-utils changes and associated tests.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly and concisely describes the primary change by indicating that the findProjectRoot function now supports traversing parent directories, directly aligning with the core fix implemented in the pull request.
Linked Issues Check ✅ Passed The submitted changes satisfy all primary objectives of issue #1301 by adding robust upward traversal with error handling, preventing infinite loops at the filesystem root, prioritizing Task Master–specific markers, expanding marker recognition, and including comprehensive unit tests that verify the new behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0df6595 and 68ff616.

📒 Files selected for processing (2)
  • src/utils/path-utils.js (1 hunks)
  • tests/unit/path-utils-find-project-root.test.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
tests/{unit,integration,e2e,fixtures}/**/*.js

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Test files must be organized as follows: unit tests in tests/unit/, integration tests in tests/integration/, end-to-end tests in tests/e2e/, and test fixtures in tests/fixtures/.

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/unit/*.js

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Each module should have a corresponding unit test file in tests/unit/ that reflects the module structure (one test file per module).

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.{test,spec}.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/git_workflow.mdc)

**/*.{test,spec}.{js,ts,jsx,tsx}: Create a test file and ensure all tests pass when all subtasks are complete; commit tests if added or modified
When all subtasks are complete, run final testing using the appropriate test runner (e.g., npm test, jest, or manual testing)

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.test.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

**/*.test.js: Never use asynchronous operations in tests. Make all mocks return synchronous values when possible.
Always mock tests properly based on the way the tested functions are defined and used.
Follow the test file organization: mocks must be set up before importing modules under test, and spies on mocked modules should be set up after imports.
Use fixtures from tests/fixtures/ for consistent sample data across tests.
Always declare mocks before importing the modules being tested in Jest test files.
Use jest.spyOn() after imports to create spies on mock functions and reference these spies in test assertions.
When testing functions with callbacks, get the callback from your mock's call arguments, execute it directly with test inputs, and verify the results.
For ES modules, use jest.mock() before static imports and jest.unstable_mockModule() before dynamic imports to mock dependencies.
Reset mock functions (mockFn.mockReset()) before dynamic imports if they might have been called previously.
When verifying console assertions, assert against the actual arguments passed (single formatted string), not multiple arguments.
Use mock-fs to mock file system operations in tests, and restore the file system after each test.
Mock API calls (e.g., Anthropic/Claude) by mocking the entire module and providing predictable responses.
Set mock environment variables in test setup and restore them after each test.
Maintain test fixtures separate from test logic.
Follow the mock-first-then-import pattern for all Jest mocks.
Do not define mock variables before jest.mock() calls (they won't be accessible due to hoisting).
Use test-specific file paths (e.g., 'test-tasks.json') for all file operations in tests.
Mock readJSON and writeJSON to avoid real file system interactions in tests.
Verify file operations use the correct paths in expect statements.
Use different file paths for each test to avoid test interdependence.
Verify modifications on the in-memory task objects passed to w...

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/unit/**/*.test.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

tests/unit/**/*.test.js: Unit tests must be located in tests/unit/, test individual functions and utilities in isolation, mock all external dependencies, and keep tests small, focused, and fast.
Do not include actual command execution in unit tests.

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/{unit,integration,e2e}/**/*.test.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

tests/{unit,integration,e2e}/**/*.test.js: When testing CLI commands built with Commander.js, test the command action handlers directly rather than trying to mock the entire Commander.js chain.
When mocking the Commander.js chain, mock ALL chainable methods (option, argument, action, on, etc.) and return this (or the mock object) from all chainable method mocks.
Explicitly handle all options, including defaults and shorthand flags (e.g., -p for --prompt), and include null/undefined checks in test implementations for parameters that might be optional.
Do not try to use the real action implementation without proper mocking, and do not mock Commander partially—either mock it completely or test the action directly.
Mock the action handlers for CLI commands and verify they're called with correct arguments.
Use sample task fixtures for consistent test data, mock file system operations, and test both success and error paths for task operations.
Mock console output and verify correct formatting in UI function tests. Use flexible assertions like toContain() or toMatch() for formatted output.
Mock chalk functions to return the input text to make testing easier while still verifying correct function calls.

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

**/*.js: Declare and initialize global variables at the top of modules to avoid hoisting issues.
Use proper function declarations to avoid hoisting issues and initialize variables before they are referenced.
Do not reference variables before their declaration in module scope.
Use dynamic imports (import()) to avoid initialization order issues in modules.

Files:

  • tests/unit/path-utils-find-project-root.test.js
  • src/utils/path-utils.js
**/*.{test,spec}.*

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Test files should follow naming conventions: .test., .spec., or _test. depending on the language

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/{unit,integration,e2e}/**

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Organize test directories by test type (unit, integration, e2e) and mirror source structure where possible

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{test,spec}.{js,jsx,ts,tsx}: NEVER use async/await in test functions unless testing actual asynchronous operations
Use synchronous top-level imports in tests; avoid dynamic await import()
Keep test bodies synchronous whenever possible

Files:

  • tests/unit/path-utils-find-project-root.test.js
{src/utils/**,src/middleware/**}

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Test coverage for all code should meet or exceed 80% lines/functions and 70% branches globally; critical code (utils, middleware) should meet higher thresholds (90% utils, 85% middleware)

Files:

  • src/utils/path-utils.js
🧠 Learnings (5)
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to **/*.test.js : Verify file operations use the correct paths in expect statements.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to tests/{unit,integration,e2e}/**/*.test.js : Use sample task fixtures for consistent test data, mock file system operations, and test both success and error paths for task operations.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:12:57.903Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/new_features.mdc:0-0
Timestamp: 2025-07-18T17:12:57.903Z
Learning: Applies to scripts/modules/**/*.test.js : Test core logic independently with both data formats, mock file system operations, test tag resolution behavior, and verify migration compatibility in unit tests.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:18:17.759Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/utilities.mdc:0-0
Timestamp: 2025-07-18T17:18:17.759Z
Learning: Applies to mcp-server/src/tools/utils.js : Use `normalizeProjectRoot(rawPath, log)`, `getRawProjectRootFromSession(session, log)`, and `withNormalizedProjectRoot(executeFn)` in `mcp-server/src/tools/utils.js` to ensure project root paths are normalized for MCP tools.

Applied to files:

  • src/utils/path-utils.js
📚 Learning: 2025-07-18T17:11:36.732Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/mcp.mdc:0-0
Timestamp: 2025-07-18T17:11:36.732Z
Learning: Applies to mcp-server/src/core/direct-functions/*.js : The *Direct function is responsible for finding the exact tasks.json path using findTasksJsonPath, relying on the projectRoot passed in args.

Applied to files:

  • src/utils/path-utils.js
🧬 Code graph analysis (2)
tests/unit/path-utils-find-project-root.test.js (1)
src/utils/path-utils.js (1)
  • projectMarkers (57-76)
src/utils/path-utils.js (2)
mcp-server/src/core/utils/path-utils.js (1)
  • findProjectRoot (201-203)
scripts/modules/config-manager.js (1)
  • rootDir (858-858)
🔇 Additional comments (3)
src/utils/path-utils.js (3)

55-76: Excellent expansion of project marker support.

The reordering to prioritize Task Master-specific markers and the expansion to include markers for Rust, Go, Python, Ruby, and PHP ecosystems significantly improves cross-language project detection. This aligns well with the PR objectives to support multi-repo and varied project structures.


83-97: Good error handling for robust traversal.

The try-catch wrapper around fs.existsSync effectively handles permission errors and other filesystem issues, allowing the search to continue. This addresses the PR objective of handling inaccessible directories during traversal.

The broad catch (all errors, not just EACCES) is acceptable here since continuing the search is the correct behavior regardless of the specific error type.


98-114: Robust root detection and guaranteed return value.

The safety check at lines 103-105 correctly prevents infinite loops when path.dirname() returns the same path (filesystem root), addressing one of the core PR objectives. The guaranteed fallback to process.cwd() at line 113 ensures the function always returns a valid string path, improving reliability.

These changes directly support the PR's goals of preventing infinite loops and ensuring deterministic behavior.

Comment thread src/utils/path-utils.js
Comment thread tests/unit/path-utils-find-project-root.test.js Outdated
Comment thread tests/unit/path-utils-find-project-root.test.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68ff616 and f069c54.

📒 Files selected for processing (1)
  • .changeset/fix-parent-directory-traversal.md (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/changeset.mdc)

.changeset/*.md: When running npm run changeset or npx changeset add, provide a concise summary of the changes for the CHANGELOG.md in imperative mood, typically a single line, and not a detailed Git commit message.
The changeset summary should be user-facing, describing what changed in the released version that is relevant to users or consumers of the package.
Do not use your detailed Git commit message body as the changeset summary.

Files:

  • .changeset/fix-parent-directory-traversal.md
.changeset/*

📄 CodeRabbit inference engine (.cursor/rules/new_features.mdc)

Create appropriate changesets for new features, use semantic versioning, include tagged system information in release notes, and document breaking changes if any.

Files:

  • .changeset/fix-parent-directory-traversal.md
.changeset/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Changeset entries should be user-facing, describing the end-user impact rather than code specifics

Files:

  • .changeset/fix-parent-directory-traversal.md
🪛 markdownlint-cli2 (0.18.1)
.changeset/fix-parent-directory-traversal.md

5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

Comment thread .changeset/fix-parent-directory-traversal.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (3)
src/utils/path-utils.js (1)

48-54: LGTM! Documentation is comprehensive.

The JSDoc now properly documents the traversal behavior, including the maxDepth limit (50 levels) that was flagged in the previous review. The documentation clearly describes the function's behavior, parameters, and return value.

tests/unit/path-utils-find-project-root.test.js (2)

98-116: LGTM! Test correctly validates current directory is checked first.

The test has been properly updated based on previous review feedback. The assertion now correctly expects /project/subdir as the result (line 113), and the comment accurately describes the behavior: markers are checked in order at the current directory, so .git at the current directory is found before .taskmaster at the parent directory.


209-230: LGTM! Test correctly validates max depth limit.

The test has been properly updated based on previous review feedback. The assertion on line 227 now verifies that traversal stops at the maxDepth limit (50 levels) by checking that the total number of existsSync calls doesn't exceed 50 * 18 (maxDepth × number of markers). This ensures the depth limit is enforced and prevents excessive traversal in pathological cases.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f069c54 and 8a963a9.

📒 Files selected for processing (3)
  • .changeset/fix-parent-directory-traversal.md (1 hunks)
  • src/utils/path-utils.js (1 hunks)
  • tests/unit/path-utils-find-project-root.test.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (14)
.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/changeset.mdc)

.changeset/*.md: When running npm run changeset or npx changeset add, provide a concise summary of the changes for the CHANGELOG.md in imperative mood, typically a single line, and not a detailed Git commit message.
The changeset summary should be user-facing, describing what changed in the released version that is relevant to users or consumers of the package.
Do not use your detailed Git commit message body as the changeset summary.

Files:

  • .changeset/fix-parent-directory-traversal.md
.changeset/*

📄 CodeRabbit inference engine (.cursor/rules/new_features.mdc)

Create appropriate changesets for new features, use semantic versioning, include tagged system information in release notes, and document breaking changes if any.

Files:

  • .changeset/fix-parent-directory-traversal.md
.changeset/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Changeset entries should be user-facing, describing the end-user impact rather than code specifics

Files:

  • .changeset/fix-parent-directory-traversal.md
tests/{unit,integration,e2e,fixtures}/**/*.js

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Test files must be organized as follows: unit tests in tests/unit/, integration tests in tests/integration/, end-to-end tests in tests/e2e/, and test fixtures in tests/fixtures/.

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/unit/*.js

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Each module should have a corresponding unit test file in tests/unit/ that reflects the module structure (one test file per module).

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.{test,spec}.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/git_workflow.mdc)

**/*.{test,spec}.{js,ts,jsx,tsx}: Create a test file and ensure all tests pass when all subtasks are complete; commit tests if added or modified
When all subtasks are complete, run final testing using the appropriate test runner (e.g., npm test, jest, or manual testing)

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.test.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

**/*.test.js: Never use asynchronous operations in tests. Make all mocks return synchronous values when possible.
Always mock tests properly based on the way the tested functions are defined and used.
Follow the test file organization: mocks must be set up before importing modules under test, and spies on mocked modules should be set up after imports.
Use fixtures from tests/fixtures/ for consistent sample data across tests.
Always declare mocks before importing the modules being tested in Jest test files.
Use jest.spyOn() after imports to create spies on mock functions and reference these spies in test assertions.
When testing functions with callbacks, get the callback from your mock's call arguments, execute it directly with test inputs, and verify the results.
For ES modules, use jest.mock() before static imports and jest.unstable_mockModule() before dynamic imports to mock dependencies.
Reset mock functions (mockFn.mockReset()) before dynamic imports if they might have been called previously.
When verifying console assertions, assert against the actual arguments passed (single formatted string), not multiple arguments.
Use mock-fs to mock file system operations in tests, and restore the file system after each test.
Mock API calls (e.g., Anthropic/Claude) by mocking the entire module and providing predictable responses.
Set mock environment variables in test setup and restore them after each test.
Maintain test fixtures separate from test logic.
Follow the mock-first-then-import pattern for all Jest mocks.
Do not define mock variables before jest.mock() calls (they won't be accessible due to hoisting).
Use test-specific file paths (e.g., 'test-tasks.json') for all file operations in tests.
Mock readJSON and writeJSON to avoid real file system interactions in tests.
Verify file operations use the correct paths in expect statements.
Use different file paths for each test to avoid test interdependence.
Verify modifications on the in-memory task objects passed to w...

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/unit/**/*.test.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

tests/unit/**/*.test.js: Unit tests must be located in tests/unit/, test individual functions and utilities in isolation, mock all external dependencies, and keep tests small, focused, and fast.
Do not include actual command execution in unit tests.

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/{unit,integration,e2e}/**/*.test.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

tests/{unit,integration,e2e}/**/*.test.js: When testing CLI commands built with Commander.js, test the command action handlers directly rather than trying to mock the entire Commander.js chain.
When mocking the Commander.js chain, mock ALL chainable methods (option, argument, action, on, etc.) and return this (or the mock object) from all chainable method mocks.
Explicitly handle all options, including defaults and shorthand flags (e.g., -p for --prompt), and include null/undefined checks in test implementations for parameters that might be optional.
Do not try to use the real action implementation without proper mocking, and do not mock Commander partially—either mock it completely or test the action directly.
Mock the action handlers for CLI commands and verify they're called with correct arguments.
Use sample task fixtures for consistent test data, mock file system operations, and test both success and error paths for task operations.
Mock console output and verify correct formatting in UI function tests. Use flexible assertions like toContain() or toMatch() for formatted output.
Mock chalk functions to return the input text to make testing easier while still verifying correct function calls.

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

**/*.js: Declare and initialize global variables at the top of modules to avoid hoisting issues.
Use proper function declarations to avoid hoisting issues and initialize variables before they are referenced.
Do not reference variables before their declaration in module scope.
Use dynamic imports (import()) to avoid initialization order issues in modules.

Files:

  • tests/unit/path-utils-find-project-root.test.js
  • src/utils/path-utils.js
**/*.{test,spec}.*

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Test files should follow naming conventions: .test., .spec., or _test. depending on the language

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/{unit,integration,e2e}/**

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Organize test directories by test type (unit, integration, e2e) and mirror source structure where possible

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{test,spec}.{js,jsx,ts,tsx}: NEVER use async/await in test functions unless testing actual asynchronous operations
Use synchronous top-level imports in tests; avoid dynamic await import()
Keep test bodies synchronous whenever possible

Files:

  • tests/unit/path-utils-find-project-root.test.js
{src/utils/**,src/middleware/**}

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Test coverage for all code should meet or exceed 80% lines/functions and 70% branches globally; critical code (utils, middleware) should meet higher thresholds (90% utils, 85% middleware)

Files:

  • src/utils/path-utils.js
🧠 Learnings (6)
📚 Learning: 2025-07-18T17:07:53.100Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/changeset.mdc:0-0
Timestamp: 2025-07-18T17:07:53.100Z
Learning: Applies to .changeset/*.md : When running `npm run changeset` or `npx changeset add`, provide a concise summary of the changes for the `CHANGELOG.md` in imperative mood, typically a single line, and not a detailed Git commit message.

Applied to files:

  • .changeset/fix-parent-directory-traversal.md
📚 Learning: 2025-10-08T12:21:14.455Z
Learnt from: Crunchyman-ralph
PR: eyaltoledano/claude-task-master#1285
File: .changeset/nice-ways-hope.md:5-17
Timestamp: 2025-10-08T12:21:14.455Z
Learning: For changeset files (.changeset/*.md): The first line should be concise and in imperative mood, but the body after the first line can include as many user-facing details as desired (bullets, explanations, links, etc.) to provide context for the CHANGELOG.md.

Applied to files:

  • .changeset/fix-parent-directory-traversal.md
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to **/*.test.js : Verify file operations use the correct paths in expect statements.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to tests/{unit,integration,e2e}/**/*.test.js : Use sample task fixtures for consistent test data, mock file system operations, and test both success and error paths for task operations.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:18:17.759Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/utilities.mdc:0-0
Timestamp: 2025-07-18T17:18:17.759Z
Learning: Applies to mcp-server/src/tools/utils.js : Use `normalizeProjectRoot(rawPath, log)`, `getRawProjectRootFromSession(session, log)`, and `withNormalizedProjectRoot(executeFn)` in `mcp-server/src/tools/utils.js` to ensure project root paths are normalized for MCP tools.

Applied to files:

  • src/utils/path-utils.js
📚 Learning: 2025-07-18T17:11:36.732Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/mcp.mdc:0-0
Timestamp: 2025-07-18T17:11:36.732Z
Learning: Applies to mcp-server/src/core/direct-functions/*.js : The *Direct function is responsible for finding the exact tasks.json path using findTasksJsonPath, relying on the projectRoot passed in args.

Applied to files:

  • src/utils/path-utils.js
🧬 Code graph analysis (2)
tests/unit/path-utils-find-project-root.test.js (1)
src/utils/path-utils.js (2)
  • findProjectRoot (55-115)
  • projectMarkers (58-77)
src/utils/path-utils.js (2)
mcp-server/src/core/utils/path-utils.js (1)
  • findProjectRoot (201-203)
scripts/modules/config-manager.js (1)
  • rootDir (858-858)
🪛 markdownlint-cli2 (0.18.1)
.changeset/fix-parent-directory-traversal.md

5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

🔇 Additional comments (11)
.changeset/fix-parent-directory-traversal.md (1)

1-7: LGTM! Changeset format is correct.

The changeset summary is concise, user-facing, and in the imperative mood as required. The patch-level change classification and issue reference are appropriate. The static analysis warning about requiring a top-level heading (MD041) is a false positive—changeset files follow their own format convention and don't need standard markdown document structure.

src/utils/path-utils.js (4)

55-77: LGTM! Marker prioritization and expansion aligns with PR objectives.

The expanded marker list now covers multiple language ecosystems (Rust, Go, Python, Ruby, PHP) as described in the PR objectives. The prioritization strategy (Task Master markers → VCS markers → language-specific markers) ensures that Task Master-specific configuration is found first, which is the correct behavior for the tool.


79-82: LGTM! Traversal initialization is correct.

The initialization properly resolves the starting directory, identifies the filesystem root, and sets a reasonable depth limit (50 levels) to prevent excessive traversal in pathological cases.


84-110: LGTM! Traversal logic is robust and handles edge cases correctly.

The implementation properly addresses the core requirements from issue #1301:

  1. Permission error handling (lines 89-97): try-catch allows traversal to continue when directories are inaccessible
  2. Filesystem root detection (lines 104-106): safety check prevents infinite loops when path.dirname() returns the same path
  3. Upward traversal: correctly moves to parent directories using path.dirname()

The early return on marker discovery and depth counter increment ensure efficient and bounded traversal.


112-115: LGTM! Fallback behavior ensures robust handling of edge cases.

The fallback to process.cwd() when no markers are found ensures the function always returns a valid path, preventing null/undefined return values that could cause downstream errors. This aligns with the updated return type documentation (line 53) which specifies the function returns string unconditionally.

tests/unit/path-utils-find-project-root.test.js (6)

68-82: LGTM! Test correctly validates multi-level traversal.

This test uses exact path comparison in the mock (line 74), which clearly expresses the test's intent: the marker exists at /project/.taskmaster only, and the function should traverse from a deeply nested directory up to that location.


84-96: LGTM! Test correctly validates fallback behavior.

This test properly verifies that findProjectRoot falls back to process.cwd() when no markers are found anywhere in the traversal, ensuring the function always returns a valid path as documented.


141-153: LGTM! Test correctly validates filesystem root handling.

This test verifies that when starting from the filesystem root with no markers present, the function correctly detects it has reached the root and falls back to process.cwd() without attempting further traversal.


155-182: LGTM! Comprehensive test validates all documented markers.

This test iterates through various project markers (Task Master, VCS, and language-specific markers) to verify that each one is recognized during traversal. This provides good coverage of the expanded marker list documented in the PR objectives.


186-192: LGTM! Test correctly validates empty string handling.

This edge case test ensures the function gracefully handles an empty string input by returning a valid path (likely process.cwd() after path.resolve('')), preventing crashes from invalid input.


194-207: LGTM! Test validates relative path resolution.

This test ensures that relative paths are properly resolved to absolute paths by the path.resolve() call in the implementation (line 79 of src/utils/path-utils.js), allowing the function to work correctly regardless of whether absolute or relative paths are provided.

Comment thread tests/unit/path-utils-find-project-root.test.js
Comment thread tests/unit/path-utils-find-project-root.test.js

@Crunchyman-ralph Crunchyman-ralph left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm, just address coderabbit comments, run npm run format, make sure CI is passing and lets merge this!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a963a9 and 2e8579e.

📒 Files selected for processing (2)
  • src/utils/path-utils.js (1 hunks)
  • tests/unit/path-utils-find-project-root.test.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
tests/{unit,integration,e2e,fixtures}/**/*.js

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Test files must be organized as follows: unit tests in tests/unit/, integration tests in tests/integration/, end-to-end tests in tests/e2e/, and test fixtures in tests/fixtures/.

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/unit/*.js

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Each module should have a corresponding unit test file in tests/unit/ that reflects the module structure (one test file per module).

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.{test,spec}.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/git_workflow.mdc)

**/*.{test,spec}.{js,ts,jsx,tsx}: Create a test file and ensure all tests pass when all subtasks are complete; commit tests if added or modified
When all subtasks are complete, run final testing using the appropriate test runner (e.g., npm test, jest, or manual testing)

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.test.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

**/*.test.js: Never use asynchronous operations in tests. Make all mocks return synchronous values when possible.
Always mock tests properly based on the way the tested functions are defined and used.
Follow the test file organization: mocks must be set up before importing modules under test, and spies on mocked modules should be set up after imports.
Use fixtures from tests/fixtures/ for consistent sample data across tests.
Always declare mocks before importing the modules being tested in Jest test files.
Use jest.spyOn() after imports to create spies on mock functions and reference these spies in test assertions.
When testing functions with callbacks, get the callback from your mock's call arguments, execute it directly with test inputs, and verify the results.
For ES modules, use jest.mock() before static imports and jest.unstable_mockModule() before dynamic imports to mock dependencies.
Reset mock functions (mockFn.mockReset()) before dynamic imports if they might have been called previously.
When verifying console assertions, assert against the actual arguments passed (single formatted string), not multiple arguments.
Use mock-fs to mock file system operations in tests, and restore the file system after each test.
Mock API calls (e.g., Anthropic/Claude) by mocking the entire module and providing predictable responses.
Set mock environment variables in test setup and restore them after each test.
Maintain test fixtures separate from test logic.
Follow the mock-first-then-import pattern for all Jest mocks.
Do not define mock variables before jest.mock() calls (they won't be accessible due to hoisting).
Use test-specific file paths (e.g., 'test-tasks.json') for all file operations in tests.
Mock readJSON and writeJSON to avoid real file system interactions in tests.
Verify file operations use the correct paths in expect statements.
Use different file paths for each test to avoid test interdependence.
Verify modifications on the in-memory task objects passed to w...

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/unit/**/*.test.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

tests/unit/**/*.test.js: Unit tests must be located in tests/unit/, test individual functions and utilities in isolation, mock all external dependencies, and keep tests small, focused, and fast.
Do not include actual command execution in unit tests.

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/{unit,integration,e2e}/**/*.test.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

tests/{unit,integration,e2e}/**/*.test.js: When testing CLI commands built with Commander.js, test the command action handlers directly rather than trying to mock the entire Commander.js chain.
When mocking the Commander.js chain, mock ALL chainable methods (option, argument, action, on, etc.) and return this (or the mock object) from all chainable method mocks.
Explicitly handle all options, including defaults and shorthand flags (e.g., -p for --prompt), and include null/undefined checks in test implementations for parameters that might be optional.
Do not try to use the real action implementation without proper mocking, and do not mock Commander partially—either mock it completely or test the action directly.
Mock the action handlers for CLI commands and verify they're called with correct arguments.
Use sample task fixtures for consistent test data, mock file system operations, and test both success and error paths for task operations.
Mock console output and verify correct formatting in UI function tests. Use flexible assertions like toContain() or toMatch() for formatted output.
Mock chalk functions to return the input text to make testing easier while still verifying correct function calls.

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

**/*.js: Declare and initialize global variables at the top of modules to avoid hoisting issues.
Use proper function declarations to avoid hoisting issues and initialize variables before they are referenced.
Do not reference variables before their declaration in module scope.
Use dynamic imports (import()) to avoid initialization order issues in modules.

Files:

  • tests/unit/path-utils-find-project-root.test.js
  • src/utils/path-utils.js
**/*.{test,spec}.*

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Test files should follow naming conventions: .test., .spec., or _test. depending on the language

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/{unit,integration,e2e}/**

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Organize test directories by test type (unit, integration, e2e) and mirror source structure where possible

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{test,spec}.{js,jsx,ts,tsx}: NEVER use async/await in test functions unless testing actual asynchronous operations
Use synchronous top-level imports in tests; avoid dynamic await import()
Keep test bodies synchronous whenever possible

Files:

  • tests/unit/path-utils-find-project-root.test.js
{src/utils/**,src/middleware/**}

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Test coverage for all code should meet or exceed 80% lines/functions and 70% branches globally; critical code (utils, middleware) should meet higher thresholds (90% utils, 85% middleware)

Files:

  • src/utils/path-utils.js
🧠 Learnings (5)
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to **/*.test.js : Verify file operations use the correct paths in expect statements.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to tests/{unit,integration,e2e}/**/*.test.js : Use sample task fixtures for consistent test data, mock file system operations, and test both success and error paths for task operations.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to **/*.test.js : Use mock-fs to mock file system operations in tests, and restore the file system after each test.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to **/*.test.js : Use test-specific file paths (e.g., 'test-tasks.json') for all file operations in tests.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:11:36.732Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/mcp.mdc:0-0
Timestamp: 2025-07-18T17:11:36.732Z
Learning: Applies to mcp-server/src/core/direct-functions/*.js : The *Direct function is responsible for finding the exact tasks.json path using findTasksJsonPath, relying on the projectRoot passed in args.

Applied to files:

  • src/utils/path-utils.js
🧬 Code graph analysis (2)
tests/unit/path-utils-find-project-root.test.js (1)
src/utils/path-utils.js (2)
  • findProjectRoot (55-115)
  • projectMarkers (58-77)
src/utils/path-utils.js (2)
mcp-server/src/core/utils/path-utils.js (1)
  • findProjectRoot (201-203)
scripts/modules/config-manager.js (1)
  • rootDir (858-858)
🔇 Additional comments (2)
tests/unit/path-utils-find-project-root.test.js (1)

1-223: LGTM! Comprehensive test coverage.

The test suite provides excellent coverage of the findProjectRoot functionality:

✅ Parent directory traversal across multiple levels
✅ Detection of various project markers (Task Master, VCS, language-specific)
✅ Permission error handling without aborting traversal
✅ Filesystem root detection and safe termination
✅ Max depth enforcement to prevent excessive traversal
✅ Fallback to process.cwd() when no markers are found
✅ Edge cases (empty/relative paths)

The test suite successfully validates all the requirements from issue #1301 and ensures the enhanced findProjectRoot function behaves correctly in various scenarios including multi-repo monorepos and deep directory structures.

Based on coding guidelines.

src/utils/path-utils.js (1)

55-115: LGTM! Robust parent directory traversal implementation.

The enhanced findProjectRoot function successfully addresses all requirements from issue #1301:

Upward traversal: Explicitly moves up via path.dirname(currentDir) and checks markers at each level
Error tolerance: try-catch around fs.existsSync handles permission errors gracefully without aborting traversal
Safe termination: Detects filesystem root via parentDir === currentDir check and enforces maxDepth limit (50 levels)
Priority ordering: Task Master markers (.taskmaster, config, tasks) checked before generic markers (.git, VCS, language-specific)
Broad compatibility: Expanded marker set includes Rust, Go, Python, Ruby, PHP, and Node.js ecosystems
Reliable fallback: Returns process.cwd() when no markers found, ensuring function always returns a valid path
Clear documentation: JSDoc updated to document traversal behavior, depth limit, and fallback semantics

The implementation is well-structured, handles edge cases properly, and enables the use cases described in the PR objectives (multi-repo monorepos, deep directory structures, consistent command behavior from subdirectories).

Optional enhancement: Consider logging when maxDepth is reached without finding a marker, which could aid debugging in projects with extremely deep structures. However, this may be intentionally omitted to keep the utility function lightweight.

Based on coding guidelines.

Comment thread tests/unit/path-utils-find-project-root.test.js
Comment on lines +111 to +130
test('should handle permission errors gracefully', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');

mockExistsSync.mockImplementation((checkPath) => {
const normalized = path.normalize(checkPath);
// Throw permission error for checks in /project/subdir
if (normalized.startsWith('/project/subdir/')) {
throw new Error('EACCES: permission denied');
}
// Return true only for .taskmaster at /project
return normalized.includes('/project/.taskmaster');
});

const result = findProjectRoot('/project/subdir');

// Should handle permission errors in subdirectory and traverse to parent
expect(result).toBe('/project');

mockExistsSync.mockRestore();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Improve mock precision with exact path comparisons.

The mock uses .startsWith() and .includes() which could match unintended paths. While .startsWith('/project/subdir/') is better than .includes(), it would still match paths like /project/subdir/any/nested/path/.taskmaster. For consistency with the other tests that were revised to use exact comparison (lines 15-29, 31-44, 46-59), consider using exact path matching.

Consider refactoring for more explicit path matching:

 		test('should handle permission errors gracefully', () => {
 			const mockExistsSync = jest.spyOn(fs, 'existsSync');
 
 			mockExistsSync.mockImplementation((checkPath) => {
 				const normalized = path.normalize(checkPath);
-				// Throw permission error for checks in /project/subdir
-				if (normalized.startsWith('/project/subdir/')) {
+				// Throw permission error only for marker checks in the starting subdirectory
+				const subdirMarkers = ['.taskmaster', '.git', 'package.json', 'Cargo.toml', 
+					'go.mod', 'pyproject.toml', 'requirements.txt', 'Gemfile', 'composer.json',
+					'yarn.lock', 'package-lock.json', 'pnpm-lock.yaml', '.svn', 'tasks.json'];
+				const isSubdirCheck = subdirMarkers.some(marker => 
+					normalized === path.normalize(`/project/subdir/${marker}`) ||
+					normalized === path.normalize(`/project/subdir/.taskmaster/${marker}`)
+				);
+				if (isSubdirCheck) {
 					throw new Error('EACCES: permission denied');
 				}
-				// Return true only for .taskmaster at /project
-				return normalized.includes('/project/.taskmaster');
+				// Return true only for exact .taskmaster marker at /project
+				return normalized === path.normalize('/project/.taskmaster');
 			});
 
 			const result = findProjectRoot('/project/subdir');
 
-			// Should handle permission errors in subdirectory and traverse to parent
 			expect(result).toBe('/project');
 
 			mockExistsSync.mockRestore();
 		});

Based on coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('should handle permission errors gracefully', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');
mockExistsSync.mockImplementation((checkPath) => {
const normalized = path.normalize(checkPath);
// Throw permission error for checks in /project/subdir
if (normalized.startsWith('/project/subdir/')) {
throw new Error('EACCES: permission denied');
}
// Return true only for .taskmaster at /project
return normalized.includes('/project/.taskmaster');
});
const result = findProjectRoot('/project/subdir');
// Should handle permission errors in subdirectory and traverse to parent
expect(result).toBe('/project');
mockExistsSync.mockRestore();
});
test('should handle permission errors gracefully', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');
mockExistsSync.mockImplementation((checkPath) => {
const normalized = path.normalize(checkPath);
// Throw permission error only for marker checks in the starting subdirectory
const subdirMarkers = ['.taskmaster', '.git', 'package.json', 'Cargo.toml',
'go.mod', 'pyproject.toml', 'requirements.txt', 'Gemfile', 'composer.json',
'yarn.lock', 'package-lock.json', 'pnpm-lock.yaml', '.svn', 'tasks.json'];
const isSubdirCheck = subdirMarkers.some(marker =>
normalized === path.normalize(`/project/subdir/${marker}`) ||
normalized === path.normalize(`/project/subdir/.taskmaster/${marker}`)
);
if (isSubdirCheck) {
throw new Error('EACCES: permission denied');
}
// Return true only for exact .taskmaster marker at /project
return normalized === path.normalize('/project/.taskmaster');
});
const result = findProjectRoot('/project/subdir');
expect(result).toBe('/project');
mockExistsSync.mockRestore();
});

Comment on lines +146 to +174
test('should recognize various project markers', () => {
const projectMarkers = [
'.taskmaster',
'.git',
'package.json',
'Cargo.toml',
'go.mod',
'pyproject.toml',
'requirements.txt',
'Gemfile',
'composer.json'
];

projectMarkers.forEach((marker) => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');

mockExistsSync.mockImplementation((checkPath) => {
const normalized = path.normalize(checkPath);
return normalized.includes(`/project/${marker}`);
});

const result = findProjectRoot('/project/subdir');

expect(result).toBe('/project');

mockExistsSync.mockRestore();
});
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Improve mock precision with exact path comparisons.

The mock uses .includes() which could match unintended paths like /some/other/project/Cargo.toml or /project/Cargo.toml/nested/file. For consistency with the refactored tests (lines 15-29, 31-44, 46-59), consider using exact path comparison.

Apply this diff to use exact path matching:

 		test('should recognize various project markers', () => {
 			const projectMarkers = [
 				'.taskmaster',
 				'.git',
 				'package.json',
 				'Cargo.toml',
 				'go.mod',
 				'pyproject.toml',
 				'requirements.txt',
 				'Gemfile',
 				'composer.json'
 			];
 
 			projectMarkers.forEach((marker) => {
 				const mockExistsSync = jest.spyOn(fs, 'existsSync');
 
 				mockExistsSync.mockImplementation((checkPath) => {
 					const normalized = path.normalize(checkPath);
-					return normalized.includes(`/project/${marker}`);
+					return normalized === path.normalize(`/project/${marker}`);
 				});
 
 				const result = findProjectRoot('/project/subdir');
 
 				expect(result).toBe('/project');
 
 				mockExistsSync.mockRestore();
 			});
 		});

Based on coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('should recognize various project markers', () => {
const projectMarkers = [
'.taskmaster',
'.git',
'package.json',
'Cargo.toml',
'go.mod',
'pyproject.toml',
'requirements.txt',
'Gemfile',
'composer.json'
];
projectMarkers.forEach((marker) => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');
mockExistsSync.mockImplementation((checkPath) => {
const normalized = path.normalize(checkPath);
return normalized.includes(`/project/${marker}`);
});
const result = findProjectRoot('/project/subdir');
expect(result).toBe('/project');
mockExistsSync.mockRestore();
});
});
});
test('should recognize various project markers', () => {
const projectMarkers = [
'.taskmaster',
'.git',
'package.json',
'Cargo.toml',
'go.mod',
'pyproject.toml',
'requirements.txt',
'Gemfile',
'composer.json'
];
projectMarkers.forEach((marker) => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');
mockExistsSync.mockImplementation((checkPath) => {
const normalized = path.normalize(checkPath);
return normalized === path.normalize(`/project/${marker}`);
});
const result = findProjectRoot('/project/subdir');
expect(result).toBe('/project');
mockExistsSync.mockRestore();
});
});
});
🤖 Prompt for AI Agents
In tests/unit/path-utils-find-project-root.test.js around lines 146 to 174, the
test's mock for fs.existsSync uses string.includes which can produce false
positives; change the mockImplementation to perform exact normalized path
comparisons (e.g., normalize the checkPath and compare it strictly to the
expected marker path like path.join('/project', marker) or the full file path
you expect) and restore the spy afterwards — update the mock to return true only
when the normalized checkPath equals the exact path for the marker so the
assertion remains deterministic.

Comment on lines +177 to +198
test('should handle empty string as startDir', () => {
const result = findProjectRoot('');

// Should use process.cwd() or fall back appropriately
expect(typeof result).toBe('string');
expect(result.length).toBeGreaterThan(0);
});

test('should handle relative paths', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');

mockExistsSync.mockImplementation((checkPath) => {
// Simulate .git existing in the resolved path
return checkPath.includes('.git');
});

const result = findProjectRoot('./subdir');

expect(typeof result).toBe('string');

mockExistsSync.mockRestore();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Strengthen edge case test assertions.

Both edge case tests have weak assertions that only verify the result is a non-empty string, but don't verify the actual behavior of findProjectRoot. The empty string test doesn't verify that the function properly handles empty input, and the relative paths test doesn't verify that the function resolves relative paths correctly before traversing.

Consider strengthening these tests to verify actual behavior:

 		test('should handle empty string as startDir', () => {
+			const mockExistsSync = jest.spyOn(fs, 'existsSync');
+			mockExistsSync.mockReturnValue(false);
+			
 			const result = findProjectRoot('');
 
-			// Should use process.cwd() or fall back appropriately
-			expect(typeof result).toBe('string');
-			expect(result.length).toBeGreaterThan(0);
+			// Should fall back to process.cwd() when no markers are found
+			expect(result).toBe(process.cwd());
+			
+			mockExistsSync.mockRestore();
 		});
 
 		test('should handle relative paths', () => {
 			const mockExistsSync = jest.spyOn(fs, 'existsSync');
 
 			mockExistsSync.mockImplementation((checkPath) => {
-				// Simulate .git existing in the resolved path
-				return checkPath.includes('.git');
+				const normalized = path.normalize(checkPath);
+				// .git exists at the resolved absolute parent of ./subdir
+				const expectedPath = path.normalize(path.join(process.cwd(), '.git'));
+				return normalized === expectedPath;
 			});
 
 			const result = findProjectRoot('./subdir');
 
-			expect(typeof result).toBe('string');
+			// Should resolve relative path and find parent marker
+			expect(result).toBe(process.cwd());
 
 			mockExistsSync.mockRestore();
 		});

Based on coding guidelines.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('should handle empty string as startDir', () => {
const result = findProjectRoot('');
// Should use process.cwd() or fall back appropriately
expect(typeof result).toBe('string');
expect(result.length).toBeGreaterThan(0);
});
test('should handle relative paths', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');
mockExistsSync.mockImplementation((checkPath) => {
// Simulate .git existing in the resolved path
return checkPath.includes('.git');
});
const result = findProjectRoot('./subdir');
expect(typeof result).toBe('string');
mockExistsSync.mockRestore();
});
test('should handle empty string as startDir', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');
mockExistsSync.mockReturnValue(false);
const result = findProjectRoot('');
// Should fall back to process.cwd() when no markers are found
expect(result).toBe(process.cwd());
mockExistsSync.mockRestore();
});
test('should handle relative paths', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');
mockExistsSync.mockImplementation((checkPath) => {
const normalized = path.normalize(checkPath);
// .git exists at the resolved absolute parent of ./subdir
const expectedPath = path.normalize(path.join(process.cwd(), '.git'));
return normalized === expectedPath;
});
const result = findProjectRoot('./subdir');
// Should resolve relative path and find parent marker
expect(result).toBe(process.cwd());
mockExistsSync.mockRestore();
});
🤖 Prompt for AI Agents
In tests/unit/path-utils-find-project-root.test.js around lines 177 to 198,
strengthen the two edge-case tests: for the empty-string case, assert that
findProjectRoot('') resolves to process.cwd() (or path.resolve('')) rather than
just a non-empty string; for the relative-path case, compute const start =
path.resolve('./subdir') before calling findProjectRoot, then assert the
returned result is rooted at or equal to that resolved start (e.g.
result.startsWith(start) or result === start) and also assert the mocked
fs.existsSync was called with the expected .git check path (e.g.
path.join(start, '.git')), then restore the mock.

@Crunchyman-ralph Crunchyman-ralph left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm, want to play around with it manually a little bit before releasing as part of 0.29.0, so it will probably be a part of 0.30.0

@Crunchyman-ralph Crunchyman-ralph changed the base branch from main to next October 14, 2025 15:28
@Crunchyman-ralph Crunchyman-ralph force-pushed the fix/parent-directory-traversal branch from 6840387 to 54e3161 Compare October 14, 2025 15:29
bjcoombs and others added 6 commits October 14, 2025 17:29
Improves parent directory traversal in findProjectRoot() to correctly
locate Task Master projects when running commands from subdirectories.

Changes:
- Enhanced project marker detection with Task Master-specific markers prioritized
- Added error handling for permission errors during directory traversal
- Added safety check for filesystem root detection
- Expanded project marker list to support more project types
- Improved documentation and code comments

This fix enables Task Master to work seamlessly in multi-repo projects
where subdirectories need to reference a parent-level Task Master configuration.

Resolves issue where running task-master commands from subdirectories
would incorrectly create new .taskmaster directories instead of finding
the parent project's configuration.
- Document maxDepth limit (50 levels) in JSDoc
- Fix test assertion to verify current directory priority
- Add precise depth limit assertion in test
- Simplify changeset description to imperative mood
- Replace substring matching with exact path comparisons in mocks
- Make permission error test more explicit about error location
- Run formatter (biome) to ensure consistent code style

Addresses CodeRabbit review feedback
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
@Crunchyman-ralph Crunchyman-ralph force-pushed the fix/parent-directory-traversal branch from 54e3161 to af3004c Compare October 14, 2025 15:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

♻️ Duplicate comments (5)
tests/unit/path-utils-find-project-root.test.js (5)

94-100: Use exact path comparison instead of substring includes.

Prevent false positives by matching exact normalized/resolved paths.

- if (normalized.includes('/project/subdir/.git')) return true;
- if (normalized.includes('/project/.taskmaster')) return true;
+ if (normalized === path.resolve('/project/subdir', '.git')) return true;
+ if (normalized === path.resolve('/project', '.taskmaster')) return true;
  return false;

111-122: Tighten permission‑error mock to exact marker checks.

startsWith/includes can mask unintended paths; enumerate exact marker probe paths in the starting dir.

- if (normalized.startsWith('/project/subdir/')) {
+ const subdirMarkers = [
+   '.taskmaster', '.git', 'package.json', 'Cargo.toml', 'go.mod',
+   'pyproject.toml', 'requirements.txt', 'Gemfile', 'composer.json',
+   'yarn.lock', 'package-lock.json', 'pnpm-lock.yaml', '.svn', 'tasks.json'
+ ];
+ const isSubdirCheck = subdirMarkers.some(m =>
+   normalized === path.resolve('/project/subdir', m) ||
+   normalized === path.resolve('/project/subdir/.taskmaster', m)
+ );
+ if (isSubdirCheck) {
    throw new Error('EACCES: permission denied');
  }
- return normalized.includes('/project/.taskmaster');
+ return normalized === path.resolve('/project', '.taskmaster');

146-165: Use exact path equality for marker recognition.

Replace includes with exact resolved path checks for determinism.

- return normalized.includes(`/project/${marker}`);
+ return normalized === path.resolve('/project', marker);

176-183: Strengthen empty‑input test to assert fallback precisely.

Assert explicit fallback and avoid vague “non‑empty string”.

- const result = findProjectRoot('');
- // Should use process.cwd() or fall back appropriately
- expect(typeof result).toBe('string');
- expect(result.length).toBeGreaterThan(0);
+ const mockExistsSync = jest.spyOn(fs, 'existsSync').mockReturnValue(false);
+ const result = findProjectRoot('');
+ expect(result).toBe(process.cwd());
+ mockExistsSync.mockRestore();

185-197: Resolve relative paths test and assert exact result.

Make the mock and expectation precise and platform‑neutral.

- mockExistsSync.mockImplementation((checkPath) => {
-   // Simulate .git existing in the resolved path
-   return checkPath.includes('.git');
- });
+ mockExistsSync.mockImplementation((checkPath) => {
+   const expected = path.resolve(process.cwd(), '.git');
+   return path.normalize(checkPath) === expected;
+ });

- const result = findProjectRoot('./subdir');
- expect(typeof result).toBe('string');
+ const result = findProjectRoot('./subdir');
+ expect(result).toBe(process.cwd());
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e8579e and af3004c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • .changeset/fix-parent-directory-traversal.md (1 hunks)
  • CHANGELOG.md (1 hunks)
  • apps/cli/CHANGELOG.md (1 hunks)
  • apps/docs/CHANGELOG.md (1 hunks)
  • apps/docs/package.json (1 hunks)
  • apps/extension/CHANGELOG.md (1 hunks)
  • apps/extension/package.json (1 hunks)
  • package.json (1 hunks)
  • packages/ai-sdk-provider-grok-cli/CHANGELOG.md (1 hunks)
  • packages/build-config/CHANGELOG.md (1 hunks)
  • packages/claude-code-plugin/CHANGELOG.md (1 hunks)
  • packages/claude-code-plugin/package.json (1 hunks)
  • packages/tm-core/CHANGELOG.md (1 hunks)
  • src/utils/path-utils.js (1 hunks)
  • tests/unit/path-utils-find-project-root.test.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (15)
package.json

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Add and update test scripts in package.json to include test, test:watch, test:coverage, test:unit, test:integration, test:e2e, and test:ci

Files:

  • package.json
.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/changeset.mdc)

.changeset/*.md: When running npm run changeset or npx changeset add, provide a concise summary of the changes for the CHANGELOG.md in imperative mood, typically a single line, and not a detailed Git commit message.
The changeset summary should be user-facing, describing what changed in the released version that is relevant to users or consumers of the package.
Do not use your detailed Git commit message body as the changeset summary.

Files:

  • .changeset/fix-parent-directory-traversal.md
.changeset/*

📄 CodeRabbit inference engine (.cursor/rules/new_features.mdc)

Create appropriate changesets for new features, use semantic versioning, include tagged system information in release notes, and document breaking changes if any.

Files:

  • .changeset/fix-parent-directory-traversal.md
.changeset/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Changeset entries should be user-facing, describing the end-user impact rather than code specifics

Files:

  • .changeset/fix-parent-directory-traversal.md
tests/{unit,integration,e2e,fixtures}/**/*.js

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Test files must be organized as follows: unit tests in tests/unit/, integration tests in tests/integration/, end-to-end tests in tests/e2e/, and test fixtures in tests/fixtures/.

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/unit/*.js

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Each module should have a corresponding unit test file in tests/unit/ that reflects the module structure (one test file per module).

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.{test,spec}.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/git_workflow.mdc)

**/*.{test,spec}.{js,ts,jsx,tsx}: Create a test file and ensure all tests pass when all subtasks are complete; commit tests if added or modified
When all subtasks are complete, run final testing using the appropriate test runner (e.g., npm test, jest, or manual testing)

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.test.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

**/*.test.js: Never use asynchronous operations in tests. Make all mocks return synchronous values when possible.
Always mock tests properly based on the way the tested functions are defined and used.
Follow the test file organization: mocks must be set up before importing modules under test, and spies on mocked modules should be set up after imports.
Use fixtures from tests/fixtures/ for consistent sample data across tests.
Always declare mocks before importing the modules being tested in Jest test files.
Use jest.spyOn() after imports to create spies on mock functions and reference these spies in test assertions.
When testing functions with callbacks, get the callback from your mock's call arguments, execute it directly with test inputs, and verify the results.
For ES modules, use jest.mock() before static imports and jest.unstable_mockModule() before dynamic imports to mock dependencies.
Reset mock functions (mockFn.mockReset()) before dynamic imports if they might have been called previously.
When verifying console assertions, assert against the actual arguments passed (single formatted string), not multiple arguments.
Use mock-fs to mock file system operations in tests, and restore the file system after each test.
Mock API calls (e.g., Anthropic/Claude) by mocking the entire module and providing predictable responses.
Set mock environment variables in test setup and restore them after each test.
Maintain test fixtures separate from test logic.
Follow the mock-first-then-import pattern for all Jest mocks.
Do not define mock variables before jest.mock() calls (they won't be accessible due to hoisting).
Use test-specific file paths (e.g., 'test-tasks.json') for all file operations in tests.
Mock readJSON and writeJSON to avoid real file system interactions in tests.
Verify file operations use the correct paths in expect statements.
Use different file paths for each test to avoid test interdependence.
Verify modifications on the in-memory task objects passed to w...

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/unit/**/*.test.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

tests/unit/**/*.test.js: Unit tests must be located in tests/unit/, test individual functions and utilities in isolation, mock all external dependencies, and keep tests small, focused, and fast.
Do not include actual command execution in unit tests.

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/{unit,integration,e2e}/**/*.test.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

tests/{unit,integration,e2e}/**/*.test.js: When testing CLI commands built with Commander.js, test the command action handlers directly rather than trying to mock the entire Commander.js chain.
When mocking the Commander.js chain, mock ALL chainable methods (option, argument, action, on, etc.) and return this (or the mock object) from all chainable method mocks.
Explicitly handle all options, including defaults and shorthand flags (e.g., -p for --prompt), and include null/undefined checks in test implementations for parameters that might be optional.
Do not try to use the real action implementation without proper mocking, and do not mock Commander partially—either mock it completely or test the action directly.
Mock the action handlers for CLI commands and verify they're called with correct arguments.
Use sample task fixtures for consistent test data, mock file system operations, and test both success and error paths for task operations.
Mock console output and verify correct formatting in UI function tests. Use flexible assertions like toContain() or toMatch() for formatted output.
Mock chalk functions to return the input text to make testing easier while still verifying correct function calls.

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

**/*.js: Declare and initialize global variables at the top of modules to avoid hoisting issues.
Use proper function declarations to avoid hoisting issues and initialize variables before they are referenced.
Do not reference variables before their declaration in module scope.
Use dynamic imports (import()) to avoid initialization order issues in modules.

Files:

  • tests/unit/path-utils-find-project-root.test.js
  • src/utils/path-utils.js
**/*.{test,spec}.*

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Test files should follow naming conventions: .test., .spec., or _test. depending on the language

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/{unit,integration,e2e}/**

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Organize test directories by test type (unit, integration, e2e) and mirror source structure where possible

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{test,spec}.{js,jsx,ts,tsx}: NEVER use async/await in test functions unless testing actual asynchronous operations
Use synchronous top-level imports in tests; avoid dynamic await import()
Keep test bodies synchronous whenever possible

Files:

  • tests/unit/path-utils-find-project-root.test.js
{src/utils/**,src/middleware/**}

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Test coverage for all code should meet or exceed 80% lines/functions and 70% branches globally; critical code (utils, middleware) should meet higher thresholds (90% utils, 85% middleware)

Files:

  • src/utils/path-utils.js
🧠 Learnings (10)
📚 Learning: 2025-07-18T17:07:53.100Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/changeset.mdc:0-0
Timestamp: 2025-07-18T17:07:53.100Z
Learning: Applies to .changeset/*.md : When running `npm run changeset` or `npx changeset add`, provide a concise summary of the changes for the `CHANGELOG.md` in imperative mood, typically a single line, and not a detailed Git commit message.

Applied to files:

  • .changeset/fix-parent-directory-traversal.md
📚 Learning: 2025-10-08T12:21:14.455Z
Learnt from: Crunchyman-ralph
PR: eyaltoledano/claude-task-master#1285
File: .changeset/nice-ways-hope.md:5-17
Timestamp: 2025-10-08T12:21:14.455Z
Learning: For changeset files (.changeset/*.md): The first line should be concise and in imperative mood, but the body after the first line can include as many user-facing details as desired (bullets, explanations, links, etc.) to provide context for the CHANGELOG.md.

Applied to files:

  • .changeset/fix-parent-directory-traversal.md
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to **/*.test.js : Verify file operations use the correct paths in expect statements.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to tests/{unit,integration,e2e}/**/*.test.js : Use sample task fixtures for consistent test data, mock file system operations, and test both success and error paths for task operations.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to **/*.test.js : Use mock-fs to mock file system operations in tests, and restore the file system after each test.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to **/*.test.js : Use test-specific file paths (e.g., 'test-tasks.json') for all file operations in tests.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:11:36.732Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/mcp.mdc:0-0
Timestamp: 2025-07-18T17:11:36.732Z
Learning: Applies to mcp-server/src/core/direct-functions/*.js : The *Direct function is responsible for finding the exact tasks.json path using findTasksJsonPath, relying on the projectRoot passed in args.

Applied to files:

  • src/utils/path-utils.js
📚 Learning: 2025-09-26T19:03:33.225Z
Learnt from: Crunchyman-ralph
PR: eyaltoledano/claude-task-master#1252
File: package.json:130-132
Timestamp: 2025-09-26T19:03:33.225Z
Learning: In the eyaltoledano/claude-task-master repository, all packages with tm prefix are internal packages that are part of the monorepo structure and are not published externally.

Applied to files:

  • packages/claude-code-plugin/package.json
📚 Learning: 2025-09-26T19:05:47.555Z
Learnt from: Crunchyman-ralph
PR: eyaltoledano/claude-task-master#1252
File: packages/ai-sdk-provider-grok-cli/package.json:11-13
Timestamp: 2025-09-26T19:05:47.555Z
Learning: In the eyaltoledano/claude-task-master repository, internal tm/ packages use a specific export pattern where the "exports" field points to TypeScript source files (./src/index.ts) while "main" points to compiled output (./dist/index.js) and "types" points to source files (./src/index.ts). This pattern is used consistently across internal packages like tm/core and tm/ai-sdk-provider-grok-cli because they are consumed directly during build-time bundling with tsdown rather than being published as separate packages.

Applied to files:

  • packages/claude-code-plugin/package.json
📚 Learning: 2025-09-22T19:45:13.323Z
Learnt from: Crunchyman-ralph
PR: eyaltoledano/claude-task-master#1232
File: packages/build-config/package.json:14-15
Timestamp: 2025-09-22T19:45:13.323Z
Learning: In the eyaltoledano/claude-task-master repository, Crunchyman-ralph intentionally omits version fields from internal packages (like tm/build-config) to prevent changesets from releasing new versions for these packages. This is the desired behavior for internal tooling packages that should not be published or versioned independently.

Applied to files:

  • packages/claude-code-plugin/package.json
🧬 Code graph analysis (2)
tests/unit/path-utils-find-project-root.test.js (1)
src/utils/path-utils.js (2)
  • findProjectRoot (55-115)
  • projectMarkers (58-77)
src/utils/path-utils.js (2)
mcp-server/src/core/utils/path-utils.js (1)
  • findProjectRoot (202-204)
scripts/modules/config-manager.js (1)
  • rootDir (858-858)
🪛 LanguageTool
apps/docs/CHANGELOG.md

[grammar] ~3-~3: Hier könnte ein Fehler sein.
Context: # docs ## 0.0.6 ## 0.0.5 ## 0.0.4 ## 0.0.3 ## 0.0.2 ## ...

(QB_NEW_DE)

packages/build-config/CHANGELOG.md

[grammar] ~7-~7: Hier könnte ein Fehler sein.
Context: .../build-config ## null ## null ## null ## 1.0.1

(QB_NEW_DE)

packages/claude-code-plugin/CHANGELOG.md

[grammar] ~1-~1: Hier könnte ein Fehler sein.
Context: # @tm/claude-code-plugin ## 0.0.2

(QB_NEW_DE)


[grammar] ~3-~3: Hier könnte ein Fehler sein.
Context: # @tm/claude-code-plugin ## 0.0.2

(QB_NEW_DE)

apps/extension/CHANGELOG.md

[grammar] ~3-~3: Use correct spacing
Context: # Change Log ## 0.25.6 ## 0.25.6-rc.0 ### Patch Changes - Update...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

apps/cli/CHANGELOG.md

[grammar] ~14-~14: There might be a mistake here.
Context: ... ## null ### Patch Changes - Updated dependencies []: - @tm/core@null ## null ### Patch C...

(QB_NEW_EN_OTHER)


[grammar] ~15-~15: Use correct spacing
Context: ...dated dependencies []: - @tm/core@null ## null ### Patch Changes - Updated depen...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~17-~17: Use correct spacing
Context: ...endencies []: - @tm/core@null ## null ### Patch Changes - Updated dependencies []...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~19-~19: Use correct spacing
Context: ...tm/core@null ## null ### Patch Changes - Updated dependencies []: - @tm/core@nu...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

.changeset/fix-parent-directory-traversal.md

[grammar] ~5-~5: There might be a mistake here.
Context: ...s to find project root from nested paths Fixes #1301

(QB_NEW_EN_OTHER)

packages/tm-core/CHANGELOG.md

[grammar] ~7-~7: Use correct spacing
Context: # Changelog ## null ## null ## null ## 0.26.1 All notable changes to the @task...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

🪛 markdownlint-cli2 (0.18.1)
packages/ai-sdk-provider-grok-cli/CHANGELOG.md

5-5: Multiple headings with the same content

(MD024, no-duplicate-heading)

apps/cli/CHANGELOG.md

17-17: Multiple headings with the same content

(MD024, no-duplicate-heading)

.changeset/fix-parent-directory-traversal.md

5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

packages/tm-core/CHANGELOG.md

7-7: Multiple headings with the same content

(MD024, no-duplicate-heading)

🔇 Additional comments (10)
packages/claude-code-plugin/CHANGELOG.md (1)

1-3: Changelog entry OK

Header for 0.0.2 added; no issues.

apps/docs/CHANGELOG.md (1)

3-4: Changelog bump looks good

0.0.6 section added appropriately.

packages/claude-code-plugin/package.json (1)

3-3: Version bump consistent

0.0.2 matches the changelog; no issues.

package.json (1)

13-39: Test scripts complete and aligned

All required test scripts present; Jest/runtime and typings are aligned for 29.x. Looks good.

apps/extension/package.json (1)

6-6: Version bump looks good.

0.25.6 aligns with the new changelog entry.

apps/extension/CHANGELOG.md (1)

3-4: Changelog entry added correctly.

Header for 0.25.6 matches the package.json bump.

tests/unit/path-utils-find-project-root.test.js (1)

200-221: Depth‑limit test looks solid.

Bounds assertion aligns with 50 levels × 18 markers.

.changeset/fix-parent-directory-traversal.md (1)

1-7: Changeset frontmatter correctly targets the affected package Both path-utils.js implementations belong to task-master-ai; no additional packages needed.

src/utils/path-utils.js (2)

84-110: Traversal logic + root/loop guards look solid.

Depth cap, root check, and parentDir safety prevent infinite loops and handle deep trees well. Nice.


55-56: Ignore startDir fallback change suggestion
Current tests require findProjectRoot to fall back to process.cwd() when no markers are found, even if a startDir is provided; returning the provided startDir will break existing behavior and tests.

Likely an incorrect or invalid review comment.

Comment thread apps/cli/CHANGELOG.md
Comment on lines +14 to +20
- Updated dependencies []:
- @tm/core@null

## null

### Patch Changes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove duplicate “## null” patch block

Redundant placeholder section; consolidate or replace with real versioned entry to satisfy MD024.

-- Updated dependencies []:
-  - @tm/core@null
-
-## null
-
-### Patch Changes
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- Updated dependencies []:
- @tm/core@null
## null
### Patch Changes
🧰 Tools
🪛 LanguageTool

[grammar] ~14-~14: There might be a mistake here.
Context: ... ## null ### Patch Changes - Updated dependencies []: - @tm/core@null ## null ### Patch C...

(QB_NEW_EN_OTHER)


[grammar] ~15-~15: Use correct spacing
Context: ...dated dependencies []: - @tm/core@null ## null ### Patch Changes - Updated depen...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~17-~17: Use correct spacing
Context: ...endencies []: - @tm/core@null ## null ### Patch Changes - Updated dependencies []...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~19-~19: Use correct spacing
Context: ...tm/core@null ## null ### Patch Changes - Updated dependencies []: - @tm/core@nu...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

🪛 markdownlint-cli2 (0.18.1)

17-17: Multiple headings with the same content

(MD024, no-duplicate-heading)

🤖 Prompt for AI Agents
In apps/cli/CHANGELOG.md around lines 14 to 20, there is a redundant "## null"
patch block and an invalid dependency entry; remove the duplicate "## null"
section (or merge its contents into the correct versioned section), ensure only
one version header exists for that change, and either replace the placeholder
dependency entry "@tm/core@null" with the correct version or remove the empty
dependency bullet so the file contains a single valid versioned entry and
satisfies MD024.

Comment thread apps/docs/package.json
{
"name": "docs",
"version": "0.0.5",
"version": "0.0.6",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Optional: Add standard test scripts or confirm omission

Docs app lacks test scripts. If intentional, ignore; otherwise, add minimal pass-through scripts to align with workspace conventions.

🤖 Prompt for AI Agents
In apps/docs/package.json at line 3, there are no test scripts defined; add a
minimal scripts section to align with workspace conventions (or confirm
omission). Specifically, add a "scripts" object containing at least a "test"
script that is a pass-through/no-op returning success (or delegating to the
workspace test runner), and optionally add "test:ci" or other standard targets
to match other packages; if omission is intentional, leave as-is and reply
confirming that decision.

Comment on lines +4 to +5

## null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix duplicate/placeholder “## null” heading

Duplicate “## null” section added. Remove or replace with a real version to avoid MD024 and confusion.

-## null
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## null
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

5-5: Multiple headings with the same content

(MD024, no-duplicate-heading)

🤖 Prompt for AI Agents
In packages/ai-sdk-provider-grok-cli/CHANGELOG.md around lines 4 to 5, remove
the duplicate placeholder heading "## null" (or replace it with a valid heading
such as "## Unreleased" or a proper version header like "## vX.Y.Z -
YYYY-MM-DD"); ensure there are no other duplicate level-2 headings to avoid
MD024 and keep the changelog consistent.

Comment on lines +7 to +8
## null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove placeholder/duplicate “## null” sections

“## null” is a placeholder and appears multiple times (duplicate headings). Clean up or replace with actual version entries to satisfy MD024 and readability.

Apply this minimal fix for the newly added lines:

-## null
-
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## null
🧰 Tools
🪛 LanguageTool

[grammar] ~7-~7: Hier könnte ein Fehler sein.
Context: .../build-config ## null ## null ## null ## 1.0.1

(QB_NEW_DE)

🤖 Prompt for AI Agents
In packages/build-config/CHANGELOG.md around lines 7 to 8, there is a
placeholder heading "## null" (duplicate/invalid entry); remove the "## null"
heading or replace it with the correct changelog version/title and accompanying
notes so the file contains valid, non-duplicate section headings (ensuring MD024
compliance and readability).

Comment on lines +7 to +8
## null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove stray “## null” section.

Duplicate/empty “## null” heading adds noise and triggers MD024. Drop the added block.

-## null
-

Based on static analysis hints.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## null
🧰 Tools
🪛 LanguageTool

[grammar] ~7-~7: Use correct spacing
Context: # Changelog ## null ## null ## null ## 0.26.1 All notable changes to the @task...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

🪛 markdownlint-cli2 (0.18.1)

7-7: Multiple headings with the same content

(MD024, no-duplicate-heading)

🤖 Prompt for AI Agents
In packages/tm-core/CHANGELOG.md around lines 7 to 8, remove the stray "## null"
heading: delete that empty/duplicate markdown section so the changelog no longer
contains a useless "## null" heading (ensuring surrounding content/spacing
remains valid and no other changelog entries are accidentally removed).

Comment thread src/utils/path-utils.js
Comment on lines +56 to 77
// Define project markers that indicate a project root
// Prioritize Task Master specific markers first
const projectMarkers = [
'.taskmaster',
TASKMASTER_TASKS_FILE,
'tasks.json',
LEGACY_TASKS_FILE,
'.git',
'.svn',
'package.json',
'yarn.lock',
'package-lock.json',
'pnpm-lock.yaml'
'.taskmaster', // Task Master directory (highest priority)
TASKMASTER_CONFIG_FILE, // .taskmaster/config.json
TASKMASTER_TASKS_FILE, // .taskmaster/tasks/tasks.json
LEGACY_CONFIG_FILE, // .taskmasterconfig (legacy)
LEGACY_TASKS_FILE, // tasks/tasks.json (legacy)
'tasks.json', // Root tasks.json (legacy)
'.git', // Git repository
'.svn', // SVN repository
'package.json', // Node.js project
'yarn.lock', // Yarn project
'package-lock.json', // npm project
'pnpm-lock.yaml', // pnpm project
'Cargo.toml', // Rust project
'go.mod', // Go project
'pyproject.toml', // Python project
'requirements.txt', // Python project
'Gemfile', // Ruby project
'composer.json' // PHP project
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Marker set looks good; consider adding common monorepo/workspace roots.

Adding these improves detection in large repos without affecting your Task Master–first priority:

  • pnpm-workspace.yaml
  • lerna.json
  • turbo.json
  • rush.json
  • nx.json

Apply diff inside projectMarkers:

   const projectMarkers = [
     '.taskmaster', // Task Master directory (highest priority)
     TASKMASTER_CONFIG_FILE, // .taskmaster/config.json
     TASKMASTER_TASKS_FILE, // .taskmaster/tasks/tasks.json
     LEGACY_CONFIG_FILE, // .taskmasterconfig (legacy)
     LEGACY_TASKS_FILE, // tasks/tasks.json (legacy)
     'tasks.json', // Root tasks.json (legacy)
     '.git', // Git repository
     '.svn', // SVN repository
     'package.json', // Node.js project
     'yarn.lock', // Yarn project
     'package-lock.json', // npm project
     'pnpm-lock.yaml', // pnpm project
+    'pnpm-workspace.yaml', // pnpm workspace root
+    'lerna.json', // Lerna monorepo
+    'turbo.json', // Turborepo
+    'rush.json', // Rush monorepo
+    'nx.json', // Nx workspace
     'Cargo.toml', // Rust project
     'go.mod', // Go project
     'pyproject.toml', // Python project
     'requirements.txt', // Python project
     'Gemfile', // Ruby project
     'composer.json' // PHP project
   ];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Define project markers that indicate a project root
// Prioritize Task Master specific markers first
const projectMarkers = [
'.taskmaster',
TASKMASTER_TASKS_FILE,
'tasks.json',
LEGACY_TASKS_FILE,
'.git',
'.svn',
'package.json',
'yarn.lock',
'package-lock.json',
'pnpm-lock.yaml'
'.taskmaster', // Task Master directory (highest priority)
TASKMASTER_CONFIG_FILE, // .taskmaster/config.json
TASKMASTER_TASKS_FILE, // .taskmaster/tasks/tasks.json
LEGACY_CONFIG_FILE, // .taskmasterconfig (legacy)
LEGACY_TASKS_FILE, // tasks/tasks.json (legacy)
'tasks.json', // Root tasks.json (legacy)
'.git', // Git repository
'.svn', // SVN repository
'package.json', // Node.js project
'yarn.lock', // Yarn project
'package-lock.json', // npm project
'pnpm-lock.yaml', // pnpm project
'Cargo.toml', // Rust project
'go.mod', // Go project
'pyproject.toml', // Python project
'requirements.txt', // Python project
'Gemfile', // Ruby project
'composer.json' // PHP project
];
// Define project markers that indicate a project root
// Prioritize Task Master specific markers first
const projectMarkers = [
'.taskmaster', // Task Master directory (highest priority)
TASKMASTER_CONFIG_FILE, // .taskmaster/config.json
TASKMASTER_TASKS_FILE, // .taskmaster/tasks/tasks.json
LEGACY_CONFIG_FILE, // .taskmasterconfig (legacy)
LEGACY_TASKS_FILE, // tasks/tasks.json (legacy)
'tasks.json', // Root tasks.json (legacy)
'.git', // Git repository
'.svn', // SVN repository
'package.json', // Node.js project
'yarn.lock', // Yarn project
'package-lock.json', // npm project
'pnpm-lock.yaml', // pnpm project
'pnpm-workspace.yaml', // pnpm workspace root
'lerna.json', // Lerna monorepo
'turbo.json', // Turborepo
'rush.json', // Rush monorepo
'nx.json', // Nx workspace
'Cargo.toml', // Rust project
'go.mod', // Go project
'pyproject.toml', // Python project
'requirements.txt', // Python project
'Gemfile', // Ruby project
'composer.json' // PHP project
];
🤖 Prompt for AI Agents
In src/utils/path-utils.js around lines 56 to 77, the projectMarkers array is
missing common monorepo/workspace root files; add the following entries into the
array (keeping Task Master markers first and general project markers after
lockfiles): pnpm-workspace.yaml, lerna.json, turbo.json, rush.json, nx.json —
place them e.g. after 'pnpm-lock.yaml' (or grouped with other workspace/monorepo
entries) so detection in large repos improves without changing Task Master
priority, and update any related tests or comments accordingly.

Comment thread src/utils/path-utils.js
Comment on lines +89 to 97
try {
if (fs.existsSync(markerPath)) {
// Found a project marker - return this directory as project root
return currentDir;
}
} catch (error) {
// Ignore permission errors and continue searching
continue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Don’t swallow all fs errors; limit to permission errors (and optionally log others).

Catching all errors can hide real problems (e.g., bad symlinks). Prefer filtering by code and only ignoring EACCES/EPERM.

-      try {
-        if (fs.existsSync(markerPath)) {
-          // Found a project marker - return this directory as project root
-          return currentDir;
-        }
-      } catch (error) {
-        // Ignore permission errors and continue searching
-        continue;
-      }
+      try {
+        if (fs.existsSync(markerPath)) {
+          // Found a project marker - return this directory as project root
+          return currentDir;
+        }
+      } catch (error) {
+        // Ignore permission errors and continue searching
+        if (error && (error.code === 'EACCES' || error.code === 'EPERM')) {
+          continue;
+        }
+        // Optional: add debug logging here if you have a logger
+        continue;
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
if (fs.existsSync(markerPath)) {
// Found a project marker - return this directory as project root
return currentDir;
}
} catch (error) {
// Ignore permission errors and continue searching
continue;
}
try {
if (fs.existsSync(markerPath)) {
// Found a project marker - return this directory as project root
return currentDir;
}
} catch (error) {
// Ignore permission errors and continue searching
if (error && (error.code === 'EACCES' || error.code === 'EPERM')) {
continue;
}
// Optional: add debug logging here if you have a logger
continue;
}
🤖 Prompt for AI Agents
In src/utils/path-utils.js around lines 89 to 97, the catch block currently
swallows all fs errors; change it to only ignore permission errors by checking
error.code (accept 'EACCES' and 'EPERM') and continue in those cases, otherwise
either log the unexpected error and rethrow it (or just rethrow) so real issues
(e.g., bad symlinks) are not hidden; keep the original intent of continuing on
permission errors only.

Comment on lines +15 to +29
test('should find .taskmaster in parent directory', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');

mockExistsSync.mockImplementation((checkPath) => {
const normalized = path.normalize(checkPath);
// .taskmaster exists only at /project
return normalized === path.normalize('/project/.taskmaster');
});

const result = findProjectRoot('/project/subdir');

expect(result).toBe('/project');

mockExistsSync.mockRestore();
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Make tests OS‑agnostic (avoid hard‑coded POSIX paths).

Use path.resolve for expected paths and in mocks to match platform separators and drive letters.

- return normalized === path.normalize('/project/.taskmaster');
+ return normalized === path.resolve('/project', '.taskmaster');
...
- expect(result).toBe('/project');
+ expect(result).toBe(path.resolve('/project'));

Apply similarly for .git, package.json, and multi‑level traversal assertions (use path.resolve('/project') and path.resolve('/project', marker) in mocks). As per coding guidelines.

Also applies to: 31-44, 46-59, 61-75

🤖 Prompt for AI Agents
In tests/unit/path-utils-find-project-root.test.js around lines 15 to 29 (and
similarly for ranges 31-44, 46-59, 61-75), the tests use hard-coded POSIX paths
which break on non-POSIX platforms; update the test expectations and any path
checks inside mocked fs.existsSync to use path.resolve(...) (and
path.resolve('/project', marker) for marker files) so path separators and drive
letters are normalized, and replace literal '/project' with
path.resolve('/project') in expect assertions and mock comparisons; ensure all
mocked returned checks compare normalized paths via path.resolve or
path.normalize accordingly and restore mocks as before.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

♻️ Duplicate comments (5)
tests/unit/path-utils-find-project-root.test.js (5)

162-165: Prefer exact normalized comparisons for marker checks.

Prevents accidental matches on similarly named paths.

-					return normalized.includes(`/project/${marker}`);
+					return normalized === path.normalize(`/project/${marker}`);

94-100: Use exact path comparison in mocks to avoid false positives.

Replace substring matching with strict normalized equality for determinism.

-				// .git exists at /project/subdir, .taskmaster exists at /project
-				if (normalized.includes('/project/subdir/.git')) return true;
-				if (normalized.includes('/project/.taskmaster')) return true;
+				// .git exists at /project/subdir, .taskmaster exists at /project
+				if (normalized === path.normalize('/project/subdir/.git')) return true;
+				if (normalized === path.normalize('/project/.taskmaster')) return true;
 				return false;

188-197: Make relative path test precise and platform-safe.

Resolve expected path and compare exactly; avoid broad includes().

-			mockExistsSync.mockImplementation((checkPath) => {
-				// Simulate .git existing in the resolved path
-				return checkPath.includes('.git');
-			});
+			mockExistsSync.mockImplementation((checkPath) => {
+				const normalized = path.normalize(checkPath);
+				const expectedPath = path.normalize(path.join(process.cwd(), '.git'));
+				return normalized === expectedPath;
+			});
 
 			const result = findProjectRoot('./subdir');
 
-			expect(typeof result).toBe('string');
+			expect(result).toBe(process.cwd());

177-183: Strengthen empty input test to assert fallback behavior.

Mock no markers and assert exact fallback to process.cwd().

-		test('should handle empty string as startDir', () => {
-			const result = findProjectRoot('');
-
-			// Should use process.cwd() or fall back appropriately
-			expect(typeof result).toBe('string');
-			expect(result.length).toBeGreaterThan(0);
-		});
+		test('should handle empty string as startDir', () => {
+			const mockExistsSync = jest.spyOn(fs, 'existsSync').mockReturnValue(false);
+			const result = findProjectRoot('');
+			expect(result).toBe(process.cwd());
+			mockExistsSync.mockRestore();
+		});

114-122: Tighten permission-error mock and parent marker match.

Avoid startsWith/includes; compare normalized paths precisely.

-				// Throw permission error for checks in /project/subdir
-				if (normalized.startsWith('/project/subdir/')) {
+				// Throw permission error only for marker checks within the starting subdirectory
+				if (path.dirname(normalized) === path.normalize('/project/subdir')) {
 					throw new Error('EACCES: permission denied');
 				}
-				// Return true only for .taskmaster at /project
-				return normalized.includes('/project/.taskmaster');
+				// Return true only for .taskmaster at /project
+				return normalized === path.normalize('/project/.taskmaster');
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e8579e and af3004c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • .changeset/fix-parent-directory-traversal.md (1 hunks)
  • CHANGELOG.md (1 hunks)
  • apps/cli/CHANGELOG.md (1 hunks)
  • apps/docs/CHANGELOG.md (1 hunks)
  • apps/docs/package.json (1 hunks)
  • apps/extension/CHANGELOG.md (1 hunks)
  • apps/extension/package.json (1 hunks)
  • package.json (1 hunks)
  • packages/ai-sdk-provider-grok-cli/CHANGELOG.md (1 hunks)
  • packages/build-config/CHANGELOG.md (1 hunks)
  • packages/claude-code-plugin/CHANGELOG.md (1 hunks)
  • packages/claude-code-plugin/package.json (1 hunks)
  • packages/tm-core/CHANGELOG.md (1 hunks)
  • src/utils/path-utils.js (1 hunks)
  • tests/unit/path-utils-find-project-root.test.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (15)
tests/{unit,integration,e2e,fixtures}/**/*.js

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Test files must be organized as follows: unit tests in tests/unit/, integration tests in tests/integration/, end-to-end tests in tests/e2e/, and test fixtures in tests/fixtures/.

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/unit/*.js

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Each module should have a corresponding unit test file in tests/unit/ that reflects the module structure (one test file per module).

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.{test,spec}.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/git_workflow.mdc)

**/*.{test,spec}.{js,ts,jsx,tsx}: Create a test file and ensure all tests pass when all subtasks are complete; commit tests if added or modified
When all subtasks are complete, run final testing using the appropriate test runner (e.g., npm test, jest, or manual testing)

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.test.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

**/*.test.js: Never use asynchronous operations in tests. Make all mocks return synchronous values when possible.
Always mock tests properly based on the way the tested functions are defined and used.
Follow the test file organization: mocks must be set up before importing modules under test, and spies on mocked modules should be set up after imports.
Use fixtures from tests/fixtures/ for consistent sample data across tests.
Always declare mocks before importing the modules being tested in Jest test files.
Use jest.spyOn() after imports to create spies on mock functions and reference these spies in test assertions.
When testing functions with callbacks, get the callback from your mock's call arguments, execute it directly with test inputs, and verify the results.
For ES modules, use jest.mock() before static imports and jest.unstable_mockModule() before dynamic imports to mock dependencies.
Reset mock functions (mockFn.mockReset()) before dynamic imports if they might have been called previously.
When verifying console assertions, assert against the actual arguments passed (single formatted string), not multiple arguments.
Use mock-fs to mock file system operations in tests, and restore the file system after each test.
Mock API calls (e.g., Anthropic/Claude) by mocking the entire module and providing predictable responses.
Set mock environment variables in test setup and restore them after each test.
Maintain test fixtures separate from test logic.
Follow the mock-first-then-import pattern for all Jest mocks.
Do not define mock variables before jest.mock() calls (they won't be accessible due to hoisting).
Use test-specific file paths (e.g., 'test-tasks.json') for all file operations in tests.
Mock readJSON and writeJSON to avoid real file system interactions in tests.
Verify file operations use the correct paths in expect statements.
Use different file paths for each test to avoid test interdependence.
Verify modifications on the in-memory task objects passed to w...

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/unit/**/*.test.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

tests/unit/**/*.test.js: Unit tests must be located in tests/unit/, test individual functions and utilities in isolation, mock all external dependencies, and keep tests small, focused, and fast.
Do not include actual command execution in unit tests.

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/{unit,integration,e2e}/**/*.test.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

tests/{unit,integration,e2e}/**/*.test.js: When testing CLI commands built with Commander.js, test the command action handlers directly rather than trying to mock the entire Commander.js chain.
When mocking the Commander.js chain, mock ALL chainable methods (option, argument, action, on, etc.) and return this (or the mock object) from all chainable method mocks.
Explicitly handle all options, including defaults and shorthand flags (e.g., -p for --prompt), and include null/undefined checks in test implementations for parameters that might be optional.
Do not try to use the real action implementation without proper mocking, and do not mock Commander partially—either mock it completely or test the action directly.
Mock the action handlers for CLI commands and verify they're called with correct arguments.
Use sample task fixtures for consistent test data, mock file system operations, and test both success and error paths for task operations.
Mock console output and verify correct formatting in UI function tests. Use flexible assertions like toContain() or toMatch() for formatted output.
Mock chalk functions to return the input text to make testing easier while still verifying correct function calls.

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.js

📄 CodeRabbit inference engine (.cursor/rules/tests.mdc)

**/*.js: Declare and initialize global variables at the top of modules to avoid hoisting issues.
Use proper function declarations to avoid hoisting issues and initialize variables before they are referenced.
Do not reference variables before their declaration in module scope.
Use dynamic imports (import()) to avoid initialization order issues in modules.

Files:

  • tests/unit/path-utils-find-project-root.test.js
  • src/utils/path-utils.js
**/*.{test,spec}.*

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Test files should follow naming conventions: .test., .spec., or _test. depending on the language

Files:

  • tests/unit/path-utils-find-project-root.test.js
tests/{unit,integration,e2e}/**

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Organize test directories by test type (unit, integration, e2e) and mirror source structure where possible

Files:

  • tests/unit/path-utils-find-project-root.test.js
**/*.{test,spec}.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{test,spec}.{js,jsx,ts,tsx}: NEVER use async/await in test functions unless testing actual asynchronous operations
Use synchronous top-level imports in tests; avoid dynamic await import()
Keep test bodies synchronous whenever possible

Files:

  • tests/unit/path-utils-find-project-root.test.js
{src/utils/**,src/middleware/**}

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Test coverage for all code should meet or exceed 80% lines/functions and 70% branches globally; critical code (utils, middleware) should meet higher thresholds (90% utils, 85% middleware)

Files:

  • src/utils/path-utils.js
package.json

📄 CodeRabbit inference engine (.cursor/rules/test_workflow.mdc)

Add and update test scripts in package.json to include test, test:watch, test:coverage, test:unit, test:integration, test:e2e, and test:ci

Files:

  • package.json
.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/changeset.mdc)

.changeset/*.md: When running npm run changeset or npx changeset add, provide a concise summary of the changes for the CHANGELOG.md in imperative mood, typically a single line, and not a detailed Git commit message.
The changeset summary should be user-facing, describing what changed in the released version that is relevant to users or consumers of the package.
Do not use your detailed Git commit message body as the changeset summary.

Files:

  • .changeset/fix-parent-directory-traversal.md
.changeset/*

📄 CodeRabbit inference engine (.cursor/rules/new_features.mdc)

Create appropriate changesets for new features, use semantic versioning, include tagged system information in release notes, and document breaking changes if any.

Files:

  • .changeset/fix-parent-directory-traversal.md
.changeset/**/*.md

📄 CodeRabbit inference engine (CLAUDE.md)

Changeset entries should be user-facing, describing the end-user impact rather than code specifics

Files:

  • .changeset/fix-parent-directory-traversal.md
🧠 Learnings (11)
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to **/*.test.js : Verify file operations use the correct paths in expect statements.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to tests/{unit,integration,e2e}/**/*.test.js : Use sample task fixtures for consistent test data, mock file system operations, and test both success and error paths for task operations.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to **/*.test.js : Use mock-fs to mock file system operations in tests, and restore the file system after each test.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-07-18T17:16:13.793Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/tests.mdc:0-0
Timestamp: 2025-07-18T17:16:13.793Z
Learning: Applies to **/*.test.js : Use test-specific file paths (e.g., 'test-tasks.json') for all file operations in tests.

Applied to files:

  • tests/unit/path-utils-find-project-root.test.js
📚 Learning: 2025-09-26T19:03:33.225Z
Learnt from: Crunchyman-ralph
PR: eyaltoledano/claude-task-master#1252
File: package.json:130-132
Timestamp: 2025-09-26T19:03:33.225Z
Learning: In the eyaltoledano/claude-task-master repository, all packages with tm prefix are internal packages that are part of the monorepo structure and are not published externally.

Applied to files:

  • packages/claude-code-plugin/package.json
📚 Learning: 2025-09-26T19:05:47.555Z
Learnt from: Crunchyman-ralph
PR: eyaltoledano/claude-task-master#1252
File: packages/ai-sdk-provider-grok-cli/package.json:11-13
Timestamp: 2025-09-26T19:05:47.555Z
Learning: In the eyaltoledano/claude-task-master repository, internal tm/ packages use a specific export pattern where the "exports" field points to TypeScript source files (./src/index.ts) while "main" points to compiled output (./dist/index.js) and "types" points to source files (./src/index.ts). This pattern is used consistently across internal packages like tm/core and tm/ai-sdk-provider-grok-cli because they are consumed directly during build-time bundling with tsdown rather than being published as separate packages.

Applied to files:

  • packages/claude-code-plugin/package.json
📚 Learning: 2025-09-22T19:45:13.323Z
Learnt from: Crunchyman-ralph
PR: eyaltoledano/claude-task-master#1232
File: packages/build-config/package.json:14-15
Timestamp: 2025-09-22T19:45:13.323Z
Learning: In the eyaltoledano/claude-task-master repository, Crunchyman-ralph intentionally omits version fields from internal packages (like tm/build-config) to prevent changesets from releasing new versions for these packages. This is the desired behavior for internal tooling packages that should not be published or versioned independently.

Applied to files:

  • packages/claude-code-plugin/package.json
📚 Learning: 2025-07-18T17:18:17.759Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/utilities.mdc:0-0
Timestamp: 2025-07-18T17:18:17.759Z
Learning: Applies to mcp-server/src/tools/utils.js : Use `normalizeProjectRoot(rawPath, log)`, `getRawProjectRootFromSession(session, log)`, and `withNormalizedProjectRoot(executeFn)` in `mcp-server/src/tools/utils.js` to ensure project root paths are normalized for MCP tools.

Applied to files:

  • src/utils/path-utils.js
📚 Learning: 2025-07-18T17:11:36.732Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/mcp.mdc:0-0
Timestamp: 2025-07-18T17:11:36.732Z
Learning: Applies to mcp-server/src/core/direct-functions/*.js : The *Direct function is responsible for finding the exact tasks.json path using findTasksJsonPath, relying on the projectRoot passed in args.

Applied to files:

  • src/utils/path-utils.js
📚 Learning: 2025-07-18T17:07:53.100Z
Learnt from: CR
PR: eyaltoledano/claude-task-master#0
File: .cursor/rules/changeset.mdc:0-0
Timestamp: 2025-07-18T17:07:53.100Z
Learning: Applies to .changeset/*.md : When running `npm run changeset` or `npx changeset add`, provide a concise summary of the changes for the `CHANGELOG.md` in imperative mood, typically a single line, and not a detailed Git commit message.

Applied to files:

  • .changeset/fix-parent-directory-traversal.md
📚 Learning: 2025-10-08T12:21:14.455Z
Learnt from: Crunchyman-ralph
PR: eyaltoledano/claude-task-master#1285
File: .changeset/nice-ways-hope.md:5-17
Timestamp: 2025-10-08T12:21:14.455Z
Learning: For changeset files (.changeset/*.md): The first line should be concise and in imperative mood, but the body after the first line can include as many user-facing details as desired (bullets, explanations, links, etc.) to provide context for the CHANGELOG.md.

Applied to files:

  • .changeset/fix-parent-directory-traversal.md
🧬 Code graph analysis (2)
tests/unit/path-utils-find-project-root.test.js (1)
src/utils/path-utils.js (2)
  • findProjectRoot (55-115)
  • projectMarkers (58-77)
src/utils/path-utils.js (2)
mcp-server/src/core/utils/path-utils.js (1)
  • findProjectRoot (202-204)
scripts/modules/config-manager.js (1)
  • rootDir (858-858)
🪛 LanguageTool
packages/tm-core/CHANGELOG.md

[grammar] ~7-~7: Use correct spacing
Context: # Changelog ## null ## null ## null ## 0.26.1 All notable changes to the @task...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

apps/docs/CHANGELOG.md

[grammar] ~3-~3: Hier könnte ein Fehler sein.
Context: # docs ## 0.0.6 ## 0.0.5 ## 0.0.4 ## 0.0.3 ## 0.0.2 ## ...

(QB_NEW_DE)

packages/claude-code-plugin/CHANGELOG.md

[grammar] ~1-~1: Hier könnte ein Fehler sein.
Context: # @tm/claude-code-plugin ## 0.0.2

(QB_NEW_DE)


[grammar] ~3-~3: Hier könnte ein Fehler sein.
Context: # @tm/claude-code-plugin ## 0.0.2

(QB_NEW_DE)

packages/build-config/CHANGELOG.md

[grammar] ~7-~7: Hier könnte ein Fehler sein.
Context: .../build-config ## null ## null ## null ## 1.0.1

(QB_NEW_DE)

apps/extension/CHANGELOG.md

[grammar] ~3-~3: Use correct spacing
Context: # Change Log ## 0.25.6 ## 0.25.6-rc.0 ### Patch Changes - Update...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

.changeset/fix-parent-directory-traversal.md

[grammar] ~5-~5: There might be a mistake here.
Context: ...s to find project root from nested paths Fixes #1301

(QB_NEW_EN_OTHER)

apps/cli/CHANGELOG.md

[grammar] ~14-~14: There might be a mistake here.
Context: ... ## null ### Patch Changes - Updated dependencies []: - @tm/core@null ## null ### Patch C...

(QB_NEW_EN_OTHER)


[grammar] ~15-~15: Use correct spacing
Context: ...dated dependencies []: - @tm/core@null ## null ### Patch Changes - Updated depen...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~17-~17: Use correct spacing
Context: ...endencies []: - @tm/core@null ## null ### Patch Changes - Updated dependencies []...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~19-~19: Use correct spacing
Context: ...tm/core@null ## null ### Patch Changes - Updated dependencies []: - @tm/core@nu...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

🪛 markdownlint-cli2 (0.18.1)
packages/tm-core/CHANGELOG.md

7-7: Multiple headings with the same content

(MD024, no-duplicate-heading)

packages/ai-sdk-provider-grok-cli/CHANGELOG.md

5-5: Multiple headings with the same content

(MD024, no-duplicate-heading)

.changeset/fix-parent-directory-traversal.md

5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

apps/cli/CHANGELOG.md

17-17: Multiple headings with the same content

(MD024, no-duplicate-heading)

🔇 Additional comments (5)
packages/claude-code-plugin/CHANGELOG.md (1)

1-3: Changelog entry looks good.

File structure and version header are fine.

.changeset/fix-parent-directory-traversal.md (1)

1-7: Good changeset formatting and summary.

Concise, imperative first line with correct bump and context.

src/utils/path-utils.js (3)

48-54: LGTM! Documentation addresses previous review.

The JSDoc now clearly documents the traversal behavior, depth limit (50 levels), and fallback mechanism. This resolves the concern raised in the previous review.


56-77: LGTM! Excellent expansion and prioritization.

The expanded project marker set significantly improves cross-language and cross-platform compatibility. Prioritizing Task Master-specific markers (.taskmaster, config files) before generic markers (.git, package.json) is the correct approach to avoid false positives in nested projects.


100-109: LGTM! Robust root detection safety.

The safety check on lines 103-106 provides valuable defense against infinite loops if path.dirname() returns the same path at the filesystem root. This complements the root check in the while condition (line 85) and handles edge cases across different platforms.

Comment thread apps/cli/CHANGELOG.md
Comment thread packages/ai-sdk-provider-grok-cli/CHANGELOG.md
Comment thread packages/build-config/CHANGELOG.md
Comment thread packages/tm-core/CHANGELOG.md
Comment thread src/utils/path-utils.js
Comment on lines +89 to 97
try {
if (fs.existsSync(markerPath)) {
// Found a project marker - return this directory as project root
return currentDir;
}
} catch (error) {
// Ignore permission errors and continue searching
continue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Clarify the error handling comment.

The try-catch correctly handles errors during marker detection, but the comment on line 95 specifically mentions "permission errors" when it actually catches all errors. This is appropriate behavior (any error should allow traversal to continue), but the comment could be more accurate.

Consider updating the comment:

-				// Ignore permission errors and continue searching
+				// Ignore any errors (permission, I/O, etc.) and continue searching
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
if (fs.existsSync(markerPath)) {
// Found a project marker - return this directory as project root
return currentDir;
}
} catch (error) {
// Ignore permission errors and continue searching
continue;
}
try {
if (fs.existsSync(markerPath)) {
// Found a project marker - return this directory as project root
return currentDir;
}
} catch (error) {
// Ignore any errors (permission, I/O, etc.) and continue searching
continue;
}
🤖 Prompt for AI Agents
In src/utils/path-utils.js around lines 89 to 97, the catch block currently
comments that it ignores "permission errors" but it actually catches and ignores
all errors during marker detection; update the comment to accurately reflect
that any error encountered while checking for the marker is intentionally
ignored and traversal continues (e.g., "Ignore any error encountered while
checking marker and continue searching"), keeping behavior unchanged.

Comment thread src/utils/path-utils.js
Comment on lines 112 to 114
// Fallback to current working directory if no project root found
// This ensures the function always returns a valid path
return process.cwd();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

LGTM! Consistent fallback behavior.

The function now consistently returns a valid path, falling back to process.cwd() when no project markers are found. This is a sensible default and makes the function more reliable.

Verify that all existing callers handle the new behavior where findProjectRoot() always returns a string (never null):


🏁 Script executed:

#!/bin/bash
# Description: Search for null checks on findProjectRoot() results that might now be redundant
# Expected: Identify any code that checks for null after calling findProjectRoot()

rg -nP --type=js -A3 'findProjectRoot\(\)' | rg -A2 'if.*\!\w+|if.*===.*null|if.*==.*null'

Length of output: 106


🏁 Script executed:

#!/bin/bash
# 1. Show all usages of findProjectRoot()
rg -n -C3 "findProjectRoot\\(\\)" --type=js

# 2. Find direct comparisons of findProjectRoot() to null
rg -nP --type=js "findProjectRoot\\(\\)\\s*(?:===|==)\\s*null" -C3

# 3. Find negation checks on findProjectRoot()
rg -nP --type=js "if\\s*\\(\\s*!\\s*findProjectRoot\\(\\)" -C3

Length of output: 9758


Remove unreachable null‐checks on projectRoot

  • In expand-all-tasks.js, migrate.js, update-tasks.js, update-task-by-id.js, research.js, update-subtask-by-id.js and config-manager.js the if (!projectRoot) or if (!rootDir) branches will never fire (findProjectRoot() now always returns a non-null string). Remove or simplify these redundant guards.

Comment on lines +1 to +223
/**
* Unit tests for findProjectRoot() function
* Tests the parent directory traversal functionality
*/

import { jest } from '@jest/globals';
import path from 'path';
import fs from 'fs';

// Import the function to test
import { findProjectRoot } from '../../src/utils/path-utils.js';

describe('findProjectRoot', () => {
describe('Parent Directory Traversal', () => {
test('should find .taskmaster in parent directory', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');

mockExistsSync.mockImplementation((checkPath) => {
const normalized = path.normalize(checkPath);
// .taskmaster exists only at /project
return normalized === path.normalize('/project/.taskmaster');
});

const result = findProjectRoot('/project/subdir');

expect(result).toBe('/project');

mockExistsSync.mockRestore();
});

test('should find .git in parent directory', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');

mockExistsSync.mockImplementation((checkPath) => {
const normalized = path.normalize(checkPath);
return normalized === path.normalize('/project/.git');
});

const result = findProjectRoot('/project/subdir');

expect(result).toBe('/project');

mockExistsSync.mockRestore();
});

test('should find package.json in parent directory', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');

mockExistsSync.mockImplementation((checkPath) => {
const normalized = path.normalize(checkPath);
return normalized === path.normalize('/project/package.json');
});

const result = findProjectRoot('/project/subdir');

expect(result).toBe('/project');

mockExistsSync.mockRestore();
});

test('should traverse multiple levels to find project root', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');

mockExistsSync.mockImplementation((checkPath) => {
const normalized = path.normalize(checkPath);
// Only exists at /project, not in any subdirectories
return normalized === path.normalize('/project/.taskmaster');
});

const result = findProjectRoot('/project/subdir/deep/nested');

expect(result).toBe('/project');

mockExistsSync.mockRestore();
});

test('should return current directory as fallback when no markers found', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');

// No project markers exist anywhere
mockExistsSync.mockReturnValue(false);

const result = findProjectRoot('/some/random/path');

// Should fall back to process.cwd()
expect(result).toBe(process.cwd());

mockExistsSync.mockRestore();
});

test('should find markers at current directory before checking parent', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');

mockExistsSync.mockImplementation((checkPath) => {
const normalized = path.normalize(checkPath);
// .git exists at /project/subdir, .taskmaster exists at /project
if (normalized.includes('/project/subdir/.git')) return true;
if (normalized.includes('/project/.taskmaster')) return true;
return false;
});

const result = findProjectRoot('/project/subdir');

// Should find /project/subdir first because .git exists there,
// even though .taskmaster is earlier in the marker array
expect(result).toBe('/project/subdir');

mockExistsSync.mockRestore();
});

test('should handle permission errors gracefully', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');

mockExistsSync.mockImplementation((checkPath) => {
const normalized = path.normalize(checkPath);
// Throw permission error for checks in /project/subdir
if (normalized.startsWith('/project/subdir/')) {
throw new Error('EACCES: permission denied');
}
// Return true only for .taskmaster at /project
return normalized.includes('/project/.taskmaster');
});

const result = findProjectRoot('/project/subdir');

// Should handle permission errors in subdirectory and traverse to parent
expect(result).toBe('/project');

mockExistsSync.mockRestore();
});

test('should detect filesystem root correctly', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');

// No markers exist
mockExistsSync.mockReturnValue(false);

const result = findProjectRoot('/');

// Should stop at root and fall back to process.cwd()
expect(result).toBe(process.cwd());

mockExistsSync.mockRestore();
});

test('should recognize various project markers', () => {
const projectMarkers = [
'.taskmaster',
'.git',
'package.json',
'Cargo.toml',
'go.mod',
'pyproject.toml',
'requirements.txt',
'Gemfile',
'composer.json'
];

projectMarkers.forEach((marker) => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');

mockExistsSync.mockImplementation((checkPath) => {
const normalized = path.normalize(checkPath);
return normalized.includes(`/project/${marker}`);
});

const result = findProjectRoot('/project/subdir');

expect(result).toBe('/project');

mockExistsSync.mockRestore();
});
});
});

describe('Edge Cases', () => {
test('should handle empty string as startDir', () => {
const result = findProjectRoot('');

// Should use process.cwd() or fall back appropriately
expect(typeof result).toBe('string');
expect(result.length).toBeGreaterThan(0);
});

test('should handle relative paths', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');

mockExistsSync.mockImplementation((checkPath) => {
// Simulate .git existing in the resolved path
return checkPath.includes('.git');
});

const result = findProjectRoot('./subdir');

expect(typeof result).toBe('string');

mockExistsSync.mockRestore();
});

test('should not exceed max depth limit', () => {
const mockExistsSync = jest.spyOn(fs, 'existsSync');

// Track how many times existsSync is called
let callCount = 0;
mockExistsSync.mockImplementation(() => {
callCount++;
return false; // Never find a marker
});

// Create a very deep path
const deepPath = '/a/'.repeat(100) + 'deep';
const result = findProjectRoot(deepPath);

// Should stop after max depth (50) and not check 100 levels
// Each level checks multiple markers, so callCount will be high but bounded
expect(callCount).toBeLessThan(1000); // Reasonable upper bound
// With 18 markers and max depth of 50, expect around 900 calls maximum
expect(callCount).toBeLessThanOrEqual(50 * 18);

mockExistsSync.mockRestore();
});
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Optional: add a test to verify marker priority within the same directory.

Ensures Task Master markers win when both exist at the same level.

test('should prioritize Task Master markers over generic markers in same directory', () => {
  const mockExistsSync = jest.spyOn(fs, 'existsSync');
  mockExistsSync.mockImplementation((checkPath) => {
    const normalized = path.normalize(checkPath);
    return normalized === path.normalize('/project/.taskmaster') ||
           normalized === path.normalize('/project/.git');
  });

  const result = findProjectRoot('/project'); // both markers in /project
  expect(result).toBe('/project'); // current dir wins; order asserts priority
  mockExistsSync.mockRestore();
});
🤖 Prompt for AI Agents
In tests/unit/path-utils-find-project-root.test.js lines 1-223, add a unit test
that verifies marker priority when multiple markers exist in the same directory:
create a test that spies on fs.existsSync, mocks it to return true for both
path.normalize('/project/.taskmaster') and path.normalize('/project/.git'),
calls findProjectRoot('/project'), asserts the result is '/project', and
restores the mock; ensure the mock compares normalized paths (using
path.normalize) and clean up with mockRestore().

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.

Bug: Task Master doesn't traverse parent directories from subdirectories

2 participants