Skip to content

feat: [ENG-1896] Implement QueryLogUseCase with list and detail views#349

Merged
bao-byterover merged 2 commits intoproj/query-logfrom
feat/ENG-1896
Apr 9, 2026
Merged

feat: [ENG-1896] Implement QueryLogUseCase with list and detail views#349
bao-byterover merged 2 commits intoproj/query-logfrom
feat/ENG-1896

Conversation

@bao-byterover
Copy link
Copy Markdown
Collaborator

Summary

  • Problem: brv query-log view command (ENG-1897) has a stub QueryLogUseCase that produces no output — the use case, entity, and store interface are empty shells.
  • Why it matters: Without the use case implementation, the command is non-functional. Users cannot view query log history, filter by tier/status, or inspect individual entries.
  • What changed: Implemented QueryLogUseCase with list view (aligned table) and detail view (full entry display). Defined QueryLogEntry discriminated union, IQueryLogStore interface, and ITerminal output port. Extracted shared formatting utilities to log-format-utils.ts. Updated FileQueryLogStore stub to satisfy the interface.
  • What did NOT change (scope boundary): No changes to the oclif command (ENG-1897), no store implementation (ENG-1889), no daemon integration. The store still returns empty data — this PR only implements the formatting/presentation layer.

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-1896
  • Related ENG-1897, ENG-1887, ENG-1888, ENG-1889

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

N/A

Test plan

  • Coverage added:
    • Unit test
    • Integration test
    • Manual verification only
  • Test file(s): test/unit/infra/usecase/query-log-use-case.test.ts
  • Key scenario(s) covered:
    • List view: aligned table, empty states (with/without filters), query truncation (40 chars), JSON output, filter passthrough (tier, status, after/before, limit), error entry shows "—" for Time
    • Detail view: all fields for completed entry, error message display, not-found message, duration formatting (ms/<1s, seconds/>=1s, dash/missing), matched docs with scores, response truncation (500 chars)
    • 16 test cases total (15 from ticket + 1 for error entry list view duration)

User-visible changes

None yet — FileQueryLogStore is still a stub returning empty data. Once the store is implemented (ENG-1889), brv query-log view will display:

  • List view: Table with ID, Tier badge (T0-T4), Status, Duration, Query (truncated)
  • Detail view: Full entry with tier label, timestamps, duration, query, matched docs with scores, search metadata, response (truncated)
  • JSON mode: Machine-parseable output with command, success, data, retrievedAt fields

Evidence

  • Failing test/log before + passing after

Before (stub): 16 failing, 0 passing
After (implementation): 0 failing, 16 passing

  QueryLogUseCase
    list mode
      ✔ should show aligned table with ID, Tier, Status, Time, Query columns
      ✔ should show empty state message when no entries
      ✔ should show filter-specific empty message when no entries match filters
      ✔ should truncate query text at 40 chars in list view
      ✔ should output valid JSON with all fields
      ✔ should pass tier filter to store.list
      ✔ should pass status filter to store.list
      ✔ should pass after and before filters to store.list
      ✔ should pass limit to store.list
      ✔ should show dash for Time column on error entries
    detail mode
      ✔ should show all fields for completed entry
      ✔ should show error message for error entry
      ✔ should show not-found message for non-existent ID
      ✔ should format duration correctly: ms for <1s, seconds for >=1s, dash for missing
      ✔ should display matched docs with scores in detail view
      ✔ should truncate response at 500 chars in detail view

  16 passing (10ms)

Checklist

  • Tests added or updated and passing (npm test)
  • Lint passes (npm run lint)
  • Type check passes (npm run typecheck)
  • Build succeeds (npm run build)
  • Commits follow Conventional Commits format
  • Documentation updated (if applicable)
  • No breaking changes (or clearly documented above)
  • Branch is up to date with main

Risks and mitigations

  • Risk: QueryLogEntry discriminated union fields may not match what FileQueryLogStore (ENG-1889) actually persists.
    • Mitigation: Entity was designed Outside-In from the use case's display needs. The store implementation will validate with Zod against this same shape. Any mismatch will surface as type errors at compile time.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

Claude finished @bao-byterover's task in 2m 31s —— View job


PR Review: feat: [ENG-1896] Implement QueryLogUseCase

  • Gather context
  • Read changed files
  • Review code quality, correctness, and security
  • Post inline comments
  • Post overall summary

Overall Assessment: 4 / 5

Well-executed presentation layer. TDD is clearly followed (16 tests, all pass), Outside-In approach is correctly applied (store interface derived from use case needs, not the other way around), and the scope boundary is properly enforced — no store implementation, no oclif changes.

No security issues. The code is output-only, all data comes from a trusted local store, and JSON.stringify is used safely.


Issues Found

Severity Location Summary
issue log-format-utils.ts:11 formatEntryDuration returns for error/cancelled in list, but showDetail uses formatDuration directly — list and detail views are asymmetric for non-completed statuses
issue query-log-use-case.test.ts:196 No test for cancelled entry — the only status variant not covered in list or detail view
suggestion query-log-use-case.ts:31–49 run redeclares options inline instead of using Parameters<IQueryLogUseCase['run']>[0] — drift risk if interface changes
nitpick query-log-use-case.ts:179 detail flag in list mode only shows matched docs; parameter name implies broader expansion
nitpick query-log-use-case.ts:164 Separator +8 magic number is correct but unexplained

Highlights

  • QueryLogEntry discriminated union (query-log-entry.ts) is cleanly modeled — processing correctly omits completedAt, cancelled and error carry it. The as const pattern for tiers/statuses with derived types is a good pattern.
  • ITerminal output port is the right call — decouples the use case from console and makes it trivially testable without sinon stubs.
  • Filter passthrough tests (Tests 6–9) correctly assert on the exact object passed to store.list, not just the output — this is the right level of assertion for a use case.
  • FileQueryLogStore stub uses Parameters<IQueryLogStore['list']>[0] to stay structurally in sync with the interface — solid pattern for a temporary stub.
  • log-format-utils.ts utilities are pure functions with no side effects, making them trivially testable if needed later.

Fix list/detail duration asymmetry →

}

export function formatEntryDuration(entry: QueryLogEntry): string {
if (entry.status !== 'completed') return '—'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (design inconsistency): formatEntryDuration returns '—' for error entries in the list view, but showDetail calls formatDuration(entry.startedAt, entry.completedAt) directly (line 88 of the use case), which will show the real duration for cancelled and error entries in detail view.

An error entry that took 2s shows in the list but 2.0s in the detail. This asymmetry is tested/documented ("error entry shows '—' for Time"), but it's a design trap — if a future maintainer adds 'cancelled' or 'error' to the condition, the list and detail views will still diverge because they use different code paths.

Consider either:

  1. Keeping the intentional for non-completed entries in both views (update showDetail to use formatEntryDuration too), or
  2. Accepting the asymmetry and documenting it explicitly here.
Suggested change
if (entry.status !== 'completed') return '—'
export function formatEntryDuration(entry: QueryLogEntry): string {
if (entry.status !== 'completed') return '—'
return formatDuration(entry.startedAt, entry.completedAt)
}

Comment on lines +31 to +49
async run({
after,
before,
detail = false,
format = 'text',
id,
limit = 10,
status,
tier,
}: {
after?: number
before?: number
detail?: boolean
format?: 'json' | 'text'
id?: string
limit?: number
status?: QueryLogStatus[]
tier?: QueryLogTier[]
}): Promise<void> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (DRY): The run method redeclares the full options type inline, duplicating the IQueryLogUseCase interface. If the interface gains or drops a parameter, this must be updated separately — and since TypeScript structural typing won't catch a missing optional param, drift could go unnoticed.

Suggested change
async run({
after,
before,
detail = false,
format = 'text',
id,
limit = 10,
status,
tier,
}: {
after?: number
before?: number
detail?: boolean
format?: 'json' | 'text'
id?: string
limit?: number
status?: QueryLogStatus[]
tier?: QueryLogTier[]
}): Promise<void> {
async run(options: Parameters<IQueryLogUseCase['run']>[0]): Promise<void> {
const {
after,
before,
detail = false,
format = 'text',
id,
limit = 10,
status,
tier,
} = options
await (id
? this.showDetail(id, format)
: this.showList({after, before, detail, format, limit, status, tier}))
}

].join(' ')
this.log(row)

if (detail && entry.status === 'completed' && entry.matchedDocs.length > 0) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: detail mode in list view only inlines matched docs — the parameter name implies general detail expansion (timestamps, error messages, etc.) but the implementation is narrower. This could confuse callers who pass detail: true expecting richer output for error or cancelled rows.

If the scope is intentionally "matched docs only for now", consider either:

  • Renaming to showMatchedDocs for clarity, or
  • Adding a // TODO(ENG-XXXX): expand detail to include error messages and timestamps comment documenting the intentional limitation.

].join(' ')

this.log(header)
this.log('─'.repeat(idWidth + tierWidth + statusWidth + timeWidth + queryWidth + 8))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: The separator width calculation (idWidth + tierWidth + statusWidth + timeWidth + queryWidth + 8) is correct (4 separators × 2 spaces = 8), but queryWidth isn't actually used in the separator — the Query column can overflow queryWidth if a padded entry is longer. This works fine today since the last column isn't padded, but a comment explaining the +8 would help future column additions.

Suggested change
this.log('─'.repeat(idWidth + tierWidth + statusWidth + timeWidth + queryWidth + 8))
this.log('─'.repeat(idWidth + tierWidth + statusWidth + timeWidth + queryWidth + 8)) // +8 = 4 columns × 2-space separator

expect(output).to.include('T2')
expect(output).to.include('error')
expect(output).to.include('—')
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (coverage gap): The cancelled status variant is part of the discriminated union and has its own unique shape (completedAt present, no error, no matchedDocs), but there's no test for how a cancelled entry renders in either list or detail view.

In the list view, formatEntryDuration returns '—' for cancelled (same as error). In detail view, the entry.status !== 'processing' guard will show Finished and Duration for a cancelled entry — but no test verifies this. Adding a makeCancelledEntry helper and at least one list + one detail test would complete the coverage for the discriminated union.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

Claude finished @bao-byterover's task in 2m 20s —— View job


PR Review: feat: [ENG-1896] Implement QueryLogUseCase

  • Gather context
  • Read changed files and new commits
  • Review code quality, correctness, and security
  • Post inline comments
  • Post overall summary

Overall Assessment: 4.5 / 5

The "Fix review" commit (0315068) properly addresses both critical issues from the previous round. The remaining items are a suggestion and two nitpicks — all carried over and still open.

No security issues. Output-only code with no user-controlled format strings or injection vectors.


What Was Fixed in the Latest Commit

Previous Severity Issue Status
issue List/detail duration asymmetry for error/cancelled entries ✅ Fixed
issue No test for cancelled status variant ✅ Fixed

Duration fix (query-log-use-case.ts:86–92): showDetail now correctly splits the two concerns — Finished: is shown for any non-processing entry (because completedAt is available), but Duration: is only shown for completed entries using formatEntryDuration. List and detail views are now symmetric.

Cancelled coverage: makeCancelledEntry() helper added, plus three new test cases:

  • List view: cancelled shows for Time column
  • Detail view: cancelled shows Finished: but not Duration:
  • Detail view: error shows Finished: but not Duration: (bonus)

Total test count is now 19 (up from 16).


Still Open

Severity Location Summary
suggestion query-log-use-case.ts:31–49 run redeclares the full options type inline — use Parameters<IQueryLogUseCase['run']>[0] to avoid drift
nitpick query-log-use-case.ts:167 +8 magic number is correct but unexplained
nitpick query-log-use-case.ts:182 detail flag only expands matched docs in list mode — narrower than the name implies, worth a comment

Highlights

  • The duration asymmetry was resolved cleanly without overcomplicating the guard logic — two separate if blocks is more readable than a combined condition.
  • formatDuration remains correctly used as a building block inside formatEntryDuration; it is not orphaned.
  • FileQueryLogStore uses Parameters<IQueryLogStore['list']>[0] to stay in sync — same pattern that the suggestion above proposes for QueryLogUseCase.run.

limit?: number
status?: QueryLogStatus[]
tier?: QueryLogTier[]
}): Promise<void> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (DRY — still open from previous review): The run method's parameter type is still declared inline, duplicating IQueryLogUseCase['run']'s signature. If the interface gains a new option, this must be updated separately and TypeScript won't flag a missing optional param.

Suggested change
}): Promise<void> {
async run(options: Parameters<IQueryLogUseCase['run']>[0]): Promise<void> {
const {
after,
before,
detail = false,
format = 'text',
id,
limit = 10,
status,
tier,
} = options

].join(' ')

this.log(header)
this.log('─'.repeat(idWidth + tierWidth + statusWidth + timeWidth + queryWidth + 8))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (still open from previous review): The +8 magic number is correct (4 column separators × 2 spaces each), but unexplained. A future maintainer adding a column will likely copy the pattern without understanding why.

Suggested change
this.log('─'.repeat(idWidth + tierWidth + statusWidth + timeWidth + queryWidth + 8))
this.log('─'.repeat(idWidth + tierWidth + statusWidth + timeWidth + queryWidth + 8)) // +8 = 4 column separators × 2 spaces

].join(' ')
this.log(row)

if (detail && entry.status === 'completed' && entry.matchedDocs.length > 0) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (still open from previous review): detail: true in list mode only expands matched docs. The parameter name implies general detail expansion (timestamps, error messages, etc.) but the implementation is narrower. Consider a comment to document the intentional scope limit:

Suggested change
if (detail && entry.status === 'completed' && entry.matchedDocs.length > 0) {
if (detail && entry.status === 'completed' && entry.matchedDocs.length > 0) { // detail: inline matched docs only

@bao-byterover bao-byterover merged commit 09a760c into proj/query-log Apr 9, 2026
6 checks passed
@bao-byterover bao-byterover deleted the feat/ENG-1896 branch April 11, 2026 16:35
bao-byterover added a commit that referenced this pull request Apr 16, 2026
* feat: [ENG-1897] Create brv query-log view oclif command (#347)

* feat: [ENG-1897] Create brv query-log view oclif command

* feat: [ENG-1897] fix review

* feat: [ENG-1896] Implement QueryLogUseCase with list and detail views (#349)

* feat: [ENG-1896] Implement QueryLogUseCase with list and detail views

* feat: [ENG-1896] Fix review

* feat: [ENG-1888] Define IQueryLogStore interface (#351)

* feat: [ENG-1887] Define QueryLogEntry entity with discriminated union (#353)

* feat: [ENG-1889] Implement FileQueryLogStore with Zod validation (#354)

* feat: [ENG-1889] Implement FileQueryLogStore with Zod validation

* feat: [ENG-1889] fix review

* feat: [ENG-1899] Create brv query-log summary oclif command (#357)

* feat: [ENG-1899] Create brv query-log summary oclif command

* feat: [ENG-1899] fix review

* feat: [ENG-1898] Implement QueryLogSummaryUseCase (#368)

* feat: [ENG-1892] Add QueryExecutorResult type with tier and timing metadata (#369)

* feat: [ENG-1893] Wire QueryLogHandler into daemon lifecycle (#370)

* feat: [ENG-1894] Wire QueryLogHandler into daemon lifecycle (#371)

* feat: [ENG-1894] enhance message output

* feat: [proj/query-log] fix review

* feat: [ENG-2123] brv curate view / brv query-log view truncate long c… (#424)

* feat: [ENG-2123] brv curate view / brv query-log view truncate long content

* feat: [ENG-2123] fix review

* feat: [ENG-2177] Increase Storage Limit for Curate Log and Query Log (#430)

* feat: [ENG-2177] Increase Storage Limit for Curate Log and Query Log

* feat: [ENG-2177] fix review

* feat: [ENG-2177] fix slow test

---------

Co-authored-by: Hoang Pham <lehoangpham1092@gmail.com>
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