kiro-spec-engine 1.2.3 → 1.4.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/CHANGELOG.md +135 -0
- package/README.md +239 -213
- package/README.zh.md +0 -330
- package/bin/kiro-spec-engine.js +62 -0
- package/docs/README.md +223 -0
- package/docs/agent-hooks-analysis.md +815 -0
- package/docs/command-reference.md +252 -0
- package/docs/cross-tool-guide.md +554 -0
- package/docs/examples/add-export-command/design.md +194 -0
- package/docs/examples/add-export-command/requirements.md +110 -0
- package/docs/examples/add-export-command/tasks.md +88 -0
- package/docs/examples/add-rest-api/design.md +855 -0
- package/docs/examples/add-rest-api/requirements.md +323 -0
- package/docs/examples/add-rest-api/tasks.md +355 -0
- package/docs/examples/add-user-dashboard/design.md +192 -0
- package/docs/examples/add-user-dashboard/requirements.md +143 -0
- package/docs/examples/add-user-dashboard/tasks.md +91 -0
- package/docs/faq.md +696 -0
- package/docs/integration-modes.md +525 -0
- package/docs/integration-philosophy.md +313 -0
- package/docs/manual-workflows-guide.md +417 -0
- package/docs/quick-start-with-ai-tools.md +374 -0
- package/docs/quick-start.md +711 -0
- package/docs/spec-workflow.md +453 -0
- package/docs/steering-strategy-guide.md +196 -0
- package/docs/tools/claude-guide.md +653 -0
- package/docs/tools/cursor-guide.md +705 -0
- package/docs/tools/generic-guide.md +445 -0
- package/docs/tools/kiro-guide.md +308 -0
- package/docs/tools/vscode-guide.md +444 -0
- package/docs/tools/windsurf-guide.md +390 -0
- package/docs/troubleshooting.md +795 -0
- package/docs/zh/README.md +275 -0
- package/docs/zh/quick-start.md +711 -0
- package/docs/zh/tools/claude-guide.md +348 -0
- package/docs/zh/tools/cursor-guide.md +280 -0
- package/docs/zh/tools/generic-guide.md +498 -0
- package/docs/zh/tools/kiro-guide.md +342 -0
- package/docs/zh/tools/vscode-guide.md +448 -0
- package/docs/zh/tools/windsurf-guide.md +377 -0
- package/lib/adoption/detection-engine.js +14 -4
- package/lib/commands/adopt.js +117 -3
- package/lib/commands/context.js +99 -0
- package/lib/commands/prompt.js +105 -0
- package/lib/commands/status.js +225 -0
- package/lib/commands/task.js +199 -0
- package/lib/commands/watch.js +569 -0
- package/lib/commands/workflows.js +240 -0
- package/lib/commands/workspace.js +189 -0
- package/lib/context/context-exporter.js +378 -0
- package/lib/context/prompt-generator.js +482 -0
- package/lib/steering/adoption-config.js +164 -0
- package/lib/steering/steering-manager.js +289 -0
- package/lib/task/task-claimer.js +430 -0
- package/lib/utils/tool-detector.js +383 -0
- package/lib/watch/action-executor.js +458 -0
- package/lib/watch/event-debouncer.js +323 -0
- package/lib/watch/execution-logger.js +550 -0
- package/lib/watch/file-watcher.js +499 -0
- package/lib/watch/presets.js +266 -0
- package/lib/watch/watch-manager.js +533 -0
- package/lib/workspace/workspace-manager.js +370 -0
- package/lib/workspace/workspace-sync.js +356 -0
- package/package.json +3 -1
- package/template/.kiro/tools/backup_manager.py +295 -0
- package/template/.kiro/tools/configuration_manager.py +218 -0
- package/template/.kiro/tools/document_evaluator.py +550 -0
- package/template/.kiro/tools/enhancement_logger.py +168 -0
- package/template/.kiro/tools/error_handler.py +335 -0
- package/template/.kiro/tools/improvement_identifier.py +444 -0
- package/template/.kiro/tools/modification_applicator.py +737 -0
- package/template/.kiro/tools/quality_gate_enforcer.py +207 -0
- package/template/.kiro/tools/quality_scorer.py +305 -0
- package/template/.kiro/tools/report_generator.py +154 -0
- package/template/.kiro/tools/ultrawork_enhancer_refactored.py +0 -0
- package/template/.kiro/tools/ultrawork_enhancer_v2.py +463 -0
- package/template/.kiro/tools/ultrawork_enhancer_v3.py +606 -0
- package/template/.kiro/tools/workflow_quality_gate.py +100 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Command
|
|
3
|
+
*
|
|
4
|
+
* Generates task-specific prompts for AI coding assistants
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const PromptGenerator = require('../context/prompt-generator');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate task prompt
|
|
12
|
+
*
|
|
13
|
+
* @param {string} specName - Spec name
|
|
14
|
+
* @param {string} taskId - Task ID
|
|
15
|
+
* @param {Object} options - Command options
|
|
16
|
+
* @param {string} options.tool - Target tool (generic, claude-code, cursor, codex, kiro)
|
|
17
|
+
* @param {number} options.maxLength - Maximum context length
|
|
18
|
+
* @returns {Promise<void>}
|
|
19
|
+
*/
|
|
20
|
+
async function generatePrompt(specName, taskId, options = {}) {
|
|
21
|
+
const projectPath = process.cwd();
|
|
22
|
+
const generator = new PromptGenerator();
|
|
23
|
+
|
|
24
|
+
console.log(chalk.red('🔥') + ' Generating Task Prompt');
|
|
25
|
+
console.log();
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
console.log(`Spec: ${chalk.cyan(specName)}`);
|
|
29
|
+
console.log(`Task: ${chalk.cyan(taskId)}`);
|
|
30
|
+
|
|
31
|
+
const targetTool = options.tool || 'generic';
|
|
32
|
+
console.log(`Target Tool: ${chalk.cyan(targetTool)}`);
|
|
33
|
+
console.log();
|
|
34
|
+
|
|
35
|
+
const generateOptions = {
|
|
36
|
+
targetTool,
|
|
37
|
+
maxContextLength: options.maxLength || 10000
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
console.log('Generating prompt...');
|
|
41
|
+
console.log();
|
|
42
|
+
|
|
43
|
+
const result = await generator.generatePrompt(
|
|
44
|
+
projectPath,
|
|
45
|
+
specName,
|
|
46
|
+
taskId,
|
|
47
|
+
generateOptions
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (result.success) {
|
|
51
|
+
console.log(chalk.green('✅ Prompt generated successfully'));
|
|
52
|
+
console.log();
|
|
53
|
+
console.log(`Prompt file: ${chalk.cyan(result.promptPath)}`);
|
|
54
|
+
console.log(`Size: ${chalk.gray(formatBytes(result.size))}`);
|
|
55
|
+
console.log();
|
|
56
|
+
console.log('Usage:');
|
|
57
|
+
console.log(' 1. Copy the prompt file content');
|
|
58
|
+
console.log(' 2. Paste it into your AI coding assistant');
|
|
59
|
+
console.log(' 3. Follow the implementation guidelines');
|
|
60
|
+
console.log(' 4. Update task status after completion');
|
|
61
|
+
console.log();
|
|
62
|
+
|
|
63
|
+
// Tool-specific tips
|
|
64
|
+
if (targetTool === 'claude-code') {
|
|
65
|
+
console.log(chalk.blue('💡 Claude Code Tips:'));
|
|
66
|
+
console.log(' • Copy the entire prompt into the chat');
|
|
67
|
+
console.log(' • Reference specific sections as needed');
|
|
68
|
+
} else if (targetTool === 'cursor') {
|
|
69
|
+
console.log(chalk.blue('💡 Cursor Tips:'));
|
|
70
|
+
console.log(' • Paste the prompt into the composer');
|
|
71
|
+
console.log(' • Use Cmd+K to apply changes');
|
|
72
|
+
} else if (targetTool === 'codex') {
|
|
73
|
+
console.log(chalk.blue('💡 GitHub Copilot Tips:'));
|
|
74
|
+
console.log(' • Include the prompt in code comments');
|
|
75
|
+
console.log(' • Let Copilot suggest implementations');
|
|
76
|
+
} else if (targetTool === 'kiro') {
|
|
77
|
+
console.log(chalk.blue('💡 Kiro IDE Tips:'));
|
|
78
|
+
console.log(' • Steering rules are loaded automatically');
|
|
79
|
+
console.log(' • Use #File to reference specific files');
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
console.log(chalk.red('❌ Prompt generation failed'));
|
|
83
|
+
console.log();
|
|
84
|
+
console.log(`Error: ${result.error}`);
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.log(chalk.red('❌ Error:'), error.message);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Format bytes to human-readable string
|
|
93
|
+
*
|
|
94
|
+
* @param {number} bytes - Bytes
|
|
95
|
+
* @returns {string} Formatted string
|
|
96
|
+
*/
|
|
97
|
+
function formatBytes(bytes) {
|
|
98
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
99
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
100
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
generatePrompt
|
|
105
|
+
};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status Command
|
|
3
|
+
*
|
|
4
|
+
* Displays project status including specs, tasks, and team activity
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fs = require('fs-extra');
|
|
10
|
+
const TaskClaimer = require('../task/task-claimer');
|
|
11
|
+
const WorkspaceManager = require('../workspace/workspace-manager');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Executes the status command
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} options - Command options
|
|
17
|
+
* @param {boolean} options.verbose - Show detailed information
|
|
18
|
+
* @param {boolean} options.team - Show team activity
|
|
19
|
+
* @returns {Promise<void>}
|
|
20
|
+
*/
|
|
21
|
+
async function statusCommand(options = {}) {
|
|
22
|
+
const { verbose = false, team = false } = options;
|
|
23
|
+
const projectPath = process.cwd();
|
|
24
|
+
|
|
25
|
+
console.log(chalk.red('🔥') + ' Kiro Spec Engine - Project Status');
|
|
26
|
+
console.log();
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// 1. Check if .kiro/ exists
|
|
30
|
+
const kiroPath = path.join(projectPath, '.kiro');
|
|
31
|
+
const kiroExists = await fs.pathExists(kiroPath);
|
|
32
|
+
|
|
33
|
+
if (!kiroExists) {
|
|
34
|
+
console.log(chalk.yellow('⚠️ No .kiro/ directory found'));
|
|
35
|
+
console.log();
|
|
36
|
+
console.log('This project has not been adopted yet.');
|
|
37
|
+
console.log('Run ' + chalk.cyan('kse adopt') + ' to get started.');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 2. Check multi-user mode
|
|
42
|
+
const workspaceManager = new WorkspaceManager();
|
|
43
|
+
const isMultiUser = await workspaceManager.isMultiUserMode(projectPath);
|
|
44
|
+
|
|
45
|
+
console.log(chalk.blue('📊 Project Information'));
|
|
46
|
+
console.log(` Mode: ${isMultiUser ? chalk.cyan('Multi-User') : chalk.gray('Single-User')}`);
|
|
47
|
+
|
|
48
|
+
if (isMultiUser) {
|
|
49
|
+
const workspaces = await workspaceManager.listWorkspaces(projectPath);
|
|
50
|
+
console.log(` Active Users: ${chalk.cyan(workspaces.length)}`);
|
|
51
|
+
if (verbose) {
|
|
52
|
+
workspaces.forEach(username => {
|
|
53
|
+
console.log(` • ${username}`);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log();
|
|
59
|
+
|
|
60
|
+
// 3. List specs
|
|
61
|
+
const specsPath = path.join(projectPath, '.kiro/specs');
|
|
62
|
+
const specsExist = await fs.pathExists(specsPath);
|
|
63
|
+
|
|
64
|
+
if (!specsExist) {
|
|
65
|
+
console.log(chalk.yellow('📁 No specs found'));
|
|
66
|
+
console.log();
|
|
67
|
+
console.log('Create your first spec: ' + chalk.cyan('kse create-spec my-feature'));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const entries = await fs.readdir(specsPath, { withFileTypes: true });
|
|
72
|
+
const specDirs = entries.filter(entry =>
|
|
73
|
+
entry.isDirectory() && !entry.name.startsWith('.')
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (specDirs.length === 0) {
|
|
77
|
+
console.log(chalk.yellow('📁 No specs found'));
|
|
78
|
+
console.log();
|
|
79
|
+
console.log('Create your first spec: ' + chalk.cyan('kse create-spec my-feature'));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log(chalk.blue(`📁 Specs (${specDirs.length})`));
|
|
84
|
+
console.log();
|
|
85
|
+
|
|
86
|
+
// 4. Analyze each spec
|
|
87
|
+
const taskClaimer = new TaskClaimer();
|
|
88
|
+
const allClaimedTasks = [];
|
|
89
|
+
|
|
90
|
+
for (const specDir of specDirs) {
|
|
91
|
+
const specName = specDir.name;
|
|
92
|
+
const specPath = path.join(specsPath, specName);
|
|
93
|
+
|
|
94
|
+
// Check for tasks.md
|
|
95
|
+
const tasksPath = path.join(specPath, 'tasks.md');
|
|
96
|
+
const tasksExist = await fs.pathExists(tasksPath);
|
|
97
|
+
|
|
98
|
+
if (!tasksExist) {
|
|
99
|
+
console.log(chalk.gray(` ${specName}`));
|
|
100
|
+
console.log(chalk.gray(' No tasks.md found'));
|
|
101
|
+
console.log();
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Parse tasks
|
|
106
|
+
const tasks = await taskClaimer.parseTasks(tasksPath);
|
|
107
|
+
const totalTasks = tasks.length;
|
|
108
|
+
const completedTasks = tasks.filter(t => t.status === 'completed').length;
|
|
109
|
+
const inProgressTasks = tasks.filter(t => t.status === 'in-progress').length;
|
|
110
|
+
const claimedTasks = tasks.filter(t => t.claimedBy);
|
|
111
|
+
|
|
112
|
+
// Calculate completion percentage
|
|
113
|
+
const completionPercent = totalTasks > 0
|
|
114
|
+
? Math.round((completedTasks / totalTasks) * 100)
|
|
115
|
+
: 0;
|
|
116
|
+
|
|
117
|
+
// Display spec info
|
|
118
|
+
console.log(chalk.cyan(` ${specName}`));
|
|
119
|
+
console.log(` Tasks: ${chalk.green(completedTasks)}/${totalTasks} completed (${completionPercent}%)`);
|
|
120
|
+
|
|
121
|
+
if (inProgressTasks > 0) {
|
|
122
|
+
console.log(` In Progress: ${chalk.yellow(inProgressTasks)}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (claimedTasks.length > 0) {
|
|
126
|
+
console.log(` Claimed: ${chalk.blue(claimedTasks.length)}`);
|
|
127
|
+
|
|
128
|
+
if (verbose || team) {
|
|
129
|
+
claimedTasks.forEach(task => {
|
|
130
|
+
const staleMarker = task.isStale ? chalk.red(' [STALE]') : '';
|
|
131
|
+
console.log(` • ${task.taskId} ${task.title}`);
|
|
132
|
+
console.log(` ${chalk.gray(`@${task.claimedBy}, ${new Date(task.claimedAt).toLocaleDateString()}`)}${staleMarker}`);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Collect for team view
|
|
137
|
+
claimedTasks.forEach(task => {
|
|
138
|
+
allClaimedTasks.push({
|
|
139
|
+
...task,
|
|
140
|
+
specName
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 5. Team activity view
|
|
149
|
+
if (team && allClaimedTasks.length > 0) {
|
|
150
|
+
console.log(chalk.blue('👥 Team Activity'));
|
|
151
|
+
console.log();
|
|
152
|
+
|
|
153
|
+
// Group by user
|
|
154
|
+
const tasksByUser = {};
|
|
155
|
+
allClaimedTasks.forEach(task => {
|
|
156
|
+
if (!tasksByUser[task.claimedBy]) {
|
|
157
|
+
tasksByUser[task.claimedBy] = [];
|
|
158
|
+
}
|
|
159
|
+
tasksByUser[task.claimedBy].push(task);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Display by user
|
|
163
|
+
Object.keys(tasksByUser).sort().forEach(username => {
|
|
164
|
+
const userTasks = tasksByUser[username];
|
|
165
|
+
const staleTasks = userTasks.filter(t => t.isStale);
|
|
166
|
+
|
|
167
|
+
console.log(chalk.cyan(` ${username}`));
|
|
168
|
+
console.log(` Active Tasks: ${userTasks.length}`);
|
|
169
|
+
|
|
170
|
+
if (staleTasks.length > 0) {
|
|
171
|
+
console.log(` ${chalk.red(`Stale Claims: ${staleTasks.length}`)}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (verbose) {
|
|
175
|
+
userTasks.forEach(task => {
|
|
176
|
+
const staleMarker = task.isStale ? chalk.red(' [STALE]') : '';
|
|
177
|
+
const statusColor = task.status === 'completed' ? chalk.green :
|
|
178
|
+
task.status === 'in-progress' ? chalk.yellow :
|
|
179
|
+
chalk.gray;
|
|
180
|
+
console.log(` • [${task.specName}] ${task.taskId} ${task.title}`);
|
|
181
|
+
console.log(` ${statusColor(task.status)} • ${chalk.gray(new Date(task.claimedAt).toLocaleDateString())}${staleMarker}`);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
console.log();
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 6. Summary
|
|
190
|
+
if (allClaimedTasks.length > 0) {
|
|
191
|
+
const staleClaims = allClaimedTasks.filter(t => t.isStale);
|
|
192
|
+
|
|
193
|
+
if (staleClaims.length > 0) {
|
|
194
|
+
console.log(chalk.yellow('⚠️ Warning'));
|
|
195
|
+
console.log(` ${staleClaims.length} task(s) have stale claims (>7 days old)`);
|
|
196
|
+
console.log();
|
|
197
|
+
|
|
198
|
+
if (!verbose && !team) {
|
|
199
|
+
console.log(chalk.gray(' Run with --team or --verbose to see details'));
|
|
200
|
+
console.log();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 7. Next steps
|
|
206
|
+
console.log(chalk.blue('💡 Commands'));
|
|
207
|
+
console.log(' View team activity: ' + chalk.cyan('kse status --team'));
|
|
208
|
+
console.log(' Detailed view: ' + chalk.cyan('kse status --verbose'));
|
|
209
|
+
|
|
210
|
+
if (isMultiUser) {
|
|
211
|
+
console.log(' Claim a task: ' + chalk.cyan('kse task claim <spec-name> <task-id>'));
|
|
212
|
+
console.log(' Sync workspace: ' + chalk.cyan('kse workspace sync'));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.log();
|
|
217
|
+
console.log(chalk.red('❌ Error:'), error.message);
|
|
218
|
+
console.log();
|
|
219
|
+
console.log(chalk.gray('If you need help, please report this issue:'));
|
|
220
|
+
console.log(chalk.cyan('https://github.com/heguangyong/kiro-spec-engine/issues'));
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
module.exports = statusCommand;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Command Group
|
|
3
|
+
*
|
|
4
|
+
* Manages task claiming and status updates
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const TaskClaimer = require('../task/task-claimer');
|
|
9
|
+
const WorkspaceManager = require('../workspace/workspace-manager');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Claim a task
|
|
13
|
+
*
|
|
14
|
+
* @param {string} specName - Spec name
|
|
15
|
+
* @param {string} taskId - Task ID
|
|
16
|
+
* @param {Object} options - Command options
|
|
17
|
+
* @param {string} options.user - Override username
|
|
18
|
+
* @param {boolean} options.force - Force claim even if already claimed
|
|
19
|
+
* @returns {Promise<void>}
|
|
20
|
+
*/
|
|
21
|
+
async function claimTask(specName, taskId, options = {}) {
|
|
22
|
+
const projectPath = process.cwd();
|
|
23
|
+
const taskClaimer = new TaskClaimer();
|
|
24
|
+
const workspaceManager = new WorkspaceManager();
|
|
25
|
+
|
|
26
|
+
console.log(chalk.red('🔥') + ' Claiming Task');
|
|
27
|
+
console.log();
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const username = options.user || await workspaceManager.detectUsername();
|
|
31
|
+
|
|
32
|
+
if (!username) {
|
|
33
|
+
console.log(chalk.red('❌ Could not detect username'));
|
|
34
|
+
console.log();
|
|
35
|
+
console.log('Please configure git or use --user flag');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log(`Spec: ${chalk.cyan(specName)}`);
|
|
40
|
+
console.log(`Task: ${chalk.cyan(taskId)}`);
|
|
41
|
+
console.log(`User: ${chalk.cyan(username)}`);
|
|
42
|
+
console.log();
|
|
43
|
+
|
|
44
|
+
const result = await taskClaimer.claimTask(
|
|
45
|
+
projectPath,
|
|
46
|
+
specName,
|
|
47
|
+
taskId,
|
|
48
|
+
username,
|
|
49
|
+
options.force
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (result.success) {
|
|
53
|
+
console.log(chalk.green('✅ Task claimed successfully'));
|
|
54
|
+
console.log();
|
|
55
|
+
console.log(`Task: ${result.taskTitle}`);
|
|
56
|
+
console.log(`Claimed by: ${chalk.cyan(username)}`);
|
|
57
|
+
console.log(`Claimed at: ${chalk.gray(result.claimedAt)}`);
|
|
58
|
+
|
|
59
|
+
if (result.previousClaim) {
|
|
60
|
+
console.log();
|
|
61
|
+
console.log(chalk.yellow('⚠️ Previous claim overridden:'));
|
|
62
|
+
console.log(` User: ${result.previousClaim.username}`);
|
|
63
|
+
console.log(` Time: ${result.previousClaim.timestamp}`);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
console.log(chalk.red('❌ Failed to claim task'));
|
|
67
|
+
console.log();
|
|
68
|
+
console.log(`Error: ${result.error}`);
|
|
69
|
+
|
|
70
|
+
if (result.existingClaim) {
|
|
71
|
+
console.log();
|
|
72
|
+
console.log('Task is already claimed by:');
|
|
73
|
+
console.log(` User: ${chalk.cyan(result.existingClaim.username)}`);
|
|
74
|
+
console.log(` Time: ${chalk.gray(result.existingClaim.timestamp)}`);
|
|
75
|
+
console.log();
|
|
76
|
+
console.log('Use ' + chalk.cyan('--force') + ' to override the claim');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.log(chalk.red('❌ Error:'), error.message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Unclaim a task
|
|
86
|
+
*
|
|
87
|
+
* @param {string} specName - Spec name
|
|
88
|
+
* @param {string} taskId - Task ID
|
|
89
|
+
* @param {Object} options - Command options
|
|
90
|
+
* @param {string} options.user - Override username
|
|
91
|
+
* @returns {Promise<void>}
|
|
92
|
+
*/
|
|
93
|
+
async function unclaimTask(specName, taskId, options = {}) {
|
|
94
|
+
const projectPath = process.cwd();
|
|
95
|
+
const taskClaimer = new TaskClaimer();
|
|
96
|
+
const workspaceManager = new WorkspaceManager();
|
|
97
|
+
|
|
98
|
+
console.log(chalk.red('🔥') + ' Unclaiming Task');
|
|
99
|
+
console.log();
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const username = options.user || await workspaceManager.detectUsername();
|
|
103
|
+
|
|
104
|
+
if (!username) {
|
|
105
|
+
console.log(chalk.red('❌ Could not detect username'));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log(`Spec: ${chalk.cyan(specName)}`);
|
|
110
|
+
console.log(`Task: ${chalk.cyan(taskId)}`);
|
|
111
|
+
console.log(`User: ${chalk.cyan(username)}`);
|
|
112
|
+
console.log();
|
|
113
|
+
|
|
114
|
+
const result = await taskClaimer.unclaimTask(
|
|
115
|
+
projectPath,
|
|
116
|
+
specName,
|
|
117
|
+
taskId,
|
|
118
|
+
username
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (result.success) {
|
|
122
|
+
console.log(chalk.green('✅ Task unclaimed successfully'));
|
|
123
|
+
console.log();
|
|
124
|
+
console.log(`Task: ${result.taskTitle}`);
|
|
125
|
+
} else {
|
|
126
|
+
console.log(chalk.red('❌ Failed to unclaim task'));
|
|
127
|
+
console.log();
|
|
128
|
+
console.log(`Error: ${result.error}`);
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.log(chalk.red('❌ Error:'), error.message);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* List claimed tasks
|
|
137
|
+
*
|
|
138
|
+
* @param {string} specName - Spec name (optional)
|
|
139
|
+
* @param {Object} options - Command options
|
|
140
|
+
* @param {string} options.user - Filter by username
|
|
141
|
+
* @returns {Promise<void>}
|
|
142
|
+
*/
|
|
143
|
+
async function listClaimedTasks(specName, options = {}) {
|
|
144
|
+
const projectPath = process.cwd();
|
|
145
|
+
const taskClaimer = new TaskClaimer();
|
|
146
|
+
|
|
147
|
+
console.log(chalk.red('🔥') + ' Claimed Tasks');
|
|
148
|
+
console.log();
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
if (specName) {
|
|
152
|
+
// List claimed tasks for specific spec
|
|
153
|
+
const tasks = await taskClaimer.getClaimedTasks(projectPath, specName);
|
|
154
|
+
|
|
155
|
+
if (tasks.length === 0) {
|
|
156
|
+
console.log(chalk.gray('No claimed tasks found'));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log(`Spec: ${chalk.cyan(specName)}`);
|
|
161
|
+
console.log();
|
|
162
|
+
|
|
163
|
+
// Group by user
|
|
164
|
+
const byUser = {};
|
|
165
|
+
for (const task of tasks) {
|
|
166
|
+
if (!byUser[task.claimedBy]) {
|
|
167
|
+
byUser[task.claimedBy] = [];
|
|
168
|
+
}
|
|
169
|
+
byUser[task.claimedBy].push(task);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for (const [user, userTasks] of Object.entries(byUser)) {
|
|
173
|
+
if (options.user && user !== options.user) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log(chalk.cyan(`${user} (${userTasks.length} task(s))`));
|
|
178
|
+
for (const task of userTasks) {
|
|
179
|
+
const staleMarker = task.isStale ? chalk.yellow(' [STALE]') : '';
|
|
180
|
+
console.log(` ${chalk.gray('•')} ${task.taskId} ${task.taskTitle}${staleMarker}`);
|
|
181
|
+
console.log(` ${chalk.gray(task.claimedAt)}`);
|
|
182
|
+
}
|
|
183
|
+
console.log();
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
console.log(chalk.gray('Please specify a spec name'));
|
|
187
|
+
console.log();
|
|
188
|
+
console.log('Usage: ' + chalk.cyan('kse task list <spec-name>'));
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.log(chalk.red('❌ Error:'), error.message);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = {
|
|
196
|
+
claimTask,
|
|
197
|
+
unclaimTask,
|
|
198
|
+
listClaimedTasks
|
|
199
|
+
};
|