feat: v0.8.67 constitution-first setup — model-assisted onboarding, runtime posture, cleanup#3861
Conversation
Add the safety layer under every v0.8.67 setup step: atomic file writes (temp + rename, 0o600) and a SetupTransaction that stages multiple file writes and either fully applies or fully rolls back to each file's pre-commit state, so a partial failure never leaves a half-written file. Preview reports intended writes without touching disk; a dropped (uncommitted) transaction changes nothing. Also add redact_secrets(), a dependency-free backstop that masks secret-bearing keyed assignments (api_key/token/password/...) and bare opaque-token prefixes so config text echoed into reports, logs, or diagnostics never leaks credentials. Tests cover atomic replace, owner-only perms, preview-writes-nothing, commit-applies-all, rollback on partial failure (restore + remove created files), and redaction across TOML/JSON/env shapes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016pe1SXnGCNdjqFPA2DFcYf
Add the single setup-state record every v0.8.67 step (#3404-#3412) reads and writes, persisted as an atomic JSON sidecar (setup_state.json) under $CODEWHALE_HOME via the transactional persistence layer. The model defines the shared vocabulary so /setup, doctor, and the context report never invent their own meanings: - SetupStep ids (language, provider_model, trust_sandbox, tools_mcp, hotbar, remote_runtime, constitution, verification). - StepStatus enum (not_started/recommended/optional/deferred/in_progress/ verified/needs_action/failed/skipped) + per-step StepEntry carrying a required flag, a safe summary (never secrets), and the writing lane. - Constitution-first fields: choice, checkpoint_completed_for, language, source, validity, preview_hash, preview_version, runtime_posture_source. Readiness is derived, not persisted: first_run_ready (language verified, provider/model verified-or-needs-action, posture reviewed, explicit constitution choice) and update_ready (constitution checkpoint complete for the lane). derive_inherited() builds a safe inherited state for existing users with no sidecar so an update never looks like a broken fresh install, while still re-arming the once-per-version constitution checkpoint. A missing or corrupt record falls back to None (derive from config) rather than forcing the wizard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016pe1SXnGCNdjqFPA2DFcYf
…derer (#3793) Add the guided global constitution's data model and renderer. The normal output is structured data persisted under $CODEWHALE_HOME (constitution.json), not a blank Markdown editor. - UserConstitution: about, working_style, priorities, autonomy_preference, bounded free-prose notes, language. All fields optional and forward-compatible. - render_body(): a pure, deterministic function of the struct; render_block() wraps it as the model-facing <codewhale_user_constitution> prose block. - preview_hash(): stable FNV-1a fingerprint over the body, independent of the home path, so a preview matches its saved form. - bounded(): caps free prose and list items in length and count and drops blanks; freeform is advisory and never parsed as enforceable policy. - AutonomyPreference renders as guidance explicitly labeled as NOT changing approval policy, sandbox, shell, network, trust, MCP permissions, or default mode. There is no code path from this module to runtime config; applying posture stays owned by #3406. - load_from()/save_to() classify Missing/Empty/Unreadable/Invalid/Loaded into ConstitutionValidity for the setup-state record; save persists the bounded form atomically. A guard test asserts the persisted file carries no runtime-control keys. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016pe1SXnGCNdjqFPA2DFcYf
The preview_hash_is_independent_of_source_path test discarded the #[must_use] Option returned by render_block, which fails the CI test build under -D warnings (surfaced first on windows-latest). Bind and assert on the result, which also strengthens the test: rendering with a source path embeds that path in the block yet leaves the body-derived preview hash unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016pe1SXnGCNdjqFPA2DFcYf
Load the structured user-global constitution from CODEWHALE_HOME and render it as its own model-facing system block using UserConstitution::render_block(). Missing or empty files stay silent, while invalid or unreadable files are skipped with prompt-layer warnings instead of poisoning the prompt. The rendered block uses the stable user-global source label rather than a device-specific home path, and lands after the base/project context layer but before volatile environment data. Tests cover valid injection and invalid-file fallback.
Add a localized /setup modal shell backed by the shared SetupState model, including step navigation, status rendering, inherited-state derivation, and the v0.8.67 constitution checkpoint action. Interactive launches for existing users now auto-open the checkpoint when onboarding is complete and the versioned checkpoint is still incomplete; headless/noninteractive paths are untouched. Choosing bundled/default constitution records constitution_checkpoint_completed_for = 0.8.67 and creates no custom constitution file. Cancel only closes the modal; staged skip/retry state remains in memory unless an explicit commit event is emitted. Refs #3404. Refs #3794. Tests: cargo fmt --all -- --check Tests: git diff --check Tests: cargo test -p codewhale-tui --bin codewhale-tui --locked setup -- --nocapture Tests: cargo test -p codewhale-tui --bin codewhale-tui --locked localization::tests -- --nocapture Tests: RUSTFLAGS="-D warnings" cargo test -p codewhale-tui --bin codewhale-tui --locked --no-run
Scope the SetupWizardView state accessor to tests so the non-test TUI binary stays clean under CI's workspace clippy and release smoke gates with -D warnings.
Add the /constitution command surface with status, preview, repo-local law, AGENTS explanation, setup-step routing, and bundled/default selection actions. Respect setup-state bundled/deferred/expert override choices when loading the user-global constitution block so stale custom law is not injected after a bundled/default selection. Also keep the once-per-version setup checkpoint out of --skip-onboarding/headless PTY harnesses. Tests run: - cargo fmt --all -- --check - git diff --check - jq empty crates/tui/locales/en.json crates/tui/locales/zh-Hans.json - cargo test -p codewhale-tui --bin codewhale-tui --locked constitution -- --nocapture - cargo test -p codewhale-tui --bin codewhale-tui --locked localization::tests -- --nocapture - cargo test -p codewhale-tui --test qa_pty --locked -- --nocapture - RUSTFLAGS="-D warnings" cargo test -p codewhale-tui --bin codewhale-tui --locked --no-run - cargo clippy --workspace --all-features --locked -- -D warnings -A clippy::uninlined_format_args -A clippy::too_many_arguments -A clippy::unnecessary_map_or -A clippy::collapsible_if -A clippy::assertions_on_constants Refs #3806 #3794 #3404
Add compact provider/model and runtime posture cards to the constitution-first setup shell. Enter records safe SetupState summaries for inherited provider/model readiness or confirmed runtime posture without changing provider credentials, default model, approval policy, sandbox, shell, trust, or network config. The runtime posture card explicitly keeps enforced config separate from constitution guidance, and localized copy covers the new card rows and status messages in English and zh-Hans. Tests run: - cargo fmt --all -- --check - git diff --check - jq empty crates/tui/locales/en.json crates/tui/locales/zh-Hans.json - cargo test -p codewhale-tui --bin codewhale-tui --locked setup -- --nocapture - cargo test -p codewhale-tui --bin codewhale-tui --locked localization::tests -- --nocapture - cargo test -p codewhale-tui --test qa_pty --locked -- --nocapture - RUSTFLAGS="-D warnings" cargo test -p codewhale-tui --bin codewhale-tui --locked --no-run - cargo test -p codewhale-config --lib - cargo clippy --workspace --all-features --locked -- -D warnings -A clippy::uninlined_format_args -A clippy::too_many_arguments -A clippy::unnecessary_map_or -A clippy::collapsible_if -A clippy::assertions_on_constants Refs #3405 #3406 #3736
Stop loading project and global WHALE.md files as prompt context. The loader now treats them as ignored migration markers, keeps them in cache invalidation, and surfaces the migration warning through /constitution and context report diagnostics without reading the legacy body. Update configuration docs and regression tests to describe AGENTS.md plus .codewhale/constitution.json as the supported migration path. Refs #3798. Also advances #3806 and #3411 diagnostics. Verified locally: cargo fmt --all -- --check; git diff --check; cargo test -p codewhale-tui --bin codewhale-tui --locked project_context -- --nocapture; cargo test -p codewhale-tui --bin codewhale-tui --locked constitution -- --nocapture; cargo test -p codewhale-tui --bin codewhale-tui --locked context_report -- --nocapture; RUSTFLAGS="-D warnings" cargo test -p codewhale-tui --bin codewhale-tui --locked --no-run; cargo test -p codewhale-config --lib; cargo test -p codewhale-tui --test qa_pty --locked -- --nocapture; cargo clippy --workspace --all-features --locked -- -D warnings -A clippy::uninlined_format_args -A clippy::too_many_arguments -A clippy::unnecessary_map_or -A clippy::collapsible_if -A clippy::assertions_on_constants.
Wire the final setup verification step to the shared SetupState model so /setup report records a reviewed status, shows first-run/update readiness, and points to the next setup action. Expose the same setup-state vocabulary through doctor and doctor --json, deriving inherited state when setup_state.json is absent and honoring persisted state when it exists. Tests: cargo fmt --all -- --check; git diff --check; jq empty crates/tui/locales/en.json crates/tui/locales/zh-Hans.json; cargo test -p codewhale-tui --bin codewhale-tui --locked setup -- --nocapture; cargo test -p codewhale-tui --bin codewhale-tui --locked doctor_setup -- --nocapture; cargo test -p codewhale-tui --bin codewhale-tui --locked localization::tests -- --nocapture; RUSTFLAGS="-D warnings" cargo test -p codewhale-tui --bin codewhale-tui --locked --no-run; cargo test -p codewhale-config --lib; cargo test -p codewhale-tui --test qa_pty --locked -- --nocapture; cargo clippy --workspace --all-features --locked -- -D warnings -A clippy::uninlined_format_args -A clippy::too_many_arguments -A clippy::unnecessary_map_or -A clippy::collapsible_if -A clippy::assertions_on_constants Refs #3411
Add a contextual G action to the constitution setup step that scaffolds a balanced structured user-global constitution, records GuidedCustom/source/validity/hash/version in SetupState, and persists constitution.json together with setup_state.json through SetupTransaction. Keep runtime posture untouched; the generated autonomy preference is model guidance only and does not alter approval, sandbox, shell, network, trust, or MCP permissions. Tests: cargo fmt --all -- --check; git diff --check; jq empty crates/tui/locales/en.json crates/tui/locales/zh-Hans.json; cargo test -p codewhale-tui --bin codewhale-tui --locked setup -- --nocapture; cargo test -p codewhale-tui --bin codewhale-tui --locked localization::tests -- --nocapture; RUSTFLAGS="-D warnings" cargo test -p codewhale-tui --bin codewhale-tui --locked --no-run; cargo test -p codewhale-config --lib; cargo test -p codewhale-tui --test qa_pty --locked -- --nocapture; cargo clippy --workspace --all-features --locked -- -D warnings -A clippy::uninlined_format_args -A clippy::too_many_arguments -A clippy::unnecessary_map_or -A clippy::collapsible_if -A clippy::assertions_on_constants Refs #3793. Refs #3806.
Document /constitution as the primary personal constitution surface, with structured user-global constitution data rendered to prose, repo-local law as optional project policy, AGENTS.md as project instructions, and memory/handoffs below those layers. Mark the full base-prompt Markdown override as expert-only and keep WHALE.md as deprecated migration-only guidance. Update README translations and the web constitution page so the docs map has localized coverage. Tests: git diff --check; npm --prefix web run check:docs; npm --prefix web run lint; cargo fmt --all -- --check Refs #3811. Refs #3412. Refs #3806.
Issue #3859 reported that the running-command hint sounded like Bash job control and set the wrong expectation for long foreground shell waits. Rename the user-facing Ctrl+B affordance around moving the active shell wait into /jobs, update localized help copy and keybinding docs, and keep the model-facing detach result pointed at exec_shell_wait for follow-up inspection. Tests run: - cargo fmt --all -- --check - git diff --check - jq empty crates/tui/locales/en.json crates/tui/locales/es-419.json crates/tui/locales/ja.json crates/tui/locales/pt-BR.json crates/tui/locales/vi.json crates/tui/locales/zh-Hans.json - cargo test -p codewhale-tui --bin codewhale-tui --locked exec_cell_renders_live_shell_output_before_final_output -- --nocapture - cargo test -p codewhale-tui --bin codewhale-tui --locked test_exec_shell_foreground_can_move_to_background -- --nocapture - cargo test -p codewhale-tui --bin codewhale-tui --locked localization::tests -- --nocapture - RUSTFLAGS="-D warnings" cargo test -p codewhale-tui --bin codewhale-tui --locked --no-run - cargo test -p codewhale-config --lib
Issue #3841 identified four ignored full-engine mock LLM placeholders that only ended in unreachable!("ignored") while the real blocker is the missing Engine Arc<dyn LlmClient> seam. Delete the placeholders and keep the blocker as README/module documentation so the integration test reports real executable coverage instead of dormant future-test outlines. Tests run: - cargo fmt --all -- --check - git diff --check - cargo test -p codewhale-tui --test integration_mock_llm --locked -- --nocapture - cargo check -p codewhale-tui --all-targets --locked - RUSTFLAGS="-D warnings" cargo test -p codewhale-tui --bin codewhale-tui --locked --no-run - cargo test -p codewhale-config --lib - rg "#\[ignore\]|unreachable!\(\"ignored\"\)|engine_full_" crates/tui/tests/integration_mock_llm.rs crates/tui/tests/README.md
Issue #3840 identified project_doc.rs as a replaced project-instruction loader path whose only live consumer was find_git_root for project_context.rs. Move the git-root helper beside its remaining project-context callers, remove the unused module wiring, and delete the stale discovery/loading API plus its self-contained tests. Project-context keeps the active AGENTS.md/constitution loading and symlink-safety coverage. Tests run: - cargo fmt --all -- --check - git diff --check - cargo test -p codewhale-tui --bin codewhale-tui --locked project_context -- --nocapture - cargo test -p codewhale-tui --bin codewhale-tui --locked project_doc -- --nocapture - cargo check -p codewhale-tui --all-targets --locked - RUSTFLAGS="-D warnings" cargo test -p codewhale-tui --bin codewhale-tui --locked --no-run - cargo test -p codewhale-config --lib - rg "project_doc::|mod project_doc|read_project_docs|load_from_workspace|ProjectDoc|DOC_FILENAMES|format_instructions|discover_paths" crates/tui/src crates/tui/tests
Reviewing PR #3861 (v0.8.67 constitution-first setup)
|
Approval-gate hunt finding: a tool present in the model-facing catalog (tool_def = Some) but absent from the execution registry — or when the registry itself is None — fell through every approval branch in the tool gate. approval_required stayed false, read_only stayed false, no auto-review classification ran, and it executed with no gate. The not-available block only fired when tool_def was None. The block now keys on whether the tool actually has an execution path (a registry spec, or being MCP/code/js/search) rather than on catalog presence, so an unrunnable tool is blocked as not-available instead of running unguarded. Such a tool has no execution path anyway, so this is not a regression for any runnable tool. Tests: - cargo test -p codewhale-tui --bin codewhale-tui --locked engine (273) + turn_loop (40) - cargo clippy --workspace (CI flags) clean
|
Claude finished @Hmbown's task in 2m 12s —— View job Reviewing PR #3861 (v0.8.67 constitution-first setup)
|
…ack, count image tokens A correctness hunt over the compaction / token-accounting pipeline found several real defects; these three are the safe, high-value fixes (the trigger-unit reconciliation and unbounded-summary-prefix items are noted for careful review since they change WHEN compaction fires). - Emergency/preflight compaction dropped all working-set pins. The pin-aware manual path passes the pinned message indices + top paths to compact_messages_safe, but recover_context_overflow passed None/None — and on large windows that emergency path is the one that actually fires, so pinned errors, patches, and the files the user is editing could be summarized away mid-turn. It now computes and passes the same pins/paths as the manual path. - The cache-aligned summary request replays a non-contiguous message subsequence (pinned messages removed from the middle), which can violate strict role-ordering (a non-transient InvalidInput). create_summary only fell back to the formatted summary on context-window errors, so a role-ordering error aborted compaction entirely and let context keep growing. It now falls back on ANY cache-aligned failure. - Inline images (ContentBlock::ImageUrl) estimated to 0 tokens in every estimator, undercounting the budget and risking overflow in image-heavy sessions. They now carry a conservative flat estimate (erring high compacts slightly early rather than overflowing). Tests: - cargo test -p codewhale-tui --bin codewhale-tui --locked compaction (68 passed; new inline_image_estimates_nonzero_tokens) - RUSTFLAGS="-D warnings" --no-run clean; cargo clippy (CI flags) clean
|
Claude finished @Hmbown's task in 2m 19s —— View job Reviewing PR #3861 (v0.8.67 constitution-first setup)
|
…e guard, durable rename Four session-persistence correctness fixes from the hunt: - Backtrack (Esc-Esc) truncated api_messages by counting role=="user" messages, but tool results are stored as role=="user" too — so in any turn with tool calls the cut landed mid-turn on a tool_result, leaving a dangling assistant tool_use with no matching result (a transcript the provider rejects) and diverging the visible history from what the model sees. It now counts only messages that yield a HistoryCell::User (the same predicate apply_loaded_session uses), via a testable backtrack_api_cut_index helper. - The persistence actor tracked checkpoint-write and checkpoint-clear as independent flags and always cleared-then-wrote in the drain, so a Checkpoint and a ClearCheckpoint coalesced in one batch would clear latest.json and immediately re-write the stale checkpoint — undoing the clear and resurrecting a finished turn as a false crash-recovery on next launch. Now last-writer-wins: a fresh checkpoint drops a pending clear and vice versa. - /sessions prune deleted every session older than the cutoff with no exclusion, so it could delete the active, just-resumed session (whose updated_at is stale until its first post-resume save) out from under the live TUI. Added prune_sessions_older_than_keeping and passed the active session id, mirroring the boot-janitor guard added earlier. - write_atomic synced the temp file data before rename but never fsynced the parent directory after, so a power loss right after the rename could lose the new directory entry — silently dropping a crash-recovery checkpoint the subsystem exists to preserve. Now best-effort fsyncs the parent dir after persist. Tests: - backtrack_cut_index_skips_tool_result_user_messages (new) - session_manager 45, persistence 15, backtrack 22, write_atomic 3 passing - cargo clippy --workspace (CI flags) clean
|
Claude finished @Hmbown's task in 2m 10s —— View job Reviewing PR #3861 (v0.8.67 constitution-first setup)
|
The approval prompt is a bottom-anchored inline band (#3799), not the old centered full-screen card; an orphaned doc comment from the removed card-rect helper still described it as a centered takeover. Doc only. Tests: build only (comment change)
|
Claude finished @Hmbown's task in 2m 30s —— View job Reviewing PR #3861 (v0.8.67 constitution-first setup)
|
The Anthropic Messages path and the OpenAI Responses path each decoded every raw bytes_stream() chunk individually with String::from_utf8_lossy(&chunk). reqwest yields bytes at arbitrary TCP/HTTP2 frame boundaries, so any multi-byte UTF-8 character — CJK, emoji, accented letter — split across two reads had its tail bytes turned into U+FFFD in one chunk and its lead bytes into U+FFFD in the next, permanently garbling the character in both visible text and tool-call arguments. For a DeepSeek-centric, CJK-heavy user base this triggers routinely (e.g. "你好" rendering as "你�"). Both paths now buffer raw bytes and decode only COMPLETE lines via a shared take_sse_line helper — the '\n' delimiter is ASCII and can never fall inside a multi-byte sequence, so the split char is reassembled intact. This mirrors the already-correct chat.rs main path. Tests: - take_sse_line_preserves_multibyte_split_across_reads (splits '好' mid-char), take_sse_line_returns_none_without_newline - client::tests 148, streaming 148 passing; cargo clippy --workspace (CI flags) clean
Reviewing PR #3861 (v0.8.67 constitution-first setup)
|
Two more streaming-parse fixes in the main chat SSE path (both no-ops for
the primary DeepSeek path, which sends [DONE]):
- The pending line_buf was flushed only on an SSE blank-line boundary, so a
provider that ends the stream straight after its last `data:` line — or
whose final line lacks a trailing newline — had its final delta (last
tokens, finish_reason, and usage) silently dropped. A post-loop flush now
processes any leftover byte_buf line + line_buf as a final frame. It is
gated on a new saw_done flag so post-[DONE] trailing bytes are never
reprocessed; for DeepSeek ([DONE]) and blank-line-terminated streams the
flush is a no-op.
- Multiple `data:` fields within one SSE event were concatenated with no
separator, yielding `{…}{…}` that fails JSON parsing and drops the whole
frame. They are now joined with '\n' per the SSE spec. No-op for the
common single-data-line event (line_buf empty when the first is pushed).
Verification: build + cargo clippy --workspace (CI flags) clean; client::tests
148, chat 100, parse_sse 3 passing (no regression). No dedicated end-to-end
streaming test — the crate has no MockServer SSE-stream harness, and both
changes are provably no-ops for the primary [DONE]/blank-line-terminated
paths, only recovering the currently-dropped final frame.
|
Claude finished @Hmbown's task in 3m 5s —— View job Reviewing PR #3861 (v0.8.67 constitution-first setup)
|
…, dead timeout Four MCP transport hardening fixes from a trust-boundary hunt (the MCP server is third-party; its data flows into the model and the gate): - SSRF (high): the server-supplied `endpoint` SSE event accepted an absolute cross-origin URL, and SseTransport::send then POSTed to it with the Bearer/OAuth headers attached — the endpoint host was never re-vetted after the one connect-time network-policy check. A malicious MCP server could redirect the client's authenticated POSTs to 169.254.169.254 or a localhost admin port. resolve_endpoint_url now requires the resolved endpoint to be same-origin (scheme+host+port) with the connect URL; relative endpoints are same-origin by construction. - UTF-8 corruption: the SSE loop decoded each raw network chunk with String::from_utf8_lossy, garbling any multi-byte char split across reads (same class of bug just fixed provider-side, here in the MCP transport). It now buffers raw bytes and decodes only complete event blocks via a byte-oriented separator finder. - OOM DoS: the SSE frame buffer grew without bound if a server never emitted a separator. Capped at MAX_SSE_FRAME_BYTES (8 MiB). - Dead timeout: the HTTP client set reqwest's TOTAL .timeout() from connect_timeout (10s), silently capping every request at 10s and making the per-server execute_timeout/read_timeout dead. Now connect_timeout bounds the connect phase and read_timeout is the total backstop. Tests: - resolve_endpoint_accepts_relative_and_same_origin, resolve_endpoint_rejects_cross_origin_ssrf, find_sse_event_separator_bytes_matches_str_and_survives_multibyte - mcp suite 134 passing; cargo clippy --workspace (CI flags) clean
Reviewing PR #3861 (v0.8.67 constitution-first setup)
|
Completes the transport-read hardening: a misbehaving or malicious MCP server could OOM the client at transport-read time (before any transcript-level spillover), via two unbounded reads: - Streamable-HTTP used response.text() to slurp the entire body into memory. It now streams chunk-by-chunk through read_body_capped and aborts past MAX_MCP_RESPONSE_BYTES (16 MiB). - The stdio transport used the unbounded read_line, so a server emitting a newline-free multi-GB "line" would OOM. A new read_line_capped uses fill_buf/consume and aborts once a line exceeds the same cap without a newline. Tests: - reads_a_line_and_reports_eof, aborts_on_newline_free_line_over_cap - mcp suite 136 passing; cargo clippy (CI flags) clean
|
Claude finished @Hmbown's task in 1m 44s —— View job Reviewing PR #3861 (v0.8.67 constitution-first setup)
|
…omic writes
A hunt over the file-write/patch tools found real, non-adversarial
corruption of the user's source files. Fixes (the gate cross-check
confirmed repo_law's path parsing has no bypass — the weakness was
content-level, exactly these):
- apply_patch dropped the file's trailing newline on EVERY patch and
flipped CRLF files to LF: it round-tripped content through
str::lines() (which strips both the trailing \n and each \r) and
reassembled with join("\n"). Now reassemble_preserving_newlines detects
the base file's dominant terminator (CRLF vs LF) and trailing-newline
state and reproduces it, so a patched file keeps its exact line-ending
style. (The old tests only asserted content.contains(...), never the
trailing byte, so this went uncaught.)
- apply_patch's fuzz default was MAX_FUZZ (50), not the schema-documented
3, so a hunk with no fuzz argument could silently apply up to 50 lines
from its stated position — landing in the wrong region of a file with
repeated blocks and reporting success. Default is now DEFAULT_FUZZ = 3
(ceiling stays 50 for explicit opt-in).
- write_file, edit_file, apply_patch, and fim_edit wrote the user's real
source with plain fs::write (truncate-then-write), so an ENOSPC/EIO/crash
mid-write left the original truncated. All four — and apply_patch's
rollback — now use the atomic temp+fsync+rename write_atomic path that
the session/config/artifact writers already use.
Tests:
- reassemble_preserving_newlines_keeps_style, apply_patch trailing-newline
and apply_patch_preserves_crlf_line_endings regressions
- apply_patch 47, tools::file 61, fim 4 passing; cargo clippy --workspace (CI flags) clean
|
Claude finished @Hmbown's task in 1m 58s —— View job Reviewing PR #3861 (v0.8.67 constitution-first setup)
|
The bytes_stream()-based read_body_capped introduced a CI-only failure in streamable_http_stale_session_reconnects_and_retries_tool_call: that test drives a raw TCP server using connection-close framing, and the chunk-by-chunk stream read raced with the peer shutdown differently than .text() under macOS/Windows CI timing (it passed locally). Restore .text() for the read (identical connection framing to before) and enforce the OOM bound with a Content-Length pre-check that rejects an over-large declared body before it is read into memory. The SSE frame-buffer cap and stdio line cap — the primary streaming OOM vectors — are unchanged. Removed the now-unused read_body_capped helper. Tests: streamable_http_stale_session passes 4/4; mcp suite 136; clippy clean
Reviewing PR #3861 (v0.8.67 constitution-first setup)
|
These hold local-only planning notes (unreleased roadmap) and scratch files that must not land in git history.
|
Claude finished @Hmbown's task in 14m 51s —— View job Reviewing PR #3861 (v0.8.67 constitution-first setup)Review complete — 5 parallel deep-dives over the diff (
Findings (most severe first)1. [CRITICAL, verified] Interactive-origin destructive commands are not held in YOLO/Bypass mode, contradicting the PR's own blocker-#3883 claim. 2. [HIGH, verified] 3. [HIGH, verified] MCP Streamable HTTP OOM guard bypassable via chunked transfer-encoding. 4. [MEDIUM, verified] Four shipped locales silently fall back to English for the entire new setup/constitution wizard. 5. [MEDIUM, plausible] 6. [LOW] PR-description mismatch: 7. [LOW] WHALE.md "migration" is warning-only, not an actual migration. Areas that look solid
Not run in this review (no build/test tool access in this session): |
Resolves the conflicts with the #3861 constitution-first-setup merge: - @mention completion: main landed the #3757 candidate cache (one needle-independent walk serves every keystroke of a non-path-like mention token, ranked in memory with a 4s TTL). Kept it as the fast path, adapted to the MentionCompletionKey cache form, and kept this branch's #3899 background walk for exactly the needles the candidate cache bypasses (path-like and browser) — the two fixes compose: non-path needles rarely walk at all, and when a walk is needed it no longer blocks the UI thread. - app.rs: kept both structs/fields (MentionCompletionPending from #3899, MentionCandidateCache from #3757). - sidebar.rs: kept both tests added at the same location (this branch's tree-sort exactly-once test and main's terminal-row collapse test). cargo test -p codewhale-tui --locked after the merge: 5715 passed, 1 failed (sandbox::tests::test_parity_linux_landlock_available — pre-existing container-environment failure, kernel lacks Landlock).

v0.8.67 — constitution-first setup, plus an overnight quality pass
Cargo.tomlstays at0.8.66; no tag/publish/release here.The release: constitution-first setup
First launch is a ritual, not a config form — choose a model, let it help draft the constitution it will live under, read and ratify. Nothing becomes law until you confirm.
/setupentry, resume/skip/retry, provider-readiness card, runtime-posture card (work mode separated from approval policy), secret-free verification report + doctor integration./constitutionas the primary management surface.Constitution as mechanism (new)
Repo-local
.codewhale/constitution.jsonprotected_invariantsmay now carry path globs +action(ask|block), compiled into non-bypassable, tighten-only write holds in the tool gate — the law mechanically stops a violating write, with a receipt naming the invariant. The user-global constitution stays advisory prose and never reaches enforcement.Blockers
rm -rf ~/,dd→device,mkfs,shred,git push) still hold in every mode.Overnight quality pass
agenttool properly classified./providerat that provider's key entry.--help; actionable empty states./constitutionand/modelspages, community hub + non-goals, docs dark mode.Verification
cargo test -p codewhale-tui5686 passed / 0 failed ·codewhale-config342 passed ·cargo fmt --check·git diff --check·jqlocales ·RUSTFLAGS="-D warnings" --no-runclean · release build clean ·scripts/v0867-setup-qa.sh9/9 ·web/ npm run build40 pages clean.A five-lens adversarial review (34 agents) ran over the branch; every confirmed finding was fixed (including a self-caught safety-floor gap).
🤖 Generated with Claude Code