66block holding a paste-ready Claude Code prompt that executes this repo's own
77documented workflow (``bibliography/README.md`` "Adding a paper",
88``wiki/CLAUDE.md``'s schema) — plus contents cards with ``/memory <domain>``
9- recall chips.
9+ recall chips. Reading-queue sections expand to the individual papers: each
10+ title links out (arXiv abstract page, or a title search when the line has no
11+ ref) and carries three prefilled-GitHub-issue actions — 📥 intake-into-memory
12+ (really interesting: full filing), 📑 make-citeable (worth citing, not
13+ pivotal: bib entry + minimal sources section), both open work items carrying
14+ the human's free-text notes, and ✅ read-don't-file (processed automatically
15+ by ``queue_actions.yml`` via ``queue_mark_done.py``). Nothing changes state
16+ until the human submits the issue.
1017
1118**Contents-level only.** The board shows titles and counts, never claim text
1219or summaries — the knowledge itself stays in the wiki pages.
3542import subprocess
3643import sys
3744from pathlib import Path
45+ from urllib .parse import quote as _quote
3846
3947MEMORY_HOME = Path (__file__ ).resolve ().parents [1 ]
4048
4856# queue is also the reading history.
4957QUEUE_SECTION_RE = re .compile (r"^##\s+(.+?)\s*$" )
5058QUEUE_DONE_RE = re .compile (r"^DONE\b" )
59+ QUEUE_DONE_LINE_RE = re .compile (r"^DONE\s+(\d{4}-\d{2}-\d{2})\s*—\s*(.*)$" )
60+ # A paper line may end ` — <arXiv id or URL>`; anything else after an em dash
61+ # is part of the title.
62+ QUEUE_REF_RE = re .compile (
63+ r"^(.*\S)\s+—\s+((?:arXiv:)?\d{4}\.\d{4,5}(?:v\d+)?|https?://\S+)$" )
5164
5265
5366def _owner_repo () -> tuple [str , str ]:
@@ -113,21 +126,22 @@ def collect(root: Path | None = None) -> dict:
113126
114127 queue = root / "reading-queue.md"
115128 if queue .exists ():
116- section , count , done = None , 0 , 0
129+ section , papers = None , []
117130 def _flush ():
118131 if section is not None :
119- snapshot ["queue" ].append ({"section" : section , "count" : count ,
120- "done" : done })
132+ snapshot ["queue" ].append ({
133+ "section" : section ,
134+ "count" : sum (1 for p in papers if not p ["done" ]),
135+ "done" : sum (1 for p in papers if p ["done" ]),
136+ "papers" : papers ,
137+ })
121138 for line in queue .read_text (errors = "replace" ).splitlines ():
122139 m = QUEUE_SECTION_RE .match (line )
123140 if m and not m .group (1 ).startswith ("#" ):
124141 _flush ()
125- section , count , done = m .group (1 ), 0 , 0
142+ section , papers = m .group (1 ), []
126143 elif section is not None and line .strip ():
127- if QUEUE_DONE_RE .match (line .strip ()):
128- done += 1
129- else :
130- count += 1
144+ papers .append (_parse_paper (line .strip ()))
131145 _flush ()
132146
133147 slugs = [s .split ("|" )[0 ].strip () for s in all_slugs ]
@@ -141,6 +155,91 @@ def _flush():
141155
142156
143157# --- pure helpers --------------------------------------------------------------
158+ def _parse_paper (text : str ) -> dict :
159+ """One queue line → {title, ref, done, done_date, line} (line = as written)."""
160+ raw , done_date = text , None
161+ m = QUEUE_DONE_LINE_RE .match (text )
162+ if m :
163+ done_date , text = m .group (1 ), m .group (2 )
164+ elif QUEUE_DONE_RE .match (text ):
165+ done_date , text = "" , text [4 :].lstrip (" —-" )
166+ ref = None
167+ m = QUEUE_REF_RE .match (text )
168+ if m :
169+ text , ref = m .group (1 ), m .group (2 )
170+ return {"title" : text , "ref" : ref , "done" : done_date is not None ,
171+ "done_date" : done_date or None , "line" : raw }
172+
173+
174+ def paper_url (paper : dict ) -> str :
175+ """Where to read the paper: its abstract page, or an arXiv title search."""
176+ ref = paper .get ("ref" )
177+ if ref :
178+ if ref .startswith ("http" ):
179+ return ref
180+ return "https://arxiv.org/abs/" + ref .removeprefix ("arXiv:" )
181+ return ("https://arxiv.org/search/?searchtype=title&query="
182+ + _quote (paper .get ("title" , "" ), safe = "" ))
183+
184+
185+ def _queue_issue_url (snapshot : dict , section : str , paper : dict ,
186+ action : str ) -> str :
187+ """A prefilled new-issue URL for a per-paper action.
188+
189+ Three tiers — 'intake' (really interesting: full wiki filing), 'cite'
190+ (worth citing, not pivotal: bib entry + a minimal sources section), and
191+ 'read' (done with it: DONE-mark only, processed automatically by
192+ queue_actions.yml). Nothing changes state until the human submits the
193+ issue on GitHub; intake/cite issues stay open as the filing work item,
194+ and their `notes:` field is free text the human edits before submitting.
195+ Empty when the checkout has no remote (spawned templates).
196+ """
197+ repo_url = _repo_url (snapshot )
198+ if not repo_url :
199+ return ""
200+ where = f"section: { section } \n line: { paper ['line' ]} "
201+ notes = ("notes: (optional — replace this with why you added it or "
202+ "what was noteworthy, in your own words; whoever files the "
203+ "paper folds it in)" )
204+ if action == "read" :
205+ issue_title = f"queue read: { paper ['title' ]} " [:200 ]
206+ label = "queue-read"
207+ body = ("Mark this reading-queue paper as read without filing it "
208+ "(the line stays, DONE-prefixed — the reading history).\n \n "
209+ f"{ where } \n \n "
210+ "Processed automatically by queue_actions.yml." )
211+ elif action == "cite" :
212+ issue_title = f"queue cite: { paper ['title' ]} " [:200 ]
213+ label = "queue-cite"
214+ body = ("Make this reading-queue paper citeable — read, worth "
215+ "citing, but not pivotal enough for full wiki treatment.\n \n "
216+ f"{ where } \n \n "
217+ f"{ notes } \n \n "
218+ "Workflow (bibliography/README.md \" Adding a paper\" , "
219+ "wiki/CLAUDE.md): verify the paper against an authoritative "
220+ "record, add its canonical entry to the bibliography/ BibTeX "
221+ "file, add a minimal section to the matching "
222+ "wiki/<domain>/sources/ page — canonical key plus the notes "
223+ "above, nothing deeper — mark its queue line "
224+ "'DONE <date> — <title>' in reading-queue.md, and run "
225+ "make validate." )
226+ else :
227+ issue_title = f"queue intake: { paper ['title' ]} " [:200 ]
228+ label = "queue-intake"
229+ body = ("File this reading-queue paper into memory.\n \n "
230+ f"{ where } \n \n "
231+ f"{ notes } \n \n "
232+ "Workflow (bibliography/README.md \" Adding a paper\" , "
233+ "wiki/CLAUDE.md): verify the paper against an authoritative "
234+ "record, add its canonical entry to the bibliography/ BibTeX "
235+ "file, stub it in the matching wiki/<domain>/sources/ page — "
236+ "incorporating the notes above — mark its queue line "
237+ "'DONE <date> — <title>' in reading-queue.md, and run "
238+ "make validate." )
239+ return (f"{ repo_url } /issues/new?title={ _quote (issue_title , safe = '' )} "
240+ f"&body={ _quote (body , safe = '' )} &labels={ _quote (label , safe = '' )} " )
241+
242+
144243def _totals (snapshot : dict ) -> dict :
145244 wikis = snapshot .get ("wikis" ) or []
146245 statuses : dict [str , int ] = {}
@@ -216,7 +315,20 @@ def _render_md(snapshot: dict) -> str:
216315 lines += ["" , "## Reading queue" , "" ]
217316 for q in snapshot .get ("queue" ) or []:
218317 done = f", { q ['done' ]} read" if q .get ("done" ) else ""
219- lines .append (f"- { q ['section' ]} : { q ['count' ]} waiting{ done } " )
318+ lines += [f"<details><summary>{ q ['section' ]} : { q ['count' ]} "
319+ f"waiting{ done } </summary>" , "" ]
320+ for p in q .get ("papers" ) or []:
321+ if p ["done" ]:
322+ continue
323+ label = p ["title" ].replace ("[" , "\\ [" ).replace ("]" , "\\ ]" )
324+ bits = [f"- [{ label } ]({ paper_url (p )} )" ]
325+ for action , chip in (("intake" , "intake 📥" ), ("cite" , "cite 📑" ),
326+ ("read" , "read ✅" )):
327+ u = _queue_issue_url (snapshot , q ["section" ], p , action )
328+ if u :
329+ bits .append (f"[{ chip } ]({ u } )" )
330+ lines .append (" · " .join (bits ))
331+ lines += ["" , "</details>" ]
220332 if not snapshot .get ("queue" ):
221333 lines .append ("- _(queue empty or unavailable)_" )
222334 url = pages_url (snapshot )
@@ -241,7 +353,7 @@ def _copy_btn(payload: str, label: str = "copy") -> str:
241353 return (f"<button class='copy' type='button' "
242354 f"title='{ _html .escape (label , quote = True )} ' "
243355 f"data-copy=\" { _html .escape (payload , quote = True )} \" "
244- f"onclick='cp(this)'>📋</button>" )
356+ f"onclick='cp(this,event )'>📋</button>" )
245357
246358
247359def _bar (statuses : dict ) -> str :
@@ -259,17 +371,45 @@ def _render_html(snapshot: dict) -> str:
259371 pct = round (100 * t ["resolved" ] / t ["sections" ]) if t ["sections" ] else 0
260372 repo_url = _repo_url (snapshot )
261373
262- queue_rows = []
374+ queue_blocks = []
263375 for q in snapshot .get ("queue" ) or []:
264- done = (f" <span class='meta'>· { q ['done' ]} read</span>"
265- if q .get ("done" ) else "" )
266- queue_rows .append (
267- f"<tr><td class='name'>{ _html .escape (q ['section' ])} </td>"
268- f"<td>{ q ['count' ]} waiting{ done } "
376+ done = (f" · { q ['done' ]} read" if q .get ("done" ) else "" )
377+ items = []
378+ hist_items = []
379+ for p in q .get ("papers" ) or []:
380+ if p ["done" ]:
381+ hist_items .append (
382+ f"<li class='done'>DONE { _html .escape (p .get ('done_date' ) or '?' )} "
383+ f" — { _html .escape (p ['title' ])} </li>" )
384+ continue
385+ acts = []
386+ for action , icon , hint in (
387+ ("intake" , "📥" , "intake into memory: really interesting — "
388+ "opens a prefilled issue (add your notes) "
389+ "that becomes the full filing work item" ),
390+ ("cite" , "📑" , "make citeable: worth citing, not pivotal — "
391+ "opens a prefilled issue (add your notes) "
392+ "for a bib entry + minimal sources section" ),
393+ ("read" , "✅" , "read — don't file: opens a prefilled issue; "
394+ "submitting marks this line DONE" )):
395+ u = _queue_issue_url (snapshot , q ["section" ], p , action )
396+ if u :
397+ acts .append (f"<a class='act' href=\" { _html .escape (u , quote = True )} \" "
398+ f"title='{ _html .escape (hint , quote = True )} '>{ icon } </a>" )
399+ items .append (
400+ f"<li><a href=\" { _html .escape (paper_url (p ), quote = True )} \" >"
401+ f"{ _html .escape (p ['title' ])} </a>{ '' .join (acts )} </li>" )
402+ hist = (f"<details class='hist'><summary class='meta'>reading history "
403+ f"({ len (hist_items )} )</summary><ul class='papers'>"
404+ f"{ '' .join (hist_items )} </ul></details>" if hist_items else "" )
405+ queue_blocks .append (
406+ f"<details class='qsec'><summary><span class='name'>"
407+ f"{ _html .escape (q ['section' ])} </span> <span class='meta'>"
408+ f"{ q ['count' ]} waiting{ done } </span> "
269409 f"{ _copy_btn (_read_prompt (snapshot , q ['section' ]), 'copy: file the next paper' )} "
270- f"</td></tr >" )
271- if not queue_rows :
272- queue_rows .append ("<tr><td colspan='2 '>queue empty or unavailable</td></tr >" )
410+ f"</summary><ul class='papers'> { '' . join ( items ) } </ul> { hist } </details >" )
411+ if not queue_blocks :
412+ queue_blocks .append ("<p class='meta '>queue empty or unavailable</p >" )
273413
274414 todo_rows = []
275415 for w in snapshot .get ("wikis" ) or []:
@@ -328,10 +468,25 @@ def _render_html(snapshot: dict) -> str:
328468 color: #c9d1d9; cursor: pointer; padding: .05rem .45rem;
329469 margin-left: .35rem; font-size: .85rem; line-height: 1.4; }}
330470 button.copy:hover {{ background: #30363d; }}
471+ details.qsec {{ border-top: 1px solid #21262d; padding: .5rem .25rem; }}
472+ details.qsec > summary {{ cursor: pointer; list-style: none; }}
473+ details.qsec > summary::-webkit-details-marker {{ display: none; }}
474+ details.qsec > summary .name::before {{ content: "▸ "; color: #8b949e; }}
475+ details.qsec[open] > summary .name::before {{ content: "▾ "; }}
476+ .name {{ font-weight: 600; }}
477+ ul.papers {{ margin: .5rem 0 .25rem; padding-left: 1.3rem; }}
478+ ul.papers li {{ margin: .4rem 0; }}
479+ ul.papers li.done {{ color: #8b949e; }}
480+ a.act {{ margin-left: .35rem; padding: .05rem .35rem; font-size: .85rem;
481+ border: 1px solid #30363d; border-radius: 6px; background: #21262d; }}
482+ a.act:hover {{ background: #30363d; text-decoration: none; }}
483+ details.hist {{ margin: .25rem 0 .25rem 1.3rem; }}
484+ details.hist > summary {{ cursor: pointer; }}
331485 footer {{ margin-top: 2rem; color: #8b949e; font-size: .8rem; }}
332486</style>
333487<script>
334- function cp(b){{var t=b.getAttribute('data-copy');
488+ function cp(b,e){{if(e){{e.preventDefault();e.stopPropagation();}}
489+ var t=b.getAttribute('data-copy');
335490 if(navigator.clipboard&&navigator.clipboard.writeText){{
336491 navigator.clipboard.writeText(t).then(function(){{ok(b)}},function(){{fb(t)}});
337492 }}else{{fb(t)}}}}
@@ -343,9 +498,12 @@ def _render_html(snapshot: dict) -> str:
343498 <p><span class="pill">{ t ['pages' ]} pages · { pct } % cited</span> <span class="meta"><a href="dashboard.md">markdown version</a></span></p>
344499 <p class="meta">Contents and work queues for the organism's long-term memory
345500 — titles and counts only; the knowledge itself lives in the wiki pages.
346- 📋 copies a paste-ready prompt for a Claude Code chat.</p>
501+ 📋 copies a paste-ready prompt for a Claude Code chat; on a paper, the
502+ buttons open a prefilled GitHub issue — edit in your notes, submit to act:
503+ 📥 intake into memory (really interesting), 📑 make citeable (worth citing,
504+ not pivotal), ✅ read — don't file (marked DONE automatically).</p>
347505 <h2>Reading queue <span class='meta'>({ t ['queued' ]} papers waiting)</span></h2>
348- <table> { '' .join (queue_rows ) } </table>
506+ { '' .join (queue_blocks ) }
349507 <h2>Citation work queue <span class='meta'>({ t ['todo' ]} sections need a canonical key)</span></h2>
350508 <table>{ '' .join (todo_rows )} </table>
351509 <h2>Sub-wikis <span class='meta'>({ snapshot .get ('bib_entries' , 0 )} bibliography entries ·
0 commit comments