|
40 | 40 | # 1. the payload parses and is a UserPromptSubmit event |
41 | 41 | # 2. the prompt does not begin with `/` (a slash command is an invocation, not |
42 | 42 | # a change request — and `/v:triage` itself is one) |
| 43 | +# 2b. the prompt is not a SHORT QUESTION (<=200 chars ending in `?`). A question |
| 44 | +# is not a change request, and spending the session's one nudge on it is the |
| 45 | +# hole the audit named. Skipping here leaves the session ARMED. |
43 | 46 | # 3. a `session_id` is present (without it there is nothing to deduplicate on, |
44 | 47 | # and a nudge on every prompt is worse than no nudge) |
45 | 48 | # 4. the project looks Compound-V-enabled: `docs/superpowers/` or |
|
104 | 107 | # |
105 | 108 | # COST, measured on the development machine (macOS, /usr/bin/python3, mean of |
106 | 109 | # 10 runs against this repository): |
107 | | -# early exit (slash command, or the marker already set) ~9 ms |
| 110 | +# early exit (slash command, short question, or marker set) ~9 ms |
108 | 111 | # full eligibility path (records scan + resume query) ~89 ms |
109 | 112 | # The full path runs at most once per session — except while a run is active, |
110 | 113 | # when no marker is written and conditions 6-7 are re-evaluated on each prompt. |
@@ -246,16 +249,21 @@ hook_main() { |
246 | 249 | ((.hook_event_name // "") | tostring | gsub("[^A-Za-z]"; "")), |
247 | 250 | (((.prompt // "") | tostring)[0:1] | gsub("[^/]"; "")), |
248 | 251 | (((.session_id // "") | tostring) | gsub("[^A-Za-z0-9._:-]"; "")), |
249 | | - (((.cwd // "") | tostring) | gsub("[\n\r]"; "")) |
| 252 | + (((.cwd // "") | tostring) | gsub("[\n\r]"; "")), |
| 253 | + (((.prompt // "") | tostring) | ascii_downcase |
| 254 | + | gsub("^[[:space:]]+"; "") | gsub("[[:space:]]+$"; "") |
| 255 | + | if (length > 0 and length <= 200 and (.[-1:] == "?")) |
| 256 | + then "q" else "" end) |
250 | 257 | ' 2>/dev/null)" || return 1 |
251 | 258 | [ -n "$fields" ] || return 1 |
252 | 259 |
|
253 | | - local ev slash sid cwdv |
| 260 | + local ev slash sid cwdv question |
254 | 261 | { |
255 | 262 | read -r ev |
256 | 263 | read -r slash |
257 | 264 | read -r sid |
258 | 265 | read -r cwdv |
| 266 | + read -r question |
259 | 267 | } <<EOF |
260 | 268 | ${fields} |
261 | 269 | EOF |
|
270 | 278 | # `/v:status` and `/clear` all arrive here. |
271 | 279 | [ -z "${slash:-}" ] || return 1 |
272 | 280 |
|
| 281 | + # A SHORT QUESTION IS NOT A CHANGE REQUEST, AND MUST NOT SPEND THE NUDGE. |
| 282 | + # |
| 283 | + # The nudge is once per session, and the audit named the hole precisely: a |
| 284 | + # session whose first prompt is "what does this do?" burns the reminder on a |
| 285 | + # question and the real change request that follows gets nothing. Returning |
| 286 | + # here — BEFORE the marker is written and before the ~89 ms eligibility path — |
| 287 | + # leaves the session armed and costs the ~9 ms early exit. |
| 288 | + # |
| 289 | + # The test is deliberately narrow, because the general problem (is this a |
| 290 | + # change request?) is not decidable in a hook: a prompt of at most 200 |
| 291 | + # characters that ENDS IN A QUESTION MARK. Nothing else is treated as a |
| 292 | + # question. "Rename getUser to fetchUser, ok?" is 33 characters and ends in |
| 293 | + # `?` and will be skipped — that is the deliberate direction of the error: a |
| 294 | + # missed nudge costs a reminder, a spent one costs the session's only reminder. |
| 295 | + # A long prompt that happens to end in `?` is a description with a question |
| 296 | + # attached, and still nudges. |
| 297 | + if [ "${question:-}" = "q" ]; then |
| 298 | + return 1 |
| 299 | + fi |
| 300 | + |
273 | 301 | # No session id ⇒ nothing to deduplicate on ⇒ stay silent. |
274 | 302 | [ -n "${sid:-}" ] || return 1 |
275 | 303 |
|
|
0 commit comments