Skip to content

fix(context,impact): bug-report intent, named-repo scoping, UI-page required owners - #240

Merged
nugehs merged 1 commit into
mainfrom
fix/context-impact-intent-scoping-gaps
Sep 28, 2026
Merged

nugehs merged 1 commit into
mainfrom
fix/context-impact-intent-scoping-gaps

Conversation

@nugehs

@nugehs nugehs commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Otito was used today to review a real cross-repo fix (bashbop-api PR #527, bashbop-mobile-app PR #21, bashbop-event-web PR #590 — ticket-scan/QR check-in + date-timezone display bugs). This PR fixes the three ranking/intent gaps that session found, root-caused against otito's own source, and adds regression tests that reproduce each gap and prove the fix. npm run skills:check fails identically on a clean origin/main checkout — pre-existing, unrelated to this change, not touched here.

Base branch note: origin/develop exists but is a strict ancestor of origin/main (0 commits ahead, 6 behind — all "Merge pull request # from BASHBOP/develop" merges that never made it back onto develop). Branching from or targeting the stale develop would pull in an unrelated 6-commit delta and risk conflicts, so this PR is branched from and targets main, the actually up-to-date integration branch. Flagging this in case develop is meant to be kept current.

Gaps found, evidence, root cause, fix status

# Symptom (observed 2026-09-28) Root cause Fixed
1 context_pack("...bugs in the mobile app", paths:[mobile,api]) → intent.action: "unknown", open question "requested action is ambiguous" inferIntent only recognizes verbs in actionWords (add/build/.../review/test/update); a symptom-framed query ("... bugs") never names one Yes
1 Same call: primary files almost all from the API repo despite "in the mobile app"; only 1 of 8 primaries was mobile "mobile" and "app" are in context-engine's stopWords (too generic for content matching), so the one explicit repo-scoping word in the query was silently discarded before scoring ever saw it Yes
3 change_impact on bashbop-event-web, "deprecate web scan-ticket page..." → app/dashboard/scan-ticket/page.tsx (top-scored, actually-changed) ranked "advisory"; MobileTicketButton.tsx/TicketPurchaseDialog.tsx (weak "ticket" overlap, not actually changed) became "required" REQUEST_BOUNDARY_KINDS includes route, code-map's kind for a Next.js page.tsx/layout.tsx (a UI screen), and gates required-owner candidacy behind literal API vocabulary (api/endpoint/form/payload/request/route/submit) that a page-deprecation request naturally never uses Yes
2 change_impact on bashbop-api, multi-clause ticket/mobile/web query → organizer-ticket-sales.controller.ts/.dto.ts (unrelated "ticket sales" reporting) ranked as "required owners"; booking.controller.ts (actually changed) ranked 7th as merely "supporting"; a long tail of unrelated authentication/* files pulled in as fan-out impact.js's scoreFile sums path+symbol+export+import+route hits with no per-term frequency dampening (context-engine.js already has this as tokenWeightFactor/computeTokenDocFrequency; impact.js never did), so a file whose entire domain name is a generic query word (here "ticket") stacks that one weak signal across five fields Partially — see below
2 The real new file (src/booking/utils/ticket-qr.util.ts, kind source) never became a required owner even though it was the single highest-scored, diff-confirmed file genericOwnerFallback (the pool that lets source/hook kind files ground a task) only runs when no conventional owner kind (controller/dto/...) matched at all — once any controller matched generically, the real utility-file owner was never even considered Yes
2 Inferred concepts: none for the bashbop-api query CONCEPT_SYNONYMS only covers auth/payment/data-model/request-surface/config vocabulary; a booking/ticket/date-display domain has no concept bucket Not fixed — see below
3 Missed every inbound link to /dashboard/scan-ticket (routes.ts, nav, guide, PublishChecklistCard.tsx) and the 5 messages/*.json i18n files No reverse-reference signal for routes/URL string literals; translation catalogs are demoted by design unless the query is about copy Not fixed — see below
— UserPromptSubmit hook routed the original request to tier "mid" Out of scope: that's model-route.js/the prompt hook, not context_pack/change_impact scoring; not investigated in this pass Not investigated

What was fixed

  • src/lib/context-engine.js
    • inferIntent: a debugSynonyms set (bug/bugs/bugfix/broken/crash/crashes/crashing/regression/regressions) now yields intent.action: "debug" when no verb from actionWords is present. A query with neither still correctly reports "unknown" (regression-tested).
    • computeRepoHints (new): when a context_pack call spans 2+ repos and the raw query names one of them by a word unique to its folder name (discriminating segments only — a shared prefix like every Bashbop repo's bashbop- doesn't count), that repo's files get a +40 bonus and every other queried repo's files are multiplied by 0.6. No-ops for single-repo calls and for a hint that would name every queried repo.
  • src/lib/impact.js
    • REQUEST_BOUNDARY_KINDS: dropped route (UI page). apiRoute (app/api/**/route.ts) already covers the actual API-boundary case; controller/dto/apiClient are untouched and still gated.
    • computeTokenDocFrequency/tokenWeightFactor (new, ported from context-engine.js with identical thresholds and the same totalFiles < 12 neutral-on-small-repos guard): every path/symbol/export/import/route match in scoreFile is now weighted by how common that term is across the indexed repo.
    • classifyImpactRoles: genericOwnerFallback's candidates are now merged into the conventional-owner pool (mergeOwnerCandidates, new) instead of being used only when the conventional pool is empty, so a strong source/hook-kind owner can outrank a weaker conventional-kind one.

Measured before/after (below) uses the live library against the real repos with diffBase: origin/main, matching what change_impact/context_pack do internally.

Gap 1 — context_pack, mobile+api, "...bugs...in the mobile app"

Before After
intent.action "unknown" "debug"
openQuestions ["The requested action is ambiguous..."] []
primaryFiles (limit 8) repo split 7 bash-server / 1 bashbop 4 bash-server / 4 bashbop
mobile files in primaryFiles tickets/index.tsx only tickets/index.tsx, hooks/useScanner.ts, lib/scanner.ts, lib/bookingTicket.ts (at limit 12, also app/(tabs)/scanner.tsx)
test/scanner.test.mjs, test/eventDateDisplay.test.mjs absent from tests both present

Not fully solved: lib/eventDateDisplay.ts, lib/exploreFeed.ts, components/ScanResult.tsx still don't clear the top-8/12 cut (the diversifyByDomain cap of 2 files/domain in primaryFiles is a separate, deliberate mechanism this PR did not touch).

Gap 3 — change_impact, bashbop-event-web, "deprecate web scan-ticket page..." (diffBase: origin/main)

Before After
requiredOwners MobileTicketButton.tsx, TicketPurchaseDialog.tsx, BookingTicket.tsx (none actually changed) app/dashboard/scan-ticket/page.tsx (the one actually-changed file)
validation.confirmedDirect [] ["app/dashboard/scan-ticket/page.tsx"]
page.tsx role advisory (score 146, #1 by score, but excluded from required) required

Not fixed (documented, not attempted here): the 18 missedChangedFiles include inbound-link files (routes.ts, nav, PublishChecklistCard.tsx) and the 5 locale JSON files — no reverse-reference/route-usage signal exists yet, and translations stay demoted by design.

Gap 2 — change_impact, bashbop-api, multi-clause query (diffBase: origin/main)

Before After
requiredOwners organizer-ticket-sales.dto.ts, organizer-ticket-sales.controller.ts (neither changed) scan-ticket.dto.ts (not changed, false positive remains), ticket-qr.util.ts (changed — the real fix file), organizer-ticket-sales.controller.ts (not changed, false positive remains)
validation.confirmedDirect [] ["src/booking/utils/ticket-qr.util.ts"]
booking.controller.ts score / role 63.2 / supporting 75.9 / supporting
missedChangedFiles count 10 11 (scan-ticket.dto.ts false positive now also present; the underlying set of real changed files not yet surfaced is effectively unchanged)

Improved (a real changed file now confirms), but organizer-ticket-sales' "ticket sales" domain and the actual "ticket scan/check-in" domain remain lexically entangled — see "deliberately not fixed" below.

What was deliberately not fixed, and why

  • Gap 2's remaining false positives (organizer-ticket-sales.*, scan-ticket.dto.ts as required owners; real changed files like booking.controller.ts/events.service.ts/constants.ts still not "required"). The IDF dampening ported here helps but isn't enough: "ticket" is still specific enough (~14% doc frequency in bashbop-api) to only get a 0.55× haircut, not enough to overcome a file whose path+symbols+exports+route all repeat it. The principled fix is porting context-engine.js's phrase-matching (extractPhrases/scorePhraseMatches — "ticket scan"/"scan ticket" as a 2–3 word unit) into impact.js, which would specifically distinguish "ticket scan" from "ticket sales" without relying on frequency alone. That's a second, similarly-sized change touching the same scoring core that every impact.js consumer (AX, model-route, convergence) depends on; bundling it here risked under-testing it under this review's time budget. Left as explicit follow-up.
  • Gap 3's missed inbound links and i18n files. Needs a genuinely new signal (grep/AST for the route's string literal across the repo, e.g. /dashboard/scan-ticket) that context-engine.js/impact.js don't have in any form today — a bigger, separate feature, not a targeted bug fix.
  • Inferred concepts: none for booking/ticket/date-display domains. CONCEPT_SYNONYMS is intentionally a small, curated vocabulary (auth/payment/data-model/request-surface/config); expanding it to every product domain risks the concept-demotion mechanism becoming noise. Left alone.
  • UserPromptSubmit hook tier routing ("mid"). Not investigated — that's the prompt hook / model-route.js, a different code path from context_pack/change_impact, out of scope for this review pass.

Checks run

All from a clean checkout, in this order — baseline on origin/main first, then on this branch.

Baseline (origin/main, clean checkout, before any change):

Check Result
npm run format:check ✅ pass
npm run lint ✅ pass
npm run typecheck ✅ pass
npm run version:check ✅ pass
npm run docs:diagram:check ✅ pass
npm run skills:check ❌ fails — .cursor/skills/otito-scope/SKILL.md and .codex/skills/otito-scope/SKILL.md drifted from codex/skills. Pre-existing, unrelated to this change.
npm test ✅ pass — 787/787
npm run test:coverage ✅ pass — 94.64% lines / 81.14% branches / 95.98% functions (thresholds 70/60/75)

This branch (after the fix):

Check Result
npm run format:check ✅ pass
npm run lint ✅ pass
npm run typecheck ✅ pass
npm run version:check ✅ pass
npm run docs:diagram:check ✅ pass
npm run skills:check ❌ fails identically to baseline — same two files, unrelated to this change, not touched here
npm test ✅ pass — 795/795 (787 baseline + 8 new regression tests)
npm run test:coverage ✅ pass — 94.66% lines / 81.17% branches / 96.00% functions

CI note: the task brief for this review flagged that GitHub Actions on the BASHBOP org was not starting jobs due to a billing/spending-limit issue. That did not reproduce on this PR: all checks ran and passed — Docs build, Generate PR review context, Otito readiness, and Quality gates all green (auto-merge shows "skipping", which is expected, not a failure). See https://github.com/BASHBOP/otito/pull/240/checks.

Test plan

  • npm run format:check / npm run lint / npm run typecheck clean on this branch
  • npm test — 795/795 passing (8 new regression tests reproduce each fixed gap)
  • npm run test:coverage — thresholds met
  • npm run version:check / npm run docs:diagram:check clean
  • npm run skills:check fails identically to a clean origin/main baseline (pre-existing, documented, not touched)
  • Before/after measured directly against the three real Bashbop repos (bashbop-api, bashbop-mobile-app, bashbop-event-web) with diffBase: origin/main
  • GitHub Actions CI — all checks passed (Docs build, Generate PR review context, Otito readiness, Quality gates); contrary to the task brief's billing-outage warning, Actions ran normally for this PR

… multi-repo queries, stop excluding UI pages from required owners

Root-caused against a real cross-repo review (bashbop-api/mobile-app/event-web
ticket-scan and date-display fixes) that otito scored itself:

- context-engine: a query with no verb from actionWords (e.g. "... bugs in
  the mobile app") came back with intent "unknown" and the ambiguous-action
  open question, even though "bugs" is an unambiguous debug signal. Add a
  debug-synonym set (bug/bugs/broken/crash/regression/...).
- context-engine: "mobile" and "app" are stopWords for content scoring (too
  generic to match file text with), so a multi-repo query naming one repo by
  a word from its own folder name had that signal silently discarded, and a
  larger sibling repo's hotspots dominated primaryFiles. Add computeRepoHints:
  when 2+ repos are queried and the raw query names one by a discriminating
  folder-name segment, boost that repo's files and softly demote the rest.
- impact: REQUEST_BOUNDARY_KINDS gated `route` (a Next.js page.tsx/layout.tsx,
  a rendered UI screen) behind the same "does the query use an API word"
  check meant for actual request-boundary kinds (apiRoute/controller/dto).
  A page-deprecation request naturally uses no API vocabulary, so the
  changed, top-scored page.tsx was excluded from requiredOwners while
  unrelated components that merely shared a word became "required" instead.
  Drop `route` from the set; `apiRoute` already covers real API endpoints.
- impact: ported context-engine's IDF-style tokenWeightFactor/
  computeTokenDocFrequency dampening (a query term that recurs across most of
  the repo counts for less) to impact.js's path/symbol/export/import/route
  matching, which never had it.
- impact: genericOwnerFallback (the pool for `source`-kind owners like
  *.util.ts) only ran when no conventional owner kind matched the query at
  all, so a strong utility-file owner was never even considered once any
  controller/dto/component also matched generically. Merge the two pools and
  let files compete on score instead of treating the fallback as last resort.

Regression tests added: tests/context-engine.test.js (debug-intent inference,
repo-hint boost with an unhinted control and a single-repo no-op check),
tests/impact.test.js (tokenWeightFactor/computeTokenDocFrequency unit test,
UI-page-route required-owner, controller still boundary-gated, source-kind
vs conventional-kind owner-pool merge).
@nugehs

nugehs commented Sep 28, 2026

Copy link
Copy Markdown
Collaborator Author

Agent Experience (AX) before/after

Measured with otito ax <repo> <query> --json on origin/main vs this branch, against the three Bashbop fix PRs this change was calibrated on:

Repo / query main this branch
bashbop-api: "fix ticket scan QR payload suffix and overnight check-in closing" 71 good (chg 94, cont 38, guard 100, clar 20; 6 files) 73 good (chg 95, cont 41, guard 100, clar 20; 5 files)
bashbop-event-web: "deprecate web scan-ticket page, direct organisers to iOS app download to scan tickets" 69 good (chg 99, cont 46, guard 75, clar 20; 2 files) 70 good (chg 100, cont 46, guard 75, clar 20; 2 files)
bashbop-mobile-app: "ticket scanning (QR check-in) and date/time display formatting bugs in the mobile app" 59 fair (chg 96, cont 7, guard 75, clar 43; 6 files) 61 good (chg 99, cont 11, guard 75, clar 40; 5 files)

Containment improves because fewer false-positive owners are pulled in. Clarity stays at 20 for the API and web queries: the concept vocabulary still infers no concepts for the booking/ticket domain (left as a follow-up in the description).

@nugehs
nugehs merged commit 8eaf16a into main Sep 28, 2026
5 checks passed
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