Commit graph

1553 commits

Author SHA1 Message Date
Tejas Chopra
7c1cff226f
Merge pull request #266 from ipapapa/fix/31-qdrant-env-vars
fix(memory): resolve Qdrant connection from HEADROOM_QDRANT_* env vars (#31)
2026-04-25 13:33:48 -07:00
chopratejas
4429a11166 Merge remote-tracking branch 'origin/main' into rust-rewrite
# Conflicts:
#	headroom/proxy/server.py
2026-04-25 13:01:37 -07:00
ipapapa
d3c37d7098 feat(memory): resolve Qdrant connection from HEADROOM_QDRANT_* env vars (#31)
Adds `HEADROOM_QDRANT_URL`, `_HOST`, `_PORT`, `_API_KEY`, `_HTTPS`,
`_PREFER_GRPC`, `_GRPC_PORT` support across the memory stack:

- `headroom/memory/qdrant_env.py`: shared resolver helper with
  explicit-arg > env > default precedence (URL wins over host/port;
  booleans parsed via standard truthy set).
- `memory/easy.py`, `backends/{mem0,direct_mem0}.py`,
  `proxy/memory_handler.py`: call the resolver so
  `Memory(backend="qdrant-neo4j")`, `Mem0Config`, and the proxy's
  `MemoryConfig` all honor the same env keys.
- `proxy/models.py` + `proxy/server.py`: `ProxyConfig` picks up the
  same keys so hosted Qdrant (e.g. Qdrant Cloud) works without code
  changes.
- `cli/proxy.py`: adds `--memory-qdrant-{url,host,port,api-key}`
  flags that override the env when present.
- `tests/test_memory/test_qdrant_env.py`: unit coverage for
  precedence, URL-vs-host/port, boolean parsing, and unset defaults.
- `CHANGELOG.md`: documented under [Unreleased] / Added.

Explicit constructor arguments still win; unset env keeps the existing
localhost:6333 defaults, so this is backwards-compatible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:16:16 -07:00
Tejas Chopra
6dede0c2b4
Merge pull request #262 from gglucass/fix/traffic-learner-error-recovery
fix(memory): collapse and decay error_recovery patterns in MEMORY.md
2026-04-24 20:17:41 -07:00
Tejas Chopra
e635bab336
Merge pull request #263 from gglucass/chore/renormalize-line-endings
chore: renormalize line endings to LF
2026-04-24 20:17:09 -07:00
chopratejas
0414cb70e4 feat(rust): scaffold workspace + parity harness (phase-0)
Bootstrap the Rust port of Headroom. Additive only — no existing Python
code modified. Ships the workshop, not the widgets.

Layout
  Cargo.toml (workspace) + rust-toolchain.toml
  crates/headroom-core    — transform library, stub only
  crates/headroom-proxy   — axum binary, /healthz only
  crates/headroom-py      — PyO3 cdylib, exposes headroom._core.hello()
  crates/headroom-parity  — Rust-vs-Python oracle harness + parity-run CLI

Tooling
  Makefile: test, test-parity, bench, build-proxy, build-wheel, fmt, lint
  .github/workflows/rust.yml: test, wheels (linux/mac), audit, parity-nightly
  deny.toml for cargo-deny

Parity corpus
  tests/parity/recorder.py + scripts/record_fixtures.py
  125 recorded fixtures across 5 leaf transforms (ccr, tokenizer,
  log_compressor, diff_compressor, cache_aligner)

Docs
  RUST_DEV.md — developer setup and workspace reference
  docs/spec/022-rust-migration.md — migration plan and stage breakdown

.gitignore: whitelist scripts/record_fixtures.py; ignore target/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 13:39:48 -07:00
Tejas Chopra
32152f5202
Merge pull request #246 from Kayzo/fix/memory-batch-onnx-sqlitevec
fix(memory): batch onnx embeddings and sqlite-vec ops
2026-04-24 07:56:59 -07:00
Garm
ac493cba1e test(memory): raise patch coverage from 83% to 98% on error_recovery fixes
26 new tests covering:

- TestNormalizeBashForHash — empty string, no-suffix, head/tail strip,
  trailing context flags, stderr redirect, chain-boundary truncation
- TestParseIsoTimestamp — None, empty, non-string, invalid format,
  naive (assumed UTC), tz-aware preserved
- TestLoadPersistedPatternsTimestamps — reads first_seen_at/last_seen_at
  from metadata, falls back to created_at, collision-merges timestamps
  and bumps importance to max, handles malformed JSON and non-numeric
  importance cells gracefully
- TestBumpPersistsLastSeenAt — verifies _bump_persisted_evidence writes
  $.last_seen_at into metadata JSON
- TestHydrateLegacyRow — legacy rows without category, rows with
  unknown/invalid category, rows with empty content
- TestCollectAllPatternsTimestamps — in-session re-sighting bumps
  last_seen_at past stale persisted timestamp
- TestRefineErrorRecovery (additions) — refine-empties-section skips
  recommendation entirely, OSError during re-validation keeps the row,
  Read patterns without success_path skip re-validation cleanly

Remaining uncovered lines in patch (4): defensive exception handlers
in _hydrate_persisted_state (sqlite connect OperationalError, asyncio
thread exception, JSONDecodeError on metadata) that require heavy
mocking for marginal value.

91 tests pass, ruff + mypy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:53:51 +02:00
Garm
efd2ac1ca4 chore: renormalize line endings to LF
`.gitattributes` declares `*.py text eol=lf` and `*.sh text eol=lf`, but
74 files (73 .py, 1 .sh) are stored in the index with CRLF line endings,
violating that contract. Every macOS/Linux clone reports these files as
"modified" on fresh checkout because git's diff engine sees the stored
bytes don't match the attribute contract, even though the working tree
and index match byte-for-byte.

Running `git add --renormalize .` rewrites each affected blob so the
stored form matches the attribute declaration. No semantic changes —
every affected file's diff is "N insertions, N deletions" with inserts
and deletes being the same lines modulo line endings.

Follow-up commit adds `.git-blame-ignore-revs` so `git blame` / GitHub
blame skip this mechanical commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:33:30 +02:00
Garm
879064fea5 fix(memory): collapse and decay error_recovery patterns in MEMORY.md
The Learned: error recovery section was bloating with stale, near-duplicate,
and contradictory entries because the dedup key was the literal rendered
bullet text and there was no TTL or re-validation.

- Normalize the hash key for error_recovery patterns. Read recoveries key
  on (basename(error_path), basename(success_path)); Bash recoveries strip
  volatile suffixes (| tail -N, 2>&1, etc.) and hash only the primary
  command before the first | or &&. Non-error-recovery categories keep
  literal-content hashing.
- Stamp first_seen_at / last_seen_at on every pattern; bump both in
  _bump_persisted_evidence via json_set. Stored in metadata JSON — no
  schema change.
- Refine at render time (error_recovery only): drop rows not re-observed
  in 21 days, re-validate Read success paths against the filesystem,
  collapse same-error_path-with-multiple-targets into one "use Glob/Grep
  first" bullet, rank by evidence_count * 0.5 ** (days/5), cap at 15
  bullets.

15 new tests (TestNormalizedHash, TestRefineErrorRecovery). Full suite:
526 passed, 1 skipped. Ruff + mypy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:20:51 +02:00
Kayzo
f5cea7c51e fix(memory): batch onnx embeddings and sqlite-vec ops
Make the ONNX + sqlite-vec memory path truly batched.

Batch ONNX embed_batch calls, batch sqlite-vec index/remove work under a single cached connection, and update MCP warm-up to use batch embed/save/index flows.

Add focused regression tests for ONNX batching, sqlite-vec single-connection batch behavior, and MCP warm-up batching.

Skip the MCP-specific test when optional MCP dependencies are not installed.

Refs #240
2026-04-24 09:49:28 +00:00
JerrettDavis
301563f11d test(init): make verbose stderr assertion click-version-agnostic
The test added in bb91cfe used ``CliRunner(mix_stderr=False)`` to keep
stderr separate from stdout for assertion purposes. That parameter was
removed in Click 8.2. The repo's pyproject.toml pins ``click>=8.1.0``,
so either Click 8.1 (needs mix_stderr) or Click 8.2+ (must omit it)
could appear in CI.

Switch to reading ``result.stderr`` when the attribute is populated,
falling back to ``result.output`` (combined stream) otherwise. This
covers every Click 8.x variant without branching on the installed
version.

Verified in the Docker e2e image (Click 8.3.3): all 45 tests in
tests/test_cli/test_init_cli.py pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 16:19:17 -05:00
JerrettDavis
bb91cfe688 feat(init): add -v/--verbose flag for debug diagnostics
When users hit an init regression it's opaque why: no visible state
about which agents were probed, which paths were written, which
subprocesses ran. Add a top-level flag to ``headroom init`` that routes
debug-level logging from the ``headroom.cli.init`` logger to stderr.

Instrumented decision points:

* detect_init_targets / _probe_init_targets — scope + per-target
  shutil.which result
* _write_json, _ensure_claude_hooks, _ensure_copilot_hooks,
  _ensure_codex_hooks, _ensure_codex_provider — file paths being written
* _apply_user_env — chosen scope (windows vs unix) and env-var keys
* _run_checked — each subprocess command + exit code + truncated
  stdout/stderr (useful when ``claude plugin install`` fails)
* _run_init_targets — target dispatch order and resolved profile
* top-level init callback — all flag values and invoked_subcommand

Log output goes to stderr so stdout stays clean for pipes. The handler
attached by ``_enable_verbose_logging`` is idempotent - nested
subcommand invocations don't duplicate output. The logger does not
propagate to the root logger, so enabling ``headroom init -v`` does not
affect the rest of the process.

The flag is declared on the parent Click group. Subcommands (claude,
codex, copilot, openclaw) inherit the enabled logger automatically
because the group callback runs before dispatch.

Added tests cover:

* ``init -v`` emits the expected markers to stderr, including
  ``detect_init_targets``, ``global_scope=True``, and each agent name
* ``_enable_verbose_logging`` is safe to call repeatedly (handler
  remains singular)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:59:57 -05:00
JerrettDavis
4c062319f0 fix(init): guide users when no agents are auto-detected
Fixes #245.

Running ``headroom init -g`` with no supported agents on PATH previously
produced a single-line ClickException that read like the -g flag had
been removed:

    Error: No supported user init targets were auto-detected. Specify one explicitly.

This left reporter #245 concluding the feature was gone. Replace that
message with a structured diagnostic that:

* states which scope (user / local) was tried
* lists every target probed (claude, codex, copilot, openclaw) and the
  shutil.which() result for each
* explicitly confirms that -g / --global is still a supported flag
* shows the concrete per-target invocation for each agent
  (``headroom init -g claude``, ...) so the user knows the escape hatch

The implementation factors ``detect_init_targets`` into a ``_probe_init_targets``
helper that returns ``[(name, which_result)]``. ``detect_init_targets``
keeps its existing signature so the test suite and external imports
aren't broken; the new helper backs both the auto-detection path and
the diagnostic error formatter.

Unit tests in tests/test_cli/test_init_cli.py cover:
* the end-to-end message shape (structural markers + every target name +
  the example invocation)
* the local-scope variant omitting global-only agents (copilot / openclaw)
* that found binaries are surfaced with their absolute path so users can
  debug cases where shutil.which returns an unexpected result

No behavior change when at least one target is detected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:55:12 -05:00
Tejas Chopra
429e1d194b
Merge pull request #244 from gglucass/pr/harden-livez-backpressure-test
test(backpressure): harden livez-under-backpressure against CI jitter
2026-04-23 12:38:41 -07:00
Tejas Chopra
ab0ee652e1
Merge pull request #254 from JerrettDavis/fix/unwrap-codex-restores-config
fix(wrap): unwrap codex restores prior config.toml
2026-04-23 09:20:21 -07:00
JerrettDavis
281bc171dc fix(wrap): unwrap codex restores prior config.toml
`headroom wrap codex` injects a `model_provider = "headroom"` block
plus a `[model_providers.headroom]` table into `~/.codex/config.toml`
so Codex routes both HTTP and WebSocket traffic through the proxy. The
matching `unwrap codex` subcommand did not exist, so the injected
block stayed in `config.toml` forever — the moment the proxy stopped,
Codex (CLI and macOS app) started erroring with
`Missing environment variable: OPENAI_API_KEY`, and users had to hand-
edit the file to recover.

Fix:

* `_inject_codex_provider_config` now snapshots the pre-wrap file to
  `~/.codex/config.toml.headroom-backup` before the first modification
  and leaves that snapshot untouched on subsequent wrap runs. The
  injection is also rewritten to use two self-contained marker-
  delimited blocks (top-level key and provider table) so stripping
  them never consumes user content that sits between them.
* `_inject_memory_mcp_config` takes the same snapshot, so
  `wrap codex --memory` without a full provider injection is still
  fully reversible.
* New `_restore_codex_provider_config` helper and `unwrap codex`
  click command:
  * backup present → restore byte-for-byte and delete the backup;
  * backup absent but Headroom block present → strip the block and
    keep surrounding user content;
  * config contained only Headroom content → remove the file so
    Codex falls back to defaults;
  * nothing to undo → safe no-op.

Codex is the only wrap target that modifies a persistent user config
file: claude/aider/cursor/copilot all go through env vars or project-
scoped files only, so this bug was unique to Codex.

Tests:

* `tests/test_cli/test_wrap_codex.py` adds 20 new cases covering the
  strip/snapshot helpers directly, round-trip idempotency of
  wrap → wrap → unwrap, handling of malformed prior configs, and
  end-to-end CliRunner invocations of `headroom wrap codex
  --prepare-only` / `headroom unwrap codex` against a temp `$HOME`.
* All 153 existing `tests/test_cli/` tests continue to pass.

Plugin manifest versions were re-synced from `pyproject.toml` (0.11.2)
by the `sync-plugin-versions` pre-commit hook; the previous values
(0.10.3) had drifted.

Reported by @raenaryl in Discord on 0.6.3; confirmed still broken on
current `main` (0.11.x).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 11:13:54 -05:00
Tejas Chopra
8d4f21b3c6
Merge pull request #247 from gglucass/fix/learn-prior-block-to-llm
fix(learn): show prior patterns block to LLM to prevent dangling cross-references
2026-04-23 09:09:26 -07:00
Tejas Chopra
f2c6e9065f
Merge pull request #248 from gglucass/fix/turn-id-strip-cache-control
fix(proxy): strip cache_control before hashing turn_id
2026-04-23 09:08:48 -07:00
JD Davis
74f0c24ddd
Merge branch 'chopratejas:main' into coverage-main 2026-04-23 10:49:51 -05:00
Garm
084678df7c fix(proxy): strip cache_control before hashing turn_id
compute_turn_id hashed the raw message dicts, which meant the same
user-text message produced a different hash on each call of one agent
loop because clients (notably Claude Code) move the cache_control
breakpoint to the newest message per call. The user-text block carries
cache_control on call 1 and not on call 2, so the serialized prefix
differs and the turn_id rolls over. Effect downstream: every API call
becomes its own "turn" and any prompt-level aggregation (e.g. the
Headroom desktop app's prompt all-time record) collapses to the
largest single call, not the sum across the prompt.

Add a small recursive normalization pass that strips cache_control from
the hashed prefix and from list-shaped system prompts before hashing.
Two new tests cover cache_control moving between calls on both the
messages array and the system prompt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 16:04:57 +02:00
JerrettDavis
a5a4486a30 test: apply linux ruff formatting
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 08:49:50 -05:00
JerrettDavis
50e712ac24 test: normalize batch handler test formatting
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 08:33:29 -05:00
JerrettDavis
96c7e940fb test: avoid platform leaks in cli tool tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 08:25:26 -05:00
JerrettDavis
d4574f4ae2 test: avoid global platform leaks in install tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 08:15:41 -05:00
JerrettDavis
65481d243a test: restore CCR module cleanup in batch tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 08:00:17 -05:00
JerrettDavis
6e9ea54a04 test: fix PR formatting and cover log compressor
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 07:49:27 -05:00
JerrettDavis
38bf3e639c test: expand coverage across helper slices
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 07:39:52 -05:00
Garm
35073ccb23 style(learn): ruff format test_analyzer.py
Line-length wrapping only. No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:24:46 +02:00
Garm
6d2aba8741 fix(learn): show prior patterns block to LLM to prevent dangling refs
When `headroom learn` re-surfaced a section heading that already existed
in CLAUDE.md / MEMORY.md, the writer replaced that section wholesale —
but the LLM never saw the prior block, so it emitted condensed bullets
like "X is *also* large — same rule as Y, Z" assuming Y and Z would
remain siblings. After replacement, Y and Z were gone and the "also"
dangled.

This threads the project's current `<!-- headroom:learn -->` block (from
both CLAUDE.md and MEMORY.md) into the digest as a "Prior Learned
Patterns" section, and extends the system prompt to make the re-emission
contract explicit: re-stating a section replaces it wholesale, so the
LLM must copy forward prior bullets it still agrees with. Prior sections
the LLM omits entirely are still carried forward by the writer (#231
behavior preserved as a safety net).

Changes:
- New `extract_marker_block(file_content)` helper in `learn.writer` that
  returns the raw marker block (delimiters included) or None.
- New `_build_prior_patterns_section(project)` in `learn.analyzer` reads
  `project.context_file` and `project.memory_file` via the new helper
  and formats a labeled section ahead of the per-session event stream.
- `_build_digest` emits the prior-patterns section when present; char
  budget accounting unchanged (prior blocks are small).
- `_SYSTEM_PROMPT` gains a "Prior Learned Patterns" rule block telling
  the LLM how to integrate prior bullets (preserve / revise / drop-only-
  if-contradicted) and warning against unresolved cross-references.
- Tests: 6 new `TestPriorPatternsInjection` cases (present/absent files,
  no-marker-block, both-files, end-to-end via mocked `_call_llm`); 4 new
  `TestExtractMarkerBlock` cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 12:59:02 +02:00
Garm
924076a243 test(backpressure): harden livez-under-backpressure against CI jitter
The /livez-unaffected test flaked deterministically on the Python 3.10
matrix job of this PR while passing on 3.11/3.12/3.13 and on main. Root
cause: the 3-request warmup did not cover every lazy-init path the
restructured proxy triggers on first request, so one measured sample
(consistently index 2 of 20) came in at 336-356ms instead of <1ms.
Compounding this, the assertion called `statistics.quantiles(n=100)[98]`
"p99" on only 20 samples — which collapses to `max(latencies)` and fails
on any single stall.

Fix the test for real, not just for this PR:

- Bump warmup from 3 to 10 to clear all lazy-init paths exposed by the
  upstream canonical-pipeline restructure. CI traces placed the rogue
  sample at measured-index 2 (request #6 overall), so 10 is comfortably
  past every observed lazy boundary.
- Stop mislabelling `max(latencies)` as p99. With 20 samples, drop the
  single worst outlier and assert on the next-worst. A genuine regression
  (semaphore actually blocking /livez) still fails hard because every
  sample would cluster near the drained timeout; a single GC/scheduler
  jitter no longer trips the assertion.
- Drop now-unused `statistics` import.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:08:04 +02:00
Garm
e1694c78fe Merge upstream/main into pr/turn-id-tracking
Conflicts:
- CHANGELOG.md: upstream landed Live flush + traffic-learner fix entries.
  Placed turn_id entry at the top of the first [Unreleased] ### Added
  section so both features coexist; preserved upstream ordering.
- headroom/proxy/server.py: upstream restructured the file substantially,
  producing a whole-file conflict. Took upstream's version and
  re-applied the single-line `"turn_id": log.get("turn_id")` addition
  to the /transformations/feed response dict at its new location.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 09:27:16 +02:00
Tejas Chopra
e4b87200b3
Merge pull request #237 from gglucass/fix/traffic-learner-evidence-count
feat+fix(learn): live flush of traffic patterns to CLAUDE.md / MEMORY.md + persist real evidence_count
2026-04-22 23:47:22 -07:00
JerrettDavis
535c4ac644 test: clean up lazy provider export cache
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 22:35:56 -05:00
JerrettDavis
8310a495ba style: match CI ruff formatting
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 22:21:00 -05:00
JerrettDavis
c8fc415707 test: stabilize provider package init coverage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 22:16:19 -05:00
JerrettDavis
4576f9caba test: remove provider diff churn
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 22:13:13 -05:00
JerrettDavis
7831620eca test: expand provider slice coverage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 21:58:03 -05:00
JerrettDavis
88dc15ea85 Merge upstream/main into feat/canonical-pipeline
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 18:47:41 -05:00
Garm
58282bbc5e test(proxy): cover turn_id branches flagged by codecov
Patch coverage on helpers.py was 87% — 5 lines of compute_turn_id
were untested. Add cases for: non-dict / non-user messages in the
reverse scan, empty-string user content (should keep scanning),
mixed text+tool_result content (agent-loop continuation, not a
turn boundary), and system=None (hashes without the system segment).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 23:34:41 +02:00
Garm
e8835affb8 docs(changelog): record turn_id feature; fix import order in new test
Follow-up to b2536e6: add the Unreleased changelog entry describing the
prompt-turn identifier, and pick up the ruff-fixed import layout in the
new test file (ruff --fix of I001).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 23:16:38 +02:00
Garm
c4464c066f feat(proxy): emit turn_id linking agent-loop API calls from one user prompt
Adds compute_turn_id() helper that hashes (model, system, messages prefix
up to the last user text message). An agent loop sends the same user-text
prefix across every iteration plus a growing tool chain, so this id is
stable across the turn but rolls over when the user sends a new prompt.

Stamps the id onto RequestLog at all three call sites (anthropic handler
bedrock + direct branches, and the streaming handler) and surfaces it as
turn_id in /transformations/feed so downstream consumers can aggregate
savings per user prompt rather than per API call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 23:15:55 +02:00
Kayzo
af9af3d2b5 chore(merge): resolve upstream main conflicts 2026-04-22 19:22:40 +00:00
Tejas Chopra
e71ebd5dfb
Merge pull request #230 from JerrettDavis/fix/copilot-oauth-runtime
fix: support live copilot oauth runtime
2026-04-22 11:47:37 -07:00
Tejas Chopra
40f11d8dff
Merge pull request #232 from gglucass/fix/learn-preserve-prior-recommendations
fix(learn): preserve prior recommendations across runs
2026-04-22 11:47:20 -07:00
Garm
aad799d76e test(learn): cover _parse_prior_recommendations edge cases
Closes the codecov gap flagged on PR 232 (88.89% → near 100% on the patch):

- A file with no marker block returns no prior recommendations.
- A marker block with nothing between the markers yields an empty list
  (the re.split fast-path with zero sections).
- A stray `### ` with no heading text inside the block is silently
  skipped (the `if not heading: continue` branch, previously
  unexercised in tests) — a real section after it still parses cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 20:13:49 +02:00
JerrettDavis
87a13ef2cd Merge upstream/main into feat/canonical-pipeline
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-22 13:10:53 -05:00
Garm
b2536e602a test(learn): cover flush_to_file, backend edge cases, and hydrate/bump error paths
Adds 17 targeted tests to close the coverage gap on the new traffic_learner
paths (codecov flagged ~51%). Exercises:

- `flush_to_file` end-to-end with a fake learn plugin + writer: verifies
  anchored patterns are bucketed per project, recommendations are passed
  to the writer, writer exceptions are swallowed, and each early-return
  branch (no plugin, no patterns, discover_projects failure, un-anchored
  patterns) is hit without raising.
- `_resolve_backend_db_path` on None backend, backend without
  `_config`, and backend with empty `db_path`.
- `_collect_all_patterns` merging persisted + accumulator patterns by
  content_hash with summed evidence_count, plus the missing-DB branch.
- `_hydrate_persisted_state` with backend=None and with a backend
  pointing at a non-existent DB file (both no-ops).
- `_bump_persisted_evidence` with no backend, missing DB, and
  unknown memory id (all silent no-ops so the proxy hot path never
  blows up on malformed state).
- `stop()` cancelling the flush task cleanly.

All new tests use the existing `_FakeBackend` + `_init_db` helpers so
they exercise real SQLite paths, not mocks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 20:06:44 +02:00
Garm
bde7807421 test(proxy): warm up TestClient before measuring /livez backpressure latency
The test drained the anthropic pre-upstream semaphore and asserted that 20
subsequent /livez calls stayed under 100 ms. With only 20 samples, the p99
computation falls through to max(latencies) — one cold-start outlier was
enough to fail the test. Observed on CI py3.10 runners where the first
TestClient request paid ~330 ms of one-time ASGI lifespan / import /
route-resolution cost while every subsequent request was sub-ms.

Add 3 warm-up requests before timing starts. Preserves the test's
signal (if /livez were actually blocked on the drained semaphore, all
post-warmup samples would still be slow) while removing the
runner-speed flake.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 19:58:55 +02:00
Garm
934345d81b Merge remote-tracking branch 'upstream/main' into fix/streaming-anthropic-logger 2026-04-22 19:51:59 +02:00