feat(apple-silicon-local): Apple Silicon + macOS universal + Google Fonts self-host + 9765 + pricing + e2e + upstream sync - #1
Merged
Conversation
Embed Inter and JetBrains Mono woff2 subsets (Latin, Cyrillic, Greek, Vietnamese) plus their @font-face CSS under frontend/public/fonts. Vite copies them into the built bundle as static assets, so the embedded Go binary serves them from the same origin as the SPA. Drop the fonts.googleapis.com and fonts.gstatic.com preconnects and stylesheet, and remove both domains from the server-side CSP. The front-end now loads fonts from /fonts/fonts.css with no external network requests, removing the last runtime CDN dependency of the local-first UI. Tradeoffs: - Binary grows by ~300 KB (woff2 subsets + CSS) - We pin to upstream font snapshots instead of relying on Google's UA-driven subsetting; the included subsets cover the same ranges Google served
Add two new Makefile targets so local-first Apple users can build single-file binaries without picking architecture: - build-local-apple-silicon: cross-compiles the current checkout to darwin/arm64 even when the host is Intel, and verifies the output with `file`. Drops the dist/agentsview-darwin-arm64 binary. - release-universal-apple: builds both arm64 and amd64 slices and merges them with `lipo` into dist/agentsview-darwin-universal. Refuses to run on non-Darwin hosts (lipo is not portable) so the error surfaces early instead of producing a one-arch binary. Both targets reuse the existing pricing-snapshot and frontend dependencies so the embedded SPA and LiteLLM fallback pricing snapshot are baked into the resulting binary the same way as the per-arch release targets. They live next to release-darwin-arm64 in the Makefile so CI can opt in by invoking them on macOS runners.
Expose the offline-first build path as one-shot Makefile targets so users on an M-series Mac can verify the binary boots without any external network access: - run-offline: depends on `build`, then runs the freshly built ./agentsview with AGENTSVIEW_TELEMETRY_ENABLED=false, --no-update-check, --host 127.0.0.1, --no-browser, and PORT override (default 8080). The embedded LiteLLM fallback snapshot is used if the background pricing refresh fails, so a fresh box with no Internet still gets usable cost numbers. - run-offline-universal: same as above but uses release-universal-apple as the dependency, exercising the merged arm64+amd64 binary on the current Mac. Document the new Apple targets in `make help` next to the existing build/install block. Both targets compose the existing Apple build work; nothing else needed to be implemented.
…n-io#895) Agentsview can already show JetBrains Copilot sessions today through the exporter workflow discussed on issue kenn-io#104, but the repo's own docs and CLI help never say that plainly. This PR documents the supported path instead of implying native JetBrains database parsing is still missing. The change stays narrow to discoverability. It adds a README note for the `copilot-jetbrains-exporter` flow and updates the root `COPILOT_DIR` help text so users can point agentsview at exported JetBrains Copilot JSONL without guessing which directory knob to use. The parser and sync behavior stay unchanged because the repo already supports the exported JSONL layout. The only code change is the root help text and its regression coverage in `cmd/agentsview`, so reviewers can focus on whether the documented workflow matches the issue-thread direction and the existing Copilot parser surface. Fixes kenn-io#104 Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
Filtered PostgreSQL pushes are used by default-deny allow-list pipelines, but the previous global-only watermark model left those workflows unable to advance a cursor safely. Repeated `pg push --projects ...` runs had to keep scanning the full included project set, while the documented unfiltered workaround would publish projects the operator intentionally excluded. This changes filtered pushes to track `last_push_at` and boundary fingerprints under a deterministic target/filter-set scope. The unfiltered/global cursor remains untouched, so allow-list pushes can become incremental without making later unfiltered pushes skip unrelated projects. Legacy global state is not migrated into filtered scopes because old state has no filter metadata; the first filtered run for a project set after upgrade seeds the new scoped watermark. `pg status` now accepts the same project filter flags as `pg push` so ad hoc filtered workflows can inspect the matching scoped watermark instead of seeing only the global status. The PG sync docs describe the scoped watermark behavior and the one-time upgrade consequence. Reviewers should focus on the sync-state scope construction, filtered push finalization, and the decision not to migrate legacy/global watermarks into project-filtered scopes. Fixes kenn-io#891 Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
…ps (alt to kenn-io#867) (kenn-io#869) Alternative to kenn-io#867. This branch is built on @rodboev's commits (kept in the history, so the original work and authorship are preserved); it changes only how "active duration" is computed. ## What changed kenn-io#867 computes a session's active duration by summing only the gaps that *follow* a tool-use message. That captures tool-execution latency but discards all model generation and thinking time — including the time spent before the first tool call — so a session that reasons for minutes and then fires one quick tool is scored as nearly idle. This branch instead defines active duration the same way the existing velocity "active minutes" metric already does: the sum of consecutive inter-message gaps, with each gap capped at 5 minutes. Every gap counts — model generation, tool execution, and quick human turnarounds — and only stretches longer than the cap are bounded as idle. ## Why Message timestamps alone can't tell a 20-minute subagent run from a 20-minute coffee break; both are one long gap. Capping each gap fails gracefully in both directions (a long idle gap and a long active gap each contribute at most 5 minutes) instead of guessing, and it keeps the metric robust to messy data: resumed sessions, machine sleep, and API stalls produce the largest gaps, and those would otherwise dominate the sum. It also unifies the definition. The 5-minute cap is hoisted to a single shared constant (`db.ActiveGapCapSec` / `ActiveGapCapMs`) used by both the velocity metric and Top Sessions, so the two "active" numbers on the dashboard can't drift; a test asserts the seconds and milliseconds forms agree. ## Tradeoff A genuinely long active operation — a 15-minute test run, a 20-minute subagent — is also capped at 5 minutes, so it is undercounted. That is the deliberate cost of not guessing whether a long gap was work or idle; the error is bounded and symmetric. ## Where to look - `internal/db/analytics.go` — shared constant, SQLite active-duration SQL, and the velocity metric now sourcing the same cap. - `internal/db/timing.go` — SQLite Go fallback (timezone-aware ranking path) running the same clamp. - `internal/postgres/analytics.go`, `internal/duckdb/analytics_usage.go` — PostgreSQL and DuckDB twins. All three SQL backends drop the `has_tool_use` filter and the trailing gap to `ended_at` so the value matches the velocity computation. <sup>generated by a clanker</sup> Co-authored-by: Marius van Niekerk <mariusvniekerk@users.noreply.github.com>
Localizes remaining hard-coded frontend UI copy across shared date/refresh helpers, Insights quality pattern text, publish fallback errors, and compact content blocks. The change keeps agent names, model names, API paths, command snippets, file paths, and rendered session/user content unchanged. Count-sensitive labels use Paraglide messages where English needs singular/plural handling. Co-authored-by: icatw <icatw@users.noreply.github.com>
…nn-io#897) When Claude Code runs with `CLAUDE_CONFIG_DIR` set, it stores project data under that custom root, but agentsview still looks only at the default `~/.claude/projects` path unless users also set `CLAUDE_PROJECTS_DIR`. That leaves normal discovery, usage, and stats commands blind to Claude sessions in the common "custom config dir, default projects subdir" setup, and it also leaves remote SSH discovery and pg-service install warnings out of sync with the same Claude root contract. This change teaches the Claude implicit default to honor `CLAUDE_CONFIG_DIR` without changing the existing precedence contract. `CLAUDE_PROJECTS_DIR` still wins as the explicit path override, `claude_project_dirs` in `config.toml` still beats the implicit default, and the new behavior only re-roots the fallback path when neither stronger override is present. The env-var name stays on the Claude registry entry, config loading still owns the final path expansion, the remote SSH resolver now follows the same root-env fallback when it builds transfer targets, and `pg service install` now warns when `CLAUDE_CONFIG_DIR` would affect runtime discovery but the background service would not inherit it. The tests cover the re-rooted default, `CLAUDE_PROJECTS_DIR` precedence, config-file precedence, the remote resolve script's Claude root-env fallback, and the pg-service warning surface, so the fix stays narrow and auditable against the existing discovery contract. Fixes kenn-io#317 Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
When agentsview derives `temporal.reporter_timezone` from `time.Local.String()`, some hosts publish the Go sentinel `Local` instead of an IANA zone. Downstream consumers treat that string as a real timezone, which is enough to skew streak and heatmap logic instead of falling back cleanly. This change introduces one shared best-effort helper for env and system-local timezone names, then uses it only for the stats metadata path. Explicit `--timezone` values still win, valid `TZ` values still pass through, and the CLI usage bucketing path stays on its existing local-time fallback, so the fix stays scoped to emitted reporter metadata. The tests cover valid env precedence, valid local names, and the `Local` sentinel case. This is only the timezone slice from issue kenn-io#358; Cursor `ai-tracking.db` attribution remains follow-up work on the same tracker. Part of kenn-io#358 Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
8080 collides with the common HTTP-alt port and any other toolchain that grabs it by default. 9765 is in the user/ registered range, well away from common services (3000 dev, 5000 macOS AirPlay, 5432 Postgres, 6379 Redis, 8080 HTTP-alt), and unlikely to be claimed by another process on a workstation. Update the three default literals in internal/config (the zero-value Config, the proxy-mode reset, and both pflag/stdflag registrations) plus the matching test assertion in config_test.go. Existing installs that bound port 8080 in config.toml or via --port are unaffected; this only changes the default when no port is specified anywhere.
…aths
Add two regression tests around GetDailyUsage so future changes
that silently drop unpriced models can be caught immediately.
TestGetDailyUsageForkModelPricing inserts a custom model pattern
("internal-private-model") via UpsertModelPricing and verifies
that the day entry exists with the expected input/output token
counts and a non-zero cost. This is the case the user hit in
June 2026: a downstream fork that uses internal/private model
identifiers saw $0.00 cost because the model name did not
canonicalize to any upstream LiteLLM catalog key, even though
the database had rows with valid token_usage payloads.
TestGetDailyUsageUnknownModelHasZeroCostButCountsTokens covers
the fall-through case where a model is genuinely not priced:
the day entry must still exist (tokens are still counted), but
cost is $0. If this test ever fails with len(Daily)==0 the
upstream time-window SQL from issue kenn-io#904 has regressed.
The seed-and-refresh loop in cmd/agentsview previously only talked to the LiteLLM pricing catalog. When the upstream fetch failed (offline, DNS broken, rate-limited) and the embedded fallback snapshot did not contain the user's model, daily usage cost silently dropped to $0 — the same symptom that the fork model custom-pricing test guards against. Wire in OpenRouter's public /models endpoint as a second background source. LiteLLM stays first because it covers the public models agentsview normally parses; OpenRouter fills in fork-tuned and private model prices LiteLLM has not yet picked up. Each fetch failure is logged but never aborts the loop, so a partial outage of one upstream does not prevent the other from seeding. All successful results are merged with first-non-zero precedence per model_pattern. Adds: - internal/pricing/catalog/openrouter.go: fetcher and parser - internal/pricing/openrouter_test.go: unit tests for parser filtering, per-token-to-per-million conversion, and merge precedence - DefaultPricingSources() and MergePricing() in litellm.go exposing the source list and the merge helper so other callers (CLI statusline, future config-driven sources) can reuse them - refreshPricingFromSources() in cmd/agentsview/usage.go replacing the previous single-source call Also bumps the default-port assertion in cmd/agentsview/main_test.go and pg_test.go from 8080 to 9765 to match the port change landed earlier.
Refinement on top of kenn-io#886 / 9bee8a3, which already taught the Codex parser to classify `/goal` continuation envelopes as system content for newly parsed sessions. This PR handles the archive and compatibility side of that fix. It bumps the parser data version to 56 on top of main's version-55 Kimi usage-event bump, so existing source-backed Codex sessions are non-destructively re-parsed and old stored `/goal` rows are removed from message lists and user-turn counts instead of remaining in persistent SQLite archives. It also adds read-path fallbacks for legacy rows that may still exist before reparse, or in rows that cannot be re-emitted from source files. Backend system-prefix filtering now uses a dedicated Codex goal-context predicate plus SQLite/PostgreSQL/DuckDB SQL variants, while frontend system-message detection and HTML/focused export use the same wrapper semantics. Both the current `<codex_internal_context ... source="goal">` form, including extra attributes, and the older `<goal_context>` wrapper stay out of search, transcript filtering, and exported sessions. The main tradeoff is the data-version bump: multi-host PostgreSQL deployments should upgrade `pg serve` and all pushers together, because older agentsview binaries reject rows written by a newer parser data version. Co-authored-by: Trent Nelson <tpn@users.noreply.github.com>
…onfig (kenn-io#899) Issue `kenn-io#370` asks for the full literal-pattern config surface on the automated-session classifier. Prefixes were already made configurable in PR `kenn-io#383`, building on the built-in classifier expansion from PR `kenn-io#369`, but substrings and exact-message matches still stay hardcoded-only, which leaves users without a config-driven way to model embedded tool markers or whole-message pings. This change adds `substrings` and `exact_matches` alongside the existing `prefixes` field, wires all three categories through the existing classifier-config entrypoint, and includes each user-configured category in `ClassifierHash()` so behavior changes still trigger the expected rebuild path. Built-in patterns remain active, and the single-turn gate stays unchanged. The tests cover the new config round-trip, substring and exact-match semantics, hash invalidation for each new category, and preservation of the current multi-turn gate. Fixes kenn-io#370 Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
…enn-io#905) `agentsview usage daily --since/--until` accepted only `YYYY-MM-DD`. A `stats`-style duration like `7d` did not error; it flowed into the query as a malformed date and silently produced a wrong or empty window with exit 0. `stats` already accepted durations, so the two commands diverged and the `usage` path failed silently. This resolves both bounds through a shared `db.ResolveWindowDate` (reusing the `stats` `parseWindowPoint` grammar) before the filter is built, so durations and dates work on both the direct-SQLite and daemon-HTTP paths. Unparseable input and an inverted explicit window (`--since` after `--until`) now fail loudly with a non-zero exit instead of returning silent empty output. Flag help and the `usage daily` docs are updated to match. **Where to look:** - `internal/db/session_stats.go` — `ResolveWindowDate`, the shared duration-or-date resolver built on the existing `stats` parser. - `cmd/agentsview/usage.go` — `resolveUsageWindow` resolves `--until` first, anchors a duration `--since` to it, and rejects bad/inverted input; `runUsageDaily` exits non-zero on that error. - `cmd/agentsview/cli.go`, `docs/commands.md`, `docs/token-usage.md` — help text and reference rows. **Notes / limitations:** - Resolution happens once at the CLI layer, so both storage backends get the same concrete date — no per-backend logic, preserving backend parity. - A duration `--since` anchors to the resolved `--until` (or to `now` when `--until` is open), and dates stand alone — matching how `stats` resolves a window. So `--until 2026-04-10 --since 14d` yields the 14 days ending April 10. - Scope is the `usage daily` CLI flags. The HTTP API, MCP `get_usage_summary`, and `usage cursor` still take dates only; extending duration sugar there is a separate API-design call. Fixes kenn-io#904 Co-authored-by: Prateek Rungta <prateek@users.noreply.github.com>
This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [github.com/mattn/go-sqlite3](https://redirect.github.com/mattn/go-sqlite3) | `v1.14.45` → `v1.14.47` |  |  | | [github.com/shirou/gopsutil/v4](https://redirect.github.com/shirou/gopsutil) | `v4.26.3` → `v4.26.5` |  |  | | [github.com/testcontainers/testcontainers-go](https://redirect.github.com/testcontainers/testcontainers-go) | `v0.42.0` → `v0.43.0` |  |  | | [github.com/testcontainers/testcontainers-go/modules/postgres](https://redirect.github.com/testcontainers/testcontainers-go) | `v0.42.0` → `v0.43.0` |  |  | | [go.kenn.io/kit](https://redirect.github.com/kenn-io/kit) | `v0.1.5` → `v0.1.7` |  |  | --- ### Release Notes <details> <summary>mattn/go-sqlite3 (github.com/mattn/go-sqlite3)</summary> ### [`v1.14.47`](https://redirect.github.com/mattn/go-sqlite3/compare/v1.14.46...v1.14.47) [Compare Source](https://redirect.github.com/mattn/go-sqlite3/compare/v1.14.46...v1.14.47) ### [`v1.14.46`](https://redirect.github.com/mattn/go-sqlite3/compare/v1.14.45...v1.14.46) [Compare Source](https://redirect.github.com/mattn/go-sqlite3/compare/v1.14.45...v1.14.46) </details> <details> <summary>shirou/gopsutil (github.com/shirou/gopsutil/v4)</summary> ### [`v4.26.5`](https://redirect.github.com/shirou/gopsutil/releases/tag/v4.26.5) [Compare Source](https://redirect.github.com/shirou/gopsutil/compare/v4.26.4...v4.26.5) <!-- Release notes generated using configuration in .github/release.yml at v4.26.5 --> #### What's Changed ##### disk - disk: report effective mount mode on Linux by [@​paulojmdias](https://redirect.github.com/paulojmdias) in [#​2085](https://redirect.github.com/shirou/gopsutil/pull/2085) ##### net - \[net]: add more information on ProtoCounters godoc by [@​shirou](https://redirect.github.com/shirou) in [#​2089](https://redirect.github.com/shirou/gopsutil/pull/2089) ##### process - \[process]: fix envp leak into Cmdline on darwin by [@​kerlenton](https://redirect.github.com/kerlenton) in [#​2092](https://redirect.github.com/shirou/gopsutil/pull/2092) - perf(process): build snapshot map once on Windows to fix O(N²) process listing by [@​HarshalPatel1972](https://redirect.github.com/HarshalPatel1972) in [#​2062](https://redirect.github.com/shirou/gopsutil/pull/2062) #### New Contributors - [@​paulojmdias](https://redirect.github.com/paulojmdias) made their first contribution in [#​2085](https://redirect.github.com/shirou/gopsutil/pull/2085) - [@​HarshalPatel1972](https://redirect.github.com/HarshalPatel1972) made their first contribution in [#​2062](https://redirect.github.com/shirou/gopsutil/pull/2062) **Full Changelog**: <shirou/gopsutil@v4.26.4...v4.26.5> ### [`v4.26.4`](https://redirect.github.com/shirou/gopsutil/releases/tag/v4.26.4) [Compare Source](https://redirect.github.com/shirou/gopsutil/compare/v4.26.3...v4.26.4) <!-- Release notes generated using configuration in .github/release.yml at v4.26.4 --> #### What's Changed ##### cpu - \[cpu]\[windows]: fix percpu stats on Windows hosts with multiple processor groups by [@​shirou](https://redirect.github.com/shirou) in [#​2081](https://redirect.github.com/shirou/gopsutil/pull/2081) ##### disk - Fix infinite loop when failed to find path for volume on Windows by [@​woct0rdho](https://redirect.github.com/woct0rdho) in [#​2066](https://redirect.github.com/shirou/gopsutil/pull/2066) ##### host - host: add testInvoker dependency injection for AIX by [@​Dylan-M](https://redirect.github.com/Dylan-M) in [#​2040](https://redirect.github.com/shirou/gopsutil/pull/2040) - Use getconf instead of bootinfo on AIX to get kernel architecture by [@​pgimalac](https://redirect.github.com/pgimalac) in [#​2079](https://redirect.github.com/shirou/gopsutil/pull/2079) ##### load - load: fix MiscWithContext process state parsing on AIX nocgo by [@​Dylan-M](https://redirect.github.com/Dylan-M) in [#​2037](https://redirect.github.com/shirou/gopsutil/pull/2037) ##### mem - Fix NetBSD mem / net stats by [@​fraggerfox](https://redirect.github.com/fraggerfox) in [#​2077](https://redirect.github.com/shirou/gopsutil/pull/2077) ##### net - net: populate BytesSent/BytesRecv via entstat on AIX nocgo by [@​Dylan-M](https://redirect.github.com/Dylan-M) in [#​2034](https://redirect.github.com/shirou/gopsutil/pull/2034) - fix: add bounds check for /proc/net/dev fields to prevent panic by [@​Yanhu007](https://redirect.github.com/Yanhu007) in [#​2075](https://redirect.github.com/shirou/gopsutil/pull/2075) - net: add parseEntstat and parseNetstatI unit tests with AIX fixtures by [@​Dylan-M](https://redirect.github.com/Dylan-M) in [#​2073](https://redirect.github.com/shirou/gopsutil/pull/2073) - Implement net ProtoCounters for AIX by [@​pgimalac](https://redirect.github.com/pgimalac) in [#​2083](https://redirect.github.com/shirou/gopsutil/pull/2083) ##### process - fix: add bounds check for /proc/\[pid]/stat fields in fillFromTIDStat by [@​Yanhu007](https://redirect.github.com/Yanhu007) in [#​2076](https://redirect.github.com/shirou/gopsutil/pull/2076) - \[process]\[linux]: use TrimSuffix instead of Trim by [@​shirou](https://redirect.github.com/shirou) in [#​2068](https://redirect.github.com/shirou/gopsutil/pull/2068) #### New Contributors - [@​woct0rdho](https://redirect.github.com/woct0rdho) made their first contribution in [#​2066](https://redirect.github.com/shirou/gopsutil/pull/2066) - [@​Yanhu007](https://redirect.github.com/Yanhu007) made their first contribution in [#​2075](https://redirect.github.com/shirou/gopsutil/pull/2075) - [@​fraggerfox](https://redirect.github.com/fraggerfox) made their first contribution in [#​2077](https://redirect.github.com/shirou/gopsutil/pull/2077) **Full Changelog**: <shirou/gopsutil@v4.26.3...v4.26.4> </details> <details> <summary>testcontainers/testcontainers-go (github.com/testcontainers/testcontainers-go)</summary> ### [`v0.43.0`](https://redirect.github.com/testcontainers/testcontainers-go/releases/tag/v0.43.0) [Compare Source](https://redirect.github.com/testcontainers/testcontainers-go/compare/v0.42.0...v0.43.0) ##### What's Changed #####⚠️ Breaking Changes - chore(wait)!: change url callback in wait.ForSQL to accept network.Port ([#​3650](https://redirect.github.com/testcontainers/testcontainers-go/issues/3650)) [@​thaJeztah](https://redirect.github.com/thaJeztah) Users of `wait.ForSQL` need to follow the new API contract, using Moby's `network.Port` instead of `string` when building the callback function to check the URL. Please see <https://golang.testcontainers.org/features/wait/sql/> - feat!: add PullImageWithPlatform to DockerProvider ([#​3710](https://redirect.github.com/testcontainers/testcontainers-go/issues/3710)) [@​blueprismo](https://redirect.github.com/blueprismo) Users implementing their own `testcontainers.ImageProvider` need to implement the new `PullImageWithPlatform` method introduced by this PR. ##### 🚀 Features - feat(k3s): pull image opts ([#​3716](https://redirect.github.com/testcontainers/testcontainers-go/issues/3716)) [@​blueprismo](https://redirect.github.com/blueprismo) - feat(wait): implement AnyMultiStrategy: ForAny equivalent to ForAll. ([#​3719](https://redirect.github.com/testcontainers/testcontainers-go/issues/3719)) [@​jeanbza](https://redirect.github.com/jeanbza) - feat(eventhubs): add WithAzuriteContainer and functional-options config builder ([#​3722](https://redirect.github.com/testcontainers/testcontainers-go/issues/3722)) [@​mdelapenya](https://redirect.github.com/mdelapenya) - feat!: add PullImageWithPlatform to DockerProvider ([#​3710](https://redirect.github.com/testcontainers/testcontainers-go/issues/3710)) [@​blueprismo](https://redirect.github.com/blueprismo) - feat(modules/dex): add Dex OIDC provider module ([#​3659](https://redirect.github.com/testcontainers/testcontainers-go/issues/3659)) [@​guilycst](https://redirect.github.com/guilycst) ##### 🐛 Bug Fixes - fix(security): remove debug code that leaks Docker credentials ([#​3721](https://redirect.github.com/testcontainers/testcontainers-go/issues/3721)) [@​mdelapenya](https://redirect.github.com/mdelapenya) - fix(ollama): align local exec test with Ollama 0.30.6 log format ([#​3715](https://redirect.github.com/testcontainers/testcontainers-go/issues/3715)) [@​mdelapenya](https://redirect.github.com/mdelapenya) - fix: close temp file handle before removal ([#​3672](https://redirect.github.com/testcontainers/testcontainers-go/issues/3672)) [@​acouvreur](https://redirect.github.com/acouvreur) - fix(compose): close docker clients to prevent goroutine leaks ([#​3661](https://redirect.github.com/testcontainers/testcontainers-go/issues/3661)) [@​mdelapenya](https://redirect.github.com/mdelapenya) - fix: wait for log production goroutine to drain on stop ([#​3660](https://redirect.github.com/testcontainers/testcontainers-go/issues/3660)) [@​mdelapenya](https://redirect.github.com/mdelapenya) ##### 📖 Documentation - chore: update usage metrics (2026-06) ([#​3714](https://redirect.github.com/testcontainers/testcontainers-go/issues/3714)) @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ##### 🧹 Housekeeping - chore(wait)!: change url callback in wait.ForSQL to accept network.Port ([#​3650](https://redirect.github.com/testcontainers/testcontainers-go/issues/3650)) [@​thaJeztah](https://redirect.github.com/thaJeztah) - chore: update usage metrics (2026-05) ([#​3670](https://redirect.github.com/testcontainers/testcontainers-go/issues/3670)) @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) - chore: remove cgroupnsMode setting from K3s container configuration ([#​3653](https://redirect.github.com/testcontainers/testcontainers-go/issues/3653)) [@​lixin9311](https://redirect.github.com/lixin9311) ##### 📦 Dependency updates - chore(deps): update dependencies to latest versions in go.mod and go.sum ([#​3729](https://redirect.github.com/testcontainers/testcontainers-go/issues/3729)) [@​Steven-Harris](https://redirect.github.com/Steven-Harris) - chore: bump sshd-docker image to 1.4.0 ([#​3727](https://redirect.github.com/testcontainers/testcontainers-go/issues/3727)) [@​mdelapenya](https://redirect.github.com/mdelapenya) - chore(deps): bump Ryuk to v0.14.0 ([#​3313](https://redirect.github.com/testcontainers/testcontainers-go/issues/3313)) [@​mdelapenya](https://redirect.github.com/mdelapenya) - chore(deps): bump github.com/shirou/gopsutil/v4 from 4.26.4 to 4.26.5 ([#​3713](https://redirect.github.com/testcontainers/testcontainers-go/issues/3713)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump golang.org/x/sys from 0.44.0 to 0.45.0 ([#​3712](https://redirect.github.com/testcontainers/testcontainers-go/issues/3712)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump mkdocs-include-markdown-plugin from 7.2.2 to 7.3.0 ([#​3711](https://redirect.github.com/testcontainers/testcontainers-go/issues/3711)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump slackapi/slack-github-action from 2.1.1 to 3.0.3 ([#​3677](https://redirect.github.com/testcontainers/testcontainers-go/issues/3677)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump idna from 3.11 to 3.15 ([#​3708](https://redirect.github.com/testcontainers/testcontainers-go/issues/3708)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump github.com/containerd/containerd/v2 from 2.2.2 to 2.2.4 in /modules/compose ([#​3709](https://redirect.github.com/testcontainers/testcontainers-go/issues/3709)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump urllib3 from 2.6.3 to 2.7.0 ([#​3704](https://redirect.github.com/testcontainers/testcontainers-go/issues/3704)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump github.com/shirou/gopsutil/v4 from 4.26.3 to 4.26.4 ([#​3667](https://redirect.github.com/testcontainers/testcontainers-go/issues/3667)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump github.com/moby/moby/api from 1.54.1 to 1.54.2 ([#​3676](https://redirect.github.com/testcontainers/testcontainers-go/issues/3676)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump golang.org/x/crypto from 0.48.0 to 0.51.0 ([#​3689](https://redirect.github.com/testcontainers/testcontainers-go/issues/3689)) [@​mdelapenya](https://redirect.github.com/mdelapenya) - chore(deps): bump google.golang.org/grpc from 1.79.3 to 1.81.0 in /modules/gcloud ([#​3690](https://redirect.github.com/testcontainers/testcontainers-go/issues/3690)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump google.golang.org/grpc from 1.75.0 to 1.81.0 in /modules/dex ([#​3686](https://redirect.github.com/testcontainers/testcontainers-go/issues/3686)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump github.com/in-toto/in-toto-golang from 0.10.0 to 0.11.0 in /modules/compose ([#​3674](https://redirect.github.com/testcontainers/testcontainers-go/issues/3674)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump docker/setup-docker-action from 4.5.0 to 5.1.0 ([#​3664](https://redirect.github.com/testcontainers/testcontainers-go/issues/3664)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump mkdocs-include-markdown-plugin from 7.2.1 to 7.2.2 ([#​3665](https://redirect.github.com/testcontainers/testcontainers-go/issues/3665)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump github.com/apache/thrift from 0.21.0 to 0.23.0 in /modules/nebulagraph ([#​3673](https://redirect.github.com/testcontainers/testcontainers-go/issues/3673)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump github.com/Azure/go-ntlmssp from 0.0.0-20221128193559-754e69321358 to 0.1.1 in /modules/openldap ([#​3658](https://redirect.github.com/testcontainers/testcontainers-go/issues/3658)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump github.com/jackc/pgx/v5 from 5.9.0 to 5.9.2 in /modules/postgres ([#​3657](https://redirect.github.com/testcontainers/testcontainers-go/issues/3657)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump github.com/jackc/pgx/v5 from 5.5.4 to 5.9.2 in /modules/cockroachdb ([#​3656](https://redirect.github.com/testcontainers/testcontainers-go/issues/3656)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) - chore(deps): bump github.com/jackc/pgx/v5 from 5.5.4 to 5.9.0 in /modules/postgres ([#​3652](https://redirect.github.com/testcontainers/testcontainers-go/issues/3652)) @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) </details> <details> <summary>kenn-io/kit (go.kenn.io/kit)</summary> ### [`v0.1.7`](https://redirect.github.com/kenn-io/kit/compare/v0.1.6...v0.1.7) [Compare Source](https://redirect.github.com/kenn-io/kit/compare/v0.1.6...v0.1.7) ### [`v0.1.6`](https://redirect.github.com/kenn-io/kit/releases/tag/v0.1.6) [Compare Source](https://redirect.github.com/kenn-io/kit/compare/v0.1.5...v0.1.6) #### What's Changed - Unify CI concurrency group across trigger events by [@​mariusvniekerk](https://redirect.github.com/mariusvniekerk) in [#​15](https://redirect.github.com/kenn-io/kit/pull/15) - Add Renovate dependency updates by [@​mariusvniekerk](https://redirect.github.com/mariusvniekerk) in [#​17](https://redirect.github.com/kenn-io/kit/pull/17) - Hide git runner console windows on Windows by [@​mariusvniekerk](https://redirect.github.com/mariusvniekerk) in [#​21](https://redirect.github.com/kenn-io/kit/pull/21) - Fix Windows daemon console probe hang by [@​mariusvniekerk](https://redirect.github.com/mariusvniekerk) in [#​20](https://redirect.github.com/kenn-io/kit/pull/20) **Full Changelog**: <kenn-io/kit@v0.1.5...v0.1.6> </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/kenn-io/agentsview). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDIuMiIsInVwZGF0ZWRJblZlciI6IjQzLjI0Mi4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119--> Co-authored-by: renovate[bot] <renovate[bot]@users.noreply.github.com>
…enn-io#903) Fixes kenn-io#902 Every command that emits machine-readable output now accepts the same flag pair: `--format human|json`, with `--json` as a boolean alias for `--format json`. Before this change the surface was split three ways, so the same flag spelling worked on one command and failed on another: - `--format`-only: `stats`, `secrets` - bare `--json`-only: `projects`, `health`, `usage daily`, `activity report`, `parse-diff` - JSON-only with no flag: `token-use`, `openapi` A scripter can now use one convention everywhere, and every previously-working `--json` invocation still works. ## How it fits together Registration and reading are centralized so the convention can't drift again: - `registerFormatFlags` installs the `--format`/`--json` pair. Every machine-readable command calls it instead of declaring its own flag. - `formatFlag` (a `pflag.Value` enum) restricts `--format` to `human` or `json`. - `outputFormat` is the single reader; `--json` wins when set, otherwise the `--format` value, defaulting to `human`. - Streaming commands that can't honor a format choice reject the inherited pair through a shared `rejectFormatFlags` helper: `session export` (raw bytes) and `session watch` (NDJSON). `token-use` (deprecated) and `openapi` (a spec dump with no human form) stay JSON-only. ## Tradeoffs and limitations - One behavior change goes beyond pure consistency: a mistyped `--format` value now errors at parse time. Previously, on the commands that already had `--format`, an unrecognized value silently fell back to human output, which is a quiet trap for a script expecting JSON. The enum surfaces it instead. - `session export` and `session watch` inherit `--format`/`--json` from the `session` group's persistent flags and so still list them in `--help`, even though they reject them at runtime. That is a cobra inheritance limitation, not a new contract; the rejection and the docs make the behavior explicit. ## Where to look - `cmd/agentsview/session.go` — the shared helpers (`registerFormatFlags`, `formatFlag`, `outputFormat`, `rejectFormatFlags`). - `cmd/agentsview/cli.go`, `parse_diff.go`, `stats.go`, `secrets.go` — the migrated commands. - `cmd/agentsview/session_export.go`, `session_watch.go` — the streaming rejections. - `cmd/agentsview/output_format_test.go` — `TestFormatAndJSONFlagsArePaired` walks the command tree and fails the build if any command ever registers one flag without the other. - `docs/` — command reference tables updated to show the flag pair. Co-authored-by: Prateek Rungta <prateek@users.noreply.github.com>
* docs(parser): design provider facade layer
Parser integration has grown across registry callbacks, sync-engine switches, and provider-specific source handling. This design captures a shared provider facade so future parser work has one contract for discovery, fingerprinting, parsing, capabilities, and source lookup while preserving current normalized output types and sync semantics.
It also records the decision to keep source shape provider-owned, provide reusable JSONL source helpers, migrate every existing provider, and use enumer-generated capability support values for readable JSON reporting.
docs(parser): clarify provider embedding pattern
The provider facade design was intended to use embedded defaults, but the written spec did not show that pattern concretely enough. Add explicit provider examples so new provider implementations start from ProviderBase and embed or delegate source helper types instead of treating the facade as a loose collection of functions.
docs(parser): harden provider facade contract
The provider facade spec needed sharper boundaries before implementation: providers must be root-bound instances, source lookup cannot assume persisted paths are real files, and parse outcomes need to preserve retry eligibility without corrupting data-version state.
This keeps ProviderBase as the single embedded no-op surface while making reusable source discovery plain composition with explicit forwarding, so new provider work has a predictable contract without a hidden hook layer.
Validation: rg stale contract terms; git diff --check.
docs(parser): clarify provider facade edge cases
The provider facade contract still had ambiguity around mixed multi-session outcomes, fresh source lookup, changed-path filtering, and migration staging. Those gaps would let different provider migrations handle retries, diagnostics, and lookup callers differently.
This follow-up makes data-version state per parsed result, defines fresh-source resolution without assuming filesystem paths, gives fingerprint performance criteria, and splits caller migration into reviewable stages.
Validation: rg stale contract terms; git diff --check. Go tests not run because this is docs-only.
docs(parser): require provider config snapshots
Provider instances are meant to be root-bound and immutable after construction. The examples now show ProviderConfig cloning so helper roots and ProviderBase.Config cannot share a mutable caller slice.
Validation: rg stale contract terms; git diff --check. Go tests not run because this is docs-only.
docs(parser): define partial source failure semantics
Multi-session providers need an unambiguous rule for retryable session failures. The spec now requires SourceError.SessionID for per-session errors, routes unknown-scope failures to whole-source errors, and preserves absent rows during partial parses unless the provider explicitly excludes or cleanly replaces them.
Validation: rg stale contract terms; git diff --check. Go tests not run because this is docs-only.
docs(parser): tighten provider retry cache semantics
Provider results now have per-session retry state, but skip-cache persistence remains source-scoped. The spec now makes that aggregate rule explicit and also requires independent root-slice ownership between ProviderBase config and source helpers.
Validation: rg stale contract terms; git diff --check. Go tests not run because this is docs-only.
docs(parser): require complete results for clean source skips
Per-result retry state does not make skip-cache entries per-result. The spec now requires providers to declare a complete result set before the engine can persist a clean source/fingerprint skip, and keeps failures as diagnostic or failure-cache state instead of clean source state.
Validation: rg stale contract terms; git diff --check. Go tests not run because this is docs-only.
docs(parser): define provider contract edge cases
Provider migration depends on source references being fingerprintable, outcome IDs being comparable to persisted rows, and incremental parsing having unambiguous fallback semantics. Without those rules, a new provider could satisfy the facade shape while diverging in skip-cache, retry, or caller migration behavior.
This also records provider concurrency and SourceRef lifetime requirements so helpers can stay plain data holders while engine callers can safely share one provider instance.
docs(parser): require dual-run provider migration
Provider branches need to prove migration, not just add parallel provider implementations. Documenting the root dual-run harness, manifest opt-in, and stack-tip-only legacy removal gives each PR an obvious review surface while preserving legacy sync as the writer during the stack.
Validation: git diff --check; spec self-review for placeholders and stale defaulting language; mdformat hook formatting.
* feat(parser): add provider facade core
Introduce the Provider interface, ProviderBase/ProviderFactory, and source-set
helpers; own provider discovery and lookup at the root; and add the
legacy-call shim scan that gates provider files.
fix(parser): cover Aider, OMP, Reasonix in migration manifest
These agents live in the registry but were absent from the provider
migration manifest, so ValidateProviderMigrationModes failed once the
registry began enforcing that every agent has a mode. They remain on the
legacy path here; later stack commits migrate them to concrete providers
and flip these entries to provider-authoritative.
fix(sync): keep shadow provider discovery observational
Shadow provider mode must not add provider-only work or satisfy source lookups that the legacy runtime would miss. Otherwise a migration comparison can change live sync behavior before the provider becomes authoritative.\n\nProvider-authoritative discovery now reports discovery failures as sync failures and suppresses the provider completion watermark for that run, preserving the next incremental pass. The shim scan also keeps pending exemptions honest by failing stale entries while ignoring provider-owned selector methods.\n\nValidation: go test -tags "fts5" ./internal/parser -run 'TestProviderFilesDoNotReferenceLegacyEntrypoints' -count=1; go test -tags "fts5" ./internal/sync -run 'Test(DiscoverProviderSourcesOnlyRunsAuthoritativeProviders|SyncAllProviderDiscoveryFailureSkipsFinishedWatermark|FindSourceFileFallsBackToAuthoritativeNonFileProvider|ClassifyProviderChangedPath|ProcessFileShadow|ProcessFileProviderAuthoritative|ProviderVirtualSourceBackedByEvent)' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go vet ./...; git diff --check
docs(parser): clarify provider freshness contract
The facade spec still described successful parses as eligible for a clean skip-cache entry, which conflicts with the no-schema-change data-version model and can leave unchanged sessions stale after parser upgrades.\n\nDocument stored changed-path hints explicitly and keep successful unchanged-source freshness tied to DB metadata plus parser data version, reserving skipped_files for retry, failure, and explicit skip cases.\n\nValidation: go test -tags "fts5" ./internal/parser -run 'TestProviderFilesDoNotReferenceLegacyEntrypoints' -count=1; git diff --check. mdformat ran via commit hook.
docs(parser): pin provider source identity semantics
The facade contract needs to say exactly which provider source key is persisted because the migration intentionally avoids a schema change. Without that rule, providers could diverge between SourceRef, SourceFingerprint, and sessions.file_path identities.\n\nAlso define capability conformance by meaningful field presence so unsupported zero-value fields are treated consistently in provider tests.\n\nValidation: git diff --check. mdformat is unavailable on PATH, but the commit hook ran.
style(docs): mdformat provider dual-run harness plan
* ci: run tests on all PRs, gate desktop bundles to main, fix nilaway path
Three CI corrections for the stacked-PR workflow:
ci.yml only triggered on pull requests targeting main, so stacked PRs
that target another feature branch ran no tests, lint, or e2e at all --
only desktop-artifacts.yml fired on them. Drop the pull_request base
filter so every PR runs the suite, and add branches: [main] to
desktop-artifacts.yml so the expensive multi-platform tauri bundle
builds only run once a PR reaches main. A guard test pins both triggers.
The Makefile nilaway target derived per-package paths with
"./${dir#$root/}", which fails for the repo-root package: its dir equals
$root with no trailing slash, so the $root/ prefix never matches and the
absolute path leaks through as ".//abs/path", which custom-gcl then
rejects as a missing directory and the whole lint job fails. Special-case
dir == root to ".".
* ci: also build desktop bundles on push to main
The previous change restricted desktop-artifacts.yml to pull requests
targeting main. Add a push trigger on main with the same path filter so
the desktop/tauri bundles are also rebuilt when desktop-relevant files
land on main, not only while a PR is open. Both triggers stay scoped to
main and to desktop-relevant paths, so stacked feature-branch PRs still
skip the expensive multi-platform builds.
The analytics dashboard can already narrow by project, machine, agent, and time,
but still rolls every chart and summary together across models. This adds a
model-name filter to the main dashboard. A comma-separated `model` value is
threaded through the analytics route layer, the shared `AnalyticsFilter`, the
analytics/trends/filtered-model lookup helpers, and the dashboard request state.
When a model filter is active, the dashboard first scopes to sessions that contain
at least one matching model message, then derives message, token, tool-call,
trend-term, session-shape, and velocity metrics from the matching model rows within
that session set. Summary totals, activity, heatmap, projects, tools, skills, top
sessions, signals, trend terms, session shape, and velocity stay aligned across the
SQLite, PostgreSQL, and DuckDB backends, including mixed-model sessions and hour/day
slices.
`model` is the first message-grain analytics filter — a session spans many models —
so it can't ride the session-grain WHERE builders the other filters use. Each panel
instead needs a user->assistant pairing pass over candidate message rows. Rather
than copy that pairing into every panel, the model membership, user-turn pairing,
and day/hour match now live in one shared streaming reducer (the `internal/db`
message-scope helpers); each backend keeps its own candidate-row SQL (dialect,
placeholders, driver) and feeds rows through the shared reducer and its stats/timing
projections. Analytics and trends across all three backends share one implementation
instead of six near-duplicate pairing loops. The reducer requires only that rows
arrive grouped by session with ascending per-session ordinal — what
`ORDER BY session_id, ordinal` yields under any collation — so PostgreSQL's
collation-dependent ordering stays correct alongside SQLite and DuckDB.
On the frontend, the analytics store gains model-filter state, request params,
clear/toggle helpers, and active-filter chips, and the toolbar gains a model
dropdown that keeps known models stable across refreshes and filtered reloads.
One known limitation: the summary aggregation (median, p90, concentration) is still
computed per backend rather than shared; folding it into the same shared path is a
pre-existing cleanup left out here to keep this change scoped to model filtering.
Reviewers: the shared reducer (`internal/db/messagescope.go`,
`messagescope_reducer.go`) and the per-backend candidate-row resolvers
(`internal/{db,postgres,duckdb}/analytics_scope.go`) are the core; the rest is
route, filter, and frontend wiring. New analytics tests cover the filter across all
three backends.
Fixes kenn-io#633
Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
* feat(parser): add reusable source-set bases and functional options
Introduce the SourceSet bases (JSONL, directory JSONL, single-file,
multi-session container, sibling-metadata, SQLite fan-out), the
functional with*() option set, the generic SourceSet provider/factory
plumbing, and the virtual-path and source-identity helpers up front, so
every provider migration constructs its source set through options
instead of a struct literal.
* refactor(parser): remove dead SQLiteFanoutSourceSet
SQLiteFanoutSourceSet had no production callers -- it was referenced only by its own definition and duplicated multiSessionContainerSourceSet. The package-level helpers it relied on (cleanJSONLRoots, addJSONLSource, sortJSONLSources, and friends) are defined and used elsewhere, so removing it orphans nothing.
* refactor(parser): unify path-under-root containment on root-first arg order
pathIsUnderRoot took (path, root), reversing the root-first convention
used by pathUnderRoot(root, candidate) and relUnder(dir, child). Its only
caller already had root and path available, and pathUnderRoot has the same
containment semantics (root==path is not "under"; rejects ".." escape;
handles trailing separators via filepath.Clean). Drop the duplicate and
repoint the caller so all containment checks share one root-first helper.
* feat(parser): add withCompanionFiles sidecar option to jsonl source set
Providers whose transcript freshness depends on a sidecar file had no
base-level hook: only the SiblingMetadataSourceSet wrapper handled
companions, forcing a separate wrapper type instead of a plain option.
Add withCompanionFiles(transcriptPath -> companions) so the JSONLSourceSet
base folds companions into the three places they matter: their basenames
join the watch-plan include globs, their size/mtime (and content when
hashing is enabled) fold into the SourceFingerprint, and a changed
companion path maps back to its owning transcript so a sidecar write
re-parses the session. The wiring reuses the existing sibling-metadata
helpers rather than adding a third independent sidecar mechanism.
* test(parser): relocate shared jsonl source-set test helpers to framework
writeSourceFile and the generic source-set tests were introduced by the qwenpaw migration but are framework-level helpers used by ~20 provider test files. Placing them on the source-set-framework branch lets every family branch build its tests.
* refactor(parser): export the source-set framework API
The reusable source-set scaffolding (functional options, source-set constructors, and the generic source-set provider/factory) was unexported. Because providers don't consume it until higher branches in the stack, staticcheck's unused linter flagged ~62 of these symbols as dead at every mid-stack branch, and the always-run golangci-lint pre-commit hook failed on each such commit. Exporting the API makes the unused analyzer ignore them (it never reports exported identifiers), eliminating the spurious findings stack-wide with no import cycle.
* fix(parser): normalize JSONL RelPath to forward slashes
filepath.Rel returns OS-native separators, so on Windows the JSONLSource
RelPath came back with backslashes (nested\deleted.jsonl) while the rest
of the parser keys, display paths, and tests use forward-slash relative
paths. Normalize with filepath.ToSlash so RelPath is platform-stable; this
is a no-op on Unix and fixes the Windows Go Test failure in
TestJSONLSourceSetChangedPathClassifiesDeletedFiles.
* feat(parser,sync): add S3 discovery scaffolding to the source-set framework
The provider migration replaced each agent's DiscoverFunc, which handled
both local and s3:// roots, with provider.Discover. The source-set framework
this branch introduces had no way to carry an s3:// object's durable metadata
through discovery, so a migrated agent that enumerated remote objects would
lose the machine, size, mtime, and fingerprint the S3 sync path depends on.
Add the shared pieces here, ahead of any agent that uses them: an
S3DiscoveredSource Opaque payload plus s3SourceRefFromDiscoveredFile to build
an s3:// SourceRef, and engine threading that copies that metadata back into
the DiscoveredFile so the existing S3 sync path (object fetch, fingerprinting,
machine-ID namespacing, freshness, dedup, mtime cutoff) sees the same identity
legacy discovery emitted directly. Nothing produces these refs yet; the Claude
and Codex source sets wire them up on their own migration branches.
Also stop cleanJSONLRoots from running filepath.Clean on s3:// roots: Clean
collapses the scheme to s3:/ and defeats the HasPrefix("s3://") checks that
route discovery to the object store.
* test(parser): make cleanJSONLRoots test OS-agnostic
The local-root cases hard-coded forward-slash expectations, but filepath.Clean
emits OS-native separators -- backslashes on Windows -- so the test failed the
Windows CI job (\tmp\bar vs /tmp/bar). Build the expected local paths with
filepath.FromSlash so the assertion matches on every platform; the s3:// cases
stay verbatim because their preservation is separator-independent.
* fix(sync): clear ProviderProcess for discovered s3 sources
discoverProviderSources stamped ProviderProcess: true on every provider source,
including s3:// objects, even though the accompanying comment says s3 objects
route through processS3Session. Providers read local files, so the provider
Fingerprint/Parse path cannot service a remote object. The s3:// guard in
processProviderFile already declines these once a provider emits them, but the
flag contradicted the intent and relied solely on that downstream guard.
Clear ProviderProcess for s3 sources at the point the metadata is threaded, so
processProviderFile declines them via its ProviderSource-without-process check
and they route through the dedicated S3 sync path regardless of the later
HasPrefix guard.
* fix(parser): re-resolve stale stored paths in single-file FindSource
singleFileSourceSet.FindSource returned any stored-path/fingerprint-key hit the
moment classifyPath accepted it by shape, ignoring RequireFreshSource and never
checking that the path still exists. Single-file providers like Reasonix and
Cowork classify purely on path shape, so a moved or deleted stored file_path was
returned as found, short-circuiting the raw-ID re-resolution that would have
located the live transcript -- causing single-session resync/source lookup to
fail on a stale path.
Mirror the multiSessionContainerSourceSet.FindSource freshness guard: under
RequireFreshSource, skip a classified stored path that is not a regular file so
the lookup falls through to raw-ID re-resolution. PreferStoredSource semantics
for still-present paths are unchanged, since only RequireFreshSource gates it.
* feat(parser): add reusable source-set bases and functional options
Introduce the SourceSet bases (JSONL, directory JSONL, single-file,
multi-session container, sibling-metadata, SQLite fan-out), the
functional with*() option set, the generic SourceSet provider/factory
plumbing, and the virtual-path and source-identity helpers up front, so
every provider migration constructs its source set through options
instead of a struct literal.
* refactor(parser): remove dead SQLiteFanoutSourceSet
SQLiteFanoutSourceSet had no production callers -- it was referenced only by its own definition and duplicated multiSessionContainerSourceSet. The package-level helpers it relied on (cleanJSONLRoots, addJSONLSource, sortJSONLSources, and friends) are defined and used elsewhere, so removing it orphans nothing.
* refactor(parser): unify path-under-root containment on root-first arg order
pathIsUnderRoot took (path, root), reversing the root-first convention
used by pathUnderRoot(root, candidate) and relUnder(dir, child). Its only
caller already had root and path available, and pathUnderRoot has the same
containment semantics (root==path is not "under"; rejects ".." escape;
handles trailing separators via filepath.Clean). Drop the duplicate and
repoint the caller so all containment checks share one root-first helper.
* feat(parser): add withCompanionFiles sidecar option to jsonl source set
Providers whose transcript freshness depends on a sidecar file had no
base-level hook: only the SiblingMetadataSourceSet wrapper handled
companions, forcing a separate wrapper type instead of a plain option.
Add withCompanionFiles(transcriptPath -> companions) so the JSONLSourceSet
base folds companions into the three places they matter: their basenames
join the watch-plan include globs, their size/mtime (and content when
hashing is enabled) fold into the SourceFingerprint, and a changed
companion path maps back to its owning transcript so a sidecar write
re-parses the session. The wiring reuses the existing sibling-metadata
helpers rather than adding a third independent sidecar mechanism.
* test(parser): relocate shared jsonl source-set test helpers to framework
writeSourceFile and the generic source-set tests were introduced by the qwenpaw migration but are framework-level helpers used by ~20 provider test files. Placing them on the source-set-framework branch lets every family branch build its tests.
* refactor(parser): export the source-set framework API
The reusable source-set scaffolding (functional options, source-set constructors, and the generic source-set provider/factory) was unexported. Because providers don't consume it until higher branches in the stack, staticcheck's unused linter flagged ~62 of these symbols as dead at every mid-stack branch, and the always-run golangci-lint pre-commit hook failed on each such commit. Exporting the API makes the unused analyzer ignore them (it never reports exported identifiers), eliminating the spurious findings stack-wide with no import cycle.
* fix(parser): normalize JSONL RelPath to forward slashes
filepath.Rel returns OS-native separators, so on Windows the JSONLSource
RelPath came back with backslashes (nested\deleted.jsonl) while the rest
of the parser keys, display paths, and tests use forward-slash relative
paths. Normalize with filepath.ToSlash so RelPath is platform-stable; this
is a no-op on Unix and fixes the Windows Go Test failure in
TestJSONLSourceSetChangedPathClassifiesDeletedFiles.
* feat(parser,sync): add S3 discovery scaffolding to the source-set framework
The provider migration replaced each agent's DiscoverFunc, which handled
both local and s3:// roots, with provider.Discover. The source-set framework
this branch introduces had no way to carry an s3:// object's durable metadata
through discovery, so a migrated agent that enumerated remote objects would
lose the machine, size, mtime, and fingerprint the S3 sync path depends on.
Add the shared pieces here, ahead of any agent that uses them: an
S3DiscoveredSource Opaque payload plus s3SourceRefFromDiscoveredFile to build
an s3:// SourceRef, and engine threading that copies that metadata back into
the DiscoveredFile so the existing S3 sync path (object fetch, fingerprinting,
machine-ID namespacing, freshness, dedup, mtime cutoff) sees the same identity
legacy discovery emitted directly. Nothing produces these refs yet; the Claude
and Codex source sets wire them up on their own migration branches.
Also stop cleanJSONLRoots from running filepath.Clean on s3:// roots: Clean
collapses the scheme to s3:/ and defeats the HasPrefix("s3://") checks that
route discovery to the object store.
* test(parser): make cleanJSONLRoots test OS-agnostic
The local-root cases hard-coded forward-slash expectations, but filepath.Clean
emits OS-native separators -- backslashes on Windows -- so the test failed the
Windows CI job (\tmp\bar vs /tmp/bar). Build the expected local paths with
filepath.FromSlash so the assertion matches on every platform; the s3:// cases
stay verbatim because their preservation is separator-independent.
* fix(sync): clear ProviderProcess for discovered s3 sources
discoverProviderSources stamped ProviderProcess: true on every provider source,
including s3:// objects, even though the accompanying comment says s3 objects
route through processS3Session. Providers read local files, so the provider
Fingerprint/Parse path cannot service a remote object. The s3:// guard in
processProviderFile already declines these once a provider emits them, but the
flag contradicted the intent and relied solely on that downstream guard.
Clear ProviderProcess for s3 sources at the point the metadata is threaded, so
processProviderFile declines them via its ProviderSource-without-process check
and they route through the dedicated S3 sync path regardless of the later
HasPrefix guard.
* fix(parser): re-resolve stale stored paths in single-file FindSource
singleFileSourceSet.FindSource returned any stored-path/fingerprint-key hit the
moment classifyPath accepted it by shape, ignoring RequireFreshSource and never
checking that the path still exists. Single-file providers like Reasonix and
Cowork classify purely on path shape, so a moved or deleted stored file_path was
returned as found, short-circuiting the raw-ID re-resolution that would have
located the live transcript -- causing single-session resync/source lookup to
fail on a stale path.
Mirror the multiSessionContainerSourceSet.FindSource freshness guard: under
RequireFreshSource, skip a classified stored path that is not a regular file so
the lookup falls through to raw-ID re-resolution. PreferStoredSource semantics
for still-present paths are unchanged, since only RequireFreshSource gates it.
* feat(parser): migrate commandcode and iflow providers
Command Code and iFlow both fit the directory JSONL source shape, so moving them together proves the helper against real providers without mixing in nested layouts like Qwen or composite providers like WorkBuddy.
The providers keep source discovery, changed-path classification, persisted lookup, fingerprinting, and parse normalization behind concrete facade implementations while preserving the legacy parser functions for current runtime callers.
fix(parser): preserve JSONL provider symlink discovery
Command Code and iFlow legacy discovery followed symlinked project directories. The migrated providers should keep that behavior so users with linked project roots do not silently lose discovery or raw-session lookup after moving onto the provider facade.
test(parser): opt commandcode iflow into provider shadow
CommandCode and iFlow now have concrete facade providers on this branch, so keeping them legacy-only would let the migration branch remain additive instead of exercised by the shared provider harness.
This makes the migration manifest fail closed for the providers introduced here while leaving unrelated providers for their own stack branches.
Validation: go test -tags "fts5" ./internal/parser -run TestProviderMigrationModes -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
test(sync): compare commandcode iflow shadow parity
Command Code and iFlow now opt into shadow comparison, so their provider branch should prove more than provider-local parsing. Add source-level migration tests that run ObserveProviderSource and compare the normalized provider output against the legacy parser functions for both agents.
Validation: go fmt ./...; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go vet ./...; git diff --check; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...
refactor(parser): fold commandcode and iflow into provider
Move Command Code and iFlow parse ownership onto their concrete
providers and delete the package-level discover/find/parse entrypoints
plus the legacy sync dispatch for both agents. Both agents become
provider-authoritative so runtime sync routes through provider
changed-path classification and processProviderFile instead of the
removed processCommandCode/processIflow methods.
Command Code:
- parseSession moves onto the provider; DiscoverCommandCodeSessions,
FindCommandCodeSourceFile, and ParseCommandCodeSession are removed.
- The provider reproduces the legacy .meta.json companion behavior:
WatchPlan includes *.meta.json, SourcesForChangedPath remaps a
changed .meta.json back to its .jsonl transcript, the composite
Fingerprint folds the companion size, mtime, and content into the
freshness identity, and Parse overrides File.Size/File.Mtime with the
combined transcript+meta effective info. commandCodeEffectiveInfo
stays in the engine for the SourceMtime watcher fallback.
iFlow:
- parseSession moves onto the provider; DiscoverIflowProjects,
FindIflowSourceFile, and ParseIflowSession are removed.
- Parse mirrors the legacy sync path: it resolves the project from the
recorded cwd and git branch (falling back to GetProjectName of the
project directory), applies InferRelationshipTypes to derive
continuation/subagent links, and enables source content hashing so
File.Hash matches the legacy ComputeFileHash value.
Tests move from the deleted free functions to provider API coverage,
add guard tests asserting the legacy entrypoints stay gone, drop the
shadow comparison test, and remove both provider files from the
pending-shim scan list.
fix(parser): preserve commandcode file hash parity
Command Code needs a composite provider fingerprint so metadata-only edits invalidate freshness, but that value should not replace the persisted transcript content hash. The legacy sync path stored the SHA-256 of the transcript file in file_hash, and changing that semantic would make metadata-only edits look like transcript content changes.\n\nKeep the composite value scoped to SourceFingerprint and recompute Session.File.Hash from the transcript during provider parse. The provider test now exercises Fingerprint -> Parse with a .meta.json companion to prove the two hashes remain distinct.\n\nValidation: go test -tags "fts5" ./internal/parser -run TestCommandCodeProvider -count=1; go test -tags "fts5" ./internal/parser -count=1; go test -tags "fts5" ./internal/sync -run 'Test.*CommandCode|Test.*Iflow' -count=1; go vet ./...; git diff --check
fix(parser): thread ctx through commandcode and iflow source lookups
* feat(parser): migrate gptme to provider facade
The facade needs at least one real provider implementation before caller migration can prove the contract. Gptme is a narrow first target because it has a single-session JSONL source layout and an existing parser path that can be wrapped without changing runtime sync dispatch.
This keeps gptme source behavior explicit: the provider composes JSONLSourceSet for filesystem mechanics, filters to the legacy one-level conversation.jsonl layout, and returns complete current parse outcomes while the rest of the registry remains on legacy adapters.
fix(parser): preserve gptme provider source parity
The gptme provider is intended to be a no-behavior-change facade migration, so it needs to preserve the legacy source semantics before sync callers can safely move to it. Symlinked session directories, deleted source events, and persisted lookup hints are all observable through the current discovery and session lookup paths.
This keeps provider-backed gptme discovery and changed-path classification compatible with those legacy expectations while leaving runtime dispatch unchanged.
test(parser): opt gptme into provider shadow
Gptme now has a concrete facade provider on this branch, so the migration manifest should force it through the shared shadow-compare harness instead of leaving the provider implementation additive and unexercised.
Lower branch opt-ins remain inherited and later provider families stay legacy-only until their own branches introduce concrete providers.
Validation: go test -tags "fts5" ./internal/parser -run TestProviderMigrationModes -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
test(sync): compare gptme shadow parity
GPTMe is marked shadow-compare on this branch, so add the shared source-level migration proof beside the concrete provider. The test runs ObserveProviderSource and compares normalized provider output with ParseGptmeSession while preserving the provider-computed content hash.
Validation: go fmt ./...; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go vet ./...; git diff --check; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...
refactor(parser): fold gptme into provider
GPTMe should be a migrated provider on this branch, not a provider wrapper around exported legacy parser entrypoints. Keeping DiscoverGptmeSessions, FindGptmeSourceFile, ParseGptmeSession, and the engine processGptme path made the stack additive and left two public shapes to maintain.
Move GPTMe parsing behind the concrete provider, make GPTMe provider-authoritative at this branch, remove its legacy AgentDef hooks and engine dispatch, and replace shadow-baseline tests with provider API coverage plus a guard that the legacy symbols stay gone.
Validation: go test -tags "fts5" ./internal/parser ./internal/sync ./cmd/agentsview -count=1; go vet ./...; git diff --check
fix(parser): thread ctx through gptme source lookups
* feat(parser): migrate deepseek tui provider
DeepSeek TUI has a shallow one-file-per-session JSON layout, so moving it next keeps the provider migration incremental while exercising the JSON source helper with non-JSONL extensions.
The provider preserves legacy discovery filters for latest and offline queue files, raw/full ID lookup, changed-path classification, fingerprint propagation, and parse normalization without changing runtime sync dispatch.
fix(parser): preserve deepseek tui symlink files
DeepSeek TUI legacy lookup and parsing followed direct symlinks to session JSON files, so the facade provider needs an explicit way to preserve that source shape instead of silently dropping linked archives.
The JSONL source helper keeps symlink-file following opt-in, DeepSeek TUI enables it, and the branch manifest opts the concrete provider into shadow comparison so the migration is exercised rather than additive.
Validation: go test -tags "fts5" ./internal/parser -run 'Test(DeepSeekTUIProvider|JSONLSourceSet|ProviderMigrationModes)' -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
test(parser): skip deepseek symlink test when unsupported
Some test environments deny symlink creation even though the provider behavior is valid when links are available. The regression should skip in that environment instead of failing for host permissions.
Validation: go test -tags "fts5" ./internal/parser -run 'Test(DeepSeekTUIProvider|ProviderMigrationModes)' -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
test(sync): compare deepseek tui shadow parity
DeepSeek TUI is shadow-compared on this branch, so add the shared source-level proof that provider observation matches the existing ParseDeepSeekTUISession output. This keeps the branch review focused on an actual migration surface rather than only provider-local parser tests.
Validation: go fmt ./...; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go vet ./...; git diff --check; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...
refactor(parser): fold deepseek tui into provider
DeepSeek TUI should have one maintained parser shape on this branch. Leaving exported discover, lookup, and parse functions beside the concrete provider kept the migration additive and forced sync to preserve a second dispatch path.
Make the concrete provider authoritative, move parsing onto the provider, remove the AgentDef legacy hooks and engine dispatch, and replace shadow-baseline tests with provider API coverage plus a guard that the old symbols stay gone.
Validation: go test -tags "fts5" ./internal/parser ./internal/sync ./cmd/agentsview -count=1; go vet ./...; git diff --check
fix(parser): preserve deepseek tui file hash
DeepSeek TUI legacy sync stored the transcript content hash, but the migrated provider source set did not request hashing, so provider-authoritative Parse left Session.File.Hash empty when using the real Fingerprint path.\n\nEnable source hashing for DeepSeek TUI and make the provider parse test use Fingerprint -> Parse to assert the persisted file_hash value comes from the session JSON content.\n\nValidation: go test -tags "fts5" ./internal/parser -run TestDeepSeekTUIProvider -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
* feat(parser): migrate amp and zencoder providers
Amp and Zencoder both use shallow session-file roots, so migrating them together keeps the provider stack moving without introducing another source helper.
The concrete providers preserve legacy filename filters, raw/full ID lookup, deleted-path classification, fingerprint propagation, and parse normalization while continuing to compose the shared JSON source mechanics explicitly.
fix(parser): preserve JSONL symlink file sources
Migrated providers are intended to preserve legacy source discovery while moving behind the provider facade. Several legacy JSON/JSONL discoveries accepted matching symlinked session files and the parsers read through those symlink targets, so the shared source helper needs an explicit opt-in for that source shape instead of treating every symlink as non-regular metadata.
This keeps the default helper behavior strict while allowing shallow and directory JSONL providers to opt into the compatibility path they already had before the migration.
Validation: go test -tags "fts5" ./internal/parser -run 'Test(Amp|Zencoder)ProviderSourceMethodsFollowSymlinkedSessionFile' -count=1; go test -tags "fts5" ./internal/parser -run 'Test(Amp|Zencoder|DeepSeekTUI)ProviderSourceMethodsFollowSymlinkedSessionFile|Test(CommandCode|Iflow)ProviderDiscoversSymlinkedProjectDirectory|TestGptmeProvider' -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; make test-short; make nilaway; git diff --check
test(parser): opt amp zencoder into provider shadow
Amp and Zencoder now have concrete facade providers on this branch, so their migration modes should fail closed through the shared shadow-compare harness instead of leaving those implementations additive.
Earlier provider opt-ins stay inherited from lower stack branches, and later provider families remain legacy-only until their own branches introduce concrete providers.
Validation: go test -tags "fts5" ./internal/parser -run TestProviderMigrationModes -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
test(sync): compare amp zencoder shadow parity
Amp and Zencoder are shadow-compared on this branch, so add source-level migration tests that run ObserveProviderSource and compare provider output to the legacy ParseAmpSession and ParseZencoderSession functions.
Validation: go fmt ./...; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go vet ./...; git diff --check; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...
refactor(parser): fold amp zencoder into providers
Amp and Zencoder should stop carrying two public parser shapes once their concrete providers exist. Keeping exported parser entrypoints and legacy sync dispatch made this branch additive instead of a real migration.
Make both providers authoritative, move parsing behind provider methods, remove source callbacks and engine dispatch, and replace shadow-baseline tests with provider API coverage plus guards that the old symbols stay gone.
Validation: go test -tags "fts5" ./internal/parser ./internal/sync ./cmd/agentsview -count=1; go vet ./...; git diff --check
fix(parser): preserve amp zencoder file hashes
Amp and Zencoder legacy sync stored the source content hash, but the migrated providers did not request hashed source fingerprints. Provider-authoritative writes would therefore clear file_hash when running through the real provider path.\n\nEnable source hashing for both providers and update their provider tests to exercise Fingerprint -> Parse instead of passing manually injected hashes.\n\nValidation: go test -tags "fts5" ./internal/parser -run 'Test(Amp|Zencoder)ProviderParse' -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
* feat(parser): migrate pi provider
Pi is the next JSONL-shaped parser that can move behind the provider facade without introducing a new source framework. Its source layout is still simple enough to compose the directory JSONL helper, but it needs provider-owned filtering because legacy discovery validates the session header while raw session lookup only checks the expected filename under encoded-cwd directories.
This keeps that discovery-versus-lookup asymmetry explicit in the provider and preserves symlinked encoded-cwd directory support while parse output continues to come from the existing Pi parser.
Validation: go test -tags "fts5" ./internal/parser -run TestPiProvider -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; make test-short; git diff --check
fix(parser): preserve pi header-based discovery
Pi discovery has historically treated the filename as source shape only: any one-level JSONL file under an encoded-cwd directory can be a session if its header has type=session. The provider migration accidentally applied raw session ID filename validation before header validation, which would drop valid files whose session ID comes from the header instead of the filename.
Raw-ID lookup still validates the requested ID before reconstructing <id>.jsonl, so the legacy discovery-versus-lookup asymmetry remains explicit without broadening lookup inputs.
Validation: go test -tags "fts5" ./internal/parser -run TestPiProviderDiscoveryAcceptsSessionHeaderInNonSessionIDFilename -count=1; go test -tags "fts5" ./internal/parser -run 'TestPiProvider(DiscoveryAcceptsSessionHeaderInNonSessionIDFilename|SourceMethods|Parse|DiscoversSymlinkedCWDDirectory|FactoryReplacesLegacyAdapter)' -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; make test-short; git diff --check
test(parser): opt pi into provider shadow
Pi now has a concrete facade provider on this branch, so its migration mode should enter the shared shadow-compare harness instead of remaining an additive implementation behind legacy-only dispatch.
The stack keeps lower provider opt-ins inherited and leaves later provider branches legacy-only until their own migrations land.
Validation: go test -tags "fts5" ./internal/parser -run TestProviderMigrationModes -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
test(sync): compare pi shadow parity
Pi is shadow-compared on this branch, so add the shared source-level proof that provider observation matches ParsePiSession output for a representative session file.
Validation: go fmt ./...; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go vet ./...; git diff --check; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...
refactor(parser): fold pi into provider
Pi should not keep exported parser and source callback APIs after its concrete provider exists. Removing those hooks also exposed that full sync and single-session lookup still assumed AgentDef callbacks, so provider-authoritative agents were not actually runnable without legacy callbacks.
Move Pi parsing behind the provider, remove its legacy discovery and sync dispatch, add provider discovery and provider lookup to the sync root path, and replace shadow-baseline coverage with provider API tests plus a guard that the old symbols stay gone.
Validation: go test -tags "fts5" ./internal/parser ./internal/sync ./cmd/agentsview -count=1; go vet ./...; git diff --check
fix(parser): preserve pi family provider capabilities
OMP shared the Pi on-disk format but was left legacy-only after the legacy registry hooks were removed, so full sync and changed-path sync could no longer reach it through the migrated provider path. Parse-diff had the same shape of regression for provider-authoritative agents because it only trusted AgentDef discovery callbacks.\n\nFold OMP into the concrete Pi-family provider, derive parse identity from the provider definition, and teach parse-diff plus CLI validation to accept provider-authoritative on-disk sources. This keeps the branch as an actual migration rather than a shim around removed legacy functions.\n\nValidation: go test -tags "fts5" ./internal/parser -count=1; go test -tags "fts5" ./cmd/agentsview -run 'TestParseDiff' -count=1; go test -tags "fts5" ./internal/sync -run 'Test(ParseDiff|OMPSyncAllAndChangedPathUseProvider)' -count=1; go test -tags "fts5" ./internal/sync -count=1; go vet ./...; git diff --check
fix(parser): thread ctx through pi source lookups
* feat(parser): migrate workbuddy provider
WorkBuddy is still JSONL-backed, but its source layout has two valid shapes: project-level session files and nested subagent files. Moving it behind a concrete provider keeps that provider-specific shape explicit while continuing to reuse the shared JSONL filesystem mechanics.
The provider preserves legacy discovery and lookup behavior, including symlinked project directories and files, compound subagent raw IDs, deleted-path classification, source fingerprinting, and existing parser normalization for parent/subagent relationships.
Validation: go fmt ./...; go test -tags "fts5" ./internal/parser -run TestWorkBuddyProvider -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; make test-short; git diff --check
test(parser): document workbuddy subagent discovery
WorkBuddy legacy discovery accepts any JSONL filename under a valid parent session's subagents directory, while raw subagent lookup still validates the requested ID. The provider migration intentionally preserves that asymmetry rather than tightening discovery and dropping sources that older code would import.
Validation: go fmt ./...; go test -tags "fts5" ./internal/parser -run TestWorkBuddyProviderSourceMethods -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check; make nilaway
test(parser): opt workbuddy into provider shadow
WorkBuddy now has a concrete facade provider on this branch, so its migration mode should enter the shared shadow-compare harness rather than remaining legacy-only and additive.
Lower provider opt-ins stay inherited and later provider branches remain responsible for their own concrete providers.
Validation: go test -tags "fts5" ./internal/parser -run TestProviderMigrationModes -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
test(sync): compare workbuddy shadow parity
WorkBuddy is shadow-compared on this branch, so add source-level migration coverage that compares provider observation with ParseWorkBuddySession.
The test covers both the main session file and nested subagent file shape so parent relationship parity stays visible while the stack migrates provider by provider.
Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatchesWorkBuddyLegacyParser|TestWorkBuddyProvider|TestParseWorkBuddy' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...; git diff --check; go test -tags "fts5" ./internal/sync -run TestObserveProviderSourceMatchesWorkBuddyLegacyParser -count=1
refactor(parser): fold workbuddy into provider
WorkBuddy already had a concrete provider, but it still depended on exported legacy parser/source functions and legacy sync dispatch. That kept the branch additive and let the old shape remain authoritative.\n\nMove parsing and composite subagent source lookup behind the provider, remove registry callbacks and sync dispatch, and convert the WorkBuddy tests to provider-backed helpers plus a guard that the old entrypoints stay gone.\n\nValidation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestWorkBuddy|TestDiscoverWorkBuddy|TestParseWorkBuddy|TestFindWorkBuddy|TestEngineClassifyWorkBuddy|TestWorkBuddyRegistry' -count=1 -v; go test -tags "fts5" ./internal/parser ./internal/sync ./cmd/agentsview -count=1; go vet ./...; git diff --check
fix(parser): preserve workbuddy file hashes
WorkBuddy legacy sync stored the transcript content hash for both main sessions and subagent transcripts. The provider migration kept copying Fingerprint.Hash into Session.File.Hash, but the recursive source set did not request hashed fingerprints, so provider-authoritative writes would clear file_hash.\n\nEnable source hashing and make the provider parse test exercise Fingerprint -> Parse for both main and subagent sources.\n\nValidation: go test -tags "fts5" ./internal/parser -run TestWorkBuddyProvider -count=1; go test -tags "fts5" ./internal/parser -count=1; go test -tags "fts5" ./internal/sync -run 'Test.*WorkBuddy' -count=1; go vet ./...; git diff --check
fix(parser): thread ctx through workbuddy source lookups
* feat(parser): migrate cortex provider
Cortex has a shallow metadata-file source shape, with optional companion history JSONL handled inside the existing parser. Moving it behind a concrete provider keeps source discovery and lookup explicit without adding a new source abstraction.
The provider preserves the legacy Cortex session-file predicate, backup/history companion exclusions, symlinked file behavior, deleted-path classification, source fingerprinting, and parse normalization for session names, cwd, and tool content.
Validation: go fmt ./...; go test -tags "fts5" ./internal/parser -run TestCortexProvider -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; make test-short; git diff --check
fix(parser): include cortex history companions
Cortex split-history sessions parse messages from a sibling .history.jsonl file, so provider-backed live sync has to treat that companion as part of the same source. Otherwise a history-only append can be watched but never mapped back to the metadata session, or can keep the same freshness identity and skip reparsing.
This keeps the persisted source key on the .json metadata file while adding companion watch classification and a composite fingerprint over the metadata and history files when the companion exists.
Validation: go fmt ./...; go test -tags "fts5" ./internal/parser -run TestCortexProviderClassifiesAndFingerprintsHistoryCompanion -count=1; go test -tags "fts5" ./internal/parser -run TestCortexProvider -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; make test-short; git diff --check; make nilaway
test(parser): opt cortex into provider shadow
Cortex now has a concrete facade provider on this branch, so its migration mode should enter shadow comparison instead of staying legacy-only and additive.
Lower provider opt-ins stay inherited and later provider branches own their own manifest changes.
Validation: go test -tags "fts5" ./internal/parser -run TestProviderMigrationModes -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
test(sync): compare cortex shadow parity
Cortex is shadow-compared on this branch, so add source-level migration coverage that compares provider observation with ParseCortexSession.
The fixture uses Cortex's split metadata/history format so the test proves the provider path preserves companion-history parse behavior while still planning the primary session ID.
Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatchesCortexLegacyParser|TestCortexProvider|TestParseCortex' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...
refactor(parser): fold cortex into provider
Move Cortex parse ownership onto the concrete provider and remove the package-level discover/find/parse entrypoints. Route Cortex sync classification and processing through provider changed-path handling so this branch migrates the provider instead of adding another shim.
fix(sync): include cortex companion mtimes in quick sync
Provider-authoritative Cortex discovery emits the metadata JSON as the source, but its freshness identity also includes the split .history.jsonl companion. SyncAllSince was still filtering on the metadata file mtime before provider fingerprinting, so history-only updates could be dropped during quick sync.\n\nUse provider fingerprint mtimes for provider-process discovered files before applying the since cutoff, falling back to the existing per-agent stat logic when the provider has no mtime. Cortex full parses now replace messages as well, because split history rewrites can change existing ordinals rather than only append.\n\nValidation: go test -tags "fts5" ./internal/sync -run TestSyncAllSinceCortexHistoryUpdateTriggersResync -count=1; go test -tags "fts5" ./internal/parser -run Cortex -count=1; go test -tags "fts5" ./internal/sync -run 'Cortex|TestClassifyOnePath_Cortex|TestSyncAllSinceCortexHistoryUpdateTriggersResync' -count=1; go test -tags "fts5" ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check
fix(parser): thread ctx through cortex source lookups
* feat(parser): migrate kimi provider
Kimi uses two wire.jsonl layouts whose raw IDs include colon-delimited path components, so it cannot rely entirely on the generic JSONL raw-ID lookup. Moving it behind a concrete provider keeps discovery and source classification on the shared JSONL helper while preserving Kimi-specific layout validation and lookup semantics.\n\nThe provider keeps legacy support for both the .kimi project/session layout and the .kimi-code workdir/session/agents layout, including symlinked directories, invalid component filtering, project hints, deleted-path classification, and parser output normalization.
test(parser): cover kimi new-layout provider parse
The roborev design review questioned whether the provider-backed Kimi migration proved the newer .kimi-code layout could round-trip through lookup and parsing. The existing parser and lookup code already handled that raw ID shape, but the provider tests only parsed the legacy layout.\n\nThis adds provider-level coverage for the .kimi-code workdir/session/agents layout so the branch itself documents the persisted session ID, project hint, source path, machine, hash propagation, and message output expected from that source shape.
test(parser): opt kimi into provider shadow
Kimi now has a concrete facade provider on this branch, so its migration mode should enter shadow comparison instead of remaining legacy-only and additive.
Lower provider opt-ins stay inherited and later branches own their provider modes.
Validation: go test -tags "fts5" ./internal/parser -run TestProviderMigrationModes -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
test(sync): compare kimi shadow parity
Kimi is shadow-compared on this branch, so add source-level migration coverage that compares provider observation with ParseKimiSession.
The test covers both the legacy project/session wire.jsonl layout and the newer .kimi-code agents layout, keeping the fragile path-derived ID and project behavior visible during review.
Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatchesKimiLegacyParser|TestKimiProvider|TestParseKimi|TestSyncPathsAndSingleSession_KimiNewLayout|TestClassifyOnePath_Kimi' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...; make nilaway
refactor(parser): fold kimi into provider
Move Kimi parse and raw-ID source lookup onto the concrete provider and remove package-level discover/find/parse entrypoints. Route Kimi sync classification and processing through provider changed-path handling so the branch migrates the provider instead of preserving legacy dispatch.
* refactor(parser): adopt exported source-set API in cli-jsonl providers
Update the cli-jsonl provider call sites to the exported source-set framework API (WithRecursive, NewJSONLSourceSet, etc.) renamed on the source-set-framework branch.
* test(sync): pass context to filterFilesByMtime in S3 tests
This branch adds a context.Context parameter to filterFilesByMtime but
left the S3 source tests calling the old two-argument form, so
internal/sync failed to compile on this branch and every branch above it
until a later commit happened to fix the calls. Update the seven call
sites here, where the signature changed, so the branch builds on its own.
* test(parser): create symlink targets before linking for Windows
TestCommandCodeProviderDiscoversSymlinkedProjectDirectory and the iflow
equivalent created the directory symlink before the target directory
existed. On Windows os.Symlink to a missing target produces a file
symlink, so discovery could not descend into the linked project directory
and found zero sources. Populate the target directory first, matching the
claude/kimi/qwen/workbuddy symlink tests that already pass on Windows; this
is order-only and unchanged on Unix.
* fix(parser): use shared content hashing for pi/kimi/cortex/commandcode
The provider migration dropped the per-agent file_hash that the legacy
parse computed for these file-based agents: their JSONL source sets never
enabled content hashing, so a normal sync fingerprint carried an empty hash
and a resync cleared the stored file_hash to NULL.
Enable WithContentHashing on all four. Cortex and Command Code also
hand-rolled a bespoke Fingerprint (plus WatchPlan and SourcesForChangedPath
overrides) solely to fold their .history.jsonl / .meta.json sidecar into the
freshness identity. The shared JSONLSourceSet already does this through the
WithCompanionFiles hook, which folds companion size, mtime, and content into
the fingerprint, watches the sidecar, and maps a changed sidecar back to its
transcript. Route both providers through that hook and delete the bespoke
methods so file_hash and the companion fold come from one place; Command
Code's parse now threads the shared fingerprint hash rather than a separate
transcript-only hash.
The companion size/mtime fold is arithmetically identical to the old bespoke
fold, and the DB-freshness skip keys on size and mtime (not hash), so no
unchanged session reparses. The shared watch plan tracks each discovered
session's specific sidecar rather than a wildcard.
* test(parser): call renamed parseKimiSession in Kimi cost tests
The Kimi aggregate-cost tests carried in from main call the exported
ParseKimiSession, which the provider migration renames to the unexported
parseKimiSession. Point them at the renamed helper so the parser package
test binary compiles after the migration.
* feat(parser): migrate qwen provider Qwen uses a nested project/chats JSONL source shape, so it is a good next provider facade slice after the shallow and directory JSONL migrations. Moving it behind a concrete provider keeps discovery, lookup, fingerprinting, and parse output explicit without introducing another source framework. Legacy discovery accepts any one-level .jsonl file under chats while raw-session lookup still validates the requested ID before matching filename-derived IDs. The provider keeps that asymmetry, symlinked project directory and file behavior, project hints, and existing parser normalization intact. Validation: go fmt ./...; go test -tags "fts5" ./internal/parser -run TestQwenProvider -count=1; go test -tags "fts5" ./internal/parser -count=1; make test-short; go vet ./...; git diff --check test(parser): opt qwen into provider shadow Qwen now has a concrete facade provider on this branch, so its migration mode should enter shadow comparison instead of remaining an additive legacy-only provider. Lower provider opt-ins stay inherited and later branches remain responsible for their own concrete providers. Validation: go test -tags "fts5" ./internal/parser -run TestProviderMigrationModes -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check test(sync): compare qwen shadow parity Qwen is shadow-compared on this branch, so add the source-level migration proof that provider observation matches ParseQwenSession for its nested project/chats layout. This keeps reviewers focused on behavioral parity while later branches continue migrating their own provider shapes. Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatchesQwenLegacyParser|TestQwenProvider|TestParseQwen' -count=1; go fmt ./...; go vet ./...; git diff --check; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/... refactor(parser): fold qwen into provider Qwen already had a concrete provider, but the branch still kept exported legacy parser/source functions and legacy sync dispatch. That left the migration additive instead of making the provider shape authoritative.\n\nMove Qwen parsing behind the provider method, remove the registry callbacks and sync processor/classifier, and replace the shadow comparison with provider API coverage plus a guard that the old entrypoints stay gone.\n\nValidation: go test -tags "fts5" ./internal/parser -run 'TestQwen|TestParseQwenSession' -count=1 -v; go test -tags "fts5" ./internal/sync -run 'TestEngine_ClassifyPathsQwenSession|TestProviderMigration|TestObserveProvider|TestSyncSingle.*Qwen|TestQwen' -count=1 -v; go test -tags "fts5" ./internal/parser ./internal/sync ./cmd/agentsview -count=1; go vet ./...; git diff --check fix(parser): thread ctx through qwen source lookups * feat(parser): migrate qwenpaw provider QwenPaw stores rewritten JSON session snapshots under workspace-scoped sessions directories, with a second one-level subdirectory namespace for console sessions. Moving it behind a concrete provider keeps those raw ID shapes explicit while continuing to use the shared file-source mechanics where they fit. The provider preserves workspace project hints, root and console source lookup, hidden/deeper layout rejection, symlinked workspace discovery, content-hash fingerprinting, and force-replace parse semantics for rewritten session files. fix(parser): prune qwenpaw source traversal QwenPaw legacy discovery only walks valid workspace directories, the sessions directory, and one real non-hidden namespace below sessions. The provider migration reused the generic recursive JSONL walker, which preserved emitted source filtering but still allowed traversal and event classification through deeper or symlinked session namespaces. Add a shared traversal predicate to the JSONL source helper so providers can keep discovery and changed-path classification aligned when their source layouts are narrower than an unbounded recursive scan. test(parser): opt qwenpaw into provider shadow QwenPaw now has a concrete facade provider on this branch, so its migration mode should enter shadow comparison instead of remaining legacy-only and additive. Earlier provider opt-ins stay inherited and later branches own their modes. Validation: go test -tags "fts5" ./internal/parser -run TestProviderMigrationModes -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check test(sync): compare qwenpaw shadow parity QwenPaw is shadow-compared on this branch, so add source-level migration coverage that compares provider observation with ParseQwenPawSession. The test covers both root session files and console subdirectory session files so the path-derived workspace/session IDs and planned data-version behavior stay visible during review. Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatchesQwenPawLegacyParser|TestQwenPawProvider|TestParseQwenPaw|TestSyncSingleSession_QwenPaw|TestWriteBatchQwenPaw' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/... refactor(parser): fold qwenpaw into provider Move QwenPaw parse and source-lookup ownership onto the concrete qwenPawProvider and delete the package-level legacy entrypoints (DiscoverQwenPawSessions, FindQwenPawSourceFile, ParseQwenPawSession) plus their unexported discovery/traversal helpers. ParseQwenPawSession becomes the provider parseSession method, and the rawID resolution from FindQwenPawSourceFile moves onto the provider as sourceFileForRawID with its traversal guard. The provider's existing JSONLSourceSet already reproduces workspace/sessions/console discovery, symlink handling, and hidden-subdir pruning, so the legacy DiscoverQwenPawSessions free function is dropped entirely. Route QwenPaw sync classification and processing through the provider-neutral runtime by removing its legacy engine dispatch: the classifyOnePath workspace/sessions block, the processFile case arm, and the processQwenPaw method. The provider's SourcesForChangedPath and forceReplace-on-parse capability preserve changed-path remapping and the full-rewrite write semantics the legacy path provided. Make QwenPaw provider-authoritative in the migration manifest, drop its AgentDef DiscoverFunc/FindSourceFunc hooks, remove it from the pending shim scan list, and replace the shadow-baseline test with provider API coverage plus a guard that the legacy symbols stay gone. To preserve single-session resync parity, FindSource now resolves a DB-stored file_path that points outside any configured QWENPAW_DIR by synthesizing a source from the path's implicit <root>/<workspace>/sessions/ layout, recovering the workspace as ProjectHint so a reparse keeps the canonical qwenpaw:<workspace>:<stem> ID instead of orphaning it under an empty workspace. fix(parser): thread ctx through qwenpaw source lookups * refactor(parser): adopt exported source-set API in qwen-family providers Update qwen and qwenpaw provider call sites to the exported source-set framework API. * fix(parser): restore Qwen content hashing in provider The migrated Qwen provider built its JSONLSourceSet without WithContentHashing(), so provider.Fingerprint returned an empty hash and qwenParseFile left Session.File.Hash empty. The legacy processQwen path computed a full-file hash via ComputeFileHash and persisted file_hash; because the full-parse write path overwrites file_hash unconditionally, the migration would clear existing Qwen file_hash values to NULL on the next resync. Enable content hashing so the stored hash is preserved. * fix(parser): resolve qwen stored paths outside configured roots Single-session resync of a Qwen session whose DB-stored file_path lives outside any configured QWEN_PROJECTS_DIR failed with "provider source not found" after the provider migration: JSONLSourceSet.FindSource only accepts stored paths under a configured root unless a StoredPathFallbackRoot is set, and Qwen had none. Legacy processQwen parsed the stored file_path directly with no root-containment check, so this was a behavioral regression and an asymmetry with the QwenPaw sibling migrated in the same change, which already ships WithStoredPathFallbackRoot. Derive the implicit root from the <root>/<project>/chats/<stem>.jsonl layout (grandparent of chats/), validating the source shape and that the file still exists so a stale row cannot resolve to a missing file.
* feat(parser): migrate claw providers OpenClaw and QClaw share a Claw-style source layout where each agent directory owns a sessions folder and active JSONL files compete with archived JSONL variants for the same logical session. Moving them behind concrete provider facades keeps that active-over-archive and newest-archive policy explicit without broadening the generic JSONL source helpers around variable archive suffixes. The providers preserve colon-delimited agent/session lookup, selected-source change classification, symlinked agent directories, stale stored-path remapping, source fingerprinting, and existing parse normalization. fix(parser): promote claw archives on removal Claw providers choose a single source per logical session, so live-sync removal events need to account for source promotion. When an active file or newest archive disappears, another archive may become the selected source even though the changed path is no longer the source to parse. This keeps write events strict about the selected path, while remove and rename-style missing-path events can remap a valid stale Claw path to the newly selected source for the same raw session ID. test(parser): opt openclaw qclaw into provider shadow OpenClaw and QClaw now have concrete facade providers on this branch, so their migration modes should enter shadow comparison rather than staying legacy-only and additive. Earlier provider opt-ins remain inherited; later provider branches still own their own modes. Validation: go test -tags "fts5" ./internal/parser -run TestProviderMigrationModes -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check test(sync): compare claw shadow parity OpenClaw and QClaw are shadow-compared on this branch, so add source-level migration coverage that compares provider observation with their legacy parsers. The paired test follows the shared provider implementation and keeps the agent/session raw ID shape and planned data-version behavior visible during review. Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatchesClawLegacyParsers|Test(OpenClaw|QClaw)Provider|TestParse(OpenClaw|QClaw)' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/... refactor(parser): fold claw providers into provider OpenClaw and QClaw should no longer keep exported discover/find/parse entrypoints beside the provider facade. Folding discovery, raw-ID lookup, archive selection, and parsing into the concrete providers makes this branch a real migration instead of another shim around the legacy path. The sync engine now relies on provider changed-path handling for this family, so the provider migration mode can become authoritative and the shadow-only comparison test is removed. Validation: go test -tags "fts5" ./internal/parser -run 'TestClawProvidersOwnLegacyEntrypoints|TestOpenClaw|TestQClaw|TestClawProvider|TestParseOpenClaw|TestParseQClaw|TestDiscoverOpenClaw|TestDiscoverQClaw|TestFindOpenClaw|TestFindQClaw' -count=1 -v; go test -tags "fts5" ./internal/sync -run 'TestEngine_ClassifyPathsQClaw|TestProviderMigration|TestObserveProvider|TestProviderProcess' -count=1 -v; go fmt ./...; go test -tags "fts5" ./internal/parser ./internal/sync ./cmd/agentsview -count=1; go vet ./...; git diff --check * refactor(parser): adopt exported source-set API in claw providers Update openclaw and qclaw provider call sites to the exported source-set framework API. * fix(parser): restore Claw content hashing in provider fingerprint clawSourceSet.Fingerprint built a SourceFingerprint without a Hash, so clawParseOutcome's guarded assignment left Session.File.Hash empty for both OpenClaw and QClaw. The legacy processOpenClaw/processQClaw paths always computed a full-file hash via ComputeFileHash and persisted file_hash, and the full-parse write overwrites file_hash unconditionally, so the migration cleared existing hashes to NULL on resync. Compute the content hash in Fingerprint via hashJSONLSourceFile.
…code) (kenn-io#881) * feat(parser): migrate opencode-family providers OpenCode, Kilo, and MiMoCode share the same storage/session, message, part, and legacy SQLite source model. Moving them behind one concrete provider keeps that shared source contract explicit instead of spreading it across sync-only classifier paths. The provider preserves storage-first discovery, hybrid SQLite fallback, duplicate filtering, child-file changed-path classification, SQLite virtual paths, composite source mtimes, storage fingerprints, and fork-specific ID relabeling. fix(parser): classify removed opencode storage sessions OpenCode-family storage sessions are watched recursively, so delete and rename-style events for the primary session JSON need to map back to the same provider source even after the file no longer exists. Without that syntactic fallback, provider-path sync can miss stale storage sources until a broader resync. Move OpenCode, Kilo, and MiMoCode into shadow comparison on this branch so the stack continues as a real migration rather than an additive provider implementation. Validation: go test -tags "fts5" ./internal/parser -run 'Test(OpenCodeProvider|OpenCodeFamilyProvider|ProviderMigration)' -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check fix(sync): classify removed opencode session files The provider path now handles deleted OpenCode-family storage session JSONs, but the legacy SyncPaths classifier is still active during the migration. It needs the same syntactic fallback so watcher-driven sync remains behaviorally equivalent while both forms run. Validation: go test -tags "fts5" ./internal/sync -run 'TestEngine_ClassifyPathsOpenCodeFamilyRemovedSessionFile' -count=1; go test -tags "fts5" ./internal/parser -run 'Test(OpenCodeProvider|OpenCodeFamilyProvider|ProviderMigration)' -count=1; go test -tags "fts5" ./internal/sync -count=1; go vet ./...; git diff --check test(sync): compare opencode family shadow parity OpenCode, Kilo, and MiMoCode share the OpenCode-format provider implementation on this branch, so add source-level migration coverage for all three storage-mode source shapes. The table test compares provider observation with each legacy parser and verifies session/message/data-version parity while preserving provider-computed storage fingerprints. Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatchesOpenCodeFamilyLegacyParsers|TestOpenCode|TestParseOpenCode|TestParseKilo|TestParseMiMoCode|TestDiscoverKilo|TestDiscoverMiMoCode|TestProviderMigrationModes' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...; git diff --check refactor(parser): fold opencode family into providers OpenCode, Kilo, and MiMoCode share one on-disk format (storage/session JSON plus message/part files, with a legacy SQLite fallback exposed as <db>#<sessionID> virtual paths). They were still shims: the concrete provider delegated to package-level free functions and the agents stayed LegacyOnly, which violated the migration manifest invariant (a concrete provider must not remain legacy-only) and left the runtime on the legacy sync dispatch. Make the three providers authoritative and own their behavior. One shared openCodeFormatProvider implementation is parameterized per agent by a format struct (SQLite filename, storage session subdir, ID prefix); Kilo and MiMoCode reuse the OpenCode storage and SQLite readers and only relabel the parsed session onto their own agent and ID prefix, so the parse/discover/find logic is not duplicated three times. The 15 legacy free functions (Discover/Find/ParseFile/ParseSession/ ParseSQLiteVirtualPath for each of opencode/kilo/mimocode) are deleted. ParseOpenCodeFile/ParseOpenCodeSession move to unexported helpers (parseOpenCodeStorageFile/parseOpenCodeDBSession) that the provider spec drives. SQLite virtual-path resolution now goes through the provider-neutral ParseVirtualSourcePathForBase, so engine, parsediff, and resume callers no longer reference deleted parsers. Remove the opencode-family legacy engine dispatch: the classify blocks and classifyOpenCodeFormatPath, the processFile arm and processOpenCodeFormat, the DB-backed sync pass, single-session resync, and orphaned helpers. Runtime now routes through provider changed-path classification and processProviderFile. Because these agents now flow through file discovery, the resync empty-discovery guard tracks a non-container discovered count so a self-preserving storage store cannot mask plain file-backed sessions whose directories went empty, and parse-diff discovers them through the provider facade. fix(sync): skip provider-authoritative agents in parse-diff db synthesis parseDiffDatabaseSources synthesized a raw opencode.db/kilo.db source so the legacy processOpenCode fan-out re-parsed every DB session. Once those agents became provider-authoritative, parseDiffProviderSources already enumerates their DB sessions through the provider, which applies the storage-ID filter that drops a file-backed storage session's stale db row. Re-adding the raw db then double-counted those sessions and parsed the filtered storage row, surfacing a spurious ParseError and an extra examined file. Skip agents that have dropped their DiscoverFunc; the Kiro data.sqlite3 synthesis still runs because Kiro keeps its legacy DiscoverFunc until its own fold. refactor(parser): delete opencode legacy whole-database parser ParseOpenCodeDB parsed every session in an OpenCode SQLite database, but the provider (and the Kilo/MiMoCode reuse) routes per-session through parseOpenCodeDBSession, so the free function survived only as test-exercised dead production code. Delete ParseOpenCodeDB along with the orphaned loadOpenCodeSessions and the OpenCodeSession bundle type; loadOpenCodeProjects stays since the per-session path also resolves worktrees through it. The retained parse tests reproduce the whole-database walk with the provider's own primitives (ListOpenCodeSessionMeta + parseOpenCodeDBSession). fix(sync): preserve parse-diff virtual sqlite identity OpenCode-family providers expose SQLite sessions as per-session virtual sources. Parse-diff was still collapsing those db#session paths to the shared database path before error attribution, presence sweep, and limit accounting, which could apply one session's parse failure or omitted sample to every sibling in the DB. Keep exact source keys for OpenCode, Kilo, and MiMoCode provider virtual SQLite paths while retaining shared-base grouping for true physical multi-session jobs. Source existence checks still stat the physical DB path so virtual identities do not look missing. Validation: go test -tags "fts5" ./internal/sync -run 'TestParseDiffProviderVirtualSQLite(ErrorUsesExactSource|PresenceUsesExactSource|LimitUsesExactSource)|TestStripVirtualSourceSuffixVisualStudioCopilot' -count=1; go test -tags "fts5" ./internal/sync -run 'TestParseDiff(CoversMixedOpenCodeRoot|CoversMixedKiloRoot|ProviderVirtualSQLite|PresenceSweep|LimitNewestFirst|ReportHasFailures)' -count=1; go test -tags "fts5" ./internal/parser -run 'Test(OpenCodeProvider|OpenCodeFamilyProvider|Kilo|MiMoCode)' -count=1; go vet ./...; git diff --check * fix(parser): tolerate a corrupt optional SQLite DB in OpenCode discovery OpenCode-family roots can carry both filesystem storage sessions and an optional SQLite DB (opencode.db/kilo.db/mimocode.db). When the DB was present but corrupt, sqliteSources returned an error and Discover returned nil, err, dropping the valid storage-backed sessions already collected for that root. Legacy discovery handled the two sources independently and only logged DB listing failures. Scope the SQLite failure to the DB portion when storage mode is available: log and continue with the storage sources, while still propagating context cancellation and failing SQLite-only roots. * fix(parser): build opencode sqlite sources without per-row db reopen sqliteSources listed every session row from the SQLite DB and then called sourceRef for each, which reopened the same DB via OpenCodeSQLiteSessionExists once per row. For n sessions that is n redundant SQLite opens on every discovery and every WAL-change classification, and a row whose redundant probe failed would be silently dropped even though it was just read from that DB. Build the SourceRef directly from the listed metadata, keeping the virtual-path parse and under-root validation but skipping the existence probe the caller already satisfied. * feat(parser): finish IcodeMate migration to the OpenCode provider IcodeMate is provider-authoritative through the shared OpenCode-format provider. Remove the now-dead per-agent ParseIcodemateFile/Session wrappers, whose underlying ParseOpenCodeFile/Session this migration deletes, and cover the migrated discover/parse/relabel path with a provider-level test.
…mes, claude, cowork) (kenn-io#882) * feat(parser): migrate openhands provider OpenHands stores each conversation as a directory with metadata and event files, so the provider needs a directory source facade rather than a JSONL file wrapper. This keeps the legacy discovery and dashed/undashed ID lookup behavior while making the composite snapshot fingerprint explicit at the provider boundary. The provider uses the existing OpenHands parser and snapshot helpers so freshness, shallow watch planning, changed-path classification, and normalized parse output stay aligned with the legacy sync path. test(parser): opt openhands into provider shadow OpenHands now has a concrete facade provider on this branch, so its migration mode should enter shadow comparison instead of remaining legacy-only and additive. Earlier provider opt-ins stay inherited and later provider branches own their modes. Validation: go test -tags "fts5" ./internal/parser -run TestProviderMigrationModes -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check test(sync): compare openhands shadow parity OpenHands is shadow-compared on this branch, so add source-level migration coverage that compares provider observation with ParseOpenHandsSession. The test uses the directory snapshot source shape so the provider fingerprint path and planned data-version behavior stay visible while the branch migrates away from legacy dispatch. Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatchesOpenHandsLegacyParser|TestOpenHandsProvider|TestParseOpenHands|TestDiscoverAndFindOpenHands|TestClassifyOnePath_OpenHands|TestProcessFileOpenHandsUsesSnapshotMtimeForRetryCache' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/... refactor(parser): fold openhands into provider Move OpenHands discovery, source lookup, and parse ownership onto the concrete provider and delete the package-level DiscoverOpenHandsSessions, FindOpenHandsSourceFile, and ParseOpenHandsSession free functions. Discovery now walks conversation roots directly in the provider source set, raw-session-ID lookup folds the literal/dash-stripped/normalized matching into sessionDirForID, and parsing runs on a provider receiver method. The provider-neutral snapshot, session-dir predicate, and event parse helpers stay as shared free functions. Make OpenHands provider-authoritative and remove its legacy sync dispatch: the classifyOnePath block, the processFile case arm, the OpenHands snapshot-mtime branch, and processOpenHands are gone. Sync now classifies and processes OpenHands through provider changed-path handling, which preserves the base_state.json/TASKS.json/events companion remap to the session directory and keeps the snapshot mtime driving the skip-retry cache via the provider fingerprint. Drop the OpenHands AgentDef DiscoverFunc/FindSourceFunc hooks, remove the shadow baseline test, exempt the provider file from the shim scan, and add a guard asserting the legacy entrypoints stay deleted. * feat(parser): migrate cursor provider Cursor transcript sources have two legacy layouts and select .jsonl over .txt when both exist for a session. Moving Cursor behind a concrete provider keeps that selection policy explicit at the provider boundary instead of relying on the legacy parser adapter.\n\nThe provider preserves recursive project discovery, raw/full ID lookup, stale .txt path promotion, changed-path classification, content-hash fingerprinting, and parser output normalization while using the same Cursor discovery and parsing helpers as the previous sync path. fix(parser): preserve cursor project-scoped source selection Cursor session IDs are only unique within an encoded project directory, but the provider was resolving stored and changed paths through a root-wide lookup. That could silently select the same transcript stem from a different project and drop valid sources during discovery. Resolve Cursor source promotion inside the project derived from the incoming path, add duplicate-stem coverage, and mark model output unsupported until the parser actually fills message models. This lets the Cursor branch enter shadow comparison as a real migration step. Validation: go test -tags "fts5" ./internal/parser -run 'Test(CursorProvider|ProviderMigrationModes)' -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check test(sync): compare cursor shadow parity Cursor is shadow-compared on this branch, so add source-level migration coverage that compares provider observation with ParseCursorSession. The test uses duplicate transcript stems in different encoded project directories to lock in the current parser ID behavior while proving provider source observation stays project-scoped. Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatchesCursorLegacyParser|TestCursorProvider|TestParseCursor|TestCursorSessionID' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/... test(sync): assert cursor provider hash parity Roborev job 2709 caught that the Cursor shadow parity fixture normalized the legacy session hash before proving the provider fingerprint matched the legacy parser hash. That left the test unable to detect a provider fingerprint regression that propagated into parsed output. Assert hash parity before normalizing the legacy session for the full struct comparison, keeping the existing duplicate-stem fixture focused on provider/legacy equivalence. Validation: go test -tags "fts5" ./internal/sync -run TestObserveProviderSourceMatchesCursorLegacyParser -count=1; go fmt ./...; go vet ./...; git diff --check refactor(parser): fold cursor into provider Move Cursor source discovery, lookup, and parse ownership onto the concrete cursorProvider and remove the package-level DiscoverCursorSessions, FindCursorSourceFile, and ParseCursorSession free functions. Discovery and find-source bodies now live as provider-owned helpers (discoverTranscriptPaths, cursorAddSeen, cursorFindSourceFile) on the cursor source set, and parseSession is a receiver method. Make Cursor provider-authoritative and drop its legacy sync dispatch: the classifyOnePath transcript block, the processFile case arm, the processCursor method, and its now-orphaned validateCursorContainment and findContainingDir helpers. Source classification, containment, .txt/.jsonl precedence, and project-hint decoding are all reproduced through the provider's changed-path and discovery paths, so runtime behavior is preserved. ParseCursorTranscriptRelPath stays a shared provider-neutral path validator used by both the engine's project enrichment and the provider. Replace the shadow-baseline test with provider API coverage plus a guard asserting the legacy entrypoints stay gone, and remove cursor from the pending-shim list. fix(parser): cap cursor provider fingerprinting Cursor parsing already rejects transcripts over 10 MiB, but the migrated provider fingerprint path still hashed the full source before parse. That made oversized files pay an unbounded read cost in the provider freshness path even though parse would never accept them.\n\nKeep normal-size content hashing intact and return only metadata for oversized Cursor transcripts so parse remains the sole place that reads up to the guarded cap.\n\nValidation: go test -tags "fts5" ./internal/parser -run 'TestCursorProvider' -count=1; go vet ./...; git diff --check * feat(parser): migrate vibe provider Vibe stores transcript content in messages.jsonl while canonical session identity, title, timestamps, model, and usage can live in a sibling meta.json. Moving it behind a concrete provider keeps that companion relationship explicit at the provider boundary.\n\nThe provider preserves recursive session discovery, symlinked session directories, raw and full ID lookup through meta.json, meta-sidecar changed-path classification, effective size and mtime freshness, transcript hashing, fallback-ID exclusion, and parser output normalization through the existing Vibe parser wrapper. fix(parser): classify removed vibe transcripts Vibe source events need to keep working after the primary messages.jsonl has already disappeared. Routing deletion and rename-style events through the existing file check meant the watcher could ignore the exact event that should refresh or remove the stored session. Synthesize source refs only for missing-path removal semantics, keep ordinary lookups existence-checked, and pin the intentionally shallow session directory layout in provider tests. This lets the Vibe provider enter shadow comparison as a real migration step. Validation: go test -tags "fts5" ./internal/parser -run 'Test(VibeProvider|ProviderMigrationModes)' -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check test(sync): compare vibe shadow parity Vibe is shadow-compared on this branch, so add source-level migration coverage that compares provider observation with ParseVibeSessionWrapper. The test includes meta.json canonical ID promotion, provider-adjusted fingerprint metadata, usage events, and excluded fallback IDs so reviewers can see the migration preserves the composite source behavior. Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatchesVibeLegacyParser|TestVibeProvider|TestParseVibe|TestClassifyOnePath_Vibe|TestSyncVibe|TestSourceMtimeVibe|TestProcessVibe' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/... test(sync): cover vibe provider usage parity Roborev job 2711 caught that the Vibe shadow parity fixture compared empty usage slices, so it could not detect regressions in aggregate usage emission. Seed the fixture with real Vibe metadata fields for active model and nonzero stats, then assert both legacy and provider paths emit usage before comparing them. Validation: go test -tags "fts5" ./internal/sync -run TestObserveProviderSourceMatchesVibeLegacyParser -count=1; go fmt ./...; go vet ./...; git diff --check refactor(parser): fold vibe into provider Move Vibe source discovery, lookup, and parse ownership onto the concrete vibeProvider and delete the package-level DiscoverVibeSessions, FindVibeSourceFile, and ParseVibeSessionWrapper free functions. Discovery and find-source bodies now live as provider-owned helpers (discoverSessionPaths, findSourceFile) on the vibe source set, the isVibeMessagesFile guard moves to the provider file, and the messages.jsonl parser becomes the provider parseVibeResult/parseSession methods. Make Vibe provider-authoritative and drop its legacy sync dispatch: the classifyContainerPath classifyVibePath call and method, the processFile case arm, the processVibe method, and its now-orphaned isSessionBlocked and isSessionTrashed helpers. vibeEffectiveInfo stays as a shared composite-mtime helper used by the skip-cache and fingerprint paths. Because a provider has no database handle, the engine reproduces Vibe's DB-aware, file-path-scoped bookkeeping in applyProviderFilePathPolicies for single-session-per-file providers: stale stored IDs at the same source path are excluded, and a freshly parsed row is suppressed when the user already removed (trashed or deleted) the session occupying that path, so a canonical ID flipping between the meta.json session_id and the directory-name fallback no longer resurrects a hidden session. This is a no-op for stable-ID providers and skipped for multi-session sources. Drop the Vibe AgentDef DiscoverFunc/FindSourceFunc hooks, remove it from the pending shim scan list, replace the shadow-baseline test with provider API coverage plus a guard that the legacy entrypoints stay gone, and route the package and engine tests through the provider methods. The obsolete classifyOnePath Vibe test is removed; the provider's SourcesForChangedPath coverage replaces it. * feat(parser): migrate hermes provider Hermes can represent a configured root as either individual transcript files or as a state.db archive that fans out into multiple sessions. Moving it behind a concrete provider makes that source choice explicit instead of leaving archive behavior inside the legacy adapter path.\n\nThe provider preserves transcript discovery and lookup while treating state.db as a multi-session, force-replace source. Its fingerprint covers the archive database plus sibling transcripts so transcript-quality changes can refresh the archive source that ParseHermesArchive reads. fix(parser): preserve hermes archive event coverage Hermes archive discovery can normalize a configured sessions directory or direct state.db path into a sibling archive source, but the watch plan and changed-path classifier still assumed the configured root was the only event root. That left state.db updates and removed primary files invisible to provider-path sync. Normalize archive watch roots, map delete and rename-style events syntactically when primary files are gone, and cover archive-parent, sessions-directory, and direct-state roots. This lets Hermes enter shadow comparison as an actual migration branch. Validation: go test -tags "fts5" ./internal/parser -run 'Test(HermesProvider|ProviderMigrationModes)' -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check fix(parser): watch hermes archive roots syntactically Hermes archive configs can point at the archive parent, its sessions directory, or the state.db file before the sibling archive components have been created. Watch planning needs to treat those shapes as archive roots from their paths, not from startup-time existence checks, otherwise late-created metadata or transcripts are invisible until a full sync. The transcript watch root is now retained for archive-shaped roots even when sessions/ is not present yet, while ordinary transcript-only roots keep their recursive file watch. Validation: go test -tags "fts5" ./internal/parser -run 'TestHermesProvider' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check fix(parser): feed hermes archive roots to runtime watcher Hermes provider watch planning now knows how to follow archive-shaped roots, but the actual serve-time watcher still reads registry watch resolvers. Without a matching Hermes resolver there, the default .hermes/sessions config can miss sibling state.db creation or updates in live sync. Expose Hermes shallow archive-parent watch roots through the registry while keeping transcript roots recursive, and add shadow parity coverage so this branch remains a migration rather than an additive provider implementation. Validation: go test -tags "fts5" ./cmd/agentsview ./internal/parser ./internal/sync -run 'TestCollectWatchRootsHermesSessionsWatchesStateDBParent|TestHermesProvider|TestParseHermes|TestProviderMigrationModes|TestObserveProviderSourceMatchesHermesLegacyParser' -count=1; go test -tags "fts5" ./cmd/agentsview ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; ./custom-gcl run --config .golangci.nilaway.yml ./cmd/agentsview/... ./internal/parser/... ./internal/sync/...; git diff --check fix(sync): classify hermes archive watcher events Roborev jobs 2715 and 2716 caught that Hermes archive watch roots were subscribed but the legacy SyncPaths classifier still ignored sibling state.db events. That meant live sync could wait for a periodic full sync even though the watcher saw the change. Map configured Hermes archive roots, state.db events, and direct archive transcript events back to the state.db source that processHermes already parses, while preserving transcript-only root classification for standalone Hermes session files. Validation: go test -tags "fts5" ./internal/sync -run TestSyncPathsHermesStateDBEventRefreshesArchive -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -run 'Test(HermesProvider|ObserveProviderSourceMatchesHermesLegacyParser|SyncPathsHermesStateDBEventRefreshesArchive)' -count=1; go fmt ./...; go vet ./...; git diff --check fix(sync): include hermes transcripts in archive skips Roborev job 2803 caught that Hermes transcript watcher events could still be suppressed by state.db-only skip metadata after being routed to the archive source. In mixed state-db/transcript archives, state.db can be unchanged while a sibling transcript is new or updated. Use archive-effective size and mtime for state.db skip checks by folding direct transcript files from the sibling sessions directory into the snapshot, and add a regression where a transcript event refreshes an already-indexed archive. Validation: go test -tags "fts5" ./internal/sync -run 'TestSyncPathsHermes(ArchiveTranscriptEventRefreshesArchive|StateDBEventRefreshesArchive)' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -run 'Test(HermesProvider|ObserveProviderSourceMatchesHermesLegacyParser|SyncPathsHermes)' -count=1; go fmt ./...; go vet ./...; git diff --check fix(sync): use aggregate hermes archive fingerprints Hermes archive freshness needs the state.db sync path to compare the same aggregate fingerprint it persists. Discovering through the public Hermes session lister reselected state.db and missed sibling transcripts, so state.db events could avoid real skip-cache parity.\n\nEnumerate direct transcript files for the archive snapshot and stamp archive parse results with the aggregate state.db fingerprint before writing. This keeps unchanged archive syncs comparable while still refreshing when sibling transcripts change.\n\nValidation: go test -tags "fts5" ./internal/parser ./internal/sync; go vet ./...; make nilaway fix(sync): apply hermes archive fingerprints consistently Hermes archive refresh paths need to compare and persist the same aggregate fingerprint for state.db plus sibling transcripts. Otherwise cached parse skips and single-session refreshes can fall back to raw state.db metadata and miss transcript-only archive changes. Use the aggregate archive file info before generic skip-cache checks and share the archive parse-and-stamp helper between full archive processing and single-session refreshes. The regression coverage now persists the metadata, checks unchanged archive skips, and covers transcript discovery/removal behavior. Validation: go test -tags "fts5" ./internal/sync -run 'TestHermesArchive|TestProcessFileHermes|TestProcessHermesArchive|TestSyncSingleHermesArchive' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync; go vet ./...; make nilaway refactor(parser): fold hermes into provider Move Hermes source discovery, lookup, and parse ownership onto the concrete hermesProvider and delete the package-level DiscoverHermesSessions, FindHermesSourceFile, ParseHermesArchive, and ParseHermesSession free functions. Discovery and find-source bodies now live as provider-owned helpers (discoverHermesSessions, findHermesSourceFile); parse, archive parse, the state-db reader, and the transcript-archive fallback become hermesProvider methods (parseSession, parseArchive, parseStateDB, parseTranscriptArchive). Reproduce Hermes archive behavior on the provider. The provider's archive Parse now stamps every state.db session with the state.db path plus the aggregate (state.db + direct transcripts) size and mtime, replacing the engine's stampHermesArchiveResults/hermesArchiveEffectiveInfo so a transcript-only change still refreshes the archive's stored freshness. The new provider helpers hermesArchiveEffectiveFileInfo and hermesArchiveTranscriptFiles mirror the legacy engine aggregation (every .jsonl and session_*.json directly under the sessions directory, no dedup). The existing composite archive Fingerprint and archive watch/ classify source-set methods already carried the rest. Make Hermes provider-authoritative and drop its legacy sync dispatch: remove classifyHermesPath (and its hermesSyncArchivePaths, hermesSyncDirExists, hermesSyncTranscriptPath helpers), the processFile hermesArchiveEffectiveInfo stat hook and case arm, processHermes, parseHermesArchive, stampHermesArchiveResults, hermesArchiveEffectiveInfo, hermesArchiveTranscriptFiles, hermesArchiveSourcePaths, and the syncSingleHermesArchive special-case plus its method. Single-session resync of an archive now falls through to the generic provider path, which reparses the whole archive (ForceReplace) the same way a full sync does. Drop the Hermes AgentDef DiscoverFunc/FindSourceFunc hooks (the provider-owned WatchRootsFunc/ShallowWatchRootsFunc stay), remove hermes_provider.go from the pending shim scan list, replace the shadow-baseline test with provider-API coverage plus a guard that the legacy entrypoints stay gone, and route the package and engine archive tests through provider methods and the provider-authoritative processFile/ SyncPaths paths. Add internal/sync/provider_shadow_support_test.go defining the shared writeProviderShadowSourceFile test helper that the remaining vibe shadow test still references, which was orphaned by a predecessor commit. test(sync): drop unused shadow source-file helper The hermes fold left writeProviderShadowSourceFile in a dedicated test support file, but every shadow test writes its fixtures inline, so the helper has no callers and trips the unused linter. Remove the dead scaffolding. * feat(parser): migrate claude provider Claude has both regular project transcripts and nested subagent transcripts, plus an existing append-only incremental parser. Moving it behind a concrete provider keeps those source shapes and optional incremental capability explicit at the provider boundary.\n\nThe provider preserves recursive project discovery, symlinked project directories, standard and subagent raw-ID lookup, changed-path classification, content hashing, project-name normalization, excluded-session reporting, relationship inference, and incremental append parsing for linear JSONL growth. fix(parser): preserve claude provider edge events Claude provider sync must distinguish true append idleness from files that were truncated or replaced, and watcher classification must still identify deleted primary and subagent transcripts after the file is gone. Otherwise provider-path sync can retain stale messages or miss removals. Return full-parse status for truncated incremental inputs, add missing-path classification for valid Claude source shapes, and make raw subagent lookup follow symlinked project directories like discovery does. This branch now opts Claude into shadow comparison. Validation: go test -tags "fts5" ./internal/parser -run 'Test(ClaudeProvider|FindClaudeSourceFile|ProviderMigrationModes)' -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check fix(sync): replace claude content after file rewrites Claude incremental parsing is append-oriented, so any fallback caused by truncation or file replacement must replace persisted messages instead of flowing through the append-preserving write path. Otherwise stale higher ordinals or stale tool rows can survive a full parse fallback. The provider now marks truncated incremental inputs as force-replace, and the legacy engine path carries forceReplace when file identity changes or the file shrinks before falling back to a full parse. Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestClaudeProviderParseIncremental|TestIncrementalSync_Claude(FileReplaced|TruncatedFileReplacesStoredMessages|SameSizeFileReplaceUsesFullParse|MidStreamSplitFallsBackToFullParse|AgentIDFallbackUpdatesStoredToolCall)' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...; git diff --check fix(sync): replace claude same-size rewrites A same-size rewrite can reach the full-parse fallback when the normal skip check did not skip the file, which means the content changed even though the byte count did not. That fallback must replace persisted rows, or stale higher ordinals and tool rows can survive the parse. The regression rewrites a Claude file in place to the same byte length with fewer logical messages and verifies the stale assistant row is deleted. Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatchesClaudeLegacyParser|TestClaudeProviderParseIncremental|TestIncrementalSync_Claude(FileReplaced|TruncatedFileReplacesStoredMessages|SameSizeFileReplaceUsesFullParse|SameSizeInPlaceRewriteClearsStaleRows|MidStreamSplitFallsBackToFullParse|AgentIDFallbackUpdatesStoredToolCall)' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...; git diff --check test(sync): compare claude shadow parity Claude is shadow-compared on this branch, so add source-level migration coverage that compares provider observation with ParseClaudeSessionWithExclusions. The fixture exercises the project-directory source shape and verifies session, message, usage, exclusion, and data-version planning parity while preserving provider-computed file hashes. Validation: go test -tags "fts5" ./internal/sync -run TestObserveProviderSourceMatchesClaudeLegacyParser -count=1 test(sync): cover claude provider usage exclusions Roborev job 2721 caught that the Claude shadow parity fixture only compared a plain exchange, so it did not prove provider parity for per-message token usage or /usage-only session exclusions. Add assistant message usage metadata to the normal fixture and a separate /usage-only source discovered by the provider, then assert non-empty token metadata and excluded IDs against the legacy parser. Validation: go test -tags "fts5" ./internal/sync -run TestObserveProviderSourceMatchesClaudeLegacyParser -count=1; go fmt ./...; go vet ./...; git diff --check refactor(parser): fold claude into provider Move Claude source discovery, lookup, full parse, exclusion handling, and append-only incremental parse ownership onto the concrete claudeProvider and delete the package-level DiscoverClaudeProjects, FindClaudeSourceFile, ParseClaudeSessionFrom, and ParseClaudeSessionWithExclusions free functions. The discover and find-source bodies stay as provider-neutral helpers (ClaudeProjectSessionFiles, claudeFindSourceFile) and the parse bodies become claudeParseWithExclusions and claudeParseSessionFrom; the public ParseClaudeSession wrapper and the Cowork parser (which reuses the Claude transcript format) call the shared helper, so no provider file references a legacy Discover/Find/Parse entrypoint. Make Claude provider-authoritative and drop its legacy sync dispatch: the classifyOnePath Claude block, the processFile case arm, and the processClaude method. Source classification, project resolution, and exclusion handling are reproduced through the provider's changed-path and parse paths. The provider's SourcesForChangedPath also reproduces the legacy "classify despite a transient stat error" behavior so a changed path under a momentarily unreadable parent is not dropped. Wire the provider-authoritative engine path to preserve Claude's DB-aware single-file semantics, which a stateless provider cannot do alone: - tryProviderIncrementalAppend drives the provider's ParseIncremental through the shared tryIncrementalJSONL bookkeeping (session lookup, data-version and inode/device identity guards, ordinal resume, cross-sync split detection, cumulative counters, and forceReplace fallback), so append-only syncs keep the stored file hash and append rows instead of recomputing and rewriting. - providerSingleSessionFresh reproduces the shouldSkipFile gate so an unchanged, already-synced session is skipped instead of re-parsed every full sync and a single-session resync does not reapply a worktree project mapping to an unchanged file. - stampProviderFileIdentity stamps inode/device on parsed results so the incremental path can later detect an atomic file replacement. - processProviderFile honors a caller-supplied file.Project as the source ProjectHint when no explicit ProviderSource was given, so a SyncSingleSession does not revert a user's project override. The engine's expandClaudeDuplicateCandidates and dedupeClaudeDiscoveredFiles stay as provider-neutral engine-level dedup plumbing; expansion now enumerates via ClaudeProjectSessionFiles. The duplicate-candidate expansion and session-ID dedup/precedence behavior is unchanged. Because dropping the Claude DiscoverFunc would otherwise remove Claude from surfaces that gate on DiscoverFunc != nil, parse-diff (engine and CLI flag validation) and the SSH remote resolve script now also include file-based agents that have left legacy-only mode through the provider facade, restoring Claude (and the other already-folded agents) to those surfaces. Drop the Claude AgentDef DiscoverFunc/FindSourceFunc hooks, set its provider migration mode to ProviderAuthoritative, remove claude_provider.go from the pending shim scan list, replace the shadow baseline test with provider-API coverage plus a guard asserting the four legacy entrypoints stay gone, and re-vehicle the generic shadow-mechanism caller tests onto the still-legacy Cowork agent since Claude no longer has a legacy process arm to observe in shadow. refactor(parser): fold ParseClaudeSession onto the Claude provider Delete the ParseClaudeSession free function and route its only production caller (the session upload handler) plus the test suite through the Claude provider's new ParseUploadedTranscript method, exposed via the ClaudeUploadParser interface. Uploads live outside any configured root, so the method parses the staged transcript directly under the caller-supplied project. That project stays authoritative rather than being overridden by the transcript's recorded cwd, matching the prior upload behavior and unlike the discovered-session Parse path. Unexport ClassifyClaudeSystemMessage to classifyClaudeSystemMessage; it is a Claude-internal classifier with no callers outside the package. Both removals clear the last provider-specific legacy parse/classify entrypoints this branch owned. fix(sync): skip fresh claude before fingerprinting The Claude provider migration preserved DB freshness skipping, but only after provider fingerprinting had already hashed the whole transcript. That lost the legacy cheap size/mtime/data-version gate for unchanged files.\n\nRun the single-session freshness check before provider fingerprinting, and pass the computed fingerprint into incremental parsing so truncation detection can distinguish appended files from zero-byte rewrites. Zero-byte truncation now forces a full replacement parse instead of reporting no new data.\n\nValidation: go test -tags "fts5" ./internal/parser -run 'TestClaudeProviderParseIncremental(Truncated|EmptyTruncation)NeedsFullParse' -count=1; go test -tags "fts5" ./internal/sync -run 'TestIncrementalSync_ClaudeAppend|TestProcessFileProviderAuthoritativeSkipsFreshClaudeBeforeFingerprint' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go vet ./...; git diff --check * feat(parser): migrate cowork provider Cowork stores Claude-shaped transcripts behind local-agent metadata, so the provider boundary needs to preserve that metadata-to-transcript relationship instead of treating the files as plain Claude JSONL sources. The concrete provider keeps shallow metadata watching, metadata change classification, subagent transcript discovery, raw/full ID lookup, composite mtime freshness, and hash propagation explicit for the sync path. fix(parser): cover cowork nested watch events Cowork metadata and transcripts live below org/workspace/session directories, so a shallow root watch could not deliver the paths the provider claimed to classify. Deleted metadata also lost the JSON needed to resolve the transcript, leaving stale provider state after remove or rename events. Make the watch plan recursive for Cowork source globs, recover deleted metadata from the local session directory shape, cover removed metadata/main/subagent paths, and move Cowork into shadow comparison as its branch-local migration step. Validation: go test -tags "fts5" ./internal/parser -run 'Test(CoworkProvider|ProviderMigrationModes)' -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check fix(parser): reject ambiguous cowork metadata removal Deleted Cowork metadata can only be recovered from the local session directory shape. If that directory contains multiple main transcripts, choosing the first filesystem match would attach the event to an arbitrary source and leave the real stale source unresolved. Refuse ambiguous deleted-metadata recovery unless exactly one main transcript is present, and cover the multi-transcript case. The regular single-transcript metadata removal path remains supported. Validation: go test -tags "fts5" ./internal/parser -run 'Test(CoworkProvider|ProviderMigrationModes)' -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check fix(parser): validate cowork deleted metadata candidates Cowork metadata deletion recovery scans project directories after the metadata file is gone, so it cannot rely on the normal metadata-guided resolution path. It still needs the same transcript validity rules as normal discovery: regular files only, and symlink targets must stay inside the local session directory. Apply that validation before selecting or counting fallback candidates so symlink escapes are ignored and broken symlinks do not create false ambiguity. Validation: go test -tags "fts5" ./internal/parser -run 'TestCoworkProvider|TestResolveCoworkSessionRejectsSymlinkEscape|TestClassifyCoworkPath|TestParseCowork' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...; git diff --check test(sync): compare cowork shadow parity Cowork is a sidecar-backed Claude transcript provider, so add source-level migration coverage that compares provider observation with ParseCoworkSession. The fixture includes local-agent metadata plus the nested Claude transcript and verifies session, messages, usage, excluded IDs, and data-version planning parity while preserving provider-computed hashes. Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatchesCoworkLegacyParser|TestCoworkProvider|TestParseCowork|TestClassifyCoworkPath' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...; git diff --check refactor(parser): fold cowork into provider Move Cowork source discovery, lookup, parse, and changed-path classification onto the concrete coworkProvider and delete the package-level DiscoverCoworkSessions, FindCoworkSourceFile, ParseCoworkSession, and ClassifyCoworkPath free functions. Discovery and find-source bodies now live as provider-owned helpers (discoverTranscriptPaths, coworkFindSourceFile), parseSession is a receiver method, and the metadata-to-transcript classifier moves onto SourcesForChangedPath as classifyCoworkPath so a sibling local_<uuid>.json change still resolves to the session's main transcript. Make Cowork provider-authoritative and drop its legacy sync dispatch: the classifyOnePath cowork block, the processFile case arm, and the processCowork method. The sibling-meta composite freshness is preserved on the provider's Fingerprint, which already folds CoworkSessionMtime (the max of transcript and metadata mtime) into the freshness identity so a title-only rename triggers a reparse through processProviderFile. CoworkSessionMtime stays exported and the engine's skip-cache and SourceMtime watcher-fallback blocks keep calling it, mirroring how the commandcode fold retained commandCodeEffectiveInfo. Replace the legacy free-function tests with provider API coverage plus a guard asserting the four entrypoints stay gone, drop the shadow-baseline comparison test, relocate the shared writeProviderShadowSourceFile helper into provider_shadow_support_test.go, and remove cowork_provider.go from the pending-shim scan list. test(sync): drop obsolete cowork shadow-legacy tests Folding cowork into its provider removes its legacy processFile arm, so the two shadow-compare tests that built fixtures via the deleted parser.ParseCoworkSession and asserted a legacy result coexisting with the shadow provider can no longer pass: a non-authoritative cowork file now falls through to the unknown-agent default. The shadow machinery keeps coverage through provider_shadow_test.go and the cached-skip not-comparable case. fix(sync): skip fresh cowork provider sources Cowork moved behind the provider-authoritative sync path, but the migrated path still fingerprinted and parsed unchanged transcripts before checking the stored file metadata. That dropped the cheap DB freshness gate the legacy Cowork path relied on and made full syncs rewrite fresh sessions unnecessarily.\n\nRestore that gate for Cowork before provider fingerprinting, using the same transcript size plus CoworkSessionMtime identity stored in the database. Per-file force parses still bypass the gate so metadata-driven refreshes and explicit reparses continue to reach the provider.\n\nValidation: go test -tags "fts5" ./internal/sync -run 'TestProcessFileProviderAuthoritative(SkipsFreshCoworkBeforeFingerprint|ForceParseBypassesFreshCoworkSkip)|TestSyncAllSinceCoworkMetaUpdateTriggersResync|TestSyncPathsCoworkReplacesUpdatedMessageOrdinal' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go vet ./...; git diff --check * fix(sync,parser): reclassify parse-diff raced sources and clear shim-scan list The Claude provider migration routes parse-diff through the provider path, which regressed live-write skew detection: a concurrently rewritten source was classified as Changed instead of Raced, tripping --fail-on-change on a daemon write. Gate raced-source reliability on parseDiffAgentDiscoverable so provider-folded agents keep the raced reclassification. Also clear pendingShimProviderFiles: every provider in this stack is folded on the branch that introduces it, so no provider file is a standing shim and the exempt list must be empty. * test(parser): close hermes state.db setup handle before deletion createHermesStateDB held the SQLite setup handle open until test cleanup. TestHermesProviderStateDBSourceMethods removes state.db mid-run to exercise deletion handling, and Windows refuses to delete a file still held open by this process, so the test failed there. Close the handle when the helper returns; it is never used by callers after setup and the data is already persisted. No behavior change on Unix. * feat(parser,sync): discover Claude S3 sessions through the provider facade This branch migrates Claude to a provider-authoritative source set, so discoverProviderSources (which only calls provider.Discover) becomes the sole on-disk discovery path for it. claudeSourceSet.Discover resolved every enumerated file through sourceRef, whose local IsRegularFile gate rejects an s3:// object, so a migrated Claude that pointed at an s3:// projects root would silently stop discovering remote sessions -- a regression against the pre-migration DiscoverFunc, which handled both layouts. Route enumerated files through discoveredSourceRef: s3:// objects (which ClaudeProjectSessionFiles already surfaces via discoverClaudeS3) build an S3 SourceRef carrying the durable object metadata in its Opaque payload, while local files keep the regular file-backed ref. The engine threads that metadata back into the DiscoveredFile and the s3:// guard keeps processing on the dedicated S3 sync path, so freshness, dedup, mtime cutoff, and machine-ID namespacing behave exactly as they did before the migration. The sync test asserts an s3:// provider-discovered Claude source still routes to the S3 path and writes a machine-namespaced session. * fix(sync): skip unchanged provider-authoritative sources before reparse processProviderFile only had pre-parse DB-freshness skips for Claude (gated to IncrementalAppend) and Cowork. Every other agent this branch flips to provider-authoritative -- OpenHands, Cursor, Hermes, Vibe -- fell through to provider.Parse and writeBatch even when the stored file_size, file_mtime, and data_version already matched, so a full or periodic sync reparsed and rewrote untouched sessions every time, regressing the per-agent shouldSkipByPath skip those agents had before migration. Add a generic providerSourceUnchangedInDB check after fingerprinting: when the discovered path's stored size+mtime match the fingerprint and data_version is current (and the stored project does not need reparsing), skip without parsing. It only skips on an exact size+mtime match, so any real change still reparses, and it is backend-agnostic (GetFileInfoByPath/GetDataVersionByPath/ GetProjectByPath behave identically on SQLite and PostgreSQL). * fix(parser): fall back to hermes transcripts when state.db lookup fails Hermes FindSource aborted the whole lookup when a state.db query errored, even though parseArchive deliberately falls back to the transcript parser when state.db is unreadable or schema-incompatible. A valid transcript session sitting next to a corrupt or legacy state.db could no longer be located for resync. Treat a state.db lookup error as non-claiming for that root and continue to the transcript lookup, matching the parser's documented fallback. This lands at the branch that makes Hermes provider-authoritative so the regression and its fix stay in the same PR.
* feat(parser): migrate codex provider
Codex sessions have an append-only JSONL transcript plus a session_index.jsonl title sidecar. Moving Codex behind a concrete provider keeps that composite source identity and incremental append capability explicit at the provider boundary.
The provider preserves dated and archived discovery, live-over-archived lookup, shallow index watch planning, index-event classification, index-aware mtimes, source hashing, full parse output, and append parsing with full-parse fallback signals.
fix(parser): preserve codex provider sidecar semantics
Codex index changes are part of source freshness, so the provider cannot treat unchanged transcript size as no new data when the index mtime drove the fingerprint. The provider also needs to keep legacy live-over-archived UUID behavior and classify removed transcript paths syntactically.
Index events now conservatively refresh sibling Codex sources because this provider layer has no DB state for title diffing; the sync engine can still apply its DB-aware filtering before provider dispatch is fully authoritative.
Validation: go test -tags "fts5" ./internal/parser -run TestCodexProvider -count=1; go vet ./...; git diff --check. go test -tags "fts5" ./internal/parser -count=1 currently fails on TestProviderMigrationModes because inherited lower provider branches such as claude still need their branch-local shadow opt-ins.
fix(parser): make codex provider sidecars authoritative
The Codex provider could not safely infer sidecar-only freshness from a single max mtime. Rather than advertise append-only parsing with incomplete sidecar state, keep provider-authoritative Codex parses on the full-parse path until the facade can model sidecar dirtiness explicitly.
Also route persisted path lookup and changed-path classification through the same UUID canonicalization as discovery so archived duplicates do not win over live dated transcripts.
Validation: go test -tags "fts5" ./internal/parser -run 'Test(CodexProvider|ProviderMigrationModes)' -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
test(sync): compare codex shadow parity
Codex is shadow-compared on this branch, so add source-level migration coverage that compares provider observation with ParseCodexSession.
The fixture uses the real sessions/YYYY/MM/DD layout plus sibling session_index.jsonl, proving the provider preserves title sidecar behavior, parser output, and data-version planning.
Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatchesCodexLegacyParser|TestCodexProvider|TestParseCodex|TestProviderMigrationModes' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...; git diff --check
fix(parser): accept codex legacy-shaped sources
Provider-authoritative Codex sync still has to rediscover sessions that were stored by the legacy parser even when their rollout filename does not expose a UUID-shaped session id. Without that compatibility path, the later dispatch migration can drop or fail to reprocess valid Codex transcripts that ParseCodexSession can read from session metadata.
Keep the UUID-aware source contract as the preferred path and fall back to root-scoped JSONL sources only when Codex path metadata does not apply, so normal duplicate canonicalization remains unchanged while legacy-shaped fixtures stay reachable.
Validation: go test ./internal/parser -count=1; go fmt ./...; go vet ./...; go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestCodexProvider|TestSyncEngineCodex|TestSyncSingleSessionHashCodex|TestSyncEngineSkipCache' -count=1; git diff --check
refactor(parser): fold codex into provider
Make the Codex provider own its source discovery, lookup, and parse
behavior instead of shimming the package-level free functions. Delete
DiscoverCodexSessions, FindCodexSourceFile, ParseCodexSession, and
ParseCodexSessionFrom: discovery and find-source bodies move onto the
codex source set (discoverSessionPaths, findSourceFile), and parse moves
onto the provider (parseSession, parseSessionFrom). Drop the Codex
AgentDef DiscoverFunc/FindSourceFunc hooks and make Codex
provider-authoritative; ShallowWatchRootsFunc and the exec-source helpers
(IsCodexExecSessionFile, ResolveCodexShallowWatchRoots, the one-time
codex_exec skip migration) stay since only the four parser entrypoints
must go.
A provider has no database handle, so the engine reproduces the DB-aware
and mtime-aware bookkeeping the legacy single-session JSONL path
performed, scoped to Codex to preserve behavior exactly:
- shouldSkipProviderSourceByDB folds the session_index.jsonl sidecar
into a DB-stored fingerprint skip, so an unchanged transcript is not
reparsed when only the shared index mtime advanced and this session's
title did not change, and a resync still skips after the in-memory
skip cache is cleared.
- The provider Parse force-replaces stored rows because Codex emits a
full parse (it does not advertise incremental append); a late
token_count line appended to an existing turn rewrites the stored
message instead of being dropped by an append-only write.
- Index events keep flowing through the engine's DB-aware
classifyCodexIndexPath rather than the provider's broad index
fan-out: the engine fans out only to sessions whose stored title
changed and pins the chosen on-disk copy (SourceRefForPath) so the
provider's live-over-archived canonicalization cannot resurrect a
stale duplicate over the stored copy.
- SyncAllSince re-expands a UUID's live and archived duplicates
(AllSourcePathsForUUID) before the mtime cutoff filter, restoring the
legacy discover-then-filter order so a changed archived copy newer
than the cutoff is not lost behind an older live copy.
Route parse-diff, the token-use disk probe, and the SSH remote resolve
script through provider Discover/FindSource for provider-authoritative
agents that no longer carry a DiscoverFunc, so Codex sources stay
discoverable, resolvable on disk, and transferable (including the
session_index.jsonl sidecar).
Replace the deleted shadow-baseline test with provider-API coverage
(provider Discover/Parse through ObserveProviderSource) plus a guard that
the four legacy entrypoints stay gone, route the package and engine tests
through the provider methods, and remove codex_provider.go from the
pending shim scan list. This also fixes the previously known-failing
TestSyncPathsCodexIndexEventRefreshesStoredDuplicate, since the index
event now honors the stored archived copy.
test(sync): host shared shadow source helper at codex fold
The per-provider shadow/parse tests share writeProviderShadowSourceFile
to write source fixtures. The Codex fold is the lowest branch that calls
it, so the canonical definition lives here; later provider folds inherit
it instead of redeclaring their own copies.
test(sync): remove unused codex stat assignments
The pre-commit lint hook rejects two Codex appended-fixture tests because they assign os.Stat results back to info without using the value. The tests already assert the append and close operations that matter for setup.
Removing the unused assignments keeps staticcheck clean for the Codex provider migration branch.
fix(parser): pin codex duplicate sources
Codex discovery and raw-ID lookup should still prefer the live dated transcript, but exact filesystem events and DB-stored source hints are different: the caller has already selected a concrete source path. Canonicalizing those paths back to a stale live duplicate can overwrite an updated archived transcript.
Changed-path classification now returns the source pinned to the event path, and non-fresh stored path/fingerprint lookup returns the exact source so SyncSingleSession preserves the archived path already recorded in the database.
Validation: go test -tags "fts5" ./internal/parser -run 'TestCodexProvider(FindSourcePinsExactArchivedDuplicate|ChangedPathPinsArchivedDuplicate|SourceMethods|DiscoverDedupesLiveAndArchivedByUUID)' -count=1; go test -tags "fts5" ./internal/sync -run 'TestSync(PathsCodexArchivedDuplicateEventPinsChangedFile|SingleSessionCodexPreservesStoredArchivedDuplicate|PathsCodexIndexEventRefreshesStoredDuplicate|AllSinceCodexKeepsChangedArchivedDuplicate)' -count=1; go test -tags "fts5" ./internal/parser -run 'TestCodexProvider|TestParseCodex|TestDiscoverCodex' -count=1; go test -tags "fts5" ./internal/sync -run 'Test.*Codex.*' -count=1; go vet ./...; git diff --check
fix(sync): keep codex freshness skips out of cache
Codex provider DB-fresh skips are successful freshness decisions, not parse failures or intentional no-session skips. Recording them in the persistent skip cache can hide a later parser data-version bump because the cache check runs before the DB freshness check.\n\nKeep DB-fresh provider skips non-cacheable and make existing skip-cache entries fall through when a stored row at that path has a stale data version. The same bypass helper still preserves the existing stale-project self-healing behavior.\n\nValidation: go test -tags "fts5" ./internal/sync -run 'TestProcessFile(SkipCacheReparsesStaleCodex(Project|DataVersion)|CodexDBFreshSkipIsNotCached)|Test.*Codex.*' -count=1; go test -tags "fts5" ./internal/parser -run 'TestCodexProvider|TestParseCodex|TestDiscoverCodex' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go vet ./...; git diff --check
fix(sync): surface codex provider discovery failures
Provider-backed parse-diff should not report a clean or incomplete diff when provider discovery failed. Returning that error keeps requested provider-authoritative agents honest and matches the expectation that parse-diff is a verification surface, not a best-effort sync.\n\nAlso pin coverage for stale Codex index entries whose transcripts no longer resolve, so the existing empty-candidate guard cannot regress into an invalid empty work item.\n\nValidation: go test -tags "fts5" ./internal/sync -run 'Test(ParseDiffProviderDiscoveryErrorFails|ClassifyCodexIndexPathSkipsMissingTranscript|ProcessFile(SkipCacheReparsesStaleCodex(Project|DataVersion)|CodexDBFreshSkipIsNotCached))' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go vet ./...; git diff --check
fix(sync): drop duplicate shadowCallerProvider Discover in codex test
* feat(parser): migrate gemini copilot providers
Gemini and Copilot are direct local file sources, but each has source-shape details that were still coupled to the legacy adapter path. Moving them behind concrete providers keeps Gemini tmp/<project>/chats discovery and Copilot bare-vs-directory precedence explicit.
The providers preserve raw and full ID lookup, changed-path classification, source hashing, Gemini project hints, Copilot workspace.yaml freshness, aggregate usage events, and parser output normalization.
fix(parser): preserve gemini copilot provider freshness
Gemini and Copilot now advertise provider-owned watch classification, so remove and rename events need to map back to syntactic source refs even after the filesystem entry has disappeared. Without that fallback, watcher-driven sync can leave stale provider sessions until a wider resync happens.\n\nCopilot also exposes a composite fingerprint that includes workspace.yaml freshness and shutdown aggregate usage. The provider parse result has to carry that same file metadata and usage event slice because sync consumes ParseResult, not only ParsedSession.\n\nValidation: go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
fix(parser): include gemini project metadata freshness
Gemini project names can come from projects.json or trustedFolders.json, so treating only the transcript as the provider source leaves metadata-only changes stale. The provider now watches those root-level sidecars, classifies their changes back to discovered sessions, and folds their contents into the source fingerprint.\n\nValidation: go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
fix(parser): hash copilot workspace metadata
Copilot workspace.yaml can change the provider-visible title without changing the event stream. Size and mtime are useful freshness guards, but the provider hash should also include the workspace file contents so same-length title edits cannot be skipped.\n\nValidation: go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
fix(sync): bridge provider path classification
Concrete providers own source sidecars that legacy path classifiers do not know about. SyncPaths now falls back to provider changed-path classification after the legacy classifiers miss, and provider-classified files force a full parse so metadata-only events can refresh stored session state.\n\nLegacy classification remains authoritative when it recognizes a path, preserving existing project extraction and optimized sidecar filters while still letting migrated providers cover new sidecar surfaces.\n\nValidation: go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go vet ./...; git diff --check
fix(sync): preserve provider sidecar reparses
Provider sidecar events can map to the same session file as a legacy path event in one watcher batch. Keeping only the first classified file made the result order-dependent and could drop the force-parse signal that metadata-only changes rely on.
Per-file forced parses also need to bypass the generic skip cache, not just the agent-specific mtime checks, because sidecar updates may leave the transcript mtime untouched while still changing parsed session metadata.
Validation: go test -tags "fts5" ./internal/sync -run 'TestSyncPathsGeminiProjectMetadataEventRefreshesProject' -count=1; go test -tags "fts5" ./internal/sync -count=1; go test -tags "fts5" ./internal/parser -run 'Test(Gemini|Copilot|ProviderMigration)' -count=1; go vet ./...; git diff --check
fix(sync): skip removed provider source events
Provider changed-path classification can return syntactic source refs for deleted files so providers can model remove events. While legacy file processing is still authoritative, enqueueing an exact missing source path makes SyncPaths fail at the initial stat instead of treating the watcher remove as a no-op.
Keep sidecar fanout intact for existing sources, because metadata changes such as Gemini projects.json still need to force a reparse even when the transcript mtime is unchanged.
Validation: go test -tags "fts5" ./internal/sync -run 'TestEngine_ClassifyPathsProvider(RemoveSkipsMissingGeminiSource|SidecarKeepsExistingGeminiSources)|TestSyncPathsGeminiProjectMetadataEventRefreshesProject' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...; git diff --check
test(sync): compare gemini copilot shadow parity
Gemini and Copilot are migrated through concrete providers on this branch, so reviewers need a sync-level parity check that exercises the provider observation contract rather than only parser-local behavior.
The fixtures cover their sidecar-sensitive source shapes: Gemini project metadata feeds the resolved project hint, and Copilot workspace.yaml participates in both title selection and the composite fingerprint.
Validation: go test -tags "fts5" ./internal/sync -run 'TestObserveProviderSourceMatches(Gemini|Copilot)LegacyParser' -count=1; go test -tags "fts5" ./internal/parser -run 'Test(Gemini|Copilot)Provider' -count=1; go vet ./...; git diff --check. Full go test -tags "fts5" ./internal/parser ./internal/sync -count=1 currently fails in existing TestSyncPathsCodexIndexEventRefreshesStoredDuplicate.
refactor(parser): fold gemini and copilot into providers
Move Gemini and Copilot source discovery, lookup, and parse ownership onto
the concrete geminiProvider and copilotProvider and delete the six
package-level legacy entrypoints: DiscoverGeminiSessions,
FindGeminiSourceFile, ParseGeminiSession, DiscoverCopilotSessions,
FindCopilotSourceFile, and ParseCopilotSession.
Discovery and find-source bodies now live as provider-owned source-set
helpers (discoverSessionPaths and findSourceFile on each source set), the
gemini confirmGeminiSessionID guard moves to the provider file, and the
parsers become the providers' parseSession methods. The copilot source set's
bare/dir precedence and dedup, and the gemini session-filename matching, are
reproduced on the provider exactly as before.
Gemini project resolution is preserved on the provider: sourceRef already
resolves the project via BuildGeminiProjectMap/ResolveGeminiProject for both
discovery and changed-path classification, so removing the engine's gemini
project-map plumbing loses no project names. BuildGeminiProjectMap and
ResolveGeminiProject stay exported package helpers used by the provider.
Make both Gemini and Copilot provider-authoritative and drop their legacy
sync dispatch: the classifyOnePath copilot and gemini blocks (and the now
unused geminiProjectsByDir parameter threaded through classifyOnePath and
classifyPaths), the processFile case arms, and the processGemini,
processCopilot, and shouldSkipCopilot methods. copilotEffectiveMtime stays as
a shared composite-mtime helper used by discoveredFileMtime.
Wire the provider facade into parse-diff: agents that dropped their
DiscoverFunc are now discovered through discoverProviderSources (filtered to
the resolved, provider-discoverable agents), and resolveParseDiffAgents
accepts file-based agents backed by a shadow-compare or
provider-authoritative provider. Without this, a provider-authoritative agent
would silently fall out of parse-diff once its DiscoverFunc was removed.
Drop the Gemini and Copilot AgentDef DiscoverFunc/FindSourceFunc hooks, remove
both files from the pending shim scan list, delete the shared shadow-baseline
test file, and replace it with provider-API coverage plus guards asserting the
legacy entrypoints stay gone. Package and engine tests route through the
provider methods via new test helpers.
test(sync): drop duplicate shadow source helper def
The canonical writeProviderShadowSourceFile now lives at the Codex fold,
so this redeclaration in provider_shadow_test.go conflicts with it. Drop
the local copy and its now-unused os/path filepath imports; callers use
the inherited shared helper.
test(sync): restore provider-aware classify tests at gemini fold
The original restack mis-merged engine_test.go on this branch, reverting
the OpenCode SQLite, OpenCode removed-file, Claude stat-error, and Vibe
meta-only classification tests to their stale pre-fold shapes (fake
opencode.db bytes instead of a seeded session, dropped
seedOpenCodeSQLiteSession helper) and re-adding a classify_vibe_test.go
that exists on no lower branch. Those stale tests asserted the legacy
direct-classification behavior and failed against the provider-routed
path. Restore the correct versions inherited from the codex branch, keep
this branch's two new Gemini provider classify tests, and drop the
spurious classify_vibe_test.go.
test(sync): restore gemini provider classify tests at gemini fold
Re-add the two Gemini changed-path classify tests
(TestEngine_ClassifyPathsProviderRemoveSkipsMissingGeminiSource and
TestEngine_ClassifyPathsProviderSidecarKeepsExistingGeminiSources) that
were dropped while restoring this branch's mis-merged engine_test.go to
its provider-aware shape.
fix(sync): skip fresh gemini copilot before hashing
Gemini and Copilot lost their legacy DB freshness gates when the provider-authoritative path took over. That made unchanged sessions reach provider fingerprinting and parsing during normal full syncs, which is unnecessary work and no longer matches the old processGemini/processCopilot behavior.\n\nRestore the cheap pre-fingerprint checks for those two agents: Gemini compares the stored file path size and mtime, while Copilot compares transcript size plus the workspace.yaml effective mtime. Force-parse paths still flow through the provider so sidecar-driven reparses and parse-diff are not suppressed.\n\nValidation: go test -tags "fts5" ./internal/sync -run 'TestProcessFileProviderAuthoritativeSkipsFresh(Gemini|Copilot)BeforeFingerprint|TestProcessCodexAppendedStaleProject(DoesFullReparse|CarriesForceReplace)' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go vet ./...; git diff --check
fix(sync): restore discover fields on shadowCallerProvider
The rebase onto origin/main dropped the discoverSources and discoverErr
fields from the shadowCallerProvider test struct while keeping the Discover
method that reads them, leaving this branch and every branch stacked above
it uncompilable. Restore the two fields so the Discover stub resolves.
* feat(parser): migrate copilot ide providers
VS Code Copilot and Visual Studio Copilot both needed concrete providers because their source identity is richer than a plain parser callback. VS Code needs workspace and global chat discovery with .jsonl preference, while Visual Studio needs virtual per-conversation trace sources with sibling-aware freshness.
The providers preserve raw and full ID lookup, watch classification, source hashing, VS Code project hints, Visual Studio physical trace fan-out, strict composite trace fingerprints, force-replace parse semantics, and parser output normalization.
fix(parser): classify copilot ide source changes
The Copilot IDE providers advertised changed-path classification, but the initial migration only accepted source paths that still existed. That dropped deletion and metadata-only events before the sync layer could make a refresh or removal decision.
Classify syntactically valid removed VS Code chat files and Visual Studio trace files, fan workspace.json changes out to current workspace chat sessions, and cover Visual Studio physical trace fan-out with multiple conversations.
fix(parser): include vscode workspace metadata freshness
VS Code Copilot project names come from workspace.json, so classifying manifest writes is not enough if the source fingerprint still only reflects the chat transcript. An unchanged chat file could skip the parse that refreshes Session.Project.
Fold workspace.json size, mtime, and content hash into workspace chat fingerprints while leaving global chat fingerprints unchanged, and cover metadata-only freshness in the provider tests.
fix(sync): refresh vscode copilot workspace metadata
VS Code Copilot was provider-aware for workspace.json freshness, but this stack still runs legacy sync writes. Without mirroring that freshness in the legacy process path, metadata-only workspace renames could be classified but then skipped against the unchanged chat transcript.
Move the Copilot IDE providers into shadow compare on their migration branch, preserve .jsonl priority during provider changed-path classification, and store composite workspace freshness for VS Code Copilot sessions while both shapes run.
Validation: go test -tags "fts5" ./internal/sync -run 'TestSyncPathsVSCodeCopilot(JSONLPriority|WorkspaceMetadataRefreshesProject)' -count=1; go test -tags "fts5" ./internal/parser -run 'Test(VSCodeCopilotProvider|VisualStudioCopilotProvider|ProviderMigrationModes)' -count=1; go test -tags "fts5" ./internal/sync -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
test(sync): compare copilot ide shadow parity
VS Code Copilot and Visual Studio Copilot are already opted into shadow comparison on this branch, but provider method tests alone do not prove the migration path still matches the legacy parser output consumed by sync.
Cover the workspace-backed VS Code JSONL source and Visual Studio virtual trace source through ObserveProviderSource so reviewers can see provider observation, data-version planning, and legacy parser parity in one place.
Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatches(VSCodeCopilot|VisualStudioCopilot)LegacyParser|TestCopilotIDEProvider|Test(VSCodeCopilotProvider|VisualStudioCopilotProvider)' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...; git diff --check
refactor(parser): fold copilot IDE providers
Move VSCode Copilot and Visual Studio Copilot source discovery, lookup, and
parse ownership onto their concrete providers and delete the seven legacy
package-level free functions: DiscoverVSCodeCopilotSessions,
FindVSCodeCopilotSourceFile, ParseVSCodeCopilotSession,
DiscoverVisualStudioCopilotSessions, FindVisualStudioCopilotSourceFile,
ParseVisualStudioCopilotConversation, and ParseVisualStudioCopilotVirtualPath.
VSCode Copilot: discoverSessionFiles and findSourceFile become source-set
helpers, parseSession becomes a provider method, and the shared
discoverVSCodeSessionFiles helper stays in discovery.go.
Visual Studio Copilot: discoverSessionFiles and findSourceFile become
source-set helpers (over the retained findVisualStudioCopilotTraceSourceFile
and discoverVisualStudioCopilotSessionFiles helpers), and parseConversation
becomes a provider method. The virtual-path resolution is reproduced on the
provider via the provider-neutral ParseVirtualSourcePath helper plus the
trace-file and conversation-ID predicates (splitVisualStudioCopilotVirtualPath),
replacing the deleted ParseVisualStudioCopilotVirtualPath. External callers
(session export, direct service, parsediff, engine skip-path checks) use the
new exported SplitVisualStudioCopilotVirtualPath, which wraps the same neutral
splitter. The provider's discovery now surfaces an unreadable physical trace
file as a source so the read failure is reported instead of being dropped.
Make both providers provider-authoritative and drop their legacy sync dispatch:
the classifyOnePath VSCode block, classifyVisualStudioCopilotPath and its call,
the processFile case arms, processVSCodeCopilot and its vscodeCopilot* helpers,
processVisualStudioCopilot, the vscodeJSONLSiblingExists helper, and the
now-dead legacy-preamble references to these agents.
Drop the AgentDef DiscoverFunc/FindSourceFunc hooks for both, remove both
provider files from the pending shim scan list, and replace the shadow-baseline
test with provider API coverage plus a guard asserting the legacy entrypoints
stay gone. Re-home the shared writeProviderShadowSourceFile test helper into
provider_shadow_test.go so the sync test package builds.
fix(parser): preserve copilot provider metadata
Provider-authoritative Copilot sync consumes ParseResult side channels, not only fields stored on ParsedSession. VS Code Copilot was parsing aggregate token usage but returning an empty ParseResult.UsageEvents slice, so a provider resync could erase usage rows.
Visual Studio Copilot single-session resyncs carry the stored project through Source.ProjectHint. Honoring that hint prevents the provider default from overwriting preserved project metadata, while VS Code now also carries the composite fingerprint size and mtime alongside the hash.
Validation: go test -tags "fts5" ./internal/parser -run 'Test(VSCodeCopilotProviderSourceMethods|VisualStudioCopilotProviderSourceMethods)' -count=1; go test -tags "fts5" ./internal/sync -run 'TestSyncPathsVSCodeCopilotPersistsUsageEvents|TestSyncSingleSessionContextVisualStudioCopilotPreservesProject' -count=1; go test -tags "fts5" ./internal/parser -run 'Test.*Copilot.*Provider|TestParseVSCodeCopilotSession_TokenUsage|TestParseVisualStudioCopilot' -count=1; go test -tags "fts5" ./internal/sync -run 'Test.*(VSCodeCopilot|VisualStudioCopilot).*' -count=1; go vet ./...; git diff --check
test(parser): guard visual studio copilot session fold
The Copilot IDE fold deleted ParseVisualStudioCopilotSession along with the other Visual Studio Copilot legacy entrypoints, but the regression guard did not name that symbol. Adding it prevents a future shim from reappearing unnoticed.
Validation: go test -tags "fts5" ./internal/parser -run 'TestCopilotIDEProvidersOwnLegacyEntrypoints|Test(VSCodeCopilotProviderSourceMethods|VisualStudioCopilotProviderSourceMethods)' -count=1; git diff --check
* feat(parser): migrate positron provider
Fold Positron onto a concrete provider-authoritative implementation and
delete the duplicated legacy parser path so there is a single source of
truth for its workspaceStorage-only layout and parse behavior. Discovery,
source lookup, and parse move onto the provider; the package-level
DiscoverPositronSessions, FindPositronSourceFile, and ParsePositronSession
free functions are removed and positron.go is deleted. The engine's
positron-specific dispatch, effective-mtime, and skip-cache blocks are
removed in favor of the provider Fingerprint, which folds workspace.json
size, mtime, and a chat+workspace composite hash into the source
fingerprint so a workspace-only project rename still re-syncs.
To keep that composite freshness once positron has no legacy mtime block,
the SyncAllSince mtime filter resolves provider-authoritative sources
through the provider Fingerprint (discoveredFileEffectiveMtime) instead of
the legacy per-agent mtime path. Codex is excluded from that path: its
Fingerprint folds the shared session_index.jsonl mtime into every session,
which is correct for the skip cache but defeats the per-copy mtime
discrimination the incremental-sync cutoff needs to preserve a changed
archived duplicate, so codex keeps its raw per-file mtime and the index
refresh stays handled separately by codexIndexRefresh. The OpenCode
incremental-sync test asserts the resulting composite freshness, where a
part-only edit advances the source mtime past the cutoff and re-syncs.
* test(sync): update Codex incremental fingerprint expectation
Codex does not advertise incremental append, so re-syncing an appended
transcript is a full re-parse that stores the raw file size and hash,
including the ignored partial trailing line. The parsed-snapshot versus
partial-tail distinction is enforced at parse-diff time via
CodexTranscriptConsumedSize, not in the stored fingerprint. Align the
regression with the provider-folded behavior.
* test(parser): drop migrated providers from the pending shim list
This branch folds the positron, Visual Studio Copilot, and VS Code
Copilot providers onto their provider-owned source sets, so their
*_provider.go files no longer reference legacy free functions. The
anti-shim gate (TestProviderFilesDoNotReferenceLegacyEntrypoints)
requires a provider be removed from pendingShimProviderFiles on the same
branch it stops being a shim, so leaving these three entries fails the
gate here and on every branch up to where they were previously removed.
Remove them now; the remaining entries fold in on later branches.
* fix(sync): reparse gemini sessions when project metadata changes
The pre-fingerprint fast skip compared only the Gemini session transcript's size and mtime, but the Gemini provider's fingerprint is composite: it folds in projects.json and trustedFolders.json, which resolve a session's project. A scheduled SyncAll could therefore skip a session whose transcript was unchanged while its project metadata had changed, leaving a stale project until the transcript itself was rewritten.
Drop Gemini from the fast skip so it computes the composite fingerprint and relies on the post-fingerprint skip cache, whose mtime folds the metadata in, exactly as the other non-Codex providers already do. The live-watcher metadata refresh path was already correct; this closes the periodic-sync gap. The obsolete shadow-caller test that asserted the old skip is replaced by a behavioral SyncAll reparse test.
* feat(parser): discover Codex S3 sessions through the provider facade
This branch migrates Codex to a provider-authoritative source set, making
discoverProviderSources its sole on-disk discovery path. codexSourceSet.discover
only walked a local session-directory layout, so a migrated Codex pointed at an
s3:// rollout root enumerated nothing -- a regression against the pre-migration
DiscoverFunc, which enumerated remote objects directly.
Add an s3:// branch to discover: it lists rollout objects via discoverCodexS3
and builds an S3 SourceRef per object, carrying the durable metadata in the
Opaque payload for the engine to thread back into the DiscoveredFile. Each
object is its own URI-keyed session, so the local live-over-archived preference
does not apply. s3:// objects keep processing on the dedicated S3 sync path.
* test(sync): cover S3 discovery+sync against a real object store
The provider migration once silently dropped s3:// discovery because every S3
test stubbed the listS3Objects/fetchS3Object seam or injected already-formed
s3:// paths into processing -- nothing ran real discovery against an object
store, so the regression passed the whole suite.
Add a testcontainers integration test (s3test build tag + Docker, mirroring the
pgtest setup) that boots a MinIO container, points the production env-driven
s3Client at it, uploads a Claude session and a Codex rollout under the
/<machine>/raw/<provider> layout, and runs a full provider-authoritative
SyncAll against s3:// roots. It asserts both remote sessions are discovered,
fetched, parsed, and persisted machine-namespaced from the s3 root -- the
end-to-end path with no other real-store coverage. testcontainers manages the
container lifecycle, so make test-s3 needs only a working Docker daemon. Any
S3-compatible image (e.g. rustfs) works by swapping s3ContainerImage.
* test(sync): run the S3 integration container on rustfs
MinIO is no longer maintained, so the S3 discovery integration test now boots
rustfs -- an actively maintained S3-compatible object store -- instead. rustfs
takes credentials via RUSTFS_ACCESS_KEY/RUSTFS_SECRET_KEY, serves the S3 API on
:9000, and answers /health with 200 once ready, which drives the testcontainers
wait strategy. The discovery+sync assertions are unchanged; only the backing
container image and its credential/health wiring move.
* feat(parser): migrate zed shelley providers Zed and Shelley both store multiple conversations in a shared SQLite database, so their provider boundary needs to model the physical database source separately from per-session virtual paths. Keeping that source shape explicit makes lookup, watch classification, and force-replace parse behavior available through the shared facade instead of the legacy adapter path. The providers preserve physical DB discovery, WAL/SHM change classification, raw and full ID lookup, virtual source fingerprints, multi-session parse fan-out, per-session Shelley content fingerprints, and parser output normalization. fix(parser): define sqlite provider deletion semantics Shared SQLite providers need to treat deletion events as source-level state, not as unclassifiable paths. Without that, a deleted Zed or Shelley database can disappear before the provider facade reports a complete empty source, leaving future cleanup behavior under-specified. Classifying syntactically valid DB, WAL, and SHM paths even after the main DB is gone preserves the watcher-to-parse path. A missing backing DB now produces a complete force-replace SkipNoSession outcome, and the Zed fingerprint comment records the intentional legacy whole-DB hash tradeoff. fix(parser): align zed shelley capabilities Provider capabilities are used as a contract for what normalized content a provider can actually emit. Shelley currently records token totals from messages but does not emit aggregate usage events, and Zed filters child threads before relationship fields can surface. Keep the declarations conservative so the facade does not advertise unsupported content features while the providers continue to preserve the parser behavior they actually expose today. fix(parser): tighten sqlite sidecar classification Deleted DB sidecar events need to remain classifiable, but basename-only matching is too broad once missing DB parses become complete force-replace outcomes. Unrelated files under the same provider root should not synthesize the canonical shared DB source. Restrict Zed sidecars to the watched threads directory and Shelley sidecars to the provider root, with regression tests for unrelated matching basenames. fix(parser): shadow zed shelley providers The Zed and Shelley branch had concrete providers and passing provider coverage, but still left both agents marked legacy-only. That kept the stack additive and prevented provider changed-path classification from participating while both shapes run. Move both agents into shadow compare on their migration branch so the runtime bridge can exercise the concrete providers before the legacy path is removed later in the stack. Validation: go test -tags "fts5" ./internal/parser -run 'Test(ZedProvider|ShelleyProvider|ProviderMigrationModes)' -count=1; go test -tags "fts5" ./internal/sync -run 'TestSync.*(Zed|Shelley)' -count=1; go test -tags "fts5" ./internal/parser -count=1; go test -tags "fts5" ./internal/sync -count=1; go vet ./...; git diff --check fix(sync): tolerate deleted sqlite provider sources Zed and Shelley providers can classify removed physical database paths while shadow comparison is enabled. Legacy sync still owns writes on this branch, so those forced remove events must not fail at the pre-parse stat step. Treat provider-classified deleted physical SQLite sources as an OK no-result parse. This keeps the archive-preserving legacy behavior while avoiding watcher failures until provider-authoritative deletion semantics are implemented at the stack tip. Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestEngine_(ClassifyPathsProviderRemoveKeepsDeletedSQLiteSources|ProcessFileProviderDeletedSQLiteSourcesDoNotFail)|Test(Zed|Shelley)ProviderClassifiesDeletedPhysicalDB|Test(Zed|Shelley)Provider' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...; git diff --check test(sync): compare zed shelley shadow parity Zed and Shelley are shadow-compared database-backed providers, so provider method tests are not enough to prove the sync migration preserves legacy parser output. Cover physical DB source observation for both providers and compare sessions, messages, force-replace intent, and data-version planning against the legacy DB parsers. Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatches(Zed|Shelley)LegacyParser|Test(Zed|Shelley)Provider' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...; git diff --check test(parser): cover sqlite provider stored hints SQLite fan-out providers need to treat stored virtual source paths differently depending on caller intent. Fresh lookups should reject deleted rows or malformed/stale virtual paths, while non-fresh lookup still needs to preserve the virtual source identity so changed-path cleanup can observe a SkipNoSession tombstone. This closes part of the stored-hint compatibility gap for the Zed/Shelley branch without making provider writes authoritative. The tests document row deletion, invalid virtual paths, stale DB-path hints, and tombstone parse behavior while legacy sync remains the write path. Validation: go test -tags "fts5" ./internal/parser -run 'Test(Zed|Shelley)Provider' -count=1 -v; go test -tags "fts5" ./internal/sync -run 'TestObserveProviderSourceMatches(Zed|Shelley)LegacyParser|TestEngine_ClassifyPathsProviderRemoveKeepsDeletedSQLiteSources|TestEngine_ProcessFileProviderDeletedSQLiteSourcesDoNotFail' -count=1 -v; go test -tags "fts5" ./internal/parser ./internal/sync -count=1 (sync fails only known TestSyncPathsCodexIndexEventRefreshesStoredDuplicate on this branch); go fmt ./...; go vet ./...; manual ./custom-gcl package loop with GOMAXPROCS=1 GOGC=5 GOMEMLIMIT=128MiB; git diff --check Generated with Codex Co-authored-by: Codex <codex@openai.com> refactor(parser): fold zed and shelley providers Move Zed and Shelley source ownership onto their concrete providers and delete the ten package-level legacy entrypoints (DiscoverZedSessions, FindZedSourceFile, ParseZedSQLiteVirtualPath, ParseZedThreadDirect, ParseZedThreadFromDB, DiscoverShelleySessions, FindShelleySourceFile, ParseShelleyConversationDirect, ParseShelleyConversationFromDB, ParseShelleyVirtualPath) plus the now-orphaned FindShelleyDBPath helper. Both agents become provider-authoritative so runtime sync routes through provider discovery, changed-path classification, and processProviderFile instead of the removed processZed/processShelley methods and syncSingleZed. Both providers keep multiple conversations in one shared SQLite database addressed by a "<dbPath>#<id>" virtual path. The fold preserves that shape: - Discovery surfaces the single physical threads.db / shelley.db as one source; Parse fans it out to one session per thread/conversation. - Virtual-path resolution flows through the provider-neutral ParseVirtualSourcePathForBase helper. parseZedVirtualPath restores the legacy IsValidSessionID guard the bespoke parser enforced; parseShelley VirtualPath maps directly onto the shared helper. Every engine call site that split a virtual path now uses the neutral helper too, and the surviving ZedSQLiteSourceMtime / ShelleySourceMtime watchers were repointed at it. - The Zed and Shelley single-conversation direct parses move onto the providers as parseThreadDirect / parseConversationDirect over the unexported parseZedThreadFromDB / parseShelleyConversationFromDB. Because a provider has no database handle, the engine reproduces the per-session skip the legacy fan-out loops performed in dropUnchangedSharedSQLiteResults: the provider re-parses every session on any database change, and the engine drops results whose stored file_mtime (plus the content fingerprint in file_hash for Shelley's second-precision timestamps) and data_version already match, applying the path rewriter so remote stored paths resolve. Force-parse runs keep every result. A forced parse on a deleted shared database now completes as an empty force-replace in processProviderFile so the engine retires the removed sessions instead of failing. ParseDiff synthesizes the Zed/Shelley database source the way it already does for Kiro/OpenCode/Kilo so --agent zed/shelley keeps working without a DiscoverFunc. Tests move from the deleted free functions to provider API coverage, add a guard asserting the legacy entrypoints stay gone, drop both provider files from the pending shim scan list, and remove the shadow comparison test. The shared writeProviderShadowSourceFile helper is rehomed into a dedicated support file so the sync package keeps compiling after the shadow test is deleted. refactor(parser): delete zed legacy whole-database parser ParseZedSessions parsed every top-level thread in a Zed threads.db, but the provider routes per-thread through parseZedThreadFromDB, so the free function survived only as test-exercised dead production code. Delete ParseZedSessions; the retained parse tests reproduce the whole-database walk with the provider's own primitives (ListZedThreadMetas + parseZedThreadFromDB), which share the top-level parent_id filter and ordering, so they exercise the production path without the deleted shim. fix(sync): preserve zed shelley force replaces Zed and Shelley share one physical SQLite database across many virtual session paths. Provider-authoritative sync needs source-level force-replace behavior to retire those virtual sessions when a physical database disappears or when a provider reports a complete empty outcome. SyncSingleSession also marks the discovered source with ForceParse, so unchanged-result filtering must respect the per-file flag instead of only the engine-wide flag. Otherwise a targeted resync can silently keep corrupted stored rows because the shared SQLite fingerprint has not changed. Validation: go test -tags "fts5" ./internal/sync -run 'TestSync(SingleSessionZedForce|PathsZedDeleted|SingleSessionShelleyForce|PathsShelleyDeleted)' -count=1; go test -tags "fts5" ./internal/parser -run 'Test(Zed|Shelley)Provider' -count=1; go test -tags "fts5" ./internal/sync -run 'Test.*(Zed|Shelley).*' -count=1; go vet ./...; git diff --check * feat(parser): migrate kiro providers Kiro has two source families that were still coupled to the legacy sync adapter: CLI JSONL plus current-store SQLite for Kiro, and old .chat plus workspace-session JSON for Kiro IDE. Moving them behind concrete providers keeps those source shapes explicit at the facade boundary. The Kiro provider preserves current-store fan-out, per-session SQLite virtual lookup, legacy JSONL shadowing, source hashing, changed-path classification, force-replace SQLite parses, per-session source errors, and Kiro IDE old/new session parsing through the existing parsers. fix(parser): prefer kiro sqlite lookup Kiro sessions can migrate from legacy JSONL files into the current-store SQLite database while the persisted row still points at the old source path. Source lookup needs to treat the session ID as authoritative in that case, otherwise explicit resyncs can resolve the shadowed JSONL file and skip the current SQLite session. fix(parser): align kiro provider shadowing The legacy Kiro sync path treated current-store SQLite sessions as globally shadowing legacy JSONL files across all configured roots. The provider needs the same behavior so multi-root setups do not parse the same logical session from both source families. Deleted SQLite DBs and per-session rows also need to fingerprint as tombstones so the provider caller can still reach parse and produce the force-replace SkipNoSession outcome used for archive cleanup. fix(parser): shadow kiro providers The Kiro provider branch had concrete Kiro and Kiro IDE providers, but the migration manifest still held both agents on legacy-only mode. That prevented the provider bridge from exercising their changed-path behavior during the dual-run phase. Move both Kiro agents into shadow compare on their migration branch so the stack remains a runtime migration instead of an additive provider implementation. Validation: go test -tags "fts5" ./internal/parser -run 'Test(KiroProvider|KiroIDEProvider|ProviderMigrationModes)' -count=1; go test -tags "fts5" ./internal/sync -run 'Test.*Kiro' -count=1; go test -tags "fts5" ./internal/parser -count=1; go test -tags "fts5" ./internal/sync -count=1; go vet ./...; git diff --check test(sync): compare kiro family shadow parity Kiro and Kiro IDE are shadow-compared on this branch, so the migration should prove provider observation still matches the legacy parsers that currently feed sync writes. Cover Kiro SQLite database sources and Kiro IDE workspace-session JSON sources through ObserveProviderSource, including force-replace intent and data-version planning. Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestObserveProviderSourceMatches(KiroSQLite|KiroIDE)LegacyParser|TestKiroProvider|TestKiroIDEProvider' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser/... ./internal/sync/...; git diff --check test(parser): cover kiro stored source hints Kiro's SQLite provider already supports tombstone parsing for missing database rows and deleted databases, but fresh stored-source lookup still accepted those stale hints. During the dual-run migration, callers use RequireFreshSource to distinguish explicit fresh lookup from changed-path cleanup, so the provider needs to honor that contract before legacy dispatch can be removed. Fresh stored SQLite paths now require the physical DB or virtual row to exist, while non-fresh lookup still preserves source identity for SkipNoSession tombstones. The tests also reject malformed and stale SQLite virtual paths under the Kiro root. Validation: go test -tags "fts5" ./internal/parser -run 'TestKiro' -count=1; go test -tags "fts5" ./internal/sync -run 'TestObserveProviderSourceMatchesKiro(SQLite|IDE)LegacyParser|TestProcessFileProviderAuthoritativeSourceErrorsOnlyForceParse' -count=1 -v; go fmt ./...; go vet ./...; GOMAXPROCS=1 GOGC=5 GOMEMLIMIT=128MiB ./custom-gcl run --config .golangci.nilaway.yml ./internal/parser; git diff --check Generated with Codex Co-authored-by: Codex <codex@openai.com> refactor(parser): fold kiro providers Kiro and Kiro IDE were still dual-running: concrete providers existed but the migration manifest held both on shadow-compare, so the legacy package-level entrypoints and a large legacy sync dispatch still owned writes. Promote both agents to provider-authoritative and delete that legacy surface so the providers are the single source of truth. The eight legacy free functions are removed: DiscoverKiroSessions, FindKiroSourceFile, ParseKiroSession, FindKiroSQLiteDBPath, ParseKiroSQLiteVirtualPath, ParseKiroSQLiteSession (Kiro) and FindKiroIDESourceFile, ParseKiroIDESession (Kiro IDE). Discovery, legacy-JSONL source lookup, and both parse paths move onto the concrete providers; the orphaned DiscoverKiroIDESessions helper goes with them. SQLite virtual-path handling is preserved through the provider-neutral resolver. The Kiro provider continues to give each conversation row a stable identity via KiroSQLiteVirtualPath/VirtualSourcePath and resolves a "<db>#<sessionID>" path back through ParseVirtualSourcePathForBase (now via the unexported kiroSQLiteVirtualPathParts in the parser and a sync-package equivalent). Current-store fan-out, per-session virtual lookup, cross-root legacy shadowing, source hashing, force-replace SQLite parses, and per-session source errors all keep their existing behavior. The engine loses its kiro legacy dispatch: the bulk syncKiroSQLite phase, classifyKiroSQLitePath plus the legacy-JSONL classifyOnePath block, the processKiro/processKiroIDE arms and methods, syncSingleKiroSQLite, and the now-redundant per-session count/shadow helpers. Provider discovery now emits the data.sqlite3 source and processProviderFile fans it out, so the DB is counted once via normal file sync instead of the separate DB-backed accounting. The cross-root legacy shadow filter stays in the engine because a scoped sync configures the provider with only the in-scope roots and cannot otherwise see a current-store DB in an out-of-scope root. providerChangedPathEventKind now resolves a virtual source path to its physical container before the existence check so a per-session SQLite resync via SyncPaths is treated as a write rather than a phantom remove. Parse-diff discovers kiro through the provider facade (parseDiffProviderDiscover) now that it carries no DiscoverFunc hook. The two shadow-baseline assertions that encoded the old bulk-sync idempotency (a no-op resync counting zero) are updated for the authoritative model, where the database is rediscovered and re-parsed every full sync; archive preservation on a malformed update is unchanged. The shadow parity test is replaced with provider-API coverage, a parser guard asserts the legacy entrypoints stay gone, and both provider files leave the pending-shim scan list. fix(parser): thread ctx through kiro_ide source lookups * feat(parser): migrate antigravity providers Move Antigravity IDE and CLI source discovery, lookup, and parse ownership onto concrete antigravityProvider and antigravityCLIProvider types, deleting the package-level legacy free functions and their legacy sync dispatch. Both agents become provider-authoritative. Sidecar and freshness semantics are preserved through the providers' SourcesForChangedPath fan-out and composite fingerprints rather than engine-level classifiers: the IDE provider maps annotations and brain artifacts back to the conversation DB, and the CLI provider maps history, brain, trajectory, and db/pb-precedence sidecars to every affected source. Drop the obsolete engine-level TestClassifyOnePath_AntigravityCLI, which exercised the removed classifyOnePath antigravity arm. The antigravity provider unit tests cover the per-path sidecar-to-source mappings and the engine integration tests cover the engine-to-provider routing, so the test asserted removed behavior without adding coverage. fix(parser): preserve antigravity history invalidation Antigravity CLI history changes are watched and classified through fresh provider instances, so provider-local history snapshots cannot reliably detect rows that were removed or retagged. Treat history.jsonl writes conservatively and fan out to all current CLI sources, which preserves stale-metadata cleanup at the cost of a broader reparse on history-only updates. The file watcher now consumes provider watch plans for agents that only had plain WatchSubdirs wiring, so provider-owned roots such as Antigravity CLI's history.jsonl parent are observed by the real watcher setup while bespoke legacy watch-root functions keep their existing behavior. Validation: go test -tags fts5 ./internal/parser -count=1; go test -tags fts5 ./internal/sync -run 'Test.*AntigravityCLI|TestProcessAntigravity|TestSyncPathsAntigravity' -count=1; go test -tags fts5 ./cmd/agentsview -run TestCollectWatchRoots -count=1; go vet ./...; git diff --check * feat(parser): migrate db-backed providers Move Forge, Piebald, and Warp DB discovery and per-session parse ownership onto their shared db-backed provider implementation, deleting the package-level legacy entrypoints and per-agent engine sync dispatch. The three become provider-authoritative. Full-sync change detection runs through syncProviderDBBackedAgent, which enumerates provider sources and skips those whose fingerprint mtime matches the stored data-version mtime, so a repeat sync of unchanged data stays a no-op. FindSourceFile, SourceMtime, and SyncSingleSession route these agents through the provider facade, preserving Piebald's chat-source-resolves-fork semantics including rejection of unknown forks. Assert the provider-authoritative skip in the Piebald process test: an unchanged chat skips on its per-chat updated_at fingerprint, matching the legacy piebaldPendingSessionIDs skip and the Forge sibling. The prior test asserted the opposite, a stale shadow-compare expectation that reparsed an untouched session on every full sync. refactor(parser): delete db-backed legacy whole-database parsers The db-backed provider migration left the exported whole-database and single-session parse free functions (ParseForgeDB, ParsePiebaldDB, ParsePiebaldSession, ParseWarpDB) in place: the provider routes through the lowercase per-session helpers (parseForgeSession, parsePiebaldSessionResults, parseWarpSession), so these survived only as dead production code kept alive by their own tests. ParsePiebaldDB had no references at all. Delete the four functions and the now-orphaned chain (loadForgeConversations, loadWarpConversations, loadPiebaldChats, and the ForgeSession/WarpSession/PiebaldSession bundle types). The retained parse tests now drive the provider facade (Discover + Fingerprint + Parse) via a shared parseDBBackedAll helper instead of the deleted free functions, so they exercise the production path. Extend the db-backed deletion guard to assert the four names stay gone. fix(parser): honor sqlite fanout watch roots SQLite fanout providers can publish a filesystem watch root that differs from the configured source root when FindDB resolves a canonical database under a subdirectory. Changed-path classification still compared WatchRoot to the configured root, so real DB/WAL/SHM events from the planned watch root produced no sources.\n\nAccept the emitted canonical DB directory as the matching watch root while keeping the configured-root compatibility path, and cover the FindDB subdirectory case with a WatchPlan-driven WAL event regression. The commit also removes two unused Codex fixture restats that blocked the existing staticcheck hook.\n\nValidation: go test -tags "fts5" ./internal/parser -run 'TestSQLiteFanoutSourceSet|TestDBBacked' -count=1 -v; go test -tags "fts5" ./internal/parser -count=1; go test -tags "fts5" ./internal/sync -run 'Test.*Codex' -count=1; go fmt ./...; go vet ./... style(docs): mdformat provider facade design spec * fix(sync): count Kiro SQLite fan-out rows in TotalSessions A Kiro SQLite store is discovered as one container source but fans out into one session per row, so the file tally counted it once. Add the extra sessions it produced to keep TotalSessions a session count, matching the per-session tally the legacy syncKiroSQLite phase reported. * test(parser): close kiro/shelley setup handles before deletion TestKiroProviderParsePhysicalVirtualAndLegacySources and the shelley ClassifiesDeleted tests removed the SQLite db file while their setup handle (from newKiroProviderSQLiteDBAt / newShelleyTestDB) was still open. Windows refuses to unlink a file this process holds open, so the removals failed there. Close the handle first, matching the existing kiro tombstone test that already does this. No behavior change on Unix. * fix(sync): preserve archived sessions when a backing DB file is deleted DB-backed providers (Shelley, Zed, Kiro, Hermes, Forge) returned a force-replacing SkipNoSession outcome whenever a session produced no results, including when the entire backing SQLite file was removed from disk. The engine expands a ForceReplace+ResultSetComplete skip into every stored session ID for the source and deletes them, so removing a DB file permanently deleted its archived sessions, violating the archive preservation rule that session data must survive even when source files no longer exist on disk. Distinguish a vanished source file from an in-DB removal: when the DB file is gone (os.IsNotExist, or the container is no longer a regular file) skip without ForceReplace so the engine keeps the stored sessions; keep ForceReplace for the sql.ErrNoRows and empty-results cases where the DB is still present and a row was genuinely deleted. The Deleted*PhysicalDB tests now assert preservation rather than removal. * fix(sync): skip unchanged Kiro SQLite rows on full sync dropUnchangedSharedSQLiteResults filtered unchanged container rows only for Shelley, Aider, and Zed; Kiro fell through the default and returned all rows. Kiro discovers data.sqlite3 as one container source fanned out to a session per row, so every unchanged row was reparsed, rewritten, and recounted on each full sync. The legacy processKiro container loop skipped unchanged rows on mtime plus data version. Add Kiro to the mtime-only branch (it has no per-row content hash) so unchanged rows are dropped from the write batch. * fix(parser): force-replace rows deleted from present container DBs Kiro, Zed, and Shelley classified a database change event to only the whole-container source, which fans out the surviving members on parse. A member row deleted from a still-present container was therefore never excluded: the surviving members were rewritten but the removed row lingered in the archive, diverging from the db-backed providers that drop it. (A vanished container file is still preserved per the persistent-archive rule; this only affects a row removed while the container remains.) Consume ChangedPathRequest.StoredSourcePaths to emit a per-member tombstone for each stored member that belongs to the changed container, is no longer present, and whose container file still exists. Each tombstone parses to a force-replacing skip that drops exactly that session, mirroring the tested db-backed behavior. Shelley additionally returned an error when fingerprinting a missing member, which aborted before Parse could run; it now returns a keyed-empty fingerprint without error like the db-backed and Kiro providers. * fix(parser): return keyed-empty Zed fingerprint for deleted thread When a thread is deleted from a still-present threads.db, zedFingerprintSource fell back to the physical DB size/mtime/hash instead of a keyed-empty fingerprint. Shelley and Kiro already return keyed-empty in this case so the engine reaches Parse and force-replaces the stale row out of the archive. Because the engine fingerprints before parsing, a DB-level fingerprint for a deleted member can let the pre-parse freshness check skip Parse whenever the stored metadata happens to match, stranding the deleted thread. Distinguish sql.ErrNoRows (the row is gone) from transient errors: on a missing row return a keyed-empty fingerprint so the tombstone flows to Parse; on any other error keep the physical DB mtime fallback, preserving prior behavior. The deleted-thread tombstone test now fingerprints the source and asserts it carries no DB size/mtime/hash.
…anup (kenn-io#885) * fix(parser): require explicit provider factories Make the provider registry force every agent onto an explicit facade path instead of silently inheriting a legacy fallback factory. Remove the legacy provider fallback entirely so an unhandled AgentDef is a loud construction error, and represent the non-filesystem export parsers (Claude.ai, ChatGPT) with explicit import-only providers. Mark the concrete providers authoritative in the migration manifest and drop the legacy-only marker. Route FindSourceFile and SourceMtime through provider FindSource and Fingerprint so the stack tip exercises provider-owned source identities and composite mtimes rather than parallel legacy dispatch. Retire the test scaffolding that depended on the removed legacy types: per-provider tests assert concrete construction, the obsolete legacy-fallback and legacy-only-mode registry tests are dropped, and the zero-legacy anti-shim gate runs with an empty pending list. With codex's legacy ShallowWatchRootsFunc removed in favor of the provider WatchPlan, fix collectProviderWatchRoots so a WatchPlan root that does not exist yet but lives under an already-watched ancestor no longer marks the whole directory for unwatched polling. A not-yet-created per-session recursive root otherwise regressed parity by polling two codex dirs that share a watched state-directory root; the ancestor watch observes the target's creation and a later sync establishes the deeper watch. refactor(parser): fold export parsers onto the import-only providers ChatGPT and Claude.ai sessions never come from disk discovery; they enter the archive only through a one-shot import. Move the ParseChatGPTExport and ParseClaudeAIExport free functions onto the import-only provider as the ChatGPTExportParser and ClaudeAIExportParser methods, and route the importer and tests through NewProvider plus a type assertion. This removes the last provider-specific legacy parse free functions, so the parser package now owns every agent's parse behavior on provider receivers rather than on standalone entrypoints. refactor(sync): remove the dead provider shadow-compare harness No provider runs in shadow-compare mode: every agent is now either provider-authoritative or import-only. The shadow harness that dual-ran a side-effect-free provider parse against the legacy result and recorded the diff was therefore never invoked at runtime. Delete the harness end to end: the ObserveProviderSource entry point and its comparison machinery, the Engine.observeProviderShadow hook and its two call sites, the ProviderShadowRecorder config/field wiring, and the ProviderMigrationShadowCompare mode (collapsing every switch that paired it with provider-authoritative). The provider outcome validation and effect planning helpers that the live parse path still relies on move to provider_effects.go, which is all that file ever held that was reachable. test(parser): drop the facade-migration anti-shim scaffolding With every provider folded onto receiver methods and zero provider-specific legacy parse free functions left, the migration's enforcement tests have served their purpose. They assert the absence of named functions and that provider files do not shim legacy entrypoints, which is only meaningful while the stack is mid-migration; after merge they are pure maintenance drag that breaks whenever a symbol is legitimately renamed. Delete the per-provider Test*ProviderOwnsLegacyEntrypoints guards and the shared anti-shim scan (provider_shim_scan_test.go). The providers' behavioral tests remain and are what actually protect the parse paths going forward. feat(parser): migrate aider, omp, reasonix to providers origin/main carries three agents the facade stack never migrated: Aider, OhMyPi, and Reasonix. After rebasing onto it, the explicit provider registry panicked on startup because those agents had no concrete factory, and the migration manifest still listed them as legacy-only against a manifest that no longer defines that mode. Give each a concrete provider so the zero-legacy registry stays intact: - OMP shares Pi's JSONL session format, so the Pi provider is parameterized by AgentDef (type and ID prefix) and serves both Pi and OhMyPi; ParseOMPSession is folded away. - Reasonix gets a single-file provider whose composite fingerprint folds the .jsonl.meta sidecar (mirroring reasonixEffectiveInfo) and whose changed-path classifier reproduces the project/global/archive/subagent layouts and the sidecar-to-transcript mapping. - Aider gets a multi-session provider that fans one history file out into one session per run under "<history>#<idx>" virtual paths and force-replaces on parse, mirroring the Shelley shape. Per-run skip is handled by dropUnchangedSharedSQLiteResults (content-hash compare); remote-sync identity stability is preserved by threading the path rewriter through ProviderConfig so per-run IDs stay stable across temp extraction dirs. The three manifest entries flip to provider-authoritative and the legacy engine methods (processAider, processReasonix, aiderFileUnchanged, aiderIdentityPath, classifyAiderPath) plus the now-dead legacy processFile fall-through are removed. The two codex append regression tests that were re-pointed onto processFile no longer consume their re-stat result; drop the unused assignment to satisfy staticcheck. test(sync): restore provider runtime regressions The shadow-compare harness removal also deleted coverage for live provider-authoritative runtime behavior. Restore those checks against the final processFile and SyncSingleSession paths with a small fake provider so future migrations cannot drop source lookup, retry data-version, skip-cache, skip-reason, not-found, or force-parse behavior silently.\n\nAlso assert the checked-in migration manifest only contains final provider modes and remove stale shadow-compare wording from live sync comments.\n\nValidation: go test -tags "fts5" ./internal/sync -run 'TestProcessFileProvider|TestSyncSingleSessionProviderAuthoritativeBypassesProviderSkipCache' -count=1; go test -tags "fts5" ./internal/parser -run 'TestProviderMigrationModes' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check fix(sync): make provider source lookup authoritative Stored file_path values can be stale after provider migration, especially for virtual DB-backed sources and remote-canonical Aider histories. Treat them as lookup hints instead of source-of-truth paths so provider-owned identity and freshness decide what can be resynced. Aider now resolves remote canonical physical and virtual hints using the same path-rewriter identity model as parse, Hermes verifies state.db contains the requested raw ID before claiming it, and DB-backed providers fall through from stale hints to raw-ID lookup while fresh deleted rows remain not found. Validation: go test -tags "fts5" ./internal/parser ./internal/sync -run 'TestDBBackedProviderStoredVirtualPathFreshness|TestDBBackedProviderRejectsInvalidStoredVirtualPaths|TestFindSourceFileProviderAuthoritativePrefersProviderOverStoredPath|TestSyncForgeMissingConversation' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check fix(watch): preserve provider watch root semantics Provider-authoritative agents must drive live watcher setup from their WatchPlan, otherwise legacy flags such as Cowork's ShallowWatch can silently narrow recursive coverage. Keep the provider root shape when collecting watcher roots so recursive provider roots stay recursive. Missing provider roots are only treated as covered when an existing recursive watch covers the subtree or a shallow root can observe direct creation of that missing root. This preserves Codex's shallow parent behavior without letting shallow ancestors hide deeper missing recursive roots from polling. Validation: go test -tags "fts5" ./cmd/agentsview -run 'TestCollectWatchRoots(UsesCoworkProviderRecursiveRoot|UsesGeminiProviderMetadataRoot|UsesAntigravityCLIHistoryRoot|PreservesDirsSharingWatchRoot|HermesSessionsWatchesStateDBParent)|TestMissingWatchRootCoverageDoesNotTreatShallowAncestorAsRecursive' -count=1; go test -tags "fts5" ./cmd/agentsview ./internal/parser -run 'TestCollectWatchRoots|TestMissingWatchRootCoverage|TestCoworkProviderSourceMethods|TestGeminiProvider|TestAntigravityCLI|TestSQLiteFanoutSourceSetUsesFindDBPathAsCanonical' -count=1; go test -tags "fts5" ./cmd/agentsview ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check test(provider): cover non-sync provider callers Provider migration removed legacy discovery and source lookup hooks, so non-sync callers need explicit coverage that they continue to use provider capabilities. Add contracts for parse-diff supported-agent resolution, token-use raw disk probing, and SSH remote directory resolution across the provider-authoritative agents called out by review. The Cursor token-use case uses a real provider-owned source layout to prove an unsynced raw or canonical session ID resolves through provider FindSource. Comments now describe the shared disk source lookup instead of implying FindSourceFunc is still the only path. Validation: go test -tags "fts5" ./cmd/agentsview ./internal/sync ./internal/ssh -run 'TestParseDiffSupportedAgentsIncludesProviderAuthoritativeAgents|TestParseDiffProviderAuthoritativeAgentsAreDiscoverable|TestParseDiffDiscoversProviderSources|TestResolveSessionID_ProviderAuthoritativeCursorOnDiskNotInDB|TestAgentHasDiskSourceLookupIncludesProviderAuthoritativeAgents|TestBuildResolveScript' -count=1; go test -tags "fts5" ./cmd/agentsview ./internal/sync ./internal/ssh -count=1; go fmt ./...; go vet ./...; git diff --check docs(provider): spell out source identity contract Provider implementers need the source-hint and freshness rules at the API boundary, not only embedded in migration review context. Document that stored file paths and fingerprint keys are advisory, provider lookup is authoritative, and persisted source identity must stay compatible with source metadata, skip-cache, data-version, PostgreSQL, and session metadata consumers. Also update the facade design note so the current stack tip no longer claims shadow-compare mode still exists. Validation: go test -tags "fts5" ./internal/parser -run 'TestProvider|TestProviderMigrationModes' -count=1; go fmt ./...; go vet ./...; git diff --check; mdformat applied by commit hook test(provider): enforce anti-shim ownership policy The migration should not rely on per-agent AgentDef hooks or exported provider-specific facade functions once a provider is authoritative. Add one maintained package-wide guard for that policy and remove the obsolete Kiro-only scan. Aider and Reasonix were still wired through legacy DiscoverFunc/FindSourceFunc despite having provider-authoritative implementations. Remove those hook assignments and make their provider-specific parser/discovery/lookup helpers package-local so runtime callers go through the provider registry. Validation: go test -tags "fts5" ./internal/parser -run 'TestProviderAuthoritativeAgentDefsDoNotExposeLegacyHooks|TestNoExportedProviderFacadeShims|TestNoProviderFacadeShimNamePolicyDocumentsAllowedHelpers|TestProviderAntiShimScanReadsExpectedPackage|TestAider|TestReasonix|TestProviderMigrationModes' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -run 'Aider|Reasonix|TestProviderAuthoritativeAgentDefsDoNotExposeLegacyHooks|TestNoExportedProviderFacadeShims|TestProviderMigrationModes' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check; mdformat applied by commit hook fix(parser): make import export capabilities agent-specific ChatGPT and Claude.ai imports should advertise only the export parser surface that belongs to their agent. The shared import-only provider type made both interface assertions succeed, which hid capability loss during migration review and let callers treat either provider as supporting the other export format. Validation: go test -tags "fts5" ./internal/parser -run 'TestImportOnlyProviderExportCapabilitiesAreAgentSpecific|TestParseChatGPTExport|TestParseClaudeAIExport' -count=1; go test -tags "fts5" ./internal/parser -count=1; go fmt ./...; go vet ./...; git diff --check fix(parser): hash reasonix metadata sidecars Reasonix metadata-only edits can change session fields without touching the transcript bytes. Provider fingerprinting already folded sidecar size and mtime, but the hash stayed transcript-only, so stored freshness state could miss metadata-only changes under hash-based comparisons. Keep missing-sidecar hashes compatible with the existing transcript-only value, and add provider coverage for layout classification plus deleted sidecar/transcript behavior so the migration contract is explicit. Validation: go test -tags "fts5" ./internal/parser -run 'TestReasonixProvider(Fingerprint|ChangedPath)' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -run 'Reasonix' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check fix(sync): keep aider off mtime-only skip cache Aider histories fan out one physical file into multiple virtual run rows. Letting the generic skip cache key the physical history by mtime alone can bypass the provider fingerprint and per-run DB checks, hiding same-mtime content changes, missing run rows, stale hashes, or stale data versions. Disable generic skip caching for Aider and rely on the provider parse plus dropUnchangedSharedSQLiteResults hash/data-version filtering. This preserves correctness at the cost of reparsing the shared history before dropping unchanged runs. Validation: go test -tags "fts5" ./internal/sync -run 'TestProcessFileAiderProvider' -count=1; go test -tags "fts5" ./internal/parser -run 'TestAiderProviderFindSourceUsesCanonicalIdentity|TestAiderProviderRemoteIdentityStable' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -run 'Aider' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check refactor(parser): extract multi-session container base Shelley and Aider both surface one physical container (a SQLite DB / a chat history file) as many virtual member sessions, and each hand-rolled the same source-set scaffolding: discovery, watch plan, changed-path classification, the StoredFilePath/FingerprintKey/RawSessionID FindSource tiers, fingerprinting, and the container/member parse fan-out. Introduce multiSessionContainerSourceSet, a reusable source set, provider, and factory configured entirely through functional options (with*()), so a new special case is a new option rather than a wider signature or a new interface method. Aider's remote-sync identity (PathRewriter) and its canonical-path stored fallback, and Shelley's member-presence check, are all expressed as options. Fold shelley_provider.go and aider_provider.go onto the base. Per-provider code drops ~45% each (shelley 532->289, aider 501->274); the 469-line base is a one-time cost the remaining container-family providers (zed, kiro, opencode, db-backed, copilot) can reuse. refactor(parser): add generic SourceSet provider plumbing The multi-session container base shipped with its own ProviderFactory and a delegating Provider that forwarded six methods to the source set. Every future reusable base (single-file sidecar, and the rest) would re-hand-roll that same factory + forwarding shell. Extract it once: a SourceSet interface (the Provider source methods plus Parse, minus the Definition/Capabilities/config plumbing), a sourceSetProvider wrapper that supplies ProviderBase and applies the two normalizations every provider shares (raw-session-ID injection on FindSource, the request/config machine fallback on Parse), and a generic sourceSetFactory built from def + caps + a per-config SourceSet constructor. multiSessionContainerSourceSet now implements SourceSet directly (gaining a Parse method); newMultiSessionProviderFactory becomes a thin adapter over newSourceSetFactory. ParseIncremental stays on the ProviderBase unsupported default until a base needs it. refactor(parser): add single-file source-set base, fold reasonix Add singleFileSourceSet, the second reusable SourceSet base, for providers whose physical source is one file that parses into exactly one session (no virtual member paths, no fan-out). Like multiSessionContainerSourceSet it is configured through functional options (withFile*()) and plugs into newSourceSetFactory. The sidecar/composite fingerprint variance across this family stays inside each provider's withFileFingerprint closure, so the base carries no sidecar knowledge until a shared helper is warranted. Fold reasonix onto it as the validation provider: discovery, the .jsonl.meta sidecar changed-path mapping, the WatchSubdirs-aware changed-path resolution, the composite fingerprint, and the project-hint + fingerprint stamping in parse all become option closures. The provider file drops from 457 to ~280 lines; behavior is preserved (full parser and sync suites green). refactor(parser): fold zed onto multi-session container base Zed is a SQLite-DB-per-root thread container like Shelley. Replace its hand-rolled factory/provider/source set with multiSessionContainerSourceSet option closures, preserving the zed specifics: the container path is root/threads/threads.db, the member fingerprint mtime comes from ZedSQLiteSourceMtime, and the container fan-out stamps the DB's own content hash (computed in the parseContainer closure, not the request fingerprint, so missing-fingerprint parses still hash the rows). sqliteDBCompositeMtime stays put; it is shared with shelley and kiro. refactor(parser): fold visual studio copilot onto container base Visual Studio Copilot surfaces conversations from shared trace files, but unlike the SQLite containers it discovers one source per conversation (deduped across traces, newest wins) plus a bare source per unreadable trace. Two base generalizations support that: withSourceDiscovery lets a provider emit member-level matches at discovery time, and multiSessionMatch now carries a ProjectHint surfaced on the SourceRef. The multi-session parse closures now receive the full ParseRequest instead of just the machine string, mirroring the single-file base. This lets vs_copilot honor req.Source.ProjectHint, which the engine sets to the DB-preserved project on single-session re-sync so a user's project override is not reverted. shelley, zed, and aider closures are updated to the new signature (they read req.Machine). parseConversation becomes a free function and the test helpers call it directly. vs_copilot keeps its virtual-paths-always-strict changed-path classification and stamps the shared trace hash on every fanned-out conversation. Provider file drops 454->250; full parser and sync suites green. refactor(parser): fold cowork onto single-file base Cowork sources are single Claude-format transcripts, but one transcript can yield several sessions (main plus subagents) and a parse drives removals via excluded session IDs. Generalize singleFileSourceSet's parse contract to return ([]ParseResult, []string excluded) instead of one *ParseResult, and add withAlwaysCompleteResultSet so a parse that only excludes sessions still reports a complete (not skipped) result set. SourcesForChangedPath now derives allowMissing from jsonlMissingPathFallbackAllowed(req) rather than hardcoding true, which cowork (and vibe) require and reasonix is indifferent to. reasonix's parse closure is updated to the new slice signature. Cowork's hand-rolled factory/provider/source set become option closures; parseSession becomes the free function parseCoworkSession; the metadata-to-transcript changed-path mapping, composite mtime, and project-hint-from-metadata are all preserved. Provider file drops 523->352; full parser and sync suites green. refactor(parser): fold vibe onto single-file base Vibe sessions are single messages.jsonl transcripts with a sibling meta.json. Fold onto singleFileSourceSet: discovery, the messages.jsonl/meta.json changed-path mapping (strict vs session-dir-name fallback under allowMissing), the composite fingerprint, and the fallback-ID exclusion become option closures. The single result plus exclusions and the skip-on-no-session behavior ride the base's multi-result parse contract without withAlwaysCompleteResultSet, since vibe still skips when no session is parsed. parseSession and parseVibeResult become the free functions parseVibeSession and parseVibeResultFile; the test helpers call them and the provider's Discover directly instead of a concrete *vibeProvider. Provider file drops 507->300; full parser and sync suites green. refactor(parser): make JSONLSourceSet a SourceSet, fold amp Add a ParseFile option to JSONLSourceSetOptions and a Parse method to JSONLSourceSet, so the directory-of-files source set (and DirectoryJSONLSourceSet) implements the full SourceSet interface and rides the generic sourceSetFactory. Parse mirrors the single-file base: empty results with no exclusions is a clean no-session skip; req.Machine is resolved by sourceSetProvider. Amp is folded as the template: its hand-rolled factory, provider, five forwarding methods, and Parse collapse to newSourceSetFactory plus an ampParseFile closure; parseSession becomes the free function parseAmpSession. 139->63 lines. refactor(parser): fold qwen provider onto SourceSet factory Replace the hand-rolled qwenProvider struct, factory struct, var _ Provider assertion, forwarding methods, and Parse with the generic newSourceSetFactory plus a ParseFile option on the JSONL source set. The ParseFile closure passes req.Source.ProjectHint as the project hint. Convert parseSession from a *qwenProvider method to the free function parseQwenSession and update the test helper accordingly. refactor(parser): fold workbuddy onto SourceSet convergence Replace the hand-rolled workBuddyProvider struct, factory, and forwarding methods with newSourceSetFactory plus a ParseFile option, matching the amp provider. Convert parseSession from a method to the free function parseWorkBuddySession. Add an optional LookupIDValid predicate to JSONLSourceSetOptions so the generic FindSource fallback accepts WorkBuddy composite subagent IDs (<id>:subagent:<id>), which IsValidSessionID rejects. The option defaults to IsValidSessionID, preserving behavior for all other source-set providers. refactor(parser): fold deepseek_tui onto SourceSet convergence refactor(parser): fold zencoder onto SourceSet convergence refactor(parser): thread context through JSONLSourceSet ParseFile ParseFile now receives the parse context so directory-of-files folds can do context-aware work such as git-root project resolution; the existing single-result closures ignore it. Also adds a RawSessionIDForLookup hook that normalizes a stored raw session ID before the FindSource discovery comparison, for providers whose stored IDs carry a suffix the discovered filename stem lacks. refactor(parser): fold iflow onto SourceSet convergence iFlow now rides the generic source-set factory via DirectoryJSONLSourceSet's ParseFile. Its multi-result parse, relationship inference, and git-aware project resolution move into the ParseFile closure using the threaded context, and the subagent-base-ID normalization its custom FindSource performed is carried by the RawSessionIDForLookup hook. refactor(parser): fold kimi onto SourceSet convergence Kimi rides the generic source-set factory via ParseFile. Its colon-joined raw session IDs cannot be matched by the filename-stem discovery scan, so the wire.jsonl path reconstruction (including the agents/ subagent layout) moves to a new RawSessionIDSourceFiles hook that FindSource consults before the scan. refactor(parser): fold kiro_ide onto SourceSet convergence Kiro IDE rides the generic source-set factory via ParseFile. Its two on-disk layouts (old <ws-hash>:<file-hash> .chat IDs and new UUID .json files under workspace-sessions/) are resolved by reconstructing candidate paths in the RawSessionIDSourceFiles hook, since the colon-joined old IDs cannot be matched by the filename-stem discovery scan. refactor(parser): fold qwenpaw onto SourceSet convergence QwenPaw rides the generic source-set factory via ParseFile. Its colon-joined raw IDs are reconstructed through RawSessionIDSourceFiles; a DB-recorded file_path outside the configured roots is honored via the new StoredPathFallbackRoot hook, which synthesizes the implicit <root>/<workspace>/sessions/ layout; and the wholesale-rewrite outcome is carried by the ForceReplace option. refactor(parser): convert JSONLSourceSet to functional options JSONLSourceSet and DirectoryJSONLSourceSet are now built with with*() option closures plus default bundles (withContentHashing, withSymlinkFollowing) instead of a struct literal, matching the multiSessionContainer and singleFile bases. Each source only states what differs from the zero-value defaults. Constructors are lowercased (newJSONLSourceSet / newDirectoryJSONLSourceSet) as they are package-internal. Behavior is unchanged; SiblingMetadataSourceSet keeps its struct-based constructor via the shared jsonlSourceSetFromOptions. fix(parser): make aider discovery opt-in Aider had no central session store, so the registry gave it DefaultDirs [""], which resolves to $HOME and drove an always-on bounded walk. For a passive viewer that is a poor default: background refreshes (usage reports, desktop launches) enumerate $HOME and trigger macOS privacy prompts for Documents/Downloads/Music/Photos. Drop the default so aider is discovered only when the user opts in via AIDER_DIR or aider_dirs; a configured broad root still gets the bounded, protected-folder-pruned walk. This aligns the provider-migration branch with the opt-in behavior shipped on main in kenn-io#844, which this branch predates. fix(ssh): resolve remote aider history without DefaultDirs Making aider discovery opt-in emptied its DefaultDirs, but the SSH resolve script only emitted the AIDER_DIR history-file snippet while iterating that list, so an explicitly configured remote AIDER_DIR stopped resolving any .aider.chat.history.md files for transfer. Handle aider independently of DefaultDirs, emitting buildAiderResolveSnippet whenever its EnvVar is set while still avoiding any default $HOME scan. Also cover JSONLSourceSet.FindSource RawSessionIDForLookup normalization, which runs before both the LookupIDValid gate and the SessionIDFromPath discovery comparison. * refactor(parser): remove dead legacy DiscoverFunc/FindSourceFunc hooks Every agent is now provider-authoritative, so no AgentDef sets DiscoverFunc or FindSourceFunc. The fields, the always-false branches that consulted them (full-sync discovery, parse-diff discovery and discoverability, SSH resolve, source lookup, token-use disk probing), and the now-empty parseDiffDatabaseSources were left behind by the staged migration. Provider discovery (discoverProviderSources / parseDiffProviderSources) and provider lookup (findProviderSourceFile) already own every one of these paths. Also drop the migration-era scaffolding tests that only asserted this legacy code no longer exists: the provider_anti_shim_test.go suite (legacy-hook nil checks plus the AST scan for exported Discover/Find/Parse/Process facade functions) and the per-agent require.Nil(def.DiscoverFunc) registry assertions. With the fields gone, their absence is enforced by the compiler. * test(provider): harden provider lookup and source-pinning coverage Guard provider.findRequests with require.Len before indexing so a missing request fails as a clear length assertion rather than a panic. Drive the parse-diff provider-authoritative contract from the registry so it covers every current file-based provider-authoritative agent instead of a hand-maintained subset. Add a Codex regression test pinning PreferStoredSource to the stored archived duplicate, contrasted with the canonicalize-to-live behavior it opts out of. * fix(parser): align visual studio copilot source selection and refresh single-file doc Single-session lookup picked the lexicographically last trace file while discovery picked the newest by mtime, so a conversation could resolve to a different virtual path on resync when filename and mtime order disagreed. Extract a shared visualStudioCopilotCandidateWins selector and use it from both paths, and add a test where mtime and path order diverge. Also correct the single-file source-set doc comment, which still claimed exactly one session per file even though Cowork fans one file into many. * docs(provider): mark shadow-compare migration sequence as historical The facade-design spec's top note says shadow-compare was removed, but the Migration Plan section still prescribed opting providers into shadow-compare, which reads as current guidance. Add a historical-scope banner so future migration authors do not try to use the retired mode. * revert(sync): restore deliberate Kiro TotalSessions parity guard An unrelated edit to the Kiro session-count guard was swept into the prior commit. It flipped the len(r.results) > 1 guard to != 1 with a comment claiming it corrects the zero-session case, but the empty-result branch returns earlier so the change was inert and the comment misleading. Restore the > 1 guard: a zero-session container stays counted as one discovered source, matching the legacy syncKiroSQLite tally and how every other zero-session file is counted. * style(parser): gofmt antigravity CLI version test call sites The antigravity provider-fold renamed these helpers to the test-local parseAntigravityCLITestSession wrapper, and the mechanical replacement left the t-argument without a separating space. Restore canonical gofmt spacing so the source set stays format-clean. * refactor(parser): remove the folded DiscoverCodexSessions seam DiscoverCodexSessions was retained only as the s3:// Codex discovery entry that the legacy S3 sync path consumed until S3 support folded into the source sets. With the Codex source set now enumerating s3:// roots through discoverCodexS3 directly and local layouts through discoverSessionPaths, the function has no remaining callers in production or tests, and its local branch only duplicated discoverSessionPaths. Remove it so there is one Codex discovery path. * refactor(parser): extract a general s3PrefixScan for the /<machine>/raw/<provider> layout discoverClaudeS3 and discoverCodexS3 were the same scan with three small per-agent differences: which objects to keep, how to derive the project, and whether to fold sidecar metadata. They shared the rest -- list every object under the prefix, derive the source machine from the .../<machine>/raw/<provider> layout, and emit a DiscoveredFile carrying the object's size/mtime/fingerprint. Lift that skeleton into one s3PrefixScan parameterized by an s3SessionScanner (Keep/Project/Sidecars predicates). The two discover functions become thin configurations of it, and any JSONL provider whose sessions land under the same layout can reuse the scan by supplying its own predicates rather than copying the listing/metadata-folding body. Generalize s3MachineFromRoot to match raw/<provider> for the agent being scanned instead of a hardcoded claude/codex check, so the machine derivation works for any provider segment. Behavior for Claude and Codex is unchanged. * refactor(sync): route S3 sessions through provider.Parse processS3Session previously branched on file.Agent to call the per-agent processClaudeWithStoredSkip/processCodex methods on the materialized temp file. A fetched S3 object is just a Reader buffered to disk, so it can take the same parse path a local source of that agent would, instead of an S3-specific switch. parseMaterializedS3Source configures a provider rooted at the temp dir and parses via a SourceRef carrying the new MaterializedFileSource opaque, which the claude/codex source sets resolve straight to the temp path rather than re-deriving identity from a configured-root layout (the prefix-anchored temp path need not match the on-disk convention, and the project comes from ProjectHint). The remaining S3-specific handling -- machine-ID namespacing, recording the s3:// URI as the stored source, the pre-fetch metadata skip, and forced replacement on source change -- stays in processS3Session. With nothing left routing through them, delete the per-agent S3 parse methods (processClaudeWithStoredSkip, processCodex, and the processCodex-only shouldSkipCodex) and the thin exported parser seams they reached across packages (ParseClaudeSessionWithExclusions, ParseClaudeSessionFrom, ParseCodexSession, ParseCodexSessionFrom); the providers already call the unexported parse cores directly. The title-rename masking coverage that exercised shouldSkipCodex now drives the live codexIndexSessionNameChanged instead. * fix(sync): correct S3 provider exclusions and incremental mtime cutoff Two regressions from routing S3 sessions through the provider facade: parseMaterializedS3Source short-circuited on an empty Results slice and dropped the parse outcome's ExcludedSessionIDs and ForceReplace. A content-free source still carries exclusions -- a Claude /usage probe parses to no live session but excludes its ID -- and the S3 caller needs that ID to remove the previously-archived row on resync. Always build the processResult so exclusions survive a zero-result parse. discoveredFileEffectiveMtime routed provider-discovered s3:// objects into providerSourceMtime, but providers read local files and cannot Fingerprint an s3:// URI, so the call errored and filterFilesByMtime keeps any file whose mtime cannot be resolved -- silently defeating the incremental cutoff so every old S3 object was reprocessed on each sync. Short-circuit s3:// paths to the threaded object metadata before the provider Fingerprint, the same way Codex already bypasses it. Both paths gain a regression test that fails before the fix. * docs(parser): reconcile the SourceRef.Opaque contract with S3 threading The Opaque doc stated the engine must never inspect the payload, but the S3 work added two engine-recognized payloads that contradict that: the engine type-asserts S3DiscoveredSource to thread object metadata from discovery into the DiscoveredFile, and constructs MaterializedFileSource so a provider parses a fetched S3 object from a temp file. Document these as the defined exception -- in-memory only, never persisted, never a lookup requirement -- so a future reader does not "fix" the contract by removing the payloads and silently breaking S3 freshness or parsing. Also drop the now-false claim on S3DiscoveredSource that an S3 source is never parsed through the provider path; it is, via provider.Parse. * refactor(parser): remove the dead shadow-compare migration mode ProviderMigrationShadowCompare was still accepted by ValidateProviderMigrationModes, but every runtime consumer (provider discovery, changed-path classification, processProviderFile, source lookup, parse-diff filtering) acts only on provider-authoritative, and the provider_shadow runtime was deleted once the migration completed. A provider left in shadow-compare would therefore pass validation yet run neither shadow observation nor provider processing -- false confidence that parity is being checked. Drop the constant so the validation default rejects "shadow-compare", and correct the superseded dual-run-harness plan, which still recommended shadow-compare as a live transitional mode. * fix(sync): thread S3 discovery metadata in parse-diff provider sources parseDiffProviderSources built DiscoveredFile values without copying the parser.S3DiscoveredSource metadata that the main sync discovery path threads on (Machine, size, mtime, fingerprint, project). For s3:// sources this left SourceMtime at zero, so parse-diff ordering fell back to a per-file network stat and, if that stat failed, treated the session as oldest -- skewing --limit selection and possibly omitting recent S3 sessions. Copy the S3 metadata onto the file exactly as the engine discovery path does. * fix(parser): re-resolve stale aider run paths on fresh lookups Aider run sources are positional virtual paths (<history>#<idx>), but the per-run session ID is content-derived and stable. A single-session resync (RequireFreshSource) trusted the stored positional path directly, so when an earlier run was inserted or removed the index pointed at a different run and the resync parsed and force-replaced the wrong session while the requested one went unsynced. memberPresent could not catch this because it never received the requested raw ID. Add an optional freshStoredMember hook to the multi-session container base that validates a stored member against the requested raw session ID under RequireFreshSource; Aider supplies it by recomputing the run hash at the stored index and falling through to raw-ID re-resolution on any mismatch. Providers with stable member IDs are unaffected. * refactor(parser): drop IcodeMate legacy discovery hooks IcodeMate is provider-authoritative through the shared OpenCode-format provider, so its registry entry no longer needs the DiscoverFunc and FindSourceFunc hooks that this branch removes from AgentDef.
> ℹ️ **Note**
>
> This PR body was truncated due to platform limits.
This PR contains the following updates:
| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@inlang/paraglide-js](https://paraglidejs.com) ([source](https://redirect.github.com/opral/paraglide-js)) | [`2.19.0` → `2.20.1`](https://renovatebot.com/diffs/npm/@inlang%2fparaglide-js/2.19.0/2.20.1) |  |  |
| [@lucide/svelte](https://lucide.dev) ([source](https://redirect.github.com/lucide-icons/lucide/tree/HEAD/packages/svelte)) | [`1.17.0` → `1.21.0`](https://renovatebot.com/diffs/npm/@lucide%2fsvelte/1.17.0/1.21.0) |  |  |
| [@playwright/test](https://playwright.dev) ([source](https://redirect.github.com/microsoft/playwright)) | [`1.60.0` → `1.61.0`](https://renovatebot.com/diffs/npm/@playwright%2ftest/1.60.0/1.61.0) |  |  |
| [@playwright/test](https://playwright.dev) ([source](https://redirect.github.com/microsoft/playwright)) | [`1.55.1` → `1.61.0`](https://renovatebot.com/diffs/npm/@playwright%2ftest/1.55.1/1.61.0) |  |  |
| [@tanstack/virtual-core](https://tanstack.com/virtual) ([source](https://redirect.github.com/TanStack/virtual/tree/HEAD/packages/virtual-core)) | [`3.17.0` → `3.17.1`](https://renovatebot.com/diffs/npm/@tanstack%2fvirtual-core/3.17.0/3.17.1) |  |  |
| [@tauri-apps/cli](https://redirect.github.com/tauri-apps/tauri) | [`2.11.2` → `2.11.3`](https://renovatebot.com/diffs/npm/@tauri-apps%2fcli/2.11.2/2.11.3) |  |  |
| [@testing-library/svelte](https://redirect.github.com/testing-library/svelte-testing-library) ([source](https://redirect.github.com/testing-library/svelte-testing-library/tree/HEAD/packages/svelte)) | [`5.3.1` → `5.4.1`](https://renovatebot.com/diffs/npm/@testing-library%2fsvelte/5.3.1/5.4.1) |  |  |
| [openapi-typescript-codegen](https://redirect.github.com/ferdikoomen/openapi-typescript-codegen) | [`^0.30.0` → `^0.31.0`](https://renovatebot.com/diffs/npm/openapi-typescript-codegen/0.30.0/0.31.0) |  |  |
| [svelte](https://svelte.dev) ([source](https://redirect.github.com/sveltejs/svelte/tree/HEAD/packages/svelte)) | [`5.56.1` → `5.56.3`](https://renovatebot.com/diffs/npm/svelte/5.56.1/5.56.3) |  |  |
| [vite](https://viteplus.dev/guide) ([source](https://redirect.github.com/voidzero-dev/vite-plus/tree/HEAD/packages/core)) | [`0.1.24` → `0.2.1`](https://renovatebot.com/diffs/npm/vite/0.1.24/0.2.1) |  |  |
| [vite-plus](https://viteplus.dev/guide) ([source](https://redirect.github.com/voidzero-dev/vite-plus/tree/HEAD/packages/cli)) | [`0.1.24` → `0.2.1`](https://renovatebot.com/diffs/npm/vite-plus/0.1.24/0.2.1) |  |  |
---
### Release Notes
<details>
<summary>opral/paraglide-js (@​inlang/paraglide-js)</summary>
### [`v2.20.1`](https://redirect.github.com/opral/paraglide-js/blob/HEAD/CHANGELOG.md#2201)
##### Patch Changes
- [`8c3493d`](https://redirect.github.com/opral/paraglide-js/commit/8c3493d): Fix server cookie locale parsing when Cookie headers omit whitespace after semicolons.
### [`v2.20.0`](https://redirect.github.com/opral/paraglide-js/blob/HEAD/CHANGELOG.md#2200)
##### Minor Changes
- [`2c34351`](https://redirect.github.com/opral/paraglide-js/commit/2c34351): Emit `messages/package.json` with `{ "type": "module", "sideEffects": false }` for `message-modules` output, declaring the generated message modules side-effect-free.
This lets bundlers (notably Vite 8 / Rolldown) drop unused re-exports from the `m` barrel per entry, instead of bundling every message used anywhere in the app into one shared chunk that every entry downloads. Without it, per-page JS scales with the union of all messages used across the app rather than with the messages a given route actually uses.
The declaration is scoped to `messages/`, so `runtime.js` (which has real side effects) is unaffected. `type: "module"` is included because the package.json creates a new module scope for `messages/`; without it, the generated ESM files would default to CommonJS (a package.json without `type` is CJS in Node, even when the consuming project is `type: "module"`).
See [#​668](https://redirect.github.com/opral/paraglide-js/issues/668)
##### Patch Changes
- [`921c3be`](https://redirect.github.com/opral/paraglide-js/commit/921c3be): `experimentalMiddlewareLocaleSplitting`: the injected inline script now reuses the nonce from the response's `Content-Security-Policy` header, so it is allowed under a strict CSP instead of being blocked and breaking hydration. Automatic - no configuration needed.
</details>
<details>
<summary>lucide-icons/lucide (@​lucide/svelte)</summary>
### [`v1.21.0`](https://redirect.github.com/lucide-icons/lucide/releases/tag/1.21.0): Version 1.21.0
[Compare Source](https://redirect.github.com/lucide-icons/lucide/compare/1.20.0...1.21.0)
#### What's Changed
- ci(release.yml): Remove new-version in release flow by [@​ericfennis](https://redirect.github.com/ericfennis) in [#​4478](https://redirect.github.com/lucide-icons/lucide/pull/4478)
- ci(release.yml): Fix workflow and remove `version` scripts in package scripts by [@​ericfennis](https://redirect.github.com/ericfennis) in [#​4479](https://redirect.github.com/lucide-icons/lucide/pull/4479)
- fix(docs): rename navigation category label by [@​Hsiii](https://redirect.github.com/Hsiii) in [#​4483](https://redirect.github.com/lucide-icons/lucide/pull/4483)
- feat(icons): added `broken-bone` icon by [@​Patolord](https://redirect.github.com/Patolord) in [#​4131](https://redirect.github.com/lucide-icons/lucide/pull/4131)
#### New Contributors
- [@​Hsiii](https://redirect.github.com/Hsiii) made their first contribution in [#​4483](https://redirect.github.com/lucide-icons/lucide/pull/4483)
- [@​Patolord](https://redirect.github.com/Patolord) made their first contribution in [#​4131](https://redirect.github.com/lucide-icons/lucide/pull/4131)
**Full Changelog**: <https://github.com/lucide-icons/lucide/compare/1.20.0...1.21.0>
### [`v1.20.0`](https://redirect.github.com/lucide-icons/lucide/releases/tag/1.20.0): Version 1.20.0
[Compare Source](https://redirect.github.com/lucide-icons/lucide/compare/1.19.0...1.20.0)
#### What's Changed
- fix(icons): decreased size of arrows inside `square-arrow-*` icons by [@​jguddas](https://redirect.github.com/jguddas) in [#​3926](https://redirect.github.com/lucide-icons/lucide/pull/3926)
- chore(tags): Add tags to `search-` icons by [@​jamiemlaw](https://redirect.github.com/jamiemlaw) in [#​4099](https://redirect.github.com/lucide-icons/lucide/pull/4099)
- feat(icons): added `save-check` icon by [@​Konixy](https://redirect.github.com/Konixy) in [#​3120](https://redirect.github.com/lucide-icons/lucide/pull/3120)
- feat(icons): added `tag-plus` and `tag-x` icons by [@​adam-kov](https://redirect.github.com/adam-kov) in [#​3980](https://redirect.github.com/lucide-icons/lucide/pull/3980)
- feat(icons): added `banknote-check` icon by [@​mfjramirezf](https://redirect.github.com/mfjramirezf) in [#​3956](https://redirect.github.com/lucide-icons/lucide/pull/3956)
- feat(icons): added `clock-arrow-in` icon by [@​jguddas](https://redirect.github.com/jguddas) in [#​2403](https://redirect.github.com/lucide-icons/lucide/pull/2403)
- feat(icons): added `summary` icon by [@​jpjacobpadilla](https://redirect.github.com/jpjacobpadilla) in [#​3114](https://redirect.github.com/lucide-icons/lucide/pull/3114)
- feat(icons): added `user-round-arrow-in` icon by [@​jguddas](https://redirect.github.com/jguddas) in [#​2283](https://redirect.github.com/lucide-icons/lucide/pull/2283)
- feat(icons): added `clock-arrow-out` icon by [@​jguddas](https://redirect.github.com/jguddas) in [#​2404](https://redirect.github.com/lucide-icons/lucide/pull/2404)
- docs(docs): fix broken Svelte package source link in README by [@​SRKrukowski](https://redirect.github.com/SRKrukowski) in [#​4468](https://redirect.github.com/lucide-icons/lucide/pull/4468)
- chore(deps-dev): bump [@​angular/compiler](https://redirect.github.com/angular/compiler) from 21.2.5 to 21.2.17 by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​4474](https://redirect.github.com/lucide-icons/lucide/pull/4474)
- chore(deps-dev): bump [@​angular/core](https://redirect.github.com/angular/core) from 21.2.5 to 21.2.17 by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​4470](https://redirect.github.com/lucide-icons/lucide/pull/4470)
- chore(deps-dev): bump vitest from 4.0.12 to 4.1.0 by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​4429](https://redirect.github.com/lucide-icons/lucide/pull/4429)
- chore(deps-dev): bump markdown-it from 14.1.1 to 14.2.0 by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​4475](https://redirect.github.com/lucide-icons/lucide/pull/4475)
- chore(deps-dev): bump [@​angular/common](https://redirect.github.com/angular/common) from 21.2.5 to 21.2.17 by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​4471](https://redirect.github.com/lucide-icons/lucide/pull/4471)
- feat(icons): added `pencil-sparkles` icon by [@​jennieboops](https://redirect.github.com/jennieboops) in [#​4445](https://redirect.github.com/lucide-icons/lucide/pull/4445)
#### New Contributors
- [@​Konixy](https://redirect.github.com/Konixy) made their first contribution in [#​3120](https://redirect.github.com/lucide-icons/lucide/pull/3120)
- [@​adam-kov](https://redirect.github.com/adam-kov) made their first contribution in [#​3980](https://redirect.github.com/lucide-icons/lucide/pull/3980)
- [@​mfjramirezf](https://redirect.github.com/mfjramirezf) made their first contribution in [#​3956](https://redirect.github.com/lucide-icons/lucide/pull/3956)
- [@​SRKrukowski](https://redirect.github.com/SRKrukowski) made their first contribution in [#​4468](https://redirect.github.com/lucide-icons/lucide/pull/4468)
- [@​jennieboops](https://redirect.github.com/jennieboops) made their first contribution in [#​4445](https://redirect.github.com/lucide-icons/lucide/pull/4445)
**Full Changelog**: <https://github.com/lucide-icons/lucide/compare/1.19.0...1.20.0>
### [`v1.19.0`](https://redirect.github.com/lucide-icons/lucide/releases/tag/1.19.0): Version 1.19.0
[Compare Source](https://redirect.github.com/lucide-icons/lucide/compare/1.18.0...1.19.0)
#### What's Changed
- chore(deps): upgrade pnpm to version 11.6.0 by [@​ericfennis](https://redirect.github.com/ericfennis) in [#​4458](https://redirect.github.com/lucide-icons/lucide/pull/4458)
- feat(icons): added `star-*` icons by [@​RajnishKMehta](https://redirect.github.com/RajnishKMehta) in [#​3918](https://redirect.github.com/lucide-icons/lucide/pull/3918)
- chore(suggest-tags): Update metadata suggestion script by [@​ericfennis](https://redirect.github.com/ericfennis) in [#​4462](https://redirect.github.com/lucide-icons/lucide/pull/4462)
- feat(icons): added `save-pen` icon by [@​vaporvee](https://redirect.github.com/vaporvee) in [#​4179](https://redirect.github.com/lucide-icons/lucide/pull/4179)
- feat(icons): added `wrench-off` icon by [@​nilsjonsson](https://redirect.github.com/nilsjonsson) in [#​4434](https://redirect.github.com/lucide-icons/lucide/pull/4434)
- feat(icons): added `ad` icon by [@​jamiemlaw](https://redirect.github.com/jamiemlaw) in [#​4323](https://redirect.github.com/lucide-icons/lucide/pull/4323)
- feat(icons): added `eye-dashed` icon by [@​karsa-mistmere](https://redirect.github.com/karsa-mistmere) in [#​4415](https://redirect.github.com/lucide-icons/lucide/pull/4415)
- feat(icons): added `save-plus` icon by [@​jwlinqx](https://redirect.github.com/jwlinqx) in [#​4448](https://redirect.github.com/lucide-icons/lucide/pull/4448)
- feat(icons): added `list-sort-descending` icon by [@​ericfennis](https://redirect.github.com/ericfennis) in [#​4457](https://redirect.github.com/lucide-icons/lucide/pull/4457)
- fix(lucide-react-native): Fix provider exports by [@​ericfennis](https://redirect.github.com/ericfennis) in [#​4463](https://redirect.github.com/lucide-icons/lucide/pull/4463)
- fix(site): reserve space for icon detail drawer by [@​vyctorbrzezowski](https://redirect.github.com/vyctorbrzezowski) in [#​4344](https://redirect.github.com/lucide-icons/lucide/pull/4344)
- fix(icons): changed `wallet-cards` icon by [@​jguddas](https://redirect.github.com/jguddas) in [#​3888](https://redirect.github.com/lucide-icons/lucide/pull/3888)
- feat(site): Improve search and add sorting options by [@​ericfennis](https://redirect.github.com/ericfennis) in [#​4453](https://redirect.github.com/lucide-icons/lucide/pull/4453)
- feat(icons): added `podium` icon by [@​jguddas](https://redirect.github.com/jguddas) in [#​2124](https://redirect.github.com/lucide-icons/lucide/pull/2124)
#### New Contributors
- [@​vaporvee](https://redirect.github.com/vaporvee) made their first contribution in [#​4179](https://redirect.github.com/lucide-icons/lucide/pull/4179)
- [@​nilsjonsson](https://redirect.github.com/nilsjonsson) made their first contribution in [#​4434](https://redirect.github.com/lucide-icons/lucide/pull/4434)
- [@​jwlinqx](https://redirect.github.com/jwlinqx) made their first contribution in [#​4448](https://redirect.github.com/lucide-icons/lucide/pull/4448)
- [@​vyctorbrzezowski](https://redirect.github.com/vyctorbrzezowski) made their first contribution in [#​4344](https://redirect.github.com/lucide-icons/lucide/pull/4344)
**Full Changelog**: <https://github.com/lucide-icons/lucide/compare/1.18.0...1.19.0>
### [`v1.18.0`](https://redirect.github.com/lucide-icons/lucide/releases/tag/1.18.0): Version 1.18.0
[Compare Source](https://redirect.github.com/lucide-icons/lucide/compare/1.17.0...1.18.0)
#### What's Changed
- chore(site): Remove survey from site by [@​ericfennis](https://redirect.github.com/ericfennis) in [#​4417](https://redirect.github.com/lucide-icons/lucide/pull/4417)
- feat(icons): added `play-off` icon by [@​Ahmed-Dghaies](https://redirect.github.com/Ahmed-Dghaies) in [#​4412](https://redirect.github.com/lucide-icons/lucide/pull/4412)
- fix(metadata): add missing use-cases prop on play-off.json by [@​karsa-mistmere](https://redirect.github.com/karsa-mistmere) in [#​4423](https://redirect.github.com/lucide-icons/lucide/pull/4423)
- fix(docs): force hide #bb-banner, if html.has-bb-banner is missing by [@​karsa-mistmere](https://redirect.github.com/karsa-mistmere) in [#​4422](https://redirect.github.com/lucide-icons/lucide/pull/4422)
- fix(docs): Remove `@next` from installation instructions for`@lucide/svelte` by [@​alecglassford](https://redirect.github.com/alecglassford) in [#​4432](https://redirect.github.com/lucide-icons/lucide/pull/4432)
- feat(packages/angular): add support for Angular v22 and onwards by [@​karsa-mistmere](https://redirect.github.com/karsa-mistmere) in [#​4450](https://redirect.github.com/lucide-icons/lucide/pull/4450)
- fix(ci): add check to skip release if latest tag was created today by [@​ericfennis](https://redirect.github.com/ericfennis) in [#​4085](https://redirect.github.com/lucide-icons/lucide/pull/4085)
- feat(icons): added `webcam-off` icon by [@​jordan-burnett](https://redirect.github.com/jordan-burnett) in [#​4242](https://redirect.github.com/lucide-icons/lucide/pull/4242)
#### New Contributors
- [@​alecglassford](https://redirect.github.com/alecglassford) made their first contribution in [#​4432](https://redirect.github.com/lucide-icons/lucide/pull/4432)
- [@​jordan-burnett](https://redirect.github.com/jordan-burnett) made their first contribution in [#​4242](https://redirect.github.com/lucide-icons/lucide/pull/4242)
**Full Changelog**: <https://github.com/lucide-icons/lucide/compare/1.17.0...1.18.0>
</details>
<details>
<summary>microsoft/playwright (@​playwright/test)</summary>
### [`v1.61.0`](https://redirect.github.com/microsoft/playwright/releases/tag/v1.61.0)
[Compare Source](https://redirect.github.com/microsoft/playwright/compare/v1.60.0...v1.61.0)
##### 🔑 WebAuthn passkeys
New [Credentials](https://playwright.dev/docs/api/class-credentials) virtual authenticator, available via [browserContext.credentials](https://playwright.dev/docs/api/class-browsercontext#browser-context-credentials), lets tests register passkeys and answer `navigator.credentials.create()` / `navigator.credentials.get()` ceremonies in the page — no real hardware key required, works in all browsers:
```js
const context = await browser.newContext();
// Seed a passkey your backend provisioned for a test user.
await context.credentials.create('example.com', {
id: credentialId,
userHandle,
privateKey,
publicKey,
});
await context.credentials.install();
const page = await context.newPage();
await page.goto('https://example.com/login');
// The page's navigator.credentials.get() is answered with the seeded passkey.
```
You can also let the app register a passkey once in a setup test, read it back with [credentials.get()](https://playwright.dev/docs/api/class-credentials#credentials-get), and seed it into later tests — see [Credentials](https://playwright.dev/docs/api/class-credentials) for details.
##### 🗃️ Web Storage
New [WebStorage](https://playwright.dev/docs/api/class-webstorage) API, available via [page.localStorage](https://playwright.dev/docs/api/class-page#page-local-storage) and [page.sessionStorage](https://playwright.dev/docs/api/class-page#page-session-storage), reads and writes the page's storage for the current origin:
```js
await page.localStorage.setItem('token', 'abc');
const token = await page.localStorage.getItem('token');
const items = await page.sessionStorage.items();
```
##### New APIs
##### Network
- [apiResponse.securityDetails()](https://playwright.dev/docs/api/class-apiresponse#api-response-security-details) and [apiResponse.serverAddr()](https://playwright.dev/docs/api/class-apiresponse#api-response-server-addr) mirror the browser-side [response.securityDetails()](https://playwright.dev/docs/api/class-response#response-security-details) and [response.serverAddr()](https://playwright.dev/docs/api/class-response#response-server-addr).
##### Browser and Screencast
- New option `artifactsDir` in [browserType.connectOverCDP()](https://playwright.dev/docs/api/class-browsertype#browser-type-connect-over-cdp) controls where artifacts such as traces and downloads are stored when attached to an existing browser.
- New option `cursor` in [screencast.showActions()](https://playwright.dev/docs/api/class-screencast#screencast-show-actions) controls the cursor decoration rendered for pointer actions.
- The `onFrame` callback in [screencast.start()](https://playwright.dev/docs/api/class-screencast#screencast-start) now receives a `timestamp` of when the frame was presented by the browser.
##### Test runner
- The [testOptions.video](https://playwright.dev/docs/api/class-testoptions#test-options-video) option now supports the same set of modes as `trace`: new `'on-all-retries'`, `'retain-on-first-failure'` and `'retain-on-failure-and-retries'` values. See the [video modes table](https://playwright.dev/docs/test-use-options#video-modes) for which runs are recorded and kept in each mode.
- Supported `expect.soft.poll(...)`.
- New [fullConfig.argv](https://playwright.dev/docs/api/class-fullconfig#full-config-argv) — a snapshot of `process.argv` from the runner process, handy for reading custom arguments passed after the `--` separator.
- New [fullConfig.failOnFlakyTests](https://playwright.dev/docs/api/class-fullconfig#full-config-fail-on-flaky-tests) mirrors the config option, so reporters can explain why a flaky run failed.
- [testInfo.errors](https://playwright.dev/docs/api/class-testinfo#test-info-errors) now lists each sub-error of an `AggregateError` as a separate entry.
- New `-G` command line shorthand for `--grep-invert`.
##### 🛠️ Other improvements
- Playwright now supports Ubuntu 26.04.
- HAR and trace recordings now include WebSocket requests.
##### Browser Versions
- Chromium 149.0.7827.55
- Mozilla Firefox 151.0
- WebKit 26.5
This version was also tested against the following stable channels:
- Google Chrome 149
- Microsoft Edge 149
</details>
<details>
<summary>TanStack/virtual (@​tanstack/virtual-core)</summary>
### [`v3.17.1`](https://redirect.github.com/TanStack/virtual/blob/HEAD/packages/virtual-core/CHANGELOG.md#3171)
[Compare Source](https://redirect.github.com/TanStack/virtual/compare/@tanstack/virtual-core@3.17.0...@tanstack/virtual-core@3.17.1)
##### Patch Changes
- [#​1199](https://redirect.github.com/TanStack/virtual/pull/1199) [`ef69ea3`](https://redirect.github.com/TanStack/virtual/commit/ef69ea31738caa2819142e922efa03d3c408e25c) - Fix "items jump while scrolling up": the default scroll-adjustment predicate now compensates scrollTop on the first measurement of an above-viewport item even while scrolling backward (the estimate→actual delta must be absorbed), and only skips compensation for re-measurements during backward scroll to avoid the cascading jank
</details>
<details>
<summary>tauri-apps/tauri (@​tauri-apps/cli)</summary>
### [`v2.11.3`](https://redirect.github.com/tauri-apps/tauri/releases/tag/%40tauri-apps/cli-v2.11.3): @​tauri-apps/cli v2.11.3
[Compare Source](https://redirect.github.com/tauri-apps/tauri/compare/@tauri-apps/cli-v2.11.2...@tauri-apps/cli-v2.11.3)
#### \[2.11.3]
##### Bug Fixes
- [`50b0237ed`](https://www.github.com/tauri-apps/tauri/commit/50b0237edb9ed683979b7954975b98a4d22a9f70) ([#​15549](https://redirect.github.com/tauri-apps/tauri/pull/15549) by [@​Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Escape special characters in `productName` when generating Android `strings.xml`
- [`728c8d4a5`](https://www.github.com/tauri-apps/tauri/commit/728c8d4a5d9e3badf4683eb2e493d950d27d6b66) ([#​15473](https://redirect.github.com/tauri-apps/tauri/pull/15473) by [@​Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Skip building bundles when using `tauri android run`
- [`be0cb0d43`](https://www.github.com/tauri-apps/tauri/commit/be0cb0d4378ddf26bc33066b3750f2639ade15f5) ([#​15344](https://redirect.github.com/tauri-apps/tauri/pull/15344) by [@​raglady](https://www.github.com/tauri-apps/tauri/../../raglady)) Fix NDK\_HOME environment variable not honored when set
- [`ed8fd411f`](https://www.github.com/tauri-apps/tauri/commit/ed8fd411fe10469da33f63ed5bd9d7ae19e77d84) ([#​15552](https://redirect.github.com/tauri-apps/tauri/pull/15552) by [@​Legend-Master](https://www.github.com/tauri-apps/tauri/../../Legend-Master)) Make `ureq_proto` show trace level logs only on `-vvv` instead of `-vv`
- [`fca4a31f9`](https://www.github.com/tauri-apps/tauri/commit/fca4a31f94f8ba709d1b28e073e69867b8704e6e) ([#​15454](https://redirect.github.com/tauri-apps/tauri/pull/15454) by [@​fallintoplace](https://www.github.com/tauri-apps/tauri/../../fallintoplace)) Fix `tauri migrate` generating invalid namespace imports for aliased pluginified imports from `@tauri-apps/api`.
Inputs like `import { cli as superCli } from "@​tauri-apps/api"` now migrate to `import * as superCli from "@​tauri-apps/plugin-cli"` instead of producing invalid ESM syntax. The migration tests also reparse migrated JS, Svelte, and Vue output so syntax regressions are caught directly.
##### Dependencies
- Upgraded to `tauri-cli@2.11.3`
</details>
<details>
<summary>testing-library/svelte-testing-library (@​testing-library/svelte)</summary>
### [`v5.4.1`](https://redirect.github.com/testing-library/svelte-testing-library/releases/tag/%40testing-library/svelte%405.4.1)
[Compare Source](https://redirect.github.com/testing-library/svelte-testing-library/compare/@testing-library/svelte@5.4.0...@testing-library/svelte@5.4.1)
#### [@​testing-library/svelte](https://redirect.github.com/testing-library/svelte) [5.4.1](https://redirect.github.com/testing-library/svelte-testing-library/compare/@testing-library/svelte@5.4.0...@testing-library/svelte@5.4.1) (2026-06-21)
##### Dependencies
- **[@​testing-library/svelte-core](https://redirect.github.com/testing-library/svelte-core):** upgraded to 1.1.2
### [`v5.4.0`](https://redirect.github.com/testing-library/svelte-testing-library/releases/tag/%40testing-library/svelte%405.4.0)
[Compare Source](https://redirect.github.com/testing-library/svelte-testing-library/compare/@testing-library/svelte@5.3.1...@testing-library/svelte@5.4.0)
#### [@​testing-library/svelte](https://redirect.github.com/testing-library/svelte) [5.4.0](https://redirect.github.com/testing-library/svelte-testing-library/compare/@testing-library/svelte@5.3.1...@testing-library/svelte@5.4.0) (2026-06-20)
##### Features
- add wrapper option ([#​492](https://redirect.github.com/testing-library/svelte-testing-library/issues/492)) ([959f8c5](https://redirect.github.com/testing-library/svelte-testing-library/commit/959f8c5a5d7b1540907043bef23cc070757903e8))
##### Dependencies
- **[@​testing-library/svelte-core](https://redirect.github.com/testing-library/svelte-core):** upgraded to 1.1.0
</details>
<details>
<summary>ferdikoomen/openapi-typescript-codegen (openapi-typescript-codegen)</summary>
### [`v0.31.0`](https://redirect.github.com/ferdikoomen/openapi-typescript-codegen/releases/tag/v0.31.0)
[Compare Source](https://redirect.github.com/ferdikoomen/openapi-typescript-codegen/compare/v0.30.0...v0.31.0)
#### v0.31.0
Maintenance release: dependency upgrades (including several major-version bumps), TypeScript 6 compatibility, and a build script fix. No changes to the generator's output or public API.
##### Dependencies
- commander 14.0.2 → 14.0.3
- fs-extra 11.3.3 → 11.3.5
- handlebars 4.7.8 → 4.7.9
##### Dev dependencies
- [@​angular-devkit/build-angular](https://redirect.github.com/angular-devkit/build-angular) 21.0.4 → 22.0.3
- [@​angular/animations](https://redirect.github.com/angular/animations) 21.0.6 → 22.0.2
- [@​angular/cli](https://redirect.github.com/angular/cli) 21.0.4 → 22.0.3
- [@​angular/common](https://redirect.github.com/angular/common) 21.0.6 → 22.0.2
- [@​angular/compiler](https://redirect.github.com/angular/compiler) 21.0.6 → 22.0.2
- [@​angular/compiler-cli](https://redirect.github.com/angular/compiler-cli) 21.0.6 → 22.0.2
- [@​angular/core](https://redirect.github.com/angular/core) 21.0.6 → 22.0.2
- [@​angular/forms](https://redirect.github.com/angular/forms) 21.0.6 → 22.0.2
- [@​angular/platform-browser](https://redirect.github.com/angular/platform-browser) 21.0.6 → 22.0.2
- [@​angular/platform-browser-dynamic](https://redirect.github.com/angular/platform-browser-dynamic) 21.0.6 → 22.0.2
- [@​angular/router](https://redirect.github.com/angular/router) 21.0.6 → 22.0.2
- [@​babel/cli](https://redirect.github.com/babel/cli) 7.28.3 → 7.29.7
- [@​babel/core](https://redirect.github.com/babel/core) 7.28.5 → 7.29.7
- [@​babel/preset-env](https://redirect.github.com/babel/preset-env) 7.28.5 → 7.29.7
- [@​babel/preset-typescript](https://redirect.github.com/babel/preset-typescript) 7.28.5 → 7.29.7
- [@​eslint/js](https://redirect.github.com/eslint/js) 9.39.2 → 10.0.1
- [@​rollup/plugin-commonjs](https://redirect.github.com/rollup/plugin-commonjs) 29.0.0 → 29.0.3
- [@​rollup/plugin-terser](https://redirect.github.com/rollup/plugin-terser) 0.4.4 → 1.0.0
- [@​types/node](https://redirect.github.com/types/node) 25.0.3 → 26.0.0
- [@​types/qs](https://redirect.github.com/types/qs) 6.14.0 → 6.15.1
- [@​typescript-eslint/eslint-plugin](https://redirect.github.com/typescript-eslint/eslint-plugin) 8.50.1 → 8.61.1
- [@​typescript-eslint/parser](https://redirect.github.com/typescript-eslint/parser) 8.50.1 → 8.61.1
- axios 1.13.2 → 1.18.0
- eslint 9.39.2 → 10.5.0
- eslint-plugin-prettier 5.5.4 → 5.5.6
- eslint-plugin-simple-import-sort 12.1.1 → 13.0.0
- form-data 4.0.5 → 4.0.6
- glob 13.0.0 → 13.0.6
- globals 16.5.0 → 17.6.0
- jest 30.2.0 → 30.4.2
- jest-cli 30.2.0 → 30.4.2
- prettier 3.7.4 → 3.8.4
- puppeteer 24.34.0 → 24.43.1
- qs 6.14.0 → 6.15.2
- rimraf 6.1.2 → 6.1.3
- rollup 4.54.0 → 4.62.2
- typescript 5.9.3 → 6.0.3
- typescript-eslint 8.50.1 → 8.61.1
- zone.js 0.16.0 → 0.16.2
**Full Changelog**: <https://github.com/ferdikoomen/openapi-typescript-codegen/compare/v0.30.0...v0.31.0>
</details>
<details>
<summary>sveltejs/svelte (svelte)</summary>
### [`v5.56.3`](https://redirect.github.com/sveltejs/svelte/blob/HEAD/packages/svelte/CHANGELOG.md#5563)
[Compare Source](https://redirect.github.com/sveltejs/svelte/compare/svelte@5.56.2...svelte@5.56.3)
##### Patch Changes
- fix: ignore errors that occur in destroyed effects ([#​18384](https://redirect.github.com/sveltejs/svelte/pull/18384))
- fix: type BigInts in `$state.snapshot(...)` return values ([#​18388](https://redirect.github.com/sveltejs/svelte/pull/18388))
### [`v5.56.2`](https://redirect.github.com/sveltejs/svelte/blob/HEAD/packages/svelte/CHANGELOG.md#5562)
[Compare Source](https://redirect.github.com/sveltejs/svelte/compare/svelte@5.56.1...svelte@5.56.2)
##### Patch Changes
- fix: properly track effect end node for async sibling component ([#​18371](https://redirect.github.com/sveltejs/svelte/pull/18371))
- fix: prevent false-positive reactivity loss warning ([#​18373](https://redirect.github.com/sveltejs/svelte/pull/18373))
- chore: bump esrap dependency ([#​18372](https://redirect.github.com/sveltejs/svelte/pull/18372))
- fix: ignore declaration tags for animation directive ([#​18366](https://redirect.github.com/sveltejs/svelte/pull/18366))
- fix: reject pending async deriveds on discard ([#​18308](https://redirect.github.com/sveltejs/svelte/pull/18308))
</details>
<details>
<summary>voidzero-dev/vite-plus (vite)</summary>
### [`v0.2.1`](https://redirect.github.com/voidzero-dev/vite-plus/releases/tag/v0.2.1): vite-plus v0.2.1
[Compare Source](https://redirect.github.com/voidzero-dev/vite-plus/compare/v0.2.0...v0.2.1)
Restores support for older Node.js (back to `20.19.0`) and makes `vp exec --fail-if-no-match` fail correctly on unmatched filters.
##### Fixes & Enhancements
- Stop blocking older Node.js versions: v0.2.0 blocked commands when the resolved Node.js version fell outside the declared range. This reverts that enforcement and widens `engines.node` to `^20.19.0 || ^22.18.0 || >=24.11.0`, matching Vite's own `^20.19.0` floor, so older Node that works in practice (e.g. Node 20 in rolldown CI) is no longer rejected ([#​1865](https://redirect.github.com/voidzero-dev/vite-plus/pull/1865)), by [@​fengmk2](https://redirect.github.com/fengmk2)
- `vp exec --fail-if-no-match`: exit non-zero when one or more `--filter` expressions match no workspace packages. Strict mode previously only warned and returned success, so typoed filters looked successful in CI even though no package command ran ([#​1859](https://redirect.github.com/voidzero-dev/vite-plus/pull/1859)), by [@​jong-kyung](https://redirect.github.com/jong-kyung)
##### Bundled Versions
| Tool | Version | Source |
| --------------- | -------- | ------------------------------------------------------------------------------------------------- |
| vite | `8.0.16` | [`f94df87`](https://redirect.github.com/vitejs/vite/commit/f94df87ff03b40b65e29bacdc04cc18c7bccaa4a) |
| rolldown | `1.1.1` | [`d7f919c`](https://redirect.github.com/rolldown/rolldown/commit/d7f919c18980e6b4a26d06bd071d7cf14cf810a7) |
| tsdown | `0.22.3` | [npm](https://npmx.dev/package/tsdown/v/0.22.3) |
| vitest | `4.1.9` | [npm](https://npmx.dev/package/vitest/v/4.1.9) |
| oxlint | `1.70.0` | [npm](https://npmx.dev/package/oxlint/v/1.70.0) |
| oxlint-tsgolint | `0.23.0` | [npm](https://npmx.dev/package/oxlint-tsgolint/v/0.23.0) |
| oxfmt | `0.55.0` | [npm](https://npmx.dev/package/oxfmt/v/0.55.0) |
##### Upgrade
```bash
vp upgrade
```
##### Upgrading from 0.1.x to 0.2.1 Prompt
```md
You are upgrading a project that uses Vite+ (the `vp` CLI) from v0.1.x to v0.2.1.
v0.2.1 has one breaking change vs v0.1.x: it consumes upstream Vitest directly. The `@voidzero-dev/vite-plus-test` wrapper package is removed. `vitest` and the base browser runtime (`@vitest/browser`, `@vitest/browser-preview`) now come in transitively through `vite-plus`. The opt-in browser providers (`@vitest/browser-playwright`, `@vitest/browser-webdriverio`) are NOT shipped by `vite-plus`: any project that runs browser-mode tests must install the provider it uses itself.
Do not run `vp migrate` for this upgrade; it is not reliable enough yet. Make the changes yourself by editing the project's files, then verify by running the tools.
How to run vp: if a global `vp` is available, use it. Otherwise this project only ships the local CLI from the `vite-plus` package, so run vp as the project-local binary (for example via the package manager's exec: pnpm exec, npx, yarn, or bunx). After any install, re-resolve vp so you always run the version currently in the project.
Do the following:
1. Set the `vite-plus` dependency to the exact version `0.2.1` and reinstall, so the new toolchain is installed and the lockfile moves off 0.1.x. In a monorepo, do this for every workspace package that depends on `vite-plus` (a shared `catalog:` entry covers them all at once). Changing the spec to `0.2.1` is what moves the lockfile off the old resolution; a reinstall that leaves the spec unchanged would keep the old version.
2. Remove the `@voidzero-dev/vite-plus-test` wrapper from the project. Search everywhere it could appear: package.json, the lockfile, any workspace or catalog config (such as pnpm-workspace.yaml or .yarnrc.yml), and the source files. Then classify the project and apply the matching case. Note these are not exclusive: a browser-mode project is also handled by case C in addition to removing the wrapper config.
First, determine the project's Vitest usage:
- BROWSER MODE: the project runs Vitest in the browser. It does if a config or test file imports a real browser provider (`vite-plus/test/browser-playwright` or `vite-plus/test/browser-webdriverio`, or the pre-upgrade raw forms `@vitest/browser-playwright` / `@vitest/browser-webdriverio`), or sets `test.browser.enabled`. This needs extra deps regardless of anything below; see case C.
- DIRECT vitest usage: a source or test file imports directly from `vitest` or `@vitest/...`, or a `@vitest/*` package is listed in its dependencies (for example a coverage provider). Plain imports from `vite-plus/test` and `vite-plus/test/*` do NOT count as direct usage; a `vite-plus/test/browser-*` provider import is a browser-mode signal (case C), not direct usage.
Case A - node-mode only (no direct vitest usage, no browser mode; the common case): remove the vitest configuration entirely. In package.json, delete the `vitest` entry from `dependencies` / `devDependencies` in whatever form it takes (a `@voidzero-dev/vite-plus-test` alias, a `catalog:` reference, or a plain version). Also remove the `vitest` entry from every dependency-resolution mechanism in the project: both `overrides` and `resolutions`, pnpm `overrides`/`catalog` (in package.json or pnpm-workspace.yaml), and any catalog entry. If `vitest` appears in more than one of these, remove all of them. Do not add a pinned `vitest`; it arrives transitively through `vite-plus` and the node-mode test command works without it.
Case B - direct vitest usage: pin upstream vitest to the version bundled with vite-plus (4.1.9 for v0.2.1), and upgrade every vitest ecosystem package the project depends on so the whole tree resolves to a single vitest. Set each `@vitest/*` package the project lists (for example `@vitest/coverage-v8`, `@vitest/ui`, `@vitest/browser`) to that same version (4.1.9), since those are pinned to an exact vitest version. Also update any other vitest integration package (such as `vitest-browser-*`) to a release compatible with that vitest version. Leaving an ecosystem package on an older version pulls in a second copy of vitest, which Vitest rejects at runtime.
Case C - browser mode (in addition to removing the wrapper config): you MUST add two deps to the workspace package that runs the browser tests (not the repo root, unless that is where the tests live), both pinned to the bundled vitest version so the tree still resolves to a single vitest:
- The browser provider the project actually uses: `@vitest/browser-playwright@4.1.9` and/or `@vitest/browser-webdriverio@4.1.9`. Without it, config load fails with `Cannot find package '@​vitest/browser-playwright'` from `vite-plus/test/browser-playwright`. Make sure its framework peer is present too (`playwright` for Playwright, `webdriverio` for WebdriverIO); the project usually already has it.
- A direct `vitest@4.1.9`. This is the one case where you DO add a pinned vitest, and it contradicts the "never add vitest" rule that holds for node mode. Reason: under pnpm's isolated node_modules, `vitest` is only a transitive dep of `vite-plus`, so the browser-tester Vite server (rooted at the consumer project) cannot resolve `vitest/internal/browser` or the `vitest > ...` optimizeDeps entries. The symptom is `Failed to resolve import "vitest/internal/browser"` followed by `Failed to connect to the browser session ... within the timeout` and a `no tests` run. vite-plus 0.2.1 ships a `vite-plus:vitest-resolver` plugin meant to rescue this, but it does not reach the separate `@vitest/browser` orchestrator server, so a direct `vitest@4.1.9` (matching the bundled version, single copy preserved) is required. (If a future vite-plus fixes the resolver to cover the browser-tester server, this direct `vitest` may become unnecessary; re-check.)
In all cases, also delete any dependency-resolution config that existed only to accommodate the wrapper or the old vitest, for example pnpm `peerDependencyRules` entries (`allowedVersions` / `ignoreMissing`) referencing `vitest`, `@vitest/*`, or `@voidzero-dev/vite-plus-test`, and the equivalent peer-tweak config in other package managers (such as yarn `packageExtensions`). Leave rules that are unrelated to vitest or the wrapper untouched.
3. Keep the `vite` -> Vite+ core override (it is still required) and set it to the matching exact version: map `vite` to `npm:@​voidzero-dev/vite-plus-core@0.2.1` in whatever override, resolution, or catalog form the project already uses. `@voidzero-dev/vite-plus-core` is released in lockstep with `vite-plus`.
4. Leave imports from `vite-plus/test` (and `vite-plus/test/*`, including `vite-plus/test/browser-playwright`) unchanged; that is the stable public API. Only if a file imports directly from `@voidzero-dev/vite-plus-test`, repoint it to `vite-plus/test`. Leave `declare module 'vitest'` / `declare module '@​vitest/browser*'` type augmentations pointing at the upstream module (they must target the upstream identity to merge).
5. Reinstall so the lockfile reflects your edits, then verify:
- No reference to `@voidzero-dev/vite-plus-test` remains anywhere outside node_modules (source, configs, lockfile).
- The dependency tree resolves to a single `vitest` version (4.1.9) with no duplicate copies. Note that one `vitest@4.1.9:` entry in the lockfile `packages:` section plus one `vitest@4.1.9(...)` key in `snapshots:` is still a single version, not a duplicate.
- The project's tests pass with Vitest's native banner; for browser mode, confirm the suite actually runs in the browser (you get passing test files, not `no tests` or a session timeout). Browser tests also need the browser binary installed (e.g. `npx playwright install chromium`).
- The Vite+ check workflow passes (exit 0). A pre-existing lint/format warning in a file you did not touch is not a failure; report it but do not fix it.
Troubleshooting: if you hit `vitest/internal/browser` resolution errors, or see duplicate `@vitest/browser` / `vite-plus` peer-variant directories under `node_modules/.pnpm` after several sequential installs across this dependency-graph change, do a clean reinstall (remove `node_modules` in the root and all workspaces, remove the Vite optimize caches `node_modules/.vite`, then reinstall) to collapse the stale variants before concluding it is a code problem.
Constraints:
- Do not run `vp migrate`.
- Do not bypass git hooks. If a pre-existing failure blocks you, report it rather than forcing through.
- Make the smallest set of edits that reaches the end state above; do not reformat unrelated files. (For browser mode, the added `vitest` + provider pins ARE part of that minimal end state.)
- When done, give me a short summary: old vs new `vite-plus` version, the files you changed, the test/check results, and call out explicitly any dependency you added beyond the wrapper removal (especially a direct `vitest`) with the reason.
```
**Full Changelog**: <https://github.com/voidzero-dev/vite-plus/compare/v0.2.0...v0.2.1>
##### Published Packages
- `@voidzero-dev/vite-plus-core@0.2.1`
- `vite-plus@0.2.1`
##### Installation
**macOS/Linux:**
```bash
curl -fsSL https://vite.plus | bash
```
**Windows:**
```powershell
irm https://vite.plus/ps1 | iex
```
Or download and run `vp-setup.exe` from the assets below.
### [`v0.2.0`](https://redirect.github.com/voidzero-dev/vite-plus/releases/tag/v0.2.0): vite-plus v0.2.0
[Compare Source](https://redirect.github.com/voidzero-dev/vite-plus/compare/v0.1.24...v0.2.0)
Vite+ now consumes upstream Vitest directly (no wrapper), raises the minimum supported Node.js version to 22.18.0, and ships corepack and devEngines support.
##### Highlights
- **`vp test` now runs upstream Vitest directly (breaking)**: Vite+ used to ship `@voidzero-dev/vite-plus-test`, a rebundled copy of Vitest that lagged upstream releases. That package is removed; `vp test` now runs the real upstream `vitest`, which is installed automatically as a dependency of `vite-plus` (you no longer add `vitest` or `@vitest/*` yourself, and `vite` still resolves to `@voidzero-dev/vite-plus-core` via package-manager overrides). Your `import ... from 'vite-plus/test'` code keeps working unchanged and `vp migrate` updates existing projects ([#​1588](https://redirect.github.com/voidzero-dev/vite-plus/pull/1588)), by [@​Brooooooklyn](https://redirect.github.com/Brooooooklyn)
- **Minimum supported Node.js version raised to `^22.18.0 || >=24.11.0` (breaking)**: Node 20 reached end-of-life and the bundled tsdown already required `^22.18.0`, so the published engines range now matches what `vp pack` can actually deliver; `vp exec` / `vp run` / `vp dlx` reject projects resolving an older Node with the existing incompatibility error ([#​1813](https://redirect.github.com/voidzero-dev/vite-plus/pull/1813)), by [@​fengmk2](https://redirect.github.com/fengmk2)
- **Corepack now works under Vite+**: `corepack` now set up by default, so `corepack enable` and the pnpm/yarn launchers just work, even on Node 25+ which no longer ships it. ([#​1808](https://redirect.github.com/voidzero-dev/vite-plus/pull/1808)), by [@​fengmk2](https://redirect.github.com/fengmk2)
- **devEngines support for runtime and package-manager selection**: Vite+ reads `devEngines.runtime` (ranked above `engines.node`) and `devEngines.packageManager`; auto-pin and `vp migrate` write `devEngines.packageManager`, `vp env pin` / `unpin` target `devEngines.runtime`, and `vp env doctor` reports conflicts instead of silently resolving them ([#​1760](https://redirect.github.com/voidzero-dev/vite-plus/pull/1760)), by [@​fengmk2](https://redirect.github.com/fengmk2)
##### Features
- `vp pm approve-builds`: forward to npm's new `approve-scripts` / `deny-scripts` (npm >= 11.16.0) instead of the previous no-op, matching `pnpm approve-builds` / `bun pm trust`; mixed approve+deny is rejected with actionable guidance and npm's advisory-only caveat is surfaced ([#​1733](https://redirect.github.com/voidzero-dev/vite-plus/pull/1733)), by [@​fengmk2](https://redirect.github.com/fengmk2)
- `vp create`: support local monorepo templates declared in `create.templates` in `vite.config.ts`; `vp create vite:generator` scaffolds a Bingo generator and auto-registers it in the picker, replacing the old package.json-keyword inference ([#​1777](https://redirect.github.com/voidzero-dev/vite-plus/pull/1777)), by [@​fengmk2](https://redirect.github.com/fengmk2)
- `vp create`: detect direct dependencies whose build scripts the package manager gated (e.g. native builds like `better-sqlite3`) and act on them; prompt to approve each (default off) interactively, point at `vp pm approve-builds` non-interactively, or build them with `--approve-builds` ([#​1828](https://redirect.github.com/voidzero-dev/vite-plus/pull/1828)), by [@​fengmk2](https://redirect.github.com/fengmk2)
- `vp config`: add `--no-hooks` and `--no-agent` opt-outs to skip git-hook installation and coding-agent instruction updates ([#​1842](https://redirect.github.com/voidzero-dev/vite-plus/pull/1842)), by [@​leno23](https://redirect.github.com/leno23)
- `vp list -g`: sort the global package list output so entries appear in a stable order ([#​1748](https://redirect.github.com/voidzero-dev/vite-plus/pull/1748)), by [@​liangmiQwQ](https://redirect.github.com/liangmiQwQ)
- Upgrade upstream dependencies: rolldown `1.0.3 -> 1.1.1`, tsdown `0.22.1 -> 0.22.3`, oxlint `1.67.0 -> 1.70.0`, oxfmt `0.52.0 -> 0.55.0`, vitest `4.1.8 -> 4.1.9`, and the oxc toolchain `0.133.0 -> 0.136.0` ([#​1749](https://redirect.github.com/voidzero-dev/vite-plus/pull/1749), [#​1767](https://redirect.github.com/voidzero-dev/vite-plus/pull/1767), [#​1812](https://redirect.github.com/voidzero-dev/vite-plus/pull/1812), [#​1834](https://redirect.github.com/voidzero-dev/vite-plus/pull/1834), [#​1855](https://redirect.github.com/voidzero-dev/vite-plus/pull/1855)), by [@​voidzero-guard](https://redirect.github.com/voidzero-guard)\[bot]
##### Fixes & Enhancements
- Security: resolve open Rust Dependabot advisories by bumping transitive `openssl` `0.10.76 -> 0.10.80` (`openssl-sys` `0.9.112 -> 0.9.116`), fixing five high-severity rust-openssl issues (buffer overflows in key derivation, AES key wrap, and digest finalization; an unchecked PSK/cookie trampoline length leaking adjacent memory; and OCSP-responder undefined behavior: [GHSA-pqf5-4pqq-29f5](https://redirect.github.com/advisories/GHSA-pqf5-4pqq-29f5), [GHSA-8c75-8mhr-p7r9](https://redirect.github.com/advisories/GHSA-8c75-8mhr-p7r9), [GHSA-ghm9-cr32-g9qj](https://redirect.github.com/advisories/GHSA-ghm9-cr32-g9qj), [GHSA-hppc-g8h3-xhp3](https://redirect.github.com/advisories/GHSA-hppc-g8h3-xhp3), [GHSA-xp3w-r5p5-63rr](https://redirect.github.com/advisories/GHSA-xp3w-r5p5-63rr)), and drop the unmaintained, unsound `libyml` ([GHSA-gfxp-f68g-8x78](https://redirect.github.com/advisories/GHSA-gfxp-f68g-8x78), high) by removing dead `serde_yml` code ([#​1742](https://redirect.github.com/voidzero-dev/vite-plus/pull/1742)), by [@​fengmk2](https://redirect.github.com/fengmk2)
- Security (docs site): update `mermaid` `11.13.0 -> 11.15.0` to fix improper `classDef` sanitization in state diagrams that allowed HTML injection ([CVE-2026-41149](https://nvd.nist.gov/vuln/detail/CVE-2026-41149) / [GHSA-ghcm-xqfw-q4vr](https://redirect.github.com/advisories/GHSA-ghcm-xqfw-q4vr), medium severity; `<script>` tags are stripped so it does not reach XSS) ([#​1745](https://redirect.github.com/voidzero-dev/vite-plus/pull/1745)), by [@​renovate](https://redirect.github.com/renovate)\[bot]
- `vp check --fix` / `vp staged`: create/migrate now wrap inline Vite `plugins: [...]` arrays with `lazyPlugins(...)` so plugin factories aren't eagerly executed (and don't hang on open handles) during lint/format/check config loading ([#​1752](https://redirect.github.com/voidzero-dev/vite-plus/pull/1752)), by [@​jong-kyung](https://redirect.github.com/jong-kyung)
- `vp migrate`: complete pending migration work for projects that already have `vite-plus` installed (scripts, imports, tsconfig types, ESLint/Prettier, legacy hooks, package-manager settings) instead of treating `vite-plus` as migration-complete; fully migrated projects stay idempotent ([#​1821](https://redirect.github.com/voidzero-dev/vite-plus/pull/1821)), by [@​jong-kyung](https://redirect.github.com/jong-kyung)
- `vp create` / `vp migrate`: detect shorthand `fmt,` / `lint,` config keys so a duplicate inline block is no longer injected ([#​1843](https://redirect.github.com/voidzero-dev/vite-plus/pull/1843)), by [@​fengmk2](https://redirect.github.com/fengmk2)
- IDE oxlint/oxfmt wrappers: set `VP_COMMAND` so `lazyPlugins()` skips framework plugins during LSP config reads, preventing a stray `.svelte-kit` (and similar) directory at the monorepo root ([#​1764](https://redirect.github.com/voidzero-dev/vite-plus/pull/1764)), by [@​jong-kyung](https://redirect.github.com/jong-kyung)
- `vp lint` / `vp run -r lint` on Windows: keep the absolute `tsgolint` path for workspace lint runs instead of downgrading it to a wrong cwd-relative path ([#​1758](https://redirect.github.com/voidzero-dev/vite-plus/pull/1758)), by [@​semimikoh](https://redirect.github.com/semimikoh)
- oxlint wrapper: set the `tsgolint` path so type-aware lint resolves it ([#​1811](https://redirect.github.com/voidzero-dev/vite-plus/pull/1811)), by [@​jong-kyung](https://redirect.github.com/jong-kyung)
- `vp install -g`: use a unique backup directory and treat stale-backup cleanup as best-effort so a locked Windows binary no longer fails an otherwise successful reinstall ([#​1753](https://redirect.github.com/voidzero-dev/vite-plus/pull/1753)), by [@​fengmk2](https://redirect.github.com/fengmk2)
- `vp install -g`: remove stale managed binary shims when a reinstalled package drops a bin from its `package.json#bin` ([#​1765](https://redirect.github.com/voidzero-dev/vite-plus/pull/1765)), by [@​liangmiQwQ](https://redirect.github.com/liangmiQwQ)
- `vp create --git`: surface git's actual stdout/stderr when the initial commit fails instead of always blaming `user.name` / `user.email` ([#​1819](https://redirect.github.com/voidzero-dev/vite-plus/pull/1819)), by [@​fengmk2](https://redirect.github.com/fengmk2)
- `vp create vite:generator`: reject `--git` / `--no-git`, since adding a generator to an existing monorepo does not initialize git ([#​1788](https://redirect.github.com/voidzero-dev/vite-plus/pull/1788)), by [@​jong-kyung](https://redirect.github.com/jong-kyung)
- Global CLI: harden `find_system_tool` against a self-exec loop (skip the running executable's own bin directory) and fix two `vite_global_cli` tests that could hang ([#​1820](https://redirect.github.com/voidzero-dev/vite-plus/pull/1820)), by [@​fengmk2](https://redirect.github.com/fengmk2)
- CLI help: unify alias display ([#​1832](https://redirect.github.com/voidzero-dev/vite-plus/pull/1832)), show supported `run` options ([#​1797](https://redirect.github.com/voidzero-dev/vite-plus/pull/1797)), show `--fail-if-no-match` in `exec` help ([#​1798](https://redirect.github.com/voidzero-dev/vite-plus/pull/1798)), add the `implode` documentation link ([#​1796](https://redirect.github.com/voidzero-dev/vite-plus/pull/1796)), and handle nested-command typo help ([#​1803](https://redirect.github.com/voidzero-dev/vite-plus/pull/1803)), by [@​jong-kyung](https://redirect.github.com/jong-kyung)
##### Docs
- Document `vp create` opt-out options ([#​1790](https://redirect.github.com/voidzero-dev/vite-plus/pull/1790)), by [@​jong-kyung](https://redirect.github.com/jong-kyung)
- Document `vp upgrade` options ([#​1847](https://redirect.github.com/voidzero-dev/vite-plus/pull/1847)), by [@​jong-kyung](https://redirect.github.com/jong-kyung)
- Align the config overview with the sidebar ([#​1846](https://redirect.github.com/voidzero-dev/vite-plus/pull/1846)), by [@​jong-kyung](https://redirect.github.com/jong-kyung)
- Sync the documented command lists with the help output ([#​1850](https://redirect.github.com/voidzero-dev/vite-plus/pull/1850)), by [@​jong-kyung](https://redirect.github.com/jong-kyung)
- Clarify lazy plugin side effects ([#​1841](https://redirect.github.com/voidzero-dev/vite-plus/pull/1841)), by [@​leno23](https://redirect.github.com/leno23)
- Add JongKyung's X profile ([#​1844](https://redirect.github.com/voidzero-dev/vite-plus/pull/1844)) and update Christoph's X profile ([#​1845](https://redirect.github.com/voidzero-dev/vite-plus/pull/1845)) on the team page, by [@​jong-kyung](https://redirect.github.com/jong-kyung)
##### Refactor
- Remove the CLI tips system; the shortcuts it printed on `vp install` are already covered by the help system and added unnecessary complexity ([#​1799](https://redirect.github.com/voidzero-dev/vite-plus/pull/1799)), by [@​cpojer](https://redirect.github.com/cpojer)
##### Chore
- Re-enable Renovate dependency updates with a targeted ignore-list ([#​1744](https://redirect.github.com/voidzero-dev/vite-plus/pull/1744)), by [@​fengmk2](https://redirect.github.com/fengmk2)
- Keep generated NAPI bindings during upgrade-deps ([#
> ✂ **Note**
>
> PR body was truncated to here.
</details>
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box
---
This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/kenn-io/agentsview).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDIuMiIsInVwZGF0ZWRJblZlciI6IjQzLjI0Mi4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
Co-authored-by: renovate[bot] <renovate[bot]@users.noreply.github.com>
…stence (kenn-io#898) PG serve already supports shared stars and pins, but the rest of the dashboard curation surface still behaves as read-only. Session rename, trash, restore, empty trash, insight delete, and generated-insight persistence all route through the existing `db.Store` seam, yet the PostgreSQL adapter still returns `db.ErrReadOnly` for those methods, and the insight-generation route stops on a coarse read-only check before it can save anything. This change fills in the remaining PG dashboard-write slice without widening scope to local-only features or other read-only backends. PostgreSQL gets the missing insight storage plus Store implementations for insight CRUD and session-management methods, and the server now treats generated-insight writes as an explicit capability: pg serve advertises it, the frontend enables the Generate controls in that mode, and plain read-only stores such as duckdb still fail fast before any generator work starts. Settings remain blocked in pg serve through the existing read-only contract, while local `serve --no-sync` stays writable because it still uses a local Store. The tests cover the PG round trips for insight CRUD and session management, the read-only capability split on the insight route, the pg serve and local no-sync settings behavior, the pg serve version metadata that enables the frontend, and the two frontend insight entry points. Stars and pins are left untouched, and ingestion-oriented methods such as `WriteSessionBatchAtomic` remain read-only. Closes kenn-io#183 Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
Serve should not ask operators to manually stop an older compatible daemon when the rest of the CLI already trusts the safe replacement policy. This PR brings foreground serve in line with background serve and autostart while keeping dev builds, downgrades, and forward API/data conflicts behind an explicit --replace choice. It also makes daemon status describe the real conflict when an incompatible writable daemon is alive, including the running and current versions, so users can tell whether to retry with --replace or stop the daemon. The replacement paths preflight data-version compatibility before stopping an existing daemon, leave read-only PG mirrors untouched, and keep the existing direct-write guard as a backstop for invalid multi-writer runtime records. Reviewers should look at the serve lifecycle and transport classifier paths first; the docs cover the resulting policy and the remaining tradeoff that a stop-then-start failure can leave no daemon running. Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
Remote sync through the daemon could look idle for most of the run because the CLI collapsed remote progress into an overwritten status line and left only the final summary visible. That made slow SSH, download, and processing phases hard to distinguish from a hang. This changes the daemon-backed remote sync CLI output to keep each remote phase visible with elapsed time while preserving the inline session counter during active parsing. Reviewers should focus on the terminal-output behavior in `cmd/agentsview/sync.go`, since the daemon already emits the phase details and this change is intentionally scoped to CLI rendering. Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
…rrectly (kenn-io#912) ## Background A dataVersion resync was spending minutes in discovery, but the CLI progress printer mis-credited that time to the previous phase ("disabling temporary search index updates"), because it timed each phase as the wall-clock gap between phase labels and discovery had no label of its own. Adding real per-phase timing surfaced that discovery itself was the regression, introduced by the recent provider-factory refactor. ## Progress reporting + instrumentation - `resyncAllLocked` reports `PhaseDiscovering` ("Discovering sessions") before the sync pass and logs `PhaseStats` afterward, so resync uses the same bulk-write profiler as CLI sync. - `syncAllLocked` logs discovery timing unconditionally and times the `countDBBackedSessions` pass separately. - `discoverProviderSources` logs per-provider discovery timing when a provider exceeds 100ms, attributing slow discovery to the provider by name. - `TestResyncAllReportsDiscoveryBeforeSyncing` asserts `PhaseDiscovering` precedes the first `PhaseSyncing` event. ## Discovery perf regression The refactor left several providers recomputing root-derived project info for every discovered session inside discovery's per-source loop: - Gemini re-built the full project map (read + JSON parse + SHA-256 map) per source. Gemini discovery on a large store dropped from ~2m47s to ~687ms. - positron and vscode-copilot re-read `workspace.json` per session. - antigravity-cli re-read and re-parsed `history.jsonl` per project-less source. Each provider now builds the root-derived project info once per root (or workspace dir) and threads it into per-source resolution. Single-path event callers keep per-path resolution by passing an empty project. Count-based seam tests assert the manifest/map is built once per root regardless of session count. Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
…enn-io#914) ## What Adds a persisted per-session `transcript_fidelity` signal (`full` / `summary`) for Antigravity CLI sessions and surfaces it where users look: - The `antigravity-cli` parser classifies each session: `full` only when a covering `agy-reader` trajectory sidecar produced the transcript; `summary` for the degraded fallbacks (heuristic `.db` decode, partial/stale sidecar, history + brain, or `ANTIGRAVITY_KEY` decrypt). Empty/absent is treated as `full`, so every other agent is unaffected. - A subtle **"Summary mode" badge** in the session detail header (`SessionBreadcrumb`) for `antigravity-cli` summary sessions, linking to the existing "Antigravity CLI: high-resolution transcripts" docs. - An `agentsview doctor sync` line reporting how many Antigravity CLI sessions are in summary mode. ## Why Without an `agy-reader` sidecar, an Antigravity CLI session still renders — with normal metadata but a thin transcript (no structured tool results, thinking, or diffs). Nothing signalled that the richer transcript was missing or recoverable, so a degraded session was indistinguishable from a naturally short one, and the `agy-reader` remediation lived only in docs. This makes the degradation visible and points at the fix. ## Backend parity & migration `transcript_fidelity` is threaded through all three session backends — SQLite, PostgreSQL, and DuckDB (schema, non-destructive `ADD COLUMN` migration, upsert, scan, push, and change-detection/fingerprint) — and the `parse-diff` parser-drift audit. The SQLite data version is bumped so existing rows are re-labeled on the next sync. That is a one-time full resync; the field is parser-derived, so a lazy backfill (only on file change) would leave static legacy sessions permanently unlabeled, defeating the feature for exactly the sessions it targets. ## Scope / limitations - The IDE `antigravity` agent is out of scope — `agy-reader` only serves the CLI, so there is no remediation to point at; those sessions get no badge. - The badge is detail-header only (no per-row session-list badge). The copy is intentionally soft ("install it, or if installed, let it catch up") so an in-progress session whose sidecar is still catching up is not nagged; both Antigravity parsers full-replace messages on reparse, so the badge clears once a covering sidecar lands. ## Where to look - Classification: `internal/parser/antigravity_cli.go` (`full` set only on the covering-sidecar branches). - Badge: `frontend/src/lib/components/layout/SessionBreadcrumb.svelte` and `frontend/messages/{en,zh-CN}.json`. - Doctor: `cmd/agentsview/doctor.go`. - Parity: `internal/db`, `internal/postgres`, `internal/duckdb`, and `internal/sync/parsediff_*`. Co-authored-by: Matthew Jacobs <mjacobs@users.noreply.github.com>
## Problem Running `agentsview` after a rebuild could die instantly with `zsh: killed` / exit 137 (SIGKILL), even for `agentsview --version`. The binary on disk is intact and its ad-hoc signature verifies, yet the kernel kills every exec of it. Cause: `make install` overwrites the binary in place with `cp`, which truncates and rewrites the **same inode**. When a previous `agentsview` (e.g. a long-running `serve` / file watcher) is still running, macOS has the old binary's signed pages cached for that vnode. After the in-place overwrite the on-disk pages no longer match the cached cdhash, so the kernel refuses to exec the vnode and sends SIGKILL — with no log entry. Killing the old process or writing a fresh inode clears it. ## Fix Install by copying to a temp file in the destination directory and then `mv`-ing it into place. Rename is atomic and produces a fresh inode, so an in-flight overwrite can never collide with a still-running process's cached code-signature pages. The two install branches (`~/.local/bin` vs `$GOBIN`/`$GOPATH/bin`) are unified so both resolve `INSTALL_DIR` and then share the same copy+rename step. Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
Renovate already had a GitHub Actions package group, but grouped update branches can still be split by update type when the split controls are left at their defaults. That matters for the SHA-pinned workflow actions in this repo, where digest-style and version-style updates should land together instead of creating separate Renovate PRs. This keeps the existing GitHub Actions group name and slug while making the remaining split behavior explicit, so future action updates stay grouped under the same dependency PR. <sup>generated by a clanker</sup> Co-authored-by: Marius van Niekerk <mariusvniekerk@users.noreply.github.com>
The Insights page currently opens straight into filters, deterministic recommendations, quality patterns, and generated archives. That makes the page hard to interpret for first-time users even though the docs already explain the concepts and workflow. This adds a small in-app help entry point that links to the Insights documentation and introduces the key distinction between deterministic quality facts and generated insight text. It keeps to the narrow docs/help slice `wesm` asked for in the issue thread, and it does not redesign the insights workflow, change generation behavior, or touch backend scoring. The focused test asserts that the page exposes the docs URL and the explanatory affordance, and `npm run check` covers the localized message wiring. Fixes kenn-io#178 Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
The Windows desktop wrapper already has a supported `desktop.env` escape hatch, but it forwards WSL-style directory values to the Windows sidecar unchanged. That leaves users who keep sessions under WSL without a documented value shape the packaged Windows app can actually read. This adds a desktop-only bridge for explicit WSL path values in `desktop.env`, converting a distro-qualified marker such as `wsl:Ubuntu:/home/me/.codex/sessions` into the UNC path the Windows sidecar can access. The change stays in the Tauri wrapper where environment assembly already lives, and it leaves Go config loading, parser discovery, sync behavior, and storage untouched. The focused coverage proves valid conversion, malformed-marker and URL-like literal preservation, ordinary Windows and UNC path preservation, and existing env precedence. The README now documents the Windows `desktop.env` syntax separately from `AGENTSVIEW_DESKTOP_PATH`, which remains the executable search-path override. Fixes kenn-io#177 Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
Claude.ai exports can include code suggestions and other text-bearing content in a message's `attachments` array, but the current import path only turns top-level text and supported `content` blocks into message content. That means a conversation can import successfully while the code the user expected to review never appears in agentsview, and reimporting the same archive still leaves old truncated messages behind because the unchanged-session skip path only looks at message count and `updated_at`. The parser now decodes text-bearing Claude.ai attachments and appends recovered attachment content after the existing text and thinking assembly rules, while still ignoring empty or metadata-only attachment shapes. The importer also compares stored messages to the newly parsed output before taking the unchanged-session skip path, so reimporting an older Claude.ai archive repairs sessions that were previously missing attachment-backed content. There is no new database schema, general attachment UI, or ChatGPT import change. The attachment follow-up comes from wesm's direction in kenn-io#254 (comment), after PR kenn-io#258 shipped the base Claude.ai import path. Focused parser and importer tests cover fresh imports, same-archive reimports, the unlabeled and fallback attachment paths, and the negative space around plain text, thinking blocks, and unsupported attachments. Fixes kenn-io#254 Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
The desktop wrapper already captures sidecar stdout and stderr while it waits for the backend to become ready, but on Windows release builds those diagnostics disappear with the GUI-only process. That makes startup failures much harder to debug even when the wrapper saw the exact progress or error output. This keeps the fix desktop-owned. Sidecar events now go through a bounded background log writer that appends durable records under the app log directory without blocking startup parsing or restart handling, the persisted stdout path redacts token-bearing startup lines before they hit disk even when stdout arrives in split chunks, and the native File menu gets an Open Logs Folder action so the captured output is reachable from the app. The existing status parsing, port detection, stage transitions, stderr mirroring, and best-effort failure tolerance stay in place. The last review pass tightened the log output itself by splitting bare `\r` progress updates into readable line-delimited records instead of collapsing them into a single mutated line. Focused validation passed on `cargo fmt --all -- --check`, `cargo test --lib`, and `cargo clippy --lib --tests -- -D warnings`. The change stays desktop-only and directly addresses the hidden-stderr Windows case called out in the issue thread. Fixes kenn-io#132 Co-authored-by: Rod Boev <rodboev@users.noreply.github.com>
This PR contains the following updates:
| Package | Type | Update | Change | Pending |
|---|---|---|---|---|
| [actions/checkout](https://redirect.github.com/actions/checkout) | action | major | `v6.0.3` → `v7.0.0` | |
| [actions/setup-python](https://redirect.github.com/actions/setup-python) | action | minor | `v6.0.0` → `v6.2.0` | `v6.3.0` |
| [astral-sh/setup-uv](https://redirect.github.com/astral-sh/setup-uv) | action | major | `v7.1.2` → `v8.2.0` | |
| [codecov/codecov-action](https://redirect.github.com/codecov/codecov-action) | action | major | `v6.0.1` → `v7.0.0` | |
| [msys2/setup-msys2](https://redirect.github.com/msys2/setup-msys2) ([changelog](https://redirect.github.com/msys2/setup-msys2/compare/e9898307ac31d1a803454791be09ab9973336e1c..66cd2cce69caa17b53920067426061ca1de3a884)) | action | digest | `e989830` → `66cd2cc` | |
| [python](https://redirect.github.com/actions/python-versions) | uses-with | minor | `3.12` → `3.14` | |
| [softprops/action-gh-release](https://redirect.github.com/softprops/action-gh-release) | action | patch | `v3.0.0` → `v3.0.1` | |
---
### Release Notes
<details>
<summary>actions/checkout (actions/checkout)</summary>
### [`v7.0.0`](https://redirect.github.com/actions/checkout/blob/HEAD/CHANGELOG.md#v700)
[Compare Source](https://redirect.github.com/actions/checkout/compare/v7.0.0...v7.0.0)
- Block checking out fork PR for pull\_request\_target and workflow\_run by [@​aiqiaoy](https://redirect.github.com/aiqiaoy) in [#​2454](https://redirect.github.com/actions/checkout/pull/2454)
- Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the minor-actions-dependencies group across 1 directory by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​2458](https://redirect.github.com/actions/checkout/pull/2458)
- Bump flatted from 3.3.1 to 3.4.2 by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​2460](https://redirect.github.com/actions/checkout/pull/2460)
- Bump js-yaml from 4.1.0 to 4.2.0 by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​2461](https://redirect.github.com/actions/checkout/pull/2461)
- Bump [@​actions/core](https://redirect.github.com/actions/core) and [@​actions/tool-cache](https://redirect.github.com/actions/tool-cache) and Remove uuid by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​2459](https://redirect.github.com/actions/checkout/pull/2459)
- upgrade module to esm and update dependencies by [@​aiqiaoy](https://redirect.github.com/aiqiaoy) in [#​2463](https://redirect.github.com/actions/checkout/pull/2463)
- Bump the minor-npm-dependencies group across 1 directory with 3 updates by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​2462](https://redirect.github.com/actions/checkout/pull/2462)
### [`v7`](https://redirect.github.com/actions/checkout/blob/HEAD/CHANGELOG.md#v700)
[Compare Source](https://redirect.github.com/actions/checkout/compare/v6.0.3...v7.0.0)
- Block checking out fork PR for pull\_request\_target and workflow\_run by [@​aiqiaoy](https://redirect.github.com/aiqiaoy) in [#​2454](https://redirect.github.com/actions/checkout/pull/2454)
- Bump actions/publish-immutable-action from 0.0.3 to 0.0.4 in the minor-actions-dependencies group across 1 directory by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​2458](https://redirect.github.com/actions/checkout/pull/2458)
- Bump flatted from 3.3.1 to 3.4.2 by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​2460](https://redirect.github.com/actions/checkout/pull/2460)
- Bump js-yaml from 4.1.0 to 4.2.0 by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​2461](https://redirect.github.com/actions/checkout/pull/2461)
- Bump [@​actions/core](https://redirect.github.com/actions/core) and [@​actions/tool-cache](https://redirect.github.com/actions/tool-cache) and Remove uuid by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​2459](https://redirect.github.com/actions/checkout/pull/2459)
- upgrade module to esm and update dependencies by [@​aiqiaoy](https://redirect.github.com/aiqiaoy) in [#​2463](https://redirect.github.com/actions/checkout/pull/2463)
- Bump the minor-npm-dependencies group across 1 directory with 3 updates by [@​dependabot](https://redirect.github.com/dependabot)\[bot] in [#​2462](https://redirect.github.com/actions/checkout/pull/2462)
</details>
<details>
<summary>actions/setup-python (actions/setup-python)</summary>
### [`v6.2.0`](https://redirect.github.com/actions/setup-python/releases/tag/v6.2.0)
[Compare Source](https://redirect.github.com/actions/setup-python/compare/v6.1.0...v6.2.0)
#### What's Changed
##### Dependency Upgrades
- Upgrade dependencies to Node 24 compatible versions by [@​salmanmkc](https://redirect.github.com/salmanmkc) in [#​1259](https://redirect.github.com/actions/setup-python/pull/1259)
- Upgrade urllib3 from 2.5.0 to 2.6.3 in `/__tests__/data` by [@​dependabot](https://redirect.github.com/dependabot) in [#​1253](https://redirect.github.com/actions/setup-python/pull/1253) and [#​1264](https://redirect.github.com/actions/setup-python/pull/1264)
**Full Changelog**: <https://github.com/actions/setup-python/compare/v6...v6.2.0>
### [`v6.1.0`](https://redirect.github.com/actions/setup-python/releases/tag/v6.1.0)
[Compare Source](https://redirect.github.com/actions/setup-python/compare/v6...v6.1.0)
#### What's Changed
##### Enhancements:
- Add support for `pip-install` input by [@​gowridurgad](https://redirect.github.com/gowridurgad) in [#​1201](https://redirect.github.com/actions/setup-python/pull/1201)
- Add graalpy early-access and windows builds by [@​timfel](https://redirect.github.com/timfel) in [#​880](https://redirect.github.com/actions/setup-python/pull/880)
##### Dependency and Documentation updates:
- Enhanced wording and updated example usage for `allow-prereleases` by [@​yarikoptic](https://redirect.github.com/yarikoptic) in [#​979](https://redirect.github.com/actions/setup-python/pull/979)
- Upgrade urllib3 from 1.26.19 to 2.5.0 and document breaking changes in v6 by [@​dependabot](https://redirect.github.com/dependabot) in [#​1139](https://redirect.github.com/actions/setup-python/pull/1139)
- Upgrade typescript from 5.4.2 to 5.9.3 and Documentation update by [@​dependabot](https://redirect.github.com/dependabot) in [#​1094](https://redirect.github.com/actions/setup-python/pull/1094)
- Upgrade actions/publish-action from 0.3.0 to 0.4.0 & Documentation update for pip-install input by [@​dependabot](https://redirect.github.com/dependabot) in [#​1199](https://redirect.github.com/actions/setup-python/pull/1199)
- Upgrade requests from 2.32.2 to 2.32.4 by [@​dependabot](https://redirect.github.com/dependabot) in [#​1130](https://redirect.github.com/actions/setup-python/pull/1130)
- Upgrade prettier from 3.5.3 to 3.6.2 by [@​dependabot](https://redirect.github.com/dependabot) in [#​1234](https://redirect.github.com/actions/setup-python/pull/1234)
- Upgrade [@​types/node](https://redirect.github.com/types/node) from 24.1.0 to 24.9.1 and update macos-13 to macos-15-intel by [@​dependabot](https://redirect.github.com/dependabot) in [#​1235](https://redirect.github.com/actions/setup-python/pull/1235)
#### New Contributors
- [@​yarikoptic](https://redirect.github.com/yarikoptic) made their first contribution in [#​979](https://redirect.github.com/actions/setup-python/pull/979)
**Full Changelog**: <https://github.com/actions/setup-python/compare/v6...v6.1.0>
</details>
<details>
<summary>astral-sh/setup-uv (astral-sh/setup-uv)</summary>
### [`v8.2.0`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v8.2.0): 🌈 New inputs `quiet` and `download-from-astral-mirror`
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v8.1.0...v8.2.0)
##### Changes
This release brings two new inputs and a few bug fixes.
##### New inputs
Lets talk about the new inputs first.
##### quiet
Pretty simple. It turns of all `info` loggings. Useful if you use this in a composite action and are not interested in all the details.
In the upcoming releases we will add log groups to fully implement support for "less noise"
> \[!NOTE]\
> Warnings and errors are always logged.
##### download-from-astral-mirror
In some cases you may want to directly use the fallback of checking for available versions and downloading releases from GitHub instead of using the astral.sh mirror. Setting `download-from-astral-mirror: false` allows you to do that.
##### Bugfixes
When using the astral.sh mirror to query available versions and download releases (done by default) we now stop sending the GitHub token in the header. The mirror never looked at it but we shouldn't be handing out that data even if it is just a short lived token.
All other bugfixes try to limit the impact of failed GitHub queries due to retries and other faults.
We couldn't pinpoint all rootcauses yet but added more logging for error cases to track them down.
##### 🐛 Bug fixes
- fix: report unexpected cache save failures [@​eifinger](https://redirect.github.com/eifinger) ([#​896](https://redirect.github.com/astral-sh/setup-uv/issues/896))
- fix: report unexpected setup failures [@​eifinger](https://redirect.github.com/eifinger) ([#​895](https://redirect.github.com/astral-sh/setup-uv/issues/895))
- fix: add timeout to fetch to prevent silent hangs [@​eifinger-bot](https://redirect.github.com/eifinger-bot) ([#​883](https://redirect.github.com/astral-sh/setup-uv/issues/883))
- Limit GitHub tokens to github.com download URLs [@​zsol](https://redirect.github.com/zsol) ([#​878](https://redirect.github.com/astral-sh/setup-uv/issues/878))
- increase libuv-workaround timeout to 100ms [@​eifinger](https://redirect.github.com/eifinger) ([#​880](https://redirect.github.com/astral-sh/setup-uv/issues/880))
##### 🚀 Enhancements
- Add quiet input to suppress info-level log output [@​eifinger](https://redirect.github.com/eifinger) ([#​898](https://redirect.github.com/astral-sh/setup-uv/issues/898))
- feat: add `download-from-astral-mirror` input [@​eifinger](https://redirect.github.com/eifinger) ([#​897](https://redirect.github.com/astral-sh/setup-uv/issues/897))
##### 🧰 Maintenance
- docs: update dependabot rollup biome guidance [@​eifinger](https://redirect.github.com/eifinger) ([#​902](https://redirect.github.com/astral-sh/setup-uv/issues/902))
- chore: update known checksums for 0.11.18 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​899](https://redirect.github.com/astral-sh/setup-uv/issues/899))
- chore: update known checksums for 0.11.17 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​892](https://redirect.github.com/astral-sh/setup-uv/issues/892))
- chore: update known checksums for 0.11.16 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​889](https://redirect.github.com/astral-sh/setup-uv/issues/889))
- chore: update known checksums for 0.11.15 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​885](https://redirect.github.com/astral-sh/setup-uv/issues/885))
- chore: update known checksums for 0.11.14 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​879](https://redirect.github.com/astral-sh/setup-uv/issues/879))
- chore: update known checksums for 0.11.13 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​877](https://redirect.github.com/astral-sh/setup-uv/issues/877))
- chore: update known checksums for 0.11.12 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​876](https://redirect.github.com/astral-sh/setup-uv/issues/876))
- chore: update known checksums for 0.11.11 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​873](https://redirect.github.com/astral-sh/setup-uv/issues/873))
- chore: update known checksums for 0.11.9/0.11.10 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​871](https://redirect.github.com/astral-sh/setup-uv/issues/871))
- chore: update known checksums for 0.11.8 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​867](https://redirect.github.com/astral-sh/setup-uv/issues/867))
- Bump setup-uv references to v8.1.0 SHA in docs [@​eifinger](https://redirect.github.com/eifinger) ([#​862](https://redirect.github.com/astral-sh/setup-uv/issues/862))
- Add update-docs.yml workflow [@​eifinger](https://redirect.github.com/eifinger) ([#​861](https://redirect.github.com/astral-sh/setup-uv/issues/861))
##### ⬆️ Dependency updates
- chore(deps): roll up dependabot updates [@​eifinger](https://redirect.github.com/eifinger) ([#​903](https://redirect.github.com/astral-sh/setup-uv/issues/903))
- chore(deps): roll up dependabot updates [@​eifinger](https://redirect.github.com/eifinger) ([#​901](https://redirect.github.com/astral-sh/setup-uv/issues/901))
- chore(deps): bump release-drafter/release-drafter from 7.3.0 to 7.3.1 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​900](https://redirect.github.com/astral-sh/setup-uv/issues/900))
- chore(deps): bump eifinger/actionlint-action from 1.10.1 to 1.10.2 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​842](https://redirect.github.com/astral-sh/setup-uv/issues/842))
- chore(deps): bump github/codeql-action from 4.35.4 to 4.36.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​893](https://redirect.github.com/astral-sh/setup-uv/issues/893))
- chore(deps): bump zizmorcore/zizmor-action from 0.5.5 to 0.5.6 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​891](https://redirect.github.com/astral-sh/setup-uv/issues/891))
- chore(deps): bump release-drafter/release-drafter from 7.2.0 to 7.3.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​884](https://redirect.github.com/astral-sh/setup-uv/issues/884))
- chore(deps): bump zizmorcore/zizmor-action from 0.5.3 to 0.5.5 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​888](https://redirect.github.com/astral-sh/setup-uv/issues/888))
- chore(deps): bump github/codeql-action from 4.35.3 to 4.35.4 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​881](https://redirect.github.com/astral-sh/setup-uv/issues/881))
- chore(deps): bump github/codeql-action from 4.32.2 to 4.35.3 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​875](https://redirect.github.com/astral-sh/setup-uv/issues/875))
- chore(deps): bump actions/setup-node from 6.3.0 to 6.4.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​866](https://redirect.github.com/astral-sh/setup-uv/issues/866))
- chore(deps): bump zizmorcore/zizmor-action from 0.5.2 to 0.5.3 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​864](https://redirect.github.com/astral-sh/setup-uv/issues/864))
- chore(deps): bump peter-evans/create-pull-request from 8.1.0 to 8.1.1 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​863](https://redirect.github.com/astral-sh/setup-uv/issues/863))
### [`v8.1.0`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v8.1.0): 🌈 New input `no-project`
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v8.0.0...v8.1.0)
##### Changes
This add the a new boolean input `no-project`.
It only makes sense to use in combination with `activate-environment: true` and will append `--no project` to the `uv venv` call. This is for example useful [if you have a pyproject.toml file with parts unparseable by uv](https://redirect.github.com/astral-sh/setup-uv/issues/854)
##### 🚀 Enhancements
- Add input no-project in combination with activate-environment [@​eifinger](https://redirect.github.com/eifinger) ([#​856](https://redirect.github.com/astral-sh/setup-uv/issues/856))
##### 🧰 Maintenance
- fix: grant contents:write to validate-release job [@​eifinger](https://redirect.github.com/eifinger) ([#​860](https://redirect.github.com/astral-sh/setup-uv/issues/860))
- Add a release-gate step to the release workflow [@​zanieb](https://redirect.github.com/zanieb) ([#​859](https://redirect.github.com/astral-sh/setup-uv/issues/859))
- Draft commitish releases [@​eifinger](https://redirect.github.com/eifinger) ([#​858](https://redirect.github.com/astral-sh/setup-uv/issues/858))
- Add action-types.yml to instructions [@​eifinger](https://redirect.github.com/eifinger) ([#​857](https://redirect.github.com/astral-sh/setup-uv/issues/857))
- chore: update known checksums for 0.11.7 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​853](https://redirect.github.com/astral-sh/setup-uv/issues/853))
- Refactor version resolving [@​eifinger](https://redirect.github.com/eifinger) ([#​852](https://redirect.github.com/astral-sh/setup-uv/issues/852))
- chore: update known checksums for 0.11.6 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​850](https://redirect.github.com/astral-sh/setup-uv/issues/850))
- chore: update known checksums for 0.11.5 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​845](https://redirect.github.com/astral-sh/setup-uv/issues/845))
- chore: update known checksums for 0.11.4 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​843](https://redirect.github.com/astral-sh/setup-uv/issues/843))
- Add a release workflow [@​zanieb](https://redirect.github.com/zanieb) ([#​839](https://redirect.github.com/astral-sh/setup-uv/issues/839))
- chore: update known checksums for 0.11.3 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​836](https://redirect.github.com/astral-sh/setup-uv/issues/836))
##### 📚 Documentation
- Update ignore-nothing-to-cache documentation [@​eifinger](https://redirect.github.com/eifinger) ([#​833](https://redirect.github.com/astral-sh/setup-uv/issues/833))
- Pin setup-uv docs to v8 [@​eifinger](https://redirect.github.com/eifinger) ([#​829](https://redirect.github.com/astral-sh/setup-uv/issues/829))
##### ⬆️ Dependency updates
- chore(deps): bump release-drafter/release-drafter from 7.1.1 to 7.2.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​855](https://redirect.github.com/astral-sh/setup-uv/issues/855))
### [`v8.0.0`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v8.0.0): 🌈 Immutable releases and secure tags
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.6.0...v8.0.0)
##### This is the first immutable release of `setup-uv` 🥳
All future releases are also immutable, if you want to know more about what this means checkout [the docs](https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases).
This release also has two breaking changes
##### New format for `manifest-file`
The previously deprecated way of defining a custom version manifest to control which `uv` versions are available and where to download them from got removed. The functionality is still there but you have to use the [new format](https://redirect.github.com/astral-sh/setup-uv/blob/main/docs/customization.md#format).
##### No more major and minor tags
To increase **security** even more we will **stop publishing minor tags**. You won't be able to use `@v8` or `@v8.0` any longer. We do this because pinning to major releases opens up users to supply chain attacks like what happened to [tj-actions](https://unit42.paloaltonetworks.com/github-actions-supply-chain-attack/).
> \[!TIP]
> Use the immutable tag as a version `astral-sh/setup-uv@v8.0.0`
> Or even better the githash `astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57`
##### 🚨 Breaking changes
- Remove update-major-minor-tags workflow [@​eifinger](https://redirect.github.com/eifinger) ([#​826](https://redirect.github.com/astral-sh/setup-uv/issues/826))
- Remove deprecrated custom manifest [@​eifinger](https://redirect.github.com/eifinger) ([#​813](https://redirect.github.com/astral-sh/setup-uv/issues/813))
##### 🧰 Maintenance
- Shortcircuit latest version from manifest [@​eifinger](https://redirect.github.com/eifinger) ([#​828](https://redirect.github.com/astral-sh/setup-uv/issues/828))
- Simplify inputs.ts [@​eifinger](https://redirect.github.com/eifinger) ([#​827](https://redirect.github.com/astral-sh/setup-uv/issues/827))
- Bump release-drafter to v7.1.1 [@​eifinger](https://redirect.github.com/eifinger) ([#​825](https://redirect.github.com/astral-sh/setup-uv/issues/825))
- Refactor inputs [@​eifinger](https://redirect.github.com/eifinger) ([#​823](https://redirect.github.com/astral-sh/setup-uv/issues/823))
- Replace inline compile args with tsconfig [@​eifinger](https://redirect.github.com/eifinger) ([#​824](https://redirect.github.com/astral-sh/setup-uv/issues/824))
- chore: update known checksums for 0.11.2 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​821](https://redirect.github.com/astral-sh/setup-uv/issues/821))
- chore: update known checksums for 0.11.1 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​817](https://redirect.github.com/astral-sh/setup-uv/issues/817))
- chore: update known checksums for 0.11.0 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​815](https://redirect.github.com/astral-sh/setup-uv/issues/815))
- Fix latest-version workflow check [@​eifinger](https://redirect.github.com/eifinger) ([#​812](https://redirect.github.com/astral-sh/setup-uv/issues/812))
- chore: update known checksums for 0.10.11/0.10.12 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​811](https://redirect.github.com/astral-sh/setup-uv/issues/811))
### [`v7.6.0`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v7.6.0): 🌈 Fetch uv from Astral's mirror by default
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.6.0...v7.6.0)
##### Changes
We now default to download uv from `releases.astral.sh`.
This means by default we don't hit the GitHub API at all and shouldn't see any rate limits and timeouts any more.
##### 🚀 Enhancements
- Fetch uv from Astral's mirror by default [@​zsol](https://redirect.github.com/zsol) ([#​809](https://redirect.github.com/astral-sh/setup-uv/issues/809))
##### 🧰 Maintenance
- Switch to ESM for source and test, use CommonJS for dist [@​eifinger](https://redirect.github.com/eifinger) ([#​806](https://redirect.github.com/astral-sh/setup-uv/issues/806))
- chore: update known checksums for 0.10.10 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​804](https://redirect.github.com/astral-sh/setup-uv/issues/804))
##### ⬆️ Dependency updates
- chore(deps): bump zizmorcore/zizmor-action from 0.5.0 to 0.5.2 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​808](https://redirect.github.com/astral-sh/setup-uv/issues/808))
- Bump deps [@​eifinger](https://redirect.github.com/eifinger) ([#​805](https://redirect.github.com/astral-sh/setup-uv/issues/805))
### [`v7.6`](https://redirect.github.com/astral-sh/setup-uv/compare/v7.5.0...v7.6.0)
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.5.0...v7.6.0)
### [`v7.5.0`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v7.5.0): 🌈 Use `astral-sh/versions` as version provider
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.5.0...v7.5.0)
##### No more rate-limits
This release addresses a long-standing source of timeouts and rate-limit failures in setup-uv.
Previously, the action resolved version identifiers like 0.5.x by iterating over available uv releases via the GitHub API to find the best match. In contrast, latest and exact versions such as 0.5.0 skipped version resolution entirely and downloaded uv directly.
The `manifest-file` input was an earlier attempt to improve this. It allows providing an url to a file that lists available versions, checksums, and even custom download URLs. The action also shipped with such a manifest.
However, because that bundled file could become outdated whenever new uv releases were published, the action still had to fall back to the GitHub API in many cases.
This release solves the problem by sourcing version data from Astral’s versions repository via the raw content endpoint:
<https://raw.githubusercontent.com/astral-sh/versions/refs/heads/main/v1/uv.ndjson>
By using the raw endpoint instead of the GitHub API, version resolution no longer depends on API authentication and is much less likely to run into rate limits or timeouts.
***
> \[!TIP]
> The next section is only interesting for users of the `manifest-file` input
The `manifest-file` input lets you override that source with your own URL, for example to test custom uv builds or alternate download locations.
The manifest file must be in NDJSON format, where each line is a JSON object representing a version and its artifacts. For example:
```json
{"version":"0.10.7","artifacts":[{"platform":"x86_64-unknown-linux-gnu","variant":"default","url":"https://example.com/uv-x86_64-unknown-linux-gnu.tar.gz","archive_format":"tar.gz","sha256":"..."}]}
{"version":"0.10.6","artifacts":[{"platform":"x86_64-unknown-linux-gnu","variant":"default","url":"https://example.com/uv-x86_64-unknown-linux-gnu.tar.gz","archive_format":"tar.gz","sha256":"..."}]}
```
> \[!WARNING]\
> The old format still works but is deprecated. A warning will be logged when you use it.
##### Changes
- docs: replace copilot instructions with AGENTS.md [@​eifinger](https://redirect.github.com/eifinger) ([#​794](https://redirect.github.com/astral-sh/setup-uv/issues/794))
##### 🚀 Enhancements
- Use astral-sh/versions as primary version provider [@​eifinger](https://redirect.github.com/eifinger) ([#​802](https://redirect.github.com/astral-sh/setup-uv/issues/802))
##### 📚 Documentation
- docs: add cross-client dependabot rollup skill [@​eifinger](https://redirect.github.com/eifinger) ([#​793](https://redirect.github.com/astral-sh/setup-uv/issues/793))
### [`v7.5`](https://redirect.github.com/astral-sh/setup-uv/compare/v7.4.0...v7.5.0)
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.4.0...v7.5.0)
### [`v7.4.0`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v7.4.0): 🌈 Add riscv64 architecture support to platform detection
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.4.0...v7.4.0)
##### Changes
Thank you [@​luhenry](https://redirect.github.com/luhenry) for adding support for riscv64 arch
##### 🚀 Enhancements
- Add riscv64 architecture support to platform detection [@​luhenry](https://redirect.github.com/luhenry) ([#​791](https://redirect.github.com/astral-sh/setup-uv/issues/791))
##### 🧰 Maintenance
- Delete .github/workflows/dependabot-build.yml [@​eifinger](https://redirect.github.com/eifinger) ([#​789](https://redirect.github.com/astral-sh/setup-uv/issues/789))
- Harden Dependabot build workflow [@​eifinger](https://redirect.github.com/eifinger) ([#​788](https://redirect.github.com/astral-sh/setup-uv/issues/788))
- Fix: check PR author instead of event sender for Dependabot detection [@​eifinger-bot](https://redirect.github.com/eifinger-bot) ([#​787](https://redirect.github.com/astral-sh/setup-uv/issues/787))
- chore: update known checksums for 0.10.9 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​783](https://redirect.github.com/astral-sh/setup-uv/issues/783))
- Add workflow to auto-build dist on Dependabot PRs [@​eifinger-bot](https://redirect.github.com/eifinger-bot) ([#​782](https://redirect.github.com/astral-sh/setup-uv/issues/782))
- chore: update known checksums for 0.10.8 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​779](https://redirect.github.com/astral-sh/setup-uv/issues/779))
- chore: update known checksums for 0.10.7 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​775](https://redirect.github.com/astral-sh/setup-uv/issues/775))
##### ⬆️ Dependency updates
- chore(deps): bump versions [@​eifinger](https://redirect.github.com/eifinger) ([#​792](https://redirect.github.com/astral-sh/setup-uv/issues/792))
- Bump actions/setup-node from 6.2.0 to 6.3.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​790](https://redirect.github.com/astral-sh/setup-uv/issues/790))
- Bump eifinger/actionlint-action from 1.10.0 to 1.10.1 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​778](https://redirect.github.com/astral-sh/setup-uv/issues/778))
### [`v7.4`](https://redirect.github.com/astral-sh/setup-uv/compare/v7.3.1...v7.4.0)
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.3.1...v7.4.0)
### [`v7.3.1`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v7.3.1): 🌈 fall back to VERSION_CODENAME when VERSION_ID is not available
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.3...v7.3.1)
##### Changes
This release adds support for running in containers like `debian:testing` or `debian:unstable`
##### 🐛 Bug fixes
- fix: fall back to VERSION\_CODENAME when VERSION\_ID is not available [@​eifinger-bot](https://redirect.github.com/eifinger-bot) ([#​774](https://redirect.github.com/astral-sh/setup-uv/issues/774))
##### 🧰 Maintenance
- chore: update known checksums for 0.10.6 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​771](https://redirect.github.com/astral-sh/setup-uv/issues/771))
- chore: update known checksums for 0.10.5 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​770](https://redirect.github.com/astral-sh/setup-uv/issues/770))
- chore: update known checksums for 0.10.4 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​768](https://redirect.github.com/astral-sh/setup-uv/issues/768))
- chore: update known checksums for 0.10.3 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​767](https://redirect.github.com/astral-sh/setup-uv/issues/767))
- chore: update known checksums for 0.10.2 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​765](https://redirect.github.com/astral-sh/setup-uv/issues/765))
- chore: update known checksums for 0.10.1 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​764](https://redirect.github.com/astral-sh/setup-uv/issues/764))
##### ⬆️ Dependency updates
- Bump github/codeql-action from 4.31.9 to 4.32.2 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​766](https://redirect.github.com/astral-sh/setup-uv/issues/766))
- Bump zizmorcore/zizmor-action from 0.4.1 to 0.5.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​763](https://redirect.github.com/astral-sh/setup-uv/issues/763))
### [`v7.3.0`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v7.3.0): 🌈 New features and bug fixes for activate-environment
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.3...v7.3)
##### Changes
This release contains a few bug fixes and a new feature for the activate-environment functionality.
##### 🐛 Bug fixes
- fix: warn instead of error when no python to cache [@​eifinger](https://redirect.github.com/eifinger) ([#​762](https://redirect.github.com/astral-sh/setup-uv/issues/762))
- fix: use --clear to create venv [@​eifinger](https://redirect.github.com/eifinger) ([#​761](https://redirect.github.com/astral-sh/setup-uv/issues/761))
##### 🚀 Enhancements
- feat: add venv-path input for activate-environment [@​eifinger](https://redirect.github.com/eifinger) ([#​746](https://redirect.github.com/astral-sh/setup-uv/issues/746))
##### 🧰 Maintenance
- chore: update known checksums for 0.10.0 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​759](https://redirect.github.com/astral-sh/setup-uv/issues/759))
- refactor: tilde-expansion tests as unittests and no self-hosted tests [@​eifinger](https://redirect.github.com/eifinger) ([#​760](https://redirect.github.com/astral-sh/setup-uv/issues/760))
- chore: update known checksums for 0.9.30 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​756](https://redirect.github.com/astral-sh/setup-uv/issues/756))
- chore: update known checksums for 0.9.29 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​748](https://redirect.github.com/astral-sh/setup-uv/issues/748))
##### 📚 Documentation
- Fix punctuation [@​pm-dev563](https://redirect.github.com/pm-dev563) ([#​747](https://redirect.github.com/astral-sh/setup-uv/issues/747))
##### ⬆️ Dependency updates
- Bump typesafegithub/github-actions-typing from 2.2.1 to 2.2.2 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​753](https://redirect.github.com/astral-sh/setup-uv/issues/753))
- Bump peter-evans/create-pull-request from 8.0.0 to 8.1.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​751](https://redirect.github.com/astral-sh/setup-uv/issues/751))
- Bump actions/checkout from 6.0.1 to 6.0.2 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​740](https://redirect.github.com/astral-sh/setup-uv/issues/740))
- Bump release-drafter/release-drafter from 6.1.0 to 6.2.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​743](https://redirect.github.com/astral-sh/setup-uv/issues/743))
- Bump eifinger/actionlint-action from 1.9.3 to 1.10.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​731](https://redirect.github.com/astral-sh/setup-uv/issues/731))
- Bump actions/setup-node from 6.1.0 to 6.2.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​738](https://redirect.github.com/astral-sh/setup-uv/issues/738))
### [`v7.3`](https://redirect.github.com/astral-sh/setup-uv/compare/v7.2.1...v7.3)
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.2.1...v7.3)
### [`v7.2.1`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v7.2.1): 🌈 update known checksums up to 0.9.28
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.2...v7.2.1)
##### Changes
##### 🧰 Maintenance
- chore: update known checksums for 0.9.28 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​744](https://redirect.github.com/astral-sh/setup-uv/issues/744))
- chore: update known checksums for 0.9.27 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​742](https://redirect.github.com/astral-sh/setup-uv/issues/742))
- chore: update known checksums for 0.9.26 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​734](https://redirect.github.com/astral-sh/setup-uv/issues/734))
- chore: update known checksums for 0.9.25 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​733](https://redirect.github.com/astral-sh/setup-uv/issues/733))
- chore: update known checksums for 0.9.24 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​730](https://redirect.github.com/astral-sh/setup-uv/issues/730))
##### 📚 Documentation
- Clarify impact of using actions/setup-python [@​eifinger](https://redirect.github.com/eifinger) ([#​732](https://redirect.github.com/astral-sh/setup-uv/issues/732))
##### ⬆️ Dependency updates
- Bump zizmorcore/zizmor-action from 0.3.0 to 0.4.1 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​741](https://redirect.github.com/astral-sh/setup-uv/issues/741))
### [`v7.2.0`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v7.2.0): 🌈 add outputs python-version and python-cache-hit
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.2...v7.2)
##### Changes
Among some minor typo fixes and quality of life features for developers of actions the main feature of this release are new outputs:
- **python-version:** The Python version that was set (same content as existing `UV_PYTHON`)
- **python-cache-hit:** A boolean value to indicate the Python cache entry was found
While implementing this it became clear, that it is easier to handle the Python binaries in a separate cache entry. The added benefit for users is that the "normal" cache containing the dependencies can be used in all runs no matter if these cache the Python binaries or not.
> \[!NOTE]\
> This release will invalidate caches that contain the Python binaries. This happens a single time.
##### 🐛 Bug fixes
- chore: remove stray space from UV\_PYTHON\_INSTALL\_DIR message [@​akx](https://redirect.github.com/akx) ([#​720](https://redirect.github.com/astral-sh/setup-uv/issues/720))
##### 🚀 Enhancements
- add outputs python-version and python-cache-hit [@​eifinger](https://redirect.github.com/eifinger) ([#​728](https://redirect.github.com/astral-sh/setup-uv/issues/728))
- Add action typings with validation [@​krzema12](https://redirect.github.com/krzema12) ([#​721](https://redirect.github.com/astral-sh/setup-uv/issues/721))
##### 🧰 Maintenance
- fix: use uv\_build backend for old-python-constraint-project [@​eifinger](https://redirect.github.com/eifinger) ([#​729](https://redirect.github.com/astral-sh/setup-uv/issues/729))
- chore: update known checksums for 0.9.22 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​727](https://redirect.github.com/astral-sh/setup-uv/issues/727))
- chore: update known checksums for 0.9.21 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​726](https://redirect.github.com/astral-sh/setup-uv/issues/726))
- chore: update known checksums for 0.9.20 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​725](https://redirect.github.com/astral-sh/setup-uv/issues/725))
- chore: update known checksums for 0.9.18 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​718](https://redirect.github.com/astral-sh/setup-uv/issues/718))
##### ⬆️ Dependency updates
- Bump peter-evans/create-pull-request from 7.0.9 to 8.0.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​719](https://redirect.github.com/astral-sh/setup-uv/issues/719))
- Bump github/codeql-action from 4.31.6 to 4.31.9 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​723](https://redirect.github.com/astral-sh/setup-uv/issues/723))
### [`v7.2`](https://redirect.github.com/astral-sh/setup-uv/compare/v7.1.6...v7.2)
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.1.6...v7.2)
### [`v7.1.6`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v7.1.6): 🌈 add OS version to cache key to prevent binary incompatibility
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.1.5...v7.1.6)
##### Changes
This release will invalidate your cache existing keys!
The os version e.g. `ubuntu-22.04` is now part of the cache key. This prevents failing builds when a cache got populated with wheels built with different tools (e.g. glibc) than are present on the runner where the cache got restored.
##### 🐛 Bug fixes
- feat: add OS version to cache key to prevent binary incompatibility [@​eifinger](https://redirect.github.com/eifinger) ([#​716](https://redirect.github.com/astral-sh/setup-uv/issues/716))
##### 🧰 Maintenance
- chore: update known checksums for 0.9.17 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​714](https://redirect.github.com/astral-sh/setup-uv/issues/714))
##### ⬆️ Dependency updates
- Bump actions/checkout from 5.0.0 to 6.0.1 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​712](https://redirect.github.com/astral-sh/setup-uv/issues/712))
- Bump actions/setup-node from 6.0.0 to 6.1.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​715](https://redirect.github.com/astral-sh/setup-uv/issues/715))
### [`v7.1.5`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v7.1.5): 🌈 allow setting `cache-local-path` without `enable-cache: true`
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.1.4...v7.1.5)
##### Changes
[#​612](https://redirect.github.com/astral-sh/setup-uv/pull/612) fixed a faulty behavior where this action set `UV_CACHE_DIR` even though `enable-cache` was `false`. It also fixed the cases were the cache dir is already configured in a settings file like `pyproject.toml` or `UV_CACHE_DIR` was already set. Here the action shouldn't overwrite or set `UV_CACHE_DIR`.
These fixes introduced an unwanted behavior: You can still set `cache-local-path` but this action didn't do anything. This release fixes that.
You can now use `cache-local-path` to automatically set `UV_CACHE_DIR` even when `enable-cache` is `false` (or gets set to false by default e.g. on self-hosted runners)
```yaml
- name: This is now possible
uses: astral-sh/setup-uv@v7
with:
enable-cache: false
cache-local-path: "/path/to/cache"
```
##### 🐛 Bug fixes
- allow cache-local-path w/o enable-cache [@​eifinger](https://redirect.github.com/eifinger) ([#​707](https://redirect.github.com/astral-sh/setup-uv/issues/707))
##### 🧰 Maintenance
- set biome files.maxSize to 2MiB [@​eifinger](https://redirect.github.com/eifinger) ([#​708](https://redirect.github.com/astral-sh/setup-uv/issues/708))
- chore: update known checksums for 0.9.16 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​706](https://redirect.github.com/astral-sh/setup-uv/issues/706))
- chore: update known checksums for 0.9.15 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​704](https://redirect.github.com/astral-sh/setup-uv/issues/704))
- chore: use `npm ci --ignore-scripts` everywhere [@​woodruffw](https://redirect.github.com/woodruffw) ([#​699](https://redirect.github.com/astral-sh/setup-uv/issues/699))
- chore: update known checksums for 0.9.14 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​700](https://redirect.github.com/astral-sh/setup-uv/issues/700))
- chore: update known checksums for 0.9.13 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​694](https://redirect.github.com/astral-sh/setup-uv/issues/694))
- chore: update known checksums for 0.9.12 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​693](https://redirect.github.com/astral-sh/setup-uv/issues/693))
- chore: update known checksums for 0.9.11 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​688](https://redirect.github.com/astral-sh/setup-uv/issues/688))
##### ⬆️ Dependency updates
- Bump peter-evans/create-pull-request from 7.0.8 to 7.0.9 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​695](https://redirect.github.com/astral-sh/setup-uv/issues/695))
- bump dependencies [@​eifinger](https://redirect.github.com/eifinger) ([#​709](https://redirect.github.com/astral-sh/setup-uv/issues/709))
- Bump github/codeql-action from 4.30.9 to 4.31.6 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​698](https://redirect.github.com/astral-sh/setup-uv/issues/698))
- Bump zizmorcore/zizmor-action from 0.2.0 to 0.3.0 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​696](https://redirect.github.com/astral-sh/setup-uv/issues/696))
- Bump eifinger/actionlint-action from 1.9.2 to 1.9.3 @​[dependabot\[bot\]](https://redirect.github.com/apps/dependabot) ([#​690](https://redirect.github.com/astral-sh/setup-uv/issues/690))
### [`v7.1.4`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v7.1.4): 🌈 Fix libuv closing bug on Windows
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.1.3...v7.1.4)
##### Changes
This release fixes the bug `Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)` on Windows runners
##### 🐛 Bug fixes
- Wait 50ms before exit to fix libuv bug [@​eifinger](https://redirect.github.com/eifinger) ([#​689](https://redirect.github.com/astral-sh/setup-uv/issues/689))
##### 🧰 Maintenance
- chore: update known checksums for 0.9.10 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​681](https://redirect.github.com/astral-sh/setup-uv/issues/681))
- chore: update known checksums for 0.9.9 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​679](https://redirect.github.com/astral-sh/setup-uv/issues/679))
### [`v7.1.3`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v7.1.3): 🌈 Support act
[Compare Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.1.2...v7.1.3)
##### Changes
This bug fix release adds support for <https://github.com/nektos/act>
It was previously broken because of a too new `undici` version and TS transpilation target.
Compatibility with act is now automatically tested.
##### 🐛 Bug fixes
- use old undici and ES2022 target for act support [@​eifinger](https://redirect.github.com/eifinger) ([#​678](https://redirect.github.com/astral-sh/setup-uv/issues/678))
##### 🧰 Maintenance
- chore: update known checksums for 0.9.8 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​677](https://redirect.github.com/astral-sh/setup-uv/issues/677))
- chore: update known checksums for 0.9.7 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​671](https://redirect.github.com/astral-sh/setup-uv/issues/671))
- chore: update known checksums for 0.9.6 @​[github-actions\[bot\]](https://redirect.github.com/apps/github-actions) ([#​670](https://redirect.github.com/astral-sh/setup-uv/issues/670))
##### 📚 Documentation
- Correct description of `cache-dependency-glob` [@​allanlewis](https://redirect.github.com/allanlewis) ([#​676](https://redirect.github.com/astral-sh/setup-uv/issues/676))
</details>
<details>
<summary>codecov/codecov-action (codecov/codecov-action)</summary>
### [`v7.0.0`](https://redirect.github.com/codecov/codecov-action/releases/tag/v7.0.0)
[Compare Source](https://redirect.github.com/codecov/codecov-action/compare/v7.0.0...v7.0.0)
⚠️ Due to migration issues with keybase, we are unable to update our keys under the `codecovsecurity` account. We have deleted the account and are using `codecovsecops` with the original gpg key
##### What's Changed
- ci: remove Enforce License Compliance workflow by [@​thomasrockhu-codecov](https://redirect.github.com/thomasrockhu-codecov) in [#​1950](https://redirect.github.com/codecov/codecov-action/pull/1950)
- chore(release): 7.0.0 by [@​thomasrockhu-codecov](https://redirect.github.com/thomasrockhu-codecov) in [#​1957](https://redirect.github.com/codecov/codecov-action/pull/1957)
**Full Changelog**: <https://github.com/codecov/codecov-action/compare/v6.0.1...v7.0.0>
### [`v7`](https://redirect.github.com/codecov/codecov-action/compare/v6.0.2...v7.0.0)
[Compare Source](https://redirect.github.com/codecov/codecov-action/compare/v6.0.2...v7.0.0)
### [`v6.0.2`](https://redirect.github.com/codecov/codecov-action/releases/tag/v6.0.2)
[Compare Source](https://redirect.github.com/codecov/codecov-action/compare/v6.0.1...v6.0.2)
This is a copy of the `v7.0.0` release to make updates easier
##### What's Changed
- ci: remove Enforce License Compliance workflow by [@​thomasrockhu-codecov](https://redirect.github.com/thomasrockhu-codecov) in [#​1950](https://redirect.github.com/codecov/codecov-action/pull/1950)
- chore(release): 7.0.0 by [@​thomasrockhu-codecov](https://redirect.github.com/thomasrockhu-codecov) in [#​1957](https://redirect.github.com/codecov/codecov-action/pull/1957)
**Full Changelog**: <https://github.com/codecov/codecov-action/compare/v6.0.1...v6.0.2>
</details>
<details>
<summary>actions/python-versions (python)</summary>
### [`v3.14.6`](https://redirect.github.com/actions/python-versions/releases/tag/3.14.6-27283001424): 3.14.6
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.14.5-25647354415...3.14.6-27283001424)
Python 3.14.6
### [`v3.14.5`](https://redirect.github.com/actions/python-versions/releases/tag/3.14.5-25647354415): 3.14.5
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.14.4-25113653268...3.14.5-25647354415)
Python 3.14.5
### [`v3.14.4`](https://redirect.github.com/actions/python-versions/releases/tag/3.14.4-25113653268): 3.14.4
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.14.3-21673711214...3.14.4-25113653268)
Python 3.14.4
### [`v3.14.3`](https://redirect.github.com/actions/python-versions/releases/tag/3.14.3-21673711214): 3.14.3
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.14.2-20014991423...3.14.3-21673711214)
Python 3.14.3
### [`v3.14.2`](https://redirect.github.com/actions/python-versions/releases/tag/3.14.2-20014991423): 3.14.2
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.14.1-19879739908...3.14.2-20014991423)
Python 3.14.2
### [`v3.14.1`](https://redirect.github.com/actions/python-versions/releases/tag/3.14.1-19879739908): 3.14.1
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.14.0-18313368925...3.14.1-19879739908)
Python 3.14.1
### [`v3.14.0`](https://redirect.github.com/actions/python-versions/releases/tag/3.14.0-18313368925): 3.14.0
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.14-27320626148...3.14.0-18313368925)
Python 3.14.0
### [`v3.13.14`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.14-27320626148): 3.13.14
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.13-27225391538...3.13.14-27320626148)
Python 3.13.14
### [`v3.13.13`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.13-27225391538): 3.13.13
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.12-21673645133...3.13.13-27225391538)
Python 3.13.13
### [`v3.13.12`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.12-21673645133): 3.13.12
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.11-20014977833...3.13.12-21673645133)
Python 3.13.12
### [`v3.13.11`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.11-20014977833): 3.13.11
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.10-19879712315...3.13.11-20014977833)
Python 3.13.11
### [`v3.13.10`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.10-19879712315): 3.13.10
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.9-18515951191...3.13.10-19879712315)
Python 3.13.10
### [`v3.13.9`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.9-18515951191): 3.13.9
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.8-18331000654...3.13.9-18515951191)
Python 3.13.9
### [`v3.13.8`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.8-18331000654): 3.13.8
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.7-16980743123...3.13.8-18331000654)
Python 3.13.8
### [`v3.13.7`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.7-16980743123): 3.13.7
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.6-16792117939...3.13.7-16980743123)
Python 3.13.7
### [`v3.13.6`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.6-16792117939): 3.13.6
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.5-15601068749...3.13.6-16792117939)
Python 3.13.6
### [`v3.13.5`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.5-15601068749): 3.13.5
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.4-15433317575...3.13.5-15601068749)
Python 3.13.5
### [`v3.13.4`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.4-15433317575): 3.13.4
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.3-14344076652...3.13.4-15433317575)
Python 3.13.4
### [`v3.13.3`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.3-14344076652): 3.13.3
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.2-13708744326...3.13.3-14344076652)
Python 3.13.3
### [`v3.13.2`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.2-13708744326): 3.13.2
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.1-13437882550...3.13.2-13708744326)
Python 3.13.2
### [`v3.13.1`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.1-13437882550): 3.13.1
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.13.0-13707372259...3.13.1-13437882550)
Python 3.13.1
### [`v3.13.0`](https://redirect.github.com/actions/python-versions/releases/tag/3.13.0-13707372259): 3.13.0
[Compare Source](https://redirect.github.com/actions/python-versions/compare/3.12.13-27650778726...3.13.0-13707372259)
Python 3.13.0
</details>
<details>
<summary>softprops/action-gh-release (softprops/action-gh-release)</summary>
### [`v3.0.1`](https://redirect.github.com/softprops/action-gh-release/releases/tag/v3.0.1)
[Compare Source](https://redirect.github.com/softprops/action-gh-release/compare/v3.0.0...v3.0.1)
#### 3.0.1
- maintenance release with updated dependencies
</details>
---
### Configuration
📅 **Schedule**: (UTC)
- Branch creation
- At any time (no schedule defined)
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box
---
This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/kenn-io/agentsview).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDIuMiIsInVwZGF0ZWRJblZlciI6IjQzLjI0Mi4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
Co-authored-by: renovate[bot] <renovate[bot]@users.noreply.github.com>
Some users cannot rely on passwordless SSH on fleet nodes, but still need collector-owned ingestion of raw session files. This adds a daemon-backed HTTP transport for persisted remote hosts so Tailscale-secured nodes can expose authenticated remote-sync targets and archive endpoints while SSH remains the default and ad hoc host sync stays SSH-only. The implementation keeps the collector-owns-the-archive model: configured API sync requests resolve transport, URL, and token from server config instead of trusting request payloads; daemon endpoints authenticate before host and CORS checks; and HTTP archive import reuses the extraction, remap, and skip-cache protections shared with SSH. Reviewers should focus on the shared remotesync package, the server remote-sync routes and auth middleware, and the three dispatch paths for CLI, API-triggered, and interval remote sync. The detached daemon idle-timeout setting applies to serve --background; supervised daemons remain always-on under their process manager. Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
Now that the provider-facade parser migration (kenn-io#877–kenn-io#885) has landed on `main`, this removes the dead code and transitional tests it left behind. Everything here was confirmed to have no remaining production callers; nothing changes runtime behavior. ## What's removed **Parser dead code** (`refactor(parser)`): - Per-agent `*StorageSessionIDs` / `Discover*` / `Find*SourceFile` wrappers in `discovery.go` for OpenCode, Kilo, IcodeMate, and MiMoCode. The OpenCode-format provider calls the shared helpers directly; IcodeMate's wrappers were re-added during the rebase and never wired in. - `ProviderSupportsSourceDiscovery` (added but never wired; the same check is inlined at its one intended call site). - `ListAiderRunMetas` / `AiderRunMeta` (engine caller replaced by the provider run fan-out). - `ValidRole` (superseded by the `db` package's message-role validation). - The never-set `JSONLSourceSetOptions.DisplayPath` field and the unused `WithFingerprintKey` setter. - The `SiblingMetadataSourceSet` type/options/constructor/methods, superseded by the `WithCompanionFiles` path. The two fingerprint helpers that path shares are kept. **Migration guard tests** (`test(parser)`): - The per-agent `*Factory(Replaces|Replace)LegacyAdapter` factory-existence checks, subsumed by `TestProviderRegistryMirrorsAgentRegistry` and `TestProviderFactoryLookupRejectsMissingAgent`. - The `*OwnLegacyEntrypoints` tests, which made source-text assertions the repo style forbids and guarded a shim re-entry that can no longer happen. - `TestProviderMigrationModesUseOnlyFinalModes` and `assertNotLegacyProvider`, which policed transitional state that no longer exists. **Sync engine** (`refactor(sync)`): - The no-op `classifyContainerPath` and the now-unreachable `classifyOnePath` plus its dead call site; changed-path classification flows solely through `classifyCodexIndexPath` and `classifyProviderChangedPath`. - The unreachable non-provider branch of `countDBBackedProgressTotal`. ## Where reviewers should look Five family tests additionally asserted a provider's `Capabilities()` manifest (Cortex, Gptme, WorkBuddy, Zed, Shelley). That per-provider coverage is not duplicated elsewhere, so those tests are **kept** and renamed to `Test<Agent>ProviderCapabilities` rather than deleted — worth confirming that call was right. The `sibling_metadata_source_set.go` reduction is the other spot to check: the dead source-set cluster is gone but the live `siblingMetadataFileInfo` / `addSiblingMetadataFingerprintPart` helpers used by `WithCompanionFiles` are preserved. Co-authored-by: Marius van Niekerk <mariusvniekerk@users.noreply.github.com>
Windows CI profiling showed the full Go test job was dominated by expensive fixture setup, especially DuckDB mirror setup and repeated SQLite/server fixtures, rather than the core query work. This keeps full Windows Go coverage in place and fixes the hot paths instead: DuckDB pricing sync filters unchanged rows and writes changed pricing rows in batches, analytics/store fixtures share read-only mirrors where practical, query-only and schema-only DuckDB fixtures use in-memory mirrors, and file-backed sync tests remain where persistence or reopen behavior is under review. DuckDB connections are explicitly configured with SET threads TO <effective CPUs> so DuckDB can use the process's available cores for query execution while agentsview still keeps one database/sql connection per mirror file to avoid file-lock contention. Several high-cost SQLite/server tests now seed through batch writes or shared validation fixtures while preserving the same endpoint/query assertions, and settings-mode tests avoid the unrelated GitHub CLI token fallback covered elsewhere. The PR also keeps the scheduled MSYS2 workflow fix that creates the frontend embed stub with PowerShell-native commands, plus workflow guard coverage asserting the full Go suite remains wired on CI/MSYS2. Reviewers should focus on the fixture changes around DuckDB sync/schema setup, batched SQLite seeds, and the Windows workflow guard. Co-authored-by: Wes McKinney <wesm@users.noreply.github.com>
Captures the diagnostic notes from the 2026-06-28 session that traced the "no usage stats for recent days" symptom to fork models missing from the upstream pricing catalog. The fix itself (INSERT pricing rows + OpenRouter fallback source) lives in earlier commits; this file documents the reproduction steps and root-cause hypothesis so a future reader does not have to re-derive them.
Brings in 30 commits landed on kenn-io/agentsview since the fork base (kenn-io#888 at 746242d), notably: - kenn-io#905 fix(usage) accept duration syntax for --since/--until (the upstream fix for the same bug we worked around elsewhere) - kenn-io#909 HTTP daemon remote sync - kenn-io#898 PG serve session curation and insight persistence - kenn-io#924 remove provider-facade migration remnants - kenn-io#876-kenn-io#881, kenn-io#885 parser provider migration to a reusable source-set framework (large internal refactor, no observable behaviour change) - kenn-io#916 ci Windows checks + DuckDB tests - kenn-io#910 make install via atomic rename - kenn-io#913 Claude.ai export attachments - kenn-io#917 WSL paths in desktop env - kenn-io#918 insights in-page help - kenn-io#915 Renovate group update Conflicts resolved: - Makefile: union of upstream targets (test-s3 added) with the fork's new release-universal-apple, build-local-apple-silicon, run-offline, run-offline-universal targets.
Drop a sync-upstream.yml in .github/workflows/ that runs once a day at 06:00 UTC, detects how far behind fork:main has drifted from kenn-io/agentsview:main, and either opens or updates a "sync: upstream main" PR when there is drift. The PR body lists every commit being brought in (reverse-chronological, short hash + subject) plus a review checklist, so a human can scan the change set and click through the diff without leaving the PR. If an existing sync PR is already open, the workflow reuses it and just refreshes the body, so a missed review does not spawn a new PR per day. The job is gated to only run on the godlockin/agentsview repository (the fork); manual workflow_dispatch still works from any fork for local debugging. Fork-specific paths (Makefile, frontend, internal/config, internal/server, internal/pricing, internal/db/usage_test, sync workflow itself, investigations) are checked out from HEAD on top of the upstream branch before commit, so a clean conflict is raised if any of them collide with upstream changes instead of silently overwriting fork work. Security: the workflow only consumes fixed-shape values (event_name, repository, secrets.GITHUB_TOKEN, the rev-list --count drift count) and treats all commit subjects as untrusted text. Commit subjects are routed through `git log --no-color` + `sed -e 's/[[:cntrl:]]//g'` into a body file and never reach a `run:` script, so a crafted upstream subject cannot inject shell. The drift count is interpolated with printf %d, which fails closed if rev-list ever returns non-numeric.
mjacobs
pushed a commit
that referenced
this pull request
Jul 24, 2026
Restore synchronous startup fallback seeding while keeping the initial network refresh asynchronous, and remove the periodic custom-pricing map write that raced with request reads. Track OpenRouter aliases so refreshes can remove obsolete bare names locally and in PostgreSQL, preserve ordered LiteLLM precedence, and accept free-model zero prices. VALID (fixed): #1, #2, #3, kenn-io#4, kenn-io#5 INVALID (dismissed): none PEDANTIC (skipped): none
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR brings together the agent work done against the
godlockin/agentsview fork while it was following upstream by
30+ commits. It layers seven functional changes plus a
daily upstream-sync workflow on top of
kenn-io/agentsview@12d3b03.Changes
Build & packaging
feat(build):new Makefile targetsbuild-local-apple-silicon,release-universal-apple(lipo arm64+amd64),run-offline,and
run-offline-universal. Apple Silicon users can nowbuild native arm64 in one command and a universal binary
with another. Offline runs disable telemetry, the update
check, and bind to loopback.
feat(config):default server port changed from8080to
9765to free the well-known HTTP-alt port for othertools. Explicit
--portflags andportinconfig.tomlare unaffected.
Frontend
feat(frontend):self-host Inter and JetBrains Mono woff2subsets under
frontend/public/fonts/. Vite copies theminto the SPA bundle, so the embedded Go binary serves them
from the same origin. Removed
fonts.googleapis.comandfonts.gstatic.comfromindex.htmland from the CSPemitted by
internal/server/server.go. The local-first UIno longer reaches out to any CDN at runtime.
Pricing & cost tracking
feat(pricing):add OpenRouter as a second backgroundpricing source alongside LiteLLM. The seed-and-refresh
loop in
cmd/agentsview/usage.gonow walksDefaultPricingSources()and merges whichever catalogsrespond (first non-zero field wins per
model_pattern).Each fetch failure is logged but never aborts the loop.
test(db):regression tests for two paths: custom modelpatterns inserted via
UpsertModelPricingand unknownmodels that should still count tokens but not produce cost.
Both guard against the upstream time-window SQL from
issue usage daily --since silently returns a wrong/empty window for duration inputs like 7d kenn-io/agentsview#904 silently dropping recent days.
Documentation
docs(investigations):a note that records the diagnostictrail for the "no usage stats for recent days" symptom
and the fix. Keeps the next reader from re-deriving it.
CI
ci(fork):add.github/workflows/sync-upstream.yml. Adaily 06:00 UTC job detects drift between this fork and
kenn-io/agentsview:main, then opens (or reuses) async: upstream mainPR with a per-commit summary so ahuman can review. Manual
workflow_dispatchis supported.Fork-private paths are preserved on top of the upstream
branch before commit, so any collision is a real PR
conflict instead of a silent overwrite.
Upstream sync
kenn-io/agentsviewbetween theoriginal fork base (
#888) and12d3b03were mergedin (
f47c52a). Notable:fix(usage)accept durationsyntax for
--since/--until(the upstream fix for issueusage daily --since silently returns a wrong/empty window for duration inputs like 7d kenn-io/agentsview#904),
Add HTTP daemon remote sync(Add HTTP daemon remote sync kenn-io/agentsview#909),Remove provider-facade migration remnants(Remove provider-facade migration remnants kenn-io/agentsview#924), the parserprovider migration to a reusable source-set framework
(feat(parser): provider facade core kenn-io/agentsview#876-refactor(parser): require explicit provider factories + migration cleanup kenn-io/agentsview#885), the PG serve session curation and insight
persistence (feat(postgres): implement PG serve session curation and insight persistence kenn-io/agentsview#898), and the rest of the recent
merge-train.
Verification
go test -short ./...passes after the upstream merge.make e2eruns 71 specs in ~40s: 70 passed, 1 skipped(DuckDB smoke that needs
make e2e-duckdb), 0 failed.dist/agentsview-darwin-arm64starts in ~160ms on the local M-series Mac and serves
the existing
~/.agentsview/sessions.dbwith thenew pricing rows.
Notes
9765is the only behaviorchange that is visible to users who were not following
the fork; everyone else can keep their
port = 8080in
config.toml.model_pricingtable for each of the four fork-privatemodels (
MiniMax-M3,MiniMax-M2.7,deepseek-v4-flash,deepseek-v4-pro) so the June usage board reports costinstead of $0. The OpenRouter source added in this PR
will keep them priced once a public catalog lists them,
until then
~/.agentsview/config.toml[custom_model_pricing]is the long-term home.