@e0ipso/ai-task-manager 1.9.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e0ipso/ai-task-manager",
3
- "version": "1.9.1",
3
+ "version": "1.10.0",
4
4
  "description": "Task management for AI coding assistants",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -3,64 +3,233 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
+ // Enable debug logging via environment variable
7
+ const DEBUG = process.env.DEBUG === 'true';
8
+
9
+ /**
10
+ * Debug logging utility
11
+ * @param {string} message - Debug message
12
+ * @param {...any} args - Additional arguments to log
13
+ */
14
+ function debugLog(message, ...args) {
15
+ if (DEBUG) {
16
+ console.error(`[DEBUG] ${message}`, ...args);
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Error logging utility
22
+ * @param {string} message - Error message
23
+ * @param {...any} args - Additional arguments to log
24
+ */
25
+ function errorLog(message, ...args) {
26
+ console.error(`[ERROR] ${message}`, ...args);
27
+ }
28
+
6
29
  /**
7
- * Find the task manager root directory by traversing up from CWD
30
+ * Find the task manager root directory by traversing up from current working directory
8
31
  * @returns {string|null} Path to task manager root or null if not found
9
32
  */
10
33
  function findTaskManagerRoot() {
34
+ // Start from the actual current working directory, not process.cwd()
35
+ // This ensures we start from the correct context where the script is being executed
11
36
  let currentPath = process.cwd();
12
- const root = path.parse(currentPath).root;
37
+ const filesystemRoot = path.parse(currentPath).root;
38
+
39
+ debugLog(`Starting search for task manager root from: ${currentPath}`);
40
+ debugLog(`Filesystem root: ${filesystemRoot}`);
41
+
42
+ // Traverse upward through parent directories until we reach the filesystem root
43
+ while (currentPath !== filesystemRoot) {
44
+ const taskManagerPlansPath = path.join(currentPath, '.ai', 'task-manager', 'plans');
45
+ debugLog(`Checking for task manager at: ${taskManagerPlansPath}`);
46
+
47
+ try {
48
+ // Check if this is a valid task manager directory
49
+ if (fs.existsSync(taskManagerPlansPath)) {
50
+ // Verify it's a directory, not a file
51
+ const stats = fs.lstatSync(taskManagerPlansPath);
52
+ if (stats.isDirectory()) {
53
+ const taskManagerRoot = path.join(currentPath, '.ai', 'task-manager');
54
+ debugLog(`Found valid task manager root at: ${taskManagerRoot}`);
55
+ return taskManagerRoot;
56
+ } else {
57
+ debugLog(`Path exists but is not a directory: ${taskManagerPlansPath}`);
58
+ }
59
+ } else {
60
+ debugLog(`Task manager path does not exist: ${taskManagerPlansPath}`);
61
+ }
62
+ } catch (err) {
63
+ // Handle permission errors or other filesystem issues gracefully
64
+ // Continue searching in parent directories
65
+ if (err.code === 'EPERM' || err.code === 'EACCES') {
66
+ const warningMsg = `Warning: Permission denied accessing ${taskManagerPlansPath}`;
67
+ console.warn(warningMsg);
68
+ debugLog(`Permission error: ${err.message}`);
69
+ } else {
70
+ debugLog(`Filesystem error checking ${taskManagerPlansPath}: ${err.message}`);
71
+ }
72
+ }
73
+
74
+ // Move up to parent directory
75
+ const parentPath = path.dirname(currentPath);
13
76
 
14
- while (currentPath !== root) {
15
- const taskManagerPath = path.join(currentPath, '.ai', 'task-manager', 'plans');
16
- if (fs.existsSync(taskManagerPath)) {
17
- return path.join(currentPath, '.ai', 'task-manager');
77
+ // Safety check: if path.dirname returns the same path, we've reached the root
78
+ if (parentPath === currentPath) {
79
+ debugLog(`Reached filesystem root, stopping traversal`);
80
+ break;
18
81
  }
19
- currentPath = path.dirname(currentPath);
82
+
83
+ currentPath = parentPath;
84
+ debugLog(`Moving up to parent directory: ${currentPath}`);
20
85
  }
21
86
 
22
- // Check root directory as well
23
- const rootTaskManager = path.join(root, '.ai', 'task-manager', 'plans');
24
- if (fs.existsSync(rootTaskManager)) {
25
- return path.join(root, '.ai', 'task-manager');
87
+ // Check the filesystem root as the final attempt
88
+ try {
89
+ const rootTaskManagerPlans = path.join(filesystemRoot, '.ai', 'task-manager', 'plans');
90
+ debugLog(`Final check at filesystem root: ${rootTaskManagerPlans}`);
91
+
92
+ if (fs.existsSync(rootTaskManagerPlans)) {
93
+ const stats = fs.lstatSync(rootTaskManagerPlans);
94
+ if (stats.isDirectory()) {
95
+ const taskManagerRoot = path.join(filesystemRoot, '.ai', 'task-manager');
96
+ debugLog(`Found task manager root at filesystem root: ${taskManagerRoot}`);
97
+ return taskManagerRoot;
98
+ }
99
+ }
100
+ } catch (err) {
101
+ debugLog(`Error checking filesystem root: ${err.message}`);
26
102
  }
27
103
 
104
+ debugLog(`Task manager root not found in any parent directory`);
28
105
  return null;
29
106
  }
30
107
 
31
108
  /**
32
- * Parse YAML frontmatter for ID with resilience to different formats
109
+ * Parse YAML frontmatter for ID with comprehensive error handling and debug logging
33
110
  * @param {string} content - File content
111
+ * @param {string} [filePath] - Optional file path for error context
34
112
  * @returns {number|null} Extracted ID or null
35
113
  */
36
- function extractIdFromFrontmatter(content) {
37
- const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
38
- if (!frontmatterMatch) return null;
114
+ function extractIdFromFrontmatter(content, filePath = 'unknown') {
115
+ debugLog(`Attempting to extract ID from frontmatter in: ${filePath}`);
116
+
117
+ // Check for frontmatter block existence
118
+ const frontmatterMatch = content.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
119
+ if (!frontmatterMatch) {
120
+ debugLog(`No frontmatter block found in: ${filePath}`);
121
+ return null;
122
+ }
39
123
 
40
124
  const frontmatterText = frontmatterMatch[1];
125
+ debugLog(`Found frontmatter block in ${filePath}:\n${frontmatterText}`);
41
126
 
42
- // Handle various YAML formats for id field using regex:
43
- // id: 5
44
- // id: "5"
45
- // id: '5'
46
- // "id": 5
47
- // 'id': 5
48
- // id : 5 (with spaces)
127
+ // Enhanced patterns to handle various YAML formats and edge cases:
128
+ // - id: 5 (simple numeric)
129
+ // - id: "5" (double quoted)
130
+ // - id: '5' (single quoted)
131
+ // - "id": 5 (quoted key)
132
+ // - 'id': 5 (single quoted key)
133
+ // - id : 5 (extra spaces)
134
+ // - id: 05 (zero-padded)
135
+ // - id: +5 (explicit positive)
136
+ // - Mixed quotes: 'id': "5" (different quote types)
49
137
  const patterns = [
50
- /^\s*["']?id["']?\s*:\s*["']?(\d+)["']?\s*$/m, // Most flexible pattern
51
- /^\s*id\s*:\s*(\d+)\s*$/m, // Simple numeric
52
- /^\s*id\s*:\s*"(\d+)"\s*$/m, // Double quoted
53
- /^\s*id\s*:\s*'(\d+)'\s*$/m, // Single quoted
138
+ // Most flexible pattern - handles quoted/unquoted keys and values with optional spaces
139
+ {
140
+ regex: /^\s*["']?id["']?\s*:\s*["']?([+-]?\d+)["']?\s*(?:#.*)?$/mi,
141
+ description: 'Flexible pattern with optional quotes and comments'
142
+ },
143
+ // Simple numeric with optional whitespace and comments
144
+ {
145
+ regex: /^\s*id\s*:\s*([+-]?\d+)\s*(?:#.*)?$/mi,
146
+ description: 'Simple numeric with optional comments'
147
+ },
148
+ // Double quoted values
149
+ {
150
+ regex: /^\s*["']?id["']?\s*:\s*"([+-]?\d+)"\s*(?:#.*)?$/mi,
151
+ description: 'Double quoted values'
152
+ },
153
+ // Single quoted values
154
+ {
155
+ regex: /^\s*["']?id["']?\s*:\s*'([+-]?\d+)'\s*(?:#.*)?$/mi,
156
+ description: 'Single quoted values'
157
+ },
158
+ // Mixed quotes - quoted key, unquoted value
159
+ {
160
+ regex: /^\s*["']id["']\s*:\s*([+-]?\d+)\s*(?:#.*)?$/mi,
161
+ description: 'Quoted key, unquoted value'
162
+ },
163
+ // YAML-style with pipe or greater-than indicators (edge case)
164
+ {
165
+ regex: /^\s*id\s*:\s*[|>]\s*([+-]?\d+)\s*$/mi,
166
+ description: 'YAML block scalar indicators'
167
+ }
54
168
  ];
55
169
 
56
- for (const pattern of patterns) {
57
- const match = frontmatterText.match(pattern);
170
+ // Try each pattern in order
171
+ for (let i = 0; i < patterns.length; i++) {
172
+ const { regex, description } = patterns[i];
173
+ debugLog(`Trying pattern ${i + 1} (${description}) on ${filePath}`);
174
+
175
+ const match = frontmatterText.match(regex);
58
176
  if (match) {
59
- const id = parseInt(match[1], 10);
60
- if (!isNaN(id)) return id;
177
+ debugLog(`Pattern ${i + 1} matched in ${filePath}: "${match[0].trim()}"`);
178
+
179
+ const rawId = match[1];
180
+ const id = parseInt(rawId, 10);
181
+
182
+ // Validate the parsed ID
183
+ if (isNaN(id)) {
184
+ errorLog(`Invalid ID value "${rawId}" in ${filePath} - not a valid number`);
185
+ continue;
186
+ }
187
+
188
+ if (id < 0) {
189
+ errorLog(`Invalid ID value ${id} in ${filePath} - ID must be non-negative`);
190
+ continue;
191
+ }
192
+
193
+ if (id > Number.MAX_SAFE_INTEGER) {
194
+ errorLog(`Invalid ID value ${id} in ${filePath} - ID exceeds maximum safe integer`);
195
+ continue;
196
+ }
197
+
198
+ debugLog(`Successfully extracted ID ${id} from ${filePath}`);
199
+ return id;
200
+ } else {
201
+ debugLog(`Pattern ${i + 1} did not match in ${filePath}`);
202
+ }
203
+ }
204
+
205
+ // If no patterns matched, try to identify common issues
206
+ debugLog(`All patterns failed for ${filePath}. Analyzing frontmatter for common issues...`);
207
+
208
+ // Check for 'id' field existence (case-insensitive)
209
+ const hasIdField = /^\s*["']?id["']?\s*:/mi.test(frontmatterText);
210
+ if (!hasIdField) {
211
+ debugLog(`No 'id' field found in frontmatter of ${filePath}`);
212
+ } else {
213
+ // ID field exists but didn't match - might be malformed
214
+ const idLineMatch = frontmatterText.match(/^\s*["']?id["']?\s*:.*$/mi);
215
+ if (idLineMatch) {
216
+ const idLine = idLineMatch[0].trim();
217
+ errorLog(`Found malformed ID line in ${filePath}: "${idLine}"`);
218
+
219
+ // Check for common formatting issues
220
+ if (idLine.includes('null') || idLine.includes('undefined')) {
221
+ errorLog(`ID field has null/undefined value in ${filePath}`);
222
+ } else if (idLine.match(/:\s*$/)) {
223
+ errorLog(`ID field has missing value in ${filePath}`);
224
+ } else if (idLine.includes('[') || idLine.includes('{')) {
225
+ errorLog(`ID field appears to be array/object instead of number in ${filePath}`);
226
+ } else {
227
+ errorLog(`ID field has unrecognized format in ${filePath}`);
228
+ }
61
229
  }
62
230
  }
63
231
 
232
+ errorLog(`Failed to extract ID from frontmatter in ${filePath}`);
64
233
  return null;
65
234
  }
66
235
 
@@ -72,52 +241,190 @@ function getNextPlanId() {
72
241
  const taskManagerRoot = findTaskManagerRoot();
73
242
 
74
243
  if (!taskManagerRoot) {
75
- console.error('Error: No .ai/task-manager/plans directory found in current directory or any parent directory.');
76
- console.error('Please ensure you are in a project with task manager initialized.');
244
+ errorLog('No .ai/task-manager/plans directory found in current directory or any parent directory.');
245
+ errorLog('');
246
+ errorLog('Please ensure you are in a project with task manager initialized, or navigate to the correct');
247
+ errorLog('project directory. The task manager looks for the .ai/task-manager/plans structure starting');
248
+ errorLog('from the current working directory and traversing upward through parent directories.');
249
+ errorLog('');
250
+ errorLog(`Current working directory: ${process.cwd()}`);
77
251
  process.exit(1);
78
252
  }
79
253
 
254
+ debugLog(`Task manager root found: ${taskManagerRoot}`);
255
+
80
256
  const plansDir = path.join(taskManagerRoot, 'plans');
81
257
  const archiveDir = path.join(taskManagerRoot, 'archive');
82
258
 
259
+ debugLog(`Scanning directories: ${plansDir}, ${archiveDir}`);
260
+
83
261
  let maxId = 0;
262
+ let filesScanned = 0;
263
+ let errorsEncountered = 0;
84
264
 
85
265
  // Scan both plans and archive directories
86
266
  [plansDir, archiveDir].forEach(dir => {
87
- if (!fs.existsSync(dir)) return;
267
+ const dirName = path.basename(dir);
268
+ debugLog(`Scanning directory: ${dir}`);
269
+
270
+ if (!fs.existsSync(dir)) {
271
+ debugLog(`Directory does not exist: ${dir}`);
272
+ return;
273
+ }
88
274
 
89
275
  try {
90
276
  const entries = fs.readdirSync(dir, { withFileTypes: true });
277
+ debugLog(`Found ${entries.length} entries in ${dir}`);
91
278
 
92
279
  entries.forEach(entry => {
93
- if (entry.isFile() && entry.name.match(/^plan-\d+--.*\.md$/)) {
280
+ if (entry.isDirectory() && entry.name.match(/^\d+--/)) {
281
+ // This is a plan directory, look for plan files inside
282
+ const planDirPath = path.join(dir, entry.name);
283
+ debugLog(`Scanning plan directory: ${planDirPath}`);
284
+
285
+ try {
286
+ const planDirEntries = fs.readdirSync(planDirPath, { withFileTypes: true });
287
+
288
+ planDirEntries.forEach(planEntry => {
289
+ if (planEntry.isFile() && planEntry.name.match(/^plan-\d+--.*\.md$/)) {
290
+ filesScanned++;
291
+ const filePath = path.join(planDirPath, planEntry.name);
292
+ debugLog(`Processing plan file: ${filePath}`);
293
+
294
+ // Extract ID from directory name as primary source
295
+ const dirMatch = entry.name.match(/^(\d+)--/);
296
+ let dirId = null;
297
+ if (dirMatch) {
298
+ dirId = parseInt(dirMatch[1], 10);
299
+ if (!isNaN(dirId)) {
300
+ debugLog(`Extracted ID ${dirId} from directory name: ${entry.name}`);
301
+ if (dirId > maxId) {
302
+ maxId = dirId;
303
+ debugLog(`New max ID from directory name: ${maxId}`);
304
+ }
305
+ }
306
+ }
307
+
308
+ // Extract ID from filename as secondary source
309
+ const filenameMatch = planEntry.name.match(/^plan-(\d+)--/);
310
+ let filenameId = null;
311
+ if (filenameMatch) {
312
+ filenameId = parseInt(filenameMatch[1], 10);
313
+ if (!isNaN(filenameId)) {
314
+ debugLog(`Extracted ID ${filenameId} from filename: ${planEntry.name}`);
315
+ if (filenameId > maxId) {
316
+ maxId = filenameId;
317
+ debugLog(`New max ID from filename: ${maxId}`);
318
+ }
319
+ }
320
+ }
321
+
322
+ // Also check frontmatter for most reliable ID
323
+ try {
324
+ const content = fs.readFileSync(filePath, 'utf8');
325
+ const frontmatterId = extractIdFromFrontmatter(content, filePath);
326
+
327
+ if (frontmatterId !== null) {
328
+ debugLog(`Extracted ID ${frontmatterId} from frontmatter: ${filePath}`);
329
+ if (frontmatterId > maxId) {
330
+ maxId = frontmatterId;
331
+ debugLog(`New max ID from frontmatter: ${maxId}`);
332
+ }
333
+
334
+ // Validate consistency between all sources
335
+ if (dirId !== null && dirId !== frontmatterId) {
336
+ errorLog(`ID mismatch in ${filePath}: directory has ${dirId}, frontmatter has ${frontmatterId}`);
337
+ errorsEncountered++;
338
+ }
339
+ if (filenameId !== null && filenameId !== frontmatterId) {
340
+ errorLog(`ID mismatch in ${filePath}: filename has ${filenameId}, frontmatter has ${frontmatterId}`);
341
+ errorsEncountered++;
342
+ }
343
+ } else {
344
+ debugLog(`No ID found in frontmatter: ${filePath}`);
345
+ if (dirId === null && filenameId === null) {
346
+ errorLog(`No valid ID found in directory, filename, or frontmatter: ${filePath}`);
347
+ errorsEncountered++;
348
+ }
349
+ }
350
+ } catch (err) {
351
+ errorLog(`Failed to read file ${filePath}: ${err.message}`);
352
+ errorsEncountered++;
353
+ }
354
+ }
355
+ });
356
+ } catch (err) {
357
+ errorLog(`Failed to read plan directory ${planDirPath}: ${err.message}`);
358
+ errorsEncountered++;
359
+ }
360
+ } else if (entry.isFile() && entry.name.match(/^plan-\d+--.*\.md$/)) {
361
+ // Legacy: direct plan file in plans/archive directory (fallback for old format)
362
+ filesScanned++;
363
+ const filePath = path.join(dir, entry.name);
364
+ debugLog(`Processing legacy plan file: ${filePath}`);
365
+
94
366
  // Extract ID from filename as fallback
95
367
  const filenameMatch = entry.name.match(/^plan-(\d+)--/);
368
+ let filenameId = null;
96
369
  if (filenameMatch) {
97
- const id = parseInt(filenameMatch[1], 10);
98
- if (!isNaN(id) && id > maxId) maxId = id;
370
+ filenameId = parseInt(filenameMatch[1], 10);
371
+ if (!isNaN(filenameId)) {
372
+ debugLog(`Extracted ID ${filenameId} from legacy filename: ${entry.name}`);
373
+ if (filenameId > maxId) {
374
+ maxId = filenameId;
375
+ debugLog(`New max ID from legacy filename: ${maxId}`);
376
+ }
377
+ }
99
378
  }
100
379
 
101
380
  // Also check frontmatter for more reliable ID
102
381
  try {
103
- const filePath = path.join(dir, entry.name);
104
382
  const content = fs.readFileSync(filePath, 'utf8');
105
- const id = extractIdFromFrontmatter(content);
383
+ const frontmatterId = extractIdFromFrontmatter(content, filePath);
384
+
385
+ if (frontmatterId !== null) {
386
+ debugLog(`Extracted ID ${frontmatterId} from legacy frontmatter: ${filePath}`);
387
+ if (frontmatterId > maxId) {
388
+ maxId = frontmatterId;
389
+ debugLog(`New max ID from legacy frontmatter: ${maxId}`);
390
+ }
106
391
 
107
- if (id !== null && id > maxId) {
108
- maxId = id;
392
+ // Validate consistency between filename and frontmatter
393
+ if (filenameId !== null && filenameId !== frontmatterId) {
394
+ errorLog(`ID mismatch in legacy ${filePath}: filename has ${filenameId}, frontmatter has ${frontmatterId}`);
395
+ errorsEncountered++;
396
+ }
397
+ } else {
398
+ debugLog(`No ID found in legacy frontmatter: ${filePath}`);
399
+ if (filenameId === null) {
400
+ errorLog(`No valid ID found in legacy filename or frontmatter: ${filePath}`);
401
+ errorsEncountered++;
402
+ }
109
403
  }
110
404
  } catch (err) {
111
- // Skip corrupted files
405
+ errorLog(`Failed to read legacy file ${filePath}: ${err.message}`);
406
+ errorsEncountered++;
112
407
  }
408
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
409
+ debugLog(`Skipping non-plan file: ${entry.name}`);
410
+ } else if (entry.isDirectory()) {
411
+ debugLog(`Skipping non-plan directory: ${entry.name}`);
113
412
  }
114
413
  });
115
414
  } catch (err) {
116
- // Skip directories that can't be read
415
+ errorLog(`Failed to read directory ${dir}: ${err.message}`);
416
+ errorsEncountered++;
117
417
  }
118
418
  });
119
419
 
120
- return maxId + 1;
420
+ const nextId = maxId + 1;
421
+ debugLog(`Scan complete. Files scanned: ${filesScanned}, Errors: ${errorsEncountered}, Max ID found: ${maxId}, Next ID: ${nextId}`);
422
+
423
+ if (errorsEncountered > 0) {
424
+ errorLog(`Encountered ${errorsEncountered} errors during scan. Next ID calculation may be inaccurate.`);
425
+ }
426
+
427
+ return nextId;
121
428
  }
122
429
 
123
430
  // Output the next plan ID
@@ -4,6 +4,8 @@ description: Create a comprehensive plan to accomplish the request from the user
4
4
  ---
5
5
  # Comprehensive Plan Creation
6
6
 
7
+ Think harder and use tools.
8
+
7
9
  You are a comprehensive task planning assistant. Your role is to think hard to create detailed, actionable plans based on user input while ensuring you have all necessary context before proceeding.
8
10
 
9
11
  Include .ai/task-manager/config/TASK_MANAGER.md for the directory structure of tasks.
@@ -0,0 +1,39 @@
1
+ ---
2
+ argument-hint: [test-command]
3
+ description: Fix the tests your task execution broke.
4
+ ---
5
+ Fix all failing tests in this repository. Think harder and use tools.
6
+
7
+ Execute this command to run the tests:
8
+
9
+ <test-command>
10
+ $1
11
+ </test-command>
12
+
13
+ If the test command is empty, read CLAUDE.md for test execution instructions.
14
+
15
+ ⚠️ **CRITICAL INTEGRITY REQUIREMENT** ⚠️
16
+ You MUST fix the actual bugs in the source code. Green tests are worthless if achieved through cheating.
17
+
18
+ **This is CHEATING (absolutely forbidden):**
19
+ - Skipping tests with conditionals
20
+ - Modifying test assertions to pass
21
+ - Adding test-environment-specific code to source
22
+ - Disabling or commenting out tests
23
+ - ANY workaround that doesn't fix the real bug
24
+
25
+ **This is THE RIGHT WAY:**
26
+ - Find the root cause in the source code
27
+ - Fix the actual bug
28
+ - Ensure tests pass because the code truly works
29
+
30
+ **Process:**
31
+ 1. Run all tests to identify failures
32
+ 2. Fix EVERY failing test iteratively
33
+ 3. Verify all tests pass legitimately
34
+
35
+ DO NOT STOP after fixing some tests - fix ALL of them.
36
+
37
+ Remember: The entire point of tests is to ensure code robustness. If you cheat in ANY way, the tests become meaningless and I cannot trust that the code actually works.
38
+
39
+ If you get stuck and cannot fix a test properly, ask for help rather than resorting to shortcuts.
@@ -2,7 +2,11 @@
2
2
  argument-hint: [plan-ID]
3
3
  description: Generate tasks to implement the plan with the provided ID.
4
4
  ---
5
+
5
6
  # Comprehensive Task List Creation
7
+
8
+ Think harder and use tools.
9
+
6
10
  You are a comprehensive task planning assistant. Your role is to create detailed, actionable plans based on user input while ensuring you have all necessary context before proceeding.
7
11
 
8
12
  Include /TASK_MANAGER.md for the directory structure of tasks.