Skip to content

Employer-ready pass: docs, live-AI demo, Cloudflare bundle — plus the AI-fallback bug it uncovered - #113

Merged
trs-80 merged 17 commits into
mainfrom
chore/repo-presentation
Aug 9, 2026
Merged

Employer-ready pass: docs, live-AI demo, Cloudflare bundle — plus the AI-fallback bug it uncovered#113
trs-80 merged 17 commits into
mainfrom
chore/repo-presentation

Conversation

@trs-80

@trs-80 trs-80 commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Three requested items, plus a product bug the demo work uncovered.

The bug (unplanned, and the most important change here)

aiDiagnose ran at max_tokens=1024 with a 10s timeout. No current model can return a complete structured diagnosis in that budget — nine live sonnet-5 runs against the real pg-replication prompt came back at 2259–3742 output tokens and 28–40s. Every response arrived truncated with stop_reason=max_tokens, JSON.parse threw Unterminated string, aiDiagnose returned null, and the caller degraded to rule-based heuristics.

The failure was invisible: it looked like "AI is unavailable" rather than "the budget is too small". It affected the pg-replication agent, the kafka agent and bundle ingest, regardless of whether an API key was set. ai-explainer and root-cause-synthesis were failing the same way — root-cause-synthesis also parsed with a bare .trim() and no stripCodeFence, so 2 of 3 live calls threw on a ```json fence.

Budgets are now set per call site with measured headroom rather than one global number: 60s/6144 for the non-interactive toolkit, 30s/4096 for the two foreground paths where a fast fallback beats a long hang. All 7 callClaude call sites audited. 18 hermetic regression tests added, 8 verified to fail against the old constants, no live API calls in tests.

1. Documentation

README 477 → 258 lines, with depth moved into docs/cli-reference.md and docs/coverage.md. The three overlapping onboarding docs now have one home per audience.

Claims that contradicted the codebase were corrected — most notably the agent table listed six agents as "Live (execute-capable)" when their manifests say simulator_only, so the README was overstating what the tool itself reports. Also fixed: both agent guides told readers to use invalid PluginMaturity values, playbook-authoring.md's flagship example failed validation, and the agent count was 19 vs 26 actual.

2. Demo re-recorded against live AI

Recording previously ran under env -u ANTHROPIC_API_KEY, so it showed the rule-based fallback's fixed 92%/three-finding output. It now runs the default model with no CRISISMODE_AI_MODEL override, showing what a user actually gets: replication_lag_cascade at 78% with a model-authored root cause.

Honesty trade-offs are documented in the script rather than hidden: the cast is no longer byte-reproducible, and pace-cast.mjs caps idle gaps at 4s so the real 28–56s model wait doesn't ship as a frozen frame. The page discloses that playback is pace-adjusted.

3. Site and Cloudflare bundle

Plays the real cast inline via vendored asciinema-player v3.17.0, GIF as fallback only. scripts/package-site.sh emits output/crisismode-site.zipindex.html at the zip root, no .DS_Store, byte-identical across runs. Removes site/index.html.zip, a stale artifact whose upload would have deployed a page with four 404ing assets.

The GIF teaser was 2.8MB against a 1MB pre-commit ceiling; re-anchored to Phase 6 at 2fps for 905KiB, keeping the model's root-cause paragraph. Total bundle 990KiB.

Verification

  • typecheck clean, lint clean, 3192 passed / 24 skipped across 227 files
  • Cast confirmed AI-produced: zero hits for rule-based, Set ANTHROPIC_API_KEY, timed out, could not be parsed
  • Site verified by real browser render (headless Chromium against a local server): all assets 200, player paints, zero console errors, fallback swaps correctly — repeated against the extracted zip
  • Bundle reproducibility confirmed by identical SHA-256 across two runs

Known and deliberately left

ai-diagnosis-universal.ts:126 and ask.ts:218 remain at 1024 tokens. Neither parses its response, so truncation there is a visibly cut-off answer rather than a silent fallback.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added comprehensive CLI reference, coverage guidance, and expanded setup documentation.
    • Added prebuilt-binary quick-start workflows, including service detection, diagnosis, recovery, AWS, webhooks, and script-friendly output.
    • Added a recorded PostgreSQL recovery demo to the website.
  • Bug Fixes
    • Improved diagnosis commands for service targets and plugins.
    • Improved handling of longer, delayed, fenced, truncated, and malformed AI responses.
    • Standardized plugin scaffolding around --plugin, while retaining deprecated compatibility.
  • Documentation
    • Clarified safety defaults, validation maturity, supported environments, integrations, and recovery workflows.

trs-80 and others added 6 commits August 9, 2026 11:59
aiDiagnose ran at max_tokens=1024 with a 10s timeout. No current model can
return a complete structured diagnosis in that budget: nine live sonnet-5
runs against the real pg-replication prompt came back at 2259-3742 output
tokens and 28-40s. Every response arrived truncated with
stop_reason=max_tokens, JSON.parse threw "Unterminated string", aiDiagnose
returned null, and the caller degraded to its rule-based heuristics.

The failure was invisible: it looked like "AI is unavailable" rather than
"the budget is too small". It affected the pg-replication agent, the kafka
agent, and `bundle ingest` — regardless of whether an API key was set.

- ai-diagnosis.ts: 60s / 6144 tokens (above the measured 3742 ceiling, since
  4096 leaves ~9% headroom and one wordier finding list would truncate again)
- ai-client.ts fallbacks: 60s / 4096
- ai-explainer.ts: 4096 / 30s, measured 1407-1484 tokens at 15.2-16.4s
- root-cause-synthesis.ts: 4096 / 30s, measured 749-1522 tokens at 8.8-17.7s
  Both are foreground paths with an operator waiting, so they take 30s rather
  than 60s: a fast fallback beats a long hang. Both were falling back before.
- root-cause-synthesis.ts also parsed with a bare .trim() and no
  stripCodeFence, so 2 of 3 live calls threw on a ```json fence — a failure
  mode independent of the token budget.
- parseStandardDiagnosisResponse now repairs raw control characters inside
  JSON string literals and retries once, after a live response lost its
  diagnosis to "Bad control character in string literal at position 1741".
- Corrected 8 stale "10s / 1024" claims, including a spec reference to
  claude-sonnet-4-20250514, retired 2026-06-15.

All 7 callClaude call sites audited; each passes both values explicitly, so
no interactive path regressed. 18 hermetic regression tests added, 8 of them
verified to fail against the old constants. No live API calls in tests.

Still at 1024: ai-diagnosis-universal.ts:126 and ask.ts:218. Neither parses
its response, so truncation there is a visibly cut-off answer rather than a
silent fallback.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two copy-pasteable dead ends in scan output.

scan.ts pointed at `crisismode init --plugin my-check`, but init only parses
--agent, so the suggested command errors out.

incident-summary.ts suggested `crisismode diagnose <finding-id>`. diagnose
reads positionals[0] as a *target* name, and only PLUG-<n> is routed to a
plugin's diagnose verb — so the hint worked for plugin findings and failed
with `Target "PG-001" not found in config` for every agent finding.
diagnoseCommandFor now derives the addressable target from the trailing
parenthesized group scan builds into `service`, keeps the raw id for PLUG-<n>,
and falls back to the bare command rather than emitting something broken.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
README was 477 lines and doubled as a manual, while QUICKSTART (205) and
GETTING_STARTED (263) largely repeated it — three competing onboarding paths.

- README down to 258 lines; depth moved into docs/cli-reference.md and
  docs/coverage.md
- one home per audience: QUICKSTART = using the CLI, GETTING_STARTED =
  developing on it, CONTRIBUTING = contributing (its duplicated setup section
  now links out)

Corrected claims that contradicted the codebase:

- the agent table listed Redis, AWS, deploy-rollback, db-migration,
  queue-backlog and config-drift as "Live (execute-capable)" when their
  manifests say simulator_only — the README was overstating what the tool
  itself reports. Now uses the real two-label vocabulary and the verified
  9-of-26 live-validated count.
- both agent guides told readers to set plugin.maturity to "beta"/"stable";
  neither is a valid PluginMaturity
- playbook-authoring.md's flagship example failed validation until a missing
  capability id was added
- GETTING_STARTED claimed 19 agents against 26 actual

Every command and flag verified against a fresh build; no broken internal
links or anchors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
record-demo.sh ran under `env -u ANTHROPIC_API_KEY` so the capture was
offline and deterministic. That also meant the recording showed the
rule-based fallback — a fixed 92% confidence with three findings and no root
cause — rather than the product's actual AI path.

Recording now runs with the live key against the default model (sonnet-5,
no CRISISMODE_AI_MODEL override) so it shows what a user gets out of the box:
replication_lag_cascade at 78% with a model-authored root-cause paragraph and
six recommendations. 56.2s, 314 events.

The trade-off is stated in the script header: the recording makes a live API
call and is no longer byte-reproducible. pace-cast.mjs now caps idle gaps at
4s so the real 28-56s model wait does not ship as a frozen frame — the cast
was already re-timed for readability rather than real-time, and the header
documents the cap.

The GIF teaser is generated by the same script but not committed here; it
exceeds the repo's 1MB pre-commit ceiling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- vendors asciinema-player v3.17.0 and plays site/assets/demo.cast inline,
  with the GIF kept only as a fallback (it is not fetched when the player
  works). Closes a hole where JS-on-but-player-missing rendered an empty box
  with no GIF at all.
- rewrites the demo alt text. It claimed 92% confidence, which is the
  rule-based fallback's fixed figure; the live-AI recording shows
  replication_lag_cascade at 78% with a primary-side WAL-generation root
  cause. It now also says the GIF is only the opening excerpt.
- discloses that playback is pace-adjusted and the real AI call took ~30-60s,
  rather than letting the capped idle gaps imply the tool is faster than it is
- player options re-checked against the new cast (314 events, 56.24s, max idle
  gap 4.0s; poster npt:0:03 still lands on the title card)

scripts/package-site.sh emits output/crisismode-site.zip for Cloudflare Pages
direct upload: index.html at the zip root plus blog/ and assets/, no .DS_Store,
no resource forks, byte-identical across runs.

Removes site/index.html.zip: committed 2026-07-20, 21KB, a stale index.html
predating the player and no assets/ at all — uploading it to Pages would
deploy a page whose player JS, CSS, cast and GIF all 404.

Verified by real browser render (headless Chromium against a local server):
all assets 200, player paints with the 78% diagnosis and plan table visible,
zero console errors, fallback swaps in correctly — repeated against the
extracted zip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The teaser rendered at 2.8MB, which the pre-commit hook rejects. Preserving
the old Phase 7 cut was not an option: rendering through the plan table costs
1.3MB even at font-size 10 and 4fps, which is past legible.

Re-anchors the cut on the Phase 6 heading and drops to 2fps: 905KiB, and it
still contains the part worth recording live — the model's own root-cause
paragraph at 78% confidence. The anchor stays content-based rather than a
fixed timestamp, so a wordier AI response cannot silently truncate it.

The size guard now fails the build at 1MB instead of warning at 3MB, so this
regresses loudly rather than at commit time. Alt text updated: the excerpt now
ends as plan creation begins, not at plan validation.

Cloudflare bundle drops from 2.8MB to 990KiB total.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@trs-80, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 3 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7c5a60bb-1b31-4c24-acdb-f1b56639c569

📥 Commits

Reviewing files that changed from the base of the PR and between f00cf66 and 2197e6f.

📒 Files selected for processing (7)
  • .cbmignore
  • docs/playbook-authoring.md
  • playbooks/examples/redis-memory-pressure.md
  • src/__tests__/ask-machine-output.test.ts
  • src/__tests__/init-plugin-flag.test.ts
  • src/cli/commands/ask.ts
  • src/cli/commands/init.ts
📝 Walkthrough

Walkthrough

The change restructures project documentation, adds CLI and coverage references, updates AI response budgets and parsing, corrects diagnosis guidance, and adds scripts and assets for recording, packaging, and playing the recovery demo.

Changes

Documentation and runtime updates

Layer / File(s) Summary
Developer setup and contribution guidance
CONTRIBUTING.md, GETTING_STARTED.md, docs/guides/*, docs/agents/*
Consolidates setup instructions and defines recovery-agent maturity declarations, validation evidence, hooks, commands, and further reading.
Operator documentation and validation references
README.md, QUICKSTART.md, docs/cli-reference.md, docs/architecture.md, docs/coverage.md, docs/guide-verification/*
Documents binary installation, scanning, diagnosis, readiness, recovery, output modes, integrations, maturity, validation, and implementation limits.
AI response budgets and parsing
specs/architecture/*, src/framework/*, src/cli/commands/ask.ts, src/__tests__/ai-*.test.ts
Increases call-site token and timeout budgets, exposes stop reasons, repairs malformed diagnosis JSON, strips synthesis code fences, and adds regression coverage.
CLI diagnosis and plugin workflows
src/cli/*, src/__tests__/incident-summary*.test.ts, src/__tests__/init-plugin-flag.test.ts
Generates diagnosis commands from plugin IDs or service targets and makes --plugin canonical while retaining --agent as a deprecated alias.
Demo recording, packaging, and playback
scripts/*, site/assets/*, site/index.html
Adds recording, pacing, packaging, Asciinema playback, fallback GIF handling, terminal styling, and a PostgreSQL recovery recording.
Playbook safety guidance
docs/playbook-authoring.md, playbooks/examples/redis-memory-pressure.md
Documents registered capabilities and changes Redis cleanup to bounded passive expiration with updated rollback and risk guidance.

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

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main documentation, live-AI demo, Cloudflare bundle, and AI fallback changes.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/repo-presentation

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.

@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.54545% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.16%. Comparing base (e029352) to head (2197e6f).

Files with missing lines Patch % Lines
src/cli/commands/ask.ts 90.00% 3 Missing and 1 partial ⚠️
src/cli/index.ts 0.00% 1 Missing ⚠️
src/framework/ai-client.ts 83.33% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #113      +/-   ##
==========================================
+ Coverage   68.79%   70.16%   +1.37%     
==========================================
  Files         278      278              
  Lines       14856    14932      +76     
  Branches     4468     4491      +23     
==========================================
+ Hits        10220    10477     +257     
+ Misses       3007     2837     -170     
+ Partials     1629     1618      -11     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@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: 12

🧹 Nitpick comments (3)
site/assets/asciinema-player.css (1)

1-2: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exclude this vendored file from Stylelint and record its provenance.

This file is the upstream asciinema-player distribution CSS. Stylelint reports three violations in it: word-wrap is deprecated at line 120, and Menlo and Consolas use non-lowercase keywords at line 2. Do not edit them. Any edit is lost on the next vendor update and diverges from upstream. Add the path to .stylelintignore instead.

The file also carries no version or license marker, unlike the other files in this PR, which use SPDX headers. Add a short comment that names the upstream project, the pinned version, and the license, so the vendored copy can be refreshed and attributed.

♻️ Proposed provenance header
+/*
+ * Vendored from asciinema-player v<VERSION> — https://github.com/asciinema/asciinema-player
+ * Licensed under the Apache License 2.0. Do not edit; refresh from upstream.
+ */
 .ap-default-term-ff {
🤖 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 `@site/assets/asciinema-player.css` around lines 1 - 2, Add
site/assets/asciinema-player.css to .stylelintignore without modifying its
vendored CSS content, and add a concise provenance comment identifying
asciinema-player, the pinned upstream version, and its license.

Source: Linters/SAST tools

scripts/package-site.sh (1)

92-109: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Capture the zip listing once and reuse it.

The script runs unzip -Z1 "$ZIP" four times. Each run pipes into grep -q or wc. With set -o pipefail, grep -q can close the pipe after the first match and leave unzip killed by SIGPIPE, which makes the pipeline status non-zero. At line 93 that turns a correct archive into a false "index.html is not at the zip root" failure. At line 97 it can hide a real leak, because the if condition then evaluates false. The output is small today, so the pipe buffer usually absorbs it, but a single capture removes the interaction and the repeated work.

♻️ Proposed refactor
+ENTRIES="$(unzip -Z1 "$ZIP")"
+
 # Assert the layout Pages needs rather than asking the reader to eyeball it.
-unzip -Z1 "$ZIP" | grep -qx 'index.html' || {
+printf '%s\n' "$ENTRIES" | grep -qx 'index.html' || {
   echo "error: index.html is not at the zip root; Pages would 404 on /." >&2
   exit 1
 }
-if unzip -Z1 "$ZIP" | grep -qE '(^|/)(\.DS_Store|\._|__MACOSX/)|\.zip$'; then
+if printf '%s\n' "$ENTRIES" | grep -qE '(^|/)(\.DS_Store|\._|__MACOSX/)|\.zip$'; then
   echo "error: excluded files leaked into the archive:" >&2
-  unzip -Z1 "$ZIP" | grep -E '(^|/)(\.DS_Store|\._|__MACOSX/)|\.zip$' >&2
+  printf '%s\n' "$ENTRIES" | grep -E '(^|/)(\.DS_Store|\._|__MACOSX/)|\.zip$' >&2
   exit 1
 fi
@@
-echo "      $((ZIP_BYTES / 1024)) KiB ($ZIP_BYTES bytes), $(unzip -Z1 "$ZIP" | wc -l | tr -d ' ') entries"
+echo "      $((ZIP_BYTES / 1024)) KiB ($ZIP_BYTES bytes), $(printf '%s\n' "$ENTRIES" | wc -l | tr -d ' ') entries"
🤖 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 `@scripts/package-site.sh` around lines 92 - 109, Capture the output of unzip
-Z1 "$ZIP" once in a listing variable before the validation checks, then reuse
that captured listing for the index.html assertion, excluded-file detection and
diagnostic output, while preserving the existing patterns and archive summary
behavior.
site/index.html (1)

933-940: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Provide a text alternative when the player runs.

The GIF alt text at line 938 is the only description of the recording, and it lives inside <noscript>. When JavaScript is enabled, the browser never exposes that text, and the player mount at line 933 has no accessible name. Assistive technology then presents an unlabeled region. Add a label on the mount, or add a visually hidden summary next to it, so the recording is described in both paths.

♻️ Proposed change
-        <div class="terminal-body terminal-player" id="terminal-player"></div>
+        <div class="terminal-body terminal-player" id="terminal-player"
+             role="group" aria-label="Terminal recording of the crisismode demo: a PostgreSQL replication-lag cascade, diagnosed live and recovered through a 10-step plan with a human approval gate."></div>
🤖 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 `@site/index.html` around lines 933 - 940, Add an accessible name or visually
hidden text description to the terminal player mount identified by
`id="terminal-player"`, ensuring the recording is described when JavaScript runs
while preserving the existing `<noscript>` fallback and its GIF alt text.
🤖 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 `@docs/guide-verification/2026-08-09-docs-pass-report.md`:
- Around line 114-117: Update the “Flags” statement in the report to avoid
claiming that only --json, --target, and --agent appear across all
documentation. Either list every documented flag, including those in
docs/cli-reference.md, or explicitly limit the sentence to newly added flags.

In `@docs/guides/your-first-agent.md`:
- Around line 772-777: Update the maturity-label guidance around plugin.maturity
to distinguish diagnosis validation from execute-verified recovery: allow a real
deployment with validated diagnosis to use "live_validated" without requiring a
mutating recovery, reserve "dry_run_only" for diagnosis validated only through
dry runs, and retain "simulator_only" until live diagnosis is confirmed.

In `@docs/playbook-authoring.md`:
- Around line 254-260: The “Evict expired keys” playbook entry must not describe
arbitrary deletion as TTL-based eviction. Update the command and surrounding
metadata to use a bounded, expiry-aware operation that only targets expired
keys, or revise the title, capability, risk, preservation, and success criteria
to accurately describe deleting arbitrary keys.

In `@GETTING_STARTED.md`:
- Around line 176-184: Resolve MD040 by adding the text language identifier to
every specified fenced block: GETTING_STARTED.md:176-184, QUICKSTART.md:40-58,
README.md:58-66 and 91-115, and docs/cli-reference.md:107-109, 114-116, and
130-133. Preserve all existing project-layout, scan-output, recovery-transcript,
and row contents.
- Around line 18-19: Update the pnpm setup instruction in GETTING_STARTED.md to
install the version declared by the packageManager field in package.json, such
as by enabling Corepack or explicitly pinning pnpm@10.30.3, so local setup
matches CI.

In `@scripts/pace-cast.mjs`:
- Around line 144-161: Ensure the verification failure path in the pace-cast
output flow removes the already-written outPath before exiting, so a rejected
cast cannot overwrite the committed asset; keep successful output behavior
unchanged.

In `@scripts/record-demo.sh`:
- Line 30: Validate the DECISION argument in scripts/record-demo.sh immediately
after its default assignment, allowing only approve, skip, or reject as
documented. Reject any other value with an error and exit before recording or
invoking the driver here-document, while preserving the existing behavior for
valid values.
- Around line 68-69: Update the temporary-path setup in the demo recording
script to create a temporary directory, then define DRIVER as an .exp file
inside that directory. Keep the existing cleanup trap, but remove the directory
recursively so both the mktemp-created location and the generated driver file
are cleaned up atomically.
- Around line 170-176: Update the agg installation hint in the script’s
tool-check logic to require version 1.9.0 or newer, matching the --select usage
in the GIF generation command. Keep the existing missing-tool validation
behavior unchanged.

In `@site/assets/demo.cast`:
- Around line 33-37: Update getCatalogEntry, matchCatalog, and isCatalogCovered
in the catalog flow so entries whose expiresAt is earlier than the demo/current
trigger time are treated as out of date and do not report catalog coverage;
include the expiry validation in isCatalogCovered output, then re-record
site/assets/demo.cast with the corrected result.

In `@site/index.html`:
- Around line 1423-1445: Update the initialization flow around
AsciinemaPlayer.create to fetch and parse assets/demo.cast before constructing
the player, calling showTerminalFallback() when fetching or parsing fails. Pass
the successfully loaded cast data into AsciinemaPlayer.create, while retaining
its existing try/catch to handle synchronous player-construction failures.

In `@src/__tests__/ai-response-budgets.test.ts`:
- Around line 167-173: Add a valid statePreservation.before capture to the
elevated-risk system_action fixture identified by stepId step-004, preserving
the existing action fields and satisfying the recovery-plan safety contract.

---

Nitpick comments:
In `@scripts/package-site.sh`:
- Around line 92-109: Capture the output of unzip -Z1 "$ZIP" once in a listing
variable before the validation checks, then reuse that captured listing for the
index.html assertion, excluded-file detection and diagnostic output, while
preserving the existing patterns and archive summary behavior.

In `@site/assets/asciinema-player.css`:
- Around line 1-2: Add site/assets/asciinema-player.css to .stylelintignore
without modifying its vendored CSS content, and add a concise provenance comment
identifying asciinema-player, the pinned upstream version, and its license.

In `@site/index.html`:
- Around line 933-940: Add an accessible name or visually hidden text
description to the terminal player mount identified by `id="terminal-player"`,
ensuring the recording is described when JavaScript runs while preserving the
existing `<noscript>` fallback and its GIF alt text.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ac8e5c1d-850e-45d9-94e0-ea2d68548436

📥 Commits

Reviewing files that changed from the base of the PR and between e029352 and 2734759.

⛔ Files ignored due to path filters (3)
  • site/assets/asciinema-player.min.js is excluded by !**/*.min.js
  • site/assets/demo.gif is excluded by !**/*.gif
  • site/index.html.zip is excluded by !**/*.zip
📒 Files selected for processing (32)
  • CONTRIBUTING.md
  • GETTING_STARTED.md
  • QUICKSTART.md
  • README.md
  • docs/architecture.md
  • docs/cli-reference.md
  • docs/coverage.md
  • docs/guide-verification/2026-08-09-docs-pass-report.md
  • docs/guides/creating-a-recovery-agent.md
  • docs/guides/your-first-agent.md
  • docs/playbook-authoring.md
  • scripts/pace-cast.mjs
  • scripts/package-site.sh
  • scripts/record-demo.sh
  • site/assets/asciinema-player.css
  • site/assets/demo.cast
  • site/index.html
  • specs/architecture/operator-health-and-ai-services.md
  • src/__tests__/ai-client.test.ts
  • src/__tests__/ai-diagnosis-token-budget.test.ts
  • src/__tests__/ai-response-budgets.test.ts
  • src/__tests__/ai-summary.test.ts
  • src/__tests__/incident-summary.test.ts
  • src/agent/kafka/ai-diagnosis.ts
  • src/agent/pg-replication/ai-diagnosis.ts
  • src/cli/commands/scan.ts
  • src/cli/incident-summary.ts
  • src/framework/ai-client.ts
  • src/framework/ai-diagnosis.ts
  • src/framework/ai-explainer.ts
  • src/framework/evidence-bundle-respond.ts
  • src/framework/root-cause-synthesis.ts

Comment thread docs/guide-verification/2026-08-09-docs-pass-report.md Outdated
Comment thread docs/guides/your-first-agent.md Outdated
Comment thread docs/playbook-authoring.md Outdated
Comment thread GETTING_STARTED.md Outdated
Comment thread GETTING_STARTED.md Outdated
Comment thread scripts/record-demo.sh Outdated
Comment thread scripts/record-demo.sh
Comment thread site/assets/demo.cast
Comment on lines +33 to +37
[0.1,"o","\u001b[2m Approved by: jane.chen@example.com\u001b[22m\r\r\n"]
[0.1,"o","\u001b[2m Expires: 2026-05-15T10:00:00Z\u001b[22m\r\r\n"]
[0.1,"o","\u001b[2m Covers: routine, elevated risk levels\u001b[22m\r\r\n"]
[0.05,"o","\r\r\n"]
[0.16,"o","\u001b[32m ✓ Catalog entry found for this scenario\u001b[39m\r\r\n"]

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Locate the demo catalog fixture and the catalog expiry check.
rg -n -C5 -e 'pg-replication-standard-recovery' -e '2026-05-15' -e 'jane.chen' --glob '!site/assets/*'
rg -n -C6 -e 'expiresAt' -e 'expires' -e 'isExpired' --glob '*.ts' -g '!**/*.test.ts' | rg -n -i -C6 'catalog'

Repository: trs-80/crisismode

Length of output: 155


🏁 Script executed:

#!/bin/bash
set -u

echo "== files =="
git ls-files | sed -n '1,200p'

echo "== locate cast and keywords =="
rg -n -C4 -e 'Approved by:|Expires:|Covers:|Catalog entry found|Catalog entry.*matched|jane.chen@example.com|2026-05-15|2026-08-09|pg-replication-standard-recovery' . || true

echo "== cast sections =="
if [ -f site/assets/demo.cast ]; then
  nl -ba site/assets/demo.cast | sed -n '1,80p'
  echo "---"
  nl -ba site/assets/demo.cast | sed -n '140,190p'
fi

Repository: trs-80/crisismode

Length of output: 22746


🏁 Script executed:

#!/bin/bash
set -u

echo "== src/framework/catalog.ts =="
cat -n src/framework/catalog.ts | sed -n '1,190p'

echo "== src/demo/runner.ts relevant =="
cat -n src/demo/runner.ts | sed -n '1,90p'

echo "== src/framework/display.ts relevant =="
cat -n site/assets/demo.cast | sed -n '160,176p'

echo "== cast phase 2/3 lines =="
cat -n site/assets/demo.cast | sed -n '14,38p'

echo "== find all shouldRequireApproval calls =="
rg -n -C4 'shouldRequireApproval|catalogCovers' src --glob '*.ts'

Repository: trs-80/crisismode

Length of output: 13838


Treat expired catalog entries as out of date.

The demo publishes jane.chen@example.com, which is acceptable. The demo-trigger date is 2026-08-09T15:37:18.237Z, but getCatalogEntry() still has expiresAt: 2026-05-15T10:00:00Z; matchCatalog() then ignores the expiry and reports a match. Fix the expiresAt handling in src/framework/catalog.ts and include this check in isCatalogCovered() output, then re-record the cast.

🤖 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 `@site/assets/demo.cast` around lines 33 - 37, Update getCatalogEntry,
matchCatalog, and isCatalogCovered in the catalog flow so entries whose
expiresAt is earlier than the demo/current trigger time are treated as out of
date and do not report catalog coverage; include the expiry validation in
isCatalogCovered output, then re-record site/assets/demo.cast with the corrected
result.

Comment thread site/index.html
Comment on lines +1423 to +1445
let player;
try {
player = AsciinemaPlayer.create('assets/demo.cast', terminalMount, {
theme: 'crisismode',
fit: 'width',
loop: true,
// No idleTimeLimit: scripts/pace-cast.mjs assigns every gap and
// clamps the longest to 4s, so there is nothing left to trim.
// Verified against the current cast: 314 events, 56.2s, max gap 4.0s.
// Trimming here would also cut the deliberate pause on the approval
// gate, which is the point of that frame.
poster: 'npt:0:03',
controls: 'auto',
});
} catch {
// A broken vendored player should degrade to the GIF, not an empty box.
showTerminalFallback();
return;
}

// Autoplay is muted-equivalent (no audio), so browsers allow it. If a
// policy blocks it the controls remain, so this stays non-fatal.
Promise.resolve(player.play()).catch(() => {});

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

🧩 Analysis chain

🌐 Web query:

asciinema-player AsciinemaPlayer.create inline cast source object option

💡 Result:

In the asciinema player library, the AsciinemaPlayer.create function is used to initialize and mount the player into the DOM [1][2][3]. While the basic usage involves passing a URL string as the src argument [1][2][4], you can also pass an object to the src argument to provide the recording data directly (inline) or to customize how the data is fetched [5][6]. When using an object for the src argument, the following structure is supported: { data: data } This is used to provide the recording content directly [5][6]. The data value can be a string (v1, v2, or v3 format), an array (v2 or v3 format), an object (v1 format), or a function that returns the recording data (optionally asynchronously) [5][6]. Example: AsciinemaPlayer.create( { data: '{"version": 2, "width": 80, "height": 24}\n[1.0, "o", "hello"]' }, document.getElementById('demo')); { url: url, parser: parser } This allows you to specify a custom parser or configuration when loading from a URL [5][6]. { url: url, fetch: {... } } This allows you to customize the HTTP fetch request (e.g., adding headers or credentials) when loading from a URL [6]. The third argument to AsciinemaPlayer.create, opts, remains a separate configuration object used to control player behavior such as speed, theme, or loop settings, and is distinct from the src source object [1][7][2].

Citations:


🏁 Script executed:

<analysis>
- Initial web search supports that AsciinemaPlayer.create accepts an object source with `{ data: cast }`.
- Need inspect repository site/index.html and related scripts to verify exact context and fallback behavior.
</analysis>
[task verify_review_comment_1/1]
[observations]
- Original concern: AsciinemaPlayer.create('assets/demo.cast', ...) uses async fetch; catch around create does not cover fetch failure.
- Need verify vendored player version/usage and fallback wiring.
[pending]
- Inspect site/index.html around terminal fallback and AsciinemaPlayer script.
- Inspect scripts/demo/package? scripts for vendored player.
- Run deterministic source parse/check for Try/Catch scope around AsciinemaPlayer.create and any global error handling or player.on events.
[actions]
- Use rg/fd/sed to locate relevant files and code.
[done]
- Initial web results incorporated.
[scratchpad_end] -->
#!/bin/bash
set -euo pipefail

printf 'Files matching asciinema/player related:\n'
git ls-files | rg -i 'asciinema|player|package|demo|site/index\.html' || true

printf '\nRelevant index.html lines 1380-1455:\n'
sed -n '1380,1455p' site/index.html | cat -n -v

printf '\nOccurrences of showTerminalFallback and terminalMount:\n'
rg -n "showTerminalFallback|terminalMount|AsciinemaPlayer|create\(" site/index.html scripts site 2>/dev/null || true

Repository: trs-80/crisismode

Length of output: 241


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching asciinema/player related:\n'
git ls-files | rg -i 'asciinema|player|package|demo|site/index\.html' || true

printf '\nRelevant index.html lines 1380-1455:\n'
sed -n '1380,1455p' site/index.html | cat -n -v

printf '\nOccurrences of showTerminalFallback and terminalMount:\n'
rg -n "showTerminalFallback|terminalMount|AsciinemaPlayer|create\(" site/index.html scripts site 2>/dev/null || true

Repository: trs-80/crisismode

Length of output: 50374


Show the GIF when the cast cannot load.

AsciinemaPlayer.create('assets/demo.cast', ...) returns synchronously and loads the cast asynchronously, so the try/catch around construction does not handle missing or failed casts. Fetch the cast before creating the player and call showTerminalFallback() for fetch/parse failures, keeping the catch for synchronous player failures.

🤖 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 `@site/index.html` around lines 1423 - 1445, Update the initialization flow
around AsciinemaPlayer.create to fetch and parse assets/demo.cast before
constructing the player, calling showTerminalFallback() when fetching or parsing
fails. Pass the successfully loaded cast data into AsciinemaPlayer.create, while
retaining its existing try/catch to handle synchronous player-construction
failures.

Comment thread src/__tests__/ai-response-budgets.test.ts
trs-80 and others added 5 commits August 9, 2026 12:50
The two call sites left at max_tokens=1024 were measured across 39 live
sonnet-5 calls and set explicitly:

- ai-diagnosis-universal.ts: 4096 / 45s. Measured 1076-1891 tokens and
  10.1-23.4s across the three prompt shapes it builds, so 1024 truncated
  every shape including the simplest question, and the old 15s timeout was
  below the widest shape's 15.4-23.4s.
- ask.ts REPL: 6144 / 45s. Measured 543-3638 tokens and 6.7-37.1s; only the
  first turn ever fit in 1024, and the old 30s was below a long runbook
  session's 29.0-37.1s.

Truncation was also undetectable. callClaude returned only concatenated text
and discarded stop_reason, so a cut-off answer was indistinguishable from a
finished one. A text heuristic is not good enough: in 24 forced-truncation
trials, 2 of 20 truncated answers ended on a period and read as complete.

Adds a strictly additive callClaudeDetailed() returning a typed
{text, stopReason}; callClaude delegates to it, so no existing signature or
behaviour changes. `truncated` is a required field on both result types, and
both ask modes now say plainly that an answer was cut off — verified at the
real CLI, not only in tests.

Also fixes the hermeticity break this new export caused.
ai-timeout-reporting.test.ts mocked ai-client with `{...actual, callClaude}`,
so callClaudeDetailed fell through to a real Anthropic client and the suite
made live authenticated calls (genuine 401s with fresh request_ids). Mocking
@anthropic-ai/sdk itself means no future export can reach the network,
whatever the module mock spreads. Confirmed by re-breaking the spread mock:
2 loud failures and 0 network attempts, against 6 live calls before.

New tests mutation-tested: reverting the fix fails 9 of 17.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`init --agent <name>` scaffolds a CHECK PLUGIN, not an agent. In a codebase
where "agent" is a distinct core concept (RecoveryAgent, src/agent/*,
AgentManifest), the flag actively misled — the help text even read
`init --agent <name>   Scaffold a check plugin`, contradicting itself.

`--plugin` is now canonical. `--agent` still works as a deprecated alias with
a one-line stderr notice, and `--plugin` wins if both are passed, so no
existing user breaks. Help text, the scan hint and all three completion
scripts now agree with the real surface.

Two latent bugs in the same path, found while fixing it:

- a valueless `--plugin`/`--agent` crashed with a raw internal error; it is
  now a usage error exiting 1
- a name like `../../evil` scaffolded OUTSIDE checks/ — path traversal via an
  unvalidated directory name. Names are now restricted to letters, digits,
  ".", "_" and "-", exiting 1 on anything else.

Also drops `init --agent` from the `--local` documentation row, since init
never honored `--local`.

Verified at the real CLI after a build: traversal exits 1, valueless exits 1,
the deprecated alias exits 0 and warns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov flagged 9 uncovered lines in the token/timeout fix, concentrated in
the new error-recovery code — exactly the paths that must not rot silently.

Coverage across the five affected files goes from 85.93% to 99.74% lines and
78.49% to 98.56% branches, prioritising this PR's new behaviour over raw
percentage: escapeControlCharsInStrings escape-tracking through \" and \\,
the \uXXXX fallback, and the retry-once contract proved by spying on
JSON.parse (1 call when already valid, 1 when the repair is a no-op and
rethrows, 2 when the repair runs and must still throw).

Two assumptions in the brief were wrong and are corrected here: diagnoseCommandFor
and root-cause-synthesis's stripCodeFence were already covered, and ai-client's
110/136 are ternary arms rather than the abort path, which was also already
covered. The 9 missing incident-summary lines were all printIncidentSummary.
Four lines stay uncovered deliberately — two are genuinely unreachable, two are
pre-existing cosmetic string arms not worth contorting a fixture for.

Separately, the elevated-risk fixture in ai-response-budgets.test.ts violated
this repo's own safety contract. It could not even be validated (TypeError in
validator.ts at the missing requiredCapabilities), and once runnable it failed
TWO checks: state preservation AND human notification for elevated+ plans. A
fixture that breaks the safety rules teaches the wrong shape to anyone who
copies it.

Swept all 11 test files in scope: 2 plan fixtures, 3 violations total, all
fixed, plus a `command: 'shell'` that is not a member of the Command union. No
deliberately-invalid negative tests were touched. Adds a mechanical test that
runs every plan fixture through the real validator, so future validator rules
apply automatically instead of waiting for a reviewer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four CodeRabbit findings on the demo tooling, each reproduced before fixing.

pace-cast.mjs wrote outPath and only then verified that output bytes matched
input. record-demo.sh passes site/assets/demo.cast as outPath, so a failed
check left the rejected cast in place — having already overwritten the
committed asset — while printing "refusing to ship". Reproduced by forcing a
byte mismatch and watching the committed file's hash change. It now writes a
sibling temp and renames only after the check passes, with cleanup wired to
exit, SIGINT, SIGTERM and SIGHUP; verified across success, forced failure,
SIGTERM, SIGINT and an uncaught throw with zero leftovers.

record-demo.sh leaked a temp file per run: `mktemp -t ...).exp` appends `.exp`
to the path mktemp already created, so `cat` wrote to a different file and the
trap only removed the second one. Eight leaked zero-byte files were sitting in
$TMPDIR from earlier runs. Now uses mktemp -d with a recursive trap.

DECISION was documented as [approve|skip|reject] but accepted any argument and
was interpolated into the expect here-doc as Tcl, so a typo burned a live API
call. It is now validated, exiting 2. Going further than the finding:
$REPO_ROOT was interpolated into Tcl too, and a checkout path containing
brackets provably breaks the driver with "invalid command name" — the here-doc
is now quoted and the path and decision pass through the environment.

Adds an agg >= 1.9.0 check, since --select is load-bearing for the GIF cut and
only existence was verified before.

No behavioural change to the recording: same content-anchored Phase 6 cut,
same 2fps and 1MB budget, and pace-cast still verifies output bytes equal
input. site/assets/demo.cast is byte-identical, confirmed by hash.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five review findings, all verified against the code before changing anything;
two were narrower than the underlying defect.

The maturity guidance required execute-verified recovery before a contributor
could use `live_validated`, but docs/coverage.md separates diagnosis maturity
from execute-verified recovery: a deployment that validated diagnosis can
legitimately be `live_validated`, and `dry_run_only` covers dry-run-validated
diagnosis. As written it pushed contributors to under-report. The finding named
your-first-agent.md, but the same rule was wrong in CONTRIBUTING.md — the
canonical location the guide links to — so both are fixed.

The playbook example was worse than mislabelled. A step carried the
`cache.expiry.trigger` capability at routine risk while the command scanned `*`
and unlinked the first 1000 keys regardless of TTL, so a contributor copying it
would delete live keys and cause a cache-miss storm. The replacement deletes
nothing itself: it relies on Redis passive expiration through a bounded
1000-key TTL touch, leaving live and persistent keys in place, and it validates
at 13 safety checks through the real CLI. The same example had a broken
three-backtick fence that rendered half of it as body text.

Also: corrects the flag-audit claim in the docs-pass report without rewriting
what was true when it was written, and labels 17 unlabelled code fences
(MD040) — 10 more than the review listed. MD040 is now zero across every file
outside docs/superpowers, and all links and anchors resolve.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@trs-80

trs-80 commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

Follow-up wave: review findings + coverage (5 commits)

Addresses all 12 CodeRabbit findings and the Codecov gap. Each finding was verified against the code before acting; none were refuted, and three turned out narrower than the underlying defect.

Two real bugs in the demo tooling, both reproduced first

  • pace-cast.mjs wrote its output before verifying byte equality, so a failed check left the rejected cast having already overwritten the committed asset — while printing "refusing to ship". Confirmed by forcing a mismatch and watching the hash change.
  • record-demo.sh leaked one temp file per run (mktemp -t ...).exp appends to a path mktemp already created). Eight leaked files were found in $TMPDIR from earlier runs.

Beyond the finding: $REPO_ROOT was interpolated into the expect here-doc as Tcl, and a checkout path containing brackets provably breaks the driver.

Path traversal in init--plugin ../../evil scaffolded outside checks/. Found while renaming the misleading --agent flag (it creates a check plugin, not an agent). --agent stays as a deprecated alias, so nothing breaks.

Test suite was making live API calls. A new callClaudeDetailed export fell through {...actual, callClaude} in an existing vi.mock, so the suite made real authenticated requests to api.anthropic.com — genuine 401s with fresh request_ids. Fixed by mocking @anthropic-ai/sdk itself, so no future export can reach the network regardless of what a module mock spreads. Confirmed by re-breaking the spread mock: 2 loud failures, 0 network attempts, against 6 live calls before.

A test fixture violated this repo's own safety contract — and couldn't even be validated (TypeError on missing requiredCapabilities). Once runnable it failed two checks, not the one reported. A mechanical test now runs every plan fixture through the real validator so future rules apply automatically.

Docs — the maturity rule wrongly required execute-verified recovery for live_validated, in CONTRIBUTING.md as well as the guide. A playbook example carried cache.expiry.trigger at routine risk while unlinking 1000 keys regardless of TTL; the replacement deletes nothing, relying on passive expiration via a bounded TTL touch, and validates at 13 safety checks.

Coverage 85.93% → 99.74% lines, 78.49% → 98.56% branches on the affected files, prioritising the new error-recovery paths. Four lines left uncovered deliberately and explained.

Known, not landed here

  • 4 pre-existing live-call sites remain in the suite, in files outside this change: ai-diagnosis-universal.test.ts (9), scan-run-best-effort (4), scan-config-collision (3). Found by a suite-wide network guard. Worth a dedicated PR.
  • Two informational review comments (the demo.cast catalog-entry expiry, and asciinema's inline src object option) were not acted on.
  • CodeRabbit reported "Review rate limited" on this push, so these 5 commits have not been re-reviewed.

typecheck clean, lint clean, 3288 passed / 24 skipped across 234 files.

@trs-80

trs-80 commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

The previous review was rate limited, so the last 5 commits (075a616..e904513) have not been reviewed. Please review them:

  • fix(ai): new callClaudeDetailed export, token/timeout budgets, truncation surfacing, and an @anthropic-ai/sdk mock replacing a spread module mock
  • fix(cli): init --plugin canonical with --agent as a deprecated alias, plus plugin-name validation against path traversal
  • test: coverage for the AI recovery paths and a mechanical plan-fixture validator test
  • fix(scripts): temp-file and write-ordering fixes in pace-cast.mjs and record-demo.sh
  • docs: maturity-rule correction and a replaced cache-eviction playbook example

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

@trs-80 I will review the five commits from 075a616 through e904513.

⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Both findings from a local CodeRabbit review of 075a616..e904513, which the
GitHub integration could not review because it was rate limited.

scan.ts reported "No check plugins found", but the count it checks is
healthPlugins — filtered at line 429 to plugins declaring the `health` verb.
A repo with only diagnose-only or plan-only plugins would be told nothing was
found while plugins were in fact discovered.

The docs-pass report also still recorded the scan hint as `init --agent
my-check`, contradicting its own superseding note four lines earlier that says
both now read `--plugin`.

Verified at the real CLI, not only in tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/cli-reference.md (2)

89-90: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Correct the down exit-code table.

Missing service configuration exits with code 0, not 2. Update the second row so down exits 0 when no services are configured and exit 2 only for parsing errors such as unrecognized flags, --config missing values, and invalid service arguments.

🤖 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 `@docs/cli-reference.md` around lines 89 - 90, The `down` exit-code row in the
CLI reference incorrectly assigns missing service configuration to exit code 2.
Update that row so no configured services exits 0, while exit code 2 is reserved
for parsing errors such as unrecognized flags, missing `--config` values, or
invalid service arguments.

61-62: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Add the missing per-command flags to this table.

docs/cli-reference.md says this is the complete flag set, but crisismode bundle also has --output <file>, and crisismode down uses --config <path> with exit code 2 for unrecognized flags. Add bundle --output and down --config to the appropriate command rows, or state that the table omits bundle-specific usage.

🤖 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 `@docs/cli-reference.md` around lines 61 - 62, Update the CLI flag table in
docs/cli-reference.md to accurately document the command-specific options: add
bundle --output <file> and down --config <path> to their respective command
rows, or revise the completeness statement to explicitly exclude bundle-specific
usage.
🧹 Nitpick comments (3)
src/__tests__/ai-diagnosis-response-recovery.test.ts (1)

96-104: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename withUnitSeparator to match the character used.

The fixture uses \u0001 (start of heading), not the unit separator (\u001F). The name and the surrounding prose disagree with the value, which can mislead a later reader who changes the fixture.

♻️ Proposed rename
-    const withUnitSeparator =
+    const withStartOfHeading =
       '{"status":"identified","scenario":"pipe\u0001delimited","confidence":0.5,"findings":[]}';
 
-    expect(() => JSON.parse(withUnitSeparator)).toThrow(/control character/i);
+    expect(() => JSON.parse(withStartOfHeading)).toThrow(/control character/i);
 
-    const result = parseStandardDiagnosisResponse(withUnitSeparator);
+    const result = parseStandardDiagnosisResponse(withStartOfHeading);
🤖 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 `@src/__tests__/ai-diagnosis-response-recovery.test.ts` around lines 96 - 104,
Rename the test fixture variable withUnitSeparator to reflect the \u0001
start-of-heading character it contains, and update its reference in the test; do
not change the fixture value or assertions.
src/__tests__/ai-universal-token-budget.test.ts (1)

205-212: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restore the console spy in afterEach.

consoleSpy.mockRestore() runs only on the success path. If an assertion above it fails, the spy stays installed for the rest of the file and suppresses console.error output from later tests. Add vi.restoreAllMocks() to afterEach, as ask-token-budget.test.ts already does.

♻️ Proposed change
   afterEach(() => {
     vi.useRealTimers();
+    vi.restoreAllMocks();
     if (originalApiKey !== undefined) {

Then drop the inline consoleSpy.mockRestore() calls at the end of the two timeout tests.

Also applies to: 241-253, 297-310

🤖 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 `@src/__tests__/ai-universal-token-budget.test.ts` around lines 205 - 212,
Update the afterEach cleanup in the test suite to call vi.restoreAllMocks() so
console spies are restored even when assertions fail, while preserving the
ANTHROPIC_API_KEY restoration. Remove the inline consoleSpy.mockRestore() calls
from both timeout tests, including the ranges identified in the comment.
src/framework/ai-client.ts (1)

83-98: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Keep the duplicate stop-reason union small, or align it with @anthropic-ai/sdk's StopReason.

@anthropic-ai/sdk@0.115.0 declares Message.stop_reason as StopReason | null, whose non-null values match the current ClaudeStopReason non-null union. There is no TypeScript error here, so this is only an optional cleanup if you want this module to derive Claude stop reasons from the SDK type.

🤖 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 `@src/framework/ai-client.ts` around lines 83 - 98, Optionally replace the
duplicated ClaudeStopReason union with the corresponding StopReason type from
`@anthropic-ai/sdk`, preserving the existing nullability and
CallClaudeResult.stopReason contract. If retaining the local alias, keep its
non-null members aligned with the SDK’s StopReason values.
🤖 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 `@docs/playbook-authoring.md`:
- Around line 263-269: Replace the pipeline in docs/playbook-authoring.md lines
263-269 with a bounded SCAN loop that explicitly checks failures from each Redis
SCAN and TTL command, propagating any failure so the recovery step cannot report
success incorrectly; avoid adding pipefail with unhandled head-induced SIGPIPE.
Apply the identical failure-propagation change to the checked-in example in
playbooks/examples/redis-memory-pressure.md lines 62-68.

In `@src/cli/commands/ask.ts`:
- Around line 114-121: Update the truncated-response handling in runAsk so
machine-output mode emits a truncation marker alongside result.response despite
printWarning and printInfo suppression. Preserve the existing human-readable
warning behavior while ensuring --json consumers can detect that the answer was
truncated.

In `@src/cli/commands/init.ts`:
- Around line 32-35: Update pluginNameFrom to treat only undefined as absent;
boolean false must reach the validation error path and be rejected. Add
regression coverage through runInit for both plugin: false and agent: false,
confirming neither silently creates the configuration.

---

Outside diff comments:
In `@docs/cli-reference.md`:
- Around line 89-90: The `down` exit-code row in the CLI reference incorrectly
assigns missing service configuration to exit code 2. Update that row so no
configured services exits 0, while exit code 2 is reserved for parsing errors
such as unrecognized flags, missing `--config` values, or invalid service
arguments.
- Around line 61-62: Update the CLI flag table in docs/cli-reference.md to
accurately document the command-specific options: add bundle --output <file> and
down --config <path> to their respective command rows, or revise the
completeness statement to explicitly exclude bundle-specific usage.

---

Nitpick comments:
In `@src/__tests__/ai-diagnosis-response-recovery.test.ts`:
- Around line 96-104: Rename the test fixture variable withUnitSeparator to
reflect the \u0001 start-of-heading character it contains, and update its
reference in the test; do not change the fixture value or assertions.

In `@src/__tests__/ai-universal-token-budget.test.ts`:
- Around line 205-212: Update the afterEach cleanup in the test suite to call
vi.restoreAllMocks() so console spies are restored even when assertions fail,
while preserving the ANTHROPIC_API_KEY restoration. Remove the inline
consoleSpy.mockRestore() calls from both timeout tests, including the ranges
identified in the comment.

In `@src/framework/ai-client.ts`:
- Around line 83-98: Optionally replace the duplicated ClaudeStopReason union
with the corresponding StopReason type from `@anthropic-ai/sdk`, preserving the
existing nullability and CallClaudeResult.stopReason contract. If retaining the
local alias, keep its non-null members aligned with the SDK’s StopReason values.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b0c21490-269b-4451-b2a1-32ea001c4f7d

📥 Commits

Reviewing files that changed from the base of the PR and between 2734759 and f00cf66.

📒 Files selected for processing (35)
  • CONTRIBUTING.md
  • GETTING_STARTED.md
  • QUICKSTART.md
  • README.md
  • docs/agents/backup-verification.md
  • docs/architecture.md
  • docs/cli-reference.md
  • docs/guide-verification/2026-08-09-docs-pass-report.md
  • docs/guides/creating-a-check-plugin.md
  • docs/guides/creating-a-recovery-agent.md
  • docs/guides/your-first-agent.md
  • docs/playbook-authoring.md
  • playbooks/examples/redis-memory-pressure.md
  • scripts/pace-cast.mjs
  • scripts/record-demo.sh
  • src/__tests__/ai-client.test.ts
  • src/__tests__/ai-diagnosis-response-recovery.test.ts
  • src/__tests__/ai-explainer-fallback.test.ts
  • src/__tests__/ai-explainer-plan-fixtures.ts
  • src/__tests__/ai-response-budgets.test.ts
  • src/__tests__/ai-timeout-reporting.test.ts
  • src/__tests__/ai-universal-token-budget.test.ts
  • src/__tests__/ask-token-budget.test.ts
  • src/__tests__/completions.test.ts
  • src/__tests__/incident-summary-print.test.ts
  • src/__tests__/incident-summary.test.ts
  • src/__tests__/init-plugin-flag.test.ts
  • src/__tests__/root-cause-synthesis-ai-path.test.ts
  • src/cli/commands/ask.ts
  • src/cli/commands/completions.ts
  • src/cli/commands/init.ts
  • src/cli/commands/scan.ts
  • src/cli/index.ts
  • src/framework/ai-client.ts
  • src/framework/ai-diagnosis-universal.ts
🚧 Files skipped from review as they are similar to previous changes (13)
  • docs/guides/creating-a-recovery-agent.md
  • docs/guides/your-first-agent.md
  • CONTRIBUTING.md
  • docs/guide-verification/2026-08-09-docs-pass-report.md
  • src/tests/ai-response-budgets.test.ts
  • scripts/pace-cast.mjs
  • src/tests/incident-summary.test.ts
  • GETTING_STARTED.md
  • docs/architecture.md
  • src/cli/commands/scan.ts
  • scripts/record-demo.sh
  • QUICKSTART.md
  • README.md

Comment thread docs/playbook-authoring.md
Comment thread src/cli/commands/ask.ts
Comment thread src/cli/commands/init.ts
trs-80 and others added 5 commits August 9, 2026 13:33
`crisismode ask --json` reached machine mode but printed the raw answer with
console.log, while the truncation notice went through printWarning/printInfo —
both of which return early when the output mode is machine (output.ts:460 and
:470). A machine consumer therefore received a partial answer with no marker.
In practice it was worse than reported: the command emitted no JSON at all.

That defeats the point of the truncation work earlier in this PR. A cut-off
answer must never be presentable as a complete one, and an automated caller is
the one least able to notice.

Both single-shot and REPL paths now emit one
{"type":"ask",question,answer,source,truncated} JSONL record per turn through
the existing jsonOut() convention, matching what down, triage and readiness
already produce. Human and pipe output are byte-identical and the warning
wording is untouched.

11 hermetic tests with @anthropic-ai/sdk mocked, including a guard proven to
fail if the marker is suppressed again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The cache-eviction example replaced earlier in this PR fixed the safety
problem — it no longer deletes keys — but its exit status came from the final
`while` loop rather than the Redis commands. Reproduced with a fake redis-cli
on PATH: the pipeline exits 0 both when SCAN fails outright with a connection
refused, and when an intermediate TTL fails before a later success. A recovery
step that silently succeeds after its cleanup failed is worse than one that
fails loudly, because the engine proceeds believing memory pressure was
handled.

`set -o pipefail` is not the fix and was verified not to be: with it, a
HEALTHY Redis exits 141 from head's SIGPIPE, trading a silent success for a
spurious failure.

Both files now carry an identical cursor-driven, bounded SCAN loop with no
pipeline at all. Each SCAN reply is fed through a here-document and every
redis-cli exit status is tested directly, so a SCAN or TTL failure exits 1.
The safety property is preserved: read-only SCAN and TTL only, no DEL, UNLINK
or EXPIRE, capped at 1000 keys and 50 scan rounds.

Verified through the real CLI: both the checked-in example and the docs
Complete Example report 13 safety checks passed, dry-run compiles 8 steps with
step 4 still routine, and shellcheck -s sh is clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pluginNameFrom returned early on `value === false`, so runInit(undefined,
{ plugin: false }) was read as "flag not given" and wrote crisismode.yaml
instead of reporting invalid input.

Checked why the case existed before removing it: --plugin and --agent are
registered as { type: 'string' } with no default, so under parseArgs an
omitted flag is always undefined, a valueless flag is true, --plugin= is "",
and --no-plugin lands on its own key. Verified against real node:util
parseArgs on node v26.5.1. The false branch guarded an unreachable state, so
dropping it cannot break the ordinary omitted-flag path, and no change is
needed at the parse level.

Adds 5 regression tests, including parser-level ones pinning the behaviour the
fix depends on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Checking redis-cli's exit status was not enough. Without -e, redis-cli exits 0
even when Redis returns an error reply, so the step reported success under
NOAUTH, NOPERM, WRONGTYPE or an unknown command. Reproduced against a real
redis-server 8.8.0: with requirepass set, the checked-in snippet printed
"touched 0 keys, deleted none" and exited 0 while every command returned
NOAUTH; an ACL-restricted --user reproduced it with NOPERM.

The elevated step is the one that matters most, and it did not need an auth
failure to fail silently at all: `CONFIG SET maxmemory 8gigabytes` exits 0
without -e on a healthy authenticated Redis, so a bad unit alone made an
elevated step report the memory ceiling raised when nothing had changed.

-e alone was also insufficient for the INFO/DBSIZE step, because runtime.ts
compiles an sh block into a single structured_command and only the last line's
status is reported. Proved with an ACL user granted +dbsize but not +info:
exit 0 with -e alone, exit 1 once set -e is added.

Two prose snippets deliberately keep no -e: one is the destructive anti-pattern
the page argues against, and the other is the broken pipeline one-liner that -e
would not fix anyway.

Also corrects a paragraph that claimed "a clean run is the only way to exit 0",
which was not true before this change.

Healthy path re-verified per command (INFO bulk string, DBSIZE integer 0,
CONFIG SET +OK, empty keyspace, TTL -1 and -2, non-string types, and a 1500-key
keyspace still stopping at 1000). The shared step-4 block stays byte-identical
across both files. playbook validate reports 13 safety checks passed on both,
dry-run 13 checks over 8 steps with step 4 routine and step 7 elevated, and
shellcheck -s sh is clean on all 5 extracted blocks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
.cbmignore is not a gitignore — everything it lists stays tracked, shipped and
required at runtime. It only keeps site/assets/ out of the codebase-memory
knowledge graph: the minified asciinema-player bundle contributes hundreds of
single-letter symbols ($, $A, $e, $t, ...) that outrank real source in
full-text search, and no structural query about this codebase should resolve
into third-party minified output.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@trs-80
trs-80 merged commit a6c9c7f into main Aug 9, 2026
8 checks passed
@trs-80
trs-80 deleted the chore/repo-presentation branch August 9, 2026 18:20
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