Skip to content

feat: force-run, and the recommendation the corpus reading was missing - #255

Merged
thedavidmeister merged 2 commits into
mainfrom
2026-08-10-force-run-and-recommendation
Aug 10, 2026
Merged

thedavidmeister merged 2 commits into
mainfrom
2026-08-10-force-run-and-recommendation

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

Refs #252

This completes the original request. The user asked for

"a fsm command that embodies forcing producer and vetter, watching, reviewing
tool corpus then making optimization recommendation"

— four things. #253 tooled the
middle two and dropped the outer two: forcing stayed prose the caller
retyped, and the recommendation was replaced with an explicit refusal to make
one. Both are restored here. Nothing in watch-run, token-profile or the
existing corpus-report table changes behaviour.

1. Forcing is a typed call now

pr-review-report force-run <producer|vetter> --install-dir <dir> [--no-run]
role positional, producer or vetter. Refused otherwise (exit 2) rather than defaulted — a typo'd vetter that fell through to the producer is a whole run spent on the wrong queue
--install-dir falls back to INSTALL_DIR; empty counts as absent. Refused (exit 2) if neither is set: a run forced against a guessed dir spends real money somewhere nobody is watching. Canonicalised, because it lands verbatim in a git+file:// URL where .. resolves against nothing
--no-run fast-forward and print the runner invocation, but do not start it
exit 3 the install dir will not fast-forward — the one refusal this subcommand owns
otherwise the exit code is the run's

It does the two steps that were prose in observe-run.md: git -C <dir> pull --ff-only first (the runner is a flake package built from that dir's own git
HEAD, so a run against a stale checkout silently exercises old code — which
happened twice on 2026-08-09 at a full run's cost each time), then nix run git+file://<dir>#<campaign|review>-run -- --force.

It streams the child's stdout rather than handing off to watch-run, through
the same watch_line_signal filter. Three reasons, and the first is the one that
decided it:

  1. No gap. A run started in one call and watched in the next has a window
    between them, and every lifecycle line the runner writes inside it (FORCED past …, usage-gate: …, run START) belongs to that run and would be
    missed. A child's stdout has no gap and carries nothing but this run, so
    neither half of the attribution can go wrong — which is the exact bug class
    human-fsm command: force a run, watch it, measure it, mine the corpus, recommend the next subcommand #252 is about, arriving from the opposite direction to the tail-replay one.
  2. The exit code is the run's, so the command learns the outcome from the
    same call it started.
  3. Killing works. Kill this call and the runner dies with it, releasing the
    flock (an open descriptor on the runner process). A detached run outlives a
    killed watcher and keeps spending, blind.

watch-run is unchanged and still shipped: it is the reattach path for a run
this command did not start, or one whose stream was cut, and observe-run.md
says so.

--force semantics are untouched — the policy/correctness split stays entirely
the runner's. The fast-forward refusal is force-run's own correctness stop and
there is no flag that walks past it.

2. The recommendation is back

Computed in corpus-report, so it is testable and available without the command,
and relayed by the command's final step. corpus-report now ends on a BUILD NEXT: block; the JSON gains a recommendation object.

The rule, encoded rather than left to the reader:

  1. A shrinking hand-roll is not a candidate. Shrinking is measured over the
    runs that still do it — latest_sighting against peak — not over the full
    series. That normalisation is the whole of "the render harness is rebuilt by
    every run that takes a screenshot item": the harness scaffold's full series is
    11 → 7 → 7 → 0 and reads as a collapse, while the runs that actually took a
    screenshot item did 11, 7, 7 and did not shrink at all.
  2. Among what is left, the one seen in the MOST traces wins, ties broken by
    most recent sighting, then volume, then name (a total order, so the pick never
    depends on the order the metrics are declared in).

Frequency is the discriminant because novelty is not, and there is a test named
after the mistake: the tarball extraction is newer than everything else, is at
its own peak, and reads holding — and it still loses, because it is in one
trace. That is the "the newest run wasted a worker on a tarball" answer the issue
says would have been wrong.

The evidence travels with the recommendation. The block names the traces that
exhibited the pick and the count in each, everything it beat and on what, and
everything ruled out as shrinking with the ratio that ruled it out. A
recommendation nobody can check is worse than a table.

Where the corpus has no case — every hand-roll absent or already shrinking — it
says so instead of promoting the least-bad row.

What it says against the live corpus today

corpus-report /home/gildlab/issue-pr-cron/runs, read-only, 21 traces:

corpus: /home/gildlab/issue-pr-cron/runs
traces seen: 21 (6 with tool calls) — 20260729T170004Z … 20260809T155241Z
KEEP_RUNS bounds this window: a trace already rotated out is not evidence of anything.

trace                calls            probes        raw gh api           tarball      interpreters    helper scripts  harness scaffold
20260729T170004Z      2436               365                48                 0                32                34                11
20260731T130004Z         0                 0                 0                 0                 0                 0                 0
20260731T170003Z         0                 0                 0                 0                 0                 0                 0
20260731T210003Z         0                 0                 0                 0                 0                 0                 0
20260801T010003Z         0                 0                 0                 0                 0                 0                 0
20260801T050002Z         0                 0                 0                 0                 0                 0                 0
20260801T090003Z         0                 0                 0                 0                 0                 0                 0
20260801T130003Z         0                 0                 0                 0                 0                 0                 0
20260801T170002Z         0                 0                 0                 0                 0                 0                 0
20260801T210003Z         0                 0                 0                 0                 0                 0                 0
20260802T010002Z         0                 0                 0                 0                 0                 0                 0
20260802T050002Z         0                 0                 0                 0                 0                 0                 0
20260802T090003Z         0                 0                 0                 0                 0                 0                 0
20260802T130003Z      2676                92                85                 0                42                19                 7
20260802T170008Z         0                 0                 0                 0                 0                 0                 0
20260802T210003Z         0                 0                 0                 0                 0                 0                 0
20260804T114433Z       414                 7                 0                 0                 1                10                 7
20260805T184152Z         0                 0                 0                 0                 0                 0                 0
20260809T145150Z         4                 0                 0                 0                 0                 0                 0
20260809T154735Z         1                 0                 0                 0                 0                 0                 0
20260809T155241Z        91                 3                 0                 2                 3                 1                 0

metric               first    peak  latest    runs  last seen         shape
probes                 365     365       3       4  20260809T155241Z  falling
raw gh api              48      85       0       2  20260802T130003Z  quiet
tarball                  0       2       2       1  20260809T155241Z  holding
interpreters            32      42       3       4  20260809T155241Z  falling
helper scripts          34      34       1       4  20260809T155241Z  falling
harness scaffold        11      11       0       3  20260804T114433Z  quiet

shape reads the FULL series over the 6 active traces, so a zero in it can mean a landed tool retired the hand-roll or that this run had no occasion for one. The recommendation below reads the runs that exhibited each hand-roll AT ALL, which is what tells those two apart.

BUILD NEXT: harness scaffold
  harness scaffold — 3 trace(s), last 20260804T114433Z, counts: 20260729T170004Z 11, 20260802T130003Z 7, 20260804T114433Z 7
  latest sighting 7 against a peak of 11 — the runs that still do it are not shrinking, so nothing landed is killing it
  it beat, on how many traces still do it:
    raw gh api — 2 trace(s), last 20260802T130003Z, counts: 20260729T170004Z 48, 20260802T130003Z 85
    tarball — 1 trace(s), last 20260809T155241Z, counts: 20260809T155241Z 2
  ruled out as already shrinking (a landed tool is killing these):
    probes — latest sighting 3 against a peak of 365
    interpreters — latest sighting 3 against a peak of 42
    helper scripts — latest sighting 1 against a peak of 34
  DISAGREE FROM THE COUNTS ABOVE, not from this line: the pick is the hand-roll that is not shrinking and is seen in the most traces. A newer, vivid waste seen in ONE trace is the answer this rule exists to refuse.

It names harness scaffold — the same answer the 2026-08-09 session reached by
hand, reached here from the counts. It beats raw gh api (2 traces, last seen
20260802) and the tarball (1 trace, last seen 20260809), and rules out probes,
interpreters and helper scripts as already shrinking — each of which is a tool
that landed and is working.

Also

  • Plugin 0.17.0 → 0.18.0 (marketplace + manifest), as the version gate requires.
  • observe-run.md rewritten: step 1 is the force-run call, step 2 is
    watch-run as the reattach path, and the final step names the recommendation
    and prints its counts. The "do not name the next subcommand to build"
    paragraph is gone — it was never the user's instruction.
  • allowed-tools narrows to Bash(pr-review-report:*): with forcing typed,
    the command has no reason to reach for nix or git at all.

QA

  • Discriminating tests: 13 new unit tests in observation_recommendation_tests + 9 integration tests in pr-review-report-rs/tests/force_run.rs (real git repos against file:// remotes; no run is ever started — --no-run throughout, both DISABLED flags untouched). All fail on base: the subjects do not exist there. Headline: the_corpus_names_the_hand_roll_worth_tooling_next, the_newest_runs_most_visible_waste_does_not_win_on_novelty, shrinking_is_read_over_the_runs_that_still_do_it, an_install_dir_that_will_not_fast_forward_is_a_stop, the_dir_in_the_flake_ref_is_canonical.
  • Mutations applied: 25 mutants over the new lines, 25 killed, 0 surviving. Two survived the first pass, both weak assertions rather than missing tests, and both in the_corpus_names_the_hand_roll_worth_tooling_next: it checked that the evidence line contained a count (so a mutant that ran the counts into the run id beside them still passed) and never checked the recency the ranking breaks ties on. It now asserts the whole line and the pick's last_seen outright, and both mutants die. Full list:
    • shrinking is read off the newest run, not the newest sighting -> killed by shrinking_is_read_over_the_runs_that_still_do_it
    • the shrinking test is inverted -> killed by the_corpus_names_the_hand_roll_worth_tooling_next
    • nothing is ever shrinking -> killed by a_corpus_where_everything_is_shrinking_recommends_nothing
    • candidates and shrinking are swapped -> killed by the_corpus_names_the_hand_roll_worth_tooling_next
    • the rarest hand-roll wins instead of the commonest -> killed by the_newest_runs_most_visible_waste_does_not_win_on_novelty
    • the recency tie-break is dropped -> killed by the_ranking_never_depends_on_declaration_order
    • the volume tie-break is dropped -> killed by the_ranking_never_depends_on_declaration_order
    • the name tie-break is dropped, so a full tie follows declaration order -> killed by the_ranking_never_depends_on_declaration_order
    • never-seen metrics are ranked as candidates -> killed by a_corpus_with_no_evidence_recommends_nothing
    • sightings include the runs that did not exhibit it -> killed by the_frequency_the_recency_and_the_counts_are_one_series
    • last seen is the OLDEST sighting -> killed by the_corpus_names_the_hand_roll_worth_tooling_next
    • the evidence line drops the per-run counts -> killed by the_corpus_names_the_hand_roll_worth_tooling_next
    • an unknown role defaults to the producer -> killed by a_role_is_producer_or_vetter_and_nothing_else
    • the roles reach each other's runner -> killed by the_plan_pulls_the_dir_it_then_builds_the_runner_from
    • the roles reach each other's log -> killed by the_plan_pulls_the_dir_it_then_builds_the_runner_from
    • the runner is invoked without --force -> killed by the_plan_pulls_the_dir_it_then_builds_the_runner_from
    • the stream is unfiltered -> killed by the_forced_runs_own_stdout_is_filtered_by_the_same_rule
    • the forced stream stops at the run's END line, truncating it -> killed by the_forced_stream_does_not_stop_at_the_runs_end_line
    • the pull rebases instead of refusing to fast-forward -> killed by an_install_dir_that_will_not_fast_forward_is_a_stop
    • a refused fast-forward reports success -> killed by an_install_dir_that_will_not_fast_forward_is_a_stop
    • the install dir is used as given, not canonicalised -> killed by the_dir_in_the_flake_ref_is_canonical
    • --no-run starts the runner anyway -> killed by a_stale_install_dir_is_fast_forwarded_before_the_runner_is_named
    • an absent install dir is guessed instead of refused -> killed by a_missing_install_dir_is_refused_rather_than_guessed
    • an empty INSTALL_DIR is taken literally -> killed by an_empty_install_dir_in_the_environment_is_not_an_install_dir
    • the environment fallback is dropped -> killed by the_install_dir_may_come_from_the_environment
  • Oracle: the user's own sentence for what the command must do, and the issue's measured corpus for what the recommendation must land on — the 2026-08-09 session concluded "the render harness" by hand against these traces, and the encoded rule reaches the same answer from the same counts, beating the tarball answer the issue names as the wrong one. The fixture reproduces all six live per-run columns (asserted by the_fixture_reproduces_the_live_per_run_counts), and the live run above is reproduced in the body.
  • Category check: user asked for force + watch + corpus review + recommendation; feat: observation-run instruments — watch-run, token-profile, corpus-report #253 covered watch and corpus review, this covers force (force-run) and recommendation (corpus-report's BUILD NEXT block, relayed by the command) — covered A,B,C,D. Not built: no issue is filed from the recommendation, no cron wiring, no prompt edit; the command still writes no GitHub state.

Summary by CodeRabbit

  • New Features

    • Added a force-run workflow to update and run producer or vetter tools in the foreground.
    • Added corpus analysis with recommended next-tool suggestions, runner-up options, shrinking metrics, and per-run evidence.
    • Added JSON output support for corpus recommendations and evidence.
    • Improved run observation with clearer reattachment, token profiling, and corpus analysis guidance.
  • Bug Fixes

    • Added safeguards for invalid, diverged, missing, or incorrectly configured installations.
  • Chores

    • Updated the plugin version to 0.18.0.

The user asked for a command "forcing producer and vetter, watching, reviewing
tool corpus then making optimization recommendation" — four things. #253 tooled
the middle two and dropped the outer two. This restores both.

FORCING IS A TYPED CALL. `force-run <producer|vetter> --install-dir <dir>`
fast-forwards the install dir first — the runner builds from that dir's own git
HEAD, so a run against a stale checkout silently exercises old code — refuses
with exit 3 if it will not fast-forward, then invokes the runner with `--force`
and streams it through the same filter `watch-run` applies to the log. It streams
the child rather than handing off because a run started in one call and watched
in the next has a gap, and every lifecycle line written inside that gap belongs
to the run and would be missed. `watch-run` stays as the reattach path.

THE RECOMMENDATION IS BACK, computed in `corpus-report` so it is testable and
usable without the command. A hand-roll is shrinking when the runs that STILL DO
IT do less of it than they used to — measured over sightings, not over the full
series, which is the whole of "the render harness is rebuilt by every run that
takes a screenshot item". Shrinking shapes are ruled out; among the rest the one
seen in the most traces wins. Frequency is the discriminant because novelty is
not: the tarball extraction is newer, at its own peak, and in one trace, and it
is the answer the issue names as wrong.

Against the live corpus it names the harness scaffold — the same answer the
2026-08-09 session reached by hand — and prints the per-run counts underneath it,
because a recommendation nobody can check is worse than a table.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thedavidmeister thedavidmeister self-assigned this Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR updates the human-fsm plugin to version 0.18.0, adds corpus-based next-tool recommendations, introduces validated force-run execution, and updates observe-run to use the new typed commands.

Changes

Observation tooling

Layer / File(s) Summary
Corpus recommendations
pr-review-report-rs/src/main.rs
Corpus metrics retain positive per-run sightings. Reports include recommendation rankings, runners-up, shrinking metrics, and per-run evidence in human and JSON formats.
Force-run execution
pr-review-report-rs/src/main.rs, pr-review-report-rs/tests/force_run.rs
The CLI validates roles and install directories, fast-forwards Git checkouts, rejects divergence, supports --no-run, launches forced producer or vetter runs, and filters output. Tests cover repository and argument behavior.
Observation command integration
plugins/human-fsm/commands/observe-run.md, .claude-plugin/marketplace.json, plugins/human-fsm/.claude-plugin/plugin.json
observe-run now requires an install directory and uses force-run, watch-run, token-profile, and corpus-report. Both plugin manifests use version 0.18.0.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ForceRunCLI
  participant force_run_mode
  participant GitRepository
  participant Runner
  ForceRunCLI->>force_run_mode: parse role and install directory
  force_run_mode->>GitRepository: git pull --ff-only
  GitRepository-->>force_run_mode: fast-forward result
  force_run_mode->>Runner: run selected role with --force
  Runner-->>ForceRunCLI: filtered output and exit status
Loading

Possibly related PRs

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the two main changes: the new force-run command and corpus-based recommendations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 2026-08-10-force-run-and-recommendation

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.45.0)
pr-review-report-rs/src/main.rs

ast-grep timed out on this file


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

#254 landed `render-component` on main; both it and this branch append a
self-contained block at the same anchor — the line before `/// The CLI
surface.` — so git returned both blocks whole rather than aligning two
independent insertions. Resolved as the union: each block keeps its own
closing braces, neither side's trailer is shared with the other.

Nothing is dropped or reconciled: the merged file's diff against each
side is hunk-for-hunk identical to that side's diff against the merge
base.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/human-fsm/commands/observe-run.md`:
- Line 3: Update the observe-run command documentation’s argument hint from a
positional install directory to producer|vetter --install-dir <install-dir>, and
revise the related usage prose to state that the directory value follows
--install-dir. Keep the existing INSTALL_DIR environment-variable behavior and
other command guidance unchanged.
- Line 30: Update every runnable command fence in observe-run.md, including the
fences at the referenced locations, to specify the shell language by changing
each opening fence to use sh.
- Around line 112-118: Update the recommendation-ranking explanation in the
hand-roll section to document the complete tie-break order: trace frequency
first, then recency, followed by volume, and finally name. Ensure the prose
makes clear that each later field resolves ties from the preceding metric.

In `@pr-review-report-rs/src/main.rs`:
- Around line 71885-71893: In the test containing the measured_corpus() and want
iteration, assert that measured_corpus().len() equals want.len() before calling
zip. Keep the existing per-row assertions unchanged so every fixture row is
still validated after the count check.
- Around line 37218-37235: Add a pr-review-report subcommand that repairs stale
or diverged install directories after fast-forward failure, then update the
exit-3 message in force_run_mode to name the exact executable repair command
callers should run before retrying. Ensure the new command performs the required
checkout synchronization and is wired into the CLI dispatch.

In `@pr-review-report-rs/tests/force_run.rs`:
- Around line 16-26: Update tmp_dir to return an RAII temporary-directory guard,
preferably by using the existing temporary-directory helper if available, so the
root directory and all test repositories beneath it are removed on Drop.
Preserve the current unique-directory creation behavior and update callers in
the force-run tests to use the guard’s path.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9c65f397-564d-4aed-a7c3-8025fea48be6

📥 Commits

Reviewing files that changed from the base of the PR and between f0e91f7 and 20826c7.

📒 Files selected for processing (5)
  • .claude-plugin/marketplace.json
  • plugins/human-fsm/.claude-plugin/plugin.json
  • plugins/human-fsm/commands/observe-run.md
  • pr-review-report-rs/src/main.rs
  • pr-review-report-rs/tests/force_run.rs

argument-hint: producer|vetter [install-dir]
allowed-tools: Bash(pr-review-report:*), Bash(nix run:*), Bash(git:*)
description: Force a producer or vetter run and watch it, measure what its context cost, read the retained trace corpus, and name the hand-roll worth tooling next — with the per-run counts that pick it, so a human can disagree.
argument-hint: producer|vetter <install-dir>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the install-directory syntax consistent.

The argument hint and prose define INSTALL-DIR as the second positional argument. force-run requires --install-dir <dir> unless INSTALL_DIR is set. A user who follows this syntax can receive a usage error instead of starting the observation.

Change the hint to producer|vetter --install-dir <install-dir>. State that the directory value follows --install-dir.

Also applies to: 9-13

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/human-fsm/commands/observe-run.md` at line 3, Update the observe-run
command documentation’s argument hint from a positional install directory to
producer|vetter --install-dir <install-dir>, and revise the related usage prose
to state that the directory value follows --install-dir. Keep the existing
INSTALL_DIR environment-variable behavior and other command guidance unchanged.

`git -C <install-dir> pull --ff-only` and say what it moved to; a checkout that
will not fast-forward is a stop, not a thing to force past — report it and do
not start a run.
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Specify the shell language for each command fence.

markdownlint reports MD040 for these runnable fences. Add sh to each opening fence.

Also applies to: 65-65, 80-80, 94-94

🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 30-30: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/human-fsm/commands/observe-run.md` at line 30, Update every runnable
command fence in observe-run.md, including the fences at the referenced
locations, to specify the shell language by changing each opening fence to use
sh.

Source: Linters/SAST tools

Comment on lines +112 to +118
The rule it applies is worth understanding before you relay it. A hand-roll is
**shrinking** when the runs that _still do it_ do less of it than they used to —
the signature of a tool that already landed and is killing it — and shrinking
shapes are ruled out. Among what is left, the one seen in the **most traces**
wins, ties broken by the most recent sighting. Frequency is the discriminant
precisely because novelty is not: the tarball extraction is newer, bigger and at
its own peak, and it still loses to something last seen days earlier.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Document all recommendation tie-breakers.

The documented ranking stops after trace frequency and recency. The required ranking also uses volume and name. When two metrics tie on the documented fields, this text can make the selected recommendation appear incorrect.

State the complete ordering: trace frequency, recency, volume, then name.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/human-fsm/commands/observe-run.md` around lines 112 - 118, Update the
recommendation-ranking explanation in the hand-roll section to document the
complete tie-break order: trace frequency first, then recency, followed by
volume, and finally name. Ensure the prose makes clear that each later field
resolves ties from the preceding metric.

Comment on lines +37218 to +37235
let ff = Command::new(&plan.fast_forward[0])
.args(&plan.fast_forward[1..])
.output();
match ff {
Ok(out) if out.status.success() => {}
Ok(out) => {
eprint!("{}", String::from_utf8_lossy(&out.stderr));
eprintln!(
"force-run: {dir} will not fast-forward — a stale or diverged install dir is a \
STOP, not something to force past. Fix the checkout, then force the run."
);
return 3;
}
Err(e) => {
eprintln!("error: cannot run {:?}: {e}", plan.fast_forward.join(" "));
return 2;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Look for an existing install-dir repair transition in the CLI surface.
set -euo pipefail

# List every clap subcommand variant declared in the CLI enum.
ast-grep outline pr-review-report-rs/src/main.rs --items all --type enum --match 'Cmd'

# Search for any existing repair/reset/fast-forward transition.
rg -n --type=rust -C3 'ff-only|fast_forward|fast-forward|reset --hard|install_dir'

Repository: rainlanguage/issue-pr-cron

Length of output: 947


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant definitions without running repo code or installing deps.
wc -l pr-review-report-rs/src/main.rs
echo '--- ForceRun definition ---'
sed -n '41280,41390p' pr-review-report-rs/src/main.rs
echo '--- ForceRun dispatch snippet ---'
sed -n '45228,45255p' pr-review-report-rs/src/main.rs
echo '--- force_run_mode ---'
sed -n '37190,37240p' pr-review-report-rs/src/main.rs
echo '--- command matches containing install/fast-forward/repair/reset/diverge ---'
rg -n --type=rust -C2 'Fast|Install|InstallDir|reset --hard|checkout|fast.forward|ff-only|diverge|rerun|force-run|force_run|RepairQaBlock|WeakenCloses' pr-review-report-rs/src/main.rs

Repository: rainlanguage/issue-pr-cron

Length of output: 50383


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- exact force-run mode error string ---'
rg -n 'will not fast-forward|Fix the checkout|force-run|no run' pr-review-report-rs/src/main.rs

echo '--- command names likely candidates ---'
sed -n '41110,41255p' pr-review-report-rs/src/main.rs | rg -n 'Repair|Reset|Rebase|Checkout|Fast|Pull|Force' \
  || true

echo '--- tests around force-run fast-forward failure message ---'
rg -n -C4 'will not fast-forward|force-run .* Fix|fast-forward.*dive' pr-review-report-rs/src/main.rs

Repository: rainlanguage/issue-pr-cron

Length of output: 3648


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- exact force-run mode error string ---'
rg -n 'will not fast-forward|Fix the checkout|force-run|no run' pr-review-report-rs/src/main.rs

echo '--- command names likely candidates ---'
sed -n '41110,41255p' pr-review-report-rs/src/main.rs | rg -n 'Repair|Reset|Rebase|Checkout|Fast|Pull|Force' \
  || true

echo '--- tests around force-run fast-forward failure message ---'
rg -n -C4 'will not fast-forward|force-run .* Fix|fast-forward.*dive' pr-review-report-rs/src/main.rs

Repository: rainlanguage/issue-pr-cron

Length of output: 3648


Provide an executable install-dir repair transition for failed fast-forwards.

force_run_mode exits 3 and says "Fix the checkout, then force the run", but no pr-review-report subcommand performs that repair. Add or extend a pr-review-report subcommand that handles a stale or diverged install dir and point callers at it from the exit-3 message.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pr-review-report-rs/src/main.rs` around lines 37218 - 37235, Add a
pr-review-report subcommand that repairs stale or diverged install directories
after fast-forward failure, then update the exit-3 message in force_run_mode to
name the exact executable repair command callers should run before retrying.
Ensure the new command performs the required checkout synchronization and is
wired into the CLI dispatch.

Source: Coding guidelines

Comment on lines +71885 to +71893
for (row, (run, p, g, t, i, helpers, h)) in measured_corpus().iter().zip(want.iter()) {
assert_eq!(&row.run, run);
assert_eq!(row.probes, *p, "{run} probes");
assert_eq!(row.gh_api, *g, "{run} gh api");
assert_eq!(row.tarball, *t, "{run} tarball");
assert_eq!(row.interpreters, *i, "{run} interpreters");
assert_eq!(row.helper_scripts, *helpers, "{run} helper scripts");
assert_eq!(row.harness_scaffold, *h, "{run} harness scaffold");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the fixture row count before zipping.

zip stops at the shorter iterator. If measured_corpus() later returns fewer rows, this test still passes and the fixture-integrity guarantee is silently lost. Every later test builds on this fixture.

♻️ Proposed fix to make the fixture check total
-        for (row, (run, p, g, t, i, helpers, h)) in measured_corpus().iter().zip(want.iter()) {
+        let rows = measured_corpus();
+        assert_eq!(rows.len(), want.len(), "the fixture lost a run");
+        for (row, (run, p, g, t, i, helpers, h)) in rows.iter().zip(want.iter()) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (row, (run, p, g, t, i, helpers, h)) in measured_corpus().iter().zip(want.iter()) {
assert_eq!(&row.run, run);
assert_eq!(row.probes, *p, "{run} probes");
assert_eq!(row.gh_api, *g, "{run} gh api");
assert_eq!(row.tarball, *t, "{run} tarball");
assert_eq!(row.interpreters, *i, "{run} interpreters");
assert_eq!(row.helper_scripts, *helpers, "{run} helper scripts");
assert_eq!(row.harness_scaffold, *h, "{run} harness scaffold");
}
let rows = measured_corpus();
assert_eq!(rows.len(), want.len(), "the fixture lost a run");
for (row, (run, p, g, t, i, helpers, h)) in rows.iter().zip(want.iter()) {
assert_eq!(&row.run, run);
assert_eq!(row.probes, *p, "{run} probes");
assert_eq!(row.gh_api, *g, "{run} gh api");
assert_eq!(row.tarball, *t, "{run} tarball");
assert_eq!(row.interpreters, *i, "{run} interpreters");
assert_eq!(row.helper_scripts, *helpers, "{run} helper scripts");
assert_eq!(row.harness_scaffold, *h, "{run} harness scaffold");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pr-review-report-rs/src/main.rs` around lines 71885 - 71893, In the test
containing the measured_corpus() and want iteration, assert that
measured_corpus().len() equals want.len() before calling zip. Keep the existing
per-row assertions unchanged so every fixture row is still validated after the
count check.

Comment on lines +16 to +26
fn tmp_dir(tag: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!(
"force-run-{tag}-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&dir).expect("scratch dir");
dir

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Clean up the test repositories.

tmp_dir creates a new root directory for every test, but no test removes it. Repeated local or CI runs retain bare repositories, clones, and Git objects in the system temporary directory.

Return an RAII cleanup guard for the root directory, or use an existing temporary-directory helper with Drop cleanup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pr-review-report-rs/tests/force_run.rs` around lines 16 - 26, Update tmp_dir
to return an RAII temporary-directory guard, preferably by using the existing
temporary-directory helper if available, so the root directory and all test
repositories beneath it are removed on Drop. Preserve the current
unique-directory creation behavior and update callers in the force-run tests to
use the guard’s path.

@thedavidmeister

Copy link
Copy Markdown
Contributor Author

Reviewed 20826c7: pass

This PR restores two things I dropped when I wrote #252. The user asked for a command that embodies "forcing producer and vetter, watching, reviewing tool corpus then making optimization recommendation"; #253 shipped the middle two because my issue and brief narrowed the ask — I wrote "gathers and measures, it does not decide" and instructed the agent not to name what to build. That was my invention, not their words.

force-run <producer|vetter> --install-dir <dir> [--no-run]. Role positional and refused rather than defaulted, because a typo running the wrong queue is a whole run wasted. Exit 3 when the checkout will not fast-forward — a correctness stop no flag walks past. It streams the child rather than handing off to watch-run, and the reasoning is sound: a start-then-watch handoff has a gap, and the run's own FORCED past … / usage-gate: / run START lines land inside it. Streaming also makes the exit code the run's and makes killing work — kill the call, the runner dies, the flock releases. --no-run is what the tests drive, so no run was executed to build this.

The recommendation lives in corpus-report, testable and usable without forcing a run. The rule is better than the one I would have written: a hand-roll counts as shrinking only when the runs that STILL do it do less of it than they used to — latest sighting against peak, not the whole series. That normalisation stops "rebuilt by every run that takes a screenshot item" reading as decay merely because recent runs took none. Against the live corpus it names harness scaffold and explicitly beats the tarball finding, which is newer and at its own peak and is the answer the issue names as wrong; there is a test named after that mistake.

The merge resolution. Git split the conflict into three regions by aligning ACCIDENTAL common trailers (} / } / }) belonging to only one side each — the silent-botch shape, which compiles. The resolution discarded git's interleaving and rebuilt the span as each side's own contiguous insertion lifted verbatim from its blob. Proven a true union mechanically rather than by eye: diff ours→merged is hunk-for-hunk byte-identical in offsets and sizes to diff base→main, and diff theirs→merged to diff base→255. 52 variants, 52 arms, bijective, arithmetic checked (51 + 51 − 50), with --help exercised to catch the compiles-but-wrong case. Test count is an exact union too: 1152 + 1205 − 1139 = 1218, matching the binary.

Rulings-conformance:

  • "probably we want a fsm command that embodies forcing producer and vetter, watching, reviewing tool corpus then making optimization recommendation" (thedavidmeister, 2026-08-09). NOW OBEYED in full — all four parts. Two were missing until this PR.
  • "i want what i asked for" (thedavidmeister, 2026-08-10). OBEYED: this is the correction, and the omission was mine.
  • "never invent scope" (standing). The failure this PR fixes was SUBTRACTION rather than addition — dropping two of four named parts is as much a rewrite as adding two. Recorded as such.
  • Merge base in, never rebase; run the full suite on the merge commit (standing). OBEYED: a true merge commit, and 1331 tests plus all three closure gates run on the merge commit itself.
  • No producer or vetter run executed at any point.

25 mutants over the new lines, 25 killed after the first pass exposed two weak assertions in the decay-table test.

One pre-existing thing correctly flagged and not touched: a local cargo clippy -D warnings fails on collapsible_match at main.rs:4234, byte-identical in base, 255 and main, and reproducible against origin/main alone. CI's rainix-pinned clippy is green on both; the local toolchain is newer. Not merge-introduced.

CI: all checks pass; the single non-pass is a plugin change bumps its version reporting skipping on a duplicate run. Merging with --merge --admin per the standing no-squash rule.

@thedavidmeister
thedavidmeister merged commit 9c71d4e into main Aug 10, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant