kiro-spec-engine 1.1.0 → 1.2.1
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 +66 -0
- package/README.md +88 -0
- package/README.zh.md +88 -0
- package/bin/kiro-spec-engine.js +60 -1
- package/lib/adoption/adoption-strategy.js +516 -0
- package/lib/adoption/detection-engine.js +242 -0
- package/lib/backup/backup-system.js +372 -0
- package/lib/commands/adopt.js +231 -0
- package/lib/commands/rollback.js +219 -0
- package/lib/commands/upgrade.js +231 -0
- package/lib/upgrade/migration-engine.js +364 -0
- package/lib/upgrade/migrations/.gitkeep +52 -0
- package/lib/upgrade/migrations/1.0.0-to-1.1.0.js +78 -0
- package/lib/utils/validation.js +306 -0
- package/lib/version/version-checker.js +156 -0
- package/package.json +1 -1
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides validation functions for project structure, version files,
|
|
5
|
+
* and dependencies. Used for post-operation validation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { pathExists, readJSON } = require('./fs-utils');
|
|
10
|
+
const { spawn } = require('child_process');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validates project structure
|
|
14
|
+
* Checks if all required files and directories exist
|
|
15
|
+
*
|
|
16
|
+
* @param {string} projectPath - Absolute path to project root
|
|
17
|
+
* @returns {Promise<ValidationResult>}
|
|
18
|
+
*/
|
|
19
|
+
async function validateProjectStructure(projectPath) {
|
|
20
|
+
const errors = [];
|
|
21
|
+
const warnings = [];
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const kiroPath = path.join(projectPath, '.kiro');
|
|
25
|
+
|
|
26
|
+
// Check if .kiro/ directory exists
|
|
27
|
+
const kiroExists = await pathExists(kiroPath);
|
|
28
|
+
if (!kiroExists) {
|
|
29
|
+
errors.push('.kiro/ directory not found');
|
|
30
|
+
return { success: false, errors, warnings };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check required directories
|
|
34
|
+
const requiredDirs = [
|
|
35
|
+
{ path: 'specs', required: false },
|
|
36
|
+
{ path: 'steering', required: true },
|
|
37
|
+
{ path: 'tools', required: true },
|
|
38
|
+
{ path: 'backups', required: false }
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
for (const dir of requiredDirs) {
|
|
42
|
+
const dirPath = path.join(kiroPath, dir.path);
|
|
43
|
+
const exists = await pathExists(dirPath);
|
|
44
|
+
|
|
45
|
+
if (!exists) {
|
|
46
|
+
if (dir.required) {
|
|
47
|
+
errors.push(`Required directory not found: ${dir.path}/`);
|
|
48
|
+
} else {
|
|
49
|
+
warnings.push(`Optional directory not found: ${dir.path}/`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check required steering files
|
|
55
|
+
const requiredSteeringFiles = [
|
|
56
|
+
{ path: 'steering/CORE_PRINCIPLES.md', required: true },
|
|
57
|
+
{ path: 'steering/ENVIRONMENT.md', required: true },
|
|
58
|
+
{ path: 'steering/CURRENT_CONTEXT.md', required: true },
|
|
59
|
+
{ path: 'steering/RULES_GUIDE.md', required: true }
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
for (const file of requiredSteeringFiles) {
|
|
63
|
+
const filePath = path.join(kiroPath, file.path);
|
|
64
|
+
const exists = await pathExists(filePath);
|
|
65
|
+
|
|
66
|
+
if (!exists) {
|
|
67
|
+
if (file.required) {
|
|
68
|
+
errors.push(`Required file not found: ${file.path}`);
|
|
69
|
+
} else {
|
|
70
|
+
warnings.push(`Optional file not found: ${file.path}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check required tool files
|
|
76
|
+
const requiredToolFiles = [
|
|
77
|
+
{ path: 'tools/ultrawork_enhancer.py', required: true }
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
for (const file of requiredToolFiles) {
|
|
81
|
+
const filePath = path.join(kiroPath, file.path);
|
|
82
|
+
const exists = await pathExists(filePath);
|
|
83
|
+
|
|
84
|
+
if (!exists) {
|
|
85
|
+
if (file.required) {
|
|
86
|
+
warnings.push(`Tool file not found: ${file.path}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
success: errors.length === 0,
|
|
93
|
+
errors,
|
|
94
|
+
warnings
|
|
95
|
+
};
|
|
96
|
+
} catch (error) {
|
|
97
|
+
errors.push(`Validation failed: ${error.message}`);
|
|
98
|
+
return { success: false, errors, warnings };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Validates version.json file structure
|
|
104
|
+
*
|
|
105
|
+
* @param {string} projectPath - Absolute path to project root
|
|
106
|
+
* @returns {Promise<ValidationResult>}
|
|
107
|
+
*/
|
|
108
|
+
async function validateVersionFile(projectPath) {
|
|
109
|
+
const errors = [];
|
|
110
|
+
const warnings = [];
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const versionPath = path.join(projectPath, '.kiro', 'version.json');
|
|
114
|
+
|
|
115
|
+
// Check if version.json exists
|
|
116
|
+
const exists = await pathExists(versionPath);
|
|
117
|
+
if (!exists) {
|
|
118
|
+
errors.push('version.json not found');
|
|
119
|
+
return { success: false, errors, warnings };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Read and validate structure
|
|
123
|
+
const versionInfo = await readJSON(versionPath);
|
|
124
|
+
|
|
125
|
+
// Check required fields
|
|
126
|
+
const requiredFields = [
|
|
127
|
+
'kse-version',
|
|
128
|
+
'template-version',
|
|
129
|
+
'created',
|
|
130
|
+
'last-upgraded',
|
|
131
|
+
'upgrade-history'
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
for (const field of requiredFields) {
|
|
135
|
+
if (!(field in versionInfo)) {
|
|
136
|
+
errors.push(`Missing required field in version.json: ${field}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Validate field types
|
|
141
|
+
if (versionInfo['kse-version'] && typeof versionInfo['kse-version'] !== 'string') {
|
|
142
|
+
errors.push('kse-version must be a string');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (versionInfo['template-version'] && typeof versionInfo['template-version'] !== 'string') {
|
|
146
|
+
errors.push('template-version must be a string');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (versionInfo['upgrade-history'] && !Array.isArray(versionInfo['upgrade-history'])) {
|
|
150
|
+
errors.push('upgrade-history must be an array');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Validate upgrade history entries
|
|
154
|
+
if (Array.isArray(versionInfo['upgrade-history'])) {
|
|
155
|
+
versionInfo['upgrade-history'].forEach((entry, index) => {
|
|
156
|
+
if (!entry.from || !entry.to || !entry.date || typeof entry.success !== 'boolean') {
|
|
157
|
+
warnings.push(`Invalid upgrade history entry at index ${index}`);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
success: errors.length === 0,
|
|
164
|
+
errors,
|
|
165
|
+
warnings
|
|
166
|
+
};
|
|
167
|
+
} catch (error) {
|
|
168
|
+
errors.push(`Failed to validate version.json: ${error.message}`);
|
|
169
|
+
return { success: false, errors, warnings };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Validates dependencies (Node.js and Python versions)
|
|
175
|
+
*
|
|
176
|
+
* @param {string} projectPath - Absolute path to project root
|
|
177
|
+
* @returns {Promise<ValidationResult>}
|
|
178
|
+
*/
|
|
179
|
+
async function validateDependencies(projectPath) {
|
|
180
|
+
const errors = [];
|
|
181
|
+
const warnings = [];
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
// Check Node.js version
|
|
185
|
+
const nodeVersion = process.version;
|
|
186
|
+
const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0]);
|
|
187
|
+
|
|
188
|
+
if (nodeMajor < 16) {
|
|
189
|
+
errors.push(`Node.js version ${nodeVersion} is not supported. Requires Node.js 16+`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Check Python version (if Ultrawork tools are present)
|
|
193
|
+
const toolPath = path.join(projectPath, '.kiro/tools/ultrawork_enhancer.py');
|
|
194
|
+
const toolExists = await pathExists(toolPath);
|
|
195
|
+
|
|
196
|
+
if (toolExists) {
|
|
197
|
+
try {
|
|
198
|
+
const pythonVersion = await checkPythonVersion();
|
|
199
|
+
|
|
200
|
+
if (!pythonVersion) {
|
|
201
|
+
warnings.push('Python not found. Ultrawork tools require Python 3.8+');
|
|
202
|
+
} else {
|
|
203
|
+
const [major, minor] = pythonVersion.split('.').map(Number);
|
|
204
|
+
|
|
205
|
+
if (major < 3 || (major === 3 && minor < 8)) {
|
|
206
|
+
warnings.push(`Python ${pythonVersion} found. Ultrawork tools require Python 3.8+`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
warnings.push('Could not check Python version');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
success: errors.length === 0,
|
|
216
|
+
errors,
|
|
217
|
+
warnings
|
|
218
|
+
};
|
|
219
|
+
} catch (error) {
|
|
220
|
+
errors.push(`Failed to validate dependencies: ${error.message}`);
|
|
221
|
+
return { success: false, errors, warnings };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Checks Python version
|
|
227
|
+
*
|
|
228
|
+
* @returns {Promise<string|null>} - Python version string or null if not found
|
|
229
|
+
*/
|
|
230
|
+
function checkPythonVersion() {
|
|
231
|
+
return new Promise((resolve) => {
|
|
232
|
+
const python = spawn('python', ['--version']);
|
|
233
|
+
let output = '';
|
|
234
|
+
|
|
235
|
+
python.stdout.on('data', (data) => {
|
|
236
|
+
output += data.toString();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
python.stderr.on('data', (data) => {
|
|
240
|
+
output += data.toString();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
python.on('close', (code) => {
|
|
244
|
+
if (code === 0 && output) {
|
|
245
|
+
// Extract version number from "Python 3.x.x"
|
|
246
|
+
const match = output.match(/Python (\d+\.\d+\.\d+)/);
|
|
247
|
+
if (match) {
|
|
248
|
+
resolve(match[1]);
|
|
249
|
+
} else {
|
|
250
|
+
resolve(null);
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
resolve(null);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
python.on('error', () => {
|
|
258
|
+
resolve(null);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Timeout after 5 seconds
|
|
262
|
+
setTimeout(() => {
|
|
263
|
+
python.kill();
|
|
264
|
+
resolve(null);
|
|
265
|
+
}, 5000);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Validates complete project (structure + version + dependencies)
|
|
271
|
+
*
|
|
272
|
+
* @param {string} projectPath - Absolute path to project root
|
|
273
|
+
* @returns {Promise<ValidationResult>}
|
|
274
|
+
*/
|
|
275
|
+
async function validateProject(projectPath) {
|
|
276
|
+
const allErrors = [];
|
|
277
|
+
const allWarnings = [];
|
|
278
|
+
|
|
279
|
+
// Run all validations
|
|
280
|
+
const structureResult = await validateProjectStructure(projectPath);
|
|
281
|
+
const versionResult = await validateVersionFile(projectPath);
|
|
282
|
+
const depsResult = await validateDependencies(projectPath);
|
|
283
|
+
|
|
284
|
+
// Combine results
|
|
285
|
+
allErrors.push(...structureResult.errors);
|
|
286
|
+
allErrors.push(...versionResult.errors);
|
|
287
|
+
allErrors.push(...depsResult.errors);
|
|
288
|
+
|
|
289
|
+
allWarnings.push(...structureResult.warnings);
|
|
290
|
+
allWarnings.push(...versionResult.warnings);
|
|
291
|
+
allWarnings.push(...depsResult.warnings);
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
success: allErrors.length === 0,
|
|
295
|
+
errors: allErrors,
|
|
296
|
+
warnings: allWarnings
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
module.exports = {
|
|
301
|
+
validateProjectStructure,
|
|
302
|
+
validateVersionFile,
|
|
303
|
+
validateDependencies,
|
|
304
|
+
validateProject,
|
|
305
|
+
checkPythonVersion
|
|
306
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version Checker
|
|
3
|
+
*
|
|
4
|
+
* Automatically detects version mismatches between project and installed kse.
|
|
5
|
+
* Displays warnings and upgrade suggestions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const VersionManager = require('./version-manager');
|
|
10
|
+
|
|
11
|
+
class VersionChecker {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.versionManager = new VersionManager();
|
|
14
|
+
this.suppressWarnings = false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Checks for version mismatch and displays warning if needed
|
|
19
|
+
*
|
|
20
|
+
* @param {string} projectPath - Absolute path to project root
|
|
21
|
+
* @param {Object} options - Check options
|
|
22
|
+
* @param {boolean} options.noVersionCheck - Suppress warnings
|
|
23
|
+
* @returns {Promise<VersionCheckResult>}
|
|
24
|
+
*/
|
|
25
|
+
async checkVersion(projectPath, options = {}) {
|
|
26
|
+
const { noVersionCheck = false } = options;
|
|
27
|
+
|
|
28
|
+
if (noVersionCheck || this.suppressWarnings) {
|
|
29
|
+
return { mismatch: false, shouldUpgrade: false };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
// Read project version
|
|
34
|
+
const projectVersionInfo = await this.versionManager.readVersion(projectPath);
|
|
35
|
+
|
|
36
|
+
if (!projectVersionInfo) {
|
|
37
|
+
// No version.json - project might not be initialized
|
|
38
|
+
return { mismatch: false, shouldUpgrade: false };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const projectVersion = projectVersionInfo['kse-version'];
|
|
42
|
+
|
|
43
|
+
// Get current kse version
|
|
44
|
+
const packageJson = require('../../package.json');
|
|
45
|
+
const kseVersion = packageJson.version;
|
|
46
|
+
|
|
47
|
+
// Check if upgrade is needed
|
|
48
|
+
const needsUpgrade = this.versionManager.needsUpgrade(projectVersion, kseVersion);
|
|
49
|
+
|
|
50
|
+
if (needsUpgrade) {
|
|
51
|
+
this.displayWarning(projectVersion, kseVersion);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
mismatch: needsUpgrade,
|
|
56
|
+
shouldUpgrade: needsUpgrade,
|
|
57
|
+
projectVersion,
|
|
58
|
+
kseVersion
|
|
59
|
+
};
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// Silently fail - don't block commands if version check fails
|
|
62
|
+
return { mismatch: false, shouldUpgrade: false, error: error.message };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Displays version mismatch warning
|
|
68
|
+
*
|
|
69
|
+
* @param {string} projectVersion - Project version
|
|
70
|
+
* @param {string} kseVersion - Current kse version
|
|
71
|
+
*/
|
|
72
|
+
displayWarning(projectVersion, kseVersion) {
|
|
73
|
+
console.log();
|
|
74
|
+
console.log(chalk.yellow('⚠️ Version Mismatch Detected'));
|
|
75
|
+
console.log(chalk.gray(' Project initialized with kse'), chalk.cyan(`v${projectVersion}`));
|
|
76
|
+
console.log(chalk.gray(' Current kse version:'), chalk.cyan(`v${kseVersion}`));
|
|
77
|
+
console.log();
|
|
78
|
+
console.log(chalk.blue('💡 Tip:'), chalk.gray('Run'), chalk.cyan('kse upgrade'), chalk.gray('to update project templates'));
|
|
79
|
+
console.log(chalk.gray(' Or use'), chalk.cyan('--no-version-check'), chalk.gray('to suppress this warning'));
|
|
80
|
+
console.log();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Checks version and displays detailed information
|
|
85
|
+
*
|
|
86
|
+
* @param {string} projectPath - Absolute path to project root
|
|
87
|
+
* @returns {Promise<void>}
|
|
88
|
+
*/
|
|
89
|
+
async displayVersionInfo(projectPath) {
|
|
90
|
+
try {
|
|
91
|
+
const projectVersionInfo = await this.versionManager.readVersion(projectPath);
|
|
92
|
+
|
|
93
|
+
if (!projectVersionInfo) {
|
|
94
|
+
console.log(chalk.yellow('⚠️ No version information found'));
|
|
95
|
+
console.log(chalk.gray(' This project may not be initialized with kse'));
|
|
96
|
+
console.log(chalk.gray(' Run'), chalk.cyan('kse adopt'), chalk.gray('to adopt this project'));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const packageJson = require('../../package.json');
|
|
101
|
+
const kseVersion = packageJson.version;
|
|
102
|
+
|
|
103
|
+
console.log(chalk.blue('📦 Version Information'));
|
|
104
|
+
console.log();
|
|
105
|
+
console.log(chalk.gray('Project:'));
|
|
106
|
+
console.log(` kse version: ${chalk.cyan(projectVersionInfo['kse-version'])}`);
|
|
107
|
+
console.log(` Template version: ${chalk.cyan(projectVersionInfo['template-version'])}`);
|
|
108
|
+
console.log(` Created: ${chalk.gray(new Date(projectVersionInfo.created).toLocaleString())}`);
|
|
109
|
+
console.log(` Last upgraded: ${chalk.gray(new Date(projectVersionInfo['last-upgraded']).toLocaleString())}`);
|
|
110
|
+
|
|
111
|
+
console.log();
|
|
112
|
+
console.log(chalk.gray('Installed:'));
|
|
113
|
+
console.log(` kse version: ${chalk.cyan(kseVersion)}`);
|
|
114
|
+
|
|
115
|
+
if (projectVersionInfo['upgrade-history'].length > 0) {
|
|
116
|
+
console.log();
|
|
117
|
+
console.log(chalk.gray('Upgrade History:'));
|
|
118
|
+
projectVersionInfo['upgrade-history'].forEach((entry, index) => {
|
|
119
|
+
const icon = entry.success ? chalk.green('✅') : chalk.red('❌');
|
|
120
|
+
const date = new Date(entry.date).toLocaleString();
|
|
121
|
+
console.log(` ${icon} ${entry.from} → ${entry.to} (${date})`);
|
|
122
|
+
if (entry.error) {
|
|
123
|
+
console.log(` ${chalk.red('Error:')} ${entry.error}`);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
console.log();
|
|
129
|
+
|
|
130
|
+
const needsUpgrade = this.versionManager.needsUpgrade(
|
|
131
|
+
projectVersionInfo['kse-version'],
|
|
132
|
+
kseVersion
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (needsUpgrade) {
|
|
136
|
+
console.log(chalk.yellow('⚠️ Upgrade available'));
|
|
137
|
+
console.log(chalk.gray(' Run'), chalk.cyan('kse upgrade'), chalk.gray('to update to'), chalk.cyan(`v${kseVersion}`));
|
|
138
|
+
} else {
|
|
139
|
+
console.log(chalk.green('✅ Project is up to date'));
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.log(chalk.red('❌ Error:'), error.message);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Suppresses version check warnings
|
|
148
|
+
*
|
|
149
|
+
* @param {boolean} suppress - Whether to suppress warnings
|
|
150
|
+
*/
|
|
151
|
+
setSuppressWarnings(suppress) {
|
|
152
|
+
this.suppressWarnings = suppress;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = VersionChecker;
|
package/package.json
CHANGED