Skip to content

Commit 5783b0e

Browse files
committed
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.
1 parent c8cfd69 commit 5783b0e

4 files changed

Lines changed: 140 additions & 0 deletions

File tree

.mcp.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"mcpServers": {
3+
"roam-code": {
4+
"command": "roam",
5+
"args": ["mcp"]
6+
}
7+
}
8+
}

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
77

88
## [Unreleased]
99

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`.
34+
1035
## [13.3] — 2026-05-19
1136

1237
### MCP runtime security wave (2026-05-18)

0 commit comments

Comments
 (0)