C++ search - #210
Draft
ms609 wants to merge 1161 commits into
Draft
Conversation
ms609
marked this pull request as draft
March 25, 2026 14:21
ms609
added a commit
that referenced
this pull request
Mar 28, 2026
ms609
added a commit
that referenced
this pull request
Mar 28, 2026
ms609
added a commit
that referenced
this pull request
May 18, 2026
In R CMD check, R runs as a non-interactive subprocess with captured stdout. R_FlushConsole() calls fflush() on that pipe; when the buffer fills the call blocks indefinitely, causing the 6 h GHA timeout seen on every ubuntu runner for PR #210. Gate the \r-overwrite progress line and the flush behind R_Interactive (FALSE in batch/check contexts). Interactive sessions are unchanged. At verbosity >= 2 in batch mode, emit plain \n-terminated lines so diagnostic logs still carry progress detail without the flush risk. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ms609
added a commit
that referenced
this pull request
May 18, 2026
In R CMD check, R runs as a non-interactive subprocess with captured stdout. R_FlushConsole() calls fflush() on that pipe; when the buffer fills the call blocks indefinitely, causing the 6 h GHA timeout seen on every ubuntu runner for PR #210. Gate the \r-overwrite progress line and the flush behind R_Interactive (FALSE in batch/check contexts). Interactive sessions are unchanged. At verbosity >= 2 in batch mode, emit plain \n-terminated lines so diagnostic logs still carry progress detail without the flush risk. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This was referenced May 19, 2026
ms609
added a commit
that referenced
this pull request
Jul 3, 2026
The Goloboff-1996 union-construct screen refutation: lower-bound proof note (unsound with collapsed uppass finals) and the gate script that exercises it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Kernel speed-baseline / reslice-plan / goloboff-gates notes plus the disc_nstates and corpus_state_heterogeneity characterisation scripts (+ small .rds caches). The TS_AUDIT_PROBE bail-depth instrumentation from the same original commit is intentionally OMITTED here: origin/cpp-search rewrote ts_fitch.cpp (+264 lines, spr-drift/L3b) so the probe's call-sites no longer map; it remains preserved on branch claude/mission-b-kernel-goloboff-6f5c0e for a future manual port if needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…#2/#3) + harnesses project5432 Mission A: all cold-start-basin-capture angles refuted by measurement. - #1 constructor CLOSED five ways: per-split detectability (anti-detect), absolute synapomorphy count (chance), joint clique recovery (36th pctile, count-matched), scaffold-sufficiency (all-deep-fixed 1/12), clique-START routability (0/60, worse than RAS), clique-proportional reweight (~uniform, wrong-skew). - #2 basin-hop schedule CLOSED (all near-optimal TS trees >=97 TBR from 1943). - #3 structured breadth CLOSED (random resample lands farther, 0/400). Capstone: the 1943 deep backbone is an emergent aggregate (~4/183 clean-supporting chars), so no per-char/compatibility/reweight start-generator can bias toward it; reach is search-side only (not a hard limit -- TNT ~1/3). Adds dev/benchmarks harnesses. Updates the deliverable's open-angles + status so the next 1939 phase does not re-run these. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e skips them The mission brief still listed constructor/schedule/breadth as 'none yet refuted' — the exact re-investigation trap. Now points to the closure record + capstone (emergent-aggregate backbone => search-side only, not a constructor). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t-in) Recovered from a stale GitHub Desktop auto-stash. POOL_RESEED (TS_POOL_RESEED) reseeds a fraction of replicates from a retained best-score pool tree instead of a fresh Wagner start, giving the diverse equal-length pool its own sectorial re-solve pass. Terminal fuse (TS_TERMINAL_FUSE=1) runs a final fuse over a few best-score anchors after the loop converges, recombining complementary clades the in-loop fuse never reached. Both default-off; deployed behaviour is byte-identical. Also carries dev/profiling driver scripts used during that exploration and a stray citation-syntax fix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…result Commit the score-verified best-known artifacts for the Mission-A epitome instance project5432, so they can never go missing again (the lesson of the retracted "1939" trace phantom): - dev/benchmarks/hardtail/project5432_best_1942_tnt.tre (1942, verified) - dev/benchmarks/hardtail/project5432_ts_reach_1943.tre (1943, verified) - dev/benchmarks/hardtail/README.md (regime, provenance, config, re-run footnote) Result (Hamilton array 17943743, 16 seeds x ~48h): TreeSearch's aggressive perturbation swarm reaches 1943 (6/16 seeds) — the new best TS-generated score, one step below the prior TS-side best of 1944. Recorded observationally: block-1 cold still floors ~1945-1947; sustained iteration under the perturbation stack descends to 1943. Reframing: 1943 is a SHARED TS/TNT floor (28h heavy TNT also floored at 1943 across all seeds; 1942 = one lucky regen; 1939 = phantom). The residual 1943->1942 gap is a rarity/luck-and-speed gap, not a TS pathology. Updates the Mission-A plan doc's dated result-note + artifact pointers; CLOSED sections left intact. Swarm ran on Hamilton lib TreeSearch 2.0.0 (lags cpp-search); a current-engine re-run to settle whether TS reaches 1942 is held, not launched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two of four compute_collapsed_flags* recompute sites in tbr_search (post-accept :2859, legacy-reroot :2991) called the conservative kernel unconditionally instead of branching on collapse_aggr like the other two sites (:1539, :2967). Under TS_COLLAPSE_AGGRESSIVE=1 this meant the aggressive criterion only applied to the first pass before silently reverting for the rest of the search. Confirmed via the strategy briefing that no "aggressive once, then conservative" design was intended -- this was an oversight. Exact- preserving (collapsed[] only gates enumeration skips, never a scorer); verified via load_all smoke test that TS_COLLAPSE_AGGRESSIVE=1 still reaches the same full-rescore-matched score, and test-ts-collapsed.R passes unchanged (24/24).
Areas 1 (Fitch scoring correctness) and 2 (Search topology invariants), both directed at what is new in MaximizeParsimony since the 2026-05-26 inspection. Both seams ran dry at opus on the new default-ON surface (L3b incremental edge-sets, bail-cutoff drop-the-+1, x4 reroot, spr_search default) with airtight construction/oracle evidence; each escalates to fable next visit, aimed at the named oracle-blind residuals (compute_insertion_edge_sets absolute correctness; update_base value correctness). Area 2 filed T-334 (P3): the aggressive-collapse neighbourhood criterion reverts to conservative after the first accepted move (exact-preserving, opt-in default-OFF prototype). It was independently adjudicated unintentional and fixed in a concurrent commit (b1e9e09) on this shared branch, so this commit carries only the red-team coordination log plus the area-1 inline doc fix -- NOT the ts_tbr.cpp code fix. - dev/red-team/log.md: area-1 & area-2 round entries; last_focus -> 2 - src/ts_data.cpp: correct stale "TS_PACK_LOCAL (default OFF)" header comment (default-ON since 2026-07-16) -- A1-OP-01, trivial doc-only Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Area 3 (Ratchet & perturbation), directed at what is new since the 2026-05-26 inspection: sector in-sector drift, RSS size-scaling, TS_SECT_COLREDUCE, prune-reinsert exact scorer, fuse per-round reroot. Opus fresh-surface pass, pinned @ ec8edd5. Core deliverable: the full-dataset-rescore firewall = YES -- every sector/prune-reinsert splice-back accept is gated on score_tree against the COMPLETE dataset (HSJ/XFORM sectors skipped entirely), so the HSJ/XFORM-blind reduced dataset and TS_SECT_COLREDUCE are heuristic-quality (reach), never a wrong returned score. The exact_verify NA-cache reach-leak hypothesis traced to a confirmed non-bug (per-instance cache, isolated rd.data, dataset+regime key). Perturbation composition, fuse per-round reroot, and prune-reinsert exact scorer all clean. Filed T-335 (P3): TS_SECT_COLREDUCE leaves per-pattern arrays stale -- inert under the current EW-only gate, a live wrong-score + OOB tripwire if the gate is loosened toward IW (filed defensively; concurrent mission-b work is on ts_sector). RT-A3-02 refuted (finder inverted the field-divergence claim). Seam ran dry -> next visit escalates to fable, but the residual is thin; flagged area 3 as a downtier/retire candidate for the next meta-review. - dev/red-team/findings.md: T-335 - dev/red-team/log.md: area-3 round entry; last_focus -> 3 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Area 4 (Parallelism & RNG), opus fresh-surface pass on the ~2.5k lines of mission-b speed code new since the 2026-06-15 inspection. - T-336 (P1): shared ConstraintData raced across parallel Resample() workers (worker lambda captures cd by ref; parallel_driven copies per-worker, resample path does not). Public-API reachable. Fix filed, NOT applied (concurrency fix to concurrently-edited ts_parallel.cpp). - T-337 (P2): build_dataset Rf_error longjmps across threads on the parallel resample worker path for n_states>32 (T-309 class). - T-338 (P3): 3 consolidated inert worker-thread tripwires (getenv / verbosity-Rprintf / TS_AUDIT_PROBE statics). Crown-jewel RNG class confirmed intact (T-309 fix holds; POOL_RESEED cleared). Seam still yielding -> next area-4 visit stays at opus. Verified: opus finder + opus peer verifier (P1) + haiku (low/latent). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… race) parallel_resample's worker lambda captured the single ConstraintData `cd` by reference and handed the SAME pointer to every worker's resample_search -> driven_search. ConstraintData carries mutable per-tree/per-clip workspace (constraint_node, dfs_entry/exit, clip_zones, clip_tip_mask, posthoc_data) that each search WRITES via map_constraint_nodes/impose_constraint and reads back to gate acceptance. Sharing one instance across workers is a data race (UB), gates a constraint against another worker's tree mapping, and breaks reproducibility. Reachable from public Resample(constraint=, nReplicates>1, nThreads>1). worker_thread (parallel_driven) already makes a per-worker copy `cd_local = *ctx.cd_prototype`; parallel_resample did not. Fix mirrors that pattern: each worker copies the active cd once (searches within a worker run sequentially, so one copy per worker suffices) and passes its own pointer; null/inactive cd passes through unchanged. Red-team area 4 (Parallelism & RNG), 2026-07-24: opus finder + opus peer verifier traced all five links; found+fixed same turn. Build clean (build-fast 6.6s); functional smoke (constrained + unconstrained parallel Resample) passes. Thread-race validation belongs in GHA-ASan/ TSan (local ASan blocked on MinGW). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The P1 shared-ConstraintData data race in parallel_resample was approved by the user and fixed same-turn (f951df5). Update the findings row to FIXED (kept until merged) and correct the area-4 log entry (was "filed, not applied"). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Area 5 (Data pipeline & simplification), opus fresh-surface pass on the post-2026-06-15 commits: per-block-local-alphabet packing (89ebd87, default-ON), char homoplasy-ordering (d8c5999), ambiguity fixes (53ffb05/942bfe5c). ts_ls.cpp excluded as a behaviour-preserving extraction (78b7414) of already-inspected LS code. Seam ran DRY at opus. The P1-capable "corrupted ds -> silently wrong score on every rescore path" hypothesis was tested directly (from-tip on/off differential) and did NOT reproduce: TS_PACK_LOCAL default-ON is byte-identical to OFF across NA/EW/IW/profile/XPIWE. char-reorder is index-based (T-335 shape N/A); per-block DAT-001 ruled out; DAT-001/DAT-002 fixes hold. One finding, PACK-COLRED-01 (reduce_sector_columns_ew leaves the new plane_state field zero-init) verified REAL-but-inert (haiku) and FOLDED into T-335 (same function, same EW-gate inertness) rather than a new row. Next area-5 visit escalates to fable; thin residual = PACK_LOCAL x HSJ/XFORM tip-data interaction (set by the Rcpp bridge after build_dataset). last_focus -> 5; next rotation area = 6. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…339..T-344 Ran opus and fable finders in parallel on identical fresh area-6 scope (R<->C++ interface) per explicit user request, both to make progress and to check whether fable regresses toward opus-like guardrail caution on adversarial-framed bug-hunting. No regression observed: fable was as direct and technical as opus, found a non-overlapping headline bug, and closed a cross-check opus's report had left open. Six findings verified and filed (T-339/T-340 P2, T-341/T-342/T-343/T-344 P3); one trivial doc fix applied inline (timings roxygen list, both finders flagged the same gap independently). last_focus: 5 -> 6. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`tree = <multiPhylo>` used only its first tree and silently discarded the rest, so resuming a search from a previous run's most-parsimonious trees threw away exactly the topological diversity that tree fusing consumes. Replicate i now warm-starts from tree i; replicates beyond the pool build random Wagner trees as before. R side keeps the whole list and normalizes each member (resolve, drop extraneous tips, renumber, reroot so the root's first child is a tip), resolving the tip-set mismatch once up front because that step mutates `dataset`. C++ `start_edge` becomes `start_edges`, and both the serial loop and the parallel worker select on `rep < start_edges.size()` instead of `rep == 0`; reps are claimed dynamically in parallel but rep -> tree stays 1:1, as rep -> seed already does. `unpack_runtime()` accepts either a bare edge matrix (the flat `ts_driven_search()` wrapper and `Resample()` still pass one) or a list. Verified free for existing callers: against a build of a485581 with the same seed, cold start, a single `phylo`, and a `multiPhylo` of one are bit-identical in score, `replicate_scores`, `candidates_evaluated` and returned topologies. `rep < 1` is `rep == 0`, so this is structural. Also fixes a segfault this path could reach. `ape::unroot()` accepts TreeTools' `order = "preorder"` attribute and then mishandles it, so unrooting a TreeTools tree yields an edge matrix containing NA; rooting one killed the session below the level at which R can trap anything. Pre-existing (a485581 crashes identically) but newly reachable from pool members at index >= 2, which normalization no longer skips. `.CheckStartTree()` rejects such an object before any traversal code sees it, and again after `MakeTreeBinary()`, which can emit one itself. Unused pool members are now reported against the replicates actually run: consumption is bounded by whichever stopping rule fires first, usually `targetHits`, so `maxReplicates` was not a truthful bound to warn against. Known gap, left alone deliberately: some genuinely valid unrooted trees fail with "argument is of length zero" (also pre-existing). Rooting before the binary check fixes it but changes which arbitrary resolution `MakeTreeBinary()` picks for other unrooted inputs (star tree 208 -> 191), so it needs an A/B rather than a blind patch. Findings T-345..T-353 from a directed red-team round on this diff. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-353) Diff-scoped review rather than an area visit, so `last_focus:` stays at 6 and the organic rotation still goes to area 7 next. Yield: two P1s (one segfault reachable from a single `phylo`, pre-existing; the same defect newly reachable from pool members at index >= 2), one P2 (pool consumption bounded by `targetHits`, not `maxReplicates`, so the warning and docs were both wrong), and six P3s. All fixed in ee91dac except the valid-unrooted-tree gap, which needs an A/B because rooting first changes the resolution `MakeTreeBinary()` picks. Nothing found at P1 in the C++ index logic: `rep` bounds, `start_n_edge` sharing and `init_from_edge` were confirmed sound rather than left unsaid. Records the rung/version rule that a dry verdict is scoped to the model version that produced it, plus the escalation backlog it feeds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…licates `adaptiveStart = TRUE` updated the Thompson-sampling bandit for every replicate, including those handed a starting tree via `tree = `. Such a replicate never reaches the strategy switch in run_single_replicate() (`if (starting_tree)` short-circuits it), so the default WAGNER_RANDOM was credited for a Wagner start that was never built. Reseeded (POOL_RESEED) reps were already excluded on exactly this reasoning; `user_started` now extends the same guard to user-supplied starts. Latent for replicate 0 alone until `tree = <multiPhylo>` (ee91dac) began warm-starting one replicate per supplied tree, turning one phantom credit into k per search. decay() moves outside the guard: it measures how stale the accumulated evidence is, not which arm ran, so it still fires when a warm-started replicate improves the best score. Kept as a separate `if` so the A/B below measures one change, not two. dev/benchmarks/ab_bandit_warmstart.R (3 datasets x 5 seeds, matched starting pools, serial): mechanism cleanly confirmed -- wag_rand's share of bandit attempts drops from 60.3% to 25.9% (uniform would be 25%), and the clearest cell (Agnarsson2004 seed 2) shows the bandit locking onto wag_rand for all 15 cold replicates before the fix, exploring after. No time-to-optimum regression: score ties 13/15 (1-1 split on the rest), wall trends slightly in the fix's favour in both replications. Verdict: ADOPT. dev/benchmarks/ab_bandit_coldstart_invariance.R backs the NEWS claim that `adaptiveStart` used without `tree = ` is unaffected: every cell across 2 datasets x 3 seeds matches before/after on both score and the per-arm attempt vector exactly, as it must -- with no warm start, user_started is always false and the guard is unchanged. Reviewed under the same red-team round as ee91dac (T-345..T-353): the finder found no defect in this hunk; the P1/P2 findings were both in the warm-start-pool change it landed alongside. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…heck A valid unrooted binary tree has nrow(edge) == 2 * NTip - 3, which fails the "is this already bifurcating?" test the same way a genuine polytomy would, since that test conflates "needs resolving" with "needs rooting". MakeTreeBinary() then misreads the unrooted root's legitimate degree-3 trifurcation as a polytomy, corrupting the tree (dropping its `order` attribute), which previously surfaced downstream as "argument is of length zero" in Preorder/RenumberTips. A/B'd on Vinther2008/DeAssis2011/Wortley2006 (8 seeds x RandomTree(root= FALSE) and StarTree starts, which already worked pre-fix) to confirm no reached-score regression before adopting. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes MaximizeParsimony(tree = <unrooted phylo>) failing with "argument is of length zero" on valid unrooted starting trees. See commit 6fa9776 for root cause and A/B verification. # Conflicts: # NEWS.md
…el-identity check Fable's per-token rate is ~2x opus's, so near-identical token counts this round mean it actually cost ~2x, consistent with doctrine -- not contrary to it as the first version of this note claimed (user caught the error). Also records the post-round self-identification check on both finder agents (distinct, correctly-matched model IDs; not dispositive, since a self-report only reflects the harness's own template, not server-side attestation -- Console billing is the authoritative source). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Area 7 at sonnet (Sonnet 5); last visit yielded 5, so same rung with a fresh agent, and the 4.6->5 bump agrees with that routing rather than competing. A date-filtered `git log --since=2026-06-16 -- inst/Parsimony/` decided the round's shape: the only non-test source change since the last visit is the MorphyLib removal's 9-line footprint, so server.R and mod_search.R are byte-identical to what that round already mined, and the never-opened modules were the whole new surface. Yield: one P2 and six P3s from nine candidates (one refuted in verification, three self-refuted by the finder with evidence). T-354 is the substantive one and was confirmed by a committed artifact rather than by tracing: a passing snapshot of the "R script" download reconstructs the plotted trees by stride sampling where the app ran WideSample(), so the downloaded script recomputes silhouettes and cluster consensus from a different, non-representative subset (19/48 overlap on Sun2018; the stride subset contains duplicate topologies). Fires on the default path for any tree set over 48 trees. Two pieces of tracker rot found and fixed: - findings.md listed T-309..T-313 as open; all five were fixed in 78b7414 on 2026-06-19 and nothing flipped their status. Each confirmed at tip by inspection, annotated pending a tidy archive. The stale rows cost tokens this round -- the finder brief asserted they were open. A tidy pass is due. - focus-areas.md row 7 named a phantom server/events.R while omitting global.R, ui.R, app_state.R and logging.R. Three of this round's findings sit in files the old row did not name, so the stale scope was suppressing yield. Seam still yielding; next area-7 visit stays at sonnet and should take mod_search.R in full, the one genuinely uncovered block left. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TreeState::init_from_edge derives n_tip, n_internal and n_node from the edge count alone, which identifies a tree only when it is binary. A multifurcating edge list broke that derivation in both parities: an odd edge count left the topology arrays one element short, so parent[] and left[]/right[] were written out of bounds, while an even one kept the indices in range but rooted the tree on a real tip, leaving a one-element postorder whose downpass read prelim.data() - total_words. Either way the caller got a plausible number instead of an error, and the number varied between identical calls; a polytomous startEdge segfaulted. Refuse the edge list at that boundary rather than at each of the R entry points that reach it: edge_list_is_binary() checks the shape from the edge arrays alone, before anything is written, and init_from_edge throws for Rcpp to forward. build_topology_tree() clones the same derivation for the least-squares path and gets the same check. ts_driven_search() screens start trees with the predicate on the main thread, since an uncaught throw on a parallel worker would terminate the session. TreeLength.list(), .CheckTreeCharLen(), TreeScore() and EdgeListScore() gain the R-level check so the message matches the one TreeLength.phylo() has always given. FastCharacterLength() is left unchecked, as documented; the kernel now gives it the same message. The Shiny app scores every tree it displays, so it now searches with collapse = FALSE. Also extends the T-261 zeroing audit in reset_states() to name the collapse kernels, whose whole-row memcmps read words no pass writes, and to name every path that zero-fills the state arrays rather than only init_from_edge. The T-382 one-sidedness comment keeps its original reasoning for prelim -- a tip sibling always carries real states, so a ratchet-zeroed block makes equality harder -- and gains the down2 / subtree_actives case, where the words really are always zero. Fixes #16 Fixes #24 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ps tests External review of the prior commit found the roxygen text factually wrong: AdditionTree() never returns a $score (it discards result$score), so "affects only the reported $score" is false -- correct it to state plainly that numeric concavity has no effect on AdditionTree()'s output. Also addresses two test gaps the review flagged: the first new test exercised ts_wagner_tree() directly rather than AdditionTree() (retitled to make that explicit, and its equality assertion tightened to a directional/finiteness check so a NaN or sign error couldn't pass), and two cheap regression tests were missing: that concavity = Inf still forwards min_steps = integer(0), and that concavity = "profile" still runs cleanly now min_steps is computed post-recoding. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
`Resample(constraint =, nReplicates > 1)` errored: `R/Resample.R:372` splats `.PrepareConstraint()`'s whole list into `ts_parallel_resample()`, which has no `consZero` formal. It was the one splat site of four not filtered, and no test covered a constrained `Resample()` at all; a test now asserts the filter against every flat kernel's formals. The collapse protection over-reached. It protected the MRCA of a group unconditionally, so an unsupported branch that merely happened to be the narrowest node containing the 0 group was returned resolved -- the "unsupported non-constraint branches still collapse" half of the promise, and a shift in `n_topologies`. It now protects only when no realising edge survives the contraction on its own. A discarded replicate no longer feeds the strategy bandit, the Chao1 coverage scores or the replicate report: a violating tree scores better than any legal one, so those would be credited to whatever produced it. Its stopping rules still run -- skipping them would outlive the deadline and swallow an interrupt. An interrupted replicate is now tested but not repaired, since `impose_constraint()` has no interrupt check of its own. The constraint check short-circuits on the locked-node mapping, which is cheaper than the post-hoc Fitch check and strictly stronger, so only an unmapped split pays for the latter. Constrained wall-clock on Vinther2008 (30 paired seeds) goes from a 1.12 median ratio, 11 seeds >10% slower, to 1.003 with none >10%. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ci: fix the Windows/macOS MaxMin resolution failure that reddened every branch
test(EasyTrees): let the dataset load settle before setting treeRange
Self-review of the two commits before it. A constraint is enforced as a split throughout -- the locked-node filter screens rearrangements on it, and impose_constraint() repairs to it and nothing else -- so a character with a third state has taxa nothing constrains. The capture gate was judging that same tree by the stricter full-Fitch reading, which is not a standard the search can reach: a probe on a three-state constraint discarded all four replicates and errored with an empty pool, where a partial answer existed. The gate now follows the mapping, and `.PrepareConstraint()` warns at input that an intermediate state is unconstrained, rather than leaving the caller to infer from `@param constraint` that it is not. `constraint_satisfied()` refreshes the DFS timestamps alongside the node ids. map_constraint_nodes() alone left the two out of step, and spr_search() reads both without re-mapping, so the `sprFirst = TRUE` warmup could classify a regraft against this tree's nodes and another tree's timestamps. A start whose repair fails falls back to a Wagner build, which is exactly the constructor that can exhaust its reshuffles and return a violating tree; it is now repaired rather than trusted. Also: the collapse protection's group sizes are counted once instead of per tree; the kernel-formals test now asserts that no constraint field is dropped, not just that none is unknown; `.ConstraintViolated()` indexes nodes by column so its accumulation runs down a column-major matrix rather than across it. Constrained wall-clock on Vinther2008 (20 paired seeds) is 0.95 median against `cpp-search`, scores identical. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
red-team: area 12 round 2026-08-04 — scope-coverage diff, routing-channel finding, rearrange.cpp header correction
fitch_label_char()'s uppass loop formed `&co.kids[co.kidOff[node]]` before testing `nk`, the node's child count. CanonOrder stores children CSR-style, so `kidOff[n]` for a childless node is whatever `kids.size()` happened to be when the DFS popped it -- and for the LAST node popped that is the final size, every other node having already contributed its children by then. `co` arrives as a const reference, so this is `std::vector<int>::operator[](size()) const`: a dereference of one past the end. The downpass and the tie-break accumulation loops above both already `continue` on `nk == 0`; this loop did not. Reproduced against `-D_GLIBCXX_ASSERTIONS` (flag in PKG_CPPFLAGS, since ~/.R/Makevars.win zeroes PKG_CXXFLAGS; 34 hits in the build log). Pre-fix, four test files abort on entry to their first HSJ block with `Assertion '__n < this->size()' failed`; post-fix all four run clean: test-tree_length.R abort -> 77 passed test-ts-xform.R abort -> 138 passed test-ts-hsj.R abort -> 147 passed test-ts-resample-hierarchy.R abort -> 74 passed The reporter's second reproducer (test-ts-xform.R) is the same defect, not a second one: it aborts inside the third test, which is the file's first `inapplicable = "hsj"` search -- hence exactly five assertions first, from the two preceding pure-xform tests. `kid` is never dereferenced when `nk == 0`, so no value was read through the bad reference and no score moves: 900 HSJ and x-transformation lengths over random matrices (4-16 tips, alpha 0/0.5/1), the issue's own data, and three seeded end-to-end searches are bit-identical either side of the fix, Inf entries included. Fixes #51 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… routing and scope gaps The user-level `/red-team` skill carried dual-mode machinery for a `findings.md` store this project deleted at the 2026-08-04 migration. Retired to `~/.claude/skills-retired/red-team/` — the only surviving record of file mode, which is still correct for a project with no GitHub remote — and replaced with a project-local skill that assumes issues throughout, alongside `/next-issue`. The one-off process for standing up a repo under `agent-issues` (fork, default branch, push block, labels, Actions, secrets, findings migration) moves to a subsidiary `github-repo-setup.md` that a normal round never opens. Fixes #41 — the rotation's routing state was tier-only: - a work-shape gate between brief assembly and dispatch, so a recorded "the next visit should NOT be a finder" verdict stops the round and reports instead of auto-dispatching. Areas 10 and 13 both carry one, and area 13 is next. - `needs-escalation` is documented as a tier flag and nothing else; sequencing and cross-area residuals stay prose, and step 3 now reads `escalation-backlog.md` whether or not the label query hit — a hit used to suppress the row holding the actual ask. - the finder brief carries the area's open issues, so a finder cannot spend its budget re-investigating a mechanism already filed. - `area:N` semantics stated: it marks which area owns the code, not which round found it, and the compound label query ANDs correctly. Addresses #42 — scope coverage: - the `tidy` pass now leads with a scope-coverage diff (glob `R/*.[Rr]`; the lowercase `pp_info_extra_step.r` is invisible to a case-sensitive pattern). - area 14 (statistics & support metrics, 14 files) and area 15 (legacy pure-R search API, 9 files) added, both `sonnet` / UNMEASURED. #42 recommended `opus` for 14 and review-once-then-park for 15; the maintainer chose measured maturity for both. N is now 15. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ion state only The previous commit put the skill and its repo-setup guide inside this repo. Both were wrong. The skill is shared across projects, so burying it here takes /red-team away from every other project; and `github-repo-setup.md` documents standing a repo UP under agent-issues, which is precisely the thing already done here and never needed again — it belongs where a not-yet-configured project reads it, next to the skill. Both now live at `~/.claude/skills/red-team/`, with the #41 routing fixes applied there. This branch keeps only what is genuinely project state: areas 14 and 15, N = 15, and the README/AGENTS label range. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Issue agent-issues#51 asked for this: the memory note it was filed against points at the ASan workflow as the route to a container-OOB, and that workflow was red on trunk for the very defect the previous commit fixes. A local -D_GLIBCXX_ASSERTIONS build reproduces the same class in seconds on Windows and can be aimed at one test file, so it belongs in the subsystem's own memory file, next to the flat-vector layouts that make this the recurring failure mode here. Also records the two ways to misread its output: the abort names the container type, never the call site; and `lib.loc` must be an absolute Windows path, or test_file()'s chdir breaks the lazy-load DB and fakes several regressions. Qualifies the in-source issue reference per AGENTS.md, since src/ fast-forwards to the public upstream, where a bare `#51` resolves to an unrelated issue. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Code-review follow-up on the T-300 rerooting patch. `n_reroot_accepts` exists solely so the new regression test can tell a correct dirty-set rerooting rescore from an unexercised one. It was incremented for every rerooting move regardless of `incremental_ok`, so under HSJ/XFORM — where a rerooting accept still falls through to `full_rescore` — the counter would be positive while the arm it guards never ran. Adding an HSJ/XFORM case to the test would then have made `expect_gt(n_reroot, 0)` pass vacuously, which is exactly the failure the counter was added to prevent. Gate it on `incremental_ok`. Also record what it actually counts: the increment sits at the rescore, before the accept/reject decision, so a move counted here can still be rejected by the constraint check, the tabu test or the score comparison. It is an upper bound on accepted rerootings, not a count of them. No behaviour change: EW / IW / NA / NA-IW all have `incremental_ok`, so every case the test drives is unaffected. Third hunk is a stale comment: the NA dirty passes are no longer SPR-only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Independent review of ca5c1c0 disputed "the last node DFS popped". It is not one node but a run of them: every childless node reached after the final push_back carries the end offset. Measured on an R mirror of build_canon_order() over 900 random trees, 2-24 tips: trees with NO kidOff==size node: 0 trees with >1 such node : 843 kidOff/kidNum CSR consistency : OK The first line matters most -- the pre-fix code formed a reference to co.kids.end() on EVERY HSJ scoring call, not on some unlucky shape. The third rules out the alternative reading that the guard papers over a corrupt CSR: kids[off + 1 .. off + num] is exactly each node's canonical children, so kidOff/kidNum are sound and only the missing nk == 0 test was wrong. Comment and NEWS reworded accordingly; the script lands under dev/red-team/reviews/ as the standing evidence. The same review found a SEPARATE unguarded bound -- tip_labels' row count is validated at neither Rcpp bridge, giving an identical `_Tp = int` const-operator[] abort from a hand-crafted TreeSearch::: call. Confirmed against a build already carrying this fix, so it is not the same defect, and it is unreachable from the public API. Filed as agent-issues#58 rather than widened into this branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…opped Review read "which is what made the AddressSanitizer workflow unusable" as crediting ASan's own instrumentation, and objected -- correctly -- that ASan watches accesses, not address arithmetic, and that co.kids.reserve(n_node) leaves the offending address inside the live allocation anyway. The claim was about the right defect but named the wrong instrument. What aborted the gcc-ASAN job is visible in agent-issues#51's own evidence: `stl_vector.h:1282 ... Assertion '__n < this->size()' failed`, a libstdc++ precondition check, not a sanitizer report. The r-hub gcc-asan container compiles with the hardened library; the flag is not in ASan.yml, which is why grepping .github/workflows/ for it finds nothing and misleads. Reworded to say what actually fires and why that workflow could not get past this package. Also distinct from the EARLIER ASan unusability that PRs #262/#275 fixed -- that was the runner configuration; this is a defect the now-working workflow immediately hit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs: state Wagner's equal-weights placement contract; pass min_steps
fix: reject non-binary trees at the TreeState boundary
…103cc perf(tbr): finish T-300 — incremental rescore for TBR-rerooting accepts
…/ on Windows core.ignorecase=true on Windows folds "/.agent*" onto the tracked .AGENTS/ memory directory (case-insensitive prefix + wildcard suffix match), causing `git add .AGENTS/memory/*.md` to silently refuse without -f. Narrow the pattern to the actual .agent-<id> naming convention and add a defensive negation for good measure. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lision Narrow /.agent* gitignore pattern to stop matching .AGENTS/ on Windows
fix: OOB read in HSJ token_states/CanonOrder lookup
fix: enforce user constraints at the start-tree, pool-capture and collapse boundaries
…-4853c6 red-team: add focus areas 14 and 15 (N=15); skill rewrite lands user-level
Update from agent-issues
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Manual testing underway; shiny app in particular has some usability issues.