← back to Rejected Prompts Viewer
chore: lint, refactor, v1.3.1 (session close) — clearer names, with-blocks, type hints, +x; behavior/schema unchanged
58fa36f8bccaa105334420b15964bb8548f860bb · 2026-08-19 12:43:52 -0700 · Steve Abrams
Files touched
M build-data.pyM package.jsonM refresh.pyM scan.py
Diff
commit 58fa36f8bccaa105334420b15964bb8548f860bb
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Aug 19 12:43:52 2026 -0700
chore: lint, refactor, v1.3.1 (session close) — clearer names, with-blocks, type hints, +x; behavior/schema unchanged
---
build-data.py | 25 +++++++++++++------------
package.json | 2 +-
refresh.py | 31 ++++++++++++++++---------------
scan.py | 49 +++++++++++++++++++++++++------------------------
4 files changed, 55 insertions(+), 52 deletions(-)
diff --git a/build-data.py b/build-data.py
old mode 100644
new mode 100755
index 1ba8b59..6d80ab9
--- a/build-data.py
+++ b/build-data.py
@@ -13,19 +13,20 @@ CLASSIFIED = sys.argv[1] if len(sys.argv) > 1 else '/tmp/refusal_classified.json
PROJROOT = os.path.expanduser('~/.claude/projects')
# map transcript basename -> readable project name
-basename_project = {}
-for d in glob.glob(os.path.join(PROJROOT, '*')):
- proj = os.path.basename(d)
+basename_project: dict[str, str] = {}
+for proj_dir in glob.glob(os.path.join(PROJROOT, '*')):
+ proj_base = os.path.basename(proj_dir)
# -Users-macstudio3-Projects-designerwallcoverings -> designerwallcoverings
- readable = proj.split('-Projects-')[-1] if '-Projects-' in proj else proj.lstrip('-')
- for f in glob.glob(os.path.join(d, '**', '*.jsonl'), recursive=True):
+ readable = proj_base.split('-Projects-')[-1] if '-Projects-' in proj_base else proj_base.lstrip('-')
+ for f in glob.glob(os.path.join(proj_dir, '**', '*.jsonl'), recursive=True):
basename_project[os.path.basename(f)] = readable
try:
- rows = json.load(open(CLASSIFIED))
+ with open(CLASSIFIED) as fh:
+ rows = json.load(fh)
except (FileNotFoundError, json.JSONDecodeError) as e:
sys.exit(f"ERROR: cannot read {CLASSIFIED}: {e}")
-items = []
+items: list[dict] = []
for r in rows:
items.append({
'user': r.get('user', ''),
@@ -50,8 +51,8 @@ out = {
'items': items,
}
os.makedirs(os.path.join(HERE, 'data'), exist_ok=True)
-_data = os.path.join(HERE, 'data', 'rejections.json')
-with open(_data + '.tmp', 'w') as _fh: # atomic write — never leave a truncated data file
- json.dump(out, _fh, indent=1)
-os.replace(_data + '.tmp', _data)
-print(f"wrote {len(items)} items ({confirmed_count} confirmed + {len(items)-confirmed_count} borderline), scanned {scanned} transcripts -> data/rejections.json")
+out_path = os.path.join(HERE, 'data', 'rejections.json')
+with open(out_path + '.tmp', 'w') as fh: # atomic write — never leave a truncated data file
+ json.dump(out, fh, indent=1)
+os.replace(out_path + '.tmp', out_path)
+print(f"wrote {len(items)} items ({confirmed_count} confirmed + {len(items) - confirmed_count} borderline), scanned {scanned} transcripts -> data/rejections.json")
diff --git a/package.json b/package.json
index e3048a6..b55178b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "rejected-prompts-viewer",
- "version": "1.3.0",
+ "version": "1.3.1",
"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/refresh.py b/refresh.py
index b6fd186..6312be1 100755
--- a/refresh.py
+++ b/refresh.py
@@ -27,7 +27,7 @@ SYS = ("You audit an AI coding assistant's replies. Given the USER request and t
'{"refusal":true|false,"category":"copyright|trademark|ip-impersonation|website-tos|other","reason":"<=12 words"}')
-def classify(user, refusal):
+def classify(user: str, refusal: str) -> dict:
body = json.dumps({"model": MODEL, "stream": False, "think": False,
"messages": [{"role": "system", "content": SYS},
{"role": "user", "content": f"USER REQUEST:\n{user}\n\nASSISTANT REPLY:\n{refusal}\n\nJSON:"}],
@@ -38,7 +38,7 @@ def classify(user, refusal):
return json.loads(mm.group(0)) if mm else {"refusal": False, "category": "other", "reason": "parse-fail"}
-def main():
+def main() -> None:
# Stage 1 — scan
subprocess.run([sys.executable, os.path.join(HERE, 'scan.py')], check=True)
with open(CANDS) as fh:
@@ -46,22 +46,23 @@ def main():
if not isinstance(cands, list):
sys.exit(f"[refresh] unexpected scan output in {CANDS} (expected list, got {type(cands).__name__})")
- # verdict cache from the existing data file (key = file + ts)
- cache = {}
+ # verdict cache from the existing data file (key = (file, ts))
+ cache: dict[tuple, dict] = {}
if os.path.exists(DATA):
with open(DATA) as fh:
- for it in json.load(fh).get('items', []):
- cache[(it.get('file'), it.get('ts'))] = it
+ for cached_item in json.load(fh).get('items', []):
+ cache[(cached_item.get('file'), cached_item.get('ts'))] = cached_item
- # project map
- basename_project = {}
- for d in glob.glob(os.path.join(PROJROOT, '*')):
- readable = os.path.basename(d).split('-Projects-')[-1] if '-Projects-' in os.path.basename(d) else os.path.basename(d).lstrip('-')
- for f in glob.glob(os.path.join(d, '**', '*.jsonl'), recursive=True):
+ # project dir basename -> readable project name
+ basename_project: dict[str, str] = {}
+ for proj_dir in glob.glob(os.path.join(PROJROOT, '*')):
+ proj_base = os.path.basename(proj_dir)
+ readable = proj_base.split('-Projects-')[-1] if '-Projects-' in proj_base else proj_base.lstrip('-')
+ for f in glob.glob(os.path.join(proj_dir, '**', '*.jsonl'), recursive=True):
basename_project[os.path.basename(f)] = readable
- items = []
- new = 0
+ items: list[dict] = []
+ new_count = 0
t0 = time.time()
for c in cands:
prev = cache.get((c.get('file'), c.get('ts')))
@@ -76,7 +77,7 @@ def main():
v = {"refusal": False, "category": "other", "reason": "(unclassified — model unavailable)"}
err = True # flag so this item is NOT cached-reused — it re-classifies next run until the model answers
conf, cat, rsn = bool(v.get('refusal')), v.get('category', 'other'), v.get('reason', '')
- new += 1
+ new_count += 1
items.append({'user': c['user'], 'refusal': c['refusal'], 'confirmed': conf,
'category': cat, 'reason': rsn, 'net': c.get('net', 'A'), # 'A' = Net A (keyword net); 'B' = tone-only net
'error': err, 'ts': c['ts'], 'file': c['file'],
@@ -94,7 +95,7 @@ def main():
with open(tmp, 'w') as fh:
json.dump(out, fh, indent=1)
os.replace(tmp, DATA)
- print(f"[{out['generated']}] refresh: {len(items)} items ({out['confirmed_count']} confirmed), {new} newly classified, {time.time()-t0:.0f}s")
+ print(f"[{out['generated']}] refresh: {len(items)} items ({out['confirmed_count']} confirmed), {new_count} newly classified, {time.time() - t0:.0f}s")
if __name__ == '__main__':
diff --git a/scan.py b/scan.py
old mode 100644
new mode 100755
index ccbf506..ae5f35b
--- a/scan.py
+++ b/scan.py
@@ -29,12 +29,13 @@ capability = re.compile(r"(from (in )?here|from the shell|from this session|for
# META filter: this build's OWN session discusses refusals/copyright at length — exclude self-referential noise
META = re.compile(r"(:9858|rejected-prompts|refusal_cands|refusal_classified|borderline candidate|heretic model|\bNet [AB]\b|classify\.py|build-data|scan\.py|policy declines|decline text|two-signal|capability limit)", re.IGNORECASE)
-seen = set()
-cands = []
+seen: set[tuple[str, str]] = set()
+cands: list[dict] = []
for f in glob.glob(os.path.join(PROJROOT, '**', '*.jsonl'), recursive=True):
try:
- raw = open(f, errors='ignore').read()
- except Exception:
+ with open(f, errors='ignore') as fh:
+ raw = fh.read()
+ except Exception: # noqa: BLE001,S112
continue
# file-level exclusion: skip any transcript that is ABOUT building this tool (self-pollution)
# specific signatures only — bare ':9858' matched token counts like ':985835' and
@@ -42,32 +43,31 @@ for f in glob.glob(os.path.join(PROJROOT, '**', '*.jsonl'), recursive=True):
if any(sig in raw for sig in ('rejected-prompts-viewer', 'http://127.0.0.1:9858',
'rejected.agentabrams', 'refusal_cands', 'refusal_classified', 'qwen3.8-27b-heretic')):
continue
- lines = raw.splitlines()
- 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:
+ recent_users: list[str] = [] # rolling last-USER_TURNS user turns for context (HOLE 2)
+ for line in raw.splitlines():
+ if '"user"' not in line and '"assistant"' not in line:
continue
try:
- d = json.loads(l)
- except Exception:
+ d = json.loads(line)
+ except Exception: # noqa: BLE001
continue
- t, m = d.get('type'), d.get('message')
- if not isinstance(m, dict):
+ msg_type, msg = d.get('type'), d.get('message')
+ if not isinstance(msg, dict):
continue
- if t == 'user':
- c = m.get('content')
+ if msg_type == 'user':
+ content = msg.get('content')
txt = None
- if isinstance(c, str):
- txt = c
- elif isinstance(c, list):
- txt = ' '.join(x.get('text', '') for x in c if isinstance(x, dict) and x.get('type') == 'text')
+ if isinstance(content, str):
+ txt = content
+ elif isinstance(content, list):
+ txt = ' '.join(x.get('text', '') for x in content if isinstance(x, dict) and x.get('type') == 'text')
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':
- tx = it.get('text', '')
+ recent_users = recent_users[-USER_TURNS:] # keep only the last USER_TURNS
+ elif msg_type == 'assistant':
+ for block in (msg.get('content') or []):
+ if isinstance(block, dict) and block.get('type') == 'text':
+ tx = block.get('text', '')
if len(tx) >= MAX_ASST_LEN:
continue
netA = decline.search(tx) and reason.search(tx)
@@ -87,5 +87,6 @@ for f in glob.glob(os.path.join(PROJROOT, '**', '*.jsonl'), recursive=True):
'net': 'A' if netA else 'B'})
break
-json.dump(cands, open('/tmp/refusal_cands.json', 'w'), indent=1)
+with open('/tmp/refusal_cands.json', 'w') as fh:
+ json.dump(cands, fh, indent=1)
print(f"CANDIDATES: {len(cands)} by-net={dict(Counter(c['net'] for c in cands))} -> /tmp/refusal_cands.json")
← ae2a0e7 chore: gitignore __pycache__/*.pyc (auto-snapshot picked up
·
back to Rejected Prompts Viewer
·
auto-data-snapshot: 2026-08-19T16:13:26 (1 data files) — dat 46f0a28 →