Skip to content

Modular scope resolution for iOS/Android/web, refusal and crash fixes - #57

Merged
khezen merged 3 commits into
mainfrom
modular-scope-resolution
Oct 23, 2026
Merged

khezen merged 3 commits into
mainfrom
modular-scope-resolution

Conversation

@khezen

@khezen khezen commented Oct 23, 2026

Copy link
Copy Markdown
Owner

Summary

Modular repositories (iOS Swift packages, Android Gradle modules, web monorepos) are now reviewed one module per scope. Before this change, peaks-ios PR #3276 resolved to a single root scope . with all 101 files. The PR also fixes the Bedrock content_filter refusals and two crashes seen on that PR.

Scope resolution

  • Depth tie bug (_assign_files): root . and first-level modules both had depth 0, and the tie-break put root first, so its empty prefix captured every file. Root now has depth 0 and other scopes slashes + 1. This affected every manifest one level below the root (e.g. SettingsFeature/, androidApp/).
  • Root-manifest guard (_apply_boundaries): a root manifest (e.g. a root build.gradle.kts or Gemfile) dropped every boundary the LLM proposed. Boundaries are now dropped only inside a non-root manifest scope, or when the root manifest is the only one.
  • Deterministic manifest choice: directory listings are sorted, and manifests are picked by an explicit priority. A root Gemfile no longer wins over Peaks.xcodeproj.
  • New manifests:
    • Apple: *.xcodeproj, *.xcworkspace, *.podspec, Podfile, project.yml (XcodeGen), Project.swift (Tuist)
    • Gradle: settings.gradle(.kts)
    • Web: deno.json(c), pnpm-workspace.yaml, project.json (Nx, only when the repo root has nx.json)
    • Go: go.work
    • Lock files: bun.lock(b), deno.lock, Package.resolved, Podfile.lock
  • Xcode projects: the manifest path points at project.pbxproj. Only a change to the SwiftPM lock (Package.resolved) inside the bundle counts as a dependency change. No scopes are created inside the bundle directories.
  • Package names: Gradle modules use their Android namespace. Xcode, podspec and Tuist projects use their project name.

Concurrency

  • New setting review.max_concurrent_scopes (default 8; env REVIEW_MAX_CONCURRENT_SCOPES, Action input max-concurrent-scopes). code_review, doc and supply_chain each limit how many scopes they review at once, MCP startup included.

Refusals and resilience

  • RLM steps no longer go through the extraction model. Each RLM step's answer is now split at the first code block: the text before it becomes reasoning, the block becomes code.
    • Before, the extraction model (nemotron-nano) sometimes wrote its own commentary into reasoning, e.g. "I need to extract the reasoning and code…" (6 of 20 in testing).
    • That text was fed back to Opus as REPL history and saved to memory, and Opus refused those prompts.
    • Replaying the saved refusal prompts: removing the leaked text stops the refusal in 5 of the 6 prompts that had REPL history.
  • Scope refinement failure now falls back to the manifest scopes instead of putting the whole PR in one root scope.
  • Audit failure no longer aborts the run. The review is posted with "Audit unavailable" and a fixed recommendation: NEEDS_DISCUSSION with findings, APPROVE without.

Crash fixes

  • Exception text is escaped in all CLI error prints, and the stdout review is printed without Rich markup. Before, text such as [/{accountID}] raised MarkupError, which hid the real error.

Breaking changes

  • Scope topic IDs change for Gradle modules (now their namespace) and for repo roots now detected through a project file (e.g. iOS root Peaks). Earlier memory stored under the old IDs is no longer recalled for those scopes. This is accepted, and no migration is planned.

Known follow-ups

  • Exception text in the posted review: the audit fallback includes the first line of the exception in the review posted on the PR. A provider error could put an account or role ARN there. Replace it with a fixed reason.
  • .xcworkspace manifest path: when it is the chosen manifest, its path stays a directory, which supply_chain can't read. Point it at contents.xcworkspacedata.
  • Polluted iOS memory: the existing peaks-ios repo-wide memory briefing still contains leaked extraction text. It needs regenerating or pruning, or first-step refusals will continue.

Testing

  • New tests:
    • tests/test_modular_scopes.py (19): iOS and Android layouts, manifest priority, Nx, LLM boundaries, parsers, concurrency cap, refinement and audit fallbacks
    • tests/test_rlm_action_split.py (6): the extraction model is never called for RLM steps, on both the sync and async paths
    • tests/test_rich_markup_safety.py (2)
  • Full suite: 687 passed. The same 20 failures and 3 errors exist on main (cerebral, config_memory, doc_extractor and review_memories tests).
  • ruff: no new findings in the changed files.
  • Replayed the refusal prompts against Bedrock, and checked the new RLM-step split on fresh Opus answers.

- Fix scope depth tie that folded first-level modules (Swift packages,
  Gradle modules) into the root scope
- Keep LLM scope boundaries when the root manifest is only a container
- Pick manifests deterministically by priority; add Xcode, CocoaPods,
  XcodeGen, Tuist, Gradle settings, Deno, Nx, pnpm workspace, go.work
- Extract Gradle namespace and Xcode project names as package names
- Add review.max_concurrent_scopes cap for per-scope fan-out
- Split RLM action steps locally so extraction-model commentary no
  longer reaches Opus (content_filter refusals)
- Fall back to deterministic scopes when refinement fails; soft-fail
  the audit instead of aborting the review
- Escape untrusted text printed through Rich markup
@khezen
khezen merged commit 51949d2 into main Oct 23, 2026
6 checks passed
@khezen
khezen deleted the modular-scope-resolution branch October 23, 2026 16:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant