
Backward Traceability
- 1 installs
- 255 repo stars
- Updated February 27, 2026
- lingzhi227/claude-research-skills
Makes every number in a paper's final PDF hyperlink back to the exact code line that produced it using LaTeX hypertarget/hyperlink and compile-time \num evaluation.
About
Tags code outputs and paper text with LaTeX hypertarget/hyperlink anchors so every numeric result in a research PDF traces to the code line that produced it, and verifies cross-reference integrity. Use it for reproducibility and data-integrity checks when writing research papers.
- Scans and verifies hypertarget/hyperlink references via a Python script
- Uses \num{} for compile-time evaluation of derived values
Backward Traceability by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,361 of 1,879 Documentation skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/lingzhi227/claude-research-skills --skill backward-traceabilityAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 255 |
| Last updated | February 27, 2026 |
| Repository | lingzhi227/claude-research-skills ↗ |
What it does
Makes every number in a paper's final PDF hyperlink back to the exact code line that produced it using LaTeX hypertarget/hyperlink and compile-time \num evaluation.
Files
Backward Traceability
Make every number in the final PDF hyperlink back to the exact code line that produced it.
Input
$0— Paper project directory containing code and LaTeX files
References
- Traceability patterns and LaTeX commands:
~/.claude/skills/backward-traceability/references/traceability-patterns.md
Scripts
Scan hypertarget/hyperlink references
python ~/.claude/skills/backward-traceability/scripts/ref_numeric_values.py \
--scan paper/main.tex --output report.jsonReports: all hypertargets, hyperlinks, orphan references, unreferenced numeric values.
Verify cross-reference integrity
python ~/.claude/skills/backward-traceability/scripts/ref_numeric_values.py \
--verify paper/main.tex --code-output results.txtCross-checks values between paper text and code output. Reports mismatches.
Workflow
Step 1: Tag Code Outputs
For every numeric value produced by experiment code, add hypertarget tags:
# In experiment code output:
print(f"\\hypertarget{{R1a}}{{45.3}}") # Mean accuracy
print(f"\\hypertarget{{R1b}}{{2.1}}") # Std deviationLabel format: {prefix}{line_number}{letter} where letter = a, b, c... for multiple values on same line.
Step 2: Reference in Paper Text
Use \hyperlink to create clickable references in the paper:
Our method achieves \hyperlink{R1a}{45.3}\% accuracy
($\pm$\hyperlink{R1b}{2.1}).Step 3: Use \num for Computed Values
For values derived from other values, use \num{} for compile-time evaluation:
% \num{formula, "explanation"} → evaluated at compile time
The improvement is \num{45.3 - 38.7, "accuracy gain"}\%.Step 4: Generate Appendix Code Listing
Create an appendix with the full code listing, with \hypertarget anchors at relevant lines:
\section*{Appendix: Code Listing}
\begin{lstlisting}[escapechar=@]
@\hypertarget{code1}{}@result = model.evaluate(test_data)
@\hypertarget{code2}{}@accuracy = result['accuracy']
\end{lstlisting}Step 5: Verify Traceability
- Every number in the paper text must have a corresponding
\hypertargetin the code - Every
\num{}formula must evaluate correctly - Click-test: every hyperlink in the PDF must jump to the correct code line
LaTeX Setup
Required packages:
\usepackage{hyperref}
\usepackage{listings}Rules
- Every numeric result in the paper MUST trace to code output
- Never manually type numbers — always reference tagged outputs
- Use
\num{}for any derived/computed values - Code listing in appendix must match actual executed code
- Verify all hyperlinks resolve correctly after compilation
Related Skills
- Upstream: experiment-code, data-analysis
- Downstream: paper-compilation
- See also: paper-assembly
Backward Traceability Patterns
Extracted from data-to-paper (ref_numeric_values.py, referencable_text.py, latex_to_pdf.py).
Core LaTeX Commands
\hypertarget — Mark a Value in Code Output
% In code-generated output:
\hypertarget{R1a}{45.3}
% R1a = reference label, 45.3 = the value\hyperlink — Reference a Value in Paper Text
% In paper body:
Our method achieves \hyperlink{R1a}{45.3}\% accuracy.
% Clicking 45.3 in PDF jumps to the code output\num — Compile-Time Evaluated Formula
% Compute derived values at compile time:
\num{45.3 - 38.7, "accuracy improvement over baseline"}
% Evaluates to 6.6 and stores explanationLabel Format Convention (data-to-paper)
Label = {prefix}{line_number}{letter}
prefix: identifies the code block (e.g., "code", "R")
line_number: line in the code that produces the value
letter: a, b, c... for multiple values on same line
Examples:
code1a → code block, line 1, 1st value
code1b → code block, line 1, 2nd value
code12a → code block, line 12, 1st value
R3c → results block, line 3, 3rd valueLetter conversion (data-to-paper referencable_text.py):
def _num_to_letters(num):
"""1→a, 2→b, ... 26→z, 27→aa, 28→ab, ..."""
letters = ''
while num > 0:
num -= 1
letters = chr(ord('a') + num % 26) + letters
num //= 26
return lettersHypertargetPosition Modes (data-to-paper)
class HypertargetPosition(Enum):
WRAP = "wrap" # \hypertarget{label}{value}
ADJACENT = "adjacent" # \hypertarget{label}{}value
HEADER = "header" # \hypertarget{label}{} (value elsewhere)
NONE = "none" # No hypertargets\num Implementation (data-to-paper latex_to_pdf.py)
def evaluate_latex_num_command(latex_str, ref_prefix='',
enforce_explanation=True):
r"""
Evaluates \num{formula} or \num{formula, "explanation"} in latex.
If ref_prefix provided, adds \hyperlink{ref_prefix?}{result}
where ? is the index.
Returns:
- new_latex_str: with \num{} replaced by computed values
- labels_to_notes: dict mapping labels to "formula = result"
"""
# Available math functions for eval():
namespace = {
'exp': np.exp, 'log': np.log,
'sin': np.sin, 'cos': np.cos, 'tan': np.tan,
'pi': np.pi, 'e': np.e,
'sqrt': np.sqrt, 'log2': np.log2, 'log10': np.log10,
'abs': np.abs,
}
result = eval(formula_without_hyperlinks, namespace)
# Create hyperlink:
label = f'{ref_prefix}{index}'
replace_with = f'\\hyperlink{{{label}}}{{{result}}}'
# Store note:
labels_to_notes[label] = f"{formula} = {result}"
if explanation:
labels_to_notes[label] += f" ({explanation})"Code Output Tagging Pattern
Python Code (Experiment Script)
# Tag every numeric output with hypertarget
def save_results_with_targets(results, prefix="R"):
"""Save results with LaTeX hypertarget tags."""
lines = []
line_no = 1
for key, value in results.items():
letter = 'a'
if isinstance(value, dict):
for subkey, subval in value.items():
label = f"{prefix}{line_no}{letter}"
lines.append(f"\\hypertarget{{{label}}}{{{subval:.4f}}}")
letter = chr(ord(letter) + 1)
else:
label = f"{prefix}{line_no}a"
lines.append(f"\\hypertarget{{{label}}}{{{value:.4f}}}")
line_no += 1
return linesUsage in Experiment
results = {
"accuracy": 0.453,
"precision": 0.421,
"recall": 0.487,
"f1": 0.452,
}
tagged = save_results_with_targets(results)
# Output:
# \hypertarget{R1a}{0.4530}
# \hypertarget{R2a}{0.4210}
# \hypertarget{R3a}{0.4870}
# \hypertarget{R4a}{0.4520}Appendix Code Listing Template
\section*{Appendix A: Experiment Code}
\begin{lstlisting}[
language=Python,
basicstyle=\ttfamily\scriptsize,
numbers=left,
numberstyle=\tiny,
escapechar=@,
caption={Main experiment code with traceability anchors}
]
@\hypertarget{code1}{}@import torch
@\hypertarget{code2}{}@from model import MyModel
@\hypertarget{code5}{}@model = MyModel(hidden_dim=256)
@\hypertarget{code6}{}@optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
@\hypertarget{code10}{}@for epoch in range(100):
@\hypertarget{code11}{}@ loss = train_epoch(model, train_loader)
@\hypertarget{code12}{}@ acc = evaluate(model, test_loader)
@\hypertarget{code15}{}@final_accuracy = evaluate(model, test_loader)
@\hypertarget{code16}{}@print(f"Final: {final_accuracy:.4f}")
\end{lstlisting}Calculation Notes Section
\section*{Appendix B: Calculation Notes}
The following values in this paper are computed from experimental results:
\begin{itemize}
\item \hyperlink{N1}{6.6}: $45.3 - 38.7 = 6.6$ (accuracy improvement)
\item \hyperlink{N2}{1.17}: $45.3 / 38.7 = 1.17$ (relative improvement)
\item \hyperlink{N3}{14.6}: $(45.3 - 38.7) / 45.3 \times 100 = 14.6$ (\% relative gain)
\end{itemize}Required LaTeX Packages
\usepackage{hyperref} % For \hypertarget, \hyperlink
\usepackage{listings} % For code listings with escapechar
\usepackage{xcolor} % For colored hyperlinks (optional)
% Optional: make hyperlinks colored
\hypersetup{
colorlinks=true,
linkcolor=blue,
citecolor=blue,
}Verification Checklist
For every number in the paper text:
[ ] Has a corresponding \hypertarget in code output
[ ] \hyperlink label matches the \hypertarget label
[ ] Value in text matches value in code output
[ ] Click-test: hyperlink in PDF jumps to correct location
For every \num{} command:
[ ] Formula evaluates correctly
[ ] Explanation is provided
[ ] Result matches expected value
[ ] Hyperlink (if ref_prefix) resolves correctly#!/usr/bin/env python3
"""Scan and verify hypertarget/hyperlink numeric references in LaTeX files.
Two modes:
--scan: Report all \\hypertarget and \\hyperlink usage in a .tex file
--verify: Cross-reference targets vs links for integrity
Self-contained: uses only stdlib.
Extracted from data-to-paper's ref_numeric_values.py.
Usage:
python ref_numeric_values.py --scan main.tex --output report.json
python ref_numeric_values.py --verify main.tex --code-output results.txt
python ref_numeric_values.py --scan main.tex
"""
import argparse
import json
import os
import re
import sys
from dataclasses import dataclass, asdict
from typing import Optional
TARGET = r'\hypertarget'
LINK = r'\hyperlink'
def get_numeric_value_pattern(must_follow: Optional[str] = None,
allow_commas: bool = True) -> str:
"""Get a regex pattern for numeric values."""
prefix = ""
if must_follow is not None:
prefix = f"(?<={must_follow})"
if allow_commas:
pattern = r'(?:[-+]?\d+(?:,\d{3})*(?:\.\d+)?(?:e[-+]?\d+)?|\d{1,3}(?:,\d{3})+)(?!\d)'
else:
pattern = r'[-+]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?'
return prefix + pattern
NUMERIC_PATTERN = get_numeric_value_pattern(
must_follow=r'[$,{<=\s\n\(\[]', allow_commas=True
)
@dataclass
class ReferencedValue:
"""A numeric value with a reference label."""
value: str
label: Optional[str] = None
is_target: bool = True
line_num: int = 0
@property
def command(self) -> str:
return TARGET if self.is_target else LINK
def get_hyperlink_pattern(is_target: bool = False) -> str:
"""Get regex pattern for \\hypertarget or \\hyperlink commands."""
command = re.escape(TARGET if is_target else LINK)
return rf'{command}\{{(?P<reference>[^}}]*)\}}\{{(?P<value>[^}}]*)\}}'
def find_references(text: str, is_targets: bool = False) -> list[ReferencedValue]:
"""Find all hypertarget or hyperlink references in text."""
pattern = get_hyperlink_pattern(is_targets)
refs = []
for i, line in enumerate(text.splitlines(), 1):
for match in re.finditer(pattern, line):
refs.append(ReferencedValue(
value=match.group('value'),
label=match.group('reference'),
is_target=is_targets,
line_num=i,
))
return refs
def find_numeric_values(text: str, remove_hyperlinks: bool = True) -> list[str]:
"""Find all unreferenced numeric values in text."""
text = ' ' + text + ' '
if remove_hyperlinks:
text = re.sub(get_hyperlink_pattern(is_target=False), '', text)
text = re.sub(get_hyperlink_pattern(is_target=True), '', text)
return re.findall(NUMERIC_PATTERN, text)
def replace_hyperlinks_with_values(text: str, is_targets: bool = False) -> str:
"""Replace all hypertarget/hyperlink commands with just their values."""
def replace_match(match):
return match.group('value')
pattern = get_hyperlink_pattern(is_targets)
return re.sub(pattern, replace_match, text)
def scan_file(tex_content: str) -> dict:
"""Scan a .tex file and report all hypertarget/hyperlink usage."""
targets = find_references(tex_content, is_targets=True)
links = find_references(tex_content, is_targets=False)
unreferenced = find_numeric_values(tex_content)
return {
"hypertargets": [asdict(t) for t in targets],
"hyperlinks": [asdict(l) for l in links],
"target_count": len(targets),
"link_count": len(links),
"target_labels": sorted(set(t.label for t in targets if t.label)),
"link_labels": sorted(set(l.label for l in links if l.label)),
"unreferenced_numeric_values": unreferenced[:50],
"unreferenced_count": len(unreferenced),
}
def verify_integrity(tex_content: str, code_output: str = "") -> dict:
"""Verify cross-reference integrity between targets and links."""
targets = find_references(tex_content, is_targets=True)
links = find_references(tex_content, is_targets=False)
target_labels = {t.label for t in targets if t.label}
link_labels = {l.label for l in links if l.label}
# Find mismatches
unresolved_links = link_labels - target_labels
unused_targets = target_labels - link_labels
# Check value consistency (same label should have same value)
target_values = {}
for t in targets:
if t.label:
target_values[t.label] = t.value
link_values = {}
for l in links:
if l.label:
link_values[l.label] = l.value
value_mismatches = []
for label in target_labels & link_labels:
tv = target_values.get(label, "")
lv = link_values.get(label, "")
if tv and lv and tv != lv:
value_mismatches.append({
"label": label,
"target_value": tv,
"link_value": lv,
})
# Check against code output if provided
code_values = {}
if code_output:
for line in code_output.splitlines():
m = re.search(get_hyperlink_pattern(is_target=True), line)
if m:
code_values[m.group('reference')] = m.group('value')
code_mismatches = []
if code_values:
for label, code_val in code_values.items():
tex_val = target_values.get(label)
if tex_val and tex_val != code_val:
code_mismatches.append({
"label": label,
"code_value": code_val,
"tex_value": tex_val,
})
result = {
"total_targets": len(targets),
"total_links": len(links),
"unresolved_links": sorted(unresolved_links),
"unused_targets": sorted(unused_targets),
"value_mismatches": value_mismatches,
"code_mismatches": code_mismatches,
"integrity_ok": (
len(unresolved_links) == 0
and len(value_mismatches) == 0
and len(code_mismatches) == 0
),
}
return result
def main():
parser = argparse.ArgumentParser(
description="Scan and verify hypertarget/hyperlink references in LaTeX"
)
parser.add_argument("tex_file", help="LaTeX file to analyze")
parser.add_argument("--scan", action="store_true",
help="Scan mode: report all hypertarget/hyperlink usage")
parser.add_argument("--verify", action="store_true",
help="Verify mode: check cross-reference integrity")
parser.add_argument("--code-output", help="Code output file for cross-referencing")
parser.add_argument("--output", "-o", help="Output JSON file (default: stdout)")
args = parser.parse_args()
if not args.scan and not args.verify:
args.scan = True # Default to scan mode
if not os.path.exists(args.tex_file):
print(f"Error: {args.tex_file} not found", file=sys.stderr)
sys.exit(1)
with open(args.tex_file, encoding="utf-8", errors="replace") as f:
tex_content = f.read()
code_output = ""
if args.code_output and os.path.exists(args.code_output):
with open(args.code_output, encoding="utf-8", errors="replace") as f:
code_output = f.read()
if args.scan:
result = scan_file(tex_content)
print(f"Targets: {result['target_count']}, Links: {result['link_count']}, "
f"Unreferenced numbers: {result['unreferenced_count']}", file=sys.stderr)
else:
result = verify_integrity(tex_content, code_output)
status = "OK" if result["integrity_ok"] else "ISSUES FOUND"
print(f"Integrity: {status}", file=sys.stderr)
if result["unresolved_links"]:
print(f" Unresolved links: {result['unresolved_links']}", file=sys.stderr)
if result["value_mismatches"]:
print(f" Value mismatches: {len(result['value_mismatches'])}", file=sys.stderr)
if result["code_mismatches"]:
print(f" Code mismatches: {len(result['code_mismatches'])}", file=sys.stderr)
output = json.dumps(result, indent=2, ensure_ascii=False)
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(output)
print(f"Report written to {args.output}", file=sys.stderr)
else:
print(output)
if args.verify and not result["integrity_ok"]:
sys.exit(1)
if __name__ == "__main__":
main()