* test(agent): cover antigravity in fakeagent and the e2e user journey
* no-mistakes(document): Add antigravity to agent lists, doctor example, and fakeagent doc comment
* no-mistakes(document): Add antigravity to agent Values and auto resolution order in docs
* feat(recordfixture): capture antigravity fixtures via the owned e2e-record workflow
* no-mistakes(review): Assert Antigravity recorder argv forwarding; focused tests pass
* fix(recordfixture): stage agy captures atomically and contain the process tree
* no-mistakes(review): Test plain fixture replay path by splitting TestRunAgyReplaysRecordedFixture into structured/plain subtests
* no-mistakes(review): Validate Antigravity captures and replay configured fixtures reliably
* no-mistakes(document): Document Antigravity capture and replay behavior
* no-mistakes: apply CI fixes
* fix(recordfixture): expect cmd-exe quote escaping in agy argv test; dedupe antigravity docs
The windows-core run at 0ba9bd95 failed TestCaptureAgyPlacesForwardedFlagsBeforePromptAndSchemaLast
because the fake .cmd agent records Go's literal backslash-quote escaping:
cmd.exe %~1 expansion does not undo it, while real agy parses argv with
CommandLineToArgvW and sees the unescaped JSON. Assert the escaped schema
arg on Windows with a comment explaining the harness-only artifact.
After rebasing onto main (#812), agents.md carried two Antigravity adapter
sections and two supported-agents table rows. Keep one of each, reflecting
final capabilities: session resume via --conversation and
terminal-authoritative result precedence (structured_output >
result.response > stream deltas).
* fix(recordfixture): reject any non-SUCCESS agy result during capture validation
validateAgyCapture only inspected the final result status, so a capture
containing an ERROR result followed by SUCCESS validated even though the
production replay parser treats any ERROR result as fatal. Fail fast on
the first non-SUCCESS result and require at least one result event.
* feat(agent): resume antigravity conversations for review-fixer sessions
* no-mistakes(review): add antigravity to session resume per-adapter pin test
* no-mistakes(document): Fixed two stale antigravity references in agents.md and global-config.md
* no-mistakes: apply CI fixes
* fix(agent): address review findings on antigravity session resume PR
- Parse agy thinking_tokens as ReasoningTokens in both step_update and
result usage handlers so reasoning usage is not undercounted
- Fix antigravity docs: stream deltas outrank result.response, not vice
versa; --dangerously-skip-permissions is always present (cannot be
suppressed via agent_args_override)
- Add Antigravity to supported agents table, review session reuse list,
one-shot agents list, and session-control reserved flags
- P1 stale-fallback instrumentation was already fixed in prior commit
* fix(pipeline): bound every agent invocation so a stall cannot hang a run
PR #807 only timed out Test; document, lint, rebase, PR, CI-fix, and auto-fix still called Agent.Run with an unbounded context. Enforce a 30m default at the shared RunAgent seam (and an executor backstop) so a stalled agent fails with a diagnostic instead of wedging the run.
* no-mistakes(document): Clarify agent timeout fallback behavior
* no-mistakes: apply CI fixes
* fix(test): bound stalled evidence agents
The post-test evidence agent inherited the unbounded run context, so a
0%-CPU Codex stall waited forever. Give each Test agent turn its own
deadline, matching the review-agent timeout, and fail the run loudly.
* no-mistakes(review): Scope test timeout to agent invocation
* no-mistakes(review): Reject successful agent returns after timeout
* no-mistakes(review): Strengthen late timeout commit regression test
* feat(agent): unify model and reasoning effort across harnesses
Model and reasoning effort existed only as raw per-harness flags. An
operator had to know that codex spells effort `-c model_reasoning_effort`,
copilot and claude `--effort`, pi `--thinking`, and grok
`--reasoning-effort`, and the eval path carried its own divergent copy of
the model rule that could not pin an ACP target at all.
internal/agentcfg is the harness-neutral layer: a Profile of model plus
effort, and one table mapping each knob down to what that harness can
actually express. agent.NewWithOptions is the single funnel, so the
pipeline (agent_config in the global YAML) and eval replay (the candidate)
reach every harness by the same path.
- ACP model pinning now works through acpx's own --model, closing the gap
that made cursor and acp:<target> candidates impossible. Effort stays
unmappable there and is refused rather than dropped, as it is for
rovodev and antigravity, which expose no mechanism at all.
- opencode carries both knobs in its session-message body. `opencode
serve` exits with usage on an unknown flag, so the previously
documented agent_args_override model pin would have taken the server
down instead of being ignored.
- Precedence: a raw agent_args_override flag that already pins a knob
natively wins and the mapped value is not emitted, so every existing
configuration keeps its exact argv and no harness receives one knob
twice.
- eval candidates become agent,model=<model>[,effort=<level>], mirroring
the YAML keys. The replaced agent+model spelling is refused with a
migration message. Capture now also strips agent_config so a replay
cannot inherit the capturing machine's own pins.
* no-mistakes(review): Fix Codex config pin detection
* no-mistakes(document): Correct unified agent configuration documentation
* no-mistakes: apply CI fixes
* no-mistakes: apply CI fixes
* feat(agent): reuse Pi fixer sessions
* fix(agent): report Pi model telemetry and prove session fallback end to end
- Capture provider/model from Pi's final assistant message into Result so
local invocation telemetry matches Claude/Codex/Grok.
- Drop cwd from the resume-test session-header fixture: %CD% expands to
backslash paths that produce invalid JSON on Windows.
- Add a RunSessions integration test driving the real piAgent through
persist -> dead-resume fallback -> replacement resume.
* no-mistakes(document): Scope stats token-counter cumulativeness note to codex vs pi
* no-mistakes(document): Scope stats token-counter cumulativeness note to codex vs pi in environment.md
* no-mistakes: apply CI fixes
* no-mistakes: apply CI fixes
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Kun Chen <kunchenguid@users.noreply.github.com>
* fix(branchsync): give each remote op its own refresh deadline
Refresh shared a single 15s context.WithTimeout across the sequential
ls-remote and fetch calls, so a slow-but-successful ls-remote could
consume the fetch's budget and falsely report a reachable push target
as offline (JVPT: axi sync returning offline with changed:false even
though both git operations succeed independently). Each remote call
now gets its own deadline derived from the caller's context, so parent
cancellation is still honored but one call's latency no longer starves
the next.
* no-mistakes(document): Document per-operation branch-sync deadlines
* no-mistakes(document): Clarify branch-sync remote deadline invariant
* fix(branchsync): add configurable global remote-operation timeout
Root-cause diagnosis (JVPT incident) disproved this PR's original premise:
per-operation timeout isolation alone does not fix the "could not refresh
the configured push target" / offline misclassification, because the
observed failure happens at the ls-remote call itself, whose own budget
was already a fresh 15s before and after that change. The real cause is a
hardcoded 15s budget that is too short for a legitimate private-repo
credential-helper round trip (git spawning `gh auth git-credential` as a
child process), independently measured taking ~19-22s.
internal/branchsync/sync.go's Service now takes an exported RemoteTimeout,
sourced from a new global-only config key (branch_sync_remote_timeout,
60s default, internal/config/config.go), following the existing
daemon_connect_timeout precedent. It is deliberately never read from a
repo's .no-mistakes.yaml, so a pushed branch cannot widen or narrow how
long this service waits before failing closed - RepoConfig has no
matching field. The per-operation isolation from the earlier commits is
retained as legitimate, independent hardening.
Verified against the real production credential-helper path: a locally
built (not installed) revised CLI's `axi sync --check`, run read-only
against the actual JVPT worktree, now resolves a live remote head instead
of reporting offline, while the worktree's head and cleanliness are
confirmed unchanged before and after.
* no-mistakes(review): Reject invalid branch-sync timeout configuration
* no-mistakes(document): Document branch-sync remote timeout
* no-mistakes: apply CI fixes
* no-mistakes: apply CI fixes
* no-mistakes: apply CI fixes
* no-mistakes: apply CI fixes
* no-mistakes: apply CI fixes
---------
Co-authored-by: Rene Garza Jr <rega10tam@gmail.com>
Co-authored-by: kunchenguid <kun@kunchenguid.com>
* fix: don't mark ci-monitoring runs failed on daemon restart
Fixes#361
* no-mistakes(review): reclaim safe ci-interrupted worktrees; centralize terminal-status check
* no-mistakes(document): document ci_monitor_interrupted daemon crash-recovery and worktree preservation
* no-mistakes: apply CI fixes
* fix(daemon): thread ctx and worktree path into the orphan-process sweep
skipWorktreeCleanup gained ctx and wtPath in this branch so a
ci_monitor_interrupted run can be preserved when its worktree head differs
from the last pushed commit. sweepOrphanRunProcesses arrived on main in the
meantime and still called the two-argument form, which no longer compiles.
Rebuild the worktree path from the repo and run IDs procreap already passes,
the same convention cleanupOrphanWorktrees uses.
---------
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Kun Chen <kunchenguid@users.noreply.github.com>
* fix(pipeline): render Bitbucket Cloud PR bodies as no-HTML markdown
Bitbucket Cloud escapes raw HTML, so the GitHub <details> folds and
attestation comment were leaking into reviewers. Keep the HTML skin on
every other host and emit the same pipeline and testing facts as
headings, fences, and links on bitbucket.org.
* no-mistakes(review): Escape ### / <details> fold markers in embedded PR text
* fix(pipeline): keep Bitbucket testing summaries as prose
A one-line testing_summary that mentioned HTML tags was wrapped in
backticks, so Bitbucket Cloud rendered the whole paragraph as a
numbered code bar.
* no-mistakes(review): Escape fold markers in Bitbucket testing summary prose
* no-mistakes(review): Escape failed-step error text with flavor-aware fold-marker escaping
* no-mistakes(review): Escape finding.File with fold-marker-safe escapePRText
* no-mistakes(review): Escape fold markers in embedded test artifact content
* Fix(pipeline): clarify pipeline html format for docs
* refactor(pipeline): shrink Bitbucket PR body flavor plumbing
Drop unused HTML wrappers, the tautology allowlist, and a cloned HTML-leak test.
* no-mistakes(review): Restore Bitbucket tautology collapse and real HTML-leak decode check
* Keep main's no-mistakes-required workflow after conflict resolution
The merge had no workflow conflict; dropping main's copy would have reverted #773 on this PR.
---------
Co-authored-by: kunchenguid <kun@kunchenguid.com>
* feat(daemon): place run worktrees per repository
Pipeline run worktrees are created under <NM_HOME>/worktrees/<repo>/<run>,
outside every checkout. Directory-scoped toolchain configuration - mise,
direnv - resolves by path ancestry, so none of it ever reaches a run
worktree: an operator whose repositories are configured that way gets a
run environment that does not match the one they develop in, and there is
no way to move the worktree without moving NM_HOME for everything.
Add a worktree_roots map to the global config: keys are registered
checkout paths, values are absolute directories. A repository with an
entry gets its run worktrees at <value>/<runID>.
Placement is decided in exactly one place, internal/worktrees, and every
consumer of a worktree path goes through it: run creation, parked-run
resume, the fix-review step diff, startup cleanup, the run-cleanup and
startup process sweeps, gate context classification, and eject. That
package also owns the single path canonicalization the feature compares
with, so a spelling that matches in one consumer matches in all of them.
A configured root is the operator's own directory - it holds the
mise.local.toml or .envrc that motivated pointing runs at it - so what
may be touched there is bounded twice over. Cleanup and the process sweep
consider only directories named like a run ID (uppercase ULIDs, as this
daemon mints them); eject goes further and removes only the directories
this repository's own run rows name, so a leftover from anything else
survives. Validation makes the config incapable of expressing the case
that would defeat both: two checkouts sharing a root, two spellings of
one checkout, a non-absolute key or value, or a root equal to its
checkout are all rejected at load time. `init --worktree-root` refuses a
root inside the repository or inside <NM_HOME>/worktrees, and daemon
startup reports a key that matches no registered checkout, which would
otherwise silently place nothing.
With no worktree_roots entries the resolved paths, the directories
walked, and the processes matched are exactly what they were before.
* no-mistakes(review): reject unusable worktree roots and record run placement
* no-mistakes(review): sweep recorded run worktrees instead of configured roots
* no-mistakes(review): bound startup sweep set and tighten placement validation
* no-mistakes(review): resolve recorded run worktrees without consulting config
* no-mistakes(review): sweep resumed run worktrees and refuse in-checkout roots
* no-mistakes(review): align init placement checks and scope run validation
* no-mistakes(review): detect worktree_roots key in document, narrow classifier
* no-mistakes(review): tell operators to replace a re-pointed worktree_roots entry
* no-mistakes(review): sweep before every worktree removal, compare paths by identity
* no-mistakes(review): sweep at eject and tolerate unmigrated preflight schema
* no-mistakes(review): sweep eject placements from one bounded process snapshot
* no-mistakes(review): sweep startup cleanup from one process snapshot
* no-mistakes(review): guide worktree_roots edits by document shape
* no-mistakes(review): match the worktree_roots block's own indentation
* no-mistakes(document): document configurable run worktree placement in stale doc owners
* no-mistakes(review): refuse init registering configured roots; sweep default tree
* no-mistakes(review): never rewrite a worktree_roots key nobody could read
* no-mistakes(review): read block indent from unparseable config; refuse unloadable init
* no-mistakes(review): drop unreachable unparsed-config guidance path
* no-mistakes(document): document every-init worktree_roots refusals and root cleanup
* no-mistakes: apply CI fixes
* fix(daemon): fail closed when the registered-checkout list is unreadable
The placement guard validated against an empty protected set when
RepoWorkingPaths errored, so a configured root inside an unlisted
registered checkout passed startup and run-creation validation and the
run worktree dirtied that checkout. A guard that cannot see the set it
protects must refuse: startup now reports the read failure instead of
starting, and run creation fails that run with the cause recorded. The
database is already open and migrated by the time either caller runs,
so the failure is never routine.
Raised by Greptile on the pull request; addressed as a follow-up commit
per run custody.
* no-mistakes(document): document unusable worktree_roots startup refusal in troubleshooting
* fix(agent): parse pi JSONL fences glued to body on the same line
pi's final assistant message concatenates content blocks without
separators, producing a ```json fence glued to the JSON body on the
same line (no newline after the fence info token) and no closing fence.
The fence parser only handled newline-separated, closed fences.
- fenceContentStart now takes the info token up to the first
non-word char, tolerating ```json{...} on one line.
- fencedJSONCandidates treats an unclosed fence as running to end of
text; parseStructuredCandidate validates and only adopts valid JSON.
Regression tests cover the pi same-line unclosed shape plus the
fence-info-token boundary.
* fix(agent): prefer closed JSON fences over unclosed pi tails
The same-line fence fix (d701ef8) still returned early when an opening
```json fence had no closing fence, so prose quoting ```json as an
inline example swallowed a real trailing fenced block and the whole
parse failed with "invalid character '`' after top-level value" —
hit in a real gate pipeline review run where pi quoted the fence shape.
- fencedJSONCandidates now returns closed and open candidates and never
stops at the first opener: it resumes after the opener marker so a
later real block is always discovered on its own.
- parseStructuredTextOutput prefers closed fences; open (unclosed, pi
JSONL shape) candidates are consulted only when no closed fence
parses, so quoted examples never shadow a trailing block.
- lastBareJSONObject tolerates unpaired fence markers in prose instead
of aborting the whole scan.
- Regression test: prose quoting a ```json example followed by a real
closed block parses the trailing block.
* fix(pipeline): carry a user's declined findings across steps and runs
Approve, skip, and abort wrote no finding-level state, so a round where
the user read a blocking finding and said "ship it as is" was
indistinguishable from a round with no findings. The only durable
statement of what the change must do then stayed the user-intent prose,
and a later step could re-derive the requirement from that prose and
re-apply the exact change the user had just declined.
Record those three resolutions as a real decision, stored the way a
partial selection already stores one: as the complement of
selected_finding_ids, using an explicit empty array so "selected
nothing" is representable at all. Carry the resulting declines to the
run's other steps and to later runs on the same branch, and state in the
prompt that a recorded decision supersedes the intent wording.
Deliberately advisory and fail-open: no step is blocked and no commit is
gated, so an agent may still raise a declined finding again when the code
genuinely changed.
* no-mistakes(review): Preserve chronological decisions and bound prompt history
* no-mistakes(review): Preserve chronological decisions across same-run steps
* no-mistakes(review): Correct partial-selection decision history assertions
* no-mistakes(review): Exclude declined rounds from pending fix stats
* no-mistakes(review): Disclose truncated branch decision history
* no-mistakes(document): Document durable finding decisions
* no-mistakes: apply CI fixes
---------
Co-authored-by: Kun Chen <kun-1@kunchenguid.com>
* fix(eval): stop display commands from mutating pipeline state and clipping strata
Two defects shipped in #784 that the pipeline's own review passed green.
evalRepoNames resolved repository names through db.Open, which creates the
database and runs every migration. `eval sets`, `eval report`, and `eval run`
therefore initialized pipeline state on a machine that had none and migrated
the schema of a database a running daemon owns, all for a display-only lookup.
db.OpenReadOnly exists for exactly this case; a missing database is the
ordinary outcome and still falls back to fingerprints. The idempotency tests
could not have caught it: they asserted stdout equality only, and every fixture
created the database up front, so the no-database path was never exercised.
Both gaps are now covered - a dedicated test asserts the three display commands
leave no state.sqlite on a fresh app root, and the CLI idempotency test asserts
the app root's shape as well as its output.
compositionLines sized only the repository column. Once the fixed strata grew
past the room the box has, that column hit its minimum and had nothing left to
give, so the composed line ran past the content width and the box renderer cut
the finding type off the end of the row. The strata are now shortened to the
space that actually remains, measured against the column the names occupy so a
table of short names keeps its strata intact. The width test previously varied
only the repository-name axis; it now has a sibling that varies the strata.
The reachable source of an oversized stratum was the ingest payload, whose
severity was free text and whose action was dropped without a word. Both are
now validated against the finding vocabulary, which internal/types owns.
* no-mistakes(review): Handle expanding eval composition case counts
* no-mistakes(document): Document eval display and ingest safety
* feat(eval): name repositories and table the confusion matrix in eval sets
The eval-sets dashboard showed each stratum's repository as the opaque
fingerprint a case stores, and rendered finding-level gold as one dense
prose line. Resolve the fingerprint back to the repository's name from the
locally registered repositories (no recapture, no network; unresolved
fingerprints still render), and lay the gold out as a confusion-matrix
table with the review's call on the rows and the recorded truth on the
columns. True negatives stay uncounted rather than fabricated as zero,
since a correctly silent review leaves no gold row.
* no-mistakes(review): Fix repository naming and composition identity collisions
* no-mistakes(review): Fix repository shortening, parsing, and stable ordering
* no-mistakes(document): Clarify eval set repository display fallbacks
* feat(eval): idempotent subcommands plus sets/run dashboards with instant diversified self-score
Idempotency, proven per subcommand (internal/eval/idempotency_test.go and
CLI double-run tests):
- capture/relabel converge in place; mergeGold now dedupes ID-less gold by
content so recapture cannot silently grow labels.json
- replay never rewrites case labels: queued unmatched-finding counts derive
from the evaluations table (Store.pendingFindingCounts) instead of an
incremented stored counter, and identical eval runs land in the same
cohort so reports aggregate instead of fragmenting
- sets reads self-stabilize diversified pins; writeJSON publishes atomically
so a crash cannot corrupt a case file
TUIs, consistent with the stats dashboard (shared renderTitledBox, metric
lines, progress bars, lipgloss styles):
- eval sets headlines the diversified holdout with an instant self-score
(SelfScoreRecordedReviews: recorded reviews scored against their own gold
from already-captured files, no replay), with the other sets as footnotes
- eval run streams a scored progress line per replay and closes with a
session score dashboard
* no-mistakes(review): Repair duplicate ID-less eval gold during recapture
* no-mistakes(document): Clarify eval idempotency guarantees
* feat(eval): key finding gold on the recorded decision and match gold globally
Labeling a review finding used to key on whether a later review round still
raised it, which conflates the two decisions that matter: a finding the human
chose to fix and a finding they chose to ship both disappear from later rounds.
Gold now follows the round's own recorded fix-vs-skip decision plus the source
run's merge state. Selecting a finding for fix on a merged run is true-positive
gold even if a later round re-raised or rewrote it; leaving one unselected on a
merged run is shipped-unfixed false-positive gold; a round whose gate decision
was never recorded stays unlabeled.
The false-positive half deliberately reverses the earlier "never auto-label FP
from a skip" stance: in this operator's corpus, a finding they approve and ship
without fixing is a false positive. It still needs both halves - a recorded
decision and the merge - and no-op findings are never labeled.
Scoring replaces per-strength-tier greedy assignment with one globally optimal
bipartite matching over all gold and candidate findings, weighted so an exact
match outweighs any number of fuzzy ones. The tiered matcher could hand a
candidate to a gold that had alternatives and strand a gold that had none,
understating recall for reasons unrelated to the review under test.
* no-mistakes: apply CI fixes
* feat(review): persist uncertified fixer commits for the next initial review
A cancelled re-review left pipeline-authored commits with no marker, so the replacement run reviewed them as ordinary author code. Persist the per-branch uncertified range and feed fix-round provenance on the next initial review; rerun still proceeds.
* no-mistakes(review): Preserve uncertified range across runs and rebases
* no-mistakes(document): Document uncertified review provenance across runs
* feat(eval): ingest confirmed post-PR misses as false-negative gold
A green review that later proved wrong was unscorable: capture only labels from gate decisions, and there was no way to record the greptile-caught class. Add eval miss ingest so a vetted finding becomes FN gold on the last non-blocking review pass, skip incomplete sibling rounds, and refuse parked or blocking reviews.
* no-mistakes(document): Align eval docs with post-PR miss ingest
* feat(review): ungate counterexample tracing and name silent-wrong-result risks
Review was reconstructing failing sequences only for claimed durable fixes, so feature changes never got that discipline, and silently wrong values, labels, or sets were unnamed in the risk vocabulary. Make tracing a short general principle for any new or changed logic, and keep intent conformance from substituting for correctness.
* no-mistakes(document): Clarify conformance does not replace correctness
* feat(eval): pin gold-only diversified holdout and real precision
Keep unlabeled cases out of the official set, auto-label merged auto-fixes as true positives and shipped-unfixed findings as false positives, and headline F1 only when false-positive gold exists so precision is not recall in disguise.
* no-mistakes(review): Skip later-fixed shipped-unfixed gold labels
* no-mistakes(document): Fix leftover stale eval documentation
* fix(eval): honor last-round shipped-unfixed, live holdout cap, and optimal matching
Stop labeling findings that were gone before merge as false positives, trim diversified pins on ListCases when the cap shrinks, and assign candidates by exact-then-fuzzy maximum matching so gold order cannot undercount.
* no-mistakes(document): Align eval docs with merge-derived gold
* test(eval): prove relabel clears a stored shipped-unfixed false positive
Cover the retention path separately from the write trigger: a labels.json shipped-unfixed FP must be removed when recomputed gold no longer supports it, both in mergeGold and on disk after RelabelRun.
* no-mistakes(document): Correct stale eval daemon-entry comments
* fix(eval): keep at most one diversified pin per stratum when the cap shrinks
Zero and lowered caps now collapse existing pins to one official case per stratum on ListCases, so a prior Hamilton allocation cannot leave extras in the holdout or steal them from tune.
* no-mistakes(document): Eval Phase A documentation already current
* fix(eval): do not reallocate multiple holdout seats into one stratum
After a lower cap collapses duplicate-stratum pins, leftover seats may fill new strata but Hamilton must not recreate k>1 in one of them.
* no-mistakes(document): Holdout cap docs already current
A single Windows ./... job could exceed timeout-minutes without any binary hitting go test -timeout. Split git-heavy packages from the remainder, replace the bash SIGTERM trap that macOS still raced, and cancel parked executor tests so Windows can unlink lint.log.
* fix(daemon): keep run evidence off shared /tmp and reap it ourselves
The test step wrote evidence to os.TempDir()/no-mistakes-evidence. The
daemon's service unit exports only HOME, PATH, and proxy variables, so
TMPDIR is unset and that resolved to the shared /tmp - which Ubuntu has
mounted as a systemd tmpfs since 24.10, putting every screenshot and
rendered-HTML artifact in RAM.
Nothing in this program ever removed it, either. The directory was a
fixed name that accumulated one subdirectory per run, cleaned only by an
OS timer we do not control and that has no equivalent on Windows. On one
developer machine that was 876 run directories over ten days, 823 of them
empty: the test step creates the directory before the agent decides
whether it has anything to write.
Evidence now lives at <NM_HOME>/evidence/<runID>, owned by internal/paths
like every other location. The app root is disk backed on macOS, Linux,
and Windows alike, so there is no runtime.GOOS branch and the macOS path
is not regressed - it improves, since /var/folders is periodically purged
and that silently broke the local artifact paths older PR bodies fall
back to.
Cleanup is now ours, in three layers: a finished run's empty directory is
removed at run cleanup, a reaper bounds the directory by age and count
oldest-first after every run and at daemon startup, and the
pre-relocation temp directory is drained under the same policy. All three
reuse the existing pending/running guard, so a run still in flight is
never touched.
test.evidence gains local_root, retention, and max_runs. They are
global-only: a repository does not get to name a filesystem path this
machine's daemon writes to, nor set the retention budget for a directory
every repository on the machine shares. There is deliberately no
environment variable - the service unit preserves only proxy keys, so an
env-gated value would be dropped on the next update.
Also removes the second, independent copy of the evidence path in the
agent steering preamble, which rebuilt it from os.TempDir() on its own.
The executor now resolves the path once and both consumers read it.
* no-mistakes(review): Protect unowned directories from evidence reaping
* no-mistakes(document): Clarify managed evidence storage scope
* no-mistakes: apply CI fixes
* no-mistakes: apply CI fixes
* fix(daemon): clean run evidence at the recovered-run completion boundary
Evidence ownership claims cleanup runs after each run, but only the
fresh-run defer called cleanupRunEvidence. A run parked at an approval
gate when the daemon stopped is finished by resumeRecoveredRun, which has
its own completion defer, so a resumed run kept its empty evidence
directory until some later run or a restart happened to sweep it.
There are exactly two completion boundaries and both are now covered. The
regression test drives resumeRecoveredRun and asserts the directory is
gone afterwards; every outcome of a resumed run shares that defer, so the
boundary is the thing worth asserting on.
* ci: give the test job a budget the Windows leg actually fits in
The Windows leg was cancelled at timeout-minutes twice in a row with no
verdict, on a suite where every package that reported had passed. It is
process-spawn bound rather than compute bound, so it compiles every test
binary and then runs the git-backed packages at roughly 10x their Linux
cost, and 25 minutes no longer covered that.
An evidence-free cancellation is worse than a slow job: it is reported as
a check failure and reads as a defect in the change under test, which is
exactly how it was read here.
The cap stays a runaway guard rather than a target. Linux and macOS
finish far inside it, 40 minutes still keeps a wedged runner well short
of the six-hour default, and go test -timeout is deliberately unchanged
so a genuine hang still produces a goroutine dump first.
This does not close the coherence gap behind it: -timeout bounds each
test binary while timeout-minutes bounds the whole job including compile,
so total wall can still exceed the cap with no binary reaching its own
deadline. Bounding per-job wall properly is follow-up work.
* chore: pin review auto-fix to the product default in this repo
The code default for auto_fix.review is 0 so review findings park for a
human decision, but this repo carried no auto_fix block and therefore
inherited whatever an operator set globally.
With it enabled, a one-line CI timeout change drew a speculative review
finding that the auto-fixer answered with a new DB table, a schema
migration, and a new package, then spent three rounds finding errors in
its own invention. None of it was asked for, and a parked finding would
have surfaced the question instead of quietly answering it.
Only review is pinned. The deterministic steps keep whatever the operator
configured, because a lint or test finding has an objective checker and
does not need a human to decide whether the fix belongs.
* no-mistakes(review): Reject evidence roots inside managed worktrees
* no-mistakes(document): Clarify evidence storage constraints
* feat(eval): score findings as true/false positives, not park/pass
Park/pass treated skip and approve as a pass and asked the wrong question.
Capture now writes finding-level gold from recorded Fix and add-finding
evidence, and replay/report score TP/FN with unmatched findings left pending.
* no-mistakes(review): Capture user-added eval gold independently
* no-mistakes(review): Remove duplicate eval guidance
* no-mistakes(review): Persist eval decision provenance atomically
* no-mistakes(review): Make eval finding scoring evidence-safe
* no-mistakes(review): Make eval scoring evidence-safe and relabel recall range
* no-mistakes(review): Keep eval matching and recall evidence-safe
* no-mistakes(test): Match eval findings by finding ID
* no-mistakes(document): Document finding-level eval scoring