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,66 @@
1
+ #!/bin/bash
2
+ # block-push.sh — PreToolUse:Bash hook
3
+ #
4
+ # Enforces stage-specific git restrictions using flag files
5
+ # managed by git-commit-guard.sh (PreToolUse:Skill):
6
+ # .block-push-active → blocks git push (during /git-commit)
7
+ # .implementing-session → blocks git commit/add (during /scope-implement)
8
+ set -euo pipefail
9
+
10
+ INPUT=$(cat)
11
+
12
+ echo "$INPUT" | jq empty 2>/dev/null || exit 0
13
+
14
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
15
+ [ -z "$TOOL_NAME" ] && TOOL_NAME="$CLAUDE_TOOL_NAME"
16
+ [ "$TOOL_NAME" != "Bash" ] && exit 0
17
+
18
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
19
+ PUSH_FLAG="$PROJECT_DIR/.claude/.block-push-active"
20
+ IMPL_FLAG="$PROJECT_DIR/.claude/.implementing-session"
21
+
22
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
23
+ [ -z "$COMMAND" ] && COMMAND="$INPUT"
24
+
25
+ HOOK_DIR="$(dirname "$0")"
26
+
27
+ # Build workflow-specific pipeline hint from manifest (only on block path)
28
+ _pipeline_after() {
29
+ local start="$1" found=false result=""
30
+ source "$HOOK_DIR/scope-helpers.sh" 2>/dev/null || return
31
+ for s in $WORKFLOW_STATUSES; do
32
+ if [ "$found" = true ]; then
33
+ result="${result:+$result → }$s"
34
+ fi
35
+ [ "$s" = "$start" ] && found=true
36
+ done
37
+ echo "$result"
38
+ }
39
+
40
+ # Block git push during /git-commit
41
+ if [ -f "$PUSH_FLAG" ]; then
42
+ if echo "$COMMAND" | grep -qE '(^|[;&|]\s*)git\s+push'; then
43
+ "$HOOK_DIR/orbital-emit.sh" VIOLATION '{"rule":"block-push","outcome":"blocked"}' 2>/dev/null || true
44
+ REMAINING=$(_pipeline_after "completed")
45
+ echo "BLOCKED: /git-commit only commits locally — it does not push to remote."
46
+ echo ""
47
+ echo "Your job is done once the commit is created. Do not push."
48
+ echo "The next workflow step pushes to: ${REMAINING:-the next stage}."
49
+ exit 2
50
+ fi
51
+ fi
52
+
53
+ # Block git commit/add during /scope-implement
54
+ if [ -f "$IMPL_FLAG" ]; then
55
+ if echo "$COMMAND" | grep -qE '(^|[;&|]\s*)git\s+(commit|add)'; then
56
+ "$HOOK_DIR/orbital-emit.sh" VIOLATION '{"rule":"block-commit-implementing","outcome":"blocked"}' 2>/dev/null || true
57
+ REMAINING=$(_pipeline_after "implementing")
58
+ echo "BLOCKED: Implementing sessions must not commit."
59
+ echo ""
60
+ echo "Code changes stay uncommitted until the review pipeline handles them."
61
+ echo "Remaining pipeline: ${REMAINING:-review → completed}."
62
+ exit 2
63
+ fi
64
+ fi
65
+
66
+ exit 0
@@ -0,0 +1,61 @@
1
+ #!/bin/bash
2
+ #
3
+ # Claude Code PreToolUse Hook: Block Dangerous Workarounds
4
+ #
5
+ # Blocks two critical patterns that bypass safety:
6
+ # 1. --no-verify (skips pre-commit hooks)
7
+ # 2. Direct push to main (bypasses PR workflow)
8
+ #
9
+ set -euo pipefail
10
+
11
+ INPUT=$(cat)
12
+ echo "$INPUT" | jq empty 2>/dev/null || exit 0
13
+
14
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
15
+ [ -z "$TOOL_NAME" ] && TOOL_NAME="$CLAUDE_TOOL_NAME"
16
+
17
+ # Only process Bash tool calls
18
+ [ "$TOOL_NAME" != "Bash" ] && exit 0
19
+
20
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
21
+ [ -z "$COMMAND" ] && COMMAND="$INPUT"
22
+
23
+ HOOK_DIR="$(dirname "$0")"
24
+
25
+ # Override mechanism: OVERRIDE_RULE="rule-name:reason"
26
+ # When set, logs the override and allows the command through
27
+ if [ -n "${OVERRIDE_RULE:-}" ]; then
28
+ RULE_NAME="${OVERRIDE_RULE%%:*}"
29
+ REASON="${OVERRIDE_RULE#*:}"
30
+ OVERRIDE_DATA=$(jq -n --arg rule "$RULE_NAME" --arg reason "$REASON" --arg outcome "overridden" '{rule: $rule, reason: $reason, outcome: $outcome}')
31
+ "$HOOK_DIR/orbital-emit.sh" OVERRIDE "$OVERRIDE_DATA"
32
+ echo "OVERRIDE: Rule '$RULE_NAME' overridden (reason: $REASON)"
33
+ exit 0
34
+ fi
35
+
36
+ # Pattern 1: --no-verify flag
37
+ if echo "$COMMAND" | grep -qE "\-\-no-verify"; then
38
+ "$HOOK_DIR/orbital-emit.sh" VIOLATION '{"rule":"no-verify","pattern":"--no-verify","outcome":"blocked"}'
39
+ echo "BLOCKED: Attempting to skip verification hooks"
40
+ echo ""
41
+ echo "Fix the failing checks instead:"
42
+ echo " cd backend && npm run type-check"
43
+ echo " cd backend && npm run lint"
44
+ echo ""
45
+ echo "If a check is genuinely wrong, discuss with the user."
46
+ exit 2
47
+ fi
48
+
49
+ # Pattern 2: Direct push to main (covers: git push origin main, git push --force origin main, HEAD:main)
50
+ if echo "$COMMAND" | grep -qE 'git push\b.*\bmain\b|HEAD:main'; then
51
+ "$HOOK_DIR/orbital-emit.sh" VIOLATION '{"rule":"push-main","pattern":"push to main","outcome":"blocked"}'
52
+ echo "BLOCKED: Direct push to main is forbidden"
53
+ echo ""
54
+ echo "Use /git-commit to route to the proper workflow:"
55
+ echo " - Creates feature branch if needed"
56
+ echo " - Opens PR to staging (not main)"
57
+ echo " - Ensures CI runs before merge"
58
+ exit 2
59
+ fi
60
+
61
+ exit 0
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+ # blocker-check.sh — Note unresolved blockers when advancing scope status
3
+ # Trigger: PreToolUse:Edit (scope file with status change)
4
+ # Nudge-style: always exits 0
5
+ set -e
6
+
7
+ INPUT=$(cat)
8
+ echo "$INPUT" | jq empty 2>/dev/null || exit 0
9
+
10
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
11
+ NEW_STRING=$(echo "$INPUT" | jq -r '.tool_input.new_string // empty')
12
+
13
+ source "$(dirname "$0")/scope-helpers.sh"
14
+ is_scope_file "$FILE_PATH" || exit 0
15
+ echo "$NEW_STRING" | grep -qiE "status:.*implementing|status:.*backlog|In Progress" || exit 0
16
+
17
+ # Count BLOCKER references (exclude resolved ones)
18
+ [ -f "$FILE_PATH" ] || exit 0
19
+ BLOCKERS=$(grep -c "^- \[B-" "$FILE_PATH" || echo "0")
20
+
21
+ if [ "$BLOCKERS" -gt 0 ]; then
22
+ echo ""
23
+ echo "🚧 $BLOCKERS blocker(s) found in $(basename "$FILE_PATH")"
24
+ echo " Verify all are resolved before advancing status."
25
+ echo ""
26
+ fi
27
+
28
+ exit 0
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+ # completion-checklist.sh — Block completion when Definition of Done items are unchecked
3
+ # Trigger: PreToolUse:Edit (scope status → complete)
4
+ # Blocking: exits 2 when unchecked DoD items found
5
+ set -e
6
+
7
+ INPUT=$(cat)
8
+ echo "$INPUT" | jq empty 2>/dev/null || exit 0
9
+
10
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
11
+ NEW_STRING=$(echo "$INPUT" | jq -r '.tool_input.new_string // empty')
12
+
13
+ source "$(dirname "$0")/scope-helpers.sh"
14
+ is_scope_file "$FILE_PATH" || exit 0
15
+ echo "$NEW_STRING" | grep -qiE "status:.*complete" || exit 0
16
+
17
+ # Count unchecked DoD items
18
+ UNCHECKED=$(sed -n '/Definition of Done/,/^═/p' "$FILE_PATH" 2>/dev/null | grep -c "\- \[ \]")
19
+
20
+ if [ "${UNCHECKED:-0}" -gt 0 ]; then
21
+ echo "" >&2
22
+ echo "MUST_BLOCK: $UNCHECKED unchecked Definition of Done items in $(basename "$FILE_PATH")" >&2
23
+ echo " Check all items in the '## Definition of Done' section: - [x] item (checked) vs - [ ] item (unchecked)." >&2
24
+ echo "" >&2
25
+ exit 2
26
+ fi
27
+
28
+ exit 0
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+ # decision-capture.sh — Prompt to log decisions after user questions
3
+ # Trigger: PostToolUse:AskUserQuestion
4
+ # Nudge-style: always exits 0
5
+ set -e
6
+
7
+ source "$(dirname "$0")/scope-helpers.sh"
8
+ SCOPE=$(find_active_scope) || exit 0
9
+
10
+ echo ""
11
+ echo "📋 User decision made — consider logging in"
12
+ echo " PROCESS > Decisions & Reasoning of $(basename "$SCOPE")"
13
+ echo ""
14
+
15
+ exit 0
@@ -0,0 +1,27 @@
1
+ #!/bin/bash
2
+ # dependency-check.sh — Check blocked_by dependencies when advancing status
3
+ # Trigger: PreToolUse:Edit (scope file with status change)
4
+ # Nudge-style: always exits 0
5
+ set -e
6
+
7
+ INPUT=$(cat)
8
+ echo "$INPUT" | jq empty 2>/dev/null || exit 0
9
+
10
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
11
+ NEW_STRING=$(echo "$INPUT" | jq -r '.tool_input.new_string // empty')
12
+
13
+ source "$(dirname "$0")/scope-helpers.sh"
14
+ is_scope_file "$FILE_PATH" || exit 0
15
+ echo "$NEW_STRING" | grep -qiE "status:.*implementing|status:.*backlog" || exit 0
16
+
17
+ # Extract blocked_by from frontmatter
18
+ BLOCKED_BY=$(sed -n '2,/^---$/p' "$FILE_PATH" | grep "^blocked_by:" | sed 's/blocked_by:[[:space:]]*//' | tr -d '[]"')
19
+
20
+ if [ -n "$BLOCKED_BY" ] && [ "$BLOCKED_BY" != " " ]; then
21
+ echo ""
22
+ echo "🔗 Dependencies: blocked_by: $BLOCKED_BY"
23
+ echo " Verify blocking scopes are complete before starting."
24
+ echo ""
25
+ fi
26
+
27
+ exit 0
@@ -0,0 +1,31 @@
1
+ #!/bin/bash
2
+ #
3
+ # Claude Code SessionEnd Hook: Session Cleanup
4
+ #
5
+ # Emits SESSION_END event to Orbital dashboard so active dispatch indicators
6
+ # are cleared when a Claude session exits normally.
7
+ #
8
+ set -e
9
+
10
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
11
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12
+ PID="$PPID"
13
+
14
+ # Emit session end event to Orbital dashboard (non-blocking)
15
+ SESSION_DATA="{\"pid\":$PID"
16
+ # Include dispatch ID if this session was launched by Orbital dispatch
17
+ if [ -n "$ORBITAL_DISPATCH_ID" ]; then
18
+ SESSION_DATA="${SESSION_DATA},\"dispatch_id\":\"$ORBITAL_DISPATCH_ID\""
19
+ fi
20
+ SESSION_DATA="${SESSION_DATA}}"
21
+ "$SCRIPT_DIR/orbital-emit.sh" SESSION_END "$SESSION_DATA" 2>/dev/null &
22
+
23
+ # Clean up cached session ID file (new glob format + old format)
24
+ rm -f "$PROJECT_DIR/.claude/metrics/.session-ids/${PID}"-* 2>/dev/null
25
+ rm -f "$PROJECT_DIR/.claude/metrics/.session-ids/$PID" 2>/dev/null
26
+
27
+ # Clean up skill guard flags
28
+ rm -f "$PROJECT_DIR/.claude/.block-push-active" 2>/dev/null
29
+
30
+ # SessionEnd hooks must never block termination
31
+ exit 0
@@ -0,0 +1,37 @@
1
+ #!/bin/bash
2
+ # exploration-logger.sh — Remind to log exploration findings periodically
3
+ # Trigger: PostToolUse:Grep|Glob (search operations)
4
+ # Nudge-style: always exits 0
5
+ set -e
6
+
7
+ INPUT=$(cat)
8
+ echo "$INPUT" | jq empty 2>/dev/null || exit 0
9
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
10
+ [[ "$TOOL_NAME" == "Grep" || "$TOOL_NAME" == "Glob" ]] || exit 0
11
+
12
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
13
+ COUNTER_FILE="$PROJECT_DIR/.claude/metrics/.exploration-count"
14
+ mkdir -p "$(dirname "$COUNTER_FILE")"
15
+
16
+ # Atomic counter increment with flock
17
+ (
18
+ flock -x 200 2>/dev/null || true
19
+ COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo "0")
20
+ COUNT=$((COUNT + 1))
21
+ echo "$COUNT" > "$COUNTER_FILE"
22
+ ) 200>"${COUNTER_FILE}.lock"
23
+ rm -f "${COUNTER_FILE}.lock"
24
+ COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo "0")
25
+
26
+ # Remind every 25 searches
27
+ if [ $((COUNT % 25)) -eq 0 ]; then
28
+ source "$(dirname "$0")/scope-helpers.sh"
29
+ SCOPE=$(find_active_scope) || exit 0
30
+
31
+ echo ""
32
+ echo "🔍 $COUNT searches this session — log key findings in"
33
+ echo " PROCESS > Exploration Log of $(basename "$SCOPE")"
34
+ echo ""
35
+ fi
36
+
37
+ exit 0
@@ -0,0 +1,37 @@
1
+ #!/bin/bash
2
+ # files-changed-summary.sh — Show planned vs actual files before completion
3
+ # Trigger: PreToolUse:Edit (scope status → complete)
4
+ # Nudge-style: always exits 0
5
+ set -e
6
+
7
+ INPUT=$(cat)
8
+ echo "$INPUT" | jq empty 2>/dev/null || exit 0
9
+
10
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
11
+ NEW_STRING=$(echo "$INPUT" | jq -r '.tool_input.new_string // empty')
12
+
13
+ source "$(dirname "$0")/scope-helpers.sh"
14
+ is_scope_file "$FILE_PATH" || exit 0
15
+ echo "$NEW_STRING" | grep -qiE "status:.*complete" || exit 0
16
+
17
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
18
+
19
+ # Count file references in scope
20
+ PLANNED=$(grep -cE '`[^`]+\.(ts|tsx|js|jsx|css|sh)`' "$FILE_PATH" 2>/dev/null || echo "?")
21
+
22
+ # Count actual files changed since scope began (use baseCommit if available)
23
+ BASE_COMMIT=$(get_frontmatter "$FILE_PATH" "baseCommit" 2>/dev/null)
24
+ if [ -n "$BASE_COMMIT" ]; then
25
+ ACTUAL=$(cd "$PROJECT_DIR" && git diff --name-only "$BASE_COMMIT"...HEAD 2>/dev/null | wc -l | tr -d ' ')
26
+ else
27
+ ACTUAL=$(cd "$PROJECT_DIR" && git diff --name-only HEAD~5 2>/dev/null | wc -l | tr -d ' ')
28
+ fi
29
+
30
+ echo ""
31
+ echo "📂 Scope completion summary for $(basename "$FILE_PATH"):"
32
+ echo " Planned: ~$PLANNED file references in scope"
33
+ echo " Changed: ~$ACTUAL files in recent commits"
34
+ echo " Review PROCESS > Deviations from Spec if significant difference"
35
+ echo ""
36
+
37
+ exit 0
@@ -0,0 +1,49 @@
1
+ #!/bin/bash
2
+ # get-session-id.sh — Parallel-safe session UUID lookup
3
+ #
4
+ # Reads the session ID cached by init-session.sh at session start.
5
+ # Walks up the process tree from $PPID to find the session file,
6
+ # which handles callers nested at any depth (e.g. subagent shells).
7
+ #
8
+ # Usage (from skills via Bash tool):
9
+ # SESSION_UUID=$(bash .claude/hooks/get-session-id.sh)
10
+ #
11
+ # Exit codes:
12
+ # 0 — Success, UUID printed to stdout
13
+ # 1 — Session ID not found
14
+ set -e
15
+
16
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
17
+ SESSION_DIR="$PROJECT_DIR/.claude/metrics/.session-ids"
18
+
19
+ # Walk up the process tree checking each ancestor PID.
20
+ # init-session.sh keys on $PPID (Claude Code's PID), but callers
21
+ # may be nested deeper (e.g. Skill tool -> Bash tool -> this script).
22
+ CURRENT_PID=$PPID
23
+ VISITED=""
24
+ while [ "$CURRENT_PID" -gt 1 ] 2>/dev/null; do
25
+ # Cycle detection
26
+ case " $VISITED " in
27
+ *" $CURRENT_PID "*) break ;;
28
+ esac
29
+ VISITED="$VISITED $CURRENT_PID"
30
+
31
+ # New format: {PID}-{UUID}
32
+ for f in "$SESSION_DIR/${CURRENT_PID}"-*; do
33
+ if [ -f "$f" ]; then
34
+ cat "$f"
35
+ exit 0
36
+ fi
37
+ done
38
+ # Old format: just {PID}
39
+ if [ -f "$SESSION_DIR/$CURRENT_PID" ]; then
40
+ cat "$SESSION_DIR/$CURRENT_PID"
41
+ exit 0
42
+ fi
43
+ # Move to parent
44
+ CURRENT_PID=$(ps -o ppid= -p "$CURRENT_PID" 2>/dev/null | tr -d ' ')
45
+ [ -z "$CURRENT_PID" ] && break
46
+ done
47
+
48
+ echo "ERROR: No session ID found in process tree from PID $PPID. Was init-session.sh invoked?" >&2
49
+ exit 1
@@ -0,0 +1,34 @@
1
+ #!/bin/bash
2
+ # git-commit-guard.sh — PreToolUse:Skill hook
3
+ #
4
+ # Manages skill-scoped flag files so block-push.sh can enforce
5
+ # stage-specific restrictions:
6
+ # /git-commit → .block-push-active (blocks git push)
7
+ # /scope-implement → .implementing-session (blocks git commit/add)
8
+ # Clears flags when any other skill is invoked.
9
+ set -euo pipefail
10
+
11
+ INPUT=$(cat)
12
+ echo "$INPUT" | jq empty 2>/dev/null || exit 0
13
+
14
+ SKILL=$(echo "$INPUT" | jq -r '.tool_input.skill // empty')
15
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
16
+
17
+ PUSH_FLAG="$PROJECT_DIR/.claude/.block-push-active"
18
+ IMPL_FLAG="$PROJECT_DIR/.claude/.implementing-session"
19
+
20
+ # /git-commit → block pushes during commit skill
21
+ if [ "$SKILL" = "git-commit" ]; then
22
+ touch "$PUSH_FLAG"
23
+ else
24
+ rm -f "$PUSH_FLAG"
25
+ fi
26
+
27
+ # /scope-implement → block commits during implementing
28
+ if [ "$SKILL" = "scope-implement" ]; then
29
+ touch "$IMPL_FLAG"
30
+ else
31
+ rm -f "$IMPL_FLAG"
32
+ fi
33
+
34
+ exit 0
@@ -0,0 +1,93 @@
1
+ #!/bin/bash
2
+ #
3
+ # Claude Code SessionStart Hook: Session Initialization
4
+ #
5
+ set -e
6
+
7
+ INPUT=$(cat)
8
+ if echo "$INPUT" | jq empty 2>/dev/null; then
9
+ SOURCE=$(echo "$INPUT" | jq -r '.source // "startup"')
10
+ else
11
+ SOURCE="startup"
12
+ fi
13
+ [ -z "$SOURCE" ] && SOURCE="startup"
14
+
15
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
16
+
17
+ # ─── Cache session ID for parallel-safe lookup by skills ───
18
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
19
+ if [ -n "$SESSION_ID" ]; then
20
+ SESSION_DIR="$PROJECT_DIR/.claude/metrics/.session-ids"
21
+ mkdir -p "$SESSION_DIR"
22
+ printf '%s' "$SESSION_ID" > "$SESSION_DIR/${PPID}-${SESSION_ID}"
23
+ # Clean up stale session files older than 4h with PID liveness check
24
+ find "$SESSION_DIR" -type f -mmin +240 2>/dev/null | while IFS= read -r f; do
25
+ STALE_PID=$(basename "$f" | cut -d'-' -f1)
26
+ if [ -n "$STALE_PID" ] && ! kill -0 "$STALE_PID" 2>/dev/null; then
27
+ rm -f "$f"
28
+ fi
29
+ done
30
+ fi
31
+
32
+ # Clean stale markers from previous sessions
33
+ rm -f "$PROJECT_DIR/.claude/metrics/.scope-create-session"
34
+ rm -f "$PROJECT_DIR/.claude/metrics/.exploration-count"
35
+ rm -f "$PROJECT_DIR/.claude/metrics/.active-scope"
36
+
37
+ # Resolve project name from orbital.config.json or git repo name
38
+ PROJECT_NAME=""
39
+ if command -v jq >/dev/null 2>&1 && [ -f "$PROJECT_DIR/.claude/orbital.config.json" ]; then
40
+ PROJECT_NAME=$(jq -r '.projectName // empty' "$PROJECT_DIR/.claude/orbital.config.json" 2>/dev/null)
41
+ fi
42
+ [ -z "$PROJECT_NAME" ] && PROJECT_NAME=$(basename "$(git -C "$PROJECT_DIR" rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null) || true
43
+ [ -z "$PROJECT_NAME" ] && PROJECT_NAME="Project"
44
+
45
+ # Abbreviated banner for resumed/compacted sessions
46
+ if [ "$SOURCE" = "resume" ] || [ "$SOURCE" = "compact" ]; then
47
+ cat << EOF
48
+
49
+ ══════════════════════════════════════════════════════════════════════════════
50
+ ${PROJECT_NAME} SESSION RESUMED
51
+ ══════════════════════════════════════════════════════════════════════════════
52
+
53
+ Rules: .claude/quick/rules.md | Git: /git-commit | Test: /test-checks
54
+
55
+ ══════════════════════════════════════════════════════════════════════════════
56
+
57
+ EOF
58
+ exit 0
59
+ fi
60
+
61
+ # Full banner for new/cleared sessions
62
+ cat << EOF
63
+
64
+ ══════════════════════════════════════════════════════════════════════════════
65
+ ${PROJECT_NAME} SESSION INITIALIZED
66
+ ══════════════════════════════════════════════════════════════════════════════
67
+
68
+ KEY REFERENCES:
69
+
70
+ Entry point: .claude/INDEX.md
71
+ All rules: .claude/quick/rules.md
72
+ Anti-patterns: .claude/anti-patterns/dangerous-shortcuts.md
73
+
74
+ GIT WORKFLOW:
75
+
76
+ /git-commit → Commit work
77
+ /git-main → Push to main (or /git-staging, /git-production for Gitflow)
78
+
79
+ ══════════════════════════════════════════════════════════════════════════════
80
+
81
+ EOF
82
+
83
+ # Emit session start event to Orbital dashboard (non-blocking)
84
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
85
+ SESSION_DATA="{\"source\":\"$SOURCE\",\"pid\":$PPID"
86
+ # Include dispatch ID if this session was launched by Orbital dispatch
87
+ if [ -n "$ORBITAL_DISPATCH_ID" ]; then
88
+ SESSION_DATA="${SESSION_DATA},\"dispatch_id\":\"$ORBITAL_DISPATCH_ID\""
89
+ fi
90
+ SESSION_DATA="${SESSION_DATA}}"
91
+ "$SCRIPT_DIR/orbital-emit.sh" SESSION_START "$SESSION_DATA" 2>/dev/null &
92
+
93
+ exit 0
@@ -0,0 +1,79 @@
1
+ #!/bin/bash
2
+ # orbital-emit.sh — Emit events to Orbital dashboard via file-based bus
3
+ #
4
+ # Usage:
5
+ # orbital-emit EVENT_TYPE '{"key": "value"}'
6
+ # orbital-emit EVENT_TYPE '{"key": "value"}' --scope 78 --agent attacker --session abc123
7
+ #
8
+ # Events are written as JSON files to .claude/orbital-events/
9
+ # The Orbital server watches this directory and ingests them.
10
+ # If the server isn't running, events queue up and are processed on startup.
11
+ set -e
12
+
13
+ EVENT_TYPE="${1:?Usage: orbital-emit EVENT_TYPE '{\"key\": \"value\"}' [--scope N] [--agent NAME] [--session ID]}"
14
+ EVENT_DATA="${2:-'{}'}"
15
+ shift 2 2>/dev/null || shift 1
16
+
17
+ # Parse optional named arguments
18
+ SCOPE_ID=""
19
+ AGENT=""
20
+ SESSION_ID=""
21
+
22
+ while [[ $# -gt 0 ]]; do
23
+ case "$1" in
24
+ --scope) SCOPE_ID="$2"; shift 2 ;;
25
+ --agent) AGENT="$2"; shift 2 ;;
26
+ --session) SESSION_ID="$2"; shift 2 ;;
27
+ *) shift ;;
28
+ esac
29
+ done
30
+
31
+ TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
32
+ EVENT_ID=$(uuidgen 2>/dev/null | tr '[:upper:]' '[:lower:]' \
33
+ || cat /proc/sys/kernel/random/uuid 2>/dev/null \
34
+ || python3 -c 'import uuid; print(uuid.uuid4())' 2>/dev/null \
35
+ || echo "$(date +%s)-$$-$RANDOM")
36
+
37
+ # Find project root
38
+ if [ -n "$CLAUDE_PROJECT_DIR" ]; then
39
+ PROJECT_ROOT="$CLAUDE_PROJECT_DIR"
40
+ else
41
+ PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo ".")
42
+ fi
43
+
44
+ EVENTS_DIR="${PROJECT_ROOT}/.claude/orbital-events"
45
+ mkdir -p "$EVENTS_DIR"
46
+
47
+ # Validate inputs
48
+ [[ -n "$SCOPE_ID" && ! "$SCOPE_ID" =~ ^[0-9]+$ ]] && SCOPE_ID=""
49
+
50
+ # Validate EVENT_DATA is valid JSON; fall back to wrapping as string
51
+ if ! echo "$EVENT_DATA" | jq empty 2>/dev/null; then
52
+ EVENT_DATA=$(jq -n --arg d "$EVENT_DATA" '$d')
53
+ fi
54
+
55
+ # Build JSON with optional top-level fields using jq for safe escaping
56
+ EVENT_FILE="${EVENTS_DIR}/${EVENT_ID}.json"
57
+
58
+ JQ_ARGS=(
59
+ --arg id "$EVENT_ID"
60
+ --arg type "$EVENT_TYPE"
61
+ --argjson data "$EVENT_DATA"
62
+ --arg timestamp "$TIMESTAMP"
63
+ )
64
+ JQ_EXPR='{id: $id, type: $type, data: $data, timestamp: $timestamp}'
65
+
66
+ if [ -n "$SCOPE_ID" ]; then
67
+ JQ_ARGS+=(--argjson scope_id "$SCOPE_ID")
68
+ JQ_EXPR='{id: $id, type: $type, scope_id: $scope_id, data: $data, timestamp: $timestamp}'
69
+ fi
70
+ if [ -n "$AGENT" ]; then
71
+ JQ_ARGS+=(--arg agent "$AGENT")
72
+ JQ_EXPR=$(echo "$JQ_EXPR" | sed 's/}$/, agent: $agent}/')
73
+ fi
74
+ if [ -n "$SESSION_ID" ]; then
75
+ JQ_ARGS+=(--arg session_id "$SESSION_ID")
76
+ JQ_EXPR=$(echo "$JQ_EXPR" | sed 's/}$/, session_id: $session_id}/')
77
+ fi
78
+
79
+ jq -n "${JQ_ARGS[@]}" "$JQ_EXPR" > "$EVENT_FILE"