@e0ipso/ai-task-manager 1.8.2 → 1.8.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e0ipso/ai-task-manager",
3
- "version": "1.8.2",
3
+ "version": "1.8.3",
4
4
  "description": "Task management for AI coding assistants",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -0,0 +1,124 @@
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 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
+ }
66
+
67
+ /**
68
+ * Get the next available plan ID by scanning existing plan files
69
+ * @returns {number} Next available plan ID
70
+ */
71
+ function getNextPlanId() {
72
+ const taskManagerRoot = findTaskManagerRoot();
73
+
74
+ 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.');
77
+ process.exit(1);
78
+ }
79
+
80
+ const plansDir = path.join(taskManagerRoot, 'plans');
81
+ const archiveDir = path.join(taskManagerRoot, 'archive');
82
+
83
+ let maxId = 0;
84
+
85
+ // Scan both plans and archive directories
86
+ [plansDir, archiveDir].forEach(dir => {
87
+ if (!fs.existsSync(dir)) return;
88
+
89
+ try {
90
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
91
+
92
+ entries.forEach(entry => {
93
+ if (entry.isFile() && entry.name.match(/^plan-\d+--.*\.md$/)) {
94
+ // Extract ID from filename as fallback
95
+ const filenameMatch = entry.name.match(/^plan-(\d+)--/);
96
+ if (filenameMatch) {
97
+ const id = parseInt(filenameMatch[1], 10);
98
+ if (!isNaN(id) && id > maxId) maxId = id;
99
+ }
100
+
101
+ // Also check frontmatter for more reliable ID
102
+ try {
103
+ const filePath = path.join(dir, entry.name);
104
+ const content = fs.readFileSync(filePath, 'utf8');
105
+ const id = extractIdFromFrontmatter(content);
106
+
107
+ if (id !== null && id > maxId) {
108
+ maxId = id;
109
+ }
110
+ } catch (err) {
111
+ // Skip corrupted files
112
+ }
113
+ }
114
+ });
115
+ } catch (err) {
116
+ // Skip directories that can't be read
117
+ }
118
+ });
119
+
120
+ return maxId + 1;
121
+ }
122
+
123
+ // Output the next plan ID
124
+ console.log(getNextPlanId());
@@ -0,0 +1,160 @@
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 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
+ }
66
+
67
+ /**
68
+ * Get the next available task ID for a specific plan
69
+ * @param {number|string} planId - The plan ID to get next task ID for
70
+ * @returns {number} Next available task ID
71
+ */
72
+ function getNextTaskId(planId) {
73
+ if (!planId) {
74
+ console.error('Error: Plan ID is required');
75
+ process.exit(1);
76
+ }
77
+
78
+ const taskManagerRoot = findTaskManagerRoot();
79
+
80
+ if (!taskManagerRoot) {
81
+ console.error('Error: No .ai/task-manager/plans directory found in current directory or any parent directory.');
82
+ console.error('Please ensure you are in a project with task manager initialized.');
83
+ process.exit(1);
84
+ }
85
+
86
+ const plansDir = path.join(taskManagerRoot, 'plans');
87
+
88
+ // Find the plan directory (supports both padded and unpadded formats)
89
+ const paddedPlanId = String(planId).padStart(2, '0');
90
+
91
+ let planDir = null;
92
+
93
+ // Optimization: 90% of the time there are no tasks, so check if plans directory exists first
94
+ if (!fs.existsSync(plansDir)) {
95
+ return 1; // No plans directory = no tasks = start with ID 1
96
+ }
97
+
98
+ try {
99
+ const entries = fs.readdirSync(plansDir, { withFileTypes: true });
100
+
101
+ // Look for directory matching the plan ID pattern
102
+ for (const entry of entries) {
103
+ if (entry.isDirectory()) {
104
+ const match = entry.name.match(/^(\d+)--/);
105
+ if (match) {
106
+ const dirPlanId = match[1].padStart(2, '0');
107
+ if (dirPlanId === paddedPlanId) {
108
+ const tasksPath = path.join(plansDir, entry.name, 'tasks');
109
+ if (fs.existsSync(tasksPath)) {
110
+ planDir = tasksPath;
111
+ }
112
+ break;
113
+ }
114
+ }
115
+ }
116
+ }
117
+ } catch (err) {
118
+ // Directory doesn't exist or can't be read
119
+ }
120
+
121
+ // Optimization: If no tasks directory exists, return 1 immediately (90% case)
122
+ if (!planDir) {
123
+ return 1;
124
+ }
125
+
126
+ let maxId = 0;
127
+
128
+ try {
129
+ const entries = fs.readdirSync(planDir, { withFileTypes: true });
130
+
131
+ // Another optimization: If directory is empty, return 1 immediately
132
+ if (entries.length === 0) {
133
+ return 1;
134
+ }
135
+
136
+ entries.forEach(entry => {
137
+ if (entry.isFile() && entry.name.endsWith('.md')) {
138
+ try {
139
+ const filePath = path.join(planDir, entry.name);
140
+ const content = fs.readFileSync(filePath, 'utf8');
141
+ const id = extractIdFromFrontmatter(content);
142
+
143
+ if (id !== null && id > maxId) {
144
+ maxId = id;
145
+ }
146
+ } catch (err) {
147
+ // Skip corrupted files
148
+ }
149
+ }
150
+ });
151
+ } catch (err) {
152
+ // Skip directories that can't be read
153
+ }
154
+
155
+ return maxId + 1;
156
+ }
157
+
158
+ // Get plan ID from command line argument
159
+ const planId = process.argv[2];
160
+ console.log(getNextTaskId(planId));
@@ -132,25 +132,25 @@ The schema for this frontmatter is:
132
132
 
133
133
  **Auto-generate the next plan ID:**
134
134
  ```bash
135
- echo $(($(find .ai/task-manager/{plans,archive} -name "plan-[0-9]*--*.md" 2>/dev/null -exec sh -c 'grep -m1 "^[[:space:]]*id:[[:space:]]*[0-9][0-9]*[[:space:]]*$" "$1" 2>/dev/null || echo "id: 0"' _ {} \; | sed -E "s/^[[:space:]]*id:[[:space:]]*([0-9]+)[[:space:]]*$/\1/" | awk 'BEGIN{max=0} {if($1+0>max) max=$1+0} END{print max}') + 1))
135
+ node .ai/task-manager/config/scripts/get-next-plan-id.js
136
136
  ```
137
137
 
138
138
  **Key formatting:**
139
139
  - **Front-matter**: Use numeric values (`id: 7`)
140
140
  - **Directory names**: Use zero-padded strings (`07--plan-name`)
141
141
 
142
- This enhanced command provides robust plan ID generation with comprehensive error handling:
142
+ This Node.js script provides robust plan ID generation with comprehensive error handling:
143
143
 
144
144
  **Features:**
145
- - **Flexible Whitespace Handling**: Supports various patterns: `id: 5`, `id:5`, `id: 15`, `id: 25` (tabs)
145
+ - **Flexible Whitespace Handling**: Supports various frontmatter patterns
146
146
  - **Validation Layer**: Only processes files with valid numeric ID fields in YAML frontmatter
147
147
  - **Error Resilience**: Gracefully handles empty directories, corrupted files, and parsing failures
148
- - **Fallback Logic**: Returns ID 1 when no valid plans found, ensuring command never fails
149
- - **Robust Parsing**: Uses POSIX character classes for reliable whitespace matching across systems
148
+ - **Fallback Logic**: Returns ID 1 when no valid plans found, ensuring script never fails
149
+ - **Dual ID Detection**: Checks both filename patterns and frontmatter for maximum reliability
150
150
 
151
151
  **Handles Edge Cases:**
152
152
  - Empty plans/archive directories → Returns 1
153
153
  - Corrupted or malformed YAML frontmatter → Skips invalid files
154
154
  - Non-numeric ID values → Filters out automatically
155
- - Missing frontmatter → Ignored safely
156
- - File system errors → Suppressed with 2>/dev/null
155
+ - Missing frontmatter → Uses filename fallback
156
+ - File system errors → Gracefully handled
@@ -220,18 +220,18 @@ When creating tasks, you need to determine the next available task ID for the sp
220
220
 
221
221
  #### Command
222
222
  ```bash
223
- PLAN_ID=$1; echo $(($(find .ai/task-manager/plans/$(printf "%02d" $PLAN_ID)--*/tasks -name "*.md" -exec grep "^id: *[0-9][0-9]* *$" {} \; 2>/dev/null | sed 's/.*id: *//' | sed 's/ *$//' | sort -n | tail -1 | sed 's/^$/0/') + 1))
223
+ node .ai/task-manager/config/scripts/get-next-task-id.js $1
224
224
  ```
225
225
 
226
226
  #### How It Works
227
- 1. **Finds task files** using the pattern `*.md` in the specific plan's tasks directory
228
- 2. **Validates and extracts front-matter IDs** using grep to find `id:` lines with valid numeric values, filtering out malformed or string IDs
229
- 3. **Strips the `id:` prefix and whitespace** using sed to get clean numeric values only
230
- 4. **Sorts numerically** to find the highest existing task ID
231
- 5. **Handles empty results** by defaulting to 0 if no valid tasks exist
232
- 6. **Adds 1** to get the next available task ID
227
+ 1. **Finds the plan directory** using pattern matching for plan ID directories
228
+ 2. **Scans task files** in the plan's tasks directory for `.md` files
229
+ 3. **Validates frontmatter IDs** by parsing YAML frontmatter and extracting numeric ID values
230
+ 4. **Finds maximum ID** by comparing all valid task IDs numerically
231
+ 5. **Handles edge cases** by gracefully handling missing directories, corrupted files, and parsing errors
232
+ 6. **Returns next ID** by incrementing the maximum found ID by 1
233
233
 
234
- This command reads the actual `id:` values from task front-matter, making it the definitive source of truth.
234
+ This Node.js script provides robust error handling and reliable ID generation.
235
235
 
236
236
  #### Parameter Usage
237
237
  - `$1` is the plan ID parameter passed to this template
@@ -249,7 +249,7 @@ This command reads the actual `id:` values from task front-matter, making it the
249
249
  **Example 1: Plan 6 with existing tasks**
250
250
  ```bash
251
251
  # Command execution (plan ID = 6)
252
- PLAN_ID=6; echo $(($(find .ai/task-manager/plans/$(printf "%02d" $PLAN_ID)--*/tasks -name "*.md" -exec grep "^id: *[0-9][0-9]* *$" {} \; 2>/dev/null | sed 's/.*id: *//' | sed 's/ *$//' | sort -n | tail -1 | sed 's/^$/0/') + 1))
252
+ node .ai/task-manager/config/scripts/get-next-task-id.js 6
253
253
  # Output: 5 (if highest valid numeric task front-matter has id: 4)
254
254
 
255
255
  # Front-matter usage:
@@ -266,7 +266,7 @@ skills: ["api-endpoints", "database"]
266
266
  **Example 2: Plan 1 with no existing tasks**
267
267
  ```bash
268
268
  # Command execution (plan ID = 1)
269
- PLAN_ID=1; echo $(($(find .ai/task-manager/plans/$(printf "%02d" $PLAN_ID)--*/tasks -name "*.md" -exec grep "^id: *[0-9][0-9]* *$" {} \; 2>/dev/null | sed 's/.*id: *//' | sed 's/ *$//' | sort -n | tail -1 | sed 's/^$/0/') + 1))
269
+ node .ai/task-manager/config/scripts/get-next-task-id.js 1
270
270
  # Output: 1 (empty tasks directory, no valid front-matter to read)
271
271
 
272
272
  # Front-matter usage: