@e0ipso/ai-task-manager 1.26.8 → 1.26.10
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 +1 -1
- package/templates/ai-task-manager/config/hooks/PRE_PHASE.md +17 -2
- package/templates/ai-task-manager/config/hooks/PRE_PLAN.md +1 -0
- package/templates/ai-task-manager/config/scripts/create-feature-branch.cjs +232 -0
- package/templates/assistant/commands/tasks/execute-blueprint.md +1 -1
package/package.json
CHANGED
|
@@ -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
|
-
|
|
7
|
+
### Feature Branch Creation
|
|
8
8
|
|
|
9
|
-
|
|
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
|
|
|
@@ -10,6 +10,7 @@ This hook provides pre-planning guidance to ensure scope control, simplicity pri
|
|
|
10
10
|
- **Question Everything Extra**: If not directly mentioned by the user, don't add it
|
|
11
11
|
- **Avoid Feature Creep**: Resist the urge to add "helpful" features or "nice-to-have" additions
|
|
12
12
|
- **YAGNI Principle**: _You Aren't Gonna Need It_ - don't build for hypothetical future needs
|
|
13
|
+
- **Do NOT add backwards compatibility, unless requested**: If there is a potential BC break, ask the user if they want to BC support. Do not assume the want it.
|
|
13
14
|
|
|
14
15
|
**Common Scope Creep Anti-Patterns to Avoid:**
|
|
15
16
|
1. Adding extra commands or features "for completeness"
|
|
@@ -0,0 +1,232 @@
|
|
|
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
|
+
// Chalk instance - loaded dynamically to handle ESM module
|
|
19
|
+
let chalkInstance = null;
|
|
20
|
+
|
|
21
|
+
// Initialize chalk instance dynamically
|
|
22
|
+
async function _initChalk() {
|
|
23
|
+
if (chalkInstance) return chalkInstance;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const { default: chalk } = await import('chalk');
|
|
27
|
+
chalkInstance = chalk;
|
|
28
|
+
} catch (_error) {
|
|
29
|
+
// Chalk not available, will fall back to plain console output
|
|
30
|
+
chalkInstance = null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return chalkInstance;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Color functions for output
|
|
37
|
+
const _printError = (message, chalk) => {
|
|
38
|
+
const formattedMessage = chalk?.red(`ERROR: ${message}`) || `ERROR: ${message}`;
|
|
39
|
+
console.error(formattedMessage);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const _printSuccess = (message, chalk) => {
|
|
43
|
+
const formattedMessage = chalk?.green(`✓ ${message}`) || `✓ ${message}`;
|
|
44
|
+
console.log(formattedMessage);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const _printWarning = (message, chalk) => {
|
|
48
|
+
const formattedMessage = chalk?.yellow(`⚠ ${message}`) || `⚠ ${message}`;
|
|
49
|
+
console.log(formattedMessage);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const _printInfo = (message, chalk) => {
|
|
53
|
+
const formattedMessage = chalk?.blue(message) || message;
|
|
54
|
+
console.log(formattedMessage);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Execute a git command and return the output
|
|
59
|
+
* @param {string} command - Git command to execute
|
|
60
|
+
* @returns {string|null} Command output or null on error
|
|
61
|
+
*/
|
|
62
|
+
const _execGit = (command) => {
|
|
63
|
+
try {
|
|
64
|
+
return execSync(command, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
65
|
+
} catch (_error) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check if current directory is inside a git repository
|
|
72
|
+
* @returns {boolean}
|
|
73
|
+
*/
|
|
74
|
+
const _isGitRepo = () => {
|
|
75
|
+
const result = _execGit('git rev-parse --is-inside-work-tree');
|
|
76
|
+
return result === 'true';
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get current git branch name
|
|
81
|
+
* @returns {string|null}
|
|
82
|
+
*/
|
|
83
|
+
const _getCurrentBranch = () => {
|
|
84
|
+
return _execGit('git rev-parse --abbrev-ref HEAD');
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Check if working tree has uncommitted changes
|
|
89
|
+
* @returns {boolean}
|
|
90
|
+
*/
|
|
91
|
+
const _hasUncommittedChanges = () => {
|
|
92
|
+
const status = _execGit('git status --porcelain');
|
|
93
|
+
return status !== null && status.length > 0;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Check if a branch exists locally or remotely
|
|
98
|
+
* @param {string} branchName - Branch name to check
|
|
99
|
+
* @returns {boolean}
|
|
100
|
+
*/
|
|
101
|
+
const _branchExists = (branchName) => {
|
|
102
|
+
// Check local branches
|
|
103
|
+
const localBranches = _execGit('git branch --list');
|
|
104
|
+
if (localBranches && localBranches.split('\n').some(b => b.trim().replace('* ', '') === branchName)) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check remote branches
|
|
109
|
+
const remoteBranches = _execGit('git branch -r --list');
|
|
110
|
+
if (remoteBranches && remoteBranches.split('\n').some(b => b.trim().includes(branchName))) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return false;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Sanitize plan name for use in branch name
|
|
119
|
+
* @param {string} planName - Original plan name from directory
|
|
120
|
+
* @returns {string} Sanitized branch name segment
|
|
121
|
+
*/
|
|
122
|
+
const _sanitizeBranchName = (planName) => {
|
|
123
|
+
return planName
|
|
124
|
+
.toLowerCase()
|
|
125
|
+
.replace(/[^a-z0-9-]/g, '-') // Replace non-alphanumeric chars with hyphens
|
|
126
|
+
.replace(/-+/g, '-') // Collapse multiple hyphens
|
|
127
|
+
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens
|
|
128
|
+
.substring(0, 60); // Max 60 chars
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Extract plan name from plan directory
|
|
133
|
+
* @param {string} planDir - Full path to plan directory
|
|
134
|
+
* @returns {string} Plan name portion (e.g., "update-docs" from "58--update-docs")
|
|
135
|
+
*/
|
|
136
|
+
const _extractPlanName = (planDir) => {
|
|
137
|
+
const dirName = path.basename(planDir);
|
|
138
|
+
// Match pattern: {id}--{name}
|
|
139
|
+
const match = dirName.match(/^\d+--(.+)$/);
|
|
140
|
+
return match ? match[1] : dirName;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Main function
|
|
144
|
+
const _main = async (startPath = process.cwd()) => {
|
|
145
|
+
// Initialize chalk
|
|
146
|
+
const chalk = await _initChalk();
|
|
147
|
+
|
|
148
|
+
// Check arguments
|
|
149
|
+
if (process.argv.length < 3) {
|
|
150
|
+
_printError('Missing plan ID argument', chalk);
|
|
151
|
+
console.log('Usage: node create-feature-branch.cjs <plan-id-or-path>');
|
|
152
|
+
console.log('Example: node create-feature-branch.cjs 58');
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const inputId = process.argv[2];
|
|
157
|
+
|
|
158
|
+
// Step 1: Check if this is a git repository
|
|
159
|
+
if (!_isGitRepo()) {
|
|
160
|
+
_printError('Not a git repository', chalk);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Step 2: Resolve the plan
|
|
165
|
+
const resolved = resolvePlan(inputId, startPath);
|
|
166
|
+
|
|
167
|
+
if (!resolved) {
|
|
168
|
+
_printError(`Plan "${inputId}" not found or invalid`, chalk);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const { planDir, planId } = resolved;
|
|
173
|
+
_printInfo(`Found plan: ${path.basename(planDir)}`, chalk);
|
|
174
|
+
|
|
175
|
+
// Step 3: Check current branch
|
|
176
|
+
const currentBranch = _getCurrentBranch();
|
|
177
|
+
|
|
178
|
+
if (!currentBranch) {
|
|
179
|
+
_printError('Could not determine current git branch', chalk);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (currentBranch !== 'main' && currentBranch !== 'master') {
|
|
184
|
+
_printWarning(`Not on main/master branch (current: ${currentBranch})`, chalk);
|
|
185
|
+
_printInfo('Proceeding without creating a new branch', chalk);
|
|
186
|
+
process.exit(0);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Step 4: Check for uncommitted changes
|
|
190
|
+
if (_hasUncommittedChanges()) {
|
|
191
|
+
_printError('Uncommitted changes detected in working tree', chalk);
|
|
192
|
+
_printInfo('Please commit or stash your changes before creating a feature branch', chalk);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Step 5: Build branch name
|
|
197
|
+
const planName = _extractPlanName(planDir);
|
|
198
|
+
const sanitizedName = _sanitizeBranchName(planName);
|
|
199
|
+
const branchName = `feature/${planId}--${sanitizedName}`;
|
|
200
|
+
|
|
201
|
+
// Step 6: Check if branch already exists
|
|
202
|
+
if (_branchExists(branchName)) {
|
|
203
|
+
_printWarning(`Branch "${branchName}" already exists`, chalk);
|
|
204
|
+
_printInfo('Proceeding with existing branch', chalk);
|
|
205
|
+
process.exit(0);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Step 7: Create and checkout the branch
|
|
209
|
+
const createResult = _execGit(`git checkout -b "${branchName}"`);
|
|
210
|
+
|
|
211
|
+
if (createResult === null) {
|
|
212
|
+
_printError(`Failed to create branch "${branchName}"`, chalk);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
_printSuccess(`Created and switched to branch: ${branchName}`, chalk);
|
|
217
|
+
process.exit(0);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Run the script
|
|
221
|
+
if (require.main === module) {
|
|
222
|
+
_main().catch((error) => {
|
|
223
|
+
console.error('Script execution failed:', error);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
module.exports = {
|
|
229
|
+
_main,
|
|
230
|
+
_sanitizeBranchName,
|
|
231
|
+
_extractPlanName
|
|
232
|
+
};
|
|
@@ -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
|
|
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.
|