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.
This commit is contained in:
Eyal Toledano
2025-06-13 18:27:51 -04:00
parent a047886910
commit 32236a0bc5
16 changed files with 127 additions and 41 deletions

View File

@@ -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

View File

@@ -16,9 +16,10 @@ import {
* @param {Object} log - Logger object.
* @returns {Promise<Object>} - 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) {

View File

@@ -21,9 +21,10 @@ import {
* @param {Object} log - Logger object
* @returns {Promise<Object>} - 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 {

View File

@@ -23,9 +23,10 @@ import {
* @param {Object} log - Logger object
* @returns {Promise<Object>} - 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,

View File

@@ -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) {

View File

@@ -24,8 +24,7 @@ import { findTasksPath } from '../utils/path-utils.js';
* @returns {Promise<Object>} - 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,

View File

@@ -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(