# Clavue Upgrade Plan v9.0 — World-class Leap

> Recovered from session `13769593-769a-421e-abaa-9af6c0083456` (msg
> `96fcbb18-8f57-4c61-b33f-28793ec9a885`, 2026-05-16). Persisted here so
> later sessions can resume against a stable checklist instead of mining
> the jsonl. Edit only when the plan itself changes.
>
> **Goal**: 8.4/10 → 9.5+, align with upstream Claude Code 2.1.143
> capability surface while honoring `ARCHITECTURE.md`'s "one runtime
> decision per turn" promise.
>
> **Method**: Karpathy autoresearch loop. Every slice carries hypothesis,
> metric, evidence, keep/discard.
>
> **Principles**: never break `npm run check`; each phase independently
> shippable; high-blast-radius actions land in their own slice.

---

## P0 — Architectural baseline (Week 1, must precede everything)

### Slice 0.1 · Split `src/query.ts` (1905 lines → 4 modules < 500 each)

**Hypothesis**: lower per-module complexity, statically resolvable feature
flags, smaller bundle, type inference recovers.

**Scope** (zero behavior change):
- New `src/query/featureBoot.ts` — centralize the 6 dynamic `require()`
  loads (reactiveCompact / contextCollapse / skillPrefetch / jobClassifier
  / snipCompact / taskSummary). Expose `getFeatureModules()`. **Prefer
  static import; fall back to require only when the module is missing.**
- New `src/query/turnLoop.ts` — single-turn main loop (`query()` body,
  stream handling, tool-block collection).
- New `src/query/recovery.ts` — retry / fallback / `PROMPT_TOO_LONG` /
  context-window recovery.
- New `src/query/toolDispatch.ts` — glue currently calling `runTools`
  inline (avoid duplicating `services/tools/toolOrchestration.ts`).
- `src/query.ts` shrinks to a ~50-line barrel re-exporting `query` /
  `QueryParams`.

**Verify**: `npm run check`, `test:route-policy`, `test:workflow-continuity`,
`test:coding-regression`. **Keep** if all green and `wc -l src/query.ts`
< 100, bundle size up < 1%.

### Slice 0.2 · Split `src/services/api/claude.ts` (3583 lines → 6 domain modules)

Pure shuffle, no behavior change.

- `claude/cacheControl.ts` — `getCacheControl`, `should1hCacheTTL`,
  `addCacheBreakpoints`, `getPromptCachingEnabled`, all cache breakpoint
  logic.
- `claude/effort.ts` — `configureEffortParams`, `configureTaskBudgetParams`.
- `claude/streaming.ts` — `queryModelWithStreaming`,
  `executeNonStreamingRequest`, `cleanupStream`,
  `adjustParamsForNonStreaming`.
- `claude/usage.ts` — `updateUsage`, `accumulateUsage`, token accounting.
- `claude/messages.ts` — `userMessageToMessageParam`,
  `assistantMessageToMessageParam`, `stripExcessMediaItems`,
  `buildSystemPromptBlocks`.
- `claude/maxTokens.ts` — `MAX_NON_STREAMING_TOKENS`,
  `getMaxOutputTokensForModel`, `isMaxTokensCapEnabled`.
- `claude.ts` keeps only `queryWithModel`, `queryHaiku`, `verifyApiKey` +
  barrel re-exports. Target < 600 lines.

**Verify**: `npm run check`, plus `tests/api-claude-public-surface.test.mjs`
locking the export list.

### Slice 0.3 · Split `src/QueryEngine.ts` (1307 lines)

Audit subagent maps the public API first; aim for 3–4 modules. _Status as
of 2026-05-17: 5 cuts taken, QueryEngine.ts → 1101 lines, +5 modules under
`src/queryEngine/`._ Remaining material is the per-turn enrichment loop;
further extraction would inflate helper interfaces — saturate.

### Slice 0.4 · Split `src/tools/BashTool/bashSecurity.ts` (2592 lines)

Slice by function: `pattern-matchers/`, `wrapper-detection/` (`env` /
`sudo` / `watch` / `ionice` / `setsid`), `path-rules/`,
`compound-commands/`, `escape-handling/`. **Refactor before patching**;
P1 patches land afterward. _Status: deferred — current file structure is
already domain-clean; forced extraction is over-engineering._

### Slice 0.5 · Runtime Decision payoff

**Hypothesis**: `ARCHITECTURE.md` advertises "one decision per turn" but
6 feature flags currently steer at module-load time, violating the
promise.

**Scope**:
- Extend `RuntimeDecision` with `featureModules: { reactiveCompact,
  contextCollapse, skillPrefetch, jobClassifier, snipCompact, taskSummary }`.
- `runtimeDecisionResolver` resolves once per turn from route + provider
  + user config.
- `query.ts` reads `decision.featureModules`, never imports feature flags.

**Verify**: `tests/runtime-decision-feature-modules.test.mjs` asserts
single-resolution + route consistency. _Status: blocked — no
`RuntimeDecision` type exists in `src/`._

---

## P1 — Security & stability (Week 2)

### Slice 1.1 · Bash security patch sync (upstream 2.1.98 / 2.1.113 / 2.1.139) ✅ DONE (2026-05-17)

- Audit `env` / `sudo` / `watch` / `ionice` / `setsid` wrapper coverage
  via `bashSecurity.ts` grep. _Done — `setsid`/`ionice`/`watch` are
  intentionally absent from `SAFE_WRAPPER_PATTERNS`
  (`bashPermissions.ts:539`) so the wrapper's own name surfaces in
  permission prompts; `destructiveWarning.ts` strips them only for
  warning text. `sudo`/`doas`/`pkexec` are in `BARE_SHELL_PREFIXES`
  (`bashPermissions.ts:230`) so prefix rules can never auto-approve._
- Patch backslash-escape flag (already in `validateBackslashEscapedOperators`),
  compound commands (per-segment validation), env prefixes
  (`SAFE_ENV_VARS` allowlist), `/dev/tcp` / `/dev/udp` redirection
  bypasses. _Done — `validateRedirections` in `bashSecurity.ts:875`
  + `validateOutputRedirections` in `pathValidation.ts:946` both
  short-circuit `/dev/(tcp|udp)/` with a network-specific ask message._
- `find` no longer auto-approves `-exec` / `-delete` (already enforced
  via `readOnlyValidation.ts:1569` deny pattern).
- `dangerouslyDisableSandbox` still triggers permission prompt
  (already enforced; see `tests/sandbox-deny-overrides-permission.test.mjs`).
- `--dangerously-skip-permissions` cannot skip prompts for `.clavue/`,
  `.git/`, `.vscode/`, shell config (already enforced via
  `checkEditableInternalPath`).
- `Bash` deny rules match wrapper forms (`env`/`sudo`/`watch`/`ionice`/
  `setsid`) — verified via `BARE_SHELL_PREFIXES` blocking auto-suggestion
  and per-wrapper `destructiveWarning.ts` stripping.

**Verify**: `tests/bash-dev-tcp-udp-bypass.test.mjs` (source anchors)
+ `tests/bash-security-validators.test.mjs` `/dev/tcp /dev/udp` fixture
(functional). 1329 / 0 fail / 3 skipped, source-build + purity green.

### Slice 1.2 · Hook hardening (upstream 2.1.143 / 2.1.139 / 2.1.141)

- **Stop-hook 8-loop circuit breaker**: `STOP_HOOK_MAX_LOOPS=8`, env
  override, warn + abort over the cap.
- **`args: string[]` no-shell mode**: avoids hook command shell injection.
- **`PostToolUse.continueOnBlock`** — `types/hooks.ts` already has the
  schema; verify implementation.
- **`PreCompact` block path** — `exit 2` / `{decision:"block"}` cancels
  compact.
- **`terminalSequence` field** — notification / title / bell without
  controlling terminal.
- **`type: "mcp_tool"` hook type** — hook calls MCP tool directly
  (2.1.118).

**Verify**: `tests/hooks-stop-loop-cap.test.mjs`,
`tests/hooks-pre-compact-block.test.mjs`,
`tests/hooks-args-no-shell.test.mjs`.

### Slice 1.3 · MCP stability

- `MCP_TOOL_TIMEOUT` already on env list (`src/utils/managedEnvConstants.ts:161`)
  but defaults to 100 000 000 ms ≈ 27.7 h — **change default to 60 000**;
  timeout must apply to HTTP/SSE fetch in addition to response wait.
- **OAuth refresh token concurrency** (2.1.136): refresh token must not
  be overwritten by concurrent requests. Audit `src/services/mcp/xaa.ts`.
- **`alwaysLoad` option** — skip tool-search deferral.
- **`workspace` reserved server name** (2.1.128).
- **MCP survives `/clear`** (2.1.136).

**Verify**: `tests/mcp-tool-timeout.test.mjs`,
`tests/mcp-oauth-concurrent-refresh.test.mjs`.

### Slice 1.4 · Sandbox & permissions ✅ DONE (2026-05-17)

- `sandbox.network.deniedDomains` aggregation across SETTING_SOURCES
  (2.1.114). _Done — `sandboxTypes.ts`, `sandbox-adapter.ts`,
  `tests/sandbox-denied-domains.test.mjs`._
- `autoMode.hard_deny` unconditional block rules (2.1.136). _Done —
  `settings/types.ts`, `settings.ts`, `yoloClassifier.ts`._
- `"$defaults"` literal extends built-ins inside
  `autoMode.{allow,soft_deny,hard_deny,environment}` (2.1.118). _Done —
  `tests/automode-hard-deny-and-defaults.test.mjs`._
- Plan mode blocks file writes even when an `Edit(...)` allow rule
  matches (2.1.136). _Done — `filesystem.ts:1300-1322`,
  `tests/plan-mode-blocks-file-write.test.mjs`._
- `permissions.deny` overrides PreToolUse hook
  `permissionDecision: "ask"` (2.1.101). _Done — `toolHooks.ts:435-450`,
  `tests/permissions-deny-overrides-hook-ask.test.mjs`._

---

## P2 — High-ROI capability fill-in (Week 3)

### Slice 2.1 · Prompt cache 1h TTL completeness ✅ DONE (2026-05-17)

- Existing: `bootstrap/state.ts` already has 1h allowlist + eligibility
  cache.
- Verify `ENABLE_PROMPT_CACHING_1H=1` activation; relay proxies (the
  v8.10.17 fix scenario) must remain unaffected. _Done — env-gate order
  pinned by `tests/prompt-cache-1h-ttl.test.mjs`: FORCE_PROMPT_CACHING_5M
  short-circuits BEFORE the 1h opt-in. `CLAVUE_FORCE_PROMPT_CACHING_5M`
  alias intact._
- `cache_control.ttl='1h'` correctly forwarded on the Anthropic-native
  path; OpenAI Responses path degrades gracefully. _Done — the
  conditional spread `...(should1hCacheTTL(querySource) && { ttl: '1h' })`
  guarantees no `ttl: undefined` leaks on the 5m path; OpenAI Responses
  adapter never reads the `ttl` field._

### Slice 2.2 · `PostToolUse` `updatedToolOutput` for every tool (upstream 2.1.121) ✅ DONE (2026-05-17)

- `src/types/hooks.ts:107` already has the schema. Confirm `query.ts` /
  `toolOrchestration.ts` consume it after every tool, not just MCP.
  _Done — `toolHooks.ts:157` symmetric branch yields
  `updatedToolOutput` for non-MCP tools; `toolExecution.ts:1501`
  consumes the branch with `outputWasUnchanged=false`. `isMcpTool`
  guard prevents cross-pollination._
- Add tests asserting each core tool (Bash / Edit / Read / Grep / Glob /
  Web*) is rewritable by hook output. _Done —
  `tests/posttooluse-updated-tool-output.test.mjs` pins the symmetry
  contract via source anchors (toolHooks + toolExecution + hook schema)._

### Slice 2.3 · Skill UX

- `Skill(name *)` wildcard prefix matching (2.1.139).
- `skillOverrides` setting (2.1.129): `off` / `user-invocable-only` /
  `name-only`.
- Per-skill token estimate displayed in `/skills` UI.
- Type-filtered search (2.1.121).

### Slice 2.4 · Plugin UX

- `claude plugin details <name>` shows component inventory + projected
  token cost.
- `claude plugin prune` removes orphan dependencies.
- Marketplace browse pane shows per-turn / per-invocation projected
  token cost (2.1.143).
- Dependency enforcement: refuse disable when a dependent exists.

### Slice 2.5 · Reactive compact improvements

- Static imports (Slice 0.5).
- "Reactive compaction seeding from overflow size" (2.1.142): compact
  depth driven by actual overflow size.

---

## P3 — Startup / long sessions / provider expansion (Week 4)

### Slice 3.1 · Native binary startup path (upstream 2.1.113)

**Highest ROI, largest blast radius — separate slice.**

- `bin/clavue` becomes a launcher: detect optional dep
  `clavue-native-{platform}`; if present, spawn native; otherwise fall
  back to `dist/cli.js`.
- Startup target < 100 ms.
- **Must not affect `npm run check`**: `dist/cli.js` stays fully runnable.

### Slice 3.2 · Long-session resume UX

- `/recap` (2.1.108): session re-entry summary.
- `/focus` (2.1.110): split out from `Ctrl+O`.
- `/undo` aliases `/rewind`.
- "Summarize up to here" option in `/rewind` (2.1.141).

### Slice 3.3 · Provider expansion

- **Bedrock service tier** (2.1.122): `ANTHROPIC_BEDROCK_SERVICE_TIER`.
  _Done — `client.ts` injects `X-Amzn-Bedrock-Service-Tier` header on
  Bedrock client; CLAVUE_-prefixed alias takes precedence; anchored by
  `tests/bedrock-service-tier-env.test.mjs`._
- **Vertex mTLS / X.509 WIF** (2.1.120): enterprise scenarios.
- **`ANTHROPIC_WORKSPACE_ID`** (2.1.141): workload identity federation.
  _Done — `client.ts` emits `anthropic-workspace-id` defaultHeader before
  provider branching; CLAVUE_-prefixed alias takes precedence; anchored
  by `tests/anthropic-workspace-id.test.mjs`._
- **PR URL paste into `/resume`** (2.1.122): GitHub / GitLab / Bitbucket
  auto-recover session.

### Slice 3.4 · Agent entrypoint alignment

- `claude agents` accepts `--add-dir` / `--settings` / `--mcp-config` /
  `--plugin-dir` / `--permission-mode` / `--model` / `--effort` /
  `--dangerously-skip-permissions` (2.1.143).
- `claude agents --cwd <path>` scope filter.
- `subagent_type` case- and separator-insensitive match (2.1.140).

### Slice 3.5 · Worktree improvements (partial)

- `worktree.bgIsolation: "none"` (2.1.143). _Schema landed in
  `settings/types.ts`; runtime wiring (sandbox-adapter consumer)
  pending — schema-only acceptance lets policy admins declare intent
  ahead of the runtime flip._
- `worktree.baseRef`: `fresh` | `head` (2.1.133). _Schema landed;
  `EnterWorktreeTool` consumer to follow._
- `EnterWorktree` accepts `path` to switch into existing worktree
  (2.1.105).
- Failure path **does not** fall back to `rm -rf` (2.1.143 already fixed,
  verify).

---

## P4 — Engineering discipline & testing (Week 5)

### Slice 4.1 · Type-up tests

- Migrate top 25% high-value tests to `.test.ts` (vitest or `tsx --test`):
  route-policy, bash-security, runtime-decision, compact, permissions.
- Keep `.mjs` runner support; gradual migration.
- Add mutation testing (stryker or mull) on 5 core modules, weekly.
- Property-based testing: extend
  `tests/agent-budget-properties.test.mjs`; deepen
  `coding-route-policy-properties.test.mjs`; new
  `bash-security-properties.test.mjs`,
  `compact-handoff-properties.test.mjs`.

### Slice 4.2 · Doc governance

- `docs/archive/<year>/<quarter>/` for `v2_*`, old release notes, old
  audits.
- Top-level `claude-code-source/` cleanup: archive `bug.md`,
  `shengji.md`, `tisheng.md`, `wendanG.MD`, `yunying.md`,
  `伴侣设置1.md`, `伴侣设置2.md`, `COMPANION_*.md`,
  `UPGRADE_COHERENCE_AUDIT.md`, `MYCLAUDE_PROGRAM.md`, `image*.png`.
- `docs/INDEX.md` is the single doc entry, grouped by topic
  (architecture / runtime / provider / autoresearch / release / archive).

### Slice 4.3 · TODO triage

- Sort all 11 `TODO`s into `delete` / `fix-now` / `track-issue`. Each
  keeps owner + due date or gets removed.

### Slice 4.4 · Source purity hardening

- `check-source-purity.mjs`: forbid `require(` patterns inside `src/`
  (vendor exempted).
- `check-architecture-boundaries.mjs`: warn at LOC > 1000, fail at LOC
  > 1500 (explicit exemption list).

---

## Execution schedule

| Week | Slices | Verify | Keep criterion |
|------|--------|--------|----------------|
| 1 | 0.1–0.5 | `npm run check`, route-policy, workflow-continuity | all green; bundle ≤ +1% |
| 2 | 1.1–1.4 | + new attack-vector / hooks / mcp tests | 100% new tests pass |
| 3 | 2.1–2.5 | + skill / plugin / hook E2E smoke | user-visible features unlocked |
| 4 | 3.1–3.5 | + startup latency benchmark + provider matrix | startup < 100 ms native / < 500 ms fallback |
| 5 | 4.1–4.4 | mutation report + doc tree audit | mutation score > 70% on critical modules |

Weekly: `npm run check && npm run test:coding-regression && npm run
verify:provider:standard && npm run eval:world-class && npm run
autoresearch:platform`. Results in
`docs/evals/world-class/results.tsv` + `keep-discard-ledger.md`.

---

## Risk & rollback

| Risk | Mitigation |
|------|------------|
| query.ts split introduces import cycle | each new file < 500 lines; audit subagent maps deps first |
| Bash security patch over-blocks | every rule fixtured; new + legacy green tests run together |
| Native binary path breaks `npm publish` | own slice, feature-flag `CLAVUE_NATIVE_BIN=1`, default off |
| Hook circuit-breaker interrupts legit long hooks | env `STOP_HOOK_MAX_LOOPS` adjustable, default 8 |
| Doc reshuffle breaks external links | `docs/archive/` keeps a redirect README at the original paths |

Each slice ships as its own commit + RC; any metric regression triggers
`git revert <slice>`.

---

## Status tracker (updated 2026-05-17)

| Slice | State | Notes |
|-------|-------|-------|
| 0.1 | done (earlier session) | query.ts split |
| 0.2 | done (earlier session) | claude.ts → 6 modules |
| 0.3 | done (5 cuts) | QueryEngine.ts 1307 → 1101, 5 modules under `src/queryEngine/` |
| 0.4 | deferred | bashSecurity.ts already domain-clean; forced split = over-engineering |
| 0.5 | blocked | `RuntimeDecision` type missing in `src/` |
| 1.1 | **done** | `/dev/tcp` `/dev/udp` redirection guard + wrapper audit complete |
| 1.2 | **partial** | `STOP_HOOK_MAX_LOOPS=8` + `CLAVUE_STOP_HOOK_MAX_LOOPS` env override (`query.ts:121-127`); `args: string[]` no-shell mode (`utils/hooks.ts:993-998`); PreCompact block path (`utils/hooks.ts:4055-4073`); `PostToolUse.continueOnBlock` demote→additionalContext + permissionBehavior=deny clear pinned by `posttooluse-continue-on-block.test.mjs`; `terminalSequence` + `type: "mcp_tool"` hook type deferred (new code paths, medium blast) |
| 1.3 | **partial** | `MCP_TOOL_TIMEOUT` default 60s (`utils.ts:289`, pinned by `mcp-tool-timeout-default.test.mjs`); `alwaysLoad` already wired (`Tool.ts:451`, `ToolSearchTool/prompt.ts:65`); OAuth refresh concurrency lock + freshest-token reread already in `auth.ts:1664-1681 / 2240-2256`; `workspace` reserved server name (`config.ts:642`); `/clear` preserves `pluginReconnectKey` so MCP re-init works (pinned by `clear-preserves-mcp-reconnect.test.mjs`) |
| 1.4 | **done** | sandbox `deniedDomains`, plan-mode write block, hook-ask deny override, `hard_deny` + `$defaults` |
| 2.1 | **done** | 1h cache TTL gate order + 5m force-override pinned by `prompt-cache-1h-ttl.test.mjs` |
| 2.2 | **done** | `updatedToolOutput` symmetry pinned by `posttooluse-updated-tool-output.test.mjs` |
| 2.3 | **partial** | Skill `name *` space-asterisk wildcard normalized to `:*` (`skill-name-space-wildcard.test.mjs`); `skillOverrides` 3-mode + `:*` prefix already in source; per-skill token estimate already in `SkillsMenu.tsx:143-156` (`estimateSkillFrontmatterTokens`); type-filtered ToolSearch pending |
| 2.4 | open | Plugin UX |
| 2.5 | deferred | reactiveCompactPlan already preserves only the last group (max-aggressive); upstream 2.1.142 "overflow-size seeding" requires a clarified spec — deferred until that spec is captured |
| 3.1 | open | Native binary launcher |
| 3.2 | **partial** | `/undo` alias for `/rewind` landed (`rewind-undo-alias.test.mjs`); `/recap` slash command + `/focus` + "summarize up to here" rewind option pending |
| 3.3 | **partial** | Bedrock service tier header + ANTHROPIC_WORKSPACE_ID header done; Vertex mTLS + PR URL paste pending |
| 3.4 | **partial** | Case-insensitive `subagent_type` (`subagent-type-case-insensitive.test.mjs`) + `claude agents --cwd` (`agents-cwd-filter.test.mjs`) done; remaining flags (`--add-dir`/`--settings`/`--mcp-config`/`--plugin-dir`/`--permission-mode`/`--model`/`--effort`/`--dangerously-skip-permissions`) deferred — upstream `claude agents` mounts FleetView for background sessions; our handler only lists configured static agents from `.claude/agents/*`. Those dispatch-time flags have no consumer here without porting the FleetView surface |
| 3.5 | **partial** | `worktree.bgIsolation` + `worktree.baseRef` schema landed (`worktree-bg-isolation-baseref.test.mjs`); runtime consumer pending |
| 4.1 | open | Type-up tests |
| 4.2 | open | Doc governance |
| 4.3 | open | TODO triage |
| 4.4 | open | Source purity hardening |
