@windyroad/itil 0.19.2-preview.196 → 0.19.3-preview.198
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/hooks/hooks.json
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
{ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/bin/check-deps.sh wr-itil wr-risk-scorer" }] }
|
|
5
5
|
],
|
|
6
6
|
"UserPromptSubmit": [
|
|
7
|
-
{ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/itil-assistant-output-gate.sh" }] }
|
|
7
|
+
{ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/itil-assistant-output-gate.sh" }] },
|
|
8
|
+
{ "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/itil-correction-detect.sh" }] }
|
|
8
9
|
],
|
|
9
10
|
"PreToolUse": [
|
|
10
11
|
{
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# P078 / ADR-013 / ADR-038: itil correction-signal UserPromptSubmit gate.
|
|
3
|
+
#
|
|
4
|
+
# When the incoming user prompt carries a strong-affect correction
|
|
5
|
+
# signal (FFS / all-caps imperatives like DO NOT / direct contradiction
|
|
6
|
+
# / exasperation markers / meta-correction "you always|never|keep"),
|
|
7
|
+
# inject a MANDATORY reminder so the assistant OFFERS to capture a
|
|
8
|
+
# problem ticket BEFORE addressing the operational request — the
|
|
9
|
+
# correction is almost always a class-of-behaviour, not a one-off slip,
|
|
10
|
+
# and a ticket preserves the signal as durable WSJF-ranked backlog.
|
|
11
|
+
#
|
|
12
|
+
# Once-per-session full block; terse reminder on subsequent corrections
|
|
13
|
+
# (per ADR-038 progressive disclosure). The full block names the
|
|
14
|
+
# matched pattern so the assistant has the why-this-fired context.
|
|
15
|
+
#
|
|
16
|
+
# Forward-compat: advertises /wr-itil:capture-problem (ADR-032 — not
|
|
17
|
+
# yet shipped) with /wr-itil:manage-problem as the today-target
|
|
18
|
+
# fallback. When capture-problem ships the wording stays valid.
|
|
19
|
+
|
|
20
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
# shellcheck source=lib/session-marker.sh
|
|
22
|
+
source "$SCRIPT_DIR/lib/session-marker.sh"
|
|
23
|
+
# shellcheck source=lib/detectors.sh
|
|
24
|
+
source "$SCRIPT_DIR/lib/detectors.sh"
|
|
25
|
+
|
|
26
|
+
INPUT=$(cat)
|
|
27
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null || echo "")
|
|
28
|
+
PROMPT=$(echo "$INPUT" | jq -r '.prompt // empty' 2>/dev/null || echo "")
|
|
29
|
+
|
|
30
|
+
# Guard: if the prompt carries no correction signal, do nothing.
|
|
31
|
+
# Burning the announcement marker on non-correction prompts would waste
|
|
32
|
+
# the once-per-session budget on no-op context.
|
|
33
|
+
if [ -z "$PROMPT" ]; then
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
MATCHED=$(echo "$PROMPT" | detect_correction_signal)
|
|
38
|
+
if [ -z "$MATCHED" ]; then
|
|
39
|
+
exit 0
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
if has_announced "itil-correction-detect" "$SESSION_ID"; then
|
|
43
|
+
cat <<HOOK_OUTPUT
|
|
44
|
+
MANDATORY: correction signal ($MATCHED). OFFER /wr-itil:capture-problem (fallback /wr-itil:manage-problem) BEFORE addressing the request. See P078.
|
|
45
|
+
HOOK_OUTPUT
|
|
46
|
+
else
|
|
47
|
+
cat <<HOOK_OUTPUT
|
|
48
|
+
INSTRUCTION: MANDATORY — correction signal detected. OFFER ticket capture FIRST.
|
|
49
|
+
DETECTED: incoming user prompt contains a strong-affect correction
|
|
50
|
+
signal (matched pattern: $MATCHED).
|
|
51
|
+
|
|
52
|
+
NON-OPTIONAL RULES:
|
|
53
|
+
|
|
54
|
+
1. BEFORE addressing the operational request in the prompt, OFFER
|
|
55
|
+
to capture a problem ticket for the underlying behavioural pattern.
|
|
56
|
+
Use /wr-itil:capture-problem when shipped (ADR-032 background
|
|
57
|
+
capture pattern). Until then, fall back to /wr-itil:manage-problem
|
|
58
|
+
for synchronous capture.
|
|
59
|
+
|
|
60
|
+
2. The correction is almost always a class-of-behaviour, not a one-off
|
|
61
|
+
slip. Acknowledging verbally and moving on lets the signal decay
|
|
62
|
+
with session context; the same pattern recurs next session and the
|
|
63
|
+
user has to flag it again. A ticket preserves the correction as
|
|
64
|
+
durable, WSJF-ranked backlog.
|
|
65
|
+
|
|
66
|
+
3. The offer is non-blocking — the user can decline — but the offer
|
|
67
|
+
MUST appear before the operational response. Do NOT wait for the
|
|
68
|
+
user to request the ticket themselves; that's exactly the manual-
|
|
69
|
+
policing-AI-output friction P078 closes.
|
|
70
|
+
|
|
71
|
+
4. Suggested phrasing: "Before I address that — want me to capture a
|
|
72
|
+
problem ticket for this pattern?" Then proceed with the operational
|
|
73
|
+
request regardless of the answer.
|
|
74
|
+
|
|
75
|
+
See:
|
|
76
|
+
- ~/.claude/projects/.../memory/feedback_capture_on_correction.md
|
|
77
|
+
- docs/problems/078-assistant-does-not-offer-problem-ticket-on-user-correction.*.md
|
|
78
|
+
- packages/itil/hooks/lib/detectors.sh (CORRECTION_SIGNAL_PATTERNS)
|
|
79
|
+
HOOK_OUTPUT
|
|
80
|
+
mark_announced "itil-correction-detect" "$SESSION_ID"
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
exit 0
|
package/hooks/lib/detectors.sh
CHANGED
|
@@ -51,6 +51,30 @@ DIRECTION_PIN_PATTERNS=(
|
|
|
51
51
|
'\bship it\b'
|
|
52
52
|
)
|
|
53
53
|
|
|
54
|
+
# Correction-signal patterns — strong-affect correction signals in
|
|
55
|
+
# the user's incoming prompt that should trigger an offer to capture
|
|
56
|
+
# a problem ticket for the underlying behavioural pattern (P078).
|
|
57
|
+
# Extracted from feedback_capture_on_correction.md and the P078 ticket
|
|
58
|
+
# Root Cause Analysis "missing trigger" vocabulary.
|
|
59
|
+
#
|
|
60
|
+
# Case-insensitive (grep -Eqi). False-positive budget is accepted per
|
|
61
|
+
# P078 Investigation Tasks (a user message quoting the vocabulary in
|
|
62
|
+
# non-correction context will fire — minor over-report traded for
|
|
63
|
+
# signal reliability).
|
|
64
|
+
CORRECTION_SIGNAL_PATTERNS=(
|
|
65
|
+
'\bFFS\b'
|
|
66
|
+
"for f.{1,3}'?s sake"
|
|
67
|
+
'\bDO NOT\b'
|
|
68
|
+
"\bDON'T\b"
|
|
69
|
+
'\bSTOP\b'
|
|
70
|
+
"that'?s wrong"
|
|
71
|
+
"that'?s not right"
|
|
72
|
+
"you'?re not listening"
|
|
73
|
+
'\byou (always|never|keep)\b'
|
|
74
|
+
'!{2,}'
|
|
75
|
+
'\bno\b.*\bwrong\b'
|
|
76
|
+
)
|
|
77
|
+
|
|
54
78
|
# detect_prose_ask: scans text on stdin for canonical prose-ask
|
|
55
79
|
# phrasings. Exits 0 if any pattern matches, 1 otherwise. Writes the
|
|
56
80
|
# first matched phrase to stdout (for observability in the Stop hook
|
|
@@ -88,3 +112,23 @@ detect_direction_pin() {
|
|
|
88
112
|
done
|
|
89
113
|
return 1
|
|
90
114
|
}
|
|
115
|
+
|
|
116
|
+
# detect_correction_signal: scans text on stdin for strong-affect
|
|
117
|
+
# correction signals (P078). Exits 0 if any pattern matches, 1
|
|
118
|
+
# otherwise. Writes the first matched phrase to stdout for
|
|
119
|
+
# observability in the hook's systemMessage.
|
|
120
|
+
#
|
|
121
|
+
# Usage:
|
|
122
|
+
# if echo "$prompt" | detect_correction_signal > /dev/null; then ... fi
|
|
123
|
+
detect_correction_signal() {
|
|
124
|
+
local text
|
|
125
|
+
text=$(cat)
|
|
126
|
+
local pattern
|
|
127
|
+
for pattern in "${CORRECTION_SIGNAL_PATTERNS[@]}"; do
|
|
128
|
+
if echo "$text" | grep -Eqi -- "$pattern"; then
|
|
129
|
+
echo "$pattern"
|
|
130
|
+
return 0
|
|
131
|
+
fi
|
|
132
|
+
done
|
|
133
|
+
return 1
|
|
134
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# P078: itil-correction-detect.sh UserPromptSubmit hook must detect
|
|
4
|
+
# strong-signal correction patterns in the incoming user prompt
|
|
5
|
+
# (FFS / DO NOT / direct contradiction / exasperation markers /
|
|
6
|
+
# meta-correction "you always|you never|you keep") and inject a
|
|
7
|
+
# MANDATORY systemMessage instructing the assistant to OFFER
|
|
8
|
+
# /wr-itil:capture-problem (with /wr-itil:manage-problem fallback)
|
|
9
|
+
# BEFORE addressing the operational request.
|
|
10
|
+
#
|
|
11
|
+
# Per ADR-038: full block emits once per session; subsequent prompts
|
|
12
|
+
# emit a terse reminder (<250 bytes) preserving the MANDATORY signal,
|
|
13
|
+
# the gate name, and the capture-problem affordance.
|
|
14
|
+
#
|
|
15
|
+
# Per feedback_behavioural_tests.md (P081): these are behavioural
|
|
16
|
+
# assertions — they simulate the hook payload on stdin and assert on
|
|
17
|
+
# what the hook emits, not on the source text of the hook file.
|
|
18
|
+
|
|
19
|
+
setup() {
|
|
20
|
+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
21
|
+
HOOK="$REPO_ROOT/packages/itil/hooks/itil-correction-detect.sh"
|
|
22
|
+
SID="itil-correction-test-$$-$RANDOM"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
teardown() {
|
|
26
|
+
rm -f "/tmp/itil-correction-detect-announced-${SID}"
|
|
27
|
+
rm -f "/tmp/itil-correction-detect-announced-${SID}-alt"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
run_hook() {
|
|
31
|
+
local sid="$1"
|
|
32
|
+
local prompt="$2"
|
|
33
|
+
echo "{\"session_id\":\"$sid\",\"prompt\":$(printf '%s' "$prompt" | jq -Rs .)}" | bash "$HOOK"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@test "correction-detect: 'FFS' triggers full MANDATORY block on first emission" {
|
|
37
|
+
run run_hook "$SID" "FFS, you didn't run the test"
|
|
38
|
+
[ "$status" -eq 0 ]
|
|
39
|
+
[ "${#output}" -gt 300 ]
|
|
40
|
+
[[ "$output" == *"MANDATORY"* ]]
|
|
41
|
+
[[ "$output" == *"capture-problem"* ]] || [[ "$output" == *"manage-problem"* ]]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@test "correction-detect: writes the announcement marker on first emission" {
|
|
45
|
+
run run_hook "$SID" "FFS stop framing it that way"
|
|
46
|
+
[ "$status" -eq 0 ]
|
|
47
|
+
[ -f "/tmp/itil-correction-detect-announced-${SID}" ]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@test "correction-detect: all-caps imperative 'DO NOT' triggers the block" {
|
|
51
|
+
run run_hook "$SID" "DO NOT TELL ME the cache is stale when you haven't installed it"
|
|
52
|
+
[ "$status" -eq 0 ]
|
|
53
|
+
[ "${#output}" -gt 300 ]
|
|
54
|
+
[[ "$output" == *"MANDATORY"* ]]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@test "correction-detect: meta-correction 'you always' triggers the block" {
|
|
58
|
+
run run_hook "$SID" "you always do this — frame your own failures as external"
|
|
59
|
+
[ "$status" -eq 0 ]
|
|
60
|
+
[ "${#output}" -gt 300 ]
|
|
61
|
+
[[ "$output" == *"MANDATORY"* ]]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@test "correction-detect: direct contradiction 'that's wrong' triggers the block" {
|
|
65
|
+
run run_hook "$SID" "no, that's wrong — the cache wasn't refreshed because you didn't install"
|
|
66
|
+
[ "$status" -eq 0 ]
|
|
67
|
+
[ "${#output}" -gt 300 ]
|
|
68
|
+
[[ "$output" == *"MANDATORY"* ]]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@test "correction-detect: exasperation '!!!' triggers the block" {
|
|
72
|
+
run run_hook "$SID" "stop framing your own failure as external!!!"
|
|
73
|
+
[ "$status" -eq 0 ]
|
|
74
|
+
[ "${#output}" -gt 300 ]
|
|
75
|
+
[[ "$output" == *"MANDATORY"* ]]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@test "correction-detect: 'you're not listening' triggers the block" {
|
|
79
|
+
run run_hook "$SID" "you're not listening — install first then check the cache"
|
|
80
|
+
[ "$status" -eq 0 ]
|
|
81
|
+
[ "${#output}" -gt 300 ]
|
|
82
|
+
[[ "$output" == *"MANDATORY"* ]]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@test "correction-detect: second correction prompt in same session emits terse reminder only" {
|
|
86
|
+
run_hook "$SID" "FFS that's not right" >/dev/null
|
|
87
|
+
run run_hook "$SID" "DO NOT do that again"
|
|
88
|
+
[ "$status" -eq 0 ]
|
|
89
|
+
[ "${#output}" -lt 250 ]
|
|
90
|
+
[[ "$output" == *"capture-problem"* ]] || [[ "$output" == *"manage-problem"* ]]
|
|
91
|
+
[[ "$output" != *"NON-OPTIONAL RULES"* ]]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@test "correction-detect: terse reminder preserves MANDATORY / REQUIRED signal word" {
|
|
95
|
+
run_hook "$SID" "FFS" >/dev/null
|
|
96
|
+
run run_hook "$SID" "you keep doing this"
|
|
97
|
+
[ "$status" -eq 0 ]
|
|
98
|
+
[[ "$output" == *"MANDATORY"* ]] || [[ "$output" == *"REQUIRED"* ]] || [[ "$output" == *"NON-OPTIONAL"* ]]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@test "correction-detect: emitted block names capture-problem and manage-problem fallback" {
|
|
102
|
+
run run_hook "$SID" "FFS!"
|
|
103
|
+
[ "$status" -eq 0 ]
|
|
104
|
+
[[ "$output" == *"capture-problem"* ]]
|
|
105
|
+
[[ "$output" == *"manage-problem"* ]]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@test "correction-detect: different session_id re-emits the full block" {
|
|
109
|
+
run_hook "$SID" "FFS" >/dev/null
|
|
110
|
+
local SID2="${SID}-alt"
|
|
111
|
+
run run_hook "$SID2" "FFS"
|
|
112
|
+
[ "$status" -eq 0 ]
|
|
113
|
+
[ "${#output}" -gt 300 ]
|
|
114
|
+
rm -f "/tmp/itil-correction-detect-announced-${SID2}"
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@test "correction-detect: empty session_id emits the full block and writes no marker" {
|
|
118
|
+
run run_hook "" "FFS, that's wrong"
|
|
119
|
+
[ "$status" -eq 0 ]
|
|
120
|
+
[ "${#output}" -gt 300 ]
|
|
121
|
+
[ ! -f "/tmp/itil-correction-detect-announced-" ]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@test "correction-detect: plain conversational prompt does not emit a block" {
|
|
125
|
+
run run_hook "$SID" "what does ADR-013 say about ambiguous decisions?"
|
|
126
|
+
[ "$status" -eq 0 ]
|
|
127
|
+
[ -z "$output" ]
|
|
128
|
+
[ ! -f "/tmp/itil-correction-detect-announced-${SID}" ]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@test "correction-detect: direction-pin 'yes' alone does not trigger" {
|
|
132
|
+
run run_hook "$SID" "yes, go ahead and update the ticket"
|
|
133
|
+
[ "$status" -eq 0 ]
|
|
134
|
+
[ -z "$output" ]
|
|
135
|
+
[ ! -f "/tmp/itil-correction-detect-announced-${SID}" ]
|
|
136
|
+
}
|
package/package.json
CHANGED