by yukineko
v0.1.28: fixes the defect v0.1.27's wiring fix exposed — the hook was reachable again, but `sync` could not finish from the store's own steady state. `cmd_sync` pulled BEFORE committing local appends, and pulled with `--ff-only`. Both abort in normal operation: the store files live inside the sync dir, so every `record` leaves them as uncommitted working-tree modifications (`git pull` then refuses with "Your local changes to the following files would be overwritten by merge", exit 1), and any second machine pushing makes the histories diverge (`--ff-only` then exits 128, "Not possible to fast-forward"). Both were reproduced against a copy of the real store. The order is now commit → pull `--no-rebase --no-edit` → push, with the push skipped only when the branch can be *established* to be level with its upstream (an undeterminable position pushes anyway). Reordering alone was still not enough: two machines appending to the same JSONL land their additions adjacent at end-of-file, which the default merge driver reports as a content conflict, so `sync` now ensures the sync dir's `.gitattributes` declares `episodes.jsonl`/`playbooks.jsonl` as `merge=union` — it keeps BOTH sides' lines, and the store is already deduplicated by content hash, so a duplicate line is recoverable where a dropped episode is not. Finally, a failed sync no longer fails into silence: its only caller is a `SessionEnd` hook whose exit code and stderr reach nobody, so the failure is written to `~/.fugu-router/sync-error.json` and surfaced by this plugin's own `UserPromptSubmit` hook (as `additionalContext` for the model AND `systemMessage` for the user) until a later sync succeeds and clears it. An unreadable or unparseable marker still produces a notice — its presence already says the last sync failed. New test `sync_ordering.rs` (4 tests, RED observed before GREEN) covers the steady state, idempotency on repeat, surfacing, and clearing. v0.1.27: `hooks/hooks.json` now declares the record-store sync as a `SessionEnd` hook (`${CLAUDE_PLUGIN_ROOT}/bin/fugu-router sync`, timeout 30). The wiring previously existed only as an orphaned absolute path in the user's `~/.claude/settings.json` (`~/.cargo/bin/fugu-router sync`), whose build had been renamed away — so the hook exited 127 every session with neither exit code nor stderr reaching the agent or the user, making a sync that had nothing to do indistinguishable from a sync that never ran (CLAUDE.md §1/§3: it went dark, not red). The damage was silent and cumulative — measured 2026-08-26: the last `fugu-router sync` commit in `~/.fugu-router/record-repo` is 2026-07-23 09:57:38 +0900, while `episodes.jsonl`/`playbooks.jsonl` in that repo kept growing as uncommitted, unpushed working-tree modifications for ~34 days, so every episode recorded in that window existed only on one machine. New test `session_end_sync_hook_wired.rs` pins the structural fix: the declaration must live in-plugin, every declared command must resolve through `${CLAUDE_PLUGIN_ROOT}` and never through `~/.cargo/bin`, and every declared subcommand must actually exist in the binary (RED observed before GREEN). fugu-style per-model routing for Claude Code orchestration. Learns from past task outcomes (which model passed verification, at what cost) and picks the cheapest Claude tier that historically clears similar work. Feeds condukt's suggested_model deterministically; records outcomes back to a local episode store. No API key, no embedding service — lexical k-NN over a JSONL store. v0.1.24: new mode axis `--mode fast|normal|high` for `route`/`suggest` — a deterministic clamp applied AFTER the policy has already picked a worker/verifier pair (never touches `decide`/`decide_bandit`'s learning logic). `fast` shifts the worker one tier down (capped at sonnet — opus never selected as worker or verifier); `high` shifts one tier up (capped at opus); `normal` is the backward-compatible identity default. The verifier is always recomputed from the clamped worker via `policy::verifier_model`, additionally capped at sonnet under `fast`. Precedence: `--mode` flag > env `FUGU_ROUTER_MODE` > config.toml `mode` > `normal`; an invalid env/config value is an explicit error + non-zero exit, never silently coerced to `normal`. A `gated` decision is a no-op under every mode (mirrors `downgrade_for_budget`'s own gated no-op). Ordering: the mode clamp runs BEFORE `downgrade_for_budget` — budget is a hard resource limit and wins over a mode preference, and the rationale records both the mode's shift and any budget negation so a `high` pick never silently disappears. `Episode` gains a measurement-only `mode: Option<String>` field (`record --mode`); an absent value means "not recorded", never conflated with `Some("normal")`. v0.1.20: new `duration-outliers` command flags models whose avg duration within a task class is a relative outlier vs other models in the same class (>1.5x the cross-model mean by default), and cross-references outlier-vs-normal effective pass rate — the measurement tool for PDO hypothesis ae64db03. v0.1.19: `Episode` gains measurement-only `route_basis`/`route_confidence`/`route_rationale` (the routing `Decision`'s provenance), `lines_added`/`lines_removed`, and `tokens_input`/`tokens_output`, all `Option<_>` and backward-compatible (older JSONL lines parse with these as `None`); `record` gains matching `--route-basis`/`--route-confidence`/`--route-rationale`/`--lines-added`/`--lines-removed`/`--tokens-input`/`--tokens-output` flags. None of this is consulted by `policy::route`/`decide_bandit` — it exists so routing decisions and task cost can be retrospectively correlated against actual pass/fail outcomes. v0.1.12: `store::append_playbook` now writes body+newline in one `write_all` call (mirroring `append`'s existing single-syscall pattern) instead of `writeln!`'s two syscalls, closing the same O_APPEND interleaving hazard for playbook records; a JSON-serialization failure now propagates as an `io::Result` error instead of silently writing an empty line. New regression test `concurrent_append_playbook_never_interleaves_records`. v0.1.13: every git subprocess in `cmd_sync` (pull/clone/status/add/diff/commit/push) is now bounded by a timeout (30s network ops, 10s local ops) via `wait-timeout`, killing a hung/stalled git process instead of wedging the sync command indefinitely. v0.1.14: `Episode` gains a measurement-only `duration_secs: f64` field (`#[serde(default)]`, backward-compatible with older JSONL lines) and `record` gains a `--duration` flag threading it through; not consulted by `policy::route`/`decide_bandit` — routing/scoring behavior is unchanged.
Claude Code1 Skill