fix: normalize task IDs to numbers on load to fix comparison issues
When tasks.json contains string IDs (e.g., "5" instead of 5), task lookups fail because the code uses parseInt() and strict equality (===) for comparisons. This fix normalizes all task and subtask IDs to numbers when loading the JSON, ensuring consistent comparisons throughout the codebase without requiring changes to multiple comparison locations. Fixes task not found errors when using string IDs in tasks.json.
This commit is contained in:
committed by
Ralph Khreish
parent
45a14c323d
commit
b79eb4f7a2
@@ -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,49 +203,73 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +277,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;
|
||||||
@@ -282,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}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,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}`);
|
||||||
}
|
}
|
||||||
@@ -304,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`);
|
||||||
}
|
}
|
||||||
@@ -329,9 +353,9 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
|||||||
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
|
||||||
@@ -369,7 +393,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...`);
|
||||||
@@ -379,19 +403,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}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -414,7 +438,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 {
|
||||||
@@ -435,7 +459,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'
|
||||||
@@ -448,15 +472,17 @@ 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) {
|
||||||
|
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;
|
||||||
@@ -464,15 +490,17 @@ 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) {
|
||||||
|
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) {
|
||||||
@@ -481,8 +509,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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,14 +521,16 @@ 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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -524,26 +554,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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -554,7 +584,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;
|
||||||
|
|
||||||
@@ -565,20 +595,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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -591,18 +621,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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -615,7 +645,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)) {
|
||||||
@@ -624,24 +654,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}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -655,7 +685,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;
|
||||||
@@ -671,12 +701,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 = {
|
||||||
@@ -685,8 +715,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
|
||||||
@@ -703,30 +733,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
|
||||||
@@ -749,15 +779,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -798,7 +828,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;
|
||||||
@@ -806,19 +836,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;
|
||||||
}
|
}
|
||||||
@@ -870,9 +900,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);
|
||||||
|
|
||||||
@@ -893,11 +923,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -916,17 +946,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);
|
||||||
|
|
||||||
@@ -940,7 +970,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;
|
||||||
}
|
}
|
||||||
@@ -953,7 +983,7 @@ function findTaskById(
|
|||||||
return {
|
return {
|
||||||
task: subtask || null,
|
task: subtask || null,
|
||||||
originalSubtaskCount: null,
|
originalSubtaskCount: null,
|
||||||
originalSubtasks: null
|
originalSubtasks: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -983,7 +1013,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;
|
||||||
@@ -1020,7 +1050,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1040,7 +1070,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);
|
||||||
@@ -1057,7 +1087,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);
|
||||||
}
|
}
|
||||||
@@ -1086,21 +1116,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
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1111,11 +1141,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1125,7 +1155,7 @@ function detectCamelCaseFlags(args) {
|
|||||||
if (kebabVersion !== flagName) {
|
if (kebabVersion !== flagName) {
|
||||||
camelCaseFlags.push({
|
camelCaseFlags.push({
|
||||||
original: flagName,
|
original: flagName,
|
||||||
kebabCase: kebabVersion
|
kebabCase: kebabVersion,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1149,13 +1179,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();
|
||||||
@@ -1168,7 +1198,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;
|
||||||
@@ -1181,7 +1211,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];
|
||||||
}
|
}
|
||||||
@@ -1197,14 +1227,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;
|
||||||
@@ -1216,9 +1246,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;
|
||||||
@@ -1229,7 +1259,7 @@ function getCurrentTag(projectRoot) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Final fallback
|
// Final fallback
|
||||||
return 'master';
|
return "master";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1243,7 +1273,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
|
||||||
@@ -1311,7 +1341,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
|
||||||
@@ -1325,7 +1355,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}]`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1343,8 +1373,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();
|
||||||
@@ -1354,7 +1384,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
|
||||||
@@ -1412,5 +1442,5 @@ export {
|
|||||||
createStateJson,
|
createStateJson,
|
||||||
markMigrationForNotice,
|
markMigrationForNotice,
|
||||||
flattenTasksWithSubtasks,
|
flattenTasksWithSubtasks,
|
||||||
ensureTagMetadata
|
ensureTagMetadata,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user