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,537 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const Workspace = require('./workspace');
5
+
6
+ /**
7
+ * WorkspaceStateManager - Single Source of Truth for workspace state
8
+ *
9
+ * Implements the Data Atomicity Principle by managing all workspace-related
10
+ * data in a single configuration file. This ensures atomic updates and
11
+ * eliminates data inconsistency risks.
12
+ *
13
+ * Architecture: Single file (~/.kse/workspace-state.json) contains:
14
+ * - All workspace entries
15
+ * - Active workspace selection
16
+ * - User preferences
17
+ *
18
+ * This replaces the previous dual-file approach (workspaces.json + config.json)
19
+ * which violated the Single Source of Truth principle.
20
+ */
21
+ class WorkspaceStateManager {
22
+ /**
23
+ * Create a new WorkspaceStateManager instance
24
+ *
25
+ * @param {string} statePath - Path to workspace-state.json (optional)
26
+ */
27
+ constructor(statePath = null) {
28
+ this.statePath = statePath || this.getDefaultStatePath();
29
+ this.state = {
30
+ version: '1.0',
31
+ activeWorkspace: null,
32
+ workspaces: new Map(),
33
+ preferences: {
34
+ autoDetectWorkspace: true,
35
+ confirmDestructiveOperations: true
36
+ }
37
+ };
38
+ this.loaded = false;
39
+ }
40
+
41
+ /**
42
+ * Get the default state file path
43
+ *
44
+ * @returns {string} Path to ~/.kse/workspace-state.json
45
+ */
46
+ getDefaultStatePath() {
47
+ const homeDir = os.homedir();
48
+ return path.join(homeDir, '.kse', 'workspace-state.json');
49
+ }
50
+
51
+ /**
52
+ * Load workspace state from disk
53
+ *
54
+ * Supports automatic migration from legacy format (workspaces.json + config.json)
55
+ *
56
+ * @returns {Promise<boolean>} True if loaded successfully
57
+ */
58
+ async load() {
59
+ try {
60
+ // Try loading new format
61
+ const exists = await fs.pathExists(this.statePath);
62
+
63
+ if (exists) {
64
+ await this.loadNewFormat();
65
+ this.loaded = true;
66
+ return true;
67
+ }
68
+
69
+ // Check for legacy format and migrate
70
+ if (await this.hasLegacyFiles()) {
71
+ console.log('Migrating workspace configuration to new format...');
72
+ await this.migrateFromLegacy();
73
+ this.loaded = true;
74
+ return true;
75
+ }
76
+
77
+ // Initialize empty state
78
+ this.state = {
79
+ version: '1.0',
80
+ activeWorkspace: null,
81
+ workspaces: new Map(),
82
+ preferences: {
83
+ autoDetectWorkspace: true,
84
+ confirmDestructiveOperations: true
85
+ }
86
+ };
87
+ this.loaded = true;
88
+ return true;
89
+
90
+ } catch (error) {
91
+ if (error instanceof SyntaxError) {
92
+ throw new Error(`Workspace state file is corrupted: ${this.statePath}. ` +
93
+ `Please backup and delete the file, then try again.`);
94
+ }
95
+ throw error;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Load state from new format file
101
+ *
102
+ * @private
103
+ */
104
+ async loadNewFormat() {
105
+ const content = await fs.readFile(this.statePath, 'utf8');
106
+ const data = JSON.parse(content);
107
+
108
+ // Validate version
109
+ if (data.version !== this.state.version) {
110
+ console.warn(`Warning: State version mismatch. Expected ${this.state.version}, got ${data.version}`);
111
+ }
112
+
113
+ // Load state
114
+ this.state.version = data.version;
115
+ this.state.activeWorkspace = data.activeWorkspace || null;
116
+
117
+ // Load workspaces
118
+ this.state.workspaces = new Map();
119
+ if (data.workspaces && Array.isArray(data.workspaces)) {
120
+ for (const workspaceData of data.workspaces) {
121
+ const workspace = Workspace.fromDict(workspaceData);
122
+ this.state.workspaces.set(workspace.name, workspace);
123
+ }
124
+ }
125
+
126
+ // Load preferences
127
+ this.state.preferences = {
128
+ autoDetectWorkspace: data.preferences?.autoDetectWorkspace ?? true,
129
+ confirmDestructiveOperations: data.preferences?.confirmDestructiveOperations ?? true
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Check if legacy configuration files exist
135
+ *
136
+ * @private
137
+ * @returns {Promise<boolean>}
138
+ */
139
+ async hasLegacyFiles() {
140
+ const homeDir = os.homedir();
141
+ const legacyWorkspacesPath = path.join(homeDir, '.kse', 'workspaces.json');
142
+ const legacyConfigPath = path.join(homeDir, '.kse', 'config.json');
143
+
144
+ const workspacesExists = await fs.pathExists(legacyWorkspacesPath);
145
+ const configExists = await fs.pathExists(legacyConfigPath);
146
+
147
+ return workspacesExists || configExists;
148
+ }
149
+
150
+ /**
151
+ * Migrate from legacy format (workspaces.json + config.json)
152
+ *
153
+ * @private
154
+ */
155
+ async migrateFromLegacy() {
156
+ const homeDir = os.homedir();
157
+ const legacyWorkspacesPath = path.join(homeDir, '.kse', 'workspaces.json');
158
+ const legacyConfigPath = path.join(homeDir, '.kse', 'config.json');
159
+
160
+ // Load legacy workspaces
161
+ let legacyWorkspaces = [];
162
+ if (await fs.pathExists(legacyWorkspacesPath)) {
163
+ const content = await fs.readFile(legacyWorkspacesPath, 'utf8');
164
+ const data = JSON.parse(content);
165
+ legacyWorkspaces = data.workspaces || [];
166
+ }
167
+
168
+ // Load legacy config
169
+ let legacyActiveWorkspace = null;
170
+ let legacyPreferences = {};
171
+ if (await fs.pathExists(legacyConfigPath)) {
172
+ const content = await fs.readFile(legacyConfigPath, 'utf8');
173
+ const data = JSON.parse(content);
174
+ legacyActiveWorkspace = data.active_workspace || null;
175
+ legacyPreferences = data.preferences || {};
176
+ }
177
+
178
+ // Merge into new format
179
+ this.state.activeWorkspace = legacyActiveWorkspace;
180
+ this.state.workspaces = new Map();
181
+
182
+ for (const workspaceData of legacyWorkspaces) {
183
+ const workspace = Workspace.fromDict(workspaceData);
184
+ this.state.workspaces.set(workspace.name, workspace);
185
+ }
186
+
187
+ this.state.preferences = {
188
+ autoDetectWorkspace: legacyPreferences.auto_detect_workspace ?? true,
189
+ confirmDestructiveOperations: legacyPreferences.confirm_destructive_operations ?? true
190
+ };
191
+
192
+ // Save to new format
193
+ await this.save();
194
+
195
+ // Backup legacy files
196
+ await this.backupLegacyFiles();
197
+ }
198
+
199
+ /**
200
+ * Backup legacy configuration files
201
+ *
202
+ * @private
203
+ */
204
+ async backupLegacyFiles() {
205
+ const homeDir = os.homedir();
206
+ const legacyWorkspacesPath = path.join(homeDir, '.kse', 'workspaces.json');
207
+ const legacyConfigPath = path.join(homeDir, '.kse', 'config.json');
208
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
209
+
210
+ if (await fs.pathExists(legacyWorkspacesPath)) {
211
+ const backupPath = path.join(homeDir, '.kse', `workspaces.json.backup-${timestamp}`);
212
+ await fs.copy(legacyWorkspacesPath, backupPath);
213
+ await fs.remove(legacyWorkspacesPath);
214
+ }
215
+
216
+ if (await fs.pathExists(legacyConfigPath)) {
217
+ const backupPath = path.join(homeDir, '.kse', `config.json.backup-${timestamp}`);
218
+ await fs.copy(legacyConfigPath, backupPath);
219
+ await fs.remove(legacyConfigPath);
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Save workspace state to disk (atomic operation)
225
+ *
226
+ * Uses temp file + atomic rename to ensure consistency
227
+ *
228
+ * @returns {Promise<boolean>} True if saved successfully
229
+ */
230
+ async save() {
231
+ try {
232
+ // Ensure directory exists
233
+ const stateDir = path.dirname(this.statePath);
234
+ await fs.ensureDir(stateDir);
235
+
236
+ // Serialize state
237
+ const workspacesArray = Array.from(this.state.workspaces.values()).map(ws => ws.toDict());
238
+
239
+ const data = {
240
+ version: this.state.version,
241
+ activeWorkspace: this.state.activeWorkspace,
242
+ workspaces: workspacesArray,
243
+ preferences: {
244
+ autoDetectWorkspace: this.state.preferences.autoDetectWorkspace,
245
+ confirmDestructiveOperations: this.state.preferences.confirmDestructiveOperations
246
+ }
247
+ };
248
+
249
+ // Write to temp file first
250
+ const tempPath = `${this.statePath}.tmp`;
251
+ await fs.writeFile(tempPath, JSON.stringify(data, null, 2), 'utf8');
252
+
253
+ // Atomic rename (ensures consistency)
254
+ await fs.rename(tempPath, this.statePath);
255
+
256
+ return true;
257
+ } catch (error) {
258
+ throw new Error(`Failed to save workspace state: ${error.message}`);
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Ensure state is loaded before operations
264
+ *
265
+ * @private
266
+ */
267
+ async ensureLoaded() {
268
+ if (!this.loaded) {
269
+ await this.load();
270
+ }
271
+ }
272
+
273
+ // ==================== Workspace Operations ====================
274
+
275
+ /**
276
+ * Create a new workspace (atomic operation)
277
+ *
278
+ * @param {string} name - Unique workspace name
279
+ * @param {string} workspacePath - Path to workspace directory
280
+ * @returns {Promise<Workspace>} Created workspace
281
+ * @throws {Error} If name already exists or path is invalid
282
+ */
283
+ async createWorkspace(name, workspacePath) {
284
+ await this.ensureLoaded();
285
+
286
+ // Validate name
287
+ if (!name || typeof name !== 'string' || name.trim().length === 0) {
288
+ throw new Error('Workspace name cannot be empty');
289
+ }
290
+
291
+ // Check for duplicate name
292
+ if (this.state.workspaces.has(name)) {
293
+ throw new Error(`Workspace "${name}" already exists`);
294
+ }
295
+
296
+ // Validate path (check for .kiro directory)
297
+ const kiroPath = path.join(workspacePath, '.kiro');
298
+ const kiroExists = await fs.pathExists(kiroPath);
299
+ if (!kiroExists) {
300
+ throw new Error(`Path "${workspacePath}" is not a valid kse project directory. ` +
301
+ `Ensure it exists and contains a .kiro/ directory.`);
302
+ }
303
+
304
+ // Create workspace
305
+ const workspace = new Workspace(name, workspacePath);
306
+ this.state.workspaces.set(name, workspace);
307
+
308
+ // Atomic save
309
+ await this.save();
310
+
311
+ return workspace;
312
+ }
313
+
314
+ /**
315
+ * Get a workspace by name
316
+ *
317
+ * @param {string} name - Workspace name
318
+ * @returns {Promise<Workspace|null>} Workspace or null if not found
319
+ */
320
+ async getWorkspace(name) {
321
+ await this.ensureLoaded();
322
+ return this.state.workspaces.get(name) || null;
323
+ }
324
+
325
+ /**
326
+ * List all registered workspaces
327
+ *
328
+ * @returns {Promise<Array<Workspace>>} Array of workspaces
329
+ */
330
+ async listWorkspaces() {
331
+ await this.ensureLoaded();
332
+ return Array.from(this.state.workspaces.values());
333
+ }
334
+
335
+ /**
336
+ * Remove a workspace from the registry (atomic operation)
337
+ *
338
+ * @param {string} name - Workspace name
339
+ * @returns {Promise<boolean>} True if removed, false if not found
340
+ */
341
+ async removeWorkspace(name) {
342
+ await this.ensureLoaded();
343
+
344
+ if (!this.state.workspaces.has(name)) {
345
+ return false;
346
+ }
347
+
348
+ // Remove workspace
349
+ this.state.workspaces.delete(name);
350
+
351
+ // Clear active workspace if it was the removed one
352
+ if (this.state.activeWorkspace === name) {
353
+ this.state.activeWorkspace = null;
354
+ }
355
+
356
+ // Atomic save
357
+ await this.save();
358
+
359
+ return true;
360
+ }
361
+
362
+ /**
363
+ * Switch to a workspace (atomic operation)
364
+ *
365
+ * Updates both active workspace and last accessed timestamp atomically
366
+ *
367
+ * @param {string} name - Workspace name
368
+ * @returns {Promise<void>}
369
+ * @throws {Error} If workspace doesn't exist
370
+ */
371
+ async switchWorkspace(name) {
372
+ await this.ensureLoaded();
373
+
374
+ const workspace = this.state.workspaces.get(name);
375
+ if (!workspace) {
376
+ const available = Array.from(this.state.workspaces.keys());
377
+ throw new Error(
378
+ `Workspace "${name}" does not exist.\n` +
379
+ `Available workspaces: ${available.join(', ') || 'none'}`
380
+ );
381
+ }
382
+
383
+ // Update state atomically
384
+ this.state.activeWorkspace = name;
385
+ workspace.updateLastAccessed();
386
+
387
+ // Atomic save
388
+ await this.save();
389
+ }
390
+
391
+ /**
392
+ * Get the active workspace
393
+ *
394
+ * @returns {Promise<Workspace|null>} Active workspace or null
395
+ */
396
+ async getActiveWorkspace() {
397
+ await this.ensureLoaded();
398
+
399
+ if (!this.state.activeWorkspace) {
400
+ return null;
401
+ }
402
+
403
+ const workspace = this.state.workspaces.get(this.state.activeWorkspace);
404
+ if (!workspace) {
405
+ // Active workspace no longer exists, clear it
406
+ this.state.activeWorkspace = null;
407
+ await this.save();
408
+ return null;
409
+ }
410
+
411
+ return workspace;
412
+ }
413
+
414
+ /**
415
+ * Clear the active workspace
416
+ *
417
+ * @returns {Promise<void>}
418
+ */
419
+ async clearActiveWorkspace() {
420
+ await this.ensureLoaded();
421
+ this.state.activeWorkspace = null;
422
+ await this.save();
423
+ }
424
+
425
+ /**
426
+ * Find workspace that contains the given path
427
+ *
428
+ * @param {string} targetPath - Path to search for
429
+ * @returns {Promise<Workspace|null>} Workspace containing the path, or null
430
+ */
431
+ async findWorkspaceByPath(targetPath) {
432
+ await this.ensureLoaded();
433
+
434
+ const absolutePath = path.isAbsolute(targetPath)
435
+ ? targetPath
436
+ : path.resolve(targetPath);
437
+
438
+ for (const workspace of this.state.workspaces.values()) {
439
+ if (workspace.containsPath(absolutePath)) {
440
+ return workspace;
441
+ }
442
+ }
443
+
444
+ return null;
445
+ }
446
+
447
+ // ==================== Preference Operations ====================
448
+
449
+ /**
450
+ * Get a preference value
451
+ *
452
+ * @param {string} key - Preference key
453
+ * @returns {Promise<any>} Preference value
454
+ */
455
+ async getPreference(key) {
456
+ await this.ensureLoaded();
457
+ return this.state.preferences[key];
458
+ }
459
+
460
+ /**
461
+ * Set a preference value
462
+ *
463
+ * @param {string} key - Preference key
464
+ * @param {any} value - Preference value
465
+ * @returns {Promise<void>}
466
+ */
467
+ async setPreference(key, value) {
468
+ await this.ensureLoaded();
469
+ this.state.preferences[key] = value;
470
+ await this.save();
471
+ }
472
+
473
+ /**
474
+ * Get all preferences
475
+ *
476
+ * @returns {Promise<Object>} All preferences
477
+ */
478
+ async getPreferences() {
479
+ await this.ensureLoaded();
480
+ return { ...this.state.preferences };
481
+ }
482
+
483
+ // ==================== Utility Methods ====================
484
+
485
+ /**
486
+ * Check if a workspace name exists
487
+ *
488
+ * @param {string} name - Workspace name
489
+ * @returns {Promise<boolean>} True if exists
490
+ */
491
+ async hasWorkspace(name) {
492
+ await this.ensureLoaded();
493
+ return this.state.workspaces.has(name);
494
+ }
495
+
496
+ /**
497
+ * Get count of registered workspaces
498
+ *
499
+ * @returns {Promise<number>} Number of workspaces
500
+ */
501
+ async count() {
502
+ await this.ensureLoaded();
503
+ return this.state.workspaces.size;
504
+ }
505
+
506
+ /**
507
+ * Clear all workspaces (for testing purposes)
508
+ *
509
+ * @returns {Promise<void>}
510
+ */
511
+ async clear() {
512
+ await this.ensureLoaded();
513
+ this.state.workspaces.clear();
514
+ this.state.activeWorkspace = null;
515
+ await this.save();
516
+ }
517
+
518
+ /**
519
+ * Reset to default state
520
+ *
521
+ * @returns {Promise<void>}
522
+ */
523
+ async reset() {
524
+ this.state = {
525
+ version: '1.0',
526
+ activeWorkspace: null,
527
+ workspaces: new Map(),
528
+ preferences: {
529
+ autoDetectWorkspace: true,
530
+ confirmDestructiveOperations: true
531
+ }
532
+ };
533
+ await this.save();
534
+ }
535
+ }
536
+
537
+ module.exports = WorkspaceStateManager;
@@ -0,0 +1,90 @@
1
+ const PathUtils = require('./path-utils');
2
+
3
+ /**
4
+ * Workspace - Data model for a registered kse project workspace
5
+ *
6
+ * Represents a single workspace entry in the workspace registry.
7
+ * Each workspace has a unique name, absolute path, and timestamps.
8
+ */
9
+ class Workspace {
10
+ /**
11
+ * Create a new Workspace instance
12
+ *
13
+ * @param {string} name - Unique workspace name
14
+ * @param {string} workspacePath - Absolute path to the workspace directory
15
+ * @param {Date|string} createdAt - Creation timestamp (optional, defaults to now)
16
+ * @param {Date|string} lastAccessed - Last accessed timestamp (optional, defaults to now)
17
+ */
18
+ constructor(name, workspacePath, createdAt = null, lastAccessed = null) {
19
+ this.name = name;
20
+ this.path = PathUtils.normalize(workspacePath);
21
+ this.createdAt = createdAt ? new Date(createdAt) : new Date();
22
+ this.lastAccessed = lastAccessed ? new Date(lastAccessed) : new Date();
23
+ }
24
+
25
+ /**
26
+ * Get platform-specific path for runtime use
27
+ *
28
+ * @returns {string} Platform-specific path
29
+ */
30
+ getPlatformPath() {
31
+ return PathUtils.toPlatform(this.path);
32
+ }
33
+
34
+ /**
35
+ * Serialize workspace to JSON-compatible object
36
+ *
37
+ * @returns {Object} Serialized workspace data
38
+ */
39
+ toDict() {
40
+ return {
41
+ name: this.name,
42
+ path: this.path,
43
+ createdAt: this.createdAt.toISOString(),
44
+ lastAccessed: this.lastAccessed.toISOString()
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Deserialize workspace from JSON-compatible object
50
+ *
51
+ * @param {Object} data - Serialized workspace data
52
+ * @returns {Workspace} Workspace instance
53
+ */
54
+ static fromDict(data) {
55
+ return new Workspace(
56
+ data.name,
57
+ data.path,
58
+ data.createdAt,
59
+ data.lastAccessed
60
+ );
61
+ }
62
+
63
+ /**
64
+ * Update last accessed timestamp to current time
65
+ */
66
+ updateLastAccessed() {
67
+ this.lastAccessed = new Date();
68
+ }
69
+
70
+ /**
71
+ * Check if this workspace path matches or contains the given path
72
+ *
73
+ * @param {string} targetPath - Path to check
74
+ * @returns {boolean} True if targetPath is within this workspace
75
+ */
76
+ containsPath(targetPath) {
77
+ return PathUtils.isWithin(targetPath, this.path);
78
+ }
79
+
80
+ /**
81
+ * Get a string representation of the workspace
82
+ *
83
+ * @returns {string} String representation
84
+ */
85
+ toString() {
86
+ return `Workspace(name="${this.name}", path="${this.path}")`;
87
+ }
88
+ }
89
+
90
+ module.exports = Workspace;