scene-capability-engine 3.0.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.
Files changed (336) hide show
  1. package/CHANGELOG.md +2513 -0
  2. package/LICENSE +21 -0
  3. package/README.md +765 -0
  4. package/README.zh.md +630 -0
  5. package/bin/kiro-spec-engine.js +796 -0
  6. package/bin/kse.js +3 -0
  7. package/bin/sce.js +3 -0
  8. package/bin/sco.js +3 -0
  9. package/docs/331-poc-adaptation-roadmap.md +156 -0
  10. package/docs/331-poc-dual-track-integration-guide.md +120 -0
  11. package/docs/331-poc-weekly-delivery-checklist.md +52 -0
  12. package/docs/OFFLINE_INSTALL.md +96 -0
  13. package/docs/README.md +279 -0
  14. package/docs/adopt-migration-guide.md +599 -0
  15. package/docs/adoption-guide.md +616 -0
  16. package/docs/agent-hooks-analysis.md +815 -0
  17. package/docs/architecture.md +733 -0
  18. package/docs/articles/ai-driven-development-philosophy-and-practice-review.md +208 -0
  19. package/docs/articles/ai-driven-development-philosophy-and-practice.en.md +459 -0
  20. package/docs/articles/ai-driven-development-philosophy-and-practice.md +492 -0
  21. package/docs/autonomous-control-guide.md +851 -0
  22. package/docs/command-reference.md +1368 -0
  23. package/docs/community.md +115 -0
  24. package/docs/cross-tool-guide.md +555 -0
  25. package/docs/developer-guide.md +619 -0
  26. package/docs/document-governance.md +865 -0
  27. package/docs/environment-management-guide.md +526 -0
  28. package/docs/examples/add-export-command/design.md +194 -0
  29. package/docs/examples/add-export-command/requirements.md +110 -0
  30. package/docs/examples/add-export-command/tasks.md +88 -0
  31. package/docs/examples/add-rest-api/design.md +855 -0
  32. package/docs/examples/add-rest-api/requirements.md +323 -0
  33. package/docs/examples/add-rest-api/tasks.md +355 -0
  34. package/docs/examples/add-user-dashboard/design.md +192 -0
  35. package/docs/examples/add-user-dashboard/requirements.md +143 -0
  36. package/docs/examples/add-user-dashboard/tasks.md +91 -0
  37. package/docs/faq.md +697 -0
  38. package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.json +156 -0
  39. package/docs/handoffs/evidence/ontology/moqui-template-baseline-2026-02-17-232922.md +24 -0
  40. package/docs/images/wechat-qr.png +0 -0
  41. package/docs/integration-modes.md +529 -0
  42. package/docs/integration-philosophy.md +313 -0
  43. package/docs/knowledge-management-guide.md +263 -0
  44. package/docs/manual-workflows-guide.md +418 -0
  45. package/docs/moqui-capability-matrix.md +73 -0
  46. package/docs/moqui-template-core-library-playbook.md +109 -0
  47. package/docs/multi-agent-coordination-guide.md +553 -0
  48. package/docs/multi-repo-management-guide.md +1344 -0
  49. package/docs/quick-start-with-ai-tools.md +375 -0
  50. package/docs/quick-start.md +146 -0
  51. package/docs/release-checklist.md +121 -0
  52. package/docs/releases/README.md +13 -0
  53. package/docs/releases/v1.46.2-validation.md +45 -0
  54. package/docs/releases/v1.46.2.md +50 -0
  55. package/docs/scene-runtime-guide.md +347 -0
  56. package/docs/spec-collaboration-guide.md +369 -0
  57. package/docs/spec-locking-guide.md +225 -0
  58. package/docs/spec-numbering-guide.md +348 -0
  59. package/docs/spec-workflow.md +519 -0
  60. package/docs/steering-strategy-guide.md +196 -0
  61. package/docs/team-collaboration-guide.md +465 -0
  62. package/docs/testing-strategy.md +272 -0
  63. package/docs/tools/claude-guide.md +654 -0
  64. package/docs/tools/cursor-guide.md +706 -0
  65. package/docs/tools/generic-guide.md +446 -0
  66. package/docs/tools/kiro-guide.md +308 -0
  67. package/docs/tools/vscode-guide.md +445 -0
  68. package/docs/tools/windsurf-guide.md +391 -0
  69. package/docs/troubleshooting.md +1135 -0
  70. package/docs/upgrade-guide.md +639 -0
  71. package/docs/value-observability-guide.md +127 -0
  72. package/docs/zh/README.md +341 -0
  73. package/docs/zh/quick-start.md +764 -0
  74. package/docs/zh/release-checklist.md +121 -0
  75. package/docs/zh/releases/README.md +13 -0
  76. package/docs/zh/releases/v1.46.2-validation.md +45 -0
  77. package/docs/zh/releases/v1.46.2.md +50 -0
  78. package/docs/zh/spec-numbering-guide.md +348 -0
  79. package/docs/zh/tools/claude-guide.md +349 -0
  80. package/docs/zh/tools/cursor-guide.md +281 -0
  81. package/docs/zh/tools/generic-guide.md +499 -0
  82. package/docs/zh/tools/kiro-guide.md +342 -0
  83. package/docs/zh/tools/vscode-guide.md +449 -0
  84. package/docs/zh/tools/windsurf-guide.md +378 -0
  85. package/docs/zh/value-observability-guide.md +127 -0
  86. package/docs//344/272/244/344/273/230/346/270/205/345/215/225.md +75 -0
  87. package/lib/adoption/adoption-logger.js +487 -0
  88. package/lib/adoption/adoption-strategy.js +538 -0
  89. package/lib/adoption/backup-manager.js +420 -0
  90. package/lib/adoption/conflict-resolver.js +410 -0
  91. package/lib/adoption/detection-engine.js +275 -0
  92. package/lib/adoption/diff-viewer.js +226 -0
  93. package/lib/adoption/error-formatter.js +509 -0
  94. package/lib/adoption/file-classifier.js +385 -0
  95. package/lib/adoption/progress-reporter.js +534 -0
  96. package/lib/adoption/smart-orchestrator.js +470 -0
  97. package/lib/adoption/strategy-selector.js +218 -0
  98. package/lib/adoption/summary-generator.js +493 -0
  99. package/lib/adoption/template-sync.js +605 -0
  100. package/lib/auto/autonomous-engine.js +485 -0
  101. package/lib/auto/checkpoint-manager.js +300 -0
  102. package/lib/auto/close-loop-runner.js +2476 -0
  103. package/lib/auto/config-schema.js +176 -0
  104. package/lib/auto/decision-engine.js +344 -0
  105. package/lib/auto/error-recovery-manager.js +580 -0
  106. package/lib/auto/goal-decomposer.js +278 -0
  107. package/lib/auto/progress-tracker.js +502 -0
  108. package/lib/auto/safety-manager.js +186 -0
  109. package/lib/auto/semantic-decomposer.js +137 -0
  110. package/lib/auto/state-manager.js +126 -0
  111. package/lib/auto/task-queue-manager.js +340 -0
  112. package/lib/backup/backup-system.js +372 -0
  113. package/lib/backup/selective-backup.js +207 -0
  114. package/lib/collab/agent-registry.js +240 -0
  115. package/lib/collab/collab-manager.js +285 -0
  116. package/lib/collab/contract-manager.js +320 -0
  117. package/lib/collab/coordinator.js +370 -0
  118. package/lib/collab/dependency-manager.js +280 -0
  119. package/lib/collab/index.js +20 -0
  120. package/lib/collab/integration-manager.js +202 -0
  121. package/lib/collab/merge-coordinator.js +252 -0
  122. package/lib/collab/metadata-manager.js +233 -0
  123. package/lib/collab/multi-agent-config.js +120 -0
  124. package/lib/collab/spec-lifecycle-manager.js +304 -0
  125. package/lib/collab/sync-barrier.js +88 -0
  126. package/lib/collab/visualizer.js +208 -0
  127. package/lib/commands/adopt.js +749 -0
  128. package/lib/commands/auto.js +19559 -0
  129. package/lib/commands/collab.js +275 -0
  130. package/lib/commands/context.js +99 -0
  131. package/lib/commands/docs.js +808 -0
  132. package/lib/commands/doctor.js +273 -0
  133. package/lib/commands/env.js +420 -0
  134. package/lib/commands/knowledge.js +309 -0
  135. package/lib/commands/lock.js +235 -0
  136. package/lib/commands/ops.js +409 -0
  137. package/lib/commands/orchestrate.js +446 -0
  138. package/lib/commands/prompt.js +105 -0
  139. package/lib/commands/repo.js +118 -0
  140. package/lib/commands/rollback.js +219 -0
  141. package/lib/commands/scene.js +15549 -0
  142. package/lib/commands/spec-bootstrap.js +147 -0
  143. package/lib/commands/spec-gate.js +157 -0
  144. package/lib/commands/spec-pipeline.js +205 -0
  145. package/lib/commands/status.js +321 -0
  146. package/lib/commands/task.js +199 -0
  147. package/lib/commands/templates.js +654 -0
  148. package/lib/commands/upgrade.js +231 -0
  149. package/lib/commands/value.js +569 -0
  150. package/lib/commands/watch.js +684 -0
  151. package/lib/commands/workflows.js +240 -0
  152. package/lib/commands/workspace-multi.js +325 -0
  153. package/lib/commands/workspace.js +189 -0
  154. package/lib/context/context-exporter.js +378 -0
  155. package/lib/context/prompt-generator.js +482 -0
  156. package/lib/data/moqui-capability-lexicon.json +45 -0
  157. package/lib/environment/backup-system.js +189 -0
  158. package/lib/environment/environment-manager.js +379 -0
  159. package/lib/environment/environment-registry.js +168 -0
  160. package/lib/gitignore/gitignore-backup.js +229 -0
  161. package/lib/gitignore/gitignore-detector.js +239 -0
  162. package/lib/gitignore/gitignore-integration.js +267 -0
  163. package/lib/gitignore/gitignore-transformer.js +193 -0
  164. package/lib/gitignore/layered-rules-template.js +42 -0
  165. package/lib/governance/archive-tool.js +284 -0
  166. package/lib/governance/cleanup-tool.js +237 -0
  167. package/lib/governance/config-manager.js +186 -0
  168. package/lib/governance/diagnostic-engine.js +271 -0
  169. package/lib/governance/doc-reference-checker.js +200 -0
  170. package/lib/governance/execution-logger.js +243 -0
  171. package/lib/governance/file-scanner.js +285 -0
  172. package/lib/governance/hooks-manager.js +333 -0
  173. package/lib/governance/reporter.js +337 -0
  174. package/lib/governance/validation-engine.js +181 -0
  175. package/lib/i18n.js +79 -0
  176. package/lib/knowledge/entry-manager.js +208 -0
  177. package/lib/knowledge/index-manager.js +261 -0
  178. package/lib/knowledge/knowledge-manager.js +273 -0
  179. package/lib/knowledge/template-manager.js +191 -0
  180. package/lib/lock/index.js +21 -0
  181. package/lib/lock/lock-file.js +192 -0
  182. package/lib/lock/lock-manager.js +321 -0
  183. package/lib/lock/machine-identifier.js +135 -0
  184. package/lib/lock/steering-file-lock.js +207 -0
  185. package/lib/lock/task-lock-manager.js +345 -0
  186. package/lib/operations/audit-logger.js +293 -0
  187. package/lib/operations/feedback-manager.js +1147 -0
  188. package/lib/operations/index.js +23 -0
  189. package/lib/operations/models/index.js +170 -0
  190. package/lib/operations/operations-manager.js +151 -0
  191. package/lib/operations/operations-validator.js +280 -0
  192. package/lib/operations/permission-manager.js +354 -0
  193. package/lib/operations/template-loader.js +143 -0
  194. package/lib/orchestrator/agent-spawner.js +629 -0
  195. package/lib/orchestrator/bootstrap-prompt-builder.js +236 -0
  196. package/lib/orchestrator/index.js +19 -0
  197. package/lib/orchestrator/orchestration-engine.js +1270 -0
  198. package/lib/orchestrator/orchestrator-config.js +173 -0
  199. package/lib/orchestrator/status-monitor.js +591 -0
  200. package/lib/python-checker.js +209 -0
  201. package/lib/repo/config-manager.js +580 -0
  202. package/lib/repo/errors/config-error.js +13 -0
  203. package/lib/repo/errors/git-error.js +15 -0
  204. package/lib/repo/errors/repo-error.js +14 -0
  205. package/lib/repo/git-operations.js +181 -0
  206. package/lib/repo/handlers/.gitkeep +1 -0
  207. package/lib/repo/handlers/exec-handler.js +155 -0
  208. package/lib/repo/handlers/health-handler.js +169 -0
  209. package/lib/repo/handlers/init-handler.js +197 -0
  210. package/lib/repo/handlers/status-handler.js +176 -0
  211. package/lib/repo/output-formatter.js +184 -0
  212. package/lib/repo/path-resolver.js +178 -0
  213. package/lib/repo/repo-manager.js +514 -0
  214. package/lib/scene-runtime/audit-emitter.js +59 -0
  215. package/lib/scene-runtime/binding-plugin-loader.js +351 -0
  216. package/lib/scene-runtime/binding-registry.js +349 -0
  217. package/lib/scene-runtime/eval-bridge.js +44 -0
  218. package/lib/scene-runtime/index.js +19 -0
  219. package/lib/scene-runtime/moqui-adapter.js +620 -0
  220. package/lib/scene-runtime/moqui-client.js +606 -0
  221. package/lib/scene-runtime/moqui-extractor.js +2029 -0
  222. package/lib/scene-runtime/plan-compiler.js +208 -0
  223. package/lib/scene-runtime/policy-gate.js +58 -0
  224. package/lib/scene-runtime/runtime-executor.js +358 -0
  225. package/lib/scene-runtime/scene-loader.js +96 -0
  226. package/lib/scene-runtime/scene-ontology.js +959 -0
  227. package/lib/scene-runtime/scene-template-linter.js +852 -0
  228. package/lib/scene-runtime/templates/scene-template-erp-query-v0.1.yaml +28 -0
  229. package/lib/scene-runtime/templates/scene-template-hybrid-shadow-v0.1.yaml +34 -0
  230. package/lib/spec/bootstrap/context-collector.js +48 -0
  231. package/lib/spec/bootstrap/draft-generator.js +158 -0
  232. package/lib/spec/bootstrap/questionnaire-engine.js +70 -0
  233. package/lib/spec/bootstrap/trace-emitter.js +59 -0
  234. package/lib/spec/multi-spec-orchestrate.js +93 -0
  235. package/lib/spec/pipeline/constants.js +6 -0
  236. package/lib/spec/pipeline/stage-adapters.js +118 -0
  237. package/lib/spec/pipeline/stage-runner.js +146 -0
  238. package/lib/spec/pipeline/state-store.js +119 -0
  239. package/lib/spec-gate/engine/gate-engine.js +165 -0
  240. package/lib/spec-gate/policy/default-policy.js +22 -0
  241. package/lib/spec-gate/policy/policy-loader.js +103 -0
  242. package/lib/spec-gate/result-emitter.js +81 -0
  243. package/lib/spec-gate/rules/default-rules.js +156 -0
  244. package/lib/spec-gate/rules/rule-registry.js +51 -0
  245. package/lib/steering/adoption-config.js +164 -0
  246. package/lib/steering/compliance-auto-fixer.js +204 -0
  247. package/lib/steering/compliance-cache.js +99 -0
  248. package/lib/steering/compliance-error-reporter.js +70 -0
  249. package/lib/steering/context-sync-manager.js +273 -0
  250. package/lib/steering/index.js +92 -0
  251. package/lib/steering/spec-steering.js +230 -0
  252. package/lib/steering/steering-compliance-checker.js +73 -0
  253. package/lib/steering/steering-loader.js +144 -0
  254. package/lib/steering/steering-manager.js +289 -0
  255. package/lib/task/index.js +12 -0
  256. package/lib/task/task-claimer.js +489 -0
  257. package/lib/task/task-status-store.js +418 -0
  258. package/lib/templates/cache-manager.js +440 -0
  259. package/lib/templates/content-generalizer.js +247 -0
  260. package/lib/templates/frontmatter-generator.js +128 -0
  261. package/lib/templates/git-handler.js +471 -0
  262. package/lib/templates/metadata-collector.js +328 -0
  263. package/lib/templates/path-utils.js +144 -0
  264. package/lib/templates/registry-parser.js +505 -0
  265. package/lib/templates/spec-reader.js +216 -0
  266. package/lib/templates/template-applicator.js +249 -0
  267. package/lib/templates/template-creator.js +256 -0
  268. package/lib/templates/template-error.js +143 -0
  269. package/lib/templates/template-exporter.js +502 -0
  270. package/lib/templates/template-manager.js +782 -0
  271. package/lib/templates/template-validator.js +361 -0
  272. package/lib/upgrade/migration-engine.js +382 -0
  273. package/lib/upgrade/migrations/.gitkeep +52 -0
  274. package/lib/upgrade/migrations/1.0.0-to-1.1.0.js +78 -0
  275. package/lib/utils/file-diff.js +177 -0
  276. package/lib/utils/fs-utils.js +274 -0
  277. package/lib/utils/tool-detector.js +383 -0
  278. package/lib/utils/validation.js +324 -0
  279. package/lib/value/gate-summary-emitter.js +99 -0
  280. package/lib/value/metric-contract-loader.js +210 -0
  281. package/lib/value/risk-evaluator.js +117 -0
  282. package/lib/value/weekly-snapshot-builder.js +61 -0
  283. package/lib/version/version-checker.js +156 -0
  284. package/lib/version/version-manager.js +327 -0
  285. package/lib/watch/action-executor.js +458 -0
  286. package/lib/watch/event-debouncer.js +323 -0
  287. package/lib/watch/execution-logger.js +550 -0
  288. package/lib/watch/file-watcher.js +499 -0
  289. package/lib/watch/presets.js +266 -0
  290. package/lib/watch/watch-manager.js +533 -0
  291. package/lib/workspace/multi/global-config.js +150 -0
  292. package/lib/workspace/multi/index.js +22 -0
  293. package/lib/workspace/multi/path-utils.js +173 -0
  294. package/lib/workspace/multi/workspace-context-resolver.js +244 -0
  295. package/lib/workspace/multi/workspace-registry.js +196 -0
  296. package/lib/workspace/multi/workspace-state-manager.js +537 -0
  297. package/lib/workspace/multi/workspace.js +90 -0
  298. package/lib/workspace/workspace-manager.js +370 -0
  299. package/lib/workspace/workspace-sync.js +356 -0
  300. package/locales/en.json +114 -0
  301. package/locales/zh.json +114 -0
  302. package/package.json +102 -0
  303. package/template/.kiro/README.md +247 -0
  304. package/template/.kiro/hooks/check-spec-on-create.kiro.hook +17 -0
  305. package/template/.kiro/hooks/run-tests-on-save.kiro.hook +13 -0
  306. package/template/.kiro/hooks/sync-tasks-on-edit.kiro.hook +16 -0
  307. package/template/.kiro/specs/SPEC_WORKFLOW_GUIDE.md +134 -0
  308. package/template/.kiro/steering/CORE_PRINCIPLES.md +133 -0
  309. package/template/.kiro/steering/CURRENT_CONTEXT.md +30 -0
  310. package/template/.kiro/steering/ENVIRONMENT.md +35 -0
  311. package/template/.kiro/steering/RULES_GUIDE.md +46 -0
  312. package/template/.kiro/templates/operations/default/change-impact.md +112 -0
  313. package/template/.kiro/templates/operations/default/deployment.md +91 -0
  314. package/template/.kiro/templates/operations/default/feedback-response.md +269 -0
  315. package/template/.kiro/templates/operations/default/migration-plan.md +172 -0
  316. package/template/.kiro/templates/operations/default/monitoring.md +135 -0
  317. package/template/.kiro/templates/operations/default/operations.md +135 -0
  318. package/template/.kiro/templates/operations/default/rollback.md +143 -0
  319. package/template/.kiro/templates/operations/default/tools.yaml +364 -0
  320. package/template/.kiro/templates/operations/default/troubleshooting.md +123 -0
  321. package/template/.kiro/tools/backup_manager.py +295 -0
  322. package/template/.kiro/tools/configuration_manager.py +218 -0
  323. package/template/.kiro/tools/document_evaluator.py +550 -0
  324. package/template/.kiro/tools/enhancement_logger.py +168 -0
  325. package/template/.kiro/tools/error_handler.py +335 -0
  326. package/template/.kiro/tools/improvement_identifier.py +444 -0
  327. package/template/.kiro/tools/modification_applicator.py +737 -0
  328. package/template/.kiro/tools/quality_gate_enforcer.py +207 -0
  329. package/template/.kiro/tools/quality_scorer.py +305 -0
  330. package/template/.kiro/tools/report_generator.py +154 -0
  331. package/template/.kiro/tools/ultrawork_enhancer.py +676 -0
  332. package/template/.kiro/tools/ultrawork_enhancer_refactored.py +0 -0
  333. package/template/.kiro/tools/ultrawork_enhancer_v2.py +463 -0
  334. package/template/.kiro/tools/ultrawork_enhancer_v3.py +606 -0
  335. package/template/.kiro/tools/workflow_quality_gate.py +100 -0
  336. package/template/README.md +111 -0
@@ -0,0 +1,580 @@
1
+ const fs = require('fs').promises;
2
+ const path = require('path');
3
+ const ConfigError = require('./errors/config-error');
4
+ const PathResolver = require('./path-resolver');
5
+
6
+ /**
7
+ * ConfigManager - Manages the project-repos.json configuration file
8
+ *
9
+ * Handles loading, saving, and validating repository configuration with
10
+ * JSON schema validation and comprehensive error checking.
11
+ */
12
+ class ConfigManager {
13
+ /**
14
+ * Create a new ConfigManager
15
+ * @param {string} projectRoot - The project root directory
16
+ */
17
+ constructor(projectRoot) {
18
+ if (!projectRoot) {
19
+ throw new Error('Project root is required');
20
+ }
21
+ this.projectRoot = projectRoot;
22
+ this.pathResolver = new PathResolver();
23
+ this.configFileName = 'project-repos.json';
24
+ }
25
+
26
+ /**
27
+ * Get the configuration file path
28
+ * @returns {string} Absolute path to the configuration file
29
+ */
30
+ getConfigPath() {
31
+ return path.join(this.projectRoot, '.kiro', this.configFileName);
32
+ }
33
+
34
+ /**
35
+ * Check if configuration file exists
36
+ * @returns {Promise<boolean>} True if configuration file exists
37
+ */
38
+ async configExists() {
39
+ try {
40
+ await fs.access(this.getConfigPath());
41
+ return true;
42
+ } catch (error) {
43
+ return false;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Load and validate configuration from disk
49
+ * @param {Object} options - Load options
50
+ * @param {boolean} options.skipFilesystemValidation - Skip filesystem validation (for testing)
51
+ * @returns {Promise<Object>} The loaded and validated configuration
52
+ * @throws {ConfigError} If file is missing, invalid JSON, or validation fails
53
+ */
54
+ async loadConfig(options = {}) {
55
+ const { skipFilesystemValidation = false } = options;
56
+ const configPath = this.getConfigPath();
57
+
58
+ // Check if file exists
59
+ if (!(await this.configExists())) {
60
+ throw new ConfigError(
61
+ 'Configuration file not found. Run "kse repo init" to create it.',
62
+ { path: configPath }
63
+ );
64
+ }
65
+
66
+ // Read and parse JSON
67
+ let configData;
68
+ try {
69
+ const fileContent = await fs.readFile(configPath, 'utf8');
70
+ configData = JSON.parse(fileContent);
71
+ } catch (error) {
72
+ if (error instanceof SyntaxError) {
73
+ throw new ConfigError(
74
+ 'Configuration file contains invalid JSON',
75
+ { path: configPath, parseError: error.message }
76
+ );
77
+ }
78
+ throw new ConfigError(
79
+ `Failed to read configuration file: ${error.message}`,
80
+ { path: configPath }
81
+ );
82
+ }
83
+
84
+ // Validate configuration structure
85
+ const validation = this.validateConfig(configData, { validateFilesystem: false });
86
+ if (!validation.valid) {
87
+ throw new ConfigError(
88
+ 'Configuration validation failed',
89
+ { errors: validation.errors }
90
+ );
91
+ }
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
+
111
+ return configData;
112
+ }
113
+
114
+ /**
115
+ * Save configuration to disk
116
+ * @param {Object} config - The configuration object to save
117
+ * @returns {Promise<void>}
118
+ * @throws {ConfigError} If validation fails or save fails
119
+ */
120
+ async saveConfig(config) {
121
+ // Validate before saving
122
+ const validation = this.validateConfig(config);
123
+ if (!validation.valid) {
124
+ throw new ConfigError(
125
+ 'Cannot save invalid configuration',
126
+ { errors: validation.errors }
127
+ );
128
+ }
129
+
130
+ const configPath = this.getConfigPath();
131
+
132
+ try {
133
+ // Ensure .kiro directory exists
134
+ const kiroDir = path.dirname(configPath);
135
+ await fs.mkdir(kiroDir, { recursive: true });
136
+
137
+ // Write configuration with pretty formatting
138
+ const jsonContent = JSON.stringify(config, null, 2);
139
+ await fs.writeFile(configPath, jsonContent, 'utf8');
140
+ } catch (error) {
141
+ throw new ConfigError(
142
+ `Failed to save configuration: ${error.message}`,
143
+ { path: configPath }
144
+ );
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Validate configuration structure and content
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)
153
+ * @returns {{valid: boolean, errors: string[]}} Validation result
154
+ */
155
+ validateConfig(config, options = {}) {
156
+ const errors = [];
157
+
158
+ // Check if config is an object
159
+ if (!config || typeof config !== 'object') {
160
+ return { valid: false, errors: ['Configuration must be an object'] };
161
+ }
162
+
163
+ // Validate version field (optional, defaults to '1.0')
164
+ const version = config.version || '1.0';
165
+ if (typeof version !== 'string') {
166
+ errors.push('Field "version" must be a string');
167
+ } else if (!this._isSupportedVersion(version)) {
168
+ errors.push(
169
+ `Unsupported configuration version: ${version}. ` +
170
+ 'Please upgrade to the latest version of kse.'
171
+ );
172
+ }
173
+
174
+ // Validate repositories array
175
+ if (!config.repositories) {
176
+ errors.push('Missing required field: repositories');
177
+ return { valid: false, errors }; // Can't continue without repositories
178
+ }
179
+
180
+ if (!Array.isArray(config.repositories)) {
181
+ errors.push('Field "repositories" must be an array');
182
+ return { valid: false, errors };
183
+ }
184
+
185
+ // Validate each repository
186
+ const repoNames = new Set();
187
+ const repoPaths = [];
188
+
189
+ config.repositories.forEach((repo, index) => {
190
+ const repoErrors = this._validateRepository(repo, index, config.repositories, options.validateFilesystem);
191
+ errors.push(...repoErrors);
192
+
193
+ // Collect names and paths for duplicate checking
194
+ if (repo.name) {
195
+ if (repoNames.has(repo.name)) {
196
+ errors.push(`Duplicate repository name: "${repo.name}"`);
197
+ }
198
+ repoNames.add(repo.name);
199
+ }
200
+
201
+ if (repo.path) {
202
+ repoPaths.push(repo.path);
203
+ }
204
+ });
205
+
206
+ // Validate parent references
207
+ const parentErrors = this._validateParentReferences(config.repositories);
208
+ errors.push(...parentErrors);
209
+
210
+ // Validate no duplicate or overlapping paths
211
+ if (repoPaths.length > 0) {
212
+ // Check if nested mode is enabled in settings
213
+ const nestedMode = config.settings && config.settings.nestedMode === true;
214
+ const pathValidation = this._validatePaths(repoPaths, nestedMode);
215
+ errors.push(...pathValidation.errors);
216
+ }
217
+
218
+ // Validate optional groups field
219
+ if (config.groups && typeof config.groups !== 'object') {
220
+ errors.push('Field "groups" must be an object');
221
+ }
222
+
223
+ // Validate optional settings field
224
+ if (config.settings && typeof config.settings !== 'object') {
225
+ errors.push('Field "settings" must be an object');
226
+ }
227
+
228
+ return {
229
+ valid: errors.length === 0,
230
+ errors
231
+ };
232
+ }
233
+
234
+ /**
235
+ * Validate a single repository configuration
236
+ * @private
237
+ * @param {Object} repo - Repository configuration object
238
+ * @param {number} index - Repository index in array
239
+ * @param {Array<Object>} allRepos - All repositories (for parent validation)
240
+ * @returns {string[]} Array of validation errors
241
+ */
242
+ _validateRepository(repo, index, allRepos = []) {
243
+ const errors = [];
244
+ const prefix = `Repository at index ${index}`;
245
+
246
+ // Check if repo is an object
247
+ if (!repo || typeof repo !== 'object') {
248
+ return [`${prefix}: must be an object`];
249
+ }
250
+
251
+ // Validate required fields
252
+ if (!repo.name) {
253
+ errors.push(`${prefix}: missing required field "name"`);
254
+ } else if (typeof repo.name !== 'string') {
255
+ errors.push(`${prefix}: field "name" must be a string`);
256
+ } else if (!this._isValidRepoName(repo.name)) {
257
+ errors.push(
258
+ `${prefix}: invalid repository name "${repo.name}". ` +
259
+ 'Names must contain only alphanumeric characters, hyphens, underscores, and dots.'
260
+ );
261
+ }
262
+
263
+ if (!repo.path) {
264
+ errors.push(`${prefix}: missing required field "path"`);
265
+ } else if (typeof repo.path !== 'string') {
266
+ errors.push(`${prefix}: field "path" must be a string`);
267
+ }
268
+
269
+ // Validate optional fields
270
+ if (repo.remote !== undefined && repo.remote !== null && typeof repo.remote !== 'string') {
271
+ errors.push(`${prefix}: field "remote" must be a string or null`);
272
+ }
273
+
274
+ if (repo.defaultBranch !== undefined && typeof repo.defaultBranch !== 'string') {
275
+ errors.push(`${prefix}: field "defaultBranch" must be a string`);
276
+ }
277
+
278
+ if (repo.description !== undefined && typeof repo.description !== 'string') {
279
+ errors.push(`${prefix}: field "description" must be a string`);
280
+ }
281
+
282
+ if (repo.tags !== undefined) {
283
+ if (!Array.isArray(repo.tags)) {
284
+ errors.push(`${prefix}: field "tags" must be an array`);
285
+ } else if (!repo.tags.every(tag => typeof tag === 'string')) {
286
+ errors.push(`${prefix}: all tags must be strings`);
287
+ }
288
+ }
289
+
290
+ if (repo.group !== undefined && typeof repo.group !== 'string') {
291
+ errors.push(`${prefix}: field "group" must be a string`);
292
+ }
293
+
294
+ // Validate parent field (NEW)
295
+ if (repo.parent !== undefined && repo.parent !== null) {
296
+ if (typeof repo.parent !== 'string') {
297
+ errors.push(`${prefix}: field "parent" must be a string or null`);
298
+ }
299
+ // Note: Parent reference validation is done separately in _validateParentReferences
300
+ }
301
+
302
+ return errors;
303
+ }
304
+
305
+ /**
306
+ * Normalize path for comparison
307
+ * @private
308
+ * @param {string} pathStr - Path to normalize
309
+ * @returns {string} Normalized path
310
+ */
311
+ _normalizePath(pathStr) {
312
+ if (!pathStr) return '';
313
+
314
+ // Convert backslashes to forward slashes
315
+ let normalized = pathStr.replace(/\\/g, '/');
316
+
317
+ // Remove trailing slashes
318
+ normalized = normalized.replace(/\/+$/, '');
319
+
320
+ // Remove leading './'
321
+ normalized = normalized.replace(/^\.\//, '');
322
+
323
+ // Handle '.' as current directory
324
+ if (normalized === '.') {
325
+ normalized = '';
326
+ }
327
+
328
+ return normalized;
329
+ }
330
+
331
+ /**
332
+ * Validate parent references in repositories
333
+ * @private
334
+ * @param {Array<Object>} repos - All repositories
335
+ * @returns {string[]} Array of validation errors
336
+ */
337
+ _validateParentReferences(repos) {
338
+ const errors = [];
339
+
340
+ // Build a map of repository paths for O(1) lookup
341
+ const pathMap = new Map();
342
+ repos.forEach(repo => {
343
+ if (repo.path) {
344
+ const normalizedPath = this._normalizePath(repo.path);
345
+ pathMap.set(normalizedPath, repo);
346
+ }
347
+ });
348
+
349
+ // Check each repository's parent reference
350
+ repos.forEach((repo, index) => {
351
+ if (repo.parent) {
352
+ // Check if parent path exists (using normalized paths)
353
+ const normalizedParent = this._normalizePath(repo.parent);
354
+ if (!pathMap.has(normalizedParent)) {
355
+ errors.push(
356
+ `Repository "${repo.name}" (index ${index}): ` +
357
+ `parent path "${repo.parent}" does not reference an existing repository. ` +
358
+ `Available paths: ${Array.from(pathMap.keys()).join(', ')}`
359
+ );
360
+ }
361
+ }
362
+ });
363
+
364
+ // Detect circular parent references using depth-first search
365
+ const visited = new Set();
366
+ const recursionStack = new Set();
367
+
368
+ const detectCycle = (normalizedPath, path = []) => {
369
+ if (recursionStack.has(normalizedPath)) {
370
+ // Found a cycle
371
+ const cycleStart = path.indexOf(normalizedPath);
372
+ const cycle = path.slice(cycleStart).concat(normalizedPath);
373
+ errors.push(
374
+ `Circular parent reference detected: ${cycle.join(' → ')}`
375
+ );
376
+ return true;
377
+ }
378
+
379
+ if (visited.has(normalizedPath)) {
380
+ return false;
381
+ }
382
+
383
+ visited.add(normalizedPath);
384
+ recursionStack.add(normalizedPath);
385
+ path.push(normalizedPath);
386
+
387
+ const repo = pathMap.get(normalizedPath);
388
+ if (repo && repo.parent) {
389
+ const normalizedParent = this._normalizePath(repo.parent);
390
+ detectCycle(normalizedParent, path);
391
+ }
392
+
393
+ path.pop();
394
+ recursionStack.delete(normalizedPath);
395
+ return false;
396
+ };
397
+
398
+ // Check for cycles starting from each repository (use normalized paths)
399
+ repos.forEach(repo => {
400
+ if (repo.path) {
401
+ const normalizedPath = this._normalizePath(repo.path);
402
+ if (!visited.has(normalizedPath)) {
403
+ detectCycle(normalizedPath, []);
404
+ }
405
+ }
406
+ });
407
+
408
+ return errors;
409
+ }
410
+
411
+ /**
412
+ * Validate repository paths for duplicates and overlaps
413
+ * @private
414
+ * @param {string[]} paths - Array of repository paths
415
+ * @param {boolean} allowNested - Whether to allow nested paths (for nested mode)
416
+ * @returns {{errors: string[]}} Validation result
417
+ */
418
+ _validatePaths(paths, allowNested = false) {
419
+ const errors = [];
420
+
421
+ // Resolve all paths to absolute for comparison
422
+ const resolvedPaths = paths.map(p => {
423
+ try {
424
+ return this.pathResolver.resolvePath(p, this.projectRoot);
425
+ } catch (error) {
426
+ // If path resolution fails, return the original path
427
+ // The error will be caught during actual operations
428
+ return p;
429
+ }
430
+ });
431
+
432
+ // Check for duplicates and overlaps
433
+ const pathValidation = this.pathResolver.validateNoOverlap(resolvedPaths);
434
+
435
+ if (!pathValidation.valid) {
436
+ // Categorize errors into duplicate and nested types
437
+ const duplicateErrors = [];
438
+ const nestedErrors = [];
439
+
440
+ pathValidation.errors.forEach(error => {
441
+ if (error.includes('Duplicate path found')) {
442
+ duplicateErrors.push(error);
443
+ } else if (error.includes('nested within')) {
444
+ nestedErrors.push(error);
445
+ }
446
+ });
447
+
448
+ // Always report duplicate paths (always invalid)
449
+ errors.push(...duplicateErrors);
450
+
451
+ // Report nested paths only if nestedMode is not enabled
452
+ if (nestedErrors.length > 0 && !allowNested) {
453
+ errors.push(...nestedErrors);
454
+ // Add helpful hint about enabling nestedMode
455
+ errors.push(
456
+ 'Hint: Enable nestedMode in settings to allow nested repositories: ' +
457
+ '{ "settings": { "nestedMode": true } }'
458
+ );
459
+ }
460
+ }
461
+
462
+ return { errors };
463
+ }
464
+
465
+ /**
466
+ * Check if repository name is valid
467
+ * @private
468
+ * @param {string} name - Repository name to validate
469
+ * @returns {boolean} True if name is valid
470
+ */
471
+ _isValidRepoName(name) {
472
+ // Allow alphanumeric, hyphens, underscores, dots, and names starting with dots (hidden directories)
473
+ // Must not contain spaces or special characters like @, #, $, etc.
474
+ // Examples: "backend", ".github", ".kiro", "my-repo", "repo_name", "repo.name"
475
+ const validPattern = /^\.?[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
476
+ return validPattern.test(name);
477
+ }
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
+
567
+ /**
568
+ * Check if configuration version is supported
569
+ * @private
570
+ * @param {string} version - Version string to check
571
+ * @returns {boolean} True if version is supported
572
+ */
573
+ _isSupportedVersion(version) {
574
+ // Currently only version 1.0 is supported
575
+ const supportedVersions = ['1.0'];
576
+ return supportedVersions.includes(version);
577
+ }
578
+ }
579
+
580
+ module.exports = ConfigManager;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Error thrown when configuration operations fail
3
+ */
4
+ class ConfigError extends Error {
5
+ constructor(message, details = null) {
6
+ super(message);
7
+ this.name = 'ConfigError';
8
+ this.details = details;
9
+ Error.captureStackTrace(this, this.constructor);
10
+ }
11
+ }
12
+
13
+ module.exports = ConfigError;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Error thrown when Git operations fail
3
+ */
4
+ class GitError extends Error {
5
+ constructor(message, command = null, exitCode = null, details = null) {
6
+ super(message);
7
+ this.name = 'GitError';
8
+ this.command = command;
9
+ this.exitCode = exitCode;
10
+ this.details = details;
11
+ Error.captureStackTrace(this, this.constructor);
12
+ }
13
+ }
14
+
15
+ module.exports = GitError;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Error thrown when repository operations fail
3
+ */
4
+ class RepoError extends Error {
5
+ constructor(message, repoName = null, details = null) {
6
+ super(message);
7
+ this.name = 'RepoError';
8
+ this.repoName = repoName;
9
+ this.details = details;
10
+ Error.captureStackTrace(this, this.constructor);
11
+ }
12
+ }
13
+
14
+ module.exports = RepoError;