sciwrite-lint 0.2.0__py3-none-any.whl

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 (76) hide show
  1. sciwrite_lint/__init__.py +3 -0
  2. sciwrite_lint/__main__.py +527 -0
  3. sciwrite_lint/_network.py +195 -0
  4. sciwrite_lint/api.py +1484 -0
  5. sciwrite_lint/checks/__init__.py +1 -0
  6. sciwrite_lint/checks/_section_utils.py +111 -0
  7. sciwrite_lint/checks/cite_purpose.py +122 -0
  8. sciwrite_lint/checks/claim_support.py +96 -0
  9. sciwrite_lint/checks/cross_section_consistency.py +185 -0
  10. sciwrite_lint/checks/dangling_cite.py +93 -0
  11. sciwrite_lint/checks/dangling_ref.py +116 -0
  12. sciwrite_lint/checks/full_paper_consistency.py +604 -0
  13. sciwrite_lint/checks/ref_internal_checks.py +919 -0
  14. sciwrite_lint/checks/reference_accuracy.py +277 -0
  15. sciwrite_lint/checks/reference_exists.py +119 -0
  16. sciwrite_lint/checks/reference_unreliable.py +244 -0
  17. sciwrite_lint/checks/registry.py +136 -0
  18. sciwrite_lint/checks/retracted_cite.py +96 -0
  19. sciwrite_lint/checks/structure_promises.py +115 -0
  20. sciwrite_lint/checks/unreferenced_figure.py +70 -0
  21. sciwrite_lint/claims.py +330 -0
  22. sciwrite_lint/claude_backend.py +94 -0
  23. sciwrite_lint/claude_cli.py +405 -0
  24. sciwrite_lint/cli/__init__.py +1 -0
  25. sciwrite_lint/cli/check.py +480 -0
  26. sciwrite_lint/cli/config.py +229 -0
  27. sciwrite_lint/cli/fetch.py +250 -0
  28. sciwrite_lint/cli/misc.py +1202 -0
  29. sciwrite_lint/cli/rank.py +470 -0
  30. sciwrite_lint/cli/verify.py +437 -0
  31. sciwrite_lint/config.py +646 -0
  32. sciwrite_lint/cross_paper.py +174 -0
  33. sciwrite_lint/eval_claims.py +1196 -0
  34. sciwrite_lint/fulltext.py +851 -0
  35. sciwrite_lint/llm_utils.py +386 -0
  36. sciwrite_lint/local_pdfs.py +122 -0
  37. sciwrite_lint/manuscript_store.py +674 -0
  38. sciwrite_lint/models.py +139 -0
  39. sciwrite_lint/pdf/__init__.py +1 -0
  40. sciwrite_lint/pdf/grobid.py +785 -0
  41. sciwrite_lint/pdf/pdf_download.py +258 -0
  42. sciwrite_lint/pipeline.py +2694 -0
  43. sciwrite_lint/prompt_safety.py +30 -0
  44. sciwrite_lint/rate_limiter.py +227 -0
  45. sciwrite_lint/references/__init__.py +1 -0
  46. sciwrite_lint/references/citations.py +715 -0
  47. sciwrite_lint/references/crossref.py +282 -0
  48. sciwrite_lint/references/embedding_store.py +380 -0
  49. sciwrite_lint/references/matching.py +273 -0
  50. sciwrite_lint/references/metadata.py +273 -0
  51. sciwrite_lint/references/reference_store.py +823 -0
  52. sciwrite_lint/references/retraction_watch.py +178 -0
  53. sciwrite_lint/references/workspace_db.py +1390 -0
  54. sciwrite_lint/report.py +163 -0
  55. sciwrite_lint/schemas.py +260 -0
  56. sciwrite_lint/scoring/__init__.py +1 -0
  57. sciwrite_lint/scoring/chain.py +716 -0
  58. sciwrite_lint/scoring/contribution.py +322 -0
  59. sciwrite_lint/scoring/scilint_score.py +611 -0
  60. sciwrite_lint/tex_parser.py +248 -0
  61. sciwrite_lint/usage.py +594 -0
  62. sciwrite_lint/vision/__init__.py +1 -0
  63. sciwrite_lint/vision/cache.py +140 -0
  64. sciwrite_lint/vision/describe.py +311 -0
  65. sciwrite_lint/vision/image_extraction.py +491 -0
  66. sciwrite_lint/vision/pipeline.py +207 -0
  67. sciwrite_lint/vllm/__init__.py +1 -0
  68. sciwrite_lint/vllm/metrics.py +157 -0
  69. sciwrite_lint/vllm/vllm_server.py +445 -0
  70. sciwrite_lint/web.py +369 -0
  71. sciwrite_lint-0.2.0.dist-info/METADATA +268 -0
  72. sciwrite_lint-0.2.0.dist-info/RECORD +76 -0
  73. sciwrite_lint-0.2.0.dist-info/WHEEL +5 -0
  74. sciwrite_lint-0.2.0.dist-info/entry_points.txt +2 -0
  75. sciwrite_lint-0.2.0.dist-info/licenses/LICENSE +21 -0
  76. sciwrite_lint-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,116 @@
1
+ r"""Check: dangling-ref — cross-reference has no matching target.
2
+
3
+ For LaTeX: \ref{X} has no matching \label{X}.
4
+ For PDF: rendered "??" patterns in section text (broken references).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ from pathlib import Path
11
+
12
+ from sciwrite_lint.checks.registry import check
13
+ from sciwrite_lint.config import LintConfig
14
+ from sciwrite_lint.models import Finding
15
+ from sciwrite_lint.tex_parser import extract_body, find_all_commands, strip_comments
16
+
17
+
18
+ def _check_latex(tex_path: Path) -> list[Finding]:
19
+ r"""Check that all \ref{} have matching \label{}."""
20
+ text = strip_comments(tex_path.read_text(encoding="utf-8"))
21
+ body = extract_body(text)
22
+
23
+ labels = {arg for _, arg in find_all_commands(body, "label")}
24
+ refs = set()
25
+ for cmd in ["ref", "eqref", "pageref"]:
26
+ refs |= {arg for _, arg in find_all_commands(body, cmd)}
27
+
28
+ findings: list[Finding] = []
29
+ for ref in sorted(refs - labels):
30
+ findings.append(
31
+ Finding(
32
+ level="error",
33
+ rule_id="dangling-ref",
34
+ message=f"\\ref{{{ref}}} has no matching \\label",
35
+ file=tex_path.name,
36
+ )
37
+ )
38
+ return findings
39
+
40
+
41
+ # Pattern for broken references rendered as "??" in PDFs
42
+ _BROKEN_REF_PATTERN = re.compile(
43
+ r"""(?:
44
+ (?:Figure|Fig\.|Table|Tab\.|Section|Sec\.|Equation|Eq\.|
45
+ Theorem|Lemma|Proposition|Corollary|Definition|
46
+ Algorithm|Alg\.|Appendix|App\.|Chapter|Ch\.)\s*\?\?
47
+ |
48
+ \(\s*\?\?\s*\) # (??), e.g. rendered \eqref
49
+ |
50
+ (?<!\?)\?\?(?!\?) # standalone ?? (not part of ???+)
51
+ )""",
52
+ re.VERBOSE | re.IGNORECASE,
53
+ )
54
+
55
+
56
+ def _check_pdf(ctx: object) -> list[Finding]:
57
+ r"""Check for broken references ("??") in PDF section text.
58
+
59
+ In a rendered PDF, a dangling \ref{} appears as "??" (e.g.,
60
+ "Figure ??", "Eq. (??)"). GROBID preserves these in section text.
61
+ """
62
+ from sciwrite_lint.manuscript_store import ManuscriptContext
63
+
64
+ assert isinstance(ctx, ManuscriptContext)
65
+
66
+ findings: list[Finding] = []
67
+ seen_contexts: set[str] = set()
68
+
69
+ for sec in ctx.sections:
70
+ for match in _BROKEN_REF_PATTERN.finditer(sec.clean_text):
71
+ matched = match.group(0).strip()
72
+ # Dedup by matched text + section
73
+ dedup = f"{sec.title}:{matched}"
74
+ if dedup in seen_contexts:
75
+ continue
76
+ seen_contexts.add(dedup)
77
+
78
+ findings.append(
79
+ Finding(
80
+ level="error",
81
+ rule_id="dangling-ref",
82
+ message=f"Broken reference '{matched}' in section '{sec.title}'",
83
+ file=ctx.source_path.name,
84
+ )
85
+ )
86
+
87
+ # Also check abstract
88
+ for match in _BROKEN_REF_PATTERN.finditer(ctx.abstract):
89
+ matched = match.group(0).strip()
90
+ dedup = f"abstract:{matched}"
91
+ if dedup not in seen_contexts:
92
+ seen_contexts.add(dedup)
93
+ findings.append(
94
+ Finding(
95
+ level="error",
96
+ rule_id="dangling-ref",
97
+ message=f"Broken reference '{matched}' in abstract",
98
+ file=ctx.source_path.name,
99
+ )
100
+ )
101
+
102
+ return findings
103
+
104
+
105
+ @check(
106
+ id="dangling-ref",
107
+ category="manuscript",
108
+ severity="error",
109
+ description="Cross-reference has no matching target.",
110
+ )
111
+ def check_dangling_ref(tex_path: Path, config: LintConfig) -> list[Finding]:
112
+ """Check dangling references. Supports both LaTeX and PDF input."""
113
+ if config.is_pdf:
114
+ return _check_pdf(config.manuscript_context)
115
+
116
+ return _check_latex(tex_path)