@orchestrator-claude/cli 3.17.1 → 3.19.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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/base/CLAUDE.md.hbs +45 -28
- package/dist/templates/base/claude/agents/orchestrator.md +84 -117
- package/dist/templates/base/claude/hooks/dangling-guard.ts +53 -0
- package/dist/templates/base/claude/hooks/gate-guardian.ts +102 -0
- package/dist/templates/base/claude/hooks/lib/api-client.ts +293 -0
- package/dist/templates/base/claude/hooks/lib/git-checkpoint.ts +91 -0
- package/dist/templates/base/claude/hooks/package.json +13 -0
- package/dist/templates/base/claude/hooks/post-compact.ts +44 -0
- package/dist/templates/base/claude/hooks/session-start.ts +97 -0
- package/dist/templates/base/claude/hooks/subagent-start.ts +50 -0
- package/dist/templates/base/claude/hooks/subagent-stop.ts +57 -0
- package/dist/templates/base/claude/hooks/tsconfig.json +18 -0
- package/dist/templates/base/claude/hooks/user-prompt.ts +95 -0
- package/dist/templates/base/claude/hooks/workflow-guard.ts +120 -0
- package/dist/templates/base/claude/settings.json +23 -22
- package/dist/templates/base/claude/skills/orchestrator/SKILL.md +108 -0
- package/package.json +1 -1
- package/templates/base/CLAUDE.md.hbs +45 -28
- package/templates/base/claude/agents/orchestrator.md +84 -117
- package/templates/base/claude/hooks/dangling-guard.ts +53 -0
- package/templates/base/claude/hooks/gate-guardian.ts +102 -0
- package/templates/base/claude/hooks/lib/api-client.ts +293 -0
- package/templates/base/claude/hooks/lib/git-checkpoint.ts +91 -0
- package/templates/base/claude/hooks/package.json +13 -0
- package/templates/base/claude/hooks/post-compact.ts +44 -0
- package/templates/base/claude/hooks/session-start.ts +97 -0
- package/templates/base/claude/hooks/subagent-start.ts +50 -0
- package/templates/base/claude/hooks/subagent-stop.ts +57 -0
- package/templates/base/claude/hooks/tsconfig.json +18 -0
- package/templates/base/claude/hooks/user-prompt.ts +95 -0
- package/templates/base/claude/hooks/workflow-guard.ts +120 -0
- package/templates/base/claude/settings.json +23 -22
- package/templates/base/claude/skills/orchestrator/SKILL.md +108 -0
- package/dist/templates/base/claude/hooks/approval-guardian.sh +0 -62
- package/dist/templates/base/claude/hooks/dangling-workflow-guard.sh +0 -57
- package/dist/templates/base/claude/hooks/gate-guardian.sh +0 -84
- package/dist/templates/base/claude/hooks/orch-helpers.sh +0 -135
- package/dist/templates/base/claude/hooks/ping-pong-enforcer.sh +0 -58
- package/dist/templates/base/claude/hooks/post-phase-checkpoint.sh +0 -203
- package/dist/templates/base/claude/hooks/prompt-orchestrator.sh +0 -41
- package/dist/templates/base/claude/hooks/session-orchestrator.sh +0 -54
- package/dist/templates/base/claude/hooks/track-agent-invocation.sh +0 -230
- package/dist/templates/base/claude/hooks/workflow-guard.sh +0 -79
- package/templates/base/claude/hooks/approval-guardian.sh +0 -62
- package/templates/base/claude/hooks/dangling-workflow-guard.sh +0 -57
- package/templates/base/claude/hooks/gate-guardian.sh +0 -84
- package/templates/base/claude/hooks/orch-helpers.sh +0 -135
- package/templates/base/claude/hooks/ping-pong-enforcer.sh +0 -58
- package/templates/base/claude/hooks/post-phase-checkpoint.sh +0 -203
- package/templates/base/claude/hooks/prompt-orchestrator.sh +0 -41
- package/templates/base/claude/hooks/session-orchestrator.sh +0 -54
- package/templates/base/claude/hooks/track-agent-invocation.sh +0 -230
- package/templates/base/claude/hooks/workflow-guard.sh +0 -79
|
@@ -39,11 +39,11 @@
|
|
|
39
39
|
"hooks": {
|
|
40
40
|
"SessionStart": [
|
|
41
41
|
{
|
|
42
|
-
"matcher": "startup",
|
|
42
|
+
"matcher": "startup|resume",
|
|
43
43
|
"hooks": [
|
|
44
44
|
{
|
|
45
45
|
"type": "command",
|
|
46
|
-
"command": ".claude/hooks/session-
|
|
46
|
+
"command": "npx tsx .claude/hooks/session-start.ts",
|
|
47
47
|
"timeout": 10000,
|
|
48
48
|
"on_failure": "ignore"
|
|
49
49
|
}
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"hooks": [
|
|
57
57
|
{
|
|
58
58
|
"type": "command",
|
|
59
|
-
"command": ".claude/hooks/prompt
|
|
59
|
+
"command": "npx tsx .claude/hooks/user-prompt.ts",
|
|
60
60
|
"timeout": 10000,
|
|
61
61
|
"on_failure": "ignore"
|
|
62
62
|
}
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"hooks": [
|
|
70
70
|
{
|
|
71
71
|
"type": "command",
|
|
72
|
-
"command": ".claude/hooks/gate-guardian.
|
|
72
|
+
"command": "npx tsx .claude/hooks/gate-guardian.ts",
|
|
73
73
|
"timeout": 15000,
|
|
74
74
|
"on_failure": "warn"
|
|
75
75
|
}
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"hooks": [
|
|
81
81
|
{
|
|
82
82
|
"type": "command",
|
|
83
|
-
"command": ".claude/hooks/workflow-guard.
|
|
83
|
+
"command": "npx tsx .claude/hooks/workflow-guard.ts",
|
|
84
84
|
"timeout": 10000,
|
|
85
85
|
"on_failure": "warn"
|
|
86
86
|
}
|
|
@@ -89,12 +89,12 @@
|
|
|
89
89
|
],
|
|
90
90
|
"SubagentStart": [
|
|
91
91
|
{
|
|
92
|
-
"matcher": "implementer|specifier|planner|task-generator|reviewer|researcher
|
|
92
|
+
"matcher": "implementer|specifier|planner|task-generator|reviewer|researcher",
|
|
93
93
|
"hooks": [
|
|
94
94
|
{
|
|
95
95
|
"type": "command",
|
|
96
|
-
"command": ".claude/hooks/
|
|
97
|
-
"timeout":
|
|
96
|
+
"command": "npx tsx .claude/hooks/subagent-start.ts",
|
|
97
|
+
"timeout": 10000,
|
|
98
98
|
"on_failure": "ignore"
|
|
99
99
|
}
|
|
100
100
|
]
|
|
@@ -102,37 +102,38 @@
|
|
|
102
102
|
],
|
|
103
103
|
"SubagentStop": [
|
|
104
104
|
{
|
|
105
|
-
"matcher": "implementer|specifier|planner|task-generator|reviewer|researcher
|
|
105
|
+
"matcher": "implementer|specifier|planner|task-generator|reviewer|researcher",
|
|
106
106
|
"hooks": [
|
|
107
107
|
{
|
|
108
108
|
"type": "command",
|
|
109
|
-
"command": ".claude/hooks/
|
|
110
|
-
"timeout":
|
|
109
|
+
"command": "npx tsx .claude/hooks/subagent-stop.ts",
|
|
110
|
+
"timeout": 30000,
|
|
111
111
|
"on_failure": "ignore"
|
|
112
|
-
}
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
"Stop": [
|
|
117
|
+
{
|
|
118
|
+
"matcher": "",
|
|
119
|
+
"hooks": [
|
|
113
120
|
{
|
|
114
121
|
"type": "command",
|
|
115
|
-
"command": ".claude/hooks/
|
|
122
|
+
"command": "npx tsx .claude/hooks/dangling-guard.ts",
|
|
116
123
|
"timeout": 15000,
|
|
117
|
-
"on_failure": "warn"
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
"type": "command",
|
|
121
|
-
"command": ".claude/hooks/post-phase-checkpoint.sh",
|
|
122
|
-
"timeout": 30000,
|
|
123
124
|
"on_failure": "ignore"
|
|
124
125
|
}
|
|
125
126
|
]
|
|
126
127
|
}
|
|
127
128
|
],
|
|
128
|
-
"
|
|
129
|
+
"PostCompact": [
|
|
129
130
|
{
|
|
130
131
|
"matcher": "",
|
|
131
132
|
"hooks": [
|
|
132
133
|
{
|
|
133
134
|
"type": "command",
|
|
134
|
-
"command": ".claude/hooks/
|
|
135
|
-
"timeout":
|
|
135
|
+
"command": "npx tsx .claude/hooks/post-compact.ts",
|
|
136
|
+
"timeout": 5000,
|
|
136
137
|
"on_failure": "ignore"
|
|
137
138
|
}
|
|
138
139
|
]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: orchestrator
|
|
3
|
+
description: Workflow coordination — phase dispatch, sub-agent management, gate checks. Use when starting features/bugs/refactoring, or when the user invokes /orchestrator. The session greeting is handled by CLAUDE.md Rule #1 + hook data, NOT by this skill.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: /orchestrator
|
|
7
|
+
|
|
8
|
+
You are the **workflow orchestrator**. The main conversation coordinates all phases directly — no orchestrator sub-agent needed. Sub-agents (specifier, planner, implementer, etc.) are dispatched via the Agent tool from this conversation.
|
|
9
|
+
|
|
10
|
+
**Note:** The session greeting (AskUserQuestion on start) is handled by CLAUDE.md Rule #1 using hook-injected data. This skill is loaded only when the user chooses to start or continue a workflow.
|
|
11
|
+
|
|
12
|
+
## Step 1: Start Workflow
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
mcp__orchestrator-tools__detectWorkflow({ prompt: "user's request" })
|
|
16
|
+
mcp__orchestrator-tools__startWorkflow({ type: "feature_development|bug_fix|refactoring", prompt: "..." })
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Workflow types: `feature_development`, `bug_fix`, `refactoring`, `emergency_debug`
|
|
20
|
+
|
|
21
|
+
## Step 4: Phase Dispatch Loop
|
|
22
|
+
|
|
23
|
+
For each phase in the workflow, follow this cycle:
|
|
24
|
+
|
|
25
|
+
### 4a. Dispatch Phase Sub-Agent
|
|
26
|
+
|
|
27
|
+
| Phase | Agent | What it produces |
|
|
28
|
+
|-------|-------|-----------------|
|
|
29
|
+
| research | researcher | Research findings |
|
|
30
|
+
| specify | specifier | spec.md → artifactStore |
|
|
31
|
+
| plan | planner | plan.md → artifactStore |
|
|
32
|
+
| tasks | task-generator | tasks.md → artifactStore |
|
|
33
|
+
| implement | implementer | Code + tests |
|
|
34
|
+
| review | reviewer | Review feedback |
|
|
35
|
+
|
|
36
|
+
Dispatch the appropriate sub-agent:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Agent(subagent_type: "{phase_agent}", prompt: "
|
|
40
|
+
[Workflow] Type: {type}, Phase: {phase}, Project: {projectId}
|
|
41
|
+
[Previous artifact] {summary of previous phase artifact, if any}
|
|
42
|
+
[Task] {phase-specific instructions from user's original request}
|
|
43
|
+
")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Critical:** Sub-agents MUST use `artifactStore` to persist artifacts to MinIO. Include this instruction in every sub-agent prompt:
|
|
47
|
+
> Store your output artifact via mcp__orchestrator-extended__artifactStore with workflowId, type (spec|plan|tasks), and content.
|
|
48
|
+
|
|
49
|
+
### 4b. Gate Check via AskUserQuestion
|
|
50
|
+
|
|
51
|
+
After each sub-agent completes, present the artifact summary and ask for approval:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
AskUserQuestion({
|
|
55
|
+
question: "Phase '{phase}' complete.\n\n{artifact_summary}\n\nApprove to proceed to next phase?",
|
|
56
|
+
options: ["Approve", "Request changes", "Abort workflow"]
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- **Approve**: Call `advancePhase` and dispatch next phase agent
|
|
61
|
+
- **Request changes**: Re-dispatch the same sub-agent with feedback
|
|
62
|
+
- **Abort**: Call `completeWorkflow` with cancelled status
|
|
63
|
+
|
|
64
|
+
### 4c. Advance Phase
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
mcp__orchestrator-tools__advancePhase({ workflowId: "..." })
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Then loop back to 4a for the next phase.
|
|
71
|
+
|
|
72
|
+
## Step 5: Complete Workflow
|
|
73
|
+
|
|
74
|
+
After all phases are done:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
ToolSearch("select:mcp__orchestrator-extended__completeWorkflow")
|
|
78
|
+
mcp__orchestrator-extended__completeWorkflow({ workflowId: "..." })
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Summarize what was accomplished and any follow-up items.
|
|
82
|
+
|
|
83
|
+
## Rules
|
|
84
|
+
|
|
85
|
+
1. **YOU are the orchestrator** — dispatch sub-agents via Agent tool, do NOT invoke Agent(subagent_type: "orchestrator")
|
|
86
|
+
2. **Every gate uses AskUserQuestion** — NEVER auto-approve phase transitions
|
|
87
|
+
3. **Artifacts go to MinIO** — sub-agents MUST call artifactStore
|
|
88
|
+
4. **MCP tools are deferred** — call ToolSearch before first use of any mcp__* tool
|
|
89
|
+
5. **Keep sub-agent prompts focused** — include workflow context + previous artifact summary + task description
|
|
90
|
+
6. **Summarize, don't dump** — when presenting gate checks, show a concise summary of the artifact, not the full content
|
|
91
|
+
|
|
92
|
+
## Phase-Specific Skills
|
|
93
|
+
|
|
94
|
+
Load these skills for phase-specific guidance when needed:
|
|
95
|
+
|
|
96
|
+
| Skill | When |
|
|
97
|
+
|-------|------|
|
|
98
|
+
| `/artifact-production` | Before dispatching specify/plan/tasks agents |
|
|
99
|
+
| `/tdd-discipline` | Before dispatching implementer |
|
|
100
|
+
| `/project-conventions` | Before any implementation work |
|
|
101
|
+
| `/checkpoint-protocol` | After completing task groups |
|
|
102
|
+
|
|
103
|
+
## Error Recovery
|
|
104
|
+
|
|
105
|
+
- **Sub-agent fails**: Read the error, fix the issue, re-dispatch
|
|
106
|
+
- **MCP tool not found**: Call ToolSearch to load the schema, then retry
|
|
107
|
+
- **Workflow stuck**: Call `getStatus` to diagnose, present options to user via AskUserQuestion
|
|
108
|
+
- **Context getting large**: Sub-agents handle heavy work; main conversation stays lean with summaries
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# approval-guardian.sh — TD-128 F-09 Hook
|
|
3
|
-
# Trigger: PreToolUse on mcp__orchestrator-tools__approveAction and mcp__orchestrator-extended__completeWorkflow
|
|
4
|
-
# Purpose: Block auto-approve and auto-complete when workflow is awaiting_approval.
|
|
5
|
-
# Requires explicit human confirmation before proceeding.
|
|
6
|
-
#
|
|
7
|
-
# Output: JSON with permissionDecision (deny/allow)
|
|
8
|
-
|
|
9
|
-
set -euo pipefail
|
|
10
|
-
|
|
11
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
-
source "$SCRIPT_DIR/orch-helpers.sh"
|
|
13
|
-
|
|
14
|
-
STDIN_DATA=$(orch_read_stdin)
|
|
15
|
-
TOOL_NAME=$(orch_json_field "$STDIN_DATA" "tool_name")
|
|
16
|
-
orch_log "APPROVAL-GUARDIAN: PreToolUse $TOOL_NAME triggered"
|
|
17
|
-
|
|
18
|
-
# Extract workflow ID from tool_input
|
|
19
|
-
WORKFLOW_ID=$(orch_json_field "$STDIN_DATA" "tool_input.workflowId")
|
|
20
|
-
[ -z "$WORKFLOW_ID" ] && WORKFLOW_ID=$(orch_json_field "$STDIN_DATA" "workflowId")
|
|
21
|
-
|
|
22
|
-
if [ -z "$WORKFLOW_ID" ]; then
|
|
23
|
-
# No workflow ID — allow (fail-open for non-workflow calls)
|
|
24
|
-
orch_log "APPROVAL-GUARDIAN: ALLOW (no workflowId)"
|
|
25
|
-
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":"No workflowId found, allowing."}}'
|
|
26
|
-
exit 0
|
|
27
|
-
fi
|
|
28
|
-
|
|
29
|
-
# Get auth token
|
|
30
|
-
TOKEN=$(orch_get_token 2>/dev/null) || TOKEN=""
|
|
31
|
-
if [ -z "$TOKEN" ]; then
|
|
32
|
-
# Can't check status — fail-open (auth issues shouldn't block workflow completion)
|
|
33
|
-
orch_log "APPROVAL-GUARDIAN: ALLOW (auth failed, fail-open)"
|
|
34
|
-
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":"Auth failed, allowing."}}'
|
|
35
|
-
exit 0
|
|
36
|
-
fi
|
|
37
|
-
|
|
38
|
-
# Get workflow status
|
|
39
|
-
STATUS=$(curl -sf --max-time 5 "${API_URL}/api/v1/workflows/${WORKFLOW_ID}" \
|
|
40
|
-
-H "Authorization: Bearer $TOKEN" \
|
|
41
|
-
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null) || STATUS=""
|
|
42
|
-
|
|
43
|
-
if [ -z "$STATUS" ]; then
|
|
44
|
-
orch_log "APPROVAL-GUARDIAN: ALLOW (could not fetch workflow status)"
|
|
45
|
-
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":"Could not fetch workflow status, allowing."}}'
|
|
46
|
-
exit 0
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
WORKFLOW_STATUS=$(orch_json_field "$STATUS" "status")
|
|
50
|
-
orch_log "APPROVAL-GUARDIAN: workflow=$WORKFLOW_ID status=$WORKFLOW_STATUS tool=$TOOL_NAME"
|
|
51
|
-
|
|
52
|
-
# Block approveAction AND completeWorkflow when awaiting_approval
|
|
53
|
-
if [ "$WORKFLOW_STATUS" = "awaiting_approval" ]; then
|
|
54
|
-
orch_log "APPROVAL-GUARDIAN: DENY (workflow awaiting_approval — human confirmation required for $TOOL_NAME)"
|
|
55
|
-
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Approval Guardian: Workflow is awaiting human approval. You MUST ask the user for explicit confirmation before calling ${TOOL_NAME}.\",\"additionalContext\":\"Present the workflow summary to the user and ask: 'Do you approve advancing to IMPLEMENT?' Wait for their response. Do NOT auto-approve or auto-complete.\"}}"
|
|
56
|
-
exit 0
|
|
57
|
-
fi
|
|
58
|
-
|
|
59
|
-
# All other statuses: ALLOW
|
|
60
|
-
orch_log "APPROVAL-GUARDIAN: ALLOW ($TOOL_NAME, status=$WORKFLOW_STATUS)"
|
|
61
|
-
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"additionalContext\":\"${TOOL_NAME} allowed (status=${WORKFLOW_STATUS}).\"}}"
|
|
62
|
-
exit 0
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# dangling-workflow-guard.sh — ADR-013 Phase 5 Hook (JSON Structured Output)
|
|
3
|
-
# Trigger: Stop
|
|
4
|
-
# Purpose: Before conversation ends, check for workflows in in_progress state.
|
|
5
|
-
# Blocks stop and instructs LLM to call completeWorkflow.
|
|
6
|
-
#
|
|
7
|
-
# Output: JSON with decision: "block" + reason (forces LLM to continue)
|
|
8
|
-
# Exit 0 with block JSON = prevent stop
|
|
9
|
-
# Check stop_hook_active to prevent infinite loops
|
|
10
|
-
|
|
11
|
-
set -euo pipefail
|
|
12
|
-
|
|
13
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
-
source "$SCRIPT_DIR/orch-helpers.sh"
|
|
15
|
-
|
|
16
|
-
STDIN_DATA=$(orch_read_stdin)
|
|
17
|
-
orch_log "DANGLING-GUARD: Stop hook triggered"
|
|
18
|
-
|
|
19
|
-
# Prevent infinite loops
|
|
20
|
-
STOP_ACTIVE=$(orch_json_field "$STDIN_DATA" "stop_hook_active")
|
|
21
|
-
if [ "$STOP_ACTIVE" = "true" ]; then
|
|
22
|
-
orch_log "DANGLING-GUARD: stop_hook_active=true, allowing stop to prevent loop"
|
|
23
|
-
exit 0
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
# Find active workflow
|
|
27
|
-
WORKFLOW_ID=$(orch_get_active_workflow 2>/dev/null) || WORKFLOW_ID=""
|
|
28
|
-
|
|
29
|
-
if [ -z "$WORKFLOW_ID" ]; then
|
|
30
|
-
orch_log "DANGLING-GUARD: No active workflow, clean exit"
|
|
31
|
-
exit 0
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
# Get workflow status
|
|
35
|
-
TOKEN=$(orch_get_token 2>/dev/null) || TOKEN=""
|
|
36
|
-
if [ -z "$TOKEN" ]; then
|
|
37
|
-
orch_log "DANGLING-GUARD: Auth failed, allowing stop"
|
|
38
|
-
exit 0
|
|
39
|
-
fi
|
|
40
|
-
|
|
41
|
-
STATUS_RESP=$(curl -sf --max-time 5 "${API_URL}/api/v1/workflows/${WORKFLOW_ID}/status" \
|
|
42
|
-
-H "Authorization: Bearer $TOKEN" \
|
|
43
|
-
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null) || STATUS_RESP=""
|
|
44
|
-
|
|
45
|
-
PHASE=$(orch_json_field "$STATUS_RESP" "currentPhase")
|
|
46
|
-
STATUS=$(orch_json_field "$STATUS_RESP" "status")
|
|
47
|
-
|
|
48
|
-
orch_log "DANGLING-GUARD: workflow=$WORKFLOW_ID status=$STATUS phase=$PHASE"
|
|
49
|
-
|
|
50
|
-
if [ "$STATUS" = "in_progress" ] || [ "$STATUS" = "awaiting_agent" ] || [ "$STATUS" = "awaiting_approval" ]; then
|
|
51
|
-
orch_log "DANGLING-GUARD: BLOCK stop (workflow still active)"
|
|
52
|
-
echo "{\"decision\":\"block\",\"reason\":\"[DANGLING-GUARD] Workflow ${WORKFLOW_ID} is still ${STATUS} (phase: ${PHASE}). You must call mcp__orchestrator-extended__completeWorkflow({ workflowId: '${WORKFLOW_ID}' }) before ending the session.\"}"
|
|
53
|
-
exit 0
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
orch_log "DANGLING-GUARD: Workflow completed or not active, allowing stop"
|
|
57
|
-
exit 0
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# gate-guardian.sh — ADR-013 Phase 5 Hook (JSON Structured Output)
|
|
3
|
-
# Trigger: PreToolUse on mcp__orchestrator-tools__advancePhase
|
|
4
|
-
# Purpose: Guard IMPLEMENT phase advance (requires human approval). ALLOWs all other phases.
|
|
5
|
-
# Note: POST /gate/evaluate endpoint removed (TD-127). Phase transitions are now handled
|
|
6
|
-
# atomically by setPendingAction() via the ping-pong-enforcer hook.
|
|
7
|
-
#
|
|
8
|
-
# Output: JSON with permissionDecision (deny/allow) + additionalContext
|
|
9
|
-
# Exit 0 with JSON = structured decision
|
|
10
|
-
|
|
11
|
-
set -euo pipefail
|
|
12
|
-
|
|
13
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
-
source "$SCRIPT_DIR/orch-helpers.sh"
|
|
15
|
-
|
|
16
|
-
STDIN_DATA=$(orch_read_stdin)
|
|
17
|
-
orch_log "GATE-GUARDIAN: PreToolUse advancePhase triggered"
|
|
18
|
-
|
|
19
|
-
# Extract parameters
|
|
20
|
-
WORKFLOW_ID=$(orch_json_field "$STDIN_DATA" "tool_input.workflowId")
|
|
21
|
-
TARGET_PHASE=$(orch_json_field "$STDIN_DATA" "tool_input.targetPhase")
|
|
22
|
-
[ -z "$WORKFLOW_ID" ] && WORKFLOW_ID=$(orch_json_field "$STDIN_DATA" "workflowId")
|
|
23
|
-
[ -z "$TARGET_PHASE" ] && TARGET_PHASE=$(orch_json_field "$STDIN_DATA" "targetPhase")
|
|
24
|
-
|
|
25
|
-
orch_log "GATE-GUARDIAN: workflow=$WORKFLOW_ID targetPhase=$TARGET_PHASE"
|
|
26
|
-
|
|
27
|
-
# ADR-013 Phase 6: Check workflow mode before gate evaluation
|
|
28
|
-
# quick and interactive modes skip gate evaluation entirely
|
|
29
|
-
if [ -n "$WORKFLOW_ID" ]; then
|
|
30
|
-
MODE=$(orch_get_workflow_mode "$WORKFLOW_ID" 2>/dev/null) || MODE=""
|
|
31
|
-
orch_log "GATE-GUARDIAN: workflow=$WORKFLOW_ID mode=${MODE:-legacy}"
|
|
32
|
-
|
|
33
|
-
if [ "$MODE" = "quick" ]; then
|
|
34
|
-
orch_log "GATE-GUARDIAN: ALLOW (quick mode skips gate evaluation)"
|
|
35
|
-
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"additionalContext\":\"Gate to '${TARGET_PHASE}' allowed: quick mode skips gate evaluation.\"}}"
|
|
36
|
-
exit 0
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
|
-
if [ "$MODE" = "interactive" ]; then
|
|
40
|
-
orch_log "GATE-GUARDIAN: ALLOW (interactive mode skips gate evaluation, artifact gates not required)"
|
|
41
|
-
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"additionalContext\":\"Gate to '${TARGET_PHASE}' allowed: interactive mode skips artifact gate evaluation.\"}}"
|
|
42
|
-
exit 0
|
|
43
|
-
fi
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
# FAIL-CLOSED: if we can't parse input, DENY
|
|
47
|
-
if [ -z "$WORKFLOW_ID" ] || [ -z "$TARGET_PHASE" ]; then
|
|
48
|
-
orch_log "GATE-GUARDIAN: DENY (could not parse input)"
|
|
49
|
-
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Gate Guardian: Could not parse workflowId or targetPhase.","additionalContext":"Ensure you pass both workflowId and targetPhase to advancePhase."}}'
|
|
50
|
-
exit 0
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
# Get auth token — FAIL-CLOSED on auth failure
|
|
54
|
-
TOKEN=$(orch_get_token 2>/dev/null) || TOKEN=""
|
|
55
|
-
if [ -z "$TOKEN" ]; then
|
|
56
|
-
orch_log "GATE-GUARDIAN: DENY (auth failed)"
|
|
57
|
-
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Gate Guardian: Authentication failed.","additionalContext":"Check ORCHESTRATOR_ADMIN_EMAIL, ORCHESTRATOR_ADMIN_PASSWORD, ORCHESTRATOR_PROJECT_ID env vars."}}'
|
|
58
|
-
exit 0
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
# Special handling for IMPLEMENT phase: require approval
|
|
62
|
-
TARGET_LOWER=$(echo "$TARGET_PHASE" | tr '[:upper:]' '[:lower:]')
|
|
63
|
-
if [ "$TARGET_LOWER" = "implement" ]; then
|
|
64
|
-
orch_log "GATE-GUARDIAN: Checking approval for IMPLEMENT phase"
|
|
65
|
-
|
|
66
|
-
PENDING=$(curl -sf --max-time 5 "${API_URL}/api/v1/workflows/${WORKFLOW_ID}/pending-action" \
|
|
67
|
-
-H "Authorization: Bearer $TOKEN" \
|
|
68
|
-
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null) || PENDING=""
|
|
69
|
-
|
|
70
|
-
if [ -n "$PENDING" ]; then
|
|
71
|
-
PENDING_STATUS=$(orch_json_field "$PENDING" "status")
|
|
72
|
-
if [ "$PENDING_STATUS" != "approved" ] && [ "$PENDING_STATUS" != "awaiting_agent" ]; then
|
|
73
|
-
orch_log "GATE-GUARDIAN: DENY (IMPLEMENT requires approval, status=$PENDING_STATUS)"
|
|
74
|
-
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Gate Guardian: IMPLEMENT requires human approval. Status: ${PENDING_STATUS}.\",\"additionalContext\":\"Ask the user for approval first. Then call mcp__orchestrator-tools__approveAction.\"}}"
|
|
75
|
-
exit 0
|
|
76
|
-
fi
|
|
77
|
-
fi
|
|
78
|
-
fi
|
|
79
|
-
|
|
80
|
-
# Non-IMPLEMENT phases: ALLOW directly.
|
|
81
|
-
# Phase transitions are handled atomically by setPendingAction() via ping-pong-enforcer.
|
|
82
|
-
orch_log "GATE-GUARDIAN: ALLOW (non-IMPLEMENT phase, no gate evaluation required)"
|
|
83
|
-
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"additionalContext\":\"Gate to '${TARGET_PHASE}' allowed.\"}}"
|
|
84
|
-
exit 0
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# orch-helpers.sh — Shared helper functions for Orchestrator hooks
|
|
3
|
-
# ADR-013: Deterministic Orchestration
|
|
4
|
-
# Sourced by all hooks. NOT called directly.
|
|
5
|
-
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
-
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
8
|
-
STATE_DIR="$PROJECT_ROOT/.orchestrator/.state"
|
|
9
|
-
DEBUG_LOG="$STATE_DIR/hook-debug.log"
|
|
10
|
-
|
|
11
|
-
# API Configuration (from .env or defaults)
|
|
12
|
-
API_URL="${ORCHESTRATOR_API_URL:-http://localhost:3001}"
|
|
13
|
-
AUTH_EMAIL="${ORCHESTRATOR_ADMIN_EMAIL:-${ORCHESTRATOR_AUTH_EMAIL:-admin@orchestrator.local}}"
|
|
14
|
-
AUTH_PASSWORD="${ORCHESTRATOR_ADMIN_PASSWORD:-${ORCHESTRATOR_AUTH_PASSWORD:-admin123}}"
|
|
15
|
-
PROJECT_ID="${ORCHESTRATOR_PROJECT_ID}"
|
|
16
|
-
|
|
17
|
-
mkdir -p "$STATE_DIR"
|
|
18
|
-
|
|
19
|
-
orch_log() {
|
|
20
|
-
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
# Read stdin (Claude Code passes JSON via stdin)
|
|
24
|
-
orch_read_stdin() {
|
|
25
|
-
if [ ! -t 0 ]; then
|
|
26
|
-
cat
|
|
27
|
-
else
|
|
28
|
-
echo ""
|
|
29
|
-
fi
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
# Get cached JWT token (login if expired)
|
|
33
|
-
orch_get_token() {
|
|
34
|
-
local cached="$STATE_DIR/jwt-token"
|
|
35
|
-
if [ -f "$cached" ]; then
|
|
36
|
-
local age
|
|
37
|
-
age=$(( $(date +%s) - $(stat -c%Y "$cached" 2>/dev/null || stat -f%m "$cached" 2>/dev/null || echo 0) ))
|
|
38
|
-
if [ "$age" -lt 3500 ]; then
|
|
39
|
-
cat "$cached"
|
|
40
|
-
return
|
|
41
|
-
fi
|
|
42
|
-
fi
|
|
43
|
-
|
|
44
|
-
[ -z "$PROJECT_ID" ] && return 1
|
|
45
|
-
|
|
46
|
-
local resp
|
|
47
|
-
resp=$(curl -sf --max-time 5 -X POST "${API_URL}/api/v1/auth/login" \
|
|
48
|
-
-H "Content-Type: application/json" \
|
|
49
|
-
-H "X-Project-ID: $PROJECT_ID" \
|
|
50
|
-
-d "{\"email\":\"${AUTH_EMAIL}\",\"password\":\"${AUTH_PASSWORD}\"}" 2>/dev/null) || return 1
|
|
51
|
-
|
|
52
|
-
local token
|
|
53
|
-
token=$(echo "$resp" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);console.log(j.accessToken||j.data?.accessToken||'')}catch{console.log('')}})" 2>/dev/null)
|
|
54
|
-
|
|
55
|
-
if [ -n "$token" ] && [ "$token" != "" ]; then
|
|
56
|
-
echo "$token" > "$cached"
|
|
57
|
-
echo "$token"
|
|
58
|
-
else
|
|
59
|
-
return 1
|
|
60
|
-
fi
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
# Get the active (non-terminal) workflow ID
|
|
64
|
-
# Checks in_progress, awaiting_agent, and awaiting_approval statuses
|
|
65
|
-
orch_get_active_workflow() {
|
|
66
|
-
local token
|
|
67
|
-
token=$(orch_get_token) || return 1
|
|
68
|
-
|
|
69
|
-
local status id
|
|
70
|
-
for status in in_progress awaiting_agent awaiting_approval; do
|
|
71
|
-
local resp
|
|
72
|
-
resp=$(curl -sf --max-time 3 "${API_URL}/api/v1/workflows?status=${status}&limit=1" \
|
|
73
|
-
-H "Authorization: Bearer $token" \
|
|
74
|
-
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null) || continue
|
|
75
|
-
|
|
76
|
-
id=$(echo "$resp" | node -e "
|
|
77
|
-
let d='';
|
|
78
|
-
process.stdin.on('data',c=>d+=c);
|
|
79
|
-
process.stdin.on('end',()=>{
|
|
80
|
-
try {
|
|
81
|
-
const j=JSON.parse(d);
|
|
82
|
-
const wfs=j.data||j;
|
|
83
|
-
const wf=Array.isArray(wfs)?wfs[0]:wfs;
|
|
84
|
-
const id=wf?.id||'';
|
|
85
|
-
if(id) console.log(id); else process.exit(1);
|
|
86
|
-
} catch { process.exit(1); }
|
|
87
|
-
});" 2>/dev/null) && [ -n "$id" ] && echo "$id" && return 0
|
|
88
|
-
done
|
|
89
|
-
return 1
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
# Call getNextAction for a workflow
|
|
93
|
-
orch_get_next_action() {
|
|
94
|
-
local workflow_id="$1"
|
|
95
|
-
local token
|
|
96
|
-
token=$(orch_get_token) || return 1
|
|
97
|
-
|
|
98
|
-
curl -sf --max-time 5 "${API_URL}/api/v1/workflows/${workflow_id}/pending-action" \
|
|
99
|
-
-H "Authorization: Bearer $token" \
|
|
100
|
-
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
# ADR-013 Phase 6: Get the mode of a workflow (quick/standard/full/interactive)
|
|
104
|
-
# Returns the mode string, or empty string for legacy workflows (mode=null)
|
|
105
|
-
# Returns exit code 1 on network/auth error
|
|
106
|
-
orch_get_workflow_mode() {
|
|
107
|
-
local workflow_id="$1"
|
|
108
|
-
local token
|
|
109
|
-
token=$(orch_get_token) || return 1
|
|
110
|
-
|
|
111
|
-
local resp
|
|
112
|
-
resp=$(curl -sf --max-time 3 "${API_URL}/api/v1/workflows/${workflow_id}/status" \
|
|
113
|
-
-H "Authorization: Bearer $token" \
|
|
114
|
-
-H "X-Project-ID: $PROJECT_ID" 2>/dev/null) || return 1
|
|
115
|
-
|
|
116
|
-
orch_json_field "$resp" "mode"
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
# Extract a field from JSON string using node
|
|
120
|
-
orch_json_field() {
|
|
121
|
-
local json="$1"
|
|
122
|
-
local field="$2"
|
|
123
|
-
echo "$json" | node -e "
|
|
124
|
-
let d='';
|
|
125
|
-
process.stdin.on('data',c=>d+=c);
|
|
126
|
-
process.stdin.on('end',()=>{
|
|
127
|
-
try {
|
|
128
|
-
const j=JSON.parse(d);
|
|
129
|
-
const parts='${field}'.split('.');
|
|
130
|
-
let v=j;
|
|
131
|
-
for(const p of parts) v=v?.[p];
|
|
132
|
-
console.log(v||'');
|
|
133
|
-
} catch { console.log(''); }
|
|
134
|
-
});" 2>/dev/null
|
|
135
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# ping-pong-enforcer.sh — ADR-013 Phase 5 Hook (JSON Structured Output)
|
|
3
|
-
# Trigger: PostToolUse on Task (Agent tool)
|
|
4
|
-
# Purpose: After ANY sub-agent returns, call getNextAction and inject
|
|
5
|
-
# the result as additionalContext into the main conversation.
|
|
6
|
-
#
|
|
7
|
-
# Output: JSON with additionalContext containing next action instructions
|
|
8
|
-
|
|
9
|
-
set -euo pipefail
|
|
10
|
-
|
|
11
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
-
source "$SCRIPT_DIR/orch-helpers.sh"
|
|
13
|
-
|
|
14
|
-
orch_log "PING-PONG: PostToolUse Agent triggered"
|
|
15
|
-
|
|
16
|
-
# Find active workflow
|
|
17
|
-
WORKFLOW_ID=$(orch_get_active_workflow 2>/dev/null) || WORKFLOW_ID=""
|
|
18
|
-
|
|
19
|
-
if [ -z "$WORKFLOW_ID" ]; then
|
|
20
|
-
orch_log "PING-PONG: No active workflow, skipping"
|
|
21
|
-
exit 0
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
orch_log "PING-PONG: Active workflow=$WORKFLOW_ID"
|
|
25
|
-
|
|
26
|
-
# Call getNextAction
|
|
27
|
-
NEXT_ACTION=$(orch_get_next_action "$WORKFLOW_ID" 2>/dev/null) || NEXT_ACTION=""
|
|
28
|
-
|
|
29
|
-
if [ -z "$NEXT_ACTION" ]; then
|
|
30
|
-
orch_log "PING-PONG: getNextAction failed or empty"
|
|
31
|
-
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PostToolUse\",\"additionalContext\":\"[PING-PONG] Workflow ${WORKFLOW_ID} is active but getNextAction returned empty. Check workflow status with mcp__orchestrator-tools__getStatus.\"}}"
|
|
32
|
-
exit 0
|
|
33
|
-
fi
|
|
34
|
-
|
|
35
|
-
# Extract key fields for the context message
|
|
36
|
-
HAS_ACTION=$(orch_json_field "$NEXT_ACTION" "hasAction")
|
|
37
|
-
AGENT=$(orch_json_field "$NEXT_ACTION" "pendingAction.agent")
|
|
38
|
-
STATUS=$(orch_json_field "$NEXT_ACTION" "pendingAction.status")
|
|
39
|
-
PROMPT=$(orch_json_field "$NEXT_ACTION" "pendingAction.prompt")
|
|
40
|
-
|
|
41
|
-
orch_log "PING-PONG: hasAction=$HAS_ACTION agent=$AGENT status=$STATUS"
|
|
42
|
-
|
|
43
|
-
if [ "$HAS_ACTION" = "true" ]; then
|
|
44
|
-
if [ "$STATUS" = "awaiting_agent" ]; then
|
|
45
|
-
CONTEXT="[PING-PONG] Next action for workflow ${WORKFLOW_ID}: Invoke agent '${AGENT}' via Agent tool. Status: awaiting_agent."
|
|
46
|
-
[ -n "$PROMPT" ] && CONTEXT="${CONTEXT} Prompt: ${PROMPT}"
|
|
47
|
-
elif [ "$STATUS" = "awaiting_approval" ]; then
|
|
48
|
-
CONTEXT="[PING-PONG] Workflow ${WORKFLOW_ID} requires human approval before continuing. Ask the user to approve, then call mcp__orchestrator-tools__approveAction."
|
|
49
|
-
else
|
|
50
|
-
CONTEXT="[PING-PONG] Workflow ${WORKFLOW_ID} pending action: agent=${AGENT}, status=${STATUS}."
|
|
51
|
-
fi
|
|
52
|
-
else
|
|
53
|
-
CONTEXT="[PING-PONG] No pending actions for workflow ${WORKFLOW_ID}. If current phase is implement, call mcp__orchestrator-extended__completeWorkflow to finalize."
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
# Output structured JSON
|
|
57
|
-
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PostToolUse\",\"additionalContext\":$(echo "$CONTEXT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.stringify(d)))" 2>/dev/null)}}"
|
|
58
|
-
exit 0
|