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,188 @@
1
+ #!/bin/bash
2
+ # scope-helpers.sh — Shared utilities for scope lifecycle hooks
3
+ #
4
+ # Source this file from other scope hooks:
5
+ # source "$(dirname "$0")/scope-helpers.sh"
6
+ set -e
7
+
8
+ # Check if a file path is a scope markdown file
9
+ is_scope_file() {
10
+ local fp="$1"
11
+ [[ "$fp" == *"scopes/"* && "$fp" == *.md ]]
12
+ }
13
+
14
+ SCOPE_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
15
+ SCOPE_IMPLEMENTING_DIR="$SCOPE_PROJECT_DIR/scopes/implementing"
16
+
17
+ # ─── Source workflow manifest (auto-generated by WorkflowEngine) ───
18
+ MANIFEST_FILE="$SCOPE_PROJECT_DIR/.claude/config/workflow-manifest.sh"
19
+ if [ -f "$MANIFEST_FILE" ]; then
20
+ source "$MANIFEST_FILE"
21
+ else
22
+ # Fallback defaults (matches new Default trunk workflow)
23
+ WORKFLOW_BRANCHING_MODE="trunk"
24
+ WORKFLOW_STATUSES="icebox planning backlog implementing review completed main"
25
+ WORKFLOW_DIR_STATUSES="icebox planning backlog implementing review completed main"
26
+ WORKFLOW_ENTRY_STATUS="icebox"
27
+ WORKFLOW_EDGES=(
28
+ "planning:backlog:reviewScope"
29
+ "backlog:implementing:implementScope"
30
+ "implementing:review:reviewGate"
31
+ "review:completed:commit"
32
+ "completed:main:pushToMain"
33
+ )
34
+ WORKFLOW_BRANCH_MAP=(
35
+ "main:completed:main:pushToMain"
36
+ )
37
+ WORKFLOW_COMMIT_BRANCHES="^(main|feat/|fix/|scope/|chore/)"
38
+ WORKFLOW_DIRECTION_ALIASES=()
39
+ fi
40
+
41
+ # Ensure defaults for variables the manifest may not define
42
+ WORKFLOW_COMMIT_BRANCHES="${WORKFLOW_COMMIT_BRANCHES:-^(main|feat/|fix/|scope/|chore/)}"
43
+ if [ -z "${WORKFLOW_DIRECTION_ALIASES+x}" ]; then
44
+ WORKFLOW_DIRECTION_ALIASES=()
45
+ fi
46
+
47
+ # Map status to directory name (manifest-driven)
48
+ status_to_dir() {
49
+ local scope_status="$1"
50
+ for s in $WORKFLOW_DIR_STATUSES; do
51
+ [ "$s" = "$scope_status" ] && echo "$scope_status" && return 0
52
+ done
53
+ echo "$WORKFLOW_ENTRY_STATUS"
54
+ }
55
+
56
+ # Move a scope file to the directory matching target_status
57
+ move_scope() {
58
+ local src="$1" target_status="$2"
59
+ local dir_name=$(status_to_dir "$target_status")
60
+ local target_dir="$SCOPE_PROJECT_DIR/scopes/$dir_name"
61
+ local filename=$(basename "$src")
62
+ mkdir -p "$target_dir"
63
+ if ! git mv "$src" "$target_dir/$filename" 2>/dev/null; then
64
+ echo "WARNING: git mv failed for $filename, using plain mv" >&2
65
+ mv "$src" "$target_dir/$filename"
66
+ fi
67
+ echo "$target_dir/$filename"
68
+ }
69
+
70
+ # Find scopes with a given status in a specific directory
71
+ find_scopes_by_status() {
72
+ local find_status="$1"
73
+ local dir="${2:-$SCOPE_IMPLEMENTING_DIR}"
74
+ [ -d "$dir" ] || return 1
75
+ for f in "$dir"/*.md; do
76
+ [ -f "$f" ] || continue
77
+ head -15 "$f" | grep -q "^status: $find_status" && echo "$f"
78
+ done
79
+ }
80
+
81
+ # Get the currently active scope (implementing > review > backlog > planning)
82
+ # Uses a session-scoped cache to avoid O(n) directory scans on every call.
83
+ # Call invalidate_active_scope_cache() after scope transitions.
84
+ find_active_scope() {
85
+ local cache_file="$SCOPE_PROJECT_DIR/.claude/metrics/.active-scope"
86
+ # Check cache: must exist, be < 60s old, and point to a real file
87
+ if [ -f "$cache_file" ]; then
88
+ local cached
89
+ cached=$(cat "$cache_file" 2>/dev/null)
90
+ if [ -n "$cached" ] && [ -f "$cached" ]; then
91
+ local cache_age
92
+ if [ "$(uname)" = "Darwin" ]; then
93
+ cache_age=$(( $(date +%s) - $(stat -f %m "$cache_file") ))
94
+ else
95
+ cache_age=$(( $(date +%s) - $(stat -c %Y "$cache_file") ))
96
+ fi
97
+ if [ "$cache_age" -lt 60 ]; then
98
+ echo "$cached"
99
+ return 0
100
+ fi
101
+ fi
102
+ fi
103
+
104
+ # Cache miss — scan directories
105
+ local scope
106
+ scope=$(find_scopes_by_status "implementing" "$SCOPE_IMPLEMENTING_DIR" | head -1)
107
+ [ -z "$scope" ] && scope=$(find_scopes_by_status "review" "$SCOPE_PROJECT_DIR/scopes/review" | head -1)
108
+ [ -z "$scope" ] && scope=$(find_scopes_by_status "backlog" "$SCOPE_PROJECT_DIR/scopes/backlog" | head -1)
109
+ [ -z "$scope" ] && scope=$(find_scopes_by_status "planning" "$SCOPE_PROJECT_DIR/scopes/planning" | head -1)
110
+
111
+ if [ -n "$scope" ]; then
112
+ mkdir -p "$(dirname "$cache_file")"
113
+ printf '%s' "$scope" > "$cache_file"
114
+ echo "$scope"
115
+ return 0
116
+ fi
117
+ return 1
118
+ }
119
+
120
+ # Invalidate the active scope cache (call after transitions)
121
+ invalidate_active_scope_cache() {
122
+ rm -f "$SCOPE_PROJECT_DIR/.claude/metrics/.active-scope"
123
+ }
124
+
125
+ # Get a YAML frontmatter field from a scope file
126
+ get_frontmatter() {
127
+ local file="$1" field="$2"
128
+ sed -n '2,/^---$/p' "$file" | grep "^$field:" | sed "s/^$field:[[:space:]]*//; s/^[\"']//; s/[\"']$//"
129
+ }
130
+
131
+ # Set a simple frontmatter field (top-level only, e.g. status, updated)
132
+ set_frontmatter() {
133
+ local file="$1" field="$2" value="$3"
134
+ local tmp="${file}.tmp.$$"
135
+ (
136
+ flock -x 200 2>/dev/null || true
137
+ sed "s/^${field}:.*/${field}: ${value}/" "$file" > "$tmp" && mv "$tmp" "$file"
138
+ ) 200>"${file}.lock"
139
+ rm -f "${file}.lock"
140
+ }
141
+
142
+ # Append a UUID to a sessions array key in frontmatter
143
+ # Handles inline YAML arrays: key: [uuid1, uuid2]
144
+ append_session_uuid() {
145
+ local file="$1" key="$2" uuid="$3"
146
+ local tmp="${file}.tmp.$$"
147
+ (
148
+ flock -x 200 2>/dev/null || true
149
+ awk -v key="$key" -v uuid="$uuid" '
150
+ BEGIN { found_sessions=0; found_key=0; added=0 }
151
+ /^sessions:/ { found_sessions=1 }
152
+ found_sessions && $0 ~ "^ " key ":" {
153
+ found_key=1
154
+ if (index($0, uuid) > 0) { added=1; print; next }
155
+ # Append UUID to existing array: [a, b] -> [a, b, uuid]
156
+ sub(/\]$/, ", " uuid "]")
157
+ added=1
158
+ }
159
+ # After sessions: block ends (next top-level key), insert key if not found
160
+ found_sessions && !found_key && /^[^ ]/ && !/^sessions:/ {
161
+ print " " key ": [" uuid "]"
162
+ found_key=1; added=1
163
+ }
164
+ { print }
165
+ END {
166
+ # If sessions: block was never found, or key never added
167
+ if (!found_sessions) {
168
+ print "sessions:"
169
+ print " " key ": [" uuid "]"
170
+ } else if (!added) {
171
+ print " " key ": [" uuid "]"
172
+ }
173
+ }
174
+ ' "$file" > "$tmp" && mv "$tmp" "$file"
175
+ ) 200>"${file}.lock"
176
+ rm -f "${file}.lock"
177
+ }
178
+
179
+ # Find scope file by numeric ID across all directories
180
+ find_scope_by_id() {
181
+ local id="$1"
182
+ local padded
183
+ local id_clean
184
+ id_clean=$(echo "$id" | sed 's/^0*//')
185
+ [ -z "$id_clean" ] && id_clean="0"
186
+ padded=$(printf '%03d' "$id_clean")
187
+ find "$SCOPE_PROJECT_DIR/scopes/" -name "${padded}-*.md" -o -name "${padded}[a-dX]-*.md" 2>/dev/null | head -1
188
+ }
@@ -0,0 +1,139 @@
1
+ #!/bin/bash
2
+ # scope-lifecycle-gate.sh — PreToolUse:Bash hook
3
+ #
4
+ # Intercepts git commit / git push / gh pr create and records session
5
+ # UUIDs on active scopes before the command executes.
6
+ #
7
+ # Session recording:
8
+ # git commit (on feature branch) → records "commit" session on active scope
9
+ # git push to staging / gh pr --base staging → transitions dev → staging (with BATCH_SCOPE_IDS)
10
+ # git push to main / gh pr --base main → transitions staging → production (with BATCH_SCOPE_IDS)
11
+ #
12
+ # NOTE: completed → dev auto-transition removed (2026-03-04).
13
+ # Use /git-dev to explicitly merge feature→dev.
14
+ #
15
+ # BATCH_SCOPE_IDS must be set (e.g. BATCH_SCOPE_IDS=093,094) for any
16
+ # transition to occur. Without it, a warning is printed and no files move.
17
+ #
18
+ # Exit codes:
19
+ # 0 — Allow the command to proceed
20
+ # 2 — Block (never used here; transitions are advisory)
21
+ set -euo pipefail
22
+
23
+ INPUT=$(cat)
24
+ echo "$INPUT" | jq empty 2>/dev/null || exit 0
25
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
26
+
27
+ # ─── Fast exit: only process git commit, git push, gh pr create ───
28
+ echo "$COMMAND" | grep -qE '^git (commit|push)|^gh pr create' || exit 0
29
+
30
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
31
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
32
+
33
+ # ─── Resolve session UUID (from hook input or process tree) ───
34
+ SESSION_UUID=$(echo "$INPUT" | jq -r '.session_id // empty')
35
+ if [ -z "$SESSION_UUID" ]; then
36
+ SESSION_UUID=$("$SCRIPT_DIR/get-session-id.sh" 2>/dev/null || true)
37
+ fi
38
+
39
+ source "$SCRIPT_DIR/scope-helpers.sh"
40
+
41
+ # ─── Determine current branch ───
42
+ BRANCH=$(cd "$PROJECT_DIR" && git branch --show-current 2>/dev/null || true)
43
+
44
+ # ─── git commit: record session on active scope ───
45
+ if echo "$COMMAND" | grep -qE '^git commit'; then
46
+ # Only on branches matching commit pattern from manifest
47
+ if [[ "$BRANCH" =~ $WORKFLOW_COMMIT_BRANCHES ]]; then
48
+ # Record session on active scopes AND review scopes
49
+ # Note: scopes are gitignored, so no git add needed
50
+ if [ -n "$SESSION_UUID" ]; then
51
+ ACTIVE_SCOPE=$(find_active_scope 2>/dev/null || true)
52
+ if [ -n "$ACTIVE_SCOPE" ] && [ -f "$ACTIVE_SCOPE" ]; then
53
+ append_session_uuid "$ACTIVE_SCOPE" "commit" "$SESSION_UUID"
54
+ fi
55
+ # Also record on all review scopes (for review→completed transitions)
56
+ REVIEW_DIR="$PROJECT_DIR/scopes/review"
57
+ if [ -d "$REVIEW_DIR" ]; then
58
+ for f in "$REVIEW_DIR"/*.md; do
59
+ [ -f "$f" ] || continue
60
+ append_session_uuid "$f" "commit" "$SESSION_UUID"
61
+ done
62
+ fi
63
+ fi
64
+
65
+ # NOTE: completed → dev auto-transition removed.
66
+ # Use /git-dev to explicitly merge feature→dev and transition scopes.
67
+ fi
68
+ exit 0
69
+ fi
70
+
71
+ # ─── git push / gh pr create: determine target branch ───
72
+ TARGET_BRANCH=""
73
+
74
+ if echo "$COMMAND" | grep -qE '^git push'; then
75
+ # Check for branch names mentioned in WORKFLOW_BRANCH_MAP
76
+ for mapping in "${WORKFLOW_BRANCH_MAP[@]}"; do
77
+ IFS=':' read -r map_branch map_from map_to map_skey <<< "$mapping"
78
+ if echo "$COMMAND" | grep -qE "\\b${map_branch}\\b"; then
79
+ TARGET_BRANCH="$map_branch"
80
+ break
81
+ fi
82
+ done
83
+ # Also check if current branch matches a mapped branch
84
+ if [ -z "$TARGET_BRANCH" ]; then
85
+ for mapping in "${WORKFLOW_BRANCH_MAP[@]}"; do
86
+ IFS=':' read -r map_branch map_from map_to map_skey <<< "$mapping"
87
+ if [ "$BRANCH" = "$map_branch" ]; then
88
+ TARGET_BRANCH="$map_branch"
89
+ break
90
+ fi
91
+ done
92
+ fi
93
+ elif echo "$COMMAND" | grep -qE '^gh pr create'; then
94
+ # gh pr create --base staging / --base main
95
+ TARGET_BRANCH=$(echo "$COMMAND" | grep -oE '\-\-base[[:space:]]+[a-z]+' | awk '{print $2}')
96
+ fi
97
+
98
+ # ─── Resolve target branch to transition via manifest ───
99
+ TRANSITION_FROM=""
100
+ TRANSITION_TO=""
101
+
102
+ for mapping in "${WORKFLOW_BRANCH_MAP[@]}"; do
103
+ IFS=':' read -r map_branch map_from map_to map_skey <<< "$mapping"
104
+ if [ "$TARGET_BRANCH" = "$map_branch" ]; then
105
+ TRANSITION_FROM="$map_from"
106
+ TRANSITION_TO="$map_to"
107
+ break
108
+ fi
109
+ done
110
+
111
+ # ─── Execute transition if mapped ───
112
+ if [ -n "$TRANSITION_FROM" ] && [ -n "$TRANSITION_TO" ]; then
113
+ if [ -n "$BATCH_SCOPE_IDS" ]; then
114
+ if ! echo "$BATCH_SCOPE_IDS" | grep -qE '^[0-9]+(,[0-9]+)*$'; then
115
+ echo "ERROR: BATCH_SCOPE_IDS contains invalid characters. Must be comma-separated integers with no spaces, e.g. BATCH_SCOPE_IDS=093,094,095" >&2
116
+ exit 1
117
+ fi
118
+ echo ""
119
+ echo "Batch: transitioning scopes [$BATCH_SCOPE_IDS] → $TRANSITION_TO..."
120
+ IFS=',' read -ra BATCH_IDS <<< "$BATCH_SCOPE_IDS"
121
+ for bid in "${BATCH_IDS[@]}"; do
122
+ bash "$SCRIPT_DIR/scope-transition.sh" --from "$TRANSITION_FROM" --to "$TRANSITION_TO" \
123
+ --scope "$bid" ${SESSION_UUID:+--session "$SESSION_UUID"}
124
+ done
125
+ echo ""
126
+ else
127
+ # Guard: require explicit BATCH_SCOPE_IDS to prevent accidental bulk transitions
128
+ SOURCE_DIR="$PROJECT_DIR/scopes/$TRANSITION_FROM"
129
+ if [ -d "$SOURCE_DIR" ]; then
130
+ SOURCE_COUNT=$(find "$SOURCE_DIR" -name '*.md' ! -name '_template.md' 2>/dev/null | wc -l | tr -d ' ')
131
+ if [ "$SOURCE_COUNT" -gt 0 ]; then
132
+ echo " ⚠️ $SOURCE_COUNT $TRANSITION_FROM scope(s) found but no BATCH_SCOPE_IDS set."
133
+ echo " Set BATCH_SCOPE_IDS=093,094 to transition specific scopes."
134
+ fi
135
+ fi
136
+ fi
137
+ fi
138
+
139
+ exit 0
@@ -0,0 +1,244 @@
1
+ #!/bin/bash
2
+ # scope-prepare.sh — One-shot scope file preparation
3
+ #
4
+ # Consolidates file lookup, ID assignment, template scaffolding,
5
+ # session recording, and gate cleanup into a single Bash call.
6
+ #
7
+ # Modes:
8
+ # --promote ID Promote icebox idea to planning (renumber + move + scaffold)
9
+ # --scaffold ID Apply template to existing planning scope
10
+ # --new Create brand new scope
11
+ #
12
+ # Options:
13
+ # --title "..." Scope title (required for --new)
14
+ # --desc "..." Description / problem statement
15
+ # --category "..." Category tag (default: TBD)
16
+ #
17
+ # Output: JSON to stdout
18
+ # Errors: to stderr
19
+ # Exit: 0=success, 1=arg error, 2=source not found, 3=template missing, 4=collision
20
+ set -e
21
+
22
+ HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
23
+ source "$HOOK_DIR/scope-helpers.sh"
24
+
25
+ # ─── Argument parsing ────────────────────────────────────────────
26
+ MODE=""
27
+ SOURCE_ID=""
28
+ TITLE=""
29
+ DESCRIPTION=""
30
+ CATEGORY="TBD"
31
+
32
+ while [[ $# -gt 0 ]]; do
33
+ case "$1" in
34
+ --promote) MODE="promote"; SOURCE_ID="$2"; shift 2 ;;
35
+ --scaffold) MODE="scaffold"; SOURCE_ID="$2"; shift 2 ;;
36
+ --new) MODE="new"; shift ;;
37
+ --title) TITLE="$2"; shift 2 ;;
38
+ --desc) DESCRIPTION="$2"; shift 2 ;;
39
+ --category) CATEGORY="$2"; shift 2 ;;
40
+ *) echo "Unknown argument: $1" >&2; exit 1 ;;
41
+ esac
42
+ done
43
+
44
+ if [ -z "$MODE" ]; then
45
+ echo "Usage: scope-prepare.sh --promote ID | --scaffold ID | --new --title \"...\"" >&2
46
+ exit 1
47
+ fi
48
+
49
+ if [ "$MODE" = "new" ] && [ -z "$TITLE" ]; then
50
+ echo "Error: --new requires --title" >&2
51
+ exit 1
52
+ fi
53
+
54
+ # ─── Session ID (inlined from get-session-id.sh) ────────────────
55
+ SESSION_ID=""
56
+ SESSION_DIR="$SCOPE_PROJECT_DIR/.claude/metrics/.session-ids"
57
+ if [ -d "$SESSION_DIR" ]; then
58
+ CURRENT_PID=$PPID
59
+ VISITED=""
60
+ while [ "$CURRENT_PID" -gt 1 ] 2>/dev/null; do
61
+ case " $VISITED " in *" $CURRENT_PID "*) break ;; esac
62
+ VISITED="$VISITED $CURRENT_PID"
63
+ for f in "$SESSION_DIR/${CURRENT_PID}"-*; do
64
+ if [ -f "$f" ]; then
65
+ SESSION_ID=$(cat "$f")
66
+ break 2
67
+ fi
68
+ done
69
+ if [ -f "$SESSION_DIR/$CURRENT_PID" ]; then
70
+ SESSION_ID=$(cat "$SESSION_DIR/$CURRENT_PID")
71
+ break
72
+ fi
73
+ CURRENT_PID=$(ps -o ppid= -p "$CURRENT_PID" 2>/dev/null | tr -d ' ')
74
+ [ -z "$CURRENT_PID" ] && break
75
+ done
76
+ fi
77
+
78
+ # ─── Resolve source file + extract metadata ─────────────────────
79
+ OLD_FILE=""
80
+ NOW_DATE=$(date +%Y-%m-%d)
81
+ NOW_TIME=$(date +%H:%M)
82
+ CREATED_DATE="$NOW_DATE"
83
+
84
+ case "$MODE" in
85
+ promote)
86
+ OLD_FILE=$(find_scope_by_id "$SOURCE_ID")
87
+ if [ -z "$OLD_FILE" ] || [ ! -f "$OLD_FILE" ]; then
88
+ echo "Error: Scope $SOURCE_ID not found" >&2
89
+ exit 2
90
+ fi
91
+ [ -z "$TITLE" ] && TITLE=$(get_frontmatter "$OLD_FILE" "title")
92
+ # Preserve original created date
93
+ orig_created=$(get_frontmatter "$OLD_FILE" "created")
94
+ [ -n "$orig_created" ] && CREATED_DATE="$orig_created"
95
+ # Extract description from body (everything after frontmatter closing ---)
96
+ if [ -z "$DESCRIPTION" ]; then
97
+ DESCRIPTION=$(awk 'BEGIN{fm=0} /^---$/{fm++; next} fm>=2{print}' "$OLD_FILE" | head -20 | sed '/^$/d' | head -5)
98
+ fi
99
+ # Get category if set
100
+ orig_cat=$(get_frontmatter "$OLD_FILE" "category")
101
+ [ -n "$orig_cat" ] && [ "$orig_cat" != "TBD" ] && CATEGORY="$orig_cat"
102
+ ;;
103
+
104
+ scaffold)
105
+ OLD_FILE=$(find_scope_by_id "$SOURCE_ID")
106
+ if [ -z "$OLD_FILE" ] || [ ! -f "$OLD_FILE" ]; then
107
+ echo "Error: Scope $SOURCE_ID not found" >&2
108
+ exit 2
109
+ fi
110
+ [ -z "$TITLE" ] && TITLE=$(get_frontmatter "$OLD_FILE" "title")
111
+ orig_created=$(get_frontmatter "$OLD_FILE" "created")
112
+ [ -n "$orig_created" ] && CREATED_DATE="$orig_created"
113
+ if [ -z "$DESCRIPTION" ]; then
114
+ DESCRIPTION=$(awk 'BEGIN{fm=0} /^---$/{fm++; next} fm>=2{print}' "$OLD_FILE" | head -20 | sed '/^$/d' | head -5)
115
+ fi
116
+ orig_cat=$(get_frontmatter "$OLD_FILE" "category")
117
+ [ -n "$orig_cat" ] && [ "$orig_cat" != "TBD" ] && CATEGORY="$orig_cat"
118
+ ;;
119
+ esac
120
+
121
+ # ─── Compute scope ID ───────────────────────────────────────────
122
+ compute_next_id() {
123
+ local max_id=0
124
+ if [ -d "$SCOPE_PROJECT_DIR/scopes" ]; then
125
+ for dir in "$SCOPE_PROJECT_DIR/scopes"/*/; do
126
+ [ -d "$dir" ] || continue
127
+ [ "$(basename "$dir")" = "icebox" ] && continue
128
+ for f in "$dir"*.md; do
129
+ [ -f "$f" ] || continue
130
+ local num
131
+ num=$(basename "$f" | grep -oE '^[0-9]+' | sed 's/^0*//')
132
+ [ -z "$num" ] && continue
133
+ [ "$num" -gt "$max_id" ] 2>/dev/null && max_id=$num
134
+ done
135
+ done
136
+ fi
137
+ echo $((max_id + 1))
138
+ }
139
+
140
+ case "$MODE" in
141
+ promote)
142
+ SCOPE_ID=$(compute_next_id)
143
+ ;;
144
+ scaffold)
145
+ # Use existing ID from frontmatter
146
+ SCOPE_ID=$(get_frontmatter "$OLD_FILE" "id" | sed 's/^0*//')
147
+ [ -z "$SCOPE_ID" ] && SCOPE_ID=$(basename "$OLD_FILE" | grep -oE '^[0-9]+' | sed 's/^0*//')
148
+ ;;
149
+ new)
150
+ SCOPE_ID=$(compute_next_id)
151
+ ;;
152
+ esac
153
+
154
+ PADDED_ID=$(printf '%03d' "$SCOPE_ID")
155
+
156
+ # ─── Build slug + paths ─────────────────────────────────────────
157
+ SLUG=$(printf '%s' "$TITLE" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g; s/--*/-/g; s/^-//; s/-$//' | cut -c1-60)
158
+ PLANNING_DIR="$SCOPE_PROJECT_DIR/scopes/planning"
159
+ mkdir -p "$PLANNING_DIR"
160
+ NEW_FILE="$PLANNING_DIR/${PADDED_ID}-${SLUG}.md"
161
+
162
+ # For scaffold mode, the target IS the existing file (possibly same path)
163
+ if [ "$MODE" = "scaffold" ]; then
164
+ NEW_FILE="$OLD_FILE"
165
+ fi
166
+
167
+ # Collision check (promote/new only)
168
+ if [ "$MODE" != "scaffold" ] && [ -f "$NEW_FILE" ]; then
169
+ echo "Error: Target file already exists: $NEW_FILE" >&2
170
+ exit 4
171
+ fi
172
+
173
+ # ─── Template scaffolding ───────────────────────────────────────
174
+ TEMPLATE="$SCOPE_PROJECT_DIR/scopes/_template.md"
175
+ if [ ! -f "$TEMPLATE" ]; then
176
+ echo "Error: Template not found: $TEMPLATE (run 'orbital init' first)" >&2
177
+ exit 3
178
+ fi
179
+
180
+ # Escape description for sed replacement (handle &, /, \, newlines)
181
+ ESCAPED_DESC=$(printf '%s' "$DESCRIPTION" | head -1 | sed 's/[&/\]/\\&/g')
182
+ [ -z "$ESCAPED_DESC" ] && ESCAPED_DESC="[Problem statement - what's broken or needed]"
183
+
184
+ # Escape title for sed
185
+ ESCAPED_TITLE=$(printf '%s' "$TITLE" | sed 's/[&/\]/\\&/g; s/"/\\"/g')
186
+
187
+ # Build sessions line
188
+ if [ -n "$SESSION_ID" ]; then
189
+ SESSIONS_REPLACEMENT="sessions:\\
190
+ createScope: [$SESSION_ID]"
191
+ else
192
+ SESSIONS_REPLACEMENT="sessions: {}"
193
+ fi
194
+
195
+ # Apply template substitutions
196
+ # Note: some template lines have trailing YAML comments (e.g. "category: "TBD" # ..."),
197
+ # so we match the value portion without anchoring to end-of-line.
198
+ sed \
199
+ -e "s/^id: NNN/id: $PADDED_ID/" \
200
+ -e "s/^title: \"Scope Title\"/title: \"$ESCAPED_TITLE\"/" \
201
+ -e "s/^category: \"TBD\".*/category: \"$CATEGORY\"/" \
202
+ -e "s/^created: YYYY-MM-DD/created: $CREATED_DATE/" \
203
+ -e "s/^updated: YYYY-MM-DD/updated: $NOW_DATE/" \
204
+ -e "s/^sessions: {}.*/$SESSIONS_REPLACEMENT/" \
205
+ -e "s/# Scope NNN: Title/# Scope $PADDED_ID: $ESCAPED_TITLE/" \
206
+ -e "s/YYYY-MM-DD HH:MM/$NOW_DATE $NOW_TIME/g" \
207
+ -e "s/Scope created$/Scope created via \/scope-create/" \
208
+ -e "s/\[Problem statement - what's broken or needed\]/$ESCAPED_DESC/" \
209
+ -e "s/\[What prompted this exploration\]/$ESCAPED_DESC/" \
210
+ "$TEMPLATE" > "$NEW_FILE"
211
+
212
+ # ─── Cleanup old file (promote only) ────────────────────────────
213
+ if [ "$MODE" = "promote" ] && [ -n "$OLD_FILE" ] && [ "$OLD_FILE" != "$NEW_FILE" ]; then
214
+ rm -f "$OLD_FILE"
215
+ fi
216
+
217
+ # ─── Gate lifecycle: remove marker + emit event ─────────────────
218
+ MARKER="$SCOPE_PROJECT_DIR/.claude/metrics/.scope-create-session"
219
+ rm -f "$MARKER"
220
+
221
+ # Emit event in background (non-blocking)
222
+ "$HOOK_DIR/orbital-emit.sh" SCOPE_GATE_LIFTED \
223
+ "{\"scope_file\":\"$NEW_FILE\",\"id\":$SCOPE_ID,\"mode\":\"$MODE\"}" \
224
+ --scope "$SCOPE_ID" --session "$SESSION_ID" 2>/dev/null &
225
+
226
+ # ─── JSON output ────────────────────────────────────────────────
227
+ # Compute relative path
228
+ REL_PATH="${NEW_FILE#"$SCOPE_PROJECT_DIR/"}"
229
+
230
+ # Manual JSON construction (no jq dependency)
231
+ printf '{"id":"%s","path":"%s","title":"%s","description":"%s","session_id":"%s","category":"%s","mode":"%s"}\n' \
232
+ "$PADDED_ID" \
233
+ "$REL_PATH" \
234
+ "$(printf '%s' "$TITLE" | sed 's/"/\\"/g')" \
235
+ "$(printf '%s' "$DESCRIPTION" | head -1 | sed 's/"/\\"/g')" \
236
+ "$SESSION_ID" \
237
+ "$CATEGORY" \
238
+ "$MODE"
239
+
240
+ # Print reminder to stderr (visible to Claude but not parsed as JSON)
241
+ echo "" >&2
242
+ echo "Scope document written. Write gate lifted." >&2
243
+ echo "Remember: STOP here. Implementation is a separate session:" >&2
244
+ echo " /scope-implement $PADDED_ID" >&2