Twinned from https://github.com/cfsh/ninefold/issues/373 by tools/beads/import_github.py's reconcile pass.
Suggestions from ninefold-reviewer's review of #368. None of these blocked the PR — F1/F2/F3 from the earlier verdict are all fixed and verified in the tree at 6618fc0. These are the residuals that were below the gate floor.
1. manifest.sh: a branch that advanced since the last f5 rebuild is now silently dropped.
The F1 fix (git fetch -q origin --prune) closed the frozen-refs case, but it moved the failure to the other end: origin/<br> is now the branch's *current* head, while the build contains whatever rebuild_f5.sh merged. If the coding bot pushes to a branch and Aedan runs ./f5.sh before the next f5 rebuild, git merge-base --is-ancestor "$sha" HEAD fails and || continue drops the row — under-reporting a branch that IS in the build, which is the thing the script's header exists to prevent.
Sturdier source for the sha: the merge commit's second parent is exactly what was merged, so it's ancestry-true by construction. git log --merges --format='%H %P %s' origin/main..HEAD already gives it; keep the gh-derived candidate + ancestry path as the fallback for no-op merges (#334 under #335).
2. f5.sh short-sha length is coupled to core.abbrev (auto); PlaytestLog.ShortSha is hard-coded to 7.
f5.sh:107 uses git rev-parse --short HEAD (7 chars today, core.abbrev unset → auto, grows with the object count). PlaytestJournal.Build truncates to exactly 7. The moment git starts abbreviating to 8, f5.sh's session dir and the dir CurrentNameUnder derives stop matching: the journal lands in a sibling folder, and f5.sh's manifest.json-only dir is then skipped forever by the new evidence guard — the manifest never ships. One-character fix: git rev-parse --short=7 HEAD.
3. publish.sh republish churn on a same-sha re-run.
f5.sh rewrites manifest.json into the session dir on every run, so if f5/integration hasn't moved (same <date>-<sha> dir) and the dir already has evidence, find -newer sees the fresh manifest and republishes a sitting whose only delta is the manifest's built timestamp — a commit + push to the shared branch for no new evidence. Cheap narrowing: compare against journal.jsonl / capture-*.png mtimes only, not every file.
4. A session dir the *game* creates never contains a manifest.
Consequence of the F2 fix, and the right trade against a folder name that lies about the build. But: press F5 the next morning without re-running ./f5.sh and CurrentNameUnder makes <newdate>-<sha>/, which has no manifest.json — the evidence publishes, the build context doesn't. The folder name still carries the sha, so /playtest can resolve the manifest from a sibling dir; worth stating explicitly in the skill's contract, or having the game copy the flagged dir's manifest.json forward when it names a new folder.
5. publish.sh exits 0 on every failure path, so f5.sh's ⚠ playtest publish skipped can never fire.
f5.sh:82 guards on the exit code, but publish.sh returns 0 for "cannot fetch", "cannot create worktree", "commit failed" and "could not push". The only trace of a total failure is a [playtest-publish] line partway up the f5 output. A distinct non-zero exit for "tried and failed" (keeping 0 for "nothing to do") would make the guard mean something. Related: the f5 clone has only ever fetched, so git commit there needs an identity — git -c user.name=... -c user.email=... on the commit would remove that whole class of first-run failure.
6. CurrentNameUnder is untested.
It's pure string logic (PlaytestJournal.cs:217-225) doing the load-bearing part of the F2 fix, but it sits in the Godot-facing class so nothing can reach it. PlaytestLog is already Godot-free and cross-linked into tests/Ninefold.Tests.csproj; moving it there buys the same coverage the NativePath tests got.
None.
No comments.