Skip to content

Add question bank merge support#29

Merged
ModerRAS merged 2 commits into
masterfrom
codex/question-bank-merge
May 29, 2026
Merged

Add question bank merge support#29
ModerRAS merged 2 commits into
masterfrom
codex/question-bank-merge

Conversation

@ModerRAS

@ModerRAS ModerRAS commented May 29, 2026

Copy link
Copy Markdown
Owner

Summary\n- add multi-file question bank merge with optional deduplication\n- expose merge flow in CLI, MCP, and Avalonia UI\n- extend source detection to KSB and MTB exports\n\n## Verification\n- dotnet test ModerBox.QuestionBank.Test/ModerBox.QuestionBank.Test.csproj\n- dotnet test ModerBox.Cli.Test/ModerBox.Cli.Test.csproj\n- dotnet build ModerBox.MCP/ModerBox.MCP.csproj\n- dotnet build ModerBox/ModerBox.csproj

Summary by CodeRabbit

Release Notes

  • New Features

    • Users can now merge multiple question bank source files into a single output file with automatic format detection
    • Added optional deduplication feature when merging to remove duplicate questions
    • CLI question-bank merge command with support for multiple sources, customizable target format, and deduplication control
    • Enhanced UI to support multi-file selection and merging workflows
  • Documentation

    • Updated command-line and API documentation with merge examples and usage instructions

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@ModerRAS, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 33 minutes and 13 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b1675b97-6c09-4462-90f5-6ee03bb01d27

📥 Commits

Reviewing files that changed from the base of the PR and between 692488e and 42dbdcd.

📒 Files selected for processing (3)
  • ModerBox.Cli.Test/CommandTests.cs
  • ModerBox.Cli/README.md
  • ModerBox/ViewModels/QuestionBankConversionViewModel.cs
📝 Walkthrough

Walkthrough

This PR adds comprehensive multi-file question bank merge functionality. The core service layer implements format detection for Ksb and Mtb Excel formats, multi-file reading with optional deduplication, and merge APIs. The CLI exposes a qb merge subcommand with configurable options. The ViewModel and UI support multi-file selection with dynamic labels and an optional deduplication checkbox. Tests cover merge behavior with and without deduplication and format detection. Documentation describes the feature across CLI, service library, and project README.

Changes

Question Bank Merge Feature

Layer / File(s) Summary
Question bank merge service and format detection
ModerBox.QuestionBank/Services/QuestionBankConversionService.cs
Enum extended with Ksb and Mtb source formats. Read method validates file existence. Excel auto-detection prioritizes Ksb/Mtb by checking worksheet headers. New ReadAndMerge API aggregates and optionally deduplicates questions across multiple source files. New Merge API wraps ReadAndMerge and writes output. Deduplication normalizes question text and removes option prefixes before key generation. New public record types (QuestionBankMergeSourceSummary, QuestionBankMergeReadResult, QuestionBankMergeSummary) expose merge results.
CLI merge subcommand and execution
ModerBox.Cli/Commands/QuestionBankCommand.cs
CreateMergeCommand wires qb merge with options for --sources (semicolon-separated), --target, --source-format, --target-format, and --deduplicate. ExecuteMergeAsync handler expands source arguments, validates file existence, invokes QuestionBankConversionService.Merge, and reports results as JSON or formatted text. ExpandSourceArgs splits and normalizes semicolon-separated source paths.
MCP merge tool and result types
ModerBox.MCP/Tools/QuestionBankTools.cs
MergeQuestionBanks MCP tool validates source paths and invokes the service merge API, populating QuestionBankMergeResult with counts and per-source metadata. New DTOs (QuestionBankMergeResult, QuestionBankMergeSourceResult) structure the result.
ViewModel multi-file support and merge logic
ModerBox/ViewModels/QuestionBankConversionViewModel.cs
SourceFile now maintains an internal list of parsed source paths. New properties HasMultipleSourceFiles, SourceFileLabel, RunButtonText, and DeduplicateWhenMerging drive merge-specific UI behavior and labels. SelectSourceFileTask refactored to support multi-file picking and update internal paths; auto-detection applied only for single-file selection. RunConversionTask branched to handle merge vs single conversion, using ReadAndMerge for merge scenarios and updating status with file/question/duplicate counts. File picker helpers refactored to support multi-select and target filename inference.
XAML UI controls for merge mode
ModerBox/Views/UserControls/QuestionBankConversion.axaml
Source TextBox enhanced with multi-line support and wrapping. New deduplication CheckBox shown only when multiple source files are selected. Button text switched to dynamic RunButtonText binding. InfoBar documentation expanded with merge capability.
Merge functionality tests
ModerBox.Cli.Test/CommandTests.cs, ModerBox.QuestionBank.Test/QuestionBankServiceTests.cs
CLI tests verify merge subcommand existence and end-to-end merge with output validation. Service tests cover merge with deduplication enabled (validates duplicate counting and output), merge with deduplication disabled (validates duplicates are retained), and auto-detection of Ksb/Mtb formats for exported XLSX files.
Documentation updates
ModerBox.Cli/README.md, ModerBox.QuestionBank/README.md, README.md
CLI README documents qb merge usage with examples and option descriptions. QuestionBank README adds supported source formats, UI merge-mode steps, and code example using Merge API. Root README updates command reference to show merge workflow and clarifies GPL-3.0 license.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


🐰 A merge of many files so neat,
Deduplication makes it complete,
With formats detected with care,
Question banks merge everywhere!
From CLI to UI, the feature's so sweet!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main change: adding merge support to the question bank feature, which is the primary objective across all modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/question-bank-merge

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented May 29, 2026

Copy link
Copy Markdown

Code Coverage Report

  • Minimum Required Coverage: 50%
  • Current Coverage: 62.77%

📄 View Full Report

🚀 Keep up the good work!

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ModerBox.Cli/Commands/QuestionBankCommand.cs (1)

40-46: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix duplicate option aliases between question-bank and merge.

question-bank defines required options --source/-s and --target/-t, while the merge subcommand also defines --sources/-s and --target/-t. In System.CommandLine, option aliases must be unique in the command tree—redeclaring the same aliases leads to parser/grammar validation errors (e.g., “Alias '--duplicate' is already in use.”). Moving the convert-only options off the parent (so the parent is a container, with the conversion flow behind its own subcommand) removes the alias collisions and keeps merge unambiguous.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ModerBox.Cli/Commands/QuestionBankCommand.cs` around lines 40 - 46, The
parent Command("question-bank") currently registers convert-specific options
(sourceOption, targetOption, sourceFormatOption, targetFormatOption) which
conflict with the merge subcommand's aliases; remove these options from the
parent and instead create a dedicated "convert" subcommand (e.g.,
Command("convert") or "convert-to") that registers sourceOption, targetOption,
sourceFormatOption and targetFormatOption, then call
command.AddCommand(CreateMergeCommand()) and command.AddCommand(convertCommand)
so the parent acts only as a container and alias collisions with
CreateMergeCommand() are avoided.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ModerBox.Cli/README.md`:
- Around line 125-133: The fenced code block containing the command starting
with "qb merge --sources" is missing a language identifier; update the opening
fence from ``` to ```bash so the block becomes a bash code block (i.e., add
"bash" after the opening backticks) to satisfy markdownlint and enable proper
syntax highlighting for the snippet.

In `@ModerBox/ViewModels/QuestionBankConversionViewModel.cs`:
- Around line 412-413: The call to _llmService.GenerateAnalysisAsync currently
passes sourcePaths[0] which becomes the persisted progress key; when isMerge is
true this can collide with single-file runs or other merges. Change the call to
compute a deterministic merge-specific key (e.g., hash or join of the full
sourcePaths list) when isMerge==true and pass that key instead of
sourcePaths[0]; update any variable passed to GenerateAnalysisAsync so
GenerateAnalysisAsync receives the full-source merge key for merged runs while
non-merge runs continue to pass the single-file key.

---

Outside diff comments:
In `@ModerBox.Cli/Commands/QuestionBankCommand.cs`:
- Around line 40-46: The parent Command("question-bank") currently registers
convert-specific options (sourceOption, targetOption, sourceFormatOption,
targetFormatOption) which conflict with the merge subcommand's aliases; remove
these options from the parent and instead create a dedicated "convert"
subcommand (e.g., Command("convert") or "convert-to") that registers
sourceOption, targetOption, sourceFormatOption and targetFormatOption, then call
command.AddCommand(CreateMergeCommand()) and command.AddCommand(convertCommand)
so the parent acts only as a container and alias collisions with
CreateMergeCommand() are avoided.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dc770182-a949-42d1-b34f-f06f7c835860

📥 Commits

Reviewing files that changed from the base of the PR and between 524b78b and 692488e.

📒 Files selected for processing (10)
  • ModerBox.Cli.Test/CommandTests.cs
  • ModerBox.Cli/Commands/QuestionBankCommand.cs
  • ModerBox.Cli/README.md
  • ModerBox.MCP/Tools/QuestionBankTools.cs
  • ModerBox.QuestionBank.Test/QuestionBankServiceTests.cs
  • ModerBox.QuestionBank/README.md
  • ModerBox.QuestionBank/Services/QuestionBankConversionService.cs
  • ModerBox/ViewModels/QuestionBankConversionViewModel.cs
  • ModerBox/Views/UserControls/QuestionBankConversion.axaml
  • README.md

Comment thread ModerBox.Cli/README.md Outdated
Comment on lines +87 to +148
[McpServerTool, Description("Merge multiple question bank files into one output file. Supports auto-detection per source file and optional duplicate removal.")]
public static QuestionBankMergeResult MergeQuestionBanks(
[Description("Source question bank file paths to merge")] string[] sourcePaths,
[Description("Target output file path")] string targetPath,
[Description("Target format (Ksb, Mtb, Wldx, Wldx4, Xiaobao, XiaobaoTxt)")] QuestionBankTargetFormat targetFormat,
[Description("Source format (AutoDetect, Txt, Ksb, Mtb, Wldx, Wldx4, Exc, Gdpx, Simple). If not specified, each source file will be auto-detected.")] QuestionBankSourceFormat? sourceFormat = null,
[Description("Remove duplicate questions while merging. Defaults to true.")] bool deduplicate = true)
{
var result = new QuestionBankMergeResult
{
SourcePaths = sourcePaths,
TargetPath = targetPath,
TargetFormat = targetFormat.ToString()
};

try
{
if (sourcePaths.Length == 0)
{
result.Success = false;
result.ErrorMessage = "At least one source file is required.";
return result;
}

var missing = sourcePaths.FirstOrDefault(path => !File.Exists(path));
if (missing is not null)
{
result.Success = false;
result.ErrorMessage = $"Source file does not exist: {missing}";
return result;
}

var service = new QuestionBankConversionService();
var sourceFmt = sourceFormat ?? QuestionBankSourceFormat.AutoDetect;
var title = Path.GetFileNameWithoutExtension(targetPath);

var summary = service.Merge(sourcePaths, targetPath, sourceFmt, targetFormat, deduplicate, title);

result.Success = true;
result.SourceFileCount = summary.SourceFileCount;
result.TotalQuestionCount = summary.TotalQuestionCount;
result.DuplicateQuestionCount = summary.DuplicateQuestionCount;
result.OutputQuestionCount = summary.OutputQuestionCount;
result.TargetFormat = summary.TargetFormat.ToString();
result.OutputPath = summary.TargetPath;
result.Sources = summary.Sources
.Select(source => new QuestionBankMergeSourceResult
{
SourcePath = source.SourcePath,
SourceFormat = source.SourceFormat.ToString(),
QuestionCount = source.QuestionCount
})
.ToArray();
}
catch (Exception ex)
{
result.Success = false;
result.ErrorMessage = ex.Message;
}

return result;
}

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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the target file
ls -la ModerBox.MCP/Tools/QuestionBankTools.cs

# Show the relevant section with line numbers
sed -n '1,220p' ModerBox.MCP/Tools/QuestionBankTools.cs | nl -ba | sed -n '60,190p'

# Search for async tool methods in same file and attribute usage patterns
rg -n --context 2 "\[McpServerTool" ModerBox.MCP/Tools/QuestionBankTools.cs
rg -n "public (static )?(async )?" ModerBox.MCP/Tools/QuestionBankTools.cs

# Check if any other tool methods in ModerBox.MCP/Tools are async-first (Task-returning)
rg -n "Task<.*>\s+.*\(" ModerBox.MCP/Tools -S --glob '*.cs'
rg -n "async\s+Task<" ModerBox.MCP/Tools -S --glob '*.cs'

Repository: ModerRAS/ModerBox

Length of output: 211


🏁 Script executed:

#!/bin/bash
set -euo pipefail

ls -la ModerBox.MCP/Tools/QuestionBankTools.cs

# Show method region around the reported lines
sed -n '1,240p' ModerBox.MCP/Tools/QuestionBankTools.cs | nl -ba | sed -n '70,170p'

# Confirm current signature (sync vs Task) and attribute usage
rg -n "MergeQuestionBanks" ModerBox.MCP/Tools/QuestionBankTools.cs
rg -n "\[McpServerTool" ModerBox.MCP/Tools/QuestionBankTools.cs

# Check for async-first patterns elsewhere in MCP tools
rg -n "async\s+Task<" ModerBox.MCP/Tools --glob '*.cs'
rg -n "Task<[^>]+>\s+public.*\(" ModerBox.MCP/Tools --glob '*.cs'
rg -n "public\s+static\s+[A-Za-z0-9_<>]+\s+MergeQuestionBanks\s*\(" ModerBox.MCP/Tools --glob '*.cs'

Repository: ModerRAS/ModerBox

Length of output: 211


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Confirm file exists
ls -la ModerBox.MCP/Tools/QuestionBankTools.cs

# Show the reported region with line numbers (no nl available)
cat -n ModerBox.MCP/Tools/QuestionBankTools.cs | sed -n '70,180p'

# Locate MergeQuestionBanks definition + attributes
rg -n "MergeQuestionBanks" ModerBox.MCP/Tools/QuestionBankTools.cs
rg -n "\\[McpServerTool" ModerBox.MCP/Tools/QuestionBankTools.cs
rg -n "Description\\(" ModerBox.MCP/Tools/QuestionBankTools.cs

# Scan other MCP tools for async-first patterns (Task-returning / async Task)
rg -n --glob '*.cs' "async\s+Task<" ModerBox.MCP/Tools
rg -n --glob '*.cs' "Task<[^>]+>\s+public\s+.*\(" ModerBox.MCP/Tools
rg -n --glob '*.cs' "public\s+static\s+.*\s+MergeQuestionBanks\s*\(" ModerBox.MCP/Tools

Repository: ModerRAS/ModerBox

Length of output: 9223


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find QuestionBankConversionService definition
rg -n "class QuestionBankConversionService" -S . --glob '*.cs'
rg -n "QuestionBankConversionService" ModerBox.MCP/Tools/QuestionBankTools.cs

# Locate Merge implementation
rg -n "Merge\s*\(" -S . --glob '*.cs' | rg "QuestionBankConversionService|MergeQuestion" || true
rg -n "QuestionBankConversionService\s*\.\s*Merge|service\.Merge" -S . --glob '*.cs'

# Print the Merge method implementation if found
rg -n "MergeQuestionBanks|Merge\(" -S . --glob '*.cs' --files-with-matches | head -n 50

Repository: ModerRAS/ModerBox

Length of output: 1809


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="ModerBox.QuestionBank/Services/QuestionBankConversionService.cs"

# Show the Merge method + any related async patterns around it
sed -n '180,360p' "$file" | cat -n

# Search within the service file for async keywords
rg -n "async\s+Task|Task<|ValueTask" "$file"

Repository: ModerRAS/ModerBox

Length of output: 7946


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="ModerBox.MCP/Tools/QuestionBankTools.cs"
# Show top of file with attributes/class/method signatures
sed -n '1,120p' "$file" | cat -n

# Show any other tool method signatures in this file
rg -n "\\[McpServerTool" "$file"
rg -n "public\\s+static\\s+(async\\s+)?(Task<)?\\s*[^\\s]+\\s+\\w+\\s*\\(" "$file"

Repository: ModerRAS/ModerBox

Length of output: 6483


Make MergeQuestionBanks async to avoid blocking MCP threads.

In ModerBox.MCP/Tools/QuestionBankTools.cs (lines ~87-148), MergeQuestionBanks is synchronous (public static QuestionBankMergeResult ...) and does full disk I/O + merge/write inline via QuestionBankConversionService.Merge(...). Other MCP tools here use public static async Task<...>, so update this tool to return Task<QuestionBankMergeResult> and implement async-first execution (make the service async or offload the work with await).

Also, ConvertQuestionBank and DetectQuestionBankFormat in the same file are currently synchronous and likely need the same async-first treatment to match the guideline.

Comment thread ModerBox/ViewModels/QuestionBankConversionViewModel.cs Outdated
@ModerRAS ModerRAS merged commit b8ba045 into master May 29, 2026
2 checks passed
@ModerRAS ModerRAS deleted the codex/question-bank-merge branch May 29, 2026 02:05
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