Commit Graph

322 Commits

Author SHA1 Message Date
CodeWhale Bot be5c8f8bfd chore(release): prepare 0.9.6
Bump the workspace, every published crate, the npm CLI package and its
codewhaleBinaryVersion, the runtime SDK, and the VS Code extension to 0.9.6 —
the four version sources the release workflow cross-checks before it will
tag.

The changelog entry describes v0.9.6 as what it is: a subtractive release.
The guards that interrupted live work, the per-mode prompt doctrine, and the
deterministic second compaction system are gone; a truncated provider
response can no longer be recorded as a finished answer. Most of these were
found by running v0.9.5 against Terminal-Bench 2.1 beside Pi 0.8.41 on the
same model, effort, endpoint, and task digests, then reading the trials
Codewhale lost — so the entry names those trials rather than describing the
fixes in the abstract.

Mistral AI ships in this release; Xavier Pestel (@xavierpestel-ai) is
credited in the contributors section for #5295.
2026-08-09 01:02:51 -07:00
CodeWhale Bot dd84af2297 chore(release): prepare 0.9.5 candidate
Bump every tagged package and internal dependency pin to 0.9.5, refresh Cargo and npm lock records, regenerate the packaged changelog and web facts, and record the user-visible candidate contract. The changelog deliberately remains marked Unreleased candidate until the rebuilt binary is dogfooded and publication is explicitly approved.
2026-08-07 21:47:47 -07:00
CodeWhale Bot 6c63aacbbc WIP(core): wire #5261 scaffolding modules into lib roots + fix session-tree compile
Uncommitted in-flight work recovered from the working tree:
- crates/core: expose engine/ids/journal/request/session modules (files
  landed in cae5626e6 but were never wired into lib.rs)
- crates/protocol: expose ids/op/event_msg/journal modules
- tui: fix BranchSummary pattern (parent_branch_id field), fork_from_session
  Option arg, branch_to borrow in update_session, mcp_server_management cap
- workspace: add tokio-util dep for crates/core

Verified: cargo check -p codewhale-core -p codewhale-protocol -p codewhale-tui
passes (warnings only). Tests and clippy not yet run.

Refs #5261
2026-08-07 18:49:20 -07:00
CodeWhale Bot cae5626e6c feat(session-tree): append-only entry journal + /tree /branch /fork /resume (#5262)
- Every session entry carries id + parentId, leafId tracks active position,
  in-memory tree projects from journal, context rebuilds root->leaf.
- Tree operations as commands: /tree (render), /branch (move leaf only,
  never rewrites history), /fork (new session from any node, interactive
  picker per #576 via /fork picker), /resume (picker + foreign-session
  import/export container).
- branch_summary and compaction entries are first-class SessionEntryKind
  variants (data shape lands now, strategies deferred).
- Spawn-depth tracking on SessionMetadata and Journal; fork increments.
- Foreign-session import/export via SessionImportContainer so /resume can
  ingest sessions from other agents.
- SavedSession journal migration: old linear messages -> journal entries
  with linked parent chain, leaf = last; new sessions write both journal
  and derived messages for compat. Atomic write/fsync/crash-checkpoint
  and MAX_SESSIONS=50 preserved.
- Shares entry shape with compaction (same SessionEntry envelope).

Co-depends on #5261 engine split (core journal placeholder already
landed in parallel work on same branch).
2026-08-07 06:29:10 -07:00
CodeWhale Bot 5e5608cee2 fix: fmt/clippy follow-ups for single-binary + deps shrink 2026-08-07 06:01:14 -07:00
CodeWhale Bot aa9d88121f deps(tui): shrink 708-package graph — dedupe, prune features, drop redundant stacks (#5248)
Epic #5249 build-time lane. Baseline 708 (measured 710 via cargo
metadata --offline, 91 normal duplicate entries, 56 cargo-deny warnings
→ 674 packages after shrink (-36, -5% on Cargo.lock, -27 normal dups).
Remaining duplicates are explicitly ratcheted in deny.toml (warn/dry-run).

What changed, why it is safe, and what was measured:

* http: tiny_http 0.12 (blocking, 3 packages: tiny_http+ascii+chunked_transfer)
  removed from codewhale-tui. OAuth loopback (crates/tui/src/mcp/oauth.rs)
  now uses tokio::net::TcpListener + minimal HTTP/1.1 parsing. The previous
  server was only for the OAuth redirect (single GET /callback?code=…); the
  new code keeps the same parse_oauth_callback contract and writes the same
  200/400 bodies. tiny_http is kept as [dev-dependencies] for the two
  integration tests that still use it (skill_cli, plugin_e2e_acceptance) so
  cargo test --workspace still compiles. Saves 3 normal packages.
* html: readability 0.3 (html5ever 0.26 / markup5ever 0.11 stack, 16
  packages: readability+phf 0.10+string_cache 0.8+tendril 0.4+xml5ever 0.17
  etc.) removed from crates/tui. crates/tui/src/tools/web/extract.rs now
  uses the existing fallback_main_html regex for cleaned_html and
  html_to_plain_text for text, keeping the meaningful_text≥32/≥5-words
  signal. htmd 0.5 (html5ever 0.38) is retained as the single HTML→Markdown
  stack. The two stacks were compiling incompatible html5ever trees; the
  fallback path was already the JS-required fallback, so behavior is
  preserved. Saves 16 packages (phf 0.10, string_cache 0.8, etc. gone).
* jsonschema: workspace 0.48 → 0.46 to match schemaui 0.12 (which pins
  ^0.46). crates/workflow-js now uses the same 0.46 validator (validator_for
  exists in both). Saves jsonschema 0.48.5 + referencing 0.48 etc. (6).
* tower-http: workspace 0.7 → 0.6 to match reqwest 0.13 (which depends on
  0.6). CorsLayer API is identical for the single use (cors::CorsLayer).
  Saves tower-http 0.7.
* lru: crates/tui 0.18 → 0.16 to match ratatui-core =0.1.0 (which pins
  0.16). LruCache::new(NonZeroUsize) + get/put API is identical.
* base64: crates/tui 0.23.0 → 0.22.1 to match oauth2 5.0 (0.22). Engine
  API (general_purpose::STANDARD) is identical since 0.21.
* reqwest: crates/tui removes unused gzip feature (compression-codecs
  + async-compression). http2/keep-alive, blocking (fleet/alerts,
  xai_oauth), stream (fetch::bytes_stream), form (OAuth) are kept and
  verified via cargo check. Saves 3 packages.
* cargo-deny ratchet: [bans] multiple-versions stays warn (dry-run per
  AGENTS.md) but every surviving duplicate is now in skip/skip-tree with a
  blocker comment (oauth2 5.0 → reqwest 0.12/sha2 0.10, portable-pty 0.9 →
  filedescriptor 0.8 → thiserror 1, rust-i18n 4.2 → toml 0.8 stack,
  windows-* split, etc.). cargo deny check now reports 0 duplicate warnings
  (was 56) and will warn on any new duplicate. The gate stays warm until
  Hunter approves deny.

Metrics (cargo metadata --offline / cargo tree -d -e normal --workspace /
cargo deny check / scripts/check-source-structure-budget.py, all --offline
where applicable, on a clean release/0.9.5 checkout):

  before: 710 packages, 91 duplicate entries (44 crates), 56 deny dups,
          28 build scripts, 680592 owned Rust lines, 178 large modules,
          max module 17631 (main.rs)
  after:  674 packages (-36), 64 duplicate entries (-27), 0 deny dups,
          680653 owned Rust lines (+61, still < 680700), 178 large modules,
          max module still 17631. Budget PASS.

  cargo check -p codewhale-tui --offline  PASS
  cargo check -p codewhale-cli --offline PASS (wrapped set_var/remove_var
    in unsafe for Rust 1.97)
  cargo test -p codewhale-tui --no-run PASS
  cargo deny check PASS (0 duplicate warnings)
  scripts/check-source-structure-budget.py PASS

The HTTP server evaluation (axum 0.8 vs tiny_http) and the HTML evaluation
(readability vs htmd) are documented above: axum is kept for the main TUI
runtime; tiny_http is dropped for the OAuth loopback in favor of a ~80-line
Tokio listener. readability is dropped in favor of the fallback + htmd
single stack; keeping both was compiling two html5ever trees.

Fixes #5248.

Co-authored-by: codewhale --provider deepseek --model deepseek-v4-flash exec (deps analysis)
EOF
)
2026-08-07 05:36:47 -07:00
CodeWhale Bot a50b6532bd feat(tui): harness/continual ledger, effort, recommend, voice ASR, ui split — 0.9.4 final bundle
- continual_harness ledger (bounded 24, PromptNote/SubagentSpec/SkillHint) + harness tool (overview/refine/remove) for persistent working context
- /effort + /thinking aliases for ReasoningEffort
- skills/recommend: deterministic skill ranker for /plugin suggest|recommend
- voice: free ASR stack (GROQ_ASR_URL, LOCAL_WHISPER_BINS, detect_free_asr)
- tui/ui split: apply/event_loop/handlers/dispatch/motion/overlays/provider_routes/release_check/session_state/frame/terminal extracted from 19k-line ui.rs
- model catalog: Muse Spark 1.2 + ultra effort + cache_read costs
- provider_defaults: gpt-5.5→5.6, muse-spark-1.1→1.2 canonical routes
- locales: CmdEffortDescription, PickerActionSetStartupDefault, operate hint refresh
- docs + budgets resynced; drop stale HANDOFF/RELEASE_EVIDENCE working notes
- .gitignore: ignore .ralph/ agent scratch
2026-08-06 00:35:36 -07:00
Hmbown 50b96929b6 feat(update): throttle the startup update check and name the right command
The TUI has told users about new releases since #3961/#14, but it asked
GitHub on every single launch and always advertised `codewhale update` --
which is the wrong command for most installs and actively harmful for some.
This adds the "throttled" half of #5053 and fixes the wording.

Throttling. `codewhale-release::check` caches the answer in
~/.codewhale/update-check.json and reuses it for `check_interval_hours`
(default 24). The cache stores the *tag we last saw*, not a "checked
recently" flag: a user on a stale binary still sees the notice on every
launch while the network is touched once a day. Caching only a timestamp
would have hidden the notice for the whole interval, which is the opposite
of the point. A failed check is deliberately not cached, so an outage does
not suppress the notice until tomorrow.

Suppression. Checks are skipped without touching the network in CI
(CI, GITHUB_ACTIONS, GITLAB_CI, ...) and on CODEWHALE_NO_UPDATE_CHECK or
NO_UPDATE_NOTIFIER. Values of "", 0, false, no, off do not count as set, so
a `CI=false` export does not disable checks for ordinary users. The
decision is factored into a pure `resolve_version_check_source` so this
repo's own CI run does not change the answer under test.

Install-method awareness. `codewhale-release::install` classifies the
running binary from its path -- npm (node_modules), Homebrew (Cellar /
linuxbrew), cargo (~/.cargo/bin), or a plain release binary -- and the
notice now names that manager's command. Package-managed installs also get
an explicit warning against `codewhale update`: overwriting a binary
Homebrew or npm owns leaves the manager describing a version that is no
longer on disk, and its next upgrade silently reverts the user. `codewhale
update` itself prints the same warning before proceeding; it warns rather
than refuses, since the download still yields a working binary and refusing
would break workflows that have relied on it. Homebrew intentionally points
at the legacy `deepseek-tui` formula -- no `codewhale` formula is published
yet, and naming one that does not exist would hand the user a failing
command.

Nothing is installed without the user asking. The check remains
fire-and-forget: it never delays startup and never blocks a turn.

Still open on #5053: the one-chord update-and-relaunch. Left out rather
than half-wired -- running a package manager on the user's behalf from
inside the TUI needs a confirmation surface and a clean re-exec path that
this change does not build.

Tests: 6 new in codewhale-release (cache freshness, clock skew, atomic
round-trip, corrupt cache, install detection), 5 new in the TUI (CI
suppression, cache hit answers offline, failure is not cached,
install-specific wording), 1 in the CLI updater.
2026-08-04 01:44:26 -07:00
Hmbown 9659d3c6be feat(tui): telemetry session lifecycle, panic and signal exit classes
This is the read the whole feature was missing. `resolve_runtime_options` had
no non-test caller in this crate, so neither `telemetry = false` in the config
file nor `CODEWHALE_TELEMETRY=0` was ever consulted by the process that would
have emitted. `arm_telemetry` performs it once, before the dispatch, and hands
the resulting consent token to `init`. Until that returns, the panic hook
installed back in `main` — and every other write path — is a hard no-op,
because the gate is a `OnceLock` that is by construction empty until resolution
completes. A user who never opted in panics without writing a byte and without
creating a directory.

The surface is a function of the parsed subcommand, never of the executable.
This one binary serves `tui`, `exec`, `serve`, `mcp-server`, and `cli`;
`current_exe()` would label all five the same.

Three exit paths, three classes, all stated rather than derived:

  - the process panic hook records `Panic` and the allowlist-reduced site.
    The panic *message* is never read: a slicing panic embeds the entire
    string being sliced, and this tree slices user and model text in dozens
    of places.
  - the signal task records `Signal` and a synchronous `session_end`
    immediately before `std::process::exit`. `record_blocking` takes no lock
    for exactly this reason — `flock` is per-fd within a process, so a second
    Codewhale sharing CODEWHALE_HOME would otherwise hang Ctrl-C, and the
    second-signal short-circuit has to stay reachable.
  - the exec path states `Error` from `RunTerminationReason`, and ordinary
    teardown escalates `Clean` to `Error` only if nothing has spoken already.

None of this can come from an exit code. `RunTerminationReason::Canceled` maps
to 130, the same value the SIGINT path uses, so a code-based derivation would
report every Esc-cancelled turn as a signal.

`startup_trace::elapsed_ms` is a new accessor rather than a reuse of
`log_summary`, which computes the same number into a local, emits it through
`tracing`, clears its milestone buffer, and returns `()`. `mark_cold_start` is
called only from the interactive path, which is what makes the cold-start
bucket absent rather than invented on the surfaces that have no event loop.

The shutdown flush goes to the blocking pool and is bounded there. The
persistence actor's unbounded `task.await` next door is deliberately not
copied: a hung TLS handshake would hold the process open past the last frame.

Gate: cargo test -p codewhale-tui --bin codewhale-tui (9793 passed)
2026-08-03 20:50:21 -07:00
Hmbown 5ba395c8be feat(cli): telemetry surface identity
The dispatcher runs four subcommand families in its own process — `config`,
`auth`, `update`, and the non-delegated half of `app-server`. Until now none of
them could emit anything, because nothing armed telemetry outside the TUI and
because the surface a batch reports cannot be derived from the executable:
`app-server` runs in-process inside `codewhale`, so `current_exe()` would label
every one of those sessions `cli`.

Each of those branches now resolves the predicate itself and names its own
surface. `start_cli_telemetry` returns `Some` only behind
`TelemetryDecision::Enabled`, so the session token's existence is the
permission; the default state of every installation — no notice answered —
yields `None` and touches nothing. `finish_cli_telemetry` closes the session
from what actually happened rather than from an exit code (a cancelled run and
a signal share 130) and hands the flush a bounded deadline. Because the flush
re-resolves from disk, `codewhale config set telemetry false` takes effect on
the very run that wrote it.

`--telemetry` had no help string and was therefore invisible in `--help`. A
consent control nobody can find is a consent control nobody has.

`CODEWHALE_TELEMETRY_ENDPOINT` now travels with the switch to delegated
children, and only when one is configured: naming the variable with an empty
value would read as "configured" to anything that checks for presence. The
child re-validates the scheme, so forwarding is a convenience and never an
authorization.

Install-or-upgrade detection lands in `init` rather than at any one call site.
It is a per-machine fact, not a per-surface one, and all six surfaces arm
through the same door. The previous version is read from
`$CODEWHALE_HOME/telemetry/state.json` and from nowhere else; the state file is
written before the event is queued, so a process that dies between the two
reports nothing rather than re-reporting the same upgrade on every launch.

Gate: cargo test -p codewhale-cli -p codewhale-telemetry
2026-08-03 20:40:20 -07:00
Hmbown aff8106d6e feat(build): emit a release-only build sha
`CODEWHALE_RELEASE_BUILD_SHA` is emitted only when `DEEPSEEK_BUILD_SHA` or
`GITHUB_SHA` was in the build environment. For every locally built binary it
is simply absent, `option_env!` yields `None`, and a telemetry payload carries
`git_sha: null`.

There is deliberately no fallback to the local checkout, and the "was this a
published release" gate proposed earlier cannot be built as described.
`codewhale_release::latest_release_tag_{async,blocking}` are network calls to
`api.github.com` that return *tag names*, not shas, so the only available
comparison is version-vs-version — and a maintainer running the release build
this repo's own guidance prescribes, on a private tree at the shipping
version, would compare equal and ship their private HEAD. Meanwhile
`build_commit` already falls back to `git rev-parse`, which is why
`CODEWHALE_BUILD_COMMIT` is the builder's own HEAD on every local build and is
named here as a forbidden source. Build-time provenance is deterministic,
network-free, and verifiable from the repository.

The rule lives in `release_build_sha`, which takes the environment as a
closure so it is tested against injected values rather than by mutating the
process: absent, GitHub-only, Codewhale-wins-over-GitHub, a value that is not
a full 40-hex sha, and `CODEWHALE_BUILD_COMMIT` offered as a decoy.

The build script belongs to `crates/telemetry` rather than to a consumer
because `option_env!` reads the environment of the crate being compiled — a
`rustc-env` emitted by `crates/tui`'s build script is not visible to
`crates/telemetry`. Its rerun conditions are the two SHA variables and
nothing about `.git/HEAD`: watching HEAD would rebuild on every local commit
for a value that is `None` on every local build by design.
2026-08-03 20:32:42 -07:00
Hmbown f31f23f401 feat(telemetry): add the codewhale-telemetry leaf crate
A complete, tested telemetry client with no callers. It ships inert: nothing
in the tree calls `init`, so nothing is collected, nothing is written, and no
directory is created by this commit.

The crate is placed at `crates/telemetry` because a module under
`crates/tui/src/` is unreachable from two of the three consumers —
`codewhale-cli` does not depend on `codewhale-tui`, `codewhale-tui` does not
depend on `codewhale-cli`, and app-server is linked into the `codewhale`
binary. It depends on `codewhale-config`, whose own dependencies are
execpolicy, paths, and secrets, so there is no cycle and no plausible future
one: config has no reason to consult telemetry state. That dependency is what
lets the emit predicate live in exactly one function instead of being
re-derived at six init sites.

Consent is a value, not a convention. `decide()` is the only constructor of
`TelemetryConsent`; `init` takes one by value and there is no bool-taking
sibling. `TelemetryDecision` splits `OptedOut` from `ForcedOff` because
"telemetry resolved to false" is the default state of every installation — a
wipe keyed on it would delete a consenting user's identity and unflushed
buffer every time they ran one command with a transient
`CODEWHALE_TELEMETRY=0`, which is the recipe the runtime docs prescribe. Only
`OptedOut` touches disk. `ForcedOff` touches nothing, ever, and a test asserts
a seeded home is byte-identical across every `ForcedOff` row.

Arming is a `OnceLock` consulted by every write path, `record_blocking`
included. The process panic hook is installed before the command line is
parsed, long before any config resolution, so it cannot consult a resolved
value — but it can consult a lock that is by construction empty until
resolution completes. A disabled user's panic therefore writes nothing.
Arming also truncates the buffer, so no event recorded before consent can be
in the batch that follows it.

Appends never take a lock. One `O_APPEND` write under `PIPE_BUF`, then
`sync_data`. `flock` is per-fd within a process, so a blocking acquisition on
the panic hook would self-deadlock if the writer thread panicked holding the
compaction lock, and a second process sharing `CODEWHALE_HOME` would hang
Ctrl-C. Compaction is the only lock holder and uses `try_write`. The drain
tolerates a torn trailing line, because `std::process::exit` on the signal
path can cut a concurrent write.

The wipe truncates rather than unlinks — replacing the file would leave
appenders on a dead inode, and unlinking the sibling lock file would leave
holders serialising against nothing. It writes the `disabled` tombstone first
and never removes it, every append and every send re-checks that tombstone,
and a failed wipe therefore fails closed: the tombstone alone makes the buffer
permanently undrainable.

Transport requires `https://`. Plaintext is permitted only for loopback, where
a batch never reaches a wire. There is no environment variable that overrides
this, and `CODEWHALE_ALLOW_INSECURE_HTTP` is deliberately not consulted: that
variable authorizes an insecure *provider* base URL for harnesses that
intercept model traffic, and honouring it here would let that decision also
authorize telemetry POSTs to an arbitrary host. With no endpoint configured —
the shipped default — no HTTP client is constructed at all and batches go to
`dryrun.jsonl`.

`install_id` is `Uuid::new_v4`, never derived from hostname, MAC,
`machine-id`, home, username, or executable path, and it rotates every 90 days
with `rotated_at` recorded. A derived id is a device fingerprint that survives
reinstall and re-identifies a user across their own opt-out.

The schema has no free-form string type and no open-keyed map. `counters` and
`errors` are structs of named `u32` fields rather than maps, so the key set is
closed by the compiler. `every_payload_field_is_bounded` walks a
fully-populated batch and asserts every string leaf is a member of a declared
enum set or one of three regexed strings. The scrubber assertions run
`redact_for_disclosure` per string leaf and never over the serialized
document: that function tokenizes on spaces, and a compact JSON batch is one
token, so a document-level check would report clean on a payload containing a
path, a key, and a whole prompt.

`ResolvedRuntimeOptions` gains `telemetry_endpoint`, resolved from
`CODEWHALE_TELEMETRY_ENDPOINT` then the config file, because `decide` needs to
see the endpoint to refuse a bad one. `crates/workflow` exports its redaction
module so the leak assertions above can use it.
2026-08-03 20:30:20 -07:00
Sun Zhenyuan e3a45388fc fix(tui): pin ratatui to 0.30.0
ratatui-core 0.1.1+ queries cursor position in Terminal::clear, racing
our input loop at startup ("The cursor position could not be read within
a normal duration"). Pin ratatui =0.30.0 and ratatui-core =0.1.0 until
upstream fixes it (ratatui/ratatui#2483, #2640).

(cherry picked from commit cc8bbb1bb57f5968da425b0bd5ab06ec85ea8de0)
2026-08-03 03:18:32 -07:00
Hmbown fb358d092f refactor(tui): delete the Starlark execpolicy half and the check verb
The execpolicy module shipped two engines: the live TOML rules
(rules.rs, feature-gated shell enforcement) and a legacy Starlark
PolicyParser tree (decision/error/policy/rule/parser*) that only the
`execpolicy check` CLI verb consumed. The runtime never enforced
Starlark policy, so a green check validated a format nothing reads.

Deletes the Starlark tree, the `execpolicy check` verb and its
dispatch, the blanket #![allow(dead_code)] in mod.rs, and the now
unused starlark + multimap dependencies. The TOML rules engine and
matcher stay; the codewhale-execpolicy crate's permission engine is
untouched.

Evidence: cross-surface-tech-debt-audit-2026-08-03.md §1.3
'execpolicy check validates a format the runtime ignores'; §11.3 row 3.
Gate: cargo test -p codewhale-tui --bin codewhale-tui execpolicy
-> 11 passed, 0 failed.
2026-08-02 20:59:24 -07:00
Hmbown fd65176f56 chore(release): prepare v0.9.4 source candidate
Date the v0.9.4 changelog, preserve contributor credit and unpublished-release truth, and synchronize every crate, npm, lockfile, smoke-test, and generated public-fact version surface. This prepares a source candidate only; it does not tag, publish, or create a release.

Signed-off-by: Hmbown <101357273+Hmbown@users.noreply.github.com>
2026-08-01 10:12:09 -07:00
Hmbown f3e0f3d4f8 feat(account): share secure session identity with runtime
Move the existing CLI session envelope and profile/origin slot into codewhale-secrets so CLI, TUI, and Runtime read one secure record. Runtime info now advertises account_session and an authenticated, token-free account receipt with durable IDs, explicit stored scopes, and normalized cached/expired/revoked states. Anonymous probes stay signed out and never read secure storage; local signed-out Work remains valid.

(cherry picked from commit a580678503)
Signed-off-by: Hmbown <101357273+Hmbown@users.noreply.github.com>
2026-08-01 10:12:02 -07:00
Hmbown d527160666 feat(account): add managed CLI sign-in
Add browser device login, profile-scoped OS credential storage, refresh and logout, and redacted BYOK vault management for the Codewhale account. Keep provider auth separate and retain cloud as a compatibility alias.

(cherry picked from commit 3c7a614d7c)
Signed-off-by: Hmbown <101357273+Hmbown@users.noreply.github.com>
2026-08-01 10:11:54 -07:00
dependabot[bot] 2a094708ba chore(deps): bump ratatui from 0.30.0 to 0.30.2 (#5013)
Update ratatui to 0.30.2 and teach the PTY acceptance harness to answer standard, DEC, and chunk-split cursor-position queries.

Cargo audit, deny, exact PTY lifecycle, underwater presentation, all platform tests, lint, security, and source-budget checks pass.
2026-08-01 04:57:15 -07:00
dependabot[bot] 0abdc70af9 chore(deps): bump libc from 0.2.186 to 0.2.189 (#5016)
Update libc to 0.2.189 and refresh the locked graph.

Cargo audit, deny, all platform tests, lint, security, and source-budget checks pass.
2026-08-01 04:56:35 -07:00
dependabot[bot] 63e819ad0a chore(deps): bump futures-util from 0.3.32 to 0.3.33 (#5015)
Update futures-util to 0.3.33 and refresh the locked graph.

Cargo audit, deny, all platform tests, lint, security, and source-budget checks pass.
2026-08-01 04:55:55 -07:00
dependabot[bot] d20a8c48ad chore(deps): bump clap_complete from 4.6.7 to 4.6.8 (#5014)
Update clap_complete to 4.6.8 and refresh the locked graph.

Cargo audit, deny, all platform tests, lint, security, and source-budget checks pass.
2026-08-01 04:55:15 -07:00
dependabot[bot] 30f8f7b776 chore(deps): bump globset from 0.4.18 to 0.4.19 (#5011)
Update globset to 0.4.19 and refresh the locked graph.

Cargo audit, deny, all platform tests, lint, security, and source-budget checks pass.
2026-08-01 04:54:34 -07:00
Sh1Zuku 0010326469 fix(tui): measure circled digits and keycaps as 2 columns everywhere (#4479) (#5001)
The v0.9.1 fix (9be600bf4) made CodeWhale's own width helpers grapheme-
aware, but Ratatui's renderer still measures ambiguous-width characters
through its own unicode-width dependency, whose width() reports
Enclosed Alphanumerics (circled digits/letters) as 1 column while CJK
terminals paint them as 2. The 1-column cell placement shifts every
downstream column by one, producing the intermittent missing-characters
/ phantom-space glitch that mouse selection only temporarily repairs.

Fix the width accounting at all three layers:

1. Patch unicode-width 0.2.2 via [patch.crates-io] so UnicodeWidthChar::
   width() and UnicodeWidthStr::width() report 2 columns for Enclosed
   Alphanumerics (U+2460-U+24FF), Dingbat Circled Digits (U+2776-U+2793),
   and Circled Numbers on Black Square (U+3248-U+324F). The patch is
   scoped to exactly those ranges; box-drawing glyphs and ellipsis keep
   their 1-column non-CJK widths, so table borders and truncation do not
   regress.
2. Keep the explicit range override in char_display_width as a defense
   in depth that does not depend on the patch.
3. Keep the U+20E3 override in grapheme_display_width /
   markdown_grapheme_width for keycap sequences that lack an FE0F
   variation selector (unicode-width reports those as 1 column too).

Adds regression tests asserting UnicodeWidthChar::width('\u{2460}') ==
Some(2) and that circled digits count as 2 columns in text_display_width,
plus the existing keycap suite. 26 ui_text tests and 52 markdown_render
tests pass.
2026-07-31 21:34:52 -07:00
Hunter Bown e8d2538a9d fix(ci): repair three shared check failures blocking open PRs (#5017)
* fix(ci): skip the PR-issue link gate for bot-authored PRs

The `link` job fails on every dependabot PR (e.g. #5010-#5016): dependabot
bodies are machine-generated dependency bumps that never contain a closing
keyword, so the gate exits 1 ("This PR neither closes an issue nor says why
it doesn't", run 30665868640/job 91272641294).

Skip the gate step when the PR author is a GitHub-verified bot account
(`pull_request.user.type == 'Bot'`, set by GitHub so it cannot be spoofed).
This covers dependabot today and any other automation (renovate, etc.)
without hand-editing every bot body, while keeping the gate strict for every
human PR.

Policy choice: all verified bots, not just dependabot[bot]. Bots cannot
respond to a prompt, and their changes never close issues; the check's
purpose is keeping human work linked to its issue.

* fix(ci): provision the deny container's Rust toolchain explicitly

cargo-deny runs inside EmbarkStudios/cargo-deny-action@v2's alpine container
(host triple x86_64-unknown-linux-musl, rustup default 1.85.0-musl). The
repo's rust-toolchain.toml pins `channel = "stable"` with no target, so the
action's `rustup show` (entrypoint.sh) resolves it to
`stable-x86_64-unknown-linux-musl` — not installed — and every run prints:

  error: override toolchain 'stable-x86_64-unknown-linux-musl' is not
  installed: the toolchain file at '/github/workspace/rust-toolchain.toml'
  specifies an uninstalled toolchain

then auto-installs mid-run (network-dependent ~12s detour; hard failure if
the download stalls; seen in run 30665967039/job 91272951661).

Pass `rust-version: stable` so the entrypoint provisions the channel
(`rustup default stable`, same musl host triple) before cargo-deny touches
the workspace; the toolchain file then resolves to an installed toolchain.

* fix(deps): bump event-listener 5.4.1 -> 5.4.2 (RUSTSEC-2026-0221)

cargo-deny (advisories) fails on every dependabot PR (e.g. run
30665967039/job 91272951661) with the unsound advisory:

  error[unsound]: `event-listener` allows `!Send` tags to cross thread
  boundaries via `StackSlot`  (RUSTSEC-2026-0221)

event-listener 5.4.1 unconditionally implements Send/Sync for StackSlot,
allowing a !Send tag to cross threads via Event::with_tag. Pulled in via
codewhale-tui -> codewhale-workflow-js -> rquickjs -> rquickjs-core ->
async-lock (and event-listener-strategy). Advisory solution: >=5.4.2, the
latest published version.

`cargo update -p event-listener` lands exactly on 5.4.2 (also drops its now
unneeded concurrent-queue dependency). No deny.toml ignore needed.

* fix(ci): fetch the locked graph before the offline runtime-contract check

The Lint job's "Check runtime-contract budget" step fails on every
dependabot PR (run 30665967040/job 91273020141, PR #5016):

  error: failed to download `assert-json-diff v2.0.2`
  Caused by: attempting to make an HTTP request, but --offline was specified
  subprocess.CalledProcessError: ... returned non-zero exit status 101.
  [runtime-contract-budget] ERROR: runtime-contract measurement failed with exit code 1

Root cause: check-runtime-contract-budget.py hardcodes CARGO_NET_OFFLINE=true
and runs `cargo test --locked` on codewhale-tui, which needs the dev-dependency
graph (wiremock -> assert-json-diff). Clippy above builds no test targets, and
Swatinem/rust-cache keys its registry cache on Cargo.lock, so any lock-changing
PR (every dependabot bump) misses the cache and the offline test cannot
download the missing crate. Reproduced locally with the identical error by
removing assert-json-diff from the registry cache and re-running the exact
CI command.

Fix: fetch the full locked graph (`cargo fetch --locked`, dev-deps included)
once in the heavy path before the measurement, so the hermetic offline
measurement is deterministic on every branch. The budget contract itself is
unchanged and still enforced.

* fix(ci): reviewed source-structure budget update for merged LaTeX module

Merging #4981 (LaTeX rendering) added crates/tui/src/tui/history/
latex_render.rs (1734 lines) and grew aggregate owned Rust source to the
measured merged state (644756 lines). The ratchet's contract requires an
explicit reviewed update for new thousand-line modules and aggregate
growth; this commit allows the merged module and raises the ceiling to
the exact CI-measured value. Verified: check-source-structure-budget.py
PASS on the branch; runtime-contract budget still PASS 55/55.
2026-07-31 21:21:07 -07:00
Hmbown 2583b0710c chore(release): prepare v0.9.3
Bump the Rust workspace, internal dependency pins, npm wrapper, lockfiles, public candidate facts, remote smoke default, generated site facts, and packaged changelog together.\n\nAdd dated release notes for DeepSeek V4 Flash Responses support, the canonical action cutover, safety and durability hardening, and contributor credit. Public published-release facts remain pinned to v0.9.2 until v0.9.3 assets actually exist.
2026-07-31 02:28:28 -07:00
Hmbown 13f1e413ce feat(auth): add pipe-only API key handoff
Resolve the requested provider through the same runtime config and secret-store precedence used by dispatch, while rejecting OAuth and imported bearer routes.

Refuse terminals before credential resolution, zeroize the transient string, emit only the secret plus newline, and settle closed pipes without logging credential or operating-system error details. Centralize shared auth-mode classifiers in config and delete the TUI copies.

Work item: v0.9.3 explicit credential handoff (API-key slice).
2026-07-30 22:17:48 -07:00
Hmbown 115bbf4636 refactor(tui): remove bundled PDF parser chain
Delete pdf-extract, lopdf, and their parser/font transitive dependencies. File and web tools now share one optional pdftotext adapter with typed binary-unavailable behavior, while raw PDF downloads remain preservable without text extraction.

Remove the obsolete extractor preference from runtime and configuration surfaces while retaining read-only compatibility for legacy settings files. Hermetic fake-binary tests pin file, fetched-byte, page-window, and missing-binary behavior; local Poppler and diagnostic/config regressions cover the production route.

Local work item: #4382
2026-07-30 17:56:03 -07:00
Hmbown e6d1a316d3 fix(tui): share user state path authority
Delegate TUI parsing of CODEWHALE_HOME and platform home variables to codewhale-paths across config, credentials, logs, automations, sessions, tasks, runtime API fallback, runtime-thread roots, and resource admission. Preserve explicit subsystem directory overrides and ambient legacy fallbacks while treating whitespace and non-Unicode overrides consistently.

Make explicit CODEWHALE_HOME an isolation boundary for config-owned skills, MCP, notes, memory, tasks, and test-only file-secret access. Add cross-entry regressions proving ambient credentials and legacy state are not consulted across that boundary while whitespace retains normal ambient compatibility.

Retain the includable skill CLI config leaf and the Windows HOMEDRIVE/HOMEPATH compatibility fallback in the shared resolver.
2026-07-30 09:02:43 -07:00
Hmbown d8f196dc6c fix(paths): unify user runtime home resolution
Introduce a leaf codewhale-paths crate so config, state, secrets, and CLI metrics share CODEWHALE_HOME normalization and HOME/USERPROFILE precedence. Preserve explicit-home isolation for non-Unicode Unix paths and keep legacy fallback decisions with each owning subsystem.

Add a public-API integration regression covering whitespace overrides, home precedence, legacy fallback, and non-Unicode isolation across config, state, and secrets.
2026-07-30 08:19:42 -07:00
Hunter Bown 654ba9d848 Merge pull request #4931 from raphamorim/main
Migrate QA PTY test harness from vt100 to rio-vt
2026-07-28 20:24:54 -07:00
Raphael Amorim 1d8c577dbd bump rio-vt to 0.5.1 to drop net2 2026-07-28 08:46:46 +02:00
Hmbown c1b47796ce chore: land the bounded dead-code slice and stop the wall regrowing
#4785 asks for a 481-attribute sweep. This is not that sweep — it is the part
that can land without judgment calls, plus the gate that makes the sweep worth
doing.

The measurements are why the gate comes first:

    issue filed   464 attributes / 143 files
    2026-07-26    426 / 111
    2026-07-28    481 / 155

The sweep was working and the total still rose, because two large landings this
week added state whose accessors only their own tests read. A sweep is a
snapshot; a budget is a direction.

scripts/check-dead-code-budget.py counts `allow(dead_code)` across crates/ and
fails when the total exceeds a committed ceiling. Lowering it is free; raising
it needs `--update` and a sentence in the PR, so growth becomes a visible
decision rather than an accident. On failure it prints per-crate now-vs-budget
and marks which crate grew. Wired into Lint next to the other check-*.py gates.

This covers a real blind spot: CI's clippy runs without `--all-targets`, so it
never lints cfg(test) or integration-test code — and a prior strip-and-check
measured 197 attributes alive *only* because a test references them.

The three zero-risk removals from the issue's cluster list:

- `codew_legacy_shim.rs` -> `codew.rs`. The issue calls this out as a rename,
  not a deletion: it is the live `codew` binary and its own docs call it
  permanent. Every sweep flags the filename; the binary name is unchanged.
- `clap` dropped from app-server and `thiserror` from lane — no source in
  either crate references them. Cargo.lock loses exactly those two edges and no
  package versions move.

The budget baseline is recorded at today's 482, not at an aspirational number,
so the gate starts truthful and the sweep can ratchet it down.
2026-07-27 18:53:25 -07:00
Raphael Amorim 5525c1e17b migrate qa pty test harness to rio-vt 2026-07-27 21:05:16 +02:00
Hmbown 0faa44a74b release: bump workspace, npm, and release metadata to 0.9.2
Cuts the [0.9.2] - 2026-07-27 changelog band from [Unreleased] and
runs scripts/release/prepare-release.sh 0.9.2: workspace and crate
versions, internal dependency pins, npm package + binary version,
lockfile, remote-smoke default tag, README install examples, public
fact matrix source-candidate version, crate changelog sync, and
regenerated web facts. check-versions.sh confirms the set agrees.

This is local release preparation only: no tag, no publication, no
push to protected main.
2026-07-27 05:59:01 -07:00
Hmbown f21c4e7958 merge: saved exact Fleets + reasoning Router harvest
# Conflicts:
#	crates/tui/src/fleet/mod.rs
#	crates/tui/src/model_inventory.rs
#	crates/tui/src/tui/model_picker.rs
2026-07-27 04:45:05 -07:00
Hmbown 9c3177e549 feat(workflow): add exact Fleet schema, Reasoning Router, and disclosure redaction
Introduces the Rust-owned core of Saved Fleets and the Reasoning Router as
pure library modules, with no runtime call path yet. Everything here is
parse/resolve/verify logic that later slices wire into the TUI.

- `fleet_exact`: the exact Fleet schema. Routes are frozen — provider and
  model are literals, and the selector tokens that are legal elsewhere in
  CodeWhale (`auto`, `inherit`, `faster`, ...) are rejected by value, while
  `deny_unknown_fields` rejects `model_strength`/`loadout`/`model_class` as
  keys. Roles compare under one canonical key, so the `oracle`/`advisor` →
  `consultant` rename resolves in both directions.
- `reasoning_router`: the saved Router service profile, addressable either
  as a named reference several Fleets can share or as a legacy inline
  router captured from an older file.
- `fleet_reasoning`: requested → effective reasoning resolution, the Router
  call plan, and strict decision parsing. Parsing rejects trailing content,
  duplicate keys, and anything that would mutate a frozen route, so a
  malformed Router reply fails closed rather than silently re-routing.
- `fleet_preflight`: endpoint identity and credential readiness, evaluated
  before any Router spend.
- `fleet_snapshot`: content-addressed Fleet snapshots. Hashes are
  recomputed and validated on read, and no absolute path is ever captured.
- `redaction`: the single chokepoint that strips secrets and absolute paths
  out of anything bound for a receipt.

`named_fleet` gains `FleetSearchRoot`/`FleetDocument`/`load_by_name` and is
committed together with the leaf modules rather than ahead of them: it
imports `fleet_exact` and `fleet_snapshot`, and those import back, so the
two halves do not compile apart.

Two corrections to the harvested source:

- `redaction` dropped a dead `arm` reset that `redact_token` overwrites
  before it can be read. Behavior is unchanged; the assignment only tripped
  `unused_assignments` under `-D warnings`.
- `legacy_advisory_role_names_canonicalize_to_consultant` asserted that a
  lookup by the legacy spelling returns `None`, which contradicts both the
  module's stated contract and `member_by_role`, which canonicalizes the
  lookup key. The assertion now checks that both spellings land on the same
  member. This is a test fix, not a behavior change.

Tests: 222 workflow lib tests, 16 exact_fleet_workflow integration tests.
2026-07-27 02:51:53 -07:00
Hmbown cc9cf2d2ce harvest(cost-truth): reland #4797 truthful cost accounting on the v0.9.2 candidate
Replays the archived cost-truth working tree
(backups/v092-cost-truth-dirty-20260727-0205) onto the integration
candidate at c46eb2936, 54 commits ahead of the base the work was
written against.

The overlap that mattered is the provider reasoning-route-truth series
(039f8d81b..ed8bdf395), which introduced `TurnRoute::receipt` — a
secret-free proof of the endpoint and credential generation the turn's
client was *installed* on. The cost work independently introduced a
dispatch-time billing envelope on the same struct. These are not
alternatives: the receipt answers "which credential/endpoint was
installed", the envelope answers "what exactly went on the wire and how
is it billed". Both are kept, and `Event::RouteDispatched` now narrows
the route at the true wire boundary while `TurnStarted` keeps carrying
the installed-route receipt that hosts already authorize against.

Structural drift resolved by intent rather than text:

- Sub-agent mailbox construction moved into
  `build_turn_tool_registry_and_catalog`. The turn-end flush barrier
  (seal -> flush -> await drainer) has to be owned by the code that
  emits `TurnComplete`, so the handle escapes through a new
  `TurnMailboxBarrier` on `TurnToolBuild` instead of being a local.
  Without this, detached-child usage was "whatever arrived in time".
- The drainer now stamps `turn_id` on every envelope, matching the
  base's `(turn_id, seq)` dedup contract.
- `capture_turn_started_metadata` became a match so `RouteDispatched`
  can refresh the active route without re-opening the auto-route or
  prompt-suggestion authority decisions already taken at turn start.

No behavior is asserted as fixed here; this commit only makes the
harvested work compile and hold both invariants. The nine release-audit
blockers are addressed in the commits that follow.
2026-07-27 02:16:07 -07:00
Hunter B 565ab2c853 fix(fetch_url): decode declared non-UTF-8 text safely
Harvested from PR #4909

Preserve BOM and transport precedence, bound HTML-only meta sniffing, and keep binary fallbacks truthful.

Co-authored-by: h3c-hexin <13790929+h3c-hexin@users.noreply.github.com>
2026-07-26 23:33:51 -07:00
Hunter Bown 54f4634d5d feat(memory): start local-native v0.9.2 memory lifecycle (#4870)
* feat(memory): add local-native markdown and FTS5 store

Add an explicit native memory backend and a rebuildable SQLite FTS5 index beneath the CodeWhale home. Expose reviewed remember, search, reindex, delete, status, and non-destructive legacy import through /memory native while preserving old configs. Keep Markdown as the source of truth and carry provenance into search results.

* feat(memory): refresh direct edits and scope by repository origin

Keep Markdown authoritative during search and derive workspace memory from the repository origin so worktrees share durable scope without trusting arbitrary paths. Add tests for direct edits, shared origins, unrelated directories, and automatic workspace capture.

* feat(memory): wire native recall into engine prompts

Use the explicit native backend path for engine prompt assembly, refresh bounded Markdown recall on first turn and prompt refresh, and preserve the lower-authority untrusted-data boundary. Keep Moraine and legacy configurations unchanged.

* feat(memory): complete local memory controls

Add provenance-aware get and export operations and make global, workspace, and all-scope deletion safe. Preserve the remaining Markdown source tree when deleting one scope and verify the controls together.

* fix(memory): serialize cross-process native writes

Use the existing fd-lock dependency around reviewed imports, writes, reindex, and deletion. Add a concurrency regression so separate processes cannot lose notes while Markdown remains the source of truth.

* fix(memory): rebuild corrupt native index safely

Detect unsupported cache schema and SQLite corruption, discard only the disposable cache, and rebuild from Markdown. Add regression coverage for both recovery paths.

* fix(memory): serialize cache repair and scope native search

Keep cache initialization and disposable-file repair under the native memory write lock, refresh only changed Markdown sources, and apply workspace scope in SQL before ranking limits. Preserve newest prompt entries when character bounds apply.\n\nNo-Issue: follow-up fixes for #4867 acceptance and review findings.

* test(memory): include native subcommand in help expectation
2026-07-26 03:31:50 -07:00
dependabot[bot] 74de0d203e chore(deps): bump shellexpand from 3.1.1 to 3.1.2 (#4773)
Bumps [shellexpand](https://gitlab.com/ijackson/rust-shellexpand) from 3.1.1 to 3.1.2.
- [Commits](https://gitlab.com/ijackson/rust-shellexpand/compare/shellexpand-3.1.1...shellexpand-3.1.2)

---
updated-dependencies:
- dependency-name: shellexpand
  dependency-version: 3.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-25 04:17:38 -05:00
dependabot[bot] bec32587cd chore(deps): bump jsonschema from 0.48.1 to 0.48.5 (#4772)
Bumps [jsonschema](https://github.com/Stranger6667/jsonschema) from 0.48.1 to 0.48.5.
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/ruby-v0.48.1...ruby-v0.48.5)

---
updated-dependencies:
- dependency-name: jsonschema
  dependency-version: 0.48.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-25 04:17:35 -05:00
dependabot[bot] f01511a727 chore(deps): bump ignore from 0.4.30 to 0.4.31 (#4775)
Bumps [ignore](https://github.com/BurntSushi/ripgrep) from 0.4.30 to 0.4.31.
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/ignore-0.4.30...ignore-0.4.31)

---
updated-dependencies:
- dependency-name: ignore
  dependency-version: 0.4.31
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-25 01:49:24 -05:00
dependabot[bot] 9a5adb7b65 chore(deps): bump rquickjs from 0.12.0 to 0.12.1 (#4774)
Bumps [rquickjs](https://github.com/DelSkayn/rquickjs) from 0.12.0 to 0.12.1.
- [Changelog](https://github.com/DelSkayn/rquickjs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/DelSkayn/rquickjs/compare/v0.12.0...v0.12.1)

---
updated-dependencies:
- dependency-name: rquickjs
  dependency-version: 0.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-25 01:49:16 -05:00
dependabot[bot] c09f72d768 chore(deps): bump base64 from 0.22.1 to 0.23.0 (#4771)
Bumps [base64](https://github.com/marshallpierce/rust-base64) from 0.22.1 to 0.23.0.
- [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/marshallpierce/rust-base64/compare/v0.22.1...v0.23.0)

---
updated-dependencies:
- dependency-name: base64
  dependency-version: 0.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-25 01:49:08 -05:00
Hunter B c6b0f9675e Revert the 0.9.2 version promotion, keep the fixes
Bumping the workspace to 0.9.2 was premature. 104 issues are still open on
the milestone and none of this branch's fixes have merged, so the version
claimed a release that is nowhere near cut. The repository's own gates said
so: the contributor ledger wanted a 0.9.2 credit band that cannot honestly
exist yet, and `screenshot.sourceVersion` wanted the TUI capture retaken at
0.9.2. Those are release acts, not bookkeeping.

Everything version-shaped goes back to 0.9.1 — workspace, crate pins, npm
wrapper and its binary pointer, lockfiles, generated web facts, mirrored
TUI changelog, docs, remote-smoke default, and the public surface matrix.

The changelog entry is kept, moved under [Unreleased], which is what it
actually describes: landed since v0.9.1, not yet released. The [Unreleased]
compare link returns to v0.9.0...HEAD, the form the surface contract
accepts while the workspace version is the published one.

Deliberately kept from the promotion work: the contract test now derives
the version from FACTS instead of hard-coding it, so the next real bump
does not break it; the postcss advisory fix; and the `tracing` dependency
the lane fix needs.

check-versions.sh passes, the release-tag and prepare-release guards pass,
and all 136 web tests pass.
2026-07-24 20:57:16 -07:00
Hunter B 82f068e5a7 chore(release): prepare Codewhale 0.9.2
Promote the workspace, internal crate pins, npm wrapper, package lock,
generated web facts, mirrored TUI changelog, current docs, and the
remote-smoke default from 0.9.1 to 0.9.2.

The 0.9.2 entry is an unreleased source candidate covering the audit bug
cluster landed on this lane: execpolicy deny-rule flag bypass and
platform-conditional path case folding, MCP call-once and deterministic
qualified-name resolution, session-index torn-line survival and
append/compaction locking, the shared write-tool allowlist, and the four
fixes from this takeover — the config no-op that tore down the runtime
bridge, broken project configs that were indistinguishable from absent
ones, lane branches outliving their expired worktrees, and stdio turns
that could not be interrupted.

check-versions.sh passes. No tag, release, or publish step is implied by
this commit; the changelog heading stays "Unreleased candidate" until a
release actually dates it. The issues themselves are closed by the
commits that fixed them, not by this one.
2026-07-24 20:28:04 -07:00
Claude cb1ad677b0 fix(lane): delete the lane branch when its expired worktree is removed
`remove_worktree_if_expired` removed the worktree directory and stopped
there, leaving behind the branch created alongside it. Lane branch names
are derived from the user's launch name (`codex/{slug}`), not from a
UUID, so reusing that name afterwards failed with "branch already
exists" — blocked by a branch whose worktree no longer existed. A
legitimate lane-reuse path, reproducibly broken.

Cleanup now asks the worktree which repository owns it and which branch
it has checked out before deleting the directory, since neither is
recoverable from the path once the directory is gone.

Branch deletion uses `branch -d`, not `-D`. A lane branch with nothing on
it beyond its base is merged and deletes cleanly, which is the case that
was broken. A branch carrying unmerged commits is someone's work, and a
TTL timer is not a mandate to throw it away: that one is kept and its
name stays taken until a human decides otherwise. Both behaviors are
tested.

`git worktree remove` also ran without a working directory, so it
depended on the calling process's cwd being inside the repository and
otherwise silently fell through to `rm -rf`, leaving git's registration
of the worktree intact — which `git worktree add` then refuses. It now
runs in the discovered repository root, and the fallback path prunes.

Closes #4731

Signed-off-by: Claude <noreply@anthropic.com>
2026-07-25 03:10:23 +00:00
Claude 0c4dc65760 fix(state): survive a torn session-index line, and lock compaction
`session_index_map` propagated the first JSON parse error with `?`, so a
single bad line aborted the whole read. Appends were unsynced `writeln!`
calls, so a crash mid-write left exactly that: a truncated final line.
Every thread-name lookup funnels through this function, so one torn write
broke all of them at once — and because compaction reads through the same
function to build its rewrite, the index's own self-repair path failed on
the same line. The file could not recover on its own; a user had to delete
it by hand. Unparseable lines are now skipped with a warning, and appends
`sync_data` so the torn line is far less likely in the first place.

Compaction snapshots the index, writes the snapshot to a temp file, and
renames it over the live one, with no coordination against appenders. An
append from a second process landing between the snapshot and the rename
was discarded — silently, since the append had already returned success.
Append and compaction now share an exclusive lock on an adjacent `.lock`
file, the same discipline `codewhale-config` uses for `config.toml`. The
lock is a sibling file rather than the index itself so the rename cannot
pull it out from under a waiter, and `append_thread_name` takes it once
and calls a `_locked` compaction to avoid re-entering it.

The race is real but narrow, so the test drives it deterministically: a
`#[cfg(test)]` hook at the compaction midpoint releases a racing appender
and waits. Reverting the lock fails the test.

Closes #4735
Closes #4736
2026-07-25 02:54:32 +00:00
Claude cccaa4f3f8 fix(mcp): call a tool once, and resolve qualified names deterministically
`call_qualified_tool` had a fast path guarded by `let Ok(result) = self
.call_tool(...)`, so a *call* failure — an ordinary outcome for a flaky
server — made the whole condition false and fell through to a
re-resolution loop that called the same server and tool again. If that
loop's linear scan didn't match, a third invocation followed. For a file
write, a git commit, a message send, or a paid API call, each retry is a
real side effect the caller never asked for, and the original error was
discarded rather than reported. The fast path now returns whatever the
tool returns, error included; only a failed *lookup* falls through.

The fallback scan returned the first server whose qualified name matched,
iterating a HashMap. Because `sanitize_component` folds `my-server`,
`my_server`, and `My.Server` to the same `mcp__my_server__*` prefix, two
differently-named servers could both answer one qualified name, and which
one ran depended on iteration order — letting a carelessly or maliciously
named server shadow tool calls meant for a trusted one. The scan now
collects every match and errors on more than one instead of silently
picking, and `register_server` refuses a name that collides with an
already-registered server after sanitizing. Re-registering the same name
still replaces it, so restart is unaffected.

`register_server` returns `Result` as a result; the stdio bootstrap logs
and skips a colliding server rather than shadowing the one already
holding the prefix, and the `server/register` and `server/start` handlers
surface the collision as an invalid-params error.

Closes #4728
Closes #4729
2026-07-25 02:39:58 +00:00
dependabot[bot] 8814619948 chore(deps): bump tower from 0.5.2 to 0.5.3
Bumps [tower](https://github.com/tower-rs/tower) from 0.5.2 to 0.5.3.
- [Release notes](https://github.com/tower-rs/tower/releases)
- [Commits](https://github.com/tower-rs/tower/compare/tower-0.5.2...tower-0.5.3)

---
updated-dependencies:
- dependency-name: tower
  dependency-version: 0.5.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 23b7a95e2822dce6fd2fb9489939abb1afa5be4c)
2026-07-23 19:51:41 -07:00