Skip to content

Commit 063338c

Browse files
IMNMVclaude
andcommitted
Pilot 1 fixes: parenthesized DOIs, docx routing in verify_references,
protocol truncation guard, exact-match counts (0.11.1) All four surfaced by the first full blinded audit of the demo fixture (11/12 planted defects caught, zero false positives, four unplanned real defects found): - extract_dois (new helper, R/citations.R): Crossref-recommended DOI character class keeps parentheses, so legacy Elsevier DOIs like 10.1016/S1364-6613(03)00028-7 no longer truncate into false 404s; trailing punctuation and unbalanced closers from prose are stripped - verify_references_impl routes file input through read_as_text_lines, so Pass 4 line-range mode on a .docx extracts text instead of returning raw zip bytes - cat_protocol: reviewer_zero_prompt and referee_prompt write the composed protocol to a temp file and announce the path first, so a truncated console printout is one read_file from recovery - reviewer_zero.md Step 3b: integer counts (Ns, dfs, tallies) must match exactly; all.equal tolerance is for continuous statistics only CI: extract_dois functional test. R CMD check: Status OK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 69bb6b3 commit 063338c

5 files changed

Lines changed: 69 additions & 10 deletions

File tree

.github/scripts/checks.R

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,5 +437,17 @@ r <- tryCatch({
437437
}, error = function(e) conditionMessage(e))
438438
if (isTRUE(r)) pass("cross-restart history: past logs parsed, agent filter works") else fail("past history:", r)
439439

440+
# --- 14. DOI extraction: parenthesized DOIs, trailing junk, prose parens ---
441+
r <- tryCatch({
442+
d <- env$extract_dois(paste(
443+
"Monsell, S. (2003). Task switching. TiCS. https://doi.org/10.1016/S1364-6613(03)00028-7",
444+
"Also see (doi: 10.1037/a0019842) and https://doi.org/10.1126/science.1201068.",
445+
sep = "\n"
446+
))
447+
all(c("10.1016/S1364-6613(03)00028-7", "10.1037/a0019842",
448+
"10.1126/science.1201068") %in% d) && length(d) == 3
449+
}, error = function(e) conditionMessage(e))
450+
if (isTRUE(r)) pass("extract_dois: parens kept, prose parens and trailing dots stripped") else fail("extract_dois:", r)
451+
440452
if (!ok) quit(status = 1)
441453
cat("\nAll checks passed.\n")

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: ClaudeR
22
Title: R Integration for Claude AI
3-
Version: 0.11.0
3+
Version: 0.11.1
44
Authors@R: person("Nykko", "Vitali", email = "nykvt@icloud.com", role = c("aut", "cre"))
55
Description: Connects RStudio with Claude AI to enable interactive coding sessions.
66
License: MIT + file LICENSE

R/citations.R

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,33 @@ get_bibtex_impl <- function(doi) {
7575
bib
7676
}
7777

78+
# Extract DOIs from text using the Crossref-recommended character class,
79+
# which allows parentheses: legacy Elsevier DOIs like
80+
# 10.1016/S1364-6613(03)00028-7 are common in psychology bibliographies and
81+
# were truncated at the paren by the old pattern, producing false 404s.
82+
# Trailing punctuation and unbalanced closing brackets (from prose like
83+
# "(doi: 10.1037/a0019842)") are stripped after matching.
84+
extract_dois <- function(text) {
85+
pattern <- "10\\.\\d{4,9}/[-._;()/:a-zA-Z0-9]+"
86+
dois <- regmatches(text, gregexpr(pattern, text, perl = TRUE))[[1]]
87+
dois <- unique(trimws(dois))
88+
dois <- sub("[.,;:]+$", "", dois)
89+
strip_unbalanced <- function(d) {
90+
repeat {
91+
last <- substr(d, nchar(d), nchar(d))
92+
if (!last %in% c(")", "]")) break
93+
opener <- if (last == ")") "(" else "["
94+
n_open <- lengths(regmatches(d, gregexpr(opener, d, fixed = TRUE)))
95+
n_close <- lengths(regmatches(d, gregexpr(last, d, fixed = TRUE)))
96+
if (n_close > n_open) {
97+
d <- sub("[.,;:]+$", "", substr(d, 1, nchar(d) - 1))
98+
} else break
99+
}
100+
d
101+
}
102+
unique(vapply(dois, strip_unbalanced, character(1), USE.NAMES = FALSE))
103+
}
104+
78105
# GET a Crossref API URL with retry/backoff. Crossref rate-limits bursts
79106
# (HTTP 429); a failed lookup mid-audit silently truncates the reference
80107
# check, so wait and retry before giving up.

R/ui.R

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2179,13 +2179,15 @@ verify_references_impl <- function(file_path = NULL, text = NULL,
21792179
return("Error: jsonlite package is required. Install with install.packages('jsonlite')")
21802180
}
21812181

2182-
# Get text from file or direct input
2182+
# Get text from file or direct input. Manuscripts (.docx/.pdf) route
2183+
# through the structured extractor; a raw readLines() on a .docx returns
2184+
# zip bytes, which is how the Pass 4 line-range mode broke in the field.
21832185
if (!is.null(file_path)) {
21842186
file_path <- path.expand(file_path)
21852187
if (!file.exists(file_path)) {
21862188
return(paste0("Error: File not found: ", file_path))
21872189
}
2188-
lines <- readLines(file_path, warn = FALSE)
2190+
lines <- read_as_text_lines(file_path)
21892191
if (!is.null(start_line)) {
21902192
end_l <- if (!is.null(end_line)) min(end_line, length(lines)) else length(lines)
21912193
lines <- lines[max(1, start_line):end_l]
@@ -2195,11 +2197,9 @@ verify_references_impl <- function(file_path = NULL, text = NULL,
21952197
return("Error: Either file_path or text must be provided")
21962198
}
21972199

2198-
# Extract DOIs using regex
2199-
doi_pattern <- "10\\.\\d{4,9}/[^\\s,;\\]\\)>\"']+"
2200-
dois <- regmatches(text, gregexpr(doi_pattern, text, perl = TRUE))[[1]]
2201-
dois <- unique(trimws(dois))
2202-
dois <- sub("[\\.,;]+$", "", dois)
2200+
# Extract DOIs
2201+
dois <- extract_dois(text)
2202+
doi_pattern <- "10\\.\\d{4,9}/[-._;()/:a-zA-Z0-9]+"
22032203

22042204
# Cap per call: each lookup blocks the R session, and the MCP bridge times
22052205
# out at 120s. Large bibliographies should be paged via start_line/end_line.
@@ -2546,7 +2546,7 @@ reviewer_zero_prompt <- function(prereg_path = NULL, robustness = FALSE,
25462546
txt <- paste0(txt, "\n", build_referee_text())
25472547
}
25482548

2549-
cat(txt, "\n")
2549+
cat_protocol(txt)
25502550
invisible(txt)
25512551
}
25522552

@@ -2706,10 +2706,25 @@ referee_prompt <- function(lenses = c("logic", "methods", "consistency",
27062706
reviewers_per_lens = reviewers_per_lens,
27072707
model = model, cross_vendor = cross_vendor,
27082708
stance = stance)
2709-
cat(txt, "\n")
2709+
cat_protocol(txt)
27102710
invisible(txt)
27112711
}
27122712

2713+
# Print a protocol, but write the composed text to a file first and announce
2714+
# the path. Long protocols exceed the console-output cap (agents saw "262
2715+
# lines elided" mid-protocol in the field); the file makes the full text one
2716+
# read_file call away.
2717+
cat_protocol <- function(txt) {
2718+
proto_file <- tempfile(pattern = "clauder_protocol_", fileext = ".md")
2719+
ok <- tryCatch({ writeLines(txt, proto_file); TRUE }, error = function(e) FALSE)
2720+
if (ok) {
2721+
cat("[Full protocol saved to:", proto_file,
2722+
"-- if this printout is truncated, read that file completely before starting.]\n\n")
2723+
}
2724+
cat(txt, "\n")
2725+
invisible(NULL)
2726+
}
2727+
27132728
#' Print the R Best Practices prompt template
27142729
#'
27152730
#' Displays the built-in R statistical analysis protocol based on

inst/prompts/reviewer_zero.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,11 @@ claim_registry$status[i] <- if (is_match) "match" else if (is_rounding) "roundin
257257

258258
R sets the status. You do not. This prevents eyeballing "close enough" values.
259259

260+
Integer counts are exempt from tolerance: sample sizes, degrees of freedom,
261+
cell counts, and exclusion tallies must match EXACTLY. A count that differs
262+
by one is a discrepancy, never rounding. Apply `all.equal` tolerance only to
263+
continuous statistics.
264+
260265
For claims with multiple values (e.g., "t(38) = 2.12, p = .041, d = 0.34"),
261266
test each value separately. If any single value is a discrepancy, the whole
262267
claim is a discrepancy.

0 commit comments

Comments
 (0)