Don't mess up formatting
This commit is contained in:
committed by
Ralph Khreish
parent
0609b9ae28
commit
3fa0c7f7af
@@ -3,18 +3,18 @@
|
|||||||
* Utility functions for the Task Master CLI
|
* Utility functions for the Task Master CLI
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from "fs";
|
import fs from 'fs';
|
||||||
import path from "path";
|
import path from 'path';
|
||||||
import chalk from "chalk";
|
import chalk from 'chalk';
|
||||||
import dotenv from "dotenv";
|
import dotenv from 'dotenv';
|
||||||
// Import specific config getters needed here
|
// Import specific config getters needed here
|
||||||
import { getLogLevel, getDebugFlag } from "./config-manager.js";
|
import { getLogLevel, getDebugFlag } from './config-manager.js';
|
||||||
import * as gitUtils from "./utils/git-utils.js";
|
import * as gitUtils from './utils/git-utils.js';
|
||||||
import {
|
import {
|
||||||
COMPLEXITY_REPORT_FILE,
|
COMPLEXITY_REPORT_FILE,
|
||||||
LEGACY_COMPLEXITY_REPORT_FILE,
|
LEGACY_COMPLEXITY_REPORT_FILE,
|
||||||
LEGACY_CONFIG_FILE,
|
LEGACY_CONFIG_FILE
|
||||||
} from "../../src/constants/paths.js";
|
} from '../../src/constants/paths.js';
|
||||||
|
|
||||||
// Global silent mode flag
|
// Global silent mode flag
|
||||||
let silentMode = false;
|
let silentMode = false;
|
||||||
@@ -39,10 +39,10 @@ function resolveEnvVariable(key, session = null, projectRoot = null) {
|
|||||||
|
|
||||||
// 2. Read .env file at projectRoot
|
// 2. Read .env file at projectRoot
|
||||||
if (projectRoot) {
|
if (projectRoot) {
|
||||||
const envPath = path.join(projectRoot, ".env");
|
const envPath = path.join(projectRoot, '.env');
|
||||||
if (fs.existsSync(envPath)) {
|
if (fs.existsSync(envPath)) {
|
||||||
try {
|
try {
|
||||||
const envFileContent = fs.readFileSync(envPath, "utf-8");
|
const envFileContent = fs.readFileSync(envPath, 'utf-8');
|
||||||
const parsedEnv = dotenv.parse(envFileContent); // Use dotenv to parse
|
const parsedEnv = dotenv.parse(envFileContent); // Use dotenv to parse
|
||||||
if (parsedEnv && parsedEnv[key]) {
|
if (parsedEnv && parsedEnv[key]) {
|
||||||
// console.log(`DEBUG: Found key ${key} in ${envPath}`); // Optional debug log
|
// console.log(`DEBUG: Found key ${key} in ${envPath}`); // Optional debug log
|
||||||
@@ -50,7 +50,7 @@ function resolveEnvVariable(key, session = null, projectRoot = null) {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log error but don't crash, just proceed as if key wasn't found in file
|
// Log error but don't crash, just proceed as if key wasn't found in file
|
||||||
log("warn", `Could not read or parse ${envPath}: ${error.message}`);
|
log('warn', `Could not read or parse ${envPath}: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,15 +72,15 @@ function resolveEnvVariable(key, session = null, projectRoot = null) {
|
|||||||
* @returns {string} Slugified tag name safe for filesystem use
|
* @returns {string} Slugified tag name safe for filesystem use
|
||||||
*/
|
*/
|
||||||
function slugifyTagForFilePath(tagName) {
|
function slugifyTagForFilePath(tagName) {
|
||||||
if (!tagName || typeof tagName !== "string") {
|
if (!tagName || typeof tagName !== 'string') {
|
||||||
return "unknown-tag";
|
return 'unknown-tag';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace invalid filesystem characters with hyphens and clean up
|
// Replace invalid filesystem characters with hyphens and clean up
|
||||||
return tagName
|
return tagName
|
||||||
.replace(/[^a-zA-Z0-9_-]/g, "-") // Replace invalid chars with hyphens
|
.replace(/[^a-zA-Z0-9_-]/g, '-') // Replace invalid chars with hyphens
|
||||||
.replace(/^-+|-+$/g, "") // Remove leading/trailing hyphens
|
.replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
|
||||||
.replace(/-+/g, "-") // Collapse multiple hyphens
|
.replace(/-+/g, '-') // Collapse multiple hyphens
|
||||||
.toLowerCase() // Convert to lowercase
|
.toLowerCase() // Convert to lowercase
|
||||||
.substring(0, 50); // Limit length to prevent overly long filenames
|
.substring(0, 50); // Limit length to prevent overly long filenames
|
||||||
}
|
}
|
||||||
@@ -93,10 +93,10 @@ function slugifyTagForFilePath(tagName) {
|
|||||||
* @param {string} [projectRoot='.'] - The project root directory
|
* @param {string} [projectRoot='.'] - The project root directory
|
||||||
* @returns {string} The resolved file path
|
* @returns {string} The resolved file path
|
||||||
*/
|
*/
|
||||||
function getTagAwareFilePath(basePath, tag, projectRoot = ".") {
|
function getTagAwareFilePath(basePath, tag, projectRoot = '.') {
|
||||||
// Use path.parse and format for clean tag insertion
|
// Use path.parse and format for clean tag insertion
|
||||||
const parsedPath = path.parse(basePath);
|
const parsedPath = path.parse(basePath);
|
||||||
if (!tag || tag === "master") {
|
if (!tag || tag === 'master') {
|
||||||
return path.join(projectRoot, basePath);
|
return path.join(projectRoot, basePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ function getTagAwareFilePath(basePath, tag, projectRoot = ".") {
|
|||||||
*/
|
*/
|
||||||
function findProjectRoot(
|
function findProjectRoot(
|
||||||
startDir = process.cwd(),
|
startDir = process.cwd(),
|
||||||
markers = ["package.json", "pyproject.toml", ".git", LEGACY_CONFIG_FILE],
|
markers = ['package.json', 'pyproject.toml', '.git', LEGACY_CONFIG_FILE]
|
||||||
) {
|
) {
|
||||||
let currentPath = path.resolve(startDir);
|
let currentPath = path.resolve(startDir);
|
||||||
const rootPath = path.parse(currentPath).root;
|
const rootPath = path.parse(currentPath).root;
|
||||||
@@ -157,7 +157,7 @@ const LOG_LEVELS = {
|
|||||||
info: 1,
|
info: 1,
|
||||||
warn: 2,
|
warn: 2,
|
||||||
error: 3,
|
error: 3,
|
||||||
success: 1, // Treat success like info level
|
success: 1 // Treat success like info level
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -165,7 +165,7 @@ const LOG_LEVELS = {
|
|||||||
* @returns {Promise<Object>} The task manager module object
|
* @returns {Promise<Object>} The task manager module object
|
||||||
*/
|
*/
|
||||||
async function getTaskManager() {
|
async function getTaskManager() {
|
||||||
return import("./task-manager.js");
|
return import('./task-manager.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -203,73 +203,49 @@ function log(level, ...args) {
|
|||||||
|
|
||||||
// GUARD: Prevent circular dependency during config loading
|
// GUARD: Prevent circular dependency during config loading
|
||||||
// Use a simple fallback log level instead of calling getLogLevel()
|
// Use a simple fallback log level instead of calling getLogLevel()
|
||||||
let configLevel = "info"; // Default fallback
|
let configLevel = 'info'; // Default fallback
|
||||||
try {
|
try {
|
||||||
// Only try to get config level if we're not in the middle of config loading
|
// Only try to get config level if we're not in the middle of config loading
|
||||||
configLevel = getLogLevel() || "info";
|
configLevel = getLogLevel() || 'info';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If getLogLevel() fails (likely due to circular dependency),
|
// If getLogLevel() fails (likely due to circular dependency),
|
||||||
// use default 'info' level and continue
|
// use default 'info' level and continue
|
||||||
configLevel = "info";
|
configLevel = 'info';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use text prefixes instead of emojis
|
// Use text prefixes instead of emojis
|
||||||
const prefixes = {
|
const prefixes = {
|
||||||
debug: chalk.gray("[DEBUG]"),
|
debug: chalk.gray('[DEBUG]'),
|
||||||
info: chalk.blue("[INFO]"),
|
info: chalk.blue('[INFO]'),
|
||||||
warn: chalk.yellow("[WARN]"),
|
warn: chalk.yellow('[WARN]'),
|
||||||
error: chalk.red("[ERROR]"),
|
error: chalk.red('[ERROR]'),
|
||||||
success: chalk.green("[SUCCESS]"),
|
success: chalk.green('[SUCCESS]')
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure level exists, default to info if not
|
// Ensure level exists, default to info if not
|
||||||
const currentLevel = LOG_LEVELS.hasOwnProperty(level) ? level : "info";
|
const currentLevel = LOG_LEVELS.hasOwnProperty(level) ? level : 'info';
|
||||||
|
|
||||||
// Check log level configuration
|
// Check log level configuration
|
||||||
if (
|
if (
|
||||||
LOG_LEVELS[currentLevel] >= (LOG_LEVELS[configLevel] ?? LOG_LEVELS.info)
|
LOG_LEVELS[currentLevel] >= (LOG_LEVELS[configLevel] ?? LOG_LEVELS.info)
|
||||||
) {
|
) {
|
||||||
const prefix = prefixes[currentLevel] || "";
|
const prefix = prefixes[currentLevel] || '';
|
||||||
// Use console.log for all levels, let chalk handle coloring
|
// Use console.log for all levels, let chalk handle coloring
|
||||||
// Construct the message properly
|
// Construct the message properly
|
||||||
const message = args
|
const message = args
|
||||||
.map((arg) => (typeof arg === "object" ? JSON.stringify(arg) : arg))
|
.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg) : arg))
|
||||||
.join(" ");
|
.join(' ');
|
||||||
console.log(`${prefix} ${message}`);
|
console.log(`${prefix} ${message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalizes task and subtask IDs to numbers to ensure consistent comparisons for Integers and Strings
|
|
||||||
* @param {Array} tasks - The tasks array to normalize
|
|
||||||
*/
|
|
||||||
function normalizeTaskIds(tasks) {
|
|
||||||
if (!Array.isArray(tasks)) return;
|
|
||||||
|
|
||||||
tasks.forEach((task) => {
|
|
||||||
// Convert task ID to number
|
|
||||||
if (task.id !== undefined) {
|
|
||||||
task.id = parseInt(task.id, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert subtask IDs to numbers
|
|
||||||
if (Array.isArray(task.subtasks)) {
|
|
||||||
task.subtasks.forEach((subtask) => {
|
|
||||||
if (subtask.id !== undefined) {
|
|
||||||
subtask.id = parseInt(subtask.id, 10);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the data object has a tagged structure (contains tag objects with tasks arrays)
|
* Checks if the data object has a tagged structure (contains tag objects with tasks arrays)
|
||||||
* @param {Object} data - The data object to check
|
* @param {Object} data - The data object to check
|
||||||
* @returns {boolean} True if the data has a tagged structure
|
* @returns {boolean} True if the data has a tagged structure
|
||||||
*/
|
*/
|
||||||
function hasTaggedStructure(data) {
|
function hasTaggedStructure(data) {
|
||||||
if (!data || typeof data !== "object") {
|
if (!data || typeof data !== 'object') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +253,7 @@ function hasTaggedStructure(data) {
|
|||||||
for (const key in data) {
|
for (const key in data) {
|
||||||
if (
|
if (
|
||||||
data.hasOwnProperty(key) &&
|
data.hasOwnProperty(key) &&
|
||||||
typeof data[key] === "object" &&
|
typeof data[key] === 'object' &&
|
||||||
Array.isArray(data[key].tasks)
|
Array.isArray(data[key].tasks)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
@@ -286,6 +262,30 @@ function hasTaggedStructure(data) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes task IDs to ensure they are numbers instead of strings
|
||||||
|
* @param {Array} tasks - Array of tasks to normalize
|
||||||
|
*/
|
||||||
|
function normalizeTaskIds(tasks) {
|
||||||
|
if (!Array.isArray(tasks)) return;
|
||||||
|
|
||||||
|
tasks.forEach(task => {
|
||||||
|
// Convert task ID to number
|
||||||
|
if (task.id !== undefined) {
|
||||||
|
task.id = parseInt(task.id, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert subtask IDs to numbers
|
||||||
|
if (Array.isArray(task.subtasks)) {
|
||||||
|
task.subtasks.forEach(subtask => {
|
||||||
|
if (subtask.id !== undefined) {
|
||||||
|
subtask.id = parseInt(subtask.id, 10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads and parses a JSON file
|
* Reads and parses a JSON file
|
||||||
* @param {string} filepath - Path to the JSON file
|
* @param {string} filepath - Path to the JSON file
|
||||||
@@ -306,7 +306,7 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
|||||||
|
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
console.log(
|
console.log(
|
||||||
`readJSON called with: ${filepath}, projectRoot: ${projectRoot}, tag: ${tag}`,
|
`readJSON called with: ${filepath}, projectRoot: ${projectRoot}, tag: ${tag}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
|||||||
|
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(fs.readFileSync(filepath, "utf8"));
|
data = JSON.parse(fs.readFileSync(filepath, 'utf8'));
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
console.log(`Successfully read JSON from ${filepath}`);
|
console.log(`Successfully read JSON from ${filepath}`);
|
||||||
}
|
}
|
||||||
@@ -328,7 +328,7 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If it's not a tasks.json file, return as-is
|
// If it's not a tasks.json file, return as-is
|
||||||
if (!filepath.includes("tasks.json") || !data) {
|
if (!filepath.includes('tasks.json') || !data) {
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
console.log(`File is not tasks.json or data is null, returning as-is`);
|
console.log(`File is not tasks.json or data is null, returning as-is`);
|
||||||
}
|
}
|
||||||
@@ -347,15 +347,18 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is legacy format - migrate it to tagged format
|
// This is legacy format - migrate it to tagged format
|
||||||
|
// Normalize task IDs before migration
|
||||||
|
normalizeTaskIds(data.tasks);
|
||||||
|
|
||||||
const migratedData = {
|
const migratedData = {
|
||||||
master: {
|
master: {
|
||||||
tasks: data.tasks,
|
tasks: data.tasks,
|
||||||
metadata: data.metadata || {
|
metadata: data.metadata || {
|
||||||
created: new Date().toISOString(),
|
created: new Date().toISOString(),
|
||||||
updated: new Date().toISOString(),
|
updated: new Date().toISOString(),
|
||||||
description: "Tasks for master context",
|
description: 'Tasks for master context'
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Write the migrated data back to the file
|
// Write the migrated data back to the file
|
||||||
@@ -393,7 +396,7 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we have tagged data, we need to resolve which tag to use
|
// If we have tagged data, we need to resolve which tag to use
|
||||||
if (typeof data === "object" && !data.tasks) {
|
if (typeof data === 'object' && !data.tasks) {
|
||||||
// This is tagged format
|
// This is tagged format
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
console.log(`File is in tagged format, resolving tag...`);
|
console.log(`File is in tagged format, resolving tag...`);
|
||||||
@@ -403,19 +406,19 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
|||||||
for (const tagName in data) {
|
for (const tagName in data) {
|
||||||
if (
|
if (
|
||||||
data.hasOwnProperty(tagName) &&
|
data.hasOwnProperty(tagName) &&
|
||||||
typeof data[tagName] === "object" &&
|
typeof data[tagName] === 'object' &&
|
||||||
data[tagName].tasks
|
data[tagName].tasks
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
ensureTagMetadata(data[tagName], {
|
ensureTagMetadata(data[tagName], {
|
||||||
description: `Tasks for ${tagName} context`,
|
description: `Tasks for ${tagName} context`,
|
||||||
skipUpdate: true, // Don't update timestamp during read operations
|
skipUpdate: true // Don't update timestamp during read operations
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If ensureTagMetadata fails, continue without metadata
|
// If ensureTagMetadata fails, continue without metadata
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
console.log(
|
console.log(
|
||||||
`Failed to ensure metadata for tag ${tagName}: ${error.message}`,
|
`Failed to ensure metadata for tag ${tagName}: ${error.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -438,7 +441,7 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Default to master tag if anything goes wrong
|
// Default to master tag if anything goes wrong
|
||||||
let resolvedTag = "master";
|
let resolvedTag = 'master';
|
||||||
|
|
||||||
// Try to resolve the correct tag, but don't fail if it doesn't work
|
// Try to resolve the correct tag, but don't fail if it doesn't work
|
||||||
try {
|
try {
|
||||||
@@ -459,7 +462,7 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
|||||||
} catch (tagResolveError) {
|
} catch (tagResolveError) {
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
console.log(
|
console.log(
|
||||||
`Tag resolution failed, using master: ${tagResolveError.message}`,
|
`Tag resolution failed, using master: ${tagResolveError.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// resolvedTag stays as 'master'
|
// resolvedTag stays as 'master'
|
||||||
@@ -472,17 +475,18 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
|||||||
// Get the data for the resolved tag
|
// Get the data for the resolved tag
|
||||||
const tagData = data[resolvedTag];
|
const tagData = data[resolvedTag];
|
||||||
if (tagData && tagData.tasks) {
|
if (tagData && tagData.tasks) {
|
||||||
|
// Normalize task IDs for the resolved tag
|
||||||
normalizeTaskIds(tagData.tasks);
|
normalizeTaskIds(tagData.tasks);
|
||||||
|
|
||||||
// Add the _rawTaggedData property and the resolved tag to the returned data
|
// Add the _rawTaggedData property and the resolved tag to the returned data
|
||||||
const result = {
|
const result = {
|
||||||
...tagData,
|
...tagData,
|
||||||
tag: resolvedTag,
|
tag: resolvedTag,
|
||||||
_rawTaggedData: originalTaggedData,
|
_rawTaggedData: originalTaggedData
|
||||||
};
|
};
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
console.log(
|
console.log(
|
||||||
`Returning data for tag '${resolvedTag}' with ${tagData.tasks.length} tasks`,
|
`Returning data for tag '${resolvedTag}' with ${tagData.tasks.length} tasks`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -490,17 +494,18 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
|||||||
// If the resolved tag doesn't exist, fall back to master
|
// If the resolved tag doesn't exist, fall back to master
|
||||||
const masterData = data.master;
|
const masterData = data.master;
|
||||||
if (masterData && masterData.tasks) {
|
if (masterData && masterData.tasks) {
|
||||||
|
// Normalize task IDs for master fallback
|
||||||
normalizeTaskIds(masterData.tasks);
|
normalizeTaskIds(masterData.tasks);
|
||||||
|
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
console.log(
|
console.log(
|
||||||
`Tag '${resolvedTag}' not found, falling back to master with ${masterData.tasks.length} tasks`,
|
`Tag '${resolvedTag}' not found, falling back to master with ${masterData.tasks.length} tasks`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...masterData,
|
...masterData,
|
||||||
tag: "master",
|
tag: 'master',
|
||||||
_rawTaggedData: originalTaggedData,
|
_rawTaggedData: originalTaggedData
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
@@ -509,8 +514,8 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
|||||||
// Return empty structure if no valid data
|
// Return empty structure if no valid data
|
||||||
return {
|
return {
|
||||||
tasks: [],
|
tasks: [],
|
||||||
tag: "master",
|
tag: 'master',
|
||||||
_rawTaggedData: originalTaggedData,
|
_rawTaggedData: originalTaggedData
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -521,16 +526,14 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
|||||||
// If anything goes wrong, try to return master or empty
|
// If anything goes wrong, try to return master or empty
|
||||||
const masterData = data.master;
|
const masterData = data.master;
|
||||||
if (masterData && masterData.tasks) {
|
if (masterData && masterData.tasks) {
|
||||||
normalizeTaskIds(masterData.tasks);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...masterData,
|
...masterData,
|
||||||
_rawTaggedData: originalTaggedData,
|
_rawTaggedData: originalTaggedData
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
tasks: [],
|
tasks: [],
|
||||||
_rawTaggedData: originalTaggedData,
|
_rawTaggedData: originalTaggedData
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -554,26 +557,26 @@ function performCompleteTagMigration(tasksJsonPath) {
|
|||||||
path.dirname(tasksJsonPath);
|
path.dirname(tasksJsonPath);
|
||||||
|
|
||||||
// 1. Migrate config.json - add defaultTag and tags section
|
// 1. Migrate config.json - add defaultTag and tags section
|
||||||
const configPath = path.join(projectRoot, ".taskmaster", "config.json");
|
const configPath = path.join(projectRoot, '.taskmaster', 'config.json');
|
||||||
if (fs.existsSync(configPath)) {
|
if (fs.existsSync(configPath)) {
|
||||||
migrateConfigJson(configPath);
|
migrateConfigJson(configPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Create state.json if it doesn't exist
|
// 2. Create state.json if it doesn't exist
|
||||||
const statePath = path.join(projectRoot, ".taskmaster", "state.json");
|
const statePath = path.join(projectRoot, '.taskmaster', 'state.json');
|
||||||
if (!fs.existsSync(statePath)) {
|
if (!fs.existsSync(statePath)) {
|
||||||
createStateJson(statePath);
|
createStateJson(statePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getDebugFlag()) {
|
if (getDebugFlag()) {
|
||||||
log(
|
log(
|
||||||
"debug",
|
'debug',
|
||||||
`Complete tag migration performed for project: ${projectRoot}`,
|
`Complete tag migration performed for project: ${projectRoot}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (getDebugFlag()) {
|
if (getDebugFlag()) {
|
||||||
log("warn", `Error during complete tag migration: ${error.message}`);
|
log('warn', `Error during complete tag migration: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -584,7 +587,7 @@ function performCompleteTagMigration(tasksJsonPath) {
|
|||||||
*/
|
*/
|
||||||
function migrateConfigJson(configPath) {
|
function migrateConfigJson(configPath) {
|
||||||
try {
|
try {
|
||||||
const rawConfig = fs.readFileSync(configPath, "utf8");
|
const rawConfig = fs.readFileSync(configPath, 'utf8');
|
||||||
const config = JSON.parse(rawConfig);
|
const config = JSON.parse(rawConfig);
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
|
|
||||||
@@ -595,20 +598,20 @@ function migrateConfigJson(configPath) {
|
|||||||
config.global = {};
|
config.global = {};
|
||||||
}
|
}
|
||||||
if (!config.global.defaultTag) {
|
if (!config.global.defaultTag) {
|
||||||
config.global.defaultTag = "master";
|
config.global.defaultTag = 'master';
|
||||||
modified = true;
|
modified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modified) {
|
if (modified) {
|
||||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
|
||||||
if (process.env.TASKMASTER_DEBUG === "true") {
|
if (process.env.TASKMASTER_DEBUG === 'true') {
|
||||||
console.log(
|
console.log(
|
||||||
"[DEBUG] Updated config.json with tagged task system settings",
|
'[DEBUG] Updated config.json with tagged task system settings'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (process.env.TASKMASTER_DEBUG === "true") {
|
if (process.env.TASKMASTER_DEBUG === 'true') {
|
||||||
console.warn(`[WARN] Error migrating config.json: ${error.message}`);
|
console.warn(`[WARN] Error migrating config.json: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -621,18 +624,18 @@ function migrateConfigJson(configPath) {
|
|||||||
function createStateJson(statePath) {
|
function createStateJson(statePath) {
|
||||||
try {
|
try {
|
||||||
const initialState = {
|
const initialState = {
|
||||||
currentTag: "master",
|
currentTag: 'master',
|
||||||
lastSwitched: new Date().toISOString(),
|
lastSwitched: new Date().toISOString(),
|
||||||
branchTagMapping: {},
|
branchTagMapping: {},
|
||||||
migrationNoticeShown: false,
|
migrationNoticeShown: false
|
||||||
};
|
};
|
||||||
|
|
||||||
fs.writeFileSync(statePath, JSON.stringify(initialState, null, 2), "utf8");
|
fs.writeFileSync(statePath, JSON.stringify(initialState, null, 2), 'utf8');
|
||||||
if (process.env.TASKMASTER_DEBUG === "true") {
|
if (process.env.TASKMASTER_DEBUG === 'true') {
|
||||||
console.log("[DEBUG] Created initial state.json for tagged task system");
|
console.log('[DEBUG] Created initial state.json for tagged task system');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (process.env.TASKMASTER_DEBUG === "true") {
|
if (process.env.TASKMASTER_DEBUG === 'true') {
|
||||||
console.warn(`[WARN] Error creating state.json: ${error.message}`);
|
console.warn(`[WARN] Error creating state.json: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -645,7 +648,7 @@ function createStateJson(statePath) {
|
|||||||
function markMigrationForNotice(tasksJsonPath) {
|
function markMigrationForNotice(tasksJsonPath) {
|
||||||
try {
|
try {
|
||||||
const projectRoot = path.dirname(path.dirname(tasksJsonPath));
|
const projectRoot = path.dirname(path.dirname(tasksJsonPath));
|
||||||
const statePath = path.join(projectRoot, ".taskmaster", "state.json");
|
const statePath = path.join(projectRoot, '.taskmaster', 'state.json');
|
||||||
|
|
||||||
// Ensure state.json exists
|
// Ensure state.json exists
|
||||||
if (!fs.existsSync(statePath)) {
|
if (!fs.existsSync(statePath)) {
|
||||||
@@ -654,24 +657,24 @@ function markMigrationForNotice(tasksJsonPath) {
|
|||||||
|
|
||||||
// Read and update state to mark migration occurred using fs directly
|
// Read and update state to mark migration occurred using fs directly
|
||||||
try {
|
try {
|
||||||
const rawState = fs.readFileSync(statePath, "utf8");
|
const rawState = fs.readFileSync(statePath, 'utf8');
|
||||||
const stateData = JSON.parse(rawState) || {};
|
const stateData = JSON.parse(rawState) || {};
|
||||||
// Only set to false if it's not already set (i.e., first time migration)
|
// Only set to false if it's not already set (i.e., first time migration)
|
||||||
if (stateData.migrationNoticeShown === undefined) {
|
if (stateData.migrationNoticeShown === undefined) {
|
||||||
stateData.migrationNoticeShown = false;
|
stateData.migrationNoticeShown = false;
|
||||||
fs.writeFileSync(statePath, JSON.stringify(stateData, null, 2), "utf8");
|
fs.writeFileSync(statePath, JSON.stringify(stateData, null, 2), 'utf8');
|
||||||
}
|
}
|
||||||
} catch (stateError) {
|
} catch (stateError) {
|
||||||
if (process.env.TASKMASTER_DEBUG === "true") {
|
if (process.env.TASKMASTER_DEBUG === 'true') {
|
||||||
console.warn(
|
console.warn(
|
||||||
`[WARN] Error updating state for migration notice: ${stateError.message}`,
|
`[WARN] Error updating state for migration notice: ${stateError.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (process.env.TASKMASTER_DEBUG === "true") {
|
if (process.env.TASKMASTER_DEBUG === 'true') {
|
||||||
console.warn(
|
console.warn(
|
||||||
`[WARN] Error marking migration for notice: ${error.message}`,
|
`[WARN] Error marking migration for notice: ${error.message}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -685,7 +688,7 @@ function markMigrationForNotice(tasksJsonPath) {
|
|||||||
* @param {string} tag - Optional tag for tag context
|
* @param {string} tag - Optional tag for tag context
|
||||||
*/
|
*/
|
||||||
function writeJSON(filepath, data, projectRoot = null, tag = null) {
|
function writeJSON(filepath, data, projectRoot = null, tag = null) {
|
||||||
const isDebug = process.env.TASKMASTER_DEBUG === "true";
|
const isDebug = process.env.TASKMASTER_DEBUG === 'true';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let finalData = data;
|
let finalData = data;
|
||||||
@@ -701,12 +704,12 @@ function writeJSON(filepath, data, projectRoot = null, tag = null) {
|
|||||||
|
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
console.log(
|
console.log(
|
||||||
`writeJSON: Detected resolved tag data missing _rawTaggedData. Re-reading raw data to prevent data loss for tag '${resolvedTag}'.`,
|
`writeJSON: Detected resolved tag data missing _rawTaggedData. Re-reading raw data to prevent data loss for tag '${resolvedTag}'.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-read the full file to get the complete tagged structure
|
// Re-read the full file to get the complete tagged structure
|
||||||
const rawFullData = JSON.parse(fs.readFileSync(filepath, "utf8"));
|
const rawFullData = JSON.parse(fs.readFileSync(filepath, 'utf8'));
|
||||||
|
|
||||||
// Merge the updated data into the full structure
|
// Merge the updated data into the full structure
|
||||||
finalData = {
|
finalData = {
|
||||||
@@ -715,8 +718,8 @@ function writeJSON(filepath, data, projectRoot = null, tag = null) {
|
|||||||
// Preserve existing tag metadata if it exists, otherwise use what's passed
|
// Preserve existing tag metadata if it exists, otherwise use what's passed
|
||||||
...(rawFullData[resolvedTag]?.metadata || {}),
|
...(rawFullData[resolvedTag]?.metadata || {}),
|
||||||
...(data.metadata ? { metadata: data.metadata } : {}),
|
...(data.metadata ? { metadata: data.metadata } : {}),
|
||||||
tasks: data.tasks, // The updated tasks array is the source of truth here
|
tasks: data.tasks // The updated tasks array is the source of truth here
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// If we have _rawTaggedData, this means we're working with resolved tag data
|
// If we have _rawTaggedData, this means we're working with resolved tag data
|
||||||
@@ -733,30 +736,30 @@ function writeJSON(filepath, data, projectRoot = null, tag = null) {
|
|||||||
// Update the specific tag with the resolved data
|
// Update the specific tag with the resolved data
|
||||||
finalData = {
|
finalData = {
|
||||||
...originalTaggedData,
|
...originalTaggedData,
|
||||||
[resolvedTag]: cleanResolvedData,
|
[resolvedTag]: cleanResolvedData
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
console.log(
|
console.log(
|
||||||
`writeJSON: Merging resolved data back into tag '${resolvedTag}'`,
|
`writeJSON: Merging resolved data back into tag '${resolvedTag}'`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up any internal properties that shouldn't be persisted
|
// Clean up any internal properties that shouldn't be persisted
|
||||||
let cleanData = finalData;
|
let cleanData = finalData;
|
||||||
if (cleanData && typeof cleanData === "object") {
|
if (cleanData && typeof cleanData === 'object') {
|
||||||
// Remove any _rawTaggedData or tag properties from root level
|
// Remove any _rawTaggedData or tag properties from root level
|
||||||
const { _rawTaggedData, tag: tagProp, ...rootCleanData } = cleanData;
|
const { _rawTaggedData, tag: tagProp, ...rootCleanData } = cleanData;
|
||||||
cleanData = rootCleanData;
|
cleanData = rootCleanData;
|
||||||
|
|
||||||
// Additional cleanup for tag objects
|
// Additional cleanup for tag objects
|
||||||
if (typeof cleanData === "object" && !Array.isArray(cleanData)) {
|
if (typeof cleanData === 'object' && !Array.isArray(cleanData)) {
|
||||||
const finalCleanData = {};
|
const finalCleanData = {};
|
||||||
for (const [key, value] of Object.entries(cleanData)) {
|
for (const [key, value] of Object.entries(cleanData)) {
|
||||||
if (
|
if (
|
||||||
value &&
|
value &&
|
||||||
typeof value === "object" &&
|
typeof value === 'object' &&
|
||||||
Array.isArray(value.tasks)
|
Array.isArray(value.tasks)
|
||||||
) {
|
) {
|
||||||
// This is a tag object - clean up any rogue root-level properties
|
// This is a tag object - clean up any rogue root-level properties
|
||||||
@@ -779,15 +782,15 @@ function writeJSON(filepath, data, projectRoot = null, tag = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.writeFileSync(filepath, JSON.stringify(cleanData, null, 2), "utf8");
|
fs.writeFileSync(filepath, JSON.stringify(cleanData, null, 2), 'utf8');
|
||||||
|
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
console.log(`writeJSON: Successfully wrote to ${filepath}`);
|
console.log(`writeJSON: Successfully wrote to ${filepath}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log("error", `Error writing JSON file ${filepath}:`, error.message);
|
log('error', `Error writing JSON file ${filepath}:`, error.message);
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
log("error", "Full error details:", error);
|
log('error', 'Full error details:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -828,7 +831,7 @@ function readComplexityReport(customPath = null) {
|
|||||||
const newPath = path.join(process.cwd(), COMPLEXITY_REPORT_FILE);
|
const newPath = path.join(process.cwd(), COMPLEXITY_REPORT_FILE);
|
||||||
const legacyPath = path.join(
|
const legacyPath = path.join(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
LEGACY_COMPLEXITY_REPORT_FILE,
|
LEGACY_COMPLEXITY_REPORT_FILE
|
||||||
);
|
);
|
||||||
|
|
||||||
reportPath = fs.existsSync(newPath) ? newPath : legacyPath;
|
reportPath = fs.existsSync(newPath) ? newPath : legacyPath;
|
||||||
@@ -836,19 +839,19 @@ function readComplexityReport(customPath = null) {
|
|||||||
|
|
||||||
if (!fs.existsSync(reportPath)) {
|
if (!fs.existsSync(reportPath)) {
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
log("debug", `Complexity report not found at ${reportPath}`);
|
log('debug', `Complexity report not found at ${reportPath}`);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reportData = readJSON(reportPath);
|
const reportData = readJSON(reportPath);
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
log("debug", `Successfully read complexity report from ${reportPath}`);
|
log('debug', `Successfully read complexity report from ${reportPath}`);
|
||||||
}
|
}
|
||||||
return reportData;
|
return reportData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
log("error", `Error reading complexity report: ${error.message}`);
|
log('error', `Error reading complexity report: ${error.message}`);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -900,9 +903,9 @@ function taskExists(tasks, taskId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle both regular task IDs and subtask IDs (e.g., "1.2")
|
// Handle both regular task IDs and subtask IDs (e.g., "1.2")
|
||||||
if (typeof taskId === "string" && taskId.includes(".")) {
|
if (typeof taskId === 'string' && taskId.includes('.')) {
|
||||||
const [parentId, subtaskId] = taskId
|
const [parentId, subtaskId] = taskId
|
||||||
.split(".")
|
.split('.')
|
||||||
.map((id) => parseInt(id, 10));
|
.map((id) => parseInt(id, 10));
|
||||||
const parentTask = tasks.find((t) => t.id === parentId);
|
const parentTask = tasks.find((t) => t.id === parentId);
|
||||||
|
|
||||||
@@ -923,11 +926,11 @@ function taskExists(tasks, taskId) {
|
|||||||
* @returns {string} The formatted task ID
|
* @returns {string} The formatted task ID
|
||||||
*/
|
*/
|
||||||
function formatTaskId(id) {
|
function formatTaskId(id) {
|
||||||
if (typeof id === "string" && id.includes(".")) {
|
if (typeof id === 'string' && id.includes('.')) {
|
||||||
return id; // Already formatted as a string with a dot (e.g., "1.2")
|
return id; // Already formatted as a string with a dot (e.g., "1.2")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof id === "number") {
|
if (typeof id === 'number') {
|
||||||
return id.toString();
|
return id.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -946,17 +949,17 @@ function findTaskById(
|
|||||||
tasks,
|
tasks,
|
||||||
taskId,
|
taskId,
|
||||||
complexityReport = null,
|
complexityReport = null,
|
||||||
statusFilter = null,
|
statusFilter = null
|
||||||
) {
|
) {
|
||||||
if (!taskId || !tasks || !Array.isArray(tasks)) {
|
if (!taskId || !tasks || !Array.isArray(tasks)) {
|
||||||
return { task: null, originalSubtaskCount: null };
|
return { task: null, originalSubtaskCount: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a subtask ID (e.g., "1.2")
|
// Check if it's a subtask ID (e.g., "1.2")
|
||||||
if (typeof taskId === "string" && taskId.includes(".")) {
|
if (typeof taskId === 'string' && taskId.includes('.')) {
|
||||||
// If looking for a subtask, statusFilter doesn't apply directly here.
|
// If looking for a subtask, statusFilter doesn't apply directly here.
|
||||||
const [parentId, subtaskId] = taskId
|
const [parentId, subtaskId] = taskId
|
||||||
.split(".")
|
.split('.')
|
||||||
.map((id) => parseInt(id, 10));
|
.map((id) => parseInt(id, 10));
|
||||||
const parentTask = tasks.find((t) => t.id === parentId);
|
const parentTask = tasks.find((t) => t.id === parentId);
|
||||||
|
|
||||||
@@ -970,7 +973,7 @@ function findTaskById(
|
|||||||
subtask.parentTask = {
|
subtask.parentTask = {
|
||||||
id: parentTask.id,
|
id: parentTask.id,
|
||||||
title: parentTask.title,
|
title: parentTask.title,
|
||||||
status: parentTask.status,
|
status: parentTask.status
|
||||||
};
|
};
|
||||||
subtask.isSubtask = true;
|
subtask.isSubtask = true;
|
||||||
}
|
}
|
||||||
@@ -983,7 +986,7 @@ function findTaskById(
|
|||||||
return {
|
return {
|
||||||
task: subtask || null,
|
task: subtask || null,
|
||||||
originalSubtaskCount: null,
|
originalSubtaskCount: null,
|
||||||
originalSubtasks: null,
|
originalSubtasks: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1013,7 +1016,7 @@ function findTaskById(
|
|||||||
filteredTask.subtasks = task.subtasks.filter(
|
filteredTask.subtasks = task.subtasks.filter(
|
||||||
(subtask) =>
|
(subtask) =>
|
||||||
subtask.status &&
|
subtask.status &&
|
||||||
subtask.status.toLowerCase() === statusFilter.toLowerCase(),
|
subtask.status.toLowerCase() === statusFilter.toLowerCase()
|
||||||
);
|
);
|
||||||
|
|
||||||
taskResult = filteredTask;
|
taskResult = filteredTask;
|
||||||
@@ -1050,7 +1053,7 @@ function truncate(text, maxLength) {
|
|||||||
function isEmpty(value) {
|
function isEmpty(value) {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.length === 0;
|
return value.length === 0;
|
||||||
} else if (typeof value === "object" && value !== null) {
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
return Object.keys(value).length === 0;
|
return Object.keys(value).length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1070,7 +1073,7 @@ function findCycles(
|
|||||||
dependencyMap,
|
dependencyMap,
|
||||||
visited = new Set(),
|
visited = new Set(),
|
||||||
recursionStack = new Set(),
|
recursionStack = new Set(),
|
||||||
path = [],
|
path = []
|
||||||
) {
|
) {
|
||||||
// Mark the current node as visited and part of recursion stack
|
// Mark the current node as visited and part of recursion stack
|
||||||
visited.add(subtaskId);
|
visited.add(subtaskId);
|
||||||
@@ -1087,7 +1090,7 @@ function findCycles(
|
|||||||
// If not visited, recursively check for cycles
|
// If not visited, recursively check for cycles
|
||||||
if (!visited.has(depId)) {
|
if (!visited.has(depId)) {
|
||||||
const cycles = findCycles(depId, dependencyMap, visited, recursionStack, [
|
const cycles = findCycles(depId, dependencyMap, visited, recursionStack, [
|
||||||
...path,
|
...path
|
||||||
]);
|
]);
|
||||||
cyclesToBreak.push(...cycles);
|
cyclesToBreak.push(...cycles);
|
||||||
}
|
}
|
||||||
@@ -1116,21 +1119,21 @@ function findCycles(
|
|||||||
const toKebabCase = (str) => {
|
const toKebabCase = (str) => {
|
||||||
// Special handling for common acronyms
|
// Special handling for common acronyms
|
||||||
const withReplacedAcronyms = str
|
const withReplacedAcronyms = str
|
||||||
.replace(/ID/g, "Id")
|
.replace(/ID/g, 'Id')
|
||||||
.replace(/API/g, "Api")
|
.replace(/API/g, 'Api')
|
||||||
.replace(/UI/g, "Ui")
|
.replace(/UI/g, 'Ui')
|
||||||
.replace(/URL/g, "Url")
|
.replace(/URL/g, 'Url')
|
||||||
.replace(/URI/g, "Uri")
|
.replace(/URI/g, 'Uri')
|
||||||
.replace(/JSON/g, "Json")
|
.replace(/JSON/g, 'Json')
|
||||||
.replace(/XML/g, "Xml")
|
.replace(/XML/g, 'Xml')
|
||||||
.replace(/HTML/g, "Html")
|
.replace(/HTML/g, 'Html')
|
||||||
.replace(/CSS/g, "Css");
|
.replace(/CSS/g, 'Css');
|
||||||
|
|
||||||
// Insert hyphens before capital letters and convert to lowercase
|
// Insert hyphens before capital letters and convert to lowercase
|
||||||
return withReplacedAcronyms
|
return withReplacedAcronyms
|
||||||
.replace(/([A-Z])/g, "-$1")
|
.replace(/([A-Z])/g, '-$1')
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/^-/, ""); // Remove leading hyphen if present
|
.replace(/^-/, ''); // Remove leading hyphen if present
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1141,11 +1144,11 @@ const toKebabCase = (str) => {
|
|||||||
function detectCamelCaseFlags(args) {
|
function detectCamelCaseFlags(args) {
|
||||||
const camelCaseFlags = [];
|
const camelCaseFlags = [];
|
||||||
for (const arg of args) {
|
for (const arg of args) {
|
||||||
if (arg.startsWith("--")) {
|
if (arg.startsWith('--')) {
|
||||||
const flagName = arg.split("=")[0].slice(2); // Remove -- and anything after =
|
const flagName = arg.split('=')[0].slice(2); // Remove -- and anything after =
|
||||||
|
|
||||||
// Skip single-word flags - they can't be camelCase
|
// Skip single-word flags - they can't be camelCase
|
||||||
if (!flagName.includes("-") && !/[A-Z]/.test(flagName)) {
|
if (!flagName.includes('-') && !/[A-Z]/.test(flagName)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1155,7 +1158,7 @@ function detectCamelCaseFlags(args) {
|
|||||||
if (kebabVersion !== flagName) {
|
if (kebabVersion !== flagName) {
|
||||||
camelCaseFlags.push({
|
camelCaseFlags.push({
|
||||||
original: flagName,
|
original: flagName,
|
||||||
kebabCase: kebabVersion,
|
kebabCase: kebabVersion
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1179,13 +1182,13 @@ function aggregateTelemetry(telemetryArray, overallCommandName) {
|
|||||||
timestamp: new Date().toISOString(), // Use current time for aggregation time
|
timestamp: new Date().toISOString(), // Use current time for aggregation time
|
||||||
userId: telemetryArray[0].userId, // Assume userId is consistent
|
userId: telemetryArray[0].userId, // Assume userId is consistent
|
||||||
commandName: overallCommandName,
|
commandName: overallCommandName,
|
||||||
modelUsed: "Multiple", // Default if models vary
|
modelUsed: 'Multiple', // Default if models vary
|
||||||
providerName: "Multiple", // Default if providers vary
|
providerName: 'Multiple', // Default if providers vary
|
||||||
inputTokens: 0,
|
inputTokens: 0,
|
||||||
outputTokens: 0,
|
outputTokens: 0,
|
||||||
totalTokens: 0,
|
totalTokens: 0,
|
||||||
totalCost: 0,
|
totalCost: 0,
|
||||||
currency: telemetryArray[0].currency || "USD", // Assume consistent currency or default
|
currency: telemetryArray[0].currency || 'USD' // Assume consistent currency or default
|
||||||
};
|
};
|
||||||
|
|
||||||
const uniqueModels = new Set();
|
const uniqueModels = new Set();
|
||||||
@@ -1198,7 +1201,7 @@ function aggregateTelemetry(telemetryArray, overallCommandName) {
|
|||||||
aggregated.totalCost += item.totalCost || 0;
|
aggregated.totalCost += item.totalCost || 0;
|
||||||
uniqueModels.add(item.modelUsed);
|
uniqueModels.add(item.modelUsed);
|
||||||
uniqueProviders.add(item.providerName);
|
uniqueProviders.add(item.providerName);
|
||||||
uniqueCurrencies.add(item.currency || "USD");
|
uniqueCurrencies.add(item.currency || 'USD');
|
||||||
});
|
});
|
||||||
|
|
||||||
aggregated.totalTokens = aggregated.inputTokens + aggregated.outputTokens;
|
aggregated.totalTokens = aggregated.inputTokens + aggregated.outputTokens;
|
||||||
@@ -1211,7 +1214,7 @@ function aggregateTelemetry(telemetryArray, overallCommandName) {
|
|||||||
aggregated.providerName = [...uniqueProviders][0];
|
aggregated.providerName = [...uniqueProviders][0];
|
||||||
}
|
}
|
||||||
if (uniqueCurrencies.size > 1) {
|
if (uniqueCurrencies.size > 1) {
|
||||||
aggregated.currency = "Multiple"; // Mark if currencies actually differ
|
aggregated.currency = 'Multiple'; // Mark if currencies actually differ
|
||||||
} else if (uniqueCurrencies.size === 1) {
|
} else if (uniqueCurrencies.size === 1) {
|
||||||
aggregated.currency = [...uniqueCurrencies][0];
|
aggregated.currency = [...uniqueCurrencies][0];
|
||||||
}
|
}
|
||||||
@@ -1227,14 +1230,14 @@ function aggregateTelemetry(telemetryArray, overallCommandName) {
|
|||||||
*/
|
*/
|
||||||
function getCurrentTag(projectRoot) {
|
function getCurrentTag(projectRoot) {
|
||||||
if (!projectRoot) {
|
if (!projectRoot) {
|
||||||
throw new Error("projectRoot is required for getCurrentTag");
|
throw new Error('projectRoot is required for getCurrentTag');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Try to read current tag from state.json using fs directly
|
// Try to read current tag from state.json using fs directly
|
||||||
const statePath = path.join(projectRoot, ".taskmaster", "state.json");
|
const statePath = path.join(projectRoot, '.taskmaster', 'state.json');
|
||||||
if (fs.existsSync(statePath)) {
|
if (fs.existsSync(statePath)) {
|
||||||
const rawState = fs.readFileSync(statePath, "utf8");
|
const rawState = fs.readFileSync(statePath, 'utf8');
|
||||||
const stateData = JSON.parse(rawState);
|
const stateData = JSON.parse(rawState);
|
||||||
if (stateData && stateData.currentTag) {
|
if (stateData && stateData.currentTag) {
|
||||||
return stateData.currentTag;
|
return stateData.currentTag;
|
||||||
@@ -1246,9 +1249,9 @@ function getCurrentTag(projectRoot) {
|
|||||||
|
|
||||||
// Fall back to defaultTag from config using fs directly
|
// Fall back to defaultTag from config using fs directly
|
||||||
try {
|
try {
|
||||||
const configPath = path.join(projectRoot, ".taskmaster", "config.json");
|
const configPath = path.join(projectRoot, '.taskmaster', 'config.json');
|
||||||
if (fs.existsSync(configPath)) {
|
if (fs.existsSync(configPath)) {
|
||||||
const rawConfig = fs.readFileSync(configPath, "utf8");
|
const rawConfig = fs.readFileSync(configPath, 'utf8');
|
||||||
const configData = JSON.parse(rawConfig);
|
const configData = JSON.parse(rawConfig);
|
||||||
if (configData && configData.global && configData.global.defaultTag) {
|
if (configData && configData.global && configData.global.defaultTag) {
|
||||||
return configData.global.defaultTag;
|
return configData.global.defaultTag;
|
||||||
@@ -1259,7 +1262,7 @@ function getCurrentTag(projectRoot) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Final fallback
|
// Final fallback
|
||||||
return "master";
|
return 'master';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1273,7 +1276,7 @@ function resolveTag(options = {}) {
|
|||||||
const { projectRoot, tag } = options;
|
const { projectRoot, tag } = options;
|
||||||
|
|
||||||
if (!projectRoot) {
|
if (!projectRoot) {
|
||||||
throw new Error("projectRoot is required for resolveTag");
|
throw new Error('projectRoot is required for resolveTag');
|
||||||
}
|
}
|
||||||
|
|
||||||
// If explicit tag provided, use it
|
// If explicit tag provided, use it
|
||||||
@@ -1341,7 +1344,7 @@ function flattenTasksWithSubtasks(tasks) {
|
|||||||
flattened.push({
|
flattened.push({
|
||||||
...task,
|
...task,
|
||||||
searchableId: task.id.toString(), // For consistent ID handling
|
searchableId: task.id.toString(), // For consistent ID handling
|
||||||
isSubtask: false,
|
isSubtask: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add subtasks if they exist
|
// Add subtasks if they exist
|
||||||
@@ -1355,7 +1358,7 @@ function flattenTasksWithSubtasks(tasks) {
|
|||||||
parentTitle: task.title,
|
parentTitle: task.title,
|
||||||
// Enhance subtask context with parent information
|
// Enhance subtask context with parent information
|
||||||
title: `${subtask.title} (subtask of: ${task.title})`,
|
title: `${subtask.title} (subtask of: ${task.title})`,
|
||||||
description: `${subtask.description} [Parent: ${task.description}]`,
|
description: `${subtask.description} [Parent: ${task.description}]`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1373,8 +1376,8 @@ function flattenTasksWithSubtasks(tasks) {
|
|||||||
* @returns {Object} The updated tag object (for chaining)
|
* @returns {Object} The updated tag object (for chaining)
|
||||||
*/
|
*/
|
||||||
function ensureTagMetadata(tagObj, opts = {}) {
|
function ensureTagMetadata(tagObj, opts = {}) {
|
||||||
if (!tagObj || typeof tagObj !== "object") {
|
if (!tagObj || typeof tagObj !== 'object') {
|
||||||
throw new Error("tagObj must be a valid object");
|
throw new Error('tagObj must be a valid object');
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
@@ -1384,7 +1387,7 @@ function ensureTagMetadata(tagObj, opts = {}) {
|
|||||||
tagObj.metadata = {
|
tagObj.metadata = {
|
||||||
created: now,
|
created: now,
|
||||||
updated: now,
|
updated: now,
|
||||||
...(opts.description ? { description: opts.description } : {}),
|
...(opts.description ? { description: opts.description } : {})
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// Ensure existing metadata has required fields
|
// Ensure existing metadata has required fields
|
||||||
@@ -1442,5 +1445,5 @@ export {
|
|||||||
createStateJson,
|
createStateJson,
|
||||||
markMigrationForNotice,
|
markMigrationForNotice,
|
||||||
flattenTasksWithSubtasks,
|
flattenTasksWithSubtasks,
|
||||||
ensureTagMetadata,
|
ensureTagMetadata
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user