11 Commits

Author SHA1 Message Date
FND e6822a256e fix(shared): resolve data-dir paths per call instead of capturing at import (#1490)
* fix(data-dir): resolve the data directory per call instead of at import

* fix(shared): refresh agent schema files once per process, not once ever

---------

Co-authored-by: Michael Ramos <mdramos8@gmail.com>
2026-09-10 16:35:49 -07:00
Michael Ramos 0255db7b4b guide: voice rules in the organizer prompt (#1330)
* guide: voice rules in the organizer prompt (short sentences, plain words, no dashes, no verdicts)

Measured on two agent-written guides: overviews averaged 120 to 135 words,
two thirds of the sentences ran past 25 words (longest 78), and one guide
used ' -- ' as a dash nine times. The prompt's Voice section now gives the
same concrete rules the plannotator-guide skill gives, so guides made
in-app and guides made by an agent read the same way.

* guide: note that GUIDE_REVIEW_PROMPT is mirrored into the plannotator-guide skill
2026-08-16 20:57:06 -07:00
Michael Ramos 64062af9a1 feat: Portable Guided Reviews — export, share links, agent-authored guides, guides.show (#1324)
A Guided Review can now leave Plannotator: as a single self-contained HTML file that renders exactly like the in-app guide, as an encrypted-by-default share link on guides.show, or authored by any agent through the new guide CLI.

Highlights: packages/guide-viewer extracted from review-editor at the injection seam (read-only host, no third renderer); guides.show Worker with R2-backed share storage, per-IP rate limiting on creation, delete tokens hashed at rest, and 128-bit ids; portable exports pin the viewer by SRI hash with budget and manifest gates in PR CI and at deploy; two-runtime parity across Bun and Pi verified; v0.27.x saved guides load unchanged. Retention is indefinite by explicit decision, to revisit with the lean sharing refactor.

Decision record: adr/decisions/007-portable-guided-reviews-20260815.md
2026-08-16 12:17:13 -07:00
Leonardo Reis 1d4e490b8e fix(review): update Codex automatic approval flag (#1231) 2026-08-13 09:42:32 -07:00
Michael Ramos ed6f44bf2e fix(release): tailscale gate exit codes and lease gating, conditional SIGHUP, informative guide validation error (#1286)
- annotate --tailscale publish failures now exit through
  annotateStartupFailureExitCode: exit 2 under a strict gate
  (--require-approval / --result-file), where exit 1 is reserved for "the
  reviewer did not approve, decision record published". Non-strict annotate
  and review keep the documented exit 1.
- the annotate client lease (auto-dismiss on abandonment) is forced off
  while tailnetPublished is set: --tailscale reads as local to the CLI
  predicate, but clients connect through the serve proxy, and a proxy
  disconnect longer than the grace would dismiss a live review. Same
  rationale as remote/shared sessions; decided at the single point both
  the /api/plan advert and the SSE endpoint read.
- the SIGHUP-to-process.exit route moved from an unconditional CLI-entry
  listener into enableTailscaleServe's success path, installed only once a
  serve mapping exists. Any SIGHUP listener overrides the ignored
  disposition nohup depends on, so plain sessions now keep zero listeners
  and "nohup plannotator review &" survives terminal close again;
  --tailscale sessions still tear their mapping down on HUP (exit 129).
- validateGuideOutput explains a fully-invalidated guide whose refs named
  files outside the changeset (count plus up to 3 example paths, with a
  pointer to the Commits panel) instead of the bare generic message; the
  generic message stays for genuinely structural emptiness. The informative
  error now flows through onJobComplete to the job failure card; Pi picks
  the change up via the vendored guide-review copy.
2026-08-12 14:04:59 -07:00
Michael Ramos 98113182b5 feat(guide): reviewer-supplied extra instructions for Guided Review (#1267)
* feat(guide): reviewer-supplied extra instructions for Guided Review (#1265)

Adds a quiet, collapsed-by-default Custom instructions affordance to the
guide launch page. The text is APPENDED to the built-in organizer
methodology as a clearly delimited section (composeGuideMethodology) and
never replaces it; absent or blank instructions produce byte-identical
prompts to before. Persisted in a dedicated cookie
(plannotator-guide-instructions) so a standing team preference survives
sessions without bloating the plannotator.agents blob past the browser's
per-cookie limit.

Server side, the launch body gains an optional guide-only instructions
field (both the Bun and Pi node:http agent-jobs handlers accept and
thread it); prompt composition lives in the shared guide-review.ts that
vendor.sh already vendors to Pi, so both runtimes compose identically.
Text is capped at GUIDE_EXTRA_INSTRUCTIONS_MAX_CHARS (2000) server-side
and mirrored by the textarea maxLength. Repair launches deliberately
ignore instructions: a repair is a mechanical JSON fix, not a rewrite.

Tests pin the regression contract (empty input keeps prior prompt bytes),
appended-not-replacing composition, the length cap, repair isolation, and
the cookie round-trip via the storage backend seam.

* refactor(guide): store standing instructions server-side, not in a cookie

Review findings on the cookie approach (silent write failure past the
encoded 4KB per-cookie limit for multi-byte text) pointed at the real
design problem: the instructions are consumed by the SERVER at launch
time, so they belong in the data dir like review-skills.json, where no
size ceiling or encoding inflation exists and the preference follows
the machine instead of one browser profile.

New GET/PUT /api/agents/guide-instructions in both runtimes backed by
shared guide-instructions-store (vendored to Pi). Guide launches apply
the stored text when the body carries none; the launch page still sends
its live textarea value (explicit wins), so a just-typed preference can
never race the debounced save. The sidebar surface sends nothing and
inherits the stored text server-side. All cookie machinery removed.

Also folds in the review fixes: marker-tag-shaped strings in
instructions are defanged so first-match nonce recovery cannot be
hijacked by pasted examples.
2026-08-11 10:24:39 -07:00
Michael Ramos 6fbf0ef560 fix: align Codex model catalog and reasoning efforts with the current Codex CLI (#1096)
* fix(agents): align Codex model catalog and reasoning efforts with Codex CLI 0.144

- Remove gpt-5.3-codex from the Codex model picker: saved picks of it are
  already force-migrated to the fallback (ChatGPT-account Codex rejects it),
  so offering it was incoherent. The 5.2/5.1 family stays — those models
  remain available to API-key Codex users.
- Per-model reasoning-effort gating: the catalog (now in
  packages/ui/utils/codexModels.ts, re-exported from AgentsTab) carries each
  model's supported efforts and default effort from the CLI catalog. The
  reasoning pickers only offer the selected model's efforts, including the
  new max/ultra tiers on the GPT-5.6 family, and useAgentSettings clamps an
  unsupported saved effort to the model's catalog default before it reaches
  the pickers or a launch payload.
- Drop `minimal` everywhere: no current Codex model supports it. Saved
  perModel reasoning entries migrate minimal -> low, and the guide repair
  launch no longer hardcodes it.
- Ask AI: bump the codex-app-server static fallback model and DEFAULT_MODEL
  from gpt-5.4 to gpt-5.6-sol (runtime model/list discovery still replaces
  the fallback and already carries per-model efforts; added Max/Ultra labels).

Claude-Session: https://claude.ai/code/session_01YXkgsNucxDwAL4GdR4XYRk

* fix(agents): prune API-shut-down Codex models per OpenAI deprecations

OpenAI's API deprecations page schedules an API-level shutdown on
2026-07-23 for gpt-5.2-codex (-> gpt-5.5), gpt-5.1-codex-max (-> gpt-5.5),
and gpt-5.1-codex-mini (-> gpt-5.4-mini) — dead for ALL auth modes, not
just ChatGPT accounts — so they leave the picker and saved picks migrate:
the first two to the surface fallback (already gpt-5.5), codex-mini to
gpt-5.4-mini with its perModel preference carried under the new key (same
pattern as the gpt-5.6 -> gpt-5.6-sol rename).

gpt-5.2 stays selectable with no migration: it was retired from the
ChatGPT product (steered to 5.5) but the API still serves it, so API-key
Codex users retain it.

Claude-Session: https://claude.ai/code/session_01YXkgsNucxDwAL4GdR4XYRk
2026-07-20 11:54:09 -07:00
Michael Ramos c645999761 fix(review): allow disabling Cursor sandbox via PLANNOTATOR_CURSOR_SANDBOX for systems where it cannot start (#1095)
The Cursor review engine hardcodes `--sandbox enabled` when launching the
`agent` CLI. On systems where Cursor's sandbox cannot start (NixOS,
AppArmor-restricted Linux) that hard-fails every job with "Sandbox mode is
enabled but not available on this system", and the flag overrides the
user's own `agent sandbox disable` configuration.

Default is unchanged: review jobs still pass `--sandbox enabled` as part
of their read-only posture. Setting PLANNOTATOR_CURSOR_SANDBOX=0 (or
`{ "cursorSandbox": false }` in ~/.plannotator/config.json; the env var
wins) omits the flag pair entirely — never `--sandbox disabled` — so the
user's own Cursor Agent sandbox configuration governs. Resolution follows
the established env-plus-config pattern via resolveCursorSandbox() in
packages/shared/config.ts, applied at the buildMarkerCommand call sites in
both the Bun server (review.ts, guide-review.ts) and the Pi server
(serverReview.ts; guide-review is vendored, with the new ../config import
rewritten in vendor.sh).

Closes #1094

Claude-Session: https://claude.ai/code/session_01YXkgsNucxDwAL4GdR4XYRk
2026-07-20 08:54:43 -07:00
Michael Ramos 0d5d1fc628 fix: surface Pi provider errors in reviews (#1061) 2026-07-16 14:15:31 -07:00
Michael Ramos aa46dbb29a feat: guide per-file summaries + GitHub Copilot CLI agent engine (#997)
* feat(guide): per-file summary on guide diff refs

Each diffs[] entry now carries a required (schema-enforced) 1-2 sentence
summary of the semantic change in that file, written from the diff hunks
alone. Sanitizer passes it through when it's a non-blank string and omits
it otherwise -- a missing summary never drops the ref or fails the guide.
Rendered as a muted inline-markdown line above each diff in the guide;
marker-engine contract and demo data updated to match.

* fix(guide): repair prompt must not invent missing summaries

The guide schema now requires summary, so a schema-enforced repair of a
payload that lacks them (marker-engine output) would force the model to
fabricate captions with no diff in sight. Instruct it to fill missing
required fields with an empty string instead; the sanitizer already
renders nothing for blanks.

* feat(agents): GitHub Copilot CLI as a marker engine for review + guide jobs

Adds copilot as the fourth marker engine (no schema flag, so it uses the
nonce-tagged marker-block contract like Cursor/OpenCode/Pi):

- marker-review.ts: COPILOT_ENGINE — 'copilot --output-format json' JSONL
  stream (assistant.message carries the assembled text; deltas skipped),
  models discovered from 'copilot help config', live-log formatting for
  tool.execution_start/complete. Non-interactive posture: --no-ask-user
  auto-denies unallowed tools, --deny-tool=write, read-only-oriented shell
  allowlist (git/gh/glab/jj/wc), builtin MCPs and auto-update disabled.
- Exported MarkerEngineId and replaced every 'cursor'|'opencode'|'pi' cast
  with it (Bun server, Pi server mirror, guide-review) so the next engine
  is a two-edit change.
- agent-jobs (both runtimes): copilot in SERVER_BUILT_PROVIDERS; capability
  entry + model discovery come free from the MARKER_ENGINES loop.
- UI: copilot review/guide engine with per-surface model settings
  (useAgentSettings), AgentsTab launch + config rows, GuideEmptyState
  launcher, job-detail labels, CopilotIcon (currentColor, official mark).
- Tests: argv/read-only flags, help-config model parsing, stream reduction,
  full marker pipeline; profile-map expectations widened.

Verified live: composed review prompt through the real copilot binary,
marker block parsed, seeded bug found.

* fix(agents): structurally deny high-consequence verbs for Copilot jobs

git:*/gh:*/glab:* stay allowed for inspection ergonomics, but Copilot's
deny-precedence rules now block the verbs a prompt-injected background
job could abuse: git push/reset/clean/checkout/restore (local reviews run
in the user's real working tree), and PR/MR/issue comment/create/merge/
close/edit/review on gh and glab. Probe-verified: git log runs, git push
--dry-run is denied by the shell(git push) rule.

Addresses the one confirmed finding from the PR #997 review round; the
other two (untracked-file reads, git -C) were refuted by live probes.
2026-07-05 09:12:20 -07:00
Michael Ramos d6b98f0c0d feat(review): Guided Review + Pi agent-job provider (#993)
* docs(adr): Guided Review ADR/spec/research + agent-provider fit studies

ADR 006 (Guided Review as a first-class feature), four implementation
spikes, synthesis, spec (iterated through preflight and review fixes),
recap, and the Flue/Pi provider-fit research syntheses.

* feat(review): Guided Review takeover + Pi agent-job provider

Guided Review (ADR 006) — a Linear-Guides-style chaptered review:
- guide agent-job provider (packages/server/guide/guide-review.ts):
  schema-constrained sections (title/overview/file refs) over the live
  patch, coverage-validated server-side (every changed file exactly once,
  fabricated paths dropped, fail-closed on empty output), importance-first
  ordering prompt with speed discipline (diff-first, no repo exploration)
- runs on claude/codex natively and cursor/opencode/pi via the marker
  contract; guide-scoped low effort defaults for quicker generation
- takeover UI (packages/review-editor/components/guide/): Guide header
  badge + Mod+Shift+G, full-width screen that CSS-hides (never unmounts)
  the file tree/dock, empty state with inline model pickers, skeleton
  loading, generating state with collapsed activity log, two-column
  section cards (sticky prose column with internally-scrolling file list,
  content-height diffs, review-independent collapse, peek semantics)
- annotation parity: guide diffs mount the real DiffViewer with
  file-scoped handlers into the same CodeAnnotation state and feedback
  export; per-section reviewed state persisted via /api/guide/:jobId
- reviewed/tour prompt speed sections; renderMarkdownProse shared between
  tour and guide (fenced-block support, muted tone variant)

Pi agent-job provider:
- third MarkerEngine (pi --mode json --no-session --no-approve) with
  live model-catalog discovery, thinking-level control (--thinking),
  fail-closed marker parsing (Pi exits 0 on in-run errors by design)
- available for review + guide jobs from both launch surfaces

Shared UX:
- searchable, provider-grouped model pickers (auto over 12 options)
- full Claude version catalog with latest-resolving aliases labeled
- AgentControls primitives extracted from AgentsTab; cross-instance
  settings sync in useAgentSettings
- Pi extension server hand-mirrors + vendor.sh entries throughout

* fix(guide): PR-993 review fixes + failure recovery ladder

Review fixes:
- coerce marker-engine title/intent at ingest (prompt-enforced output
  could store non-strings and crash the takeover)
- keep placed files out of unplacedFiles (exactly-once guarantee)
- shared REVIEW_ENGINE_LABEL (fixes 'generated by pi' header; typed
  exhaustiveness prevents recurrence)
- key ActiveGuide by jobId so per-guide focus state resets
- formatModel handles marker-engine tour/guide jobs (empty model chip)
- drop dead guideCodexFast; memoize estimateDiffHeight
- store + display Pi thinking level on job cards

Failure recovery ladder (auto -> one click -> manual -> regenerate):
- mechanical JSON repair pass (fences/slice/trailing-commas/bracket
  balance) before any guide parse fail-closes
- failed payloads captured per job (200KB cap, per-engine extraction)
- 'Fix output' repair job: pure text transform on a schema-capable
  engine (claude/codex preferred), forced low effort, lands as a
  normal guide job; repairOf threaded through launch validation
- editable output panel: GET /api/guide/:jobId/output prefills a
  textarea, POST /api/guide/:jobId/submit runs the same server-side
  validation and opens the fixed guide; inline errors for iteration
- Pi extension mirrors + AGENTS.md endpoint/body docs

* fix(guide): string-aware mechanical JSON repair (self-review)

- terminate a dangling string literal before appending bracket closers
  (truncation mid-string is the most common shape; closers appended
  inside the string never parsed)
- strip trailing commas outside string literals only (the regex could
  rewrite overview content, violating the repair contract)

* fix(guide): launch-snapshot validation, codex strict schema, repair persistence

PR-993 round-2 review fixes + live-test findings:
- validate guide completion (and manual repair) against the LAUNCH-time
  changed-file set, snapshotted per job — switching diff/base/PR while a
  guide generates no longer destroys valid output (client already
  degrades stale refs per-file)
- codex strict structured output: unplacedFiles now required in the
  guide schema (OpenAI 400s schemas with optional properties under
  additionalProperties:false — live-confirmed, then live-verified fixed:
  5 sections / 83 files placed on a ChatGPT-account codex)
- manual repair survives reload: successful /submit flips the job to
  done via completeJobExternally + status guard on the endpoint
- codex model defaults -> gpt-5.5 (5.3-codex is deprecated; ChatGPT
  accounts reject it) with one-shot migration of saved picks; guide
  codex default reasoning stays low
- pi extension guide branch snapshots launch state (TOCTOU hygiene)
- paragraph collector no longer swallows an unspaced code fence
- tour hook backports: out-of-order fetch guard; save outside updater
- guide empty-state copy rewritten value-first

* fix(guide): PR-993 round-3 — repair-diff fidelity, failure surfacing, pi tool hardening

- repair jobs validate against the FAILED job's recorded file set (the
  payload under repair references that changeset; validating against the
  diff on screen at repair time re-introduced destroy-on-switch)
- takeover follows the NEWEST running guide (progress + Cancel), and a
  failure newer than the shown guide surfaces as a dismissible strip
  (error + Fix output + Details) instead of being silently masked
- pi jobs run with --exclude-tools edit,write (bash stays: the agent
  needs git inspection; full read-only would break generation)
- tour/guide result ingestion fails closed on unexpected throw (done-
  looking jobs no longer 404 their result)
- sidebar guide mode gated on file availability like the header badge
- drop orphaned DEFAULT_GUIDE_CODEX_FAST (fast mode intentionally not
  offered for guide); retry owns its fetch cancellation; final
  trailing-comma pass after bracket-closing in the repair ladder;
  collapsed-row checkbox/expand are sibling buttons (a11y)

* fix(guide): PR-993 round-4 — callout text, guide job detail, toggle races

- single-line callouts (> [!IMPORTANT] message) keep their message — the
  form our own prompt solicits rendered an empty labeled box; type now
  derives from the [!TAG] capture, not a whole-line scan that mistyped
  '> [!NOTE] this is important'
- job detail panel gets a guide status card (Open guide via a new
  ReviewStateContext.openGuide) instead of mislabeling 'Guide Generated'
  as a red Incorrect review
- marker-engine guide jobs get readable live logs (engine fallback in
  the formatter lookup; provider stays 'guide')
- repair prefers the failed job's own schema-capable engine (proven
  working on this machine) before falling back by binary presence
- toggleReviewed/toggleChecked: pure functional updaters + persistence
  effect with seed-skip — race-proof AND StrictMode-safe (resolves the
  tradeoff previous rounds accepted)
- dedups: formatDuration/ElapsedTime shared from AgentsTab; whichCmd
  exported in the pi extension; SEARCHABLE_THRESHOLD imported by
  InlinePicker
- new renderMarkdownProse tests (single-line/multi-line callouts,
  paragraph passthrough)

* fix(guide): PR-993 round-5 — keep engine-default model option, label Pi jobs

- GuideEmptyState's marker catalogs mirror AgentsTab's per-engine
  semantics: opencode/pi prepend the engine-managed Default ('' value)
  to the discovered list (dropping it left saved-default users a blank
  pill with no way back); cursor replaces (its list includes 'auto')
- job detail provider pill labels pi review jobs 'Pi' instead of the
  'Shell' fallback

* fix(guide): PR-993 round-6 — context scoping, stale models, repair engine, staging gate

- guide takeover + auto-open scoped to the current review context: a
  guide launched against PR A no longer shows over PR B, and a guide
  finishing for an away context defers its auto-open until the reviewer
  returns to that context (unmarked in the dedupe set on purpose)
- guide launcher reconciles saved cursor/opencode/pi model ids against
  the live catalog at read time (picker + launch share one effective
  value) — no more posting dead ids after an account switch
- repair prefers the failed job's OWN engine whenever its binary is
  present (provably runnable; marker binaries resolved via
  MARKER_ENGINES — cursor's CLI is 'agent'); claude/codex are fallback
  only, killing the broken-claude repair doom loop
- per-file staging gate (canStagePath) in guide diffs AND the
  pre-existing same gap in ReviewDiffPanel — committed-only files in
  since-base reviews no longer offer a no-op Git Add

* fix(review): scope tour auto-open to current context (self-review)

Same cross-context gap just fixed for guides: a tour finishing for PR A
popped its dialog over PR B. Both auto-open effects now share one
jobMatchesCurrentContext helper with the same deferred-open semantics.

* fix(guide): PR-993 round-7 — complete the context-scoping story

Round 6 scoped guides to their review context; this closes the two
surfaces that scoping left dangling, plus a focus gap:

- switching to a context that already HAS a completed guide now shows
  that guide: the takeover falls back to the context's newest done
  guide job when activeGuideJobId belongs elsewhere (previously landed
  on the empty state with the guide sitting unreachable)
- 'Open guide' affordances are context-gated everywhere they exist:
  job cards hide it for cross-context guides (opening can't switch
  PRs), and the job detail panel explains where the guide belongs
  instead of offering a dead button; one shared
  jobMatchesReviewContext predicate now backs App, GuideScreen,
  AgentsTab, and the detail panel
- file-chip navigation retargets the guide's focus arbiter (scrolling
  under a stationary pointer fires no pointerenter, so the annotation
  toolbar stayed bound to the previous diff)

* fix(tour): retry owns its fetch cancellation; document prUrl-only scoping

- useTourData retry converts to the nonce-driven effect re-run pattern
  (the round-4 backport carried the fetch guard but not the retry fix
  useGuideData got in the same batch — half a backport)
- jobMatchesReviewContext now documents WHY it matches by prUrl only
  and not diffScope/diffContext: guides/tours reference files and
  degrade per-file when the diff shifts; scope-strict matching would
  hide useful artifacts on layer/base/mode switches. Deliberate,
  do-not-tighten-without-UX-decision

* fix(guide): PR-993 round-9 — worktree-aware context, guide-scoped marker settings

- jobMatchesReviewContext now compares diffContext.worktreePath for
  local jobs: prUrl + worktree define WHERE the review is (matched
  strictly); mode/base/scope remain WHAT VIEW (deliberately loose) —
  a guide launched against worktree A no longer opens over worktree B.
  Jobs predating the snapshot coalesce to the main tree.
- Cursor/OpenCode/Pi get guide-scoped model/thinking settings
  (guideCursor/guideOpencode/guidePi), mirroring the existing
  guideClaude/guideCodex isolation: tuning a guide's marker model no
  longer silently changes the next code review with that engine.
  Defaults are each engine's natural default, not seeded from review
  settings (same precedent as guideClaudeEffort='low'). AgentsTab's
  reconcile effects snap both surfaces against the shared catalogs.

* fix(review): keep worktree parsing browser-safe (round-9 build fix)

parseWorktreeDiffType lives in shared/review-core, which imports
node:path at module top — pulling it into App.tsx broke the vite
browser build (typecheck and tests don't catch it; only the bundle
does). Context matching now reuses App's existing hand-parsed
activeWorktreePath memo — one parse, and it's the same one that
drives the sections/tree UI, so matching aligns with what's on
screen. Also fixes a deps-array reference to the removed memo that
bundled fine (treated as a global) but would have thrown a
ReferenceError at runtime.

* docs(review): mark worktree-subtype lockstep between server parser and App copy

* fix(review): PR-993 round-10 — guide shortcut gate, settings sync race, heading markdown

- bare a/v staging/viewed shortcuts suspend while the guide takeover is
  open (the dock is only CSS-hidden, so a diff panel stayed 'active'
  underneath and bare keys acted on an invisible file)
- cross-instance settings sync replaces the was-last-update-remote
  boolean with value comparison (lastSyncedJsonRef): the flag conflated
  'a commit happened' with 'the last change was remote', so a local
  edit batched with an incoming broadcast silently skipped its own
  cookie write and rebroadcast
- ### headings in guide/tour prose run through renderInlineMarkdown
  like every other block (backticked symbols rendered as raw tokens);
  regression test added

* fix(guide): PR-993 round-11 — reveal channel for sidebar jumps, repair exit code

- new guideRevealFile channel closes the documented round-7 gap and its
  AI sibling: sidebar jumps (annotation clicks, AI line citations) made
  while the guide takeover is open no longer mutate the hidden dock's
  active file; instead the GuideSectionCard containing the target file
  expands its collapsed (reviewed) section, focuses the diff, and
  scrolls to it — so jumps into collapsed sections stop silently
  no-opping. Cleared on guide close/switch so keyed remounts don't
  replay the last reveal.
- completeJobExternally resets exitCode to 0 (both servers): a
  successfully repaired guide kept showing the failed run's Exit 1 chip
  in the job detail panel

* fix(guide): clear reveal channel in-batch at guide-switch sites (self-review)

The clear-on-change effect fires after a switched guide's keyed cards
have already mounted (child effects run before parent effects), leaving
one commit where a stale reveal from guide A could expand+scroll a
same-named file's section in guide B. All three setActiveGuideJobId
sites now clear synchronously in the same batch; the effect stays as a
backstop for close and future set sites.

* fix(guide): PR-993 round-12 — titled sections, honest large-PR validation

- sanitizeGuideSection gives every surviving section a non-empty title
  ('Untitled section' fallback): a diffs-only section rendered as a
  blank chapter inside a 'Guide Generated' job — no parse failure, so
  no recovery flow. Keeping (titled) beats dropping: placed files are
  not in unplacedFiles, so dropping would orphan them silently.
- guide launches on large PRs (layerPatchIncomplete) recompute
  changedFiles from the local checkout (git diff --numstat
  origin/<base>...HEAD, rename/binary handling) when available — the
  PR-mode prompt tells the agent to read that full local diff, but the
  changed-files block and validation snapshot came from the truncated
  platform patch, under-listing files to the model and then dropping
  its valid refs (or failing the guide) at validation. Falls back to
  the partial list on any failure. Mirrored in the Pi extension server.

Round-12's P1 (Pi marker extraction reads message_end, 'should' read
text_delta) was refuted by live probe: Pi's assistant message_end
carries the fully-assembled content blocks, which is exactly what
piExtractText reads; the delta-reading code the finding cross-
referenced is the Ask-AI STREAMING provider, a different job.

* test(guide): first direct coverage for guide-review's pure logic (self-review)

The module's repair ladder, validation, and stream parsing had zero
unit tests — exercised only end-to-end through live agent runs. Pins
the behaviors the review rounds fixed: blank-title fallback (round 12),
first-placement-wins dedup, changedFiles filtering + fail-closed empty
guides, prose-only vs lost-diffs section handling, unplacedFiles
merge/dedup/fabrication-filtering, title/intent coercion, trailing-
comma and unbalanced-bracket repair, truncated stream-line recovery.
13 tests; server suite 374 → 387.

* fix(guide): PR-993 round-13 — full-stack recompute regression, flag snapshot, unplaced reveal

- the round-12 large-PR recompute now runs ONLY in layer scope: in
  full-stack scope launchPatch is already a local full recompute, and
  the layer diff (origin/<base>...HEAD) was the WRONG file set — it
  omits earlier stack layers' files, dropping their refs at validation
  or failing the guide. Introduced by round 12; caught before any
  release. Both servers.
- layerPatchIncomplete is snapshotted with the other launch locals
  (launchLayerPatchIncomplete): coherence with launchPatch was
  positional (same sync segment) — now structural, so a future await
  inserted upstream can't silently desync them. Both servers.
- the 'Everything else' bucket handles guideRevealFile: sidebar jumps
  to unplaced files now focus + scroll their diff (the section-card
  effect only covers placed files) — closes the residual gap noted in
  the round-11 self-review
2026-07-04 10:23:59 -07:00