@windyroad/retrospective 0.1.2-preview.13 → 0.1.3-preview.26
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/README.md +55 -0
- package/hooks/hooks.json +0 -3
- package/package.json +1 -1
- package/skills/{wr:retrospective → run-retro}/SKILL.md +1 -1
- package/hooks/lib/gate-helpers.sh +0 -174
- package/hooks/lib/review-gate.sh +0 -102
- package/hooks/review-plan-enforce.sh +0 -73
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# @windyroad/retrospective
|
|
2
|
+
|
|
3
|
+
**Session retrospectives for Claude Code.** Captures learnings at the end of each session and creates problem tickets for failures and friction.
|
|
4
|
+
|
|
5
|
+
Part of [Windy Road Agent Plugins](../../README.md).
|
|
6
|
+
|
|
7
|
+
## What It Does
|
|
8
|
+
|
|
9
|
+
Every coding session produces learnings -- things that went well, things that broke, things that were harder than expected. Without a retrospective, those learnings evaporate.
|
|
10
|
+
|
|
11
|
+
The retrospective plugin:
|
|
12
|
+
|
|
13
|
+
- **Reminds** you to run a retro when a session ends
|
|
14
|
+
- **Updates** `docs/BRIEFING.md` with session learnings so future sessions start with context
|
|
15
|
+
- **Creates problem tickets** (via [`@windyroad/problem`](../problem/)) for failures and friction encountered during the session
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx @windyroad/retrospective
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Restart Claude Code after installing.
|
|
24
|
+
|
|
25
|
+
> **Requires:** [`@windyroad/problem`](../problem/) and [`@windyroad/risk-scorer`](../risk-scorer/). The installer warns if they're missing.
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
**Run a session retrospective:**
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
/wr-retrospective:run-retro
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This walks through the session's work, identifies what went well and what didn't, updates `docs/BRIEFING.md`, and creates problem tickets for any failures.
|
|
36
|
+
|
|
37
|
+
The plugin also triggers a reminder via a `Stop` hook when a session ends naturally.
|
|
38
|
+
|
|
39
|
+
## How It Works
|
|
40
|
+
|
|
41
|
+
| Hook | Trigger | What it does |
|
|
42
|
+
|------|---------|-------------|
|
|
43
|
+
| `check-deps.sh` | Session start | Verifies that `wr-problem` and `wr-risk-scorer` are installed |
|
|
44
|
+
| `retrospective-reminder.sh` | Session end | Reminds you to run a retrospective |
|
|
45
|
+
|
|
46
|
+
## Updating and Uninstalling
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx @windyroad/retrospective --update
|
|
50
|
+
npx @windyroad/retrospective --uninstall
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Licence
|
|
54
|
+
|
|
55
|
+
[MIT](../../LICENSE)
|
package/hooks/hooks.json
CHANGED
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
"SessionStart": [
|
|
4
4
|
{ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/bin/check-deps.sh wr-retrospective wr-problem wr-risk-scorer" }] }
|
|
5
5
|
],
|
|
6
|
-
"PreToolUse": [
|
|
7
|
-
{ "matcher": "ExitPlanMode", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/review-plan-enforce.sh" }] }
|
|
8
|
-
],
|
|
9
6
|
"Stop": [
|
|
10
7
|
{ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/retrospective-reminder.sh" }] }
|
|
11
8
|
]
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: wr:
|
|
2
|
+
name: wr-retrospective:run-retro
|
|
3
3
|
description: Run a session retrospective. Updates docs/BRIEFING.md with learnings and creates problem tickets for failures and friction.
|
|
4
4
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Skill
|
|
5
5
|
---
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Shared portable helpers for gate enforcement hooks.
|
|
3
|
-
# Sourced by architect-gate.sh, risk-gate.sh, and all hook scripts.
|
|
4
|
-
# Provides: _mtime, _hashcmd, _doc_exclusions, _err_trap, _get_*
|
|
5
|
-
|
|
6
|
-
# ---------------------------------------------------------------------------
|
|
7
|
-
# Portable utilities
|
|
8
|
-
# ---------------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
# Portable mtime: tries GNU stat, falls back to macOS stat
|
|
11
|
-
_mtime() { stat -c%Y "$1" 2>/dev/null || /usr/bin/stat -f%m "$1" 2>/dev/null || echo 0; }
|
|
12
|
-
|
|
13
|
-
# Portable hash: tries md5sum, falls back to md5 -r, then shasum
|
|
14
|
-
_hashcmd() { md5sum 2>/dev/null || md5 -r 2>/dev/null || shasum 2>/dev/null; }
|
|
15
|
-
|
|
16
|
-
# Paths excluded from pipeline state hashing and docs-only detection.
|
|
17
|
-
_doc_exclusions() {
|
|
18
|
-
echo ':!docs/' ':!.risk-reports/' ':!.changeset/' ':!governance/' ':!.claude/plans/' ':!CLAUDE.md' ':!AGENTS.md' ':!PRINCIPLES.md' ':!DECISION-MANAGEMENT.md' ':!AGENTIC_RISK_REGISTER.md' ':!PROBLEM-MANAGEMENT.md'
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
# ---------------------------------------------------------------------------
|
|
22
|
-
# ERR trap: outputs diagnostic JSON on hook errors (P010)
|
|
23
|
-
# Usage: source gate-helpers.sh at top of hook, then call _enable_err_trap
|
|
24
|
-
# ---------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
_enable_err_trap() {
|
|
27
|
-
trap '_err_trap_handler "$BASH_SOURCE" "$LINENO" "$BASH_COMMAND"' ERR
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
_err_trap_handler() {
|
|
31
|
-
local script="$1" line="$2" cmd="$3"
|
|
32
|
-
local name
|
|
33
|
-
name=$(basename "$script" 2>/dev/null || echo "$script")
|
|
34
|
-
# Output diagnostic as systemMessage so it's visible in conversation
|
|
35
|
-
cat <<EOF
|
|
36
|
-
{
|
|
37
|
-
"systemMessage": "Hook error in ${name} at line ${line}: ${cmd}"
|
|
38
|
-
}
|
|
39
|
-
EOF
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
# ---------------------------------------------------------------------------
|
|
43
|
-
# JSON input parsing: standardised helpers replacing inline python3
|
|
44
|
-
# Each reads from _HOOK_INPUT (set by the hook before calling these)
|
|
45
|
-
# ---------------------------------------------------------------------------
|
|
46
|
-
|
|
47
|
-
# Store hook input for reuse by parsing helpers
|
|
48
|
-
_HOOK_INPUT=""
|
|
49
|
-
|
|
50
|
-
_parse_input() {
|
|
51
|
-
_HOOK_INPUT=$(cat)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
_get_tool_name() {
|
|
55
|
-
echo "$_HOOK_INPUT" | python3 -c "
|
|
56
|
-
import sys, json
|
|
57
|
-
try:
|
|
58
|
-
data = json.load(sys.stdin)
|
|
59
|
-
print(data.get('tool_name', ''))
|
|
60
|
-
except:
|
|
61
|
-
print('')
|
|
62
|
-
" 2>/dev/null || echo ""
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
_get_session_id() {
|
|
66
|
-
echo "$_HOOK_INPUT" | python3 -c "
|
|
67
|
-
import sys, json
|
|
68
|
-
try:
|
|
69
|
-
data = json.load(sys.stdin)
|
|
70
|
-
print(data.get('session_id', ''))
|
|
71
|
-
except:
|
|
72
|
-
print('')
|
|
73
|
-
" 2>/dev/null || echo ""
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
_get_command() {
|
|
77
|
-
echo "$_HOOK_INPUT" | python3 -c "
|
|
78
|
-
import sys, json
|
|
79
|
-
try:
|
|
80
|
-
data = json.load(sys.stdin)
|
|
81
|
-
print(data.get('tool_input', {}).get('command', ''))
|
|
82
|
-
except:
|
|
83
|
-
print('')
|
|
84
|
-
" 2>/dev/null || echo ""
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
_get_file_path() {
|
|
88
|
-
echo "$_HOOK_INPUT" | python3 -c "
|
|
89
|
-
import sys, json
|
|
90
|
-
try:
|
|
91
|
-
data = json.load(sys.stdin)
|
|
92
|
-
ti = data.get('tool_input', {})
|
|
93
|
-
print(ti.get('file_path', ti.get('path', '')))
|
|
94
|
-
except:
|
|
95
|
-
print('')
|
|
96
|
-
" 2>/dev/null || echo ""
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
_get_subagent_type() {
|
|
100
|
-
echo "$_HOOK_INPUT" | python3 -c "
|
|
101
|
-
import sys, json
|
|
102
|
-
try:
|
|
103
|
-
data = json.load(sys.stdin)
|
|
104
|
-
print(data.get('tool_input', {}).get('subagent_type', ''))
|
|
105
|
-
except:
|
|
106
|
-
print('')
|
|
107
|
-
" 2>/dev/null || echo ""
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
_get_user_prompt() {
|
|
111
|
-
echo "$_HOOK_INPUT" | python3 -c "
|
|
112
|
-
import sys, json
|
|
113
|
-
try:
|
|
114
|
-
data = json.load(sys.stdin)
|
|
115
|
-
print(data.get('user_prompt', ''))
|
|
116
|
-
except:
|
|
117
|
-
print('')
|
|
118
|
-
" 2>/dev/null || echo ""
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
_get_tool_output() {
|
|
122
|
-
echo "$_HOOK_INPUT" | python3 -c "
|
|
123
|
-
import sys, json
|
|
124
|
-
try:
|
|
125
|
-
data = json.load(sys.stdin)
|
|
126
|
-
# PostToolUse provides tool_response (dict with content array), not tool_output
|
|
127
|
-
tr = data.get('tool_response', {})
|
|
128
|
-
if isinstance(tr, dict):
|
|
129
|
-
content = tr.get('content', [])
|
|
130
|
-
if isinstance(content, list):
|
|
131
|
-
texts = [c.get('text', '') for c in content if isinstance(c, dict) and c.get('type') == 'text']
|
|
132
|
-
if texts:
|
|
133
|
-
print('\n'.join(texts))
|
|
134
|
-
sys.exit(0)
|
|
135
|
-
# Fallback for older/different hook formats
|
|
136
|
-
print(data.get('tool_output', ''))
|
|
137
|
-
except:
|
|
138
|
-
print('')
|
|
139
|
-
" 2>/dev/null || echo ""
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
# ---------------------------------------------------------------------------
|
|
143
|
-
# Session-scoped tmp directory for risk files
|
|
144
|
-
# ---------------------------------------------------------------------------
|
|
145
|
-
|
|
146
|
-
# Returns the session-scoped directory for risk temp files.
|
|
147
|
-
# Creates the directory if it doesn't exist.
|
|
148
|
-
# Usage: DIR=$(_risk_dir "$SESSION_ID"); echo "1" > "$DIR/commit"
|
|
149
|
-
_risk_dir() {
|
|
150
|
-
local sid="$1"
|
|
151
|
-
local dir="${TMPDIR:-/tmp}/claude-risk-${sid}"
|
|
152
|
-
mkdir -p "$dir"
|
|
153
|
-
echo "$dir"
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
# ---------------------------------------------------------------------------
|
|
157
|
-
# Non-doc file detection for WIP gating
|
|
158
|
-
# ---------------------------------------------------------------------------
|
|
159
|
-
|
|
160
|
-
_is_doc_file() {
|
|
161
|
-
local file_path="$1"
|
|
162
|
-
local EXCL
|
|
163
|
-
EXCL=$(_doc_exclusions)
|
|
164
|
-
for pattern in $EXCL; do
|
|
165
|
-
local clean="${pattern#:!}"
|
|
166
|
-
case "$file_path" in
|
|
167
|
-
*"$clean"*) return 0 ;;
|
|
168
|
-
esac
|
|
169
|
-
done
|
|
170
|
-
case "$file_path" in
|
|
171
|
-
*.claude/*|*.risk-reports/*|*RISK-POLICY.md) return 0 ;;
|
|
172
|
-
esac
|
|
173
|
-
return 1
|
|
174
|
-
}
|
package/hooks/lib/review-gate.sh
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Shared gate logic for review enforcement hooks (a11y, voice-tone, style-guide).
|
|
3
|
-
# Sourced by *-enforce-edit.sh hooks and review-plan-enforce.sh.
|
|
4
|
-
# Provides: check_review_gate, review_gate_deny, review_gate_parse_error
|
|
5
|
-
|
|
6
|
-
# Source shared portable helpers (_mtime, _hashcmd)
|
|
7
|
-
_REVIEW_GATE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
-
source "$_REVIEW_GATE_DIR/gate-helpers.sh"
|
|
9
|
-
|
|
10
|
-
# Check review gate marker. Returns 0 if marker is valid (allow), 1 if invalid (deny).
|
|
11
|
-
# Sets REVIEW_GATE_REASON on failure.
|
|
12
|
-
# Usage: check_review_gate "$SESSION_ID" "style-guide" "docs/STYLE-GUIDE.md"
|
|
13
|
-
check_review_gate() {
|
|
14
|
-
local SESSION_ID="$1"
|
|
15
|
-
local SYSTEM="$2" # e.g., "a11y", "voice-tone", "style-guide"
|
|
16
|
-
local POLICY_FILE="$3" # e.g., "docs/STYLE-GUIDE.md"
|
|
17
|
-
local MARKER="/tmp/${SYSTEM}-reviewed-${SESSION_ID}"
|
|
18
|
-
local HASH_FILE="/tmp/${SYSTEM}-reviewed-${SESSION_ID}.hash"
|
|
19
|
-
local TTL_SECONDS="${REVIEW_TTL:-600}"
|
|
20
|
-
|
|
21
|
-
# 1. Marker must exist
|
|
22
|
-
if [ ! -f "$MARKER" ]; then
|
|
23
|
-
REVIEW_GATE_REASON="No ${SYSTEM} review marker found. The ${SYSTEM} agent must review first."
|
|
24
|
-
return 1
|
|
25
|
-
fi
|
|
26
|
-
|
|
27
|
-
# 2. TTL check — marker mtime must be within TTL
|
|
28
|
-
local NOW=$(date +%s)
|
|
29
|
-
local MARKER_TIME=$(_mtime "$MARKER")
|
|
30
|
-
local AGE=$(( NOW - MARKER_TIME ))
|
|
31
|
-
if [ "$AGE" -ge "$TTL_SECONDS" ]; then
|
|
32
|
-
rm -f "$MARKER" "$HASH_FILE"
|
|
33
|
-
REVIEW_GATE_REASON="${SYSTEM} review expired (${AGE}s old, TTL ${TTL_SECONDS}s). Re-run the ${SYSTEM} agent."
|
|
34
|
-
return 1
|
|
35
|
-
fi
|
|
36
|
-
|
|
37
|
-
# 3. Drift detection — policy file hash must match
|
|
38
|
-
if [ -f "$HASH_FILE" ] && [ -n "$POLICY_FILE" ]; then
|
|
39
|
-
local STORED_HASH=$(cat "$HASH_FILE")
|
|
40
|
-
local CURRENT_HASH=""
|
|
41
|
-
if [ -f "$POLICY_FILE" ]; then
|
|
42
|
-
CURRENT_HASH=$(cat "$POLICY_FILE" | _hashcmd | cut -d' ' -f1)
|
|
43
|
-
elif [ -d "$POLICY_FILE" ]; then
|
|
44
|
-
# Directory (e.g., docs/decisions/) — hash all .md files
|
|
45
|
-
CURRENT_HASH=$(find "$POLICY_FILE" -name '*.md' -not -name 'README.md' -print0 | sort -z | xargs -0 cat 2>/dev/null | _hashcmd | cut -d' ' -f1)
|
|
46
|
-
else
|
|
47
|
-
CURRENT_HASH="missing"
|
|
48
|
-
fi
|
|
49
|
-
if [ "$STORED_HASH" != "$CURRENT_HASH" ]; then
|
|
50
|
-
rm -f "$MARKER" "$HASH_FILE"
|
|
51
|
-
REVIEW_GATE_REASON="${SYSTEM} policy file changed since last review. Re-run the ${SYSTEM} agent."
|
|
52
|
-
return 1
|
|
53
|
-
fi
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
# Slide TTL window forward
|
|
57
|
-
touch "$MARKER"
|
|
58
|
-
return 0
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
# Store policy file hash after a successful review.
|
|
62
|
-
# Usage: store_review_hash "$SESSION_ID" "style-guide" "docs/STYLE-GUIDE.md"
|
|
63
|
-
store_review_hash() {
|
|
64
|
-
local SESSION_ID="$1"
|
|
65
|
-
local SYSTEM="$2"
|
|
66
|
-
local POLICY_FILE="$3"
|
|
67
|
-
local HASH_FILE="/tmp/${SYSTEM}-reviewed-${SESSION_ID}.hash"
|
|
68
|
-
|
|
69
|
-
if [ -n "$POLICY_FILE" ]; then
|
|
70
|
-
local HASH=""
|
|
71
|
-
if [ -f "$POLICY_FILE" ]; then
|
|
72
|
-
HASH=$(cat "$POLICY_FILE" | _hashcmd | cut -d' ' -f1)
|
|
73
|
-
elif [ -d "$POLICY_FILE" ]; then
|
|
74
|
-
HASH=$(find "$POLICY_FILE" -name '*.md' -not -name 'README.md' -print0 | sort -z | xargs -0 cat 2>/dev/null | _hashcmd | cut -d' ' -f1)
|
|
75
|
-
else
|
|
76
|
-
HASH="missing"
|
|
77
|
-
fi
|
|
78
|
-
echo "$HASH" > "$HASH_FILE"
|
|
79
|
-
fi
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
# Emit fail-closed deny JSON for PreToolUse hooks.
|
|
83
|
-
review_gate_deny() {
|
|
84
|
-
local REASON="$1"
|
|
85
|
-
cat <<EOF
|
|
86
|
-
{
|
|
87
|
-
"hookSpecificOutput": {
|
|
88
|
-
"hookEventName": "PreToolUse",
|
|
89
|
-
"permissionDecision": "deny",
|
|
90
|
-
"permissionDecisionReason": "$REASON"
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
EOF
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
# Emit fail-closed deny JSON for parse failures.
|
|
97
|
-
review_gate_parse_error() {
|
|
98
|
-
cat <<'EOF'
|
|
99
|
-
{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny",
|
|
100
|
-
"permissionDecisionReason": "BLOCKED: Could not parse hook input. Gate is fail-closed." } }
|
|
101
|
-
EOF
|
|
102
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# PreToolUse hook: Denies ExitPlanMode until review specialists have
|
|
3
|
-
# reviewed the plan. Skips UI specialists (a11y, voice-tone, style-guide,
|
|
4
|
-
# jtbd) when the plan only touches non-UI files (P008 optimization).
|
|
5
|
-
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
-
source "$SCRIPT_DIR/lib/review-gate.sh"
|
|
8
|
-
source "$SCRIPT_DIR/lib/gate-helpers.sh"
|
|
9
|
-
|
|
10
|
-
INPUT=$(cat)
|
|
11
|
-
|
|
12
|
-
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty') || true
|
|
13
|
-
|
|
14
|
-
if [ -z "$SESSION_ID" ]; then
|
|
15
|
-
review_gate_parse_error
|
|
16
|
-
exit 0
|
|
17
|
-
fi
|
|
18
|
-
|
|
19
|
-
# Detect if the plan touches UI files by checking uncommitted changes
|
|
20
|
-
# UI patterns: *.html, *.jsx, *.tsx, *.vue, *.svelte, *.astro, *.css, *.scss
|
|
21
|
-
HAS_UI_FILES=false
|
|
22
|
-
UI_PATTERNS='\.html$|\.jsx$|\.tsx$|\.vue$|\.svelte$|\.astro$|\.css$|\.scss$|\.ejs$|\.hbs$|\.erb$|\.leaf$'
|
|
23
|
-
if git diff --cached --name-only 2>/dev/null | grep -qE "$UI_PATTERNS"; then
|
|
24
|
-
HAS_UI_FILES=true
|
|
25
|
-
elif git diff --name-only 2>/dev/null | grep -qE "$UI_PATTERNS"; then
|
|
26
|
-
HAS_UI_FILES=true
|
|
27
|
-
elif git ls-files --others --exclude-standard 2>/dev/null | grep -qE "$UI_PATTERNS"; then
|
|
28
|
-
HAS_UI_FILES=true
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
# Also check the plan file itself for mentions of UI files
|
|
32
|
-
PLAN_DIR="$HOME/.claude/plans"
|
|
33
|
-
if [ -d "$PLAN_DIR" ] && [ "$HAS_UI_FILES" = false ]; then
|
|
34
|
-
LATEST_PLAN=$(ls -t "$PLAN_DIR"/*.md 2>/dev/null | head -1)
|
|
35
|
-
if [ -n "$LATEST_PLAN" ] && grep -qiE '\.html|\.jsx|\.tsx|\.vue|\.svelte|\.css|component|page|form|modal|dialog' "$LATEST_PLAN" 2>/dev/null; then
|
|
36
|
-
HAS_UI_FILES=true
|
|
37
|
-
fi
|
|
38
|
-
fi
|
|
39
|
-
|
|
40
|
-
MISSING=""
|
|
41
|
-
|
|
42
|
-
if [ "$HAS_UI_FILES" = true ]; then
|
|
43
|
-
# UI files detected — require all specialists
|
|
44
|
-
for SYSTEM in a11y voice-tone style-guide jtbd; do
|
|
45
|
-
MARKER="/tmp/${SYSTEM}-plan-reviewed-${SESSION_ID}"
|
|
46
|
-
if [ ! -f "$MARKER" ]; then
|
|
47
|
-
case "$SYSTEM" in
|
|
48
|
-
a11y) AGENT="accessibility-agents:accessibility-lead" ;;
|
|
49
|
-
voice-tone) AGENT="voice-and-tone-lead" ;;
|
|
50
|
-
style-guide) AGENT="style-guide-lead" ;;
|
|
51
|
-
jtbd) AGENT="jtbd-lead" ;;
|
|
52
|
-
esac
|
|
53
|
-
if [ -z "$MISSING" ]; then
|
|
54
|
-
MISSING="$AGENT"
|
|
55
|
-
else
|
|
56
|
-
MISSING="${MISSING}, ${AGENT}"
|
|
57
|
-
fi
|
|
58
|
-
fi
|
|
59
|
-
done
|
|
60
|
-
else
|
|
61
|
-
# No UI files — skip a11y, voice-tone, style-guide, jtbd
|
|
62
|
-
# Auto-create their markers so the gate passes
|
|
63
|
-
for SYSTEM in a11y voice-tone style-guide jtbd; do
|
|
64
|
-
touch "/tmp/${SYSTEM}-plan-reviewed-${SESSION_ID}"
|
|
65
|
-
done
|
|
66
|
-
fi
|
|
67
|
-
|
|
68
|
-
if [ -n "$MISSING" ]; then
|
|
69
|
-
review_gate_deny "BLOCKED: Cannot approve plan without specialist review. Missing: ${MISSING}. Delegate to each agent to review the plan."
|
|
70
|
-
exit 0
|
|
71
|
-
fi
|
|
72
|
-
|
|
73
|
-
exit 0
|