@e0ipso/ai-task-manager 1.36.1 → 1.38.0

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.
Files changed (72) hide show
  1. package/README.md +26 -21
  2. package/dist/cli.js +1 -32
  3. package/dist/cli.js.map +1 -1
  4. package/dist/conflict-detector.d.ts.map +1 -1
  5. package/dist/conflict-detector.js +0 -4
  6. package/dist/conflict-detector.js.map +1 -1
  7. package/dist/index.d.ts +3 -16
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +50 -245
  10. package/dist/index.js.map +1 -1
  11. package/dist/metadata.d.ts +9 -0
  12. package/dist/metadata.d.ts.map +1 -1
  13. package/dist/metadata.js +14 -0
  14. package/dist/metadata.js.map +1 -1
  15. package/dist/types.d.ts +18 -18
  16. package/dist/types.d.ts.map +1 -1
  17. package/dist/utils.d.ts +27 -58
  18. package/dist/utils.d.ts.map +1 -1
  19. package/dist/utils.js +93 -219
  20. package/dist/utils.js.map +1 -1
  21. package/package.json +4 -2
  22. package/templates/ai-task-manager/config/TASK_MANAGER.md +3 -3
  23. package/templates/ai-task-manager/config/hooks/PRE_PHASE.md +6 -26
  24. package/templates/ai-task-manager/config/hooks/PRE_TASK_ASSIGNMENT.md +6 -24
  25. package/templates/ai-task-manager/config/templates/PLAN_TEMPLATE.md +1 -1
  26. package/templates/{assistant → harness}/agents/plan-creator.md +2 -2
  27. package/templates/harness/skills/task-create-plan/SKILL.md +120 -0
  28. package/templates/harness/skills/task-create-plan/scripts/find-task-manager-root.cjs +116 -0
  29. package/templates/harness/skills/task-create-plan/scripts/get-next-plan-id.cjs +214 -0
  30. package/templates/harness/skills/task-execute-blueprint/SKILL.md +139 -0
  31. package/templates/harness/skills/task-execute-blueprint/scripts/create-feature-branch.cjs +376 -0
  32. package/templates/harness/skills/task-execute-blueprint/scripts/find-task-manager-root.cjs +116 -0
  33. package/templates/harness/skills/task-execute-blueprint/scripts/validate-plan-blueprint.cjs +375 -0
  34. package/templates/harness/skills/task-execute-task/SKILL.md +195 -0
  35. package/templates/harness/skills/task-execute-task/scripts/check-task-dependencies.cjs +437 -0
  36. package/templates/harness/skills/task-execute-task/scripts/find-task-manager-root.cjs +116 -0
  37. package/templates/harness/skills/task-execute-task/scripts/validate-plan-blueprint.cjs +375 -0
  38. package/templates/harness/skills/task-full-workflow/SKILL.md +378 -0
  39. package/templates/harness/skills/task-full-workflow/scripts/create-feature-branch.cjs +376 -0
  40. package/templates/harness/skills/task-full-workflow/scripts/find-task-manager-root.cjs +116 -0
  41. package/templates/harness/skills/task-full-workflow/scripts/get-next-plan-id.cjs +214 -0
  42. package/templates/harness/skills/task-full-workflow/scripts/get-next-task-id.cjs +312 -0
  43. package/templates/harness/skills/task-full-workflow/scripts/validate-plan-blueprint.cjs +375 -0
  44. package/templates/harness/skills/task-generate-tasks/SKILL.md +244 -0
  45. package/templates/harness/skills/task-generate-tasks/scripts/find-task-manager-root.cjs +116 -0
  46. package/templates/harness/skills/task-generate-tasks/scripts/get-next-task-id.cjs +312 -0
  47. package/templates/harness/skills/task-generate-tasks/scripts/validate-plan-blueprint.cjs +375 -0
  48. package/templates/harness/skills/task-refine-plan/SKILL.md +205 -0
  49. package/templates/harness/skills/task-refine-plan/scripts/find-task-manager-root.cjs +116 -0
  50. package/templates/harness/skills/task-refine-plan/scripts/validate-plan-blueprint.cjs +375 -0
  51. package/dist/exec.d.ts +0 -13
  52. package/dist/exec.d.ts.map +0 -1
  53. package/dist/exec.js +0 -261
  54. package/dist/exec.js.map +0 -1
  55. package/templates/ai-task-manager/config/scripts/check-task-dependencies.cjs +0 -240
  56. package/templates/ai-task-manager/config/scripts/compose-prompt.cjs +0 -234
  57. package/templates/ai-task-manager/config/scripts/create-feature-branch.cjs +0 -204
  58. package/templates/ai-task-manager/config/scripts/extract-task-skills.cjs +0 -84
  59. package/templates/ai-task-manager/config/scripts/find-root.cjs +0 -10
  60. package/templates/ai-task-manager/config/scripts/get-next-plan-id.cjs +0 -49
  61. package/templates/ai-task-manager/config/scripts/get-next-task-id.cjs +0 -81
  62. package/templates/ai-task-manager/config/scripts/shared-utils.cjs +0 -418
  63. package/templates/ai-task-manager/config/scripts/validate-plan-blueprint.cjs +0 -138
  64. package/templates/assistant/commands/tasks/create-plan-auto.md +0 -174
  65. package/templates/assistant/commands/tasks/create-plan.md +0 -175
  66. package/templates/assistant/commands/tasks/execute-blueprint.md +0 -233
  67. package/templates/assistant/commands/tasks/execute-task.md +0 -351
  68. package/templates/assistant/commands/tasks/fix-broken-tests.md +0 -44
  69. package/templates/assistant/commands/tasks/full-workflow.md +0 -849
  70. package/templates/assistant/commands/tasks/generate-tasks.md +0 -348
  71. package/templates/assistant/commands/tasks/refine-plan-auto.md +0 -172
  72. package/templates/assistant/commands/tasks/refine-plan.md +0 -163
@@ -1,240 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Script: check-task-dependencies.cjs
5
- * Purpose: Check if a task has all of its dependencies resolved (completed)
6
- * Usage: node check-task-dependencies.cjs <plan-id> <task-id>
7
- * Returns: 0 if all dependencies are resolved, 1 if not
8
- */
9
-
10
- const fs = require('fs');
11
- const path = require('path');
12
- const {
13
- resolvePlan,
14
- parseFrontmatter
15
- } = require('./shared-utils.cjs');
16
-
17
- // Color functions for output
18
- const _printError = (message) => {
19
- console.error(`ERROR: ${message}`);
20
- };
21
-
22
- const _printSuccess = (message) => {
23
- console.log(`✓ ${message}`);
24
- };
25
-
26
- const _printWarning = (message) => {
27
- console.log(`⚠ ${message}`);
28
- };
29
-
30
- const _printInfo = (message) => {
31
- console.log(message);
32
- };
33
-
34
- // Function to find task file with padded/unpadded ID handling
35
- const _findTaskFile = (planDir, taskId) => {
36
- const taskDir = path.join(planDir, 'tasks');
37
-
38
- if (!fs.existsSync(taskDir)) {
39
- return null;
40
- }
41
-
42
- const variations = [
43
- taskId,
44
- taskId.padStart(2, '0'),
45
- taskId.replace(/^0+/, '') || '0'
46
- ];
47
-
48
- const uniqueVariations = [...new Set(variations)];
49
-
50
- try {
51
- const files = fs.readdirSync(taskDir);
52
- const found = uniqueVariations.reduce((acc, v) => {
53
- if (acc) return acc;
54
- const match = files.find(f => f.startsWith(`${v}--`) && f.endsWith('.md'));
55
- return match ? path.join(taskDir, match) : null;
56
- }, null);
57
- return found;
58
- } catch (err) {
59
- return null;
60
- }
61
- };
62
-
63
-
64
- // Function to extract dependencies from frontmatter
65
- const _extractDependencies = (frontmatter) => {
66
- const lines = frontmatter.split('\n');
67
- const dependencies = [];
68
- let inDependenciesSection = false;
69
-
70
- for (let i = 0; i < lines.length; i++) {
71
- const line = lines[i];
72
-
73
- // Check for dependencies line
74
- if (line.match(/^dependencies:/)) {
75
- inDependenciesSection = true;
76
-
77
- // Check if dependencies are on the same line (array syntax)
78
- const arrayMatch = line.match(/\[(.*)\]/);
79
- if (arrayMatch) {
80
- const deps = arrayMatch[1]
81
- .split(',')
82
- .map(dep => dep.trim().replace(/['"]/g, ''))
83
- .filter(dep => dep.length > 0);
84
- dependencies.push(...deps);
85
- inDependenciesSection = false;
86
- }
87
- continue;
88
- }
89
-
90
- // If we're in dependencies section and hit a non-indented line that's not a list item, exit
91
- if (inDependenciesSection && line.match(/^[^ ]/) && !line.match(/^[ \t]*-/)) {
92
- inDependenciesSection = false;
93
- }
94
-
95
- // Parse list format dependencies
96
- if (inDependenciesSection && line.match(/^[ \t]*-/)) {
97
- const dep = line.replace(/^[ \t]*-[ \t]*/, '').replace(/[ \t]*$/, '').replace(/['"]/g, '');
98
- if (dep.length > 0) {
99
- dependencies.push(dep);
100
- }
101
- }
102
- }
103
-
104
- return dependencies;
105
- };
106
-
107
- // Function to extract status from frontmatter
108
- const _extractStatus = (frontmatter) => {
109
- const lines = frontmatter.split('\n');
110
-
111
- for (const line of lines) {
112
- if (line.match(/^status:/)) {
113
- return line.replace(/^status:[ \t]*/, '').replace(/^["']/, '').replace(/["']$/, '').trim();
114
- }
115
- }
116
-
117
- return null;
118
- };
119
-
120
- // Main function
121
- const _main = (startPath = process.cwd()) => {
122
- // Check arguments
123
- if (process.argv.length !== 4) {
124
- _printError('Invalid number of arguments');
125
- console.log('Usage: node check-task-dependencies.cjs <plan-id-or-path> <task-id>');
126
- console.log('Example: node check-task-dependencies.cjs 16 03');
127
- process.exit(1);
128
- }
129
-
130
- const inputId = process.argv[2];
131
- const taskId = process.argv[3];
132
-
133
- const resolved = resolvePlan(inputId, startPath);
134
-
135
- if (!resolved) {
136
- _printError(`Plan "${inputId}" not found or invalid`);
137
- process.exit(1);
138
- }
139
-
140
- const {
141
- planDir,
142
- planId
143
- } = resolved;
144
- _printInfo(`Found plan directory: ${planDir}`);
145
-
146
- // Find task file
147
- const taskFile = _findTaskFile(planDir, taskId);
148
-
149
- if (!taskFile || !fs.existsSync(taskFile)) {
150
- _printError(`Task with ID ${taskId} not found in plan ${planId}`);
151
- process.exit(1);
152
- }
153
-
154
- _printInfo(`Checking task: ${path.basename(taskFile)}`);
155
- console.log('');
156
-
157
- // Read and parse task file
158
- const taskContent = fs.readFileSync(taskFile, 'utf8');
159
- const frontmatter = parseFrontmatter(taskContent);
160
- const dependencies = _extractDependencies(frontmatter);
161
-
162
- // Check if there are any dependencies
163
- if (dependencies.length === 0) {
164
- _printSuccess('Task has no dependencies - ready to execute!');
165
- process.exit(0);
166
- }
167
-
168
- // Display dependencies
169
- _printInfo('Task dependencies found:');
170
- dependencies.forEach(dep => {
171
- console.log(` - Task ${dep}`);
172
- });
173
- console.log('');
174
-
175
- // Check each dependency
176
- let allResolved = true;
177
- let unresolvedDeps = [];
178
- let resolvedCount = 0;
179
- const totalDeps = dependencies.length;
180
-
181
- _printInfo('Checking dependency status...');
182
- console.log('');
183
-
184
- for (const depId of dependencies) {
185
- // Find dependency task file
186
- const depFile = _findTaskFile(planDir, depId);
187
-
188
- if (!depFile || !fs.existsSync(depFile)) {
189
- _printError(`Dependency task ${depId} not found`);
190
- allResolved = false;
191
- unresolvedDeps.push(`${depId} (not found)`);
192
- continue;
193
- }
194
-
195
- // Extract status from dependency task
196
- const depContent = fs.readFileSync(depFile, 'utf8');
197
- const depFrontmatter = parseFrontmatter(depContent);
198
- const status = _extractStatus(depFrontmatter);
199
-
200
- // Check if status is completed
201
- if (status === 'completed') {
202
- _printSuccess(`Task ${depId} - Status: completed ✓`);
203
- resolvedCount++;
204
- } else {
205
- _printWarning(`Task ${depId} - Status: ${status || 'unknown'} ✗`);
206
- allResolved = false;
207
- unresolvedDeps.push(`${depId} (${status || 'unknown'})`);
208
- }
209
- }
210
-
211
- console.log('');
212
- _printInfo('=========================================');
213
- _printInfo('Dependency Check Summary');
214
- _printInfo('=========================================');
215
- _printInfo(`Total dependencies: ${totalDeps}`);
216
- _printInfo(`Resolved: ${resolvedCount}`);
217
- _printInfo(`Unresolved: ${totalDeps - resolvedCount}`);
218
- console.log('');
219
-
220
- if (allResolved) {
221
- _printSuccess(`All dependencies are resolved! Task ${taskId} is ready to execute.`);
222
- process.exit(0);
223
- } else {
224
- _printError(`Task ${taskId} has unresolved dependencies:`);
225
- unresolvedDeps.forEach(dep => {
226
- console.log(dep);
227
- });
228
- _printInfo('Please complete the dependencies before executing this task.');
229
- process.exit(1);
230
- }
231
- };
232
-
233
- // Run the script
234
- if (require.main === module) {
235
- _main();
236
- }
237
-
238
- module.exports = {
239
- _main
240
- };
@@ -1,234 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Runtime Prompt Composition Script
5
- *
6
- * Reads markdown command files, performs variable substitution, and outputs
7
- * composed prompts to enable uninterrupted workflow execution.
8
- *
9
- * Usage:
10
- * node compose-prompt.cjs --template "create-plan.md" --variable "ARGUMENTS=user input" --variable "1=51"
11
- *
12
- * Arguments:
13
- * --template: Relative path to template file (e.g., "create-plan.md")
14
- * --variable: Key=value pairs for substitution (can be specified multiple times)
15
- *
16
- * Output:
17
- * Composed markdown prompt to stdout with variables substituted
18
- *
19
- * Exit Codes:
20
- * 0: Success
21
- * 1: Error (with message to stderr)
22
- */
23
-
24
- const fs = require('fs');
25
- const path = require('path');
26
-
27
- /**
28
- * Parse command line arguments
29
- * @returns {Object} Parsed arguments with template and variables
30
- */
31
- function parseArguments() {
32
- const args = process.argv.slice(2);
33
- const result = {
34
- template: null,
35
- variables: {}
36
- };
37
-
38
- for (let i = 0; i < args.length; i++) {
39
- if (args[i] === '--template') {
40
- if (i + 1 >= args.length) {
41
- throw new Error('--template requires a value');
42
- }
43
- result.template = args[++i];
44
- } else if (args[i] === '--variable') {
45
- if (i + 1 >= args.length) {
46
- throw new Error('--variable requires a value');
47
- }
48
- const varArg = args[++i];
49
- const eqIndex = varArg.indexOf('=');
50
- if (eqIndex === -1) {
51
- throw new Error(`Invalid variable format: ${varArg}. Expected format: key=value`);
52
- }
53
- const key = varArg.substring(0, eqIndex);
54
- const value = varArg.substring(eqIndex + 1);
55
- result.variables[key] = value;
56
- } else {
57
- throw new Error(`Unknown argument: ${args[i]}`);
58
- }
59
- }
60
-
61
- if (!result.template) {
62
- throw new Error('--template argument is required');
63
- }
64
-
65
- return result;
66
- }
67
-
68
- /**
69
- * Find the template file in the appropriate directory
70
- * Priority: .claude/commands/tasks/ then templates/assistant/commands/tasks/
71
- * @param {string} templateName - Relative path to template file
72
- * @returns {string} Absolute path to template file
73
- */
74
- function findTemplateFile(templateName) {
75
- // Get the root directory (assuming script is in templates/ai-task-manager/config/scripts/)
76
- const scriptDir = __dirname;
77
- const rootDir = path.resolve(scriptDir, '../../../..');
78
-
79
- // Define search paths in priority order
80
- const searchPaths = [
81
- path.join(rootDir, '.claude', 'commands', 'tasks', templateName),
82
- path.join(rootDir, 'templates', 'assistant', 'commands', 'tasks', templateName)
83
- ];
84
-
85
- for (const searchPath of searchPaths) {
86
- if (fs.existsSync(searchPath)) {
87
- return searchPath;
88
- }
89
- }
90
-
91
- throw new Error(`Template not found: ${templateName}`);
92
- }
93
-
94
- /**
95
- * Parse YAML frontmatter from markdown content
96
- * Based on the parseFrontmatter function from src/utils.ts
97
- * @param {string} content - The markdown content with frontmatter
98
- * @returns {Object} Object containing frontmatter and body content
99
- */
100
- function parseFrontmatter(content) {
101
- const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?$/;
102
- const match = content.match(frontmatterRegex);
103
-
104
- if (!match) {
105
- return {
106
- frontmatter: {},
107
- body: content
108
- };
109
- }
110
-
111
- const frontmatterContent = match[1] || '';
112
- const bodyContent = match[2] || '';
113
-
114
- // Simple YAML parser for our specific use case
115
- const frontmatter = {};
116
- const lines = frontmatterContent.split('\n');
117
-
118
- for (const line of lines) {
119
- const trimmed = line.trim();
120
- if (!trimmed || trimmed.startsWith('#')) continue;
121
-
122
- const colonIndex = trimmed.indexOf(':');
123
- if (colonIndex === -1) continue;
124
-
125
- const key = trimmed.substring(0, colonIndex).trim();
126
- const value = trimmed.substring(colonIndex + 1).trim();
127
-
128
- // Remove quotes if present
129
- frontmatter[key] = value.replace(/^["']|["']$/g, '');
130
- }
131
-
132
- return {
133
- frontmatter,
134
- body: bodyContent
135
- };
136
- }
137
-
138
- /**
139
- * Perform variable substitution in the template body
140
- * Replaces $ARGUMENTS, $1, $2, etc. with provided values
141
- * Does NOT process frontmatter variables (those stay as-is)
142
- * @param {string} body - The template body content
143
- * @param {Object} variables - Key-value pairs for substitution
144
- * @returns {string} Body content with substitutions applied
145
- */
146
- function substituteVariables(body, variables) {
147
- let result = body;
148
-
149
- // Replace $ARGUMENTS if provided
150
- if (variables.ARGUMENTS !== undefined) {
151
- // Use regex with negative lookahead to avoid replacing $ARGUMENTS that are part of longer identifiers
152
- result = result.replace(/\$ARGUMENTS(?![0-9A-Za-z_])/g, variables.ARGUMENTS);
153
- }
154
-
155
- // Replace positional arguments ($1, $2, $3, etc.)
156
- // Use regex with negative lookahead to avoid replacing $1 that's part of $10, etc.
157
- for (const key in variables) {
158
- if (/^\d+$/.test(key)) {
159
- const pattern = new RegExp(`\\$${key}(?![0-9])`, 'g');
160
- result = result.replace(pattern, variables[key]);
161
- }
162
- }
163
-
164
- return result;
165
- }
166
-
167
- /**
168
- * Reconstruct markdown content with frontmatter
169
- * @param {Object} frontmatter - The frontmatter object
170
- * @param {string} body - The body content
171
- * @returns {string} Complete markdown content
172
- */
173
- function reconstructMarkdown(frontmatter, body) {
174
- if (Object.keys(frontmatter).length === 0) {
175
- return body;
176
- }
177
-
178
- let result = '---\n';
179
- for (const [key, value] of Object.entries(frontmatter)) {
180
- // Preserve original formatting - add quotes if value contains special characters
181
- if (typeof value === 'string' && (value.includes(':') || value.includes('[') || value.includes(']'))) {
182
- result += `${key}: "${value}"\n`;
183
- } else {
184
- result += `${key}: ${value}\n`;
185
- }
186
- }
187
- result += '---\n';
188
-
189
- if (body) {
190
- result += body;
191
- }
192
-
193
- return result;
194
- }
195
-
196
- /**
197
- * Main execution function
198
- */
199
- function main() {
200
- try {
201
- // Parse command line arguments
202
- const { template, variables } = parseArguments();
203
-
204
- // Find the template file
205
- const templatePath = findTemplateFile(template);
206
-
207
- // Read the template file
208
- const content = fs.readFileSync(templatePath, 'utf-8');
209
-
210
- // Parse frontmatter and body
211
- const { frontmatter, body } = parseFrontmatter(content);
212
-
213
- // Perform variable substitution on the body only
214
- const substitutedBody = substituteVariables(body, variables);
215
-
216
- // Reconstruct the markdown with frontmatter preserved
217
- const output = reconstructMarkdown(frontmatter, substitutedBody);
218
-
219
- // Output to stdout
220
- process.stdout.write(output);
221
-
222
- // Exit successfully
223
- process.exit(0);
224
- } catch (error) {
225
- // Write error to stderr
226
- process.stderr.write(`Error: ${error.message}\n`);
227
-
228
- // Exit with error code
229
- process.exit(1);
230
- }
231
- }
232
-
233
- // Execute main function
234
- main();
@@ -1,204 +0,0 @@
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
- };