← back to Rejected Prompts Viewer
feat: HOLE 2 — capture last 3 user turns as context so refusals to a lone 'go'/'do it' keep what was actually asked; v1.3.0
cfc28cd966d2a3f72b79f4ba95aff790cc6ac50d · 2026-08-19 12:20:45 -0700 · Steve Abrams
Files touched
Diff
commit cfc28cd966d2a3f72b79f4ba95aff790cc6ac50d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Aug 19 12:20:45 2026 -0700
feat: HOLE 2 — capture last 3 user turns as context so refusals to a lone 'go'/'do it' keep what was actually asked; v1.3.0
---
package.json | 2 +-
scan.py | 18 ++++++++++++------
2 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/package.json b/package.json
index 24af2ef..e3048a6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "rejected-prompts-viewer",
- "version": "1.2.3",
+ "version": "1.3.0",
"private": true,
"description": "Web viewer of prompts Claude declined on copyright/trademark/website-ToS/IP grounds, mined from Claude Code transcripts.",
"scripts": {
diff --git a/scan.py b/scan.py
index b66cbcf..ccbf506 100644
--- a/scan.py
+++ b/scan.py
@@ -15,8 +15,9 @@ PROJROOT = os.path.expanduser('~/.claude/projects')
# Tunables — change these without hunting through logic below
MAX_ASST_LEN = 2500 # assistant turns longer than this are almost never bare refusals
-USER_TRUNCATE = 600 # chars stored for the user prompt
+USER_TRUNCATE = 900 # chars stored for the user context (raised for multi-turn — HOLE 2)
ASST_TRUNCATE = 800 # chars stored for the assistant decline snippet
+USER_TURNS = 3 # how many recent user turns to include as context (a lone "go"/"do it" loses what was asked)
decline = re.compile(r"(I can'?t|I cannot|I won'?t|I'm not able|I am not able|I'm not going to|I'm not comfortable|I shouldn'?t|I have to decline|I'd rather not|not something I can|can'?t ethically|won'?t help|refus|decline to|not willing to|in good conscience)", re.IGNORECASE)
reason = re.compile(r"(copyright|trademark|intellectual property|terms of service|\bToS\b|their terms|against .{0,20}terms|scrap\w* (without|permission|their)|circumvent|bypass\w* (the )?(bot|captcha|paywall|rate.?limit|detection|login|auth)|unauthorized access|impersonat|counterfeit|\bDMCA\b|someone else'?s (brand|work|design|content)|protected work|passing off|knockoff|knock-off|rip.?off .{0,15}brand|clone .{0,15}(brand|site|store))", re.IGNORECASE)
@@ -42,7 +43,7 @@ for f in glob.glob(os.path.join(PROJROOT, '**', '*.jsonl'), recursive=True):
'rejected.agentabrams', 'refusal_cands', 'refusal_classified', 'qwen3.8-27b-heretic')):
continue
lines = raw.splitlines()
- prev_user = None
+ recent_users = [] # rolling last-USER_TURNS user turns for context (HOLE 2)
for l in lines:
if '"user"' not in l and '"assistant"' not in l:
continue
@@ -55,11 +56,14 @@ for f in glob.glob(os.path.join(PROJROOT, '**', '*.jsonl'), recursive=True):
continue
if t == 'user':
c = m.get('content')
+ txt = None
if isinstance(c, str):
- prev_user = c
+ txt = c
elif isinstance(c, list):
txt = ' '.join(x.get('text', '') for x in c if isinstance(x, dict) and x.get('type') == 'text')
- prev_user = txt or prev_user
+ if txt and txt.strip():
+ recent_users.append(txt.strip())
+ del recent_users[:-USER_TURNS] # keep only the last USER_TURNS
elif t == 'assistant':
for it in (m.get('content') or []):
if isinstance(it, dict) and it.get('type') == 'text':
@@ -70,14 +74,16 @@ for f in glob.glob(os.path.join(PROJROOT, '**', '*.jsonl'), recursive=True):
netB = strong.search(tx) and not capability.search(tx)
if not (netA or netB):
continue
- if META.search(tx) or META.search(prev_user or ''): # skip this build's own meta-chatter
+ # multi-turn context, oldest->newest; keep the most-recent tail if over budget
+ user_ctx = ' ⏎ '.join(recent_users)[-USER_TRUNCATE:]
+ if META.search(tx) or META.search(user_ctx): # skip this build's own meta-chatter
continue
key = (os.path.basename(f), tx[:200])
if key in seen:
continue
seen.add(key)
cands.append({'file': os.path.basename(f), 'ts': d.get('timestamp'),
- 'user': (prev_user or '')[:USER_TRUNCATE], 'refusal': tx[:ASST_TRUNCATE],
+ 'user': user_ctx, 'refusal': tx[:ASST_TRUNCATE],
'net': 'A' if netA else 'B'})
break
← 1a42d25 data: refresh to 90 candidates (4 recovered from HOLE 1 fix)
·
back to Rejected Prompts Viewer
·
auto-data-snapshot: 2026-08-19T12:22:10 (1 data files) — __p 5d3f8ff →