beads-orchestration 2.0.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 +214 -0
- package/SKILL.md +263 -0
- package/bootstrap.py +928 -0
- package/package.json +37 -0
- package/scripts/cli.js +64 -0
- package/scripts/postinstall.js +71 -0
- package/skills/create-beads-orchestration/SKILL.md +263 -0
- package/skills/subagents-discipline/SKILL.md +158 -0
- package/templates/CLAUDE.md +326 -0
- package/templates/agents/architect.md +121 -0
- package/templates/agents/code-reviewer.md +248 -0
- package/templates/agents/detective.md +101 -0
- package/templates/agents/discovery.md +492 -0
- package/templates/agents/merge-supervisor.md +119 -0
- package/templates/agents/scout.md +100 -0
- package/templates/agents/scribe.md +96 -0
- package/templates/beads-workflow-injection-api.md +116 -0
- package/templates/beads-workflow-injection-git.md +108 -0
- package/templates/beads-workflow-injection.md +111 -0
- package/templates/frontend-reviews-requirement.md +61 -0
- package/templates/hooks/block-orchestrator-tools.sh +98 -0
- package/templates/hooks/clarify-vague-request.sh +39 -0
- package/templates/hooks/enforce-bead-for-supervisor.sh +32 -0
- package/templates/hooks/enforce-branch-before-edit.sh +47 -0
- package/templates/hooks/enforce-concise-response.sh +41 -0
- package/templates/hooks/enforce-sequential-dispatch.sh +63 -0
- package/templates/hooks/inject-discipline-reminder.sh +28 -0
- package/templates/hooks/log-dispatch-prompt.sh +39 -0
- package/templates/hooks/memory-capture.sh +104 -0
- package/templates/hooks/remind-inprogress.sh +14 -0
- package/templates/hooks/session-start.sh +121 -0
- package/templates/hooks/validate-completion.sh +131 -0
- package/templates/hooks/validate-epic-close.sh +84 -0
- package/templates/mcp.json.template +12 -0
- package/templates/memory/recall.sh +121 -0
- package/templates/settings.json +74 -0
- package/templates/skills/react-best-practices/SKILL.md +487 -0
- package/templates/skills/subagents-discipline/SKILL.md +127 -0
- package/templates/ui-constraints.md +76 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# UserPromptSubmit: Force clarification on vague requests + epic reminder
|
|
4
|
+
#
|
|
5
|
+
# Uses plain text stdout for context injection (per Claude Code docs)
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
INPUT=$(cat)
|
|
9
|
+
PROMPT=$(echo "$INPUT" | jq -r '.prompt // empty')
|
|
10
|
+
LENGTH=${#PROMPT}
|
|
11
|
+
|
|
12
|
+
if [[ $LENGTH -lt 50 ]]; then
|
|
13
|
+
cat << 'EOF'
|
|
14
|
+
<system-reminder>
|
|
15
|
+
STOP. This request is too short to act on safely.
|
|
16
|
+
|
|
17
|
+
BEFORE doing anything else, you MUST use the AskUserQuestion tool to clarify:
|
|
18
|
+
- What specific outcome does the user want?
|
|
19
|
+
- What files/components are involved?
|
|
20
|
+
- Are there any constraints or preferences?
|
|
21
|
+
|
|
22
|
+
Do NOT guess. Do NOT start working. Ask first.
|
|
23
|
+
</system-reminder>
|
|
24
|
+
EOF
|
|
25
|
+
elif [[ $LENGTH -lt 200 ]]; then
|
|
26
|
+
cat << 'EOF'
|
|
27
|
+
<system-reminder>
|
|
28
|
+
This request may be ambiguous. Consider using AskUserQuestion to clarify before proceeding.
|
|
29
|
+
</system-reminder>
|
|
30
|
+
EOF
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Always remind about epic workflow
|
|
34
|
+
cat << 'EOF'
|
|
35
|
+
<cross-domain-check>
|
|
36
|
+
CRITICAL: If this task spans multiple supervisors, you MUST create an EPIC.
|
|
37
|
+
Cross-domain = Epic. No exceptions.
|
|
38
|
+
</cross-domain-check>
|
|
39
|
+
EOF
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# PreToolUse:Task - Enforce bead exists before supervisor dispatch
|
|
4
|
+
#
|
|
5
|
+
# All supervisors must have BEAD_ID in prompt.
|
|
6
|
+
# This ensures all work is tracked.
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
INPUT=$(cat)
|
|
10
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
11
|
+
|
|
12
|
+
[[ "$TOOL_NAME" != "Task" ]] && exit 0
|
|
13
|
+
|
|
14
|
+
SUBAGENT_TYPE=$(echo "$INPUT" | jq -r '.tool_input.subagent_type // empty')
|
|
15
|
+
PROMPT=$(echo "$INPUT" | jq -r '.tool_input.prompt // empty')
|
|
16
|
+
|
|
17
|
+
# Only enforce for supervisors
|
|
18
|
+
[[ ! "$SUBAGENT_TYPE" =~ supervisor ]] && exit 0
|
|
19
|
+
|
|
20
|
+
# Exception: merge-supervisor is exempt from bead requirement
|
|
21
|
+
# Merge conflicts are incidental to other work, not tracked separately
|
|
22
|
+
[[ "$SUBAGENT_TYPE" == "merge-supervisor" ]] && exit 0
|
|
23
|
+
|
|
24
|
+
# Check for BEAD_ID in prompt
|
|
25
|
+
if [[ "$PROMPT" != *"BEAD_ID:"* ]]; then
|
|
26
|
+
cat << 'EOF'
|
|
27
|
+
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"<bead-required>\nAll supervisor work MUST be tracked with a bead.\n\n<action>\nFor standalone tasks:\n 1. bd create \"Task title\" -d \"Description\"\n 2. Dispatch with: BEAD_ID: {id}\n\nFor epic children:\n 1. bd create \"Epic\" -d \"...\" --type epic\n 2. bd create \"Child\" -d \"...\" --parent {EPIC_ID}\n 3. Dispatch with: BEAD_ID: {child_id}, EPIC_ID: {epic_id}\n</action>\n\nEach task creates its own worktree at .worktrees/bd-{BEAD_ID}/\n</bead-required>"}}
|
|
28
|
+
EOF
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
exit 0
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# PreToolUse: Block Edit/Write on main branch outside worktrees
|
|
4
|
+
#
|
|
5
|
+
# Supervisors must work in .worktrees/bd-{BEAD_ID}/ directories, not main.
|
|
6
|
+
# This prevents accidental commits to main directory.
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
INPUT=$(cat)
|
|
10
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
11
|
+
|
|
12
|
+
# Only check Edit and Write tools
|
|
13
|
+
[[ "$TOOL_NAME" != "Edit" ]] && [[ "$TOOL_NAME" != "Write" ]] && exit 0
|
|
14
|
+
|
|
15
|
+
# Get the file path being edited
|
|
16
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
|
17
|
+
|
|
18
|
+
# Allow if editing within .worktrees/ directory
|
|
19
|
+
if [[ "$FILE_PATH" == *"/.worktrees/"* ]] || [[ "$FILE_PATH" == *"\.worktrees\"* ]]; then
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Get current working directory
|
|
24
|
+
CWD=$(pwd)
|
|
25
|
+
|
|
26
|
+
# Allow if currently inside a .worktrees/ directory
|
|
27
|
+
if [[ "$CWD" == *"/.worktrees/"* ]] || [[ "$CWD" == *"\.worktrees\"* ]]; then
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Check current branch (if we're in a git repo outside worktrees)
|
|
32
|
+
CURRENT_BRANCH=$(git branch --show-current 2>/dev/null)
|
|
33
|
+
|
|
34
|
+
# Block if on main or master (and not in a worktree)
|
|
35
|
+
if [[ "$CURRENT_BRANCH" == "main" ]] || [[ "$CURRENT_BRANCH" == "master" ]]; then
|
|
36
|
+
cat << EOF
|
|
37
|
+
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Cannot edit files on $CURRENT_BRANCH branch. Supervisors must work in worktrees.
|
|
38
|
+
|
|
39
|
+
Create a worktree first using the API:
|
|
40
|
+
POST /api/git/worktree { repo_path, bead_id }
|
|
41
|
+
|
|
42
|
+
Then cd into .worktrees/bd-{BEAD_ID}/ to make changes."}}
|
|
43
|
+
EOF
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
exit 0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# PostToolUse: Enforce concise responses from subagents
|
|
4
|
+
#
|
|
5
|
+
# Subagents should return concise reports (max 10 lines, ~500 chars)
|
|
6
|
+
# This reduces context usage and keeps orchestrator focused.
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
INPUT=$(cat)
|
|
10
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
11
|
+
|
|
12
|
+
# Only check Task tool responses
|
|
13
|
+
[[ "$TOOL_NAME" != "Task" ]] && exit 0
|
|
14
|
+
|
|
15
|
+
# Get the tool response
|
|
16
|
+
RESPONSE=$(echo "$INPUT" | jq -r '.tool_result // empty')
|
|
17
|
+
[[ -z "$RESPONSE" ]] && exit 0
|
|
18
|
+
|
|
19
|
+
# Count lines and characters
|
|
20
|
+
LINE_COUNT=$(echo "$RESPONSE" | wc -l | tr -d ' ')
|
|
21
|
+
CHAR_COUNT=$(echo "$RESPONSE" | wc -c | tr -d ' ')
|
|
22
|
+
|
|
23
|
+
# Limits
|
|
24
|
+
MAX_LINES=10
|
|
25
|
+
MAX_CHARS=500
|
|
26
|
+
|
|
27
|
+
# Check limits (warn but don't block - agent already completed)
|
|
28
|
+
if [[ "$LINE_COUNT" -gt "$MAX_LINES" ]] || [[ "$CHAR_COUNT" -gt "$MAX_CHARS" ]]; then
|
|
29
|
+
# Log warning (PostToolUse can't deny, only add context)
|
|
30
|
+
cat << EOF
|
|
31
|
+
{
|
|
32
|
+
"hookSpecificOutput": {
|
|
33
|
+
"hookEventName": "PostToolUse",
|
|
34
|
+
"warning": "Subagent response exceeded limits (${LINE_COUNT} lines, ${CHAR_COUNT} chars). Target: ${MAX_LINES} lines, ${MAX_CHARS} chars. Consider asking agents for more concise reports."
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
EOF
|
|
38
|
+
exit 0
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
exit 0
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# PreToolUse:Task - Enforce sequential dispatch and design doc existence
|
|
4
|
+
#
|
|
5
|
+
# For epic child tasks:
|
|
6
|
+
# 1. Blocks dispatch if task has unresolved blockers
|
|
7
|
+
# 2. Blocks dispatch if epic has design path but file doesn't exist
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
INPUT=$(cat)
|
|
11
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
12
|
+
|
|
13
|
+
[[ "$TOOL_NAME" != "Task" ]] && exit 0
|
|
14
|
+
|
|
15
|
+
SUBAGENT_TYPE=$(echo "$INPUT" | jq -r '.tool_input.subagent_type // empty')
|
|
16
|
+
PROMPT=$(echo "$INPUT" | jq -r '.tool_input.prompt // empty')
|
|
17
|
+
|
|
18
|
+
# Only check for supervisors (not architect, scout, etc.)
|
|
19
|
+
[[ ! "$SUBAGENT_TYPE" =~ supervisor ]] && exit 0
|
|
20
|
+
|
|
21
|
+
# Worker-supervisor is exempt
|
|
22
|
+
[[ "$SUBAGENT_TYPE" == *"worker"* ]] && exit 0
|
|
23
|
+
|
|
24
|
+
# Extract BEAD_ID
|
|
25
|
+
BEAD_ID=$(echo "$PROMPT" | grep -oE "BEAD_ID: [A-Za-z0-9._-]+" | head -1 | sed 's/BEAD_ID: //')
|
|
26
|
+
[[ -z "$BEAD_ID" ]] && exit 0
|
|
27
|
+
|
|
28
|
+
# Block dispatch to closed/done beads - create a new bead instead
|
|
29
|
+
BEAD_STATUS=$(bd show "$BEAD_ID" --json 2>/dev/null | jq -r '.[0].status // empty')
|
|
30
|
+
if [[ "$BEAD_STATUS" == "closed" || "$BEAD_STATUS" == "done" ]]; then
|
|
31
|
+
cat << EOF
|
|
32
|
+
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"<closed-bead>\nBead ${BEAD_ID} is already ${BEAD_STATUS}. Do not reopen closed beads.\n\nCreate a new bead for follow-up work and relate it:\n\n bd create \"Fix: [description]\" -d \"Follow-up to ${BEAD_ID}: [details]\"\n # Returns: {NEW_ID}\n bd dep relate {NEW_ID} ${BEAD_ID}\n\nThen dispatch with the NEW bead ID.\n</closed-bead>"}}
|
|
33
|
+
EOF
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Check if this is an epic child (contains dot)
|
|
38
|
+
if [[ "$BEAD_ID" == *"."* ]]; then
|
|
39
|
+
# Extract EPIC_ID (everything before last dot)
|
|
40
|
+
EPIC_ID=$(echo "$BEAD_ID" | sed 's/\.[0-9]*$//')
|
|
41
|
+
|
|
42
|
+
# Check for unresolved blockers (exclude parent epic - it's not a real blocker)
|
|
43
|
+
BLOCKERS=$(bd dep list "$BEAD_ID" --json 2>/dev/null | jq -r --arg epic "$EPIC_ID" '.[] | select(.id != $epic and .status != "done" and .status != "closed") | .id' 2>/dev/null | tr '\n' ', ' | sed 's/,$//')
|
|
44
|
+
|
|
45
|
+
if [[ -n "$BLOCKERS" ]]; then
|
|
46
|
+
cat << EOF
|
|
47
|
+
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"<blocked-task>\nCannot dispatch ${BEAD_ID} - unresolved blockers: ${BLOCKERS}\n\nComplete blocking tasks first, then dispatch this one.\n\nUse: bd ready --json to see tasks with no blockers.\n</blocked-task>"}}
|
|
48
|
+
EOF
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Check design doc exists (if epic has design field)
|
|
53
|
+
DESIGN_PATH=$(bd show "$EPIC_ID" --json 2>/dev/null | jq -r '.[0].design // empty')
|
|
54
|
+
|
|
55
|
+
if [[ -n "$DESIGN_PATH" ]] && [[ ! -f "$DESIGN_PATH" ]]; then
|
|
56
|
+
cat << EOF
|
|
57
|
+
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"<design-doc-missing>\nEpic ${EPIC_ID} has design path '${DESIGN_PATH}' but file doesn't exist.\n\n<stop-and-think>\nBefore dispatching architect, verify you fully understand the epic:\n\n1. Are the requirements clear and unambiguous?\n2. Do you know the expected inputs/outputs?\n3. Are there edge cases or constraints to consider?\n4. Do you understand how this integrates with existing code?\n\nIf ANY ambiguity exists -> Use AskUserQuestion to clarify FIRST.\nDo NOT dispatch architect with vague requirements.\n</stop-and-think>\n\n<next-steps>\nIf requirements are CLEAR:\n Task(\n subagent_type=\"architect\",\n prompt=\"Create design doc for EPIC_ID: ${EPIC_ID}\n Output: ${DESIGN_PATH}\n \n [Provide clear, specific requirements]\"\n )\n\nIf requirements are UNCLEAR:\n AskUserQuestion(\n questions=[{\n \"question\": \"[Your specific clarifying question]\",\n \"header\": \"Clarify\",\n \"options\": [...],\n \"multiSelect\": false\n }]\n )\n</next-steps>\n</design-doc-missing>"}}
|
|
58
|
+
EOF
|
|
59
|
+
exit 0
|
|
60
|
+
fi
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
exit 0
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# PreToolUse: Inject discipline skill reminder for supervisor dispatches
|
|
4
|
+
#
|
|
5
|
+
# When orchestrator dispatches a supervisor via Task(), remind them to
|
|
6
|
+
# invoke the subagents-discipline skill at the start of implementation.
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
INPUT=$(cat)
|
|
10
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
11
|
+
|
|
12
|
+
# Only check Task tool
|
|
13
|
+
[[ "$TOOL_NAME" != "Task" ]] && exit 0
|
|
14
|
+
|
|
15
|
+
# Check if dispatching a supervisor
|
|
16
|
+
SUBAGENT_TYPE=$(echo "$INPUT" | jq -r '.tool_input.subagent_type // empty')
|
|
17
|
+
|
|
18
|
+
# Only inject for supervisors (not code-reviewer, architect, etc.)
|
|
19
|
+
if [[ "$SUBAGENT_TYPE" == *"-supervisor"* ]]; then
|
|
20
|
+
cat << 'EOF'
|
|
21
|
+
<system-reminder>
|
|
22
|
+
SUPERVISOR DISPATCH: Before implementing, invoke `/subagents-discipline` skill.
|
|
23
|
+
This ensures verification-first development with DEMO blocks.
|
|
24
|
+
</system-reminder>
|
|
25
|
+
EOF
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
exit 0
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# PostToolUse:Task (async) - Auto-log dispatch prompts to bead comments
|
|
4
|
+
#
|
|
5
|
+
# When orchestrator dispatches a supervisor via Task(), capture the prompt
|
|
6
|
+
# and log it as a DISPATCH comment on the bead. This replaces manual
|
|
7
|
+
# INVESTIGATION logging — the dispatch prompt IS the investigation record.
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
INPUT=$(cat)
|
|
11
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
12
|
+
|
|
13
|
+
# Only process Task tool
|
|
14
|
+
[[ "$TOOL_NAME" != "Task" ]] && exit 0
|
|
15
|
+
|
|
16
|
+
# Extract subagent_type
|
|
17
|
+
SUBAGENT_TYPE=$(echo "$INPUT" | jq -r '.tool_input.subagent_type // empty')
|
|
18
|
+
|
|
19
|
+
# Only log supervisor dispatches
|
|
20
|
+
[[ "$SUBAGENT_TYPE" != *"supervisor"* ]] && exit 0
|
|
21
|
+
|
|
22
|
+
# Extract prompt
|
|
23
|
+
PROMPT=$(echo "$INPUT" | jq -r '.tool_input.prompt // empty')
|
|
24
|
+
[[ -z "$PROMPT" ]] && exit 0
|
|
25
|
+
|
|
26
|
+
# Extract BEAD_ID from prompt
|
|
27
|
+
BEAD_ID=$(echo "$PROMPT" | grep -oE 'BEAD_ID: [A-Za-z0-9._-]+' | head -1 | sed 's/BEAD_ID: //')
|
|
28
|
+
[[ -z "$BEAD_ID" ]] && exit 0
|
|
29
|
+
|
|
30
|
+
# Truncate prompt at 2048 chars
|
|
31
|
+
TRUNCATED_PROMPT=$(echo "$PROMPT" | head -c 2048)
|
|
32
|
+
|
|
33
|
+
# Log dispatch to bead (fail silently)
|
|
34
|
+
# Prefix: DISPATCH_PROMPT — UI renders as collapsible "Prompt Used" entry
|
|
35
|
+
bd comment "$BEAD_ID" "DISPATCH_PROMPT [$SUBAGENT_TYPE]:
|
|
36
|
+
|
|
37
|
+
$TRUNCATED_PROMPT" 2>/dev/null || true
|
|
38
|
+
|
|
39
|
+
exit 0
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# PostToolUse:Bash (async) - Capture knowledge from bd comment commands
|
|
4
|
+
#
|
|
5
|
+
# Detects: bd comment {BEAD_ID} "LEARNED: ..."
|
|
6
|
+
# Extracts knowledge entries into .beads/memory/knowledge.jsonl
|
|
7
|
+
#
|
|
8
|
+
|
|
9
|
+
INPUT=$(cat)
|
|
10
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
|
11
|
+
|
|
12
|
+
# Only process Bash tool
|
|
13
|
+
[[ "$TOOL_NAME" != "Bash" ]] && exit 0
|
|
14
|
+
|
|
15
|
+
# Extract the command that was executed
|
|
16
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
|
|
17
|
+
[[ -z "$COMMAND" ]] && exit 0
|
|
18
|
+
|
|
19
|
+
# Only process bd comment commands containing knowledge markers
|
|
20
|
+
echo "$COMMAND" | grep -qE 'bd\s+comment\s+' || exit 0
|
|
21
|
+
echo "$COMMAND" | grep -qE 'LEARNED:' || exit 0
|
|
22
|
+
|
|
23
|
+
# Extract BEAD_ID (argument after "bd comment")
|
|
24
|
+
BEAD_ID=$(echo "$COMMAND" | sed -E 's/.*bd[[:space:]]+comment[[:space:]]+([A-Za-z0-9._-]+)[[:space:]]+.*/\1/')
|
|
25
|
+
[[ -z "$BEAD_ID" || "$BEAD_ID" == "$COMMAND" ]] && exit 0
|
|
26
|
+
|
|
27
|
+
# Extract the comment body (content inside quotes after bead ID)
|
|
28
|
+
COMMENT_BODY=$(echo "$COMMAND" | sed -E 's/.*bd[[:space:]]+comment[[:space:]]+[A-Za-z0-9._-]+[[:space:]]+["'\'']//' | sed -E 's/["'\''][[:space:]]*$//' | head -c 4096)
|
|
29
|
+
[[ -z "$COMMENT_BODY" ]] && exit 0
|
|
30
|
+
|
|
31
|
+
# Determine type and extract content (voluntary LEARNED only)
|
|
32
|
+
TYPE=""
|
|
33
|
+
CONTENT=""
|
|
34
|
+
if echo "$COMMENT_BODY" | grep -q "LEARNED:"; then
|
|
35
|
+
TYPE="learned"
|
|
36
|
+
CONTENT=$(echo "$COMMENT_BODY" | sed 's/.*LEARNED:[[:space:]]*//' | head -c 2048)
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
[[ -z "$TYPE" || -z "$CONTENT" ]] && exit 0
|
|
40
|
+
|
|
41
|
+
# Generate key from content (type + slugified first 60 chars)
|
|
42
|
+
SLUG=$(echo "$CONTENT" | head -c 60 | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-' | sed 's/^-//;s/-$//')
|
|
43
|
+
KEY="${TYPE}-${SLUG}"
|
|
44
|
+
|
|
45
|
+
# Detect source agent from CWD or transcript context
|
|
46
|
+
SOURCE="orchestrator"
|
|
47
|
+
CWD=$(echo "$INPUT" | jq -r '.cwd // empty')
|
|
48
|
+
if echo "$CWD" | grep -q '\.worktrees/'; then
|
|
49
|
+
# Inside a worktree = supervisor is running
|
|
50
|
+
SOURCE="supervisor"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Build tags array - start with type tag
|
|
54
|
+
TAGS_ARRAY=("$TYPE")
|
|
55
|
+
|
|
56
|
+
# Scan content for known tech keywords and add matching tags
|
|
57
|
+
for tag in swift swiftui appkit menubar api security test database \
|
|
58
|
+
networking ui layout performance crash bug fix workaround \
|
|
59
|
+
gotcha pattern convention architecture auth middleware \
|
|
60
|
+
async concurrency model protocol adapter scanner engine; do
|
|
61
|
+
if echo "$CONTENT" | grep -qi "$tag"; then
|
|
62
|
+
TAGS_ARRAY+=("$tag")
|
|
63
|
+
fi
|
|
64
|
+
done
|
|
65
|
+
|
|
66
|
+
# Convert tags array to JSON
|
|
67
|
+
TAGS_JSON=$(printf '%s\n' "${TAGS_ARRAY[@]}" | jq -R . | jq -s .)
|
|
68
|
+
|
|
69
|
+
# Get timestamp
|
|
70
|
+
TS=$(date +%s)
|
|
71
|
+
|
|
72
|
+
# Build JSON entry with proper escaping
|
|
73
|
+
ENTRY=$(jq -cn \
|
|
74
|
+
--arg key "$KEY" \
|
|
75
|
+
--arg type "$TYPE" \
|
|
76
|
+
--arg content "$CONTENT" \
|
|
77
|
+
--arg source "$SOURCE" \
|
|
78
|
+
--argjson tags "$TAGS_JSON" \
|
|
79
|
+
--argjson ts "$TS" \
|
|
80
|
+
--arg bead "$BEAD_ID" \
|
|
81
|
+
'{key: $key, type: $type, content: $content, source: $source, tags: $tags, ts: $ts, bead: $bead}')
|
|
82
|
+
|
|
83
|
+
# Validate JSON
|
|
84
|
+
[[ -z "$ENTRY" ]] && exit 0
|
|
85
|
+
echo "$ENTRY" | jq . >/dev/null 2>&1 || exit 0
|
|
86
|
+
|
|
87
|
+
# Resolve memory directory
|
|
88
|
+
MEMORY_DIR="${CLAUDE_PROJECT_DIR:-.}/.beads/memory"
|
|
89
|
+
mkdir -p "$MEMORY_DIR"
|
|
90
|
+
KNOWLEDGE_FILE="$MEMORY_DIR/knowledge.jsonl"
|
|
91
|
+
|
|
92
|
+
# Append entry
|
|
93
|
+
echo "$ENTRY" >> "$KNOWLEDGE_FILE"
|
|
94
|
+
|
|
95
|
+
# Rotation: archive oldest 500 when file exceeds 1000 lines
|
|
96
|
+
LINE_COUNT=$(wc -l < "$KNOWLEDGE_FILE" 2>/dev/null | tr -d ' ')
|
|
97
|
+
if [[ "$LINE_COUNT" -gt 1000 ]]; then
|
|
98
|
+
ARCHIVE_FILE="$MEMORY_DIR/knowledge.archive.jsonl"
|
|
99
|
+
head -500 "$KNOWLEDGE_FILE" >> "$ARCHIVE_FILE"
|
|
100
|
+
tail -n +501 "$KNOWLEDGE_FILE" > "$KNOWLEDGE_FILE.tmp"
|
|
101
|
+
mv "$KNOWLEDGE_FILE.tmp" "$KNOWLEDGE_FILE"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
exit 0
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# PreToolUse:Task - Soft reminder to set bead status before dispatch
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
INPUT=$(cat)
|
|
7
|
+
PROMPT=$(echo "$INPUT" | jq -r '.tool_input.prompt // empty')
|
|
8
|
+
|
|
9
|
+
# Only remind if dispatching a bead task (prompt contains BEAD_ID)
|
|
10
|
+
if [[ "$PROMPT" == *"BEAD_ID:"* ]]; then
|
|
11
|
+
echo "IMPORTANT: Before dispatching, ensure bead is in_progress: bd update {BEAD_ID} --status in_progress"
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
exit 0
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# SessionStart: Show full task context for orchestrator
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
BEADS_DIR="$CLAUDE_PROJECT_DIR/.beads"
|
|
7
|
+
|
|
8
|
+
if [[ ! -d "$BEADS_DIR" ]]; then
|
|
9
|
+
echo "No .beads directory found. Run 'bd init' to initialize."
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Check if bd is available
|
|
14
|
+
if ! command -v bd &>/dev/null; then
|
|
15
|
+
echo "beads CLI (bd) not found. Install from: https://github.com/steveyegge/beads"
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# ============================================================
|
|
20
|
+
# Dirty Parent Check - Warn if main directory has uncommitted changes
|
|
21
|
+
# ============================================================
|
|
22
|
+
REPO_ROOT=$(git -C "$CLAUDE_PROJECT_DIR" rev-parse --show-toplevel 2>/dev/null)
|
|
23
|
+
if [[ -n "$REPO_ROOT" ]]; then
|
|
24
|
+
DIRTY=$(git -C "$REPO_ROOT" status --porcelain 2>/dev/null)
|
|
25
|
+
if [[ -n "$DIRTY" ]]; then
|
|
26
|
+
echo "⚠️ WARNING: Main directory has uncommitted changes."
|
|
27
|
+
echo " Agents should only work in .worktrees/"
|
|
28
|
+
echo ""
|
|
29
|
+
fi
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# ============================================================
|
|
33
|
+
# Auto-cleanup: Detect merged PRs and cleanup worktrees
|
|
34
|
+
# ============================================================
|
|
35
|
+
WORKTREES_DIR="$CLAUDE_PROJECT_DIR/.worktrees"
|
|
36
|
+
if [[ -d "$WORKTREES_DIR" ]]; then
|
|
37
|
+
for worktree in $(git -C "$REPO_ROOT" worktree list --porcelain 2>/dev/null | grep "^worktree.*\.worktrees/bd-" | awk '{print $2}'); do
|
|
38
|
+
BEAD_ID=$(basename "$worktree" | sed 's/bd-//')
|
|
39
|
+
BRANCH=$(basename "$worktree")
|
|
40
|
+
|
|
41
|
+
# Check if branch was merged to main
|
|
42
|
+
if git -C "$REPO_ROOT" branch --merged main 2>/dev/null | grep -q "$BRANCH"; then
|
|
43
|
+
echo "✓ $BRANCH was merged - consider cleaning up"
|
|
44
|
+
echo " Run: git worktree remove \"$worktree\" && bd close \"$BEAD_ID\""
|
|
45
|
+
echo ""
|
|
46
|
+
fi
|
|
47
|
+
done
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# ============================================================
|
|
51
|
+
# Open PR Reminder
|
|
52
|
+
# ============================================================
|
|
53
|
+
if command -v gh &>/dev/null; then
|
|
54
|
+
OPEN_PRS=$(gh pr list --author "@me" --state open --json number,title,headRefName 2>/dev/null)
|
|
55
|
+
if [[ -n "$OPEN_PRS" && "$OPEN_PRS" != "[]" ]]; then
|
|
56
|
+
echo "📋 You have open PRs:"
|
|
57
|
+
echo "$OPEN_PRS" | jq -r '.[] | " #\(.number) \(.title) (\(.headRefName))"' 2>/dev/null
|
|
58
|
+
echo ""
|
|
59
|
+
fi
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
echo ""
|
|
63
|
+
echo "## Task Status"
|
|
64
|
+
echo ""
|
|
65
|
+
|
|
66
|
+
# Show in-progress beads first (highest priority)
|
|
67
|
+
IN_PROGRESS=$(bd list --status in_progress 2>/dev/null | head -5)
|
|
68
|
+
if [[ -n "$IN_PROGRESS" ]]; then
|
|
69
|
+
echo "### In Progress (resume these):"
|
|
70
|
+
echo "$IN_PROGRESS"
|
|
71
|
+
echo ""
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# Show ready (unblocked) beads
|
|
75
|
+
READY=$(bd ready 2>/dev/null | head -5)
|
|
76
|
+
if [[ -n "$READY" ]]; then
|
|
77
|
+
echo "### Ready (no blockers):"
|
|
78
|
+
echo "$READY"
|
|
79
|
+
echo ""
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Show blocked beads
|
|
83
|
+
BLOCKED=$(bd blocked 2>/dev/null | head -3)
|
|
84
|
+
if [[ -n "$BLOCKED" ]]; then
|
|
85
|
+
echo "### Blocked:"
|
|
86
|
+
echo "$BLOCKED"
|
|
87
|
+
echo ""
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# Show stale beads (no activity in 3 days)
|
|
91
|
+
STALE=$(bd stale --days 3 2>/dev/null | head -3)
|
|
92
|
+
if [[ -n "$STALE" ]]; then
|
|
93
|
+
echo "### Stale (no activity in 3 days):"
|
|
94
|
+
echo "$STALE"
|
|
95
|
+
echo ""
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# If nothing found
|
|
99
|
+
if [[ -z "$IN_PROGRESS" && -z "$READY" && -z "$BLOCKED" && -z "$STALE" ]]; then
|
|
100
|
+
echo "No active beads. Create one with: bd create \"Task title\" -d \"Description\""
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
# ============================================================
|
|
104
|
+
# Knowledge Base - Surface recent learnings
|
|
105
|
+
# ============================================================
|
|
106
|
+
KNOWLEDGE_FILE="$BEADS_DIR/memory/knowledge.jsonl"
|
|
107
|
+
if [[ -f "$KNOWLEDGE_FILE" && -s "$KNOWLEDGE_FILE" ]]; then
|
|
108
|
+
TOTAL_ENTRIES=$(wc -l < "$KNOWLEDGE_FILE" | tr -d ' ')
|
|
109
|
+
echo ""
|
|
110
|
+
echo "## Recent Knowledge ($TOTAL_ENTRIES entries)"
|
|
111
|
+
echo ""
|
|
112
|
+
# Show 5 most recent, deduplicated by key (latest wins)
|
|
113
|
+
tail -20 "$KNOWLEDGE_FILE" | jq -s '
|
|
114
|
+
group_by(.key) | map(max_by(.ts)) | sort_by(-.ts) | .[0:5] | .[] |
|
|
115
|
+
" [\(.type | ascii_upcase | .[0:5])] \(.content | .[0:100]) (\(.source))"
|
|
116
|
+
' -r 2>/dev/null
|
|
117
|
+
echo ""
|
|
118
|
+
echo " Search: .beads/memory/recall.sh \"keyword\""
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
echo ""
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# SubagentStop: Enforce bead lifecycle - work verification
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
INPUT=$(cat)
|
|
7
|
+
AGENT_TRANSCRIPT=$(echo "$INPUT" | jq -r '.agent_transcript_path // empty')
|
|
8
|
+
MAIN_TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path // empty')
|
|
9
|
+
AGENT_ID=$(echo "$INPUT" | jq -r '.agent_id // empty')
|
|
10
|
+
|
|
11
|
+
[[ -z "$AGENT_TRANSCRIPT" || ! -f "$AGENT_TRANSCRIPT" ]] && echo '{"decision":"approve"}' && exit 0
|
|
12
|
+
|
|
13
|
+
# Extract last assistant text response
|
|
14
|
+
LAST_RESPONSE=$(tail -200 "$AGENT_TRANSCRIPT" | jq -rs '
|
|
15
|
+
[.[] | select(.message?.role == "assistant" and .message?.content != null)
|
|
16
|
+
| .message.content[] | select(.text != null) | .text] | last // ""
|
|
17
|
+
' 2>/dev/null || echo "")
|
|
18
|
+
|
|
19
|
+
# === LAYER 1: Extract subagent_type from transcript (fail open) ===
|
|
20
|
+
SUBAGENT_TYPE=""
|
|
21
|
+
if [[ -n "$AGENT_ID" && -n "$MAIN_TRANSCRIPT" && -f "$MAIN_TRANSCRIPT" ]]; then
|
|
22
|
+
PARENT_TOOL_USE_ID=$(grep "\"agentId\":\"$AGENT_ID\"" "$MAIN_TRANSCRIPT" 2>/dev/null | head -1 | jq -r '.parentToolUseID // empty' 2>/dev/null)
|
|
23
|
+
if [[ -n "$PARENT_TOOL_USE_ID" ]]; then
|
|
24
|
+
SUBAGENT_TYPE=$(grep "\"id\":\"$PARENT_TOOL_USE_ID\"" "$MAIN_TRANSCRIPT" 2>/dev/null | \
|
|
25
|
+
grep '"name":"Task"' | \
|
|
26
|
+
jq -r '.message.content[]? | select(.type == "tool_use" and .id == "'"$PARENT_TOOL_USE_ID"'") | .input.subagent_type // empty' 2>/dev/null | \
|
|
27
|
+
head -1)
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# === LAYER 2: Check completion format (backup detection) ===
|
|
32
|
+
HAS_BEAD_COMPLETE=$(echo "$LAST_RESPONSE" | grep -cE "BEAD.*COMPLETE" 2>/dev/null || true)
|
|
33
|
+
HAS_WORKTREE_OR_BRANCH=$(echo "$LAST_RESPONSE" | grep -cE "(Worktree:|Branch:).*bd-" 2>/dev/null || true)
|
|
34
|
+
[[ -z "$HAS_BEAD_COMPLETE" ]] && HAS_BEAD_COMPLETE=0
|
|
35
|
+
[[ -z "$HAS_WORKTREE_OR_BRANCH" ]] && HAS_WORKTREE_OR_BRANCH=0
|
|
36
|
+
|
|
37
|
+
# Determine if this is a supervisor (Layer 1) or has completion format (Layer 2)
|
|
38
|
+
IS_SUPERVISOR="false"
|
|
39
|
+
[[ "$SUBAGENT_TYPE" == *"supervisor"* ]] && IS_SUPERVISOR="true"
|
|
40
|
+
|
|
41
|
+
NEEDS_VERIFICATION="false"
|
|
42
|
+
[[ "$IS_SUPERVISOR" == "true" ]] && NEEDS_VERIFICATION="true"
|
|
43
|
+
[[ "$HAS_BEAD_COMPLETE" -ge 1 && "$HAS_WORKTREE_OR_BRANCH" -ge 1 ]] && NEEDS_VERIFICATION="true"
|
|
44
|
+
|
|
45
|
+
# Skip verification if not needed
|
|
46
|
+
[[ "$NEEDS_VERIFICATION" == "false" ]] && echo '{"decision":"approve"}' && exit 0
|
|
47
|
+
|
|
48
|
+
# Worker supervisor is exempt
|
|
49
|
+
[[ "$SUBAGENT_TYPE" == *"worker"* ]] && echo '{"decision":"approve"}' && exit 0
|
|
50
|
+
|
|
51
|
+
# === VERIFICATION CHECKS ===
|
|
52
|
+
|
|
53
|
+
# Check 1: Completion format required for supervisors
|
|
54
|
+
if [[ "$IS_SUPERVISOR" == "true" ]] && [[ "$HAS_BEAD_COMPLETE" -lt 1 || "$HAS_WORKTREE_OR_BRANCH" -lt 1 ]]; then
|
|
55
|
+
cat << 'EOF'
|
|
56
|
+
{"decision":"block","reason":"Work verification failed: completion report missing.\n\nRequired format:\nBEAD {BEAD_ID} COMPLETE\nWorktree: .worktrees/bd-{BEAD_ID}\nFiles: [list]\nTests: pass\nSummary: [1 sentence]"}
|
|
57
|
+
EOF
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Extract BEAD_ID from response
|
|
62
|
+
BEAD_ID_FROM_RESPONSE=$(echo "$LAST_RESPONSE" | grep -oE "BEAD [A-Za-z0-9._-]+" | head -1 | awk '{print $2}')
|
|
63
|
+
IS_EPIC_CHILD="false"
|
|
64
|
+
[[ "$BEAD_ID_FROM_RESPONSE" == *"."* ]] && IS_EPIC_CHILD="true"
|
|
65
|
+
|
|
66
|
+
# Check 2: Comment required
|
|
67
|
+
HAS_COMMENT=$(grep -c '"bd comment\|"command":"bd comment' "$AGENT_TRANSCRIPT" 2>/dev/null) || HAS_COMMENT=0
|
|
68
|
+
if [[ "$HAS_COMMENT" -lt 1 ]]; then
|
|
69
|
+
cat << 'EOF'
|
|
70
|
+
{"decision":"block","reason":"Work verification failed: no comment on bead.\n\nRun: bd comment {BEAD_ID} \"Completed: [summary]\""}
|
|
71
|
+
EOF
|
|
72
|
+
exit 0
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Check 3: Worktree verification
|
|
76
|
+
REPO_ROOT=$(cd "$(git rev-parse --git-common-dir)/.." 2>/dev/null && pwd)
|
|
77
|
+
WORKTREE_PATH="$REPO_ROOT/.worktrees/bd-${BEAD_ID_FROM_RESPONSE}"
|
|
78
|
+
|
|
79
|
+
if [[ ! -d "$WORKTREE_PATH" ]]; then
|
|
80
|
+
cat << 'EOF'
|
|
81
|
+
{"decision":"block","reason":"Work verification failed: worktree not found.\n\nCreate worktree first via API."}
|
|
82
|
+
EOF
|
|
83
|
+
exit 0
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Check 4: Uncommitted changes
|
|
87
|
+
UNCOMMITTED=$(git -C "$WORKTREE_PATH" status --porcelain 2>/dev/null)
|
|
88
|
+
if [[ -n "$UNCOMMITTED" ]]; then
|
|
89
|
+
cat << 'EOF'
|
|
90
|
+
{"decision":"block","reason":"Work verification failed: uncommitted changes.\n\nRun in worktree:\n git add -A && git commit -m \"...\""}
|
|
91
|
+
EOF
|
|
92
|
+
exit 0
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# Check 5: Remote push
|
|
96
|
+
HAS_REMOTE=$(git -C "$WORKTREE_PATH" remote get-url origin 2>/dev/null)
|
|
97
|
+
if [[ -n "$HAS_REMOTE" ]]; then
|
|
98
|
+
BRANCH="bd-${BEAD_ID_FROM_RESPONSE}"
|
|
99
|
+
REMOTE_EXISTS=$(git -C "$WORKTREE_PATH" ls-remote --heads origin "$BRANCH" 2>/dev/null)
|
|
100
|
+
if [[ -z "$REMOTE_EXISTS" ]]; then
|
|
101
|
+
cat << 'EOF'
|
|
102
|
+
{"decision":"block","reason":"Work verification failed: branch not pushed.\n\nRun: git push -u origin bd-{BEAD_ID}"}
|
|
103
|
+
EOF
|
|
104
|
+
exit 0
|
|
105
|
+
fi
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# Check 6: Bead status
|
|
109
|
+
BEAD_STATUS=$(bd show "$BEAD_ID_FROM_RESPONSE" --json 2>/dev/null | jq -r '.[0].status // "unknown"')
|
|
110
|
+
EXPECTED_STATUS="inreview"
|
|
111
|
+
# Epic children also use inreview (done status not supported in bd)
|
|
112
|
+
if [[ "$BEAD_STATUS" != "$EXPECTED_STATUS" ]]; then
|
|
113
|
+
cat << EOF
|
|
114
|
+
{"decision":"block","reason":"Work verification failed: bead status is '${BEAD_STATUS}'.\n\nRun: bd update ${BEAD_ID_FROM_RESPONSE} --status ${EXPECTED_STATUS}"}
|
|
115
|
+
EOF
|
|
116
|
+
exit 0
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# Check 7: Verbosity limit
|
|
120
|
+
DECODED_RESPONSE=$(printf '%b' "$LAST_RESPONSE")
|
|
121
|
+
LINE_COUNT=$(echo "$DECODED_RESPONSE" | wc -l | tr -d ' ')
|
|
122
|
+
CHAR_COUNT=${#DECODED_RESPONSE}
|
|
123
|
+
|
|
124
|
+
if [[ "$LINE_COUNT" -gt 15 ]] || [[ "$CHAR_COUNT" -gt 800 ]]; then
|
|
125
|
+
cat << EOF
|
|
126
|
+
{"decision":"block","reason":"Work verification failed: response too verbose (${LINE_COUNT} lines, ${CHAR_COUNT} chars). Max: 15 lines, 800 chars."}
|
|
127
|
+
EOF
|
|
128
|
+
exit 0
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
echo '{"decision":"approve"}'
|