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,379 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const EnvironmentRegistry = require('./environment-registry');
4
+ const BackupSystem = require('./backup-system');
5
+
6
+ /**
7
+ * Environment Manager
8
+ *
9
+ * Core logic for environment operations including registration,
10
+ * switching, listing, and managing active environment.
11
+ */
12
+ class EnvironmentManager {
13
+ /**
14
+ * Create a new Environment Manager
15
+ * @param {string} projectRoot - Project root directory
16
+ * @param {string} [workspaceContext] - Optional workspace context
17
+ */
18
+ constructor(projectRoot, workspaceContext = null) {
19
+ this.projectRoot = projectRoot;
20
+ this.workspaceContext = workspaceContext;
21
+ this.registryPath = this.resolveRegistryPath();
22
+ this.backupSystem = new BackupSystem(projectRoot);
23
+ }
24
+
25
+ /**
26
+ * Resolve registry path based on workspace context
27
+ * @returns {string} Path to environments.json
28
+ */
29
+ resolveRegistryPath() {
30
+ if (this.workspaceContext) {
31
+ return path.join(
32
+ this.projectRoot,
33
+ '.kiro',
34
+ 'workspaces',
35
+ this.workspaceContext,
36
+ 'environments.json'
37
+ );
38
+ }
39
+ return path.join(this.projectRoot, '.kiro', 'environments.json');
40
+ }
41
+
42
+ /**
43
+ * Register new environment
44
+ * @param {Object} environmentConfig - Environment configuration
45
+ * @returns {Object} Registration result
46
+ * @throws {Error} If registration fails
47
+ */
48
+ async registerEnvironment(environmentConfig) {
49
+ // Validate environment configuration
50
+ EnvironmentRegistry.validateEnvironment(environmentConfig);
51
+
52
+ // Load existing registry
53
+ const registry = await EnvironmentRegistry.load(this.registryPath);
54
+
55
+ // Check for duplicate name
56
+ const exists = registry.environments.some(
57
+ env => env.name === environmentConfig.name
58
+ );
59
+ if (exists) {
60
+ throw new Error(
61
+ `Environment "${environmentConfig.name}" already exists`
62
+ );
63
+ }
64
+
65
+ // Validate source files exist
66
+ for (const mapping of environmentConfig.config_files) {
67
+ const sourcePath = path.join(this.projectRoot, mapping.source);
68
+ if (!await fs.pathExists(sourcePath)) {
69
+ throw new Error(
70
+ `Source file does not exist: ${mapping.source}`
71
+ );
72
+ }
73
+ }
74
+
75
+ // Add environment to registry
76
+ registry.environments.push(environmentConfig);
77
+
78
+ // Save registry
79
+ await EnvironmentRegistry.save(this.registryPath, registry);
80
+
81
+ return {
82
+ success: true,
83
+ environment: environmentConfig.name,
84
+ message: `Environment "${environmentConfig.name}" registered successfully`
85
+ };
86
+ }
87
+
88
+ /**
89
+ * List all registered environments
90
+ * @returns {Array} Array of environment objects with metadata
91
+ */
92
+ async listEnvironments() {
93
+ const registry = await EnvironmentRegistry.load(this.registryPath);
94
+ return registry.environments.map(env => ({
95
+ name: env.name,
96
+ description: env.description,
97
+ isActive: env.name === registry.active_environment,
98
+ configFilesCount: env.config_files.length,
99
+ hasVerification: !!env.verification
100
+ }));
101
+ }
102
+
103
+ /**
104
+ * Get active environment details
105
+ * @returns {Object} Active environment configuration
106
+ * @throws {Error} If no environment is active
107
+ */
108
+ async getActiveEnvironment() {
109
+ const registry = await EnvironmentRegistry.load(this.registryPath);
110
+
111
+ if (!registry.active_environment) {
112
+ throw new Error('No active environment. Use "kse env switch <name>" to activate one.');
113
+ }
114
+
115
+ const env = registry.environments.find(
116
+ e => e.name === registry.active_environment
117
+ );
118
+
119
+ if (!env) {
120
+ throw new Error(
121
+ `Active environment "${registry.active_environment}" not found in registry`
122
+ );
123
+ }
124
+
125
+ return env;
126
+ }
127
+
128
+ /**
129
+ * Set active environment
130
+ * @param {string} environmentName - Name of environment to activate
131
+ * @throws {Error} If environment doesn't exist
132
+ */
133
+ async setActiveEnvironment(environmentName) {
134
+ const registry = await EnvironmentRegistry.load(this.registryPath);
135
+
136
+ const env = registry.environments.find(e => e.name === environmentName);
137
+ if (!env) {
138
+ throw new Error(`Environment "${environmentName}" not found`);
139
+ }
140
+
141
+ registry.active_environment = environmentName;
142
+ await EnvironmentRegistry.save(this.registryPath, registry);
143
+ }
144
+
145
+ /**
146
+ * Switch to specified environment (basic implementation)
147
+ * @param {string} environmentName - Name of environment to switch to
148
+ * @param {Object} options - Switch options
149
+ * @returns {Object} Switch result with status and details
150
+ * @throws {Error} If switch operation fails
151
+ */
152
+ async switchEnvironment(environmentName, options = {}) {
153
+ try {
154
+ const registry = await EnvironmentRegistry.load(this.registryPath);
155
+
156
+ // Find environment
157
+ const env = registry.environments.find(e => e.name === environmentName);
158
+ if (!env) {
159
+ throw new Error(`Environment "${environmentName}" not found`);
160
+ }
161
+
162
+ const previousEnvironment = registry.active_environment;
163
+ const copiedFiles = [];
164
+ let backupMetadata = null;
165
+
166
+ // Create backup unless skipBackup option is set
167
+ if (!options.skipBackup) {
168
+ const targetFiles = env.config_files.map(m => m.target);
169
+ backupMetadata = await this.backupSystem.createBackup(targetFiles, environmentName);
170
+ }
171
+
172
+ // Copy each config file
173
+ for (const mapping of env.config_files) {
174
+ const sourcePath = path.join(this.projectRoot, mapping.source);
175
+ const targetPath = path.join(this.projectRoot, mapping.target);
176
+
177
+ // Validate source exists
178
+ if (!await fs.pathExists(sourcePath)) {
179
+ throw new Error(`Source file does not exist: ${mapping.source}`);
180
+ }
181
+
182
+ // Create target directory if needed
183
+ const targetDir = path.dirname(targetPath);
184
+ await fs.ensureDir(targetDir);
185
+
186
+ // Copy file
187
+ await fs.copy(sourcePath, targetPath, { overwrite: true });
188
+ copiedFiles.push(mapping.target);
189
+ }
190
+
191
+ // Update active environment
192
+ registry.active_environment = environmentName;
193
+ await EnvironmentRegistry.save(this.registryPath, registry);
194
+
195
+ return {
196
+ success: true,
197
+ previous_environment: previousEnvironment,
198
+ new_environment: environmentName,
199
+ files_copied: copiedFiles.length,
200
+ backup_created: !!backupMetadata,
201
+ backup_location: backupMetadata ? backupMetadata.backup_directory : null,
202
+ errors: []
203
+ };
204
+ } catch (error) {
205
+ // If error occurs, system remains in previous state
206
+ return {
207
+ success: false,
208
+ previous_environment: null,
209
+ new_environment: null,
210
+ files_copied: 0,
211
+ backup_created: false,
212
+ backup_location: null,
213
+ errors: [error.message]
214
+ };
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Rollback to previous environment using backup
220
+ * @returns {Object} Rollback result
221
+ * @throws {Error} If rollback fails
222
+ */
223
+ async rollbackEnvironment() {
224
+ const result = await this.backupSystem.restoreBackup();
225
+
226
+ // Update active environment to match restored backup
227
+ const registry = await EnvironmentRegistry.load(this.registryPath);
228
+ registry.active_environment = result.environment_name;
229
+ await EnvironmentRegistry.save(this.registryPath, registry);
230
+
231
+ return result;
232
+ }
233
+
234
+ /**
235
+ * Unregister environment
236
+ * @param {string} environmentName - Name of environment to remove
237
+ * @throws {Error} If environment is active or doesn't exist
238
+ */
239
+ async unregisterEnvironment(environmentName) {
240
+ const registry = await EnvironmentRegistry.load(this.registryPath);
241
+
242
+ // Check if environment exists
243
+ const envIndex = registry.environments.findIndex(
244
+ e => e.name === environmentName
245
+ );
246
+ if (envIndex === -1) {
247
+ throw new Error(`Environment "${environmentName}" not found`);
248
+ }
249
+
250
+ // Prevent unregistering active environment
251
+ if (registry.active_environment === environmentName) {
252
+ throw new Error(
253
+ `Cannot unregister active environment "${environmentName}". ` +
254
+ 'Switch to another environment first.'
255
+ );
256
+ }
257
+
258
+ // Remove environment
259
+ registry.environments.splice(envIndex, 1);
260
+ await EnvironmentRegistry.save(this.registryPath, registry);
261
+
262
+ return {
263
+ success: true,
264
+ message: `Environment "${environmentName}" unregistered successfully`
265
+ };
266
+ }
267
+
268
+ /**
269
+ * Verify current environment configuration
270
+ * @returns {Object} Verification result with status and output
271
+ * @throws {Error} If verification command fails
272
+ */
273
+ async verifyEnvironment() {
274
+ const env = await this.getActiveEnvironment();
275
+
276
+ // If no verification rules, return success
277
+ if (!env.verification) {
278
+ return {
279
+ success: true,
280
+ environment_name: env.name,
281
+ command: null,
282
+ expected_output: null,
283
+ actual_output: null,
284
+ exit_code: 0,
285
+ error: null
286
+ };
287
+ }
288
+
289
+ const { command, expected_output } = env.verification;
290
+
291
+ try {
292
+ // Execute verification command
293
+ const { execSync } = require('child_process');
294
+ const actualOutput = execSync(command, {
295
+ cwd: this.projectRoot,
296
+ encoding: 'utf8',
297
+ timeout: 30000 // 30 second timeout
298
+ }).trim();
299
+
300
+ // Check if output matches expected pattern
301
+ const success = actualOutput.includes(expected_output);
302
+
303
+ return {
304
+ success,
305
+ environment_name: env.name,
306
+ command,
307
+ expected_output,
308
+ actual_output: actualOutput,
309
+ exit_code: 0,
310
+ error: success ? null : 'Output does not match expected pattern'
311
+ };
312
+ } catch (error) {
313
+ return {
314
+ success: false,
315
+ environment_name: env.name,
316
+ command,
317
+ expected_output,
318
+ actual_output: error.stdout ? error.stdout.toString().trim() : '',
319
+ exit_code: error.status || 1,
320
+ error: error.message
321
+ };
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Run command in environment context
327
+ * @param {string} command - Command to execute
328
+ * @param {string} environmentName - Optional environment name (defaults to active)
329
+ * @returns {Object} Command execution result
330
+ * @throws {Error} If command execution fails
331
+ */
332
+ async runInEnvironment(command, environmentName = null) {
333
+ // If environment name specified, ensure it's active
334
+ if (environmentName) {
335
+ const currentEnv = await this.getActiveEnvironment();
336
+ if (currentEnv.name !== environmentName) {
337
+ // Switch to specified environment
338
+ const switchResult = await this.switchEnvironment(environmentName);
339
+ if (!switchResult.success) {
340
+ throw new Error(
341
+ `Failed to switch to environment "${environmentName}": ${switchResult.errors.join(', ')}`
342
+ );
343
+ }
344
+ }
345
+ }
346
+
347
+ const env = await this.getActiveEnvironment();
348
+
349
+ try {
350
+ // Execute command
351
+ const { execSync } = require('child_process');
352
+ const output = execSync(command, {
353
+ cwd: this.projectRoot,
354
+ encoding: 'utf8',
355
+ stdio: 'pipe'
356
+ });
357
+
358
+ return {
359
+ success: true,
360
+ environment_name: env.name,
361
+ command,
362
+ output: output.trim(),
363
+ exit_code: 0,
364
+ error: null
365
+ };
366
+ } catch (error) {
367
+ return {
368
+ success: false,
369
+ environment_name: env.name,
370
+ command,
371
+ output: error.stdout ? error.stdout.toString().trim() : '',
372
+ exit_code: error.status || 1,
373
+ error: error.message
374
+ };
375
+ }
376
+ }
377
+ }
378
+
379
+ module.exports = EnvironmentManager;
@@ -0,0 +1,168 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Environment Registry
6
+ *
7
+ * Manages persistent storage of environment configurations.
8
+ * Handles loading, saving, and validating the environments.json file.
9
+ */
10
+ class EnvironmentRegistry {
11
+ /**
12
+ * Load registry from disk
13
+ * @param {string} registryPath - Path to environments.json
14
+ * @returns {Object} Registry data
15
+ * @throws {Error} If registry is corrupted or invalid
16
+ */
17
+ static async load(registryPath) {
18
+ try {
19
+ if (!await fs.pathExists(registryPath)) {
20
+ return this.initialize();
21
+ }
22
+
23
+ const content = await fs.readFile(registryPath, 'utf8');
24
+ const data = JSON.parse(content);
25
+
26
+ this.validate(data);
27
+ return data;
28
+ } catch (error) {
29
+ if (error.name === 'SyntaxError') {
30
+ throw new Error(`Registry file is corrupted: ${error.message}`);
31
+ }
32
+ throw error;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Save registry to disk
38
+ * @param {string} registryPath - Path to environments.json
39
+ * @param {Object} registryData - Registry data to save
40
+ * @throws {Error} If save operation fails
41
+ */
42
+ static async save(registryPath, registryData) {
43
+ this.validate(registryData);
44
+
45
+ const dir = path.dirname(registryPath);
46
+ await fs.ensureDir(dir);
47
+
48
+ await fs.writeFile(
49
+ registryPath,
50
+ JSON.stringify(registryData, null, 2),
51
+ 'utf8'
52
+ );
53
+ }
54
+
55
+ /**
56
+ * Validate registry structure
57
+ * @param {Object} registryData - Registry data to validate
58
+ * @returns {boolean} True if valid
59
+ * @throws {Error} If validation fails with details
60
+ */
61
+ static validate(registryData) {
62
+ if (!registryData || typeof registryData !== 'object') {
63
+ throw new Error('Registry data must be an object');
64
+ }
65
+
66
+ if (!registryData.version) {
67
+ throw new Error('Registry must have a version field');
68
+ }
69
+
70
+ if (!Array.isArray(registryData.environments)) {
71
+ throw new Error('Registry must have an environments array');
72
+ }
73
+
74
+ // Validate each environment
75
+ const names = new Set();
76
+ for (const env of registryData.environments) {
77
+ this.validateEnvironment(env);
78
+
79
+ if (names.has(env.name)) {
80
+ throw new Error(`Duplicate environment name: ${env.name}`);
81
+ }
82
+ names.add(env.name);
83
+ }
84
+
85
+ // Validate active_environment if present
86
+ if (registryData.active_environment) {
87
+ const activeExists = registryData.environments.some(
88
+ env => env.name === registryData.active_environment
89
+ );
90
+ if (!activeExists) {
91
+ throw new Error(
92
+ `Active environment "${registryData.active_environment}" not found in registry`
93
+ );
94
+ }
95
+ }
96
+
97
+ return true;
98
+ }
99
+
100
+ /**
101
+ * Validate a single environment configuration
102
+ * @param {Object} env - Environment configuration
103
+ * @throws {Error} If validation fails
104
+ */
105
+ static validateEnvironment(env) {
106
+ if (!env.name || typeof env.name !== 'string') {
107
+ throw new Error('Environment must have a name (string)');
108
+ }
109
+
110
+ if (!/^[a-z0-9-]+$/.test(env.name)) {
111
+ throw new Error(
112
+ `Environment name must be kebab-case (lowercase, numbers, hyphens): ${env.name}`
113
+ );
114
+ }
115
+
116
+ if (!env.description || typeof env.description !== 'string') {
117
+ throw new Error(`Environment "${env.name}" must have a description`);
118
+ }
119
+
120
+ if (!Array.isArray(env.config_files) || env.config_files.length === 0) {
121
+ throw new Error(
122
+ `Environment "${env.name}" must have at least one config file mapping`
123
+ );
124
+ }
125
+
126
+ // Validate config file mappings
127
+ for (const mapping of env.config_files) {
128
+ if (!mapping.source || typeof mapping.source !== 'string') {
129
+ throw new Error(
130
+ `Environment "${env.name}" has invalid config file mapping: missing source`
131
+ );
132
+ }
133
+ if (!mapping.target || typeof mapping.target !== 'string') {
134
+ throw new Error(
135
+ `Environment "${env.name}" has invalid config file mapping: missing target`
136
+ );
137
+ }
138
+ }
139
+
140
+ // Validate verification rules if present
141
+ if (env.verification) {
142
+ if (!env.verification.command || typeof env.verification.command !== 'string') {
143
+ throw new Error(
144
+ `Environment "${env.name}" has invalid verification: missing command`
145
+ );
146
+ }
147
+ if (!env.verification.expected_output || typeof env.verification.expected_output !== 'string') {
148
+ throw new Error(
149
+ `Environment "${env.name}" has invalid verification: missing expected_output`
150
+ );
151
+ }
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Initialize empty registry
157
+ * @returns {Object} Empty registry structure
158
+ */
159
+ static initialize() {
160
+ return {
161
+ version: '1.0',
162
+ environments: [],
163
+ active_environment: null
164
+ };
165
+ }
166
+ }
167
+
168
+ module.exports = EnvironmentRegistry;