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.
- package/LICENSE +21 -0
- package/README.md +396 -0
- package/bin/orbital.js +362 -0
- package/dist/assets/WorkflowVisualizer-BZ21PIIF.js +84 -0
- package/dist/assets/WorkflowVisualizer-BZV40eAE.css +1 -0
- package/dist/assets/charts-D__PA1zp.js +72 -0
- package/dist/assets/index-D1G6i0nS.css +1 -0
- package/dist/assets/index-DpItvKpf.js +419 -0
- package/dist/assets/ui-BvF022GT.js +53 -0
- package/dist/assets/vendor-Dzv9lrRc.js +59 -0
- package/dist/index.html +19 -0
- package/dist/scanner-sweep.png +0 -0
- package/dist/server/server/adapters/index.js +34 -0
- package/dist/server/server/adapters/iterm2-adapter.js +29 -0
- package/dist/server/server/adapters/subprocess-adapter.js +21 -0
- package/dist/server/server/adapters/terminal-adapter.js +1 -0
- package/dist/server/server/config.js +156 -0
- package/dist/server/server/database.js +90 -0
- package/dist/server/server/index.js +372 -0
- package/dist/server/server/init.js +811 -0
- package/dist/server/server/parsers/event-parser.js +64 -0
- package/dist/server/server/parsers/scope-parser.js +188 -0
- package/dist/server/server/routes/config-routes.js +163 -0
- package/dist/server/server/routes/data-routes.js +461 -0
- package/dist/server/server/routes/dispatch-routes.js +215 -0
- package/dist/server/server/routes/git-routes.js +92 -0
- package/dist/server/server/routes/scope-routes.js +215 -0
- package/dist/server/server/routes/sprint-routes.js +116 -0
- package/dist/server/server/routes/version-routes.js +130 -0
- package/dist/server/server/routes/workflow-routes.js +185 -0
- package/dist/server/server/schema.js +90 -0
- package/dist/server/server/services/batch-orchestrator.js +253 -0
- package/dist/server/server/services/claude-session-service.js +352 -0
- package/dist/server/server/services/config-service.js +132 -0
- package/dist/server/server/services/deploy-service.js +51 -0
- package/dist/server/server/services/event-service.js +63 -0
- package/dist/server/server/services/gate-service.js +83 -0
- package/dist/server/server/services/git-service.js +309 -0
- package/dist/server/server/services/github-service.js +145 -0
- package/dist/server/server/services/readiness-service.js +184 -0
- package/dist/server/server/services/scope-cache.js +72 -0
- package/dist/server/server/services/scope-service.js +424 -0
- package/dist/server/server/services/sprint-orchestrator.js +312 -0
- package/dist/server/server/services/sprint-service.js +293 -0
- package/dist/server/server/services/workflow-service.js +397 -0
- package/dist/server/server/utils/cc-hooks-parser.js +49 -0
- package/dist/server/server/utils/dispatch-utils.js +305 -0
- package/dist/server/server/utils/logger.js +86 -0
- package/dist/server/server/utils/terminal-launcher.js +388 -0
- package/dist/server/server/utils/worktree-manager.js +98 -0
- package/dist/server/server/watchers/event-watcher.js +81 -0
- package/dist/server/server/watchers/scope-watcher.js +33 -0
- package/dist/server/shared/api-types.js +5 -0
- package/dist/server/shared/default-workflow.json +616 -0
- package/dist/server/shared/workflow-config.js +44 -0
- package/dist/server/shared/workflow-engine.js +353 -0
- package/index.html +15 -0
- package/package.json +110 -0
- package/postcss.config.js +6 -0
- package/schemas/orbital.config.schema.json +83 -0
- package/scripts/postinstall.js +24 -0
- package/scripts/start.sh +20 -0
- package/server/adapters/index.ts +41 -0
- package/server/adapters/iterm2-adapter.ts +37 -0
- package/server/adapters/subprocess-adapter.ts +25 -0
- package/server/adapters/terminal-adapter.ts +24 -0
- package/server/config.ts +234 -0
- package/server/database.ts +107 -0
- package/server/index.ts +452 -0
- package/server/init.ts +891 -0
- package/server/parsers/event-parser.ts +74 -0
- package/server/parsers/scope-parser.ts +240 -0
- package/server/routes/config-routes.ts +182 -0
- package/server/routes/data-routes.ts +548 -0
- package/server/routes/dispatch-routes.ts +275 -0
- package/server/routes/git-routes.ts +112 -0
- package/server/routes/scope-routes.ts +262 -0
- package/server/routes/sprint-routes.ts +142 -0
- package/server/routes/version-routes.ts +156 -0
- package/server/routes/workflow-routes.ts +198 -0
- package/server/schema.ts +90 -0
- package/server/services/batch-orchestrator.ts +286 -0
- package/server/services/claude-session-service.ts +441 -0
- package/server/services/config-service.ts +151 -0
- package/server/services/deploy-service.ts +98 -0
- package/server/services/event-service.ts +98 -0
- package/server/services/gate-service.ts +126 -0
- package/server/services/git-service.ts +391 -0
- package/server/services/github-service.ts +183 -0
- package/server/services/readiness-service.ts +250 -0
- package/server/services/scope-cache.ts +81 -0
- package/server/services/scope-service.ts +476 -0
- package/server/services/sprint-orchestrator.ts +361 -0
- package/server/services/sprint-service.ts +415 -0
- package/server/services/workflow-service.ts +461 -0
- package/server/utils/cc-hooks-parser.ts +70 -0
- package/server/utils/dispatch-utils.ts +395 -0
- package/server/utils/logger.ts +109 -0
- package/server/utils/terminal-launcher.ts +462 -0
- package/server/utils/worktree-manager.ts +104 -0
- package/server/watchers/event-watcher.ts +100 -0
- package/server/watchers/scope-watcher.ts +38 -0
- package/shared/api-types.ts +20 -0
- package/shared/default-workflow.json +616 -0
- package/shared/workflow-config.ts +170 -0
- package/shared/workflow-engine.ts +427 -0
- package/src/App.tsx +33 -0
- package/src/components/AgentBadge.tsx +40 -0
- package/src/components/BatchPreflightModal.tsx +115 -0
- package/src/components/CardDisplayToggle.tsx +74 -0
- package/src/components/ColumnHeaderActions.tsx +55 -0
- package/src/components/ColumnMenu.tsx +99 -0
- package/src/components/DeployHistory.tsx +141 -0
- package/src/components/DispatchModal.tsx +164 -0
- package/src/components/DispatchPopover.tsx +139 -0
- package/src/components/DragOverlay.tsx +25 -0
- package/src/components/DriftSidebar.tsx +140 -0
- package/src/components/EnvironmentStrip.tsx +88 -0
- package/src/components/ErrorBoundary.tsx +62 -0
- package/src/components/FilterChip.tsx +105 -0
- package/src/components/GateIndicator.tsx +33 -0
- package/src/components/IdeaDetailModal.tsx +190 -0
- package/src/components/IdeaFormDialog.tsx +113 -0
- package/src/components/KanbanColumn.tsx +201 -0
- package/src/components/MarkdownRenderer.tsx +114 -0
- package/src/components/NeonGrid.tsx +128 -0
- package/src/components/PromotionQueue.tsx +89 -0
- package/src/components/ScopeCard.tsx +234 -0
- package/src/components/ScopeDetailModal.tsx +255 -0
- package/src/components/ScopeFilterBar.tsx +152 -0
- package/src/components/SearchInput.tsx +102 -0
- package/src/components/SessionPanel.tsx +335 -0
- package/src/components/SprintContainer.tsx +303 -0
- package/src/components/SprintDependencyDialog.tsx +78 -0
- package/src/components/SprintPreflightModal.tsx +138 -0
- package/src/components/StatusBar.tsx +168 -0
- package/src/components/SwimCell.tsx +67 -0
- package/src/components/SwimLaneRow.tsx +94 -0
- package/src/components/SwimlaneBoardView.tsx +108 -0
- package/src/components/VersionBadge.tsx +139 -0
- package/src/components/ViewModeSelector.tsx +114 -0
- package/src/components/config/AgentChip.tsx +53 -0
- package/src/components/config/AgentCreateDialog.tsx +321 -0
- package/src/components/config/AgentEditor.tsx +175 -0
- package/src/components/config/DirectoryTree.tsx +582 -0
- package/src/components/config/FileEditor.tsx +550 -0
- package/src/components/config/HookChip.tsx +50 -0
- package/src/components/config/StageCard.tsx +198 -0
- package/src/components/config/TransitionZone.tsx +173 -0
- package/src/components/config/UnifiedWorkflowPipeline.tsx +216 -0
- package/src/components/config/WorkflowPipeline.tsx +161 -0
- package/src/components/source-control/BranchList.tsx +93 -0
- package/src/components/source-control/BranchPanel.tsx +105 -0
- package/src/components/source-control/CommitLog.tsx +100 -0
- package/src/components/source-control/CommitRow.tsx +47 -0
- package/src/components/source-control/GitHubPanel.tsx +110 -0
- package/src/components/source-control/GitHubSetupGuide.tsx +52 -0
- package/src/components/source-control/GitOverviewBar.tsx +101 -0
- package/src/components/source-control/PullRequestList.tsx +69 -0
- package/src/components/source-control/WorktreeList.tsx +80 -0
- package/src/components/ui/badge.tsx +41 -0
- package/src/components/ui/button.tsx +55 -0
- package/src/components/ui/card.tsx +78 -0
- package/src/components/ui/dialog.tsx +94 -0
- package/src/components/ui/popover.tsx +33 -0
- package/src/components/ui/scroll-area.tsx +54 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/tabs.tsx +52 -0
- package/src/components/ui/toggle-switch.tsx +35 -0
- package/src/components/ui/tooltip.tsx +27 -0
- package/src/components/workflow/AddEdgeDialog.tsx +217 -0
- package/src/components/workflow/AddListDialog.tsx +201 -0
- package/src/components/workflow/ChecklistEditor.tsx +239 -0
- package/src/components/workflow/CommandPrefixManager.tsx +118 -0
- package/src/components/workflow/ConfigSettingsPanel.tsx +189 -0
- package/src/components/workflow/DirectionSelector.tsx +133 -0
- package/src/components/workflow/DispatchConfigPanel.tsx +180 -0
- package/src/components/workflow/EdgeDetailPanel.tsx +236 -0
- package/src/components/workflow/EdgePropertyEditor.tsx +251 -0
- package/src/components/workflow/EditToolbar.tsx +138 -0
- package/src/components/workflow/HookDetailPanel.tsx +250 -0
- package/src/components/workflow/HookExecutionLog.tsx +24 -0
- package/src/components/workflow/HookSourceModal.tsx +129 -0
- package/src/components/workflow/HooksDashboard.tsx +363 -0
- package/src/components/workflow/ListPropertyEditor.tsx +251 -0
- package/src/components/workflow/MigrationPreviewDialog.tsx +237 -0
- package/src/components/workflow/MovementRulesPanel.tsx +188 -0
- package/src/components/workflow/NodeDetailPanel.tsx +245 -0
- package/src/components/workflow/PresetSelector.tsx +414 -0
- package/src/components/workflow/SkillCommandBuilder.tsx +174 -0
- package/src/components/workflow/WorkflowEdgeComponent.tsx +145 -0
- package/src/components/workflow/WorkflowNode.tsx +147 -0
- package/src/components/workflow/graphLayout.ts +186 -0
- package/src/components/workflow/mergeHooks.ts +85 -0
- package/src/components/workflow/useEditHistory.ts +88 -0
- package/src/components/workflow/useWorkflowEditor.ts +262 -0
- package/src/components/workflow/validateConfig.ts +70 -0
- package/src/hooks/useActiveDispatches.ts +198 -0
- package/src/hooks/useBoardSettings.ts +170 -0
- package/src/hooks/useCardDisplay.ts +57 -0
- package/src/hooks/useCcHooks.ts +24 -0
- package/src/hooks/useConfigTree.ts +51 -0
- package/src/hooks/useEnforcementRules.ts +46 -0
- package/src/hooks/useEvents.ts +59 -0
- package/src/hooks/useFileEditor.ts +165 -0
- package/src/hooks/useGates.ts +57 -0
- package/src/hooks/useIdeaActions.ts +53 -0
- package/src/hooks/useKanbanDnd.ts +410 -0
- package/src/hooks/useOrbitalConfig.ts +54 -0
- package/src/hooks/usePipeline.ts +47 -0
- package/src/hooks/usePipelineData.ts +338 -0
- package/src/hooks/useReconnect.ts +25 -0
- package/src/hooks/useScopeFilters.ts +125 -0
- package/src/hooks/useScopeSessions.ts +44 -0
- package/src/hooks/useScopes.ts +67 -0
- package/src/hooks/useSearch.ts +67 -0
- package/src/hooks/useSettings.tsx +187 -0
- package/src/hooks/useSocket.ts +25 -0
- package/src/hooks/useSourceControl.ts +105 -0
- package/src/hooks/useSprintPreflight.ts +55 -0
- package/src/hooks/useSprints.ts +154 -0
- package/src/hooks/useStatusBarHighlight.ts +18 -0
- package/src/hooks/useSwimlaneBoardSettings.ts +104 -0
- package/src/hooks/useTheme.ts +9 -0
- package/src/hooks/useTransitionReadiness.ts +53 -0
- package/src/hooks/useVersion.ts +155 -0
- package/src/hooks/useViolations.ts +65 -0
- package/src/hooks/useWorkflow.tsx +125 -0
- package/src/hooks/useZoomModifier.ts +19 -0
- package/src/index.css +797 -0
- package/src/layouts/DashboardLayout.tsx +113 -0
- package/src/lib/collisionDetection.ts +20 -0
- package/src/lib/scope-fields.ts +61 -0
- package/src/lib/swimlane.ts +146 -0
- package/src/lib/utils.ts +15 -0
- package/src/main.tsx +19 -0
- package/src/socket.ts +11 -0
- package/src/types/index.ts +497 -0
- package/src/views/AgentFeed.tsx +339 -0
- package/src/views/DeployPipeline.tsx +59 -0
- package/src/views/EnforcementView.tsx +378 -0
- package/src/views/PrimitivesConfig.tsx +500 -0
- package/src/views/QualityGates.tsx +1012 -0
- package/src/views/ScopeBoard.tsx +454 -0
- package/src/views/SessionTimeline.tsx +516 -0
- package/src/views/Settings.tsx +183 -0
- package/src/views/SourceControl.tsx +95 -0
- package/src/views/WorkflowVisualizer.tsx +382 -0
- package/tailwind.config.js +161 -0
- package/templates/agents/AUTO-INVOKE.md +180 -0
- package/templates/agents/CONFLICT-RESOLUTION.md +128 -0
- package/templates/agents/QUICK-REFERENCE.md +122 -0
- package/templates/agents/README.md +188 -0
- package/templates/agents/SKILL-TRIGGERS.md +100 -0
- package/templates/agents/blue-team/frontend-designer.md +424 -0
- package/templates/agents/green-team/architect.md +526 -0
- package/templates/agents/green-team/rules-enforcer.md +131 -0
- package/templates/agents/red-team/attacker-learned.md +24 -0
- package/templates/agents/red-team/attacker.md +486 -0
- package/templates/agents/red-team/chaos.md +548 -0
- package/templates/agents/reference/component-registry.md +82 -0
- package/templates/agents/workflows/full-mode.md +218 -0
- package/templates/agents/workflows/quick-mode.md +118 -0
- package/templates/agents/workflows/security-mode.md +283 -0
- package/templates/anti-patterns/dangerous-shortcuts.md +427 -0
- package/templates/config/agent-triggers.json +92 -0
- package/templates/hooks/agent-team-gate.sh +31 -0
- package/templates/hooks/agent-trigger.sh +97 -0
- package/templates/hooks/block-push.sh +66 -0
- package/templates/hooks/block-workarounds.sh +61 -0
- package/templates/hooks/blocker-check.sh +28 -0
- package/templates/hooks/completion-checklist.sh +28 -0
- package/templates/hooks/decision-capture.sh +15 -0
- package/templates/hooks/dependency-check.sh +27 -0
- package/templates/hooks/end-session.sh +31 -0
- package/templates/hooks/exploration-logger.sh +37 -0
- package/templates/hooks/files-changed-summary.sh +37 -0
- package/templates/hooks/get-session-id.sh +49 -0
- package/templates/hooks/git-commit-guard.sh +34 -0
- package/templates/hooks/init-session.sh +93 -0
- package/templates/hooks/orbital-emit.sh +79 -0
- package/templates/hooks/orbital-report-deploy.sh +78 -0
- package/templates/hooks/orbital-report-gates.sh +40 -0
- package/templates/hooks/orbital-report-violation.sh +36 -0
- package/templates/hooks/orbital-scope-update.sh +53 -0
- package/templates/hooks/phase-verify-reminder.sh +26 -0
- package/templates/hooks/review-gate-check.sh +82 -0
- package/templates/hooks/scope-commit-logger.sh +37 -0
- package/templates/hooks/scope-create-cleanup.sh +36 -0
- package/templates/hooks/scope-create-gate.sh +80 -0
- package/templates/hooks/scope-create-tracker.sh +17 -0
- package/templates/hooks/scope-file-sync.sh +53 -0
- package/templates/hooks/scope-gate.sh +35 -0
- package/templates/hooks/scope-helpers.sh +188 -0
- package/templates/hooks/scope-lifecycle-gate.sh +139 -0
- package/templates/hooks/scope-prepare.sh +244 -0
- package/templates/hooks/scope-transition.sh +172 -0
- package/templates/hooks/session-enforcer.sh +143 -0
- package/templates/hooks/time-tracker.sh +33 -0
- package/templates/lessons-learned.md +15 -0
- package/templates/orbital.config.json +35 -0
- package/templates/presets/development.json +42 -0
- package/templates/presets/gitflow.json +712 -0
- package/templates/presets/minimal.json +23 -0
- package/templates/quick/rules.md +218 -0
- package/templates/scopes/_template.md +255 -0
- package/templates/settings-hooks.json +98 -0
- package/templates/skills/git-commit/SKILL.md +85 -0
- package/templates/skills/git-dev/SKILL.md +99 -0
- package/templates/skills/git-hotfix/SKILL.md +223 -0
- package/templates/skills/git-main/SKILL.md +84 -0
- package/templates/skills/git-production/SKILL.md +165 -0
- package/templates/skills/git-staging/SKILL.md +112 -0
- package/templates/skills/scope-create/SKILL.md +81 -0
- package/templates/skills/scope-fix-review/SKILL.md +168 -0
- package/templates/skills/scope-implement/SKILL.md +110 -0
- package/templates/skills/scope-post-review/SKILL.md +144 -0
- package/templates/skills/scope-pre-review/SKILL.md +211 -0
- package/templates/skills/scope-verify/SKILL.md +201 -0
- package/templates/skills/session-init/SKILL.md +62 -0
- package/templates/skills/session-resume/SKILL.md +201 -0
- package/templates/skills/test-checks/SKILL.md +171 -0
- package/templates/skills/test-code-review/SKILL.md +252 -0
- package/tsconfig.json +25 -0
- 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
|