You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: changelog + backlog for smoke/BAD_JSON wave; ship .mcp.json
[Unreleased] entries + BACKLOG wave/HANG findings; re-rendered changelog.html; commit the previously-untracked .mcp.json the repo is documented to ship.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+25Lines changed: 25 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
8
8
## [Unreleased]
9
9
10
+
### Post-v13.3 execution wave (2026-05-20)
11
+
12
+
#### Added
13
+
14
+
- **B6 — adversarial × MCP-sampling "Dungeon Master".** `roam_adversarial` gains an async `compress_mode` parameter (closed enum `off` / `digest` / `defend`, default `off` so the tool stays a pure superset with zero behavior change). `digest` summarizes the adversarial challenges via `Context.sample`; `defend` threads a defender-framing system prompt into the sampling call. `compress_with_sampling` now takes a `system_prompt` kwarg so defend-mode's framing actually reaches `ctx.sample` (previously dead code). An unknown enum value sets a loud `summary.compress_mode_invalid: true` sentinel (Pattern-1D) while preserving the verdict (LAW 6). Counts unchanged (core `57` / registered `227` / commands `241`).
15
+
- **B8 — spectral-forecast block in `roam forecast`.** The forecast envelope now carries a one-shot `spectral_forecast` block (instability + decay-rate + decay-alert wording) computed from the current graph's spectral gap, plus a `topology_decay_rate_definition` sidecar. Option-B implementation: no DB column, no `USER_VERSION` bump; the decay projection reports `insufficient_history` until a persisted gap series lands. Findings are envelope-only (forecast is invocation-scoped).
16
+
- **Pre-push cascade-prevention gate.** New `scripts/prepush_check.py` (`--fast` / `--full`, per-step timing, copy-paste fix command on failure) bundles ruff (format + check) + the count/render drift scripts + an 11-test AST-scan structural-lint set into a ~43s local gate, plus a thin `.githooks/pre-push` shim that delegates to it. Catches the "new code trips a repo-wide drift guard the author never ran locally" class behind this session's fix-forward cascade. New tests `tests/test_prepush_gate_wired.py` pin the hook → script wiring and assert every FAST-set test file exists. Brings `scripts/` under ruff discipline (cleared 5 latent lint errors).
17
+
- **Bounded all-command smoke harness (dev tooling).** New `dev/roam_smoke.py` runs every canonical CLI command once, argless, in `--json` mode — each in its own subprocess with `stdin=DEVNULL` (immediate EOF, no console-handle hang) and a hard per-command timeout, on a small thread pool so one slow/hung command can't stall the sweep. A finite list run once = no loop; subprocess isolation = one crash never blocks the next. It is a HANG/CRASH detector first (argless invocation is intentional — a clean usage/error envelope is healthy Pattern-1 behavior); the failure classes it hunts are HANG, CRASH, and `BAD_JSON` / `EMPTY_STDOUT` Pattern-1C envelope violations. Output is incremental JSONL + a human summary; safe to Ctrl-C. This sweep caught the 11-command BAD_JSON usage-path class fixed below.
18
+
19
+
#### Fixed
20
+
21
+
- **W1280 — feature-envy false-positive cut (~91%).** The feature-envy detector was a pure cross-FILE outbound-edge-ratio heuristic (>=4 edges, >50% of targets in another file); a 24-sample dogfood measured ~88% false-positive / 0% true-positive — mostly `tests/` files and Click command / `emit_` / `build_` / `collect_` / `_section_` orchestrators that reference many modules by design. The predicate now (1) skips test-role files (canonical `is_test_path`) + orchestrator/assembler-named functions, and (2) requires the external refs to be concentrated on a single foreign file (true envy) rather than spread across many. Result on roam-code: feature-envy **2036 → 177 rows** (~91% drop, zero test-file leakage, not zeroed — 177 genuine single-foreign-file cases remain); total smells **7794 → 5936**. The finding message now names the dominant-foreign-file share so the claim is self-explaining. `FEATURE_ENVY_DETECTOR_VERSION` 1.0.0 → 1.1.0; composite `SMELLS_DETECTOR_VERSION` 1.4.0 → 1.5.0. +3 regression tests (orchestrator-named, spread-across-files, test-role-file all assert no fire) alongside the existing genuine-envy positive case.
22
+
- **dangerous-eval `RegExp.exec()` / declaration-line false-positives.** The code-injection detector matched `<regex>.exec(` and `function exec(` / `def exec(` declaration lines as eval-class sinks (~80% FP on JS/TS in dogfood). Added guards that skip dotted `.exec(` unless the receiver is a shell-exec module (`child_process` / `cp`) and skip declaration lines. `dangerous-eval` `DETECTOR_VERSION` → 1.1.0.
23
+
- **hotspots `py-eval-exec` identical false-positive (family-closer).** Same dotted-`.exec(` and `def exec(` declaration-line guards applied to the `py-eval-exec` security sink in `cmd_hotspots.py`.
24
+
- **god-components flagged dataclass fields as CRITICAL.** `classify` keyed only on file path and never on symbol kind, so high-fan-in `prop` / `field` symbols (e.g. `EvidenceArtifact.path`) landed in actionable-CRITICAL god components. Added a kind-aware guard so non-logic kinds are excluded/down-banded.
25
+
- **vibe-check boilerplate-inflation rate exceeded 100%.** `_detect_boilerplate_inflation` mixed per-occurrence and per-file counts, yielding rates like 163.7%. Now counts per-file so the rate stays in [0, 1].
26
+
- **W564 CI cascade seal.** Removed the inline `_SEVERITY_ORDER` table in `adversarial_compress.py`; it now delegates to the canonical `severity_rank` (`src/roam/output/_severity.py`), closing the severity-drift lint gate the B6 prototype tripped.
27
+
- **Pattern-1C — 11 commands dumped plain text on their argless `--json` usage path.** In `--json` mode, the no-argument / usage-guidance path of `preflight`, `plan`, `affected-tests`, `ask`, `relate`, `context`, `file`, `history-grep`, `ingest-trace`, `report`, and `skill-generate` emitted Click usage text, a bare `VERDICT:` line, or raw markdown — non-JSON stdout that a wrapper-bridge JSON parser chokes on (collapses to `COMMAND_FAILED`). Each now routes its argless path through `json_envelope()` with a standalone `summary.verdict` (LAW 6) + `state` + `partial_success` + an imperative `hint`, matching the canonical `cmd_grep` usage-path shape; text output is unchanged. New drift-guard `tests/test_json_usage_path_envelope.py` pins the SHAPE (stdout parses as an envelope with `command` + `summary.verdict`) across all 11 plus the two pre-existing positive controls (`grep` / `invariants`). Surfaced by the `dev/roam_smoke.py` BAD_JSON sweep.
28
+
- **health — fabricated `algebraic_connectivity: 0.0` when numpy+scipy absent (Pattern-2 honesty).** `roam health --json` exported a hard-coded `0.0` Fiedler value when the eigensolver substrate is missing, indistinguishable from a legitimate 0.0 disconnected-graph reading. A new `fiedler_failed` flag now distinguishes "couldn't compute" from a real measurement: the JSON exports `algebraic_connectivity: null` + a companion `algebraic_connectivity_available: false` (plus the existing `health_algebraic_connectivity_failed:` `warnings_out` lineage marker), at both the summary and top-level sites; text mode prints `n/a (requires numpy+scipy)`. MCP `_SCHEMA_HEALTH` updated at both sites to declare the new boolean. +2 regression tests in `tests/test_w607_m_cmd_health_warnings_out_envelope.py` (unavailable→null, available→verbatim value).
29
+
- **W1287 — shotgun-surgery detector re-implemented (~1472 → ~27 rows, wrong-axis FP).** The detector fired on `graph_metrics.in_degree > 7` — pure INBOUND popularity, ~100% FP / 0 TP in an 18-sample dogfood (the top hits were the codebase's best-factored shared symbols: conftest fixtures, `open_db` / `json_envelope` / `to_json` helpers, dataclass fields; 69% in `tests/`). High inbound reference count is good factoring, the opposite of the smell. Re-keyed onto Fowler's actual axis: the count of DISTINCT NON-TEST CALLER FILES referencing the symbol (file-SCATTER — how many separate files a change ripples across). Deliberately conservative behind a high `_SHOTGUN_MIN_CALLER_FILES = 12` threshold + the W1280-style test-role / `@property` / dataclass-field / trivial-accessor exclusions, so a well-factored repo reports ~zero rows by design. `SHOTGUN_SURGERY_DETECTOR_VERSION` 1.0.0 → 1.1.0; composite `SMELLS_DETECTOR_VERSION` 1.5.0 → 1.6.0. Tests in `test_smells.py` rewritten: genuine file-scatter fires, concentrated-popularity (50 refs from 1 file) does NOT, test-role target does NOT, below-threshold does NOT.
30
+
- **W335/W342 — impact's reported blast-radius count now agrees with preflight (Pattern-3a + cap disclosure).** `roam impact`'s default `--max-callers 100` capped both the displayed dependents AND the reported COUNT, so `affected_symbols` silently understated the true radius and contradicted `preflight`'s gate for the same symbol. The reported total is now the honest uncapped reach — computed by the IDENTICAL `nx.descendants`-over-reverse-graph computation `cmd_preflight._check_blast_radius` uses — while only the listed dependents stay capped for response size. A new `BLAST_RADIUS_AFFECTED_TOTAL` sidecar (in `metric_definitions.py`, surfaced as `affected_metric_definition`) names the shared computation so the parity is provable. The display cap is now disclosed LOUDLY (`cap_applied` + `displayed` + `total` + a verdict that reads `listing 100 of N affected symbols … raise --max-callers to list more`) instead of silently truncating the count (Pattern-1 variant-D lineage). Risk classification + reach-pct now run on the honest total too. New `test_impact_total_agrees_with_preflight_over_cap` (120-caller fixture) asserts impact↔preflight parity; the existing cap/depth tests split into `displayed` vs `total`.
31
+
- **W837 — forecast + coverage-gaps emitted Pattern-2 success envelopes on an empty corpus.** `roam forecast` on a freshly-indexed symbol-less repo produced a misleading "spectral failure band" verdict from a degenerate 2-node file graph (gap 0.0 read as `is_failed`) — a clean run when there was nothing to forecast. It now probes the symbol count directly (the indexer writes a snapshot row even for an empty corpus, so snapshot count is not a reliable empty signal), and on zero symbols replaces the verdict with an explicit `no data to forecast — corpus empty` line + `state: no_data` + `partial_success: true`, suppressing the spurious spectral clause. A repo WITH symbols but <3 snapshots stays honest (`insufficient snapshot history`, no partial flag). `roam coverage-gaps` had verdict-less envelopes on its no-gates / no-entry-points branches (`summary.error` only, no `verdict` → `None` to a LAW-6 consumer); both now carry a standalone verdict naming the absent gates/entry-points + `partial_success: true` + a closed-enum `state` (`no_gates` / `no_entries`) + `agent_contract.facts`. New tests `tests/test_w837_forecast_empty_corpus.py` + `tests/test_w837_coverage_gaps_empty_corpus.py`.
32
+
- **understand ↔ health verdict-label divergence (Pattern-3a / LAW-6).** `roam understand` labelled a 75/100 health score "healthy" via an inline `>=70` cutoff while `roam health` calls 75 "Fair" (its band reserves "Healthy" for `>=80`) — one score, two contradictory verdicts across commands. New canonical `src/roam/quality/health_band.py` owns the single band table (`>=80 Healthy / >=60 Fair / >=40 Needs attention / <40 Unhealthy`, mirroring `cmd_health._compose_verdict`); `understand` now routes its score→label through `health_band()` and stamps `health_band` + a `health_band_definition` Pattern-3a sidecar on the envelope. `quality/__init__.py` documents the new shared metric alongside `cycles` / `god_components` / `public_symbols`. New `tests/test_understand_health_band_parity.py`.
33
+
- **fan — test-role symbols/files crowded out the headline ranking (test/prod split).** `roam fan`'s #1 fan-in on roam-code was the `invoke_cli` conftest fixture (2438 refs) — pure test noise burying real production coupling. Mirroring `cmd_uses`' production/test scope split (both now classify each subject via the canonical `is_test_file` helper), `fan` drops test-role rows from the headline ranking by default and annotates every shown item with a `scope` field. A new `--include-tests` flag opts them back in. The drop is disclosed loudly — summary `test_split` / `production_items` / `test_items` / `test_filtered`, a text-mode `NOTE:` line, and a distinct `all_filtered_tests` empty-state verdict — never silent (Pattern-1-D / Pattern-2 lineage). File-mode oversamples 5× so the headline still fills after the filter. New `tests/test_fan_test_prod_split.py`.
0 commit comments