orbital-command 0.1.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 (325) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +396 -0
  3. package/bin/orbital.js +362 -0
  4. package/dist/assets/WorkflowVisualizer-BZ21PIIF.js +84 -0
  5. package/dist/assets/WorkflowVisualizer-BZV40eAE.css +1 -0
  6. package/dist/assets/charts-D__PA1zp.js +72 -0
  7. package/dist/assets/index-D1G6i0nS.css +1 -0
  8. package/dist/assets/index-DpItvKpf.js +419 -0
  9. package/dist/assets/ui-BvF022GT.js +53 -0
  10. package/dist/assets/vendor-Dzv9lrRc.js +59 -0
  11. package/dist/index.html +19 -0
  12. package/dist/scanner-sweep.png +0 -0
  13. package/dist/server/server/adapters/index.js +34 -0
  14. package/dist/server/server/adapters/iterm2-adapter.js +29 -0
  15. package/dist/server/server/adapters/subprocess-adapter.js +21 -0
  16. package/dist/server/server/adapters/terminal-adapter.js +1 -0
  17. package/dist/server/server/config.js +156 -0
  18. package/dist/server/server/database.js +90 -0
  19. package/dist/server/server/index.js +372 -0
  20. package/dist/server/server/init.js +811 -0
  21. package/dist/server/server/parsers/event-parser.js +64 -0
  22. package/dist/server/server/parsers/scope-parser.js +188 -0
  23. package/dist/server/server/routes/config-routes.js +163 -0
  24. package/dist/server/server/routes/data-routes.js +461 -0
  25. package/dist/server/server/routes/dispatch-routes.js +215 -0
  26. package/dist/server/server/routes/git-routes.js +92 -0
  27. package/dist/server/server/routes/scope-routes.js +215 -0
  28. package/dist/server/server/routes/sprint-routes.js +116 -0
  29. package/dist/server/server/routes/version-routes.js +130 -0
  30. package/dist/server/server/routes/workflow-routes.js +185 -0
  31. package/dist/server/server/schema.js +90 -0
  32. package/dist/server/server/services/batch-orchestrator.js +253 -0
  33. package/dist/server/server/services/claude-session-service.js +352 -0
  34. package/dist/server/server/services/config-service.js +132 -0
  35. package/dist/server/server/services/deploy-service.js +51 -0
  36. package/dist/server/server/services/event-service.js +63 -0
  37. package/dist/server/server/services/gate-service.js +83 -0
  38. package/dist/server/server/services/git-service.js +309 -0
  39. package/dist/server/server/services/github-service.js +145 -0
  40. package/dist/server/server/services/readiness-service.js +184 -0
  41. package/dist/server/server/services/scope-cache.js +72 -0
  42. package/dist/server/server/services/scope-service.js +424 -0
  43. package/dist/server/server/services/sprint-orchestrator.js +312 -0
  44. package/dist/server/server/services/sprint-service.js +293 -0
  45. package/dist/server/server/services/workflow-service.js +397 -0
  46. package/dist/server/server/utils/cc-hooks-parser.js +49 -0
  47. package/dist/server/server/utils/dispatch-utils.js +305 -0
  48. package/dist/server/server/utils/logger.js +86 -0
  49. package/dist/server/server/utils/terminal-launcher.js +388 -0
  50. package/dist/server/server/utils/worktree-manager.js +98 -0
  51. package/dist/server/server/watchers/event-watcher.js +81 -0
  52. package/dist/server/server/watchers/scope-watcher.js +33 -0
  53. package/dist/server/shared/api-types.js +5 -0
  54. package/dist/server/shared/default-workflow.json +616 -0
  55. package/dist/server/shared/workflow-config.js +44 -0
  56. package/dist/server/shared/workflow-engine.js +353 -0
  57. package/index.html +15 -0
  58. package/package.json +110 -0
  59. package/postcss.config.js +6 -0
  60. package/schemas/orbital.config.schema.json +83 -0
  61. package/scripts/postinstall.js +24 -0
  62. package/scripts/start.sh +20 -0
  63. package/server/adapters/index.ts +41 -0
  64. package/server/adapters/iterm2-adapter.ts +37 -0
  65. package/server/adapters/subprocess-adapter.ts +25 -0
  66. package/server/adapters/terminal-adapter.ts +24 -0
  67. package/server/config.ts +234 -0
  68. package/server/database.ts +107 -0
  69. package/server/index.ts +452 -0
  70. package/server/init.ts +891 -0
  71. package/server/parsers/event-parser.ts +74 -0
  72. package/server/parsers/scope-parser.ts +240 -0
  73. package/server/routes/config-routes.ts +182 -0
  74. package/server/routes/data-routes.ts +548 -0
  75. package/server/routes/dispatch-routes.ts +275 -0
  76. package/server/routes/git-routes.ts +112 -0
  77. package/server/routes/scope-routes.ts +262 -0
  78. package/server/routes/sprint-routes.ts +142 -0
  79. package/server/routes/version-routes.ts +156 -0
  80. package/server/routes/workflow-routes.ts +198 -0
  81. package/server/schema.ts +90 -0
  82. package/server/services/batch-orchestrator.ts +286 -0
  83. package/server/services/claude-session-service.ts +441 -0
  84. package/server/services/config-service.ts +151 -0
  85. package/server/services/deploy-service.ts +98 -0
  86. package/server/services/event-service.ts +98 -0
  87. package/server/services/gate-service.ts +126 -0
  88. package/server/services/git-service.ts +391 -0
  89. package/server/services/github-service.ts +183 -0
  90. package/server/services/readiness-service.ts +250 -0
  91. package/server/services/scope-cache.ts +81 -0
  92. package/server/services/scope-service.ts +476 -0
  93. package/server/services/sprint-orchestrator.ts +361 -0
  94. package/server/services/sprint-service.ts +415 -0
  95. package/server/services/workflow-service.ts +461 -0
  96. package/server/utils/cc-hooks-parser.ts +70 -0
  97. package/server/utils/dispatch-utils.ts +395 -0
  98. package/server/utils/logger.ts +109 -0
  99. package/server/utils/terminal-launcher.ts +462 -0
  100. package/server/utils/worktree-manager.ts +104 -0
  101. package/server/watchers/event-watcher.ts +100 -0
  102. package/server/watchers/scope-watcher.ts +38 -0
  103. package/shared/api-types.ts +20 -0
  104. package/shared/default-workflow.json +616 -0
  105. package/shared/workflow-config.ts +170 -0
  106. package/shared/workflow-engine.ts +427 -0
  107. package/src/App.tsx +33 -0
  108. package/src/components/AgentBadge.tsx +40 -0
  109. package/src/components/BatchPreflightModal.tsx +115 -0
  110. package/src/components/CardDisplayToggle.tsx +74 -0
  111. package/src/components/ColumnHeaderActions.tsx +55 -0
  112. package/src/components/ColumnMenu.tsx +99 -0
  113. package/src/components/DeployHistory.tsx +141 -0
  114. package/src/components/DispatchModal.tsx +164 -0
  115. package/src/components/DispatchPopover.tsx +139 -0
  116. package/src/components/DragOverlay.tsx +25 -0
  117. package/src/components/DriftSidebar.tsx +140 -0
  118. package/src/components/EnvironmentStrip.tsx +88 -0
  119. package/src/components/ErrorBoundary.tsx +62 -0
  120. package/src/components/FilterChip.tsx +105 -0
  121. package/src/components/GateIndicator.tsx +33 -0
  122. package/src/components/IdeaDetailModal.tsx +190 -0
  123. package/src/components/IdeaFormDialog.tsx +113 -0
  124. package/src/components/KanbanColumn.tsx +201 -0
  125. package/src/components/MarkdownRenderer.tsx +114 -0
  126. package/src/components/NeonGrid.tsx +128 -0
  127. package/src/components/PromotionQueue.tsx +89 -0
  128. package/src/components/ScopeCard.tsx +234 -0
  129. package/src/components/ScopeDetailModal.tsx +255 -0
  130. package/src/components/ScopeFilterBar.tsx +152 -0
  131. package/src/components/SearchInput.tsx +102 -0
  132. package/src/components/SessionPanel.tsx +335 -0
  133. package/src/components/SprintContainer.tsx +303 -0
  134. package/src/components/SprintDependencyDialog.tsx +78 -0
  135. package/src/components/SprintPreflightModal.tsx +138 -0
  136. package/src/components/StatusBar.tsx +168 -0
  137. package/src/components/SwimCell.tsx +67 -0
  138. package/src/components/SwimLaneRow.tsx +94 -0
  139. package/src/components/SwimlaneBoardView.tsx +108 -0
  140. package/src/components/VersionBadge.tsx +139 -0
  141. package/src/components/ViewModeSelector.tsx +114 -0
  142. package/src/components/config/AgentChip.tsx +53 -0
  143. package/src/components/config/AgentCreateDialog.tsx +321 -0
  144. package/src/components/config/AgentEditor.tsx +175 -0
  145. package/src/components/config/DirectoryTree.tsx +582 -0
  146. package/src/components/config/FileEditor.tsx +550 -0
  147. package/src/components/config/HookChip.tsx +50 -0
  148. package/src/components/config/StageCard.tsx +198 -0
  149. package/src/components/config/TransitionZone.tsx +173 -0
  150. package/src/components/config/UnifiedWorkflowPipeline.tsx +216 -0
  151. package/src/components/config/WorkflowPipeline.tsx +161 -0
  152. package/src/components/source-control/BranchList.tsx +93 -0
  153. package/src/components/source-control/BranchPanel.tsx +105 -0
  154. package/src/components/source-control/CommitLog.tsx +100 -0
  155. package/src/components/source-control/CommitRow.tsx +47 -0
  156. package/src/components/source-control/GitHubPanel.tsx +110 -0
  157. package/src/components/source-control/GitHubSetupGuide.tsx +52 -0
  158. package/src/components/source-control/GitOverviewBar.tsx +101 -0
  159. package/src/components/source-control/PullRequestList.tsx +69 -0
  160. package/src/components/source-control/WorktreeList.tsx +80 -0
  161. package/src/components/ui/badge.tsx +41 -0
  162. package/src/components/ui/button.tsx +55 -0
  163. package/src/components/ui/card.tsx +78 -0
  164. package/src/components/ui/dialog.tsx +94 -0
  165. package/src/components/ui/popover.tsx +33 -0
  166. package/src/components/ui/scroll-area.tsx +54 -0
  167. package/src/components/ui/separator.tsx +28 -0
  168. package/src/components/ui/tabs.tsx +52 -0
  169. package/src/components/ui/toggle-switch.tsx +35 -0
  170. package/src/components/ui/tooltip.tsx +27 -0
  171. package/src/components/workflow/AddEdgeDialog.tsx +217 -0
  172. package/src/components/workflow/AddListDialog.tsx +201 -0
  173. package/src/components/workflow/ChecklistEditor.tsx +239 -0
  174. package/src/components/workflow/CommandPrefixManager.tsx +118 -0
  175. package/src/components/workflow/ConfigSettingsPanel.tsx +189 -0
  176. package/src/components/workflow/DirectionSelector.tsx +133 -0
  177. package/src/components/workflow/DispatchConfigPanel.tsx +180 -0
  178. package/src/components/workflow/EdgeDetailPanel.tsx +236 -0
  179. package/src/components/workflow/EdgePropertyEditor.tsx +251 -0
  180. package/src/components/workflow/EditToolbar.tsx +138 -0
  181. package/src/components/workflow/HookDetailPanel.tsx +250 -0
  182. package/src/components/workflow/HookExecutionLog.tsx +24 -0
  183. package/src/components/workflow/HookSourceModal.tsx +129 -0
  184. package/src/components/workflow/HooksDashboard.tsx +363 -0
  185. package/src/components/workflow/ListPropertyEditor.tsx +251 -0
  186. package/src/components/workflow/MigrationPreviewDialog.tsx +237 -0
  187. package/src/components/workflow/MovementRulesPanel.tsx +188 -0
  188. package/src/components/workflow/NodeDetailPanel.tsx +245 -0
  189. package/src/components/workflow/PresetSelector.tsx +414 -0
  190. package/src/components/workflow/SkillCommandBuilder.tsx +174 -0
  191. package/src/components/workflow/WorkflowEdgeComponent.tsx +145 -0
  192. package/src/components/workflow/WorkflowNode.tsx +147 -0
  193. package/src/components/workflow/graphLayout.ts +186 -0
  194. package/src/components/workflow/mergeHooks.ts +85 -0
  195. package/src/components/workflow/useEditHistory.ts +88 -0
  196. package/src/components/workflow/useWorkflowEditor.ts +262 -0
  197. package/src/components/workflow/validateConfig.ts +70 -0
  198. package/src/hooks/useActiveDispatches.ts +198 -0
  199. package/src/hooks/useBoardSettings.ts +170 -0
  200. package/src/hooks/useCardDisplay.ts +57 -0
  201. package/src/hooks/useCcHooks.ts +24 -0
  202. package/src/hooks/useConfigTree.ts +51 -0
  203. package/src/hooks/useEnforcementRules.ts +46 -0
  204. package/src/hooks/useEvents.ts +59 -0
  205. package/src/hooks/useFileEditor.ts +165 -0
  206. package/src/hooks/useGates.ts +57 -0
  207. package/src/hooks/useIdeaActions.ts +53 -0
  208. package/src/hooks/useKanbanDnd.ts +410 -0
  209. package/src/hooks/useOrbitalConfig.ts +54 -0
  210. package/src/hooks/usePipeline.ts +47 -0
  211. package/src/hooks/usePipelineData.ts +338 -0
  212. package/src/hooks/useReconnect.ts +25 -0
  213. package/src/hooks/useScopeFilters.ts +125 -0
  214. package/src/hooks/useScopeSessions.ts +44 -0
  215. package/src/hooks/useScopes.ts +67 -0
  216. package/src/hooks/useSearch.ts +67 -0
  217. package/src/hooks/useSettings.tsx +187 -0
  218. package/src/hooks/useSocket.ts +25 -0
  219. package/src/hooks/useSourceControl.ts +105 -0
  220. package/src/hooks/useSprintPreflight.ts +55 -0
  221. package/src/hooks/useSprints.ts +154 -0
  222. package/src/hooks/useStatusBarHighlight.ts +18 -0
  223. package/src/hooks/useSwimlaneBoardSettings.ts +104 -0
  224. package/src/hooks/useTheme.ts +9 -0
  225. package/src/hooks/useTransitionReadiness.ts +53 -0
  226. package/src/hooks/useVersion.ts +155 -0
  227. package/src/hooks/useViolations.ts +65 -0
  228. package/src/hooks/useWorkflow.tsx +125 -0
  229. package/src/hooks/useZoomModifier.ts +19 -0
  230. package/src/index.css +797 -0
  231. package/src/layouts/DashboardLayout.tsx +113 -0
  232. package/src/lib/collisionDetection.ts +20 -0
  233. package/src/lib/scope-fields.ts +61 -0
  234. package/src/lib/swimlane.ts +146 -0
  235. package/src/lib/utils.ts +15 -0
  236. package/src/main.tsx +19 -0
  237. package/src/socket.ts +11 -0
  238. package/src/types/index.ts +497 -0
  239. package/src/views/AgentFeed.tsx +339 -0
  240. package/src/views/DeployPipeline.tsx +59 -0
  241. package/src/views/EnforcementView.tsx +378 -0
  242. package/src/views/PrimitivesConfig.tsx +500 -0
  243. package/src/views/QualityGates.tsx +1012 -0
  244. package/src/views/ScopeBoard.tsx +454 -0
  245. package/src/views/SessionTimeline.tsx +516 -0
  246. package/src/views/Settings.tsx +183 -0
  247. package/src/views/SourceControl.tsx +95 -0
  248. package/src/views/WorkflowVisualizer.tsx +382 -0
  249. package/tailwind.config.js +161 -0
  250. package/templates/agents/AUTO-INVOKE.md +180 -0
  251. package/templates/agents/CONFLICT-RESOLUTION.md +128 -0
  252. package/templates/agents/QUICK-REFERENCE.md +122 -0
  253. package/templates/agents/README.md +188 -0
  254. package/templates/agents/SKILL-TRIGGERS.md +100 -0
  255. package/templates/agents/blue-team/frontend-designer.md +424 -0
  256. package/templates/agents/green-team/architect.md +526 -0
  257. package/templates/agents/green-team/rules-enforcer.md +131 -0
  258. package/templates/agents/red-team/attacker-learned.md +24 -0
  259. package/templates/agents/red-team/attacker.md +486 -0
  260. package/templates/agents/red-team/chaos.md +548 -0
  261. package/templates/agents/reference/component-registry.md +82 -0
  262. package/templates/agents/workflows/full-mode.md +218 -0
  263. package/templates/agents/workflows/quick-mode.md +118 -0
  264. package/templates/agents/workflows/security-mode.md +283 -0
  265. package/templates/anti-patterns/dangerous-shortcuts.md +427 -0
  266. package/templates/config/agent-triggers.json +92 -0
  267. package/templates/hooks/agent-team-gate.sh +31 -0
  268. package/templates/hooks/agent-trigger.sh +97 -0
  269. package/templates/hooks/block-push.sh +66 -0
  270. package/templates/hooks/block-workarounds.sh +61 -0
  271. package/templates/hooks/blocker-check.sh +28 -0
  272. package/templates/hooks/completion-checklist.sh +28 -0
  273. package/templates/hooks/decision-capture.sh +15 -0
  274. package/templates/hooks/dependency-check.sh +27 -0
  275. package/templates/hooks/end-session.sh +31 -0
  276. package/templates/hooks/exploration-logger.sh +37 -0
  277. package/templates/hooks/files-changed-summary.sh +37 -0
  278. package/templates/hooks/get-session-id.sh +49 -0
  279. package/templates/hooks/git-commit-guard.sh +34 -0
  280. package/templates/hooks/init-session.sh +93 -0
  281. package/templates/hooks/orbital-emit.sh +79 -0
  282. package/templates/hooks/orbital-report-deploy.sh +78 -0
  283. package/templates/hooks/orbital-report-gates.sh +40 -0
  284. package/templates/hooks/orbital-report-violation.sh +36 -0
  285. package/templates/hooks/orbital-scope-update.sh +53 -0
  286. package/templates/hooks/phase-verify-reminder.sh +26 -0
  287. package/templates/hooks/review-gate-check.sh +82 -0
  288. package/templates/hooks/scope-commit-logger.sh +37 -0
  289. package/templates/hooks/scope-create-cleanup.sh +36 -0
  290. package/templates/hooks/scope-create-gate.sh +80 -0
  291. package/templates/hooks/scope-create-tracker.sh +17 -0
  292. package/templates/hooks/scope-file-sync.sh +53 -0
  293. package/templates/hooks/scope-gate.sh +35 -0
  294. package/templates/hooks/scope-helpers.sh +188 -0
  295. package/templates/hooks/scope-lifecycle-gate.sh +139 -0
  296. package/templates/hooks/scope-prepare.sh +244 -0
  297. package/templates/hooks/scope-transition.sh +172 -0
  298. package/templates/hooks/session-enforcer.sh +143 -0
  299. package/templates/hooks/time-tracker.sh +33 -0
  300. package/templates/lessons-learned.md +15 -0
  301. package/templates/orbital.config.json +35 -0
  302. package/templates/presets/development.json +42 -0
  303. package/templates/presets/gitflow.json +712 -0
  304. package/templates/presets/minimal.json +23 -0
  305. package/templates/quick/rules.md +218 -0
  306. package/templates/scopes/_template.md +255 -0
  307. package/templates/settings-hooks.json +98 -0
  308. package/templates/skills/git-commit/SKILL.md +85 -0
  309. package/templates/skills/git-dev/SKILL.md +99 -0
  310. package/templates/skills/git-hotfix/SKILL.md +223 -0
  311. package/templates/skills/git-main/SKILL.md +84 -0
  312. package/templates/skills/git-production/SKILL.md +165 -0
  313. package/templates/skills/git-staging/SKILL.md +112 -0
  314. package/templates/skills/scope-create/SKILL.md +81 -0
  315. package/templates/skills/scope-fix-review/SKILL.md +168 -0
  316. package/templates/skills/scope-implement/SKILL.md +110 -0
  317. package/templates/skills/scope-post-review/SKILL.md +144 -0
  318. package/templates/skills/scope-pre-review/SKILL.md +211 -0
  319. package/templates/skills/scope-verify/SKILL.md +201 -0
  320. package/templates/skills/session-init/SKILL.md +62 -0
  321. package/templates/skills/session-resume/SKILL.md +201 -0
  322. package/templates/skills/test-checks/SKILL.md +171 -0
  323. package/templates/skills/test-code-review/SKILL.md +252 -0
  324. package/tsconfig.json +25 -0
  325. package/vite.config.ts +38 -0
@@ -0,0 +1,452 @@
1
+ import express from 'express';
2
+ import { createServer } from 'http';
3
+ import { Server } from 'socket.io';
4
+ import path from 'path';
5
+ import fs from 'fs';
6
+ import { fileURLToPath } from 'url';
7
+ import { getDatabase, closeDatabase } from './database.js';
8
+ import { getConfig, resetConfig } from './config.js';
9
+ import { ScopeCache } from './services/scope-cache.js';
10
+ import { ScopeService } from './services/scope-service.js';
11
+ import { EventService } from './services/event-service.js';
12
+ import { GateService } from './services/gate-service.js';
13
+ import { DeployService } from './services/deploy-service.js';
14
+ import { SprintService } from './services/sprint-service.js';
15
+ import { SprintOrchestrator } from './services/sprint-orchestrator.js';
16
+ import { BatchOrchestrator } from './services/batch-orchestrator.js';
17
+ import { ReadinessService } from './services/readiness-service.js';
18
+ import { startScopeWatcher } from './watchers/scope-watcher.js';
19
+ import { startEventWatcher } from './watchers/event-watcher.js';
20
+ import { ensureDynamicProfiles } from './utils/terminal-launcher.js';
21
+ import { syncClaudeSessionsToDB } from './services/claude-session-service.js';
22
+ import { resolveStaleDispatches, resolveActiveDispatchesForScope, resolveDispatchesByPid, resolveDispatchesByDispatchId, linkPidToDispatch } from './utils/dispatch-utils.js';
23
+ import { createScopeRoutes } from './routes/scope-routes.js';
24
+ import { createDataRoutes } from './routes/data-routes.js';
25
+ import { createDispatchRoutes } from './routes/dispatch-routes.js';
26
+ import { createSprintRoutes } from './routes/sprint-routes.js';
27
+ import { createWorkflowRoutes } from './routes/workflow-routes.js';
28
+ import { createConfigRoutes } from './routes/config-routes.js';
29
+ import { createGitRoutes } from './routes/git-routes.js';
30
+ import { createVersionRoutes } from './routes/version-routes.js';
31
+ import { WorkflowService } from './services/workflow-service.js';
32
+ import { GitService } from './services/git-service.js';
33
+ import { GitHubService } from './services/github-service.js';
34
+ import { WorkflowEngine } from '../shared/workflow-engine.js';
35
+ import defaultWorkflow from '../shared/default-workflow.json' with { type: 'json' };
36
+ import type { WorkflowConfig } from '../shared/workflow-config.js';
37
+ import { createLogger, setLogLevel } from './utils/logger.js';
38
+ import type { LogLevel } from './utils/logger.js';
39
+
40
+ import type http from 'http';
41
+ import type Database from 'better-sqlite3';
42
+
43
+ // ─── Types ──────────────────────────────────────────────────
44
+
45
+ export interface ServerOverrides {
46
+ port?: number;
47
+ projectRoot?: string;
48
+ }
49
+
50
+ export interface ServerInstance {
51
+ app: express.Application;
52
+ io: Server;
53
+ db: Database.Database;
54
+ workflowEngine: WorkflowEngine;
55
+ httpServer: http.Server;
56
+ shutdown: () => Promise<void>;
57
+ }
58
+
59
+ // ─── Server Factory ─────────────────────────────────────────
60
+
61
+ export async function startServer(overrides?: ServerOverrides): Promise<ServerInstance> {
62
+ // Apply project root override before config loads
63
+ if (overrides?.projectRoot) {
64
+ process.env.ORBITAL_PROJECT_ROOT = overrides.projectRoot;
65
+ resetConfig();
66
+ }
67
+
68
+ const config = getConfig();
69
+ const envLevel = process.env.ORBITAL_LOG_LEVEL;
70
+ if (envLevel && ['debug', 'info', 'warn', 'error'].includes(envLevel)) {
71
+ setLogLevel(envLevel as LogLevel);
72
+ } else {
73
+ setLogLevel(config.logLevel);
74
+ }
75
+ const log = createLogger('server');
76
+ const port = overrides?.port ?? config.serverPort;
77
+
78
+ const workflowEngine = new WorkflowEngine(defaultWorkflow as WorkflowConfig);
79
+
80
+ // Generate shell manifest for bash hooks (config-driven lifecycle)
81
+ const MANIFEST_PATH = path.join(config.configDir, 'workflow-manifest.sh');
82
+ if (!fs.existsSync(config.configDir)) fs.mkdirSync(config.configDir, { recursive: true });
83
+ fs.writeFileSync(MANIFEST_PATH, workflowEngine.generateShellManifest(), 'utf-8');
84
+
85
+ const ICEBOX_DIR = path.join(config.scopesDir, 'icebox');
86
+ // Resolve path to the bundled default workflow config.
87
+ const __selfDir2 = path.dirname(fileURLToPath(import.meta.url));
88
+ const DEFAULT_CONFIG_PATH = path.resolve(__selfDir2, '../shared/default-workflow.json');
89
+
90
+ // Ensure icebox directory exists for idea files
91
+ if (!fs.existsSync(ICEBOX_DIR)) fs.mkdirSync(ICEBOX_DIR, { recursive: true });
92
+
93
+ const app = express();
94
+ const httpServer = createServer(app);
95
+
96
+ const io = new Server(httpServer, {
97
+ cors: {
98
+ origin: (origin, callback) => {
99
+ // Allow all localhost origins (dev tool, not production)
100
+ if (!origin || origin.startsWith('http://localhost:')) {
101
+ callback(null, true);
102
+ } else {
103
+ callback(new Error('CORS not allowed'));
104
+ }
105
+ },
106
+ methods: ['GET', 'POST'],
107
+ },
108
+ });
109
+
110
+ // Middleware
111
+ app.use(express.json());
112
+
113
+ // Initialize database
114
+ const db = getDatabase();
115
+
116
+ // Initialize services
117
+ const scopeCache = new ScopeCache();
118
+ const scopeService = new ScopeService(scopeCache, io, config.scopesDir, workflowEngine);
119
+ const eventService = new EventService(db, io);
120
+ const gateService = new GateService(db, io);
121
+ const deployService = new DeployService(db, io);
122
+ const sprintService = new SprintService(db, io, scopeService);
123
+ const sprintOrchestrator = new SprintOrchestrator(db, io, sprintService, scopeService, workflowEngine);
124
+ const batchOrchestrator = new BatchOrchestrator(db, io, sprintService, scopeService, workflowEngine);
125
+ const readinessService = new ReadinessService(scopeService, gateService, workflowEngine, config.projectRoot);
126
+ const workflowService = new WorkflowService(config.configDir, workflowEngine, config.scopesDir, DEFAULT_CONFIG_PATH);
127
+ workflowService.setSocketServer(io);
128
+
129
+ // Ensure in-memory engine reflects the actual active config (may differ from bundled default
130
+ // if the user applied a custom preset)
131
+ workflowEngine.reload(workflowService.getActive());
132
+ const gitService = new GitService(config.projectRoot, scopeCache);
133
+ const githubService = new GitHubService(config.projectRoot);
134
+
135
+ // Wire active-group guard into scope service (blocks manual moves for scopes in active batches/sprints)
136
+ scopeService.setActiveGroupCheck((scopeId) => sprintService.getActiveGroupForScope(scopeId));
137
+
138
+ // ─── Event Wiring ──────────────────────────────────────────
139
+
140
+ function inferScopeStatus(
141
+ eventType: string,
142
+ scopeId: unknown,
143
+ data: Record<string, unknown>
144
+ ): void {
145
+ if (scopeId == null) return;
146
+ const id = Number(scopeId);
147
+ if (isNaN(id) || id <= 0) return;
148
+
149
+ // Don't infer status for icebox idea cards
150
+ const current = scopeService.getById(id);
151
+ if (current?.status === 'icebox') return;
152
+
153
+ const currentStatus = current?.status ?? '';
154
+ const result = workflowEngine.inferStatus(eventType, currentStatus, data);
155
+ if (result === null) return;
156
+
157
+ // Handle dispatch resolution (AGENT_COMPLETED with outcome)
158
+ if (typeof result === 'object' && 'dispatchResolution' in result) {
159
+ resolveActiveDispatchesForScope(
160
+ db, io, id,
161
+ result.resolution as 'completed' | 'failed',
162
+ );
163
+ return;
164
+ }
165
+
166
+ scopeService.updateStatus(id, result, 'event');
167
+ }
168
+
169
+ eventService.onIngest((eventType, scopeId, data) => {
170
+ // Handle SESSION_START: link PID to dispatch via dispatch_id env var
171
+ if (eventType === 'SESSION_START' && typeof data.dispatch_id === 'string' && typeof data.pid === 'number') {
172
+ linkPidToDispatch(db, data.dispatch_id, data.pid);
173
+ log.info('SESSION_START: linked PID to dispatch', { pid: data.pid, dispatch_id: data.dispatch_id });
174
+ return;
175
+ }
176
+
177
+ // Handle SESSION_END: resolve dispatches by dispatch_id (preferred) or PID (fallback)
178
+ if (eventType === 'SESSION_END') {
179
+ let count = 0;
180
+ if (typeof data.dispatch_id === 'string') {
181
+ count = resolveDispatchesByDispatchId(db, io, data.dispatch_id);
182
+ if (count > 0) {
183
+ log.info('SESSION_END: resolved dispatches', { count, dispatch_id: data.dispatch_id });
184
+ }
185
+ }
186
+ // PID fallback for old hooks without dispatch_id
187
+ if (count === 0 && typeof data.pid === 'number') {
188
+ count = resolveDispatchesByPid(db, io, data.pid);
189
+ if (count > 0) {
190
+ log.info('SESSION_END: resolved dispatches by PID fallback', { count, pid: data.pid });
191
+ }
192
+ }
193
+ // Immediately resolve any batches/sprints whose session just ended,
194
+ // rather than waiting for the next stale-check interval
195
+ if (count > 0) {
196
+ batchOrchestrator.resolveStaleBatches();
197
+ }
198
+ return;
199
+ }
200
+
201
+ inferScopeStatus(eventType, scopeId, data);
202
+ });
203
+
204
+ scopeService.onStatusChange((scopeId, newStatus) => {
205
+ if (newStatus === 'dev') {
206
+ sprintOrchestrator.onScopeReachedDev(scopeId);
207
+ }
208
+ // Batch orchestrator tracks all status transitions (dev, staging, production)
209
+ batchOrchestrator.onScopeStatusChanged(scopeId, newStatus);
210
+ });
211
+
212
+ scopeService.onStatusChange((scopeId, newStatus) => {
213
+ if (workflowEngine.isTerminalStatus(newStatus)) {
214
+ resolveActiveDispatchesForScope(db, io, scopeId, 'completed');
215
+ }
216
+ });
217
+
218
+ // ─── Routes ────────────────────────────────────────────────
219
+
220
+ app.get('/api/orbital/health', (_req, res) => {
221
+ res.json({ status: 'ok', uptime: process.uptime(), timestamp: new Date().toISOString() });
222
+ });
223
+
224
+ // Serve dynamic config to the frontend
225
+ app.get('/api/orbital/config', (_req, res) => {
226
+ res.json({
227
+ projectName: config.projectName,
228
+ categories: config.categories,
229
+ agents: config.agents,
230
+ serverPort: config.serverPort,
231
+ clientPort: config.clientPort,
232
+ });
233
+ });
234
+
235
+ app.use('/api/orbital', createScopeRoutes({ db, io, scopeService, readinessService, projectRoot: config.projectRoot, engine: workflowEngine }));
236
+ app.use('/api/orbital', createDataRoutes({ db, io, gateService, deployService, engine: workflowEngine, projectRoot: config.projectRoot, inferScopeStatus }));
237
+ app.use('/api/orbital', createDispatchRoutes({ db, io, scopeService, projectRoot: config.projectRoot, engine: workflowEngine }));
238
+ app.use('/api/orbital', createSprintRoutes({ sprintService, sprintOrchestrator, batchOrchestrator }));
239
+ app.use('/api/orbital', createWorkflowRoutes({ workflowService, projectRoot: config.projectRoot }));
240
+ app.use('/api/orbital', createConfigRoutes({ projectRoot: config.projectRoot, workflowService, io }));
241
+ app.use('/api/orbital', createGitRoutes({ gitService, githubService, engine: workflowEngine }));
242
+ app.use('/api/orbital', createVersionRoutes({ io }));
243
+
244
+ // ─── Static File Serving (production) ───────────────────────
245
+
246
+ // Resolve the Vite-built frontend dist directory (server/ → ../dist).
247
+ const __selfDir = path.dirname(fileURLToPath(import.meta.url));
248
+ const distDir = path.resolve(__selfDir, '../dist');
249
+ if (fs.existsSync(path.join(distDir, 'index.html'))) {
250
+ app.use(express.static(distDir));
251
+ app.get('*', (req, res, next) => {
252
+ if (req.path.startsWith('/api/') || req.path.startsWith('/socket.io')) return next();
253
+ res.sendFile(path.join(distDir, 'index.html'));
254
+ });
255
+ } else {
256
+ // Dev mode: redirect root to Vite dev server
257
+ app.get('/', (_req, res) => res.redirect(`http://localhost:${config.clientPort}`));
258
+ }
259
+
260
+ // ─── Socket.io ──────────────────────────────────────────────
261
+
262
+ io.on('connection', (socket) => {
263
+ log.debug('Client connected', { socketId: socket.id });
264
+
265
+ socket.on('disconnect', () => {
266
+ log.debug('Client disconnected', { socketId: socket.id });
267
+ });
268
+ });
269
+
270
+ // ─── Startup ───────────────────────────────────────────────
271
+
272
+ // References for graceful shutdown
273
+ let scopeWatcher: ReturnType<typeof startScopeWatcher>;
274
+ let eventWatcher: ReturnType<typeof startEventWatcher>;
275
+ let batchRecoveryInterval: ReturnType<typeof setInterval>;
276
+ let staleCleanupInterval: ReturnType<typeof setInterval>;
277
+ let sessionSyncInterval: ReturnType<typeof setInterval>;
278
+ let gitPollInterval: ReturnType<typeof setInterval>;
279
+
280
+ const actualPort = await new Promise<number>((resolve, reject) => {
281
+ let attempt = 0;
282
+ const maxAttempts = 10;
283
+
284
+ httpServer.on('error', (err: NodeJS.ErrnoException) => {
285
+ if (err.code === 'EADDRINUSE' && attempt < maxAttempts) {
286
+ attempt++;
287
+ const nextPort = port + attempt;
288
+ log.warn('Port in use, trying next', { tried: port + attempt - 1, next: nextPort });
289
+ httpServer.listen(nextPort);
290
+ } else {
291
+ reject(new Error(`Failed to start server: ${err.message}`));
292
+ }
293
+ });
294
+
295
+ httpServer.on('listening', () => {
296
+ const addr = httpServer.address();
297
+ const listenPort = typeof addr === 'object' && addr ? addr.port : port;
298
+ resolve(listenPort);
299
+ });
300
+
301
+ httpServer.listen(port);
302
+ });
303
+
304
+ // ─── Post-listen initialization ────────────────────────────
305
+
306
+ // Sync scopes from filesystem on startup (populates in-memory cache)
307
+ const scopeCount = scopeService.syncFromFilesystem();
308
+
309
+ // Resolve stale dispatch events (terminal scopes + age-based)
310
+ const staleResolved = resolveStaleDispatches(db, io, scopeService, workflowEngine);
311
+ if (staleResolved > 0) {
312
+ log.info('Resolved stale dispatch events', { count: staleResolved });
313
+ }
314
+
315
+ // Write iTerm2 dispatch profiles (idempotent, fire-and-forget)
316
+ ensureDynamicProfiles(workflowEngine);
317
+
318
+ // Start file watchers
319
+ scopeWatcher = startScopeWatcher(config.scopesDir, scopeService);
320
+ eventWatcher = startEventWatcher(config.eventsDir, eventService);
321
+
322
+ // Recover any active sprints/batches from before server restart
323
+ sprintOrchestrator.recoverActiveSprints().catch(err => log.error('Sprint recovery failed', { error: err.message }));
324
+ batchOrchestrator.recoverActiveBatches().catch(err => log.error('Batch recovery failed', { error: err.message }));
325
+
326
+ // Resolve stale batches on startup (catches stuck dispatches from previous runs)
327
+ const staleBatchesResolved = batchOrchestrator.resolveStaleBatches();
328
+ if (staleBatchesResolved > 0) {
329
+ log.info('Resolved stale batches', { count: staleBatchesResolved });
330
+ }
331
+
332
+ // Poll active batch PIDs every 30s for two-phase completion (B-1)
333
+ batchRecoveryInterval = setInterval(() => {
334
+ batchOrchestrator.recoverActiveBatches().catch(err => log.error('Batch recovery failed', { error: err.message }));
335
+ }, 30_000);
336
+
337
+ // Periodic stale dispatch + batch cleanup (crash recovery — catches SIGKILL'd sessions)
338
+ staleCleanupInterval = setInterval(() => {
339
+ const count = resolveStaleDispatches(db, io, scopeService, workflowEngine);
340
+ if (count > 0) {
341
+ log.info('Periodic cleanup: resolved stale dispatches', { count });
342
+ }
343
+ const batchCount = batchOrchestrator.resolveStaleBatches();
344
+ if (batchCount > 0) {
345
+ log.info('Periodic cleanup: resolved stale batches', { count: batchCount });
346
+ }
347
+ }, 30_000);
348
+
349
+ // Sync frontmatter-derived sessions into DB (non-blocking)
350
+ syncClaudeSessionsToDB(db, scopeService).then((count) => {
351
+ log.info('Synced frontmatter sessions', { count });
352
+
353
+ // Purge legacy pattern-matched rows (no action = old regex system)
354
+ const purged = db.prepare(
355
+ "DELETE FROM sessions WHERE action IS NULL AND id LIKE 'claude-%'"
356
+ ).run();
357
+ if (purged.changes > 0) {
358
+ log.info('Purged legacy pattern-matched session rows', { count: purged.changes });
359
+ }
360
+ }).catch(err => log.error('Session sync failed', { error: err.message }));
361
+
362
+ // Re-sync every 5 minutes so new sessions appear without restart
363
+ sessionSyncInterval = setInterval(() => {
364
+ syncClaudeSessionsToDB(db, scopeService)
365
+ .then((count) => {
366
+ if (count > 0) io.emit('session:updated', { type: 'resync', count });
367
+ })
368
+ .catch(err => log.error('Session resync failed', { error: err.message }));
369
+ }, 5 * 60 * 1000);
370
+
371
+ // Poll git status every 10s — emit socket event on change
372
+ let lastGitHash = '';
373
+ gitPollInterval = setInterval(async () => {
374
+ try {
375
+ const hash = await gitService.getStatusHash();
376
+ if (lastGitHash && hash !== lastGitHash) {
377
+ gitService.clearCache();
378
+ io.emit('git:status:changed');
379
+ }
380
+ lastGitHash = hash;
381
+ } catch { /* ok */ }
382
+ }, 10_000);
383
+
384
+ // eslint-disable-next-line no-console
385
+ console.log(`
386
+ ╔══════════════════════════════════════════════════════╗
387
+ ║ Orbital Command ║
388
+ ║ ${config.projectName.padEnd(42)} ║
389
+ ║ ║
390
+ ║ >>> Open: http://localhost:${actualPort} <<< ║
391
+ ║ ║
392
+ ╠══════════════════════════════════════════════════════╣
393
+ ║ Scopes: ${String(scopeCount).padEnd(3)} loaded from filesystem ║
394
+ ║ API: http://localhost:${actualPort}/api/orbital/* ║
395
+ ║ Socket.io: ws://localhost:${actualPort} ║
396
+ ╚══════════════════════════════════════════════════════╝
397
+ `);
398
+
399
+ // ─── Graceful Shutdown ─────────────────────────────────────
400
+
401
+ let shuttingDown = false;
402
+ function shutdown(): Promise<void> {
403
+ if (shuttingDown) return Promise.resolve();
404
+ shuttingDown = true;
405
+ log.info('Shutting down');
406
+ scopeWatcher.close();
407
+ eventWatcher.close();
408
+ clearInterval(batchRecoveryInterval);
409
+ clearInterval(staleCleanupInterval);
410
+ clearInterval(sessionSyncInterval);
411
+ clearInterval(gitPollInterval);
412
+
413
+ return new Promise<void>((resolve) => {
414
+ const forceTimeout = setTimeout(() => {
415
+ closeDatabase();
416
+ resolve();
417
+ }, 2000);
418
+
419
+ io.close(() => {
420
+ clearTimeout(forceTimeout);
421
+ closeDatabase();
422
+ resolve();
423
+ });
424
+ });
425
+ }
426
+
427
+ return { app, io, db, workflowEngine, httpServer, shutdown };
428
+ }
429
+
430
+ // ─── Direct Execution (backward compat: tsx watch server/index.ts) ───
431
+
432
+ const isDirectRun = process.argv[1] && (
433
+ process.argv[1].endsWith('server/index.ts') ||
434
+ process.argv[1].endsWith('server/index.js') ||
435
+ process.argv[1].endsWith('server')
436
+ );
437
+
438
+ if (isDirectRun) {
439
+ startServer().then(({ shutdown }) => {
440
+ process.on('SIGINT', async () => {
441
+ await shutdown();
442
+ process.exit(0);
443
+ });
444
+ process.on('SIGTERM', async () => {
445
+ await shutdown();
446
+ process.exit(0);
447
+ });
448
+ }).catch((err) => {
449
+ createLogger('server').error('Failed to start server', { error: err.message });
450
+ process.exit(1);
451
+ });
452
+ }