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.
- sciwrite_lint/__init__.py +3 -0
- sciwrite_lint/__main__.py +527 -0
- sciwrite_lint/_network.py +195 -0
- sciwrite_lint/api.py +1484 -0
- sciwrite_lint/checks/__init__.py +1 -0
- sciwrite_lint/checks/_section_utils.py +111 -0
- sciwrite_lint/checks/cite_purpose.py +122 -0
- sciwrite_lint/checks/claim_support.py +96 -0
- sciwrite_lint/checks/cross_section_consistency.py +185 -0
- sciwrite_lint/checks/dangling_cite.py +93 -0
- sciwrite_lint/checks/dangling_ref.py +116 -0
- sciwrite_lint/checks/full_paper_consistency.py +604 -0
- sciwrite_lint/checks/ref_internal_checks.py +919 -0
- sciwrite_lint/checks/reference_accuracy.py +277 -0
- sciwrite_lint/checks/reference_exists.py +119 -0
- sciwrite_lint/checks/reference_unreliable.py +244 -0
- sciwrite_lint/checks/registry.py +136 -0
- sciwrite_lint/checks/retracted_cite.py +96 -0
- sciwrite_lint/checks/structure_promises.py +115 -0
- sciwrite_lint/checks/unreferenced_figure.py +70 -0
- sciwrite_lint/claims.py +330 -0
- sciwrite_lint/claude_backend.py +94 -0
- sciwrite_lint/claude_cli.py +405 -0
- sciwrite_lint/cli/__init__.py +1 -0
- sciwrite_lint/cli/check.py +480 -0
- sciwrite_lint/cli/config.py +229 -0
- sciwrite_lint/cli/fetch.py +250 -0
- sciwrite_lint/cli/misc.py +1202 -0
- sciwrite_lint/cli/rank.py +470 -0
- sciwrite_lint/cli/verify.py +437 -0
- sciwrite_lint/config.py +646 -0
- sciwrite_lint/cross_paper.py +174 -0
- sciwrite_lint/eval_claims.py +1196 -0
- sciwrite_lint/fulltext.py +851 -0
- sciwrite_lint/llm_utils.py +386 -0
- sciwrite_lint/local_pdfs.py +122 -0
- sciwrite_lint/manuscript_store.py +674 -0
- sciwrite_lint/models.py +139 -0
- sciwrite_lint/pdf/__init__.py +1 -0
- sciwrite_lint/pdf/grobid.py +785 -0
- sciwrite_lint/pdf/pdf_download.py +258 -0
- sciwrite_lint/pipeline.py +2694 -0
- sciwrite_lint/prompt_safety.py +30 -0
- sciwrite_lint/rate_limiter.py +227 -0
- sciwrite_lint/references/__init__.py +1 -0
- sciwrite_lint/references/citations.py +715 -0
- sciwrite_lint/references/crossref.py +282 -0
- sciwrite_lint/references/embedding_store.py +380 -0
- sciwrite_lint/references/matching.py +273 -0
- sciwrite_lint/references/metadata.py +273 -0
- sciwrite_lint/references/reference_store.py +823 -0
- sciwrite_lint/references/retraction_watch.py +178 -0
- sciwrite_lint/references/workspace_db.py +1390 -0
- sciwrite_lint/report.py +163 -0
- sciwrite_lint/schemas.py +260 -0
- sciwrite_lint/scoring/__init__.py +1 -0
- sciwrite_lint/scoring/chain.py +716 -0
- sciwrite_lint/scoring/contribution.py +322 -0
- sciwrite_lint/scoring/scilint_score.py +611 -0
- sciwrite_lint/tex_parser.py +248 -0
- sciwrite_lint/usage.py +594 -0
- sciwrite_lint/vision/__init__.py +1 -0
- sciwrite_lint/vision/cache.py +140 -0
- sciwrite_lint/vision/describe.py +311 -0
- sciwrite_lint/vision/image_extraction.py +491 -0
- sciwrite_lint/vision/pipeline.py +207 -0
- sciwrite_lint/vllm/__init__.py +1 -0
- sciwrite_lint/vllm/metrics.py +157 -0
- sciwrite_lint/vllm/vllm_server.py +445 -0
- sciwrite_lint/web.py +369 -0
- sciwrite_lint-0.2.0.dist-info/METADATA +268 -0
- sciwrite_lint-0.2.0.dist-info/RECORD +76 -0
- sciwrite_lint-0.2.0.dist-info/WHEEL +5 -0
- sciwrite_lint-0.2.0.dist-info/entry_points.txt +2 -0
- sciwrite_lint-0.2.0.dist-info/licenses/LICENSE +21 -0
- 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)
|