@windyroad/itil 0.30.2 → 0.30.3-preview.319
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/.claude-plugin/plugin.json +1 -1
- package/bin/wr-itil-plugin-exercise-index +2 -0
- package/bin/wr-itil-skill-invocations +2 -0
- package/hooks/hooks.json +2 -1
- package/hooks/itil-mid-loop-ask-detect.sh +142 -0
- package/hooks/test/itil-mid-loop-ask-detect.bats +220 -0
- package/package.json +1 -1
- package/scripts/plugin-exercise-index.sh +347 -0
- package/scripts/skill-invocations.sh +255 -0
- package/scripts/test/plugin-exercise-index.bats +386 -0
- package/scripts/test/skill-invocations.bats +320 -0
package/hooks/hooks.json
CHANGED
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
}
|
|
53
53
|
],
|
|
54
54
|
"Stop": [
|
|
55
|
-
{ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/itil-assistant-output-review.sh" }] }
|
|
55
|
+
{ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/itil-assistant-output-review.sh" }] },
|
|
56
|
+
{ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/itil-mid-loop-ask-detect.sh" }] }
|
|
56
57
|
]
|
|
57
58
|
}
|
|
58
59
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# P132 Phase 2b — wr-itil Stop hook.
|
|
3
|
+
#
|
|
4
|
+
# Detects orchestrator main-turn `AskUserQuestion` calls fired mid-loop
|
|
5
|
+
# inside `/wr-itil:work-problems` — the regression class P132 captures
|
|
6
|
+
# (2026-05-17 reopen evidence: orchestrator asked iter-target selection
|
|
7
|
+
# between iters 3 and 4; halted the AFK loop for hours of user time).
|
|
8
|
+
#
|
|
9
|
+
# Detection signal (all three must hold):
|
|
10
|
+
# 1. Last assistant turn in the transcript contains an
|
|
11
|
+
# AskUserQuestion tool_use.
|
|
12
|
+
# 2. Earlier in the transcript an assistant message issued a `Skill`
|
|
13
|
+
# tool_use to `wr-itil:work-problems` (orchestrator activation).
|
|
14
|
+
# 3. No `ALL_DONE` or `## Work Problems Summary` terminal marker has
|
|
15
|
+
# been emitted since the orchestrator activated (the orchestrator
|
|
16
|
+
# is still mid-loop, not in its post-loop wrap-up).
|
|
17
|
+
#
|
|
18
|
+
# When all three match the hook emits a structured `stopReason`
|
|
19
|
+
# advisory citing P130 + the Mid-loop ask discipline subsection of
|
|
20
|
+
# `packages/itil/skills/work-problems/SKILL.md`. Advisory ONLY — the
|
|
21
|
+
# hook NEVER blocks. The next assistant turn reads the stopReason in
|
|
22
|
+
# its context and self-corrects (queue the question to
|
|
23
|
+
# outstanding_questions and continue iterating).
|
|
24
|
+
#
|
|
25
|
+
# Mirrors the sibling `itil-assistant-output-review.sh` Stop hook
|
|
26
|
+
# precedent (P085 prose-ask detection) on transcript-extraction shape
|
|
27
|
+
# and stopReason emit format.
|
|
28
|
+
#
|
|
29
|
+
# References:
|
|
30
|
+
# P132 — this hook (Phase 2b structural enforcement).
|
|
31
|
+
# P130 — orchestrator presence-aware dispatch; named in advisory.
|
|
32
|
+
# ADR-013 — Rule 1 (interactive default) + Rule 6 (AFK fail-safe).
|
|
33
|
+
# ADR-044 — framework-resolution boundary; named in advisory.
|
|
34
|
+
# ADR-045 — hook injection budget; advisory honour-system band.
|
|
35
|
+
# ADR-052 — behavioural tests default.
|
|
36
|
+
# ADR-005 — plugin testing strategy.
|
|
37
|
+
|
|
38
|
+
# Per-surface configuration. Extending coverage to other orchestrators
|
|
39
|
+
# (run-retro Step 4b Stage 1, /install-updates Step 6a) is a future
|
|
40
|
+
# Phase 2c/2d variant — copy this hook + retarget these two vars.
|
|
41
|
+
ORCHESTRATOR_SKILL="wr-itil:work-problems"
|
|
42
|
+
TERMINAL_MARKER_RE='ALL_DONE|## Work Problems Summary'
|
|
43
|
+
|
|
44
|
+
INPUT=$(cat)
|
|
45
|
+
TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty' 2>/dev/null || echo "")
|
|
46
|
+
|
|
47
|
+
# Graceful fallback: no transcript_path or file missing means nothing
|
|
48
|
+
# to inspect. Exit clean — hook is advisory, never blocking.
|
|
49
|
+
if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Empty transcript → silent exit.
|
|
54
|
+
[ -s "$TRANSCRIPT_PATH" ] || exit 0
|
|
55
|
+
|
|
56
|
+
# Identify the LAST assistant turn. Each transcript line is a JSON
|
|
57
|
+
# object {type, message, ...}; assistant turns have type=="assistant".
|
|
58
|
+
# Malformed lines are silently tolerated by jq's `-c` + `2>/dev/null`.
|
|
59
|
+
LAST_ASSISTANT=$(grep -E '"type"[[:space:]]*:[[:space:]]*"assistant"' "$TRANSCRIPT_PATH" 2>/dev/null | tail -n 1 || true)
|
|
60
|
+
if [ -z "$LAST_ASSISTANT" ]; then
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Signal 1: last assistant turn contains AskUserQuestion tool_use.
|
|
65
|
+
if ! echo "$LAST_ASSISTANT" | jq -e '
|
|
66
|
+
.message.content
|
|
67
|
+
| if type == "array" then
|
|
68
|
+
map(select(.type == "tool_use" and .name == "AskUserQuestion")) | length > 0
|
|
69
|
+
else false end
|
|
70
|
+
' >/dev/null 2>&1; then
|
|
71
|
+
exit 0
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# Signal 2: any earlier assistant message contains a Skill tool_use to
|
|
75
|
+
# the configured orchestrator skill. Iterate every assistant line; on
|
|
76
|
+
# the first match set ORCH_LINE_NUM to its 1-based line index.
|
|
77
|
+
ORCH_LINE_NUM=0
|
|
78
|
+
LINE_NUM=0
|
|
79
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
80
|
+
LINE_NUM=$((LINE_NUM + 1))
|
|
81
|
+
# Skip non-assistant + malformed lines.
|
|
82
|
+
echo "$line" | grep -qE '"type"[[:space:]]*:[[:space:]]*"assistant"' || continue
|
|
83
|
+
echo "$line" | jq -e . >/dev/null 2>&1 || continue
|
|
84
|
+
if echo "$line" | jq -e --arg s "$ORCHESTRATOR_SKILL" '
|
|
85
|
+
.message.content
|
|
86
|
+
| if type == "array" then
|
|
87
|
+
map(select(.type == "tool_use" and .name == "Skill" and (.input.skill // .input.skillName // "") == $s)) | length > 0
|
|
88
|
+
else false end
|
|
89
|
+
' >/dev/null 2>&1; then
|
|
90
|
+
ORCH_LINE_NUM=$LINE_NUM
|
|
91
|
+
break
|
|
92
|
+
fi
|
|
93
|
+
done < "$TRANSCRIPT_PATH"
|
|
94
|
+
|
|
95
|
+
if [ "$ORCH_LINE_NUM" -eq 0 ]; then
|
|
96
|
+
exit 0
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Signal 3: no terminal marker emitted AFTER orchestrator activation.
|
|
100
|
+
# Scan lines strictly after ORCH_LINE_NUM up to the line BEFORE the
|
|
101
|
+
# last assistant turn (the AskUserQuestion turn itself shouldn't be
|
|
102
|
+
# considered a terminal-marker source — its content is a tool_use, not
|
|
103
|
+
# the orchestrator's final summary). If the AskUserQuestion turn IS
|
|
104
|
+
# the same turn that emits ALL_DONE, that's structurally implausible
|
|
105
|
+
# (you don't ask while wrapping up) — the conservative read is to
|
|
106
|
+
# scan up to and including all prior turns.
|
|
107
|
+
TOTAL_LINES=$(wc -l < "$TRANSCRIPT_PATH" | tr -d ' ')
|
|
108
|
+
# Slice: from ORCH_LINE_NUM+1 to TOTAL_LINES-1 (exclude the final
|
|
109
|
+
# assistant turn carrying the AskUserQuestion). Edge case: if the
|
|
110
|
+
# orchestrator-activation line IS the last-assistant line, the
|
|
111
|
+
# AskUserQuestion would be in the same turn as the Skill call —
|
|
112
|
+
# impossible in practice. In that degenerate case the slice is empty
|
|
113
|
+
# and no terminal marker is seen → advise.
|
|
114
|
+
SLICE_END=$((TOTAL_LINES - 1))
|
|
115
|
+
TERMINAL_SEEN="no"
|
|
116
|
+
if [ "$SLICE_END" -ge "$ORCH_LINE_NUM" ]; then
|
|
117
|
+
if sed -n "$((ORCH_LINE_NUM + 1)),${SLICE_END}p" "$TRANSCRIPT_PATH" \
|
|
118
|
+
| grep -qE "$TERMINAL_MARKER_RE" 2>/dev/null; then
|
|
119
|
+
TERMINAL_SEEN="yes"
|
|
120
|
+
fi
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
if [ "$TERMINAL_SEEN" = "yes" ]; then
|
|
124
|
+
exit 0
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# All three signals present → emit advisory stopReason. The next
|
|
128
|
+
# assistant turn reads this in its context and self-corrects.
|
|
129
|
+
# Voice-tone target ~600 bytes; ADR-045 honour-system slack < 1000.
|
|
130
|
+
jq -n '{
|
|
131
|
+
stopReason: (
|
|
132
|
+
"MID-LOOP ASK DETECTED: AskUserQuestion fired inside /wr-itil:work-problems orchestrator main turn. " +
|
|
133
|
+
"Per P130 + Mid-loop ask discipline subsection of work-problems SKILL.md, mid-loop AskUserQuestion is " +
|
|
134
|
+
"forbidden except at framework-prescribed halt points (Step 0 session-continuity, Step 2.5/2.5b loop-end emit, " +
|
|
135
|
+
"Step 6.5 above-appetite Rule 5 / CI failure, Step 6.75 dirty halt). " +
|
|
136
|
+
"If at a halt point, this advisory is inapplicable. " +
|
|
137
|
+
"If mid-loop, queue the question to outstanding_questions and continue iterating. " +
|
|
138
|
+
"See ADR-044 framework-resolution boundary."
|
|
139
|
+
)
|
|
140
|
+
}'
|
|
141
|
+
|
|
142
|
+
exit 0
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# P132 Phase 2b: itil-mid-loop-ask-detect.sh Stop hook detects
|
|
4
|
+
# orchestrator main-turn AskUserQuestion calls fired mid-loop inside
|
|
5
|
+
# /wr-itil:work-problems — the regression class P132 captures
|
|
6
|
+
# (2026-05-17 reopen: orchestrator asked iter-target selection between
|
|
7
|
+
# iters 3 and 4; halted the loop for hours of AFK time).
|
|
8
|
+
#
|
|
9
|
+
# Detection signal:
|
|
10
|
+
# 1. Last assistant turn contains an AskUserQuestion tool_use
|
|
11
|
+
# 2. Earlier transcript contains a Skill tool_use to wr-itil:work-problems
|
|
12
|
+
# 3. No `ALL_DONE` / `## Work Problems Summary` marker has been emitted
|
|
13
|
+
# since the skill activation (mid-loop, not post-loop wrap)
|
|
14
|
+
#
|
|
15
|
+
# When all three match the hook emits a structured `stopReason`
|
|
16
|
+
# advisory citing P130 + the Mid-loop ask discipline subsection of
|
|
17
|
+
# work-problems SKILL.md. Advisory only — never blocks. Mirrors the
|
|
18
|
+
# itil-assistant-output-review.sh Stop hook precedent (P085 prose-ask
|
|
19
|
+
# detection) but on a different signal class.
|
|
20
|
+
#
|
|
21
|
+
# Per ADR-005 / ADR-052 — bats live under packages/<plugin>/hooks/test/
|
|
22
|
+
# and assert behaviour on emitted JSON, not source-content. Per
|
|
23
|
+
# feedback_behavioural_tests.md (P081) — no source-grep on hook text.
|
|
24
|
+
|
|
25
|
+
setup() {
|
|
26
|
+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
27
|
+
HOOK="$REPO_ROOT/packages/itil/hooks/itil-mid-loop-ask-detect.sh"
|
|
28
|
+
TMPDIR_="$(mktemp -d)"
|
|
29
|
+
TRANSCRIPT="$TMPDIR_/transcript.jsonl"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
teardown() {
|
|
33
|
+
rm -rf "$TMPDIR_"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Helper: emit the JSONL transcript line for an assistant turn
|
|
37
|
+
# containing a Skill tool_use to wr-itil:work-problems.
|
|
38
|
+
emit_orchestrator_activation() {
|
|
39
|
+
cat <<'JSON'
|
|
40
|
+
{"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","name":"Skill","input":{"skill":"wr-itil:work-problems"}}]}}
|
|
41
|
+
JSON
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Helper: emit a benign assistant text turn (no tool_use).
|
|
45
|
+
emit_text_turn() {
|
|
46
|
+
local text="$1"
|
|
47
|
+
printf '{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":%s}]}}\n' \
|
|
48
|
+
"$(printf '%s' "$text" | jq -Rs .)"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Helper: emit an assistant turn containing an AskUserQuestion tool_use.
|
|
52
|
+
emit_ask_turn() {
|
|
53
|
+
local header="${1:-Next problem}"
|
|
54
|
+
cat <<JSON
|
|
55
|
+
{"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","name":"AskUserQuestion","input":{"questions":[{"question":"Pick next iter","header":"${header}","options":[{"label":"A","description":"x"},{"label":"B","description":"y"}]}]}}]}}
|
|
56
|
+
JSON
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
run_hook() {
|
|
60
|
+
echo "{\"session_id\":\"mid-loop-test\",\"transcript_path\":$(printf '%s' "$TRANSCRIPT" | jq -Rs .)}" | bash "$HOOK"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# --- Positive detection ---
|
|
64
|
+
|
|
65
|
+
@test "detect: orchestrator activation + final AskUserQuestion + no terminal marker emits stopReason" {
|
|
66
|
+
{
|
|
67
|
+
printf '{"type":"user","message":{"role":"user","content":"/wr-itil:work-problems"}}\n'
|
|
68
|
+
emit_orchestrator_activation
|
|
69
|
+
emit_text_turn "Iter 1 complete. Dispatching iter 2."
|
|
70
|
+
emit_text_turn "Iter 2 complete. Dispatching iter 3."
|
|
71
|
+
emit_ask_turn "Pick next iter"
|
|
72
|
+
} > "$TRANSCRIPT"
|
|
73
|
+
run run_hook
|
|
74
|
+
[ "$status" -eq 0 ]
|
|
75
|
+
[[ "$output" == *"stopReason"* ]]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@test "detect: stopReason cites P130 + framework-prescribed halt points" {
|
|
79
|
+
{
|
|
80
|
+
printf '{"type":"user","message":{"role":"user","content":"/wr-itil:work-problems"}}\n'
|
|
81
|
+
emit_orchestrator_activation
|
|
82
|
+
emit_text_turn "Iter 1 done."
|
|
83
|
+
emit_ask_turn "Next problem"
|
|
84
|
+
} > "$TRANSCRIPT"
|
|
85
|
+
run run_hook
|
|
86
|
+
[ "$status" -eq 0 ]
|
|
87
|
+
[[ "$output" == *"P130"* ]]
|
|
88
|
+
[[ "$output" == *"halt point"* ]]
|
|
89
|
+
[[ "$output" == *"outstanding_questions"* ]]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@test "detect: stopReason cites ADR-044 framework-resolution boundary" {
|
|
93
|
+
{
|
|
94
|
+
printf '{"type":"user","message":{"role":"user","content":"/wr-itil:work-problems"}}\n'
|
|
95
|
+
emit_orchestrator_activation
|
|
96
|
+
emit_text_turn "Iter 1 done."
|
|
97
|
+
emit_ask_turn "Pick"
|
|
98
|
+
} > "$TRANSCRIPT"
|
|
99
|
+
run run_hook
|
|
100
|
+
[ "$status" -eq 0 ]
|
|
101
|
+
[[ "$output" == *"ADR-044"* ]]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# --- Negative paths: silent exit ---
|
|
105
|
+
|
|
106
|
+
@test "allow: transcript with no orchestrator activation exits silently" {
|
|
107
|
+
# User asked a question outside /wr-itil:work-problems context;
|
|
108
|
+
# AskUserQuestion in this case is unrelated to mid-loop discipline.
|
|
109
|
+
{
|
|
110
|
+
printf '{"type":"user","message":{"role":"user","content":"help me design a feature"}}\n'
|
|
111
|
+
emit_text_turn "Two paths come to mind."
|
|
112
|
+
emit_ask_turn "Design choice"
|
|
113
|
+
} > "$TRANSCRIPT"
|
|
114
|
+
run run_hook
|
|
115
|
+
[ "$status" -eq 0 ]
|
|
116
|
+
[[ "$output" != *"stopReason"* ]]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@test "allow: orchestrator activated then ALL_DONE emitted (post-loop wrap)" {
|
|
120
|
+
# The orchestrator has emitted its terminal summary; subsequent
|
|
121
|
+
# AskUserQuestion is post-loop interactive follow-up, not mid-loop.
|
|
122
|
+
{
|
|
123
|
+
printf '{"type":"user","message":{"role":"user","content":"/wr-itil:work-problems"}}\n'
|
|
124
|
+
emit_orchestrator_activation
|
|
125
|
+
emit_text_turn "Iter 1 complete."
|
|
126
|
+
emit_text_turn "## Work Problems Summary
|
|
127
|
+
All iters complete.
|
|
128
|
+
ALL_DONE"
|
|
129
|
+
emit_ask_turn "Follow-up question"
|
|
130
|
+
} > "$TRANSCRIPT"
|
|
131
|
+
run run_hook
|
|
132
|
+
[ "$status" -eq 0 ]
|
|
133
|
+
[[ "$output" != *"stopReason"* ]]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@test "allow: orchestrator activated then Work Problems Summary header emitted (post-loop)" {
|
|
137
|
+
{
|
|
138
|
+
printf '{"type":"user","message":{"role":"user","content":"/wr-itil:work-problems"}}\n'
|
|
139
|
+
emit_orchestrator_activation
|
|
140
|
+
emit_text_turn "## Work Problems Summary
|
|
141
|
+
|
|
142
|
+
Completed: 3 iters."
|
|
143
|
+
emit_ask_turn "Next steps?"
|
|
144
|
+
} > "$TRANSCRIPT"
|
|
145
|
+
run run_hook
|
|
146
|
+
[ "$status" -eq 0 ]
|
|
147
|
+
[[ "$output" != *"stopReason"* ]]
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@test "allow: last assistant turn does not contain AskUserQuestion" {
|
|
151
|
+
{
|
|
152
|
+
printf '{"type":"user","message":{"role":"user","content":"/wr-itil:work-problems"}}\n'
|
|
153
|
+
emit_orchestrator_activation
|
|
154
|
+
emit_text_turn "Iter 2 dispatched."
|
|
155
|
+
} > "$TRANSCRIPT"
|
|
156
|
+
run run_hook
|
|
157
|
+
[ "$status" -eq 0 ]
|
|
158
|
+
[[ "$output" != *"stopReason"* ]]
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@test "allow: missing transcript_path exits silently" {
|
|
162
|
+
run bash -c 'echo "{\"session_id\":\"sid\"}" | bash "$1"' -- "$HOOK"
|
|
163
|
+
[ "$status" -eq 0 ]
|
|
164
|
+
[[ "$output" != *"stopReason"* ]]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@test "allow: non-existent transcript file exits silently" {
|
|
168
|
+
run bash -c "echo '{\"session_id\":\"sid\",\"transcript_path\":\"/tmp/does-not-exist-$RANDOM\"}' | bash '$HOOK'"
|
|
169
|
+
[ "$status" -eq 0 ]
|
|
170
|
+
[[ "$output" != *"stopReason"* ]]
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@test "allow: empty transcript exits silently" {
|
|
174
|
+
: > "$TRANSCRIPT"
|
|
175
|
+
run run_hook
|
|
176
|
+
[ "$status" -eq 0 ]
|
|
177
|
+
[[ "$output" != *"stopReason"* ]]
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@test "allow: malformed JSONL lines do not crash the hook" {
|
|
181
|
+
{
|
|
182
|
+
echo "not-json"
|
|
183
|
+
emit_orchestrator_activation
|
|
184
|
+
emit_text_turn "Iter 2"
|
|
185
|
+
} > "$TRANSCRIPT"
|
|
186
|
+
run run_hook
|
|
187
|
+
[ "$status" -eq 0 ]
|
|
188
|
+
# Either silent OR stopReason — but never a crash. We assert no
|
|
189
|
+
# non-zero exit; the silent-vs-stopReason split is incidental here.
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
# --- Advisory budget per ADR-045 ---
|
|
193
|
+
|
|
194
|
+
@test "advisory output stays under ADR-045 800-byte honour-system band" {
|
|
195
|
+
{
|
|
196
|
+
printf '{"type":"user","message":{"role":"user","content":"/wr-itil:work-problems"}}\n'
|
|
197
|
+
emit_orchestrator_activation
|
|
198
|
+
emit_text_turn "Iter 1 done."
|
|
199
|
+
emit_ask_turn "Pick"
|
|
200
|
+
} > "$TRANSCRIPT"
|
|
201
|
+
run run_hook
|
|
202
|
+
[ "$status" -eq 0 ]
|
|
203
|
+
[[ "$output" == *"stopReason"* ]]
|
|
204
|
+
[ "${#output}" -lt 1000 ]
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# --- Distinguishing-marker: tool_name for AskUserQuestion --
|
|
208
|
+
|
|
209
|
+
@test "detect: matches even when AskUserQuestion is intermixed with text blocks" {
|
|
210
|
+
{
|
|
211
|
+
printf '{"type":"user","message":{"role":"user","content":"/wr-itil:work-problems"}}\n'
|
|
212
|
+
emit_orchestrator_activation
|
|
213
|
+
cat <<'JSON'
|
|
214
|
+
{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Two iter targets are tied on WSJF:"},{"type":"tool_use","name":"AskUserQuestion","input":{"questions":[{"question":"Pick next iter","header":"Next iter","options":[{"label":"P132","description":"x"},{"label":"P130","description":"y"}]}]}}]}}
|
|
215
|
+
JSON
|
|
216
|
+
} > "$TRANSCRIPT"
|
|
217
|
+
run run_hook
|
|
218
|
+
[ "$status" -eq 0 ]
|
|
219
|
+
[[ "$output" == *"stopReason"* ]]
|
|
220
|
+
}
|
package/package.json
CHANGED