@e0ipso/ai-task-manager 1.26.9 → 1.26.11

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.9",
3
+ "version": "1.26.11",
4
4
  "description": "Task management for AI coding assistants",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -4,9 +4,24 @@ This hook contains the phase preparation logic that should be executed before st
4
4
 
5
5
  ## Phase Pre-Execution
6
6
 
7
- Before starting execution check if you are in the `main` branch. If so, create a git branch to work on this blueprint use the plan name for the branch name.
7
+ ### Feature Branch Creation
8
8
 
9
- If there are unstaged changes in the `main` branch, do not create a feature branch.
9
+ Create a feature branch for this plan execution (only runs from main/master with a clean working tree):
10
+
11
+ ```bash
12
+ # Create feature branch (handles all edge cases automatically)
13
+ node $root/config/scripts/create-feature-branch.cjs $1
14
+
15
+ # Exit codes:
16
+ # 0 = Success (branch created, already exists, or not on main/master)
17
+ # 1 = Error (not git repo, uncommitted changes, or plan not found)
18
+ ```
19
+
20
+ **Behavior**:
21
+ - From `main`/`master` with clean tree: Creates `feature/{planId}--{plan-name}` branch
22
+ - From `main`/`master` with uncommitted changes: Exits with error (exit 1)
23
+ - From feature branch: Proceeds without creating a new branch
24
+ - Branch already exists: Proceeds normally
10
25
 
11
26
  ## Phase Execution Workflow
12
27
 
@@ -7,47 +7,24 @@
7
7
  * Returns: 0 if all dependencies are resolved, 1 if not
8
8
  */
9
9
 
10
- const fs = require('fs-extra');
10
+ const fs = require('fs');
11
11
  const path = require('path');
12
12
  const {
13
13
  resolvePlan,
14
14
  parseFrontmatter
15
15
  } = require('./shared-utils.cjs');
16
16
 
17
- // Chalk instance - loaded dynamically to handle ESM module
18
- let chalkInstance = null;
19
-
20
- // Initialize chalk instance dynamically
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;
35
- }
36
-
37
17
  // Color functions for output
38
- const _printError = (message, chalk) => {
39
- const formattedMessage = chalk?.red(`ERROR: ${message}`) || `ERROR: ${message}`;
40
- console.error(formattedMessage);
18
+ const _printError = (message) => {
19
+ console.error(`ERROR: ${message}`);
41
20
  };
42
21
 
43
- const _printSuccess = (message, chalk) => {
44
- const formattedMessage = chalk?.green(`✓ ${message}`) || `✓ ${message}`;
45
- console.log(formattedMessage);
22
+ const _printSuccess = (message) => {
23
+ console.log(`✓ ${message}`);
46
24
  };
47
25
 
48
- const _printWarning = (message, chalk) => {
49
- const formattedMessage = chalk?.yellow(`⚠ ${message}`) || `⚠ ${message}`;
50
- console.log(formattedMessage);
26
+ const _printWarning = (message) => {
27
+ console.log(`⚠ ${message}`);
51
28
  };
52
29
 
53
30
  const _printInfo = (message) => {
@@ -141,13 +118,10 @@ const _extractStatus = (frontmatter) => {
141
118
  };
142
119
 
143
120
  // Main function
144
- const _main = async (startPath = process.cwd()) => {
145
- // Initialize chalk
146
- const chalk = await _initChalk();
147
-
121
+ const _main = (startPath = process.cwd()) => {
148
122
  // Check arguments
149
123
  if (process.argv.length !== 4) {
150
- _printError('Invalid number of arguments', chalk);
124
+ _printError('Invalid number of arguments');
151
125
  console.log('Usage: node check-task-dependencies.cjs <plan-id-or-path> <task-id>');
152
126
  console.log('Example: node check-task-dependencies.cjs 16 03');
153
127
  process.exit(1);
@@ -159,7 +133,7 @@ const _main = async (startPath = process.cwd()) => {
159
133
  const resolved = resolvePlan(inputId, startPath);
160
134
 
161
135
  if (!resolved) {
162
- _printError(`Plan "${inputId}" not found or invalid`, chalk);
136
+ _printError(`Plan "${inputId}" not found or invalid`);
163
137
  process.exit(1);
164
138
  }
165
139
 
@@ -173,7 +147,7 @@ const _main = async (startPath = process.cwd()) => {
173
147
  const taskFile = _findTaskFile(planDir, taskId);
174
148
 
175
149
  if (!taskFile || !fs.existsSync(taskFile)) {
176
- _printError(`Task with ID ${taskId} not found in plan ${planId}`, chalk);
150
+ _printError(`Task with ID ${taskId} not found in plan ${planId}`);
177
151
  process.exit(1);
178
152
  }
179
153
 
@@ -187,7 +161,7 @@ const _main = async (startPath = process.cwd()) => {
187
161
 
188
162
  // Check if there are any dependencies
189
163
  if (dependencies.length === 0) {
190
- _printSuccess('Task has no dependencies - ready to execute!', chalk);
164
+ _printSuccess('Task has no dependencies - ready to execute!');
191
165
  process.exit(0);
192
166
  }
193
167
 
@@ -212,7 +186,7 @@ const _main = async (startPath = process.cwd()) => {
212
186
  const depFile = _findTaskFile(planDir, depId);
213
187
 
214
188
  if (!depFile || !fs.existsSync(depFile)) {
215
- _printError(`Dependency task ${depId} not found`, chalk);
189
+ _printError(`Dependency task ${depId} not found`);
216
190
  allResolved = false;
217
191
  unresolvedDeps.push(`${depId} (not found)`);
218
192
  continue;
@@ -225,10 +199,10 @@ const _main = async (startPath = process.cwd()) => {
225
199
 
226
200
  // Check if status is completed
227
201
  if (status === 'completed') {
228
- _printSuccess(`Task ${depId} - Status: completed ✓`, chalk);
202
+ _printSuccess(`Task ${depId} - Status: completed ✓`);
229
203
  resolvedCount++;
230
204
  } else {
231
- _printWarning(`Task ${depId} - Status: ${status || 'unknown'} ✗`, chalk);
205
+ _printWarning(`Task ${depId} - Status: ${status || 'unknown'} ✗`);
232
206
  allResolved = false;
233
207
  unresolvedDeps.push(`${depId} (${status || 'unknown'})`);
234
208
  }
@@ -244,10 +218,10 @@ const _main = async (startPath = process.cwd()) => {
244
218
  console.log('');
245
219
 
246
220
  if (allResolved) {
247
- _printSuccess(`All dependencies are resolved! Task ${taskId} is ready to execute.`, chalk);
221
+ _printSuccess(`All dependencies are resolved! Task ${taskId} is ready to execute.`);
248
222
  process.exit(0);
249
223
  } else {
250
- _printError(`Task ${taskId} has unresolved dependencies:`, chalk);
224
+ _printError(`Task ${taskId} has unresolved dependencies:`);
251
225
  unresolvedDeps.forEach(dep => {
252
226
  console.log(dep);
253
227
  });
@@ -258,10 +232,7 @@ const _main = async (startPath = process.cwd()) => {
258
232
 
259
233
  // Run the script
260
234
  if (require.main === module) {
261
- _main().catch((error) => {
262
- console.error('Script execution failed:', error);
263
- process.exit(1);
264
- });
235
+ _main();
265
236
  }
266
237
 
267
238
  module.exports = {
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Script: create-feature-branch.cjs
5
+ * Purpose: Create a git feature branch for a plan execution
6
+ * Usage: node create-feature-branch.cjs <plan-id-or-path>
7
+ *
8
+ * Exit codes:
9
+ * 0 = Success (branch created, already exists, or not on main/master)
10
+ * 1 = Error (not git repo, uncommitted changes, plan not found)
11
+ */
12
+
13
+ const { execSync } = require('child_process');
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { resolvePlan } = require('./shared-utils.cjs');
17
+
18
+ // Color functions for output
19
+ const _printError = (message) => {
20
+ console.error(`ERROR: ${message}`);
21
+ };
22
+
23
+ const _printSuccess = (message) => {
24
+ console.log(`✓ ${message}`);
25
+ };
26
+
27
+ const _printWarning = (message) => {
28
+ console.log(`⚠ ${message}`);
29
+ };
30
+
31
+ const _printInfo = (message) => {
32
+ console.log(message);
33
+ };
34
+
35
+ /**
36
+ * Execute a git command and return the output
37
+ * @param {string} command - Git command to execute
38
+ * @returns {string|null} Command output or null on error
39
+ */
40
+ const _execGit = (command) => {
41
+ try {
42
+ return execSync(command, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
43
+ } catch (_error) {
44
+ return null;
45
+ }
46
+ };
47
+
48
+ /**
49
+ * Check if current directory is inside a git repository
50
+ * @returns {boolean}
51
+ */
52
+ const _isGitRepo = () => {
53
+ const result = _execGit('git rev-parse --is-inside-work-tree');
54
+ return result === 'true';
55
+ };
56
+
57
+ /**
58
+ * Get current git branch name
59
+ * @returns {string|null}
60
+ */
61
+ const _getCurrentBranch = () => {
62
+ return _execGit('git rev-parse --abbrev-ref HEAD');
63
+ };
64
+
65
+ /**
66
+ * Check if working tree has uncommitted changes
67
+ * @returns {boolean}
68
+ */
69
+ const _hasUncommittedChanges = () => {
70
+ const status = _execGit('git status --porcelain');
71
+ return status !== null && status.length > 0;
72
+ };
73
+
74
+ /**
75
+ * Check if a branch exists locally or remotely
76
+ * @param {string} branchName - Branch name to check
77
+ * @returns {boolean}
78
+ */
79
+ const _branchExists = (branchName) => {
80
+ // Check local branches
81
+ const localBranches = _execGit('git branch --list');
82
+ if (localBranches && localBranches.split('\n').some(b => b.trim().replace('* ', '') === branchName)) {
83
+ return true;
84
+ }
85
+
86
+ // Check remote branches
87
+ const remoteBranches = _execGit('git branch -r --list');
88
+ if (remoteBranches && remoteBranches.split('\n').some(b => b.trim().includes(branchName))) {
89
+ return true;
90
+ }
91
+
92
+ return false;
93
+ };
94
+
95
+ /**
96
+ * Sanitize plan name for use in branch name
97
+ * @param {string} planName - Original plan name from directory
98
+ * @returns {string} Sanitized branch name segment
99
+ */
100
+ const _sanitizeBranchName = (planName) => {
101
+ return planName
102
+ .toLowerCase()
103
+ .replace(/[^a-z0-9-]/g, '-') // Replace non-alphanumeric chars with hyphens
104
+ .replace(/-+/g, '-') // Collapse multiple hyphens
105
+ .replace(/^-|-$/g, '') // Remove leading/trailing hyphens
106
+ .substring(0, 60); // Max 60 chars
107
+ };
108
+
109
+ /**
110
+ * Extract plan name from plan directory
111
+ * @param {string} planDir - Full path to plan directory
112
+ * @returns {string} Plan name portion (e.g., "update-docs" from "58--update-docs")
113
+ */
114
+ const _extractPlanName = (planDir) => {
115
+ const dirName = path.basename(planDir);
116
+ // Match pattern: {id}--{name}
117
+ const match = dirName.match(/^\d+--(.+)$/);
118
+ return match ? match[1] : dirName;
119
+ };
120
+
121
+ // Main function
122
+ const _main = (startPath = process.cwd()) => {
123
+ // Check arguments
124
+ if (process.argv.length < 3) {
125
+ _printError('Missing plan ID argument');
126
+ console.log('Usage: node create-feature-branch.cjs <plan-id-or-path>');
127
+ console.log('Example: node create-feature-branch.cjs 58');
128
+ process.exit(1);
129
+ }
130
+
131
+ const inputId = process.argv[2];
132
+
133
+ // Step 1: Check if this is a git repository
134
+ if (!_isGitRepo()) {
135
+ _printError('Not a git repository');
136
+ process.exit(1);
137
+ }
138
+
139
+ // Step 2: Resolve the plan
140
+ const resolved = resolvePlan(inputId, startPath);
141
+
142
+ if (!resolved) {
143
+ _printError(`Plan "${inputId}" not found or invalid`);
144
+ process.exit(1);
145
+ }
146
+
147
+ const { planDir, planId } = resolved;
148
+ _printInfo(`Found plan: ${path.basename(planDir)}`);
149
+
150
+ // Step 3: Check current branch
151
+ const currentBranch = _getCurrentBranch();
152
+
153
+ if (!currentBranch) {
154
+ _printError('Could not determine current git branch');
155
+ process.exit(1);
156
+ }
157
+
158
+ if (currentBranch !== 'main' && currentBranch !== 'master') {
159
+ _printWarning(`Not on main/master branch (current: ${currentBranch})`);
160
+ _printInfo('Proceeding without creating a new branch');
161
+ process.exit(0);
162
+ }
163
+
164
+ // Step 4: Check for uncommitted changes
165
+ if (_hasUncommittedChanges()) {
166
+ _printError('Uncommitted changes detected in working tree');
167
+ _printInfo('Please commit or stash your changes before creating a feature branch');
168
+ process.exit(1);
169
+ }
170
+
171
+ // Step 5: Build branch name
172
+ const planName = _extractPlanName(planDir);
173
+ const sanitizedName = _sanitizeBranchName(planName);
174
+ const branchName = `feature/${planId}--${sanitizedName}`;
175
+
176
+ // Step 6: Check if branch already exists
177
+ if (_branchExists(branchName)) {
178
+ _printWarning(`Branch "${branchName}" already exists`);
179
+ _printInfo('Proceeding with existing branch');
180
+ process.exit(0);
181
+ }
182
+
183
+ // Step 7: Create and checkout the branch
184
+ const createResult = _execGit(`git checkout -b "${branchName}"`);
185
+
186
+ if (createResult === null) {
187
+ _printError(`Failed to create branch "${branchName}"`);
188
+ process.exit(1);
189
+ }
190
+
191
+ _printSuccess(`Created and switched to branch: ${branchName}`);
192
+ process.exit(0);
193
+ };
194
+
195
+ // Run the script
196
+ if (require.main === module) {
197
+ _main();
198
+ }
199
+
200
+ module.exports = {
201
+ _main,
202
+ _sanitizeBranchName,
203
+ _extractPlanName
204
+ };
@@ -112,7 +112,7 @@ Otherwise, if tasks exist, proceed directly to execution.
112
112
 
113
113
  Use your internal Todo task tool to track the execution of all phases, and the final update of the plan with the summary. Example:
114
114
 
115
- - [ ] Create feature branch from the main branch.
115
+ - [ ] Create feature branch via `node $root/config/scripts/create-feature-branch.cjs $1`
116
116
  - [ ] Validate or auto-generate tasks and execution blueprint if missing.
117
117
  - [ ] Execute $root/.ai/task-manager/config/hooks/PRE_PHASE.md hook before Phase 1.
118
118
  - [ ] Phase 1: Execute 1 task(s) in parallel.