From 32236a0bc58087988bc49b37ae5d93c2f714d05b Mon Sep 17 00:00:00 2001 From: Eyal Toledano Date: Fri, 13 Jun 2025 18:27:51 -0400 Subject: [PATCH] feat(git-workflow): Add automatic git branch-tag integration - Implement automatic tag creation when switching to new git branches - Add branch-tag mapping system for seamless context switching - Enable auto-switch of task contexts based on current git branch - Provide isolated task contexts per branch to prevent merge conflicts - Add configuration support for enabling/disabling git workflow features - Fix ES module compatibility issues in git-utils module - Maintain zero migration impact with automatic 'master' tag creation - Support parallel development with branch-specific task contexts The git workflow system automatically detects branch changes and creates corresponding empty task tags, enabling developers to maintain separate task contexts for different features/branches while preventing task-related merge conflicts during collaborative development. Resolves git workflow integration requirements for multi-context development. --- .changeset/rotten-files-clap.md | 13 ++++++ .changeset/slick-webs-lead.md | 24 +++++++++++ .../research/2025-06-13_what-is-typescript.md | 42 +++++++++++++++++++ .taskmaster/state.json | 2 +- .taskmaster/tasks/tasks.json | 16 +++++-- .../src/core/direct-functions/expand-task.js | 4 +- .../src/core/direct-functions/list-tasks.js | 9 ++-- .../src/core/direct-functions/next-task.js | 7 ++-- .../src/core/direct-functions/remove-task.js | 7 ++-- .../core/direct-functions/set-task-status.js | 6 ++- .../src/core/direct-functions/show-task.js | 5 +-- mcp-server/src/tools/get-tasks.js | 6 ++- scripts/modules/commands.js | 6 +-- scripts/modules/task-manager/research.js | 6 --- scripts/modules/utils.js | 5 +-- scripts/modules/utils/git-utils.js | 10 ++--- 16 files changed, 127 insertions(+), 41 deletions(-) create mode 100644 .changeset/rotten-files-clap.md create mode 100644 .changeset/slick-webs-lead.md create mode 100644 .taskmaster/docs/research/2025-06-13_what-is-typescript.md diff --git a/.changeset/rotten-files-clap.md b/.changeset/rotten-files-clap.md new file mode 100644 index 00000000..014a25ae --- /dev/null +++ b/.changeset/rotten-files-clap.md @@ -0,0 +1,13 @@ +--- +"task-master-ai": minor +--- + +Add an experimental automatic git branch-tag integration for seamless multi-context development + +- **Automatic Tag Creation**: System now automatically creates empty tags when switching to new git branches +- **Branch-Tag Mapping**: Maintains mapping between git branches and task contexts for seamless workflow +- **Auto-Switch on Branch Change**: Task context automatically switches when you change git branches (when git workflow is enabled) +- **Isolated Task Contexts**: Each branch gets its own clean task context, preventing merge conflicts and enabling parallel development +- **Configuration Support**: Git workflow features can be enabled/disabled via `.taskmaster/config.json` +- **Zero Migration Impact**: Existing projects continue working unchanged with automatic migration to "master" tag +- **ES Module Compatibility**: Fixed git-utils module to work properly with ES module architecture diff --git a/.changeset/slick-webs-lead.md b/.changeset/slick-webs-lead.md new file mode 100644 index 00000000..8381fa0b --- /dev/null +++ b/.changeset/slick-webs-lead.md @@ -0,0 +1,24 @@ +--- +"task-master-ai": minor +--- + +Research Save-to-File Feature & Critical MCP Tag Corruption Fix + +**🔬 New Research Save-to-File Functionality:** + +Added comprehensive save-to-file capability to the research command, enabling users to preserve research sessions for future reference and documentation. + +**CLI Integration:** +- New `--save-file` flag for `task-master research` command +- Consistent with existing `--save` and `--save-to` flags for intuitive usage +- Interactive "Save to file" option in follow-up questions menu + +**MCP Integration:** +- New `saveToFile` boolean parameter for the `research` MCP tool +- Enables programmatic research saving for AI agents and integrated tools + +**File Management:** +- Automatically creates `.taskmaster/docs/research/` directory structure +- Generates timestamped, slugified filenames (e.g., `2025-01-13_what-is-typescript.md`) +- Comprehensive Markdown format with metadata headers including query, timestamp, and context sources +- Clean conversation history formatting without duplicate information diff --git a/.taskmaster/docs/research/2025-06-13_what-is-typescript.md b/.taskmaster/docs/research/2025-06-13_what-is-typescript.md new file mode 100644 index 00000000..218bf43a --- /dev/null +++ b/.taskmaster/docs/research/2025-06-13_what-is-typescript.md @@ -0,0 +1,42 @@ +--- +title: Research Session +query: "What is TypeScript?" +date: 6/13/2025 +time: 3:07:41 PM +timestamp: 2025-06-13T19:07:41.313Z +exchanges: 2 +--- + +# Research Session + +**Query:** What is TypeScript? +**Date:** 6/13/2025 3:07:41 PM +**Exchanges:** 2 + +--- + +## Initial Query + +**Question:** What is TypeScript? + +**Response:** + +TypeScript is a strongly typed, object-oriented programming language that builds on JavaScript by adding static typing and type annotations. It is a syntactic superset of JavaScript, meaning all JavaScript code is valid TypeScript, but TypeScript adds extra syntax to specify types, enabling compile-time type checking and better tooling support. This helps catch errors early before running the code and improves code quality, especially in large-scale applications. TypeScript code is transpiled to plain JavaScript, which can run anywhere JavaScript runs, such as browsers, Node.js, or Deno. It is developed and maintained by Microsoft as open-source software under the Apache 2.0 license[1][2][3][4][5]. + +--- + +## Follow-up 1 + +**Question:** And why is the sky blue? + +**Response:** + +- The sky appears blue because sunlight interacts with Earth's atmosphere, and the shorter wavelengths of light (blue and violet) are scattered in all directions by air molecules—a process called Rayleigh scattering[1][2][3][5]. +- Blue light is scattered more than red light because it has a shorter wavelength, making the sky look blue to our eyes during the day[2][3][5]. +- If Earth's atmosphere didn't scatter sunlight, the sky would appear dark, as it does on the Moon, which lacks an atmosphere[5]. + + +--- + +*Generated by Task Master Research Command* +*Timestamp: 2025-06-13T19:07:41.313Z* diff --git a/.taskmaster/state.json b/.taskmaster/state.json index 7154f4a1..f30bfdff 100644 --- a/.taskmaster/state.json +++ b/.taskmaster/state.json @@ -1,6 +1,6 @@ { "currentTag": "v017-adds", - "lastSwitched": "2025-06-13T17:56:13.380Z", + "lastSwitched": "2025-06-13T22:24:34.119Z", "branchTagMapping": { "v017-adds": "v017-adds" }, diff --git a/.taskmaster/tasks/tasks.json b/.taskmaster/tasks/tasks.json index edaa3d21..c0db3b66 100644 --- a/.taskmaster/tasks/tasks.json +++ b/.taskmaster/tasks/tasks.json @@ -6052,7 +6052,7 @@ "id": 98, "title": "Implement Standalone 'research' CLI Command for AI-Powered Queries", "description": "Develop a new 'task-master research' (alias 'tm research') CLI command for fast, context-aware AI research queries using the ai-services-unified.js infrastructure.", - "status": "in-progress", + "status": "done", "dependencies": [ 2, 4, @@ -6128,8 +6128,8 @@ "id": 7, "title": "Add research save-to-file functionality", "description": "Implement functionality to save research results to /research/ folder with optional interactive prompts", - "details": "Add capability to save research results to files in a /research/ directory at project root. For CLI mode, use inquirer to prompt user if they want to save the research. For MCP mode, accept a saveToFile parameter.\n\nKey implementation details:\n- Create /research/ directory if it doesn't exist (similar to how tasks/ is handled)\n- Generate meaningful filenames based on query and timestamp\n- Support both CLI interactive mode (inquirer prompts) and MCP parameter mode\n- Follow project root detection pattern from add-task.js stack\n- Handle file writing with proper error handling\n- Return saved file path in response for confirmation\n\nFile structure:\n- /research/YYYY-MM-DD_query-summary.md (markdown format)\n- Include query, timestamp, context used, and full AI response\n- Add metadata header with query details and context sources\n\nImplementation approach confirmed with detailed plan:\n\n**Modified handleFollowUpQuestions:**\n- Added \"Save to file\" option to inquirer prompt choices\n- Option triggers new handleSaveToFile function when selected\n\n**New handleSaveToFile function:**\n- Manages complete file-saving workflow\n- Creates .taskmaster/docs/research/ directory structure (following project patterns)\n- Generates slugified filenames: YYYY-MM-DD_query-summary.md format\n- Formats full conversation history into comprehensive Markdown with metadata header\n- Handles file writing with proper error handling\n- Returns confirmation message with saved file path\n\n**Enhanced performResearch for MCP integration:**\n- Added saveToFile boolean parameter to options object\n- Non-interactive mode (MCP) bypasses user prompts when saveToFile=true\n- Direct invocation of handleSaveToFile logic for programmatic saves\n- Updated main action in commands.js to support new saveToFile option parameter\n\nThis maintains consistency with existing project patterns while supporting both interactive CLI and programmatic MCP usage modes.\n", - "status": "pending", + "details": "Add capability to save research results to files in a /research/ directory at project root. For CLI mode, use inquirer to prompt user if they want to save the research. For MCP mode, accept a saveToFile parameter.\n\nKey implementation details:\n- Create /research/ directory if it doesn't exist (similar to how tasks/ is handled)\n- Generate meaningful filenames based on query and timestamp\n- Support both CLI interactive mode (inquirer prompts) and MCP parameter mode\n- Follow project root detection pattern from add-task.js stack\n- Handle file writing with proper error handling\n- Return saved file path in response for confirmation\n\nFile structure:\n- /research/YYYY-MM-DD_query-summary.md (markdown format)\n- Include query, timestamp, context used, and full AI response\n- Add metadata header with query details and context sources\n\nImplementation approach confirmed with detailed plan:\n\n**Modified handleFollowUpQuestions:**\n- Added \"Save to file\" option to inquirer prompt choices\n- Option triggers new handleSaveToFile function when selected\n\n**New handleSaveToFile function:**\n- Manages complete file-saving workflow\n- Creates .taskmaster/docs/research/ directory structure (following project patterns)\n- Generates slugified filenames: YYYY-MM-DD_query-summary.md format\n- Formats full conversation history into comprehensive Markdown with metadata header\n- Handles file writing with proper error handling\n- Returns confirmation message with saved file path\n\n**Enhanced performResearch for MCP integration:**\n- Added saveToFile boolean parameter to options object\n- Non-interactive mode (MCP) bypasses user prompts when saveToFile=true\n- Direct invocation of handleSaveToFile logic for programmatic saves\n- Updated main action in commands.js to support new saveToFile option parameter\n\nThis maintains consistency with existing project patterns while supporting both interactive CLI and programmatic MCP usage modes.\n\n\n**IMPLEMENTATION COMPLETED SUCCESSFULLY**\n\n✅ **Save-to-file functionality implemented and working:**\n\n1. **Core Function Enhanced**: Modified `handleFollowUpQuestions` in `research.js` to add \"Save to file\" option\n2. **New Handler Created**: Added `handleSaveToFile` function that:\n - Creates `.taskmaster/docs/research/` directory if needed\n - Generates timestamped, slugified filenames (e.g., `2025-01-13_what-is-typescript.md`)\n - Formats research as comprehensive Markdown with metadata header\n - Saves conversation history with proper formatting\n3. **CLI Integration**: Added `--save-file` flag to research command (consistent with existing `--save` and `--save-to` flags)\n4. **MCP Integration**: Added `saveToFile` parameter to MCP research tool\n5. **Output Cleanup**: Fixed duplicate information in saved files by removing redundant header section\n\n**Testing Results:**\n- CLI command `task-master research \"What is TypeScript?\" --save-file --detail=low` works perfectly\n- File saved to `.taskmaster/docs/research/2025-01-13_what-is-typescript.md` with clean formatting\n- MCP tool has correct `saveToFile` parameter\n\n**🔧 CRITICAL BUG FIXED: Tag Corruption in MCP Tools**\n\n**Root Cause Identified**: Several MCP direct functions were calling `readJSON()` without the `projectRoot` parameter, causing tag resolution to fail and corrupting the tagged task structure.\n\n**Key Fixes Applied**:\n1. **Fixed `readJSON()` calls**: Added missing `projectRoot` parameter to all `readJSON()` calls in direct functions\n2. **Updated MCP tool parameter passing**: Ensured `projectRoot` is passed from MCP tools to direct functions\n3. **Context parameter clarification**: Only functions that perform AI operations actually need the `context = {}` parameter for session access (API keys)\n\n**Functions Fixed**:\n- `list-tasks.js` - Now passes `projectRoot` in context to core function\n- `next-task.js` - Now calls `readJSON(tasksJsonPath, projectRoot)`\n- `set-task-status.js` - Now passes `projectRoot` to `nextTaskDirect`\n- `show-task.js` - Now calls `readJSON(tasksJsonPath, projectRoot)`\n- `remove-task.js` - Now calls `readJSON(tasksJsonPath, projectRoot)`\n- `expand-task.js` - Now calls `readJSON(tasksPath, projectRoot)` in both locations\n\n**Verification**: Tag preservation now works correctly - tested with `set_task_status` MCP tool and tag remained as \"master\" instead of being corrupted.\n\n**Architecture Insight**: The `context = {}` parameter is only needed for AI operations that require `session` for API keys. Simple CRUD operations only need `projectRoot` passed correctly to maintain tag context.\n", + "status": "done", "dependencies": [], "parentTaskId": 98 }, @@ -6748,8 +6748,16 @@ ], "metadata": { "created": "2025-06-13T02:49:12.129Z", - "updated": "2025-06-13T17:26:21.856Z", + "updated": "2025-06-13T20:18:22.896Z", "description": "Tasks for master context" } + }, + "v017-adds": { + "tasks": [], + "metadata": { + "created": "2025-06-13T22:24:34.115Z", + "updated": "2025-06-13T22:24:34.115Z", + "description": "Automatically created from git branch \"v017-adds\"" + } } } \ No newline at end of file diff --git a/mcp-server/src/core/direct-functions/expand-task.js b/mcp-server/src/core/direct-functions/expand-task.js index d92d178d..9ff833dc 100644 --- a/mcp-server/src/core/direct-functions/expand-task.js +++ b/mcp-server/src/core/direct-functions/expand-task.js @@ -89,7 +89,7 @@ export async function expandTaskDirect(args, log, context = {}) { // Read tasks data log.info(`[expandTaskDirect] Attempting to read JSON from: ${tasksPath}`); - const data = readJSON(tasksPath); + const data = readJSON(tasksPath, projectRoot); log.info( `[expandTaskDirect] Result of readJSON: ${data ? 'Data read successfully' : 'readJSON returned null or undefined'}` ); @@ -207,7 +207,7 @@ export async function expandTaskDirect(args, log, context = {}) { if (!wasSilent && isSilentMode()) disableSilentMode(); // Read the updated data - const updatedData = readJSON(tasksPath); + const updatedData = readJSON(tasksPath, projectRoot); const updatedTask = updatedData.tasks.find((t) => t.id === taskId); // Calculate how many subtasks were added diff --git a/mcp-server/src/core/direct-functions/list-tasks.js b/mcp-server/src/core/direct-functions/list-tasks.js index 2a0133a3..36ccc01b 100644 --- a/mcp-server/src/core/direct-functions/list-tasks.js +++ b/mcp-server/src/core/direct-functions/list-tasks.js @@ -16,9 +16,10 @@ import { * @param {Object} log - Logger object. * @returns {Promise} - Task list result { success: boolean, data?: any, error?: { code: string, message: string } }. */ -export async function listTasksDirect(args, log) { +export async function listTasksDirect(args, log, context = {}) { // Destructure the explicit tasksJsonPath from args - const { tasksJsonPath, reportPath, status, withSubtasks } = args; + const { tasksJsonPath, reportPath, status, withSubtasks, projectRoot } = args; + const { session } = context; if (!tasksJsonPath) { log.error('listTasksDirect called without tasksJsonPath'); @@ -50,7 +51,9 @@ export async function listTasksDirect(args, log) { statusFilter, reportPath, withSubtasksFilter, - 'json' + 'json', + null, // tag + { projectRoot, session } // context ); if (!resultData || !resultData.tasks) { diff --git a/mcp-server/src/core/direct-functions/next-task.js b/mcp-server/src/core/direct-functions/next-task.js index bb1f0e33..be77525b 100644 --- a/mcp-server/src/core/direct-functions/next-task.js +++ b/mcp-server/src/core/direct-functions/next-task.js @@ -21,9 +21,10 @@ import { * @param {Object} log - Logger object * @returns {Promise} - Next task result { success: boolean, data?: any, error?: { code: string, message: string } } */ -export async function nextTaskDirect(args, log) { +export async function nextTaskDirect(args, log, context = {}) { // Destructure expected args - const { tasksJsonPath, reportPath } = args; + const { tasksJsonPath, reportPath, projectRoot } = args; + const { session } = context; if (!tasksJsonPath) { log.error('nextTaskDirect called without tasksJsonPath'); @@ -45,7 +46,7 @@ export async function nextTaskDirect(args, log) { log.info(`Finding next task from ${tasksJsonPath}`); // Read tasks data using the provided path - const data = readJSON(tasksJsonPath); + const data = readJSON(tasksJsonPath, projectRoot); if (!data || !data.tasks) { disableSilentMode(); // Disable before return return { diff --git a/mcp-server/src/core/direct-functions/remove-task.js b/mcp-server/src/core/direct-functions/remove-task.js index 26684817..34200f41 100644 --- a/mcp-server/src/core/direct-functions/remove-task.js +++ b/mcp-server/src/core/direct-functions/remove-task.js @@ -23,9 +23,10 @@ import { * @param {Object} log - Logger object * @returns {Promise} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string } } */ -export async function removeTaskDirect(args, log) { +export async function removeTaskDirect(args, log, context = {}) { // Destructure expected args - const { tasksJsonPath, id } = args; + const { tasksJsonPath, id, projectRoot } = args; + const { session } = context; try { // Check if tasksJsonPath was provided if (!tasksJsonPath) { @@ -59,7 +60,7 @@ export async function removeTaskDirect(args, log) { ); // Validate all task IDs exist before proceeding - const data = readJSON(tasksJsonPath); + const data = readJSON(tasksJsonPath, projectRoot); if (!data || !data.tasks) { return { success: false, diff --git a/mcp-server/src/core/direct-functions/set-task-status.js b/mcp-server/src/core/direct-functions/set-task-status.js index 5f35a4f9..da0eeb41 100644 --- a/mcp-server/src/core/direct-functions/set-task-status.js +++ b/mcp-server/src/core/direct-functions/set-task-status.js @@ -95,9 +95,11 @@ export async function setTaskStatusDirect(args, log, context = {}) { const nextResult = await nextTaskDirect( { tasksJsonPath: tasksJsonPath, - reportPath: complexityReportPath + reportPath: complexityReportPath, + projectRoot: projectRoot }, - log + log, + { session } ); if (nextResult.success) { diff --git a/mcp-server/src/core/direct-functions/show-task.js b/mcp-server/src/core/direct-functions/show-task.js index 09d31496..e1ea6b0c 100644 --- a/mcp-server/src/core/direct-functions/show-task.js +++ b/mcp-server/src/core/direct-functions/show-task.js @@ -24,8 +24,7 @@ import { findTasksPath } from '../utils/path-utils.js'; * @returns {Promise} - Result object with success status and data/error information. */ export async function showTaskDirect(args, log) { - // Destructure session from context if needed later, otherwise ignore - // const { session } = context; + // This function doesn't need session context since it only reads data // Destructure projectRoot and other args. projectRoot is assumed normalized. const { id, file, reportPath, status, projectRoot } = args; @@ -56,7 +55,7 @@ export async function showTaskDirect(args, log) { // --- Rest of the function remains the same, using tasksJsonPath --- try { - const tasksData = readJSON(tasksJsonPath); + const tasksData = readJSON(tasksJsonPath, projectRoot); if (!tasksData || !tasksData.tasks) { return { success: false, diff --git a/mcp-server/src/tools/get-tasks.js b/mcp-server/src/tools/get-tasks.js index cee9c6d7..240f2ab2 100644 --- a/mcp-server/src/tools/get-tasks.js +++ b/mcp-server/src/tools/get-tasks.js @@ -83,9 +83,11 @@ export function registerListTasksTool(server) { tasksJsonPath: tasksJsonPath, status: args.status, withSubtasks: args.withSubtasks, - reportPath: complexityReportPath + reportPath: complexityReportPath, + projectRoot: args.projectRoot }, - log + log, + { session } ); log.info( diff --git a/scripts/modules/commands.js b/scripts/modules/commands.js index 743660c7..4a44a495 100644 --- a/scripts/modules/commands.js +++ b/scripts/modules/commands.js @@ -1645,8 +1645,8 @@ function registerCommands(programInstance) { 'Automatically save research results to specified task/subtask ID (e.g., "15" or "15.2")' ) .option( - '--save-to-file ', - 'Save research results to the specified file' + '--save-file', + 'Save research results to .taskmaster/docs/research/ directory' ) .option('--tag ', 'Specify tag context for task operations') .action(async (prompt, options) => { @@ -1846,7 +1846,7 @@ function registerCommands(programInstance) { includeProjectTree: validatedParams.includeProjectTree, detailLevel: validatedParams.detailLevel, projectRoot: validatedParams.projectRoot, - saveToFile: validatedParams.saveFile, + saveToFile: !!options.saveFile, tag: tag }; diff --git a/scripts/modules/task-manager/research.js b/scripts/modules/task-manager/research.js index 8ffb36bf..3488162b 100644 --- a/scripts/modules/task-manager/research.js +++ b/scripts/modules/task-manager/research.js @@ -994,12 +994,6 @@ exchanges: ${conversationHistory.length} # Research Session -**Query:** ${initialQuery} -**Date:** ${date} ${time} -**Exchanges:** ${conversationHistory.length} - ---- - `; // Add each conversation exchange diff --git a/scripts/modules/utils.js b/scripts/modules/utils.js index 6eb3f556..b5d9dbab 100644 --- a/scripts/modules/utils.js +++ b/scripts/modules/utils.js @@ -9,6 +9,7 @@ import chalk from 'chalk'; import dotenv from 'dotenv'; // Import specific config getters needed here import { getLogLevel, getDebugFlag } from './config-manager.js'; +import * as gitUtils from './utils/git-utils.js'; import { COMPLEXITY_REPORT_FILE, LEGACY_COMPLEXITY_REPORT_FILE, @@ -302,8 +303,6 @@ function readJSON(filepath, projectRoot = null, tag = null) { // This needs to run synchronously BEFORE tag resolution if (projectRoot) { try { - // Import git utilities synchronously - const gitUtils = require('./utils/git-utils.js'); // Run git integration synchronously gitUtils.checkAndAutoSwitchGitTagSync(projectRoot, filepath); } catch (error) { @@ -361,8 +360,6 @@ function readJSON(filepath, projectRoot = null, tag = null) { // This needs to run synchronously BEFORE tag resolution if (projectRoot) { try { - // Import git utilities synchronously - const gitUtils = require('./utils/git-utils.js'); // Run git integration synchronously gitUtils.checkAndAutoSwitchGitTagSync(projectRoot, filepath); } catch (error) { diff --git a/scripts/modules/utils/git-utils.js b/scripts/modules/utils/git-utils.js index f7eaa051..7021d9d4 100644 --- a/scripts/modules/utils/git-utils.js +++ b/scripts/modules/utils/git-utils.js @@ -5,10 +5,10 @@ * MCP-friendly: All functions require projectRoot parameter */ -const { exec, execSync } = require('child_process'); -const { promisify } = require('util'); -const path = require('path'); -const fs = require('fs'); +import { exec, execSync } from 'child_process'; +import { promisify } from 'util'; +import path from 'path'; +import fs from 'fs'; const execAsync = promisify(exec); @@ -614,7 +614,7 @@ function getCurrentBranchSync(projectRoot) { } // Export all functions -module.exports = { +export { isGitRepository, getCurrentBranch, getLocalBranches,