kiro-spec-engine 1.12.2 → 1.13.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 +45 -0
- package/bin/kiro-spec-engine.js +24 -3
- package/lib/steering/compliance-auto-fixer.js +204 -0
- package/lib/steering/compliance-cache.js +99 -0
- package/lib/steering/compliance-error-reporter.js +70 -0
- package/lib/steering/index.js +86 -0
- package/lib/steering/steering-compliance-checker.js +73 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,51 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Steering Directory Compliance Check with Auto-Fix**: Automatic validation and repair of `.kiro/steering/` directory
|
|
12
|
+
- Enforces allowlist of 4 files: CORE_PRINCIPLES.md, ENVIRONMENT.md, CURRENT_CONTEXT.md, RULES_GUIDE.md
|
|
13
|
+
- Prohibits subdirectories to prevent context pollution
|
|
14
|
+
- **Auto-fix feature**: Automatically backs up and removes violations without user confirmation
|
|
15
|
+
- **Multi-user support**: Detects and respects `contexts/` multi-user collaboration setup
|
|
16
|
+
- Differential backup: Only backs up violating files/directories (not entire .kiro/)
|
|
17
|
+
- Backup location: `.kiro/backups/steering-cleanup-{timestamp}/`
|
|
18
|
+
- Version-based caching (~/.kse/steering-check-cache.json) to avoid repeated checks
|
|
19
|
+
- Performance target: <50ms per check
|
|
20
|
+
- Clear progress messages during auto-fix
|
|
21
|
+
- Bypass options: `--skip-steering-check` flag and `KSE_SKIP_STEERING_CHECK` environment variable
|
|
22
|
+
- Force check option: `--force-steering-check` flag
|
|
23
|
+
- Comprehensive documentation in `.kiro/README.md`
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- **CLI**: All commands now run steering directory compliance check before execution
|
|
27
|
+
- **Auto-fix behavior**: Violations are automatically fixed (backup + clean) without user confirmation
|
|
28
|
+
- **Multi-user awareness**: Auto-fix shows informational message when multi-user project detected
|
|
29
|
+
- **Documentation**: Added "Steering Directory Compliance" section with multi-user guidance to `.kiro/README.md`
|
|
30
|
+
|
|
31
|
+
### Breaking Changes
|
|
32
|
+
- Commands will automatically fix steering directory violations on first run
|
|
33
|
+
- Violating files/directories are backed up to `.kiro/backups/steering-cleanup-{timestamp}/`
|
|
34
|
+
- Use `--skip-steering-check` flag to bypass if needed during migration
|
|
35
|
+
- Multi-user projects: Personal contexts in `contexts/` are preserved during auto-fix
|
|
36
|
+
|
|
37
|
+
## [1.12.3] - 2026-01-29
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
- **Documentation Enhancement**: Comprehensive `.kiro/README.md` update (v2.0)
|
|
41
|
+
- Added complete directory structure documentation with purpose explanations
|
|
42
|
+
- Added workspace management section with detailed usage examples
|
|
43
|
+
- Added document governance section with validation commands
|
|
44
|
+
- Added data storage location details for `kse workspace` feature
|
|
45
|
+
- Added JSON data structure examples for workspace-state.json
|
|
46
|
+
- Clarified difference between `kse workspace` (cross-project) and `contexts/` (multi-user)
|
|
47
|
+
- Added key features list for workspace management
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
- **Documentation**: Updated `.kiro/README.md` version to 2.0 with comprehensive feature documentation
|
|
51
|
+
- **Documentation**: Enhanced workspace storage explanation with platform-specific paths
|
|
52
|
+
|
|
8
53
|
## [1.12.2] - 2026-01-29
|
|
9
54
|
|
|
10
55
|
### Added
|
package/bin/kiro-spec-engine.js
CHANGED
|
@@ -44,7 +44,9 @@ program
|
|
|
44
44
|
.option('-l, --lang <locale>', 'Set language (en/zh)', (locale) => {
|
|
45
45
|
i18n.setLocale(locale);
|
|
46
46
|
})
|
|
47
|
-
.option('--no-version-check', 'Suppress version mismatch warnings')
|
|
47
|
+
.option('--no-version-check', 'Suppress version mismatch warnings')
|
|
48
|
+
.option('--skip-steering-check', 'Skip steering directory compliance check (not recommended)')
|
|
49
|
+
.option('--force-steering-check', 'Force steering directory compliance check even if cache is valid');
|
|
48
50
|
|
|
49
51
|
// 初始化项目命令
|
|
50
52
|
program
|
|
@@ -478,5 +480,24 @@ async function updateProjectConfig(projectName) {
|
|
|
478
480
|
}
|
|
479
481
|
}
|
|
480
482
|
|
|
481
|
-
//
|
|
482
|
-
|
|
483
|
+
// Run steering directory compliance check before parsing commands
|
|
484
|
+
(async function() {
|
|
485
|
+
const { runSteeringComplianceCheck } = require('../lib/steering');
|
|
486
|
+
|
|
487
|
+
// Check for bypass flags
|
|
488
|
+
const args = process.argv.slice(2);
|
|
489
|
+
const skipCheck = args.includes('--skip-steering-check') ||
|
|
490
|
+
process.env.KSE_SKIP_STEERING_CHECK === '1';
|
|
491
|
+
const forceCheck = args.includes('--force-steering-check');
|
|
492
|
+
|
|
493
|
+
// Run compliance check
|
|
494
|
+
await runSteeringComplianceCheck({
|
|
495
|
+
skip: skipCheck,
|
|
496
|
+
force: forceCheck,
|
|
497
|
+
projectPath: process.cwd(),
|
|
498
|
+
version: packageJson.version
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// 解析命令行参数
|
|
502
|
+
program.parse();
|
|
503
|
+
})();
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Automatically fix steering directory compliance violations
|
|
7
|
+
* by backing up and removing disallowed files and subdirectories
|
|
8
|
+
*/
|
|
9
|
+
class ComplianceAutoFixer {
|
|
10
|
+
/**
|
|
11
|
+
* Fix compliance violations automatically (no user confirmation)
|
|
12
|
+
* @param {string} steeringPath - Path to steering directory
|
|
13
|
+
* @param {Array} violations - List of violations from compliance check
|
|
14
|
+
* @returns {Object} Fix result with backup info and cleaned files
|
|
15
|
+
*/
|
|
16
|
+
async fix(steeringPath, violations) {
|
|
17
|
+
if (!violations || violations.length === 0) {
|
|
18
|
+
return {
|
|
19
|
+
success: true,
|
|
20
|
+
message: 'No violations to fix',
|
|
21
|
+
backupPath: null,
|
|
22
|
+
cleanedFiles: [],
|
|
23
|
+
cleanedDirs: []
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Separate violations by type
|
|
28
|
+
const disallowedFiles = violations
|
|
29
|
+
.filter(v => v.type === 'disallowed_file')
|
|
30
|
+
.map(v => v.name);
|
|
31
|
+
const subdirectories = violations
|
|
32
|
+
.filter(v => v.type === 'subdirectory')
|
|
33
|
+
.map(v => v.name);
|
|
34
|
+
|
|
35
|
+
// Check if this is a multi-user project (contexts/ directory exists)
|
|
36
|
+
const contextsPath = path.join(path.dirname(path.dirname(steeringPath)), 'contexts');
|
|
37
|
+
const isMultiUser = await fs.pathExists(contextsPath);
|
|
38
|
+
|
|
39
|
+
// Show what will be fixed
|
|
40
|
+
console.log(chalk.yellow('\n🔧 Auto-fixing steering directory compliance violations...\n'));
|
|
41
|
+
|
|
42
|
+
if (disallowedFiles.length > 0) {
|
|
43
|
+
console.log(chalk.yellow('Disallowed files to be removed:'));
|
|
44
|
+
disallowedFiles.forEach(file => console.log(` - ${file}`));
|
|
45
|
+
console.log();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (subdirectories.length > 0) {
|
|
49
|
+
console.log(chalk.yellow('Subdirectories to be removed:'));
|
|
50
|
+
subdirectories.forEach(dir => console.log(` - ${dir}/`));
|
|
51
|
+
console.log();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Show multi-user warning if applicable
|
|
55
|
+
if (isMultiUser) {
|
|
56
|
+
console.log(chalk.blue('ℹ️ Multi-user project detected'));
|
|
57
|
+
console.log(chalk.blue(' Your personal CURRENT_CONTEXT.md is preserved in contexts/'));
|
|
58
|
+
console.log();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Create backup
|
|
62
|
+
const backupPath = await this._createBackup(steeringPath, disallowedFiles, subdirectories);
|
|
63
|
+
console.log(chalk.green(`✓ Backup created: ${backupPath}\n`));
|
|
64
|
+
|
|
65
|
+
// Clean up violations
|
|
66
|
+
const cleanedFiles = [];
|
|
67
|
+
const cleanedDirs = [];
|
|
68
|
+
|
|
69
|
+
// Remove disallowed files
|
|
70
|
+
for (const file of disallowedFiles) {
|
|
71
|
+
const filePath = path.join(steeringPath, file);
|
|
72
|
+
try {
|
|
73
|
+
await fs.remove(filePath);
|
|
74
|
+
cleanedFiles.push(file);
|
|
75
|
+
console.log(chalk.green(`✓ Removed file: ${file}`));
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(chalk.red(`✗ Failed to remove ${file}: ${error.message}`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Remove subdirectories
|
|
82
|
+
for (const dir of subdirectories) {
|
|
83
|
+
const dirPath = path.join(steeringPath, dir);
|
|
84
|
+
try {
|
|
85
|
+
await fs.remove(dirPath);
|
|
86
|
+
cleanedDirs.push(dir);
|
|
87
|
+
console.log(chalk.green(`✓ Removed directory: ${dir}/`));
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error(chalk.red(`✗ Failed to remove ${dir}/: ${error.message}`));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log(chalk.green('\n✓ Steering directory cleaned successfully!\n'));
|
|
94
|
+
console.log(chalk.blue('Backup location:'));
|
|
95
|
+
console.log(` ${backupPath}\n`);
|
|
96
|
+
console.log(chalk.blue('To restore from backup:'));
|
|
97
|
+
console.log(` kse rollback --backup ${path.basename(backupPath)}\n`);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
success: true,
|
|
101
|
+
message: 'Steering directory fixed successfully',
|
|
102
|
+
backupPath,
|
|
103
|
+
cleanedFiles,
|
|
104
|
+
cleanedDirs
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Create differential backup of violations only
|
|
110
|
+
* @private
|
|
111
|
+
*/
|
|
112
|
+
async _createBackup(steeringPath, files, dirs) {
|
|
113
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
|
114
|
+
const backupId = `steering-cleanup-${timestamp}`;
|
|
115
|
+
const backupPath = path.join(path.dirname(path.dirname(steeringPath)), 'backups', backupId);
|
|
116
|
+
|
|
117
|
+
// Create backup directory
|
|
118
|
+
await fs.ensureDir(backupPath);
|
|
119
|
+
|
|
120
|
+
// Backup disallowed files
|
|
121
|
+
for (const file of files) {
|
|
122
|
+
const sourcePath = path.join(steeringPath, file);
|
|
123
|
+
const destPath = path.join(backupPath, file);
|
|
124
|
+
|
|
125
|
+
if (await fs.pathExists(sourcePath)) {
|
|
126
|
+
await fs.copy(sourcePath, destPath);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Backup subdirectories
|
|
131
|
+
for (const dir of dirs) {
|
|
132
|
+
const sourcePath = path.join(steeringPath, dir);
|
|
133
|
+
const destPath = path.join(backupPath, dir);
|
|
134
|
+
|
|
135
|
+
if (await fs.pathExists(sourcePath)) {
|
|
136
|
+
await fs.copy(sourcePath, destPath);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Create backup manifest
|
|
141
|
+
const manifest = {
|
|
142
|
+
backupId,
|
|
143
|
+
timestamp: new Date().toISOString(),
|
|
144
|
+
type: 'steering-cleanup',
|
|
145
|
+
steeringPath,
|
|
146
|
+
files,
|
|
147
|
+
directories: dirs,
|
|
148
|
+
totalItems: files.length + dirs.length
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
await fs.writeJson(path.join(backupPath, 'manifest.json'), manifest, { spaces: 2 });
|
|
152
|
+
|
|
153
|
+
return backupPath;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Restore from backup
|
|
158
|
+
* @param {string} backupPath - Path to backup directory
|
|
159
|
+
* @param {string} steeringPath - Path to steering directory
|
|
160
|
+
*/
|
|
161
|
+
async restore(backupPath, steeringPath) {
|
|
162
|
+
// Read manifest
|
|
163
|
+
const manifestPath = path.join(backupPath, 'manifest.json');
|
|
164
|
+
if (!await fs.pathExists(manifestPath)) {
|
|
165
|
+
throw new Error('Invalid backup: manifest.json not found');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const manifest = await fs.readJson(manifestPath);
|
|
169
|
+
|
|
170
|
+
console.log(chalk.yellow('\n🔄 Restoring from backup...\n'));
|
|
171
|
+
|
|
172
|
+
// Restore files
|
|
173
|
+
for (const file of manifest.files) {
|
|
174
|
+
const sourcePath = path.join(backupPath, file);
|
|
175
|
+
const destPath = path.join(steeringPath, file);
|
|
176
|
+
|
|
177
|
+
if (await fs.pathExists(sourcePath)) {
|
|
178
|
+
await fs.copy(sourcePath, destPath);
|
|
179
|
+
console.log(chalk.green(`✓ Restored file: ${file}`));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Restore directories
|
|
184
|
+
for (const dir of manifest.directories) {
|
|
185
|
+
const sourcePath = path.join(backupPath, dir);
|
|
186
|
+
const destPath = path.join(steeringPath, dir);
|
|
187
|
+
|
|
188
|
+
if (await fs.pathExists(sourcePath)) {
|
|
189
|
+
await fs.copy(sourcePath, destPath);
|
|
190
|
+
console.log(chalk.green(`✓ Restored directory: ${dir}/`));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log(chalk.green('\n✓ Restore completed!\n'));
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
success: true,
|
|
198
|
+
restoredFiles: manifest.files,
|
|
199
|
+
restoredDirs: manifest.directories
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = ComplianceAutoFixer;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ComplianceCache - Manages version-based check caching
|
|
7
|
+
*
|
|
8
|
+
* Stores the last successful compliance check version to avoid
|
|
9
|
+
* repeated checks for the same kse version.
|
|
10
|
+
*/
|
|
11
|
+
class ComplianceCache {
|
|
12
|
+
/**
|
|
13
|
+
* Create a new ComplianceCache instance
|
|
14
|
+
*
|
|
15
|
+
* @param {string} cachePath - Optional custom cache file path
|
|
16
|
+
*/
|
|
17
|
+
constructor(cachePath = null) {
|
|
18
|
+
this.cachePath = cachePath || this.getDefaultCachePath();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the default cache file path
|
|
23
|
+
*
|
|
24
|
+
* @returns {string} Path to ~/.kse/steering-check-cache.json
|
|
25
|
+
*/
|
|
26
|
+
getDefaultCachePath() {
|
|
27
|
+
const homeDir = os.homedir();
|
|
28
|
+
return path.join(homeDir, '.kse', 'steering-check-cache.json');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if cache is valid for current version
|
|
33
|
+
*
|
|
34
|
+
* @param {string} currentVersion - Current kse version
|
|
35
|
+
* @returns {boolean} True if cache is valid
|
|
36
|
+
*/
|
|
37
|
+
isValid(currentVersion) {
|
|
38
|
+
try {
|
|
39
|
+
if (!fs.existsSync(this.cachePath)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const cache = JSON.parse(fs.readFileSync(this.cachePath, 'utf8'));
|
|
44
|
+
return cache.version === currentVersion && cache.lastCheck === 'success';
|
|
45
|
+
} catch (error) {
|
|
46
|
+
// Treat any cache read error as cache miss
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Update cache with successful check
|
|
53
|
+
*
|
|
54
|
+
* @param {string} version - kse version
|
|
55
|
+
* @returns {boolean} True if update succeeded
|
|
56
|
+
*/
|
|
57
|
+
update(version) {
|
|
58
|
+
try {
|
|
59
|
+
const cacheDir = path.dirname(this.cachePath);
|
|
60
|
+
|
|
61
|
+
// Ensure cache directory exists
|
|
62
|
+
if (!fs.existsSync(cacheDir)) {
|
|
63
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const cacheData = {
|
|
67
|
+
version,
|
|
68
|
+
timestamp: new Date().toISOString(),
|
|
69
|
+
lastCheck: 'success'
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
fs.writeFileSync(this.cachePath, JSON.stringify(cacheData, null, 2), 'utf8');
|
|
73
|
+
return true;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
// Log error but don't throw - cache write failure is not critical
|
|
76
|
+
console.warn(`Warning: Failed to update compliance cache: ${error.message}`);
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Clear the cache
|
|
83
|
+
*
|
|
84
|
+
* @returns {boolean} True if clear succeeded
|
|
85
|
+
*/
|
|
86
|
+
clear() {
|
|
87
|
+
try {
|
|
88
|
+
if (fs.existsSync(this.cachePath)) {
|
|
89
|
+
fs.unlinkSync(this.cachePath);
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.warn(`Warning: Failed to clear compliance cache: ${error.message}`);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = ComplianceCache;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ComplianceErrorReporter - Formats and displays compliance violations
|
|
5
|
+
*
|
|
6
|
+
* Creates user-friendly error messages with clear sections,
|
|
7
|
+
* violation details, and actionable fix suggestions.
|
|
8
|
+
*/
|
|
9
|
+
class ComplianceErrorReporter {
|
|
10
|
+
/**
|
|
11
|
+
* Format compliance violations into user-friendly message
|
|
12
|
+
*
|
|
13
|
+
* @param {ComplianceViolation[]} violations - List of violations
|
|
14
|
+
* @returns {string} Formatted error message
|
|
15
|
+
*/
|
|
16
|
+
formatError(violations) {
|
|
17
|
+
const disallowedFiles = violations.filter(v => v.type === 'disallowed_file');
|
|
18
|
+
const subdirectories = violations.filter(v => v.type === 'subdirectory');
|
|
19
|
+
|
|
20
|
+
let message = '\n';
|
|
21
|
+
message += chalk.red.bold('❌ Steering Directory Compliance Check Failed') + '\n\n';
|
|
22
|
+
message += 'The .kiro/steering/ directory contains files or subdirectories that are not allowed.\n';
|
|
23
|
+
message += 'This directory is automatically loaded in every AI session, so keeping it clean is critical.\n\n';
|
|
24
|
+
|
|
25
|
+
message += chalk.yellow.bold('Violations Found:') + '\n';
|
|
26
|
+
|
|
27
|
+
if (disallowedFiles.length > 0) {
|
|
28
|
+
message += ' • ' + chalk.yellow('Disallowed files:') + '\n';
|
|
29
|
+
disallowedFiles.forEach(v => {
|
|
30
|
+
message += ` - ${v.name}\n`;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (subdirectories.length > 0) {
|
|
35
|
+
message += ' • ' + chalk.yellow('Subdirectories (not allowed):') + '\n';
|
|
36
|
+
subdirectories.forEach(v => {
|
|
37
|
+
message += ` - ${v.name}/\n`;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
message += '\n' + chalk.green.bold('Allowed Files:') + '\n';
|
|
42
|
+
message += ' ✓ CORE_PRINCIPLES.md\n';
|
|
43
|
+
message += ' ✓ ENVIRONMENT.md\n';
|
|
44
|
+
message += ' ✓ CURRENT_CONTEXT.md\n';
|
|
45
|
+
message += ' ✓ RULES_GUIDE.md\n';
|
|
46
|
+
|
|
47
|
+
message += '\n' + chalk.cyan.bold('Fix Suggestions:') + '\n';
|
|
48
|
+
message += ' • Move analysis reports to: .kiro/specs/{spec-name}/reports/\n';
|
|
49
|
+
message += ' • Move historical data to: .kiro/specs/{spec-name}/\n';
|
|
50
|
+
message += ' • Move detailed docs to: docs/\n';
|
|
51
|
+
message += ' • Delete temporary files\n';
|
|
52
|
+
|
|
53
|
+
message += '\n' + chalk.gray('To bypass this check (not recommended):') + '\n';
|
|
54
|
+
message += chalk.gray(' kse <command> --skip-steering-check') + '\n';
|
|
55
|
+
|
|
56
|
+
return message;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Display error and exit
|
|
61
|
+
*
|
|
62
|
+
* @param {ComplianceViolation[]} violations - List of violations
|
|
63
|
+
*/
|
|
64
|
+
reportAndExit(violations) {
|
|
65
|
+
console.error(this.formatError(violations));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = ComplianceErrorReporter;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const SteeringComplianceChecker = require('./steering-compliance-checker');
|
|
3
|
+
const ComplianceCache = require('./compliance-cache');
|
|
4
|
+
const ComplianceErrorReporter = require('./compliance-error-reporter');
|
|
5
|
+
const ComplianceAutoFixer = require('./compliance-auto-fixer');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Run steering directory compliance check
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} options - Check options
|
|
11
|
+
* @param {boolean} options.skip - Skip the check entirely
|
|
12
|
+
* @param {boolean} options.force - Force check even if cache is valid
|
|
13
|
+
* @param {string} options.projectPath - Project root path (defaults to cwd)
|
|
14
|
+
* @param {string} options.version - Current kse version
|
|
15
|
+
* @returns {boolean} True if compliant or check skipped, false otherwise
|
|
16
|
+
*/
|
|
17
|
+
async function runSteeringComplianceCheck(options = {}) {
|
|
18
|
+
const {
|
|
19
|
+
skip = false,
|
|
20
|
+
force = false,
|
|
21
|
+
projectPath = process.cwd(),
|
|
22
|
+
version = require('../../package.json').version
|
|
23
|
+
} = options;
|
|
24
|
+
|
|
25
|
+
// Skip check if requested
|
|
26
|
+
if (skip) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const checker = new SteeringComplianceChecker();
|
|
32
|
+
const cache = new ComplianceCache();
|
|
33
|
+
const reporter = new ComplianceErrorReporter();
|
|
34
|
+
const fixer = new ComplianceAutoFixer();
|
|
35
|
+
|
|
36
|
+
// Check cache unless forced
|
|
37
|
+
if (!force && cache.isValid(version)) {
|
|
38
|
+
// Cache is valid, skip check
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Perform compliance check with performance measurement
|
|
43
|
+
const startTime = Date.now();
|
|
44
|
+
const steeringPath = path.join(projectPath, '.kiro', 'steering');
|
|
45
|
+
const result = checker.check(steeringPath);
|
|
46
|
+
const duration = Date.now() - startTime;
|
|
47
|
+
|
|
48
|
+
// Log warning if check exceeds performance target
|
|
49
|
+
if (duration > 50) {
|
|
50
|
+
console.warn(`Warning: Steering compliance check took ${duration}ms (target: <50ms)`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!result.compliant) {
|
|
54
|
+
// Auto-fix violations
|
|
55
|
+
console.log(''); // Empty line for better formatting
|
|
56
|
+
const fixResult = await fixer.fix(steeringPath, result.violations);
|
|
57
|
+
|
|
58
|
+
if (fixResult.success) {
|
|
59
|
+
console.log('✓ Steering directory is now compliant\n');
|
|
60
|
+
// Update cache after successful fix
|
|
61
|
+
cache.update(version);
|
|
62
|
+
return true;
|
|
63
|
+
} else {
|
|
64
|
+
// If fix failed, report and exit
|
|
65
|
+
reporter.reportAndExit(result.violations);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Update cache on success
|
|
71
|
+
cache.update(version);
|
|
72
|
+
return true;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
// Log error but don't block execution
|
|
75
|
+
console.warn(`Warning: Steering compliance check failed: ${error.message}`);
|
|
76
|
+
return true; // Allow execution to proceed
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
runSteeringComplianceCheck,
|
|
82
|
+
SteeringComplianceChecker,
|
|
83
|
+
ComplianceCache,
|
|
84
|
+
ComplianceErrorReporter,
|
|
85
|
+
ComplianceAutoFixer
|
|
86
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SteeringComplianceChecker - Validates steering directory compliance
|
|
6
|
+
*
|
|
7
|
+
* Ensures the .kiro/steering/ directory contains only allowed files
|
|
8
|
+
* and no subdirectories to prevent context pollution and excessive
|
|
9
|
+
* token consumption in AI sessions.
|
|
10
|
+
*/
|
|
11
|
+
class SteeringComplianceChecker {
|
|
12
|
+
/**
|
|
13
|
+
* Get list of allowed files in steering directory
|
|
14
|
+
*
|
|
15
|
+
* @returns {string[]} Array of allowed file names
|
|
16
|
+
*/
|
|
17
|
+
getAllowedFiles() {
|
|
18
|
+
return [
|
|
19
|
+
'CORE_PRINCIPLES.md',
|
|
20
|
+
'ENVIRONMENT.md',
|
|
21
|
+
'CURRENT_CONTEXT.md',
|
|
22
|
+
'RULES_GUIDE.md'
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if steering directory is compliant
|
|
28
|
+
*
|
|
29
|
+
* @param {string} steeringPath - Path to steering directory
|
|
30
|
+
* @returns {ComplianceResult} Result with status and violations
|
|
31
|
+
*/
|
|
32
|
+
check(steeringPath) {
|
|
33
|
+
// Non-existent directory is compliant
|
|
34
|
+
if (!fs.existsSync(steeringPath)) {
|
|
35
|
+
return { compliant: true };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const violations = [];
|
|
39
|
+
const allowedFiles = this.getAllowedFiles();
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const entries = fs.readdirSync(steeringPath, { withFileTypes: true });
|
|
43
|
+
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
if (entry.isDirectory()) {
|
|
46
|
+
// Subdirectories are not allowed
|
|
47
|
+
violations.push({
|
|
48
|
+
type: 'subdirectory',
|
|
49
|
+
name: entry.name,
|
|
50
|
+
path: path.join(steeringPath, entry.name)
|
|
51
|
+
});
|
|
52
|
+
} else if (!allowedFiles.includes(entry.name)) {
|
|
53
|
+
// File not in allowlist
|
|
54
|
+
violations.push({
|
|
55
|
+
type: 'disallowed_file',
|
|
56
|
+
name: entry.name,
|
|
57
|
+
path: path.join(steeringPath, entry.name)
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
compliant: violations.length === 0,
|
|
64
|
+
violations
|
|
65
|
+
};
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// Re-throw unexpected errors
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = SteeringComplianceChecker;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kiro-spec-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "kiro-spec-engine (kse) - A CLI tool and npm package for spec-driven development with AI coding assistants. NOT the Kiro IDE desktop application.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|