@windyroad/itil 0.23.1-preview.251 → 0.23.1-preview.252
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.
|
@@ -117,5 +117,20 @@ if check_create_gate "$SESSION_ID"; then
|
|
|
117
117
|
exit 0
|
|
118
118
|
fi
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
# P144 / ADR-048: gate-misfire recovery hint. When SOME marker exists (for
|
|
121
|
+
# any SID) but the gate denies, the agent is likely hitting the P124 Phase 3
|
|
122
|
+
# helper regression — `mark_step2_complete` succeeded but the marker landed
|
|
123
|
+
# under the wrong UUID. Append a recovery pointer to the deny message so
|
|
124
|
+
# the agent finds the documented two-tier procedure in SKILL.md Step 2
|
|
125
|
+
# substep 7 instead of reaching for the brute-force-marker anti-pattern
|
|
126
|
+
# (139-marker incident, 2026-04-28 P144 driver evidence).
|
|
127
|
+
#
|
|
128
|
+
# Routine first-creation deny (no marker for ANY SID in this session)
|
|
129
|
+
# leaves the deny message unchanged — the helper-bug signal is conditional.
|
|
130
|
+
RECOVERY_HINT=""
|
|
131
|
+
if compgen -G '/tmp/manage-problem-grep-*' > /dev/null 2>&1; then
|
|
132
|
+
RECOVERY_HINT=" (Helper succeeded but SID mismatch detected — see manage-problem SKILL.md Step 2 substep 7.)"
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
create_gate_deny "BLOCKED: Cannot Write '${BASENAME}' under docs/problems/ without running /wr-itil:manage-problem Step 2 (duplicate-check) first. New problem tickets MUST be created via the skill so the duplicate-prevention grep fires before the file lands. Invoke the Skill tool with skill='wr-itil:manage-problem' and a description of the new problem; Step 2 will grep for related existing tickets and surface any matches via AskUserQuestion before creating the new ticket. (P119)${RECOVERY_HINT}"
|
|
121
136
|
exit 0
|
|
@@ -186,3 +186,64 @@ set_marker() {
|
|
|
186
186
|
[ "$status" -eq 0 ]
|
|
187
187
|
[[ "$output" != *"BLOCKED"* ]]
|
|
188
188
|
}
|
|
189
|
+
|
|
190
|
+
# --- P144 / ADR-048: gate-misfire recovery hint on deny message ---
|
|
191
|
+
#
|
|
192
|
+
# When the deny fires AND any /tmp/manage-problem-grep-* marker exists for
|
|
193
|
+
# SOME SID, that's the helper-bug signal (P124 Phase 3 regression — helper
|
|
194
|
+
# returned wrong SID, marker exists but doesn't match runtime hook stdin).
|
|
195
|
+
# The deny message appends a recovery pointer to direct the agent at the
|
|
196
|
+
# documented two-tier procedure in SKILL.md Step 2 substep 7.
|
|
197
|
+
#
|
|
198
|
+
# Routine first-creation deny (no marker exists for any SID at all) is
|
|
199
|
+
# unchanged — recovery hint MUST NOT appear.
|
|
200
|
+
|
|
201
|
+
setup_other_sid_marker() {
|
|
202
|
+
OTHER_SID="other-sid-$$-$RANDOM"
|
|
203
|
+
: > "/tmp/manage-problem-grep-${OTHER_SID}"
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
teardown_other_sid_marker() {
|
|
207
|
+
if [ -n "${OTHER_SID:-}" ]; then
|
|
208
|
+
rm -f "/tmp/manage-problem-grep-${OTHER_SID}"
|
|
209
|
+
fi
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@test "deny without ANY /tmp/manage-problem-grep-* marker → deny message OMITS recovery hint" {
|
|
213
|
+
# Scrub any markers so the helper-bug signal cannot fire.
|
|
214
|
+
rm -f /tmp/manage-problem-grep-*
|
|
215
|
+
run run_write_hook "$PWD/docs/problems/999-foo.open.md" "$SID"
|
|
216
|
+
[ "$status" -eq 0 ]
|
|
217
|
+
[[ "$output" == *"BLOCKED"* ]]
|
|
218
|
+
# No marker exists for any SID → routine first-creation deny → no recovery hint.
|
|
219
|
+
[[ "$output" != *"SID mismatch"* ]]
|
|
220
|
+
[[ "$output" != *"Step 2 substep 7"* ]]
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@test "deny with /tmp/manage-problem-grep-* marker for OTHER SID → deny message INCLUDES recovery hint" {
|
|
224
|
+
# Scrub other markers first, then set a marker for a different SID.
|
|
225
|
+
rm -f /tmp/manage-problem-grep-*
|
|
226
|
+
setup_other_sid_marker
|
|
227
|
+
run run_write_hook "$PWD/docs/problems/999-foo.open.md" "$SID"
|
|
228
|
+
status=$?
|
|
229
|
+
teardown_other_sid_marker
|
|
230
|
+
[ "$status" -eq 0 ]
|
|
231
|
+
[[ "$output" == *"BLOCKED"* ]]
|
|
232
|
+
# Marker exists for OTHER SID → helper-bug signal → recovery hint appended.
|
|
233
|
+
[[ "$output" == *"SID mismatch"* ]]
|
|
234
|
+
[[ "$output" == *"Step 2 substep 7"* ]]
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@test "recovery hint avoids ADR-038 jargon (no internal P-number jargon in deny string)" {
|
|
238
|
+
# ADR-038 progressive disclosure — deny stays terse + actionable. Architect
|
|
239
|
+
# advisory rejected "P124-Phase-3-regression" wording in favour of plain
|
|
240
|
+
# "Helper succeeded but SID mismatch detected".
|
|
241
|
+
rm -f /tmp/manage-problem-grep-*
|
|
242
|
+
setup_other_sid_marker
|
|
243
|
+
run run_write_hook "$PWD/docs/problems/999-foo.open.md" "$SID"
|
|
244
|
+
status=$?
|
|
245
|
+
teardown_other_sid_marker
|
|
246
|
+
[ "$status" -eq 0 ]
|
|
247
|
+
[[ "$output" == *"BLOCKED"* ]]
|
|
248
|
+
[[ "$output" != *"P124-Phase-3-regression"* ]]
|
|
249
|
+
}
|
package/package.json
CHANGED
|
@@ -269,6 +269,47 @@ Before creating, search existing problems for similar issues. The user may not k
|
|
|
269
269
|
|
|
270
270
|
**Why a helper instead of inline `${CLAUDE_SESSION_ID:-default}`**: the agent's process does NOT export `CLAUDE_SESSION_ID` today; the hook side reads `session_id` from its stdin JSON payload (per the Claude Code PreToolUse contract). The prior fallback wrote the marker under `default` while the hook checked the real UUID — mismatch caused the Write deny on every first ticket of a session until the agent ad-hoc scraped a UUID-bearing marker. The helper canonicalises that scrape so every agent context discovers the SID the same way. P124.
|
|
271
271
|
|
|
272
|
+
<!-- supersedes-when: P142 ships -->
|
|
273
|
+
**Recovery if hook denial persists** (P144 / ADR-048 — auto-supersedes when P142 ships):
|
|
274
|
+
|
|
275
|
+
The P124 helper has a Phase 3 regression in orchestrator sessions that have dispatched subprocesses: it sometimes returns a subprocess SID instead of the orchestrator SID, while the runtime hook stdin still contains the orchestrator SID. The marker lands under the wrong UUID; the next `Write` is denied even though `mark_step2_complete` succeeded. The hook deny message includes a `(Helper succeeded but SID mismatch detected — see manage-problem SKILL.md Step 2 substep 7.)` pointer when this signal is observable.
|
|
276
|
+
|
|
277
|
+
**Gate-misfire signal** — recovery applies ONLY when ALL three conditions hold:
|
|
278
|
+
1. The agent is **already executing** `/wr-itil:manage-problem` Step 2 in this turn (i.e., the SKILL contract has just ordered the grep for THIS ticket creation — not a marker carried over from a prior unrelated invocation in the same session).
|
|
279
|
+
2. `mark_step2_complete` succeeded (the helper exited zero — no SID-discovery error).
|
|
280
|
+
3. The subsequent `Write` to the new `.<status>.md` file is denied by the P119 hook.
|
|
281
|
+
|
|
282
|
+
Routine creation flow does NOT match these conditions and MUST continue through the standard `Write` path. The recovery is mechanical (deterministic from the gate-misfire signal — no `AskUserQuestion` required, per ADR-044's framework-mediated surface catalog extension).
|
|
283
|
+
|
|
284
|
+
**First-tier recovery — announce-marker scrape**:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# Discover the orchestrator session UUID via the most-reliable per-session announce marker.
|
|
288
|
+
# The orchestrator SID is what the runtime hook stdin contains in the common subprocess case.
|
|
289
|
+
sid=$(ls -t /tmp/itil-assistant-gate-announced-* 2>/dev/null | head -1 | sed 's|.*itil-assistant-gate-announced-||')
|
|
290
|
+
[ -n "$sid" ] && touch "/tmp/manage-problem-grep-${sid}"
|
|
291
|
+
# Retry the Write.
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Second-tier recovery — python3-via-Bash file-write** (2026-04-29 evidence: runtime hook stdin SID may not be in any announce-marker class; first-tier returns the orchestrator SID, but the runtime SID is a different per-Write SID surfaced only by `architect-reviewed-*` mtime, not by any announce-marker):
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
# The hook is PreToolUse:Write; python3-in-Bash is not a Write tool call,
|
|
298
|
+
# so the hook never fires. Use only when first-tier fails.
|
|
299
|
+
python3 -c "from pathlib import Path; Path('docs/problems/<NNN>-<title>.open.md').write_text('''<file body>''')"
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Audit-trail-preservation test** — the second-tier procedure is sanctioned ONLY in the audit-trail-preserved branch:
|
|
303
|
+
|
|
304
|
+
- ✅ **Audit-trail-preserved**: the agent is currently executing `/wr-itil:manage-problem` Step 2 for THIS ticket creation (gate-misfire signal condition 1), AND any `/tmp/manage-problem-grep-*` marker exists. The skill flow itself is the just-ran-grep witness; the marker existence corroborates it.
|
|
305
|
+
- ❌ **Audit-trail-violated**: the agent is NOT in `/wr-itil:manage-problem` Step 2 for this ticket creation, OR no marker exists for any SID. Routine first-creation flow MUST hit the gate; the recovery procedure does NOT apply.
|
|
306
|
+
|
|
307
|
+
**Anti-pattern bound** — the loose reading "any marker from any earlier `manage-problem` invocation in this session" would let the recovery procedure apply to a fresh ticket creation that happens to reuse a stale marker from a prior unrelated invocation. That is the P131 anti-pattern surface (gate state as a workaround target instead of as a directive). The bound holds because the recovery is invoked from inside an active manage-problem flow where Step 2 has just been ordered for THIS ticket, AND the python3-via-Bash branch is named in this substep so its invocation is itself audit-trail-emitting.
|
|
308
|
+
|
|
309
|
+
**DO NOT brute-force-touch markers for every announced UUID.** That pattern (139 markers in one session, 2026-04-28 P144 evidence) satisfies the marker shape while gaming the audit trail the marker is supposed to record. The user has explicitly rejected this pattern: *"WTF? Why did you bypass instead of using the skill?"* (P144 driver correction). Brute-forcing markers for SIDs that did not run Step 2 is the canonical bypass — the recovery procedure above is the canonical use of the skill.
|
|
310
|
+
|
|
311
|
+
**Cross-references**: P124 (helper Phase 3 regression — driver of the misfire); P142 (P124 Phase 4 — structural fix that auto-supersedes this recovery when shipped); P131 (gate-exclusions-as-write-permission — adjacent anti-pattern family); ADR-048 (sanctioning + scoping ADR); ADR-009 (gate marker lifecycle); ADR-044 (mechanical-decision framework-mediated surface catalog).
|
|
312
|
+
|
|
272
313
|
**Search strategy**: Search problem filenames AND file content. A match on the filename (kebab-case title) or the Description/Symptoms sections counts. Cast a wide net — false positives are cheap (user chooses), but false negatives mean duplicate problems.
|
|
273
314
|
|
|
274
315
|
**Hook contract (P119)**: writing a `.open.md` (or any `.<status>.md`) file under `docs/problems/` without first running this Step 2 grep + marker-touch is blocked by the `manage-problem-enforce-create.sh` PreToolUse hook with a `permissionDecision: deny` directing the agent back to this skill. Agents that try to bypass the skill (e.g. mid-retrospective inline capture, post-mortem wrap-up, or any "I'll just write it directly" shortcut) will hit the deny and be redirected here. Do not work around the deny by setting the marker manually — the marker exists to record that this Step 2 ran, and a marker without a grep is the audit-trail gap P119 closes.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
#
|
|
3
|
+
# packages/itil/skills/manage-problem/test/manage-problem-p119-recovery-path.bats
|
|
4
|
+
#
|
|
5
|
+
# Behavioural tests for manage-problem Step 2 substep 7's P119 hook-misfire
|
|
6
|
+
# recovery procedure (P144 / ADR-048).
|
|
7
|
+
#
|
|
8
|
+
# Step 2 substep 7 documents a two-tier recovery for the case where
|
|
9
|
+
# `mark_step2_complete` succeeded but the P119 PreToolUse:Write hook still
|
|
10
|
+
# denies the new ticket Write — typically because the P124 helper returned
|
|
11
|
+
# a subprocess SID instead of the orchestrator SID (ADR-048 Phase 3
|
|
12
|
+
# regression). Without documented recovery, the agent reaches for the
|
|
13
|
+
# brute-force-touch-every-marker anti-pattern (139-marker incident,
|
|
14
|
+
# 2026-04-28). User correction was emphatic: "WTF? Why did you bypass
|
|
15
|
+
# instead of using the skill?"
|
|
16
|
+
#
|
|
17
|
+
# This bats fixes the contract:
|
|
18
|
+
# - Sub-block names the gate-misfire signal (active flow + helper-succeeded
|
|
19
|
+
# + Write-denied conjunction).
|
|
20
|
+
# - Two-tier procedure named (first-tier announce-marker scrape; second-tier
|
|
21
|
+
# python3-via-Bash file-write).
|
|
22
|
+
# - Audit-trail-preservation test as the gate-on-sanctioning rule.
|
|
23
|
+
# - Anti-pattern call-out ("DO NOT brute-force") in durable form.
|
|
24
|
+
# - ADR-048, P124, P142 cross-references.
|
|
25
|
+
# - <!-- supersedes-when: P142 ships --> HTML comment for cleanup
|
|
26
|
+
# discoverability.
|
|
27
|
+
#
|
|
28
|
+
# tdd-review: structural-permitted (justification: skill behavioural
|
|
29
|
+
# harness pending P012 + P081 Phase 2; SKILL.md contract assertions
|
|
30
|
+
# bridge until then; expected to migrate to behavioural form once
|
|
31
|
+
# the harness exists).
|
|
32
|
+
#
|
|
33
|
+
# @problem P144
|
|
34
|
+
# @adr ADR-048 (Documented recovery from gate misfire is the prescribed surface, not bypass)
|
|
35
|
+
# @adr ADR-009 (gate marker lifecycle)
|
|
36
|
+
# @adr ADR-013 Rule 5 (policy-authorised silent proceed)
|
|
37
|
+
# @adr ADR-022 (problem lifecycle status suffixes)
|
|
38
|
+
# @adr ADR-037 / P081 (testing strategy — bridge during harness build)
|
|
39
|
+
# @adr ADR-038 (progressive disclosure — deny message terse)
|
|
40
|
+
# @adr ADR-044 (decision-delegation — recovery is mechanical)
|
|
41
|
+
# @jtbd JTBD-001 / JTBD-101 / JTBD-201
|
|
42
|
+
|
|
43
|
+
SKILL_FILE="${BATS_TEST_DIRNAME}/../SKILL.md"
|
|
44
|
+
|
|
45
|
+
setup() {
|
|
46
|
+
[ -f "$SKILL_FILE" ]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Bound the search to Step 2 substep 7 region (between Step 2 heading and Step 3 heading).
|
|
50
|
+
step2_text() {
|
|
51
|
+
awk '/^### 2\. /,/^### 3\. /' "$SKILL_FILE"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# ── Recovery sub-block presence ─────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
@test "Step 2 SKILL.md contains a Recovery sub-block for hook-denial misfire" {
|
|
57
|
+
run step2_text
|
|
58
|
+
[ "$status" -eq 0 ]
|
|
59
|
+
[[ "$output" == *"Recovery"* ]]
|
|
60
|
+
[[ "$output" == *"hook denial"* ]] || [[ "$output" == *"hook still denies"* ]] || [[ "$output" == *"deny"* ]]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# ── Gate-misfire signal definition ──────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
@test "Step 2 SKILL.md names the gate-misfire signal precondition (active manage-problem flow)" {
|
|
66
|
+
run step2_text
|
|
67
|
+
[ "$status" -eq 0 ]
|
|
68
|
+
# The signal requires that the agent is already executing manage-problem
|
|
69
|
+
# Step 2 in the current turn — not just any prior session marker.
|
|
70
|
+
[[ "$output" == *"already executing"* ]] || [[ "$output" == *"active"* ]] || [[ "$output" == *"this turn"* ]]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@test "Step 2 SKILL.md names mark_step2_complete success as part of the misfire signal" {
|
|
74
|
+
run step2_text
|
|
75
|
+
[ "$status" -eq 0 ]
|
|
76
|
+
[[ "$output" == *"mark_step2_complete"* ]]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# ── Two-tier procedure ──────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
@test "Step 2 SKILL.md names the first-tier recovery (announce-marker scrape)" {
|
|
82
|
+
run step2_text
|
|
83
|
+
[ "$status" -eq 0 ]
|
|
84
|
+
[[ "$output" == *"first-tier"* ]] || [[ "$output" == *"First-tier"* ]]
|
|
85
|
+
[[ "$output" == *"itil-assistant-gate-announced"* ]]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@test "Step 2 SKILL.md names the second-tier recovery (python3-via-Bash file-write)" {
|
|
89
|
+
run step2_text
|
|
90
|
+
[ "$status" -eq 0 ]
|
|
91
|
+
[[ "$output" == *"second-tier"* ]] || [[ "$output" == *"Second-tier"* ]]
|
|
92
|
+
[[ "$output" == *"python3"* ]]
|
|
93
|
+
[[ "$output" == *"Bash"* ]]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# ── Audit-trail-preservation test ───────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
@test "Step 2 SKILL.md states the audit-trail-preservation test as the sanctioning rule" {
|
|
99
|
+
run step2_text
|
|
100
|
+
[ "$status" -eq 0 ]
|
|
101
|
+
[[ "$output" == *"audit-trail"* ]] || [[ "$output" == *"audit trail"* ]]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@test "Step 2 SKILL.md names the anti-pattern bound (any-marker-anywhere is NOT the test)" {
|
|
105
|
+
# Architect advisory: the bound must rule out the loose "any marker from any
|
|
106
|
+
# earlier invocation in this session" reading — that's the P131 surface.
|
|
107
|
+
run step2_text
|
|
108
|
+
[ "$status" -eq 0 ]
|
|
109
|
+
[[ "$output" == *"this ticket"* ]] || [[ "$output" == *"THIS ticket"* ]]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# ── Anti-pattern call-out (durable surface) ─────────────────────────────────
|
|
113
|
+
|
|
114
|
+
@test "Step 2 SKILL.md contains the explicit DO-NOT-brute-force anti-pattern wording" {
|
|
115
|
+
run step2_text
|
|
116
|
+
[ "$status" -eq 0 ]
|
|
117
|
+
[[ "$output" == *"DO NOT brute-force"* ]] || [[ "$output" == *"do not brute-force"* ]] || [[ "$output" == *"Do not brute-force"* ]]
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@test "Step 2 SKILL.md cites the 2026-04-28 user correction context for the anti-pattern" {
|
|
121
|
+
run step2_text
|
|
122
|
+
[ "$status" -eq 0 ]
|
|
123
|
+
[[ "$output" == *"P144"* ]]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# ── Cross-references ────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
@test "Step 2 SKILL.md cites ADR-048 for the recovery procedure scope" {
|
|
129
|
+
run step2_text
|
|
130
|
+
[ "$status" -eq 0 ]
|
|
131
|
+
[[ "$output" == *"ADR-048"* ]]
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@test "Step 2 SKILL.md cites P124 as the helper-bug source" {
|
|
135
|
+
run step2_text
|
|
136
|
+
[ "$status" -eq 0 ]
|
|
137
|
+
[[ "$output" == *"P124"* ]]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@test "Step 2 SKILL.md cites P142 as the structural fix (supersession trigger)" {
|
|
141
|
+
run step2_text
|
|
142
|
+
[ "$status" -eq 0 ]
|
|
143
|
+
[[ "$output" == *"P142"* ]]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# ── Supersession comment (CI-enforced cleanup invariant) ────────────────────
|
|
147
|
+
|
|
148
|
+
@test "Step 2 SKILL.md carries the supersedes-when HTML comment so cleanup is discoverable" {
|
|
149
|
+
# ADR-048 Reassessment Criteria: when P142's resolution ADR is accepted,
|
|
150
|
+
# this comment must be removed from SKILL.md source. Today the comment
|
|
151
|
+
# is present and this assertion passes; once P142 lands, the cleanup
|
|
152
|
+
# signal lives here.
|
|
153
|
+
run step2_text
|
|
154
|
+
[ "$status" -eq 0 ]
|
|
155
|
+
[[ "$output" == *"supersedes-when"* ]]
|
|
156
|
+
[[ "$output" == *"P142"* ]]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# ── Mechanical (no-AskUserQuestion) per ADR-044 ─────────────────────────────
|
|
160
|
+
|
|
161
|
+
@test "Step 2 SKILL.md states the recovery is mechanical (no AskUserQuestion required)" {
|
|
162
|
+
run step2_text
|
|
163
|
+
[ "$status" -eq 0 ]
|
|
164
|
+
[[ "$output" == *"mechanical"* ]] || [[ "$output" == *"ADR-044"* ]]
|
|
165
|
+
}
|