dsh-aris-panel 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/README_CN.md +87 -0
  4. package/dsh/checkout.patch.yml +38 -0
  5. package/dsh/client.js +634 -0
  6. package/dsh/cordis.patch.yml +44 -0
  7. package/dsh/index.mjs +76 -0
  8. package/dsh/run-status.mjs +182 -0
  9. package/dsh/scope-limits.mjs +50 -0
  10. package/dsh/workbench.mjs +291 -0
  11. package/mcp-servers/claude-review/README.md +93 -0
  12. package/mcp-servers/claude-review/run_with_claude_aws.sh +49 -0
  13. package/mcp-servers/claude-review/server.py +718 -0
  14. package/mcp-servers/codex-image2/README.md +65 -0
  15. package/mcp-servers/codex-image2/server.py +893 -0
  16. package/mcp-servers/feishu-bridge/requirements.txt +1 -0
  17. package/mcp-servers/feishu-bridge/server.py +240 -0
  18. package/mcp-servers/gemini-review/README.md +171 -0
  19. package/mcp-servers/gemini-review/server.py +1856 -0
  20. package/mcp-servers/llm-chat/requirements.txt +1 -0
  21. package/mcp-servers/llm-chat/server.py +664 -0
  22. package/mcp-servers/manual-review/README.md +133 -0
  23. package/mcp-servers/manual-review/server.py +910 -0
  24. package/mcp-servers/manual-review/ui.html +279 -0
  25. package/mcp-servers/minimax-chat/requirements.txt +1 -0
  26. package/mcp-servers/minimax-chat/server.py +381 -0
  27. package/package.json +51 -0
  28. package/skills/ablation-planner/SKILL.md +123 -0
  29. package/skills/alphaxiv/SKILL.md +196 -0
  30. package/skills/analyze-results/SKILL.md +46 -0
  31. package/skills/arxiv/SKILL.md +248 -0
  32. package/skills/auto-paper-improvement-loop/SKILL.md +651 -0
  33. package/skills/auto-review-loop/SKILL.md +1137 -0
  34. package/skills/auto-review-loop-llm/SKILL.md +259 -0
  35. package/skills/auto-review-loop-minimax/SKILL.md +302 -0
  36. package/skills/citation-audit/SKILL.md +502 -0
  37. package/skills/claims-drafting/SKILL.md +227 -0
  38. package/skills/comm-lit-review/SKILL.md +297 -0
  39. package/skills/deepxiv/SKILL.md +263 -0
  40. package/skills/dse-loop/SKILL.md +296 -0
  41. package/skills/embodiment-description/SKILL.md +129 -0
  42. package/skills/exa-search/SKILL.md +205 -0
  43. package/skills/experiment-audit/SKILL.md +311 -0
  44. package/skills/experiment-bridge/SKILL.md +376 -0
  45. package/skills/experiment-plan/SKILL.md +249 -0
  46. package/skills/experiment-queue/SKILL.md +431 -0
  47. package/skills/experiment-queue/scripts/build_manifest.py +142 -0
  48. package/skills/experiment-queue/scripts/queue_manager.py +433 -0
  49. package/skills/feishu-notify/SKILL.md +156 -0
  50. package/skills/figure-description/SKILL.md +138 -0
  51. package/skills/figure-spec/SKILL.md +262 -0
  52. package/skills/figure-spec/scripts/figure_renderer.py +799 -0
  53. package/skills/formula-derivation/SKILL.md +280 -0
  54. package/skills/gemini-search/SKILL.md +231 -0
  55. package/skills/grant-proposal/SKILL.md +698 -0
  56. package/skills/idea-creator/SKILL.md +542 -0
  57. package/skills/idea-discovery/SKILL.md +521 -0
  58. package/skills/idea-discovery-robot/SKILL.md +363 -0
  59. package/skills/integrity-forensics/SKILL.md +284 -0
  60. package/skills/interview-cheatsheet/SKILL.md +245 -0
  61. package/skills/invention-structuring/SKILL.md +188 -0
  62. package/skills/jurisdiction-format/SKILL.md +192 -0
  63. package/skills/kill-argument/SKILL.md +437 -0
  64. package/skills/mermaid-diagram/SKILL.md +419 -0
  65. package/skills/meta-apply/SKILL.md +141 -0
  66. package/skills/meta-optimize/SKILL.md +437 -0
  67. package/skills/monitor-experiment/SKILL.md +140 -0
  68. package/skills/novelty-check/SKILL.md +101 -0
  69. package/skills/openalex/SKILL.md +237 -0
  70. package/skills/overleaf-sync/SKILL.md +220 -0
  71. package/skills/paper-claim-audit/SKILL.md +348 -0
  72. package/skills/paper-compile/SKILL.md +266 -0
  73. package/skills/paper-figure/SKILL.md +312 -0
  74. package/skills/paper-illustration/SKILL.md +736 -0
  75. package/skills/paper-illustration-image2/SKILL.md +391 -0
  76. package/skills/paper-illustration-image2/scripts/paper_illustration_image2.py +255 -0
  77. package/skills/paper-plan/SKILL.md +386 -0
  78. package/skills/paper-poster/SKILL.md +19 -0
  79. package/skills/paper-poster-html/DESIGN_FINAL.md +176 -0
  80. package/skills/paper-poster-html/IMPLEMENTATION_CONVENTIONS.md +161 -0
  81. package/skills/paper-poster-html/LICENSES/posterly-MIT.txt +21 -0
  82. package/skills/paper-poster-html/NOTICE.md +57 -0
  83. package/skills/paper-poster-html/SKILL.md +323 -0
  84. package/skills/paper-poster-html/scripts/_posterly/__init__.py +0 -0
  85. package/skills/paper-poster-html/scripts/_posterly/canvas.py +200 -0
  86. package/skills/paper-poster-html/scripts/_posterly/measure.py +588 -0
  87. package/skills/paper-poster-html/scripts/_posterly/polish.py +498 -0
  88. package/skills/paper-poster-html/scripts/_posterly/preflight.py +489 -0
  89. package/skills/paper-poster-html/scripts/_posterly/render.py +215 -0
  90. package/skills/paper-poster-html/scripts/_posterly/textutil.py +16 -0
  91. package/skills/paper-poster-html/scripts/_posterly/verify_final.py +171 -0
  92. package/skills/paper-poster-html/scripts/asset_check.py +897 -0
  93. package/skills/paper-poster-html/scripts/extract_pdf_figures.py +666 -0
  94. package/skills/paper-poster-html/scripts/poster_check.py +251 -0
  95. package/skills/paper-poster-html/scripts/preprocess_figures.py +238 -0
  96. package/skills/paper-poster-html/scripts/render_preview.py +217 -0
  97. package/skills/paper-poster-html/scripts/run_gates.py +556 -0
  98. package/skills/paper-poster-html/scripts/style_check.py +1324 -0
  99. package/skills/paper-poster-html/templates/COMPONENTS.md +462 -0
  100. package/skills/paper-poster-html/templates/README.md +170 -0
  101. package/skills/paper-poster-html/templates/landscape_4col.html +1032 -0
  102. package/skills/paper-poster-html/templates/landscape_hero.html +1046 -0
  103. package/skills/paper-poster-html/templates/portrait_2col.html +947 -0
  104. package/skills/paper-poster-html/templates/tokens/acl.json +9 -0
  105. package/skills/paper-poster-html/templates/tokens/cvpr.json +9 -0
  106. package/skills/paper-poster-html/templates/tokens/generic.json +9 -0
  107. package/skills/paper-poster-html/templates/tokens/iclr.json +9 -0
  108. package/skills/paper-poster-html/templates/tokens/icml.json +9 -0
  109. package/skills/paper-poster-html/templates/tokens/neurips.json +9 -0
  110. package/skills/paper-slides/SKILL.md +635 -0
  111. package/skills/paper-talk/SKILL.md +381 -0
  112. package/skills/paper-write/SKILL.md +604 -0
  113. package/skills/paper-write/templates/IEEEtran.bst +2409 -0
  114. package/skills/paper-write/templates/IEEEtran.cls +6347 -0
  115. package/skills/paper-write/templates/iclr2026.tex +84 -0
  116. package/skills/paper-write/templates/icml2025.tex +87 -0
  117. package/skills/paper-write/templates/ieee_conference.tex +89 -0
  118. package/skills/paper-write/templates/ieee_journal.tex +93 -0
  119. package/skills/paper-write/templates/math_commands.tex +48 -0
  120. package/skills/paper-write/templates/neurips2025.tex +80 -0
  121. package/skills/paper-writing/SKILL.md +916 -0
  122. package/skills/patent-novelty-check/SKILL.md +153 -0
  123. package/skills/patent-pipeline/SKILL.md +344 -0
  124. package/skills/patent-review/SKILL.md +203 -0
  125. package/skills/pixel-art/SKILL.md +137 -0
  126. package/skills/prior-art-search/SKILL.md +146 -0
  127. package/skills/proof-checker/SKILL.md +866 -0
  128. package/skills/proof-orchestrator/NOTICE.md +24 -0
  129. package/skills/proof-orchestrator/SKILL.md +254 -0
  130. package/skills/proof-orchestrator/references/audit-output-contract.md +126 -0
  131. package/skills/proof-orchestrator/references/deepseek-routing.md +74 -0
  132. package/skills/proof-orchestrator/references/dispatch-prompts.md +227 -0
  133. package/skills/proof-orchestrator/references/notation-audit.md +135 -0
  134. package/skills/proof-orchestrator/references/proof-audit-rubric.md +70 -0
  135. package/skills/proof-orchestrator/references/stress-tests.md +38 -0
  136. package/skills/proof-writer/SKILL.md +223 -0
  137. package/skills/qzcli/SKILL.md +324 -0
  138. package/skills/rebuttal/SKILL.md +376 -0
  139. package/skills/render-html/SKILL.md +316 -0
  140. package/skills/render-html/scripts/render_html.py +1006 -0
  141. package/skills/render-html/scripts/templates/academic.html +703 -0
  142. package/skills/render-html/scripts/templates/dashboard.html +333 -0
  143. package/skills/research-lit/SKILL.md +756 -0
  144. package/skills/research-pipeline/SKILL.md +384 -0
  145. package/skills/research-refine/SKILL.md +770 -0
  146. package/skills/research-refine-pipeline/SKILL.md +186 -0
  147. package/skills/research-review/SKILL.md +198 -0
  148. package/skills/research-wiki/SKILL.md +461 -0
  149. package/skills/resubmit-pipeline/SKILL.md +447 -0
  150. package/skills/result-to-claim/SKILL.md +311 -0
  151. package/skills/run-experiment/SKILL.md +313 -0
  152. package/skills/semantic-scholar/SKILL.md +236 -0
  153. package/skills/serverless-modal/SKILL.md +335 -0
  154. package/skills/shared-references/acceptance-gate.md +324 -0
  155. package/skills/shared-references/assurance-contract.md +248 -0
  156. package/skills/shared-references/capture-antipatterns.md +78 -0
  157. package/skills/shared-references/citation-discipline.md +583 -0
  158. package/skills/shared-references/compute-env-contract.md +163 -0
  159. package/skills/shared-references/effort-contract.md +183 -0
  160. package/skills/shared-references/evidence-precheck.md +65 -0
  161. package/skills/shared-references/experiment-integrity.md +49 -0
  162. package/skills/shared-references/external-cadence.md +326 -0
  163. package/skills/shared-references/fan-out-pattern.md +366 -0
  164. package/skills/shared-references/injection-hygiene.md +127 -0
  165. package/skills/shared-references/integration-contract.md +461 -0
  166. package/skills/shared-references/output-composition.md +93 -0
  167. package/skills/shared-references/output-language.md +45 -0
  168. package/skills/shared-references/output-manifest.md +49 -0
  169. package/skills/shared-references/output-versioning.md +111 -0
  170. package/skills/shared-references/patent-format-cn.md +199 -0
  171. package/skills/shared-references/patent-format-ep.md +173 -0
  172. package/skills/shared-references/patent-format-us.md +161 -0
  173. package/skills/shared-references/patent-writing-principles.md +197 -0
  174. package/skills/shared-references/prior-art-databases.md +141 -0
  175. package/skills/shared-references/resumable-runs.md +109 -0
  176. package/skills/shared-references/review-scope-limits.md +81 -0
  177. package/skills/shared-references/review-tracing.md +391 -0
  178. package/skills/shared-references/reviewer-independence.md +79 -0
  179. package/skills/shared-references/reviewer-routing.md +852 -0
  180. package/skills/shared-references/skill-governance.md +104 -0
  181. package/skills/shared-references/taste-calibration.md +85 -0
  182. package/skills/shared-references/venue-checklists.md +114 -0
  183. package/skills/shared-references/wiki-helper-resolution.md +134 -0
  184. package/skills/shared-references/writing-principles.md +525 -0
  185. package/skills/skills-codex/README.md +102 -0
  186. package/skills/skills-codex/README_CN.md +100 -0
  187. package/skills/skills-codex/ablation-planner/SKILL.md +126 -0
  188. package/skills/skills-codex/alphaxiv/SKILL.md +186 -0
  189. package/skills/skills-codex/analyze-results/SKILL.md +45 -0
  190. package/skills/skills-codex/arxiv/SKILL.md +210 -0
  191. package/skills/skills-codex/auto-paper-improvement-loop/SKILL.md +574 -0
  192. package/skills/skills-codex/auto-review-loop/SKILL.md +500 -0
  193. package/skills/skills-codex/auto-review-loop-llm/SKILL.md +247 -0
  194. package/skills/skills-codex/auto-review-loop-minimax/SKILL.md +290 -0
  195. package/skills/skills-codex/citation-audit/SKILL.md +504 -0
  196. package/skills/skills-codex/claims-drafting/SKILL.md +239 -0
  197. package/skills/skills-codex/comm-lit-review/SKILL.md +299 -0
  198. package/skills/skills-codex/comm-lit-review/references/domain-taxonomy.md +57 -0
  199. package/skills/skills-codex/comm-lit-review/references/output-template.md +37 -0
  200. package/skills/skills-codex/comm-lit-review/references/source-policy.md +99 -0
  201. package/skills/skills-codex/comm-lit-review/references/venue-tiering.md +112 -0
  202. package/skills/skills-codex/deepxiv/SKILL.md +142 -0
  203. package/skills/skills-codex/dse-loop/SKILL.md +285 -0
  204. package/skills/skills-codex/embodiment-description/SKILL.md +129 -0
  205. package/skills/skills-codex/exa-search/SKILL.md +192 -0
  206. package/skills/skills-codex/experiment-audit/SKILL.md +286 -0
  207. package/skills/skills-codex/experiment-bridge/SKILL.md +356 -0
  208. package/skills/skills-codex/experiment-plan/SKILL.md +249 -0
  209. package/skills/skills-codex/experiment-queue/SKILL.md +401 -0
  210. package/skills/skills-codex/feishu-notify/SKILL.md +155 -0
  211. package/skills/skills-codex/figure-description/SKILL.md +138 -0
  212. package/skills/skills-codex/figure-spec/SKILL.md +252 -0
  213. package/skills/skills-codex/formula-derivation/SKILL.md +280 -0
  214. package/skills/skills-codex/gemini-search/SKILL.md +205 -0
  215. package/skills/skills-codex/grant-proposal/SKILL.md +626 -0
  216. package/skills/skills-codex/idea-creator/SKILL.md +405 -0
  217. package/skills/skills-codex/idea-discovery/SKILL.md +475 -0
  218. package/skills/skills-codex/idea-discovery-robot/SKILL.md +362 -0
  219. package/skills/skills-codex/integrity-forensics/SKILL.md +106 -0
  220. package/skills/skills-codex/interview-cheatsheet/SKILL.md +245 -0
  221. package/skills/skills-codex/invention-structuring/SKILL.md +188 -0
  222. package/skills/skills-codex/jurisdiction-format/SKILL.md +192 -0
  223. package/skills/skills-codex/kill-argument/SKILL.md +403 -0
  224. package/skills/skills-codex/mermaid-diagram/SKILL.md +379 -0
  225. package/skills/skills-codex/meta-apply/SKILL.md +154 -0
  226. package/skills/skills-codex/meta-optimize/SKILL.md +348 -0
  227. package/skills/skills-codex/monitor-experiment/SKILL.md +98 -0
  228. package/skills/skills-codex/novelty-check/SKILL.md +89 -0
  229. package/skills/skills-codex/openalex/SKILL.md +228 -0
  230. package/skills/skills-codex/overleaf-sync/SKILL.md +220 -0
  231. package/skills/skills-codex/paper-claim-audit/SKILL.md +350 -0
  232. package/skills/skills-codex/paper-compile/SKILL.md +253 -0
  233. package/skills/skills-codex/paper-figure/SKILL.md +311 -0
  234. package/skills/skills-codex/paper-illustration/SKILL.md +690 -0
  235. package/skills/skills-codex/paper-illustration-image2/SKILL.md +383 -0
  236. package/skills/skills-codex/paper-illustration-image2/scripts/paper_illustration_image2.py +255 -0
  237. package/skills/skills-codex/paper-plan/SKILL.md +278 -0
  238. package/skills/skills-codex/paper-poster/SKILL.md +19 -0
  239. package/skills/skills-codex/paper-poster-html/SKILL.md +377 -0
  240. package/skills/skills-codex/paper-slides/SKILL.md +571 -0
  241. package/skills/skills-codex/paper-talk/SKILL.md +381 -0
  242. package/skills/skills-codex/paper-write/SKILL.md +411 -0
  243. package/skills/skills-codex/paper-write/templates/IEEEtran.bst +2409 -0
  244. package/skills/skills-codex/paper-write/templates/IEEEtran.cls +6347 -0
  245. package/skills/skills-codex/paper-write/templates/aaai2026.bst +1493 -0
  246. package/skills/skills-codex/paper-write/templates/aaai2026.sty +315 -0
  247. package/skills/skills-codex/paper-write/templates/aaai2026.tex +952 -0
  248. package/skills/skills-codex/paper-write/templates/acl.sty +312 -0
  249. package/skills/skills-codex/paper-write/templates/acl2026.tex +377 -0
  250. package/skills/skills-codex/paper-write/templates/acl_natbib.bst +1940 -0
  251. package/skills/skills-codex/paper-write/templates/acm.bst +3081 -0
  252. package/skills/skills-codex/paper-write/templates/acm_mm2026.tex +204 -0
  253. package/skills/skills-codex/paper-write/templates/acmart.cls +3520 -0
  254. package/skills/skills-codex/paper-write/templates/cvpr.bst +1448 -0
  255. package/skills/skills-codex/paper-write/templates/cvpr.sty +508 -0
  256. package/skills/skills-codex/paper-write/templates/cvpr2026.tex +63 -0
  257. package/skills/skills-codex/paper-write/templates/iclr2026.tex +84 -0
  258. package/skills/skills-codex/paper-write/templates/iclr2026_conference.bst +1440 -0
  259. package/skills/skills-codex/paper-write/templates/iclr2026_conference.sty +246 -0
  260. package/skills/skills-codex/paper-write/templates/icml2026.sty +767 -0
  261. package/skills/skills-codex/paper-write/templates/icml2026.tex +662 -0
  262. package/skills/skills-codex/paper-write/templates/ieee_conference.tex +89 -0
  263. package/skills/skills-codex/paper-write/templates/ieee_journal.tex +93 -0
  264. package/skills/skills-codex/paper-write/templates/math_commands.tex +48 -0
  265. package/skills/skills-codex/paper-write/templates/neurips2026.tex +493 -0
  266. package/skills/skills-codex/paper-write/templates/neurips_2026.sty +437 -0
  267. package/skills/skills-codex/paper-writing/SKILL.md +731 -0
  268. package/skills/skills-codex/patent-novelty-check/SKILL.md +153 -0
  269. package/skills/skills-codex/patent-pipeline/SKILL.md +344 -0
  270. package/skills/skills-codex/patent-review/SKILL.md +202 -0
  271. package/skills/skills-codex/pixel-art/SKILL.md +139 -0
  272. package/skills/skills-codex/prior-art-search/SKILL.md +146 -0
  273. package/skills/skills-codex/proof-checker/SKILL.md +554 -0
  274. package/skills/skills-codex/proof-orchestrator/SKILL.md +260 -0
  275. package/skills/skills-codex/proof-orchestrator/references/audit-output-contract.md +126 -0
  276. package/skills/skills-codex/proof-orchestrator/references/deepseek-routing.md +76 -0
  277. package/skills/skills-codex/proof-orchestrator/references/dispatch-prompts.md +227 -0
  278. package/skills/skills-codex/proof-orchestrator/references/notation-audit.md +135 -0
  279. package/skills/skills-codex/proof-orchestrator/references/proof-audit-rubric.md +70 -0
  280. package/skills/skills-codex/proof-orchestrator/references/stress-tests.md +38 -0
  281. package/skills/skills-codex/proof-writer/SKILL.md +222 -0
  282. package/skills/skills-codex/qzcli/SKILL.md +324 -0
  283. package/skills/skills-codex/rebuttal/SKILL.md +305 -0
  284. package/skills/skills-codex/render-html/SKILL.md +305 -0
  285. package/skills/skills-codex/render-html/scripts/__pycache__/render_html.cpython-314.pyc +0 -0
  286. package/skills/skills-codex/render-html/scripts/render_html.py +909 -0
  287. package/skills/skills-codex/render-html/scripts/templates/academic.html +342 -0
  288. package/skills/skills-codex/render-html/scripts/templates/dashboard.html +333 -0
  289. package/skills/skills-codex/research-lit/SKILL.md +464 -0
  290. package/skills/skills-codex/research-pipeline/SKILL.md +340 -0
  291. package/skills/skills-codex/research-refine/SKILL.md +721 -0
  292. package/skills/skills-codex/research-refine-pipeline/SKILL.md +186 -0
  293. package/skills/skills-codex/research-review/SKILL.md +135 -0
  294. package/skills/skills-codex/research-wiki/SKILL.md +421 -0
  295. package/skills/skills-codex/resubmit-pipeline/SKILL.md +444 -0
  296. package/skills/skills-codex/result-to-claim/SKILL.md +246 -0
  297. package/skills/skills-codex/run-experiment/SKILL.md +236 -0
  298. package/skills/skills-codex/semantic-scholar/SKILL.md +219 -0
  299. package/skills/skills-codex/serverless-modal/SKILL.md +335 -0
  300. package/skills/skills-codex/shared-references/acceptance-gate.md +336 -0
  301. package/skills/skills-codex/shared-references/assurance-contract.md +139 -0
  302. package/skills/skills-codex/shared-references/capture-antipatterns.md +84 -0
  303. package/skills/skills-codex/shared-references/citation-discipline.md +452 -0
  304. package/skills/skills-codex/shared-references/compute-env-contract.md +163 -0
  305. package/skills/skills-codex/shared-references/effort-contract.md +143 -0
  306. package/skills/skills-codex/shared-references/evidence-precheck.md +73 -0
  307. package/skills/skills-codex/shared-references/experiment-integrity.md +49 -0
  308. package/skills/skills-codex/shared-references/external-cadence.md +334 -0
  309. package/skills/skills-codex/shared-references/fan-out-pattern.md +375 -0
  310. package/skills/skills-codex/shared-references/injection-hygiene.md +132 -0
  311. package/skills/skills-codex/shared-references/integration-contract.md +372 -0
  312. package/skills/skills-codex/shared-references/output-composition.md +98 -0
  313. package/skills/skills-codex/shared-references/output-language.md +45 -0
  314. package/skills/skills-codex/shared-references/output-manifest.md +40 -0
  315. package/skills/skills-codex/shared-references/output-versioning.md +111 -0
  316. package/skills/skills-codex/shared-references/patent-format-cn.md +199 -0
  317. package/skills/skills-codex/shared-references/patent-format-ep.md +173 -0
  318. package/skills/skills-codex/shared-references/patent-format-us.md +161 -0
  319. package/skills/skills-codex/shared-references/patent-writing-principles.md +197 -0
  320. package/skills/skills-codex/shared-references/prior-art-databases.md +141 -0
  321. package/skills/skills-codex/shared-references/resumable-runs.md +125 -0
  322. package/skills/skills-codex/shared-references/review-scope-limits.md +81 -0
  323. package/skills/skills-codex/shared-references/review-tracing.md +144 -0
  324. package/skills/skills-codex/shared-references/reviewer-independence.md +66 -0
  325. package/skills/skills-codex/shared-references/reviewer-routing.md +128 -0
  326. package/skills/skills-codex/shared-references/skill-governance.md +119 -0
  327. package/skills/skills-codex/shared-references/taste-calibration.md +90 -0
  328. package/skills/skills-codex/shared-references/venue-checklists.md +73 -0
  329. package/skills/skills-codex/shared-references/wiki-helper-resolution.md +69 -0
  330. package/skills/skills-codex/shared-references/writing-principles.md +525 -0
  331. package/skills/skills-codex/slides-polish/SKILL.md +563 -0
  332. package/skills/skills-codex/specification-writing/SKILL.md +211 -0
  333. package/skills/skills-codex/system-profile/SKILL.md +103 -0
  334. package/skills/skills-codex/training-check/SKILL.md +83 -0
  335. package/skills/skills-codex/vast-gpu/SKILL.md +394 -0
  336. package/skills/skills-codex/web-debug-search/SKILL.md +334 -0
  337. package/skills/skills-codex/wiki-enrich/SKILL.md +255 -0
  338. package/skills/skills-codex/writing-systems-papers/SKILL.md +184 -0
  339. package/skills/skills-codex-claude-review/README.md +79 -0
  340. package/skills/skills-codex-claude-review/README_CN.md +78 -0
  341. package/skills/skills-codex-claude-review/auto-paper-improvement-loop/SKILL.md +581 -0
  342. package/skills/skills-codex-claude-review/auto-review-loop/SKILL.md +510 -0
  343. package/skills/skills-codex-claude-review/novelty-check/SKILL.md +102 -0
  344. package/skills/skills-codex-claude-review/paper-figure/SKILL.md +319 -0
  345. package/skills/skills-codex-claude-review/paper-plan/SKILL.md +287 -0
  346. package/skills/skills-codex-claude-review/paper-write/SKILL.md +420 -0
  347. package/skills/skills-codex-claude-review/research-refine/SKILL.md +732 -0
  348. package/skills/skills-codex-claude-review/research-review/SKILL.md +149 -0
  349. package/skills/skills-codex-gemini-review/README.md +176 -0
  350. package/skills/skills-codex-gemini-review/README_CN.md +175 -0
  351. package/skills/skills-codex-gemini-review/auto-paper-improvement-loop/SKILL.md +331 -0
  352. package/skills/skills-codex-gemini-review/auto-review-loop/SKILL.md +304 -0
  353. package/skills/skills-codex-gemini-review/grant-proposal/SKILL.md +630 -0
  354. package/skills/skills-codex-gemini-review/idea-creator/SKILL.md +263 -0
  355. package/skills/skills-codex-gemini-review/idea-discovery/SKILL.md +275 -0
  356. package/skills/skills-codex-gemini-review/idea-discovery-robot/SKILL.md +365 -0
  357. package/skills/skills-codex-gemini-review/novelty-check/SKILL.md +92 -0
  358. package/skills/skills-codex-gemini-review/paper-figure/SKILL.md +289 -0
  359. package/skills/skills-codex-gemini-review/paper-plan/SKILL.md +265 -0
  360. package/skills/skills-codex-gemini-review/paper-poster-html/SKILL.md +102 -0
  361. package/skills/skills-codex-gemini-review/paper-slides/SKILL.md +582 -0
  362. package/skills/skills-codex-gemini-review/paper-write/SKILL.md +346 -0
  363. package/skills/skills-codex-gemini-review/paper-writing/SKILL.md +312 -0
  364. package/skills/skills-codex-gemini-review/research-refine/SKILL.md +674 -0
  365. package/skills/skills-codex-gemini-review/research-review/SKILL.md +112 -0
  366. package/skills/slides-polish/SKILL.md +565 -0
  367. package/skills/specification-writing/SKILL.md +211 -0
  368. package/skills/system-profile/SKILL.md +103 -0
  369. package/skills/training-check/SKILL.md +132 -0
  370. package/skills/vast-gpu/SKILL.md +394 -0
  371. package/skills/web-debug-search/SKILL.md +334 -0
  372. package/skills/wiki-enrich/SKILL.md +257 -0
  373. package/skills/writing-systems-papers/SKILL.md +184 -0
  374. package/templates/CLAUDE_MD_TEMPLATE.md +29 -0
  375. package/templates/EXPERIMENT_LOG_TEMPLATE.md +47 -0
  376. package/templates/EXPERIMENT_PLAN_TEMPLATE.md +51 -0
  377. package/templates/EXPERIMENT_PLAN_TEMPLATE_CN.md +53 -0
  378. package/templates/FINDINGS_TEMPLATE.md +52 -0
  379. package/templates/IDEA_CANDIDATES_TEMPLATE.md +47 -0
  380. package/templates/IDEA_CANDIDATES_TEMPLATE_CN.md +47 -0
  381. package/templates/INVENTION_BRIEF_TEMPLATE.md +87 -0
  382. package/templates/MANIFEST_TEMPLATE.md +7 -0
  383. package/templates/NARRATIVE_REPORT_TEMPLATE.md +49 -0
  384. package/templates/PAPER_PLAN_TEMPLATE.md +47 -0
  385. package/templates/PATENT_CLAIMS_TEMPLATE.md +78 -0
  386. package/templates/PATENT_SPECIFICATION_TEMPLATE.md +68 -0
  387. package/templates/README.md +57 -0
  388. package/templates/RESEARCH_BRIEF_TEMPLATE.md +35 -0
  389. package/templates/RESEARCH_BRIEF_TEMPLATE_CN.md +41 -0
  390. package/templates/RESEARCH_CONTRACT_TEMPLATE.md +60 -0
  391. package/templates/claude-hooks/corpus_write_guard.json +16 -0
  392. package/templates/claude-hooks/corpus_write_guard.py +85 -0
  393. package/templates/claude-hooks/meta_logging.json +74 -0
  394. package/templates/gitignore-trace.txt +3 -0
  395. package/tools/__pycache__/check_skills_inventory.cpython-314.pyc +0 -0
  396. package/tools/arxiv_fetch.py +311 -0
  397. package/tools/capture_filter.py +126 -0
  398. package/tools/check_skills_inventory.py +273 -0
  399. package/tools/convert_skills_to_llm_chat.py +282 -0
  400. package/tools/copilot_native_evidence.py +818 -0
  401. package/tools/deepxiv_fetch.py +213 -0
  402. package/tools/evidence_check.py +212 -0
  403. package/tools/exa_search.py +425 -0
  404. package/tools/experiment_queue/README.md +118 -0
  405. package/tools/experiment_queue/build_manifest.py +44 -0
  406. package/tools/experiment_queue/queue_manager.py +44 -0
  407. package/tools/extract_paper_style.py +560 -0
  408. package/tools/figure_renderer.py +69 -0
  409. package/tools/forensics_gate.py +669 -0
  410. package/tools/generate_codex_claude_review_overrides.py +299 -0
  411. package/tools/idea_discovery_gate.py +256 -0
  412. package/tools/install_aris.ps1 +1372 -0
  413. package/tools/install_aris.sh +1370 -0
  414. package/tools/install_aris_codex.sh +1023 -0
  415. package/tools/install_aris_copilot.sh +1052 -0
  416. package/tools/iteration_log.py +143 -0
  417. package/tools/lint_skills_helpers.sh +84 -0
  418. package/tools/meta_opt/check_ready.sh +80 -0
  419. package/tools/meta_opt/log_event.sh +91 -0
  420. package/tools/meta_opt/trigger_eval.py +280 -0
  421. package/tools/meta_opt/trigger_evals.sample.json +28 -0
  422. package/tools/openalex_fetch.py +326 -0
  423. package/tools/overleaf_audit.sh +104 -0
  424. package/tools/overleaf_setup.sh +150 -0
  425. package/tools/paper_illustration_image2.py +62 -0
  426. package/tools/provenance.py +294 -0
  427. package/tools/research_wiki.py +1720 -0
  428. package/tools/review_gate.py +502 -0
  429. package/tools/run_state.py +399 -0
  430. package/tools/save_trace.sh +477 -0
  431. package/tools/semantic_scholar_fetch.py +438 -0
  432. package/tools/skill-groups.tsv +116 -0
  433. package/tools/skill_picker.py +238 -0
  434. package/tools/smart_update.ps1 +521 -0
  435. package/tools/smart_update.sh +591 -0
  436. package/tools/smart_update_codex.sh +419 -0
  437. package/tools/smart_update_copilot.sh +605 -0
  438. package/tools/threat_scan.py +222 -0
  439. package/tools/verify_paper_audits.sh +487 -0
  440. package/tools/verify_papers.py +613 -0
  441. package/tools/verify_wiki_coverage.sh +176 -0
  442. package/tools/watchdog.py +485 -0
@@ -0,0 +1,666 @@
1
+ #!/usr/bin/env python3
2
+ """extract_pdf_figures -- pull real figures out of a paper PDF.
3
+
4
+ The poster pipeline forbids fabricated visuals (DESIGN_FINAL Sec. 4): every
5
+ ``data-source="paper"`` image must be traceable back to a page+bbox of the
6
+ source PDF, recorded in ``FIGURE_MANIFEST.json`` with a sha256 chain. This
7
+ script is the acquisition end of that chain. It has three subcommands:
8
+
9
+ contact-sheet Render every page at a modest dpi into
10
+ ``DIR/contact_sheet_pNN.png`` with a labelled PDF-point
11
+ coordinate grid overlaid. The grid lets a human (or an
12
+ agent reading the PNG) read crop bboxes straight off the
13
+ page, in the same point units that ``crop`` expects.
14
+ auto Detect candidate figure regions on each page from three
15
+ cheap signals -- vector-drawing clusters
16
+ (``page.get_drawings``), embedded raster bboxes
17
+ (``page.get_images``/``get_image_rects``), and the big
18
+ vertical gaps between text blocks -- merge overlapping
19
+ candidates, and print a table of (page, bbox, w x h,
20
+ kind-guess). It NEVER writes images: it only proposes
21
+ bboxes for a human/agent to pass to ``crop``.
22
+ crop Render one page clipped to ``--bbox`` at ``--dpi`` into
23
+ ``DIR/<name>.png`` and upsert that figure into
24
+ ``FIGURE_MANIFEST.json`` (located at DIR's PARENT) with
25
+ the full schema-D field set, incl. sha256 of the crop,
26
+ its natural pixel size, and the source-PDF sha256.
27
+
28
+ All bbox coordinates are PDF points (72 dpi, fitz's native top-left
29
+ origin), so a number read off a contact-sheet axis can be fed verbatim to
30
+ ``crop --bbox``. PyMuPDF (``fitz``) and PIL are imported lazily inside the
31
+ functions that need them, so ``--help`` works in a stripped environment and
32
+ a missing dep yields a readable hint instead of an import traceback.
33
+ """
34
+ from __future__ import annotations
35
+
36
+ import argparse
37
+ import hashlib
38
+ import json
39
+ import os
40
+ import sys
41
+ from pathlib import Path
42
+
43
+ # Make `_posterly` importable when run directly (consistent with the other
44
+ # scripts in this dir); we reuse its ascii_safe output-boundary escaper.
45
+ _THIS_DIR = os.path.dirname(os.path.abspath(__file__))
46
+ if _THIS_DIR not in sys.path:
47
+ sys.path.insert(0, _THIS_DIR)
48
+
49
+ from _posterly.textutil import ascii_safe # noqa: E402
50
+
51
+ SCHEMA_VERSION = 1
52
+ CONTACT_DPI = 110 # contact-sheet render dpi (readable, small files)
53
+ GRID_STEP_PT = 50 # gridline spacing in PDF points
54
+ PT_PER_INCH = 72.0
55
+
56
+
57
+ def _eprint(*args: object, **kw: object) -> None:
58
+ print(*args, file=sys.stderr, **kw) # type: ignore[arg-type]
59
+
60
+
61
+ # --------------------------------------------------------------------------
62
+ # Lazy dependency loaders. Kept tiny so the import error message is the only
63
+ # thing the caller sees when a dep is missing -- no half-initialised state.
64
+ # --------------------------------------------------------------------------
65
+ def _load_fitz():
66
+ """Import PyMuPDF or exit(2) with an actionable message.
67
+
68
+ PyMuPDF is the engine for everything here (page raster, drawings,
69
+ image bboxes), so a missing import is a hard environment error, not a
70
+ degradation we can route around.
71
+ """
72
+ try:
73
+ import fitz # type: ignore # noqa: F401
74
+ return fitz
75
+ except ImportError:
76
+ _eprint(
77
+ "ERROR: PyMuPDF (fitz) not installed -- required for all "
78
+ "extract_pdf_figures subcommands. Install with:\n"
79
+ " python -m pip install pymupdf"
80
+ )
81
+ raise SystemExit(2)
82
+
83
+
84
+ def _load_pil():
85
+ """Import PIL pieces or exit(2). Only the image-writing paths
86
+ (contact-sheet, crop) need PIL; ``auto`` does not, so it stays usable
87
+ even if PIL is absent."""
88
+ try:
89
+ from PIL import Image, ImageDraw, ImageFont # type: ignore
90
+ return Image, ImageDraw, ImageFont
91
+ except ImportError:
92
+ _eprint(
93
+ "ERROR: Pillow (PIL) not installed -- required for "
94
+ "contact-sheet / crop rendering. Install with:\n"
95
+ " python -m pip install pillow"
96
+ )
97
+ raise SystemExit(2)
98
+
99
+
100
+ # --------------------------------------------------------------------------
101
+ # Small shared helpers.
102
+ # --------------------------------------------------------------------------
103
+ def _sha256_file(path: Path) -> str:
104
+ """Stream a file through sha256 (figures can be many MB; don't slurp)."""
105
+ h = hashlib.sha256()
106
+ with open(path, "rb") as fh:
107
+ for chunk in iter(lambda: fh.read(1 << 20), b""):
108
+ h.update(chunk)
109
+ return h.hexdigest()
110
+
111
+
112
+ def _pix_to_pil(pix, Image):
113
+ """Convert a fitz Pixmap to a PIL RGB image without a temp file.
114
+
115
+ fitz hands us raw interleaved samples; an alpha plane (n==4) or a
116
+ grayscale plane (n==1) is normalised to RGB so the grid overlay and
117
+ downstream PNG are uniform.
118
+ """
119
+ mode = "RGB"
120
+ if pix.n == 1:
121
+ mode = "L"
122
+ elif pix.n == 4:
123
+ mode = "RGBA"
124
+ img = Image.frombytes(mode, (pix.width, pix.height), pix.samples)
125
+ return img.convert("RGB")
126
+
127
+
128
+ def _open_doc(fitz, pdf_path: Path):
129
+ """Open the PDF or exit(2) with a readable error (corrupt/missing)."""
130
+ try:
131
+ return fitz.open(str(pdf_path))
132
+ except Exception as exc: # fitz raises a variety of types
133
+ _eprint(f"ERROR: could not open PDF {ascii_safe(pdf_path)}: "
134
+ f"{ascii_safe(exc)}")
135
+ raise SystemExit(2)
136
+
137
+
138
+ # --------------------------------------------------------------------------
139
+ # contact-sheet
140
+ # --------------------------------------------------------------------------
141
+ def cmd_contact_sheet(args: argparse.Namespace) -> int:
142
+ """Render each page to a gridded PNG so bboxes can be read by eye.
143
+
144
+ The grid is drawn in PDF-point space (every GRID_STEP_PT points) and
145
+ labelled with the point value on both axes. Because ``crop`` consumes
146
+ point bboxes, a reader can line a figure up against the gridlines and
147
+ transcribe ``x0,y0,x1,y1`` directly -- no dpi arithmetic.
148
+ """
149
+ fitz = _load_fitz()
150
+ Image, ImageDraw, ImageFont = _load_pil()
151
+
152
+ pdf_path = Path(args.pdf).resolve()
153
+ if not pdf_path.exists():
154
+ _eprint(f"ERROR: PDF not found: {ascii_safe(pdf_path)}")
155
+ return 2
156
+ out_dir = Path(args.out)
157
+ out_dir.mkdir(parents=True, exist_ok=True)
158
+
159
+ doc = _open_doc(fitz, pdf_path)
160
+ scale = CONTACT_DPI / PT_PER_INCH # px per point
161
+ matrix = fitz.Matrix(scale, scale)
162
+ font = ImageFont.load_default()
163
+
164
+ written: list[str] = []
165
+ for pno in range(doc.page_count):
166
+ page = doc[pno]
167
+ pix = page.get_pixmap(matrix=matrix)
168
+ img = _pix_to_pil(pix, Image)
169
+ draw = ImageDraw.Draw(img)
170
+
171
+ rect = page.rect # page box in points
172
+ w_pt, h_pt = rect.width, rect.height
173
+
174
+ # Light gridlines + axis labels. Vertical lines step across X,
175
+ # horizontal lines step down Y; both labelled with the point value.
176
+ grid_rgb = (170, 200, 230)
177
+ label_rgb = (40, 90, 150)
178
+ x = 0.0
179
+ while x <= w_pt + 0.1:
180
+ px = x * scale
181
+ draw.line([(px, 0), (px, img.height)], fill=grid_rgb, width=1)
182
+ draw.text((px + 2, 2), str(int(x)), fill=label_rgb, font=font)
183
+ x += GRID_STEP_PT
184
+ y = 0.0
185
+ while y <= h_pt + 0.1:
186
+ py = y * scale
187
+ draw.line([(0, py), (img.width, py)], fill=grid_rgb, width=1)
188
+ draw.text((2, py + 2), str(int(y)), fill=label_rgb, font=font)
189
+ y += GRID_STEP_PT
190
+
191
+ # Header caption: page index (1-based) + page size in points, so a
192
+ # reader knows the coordinate frame the grid is drawn in.
193
+ caption = (f"page {pno + 1}/{doc.page_count} "
194
+ f"size={int(round(w_pt))}x{int(round(h_pt))}pt "
195
+ f"grid={GRID_STEP_PT}pt")
196
+ draw.rectangle([0, 0, max(2, len(caption) * 7), 14],
197
+ fill=(255, 255, 255))
198
+ draw.text((2, 1), caption, fill=label_rgb, font=font)
199
+
200
+ out_path = out_dir / f"contact_sheet_p{pno + 1:02d}.png"
201
+ img.save(out_path)
202
+ written.append(str(out_path))
203
+ print(f"[contact-sheet] page {pno + 1}: "
204
+ f"{int(round(w_pt))}x{int(round(h_pt))}pt -> "
205
+ f"{ascii_safe(out_path)}")
206
+
207
+ doc.close()
208
+ if not written:
209
+ _eprint("ERROR: PDF has no pages.")
210
+ return 1
211
+ print(f"[contact-sheet] wrote {len(written)} sheet(s) to "
212
+ f"{ascii_safe(out_dir)} at {CONTACT_DPI} dpi.")
213
+ return 0
214
+
215
+
216
+ # --------------------------------------------------------------------------
217
+ # auto -- candidate region detection
218
+ # --------------------------------------------------------------------------
219
+ def _rects_overlap(a, b) -> bool:
220
+ """True if two (x0,y0,x1,y1) point rects overlap or touch."""
221
+ return not (a[2] < b[0] or b[2] < a[0] or a[3] < b[1] or b[3] < a[1])
222
+
223
+
224
+ def _union_rect(a, b):
225
+ return (min(a[0], b[0]), min(a[1], b[1]),
226
+ max(a[2], b[2]), max(a[3], b[3]))
227
+
228
+
229
+ def _merge_candidates(cands: list[dict]) -> list[dict]:
230
+ """Greedily merge overlapping candidate rects.
231
+
232
+ Candidates come from three independent signals (vectors, rasters, text
233
+ gaps) that frequently describe the SAME figure (a plot is both vector
234
+ strokes and an embedded raster). Merging overlaps collapses those into
235
+ one region. ``kind`` is concatenated so the table still shows what
236
+ signals contributed (e.g. ``vector+image``).
237
+ """
238
+ merged: list[dict] = []
239
+ for c in sorted(cands, key=lambda d: (d["page"], d["bbox"][1])):
240
+ placed = False
241
+ for m in merged:
242
+ if m["page"] == c["page"] and _rects_overlap(m["bbox"], c["bbox"]):
243
+ m["bbox"] = _union_rect(m["bbox"], c["bbox"])
244
+ kinds = set(m["kind"].split("+")) | set(c["kind"].split("+"))
245
+ m["kind"] = "+".join(sorted(kinds))
246
+ placed = True
247
+ break
248
+ if not placed:
249
+ merged.append(dict(c))
250
+ return merged
251
+
252
+
253
+ def _text_gap_candidates(page, min_gap_pt: float) -> list[dict]:
254
+ """Propose figure regions from big vertical voids between text blocks.
255
+
256
+ A figure with no vector strokes and no embedded raster (e.g. a
257
+ flattened image drawn as a single form XObject, or a wide table laid
258
+ out with rules) still shows up as a tall blank band between two text
259
+ blocks. We scan the gaps between consecutive text-block y-extents and
260
+ flag any taller than ``min_gap_pt`` as a 'gap' candidate spanning the
261
+ page's text column width.
262
+ """
263
+ rect = page.rect
264
+ blocks = [b for b in page.get_text("blocks") if (b[4] or "").strip()]
265
+ if not blocks:
266
+ return []
267
+ blocks.sort(key=lambda b: b[1]) # by top y
268
+ # Text column extent (min x0 .. max x1) bounds the candidate width.
269
+ col_x0 = min(b[0] for b in blocks)
270
+ col_x1 = max(b[2] for b in blocks)
271
+ out: list[dict] = []
272
+ for prev, cur in zip(blocks, blocks[1:]):
273
+ gap_top = prev[3] # bottom of previous block
274
+ gap_bot = cur[1] # top of next block
275
+ if gap_bot - gap_top >= min_gap_pt:
276
+ out.append({
277
+ "page": page.number + 1,
278
+ "bbox": (round(col_x0, 1), round(gap_top, 1),
279
+ round(col_x1, 1), round(gap_bot, 1)),
280
+ "kind": "gap",
281
+ })
282
+ # Also a leading gap at top-of-page (banner figure above first block).
283
+ top_lead = blocks[0][1] - rect.y0
284
+ if top_lead >= min_gap_pt:
285
+ out.append({
286
+ "page": page.number + 1,
287
+ "bbox": (round(col_x0, 1), round(rect.y0, 1),
288
+ round(col_x1, 1), round(blocks[0][1], 1)),
289
+ "kind": "gap",
290
+ })
291
+ return out
292
+
293
+
294
+ def _vector_candidates(page, min_area_pt2: float) -> list[dict]:
295
+ """Cluster vector drawings into figure-sized regions.
296
+
297
+ Individual ``get_drawings`` items are tiny (one stroke); we union all
298
+ drawing rects that overlap into clusters, then keep clusters whose
299
+ area clears ``min_area_pt2`` (filters out rules, underlines, and box
300
+ borders that aren't really figures).
301
+ """
302
+ draws = page.get_drawings()
303
+ rects = []
304
+ for d in draws:
305
+ r = d.get("rect")
306
+ if r is None:
307
+ continue
308
+ # Skip zero-area / degenerate strokes.
309
+ if r.width <= 0 or r.height <= 0:
310
+ continue
311
+ rects.append((r.x0, r.y0, r.x1, r.y1))
312
+ if not rects:
313
+ return []
314
+ # Union-by-overlap clustering (same greedy merge as candidates).
315
+ clusters: list[list[float]] = []
316
+ for r in sorted(rects, key=lambda t: t[1]):
317
+ placed = False
318
+ for c in clusters:
319
+ if _rects_overlap(c, r):
320
+ c[0], c[1] = min(c[0], r[0]), min(c[1], r[1])
321
+ c[2], c[3] = max(c[2], r[2]), max(c[3], r[3])
322
+ placed = True
323
+ break
324
+ if not placed:
325
+ clusters.append(list(r))
326
+ out = []
327
+ for c in clusters:
328
+ area = (c[2] - c[0]) * (c[3] - c[1])
329
+ if area >= min_area_pt2:
330
+ out.append({
331
+ "page": page.number + 1,
332
+ "bbox": (round(c[0], 1), round(c[1], 1),
333
+ round(c[2], 1), round(c[3], 1)),
334
+ "kind": "vector",
335
+ })
336
+ return out
337
+
338
+
339
+ def _image_candidates(page) -> list[dict]:
340
+ """Embedded-raster bboxes via get_image_rects (placed location)."""
341
+ out = []
342
+ for img in page.get_images(full=True):
343
+ xref = img[0]
344
+ try:
345
+ for r in page.get_image_rects(xref):
346
+ if r.width <= 0 or r.height <= 0:
347
+ continue
348
+ out.append({
349
+ "page": page.number + 1,
350
+ "bbox": (round(r.x0, 1), round(r.y0, 1),
351
+ round(r.x1, 1), round(r.y1, 1)),
352
+ "kind": "image",
353
+ })
354
+ except Exception:
355
+ # Some xrefs aren't placed on the page (soft mask, inline);
356
+ # skip rather than abort the whole scan.
357
+ continue
358
+ return out
359
+
360
+
361
+ def _guess_kind(kind: str, bbox) -> str:
362
+ """Refine the merged signal label into a human-facing guess.
363
+
364
+ Pure 'gap' regions that are much wider than tall read like tables/banners;
365
+ we surface that as 'table?' so a reviewer doesn't crop a table expecting
366
+ a plot. Image/vector signals are reported as-is (most reliable).
367
+ """
368
+ w = bbox[2] - bbox[0]
369
+ h = bbox[3] - bbox[1]
370
+ if kind == "gap":
371
+ if h > 0 and (w / h) >= 3.0:
372
+ return "table?"
373
+ return "region?"
374
+ return kind
375
+
376
+
377
+ def cmd_auto(args: argparse.Namespace) -> int:
378
+ """Detect + print candidate figure regions; write nothing.
379
+
380
+ The output table is the contract: page, bbox(points), w x h, kind-guess.
381
+ Bboxes are point rects directly usable as ``crop --bbox x0,y0,x1,y1``.
382
+ """
383
+ fitz = _load_fitz()
384
+
385
+ pdf_path = Path(args.pdf).resolve()
386
+ if not pdf_path.exists():
387
+ _eprint(f"ERROR: PDF not found: {ascii_safe(pdf_path)}")
388
+ return 2
389
+ # `auto` writes nothing, but --out is a required top-level arg; tolerate
390
+ # its absence (don't create dirs we won't use).
391
+
392
+ doc = _open_doc(fitz, pdf_path)
393
+ min_area = args.min_area
394
+ min_gap = args.min_gap
395
+
396
+ all_cands: list[dict] = []
397
+ for pno in range(doc.page_count):
398
+ page = doc[pno]
399
+ all_cands += _vector_candidates(page, min_area)
400
+ all_cands += _image_candidates(page)
401
+ all_cands += _text_gap_candidates(page, min_gap)
402
+
403
+ merged = _merge_candidates(all_cands)
404
+ # Drop merged regions below the min area floor (a tiny vector that only
405
+ # survived because it merged with a text gap, say).
406
+ merged = [m for m in merged
407
+ if (m["bbox"][2] - m["bbox"][0]) * (m["bbox"][3] - m["bbox"][1])
408
+ >= min_area]
409
+ merged.sort(key=lambda m: (m["page"], m["bbox"][1]))
410
+
411
+ doc.close()
412
+
413
+ # Print a fixed-width table. kind-guess folds the merged signal label.
414
+ print(f"# candidate figure regions for {ascii_safe(pdf_path.name)} "
415
+ f"(units: PDF points; bbox = x0,y0,x1,y1)")
416
+ print(f"{'page':>4} {'bbox (x0,y0,x1,y1)':<30} "
417
+ f"{'w x h':<16} kind-guess")
418
+ print("-" * 70)
419
+ if not merged:
420
+ print("(no candidates >= min-area; lower --min-area or use "
421
+ "contact-sheet to pick a bbox by hand)")
422
+ for m in merged:
423
+ b = m["bbox"]
424
+ w = b[2] - b[0]
425
+ h = b[3] - b[1]
426
+ bbox_s = f"{b[0]:.0f},{b[1]:.0f},{b[2]:.0f},{b[3]:.0f}"
427
+ wh_s = f"{w:.0f} x {h:.0f}"
428
+ kind = _guess_kind(m["kind"], b)
429
+ print(f"{m['page']:>4} {bbox_s:<30} {wh_s:<16} {kind}")
430
+ print("-" * 70)
431
+ print(f"# {len(merged)} candidate(s). Crop one with:\n"
432
+ f"# python3 extract_pdf_figures.py {ascii_safe(pdf_path.name)} "
433
+ f"--out DIR crop --page P --bbox x0,y0,x1,y1 --name fig_id")
434
+ return 0
435
+
436
+
437
+ # --------------------------------------------------------------------------
438
+ # crop -- render region + manifest upsert
439
+ # --------------------------------------------------------------------------
440
+ def _parse_bbox(s: str) -> tuple[float, float, float, float]:
441
+ """Parse 'x0,y0,x1,y1' point bbox or raise argparse error."""
442
+ parts = s.split(",")
443
+ if len(parts) != 4:
444
+ raise argparse.ArgumentTypeError(
445
+ f"--bbox must be 'x0,y0,x1,y1' (4 numbers), got {s!r}")
446
+ try:
447
+ x0, y0, x1, y1 = (float(p) for p in parts)
448
+ except ValueError:
449
+ raise argparse.ArgumentTypeError(
450
+ f"--bbox values must be numbers, got {s!r}")
451
+ if x1 <= x0 or y1 <= y0:
452
+ raise argparse.ArgumentTypeError(
453
+ f"--bbox must have x1>x0 and y1>y0, got {s!r}")
454
+ return (x0, y0, x1, y1)
455
+
456
+
457
+ def _manifest_path_for(out_dir: Path) -> Path:
458
+ """Manifest lives at DIR's PARENT (contract C/D): the convention is
459
+ ``poster_html/FIGURE_MANIFEST.json`` while crops land in
460
+ ``poster_html/assets/paper_figures/``."""
461
+ return out_dir.resolve().parent / "FIGURE_MANIFEST.json"
462
+
463
+
464
+ def _rel_file_path(manifest_path: Path, png_path: Path) -> str:
465
+ """Manifest 'file' field is relative to the manifest's directory when
466
+ possible (matches the schema-D example 'assets/paper_figures/x.png');
467
+ fall back to an absolute path if the crop lives outside the manifest
468
+ tree."""
469
+ try:
470
+ return str(png_path.resolve().relative_to(manifest_path.parent))
471
+ except ValueError:
472
+ return str(png_path.resolve())
473
+
474
+
475
+ def _load_manifest(manifest_path: Path, pdf_path: Path,
476
+ pdf_sha: str) -> dict:
477
+ """Load an existing manifest or seed a fresh one.
478
+
479
+ If a manifest exists for a DIFFERENT source PDF we keep its recorded
480
+ source_pdf untouched only when the sha matches; otherwise we overwrite
481
+ source_pdf with the current PDF (the common case is re-running against
482
+ the same paper, and a sha mismatch means the old record is stale).
483
+ """
484
+ if manifest_path.exists():
485
+ try:
486
+ data = json.loads(manifest_path.read_text(encoding="utf-8"))
487
+ except (json.JSONDecodeError, OSError) as exc:
488
+ _eprint(f"ERROR: existing manifest is unreadable: "
489
+ f"{ascii_safe(exc)}")
490
+ raise SystemExit(1)
491
+ data.setdefault("schema_version", SCHEMA_VERSION)
492
+ data.setdefault("figures", [])
493
+ # Refresh source_pdf if missing or pointing at a different file.
494
+ src = data.get("source_pdf") or {}
495
+ if src.get("sha256") != pdf_sha:
496
+ data["source_pdf"] = {"path": str(pdf_path), "sha256": pdf_sha}
497
+ return data
498
+ return {
499
+ "schema_version": SCHEMA_VERSION,
500
+ "source_pdf": {"path": str(pdf_path), "sha256": pdf_sha},
501
+ "figures": [],
502
+ }
503
+
504
+
505
+ def _upsert_figure(manifest: dict, entry: dict) -> None:
506
+ """Insert or replace the figure with this asset_id (upsert by id)."""
507
+ figs = manifest["figures"]
508
+ for i, f in enumerate(figs):
509
+ if f.get("asset_id") == entry["asset_id"]:
510
+ figs[i] = entry
511
+ return
512
+ figs.append(entry)
513
+
514
+
515
+ def cmd_crop(args: argparse.Namespace) -> int:
516
+ """Render one page clipped to a point bbox at --dpi, then upsert the
517
+ manifest with the full schema-D field set (incl. sha chains)."""
518
+ fitz = _load_fitz()
519
+ Image, _ImageDraw, _ImageFont = _load_pil()
520
+
521
+ pdf_path = Path(args.pdf).resolve()
522
+ if not pdf_path.exists():
523
+ _eprint(f"ERROR: PDF not found: {ascii_safe(pdf_path)}")
524
+ return 2
525
+ out_dir = Path(args.out)
526
+ out_dir.mkdir(parents=True, exist_ok=True)
527
+
528
+ doc = _open_doc(fitz, pdf_path)
529
+ page_idx = args.page - 1 # CLI page is 1-based
530
+ if page_idx < 0 or page_idx >= doc.page_count:
531
+ _eprint(f"ERROR: --page {args.page} out of range "
532
+ f"(PDF has {doc.page_count} page(s)).")
533
+ doc.close()
534
+ return 2
535
+ page = doc[page_idx]
536
+
537
+ x0, y0, x1, y1 = args.bbox
538
+ clip = fitz.Rect(x0, y0, x1, y1)
539
+ # Clamp the bbox to the page so a slightly-too-wide crop (common when
540
+ # reading off the grid) doesn't error -- but warn so the caller knows.
541
+ page_rect = page.rect
542
+ clamped = clip & page_rect # intersection
543
+ if clamped.is_empty:
544
+ _eprint(f"ERROR: --bbox {x0},{y0},{x1},{y1} does not intersect "
545
+ f"page {args.page} (size "
546
+ f"{page_rect.width:.0f}x{page_rect.height:.0f}pt).")
547
+ doc.close()
548
+ return 2
549
+ if (abs(clamped.x0 - x0) > 0.5 or abs(clamped.y0 - y0) > 0.5
550
+ or abs(clamped.x1 - x1) > 0.5 or abs(clamped.y1 - y1) > 0.5):
551
+ _eprint(f"[crop] WARN: bbox clamped to page bounds: "
552
+ f"{clamped.x0:.0f},{clamped.y0:.0f},"
553
+ f"{clamped.x1:.0f},{clamped.y1:.0f}")
554
+ clip = clamped
555
+
556
+ scale = args.dpi / PT_PER_INCH
557
+ matrix = fitz.Matrix(scale, scale)
558
+ pix = page.get_pixmap(matrix=matrix, clip=clip)
559
+ img = _pix_to_pil(pix, Image)
560
+
561
+ name = args.name
562
+ if not name.lower().endswith(".png"):
563
+ png_path = out_dir / f"{name}.png"
564
+ else:
565
+ png_path = out_dir / name
566
+ name = name[:-4]
567
+ img.save(png_path)
568
+ natural_px = [img.width, img.height]
569
+ crop_sha = _sha256_file(png_path)
570
+ pdf_sha = _sha256_file(pdf_path)
571
+
572
+ manifest_path = _manifest_path_for(out_dir)
573
+ manifest = _load_manifest(manifest_path, pdf_path, pdf_sha)
574
+ entry = {
575
+ "asset_id": name,
576
+ "file": _rel_file_path(manifest_path, png_path),
577
+ "from_paper": True,
578
+ "page": args.page,
579
+ # Record the (possibly clamped) bbox actually rendered, rounded to
580
+ # match schema-D's float-point style.
581
+ "bbox": [round(clip.x0, 1), round(clip.y0, 1),
582
+ round(clip.x1, 1), round(clip.y1, 1)],
583
+ "dpi": args.dpi,
584
+ "sha256": crop_sha,
585
+ "natural_px": natural_px,
586
+ "caption_hint": args.caption_hint or "",
587
+ }
588
+ _upsert_figure(manifest, entry)
589
+ manifest_path.write_text(
590
+ json.dumps(manifest, indent=2, ensure_ascii=False) + "\n",
591
+ encoding="utf-8",
592
+ )
593
+
594
+ doc.close()
595
+ print(f"[crop] page {args.page} bbox=({clip.x0:.0f},{clip.y0:.0f},"
596
+ f"{clip.x1:.0f},{clip.y1:.0f})pt @ {args.dpi}dpi -> "
597
+ f"{ascii_safe(png_path)} ({natural_px[0]}x{natural_px[1]}px)")
598
+ print(f"[crop] manifest upserted: {ascii_safe(manifest_path)} "
599
+ f"(asset_id={name})")
600
+ return 0
601
+
602
+
603
+ # --------------------------------------------------------------------------
604
+ # argument parser
605
+ # --------------------------------------------------------------------------
606
+ def build_parser() -> argparse.ArgumentParser:
607
+ p = argparse.ArgumentParser(
608
+ prog="extract_pdf_figures",
609
+ description="Extract real paper figures from a PDF (contact-sheet "
610
+ "/ auto / crop). bbox units = PDF points.",
611
+ )
612
+ p.add_argument("pdf", help="source paper PDF")
613
+ p.add_argument("--out", required=True,
614
+ help="output directory for PNGs (manifest is written to "
615
+ "this dir's PARENT as FIGURE_MANIFEST.json)")
616
+ p.add_argument("--dpi", type=int, default=350,
617
+ help="crop render dpi (default 350; contact-sheet is "
618
+ "fixed at a modest dpi for readability)")
619
+ sub = p.add_subparsers(dest="cmd", required=True)
620
+
621
+ pc = sub.add_parser(
622
+ "contact-sheet",
623
+ help="render every page with a labelled PDF-point grid overlay",
624
+ )
625
+ pc.set_defaults(func=cmd_contact_sheet)
626
+
627
+ pa = sub.add_parser(
628
+ "auto",
629
+ help="detect + print candidate figure regions (writes nothing)",
630
+ )
631
+ pa.add_argument(
632
+ "--min-area", type=float, default=10000.0,
633
+ help="ignore candidate regions below this area in point^2 "
634
+ "(default 10000, ~ 100x100pt; filters rules and borders)",
635
+ )
636
+ pa.add_argument(
637
+ "--min-gap", type=float, default=60.0,
638
+ help="min vertical void between text blocks (points) to flag as a "
639
+ "figure-region candidate (default 60)",
640
+ )
641
+ pa.set_defaults(func=cmd_auto)
642
+
643
+ pcr = sub.add_parser(
644
+ "crop",
645
+ help="render page clipped to --bbox and upsert FIGURE_MANIFEST.json",
646
+ )
647
+ pcr.add_argument("--page", type=int, required=True,
648
+ help="1-based page number")
649
+ pcr.add_argument("--bbox", type=_parse_bbox, required=True,
650
+ help="crop bbox in PDF points: x0,y0,x1,y1")
651
+ pcr.add_argument("--name", required=True,
652
+ help="asset_id / output PNG stem (e.g. fig_method)")
653
+ pcr.add_argument("--caption-hint", default=None,
654
+ help="optional caption hint stored in the manifest")
655
+ pcr.set_defaults(func=cmd_crop)
656
+
657
+ return p
658
+
659
+
660
+ def main(argv: list[str] | None = None) -> int:
661
+ args = build_parser().parse_args(argv)
662
+ return args.func(args)
663
+
664
+
665
+ if __name__ == "__main__":
666
+ sys.exit(main())