Skip to content

Conversation

@wangl-cc
Copy link
Member

@wangl-cc wangl-cc commented Jan 6, 2026

Summary by Sourcery

在 CLI 中新增一个 recruit 预设命令,用于配置和运行招募任务,并提供丰富的标签配置和报表选项。

新功能:

  • 引入一个接入主命令分发器的 Recruit CLI 子命令,通过通用的预设运行器来执行招募任务。
  • 新增 RecruitParams 预设配置,支持标签选择/确认、优先标签、额外标签选择模式、运行次数、时间限制、加急许可以及服务器选择。
  • 支持可选地将招募结果上报到外部服务(企鹅物流统计 / Penguin Statistics 和 一图流 / yituliu),并提供可配置的 ID 和服务器映射。

测试:

  • recruit 命令新增全面的 CLI 解析测试,覆盖正常流程、参数组合以及校验错误等情况。
Original summary in English

Summary by Sourcery

Add a new recruit preset command to the CLI for configuring and running recruitment tasks with rich tagging and reporting options.

New Features:

  • Introduce a Recruit CLI subcommand wired into the main command dispatcher to run recruitment tasks via the generic preset runner.
  • Add RecruitParams preset configuration supporting tag selection/confirmation, preferred tags, extra tag selection modes, run count, time limits, expedited permits, and server selection.
  • Support optional reporting of recruitment results to external services (Penguin Statistics and yituliu) with configurable IDs and server mapping.

Tests:

  • Add comprehensive CLI parsing tests for the recruit command covering normal flows, parameter combinations, and validation error cases.

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - 我发现了两个问题,并给出了一些高层次的反馈:

  • into_parameters_no_context 中对 extra_tags_mode 的手动校验可以移动到 clap 的定义中(例如 value_parser = clap::value_parser!(i32).range(0..=2)),这样无效值会在解析阶段就被拒绝,而不是在转换阶段才发现。
  • recruitment_time 的解析循环中,建议使用 splitn(2, '='),并返回更结构化的错误信息(包含 level 和 minutes 部分),以便更容易调试格式错误的输入,并避免当 = 出现多于一次时产生意料之外的行为。
给 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
- The manual validation of `extra_tags_mode` in `into_parameters_no_context` could be shifted into the clap definition (e.g., `value_parser = clap::value_parser!(i32).range(0..=2)`) so invalid values are rejected at parse time rather than at conversion.
- In the `recruitment_time` parsing loop, consider using `splitn(2, '=')` and a more structured error message (including the level and minutes parts) to make debugging malformed inputs easier and avoid surprises if `=` appears more than once.

## Individual Comments

### Comment 1
<location> `crates/maa-cli/src/run/preset/recruit.rs:134-146` </location>
<code_context>
+        params.insert("extra_tags_mode", self.extra_tags_mode);
+
+        // Times
+        params.insert("times", self.times);
+
+        // Set time only when times is 0
</code_context>

<issue_to_address>
**suggestion:** 考虑在将 `times` 插入参数之前,先校验它是否为非负数。

目前任何 `i32` 都会被接受(包括 `--times=-1`),这很可能是无效的,并可能导致下游行为令人困惑。可以加一个简单的检查,比如 `if self.times < 0 { bail!("times must be non-negative") }`,以便在遇到错误输入时尽早失败。

```suggestion
        // Extra tags mode validation
        if !(0..=2).contains(&self.extra_tags_mode) {
            bail!("extra_tags_mode must be between 0 and 2");
        }
        params.insert("extra_tags_mode", self.extra_tags_mode);

        // Times validation
        if self.times < 0 {
            bail!("times must be non-negative");
        }

        // Times
        params.insert("times", self.times);

        // Set time only when times is 0
        if self.times == 0 {
            params.insert("set_time", self.set_time);
        }
```
</issue_to_address>

### Comment 2
<location> `crates/maa-cli/src/run/preset/recruit.rs:160-165` </location>
<code_context>
+        if !self.recruitment_time.is_empty() {
+            let mut time_map = std::collections::BTreeMap::new();
+
+            for time_spec in self.recruitment_time {
+                let mut parts = time_spec.split('=');
+                let level = parts.next();
+                let minutes = parts.next();
+
+                match (level, minutes) {
+                    (Some(level), Some(minutes)) => {
+                        let level_str = level.to_owned();
</code_context>

<issue_to_address>
**suggestion (bug_risk):** `recruitment_time` 解析器会静默忽略多余的 `=` 片段;建议显式拒绝这种格式错误的参数。

由于这里使用的是 `split('=')`,但只读取了两个元素,对类似 `--recruitment-time=3=540=foo` 的输入,解析结果是 `level="3"``minutes="540"`,而尾部的 `"foo"` 会被忽略。如果这不是期望的行为,建议在读取完 `level``minutes` 后检查 `parts.next().is_none()`,否则就返回错误,使格式错误的参数能尽早失败,而不是被部分接受。
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得这些评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据这些反馈来改进评审质量。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • The manual validation of extra_tags_mode in into_parameters_no_context could be shifted into the clap definition (e.g., value_parser = clap::value_parser!(i32).range(0..=2)) so invalid values are rejected at parse time rather than at conversion.
  • In the recruitment_time parsing loop, consider using splitn(2, '=') and a more structured error message (including the level and minutes parts) to make debugging malformed inputs easier and avoid surprises if = appears more than once.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The manual validation of `extra_tags_mode` in `into_parameters_no_context` could be shifted into the clap definition (e.g., `value_parser = clap::value_parser!(i32).range(0..=2)`) so invalid values are rejected at parse time rather than at conversion.
- In the `recruitment_time` parsing loop, consider using `splitn(2, '=')` and a more structured error message (including the level and minutes parts) to make debugging malformed inputs easier and avoid surprises if `=` appears more than once.

## Individual Comments

### Comment 1
<location> `crates/maa-cli/src/run/preset/recruit.rs:134-146` </location>
<code_context>
+        params.insert("extra_tags_mode", self.extra_tags_mode);
+
+        // Times
+        params.insert("times", self.times);
+
+        // Set time only when times is 0
</code_context>

<issue_to_address>
**suggestion:** Consider validating that `times` is non-negative before inserting it into parameters.

Currently any `i32` is accepted (including `--times=-1`), which is probably invalid and may lead to confusing downstream behavior. A small check like `if self.times < 0 { bail!("times must be non-negative") }` would fail fast on bad input.

```suggestion
        // Extra tags mode validation
        if !(0..=2).contains(&self.extra_tags_mode) {
            bail!("extra_tags_mode must be between 0 and 2");
        }
        params.insert("extra_tags_mode", self.extra_tags_mode);

        // Times validation
        if self.times < 0 {
            bail!("times must be non-negative");
        }

        // Times
        params.insert("times", self.times);

        // Set time only when times is 0
        if self.times == 0 {
            params.insert("set_time", self.set_time);
        }
```
</issue_to_address>

### Comment 2
<location> `crates/maa-cli/src/run/preset/recruit.rs:160-165` </location>
<code_context>
+        if !self.recruitment_time.is_empty() {
+            let mut time_map = std::collections::BTreeMap::new();
+
+            for time_spec in self.recruitment_time {
+                let mut parts = time_spec.split('=');
+                let level = parts.next();
+                let minutes = parts.next();
+
+                match (level, minutes) {
+                    (Some(level), Some(minutes)) => {
+                        let level_str = level.to_owned();
</code_context>

<issue_to_address>
**suggestion (bug_risk):** The `recruitment_time` parser silently ignores extra `=` segments; consider rejecting malformed specs explicitly.

Because you call `split('=')` and only read two elements, an input like `--recruitment-time=3=540=foo` is parsed as `level="3"`, `minutes="540"` and the trailing `"foo"` is ignored. If that’s not desired, consider checking `parts.next().is_none()` after reading `level` and `minutes` and returning an error otherwise, so malformed specs fail fast instead of being partially accepted.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +134 to +146
// Extra tags mode validation
if !(0..=2).contains(&self.extra_tags_mode) {
bail!("extra_tags_mode must be between 0 and 2");
}
params.insert("extra_tags_mode", self.extra_tags_mode);

// Times
params.insert("times", self.times);

// Set time only when times is 0
if self.times == 0 {
params.insert("set_time", self.set_time);
}
Copy link

Choose a reason for hiding this comment

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

suggestion: 考虑在将 times 插入参数之前,先校验它是否为非负数。

目前任何 i32 都会被接受(包括 --times=-1),这很可能是无效的,并可能导致下游行为令人困惑。可以加一个简单的检查,比如 if self.times < 0 { bail!("times must be non-negative") },以便在遇到错误输入时尽早失败。

Suggested change
// Extra tags mode validation
if !(0..=2).contains(&self.extra_tags_mode) {
bail!("extra_tags_mode must be between 0 and 2");
}
params.insert("extra_tags_mode", self.extra_tags_mode);
// Times
params.insert("times", self.times);
// Set time only when times is 0
if self.times == 0 {
params.insert("set_time", self.set_time);
}
// Extra tags mode validation
if !(0..=2).contains(&self.extra_tags_mode) {
bail!("extra_tags_mode must be between 0 and 2");
}
params.insert("extra_tags_mode", self.extra_tags_mode);
// Times validation
if self.times < 0 {
bail!("times must be non-negative");
}
// Times
params.insert("times", self.times);
// Set time only when times is 0
if self.times == 0 {
params.insert("set_time", self.set_time);
}
Original comment in English

suggestion: Consider validating that times is non-negative before inserting it into parameters.

Currently any i32 is accepted (including --times=-1), which is probably invalid and may lead to confusing downstream behavior. A small check like if self.times < 0 { bail!("times must be non-negative") } would fail fast on bad input.

Suggested change
// Extra tags mode validation
if !(0..=2).contains(&self.extra_tags_mode) {
bail!("extra_tags_mode must be between 0 and 2");
}
params.insert("extra_tags_mode", self.extra_tags_mode);
// Times
params.insert("times", self.times);
// Set time only when times is 0
if self.times == 0 {
params.insert("set_time", self.set_time);
}
// Extra tags mode validation
if !(0..=2).contains(&self.extra_tags_mode) {
bail!("extra_tags_mode must be between 0 and 2");
}
params.insert("extra_tags_mode", self.extra_tags_mode);
// Times validation
if self.times < 0 {
bail!("times must be non-negative");
}
// Times
params.insert("times", self.times);
// Set time only when times is 0
if self.times == 0 {
params.insert("set_time", self.set_time);
}

Comment on lines +160 to +165
for time_spec in self.recruitment_time {
let mut parts = time_spec.split('=');
let level = parts.next();
let minutes = parts.next();

match (level, minutes) {
Copy link

Choose a reason for hiding this comment

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

suggestion (bug_risk): recruitment_time 解析器会静默忽略多余的 = 片段;建议显式拒绝这种格式错误的参数。

由于这里使用的是 split('='),但只读取了两个元素,对类似 --recruitment-time=3=540=foo 的输入,解析结果是 level="3"minutes="540",而尾部的 "foo" 会被忽略。如果这不是期望的行为,建议在读取完 levelminutes 后检查 parts.next().is_none(),否则就返回错误,使格式错误的参数能尽早失败,而不是被部分接受。

Original comment in English

suggestion (bug_risk): The recruitment_time parser silently ignores extra = segments; consider rejecting malformed specs explicitly.

Because you call split('=') and only read two elements, an input like --recruitment-time=3=540=foo is parsed as level="3", minutes="540" and the trailing "foo" is ignored. If that’s not desired, consider checking parts.next().is_none() after reading level and minutes and returning an error otherwise, so malformed specs fail fast instead of being partially accepted.

@codecov
Copy link

codecov bot commented Jan 6, 2026

Codecov Report

❌ Patch coverage is 93.54839% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.66%. Comparing base (a494efb) to head (a78a00a).

Files with missing lines Patch % Lines
crates/maa-cli/src/run/preset/recruit.rs 95.08% 2 Missing and 1 partial ⚠️
crates/maa-cli/src/main.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #474      +/-   ##
==========================================
+ Coverage   69.40%   69.66%   +0.26%     
==========================================
  Files          56       57       +1     
  Lines        5608     5670      +62     
  Branches     5608     5670      +62     
==========================================
+ Hits         3892     3950      +58     
- Misses       1425     1428       +3     
- Partials      291      292       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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