claude-turing 3.4.0 → 3.5.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 (34) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/README.md +9 -2
  3. package/commands/annotate.md +23 -0
  4. package/commands/archive.md +23 -0
  5. package/commands/cite.md +23 -0
  6. package/commands/flashback.md +22 -0
  7. package/commands/present.md +23 -0
  8. package/commands/replay.md +23 -0
  9. package/commands/search.md +22 -0
  10. package/commands/template.md +22 -0
  11. package/commands/trend.md +21 -0
  12. package/commands/turing.md +14 -0
  13. package/package.json +1 -1
  14. package/src/install.js +1 -0
  15. package/src/verify.js +7 -0
  16. package/templates/scripts/__pycache__/experiment_annotations.cpython-314.pyc +0 -0
  17. package/templates/scripts/__pycache__/experiment_archive.cpython-314.pyc +0 -0
  18. package/templates/scripts/__pycache__/experiment_replay.cpython-314.pyc +0 -0
  19. package/templates/scripts/__pycache__/experiment_search.cpython-314.pyc +0 -0
  20. package/templates/scripts/__pycache__/experiment_templates.cpython-314.pyc +0 -0
  21. package/templates/scripts/__pycache__/scaffold.cpython-314.pyc +0 -0
  22. package/templates/scripts/__pycache__/session_flashback.cpython-314.pyc +0 -0
  23. package/templates/scripts/__pycache__/trend_analysis.cpython-314.pyc +0 -0
  24. package/templates/scripts/citation_manager.py +436 -0
  25. package/templates/scripts/experiment_annotations.py +392 -0
  26. package/templates/scripts/experiment_archive.py +534 -0
  27. package/templates/scripts/experiment_replay.py +592 -0
  28. package/templates/scripts/experiment_search.py +451 -0
  29. package/templates/scripts/experiment_templates.py +501 -0
  30. package/templates/scripts/generate_changelog.py +464 -0
  31. package/templates/scripts/generate_figures.py +597 -0
  32. package/templates/scripts/scaffold.py +12 -0
  33. package/templates/scripts/session_flashback.py +461 -0
  34. package/templates/scripts/trend_analysis.py +503 -0
@@ -0,0 +1,436 @@
1
+ #!/usr/bin/env python3
2
+ """Citation and attribution manager for the autoresearch pipeline.
3
+
4
+ Tracks academic citations associated with experiments. Every method,
5
+ dataset, technique, and codebase used in the research campaign should
6
+ have a citation. This script manages the citation store, audits for
7
+ missing attributions, and generates BibTeX output.
8
+
9
+ Usage:
10
+ python scripts/citation_manager.py add exp-042 --key Chen2016 --title "XGBoost" --url "..."
11
+ python scripts/citation_manager.py list
12
+ python scripts/citation_manager.py check
13
+ python scripts/citation_manager.py bib
14
+ python scripts/citation_manager.py --json
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ import json
21
+ import re
22
+ import sys
23
+ from datetime import datetime, timezone
24
+ from pathlib import Path
25
+
26
+ import yaml
27
+
28
+ from scripts.turing_io import load_config, load_experiments
29
+
30
+ DEFAULT_LOG_PATH = "experiments/log.jsonl"
31
+ DEFAULT_CITATIONS_PATH = "experiments/citations.yaml"
32
+ VALID_TYPES = ["method", "dataset", "technique", "codebase"]
33
+
34
+ # Keywords that suggest a method/technique needing citation
35
+ METHOD_KEYWORDS = {
36
+ "xgboost": "XGBoost (Chen & Guestrin, 2016)",
37
+ "lightgbm": "LightGBM (Ke et al., 2017)",
38
+ "catboost": "CatBoost (Prokhorenkova et al., 2018)",
39
+ "random_forest": "Random Forest (Breiman, 2001)",
40
+ "gradient_boosting": "Gradient Boosting (Friedman, 2001)",
41
+ "adam": "Adam optimizer (Kingma & Ba, 2015)",
42
+ "sgd": "SGD with momentum (Sutskever et al., 2013)",
43
+ "dropout": "Dropout (Srivastava et al., 2014)",
44
+ "batch_norm": "Batch Normalization (Ioffe & Szegedy, 2015)",
45
+ "resnet": "ResNet (He et al., 2016)",
46
+ "transformer": "Transformer (Vaswani et al., 2017)",
47
+ "bert": "BERT (Devlin et al., 2019)",
48
+ "lstm": "LSTM (Hochreiter & Schmidhuber, 1997)",
49
+ "svm": "SVM (Cortes & Vapnik, 1995)",
50
+ "lasso": "Lasso (Tibshirani, 1996)",
51
+ "ridge": "Ridge Regression (Hoerl & Kennard, 1970)",
52
+ "elastic_net": "Elastic Net (Zou & Hastie, 2005)",
53
+ "pca": "PCA (Pearson, 1901)",
54
+ "tsne": "t-SNE (van der Maaten & Hinton, 2008)",
55
+ "umap": "UMAP (McInnes et al., 2018)",
56
+ "cross_validation": "Cross-validation (Stone, 1974)",
57
+ "smote": "SMOTE (Chawla et al., 2002)",
58
+ }
59
+
60
+
61
+ # --- Storage ---
62
+
63
+
64
+ def load_citations(path: str = DEFAULT_CITATIONS_PATH) -> list[dict]:
65
+ """Load citations from YAML file."""
66
+ p = Path(path)
67
+ if not p.exists() or p.stat().st_size == 0:
68
+ return []
69
+ with open(p) as f:
70
+ data = yaml.safe_load(f)
71
+ return data if isinstance(data, list) else []
72
+
73
+
74
+ def save_citations(citations: list[dict], path: str = DEFAULT_CITATIONS_PATH) -> Path:
75
+ """Save citations list to YAML."""
76
+ p = Path(path)
77
+ p.parent.mkdir(parents=True, exist_ok=True)
78
+ with open(p, "w") as f:
79
+ yaml.dump(citations, f, default_flow_style=False, sort_keys=False)
80
+ return p
81
+
82
+
83
+ # --- Operations ---
84
+
85
+
86
+ def add_citation(
87
+ experiment_id: str,
88
+ key: str,
89
+ title: str,
90
+ authors: str | None = None,
91
+ year: int | None = None,
92
+ url: str | None = None,
93
+ doi: str | None = None,
94
+ cite_type: str = "method",
95
+ citations_path: str = DEFAULT_CITATIONS_PATH,
96
+ log_path: str = DEFAULT_LOG_PATH,
97
+ ) -> dict:
98
+ """Add or update a citation, associating it with an experiment.
99
+
100
+ If the citation key already exists, the experiment is appended to
101
+ its experiment list. Otherwise a new citation entry is created.
102
+ """
103
+ experiments = load_experiments(log_path)
104
+ known_ids = {e.get("experiment_id") for e in experiments}
105
+ if experiment_id not in known_ids:
106
+ return {"error": f"Experiment '{experiment_id}' not found in log"}
107
+
108
+ if cite_type not in VALID_TYPES:
109
+ return {"error": f"Invalid type '{cite_type}'. Valid: {VALID_TYPES}"}
110
+
111
+ citations = load_citations(citations_path)
112
+
113
+ # Check if key already exists
114
+ existing = None
115
+ for c in citations:
116
+ if c.get("key") == key:
117
+ existing = c
118
+ break
119
+
120
+ if existing:
121
+ if experiment_id not in existing.get("experiments", []):
122
+ existing.setdefault("experiments", []).append(experiment_id)
123
+ # Update fields if provided
124
+ if authors:
125
+ existing["authors"] = authors
126
+ if year:
127
+ existing["year"] = year
128
+ if url:
129
+ existing["url"] = url
130
+ if doi:
131
+ existing["doi"] = doi
132
+ save_citations(citations, citations_path)
133
+ return {"action": "updated", "citation": existing}
134
+
135
+ citation = {
136
+ "key": key,
137
+ "title": title,
138
+ "authors": authors or "",
139
+ "year": year or 0,
140
+ "url": url or "",
141
+ "doi": doi or "",
142
+ "type": cite_type,
143
+ "experiments": [experiment_id],
144
+ }
145
+ citations.append(citation)
146
+ save_citations(citations, citations_path)
147
+ return {"action": "added", "citation": citation}
148
+
149
+
150
+ def list_citations(
151
+ citations_path: str = DEFAULT_CITATIONS_PATH,
152
+ ) -> dict:
153
+ """List all citations grouped by type with experiment associations."""
154
+ citations = load_citations(citations_path)
155
+ grouped: dict[str, list[dict]] = {}
156
+ for c in citations:
157
+ ctype = c.get("type", "unknown")
158
+ grouped.setdefault(ctype, []).append(c)
159
+ return {
160
+ "total": len(citations),
161
+ "by_type": grouped,
162
+ "citations": citations,
163
+ }
164
+
165
+
166
+ def check_citations(
167
+ citations_path: str = DEFAULT_CITATIONS_PATH,
168
+ log_path: str = DEFAULT_LOG_PATH,
169
+ config_path: str = "config.yaml",
170
+ ) -> dict:
171
+ """Audit for missing citations — methods used without attribution.
172
+
173
+ Scans experiment configs and descriptions for known method keywords
174
+ that lack a corresponding citation entry.
175
+ """
176
+ citations = load_citations(citations_path)
177
+ experiments = load_experiments(log_path)
178
+ config = load_config(config_path)
179
+
180
+ cited_keys = {c.get("key", "").lower() for c in citations}
181
+ cited_titles = {c.get("title", "").lower() for c in citations}
182
+
183
+ missing: list[dict] = []
184
+ covered: list[str] = []
185
+
186
+ for keyword, suggestion in METHOD_KEYWORDS.items():
187
+ # Check if this keyword appears in any experiment
188
+ found_in: list[str] = []
189
+ for exp in experiments:
190
+ searchable = json.dumps(exp, default=str).lower()
191
+ if keyword.lower() in searchable:
192
+ found_in.append(exp.get("experiment_id", "?"))
193
+
194
+ # Also check config
195
+ config_str = json.dumps(config, default=str).lower()
196
+ if keyword.lower() in config_str:
197
+ found_in.append("config.yaml")
198
+
199
+ if not found_in:
200
+ continue
201
+
202
+ # Check if cited
203
+ is_cited = (
204
+ keyword.lower() in cited_keys
205
+ or keyword.lower() in cited_titles
206
+ or any(keyword.lower() in c.get("title", "").lower() for c in citations)
207
+ )
208
+
209
+ if is_cited:
210
+ covered.append(keyword)
211
+ else:
212
+ missing.append({
213
+ "keyword": keyword,
214
+ "suggestion": suggestion,
215
+ "found_in": found_in,
216
+ })
217
+
218
+ return {
219
+ "missing": missing,
220
+ "covered": covered,
221
+ "total_checked": len(METHOD_KEYWORDS),
222
+ "coverage": f"{len(covered)}/{len(covered) + len(missing)}" if (covered or missing) else "N/A",
223
+ }
224
+
225
+
226
+ def generate_bibtex(citations_path: str = DEFAULT_CITATIONS_PATH) -> str:
227
+ """Generate BibTeX output from all citations."""
228
+ citations = load_citations(citations_path)
229
+ if not citations:
230
+ return "% No citations found.\n"
231
+
232
+ entries = []
233
+ for c in citations:
234
+ key = c.get("key", "unknown")
235
+ title = c.get("title", "")
236
+ authors = c.get("authors", "")
237
+ year = c.get("year", 0)
238
+ url = c.get("url", "")
239
+ doi = c.get("doi", "")
240
+
241
+ # Determine entry type
242
+ entry_type = "misc"
243
+ if doi:
244
+ entry_type = "article"
245
+
246
+ lines = [f"@{entry_type}{{{key},"]
247
+ lines.append(f" title = {{{title}}},")
248
+ if authors:
249
+ lines.append(f" author = {{{authors}}},")
250
+ if year:
251
+ lines.append(f" year = {{{year}}},")
252
+ if url:
253
+ lines.append(f" url = {{{url}}},")
254
+ if doi:
255
+ lines.append(f" doi = {{{doi}}},")
256
+ note = f"Type: {c.get('type', 'unknown')}. Used in: {', '.join(c.get('experiments', []))}"
257
+ lines.append(f" note = {{{note}}},")
258
+ lines.append("}")
259
+ entries.append("\n".join(lines))
260
+
261
+ header = f"% Auto-generated by Turing citation manager\n% {len(entries)} citation(s)\n"
262
+ return header + "\n\n".join(entries) + "\n"
263
+
264
+
265
+ # --- Report ---
266
+
267
+
268
+ def format_citations_report(result: dict, action: str) -> str:
269
+ """Format citation results as readable text."""
270
+ lines: list[str] = []
271
+
272
+ if action == "list":
273
+ total = result.get("total", 0)
274
+ lines.append(f"# Citations ({total} total)")
275
+ lines.append("")
276
+ by_type = result.get("by_type", {})
277
+ for ctype in VALID_TYPES:
278
+ cites = by_type.get(ctype, [])
279
+ if not cites:
280
+ continue
281
+ lines.append(f"## {ctype.title()} ({len(cites)})")
282
+ lines.append("")
283
+ for c in cites:
284
+ key = c.get("key", "?")
285
+ title = c.get("title", "?")
286
+ authors = c.get("authors", "")
287
+ year = c.get("year", "")
288
+ exps = ", ".join(c.get("experiments", []))
289
+ author_year = f" ({authors}, {year})" if authors and year else ""
290
+ lines.append(f"- **[{key}]** {title}{author_year}")
291
+ if exps:
292
+ lines.append(f" Experiments: {exps}")
293
+ lines.append("")
294
+
295
+ elif action == "check":
296
+ missing = result.get("missing", [])
297
+ covered = result.get("covered", [])
298
+ coverage = result.get("coverage", "N/A")
299
+ lines.append(f"# Citation Audit (coverage: {coverage})")
300
+ lines.append("")
301
+ if missing:
302
+ lines.append(f"## Missing Citations ({len(missing)})")
303
+ lines.append("")
304
+ for m in missing:
305
+ lines.append(f"- **{m['keyword']}**: {m['suggestion']}")
306
+ lines.append(f" Found in: {', '.join(m['found_in'])}")
307
+ lines.append("")
308
+ if covered:
309
+ lines.append(f"## Covered ({len(covered)})")
310
+ lines.append("")
311
+ for kw in covered:
312
+ lines.append(f"- {kw}")
313
+ lines.append("")
314
+ if not missing:
315
+ lines.append("All detected methods have citations.")
316
+
317
+ elif action == "add":
318
+ cite = result.get("citation", {})
319
+ act = result.get("action", "added")
320
+ lines.append(f"Citation {act}: [{cite.get('key')}] {cite.get('title')}")
321
+ lines.append(f" Type: {cite.get('type')} | Experiments: {', '.join(cite.get('experiments', []))}")
322
+
323
+ return "\n".join(lines)
324
+
325
+
326
+ def save_citations_report(report: dict, path: str = "experiments/citations") -> Path:
327
+ """Save citation report to YAML."""
328
+ p = Path(path)
329
+ p.mkdir(parents=True, exist_ok=True)
330
+ out = p / f"report-{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}.yaml"
331
+ with open(out, "w") as f:
332
+ yaml.dump(report, f, default_flow_style=False, sort_keys=False)
333
+ return out
334
+
335
+
336
+ # --- Orchestration ---
337
+
338
+
339
+ def run_citation_manager(
340
+ action: str,
341
+ experiment_id: str | None = None,
342
+ key: str | None = None,
343
+ title: str | None = None,
344
+ authors: str | None = None,
345
+ year: int | None = None,
346
+ url: str | None = None,
347
+ doi: str | None = None,
348
+ cite_type: str = "method",
349
+ citations_path: str = DEFAULT_CITATIONS_PATH,
350
+ log_path: str = DEFAULT_LOG_PATH,
351
+ config_path: str = "config.yaml",
352
+ ) -> dict:
353
+ """Run citation manager operation."""
354
+ timestamp = datetime.now(timezone.utc).isoformat()
355
+
356
+ if action == "add":
357
+ if not experiment_id or not key or not title:
358
+ return {"error": "add requires experiment_id, --key, and --title"}
359
+ result = add_citation(
360
+ experiment_id, key, title, authors, year, url, doi,
361
+ cite_type, citations_path, log_path,
362
+ )
363
+ if "error" in result:
364
+ return {"timestamp": timestamp, **result}
365
+ return {"timestamp": timestamp, "action": "add", **result}
366
+
367
+ elif action == "list":
368
+ result = list_citations(citations_path)
369
+ return {"timestamp": timestamp, "action": "list", **result}
370
+
371
+ elif action == "check":
372
+ result = check_citations(citations_path, log_path, config_path)
373
+ return {"timestamp": timestamp, "action": "check", **result}
374
+
375
+ elif action == "bib":
376
+ bibtex = generate_bibtex(citations_path)
377
+ return {"timestamp": timestamp, "action": "bib", "bibtex": bibtex,
378
+ "count": len(load_citations(citations_path))}
379
+
380
+ return {"error": f"Unknown action: {action}"}
381
+
382
+
383
+ def main() -> None:
384
+ """CLI entry point."""
385
+ parser = argparse.ArgumentParser(
386
+ description="Citation and attribution manager for ML experiments",
387
+ )
388
+ parser.add_argument("action", choices=["add", "list", "check", "bib"],
389
+ help="Citation action")
390
+ parser.add_argument("experiment_id", nargs="?", default=None,
391
+ help="Experiment ID (for add)")
392
+ parser.add_argument("--key", default=None, help="Citation key (e.g., Chen2016)")
393
+ parser.add_argument("--title", default=None, help="Paper/resource title")
394
+ parser.add_argument("--authors", default=None, help="Author list")
395
+ parser.add_argument("--year", type=int, default=None, help="Publication year")
396
+ parser.add_argument("--url", default=None, help="URL to paper/resource")
397
+ parser.add_argument("--doi", default=None, help="DOI identifier")
398
+ parser.add_argument("--type", dest="cite_type", default="method",
399
+ choices=VALID_TYPES, help="Citation type")
400
+ parser.add_argument("--config", default="config.yaml", help="Path to config.yaml")
401
+ parser.add_argument("--log", default=DEFAULT_LOG_PATH, help="Path to experiment log")
402
+ parser.add_argument("--citations-path", default=DEFAULT_CITATIONS_PATH,
403
+ help="Path to citations YAML")
404
+ parser.add_argument("--json", action="store_true", help="Output raw JSON")
405
+ args = parser.parse_args()
406
+
407
+ report = run_citation_manager(
408
+ action=args.action,
409
+ experiment_id=args.experiment_id,
410
+ key=args.key,
411
+ title=args.title,
412
+ authors=args.authors,
413
+ year=args.year,
414
+ url=args.url,
415
+ doi=args.doi,
416
+ cite_type=args.cite_type,
417
+ citations_path=args.citations_path,
418
+ log_path=args.log,
419
+ config_path=args.config,
420
+ )
421
+
422
+ if args.json:
423
+ print(json.dumps(report, indent=2, default=str))
424
+ else:
425
+ if "error" in report:
426
+ print(f"ERROR: {report['error']}", file=sys.stderr)
427
+ sys.exit(1)
428
+ action = report.get("action", "")
429
+ if action == "bib":
430
+ print(report["bibtex"])
431
+ else:
432
+ print(format_citations_report(report, action))
433
+
434
+
435
+ if __name__ == "__main__":
436
+ main()