@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.
- package/README.md +26 -21
- package/dist/cli.js +1 -32
- package/dist/cli.js.map +1 -1
- package/dist/conflict-detector.d.ts.map +1 -1
- package/dist/conflict-detector.js +0 -4
- package/dist/conflict-detector.js.map +1 -1
- package/dist/index.d.ts +3 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +50 -245
- package/dist/index.js.map +1 -1
- package/dist/metadata.d.ts +9 -0
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +14 -0
- package/dist/metadata.js.map +1 -1
- package/dist/types.d.ts +18 -18
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +27 -58
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +93 -219
- package/dist/utils.js.map +1 -1
- package/package.json +4 -2
- package/templates/ai-task-manager/config/TASK_MANAGER.md +3 -3
- package/templates/ai-task-manager/config/hooks/PRE_PHASE.md +6 -26
- package/templates/ai-task-manager/config/hooks/PRE_TASK_ASSIGNMENT.md +6 -24
- package/templates/ai-task-manager/config/templates/PLAN_TEMPLATE.md +1 -1
- package/templates/{assistant → harness}/agents/plan-creator.md +2 -2
- package/templates/harness/skills/task-create-plan/SKILL.md +120 -0
- package/templates/harness/skills/task-create-plan/scripts/find-task-manager-root.cjs +116 -0
- package/templates/harness/skills/task-create-plan/scripts/get-next-plan-id.cjs +214 -0
- package/templates/harness/skills/task-execute-blueprint/SKILL.md +139 -0
- package/templates/harness/skills/task-execute-blueprint/scripts/create-feature-branch.cjs +376 -0
- package/templates/harness/skills/task-execute-blueprint/scripts/find-task-manager-root.cjs +116 -0
- package/templates/harness/skills/task-execute-blueprint/scripts/validate-plan-blueprint.cjs +375 -0
- package/templates/harness/skills/task-execute-task/SKILL.md +195 -0
- package/templates/harness/skills/task-execute-task/scripts/check-task-dependencies.cjs +437 -0
- package/templates/harness/skills/task-execute-task/scripts/find-task-manager-root.cjs +116 -0
- package/templates/harness/skills/task-execute-task/scripts/validate-plan-blueprint.cjs +375 -0
- package/templates/harness/skills/task-full-workflow/SKILL.md +378 -0
- package/templates/harness/skills/task-full-workflow/scripts/create-feature-branch.cjs +376 -0
- package/templates/harness/skills/task-full-workflow/scripts/find-task-manager-root.cjs +116 -0
- package/templates/harness/skills/task-full-workflow/scripts/get-next-plan-id.cjs +214 -0
- package/templates/harness/skills/task-full-workflow/scripts/get-next-task-id.cjs +312 -0
- package/templates/harness/skills/task-full-workflow/scripts/validate-plan-blueprint.cjs +375 -0
- package/templates/harness/skills/task-generate-tasks/SKILL.md +244 -0
- package/templates/harness/skills/task-generate-tasks/scripts/find-task-manager-root.cjs +116 -0
- package/templates/harness/skills/task-generate-tasks/scripts/get-next-task-id.cjs +312 -0
- package/templates/harness/skills/task-generate-tasks/scripts/validate-plan-blueprint.cjs +375 -0
- package/templates/harness/skills/task-refine-plan/SKILL.md +205 -0
- package/templates/harness/skills/task-refine-plan/scripts/find-task-manager-root.cjs +116 -0
- package/templates/harness/skills/task-refine-plan/scripts/validate-plan-blueprint.cjs +375 -0
- package/dist/exec.d.ts +0 -13
- package/dist/exec.d.ts.map +0 -1
- package/dist/exec.js +0 -261
- package/dist/exec.js.map +0 -1
- package/templates/ai-task-manager/config/scripts/check-task-dependencies.cjs +0 -240
- package/templates/ai-task-manager/config/scripts/compose-prompt.cjs +0 -234
- package/templates/ai-task-manager/config/scripts/create-feature-branch.cjs +0 -204
- package/templates/ai-task-manager/config/scripts/extract-task-skills.cjs +0 -84
- package/templates/ai-task-manager/config/scripts/find-root.cjs +0 -10
- package/templates/ai-task-manager/config/scripts/get-next-plan-id.cjs +0 -49
- package/templates/ai-task-manager/config/scripts/get-next-task-id.cjs +0 -81
- package/templates/ai-task-manager/config/scripts/shared-utils.cjs +0 -418
- package/templates/ai-task-manager/config/scripts/validate-plan-blueprint.cjs +0 -138
- package/templates/assistant/commands/tasks/create-plan-auto.md +0 -174
- package/templates/assistant/commands/tasks/create-plan.md +0 -175
- package/templates/assistant/commands/tasks/execute-blueprint.md +0 -233
- package/templates/assistant/commands/tasks/execute-task.md +0 -351
- package/templates/assistant/commands/tasks/fix-broken-tests.md +0 -44
- package/templates/assistant/commands/tasks/full-workflow.md +0 -849
- package/templates/assistant/commands/tasks/generate-tasks.md +0 -348
- package/templates/assistant/commands/tasks/refine-plan-auto.md +0 -172
- 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
|
-
};
|