fix: enhance findProjectRoot to traverse parent directories#1302
Conversation
🦋 Changeset detectedLatest 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 |
WalkthroughUpdates 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 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.jssrc/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.existsSynceffectively 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 toprocess.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.
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 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 runningnpm run changesetornpx changeset add, provide a concise summary of the changes for theCHANGELOG.mdin 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)
There was a problem hiding this comment.
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/subdiras the result (line 113), and the comment accurately describes the behavior: markers are checked in order at the current directory, so.gitat the current directory is found before.taskmasterat 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
maxDepthlimit (50 levels) by checking that the total number ofexistsSynccalls doesn't exceed50 * 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
📒 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 runningnpm run changesetornpx changeset add, provide a concise summary of the changes for theCHANGELOG.mdin 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.jssrc/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:
- Permission error handling (lines 89-97): try-catch allows traversal to continue when directories are inaccessible
- Filesystem root detection (lines 104-106): safety check prevents infinite loops when
path.dirname()returns the same path- 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 returnsstringunconditionally.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/.taskmasteronly, 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
findProjectRootfalls back toprocess.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()afterpath.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 ofsrc/utils/path-utils.js), allowing the function to work correctly regardless of whether absolute or relative paths are provided.
Crunchyman-ralph
left a comment
There was a problem hiding this comment.
lgtm, just address coderabbit comments, run npm run format, make sure CI is passing and lets merge this!
There was a problem hiding this comment.
Actionable comments posted: 4
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 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.jssrc/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
findProjectRootfunctionality:✅ 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 toprocess.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
findProjectRootfunction 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
findProjectRootfunction 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-catcharoundfs.existsSynchandles permission errors gracefully without aborting traversal
✅ Safe termination: Detects filesystem root viaparentDir === currentDircheck and enforcesmaxDepthlimit (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: Returnsprocess.cwd()when no markers found, ensuring function always returns a valid path
✅ Clear documentation: JSDoc updated to document traversal behavior, depth limit, and fallback semanticsThe 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
maxDepthis 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.
| 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(); | ||
| }); |
There was a problem hiding this comment.
🧹 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.
| 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(); | |
| }); |
| 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(); | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🧹 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.
| 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.
| 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(); | ||
| }); |
There was a problem hiding this comment.
🧹 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.
| 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
left a comment
There was a problem hiding this comment.
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
6840387 to
54e3161
Compare
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>
54e3161 to
af3004c
Compare
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
package-lock.jsonis 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 runningnpm run changesetornpx changeset add, provide a concise summary of the changes for theCHANGELOG.mdin 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.jssrc/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 OKHeader for 0.0.2 added; no issues.
apps/docs/CHANGELOG.md (1)
3-4: Changelog bump looks good0.0.6 section added appropriately.
packages/claude-code-plugin/package.json (1)
3-3: Version bump consistent0.0.2 matches the changelog; no issues.
package.json (1)
13-39: Test scripts complete and alignedAll 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.
| - Updated dependencies []: | ||
| - @tm/core@null | ||
|
|
||
| ## null | ||
|
|
||
| ### Patch Changes | ||
|
|
There was a problem hiding this comment.
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.
| - 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.
| { | ||
| "name": "docs", | ||
| "version": "0.0.5", | ||
| "version": "0.0.6", |
There was a problem hiding this comment.
🧹 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.
|
|
||
| ## null |
There was a problem hiding this comment.
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.
| ## 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.
| ## null | ||
|
|
There was a problem hiding this comment.
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.
| ## 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).
| ## null | ||
|
|
There was a problem hiding this comment.
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.
| ## 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).
| // 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 | ||
| ]; |
There was a problem hiding this comment.
🧹 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.
| // 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.
| 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; | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| 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.
| 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(); | ||
| }); |
There was a problem hiding this comment.
🛠️ 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.
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
package-lock.jsonis 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.jssrc/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 runningnpm run changesetornpx changeset add, provide a concise summary of the changes for theCHANGELOG.mdin 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.
| 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; | ||
| } |
There was a problem hiding this comment.
🧹 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.
| 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.
| // Fallback to current working directory if no project root found | ||
| // This ensures the function always returns a valid path | ||
| return process.cwd(); |
There was a problem hiding this comment.
🧹 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\\(\\)" -C3Length 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)orif (!rootDir)branches will never fire (findProjectRoot() now always returns a non-null string). Remove or simplify these redundant guards.
| /** | ||
| * 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(); | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🧹 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().
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
Enhanced Error Handling
fs.existsSync()to gracefully handle permission errorsImproved Root Detection
path.dirname()returns the same pathBetter Project Marker Ordering
.taskmaster, config files)Expanded Project Support
Cargo.toml), Go (go.mod), Python (pyproject.toml,requirements.txt), Ruby (Gemfile), PHP (composer.json)Enhanced Documentation
Testing
tests/unit/path-utils-find-project-root.test.jsBefore This Fix
After This Fix
Use Cases Enabled
Testing Instructions
Initialize Task Master in a project root:
Navigate to a subdirectory:
cd some/deep/subdirectoryRun any task-master command:
Verify it finds the parent
.taskmasterdirectoryChecklist
Related Issues
Closes #1301
Summary by CodeRabbit
Bug Fixes
Documentation
Chores
Tests