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 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
@@ -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
- program.parse();
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.12.2",
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": {