@windyroad/itil 0.19.2 → 0.19.3

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "wr-itil",
3
- "version": "0.19.2",
3
+ "version": "0.19.3",
4
4
  "description": "ITIL-aligned IT service management for Claude Code"
5
5
  }
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
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/itil",
3
- "version": "0.19.2",
3
+ "version": "0.19.3",
4
4
  "description": "ITIL-aligned IT service management for Claude Code (problem, and future incident/change skills)",
5
5
  "bin": {
6
6
  "windyroad-itil": "./bin/install.mjs"