adduce 0.1.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 (91) hide show
  1. adduce/__init__.py +3 -0
  2. adduce/cache.py +44 -0
  3. adduce/checklists/__init__.py +149 -0
  4. adduce/checklists/acl.yaml +44 -0
  5. adduce/checklists/neurips.yaml +44 -0
  6. adduce/cli.py +715 -0
  7. adduce/config.py +54 -0
  8. adduce/dynamic/__init__.py +7 -0
  9. adduce/dynamic/import_hook.py +111 -0
  10. adduce/dynamic/reproduce.py +169 -0
  11. adduce/dynamic/resolve.py +98 -0
  12. adduce/engine.py +134 -0
  13. adduce/evidence/__init__.py +117 -0
  14. adduce/evidence/config.py +141 -0
  15. adduce/evidence/data.py +127 -0
  16. adduce/evidence/dependencies.py +227 -0
  17. adduce/evidence/docs.py +116 -0
  18. adduce/evidence/environment.py +75 -0
  19. adduce/evidence/git.py +32 -0
  20. adduce/evidence/latex.py +280 -0
  21. adduce/evidence/notebook.py +127 -0
  22. adduce/evidence/portability.py +73 -0
  23. adduce/evidence/precision.py +118 -0
  24. adduce/evidence/python_ast.py +611 -0
  25. adduce/evidence/remote.py +179 -0
  26. adduce/evidence/results.py +146 -0
  27. adduce/evidence/run_history.py +179 -0
  28. adduce/fixers/__init__.py +157 -0
  29. adduce/fixers/codemods/__init__.py +1 -0
  30. adduce/fixers/codemods/pin_revision.py +75 -0
  31. adduce/fixers/templates/CITATION.cff.j2 +13 -0
  32. adduce/fixers/templates/Dockerfile.j2 +19 -0
  33. adduce/fixers/templates/readme_sections.md.j2 +37 -0
  34. adduce/fixers/templates/reproduce.sh.j2 +17 -0
  35. adduce/fixers/templates/seed_utils.py.j2 +59 -0
  36. adduce/graph.py +164 -0
  37. adduce/llm/__init__.py +119 -0
  38. adduce/manifest.py +256 -0
  39. adduce/manifest_builder.py +132 -0
  40. adduce/model.py +233 -0
  41. adduce/modes.py +111 -0
  42. adduce/naming/__init__.py +139 -0
  43. adduce/profiles/__init__.py +82 -0
  44. adduce/profiles/acl.toml +21 -0
  45. adduce/profiles/acm.toml +21 -0
  46. adduce/profiles/default.toml +21 -0
  47. adduce/profiles/iclr.toml +21 -0
  48. adduce/profiles/neurips.toml +21 -0
  49. adduce/profiles/strict.toml +21 -0
  50. adduce/report/__init__.py +34 -0
  51. adduce/report/appendix.py +101 -0
  52. adduce/report/badge.py +70 -0
  53. adduce/report/checksums.py +38 -0
  54. adduce/report/codemeta.py +29 -0
  55. adduce/report/croissant.py +55 -0
  56. adduce/report/json_report.py +48 -0
  57. adduce/report/latex.py +58 -0
  58. adduce/report/markdown.py +61 -0
  59. adduce/report/ro_crate.py +69 -0
  60. adduce/report/sarif.py +91 -0
  61. adduce/report/software_heritage.py +36 -0
  62. adduce/report/terminal.py +212 -0
  63. adduce/report/zenodo.py +24 -0
  64. adduce/reviewer_time.py +90 -0
  65. adduce/rules/__init__.py +4 -0
  66. adduce/rules/archival.py +97 -0
  67. adduce/rules/base.py +135 -0
  68. adduce/rules/builtin.py +5 -0
  69. adduce/rules/checkpoint.py +130 -0
  70. adduce/rules/data.py +245 -0
  71. adduce/rules/deps.py +262 -0
  72. adduce/rules/determinism.py +321 -0
  73. adduce/rules/docs.py +128 -0
  74. adduce/rules/drift.py +334 -0
  75. adduce/rules/env.py +198 -0
  76. adduce/rules/exec_.py +140 -0
  77. adduce/rules/licensing.py +91 -0
  78. adduce/rules/notebook.py +220 -0
  79. adduce/rules/portability.py +108 -0
  80. adduce/rules/precision.py +180 -0
  81. adduce/rules/reconcile.py +197 -0
  82. adduce/rules/registry.py +218 -0
  83. adduce/rules/remote.py +165 -0
  84. adduce/rules/run.py +150 -0
  85. adduce/rules/versioning.py +77 -0
  86. adduce/scoring.py +136 -0
  87. adduce-0.1.0.dist-info/METADATA +301 -0
  88. adduce-0.1.0.dist-info/RECORD +91 -0
  89. adduce-0.1.0.dist-info/WHEEL +4 -0
  90. adduce-0.1.0.dist-info/entry_points.txt +5 -0
  91. adduce-0.1.0.dist-info/licenses/LICENSE +21 -0
adduce/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """adduce: a reproducibility self-check for ML and research repositories."""
2
+
3
+ __version__ = "0.1.0"
adduce/cache.py ADDED
@@ -0,0 +1,44 @@
1
+ """Local cache under ``.adduce/cache`` for opt-in online resolutions.
2
+
3
+ Nothing in the default offline path reads or writes this cache. Online
4
+ commands (``pin-remotes``, ``--online``) store resolved public metadata
5
+ (Hugging Face revisions, GitHub SHAs, URL headers) here so repeated runs do
6
+ not re-query, and so the resolved values are inspectable.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import time
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ CACHE_DIR = ".adduce/cache"
17
+ _DEFAULT_TTL_SECONDS = 7 * 24 * 3600
18
+
19
+
20
+ class Cache:
21
+ def __init__(self, root: Path, ttl_seconds: int = _DEFAULT_TTL_SECONDS) -> None:
22
+ self.directory = root / CACHE_DIR
23
+ self.ttl_seconds = ttl_seconds
24
+
25
+ def _path_for(self, key: str) -> Path:
26
+ safe = "".join(c if c.isalnum() or c in "-._" else "_" for c in key)
27
+ return self.directory / f"{safe}.json"
28
+
29
+ def get(self, key: str) -> Any | None:
30
+ target = self._path_for(key)
31
+ if not target.is_file():
32
+ return None
33
+ try:
34
+ entry = json.loads(target.read_text(encoding="utf-8"))
35
+ except (json.JSONDecodeError, OSError):
36
+ return None
37
+ if time.time() - entry.get("stored_at", 0) > self.ttl_seconds:
38
+ return None
39
+ return entry.get("value")
40
+
41
+ def put(self, key: str, value: Any) -> None:
42
+ self.directory.mkdir(parents=True, exist_ok=True)
43
+ entry = {"stored_at": time.time(), "value": value}
44
+ self._path_for(key).write_text(json.dumps(entry, indent=2), encoding="utf-8")
@@ -0,0 +1,149 @@
1
+ """Conference checklist drafting from repository evidence.
2
+
3
+ Each bundled checklist maps its items to rule IDs. Items are answered from
4
+ finding statuses (all pass → Yes, mixed → Partial, all fail → No); items the
5
+ repository cannot answer are marked for the authors explicitly. The output
6
+ is a draft: honest wording about that is part of the design, not a
7
+ disclaimer bolted on.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass, field
13
+ from importlib import resources
14
+ from pathlib import Path
15
+
16
+ import yaml
17
+
18
+ from ..engine import CheckResult
19
+ from ..rules.base import Finding, Status
20
+
21
+
22
+ @dataclass
23
+ class ChecklistItem:
24
+ id: str
25
+ question: str
26
+ rules: list[str] = field(default_factory=list)
27
+ manual: bool = False
28
+ guidance: str = ""
29
+
30
+
31
+ @dataclass
32
+ class Checklist:
33
+ name: str
34
+ key: str
35
+ preamble: str
36
+ items: list[ChecklistItem]
37
+
38
+
39
+ def available_checklists() -> list[str]:
40
+ return sorted(
41
+ entry.name[: -len(".yaml")]
42
+ for entry in resources.files(__package__).iterdir()
43
+ if entry.name.endswith(".yaml")
44
+ )
45
+
46
+
47
+ def load_checklist(name_or_path: str) -> Checklist:
48
+ path = Path(name_or_path)
49
+ if path.suffix in {".yaml", ".yml"} and path.is_file():
50
+ text = path.read_text(encoding="utf-8")
51
+ else:
52
+ resource = resources.files(__package__).joinpath(f"{name_or_path}.yaml")
53
+ if not resource.is_file():
54
+ raise ValueError(
55
+ f"Unknown checklist '{name_or_path}'. Bundled: {', '.join(available_checklists())}."
56
+ )
57
+ text = resource.read_text(encoding="utf-8")
58
+ data = yaml.safe_load(text)
59
+ items = [
60
+ ChecklistItem(
61
+ id=item["id"],
62
+ question=item["question"],
63
+ rules=list(item.get("rules", [])),
64
+ manual=bool(item.get("manual", False)),
65
+ guidance=str(item.get("guidance", "")).strip(),
66
+ )
67
+ for item in data.get("items", [])
68
+ ]
69
+ return Checklist(
70
+ name=data.get("name", name_or_path),
71
+ key=data.get("key", name_or_path),
72
+ preamble=str(data.get("preamble", "")).strip(),
73
+ items=items,
74
+ )
75
+
76
+
77
+ def _draft_answer(findings: list[Finding]) -> tuple[str, list[str]]:
78
+ """Derive a draft answer and its supporting evidence lines."""
79
+ scored = [f for f in findings if f.status.score_value is not None]
80
+ evidence = []
81
+ for finding in findings:
82
+ marker = {Status.PASS: "found", Status.PARTIAL: "partial", Status.FAIL: "missing"}.get(
83
+ finding.status, "n/a"
84
+ )
85
+ evidence.append(f"[{marker}] {finding.rule_id}: {finding.message}")
86
+ if not scored:
87
+ return "No repository evidence", evidence
88
+ values = [f.status.score_value for f in scored]
89
+ if all(v == 1.0 for v in values):
90
+ return "Yes (draft)", evidence
91
+ if all(v == 0.0 for v in values):
92
+ return "No (draft)", evidence
93
+ return "Partial (draft)", evidence
94
+
95
+
96
+ def render_markdown(
97
+ checklist: Checklist,
98
+ result: CheckResult,
99
+ llm_drafts: dict[str, str] | None = None,
100
+ ) -> str:
101
+ """Render the filled checklist. ``llm_drafts`` optionally carries
102
+ LLM-phrased justification prose keyed by item id; the yes/no evidence
103
+ answers stay deterministic regardless."""
104
+ llm_drafts = llm_drafts or {}
105
+ findings_by_rule = {f.rule_id: f for f in result.card.findings}
106
+ lines: list[str] = []
107
+ lines.append(f"# {checklist.name}")
108
+ lines.append("")
109
+ lines.append(f"Repository: `{result.repo.root.name}`"
110
+ + (f" at commit `{(result.repo.git.head_commit or '')[:7]}`" if result.repo.git.head_commit else ""))
111
+ lines.append("")
112
+ if checklist.preamble:
113
+ lines.append(f"> {checklist.preamble}")
114
+ lines.append("")
115
+
116
+ for index, item in enumerate(checklist.items, start=1):
117
+ lines.append(f"## {index}. {item.question}")
118
+ lines.append("")
119
+ item_findings = [findings_by_rule[r] for r in item.rules if r in findings_by_rule]
120
+ if item.manual:
121
+ lines.append("**Answer:** _requires author input — not derivable from the repository._")
122
+ else:
123
+ answer, _ = _draft_answer(item_findings)
124
+ lines.append(f"**Answer:** {answer}")
125
+ lines.append("")
126
+ if item.id in llm_drafts:
127
+ lines.append("**Draft justification** (LLM-phrased from the evidence below — verify):")
128
+ lines.append("")
129
+ lines.append(llm_drafts[item.id])
130
+ lines.append("")
131
+ if item_findings:
132
+ lines.append("**Repository evidence:**")
133
+ lines.append("")
134
+ _, evidence = _draft_answer(item_findings)
135
+ for line in evidence:
136
+ lines.append(f"- {line}")
137
+ lines.append("")
138
+ if item.guidance:
139
+ lines.append(f"_{item.guidance}_")
140
+ lines.append("")
141
+
142
+ lines.append("---")
143
+ lines.append("")
144
+ lines.append(
145
+ "Drafted from static repository evidence. Verify each answer against the "
146
+ "paper before submission; answers about the paper text cannot be derived here."
147
+ )
148
+ lines.append("")
149
+ return "\n".join(lines)
@@ -0,0 +1,44 @@
1
+ name: "ACL Rolling Review Responsible Research Checklist (artifact items)"
2
+ key: acl
3
+ preamble: >
4
+ Draft answers derived from repository evidence. Complete and verify every
5
+ answer before submission; several items depend on the paper text and on
6
+ assets the tool cannot inspect.
7
+ items:
8
+ - id: artifacts-released
9
+ question: "Did you release the code and models used in your work, with sufficient documentation to use them?"
10
+ rules: [R-EXEC-002, R-DOC-001, R-LIC-001, R-VER-001]
11
+ guidance: >
12
+ Reviewers expect a repository with install and usage documentation and
13
+ an explicit license.
14
+ - id: artifacts-cited
15
+ question: "Did you cite the creators of the artifacts (datasets, models, code) you used?"
16
+ rules: [R-LIC-002, R-LIC-003]
17
+ manual: true
18
+ guidance: >
19
+ Citation of third-party artifacts lives in the paper; the repository
20
+ side is a citation section, CITATION.cff, and stated asset licenses.
21
+ - id: reproducibility-parameters
22
+ question: "Did you report the number of parameters, total computational budget, and computing infrastructure used?"
23
+ rules: [R-DOC-001, R-PREC-005, R-RUN-003]
24
+ guidance: >
25
+ Add a hardware-and-runtime note to the README and mirror it in the
26
+ paper; SLURM scripts' resource requests should match.
27
+ - id: experimental-setup
28
+ question: "Did you discuss the experimental setup, including hyperparameter search and best-found hyperparameter values?"
29
+ rules: [R-DOC-002, R-DRIFT-001, R-DRIFT-002]
30
+ guidance: >
31
+ Committed config files per reported experiment are the strongest
32
+ supporting evidence; drift findings mean paper and configs disagree.
33
+ - id: descriptive-statistics
34
+ question: "Did you report descriptive statistics about your results (e.g., error bars, summary statistics over multiple runs)?"
35
+ rules: [R-RES-003]
36
+ guidance: >
37
+ Multi-seed run scripts and variance reporting are the repository-side
38
+ evidence.
39
+ - id: data-documentation
40
+ question: "Did you document the data you used or created, including how it can be obtained?"
41
+ rules: [R-DATA-001, R-DATA-002, R-DATA-003, R-DATA-005]
42
+ guidance: >
43
+ A download script or dataset DOI plus checksums makes data access
44
+ mechanical and verifiable.
@@ -0,0 +1,44 @@
1
+ name: "NeurIPS Paper Checklist (reproducibility items)"
2
+ key: neurips
3
+ preamble: >
4
+ Draft answers derived from repository evidence. Every answer must be
5
+ reviewed and completed by the authors before submission: the tool sees the
6
+ repository, not the paper, and several checklist items depend on claims
7
+ made in the paper text.
8
+ items:
9
+ - id: experimental-reproducibility
10
+ question: "Does the paper fully disclose all the information needed to reproduce the main experimental results?"
11
+ rules: [R-DOC-001, R-DOC-002, R-DOC-003, R-DET-001, R-EXEC-003]
12
+ guidance: >
13
+ Reviewers look for the experimental setup, hyperparameters, seeds, and
14
+ the mapping from commands to reported numbers.
15
+ - id: open-access-code-data
16
+ question: "Does the paper provide open access to the data and code, with sufficient instructions to faithfully reproduce the main experimental results?"
17
+ rules: [R-EXEC-002, R-ENV-001, R-DATA-001, R-DATA-002, R-DOC-001]
18
+ guidance: >
19
+ An anonymised repository URL with exact run commands and environment
20
+ specification is the expected supporting material.
21
+ - id: experimental-details
22
+ question: "Does the paper specify all the training and test details (data splits, hyperparameters, how they were chosen, optimizer)?"
23
+ rules: [R-DOC-002, R-DRIFT-001, R-DRIFT-003, R-DATA-001]
24
+ guidance: >
25
+ Point to the committed config files for each reported experiment; drift
26
+ findings indicate paper and configs disagree.
27
+ - id: error-bars
28
+ question: "Does the paper report error bars or other information about the statistical significance of the experiments?"
29
+ rules: [R-RES-003]
30
+ guidance: >
31
+ Multi-seed run scripts and variance reporting are the repository-side
32
+ evidence; the statistical claims themselves live in the paper.
33
+ - id: compute-resources
34
+ question: "Does the paper provide sufficient information on the computer resources (type of compute workers, memory, time of execution) needed to reproduce the experiments?"
35
+ rules: [R-DOC-001, R-PREC-005, R-RUN-003, R-DRIFT-005]
36
+ guidance: >
37
+ State GPU/CPU model, count, memory, and approximate wall-clock time per
38
+ experiment; precision settings (TF32/AMP) belong here too.
39
+ - id: code-of-ethics-license
40
+ question: "Are the creators or original owners of assets (code, data, models) properly credited, and are the license and terms of use explicitly mentioned and respected?"
41
+ rules: [R-LIC-001, R-LIC-002, R-LIC-003]
42
+ guidance: >
43
+ License the released code, provide citation metadata, and state the
44
+ licenses of datasets and pretrained assets you build on.