kiro-spec-engine 1.2.3 → 1.3.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 +74 -0
- package/README.md +172 -0
- package/bin/kiro-spec-engine.js +62 -0
- package/docs/agent-hooks-analysis.md +815 -0
- package/docs/cross-tool-guide.md +554 -0
- package/docs/manual-workflows-guide.md +417 -0
- package/docs/steering-strategy-guide.md +196 -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
package/lib/commands/adopt.js
CHANGED
|
@@ -12,6 +12,9 @@ const DetectionEngine = require('../adoption/detection-engine');
|
|
|
12
12
|
const { getAdoptionStrategy } = require('../adoption/adoption-strategy');
|
|
13
13
|
const BackupSystem = require('../backup/backup-system');
|
|
14
14
|
const VersionManager = require('../version/version-manager');
|
|
15
|
+
const SteeringManager = require('../steering/steering-manager');
|
|
16
|
+
const AdoptionConfig = require('../steering/adoption-config');
|
|
17
|
+
const { detectTool, generateAutoConfig } = require('../utils/tool-detector');
|
|
15
18
|
|
|
16
19
|
/**
|
|
17
20
|
* Executes the adopt command
|
|
@@ -134,7 +137,54 @@ async function adoptCommand(options = {}) {
|
|
|
134
137
|
|
|
135
138
|
console.log();
|
|
136
139
|
|
|
137
|
-
// 7.
|
|
140
|
+
// 7. Handle steering strategy if conflicts detected
|
|
141
|
+
let steeringStrategy = null;
|
|
142
|
+
let steeringBackupId = null;
|
|
143
|
+
|
|
144
|
+
if (detection.steeringDetection && detection.steeringDetection.hasExistingSteering) {
|
|
145
|
+
console.log(chalk.blue('🎯 Handling steering files...'));
|
|
146
|
+
const steeringManager = new SteeringManager();
|
|
147
|
+
|
|
148
|
+
// Prompt for strategy
|
|
149
|
+
steeringStrategy = await steeringManager.promptStrategy(detection.steeringDetection);
|
|
150
|
+
|
|
151
|
+
if (steeringStrategy === 'use-kse') {
|
|
152
|
+
// Backup existing steering files
|
|
153
|
+
console.log(chalk.blue('📦 Backing up existing steering files...'));
|
|
154
|
+
const backupResult = await steeringManager.backupSteering(projectPath);
|
|
155
|
+
|
|
156
|
+
if (backupResult.success) {
|
|
157
|
+
steeringBackupId = backupResult.backupId;
|
|
158
|
+
console.log(chalk.green(`✅ Steering backup created: ${steeringBackupId}`));
|
|
159
|
+
|
|
160
|
+
// Install kse steering files
|
|
161
|
+
console.log(chalk.blue('📝 Installing kse steering files...'));
|
|
162
|
+
const installResult = await steeringManager.installKseSteering(projectPath);
|
|
163
|
+
|
|
164
|
+
if (installResult.success) {
|
|
165
|
+
console.log(chalk.green(`✅ Installed ${installResult.filesInstalled} kse steering file(s)`));
|
|
166
|
+
} else {
|
|
167
|
+
console.log(chalk.red(`❌ Failed to install kse steering: ${installResult.error}`));
|
|
168
|
+
console.log(chalk.yellow('Aborting adoption'));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
console.log(chalk.red(`❌ Failed to backup steering: ${backupResult.error}`));
|
|
173
|
+
console.log(chalk.yellow('Aborting adoption for safety'));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
} else if (steeringStrategy === 'use-project') {
|
|
177
|
+
console.log(chalk.blue('✅ Keeping existing steering files'));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Save steering strategy to adoption config
|
|
181
|
+
const adoptionConfig = new AdoptionConfig(projectPath);
|
|
182
|
+
await adoptionConfig.updateSteeringStrategy(steeringStrategy, steeringBackupId);
|
|
183
|
+
|
|
184
|
+
console.log();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 8. Create backup if needed
|
|
138
188
|
let backupId = null;
|
|
139
189
|
if (detection.hasKiroDir && (strategy === 'partial' || strategy === 'full')) {
|
|
140
190
|
console.log(chalk.blue('📦 Creating backup...'));
|
|
@@ -153,7 +203,7 @@ async function adoptCommand(options = {}) {
|
|
|
153
203
|
|
|
154
204
|
console.log();
|
|
155
205
|
|
|
156
|
-
//
|
|
206
|
+
// 9. Execute adoption
|
|
157
207
|
console.log(chalk.blue('🚀 Executing adoption...'));
|
|
158
208
|
const adoptionStrategy = getAdoptionStrategy(strategy);
|
|
159
209
|
const packageJson = require('../../package.json');
|
|
@@ -166,11 +216,19 @@ async function adoptCommand(options = {}) {
|
|
|
166
216
|
|
|
167
217
|
console.log();
|
|
168
218
|
|
|
169
|
-
//
|
|
219
|
+
// 10. Report results
|
|
170
220
|
if (result.success) {
|
|
171
221
|
console.log(chalk.green('✅ Adoption completed successfully!'));
|
|
172
222
|
console.log();
|
|
173
223
|
|
|
224
|
+
if (steeringStrategy) {
|
|
225
|
+
console.log(chalk.blue('Steering Strategy:'), steeringStrategy);
|
|
226
|
+
if (steeringBackupId) {
|
|
227
|
+
console.log(chalk.gray(' Backup:'), steeringBackupId);
|
|
228
|
+
}
|
|
229
|
+
console.log();
|
|
230
|
+
}
|
|
231
|
+
|
|
174
232
|
if (result.filesCreated.length > 0) {
|
|
175
233
|
console.log(chalk.blue('Files created:'));
|
|
176
234
|
result.filesCreated.forEach(file => console.log(` + ${file}`));
|
|
@@ -198,6 +256,62 @@ async function adoptCommand(options = {}) {
|
|
|
198
256
|
console.log(chalk.gray(' Run'), chalk.cyan('kse rollback'), chalk.gray('if you need to undo changes'));
|
|
199
257
|
}
|
|
200
258
|
|
|
259
|
+
console.log();
|
|
260
|
+
|
|
261
|
+
// 11. Detect tool and offer automation setup
|
|
262
|
+
console.log(chalk.blue('🔍 Detecting your development environment...'));
|
|
263
|
+
try {
|
|
264
|
+
const toolDetection = await detectTool(projectPath);
|
|
265
|
+
const autoConfig = await generateAutoConfig(toolDetection, projectPath);
|
|
266
|
+
|
|
267
|
+
console.log();
|
|
268
|
+
console.log(chalk.blue('Tool Detected:'), chalk.cyan(toolDetection.primaryTool));
|
|
269
|
+
console.log(chalk.blue('Confidence:'), autoConfig.confidence);
|
|
270
|
+
|
|
271
|
+
if (autoConfig.notes.length > 0) {
|
|
272
|
+
console.log();
|
|
273
|
+
autoConfig.notes.forEach(note => console.log(chalk.gray(` ℹ️ ${note}`)));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Offer automation setup (unless --auto)
|
|
277
|
+
if (!auto && autoConfig.suggestedPresets.length > 0) {
|
|
278
|
+
console.log();
|
|
279
|
+
const { setupAutomation } = await inquirer.prompt([
|
|
280
|
+
{
|
|
281
|
+
type: 'confirm',
|
|
282
|
+
name: 'setupAutomation',
|
|
283
|
+
message: 'Would you like to set up automation for this tool?',
|
|
284
|
+
default: true
|
|
285
|
+
}
|
|
286
|
+
]);
|
|
287
|
+
|
|
288
|
+
if (setupAutomation) {
|
|
289
|
+
console.log();
|
|
290
|
+
console.log(chalk.blue('📋 Recommended automation setup:'));
|
|
291
|
+
console.log();
|
|
292
|
+
console.log(chalk.gray('Suggested presets:'));
|
|
293
|
+
autoConfig.suggestedPresets.forEach(preset => {
|
|
294
|
+
console.log(` - ${preset}`);
|
|
295
|
+
});
|
|
296
|
+
console.log();
|
|
297
|
+
console.log(chalk.gray('Run these commands to set up:'));
|
|
298
|
+
autoConfig.suggestedCommands.forEach(cmd => {
|
|
299
|
+
console.log(chalk.cyan(` ${cmd}`));
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
} else if (autoConfig.suggestedCommands.length > 0) {
|
|
303
|
+
console.log();
|
|
304
|
+
console.log(chalk.blue('💡 Automation setup:'));
|
|
305
|
+
autoConfig.suggestedCommands.forEach(cmd => {
|
|
306
|
+
console.log(chalk.gray(` ${cmd}`));
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
} catch (toolError) {
|
|
310
|
+
// Tool detection is optional, don't fail adoption if it errors
|
|
311
|
+
console.log(chalk.yellow('⚠️ Could not detect development tool'));
|
|
312
|
+
console.log(chalk.gray(' You can manually set up automation later'));
|
|
313
|
+
}
|
|
314
|
+
|
|
201
315
|
console.log();
|
|
202
316
|
console.log(chalk.blue('💡 Next steps:'));
|
|
203
317
|
console.log(' 1. Review the .kiro/ directory structure');
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Command
|
|
3
|
+
*
|
|
4
|
+
* Exports spec context for cross-tool usage
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const ContextExporter = require('../context/context-exporter');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Export spec context
|
|
12
|
+
*
|
|
13
|
+
* @param {string} specName - Spec name
|
|
14
|
+
* @param {Object} options - Command options
|
|
15
|
+
* @param {boolean} options.requirements - Include requirements (default: true)
|
|
16
|
+
* @param {boolean} options.design - Include design (default: true)
|
|
17
|
+
* @param {boolean} options.tasks - Include tasks (default: true)
|
|
18
|
+
* @param {boolean} options.steering - Include steering rules
|
|
19
|
+
* @param {string} options.steeringFiles - Comma-separated list of steering files
|
|
20
|
+
* @returns {Promise<void>}
|
|
21
|
+
*/
|
|
22
|
+
async function exportContext(specName, options = {}) {
|
|
23
|
+
const projectPath = process.cwd();
|
|
24
|
+
const exporter = new ContextExporter();
|
|
25
|
+
|
|
26
|
+
console.log(chalk.red('🔥') + ' Exporting Context');
|
|
27
|
+
console.log();
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
console.log(`Spec: ${chalk.cyan(specName)}`);
|
|
31
|
+
console.log();
|
|
32
|
+
|
|
33
|
+
// Parse options
|
|
34
|
+
const exportOptions = {
|
|
35
|
+
includeRequirements: options.requirements !== false,
|
|
36
|
+
includeDesign: options.design !== false,
|
|
37
|
+
includeTasks: options.tasks !== false,
|
|
38
|
+
includeSteering: options.steering === true,
|
|
39
|
+
steeringFiles: options.steeringFiles
|
|
40
|
+
? options.steeringFiles.split(',').map(f => f.trim())
|
|
41
|
+
: []
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
console.log('Export options:');
|
|
45
|
+
console.log(` Requirements: ${exportOptions.includeRequirements ? chalk.green('✓') : chalk.gray('✗')}`);
|
|
46
|
+
console.log(` Design: ${exportOptions.includeDesign ? chalk.green('✓') : chalk.gray('✗')}`);
|
|
47
|
+
console.log(` Tasks: ${exportOptions.includeTasks ? chalk.green('✓') : chalk.gray('✗')}`);
|
|
48
|
+
console.log(` Steering: ${exportOptions.includeSteering ? chalk.green('✓') : chalk.gray('✗')}`);
|
|
49
|
+
|
|
50
|
+
if (exportOptions.includeSteering && exportOptions.steeringFiles.length > 0) {
|
|
51
|
+
console.log(` Steering files: ${chalk.gray(exportOptions.steeringFiles.join(', '))}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log();
|
|
55
|
+
console.log('Exporting...');
|
|
56
|
+
console.log();
|
|
57
|
+
|
|
58
|
+
const result = await exporter.exportContext(
|
|
59
|
+
projectPath,
|
|
60
|
+
specName,
|
|
61
|
+
exportOptions
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (result.success) {
|
|
65
|
+
console.log(chalk.green('✅ Context exported successfully'));
|
|
66
|
+
console.log();
|
|
67
|
+
console.log(`Export file: ${chalk.cyan(result.exportPath)}`);
|
|
68
|
+
console.log(`Sections: ${chalk.gray(result.sections)}`);
|
|
69
|
+
console.log(`Size: ${chalk.gray(formatBytes(result.size))}`);
|
|
70
|
+
console.log();
|
|
71
|
+
console.log('Usage:');
|
|
72
|
+
console.log(' 1. Copy the exported file to your AI coding assistant');
|
|
73
|
+
console.log(' 2. Reference specific sections when working on tasks');
|
|
74
|
+
console.log(' 3. Update task status in the original tasks.md after completion');
|
|
75
|
+
} else {
|
|
76
|
+
console.log(chalk.red('❌ Export failed'));
|
|
77
|
+
console.log();
|
|
78
|
+
console.log(`Error: ${result.error}`);
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.log(chalk.red('❌ Error:'), error.message);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Format bytes to human-readable string
|
|
87
|
+
*
|
|
88
|
+
* @param {number} bytes - Bytes
|
|
89
|
+
* @returns {string} Formatted string
|
|
90
|
+
*/
|
|
91
|
+
function formatBytes(bytes) {
|
|
92
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
93
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
94
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
exportContext
|
|
99
|
+
};
|
|
@@ -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;
|