[object Object]

← back to Rejected Prompts Viewer

chore: lint, refactor, v1.2.1 (session close) — with-blocks, stderr on classify fail, non-list scan guard, corrected net comment

777dc1ef29a77e46f172f65c2ae817715fb94703 · 2026-08-19 11:43:18 -0700 · Steve Abrams

Files touched

Diff

commit 777dc1ef29a77e46f172f65c2ae817715fb94703
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Aug 19 11:43:18 2026 -0700

    chore: lint, refactor, v1.2.1 (session close) — with-blocks, stderr on classify fail, non-list scan guard, corrected net comment
---
 __pycache__/refresh.cpython-314.pyc | Bin 0 -> 9059 bytes
 package.json                        |   2 +-
 refresh.py                          |  30 ++++++++++++++++++++++--------
 3 files changed, 23 insertions(+), 9 deletions(-)

diff --git a/__pycache__/refresh.cpython-314.pyc b/__pycache__/refresh.cpython-314.pyc
new file mode 100644
index 0000000..f7b6d08
Binary files /dev/null and b/__pycache__/refresh.cpython-314.pyc differ
diff --git a/package.json b/package.json
index e5dc254..34a658d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "rejected-prompts-viewer",
-  "version": "1.2.0",
+  "version": "1.2.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
old mode 100644
new mode 100755
index bb55456..01aa3a0
--- a/refresh.py
+++ b/refresh.py
@@ -4,7 +4,15 @@ Runs scan (Stage 1), then classifies ONLY candidates not already in the existing
 data/rejections.json (reusing prior heretic verdicts by (file, ts) key so the slow
 27B model is paid only for genuinely NEW rejections), then rebuilds data/rejections.json.
 Designed to run unattended on a Mac2 schedule (launchd). $0 local."""
-import json, os, re, subprocess, sys, time, urllib.request, glob, datetime
+import datetime
+import glob
+import json
+import os
+import re
+import subprocess
+import sys
+import time
+import urllib.request
 
 HERE = os.path.dirname(os.path.abspath(__file__))
 CANDS = '/tmp/refusal_cands.json'
@@ -23,7 +31,7 @@ def classify(user, refusal):
     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:"}],
-        "options": {"temperature": 0, "num_predict": 80}}).encode()
+        "options": {"temperature": 0, "num_predict": 80}}).encode()  # 80 tokens sufficient for compact JSON verdict
     r = urllib.request.urlopen(urllib.request.Request(OLLAMA_URL, body, {"Content-Type": "application/json"}), timeout=180)
     txt = re.sub(r"<think>.*?</think>", "", json.loads(r.read())["message"]["content"], flags=re.DOTALL).strip()
     mm = re.search(r"\{.*\}", txt, re.DOTALL)
@@ -33,13 +41,17 @@ def classify(user, refusal):
 def main():
     # Stage 1 — scan
     subprocess.run([sys.executable, os.path.join(HERE, 'scan.py')], check=True)
-    cands = json.load(open(CANDS))
+    with open(CANDS) as fh:
+        cands = json.load(fh)
+    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 = {}
     if os.path.exists(DATA):
-        for it in json.load(open(DATA)).get('items', []):
-            cache[(it.get('file'), it.get('ts'))] = it
+        with open(DATA) as fh:
+            for it in json.load(fh).get('items', []):
+                cache[(it.get('file'), it.get('ts'))] = it
 
     # project map
     basename_project = {}
@@ -58,12 +70,13 @@ def main():
         else:
             try:
                 v = classify(c['user'], c['refusal'])
-            except Exception:
+            except Exception as e:  # noqa: BLE001 — intentional resilience; KeyboardInterrupt/SystemExit still propagate
+                print(f"[warn] classify failed for {c.get('file')} ts={c.get('ts')}: {e}", file=sys.stderr)
                 v = {"refusal": False, "category": "other", "reason": "(unclassified — model unavailable)"}
             conf, cat, rsn = bool(v.get('refusal')), v.get('category', 'other'), v.get('reason', '')
             new += 1
         items.append({'user': c['user'], 'refusal': c['refusal'], 'confirmed': conf,
-                      'category': cat, 'reason': rsn, 'net': c.get('net', 'A'),
+                      'category': cat, 'reason': rsn, 'net': c.get('net', 'A'),  # 'A' = Net A (keyword net); 'B' = tone-only net
                       'ts': c['ts'], 'file': c['file'],
                       'project': basename_project.get(c.get('file', ''), '—')})
     items.sort(key=lambda x: (x['ts'] or ''), reverse=True)
@@ -74,7 +87,8 @@ def main():
            'confirmed_count': sum(1 for i in items if i['confirmed']),
            'items': items}
     os.makedirs(os.path.dirname(DATA), exist_ok=True)
-    json.dump(out, open(DATA, 'w'), indent=1)
+    with open(DATA, 'w') as fh:
+        json.dump(out, fh, indent=1)
     print(f"[{out['generated']}] refresh: {len(items)} items ({out['confirmed_count']} confirmed), {new} newly classified, {time.time()-t0:.0f}s")
 
 

← b2b7cb8 feat: auto-update — incremental refresh.py + launchd KeepAli  ·  back to Rejected Prompts Viewer  ·  harden: atomic write (tmp+os.replace) in refresh.py + build- fde4c52 →