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.
Files changed (78) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/README.md +239 -213
  3. package/README.zh.md +0 -330
  4. package/bin/kiro-spec-engine.js +62 -0
  5. package/docs/README.md +223 -0
  6. package/docs/agent-hooks-analysis.md +815 -0
  7. package/docs/command-reference.md +252 -0
  8. package/docs/cross-tool-guide.md +554 -0
  9. package/docs/examples/add-export-command/design.md +194 -0
  10. package/docs/examples/add-export-command/requirements.md +110 -0
  11. package/docs/examples/add-export-command/tasks.md +88 -0
  12. package/docs/examples/add-rest-api/design.md +855 -0
  13. package/docs/examples/add-rest-api/requirements.md +323 -0
  14. package/docs/examples/add-rest-api/tasks.md +355 -0
  15. package/docs/examples/add-user-dashboard/design.md +192 -0
  16. package/docs/examples/add-user-dashboard/requirements.md +143 -0
  17. package/docs/examples/add-user-dashboard/tasks.md +91 -0
  18. package/docs/faq.md +696 -0
  19. package/docs/integration-modes.md +525 -0
  20. package/docs/integration-philosophy.md +313 -0
  21. package/docs/manual-workflows-guide.md +417 -0
  22. package/docs/quick-start-with-ai-tools.md +374 -0
  23. package/docs/quick-start.md +711 -0
  24. package/docs/spec-workflow.md +453 -0
  25. package/docs/steering-strategy-guide.md +196 -0
  26. package/docs/tools/claude-guide.md +653 -0
  27. package/docs/tools/cursor-guide.md +705 -0
  28. package/docs/tools/generic-guide.md +445 -0
  29. package/docs/tools/kiro-guide.md +308 -0
  30. package/docs/tools/vscode-guide.md +444 -0
  31. package/docs/tools/windsurf-guide.md +390 -0
  32. package/docs/troubleshooting.md +795 -0
  33. package/docs/zh/README.md +275 -0
  34. package/docs/zh/quick-start.md +711 -0
  35. package/docs/zh/tools/claude-guide.md +348 -0
  36. package/docs/zh/tools/cursor-guide.md +280 -0
  37. package/docs/zh/tools/generic-guide.md +498 -0
  38. package/docs/zh/tools/kiro-guide.md +342 -0
  39. package/docs/zh/tools/vscode-guide.md +448 -0
  40. package/docs/zh/tools/windsurf-guide.md +377 -0
  41. package/lib/adoption/detection-engine.js +14 -4
  42. package/lib/commands/adopt.js +117 -3
  43. package/lib/commands/context.js +99 -0
  44. package/lib/commands/prompt.js +105 -0
  45. package/lib/commands/status.js +225 -0
  46. package/lib/commands/task.js +199 -0
  47. package/lib/commands/watch.js +569 -0
  48. package/lib/commands/workflows.js +240 -0
  49. package/lib/commands/workspace.js +189 -0
  50. package/lib/context/context-exporter.js +378 -0
  51. package/lib/context/prompt-generator.js +482 -0
  52. package/lib/steering/adoption-config.js +164 -0
  53. package/lib/steering/steering-manager.js +289 -0
  54. package/lib/task/task-claimer.js +430 -0
  55. package/lib/utils/tool-detector.js +383 -0
  56. package/lib/watch/action-executor.js +458 -0
  57. package/lib/watch/event-debouncer.js +323 -0
  58. package/lib/watch/execution-logger.js +550 -0
  59. package/lib/watch/file-watcher.js +499 -0
  60. package/lib/watch/presets.js +266 -0
  61. package/lib/watch/watch-manager.js +533 -0
  62. package/lib/workspace/workspace-manager.js +370 -0
  63. package/lib/workspace/workspace-sync.js +356 -0
  64. package/package.json +3 -1
  65. package/template/.kiro/tools/backup_manager.py +295 -0
  66. package/template/.kiro/tools/configuration_manager.py +218 -0
  67. package/template/.kiro/tools/document_evaluator.py +550 -0
  68. package/template/.kiro/tools/enhancement_logger.py +168 -0
  69. package/template/.kiro/tools/error_handler.py +335 -0
  70. package/template/.kiro/tools/improvement_identifier.py +444 -0
  71. package/template/.kiro/tools/modification_applicator.py +737 -0
  72. package/template/.kiro/tools/quality_gate_enforcer.py +207 -0
  73. package/template/.kiro/tools/quality_scorer.py +305 -0
  74. package/template/.kiro/tools/report_generator.py +154 -0
  75. package/template/.kiro/tools/ultrawork_enhancer_refactored.py +0 -0
  76. package/template/.kiro/tools/ultrawork_enhancer_v2.py +463 -0
  77. package/template/.kiro/tools/ultrawork_enhancer_v3.py +606 -0
  78. 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
+ };