@windyroad/itil 0.56.3 → 0.57.0-preview.923
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
|
@@ -59,6 +59,10 @@
|
|
|
59
59
|
"matcher": "Bash",
|
|
60
60
|
"hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/itil-commit-trailer-transition-advisory.sh" }]
|
|
61
61
|
},
|
|
62
|
+
{
|
|
63
|
+
"matcher": "Bash",
|
|
64
|
+
"hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/itil-fix-title-lifecycle-advisory.sh" }]
|
|
65
|
+
},
|
|
62
66
|
{
|
|
63
67
|
"matcher": "Write|Edit|MultiEdit",
|
|
64
68
|
"hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/itil-fictional-defer-detect.sh" }]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# wr-itil — PostToolUse:Bash hook (P345/RFC-044/STORY-038). Detects the
|
|
3
|
+
# fix-without-paired-lifecycle-transition drift: a just-landed commit whose
|
|
4
|
+
# subject is fix-typed (`fix: ...` / `fix(<pkg>): ...`) and names P<NNN>
|
|
5
|
+
# ticket(s) that are still in `docs/problems/open/` — i.e. the commit claims
|
|
6
|
+
# to fix a ticket but carried no Open -> Known Error (or -> Verifying)
|
|
7
|
+
# transition in the same grain. The ticket then ages across release + CI
|
|
8
|
+
# verification until a catch-up cadence notices (P345; P334 witness chain).
|
|
9
|
+
#
|
|
10
|
+
# ADVISORY-ONLY per ADR-092: Open -> Known Error asserts a knowledge claim
|
|
11
|
+
# (root cause known + workaround documented) that a fix-titled commit does
|
|
12
|
+
# NOT establish, so this hook must never auto-fire the transition and never
|
|
13
|
+
# block the commit. The hook DETECTS and ADVISES; a skill
|
|
14
|
+
# (/wr-itil:transition-problem) — or the human — PERFORMS the transition
|
|
15
|
+
# (ADR-014 hook-detects-skill-commits grain). Sibling (copy-and-retarget) of
|
|
16
|
+
# itil-commit-trailer-transition-advisory.sh (P378): different signal —
|
|
17
|
+
# commit-title P<NNN> tokens vs Refs: trailers.
|
|
18
|
+
#
|
|
19
|
+
# Advisory-only (ADR-013 Rule 6 fail-open; ADR-045 ≤300-byte TOTAL emission
|
|
20
|
+
# even with multiple named tickets; exit 0 on every path).
|
|
21
|
+
# Bypass: BYPASS_FIX_TITLE_LIFECYCLE_ADVISORY=1.
|
|
22
|
+
#
|
|
23
|
+
# @adr ADR-092 (advisory-only posture + binding rule) ADR-014 (hook detects,
|
|
24
|
+
# skill commits) ADR-045 (budget) ADR-013 (Rule 6 fail-open) ADR-052 (bats)
|
|
25
|
+
# @problem P345 @rfc RFC-044 @story STORY-038
|
|
26
|
+
|
|
27
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
28
|
+
# shellcheck source=lib/command-detect.sh
|
|
29
|
+
source "$SCRIPT_DIR/lib/command-detect.sh"
|
|
30
|
+
|
|
31
|
+
INPUT=$(cat)
|
|
32
|
+
TOOL_NAME=$(printf '%s' "$INPUT" | python3 -c "import sys,json
|
|
33
|
+
try: print(json.load(sys.stdin).get('tool_name',''))
|
|
34
|
+
except: print('')" 2>/dev/null || echo "")
|
|
35
|
+
[ "$TOOL_NAME" = "Bash" ] || exit 0
|
|
36
|
+
|
|
37
|
+
COMMAND=$(printf '%s' "$INPUT" | python3 -c "import sys,json
|
|
38
|
+
try: print(json.load(sys.stdin).get('tool_input',{}).get('command',''))
|
|
39
|
+
except: print('')" 2>/dev/null || echo "")
|
|
40
|
+
command_invokes_git_commit "$COMMAND" || exit 0
|
|
41
|
+
[ "${BYPASS_FIX_TITLE_LIFECYCLE_ADVISORY:-}" = "1" ] && exit 0
|
|
42
|
+
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
|
|
43
|
+
[ -d "./docs/problems/open" ] || exit 0
|
|
44
|
+
|
|
45
|
+
SUBJECT=$(git log -1 --format='%s' HEAD 2>/dev/null) || exit 0
|
|
46
|
+
case "$SUBJECT" in
|
|
47
|
+
"fix: "*|"fix("*) ;;
|
|
48
|
+
*) exit 0 ;;
|
|
49
|
+
esac
|
|
50
|
+
|
|
51
|
+
PIDS=$(printf '%s' "$SUBJECT" | grep -oE 'P[0-9]{3}' | sort -u || true)
|
|
52
|
+
[ -n "$PIDS" ] || exit 0
|
|
53
|
+
|
|
54
|
+
OPEN_IDS=""
|
|
55
|
+
OPEN_COUNT=0
|
|
56
|
+
while IFS= read -r pid; do
|
|
57
|
+
[ -n "$pid" ] || continue
|
|
58
|
+
num="${pid#P}"
|
|
59
|
+
shopt -s nullglob; files=(./docs/problems/open/${num}-*.md); shopt -u nullglob
|
|
60
|
+
if [ ${#files[@]} -gt 0 ]; then
|
|
61
|
+
OPEN_COUNT=$((OPEN_COUNT + 1))
|
|
62
|
+
# Cap the named IDs at 3 so TOTAL emission stays ≤300 bytes (ADR-045).
|
|
63
|
+
if [ "$OPEN_COUNT" -le 3 ]; then
|
|
64
|
+
OPEN_IDS="${OPEN_IDS:+$OPEN_IDS,}$pid"
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
done <<< "$PIDS"
|
|
68
|
+
[ "$OPEN_COUNT" -gt 0 ] || exit 0
|
|
69
|
+
[ "$OPEN_COUNT" -gt 3 ] && OPEN_IDS="${OPEN_IDS}+$((OPEN_COUNT - 3))"
|
|
70
|
+
|
|
71
|
+
echo "P345 ADVISORY: fix-titled commit names ${OPEN_IDS} still Open — no paired lifecycle transition landed. Transition it (/wr-itil:transition-problem <NNN> known-error) or document why not. Bypass: BYPASS_FIX_TITLE_LIFECYCLE_ADVISORY=1." >&2
|
|
72
|
+
exit 0
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
|
|
3
|
+
# P345/RFC-044/STORY-038: itil-fix-title-lifecycle-advisory.sh — post-commit
|
|
4
|
+
# ADVISORY for fix-titled commits whose named P<NNN> ticket is still Open on
|
|
5
|
+
# disk (no paired lifecycle transition in the commit). Advisory-only per
|
|
6
|
+
# ADR-092: never blocks, exit 0 on every path. Behavioural (ADR-052).
|
|
7
|
+
|
|
8
|
+
setup() {
|
|
9
|
+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
10
|
+
HOOK="$REPO_ROOT/packages/itil/hooks/itil-fix-title-lifecycle-advisory.sh"
|
|
11
|
+
DIR="$(mktemp -d)"; cd "$DIR"
|
|
12
|
+
git init -q; git config user.email t@e.x; git config user.name t
|
|
13
|
+
mkdir -p docs/problems/open docs/problems/known-error
|
|
14
|
+
echo x > seed; git add -A; git commit -qm "chore: seed"
|
|
15
|
+
}
|
|
16
|
+
teardown() { cd /; rm -rf "$DIR"; }
|
|
17
|
+
|
|
18
|
+
# Feed the hook a PostToolUse Bash payload for a `git commit` command.
|
|
19
|
+
run_hook() {
|
|
20
|
+
printf '{"tool_name":"Bash","tool_input":{"command":"git commit -m x"}}' | bash "$HOOK"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
commit() { git commit -q --allow-empty -m "$1"; }
|
|
24
|
+
|
|
25
|
+
@test "advises when a fix-titled commit names a still-Open ticket" {
|
|
26
|
+
echo t > docs/problems/open/501-widget-breaks.md
|
|
27
|
+
commit "fix(itil): P501 stop the widget breaking"
|
|
28
|
+
run run_hook
|
|
29
|
+
[ "$status" -eq 0 ]
|
|
30
|
+
[[ "$output" == *"P501"* ]]
|
|
31
|
+
[[ "$output" == *"Open"* ]]
|
|
32
|
+
[[ "$output" == *"transition"* ]]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@test "silent when the named ticket is known-error (transition already happened)" {
|
|
36
|
+
echo t > docs/problems/known-error/501-widget-breaks.md
|
|
37
|
+
commit "fix(itil): P501 stop the widget breaking"
|
|
38
|
+
run run_hook
|
|
39
|
+
[ "$status" -eq 0 ]
|
|
40
|
+
[ -z "$output" ]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@test "silent when the named ticket does not exist on disk" {
|
|
44
|
+
commit "fix(itil): P999 phantom ticket"
|
|
45
|
+
run run_hook
|
|
46
|
+
[ "$status" -eq 0 ]
|
|
47
|
+
[ -z "$output" ]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@test "silent on a non-fix-typed subject naming a P token" {
|
|
51
|
+
echo t > docs/problems/open/501-widget-breaks.md
|
|
52
|
+
commit "docs(problems): P501 add investigation notes"
|
|
53
|
+
run run_hook
|
|
54
|
+
[ "$status" -eq 0 ]
|
|
55
|
+
[ -z "$output" ]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@test "silent on a fix-typed subject with no P token" {
|
|
59
|
+
echo t > docs/problems/open/501-widget-breaks.md
|
|
60
|
+
commit "fix(itil): tidy the widget"
|
|
61
|
+
run run_hook
|
|
62
|
+
[ "$status" -eq 0 ]
|
|
63
|
+
[ -z "$output" ]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@test "advises on the bare fix: form too" {
|
|
67
|
+
echo t > docs/problems/open/502-gadget-stuck.md
|
|
68
|
+
commit "fix: P502 unstick the gadget"
|
|
69
|
+
run run_hook
|
|
70
|
+
[ "$status" -eq 0 ]
|
|
71
|
+
[[ "$output" == *"P502"* ]]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@test "total emission stays at or under 300 bytes with many still-Open tickets" {
|
|
75
|
+
for n in 511 512 513 514 515 516; do
|
|
76
|
+
echo t > "docs/problems/open/${n}-thing-${n}.md"
|
|
77
|
+
done
|
|
78
|
+
commit "fix(itil): P511 P512 P513 P514 P515 P516 mega sweep"
|
|
79
|
+
run run_hook
|
|
80
|
+
[ "$status" -eq 0 ]
|
|
81
|
+
[ -n "$output" ]
|
|
82
|
+
[ "${#output}" -le 300 ]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@test "bypass env var suppresses the advisory" {
|
|
86
|
+
echo t > docs/problems/open/501-widget-breaks.md
|
|
87
|
+
commit "fix(itil): P501 stop the widget breaking"
|
|
88
|
+
run env BYPASS_FIX_TITLE_LIFECYCLE_ADVISORY=1 bash -c 'printf "{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git commit -m x\"}}" | bash "$0"' "$HOOK"
|
|
89
|
+
[ "$status" -eq 0 ]
|
|
90
|
+
[ -z "$output" ]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@test "silent on non-Bash tool payloads" {
|
|
94
|
+
echo t > docs/problems/open/501-widget-breaks.md
|
|
95
|
+
commit "fix(itil): P501 stop the widget breaking"
|
|
96
|
+
run bash -c 'printf "{\"tool_name\":\"Write\",\"tool_input\":{}}" | bash "$0"' "$HOOK"
|
|
97
|
+
[ "$status" -eq 0 ]
|
|
98
|
+
[ -z "$output" ]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@test "silent on non-commit Bash commands" {
|
|
102
|
+
echo t > docs/problems/open/501-widget-breaks.md
|
|
103
|
+
commit "fix(itil): P501 stop the widget breaking"
|
|
104
|
+
run bash -c 'printf "{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"git log --oneline\"}}" | bash "$0"' "$HOOK"
|
|
105
|
+
[ "$status" -eq 0 ]
|
|
106
|
+
[ -z "$output" ]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@test "fail-open silent when docs/problems is absent" {
|
|
110
|
+
rm -rf docs/problems
|
|
111
|
+
commit "fix(itil): P501 stop the widget breaking"
|
|
112
|
+
run run_hook
|
|
113
|
+
[ "$status" -eq 0 ]
|
|
114
|
+
[ -z "$output" ]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@test "fail-open silent outside a git work tree" {
|
|
118
|
+
NOGIT="$(mktemp -d)"; cd "$NOGIT"
|
|
119
|
+
run run_hook
|
|
120
|
+
[ "$status" -eq 0 ]
|
|
121
|
+
[ -z "$output" ]
|
|
122
|
+
cd /; rm -rf "$NOGIT"
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@test "fail-open silent on malformed JSON input" {
|
|
126
|
+
run bash -c 'printf "not json at all" | bash "$0"' "$HOOK"
|
|
127
|
+
[ "$status" -eq 0 ]
|
|
128
|
+
[ -z "$output" ]
|
|
129
|
+
}
|
package/package.json
CHANGED