feat(paths): Implement robust project root detection and path utilities

Overhauls the project root detection system with a hierarchical precedence mechanism that intelligently locates tasks.json and identifies project roots. This improves user experience by reducing the need for explicit path parameters and enhances cross-platform compatibility.

Key Improvements:
- Implement hierarchical precedence for project root detection:
  * Environment variable override (TASK_MASTER_PROJECT_ROOT)
  * Explicitly provided --project-root parameter
  * Cached project root from previous successful operations
  * Current directory with project markers
  * Parent directory traversal to find tasks.json
  * Package directory as fallback

- Create comprehensive PROJECT_MARKERS detection system with 20+ common indicators:
  * Task Master specific files (tasks.json, tasks/tasks.json)
  * Version control directories (.git, .svn)
  * Package manifests (package.json, pyproject.toml, Gemfile, go.mod, Cargo.toml)
  * IDE/editor configurations (.cursor, .vscode, .idea)
  * Dependency directories (node_modules, venv, .venv)
  * Configuration files (.env, tsconfig.json, webpack.config.js)
  * CI/CD files (.github/workflows, .gitlab-ci.yml, .circleci/config.yml)

- DRY refactoring of path utilities:
  * Centralize path-related functions in core/utils/path-utils.js
  * Export PROJECT_MARKERS as a single source of truth
  * Add caching via lastFoundProjectRoot for performance optimization

- Enhanced user experience:
  * Improve error messages with specific troubleshooting guidance
  * Add detailed logging to indicate project root detection source
  * Update tool parameter descriptions for better clarity
  * Add recursive parent directory searching for tasks.json

Testing:
- Verified in local dev environment
- Added unit tests for the progress bar visualization
- Updated "automatically detected" description in MCP tools

This commit addresses Task #38: Implement robust project root handling for file paths.
This commit is contained in:
Eyal Toledano
2025-04-01 01:35:10 -04:00
parent b23d5afd66
commit e607c5481c
10 changed files with 597 additions and 210 deletions

View File

@@ -2,6 +2,28 @@
"task-master-ai": patch
---
- Implement robust project root detection with a hierarchical precedence system:
- Environment variable override (TASK_MASTER_PROJECT_ROOT)
- Explicitly provided project root (--project-root parameter)
- Cached project root from previous successful operations
- Current directory with project markers
- Parent directory traversal to find tasks.json
- Package directory as fallback
- Add comprehensive PROJECT_MARKERS array for detecting common project files:
- Task Master specific files (tasks.json, tasks/tasks.json)
- Version control markers (.git, .svn)
- Package files (package.json, pyproject.toml, etc.)
- IDE/editor folders (.cursor, .vscode, .idea)
- Dependency directories (node_modules, venv)
- Configuration files (.env, tsconfig.json, etc.)
- CI/CD files (.github/workflows, etc.)
- Improved error messages with specific troubleshooting guidance
- Enhanced logging to indicate the source of project root selection
- DRY refactoring by centralizing path utilities in core/utils/path-utils.js
- Add caching of lastFoundProjectRoot for improved performance
- Split monolithic task-master-core.js into separate function files within direct-functions directory
- Implement update-task MCP command for updating a single task by ID
- Implement update-subtask MCP command for appending information to specific subtasks

View File

@@ -41,12 +41,17 @@ export async function setTaskStatusDirect(args, log) {
// Get tasks file path
let tasksPath;
try {
// The enhanced findTasksJsonPath will now search in parent directories if needed
tasksPath = findTasksJsonPath(args, log);
log.info(`Found tasks file at: ${tasksPath}`);
} catch (error) {
log.error(`Error finding tasks file: ${error.message}`);
return {
success: false,
error: { code: 'TASKS_FILE_ERROR', message: error.message },
error: {
code: 'TASKS_FILE_ERROR',
message: `${error.message}\n\nPlease ensure you are in a Task Master project directory or use the --project-root parameter to specify the path to your project.`
},
fromCache: false
};
}

View File

@@ -1,10 +1,77 @@
/**
* path-utils.js
* Utility functions for file path operations in Task Master
*
* This module provides robust path resolution for both:
* 1. PACKAGE PATH: Where task-master code is installed
* (global node_modules OR local ./node_modules/task-master OR direct from repo)
* 2. PROJECT PATH: Where user's tasks.json resides (typically user's project root)
*/
import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';
import os from 'os';
// Store last found project root to improve performance on subsequent calls
export let lastFoundProjectRoot = null;
// Project marker files that indicate a potential project root
export const PROJECT_MARKERS = [
// Task Master specific
'tasks.json',
'tasks/tasks.json',
// Common version control
'.git',
'.svn',
// Common package files
'package.json',
'pyproject.toml',
'Gemfile',
'go.mod',
'Cargo.toml',
// Common IDE/editor folders
'.cursor',
'.vscode',
'.idea',
// Common dependency directories (check if directory)
'node_modules',
'venv',
'.venv',
// Common config files
'.env',
'.eslintrc',
'tsconfig.json',
'babel.config.js',
'jest.config.js',
'webpack.config.js',
// Common CI/CD files
'.github/workflows',
'.gitlab-ci.yml',
'.circleci/config.yml'
];
/**
* Gets the path to the task-master package installation directory
* @returns {string} - Absolute path to the package installation directory
*/
export function getPackagePath() {
// When running from source, __dirname is the directory containing this file
// When running from npm, we need to find the package root
const thisFilePath = fileURLToPath(import.meta.url);
const thisFileDir = path.dirname(thisFilePath);
// Navigate from core/utils up to the package root
// In dev: /path/to/task-master/mcp-server/src/core/utils -> /path/to/task-master
// In npm: /path/to/node_modules/task-master/mcp-server/src/core/utils -> /path/to/node_modules/task-master
return path.resolve(thisFileDir, '../../../../');
}
/**
* Finds the absolute path to the tasks.json file based on project root and arguments.
@@ -14,22 +81,104 @@ import fs from 'fs';
* @throws {Error} - If tasks.json cannot be found.
*/
export function findTasksJsonPath(args, log) {
// Assume projectRoot is already normalized absolute path if passed in args
// Or use getProjectRoot if we decide to centralize that logic
const projectRoot = args.projectRoot || process.cwd();
log.info(`Searching for tasks.json within project root: ${projectRoot}`);
// PRECEDENCE ORDER:
// 1. Environment variable override
// 2. Explicitly provided projectRoot in args
// 3. Previously found/cached project root
// 4. Current directory and parent traversal
// 5. Package directory (for development scenarios)
const possiblePaths = [];
// 1. If a file is explicitly provided relative to projectRoot
if (args.file) {
possiblePaths.push(path.resolve(projectRoot, args.file));
// 1. Check for environment variable override
if (process.env.TASK_MASTER_PROJECT_ROOT) {
const envProjectRoot = process.env.TASK_MASTER_PROJECT_ROOT;
log.info(`Using project root from TASK_MASTER_PROJECT_ROOT environment variable: ${envProjectRoot}`);
return findTasksJsonInDirectory(envProjectRoot, args.file, log);
}
// 2. Check the standard locations relative to projectRoot
// 2. If project root is explicitly provided, use it directly
if (args.projectRoot) {
const projectRoot = args.projectRoot;
log.info(`Using explicitly provided project root: ${projectRoot}`);
return findTasksJsonInDirectory(projectRoot, args.file, log);
}
// 3. If we have a last known project root that worked, try it first
if (lastFoundProjectRoot) {
log.info(`Trying last known project root: ${lastFoundProjectRoot}`);
try {
const tasksPath = findTasksJsonInDirectory(lastFoundProjectRoot, args.file, log);
return tasksPath;
} catch (error) {
log.info(`Task file not found in last known project root, continuing search.`);
// Continue with search if not found
}
}
// 4. Start with current directory - this is likely the user's project directory
const startDir = process.cwd();
log.info(`Searching for tasks.json starting from current directory: ${startDir}`);
// Try to find tasks.json by walking up the directory tree from cwd
try {
return findTasksJsonWithParentSearch(startDir, args.file, log);
} catch (error) {
// 5. If not found in cwd or parents, package might be installed via npm
// and the user could be in an unrelated directory
// As a last resort, check if there's a tasks.json in the package directory itself
// (for development scenarios)
const packagePath = getPackagePath();
if (packagePath !== startDir) {
log.info(`Tasks file not found in current directory tree. Checking package directory: ${packagePath}`);
try {
return findTasksJsonInDirectory(packagePath, args.file, log);
} catch (packageError) {
// Fall through to throw the original error
}
}
// If all attempts fail, throw the original error with guidance
error.message = `${error.message}\n\nPossible solutions:
1. Run the command from your project directory containing tasks.json
2. Use --project-root=/path/to/project to specify the project location
3. Set TASK_MASTER_PROJECT_ROOT environment variable to your project path`;
throw error;
}
}
/**
* Check if a directory contains any project marker files or directories
* @param {string} dirPath - Directory to check
* @returns {boolean} - True if the directory contains any project markers
*/
function hasProjectMarkers(dirPath) {
return PROJECT_MARKERS.some(marker => {
const markerPath = path.join(dirPath, marker);
// Check if the marker exists as either a file or directory
return fs.existsSync(markerPath);
});
}
/**
* Search for tasks.json in a specific directory
* @param {string} dirPath - Directory to search in
* @param {string} explicitFilePath - Optional explicit file path relative to dirPath
* @param {Object} log - Logger object
* @returns {string} - Absolute path to tasks.json
* @throws {Error} - If tasks.json cannot be found
*/
function findTasksJsonInDirectory(dirPath, explicitFilePath, log) {
const possiblePaths = [];
// 1. If a file is explicitly provided relative to dirPath
if (explicitFilePath) {
possiblePaths.push(path.resolve(dirPath, explicitFilePath));
}
// 2. Check the standard locations relative to dirPath
possiblePaths.push(
path.join(projectRoot, 'tasks.json'),
path.join(projectRoot, 'tasks', 'tasks.json')
path.join(dirPath, 'tasks.json'),
path.join(dirPath, 'tasks', 'tasks.json')
);
log.info(`Checking potential task file paths: ${possiblePaths.join(', ')}`);
@@ -38,12 +187,86 @@ export function findTasksJsonPath(args, log) {
for (const p of possiblePaths) {
if (fs.existsSync(p)) {
log.info(`Found tasks file at: ${p}`);
// Store the project root for future use
lastFoundProjectRoot = dirPath;
return p;
}
}
// If no file was found, throw an error
const error = new Error(`Tasks file not found in any of the expected locations relative to ${projectRoot}: ${possiblePaths.join(', ')}`);
const error = new Error(`Tasks file not found in any of the expected locations relative to ${dirPath}: ${possiblePaths.join(', ')}`);
error.code = 'TASKS_FILE_NOT_FOUND';
throw error;
}
/**
* Recursively search for tasks.json in the given directory and parent directories
* Also looks for project markers to identify potential project roots
* @param {string} startDir - Directory to start searching from
* @param {string} explicitFilePath - Optional explicit file path
* @param {Object} log - Logger object
* @returns {string} - Absolute path to tasks.json
* @throws {Error} - If tasks.json cannot be found in any parent directory
*/
function findTasksJsonWithParentSearch(startDir, explicitFilePath, log) {
let currentDir = startDir;
const rootDir = path.parse(currentDir).root;
// Keep traversing up until we hit the root directory
while (currentDir !== rootDir) {
// First check for tasks.json directly
try {
return findTasksJsonInDirectory(currentDir, explicitFilePath, log);
} catch (error) {
// If tasks.json not found but the directory has project markers,
// log it as a potential project root (helpful for debugging)
if (hasProjectMarkers(currentDir)) {
log.info(`Found project markers in ${currentDir}, but no tasks.json`);
}
// Move up to parent directory
const parentDir = path.dirname(currentDir);
// Check if we've reached the root
if (parentDir === currentDir) {
break;
}
log.info(`Tasks file not found in ${currentDir}, searching in parent directory: ${parentDir}`);
currentDir = parentDir;
}
}
// If we've searched all the way to the root and found nothing
const error = new Error(`Tasks file not found in ${startDir} or any parent directory.`);
error.code = 'TASKS_FILE_NOT_FOUND';
throw error;
}
function findTasksWithNpmConsideration(startDir, log) {
// First try our recursive parent search from cwd
try {
return findTasksJsonWithParentSearch(startDir, null, log);
} catch (error) {
// If that fails, try looking relative to the executable location
const execPath = process.argv[1];
const execDir = path.dirname(execPath);
log.info(`Looking for tasks file relative to executable at: ${execDir}`);
try {
return findTasksJsonWithParentSearch(execDir, null, log);
} catch (secondError) {
// If that also fails, check standard locations in user's home directory
const homeDir = os.homedir();
log.info(`Looking for tasks file in home directory: ${homeDir}`);
try {
// Check standard locations in home dir
return findTasksJsonInDirectory(path.join(homeDir, '.task-master'), null, log);
} catch (thirdError) {
// If all approaches fail, throw the original error
throw error;
}
}
}
}

View File

@@ -27,9 +27,9 @@ export function registerListTasksTool(server) {
file: z.string().optional().describe("Path to the tasks file"),
projectRoot: z
.string()
// .optional()
.optional()
.describe(
"Root directory of the project (default: current working directory)"
"Root directory of the project (default: automatically detected)"
),
}),
execute: async (args, { log }) => {

View File

@@ -30,7 +30,7 @@ export function registerSetTaskStatusTool(server) {
.string()
.optional()
.describe(
"Root directory of the project (default: current working directory)"
"Root directory of the project (default: automatically detected)"
),
}),
execute: async (args, { log }) => {

View File

@@ -6,6 +6,10 @@
import { spawnSync } from "child_process";
import path from "path";
import { contextManager } from '../core/context-manager.js'; // Import the singleton
import fs from 'fs';
// Import path utilities to ensure consistent path resolution
import { lastFoundProjectRoot, getPackagePath, PROJECT_MARKERS } from '../core/utils/path-utils.js';
/**
* Get normalized project root path
@@ -14,16 +18,53 @@ import { contextManager } from '../core/context-manager.js'; // Import the singl
* @returns {string} - Normalized absolute path to project root
*/
export function getProjectRoot(projectRootRaw, log) {
// Make sure projectRoot is set
const rootPath = projectRootRaw || process.cwd();
// PRECEDENCE ORDER:
// 1. Environment variable override
// 2. Explicitly provided projectRoot in args
// 3. Previously found/cached project root
// 4. Current directory if it has project markers
// 5. Current directory with warning
// Ensure projectRoot is absolute
const projectRoot = path.isAbsolute(rootPath)
? rootPath
: path.resolve(process.cwd(), rootPath);
// 1. Check for environment variable override
if (process.env.TASK_MASTER_PROJECT_ROOT) {
const envRoot = process.env.TASK_MASTER_PROJECT_ROOT;
const absolutePath = path.isAbsolute(envRoot)
? envRoot
: path.resolve(process.cwd(), envRoot);
log.info(`Using project root from TASK_MASTER_PROJECT_ROOT environment variable: ${absolutePath}`);
return absolutePath;
}
log.info(`Using project root: ${projectRoot}`);
return projectRoot;
// 2. If project root is explicitly provided, use it
if (projectRootRaw) {
const absolutePath = path.isAbsolute(projectRootRaw)
? projectRootRaw
: path.resolve(process.cwd(), projectRootRaw);
log.info(`Using explicitly provided project root: ${absolutePath}`);
return absolutePath;
}
// 3. If we have a last found project root from a tasks.json search, use that for consistency
if (lastFoundProjectRoot) {
log.info(`Using last known project root where tasks.json was found: ${lastFoundProjectRoot}`);
return lastFoundProjectRoot;
}
// 4. Check if the current directory has any indicators of being a task-master project
const currentDir = process.cwd();
if (PROJECT_MARKERS.some(marker => {
const markerPath = path.join(currentDir, marker);
return fs.existsSync(markerPath);
})) {
log.info(`Using current directory as project root (found project markers): ${currentDir}`);
return currentDir;
}
// 5. Default to current working directory but warn the user
log.warn(`No task-master project detected in current directory. Using ${currentDir} as project root.`);
log.warn('Consider using --project-root to specify the correct project location or set TASK_MASTER_PROJECT_ROOT environment variable.');
return currentDir;
}
/**

View File

@@ -923,7 +923,7 @@ Following MCP implementation standards:
8. Update tests to reflect the new naming conventions
9. Create a linting rule to enforce naming conventions in future development
## 34. Review functionality of all MCP direct functions [pending]
## 34. Review functionality of all MCP direct functions [in-progress]
### Dependencies: None
### Description: Verify that all implemented MCP direct functions work correctly with edge cases
### Details:
@@ -947,12 +947,159 @@ Implement the addResearch function in the MCP server's index.js file to enable r
### Details:
Implement the addTemplates function in the MCP server's index.js file to enable template-based generation. Configure proper loading of templates from the appropriate directory and ensure they're accessible to all MCP tools that need to generate formatted content.
## 38. Implement robust project root handling for file paths [pending]
## 38. Implement robust project root handling for file paths [in-progress]
### Dependencies: None
### Description: Create a consistent approach for handling project root paths across MCP tools
### Details:
Analyze and refactor the project root handling mechanism to ensure consistent file path resolution across all MCP direct functions. This should properly handle relative and absolute paths, respect the projectRoot parameter when provided, and have appropriate fallbacks when not specified. Document the approach in a comment within path-utils.js for future maintainers.
<info added on 2025-04-01T02:21:57.137Z>
Here's additional information addressing the request for research on npm package path handling:
## Path Handling Best Practices for npm Packages
### Distinguishing Package and Project Paths
1. **Package Installation Path**:
- Use `require.resolve()` to find paths relative to your package
- For global installs, use `process.execPath` to locate the Node.js executable
2. **Project Path**:
- Use `process.cwd()` as a starting point
- Search upwards for `package.json` or `.git` to find project root
- Consider using packages like `find-up` or `pkg-dir` for robust root detection
### Standard Approaches
1. **Detecting Project Root**:
- Recursive search for `package.json` or `.git` directory
- Use `path.resolve()` to handle relative paths
- Fall back to `process.cwd()` if no root markers found
2. **Accessing Package Files**:
- Use `__dirname` for paths relative to current script
- For files in `node_modules`, use `require.resolve('package-name/path/to/file')`
3. **Separating Package and Project Files**:
- Store package-specific files in a dedicated directory (e.g., `.task-master`)
- Use environment variables to override default paths
### Cross-Platform Compatibility
1. Use `path.join()` and `path.resolve()` for cross-platform path handling
2. Avoid hardcoded forward/backslashes in paths
3. Use `os.homedir()` for user home directory references
### Best Practices for Path Resolution
1. **Absolute vs Relative Paths**:
- Always convert relative paths to absolute using `path.resolve()`
- Use `path.isAbsolute()` to check if a path is already absolute
2. **Handling Different Installation Scenarios**:
- Local dev: Use `process.cwd()` as fallback project root
- Local dependency: Resolve paths relative to consuming project
- Global install: Use `process.execPath` to locate global `node_modules`
3. **Configuration Options**:
- Allow users to specify custom project root via CLI option or config file
- Implement a clear precedence order for path resolution (e.g., CLI option > config file > auto-detection)
4. **Error Handling**:
- Provide clear error messages when critical paths cannot be resolved
- Implement retry logic with alternative methods if primary path detection fails
5. **Documentation**:
- Clearly document path handling behavior in README and inline comments
- Provide examples for common scenarios and edge cases
By implementing these practices, the MCP tools can achieve consistent and robust path handling across various npm installation and usage scenarios.
</info added on 2025-04-01T02:21:57.137Z>
<info added on 2025-04-01T02:25:01.463Z>
Here's additional information addressing the request for clarification on path handling challenges for npm packages:
## Advanced Path Handling Challenges and Solutions
### Challenges to Avoid
1. **Relying solely on process.cwd()**:
- Global installs: process.cwd() could be any directory
- Local installs as dependency: points to parent project's root
- Users may run commands from subdirectories
2. **Dual Path Requirements**:
- Package Path: Where task-master code is installed
- Project Path: Where user's tasks.json resides
3. **Specific Edge Cases**:
- Non-project directory execution
- Deeply nested project structures
- Yarn/pnpm workspaces
- Monorepos with multiple tasks.json files
- Commands invoked from scripts in different directories
### Advanced Solutions
1. **Project Marker Detection**:
- Implement recursive search for package.json or .git
- Use `find-up` package for efficient directory traversal
```javascript
const findUp = require('find-up');
const projectRoot = await findUp(dir => findUp.sync('package.json', { cwd: dir }));
```
2. **Package Path Resolution**:
- Leverage `import.meta.url` with `fileURLToPath`:
```javascript
import { fileURLToPath } from 'url';
import path from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const packageRoot = path.resolve(__dirname, '..');
```
3. **Workspace-Aware Resolution**:
- Detect Yarn/pnpm workspaces:
```javascript
const findWorkspaceRoot = require('find-yarn-workspace-root');
const workspaceRoot = findWorkspaceRoot(process.cwd());
```
4. **Monorepo Handling**:
- Implement cascading configuration search
- Allow multiple tasks.json files with clear precedence rules
5. **CLI Tool Inspiration**:
- ESLint: Uses `eslint-find-rule-files` for config discovery
- Jest: Implements `jest-resolve` for custom module resolution
- Next.js: Uses `find-up` to locate project directories
6. **Robust Path Resolution Algorithm**:
```javascript
function resolveProjectRoot(startDir) {
const projectMarkers = ['package.json', '.git', 'tasks.json'];
let currentDir = startDir;
while (currentDir !== path.parse(currentDir).root) {
if (projectMarkers.some(marker => fs.existsSync(path.join(currentDir, marker)))) {
return currentDir;
}
currentDir = path.dirname(currentDir);
}
return startDir; // Fallback to original directory
}
```
7. **Environment Variable Overrides**:
- Allow users to explicitly set paths:
```javascript
const projectRoot = process.env.TASK_MASTER_PROJECT_ROOT || resolveProjectRoot(process.cwd());
```
By implementing these advanced techniques, task-master can achieve robust path handling across various npm scenarios without requiring manual specification.
</info added on 2025-04-01T02:25:01.463Z>
## 39. Implement add-dependency MCP command [done]
### Dependencies: 23.31
### Description: Create MCP tool implementation for the add-dependency command
@@ -989,3 +1136,68 @@ Analyze and refactor the project root handling mechanism to ensure consistent fi
### Details:
## 45. Support setting env variables through mcp server [pending]
### Dependencies: None
### Description: currently we need to access the env variables through the env file present in the project (that we either create or find and append to). we could abstract this by allowing users to define the env vars in the mcp.json directly as folks currently do. mcp.json should then be in gitignore if thats the case. but for this i think in fastmcp all we need is to access ENV in a specific way. we need to find that way and then implement it
### Details:
<info added on 2025-04-01T01:57:24.160Z>
To access environment variables defined in the mcp.json config file when using FastMCP, you can utilize the `Config` class from the `fastmcp` module. Here's how to implement this:
1. Import the necessary module:
```python
from fastmcp import Config
```
2. Access environment variables:
```python
config = Config()
env_var = config.env.get("VARIABLE_NAME")
```
This approach allows you to retrieve environment variables defined in the mcp.json file directly in your code. The `Config` class automatically loads the configuration, including environment variables, from the mcp.json file.
For security, ensure that sensitive information in mcp.json is not committed to version control. You can add mcp.json to your .gitignore file to prevent accidental commits.
If you need to access multiple environment variables, you can do so like this:
```python
db_url = config.env.get("DATABASE_URL")
api_key = config.env.get("API_KEY")
debug_mode = config.env.get("DEBUG_MODE", False) # With a default value
```
This method provides a clean and consistent way to access environment variables defined in the mcp.json configuration file within your FastMCP project.
</info added on 2025-04-01T01:57:24.160Z>
<info added on 2025-04-01T01:57:49.848Z>
To access environment variables defined in the mcp.json config file when using FastMCP in a JavaScript environment, you can use the `fastmcp` npm package. Here's how to implement this:
1. Install the `fastmcp` package:
```bash
npm install fastmcp
```
2. Import the necessary module:
```javascript
const { Config } = require('fastmcp');
```
3. Access environment variables:
```javascript
const config = new Config();
const envVar = config.env.get('VARIABLE_NAME');
```
This approach allows you to retrieve environment variables defined in the mcp.json file directly in your JavaScript code. The `Config` class automatically loads the configuration, including environment variables, from the mcp.json file.
You can access multiple environment variables like this:
```javascript
const dbUrl = config.env.get('DATABASE_URL');
const apiKey = config.env.get('API_KEY');
const debugMode = config.env.get('DEBUG_MODE', false); // With a default value
```
This method provides a consistent way to access environment variables defined in the mcp.json configuration file within your FastMCP project in a JavaScript environment.
</info added on 2025-04-01T01:57:49.848Z>

View File

@@ -1,102 +1,39 @@
# Task ID: 40
# Title: Implement Project Funding Documentation and Support Infrastructure
# Status: in-progress
# Title: Implement 'plan' Command for Task Implementation Planning
# Status: pending
# Dependencies: None
# Priority: medium
# Description: Create FUNDING.yml for GitHub Sponsors integration that outlines all financial support options for the Task Master project.
# Description: Create a new 'plan' command that appends a structured implementation plan to tasks or subtasks, generating step-by-step instructions for execution based on the task content.
# Details:
This task involves creating a FUNDING.yml file to enable and manage funding options for the Task Master project:
Implement a new 'plan' command that will append a structured implementation plan to existing tasks or subtasks. The implementation should:
**FUNDING.yml file**:
- Create a .github/FUNDING.yml file following GitHub's specifications
- Include configuration for multiple funding platforms:
- GitHub Sponsors (primary if available)
- Open Collective
- Patreon
- Ko-fi
- Liberapay
- Custom funding URLs (project website donation page)
- Research and reference successful implementation patterns from Vue.js, React, and TypeScript projects
- Ensure the FUNDING.yml contains sufficient information to guide users on how to support the project
- Include comments within the YAML file to provide context for each funding option
1. Accept an '--id' parameter that can reference either a task or subtask ID
2. Determine whether the ID refers to a task or subtask and retrieve the appropriate content from tasks.json and/or individual task files
3. Generate a step-by-step implementation plan using AI (Claude by default)
4. Support a '--research' flag to use Perplexity instead of Claude when needed
5. Format the generated plan within XML tags like `<implementation_plan as of timestamp>...</implementation_plan>`
6. Append this plan to the implementation details section of the task/subtask
7. Display a confirmation card indicating the implementation plan was successfully created
The implementation should maintain consistent branding and messaging with the rest of the Task Master project. Research at least 5 successful open source projects to identify best practices in funding configuration.
The implementation plan should be detailed and actionable, containing specific steps such as searching for files, creating new files, modifying existing files, etc. The goal is to frontload planning work into the task/subtask so execution can begin immediately.
Reference the existing 'update-subtask' command implementation as a starting point, as it uses a similar approach for appending content to tasks. Ensure proper error handling for cases where the specified ID doesn't exist or when API calls fail.
# Test Strategy:
Testing should verify the technical implementation of the FUNDING.yml file:
Testing should verify:
1. **FUNDING.yml validation**:
- Verify the file is correctly placed in the .github directory
- Validate YAML syntax using a linter
- Test that GitHub correctly displays funding options on the repository page
- Verify all links to external funding platforms are functional
1. Command correctly identifies and retrieves content for both task and subtask IDs
2. Implementation plans are properly generated and formatted with XML tags and timestamps
3. Plans are correctly appended to the implementation details section without overwriting existing content
4. The '--research' flag successfully switches the backend from Claude to Perplexity
5. Appropriate error messages are displayed for invalid IDs or API failures
6. Confirmation card is displayed after successful plan creation
2. **User experience testing**:
- Test the complete funding workflow from a potential supporter's perspective
- Verify the process is intuitive and barriers to contribution are minimized
- Check that the Sponsor button appears correctly on GitHub
- Ensure all funding platform links resolve to the correct destinations
- Gather feedback from 2-3 potential users on clarity and ease of use
# Subtasks:
## 1. Research and Create FUNDING.yml File [done]
### Dependencies: None
### Description: Research successful funding configurations and create the .github/FUNDING.yml file for GitHub Sponsors integration and other funding platforms.
### Details:
Implementation steps:
1. Create the .github directory at the project root if it doesn't exist
2. Research funding configurations from 5 successful open source projects (Vue.js, React, TypeScript, etc.)
3. Document the patterns and approaches used in these projects
4. Create the FUNDING.yml file with the following platforms:
- GitHub Sponsors (primary)
- Open Collective
- Patreon
- Ko-fi
- Liberapay
- Custom donation URL for the project website
5. Validate the YAML syntax using a linter
6. Test the file by pushing to a test branch and verifying the Sponsor button appears correctly on GitHub
Testing approach:
- Validate YAML syntax using yamllint or similar tool
- Test on GitHub by checking if the Sponsor button appears in the repository
- Verify each funding link resolves to the correct destination
## 4. Add Documentation Comments to FUNDING.yml [pending]
### Dependencies: 40.1
### Description: Add comprehensive comments within the FUNDING.yml file to provide context and guidance for each funding option.
### Details:
Implementation steps:
1. Add a header comment explaining the purpose of the file
2. For each funding platform entry, add comments that explain:
- What the platform is
- How funds are processed on this platform
- Any specific benefits of using this platform
- Brief instructions for potential sponsors
3. Include a comment about how sponsors will be acknowledged
4. Add information about fund allocation (maintenance, new features, infrastructure)
5. Ensure comments follow YAML comment syntax and don't break the file structure
Testing approach:
- Validate that the YAML file still passes linting with comments added
- Verify the file still functions correctly on GitHub
- Have at least one team member review the comments for clarity and completeness
## 5. Integrate Funding Information in Project README [pending]
### Dependencies: 40.1, 40.4
### Description: Add a section to the project README that highlights the funding options and directs users to the Sponsor button.
### Details:
Implementation steps:
1. Create a 'Support the Project' or 'Sponsorship' section in the README.md
2. Explain briefly why financial support matters for the project
3. Direct users to the GitHub Sponsor button
4. Mention the alternative funding platforms available
5. Include a brief note on how funds will be used
6. Add any relevant funding badges (e.g., Open Collective, GitHub Sponsors)
Testing approach:
- Review the README section for clarity and conciseness
- Verify all links work correctly
- Ensure the section is appropriately visible but doesn't overshadow project information
- Check that badges render correctly
Test cases should include:
- Running 'plan --id 123' on an existing task
- Running 'plan --id 123.1' on an existing subtask
- Running 'plan --id 123 --research' to test the Perplexity integration
- Running 'plan --id 999' with a non-existent ID to verify error handling
- Running the command on tasks with existing implementation plans to ensure proper appending
Manually review the quality of generated plans to ensure they provide actionable, step-by-step guidance that accurately reflects the task requirements.

File diff suppressed because one or more lines are too long

View File

@@ -177,26 +177,42 @@ describe('UI Module', () => {
describe('createProgressBar function', () => {
test('should create a progress bar with the correct percentage', () => {
const result = createProgressBar(50, 10);
expect(result).toBe('█████░░░░░ 50%');
const result = createProgressBar(50, 10, {
'pending': 20,
'in-progress': 15,
'blocked': 5
});
expect(result).toContain('50%');
});
test('should handle 0% progress', () => {
const result = createProgressBar(0, 10);
expect(result).toBe('░░░░░░░░░░ 0%');
expect(result).toContain('0%');
});
test('should handle 100% progress', () => {
const result = createProgressBar(100, 10);
expect(result).toBe('██████████ 100%');
expect(result).toContain('100%');
});
test('should handle invalid percentages by clamping', () => {
const result1 = createProgressBar(0, 10); // -10 should clamp to 0
expect(result1).toBe('░░░░░░░░░░ 0%');
const result1 = createProgressBar(0, 10);
expect(result1).toContain('0%');
const result2 = createProgressBar(100, 10); // 150 should clamp to 100
expect(result2).toBe('██████████ 100%');
const result2 = createProgressBar(100, 10);
expect(result2).toContain('100%');
});
test('should support status breakdown in the progress bar', () => {
const result = createProgressBar(30, 10, {
'pending': 30,
'in-progress': 20,
'blocked': 10,
'deferred': 5,
'cancelled': 5
});
expect(result).toContain('40%');
});
});