Skip to content

Feat/eng 2974#724

Open
RyanNg1403 wants to merge 9 commits into
proj/language-selectionfrom
feat/ENG-2974
Open

Feat/eng 2974#724
RyanNg1403 wants to merge 9 commits into
proj/language-selectionfrom
feat/ENG-2974

Conversation

@RyanNg1403
Copy link
Copy Markdown
Collaborator

Summary

  • Problem: ENG-2691 shipped language selection but only through brv config set language.* (project-config flat file). TUI users and WebUI users had no way to discover or change their language — the panels listed by brv settings list weren't aware of language at all.
  • Why it matters: every other user-configurable knob (concurrency, LLM budgets, task-history size, update checks) is in brv settings. Language being the lone outlier in project config made it invisible to non-CLI users and forced docs to explain two separate "settings" surfaces.
  • What changed: extended the SETTINGS_REGISTRY type system with an enum descriptor variant, registered language.mode (auto / fixed) and language.code (24 ISO-639-1 options) under a new 'language' category, and wired the wire/CLI/TUI/WebUI layers to render and mutate them. The curate read site now consults daemon settings first; brv config set language.* is sunset with a clear pointer to the new command.
  • What did NOT change (scope boundary): per-curate --lang flag, per-domain language overrides, first-run language wizard, and the MCP brv-curate-tool tool description (still built at server-boot from the auto clause — separate follow-up). The 24-entry LANGUAGE_NAMES map is unchanged; it was extracted to src/shared/language/language-names.ts so settings + WebUI + language-clause share one source.

Type of change

  • Bug fix
  • New feature
  • Refactor (no behavior change)
  • Documentation
  • Test
  • Chore (build, dependencies, CI)

Scope (select all touched areas)

  • TUI / REPL
  • Agent / Tools
  • LLM Providers
  • Server / Daemon
  • Shared (constants, types, transport events)
  • CLI Commands (oclif)
  • Hub / Connectors
  • Cloud Sync
  • CI/CD / Infra

Linked issues

  • Closes ENG-2974
  • Related ENG-2691 (foundation; original brv config set language.*)

Root cause (bug fixes only, otherwise write N/A)

N/A — feature work.

Test plan

  • Coverage added:
    • Unit test
    • Integration test
    • Manual verification only
  • Test file(s):
    • test/unit/core/domain/entities/settings-registry.test.ts — enum narrowing, language.* entries, category propagation
    • test/unit/infra/transport/handlers/settings-handler.test.ts — LIST exposes options, SET rejects non-string to enum keys
    • test/unit/shared/utils/format-settings.test.ts — enum row builder + parser + category ordering
    • test/unit/infra/storage/file-settings-store.test.ts — keyset assertion updated
  • Key scenario(s) covered:
    • findSettingDescriptor('language.mode') narrows to EnumSettingDescriptor with options: ['auto', 'fixed']
    • SettingsHandler.LIST includes both language entries; options is omitted from non-enum DTOs
    • SettingsHandler.SET rejects language.mode: 5 with expected: 'enum', accepts language.mode: 'fixed'
    • buildSettingsRows produces enum row with displayCurrent: '[ auto ]', category-ordered after updates
    • parseRowInput accepts a valid option, rejects unknown options with the allowed list
    • Full suite: 8869 passing, 16 pending, 0 failing

User-visible changes

  • brv settings list adds a LANGUAGE section with two rows: language.mode ([ auto ] / [ fixed ]) and language.code (e.g. [ en ]).
  • brv settings set language.mode fixed — accepted; rejects invalid values with expected one of [auto, fixed].
  • brv settings set language.code ko — accepted; rejects unknown codes with the full ISO list in the error.
  • brv settings get language.code — prints current, default, scope like other settings.
  • TUI Settings page → new LANGUAGE group; Enter enters edit mode; Left/Right cycles options; Enter saves; Esc cancels.
  • WebUI Configuration → General → new Language panel (between Task history and Updates) with Select dropdowns. language.code rows show ja — Japanese for readability.
  • brv config set language.mode | language.code now fails with 'language.{mode,code}' has moved to global settings. Run: brv settings set language.{mode,code} <value>. Other project-config keys (none today; dispatcher kept for future use) keep working unchanged.
  • brv curate reads language from daemon settings first, falls back to .brv/config.json for users who haven't migrated — no setting is lost on upgrade.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording

before any changes:
$ brv settings list | grep -i language
(no output — language was invisible to settings UI)

after:

$ brv settings list | grep -i language
language.mode auto (default auto)
language.code en (default en)

$ brv settings set language.mode fixed
Setting saved: language.mode = fixed.

$ brv settings set language.code xx
language.code expected one of [ar, de, el, en, es, fi, fr, he, hi, id, it, ja, ko, nl, no, pl, pt, ru, sv, th, tr, uk, vi, zh], got 'xx'.

$ brv config set language.mode fixed
'language.mode' has moved to global settings. Run: brv settings set language.mode fixed


Full suite:

$ npm test
8869 passing (21s)
16 pending

$ npm run lint
279 problems (0 errors, 279 warnings) # all pre-existing warnings in unrelated test files

$ npm run build
✓ tsc -b
✓ vite build src/webui (built in 7.32s)


## Checklist

- [x] Tests added or updated and passing (`npm test`)
- [x] Lint passes (`npm run lint`)
- [x] Type check passes (`npm run build` covers `tsc -b` + WebUI strict tsc)
- [x] Build succeeds (`npm run build`)
- [x] Commits follow [Conventional Commits](https://www.conventionalcommits.org/) format
- [ ] Documentation updated (if applicable) — `byterover-docs` settings reference still mentions `brv config set language.*`; will update under a docs-only follow-up after merge
- [x] No breaking changes (or clearly documented above) — `brv config set language.*` returns a non-zero exit with a clear pointer; existing `.brv/config.json` values still read at runtime
- [x] Branch is up to date with `proj/language-selection`

## Risks and mitigations

- Risk: existing users who set `brv config set language.code ko` before this lands will see no behavior change at first (good — fallback path still reads `.brv/config.json`), but any *new* change must go through `brv settings set`. If they re-run `brv config set language.*` thinking it still works, they'll get the deprecation error.
  - Mitigation: the deprecation error message names the exact replacement command with the user's same arguments — copy-paste resolves it.
- Risk: the MCP `brv-curate-tool` tool description is still built at server-boot from `buildLanguageClause()` (no args → auto clause). Users who set `language.mode: fixed` via `brv settings set` and then call brv via Claude Code's MCP still see auto behavior.
  - Mitigation: known limitation, documented in `brv-curate-tool.ts:53-57`, exists today on the project-config path too — not a regression. Filed separately as a "make MCP tool description dynamic" follow-up; users wanting fixed mode under MCP should write their prompt to Claude in the target language (per the auto clause's contract).
- Risk: `SettingDescriptor` widening from `Boolean | Integer` to `Boolean | Enum | Integer` is a structural API change for any consumer that did exhaustive narrowing.
  - Mitigation: `tsc -b` catches every consumer at build time; the 4 consumer sites (validator, store, handler, format-settings) were all updated in one commit (`19c9b6b47`) so the widening lands atomic.

cuongdo-byterover and others added 8 commits May 27, 2026 14:40
… in SETTINGS_REGISTRY

Adds EnumSettingDescriptor to the settings type system and registers the
two language settings (mode, code) under category 'language'. The wire
layer (CLI parser, transport DTO, validator, store, handler) now accepts
and round-trips string-valued enum settings end-to-end; TUI and WebUI
renderers will pick them up in follow-up commits.

- SettingDescriptor union grows with EnumSettingDescriptor (options field)
- SettingItem.current / default widen to boolean | number | string
- SettingsItemDTO exposes 'options' on enum-typed items
- brv settings set <enum-key> rejects invalid values with the allowed list
- SettingsValidator.validate routes enum values through validateEnum
- SETTINGS_KEYS gains LANGUAGE_MODE + LANGUAGE_CODE
- registry test: enum narrowing, options shape, language category
- handler test: LIST exposes options, SET rejects non-string to enum keys
Surfaces language.mode / language.code via the existing settings UIs.

- shared/language/language-names.ts: extracted single source of truth for
  the ISO-639-1 → English-name map (was inline in language-clause.ts);
  reused by settings registry, WebUI display labels, and language-clause
- shared SettingsRow / format-settings: adds enum row builder with
  options + display formatting "[ auto ]"
- TUI settings-page: Left/Right cycles options in edit mode; LANGUAGE
  group appears below UPDATES per CATEGORY_ORDER
- WebUI enum-settings-row.tsx: select dropdown driven by descriptor
  options; language.code rows render "ja — Japanese" via LANGUAGE_NAMES
- WebUI LanguagePanel wires into Configuration > General between
  TaskHistoryPanel and UpdatesPanel
- tests: enum row builder + parseRowInput + category ordering
…onfig set language.*

Switches the curate kickoff/continuation language read site from project
config to the daemon settings store, and points users at the new
brv settings set surface for any future change. Project config remains
a fallback for users who still have a per-project override from
ENG-2691 so no one loses their setting on upgrade.

- curate/index.ts: resolveLanguagePreference now calls FileSettingsStore
  first; falls back to ProjectConfigStore for backward compat
- config/set.ts: brv config set language.{mode,code} now fails with a
  clear "moved to brv settings set" message; pure dispatcher unchanged
  so the validation path still covers other project-config keys
…ange + integer parsed.value

Pre-push tsc rejected:
- enum-settings-row: Select.onValueChange signature is (string | null) under
  the component lib's types; the null branch is unreachable for a non-
  cleared single-select but the type system needs an explicit guard.
- settings-row IntegerSettingsRow: parsed.value widened to (number | string)
  after enum support landed; narrow to number before passing to
  toastValue/setMutation so the integer branch keeps compiling under strict.
The oclif settings command kept a duplicate local CATEGORY_ORDER + HEADERS
that wasn't extended when 'language' was added to the canonical shared
registry, so language rows silently dropped from text output even though
the JSON form exposed them. Promote CATEGORY_HEADERS plus a
CATEGORY_ORDER-derived toRowCategory helper into the shared types module
so the oclif and shared-utils formatters consume a single canonical
source. Add a regression test that iterates every category in
CATEGORY_ORDER, gating the broader "new category added but normalizer not
updated" bug class.
`oclif/curate/index.ts` was importing FileSettingsStore + SETTINGS_KEYS
directly from server/ to resolve the language preference, violating
CLAUDE.md's "oclif and tui never import from server" boundary. Route
through SettingsEvents.LIST via withDaemonRetry — same pattern used by
`brv settings get/list`. Add an intent comment on resolveLanguagePreference
explaining the daemon-vs-project-config precedence semantics for the
ENG-2691 → ENG-2974 migration window so a future reader doesn't try to
"fix" the deliberate fallback. Distinguishing "user explicitly set
mode=auto" from "user never touched settings" requires raw-overrides
access the transport doesn't expose today, and the case can't manifest
in production until project-config language ships.
The round-3 transport refactor inlined `'language.mode'` and `'language.code'`
as local string-literal consts in `oclif/commands/curate/index.ts` to avoid the
oclif → server boundary slip. That workaround reproduced the brittleness the
original `SETTINGS_KEYS` constant was designed to prevent — a rename in the
registry would no longer be a typecheck error at the curate call site (and
the same trap existed in two WebUI files).

Move `SETTINGS_KEYS` to `src/shared/types/settings-keys.ts` so every consumer
(server registry / validator / bootstrap, agent, oclif curate, WebUI labels +
enum-settings-row) imports one canonical map. Rename safety is now machine
checked. WebUI consumers that previously inlined the wire keys also switch
to the constant.

Also export `readLanguageFromSettings` with an optional `DaemonClientOptions`
parameter and add three unit tests covering: daemon returns fixed → returns
the language; daemon returns auto → undefined; daemon throws → undefined.
The function was the surface that just changed and silently swallows
daemon errors, so direct coverage is warranted.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 27, 2026

Claude finished @RyanNg1403's task in 7m 0s —— View job


Code Review — feat/ENG-2974 (Settings: language.mode/code via SETTINGS_REGISTRY)

Overall score: 4/5 — solid, well-tested feature work. The discriminated-union widening from Boolean | Integer to Boolean | Enum | Integer is clean, the descriptor narrowing tests demonstrate it compiles correctly, and the wire/store/handler/CLI/TUI/WebUI surfaces are wired symmetrically. The 80% coverage bar is comfortably cleared (registry / handler / format-settings / curate-read / file-store tests). Outside-In and TDD are visibly followed.

Main concern: the kickoff-path daemon round-trip (brv curate without --session) changes the "in-process kickoff" invariant the PR description claims is preserved — see inline comment. The other findings are doc rot / cleanup.


Findings (inline)

# Severity File / Line Topic
1 issue (non-blocking) src/oclif/commands/curate/index.ts:309 Kickoff now blocks on withDaemonRetry (default 10× retries × 1s). Suggest tight retry budget in readLanguageFromSettings.
2 praise + Q src/oclif/commands/curate/index.ts:274–281 Documented precedence quirk — reset-path UX worth confirming.
3 suggestion src/oclif/commands/config/set.ts:56 Examples + args description still advertise the now-deprecated language.* keys.
4 nit src/server/core/domain/render/language-clause.ts:19–21 Re-export + re-import as LANGUAGE_NAMES_LOCAL can collapse to one binding.
5 suggestion src/oclif/commands/settings/get.ts:107 toJsonPayload doesn't surface options for enum descriptors — scripted users can't see allowed values.
6 nit (doc rot) src/server/infra/storage/file-settings-store.ts:135 SettingsFile.values doc still says "set writes only validated numeric values"; restartRequired: true hardcoded on stored SettingItem is dead but now misrepresents boolean/enum keys.

What's good

  • SETTINGS_KEYS lifted to src/shared/types/settings-keys.ts so agent/CLI/TUI/WebUI/server all share one source of truth — exactly the right boundary fix.
  • LANGUAGE_NAMES extracted to src/shared/language/language-names.ts — settings registry, WebUI, language-clause now all consume one map.
  • Type pre-validation in SettingsHandler.checkValueType covers the new 'enum' discriminator (handler test at settings-handler.test.ts:291).
  • EnumSettingDescriptor properly narrows via discriminated union (settings-registry.test.ts:170).
  • WebUI EnumSettingsRow reuses the existing label/restart-banner plumbing and adds the "ja — Japanese" readability touch only for LANGUAGE_CODE.
  • Tests assert both category propagation and the LIST keyset includes language.* (handler test at :86).
  • readLanguageFromSettings is exported with optional DaemonClientOptions so it's testable without monkey-patching — see read-language-from-settings.test.ts.

What was checked but is fine

  • Pre-validation rejects wrong types before the store is touched (settings-handler.test.ts:253–254) — important for not corrupting state.
  • validateCoupling only operates on the numeric subset of proposed values (file-settings-store.ts:118–123), so adding enum keys doesn't break the LLM timeout coupling rule.
  • No security concerns. Enum values are constrained to a closed set, no injection vectors, and the store's atomic temp-file + rename write is untouched.

Checklist

  • Read changed files and analyze diff
  • Review settings registry / enum descriptor changes
  • Review settings handler + validator (transport)
  • Review curate read site + fallback path
  • Review TUI / WebUI rendering
  • Review tests + coverage
  • Post inline comments for issues found
  • Post final summary review
    Branch: feat/ENG-2974

Comment thread src/oclif/commands/config/set.ts
Comment thread src/server/core/domain/render/language-clause.ts Outdated
Comment thread src/oclif/commands/settings/get.ts
Comment thread src/server/infra/storage/file-settings-store.ts
Comment thread src/oclif/commands/curate/index.ts
Comment thread src/oclif/commands/curate/index.ts
…ings PR

Five inline-comment items on PR #724's auto-review against feat/ENG-2974:

1. Tight retry budget on readLanguageFromSettings — kickoff was inheriting
   withDaemonRetry's MAX_RETRIES=10 × 1s default, making every `brv curate`
   block ~9s when the daemon was unreachable before falling back to project
   config. Now passes {maxRetries:1, retryDelayMs:0, ...options} so a
   missing daemon trips the fallback immediately. Test asserts call count
   stays at most 2 (was 10 with the old default; test would have run for
   ~9s, now completes in 6ms).

2. Surface enum options on `brv settings get` — text mode now prints an
   `allowed: a, b, c` line for enum keys; JSON mode includes the `options`
   field. Mirrors how integer keys surface `min`/`max`/`range`. Scripted
   consumers can discover the legal value set without grepping source.

3. Clean stale `brv config set` surface — help text, args description, and
   examples no longer advertise the now-deprecated `language.mode` /
   `language.code` (the command-level interceptor catches those upstream
   with a deprecation-redirect). Examples point users at `brv settings set`
   instead. SETTERS dispatcher emptied with a comment that future
   project-config keys wire in by adding one entry; unreachable
   setLanguageMode / setLanguageCode functions removed; their dispatcher
   tests collapsed to skeleton coverage.

4. Collapse language-clause re-export — replaced the
   `export {LANGUAGE_NAMES} from ...` + `import {... as LANGUAGE_NAMES_LOCAL}`
   double-binding with a single `import {LANGUAGE_NAMES}` directly from the
   canonical shared module. `oclif/commands/config/set.ts` and the
   language-clause test repoint to the same canonical path.

5. file-settings-store — source `restartRequired` from the registry
   descriptor instead of hardcoding `true`. The hardcoded value was dead in
   the transport-routed path (the handler already overrides via the
   descriptor) but misrepresented boolean/enum keys whose descriptors
   carry `restartRequired: false` (update.checkForUpdates, language.mode,
   language.code) for any in-process caller reading the store directly.
   Also refresh the `SettingsFile.values` doc that still said "set writes
   only validated numeric values" — now mentions booleans and enums.

Verification: 8871 npm test passing; ESLint + typecheck + build clean;
3 sub-agents PASS on settings-get / curate-pipeline / config-deprecation
surfaces. Codex round-5 review APPROVED.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 27, 2026

Claude finished @RyanNg1403's task in 3m 48s —— View job


Code Review — feat/ENG-2974 (re-review after follow-ups)

Overall score: 4.5/5 — the previous round's 6 findings are addressed cleanly. One residual doc-rot from finding #1 remains (couldn't post inline — the comment block isn't in the diff). Everything else is shipped or properly deferred.


Status of previous findings

# Prev. Severity Status Notes
1 issue mostly fixed readLanguageFromSettings now defaults {maxRetries: 1, retryDelayMs: 0, ...options} — daemon-down kickoff falls through immediately. New regression test asserts mockConnector.callCount <= 2 (was 10 with the loose default). Residual: the handleKickoff JSDoc at src/oclif/commands/curate/index.ts:237–240 still reads "No daemon connection, no provider check"; kickoff now does a daemon LIST via resolveLanguagePreference → readLanguageFromSettings → withDaemonRetry → connector(...), which still auto-spawns the daemon on the happy path. Functionally fine, but the doc invariant is no longer true. Suggest: "Performs a tight-retry daemon LIST for language; falls back to project-config on failure. No provider check; tool mode never invokes the byterover LLM." Fix this →
2 praise + Q acknowledged The reset-path UX quirk is documented in the comment block at index.ts:272–280 — explicitly marked as migration-window behavior. Acceptable to ship; warning on brv settings reset language.mode is left for the project-config-language sunset.
3 suggestion fixed config/set.ts examples, args description, and SETTERS cleaned up. Setter functions removed; dispatcher tests collapsed to skeleton coverage. Comment explains the dispatcher is now a stub kept for future project-config keys.
4 nit fixed language-clause.ts collapsed to a single import {LANGUAGE_NAMES} from canonical shared path. Re-export dropped (no consumers depended on it). config/set.ts and the language-clause test repoint to the shared path.
5 suggestion fixed settings get now prints allowed: a, b, c in text mode and options in JSON for enum keys, mirroring min/max/range for integers.
6 nit (doc rot) fixed SettingsFile.values doc updated to mention booleans + enums. restartRequired now sourced from the descriptor rather than hardcoded true — boolean (update.checkForUpdates) and enum (language.*) keys with restartRequired: false now correctly reflect that for any in-process caller reading the store directly.

What's good in this round

  • read-language-from-settings.test.ts includes an explicit regression-guard test (callCount <= 2) with a clear comment explaining why the tight default matters. That's the right way to lock in the retry budget against future drift.
  • The doc rewrite on applyConfigSet honestly admits the dispatcher is a stub today — much clearer than the prior "currently only language" framing.
  • Symmetry: enum's allowed: text-mode line and options JSON field mirror the integer min/max/range shape, so scripted consumers learn one pattern, not two.
  • LANGUAGE_NAMES import collapsed cleanly — no leftover alias, no shim.

Tests / build

Per PR description: 8871 passing, 16 pending; lint clean; build succeeds. Spot-checked the new and modified test files — assertions are tight (call-count cap, enum option propagation, deprecation-redirect dispatcher).


Checklist

  • Inspect new commits since previous review (9 commits, latest 0f723c8 addresses 5/6 follow-ups)
  • Verify previous findings 1–6 are addressed (or note if intentionally deferred)
  • Read modified files (curate/index.ts, settings/get.ts, config/set.ts, language-clause.ts, file-settings-store.ts, new test)
  • Post inline comments for any remaining issues — handleKickoff doc isn't in the diff hunks so could not anchor inline; called out in summary above with a Fix-this link
  • Post final summary review

Branch: feat/ENG-2974

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.

2 participants