From 554b9da56ee62219ba772bf4309bc5cc61eb0eb9 Mon Sep 17 00:00:00 2001 From: Kadir Mert Okumus Date: Sat, 24 Oct 2026 00:29:33 +0300 Subject: [PATCH 1/2] Restructure the roadmap by remaining obligation (cleanup S2a) Done rows leave the roadmap; each outcome is in the log (existing rows or d565-d570). Partly done rows keep only their open part. Now and next and the tip line describe the state after cycle 1. Rule 10 exempts the append-only log from the line target (d571). Co-Authored-By: Claude Opus 5.5 --- docs/decisions.md | 7 ++ docs/development/documentation.md | 3 +- docs/development/roadmap.md | 137 ++++++------------------------ 3 files changed, 37 insertions(+), 110 deletions(-) diff --git a/docs/decisions.md b/docs/decisions.md index ddff626d..a5d9248d 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -549,3 +549,10 @@ result files when the documentation was cleaned up on 2026-10-17. | d562 | 2026-10-23 | FINDING | lsp | K11b-6 per-language places = "auto" (branch feat/k11b-6-per-language-auto, not merged): its pre-registered release-build measurement failed (mixed tree: 0 python lsp.live cards, R1a/R1b; small rust and deadline misses that the diagnosis reads as window noise, every card difference sitting on a deadline- or cap-hit call). The gate itself works: the rust index covers only .rs, python files are outlined and minted as sym:python:, and py01 discovers the two call_site.py members (hops 96 vs 94), but none is popped or listed: Contains edges carry prior 0.5 against 1.0 for calls and references, the 20-pop budget goes to the rust seeds' neighbours and answers keep 8 walk cards. No change inside the slice can surface them, so per-language auto moves to cycle 2 together with mixed-tree surfacing (Contains prior, budget, top_k), measured with a pre-registered python-seeded mixed question. A redo under a rule written after the failure is not taken. M2 closes without it. | private run record (K11b-6 measurement and diagnosis) | | d563 | 2026-10-23 | FINDING | process | Sanity 20 (M2 closed, d561 and d562): the direction holds; K11b-6's miss is a walk-ranking and seed finding, not a places one (the gate is verified; hard-coded hop priors, 20 pops, 8 walk cards; vectorless mixed arms), and its remedy is cycle 2 iteration with vectors present, a python-seeded mixed question and a discovered-nodes rule, seed-side before priors; prior art (aider's language-agnostic PageRank, Serena's explicit per-language routing, Sourcegraph's per-language SCIP) surfaces nothing by priors. The finish line is unchanged (d422); what remained was docs fold 4, K39b, K12a-2 narrowed to its measured pyright gain (0 of 7 against 5 of 5, opt-in, tsserver no gain) and the cycle 1 report. Recommended cut: K12b-2 and K31 to cycle 2, K12b-1 parked on its branch, K39b and K12a-2 kept; the K26 and K20c value windows need the owner's placement. Forecast at the time: close about 2026-10-24 02:00Z without K12b and K31. Process: two brittle pre-registrations in a row (K11b-6's positive rules could not fire under the question set; K11b-5's absolute-hash precondition failed literally under a merge of a default-off key, d558). Adopted change: every pre-registered positive rule carries a reachability line naming the baseline artefact that shows its measured quantity's input exists and that the budget leaves room; a precondition compares golden text against the baseline commit ("empty, or only default-off keys named here"), never an absolute hash; a reviewer other than the planner signs both before the quiet window. | private run record (sanity 20 review) | | d564 | 2026-10-23 | DECISION | traverse | K39b (PR 223): `run_with_trait_seeds` in `src/app/traverse/mod.rs` is split into private phases, move-only: `prepare` returns `Prepared` (the resolved call: limits as `Cow`, steer, selection, walk_off, store, loaded, observer, rescorer), `WalkSetup` (snapshot, handler, actions, priors, tie_break, identity, preset) and `Ledger` (budget, notes, warnings); the tree barrier stays in the orchestrator because the map context ties the restarts' lifetime to the call; `seeds` returns `Seeded` (found, `maps::Built`, arbitration; `find_seeds`, `build`, the arbitration and confirm checks, the trait-seed hoist); `run_first_stage` returns `Staged` (stage, exact report, stage time); the setup credit stays in the orchestrator at the same sequence point (`plan_maps` is taken, not cloned); `walk` returns `WalkOutcome` (the no-seeds answer or `Walked`: result, effective policy, enabled maps, restored nodes, notes, warnings); `finish` builds the handle, trace, map notes and response. Public paths and signatures and the report JSON are unchanged; the 18-cell `traverse_matrix` was byte-identical after each of the five commits (the fifth is the docs pass: the stale "below" comments in `mod.rs` and `maps.rs` and the module table in `architecture.md`). Fable review by normalised statement diff (495 old statements against 493 new, every residual diff glue or rustfmt reflow; the 30 note and deadline-credit sites in the same sequence; 13 `.clone()` sites unchanged; no new `pub` or `#[allow]`; the three note arms the matrix does not pin read verbatim): MERGE, no kill rule tripped. No Full gate locally (narrow, d520) and no Sol review (no behaviour change). K39 closed. | PR 223; merge 6efaf47; private run record (K39b review) | +| d565 | 2026-10-23 | FINDING | process | Cycle 1 time estimate, outcome (the roadmap subsection is retired; its text is in `git show 6d6a9bb:docs/development/roadmap.md`). Estimated agent wall time for the whole loop per slice: XS 0.5 h, S 1.5 h, S-M 2.5 h, M 3.5 h; the size table's hours were never re-measured per slice. Measured: S slices K1a, K1b and K1c 1 to 1.5 h each, one at a time (2026-10-20, Full gate about 25 min and CI about 20 min then; about 3 min and 8 min after d415 and d419); K0a part 1 (S-M) about 3.5 h with two review rounds; K4a about 2.7 h and K4b about 2 h, one review round each (d505); M1 about 20 h wall with K7b-1, K7b-2 and K9a in parallel; M2 about 30 h wall at the last count, closed 2026-10-23 about 13Z, about 3.4 days after the roadmap merge (2026-10-20 03:49Z), with lanes in parallel and without K11b-6 (d562); M3 about 8 h wall plus 6.5 h measurement machine time (lane A 3.5 h, lane B 3 h, debug builds under load, against 25 to 90 min of estimate) plus K22a-2 about 5 h wall (build 32 min, main run 4 h 12 min, release, under load; d497). Pace on 2026-10-21 (sanity 10, d459): PRs 139 to 159, 21 merges in 8 h (09:36 to 17:39 local), 2.6 per hour with four lanes, 33 merges since midnight; lanes are limited by dependencies, by merges into `main` one at a time and by build slots. Risks named at the time: the weekly usage stop, SHARED's 1 s stamp-walk kill rule on deno-sized trees (30k files), debug builds under load, M rows with two review rounds (about +30 %), Windows gaps for marksman, Serena and the ast-grep tree-sitter pin, and outputs dying with worktrees. K32-3 (pickaxe) was noted as a decision, not a slice. | roadmap@6d6a9bb "Cycle 1 time estimate" | +| d566 | 2026-10-23 | DECISION | tests | N21 (PR 196, test-only): the JSON-metrics test `evaluation_reports_required_failures_optional_skips_and_json_metrics` pins `hit_at_1_rate`, `mrr` and `role_distribution` to hand-derived values (0.0, 0.0, {}) with a poisoned skipped query that proves exclusion, and loads its definition through `load_definition` instead of asserting an `EvaluationFile` it built itself. | PR 196 (roadmap row N21 at 6d6a9bb) | +| d567 | 2026-10-23 | DECISION | engine | N18 low batch (PR 195): items (a), (c), (d) and (e) are fixed: (a) the `manifest::save` removal of the destination before the rename; (c) `serve --port 0` uses the bound port for the log, `/openapi.json` and the Origin check; (d) the navigation tests assert their own adapter's running flag; (e) the model listing probe keeps the configured URL's path prefix (`model_listing_url`) and falls back to the origin `/v1/models` on 404 or 405; the stale self LSP acceptance needle for `openapi_document` is fixed too. Item (b), the Java readiness probe, was N77 (d190). Item (f), the per-file size cap, stays open in the roadmap. | PR 195 (roadmap row N18 at 6d6a9bb) | +| d568 | 2026-10-23 | DECISION | tests | N112 and N113 (PR 190, test-only): N112, the flaky freshness test `with_sync_on_the_session_is_told_about_the_edit_and_the_barrier_replaces_it` (seen once on CI 2026-10-22, PR 188, docs-only), waits on `synced()` before it reads `sync_status`; N113, the literal suite lengths (`args(Suite::Lsp).len()` style asserts), are replaced by derived tests that every `tests/integration.rs` case module is in a focused suite or `FULL_ONLY_CASES` and every filter names a declared module. | PR 190 (roadmap rows N112 and N113 at 6d6a9bb) | +| d569 | 2026-10-23 | DECISION | tests | N115 (PR 202, 2026-10-22, test-only): the Freshness cases `a_rewritten_scip_index_refreshes_the_scip_built_maps_by_its_stamp`, `without_the_key_the_labelled_maps_are_gaps_on_a_line_shift_too` and `with_the_stat_gate_on_the_labels_equal_the_full_hash_path` get a 10-minute test deadline (the config keeps the 60 s that validate accepts; `assert_no_timeout` is kept), and the lci-maps probe gate test asserts cargo's timeout against the measured gate hold, with no wall-clock margin, so no case depends on machine speed. | PR 202 (roadmap row N115 at 6d6a9bb) | +| d570 | 2026-10-23 | DECISION | infra | tokio 1.54.1 (PR 224, merge 7ba065c, commit d8911b1; `Cargo.lock` only): RUSTSEC-2026-0386 (the `AsyncFd` constructors accept any raw fd, unix only; fixed in tokio 1.53.3 and later) failed the cargo-deny advisories check on every PR, so the lock moves to 1.54.1. | d8911b1 (PR 224; merge 7ba065c) | +| d571 | 2026-10-23 | DECISION | process | Rule change in `docs/development/documentation.md` rule 10: the append-only log `docs/decisions.md` is exempt from the line target (the target `log <= 300` is removed; the log is 551 lines at 6d6a9bb, and moving old entries to an archive would break the frozen ids that are cited everywhere). README, guide and roadmap targets are unchanged. | cleanup S2a | diff --git a/docs/development/documentation.md b/docs/development/documentation.md index 5158f332..9a7bfe40 100644 --- a/docs/development/documentation.md +++ b/docs/development/documentation.md @@ -28,7 +28,8 @@ Agents read these files without chat context and pay for every token, so the fil `*-prereg*.md` and `*-results.md` records under `docs/spike/multimap/v2`) and runs in `--suite Full`; none may be missing. 10. Short over complete: a table over prose for facts with the same shape; one example instead of three; a pointer - instead of a copy. Target sizes: README <= 260 lines, any guide <= 250, the roadmap <= 350, the log <= 300. + instead of a copy. Target sizes: README <= 260 lines, any guide <= 250, the roadmap <= 350. The append-only log + `docs/decisions.md` is exempt from the line target. 11. Vendored third-party files (`.agents/skills/rust-skills/**`) are not edited. 12. Every change to these files is checked against the code it describes (names, defaults, ports, commands) in the same change. diff --git a/docs/development/roadmap.md b/docs/development/roadmap.md index 7fb3318f..8bbcb478 100644 --- a/docs/development/roadmap.md +++ b/docs/development/roadmap.md @@ -9,7 +9,7 @@ Ids are never renumbered or reused. ## How to use this file -1. Take the next slice of "Cycle 1" in table order (named in "Now and next"), then "Earlier items outside cycle 1", then +1. Take the next slice of "Cycle 1" in table order that the owner's cut keeps in cycle 1 (see "Now and next"), then "Earlier items outside cycle 1", then the lowest tier in "Open items by tier" whose dependencies are done. 2. One item is one slice and one commit, plus the regular `cargo xtask test --suite Full` gate. Split an item that grows. 3. Load the matching project skill first (`.agents/skills/lci-*`; the list is in `AGENTS.md`). @@ -48,55 +48,35 @@ that document and is not a roadmap id. ## Now and next -On main (6efaf47): PRs up to 223, the latest being K39b (223, d564), K12a-2 (212, d533), the docs PR (222, d557 to d562), N118 (221, d560), K26-2 (220, d559), K11b-5 (219, d557) and N122 (218, d558), after K12a-1 (206, d529), K20c-1 (207, d534), N87 (208), N25 (209), N48 and N116 (210), K11b-3 (205, d550), N20 (a) (211), the Live Acceptance workflow made manual-only (213, d538), K39a (214, d554), K11b-4 (215, d555), K20c-2 (216, d556) and the docs PR (217, d529 to d556), after K7c (203, d544, d545), the previous docs PRs (201, 204), N115 (202), K26-1 (197), the K5b Serena arm (198), the CodeQL advanced setup (199, d521), K11b-1 and K11b-2 (200), N94 (193), K39a commit 0 (194), the N18 low batch (195), N21 (196), K12c, N104 + N106, K18a, N107, ASYNC-2 B, K11d-2a (d509, d510, d512), N112, N113 and B2 (N109, N110, d514), plus this docs PR. K22a-3 is measured and closed as a finding (d519). The K11b post-merge window found the stock `sync = "off"` default wrong for non-rust servers (d553) and K11b-4 flipped it (d555); K11b-5 made the readiness probe per adapter so python and go join `lsp.live` on a stock install (d557, d540). Now: M2 closed 2026-10-23 without K11b-6 (its release-build measurement failed, so per-language `places = "auto"` moves to cycle 2 with mixed-tree surfacing, d562). Cycle 1 implementation is complete: K39b (d564) and K12a-2 (d533, opt-in, the narrow pyright claim) merged, and sanity 20 (d563) recommends K12b-2 and K31 move to cycle 2 with K12b-1 parked on its branch (the tsgo adapter; the tsgo facts check is pending and nothing merges before it and the lsp-cost pair, d537). The cut is the owner's and awaits his answer (asked 2026-10-23), as does the placement of the K20c rust and K26 value windows (each needs a release quiet window with no cargo; the arbitration stays off until the K20c measurement passes), which sanity 20 proposes for cycle 2. The cycle 1 report goes to the owner next; per the autonomy rule the coordinator then waits for his word. ASYNC-2 C stays parked. Only docs PRs and the coordinator rewrite this line; a feature PR leaves it alone so parallel PRs do not conflict (d476). The live state is the coordinator's run checkpoint. +Cycle 1 implementation is complete (finish line d422): the last feature merges were K39b (d564) and K12a-2 (d533); M2 closed 2026-10-23 without K11b-6, whose release-build measurement failed, so per-language `places = "auto"` moves to cycle 2 with mixed-tree surfacing (d562). On main: tip 6d6a9bb, last PR 225, last log id d571. A cleanup of docs and code is running. Next: the cycle 1 report goes to the owner; his cut and his answers to the report questions (asked 2026-10-23) follow, then cycle 2. Waiting on the cut: sanity 20 (d563) recommends K12b-2 and K31 move to cycle 2 and K12b-1 stay parked on its branch (nothing merges before the tsgo facts check and the lsp-cost pair, d537), and the placement of the K20c rust and K26 value windows (each needs a release quiet window with no cargo; the arbitration stays off until the K20c measurement passes), which sanity 20 proposes for cycle 2. K7b-3b and K11d-2b stay open plans for the same word, and ASYNC-2 C stays parked. Per the autonomy rule the coordinator waits for the owner's word after the report. Only docs PRs and the coordinator rewrite this section; a feature PR leaves it alone so parallel PRs do not conflict (d476). The live state is the coordinator's run checkpoint. ## Cycle 1 -Plan reviewed by the planning workflow, the coordinator and a cross-family reviewer (d397). Milestones run in order; -inside a milestone the order below is the default. Each row is one slice and one commit unless marked. Check = what the -slice's tests must show (ceiling test: works given ideal input; mechanism: the code path does what it claims). Every -new behaviour is a `[run_profile]` key whose default keeps today's behaviour unless the row says otherwise. +Open slices of the cycle 1 plan (reviewed by the planning workflow, the coordinator and a cross-family reviewer, d397); done slices are in the log. Milestones run in order; inside a milestone the order below is the default. Each row is one slice +and one commit unless marked. Check = what the slice's tests must show (ceiling test: works given ideal input; mechanism: the code path does what it claims). Every new behaviour is a `[run_profile]` key whose default keeps today's behaviour unless +the row says otherwise. -M2 runs as three lanes by real dependencies: {K7b, K10a}, {K8a, K7c} and {K9a, K9b}, converging on K11d; K11e is -independent, and K20b comes before K11b so the default is tested with SCIP absent. Ceiling and mechanism checks show that -an arm works: no slice claims a gain over rg or LSP before the paired screening of cycle 2 (d422). +Ceiling and mechanism checks show that an arm works: no slice claims a gain over rg or LSP before the paired screening of cycle 2 (d422). | Slice | Item | Check | Size | Absorbs | | --- | --- | --- | --- | --- | -| **M1** | **Profile, freshness harness, no-service path**: done 2026-10-20 (K2b, d420) | | | | | **M2** | **Language servers as the source of truth** | | | | -| K7b | Partly done: shared LSP sessions across traverse calls, `[run_profile.lsp] sessions` (d414); K7b part 4 landed (SHARED, d454): `sessions = "shared"` is the default, with per-adapter routing: only a server that lists the configuration files of its tree stamp (`LspAdapter::stamp_manifests`, every adapter but jdtls) and watches its workspace (`LspAdapter::watches_workspace`, rust-analyzer alone) or is synced (`sync = "watch"`) keeps a session across calls, every other server starts per call, and every kept session passes a tree barrier before a walk uses it (a stamp from the index's file list, one directory read per stamped directory, one level of child directories and the configuration files, no recursive walk; a changed or failed stamp restarts the server; barriers of one workspace are serialised), so a reused server is never staler than `per_call` for any change inside the stamp's coverage, and its answer carries a `kept session` note (the residuals are listed in the README and d454); the outline cache keyed by content hash, `[run_profile.lsp] outline_cache` (d424); readiness signals per server as a report, `Status.analyzers` and notes, no key (K7b-3a, d446). Remaining: the `busy_repeats` feedback (K7b-3b), after SHARED (d454) merges and the N8 run (`maps lsp-cost` with K7b-3a's analyzer states): the dark calls it repeats exist only on a resident busy server, so it lands only if they are a visible share of the loaded arm's calls, else cycle 2 (the K24c run that the sanity-8 freeze waited for is done, d459) | Fake LSP tests | M | N8 (readiness part) | -| K37b | Done (d475): concurrent walks as the load test of SHARED's tree-stamp barrier, with the fake server and no product change. No stale reuse within the stamp's coverage, restarts serialised per workspace, correct "kept session" and "restarted" notes, and no per-server request queue needed (no cross-talk observed; per-id matching by construction in the transport, d422, untested against an out-of-order answer; K37a stays dropped). New fake flag `--ignore-watched-files` (test support, off by default). The optional real rust-analyzer smoke was not run and does not gate (d470) | Four integration tests and one timer-free unit test, no sleep or wall assert | S | - | -| ASYNC-1 | Done (d470): the three small fixes (2 to 4) of the async audit, no default or key change. `lsp.live` expansion bounded by the walk's `deadline_ms` only (`CALL_TIMEOUT` removed); the warm start unbounded by any clock, ended by its abort (`Manager::prime_languages(.., None, ..)`, each probe bounded by `readiness_timeout_seconds`); the version probe memoised per process (resolved path, modification time, size; success only). Two of the three 2026-10-21 wall-clock flakes fixed (`models.rs` breaker test, `grep.live` tests). | Paused-clock and gate tests, no sleep or wall assert; Freshness answers byte-identical except notes that no longer carry the deadline note | S | - | -| ASYNC-2 | A landed (d479, PR 180: `didOpen` once per changed text, per-session uri to hash table); D landed (d480); B landed (per-uri diagnostics wait, d479 B note, PR 187); C parked (d505; the rust latency pass is measured, d509, and C stays parked). The rest of the async audit. `didOpen` only on changed text (per-client uri to hash) and a per-URI `publishDiagnostics` wait replacing the 3 s `head_start` (python, typescript, go, clangd); readiness by event (rust-analyzer's 10 s cap becomes `wait_ready(remaining)`, repeats on the busy/ready watch, `Manager` owns its sleeper), measured by `cargo xtask maps lsp-cost` and K11d-2 per adapter; the test-timing item (the wall-clock asserts of the audit, a scanner seam in `channel_maps.rs` for `the_live_lexical_map_passes_c1_and_c6_and_fails_open_without_ripgrep`, the `lsp_stuck_seconds` clock); polls to waits (index watcher, data-dir lock, `try_wait` loops, the cargo gate); commit D (d480): the data-dir lock, the `try_wait` loops and the cargo gate are waits on events (lock release, child exit, gate release), the index watcher stays a poll by contract | Second definition on one file costs no sleep; lsp.live expansion on a Python fixture warm under 1 s; no `elapsed` assert in the listed tests | M | - | -| K10a | Done (K10a-1 d442, K10a-2 d449): place table without SCIP, `[run_profile.lsp] places` (`"scip"` default or `"outline"`, d442): `OutlinePlaces` over the LSP outline, ids from the index else minted with the file's language; the `LostEdge` gap closes under `outline`; `lsp.live` stays labelled `SiteDrift` because card sites are the index's. Rust members the index lacks take the ids of the `sym.defs` extractor's declarations (`chunk::extract_for_path`, not the bundle store, which `traverse::run` cannot reach; `CrateNames::read_paths`), so Rust ids equal the index's. Left open: placing a Symbol seed whose file was never outlined (module-path inversion, with K21a, d442), outlining destination files so `holder` names a reference in a never-outlined file | `lsp.live` runs with `scip.mode = off`; a shifted-line placement check: after lines are inserted above a symbol its place follows (closes the freshness `LostEdge` gap under `places = outline`; `lsp.live` stays labelled `SiteDrift`) | M | - | -| K7c | Done (K7c-1 d544, K7c-2 d545; PR 203): document lifecycle without a key. A position request sends `didChange` with the whole text to a server that advertised change sync (else a second `didOpen`, as before) and waits for the server's diagnostics; a cancelled caller leaves its pending wait to the next one (N111); typescript-language-server opens documents; a session holds at most 64 open documents (the least recently used is closed with `didClose`). The 3 s `head_start` was already gone (ASYNC-2 B, d512). Measured after the merge: the real typescript-language-server answered from the new text after a same-size, same-mtime edit through one `didChange`; lsp-cost for typescript and python is within noise of d512 (shared+sync warm 2.1 to 4.1 ms per expansion), no new diagnostics-wait timeouts | Fake LSP event log; the value check is a before and after of the lsp-cost arms on the same arm text (K7c has no key, so K11d's `open` arms do not exist) | M | N2 | -| K11e | Done (d474, first commit of the K11e + N103 PR; N103's `lsp_start_timeout_seconds` is the second): LSP down-window breaker, `[run_profile.lsp] breaker` (30 s per provider, shared by the process's managers; default off then, on since K11b-3): a failed version probe, or a server start that failed without timing out (spawn failure, exit, protocol break), opens the window, and the next probe or start of that provider is refused at once with a note that names it; a started server is still asked, a timed-out start opens nothing, `/ready` still probes. Turned on by default in K11b-3 | Fake LSP tests: a dead server costs one probe per window, not one per call | S | - | -| K39 | Speed-up: split `run_with_trait_seeds` (src/app/traverse/mod.rs, about 1,176 lines and 141 branch tokens on main at 7684817) into a few phases that return typed values (for example prepared, registered maps, execution, finalisation; each carries only what the next phase needs), keeping the public signature and the report JSON. Why: it is the file where parallel lanes collide (39 commits since 2026-10-08, 4 of the last 20 catch-up merges edited it on both sides), and every new map and `[run_profile]` key lands in that one function. Risk is temporal coupling (setup order, resource lifetimes, freshness stamps, deadline accounting, continuation semantics), not length. Starts only after K11e and N103 land and no open lane edits that file (otherwise the split is the conflict). Owner request via the maintainability audit, d487. K39a done (commit 0 in PR 194, d502: the pinned matrix `tests/cases/traverse_matrix.rs`, goldens under `tests/golden/traverse-matrix/`, extended by the review with trait seeds, registration order and off notes; commits 1 to 3 and 0b in PR 214, d554): the map registry lives in `src/app/traverse/maps.rs` (`select`, `build`), move-only, the matrix 18 of 18 after every commit, so every new map and `[run_profile]` key now lands there. K39b done (PR 223, d564): `run_with_trait_seeds` is private phases (prepare, seeds, first stage, walk, finish) with the tree barrier and the setup credit kept in the orchestrator, move-only, signatures and report JSON unchanged, the matrix byte-identical after each of five commits, Fable review by normalised statement diff, plus the docs pass (stale comments, the module table in architecture). K39 closed | Move-only commits; before the first move pin a byte-identical traverse output matrix (fresh vs resumed handle, an opt-in map off vs on, LSP down vs ready, trace and record output) that every commit reproduces; then the Full gate | M (split before picking if it grows) | - | -| K11d | K11d-2a done (d509, d510; frozen pass on release d8c812f, after-pass on a8755dd): rust with SCIP, `base` hits the 20 s deadline on every call and `shared` warms to p50 79 ms per expansion (p95 799); on python, go, typescript, c and cpp the 3 s `head_start` per expansion before ASYNC-2 B became 1.6 to 52.7 ms after it (`shared+sync`); no non-rust adapter gets an `lsp.live` card under `places = "scip"` without a SCIP index, and every call does under `places = "outline"` (warm 2.2 to 26.2 ms per expansion). K11d-2b (the `open` arms) is replaced by K7c's before and after on the same arm text, done for typescript and python (d545). Harness landed (PRs 177, 189); K11d-2 only (K11d-1 landed, d438: `cargo xtask maps lsp-cost` and the numbers on main): after SHARED (d454) the arms `base` and `per_call+sync` write `sessions = "per_call"` explicitly, and a real-rust-analyzer re-run of `--arms base,shared` records the default's cost; the `documents = open` arms (`open`, `open+shared`, `open+cache`) do not exist because K7c added no key; the typescript `outline_cache` interaction d424 deferred to K7c is still unmeasured (typescript opens documents since d544). Still open from K11d-1: N8 (the analyzer state at each dark call needs K7b-3a), and the `shared+calls` arm of K9b | Report under `test-results/lsp-cost/` | S | N8 (live diagnosis) | -| K20b | Done (K20b-1, d471): `[run_profile.scip] mode` defaults to `cache`; an index that cannot be parsed is parsed once per file stamp (size and modified time) and later calls say it was not read again; recipes with no index refuse with the hint to run `rust-analyzer scip`. The golden moved by the `scip.mode` line and the hash only; results stamped under an unset mode carry a new profile hash (K11d-1's numbers, d438, carry the old one). The label and the arbitration are K20c (`[run_profile.scip] arbitration`, off by default) | Its own tests: SCIP absent, the remembered failure (traverse unit, MCP, recipes) | S | - | -| K20c | Arbitration of SCIP edges: `role` in `maps[]`, SCIP edges subordinate and labelled, LSP wins a disagreement, confirmed and overruled counts, the navigation.rs ceiling check. After K9b and K11b (no authority exists before them). K20c-1 done (d534, PR 207; the engine, lci-maps only) and K20c-2 done (d556, PR 216): `[run_profile.scip] arbitration` (default `false`), the wiring for `scip.impact`, `scip.implements` and `structure.contains` (functions and methods only; `graph.scip` is never arbitrated), `role` under `map_freshness`, the counts in the trace, `per_map`, `totals` and a note, and the handle check. Default off until the pre-registered rust measurement (false-overrule rate at most 5 %, the edit pass, no card or deadline loss; the coordinator's lane, with a Sol review for the flip; rust only, d556) | Its own tests: SCIP present but subordinate, a disagreement | S | - | -| K11b | K11b-1 to K11b-5 done; K11b-6 (per-language `places = "auto"`) not merged, moved to cycle 2 with mixed-tree surfacing (d562). K11b-1 and K11b-2 (d523, d524; PR 200): typed probe failures; the search channel keeps a watching server. K11b-3 (d550, d531; PR 205), the default change: `[run_profile.lsp] live = true` (renamed from `lsp.default`) puts `lsp.live` in the default map set of a service call with a tree under `sessions = "shared"` (the CLI one-shot and `per_call` stay opt-in, d438), `breaker = true`, `places = "auto"` (SCIP places where the loaded graph has a node, else the server's outline; the rust pair kept `"scip"` for an indexed tree, so explicit `"scip"` and `"outline"` stay), and a walk passes one tree barrier before its seed search (review finding F1); fails open, spent from the external-call budget; the search channel's default is verified. K11b-4 (d555; PR 215), the default change `[run_profile.lsp] sync = "watch"` (was `"off"`): the service's manager watches each workspace with a session, so every server that lists its tree stamp's configuration files (all but jdtls and marksman) is kept under `sessions = "shared"` and primed at first touch (the post-merge window measured 62 to 129 times the cost per expansion for python, typescript, go and csharp under `"off"`, d553); the lsp-cost `shared` arm writes `sync = "off"` explicitly and keeps meaning the old stock, `shared+sync` is the stock now; the re-measure passed for all six adapters. Caveat: `places = "auto"` is per workspace, not per language (a rust index plus python files gives python no `lsp.live` cards, d553). K11b-5 (d557; PR 219), the per-adapter readiness probe (`gopls version`, a start that may exit non-zero for pyright), made python and go join `lsp.live`, the warm start and `/ready` on a stock install (the stock-install smoke passes without the tap's workaround; the d553 and d555 python and go numbers were those of a fixed probe, d540, and the no-workaround re-measure stays within 3x of them); the `open_split` partial-failure note is N124. K11b-6, the per-language slice, failed its pre-registered measurement (0 python cards on the mixed tree: the gate works, but the python members are never popped or listed because Contains edges carry prior 0.5) and is not merged; it moves to cycle 2 together with mixed-tree surfacing (Contains prior, budget, top_k), d562. Gates (d509, d510, d512), met: cards above 0 per adapter without a SCIP index needs `places = "outline"` (part of the default for such languages, or the default places), and the search channel adopts the watching-adapter exception (keep a watching server's session through a changed stamp, label it `kept session`). After K11d (N101 is d450), K11e and K20b | Fail-open test with a dead server; K11d's cold ms in range; tests that assumed it absent are updated | S | - | +| K7b | Remaining: the `busy_repeats` feedback (K7b-3b). The rest is done (d414, d424, d446, d454). The dark calls it repeats exist only on a resident busy server, so it lands only if they are a visible share of the loaded arm's calls (after the N8 run: `maps lsp-cost` with K7b-3a's analyzer states), else cycle 2 | Fake LSP tests | M | N8 (readiness part) | +| ASYNC-2 | C, parked (A d479, B d479 and d512, D d480 are done; d505, d512): readiness by event (rust-analyzer's 10 s cap becomes `wait_ready(remaining)`, repeats on the busy/ready watch, `Manager` owns its sleeper), measured by `cargo xtask maps lsp-cost`; and the test-timing item (the wall-clock asserts of the audit, a scanner seam in `channel_maps.rs` for `the_live_lexical_map_passes_c1_and_c6_and_fails_open_without_ripgrep`, the `lsp_stuck_seconds` clock). Parked because the remaining rust cost is the per-call cold start, which shared sessions already avoid (d512) | No `elapsed` assert in the listed tests | M | - | +| K10a | Open remainder (K10a-1 d442 and K10a-2 d449 are done): placing a Symbol seed whose file was never outlined (module-path inversion, with K21a, d442), and outlining destination files so `holder` names a reference in a never-outlined file | Tests | not sized | - | +| K11d | K11d-2b remainder (K11d-1 d438, K11d-2a d509, d510 and d512 are done; the `open` arms do not exist because K7c added no key, and K7c's before and after on the same arm text replaced them, done for typescript and python, d545): the typescript `outline_cache` interaction that d424 deferred to K7c is unmeasured (typescript opens documents since d544); N8 (the analyzer state at each dark call, with K7b-3a's states, d446); the `shared+calls` arm of K9b | Report under `test-results/lsp-cost/` | S | N8 (live diagnosis) | +| K20c | K20c-1 (d534, PR 207) and K20c-2 (d556, PR 216) are done: `[run_profile.scip] arbitration` (default `false`), `role` labels and counts. Open: the pre-registered rust measurement (false-overrule rate at most 5 %, the edit pass, no card or deadline loss; the coordinator's lane, with a Sol review for the flip; rust only, d556), which needs a release quiet window with no cargo; the arbitration stays off until it passes; its placement (cycle 1 or 2) is the owner's cut (d563) | False-overrule rate, edit pass, card and deadline loss | S | - | +| K11b | K11b-6 only (K11b-1 to K11b-5 are done: d523, d524, d550, d555, d557): per-language `places = "auto"` (SCIP places where the loaded graph has a node, else the server's outline, per language). Not merged (branch `feat/k11b-6-per-language-auto`): its pre-registered release-build measurement failed, 0 python `lsp.live` cards on the mixed tree, because Contains edges carry prior 0.5 and the 20-pop budget goes to the rust seeds' neighbours (d562). Cycle 2, together with mixed-tree surfacing (Contains prior, budget, top_k, a python-seeded mixed question, a discovered-nodes rule; seed-side before priors, d563), under a new pre-registered plan. Until then `places = "auto"` is per workspace, not per language (d553) | Pre-registered mixed-tree release measurement | S | K11b-1 to K11b-5 (done) | | **M3** | **Walk, cards, seeds, router, cost** | | | | -| K10b | Done (d452): `SymbolKind` gains Field, Variable, Interface and Class (names `field`, `variable`, `interface`, `class`), produced only by `OutlinePlaces` from LSP outlines: class 5 -> Class (was Struct), 23 Struct, 11 Trait in a `.rs` file (rust-analyzer's trait) else Interface, 7 and 8 -> Field, 13 -> Variable; enum members (22), impl blocks (19) and type parameters (26) stay without a node (EnumMember is a later XS). Fields and variables are asked like any symbol (the planned request guard was reversed in review: interface properties answer implementation). No adapter version bump: nothing persisted holds an outline node (`sym.defs` and `cache_version` hold the extractor's kinds, which are unchanged), and a bump would re-chunk every index for identical bytes. No key; the default `places = "scip"` is byte-identical | Unit tests | S | - | -| K13 | Done (d453): `[run_profile.traverse] answer_labels` (default `false`): an answer-level `completeness {complete, why, estimate}` (complete = status `complete` and no map lost, else `why` `degraded` with the maps listed; `why` the stop reason; `estimate` the K29 fraction, `TraversalResult.estimate`) and a `freshness` word (`fresh | changed | stale | unverified`) on every card, response-only (trace, checkpoint hash and handle unchanged; nothing withheld; key off is byte-identical). Left open: a `freshness` word on the search report's hits (XS follow-up, after the owner sees the traverse labels); flipping `stat_gate`'s default stays its own slice (d440) | Card schema test | S | - | -| K21a-2 | Done (d459; the 2026-10-21 measurement run `work/run/measure-2026-10-21`, `max-depth=0 --include-seeds`, no gain claim, d422): `seeds.bm25` beats `seeds.scip_names` at depth 0 on traits (set_f1 0.346 vs 0.162, paired n 146, 65 wins and 19 losses; hit@10 0.733 vs 0.508, n 120) and where-to (reg_hit@3 0.60 vs 0.20, n 25; set_f1 0.175 vs 0.006, n 34), at 289 vs 115 answer tokens per item. The d392 regex rg rule beats every seed source on traits (set_f1 0.704), and the walk with `scip.implements` matches it (0.703), so on traits the seeds are not the gap; where-to has only the as-typed rg baseline (reg_hit@3 0.48). `seeds.hybrid` ran lexical-only (8766/8767 down), so it is not the hybrid ceiling. `seeds.bm25` ran through the raw driver (no cost columns) because the bench's `SEED_MAPS` does not know it (XS row before K22a-2). Left open: one 8766 and 8767 window for the hybrid row, the live bench seed map | Gold symbol in the top k per arm (Q2 `hit@10`, Q1/Q3 `set_recall`; where-to file hits), reported under `test-results/`; cycle 2's screening reads the table | S-M | K21a-1, K5a (baseline arms), L1, L4, I-1 | -| K24c | Done (d459): log_odds off (it turns the score bound off, d432: pops per item 6.4 -> 106 on traits, 13 -> 185 on impact, 11.4 -> 188 on where-to; traits set_f1 0.703 -> 0.579, n 146; where-to and impact flat); `relative` has no effect (1 of 146 traits pairs differs, impact decl_f1 -0.004), so it leaves the cycle 2 factor list; a fair cycle 2 log_odds arm equalises the bound. Numbers are in the arm manifest | Measurement | XS-S | - | -| K22a-0 | Done (d461): the bench's `SEED_MAPS` (`xtask/src/maps/bench.rs`) knows `seeds.bm25` and `seeds.literal`; `map_emits` gives bm25 `[Symbol]` and literal `[Symbol, File]` (both match the descriptors). Naming either map in `--maps` turns its `[run_profile.seeds]` key on in a derived overlay under `//overlay`, and the row's `config=` note and `score.json` carry its hash; no default changed. `--maps seeds.bm25` ran live on traits and where-to; `--maps seeds.literal` ran live on traits (tauri, 5 items, after the fix below: 5/5 scored, cost columns present); the where-to questions name nothing in code spans, so literal has nothing to search there. A call with no search now says the ripgrep note even for a question without code spans. `lci traverse` opened the search application (the source of `seeds.literal`'s ripgrep) only when `seeds.hybrid` was named or no maps were, so `seeds.literal` alone found no seeds; fixed here in `src/main.rs` (`wants_seed_search`, with a unit test) | `--maps seeds.bm25` and `--maps seeds.literal` run through the bench and carry cost columns | XS | K21a-2, K22a-2 | -| K22a | Done for K22a-0 and K22a-2 (d461, d497): exact-literal seed source over ripgrep, `seeds.literal` under `[run_profile.seeds] literal` (off; K22a-1, d448; `exact_seeds` reuses exact cards and is not this): whole-word `rg` for the names in the question's code spans, each line placed through `SymbolPlaces` (SCIP layer today; K10a's `OutlinePlaces` is the one-argument upgrade), a `file:` seed for declaration lines (the seedless-trait cases). K22a-2 measured (d497; the 2026-10-21 K22a-2 run; `max-depth=0 --include-seeds`, release build of the frozen commit 1d0b097, lexical-only, three trait repos plus impact and where-to): `seeds.literal` alone is weak on traits (pooled set_f1 0.152, hit@10 0.183, against `seeds.scip_names` 0.162 / 0.508 and `seeds.bm25` 0.346 / 0.733) but strong on impact (decl_f1 0.676 against the walk control's 0.436); hybrid+literal beats hybrid on traits (set_f1 +0.065 [+0.040, +0.092], hit@10 +0.142 [+0.083, +0.208]); in the walk the trait seeds come from the traits path, so added seeds change nothing there (B0 vs B1 set_f1 0.703 vs 0.705). The B2 signal is not evaluable (bevy and deno B2 not run). No default changed: `seeds.literal` stays off and goes to question shapes that name literals through K22a-3 (which measured a loss and closed, d519: it stays off). Remaining: the seedless-trait mechanism test with K9a's documents fake, which expands the file seed through `lsp.live` under `places = outline`; point the K5a rg arm at `lexical::parse_matches`; B2 on bevy and deno (about 55 min, `armB B2 traits bevy|deno`) only if signal 3 is wanted | Ceiling, beside an rg baseline arm (done, d497) | S | item 4 rest | -| K22a-3 | Route `seeds.literal` by question shape through the K30a router (d456): turn `[run_profile.seeds] literal` on for questions that name literals in code spans (impact-shaped, error strings), off for trait-shaped questions and where-to; the global default stays off (d497). Measured (d519): `seeds.literal` behind hybrid loses decl_f1 on impact (-0.059 [-0.077, -0.042]), the pre-registered rule does not fire, and the slice closes as a finding with no `literal_policies` key and no default change; follow-ups for cycle 2 (each its own measured slice): literal seeds restricted to definitions, or an occurrence prior (7.70 of the 8.38 literal seeds per item are occurrences), and a larger `seed_k` for the impact preset (a sweep needs `--seed-k` forwarded by `maps bench`) | Impact-shaped items gain decl_f1 over the router's current choice (A1 0.676 and the walk control 0.436 are the reference, d497) with no loss on traits (set_f1 and hit@10 within the paired CI of the unrouted default) | S | K22a-2, K30a | -| K33a | Done (K33a-1 d444, K33a-2 d451): trajectory rows per pop (state, candidates, choice) linked to the outcome (gold hit) in evaluation runs. `lci traverse --trajectory` (CLI only, no key) writes a `pop` event per counted pop; `cargo xtask maps trajectories --runs --gold-dir [--scip ] [--set ]` writes one row per pop to `//trajectories.jsonl` with `trajectories.md` beside it (a separate command; no bench file changes). Labels come from the set's own scorer; an unlocated symbol is `null`. Pops come from `end.totals`; a put-back pop the totals do not count is `counted: false`. Cycle 2's learned racer reads the rows | Schema test | S | - | +| K13 | Open remainder (answer labels are done, d453): a `freshness` word on the search report's hits (XS follow-up, after the owner sees the traverse labels); flipping `stat_gate`'s default is its own slice (d440) | Card schema test | XS | - | +| K21a-2 | Open remainder (measured, d459; the 2026-10-21 run `measure-2026-10-21` ran lexical-only, so it is not the hybrid ceiling): one 8766 and 8767 window for the `seeds.hybrid` row; the embedder premise gets no further benches (Direction), so it waits for the owner | Gold symbol in the top k per arm (Q2 `hit@10`, Q1/Q3 `set_recall`; where-to file hits), reported under `test-results/`; cycle 2's screening reads the table | S-M | K21a-1, K5a (baseline arms), L1, L4, I-1 | +| K22a | Open remainder (K22a-0 d461, K22a-2 d497 and K22a-3 d519 are done; `seeds.literal` stays off): the seedless-trait mechanism test with K9a's documents fake, which expands the file seed through `lsp.live` under `places = outline`; point the K5a rg arm at `lexical::parse_matches`; B2 on bevy and deno (about 55 min, `armB B2 traits bevy|deno`) only if signal 3 is wanted | Ceiling, beside an rg baseline arm | S | item 4 rest | | **M4-core** | **Maps, servers, safety, mining (unconditional)** | | | | -| K26 | K26-1 done (d528; PR 197): the frontier confirmer in `lci-maps`, unreachable until K26-2; K26-2 done (d559; PR 220): the `walk.confirm` key (default `false`), the app wiring in `maps::build`, the golden and docs; the value measurement (cost and quality per call) opens cycle 2. `grep.live` proposes, `lsp.live` confirms the top-k frontier (`walk.confirm`); no Codanna dependency | Cost and quality per call against the walk without confirmation | M | G1 | +| K26 | K26-1 (d528, PR 197) and K26-2 (d559, PR 220) are done: `walk.confirm` (default `false`); `grep.live` proposes, `lsp.live` confirms the top-k frontier; no Codanna dependency. Open: the value measurement, cost and quality per call against the walk without confirmation; it needs a release quiet window with no cargo, and its placement (cycle 1 or 2) is the owner's cut (d563) | Cost and quality per call against the walk without confirmation | M | G1 | | K9c | Type-hierarchy edges. paused(measurement), moved to cycle 2: the K24c control shows SCIP `implements` edges already bring traits to the regex-rg level (d459); LSP type-hierarchy edges matter only under `scip.mode = off` with `places = outline`, which no cycle 1 run measures | Ceiling | S | - | -| K12a | Done: K12a-1 (d529; PR 206): `index_status` and `lci status` gain `servers[]` (per adapter: languages, languages in the index, configured, enabled, command, probe `probed`/`not_probed`, `breaker_open_ms`, root, `root_reason`, session) and `languages_without_server`, a report from the manager's memory with no probe run and no server started. K12a-2 (d533; PR 212), opt-in: `[run_profile.lsp] roots = "workspace"` (default, today's root) \| `"manifest"`, a server starts at the directory of the topmost manifest its adapter names among the directories of the index's files of its languages; no manifest, sibling manifests or an indexed file outside the root keep the workspace, with the reason in `servers[].root_reason`; a one-shot `lci traverse` under `per_call` has no file list (`no_file_list`). Premise check (private run record): pyright gains only when a name resolves through a nested project's own settings (`extraPaths`: workspace root 0 of 7, manifest root 5 of 5); typescript-language-server finds the nested `tsconfig.json` itself and gains nothing; one stray top-level file returns the root to the workspace. The claim is that narrow pyright gain; gopls, csharp-ls and cost are unmeasured, the default flip stays gated on the lsp-cost rule, and one session per sibling module is not built. The per-server live acceptance was not run: only pyright and typescript-language-server were exercised against real servers, in the premise check | Live acceptance per server | S | N4 | -| K12c | Done (PR 183): marksman adapter for prose | Live acceptance | S | N95 | -| K18a | Built (d485, merged in PR 185): `seeds.repo_map`, an opt-in start-node map under `[run_profile.seeds] repo_map` (default off; K18a-1): personalized PageRank over the SCIP graph (calls and containment, `implements` edges left out, damping 0.85, at most 30 iterations), with the jump on the top 64 `seeds.bm25` matches, so a function the question's matched functions call becomes a seed even when its own name holds none of the question's words; its top `seed_k` nodes go after the hybrid and `seeds.bm25` ones. No bm25 match, no seeds (no global hubs). Open: the ceiling run (`maps bench --set traits --set where-to --max-depth 0 --include-seeds`, waves `k18a-rm`, `k18a-bm25-rm`, plus the depth-1 walk `k18a-walk` against the K24c control; report the reorder rate against bm25's top 10); park K18a-2 (the SCIP-free graph) when the reorder rate is under 30 % and where-to does not move | Ceiling | M | G2 | -| K19 | Built (d483): `grep.structural`, an opt-in map under `[run_profile.grep] structural` (default off): ripgrep names the files that hold a symbol's name as a whole word, an ast-grep pattern over each file's tree (the chunker's own grammars) keeps the `impl`, `extends` and `implements` headers (`Implements` edges) and the calls (`CallsOrRefs` edges). `ast-grep-core =0.40.1` is the last release on tree-sitter 0.25: it moves with K15a. Open: the ceiling run (`maps bench --set traits --max-depth 1`, waves `k19-ctl`, `k19-on`, `k19-only`; A = the K24c control arms, B = A + the map, C = the map as the only implements source; no gain claim, d422 (11); `maps bench` turns the key on in the arm's derived overlay when `--maps` names the map) | Ceiling on structural queries | M | - | -| K32-1 | `tests.tested_by` (opt-in map over the SCIP impact index, d458): built; ceiling run done (d513): no change, no gain claim (`maps bench --set impact` with and without the map, `tests_recall` and `tests_deep_recall` per stratum) | Ceiling | S | - | -| K32-2 | `docs.mentions` (`Mentions` edges from documentation, a wrapper of `grep.live` over `SourceRole::Documentation` paths, d462): built; ceiling run done (d513): no change, no gain claim (`maps bench --set impact` with and without the map, `docs_recall`). `i1_section_*` is not a check of this row: I1 needs DocSection nodes and section lines (K32-2b, unplanned, cycle 1 optional) | Ceiling (`docs_recall`) | S | K32-1 | -| K6a | Done (d457; K6a-1 strata, K6a-2 miner): `cargo xtask gold strata` and a `guard` stratum (`core`, `grep_easy`, `agent_trap`) on every scorer, from the gold item alone; `cargo xtask mine-transcripts` counts question shapes and trace classes over synthetic transcripts (the sim trace shape), writing shapes and counts only to `test-results/transcripts/`. Estimated S, was two S (strata plus miner) | Tests | S + S | - | -| K6b | Done (d464): question shapes mined from real Claude Code and Codex transcripts, locally. `cargo xtask mine-transcripts --counts-only` (vocabulary-only report) and the stop `answered` (classed like `gold`) are the only repository change; the converters stay outside the repository. Chains by source, grep_easy/agent_trap/core/unresolved (cells under 5 hidden): Claude interactive (422) 71/<5/64/284, Claude subagent (2230) 111/399/767/953, SDK harness (1196) 217/6/268/705, Codex interactive (173) 21/<5/44/106, Codex subagent (30) <5/<5/<5/25. Per-shape cells, quantiles and the reading against the gold `guard` mix are in d464 | Counts | S | K6a | +| K12a | K12a-1 (d529) and K12a-2 (d533, opt-in `roots = "manifest"`) are done. Open: the default flip, gated on the lsp-cost rule (the claim is only the narrow pyright gain; gopls, csharp-ls and cost are unmeasured); the per-server live acceptance (only pyright and typescript-language-server ran against real servers); one session per sibling module (not built) | Live acceptance per server | S | N4 | +| K18a | Built (d485, PR 185): `seeds.repo_map`, opt-in under `[run_profile.seeds] repo_map` (default off). Open: the ceiling run (`maps bench --set traits --set where-to --max-depth 0 --include-seeds`, waves `k18a-rm`, `k18a-bm25-rm`, plus the depth-1 walk `k18a-walk` against the K24c control; report the reorder rate against bm25's top 10); park K18a-2 (the SCIP-free graph) when the reorder rate is under 30 % and where-to does not move | Ceiling | M | G2 | +| K19 | Built (d483): `grep.structural`, opt-in under `[run_profile.grep] structural` (default off). Open: the ceiling run (`maps bench --set traits --max-depth 1`, waves `k19-ctl`, `k19-on`, `k19-only`; A = the K24c control arms, B = A + the map, C = the map as the only implements source; no gain claim, d422 (11); `maps bench` turns the key on in the arm's derived overlay when `--maps` names the map). `ast-grep-core =0.40.1` is the last release on tree-sitter 0.25 and moves with K15a | Ceiling on structural queries | M | - | | **M4-conditional** | **Codanna, grammar upgrade, optional tools, external gold, baselines: each runs in cycle 1 when its condition holds** | | | | | K14b | Cargo feature `syntax-codanna`: parse one file through Codanna into LCI nodes | Ceiling on fixtures | M | blocked: owner and Codanna repo owner; K14a NO-GO as specified (d422) | | K14a2 | Run the built Codanna 0.17.1 binary on the offline TypeScript ground truth (384 and 291 distinct pairs) and join its edges with it; pre-register the kill rule first (suggested: stop below 95% precision or below 85% recall on `this.m()` plus bare calls) | Precision and recall per call shape | S | held for the owner (d422) | @@ -105,56 +85,18 @@ an arm works: no slice claims a gain over rg or LSP before the paired screening | K16 | Codanna parser tests as fixtures; its `tsconfig`, `go.mod` and `pyproject` readers for server root discovery | Tests | S | N4 (part); blocked(K14b) | | K15a | tree-sitter 0.26 and its grammar crates (javascript and python also to 0.25, K14a); every chunking test re-run; adapter version bump; default-build regression check. Waits for the Codanna decision or a measured grammar need; ast-grep-core needs tree-sitter ^0.25.4 (K19): `=0.40.1` is the last 0.25 release, so K15a re-pins it (0.40.5 or 0.44.1 for 0.26; 0.45 and later need 0.27) | Existing tests | M | - | | K17 | `oxc_resolver` for TS and JS imports, only if K14a2 shows Codanna's TS resolution is weak | Imports against typescript-go | S | - | -| K12b | typescript-go adapter: K12b-1 in progress on fakes (d535: section `[tsgo]`, provider `typescript-go`, `LCI_TSGO_PATH`; typescript and tsgo both enabled is a config error); no merge before the tsgo facts check and the lsp-cost pair against typescript-language-server (d537) | Live acceptance | M | - | +| K12b | typescript-go adapter: K12b-1 is built on fakes on its branch and not merged (d535: section `[tsgo]`, provider `typescript-go`, `LCI_TSGO_PATH`; typescript and tsgo both enabled is a config error); parked pending the owner's cut (d563); no merge before the tsgo facts check and the lsp-cost pair against typescript-language-server (d537) | Live acceptance | M | - | | K31 | Opengrep data-flow map, opt-in; consumes Opengrep's existing flow analysis, no engine written | Ceiling on a flow set | M | - | -| K4a | Done (K4a, d484): `cargo xtask gold external convert` turns a SWE-PolyBench Verified `test.csv` into where-to exemplar items at file granularity with the leak stratum, licence, url, revision and sha256 in every item, `--known-repo` overlap marks and a counts report; the LongCodeArena column adapter (`lca_row_from_columns`) is tested on synthetic rows; held out, never used for tuning (stays in cycle 1) | Leak check | S | - | -| K4b | Done (K4b, d499): `cargo xtask gold external fetch` (official Hugging Face files pinned at the revision `main` has, `fetch.json` with url, bytes and sha256, refuses a different hash without `--force`; `Fetcher` test double) and the LongCodeArena JSONL reader (no `parquet` crate, d499: the parquet files go through a one-off conversion outside the repository); leak-rule fixes from the K4a review (stem token split, repository owner ignored, wider generic directory tokens); the real runs: SWE-PolyBench Verified 382 rows, 382 items, leak 298, clean 84; LongCodeArena test split 150 rows, 100 items, leak 85, clean 15, 50 Kotlin rows dropped; counts in the PR body and d499 (`test-results/` is gitignored) | Counts | S | K4a (done) | -| K5a | Baseline arms rg-only and LSP-over-MCP wired into `maps baseline` (no A/B runs); not blocked. K5a-1 landed: the rg arm, `cargo xtask maps baseline rg --set traits or where-to` (d443), with three named rules: `fixed`, `fixed-decl-first` (`--decl-first`) and `regex` (`--rule regex`, traits only; the `rg_arm.py` rule behind d392). K5a-2 remains: LSP over MCP (the `workspace/symbol` arm), after K11d-1's `McpSession` harness | Smoke query | S | item 6 arm | -| K5b | Baseline arms Codanna and Serena wired (no A/B runs); the Serena arm is not blocked, only the Codanna arm waits for the owner. K5b-serena done (d527; PR 198): `cargo xtask maps baseline serena --set traits or where-to or impact` (xtask only, no profile key, no config field), Serena 2.1.0 (GPL-3.0-or-later application, MIT SolidLSP) run as a separate process over MCP Streamable HTTP on loopback (nothing linked or vendored), on a per-wave COPY of the frozen tree because Serena writes `.serena/` into its project; the tree's file-list hash before and after is recorded in `meta.json`; rules `symbol` (`find_symbol`, then implementations or referencing symbols) and `pattern` (`search_for_pattern`, the rg arm through Serena); needles as the rg arm; `expansions` and `model_calls` 0. The Serena path comes from `--serena` or `LCI_BASELINE_SERENA`. A baseline beside the seed arms, never a gain claim. The Codanna arm waits for the owner | Smoke query | S | - | +| K5a | K5a-2 remains: the LSP-over-MCP baseline arm (the `workspace/symbol` arm) in `maps baseline`, after K11d-1's `McpSession` harness (K5a-1, the rg arm, is done, d443); no A/B runs; not blocked | Smoke query | S | item 6 arm | +| K5b | The Codanna baseline arm (no A/B runs) waits for the owner; the Serena arm is done (d527, PR 198) | Smoke query | S | - | M0 filler, any time a build slot is free: K0a follow-ups review: a breaker-open embedder skip in traverse `search.vector` still counts one external call (`AppChunks::embedding_skipped` could return the models' `embedder_skipped()`), and the `EXTERNAL_CALLS` doc in src/app/traverse/mod.rs says no map makes any (d416). The stat-gated re-hash left this filler and landed as K38 (d440). -Finish line of the cycle 1 report (d422): M2 + M3 + M4-core plus every unblocked M4-conditional slice. Conditional slices -still run in cycle 1 when their condition holds; K4a and K4b do not move to cycle 2. +Cycle 1 finish line (d422): M2 + M3 + M4-core plus every unblocked M4-conditional slice. Implementation is complete (d563, d564); the rows above are what is left, and the owner's cut (see "Now and next") decides which stay in cycle 1 and which move to cycle 2. Deferred to cycle 2: rust-analyzer in process (`ra_ap_*`), ty or Pyrefly (in process or as servers), ts-rust as a TS server arm, rung 2 model training, a gix blame map, a tantivy engine for K21 (only after a measured gap), the K22a-3 follow-ups (d519: literal seeds restricted to definitions or an occurrence prior; a larger impact `seed_k`, which needs `--seed-k` forwarded by `maps bench`). -### Cycle 1 time estimate - -Agent wall time per slice, for the whole loop (plan, implement, Full gate, review, PR, CI): XS 0.5 h, S 1.5 h, S-M -2.5 h, M 3.5 h. Measured on 2026-10-20 for S slices only (K1a, K1b, K1c in two parts: 1 to 1.5 h each, one at a -time; the Full gate takes about 25 min and CI about 20 min); K0a part 1 (S-M) took about 3.5 h with two review -rounds. M is not measured yet. Update this table at each milestone end with the measured hours. Re-base it at the end of M2 (d422): the warm Full gate -is now about 3 min and CI about 8 min (d415, d419), so per-slice time is review rounds and serial merges, and the -`Merge wait` column then records the measured wait for a merge into `main`. Re-based on 2026-10-21 (d459): slice counts are the open rows -and hours are the size table's figures; the Measured column holds the measured wall time. - -| Milestone | Slices left (by size) | Sequential hours | Measured | Merge wait | Note | -| --- | --- | --- | --- | --- | --- | -| M1 | K0a part 2, K7a, K2a (M), K2b (S-M), K23a, K35a (XS), K34a | ~12 | ~20 h wall (roadmap merge 03:49Z to K2b), with K7b-1, K7b-2 and K9a of M2 and a 2.5 h speed-up lane in parallel | not tracked | five lanes in parallel since 2026-10-20 | -| M2 | 5 active: SHARED (M), K11e + N103 (2 S), K20b-1 (S, implemented, in review), K11b (S), K11d-2 (S); K7c (S-M) conditional on K11d-2's `base` arm | ~11, K7c +2.5 | ~30 h wall so far, with K11d-1 at 2 h machine time; closed 2026-10-23 about 13Z, about 3.4 days after the roadmap merge (2026-10-20 03:49Z) with lanes in parallel; K11b-6 deferred to cycle 2 | not tracked | serial chain SHARED, K11e, K20b-1, K11b: three default changes, each a review round of about 2 h; K37b follows SHARED; closes 10-22 evening at the earliest (usage stop to the weekly reset, N108 before K11b) | -| M3 | K22a-3 closed as a finding (d519); K30a (PR 160) landing; K22a-0, K22a-2 and the docs PR done (d461, d497) | ~1.5 | ~8 h wall plus 6.5 h measurement machine time (lane A 3.5 h, lane B 3 h, debug builds under load, for 25 to 90 min of estimate), plus K22a-2 about 5 h wall (build 32 min, main run 4 h 12 min, release, under load; d497) | not tracked | later measurement lanes use a release build of the frozen commit, one lane at a time (d459) | -| M4-core | K26 (M), K18a (M), K19 (M), K12a, K12c, K32-1, K32-2, K37b (moved to M2), K6a, K6b (S each) | ~20 | - | - | closes about 2026-10-23; K32-3 (pickaxe) is a decision, K9c is cycle 2 | -| M4-conditional (unblocked) | K5a-2, K5b, K4a, K4b, K12b (M), K31 (M); K15a and K17 on their conditions | ~15 | - | - | about 2026-10-23 to 2026-10-24; most uncertain: Codanna on tree-sitter 0.26, typescript-go, marksman, Serena and the ast-grep pin on Windows, live server checks, external gold | -| Total | about 27 slices left | ~51 with K7c, plus about 5 h of measurement machine time | - | - | hours are the size table's figures, not re-measured per slice | - -Pace on 2026-10-21 (sanity 10, d459): PRs 139 to 159, 21 merges in 8 h (09:36 to 17:39 local), 2.6 per hour with four lanes, -33 merges since midnight. Parallel lanes (separate worktrees, up to three cargo builds gated on free RAM) are limited by -dependencies, by merges into `main` one at a time and by the build slots. Estimate from the remaining work above: M2 and M3 -close about 2026-10-22 12:00Z (superseded for M2 by sanity 13, d505: see its Note), M4-core about 2026-10-23, the unblocked M4-conditional slices about 2026-10-23 to 2026-10-24; -the earlier 2026-10-22 to 2026-10-23 end holds for M2, M3 and M4-core only with one review round per M row. Measurement -machine time is about 5 h (the SEED_MAPS fix and K22a-2 1 to 3 h, the SHARED stamp walk and an edit smoke check 1 h, K11d-2 -1 to 2 h, N8 1 h). Risks, largest first: the weekly usage stop (thrift is suspended until 2026-10-22T16:00Z, but the 95 % -weekly and 85 % five-hour rules still cut lanes); SHARED's 1 s stamp-walk kill rule on deno-sized trees (30k files); lanes on -debug builds under load; M rows with two review rounds (about +30 %); Windows gaps for marksman, Serena and the ast-grep -tree-sitter pin; outputs dying with worktrees (copy to `work/run` first); this file's "Now and next" going stale after every -merge burst (d476: feature PRs no longer touch it, so it lags until the next docs PR). - -S evidence from the closed external-gold group (sanity 13, d505): K4a took about 2.7 h wall (21:1xZ to the 23:5xZ merge) and K4b -about 2 h (23:5xZ to 01:38Z), one review round each, both S. No Measured cell is filled from them. Machine time so far for M2: the -K11d-2 counts pass (about 1 h 20, 00:27Z to 01:45Z) plus builds; the latency pass, N108 and the after-pass have since run (d509, d510). - ## Earlier items outside cycle 1 | # | Item | Status | Notes | @@ -177,40 +119,20 @@ P3 umbrella research. Rows are open items only. Item text names symbols, never l | E6 | Identifier-query stratum: `cargo xtask mine-symbols` samples declaration names (seeded, names of 5+ characters, defined in one file, not test-only) with `expected_paths` = the defining file, tag `identifier`, committed apart from `self.toml`; unit tests on synthetic input; one fusion-only and one reranked baseline recorded (roadmap@bbd3292 E6) | ready | S | E2 done | | N8 | The LSP channel goes dark for 15 to 25 s windows under back-to-back searches (hypothesis: rust-analyzer swaps its crate graph after build scripts and proc macros load, invalidating the primed symbol index); diagnose and fix (roadmap@bbd3292 N8) | absorbed(K7b (readiness), K11d (the live diagnosis)) | S-M, live | N7 done | | N16 rest | A workspace search on a legacy-only table waits on the store-wide legacy guard; the revision pin and the re-read after a commit during the freshness check are not pinned by a failing test (roadmap@bbd3292 N16) | ready | S | - | -| N18 | Low batch (item (b), the Java readiness probe, was fixed as N77, d190): (a) `manifest::save` removes the destination before `rename`; (c) `serve --port 0` advertises port 0; (d) three navigation tests assert rust-analyzer's flag; (e) the embedding model listing is probed at the origin; (f) no per-file size cap (roadmap@bbd3292 N18). Landed in PR 195: (a) `manifest::save` removes the destination before the rename; (c) `serve --port 0` uses the bound port for the log, `/openapi.json` and the Origin check; (d) navigation tests assert their own adapter's running flag; (e) the model listing probe keeps the configured URL's path prefix (`model_listing_url`) and falls back to the origin `/v1/models` on 404 or 405; also the stale self LSP acceptance needle for `openapi_document`. Open: (f), the per-file size cap (item (b) was N77) | partly done (PR 195): (f) stays open | f: XS | - | -| N20 | Cancellation and lock scope: (a) done (the MCP `index_workspace` and `search_code` handlers drop their work when the request's cancellation token fires, as axum drops a REST handler; spawned scan threads still finish, see (d); the other MCP tools (navigation, recipes, `watch`/`unwatch`, `index_status` and the rest) still run to their end after a cancel); (b) lock scope; (c) done (a watcher pass queued for the workspace write lock ends on `unwatch`); (d) partly done (the data-directory file lock was fixed in N91; a `probe.compile` probe now waits for its scratch-checkout gate only as long as the query budget; still open: the scan threads in `src/app/indexing.rs` (`chunk::scan_incremental_with`, `fingerprint_with`) outlive a dropped request and run to their end, which needs cooperative cancellation in the scanner); (e) Ctrl+C graceful shutdown has no deadline (roadmap@bbd3292 N20); per-server request ordering (the part K37a was meant to absorb) is closed: no queue is needed (d475: no cross-talk observed; id matching by construction) | ready | S-M | N16 done | -| N21 | Two open test-quality items (a third, a fixed 500 ms sleep in the data-dir lock test, was fixed as N83, d193): (a) `evaluation_reports_required_failures_optional_skips_and_json_metrics` checks only that `hit_at_1_rate`, `mrr` and `role_distribution` exist, not their values (check whether another test pins them); (b) its last assert checks an `EvaluationFile` the test built itself (tautological): exercise `load_definition` instead (roadmap@bbd3292 N21) | done (PR 196): the JSON-metrics test pins `hit_at_1_rate`, `mrr` and `role_distribution` to hand-derived values (0.0, 0.0, {}) with a poisoned skipped query proving exclusion, and loads its definition through `load_definition` instead of asserting a hand-built `EvaluationFile` | XS | - | -| N25 | Done (N25, test-only): the six weak assertions in `tests/cases/indexing.rs` now measure by count or structure: (a) the hung reranker test also checks that the warning names the timeout, and its 10 s bound is a 25 s hang guard; (b) a new test counts the connections a dropping reranker accepts (one per search, no retry) and the refused-port test keeps only a hang guard (a refused connect cannot be counted by a server); (c) the cold and priming language-server tests read the fake server's message and `workspace/symbol` counts (a search that gave up at its budget leaves it inside `initialize` or its first request), the 1200 ms bounds no longer exist and the remaining clock bounds are marked as lower bounds or hang guards; (d) the stop-word query now has its words in the fixture; (e) the traced-search test asserts one-based, dense ranks per channel; (f) every `mixed_languages` deletion goes through a helper that checks the exact chunk count the file held, before and after, in the listing, the report and `status` | done | XS | - | +| N18 | Low batch, open part: (f) no per-file size cap. (a), (c), (d) and (e) are done (PR 195, d567); (b) was N77 (d190) | partly done: (f) stays open | XS | - | +| N20 | Cancellation and lock scope, open parts: (b) lock scope; (d) the scan threads in `src/app/indexing.rs` (`chunk::scan_incremental_with`, `fingerprint_with`) outlive a dropped request and run to their end, which needs cooperative cancellation in the scanner; (e) Ctrl+C graceful shutdown has no deadline; the other MCP tools (navigation, recipes, `watch`/`unwatch`, `index_status` and the rest) still run to their end after a cancel (roadmap@bbd3292 N20). Done: (a) (d551, d552), (c) and part of (d) (d412, d199), per-server request ordering (no queue needed, d475) | partly done | S-M | N16 done | | N26 c | `search_symbols` has no result cap; record the size of a broad name's answer first (roadmap@bbd3292 N26) | ready | XS | - | | N32 | Rewrite the model-facing strings within budgets: `search_code` 350 characters (436 today), `search_symbols` 280 (328), instructions 450 (732); state facts only (d278): what comes back, the first-call index cost and the navigation preconditions; no advice on when to prefer Grep, which goes to an on-demand skill measured as its own arm. Its former kill-rule thresholds move to the cycle 2 screening design (d396). Text is `cloud-ok`, the A/B arm is `local`. See [agent-payload-notes.md](agent-payload-notes.md) (roadmap@bbd3292 N32) | ready after N26 | S | N26 | | N33 | Schema documentation for the four search and navigation tools: describe `query`; list the valid `languages` and `source_roles` values (roles: source, test, benchmark, example, generated, documentation, configuration); state glob syntax, that an empty `include_paths` matches nothing and that empty language or role lists are errors; document `include_declaration`; describe `workspace_path` on every tool (today only on `WorkspaceArgs`, 73 characters); stop hard-coding "(8)" in the `top_k` text; drop what N32 makes redundant (the language-server list repeated 4 times, about 400 characters). Acceptance: a test that every argument of the four tools has a description; `tools/list` no larger than today (roadmap@bbd3292 N33) | ready | S | N32 | | N34 | `default_top_k` 5 versus 8 as a measured arm: 61 appearances, 57 at rank 5 or better, ranks 6 to 8 carry 18.7% of returned lines, 20 of 94 answered files were at rank 6 or later (U1 calls); lowest priority (roadmap@bbd3292 N34) | ready, local | S | N28 done | | N42 | Incremental-index peak memory: `Store::cached_vectors` plus a vector clone scale with the whole index (estimate 0.9 to 1.5 GB at 150,000 to 250,000 chunks of 768 floats, not measured); measure first; fix direction: load only vectors whose chunk hash is still present and move instead of clone; acceptance: state the bound and a test fails above it; `cloud-ok` (roadmap@bbd3292 N42) | ready, low | S | - | -| N48 | Windows verbatim `\\?\` prefix at paths of 260 characters or more breaks prefix comparisons (roadmap@bbd3292 N48) | Done: `dunce::canonicalize` keeps the prefix of a path of 260 characters or more while a shorter root has none, so the workspace/data-directory overlap check, `source_path`'s inside-the-workspace check, ripgrep's relative paths and LSP location paths now compare through `lci_core::path::strip_root`/`is_within`, which ignore a `\\?\` spelling on either side | XS-S | - | -| N87 | The live-acceptance workflow does not use `--require-all`, so a missing tool reads green (roadmap@bbd3292 N87) | Done: `live-acceptance.yml` is now one step, `cargo xtask live-acceptance --require-all`, so a skipped tool fails the run; the workflow never runs on pull requests, so it cannot affect `test (windows-latest)`; a unit test pins the command | S | N56 done | -| N94 | Extension comparisons are case-sensitive in 11 places (clippy pedantic census): a `.RS` or `.Py` file is skipped; decide and test (d371) | done (d522, PR 193): extensions compare as ASCII case-insensitive text | S | - | -| N95 | A Markdown language server (marksman, MIT) for prose navigation: links, headings, references; the language bar's LSP item is waived for prose until then (d375) | absorbed(K12c) | S | - | | N96 | Re-check the error-to-commit premise (item 5) with full clones and the error text of linked GitHub issues, under a new pre-registered rule (d377) | ready | M | - | -| N99 | System-1 rescoring rung: a decision model orders the reranker's 24-candidate shortlist, the seam where N98 (killed) showed the reranker's value (d382). Gate K0 passed on a hosted System One prototype arm (d383); slice 2 (the opt-in rescorer rung, with status-only HTTP errors, transmitted trace sizes and a joint-batch replay guard) merged as PR 88 and the default is unchanged; K1 passed (slice 3, d385); slice 4 failed bar S (d387): the local 4B decider loses on flow and mined search and needs about 19 GB of VRAM, so the reranker stays the default and the rung stays opt-in. The hosted model is never shipped | done (local rung killed, d387) | M | - | -| N100 | Where-to routing: an un-steered `traverse` call on a where-to question returns 16 answers (8 seeds and 8 walk) where the `where-to` preset returns the 8 first-stage answers, with set_f1 +0.015 [+0.005, +0.026] and 42% fewer answer tokens; cut to 8 the two are identical, so the gain is the shorter list (d389). Whether to route such questions to the preset (question-type detection, or a fact in the tool description under the facts-only rule) needs its own pre-registered test. Detection (b) was killed at B0 (R 0.339 against the 0.70 bar, d391) | partly done: detection killed (d391); routing is the caller's `steer.policy` (K30a, d456); the description fact (a) is parked until a tier-3 run is approved | S | - | -| N101 | The 5 s readiness probe of `lsp.live` (`PROBE_TIMEOUT`, the server `--version` call in `ManagerBackend::open`) times out under machine load and fails a walk before the server is asked anything; seen in K9a's runs. Decide a configurable or wider deadline and test it under load | partly done: the default is 15 s and `lsp.live` warm-starts shared servers at the first touch (d450); the probe ms under lane load is re-measured after merge (`lsp-cost --arms shared`) | XS | - | +| N100 | Where-to routing: whether to route an un-steered `traverse` call on a where-to question to the `where-to` preset (8 answers instead of 16: set_f1 +0.015 [+0.005, +0.026] and 42% fewer answer tokens, the gain is the shorter list, d389), by a fact in the tool description under the facts-only rule; it needs its own pre-registered test. Detection (b) was killed at B0 (R 0.339 against the 0.70 bar, d391) | partly done: detection killed (d391); routing is the caller's `steer.policy` (K30a, d456); the description fact (a) is parked until a tier-3 run is approved | S | - | +| N101 | The 5 s readiness probe of `lsp.live` (`PROBE_TIMEOUT`, the server `--version` call in `ManagerBackend::open`) timed out under machine load and failed a walk before the server was asked anything (seen in K9a's runs); the default is now 15 s and `lsp.live` warm-starts shared servers at the first touch (d450). Open: re-measure the probe ms under lane load (`lsp-cost --arms shared`) | partly done (d450): the re-measure is open | XS | - | | N102 | `[run_profile.lsp] sync` on Windows: `notify` 8.2 stops watching a directory without telling the handler when ReadDirectoryChangesW fails with anything but aborted or access denied (a 16 KiB buffer overflow during a large checkout gives ERROR_NOTIFY_ENUM_DIR), so the sync goes silent and never rescans. Detect it (a periodic stat check of the table, a bridge of notify's `log` errors into a rescan, or a larger buffer upstream) and re-open the watcher with a rescan | Unit test with an injected watcher failure; a burst of thousands of file writes on Windows reaches the server | S | K8a | -| N103 | Done (d486, second commit of the K11e + N103 PR): `lsp_start_timeout_seconds = 0`, decided: a start bound of its own, default = the request bound (`0` keeps today's behaviour, the start is bounded by `lsp_timeout_seconds`; not in the profile hash). Was: a language server's `initialize` runs under the per-request `lsp_timeout_seconds` (an inactivity timeout; default 60 s, so it bites a user who sets a low value, and the tests that did), and a timed-out `initialize` is a transport failure that is not retried (`fake_csharp_lsp_timeout_and_malformed_protocol_return_bounded_errors` pins one spawn): where the value is low a healthy server whose process start is slow (a loaded machine; test fixtures of 5 s and 10 s failed under four parallel builds, d439) becomes a failed start. Decide whether the start gets its own wider bound (for example `lsp_start_timeout_seconds`) and `lsp_timeout_seconds` keeps bounding requests to a started server; test it with the fake server's `--initialize-delay-ms` | done | S | - | -| N104 | Navigation (`symbols`, `definition`, `references`) and the search channel's `provider_symbols` reuse the service's kept language-server sessions without a tree-stamp barrier, so a non-watching server's answer can predate an edit (pre-existing, found in the SHARED review 2026-10-21); decide a barrier or a per-call route after SHARED's index-list stamp lands. Done in the N104 + N106 PR (d495): navigation and the search channel pass the tree barrier once per call and carry `lsp:` notes (`NavigationReport.notes`, `SearchReport.notes`); owed: a release-build navigation p50 on the bevy tree against the 150 ms kill bound (not run) | Done (d495, PR 184) | S | - | -| N106 | K11e test gap (review of d474): no test fails if `per_call_manager` stops attaching the shared windows (`.with_breakers(..)` in src/app/traverse/mod.rs): the dead-binary case fails at the probe through the shared manager, and the second manager in the ceiling test borrows the windows directly. Add a fake that passes `--version` and exits at start, two walks, the second refused through the call's own manager. Done in the N104 + N106 PR (d496): fake flag `--exit-at-start`, test `a_per_call_manager_opens_the_offered_managers_windows` | Done (d496, PR 184) | XS | - | -| N108 | An edit-then-search loop restarts rust-analyzer on each call under the default (N104 extends d454's restart on a changed stamp to navigation and search). Measure the edit-loop cost of the tree barrier (d495) on a real Rust repo (bevy, `sessions = "shared"`): edit, `search_code`, edit, `search_code` ... with rust-analyzer kept in the service's manager (`maps lsp-cost` with an edit step between calls, no new harness). Record per edit: restarts, whether the LSP channel answered or ended as an over-budget note (post-edit searches whose LSP channel times out), and the milliseconds to ready. Only then decide whether the search channel alone keeps a `watches_workspace` adapter's session and labels the answer (keep and label) while navigation stays exact (barrier). Evidence-gated: d495's rule (the barrier for both) stands until the measurement says otherwise. Run in the quiet window with the K11d-2 latency pass (d505); before K11b | done (d510): 10 of 10 edits restarted the server, about 31 s to ready, so K11b adopts the watching-adapter exception on the search channel | S | - | -| N109 | `Manager::tree_barrier` (lci-core/src/lsp.rs, the stamp block near :1411-1442) restarts a kept session when the index has no file list or the stamp fails, and the caller's note then says `the tree changed since its last call`, which is false in those cases. Return a distinct reason per restart (tree changed, no file list, stamp failed) and word the note by it; the `restarted` list becomes reasons, so the navigation, search and traverse notes and the tests that pin them change together | done (d514, PR 191) | S | - | -| N110 | The channel-maps `LspMap` status (src/app/search/channel_maps.rs near :405-427) drops the barrier's `restarted` and `reused` that `lsp_channel` returns, so `fuse_through_maps` notes differ from the search report's. Fold them into its status note and drop the `without_barrier_notes` mask of tests/cases/indexing.rs where it is then unnecessary (optional, small) | done (d514, PR 191; the comparison tests still compare notes without barrier notes) | XS | N104 | -| N111 | ASYNC-2 B follow-up: a navigation call cancelled in the middle of `didOpen` whose write then succeeds records the text with no diagnostics wait, so the next caller skips the open and may be answered before the server's analysis ends. Keep a pending wait (per uri, with the version) for the next caller. PLAUSIBLE (Sol re-check 1), no real-call repro | Done (K7c-1, d544, PR 203): the wait is recorded with the sent text and settled by the hook that finished it | S | ASYNC-2 B | -| N112 | Flaky on CI once (2026-10-22, PR 188, docs-only, same code as the passing PR 187): `freshness::with_sync_on_the_session_is_told_about_the_edit_and_the_barrier_replaces_it` read the replacement session's `sync_status` as `walks 0`, no backend, before its start walk ran (expected `walks 1`). Wait for the sync's walk on a real signal (not a sleep) before asserting, or have the session start await its first walk | done (PR 190: the test waits on `synced()` before reading `sync_status`) | XS | N104 | -| N113 | The literal suite lengths that merges kept breaking (`args(Suite::Lsp).len()` style asserts) | done (PR 190): derived tests that every `tests/integration.rs` case module is in a focused suite or `FULL_ONLY_CASES` and every filter names a declared module | XS | - | +| N104 | Owed from the navigation and search-channel tree barrier (done, d495, PR 184): a release-build navigation p50 on the bevy tree against the 150 ms kill bound (not run) | partly done (d495): the measurement is owed | S | - | | N114 | A fourth `RestartReason` for a session with no recorded stamp: after a NoFileList or StampFailed barrier, the next good barrier says the tree changed for a fresh session (lci-core lsp.rs tree_barrier). From the B2 review (d514) | ready, low | XS | N109 | -| N115 | Load-sensitive tests. The Freshness cases `a_rewritten_scip_index_refreshes_the_scip_built_maps_by_its_stamp`, `without_the_key_the_labelled_maps_are_gaps_on_a_line_shift_too` and `with_the_stat_gate_on_the_labels_equal_the_full_hash_path` fail under machine load: `assert_no_timeout` trips when the walk deadline cuts a map (`config.key` was built in 22 s under load). Also lci-maps `probe_map::tests::cargo_gets_only_the_budget_the_gate_wait_left` sleeps a real 2 s inside a 5 s budget. Make the walk deadline in these cases generous or injected, and make the probe test use a fake clock or a smaller sleep, so no case depends on machine speed. Seen by the K11b-1/2 implementer (passed alone) | Done (PR 202, 2026-10-22): the freshness walks get a 10-minute test deadline (the config keeps the 60 s that validate accepts; `assert_no_timeout` is kept), and the probe gate test asserts cargo's timeout against the measured gate hold, with no wall-clock margin; test-only | S | - | -| N116 | `lci init` checks a configured port of 0 with `check_port(config.port)`, although `serve` binds a free port for 0 (N18 (c)); decide what init should check for port 0 and test it. From the N18 review (F5), pre-existing | Done: no code change was needed, `check_port(0)` has returned `Ephemeral` without binding since `lci init` landed (A7b) and `init` reports port 0 as "asks the OS for a free port at every start"; a new integration test pins that `init --json` reports state `ephemeral` for `port = 0` | XS | N18 | -| N117 | A failed language-server session pinned its workspace watcher: `ensure_sync` runs when a session begins, but the idle sweeper only considered sessions with a ready client, so a session whose server never started was never swept and its sync (thread, task, file table) lived until release or shutdown; reachable by default since K11b-4 through any navigation call for a language with no installed server. From the K11b-4 review | Done (K11b-4, d555, PR 215): the idle sweeper releases failed sessions and their watcher stops; a test and a mutation check cover it | XS | K11b-4 | | N121 | If the detached start task of a session panics before it publishes an outcome, `failed()` stays false and the session is never idle-swept; a panic during priming leaves the ready client set with `primed` false. No normal server failure takes either path. Fix: publish `Err` when the task ends (a drop guard) and treat a priming panic as settled. From the K11b-4 Sol re-check | ready, low | XS | N117 | -| N122 | Flaky test `lsp_concurrent_open::a_close_publication_does_not_end_a_reopens_diagnostics_wait` (on main since K7c; it failed under load in the K11b-4 and K20c-2 Full gates and passes alone): it assumes the re-open comes at least 500 ms after the close (close publication at +1500 ms, re-open at +1000 ms), but slow opens of 65 files under load push the close later, so the re-open publication arrives first and the count is 2. Fix: re-open only after the close publication is due, or wait for a count of at least 2 before re-opening | done (d558; PR 218): the fake LSP holds the close publication until the re-open (`--close-diagnostics-on-reopen`), so the order is made by events, not timing | XS | K7c | -| N123 | The lsp-cost report text labelled the version-probe timing row ` --version` although gopls runs `gopls version` (and python's probe is skipped), and claimed a 5000 ms bound the code does not impose. From the K11b-5 review | done (d557; PR 219, ef9bf71): the report labels each row with the probe the adapter runs | XS | K11b-5 | | N124 | `open_split` partial-failure visibility: when one provider's probe fails and another passes, the walk shows the drop only per node ("no language server is available for python"). Add one note per failed provider (`lsp.live: unavailable: `). Also from the K11b-5 Sol review: a broken pyright shim passes the start probe (`/ready` green), and an initialize inactivity timeout opens no breaker window. Deferred from K11b-5 commit 2 | ready, low | XS | K11b-5 | | N125 | Under per-language `places = "auto"` (K11b-6, cycle 2), a direct symbol seed in an unindexed language gets no `lsp.live` expansion until its file is reached and outlined (the `lsp_live.rs` ask returns without `place_of`); file and chunk expansions do outline. Option: outline the seed's file first. From the K11b-6 Sol review; not the cause of the K11b-6 failure (its python seed was a chunk) | blocked(K11b-6, cycle 2), low | XS | K11b-6 | | N14 | Lexical matches credited to the first chunk of a path-suffix match: tag only (see Earlier items outside cycle 1, row 10) (roadmap@bbd3292 N14) | paused | S | - | @@ -237,8 +159,6 @@ acceptance data is in the log. | S4 | Stop loading every chunk's code per search; interval maps for channel mapping (roadmap@bbd3292 S4) | ready | M | - | | W1 | mtime+size stat cache before hashing (manifest additive fields) (roadmap@bbd3292 W1) | ready | M | - | | W2 | `notify`-based watcher with polling fallback and persisted watch roots (roadmap@bbd3292 W2) | absorbed(K8a) | M | W1 preferred | -| N2 | LSP document lifecycle: open once, `didChange` on edits, `didClose`, drop fixed sleeps (roadmap@bbd3292 N2) | absorbed(K7c) | M | - | -| N118 | Watcher health is invisible: `Manager::sync_status` is read by no product path, and both watchers failing or the silent Windows stop (N102) only `warn!`. Expose the sync state in the status (a cheap belt). From the K11b-4 review | done (d560; PR 221): a top-level `sync` key in the status, not under `analyzers`: backend, a `failed` flag and the event, batch, forwarded, walk and rescan counters | XS | K11b-4 | | N119 | Burst cost under the `sync = "watch"` default: single events skip `.gitignore`, so the writes of `npm install` or `cargo build` become batches sent to the servers; the drain stats every path under a std mutex on a runtime worker; the poll fallback rescans the tree every 2 s. Measure on a large tree before changing anything. From the K11b-4 review | measure first | S | K11b-4 | | N120 | The search channel still restarts a synced non-watching server on a changed stamp (the K11b-2 exception keeps `watches_workspace` adapters only); the loss is seconds (csharp cold first call 9 s, go 1.8 s in the K11b-4 plan's estimate), not the 31 s of rust-analyzer in d510. Measured follow-up: extend the exception to synced servers (as fresh as the OS watcher, drained before every request). K11b-4 plan residual and Sol question 4 | ready after a measurement | S | K11b-4, N104 | @@ -246,7 +166,6 @@ Short accounts. C1: carries the `impl` header into method chunks and gives later tests per language and a before/after run on both sets. S1/S2/S4: a commit today overwrites the whole workspace table and every search loads every chunk; accept with an equal-results test and a timing run on a large workspace. W1/W2: freshness cost today is a read and SHA-256 of every supported file at most every 10 s; accept with a manifest compatibility test. -N2: accept with a live LSP test that a second symbol request reuses the open document. ### P2 - agent ergonomics, navigation by name, protocol upgrade From 548afe48621e9aa35f5a9b9715ea764abaa68723 Mon Sep 17 00:00:00 2001 From: Kadir Mert Okumus Date: Sat, 24 Oct 2026 00:37:14 +0300 Subject: [PATCH 2/2] Apply the S2a review: tip line, open rows, N123 traceability (d572) Co-Authored-By: Claude Opus 5.5 --- docs/decisions.md | 1 + docs/development/roadmap.md | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/decisions.md b/docs/decisions.md index a5d9248d..92d530de 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -556,3 +556,4 @@ result files when the documentation was cleaned up on 2026-10-17. | d569 | 2026-10-23 | DECISION | tests | N115 (PR 202, 2026-10-22, test-only): the Freshness cases `a_rewritten_scip_index_refreshes_the_scip_built_maps_by_its_stamp`, `without_the_key_the_labelled_maps_are_gaps_on_a_line_shift_too` and `with_the_stat_gate_on_the_labels_equal_the_full_hash_path` get a 10-minute test deadline (the config keeps the 60 s that validate accepts; `assert_no_timeout` is kept), and the lci-maps probe gate test asserts cargo's timeout against the measured gate hold, with no wall-clock margin, so no case depends on machine speed. | PR 202 (roadmap row N115 at 6d6a9bb) | | d570 | 2026-10-23 | DECISION | infra | tokio 1.54.1 (PR 224, merge 7ba065c, commit d8911b1; `Cargo.lock` only): RUSTSEC-2026-0386 (the `AsyncFd` constructors accept any raw fd, unix only; fixed in tokio 1.53.3 and later) failed the cargo-deny advisories check on every PR, so the lock moves to 1.54.1. | d8911b1 (PR 224; merge 7ba065c) | | d571 | 2026-10-23 | DECISION | process | Rule change in `docs/development/documentation.md` rule 10: the append-only log `docs/decisions.md` is exempt from the line target (the target `log <= 300` is removed; the log is 551 lines at 6d6a9bb, and moving old entries to an archive would break the frozen ids that are cited everywhere). README, guide and roadmap targets are unchanged. | cleanup S2a | +| d572 | 2026-10-23 | DECISION | lsp | N123 (from the K11b-5 review) is done: the lsp-cost report labels the version-probe row with the probe each adapter runs (`gopls version`; the python probe is skipped) and no longer claims a 5000 ms bound the code does not impose. Recorded so the id stays traceable after its roadmap row left in cleanup S2a. | ef9bf71 (PR 219); d557 | diff --git a/docs/development/roadmap.md b/docs/development/roadmap.md index 8bbcb478..29c4f63b 100644 --- a/docs/development/roadmap.md +++ b/docs/development/roadmap.md @@ -48,7 +48,7 @@ that document and is not a roadmap id. ## Now and next -Cycle 1 implementation is complete (finish line d422): the last feature merges were K39b (d564) and K12a-2 (d533); M2 closed 2026-10-23 without K11b-6, whose release-build measurement failed, so per-language `places = "auto"` moves to cycle 2 with mixed-tree surfacing (d562). On main: tip 6d6a9bb, last PR 225, last log id d571. A cleanup of docs and code is running. Next: the cycle 1 report goes to the owner; his cut and his answers to the report questions (asked 2026-10-23) follow, then cycle 2. Waiting on the cut: sanity 20 (d563) recommends K12b-2 and K31 move to cycle 2 and K12b-1 stay parked on its branch (nothing merges before the tsgo facts check and the lsp-cost pair, d537), and the placement of the K20c rust and K26 value windows (each needs a release quiet window with no cargo; the arbitration stays off until the K20c measurement passes), which sanity 20 proposes for cycle 2. K7b-3b and K11d-2b stay open plans for the same word, and ASYNC-2 C stays parked. Per the autonomy rule the coordinator waits for the owner's word after the report. Only docs PRs and the coordinator rewrite this section; a feature PR leaves it alone so parallel PRs do not conflict (d476). The live state is the coordinator's run checkpoint. +Cycle 1 implementation is complete (finish line d422): the last feature merges were K39b (d564) and K12a-2 (d533); M2 closed 2026-10-23 without K11b-6, whose release-build measurement failed, so per-language `places = "auto"` moves to cycle 2 with mixed-tree surfacing (d562). On main: last PR 225 (6d6a9bb) before this cleanup PR, which appends d565 to d572. A cleanup of docs and code is running. Next: the cycle 1 report went to the owner on 2026-10-23; the owner's cut (asked 2026-10-23) and the answers to the report's questions follow, then cycle 2. Waiting on the cut: sanity 20 (d563) recommends K12b-2 and K31 move to cycle 2 and K12b-1 stay parked on its branch (nothing merges before the tsgo facts check and the lsp-cost pair, d537), and the placement of the K20c rust and K26 value windows (each needs a release quiet window with no cargo; the arbitration stays off until the K20c measurement passes), which sanity 20 proposes for cycle 2. K7b-3b and K11d-2b stay open (their rows), and ASYNC-2 C stays parked. Per the autonomy rule the coordinator waits for the owner's word after the report. Only docs PRs and the coordinator rewrite this section; a feature PR leaves it alone so parallel PRs do not conflict (d476). The live state is the coordinator's run checkpoint. ## Cycle 1 @@ -66,10 +66,10 @@ Ceiling and mechanism checks show that an arm works: no slice claims a gain over | K10a | Open remainder (K10a-1 d442 and K10a-2 d449 are done): placing a Symbol seed whose file was never outlined (module-path inversion, with K21a, d442), and outlining destination files so `holder` names a reference in a never-outlined file | Tests | not sized | - | | K11d | K11d-2b remainder (K11d-1 d438, K11d-2a d509, d510 and d512 are done; the `open` arms do not exist because K7c added no key, and K7c's before and after on the same arm text replaced them, done for typescript and python, d545): the typescript `outline_cache` interaction that d424 deferred to K7c is unmeasured (typescript opens documents since d544); N8 (the analyzer state at each dark call, with K7b-3a's states, d446); the `shared+calls` arm of K9b | Report under `test-results/lsp-cost/` | S | N8 (live diagnosis) | | K20c | K20c-1 (d534, PR 207) and K20c-2 (d556, PR 216) are done: `[run_profile.scip] arbitration` (default `false`), `role` labels and counts. Open: the pre-registered rust measurement (false-overrule rate at most 5 %, the edit pass, no card or deadline loss; the coordinator's lane, with a Sol review for the flip; rust only, d556), which needs a release quiet window with no cargo; the arbitration stays off until it passes; its placement (cycle 1 or 2) is the owner's cut (d563) | False-overrule rate, edit pass, card and deadline loss | S | - | -| K11b | K11b-6 only (K11b-1 to K11b-5 are done: d523, d524, d550, d555, d557): per-language `places = "auto"` (SCIP places where the loaded graph has a node, else the server's outline, per language). Not merged (branch `feat/k11b-6-per-language-auto`): its pre-registered release-build measurement failed, 0 python `lsp.live` cards on the mixed tree, because Contains edges carry prior 0.5 and the 20-pop budget goes to the rust seeds' neighbours (d562). Cycle 2, together with mixed-tree surfacing (Contains prior, budget, top_k, a python-seeded mixed question, a discovered-nodes rule; seed-side before priors, d563), under a new pre-registered plan. Until then `places = "auto"` is per workspace, not per language (d553) | Pre-registered mixed-tree release measurement | S | K11b-1 to K11b-5 (done) | +| K11b | K11b-6 only (K11b-1 to K11b-5 are done: d523, d524, d550, d555, d557): per-language `places = "auto"` (SCIP places where the loaded graph has a node, else the server's outline, per language). Not merged (branch `feat/k11b-6-per-language-auto`): its pre-registered release-build measurement failed, 0 python `lsp.live` cards on the mixed tree, because Contains edges carry prior 0.5 and the 20-pop budget goes to the rust seeds' neighbours (d562). Cycle 2, together with mixed-tree surfacing (Contains prior, budget, top_k, a python-seeded mixed question, a discovered-nodes rule; seed-side before priors, d563), under a new pre-registered plan. Until then `places = "auto"` is per workspace, not per language (d553) | Pre-registered mixed-tree release measurement | S | - | | **M3** | **Walk, cards, seeds, router, cost** | | | | | K13 | Open remainder (answer labels are done, d453): a `freshness` word on the search report's hits (XS follow-up, after the owner sees the traverse labels); flipping `stat_gate`'s default is its own slice (d440) | Card schema test | XS | - | -| K21a-2 | Open remainder (measured, d459; the 2026-10-21 run `measure-2026-10-21` ran lexical-only, so it is not the hybrid ceiling): one 8766 and 8767 window for the `seeds.hybrid` row; the embedder premise gets no further benches (Direction), so it waits for the owner | Gold symbol in the top k per arm (Q2 `hit@10`, Q1/Q3 `set_recall`; where-to file hits), reported under `test-results/`; cycle 2's screening reads the table | S-M | K21a-1, K5a (baseline arms), L1, L4, I-1 | +| K21a-2 | Open remainder (measured, d459; the 2026-10-21 run `measure-2026-10-21` ran lexical-only, so it is not the hybrid ceiling): one 8766 and 8767 window for the `seeds.hybrid` row | Gold symbol in the top k per arm (Q2 `hit@10`, Q1/Q3 `set_recall`; where-to file hits), reported under `test-results/`; cycle 2's screening reads the table | S-M | K21a-1, K5a (baseline arms), L1, L4, I-1 | | K22a | Open remainder (K22a-0 d461, K22a-2 d497 and K22a-3 d519 are done; `seeds.literal` stays off): the seedless-trait mechanism test with K9a's documents fake, which expands the file seed through `lsp.live` under `places = outline`; point the K5a rg arm at `lexical::parse_matches`; B2 on bevy and deno (about 55 min, `armB B2 traits bevy|deno`) only if signal 3 is wanted | Ceiling, beside an rg baseline arm | S | item 4 rest | | **M4-core** | **Maps, servers, safety, mining (unconditional)** | | | | | K26 | K26-1 (d528, PR 197) and K26-2 (d559, PR 220) are done: `walk.confirm` (default `false`); `grep.live` proposes, `lsp.live` confirms the top-k frontier; no Codanna dependency. Open: the value measurement, cost and quality per call against the walk without confirmation; it needs a release quiet window with no cargo, and its placement (cycle 1 or 2) is the owner's cut (d563) | Cost and quality per call against the walk without confirmation | M | G1 |