@e0ipso/ai-task-manager 1.26.3 → 1.26.5

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.26.3",
3
+ "version": "1.26.5",
4
4
  "description": "Task management for AI coding assistants",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -9,323 +9,261 @@
9
9
 
10
10
  const fs = require('fs-extra');
11
11
  const path = require('path');
12
- const { execSync } = require('child_process');
13
- const { parseFrontmatter } = require('./shared-utils.cjs');
12
+ const {
13
+ resolvePlan,
14
+ parseFrontmatter
15
+ } = require('./shared-utils.cjs');
14
16
 
15
17
  // Chalk instance - loaded dynamically to handle ESM module
16
18
  let chalkInstance = null;
17
19
 
18
20
  // Initialize chalk instance dynamically
19
- async function initChalk() {
20
- if (chalkInstance) return chalkInstance;
21
-
22
- try {
23
- const { default: chalk } = await import('chalk');
24
- chalkInstance = chalk;
25
- } catch (_error) {
26
- // Chalk not available, will fall back to plain console output
27
- chalkInstance = null;
28
- }
29
-
30
- return chalkInstance;
21
+ async function _initChalk() {
22
+ if (chalkInstance) return chalkInstance;
23
+
24
+ try {
25
+ const {
26
+ default: chalk
27
+ } = await import('chalk');
28
+ chalkInstance = chalk;
29
+ } catch (_error) {
30
+ // Chalk not available, will fall back to plain console output
31
+ chalkInstance = null;
32
+ }
33
+
34
+ return chalkInstance;
31
35
  }
32
36
 
33
37
  // Color functions for output
34
- const printError = (message, chalk) => {
35
- const formattedMessage = chalk?.red(`ERROR: ${message}`) || `ERROR: ${message}`;
36
- console.error(formattedMessage);
38
+ const _printError = (message, chalk) => {
39
+ const formattedMessage = chalk?.red(`ERROR: ${message}`) || `ERROR: ${message}`;
40
+ console.error(formattedMessage);
37
41
  };
38
42
 
39
- const printSuccess = (message, chalk) => {
40
- const formattedMessage = chalk?.green(`✓ ${message}`) || `✓ ${message}`;
41
- console.log(formattedMessage);
43
+ const _printSuccess = (message, chalk) => {
44
+ const formattedMessage = chalk?.green(`✓ ${message}`) || `✓ ${message}`;
45
+ console.log(formattedMessage);
42
46
  };
43
47
 
44
- const printWarning = (message, chalk) => {
45
- const formattedMessage = chalk?.yellow(`⚠ ${message}`) || `⚠ ${message}`;
46
- console.log(formattedMessage);
48
+ const _printWarning = (message, chalk) => {
49
+ const formattedMessage = chalk?.yellow(`⚠ ${message}`) || `⚠ ${message}`;
50
+ console.log(formattedMessage);
47
51
  };
48
52
 
49
- const printInfo = (message) => {
50
- console.log(message);
51
- };
52
-
53
- // Function to find plan directory
54
- const findPlanDirectory = (planId) => {
55
- const searchLocations = [
56
- '.ai/task-manager/plans',
57
- '.ai/task-manager/archive'
58
- ];
59
-
60
- // Generate ID variations to try (exact, padded, unpadded)
61
- const idVariations = [
62
- planId, // Try exact match first
63
- planId.padStart(2, '0'), // Try padded version (3 → 03)
64
- planId.replace(/^0+/, '') || '0' // Try unpadded version (03 → 3)
65
- ];
66
-
67
- // Remove duplicates from variations array
68
- const uniqueVariations = [...new Set(idVariations)];
69
-
70
- // Search for each variation in each location
71
- for (const id of uniqueVariations) {
72
- for (const location of searchLocations) {
73
- try {
74
- const findCommand = `find ${location} -type d -name "${id}--*" 2>/dev/null || true`;
75
- const result = execSync(findCommand, { encoding: 'utf8' }).trim();
76
- const directories = result.split('\n').filter(dir => dir.length > 0);
77
-
78
- if (directories.length > 0) {
79
- return directories[0]; // Early return on first match
80
- }
81
- } catch (error) {
82
- // Continue trying other variations/locations
83
- continue;
84
- }
85
- }
86
- }
87
-
88
- return null; // No matches found
53
+ const _printInfo = (message) => {
54
+ console.log(message);
89
55
  };
90
56
 
91
57
  // Function to find task file with padded/unpadded ID handling
92
- const findTaskFile = (planDir, taskId) => {
93
- const taskDir = path.join(planDir, 'tasks');
94
-
95
- if (!fs.existsSync(taskDir)) {
96
- return null;
97
- }
58
+ const _findTaskFile = (planDir, taskId) => {
59
+ const taskDir = path.join(planDir, 'tasks');
98
60
 
99
- // Try exact match first
100
- let pattern = `${taskId}--*.md`;
101
- let files = fs.readdirSync(taskDir).filter(file => {
102
- const regex = new RegExp(`^${taskId}--.*\\.md$`);
103
- return regex.test(file);
104
- });
61
+ if (!fs.existsSync(taskDir)) {
62
+ return null;
63
+ }
64
+
65
+ const variations = [
66
+ taskId,
67
+ taskId.padStart(2, '0'),
68
+ taskId.replace(/^0+/, '') || '0'
69
+ ];
70
+
71
+ const uniqueVariations = [...new Set(variations)];
72
+
73
+ try {
74
+ const files = fs.readdirSync(taskDir);
75
+ const found = uniqueVariations.reduce((acc, v) => {
76
+ if (acc) return acc;
77
+ const match = files.find(f => f.startsWith(`${v}--`) && f.endsWith('.md'));
78
+ return match ? path.join(taskDir, match) : null;
79
+ }, null);
80
+ return found;
81
+ } catch (err) {
82
+ return null;
83
+ }
84
+ };
105
85
 
106
- if (files.length > 0) {
107
- return path.join(taskDir, files[0]);
108
- }
109
86
 
110
- // Try with zero-padding if direct match fails
111
- const paddedTaskId = taskId.padStart(2, '0');
112
- if (paddedTaskId !== taskId) {
113
- pattern = `${paddedTaskId}--*.md`;
114
- files = fs.readdirSync(taskDir).filter(file => {
115
- const regex = new RegExp(`^${paddedTaskId}--.*\\.md$`);
116
- return regex.test(file);
117
- });
118
-
119
- if (files.length > 0) {
120
- return path.join(taskDir, files[0]);
121
- }
87
+ // Function to extract dependencies from frontmatter
88
+ const _extractDependencies = (frontmatter) => {
89
+ const lines = frontmatter.split('\n');
90
+ const dependencies = [];
91
+ let inDependenciesSection = false;
92
+
93
+ for (let i = 0; i < lines.length; i++) {
94
+ const line = lines[i];
95
+
96
+ // Check for dependencies line
97
+ if (line.match(/^dependencies:/)) {
98
+ inDependenciesSection = true;
99
+
100
+ // Check if dependencies are on the same line (array syntax)
101
+ const arrayMatch = line.match(/\[(.*)\]/);
102
+ if (arrayMatch) {
103
+ const deps = arrayMatch[1]
104
+ .split(',')
105
+ .map(dep => dep.trim().replace(/['"]/g, ''))
106
+ .filter(dep => dep.length > 0);
107
+ dependencies.push(...deps);
108
+ inDependenciesSection = false;
109
+ }
110
+ continue;
122
111
  }
123
112
 
124
- // Try removing potential zero-padding from taskId
125
- const unpaddedTaskId = taskId.replace(/^0+/, '') || '0';
126
- if (unpaddedTaskId !== taskId) {
127
- pattern = `${unpaddedTaskId}--*.md`;
128
- files = fs.readdirSync(taskDir).filter(file => {
129
- const regex = new RegExp(`^${unpaddedTaskId}--.*\\.md$`);
130
- return regex.test(file);
131
- });
132
-
133
- if (files.length > 0) {
134
- return path.join(taskDir, files[0]);
135
- }
136
-
137
- // Try with zero-padding of unpadded version
138
- const repaddedTaskId = unpaddedTaskId.padStart(2, '0');
139
- pattern = `${repaddedTaskId}--*.md`;
140
- files = fs.readdirSync(taskDir).filter(file => {
141
- const regex = new RegExp(`^${repaddedTaskId}--.*\\.md$`);
142
- return regex.test(file);
143
- });
144
-
145
- if (files.length > 0) {
146
- return path.join(taskDir, files[0]);
147
- }
113
+ // If we're in dependencies section and hit a non-indented line that's not a list item, exit
114
+ if (inDependenciesSection && line.match(/^[^ ]/) && !line.match(/^[ \t]*-/)) {
115
+ inDependenciesSection = false;
148
116
  }
149
117
 
150
- return null;
151
- };
152
-
153
-
154
- // Function to extract dependencies from frontmatter
155
- const extractDependencies = (frontmatter) => {
156
- const lines = frontmatter.split('\n');
157
- const dependencies = [];
158
- let inDependenciesSection = false;
159
-
160
- for (let i = 0; i < lines.length; i++) {
161
- const line = lines[i];
162
-
163
- // Check for dependencies line
164
- if (line.match(/^dependencies:/)) {
165
- inDependenciesSection = true;
166
-
167
- // Check if dependencies are on the same line (array syntax)
168
- const arrayMatch = line.match(/\[(.*)\]/);
169
- if (arrayMatch) {
170
- const deps = arrayMatch[1]
171
- .split(',')
172
- .map(dep => dep.trim().replace(/['"]/g, ''))
173
- .filter(dep => dep.length > 0);
174
- dependencies.push(...deps);
175
- inDependenciesSection = false;
176
- }
177
- continue;
178
- }
179
-
180
- // If we're in dependencies section and hit a non-indented line that's not a list item, exit
181
- if (inDependenciesSection && line.match(/^[^ ]/) && !line.match(/^[ \t]*-/)) {
182
- inDependenciesSection = false;
183
- }
184
-
185
- // Parse list format dependencies
186
- if (inDependenciesSection && line.match(/^[ \t]*-/)) {
187
- const dep = line.replace(/^[ \t]*-[ \t]*/, '').replace(/[ \t]*$/, '').replace(/['"]/g, '');
188
- if (dep.length > 0) {
189
- dependencies.push(dep);
190
- }
191
- }
118
+ // Parse list format dependencies
119
+ if (inDependenciesSection && line.match(/^[ \t]*-/)) {
120
+ const dep = line.replace(/^[ \t]*-[ \t]*/, '').replace(/[ \t]*$/, '').replace(/['"]/g, '');
121
+ if (dep.length > 0) {
122
+ dependencies.push(dep);
123
+ }
192
124
  }
125
+ }
193
126
 
194
- return dependencies;
127
+ return dependencies;
195
128
  };
196
129
 
197
130
  // Function to extract status from frontmatter
198
- const extractStatus = (frontmatter) => {
199
- const lines = frontmatter.split('\n');
131
+ const _extractStatus = (frontmatter) => {
132
+ const lines = frontmatter.split('\n');
200
133
 
201
- for (const line of lines) {
202
- if (line.match(/^status:/)) {
203
- return line.replace(/^status:[ \t]*/, '').replace(/^["']/, '').replace(/["']$/, '').trim();
204
- }
134
+ for (const line of lines) {
135
+ if (line.match(/^status:/)) {
136
+ return line.replace(/^status:[ \t]*/, '').replace(/^["']/, '').replace(/["']$/, '').trim();
205
137
  }
138
+ }
206
139
 
207
- return null;
140
+ return null;
208
141
  };
209
142
 
210
143
  // Main function
211
- const main = async () => {
212
- // Initialize chalk
213
- const chalk = await initChalk();
214
-
215
- // Check arguments
216
- if (process.argv.length !== 4) {
217
- printError('Invalid number of arguments', chalk);
218
- console.log('Usage: node check-task-dependencies.cjs <plan-id> <task-id>');
219
- console.log('Example: node check-task-dependencies.cjs 16 03');
220
- process.exit(1);
221
- }
222
-
223
- const planId = process.argv[2];
224
- const taskId = process.argv[3];
225
-
226
- // Find the plan directory
227
- const planDir = findPlanDirectory(planId);
228
-
229
- if (!planDir) {
230
- printError(`Plan with ID ${planId} not found`, chalk);
231
- process.exit(1);
232
- }
233
-
234
- printInfo(`Found plan directory: ${planDir}`);
235
-
236
- // Find task file
237
- const taskFile = findTaskFile(planDir, taskId);
238
-
239
- if (!taskFile || !fs.existsSync(taskFile)) {
240
- printError(`Task with ID ${taskId} not found in plan ${planId}`, chalk);
241
- process.exit(1);
144
+ const _main = async (startPath = process.cwd()) => {
145
+ // Initialize chalk
146
+ const chalk = await _initChalk();
147
+
148
+ // Check arguments
149
+ if (process.argv.length !== 4) {
150
+ _printError('Invalid number of arguments', chalk);
151
+ console.log('Usage: node check-task-dependencies.cjs <plan-id-or-path> <task-id>');
152
+ console.log('Example: node check-task-dependencies.cjs 16 03');
153
+ process.exit(1);
154
+ }
155
+
156
+ const inputId = process.argv[2];
157
+ const taskId = process.argv[3];
158
+
159
+ const resolved = resolvePlan(inputId, startPath);
160
+
161
+ if (!resolved) {
162
+ _printError(`Plan "${inputId}" not found or invalid`, chalk);
163
+ process.exit(1);
164
+ }
165
+
166
+ const {
167
+ planDir,
168
+ planId
169
+ } = resolved;
170
+ _printInfo(`Found plan directory: ${planDir}`);
171
+
172
+ // Find task file
173
+ const taskFile = _findTaskFile(planDir, taskId);
174
+
175
+ if (!taskFile || !fs.existsSync(taskFile)) {
176
+ _printError(`Task with ID ${taskId} not found in plan ${planId}`, chalk);
177
+ process.exit(1);
178
+ }
179
+
180
+ _printInfo(`Checking task: ${path.basename(taskFile)}`);
181
+ console.log('');
182
+
183
+ // Read and parse task file
184
+ const taskContent = fs.readFileSync(taskFile, 'utf8');
185
+ const frontmatter = parseFrontmatter(taskContent);
186
+ const dependencies = _extractDependencies(frontmatter);
187
+
188
+ // Check if there are any dependencies
189
+ if (dependencies.length === 0) {
190
+ _printSuccess('Task has no dependencies - ready to execute!', chalk);
191
+ process.exit(0);
192
+ }
193
+
194
+ // Display dependencies
195
+ _printInfo('Task dependencies found:');
196
+ dependencies.forEach(dep => {
197
+ console.log(` - Task ${dep}`);
198
+ });
199
+ console.log('');
200
+
201
+ // Check each dependency
202
+ let allResolved = true;
203
+ let unresolvedDeps = [];
204
+ let resolvedCount = 0;
205
+ const totalDeps = dependencies.length;
206
+
207
+ _printInfo('Checking dependency status...');
208
+ console.log('');
209
+
210
+ for (const depId of dependencies) {
211
+ // Find dependency task file
212
+ const depFile = _findTaskFile(planDir, depId);
213
+
214
+ if (!depFile || !fs.existsSync(depFile)) {
215
+ _printError(`Dependency task ${depId} not found`, chalk);
216
+ allResolved = false;
217
+ unresolvedDeps.push(`${depId} (not found)`);
218
+ continue;
242
219
  }
243
220
 
244
- printInfo(`Checking task: ${path.basename(taskFile)}`);
245
- console.log('');
221
+ // Extract status from dependency task
222
+ const depContent = fs.readFileSync(depFile, 'utf8');
223
+ const depFrontmatter = parseFrontmatter(depContent);
224
+ const status = _extractStatus(depFrontmatter);
246
225
 
247
- // Read and parse task file
248
- const taskContent = fs.readFileSync(taskFile, 'utf8');
249
- const frontmatter = parseFrontmatter(taskContent);
250
- const dependencies = extractDependencies(frontmatter);
251
-
252
- // Check if there are any dependencies
253
- if (dependencies.length === 0) {
254
- printSuccess('Task has no dependencies - ready to execute!', chalk);
255
- process.exit(0);
256
- }
257
-
258
- // Display dependencies
259
- printInfo('Task dependencies found:');
260
- dependencies.forEach(dep => {
261
- console.log(` - Task ${dep}`);
262
- });
263
- console.log('');
264
-
265
- // Check each dependency
266
- let allResolved = true;
267
- let unresolvedDeps = [];
268
- let resolvedCount = 0;
269
- const totalDeps = dependencies.length;
270
-
271
- printInfo('Checking dependency status...');
272
- console.log('');
273
-
274
- for (const depId of dependencies) {
275
- // Find dependency task file
276
- const depFile = findTaskFile(planDir, depId);
277
-
278
- if (!depFile || !fs.existsSync(depFile)) {
279
- printError(`Dependency task ${depId} not found`, chalk);
280
- allResolved = false;
281
- unresolvedDeps.push(`${depId} (not found)`);
282
- continue;
283
- }
284
-
285
- // Extract status from dependency task
286
- const depContent = fs.readFileSync(depFile, 'utf8');
287
- const depFrontmatter = parseFrontmatter(depContent);
288
- const status = extractStatus(depFrontmatter);
289
-
290
- // Check if status is completed
291
- if (status === 'completed') {
292
- printSuccess(`Task ${depId} - Status: completed ✓`, chalk);
293
- resolvedCount++;
294
- } else {
295
- printWarning(`Task ${depId} - Status: ${status || 'unknown'} ✗`, chalk);
296
- allResolved = false;
297
- unresolvedDeps.push(`${depId} (${status || 'unknown'})`);
298
- }
299
- }
300
-
301
- console.log('');
302
- printInfo('=========================================');
303
- printInfo('Dependency Check Summary');
304
- printInfo('=========================================');
305
- printInfo(`Total dependencies: ${totalDeps}`);
306
- printInfo(`Resolved: ${resolvedCount}`);
307
- printInfo(`Unresolved: ${totalDeps - resolvedCount}`);
308
- console.log('');
309
-
310
- if (allResolved) {
311
- printSuccess(`All dependencies are resolved! Task ${taskId} is ready to execute.`, chalk);
312
- process.exit(0);
226
+ // Check if status is completed
227
+ if (status === 'completed') {
228
+ _printSuccess(`Task ${depId} - Status: completed ✓`, chalk);
229
+ resolvedCount++;
313
230
  } else {
314
- printError(`Task ${taskId} has unresolved dependencies:`, chalk);
315
- unresolvedDeps.forEach(dep => {
316
- console.log(dep);
317
- });
318
- printInfo('Please complete the dependencies before executing this task.');
319
- process.exit(1);
231
+ _printWarning(`Task ${depId} - Status: ${status || 'unknown'} ✗`, chalk);
232
+ allResolved = false;
233
+ unresolvedDeps.push(`${depId} (${status || 'unknown'})`);
320
234
  }
235
+ }
236
+
237
+ console.log('');
238
+ _printInfo('=========================================');
239
+ _printInfo('Dependency Check Summary');
240
+ _printInfo('=========================================');
241
+ _printInfo(`Total dependencies: ${totalDeps}`);
242
+ _printInfo(`Resolved: ${resolvedCount}`);
243
+ _printInfo(`Unresolved: ${totalDeps - resolvedCount}`);
244
+ console.log('');
245
+
246
+ if (allResolved) {
247
+ _printSuccess(`All dependencies are resolved! Task ${taskId} is ready to execute.`, chalk);
248
+ process.exit(0);
249
+ } else {
250
+ _printError(`Task ${taskId} has unresolved dependencies:`, chalk);
251
+ unresolvedDeps.forEach(dep => {
252
+ console.log(dep);
253
+ });
254
+ _printInfo('Please complete the dependencies before executing this task.');
255
+ process.exit(1);
256
+ }
321
257
  };
322
258
 
323
259
  // Run the script
324
260
  if (require.main === module) {
325
- main().catch((error) => {
326
- console.error('Script execution failed:', error);
327
- process.exit(1);
328
- });
261
+ _main().catch((error) => {
262
+ console.error('Script execution failed:', error);
263
+ process.exit(1);
264
+ });
329
265
  }
330
266
 
331
- module.exports = { main };
267
+ module.exports = {
268
+ _main
269
+ };
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { findTaskManagerRoot } = require('./shared-utils.cjs');
4
+
5
+ try {
6
+ const root = findTaskManagerRoot();
7
+ process.exit(root ? 0 : 1);
8
+ } catch (err) {
9
+ process.exit(1);
10
+ }
@@ -2,85 +2,48 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
- const { findTaskManagerRoot, extractIdFromFrontmatter } = require('./shared-utils.cjs');
5
+ const { findTaskManagerRoot, getAllPlans } = require('./shared-utils.cjs');
6
6
 
7
7
  /**
8
8
  * Error logging utility
9
+ * @private
9
10
  * @param {string} message - Error message
10
11
  * @param {...any} args - Additional arguments to log
11
12
  */
12
- function errorLog(message, ...args) {
13
+ function _errorLog(message, ...args) {
13
14
  console.error(`[ERROR] ${message}`, ...args);
14
15
  }
15
16
 
16
17
  /**
17
18
  * Get the next available plan ID by scanning existing plan files
19
+ * @private
18
20
  * @returns {number} Next available plan ID
19
21
  */
20
- function getNextPlanId() {
22
+ function _getNextPlanId() {
21
23
  const taskManagerRoot = findTaskManagerRoot();
22
24
 
23
25
  if (!taskManagerRoot) {
24
- errorLog('No .ai/task-manager/plans directory found in current directory or any parent directory.');
25
- errorLog('');
26
- errorLog('Please ensure you are in a project with task manager initialized, or navigate to the correct');
27
- errorLog('project directory. The task manager looks for the .ai/task-manager/plans structure starting');
28
- errorLog('from the current working directory and traversing upward through parent directories.');
29
- errorLog('');
30
- errorLog(`Current working directory: ${process.cwd()}`);
26
+ _errorLog('No .ai/task-manager/plans directory found in current directory or any parent directory.');
27
+ _errorLog('');
28
+ _errorLog('Please ensure you are in a project with task manager initialized, or navigate to the correct');
29
+ _errorLog('project directory. The task manager looks for the .ai/task-manager/plans structure starting');
30
+ _errorLog('from the current working directory and traversing upward through parent directories.');
31
+ _errorLog('');
32
+ _errorLog(`Current working directory: ${process.cwd()}`);
31
33
  process.exit(1);
32
34
  }
33
35
 
34
- const plansDir = path.join(taskManagerRoot, 'plans');
35
- const archiveDir = path.join(taskManagerRoot, 'archive');
36
-
37
- let maxId = 0;
38
-
39
- // Scan both plans and archive directories
40
- [plansDir, archiveDir].forEach(dir => {
41
- if (!fs.existsSync(dir)) {
42
- return;
43
- }
44
-
45
- try {
46
- const entries = fs.readdirSync(dir, { withFileTypes: true });
47
-
48
- entries.forEach(entry => {
49
- if (entry.isDirectory() && entry.name.match(/^\d+--/)) {
50
- // This is a plan directory, look for plan files inside
51
- const planDirPath = path.join(dir, entry.name);
52
-
53
- try {
54
- const planDirEntries = fs.readdirSync(planDirPath, { withFileTypes: true });
55
-
56
- planDirEntries.forEach(planEntry => {
57
- if (planEntry.isFile() && planEntry.name.match(/^plan-\d+--.*\.md$/)) {
58
- const filePath = path.join(planDirPath, planEntry.name);
59
-
60
- try {
61
- const content = fs.readFileSync(filePath, 'utf8');
62
- const frontmatterId = extractIdFromFrontmatter(content, filePath);
63
-
64
- if (frontmatterId !== null && frontmatterId > maxId) {
65
- maxId = frontmatterId;
66
- }
67
- } catch (err) {
68
- errorLog(`Failed to read file ${filePath}: ${err.message}`);
69
- }
70
- }
71
- });
72
- } catch (err) {
73
- errorLog(`Failed to read plan directory ${planDirPath}: ${err.message}`);
74
- }
75
- }
76
- });
77
- } catch (err) {
78
- errorLog(`Failed to read directory ${dir}: ${err.message}`);
79
- }
80
- });
36
+ const plans = getAllPlans(taskManagerRoot);
37
+ const maxId = plans.reduce((max, p) => Math.max(max, p.id), 0);
81
38
 
82
39
  return maxId + 1;
83
40
  }
84
41
 
85
- // Output the next plan ID
86
- console.log(getNextPlanId());
42
+ // Output the next plan ID if run directly
43
+ if (require.main === module) {
44
+ console.log(_getNextPlanId());
45
+ }
46
+
47
+ module.exports = {
48
+ _getNextPlanId
49
+ };