kiro-spec-engine 1.20.5 → 1.21.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 CHANGED
@@ -7,6 +7,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.21.1] - 2026-02-01
11
+
12
+ ### Fixed
13
+ - **Test Suite Compatibility**: Fixed test failures introduced in v1.21.0
14
+ - Updated tests to reflect optional version field (now defaults to "1.0")
15
+ - Added `skipFilesystemValidation` option to `loadConfig()` for testing scenarios
16
+ - Mocked `_validateRepositoryPath` in handler tests to avoid filesystem dependency
17
+ - All 1697 tests now pass successfully
18
+
19
+ ### Technical Details
20
+ - Modified `ConfigManager.loadConfig()` to accept optional `skipFilesystemValidation` parameter
21
+ - Updated test expectations for optional version field validation
22
+ - Enhanced test isolation by mocking filesystem validation in unit tests
23
+ - No functional changes to production code behavior
24
+
25
+ ## [1.21.0] - 2026-02-01
26
+
27
+ ### Added
28
+ - **Manual Configuration Support**: Users can now manually create and edit `.kiro/project-repos.json` without relying solely on auto-scan
29
+ - Version field is now optional (defaults to "1.0" if omitted)
30
+ - Only `name` and `path` are required for each repository entry
31
+ - All other fields (`remote`, `defaultBranch`, `description`, `tags`, `group`, `parent`) are optional
32
+ - Filesystem validation ensures paths exist and contain valid `.git` directories
33
+ - Clear, actionable error messages guide users in fixing configuration issues
34
+ - Comprehensive documentation in `docs/multi-repo-management-guide.md` with examples and troubleshooting
35
+
36
+ ### Changed
37
+ - **Enhanced Validation**: Configuration validation now performs filesystem checks when loading from disk
38
+ - Validates that repository paths exist on the filesystem
39
+ - Verifies each path contains a `.git` directory (not file)
40
+ - Detects and rejects Git worktrees with helpful error messages
41
+ - Reports all validation errors together (not just the first one)
42
+ - Maintains backward compatibility with all v1.18.0+ configurations
43
+
44
+ ### Fixed
45
+ - **Manual Configuration Rejection**: Fixed issue where manually-created configurations were rejected even when valid
46
+ - Users can now manually curate repository lists
47
+ - Users can remove false positives from auto-scan results
48
+ - Users can add repositories that weren't auto-detected
49
+ - Minimal configurations (name + path only) now pass validation
50
+ - User-reported issue: 8 real Git repositories rejected by validation
51
+
52
+ ### Documentation
53
+ - Added comprehensive "Manual Configuration" section to multi-repo management guide
54
+ - Documented minimal configuration format with examples
55
+ - Added troubleshooting guide for common validation errors
56
+ - Included step-by-step instructions for creating manual configurations
57
+
10
58
  ## [1.20.5] - 2026-02-01 🔥 HOTFIX
11
59
 
12
60
  ### Fixed
@@ -193,6 +193,260 @@ Paths can be specified as:
193
193
  "path": "packages/frontend" // Unix/Mac
194
194
  ```
195
195
 
196
+ ## Manual Configuration
197
+
198
+ ### Overview
199
+
200
+ Starting with v1.21.0, you can manually create and edit the `.kiro/project-repos.json` configuration file without relying solely on `kse repo init`. This is useful for:
201
+
202
+ - Curating a specific list of repositories
203
+ - Removing false positives from auto-scan
204
+ - Adding repositories that weren't auto-detected
205
+ - Creating configurations for repositories that don't exist yet
206
+
207
+ ### Minimal Configuration Format
208
+
209
+ The simplest valid configuration requires only `name` and `path` for each repository:
210
+
211
+ ```json
212
+ {
213
+ "repositories": [
214
+ {
215
+ "name": "my-repo",
216
+ "path": "./my-repo"
217
+ },
218
+ {
219
+ "name": "another-repo",
220
+ "path": "./another-repo"
221
+ }
222
+ ]
223
+ }
224
+ ```
225
+
226
+ **Key points:**
227
+ - The `version` field is optional (defaults to "1.0")
228
+ - Only `name` and `path` are required for each repository
229
+ - All other fields (`remote`, `defaultBranch`, `description`, `tags`, `group`) are optional
230
+
231
+ ### Complete Configuration Example
232
+
233
+ For more detailed configurations, you can include all optional fields:
234
+
235
+ ```json
236
+ {
237
+ "version": "1.0",
238
+ "repositories": [
239
+ {
240
+ "name": "frontend",
241
+ "path": "./packages/frontend",
242
+ "remote": "https://github.com/user/frontend.git",
243
+ "defaultBranch": "main",
244
+ "description": "React frontend application",
245
+ "tags": ["ui", "react"],
246
+ "group": "client"
247
+ },
248
+ {
249
+ "name": "backend",
250
+ "path": "./packages/backend",
251
+ "remote": "https://github.com/user/backend.git",
252
+ "defaultBranch": "develop",
253
+ "description": "Node.js API server",
254
+ "tags": ["api", "nodejs"],
255
+ "group": "server"
256
+ },
257
+ {
258
+ "name": "local-only",
259
+ "path": "./local-repo"
260
+ }
261
+ ],
262
+ "groups": {
263
+ "client": {
264
+ "description": "Client-side applications"
265
+ },
266
+ "server": {
267
+ "description": "Server-side services"
268
+ }
269
+ }
270
+ }
271
+ ```
272
+
273
+ ### Field Requirements
274
+
275
+ #### Required Fields
276
+ - **name**: Unique identifier for the repository
277
+ - Must contain only alphanumeric characters, hyphens, underscores, and dots
278
+ - Examples: `"frontend"`, `"my-repo"`, `"repo.1"`, `".github"`
279
+
280
+ - **path**: Path to the repository directory
281
+ - Can be relative (e.g., `"./my-repo"`) or absolute (e.g., `"/home/user/my-repo"`)
282
+ - Must point to an existing directory containing a `.git` directory
283
+ - Cannot point to a Git worktree (`.git` file instead of directory)
284
+
285
+ #### Optional Fields
286
+ - **remote**: Git remote URL (can be omitted for local-only repositories)
287
+ - **defaultBranch**: Default branch name (e.g., `"main"`, `"develop"`)
288
+ - **description**: Human-readable description
289
+ - **tags**: Array of tags for categorization
290
+ - **group**: Group name for logical organization
291
+ - **parent**: Parent repository path (for nested repositories)
292
+
293
+ ### Validation Rules
294
+
295
+ When you manually create or edit the configuration, `kse` validates:
296
+
297
+ 1. **File Format**: Must be valid JSON
298
+ 2. **Structure**: Must have a `repositories` array
299
+ 3. **Required Fields**: Each repository must have `name` and `path`
300
+ 4. **Path Existence**: Each path must exist on the filesystem
301
+ 5. **Git Repository**: Each path must contain a `.git` directory (not file)
302
+ 6. **No Duplicates**: Repository names and paths must be unique
303
+ 7. **No Worktrees**: Paths cannot point to Git worktrees
304
+
305
+ ### Creating a Manual Configuration
306
+
307
+ **Step 1: Create the directory structure**
308
+
309
+ ```bash
310
+ mkdir -p .kiro
311
+ ```
312
+
313
+ **Step 2: Create the configuration file**
314
+
315
+ Create `.kiro/project-repos.json` with your repositories:
316
+
317
+ ```json
318
+ {
319
+ "repositories": [
320
+ {
321
+ "name": "repo1",
322
+ "path": "./repo1"
323
+ },
324
+ {
325
+ "name": "repo2",
326
+ "path": "./repo2"
327
+ }
328
+ ]
329
+ }
330
+ ```
331
+
332
+ **Step 3: Verify the configuration**
333
+
334
+ ```bash
335
+ kse repo status
336
+ ```
337
+
338
+ If there are validation errors, `kse` will display clear error messages:
339
+
340
+ ```
341
+ Error: Repository path validation failed
342
+ - Repository "repo1": path "./repo1" does not exist. Please check the path is correct.
343
+ - Repository "repo2": path "./repo2" is not a Git repository (no .git directory found). Please ensure this is a Git repository.
344
+ ```
345
+
346
+ ### Editing an Existing Configuration
347
+
348
+ You can manually edit the auto-generated configuration to:
349
+
350
+ **Remove repositories:**
351
+ ```json
352
+ {
353
+ "repositories": [
354
+ // Remove unwanted entries from this array
355
+ {
356
+ "name": "keep-this",
357
+ "path": "./keep-this"
358
+ }
359
+ // Deleted: { "name": "remove-this", "path": "./remove-this" }
360
+ ]
361
+ }
362
+ ```
363
+
364
+ **Add new repositories:**
365
+ ```json
366
+ {
367
+ "repositories": [
368
+ {
369
+ "name": "existing-repo",
370
+ "path": "./existing-repo"
371
+ },
372
+ {
373
+ "name": "new-repo",
374
+ "path": "./new-repo"
375
+ }
376
+ ]
377
+ }
378
+ ```
379
+
380
+ **Simplify to minimal format:**
381
+ ```json
382
+ {
383
+ "repositories": [
384
+ {
385
+ "name": "repo1",
386
+ "path": "./repo1"
387
+ // Removed: remote, defaultBranch, description, tags, group
388
+ }
389
+ ]
390
+ }
391
+ ```
392
+
393
+ ### Troubleshooting
394
+
395
+ #### Error: "path does not exist"
396
+
397
+ **Cause**: The specified path doesn't exist on the filesystem.
398
+
399
+ **Solution**: Check the path is correct and the directory exists:
400
+ ```bash
401
+ ls -la ./my-repo # Unix/Mac
402
+ dir .\my-repo # Windows
403
+ ```
404
+
405
+ #### Error: "is not a Git repository"
406
+
407
+ **Cause**: The path exists but doesn't contain a `.git` directory.
408
+
409
+ **Solution**: Initialize the directory as a Git repository:
410
+ ```bash
411
+ cd ./my-repo
412
+ git init
413
+ ```
414
+
415
+ #### Error: "appears to be a Git worktree"
416
+
417
+ **Cause**: The path contains a `.git` file instead of a directory (Git worktree).
418
+
419
+ **Solution**: Use the main repository path instead of the worktree path:
420
+ ```json
421
+ {
422
+ "name": "my-repo",
423
+ "path": "./main-repo" // Use main repo, not worktree
424
+ }
425
+ ```
426
+
427
+ #### Error: "Duplicate repository name"
428
+
429
+ **Cause**: Two repositories have the same name.
430
+
431
+ **Solution**: Ensure each repository has a unique name:
432
+ ```json
433
+ {
434
+ "repositories": [
435
+ { "name": "repo1", "path": "./path1" },
436
+ { "name": "repo2", "path": "./path2" } // Changed from "repo1"
437
+ ]
438
+ }
439
+ ```
440
+
441
+ #### Error: "Configuration file contains invalid JSON"
442
+
443
+ **Cause**: The JSON syntax is incorrect (missing comma, bracket, etc.).
444
+
445
+ **Solution**: Validate your JSON using a JSON validator or IDE:
446
+ - Check for missing commas between array elements
447
+ - Ensure all brackets and braces are properly closed
448
+ - Use a JSON formatter to identify syntax errors
449
+
196
450
  ## Nested Repository Support
197
451
 
198
452
  ### Overview
@@ -46,10 +46,13 @@ class ConfigManager {
46
46
 
47
47
  /**
48
48
  * Load and validate configuration from disk
49
+ * @param {Object} options - Load options
50
+ * @param {boolean} options.skipFilesystemValidation - Skip filesystem validation (for testing)
49
51
  * @returns {Promise<Object>} The loaded and validated configuration
50
52
  * @throws {ConfigError} If file is missing, invalid JSON, or validation fails
51
53
  */
52
- async loadConfig() {
54
+ async loadConfig(options = {}) {
55
+ const { skipFilesystemValidation = false } = options;
53
56
  const configPath = this.getConfigPath();
54
57
 
55
58
  // Check if file exists
@@ -78,8 +81,8 @@ class ConfigManager {
78
81
  );
79
82
  }
80
83
 
81
- // Validate configuration
82
- const validation = this.validateConfig(configData);
84
+ // Validate configuration structure
85
+ const validation = this.validateConfig(configData, { validateFilesystem: false });
83
86
  if (!validation.valid) {
84
87
  throw new ConfigError(
85
88
  'Configuration validation failed',
@@ -87,6 +90,24 @@ class ConfigManager {
87
90
  );
88
91
  }
89
92
 
93
+ // Perform filesystem validation for each repository (unless skipped)
94
+ if (!skipFilesystemValidation) {
95
+ const filesystemErrors = [];
96
+ for (const repo of configData.repositories) {
97
+ if (repo.path && repo.name) {
98
+ const pathErrors = await this._validateRepositoryPath(repo.path, repo.name);
99
+ filesystemErrors.push(...pathErrors);
100
+ }
101
+ }
102
+
103
+ if (filesystemErrors.length > 0) {
104
+ throw new ConfigError(
105
+ 'Repository path validation failed',
106
+ { errors: filesystemErrors }
107
+ );
108
+ }
109
+ }
110
+
90
111
  return configData;
91
112
  }
92
113
 
@@ -127,9 +148,11 @@ class ConfigManager {
127
148
  /**
128
149
  * Validate configuration structure and content
129
150
  * @param {Object} config - The configuration object to validate
151
+ * @param {Object} options - Validation options
152
+ * @param {boolean} options.validateFilesystem - Whether to validate paths on filesystem (default: false)
130
153
  * @returns {{valid: boolean, errors: string[]}} Validation result
131
154
  */
132
- validateConfig(config) {
155
+ validateConfig(config, options = {}) {
133
156
  const errors = [];
134
157
 
135
158
  // Check if config is an object
@@ -137,14 +160,13 @@ class ConfigManager {
137
160
  return { valid: false, errors: ['Configuration must be an object'] };
138
161
  }
139
162
 
140
- // Validate version field
141
- if (!config.version) {
142
- errors.push('Missing required field: version');
143
- } else if (typeof config.version !== 'string') {
163
+ // Validate version field (optional, defaults to '1.0')
164
+ const version = config.version || '1.0';
165
+ if (typeof version !== 'string') {
144
166
  errors.push('Field "version" must be a string');
145
- } else if (!this._isSupportedVersion(config.version)) {
167
+ } else if (!this._isSupportedVersion(version)) {
146
168
  errors.push(
147
- `Unsupported configuration version: ${config.version}. ` +
169
+ `Unsupported configuration version: ${version}. ` +
148
170
  'Please upgrade to the latest version of kse.'
149
171
  );
150
172
  }
@@ -165,7 +187,7 @@ class ConfigManager {
165
187
  const repoPaths = [];
166
188
 
167
189
  config.repositories.forEach((repo, index) => {
168
- const repoErrors = this._validateRepository(repo, index, config.repositories);
190
+ const repoErrors = this._validateRepository(repo, index, config.repositories, options.validateFilesystem);
169
191
  errors.push(...repoErrors);
170
192
 
171
193
  // Collect names and paths for duplicate checking
@@ -454,6 +476,94 @@ class ConfigManager {
454
476
  return validPattern.test(name);
455
477
  }
456
478
 
479
+ /**
480
+ * Check if a path is a valid Git repository
481
+ * @private
482
+ * @param {string} dirPath - Directory path to check
483
+ * @returns {Promise<boolean>} True if path contains a .git directory (not file)
484
+ */
485
+ async _isGitRepository(dirPath) {
486
+ try {
487
+ const gitPath = path.join(dirPath, '.git');
488
+ const stats = await fs.stat(gitPath);
489
+ // Return true only if .git is a directory (not a file, which indicates a Git worktree)
490
+ return stats.isDirectory();
491
+ } catch (error) {
492
+ // Path doesn't exist, no permissions, or other error
493
+ return false;
494
+ }
495
+ }
496
+
497
+ /**
498
+ * Validate that a repository path exists and is a valid Git repository
499
+ * @private
500
+ * @param {string} repoPath - Repository path to validate
501
+ * @param {string} repoName - Repository name (for error messages)
502
+ * @returns {Promise<string[]>} Array of validation errors
503
+ */
504
+ async _validateRepositoryPath(repoPath, repoName) {
505
+ const errors = [];
506
+
507
+ // Resolve path relative to project root
508
+ const absolutePath = path.isAbsolute(repoPath)
509
+ ? repoPath
510
+ : path.join(this.projectRoot, repoPath);
511
+
512
+ // Check if path exists
513
+ try {
514
+ await fs.access(absolutePath);
515
+ } catch (error) {
516
+ errors.push(
517
+ `Repository "${repoName}": path "${repoPath}" does not exist. ` +
518
+ 'Please check the path is correct.'
519
+ );
520
+ return errors; // Can't continue validation if path doesn't exist
521
+ }
522
+
523
+ // Check if it's a directory
524
+ try {
525
+ const stats = await fs.stat(absolutePath);
526
+ if (!stats.isDirectory()) {
527
+ errors.push(
528
+ `Repository "${repoName}": path "${repoPath}" is not a directory.`
529
+ );
530
+ return errors;
531
+ }
532
+ } catch (error) {
533
+ errors.push(
534
+ `Repository "${repoName}": cannot access path "${repoPath}". ${error.message}`
535
+ );
536
+ return errors;
537
+ }
538
+
539
+ // Check if .git exists
540
+ const gitPath = path.join(absolutePath, '.git');
541
+ try {
542
+ const gitStats = await fs.stat(gitPath);
543
+
544
+ if (gitStats.isFile()) {
545
+ // .git is a file, which indicates a Git worktree
546
+ errors.push(
547
+ `Repository "${repoName}": path "${repoPath}" appears to be a Git worktree (not supported). ` +
548
+ 'Please use the main repository path instead.'
549
+ );
550
+ } else if (!gitStats.isDirectory()) {
551
+ errors.push(
552
+ `Repository "${repoName}": path "${repoPath}" has an invalid .git entry.`
553
+ );
554
+ }
555
+ // If .git is a directory, it's valid - no error
556
+ } catch (error) {
557
+ // .git doesn't exist
558
+ errors.push(
559
+ `Repository "${repoName}": path "${repoPath}" is not a Git repository (no .git directory found). ` +
560
+ 'Please ensure this is a Git repository.'
561
+ );
562
+ }
563
+
564
+ return errors;
565
+ }
566
+
457
567
  /**
458
568
  * Check if configuration version is supported
459
569
  * @private
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kiro-spec-engine",
3
- "version": "1.20.5",
3
+ "version": "1.21.1",
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": {