@e0ipso/ai-task-manager 1.26.1 → 1.26.3
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 +6 -6
- package/templates/ai-task-manager/config/hooks/PRE_PLAN.md +1 -9
- package/templates/ai-task-manager/config/scripts/check-task-dependencies.cjs +2 -28
- package/templates/ai-task-manager/config/scripts/get-next-plan-id.cjs +5 -350
- package/templates/ai-task-manager/config/scripts/get-next-task-id.cjs +2 -62
- package/templates/ai-task-manager/config/scripts/shared-utils.cjs +171 -0
- package/templates/ai-task-manager/config/scripts/validate-plan-blueprint.cjs +1 -88
- package/templates/ai-task-manager/config/templates/PLAN_TEMPLATE.md +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@e0ipso/ai-task-manager",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.3",
|
|
4
4
|
"description": "Task management for AI coding assistants",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"access": "public"
|
|
38
38
|
},
|
|
39
39
|
"engines": {
|
|
40
|
-
"node": ">=
|
|
40
|
+
"node": ">=22.14.0"
|
|
41
41
|
},
|
|
42
42
|
"files": [
|
|
43
43
|
"dist/",
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
"@semantic-release/changelog": "^6.0.3",
|
|
68
68
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
69
69
|
"@semantic-release/git": "^10.0.1",
|
|
70
|
-
"@semantic-release/github": "^
|
|
71
|
-
"@semantic-release/npm": "^
|
|
72
|
-
"@semantic-release/release-notes-generator": "^
|
|
70
|
+
"@semantic-release/github": "^12.0.2",
|
|
71
|
+
"@semantic-release/npm": "^13.1.3",
|
|
72
|
+
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
73
73
|
"@types/diff": "^5.2.3",
|
|
74
74
|
"@types/fs-extra": "^11.0.4",
|
|
75
75
|
"@types/inquirer": "^9.0.9",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"husky": "^9.1.7",
|
|
84
84
|
"jest": "^30.1.2",
|
|
85
85
|
"prettier": "^3.6.2",
|
|
86
|
-
"semantic-release": "^
|
|
86
|
+
"semantic-release": "^25.0.2",
|
|
87
87
|
"ts-jest": "^29.4.1",
|
|
88
88
|
"ts-node": "^10.9.2",
|
|
89
89
|
"typescript": "^5.9.2"
|
|
@@ -2,14 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
This hook provides pre-planning guidance to ensure scope control, simplicity principles, and proper validation requirements are established before comprehensive plan creation.
|
|
4
4
|
|
|
5
|
-
<details>
|
|
6
|
-
<summary>
|
|
7
|
-
[IMPORTANT] Only for assistants that support assistant skills: Claude
|
|
8
|
-
</summary>
|
|
9
|
-
|
|
10
|
-
Analyze the current prompt in order to engage any relevant skills as necessary (either global or project skills).
|
|
11
|
-
</details>
|
|
12
|
-
|
|
13
5
|
## Scope Control Guidelines
|
|
14
6
|
|
|
15
7
|
**Critical: Implement ONLY what is explicitly requested**
|
|
@@ -48,4 +40,4 @@ Analyze the current prompt in order to engage any relevant skills as necessary (
|
|
|
48
40
|
- Prioritize accuracy over speed
|
|
49
41
|
- Consider both technical and non-technical aspects
|
|
50
42
|
- Use the plan template in .ai/task-manager/config/templates/PLAN_TEMPLATE.md
|
|
51
|
-
- DO NOT create or list any tasks or phases during the plan creation. This will be done in a later step. Stick to writing the PRD (Project Requirements Document).
|
|
43
|
+
- DO NOT create or list any tasks or phases during the plan creation. This will be done in a later step. Stick to writing the PRD (Project Requirements Document).
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
const fs = require('fs-extra');
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const { execSync } = require('child_process');
|
|
13
|
+
const { parseFrontmatter } = require('./shared-utils.cjs');
|
|
13
14
|
|
|
14
15
|
// Chalk instance - loaded dynamically to handle ESM module
|
|
15
16
|
let chalkInstance = null;
|
|
@@ -149,33 +150,6 @@ const findTaskFile = (planDir, taskId) => {
|
|
|
149
150
|
return null;
|
|
150
151
|
};
|
|
151
152
|
|
|
152
|
-
// Function to parse YAML frontmatter
|
|
153
|
-
const parseFrontmatter = (content) => {
|
|
154
|
-
const lines = content.split('\n');
|
|
155
|
-
let inFrontmatter = false;
|
|
156
|
-
let frontmatterEnd = false;
|
|
157
|
-
let delimiterCount = 0;
|
|
158
|
-
const frontmatterLines = [];
|
|
159
|
-
|
|
160
|
-
for (const line of lines) {
|
|
161
|
-
if (line.trim() === '---') {
|
|
162
|
-
delimiterCount++;
|
|
163
|
-
if (delimiterCount === 1) {
|
|
164
|
-
inFrontmatter = true;
|
|
165
|
-
continue;
|
|
166
|
-
} else if (delimiterCount === 2) {
|
|
167
|
-
frontmatterEnd = true;
|
|
168
|
-
break;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (inFrontmatter && !frontmatterEnd) {
|
|
173
|
-
frontmatterLines.push(line);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return frontmatterLines.join('\n');
|
|
178
|
-
};
|
|
179
153
|
|
|
180
154
|
// Function to extract dependencies from frontmatter
|
|
181
155
|
const extractDependencies = (frontmatter) => {
|
|
@@ -354,4 +328,4 @@ if (require.main === module) {
|
|
|
354
328
|
});
|
|
355
329
|
}
|
|
356
330
|
|
|
357
|
-
module.exports = { main };
|
|
331
|
+
module.exports = { main };
|
|
@@ -2,20 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
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
|
-
}
|
|
5
|
+
const { findTaskManagerRoot, extractIdFromFrontmatter } = require('./shared-utils.cjs');
|
|
19
6
|
|
|
20
7
|
/**
|
|
21
8
|
* Error logging utility
|
|
@@ -26,213 +13,6 @@ function errorLog(message, ...args) {
|
|
|
26
13
|
console.error(`[ERROR] ${message}`, ...args);
|
|
27
14
|
}
|
|
28
15
|
|
|
29
|
-
/**
|
|
30
|
-
* Find the task manager root directory by traversing up from current working directory
|
|
31
|
-
* @returns {string|null} Path to task manager root or null if not found
|
|
32
|
-
*/
|
|
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
|
|
36
|
-
let currentPath = process.cwd();
|
|
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);
|
|
76
|
-
|
|
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;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
currentPath = parentPath;
|
|
84
|
-
debugLog(`Moving up to parent directory: ${currentPath}`);
|
|
85
|
-
}
|
|
86
|
-
|
|
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}`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
debugLog(`Task manager root not found in any parent directory`);
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Parse YAML frontmatter for ID with comprehensive error handling and debug logging
|
|
110
|
-
* @param {string} content - File content
|
|
111
|
-
* @param {string} [filePath] - Optional file path for error context
|
|
112
|
-
* @returns {number|null} Extracted ID or null
|
|
113
|
-
*/
|
|
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
|
-
}
|
|
123
|
-
|
|
124
|
-
const frontmatterText = frontmatterMatch[1];
|
|
125
|
-
debugLog(`Found frontmatter block in ${filePath}:\n${frontmatterText}`);
|
|
126
|
-
|
|
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)
|
|
137
|
-
const patterns = [
|
|
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
|
-
}
|
|
168
|
-
];
|
|
169
|
-
|
|
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);
|
|
176
|
-
if (match) {
|
|
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
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
errorLog(`Failed to extract ID from frontmatter in ${filePath}`);
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
16
|
/**
|
|
237
17
|
* Get the next available plan ID by scanning existing plan files
|
|
238
18
|
* @returns {number} Next available plan ID
|
|
@@ -251,181 +31,56 @@ function getNextPlanId() {
|
|
|
251
31
|
process.exit(1);
|
|
252
32
|
}
|
|
253
33
|
|
|
254
|
-
debugLog(`Task manager root found: ${taskManagerRoot}`);
|
|
255
|
-
|
|
256
34
|
const plansDir = path.join(taskManagerRoot, 'plans');
|
|
257
35
|
const archiveDir = path.join(taskManagerRoot, 'archive');
|
|
258
36
|
|
|
259
|
-
debugLog(`Scanning directories: ${plansDir}, ${archiveDir}`);
|
|
260
|
-
|
|
261
37
|
let maxId = 0;
|
|
262
|
-
let filesScanned = 0;
|
|
263
|
-
let errorsEncountered = 0;
|
|
264
38
|
|
|
265
39
|
// Scan both plans and archive directories
|
|
266
40
|
[plansDir, archiveDir].forEach(dir => {
|
|
267
|
-
const dirName = path.basename(dir);
|
|
268
|
-
debugLog(`Scanning directory: ${dir}`);
|
|
269
|
-
|
|
270
41
|
if (!fs.existsSync(dir)) {
|
|
271
|
-
debugLog(`Directory does not exist: ${dir}`);
|
|
272
42
|
return;
|
|
273
43
|
}
|
|
274
44
|
|
|
275
45
|
try {
|
|
276
46
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
277
|
-
debugLog(`Found ${entries.length} entries in ${dir}`);
|
|
278
47
|
|
|
279
48
|
entries.forEach(entry => {
|
|
280
49
|
if (entry.isDirectory() && entry.name.match(/^\d+--/)) {
|
|
281
50
|
// This is a plan directory, look for plan files inside
|
|
282
51
|
const planDirPath = path.join(dir, entry.name);
|
|
283
|
-
debugLog(`Scanning plan directory: ${planDirPath}`);
|
|
284
52
|
|
|
285
53
|
try {
|
|
286
54
|
const planDirEntries = fs.readdirSync(planDirPath, { withFileTypes: true });
|
|
287
55
|
|
|
288
56
|
planDirEntries.forEach(planEntry => {
|
|
289
57
|
if (planEntry.isFile() && planEntry.name.match(/^plan-\d+--.*\.md$/)) {
|
|
290
|
-
filesScanned++;
|
|
291
58
|
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
59
|
|
|
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
60
|
try {
|
|
324
61
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
325
62
|
const frontmatterId = extractIdFromFrontmatter(content, filePath);
|
|
326
63
|
|
|
327
|
-
if (frontmatterId !== null) {
|
|
328
|
-
|
|
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
|
-
}
|
|
64
|
+
if (frontmatterId !== null && frontmatterId > maxId) {
|
|
65
|
+
maxId = frontmatterId;
|
|
349
66
|
}
|
|
350
67
|
} catch (err) {
|
|
351
68
|
errorLog(`Failed to read file ${filePath}: ${err.message}`);
|
|
352
|
-
errorsEncountered++;
|
|
353
69
|
}
|
|
354
70
|
}
|
|
355
71
|
});
|
|
356
72
|
} catch (err) {
|
|
357
73
|
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
|
-
|
|
366
|
-
// Extract ID from filename as fallback
|
|
367
|
-
const filenameMatch = entry.name.match(/^plan-(\d+)--/);
|
|
368
|
-
let filenameId = null;
|
|
369
|
-
if (filenameMatch) {
|
|
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
|
-
}
|
|
378
74
|
}
|
|
379
|
-
|
|
380
|
-
// Also check frontmatter for more reliable ID
|
|
381
|
-
try {
|
|
382
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
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
|
-
}
|
|
391
|
-
|
|
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
|
-
}
|
|
403
|
-
}
|
|
404
|
-
} catch (err) {
|
|
405
|
-
errorLog(`Failed to read legacy file ${filePath}: ${err.message}`);
|
|
406
|
-
errorsEncountered++;
|
|
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}`);
|
|
412
75
|
}
|
|
413
76
|
});
|
|
414
77
|
} catch (err) {
|
|
415
78
|
errorLog(`Failed to read directory ${dir}: ${err.message}`);
|
|
416
|
-
errorsEncountered++;
|
|
417
79
|
}
|
|
418
80
|
});
|
|
419
81
|
|
|
420
|
-
|
|
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;
|
|
82
|
+
return maxId + 1;
|
|
428
83
|
}
|
|
429
84
|
|
|
430
85
|
// Output the next plan ID
|
|
431
|
-
console.log(getNextPlanId());
|
|
86
|
+
console.log(getNextPlanId());
|
|
@@ -2,67 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Find the task manager root directory by traversing up from CWD
|
|
8
|
-
* @returns {string|null} Path to task manager root or null if not found
|
|
9
|
-
*/
|
|
10
|
-
function findTaskManagerRoot() {
|
|
11
|
-
let currentPath = process.cwd();
|
|
12
|
-
const root = path.parse(currentPath).root;
|
|
13
|
-
|
|
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');
|
|
18
|
-
}
|
|
19
|
-
currentPath = path.dirname(currentPath);
|
|
20
|
-
}
|
|
21
|
-
|
|
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');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Parse YAML frontmatter for ID with resilience to different formats
|
|
33
|
-
* @param {string} content - File content
|
|
34
|
-
* @returns {number|null} Extracted ID or null
|
|
35
|
-
*/
|
|
36
|
-
function extractIdFromFrontmatter(content) {
|
|
37
|
-
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
38
|
-
if (!frontmatterMatch) return null;
|
|
39
|
-
|
|
40
|
-
const frontmatterText = frontmatterMatch[1];
|
|
41
|
-
|
|
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)
|
|
49
|
-
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
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
for (const pattern of patterns) {
|
|
57
|
-
const match = frontmatterText.match(pattern);
|
|
58
|
-
if (match) {
|
|
59
|
-
const id = parseInt(match[1], 10);
|
|
60
|
-
if (!isNaN(id)) return id;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
5
|
+
const { findTaskManagerRoot, extractIdFromFrontmatter } = require('./shared-utils.cjs');
|
|
66
6
|
|
|
67
7
|
/**
|
|
68
8
|
* Get the next available task ID for a specific plan
|
|
@@ -157,4 +97,4 @@ function getNextTaskId(planId) {
|
|
|
157
97
|
|
|
158
98
|
// Get plan ID from command line argument
|
|
159
99
|
const planId = process.argv[2];
|
|
160
|
-
console.log(getNextTaskId(planId));
|
|
100
|
+
console.log(getNextTaskId(planId));
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Find the task manager root directory by traversing up from current working directory
|
|
8
|
+
* @returns {string|null} Path to task manager root or null if not found
|
|
9
|
+
*/
|
|
10
|
+
function findTaskManagerRoot() {
|
|
11
|
+
let currentPath = process.cwd();
|
|
12
|
+
const filesystemRoot = path.parse(currentPath).root;
|
|
13
|
+
|
|
14
|
+
// Traverse upward through parent directories until we reach the filesystem root
|
|
15
|
+
while (currentPath !== filesystemRoot) {
|
|
16
|
+
const taskManagerPlansPath = path.join(currentPath, '.ai', 'task-manager', 'plans');
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Check if this is a valid task manager directory
|
|
20
|
+
if (fs.existsSync(taskManagerPlansPath)) {
|
|
21
|
+
// Verify it's a directory, not a file
|
|
22
|
+
const stats = fs.lstatSync(taskManagerPlansPath);
|
|
23
|
+
if (stats.isDirectory()) {
|
|
24
|
+
const taskManagerRoot = path.join(currentPath, '.ai', 'task-manager');
|
|
25
|
+
return taskManagerRoot;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
} catch (err) {
|
|
29
|
+
// Handle permission errors or other filesystem issues gracefully
|
|
30
|
+
// Continue searching in parent directories
|
|
31
|
+
if (err.code === 'EPERM' || err.code === 'EACCES') {
|
|
32
|
+
// Silently continue - permission errors are expected in some directories
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Move up to parent directory
|
|
37
|
+
const parentPath = path.dirname(currentPath);
|
|
38
|
+
|
|
39
|
+
// Safety check: if path.dirname returns the same path, we've reached the root
|
|
40
|
+
if (parentPath === currentPath) {
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
currentPath = parentPath;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check the filesystem root as the final attempt
|
|
48
|
+
try {
|
|
49
|
+
const rootTaskManagerPlans = path.join(filesystemRoot, '.ai', 'task-manager', 'plans');
|
|
50
|
+
if (fs.existsSync(rootTaskManagerPlans)) {
|
|
51
|
+
const stats = fs.lstatSync(rootTaskManagerPlans);
|
|
52
|
+
if (stats.isDirectory()) {
|
|
53
|
+
const taskManagerRoot = path.join(filesystemRoot, '.ai', 'task-manager');
|
|
54
|
+
return taskManagerRoot;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
// Silently continue
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Parse YAML frontmatter for ID
|
|
66
|
+
* @param {string} content - File content
|
|
67
|
+
* @param {string} [filePath] - Optional file path for error context
|
|
68
|
+
* @returns {number|null} Extracted ID or null
|
|
69
|
+
*/
|
|
70
|
+
function extractIdFromFrontmatter(content, filePath = 'unknown') {
|
|
71
|
+
// Check for frontmatter block existence
|
|
72
|
+
const frontmatterMatch = content.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
|
|
73
|
+
if (!frontmatterMatch) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const frontmatterText = frontmatterMatch[1];
|
|
78
|
+
|
|
79
|
+
// Enhanced patterns to handle various YAML formats:
|
|
80
|
+
// - id: 5 (simple numeric)
|
|
81
|
+
// - id: "5" (double quoted)
|
|
82
|
+
// - id: '5' (single quoted)
|
|
83
|
+
// - "id": 5 (quoted key)
|
|
84
|
+
// - 'id': 5 (single quoted key)
|
|
85
|
+
// - id : 5 (extra spaces)
|
|
86
|
+
// - id: 05 (zero-padded)
|
|
87
|
+
// - id: +5 (explicit positive)
|
|
88
|
+
// - Mixed quotes: 'id': "5" (different quote types)
|
|
89
|
+
const patterns = [
|
|
90
|
+
// Most flexible pattern - handles quoted/unquoted keys and values with optional spaces
|
|
91
|
+
/^\s*["']?id["']?\s*:\s*["']?([+-]?\d+)["']?\s*(?:#.*)?$/mi,
|
|
92
|
+
// Simple numeric with optional whitespace and comments
|
|
93
|
+
/^\s*id\s*:\s*([+-]?\d+)\s*(?:#.*)?$/mi,
|
|
94
|
+
// Double quoted values
|
|
95
|
+
/^\s*["']?id["']?\s*:\s*"([+-]?\d+)"\s*(?:#.*)?$/mi,
|
|
96
|
+
// Single quoted values
|
|
97
|
+
/^\s*["']?id["']?\s*:\s*'([+-]?\d+)'\s*(?:#.*)?$/mi,
|
|
98
|
+
// Mixed quotes - quoted key, unquoted value
|
|
99
|
+
/^\s*["']id["']\s*:\s*([+-]?\d+)\s*(?:#.*)?$/mi,
|
|
100
|
+
// YAML-style with pipe or greater-than indicators (edge case)
|
|
101
|
+
/^\s*id\s*:\s*[|>]\s*([+-]?\d+)\s*$/mi
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
// Try each pattern in order
|
|
105
|
+
for (const regex of patterns) {
|
|
106
|
+
const match = frontmatterText.match(regex);
|
|
107
|
+
if (match) {
|
|
108
|
+
const rawId = match[1];
|
|
109
|
+
const id = parseInt(rawId, 10);
|
|
110
|
+
|
|
111
|
+
// Validate the parsed ID
|
|
112
|
+
if (isNaN(id)) {
|
|
113
|
+
console.error(`[ERROR] Invalid ID value "${rawId}" in ${filePath} - not a valid number`);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (id < 0) {
|
|
118
|
+
console.error(`[ERROR] Invalid ID value ${id} in ${filePath} - ID must be non-negative`);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (id > Number.MAX_SAFE_INTEGER) {
|
|
123
|
+
console.error(`[ERROR] Invalid ID value ${id} in ${filePath} - ID exceeds maximum safe integer`);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return id;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Parse YAML frontmatter from markdown content
|
|
136
|
+
* Returns the frontmatter text as a string (not parsed as YAML)
|
|
137
|
+
* @param {string} content - The markdown content with frontmatter
|
|
138
|
+
* @returns {string} Frontmatter text or empty string if not found
|
|
139
|
+
*/
|
|
140
|
+
function parseFrontmatter(content) {
|
|
141
|
+
const lines = content.split('\n');
|
|
142
|
+
let inFrontmatter = false;
|
|
143
|
+
let frontmatterEnd = false;
|
|
144
|
+
let delimiterCount = 0;
|
|
145
|
+
const frontmatterLines = [];
|
|
146
|
+
|
|
147
|
+
for (const line of lines) {
|
|
148
|
+
if (line.trim() === '---') {
|
|
149
|
+
delimiterCount++;
|
|
150
|
+
if (delimiterCount === 1) {
|
|
151
|
+
inFrontmatter = true;
|
|
152
|
+
continue;
|
|
153
|
+
} else if (delimiterCount === 2) {
|
|
154
|
+
frontmatterEnd = true;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (inFrontmatter && !frontmatterEnd) {
|
|
160
|
+
frontmatterLines.push(line);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return frontmatterLines.join('\n');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = {
|
|
168
|
+
findTaskManagerRoot,
|
|
169
|
+
extractIdFromFrontmatter,
|
|
170
|
+
parseFrontmatter
|
|
171
|
+
};
|
|
@@ -2,20 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
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
|
-
}
|
|
5
|
+
const { findTaskManagerRoot } = require('./shared-utils.cjs');
|
|
19
6
|
|
|
20
7
|
/**
|
|
21
8
|
* Error logging utility
|
|
@@ -26,59 +13,6 @@ function errorLog(message, ...args) {
|
|
|
26
13
|
console.error(`[ERROR] ${message}`, ...args);
|
|
27
14
|
}
|
|
28
15
|
|
|
29
|
-
/**
|
|
30
|
-
* Find the task manager root directory by traversing up from current working directory
|
|
31
|
-
* @returns {string|null} Path to task manager root or null if not found
|
|
32
|
-
*/
|
|
33
|
-
function findTaskManagerRoot() {
|
|
34
|
-
let currentPath = process.cwd();
|
|
35
|
-
const filesystemRoot = path.parse(currentPath).root;
|
|
36
|
-
|
|
37
|
-
debugLog(`Starting search for task manager root from: ${currentPath}`);
|
|
38
|
-
debugLog(`Filesystem root: ${filesystemRoot}`);
|
|
39
|
-
|
|
40
|
-
while (currentPath !== filesystemRoot) {
|
|
41
|
-
const taskManagerPlansPath = path.join(currentPath, '.ai', 'task-manager', 'plans');
|
|
42
|
-
debugLog(`Checking for task manager at: ${taskManagerPlansPath}`);
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
if (fs.existsSync(taskManagerPlansPath)) {
|
|
46
|
-
const stats = fs.lstatSync(taskManagerPlansPath);
|
|
47
|
-
if (stats.isDirectory()) {
|
|
48
|
-
const taskManagerRoot = path.join(currentPath, '.ai', 'task-manager');
|
|
49
|
-
debugLog(`Found valid task manager root at: ${taskManagerRoot}`);
|
|
50
|
-
return taskManagerRoot;
|
|
51
|
-
} else {
|
|
52
|
-
debugLog(`Path exists but is not a directory: ${taskManagerPlansPath}`);
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
debugLog(`Task manager path does not exist: ${taskManagerPlansPath}`);
|
|
56
|
-
}
|
|
57
|
-
} catch (err) {
|
|
58
|
-
if (err.code === 'EPERM' || err.code === 'EACCES') {
|
|
59
|
-
const warningMsg = `Warning: Permission denied accessing ${taskManagerPlansPath}`;
|
|
60
|
-
console.warn(warningMsg);
|
|
61
|
-
debugLog(`Permission error: ${err.message}`);
|
|
62
|
-
} else {
|
|
63
|
-
debugLog(`Filesystem error checking ${taskManagerPlansPath}: ${err.message}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const parentPath = path.dirname(currentPath);
|
|
68
|
-
|
|
69
|
-
if (parentPath === currentPath) {
|
|
70
|
-
debugLog(`Reached filesystem root, stopping traversal`);
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
currentPath = parentPath;
|
|
75
|
-
debugLog(`Moving up to parent directory: ${currentPath}`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
debugLog(`Task manager root not found in any parent directory`);
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
16
|
/**
|
|
83
17
|
* Find plan file and directory for a given plan ID
|
|
84
18
|
* @param {string|number} planId - Plan ID to search for
|
|
@@ -92,8 +26,6 @@ function findPlanById(planId) {
|
|
|
92
26
|
return null;
|
|
93
27
|
}
|
|
94
28
|
|
|
95
|
-
debugLog(`Task manager root found: ${taskManagerRoot}`);
|
|
96
|
-
|
|
97
29
|
// Convert planId to numeric for flexible matching (handles both "2" and "02")
|
|
98
30
|
const numericPlanId = parseInt(planId, 10);
|
|
99
31
|
|
|
@@ -102,23 +34,17 @@ function findPlanById(planId) {
|
|
|
102
34
|
return null;
|
|
103
35
|
}
|
|
104
36
|
|
|
105
|
-
debugLog(`Searching for plan with numeric ID: ${numericPlanId} (input was: ${planId})`);
|
|
106
|
-
|
|
107
37
|
const plansDir = path.join(taskManagerRoot, 'plans');
|
|
108
38
|
const archiveDir = path.join(taskManagerRoot, 'archive');
|
|
109
39
|
|
|
110
|
-
debugLog(`Searching for plan ID ${planId} in: ${plansDir}, ${archiveDir}`);
|
|
111
|
-
|
|
112
40
|
// Search both plans and archive directories
|
|
113
41
|
for (const dir of [plansDir, archiveDir]) {
|
|
114
42
|
if (!fs.existsSync(dir)) {
|
|
115
|
-
debugLog(`Directory does not exist: ${dir}`);
|
|
116
43
|
continue;
|
|
117
44
|
}
|
|
118
45
|
|
|
119
46
|
try {
|
|
120
47
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
121
|
-
debugLog(`Found ${entries.length} entries in ${dir}`);
|
|
122
48
|
|
|
123
49
|
for (const entry of entries) {
|
|
124
50
|
// Match directory pattern: [plan-id]--* (with flexible ID matching)
|
|
@@ -127,7 +53,6 @@ function findPlanById(planId) {
|
|
|
127
53
|
const dirMatch = entry.name.match(/^0*(\d+)--/);
|
|
128
54
|
if (dirMatch && parseInt(dirMatch[1], 10) === numericPlanId) {
|
|
129
55
|
const planDirPath = path.join(dir, entry.name);
|
|
130
|
-
debugLog(`Found matching plan directory: ${planDirPath} (extracted ID: ${dirMatch[1]} matches input: ${numericPlanId})`);
|
|
131
56
|
|
|
132
57
|
try {
|
|
133
58
|
const planDirEntries = fs.readdirSync(planDirPath, { withFileTypes: true });
|
|
@@ -139,7 +64,6 @@ function findPlanById(planId) {
|
|
|
139
64
|
const fileMatch = planEntry.name.match(/^plan-0*(\d+)--.*\.md$/);
|
|
140
65
|
if (fileMatch && parseInt(fileMatch[1], 10) === numericPlanId) {
|
|
141
66
|
const planFilePath = path.join(planDirPath, planEntry.name);
|
|
142
|
-
debugLog(`Found plan file: ${planFilePath} (extracted ID: ${fileMatch[1]} matches input: ${numericPlanId})`);
|
|
143
67
|
|
|
144
68
|
return {
|
|
145
69
|
planFile: planFilePath,
|
|
@@ -148,8 +72,6 @@ function findPlanById(planId) {
|
|
|
148
72
|
}
|
|
149
73
|
}
|
|
150
74
|
}
|
|
151
|
-
|
|
152
|
-
debugLog(`No plan file found in directory: ${planDirPath}`);
|
|
153
75
|
} catch (err) {
|
|
154
76
|
errorLog(`Failed to read plan directory ${planDirPath}: ${err.message}`);
|
|
155
77
|
}
|
|
@@ -161,7 +83,6 @@ function findPlanById(planId) {
|
|
|
161
83
|
}
|
|
162
84
|
}
|
|
163
85
|
|
|
164
|
-
debugLog(`Plan ID ${planId} not found in any directory`);
|
|
165
86
|
return null;
|
|
166
87
|
}
|
|
167
88
|
|
|
@@ -174,19 +95,16 @@ function countTasks(planDir) {
|
|
|
174
95
|
const tasksDir = path.join(planDir, 'tasks');
|
|
175
96
|
|
|
176
97
|
if (!fs.existsSync(tasksDir)) {
|
|
177
|
-
debugLog(`Tasks directory does not exist: ${tasksDir}`);
|
|
178
98
|
return 0;
|
|
179
99
|
}
|
|
180
100
|
|
|
181
101
|
try {
|
|
182
102
|
const stats = fs.lstatSync(tasksDir);
|
|
183
103
|
if (!stats.isDirectory()) {
|
|
184
|
-
debugLog(`Tasks path exists but is not a directory: ${tasksDir}`);
|
|
185
104
|
return 0;
|
|
186
105
|
}
|
|
187
106
|
|
|
188
107
|
const files = fs.readdirSync(tasksDir).filter(f => f.endsWith('.md'));
|
|
189
|
-
debugLog(`Found ${files.length} task files in ${tasksDir}`);
|
|
190
108
|
return files.length;
|
|
191
109
|
} catch (err) {
|
|
192
110
|
errorLog(`Failed to read tasks directory ${tasksDir}: ${err.message}`);
|
|
@@ -203,7 +121,6 @@ function checkBlueprintExists(planFile) {
|
|
|
203
121
|
try {
|
|
204
122
|
const planContent = fs.readFileSync(planFile, 'utf8');
|
|
205
123
|
const blueprintExists = /^## Execution Blueprint/m.test(planContent);
|
|
206
|
-
debugLog(`Blueprint section ${blueprintExists ? 'found' : 'not found'} in ${planFile}`);
|
|
207
124
|
return blueprintExists;
|
|
208
125
|
} catch (err) {
|
|
209
126
|
errorLog(`Failed to read plan file ${planFile}: ${err.message}`);
|
|
@@ -270,8 +187,6 @@ function validatePlanBlueprint(planId, fieldName) {
|
|
|
270
187
|
process.exit(1);
|
|
271
188
|
}
|
|
272
189
|
|
|
273
|
-
debugLog(`Validating plan blueprint for ID: ${planId}`);
|
|
274
|
-
|
|
275
190
|
const planInfo = findPlanById(planId);
|
|
276
191
|
|
|
277
192
|
if (!planInfo) {
|
|
@@ -308,8 +223,6 @@ function validatePlanBlueprint(planId, fieldName) {
|
|
|
308
223
|
blueprintExists: blueprintExists ? 'yes' : 'no'
|
|
309
224
|
};
|
|
310
225
|
|
|
311
|
-
debugLog('Validation complete:', result);
|
|
312
|
-
|
|
313
226
|
// If field name is provided, output just that field
|
|
314
227
|
if (fieldName) {
|
|
315
228
|
const validFields = ['planFile', 'planDir', 'taskCount', 'blueprintExists'];
|
|
@@ -74,6 +74,10 @@ Example:
|
|
|
74
74
|
2. [Measurable outcome 2]
|
|
75
75
|
3. [Measurable outcome 3]
|
|
76
76
|
|
|
77
|
+
## Documentation
|
|
78
|
+
|
|
79
|
+
[Required documentation updates to existing documentation, either human-focused documentation, the project's README.md or assistant-focused documentation like AGENTS.md, .claude/skills/* for the site, etc.]
|
|
80
|
+
|
|
77
81
|
## Resource Requirements
|
|
78
82
|
|
|
79
83
|
### Development Skills
|